diff --git a/docs/sphinx/api/languages/cpp_api.rst b/docs/sphinx/api/languages/cpp_api.rst index 34487dbefb..b4c12fac2b 100644 --- a/docs/sphinx/api/languages/cpp_api.rst +++ b/docs/sphinx/api/languages/cpp_api.rst @@ -223,6 +223,53 @@ Utilities .. doxygentypedef:: cudaq::real .. doxygenfunction:: cudaq::range(std::size_t) + +Dynamics +========= + +.. .. doxygenclass:: cudaq::EvolveResult + :members: + +.. .. doxygenclass:: cudaq::AsyncEvolveResult + :members: + +.. doxygenclass:: cudaq::operator_sum + :members: + +.. doxygenclass:: cudaq::product_operator + :members: + +.. doxygenclass:: cudaq::scalar_operator + :members: + +.. doxygenclass:: cudaq::elementary_operator + :members: + +.. doxygenclass:: cudaq::OperatorArithmetics + :members: + +.. doxygenclass:: cudaq::MatrixArithmetics + :members: + +.. doxygenclass:: cudaq::Schedule + :members: + +.. doxygenclass:: cudaq::operators + :members: + +.. doxygenclass:: cudaq::pauli + :members: + +.. .. doxygenfunction:: cudaq::evolve(Operator hamiltonian, std::map dimensions, Schedule schedule, bool store_intermediate_states) + +.. .. doxygenfunction:: cudaq::evolve(Operator hamiltonian, std::map dimensions, Schedule schedule, std::vector collapse_operators, std::vector observables, bool store_intermediate_states) + +.. .. doxygenfunction:: cudaq::evolve(Operator hamiltonian, std::map dimensions, Schedule schedule, state initial_state, std::vector collapse_operators, std::vector observables, bool store_intermediate_states) + +.. .. doxygenfunction:: cudaq::evolve(Operator hamiltonian, std::map dimensions, Schedule schedule, std::vector initial_states, std::vector collapse_operators, std::vector observables, bool store_intermediate_states) + +.. .. doxygenfunction:: cudaq::evolve_async + Namespaces =========== diff --git a/runtime/cudaq/CMakeLists.txt b/runtime/cudaq/CMakeLists.txt index a4eac4f89d..d2f23bd0f9 100644 --- a/runtime/cudaq/CMakeLists.txt +++ b/runtime/cudaq/CMakeLists.txt @@ -40,7 +40,7 @@ if (CUDA_FOUND) PRIVATE .) target_link_libraries(${LIBRARY_NAME} - PUBLIC dl cudaq-spin cudaq-common cudaq-nlopt cudaq-ensmallen + PUBLIC dl cudaq-spin cudaq-operators cudaq-common cudaq-nlopt cudaq-ensmallen PRIVATE nvqir fmt::fmt-header-only CUDA::cudart_static) target_compile_definitions(${LIBRARY_NAME} PRIVATE CUDAQ_HAS_CUDA) @@ -52,7 +52,7 @@ else() PRIVATE .) target_link_libraries(${LIBRARY_NAME} - PUBLIC dl cudaq-spin cudaq-common cudaq-nlopt cudaq-ensmallen + PUBLIC dl cudaq-spin cudaq-operators cudaq-common cudaq-nlopt cudaq-ensmallen PRIVATE nvqir fmt::fmt-header-only) endif() @@ -61,6 +61,7 @@ add_subdirectory(algorithms) add_subdirectory(platform) add_subdirectory(builder) add_subdirectory(domains) +add_subdirectory(dynamics) install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-targets DESTINATION lib) diff --git a/runtime/cudaq/base_integrator.h b/runtime/cudaq/base_integrator.h new file mode 100644 index 0000000000..e3196bd52d --- /dev/null +++ b/runtime/cudaq/base_integrator.h @@ -0,0 +1,55 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "base_time_stepper.h" +#include "operators.h" +#include "schedule.h" +#include +#include +#include + +namespace cudaq { +template +class BaseIntegrator { +protected: + std::map integrator_options; + TState state; + double t; + std::map dimensions; + std::shared_ptr schedule; + std::shared_ptr hamiltonian; + std::shared_ptr> stepper; + std::vector> collapse_operators; + + virtual void post_init() = 0; + +public: + virtual ~BaseIntegrator() = default; + + void set_state(const TState &initial_state, double t0 = 0.0) { + state = initial_state; + t = t0; + } + + void set_system( + const std::map &dimensions, std::shared_ptr schedule, + std::shared_ptr hamiltonian, + std::vector> collapse_operators = {}) { + this->dimensions = dimensions; + this->schedule = schedule; + this->hamiltonian = hamiltonian; + this->collapse_operators = collapse_operators; + } + + virtual void integrate(double t) = 0; + + std::pair get_state() const { return {t, state}; } +}; +} // namespace cudaq diff --git a/runtime/cudaq/base_time_stepper.h b/runtime/cudaq/base_time_stepper.h new file mode 100644 index 0000000000..4488de8b44 --- /dev/null +++ b/runtime/cudaq/base_time_stepper.h @@ -0,0 +1,19 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +namespace cudaq { +template +class BaseTimeStepper { +public: + virtual ~BaseTimeStepper() = default; + + virtual void compute(TState &state, double t, double step_size) = 0; +}; +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/cudm_error_handling.h b/runtime/cudaq/cudm_error_handling.h new file mode 100644 index 0000000000..d72be33906 --- /dev/null +++ b/runtime/cudaq/cudm_error_handling.h @@ -0,0 +1,30 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once +#include +#include +#include + +#define HANDLE_CUDM_ERROR(x) \ + { \ + const auto err = x; \ + if (err != CUDENSITYMAT_STATUS_SUCCESS) { \ + throw std::runtime_error(fmt::format("[cudaq] %{} in {} (line {})", err, \ + __FUNCTION__, __LINE__)); \ + } \ + } + +#define HANDLE_CUDA_ERROR(x) \ + { \ + const auto err = x; \ + if (err != cudaSuccess) { \ + throw std::runtime_error(fmt::format("[cuda] %{} in {} (line {})", err, \ + __FUNCTION__, __LINE__)); \ + } \ + } diff --git a/runtime/cudaq/cudm_helpers.h b/runtime/cudaq/cudm_helpers.h new file mode 100644 index 0000000000..c2968229fb --- /dev/null +++ b/runtime/cudaq/cudm_helpers.h @@ -0,0 +1,43 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "cudaq/operators.h" +#include "cudaq/utils/tensor.h" +#include +#include +#include +#include +#include + +namespace cudaq { +cudensitymatState_t initialize_state(cudensitymatHandle_t handle, + cudensitymatStatePurity_t purity, + const std::vector &mode_extents); + +void scale_state(cudensitymatHandle_t handle, cudensitymatState_t state, + double scale_factor, cudaStream_t stream); + +void destroy_state(cudensitymatState_t state); + +cudensitymatOperator_t +compute_lindblad_operator(cudensitymatHandle_t handle, + const std::vector &c_ops, + const std::vector &mode_extents); + +cudensitymatOperator_t convert_to_cudensitymat_operator( + cudensitymatHandle_t handle, + const std::map> ¶meters, + const operator_sum &op, const std::vector &mode_extents); + +cudensitymatOperator_t construct_liovillian( + cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, + const std::vector &collapse_operators, + double gamma); +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/cudm_state.h b/runtime/cudaq/cudm_state.h new file mode 100644 index 0000000000..814a7ad342 --- /dev/null +++ b/runtime/cudaq/cudm_state.h @@ -0,0 +1,76 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace cudaq { +class cudm_mat_state { +public: + /// @brief To initialize state with raw data. + explicit cudm_mat_state(std::vector> rawData); + + /// @brief Destructor to clean up resources + ~cudm_mat_state(); + + /// @brief Initialize the state as a density matrix or state vector based on + /// dimensions. + /// @param hilbertSpaceDims Vector representing the Hilbert Space dimensions. + void init_state(const std::vector &hilbertSpaceDims); + + /// @brief Check if the state is initialized. + /// @return True if the state is initialized, false otherwise. + bool is_initialized() const; + + /// @brief Check if the state is a density matrix. + /// @return True if the state is a density matrix, false otherwise. + bool is_density_matrix() const; + + /// @brief Dump the state data to a string for debugging purposes. + /// @return String representation of the state data. + std::string dump() const; + + /// @brief Convert the state vector to a density matrix. + /// @return A new cudm_mat_state representing the density matrix. + cudm_mat_state to_density_matrix() const; + + /// @brief Get the underlying implementation (if any). + /// @return The underlying state implementation. + cudensitymatState_t get_impl() const; + + /// @brief Attach raw data to the internal state representation + void attach_storage(); + +private: + std::vector> rawData_; + cudensitymatState_t state_; + cudensitymatHandle_t handle_; + std::vector hilbertSpaceDims_; + + /// @brief Calculate the size of the state vector for the given Hilbert space + /// dimensions. + /// @param hilbertSpaceDims Hilbert space dimensions. + /// @return Size of the state vector. + size_t calculate_state_vector_size( + const std::vector &hilbertSpaceDims) const; + + /// @brief Calculate the size of the density matrix for the given Hilbert + /// space dimensions. + /// @param hilbertSpaceDims Hilbert space dimensions. + /// @return Size of the density matrix. + size_t calculate_density_matrix_size( + const std::vector &hilbertSpaceDims) const; +}; + +} // namespace cudaq diff --git a/runtime/cudaq/definition.h b/runtime/cudaq/definition.h new file mode 100644 index 0000000000..bdf5af8ab5 --- /dev/null +++ b/runtime/cudaq/definition.h @@ -0,0 +1,136 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/qis/state.h" +#include "cudaq/utils/tensor.h" + +#include +#include +#include +#include +#include +#include + +namespace cudaq { + +// Limit the signature of the users callback function to accept a vector of ints +// for the degree of freedom dimensions, and a vector of complex doubles for the +// concrete parameter values. +using Func = std::function, std::map>)>; + +class CallbackFunction { +private: + // The user provided callback function that takes the degrees of + // freedom and a vector of complex parameters. + Func _callback_func; + +public: + CallbackFunction() = default; + + template + CallbackFunction(Callable &&callable) { + static_assert( + std::is_invocable_r_v, + std::map>>, + "Invalid callback function. Must have signature " + "matrix_2(" + "std::map, " + "std::map>)"); + _callback_func = std::forward(callable); + } + + // Copy constructor. + CallbackFunction(CallbackFunction &other) { + _callback_func = other._callback_func; + } + + CallbackFunction(const CallbackFunction &other) { + _callback_func = other._callback_func; + } + + matrix_2 + operator()(std::map degrees, + std::map> parameters) const { + return _callback_func(std::move(degrees), std::move(parameters)); + } +}; + +using ScalarFunc = std::function( + std::map>)>; + +// A scalar callback function does not need to accept the dimensions, +// therefore we will use a different function type for this specific class. +class ScalarCallbackFunction : CallbackFunction { +private: + // The user provided callback function that takes a vector of parameters. + ScalarFunc _callback_func; + +public: + ScalarCallbackFunction() = default; + + template + ScalarCallbackFunction(Callable &&callable) { + static_assert( + std::is_invocable_r_v, Callable, + std::map>>, + "Invalid callback function. Must have signature std::complex(" + "std::map>)"); + _callback_func = std::forward(callable); + } + + // Copy constructor. + ScalarCallbackFunction(ScalarCallbackFunction &other) { + _callback_func = other._callback_func; + } + + ScalarCallbackFunction(const ScalarCallbackFunction &other) { + _callback_func = other._callback_func; + } + + bool operator!() { return (!_callback_func); } + + std::complex + operator()(std::map> parameters) const { + return _callback_func(std::move(parameters)); + } +}; + +/// @brief Object used to give an error if a Definition of an elementary +/// or scalar operator is instantiated by other means than the `define` +/// class method. +class Definition { +public: + std::string id; + + // The user-provided generator function should take a variable number of + // complex doubles for the parameters. It should return a + // `cudaq::tensor` type representing the operator + // matrix. + CallbackFunction generator; + + // Constructor. + Definition(); + + // Destructor. + ~Definition(); + + void create_definition(const std::string &operator_id, + std::map expected_dimensions, + CallbackFunction &&create); + + // To call the generator function + matrix_2 generate_matrix( + const std::map °rees, + const std::map> ¶meters) const; + +private: + // Member variables + std::map m_expected_dimensions; +}; +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/CMakeLists.txt b/runtime/cudaq/dynamics/CMakeLists.txt new file mode 100644 index 0000000000..90354c7e96 --- /dev/null +++ b/runtime/cudaq/dynamics/CMakeLists.txt @@ -0,0 +1,46 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +set(LIBRARY_NAME cudaq-operators) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") +set(INTERFACE_POSITION_INDEPENDENT_CODE ON) + +set(CUDAQ_OPS_SRC + scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp rydberg_hamiltonian.cpp cudm_helpers.cpp cudm_state.cpp +) + +set(CUQUANTUM_INSTALL_PREFIX "/usr/local/lib/python3.10/dist-packages/cuquantum") +if (NOT DEFINED CUQUANTUM_INSTALL_PREFIX) + message(FATAL_ERROR "CUQUANTUM_INSTALL_PREFIX is not defined.") +endif() + +add_library(${LIBRARY_NAME} SHARED ${CUDAQ_OPS_SRC}) +set_property(GLOBAL APPEND PROPERTY CUDAQ_RUNTIME_LIBS ${LIBRARY_NAME}) +target_include_directories(${LIBRARY_NAME} + PUBLIC + $ + $ + $ + /usr/local/cuda/targets/x86_64-linux/include + $ + PRIVATE .) + +target_link_libraries(${LIBRARY_NAME} PRIVATE ${CUQUANTUM_INSTALL_PREFIX}/lib/libcudensitymat.so.0) + +set (OPERATOR_DEPENDENCIES "") +list(APPEND OPERATOR_DEPENDENCIES fmt::fmt-header-only) +add_openmp_configurations(${LIBRARY_NAME} OPERATOR_DEPENDENCIES) + +target_link_libraries(${LIBRARY_NAME} PRIVATE ${OPERATOR_DEPENDENCIES}) + +install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-operator-targets DESTINATION lib) + +install(EXPORT cudaq-operator-targets + FILE CUDAQSpinTargets.cmake + NAMESPACE cudaq:: + DESTINATION lib/cmake/cudaq) diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp new file mode 100644 index 0000000000..999ba90ff5 --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -0,0 +1,271 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/cudm_helpers.h" +#include "cudaq/cudm_error_handling.h" + +namespace cudaq { +// Function to flatten a matrix into a 1D array +std::vector> flatten_matrix(const matrix_2 &matrix) { + std::vector> flat_matrix; + + for (size_t i = 0; i < matrix.get_rows(); i++) { + for (size_t j = 0; j < matrix.get_columns(); j++) { + flat_matrix.push_back(matrix[{i, j}]); + } + } + + return flat_matrix; +} + +// Function to extract sub-space extents based on degrees +std::vector +get_subspace_extents(const std::vector &mode_extents, + const std::vector °rees) { + std::vector subspace_extents; + + for (int degree : degrees) { + if (degree >= mode_extents.size()) { + throw std::out_of_range("Degree exceeds mode_extents size."); + } + subspace_extents.push_back(mode_extents[degree]); + } + + return subspace_extents; +} + +// Function to create a cudensitymat elementary operator +cudensitymatElementaryOperator_t create_elementary_operator( + cudensitymatHandle_t handle, const std::vector &subspace_extents, + const std::vector> &flat_matrix) { + cudensitymatElementaryOperator_t cudm_elem_op; + + std::vector interleaved_matrix; + interleaved_matrix.reserve(flat_matrix.size() * 2); + + for (const auto &value : flat_matrix) { + interleaved_matrix.push_back(value.real()); + interleaved_matrix.push_back(value.imag()); + } + + HANDLE_CUDM_ERROR(cudensitymatCreateElementaryOperator( + handle, static_cast(subspace_extents.size()), + subspace_extents.data(), CUDENSITYMAT_OPERATOR_SPARSITY_NONE, 0, nullptr, + CUDA_C_64F, static_cast(interleaved_matrix.data()), + {nullptr, nullptr}, &cudm_elem_op)); + + return cudm_elem_op; +} + +// Function to append an elementary operator to a term +void append_elementary_operator_to_term( + cudensitymatHandle_t handle, cudensitymatOperatorTerm_t term, + cudensitymatElementaryOperator_t &elem_op, + const std::vector °rees) { + std::vector modeActionDuality(degrees.size(), 0); + + HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( + handle, term, static_cast(degrees.size()), &elem_op, + degrees.data(), modeActionDuality.data(), make_cuDoubleComplex(1.0, 0.0), + {nullptr, nullptr})); +} + +// Function to create and append a scalar to a term +void append_scalar_to_term(cudensitymatHandle_t handle, + cudensitymatOperatorTerm_t term, + const std::complex &coeff) { + // TODO: Implement handling for time-dependent scalars using + // cudensitymatScalarCallback_t + HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( + handle, term, 0, nullptr, nullptr, nullptr, + {make_cuDoubleComplex(coeff.real(), coeff.imag())}, {nullptr, nullptr})); +} + +cudensitymatState_t initialize_state(cudensitymatHandle_t handle, + cudensitymatStatePurity_t purity, + const std::vector &mode_extents) { + cudensitymatState_t state; + cudensitymatStatus_t status = + cudensitymatCreateState(handle, purity, mode_extents.size(), + mode_extents.data(), 1, CUDA_C_64F, &state); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + std::cerr << "Error in cudensitymatCreateState: " << status << std::endl; + } + return state; +} + +void scale_state(cudensitymatHandle_t handle, cudensitymatState_t state, + double scale_factor, cudaStream_t stream) { + if (!state) { + throw std::invalid_argument("Invalid state provided to scale_state."); + } + + HANDLE_CUDM_ERROR( + cudensitymatStateComputeScaling(handle, state, &scale_factor, stream)); +} + +void destroy_state(cudensitymatState_t state) { + cudensitymatDestroyState(state); +} + +cudensitymatOperator_t +compute_lindblad_operator(cudensitymatHandle_t handle, + const std::vector &c_ops, + const std::vector &mode_extents) { + if (c_ops.empty()) { + throw std::invalid_argument("Collapse operators cannot be empty."); + } + + cudensitymatOperator_t lindblad_op; + HANDLE_CUDM_ERROR(cudensitymatCreateOperator( + handle, static_cast(mode_extents.size()), mode_extents.data(), + &lindblad_op)); + + for (const auto &c_op : c_ops) { + size_t dim = c_op.get_rows(); + if (dim == 0 || c_op.get_columns() != dim) { + throw std::invalid_argument("Collapse operator must be a square matrix"); + } + + auto flat_matrix = flatten_matrix(c_op); + + // Create Operator term for LtL and add to lindblad_op + cudensitymatOperatorTerm_t term; + HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), mode_extents.data(), + &term)); + + // Create elementary operator form c_op + auto cudm_elem_op = + create_elementary_operator(handle, mode_extents, flat_matrix); + + // Add the elementary operator to the term + // TODO: Fix temp vector below + std::vector temp; + append_elementary_operator_to_term(handle, term, cudm_elem_op, temp); + + // Add term to lindblad operator + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm(handle, lindblad_op, term, + 0, {1.0}, scalarCallback)); + + // Destroy intermediate resources + HANDLE_CUDM_ERROR(cudensitymatDestroyOperatorTerm(term)); + HANDLE_CUDM_ERROR(cudensitymatDestroyElementaryOperator(cudm_elem_op)); + } + + return lindblad_op; +} + +std::map +convert_dimensions(const std::vector &mode_extents) { + std::map dimensions; + for (size_t i = 0; i < mode_extents.size(); i++) { + dimensions[static_cast(i)] = static_cast(mode_extents[i]); + } + return dimensions; +} + +cudensitymatOperator_t convert_to_cudensitymat_operator( + cudensitymatHandle_t handle, + const std::map> ¶meters, + const operator_sum &op, const std::vector &mode_extents) { + try { + cudensitymatOperator_t operator_handle; + HANDLE_CUDM_ERROR(cudensitymatCreateOperator( + handle, static_cast(mode_extents.size()), mode_extents.data(), + &operator_handle)); + + std::vector elementary_operators; + + for (const auto &product_op : op.get_terms()) { + cudensitymatOperatorTerm_t term; + + HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), + mode_extents.data(), &term)); + + for (const auto &component : product_op.get_terms()) { + if (std::holds_alternative(component)) { + const auto &elem_op = std::get(component); + + auto subspace_extents = + get_subspace_extents(mode_extents, elem_op.degrees); + auto flat_matrix = flatten_matrix( + elem_op.to_matrix(convert_dimensions(mode_extents), parameters)); + auto cudm_elem_op = + create_elementary_operator(handle, subspace_extents, flat_matrix); + + elementary_operators.push_back(cudm_elem_op); + append_elementary_operator_to_term(handle, term, cudm_elem_op, + elem_op.degrees); + } else if (std::holds_alternative(component)) { + auto coeff = + std::get(component).evaluate(parameters); + append_scalar_to_term(handle, term, coeff); + } + } + + // Append the product operator term to the top-level operator + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( + handle, operator_handle, term, 0, make_cuDoubleComplex(1.0, 0.0), + {nullptr, nullptr})); + + // Destroy the term + HANDLE_CUDM_ERROR(cudensitymatDestroyOperatorTerm(term)); + + // Cleanup + for (auto &elem_op : elementary_operators) { + HANDLE_CUDM_ERROR(cudensitymatDestroyElementaryOperator(elem_op)); + } + } + + return operator_handle; + } catch (const std::exception &e) { + std::cerr << "Error in convert_to_cudensitymat_operator: " << e.what() + << std::endl; + throw; + } +} + +cudensitymatOperator_t construct_liovillian( + cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, + const std::vector &collapse_operators, + double gamma) { + try { + cudensitymatOperator_t liouvillian; + auto status = cudensitymatCreateOperator(handle, 0, nullptr, &liouvillian); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to create Liouvillian operator."); + } + + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + status = cudensitymatOperatorAppendTerm(handle, liouvillian, hamiltonian, 0, + {1.0, 0.0}, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(liouvillian); + throw std::runtime_error("Failed to add hamiltonian term."); + } + + cuDoubleComplex coefficient = make_cuDoubleComplex(gamma, 0.0); + for (const auto &c_op : collapse_operators) { + status = cudensitymatOperatorAppendTerm(handle, liouvillian, c_op, 0, + coefficient, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(liouvillian); + throw std::runtime_error("Failed to add collapse operator term."); + } + } + + return liouvillian; + } catch (const std::exception &e) { + std::cerr << "Error in construct_liovillian: " << e.what() << std::endl; + throw; + } +} +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp new file mode 100644 index 0000000000..b456bef45d --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -0,0 +1,183 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include +#include +#include + +namespace cudaq { + +cudm_mat_state::cudm_mat_state(std::vector> rawData) + : rawData_(rawData), state_(nullptr), handle_(nullptr), + hilbertSpaceDims_() { + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); +} + +cudm_mat_state::~cudm_mat_state() { + if (state_) { + cudensitymatDestroyState(state_); + } + if (handle_) { + cudensitymatDestroy(handle_); + } +} + +void cudm_mat_state::init_state(const std::vector &hilbertSpaceDims) { + if (state_) { + throw std::runtime_error("State is already initialized."); + } + + hilbertSpaceDims_ = hilbertSpaceDims; + + size_t rawDataSize = rawData_.size(); + size_t expectedDensityMatrixSize = + calculate_density_matrix_size(hilbertSpaceDims); + size_t expectedStateVectorSize = + calculate_state_vector_size(hilbertSpaceDims); + + if (rawDataSize != expectedDensityMatrixSize && + rawDataSize != expectedStateVectorSize) { + throw std::invalid_argument( + "Invalid rawData size for the given Hilbert space dimensions."); + } + + cudensitymatStatePurity_t purity; + + if (rawDataSize == expectedDensityMatrixSize) { + purity = CUDENSITYMAT_STATE_PURITY_MIXED; + } else if (rawDataSize == expectedStateVectorSize) { + purity = CUDENSITYMAT_STATE_PURITY_PURE; + } + + HANDLE_CUDM_ERROR(cudensitymatCreateState( + handle_, purity, static_cast(hilbertSpaceDims.size()), + hilbertSpaceDims.data(), 1, CUDA_C_64F, &state_)); + + attach_storage(); +} + +bool cudm_mat_state::is_initialized() const { return state_ != nullptr; } + +bool cudm_mat_state::is_density_matrix() const { + if (!is_initialized()) { + return false; + } + + return rawData_.size() == calculate_density_matrix_size(hilbertSpaceDims_); +} + +std::string cudm_mat_state::dump() const { + if (!is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + + std::ostringstream oss; + oss << "State data: ["; + for (size_t i = 0; i < rawData_.size(); i++) { + oss << rawData_[i]; + if (i < rawData_.size() - 1) { + oss << ", "; + } + } + oss << "]"; + return oss.str(); +} + +cudm_mat_state cudm_mat_state::to_density_matrix() const { + if (!is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + + if (is_density_matrix()) { + throw std::runtime_error("State is already a density matrix."); + } + + std::vector> densityMatrix; + size_t vectorSize = calculate_state_vector_size(hilbertSpaceDims_); + size_t expectedDensityMatrixSize = vectorSize * vectorSize; + densityMatrix.resize(expectedDensityMatrixSize); + + for (size_t i = 0; i < vectorSize; i++) { + for (size_t j = 0; j < vectorSize; j++) { + densityMatrix[i * vectorSize + j] = rawData_[i] * std::conj(rawData_[j]); + } + } + + cudm_mat_state densityMatrixState(densityMatrix); + densityMatrixState.init_state(hilbertSpaceDims_); + return densityMatrixState; +} + +cudensitymatState_t cudm_mat_state::get_impl() const { + if (!is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + return state_; +} + +void cudm_mat_state::attach_storage() { + if (!state_) { + throw std::runtime_error("State is not initialized."); + } + + if (rawData_.empty()) { + throw std::runtime_error("Raw data is empty. Cannot attach storage."); + } + + // Retrieve the number of state components + int32_t numStateComponents; + HANDLE_CUDM_ERROR( + cudensitymatStateGetNumComponents(handle_, state_, &numStateComponents)); + + // Retrieve the storage size for each component + std::vector componentBufferSizes(numStateComponents); + HANDLE_CUDM_ERROR(cudensitymatStateGetComponentStorageSize( + handle_, state_, numStateComponents, componentBufferSizes.data())); + + // Validate that rawData_ has sufficient space for all components + size_t totalSize = 0; + for (size_t size : componentBufferSizes) { + totalSize += size; + } + if (rawData_.size() * sizeof(std::complex) < totalSize) { + throw std::invalid_argument( + "Raw data size is insufficient to cover all components."); + } + + // Attach storage for each component + std::vector componentBuffers(numStateComponents); + std::vector *> rawComponentData(numStateComponents); + + size_t offset = 0; + for (int32_t i = 0; i < numStateComponents; i++) { + rawComponentData[i] = + reinterpret_cast *>(rawData_.data()) + offset; + componentBuffers[i] = static_cast(rawComponentData[i]); + offset += componentBufferSizes[i] / sizeof(std::complex); + } + + HANDLE_CUDM_ERROR(cudensitymatStateAttachComponentStorage( + handle_, state_, numStateComponents, componentBuffers.data(), + componentBufferSizes.data())); +} + +size_t cudm_mat_state::calculate_state_vector_size( + const std::vector &hilbertSpaceDims) const { + size_t size = 1; + for (auto dim : hilbertSpaceDims) { + size *= dim; + } + return size; +} + +size_t cudm_mat_state::calculate_density_matrix_size( + const std::vector &hilbertSpaceDims) const { + size_t vectorSize = calculate_state_vector_size(hilbertSpaceDims); + return vectorSize * vectorSize; +} +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/definition.cpp b/runtime/cudaq/dynamics/definition.cpp new file mode 100644 index 0000000000..cc357fbeab --- /dev/null +++ b/runtime/cudaq/dynamics/definition.cpp @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/definition.h" +#include "cudaq/qis/state.h" + +#include +#include +#include +#include + +namespace cudaq { + +Definition::Definition() = default; + +// Convenience setter +void Definition::create_definition(const std::string &operator_id, + std::map expected_dimensions, + CallbackFunction &&create) { + id = operator_id; + generator = std::move(create); + m_expected_dimensions = std::move(expected_dimensions); +} + +matrix_2 Definition::generate_matrix( + const std::map °rees, + const std::map> ¶meters) const { + return generator(degrees, parameters); +} + +Definition::~Definition() = default; +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/elementary_operators.cpp b/runtime/cudaq/dynamics/elementary_operators.cpp new file mode 100644 index 0000000000..574891033f --- /dev/null +++ b/runtime/cudaq/dynamics/elementary_operators.cpp @@ -0,0 +1,469 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "common/EigenDense.h" +#include "cudaq/operators.h" + +#include +#include + +namespace cudaq { + +/// Elementary Operator constructor. +elementary_operator::elementary_operator(std::string operator_id, + std::vector degrees) + : id(operator_id), degrees(degrees) {} +elementary_operator::elementary_operator(const elementary_operator &other) + : m_ops(other.m_ops), expected_dimensions(other.expected_dimensions), + degrees(other.degrees), id(other.id) {} +elementary_operator::elementary_operator(elementary_operator &other) + : m_ops(other.m_ops), expected_dimensions(other.expected_dimensions), + degrees(other.degrees), id(other.id) {} + +elementary_operator elementary_operator::identity(int degree) { + std::string op_id = "identity"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + int degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + + // Build up the identity matrix. + for (std::size_t i = 0; i < dimension; i++) { + mat[{i, i}] = 1.0 + 0.0 * 'j'; + } + + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::zero(int degree) { + std::string op_id = "zero"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + // Need to set the degree via the op itself because the + // argument to the outer function goes out of scope when + // the user invokes this later on via, e.g, `to_matrix()`. + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::annihilate(int degree) { + std::string op_id = "annihilate"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + for (std::size_t i = 0; i + 1 < dimension; i++) { + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::create(int degree) { + std::string op_id = "create"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + for (std::size_t i = 0; i + 1 < dimension; i++) { + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::position(int degree) { + std::string op_id = "position"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + // position = 0.5 * (create + annihilate) + for (std::size_t i = 0; i + 1 < dimension; i++) { + mat[{i + 1, i}] = + 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::momentum(int degree) { + std::string op_id = "momentum"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + // momentum = 0.5j * (create - annihilate) + for (std::size_t i = 0; i + 1 < dimension; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::number(int degree) { + std::string op_id = "number"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + for (std::size_t i = 0; i < dimension; i++) { + mat[{i, i}] = static_cast(i) + 0.0j; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::parity(int degree) { + std::string op_id = "parity"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + for (std::size_t i = 0; i < dimension; i++) { + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator +elementary_operator::displace(int degree, std::complex amplitude) { + std::string op_id = "displace"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + // if (op.m_ops.find(op_id) == op.m_ops.end()) { + // auto func = [&](std::map dimensions, + // std::map> _none) { + // auto degree = op.degrees[0]; + // std::size_t dimension = dimensions[degree]; + // auto temp_mat = matrix_2(dimension, dimension); + // // // displace = exp[ (amplitude * create) - (conj(amplitude) * + // annihilate) ] + // // for (std::size_t i = 0; i + 1 < dimension; i++) { + // // temp_mat[{i + 1, i}] = + // // amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + // // temp_mat[{i, i + 1}] = + // // -1. * std::conj(amplitude) * std::sqrt(static_cast(i + + // 1)) + + // // 0.0 * 'j'; + // // } + // // Not ideal that our method of computing the matrix exponential + // // requires copies here. Maybe we can just use eigen directly here + // // to limit to one copy, but we can address that later. + // auto mat = temp_mat.exp(); + // std::cout << "dumping the complex mat: \n"; + // mat.dump(); + // std::cout << "\ndone\n\n"; + // return mat; + // }; + // op.define(op_id, op.expected_dimensions, func); + // } + throw std::runtime_error("currently have a bug in implementation."); + return op; +} + +elementary_operator +elementary_operator::squeeze(int degree, std::complex amplitude) { + throw std::runtime_error("Not yet implemented."); +} + +matrix_2 elementary_operator::to_matrix( + const std::map dimensions, + const std::map> parameters) const { + return m_ops.at(id).generator(dimensions, parameters); +} + +/// Elementary Operator Arithmetic. + +operator_sum elementary_operator::operator+(scalar_operator other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + std::vector> _this = { + *this}; + std::vector> _other = { + other}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +operator_sum elementary_operator::operator-(scalar_operator other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + std::vector> _this = { + *this}; + std::vector> _other = { + -1. * other}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +product_operator elementary_operator::operator*(scalar_operator other) { + std::vector> _args = { + *this, other}; + return product_operator(_args); +} + +operator_sum elementary_operator::operator+(std::complex other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + auto other_scalar = scalar_operator(other); + std::vector> _this = { + *this}; + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +operator_sum elementary_operator::operator-(std::complex other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + auto other_scalar = scalar_operator((-1. * other)); + std::vector> _this = { + *this}; + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +product_operator elementary_operator::operator*(std::complex other) { + auto other_scalar = scalar_operator(other); + std::vector> _args = { + *this, other_scalar}; + return product_operator(_args); +} + +operator_sum elementary_operator::operator+(double other) { + std::complex value(other, 0.0); + return *this + value; +} + +operator_sum elementary_operator::operator-(double other) { + std::complex value(other, 0.0); + return *this - value; +} + +product_operator elementary_operator::operator*(double other) { + std::complex value(other, 0.0); + return *this * value; +} + +operator_sum operator+(std::complex other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _self = { + self}; + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_other), product_operator(_self)}); +} + +operator_sum operator-(std::complex other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_other), (-1. * self)}); +} + +product_operator operator*(std::complex other, + elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _args = { + other_scalar, self}; + return product_operator(_args); +} + +operator_sum operator+(double other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _self = { + self}; + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_other), product_operator(_self)}); +} + +operator_sum operator-(double other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_other), (-1. * self)}); +} + +product_operator operator*(double other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _args = { + other_scalar, self}; + return product_operator(_args); +} + +product_operator elementary_operator::operator*(elementary_operator other) { + std::vector> _args = { + *this, other}; + return product_operator(_args); +} + +operator_sum elementary_operator::operator+(elementary_operator other) { + std::vector> _this = { + *this}; + std::vector> _other = { + other}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +operator_sum elementary_operator::operator-(elementary_operator other) { + std::vector> _this = { + *this}; + return operator_sum({product_operator(_this), (-1. * other)}); +} + +operator_sum elementary_operator::operator+(operator_sum other) { + std::vector> _this = { + *this}; + std::vector _prods = {product_operator(_this)}; + auto selfOpSum = operator_sum(_prods); + return selfOpSum + other; +} + +operator_sum elementary_operator::operator-(operator_sum other) { + std::vector> _this = { + *this}; + std::vector _prods = {product_operator(_this)}; + auto selfOpSum = operator_sum(_prods); + return selfOpSum - other; +} + +operator_sum elementary_operator::operator*(operator_sum other) { + std::vector> _this = { + *this}; + std::vector _prods = {product_operator(_this)}; + auto selfOpSum = operator_sum(_prods); + return selfOpSum * other; +} + +operator_sum elementary_operator::operator+(product_operator other) { + std::vector> _this = { + *this}; + return operator_sum({product_operator(_this), other}); +} + +operator_sum elementary_operator::operator-(product_operator other) { + return *this + (-1. * other); +} + +product_operator elementary_operator::operator*(product_operator other) { + std::vector> other_terms = + other.get_terms(); + /// Insert this elementary operator to the front of the terms list. + other_terms.insert(other_terms.begin(), *this); + return product_operator(other_terms); +} + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/evolution.cpp b/runtime/cudaq/dynamics/evolution.cpp new file mode 100644 index 0000000000..75e5ca0875 --- /dev/null +++ b/runtime/cudaq/dynamics/evolution.cpp @@ -0,0 +1,106 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/evolution.h" +#include +#include +#include +#include + +namespace cudaq { + +// Can be removed +matrix_2 taylor_series_expm(const matrix_2 &op_matrix, int order = 20) { + matrix_2 result = matrix_2(op_matrix.get_rows(), op_matrix.get_columns()); + matrix_2 op_matrix_n = + matrix_2(op_matrix.get_rows(), op_matrix.get_columns()); + + for (size_t i = 0; i < op_matrix.get_rows(); i++) { + result[{i, i}] = std::complex(1.0, 0.0); + op_matrix_n[{i, i}] = std::complex(1.0, 0.0); + } + + double factorial = 1.0; + for (int n = 1; n <= order; n++) { + op_matrix_n *= op_matrix; + factorial *= n; + result += std::complex(1.0 / factorial, 0.0) * op_matrix_n; + } + + return result; +} + +matrix_2 compute_step_matrix( + const operator_sum &hamiltonian, const std::map &dimensions, + const std::map> ¶meters, double dt, + bool use_gpu) { + matrix_2 op_matrix = hamiltonian.to_matrix(dimensions, parameters); + op_matrix = dt * std::complex(0, -1) * op_matrix; + + if (use_gpu) { + // TODO: Implement GPU matrix exponential using CuPy or cuQuantum + throw std::runtime_error( + "GPU-based matrix exponentiation not implemented."); + } else { + return taylor_series_expm(op_matrix); + } +} + +void add_noise_channel_for_step( + const std::string &step_kernel_name, cudaq::noise_model &noise_model, + const std::vector &collapse_operators, + const std::map &dimensions, + const std::map> ¶meters, double dt) { + for (const auto &collapse_op : collapse_operators) { + matrix_2 L = collapse_op.to_matrix(dimensions, parameters); + matrix_2 G = std::complex(-0.5, 0.0) * (L * L); + + // Kraus operators + matrix_2 M0 = (dt * G) + matrix_2(L.get_rows(), L.get_columns()); + matrix_2 M1 = std::sqrt(dt) * L; + + try { + noise_model.add_all_qubit_channel( + step_kernel_name, kraus_channel({std::move(M0), std::move(M1)})); + } catch (const std::exception &e) { + std::cerr << "Error adding noise channel: " << e.what() << std::endl; + throw; + } + } +} + +// evolve_result launch_analog_hamiltonian_kernel(const std::string +// &target_name, +// const rydberg_hamiltonian &hamiltonian, +// const Schedule &schedule, +// int shots_count, bool is_async = false) { +// // Generate the time series +// std::vector> amp_ts, ph_ts, dg_ts; + +// auto current_schedule = schedule; +// current_schedule.reset(); + +// while(auto t = current_schedule.current_step()) { +// std::map> parameters = {{"time", +// t.value()}}; + +// amp_ts.emplace_back(hamiltonian.get_amplitude().evaluate(parameters).real(), +// t.value().real()); +// ph_ts.emplace_back(hamiltonian.get_phase().evaluate(parameters).real(), +// t.value().real()); +// dg_ts.emplace_back(hamiltonian.get_delta_global().evaluate(parameters).real(), +// t.value().real()); + +// ++schedule; +// } + +// // Atom arrangement and physical fields +// cudaq::ahs::AtomArrangement atoms; + +// } +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/helpers.cpp b/runtime/cudaq/dynamics/helpers.cpp new file mode 100644 index 0000000000..0c03a81d97 --- /dev/null +++ b/runtime/cudaq/dynamics/helpers.cpp @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/helpers.h" +#include +#include +#include + +namespace cudaq { +// Aggregate parameters from multiple mappings. +std::map OperatorHelpers::aggregate_parameters( + const std::vector> ¶meter_mappings) { + std::map parameter_descriptions; + + for (const auto &descriptions : parameter_mappings) { + for (const auto &[key, new_desc] : descriptions) { + if (!parameter_descriptions[key].empty() && !new_desc.empty()) { + parameter_descriptions[key] += "\n---\n" + new_desc; + } else { + parameter_descriptions[key] = new_desc; + } + } + } + + return parameter_descriptions; +} + +// Extract documentation for a specific parameter from docstring. +std::string OperatorHelpers::parameter_docs(const std::string ¶m_name, + const std::string &docs) { + if (param_name.empty() || docs.empty()) { + return ""; + } + + try { + std::regex keyword_pattern(R"(^\s*(Arguments|Args):\s*$)", + std::regex::multiline); + std::regex param_pattern(R"(^\s*)" + param_name + + R"(\s*(\(.*\))?:\s*(.*)$)", + std::regex::multiline); + + std::smatch match; + std::sregex_iterator it(docs.begin(), docs.end(), keyword_pattern); + std::sregex_iterator end; + + if (it != end) { + std::string params_section = docs.substr(it->position() + it->length()); + if (std::regex_search(params_section, match, param_pattern)) { + std::string param_docs = match.str(2); + return std::regex_replace(param_docs, std::regex(R"(\s+)"), " "); + } + } + } catch (...) { + return ""; + } + + return ""; +} + +// Extract positional arguments and keyword-only arguments. +std::pair, std::map> +OperatorHelpers::args_from_kwargs( + const std::map &kwargs, + const std::vector &required_args, + const std::vector &kwonly_args) { + std::vector extracted_args; + std::map kwonly_dict; + + for (const auto &arg : required_args) { + if (kwargs.count(arg)) { + extracted_args.push_back(kwargs.at(arg)); + } else { + throw std::invalid_argument("Missing required argument: " + arg); + } + } + + for (const auto &arg : kwonly_args) { + if (kwargs.count(arg)) { + kwonly_dict[arg] = kwargs.at(arg); + } + } + + return {extracted_args, kwonly_dict}; +} + +// Generate all possible quantum states for given degrees and dimensions +std::vector +OperatorHelpers::generate_all_states(const std::vector °rees, + const std::map &dimensions) { + if (degrees.empty()) { + return {}; + } + + std::vector> states; + for (int state = 0; state < dimensions.at(degrees[0]); state++) { + states.push_back({std::to_string(state)}); + } + + for (size_t i = 1; i < degrees.size(); i++) { + std::vector> new_states; + for (const auto ¤t : states) { + for (int state = 0; state < dimensions.at(degrees[i]); state++) { + auto new_entry = current; + new_entry.push_back(std::to_string(state)); + new_states.push_back(new_entry); + } + } + states = new_states; + } + + std::vector result; + for (const auto &state : states) { + std::ostringstream joined; + for (const auto &s : state) { + joined << s; + } + result.push_back(joined.str()); + } + return result; +} + +// Permute a given eigen matrix +void OperatorHelpers::permute_matrix(Eigen::MatrixXcd &matrix, + const std::vector &permutation) { + Eigen::MatrixXcd permuted_matrix(matrix.rows(), matrix.cols()); + + for (size_t i = 0; i < permutation.size(); i++) { + for (size_t j = 0; j < permutation.size(); j++) { + permuted_matrix(i, j) = matrix(permutation[i], permutation[j]); + } + } + + matrix = permuted_matrix; +} + +// Canonicalize degrees by sorting in descending order +std::vector +OperatorHelpers::canonicalize_degrees(const std::vector °rees) { + std::vector sorted_degrees = degrees; + std::sort(sorted_degrees.rbegin(), sorted_degrees.rend()); + return sorted_degrees; +} + +// Initialize state based on InitialStateArgT +// TODO: Implement quantum state initialization +void OperatorHelpers::initialize_state(const InitialStateArgT &stateArg, + const std::vector &dimensions) { + if (std::holds_alternative(stateArg)) { + InitialState initState = std::get(stateArg); + switch (initState) { + case InitialState::ZERO: + // ZERO quantum state initialization + std::cout << "Initialized to ZERO state." << std::endl; + break; + case InitialState::UNIFORM: + // UNIFORM quantum state initialization + std::cout << "Initialized to UNIFORM state." << std::endl; + break; + } + } else if (std::holds_alternative(stateArg)) { + // Initialization from runtime state + std::cout << "Initialized using runtime state." << std::endl; + } else { + throw std::invalid_argument("Unsupported InitialStateArgT type."); + } +} +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/operator_sum.cpp b/runtime/cudaq/dynamics/operator_sum.cpp new file mode 100644 index 0000000000..dd3227784d --- /dev/null +++ b/runtime/cudaq/dynamics/operator_sum.cpp @@ -0,0 +1,380 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "common/EigenDense.h" +#include "cudaq/operators.h" + +#include +#include + +namespace cudaq { + +/// Operator sum constructor given a vector of product operators. +operator_sum::operator_sum(const std::vector &terms) + : m_terms(terms) {} + +// std::vector> +// operator_sum::canonicalize_product(product_operator &prod) const { +// std::vector> +// canonicalized_terms; + +// std::vector all_degrees; +// std::vector scalars; +// std::vector non_scalars; + +// for (const auto &op : prod.get_terms()) { +// if (std::holds_alternative(op)) { +// scalars.push_back(*std::get(op)); +// } else { +// non_scalars.push_back(*std::get(op)); +// all_degrees.insert(all_degrees.end(), +// std::get(op).degrees.begin(), +// std::get(op).degrees.end()); +// } +// } + +// if (all_degrees.size() == +// std::set(all_degrees.begin(), all_degrees.end()).size()) { +// std::sort(non_scalars.begin(), non_scalars.end(), +// [](const elementary_operator &a, const elementary_operator &b) { +// return a.degrees < b.degrees; +// }); +// } + +// for (size_t i = 0; std::min(scalars.size(), non_scalars.size()); i++) { +// canonicalized_terms.push_back(std::make_tuple(scalars[i], non_scalars[i])); +// } + +// return canonicalized_terms; +// } + +// std::vector> +// operator_sum::_canonical_terms() const { +// std::vector> terms; +// // for (const auto &term : m_terms) { +// // auto canonicalized = canonicalize_product(term); +// // terms.insert(terms.end(), canonicalized.begin(), canonicalized.end()); +// // } + +// // std::sort(terms.begin(), terms.end(), [](const auto &a, const auto &b) { +// // // return std::to_string(product_operator(a)) < +// // // std::to_string(product_operator(b)); +// // return product_operator(a).to_string() < +// product_operator(b).to_string(); +// // }); + +// return terms; +// } + +// operator_sum operator_sum::canonicalize() const { +// std::vector canonical_terms; +// for (const auto &term : _canonical_terms()) { +// canonical_terms.push_back(product_operator(term)); +// } +// return operator_sum(canonical_terms); +// } + +// bool operator_sum::operator==(const operator_sum &other) const { +// return _canonical_terms() == other._canonical_terms(); +// } + +// // Degrees property +// std::vector operator_sum::degrees() const { +// std::set unique_degrees; +// for (const auto &term : m_terms) { +// for (const auto &op : term.get_terms()) { +// unique_degrees.insert(op.get_degrees().begin(), +// op.get_degrees().end()); +// } +// } + +// return std::vector(unique_degrees.begin(), unique_degrees.end()); +// } + +// // Parameters property +// std::map operator_sum::parameters() const { +// std::map param_map; +// for (const auto &term : m_terms) { +// for (const auto &op : term.get_terms()) { +// auto op_params = op.parameters(); +// param_map.insert(op_params.begin(), op.params.end()); +// } +// } + +// return param_map; +// } + +// // Check if all terms are spin operators +// bool operator_sum::_is_spinop() const { +// return std::all_of( +// m_terms.begin(), m_terms.end(), [](product_operator &term) { +// return std::all_of(term.get_terms().begin(), +// term.get_terms().end(), +// [](const Operator &op) { return op.is_spinop(); +// }); +// }); +// } + +// Arithmetic operators +operator_sum operator_sum::operator+(const operator_sum &other) const { + std::vector combined_terms = m_terms; + combined_terms.insert(combined_terms.end(), + std::make_move_iterator(other.m_terms.begin()), + std::make_move_iterator(other.m_terms.end())); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator-(const operator_sum &other) const { + return *this + (-1 * other); +} + +operator_sum operator_sum::operator-=(const operator_sum &other) { + *this = *this - other; + return *this; +} + +operator_sum operator_sum::operator+=(const operator_sum &other) { + *this = *this + other; + return *this; +} + +operator_sum operator_sum::operator*(operator_sum &other) const { + auto self_terms = m_terms; + std::vector product_terms; + auto other_terms = other.get_terms(); + for (auto &term : self_terms) { + for (auto &other_term : other_terms) { + product_terms.push_back(term * other_term); + } + } + return operator_sum(product_terms); +} + +operator_sum operator_sum::operator*=(operator_sum &other) { + *this = *this * other; + return *this; +} + +operator_sum operator_sum::operator*(const scalar_operator &other) const { + std::vector combined_terms = m_terms; + for (auto &term : combined_terms) { + term *= other; + } + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator+(const scalar_operator &other) const { + std::vector combined_terms = m_terms; + std::vector> _other = { + other}; + combined_terms.push_back(product_operator(_other)); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator-(const scalar_operator &other) const { + return *this + (-1.0 * other); +} + +operator_sum operator_sum::operator*=(const scalar_operator &other) { + *this = *this * other; + return *this; +} + +operator_sum operator_sum::operator+=(const scalar_operator &other) { + *this = *this + other; + return *this; +} + +operator_sum operator_sum::operator-=(const scalar_operator &other) { + *this = *this - other; + return *this; +} + +operator_sum operator_sum::operator*(std::complex other) const { + return *this * scalar_operator(other); +} + +operator_sum operator_sum::operator+(std::complex other) const { + return *this + scalar_operator(other); +} + +operator_sum operator_sum::operator-(std::complex other) const { + return *this - scalar_operator(other); +} + +operator_sum operator_sum::operator*=(std::complex other) { + *this *= scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator+=(std::complex other) { + *this += scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator-=(std::complex other) { + *this -= scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator*(double other) const { + return *this * scalar_operator(other); +} + +operator_sum operator_sum::operator+(double other) const { + return *this + scalar_operator(other); +} + +operator_sum operator_sum::operator-(double other) const { + return *this - scalar_operator(other); +} + +operator_sum operator_sum::operator*=(double other) { + *this *= scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator+=(double other) { + *this += scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator-=(double other) { + *this -= scalar_operator(other); + return *this; +} + +operator_sum operator*(std::complex other, operator_sum self) { + return scalar_operator(other) * self; +} + +operator_sum operator+(std::complex other, operator_sum self) { + return scalar_operator(other) + self; +} + +operator_sum operator-(std::complex other, operator_sum self) { + return scalar_operator(other) - self; +} + +operator_sum operator*(double other, operator_sum self) { + return scalar_operator(other) * self; +} + +operator_sum operator+(double other, operator_sum self) { + return scalar_operator(other) + self; +} + +operator_sum operator-(double other, operator_sum self) { + return scalar_operator(other) - self; +} + +operator_sum operator_sum::operator+(const product_operator &other) const { + std::vector combined_terms = m_terms; + combined_terms.push_back(other); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator+=(const product_operator &other) { + *this = *this + other; + return *this; +} + +operator_sum operator_sum::operator-(const product_operator &other) const { + return *this + (-1. * other); +} + +operator_sum operator_sum::operator-=(const product_operator &other) { + *this = *this - other; + return *this; +} + +operator_sum operator_sum::operator*(const product_operator &other) const { + std::vector combined_terms = m_terms; + for (auto &term : combined_terms) { + term *= other; + } + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator*=(const product_operator &other) { + *this = *this * other; + return *this; +} + +operator_sum operator_sum::operator+(const elementary_operator &other) const { + std::vector combined_terms = m_terms; + std::vector> _other = { + other}; + combined_terms.push_back(product_operator(_other)); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator-(const elementary_operator &other) const { + std::vector combined_terms = m_terms; + combined_terms.push_back((-1. * other)); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator*(const elementary_operator &other) const { + std::vector combined_terms = m_terms; + for (auto &term : combined_terms) { + term *= other; + } + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator+=(const elementary_operator &other) { + std::vector> _other = { + other}; + *this = *this + product_operator(_other); + return *this; +} + +operator_sum operator_sum::operator-=(const elementary_operator &other) { + std::vector> _other = { + other}; + *this = *this - product_operator(_other); + return *this; +} + +operator_sum operator_sum::operator*=(const elementary_operator &other) { + *this = *this * other; + return *this; +} + +matrix_2 operator_sum::to_matrix( + const std::map &dimensions, + const std::map> ¶ms) const { + std::size_t total_dimension = 1; + for (const auto &[_, dim] : dimensions) { + total_dimension *= dim; + } + + matrix_2 result(total_dimension, total_dimension); + + for (const auto &term : m_terms) { + matrix_2 term_matrix = term.to_matrix(dimensions, params); + + result += term_matrix; + } + + return result; +} + +// std::string operator_sum::to_string() const { +// std::string result; +// // for (const auto &term : m_terms) { +// // result += term.to_string() + " + "; +// // } +// // // Remove last " + " +// // if (!result.empty()) +// // result.pop_back(); +// return result; +// } + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/product_operators.cpp b/runtime/cudaq/dynamics/product_operators.cpp new file mode 100644 index 0000000000..32adcaa339 --- /dev/null +++ b/runtime/cudaq/dynamics/product_operators.cpp @@ -0,0 +1,318 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "common/EigenDense.h" +#include "cudaq/operators.h" + +#include +#include +#include +#include + +namespace cudaq { + +/// Product Operator constructors. +product_operator::product_operator( + std::vector> + atomic_operators) + : m_terms(atomic_operators) {} + +// tensor kroneckerHelper(std::vector &matrices) { +// // essentially we pass in the list of elementary operators to +// // this function -- with lowest degree being leftmost -- then it computes +// the +// // kronecker product of all of them. +// auto kronecker = [](tensor self, tensor other) { +// return self.kronecker(other); +// }; + +// return std::accumulate(begin(matrices), end(matrices), +// tensor::identity(1, 1), kronecker); +// } + +matrix_2 product_operator::to_matrix( + const std::map dimensions, + const std::map> parameters) const { + // Lambda functions to retrieve degrees and matrices + auto getDegrees = [](auto &&term) { return term.degrees; }; + auto getMatrix = [&](auto &&term) { + return term.to_matrix(dimensions, parameters); + }; + + // Initialize a result matrix with a single identity element + matrix_2 result(1, 1); + result[{0, 0}] = 1.0; + + // Iterate over all terms in the product operator + for (const auto &term : m_terms) { + // Get the degrees for the current term + auto termDegrees = std::visit(getDegrees, term); + bool inserted = false; + + matrix_2 termMatrix(1, 1); + termMatrix[{0, 0}] = 1.0; + + // Build the matrix list with identities or operator matrices + for (const auto &[degree, dim] : dimensions) { + if (std::find(termDegrees.begin(), termDegrees.end(), degree) != + termDegrees.end() && + !inserted) { + // Use the operator matrix for the active degree + termMatrix.kronecker_inplace(std::visit(getMatrix, term)); + inserted = true; + } else { + // Use identity matrix for other degrees + matrix_2 identityMatrix(dim, dim); + for (std::size_t i = 0; i < dim; i++) { + identityMatrix[{i, i}] = 1.0; + } + termMatrix.kronecker_inplace(identityMatrix); + } + } + + // Multiply the result matrix by the term matrix + result *= termMatrix; + } + + return result; +} + +// /// IMPLEMENT: +// tensor product_operator::to_matrix( +// std::map dimensions, +// std::map> parameters) { + +// /// TODO: This initial logic may not be needed. +// // std::vector degrees, levels; +// // for(std::map::iterator it = dimensions.begin(); it != +// // dimensions.end(); ++it) { +// // degrees.push_back(it->first); +// // levels.push_back(it->second); +// // } +// // // Calculate the size of the full Hilbert space of the given product +// // operator. int fullSize = std::accumulate(begin(levels), end(levels), 1, +// // std::multiplies()); +// std::cout << "here 49\n"; +// auto getDegrees = [](auto &&t) { return t.degrees; }; +// auto getMatrix = [&](auto &&t) { +// auto outMatrix = t.to_matrix(dimensions, parameters); +// std::cout << "dumping the outMatrix : \n"; +// outMatrix.dump(); +// return outMatrix; +// }; +// std::vector matricesFullVectorSpace; +// for (auto &term : m_terms) { +// auto op_degrees = std::visit(getDegrees, term); +// std::cout << "here 58\n"; +// // Keeps track of if we've already inserted the operator matrix +// // into the full list of matrices. +// bool alreadyInserted = false; +// std::vector matrixWithIdentities; +// /// General procedure for inserting identities: +// // * check if the operator acts on this degree by looking through +// // `op_degrees` +// // * if not, insert an identity matrix of the proper level size +// // * if so, insert the matrix itself +// for (auto [degree, level] : dimensions) { +// std::cout << "here 68\n"; +// auto it = std::find(op_degrees.begin(), op_degrees.end(), degree); +// if (it != op_degrees.end() && !alreadyInserted) { +// std::cout << "here 71\n"; +// auto matrix = std::visit(getMatrix, term); +// std::cout << "here 75\n"; +// matrixWithIdentities.push_back(matrix); +// std::cout << "here 77\n"; +// } else { +// std::cout << "here 80\n"; +// matrixWithIdentities.push_back(tensor::identity(level, +// level)); +// } +// } +// std::cout << "here 84\n"; +// matricesFullVectorSpace.push_back(kroneckerHelper(matrixWithIdentities)); +// } +// // Now just need to accumulate with matrix multiplication all of the +// // matrices in `matricesFullVectorSpace` -- they should all be the same +// size +// // already. +// std::cout << "here 89\n"; + +// // temporary +// auto out = tensor::identity(1, 1); +// std::cout << "here 93\n"; +// return out; +// } + +// Degrees property +std::vector product_operator::degrees() const { + std::set unique_degrees; + // The variant type makes it difficult + auto beginFunc = [](auto &&t) { return t.degrees.begin(); }; + auto endFunc = [](auto &&t) { return t.degrees.end(); }; + for (const auto &term : m_terms) { + unique_degrees.insert(std::visit(beginFunc, term), + std::visit(endFunc, term)); + } + // Erase any `-1` degree values that may have come from scalar operators. + auto it = unique_degrees.find(-1); + if (it != unique_degrees.end()) { + unique_degrees.erase(it); + } + return std::vector(unique_degrees.begin(), unique_degrees.end()); +} + +operator_sum product_operator::operator+(scalar_operator other) { + std::vector> _other = { + other}; + return operator_sum({*this, product_operator(_other)}); +} + +operator_sum product_operator::operator-(scalar_operator other) { + std::vector> _other = { + other}; + return operator_sum({*this, -1. * product_operator(_other)}); +} + +product_operator product_operator::operator*(scalar_operator other) { + std::vector> + combined_terms = m_terms; + combined_terms.push_back(other); + return product_operator(combined_terms); +} + +product_operator product_operator::operator*=(scalar_operator other) { + *this = *this * other; + return *this; +} + +operator_sum product_operator::operator+(std::complex other) { + return *this + scalar_operator(other); +} + +operator_sum product_operator::operator-(std::complex other) { + return *this - scalar_operator(other); +} + +product_operator product_operator::operator*(std::complex other) { + return *this * scalar_operator(other); +} + +product_operator product_operator::operator*=(std::complex other) { + *this = *this * scalar_operator(other); + return *this; +} + +operator_sum operator+(std::complex other, product_operator self) { + return operator_sum({scalar_operator(other), self}); +} + +operator_sum operator-(std::complex other, product_operator self) { + return scalar_operator(other) - self; +} + +product_operator operator*(std::complex other, product_operator self) { + return scalar_operator(other) * self; +} + +operator_sum product_operator::operator+(double other) { + return *this + scalar_operator(other); +} + +operator_sum product_operator::operator-(double other) { + return *this - scalar_operator(other); +} + +product_operator product_operator::operator*(double other) { + return *this * scalar_operator(other); +} + +product_operator product_operator::operator*=(double other) { + *this = *this * scalar_operator(other); + return *this; +} + +operator_sum operator+(double other, product_operator self) { + return operator_sum({scalar_operator(other), self}); +} + +operator_sum operator-(double other, product_operator self) { + return scalar_operator(other) - self; +} + +product_operator operator*(double other, product_operator self) { + return scalar_operator(other) * self; +} + +operator_sum product_operator::operator+(product_operator other) { + return operator_sum({*this, other}); +} + +operator_sum product_operator::operator-(product_operator other) { + return operator_sum({*this, (-1. * other)}); +} + +product_operator product_operator::operator*(product_operator other) { + std::vector> + combined_terms = m_terms; + combined_terms.insert(combined_terms.end(), + std::make_move_iterator(other.m_terms.begin()), + std::make_move_iterator(other.m_terms.end())); + return product_operator(combined_terms); +} + +product_operator product_operator::operator*=(product_operator other) { + *this = *this * other; + return *this; +} + +operator_sum product_operator::operator+(elementary_operator other) { + std::vector> _other = { + other}; + return operator_sum({*this, product_operator(_other)}); +} + +operator_sum product_operator::operator-(elementary_operator other) { + std::vector> _other = { + other}; + return operator_sum({*this, -1. * product_operator(_other)}); +} + +product_operator product_operator::operator*(elementary_operator other) { + std::vector> + combined_terms = m_terms; + combined_terms.push_back(other); + return product_operator(combined_terms); +} + +product_operator product_operator::operator*=(elementary_operator other) { + *this = *this * other; + return *this; +} + +operator_sum product_operator::operator+(operator_sum other) { + std::vector other_terms = other.get_terms(); + other_terms.insert(other_terms.begin(), *this); + return operator_sum(other_terms); +} + +operator_sum product_operator::operator-(operator_sum other) { + auto negative_other = (-1. * other); + std::vector other_terms = negative_other.get_terms(); + other_terms.insert(other_terms.begin(), *this); + return operator_sum(other_terms); +} + +operator_sum product_operator::operator*(operator_sum other) { + std::vector other_terms = other.get_terms(); + for (auto &term : other_terms) { + term = *this * term; + } + return operator_sum(other_terms); +} + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/rydberg_hamiltonian.cpp b/runtime/cudaq/dynamics/rydberg_hamiltonian.cpp new file mode 100644 index 0000000000..3d8b125ad3 --- /dev/null +++ b/runtime/cudaq/dynamics/rydberg_hamiltonian.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/operators.h" +#include +#include + +namespace cudaq { +rydberg_hamiltonian::rydberg_hamiltonian( + const std::vector &atom_sites, const scalar_operator &litude, + const scalar_operator &phase, const scalar_operator &delta_global, + const std::vector &atom_filling, + const std::optional>> + &delta_local) + : atom_sites(atom_sites), amplitude(amplitude), phase(phase), + delta_global(delta_global), delta_local(delta_local) { + if (atom_filling.empty()) { + this->atom_filling = std::vector(atom_sites.size(), 1); + } else if (atom_sites.size() != atom_filling.size()) { + throw std::invalid_argument( + "Size of `atom_sites` and `atom_filling` must be equal."); + } else { + this->atom_filling = atom_filling; + } + + if (delta_local.has_value()) { + throw std::runtime_error( + "Local detuning is an experimental feature not yet supported."); + } +} + +const std::vector & +rydberg_hamiltonian::get_atom_sites() const { + return atom_sites; +} + +const std::vector &rydberg_hamiltonian::get_atom_filling() const { + return atom_filling; +} + +const scalar_operator &rydberg_hamiltonian::get_amplitude() const { + return amplitude; +} + +const scalar_operator &rydberg_hamiltonian::get_phase() const { return phase; } + +const scalar_operator &rydberg_hamiltonian::get_delta_global() const { + return delta_global; +} +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/scalar_operators.cpp b/runtime/cudaq/dynamics/scalar_operators.cpp new file mode 100644 index 0000000000..4d640b50b5 --- /dev/null +++ b/runtime/cudaq/dynamics/scalar_operators.cpp @@ -0,0 +1,275 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "common/EigenDense.h" +#include "cudaq/operators.h" + +#include +#include + +namespace cudaq { + +/// Constructors. +scalar_operator::scalar_operator(const scalar_operator &other) + : generator(other.generator), m_constant_value(other.m_constant_value) {} +scalar_operator::scalar_operator(scalar_operator &other) + : generator(other.generator), m_constant_value(other.m_constant_value) {} + +/// @brief Constructor that just takes and returns a complex double value. +scalar_operator::scalar_operator(std::complex value) { + m_constant_value = value; + auto func = [&](std::map> _none) { + return m_constant_value; + }; + generator = ScalarCallbackFunction(func); +} + +/// @brief Constructor that just takes a double and returns a complex double. +scalar_operator::scalar_operator(double value) { + std::complex castValue(value, 0.0); + m_constant_value = castValue; + auto func = [&](std::map> _none) { + return m_constant_value; + }; + generator = ScalarCallbackFunction(func); +} + +std::complex scalar_operator::evaluate( + std::map> parameters) const { + return generator(parameters); +} + +matrix_2 scalar_operator::to_matrix( + const std::map dimensions, + const std::map> parameters) const { + auto returnOperator = matrix_2(1, 1); + returnOperator[{0, 0}] = evaluate(parameters); + return returnOperator; +} + +#define ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(op) \ + scalar_operator operator op(std::complex other, \ + scalar_operator self) { \ + /* Create an operator for the complex double value. */ \ + auto otherOperator = scalar_operator(other); \ + /* Create an operator that we will store the result in and return to the \ + * user. */ \ + scalar_operator returnOperator; \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + returnOperator._operators_to_compose.push_back(self); \ + returnOperator._operators_to_compose.push_back(otherOperator); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + /* FIXME: I have to use this hacky `.get_val()` on the newly created \ + * operator for the given complex double -- because calling the \ + * evaluate function returns 0.0 . I have no clue why??? */ \ + return returnOperator._operators_to_compose[0] \ + .evaluate(parameters) op returnOperator._operators_to_compose[1] \ + .get_val(); \ + }; \ + returnOperator.generator = ScalarCallbackFunction(newGenerator); \ + return returnOperator; \ + } + +#define ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(op) \ + scalar_operator operator op(scalar_operator self, \ + std::complex other) { \ + /* Create an operator for the complex double value. */ \ + auto otherOperator = scalar_operator(other); \ + /* Create an operator that we will store the result in and return to the \ + * user. */ \ + scalar_operator returnOperator; \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + returnOperator._operators_to_compose.push_back(self); \ + returnOperator._operators_to_compose.push_back(otherOperator); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + /* FIXME: I have to use this hacky `.get_val()` on the newly created \ + * operator for the given complex double -- because calling the \ + * evaluate function returns 0.0 . I have no clue why??? */ \ + return returnOperator._operators_to_compose[1] \ + .get_val() op returnOperator._operators_to_compose[0] \ + .evaluate(parameters); \ + }; \ + returnOperator.generator = ScalarCallbackFunction(newGenerator); \ + return returnOperator; \ + } + +#define ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(op) \ + void operator op(scalar_operator &self, std::complex other) { \ + /* Create an operator for the complex double value. */ \ + auto otherOperator = scalar_operator(other); \ + /* Need to move the existing generating function to a new operator so that \ + * we can modify the generator in `self` in-place. */ \ + scalar_operator copy(self); \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + self._operators_to_compose.push_back(copy); \ + self._operators_to_compose.push_back(otherOperator); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + /* FIXME: I have to use this hacky `.get_val()` on the newly created \ + * operator for the given complex double -- because calling the \ + * evaluate function returns 0.0 . I have no clue why??? */ \ + return self._operators_to_compose[0] \ + .evaluate(parameters) op self._operators_to_compose[1] \ + .get_val(); \ + }; \ + self.generator = ScalarCallbackFunction(newGenerator); \ + } + +#define ARITHMETIC_OPERATIONS_DOUBLES(op) \ + scalar_operator operator op(double other, scalar_operator self) { \ + std::complex value(other, 0.0); \ + return self op value; \ + } + +#define ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(op) \ + scalar_operator operator op(scalar_operator self, double other) { \ + std::complex value(other, 0.0); \ + return value op self; \ + } + +#define ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(op) \ + void operator op(scalar_operator &self, double other) { \ + std::complex value(other, 0.0); \ + self op value; \ + } + +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(+); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(-); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(*); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(/); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(+); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(-); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(*); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(/); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(+=); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(-=); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(*=); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(/=); +ARITHMETIC_OPERATIONS_DOUBLES(+); +ARITHMETIC_OPERATIONS_DOUBLES(-); +ARITHMETIC_OPERATIONS_DOUBLES(*); +ARITHMETIC_OPERATIONS_DOUBLES(/); +ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(+); +ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(-); +ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(*); +ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(/); +ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(+=); +ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(-=); +ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(*=); +ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(/=); + +#define ARITHMETIC_OPERATIONS_SCALAR_OPS(op) \ + scalar_operator scalar_operator::operator op(scalar_operator other) { \ + /* Create an operator that we will store the result in and return to the \ + * user. */ \ + scalar_operator returnOperator; \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + returnOperator._operators_to_compose.push_back(*this); \ + returnOperator._operators_to_compose.push_back(other); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + return returnOperator._operators_to_compose[0] \ + .evaluate(parameters) op returnOperator._operators_to_compose[1] \ + .evaluate(parameters); \ + }; \ + returnOperator.generator = ScalarCallbackFunction(newGenerator); \ + return returnOperator; \ + } + +#define ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(op) \ + void operator op(scalar_operator &self, scalar_operator other) { \ + /* Need to move the existing generating function to a new operator so \ + * that we can modify the generator in `self` in-place. */ \ + scalar_operator selfCopy(self); \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + self._operators_to_compose.push_back(selfCopy); \ + self._operators_to_compose.push_back(other); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + return self._operators_to_compose[0] \ + .evaluate(parameters) op self._operators_to_compose[1] \ + .evaluate(parameters); \ + }; \ + self.generator = ScalarCallbackFunction(newGenerator); \ + } + +ARITHMETIC_OPERATIONS_SCALAR_OPS(+); +ARITHMETIC_OPERATIONS_SCALAR_OPS(-); +ARITHMETIC_OPERATIONS_SCALAR_OPS(*); +ARITHMETIC_OPERATIONS_SCALAR_OPS(/); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(+=); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(-=); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(*=); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(/=); + +operator_sum scalar_operator::operator+(elementary_operator other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + return operator_sum({product_operator({*this}), product_operator({other})}); +} + +operator_sum scalar_operator::operator-(elementary_operator other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + return operator_sum( + {product_operator({*this}), product_operator({-1. * other})}); +} + +product_operator scalar_operator::operator*(elementary_operator other) { + return product_operator({*this, other}); +} + +operator_sum scalar_operator::operator+(product_operator other) { + return operator_sum({product_operator({*this}), other}); +} + +operator_sum scalar_operator::operator-(product_operator other) { + return operator_sum({product_operator({*this}), (-1. * other)}); +} + +product_operator scalar_operator::operator*(product_operator other) { + std::vector> other_terms = + other.get_terms(); + /// Insert this scalar operator to the front of the terms list. + other_terms.insert(other_terms.begin(), *this); + return product_operator(other_terms); +} + +operator_sum scalar_operator::operator+(operator_sum other) { + std::vector other_terms = other.get_terms(); + other_terms.insert(other_terms.begin(), *this); + return operator_sum(other_terms); +} + +operator_sum scalar_operator::operator-(operator_sum other) { + auto negative_other = (-1. * other); + std::vector other_terms = negative_other.get_terms(); + other_terms.insert(other_terms.begin(), *this); + return operator_sum(other_terms); +} + +operator_sum scalar_operator::operator*(operator_sum other) { + std::vector other_terms = other.get_terms(); + for (auto &term : other_terms) + term = *this * term; + return operator_sum(other_terms); +} + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/schedule.cpp b/runtime/cudaq/dynamics/schedule.cpp new file mode 100644 index 0000000000..6e833acb4f --- /dev/null +++ b/runtime/cudaq/dynamics/schedule.cpp @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/schedule.h" +#include +#include + +namespace cudaq { + +// Constructor +Schedule::Schedule(std::vector> steps, + std::vector parameters, + std::function( + const std::string &, const std::complex &)> + value_function) + : _steps(steps), _parameters(parameters), _value_function(value_function), + _current_idx(-1) { + if (!_steps.empty()) { + m_ptr = &_steps[0]; + } else { + m_ptr = nullptr; + } +} + +// Dereference operator +Schedule::reference Schedule::operator*() const { return *m_ptr; } + +// Arrow operator +Schedule::pointer Schedule::operator->() { return m_ptr; } + +// Prefix increment +Schedule &Schedule::operator++() { + if (_current_idx + 1 < static_cast(_steps.size())) { + _current_idx++; + m_ptr = &_steps[_current_idx]; + } else { + throw std::out_of_range("No more steps in the schedule."); + } + return *this; +} + +// Postfix increment +Schedule Schedule::operator++(int) { + Schedule tmp = *this; + ++(*this); + return tmp; +} + +// Comparison operators +bool operator==(const Schedule &a, const Schedule &b) { + return a.m_ptr == b.m_ptr; +}; + +bool operator!=(const Schedule &a, const Schedule &b) { + return a.m_ptr != b.m_ptr; +}; + +// Reset schedule +void Schedule::reset() { + _current_idx = -1; + if (!_steps.empty()) { + m_ptr = &_steps[0]; + } else { + m_ptr = nullptr; + } +} + +// Get the current step +std::optional> Schedule::current_step() const { + if (_current_idx >= 0 && _current_idx < static_cast(_steps.size())) { + return _steps[_current_idx]; + } + return std::nullopt; +} + +} // namespace cudaq diff --git a/runtime/cudaq/evolution.h b/runtime/cudaq/evolution.h new file mode 100644 index 0000000000..85379f998a --- /dev/null +++ b/runtime/cudaq/evolution.h @@ -0,0 +1,79 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "common/EvolveResult.h" +#include "cudaq/base_integrator.h" +#include "cudaq/operators.h" +#include "cudaq/schedule.h" +#include "cudaq/utils/tensor.h" + +#include +#include +#include +#include + +namespace cudaq { +class Evolution { +public: + /// Computes the Taylor series expansion of the matrix exponential. + static matrix_2 taylor_series_expm(const matrix_2 &op_matrix, int order = 20); + + /// Computes the evolution step matrix + static matrix_2 compute_step_matrix( + const operator_sum &hamiltonian, const std::map &dimensions, + const std::map> ¶meters, double dt, + bool use_gpu = false); + + /// Adds noise channels based on collapse operators. + static void add_noise_channel_for_step( + const std::string &step_kernel_name, cudaq::noise_model &noise_model, + const std::vector &collapse_operators, + const std::map &dimensions, + const std::map> ¶meters, double dt); + + /// Launches an analog Hamiltonian kernel for quantum simulations. + static evolve_result launch_analog_hamiltonian_kernel( + const std::string &target_name, const rydberg_hamiltonian &hamiltonian, + const Schedule &schedule, int shots_count, bool is_async = false); + + /// Generates evolution kernels for the simulation. + static std::vector evolution_kernel( + int num_qubits, + const std::function< + matrix_2(const std::map> &, double)> + &compute_step_matrix, + const std::vector tlist, + const std::vector>> + &schedule_parameters); + + /// Evolves a single quantum state under a given `hamiltonian`. + static evolve_result + evolve_single(const operator_sum &hamiltonian, + const std::map &dimensions, + const std::shared_ptr &schedule, state initial_state, + const std::vector &collapse_operators = {}, + const std::vector &observables = {}, + bool store_intermediate_results = false, + std::shared_ptr> integrator = nullptr, + std::optional shots_count = std::nullopt); + + /// Evolves a single or multiple quantum states under a given `hamiltonian`. + /// Run only for dynamics target else throw error + static std::vector + evolve(const operator_sum &hamiltonian, const std::map &dimensions, + const std::shared_ptr &schedule, + const std::vector &initial_states, + const std::vector &collapse_operators = {}, + const std::vector &observables = {}, + bool store_intermediate_results = false, + std::shared_ptr> integrator = nullptr, + std::optional shots_count = std::nullopt); +}; +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/helpers.h b/runtime/cudaq/helpers.h new file mode 100644 index 0000000000..39f73f7969 --- /dev/null +++ b/runtime/cudaq/helpers.h @@ -0,0 +1,61 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cudaq { + +// Enum to specify the initial quantum state. +enum class InitialState { ZERO, UNIFORM }; + +using InitialStateArgT = std::variant; + +class OperatorHelpers { +public: + // Aggregate parameters from multiple mappings. + static std::map + aggregate_parameters(const std::vector> + ¶meter_mappings); + + // Extract documentation for a specific parameter from docstring. + static std::string parameter_docs(const std::string ¶m_name, + const std::string &docs); + + // Extract positional arguments and keyword-only arguments. + static std::pair, std::map> + args_from_kwargs(const std::map &kwargs, + const std::vector &required_args, + const std::vector &kwonly_args); + + // Generate all possible quantum states for given degrees and dimensions. + static std::vector + generate_all_states(const std::vector °rees, + const std::map &dimensions); + + // Permute a given Eigen matrix. + static void permute_matrix(Eigen::MatrixXcd &matrix, + const std::vector &permutation); + + // Canonicalize degrees by sorting in descending order. + static std::vector canonicalize_degrees(const std::vector °rees); + + // Initialize state based on InitialStateArgT. + static void initialize_state(const InitialStateArgT &stateArg, + const std::vector &dimensions); +}; +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/operator_utils.h b/runtime/cudaq/operator_utils.h new file mode 100644 index 0000000000..568cc8298e --- /dev/null +++ b/runtime/cudaq/operator_utils.h @@ -0,0 +1,40 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "definition.h" +#include "matrix.h" +#include +#include +#include +#include +#include +#include + +namespace cudaq { + +inline std::map> +aggregate_parameters(const std::map ¶m1, + const std::map ¶m2) { + std::map> merged_map = param1; + + for (const auto &[key, value] : param2) { + /// FIXME: May just be able to remove this whole conditional block + /// since we're not dealing with std::string entries, but instead + /// complex doubles now. + if (merged_map.find(key) != merged_map.end()) { + // do nothing + } else { + merged_map[key] = value; + } + } + + return merged_map; +} +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/operators.h b/runtime/cudaq/operators.h new file mode 100644 index 0000000000..5f07f5dd49 --- /dev/null +++ b/runtime/cudaq/operators.h @@ -0,0 +1,515 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "definition.h" +#include "utils/tensor.h" + +#include +#include +#include +#include + +namespace cudaq { + +class operator_sum; +class product_operator; +class scalar_operator; +class elementary_operator; + +/// @brief Represents an operator expression consisting of a sum of terms, where +/// each term is a product of elementary and scalar operators. Operator +/// expressions cannot be used within quantum kernels, but they provide methods +/// to convert them to data types that can. +class operator_sum { +private: + std::vector m_terms; + + std::vector> + canonicalize_product(product_operator &prod) const; + + std::vector> + _canonical_terms() const; + +public: + /// @brief Empty constructor that a user can aggregate terms into. + operator_sum() = default; + + /// @brief Construct a `cudaq::operator_sum` given a sequence of + /// `cudaq::product_operator`'s. + /// This operator expression represents a sum of terms, where each term + /// is a product of elementary and scalar operators. + operator_sum(const std::vector &terms); + + operator_sum canonicalize() const; + + /// @brief The degrees of freedom that the operator acts on in canonical + /// order. + std::vector degrees() const; + + bool _is_spinop() const; + + /// TODO: implement + // template + // TEval _evaluate(OperatorArithmetics &arithmetics) const; + + /// @brief Return the `operator_sum` as a matrix. + /// @arg `dimensions` : A mapping that specifies the number of levels, + /// that is, the dimension of each degree of freedom + /// that the operator acts on. Example for two, 2-level + /// degrees of freedom: `{0:2, 1:2}`. + /// @arg `parameters` : A map of the parameter names to their concrete, + /// complex values. + matrix_2 to_matrix( + const std::map &dimensions, + const std::map> ¶ms = {}) const; + + // Arithmetic operators + operator_sum operator+(const operator_sum &other) const; + operator_sum operator-(const operator_sum &other) const; + operator_sum operator*(operator_sum &other) const; + operator_sum operator*=(operator_sum &other); + operator_sum operator+=(const operator_sum &other); + operator_sum operator-=(const operator_sum &other); + operator_sum operator*(const scalar_operator &other) const; + operator_sum operator+(const scalar_operator &other) const; + operator_sum operator-(const scalar_operator &other) const; + operator_sum operator*=(const scalar_operator &other); + operator_sum operator+=(const scalar_operator &other); + operator_sum operator-=(const scalar_operator &other); + operator_sum operator*(std::complex other) const; + operator_sum operator+(std::complex other) const; + operator_sum operator-(std::complex other) const; + operator_sum operator*=(std::complex other); + operator_sum operator+=(std::complex other); + operator_sum operator-=(std::complex other); + operator_sum operator*(double other) const; + operator_sum operator+(double other) const; + operator_sum operator-(double other) const; + operator_sum operator*=(double other); + operator_sum operator+=(double other); + operator_sum operator-=(double other); + operator_sum operator*(const product_operator &other) const; + operator_sum operator+(const product_operator &other) const; + operator_sum operator-(const product_operator &other) const; + operator_sum operator*=(const product_operator &other); + operator_sum operator+=(const product_operator &other); + operator_sum operator-=(const product_operator &other); + operator_sum operator+(const elementary_operator &other) const; + operator_sum operator-(const elementary_operator &other) const; + operator_sum operator*(const elementary_operator &other) const; + operator_sum operator*=(const elementary_operator &other); + operator_sum operator+=(const elementary_operator &other); + operator_sum operator-=(const elementary_operator &other); + + /// @brief Return the operator_sum as a string. + std::string to_string() const; + + /// @brief Return the number of operator terms that make up this operator sum. + int term_count() const { return m_terms.size(); } + + /// @brief True, if the other value is an operator_sum with equivalent terms, + /// and False otherwise. The equality takes into account that operator + /// addition is commutative, as is the product of two operators if they + /// act on different degrees of freedom. + /// The equality comparison does *not* take commutation relations into + /// account, and does not try to reorder terms `blockwise`; it may hence + /// evaluate to False, even if two operators in reality are the same. + /// If the equality evaluates to True, on the other hand, the operators + /// are guaranteed to represent the same transformation for all arguments. + bool operator==(const operator_sum &other) const; + + /// FIXME: Protect this once I can do deeper testing in `unittests`. + // protected: + std::vector get_terms() const { return m_terms; } +}; +operator_sum operator*(std::complex other, operator_sum self); +operator_sum operator+(std::complex other, operator_sum self); +operator_sum operator-(std::complex other, operator_sum self); +operator_sum operator*(double other, operator_sum self); +operator_sum operator+(double other, operator_sum self); +operator_sum operator-(double other, operator_sum self); + +/// @brief Represents an operator expression consisting of a product of +/// elementary and scalar operators. Operator expressions cannot be used within +/// quantum kernels, but they provide methods to convert them to data types +/// that can. +class product_operator : public operator_sum { +private: + std::vector> m_terms; + +public: + product_operator() = default; + ~product_operator() = default; + + // Constructor for an operator expression that represents a product + // of scalar and elementary operators. + // arg atomic_operators : The operators of which to compute the product when + // evaluating the operator expression. + product_operator( + std::vector> + atomic_operators); + + // Arithmetic overloads against all other operator types. + operator_sum operator+(std::complex other); + operator_sum operator-(std::complex other); + product_operator operator*(std::complex other); + product_operator operator*=(std::complex other); + operator_sum operator+(double other); + operator_sum operator-(double other); + product_operator operator*(double other); + product_operator operator*=(double other); + operator_sum operator+(scalar_operator other); + operator_sum operator-(scalar_operator other); + product_operator operator*(scalar_operator other); + product_operator operator*=(scalar_operator other); + operator_sum operator+(product_operator other); + operator_sum operator-(product_operator other); + product_operator operator*(product_operator other); + product_operator operator*=(product_operator other); + operator_sum operator+(elementary_operator other); + operator_sum operator-(elementary_operator other); + product_operator operator*(elementary_operator other); + product_operator operator*=(elementary_operator other); + operator_sum operator+(operator_sum other); + operator_sum operator-(operator_sum other); + operator_sum operator*(operator_sum other); + + /// @brief True, if the other value is an operator_sum with equivalent terms, + /// and False otherwise. The equality takes into account that operator + /// addition is commutative, as is the product of two operators if they + /// act on different degrees of freedom. + /// The equality comparison does *not* take commutation relations into + /// account, and does not try to reorder terms `blockwise`; it may hence + /// evaluate to False, even if two operators in reality are the same. + /// If the equality evaluates to True, on the other hand, the operators + /// are guaranteed to represent the same transformation for all arguments. + bool operator==(product_operator other); + + /// @brief Return the `product_operator` as a string. + std::string to_string() const; + + /// @brief Return the `operator_sum` as a matrix. + /// @arg `dimensions` : A mapping that specifies the number of levels, + /// that is, the dimension of each degree of freedom + /// that the operator acts on. Example for two, 2-level + /// degrees of freedom: `{0:2, 1:2}`. + /// @arg `parameters` : A map of the parameter names to their concrete, + /// complex values. + matrix_2 + to_matrix(const std::map dimensions, + const std::map> parameters) const; + + /// @brief Creates a representation of the operator as a `cudaq::pauli_word` + /// that can be passed as an argument to quantum kernels. + // pauli_word to_pauli_word(); + + /// @brief The degrees of freedom that the operator acts on in canonical + /// order. + std::vector degrees() const; + + /// @brief Return the number of operator terms that make up this product + /// operator. + int term_count() const { return m_terms.size(); } + + /// FIXME: Protect this once I can do deeper testing in `unittests`. + // protected: + std::vector> + get_terms() const { + return m_terms; + }; +}; +operator_sum operator+(std::complex other, product_operator self); +operator_sum operator-(std::complex other, product_operator self); +product_operator operator*(std::complex other, product_operator self); +operator_sum operator+(double other, product_operator self); +operator_sum operator-(double other, product_operator self); +product_operator operator*(double other, product_operator self); + +class elementary_operator : public product_operator { +private: + std::map m_ops; + +public: + // The constructor should never be called directly by the user: + // Keeping it internally documentd for now, however. + /// @brief Constructor. + /// @arg operator_id : The ID of the operator as specified when it was + /// defined. + /// @arg degrees : the degrees of freedom that the operator acts upon. + elementary_operator(std::string operator_id, std::vector degrees); + + // Copy constructor. + elementary_operator(const elementary_operator &other); + elementary_operator(elementary_operator &other); + + // Arithmetic overloads against all other operator types. + operator_sum operator+(std::complex other); + operator_sum operator-(std::complex other); + product_operator operator*(std::complex other); + operator_sum operator+(double other); + operator_sum operator-(double other); + product_operator operator*(double other); + operator_sum operator+(scalar_operator other); + operator_sum operator-(scalar_operator other); + product_operator operator*(scalar_operator other); + operator_sum operator+(elementary_operator other); + operator_sum operator-(elementary_operator other); + product_operator operator*(elementary_operator other); + operator_sum operator+(product_operator other); + operator_sum operator-(product_operator other); + product_operator operator*(product_operator other); + operator_sum operator+(operator_sum other); + operator_sum operator-(operator_sum other); + operator_sum operator+=(operator_sum other); + operator_sum operator-=(operator_sum other); + operator_sum operator*(operator_sum other); + + /// @brief True, if the other value is an elementary operator with the same id + /// acting on the same degrees of freedom, and False otherwise. + bool operator==(elementary_operator other); + + /// @brief Return the `elementary_operator` as a string. + std::string to_string() const; + + /// @brief Return the `elementary_operator` as a matrix. + /// @arg `dimensions` : A map specifying the number of levels, + /// that is, the dimension of each degree of freedom + /// that the operator acts on. Example for two, 2-level + /// degrees of freedom: `{0 : 2, 1 : 2}`. + matrix_2 + to_matrix(const std::map dimensions, + const std::map> parameters) const; + + // Predefined operators. + static elementary_operator identity(int degree); + static elementary_operator zero(int degree); + static elementary_operator annihilate(int degree); + static elementary_operator create(int degree); + static elementary_operator momentum(int degree); + static elementary_operator number(int degree); + static elementary_operator parity(int degree); + static elementary_operator position(int degree); + /// FIXME: + static elementary_operator squeeze(int degree, + std::complex amplitude); + static elementary_operator displace(int degree, + std::complex amplitude); + + /// @brief Adds the definition of an elementary operator with the given id to + /// the class. After definition, an the defined elementary operator can be + /// instantiated by providing the operator id as well as the degree(s) of + /// freedom that it acts on. An elementary operator is a parameterized object + /// acting on certain degrees of freedom. To evaluate an operator, for example + /// to compute its matrix, the level, that is the dimension, for each degree + /// of freedom it acts on must be provided, as well as all additional + /// parameters. Additional parameters must be provided in the form of keyword + /// arguments. Note: The dimensions passed during operator evaluation are + /// automatically validated against the expected dimensions specified during + /// definition - the `create` function does not need to do this. + /// @arg operator_id : A string that uniquely identifies the defined operator. + /// @arg expected_dimensions : Defines the number of levels, that is the + /// dimension, + /// for each degree of freedom in canonical (that is sorted) order. A + /// negative or zero value for one (or more) of the expected dimensions + /// indicates that the operator is defined for any dimension of the + /// corresponding degree of freedom. + /// @arg create : Takes any number of complex-valued arguments and returns the + /// matrix representing the operator in canonical order. If the matrix + /// can be defined for any number of levels for one or more degree of + /// freedom, the `create` function must take an argument called + /// `dimension` (or `dim` for short), if the operator acts on a single + /// degree of freedom, and an argument called `dimensions` (or `dims` for + /// short), if the operator acts + /// on multiple degrees of freedom. + template + void define(std::string operator_id, std::map expected_dimensions, + Func create) { + if (m_ops.find(operator_id) != m_ops.end()) { + // todo: make a nice error message to say op already exists + throw; + } + auto defn = Definition(); + defn.create_definition(operator_id, expected_dimensions, create); + m_ops[operator_id] = defn; + } + + // Attributes. + + /// @brief The number of levels, that is the dimension, for each degree of + /// freedom in canonical order that the operator acts on. A value of zero or + /// less indicates that the operator is defined for any dimension of that + /// degree. + std::map expected_dimensions; + /// @brief The degrees of freedom that the operator acts on in canonical + /// order. + std::vector degrees; + std::string id; + + // /// @brief Creates a representation of the operator as `pauli_word` that + // can be passed as an argument to quantum kernels. + // pauli_word to_pauli_word ovveride(); +}; +// Reverse order arithmetic for elementary operators against pure scalars. +operator_sum operator+(std::complex other, elementary_operator self); +operator_sum operator-(std::complex other, elementary_operator self); +product_operator operator*(std::complex other, + elementary_operator self); +operator_sum operator+(double other, elementary_operator self); +operator_sum operator-(double other, elementary_operator self); +product_operator operator*(double other, elementary_operator self); + +class scalar_operator : public product_operator { +private: + // If someone gave us a constant value, we will just return that + // directly to them when they call `evaluate`. + std::complex m_constant_value; + +public: + /// @brief Constructor that just takes a callback function with no + /// arguments. + + scalar_operator(ScalarCallbackFunction &&create) { + generator = ScalarCallbackFunction(create); + } + + /// @brief Constructor that just takes and returns a complex double value. + /// @NOTE: This replicates the behavior of the python `scalar_operator::const` + /// without the need for an extra member function. + scalar_operator(std::complex value); + scalar_operator(double value); + + // Arithmetic overloads against other operator types. + scalar_operator operator+(scalar_operator other); + scalar_operator operator-(scalar_operator other); + scalar_operator operator*(scalar_operator other); + scalar_operator operator/(scalar_operator other); + /// TODO: implement and test pow + scalar_operator pow(scalar_operator other); + operator_sum operator+(elementary_operator other); + operator_sum operator-(elementary_operator other); + product_operator operator*(elementary_operator other); + operator_sum operator+(product_operator other); + operator_sum operator-(product_operator other); + product_operator operator*(product_operator other); + operator_sum operator+(operator_sum other); + operator_sum operator-(operator_sum other); + operator_sum operator*(operator_sum other); + + /// @brief Return the scalar operator as a concrete complex value. + std::complex + evaluate(std::map> parameters) const; + + // Return the scalar operator as a 1x1 matrix. This is needed for + // compatability with the other inherited classes. + matrix_2 + to_matrix(const std::map dimensions, + const std::map> parameters) const; + + // /// @brief Returns true if other is a scalar operator with the same + // /// generator. + // bool operator==(scalar_operator other); + + /// @brief The function that generates the value of the scalar operator. + /// The function can take a vector of complex-valued arguments + /// and returns a number. + ScalarCallbackFunction generator; + + // Only populated when we've performed arithmetic between various + // scalar operators. + std::vector _operators_to_compose; + + /// NOTE: We should revisit these constructors and remove any that have + /// become unnecessary as the implementation improves. + scalar_operator() = default; + // Copy constructor. + scalar_operator(const scalar_operator &other); + scalar_operator(scalar_operator &other); + + ~scalar_operator() = default; + + // Need this property for consistency with other inherited types. + // Particularly, to be used when the scalar operator is held within + // a variant type next to elementary operators. + std::vector degrees = {-1}; + + // REMOVEME: just using this as a temporary patch: + std::complex get_val() { return m_constant_value; }; +}; + +scalar_operator operator+(scalar_operator self, std::complex other); +scalar_operator operator-(scalar_operator self, std::complex other); +scalar_operator operator*(scalar_operator self, std::complex other); +scalar_operator operator/(scalar_operator self, std::complex other); +scalar_operator operator+(std::complex other, scalar_operator self); +scalar_operator operator-(std::complex other, scalar_operator self); +scalar_operator operator*(std::complex other, scalar_operator self); +scalar_operator operator/(std::complex other, scalar_operator self); +scalar_operator operator+(scalar_operator self, double other); +scalar_operator operator-(scalar_operator self, double other); +scalar_operator operator*(scalar_operator self, double other); +scalar_operator operator/(scalar_operator self, double other); +scalar_operator operator+(double other, scalar_operator self); +scalar_operator operator-(double other, scalar_operator self); +scalar_operator operator*(double other, scalar_operator self); +scalar_operator operator/(double other, scalar_operator self); +void operator+=(scalar_operator &self, std::complex other); +void operator-=(scalar_operator &self, std::complex other); +void operator*=(scalar_operator &self, std::complex other); +void operator/=(scalar_operator &self, std::complex other); +void operator+=(scalar_operator &self, scalar_operator other); +void operator-=(scalar_operator &self, scalar_operator other); +void operator*=(scalar_operator &self, scalar_operator other); +void operator/=(scalar_operator &self, scalar_operator other); + +/// @brief Representation of a time-dependent Hamiltonian for Rydberg system +class rydberg_hamiltonian : public operator_sum { +public: + using Coordinate = std::pair; + + /// @brief Constructor. + /// @param atom_sites List of 2D coordinates for trap sites. + /// @param amplitude Time-dependent driving amplitude, Omega(t). + /// @param phase Time-dependent driving phase, phi(t). + /// @param delta_global Time-dependent driving detuning, Delta_global(t). + /// @param atom_filling Optional. Marks occupied trap sites (1) and empty + /// sites (0). Defaults to all sites occupied. + /// @param delta_local Optional. A tuple of Delta_local(t) and site dependent + /// local detuning factors. + rydberg_hamiltonian( + const std::vector &atom_sites, + const scalar_operator &litude, const scalar_operator &phase, + const scalar_operator &delta_global, + const std::vector &atom_filling = {}, + const std::optional>> + &delta_local = std::nullopt); + + /// @brief Get atom sites. + const std::vector &get_atom_sites() const; + + /// @brief Get atom filling. + const std::vector &get_atom_filling() const; + + /// @brief Get amplitude operator. + const scalar_operator &get_amplitude() const; + + /// @brief Get phase operator. + const scalar_operator &get_phase() const; + + /// @brief Get global detuning operator. + const scalar_operator &get_delta_global() const; + +private: + std::vector atom_sites; + std::vector atom_filling; + scalar_operator amplitude; + scalar_operator phase; + scalar_operator delta_global; + std::optional>> delta_local; +}; + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/runge_kutta_integrator.h b/runtime/cudaq/runge_kutta_integrator.h new file mode 100644 index 0000000000..9914258386 --- /dev/null +++ b/runtime/cudaq/runge_kutta_integrator.h @@ -0,0 +1,47 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "base_integrator.h" +#include "runge_kutta_time_stepper.h" +#include + +namespace cudaq { +template +class RungeKuttaIntegrator : public BaseIntegrator { +public: + using DerivativeFunction = std::function; + + explicit RungeKuttaIntegrator(DerivativeFunction f) + : stepper(std::make_shared>(f)) {} + + // Initializes the integrator + void post_init() override { + if (!this->stepper) { + throw std::runtime_error("Time stepper is not set"); + } + } + + // Advances the system's state from current time to `t` + void integrate(double target_t) override { + if (!this->schedule || !this->hamiltonian) { + throw std::runtime_error("System is not properly set!"); + } + + while (this->t < target_t) { + stepper->compute(this->state, this->t); + // Time step size + this->t += 0.01; + } + } + +private: + std::shared_ptr> stepper; +}; +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/runge_kutta_time_stepper.h b/runtime/cudaq/runge_kutta_time_stepper.h new file mode 100644 index 0000000000..1dcd1f69cc --- /dev/null +++ b/runtime/cudaq/runge_kutta_time_stepper.h @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "base_time_stepper.h" +#include + +namespace cudaq { +template +class RungeKuttaTimeStepper : public BaseTimeStepper { +public: + using DerivativeFunction = std::function; + + RungeKuttaTimeStepper(DerivativeFunction f) : derivativeFunc(f) {} + + void compute(TState &state, double t, double dt = 0.01) override { + // 4th order Runge-Kutta method + TState k1 = derivativeFunc(state, t); + TState k2 = derivativeFunc(state + (dt / 2.0) * k1, t + dt / 2.0); + TState k3 = derivativeFunc(state + (dt / 2.0) * k2, t + dt / 2.0); + TState k4 = derivativeFunc(state + dt * k3, t + dt); + + state = state + (dt / 6.0) * (k1 + 2 * k2 + 2 * k3 + k4); + } + +private: + DerivativeFunction derivativeFunc; +}; +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/schedule.h b/runtime/cudaq/schedule.h new file mode 100644 index 0000000000..67d68fd951 --- /dev/null +++ b/runtime/cudaq/schedule.h @@ -0,0 +1,105 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace cudaq { + +/// @brief Create a schedule for evaluating an operator expression at different +/// steps. +class Schedule { +public: + /// Iterator tags. May be superfluous. + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = std::complex; + using pointer = std::complex *; + using reference = std::complex &; + +private: + pointer m_ptr; + std::vector> _steps; + std::vector _parameters; + std::function(const std::string &, + const std::complex &)> + _value_function; + int _current_idx; + +public: + Schedule(pointer ptr) : m_ptr(ptr){}; + + /// @brief Constructor. + /// @arg steps: The sequence of steps in the schedule. Restricted to a vector + /// of complex values. + /// @arg parameters: A sequence of strings representing the parameter names of + /// an operator expression. + /// @arg value_function: A function that takes the name of a parameter as well + /// as an additional value ("step") of arbitrary type as argument and returns + /// the complex value for that parameter at the given step. + /// @details current_idx: Intializes the current index (_current_idx) to -1 to + /// indicate that iteration has not yet begun. Once iteration starts, + /// _current_idx will be used to track the position in the sequence of steps. + Schedule(const std::vector> steps, + const std::vector parameters, + std::function(const std::string &, + const std::complex &)> + value_function); + + /// Below, I define what I believe are the minimal necessary methods needed + /// for this to behave like an iterable. This should be revisited in the + /// implementation phase. + + // Pointers. + /// @brief `Dereference` operator to access the current step value. + /// @return Reference to current complex step value. + reference operator*() const; + + /// @brief Arrow operator to access the pointer the current step value. + /// @return Pointer to the current complex step value. + pointer operator->(); + + // Prefix increment. + /// @brief Prefix increment operator to move to the next step in the schedule. + /// @return Reference to the updated Schedule object. + Schedule &operator++(); + + // Postfix increment. + /// @brief `Postfix` increment operator to move to the next step in the + /// schedule. + /// @return Copy of the previous Schedule state. + Schedule operator++(int); + + // Comparison. + /// @brief Equality comparison operator. + /// @param a: First Schedule object. + /// @param b: Second Schedule object. + /// @return True if both schedules point to the same step, false otherwise + friend bool operator==(const Schedule &a, const Schedule &b); + + /// @brief Inequality comparison operator. + /// @param a: First Schedule object. + /// @param b: Second Schedule object. + /// @return True if both schedules point to different steps, false otherwise + friend bool operator!=(const Schedule &a, const Schedule &b); + + /// @brief Reset the schedule iterator to the beginning. + void reset(); + + /// @brief Get the current step in the schedule. + /// @return The current complex step value as an optional. If no valid step, + /// returns std::nullopt. + std::optional> current_step() const; +}; +} // namespace cudaq diff --git a/runtime/cudaq/utils/tensor.cpp b/runtime/cudaq/utils/tensor.cpp index f37f959090..6d4aaa9764 100644 --- a/runtime/cudaq/utils/tensor.cpp +++ b/runtime/cudaq/utils/tensor.cpp @@ -7,6 +7,7 @@ ******************************************************************************/ #include "cudaq/utils/tensor.h" +#include #include inline std::complex &access(std::complex *p, diff --git a/runtime/cudaq/utils/tensor.h b/runtime/cudaq/utils/tensor.h index d9f9099264..8287ab93de 100644 --- a/runtime/cudaq/utils/tensor.h +++ b/runtime/cudaq/utils/tensor.h @@ -38,6 +38,9 @@ class matrix_2 { using Dimensions = std::pair; matrix_2() = default; + matrix_2(std::size_t rows, std::size_t cols) + : dimensions(std::make_pair(rows, cols)), + data{new std::complex[rows * cols]} {} matrix_2(const matrix_2 &other) : dimensions{other.dimensions}, data{new std::complex[get_size(other.dimensions)]} { diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 81b7202ae6..e2baef400c 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -44,11 +44,24 @@ set(CUDAQ_RUNTIME_TEST_SOURCES common/NoiseModelTester.cpp integration/tracer_tester.cpp integration/gate_library_tester.cpp + dynamics/scalar_ops_simple.cpp + dynamics/scalar_ops_arithmetic.cpp + dynamics/elementary_ops_simple.cpp + dynamics/elementary_ops_arithmetic.cpp + dynamics/product_operators_arithmetic.cpp + dynamics/test_runge_kutta_time_stepper.cpp + dynamics/test_runge_kutta_integrator.cpp + dynamics/test_helpers.cpp + dynamics/rydberg_hamiltonian.cpp + dynamics/test_cudm_helpers.cpp + dynamics/test_cudm_state.cpp ) # Make it so we can get function symbols set (CMAKE_ENABLE_EXPORTS TRUE) +include_directories(/usr/local/cuda/targets/x86_64-linux/include) + ## This Macro allows us to create a test_runtime executable for ## the sources in CUDAQ_RUNTIME_TEST_SOURCE for a specific backend simulator macro (create_tests_with_backend NVQIR_BACKEND EXTRA_BACKEND_TESTER) @@ -71,7 +84,9 @@ macro (create_tests_with_backend NVQIR_BACKEND EXTRA_BACKEND_TESTER) cudaq fmt::fmt-header-only cudaq-platform-default cudaq-builder - gtest_main) + gtest_main + $ENV{CUQUANTUM_INSTALL_PREFIX}/lib/libcudensitymat.so.0 + /usr/local/cuda-12.0/targets/x86_64-linux/lib/libcudart.so.12) set(TEST_LABELS "") if (${NVQIR_BACKEND} STREQUAL "qpp") target_compile_definitions(${TEST_EXE_NAME} PRIVATE -DCUDAQ_SIMULATION_SCALAR_FP64) @@ -257,6 +272,32 @@ target_link_libraries(test_spin gtest_main) gtest_discover_tests(test_spin) +# Create an executable for operators UnitTests +set(CUDAQ_OPERATOR_TEST_SOURCES + dynamics/operator_sum.cpp + dynamics/elementary_ops_simple.cpp + dynamics/elementary_ops_arithmetic.cpp + dynamics/scalar_ops_simple.cpp + dynamics/scalar_ops_arithmetic.cpp + dynamics/product_operators_arithmetic.cpp + dynamics/test_cudm_helpers.cpp + dynamics/test_cudm_state.cpp +) +add_executable(test_operators main.cpp ${CUDAQ_OPERATOR_TEST_SOURCES}) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) + target_link_options(test_operators PRIVATE -Wl,--no-as-needed) +endif() +target_link_libraries(test_operators + PRIVATE + cudaq-spin + cudaq-operators + cudaq + gtest_main + $ENV{CUQUANTUM_INSTALL_PREFIX}/lib/libcudensitymat.so.0 + /usr/local/cuda-12.0/targets/x86_64-linux/lib/libcudart.so.12 + fmt::fmt-header-only) +gtest_discover_tests(test_operators) + add_subdirectory(plugin) # build the test qudit execution manager @@ -375,7 +416,8 @@ target_link_libraries(${TEST_EXE_NAME} cudaq-platform-default cudaq-rest-qpu cudaq-builder - gtest_main) + gtest_main + $ENV{CUQUANTUM_INSTALL_PREFIX}/lib/libcudensitymat.so.0) set(TEST_LABELS "") if ("${TEST_LABELS}" STREQUAL "") gtest_discover_tests(${TEST_EXE_NAME}) @@ -417,4 +459,3 @@ if (CUDAQ_ENABLE_PYTHON) gtest_discover_tests(test_domains TEST_SUFFIX _Sampling PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}/python") endif() - diff --git a/unittests/dynamics/elementary_ops_arithmetic.cpp b/unittests/dynamics/elementary_ops_arithmetic.cpp new file mode 100644 index 0000000000..fb5c85ac49 --- /dev/null +++ b/unittests/dynamics/elementary_ops_arithmetic.cpp @@ -0,0 +1,604 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/operators.h" +#include + +/// STATUS: +/// 1. I've generated all of the `want` matrices for each test, and prepared +/// the test to check against the `got` matrix. Now waiting on finishing the +/// full `to_matrix` conversion to be able to do so. +/// + +namespace utils_0 { +void checkEqual(cudaq::matrix_2 a, cudaq::matrix_2 b) { + ASSERT_EQ(a.get_rank(), b.get_rank()); + ASSERT_EQ(a.get_rows(), b.get_rows()); + ASSERT_EQ(a.get_columns(), b.get_columns()); + ASSERT_EQ(a.get_size(), b.get_size()); + for (std::size_t i = 0; i < a.get_rows(); i++) { + for (std::size_t j = 0; j < a.get_columns(); j++) { + double a_val = a[{i, j}].real(); + double b_val = b[{i, j}].real(); + EXPECT_NEAR(a_val, b_val, 1e-8); + } + } +} + +cudaq::matrix_2 zero_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + return mat; +} + +cudaq::matrix_2 id_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = 1.0 + 0.0j; + return mat; +} + +cudaq::matrix_2 annihilate_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 create_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 position_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 momentum_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 number_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = static_cast(i) + 0.0j; + return mat; +} + +cudaq::matrix_2 parity_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + return mat; +} + +// cudaq::matrix_2 displace_matrix(std::size_t size, +// std::complex amplitude) { +// auto mat = cudaq::matrix_2(size, size); +// for (std::size_t i = 0; i + 1 < size; i++) { +// mat[{i + 1, i}] = +// amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; +// mat[{i, i + 1}] = -1. * std::conj(amplitude) * (0.5 * 'j') * +// std::sqrt(static_cast(i + 1)) + +// 0.0 * 'j'; +// } +// return mat.exp(); +// } + +} // namespace utils_0 + +TEST(ExpressionTester, checkPreBuiltElementaryOpsScalars) { + + auto function = [](std::map> parameters) { + return parameters["value"]; + }; + + /// Keeping these fixed for these more simple tests. + int level_count = 3; + int degree_index = 0; + double const_scale_factor = 2.0; + + // `elementary_operator + scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(const_scale_factor); + + // Produces an `operator_sum` type. + auto sum = self + other; + auto reverse = other + self; + + // Check the `operator_sum` attributes. + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = sum.to_matrix({{degree_index, level_count}}, {}); + // auto got_reverse_matrix = reverse.to_matrix({{degree_index, + // level_count}}, {}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) + scaled_identity; + auto want_reverse_matrix = + scaled_identity + utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator + scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(function); + + // Produces an `operator_sum` type. + auto sum = self + other; + auto reverse = other + self; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = sum.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); auto got_reverse_matrix = + // reverse.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) + scaled_identity; + auto want_reverse_matrix = + scaled_identity + utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator - scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(const_scale_factor); + + // Produces an `operator_sum` type. + auto sum = self - other; + auto reverse = other - self; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = sum.to_matrix({{degree_index, level_count}}, {}); + // auto got_reverse_matrix = reverse.to_matrix({{degree_index, + // level_count}}, {}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) - scaled_identity; + auto want_reverse_matrix = + scaled_identity - utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator - scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(function); + + // Produces an `operator_sum` type. + auto sum = self - other; + auto reverse = other - self; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = sum.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); auto got_reverse_matrix = + // reverse.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) + scaled_identity; + auto want_reverse_matrix = + scaled_identity + utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator * scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(const_scale_factor); + + // Produces an `product_operator` type. + auto product = self * other; + auto reverse = other * self; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = product.to_matrix({{degree_index, level_count}}, {}); + // auto got_reverse_matrix = reverse.to_matrix({{degree_index, + // level_count}}, {}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) * scaled_identity; + auto want_reverse_matrix = + scaled_identity * utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator * scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(function); + + // Produces an `product_operator` type. + auto product = self * other; + auto reverse = other * self; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = product.to_matrix({{degree_index, level_count}}, + // {"value", const_scale_factor}); auto got_reverse_matrix = + // reverse.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) * scaled_identity; + auto want_reverse_matrix = + scaled_identity * utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } +} + +/// Prebuilt elementary ops against one another. +TEST(ExpressionTester, checkPreBuiltElementaryOpsSelf) { + + /// Keeping this fixed throughout. + int level_count = 3; + + // Addition, same DOF. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(0); + + // Produces an `operator_sum` type. + auto sum = self + other; + ASSERT_TRUE(sum.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + // auto got_matrix = sum.to_matrix({{0, level_count}}, {}); + auto want_matrix = utils_0::annihilate_matrix(level_count) + + utils_0::create_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Addition, different DOF's. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(1); + + // Produces an `operator_sum` type. + auto sum = self + other; + ASSERT_TRUE(sum.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto annihilate_full = + cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto create_full = cudaq::kronecker(utils_0::create_matrix(level_count), + utils_0::id_matrix(level_count)); + // auto got_matrix = sum.to_matrix({{0, level_count}}, {}); + auto want_matrix = annihilate_full + create_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Subtraction, same DOF. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(0); + + // Produces an `operator_sum` type. + auto sum = self - other; + ASSERT_TRUE(sum.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + // auto got_matrix = sum.to_matrix({{0, level_count}}, {}); + auto want_matrix = utils_0::annihilate_matrix(level_count) - + utils_0::create_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Subtraction, different DOF's. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(1); + + // Produces an `operator_sum` type. + auto sum = self - other; + ASSERT_TRUE(sum.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto annihilate_full = + cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto create_full = cudaq::kronecker(utils_0::create_matrix(level_count), + utils_0::id_matrix(level_count)); + // auto got_matrix = sum.to_matrix({{0, level_count}}, {}); + auto want_matrix = annihilate_full - create_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Multiplication, same DOF. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(0); + + // Produces an `product_operator` type. + auto product = self * other; + ASSERT_TRUE(product.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + // auto got_matrix = product.to_matrix({{0, level_count}}, {}); + auto want_matrix = utils_0::annihilate_matrix(level_count) * + utils_0::create_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Multiplication, different DOF's. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(1); + + // Produces an `product_operator` type. + auto product = self * other; + ASSERT_TRUE(product.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto annihilate_full = + cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto create_full = cudaq::kronecker(utils_0::create_matrix(level_count), + utils_0::id_matrix(level_count)); + // auto got_matrix = product.to_matrix({{0, level_count}}, {}); + auto want_matrix = annihilate_full * create_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } +} + +/// Testing arithmetic between elementary operators and operator +/// sums. +TEST(ExpressionTester, checkElementaryOpsAgainstOpSum) { + + /// Keeping this fixed throughout. + int level_count = 3; + + /// `elementary_operator + operator_sum` and `operator_sum + + /// elementary_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + /// Creating an arbitrary operator sum to work against. + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + + auto got = self + operator_sum; + auto reverse = operator_sum + self; + + ASSERT_TRUE(got.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + + // auto got_matrix = got.to_matrix({{0, level_count}, {1, level_count}}, + // {}); auto got_reverse_matrix = reverse.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = self_full + term_0_full + term_1_full; + auto want_reverse_matrix = term_0_full + term_1_full + self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + /// `elementary_operator - operator_sum` and `operator_sum - + /// elementary_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + /// Creating an arbitrary operator sum to work against. + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + + auto got = self - operator_sum; + auto reverse = operator_sum - self; + + ASSERT_TRUE(got.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + + // auto got_matrix = got.to_matrix({{0, level_count}, {1, level_count}}, + // {}); auto got_reverse_matrix = reverse.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = self_full - term_0_full - term_1_full; + auto want_reverse_matrix = term_0_full + term_1_full - self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + /// `elementary_operator * operator_sum` and `operator_sum * + /// elementary_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + /// Creating an arbitrary operator sum to work against. + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + + auto got = self * operator_sum; + auto reverse = operator_sum * self; + + ASSERT_TRUE(got.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto &term : got.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + + for (auto &term : reverse.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + auto sum_full = term_0_full + term_1_full; + + // auto got_matrix = got.to_matrix({{0, level_count}, {1, level_count}}, + // {}); auto got_reverse_matrix = reverse.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = self_full * sum_full; + auto want_reverse_matrix = sum_full * self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + /// `operator_sum += elementary_operator` + { + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + operator_sum += cudaq::elementary_operator::annihilate(0); + + ASSERT_TRUE(operator_sum.term_count() == 3); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + + // auto got_matrix = operator_sum.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = term_0_full + term_1_full + self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } + + /// `operator_sum -= elementary_operator` + { + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + operator_sum -= cudaq::elementary_operator::annihilate(0); + + ASSERT_TRUE(operator_sum.term_count() == 3); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + + // auto got_matrix = operator_sum.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = term_0_full + term_1_full - self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } + + /// `operator_sum *= elementary_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + /// Creating an arbitrary operator sum to work against. + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + + operator_sum *= self; + + ASSERT_TRUE(operator_sum.term_count() == 2); + + for (auto &term : operator_sum.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + auto sum_full = term_0_full + term_1_full; + + // auto got_matrix = operator_sum.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = sum_full * self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } +} diff --git a/unittests/dynamics/elementary_ops_simple.cpp b/unittests/dynamics/elementary_ops_simple.cpp new file mode 100644 index 0000000000..172beeffe8 --- /dev/null +++ b/unittests/dynamics/elementary_ops_simple.cpp @@ -0,0 +1,208 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/operators.h" +#include + +namespace utils { +void checkEqual(cudaq::matrix_2 a, cudaq::matrix_2 b) { + ASSERT_EQ(a.get_rank(), b.get_rank()); + ASSERT_EQ(a.get_rows(), b.get_rows()); + ASSERT_EQ(a.get_columns(), b.get_columns()); + ASSERT_EQ(a.get_size(), b.get_size()); + for (std::size_t i = 0; i < a.get_rows(); i++) { + for (std::size_t j = 0; j < a.get_columns(); j++) { + double a_val = a[{i, j}].real(); + double b_val = b[{i, j}].real(); + EXPECT_NEAR(a_val, b_val, 1e-8); + } + } +} + +cudaq::matrix_2 zero_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + return mat; +} + +cudaq::matrix_2 id_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = 1.0 + 0.0j; + return mat; +} + +cudaq::matrix_2 annihilate_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 create_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 position_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 momentum_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 number_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = static_cast(i) + 0.0j; + return mat; +} + +cudaq::matrix_2 parity_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + return mat; +} + +// cudaq::matrix_2 displace_matrix(std::size_t size, +// std::complex amplitude) { +// auto mat = cudaq::matrix_2(size, size); +// for (std::size_t i = 0; i + 1 < size; i++) { +// mat[{i + 1, i}] = +// amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; +// mat[{i, i + 1}] = -1. * std::conj(amplitude) * (0.5 * 'j') * +// std::sqrt(static_cast(i + 1)) + +// 0.0 * 'j'; +// } +// return mat.exp(); +// } + +} // namespace utils + +TEST(ExpressionTester, checkPreBuiltElementaryOps) { + std::vector levels = {2, 3, 4, 5}; + + // Keeping this fixed throughout. + int degree_index = 0; + + // Identity operator. + { + for (auto level_count : levels) { + // cudaq::operators::identity(int degree) + auto id = cudaq::elementary_operator::identity(degree_index); + auto got_id = id.to_matrix({{degree_index, level_count}}, {}); + auto want_id = utils::id_matrix(level_count); + utils::checkEqual(want_id, got_id); + } + } + + // Zero operator. + { + for (auto level_count : levels) { + auto zero = cudaq::elementary_operator::zero(degree_index); + auto got_zero = zero.to_matrix({{degree_index, level_count}}, {}); + auto want_zero = utils::zero_matrix(level_count); + utils::checkEqual(want_zero, got_zero); + } + } + + // Annihilation operator. + { + for (auto level_count : levels) { + auto annihilate = cudaq::elementary_operator::annihilate(degree_index); + auto got_annihilate = + annihilate.to_matrix({{degree_index, level_count}}, {}); + auto want_annihilate = utils::annihilate_matrix(level_count); + utils::checkEqual(want_annihilate, got_annihilate); + } + } + + // Creation operator. + { + for (auto level_count : levels) { + auto create = cudaq::elementary_operator::create(degree_index); + auto got_create = create.to_matrix({{degree_index, level_count}}, {}); + auto want_create = utils::create_matrix(level_count); + utils::checkEqual(want_create, got_create); + } + } + + // Position operator. + { + for (auto level_count : levels) { + auto position = cudaq::elementary_operator::position(degree_index); + auto got_position = position.to_matrix({{degree_index, level_count}}, {}); + auto want_position = utils::position_matrix(level_count); + utils::checkEqual(want_position, got_position); + } + } + + // Momentum operator. + { + for (auto level_count : levels) { + auto momentum = cudaq::elementary_operator::momentum(degree_index); + auto got_momentum = momentum.to_matrix({{degree_index, level_count}}, {}); + auto want_momentum = utils::momentum_matrix(level_count); + utils::checkEqual(want_momentum, got_momentum); + } + } + + // Number operator. + { + for (auto level_count : levels) { + auto number = cudaq::elementary_operator::number(degree_index); + auto got_number = number.to_matrix({{degree_index, level_count}}, {}); + auto want_number = utils::number_matrix(level_count); + utils::checkEqual(want_number, got_number); + } + } + + // Parity operator. + { + for (auto level_count : levels) { + auto parity = cudaq::elementary_operator::parity(degree_index); + auto got_parity = parity.to_matrix({{degree_index, level_count}}, {}); + auto want_parity = utils::parity_matrix(level_count); + utils::checkEqual(want_parity, got_parity); + } + } + + // // // Displacement operator. + // // { + // // for (auto level_count : levels) { + // // auto amplitude = 1.0 + 1.0j; + // // auto displace = cudaq::elementary_operator::displace(degree_index, + // // amplitude); auto got_displace = + // utils::displace.to_matrix({{degree_index, + // // level_count}}, {}); auto want_displace = + // displace_matrix(level_count, + // // amplitude); utils::checkEqual(want_displace, got_displace); + // // } + // // } + + // TODO: Squeeze operator. +} + +// TEST(ExpressionTester, checkCustomElementaryOps) { +// // pass +// } \ No newline at end of file diff --git a/unittests/dynamics/operator_sum.cpp b/unittests/dynamics/operator_sum.cpp new file mode 100644 index 0000000000..c68779ef45 --- /dev/null +++ b/unittests/dynamics/operator_sum.cpp @@ -0,0 +1,572 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/matrix.h" +#include "cudaq/operators.h" +#include + +namespace utils_2 { +void checkEqual(cudaq::matrix_2 a, cudaq::matrix_2 b) { + ASSERT_EQ(a.get_rank(), b.get_rank()); + ASSERT_EQ(a.get_rows(), b.get_rows()); + ASSERT_EQ(a.get_columns(), b.get_columns()); + ASSERT_EQ(a.get_size(), b.get_size()); + for (std::size_t i = 0; i < a.get_rows(); i++) { + for (std::size_t j = 0; j < a.get_columns(); j++) { + double a_val = a[{i, j}].real(); + double b_val = b[{i, j}].real(); + EXPECT_NEAR(a_val, b_val, 1e-8); + } + } +} + +cudaq::matrix_2 zero_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + return mat; +} + +cudaq::matrix_2 id_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = 1.0 + 0.0j; + return mat; +} + +cudaq::matrix_2 annihilate_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 create_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 position_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 momentum_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 number_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = static_cast(i) + 0.0j; + return mat; +} + +cudaq::matrix_2 parity_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + return mat; +} + +// cudaq::matrix_2 displace_matrix(std::size_t size, +// std::complex amplitude) { +// auto mat = cudaq::matrix_2(size, size); +// for (std::size_t i = 0; i + 1 < size; i++) { +// mat[{i + 1, i}] = +// amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; +// mat[{i, i + 1}] = -1. * std::conj(amplitude) * (0.5 * 'j') * +// std::sqrt(static_cast(i + 1)) + +// 0.0 * 'j'; +// } +// return mat.exp(); +// } + +} // namespace utils_2 + +// TEST(ExpressionTester, checkProductOperatorSimple) { +// std::vector levels = {2, 3, 4}; + +// // std::set uniqueDegrees; +// // std::copy(this->degrees.begin(), this->degrees.end(), +// // std::inserter(uniqueDegrees, uniqueDegrees.begin())); +// // std::copy(other.degrees.begin(), other.degrees.end(), +// // std::inserter(uniqueDegrees, uniqueDegrees.begin())); + +// // Arithmetic only between elementary operators with +// // same number of levels. +// { +// // Same degrees of freedom. +// { +// for (auto level_count : levels) { +// auto op0 = cudaq::elementary_operator::annihilate(0); +// auto op1 = cudaq::elementary_operator::create(0); + +// cudaq::product_operator got = op0 * op1; +// auto got_matrix = got.to_matrix({{0, level_count}}, {}); + +// auto matrix0 = _annihilate_matrix(level_count); +// auto matrix1 = _create_matrix(level_count); +// auto want_matrix = matrix0 * matrix1; + +// // ASSERT_TRUE(want_matrix == got_matrix); +// } +// } + +// // // Different degrees of freedom. +// // { +// // for (auto level_count : levels) { +// // auto op0 = cudaq::elementary_operator::annihilate(0); +// // auto op1 = cudaq::elementary_operator::create(1); + +// // cudaq::product_operator got = op0 * op1; +// // auto got_matrix = +// // got.to_matrix({{0, level_count}, {1, level_count}}, {}); + +// // cudaq::product_operator got_reverse = op1 * op0; +// // auto got_matrix_reverse = +// // got_reverse.to_matrix({{0, level_count}, {1, level_count}}, +// {}); + +// // auto identity = _id_matrix(level_count); +// // auto matrix0 = _annihilate_matrix(level_count); +// // auto matrix1 = _create_matrix(level_count); + +// // auto fullHilbert0 = identity.kronecker(matrix0); +// // auto fullHilbert1 = matrix1.kronecker(identity); +// // auto want_matrix = fullHilbert0 * fullHilbert1; +// // auto want_matrix_reverse = fullHilbert1 * fullHilbert0; + +// // // ASSERT_TRUE(want_matrix == got_matrix); +// // // ASSERT_TRUE(want_matrix_reverse == got_matrix_reverse); +// // } +// // } + +// // // Different degrees of freedom, non-consecutive. +// // { +// // for (auto level_count : levels) { +// // auto op0 = cudaq::elementary_operator::annihilate(0); +// // auto op1 = cudaq::elementary_operator::create(2); + +// // // cudaq::product_operator got = op0 * op1; +// // // auto got_matrix = +// got.to_matrix({{0,level_count},{2,level_count}}, +// // // {}); +// // } +// // } + +// // // Different degrees of freedom, non-consecutive but all dimensions +// // // provided. +// // { +// // for (auto level_count : levels) { +// // auto op0 = cudaq::elementary_operator::annihilate(0); +// // auto op1 = cudaq::elementary_operator::create(2); + +// // // cudaq::product_operator got = op0 * op1; +// // // auto got_matrix = +// // // +// got.to_matrix({{0,level_count},{1,level_count},{2,level_count}}, +// // {}); +// // } +// // } +// } +// } + +// TEST(ExpressionTester, checkProductOperatorSimple) { + +// std::complex value_0 = 0.1 + 0.1; +// std::complex value_1 = 0.1 + 1.0; +// std::complex value_2 = 2.0 + 0.1; +// std::complex value_3 = 2.0 + 1.0; + +// auto local_variable = true; +// auto function = [&](std::map> parameters) +// { +// if (!local_variable) +// throw std::runtime_error("Local variable not detected."); +// return parameters["value"]; +// }; + +// // Scalar Ops against Elementary Ops +// { +// // Identity against constant. +// { +// auto id_op = cudaq::elementary_operator::identity(0); +// auto scalar_op = cudaq::scalar_operator(value_0); + +// // auto multiplication = scalar_op * id_op; +// // auto addition = scalar_op + id_op; +// // auto subtraction = scalar_op - id_op; +// } + +// // Identity against constant from lambda. +// { +// auto id_op = cudaq::elementary_operator::identity(0); +// auto scalar_op = cudaq::scalar_operator(function); + +// // auto multiplication = scalar_op * id_op; +// // auto addition = scalar_op + id_op; +// // auto subtraction = scalar_op - id_op; +// } +// } +// } + +TEST(ExpressionTester, checkOperatorSumAgainstScalarOperator) { + + // `operator_sum * scalar_operator` and `scalar_operator * operator_sum` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto product = sum * cudaq::scalar_operator(1.0); + auto reverse = cudaq::scalar_operator(1.0) * sum; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum + scalar_operator` and `scalar_operator + operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto sum = original + cudaq::scalar_operator(1.0); + auto reverse = cudaq::scalar_operator(1.0) + original; + + ASSERT_TRUE(sum.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum - scalar_operator` and `scalar_operator - operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto difference = original - cudaq::scalar_operator(1.0); + auto reverse = cudaq::scalar_operator(1.0) - original; + + ASSERT_TRUE(difference.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum *= scalar_operator` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum *= cudaq::scalar_operator(1.0); + + ASSERT_TRUE(sum.term_count() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum += scalar_operator` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum += cudaq::scalar_operator(1.0); + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum -= scalar_operator` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum -= cudaq::scalar_operator(1.0); + + ASSERT_TRUE(sum.term_count() == 3); + } +} + +TEST(ExpressionTester, checkOperatorSumAgainstScalars) { + std::complex value = 0.1 + 0.1; + + // `operator_sum * double` and `double * operator_sum` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto product = sum * 2.0; + auto reverse = 2.0 * sum; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum + double` and `double + operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto sum = original + 2.0; + auto reverse = 2.0 + original; + + ASSERT_TRUE(sum.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum - double` and `double - operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto difference = original - 2.0; + auto reverse = 2.0 - original; + + ASSERT_TRUE(difference.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum *= double` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum *= 2.0; + + ASSERT_TRUE(sum.term_count() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum += double` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum += 2.0; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum -= double` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum -= 2.0; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum * std::complex` and `std::complex * + // operator_sum` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto product = sum * value; + auto reverse = value * sum; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum + std::complex` and `std::complex + + // operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto sum = original + value; + auto reverse = value + original; + + ASSERT_TRUE(sum.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum - std::complex` and `std::complex - + // operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto difference = original - value; + auto reverse = value - original; + + ASSERT_TRUE(difference.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum *= std::complex` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum *= value; + + ASSERT_TRUE(sum.term_count() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum += std::complex` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum += value; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum -= std::complex` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum -= value; + + ASSERT_TRUE(sum.term_count() == 3); + } +} + +TEST(ExpressionTester, checkOperatorSumAgainstOperatorSum) { + // `operator_sum + operator_sum` + { + auto sum_0 = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + auto sum_1 = cudaq::elementary_operator::identity(0) + + cudaq::elementary_operator::annihilate(1) + + cudaq::elementary_operator::create(3); + + auto sum = sum_0 + sum_1; + + ASSERT_TRUE(sum.term_count() == 5); + } + + // `operator_sum - operator_sum` + { + auto sum_0 = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + auto sum_1 = cudaq::elementary_operator::identity(0) + + cudaq::elementary_operator::annihilate(1) + + cudaq::elementary_operator::create(2); + + auto difference = sum_0 - sum_1; + + ASSERT_TRUE(difference.term_count() == 5); + } + + // `operator_sum * operator_sum` + { + auto sum_0 = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + auto sum_1 = cudaq::elementary_operator::identity(0) + + cudaq::elementary_operator::annihilate(1) + + cudaq::elementary_operator::create(2); + + auto sum_product = sum_0 * sum_1; + + ASSERT_TRUE(sum_product.term_count() == 6); + for (auto term : sum_product.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + } + + // `operator_sum *= operator_sum` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + auto sum_1 = cudaq::elementary_operator::identity(0) + + cudaq::elementary_operator::annihilate(1) + + cudaq::elementary_operator::create(2); + + sum *= sum_1; + + ASSERT_TRUE(sum.term_count() == 6); + for (auto term : sum.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + } +} + +/// NOTE: Much of the simpler arithmetic between the two is tested in the +/// product operator test file. This mainly just tests the assignment operators +/// between the two types. +TEST(ExpressionTester, checkOperatorSumAgainstProduct) { + // `operator_sum += product_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum += product; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum -= product_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum -= product; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum *= product_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum *= product; + + ASSERT_TRUE(sum.term_count() == 2); + + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.term_count() == 3); + } + } +} diff --git a/unittests/dynamics/product_operators_arithmetic.cpp b/unittests/dynamics/product_operators_arithmetic.cpp new file mode 100644 index 0000000000..f8b6be7742 --- /dev/null +++ b/unittests/dynamics/product_operators_arithmetic.cpp @@ -0,0 +1,591 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/matrix.h" +#include "cudaq/operators.h" +#include + +#include + +namespace utils_1 { +void checkEqual(cudaq::matrix_2 a, cudaq::matrix_2 b) { + ASSERT_EQ(a.get_rank(), b.get_rank()); + ASSERT_EQ(a.get_rows(), b.get_rows()); + ASSERT_EQ(a.get_columns(), b.get_columns()); + ASSERT_EQ(a.get_size(), b.get_size()); + for (std::size_t i = 0; i < a.get_rows(); i++) { + for (std::size_t j = 0; j < a.get_columns(); j++) { + double a_val = a[{i, j}].real(); + double b_val = b[{i, j}].real(); + EXPECT_NEAR(a_val, b_val, 1e-8); + } + } +} + +cudaq::matrix_2 zero_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + return mat; +} + +cudaq::matrix_2 id_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = 1.0 + 0.0j; + return mat; +} + +cudaq::matrix_2 annihilate_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 create_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 position_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 momentum_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 number_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = static_cast(i) + 0.0j; + return mat; +} + +cudaq::matrix_2 parity_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + return mat; +} + +// cudaq::matrix_2 displace_matrix(std::size_t size, +// std::complex amplitude) { +// auto mat = cudaq::matrix_2(size, size); +// for (std::size_t i = 0; i + 1 < size; i++) { +// mat[{i + 1, i}] = +// amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; +// mat[{i, i + 1}] = -1. * std::conj(amplitude) * (0.5 * 'j') * +// std::sqrt(static_cast(i + 1)) + +// 0.0 * 'j'; +// } +// return mat.exp(); +// } + +} // namespace utils_1 + +/// TODO: Not yet testing the output matrices coming from this arithmetic. + +TEST(ExpressionTester, checkProductOperatorSimpleMatrixChecks) { + std::vector levels = {2, 3, 4}; + + { + // Same degrees of freedom. + { + for (auto level_count : levels) { + auto op0 = cudaq::elementary_operator::annihilate(0); + auto op1 = cudaq::elementary_operator::create(0); + + cudaq::product_operator got = op0 * op1; + + // auto got_matrix = got.to_matrix({{0, level_count}}, {}); + + // auto matrix0 = _annihilate_matrix(level_count); + // auto matrix1 = _create_matrix(level_count); + // auto want_matrix = matrix0 * matrix1; + + // ASSERT_TRUE(want_matrix == got_matrix); + + std::vector want_degrees = {0}; + ASSERT_TRUE(got.degrees() == want_degrees); + } + } + + // Different degrees of freedom. + { + for (auto level_count : levels) { + auto op0 = cudaq::elementary_operator::annihilate(0); + auto op1 = cudaq::elementary_operator::create(1); + + cudaq::product_operator got = op0 * op1; + // auto got_matrix = + // got.to_matrix({{0, level_count}, {1, level_count}}, {}); + + cudaq::product_operator got_reverse = op1 * op0; + // auto got_matrix_reverse = + // got_reverse.to_matrix({{0, level_count}, {1, level_count}}, {}); + + // auto identity = _id_matrix(level_count); + // auto matrix0 = _annihilate_matrix(level_count); + // auto matrix1 = _create_matrix(level_count); + + // auto fullHilbert0 = identity.kronecker(matrix0); + // auto fullHilbert1 = matrix1.kronecker(identity); + // auto want_matrix = fullHilbert0 * fullHilbert1; + // auto want_matrix_reverse = fullHilbert1 * fullHilbert0; + + // ASSERT_TRUE(want_matrix == got_matrix); + // ASSERT_TRUE(want_matrix_reverse == got_matrix_reverse); + + std::vector want_degrees = {0, 1}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + } + + // Different degrees of freedom, non-consecutive. + { + for (auto level_count : levels) { + auto op0 = cudaq::elementary_operator::annihilate(0); + auto op1 = cudaq::elementary_operator::create(2); + + cudaq::product_operator got = op0 * op1; + // auto got_matrix = got.to_matrix({{0,level_count},{2,level_count}}, + // {}); + + cudaq::product_operator got_reverse = op1 * op0; + + std::vector want_degrees = {0, 2}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + } + + // Different degrees of freedom, non-consecutive but all dimensions + // provided. + { + for (auto level_count : levels) { + auto op0 = cudaq::elementary_operator::annihilate(0); + auto op1 = cudaq::elementary_operator::create(2); + + cudaq::product_operator got = op0 * op1; + // auto got_matrix = + // got.to_matrix({{0,level_count},{1,level_count},{2,level_count}}, {}); + + cudaq::product_operator got_reverse = op1 * op0; + + std::vector want_degrees = {0, 2}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + } + } +} + +TEST(ExpressionTester, checkProductOperatorSimpleContinued) { + + std::complex value_0 = 0.1 + 0.1; + std::complex value_1 = 0.1 + 1.0; + std::complex value_2 = 2.0 + 0.1; + std::complex value_3 = 2.0 + 1.0; + + auto local_variable = true; + auto function = [&](std::map> parameters) { + if (!local_variable) + throw std::runtime_error("Local variable not detected."); + return parameters["value"]; + }; + + // Scalar Ops against Elementary Ops + { + // Annihilation against constant. + { + auto id_op = cudaq::elementary_operator::annihilate(0); + auto scalar_op = cudaq::scalar_operator(value_0); + + auto got = scalar_op * id_op; + auto got_reverse = scalar_op * id_op; + + std::vector want_degrees = {0}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + + // Annihilation against constant from lambda. + { + auto id_op = cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(function); + + auto got = scalar_op * id_op; + auto got_reverse = scalar_op * id_op; + + std::vector want_degrees = {1}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + } +} + +TEST(ExpressionTester, checkProductOperatorAgainstScalars) { + std::complex value_0 = 0.1 + 0.1; + + /// `product_operator + complex` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto sum = value_0 + product_op; + auto reverse = product_op + value_0; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(sum.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator + double` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto sum = 2.0 + product_op; + auto reverse = product_op + 2.0; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(sum.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator + scalar_operator` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(1.0); + + auto sum = scalar_op + product_op; + auto reverse = product_op + scalar_op; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(sum.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator - complex` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto difference = value_0 - product_op; + auto reverse = product_op - value_0; + + ASSERT_TRUE(difference.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(difference.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator - double` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto difference = 2.0 - product_op; + auto reverse = product_op - 2.0; + + ASSERT_TRUE(difference.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(difference.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator - scalar_operator` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(1.0); + + auto difference = scalar_op - product_op; + auto reverse = product_op - scalar_op; + + ASSERT_TRUE(difference.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(difference.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator * complex` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto product = value_0 * product_op; + auto reverse = product_op * value_0; + + ASSERT_TRUE(product.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator * double` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto product = 2.0 * product_op; + auto reverse = product_op * 2.0; + + ASSERT_TRUE(product.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator * scalar_operator` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(1.0); + + auto product = scalar_op * product_op; + auto reverse = product_op * scalar_op; + + ASSERT_TRUE(product.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator *= complex` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + product *= value_0; + + ASSERT_TRUE(product.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + } + + /// `product_operator *= double` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + product *= 2.0; + + ASSERT_TRUE(product.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + } + + /// `product_operator *= scalar_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(1.0); + + product *= scalar_op; + + ASSERT_TRUE(product.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + } +} + +TEST(ExpressionTester, checkProductOperatorAgainstProduct) { + + // `product_operator + product_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto term_1 = cudaq::elementary_operator::create(1) * + cudaq::elementary_operator::annihilate(2); + + auto sum = term_0 + term_1; + + ASSERT_TRUE(sum.term_count() == 2); + } + + // `product_operator - product_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto term_1 = cudaq::elementary_operator::create(1) * + cudaq::elementary_operator::annihilate(2); + + auto difference = term_0 - term_1; + + ASSERT_TRUE(difference.term_count() == 2); + } + + // `product_operator * product_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto term_1 = cudaq::elementary_operator::create(1) * + cudaq::elementary_operator::annihilate(2); + + auto product = term_0 * term_1; + + ASSERT_TRUE(product.term_count() == 4); + } + + // `product_operator *= product_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto term_1 = cudaq::elementary_operator::create(1) * + cudaq::elementary_operator::annihilate(2); + + term_0 *= term_1; + + ASSERT_TRUE(term_0.term_count() == 4); + } +} + +TEST(ExpressionTester, checkProductOperatorAgainstElementary) { + + // `product_operator + elementary_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto elementary = cudaq::elementary_operator::create(1); + + auto sum = product + elementary; + auto reverse = elementary + product; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + } + + // `product_operator - elementary_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto elementary = cudaq::elementary_operator::create(1); + + auto difference = product - elementary; + auto reverse = elementary - product; + + ASSERT_TRUE(difference.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + } + + // `product_operator * elementary_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto elementary = cudaq::elementary_operator::create(1); + + auto product = term_0 * elementary; + auto reverse = elementary * term_0; + + ASSERT_TRUE(product.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `product_operator *= elementary_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto elementary = cudaq::elementary_operator::create(1); + + product *= elementary; + + ASSERT_TRUE(product.term_count() == 3); + } +} + +TEST(ExpressionTester, checkProductOperatorAgainstOperatorSum) { + + // `product_operator + operator_sum` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto original_sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto sum = product + original_sum; + auto reverse = original_sum + product; + + ASSERT_TRUE(sum.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `product_operator - operator_sum` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto original_sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto difference = product - original_sum; + auto reverse = original_sum - product; + + ASSERT_TRUE(difference.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `product_operator * operator_sum` + { + auto original_product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto product = original_product * sum; + auto reverse = sum * original_product; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.term_count() == 3); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.term_count() == 3); + } + } +} diff --git a/unittests/dynamics/runge_kutta_test_helpers.h b/unittests/dynamics/runge_kutta_test_helpers.h new file mode 100644 index 0000000000..4f93ffa242 --- /dev/null +++ b/unittests/dynamics/runge_kutta_test_helpers.h @@ -0,0 +1,24 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include + +// A simple state type +using TestState = double; + +// Simple derivative function: dx/dt = -x (exponential decay) +inline TestState simple_derivative(const TestState &state, double t) { + return -state; +} + +// A complex function: dx/dt = sin(t) +inline TestState sine_derivative(const TestState &state, double t) { + return std::sin(t); +} diff --git a/unittests/dynamics/rydberg_hamiltonian.cpp b/unittests/dynamics/rydberg_hamiltonian.cpp new file mode 100644 index 0000000000..2e64cb0666 --- /dev/null +++ b/unittests/dynamics/rydberg_hamiltonian.cpp @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/operators.h" +#include + +using namespace cudaq; + +TEST(RydbergHamiltonianTest, ConstructorValidInputs) { + // Valid atom sites + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}, {1.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + // Valid atom filling + rydberg_hamiltonian hamiltonian(atom_sites, amplitude, phase, delta_global); + + EXPECT_EQ(hamiltonian.get_atom_sites().size(), atom_sites.size()); + EXPECT_EQ(hamiltonian.get_atom_filling().size(), atom_sites.size()); + EXPECT_EQ(hamiltonian.get_amplitude().evaluate({}), + std::complex(1.0, 0.0)); + EXPECT_EQ(hamiltonian.get_phase().evaluate({}), + std::complex(0.0, 0.0)); + EXPECT_EQ(hamiltonian.get_delta_global().evaluate({}), + std::complex(-0.5, 0.0)); +} + +TEST(RydbergHamiltonianTest, ConstructorWithAtomFilling) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + // Valid atom filling + std::vector atom_filling = {1, 0, 1}; + + rydberg_hamiltonian hamiltonian(atom_sites, amplitude, phase, delta_global, + atom_filling); + + EXPECT_EQ(hamiltonian.get_atom_sites().size(), atom_sites.size()); + EXPECT_EQ(hamiltonian.get_atom_filling(), atom_filling); +} + +TEST(RydbergHamiltonianTest, InvalidAtomFillingSize) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + // Invalid atom filling size + std::vector atom_filling = {1, 0}; + + EXPECT_THROW(rydberg_hamiltonian(atom_sites, amplitude, phase, delta_global, + atom_filling), + std::invalid_argument); +} + +TEST(RydbergHamiltonianTest, UnsupportedLocalDetuning) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + // Invalid delta_local + auto delta_local = + std::make_pair(scalar_operator(0.5), std::vector{0.1, 0.2, 0.3}); + + EXPECT_THROW(rydberg_hamiltonian(atom_sites, amplitude, phase, delta_global, + {}, delta_local), + std::runtime_error); +} + +TEST(RydbergHamiltonianTest, Accessors) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + rydberg_hamiltonian hamiltonian(atom_sites, amplitude, phase, delta_global); + + EXPECT_EQ(hamiltonian.get_atom_sites(), atom_sites); + EXPECT_EQ(hamiltonian.get_amplitude().evaluate({}), + std::complex(1.0, 0.0)); + EXPECT_EQ(hamiltonian.get_phase().evaluate({}), + std::complex(0.0, 0.0)); + EXPECT_EQ(hamiltonian.get_delta_global().evaluate({}), + std::complex(-0.5, 0.0)); +} + +TEST(RydbergHamiltonianTest, DefaultAtomFilling) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}, {1.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + rydberg_hamiltonian hamiltonian(atom_sites, amplitude, phase, delta_global); + + std::vector expected_filling(atom_sites.size(), 1); + EXPECT_EQ(hamiltonian.get_atom_filling(), expected_filling); +} diff --git a/unittests/dynamics/scalar_ops_arithmetic.cpp b/unittests/dynamics/scalar_ops_arithmetic.cpp new file mode 100644 index 0000000000..a8d6054a32 --- /dev/null +++ b/unittests/dynamics/scalar_ops_arithmetic.cpp @@ -0,0 +1,505 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/matrix.h" +#include "cudaq/operators.h" +#include + +TEST(ExpressionTester, checkScalarOpsArithmeticDoubles) { + // Arithmetic overloads against complex doubles. + std::complex value_0 = 0.1 + 0.1; + std::complex value_1 = 0.1 + 1.0; + std::complex value_2 = 2.0 + 0.1; + std::complex value_3 = 2.0 + 1.0; + + auto local_variable = true; + auto function = [&](std::map> parameters) { + if (!local_variable) + throw std::runtime_error("Local variable not detected."); + return parameters["value"]; + }; + + // + : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + + auto new_scalar_op = value_1 + scalar_op; + // function + scalar_op; + auto reverse_order_op = scalar_op + value_1; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_1 + value_0; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(want_value), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op + reverse_order_op; + auto got_value_third = third_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value + want_value), + 1e-5); + } + + // + : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + + auto new_scalar_op = value_0 + scalar_op; + auto reverse_order_op = scalar_op + value_0; + + auto got_value = new_scalar_op.evaluate({{"value", value_1}}); + auto got_value_1 = reverse_order_op.evaluate({{"value", value_1}}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 + value_0), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op + reverse_order_op; + auto got_value_third = third_op.evaluate({{"value", value_1}}); + auto want_value = value_0 + value_1 + value_1 + value_0; + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // - : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_1); + + auto new_scalar_op = value_3 - scalar_op; + auto reverse_order_op = scalar_op - value_3; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 - value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 - value_3), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op - reverse_order_op; + auto got_value_third = third_op.evaluate({}); + auto want_value = (value_3 - value_1) - (value_1 - value_3); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // - : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + + auto new_scalar_op = value_2 - scalar_op; + auto reverse_order_op = scalar_op - value_2; + + auto got_value = new_scalar_op.evaluate({{"value", value_1}}); + auto got_value_1 = reverse_order_op.evaluate({{"value", value_1}}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 - value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 - value_2), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op - reverse_order_op; + auto got_value_third = third_op.evaluate({{"value", value_1}}); + auto want_value = (value_2 - value_1) - (value_1 - value_2); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // * : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + + auto new_scalar_op = value_3 * scalar_op; + auto reverse_order_op = scalar_op * value_3; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 * value_2), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_2 * value_3), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op * reverse_order_op; + auto got_value_third = third_op.evaluate({}); + auto want_value = (value_3 * value_2) * (value_2 * value_3); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // * : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + + auto new_scalar_op = value_3 * scalar_op; + auto reverse_order_op = scalar_op * value_3; + + auto got_value = new_scalar_op.evaluate({{"value", value_2}}); + auto got_value_1 = reverse_order_op.evaluate({{"value", value_2}}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 * value_2), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_2 * value_3), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op * reverse_order_op; + auto got_value_third = third_op.evaluate({{"value", value_2}}); + auto want_value = (value_3 * value_2) * (value_2 * value_3); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // / : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + + auto new_scalar_op = value_3 / scalar_op; + auto reverse_order_op = scalar_op / value_3; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_3 / value_2), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op / reverse_order_op; + auto got_value_third = third_op.evaluate({}); + auto want_value = (value_2 / value_3) / (value_3 / value_2); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // / : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + + auto new_scalar_op = value_3 / scalar_op; + auto reverse_order_op = scalar_op / value_3; + + auto got_value = new_scalar_op.evaluate({{"value", value_1}}); + auto got_value_1 = reverse_order_op.evaluate({{"value", value_1}}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_1 / value_3), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_3 / value_1), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op / reverse_order_op; + auto got_value_third = third_op.evaluate({{"value", value_1}}); + auto want_value = (value_1 / value_3) / (value_3 / value_1); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // += : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + scalar_op += value_0; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_0), 1e-5); + } + + // += : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op += value_1; + + auto got_value = scalar_op.evaluate({{"value", value_0}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_1), 1e-5); + } + + // -= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + scalar_op -= value_0; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 - value_0), 1e-5); + } + + // -= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op -= value_1; + + auto got_value = scalar_op.evaluate({{"value", value_0}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 - value_1), 1e-5); + } + + // *= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + scalar_op *= value_3; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 * value_3), 1e-5); + } + + // *= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op *= value_3; + + auto got_value = scalar_op.evaluate({{"value", value_2}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 * value_3), 1e-5); + } + + // /= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + scalar_op /= value_3; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + } + + // /= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op /= value_3; + + auto got_value = scalar_op.evaluate({{"value", value_2}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + } +} + +TEST(ExpressionTester, checkScalarOpsArithmeticScalarOps) { + // Arithmetic overloads against other scalar ops. + std::complex value_0 = 0.1 + 0.1; + std::complex value_1 = 0.1 + 1.0; + std::complex value_2 = 2.0 + 0.1; + std::complex value_3 = 2.0 + 1.0; + + auto local_variable = true; + auto function = [&](std::map> parameters) { + if (!local_variable) + throw std::runtime_error("Local variable not detected."); + return parameters["value"]; + }; + + // I use another function here to make sure that local variables + // that may be unique to each ScalarOp's generators are both kept + // track of when we merge the generators. + auto alternative_local_variable = true; + auto alternative_function = + [&](std::map> parameters) { + if (!alternative_local_variable) + throw std::runtime_error("Local variable not detected."); + return parameters["other"]; + }; + + // + : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + auto other_scalar_op = cudaq::scalar_operator(value_1); + + auto new_scalar_op = other_scalar_op + scalar_op; + auto reverse_order_op = scalar_op + other_scalar_op; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_1 + value_0; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(want_value), 1e-5); + } + + // + : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other_scalar_op = cudaq::scalar_operator(alternative_function); + + auto new_scalar_op = other_scalar_op + scalar_op; + auto reverse_order_op = scalar_op + other_scalar_op; + + std::map> parameter_map = { + {"value", value_1}, {"other", value_0}}; + + auto got_value = new_scalar_op.evaluate(parameter_map); + auto got_value_1 = reverse_order_op.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 + value_0), 1e-5); + } + + // - : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + auto other_scalar_op = cudaq::scalar_operator(value_1); + + auto new_scalar_op = other_scalar_op - scalar_op; + auto reverse_order_op = scalar_op - other_scalar_op; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_1 - value_2; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(want_value), 1e-5); + } + + // - : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other_scalar_op = cudaq::scalar_operator(alternative_function); + + auto new_scalar_op = other_scalar_op - scalar_op; + auto reverse_order_op = scalar_op - other_scalar_op; + + std::map> parameter_map = { + {"value", value_1}, {"other", value_3}}; + + auto got_value = new_scalar_op.evaluate(parameter_map); + auto got_value_1 = reverse_order_op.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 - value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 - value_3), 1e-5); + } + + // * : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + auto other_scalar_op = cudaq::scalar_operator(value_3); + + auto new_scalar_op = other_scalar_op * scalar_op; + auto reverse_order_op = scalar_op * other_scalar_op; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_3 * value_2; + auto reverse_want_value = value_2 * value_3; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(reverse_want_value), 1e-5); + } + + // * : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other_scalar_op = cudaq::scalar_operator(alternative_function); + + auto new_scalar_op = other_scalar_op * scalar_op; + auto reverse_order_op = scalar_op * other_scalar_op; + + std::map> parameter_map = { + {"value", value_1}, {"other", value_3}}; + + auto got_value = new_scalar_op.evaluate(parameter_map); + auto got_value_1 = reverse_order_op.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 * value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 * value_3), 1e-5); + } + + // / : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + auto other_scalar_op = cudaq::scalar_operator(value_2); + + auto new_scalar_op = other_scalar_op / scalar_op; + auto reverse_order_op = scalar_op / other_scalar_op; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_2 / value_0; + auto reverse_want_value = value_0 / value_2; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(reverse_want_value), 1e-5); + } + + // / : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other_scalar_op = cudaq::scalar_operator(alternative_function); + + auto new_scalar_op = other_scalar_op / scalar_op; + auto reverse_order_op = scalar_op / other_scalar_op; + + std::map> parameter_map = { + {"value", value_0}, {"other", value_3}}; + + auto got_value = new_scalar_op.evaluate(parameter_map); + auto got_value_1 = reverse_order_op.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 / value_0), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_0 / value_3), 1e-5); + } + + // += : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + auto other = cudaq::scalar_operator(value_0); + scalar_op += other; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_0), 1e-5); + } + + // += : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other = cudaq::scalar_operator(value_1); + scalar_op += other; + + auto scalar_op_1 = cudaq::scalar_operator(function); + auto other_function = cudaq::scalar_operator(alternative_function); + scalar_op_1 += other_function; + + auto got_value = scalar_op.evaluate({{"value", value_0}}); + auto got_value_1 = + scalar_op_1.evaluate({{"value", value_0}, {"other", value_1}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_0 + value_1), 1e-5); + } + + // -= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + scalar_op -= value_0; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 - value_0), 1e-5); + } + + // -= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op -= value_1; + + auto got_value = scalar_op.evaluate({{"value", value_0}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 - value_1), 1e-5); + } + + // *= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + scalar_op *= value_3; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 * value_3), 1e-5); + } + + // *= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op *= value_3; + + auto got_value = scalar_op.evaluate({{"value", value_2}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 * value_3), 1e-5); + } + + // /= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + scalar_op /= value_3; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + } + + // /= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op /= value_3; + + auto got_value = scalar_op.evaluate({{"value", value_2}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + } +} \ No newline at end of file diff --git a/unittests/dynamics/scalar_ops_simple.cpp b/unittests/dynamics/scalar_ops_simple.cpp new file mode 100644 index 0000000000..c60414d705 --- /dev/null +++ b/unittests/dynamics/scalar_ops_simple.cpp @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/matrix.h" +#include "cudaq/operators.h" +#include + +TEST(ExpressionTester, checkScalarOpsSimpleComplex) { + + std::complex value_0 = 0.1 + 0.1; + std::complex value_1 = 0.1 + 1.0; + std::complex value_2 = 2.0 + 0.1; + std::complex value_3 = 2.0 + 1.0; + + // From concrete values. + { + auto operator_0 = cudaq::scalar_operator(value_0); + auto operator_1 = cudaq::scalar_operator(value_1); + auto operator_2 = cudaq::scalar_operator(value_2); + auto operator_3 = cudaq::scalar_operator(value_3); + + auto got_value_0 = operator_0.evaluate({}); + auto got_value_1 = operator_1.evaluate({}); + auto got_value_2 = operator_2.evaluate({}); + auto got_value_3 = operator_3.evaluate({}); + + EXPECT_NEAR(std::abs(value_0), std::abs(got_value_0), 1e-5); + EXPECT_NEAR(std::abs(value_1), std::abs(got_value_1), 1e-5); + EXPECT_NEAR(std::abs(value_2), std::abs(got_value_2), 1e-5); + EXPECT_NEAR(std::abs(value_3), std::abs(got_value_3), 1e-5); + } + + // From a lambda function. + { + auto function = [](std::map> parameters) { + return parameters["value"]; + }; + + std::map> parameter_map; + + auto operator_0 = cudaq::scalar_operator(function); + auto operator_1 = cudaq::scalar_operator(function); + auto operator_2 = cudaq::scalar_operator(function); + auto operator_3 = cudaq::scalar_operator(function); + + parameter_map["value"] = value_0; + auto got_value_0 = operator_0.evaluate(parameter_map); + parameter_map["value"] = value_1; + auto got_value_1 = operator_1.evaluate(parameter_map); + parameter_map["value"] = value_2; + auto got_value_2 = operator_2.evaluate(parameter_map); + parameter_map["value"] = value_3; + auto got_value_3 = operator_3.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(value_0), std::abs(got_value_0), 1e-5); + EXPECT_NEAR(std::abs(value_1), std::abs(got_value_1), 1e-5); + EXPECT_NEAR(std::abs(value_2), std::abs(got_value_2), 1e-5); + EXPECT_NEAR(std::abs(value_3), std::abs(got_value_3), 1e-5); + } +} + +TEST(ExpressionTester, checkScalarOpsSimpleDouble) { + + double value_0 = 0.1; + double value_1 = 0.2; + double value_2 = 2.1; + double value_3 = 2.2; + + // From concrete values. + { + auto operator_0 = cudaq::scalar_operator(value_0); + auto operator_1 = cudaq::scalar_operator(value_1); + auto operator_2 = cudaq::scalar_operator(value_2); + auto operator_3 = cudaq::scalar_operator(value_3); + + auto got_value_0 = operator_0.evaluate({}); + auto got_value_1 = operator_1.evaluate({}); + auto got_value_2 = operator_2.evaluate({}); + auto got_value_3 = operator_3.evaluate({}); + + EXPECT_NEAR(std::abs(value_0), std::abs(got_value_0), 1e-5); + EXPECT_NEAR(std::abs(value_1), std::abs(got_value_1), 1e-5); + EXPECT_NEAR(std::abs(value_2), std::abs(got_value_2), 1e-5); + EXPECT_NEAR(std::abs(value_3), std::abs(got_value_3), 1e-5); + } + + // From a lambda function. + { + auto function = [](std::map> parameters) { + return parameters["value"]; + }; + + std::map> parameter_map; + + auto operator_0 = cudaq::scalar_operator(function); + auto operator_1 = cudaq::scalar_operator(function); + auto operator_2 = cudaq::scalar_operator(function); + auto operator_3 = cudaq::scalar_operator(function); + + parameter_map["value"] = value_0; + auto got_value_0 = operator_0.evaluate(parameter_map); + parameter_map["value"] = value_1; + auto got_value_1 = operator_1.evaluate(parameter_map); + parameter_map["value"] = value_2; + auto got_value_2 = operator_2.evaluate(parameter_map); + parameter_map["value"] = value_3; + auto got_value_3 = operator_3.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(value_0), std::abs(got_value_0), 1e-5); + EXPECT_NEAR(std::abs(value_1), std::abs(got_value_1), 1e-5); + EXPECT_NEAR(std::abs(value_2), std::abs(got_value_2), 1e-5); + EXPECT_NEAR(std::abs(value_3), std::abs(got_value_3), 1e-5); + } +} \ No newline at end of file diff --git a/unittests/dynamics/test_cudm_helpers.cpp b/unittests/dynamics/test_cudm_helpers.cpp new file mode 100644 index 0000000000..734470365a --- /dev/null +++ b/unittests/dynamics/test_cudm_helpers.cpp @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include +#include +#include + +// Initialize operator_sum +cudaq::operator_sum initialize_operator_sum() { + std::vector degrees = {0, 1}; + + // Elementary operators + cudaq::elementary_operator pauli_x("pauli_x", {0}); + cudaq::elementary_operator pauli_z("pauli_z", {1}); + cudaq::elementary_operator identity = cudaq::elementary_operator::identity(0); + + auto prod_op_1 = cudaq::scalar_operator(std::complex(1.0, 0.0)) * + pauli_x * pauli_z; + + auto prod_op_2 = + cudaq::scalar_operator(std::complex(0.5, -0.5)) * identity; + + cudaq::operator_sum op_sum({prod_op_1, prod_op_2}); + + return op_sum; +} + +class CuDensityMatTestFixture : public ::testing::Test { +protected: + cudensitymatHandle_t handle; + cudaStream_t stream; + + void SetUp() override { + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle)); + HANDLE_CUDA_ERROR(cudaStreamCreate(&stream)); + } + + void TearDown() override { + HANDLE_CUDA_ERROR(cudaStreamDestroy(stream)); + HANDLE_CUDM_ERROR(cudensitymatDestroy(handle)); + } +}; + +// Test for initialize_state +TEST_F(CuDensityMatTestFixture, InitializeState) { + std::vector mode_extents = {2, 2}; + + auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, + mode_extents); + ASSERT_NE(state, nullptr); + + cudaq::destroy_state(state); +} + +// Test for scale_state +TEST_F(CuDensityMatTestFixture, ScaleState) { + std::vector mode_extents = {2}; + + auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, + mode_extents); + ASSERT_NE(state, nullptr); + + EXPECT_NO_THROW(cudaq::scale_state(handle, state, 2.0, stream)); + + cudaq::destroy_state(state); +} + +// Test for compute_lindblad_op +TEST_F(CuDensityMatTestFixture, ComputeLindbladOp) { + std::vector mode_extents = {2, 2}; + + cudaq::matrix_2 c_op1({1.0, 0.0, 0.0, 0.0}, {2, 2}); + cudaq::matrix_2 c_op2({0.0, 0.0, 0.0, 1.0}, {2, 2}); + std::vector c_ops = {c_op1, c_op2}; + + auto lindblad_op = + cudaq::compute_lindblad_operator(handle, c_ops, mode_extents); + ASSERT_NE(lindblad_op, nullptr); + + cudensitymatDestroyOperator(lindblad_op); +} + +// Test for convert_to_cudensitymat_operator +TEST_F(CuDensityMatTestFixture, ConvertToCuDensityMatOperator) { + std::vector mode_extents = {2, 2}; + + auto op_sum = initialize_operator_sum(); + + auto result = + cudaq::convert_to_cudensitymat_operator(handle, {}, op_sum, mode_extents); + ASSERT_NE(result, nullptr); + + cudensitymatDestroyOperator(result); +} + +// Test invalid handle +TEST_F(CuDensityMatTestFixture, InvalidHandle) { + cudensitymatHandle_t invalid_handle = nullptr; + + std::vector mode_extents = {2, 2}; + auto op_sum = initialize_operator_sum(); + + EXPECT_THROW(cudaq::convert_to_cudensitymat_operator(invalid_handle, {}, + op_sum, mode_extents), + std::runtime_error); +} diff --git a/unittests/dynamics/test_cudm_state.cpp b/unittests/dynamics/test_cudm_state.cpp new file mode 100644 index 0000000000..d8ef07ffc4 --- /dev/null +++ b/unittests/dynamics/test_cudm_state.cpp @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace cudaq; + +class CuDensityMatStateTest : public ::testing::Test { +protected: + void SetUp() override { + // Set up test data for a single 2-qubit system + hilbertSpaceDims = {2, 2}; + + // State vector (pure state) for |00> + stateVectorData = { + std::complex(1.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0)}; + + // Density matrix for |00><00| + densityMatrixData = { + std::complex(1.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0)}; + } + + void TearDown() override {} + + std::vector hilbertSpaceDims; + std::vector> stateVectorData; + std::vector> densityMatrixData; +}; + +TEST_F(CuDensityMatStateTest, InitializeWithStateVector) { + cudm_mat_state state(stateVectorData); + EXPECT_FALSE(state.is_initialized()); + + EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); + EXPECT_TRUE(state.is_initialized()); + EXPECT_FALSE(state.is_density_matrix()); + + EXPECT_NO_THROW(state.dump()); +} + +TEST_F(CuDensityMatStateTest, InitializeWithDensityMatrix) { + cudm_mat_state state(densityMatrixData); + EXPECT_FALSE(state.is_initialized()); + + EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); + EXPECT_TRUE(state.is_initialized()); + EXPECT_TRUE(state.is_density_matrix()); + + EXPECT_NO_THROW(state.dump()); +} + +TEST_F(CuDensityMatStateTest, InvalidInitialization) { + // Data size mismatch for hilbertSpaceDims (2x2 system expects size 4 or 16) + std::vector> invalidData = { + std::complex(1.0, 0.0), std::complex(0.0, 0.0)}; + + cudm_mat_state state(invalidData); + EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); +} + +TEST_F(CuDensityMatStateTest, ToDensityMatrixConversion) { + cudm_mat_state state(stateVectorData); + state.init_state(hilbertSpaceDims); + + EXPECT_FALSE(state.is_density_matrix()); + + cudm_mat_state densityMatrixState = state.to_density_matrix(); + + EXPECT_TRUE(densityMatrixState.is_density_matrix()); + EXPECT_TRUE(densityMatrixState.is_initialized()); + + EXPECT_NO_THROW(densityMatrixState.dump()); +} + +TEST_F(CuDensityMatStateTest, AlreadyDensityMatrixConversion) { + cudm_mat_state state(densityMatrixData); + state.init_state(hilbertSpaceDims); + + EXPECT_TRUE(state.is_density_matrix()); + EXPECT_THROW(state.to_density_matrix(), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, DumpUninitializedState) { + cudm_mat_state state(stateVectorData); + EXPECT_THROW(state.dump(), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, AttachStorageErrorHandling) { + cudm_mat_state state(stateVectorData); + + EXPECT_THROW(state.attach_storage(), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, DestructorCleansUp) { + cudm_mat_state state(stateVectorData); + + EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); +} + +TEST_F(CuDensityMatStateTest, InitializeWithEmptyRawData) { + std::vector> emptyData; + cudm_mat_state state(emptyData); + + EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); +} + +TEST_F(CuDensityMatStateTest, ConversionForSingleQubitSystem) { + hilbertSpaceDims = {2}; + stateVectorData = {std::complex(1.0, 0.0), + std::complex(0.0, 0.0)}; + cudm_mat_state state(stateVectorData); + + state.init_state(hilbertSpaceDims); + + EXPECT_FALSE(state.is_density_matrix()); + + cudm_mat_state densityMatrixState = state.to_density_matrix(); + EXPECT_TRUE(densityMatrixState.is_density_matrix()); + EXPECT_TRUE(densityMatrixState.is_initialized()); + + EXPECT_NO_THROW(densityMatrixState.dump()); +} + +TEST_F(CuDensityMatStateTest, InvalidHilbertSpaceDims) { + // 3x3 space is not supported by the provided rawData size + hilbertSpaceDims = {3, 3}; + cudm_mat_state state(stateVectorData); + + EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); +} + +TEST_F(CuDensityMatStateTest, ToDensityMatrixFromUninitializedState) { + cudm_mat_state state(stateVectorData); + + EXPECT_THROW(state.to_density_matrix(), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, MultipleInitialization) { + cudm_mat_state state(stateVectorData); + state.init_state(hilbertSpaceDims); + + EXPECT_TRUE(state.is_initialized()); + + EXPECT_THROW(state.init_state(hilbertSpaceDims), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, ValidDensityMatrixState) { + cudm_mat_state state(densityMatrixData); + state.init_state(hilbertSpaceDims); + + EXPECT_TRUE(state.is_density_matrix()); + EXPECT_TRUE(state.is_initialized()); +} + +TEST_F(CuDensityMatStateTest, DumpWorksForInitializedState) { + cudm_mat_state state(stateVectorData); + state.init_state(hilbertSpaceDims); + + EXPECT_NO_THROW(state.dump()); +} + +TEST_F(CuDensityMatStateTest, DumpFailsForUninitializedState) { + cudm_mat_state state(stateVectorData); + + EXPECT_THROW(state.dump(), std::runtime_error); +} diff --git a/unittests/dynamics/test_helpers.cpp b/unittests/dynamics/test_helpers.cpp new file mode 100644 index 0000000000..aa14cb5c10 --- /dev/null +++ b/unittests/dynamics/test_helpers.cpp @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/helpers.h" +#include +#include + +using namespace cudaq; + +TEST(OperatorHelpersTest, AggregateParameters_MultipleMappings) { + std::vector> mappings = { + {{"alpha", "Parameter A"}, {"beta", "Parameter B"}}, + {{"alpha", "Updated Parameter A"}, {"gamma", "New Parameter"}}}; + + auto result = OperatorHelpers::aggregate_parameters(mappings); + + EXPECT_EQ(result["alpha"], "Parameter A\n---\nUpdated Parameter A"); + EXPECT_EQ(result["beta"], "Parameter B"); + EXPECT_EQ(result["gamma"], "New Parameter"); +} + +TEST(OperatorHelpersTest, AggregateParameters_EmptyMappings) { + std::vector> mappings; + auto result = OperatorHelpers::aggregate_parameters(mappings); + + EXPECT_TRUE(result.empty()); +} + +TEST(OperatorHelpersTest, ParameterDocs_ValidExtraction) { + std::string docstring = "Description of function.\n" + "Arguments:\n" + " alpha (float): The first parameter.\n" + " beta (int): The second parameter."; + + auto result = OperatorHelpers::parameter_docs("alpha", docstring); + EXPECT_EQ(result, "The first parameter."); + + result = OperatorHelpers::parameter_docs("beta", docstring); + EXPECT_EQ(result, "The second parameter."); +} + +TEST(OperatorHelpersTest, ParameterDocs_InvalidParam) { + std::string docstring = "Description of function.\n" + "Arguments:\n" + " alpha (float): The first parameter.\n" + " beta (int): The second parameter."; + + auto result = OperatorHelpers::parameter_docs("gamma", docstring); + EXPECT_EQ(result, ""); +} + +TEST(OperatorHelpersTest, ParameterDocs_EmptyDocString) { + std::string docstring = ""; + auto result = OperatorHelpers::parameter_docs("alpha", docstring); + EXPECT_EQ(result, ""); +} + +TEST(OperatorHelpersTest, GenerateAllStates_TwoQubits) { + std::vector degrees = {0, 1}; + std::map dimensions = {{0, 2}, {1, 2}}; + + auto states = OperatorHelpers::generate_all_states(degrees, dimensions); + std::vector expected_states = {"00", "01", "10", "11"}; + + EXPECT_EQ(states, expected_states); +} + +TEST(OperatorHelpersTest, GenerateAllStates_ThreeQubits) { + std::vector degrees = {0, 1, 2}; + std::map dimensions = {{0, 2}, {1, 2}, {2, 2}}; + + auto states = OperatorHelpers::generate_all_states(degrees, dimensions); + std::vector expected_states = {"000", "001", "010", "011", + "100", "101", "110", "111"}; + + EXPECT_EQ(states, expected_states); +} + +TEST(OperatorHelpersTest, GenerateAllStates_EmptyDegrees) { + std::vector degrees; + std::map dimensions; + + auto states = OperatorHelpers::generate_all_states(degrees, dimensions); + EXPECT_TRUE(states.empty()); +} + +TEST(OperatorHelpersTest, GenerateAllStates_MissingDegreesInMap) { + std::vector degrees = {0, 1, 2}; + std::map dimensions = {{0, 2}, {1, 2}}; + + EXPECT_THROW(OperatorHelpers::generate_all_states(degrees, dimensions), + std::out_of_range); +} + +TEST(OperatorHelpersTest, PermuteMatrix_SingleSwap) { + Eigen::MatrixXcd matrix(2, 2); + matrix << 1, 2, 3, 4; + + // Swap rows and columns + std::vector permutation = {1, 0}; + + OperatorHelpers::permute_matrix(matrix, permutation); + + Eigen::MatrixXcd expected(2, 2); + expected << 4, 3, 2, 1; + + EXPECT_EQ(matrix, expected); +} + +TEST(OperatorHelpersTest, PermuteMatrix_IdentityPermutation) { + Eigen::MatrixXcd matrix(3, 3); + matrix << 1, 2, 3, 4, 5, 6, 7, 8, 9; + + // No change + std::vector permutation = {0, 1, 2}; + + OperatorHelpers::permute_matrix(matrix, permutation); + + Eigen::MatrixXcd expected(3, 3); + expected << 1, 2, 3, 4, 5, 6, 7, 8, 9; + + EXPECT_EQ(matrix, expected); +} + +TEST(OperatorHelpersTest, CanonicalizeDegrees_SortedDescending) { + std::vector degrees = {3, 1, 2}; + auto sorted = OperatorHelpers::canonicalize_degrees(degrees); + EXPECT_EQ(sorted, (std::vector{3, 2, 1})); +} + +TEST(OperatorHelpersTest, CanonicalizeDegrees_AlreadySorted) { + std::vector degrees = {5, 4, 3, 2, 1}; + auto sorted = OperatorHelpers::canonicalize_degrees(degrees); + EXPECT_EQ(sorted, (std::vector{5, 4, 3, 2, 1})); +} + +TEST(OperatorHelpersTest, CanonicalizeDegrees_EmptyList) { + std::vector degrees; + auto sorted = OperatorHelpers::canonicalize_degrees(degrees); + EXPECT_TRUE(sorted.empty()); +} + +TEST(OperatorHelpersTest, ArgsFromKwargs_ValidArgs) { + std::map kwargs = { + {"alpha", "0.5"}, {"beta", "1.0"}, {"gamma", "2.0"}}; + + std::vector required_args = {"alpha", "beta"}; + std::vector kwonly_args = {"gamma"}; + + auto [args, kwonly] = + OperatorHelpers::args_from_kwargs(kwargs, required_args, kwonly_args); + + EXPECT_EQ(args.size(), 2); + EXPECT_EQ(args[0], "0.5"); + EXPECT_EQ(args[1], "1.0"); + + EXPECT_EQ(kwonly.size(), 1); + EXPECT_EQ(kwonly["gamma"], "2.0"); +} + +TEST(OperatorHelpersTest, ArgsFromKwargs_MissingRequiredArgs) { + std::map kwargs = {{"beta", "1.0"}, + {"gamma", "2.0"}}; + + std::vector required_args = {"alpha", "beta"}; + std::vector kwonly_args = {"gamma"}; + + EXPECT_THROW( + OperatorHelpers::args_from_kwargs(kwargs, required_args, kwonly_args), + std::invalid_argument); +} diff --git a/unittests/dynamics/test_runge_kutta_integrator.cpp b/unittests/dynamics/test_runge_kutta_integrator.cpp new file mode 100644 index 0000000000..c75a7c8d6d --- /dev/null +++ b/unittests/dynamics/test_runge_kutta_integrator.cpp @@ -0,0 +1,132 @@ +// /******************************************************************************* +// * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * +// * All rights reserved. * +// * * +// * This source code and the accompanying materials are made available under * +// * the terms of the Apache License 2.0 which accompanies this distribution. * +// ******************************************************************************/ + +#include "cudaq/runge_kutta_integrator.h" +#include "runge_kutta_test_helpers.h" +#include +#include +#include + +using namespace cudaq; + +// Test fixture class +class RungeKuttaIntegratorTest : public ::testing::Test { +protected: + RungeKuttaIntegrator *integrator; + std::shared_ptr schedule; + std::shared_ptr hamiltonian; + + void SetUp() override { + integrator = new RungeKuttaIntegrator(simple_derivative); + // Initial state and time + integrator->set_state(1.0, 0.0); + + // A simple step sequence for the schedule + std::vector> steps = {0.1, 0.2, 0.3, 0.4, 0.5}; + + // Dummy parameters + std::vector parameters = {"param1"}; + + // A simple parameter function + auto value_function = [](const std::string ¶m, + const std::complex &step) { return step; }; + + // A valid schedule instance + schedule = std::make_shared(steps, parameters, value_function); + + // A simple hamiltonian as an operator_sum + hamiltonian = std::make_shared(); + *hamiltonian += 0.5 * elementary_operator::identity(0); + *hamiltonian += 0.5 * elementary_operator::number(0); + + // System with valid components + integrator->set_system({{0, 2}}, schedule, hamiltonian); + } + + void TearDown() override { delete integrator; } +}; + +// Basic integration +TEST_F(RungeKuttaIntegratorTest, BasicIntegration) { + integrator->integrate(1.0); + + // Expected result: x(t) = e^(-t) + double expected = std::exp(-1.0); + + EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) + << "Basic Runge-Kutta integration failed!"; +} + +// Time evolution +TEST_F(RungeKuttaIntegratorTest, TimeEvolution) { + integrator->integrate(2.0); + + double expected = 2.0; + + EXPECT_NEAR(integrator->get_state().first, expected, 1e-5) + << "Integrator did not correctly update time!"; +} + +// Large step size +TEST_F(RungeKuttaIntegratorTest, LargeStepSize) { + integrator->integrate(5.0); + + double expected = std::exp(-5.0); + + EXPECT_NEAR(integrator->get_state().second, expected, 1e-1) + << "Runge-Kutta integration failed for large step size!!"; +} + +// // Integrating Sine function +// TEST_F(RungeKuttaIntegratorTest, SineFunction) { +// integrator = new RungeKuttaIntegrator(sine_derivative); +// integrator->set_state(1.0, 0.0); +// integrator->set_system({{0, 2}}, schedule, hamiltonian); + +// integrator->integrate(M_PI / 2); + +// double expected = std::cos(M_PI / 2); + +// EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) << +// "Runge-Kutta integration for sine function failed!"; +// } + +// Small step size +TEST_F(RungeKuttaIntegratorTest, SmallStepIntegration) { + integrator->set_state(1.0, 0.0); + integrator->set_system({{0, 2}}, schedule, hamiltonian); + + double step_size = 0.001; + while (integrator->get_state().first < 1.0) { + integrator->integrate(integrator->get_state().first + step_size); + } + + double expected = std::exp(-1.0); + + EXPECT_NEAR(integrator->get_state().second, expected, 5e-4) + << "Runge-Kutta integration for small step size failed!"; +} + +// Large step size +TEST_F(RungeKuttaIntegratorTest, LargeStepIntegration) { + integrator->set_state(1.0, 0.0); + integrator->set_system({{0, 2}}, schedule, hamiltonian); + + double step_size = 0.5; + double t = 0.0; + double target_t = 1.0; + while (t < target_t) { + integrator->integrate(std::min(t + step_size, target_t)); + t += step_size; + } + + double expected = std::exp(-1.0); + + EXPECT_NEAR(integrator->get_state().second, expected, 1e-2) + << "Runge-Kutta integration for large step size failed!"; +} diff --git a/unittests/dynamics/test_runge_kutta_time_stepper.cpp b/unittests/dynamics/test_runge_kutta_time_stepper.cpp new file mode 100644 index 0000000000..4c4c7b7588 --- /dev/null +++ b/unittests/dynamics/test_runge_kutta_time_stepper.cpp @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/runge_kutta_time_stepper.h" +#include "runge_kutta_test_helpers.h" +#include +#include +#include + +// Test fixture class +class RungeKuttaTimeStepperTest : public ::testing::Test { +protected: + std::shared_ptr> stepper; + + void SetUp() override { + stepper = std::make_shared>( + simple_derivative); + } +}; + +// Single step integration +TEST_F(RungeKuttaTimeStepperTest, SingleStep) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 0.1; + + stepper->compute(state, t, dt); + + // Expected result using analytical solution: x(t) = e^(-t) + double expected = std::exp(-dt); + + EXPECT_NEAR(state, expected, 1e-3) + << "Single step Runge-Kutta integration failed!"; +} + +// Multiple step integration +TEST_F(RungeKuttaTimeStepperTest, MultipleSteps) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 0.1; + int steps = 10; + + for (int i = 0; i < steps; i++) { + stepper->compute(state, t, dt); + } + + // Expected result: x(t) = e^(-t) + double expected = std::exp(-1.0); + + EXPECT_NEAR(state, expected, 1e-2) + << "Multiple step Runge-Kutta integration failed!"; +} + +// Convergence to Analytical Solution +TEST_F(RungeKuttaTimeStepperTest, Convergence) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 0.01; + int steps = 100; + + for (int i = 0; i < steps; i++) { + stepper->compute(state, t, dt); + } + + double expected = std::exp(-1.0); + + EXPECT_NEAR(state, expected, 1e-3) + << "Runge-Kutta integration does not converge!"; +} + +// // Integrating Sine function +// TEST_F(RungeKuttaTimeStepperTest, SineFunction) { +// auto sine_stepper = +// std::make_shared>(sine_derivative); + +// // Initial values +// double state = 0.0; +// double t = 0.0; +// double dt = 0.1; +// int steps = 10; + +// for (int i = 0; i < steps; i++) { +// sine_stepper->compute(state, t, dt); +// } + +// // Expected integral of sin(t) over [0, 1] is 1 - cos(1) +// double expected = 1 - std::cos(1); + +// EXPECT_NEAR(state, expected, 1e-2) << "Runge-Kutta integration for sine +// function failed!"; +// } + +// Handling small steps sizes +TEST_F(RungeKuttaTimeStepperTest, SmallStepSize) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 1e-5; + int steps = 100000; + + for (int i = 0; i < steps; i++) { + stepper->compute(state, t, dt); + } + + double expected = std::exp(-1.0); + + EXPECT_NEAR(state, expected, 1e-3) + << "Runge-Kutta fails with small step sizes!"; +} + +// Handling large steps sizes +TEST_F(RungeKuttaTimeStepperTest, LargeStepSize) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 1.0; + + stepper->compute(state, t, dt); + + double expected = std::exp(-1.0); + + EXPECT_NEAR(state, expected, 1e-1) + << "Runge-Kutta is unstable with large step sizes!"; +} + +// Constant derivative (dx/dt = 0) +TEST_F(RungeKuttaTimeStepperTest, ConstantFunction) { + auto constant_stepper = + std::make_shared>( + [](const TestState &state, double t) { return 0.0; }); + + // Initial values + double state = 5.0; + double t = 0.0; + double dt = 0.1; + int steps = 10; + + for (int i = 0; i < steps; i++) { + constant_stepper->compute(state, t, dt); + } + + EXPECT_NEAR(state, 5.0, 1e-6) + << "Runge-Kutta should not change a constant function!"; +}