diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b9a0acaaa..1a44cf7494 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,6 +151,9 @@ endif() if(NOT CUTENSORNET_ROOT) SET(CUTENSORNET_ROOT "$ENV{CUQUANTUM_INSTALL_PREFIX}") endif() +if(NOT CUDENSITYMAT_ROOT) + SET(CUDENSITYMAT_ROOT "$ENV{CUQUANTUM_INSTALL_PREFIX}") +endif() if(NOT CUTENSOR_ROOT) SET(CUTENSOR_ROOT "$ENV{CUTENSOR_INSTALL_PREFIX}") endif() diff --git a/docs/sphinx/api/languages/cpp_api.rst b/docs/sphinx/api/languages/cpp_api.rst index 34487dbefb..79065bebb2 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::matrix_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/common/EvolveResult.h b/runtime/common/EvolveResult.h index 06382f4ff6..c254e3af18 100644 --- a/runtime/common/EvolveResult.h +++ b/runtime/common/EvolveResult.h @@ -108,8 +108,8 @@ class evolve_result { return final_expectation_values; } - std::optional>> - get_expectation_values() { + const std::optional>> & + get_expectation_values() const { return expectation_values; } 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..fed0238ea5 --- /dev/null +++ b/runtime/cudaq/base_integrator.h @@ -0,0 +1,81 @@ +/****************************************************************-*- 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: + /// @brief Default constructor + BaseIntegrator() = default; + + /// @brief Constructor to initialize the integrator with a state and time + /// stepper. + /// @param initial_state Initial quantum state. + /// @param t0 Initial time. + /// @param stepper Time stepper instance. + BaseIntegrator(TState &&initial_state, double t0, + std::shared_ptr> stepper) + : state(std::move(initial_state)), t(t0), stepper(std::move(stepper)) { + if (!this->stepper) { + throw std::runtime_error("Time stepper is not initialized."); + } + } + + virtual ~BaseIntegrator() = default; + + /// @brief Set the initial state and time + void set_state(const TState &initial_state, double t0 = 0.0) { + state = initial_state; + t = t0; + } + + /// @brief Set an option for the integrator + void set_option(const std::string &key, double value) { + integrator_options[key] = value; + } + + /// @brief Set the system parameters (dimensions, schedule, and operators) + // 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; + // } + + /// @brief Perform integration to the target time. + virtual void integrate(double target_time) = 0; + + /// @brief Get the current time and state. + 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..2c8150de0f --- /dev/null +++ b/runtime/cudaq/base_time_stepper.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 + +namespace cudaq { +template +class BaseTimeStepper { +public: + virtual ~BaseTimeStepper() = default; + + /// @brief Compute the next time step for the given quantum state. + /// @param state The quantum state to evolve. + /// @param t Current time. + /// @param step_size Time step size. + /// @return The updated quantum state after stepping. + virtual TState 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_expectation.h b/runtime/cudaq/cudm_expectation.h new file mode 100644 index 0000000000..0dfe3a60ec --- /dev/null +++ b/runtime/cudaq/cudm_expectation.h @@ -0,0 +1,35 @@ +/****************************************************************-*- 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 +namespace cudaq { + +class cudm_expectation { + cudensitymatHandle_t m_handle{nullptr}; + cudensitymatOperator_t m_hamOp{nullptr}; + cudensitymatExpectation_t m_expectation{nullptr}; + cudensitymatWorkspaceDescriptor_t m_workspace{nullptr}; + +public: + cudm_expectation(cudensitymatHandle_t handle, cudensitymatOperator_t op); + cudm_expectation(const cudm_expectation &) = delete; + cudm_expectation &operator=(const cudm_expectation &) = delete; + cudm_expectation(cudm_expectation &&src) { + std::swap(m_handle, src.m_handle); + std::swap(m_hamOp, src.m_hamOp); + std::swap(m_expectation, src.m_expectation); + std::swap(m_workspace, src.m_workspace); + } + ~cudm_expectation(); + void prepare(cudensitymatState_t state); + std::complex compute(cudensitymatState_t state, double time); +}; + +} // namespace cudaq diff --git a/runtime/cudaq/cudm_helpers.h b/runtime/cudaq/cudm_helpers.h new file mode 100644 index 0000000000..893dba8b32 --- /dev/null +++ b/runtime/cudaq/cudm_helpers.h @@ -0,0 +1,98 @@ +/****************************************************************-*- 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 { +class cudm_helper { +public: + explicit cudm_helper(cudensitymatHandle_t handle); + ~cudm_helper(); + + // Matrix flattening + static std::vector> + flatten_matrix(const matrix_2 &matrix); + + // State Operations + void scale_state(cudensitymatState_t state, double scale_factor, + cudaStream_t stream); + + // Compute Lindblad Operator + cudensitymatOperator_t + compute_lindblad_operator(const std::vector &c_ops, + const std::vector &mode_extents); + + // Convert operator sum to cudensitymat operator + template + cudensitymatOperator_t convert_to_cudensitymat_operator( + const std::map> ¶meters, + const operator_sum &op, + const std::vector &mode_extents); + + // Construct Liouvillian + cudensitymatOperator_t construct_liouvillian( + const operator_sum &op, + const std::vector *> + &collapse_operators, + const std::vector &mode_extents, + const std::map> ¶meters, + bool is_master_equation); + + // Construct Liouvillian + cudensitymatOperator_t construct_liouvillian( + const cudensitymatOperator_t &hamiltonian, + const std::vector &collapse_operators, + double gamma); + + // Helper Functions + std::map + convert_dimensions(const std::vector &mode_extents); + std::vector + get_subspace_extents(const std::vector &mode_extents, + const std::vector °rees); + + // Callback Wrappers + static cudensitymatWrappedScalarCallback_t + _wrap_callback(const scalar_operator &scalar_op); + static cudensitymatWrappedTensorCallback_t + _wrap_tensor_callback(const matrix_operator &op); + + // Elementary Operator Functions + void append_scalar_to_term(cudensitymatOperatorTerm_t term, + const scalar_operator &scalar_op); + cudensitymatElementaryOperator_t create_elementary_operator( + const cudaq::matrix_operator &elem_op, + const std::map> ¶meters, + const std::vector &mode_extents); + void append_elementary_operator_to_term( + cudensitymatOperatorTerm_t term, + const std::vector &elem_ops, + const std::vector> °rees, bool is_dagger); + + // GPU memory management + static void * + create_array_gpu(const std::vector> &cpu_array); + static void destroy_array_gpu(void *gpu_array); + +private: + cudensitymatHandle_t handle; +}; + +extern template cudensitymatOperator_t +cudm_helper::convert_to_cudensitymat_operator( + const std::map> &, + const operator_sum &, const std::vector &); +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/cudm_op_conversion.h b/runtime/cudaq/cudm_op_conversion.h new file mode 100644 index 0000000000..ed8db413b0 --- /dev/null +++ b/runtime/cudaq/cudm_op_conversion.h @@ -0,0 +1,78 @@ +/****************************************************************-*- 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/cudm_helpers.h" +#include "cudaq/operators.h" +#include "cudaq/schedule.h" +#include +#include + +namespace cudaq { +class cudm_op_conversion { +public: + cudm_op_conversion(const cudensitymatHandle_t handle, + const std::map &dimensions, + const std::shared_ptr schedule = nullptr); + + // Tensor product of two operator terms + std::variant> + tensor(const std::variant> &op1, + const std::variant> &op2); + + // Multiplication of two operator terms + std::variant> + mul(const std::variant> &op1, + const std::variant> &op2); + + // Addition of two operator terms + std::variant> + add(const std::variant> &op1, + const std::variant> &op2); + + // Evaluate an operator and convert it to cudensitymatOperatorTerm_t + std::variant> + evaluate(const std::variant> &op); + +private: + cudensitymatHandle_t handle_; + std::map dimensions_; + std::shared_ptr schedule_; + + cudensitymatOperatorTerm_t + _callback_mult_op(const cudensitymatWrappedScalarCallback_t &scalar, + const cudensitymatOperatorTerm_t &op); + cudensitymatOperatorTerm_t + _scalar_to_op(const cudensitymatWrappedScalarCallback_t &scalar); + // cudensitymatWrappedScalarCallback_t _wrap_callback(const scalar_operator + // &op); cudensitymatWrappedTensorCallback_t _wrap_callback_tensor(const + // matrix_operator &op); + + std::vector> get_identity_matrix(); + + std::vector get_space_mode_extents(); +}; +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/cudm_solver.h b/runtime/cudaq/cudm_solver.h new file mode 100644 index 0000000000..32fc7b00a8 --- /dev/null +++ b/runtime/cudaq/cudm_solver.h @@ -0,0 +1,58 @@ +/****************************************************************-*- 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 "runtime/common/EvolveResult.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cudaq { + +// Configuration struct for the solver +struct Config { + std::map dimensions; // Hilbert space dimensions + operator_sum hamiltonian; // Hamiltonian operator + std::vector collapse_operators; // Collapse operators + std::vector observables; // Observables to evaluate + std::variant>> + initial_state; // Initial state + Schedule schedule; // Evolution schedule + bool store_intermediate_results = false; // Flag to store intermediate states +}; + +class cudm_solver { +public: + cudm_solver(const Config &config); + + void validate_config(); + + cudm_state initialize_state(); + + void evolve(cudm_state &state, cudensitymatOperator_t &liouvillian, + const std::vector &obervable_ops, + evolve_result &result); + + evolve_result evolve_dynamics(); + + cudensitymatOperator_t construct_liouvillian( + cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, + const std::vector &collapse_operators, + bool me_solve); + +private: + Config config_; +}; +} // 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..c72494cf62 --- /dev/null +++ b/runtime/cudaq/cudm_state.h @@ -0,0 +1,140 @@ +/****************************************************************-*- 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 { +// Enum to specify the initial quantum state. +enum class InitialState { ZERO, UNIFORM }; + +using InitialStateArgT = std::variant; + +class cudm_state { +public: + /// @brief To initialize state with raw data. + explicit cudm_state(cudensitymatHandle_t handle, + const std::vector> rawData, + const std::vector &hilbertSpaceDims); + + /// @brief To initialize state from a `cudaq::state` + explicit cudm_state(cudensitymatHandle_t handle, const cudaq::state &simState, + const std::vector &hilbertSpaceDims); + + // @brief Create a zero state + static cudm_state zero_like(const cudm_state &other); + + // Prevent copies (avoids double free issues) + cudm_state(const cudm_state &) = delete; + cudm_state &operator=(const cudm_state &) = delete; + + // Allow move semantics + cudm_state(cudm_state &&other) noexcept; + cudm_state &operator=(cudm_state &&other) noexcept; + + /// @brief Destructor to clean up resources + ~cudm_state(); + + /// @brief Factory method to create an initial state. + /// @param InitialStateArgT The type or representation of the initial state. + /// @param Dimensions of the Hilbert space. + /// @param hasCollapseOps Whether collapse operators are present. + /// @return A new 'cudm_state' initialized to the specified state. + static cudm_state create_initial_state( + cudensitymatHandle_t handle, const InitialStateArgT &initialStateArg, + const std::vector &hilbertSpaceDims, bool hasCollapseOps); + + /// @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 Dump the state data to the console for debugging purposes. + void dumpDeviceData() const; + + /// @brief Convert the state vector to a density matrix. + /// @return A new cudm_state representing the density matrix. + cudm_state to_density_matrix() const; + + /// @brief Get the underlying implementation (if any). + /// @return The underlying state implementation. + cudensitymatState_t get_impl() const; + + /// @brief Get a copy of the raw data representing the quantum state. + /// @return A copy of the raw data as a vector of complex numbers. + std::vector> get_raw_data() const; + + /// @brief Get the pointer to device memory buffer storing the state. + /// @return GPU device pointer + void *get_device_pointer() const; + + /// @brief Get a copy of the hilbert space dimensions for the quantum state. + /// @return A copy of the hilbert space dimensions of a vector of integers. + std::vector get_hilbert_space_dims() const; + + /// @brief Returns the handle + /// @return The handle associated with the state + cudensitymatHandle_t get_handle() const; + + /// @brief Addition operator (element-wise) + /// @return The new state after the summation of two states. + cudm_state operator+(const cudm_state &other) const; + + /// @brief Accumulation operator + /// @return Accumulates the summation of two states. + cudm_state &operator+=(const cudm_state &other); + + /// @brief Scalar multiplication operator + /// @return The new state after multiplying scalar with the current state. + cudm_state &operator*=(const std::complex &scalar); + + cudm_state operator*(double scalar) const; + +private: + // TODO: remove this host raw data, we shouldn't keep this as it will be + // decoupled to the GPU data. + std::vector> rawData_; + int64_t gpuDataSize_ = 0; + std::complex *gpuData_; + cudensitymatState_t state_; + cudensitymatHandle_t handle_; + std::vector hilbertSpaceDims_; + // Private default constructor + cudm_state() = default; + /// @brief Attach raw data storage to GPU + void attach_storage(); + + /// @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. + static size_t calculate_state_vector_size( + const std::vector &hilbertSpaceDims); + + /// @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. + static size_t + calculate_density_matrix_size(const std::vector &hilbertSpaceDims); +}; + +} // namespace cudaq diff --git a/runtime/cudaq/cudm_time_stepper.h b/runtime/cudaq/cudm_time_stepper.h new file mode 100644 index 0000000000..6037ee00f1 --- /dev/null +++ b/runtime/cudaq/cudm_time_stepper.h @@ -0,0 +1,27 @@ +/****************************************************************-*- 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/base_time_stepper.h" +#include "cudaq/cudm_state.h" +#include + +namespace cudaq { +class cudm_time_stepper : public BaseTimeStepper { +public: + explicit cudm_time_stepper(cudensitymatHandle_t handle, + cudensitymatOperator_t liouvillian); + + cudm_state compute(cudm_state &state, double t, double step_size); + +private: + cudensitymatHandle_t handle_; + cudensitymatOperator_t liouvillian_; +}; +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/definition.h b/runtime/cudaq/definition.h new file mode 100644 index 0000000000..49decefeb6 --- /dev/null +++ b/runtime/cudaq/definition.h @@ -0,0 +1,161 @@ +/****************************************************************-*- 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/qis/state.h" +#include "cudaq/utils/tensor.h" + +#include +#include +#include +#include +#include +#include + +namespace cudaq { + +using Func = std::function, std::map>)>; + +class CallbackFunction { +private: + // The user provided callback function that takes a vector defining the + // dimension for each degree of freedom it acts on, and a map 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(const CallbackFunction &other) { + _callback_func = other._callback_func; + } + + // move constructor. + CallbackFunction(CallbackFunction &&other) { + _callback_func = std::move(other._callback_func); + } + + // assignment operator + CallbackFunction &operator=(const CallbackFunction &other) { + if (this != &other) { + _callback_func = other._callback_func; + } + return *this; + } + + // move assignment operator + CallbackFunction &operator=(CallbackFunction &&other) { + if (this != &other) { + _callback_func = std::move(other._callback_func); + } + return *this; + } + + bool operator!() { return (!_callback_func); } + + matrix_2 + operator()(std::vector relevant_dimensions, + std::map> parameters) const { + return _callback_func(std::move(relevant_dimensions), + 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(const ScalarCallbackFunction &other) { + _callback_func = other._callback_func; + } + + // move constructor. + ScalarCallbackFunction(ScalarCallbackFunction &&other) { + _callback_func = std::move(other._callback_func); + } + + // assignment operator + ScalarCallbackFunction &operator=(const ScalarCallbackFunction &other) { + if (this != &other) { + _callback_func = other._callback_func; + } + return *this; + } + + // move assignment operator + ScalarCallbackFunction &operator=(ScalarCallbackFunction &&other) { + if (this != &other) { + _callback_func = std::move(other._callback_func); + } + return *this; + } + + 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 { +private: + std::string id; + CallbackFunction generator; + std::vector m_expected_dimensions; + +public: + Definition(const std::string &operator_id, + std::vector expected_dimensions, CallbackFunction &&create); + Definition(Definition &&def); + ~Definition(); + + // To call the generator function + matrix_2 generate_matrix( + const std::vector &relevant_dimensions, + const std::map> ¶meters) const; +}; +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/CMakeLists.txt b/runtime/cudaq/dynamics/CMakeLists.txt new file mode 100644 index 0000000000..5eefd2029c --- /dev/null +++ b/runtime/cudaq/dynamics/CMakeLists.txt @@ -0,0 +1,64 @@ +# ============================================================================ # +# 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 + helpers.cpp + rydberg_hamiltonian.cpp + cudm_helpers.cpp + cudm_state.cpp + cudm_time_stepper.cpp + runge_kutta_integrator.cpp + definition.cpp + scalar_operators.cpp + matrix_operators.cpp + product_operators.cpp + operator_sum.cpp + schedule.cpp + manipulation.cpp + helpers.cpp + rydberg_hamiltonian.cpp + cudm_expectation.cpp + evolution.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_compile_definitions(${LIBRARY_NAME} PRIVATE -DCUDAQ_INSTANTIATE_TEMPLATES) + +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_expectation.cpp b/runtime/cudaq/dynamics/cudm_expectation.cpp new file mode 100644 index 0000000000..2705b648b4 --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_expectation.cpp @@ -0,0 +1,79 @@ +/******************************************************************************* + * 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_expectation.h" +#include "common/Logger.h" +#include "cudaq/cudm_error_handling.h" +#include "cudaq/cudm_helpers.h" +#include + +namespace cudaq { +cudm_expectation::cudm_expectation(cudensitymatHandle_t handle, + cudensitymatOperator_t op) + : m_handle(handle), m_hamOp(op) { + HANDLE_CUDM_ERROR( + cudensitymatCreateExpectation(m_handle, m_hamOp, &m_expectation)); + HANDLE_CUDM_ERROR(cudensitymatCreateWorkspace(m_handle, &m_workspace)); +} + +cudm_expectation::~cudm_expectation() { + if (m_workspace) + cudensitymatDestroyWorkspace(m_workspace); + if (m_expectation) + cudensitymatDestroyExpectation(m_expectation); +} + +void cudm_expectation::prepare(cudensitymatState_t state) { + std::size_t freeMem = 0, totalMem = 0; + HANDLE_CUDA_ERROR(cudaMemGetInfo(&freeMem, &totalMem)); + freeMem = static_cast(static_cast(freeMem) * 0.80); + + HANDLE_CUDM_ERROR(cudensitymatExpectationPrepare( + m_handle, m_expectation, state, CUDENSITYMAT_COMPUTE_64F, freeMem, + m_workspace, 0x0)); +} +std::complex cudm_expectation::compute(cudensitymatState_t state, + double time) { + // TODO: create a global scratch buffer + std::size_t requiredBufferSize = 0; + HANDLE_CUDM_ERROR(cudensitymatWorkspaceGetMemorySize( + m_handle, m_workspace, CUDENSITYMAT_MEMSPACE_DEVICE, + CUDENSITYMAT_WORKSPACE_SCRATCH, &requiredBufferSize)); + + void *workspaceBuffer = nullptr; + if (requiredBufferSize > 0) { + cudaq::info("Required buffer size for expectation compute: {}", + requiredBufferSize); + // Allocate GPU storage for workspace buffer + const std::size_t bufferVolume = + requiredBufferSize / sizeof(std::complex); + workspaceBuffer = cudm_helper::create_array_gpu( + std::vector>(bufferVolume, {0.0, 0.0})); + + // Attach workspace buffer + HANDLE_CUDM_ERROR(cudensitymatWorkspaceSetMemory( + m_handle, m_workspace, CUDENSITYMAT_MEMSPACE_DEVICE, + CUDENSITYMAT_WORKSPACE_SCRATCH, workspaceBuffer, requiredBufferSize)); + } + + auto *expectationValue_d = cudm_helper::create_array_gpu( + std::vector>(1, {0.0, 0.0})); + HANDLE_CUDM_ERROR(cudensitymatExpectationCompute( + m_handle, m_expectation, time, 0, nullptr, state, expectationValue_d, + m_workspace, 0x0)); + std::complex result; + HANDLE_CUDA_ERROR(cudaMemcpy(&result, expectationValue_d, + sizeof(std::complex), + cudaMemcpyDefault)); + cudm_helper::destroy_array_gpu(expectationValue_d); + if (workspaceBuffer) { + cudm_helper::destroy_array_gpu(workspaceBuffer); + } + return result; +} +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp new file mode 100644 index 0000000000..68a74517ad --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -0,0 +1,580 @@ +/******************************************************************************* + * 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" + +using namespace cudaq; + +namespace cudaq { +cudm_helper::cudm_helper(cudensitymatHandle_t handle) : handle(handle) {} + +cudm_helper::~cudm_helper() { + if (handle) { + cudensitymatDestroy(handle); + handle = nullptr; + } + + cudaDeviceSynchronize(); +} + +cudensitymatWrappedScalarCallback_t +cudm_helper::_wrap_callback(const scalar_operator &scalar_op) { + try { + std::complex evaluatedValue = scalar_op.evaluate({}); + + cudensitymatWrappedScalarCallback_t wrapped_callback; + wrapped_callback.callback = nullptr; + wrapped_callback.wrapper = new std::complex(evaluatedValue); + return wrapped_callback; + } catch (const std::exception &) { + } + + ScalarCallbackFunction generator = scalar_op.get_generator(); + + if (!generator) { + throw std::runtime_error( + "scalar_operator does not have a valid generator function."); + } + + auto callback = [](double time, int32_t num_params, const double params[], + cudaDataType_t data_type, + void *scalar_storage) -> int32_t { + try { + scalar_operator *scalar_op = + static_cast(scalar_storage); + + std::map> param_map; + for (size_t i = 0; i < num_params; i++) { + param_map[std::to_string(i)] = params[i]; + } + + std::complex result = scalar_op->evaluate(param_map); + + if (data_type == CUDA_C_64F) { + *reinterpret_cast(scalar_storage) = + make_cuDoubleComplex(result.real(), result.imag()); + } else if (data_type == CUDA_C_32F) { + *reinterpret_cast(scalar_storage) = + make_cuFloatComplex(static_cast(result.real()), + static_cast(result.imag())); + } else { + return CUDENSITYMAT_STATUS_INVALID_VALUE; + } + + return CUDENSITYMAT_STATUS_SUCCESS; + } catch (const std::exception &e) { + std::cerr << "Error in scalar callback: " << e.what() << std::endl; + return CUDENSITYMAT_STATUS_INTERNAL_ERROR; + } + }; + + cudensitymatWrappedScalarCallback_t wrappedCallback; + wrappedCallback.callback = callback; + wrappedCallback.wrapper = new scalar_operator(scalar_op); + + return wrappedCallback; +} + +cudensitymatWrappedTensorCallback_t +cudm_helper::_wrap_tensor_callback(const matrix_operator &op) { + auto callback = + [](cudensitymatElementaryOperatorSparsity_t sparsity, int32_t num_modes, + const int64_t mode_extents[], const int32_t diagonal_offsets[], + double time, int32_t num_params, const double params[], + cudaDataType_t data_type, void *tensor_storage) -> int32_t { + try { + matrix_operator *mat_op = static_cast(tensor_storage); + + std::map> param_map; + for (size_t i = 0; i < num_params; i++) { + param_map[std::to_string(i)] = params[i]; + } + + matrix_2 matrix_data = mat_op->to_matrix({}, param_map); + + std::size_t rows = matrix_data.get_rows(); + std::size_t cols = matrix_data.get_columns(); + + if (num_modes != rows) { + return CUDENSITYMAT_STATUS_INVALID_VALUE; + } + + if (data_type == CUDA_C_64F) { + cuDoubleComplex *storage = + static_cast(tensor_storage); + for (size_t i = 0; i < rows; i++) { + for (size_t j = 0; j < cols; j++) { + storage[i * cols + j] = make_cuDoubleComplex( + matrix_data[{i, j}].real(), matrix_data[{i, j}].imag()); + } + } + } else if (data_type == CUDA_C_32F) { + cuFloatComplex *storage = static_cast(tensor_storage); + for (size_t i = 0; i < rows; i++) { + for (size_t j = 0; j < cols; j++) { + storage[i * cols + j] = make_cuFloatComplex( + static_cast(matrix_data[{i, j}].real()), + static_cast(matrix_data[{i, j}].imag())); + } + } + } else { + return CUDENSITYMAT_STATUS_INVALID_VALUE; + } + + return CUDENSITYMAT_STATUS_SUCCESS; + } catch (const std::exception &e) { + std::cerr << "Error in tensor callback: " << e.what() << std::endl; + return CUDENSITYMAT_STATUS_INTERNAL_ERROR; + } + }; + + cudensitymatWrappedTensorCallback_t wrapped_callback; + wrapped_callback.callback = callback; + wrapped_callback.wrapper = new matrix_operator(op); + + return wrapped_callback; +} + +// Function to flatten a matrix into a 1D array (column major) +std::vector> +cudm_helper::flatten_matrix(const matrix_2 &matrix) { + std::vector> flat_matrix; + flat_matrix.reserve(matrix.get_size()); + for (size_t col = 0; col < matrix.get_columns(); col++) { + for (size_t row = 0; row < matrix.get_rows(); row++) { + flat_matrix.push_back(matrix[{row, col}]); + } + } + + return flat_matrix; +} + +// Function to extract sub-space extents based on degrees +std::vector +cudm_helper::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 +// Need to use std::variant +cudensitymatElementaryOperator_t cudm_helper::create_elementary_operator( + const cudaq::matrix_operator &elem_op, + const std::map> ¶meters, + const std::vector &mode_extents) { + 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)); + + if (flat_matrix.empty()) { + throw std::invalid_argument("Input matrix (flat matrix) cannot be empty."); + } + + if (subspace_extents.empty()) { + throw std::invalid_argument("subspace_extents cannot be empty."); + } + + cudensitymatWrappedTensorCallback_t wrapped_tensor_callback = {nullptr, + nullptr}; + + if (!parameters.empty()) { + + wrapped_tensor_callback = _wrap_tensor_callback(elem_op); + } + + // FIXME: leak (need to track this buffer somewhere and delete **after** the + // whole evolve) + auto *elementaryMat_d = create_array_gpu(flat_matrix); + cudensitymatElementaryOperator_t cudm_elem_op = nullptr; + + HANDLE_CUDM_ERROR(cudensitymatCreateElementaryOperator( + handle, static_cast(subspace_extents.size()), + subspace_extents.data(), CUDENSITYMAT_OPERATOR_SPARSITY_NONE, 0, nullptr, + CUDA_C_64F, elementaryMat_d, wrapped_tensor_callback, &cudm_elem_op)); + + if (!cudm_elem_op) { + std::cerr << "[ERROR] cudm_elem_op is NULL in create_elementary_operator!" + << std::endl; + destroy_array_gpu(elementaryMat_d); + throw std::runtime_error("Failed to create elementary operator."); + } + + return cudm_elem_op; +} + +// Function to append an elementary operator to a term +void cudm_helper::append_elementary_operator_to_term( + cudensitymatOperatorTerm_t term, + const std::vector &elem_ops, + const std::vector> °rees, bool is_dager) { + + if (degrees.empty()) { + throw std::invalid_argument("Degrees vector cannot be empty."); + } + + if (elem_ops.empty()) { + throw std::invalid_argument("elem_ops cannot be null."); + } + + if (degrees.size() != elem_ops.size()) { + throw std::invalid_argument( + "elem_ops and degrees must have the same size."); + } + + std::vector allDegrees; + std::vector allModeActionDuality; + for (const auto &sub_degrees : degrees) { + if (sub_degrees.size() != 1) { + throw std::runtime_error( + "Elementary operator must act on a single degree."); + } + for (int degree : sub_degrees) { + if (degree < 0) { + throw std::out_of_range("Degree cannot be negative!"); + } + allDegrees.emplace_back(degree); + allModeActionDuality.emplace_back(is_dager ? 1 : 0); + } + } + + assert(elem_ops.size() == degrees.size()); + HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( + handle, term, static_cast(allDegrees.size()), elem_ops.data(), + allDegrees.data(), allModeActionDuality.data(), + make_cuDoubleComplex(1.0, 0.0), {nullptr, nullptr})); +} + +// Function to create and append a scalar to a term +void cudm_helper::append_scalar_to_term(cudensitymatOperatorTerm_t term, + const scalar_operator &scalar_op) { + cudensitymatWrappedScalarCallback_t wrapped_callback = {nullptr, nullptr}; + + if (!scalar_op.get_generator()) { + std::complex coeff = scalar_op.evaluate({}); + HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( + handle, term, 0, nullptr, nullptr, nullptr, + {make_cuDoubleComplex(coeff.real(), coeff.imag())}, wrapped_callback)); + } else { + wrapped_callback = _wrap_callback(scalar_op); + HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( + handle, term, 0, nullptr, nullptr, nullptr, + {make_cuDoubleComplex(1.0, 0.0)}, wrapped_callback)); + } +} + +void cudm_helper::scale_state(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)); + + HANDLE_CUDA_ERROR(cudaStreamSynchronize(stream)); +} + +// c_ops: std::vector +cudensitymatOperator_t cudm_helper::compute_lindblad_operator( + 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)); + + std::vector terms; + std::vector elem_ops; + std::vector> all_degrees; + + try { + 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."); + } + + matrix_2 L_dagger_op_matrix = matrix_2::adjoint(c_op); + + const std::string L_id = "L_op"; + const std::string L_dagger_id = "L_dagger_op"; + + cudaq::matrix_operator::define( + L_id, {-1}, + [c_op](std::vector dims, + std::map>) { return c_op; }); + + cudaq::matrix_operator::define( + L_dagger_id, {-1}, + [L_dagger_op_matrix](std::vector dims, + std::map>) { + return L_dagger_op_matrix; + }); + + matrix_operator L_op(L_id, {0}); + matrix_operator L_dagger_op(L_dagger_id, {0}); + + cudensitymatElementaryOperator_t L_elem_op = + create_elementary_operator(L_op, {}, mode_extents); + + cudensitymatElementaryOperator_t L_dagger_elem_op = + create_elementary_operator(L_dagger_op, {}, mode_extents); + + if (!L_elem_op || !L_dagger_elem_op) { + throw std::runtime_error("Failed to create elementary operators in " + "compute_lindblad_operator."); + } + + elem_ops.emplace_back(L_elem_op); + all_degrees.emplace_back(L_op.degrees); + elem_ops.emplace_back(L_dagger_elem_op); + all_degrees.emplace_back(L_dagger_op.degrees); + + // D1 = L * Lt + cudensitymatOperatorTerm_t term_D1; + HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), + mode_extents.data(), &term_D1)); + + append_elementary_operator_to_term(term_D1, elem_ops, all_degrees, false); + + // Add term D1 to the Lindblad operator + + cudensitymatWrappedScalarCallback_t scalar_callback = {nullptr, nullptr}; + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( + handle, lindblad_op, term_D1, 0, make_cuDoubleComplex(1.0, 0.0), + scalar_callback)); + + // D2 = -0.5 * (Lt * L) + cudensitymatOperatorTerm_t term_D2; + HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), + mode_extents.data(), &term_D2)); + + append_elementary_operator_to_term(term_D2, elem_ops, all_degrees, false); + + // Add term D2 to the Lindblad operator + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( + handle, lindblad_op, term_D2, 0, make_cuDoubleComplex(-0.5, 0.0), + scalar_callback)); + + // D3 = -0.5 * (L * Lt) + cudensitymatOperatorTerm_t term_D3; + HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), + mode_extents.data(), &term_D3)); + append_elementary_operator_to_term(term_D3, elem_ops, all_degrees, true); + + // Add term D3 to the Lindblad operator + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( + handle, lindblad_op, term_D3, 0, make_cuDoubleComplex(-0.5, 0.0), + scalar_callback)); + } + + HANDLE_CUDA_ERROR(cudaDeviceSynchronize()); + } catch (const std::exception &e) { + std::cerr << "Exception in compute_lindblad_operator: " << e.what() + << std::endl; + + for (auto term : terms) { + HANDLE_CUDM_ERROR(cudensitymatDestroyOperatorTerm(term)); + } + + for (auto elem_op : elem_ops) { + HANDLE_CUDM_ERROR(cudensitymatDestroyElementaryOperator(elem_op)); + } + + cudensitymatDestroyOperator(lindblad_op); + return nullptr; + } + + for (auto term : terms) { + HANDLE_CUDM_ERROR(cudensitymatDestroyOperatorTerm(term)); + } + + for (auto elem_op : elem_ops) { + HANDLE_CUDM_ERROR(cudensitymatDestroyElementaryOperator(elem_op)); + } + + return lindblad_op; +} + +std::map +cudm_helper::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; +} + +template +cudensitymatOperator_t cudm_helper::convert_to_cudensitymat_operator( + const std::map> ¶meters, + const operator_sum &op, + const std::vector &mode_extents) { + if (op.get_terms().empty()) { + throw std::invalid_argument("Operator sum cannot be empty."); + } + + 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)); + + std::vector elem_ops; + std::vector> all_degrees; + for (const auto &component : product_op.get_terms()) { + // No need to check type + // just call to_matrix on it + if (const auto *elem_op = + dynamic_cast(&component)) { + auto cudm_elem_op = + create_elementary_operator(*elem_op, parameters, mode_extents); + elem_ops.emplace_back(cudm_elem_op); + all_degrees.emplace_back(elem_op->degrees); + } else { + // Catch anything that we don't know + throw std::runtime_error("Unhandled type!"); + } + } + append_elementary_operator_to_term(term, elem_ops, all_degrees, false); + auto coeff = product_op.get_coefficient(); + cudensitymatWrappedScalarCallback_t wrapped_callback = {nullptr, nullptr}; + + if (!coeff.get_generator()) { + const auto coeffVal = coeff.evaluate(); + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( + handle, operator_handle, term, 0, + make_cuDoubleComplex(coeffVal.real(), coeffVal.imag()), + wrapped_callback)); + } else { + wrapped_callback = _wrap_callback(coeff); + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( + handle, operator_handle, term, 0, make_cuDoubleComplex(1.0, 0.0), + wrapped_callback)); + } + + // FIXME: leak + // We must track these handles and destroy **after** evolve finishes + // 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 cudm_helper::construct_liouvillian( + const operator_sum &op, + const std::vector *> + &collapse_operators, + const std::vector &mode_extents, + const std::map> ¶meters, + bool is_master_equation) { + if (!is_master_equation && collapse_operators.empty()) { + auto liouvillian = op * std::complex(0.0, -1.0); + return convert_to_cudensitymat_operator(parameters, liouvillian, + mode_extents); + } else { + throw std::runtime_error("TODO: handle Lindblad equation"); + } +} + +cudensitymatOperator_t cudm_helper::construct_liouvillian( + const cudensitymatOperator_t &hamiltonian, + const std::vector &collapse_operators, + double gamma) { + try { + cudensitymatOperator_t liouvillian; + HANDLE_CUDM_ERROR( + cudensitymatCreateOperator(handle, 0, nullptr, &liouvillian)); + + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( + handle, liouvillian, hamiltonian, 0, {1.0, 0.0}, scalarCallback)); + + // Collapse operator scaled by gamma + cuDoubleComplex coefficient = make_cuDoubleComplex(gamma, 0.0); + for (const auto &c_op : collapse_operators) { + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( + handle, liouvillian, c_op, 0, coefficient, scalarCallback)); + } + + return liouvillian; + } catch (const std::exception &e) { + std::cerr << "Error in construct_liouvillian: " << e.what() << std::endl; + throw; + } +} + +// Function for creating an array copy in GPU memory +void *cudm_helper::create_array_gpu( + const std::vector> &cpu_array) { + void *gpu_array{nullptr}; + const std::size_t array_size = + cpu_array.size() * sizeof(std::complex); + if (array_size > 0) { + HANDLE_CUDA_ERROR(cudaMalloc(&gpu_array, array_size)); + HANDLE_CUDA_ERROR(cudaMemcpy(gpu_array, + static_cast(cpu_array.data()), + array_size, cudaMemcpyHostToDevice)); + } + return gpu_array; +} + +// Function to detsroy a previously created array copy in GPU memory +void cudm_helper::destroy_array_gpu(void *gpu_array) { + if (gpu_array) { + HANDLE_CUDA_ERROR(cudaFree(gpu_array)); + } +} + +template cudensitymatOperator_t +cudm_helper::convert_to_cudensitymat_operator( + const std::map> &, + const operator_sum &, const std::vector &); + +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/cudm_op_conversion.cpp b/runtime/cudaq/dynamics/cudm_op_conversion.cpp new file mode 100644 index 0000000000..2b45503cfa --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_op_conversion.cpp @@ -0,0 +1,297 @@ +/******************************************************************************* + * 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_op_conversion.h" +#include "cudaq/cudm_error_handling.h" +#include +#include +#include + +using namespace cudaq; + +namespace cudaq { +// cudm_op_conversion::cudm_op_conversion(const cudensitymatHandle_t handle, +// const std::map &dimensions, +// std::shared_ptr schedule) +// : handle_(handle), dimensions_(dimensions), schedule_(schedule) { +// if (handle_ == nullptr) { +// throw std::runtime_error("Handle cannot be null."); +// } + +// if (dimensions_.empty()) { +// throw std::invalid_argument("Dimensions map must not be empty."); +// } +// } + +// std::vector> cudm_op_conversion::get_identity_matrix() { +// size_t dim = 1; +// for (const auto &entry : dimensions_) { +// dim *= entry.second; +// } + +// std::vector> identity_matrix(dim * dim, {0.0, 0.0}); +// for (size_t i = 0; i < dim; i++) { +// identity_matrix[i * dim + i] = {1.0, 0.0}; +// } + +// return identity_matrix; +// } + +// std::vector cudm_op_conversion::get_space_mode_extents() { +// std::vector space_mode_extents; +// for (const auto &dim : dimensions_) { +// space_mode_extents.push_back(dim.second); +// } + +// return space_mode_extents; +// } + +// cudensitymatOperatorTerm_t cudm_op_conversion::_scalar_to_op( +// const cudensitymatWrappedScalarCallback_t &scalar) { +// std::vector space_mode_extents = get_space_mode_extents(); + +// cudensitymatOperatorTerm_t op_term; +// HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( +// handle_, dimensions_.size(), space_mode_extents.data(), &op_term)); + +// void *tensor_data = create_array_gpu(get_identity_matrix()); +// if (!tensor_data) { +// throw std::runtime_error("Failed to allocate GPU memory for +// tensor_data."); +// } + +// std::vector mode_action_duality(dimensions_.size(), +// CUDENSITYMAT_OPERATOR_SPARSITY_NONE); + +// cudensitymatElementaryOperator_t identity; +// HANDLE_CUDM_ERROR(cudensitymatCreateElementaryOperator( +// handle_, dimensions_.size(), space_mode_extents.data(), +// CUDENSITYMAT_OPERATOR_SPARSITY_NONE, 0, mode_action_duality.data(), +// CUDA_C_64F, tensor_data, {nullptr, nullptr}, &identity)); + +// std::vector states_modes_acted_on(dimensions_.size()); +// std::iota(states_modes_acted_on.begin(), states_modes_acted_on.end(), 0); + +// HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( +// handle_, op_term, 1, &identity, states_modes_acted_on.data(), +// mode_action_duality.data(), {1.0, 0.0}, scalar)); + +// return op_term; +// } + +// cudensitymatOperator_t cudm_op_conversion::_callback_mult_op( +// const cudensitymatWrappedScalarCallback_t &scalar, +// const cudensitymatOperatorTerm_t &op) { +// if (!op) { +// throw std::invalid_argument("Invalid operator term (nullptr)."); +// } + +// std::vector space_mode_extents = get_space_mode_extents(); + +// cudensitymatOperatorTerm_t scalar_op = _scalar_to_op(scalar); + +// if (!scalar_op) { +// throw std::runtime_error("scalar_op is NULL."); +// } + +// cudensitymatOperator_t new_op; +// HANDLE_CUDM_ERROR(cudensitymatCreateOperator( +// handle_, static_cast(dimensions_.size()), +// space_mode_extents.data(), &new_op)); + +// std::vector mode_action_duality(dimensions_.size(), +// CUDENSITYMAT_OPERATOR_SPARSITY_NONE); + +// HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm(handle_, new_op, +// scalar_op, +// mode_action_duality.size(), +// {1.0, 0.0}, scalar)); + +// HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( +// handle_, new_op, op, mode_action_duality.size(), {1.0, 0.0}, +// {nullptr, nullptr})); + +// return new_op; +// } + +// std::variant> +// cudm_op_conversion::tensor( +// const std::variant> &op1, +// const std::variant> &op2) { +// if (std::holds_alternative>(op1) && +// std::holds_alternative>(op2)) { +// return std::get>(op1) * +// std::get>(op2); +// } + +// if (std::holds_alternative>(op1)) { +// return _callback_mult_op( +// _wrap_callback(scalar_operator(std::get>(op1))), +// std::get(op2)); +// } + +// if (std::holds_alternative>(op2)) { +// return _callback_mult_op( +// _wrap_callback(scalar_operator(std::get>(op2))), +// std::get(op1)); +// } + +// if (std::holds_alternative(op1)) { +// return tensor( +// _scalar_to_op(std::get(op1)), +// std::get(op2)); +// } + +// if (std::holds_alternative(op2)) { +// return tensor( +// _scalar_to_op(std::get(op2)), +// std::get(op1)); +// } + +// std::vector space_mode_extents = get_space_mode_extents(); + +// cudensitymatOperator_t result; +// HANDLE_CUDM_ERROR(cudensitymatCreateOperator( +// handle_, dimensions_.size(), space_mode_extents.data(), &result)); + +// HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( +// handle_, result, std::get(op1), 0, {1.0, +// 0.0}, {nullptr, nullptr})); +// HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( +// handle_, result, std::get(op2), 0, {1.0, +// 0.0}, {nullptr, nullptr})); + +// return result; +// } + +// std::variant> +// cudm_op_conversion::mul(const std::variant> &op1, +// const std::variant> &op2) { +// return tensor(op1, op2); +// } + +// std::variant> +// cudm_op_conversion::add(const std::variant> &op1, +// const std::variant> &op2) { +// if (std::holds_alternative>(op1) && +// std::holds_alternative>(op2)) { +// return std::get>(op1) + +// std::get>(op2); +// } + +// if (std::holds_alternative>(op1)) { +// return _callback_mult_op( +// _wrap_callback(scalar_operator(std::get>(op1))), +// std::get(op2)); +// } + +// if (std::holds_alternative>(op2)) { +// return _callback_mult_op( +// _wrap_callback(scalar_operator(std::get>(op2))), +// std::get(op1)); +// } + +// // FIXME: Need to check later +// int32_t num_space_modes = +// std::max(static_cast(dimensions_.size()), 1); +// std::vector space_mode_extents = get_space_mode_extents(); + +// cudensitymatOperator_t result; +// HANDLE_CUDM_ERROR(cudensitymatCreateOperator( +// handle_, num_space_modes, space_mode_extents.data(), &result)); + +// HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( +// handle_, result, std::get(op1), 0, {1.0, +// 0.0}, {nullptr, nullptr})); +// HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( +// handle_, result, std::get(op2), 0, {1.0, +// 0.0}, {nullptr, nullptr})); + +// return result; +// } + +// std::variant> +// cudm_op_conversion::evaluate( +// const std::variant> &op) { +// if (std::holds_alternative(op)) { +// const scalar_operator &scalar_op = std::get(op); + +// ScalarCallbackFunction generator = scalar_op.get_generator(); + +// if (!generator) { +// return scalar_op.evaluate({}); +// } else { +// return _wrap_callback(scalar_op); +// } +// } + +// if (std::holds_alternative(op)) { +// const matrix_operator &mat_op = std::get(op); + +// std::vector space_mode_extents = get_space_mode_extents(); + +// cudensitymatOperatorTerm_t opterm; +// HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( +// handle_, dimensions_.size(), space_mode_extents.data(), &opterm)); + +// cudensitymatElementaryOperator_t elem_op; +// // Need to check if it is a static, use nullptr +// // or a callback and then only use callback +// cudensitymatWrappedTensorCallback_t callback = +// _wrap_callback_tensor(mat_op); + +// auto flat_matrix = flatten_matrix(mat_op.to_matrix(dimensions_, {})); + +// void *tensor_data = create_array_gpu(flat_matrix); +// if (!tensor_data) { +// throw std::runtime_error( +// "Failed to allocate GPU memory for tensor_data."); +// } + +// std::vector mode_action_duality( +// mat_op.degrees.size(), CUDENSITYMAT_OPERATOR_SPARSITY_NONE); + +// HANDLE_CUDM_ERROR(cudensitymatCreateElementaryOperator( +// handle_, mat_op.degrees.size(), space_mode_extents.data(), +// CUDENSITYMAT_OPERATOR_SPARSITY_NONE, 0, mode_action_duality.data(), +// CUDA_C_64F, tensor_data, callback, &elem_op)); + +// HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( +// handle_, opterm, 1, &elem_op, mat_op.degrees.data(), +// mode_action_duality.data(), {1.0, 0.0}, {nullptr, nullptr})); + +// return opterm; +// } + +// if (std::holds_alternative>(op)) { +// throw std::runtime_error( +// "Handling of product_operator is not implemented."); +// } + +// throw std::runtime_error( +// "Unknown operator type in cudm_op_conversion::evaluate."); +// } + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/cudm_solver.cpp b/runtime/cudaq/dynamics/cudm_solver.cpp new file mode 100644 index 0000000000..38f9a23591 --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_solver.cpp @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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_solver.h" +#include "cudaq/cudm_helpers.h" +#include "cudaq/cudm_state.h" +#include "cudaq/cudm_time_stepper.h" + +namespace cudaq { +cudm_solver::cudm_solver(const Config &config) : config_(config) { + validate_config(); +} + +void cudm_solver::validate_config() { + if (config_.dimensions.empty()) { + throw std::invalid_argument("Dimensions map cannot be empty."); + } + + if (config_.hamiltonian.get_terms().empty()) { + throw std::invalid_argument("Hamiltonian must have at least one term."); + } + + if (config_.dimensions.empty()) { + throw std::invalid_argument("Schedule cannot be empty."); + } +} + +cudm_state cudm_solver::initialize_state() { + std::vector mode_extents; + for (const auto &[key, value] : config_.dimensions) { + mode_extents.push_back(value); + } + + return cudm_state::create_initial_state(config_.initial_state, mode_extents, + !config_.collapse_operators.empty()); +} + +cudensitymatOperator_t cudm_solver::construct_liouvillian( + cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, + const std::vector &collapse_operators, + bool me_solve) { + return construct_liovillian(handle, hamiltonian, collapse_operators, + me_solve ? 1.0 : 0.0); +} + +void cudm_solver::evolve( + cudm_state &state, cudensitymatOperator_t &liouvillian, + const std::vector &observable_ops, + evolve_result &result) { + auto handle = state.get_impl(); + + // Initialize the stepper + cudm_time_stepper time_stepper(handle, liouvillian); +} +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp new file mode 100644 index 0000000000..f52eefcfbb --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -0,0 +1,473 @@ +/******************************************************************************* + * 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 +#include + +namespace cudaq { +cudm_state::cudm_state(cudensitymatHandle_t handle, + const cudaq::state &simState, + const std::vector &hilbertSpaceDims) + : handle_(handle), state_(nullptr), hilbertSpaceDims_(hilbertSpaceDims) { + if (!simState.is_on_gpu()) + throw std::runtime_error("Unexpected state. This must be a state created " + "by the dynamics target"); + const bool isDensityMat = simState.get_tensor().extents.size() == 2; + + gpuDataSize_ = isDensityMat ? calculate_density_matrix_size(hilbertSpaceDims) + : calculate_state_vector_size(hilbertSpaceDims); + + const size_t dataSize = gpuDataSize_ * sizeof(std::complex); + HANDLE_CUDA_ERROR(cudaMalloc(reinterpret_cast(&gpuData_), dataSize)); + + HANDLE_CUDA_ERROR(cudaMemcpy(gpuData_, simState.get_tensor().data, dataSize, + cudaMemcpyDefault)); + + const cudensitymatStatePurity_t purity = isDensityMat + ? CUDENSITYMAT_STATE_PURITY_MIXED + : CUDENSITYMAT_STATE_PURITY_PURE; + HANDLE_CUDM_ERROR(cudensitymatCreateState( + handle_, purity, static_cast(hilbertSpaceDims.size()), + hilbertSpaceDims.data(), 1, CUDA_C_64F, &state_)); + + // Query the size of the quantum state storage + std::size_t storageSize{0}; // only one storage component (tensor) is needed + HANDLE_CUDM_ERROR(cudensitymatStateGetComponentStorageSize( + handle_, state_, + 1, // only one storage component + &storageSize)); // storage size in bytes + const std::size_t stateVolume = + storageSize / sizeof(std::complex); // quantum state tensor volume + // (number of elements) + assert(stateVolume == gpuDataSize_); + // std::cout << "Quantum state storage size (bytes) = " << storageSize + // << std::endl; + + // Attach initialized GPU storage to the input quantum state + HANDLE_CUDM_ERROR(cudensitymatStateAttachComponentStorage( + handle_, state_, + 1, // only one storage component (tensor) + std::vector({gpuData_}) + .data(), // pointer to the GPU storage for the quantum state + std::vector({storageSize}) + .data())); // size of the GPU storage for the quantum state +} + +cudm_state cudm_state::zero_like(const cudm_state &other) { + cudm_state state; + state.handle_ = other.handle_; + state.hilbertSpaceDims_ = other.hilbertSpaceDims_; + state.gpuDataSize_ = other.gpuDataSize_; + const size_t dataSize = state.gpuDataSize_ * sizeof(std::complex); + HANDLE_CUDA_ERROR( + cudaMalloc(reinterpret_cast(&state.gpuData_), dataSize)); + HANDLE_CUDA_ERROR(cudaMemset(state.gpuData_, 0, dataSize)); + + const size_t expectedDensityMatrixSize = + calculate_density_matrix_size(state.hilbertSpaceDims_); + const bool isDensityMat = expectedDensityMatrixSize == state.gpuDataSize_; + const cudensitymatStatePurity_t purity = isDensityMat + ? CUDENSITYMAT_STATE_PURITY_MIXED + : CUDENSITYMAT_STATE_PURITY_PURE; + HANDLE_CUDM_ERROR(cudensitymatCreateState( + state.handle_, purity, + static_cast(state.hilbertSpaceDims_.size()), + state.hilbertSpaceDims_.data(), 1, CUDA_C_64F, &state.state_)); + + // Query the size of the quantum state storage + std::size_t storageSize{0}; // only one storage component (tensor) is needed + HANDLE_CUDM_ERROR(cudensitymatStateGetComponentStorageSize( + state.handle_, state.state_, + 1, // only one storage component + &storageSize)); // storage size in bytes + const std::size_t stateVolume = + storageSize / sizeof(std::complex); // quantum state tensor volume + // (number of elements) + assert(stateVolume == state.gpuDataSize_); + // std::cout << "Quantum state storage size (bytes) = " << storageSize + // << std::endl; + + // Attach initialized GPU storage to the input quantum state + HANDLE_CUDM_ERROR(cudensitymatStateAttachComponentStorage( + state.handle_, state.state_, + 1, // only one storage component (tensor) + std::vector({state.gpuData_}) + .data(), // pointer to the GPU storage for the quantum state + std::vector({storageSize}) + .data())); // size of the GPU storage for the quantum state + return state; +} + +cudm_state::cudm_state(cudensitymatHandle_t handle, + const std::vector> rawData, + const std::vector &hilbertSpaceDims) + : rawData_(rawData), gpuDataSize_(rawData.size()), state_(nullptr), + handle_(handle), hilbertSpaceDims_(hilbertSpaceDims) { + + if (rawData_.empty()) { + throw std::invalid_argument("Raw data cannot be empty."); + } + + // Allocate device memory + size_t dataSize = rawData_.size() * sizeof(std::complex); + HANDLE_CUDA_ERROR(cudaMalloc(reinterpret_cast(&gpuData_), dataSize)); + + // Copy data from host to device + HANDLE_CUDA_ERROR( + cudaMemcpy(gpuData_, rawData_.data(), dataSize, cudaMemcpyHostToDevice)); + + // Determine if this is a denisty matrix or state vector + 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(); +} + +cudm_state::cudm_state(cudm_state &&other) noexcept + : rawData_(std::move(other.rawData_)), gpuDataSize_(other.gpuDataSize_), + gpuData_(other.gpuData_), state_(other.state_), handle_(other.handle_), + hilbertSpaceDims_(std::move(other.hilbertSpaceDims_)) { + other.gpuData_ = nullptr; + other.state_ = nullptr; + other.gpuDataSize_ = 0; +} + +cudm_state &cudm_state::operator=(cudm_state &&other) noexcept { + if (this != &other) { + // Free existing resources + if (state_) { + cudensitymatDestroyState(state_); + } + if (gpuData_) { + cudaFree(gpuData_); + } + + // Move data from other + rawData_ = std::move(other.rawData_); + gpuData_ = other.gpuData_; + gpuDataSize_ = other.gpuDataSize_; + state_ = other.state_; + handle_ = other.handle_; + hilbertSpaceDims_ = std::move(other.hilbertSpaceDims_); + + // Nullify other + other.gpuData_ = nullptr; + other.state_ = nullptr; + other.gpuDataSize_ = 0; + } + + return *this; +} + +cudm_state::~cudm_state() { + if (state_) { + cudensitymatDestroyState(state_); + state_ = nullptr; + } + if (gpuData_) { + cudaFree(gpuData_); + gpuData_ = nullptr; + } +} + +bool cudm_state::is_initialized() const { return state_ != nullptr; } + +bool cudm_state::is_density_matrix() const { + if (!is_initialized()) { + return false; + } + + return rawData_.size() == calculate_density_matrix_size(hilbertSpaceDims_); +} + +std::vector> cudm_state::get_raw_data() const { + return rawData_; +} + +void *cudm_state::get_device_pointer() const { return gpuData_; } + +std::vector cudm_state::get_hilbert_space_dims() const { + return hilbertSpaceDims_; +} + +cudensitymatHandle_t cudm_state::get_handle() const { return handle_; } + +cudm_state cudm_state::operator+(const cudm_state &other) const { + if (rawData_.size() != other.rawData_.size()) { + throw std::invalid_argument("State size mismatch for addition."); + } + + cudm_state result = cudm_state(handle_, rawData_, hilbertSpaceDims_); + + double scalingFactor = 1.0; + double *gpuScalingFactor; + cudaMalloc(reinterpret_cast(&gpuScalingFactor), sizeof(double)); + cudaMemcpy(gpuScalingFactor, &scalingFactor, sizeof(double), + cudaMemcpyHostToDevice); + + HANDLE_CUDM_ERROR(cudensitymatStateComputeAccumulation( + handle_, other.get_impl(), result.get_impl(), gpuScalingFactor, 0)); + + cudaFree(gpuScalingFactor); + + return result; +} + +cudm_state &cudm_state::operator+=(const cudm_state &other) { + if (rawData_.size() != other.rawData_.size()) { + throw std::invalid_argument("State size mismatch for addition."); + } + + double scalingFactor = 1.0; + double *gpuScalingFactor; + cudaMalloc(reinterpret_cast(&gpuScalingFactor), sizeof(double)); + cudaMemcpy(gpuScalingFactor, &scalingFactor, sizeof(double), + cudaMemcpyHostToDevice); + + HANDLE_CUDM_ERROR(cudensitymatStateComputeAccumulation( + handle_, other.get_impl(), state_, gpuScalingFactor, 0)); + + cudaFree(gpuScalingFactor); + + return *this; +} +cudm_state &cudm_state::operator*=(const std::complex &scalar) { + void *gpuScalar; + HANDLE_CUDA_ERROR(cudaMalloc(&gpuScalar, sizeof(std::complex))); + HANDLE_CUDA_ERROR(cudaMemcpy(gpuScalar, &scalar, sizeof(std::complex), + cudaMemcpyHostToDevice)); + + HANDLE_CUDM_ERROR( + cudensitymatStateComputeScaling(handle_, state_, gpuScalar, 0)); + + HANDLE_CUDA_ERROR(cudaFree(gpuScalar)); + + return *this; +} + +cudm_state cudm_state::operator*(double scalar) const { + void *gpuScalar; + HANDLE_CUDA_ERROR(cudaMalloc(&gpuScalar, sizeof(std::complex))); + + std::complex complexScalar(scalar, 0.0); + HANDLE_CUDA_ERROR(cudaMemcpy(gpuScalar, &complexScalar, + sizeof(std::complex), + cudaMemcpyHostToDevice)); + + cudm_state result(handle_, rawData_, hilbertSpaceDims_); + + HANDLE_CUDM_ERROR( + cudensitymatStateComputeScaling(handle_, result.state_, gpuScalar, 0)); + + HANDLE_CUDA_ERROR(cudaFree(gpuScalar)); + + return result; +} + +std::string cudm_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(); +} + +void cudm_state::dumpDeviceData() const { + if (!is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + + std::vector> hostBuffer(gpuDataSize_); + HANDLE_CUDA_ERROR(cudaMemcpy(hostBuffer.data(), get_device_pointer(), + hostBuffer.size() * sizeof(std::complex), + cudaMemcpyDefault)); + std::cout << "State data: ["; + for (size_t i = 0; i < hostBuffer.size(); i++) { + std::cout << hostBuffer[i]; + if (i < hostBuffer.size() - 1) { + std::cout << ", "; + } + } + std::cout << "]\n"; +} + +cudm_state cudm_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]); + } + } + + return cudm_state(handle_, densityMatrix, hilbertSpaceDims_); +} + +cudensitymatState_t cudm_state::get_impl() const { + if (!is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + return state_; +} + +void cudm_state::attach_storage() { + if (!state_) { + throw std::runtime_error("State is not initialized."); + } + + if (rawData_.empty() || !gpuData_) { + throw std::runtime_error("Raw data is empty or device memory not " + "allocated. 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 device memory + size_t totalSize = std::accumulate(componentBufferSizes.begin(), + componentBufferSizes.end(), 0); + if (totalSize > rawData_.size() * sizeof(std::complex)) { + throw std::invalid_argument( + "Device memory size is insufficient to cover all components."); + } + + // Attach storage for using device memory (gpuData_) + std::vector componentBuffers(numStateComponents); + size_t offset = 0; + for (int32_t i = 0; i < numStateComponents; i++) { + componentBuffers[i] = static_cast(gpuData_ + offset); + offset += componentBufferSizes[i] / sizeof(std::complex); + } + + HANDLE_CUDM_ERROR(cudensitymatStateAttachComponentStorage( + handle_, state_, numStateComponents, componentBuffers.data(), + componentBufferSizes.data())); +} + +size_t cudm_state::calculate_state_vector_size( + const std::vector &hilbertSpaceDims) { + return std::accumulate(hilbertSpaceDims.begin(), hilbertSpaceDims.end(), 1, + std::multiplies<>()); +} + +size_t cudm_state::calculate_density_matrix_size( + const std::vector &hilbertSpaceDims) { + size_t vectorSize = calculate_state_vector_size(hilbertSpaceDims); + return vectorSize * vectorSize; +} + +// Initialize state based on InitialStateArgT +cudm_state cudm_state::create_initial_state( + cudensitymatHandle_t handle, const InitialStateArgT &initialStateArg, + const std::vector &hilbertSpaceDims, bool hasCollapseOps) { + size_t stateVectorSize = + std::accumulate(hilbertSpaceDims.begin(), hilbertSpaceDims.end(), + static_cast(1), std::multiplies<>{}); + + std::vector> rawData; + + if (std::holds_alternative(initialStateArg)) { + InitialState initialState = std::get(initialStateArg); + + if (initialState == InitialState::ZERO) { + rawData.resize(stateVectorSize, {0.0, 0.0}); + // |0> state + rawData[0] = {1.0, 0.0}; + } else if (initialState == InitialState::UNIFORM) { + rawData.resize(stateVectorSize, {1.0 / std::sqrt(stateVectorSize), 0.0}); + } else { + throw std::invalid_argument("Unsupported InitialState type."); + } + } else if (std::holds_alternative(initialStateArg)) { + void *runtimeState = std::get(initialStateArg); + if (!runtimeState) { + throw std::invalid_argument("Runtime state pointer is null."); + } + + try { + auto *externalData = + reinterpret_cast> *>(runtimeState); + + if (!externalData || externalData->empty()) { + throw std::invalid_argument( + "Runtime state contains invalid or empty data."); + } + + rawData = *externalData; + } catch (const std::exception &e) { + throw std::runtime_error("Failed to interpret runtime state: " + + std::string(e.what())); + } + } else { + throw std::invalid_argument("Unsupported InitialStateArgT type."); + } + + cudm_state state(handle, rawData, hilbertSpaceDims); + + // Convert to a density matrix if collapse operators are present. + if (hasCollapseOps && !state.is_density_matrix()) { + state = state.to_density_matrix(); + } + + return state; +} +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/cudm_time_stepper.cpp b/runtime/cudaq/dynamics/cudm_time_stepper.cpp new file mode 100644 index 0000000000..15be4af4f8 --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_time_stepper.cpp @@ -0,0 +1,98 @@ +/******************************************************************************* + * 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_time_stepper.h" +#include "cudaq/cudm_error_handling.h" +#include "cudaq/cudm_helpers.h" +#include + +namespace cudaq { +cudm_time_stepper::cudm_time_stepper(cudensitymatHandle_t handle, + cudensitymatOperator_t liouvillian) + : handle_(handle), liouvillian_(liouvillian) {} + +cudm_state cudm_time_stepper::compute(cudm_state &state, double t, + double step_size) { + if (step_size == 0.0) { + throw std::runtime_error("Step size cannot be zero."); + } + + if (!state.is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + + if (!handle_) { + throw std::runtime_error("cudm_time_stepper handle is not initialized."); + } + + if (!liouvillian_) { + throw std::runtime_error("Liouvillian is not initialized."); + } + + // Prepare workspace + cudensitymatWorkspaceDescriptor_t workspace; + HANDLE_CUDM_ERROR(cudensitymatCreateWorkspace(handle_, &workspace)); + + // Query free gpu memory and allocate workspace buffer + std::size_t freeMem = 0, totalMem = 0; + HANDLE_CUDA_ERROR(cudaMemGetInfo(&freeMem, &totalMem)); + // Take 80% of free memory + freeMem = static_cast(static_cast(freeMem) * 0.80); + + // Create a new state for the next step + cudm_state next_state = cudm_state::zero_like(state); + + if (!next_state.is_initialized()) { + throw std::runtime_error("Next state failed to initialize."); + } + + if (state.get_hilbert_space_dims() != next_state.get_hilbert_space_dims()) { + throw std::runtime_error( + "As the dimensions of both the old and the new state do no match, the " + "operator cannot act on the states."); + } + + // Prepare the operator for action + HANDLE_CUDM_ERROR(cudensitymatOperatorPrepareAction( + handle_, liouvillian_, state.get_impl(), next_state.get_impl(), + CUDENSITYMAT_COMPUTE_64F, freeMem, workspace, 0x0)); + + // Query required workspace buffer size + std::size_t requiredBufferSize = 0; + HANDLE_CUDM_ERROR(cudensitymatWorkspaceGetMemorySize( + handle_, workspace, CUDENSITYMAT_MEMSPACE_DEVICE, + CUDENSITYMAT_WORKSPACE_SCRATCH, &requiredBufferSize)); + + void *workspaceBuffer = nullptr; + if (requiredBufferSize > 0) { + // Allocate GPU storage for workspace buffer + const std::size_t bufferVolume = + requiredBufferSize / sizeof(std::complex); + workspaceBuffer = cudm_helper::create_array_gpu( + std::vector>(bufferVolume, {0.0, 0.0})); + + // Attach workspace buffer + HANDLE_CUDM_ERROR(cudensitymatWorkspaceSetMemory( + handle_, workspace, CUDENSITYMAT_MEMSPACE_DEVICE, + CUDENSITYMAT_WORKSPACE_SCRATCH, workspaceBuffer, requiredBufferSize)); + } + + // Apply the operator action + HANDLE_CUDA_ERROR(cudaDeviceSynchronize()); + HANDLE_CUDM_ERROR(cudensitymatOperatorComputeAction( + handle_, liouvillian_, t, 0, nullptr, state.get_impl(), + next_state.get_impl(), workspace, 0x0)); + HANDLE_CUDA_ERROR(cudaDeviceSynchronize()); + + // Cleanup + cudm_helper::destroy_array_gpu(workspaceBuffer); + HANDLE_CUDM_ERROR(cudensitymatDestroyWorkspace(workspace)); + + return next_state; +} +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/definition.cpp b/runtime/cudaq/dynamics/definition.cpp new file mode 100644 index 0000000000..61e83d0a52 --- /dev/null +++ b/runtime/cudaq/dynamics/definition.cpp @@ -0,0 +1,36 @@ +/******************************************************************************* + * 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(const std::string &operator_id, + std::vector expected_dimensions, + CallbackFunction &&create) + : id(operator_id), generator(std::move(create)), + m_expected_dimensions(std::move(expected_dimensions)) {} + +Definition::Definition(Definition &&def) + : id(def.id), generator(std::move(def.generator)), + m_expected_dimensions(std::move(def.m_expected_dimensions)) {} + +matrix_2 Definition::generate_matrix( + const std::vector &relevant_dimensions, + const std::map> ¶meters) const { + return generator(relevant_dimensions, parameters); +} + +Definition::~Definition() = default; +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/evolution.cpp b/runtime/cudaq/dynamics/evolution.cpp new file mode 100644 index 0000000000..7326d84a7f --- /dev/null +++ b/runtime/cudaq/dynamics/evolution.cpp @@ -0,0 +1,175 @@ +/****************************************************************-*- 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 "cudaq/runge_kutta_integrator.h" +#include + +#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; + +// } +evolve_result evolve_single( + const operator_sum &hamiltonian, + const std::map &dimensions, const Schedule &schedule, + const state &initial_state, + const std::vector *> + &collapse_operators, + const std::vector *> &observables, + bool store_intermediate_results, + std::shared_ptr> inputIntegrator, + std::optional shots_count) { + cudensitymatHandle_t handle; + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle)); + + cudm_helper helper(handle); + + std::vector dims; + for (const auto &[id, dim] : dimensions) + dims.emplace_back(dim); + auto liouvillian = helper.construct_liouvillian( + hamiltonian, collapse_operators, dims, {}, false); + std::cout << "Evolve Liouvillian: " << liouvillian << "\n"; + // Need to pass liouvillian here + auto time_stepper = std::make_shared(handle, liouvillian); + auto integrator = std::make_unique( + cudm_state(handle, initial_state, dims), 0.0, time_stepper, 1); + integrator->set_option("dt", 0.0001); + + std::vector expectations; + for (auto &obs : observables) + expectations.emplace_back(cudm_expectation( + handle, helper.convert_to_cudensitymat_operator( + {}, *obs, dims))); + + std::vector> expectationVals; + for (const auto &step : schedule) { + integrator->integrate(step); + auto [t, currentState] = integrator->get_state(); + if (store_intermediate_results) { + std::vector expVals; + for (auto &expectation : expectations) { + expectation.prepare(currentState.get_impl()); + const auto expVal = expectation.compute(currentState.get_impl(), step); + expVals.emplace_back(expVal.real()); + } + expectationVals.emplace_back(std::move(expVals)); + } + } + + if (store_intermediate_results) { + // TODO: need to convert to proper state + return evolve_result({initial_state}, expectationVals); + } else { + // Only final state is needed + auto [finalTime, finalState] = integrator->get_state(); + std::vector expVals; + for (auto &expectation : expectations) { + expectation.prepare(finalState.get_impl()); + const auto expVal = expectation.compute(finalState.get_impl(), finalTime); + expVals.emplace_back(expVal.real()); + } + // TODO: need to convert to proper state + return evolve_result(initial_state, expVals); + } +} + +} // 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..47301ad35f --- /dev/null +++ b/runtime/cudaq/dynamics/helpers.cpp @@ -0,0 +1,146 @@ +/******************************************************************************* + * 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 "cudaq/cudm_error_handling.h" +#include "cudaq/operators.h" +#include +#include +#include +#include +#include + +namespace cudaq { +namespace detail { + +// Aggregate parameters from multiple mappings. +std::map 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 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> +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}; +} + +/// Generates all possible states for the given dimensions ordered according +/// to the sequence of degrees (ordering is relevant if dimensions differ). +std::vector generate_all_states(std::vector degrees, + std::map dimensions) { + if (degrees.size() == 0) + return {}; + + std::vector states; + int range = dimensions[degrees[0]]; + for (auto state = 0; state < range; state++) { + states.push_back(std::to_string(state)); + } + + for (auto idx = 1; idx < degrees.size(); ++idx) { + std::vector result; + for (auto current : states) { + for (auto state = 0; state < dimensions[degrees[idx]]; state++) { + result.push_back(current + std::to_string(state)); + } + } + states = result; + } + + return states; +} + +cudaq::matrix_2 permute_matrix(cudaq::matrix_2 matrix, + std::vector permutation) { + auto result = cudaq::matrix_2(matrix.get_rows(), matrix.get_columns()); + std::vector> sorted_values; + for (std::size_t permuted : permutation) { + for (std::size_t permuted_again : permutation) { + sorted_values.push_back(matrix[{permuted, permuted_again}]); + } + } + int idx = 0; + for (std::size_t row = 0; row < result.get_rows(); row++) { + for (std::size_t col = 0; col < result.get_columns(); col++) { + result[{row, col}] = sorted_values[idx]; + idx++; + } + } + return result; +} + +std::vector canonicalize_degrees(std::vector degrees) { + std::sort(degrees.begin(), degrees.end(), std::greater()); + return degrees; +} +} // namespace detail +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/manipulation.cpp b/runtime/cudaq/dynamics/manipulation.cpp new file mode 100644 index 0000000000..e81c64ed84 --- /dev/null +++ b/runtime/cudaq/dynamics/manipulation.cpp @@ -0,0 +1,115 @@ +/******************************************************************************* + * 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 "cudaq/operators.h" + +namespace cudaq { + +std::vector +MatrixArithmetics::_compute_permutation(std::vector op_degrees, + std::vector canon_degrees) { + auto states = cudaq::detail::generate_all_states(canon_degrees, m_dimensions); + + std::vector reordering; + for (auto degree : op_degrees) { + auto it = std::find(canon_degrees.begin(), canon_degrees.end(), degree); + reordering.push_back(it - canon_degrees.begin()); + } + + std::vector op_states = + cudaq::detail::generate_all_states(op_degrees, m_dimensions); + + std::vector permutation; + for (auto state : states) { + std::string term; + for (auto i : reordering) { + term += state[i]; + } + auto it = std::find(op_states.begin(), op_states.end(), term); + permutation.push_back(it - op_states.begin()); + } + + return permutation; +} + +// Given a matrix representation that acts on the given degrees or freedom, +// sorts the degrees and permutes the matrix to match that canonical order. +// Returns: +// A tuple consisting of the permuted matrix as well as the sequence of +// degrees of freedom in canonical order. +std::tuple> +MatrixArithmetics::_canonicalize(matrix_2 &op_matrix, + std::vector op_degrees) { + auto canon_degrees = cudaq::detail::canonicalize_degrees(op_degrees); + if (op_degrees == canon_degrees) + return std::tuple>{op_matrix, canon_degrees}; + + auto permutation = this->_compute_permutation(op_degrees, canon_degrees); + auto result = cudaq::detail::permute_matrix(op_matrix, permutation); + return std::tuple>{result, canon_degrees}; +} + +EvaluatedMatrix MatrixArithmetics::tensor(EvaluatedMatrix op1, + EvaluatedMatrix op2) { + /// FIXME: do this check: + // assert len(frozenset(op1.degrees).intersection(op2.degrees)) == 0, \ + // "Operators should not have common degrees of freedom." + + auto op1_deg = std::move(op1.degrees()); + auto op2_deg = std::move(op2.degrees()); + std::vector op_degrees; + op_degrees.reserve(op1_deg.size() + op2_deg.size()); + for (auto d : op1_deg) + op_degrees.push_back(d); + for (auto d : op2_deg) + op_degrees.push_back(d); + auto op_matrix = cudaq::kronecker(op1.m_matrix, op2.m_matrix); + auto [new_matrix, new_degrees] = this->_canonicalize(op_matrix, op_degrees); + return EvaluatedMatrix(new_degrees, new_matrix); +} + +EvaluatedMatrix MatrixArithmetics::mul(EvaluatedMatrix op1, + EvaluatedMatrix op2) { + // Elementary operators have sorted degrees such that we have a unique + // convention for how to define the matrix. Tensor products permute the + // computed matrix if necessary to guarantee that all operators always have + // sorted degrees. + if (op1.m_degrees != op2.m_degrees) + throw std::runtime_error( + "Operators should have the same order of degrees."); + return EvaluatedMatrix(op1.m_degrees, (op1.m_matrix * op2.m_matrix)); +} + +EvaluatedMatrix MatrixArithmetics::add(EvaluatedMatrix op1, + EvaluatedMatrix op2) { + // Elementary operators have sorted degrees such that we have a unique + // convention for how to define the matrix. Tensor products permute the + // computed matrix if necessary to guarantee that all operators always have + // sorted degrees. + if (op1.m_degrees != op2.m_degrees) + throw std::runtime_error( + "Operators should have the same order of degrees."); + return EvaluatedMatrix(op1.m_degrees, (op1.m_matrix + op2.m_matrix)); +} + +EvaluatedMatrix +MatrixArithmetics::evaluate(std::variant> + op) { + // auto getDegrees = [](auto &&t) { return t.degrees; }; + // auto toMatrix = [&](auto &&t) { + // return t.to_matrix(this->m_dimensions, this->m_parameters); + // }; + // auto degrees = std::visit(getDegrees, op); + // auto matrix = std::visit(toMatrix, op); + // return EvaluatedMatrix(degrees, matrix); + throw std::runtime_error("implementation broken."); +} + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/matrix_operators.cpp b/runtime/cudaq/dynamics/matrix_operators.cpp new file mode 100644 index 0000000000..d0b460e1ea --- /dev/null +++ b/runtime/cudaq/dynamics/matrix_operators.cpp @@ -0,0 +1,236 @@ +/******************************************************************************* + * 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 +#include + +namespace cudaq { + +std::map matrix_operator::m_ops = {}; + +product_operator matrix_operator::identity(int degree) { + std::string op_id = "identity"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector dimensions, + std::map> _none) { + std::size_t dimension = dimensions[0]; + 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.0j; + } + return mat; + }; + matrix_operator::define(op_id, {-1}, std::move(func)); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +product_operator matrix_operator::zero(int degree) { + std::string op_id = "zero"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector 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()`. + std::size_t dimension = dimensions[0]; + auto mat = matrix_2(dimension, dimension); + return mat; + }; + matrix_operator::define(op_id, {-1}, func); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +product_operator matrix_operator::annihilate(int degree) { + std::string op_id = "annihilate"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector dimensions, + std::map> _none) { + std::size_t dimension = dimensions[0]; + 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'; + } + return mat; + }; + matrix_operator::define(op_id, {-1}, func); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +product_operator matrix_operator::create(int degree) { + std::string op_id = "create"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector dimensions, + std::map> _none) { + std::size_t dimension = dimensions[0]; + 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'; + } + return mat; + }; + matrix_operator::define(op_id, {-1}, func); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +product_operator matrix_operator::position(int degree) { + std::string op_id = "position"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector dimensions, + std::map> _none) { + std::size_t dimension = dimensions[0]; + 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'; + } + return mat; + }; + matrix_operator::define(op_id, {-1}, func); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +product_operator matrix_operator::momentum(int degree) { + std::string op_id = "momentum"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector dimensions, + std::map> _none) { + std::size_t dimension = dimensions[0]; + 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'; + } + return mat; + }; + matrix_operator::define(op_id, {-1}, func); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +product_operator matrix_operator::number(int degree) { + std::string op_id = "number"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector dimensions, + std::map> _none) { + std::size_t dimension = dimensions[0]; + auto mat = matrix_2(dimension, dimension); + for (std::size_t i = 0; i < dimension; i++) { + mat[{i, i}] = static_cast(i) + 0.0j; + } + return mat; + }; + matrix_operator::define(op_id, {-1}, func); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +product_operator matrix_operator::parity(int degree) { + std::string op_id = "parity"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector dimensions, + std::map> _none) { + std::size_t dimension = dimensions[0]; + 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; + } + return mat; + }; + matrix_operator::define(op_id, {-1}, func); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +product_operator matrix_operator::displace(int degree) { + std::string op_id = "displace"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector dimensions, + std::map> parameters) { + std::size_t dimension = dimensions[0]; + auto displacement_amplitude = parameters["displacement"]; + auto create = matrix_2(dimension, dimension); + auto annihilate = matrix_2(dimension, dimension); + for (std::size_t i = 0; i + 1 < dimension; i++) { + create[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + annihilate[{i, i + 1}] = + std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + auto term1 = displacement_amplitude * create; + auto term2 = std::conj(displacement_amplitude) * annihilate; + return (term1 - term2).exponential(); + }; + matrix_operator::define(op_id, {-1}, func); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +product_operator matrix_operator::squeeze(int degree) { + std::string op_id = "squeeze"; + if (matrix_operator::m_ops.find(op_id) == matrix_operator::m_ops.end()) { + auto func = [](std::vector dimensions, + std::map> parameters) { + std::size_t dimension = dimensions[0]; + auto squeezing = parameters["squeezing"]; + auto create = matrix_2(dimension, dimension); + auto annihilate = matrix_2(dimension, dimension); + for (std::size_t i = 0; i + 1 < dimension; i++) { + create[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + annihilate[{i, i + 1}] = + std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + auto term1 = std::conj(squeezing) * annihilate.power(2); + auto term2 = squeezing * create.power(2); + auto difference = 0.5 * (term1 - term2); + return difference.exponential(); + }; + matrix_operator::define(op_id, {-1}, func); + } + auto op = matrix_operator(op_id, {degree}); + return product_operator(1., op); +} + +matrix_2 matrix_operator::to_matrix( + std::map dimensions, + std::map> parameters) const { + auto it = matrix_operator::m_ops.find(this->id); + if (it != matrix_operator::m_ops.end()) { + std::vector relevant_dimensions; + relevant_dimensions.reserve(this->degrees.size()); + for (auto d : this->degrees) + relevant_dimensions.push_back(dimensions[d]); + return it->second.generate_matrix(relevant_dimensions, parameters); + } + throw std::range_error("unable to find operator"); +} + +} // 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..90727ae9cf --- /dev/null +++ b/runtime/cudaq/dynamics/operator_sum.cpp @@ -0,0 +1,857 @@ +/******************************************************************************* + * 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 "cudaq/operators.h" + +#include +#include +#include +#include + +namespace cudaq { + +// private methods + +template +cudaq::matrix_2 +operator_sum::m_evaluate(MatrixArithmetics arithmetics, + bool pad_terms) const { + + auto terms = this->get_terms(); + auto degrees = this->degrees(); + + // We need to make sure all matrices are of the same size to sum them up. + auto paddedTerm = [&](auto &&term) { + std::vector op_degrees; + for (auto op : term.get_terms()) { + for (auto degree : op.degrees) + op_degrees.push_back(degree); + } + for (auto degree : degrees) { + auto it = std::find(op_degrees.begin(), op_degrees.end(), degree); + if (it == op_degrees.end()) { + term *= matrix_operator::identity(degree); + } + } + return term; + }; + + auto sum = EvaluatedMatrix(); + if (pad_terms) { + auto padded_term = paddedTerm(terms[0]); + sum = EvaluatedMatrix(degrees, + padded_term.m_evaluate(arithmetics, pad_terms)); + for (auto term_idx = 1; term_idx < terms.size(); ++term_idx) { + padded_term = paddedTerm(terms[term_idx]); + sum = arithmetics.add( + sum, EvaluatedMatrix(degrees, + padded_term.m_evaluate(arithmetics, pad_terms))); + } + } else { + sum = EvaluatedMatrix(degrees, terms[0].m_evaluate(arithmetics, pad_terms)); + for (auto term_idx = 1; term_idx < terms.size(); ++term_idx) { + auto term = terms[term_idx]; + auto eval = term.m_evaluate(arithmetics, pad_terms); + sum = arithmetics.add(sum, EvaluatedMatrix(degrees, eval)); + } + } + return sum.matrix(); +} + +template +std::tuple, std::vector> +operator_sum::m_canonicalize_product( + product_operator &prod) const { + std::vector scalars = {prod.get_coefficient()}; + auto non_scalars = prod.get_terms(); + + std::vector all_degrees; + for (auto op : non_scalars) { + for (auto degree : op.degrees) + all_degrees.push_back(degree); + } + + std::set unique_degrees(all_degrees.begin(), all_degrees.end()); + + if (all_degrees.size() == unique_degrees.size()) { + // Each operator acts on different degrees of freedom; they + // hence commute and can be reordered arbitrarily. + /// FIXME: Doing nothing for now + // std::sort(non_scalars.begin(), non_scalars.end(), [](auto op){ return + // op.degrees; }) + } else { + // Some degrees exist multiple times; order the scalars, identities, + // and zeros, but do not otherwise try to reorder terms. + std::vector zero_ops; + std::vector identity_ops; + std::vector non_commuting; + for (auto op : non_scalars) { + if (op.id == "zero") + zero_ops.push_back(op); + if (op.id == "identity") + identity_ops.push_back(op); + if (op.id != "zero" || op.id != "identity") + non_commuting.push_back(op); + } + + /// FIXME: Not doing the same sorting we do in python yet + std::vector sorted_non_scalars; + sorted_non_scalars.insert(sorted_non_scalars.end(), zero_ops.begin(), + zero_ops.end()); + sorted_non_scalars.insert(sorted_non_scalars.end(), identity_ops.begin(), + identity_ops.end()); + sorted_non_scalars.insert(sorted_non_scalars.end(), non_commuting.begin(), + non_commuting.end()); + non_scalars = sorted_non_scalars; + } + return std::make_tuple(scalars, non_scalars); +} + +template +std::tuple, std::vector> +operator_sum::m_canonical_terms() const { + /// FIXME: Not doing the same sorting we do in python yet + std::tuple, std::vector> result; + std::vector scalars; + std::vector matrix_ops; + for (auto term : this->get_terms()) { + auto canon_term = m_canonicalize_product(term); + auto canon_scalars = std::get<0>(canon_term); + auto canon_elementary = std::get<1>(canon_term); + scalars.insert(scalars.end(), canon_scalars.begin(), canon_scalars.end()); + canon_elementary.insert(canon_elementary.end(), canon_elementary.begin(), + canon_elementary.end()); + } + return std::make_tuple(scalars, matrix_ops); +} + +template +void operator_sum::aggregate_terms() {} + +template +template +void operator_sum::aggregate_terms( + const product_operator &head, Args &&...args) { + this->terms.push_back(head.terms[0]); + this->coefficients.push_back(head.coefficients[0]); + aggregate_terms(std::forward(args)...); +} + +template cudaq::matrix_2 +operator_sum::m_evaluate(MatrixArithmetics arithmetics, + bool pad_terms) const; + +template std::tuple, std::vector> +operator_sum::m_canonicalize_product( + product_operator &prod) const; + +template std::tuple, std::vector> +operator_sum::m_canonical_terms() const; + +// no overload for a single product, since we don't want a constructor for a +// single term + +template void operator_sum::aggregate_terms( + const product_operator &item1, + const product_operator &item2); + +template void operator_sum::aggregate_terms( + const product_operator &item1, + const product_operator &item2, + const product_operator &item3); + +// read-only properties + +template +std::vector operator_sum::degrees() const { + std::set unsorted_degrees; + for (const std::vector &term : this->terms) { + for (const HandlerTy &op : term) + unsorted_degrees.insert(op.degrees.begin(), op.degrees.end()); + } + auto degrees = + std::vector(unsorted_degrees.begin(), unsorted_degrees.end()); + return cudaq::detail::canonicalize_degrees(degrees); +} + +template +int operator_sum::n_terms() const { + return this->terms.size(); +} + +template +std::vector> +operator_sum::get_terms() const { + std::vector> prods; + prods.reserve(this->terms.size()); + for (size_t i = 0; i < this->terms.size(); ++i) { + prods.push_back( + product_operator(this->coefficients[i], this->terms[i])); + } + return prods; +} + +template std::vector operator_sum::degrees() const; + +template int operator_sum::n_terms() const; + +template std::vector> +operator_sum::get_terms() const; + +// constructors + +template +template +operator_sum::operator_sum(const Args &...args) { + this->terms.reserve(sizeof...(Args)); + this->coefficients.reserve(sizeof...(Args)); + aggregate_terms(args...); +} + +template +operator_sum::operator_sum( + const std::vector> &terms) { + this->terms.reserve(terms.size()); + this->coefficients.reserve(terms.size()); + for (const product_operator &term : terms) { + this->terms.push_back(term.terms[0]); + this->coefficients.push_back(term.coefficients[0]); + } +} + +template +operator_sum::operator_sum( + std::vector> &&terms) { + this->terms.reserve(terms.size()); + for (const product_operator &term : terms) { + this->terms.push_back(std::move(term.terms[0])); + this->coefficients.push_back(std::move(term.coefficients[0])); + } +} + +template +operator_sum::operator_sum(const operator_sum &other) + : coefficients(other.coefficients), terms(other.terms) {} + +template +operator_sum::operator_sum(operator_sum &&other) + : coefficients(std::move(other.coefficients)), + terms(std::move(other.terms)) {} + +// no constructor for a single product, since that one should remain a product +// op + +template operator_sum::operator_sum( + const product_operator &item1, + const product_operator &item2); + +template operator_sum::operator_sum( + const product_operator &item1, + const product_operator &item2, + const product_operator &item3); + +template operator_sum::operator_sum( + const std::vector> &terms); + +template operator_sum::operator_sum( + std::vector> &&terms); + +template operator_sum::operator_sum( + const operator_sum &other); + +template operator_sum::operator_sum( + operator_sum &&other); + +// assignments + +template +operator_sum & +operator_sum::operator=(const operator_sum &other) { + if (this != &other) { + coefficients = other.coefficients; + terms = other.terms; + } + return *this; +} + +template +operator_sum & +operator_sum::operator=(operator_sum &&other) { + if (this != &other) { + coefficients = std::move(other.coefficients); + terms = std::move(other.terms); + } + return *this; +} + +template operator_sum & +operator_sum::operator=( + const operator_sum &other); + +template operator_sum & +operator_sum::operator=(operator_sum &&other); + +// evaluations + +template +std::string operator_sum::to_string() const { + throw std::runtime_error("not implemented"); +} + +template +matrix_2 operator_sum::to_matrix( + const std::map &dimensions, + const std::map> ¶meters) const { + return m_evaluate(MatrixArithmetics(dimensions, parameters)); +} + +template std::string operator_sum::to_string() const; + +template matrix_2 operator_sum::to_matrix( + const std::map &dimensions, + const std::map> ¶ms) const; + +// comparisons + +template +bool operator_sum::operator==( + const operator_sum &other) const { + throw std::runtime_error("not implemented"); +} + +template bool operator_sum::operator==( + const operator_sum &other) const; + +// unary operators + +template +operator_sum operator_sum::operator-() const { + std::vector coefficients; + coefficients.reserve(this->coefficients.size()); + for (auto &coeff : this->coefficients) + coefficients.push_back(-1. * coeff); + operator_sum sum; + sum.coefficients = std::move(coefficients); + sum.terms = this->terms; + return sum; +} + +template +operator_sum operator_sum::operator+() const { + return *this; +} + +template operator_sum +operator_sum::operator-() const; + +template operator_sum +operator_sum::operator+() const; + +// right-hand arithmetics + +#define SUM_MULTIPLICATION(otherTy) \ + template \ + operator_sum operator_sum::operator*(otherTy other) \ + const { \ + std::vector coefficients; \ + coefficients.reserve(this->coefficients.size()); \ + for (auto &coeff : this->coefficients) \ + coefficients.push_back(coeff *other); \ + operator_sum sum; \ + sum.coefficients = std::move(coefficients); \ + sum.terms = this->terms; \ + return sum; \ + } + +SUM_MULTIPLICATION(double); +SUM_MULTIPLICATION(std::complex); +SUM_MULTIPLICATION(const scalar_operator &); + +#define SUM_ADDITION(otherTy, op) \ + template \ + operator_sum operator_sum::operator op(otherTy other) \ + const { \ + std::vector coefficients; \ + coefficients.reserve(this->coefficients.size() + 1); \ + coefficients.push_back(op other); \ + for (auto &coeff : this->coefficients) \ + coefficients.push_back(coeff); \ + std::vector> terms; \ + terms.reserve(this->terms.size() + 1); \ + terms.push_back({}); \ + for (auto &term : this->terms) \ + terms.push_back(term); \ + operator_sum sum; \ + sum.coefficients = std::move(coefficients); \ + sum.terms = std::move(terms); \ + return sum; \ + } + +SUM_ADDITION(double, +); +SUM_ADDITION(double, -); +SUM_ADDITION(std::complex, +); +SUM_ADDITION(std::complex, -); +SUM_ADDITION(const scalar_operator &, +); +SUM_ADDITION(const scalar_operator &, -); + +template +operator_sum +operator_sum::operator*(const HandlerTy &other) const { + std::vector> terms; + terms.reserve(this->terms.size()); + for (auto &term : this->terms) { + std::vector prod; + prod.reserve(term.size() + 1); + for (auto &op : term) + prod.push_back(op); + prod.push_back(other); + terms.push_back(std::move(prod)); + } + operator_sum sum; + sum.coefficients = this->coefficients; + sum.terms = std::move(terms); + return sum; +} + +#define SUM_ADDITION_HANDLER(op) \ + template \ + operator_sum operator_sum::operator op( \ + const HandlerTy &other) const { \ + std::vector coefficients; \ + coefficients.reserve(this->coefficients.size() + 1); \ + coefficients.push_back(op 1.); \ + for (auto &coeff : this->coefficients) \ + coefficients.push_back(coeff); \ + std::vector> terms; \ + terms.reserve(this->terms.size() + 1); \ + std::vector newTerm; \ + newTerm.push_back(other); \ + terms.push_back(std::move(newTerm)); \ + for (auto &term : this->terms) \ + terms.push_back(term); \ + operator_sum sum; \ + sum.coefficients = std::move(coefficients); \ + sum.terms = std::move(terms); \ + return sum; \ + } + +SUM_ADDITION_HANDLER(+) +SUM_ADDITION_HANDLER(-) + +template operator_sum +operator_sum::operator*(double other) const; +template operator_sum +operator_sum::operator+(double other) const; +template operator_sum +operator_sum::operator-(double other) const; +template operator_sum +operator_sum::operator*(std::complex other) const; +template operator_sum +operator_sum::operator+(std::complex other) const; +template operator_sum +operator_sum::operator-(std::complex other) const; +template operator_sum +operator_sum::operator*(const scalar_operator &other) const; +template operator_sum +operator_sum::operator+(const scalar_operator &other) const; +template operator_sum +operator_sum::operator-(const scalar_operator &other) const; +template operator_sum +operator_sum::operator*(const matrix_operator &other) const; +template operator_sum +operator_sum::operator+(const matrix_operator &other) const; +template operator_sum +operator_sum::operator-(const matrix_operator &other) const; + +template +operator_sum operator_sum::operator*( + const product_operator &other) const { + std::vector coefficients; + coefficients.reserve(this->coefficients.size()); + for (auto &coeff : this->coefficients) + coefficients.push_back(other.coefficients[0] * coeff); + std::vector> terms; + terms.reserve(this->terms.size()); + for (auto &term : this->terms) { + std::vector prod; + prod.reserve(term.size() + other.terms[0].size()); + for (auto &op : term) + prod.push_back(op); + for (auto &op : other.terms[0]) + prod.push_back(op); + terms.push_back(std::move(prod)); + } + operator_sum sum; + sum.coefficients = std::move(coefficients); + sum.terms = std::move(terms); + return sum; +} + +#define SUM_ADDITION_PRODUCT(op) \ + template \ + operator_sum operator_sum::operator op( \ + const product_operator &other) const { \ + std::vector coefficients; \ + coefficients.reserve(this->coefficients.size() + 1); \ + for (auto &coeff : this->coefficients) \ + coefficients.push_back(coeff); \ + coefficients.push_back(op other.coefficients[0]); \ + std::vector> terms; \ + terms.reserve(this->terms.size() + 1); \ + for (auto &term : this->terms) \ + terms.push_back(term); \ + terms.push_back(other.terms[0]); \ + operator_sum sum; \ + sum.coefficients = std::move(coefficients); \ + sum.terms = std::move(terms); \ + return sum; \ + } + +SUM_ADDITION_PRODUCT(+) +SUM_ADDITION_PRODUCT(-) + +template +operator_sum +operator_sum::operator*(const operator_sum &other) const { + std::vector coefficients; + coefficients.reserve(this->coefficients.size() * other.coefficients.size()); + for (auto &coeff1 : this->coefficients) { + for (auto &coeff2 : other.coefficients) + coefficients.push_back(coeff1 * coeff2); + } + std::vector> terms; + terms.reserve(this->terms.size() * other.terms.size()); + for (auto &term1 : this->terms) { + for (auto &term2 : other.terms) { + std::vector prod; + prod.reserve(term1.size() + term2.size()); + for (auto &op : term1) + prod.push_back(op); + for (auto &op : term2) + prod.push_back(op); + terms.push_back(std::move(prod)); + } + } + operator_sum sum; + sum.coefficients = std::move(coefficients); + sum.terms = std::move(terms); + return sum; +} + +#define SUM_ADDITION_SUM(op) \ + template \ + operator_sum operator_sum::operator op( \ + const operator_sum &other) const { \ + std::vector coefficients; \ + coefficients.reserve(this->coefficients.size() + \ + other.coefficients.size()); \ + for (auto &coeff : this->coefficients) \ + coefficients.push_back(coeff); \ + for (auto &coeff : other.coefficients) \ + coefficients.push_back(op coeff); \ + std::vector> terms; \ + terms.reserve(this->terms.size() + other.terms.size()); \ + for (auto &term : this->terms) \ + terms.push_back(term); \ + for (auto &term : other.terms) \ + terms.push_back(term); \ + operator_sum sum; \ + sum.coefficients = std::move(coefficients); \ + sum.terms = std::move(terms); \ + return sum; \ + } + +SUM_ADDITION_SUM(+); +SUM_ADDITION_SUM(-); + +template operator_sum operator_sum::operator*( + const product_operator &other) const; +template operator_sum operator_sum::operator+( + const product_operator &other) const; +template operator_sum operator_sum::operator-( + const product_operator &other) const; +template operator_sum operator_sum::operator*( + const operator_sum &other) const; +template operator_sum operator_sum::operator+( + const operator_sum &other) const; +template operator_sum operator_sum::operator-( + const operator_sum &other) const; + +#define SUM_MULTIPLICATION_ASSIGNMENT(otherTy) \ + template \ + operator_sum &operator_sum::operator*=( \ + otherTy other) { \ + for (auto &coeff : this->coefficients) \ + coeff *= other; \ + return *this; \ + } + +SUM_MULTIPLICATION_ASSIGNMENT(double); +SUM_MULTIPLICATION_ASSIGNMENT(std::complex); +SUM_MULTIPLICATION_ASSIGNMENT(const scalar_operator &); + +#define SUM_ADDITION_ASSIGNMENT(otherTy, op) \ + template \ + operator_sum &operator_sum::operator op##=( \ + otherTy other) { \ + this->coefficients.push_back(op other); \ + this->terms.push_back({}); \ + return *this; \ + } + +SUM_ADDITION_ASSIGNMENT(double, +); +SUM_ADDITION_ASSIGNMENT(double, -); +SUM_ADDITION_ASSIGNMENT(std::complex, +); +SUM_ADDITION_ASSIGNMENT(std::complex, -); +SUM_ADDITION_ASSIGNMENT(const scalar_operator &, +); +SUM_ADDITION_ASSIGNMENT(const scalar_operator &, -); + +template +operator_sum & +operator_sum::operator*=(const HandlerTy &other) { + for (auto &term : this->terms) + term.push_back(other); + operator_sum sum; + return *this; +} + +#define SUM_ADDITION_HANDLER_ASSIGNMENT(op) \ + template \ + operator_sum &operator_sum::operator op##=( \ + const HandlerTy &other) { \ + coefficients.push_back(op 1.); \ + std::vector newTerm; \ + newTerm.push_back(other); \ + this->terms.push_back(std::move(newTerm)); \ + return *this; \ + } + +SUM_ADDITION_HANDLER_ASSIGNMENT(+) +SUM_ADDITION_HANDLER_ASSIGNMENT(-) + +template +operator_sum & +operator_sum::operator*=(const product_operator &other) { + for (auto &coeff : this->coefficients) + coeff *= other.coefficients[0]; + for (auto &term : this->terms) { + term.reserve(term.size() + other.terms[0].size()); + for (auto &op : other.terms[0]) + term.push_back(op); + } + return *this; +} + +#define SUM_ADDITION_PRODUCT_ASSIGNMENT(op) \ + template \ + operator_sum &operator_sum::operator op##=( \ + const product_operator &other) { \ + this->coefficients.push_back(op other.coefficients[0]); \ + this->terms.push_back(other.terms[0]); \ + return *this; \ + } + +SUM_ADDITION_PRODUCT_ASSIGNMENT(+) +SUM_ADDITION_PRODUCT_ASSIGNMENT(-) + +template +operator_sum & +operator_sum::operator*=(const operator_sum &other) { + this->coefficients.reserve(this->coefficients.size() * + other.coefficients.size()); + *this = *this * other; // we need to update all coefficients and terms anyway + return *this; +} + +#define SUM_ADDITION_SUM_ASSIGNMENT(op) \ + template \ + operator_sum &operator_sum::operator op##=( \ + const operator_sum &other) { \ + this->coefficients.reserve(this->coefficients.size() + \ + other.coefficients.size()); \ + for (auto &coeff : other.coefficients) \ + this->coefficients.push_back(op coeff); \ + this->terms.reserve(this->terms.size() + other.terms.size()); \ + for (auto &term : other.terms) \ + this->terms.push_back(term); \ + return *this; \ + } + +SUM_ADDITION_SUM_ASSIGNMENT(+); +SUM_ADDITION_SUM_ASSIGNMENT(-); + +template operator_sum & +operator_sum::operator*=(double other); +template operator_sum & +operator_sum::operator+=(double other); +template operator_sum & +operator_sum::operator-=(double other); +template operator_sum & +operator_sum::operator*=(std::complex other); +template operator_sum & +operator_sum::operator+=(std::complex other); +template operator_sum & +operator_sum::operator-=(std::complex other); +template operator_sum & +operator_sum::operator*=(const scalar_operator &other); +template operator_sum & +operator_sum::operator+=(const scalar_operator &other); +template operator_sum & +operator_sum::operator-=(const scalar_operator &other); +template operator_sum & +operator_sum::operator*=(const matrix_operator &other); +template operator_sum & +operator_sum::operator+=(const matrix_operator &other); +template operator_sum & +operator_sum::operator-=(const matrix_operator &other); +template operator_sum & +operator_sum::operator*=( + const product_operator &other); +template operator_sum & +operator_sum::operator+=( + const product_operator &other); +template operator_sum & +operator_sum::operator-=( + const product_operator &other); +template operator_sum & +operator_sum::operator*=( + const operator_sum &other); +template operator_sum & +operator_sum::operator-=( + const operator_sum &other); +template operator_sum & +operator_sum::operator+=( + const operator_sum &other); + +// left-hand arithmetics + +#define SUM_MULTIPLICATION_REVERSE(otherTy) \ + template \ + operator_sum operator*(otherTy other, \ + const operator_sum &self) { \ + std::vector coefficients; \ + coefficients.reserve(self.coefficients.size()); \ + for (auto &coeff : self.coefficients) \ + coefficients.push_back(coeff *other); \ + operator_sum sum; \ + sum.coefficients = std::move(coefficients); \ + sum.terms = self.terms; \ + return sum; \ + } + +SUM_MULTIPLICATION_REVERSE(double); +SUM_MULTIPLICATION_REVERSE(std::complex); +SUM_MULTIPLICATION_REVERSE(const scalar_operator &); + +#define SUM_ADDITION_REVERSE(otherTy, op) \ + template \ + operator_sum operator op(otherTy other, \ + const operator_sum &self) { \ + std::vector coefficients; \ + coefficients.reserve(self.terms.size() + 1); \ + coefficients.push_back(other); \ + for (auto &coeff : self.coefficients) \ + coefficients.push_back(op coeff); \ + std::vector> terms; \ + terms.reserve(self.terms.size() + 1); \ + terms.push_back({}); \ + for (auto &term : self.terms) \ + terms.push_back(term); \ + operator_sum sum; \ + sum.coefficients = std::move(coefficients); \ + sum.terms = std::move(terms); \ + return sum; \ + } + +SUM_ADDITION_REVERSE(double, +); +SUM_ADDITION_REVERSE(double, -); +SUM_ADDITION_REVERSE(std::complex, +); +SUM_ADDITION_REVERSE(std::complex, -); +SUM_ADDITION_REVERSE(const scalar_operator &, +); +SUM_ADDITION_REVERSE(const scalar_operator &, -); + +template +operator_sum operator*(const HandlerTy &other, + const operator_sum &self) { + std::vector> terms; + terms.reserve(self.terms.size()); + for (auto &term : self.terms) { + std::vector prod; + prod.reserve(term.size() + 1); + prod.push_back(other); + for (auto &op : term) + prod.push_back(op); + terms.push_back(std::move(prod)); + } + operator_sum sum; + sum.coefficients = self.coefficients; + sum.terms = std::move(terms); + return sum; +} + +#define SUM_ADDITION_HANDLER_REVERSE(op) \ + template \ + operator_sum operator op(const HandlerTy &other, \ + const operator_sum &self) { \ + std::vector coefficients; \ + coefficients.reserve(self.terms.size() + 1); \ + coefficients.push_back(1.); \ + for (auto &coeff : self.coefficients) \ + coefficients.push_back(op coeff); \ + std::vector> terms; \ + terms.reserve(self.terms.size() + 1); \ + std::vector newTerm; \ + newTerm.push_back(other); \ + terms.push_back(std::move(newTerm)); \ + for (auto &term : self.terms) \ + terms.push_back(term); \ + operator_sum sum; \ + sum.coefficients = std::move(coefficients); \ + sum.terms = std::move(terms); \ + return sum; \ + } + +SUM_ADDITION_HANDLER_REVERSE(+) +SUM_ADDITION_HANDLER_REVERSE(-) + +template operator_sum +operator*(const scalar_operator &other, + const operator_sum &self); +template operator_sum +operator*(std::complex other, + const operator_sum &self); +template operator_sum +operator*(double other, const operator_sum &self); +template operator_sum +operator*(const matrix_operator &other, + const operator_sum &self); +template operator_sum +operator+(const scalar_operator &other, + const operator_sum &self); +template operator_sum +operator+(double other, const operator_sum &self); +template operator_sum +operator+(std::complex other, + const operator_sum &self); +template operator_sum +operator+(const matrix_operator &other, + const operator_sum &self); +template operator_sum +operator-(const scalar_operator &other, + const operator_sum &self); +template operator_sum +operator-(double other, const operator_sum &self); +template operator_sum +operator-(std::complex other, + const operator_sum &self); +template operator_sum +operator-(const matrix_operator &other, + const operator_sum &self); + +} // 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..0c50f14e95 --- /dev/null +++ b/runtime/cudaq/dynamics/product_operators.cpp @@ -0,0 +1,601 @@ +/******************************************************************************* + * 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 "cudaq/operators.h" + +#include +#include +#include +#include + +namespace cudaq { + +// private methods + +EvaluatedMatrix +_padded_op(MatrixArithmetics &arithmetics, const cudaq::matrix_operator &op, + std::vector degrees, std::map dimensions, + std::map> parameters) { + /// Creating the tensor product with op being last is most efficient. + std::vector padded; + for (const auto °ree : degrees) { + if (std::find(op.degrees.begin(), op.degrees.end(), degree) == + op.degrees.end()) { + // FIXME: EITHER MAKE DIMENSIONS REQUIRED, OR GIVE AN ERROR IF DIMENSIONS + // ARE REQUIRED. + padded.push_back(EvaluatedMatrix( + {degree}, matrix_operator::identity(degree).to_matrix(dimensions))); + // FIXME: avoid creation of a product here - + // but we need to make sure identity is defined before using it (all ops + // are lazily defined...) + // padded.push_back(cudaq::matrix_operator("identity", + // {degree}).to_matrix()); + } + } + matrix_2 mat = op.to_matrix(dimensions, parameters); + auto res = EvaluatedMatrix(op.degrees, mat); // FIXME: PUT THIS LAST + for (auto &op : padded) + res = arithmetics.tensor(res, op); + return res; +} + +// FIXME: EVALUATE IS NOT SUPPOSED TO RETURN A MATRIX - +// IT SUPPOSED TO TAKE A TRANSFORMATION (ANY OPERATOR ARITHMETICS) AND APPLY IT +template +cudaq::matrix_2 +product_operator::m_evaluate(MatrixArithmetics arithmetics, + bool pad_terms) const { + const std::vector &terms = this->terms[0]; + auto degrees = this->degrees(); + cudaq::matrix_2 result; + + if (terms.size() > 0) { + if (pad_terms) { + auto evaluated = + _padded_op(arithmetics, terms[0], degrees, arithmetics.m_dimensions, + arithmetics.m_parameters); + for (auto op_idx = 1; op_idx < terms.size(); ++op_idx) { + const HandlerTy &op = terms[op_idx]; + if (op.degrees.size() != 1 || + op != cudaq::matrix_operator("identity", op.degrees)) { + auto padded = + _padded_op(arithmetics, op, degrees, arithmetics.m_dimensions, + arithmetics.m_parameters); + evaluated = arithmetics.mul(evaluated, padded); + } + } + result = evaluated.matrix(); + } else { + auto evaluated = arithmetics.evaluate(terms[0]); + for (auto op_idx = 1; op_idx < terms.size(); ++op_idx) { + auto &op = terms[op_idx]; + auto mat = + op.to_matrix(arithmetics.m_dimensions, arithmetics.m_parameters); + evaluated = + arithmetics.mul(evaluated, EvaluatedMatrix(op.degrees, mat)); + } + result = evaluated.matrix(); + } + } else { + auto full_hilbert_size = 1; + for (const auto degree : degrees) + full_hilbert_size *= arithmetics.m_dimensions[degree]; + result = cudaq::matrix_2::identity(full_hilbert_size); + } + return this->coefficients[0].evaluate(arithmetics.m_parameters) * result; +} + +template +void product_operator::aggregate_terms() {} + +template +template +void product_operator::aggregate_terms(const HandlerTy &head, + Args &&...args) { + this->terms[0].push_back(head); + aggregate_terms(std::forward(args)...); +} + +template void product_operator::aggregate_terms( + const matrix_operator &item1, const matrix_operator &item2); + +template void product_operator::aggregate_terms( + const matrix_operator &item1, const matrix_operator &item2, + const matrix_operator &item3); + +// read-only properties + +template +std::vector product_operator::degrees() const { + std::set unsorted_degrees; + for (const HandlerTy &term : this->terms[0]) { + unsorted_degrees.insert(term.degrees.begin(), term.degrees.end()); + } + auto degrees = + std::vector(unsorted_degrees.begin(), unsorted_degrees.end()); + return cudaq::detail::canonicalize_degrees(degrees); +} + +template +int product_operator::n_terms() const { + return this->terms[0].size(); +} + +template +std::vector product_operator::get_terms() const { + return this->terms[0]; +} + +template +scalar_operator product_operator::get_coefficient() const { + return this->coefficients[0]; +} + +template cudaq::matrix_2 +product_operator::m_evaluate(MatrixArithmetics arithmetics, + bool pad_terms) const; + +template std::vector product_operator::degrees() const; + +template int product_operator::n_terms() const; + +template std::vector +product_operator::get_terms() const; + +template scalar_operator +product_operator::get_coefficient() const; + +// constructors + +template +template +product_operator::product_operator(scalar_operator coefficient, + const Args &...args) { + this->coefficients.push_back(std::move(coefficient)); + std::vector ops = {}; + ops.reserve(sizeof...(Args)); + this->terms.push_back(ops); + aggregate_terms(args...); +} + +template +product_operator::product_operator( + scalar_operator coefficient, + const std::vector &atomic_operators) { + this->terms.push_back(atomic_operators); + this->coefficients.push_back(std::move(coefficient)); +} + +template +product_operator::product_operator( + scalar_operator coefficient, std::vector &&atomic_operators) { + this->terms.push_back(std::move(atomic_operators)); + this->coefficients.push_back(std::move(coefficient)); +} + +template +product_operator::product_operator( + const product_operator &other) { + this->terms = other.terms; + this->coefficients = other.coefficients; +} + +template +product_operator::product_operator( + product_operator &&other) { + this->terms = std::move(other.terms); + this->coefficients = std::move(other.coefficients); +} + +template product_operator::product_operator( + scalar_operator coefficient); + +template product_operator::product_operator( + scalar_operator coefficient, const matrix_operator &item1); + +template product_operator::product_operator( + scalar_operator coefficient, const matrix_operator &item1, + const matrix_operator &item2); + +template product_operator::product_operator( + scalar_operator coefficient, const matrix_operator &item1, + const matrix_operator &item2, const matrix_operator &item3); + +template product_operator::product_operator( + scalar_operator coefficient, + const std::vector &atomic_operators); + +template product_operator::product_operator( + scalar_operator coefficient, + std::vector &&atomic_operators); + +template product_operator::product_operator( + const product_operator &other); + +template product_operator::product_operator( + product_operator &&other); + +// assignments + +template +product_operator &product_operator::operator=( + const product_operator &other) { + if (this != &other) { + this->terms = other.terms; + this->coefficients = other.coefficients; + } + return *this; +} + +template +product_operator & +product_operator::operator=(product_operator &&other) { + if (this != &other) { + this->coefficients = std::move(other.coefficients); + this->terms = std::move(other.terms); + } + return *this; +} + +template product_operator & +product_operator::operator=( + const product_operator &other); + +template product_operator & +product_operator::operator=( + product_operator &&other); + +// evaluations + +template +std::string product_operator::to_string() const { + throw std::runtime_error("not implemented"); +} + +template +matrix_2 product_operator::to_matrix( + std::map dimensions, + std::map> parameters) const { + return this->m_evaluate(MatrixArithmetics(dimensions, parameters)); +} + +template std::string product_operator::to_string() const; + +template matrix_2 product_operator::to_matrix( + std::map dimensions, + std::map> parameters) const; + +// comparisons + +template +bool product_operator::operator==( + const product_operator &other) const { + throw std::runtime_error("not implemented"); +} + +template bool product_operator::operator==( + const product_operator &other) const; + +// unary operators + +template +product_operator product_operator::operator-() const { + return product_operator(-1. * this->coefficients[0], + this->terms[0]); +} + +template +product_operator product_operator::operator+() const { + return *this; +} + +template product_operator +product_operator::operator-() const; + +template product_operator +product_operator::operator+() const; + +// right-hand arithmetics + +#define PRODUCT_MULTIPLICATION(otherTy) \ + template \ + product_operator product_operator::operator*( \ + otherTy other) const { \ + return product_operator(other * this->coefficients[0], \ + this->terms[0]); \ + } + +PRODUCT_MULTIPLICATION(double); +PRODUCT_MULTIPLICATION(std::complex); +PRODUCT_MULTIPLICATION(const scalar_operator &); + +#define PRODUCT_ADDITION(otherTy, op) \ + template \ + operator_sum product_operator::operator op( \ + otherTy other) const { \ + return operator_sum(product_operator(op other), \ + *this); \ + } + +PRODUCT_ADDITION(double, +); +PRODUCT_ADDITION(double, -); +PRODUCT_ADDITION(std::complex, +); +PRODUCT_ADDITION(std::complex, -); +PRODUCT_ADDITION(const scalar_operator &, +); +PRODUCT_ADDITION(const scalar_operator &, -); + +template +product_operator +product_operator::operator*(const HandlerTy &other) const { + std::vector terms; + terms.reserve(this->terms[0].size() + 1); + for (auto &term : this->terms[0]) + terms.push_back(term); + terms.push_back(other); + return product_operator(this->coefficients[0], std::move(terms)); +} + +#define PRODUCT_ADDITION_HANDLER(op) \ + template \ + operator_sum product_operator::operator op( \ + const HandlerTy &other) const { \ + return operator_sum(product_operator(op 1., other), \ + *this); \ + } + +PRODUCT_ADDITION_HANDLER(+) +PRODUCT_ADDITION_HANDLER(-) + +template product_operator +product_operator::operator*(double other) const; +template operator_sum +product_operator::operator+(double other) const; +template operator_sum +product_operator::operator-(double other) const; +template product_operator +product_operator::operator*(std::complex other) const; +template operator_sum +product_operator::operator+(std::complex other) const; +template operator_sum +product_operator::operator-(std::complex other) const; +template product_operator +product_operator::operator*( + const scalar_operator &other) const; +template operator_sum +product_operator::operator+( + const scalar_operator &other) const; +template operator_sum +product_operator::operator-( + const scalar_operator &other) const; +template product_operator +product_operator::operator*( + const matrix_operator &other) const; +template operator_sum +product_operator::operator+( + const matrix_operator &other) const; +template operator_sum +product_operator::operator-( + const matrix_operator &other) const; + +template +product_operator product_operator::operator*( + const product_operator &other) const { + std::vector terms; + terms.reserve(this->terms[0].size() + other.terms[0].size()); + for (auto &term : this->terms[0]) + terms.push_back(term); + for (auto &term : other.terms[0]) + terms.push_back(term); + return product_operator(this->coefficients[0] * other.coefficients[0], + std::move(terms)); +} + +#define PRODUCT_ADDITION_PRODUCT(op) \ + template \ + operator_sum product_operator::operator op( \ + const product_operator &other) const { \ + return operator_sum(op other, *this); \ + } + +PRODUCT_ADDITION_PRODUCT(+) +PRODUCT_ADDITION_PRODUCT(-) + +template +operator_sum product_operator::operator*( + const operator_sum &other) const { + std::vector coefficients; + coefficients.reserve(other.coefficients.size()); + for (auto &coeff : other.coefficients) + coefficients.push_back(this->coefficients[0] * coeff); + std::vector> terms; + terms.reserve(other.terms.size()); + for (auto &term : other.terms) { + std::vector prod; + prod.reserve(this->terms[0].size() + term.size()); + for (auto &op : this->terms[0]) + prod.push_back(op); + for (auto &op : term) + prod.push_back(op); + terms.push_back(std::move(prod)); + } + operator_sum sum; + sum.coefficients = std::move(coefficients); + sum.terms = std::move(terms); + return sum; +} + +#define PRODUCT_ADDITION_SUM(op) \ + template \ + operator_sum product_operator::operator op( \ + const operator_sum &other) const { \ + std::vector coefficients; \ + coefficients.reserve(other.coefficients.size() + 1); \ + coefficients.push_back(this->coefficients[0]); \ + for (auto &coeff : other.coefficients) \ + coefficients.push_back(op coeff); \ + std::vector> terms; \ + terms.reserve(other.terms.size() + 1); \ + terms.push_back(this->terms[0]); \ + for (auto &term : other.terms) \ + terms.push_back(term); \ + operator_sum sum; \ + sum.coefficients = std::move(coefficients); \ + sum.terms = std::move(terms); \ + return sum; \ + } + +PRODUCT_ADDITION_SUM(+) +PRODUCT_ADDITION_SUM(-) + +template product_operator +product_operator::operator*( + const product_operator &other) const; +template operator_sum +product_operator::operator+( + const product_operator &other) const; +template operator_sum +product_operator::operator-( + const product_operator &other) const; +template operator_sum +product_operator::operator*( + const operator_sum &other) const; +template operator_sum +product_operator::operator+( + const operator_sum &other) const; +template operator_sum +product_operator::operator-( + const operator_sum &other) const; + +#define PRODUCT_MULTIPLICATION_ASSIGNMENT(otherTy) \ + template \ + product_operator &product_operator::operator*=( \ + otherTy other) { \ + this->coefficients[0] *= other; \ + return *this; \ + } + +PRODUCT_MULTIPLICATION_ASSIGNMENT(double); +PRODUCT_MULTIPLICATION_ASSIGNMENT(std::complex); +PRODUCT_MULTIPLICATION_ASSIGNMENT(const scalar_operator &); + +template +product_operator & +product_operator::operator*=(const HandlerTy &other) { + this->terms[0].push_back(other); + return *this; +} + +template +product_operator &product_operator::operator*=( + const product_operator &other) { + this->coefficients[0] *= other.coefficients[0]; + this->terms[0].reserve(this->terms[0].size() + other.terms[0].size()); + this->terms[0].insert(this->terms[0].end(), other.terms[0].begin(), + other.terms[0].end()); + return *this; +} + +template product_operator & +product_operator::operator*=(double other); +template product_operator & +product_operator::operator*=(std::complex other); +template product_operator & +product_operator::operator*=(const scalar_operator &other); +template product_operator & +product_operator::operator*=(const matrix_operator &other); +template product_operator & +product_operator::operator*=( + const product_operator &other); + +// left-hand arithmetics + +#define PRODUCT_MULTIPLICATION_REVERSE(otherTy) \ + template \ + product_operator operator*( \ + otherTy other, const product_operator &self) { \ + return product_operator(other * self.coefficients[0], \ + self.terms[0]); \ + } + +PRODUCT_MULTIPLICATION_REVERSE(double); +PRODUCT_MULTIPLICATION_REVERSE(std::complex); +PRODUCT_MULTIPLICATION_REVERSE(const scalar_operator &); + +#define PRODUCT_ADDITION_REVERSE(otherTy, op) \ + template \ + operator_sum operator op( \ + otherTy other, const product_operator &self) { \ + return operator_sum(product_operator(other), \ + op self); \ + } + +PRODUCT_ADDITION_REVERSE(double, +); +PRODUCT_ADDITION_REVERSE(double, -); +PRODUCT_ADDITION_REVERSE(std::complex, +); +PRODUCT_ADDITION_REVERSE(std::complex, -); +PRODUCT_ADDITION_REVERSE(const scalar_operator &, +); +PRODUCT_ADDITION_REVERSE(const scalar_operator &, -); + +template +product_operator operator*(const HandlerTy &other, + const product_operator &self) { + std::vector terms; + terms.reserve(self.terms[0].size() + 1); + terms.push_back(other); + for (auto &term : self.terms[0]) + terms.push_back(term); + return product_operator(self.coefficients[0], std::move(terms)); +} + +#define PRODUCT_ADDITION_HANDLER_REVERSE(op) \ + template \ + operator_sum operator op( \ + const HandlerTy &other, const product_operator &self) { \ + return operator_sum(product_operator(1., other), \ + op self); \ + } + +PRODUCT_ADDITION_HANDLER_REVERSE(+) +PRODUCT_ADDITION_HANDLER_REVERSE(-) + +template product_operator +operator*(double other, const product_operator &self); +template product_operator +operator*(std::complex other, + const product_operator &self); +template product_operator +operator*(const scalar_operator &other, + const product_operator &self); +template product_operator +operator*(const matrix_operator &other, + const product_operator &self); +template operator_sum +operator+(double other, const product_operator &self); +template operator_sum +operator+(std::complex other, + const product_operator &self); +template operator_sum +operator+(const scalar_operator &other, + const product_operator &self); +template operator_sum +operator+(const matrix_operator &other, + const product_operator &self); +template operator_sum +operator-(double other, const product_operator &self); +template operator_sum +operator-(std::complex other, + const product_operator &self); +template operator_sum +operator-(const scalar_operator &other, + const product_operator &self); +template operator_sum +operator-(const matrix_operator &other, + const product_operator &self); + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/runge_kutta_integrator.cpp b/runtime/cudaq/dynamics/runge_kutta_integrator.cpp new file mode 100644 index 0000000000..4217edb48b --- /dev/null +++ b/runtime/cudaq/dynamics/runge_kutta_integrator.cpp @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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 + +using namespace cudaq; + +namespace cudaq { +void runge_kutta_integrator::integrate(double target_time) { + if (!this->stepper) { + throw std::runtime_error("Time stepper is not initialized."); + } + + if (this->integrator_options.find("dt") == this->integrator_options.end()) { + throw std::invalid_argument( + "Time step size (dt) is missing from integrator options."); + } + + double dt = this->integrator_options["dt"]; + if (dt <= 0) { + throw std::invalid_argument("Invalid time step size for integration."); + } + + while (this->t < target_time) { + double step_size = std::min(dt, target_time - this->t); + + // std::cout << "Runge-Kutta step at time " << this->t + // << " with step size: " << step_size << std::endl; + + if (this->substeps_ == 1) { + // Euler method (1st order) + cudm_state k1 = this->stepper->compute(this->state, this->t, step_size); + k1 *= step_size; + this->state += k1; + } else if (this->substeps_ == 2) { + // Midpoint method (2nd order) + cudm_state k1 = this->stepper->compute(this->state, this->t, step_size); + k1 *= (step_size / 2.0); + + this->state += k1; + + cudm_state k2 = this->stepper->compute( + this->state, this->t + step_size / 2.0, step_size); + k2 *= (step_size / 2.0); + + this->state += k2; + } else if (this->substeps_ == 4) { + // Runge-Kutta method (4th order) + cudm_state k1 = this->stepper->compute(this->state, this->t, step_size); + k1 *= step_size; + + this->state += k1 * 0.5; + + cudm_state k2 = this->stepper->compute( + this->state, this->t + step_size / 2.0, step_size); + k2 *= step_size; + + this->state += k2 * 0.5; + + cudm_state k3 = this->stepper->compute( + this->state, this->t + step_size / 2.0, step_size); + k3 *= step_size; + + this->state += k3; + + cudm_state k4 = + this->stepper->compute(this->state, this->t + step_size, step_size); + k4 *= step_size; + + this->state += (k1 + k2 * 2.0 + k3 * 2.0 + k4) * (1.0 / 6.0); + } + + // Update time + this->t += step_size; + } + + std::cout << "Integration complete. Final time: " << this->t << std::endl; +} + +} // namespace cudaq 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..a7f2f052c9 --- /dev/null +++ b/runtime/cudaq/dynamics/scalar_operators.cpp @@ -0,0 +1,225 @@ +/******************************************************************************* + * 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 { + +// constructors and destructors + +scalar_operator::scalar_operator(double value) + : constant_value(value), generator() {} + +scalar_operator::scalar_operator(std::complex value) + : constant_value(value), generator() {} + +scalar_operator::scalar_operator(const ScalarCallbackFunction &create) + : constant_value(), generator(create) {} + +scalar_operator::scalar_operator(ScalarCallbackFunction &&create) + : constant_value() { + generator = std::move(create); +} + +scalar_operator::scalar_operator(const scalar_operator &other) + : constant_value(other.constant_value), generator(other.generator) {} + +scalar_operator::scalar_operator(scalar_operator &&other) + : constant_value(other.constant_value) { + generator = std::move(other.generator); +} + +// assignments + +scalar_operator &scalar_operator::operator=(const scalar_operator &other) { + if (this != &other) { + constant_value = other.constant_value; + generator = other.generator; + } + return *this; +} + +scalar_operator &scalar_operator::operator=(scalar_operator &&other) { + if (this != &other) { + constant_value = other.constant_value; + generator = std::move(other.generator); + } + return *this; +} + +// evaluations + +std::complex scalar_operator::evaluate( + const std::map> parameters) const { + if (constant_value.has_value()) + return constant_value.value(); + else + return generator(parameters); +} + +ScalarCallbackFunction scalar_operator::get_generator() const { + return generator; +} + +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; +} + +// comparison + +bool scalar_operator::operator==(scalar_operator other) { + if (this->constant_value.has_value() && other.constant_value.has_value()) { + return this->constant_value == other.constant_value; + } else { + throw std::runtime_error("not implemented"); + } +} + +// unary operators + +scalar_operator scalar_operator::operator-() const { return *this * (-1.); } + +scalar_operator scalar_operator::operator+() const { return *this; } + +// right-hand arithmetics + +#define ARITHMETIC_OPERATIONS(op, otherTy) \ + scalar_operator scalar_operator::operator op(otherTy other) const { \ + if (this->constant_value.has_value()) { \ + return scalar_operator(this->constant_value.value() op other); \ + } \ + auto newGenerator = \ + [other, generator = this->generator]( \ + std::map> parameters) { \ + return generator(parameters) op other; \ + }; \ + return scalar_operator(newGenerator); \ + } + +ARITHMETIC_OPERATIONS(*, double); +ARITHMETIC_OPERATIONS(/, double); +ARITHMETIC_OPERATIONS(+, double); +ARITHMETIC_OPERATIONS(-, double); +ARITHMETIC_OPERATIONS(*, std::complex); +ARITHMETIC_OPERATIONS(/, std::complex); +ARITHMETIC_OPERATIONS(+, std::complex); +ARITHMETIC_OPERATIONS(-, std::complex); + +#define ARITHMETIC_OPERATIONS_SCALAR_OPS(op) \ + scalar_operator scalar_operator::operator op(const scalar_operator &other) \ + const { \ + if (this->constant_value.has_value() && \ + other.constant_value.has_value()) { \ + auto res = this->constant_value.value() op other.constant_value.value(); \ + return scalar_operator(res); \ + } \ + auto newGenerator = \ + [other, \ + *this](std::map> parameters) { \ + return this->evaluate(parameters) op other.evaluate(parameters); \ + }; \ + return scalar_operator(newGenerator); \ + } + +ARITHMETIC_OPERATIONS_SCALAR_OPS(*); +ARITHMETIC_OPERATIONS_SCALAR_OPS(/); +ARITHMETIC_OPERATIONS_SCALAR_OPS(+); +ARITHMETIC_OPERATIONS_SCALAR_OPS(-); + +#define ARITHMETIC_OPERATIONS_ASSIGNMENT(op, otherTy) \ + scalar_operator &scalar_operator::operator op(otherTy other) { \ + if (this->constant_value.has_value()) { \ + this->constant_value.value() op other; \ + return *this; \ + } \ + auto newGenerator = \ + [other, generator = std::move(this->generator)]( \ + std::map> parameters) { \ + return generator(parameters) op other; \ + }; \ + this->generator = newGenerator; \ + return *this; \ + } + +ARITHMETIC_OPERATIONS_ASSIGNMENT(*=, double); +ARITHMETIC_OPERATIONS_ASSIGNMENT(/=, double); +ARITHMETIC_OPERATIONS_ASSIGNMENT(+=, double); +ARITHMETIC_OPERATIONS_ASSIGNMENT(-=, double); +ARITHMETIC_OPERATIONS_ASSIGNMENT(*=, std::complex); +ARITHMETIC_OPERATIONS_ASSIGNMENT(/=, std::complex); +ARITHMETIC_OPERATIONS_ASSIGNMENT(+=, std::complex); +ARITHMETIC_OPERATIONS_ASSIGNMENT(-=, std::complex); + +#define ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(op) \ + scalar_operator &scalar_operator::operator op( \ + const scalar_operator &other) { \ + if (this->constant_value.has_value() && \ + other.constant_value.has_value()) { \ + this->constant_value.value() op other.constant_value.value(); \ + return *this; \ + } \ + auto newGenerator = \ + [other, \ + *this](std::map> parameters) { \ + return this->evaluate(parameters) op other.evaluate(parameters); \ + }; \ + this->generator = newGenerator; \ + return *this; \ + } + +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(*=); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(/=); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(+=); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(-=); + +#define ARITHMETIC_OPERATIONS_RVALUE(op, otherTy) \ + scalar_operator operator op(scalar_operator &&self, otherTy other) { \ + return std::move(self op## = other); \ + } + +ARITHMETIC_OPERATIONS_RVALUE(*, double); +ARITHMETIC_OPERATIONS_RVALUE(/, double); +ARITHMETIC_OPERATIONS_RVALUE(+, double); +ARITHMETIC_OPERATIONS_RVALUE(-, double); +ARITHMETIC_OPERATIONS_RVALUE(*, std::complex); +ARITHMETIC_OPERATIONS_RVALUE(/, std::complex); +ARITHMETIC_OPERATIONS_RVALUE(+, std::complex); +ARITHMETIC_OPERATIONS_RVALUE(-, std::complex); + +// left-hand arithmetics + +#define ARITHMETIC_OPERATIONS_REVERSE(op, otherTy) \ + scalar_operator operator op(otherTy other, const scalar_operator &self) { \ + if (self.constant_value.has_value()) { \ + return scalar_operator(other op self.constant_value.value()); \ + } \ + auto newGenerator = \ + [other, generator = self.generator]( \ + std::map> parameters) { \ + return other op generator(parameters); \ + }; \ + return scalar_operator(newGenerator); \ + } + +ARITHMETIC_OPERATIONS_REVERSE(*, double); +ARITHMETIC_OPERATIONS_REVERSE(/, double); +ARITHMETIC_OPERATIONS_REVERSE(+, double); +ARITHMETIC_OPERATIONS_REVERSE(-, double); +ARITHMETIC_OPERATIONS_REVERSE(*, std::complex); +ARITHMETIC_OPERATIONS_REVERSE(/, std::complex); +ARITHMETIC_OPERATIONS_REVERSE(+, std::complex); +ARITHMETIC_OPERATIONS_REVERSE(-, std::complex); + +} // 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..313d1322ca --- /dev/null +++ b/runtime/cudaq/dynamics/schedule.cpp @@ -0,0 +1,22 @@ +/******************************************************************************* + * 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( + const std::vector &steps, + const std::vector ¶meters, + std::function(const std::string &, double)> + value_function) + : _steps(steps), _parameters(parameters), _value_function(value_function) {} +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/templates.h b/runtime/cudaq/dynamics/templates.h new file mode 100644 index 0000000000..ecf1b7241d --- /dev/null +++ b/runtime/cudaq/dynamics/templates.h @@ -0,0 +1,169 @@ +/****************************************************************-*- 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 +#include +#include + +namespace cudaq { + +class scalar_operator; + +class matrix_operator; + +template +class product_operator; + +template +class operator_sum; + +template +product_operator operator*(double other, + const product_operator &self); +template +operator_sum operator+(double other, + const product_operator &self); +template +operator_sum operator-(double other, + const product_operator &self); +template +product_operator operator*(std::complex other, + const product_operator &self); +template +operator_sum operator+(std::complex other, + const product_operator &self); +template +operator_sum operator-(std::complex other, + const product_operator &self); +template +product_operator operator*(const scalar_operator &other, + const product_operator &self); +template +operator_sum operator+(const scalar_operator &other, + const product_operator &self); +template +operator_sum operator-(const scalar_operator &other, + const product_operator &self); +template +product_operator operator*(const HandlerTy &other, + const product_operator &self); +template +operator_sum operator+(const HandlerTy &other, + const product_operator &self); +template +operator_sum operator-(const HandlerTy &other, + const product_operator &self); + +template +operator_sum operator*(double other, + const operator_sum &self); +template +operator_sum operator+(double other, + const operator_sum &self); +template +operator_sum operator-(double other, + const operator_sum &self); +template +operator_sum operator*(std::complex other, + const operator_sum &self); +template +operator_sum operator+(std::complex other, + const operator_sum &self); +template +operator_sum operator-(std::complex other, + const operator_sum &self); +template +operator_sum operator*(const scalar_operator &other, + const operator_sum &self); +template +operator_sum operator+(const scalar_operator &other, + const operator_sum &self); +template +operator_sum operator-(const scalar_operator &other, + const operator_sum &self); +template +operator_sum operator*(const HandlerTy &other, + const operator_sum &self); +template +operator_sum operator+(const HandlerTy &other, + const operator_sum &self); +template +operator_sum operator-(const HandlerTy &other, + const operator_sum &self); + +#ifndef CUDAQ_INSTANTIATE_TEMPLATES +extern template product_operator +operator*(double other, const product_operator &self); +extern template operator_sum +operator+(double other, const product_operator &self); +extern template operator_sum +operator-(double other, const product_operator &self); +extern template product_operator +operator*(std::complex other, + const product_operator &self); +extern template operator_sum +operator+(std::complex other, + const product_operator &self); +extern template operator_sum +operator-(std::complex other, + const product_operator &self); +extern template product_operator +operator*(const scalar_operator &other, + const product_operator &self); +extern template operator_sum +operator+(const scalar_operator &other, + const product_operator &self); +extern template operator_sum +operator-(const scalar_operator &other, + const product_operator &self); +extern template product_operator +operator*(const matrix_operator &other, + const product_operator &self); +extern template operator_sum +operator+(const matrix_operator &other, + const product_operator &self); +extern template operator_sum +operator-(const matrix_operator &other, + const product_operator &self); + +extern template operator_sum +operator*(double other, const operator_sum &self); +extern template operator_sum +operator+(double other, const operator_sum &self); +extern template operator_sum +operator-(double other, const operator_sum &self); +extern template operator_sum +operator*(std::complex other, + const operator_sum &self); +extern template operator_sum +operator+(std::complex other, + const operator_sum &self); +extern template operator_sum +operator-(std::complex other, + const operator_sum &self); +extern template operator_sum +operator*(const scalar_operator &other, + const operator_sum &self); +extern template operator_sum +operator+(const scalar_operator &other, + const operator_sum &self); +extern template operator_sum +operator-(const scalar_operator &other, + const operator_sum &self); +extern template operator_sum +operator*(const matrix_operator &other, + const operator_sum &self); +extern template operator_sum +operator+(const matrix_operator &other, + const operator_sum &self); +extern template operator_sum +operator-(const matrix_operator &other, + const operator_sum &self); +#endif + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/evolution.h b/runtime/cudaq/evolution.h new file mode 100644 index 0000000000..87b315de7d --- /dev/null +++ b/runtime/cudaq/evolution.h @@ -0,0 +1,94 @@ +/****************************************************************-*- 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 { + +evolve_result evolve_single( + const operator_sum &hamiltonian, + const std::map &dimensions, const Schedule &schedule, + const 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); +// 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..51c8e4bff4 --- /dev/null +++ b/runtime/cudaq/helpers.h @@ -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. * + ******************************************************************************/ + +#include "cudaq/utils/tensor.h" +#include +#include + +namespace cudaq { +namespace detail { + +// Aggregate parameters from multiple mappings. +std::map aggregate_parameters( + const std::vector> ¶meter_mappings); + +// Extract documentation for a specific parameter from docstring. +std::string parameter_docs(const std::string ¶m_name, + const std::string &docs); + +// Extract positional arguments and keyword-only arguments. +std::pair, std::map> +args_from_kwargs(const std::map &kwargs, + const std::vector &required_args, + const std::vector &kwonly_args); + +/// Generates all possible states for the given dimensions ordered according +/// to the sequence of degrees (ordering is relevant if dimensions differ). +std::vector generate_all_states(std::vector degrees, + std::map dimensions); + +// Permutes the given matrix according to the given permutation. +// If states is the current order of vector entries on which the given matrix +// acts, and permuted_states is the desired order of an array on which the +// permuted matrix should act, then the permutation is defined such that +// [states[i] for i in permutation] produces permuted_states. +cudaq::matrix_2 permute_matrix(cudaq::matrix_2 matrix, + std::vector permutation); + +// Returns the degrees sorted in canonical order. +std::vector canonicalize_degrees(std::vector degrees); +} // namespace detail +} // namespace cudaq 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..baa9e7bee6 --- /dev/null +++ b/runtime/cudaq/operators.h @@ -0,0 +1,760 @@ +/******************************************************************************* + * 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 "definition.h" +#include "dynamics/templates.h" +#include "utils/tensor.h" + +#include +#include +#include +#include +#include +#include + +namespace cudaq { + +class MatrixArithmetics; + +class scalar_operator { + +private: + // If someone gave us a constant value, we will just return that + // directly to them when they call `evaluate`. + std::optional> constant_value; + + /// @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; + +public: + // constructors and destructors + + scalar_operator(double value); + + /// @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(const ScalarCallbackFunction &create); + + /// @brief Constructor that just takes a callback function with no + /// arguments. + scalar_operator(ScalarCallbackFunction &&create); + + // copy constructor + scalar_operator(const scalar_operator &other); + + // move constructor + scalar_operator(scalar_operator &&other); + + ~scalar_operator() = default; + + // assignments + + // assignment operator + scalar_operator &operator=(const scalar_operator &other); + + // move assignment operator + scalar_operator &operator=(scalar_operator &&other); + + // evaluations + + /// @brief Return the scalar operator as a concrete complex value. + std::complex evaluate( + const std::map> parameters = {}) const; + + ScalarCallbackFunction get_generator() const; + + // Return the scalar operator as a 1x1 matrix. This is needed for + // compatibility with the other inherited classes. + matrix_2 to_matrix( + const std::map dimensions = {}, + const std::map> parameters = {}) const; + + // comparisons + + bool operator==(scalar_operator other); + + // unary operators + + scalar_operator operator-() const; + scalar_operator operator+() const; + + // right-hand arithmetics + + scalar_operator operator*(double other) const; + scalar_operator operator/(double other) const; + scalar_operator operator+(double other) const; + scalar_operator operator-(double other) const; + scalar_operator &operator*=(double other); + scalar_operator &operator/=(double other); + scalar_operator &operator+=(double other); + scalar_operator &operator-=(double other); + scalar_operator operator*(std::complex other) const; + scalar_operator operator/(std::complex other) const; + scalar_operator operator+(std::complex other) const; + scalar_operator operator-(std::complex other) const; + scalar_operator &operator*=(std::complex other); + scalar_operator &operator/=(std::complex other); + scalar_operator &operator+=(std::complex other); + scalar_operator &operator-=(std::complex other); + scalar_operator operator*(const scalar_operator &other) const; + scalar_operator operator/(const scalar_operator &other) const; + scalar_operator operator+(const scalar_operator &other) const; + scalar_operator operator-(const scalar_operator &other) const; + scalar_operator &operator*=(const scalar_operator &other); + scalar_operator &operator/=(const scalar_operator &other); + scalar_operator &operator+=(const scalar_operator &other); + scalar_operator &operator-=(const scalar_operator &other); + /// TODO: implement and test pow + + friend scalar_operator operator*(scalar_operator &&self, double other); + friend scalar_operator operator/(scalar_operator &&self, double other); + friend scalar_operator operator+(scalar_operator &&self, double other); + friend scalar_operator operator-(scalar_operator &&self, double other); + friend scalar_operator operator+(scalar_operator &&self, + std::complex other); + friend scalar_operator operator/(scalar_operator &&self, + std::complex other); + friend scalar_operator operator+(scalar_operator &&self, + std::complex other); + friend scalar_operator operator-(scalar_operator &&self, + std::complex other); + + // left-hand arithmetics + + friend scalar_operator operator*(double other, const scalar_operator &self); + friend scalar_operator operator/(double other, const scalar_operator &self); + friend scalar_operator operator+(double other, const scalar_operator &self); + friend scalar_operator operator-(double other, const scalar_operator &self); + friend scalar_operator operator*(std::complex other, + const scalar_operator &self); + friend scalar_operator operator/(std::complex other, + const scalar_operator &self); + friend scalar_operator operator+(std::complex other, + const scalar_operator &self); + friend scalar_operator operator-(std::complex other, + const scalar_operator &self); +}; + +/// @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. +template // handler needs to inherit from operation_handler +class operator_sum { + +private: + std::tuple, std::vector> + m_canonicalize_product(product_operator &prod) const; + + std::tuple, std::vector> + m_canonical_terms() const; + + matrix_2 m_evaluate(MatrixArithmetics arithmetics, + bool pad_terms = true) const; + + void aggregate_terms(); + + template + void aggregate_terms(const product_operator &head, Args &&...args); + +protected: + operator_sum() = default; + std::vector> terms; + std::vector coefficients; + +public: + // read-only properties + + /// @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 operator sum. + int n_terms() const; + + std::vector> get_terms() const; + + // constructors and destructors + + template , Args>...>::value, + void>> + operator_sum(const Args &...args); + + operator_sum(const std::vector> &terms); + + operator_sum(std::vector> &&terms); + + // copy constructor + operator_sum(const operator_sum &other); + + // move constructor + operator_sum(operator_sum &&other); + + ~operator_sum() = default; + + // assignments + + // assignment operator + operator_sum &operator=(const operator_sum &other); + + // move assignment operator + operator_sum &operator=(operator_sum &&other); + + // evaluations + + /// @brief Return the operator_sum 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> ¶meters = {}) const; + + // comparisons + + /// @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; + + // unary operators + + operator_sum operator-() const; + operator_sum operator+() const; + + // right-hand arithmetics + + operator_sum operator*(double other) const; + operator_sum operator+(double other) const; + operator_sum operator-(double other) const; + operator_sum operator*(std::complex other) const; + operator_sum operator+(std::complex other) const; + operator_sum operator-(std::complex 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) const; + operator_sum operator+(const HandlerTy &other) const; + operator_sum operator-(const HandlerTy &other) const; + operator_sum operator*(const HandlerTy &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) const; + operator_sum operator+(const operator_sum &other) const; + operator_sum operator-(const operator_sum &other) const; + operator_sum operator*(const operator_sum &other) const; + + operator_sum &operator*=(double other); + operator_sum &operator+=(double other); + operator_sum &operator-=(double other); + operator_sum &operator*=(std::complex other); + operator_sum &operator+=(std::complex other); + operator_sum &operator-=(std::complex other); + operator_sum &operator*=(const scalar_operator &other); + operator_sum &operator+=(const scalar_operator &other); + operator_sum &operator-=(const scalar_operator &other); + operator_sum &operator*=(const HandlerTy &other); + operator_sum &operator+=(const HandlerTy &other); + operator_sum &operator-=(const HandlerTy &other); + 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 operator_sum &other); + operator_sum &operator+=(const operator_sum &other); + operator_sum &operator-=(const operator_sum &other); + + // left-hand arithmetics + + // Being a bit permissive here, since otherwise the explicit template + // instantiation is a nightmare. + template + friend operator_sum operator*(double other, const operator_sum &self); + template + friend operator_sum operator+(double other, const operator_sum &self); + template + friend operator_sum operator-(double other, const operator_sum &self); + template + friend operator_sum operator*(std::complex other, + const operator_sum &self); + template + friend operator_sum operator+(std::complex other, + const operator_sum &self); + template + friend operator_sum operator-(std::complex other, + const operator_sum &self); + template + friend operator_sum operator*(const scalar_operator &other, + const operator_sum &self); + template + friend operator_sum operator+(const scalar_operator &other, + const operator_sum &self); + template + friend operator_sum operator-(const scalar_operator &other, + const operator_sum &self); + template + friend operator_sum operator*(const T &other, const operator_sum &self); + template + friend operator_sum operator+(const T &other, const operator_sum &self); + template + friend operator_sum operator-(const T &other, const operator_sum &self); + + template + friend operator_sum + product_operator::operator*(const operator_sum &other) const; + template + friend operator_sum + product_operator::operator+(const operator_sum &other) const; + template + friend operator_sum + product_operator::operator-(const operator_sum &other) const; +}; + +/// @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. +template // handler needs to inherit from operation_handler +class product_operator : public operator_sum { + friend class operator_sum; // FIXME: explicitly list members + // instead? + +private: + void aggregate_terms(); + + template + void aggregate_terms(const HandlerTy &head, Args &&...args); + + matrix_2 m_evaluate(MatrixArithmetics arithmetics, + bool pad_terms = true) const; + +public: + // read-only properties + + /// @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 n_terms() const; + + std::vector get_terms() const; + + scalar_operator get_coefficient() const; + + // constructors and destructors + + template < + class... Args, + class = std::enable_if_t< + std::conjunction...>::value, void>> + product_operator(scalar_operator coefficient, const Args &...args); + + product_operator(scalar_operator coefficient, + const std::vector &atomic_operators); + + product_operator(scalar_operator coefficient, + std::vector &&atomic_operators); + + // copy constructor + product_operator(const product_operator &other); + + // move constructor + product_operator(product_operator &&other); + + ~product_operator() = default; + + // assignments + + // assignment operator + product_operator & + operator=(const product_operator &other); + + // move assignment operator + product_operator &operator=(product_operator &&other); + + // evaluations + + /// @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(std::map dimensions = {}, + std::map> parameters = {}) const; + + // comparisons + + /// @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 product_operator &other) const; + + // unary operators + + product_operator operator-() const; + product_operator operator+() const; + + // right-hand arithmetics + + product_operator operator*(double other) const; + operator_sum operator+(double other) const; + operator_sum operator-(double other) const; + product_operator operator*(std::complex other) const; + operator_sum operator+(std::complex other) const; + operator_sum operator-(std::complex other) const; + product_operator operator*(const scalar_operator &other) const; + operator_sum operator+(const scalar_operator &other) const; + operator_sum operator-(const scalar_operator &other) const; + product_operator operator*(const HandlerTy &other) const; + operator_sum operator+(const HandlerTy &other) const; + operator_sum operator-(const HandlerTy &other) const; + product_operator + 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 operator_sum &other) const; + operator_sum operator+(const operator_sum &other) const; + operator_sum operator-(const operator_sum &other) const; + + product_operator &operator*=(double other); + product_operator &operator*=(std::complex other); + product_operator &operator*=(const scalar_operator &other); + product_operator &operator*=(const HandlerTy &other); + product_operator & + operator*=(const product_operator &other); + + // left-hand arithmetics + + // Being a bit permissive here, since otherwise the explicit template + // instantiation is a nightmare. + template + friend product_operator operator*(double other, + const product_operator &self); + template + friend operator_sum operator+(double other, + const product_operator &self); + template + friend operator_sum operator-(double other, + const product_operator &self); + template + friend product_operator operator*(std::complex other, + const product_operator &self); + template + friend operator_sum operator+(std::complex other, + const product_operator &self); + template + friend operator_sum operator-(std::complex other, + const product_operator &self); + template + friend product_operator operator*(const scalar_operator &other, + const product_operator &self); + template + friend operator_sum operator+(const scalar_operator &other, + const product_operator &self); + template + friend operator_sum operator-(const scalar_operator &other, + const product_operator &self); + template + friend product_operator operator*(const T &other, + const product_operator &self); + template + friend operator_sum operator+(const T &other, + const product_operator &self); + template + friend operator_sum operator-(const T &other, + const product_operator &self); +}; + +class matrix_operator { + +private: + static std::map m_ops; + +public: + // The constructor should never be called directly by the user: + // Keeping it internally documented 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. + matrix_operator(std::string operator_id, const std::vector °rees) + : id(operator_id), degrees(degrees) {} + + // constructor + matrix_operator(std::string operator_id, std::vector &°rees) + : id(operator_id), degrees(std::move(degrees)) {} + + // copy constructor + matrix_operator(const matrix_operator &other) + : degrees(other.degrees), id(other.id) {} + + // move constructor + matrix_operator(matrix_operator &&other) + : degrees(std::move(other.degrees)), id(other.id) {} + + // assignment operator + matrix_operator &operator=(const matrix_operator &other) { + if (this != &other) { + degrees = other.degrees; + id = other.id; + } + return *this; + } + + // move assignment operator + matrix_operator &operator=(matrix_operator &&other) { + degrees = std::move(other.degrees); + id = other.id; + return *this; + } + + virtual ~matrix_operator() = default; + + /// @brief The degrees of freedom that the operator acts on in canonical + /// order. + std::vector degrees; + std::string id; + + /// @brief Return the `matrix_operator` as a string. + std::string to_string() const; + + /// @brief Return the `matrix_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(std::map dimensions = {}, + std::map> parameters = {}) const; + + /// @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==(const matrix_operator &other) const { + return this->id == other.id && this->degrees == other.degrees; + } + + // Predefined operators. + static product_operator identity(int degree); + static product_operator zero(int degree); + static product_operator annihilate(int degree); + static product_operator create(int degree); + static product_operator momentum(int degree); + static product_operator number(int degree); + static product_operator parity(int degree); + static product_operator position(int degree); + /// Operators that accept parameters at runtime. + static product_operator squeeze(int degree); + static product_operator displace(int degree); + + /// @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. + static void define(std::string operator_id, + std::vector expected_dimensions, + CallbackFunction &&create) { + auto defn = Definition(operator_id, expected_dimensions, + std::forward(create)); + auto result = matrix_operator::m_ops.insert({operator_id, std::move(defn)}); + if (!result.second) { + // todo: make a nice error message to say op already exists + throw; + } + } +}; + +/// @brief Representation of a time-dependent Hamiltonian for Rydberg system +class rydberg_hamiltonian { +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; +}; +#ifdef CUDAQ_INSTANTIATE_TEMPLATES +template class product_operator; +template class operator_sum; +#else +extern template class product_operator; +extern template class operator_sum; +#endif + +template +class OperatorArithmetics { +public: + /// @brief Accesses the relevant data to evaluate an operator expression + /// in the leaf nodes, that is in elementary and scalar operators. + TEval evaluate(product_operator &op); + + /// @brief Adds two operators that act on the same degrees of freedom. + TEval add(TEval val1, TEval val2); + + /// @brief Multiplies two operators that act on the same degrees of freedom. + TEval mul(TEval val1, TEval val2); + + /// @brief Computes the tensor product of two operators that act on different + /// degrees of freedom. + TEval tensor(TEval val1, TEval val2); +}; + +class EvaluatedMatrix { + friend class MatrixArithmetics; + +private: + std::vector m_degrees; + matrix_2 m_matrix; + +public: + EvaluatedMatrix() = default; + EvaluatedMatrix(std::vector degrees, matrix_2 matrix) + : m_degrees(degrees), m_matrix(matrix) {} + + /// @brief The degrees of freedom that the matrix of the evaluated value + /// applies to. + std::vector degrees() { return m_degrees; } + + /// @brief The matrix representation of an evaluated operator, according + /// to the sequence of degrees of freedom associated with the evaluated + /// value. + matrix_2 matrix() { return m_matrix; } +}; + +/// Encapsulates the functions needed to compute the matrix representation +/// of an operator expression. +class MatrixArithmetics : public OperatorArithmetics { +private: + std::vector _compute_permutation(std::vector op_degrees, + std::vector canon_degrees); + std::tuple> + _canonicalize(matrix_2 &op_matrix, std::vector op_degrees); + +public: + std::map &m_dimensions; // fixme: make const + std::map> + &m_parameters; // fixme: make const + + MatrixArithmetics(std::map dimensions, + std::map> parameters) + : m_dimensions(dimensions), m_parameters(parameters) {} + + // Computes the tensor product of two evaluate operators that act on + // different degrees of freedom using the kronecker product. + EvaluatedMatrix tensor(EvaluatedMatrix op1, EvaluatedMatrix op2); + // Multiplies two evaluated operators that act on the same degrees + // of freedom. + EvaluatedMatrix mul(EvaluatedMatrix op1, EvaluatedMatrix op2); + // Adds two evaluated operators that act on the same degrees + // of freedom. + EvaluatedMatrix add(EvaluatedMatrix op1, EvaluatedMatrix op2); + // Computes the matrix of an ElementaryOperator or ScalarOperator using its + // `to_matrix` method. + EvaluatedMatrix evaluate(std::variant> + op); +}; + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/qis/state.cpp b/runtime/cudaq/qis/state.cpp index d5f9240119..717ca94272 100644 --- a/runtime/cudaq/qis/state.cpp +++ b/runtime/cudaq/qis/state.cpp @@ -119,7 +119,7 @@ state::~state() { // Current use count is 1, so the // shared_ptr is about to go out of scope, // there are no users. Delete the state data. - if (internal.use_count() == 1) + if (internal && internal.use_count() == 1) internal->destroyState(); } diff --git a/runtime/cudaq/runge_kutta_integrator.h b/runtime/cudaq/runge_kutta_integrator.h new file mode 100644 index 0000000000..bd6b4445e3 --- /dev/null +++ b/runtime/cudaq/runge_kutta_integrator.h @@ -0,0 +1,52 @@ +/****************************************************************-*- 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/base_integrator.h" +#include "cudaq/cudm_state.h" +#include "cudaq/cudm_time_stepper.h" +#include +#include + +namespace cudaq { +class runge_kutta_integrator : public BaseIntegrator { +public: + /// @brief Constructor to initialize the Runge-Kutta integrator + /// @param initial_state Initial quantum state. + /// @param t0 Initial time. + /// @param stepper Time stepper instance. + /// @param substeps Number of Runge-Kutta substeps (must be 1, 2, or 4) + runge_kutta_integrator(cudm_state &&initial_state, double t0, + std::shared_ptr stepper, + int substeps = 4) + : BaseIntegrator(std::move(initial_state), t0, stepper), + substeps_(substeps) { + if (!stepper) { + throw std::invalid_argument("Time stepper must be initialized."); + } + + if (substeps_ != 1 && substeps_ != 2 && substeps_ != 4) { + throw std::invalid_argument("Runge-Kutta substeps must be 1, 2, or 4."); + } + this->post_init(); + } + + /// @brief Perform Runge-Kutta integration until the target time. + /// @param target_time The final time to integrate to. + void integrate(double target_time) override; + +protected: + /// @brief Any post-initialization setup + void post_init() override {} + +private: + // Number of substeps in RK integration (1, 2, or 4) + int substeps_; +}; +} // namespace cudaq diff --git a/runtime/cudaq/schedule.h b/runtime/cudaq/schedule.h new file mode 100644 index 0000000000..4a7a62c06b --- /dev/null +++ b/runtime/cudaq/schedule.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 +#include + +namespace cudaq { + +/// @brief Create a schedule for evaluating an operator expression at different +/// steps. +class Schedule { +private: + std::vector _steps; + std::vector _parameters; + std::function(const std::string &, double)> + _value_function; + +public: + /// @brief Range-based iterator begin function + /// @return + std::vector::iterator begin() { return _steps.begin(); } + + /// @brief Range-based iterator end function + /// @return + std::vector::iterator end() { return _steps.end(); } + + /// @brief Range-based constant iterator begin function + /// @return + std::vector::const_iterator cbegin() const { return _steps.cbegin(); } + + /// @brief Range-based constant iterator end function + /// @return + std::vector::const_iterator cend() const { return _steps.cend(); } + + /// @brief Range-based constant iterator begin function + /// @return + std::vector::const_iterator begin() const { return cbegin(); } + + /// @brief Range-based constant iterator end function + /// @return + std::vector::const_iterator end() const { return cend(); } + + const std::vector ¶meters() const { return _parameters; } + + std::function(const std::string &, double)> + value_function() const { + return _value_function; + } + /// @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 ¶meters = {}, + std::function(const std::string &, double)> + value_function = {}); +}; +} // namespace cudaq diff --git a/runtime/cudaq/utils/tensor.cpp b/runtime/cudaq/utils/tensor.cpp index f37f959090..f556c04357 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, @@ -63,6 +64,23 @@ cudaq::matrix_2 &cudaq::matrix_2::operator-=(const cudaq::matrix_2 &right) { return *this; } +bool cudaq::operator==(const cudaq::matrix_2 &lhs, const cudaq::matrix_2 &rhs) { + if (lhs.get_rows() != rhs.get_rows() || + lhs.get_columns() != rhs.get_columns()) { + return false; + } + + for (std::size_t i = 0; i < lhs.get_rows(); i++) { + for (std::size_t j = 0; j < lhs.get_columns(); j++) { + if (lhs[{i, j}] != rhs[{i, j}]) { + return false; + } + } + } + + return true; +} + cudaq::matrix_2 & cudaq::matrix_2::kronecker_inplace(const cudaq::matrix_2 &right) { Dimensions new_dim{get_rows() * right.get_rows(), @@ -127,3 +145,70 @@ std::string cudaq::matrix_2::dump() const { out << '}'; return out.str(); } + +double _factorial(std::size_t value) { + if (value <= 1) + return 1; + return value * std::tgamma(value); +} + +// Calculate the power of a given matrix, `powers` times. +cudaq::matrix_2 cudaq::matrix_2::power(int powers) { + // Initialize as identity. + std::size_t rows = get_rows(); + std::size_t columns = get_columns(); + if (rows != columns) + throw std::runtime_error("Matrix power expects a square matrix."); + auto result = cudaq::matrix_2(rows, columns); + for (std::size_t i = 0; i < rows; i++) { + result[{i, i}] = 1.0 + 0.0j; + } + + // Calculate the matrix power iteratively. + for (std::size_t i = 0; i < powers; i++) { + result = result * *this; + } + return result; +} + +// Calculate the Taylor approximation to the exponential of the given matrix. +cudaq::matrix_2 cudaq::matrix_2::exponential() { + std::size_t rows = get_rows(); + std::size_t columns = get_columns(); + if (rows != columns) + throw std::runtime_error("Matrix exponential expects a square matrix."); + auto result = cudaq::matrix_2(rows, columns); + // Taylor Series Approximation, fixed at 20 steps. + std::size_t taylor_steps = 20; + for (std::size_t step = 0; step < taylor_steps; step++) { + auto term = this->power(step); + for (std::size_t i = 0; i < rows; i++) { + for (std::size_t j = 0; j < columns; j++) { + result[{i, j}] += term[{i, j}] / _factorial(step); + } + } + } + return result; +} + +cudaq::matrix_2 cudaq::matrix_2::identity(const std::size_t rows) { + auto result = cudaq::matrix_2(rows, rows); + for (std::size_t i = 0; i < rows; i++) + result[{i, i}] = 1. + 0.0j; + return result; +} + +// Transpose + Conjugate +cudaq::matrix_2 cudaq::matrix_2::adjoint(const matrix_2 &matrix) { + std::size_t rows = matrix.get_rows(); + std::size_t cols = matrix.get_columns(); + matrix_2 result(cols, rows); + + for (std::size_t i = 0; i < rows; i++) { + for (std::size_t j = 0; j < cols; j++) { + result[{j, i}] = std::conj(matrix[{i, j}]); + } + } + + return result; +} \ No newline at end of file diff --git a/runtime/cudaq/utils/tensor.h b/runtime/cudaq/utils/tensor.h index d9f9099264..801f54ab5b 100644 --- a/runtime/cudaq/utils/tensor.h +++ b/runtime/cudaq/utils/tensor.h @@ -27,6 +27,8 @@ matrix_2 kronecker(const matrix_2 &, const matrix_2 &); template ::value_type> matrix_2 kronecker(Iterable begin, Iterable end); +// Equality comparison operator. +bool operator==(const matrix_2 &, const matrix_2 &); //===----------------------------------------------------------------------===// @@ -38,6 +40,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)]} { @@ -96,6 +101,18 @@ class matrix_2 { friend matrix_2 kronecker(const matrix_2 &, const matrix_2 &); matrix_2 &kronecker_inplace(const matrix_2 &); + /// Matrix exponential, uses 20 terms of Taylor Series approximation. + matrix_2 exponential(); + + /// Matrix power. + matrix_2 power(int powers); + + /// Returns the conjugate transpose of a matrix. + static matrix_2 adjoint(const matrix_2 &matrix); + + /// Return a square identity matrix for the given size. + static matrix_2 identity(const std::size_t rows); + /// Kronecker a list of matrices. The list can be any container that has /// iterators defined. template diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index fa1a9be06d..1a7c87c3b6 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -49,6 +49,8 @@ set(CUDAQ_RUNTIME_TEST_SOURCES # 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) @@ -67,8 +69,10 @@ macro (create_tests_with_backend NVQIR_BACKEND EXTRA_BACKEND_TESTER) endif() target_link_libraries(${TEST_EXE_NAME} PUBLIC - nvqir-${NVQIR_BACKEND} nvqir - cudaq fmt::fmt-header-only + nvqir-${NVQIR_BACKEND} + nvqir + cudaq + fmt::fmt-header-only cudaq-platform-default cudaq-builder gtest_main) @@ -258,6 +262,58 @@ 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/matrix_ops_simple.cpp + dynamics/matrix_ops_arithmetic.cpp + dynamics/scalar_ops_simple.cpp + dynamics/scalar_ops_arithmetic.cpp + dynamics/product_operators_arithmetic.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 + fmt::fmt-header-only) +gtest_discover_tests(test_operators) + +if (CUDA_FOUND) + find_package(CUDAToolkit REQUIRED) + + # Create an executable for dynamics UnitTests + set(CUDAQ_DYNAMICS_TEST_SOURCES + dynamics/test_runge_kutta_integrator.cpp + dynamics/test_helpers.cpp + dynamics/rydberg_hamiltonian.cpp + dynamics/test_cudm_helpers.cpp + dynamics/test_cudm_state.cpp + dynamics/test_cudm_time_stepper.cpp + dynamics/test_cudm_expectation.cpp + dynamics/test_evolve_single.cpp + ) + add_executable(test_dynamics main.cpp ${CUDAQ_DYNAMICS_TEST_SOURCES}) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) + target_link_options(test_dynamics PRIVATE -Wl,--no-as-needed) + endif() + target_link_libraries(test_dynamics + PRIVATE + cudaq-spin + cudaq-operators + cudaq + nvqir-dynamics + ${CUDENSITYMAT_ROOT}/lib/libcudensitymat.so.0 + CUDA::cudart_static + gtest_main + fmt::fmt-header-only) + gtest_discover_tests(test_dynamics) +endif() add_subdirectory(plugin) # build the test qudit execution manager @@ -376,7 +432,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}) @@ -418,4 +475,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/matrix_ops_arithmetic.cpp b/unittests/dynamics/matrix_ops_arithmetic.cpp new file mode 100644 index 0000000000..d7710419fb --- /dev/null +++ b/unittests/dynamics/matrix_ops_arithmetic.cpp @@ -0,0 +1,626 @@ +/******************************************************************************* + * 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_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_real = a[{i, j}].real(); + double b_real = b[{i, j}].real(); + EXPECT_NEAR(a_real, b_real, 1e-8); + double a_imag = a[{i, j}].imag(); + double b_imag = b[{i, j}].imag(); + EXPECT_NEAR(a_imag, b_imag, 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 term1 = amplitude * create_matrix(size); + auto term2 = std::conj(amplitude) * annihilate_matrix(size); + auto difference = term1 - term2; + return difference.exponential(); +} + +cudaq::matrix_2 squeeze_matrix(std::size_t size, + std::complex amplitude) { + auto term1 = std::conj(amplitude) * annihilate_matrix(size).power(2); + auto term2 = amplitude * create_matrix(size).power(2); + auto difference = 0.5 * (term1 - term2); + return difference.exponential(); +} + +void assert_product_equal( + const cudaq::product_operator &got, + const std::complex &expected_coefficient, + const std::vector &expected_terms) { + + auto sumterms_prod = + ((const cudaq::operator_sum &)got).get_terms(); + ASSERT_TRUE(sumterms_prod.size() == 1); + ASSERT_TRUE(got.get_coefficient().evaluate() == expected_coefficient); + ASSERT_TRUE(got.get_terms() == expected_terms); +} + +} // namespace utils_0 + +// FIXME: NO LONGER ACCURATE? -> NOT TESTED ANYWHERE +/// We get an error in the test body when evaluating +/// the return of this function, since the `-1.0` value +/// is going out of scope somewhere down the line in its +/// conversion behind the scenes to a scalar operator. +cudaq::scalar_operator negate(cudaq::scalar_operator op) { return -1.0 * op; } + +TEST(OperatorExpressions, checkElementaryAgainstDouble) { + std::complex value = 0.125 + 0.125j; + + // `matrix_operator` + `complex` and `complex` + + // `matrix_operator` + { + auto elementary = cudaq::matrix_operator::annihilate(0); + + auto sum = value + elementary; + auto reverse = elementary + value; + + auto got_matrix = sum.to_matrix({{0, 3}}); + auto got_matrix_reverse = reverse.to_matrix({{0, 3}}); + + auto scaled_identity = value * utils_0::id_matrix(3); + auto want_matrix = scaled_identity + utils_0::annihilate_matrix(3); + auto want_matrix_reverse = utils_0::annihilate_matrix(3) + scaled_identity; + + utils_0::checkEqual(want_matrix, got_matrix); + utils_0::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `matrix_operator` - `complex` and `complex` - + // `matrix_operator` + { + auto elementary = cudaq::matrix_operator::position(0); + + auto difference = value - elementary; + auto reverse = elementary - value; + + auto got_matrix = difference.to_matrix({{0, 3}}); + auto got_matrix_reverse = reverse.to_matrix({{0, 3}}); + + auto scaled_identity = value * utils_0::id_matrix(3); + auto want_matrix = scaled_identity - utils_0::position_matrix(3); + auto want_matrix_reverse = utils_0::position_matrix(3) - scaled_identity; + + utils_0::checkEqual(want_matrix, got_matrix); + utils_0::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `matrix_operator` * `complex` and `complex` * + // `matrix_operator` + { + auto elementary = cudaq::matrix_operator::number(0); + + auto product = value * elementary; + auto reverse = elementary * value; + + auto got_matrix = product.to_matrix({{0, 3}}); + auto got_matrix_reverse = reverse.to_matrix({{0, 3}}); + + auto scaled_identity = value * utils_0::id_matrix(3); + auto want_matrix = scaled_identity * utils_0::number_matrix(3); + auto want_matrix_reverse = utils_0::number_matrix(3) * scaled_identity; + + utils_0::checkEqual(want_matrix, got_matrix); + utils_0::checkEqual(want_matrix_reverse, got_matrix_reverse); + } +} + +TEST(OperatorExpressions, 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; + + // `matrix_operator + scalar_operator` + { + auto self = cudaq::matrix_operator::annihilate(0); + auto other = cudaq::scalar_operator(const_scale_factor); + + auto sum = self + other; + auto reverse = other + self; + + ASSERT_TRUE(sum.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + 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_reverse_matrix); + } + + // `matrix_operator + scalar_operator` + { + auto self = cudaq::matrix_operator::parity(0); + auto other = cudaq::scalar_operator(function); + + auto sum = self + other; + auto reverse = other + self; + + ASSERT_TRUE(sum.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + 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::parity_matrix(level_count) + scaled_identity; + auto want_reverse_matrix = + scaled_identity + utils_0::parity_matrix(level_count); + utils_0::checkEqual(want_matrix, got_matrix); + utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + // `matrix_operator - scalar_operator` + { + auto self = cudaq::matrix_operator::number(0); + auto other = cudaq::scalar_operator(const_scale_factor); + + auto sum = self - other; + auto reverse = other - self; + + ASSERT_TRUE(sum.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + 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::number_matrix(level_count) - scaled_identity; + auto want_reverse_matrix = + scaled_identity - utils_0::number_matrix(level_count); + utils_0::checkEqual(want_matrix, got_matrix); + utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + // `matrix_operator - scalar_operator` + { + auto self = cudaq::matrix_operator::position(0); + auto other = cudaq::scalar_operator(function); + + auto sum = self - other; + auto reverse = other - self; + + ASSERT_TRUE(sum.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + 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::position_matrix(level_count) - scaled_identity; + auto want_reverse_matrix = + scaled_identity - utils_0::position_matrix(level_count); + utils_0::checkEqual(want_matrix, got_matrix); + utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + // `matrix_operator * scalar_operator` + { + auto self = cudaq::matrix_operator::momentum(0); + auto other = cudaq::scalar_operator(const_scale_factor); + + auto product = self * other; + auto reverse = other * self; + + utils_0::assert_product_equal(product, const_scale_factor, + {cudaq::matrix_operator("momentum", {0})}); + utils_0::assert_product_equal(reverse, const_scale_factor, + {cudaq::matrix_operator("momentum", {0})}); + + std::vector want_degrees = {0}; + ASSERT_TRUE(product.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + 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::momentum_matrix(level_count) * scaled_identity; + auto want_reverse_matrix = + scaled_identity * utils_0::momentum_matrix(level_count); + utils_0::checkEqual(want_matrix, got_matrix); + utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + // `matrix_operator * scalar_operator` + { + auto self = cudaq::matrix_operator::create(0); + auto other = cudaq::scalar_operator(function); + + auto product = self * other; + auto reverse = other * self; + + utils_0::assert_product_equal(product, other.evaluate(), + {cudaq::matrix_operator("create", {0})}); + utils_0::assert_product_equal(reverse, other.evaluate(), + {cudaq::matrix_operator("create", {0})}); + + std::vector want_degrees = {0}; + ASSERT_TRUE(product.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + 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::create_matrix(level_count) * scaled_identity; + auto want_reverse_matrix = + scaled_identity * utils_0::create_matrix(level_count); + utils_0::checkEqual(want_matrix, got_matrix); + utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } +} + +/// Prebuilt elementary ops against one another. +TEST(OperatorExpressions, checkPreBuiltElementaryOpsSelf) { + + /// Keeping this fixed throughout. + int level_count = 3; + std::map dimensions = {{0, level_count}, {1, level_count}}; + + // Addition, same DOF. + { + auto self = cudaq::matrix_operator::annihilate(0); + auto other = cudaq::matrix_operator::create(0); + + auto sum = self + other; + ASSERT_TRUE(sum.n_terms() == 2); + + auto got_matrix = sum.to_matrix(dimensions); + 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::matrix_operator::annihilate(0); + auto other = cudaq::matrix_operator::create(1); + + auto sum = self + other; + ASSERT_TRUE(sum.n_terms() == 2); + + 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}, {1, level_count}}); + auto want_matrix = annihilate_full + create_full; + utils_0::checkEqual(want_matrix, got_matrix); + } + + // Subtraction, same DOF. + { + auto self = cudaq::matrix_operator::annihilate(0); + auto other = cudaq::matrix_operator::create(0); + + auto sum = self - other; + ASSERT_TRUE(sum.n_terms() == 2); + + auto got_matrix = sum.to_matrix(dimensions); + 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::matrix_operator::annihilate(0); + auto other = cudaq::matrix_operator::create(1); + + auto sum = self - other; + ASSERT_TRUE(sum.n_terms() == 2); + + 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(dimensions); + auto want_matrix = annihilate_full - create_full; + utils_0::checkEqual(want_matrix, got_matrix); + } + + // Multiplication, same DOF. + { + auto self = cudaq::matrix_operator::annihilate(0); + auto other = cudaq::matrix_operator::create(0); + + auto product = self * other; + ASSERT_TRUE(product.n_terms() == 2); + + std::vector want_degrees = {0}; + ASSERT_TRUE(product.degrees() == want_degrees); + + auto got_matrix = product.to_matrix(dimensions); + 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::matrix_operator::annihilate(0); + auto other = cudaq::matrix_operator::create(1); + + // Produces an `product_operator` type. + auto product = self * other; + ASSERT_TRUE(product.n_terms() == 2); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(product.degrees() == want_degrees); + + 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(dimensions); + auto want_matrix = annihilate_full * create_full; + utils_0::checkEqual(want_matrix, got_matrix); + } +} + +/// Testing arithmetic between elementary operators and operator +/// sums. +TEST(OperatorExpressions, checkElementaryOpsAgainstOpSum) { + + /// Keeping this fixed throughout. + int level_count = 3; + std::complex value = 0.125 + 0.5j; + + /// `matrix_operator + operator_sum` and `operator_sum + + /// matrix_operator` + { + auto self = cudaq::matrix_operator::annihilate(0); + auto operator_sum = + cudaq::matrix_operator::create(0) + cudaq::matrix_operator::identity(1); + + auto got = self + operator_sum; + auto reverse = operator_sum + self; + + ASSERT_TRUE(got.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + 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); + } + + /// `matrix_operator - operator_sum` and `operator_sum - + /// matrix_operator` + { + auto self = cudaq::matrix_operator::annihilate(0); + auto operator_sum = + cudaq::matrix_operator::create(0) + cudaq::matrix_operator::identity(1); + + auto got = self - operator_sum; + auto reverse = operator_sum - self; + + ASSERT_TRUE(got.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + 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); + } + + /// `matrix_operator * operator_sum` and `operator_sum * + /// matrix_operator` + { + auto self = cudaq::matrix_operator::annihilate(0); + auto operator_sum = cudaq::matrix_operator::squeeze(0) + + cudaq::matrix_operator::identity(1); + + auto got = self * operator_sum; + auto reverse = operator_sum * self; + + ASSERT_TRUE(got.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + for (auto &term : got.get_terms()) + ASSERT_TRUE(term.n_terms() == 2); + for (auto &term : reverse.get_terms()) + ASSERT_TRUE(term.n_terms() == 2); + + 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::squeeze_matrix(level_count, value)); + 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}}, + {{"squeezing", value}}); + auto got_reverse_matrix = reverse.to_matrix( + {{0, level_count}, {1, level_count}}, {{"squeezing", value}}); + 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 += matrix_operator` + { + auto operator_sum = + cudaq::matrix_operator::create(0) + cudaq::matrix_operator::identity(1); + operator_sum += cudaq::matrix_operator::displace(0); + + ASSERT_TRUE(operator_sum.n_terms() == 3); + + auto self_full = + cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::displace_matrix(level_count, value)); + 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}}, {{"displacement", value}}); + auto want_matrix = term_0_full + term_1_full + self_full; + utils_0::checkEqual(want_matrix, got_matrix); + } + + /// `operator_sum -= matrix_operator` + { + auto operator_sum = + cudaq::matrix_operator::create(0) + cudaq::matrix_operator::identity(1); + operator_sum -= cudaq::matrix_operator::annihilate(0); + + ASSERT_TRUE(operator_sum.n_terms() == 3); + + 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 *= matrix_operator` + { + auto self = cudaq::matrix_operator::annihilate(0); + auto operator_sum = + cudaq::matrix_operator::create(0) + cudaq::matrix_operator::identity(1); + + operator_sum *= self; + + ASSERT_TRUE(operator_sum.n_terms() == 2); + for (auto &term : operator_sum.get_terms()) + ASSERT_TRUE(term.n_terms() == 2); + + 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/matrix_ops_simple.cpp b/unittests/dynamics/matrix_ops_simple.cpp new file mode 100644 index 0000000000..9d7548b720 --- /dev/null +++ b/unittests/dynamics/matrix_ops_simple.cpp @@ -0,0 +1,225 @@ +/******************************************************************************* + * 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_real = a[{i, j}].real(); + double b_real = b[{i, j}].real(); + EXPECT_NEAR(a_real, b_real, 1e-8); + double a_imag = a[{i, j}].imag(); + double b_imag = b[{i, j}].imag(); + EXPECT_NEAR(a_imag, b_imag, 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 term1 = amplitude * create_matrix(size); + auto term2 = std::conj(amplitude) * annihilate_matrix(size); + auto difference = term1 - term2; + return difference.exponential(); +} + +cudaq::matrix_2 squeeze_matrix(std::size_t size, + std::complex amplitude) { + auto term1 = std::conj(amplitude) * annihilate_matrix(size).power(2); + auto term2 = amplitude * create_matrix(size).power(2); + auto difference = 0.5 * (term1 - term2); + return difference.exponential(); +} + +} // namespace utils + +TEST(OperatorExpressions, checkPreBuiltElementaryOps) { + std::vector levels = {2, 3, 4, 5}; + + // Keeping this fixed throughout. + int degree_index = 0; + + // Identity operator. + { + for (auto level_count : levels) { + auto id = cudaq::matrix_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::matrix_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::matrix_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::matrix_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::matrix_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::matrix_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::matrix_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::matrix_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 displacement = 2.0 + 1.0j; + auto displace = cudaq::matrix_operator::displace(degree_index); + auto got_displace = displace.to_matrix({{degree_index, level_count}}, + {{"displacement", displacement}}); + auto want_displace = utils::displace_matrix(level_count, displacement); + utils::checkEqual(want_displace, got_displace); + } + } + + // Squeeze operator. + { + for (auto level_count : levels) { + auto squeezing = 2.0 + 1.0j; + auto squeeze = cudaq::matrix_operator::squeeze(degree_index); + auto got_squeeze = squeeze.to_matrix({{degree_index, level_count}}, + {{"squeezing", squeezing}}); + auto want_squeeze = utils::squeeze_matrix(level_count, squeezing); + utils::checkEqual(want_squeeze, got_squeeze); + } + } +} + +// TEST(OperatorExpressions, checkCustomElementaryOps) { +// pass + +// ex: +// operator acts upon {0,2} +// user gives us dimensions for {0,1,2} +//} diff --git a/unittests/dynamics/operator_sum.cpp b/unittests/dynamics/operator_sum.cpp new file mode 100644 index 0000000000..c959e1f7c7 --- /dev/null +++ b/unittests/dynamics/operator_sum.cpp @@ -0,0 +1,1164 @@ +/******************************************************************************* + * 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_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_real = a[{i, j}].real(); + double b_real = b[{i, j}].real(); + EXPECT_NEAR(a_real, b_real, 1e-8); + double a_imag = a[{i, j}].imag(); + double b_imag = b[{i, j}].imag(); + EXPECT_NEAR(a_imag, b_imag, 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 term1 = amplitude * create_matrix(size); + auto term2 = std::conj(amplitude) * annihilate_matrix(size); + auto difference = term1 - term2; + return difference.exponential(); +} + +cudaq::matrix_2 squeeze_matrix(std::size_t size, + std::complex amplitude) { + auto term1 = std::conj(amplitude) * annihilate_matrix(size).power(2); + auto term2 = amplitude * create_matrix(size).power(2); + auto difference = 0.5 * (term1 - term2); + return difference.exponential(); +} + +} // namespace utils_2 + +// TEST(OperatorExpressions, 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::matrix_operator::annihilate(0); +// auto op1 = cudaq::matrix_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::matrix_operator::annihilate(0); +// // auto op1 = cudaq::matrix_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::matrix_operator::annihilate(0); +// // auto op1 = cudaq::matrix_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::matrix_operator::annihilate(0); +// // auto op1 = cudaq::matrix_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(OperatorExpressions, 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::matrix_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::matrix_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(OperatorExpressions, checkOperatorSumAgainstScalarOperator) { + int level_count = 3; + std::complex value = 0.2 + 0.2j; + + // `operator_sum * scalar_operator` and `scalar_operator * operator_sum` + { + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + auto product = sum * cudaq::scalar_operator(value); + auto reverse = cudaq::scalar_operator(value) * sum; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.n_terms() == 1); + ASSERT_TRUE(term.get_coefficient().evaluate() == value); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.n_terms() == 1); + ASSERT_TRUE(term.get_coefficient().evaluate() == value); + } + + auto got_matrix = + product.to_matrix({{1, level_count}, {2, level_count + 1}}); + auto got_matrix_reverse = + reverse.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix * scaled_identity; + auto want_matrix_reverse = scaled_identity * sum_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum + scalar_operator` and `scalar_operator + operator_sum` + { + level_count = 2; + auto original = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + auto sum = original + cudaq::scalar_operator(value); + auto reverse = cudaq::scalar_operator(value) + original; + + ASSERT_TRUE(sum.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}); + auto got_matrix_reverse = + reverse.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix + scaled_identity; + auto want_matrix_reverse = scaled_identity + sum_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum - scalar_operator` and `scalar_operator - operator_sum` + { + auto original = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + auto difference = original - cudaq::scalar_operator(value); + auto reverse = cudaq::scalar_operator(value) - original; + + ASSERT_TRUE(difference.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + auto got_matrix = + difference.to_matrix({{1, level_count}, {2, level_count + 1}}); + auto got_matrix_reverse = + reverse.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix - scaled_identity; + auto want_matrix_reverse = scaled_identity - sum_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum *= scalar_operator` + { + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::momentum(2); + + sum *= cudaq::scalar_operator(value); + + ASSERT_TRUE(sum.n_terms() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.n_terms() == 1); + ASSERT_TRUE(term.get_coefficient().evaluate() == value); + } + + auto got_matrix = sum.to_matrix( + {{0, level_count}, {1, level_count}, {2, level_count + 1}}); + + std::vector matrices_1 = { + utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)}; + std::vector matrices_2 = { + utils_2::momentum_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + auto matrix0 = cudaq::kronecker(matrices_1.begin(), matrices_1.end()); + auto matrix1 = cudaq::kronecker(matrices_2.begin(), matrices_2.end()); + auto scaled_identity = + value * utils_2::id_matrix((level_count + 1) * level_count); + + auto want_matrix = (matrix0 + matrix1) * scaled_identity; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum += scalar_operator` + { + auto sum = + cudaq::matrix_operator::parity(1) + cudaq::matrix_operator::position(2); + + sum += cudaq::scalar_operator(value); + + ASSERT_TRUE(sum.n_terms() == 3); + + auto got_matrix = sum.to_matrix( + {{0, level_count}, {1, level_count}, {2, level_count + 1}}); + + std::vector matrices_1 = { + utils_2::id_matrix(level_count + 1), + utils_2::parity_matrix(level_count)}; + std::vector matrices_2 = { + utils_2::position_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + auto matrix0 = cudaq::kronecker(matrices_1.begin(), matrices_1.end()); + auto matrix1 = cudaq::kronecker(matrices_2.begin(), matrices_2.end()); + auto scaled_identity = + value * utils_2::id_matrix((level_count + 1) * level_count); + + auto want_matrix = matrix0 + matrix1 + scaled_identity; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum -= scalar_operator` + { + auto sum = cudaq::matrix_operator::number(1) + + cudaq::matrix_operator::annihilate(2); + + sum -= cudaq::scalar_operator(value); + + ASSERT_TRUE(sum.n_terms() == 3); + + auto got_matrix = sum.to_matrix( + {{0, level_count}, {1, level_count}, {2, level_count + 1}}); + + std::vector matrices_1 = { + utils_2::id_matrix(level_count + 1), + utils_2::number_matrix(level_count)}; + std::vector matrices_2 = { + utils_2::annihilate_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + auto matrix0 = cudaq::kronecker(matrices_1.begin(), matrices_1.end()); + auto matrix1 = cudaq::kronecker(matrices_2.begin(), matrices_2.end()); + auto scaled_identity = + value * utils_2::id_matrix((level_count + 1) * level_count); + + auto want_matrix = (matrix0 + matrix1) - scaled_identity; + utils_2::checkEqual(want_matrix, got_matrix); + } +} + +TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { + int level_count = 3; + std::complex value = 0.1 + 0.1j; + double double_value = 0.1; + + // `operator_sum * double` and `double * operator_sum` + { + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + auto product = sum * double_value; + auto reverse = double_value * sum; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.n_terms() == 1); + ASSERT_TRUE(term.get_coefficient().evaluate() == + std::complex(double_value)); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.n_terms() == 1); + ASSERT_TRUE(term.get_coefficient().evaluate() == + std::complex(double_value)); + } + + auto got_matrix = + product.to_matrix({{1, level_count}, {2, level_count + 1}}); + auto got_matrix_reverse = + reverse.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + double_value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix * scaled_identity; + auto want_matrix_reverse = scaled_identity * sum_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum + double` and `double + operator_sum` + { + auto original = cudaq::matrix_operator::momentum(1) + + cudaq::matrix_operator::position(2); + + auto sum = original + double_value; + auto reverse = double_value + original; + + ASSERT_TRUE(sum.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}); + auto got_matrix_reverse = + reverse.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::momentum_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::position_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + double_value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix + scaled_identity; + auto want_matrix_reverse = scaled_identity + sum_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum - double` and `double - operator_sum` + { + auto original = + cudaq::matrix_operator::parity(1) + cudaq::matrix_operator::number(2); + + auto difference = original - double_value; + auto reverse = double_value - original; + + ASSERT_TRUE(difference.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + auto got_matrix = + difference.to_matrix({{1, level_count}, {2, level_count + 1}}); + auto got_matrix_reverse = + reverse.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::parity_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::number_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + double_value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix - scaled_identity; + auto want_matrix_reverse = scaled_identity - sum_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum *= double` + { + auto sum = + cudaq::matrix_operator::squeeze(1) + cudaq::matrix_operator::squeeze(2); + + sum *= double_value; + + ASSERT_TRUE(sum.n_terms() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.n_terms() == 1); + ASSERT_TRUE(term.get_coefficient().evaluate() == + std::complex(double_value)); + } + + auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}, + {{"squeezing", value}}); + + auto matrix0 = + cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::squeeze_matrix(level_count, value)); + auto matrix1 = + cudaq::kronecker(utils_2::squeeze_matrix(level_count + 1, value), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + double_value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix * scaled_identity; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum += double` + { + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + sum += double_value; + + ASSERT_TRUE(sum.n_terms() == 3); + + auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + double_value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix + scaled_identity; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum -= double` + { + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + sum -= double_value; + + ASSERT_TRUE(sum.n_terms() == 3); + + auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + double_value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix - scaled_identity; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum * std::complex` and `std::complex * + // operator_sum` + { + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + auto product = sum * value; + auto reverse = value * sum; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.n_terms() == 1); + ASSERT_TRUE(term.get_coefficient().evaluate() == value); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.n_terms() == 1); + ASSERT_TRUE(term.get_coefficient().evaluate() == value); + } + + auto got_matrix = + product.to_matrix({{1, level_count}, {2, level_count + 1}}); + auto got_matrix_reverse = + reverse.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix * scaled_identity; + auto want_matrix_reverse = scaled_identity * sum_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum + std::complex` and `std::complex + + // operator_sum` + { + auto original = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + auto sum = original + value; + auto reverse = value + original; + + ASSERT_TRUE(sum.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}); + auto got_matrix_reverse = + reverse.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix + scaled_identity; + auto want_matrix_reverse = scaled_identity + sum_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum - std::complex` and `std::complex - + // operator_sum` + { + auto original = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + auto difference = original - value; + auto reverse = value - original; + + ASSERT_TRUE(difference.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + auto got_matrix = + difference.to_matrix({{1, level_count}, {2, level_count + 1}}); + auto got_matrix_reverse = + reverse.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::create_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix - scaled_identity; + auto want_matrix_reverse = scaled_identity - sum_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum *= std::complex` + { + auto sum = + cudaq::matrix_operator::displace(1) + cudaq::matrix_operator::parity(2); + + sum *= value; + + ASSERT_TRUE(sum.n_terms() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.n_terms() == 1); + ASSERT_TRUE(term.get_coefficient().evaluate() == value); + } + + auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}, + {{"displacement", value}}); + + auto matrix0 = + cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::displace_matrix(level_count, value)); + auto matrix1 = cudaq::kronecker(utils_2::parity_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix * scaled_identity; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum += std::complex` + { + auto sum = cudaq::matrix_operator::momentum(1) + + cudaq::matrix_operator::squeeze(2); + + sum += value; + + ASSERT_TRUE(sum.n_terms() == 3); + + auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}, + {{"squeezing", value}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::momentum_matrix(level_count)); + auto matrix1 = + cudaq::kronecker(utils_2::squeeze_matrix(level_count + 1, value), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix + scaled_identity; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum -= std::complex` + { + auto sum = + cudaq::matrix_operator::position(1) + cudaq::matrix_operator::number(2); + + sum -= value; + + ASSERT_TRUE(sum.n_terms() == 3); + + auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}); + + auto matrix0 = cudaq::kronecker(utils_2::id_matrix(level_count + 1), + utils_2::position_matrix(level_count)); + auto matrix1 = cudaq::kronecker(utils_2::number_matrix(level_count + 1), + utils_2::id_matrix(level_count)); + auto sum_matrix = matrix0 + matrix1; + auto scaled_identity = + value * utils_2::id_matrix((level_count) * (level_count + 1)); + + auto want_matrix = sum_matrix - scaled_identity; + utils_2::checkEqual(want_matrix, got_matrix); + } +} + +TEST(OperatorExpressions, checkOperatorSumAgainstOperatorSum) { + int level_count = 2; + + // `operator_sum + operator_sum` + { + auto sum_0 = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + auto sum_1 = cudaq::matrix_operator::parity(0) + + cudaq::matrix_operator::annihilate(1) + + cudaq::matrix_operator::create(3); + + auto sum = sum_0 + sum_1; + + ASSERT_TRUE(sum.n_terms() == 5); + + auto got_matrix = sum.to_matrix({{0, level_count}, + {1, level_count + 1}, + {2, level_count + 2}, + {3, level_count + 3}}); + + std::vector matrices_0_0; + std::vector matrices_0_1; + std::vector matrices_1_0; + std::vector matrices_1_1; + std::vector matrices_1_2; + + matrices_0_0 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_0_1 = {utils_2::id_matrix(level_count + 3), + utils_2::create_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_1_0 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::parity_matrix(level_count)}; + matrices_1_1 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::annihilate_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_1_2 = {utils_2::create_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + + auto sum_0_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) + + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto sum_1_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) + + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()) + + cudaq::kronecker(matrices_1_2.begin(), matrices_1_2.end()); + + auto want_matrix = sum_0_matrix + sum_1_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum - operator_sum` + { + auto sum_0 = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::position(2); + auto sum_1 = cudaq::matrix_operator::parity(0) + + cudaq::matrix_operator::annihilate(1) + + cudaq::matrix_operator::momentum(3); + + auto difference = sum_0 - sum_1; + + ASSERT_TRUE(difference.n_terms() == 5); + + auto got_matrix = difference.to_matrix({{0, level_count}, + {1, level_count + 1}, + {2, level_count + 2}, + {3, level_count + 3}}); + + std::vector matrices_0_0; + std::vector matrices_0_1; + std::vector matrices_1_0; + std::vector matrices_1_1; + std::vector matrices_1_2; + + matrices_0_0 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_0_1 = {utils_2::id_matrix(level_count + 3), + utils_2::position_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_1_0 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::parity_matrix(level_count)}; + matrices_1_1 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::annihilate_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_1_2 = {utils_2::momentum_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + + auto sum_0_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) + + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto sum_1_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) + + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()) + + cudaq::kronecker(matrices_1_2.begin(), matrices_1_2.end()); + + auto want_matrix = sum_0_matrix - sum_1_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum * operator_sum` + { + auto sum_0 = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + auto sum_1 = cudaq::matrix_operator::parity(0) + + cudaq::matrix_operator::annihilate(1) + + cudaq::matrix_operator::create(3); + + auto sum_product = sum_0 * sum_1; + auto sum_product_reverse = sum_1 * sum_0; + + ASSERT_TRUE(sum_product.n_terms() == 6); + ASSERT_TRUE(sum_product_reverse.n_terms() == 6); + for (auto term : sum_product.get_terms()) + ASSERT_TRUE(term.n_terms() == 2); + for (auto term : sum_product_reverse.get_terms()) + ASSERT_TRUE(term.n_terms() == 2); + + auto got_matrix = sum_product.to_matrix({{0, level_count}, + {1, level_count + 1}, + {2, level_count + 2}, + {3, level_count + 3}}); + auto got_matrix_reverse = + sum_product_reverse.to_matrix({{0, level_count}, + {1, level_count + 1}, + {2, level_count + 2}, + {3, level_count + 3}}); + + std::vector matrices_0_0; + std::vector matrices_0_1; + std::vector matrices_1_0; + std::vector matrices_1_1; + std::vector matrices_1_2; + + matrices_0_0 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_0_1 = {utils_2::id_matrix(level_count + 3), + utils_2::create_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_1_0 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::parity_matrix(level_count)}; + matrices_1_1 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::annihilate_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_1_2 = {utils_2::create_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + + auto sum_0_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) + + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto sum_1_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) + + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()) + + cudaq::kronecker(matrices_1_2.begin(), matrices_1_2.end()); + + auto want_matrix = sum_0_matrix * sum_1_matrix; + auto want_matrix_reverse = sum_1_matrix * sum_0_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + utils_2::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `operator_sum *= operator_sum` + { + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + auto sum_1 = cudaq::matrix_operator::parity(0) + + cudaq::matrix_operator::annihilate(1) + + cudaq::matrix_operator::create(3); + + sum *= sum_1; + + ASSERT_TRUE(sum.n_terms() == 6); + for (auto term : sum.get_terms()) + ASSERT_TRUE(term.n_terms() == 2); + + auto got_matrix = sum.to_matrix({{0, level_count}, + {1, level_count + 1}, + {2, level_count + 2}, + {3, level_count + 3}}); + + std::vector matrices_0_0; + std::vector matrices_0_1; + std::vector matrices_1_0; + std::vector matrices_1_1; + std::vector matrices_1_2; + + matrices_0_0 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_0_1 = {utils_2::id_matrix(level_count + 3), + utils_2::create_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_1_0 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::parity_matrix(level_count)}; + matrices_1_1 = {utils_2::id_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::annihilate_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + matrices_1_2 = {utils_2::create_matrix(level_count + 3), + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + + auto sum_0_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) + + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto sum_1_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) + + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()) + + cudaq::kronecker(matrices_1_2.begin(), matrices_1_2.end()); + + auto want_matrix = sum_0_matrix * sum_1_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + } +} + +/// 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(OperatorExpressions, checkOperatorSumAgainstProduct) { + int level_count = 2; + + // `operator_sum += product_operator` + { + auto product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + sum += product; + + ASSERT_TRUE(sum.n_terms() == 3); + + auto got_matrix = sum.to_matrix( + {{0, level_count}, {1, level_count + 1}, {2, level_count + 2}}); + std::vector matrices_0_0 = { + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::annihilate_matrix(level_count)}; + std::vector matrices_0_1 = { + utils_2::id_matrix(level_count + 2), + utils_2::annihilate_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + + std::vector matrices_1_0 = { + utils_2::id_matrix(level_count + 2), + utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + std::vector matrices_1_1 = { + utils_2::create_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), utils_2::id_matrix(level_count)}; + + auto product_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto sum_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) + + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = sum_matrix + product_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum -= product_operator` + { + auto product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + sum -= product; + + ASSERT_TRUE(sum.n_terms() == 3); + + auto got_matrix = sum.to_matrix( + {{0, level_count}, {1, level_count + 1}, {2, level_count + 2}}); + std::vector matrices_0_0 = { + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::annihilate_matrix(level_count)}; + std::vector matrices_0_1 = { + utils_2::id_matrix(level_count + 2), + utils_2::annihilate_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + + std::vector matrices_1_0 = { + utils_2::id_matrix(level_count + 2), + utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + std::vector matrices_1_1 = { + utils_2::create_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), utils_2::id_matrix(level_count)}; + + auto product_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto sum_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) + + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = sum_matrix - product_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + } + + // `operator_sum *= product_operator` + { + auto product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + sum *= product; + + ASSERT_TRUE(sum.n_terms() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.n_terms() == 3); + } + + auto got_matrix = sum.to_matrix( + {{0, level_count}, {1, level_count + 1}, {2, level_count + 2}}); + std::vector matrices_0_0 = { + utils_2::id_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), + utils_2::annihilate_matrix(level_count)}; + std::vector matrices_0_1 = { + utils_2::id_matrix(level_count + 2), + utils_2::annihilate_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + + std::vector matrices_1_0 = { + utils_2::id_matrix(level_count + 2), + utils_2::create_matrix(level_count + 1), + utils_2::id_matrix(level_count)}; + std::vector matrices_1_1 = { + utils_2::create_matrix(level_count + 2), + utils_2::id_matrix(level_count + 1), utils_2::id_matrix(level_count)}; + + auto product_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto sum_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) + + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = sum_matrix * product_matrix; + utils_2::checkEqual(want_matrix, got_matrix); + } +} \ No newline at end of file diff --git a/unittests/dynamics/product_operators_arithmetic.cpp b/unittests/dynamics/product_operators_arithmetic.cpp new file mode 100644 index 0000000000..4155178376 --- /dev/null +++ b/unittests/dynamics/product_operators_arithmetic.cpp @@ -0,0 +1,1157 @@ +/******************************************************************************* + * 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 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_real = a[{i, j}].real(); + double b_real = b[{i, j}].real(); + EXPECT_NEAR(a_real, b_real, 1e-8); + double a_imag = a[{i, j}].imag(); + double b_imag = b[{i, j}].imag(); + EXPECT_NEAR(a_imag, b_imag, 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 term1 = amplitude * create_matrix(size); + auto term2 = std::conj(amplitude) * annihilate_matrix(size); + auto difference = term1 - term2; + return difference.exponential(); +} + +cudaq::matrix_2 squeeze_matrix(std::size_t size, + std::complex amplitude) { + auto term1 = std::conj(amplitude) * annihilate_matrix(size).power(2); + auto term2 = amplitude * create_matrix(size).power(2); + auto difference = 0.5 * (term1 - term2); + return difference.exponential(); +} + +void assert_product_equal( + const cudaq::product_operator &got, + const std::complex &expected_coefficient, + const std::vector &expected_terms) { + + auto sumterms_prod = + ((const cudaq::operator_sum &)got).get_terms(); + ASSERT_TRUE(sumterms_prod.size() == 1); + ASSERT_TRUE(got.get_coefficient().evaluate() == expected_coefficient); + ASSERT_TRUE(got.get_terms() == expected_terms); +} + +void print(cudaq::matrix_2 matrix) { + for (std::size_t i = 0; i < matrix.get_rows(); i++) { + for (std::size_t j = 0; j < matrix.get_columns(); j++) { + std::cout << matrix[{i, j}] << " "; + } + std::cout << std::endl; + } +} + +} // namespace utils_1 + +TEST(OperatorExpressions, checkProductOperatorSimpleMatrixChecks) { + std::vector levels = {2, 3, 4}; + + { + // Same degrees of freedom. + { + for (auto level_count : levels) { + auto op0 = cudaq::matrix_operator::annihilate(0); + auto op1 = cudaq::matrix_operator::create(0); + + cudaq::product_operator got = op0 * op1; + utils_1::assert_product_equal( + got, 1., + {cudaq::matrix_operator("annihilate", {0}), + cudaq::matrix_operator("create", {0})}); + + auto got_matrix = got.to_matrix({{0, level_count}}); + auto matrix0 = utils_1::annihilate_matrix(level_count); + auto matrix1 = utils_1::create_matrix(level_count); + auto want_matrix = matrix0 * matrix1; + utils_1::checkEqual(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::matrix_operator::annihilate(0); + auto op1 = cudaq::matrix_operator::create(1); + + cudaq::product_operator got = op0 * op1; + cudaq::product_operator got_reverse = op1 * op0; + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + + auto got_matrix = got.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + got_reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto identity = utils_1::id_matrix(level_count); + auto matrix0 = utils_1::annihilate_matrix(level_count); + auto matrix1 = utils_1::create_matrix(level_count); + + auto fullHilbert0 = cudaq::kronecker(identity, matrix0); + auto fullHilbert1 = cudaq::kronecker(matrix1, identity); + auto want_matrix = fullHilbert0 * fullHilbert1; + auto want_matrix_reverse = fullHilbert1 * fullHilbert0; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + } + + // Different degrees of freedom, non-consecutive. + // Should produce the same matrices as the above test. + { + for (auto level_count : levels) { + auto op0 = cudaq::matrix_operator::annihilate(0); + auto op1 = cudaq::matrix_operator::create(2); + + cudaq::product_operator got = op0 * op1; + cudaq::product_operator got_reverse = op1 * op0; + + std::vector want_degrees = {2, 0}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + + auto got_matrix = got.to_matrix({{0, level_count}, {2, level_count}}); + auto got_matrix_reverse = + got_reverse.to_matrix({{0, level_count}, {2, level_count}}); + + auto identity = utils_1::id_matrix(level_count); + auto matrix0 = utils_1::annihilate_matrix(level_count); + auto matrix1 = utils_1::create_matrix(level_count); + + auto fullHilbert0 = cudaq::kronecker(identity, matrix0); + auto fullHilbert1 = cudaq::kronecker(matrix1, identity); + auto want_matrix = fullHilbert0 * fullHilbert1; + auto want_matrix_reverse = fullHilbert1 * fullHilbert0; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + } + + // Different degrees of freedom, non-consecutive but all dimensions + // provided. + { + for (auto level_count : levels) { + auto op0 = cudaq::matrix_operator::annihilate(0); + auto op1 = cudaq::matrix_operator::create(2); + + cudaq::product_operator got = op0 * op1; + cudaq::product_operator got_reverse = op1 * op0; + + std::vector want_degrees = {2, 0}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + + auto got_matrix = got.to_matrix( + {{0, level_count}, {1, level_count}, {2, level_count}}); + auto got_matrix_reverse = got_reverse.to_matrix( + {{0, level_count}, {1, level_count}, {2, level_count}}); + + auto identity = utils_1::id_matrix(level_count); + auto matrix0 = utils_1::annihilate_matrix(level_count); + auto matrix1 = utils_1::create_matrix(level_count); + + std::vector matrices_0; + std::vector matrices_1; + matrices_0 = {identity, matrix0}; + matrices_1 = {matrix1, identity}; + + auto fullHilbert0 = + cudaq::kronecker(matrices_0.begin(), matrices_0.end()); + auto fullHilbert1 = + cudaq::kronecker(matrices_1.begin(), matrices_1.end()); + auto want_matrix = fullHilbert0 * fullHilbert1; + auto want_matrix_reverse = fullHilbert1 * fullHilbert0; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(got_matrix, want_matrix); + } + } + } +} + +TEST(OperatorExpressions, 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::matrix_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::matrix_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(OperatorExpressions, checkProductOperatorAgainstScalars) { + std::complex value_0 = 0.1 + 0.1; + int level_count = 3; + + /// `product_operator + complex` + { + auto product_op = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + + auto sum = value_0 + product_op; + auto reverse = product_op + value_0; + + ASSERT_TRUE(sum.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(sum.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + auto got_matrix = sum.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product = term_0 * term_1; + auto scaled_identity = + value_0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = scaled_identity + product; + auto want_matrix_reverse = product + scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + /// `product_operator + double` + { + auto product_op = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + + auto sum = 2.0 + product_op; + auto reverse = product_op + 2.0; + + ASSERT_TRUE(sum.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(sum.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + auto got_matrix = sum.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product = term_0 * term_1; + auto scaled_identity = 2.0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = scaled_identity + product; + auto want_matrix_reverse = product + scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + /// `product_operator + scalar_operator` + { + auto product_op = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(value_0); + + auto sum = scalar_op + product_op; + auto reverse = product_op + scalar_op; + + ASSERT_TRUE(sum.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(sum.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + auto got_matrix = sum.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product = term_0 * term_1; + auto scaled_identity = + value_0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = scaled_identity + product; + auto want_matrix_reverse = product + scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + /// `product_operator - complex` + { + auto product_op = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + + auto difference = value_0 - product_op; + auto reverse = product_op - value_0; + + ASSERT_TRUE(difference.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(difference.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + auto got_matrix = + difference.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product = term_0 * term_1; + auto scaled_identity = + value_0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = scaled_identity - product; + auto want_matrix_reverse = product - scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + /// `product_operator - double` + { + auto product_op = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + + auto difference = 2.0 - product_op; + auto reverse = product_op - 2.0; + + ASSERT_TRUE(difference.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(difference.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + auto got_matrix = + difference.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product = term_0 * term_1; + auto scaled_identity = 2.0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = scaled_identity - product; + auto want_matrix_reverse = product - scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + /// `product_operator - scalar_operator` + { + auto product_op = cudaq::matrix_operator::momentum(0) * + cudaq::matrix_operator::momentum(1); + auto scalar_op = cudaq::scalar_operator(value_0); + + auto difference = scalar_op - product_op; + auto reverse = product_op - scalar_op; + + ASSERT_TRUE(difference.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(difference.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + auto got_matrix = + difference.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::momentum_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::momentum_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product = term_0 * term_1; + auto scaled_identity = + value_0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = scaled_identity - product; + auto want_matrix_reverse = product - scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + /// `product_operator * complex` + { + auto product_op = + cudaq::matrix_operator::number(0) * cudaq::matrix_operator::number(1); + ASSERT_TRUE(product_op.n_terms() == 2); + ASSERT_TRUE(product_op.get_coefficient().evaluate() == + std::complex(1.)); + + auto product = value_0 * product_op; + auto reverse = product_op * value_0; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + ASSERT_TRUE(product.get_coefficient().evaluate() == value_0); + ASSERT_TRUE(reverse.get_coefficient().evaluate() == value_0); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(product.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + auto got_matrix = product.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::number_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::number_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product_matrix = term_0 * term_1; + auto scaled_identity = + value_0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = scaled_identity * product_matrix; + auto want_matrix_reverse = product_matrix * scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + /// `product_operator * double` + { + auto product_op = + cudaq::matrix_operator::parity(0) * cudaq::matrix_operator::parity(1); + ASSERT_TRUE(product_op.n_terms() == 2); + ASSERT_TRUE(product_op.get_coefficient().evaluate() == + std::complex(1.)); + + auto product = 2.0 * product_op; + auto reverse = product_op * 2.0; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + ASSERT_TRUE(product.get_coefficient().evaluate() == + std::complex(2.)); + ASSERT_TRUE(reverse.get_coefficient().evaluate() == + std::complex(2.)); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(product.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + auto got_matrix = product.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::parity_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::parity_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product_matrix = term_0 * term_1; + auto scaled_identity = 2.0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = scaled_identity * product_matrix; + auto want_matrix_reverse = product_matrix * scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + /// `product_operator * scalar_operator` + { + auto product_op = cudaq::matrix_operator::position(0) * + cudaq::matrix_operator::position(1); + auto scalar_op = cudaq::scalar_operator(value_0); + + auto product = scalar_op * product_op; + auto reverse = product_op * scalar_op; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + ASSERT_TRUE(product.get_coefficient().evaluate() == scalar_op.evaluate()); + ASSERT_TRUE(reverse.get_coefficient().evaluate() == scalar_op.evaluate()); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(product.degrees() == want_degrees); + ASSERT_TRUE(reverse.degrees() == want_degrees); + + auto got_matrix = product.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::position_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::position_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product_matrix = term_0 * term_1; + auto scaled_identity = + value_0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = scaled_identity * product_matrix; + auto want_matrix_reverse = product_matrix * scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + /// `product_operator *= complex` + { + auto product = + cudaq::matrix_operator::number(0) * cudaq::matrix_operator::momentum(1); + product *= value_0; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(product.get_coefficient().evaluate() == value_0); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(product.degrees() == want_degrees); + + auto got_matrix = product.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::number_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::momentum_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product_matrix = term_0 * term_1; + auto scaled_identity = + value_0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = product_matrix * scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + } + + /// `product_operator *= double` + { + auto product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::create(1); + product *= 2.0; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(product.get_coefficient().evaluate() == + std::complex(2.)); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(product.degrees() == want_degrees); + + auto got_matrix = product.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::create_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product_matrix = term_0 * term_1; + auto scaled_identity = 2.0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = product_matrix * scaled_identity; + + utils_1::checkEqual(want_matrix, got_matrix); + } + + /// `product_operator *= scalar_operator` + { + auto product = + cudaq::matrix_operator::number(0) * cudaq::matrix_operator::momentum(1); + auto scalar_op = cudaq::scalar_operator(value_0); + + product *= scalar_op; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(product.get_coefficient().evaluate() == scalar_op.evaluate()); + ASSERT_TRUE(scalar_op.evaluate() == value_0); + + std::vector want_degrees = {1, 0}; + ASSERT_TRUE(product.degrees() == want_degrees); + + auto got_matrix = product.to_matrix({{0, level_count}, {1, level_count}}); + + auto term_0 = cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::number_matrix(level_count)); + auto term_1 = cudaq::kronecker(utils_1::momentum_matrix(level_count), + utils_1::id_matrix(level_count)); + auto product_matrix = term_0 * term_1; + auto scaled_identity = + value_0 * utils_1::id_matrix(level_count * level_count); + + auto want_matrix = product_matrix * scaled_identity; + utils_1::checkEqual(want_matrix, got_matrix); + } +} + +TEST(OperatorExpressions, checkProductOperatorAgainstProduct) { + + int level_count = 3; + std::map dimensions = { + {0, level_count}, {1, level_count}, {2, level_count + 1}}; + + // `product_operator + product_operator` + { + auto term_0 = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto term_1 = cudaq::matrix_operator::create(1) * + cudaq::matrix_operator::annihilate(2); + + auto sum = term_0 + term_1; + + ASSERT_TRUE(sum.n_terms() == 2); + + std::vector want_degrees = {2, 1, 0}; + ASSERT_TRUE(sum.degrees() == want_degrees); + + auto got_matrix = sum.to_matrix(dimensions); + + std::vector matrices_0_0; + std::vector matrices_0_1; + matrices_0_0 = {utils_1::id_matrix(level_count + 1), + utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)}; + matrices_0_1 = {utils_1::id_matrix(level_count + 1), + utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)}; + + std::vector matrices_1_0; + std::vector matrices_1_1; + matrices_1_0 = {utils_1::id_matrix(level_count + 1), + utils_1::create_matrix(level_count), + utils_1::id_matrix(level_count)}; + matrices_1_1 = {utils_1::annihilate_matrix(level_count + 1), + utils_1::id_matrix(level_count), + utils_1::id_matrix(level_count)}; + + auto term_0_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto term_1_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) * + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = term_0_matrix + term_1_matrix; + utils_1::checkEqual(want_matrix, got_matrix); + } + + // `product_operator - product_operator` + { + auto term_0 = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::number(1); + auto term_1 = + cudaq::matrix_operator::create(1) * cudaq::matrix_operator::momentum(2); + + auto difference = term_0 - term_1; + + ASSERT_TRUE(difference.n_terms() == 2); + + auto got_matrix = difference.to_matrix(dimensions); + + std::vector matrices_0_0; + std::vector matrices_0_1; + matrices_0_0 = {utils_1::id_matrix(level_count + 1), + utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)}; + matrices_0_1 = {utils_1::id_matrix(level_count + 1), + utils_1::number_matrix(level_count), + utils_1::id_matrix(level_count)}; + + std::vector matrices_1_0; + std::vector matrices_1_1; + matrices_1_0 = {utils_1::id_matrix(level_count + 1), + utils_1::create_matrix(level_count), + utils_1::id_matrix(level_count)}; + matrices_1_1 = {utils_1::momentum_matrix(level_count + 1), + utils_1::id_matrix(level_count), + utils_1::id_matrix(level_count)}; + + auto term_0_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto term_1_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) * + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = term_0_matrix - term_1_matrix; + utils_1::checkEqual(want_matrix, got_matrix); + } + + // `product_operator * product_operator` + { + auto term_0 = cudaq::matrix_operator::position(0) * + cudaq::matrix_operator::annihilate(1); + auto term_1 = + cudaq::matrix_operator::create(1) * cudaq::matrix_operator::parity(2); + + auto product = term_0 * term_1; + + ASSERT_TRUE(product.n_terms() == 4); + + auto got_matrix = product.to_matrix(dimensions); + + std::vector matrices_0_0; + std::vector matrices_0_1; + matrices_0_0 = {utils_1::id_matrix(level_count + 1), + utils_1::id_matrix(level_count), + utils_1::position_matrix(level_count)}; + matrices_0_1 = {utils_1::id_matrix(level_count + 1), + utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)}; + + std::vector matrices_1_0; + std::vector matrices_1_1; + matrices_1_0 = {utils_1::id_matrix(level_count + 1), + utils_1::create_matrix(level_count), + utils_1::id_matrix(level_count)}; + matrices_1_1 = {utils_1::parity_matrix(level_count + 1), + utils_1::id_matrix(level_count), + utils_1::id_matrix(level_count)}; + + auto term_0_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto term_1_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) * + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = term_0_matrix * term_1_matrix; + utils_1::checkEqual(want_matrix, got_matrix); + } + + // `product_operator *= product_operator` + { + auto term_0 = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::number(1); + auto term_1 = cudaq::matrix_operator::create(1) * + cudaq::matrix_operator::annihilate(2); + + term_0 *= term_1; + + ASSERT_TRUE(term_0.n_terms() == 4); + + auto got_matrix = term_0.to_matrix(dimensions); + + std::vector matrices_0_0; + std::vector matrices_0_1; + matrices_0_0 = {utils_1::id_matrix(level_count + 1), + utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)}; + matrices_0_1 = {utils_1::id_matrix(level_count + 1), + utils_1::number_matrix(level_count), + utils_1::id_matrix(level_count)}; + + std::vector matrices_1_0; + std::vector matrices_1_1; + matrices_1_0 = {utils_1::id_matrix(level_count + 1), + utils_1::create_matrix(level_count), + utils_1::id_matrix(level_count)}; + matrices_1_1 = {utils_1::annihilate_matrix(level_count + 1), + utils_1::id_matrix(level_count), + utils_1::id_matrix(level_count)}; + + auto term_0_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto term_1_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) * + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = term_0_matrix * term_1_matrix; + utils_1::checkEqual(want_matrix, got_matrix); + } +} + +TEST(OperatorExpressions, checkProductOperatorAgainstElementary) { + + int level_count = 3; + + // `product_operator + matrix_operator` + { + auto product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto elementary = cudaq::matrix_operator::create(1); + + auto sum = product + elementary; + auto reverse = elementary + product; + + ASSERT_TRUE(sum.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + auto got_matrix = sum.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto product_matrix = + cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)) * + cudaq::kronecker(utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)); + auto elementary_matrix = cudaq::kronecker( + utils_1::create_matrix(level_count), utils_1::id_matrix(level_count)); + + auto want_matrix = product_matrix + elementary_matrix; + auto want_matrix_reverse = elementary_matrix + product_matrix; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `product_operator - matrix_operator` + { + auto product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto elementary = cudaq::matrix_operator::create(1); + + auto difference = product - elementary; + auto reverse = elementary - product; + + ASSERT_TRUE(difference.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + auto got_matrix = + difference.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto product_matrix = + cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)) * + cudaq::kronecker(utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)); + auto elementary_matrix = cudaq::kronecker( + utils_1::create_matrix(level_count), utils_1::id_matrix(level_count)); + + auto want_matrix = product_matrix - elementary_matrix; + auto want_matrix_reverse = elementary_matrix - product_matrix; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `product_operator * matrix_operator` + { + auto term_0 = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto elementary = cudaq::matrix_operator::create(1); + + auto product = term_0 * elementary; + auto reverse = elementary * term_0; + + ASSERT_TRUE(product.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + auto got_matrix = product.to_matrix({{0, level_count}, {1, level_count}}); + auto got_matrix_reverse = + reverse.to_matrix({{0, level_count}, {1, level_count}}); + + auto product_matrix = + cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)) * + cudaq::kronecker(utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)); + auto elementary_matrix = cudaq::kronecker( + utils_1::create_matrix(level_count), utils_1::id_matrix(level_count)); + + auto want_matrix = product_matrix * elementary_matrix; + auto want_matrix_reverse = elementary_matrix * product_matrix; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `product_operator *= matrix_operator` + { + auto product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto elementary = cudaq::matrix_operator::create(1); + + product *= elementary; + + ASSERT_TRUE(product.n_terms() == 3); + + auto got_matrix = product.to_matrix({{0, level_count}, {1, level_count}}); + + auto product_matrix = + cudaq::kronecker(utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)) * + cudaq::kronecker(utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)); + auto elementary_matrix = cudaq::kronecker( + utils_1::create_matrix(level_count), utils_1::id_matrix(level_count)); + + auto want_matrix = product_matrix * elementary_matrix; + + utils_1::checkEqual(want_matrix, got_matrix); + } +} + +TEST(OperatorExpressions, checkProductOperatorAgainstOperatorSum) { + + int level_count = 3; + std::map dimensions = { + {0, level_count}, {1, level_count}, {2, level_count + 1}}; + + // `product_operator + operator_sum` + { + auto product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto original_sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + auto sum = product + original_sum; + auto reverse = original_sum + product; + + ASSERT_TRUE(sum.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + auto got_matrix = sum.to_matrix(dimensions); + auto got_matrix_reverse = reverse.to_matrix(dimensions); + + // Cast every term to full Hilbert space. + std::vector matrices_0_0 = { + utils_1::id_matrix(level_count + 1), utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)}; + std::vector matrices_0_1 = { + utils_1::id_matrix(level_count + 1), + utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)}; + std::vector matrices_1_0 = { + utils_1::id_matrix(level_count + 1), + utils_1::create_matrix(level_count), utils_1::id_matrix(level_count)}; + std::vector matrices_1_1 = { + utils_1::create_matrix(level_count + 1), + utils_1::id_matrix(level_count), utils_1::id_matrix(level_count)}; + auto product_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto sum_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) + + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = product_matrix + sum_matrix; + auto want_matrix_reverse = sum_matrix + product_matrix; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `product_operator - operator_sum` + { + auto product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto original_difference = + cudaq::matrix_operator::create(1) - cudaq::matrix_operator::create(2); + + auto difference = product - original_difference; + auto reverse = original_difference - product; + + ASSERT_TRUE(difference.n_terms() == 3); + ASSERT_TRUE(reverse.n_terms() == 3); + + auto got_matrix = difference.to_matrix(dimensions); + auto got_matrix_reverse = reverse.to_matrix(dimensions); + + // Cast every term to full Hilbert space. + std::vector matrices_0_0 = { + utils_1::id_matrix(level_count + 1), utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)}; + std::vector matrices_0_1 = { + utils_1::id_matrix(level_count + 1), + utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)}; + std::vector matrices_1_0 = { + utils_1::id_matrix(level_count + 1), + utils_1::create_matrix(level_count), utils_1::id_matrix(level_count)}; + std::vector matrices_1_1 = { + utils_1::create_matrix(level_count + 1), + utils_1::id_matrix(level_count), utils_1::id_matrix(level_count)}; + auto product_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto difference_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) - + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = product_matrix - difference_matrix; + auto want_matrix_reverse = difference_matrix - product_matrix; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } + + // `product_operator * operator_sum` + { + auto original_product = cudaq::matrix_operator::annihilate(0) * + cudaq::matrix_operator::annihilate(1); + auto sum = + cudaq::matrix_operator::create(1) + cudaq::matrix_operator::create(2); + + auto product = original_product * sum; + auto reverse = sum * original_product; + + ASSERT_TRUE(product.n_terms() == 2); + ASSERT_TRUE(reverse.n_terms() == 2); + + auto got_matrix = product.to_matrix(dimensions); + auto got_matrix_reverse = reverse.to_matrix(dimensions); + + // Cast every term to full Hilbert space. + std::vector matrices_0_0 = { + utils_1::id_matrix(level_count + 1), utils_1::id_matrix(level_count), + utils_1::annihilate_matrix(level_count)}; + std::vector matrices_0_1 = { + utils_1::id_matrix(level_count + 1), + utils_1::annihilate_matrix(level_count), + utils_1::id_matrix(level_count)}; + std::vector matrices_1_0 = { + utils_1::id_matrix(level_count + 1), + utils_1::create_matrix(level_count), utils_1::id_matrix(level_count)}; + std::vector matrices_1_1 = { + utils_1::create_matrix(level_count + 1), + utils_1::id_matrix(level_count), utils_1::id_matrix(level_count)}; + auto product_matrix = + cudaq::kronecker(matrices_0_0.begin(), matrices_0_0.end()) * + cudaq::kronecker(matrices_0_1.begin(), matrices_0_1.end()); + auto sum_matrix = + cudaq::kronecker(matrices_1_0.begin(), matrices_1_0.end()) + + cudaq::kronecker(matrices_1_1.begin(), matrices_1_1.end()); + + auto want_matrix = product_matrix * sum_matrix; + auto want_matrix_reverse = sum_matrix * product_matrix; + + utils_1::checkEqual(want_matrix, got_matrix); + utils_1::checkEqual(want_matrix_reverse, got_matrix_reverse); + } +} diff --git a/unittests/dynamics/rydberg_hamiltonian.cpp b/unittests/dynamics/rydberg_hamiltonian.cpp new file mode 100644 index 0000000000..f3883366ba --- /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..b6d9a48518 --- /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/operators.h" +#include + +TEST(OperatorExpressions, checkScalarOpsArithmeticComplex) { + // 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; + EXPECT_NEAR(std::abs(scalar_op.evaluate()), std::abs(value_0), 1e-5); + + 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_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_1}}); + auto got_value_1 = reverse_order_op.evaluate({{"value", value_1}}); + + 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({{"value", value_1}}); + auto want_value = (value_3 / value_1) / (value_1 / 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_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(OperatorExpressions, 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..550b277f00 --- /dev/null +++ b/unittests/dynamics/scalar_ops_simple.cpp @@ -0,0 +1,118 @@ +/******************************************************************************* + * 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 + +TEST(OperatorExpressions, 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(OperatorExpressions, 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_expectation.cpp b/unittests/dynamics/test_cudm_expectation.cpp new file mode 100644 index 0000000000..e3063a2e23 --- /dev/null +++ b/unittests/dynamics/test_cudm_expectation.cpp @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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 "test_mocks.h" +#include +#include +#include +#include +#include +#include +#include +#include "common/EigenDense.h" +#include + +using namespace cudaq; + +class CuDensityExpectationTest : public ::testing::Test { +protected: + cudensitymatHandle_t handle_; + + void SetUp() override { + // Create library handle + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); + } + + void TearDown() override { + // Clean up + HANDLE_CUDM_ERROR(cudensitymatDestroy(handle_)); + } +}; + +TEST_F(CuDensityExpectationTest, checkCompute) { + cudm_helper helper(handle_); + const std::vector dims = {10}; + // Check number operator on boson Fock space + auto op = cudaq::matrix_operator::number(0); + auto cudmOp = helper.convert_to_cudensitymat_operator( + {}, op, dims); + + cudm_expectation expectation(handle_, cudmOp); + + for (std::size_t stateIdx = 0; stateIdx < dims[0]; ++stateIdx) { + std::vector> initialState(dims[0], 0.0); + initialState[stateIdx] = 1.0; + auto inputState = std::make_unique(handle_, initialState, dims); + expectation.prepare(inputState->get_impl()); + const auto expVal = expectation.compute(inputState->get_impl(), 0.0); + EXPECT_NEAR(expVal.real(), 1.0 * stateIdx, 1e-12); + EXPECT_NEAR(expVal.imag(), 0.0, 1e-12); + } +} + + +TEST_F(CuDensityExpectationTest, checkCompositeSystem) { + cudm_helper helper(handle_); + const std::vector dims = {2, 10}; + // Check number operator on boson Fock space + auto op = cudaq::matrix_operator::number(1); + auto cudmOp = helper.convert_to_cudensitymat_operator( + {}, op, dims); + + cudm_expectation expectation(handle_, cudmOp); + + for (std::size_t stateIdx = 0; stateIdx < dims[1]; ++stateIdx) { + Eigen::Vector2cd qubit_state; + qubit_state << 1.0, 0.0; + Eigen::VectorXcd cavity_state = Eigen::VectorXcd::Zero(dims[1]); + cavity_state[stateIdx] = 1.0; + Eigen::VectorXcd initial_state_vec = + Eigen::kroneckerProduct(cavity_state, qubit_state); + std::vector> initialState( + initial_state_vec.data(), + initial_state_vec.data() + initial_state_vec.size()); + auto inputState = std::make_unique(handle_, initialState, dims); + expectation.prepare(inputState->get_impl()); + const auto expVal = expectation.compute(inputState->get_impl(), 0.0); + std::cout << "Result: " << expVal << "\n"; + EXPECT_NEAR(expVal.real(), 1.0 * stateIdx, 1e-12); + EXPECT_NEAR(expVal.imag(), 0.0, 1e-12); + } +} \ 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..9bce633f89 --- /dev/null +++ b/unittests/dynamics/test_cudm_helpers.cpp @@ -0,0 +1,193 @@ +/******************************************************************************* + * 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 "test_mocks.h" +#include +#include +#include +#include +#include + +// Initialize operator_sum +cudaq::operator_sum initialize_operator_sum() { + return cudaq::matrix_operator::create(0) + cudaq::matrix_operator::create(1); +} + +class CuDensityMatHelpersTestFixture : public ::testing::Test { +protected: + cudensitymatHandle_t handle; + cudaStream_t stream; + std::unique_ptr helper; + std::unique_ptr state; + + void SetUp() override { + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle)); + stream = 0; + helper = std::make_unique(handle); + + std::vector mode_extents = {2}; + std::vector> rawData = {{1.0, 0.0}, {0.0, 0.0}}; + state = std::make_unique(handle, rawData, mode_extents); + } + + void TearDown() override { HANDLE_CUDA_ERROR(cudaDeviceSynchronize()); } +}; + +// Test for initialize_state +TEST_F(CuDensityMatHelpersTestFixture, InitializeState) { + std::vector mode_extents = {2}; + + std::vector> rawData = {{1.0, 0.0}, {0.0, 0.0}}; + + cudaq::cudm_state state(handle, rawData, mode_extents); + + ASSERT_TRUE(state.is_initialized()); +} + +// Test for scale_state +// TEST_F(CuDensityMatHelpersTestFixture, ScaleState) { +// ASSERT_TRUE(state->is_initialized()); + +// EXPECT_NO_THROW(helper->scale_state(state->get_impl(), 2.0, +// stream)); +// } + +// Test for compute_lindblad_op +TEST_F(CuDensityMatHelpersTestFixture, ComputeLindbladOp) { + std::vector mode_extents = {2, 2}; + + std::vector> c_op1_values = { + {1.0, 0.0}, + {0.0, 0.0}, + {0.0, 0.0}, + {0.0, 0.0}, + }; + + std::vector> c_op2_values = { + {0.0, 0.0}, + {0.0, 1.0}, + {0.0, 0.0}, + {0.0, 0.0}, + }; + + cudaq::matrix_2 c_op1(c_op1_values, {2, 2}); + cudaq::matrix_2 c_op2(c_op2_values, {2, 2}); + std::vector c_ops = {c_op1, c_op2}; + + EXPECT_NO_THROW({ + auto lindblad_op = helper->compute_lindblad_operator(c_ops, mode_extents); + + ASSERT_NE(lindblad_op, nullptr) + << "Error: Lindblad operator creation failed!"; + + cudensitymatDestroyOperator(lindblad_op); + }); +} + +// Test for convert_to_cudensitymat_operator +TEST_F(CuDensityMatHelpersTestFixture, ConvertToCuDensityMatOperator) { + std::vector mode_extents = mock_hilbert_space_dims(); + + auto op_sum = initialize_operator_sum(); + + EXPECT_NO_THROW({ + auto result = + helper->convert_to_cudensitymat_operator( + {}, op_sum, mode_extents); + + ASSERT_NE(result, nullptr); + cudensitymatDestroyOperator(result); + }); +} + +// Test with a higher-dimensional mode extent +TEST_F(CuDensityMatHelpersTestFixture, ConvertHigherDimensionalOperator) { + std::vector mode_extents = {3, 3}; + + auto op_sum = initialize_operator_sum(); + + EXPECT_NO_THROW({ + auto result = + helper->convert_to_cudensitymat_operator( + {}, op_sum, mode_extents); + ASSERT_NE(result, nullptr); + cudensitymatDestroyOperator(result); + }); +} + +// Test with a coefficient callback function +TEST_F(CuDensityMatHelpersTestFixture, ConvertOperatorWithCallback) { + std::vector mode_extents = {2, 2}; + + auto callback_function = [](std::map>) { + return std::complex(1.5, 0.0); + }; + + cudaq::scalar_operator scalar_callback(callback_function); + + auto op_sum = scalar_callback * cudaq::matrix_operator::create(0); + + EXPECT_NO_THROW({ + auto result = + helper->convert_to_cudensitymat_operator( + {}, op_sum, mode_extents); + ASSERT_NE(result, nullptr); + cudensitymatDestroyOperator(result); + }); +} + +// Test with tensor callback function +TEST_F(CuDensityMatHelpersTestFixture, ConvertOperatorWithTensorCallback) { + std::vector mode_extents = {2, 2}; + + const std::string op_id = "custom_op"; + auto func = [](std::vector dimensions, + std::map> _none) { + if (dimensions.size() != 1) + throw std::invalid_argument("Must have a singe dimension"); + if (dimensions[0] != 2) + throw std::invalid_argument("Must have dimension 2"); + auto mat = cudaq::matrix_2(2, 2); + mat[{1, 0}] = 1.0; + mat[{0, 1}] = 1.0; + return mat; + }; + cudaq::matrix_operator::define(op_id, {-1}, func); + cudaq::matrix_operator matrix_op(op_id, {0}); + + auto wrapped_tensor_callback = + cudaq::cudm_helper::_wrap_tensor_callback(matrix_op); + + ASSERT_NE(wrapped_tensor_callback.callback, nullptr); + + // auto op_sum = matrix_op + matrix_op; + + // EXPECT_NO_THROW({ + // auto result = + // helper->convert_to_cudensitymat_operator( + // {}, op_sum, mode_extents); + // ASSERT_NE(result, nullptr); + // cudensitymatDestroyOperator(result); + // }); +} + +// Test for appending a scalar to a term +TEST_F(CuDensityMatHelpersTestFixture, AppendScalarToTerm) { + cudensitymatOperatorTerm_t term; + std::vector mode_extents = {2, 2}; + + HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), mode_extents.data(), + &term)); + + cudaq::scalar_operator scalar_op(2.0); + + EXPECT_NO_THROW(helper->append_scalar_to_term(term, scalar_op)); + + cudensitymatDestroyOperatorTerm(term); +} diff --git a/unittests/dynamics/test_cudm_op_conversion.cpp b/unittests/dynamics/test_cudm_op_conversion.cpp new file mode 100644 index 0000000000..20e8228ac3 --- /dev/null +++ b/unittests/dynamics/test_cudm_op_conversion.cpp @@ -0,0 +1,202 @@ +/******************************************************************************* + * 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_op_conversion.h" +// #include "cudaq/operators.h" +// #include "test_mocks.h" +// #include +// #include +// #include +// #include + +// using namespace cudaq; + +// class CuDmOpConversion : public ::testing::Test { +// protected: +// cudensitymatHandle_t handle; +// std::map dimensions; +// std::shared_ptr schedule; +// std::unique_ptr converter; +// std::vector space_mode_extents; + +// void SetUp() override { +// handle = mock_handle(); +// dimensions = {{0, 2}, {1, 2}}; +// for (const auto &dim : dimensions) { +// space_mode_extents.push_back(dim.second); +// } +// schedule = std::shared_ptr(); +// converter = +// std::make_unique(handle, dimensions, schedule); +// } +// }; + +// TEST_F(CuDmOpConversion, ConstructorValid) { +// EXPECT_NO_THROW(cudm_op_conversion converter(handle, dimensions, +// schedule)); +// } + +// TEST_F(CuDmOpConversion, ConstructorEmptyDimensions) { +// std::map empty_dimensions; +// EXPECT_THROW(cudm_op_conversion converter(handle, empty_dimensions, +// schedule), +// std::invalid_argument); +// } + +// TEST_F(CuDmOpConversion, ConstructorInvalidHandle) { +// cudensitymatHandle_t invalid_handle = nullptr; +// EXPECT_THROW( +// cudm_op_conversion converter(invalid_handle, dimensions, schedule), +// std::runtime_error); +// } + +// TEST_F(CuDmOpConversion, EvaluateScalarConstant) { +// scalar_operator scalar_op(2.5); +// auto result = converter->evaluate(scalar_op); + +// ASSERT_TRUE(std::holds_alternative>(result)); +// EXPECT_EQ(std::get>(result), +// std::complex(2.5, 0.0)); +// } + +// TEST_F(CuDmOpConversion, EvaluateScalarCallback) { +// scalar_operator scalar_op([](std::map>) { +// return std::complex(1.0, -1.0); +// }); +// auto result = converter->evaluate(scalar_op); + +// ASSERT_TRUE( +// std::holds_alternative(result)); +// } + +// TEST_F(CuDmOpConversion, EvaluateMatrixOperator) { +// matrix_operator mat_op = mock_matrix_operator("pauli_x", 0); +// auto result = converter->evaluate(mat_op); + +// ASSERT_TRUE(std::holds_alternative(result)); +// } + +// TEST_F(CuDmOpConversion, EvaluateProductOperator) { +// auto op0 = cudaq::matrix_operator::annihilate(0); +// auto op1 = cudaq::matrix_operator::create(0); +// product_operator product_op = op0 * op1; +// EXPECT_THROW(converter->evaluate(product_op), std::runtime_error); +// } + +// TEST_F(CuDmOpConversion, AddOperators) { +// scalar_operator scalar_op1(2.0); +// scalar_operator scalar_op2(3.0); + +// auto result = converter->add(converter->evaluate(scalar_op1), +// converter->evaluate(scalar_op2)); + +// ASSERT_TRUE(std::holds_alternative>(result)); +// EXPECT_EQ(std::get>(result), +// std::complex(5.0, 0.0)); +// } + +// TEST_F(CuDmOpConversion, AddComplexScalars) { +// std::complex scalar_1(2.0, 1.0); +// std::complex scalar_2(3.0, -1.0); + +// auto result = converter->add(scalar_1, scalar_2); + +// ASSERT_TRUE(std::holds_alternative>(result)); +// EXPECT_EQ(std::get>(result), +// std::complex(5.0, 0.0)); +// } + +// TEST_F(CuDmOpConversion, AddScalarAndOperator) { +// scalar_operator scalar_op(1.0); +// matrix_operator mat_op = mock_matrix_operator("pauli_x", 0); + +// auto scalar_result = converter->evaluate(scalar_op); +// auto op_result = converter->evaluate(mat_op); + +// auto final_result = converter->add(scalar_result, op_result); + +// ASSERT_TRUE(std::holds_alternative(final_result)); +// } + +// TEST_F(CuDmOpConversion, AddMatrixOperators) { +// matrix_operator mat_op1 = mock_matrix_operator("pauli_x", 0); +// matrix_operator mat_op2 = mock_matrix_operator("pauli_y", 0); + +// auto op_result1 = converter->evaluate(mat_op1); +// auto op_result2 = converter->evaluate(mat_op2); + +// auto final_result = converter->add(op_result1, op_result2); + +// ASSERT_TRUE(std::holds_alternative(final_result)); +// } + +// TEST_F(CuDmOpConversion, MultiplyMatrixOperators) { +// matrix_operator mat_op1 = mock_matrix_operator("pauli_x", 0); +// matrix_operator mat_op2 = mock_matrix_operator("pauli_y", 0); + +// auto op_result1 = converter->evaluate(mat_op1); +// auto op_result2 = converter->evaluate(mat_op2); + +// auto final_result = converter->mul(op_result1, op_result2); + +// ASSERT_TRUE(std::holds_alternative(final_result)); +// } + +// TEST_F(CuDmOpConversion, TensorOfMatrixOperators) { +// matrix_operator mat_op1 = mock_matrix_operator("pauli_x", 0); +// matrix_operator mat_op2 = mock_matrix_operator("pauli_y", 0); + +// auto op_result1 = converter->evaluate(mat_op1); +// auto op_result2 = converter->evaluate(mat_op2); + +// auto final_result = converter->tensor(op_result1, op_result2); + +// ASSERT_TRUE(std::holds_alternative(final_result)); +// } + +// TEST_F(CuDmOpConversion, TensorProductOfScalars) { +// auto result = converter->tensor(2.0, 3.0); +// EXPECT_TRUE(std::holds_alternative>(result)); +// std::complex final_result = std::get>(result); +// EXPECT_EQ(final_result.real(), 6); +// EXPECT_EQ(final_result.imag(), 0); +// } + +// TEST_F(CuDmOpConversion, TensorProductScalarAndOperator) { +// cudensitymatOperatorTerm_t op_term; +// HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( +// handle, dimensions.size(), space_mode_extents.data(), &op_term)); + +// auto result = converter->tensor(2.0, op_term); +// EXPECT_TRUE(std::holds_alternative(result)); + +// HANDLE_CUDM_ERROR(cudensitymatDestroyOperatorTerm(op_term)); +// } + +// TEST_F(CuDmOpConversion, MultiplyOperators) { +// auto result = converter->mul(6.0, 3.0); +// EXPECT_TRUE(std::holds_alternative>(result)); +// std::complex final_result = std::get>(result); +// EXPECT_EQ(final_result.real(), 18); +// EXPECT_EQ(final_result.imag(), 0); +// } + +// TEST_F(CuDmOpConversion, MoveSemantics) { +// scalar_operator scalar_op(1.5); +// auto scalar_result = converter->evaluate(scalar_op); + +// EXPECT_TRUE(std::holds_alternative>(scalar_result)); + +// auto moved_result = std::move(scalar_result); + +// EXPECT_TRUE(std::holds_alternative>(moved_result)); +// EXPECT_EQ(std::get>(moved_result), +// std::complex(1.5, 0.0)); + +// EXPECT_TRUE(scalar_result.index() != std::variant_npos); +// } diff --git a/unittests/dynamics/test_cudm_state.cpp b/unittests/dynamics/test_cudm_state.cpp new file mode 100644 index 0000000000..8ac6bffee2 --- /dev/null +++ b/unittests/dynamics/test_cudm_state.cpp @@ -0,0 +1,135 @@ +/******************************************************************************* + * 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: + cudensitymatHandle_t handle; + + void SetUp() override { + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle)); + + // 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 { cudensitymatDestroy(handle); } + + std::vector hilbertSpaceDims; + std::vector> stateVectorData; + std::vector> densityMatrixData; +}; + +TEST_F(CuDensityMatStateTest, InitializeWithStateVector) { + cudm_state state(handle, stateVectorData, hilbertSpaceDims); + + EXPECT_TRUE(state.is_initialized()); + EXPECT_FALSE(state.is_density_matrix()); + EXPECT_NO_THROW(state.dump()); +} + +TEST_F(CuDensityMatStateTest, InitializeWithDensityMatrix) { + cudm_state state(handle, densityMatrixData, 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 = {{1.0, 0.0}, {0.0, 0.0}}; + + EXPECT_THROW(cudm_state state(handle, invalidData, hilbertSpaceDims), + std::invalid_argument); +} + +TEST_F(CuDensityMatStateTest, ToDensityMatrixConversion) { + cudm_state state(handle, stateVectorData, hilbertSpaceDims); + EXPECT_FALSE(state.is_density_matrix()); + + cudm_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_state state(handle, densityMatrixData, hilbertSpaceDims); + + EXPECT_TRUE(state.is_density_matrix()); + EXPECT_THROW(state.to_density_matrix(), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, DestructorCleansUp) { + EXPECT_NO_THROW( + { cudm_state state(handle, stateVectorData, hilbertSpaceDims); }); +} + +TEST_F(CuDensityMatStateTest, InitializeWithEmptyRawData) { + std::vector> emptyData; + + EXPECT_THROW(cudm_state state(handle, emptyData, hilbertSpaceDims), + std::invalid_argument); +} + +TEST_F(CuDensityMatStateTest, ConversionForSingleQubitSystem) { + hilbertSpaceDims = {2}; + stateVectorData = {{1.0, 0.0}, {0.0, 0.0}}; + cudm_state state(handle, stateVectorData, hilbertSpaceDims); + + EXPECT_FALSE(state.is_density_matrix()); + + cudm_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}; + EXPECT_THROW(cudm_state state(handle, stateVectorData, hilbertSpaceDims), + std::invalid_argument); +} + +TEST_F(CuDensityMatStateTest, ValidDensityMatrixState) { + cudm_state state(handle, densityMatrixData, hilbertSpaceDims); + EXPECT_TRUE(state.is_density_matrix()); + EXPECT_TRUE(state.is_initialized()); +} + +TEST_F(CuDensityMatStateTest, DumpWorksForInitializedState) { + cudm_state state(handle, stateVectorData, hilbertSpaceDims); + EXPECT_NO_THROW(state.dump()); +} diff --git a/unittests/dynamics/test_cudm_time_stepper.cpp b/unittests/dynamics/test_cudm_time_stepper.cpp new file mode 100644 index 0000000000..914182466d --- /dev/null +++ b/unittests/dynamics/test_cudm_time_stepper.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 "test_mocks.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace cudaq; + +class CuDensityMatTimeStepperTest : public ::testing::Test { +protected: + cudensitymatHandle_t handle_; + cudensitymatOperator_t liouvillian_; + std::unique_ptr time_stepper_; + std::unique_ptr state_; + + void SetUp() override { + // Create library handle + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); + + // Create a mock Liouvillian + liouvillian_ = mock_liouvillian(handle_); + + // Initialize the time stepper + time_stepper_ = std::make_unique(handle_, liouvillian_); + + state_ = std::make_unique(handle_, mock_initial_state_data(), + mock_hilbert_space_dims()); + + ASSERT_TRUE(state_->is_initialized()); + } + + void TearDown() override { + // Clean up + HANDLE_CUDM_ERROR(cudensitymatDestroyOperator(liouvillian_)); + HANDLE_CUDM_ERROR(cudensitymatDestroy(handle_)); + } +}; + +// Test initialization of cudm_time_stepper +TEST_F(CuDensityMatTimeStepperTest, Initialization) { + ASSERT_NE(time_stepper_, nullptr); + ASSERT_TRUE(state_->is_initialized()); + ASSERT_FALSE(state_->is_density_matrix()); +} + +// Test a single compute step +TEST_F(CuDensityMatTimeStepperTest, ComputeStep) { + ASSERT_TRUE(state_->is_initialized()); + EXPECT_NO_THROW(time_stepper_->compute(*state_, 0.0, 1.0)); + ASSERT_TRUE(state_->is_initialized()); +} + +// Compute step when handle is uninitialized +TEST_F(CuDensityMatTimeStepperTest, ComputeStepUninitializedHandle) { + cudm_time_stepper invalidStepper(nullptr, liouvillian_); + EXPECT_THROW(invalidStepper.compute(*state_, 0.0, 1.0), std::runtime_error); +} + +// Compute step when liouvillian is missing +TEST_F(CuDensityMatTimeStepperTest, ComputeStepNoLiouvillian) { + cudm_time_stepper invalidStepper(handle_, nullptr); + EXPECT_THROW(invalidStepper.compute(*state_, 0.0, 1.0), std::runtime_error); +} + +// Compute step with mismatched dimensions +TEST_F(CuDensityMatTimeStepperTest, ComputeStepMistmatchedDimensions) { + EXPECT_THROW(std::unique_ptr mismatchedState = + std::make_unique(handle_, + mock_initial_state_data(), + std::vector{3, 3}), + std::invalid_argument); +} + +// Compute step with zero step size +TEST_F(CuDensityMatTimeStepperTest, ComputeStepZeroStepSize) { + EXPECT_THROW(time_stepper_->compute(*state_, 0.0, 0.0), std::runtime_error); +} + +// Compute step with large time values +TEST_F(CuDensityMatTimeStepperTest, ComputeStepLargeTimeValues) { + EXPECT_NO_THROW(time_stepper_->compute(*state_, 1e6, 1e3)); +} + +TEST_F(CuDensityMatTimeStepperTest, ComputeStepCheckOutput) { + cudm_helper helper(handle_); + const std::vector> initialState = { + {1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}; + const std::vector dims = {4}; + auto inputState = std::make_unique(handle_, initialState, dims); + auto op = cudaq::matrix_operator::create(0); + auto cudmOp = helper.convert_to_cudensitymat_operator( + {}, op, dims); // Initialize the time stepper + auto time_stepper = std::make_unique(handle_, cudmOp); + auto outputState = time_stepper->compute(*inputState, 0.0, 1.0); + + std::vector> outputStateVec(4); + HANDLE_CUDA_ERROR(cudaMemcpy( + outputStateVec.data(), outputState.get_device_pointer(), + outputStateVec.size() * sizeof(std::complex), cudaMemcpyDefault)); + // Create operator move the state up 1 step. + const std::vector> expectedOutputState = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}; + + for (std::size_t i = 0; i < expectedOutputState.size(); ++i) { + EXPECT_TRUE(std::abs(expectedOutputState[i] - outputStateVec[i]) < 1e-12); + } + HANDLE_CUDM_ERROR(cudensitymatDestroyOperator(cudmOp)); +} diff --git a/unittests/dynamics/test_evolve_single.cpp b/unittests/dynamics/test_evolve_single.cpp new file mode 100644 index 0000000000..d474067320 --- /dev/null +++ b/unittests/dynamics/test_evolve_single.cpp @@ -0,0 +1,114 @@ +// /******************************************************************************* +// * 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 "common/EigenDense.h" +#include + +TEST(EvolveTester, checkSimple) { + const std::map dims = {{0, 2}}; + const std::string op_id = "pauli_x"; + auto func = [](std::vector dimensions, + std::map> _none) { + if (dimensions.size() != 1) + throw std::invalid_argument("Must have a singe dimension"); + if (dimensions[0] != 2) + throw std::invalid_argument("Must have dimension 2"); + auto mat = cudaq::matrix_2(2, 2); + mat[{1, 0}] = 1.0; + mat[{0, 1}] = 1.0; + return mat; + }; + cudaq::matrix_operator::define(op_id, {-1}, func); + auto ham = cudaq::product_operator( + 2.0 * M_PI * 0.1, cudaq::matrix_operator(op_id, {0})); + constexpr int numSteps = 10; + cudaq::Schedule schedule(cudaq::linspace(0.0, 1.0, numSteps)); + + cudaq::matrix_operator::define( + "pauli_z", {-1}, + [](std::vector dimensions, + std::map> _none) { + if (dimensions.size() != 1) + throw std::invalid_argument("Must have a singe dimension"); + if (dimensions[0] != 2) + throw std::invalid_argument("Must have dimension 2"); + auto mat = cudaq::matrix_2(2, 2); + mat[{0, 0}] = 1.0; + mat[{1, 1}] = -1.0; + return mat; + }); + auto pauliZ = cudaq::product_operator( + std::complex{1.0, 0.0}, cudaq::matrix_operator("pauli_z", {0})); + auto initialState = + cudaq::state::from_data(std::vector>{1.0, 0.0}); + auto result = cudaq::evolve_single(ham, dims, schedule, initialState, {}, + {&pauliZ}, true); + EXPECT_TRUE(result.get_expectation_values().has_value()); + EXPECT_EQ(result.get_expectation_values().value().size(), numSteps); + std::vector theoryResults; + for (const auto &t : schedule) { + const double expected = std::cos(2 * 2.0 * M_PI * 0.1 * t); + theoryResults.emplace_back(expected); + } + + int count = 0; + for (auto expVals : result.get_expectation_values().value()) { + EXPECT_EQ(expVals.size(), 1); + EXPECT_NEAR((double)expVals[0], theoryResults[count++], 1e-3); + } +} + +TEST(EvolveTester, checkCompositeSystem) { + constexpr int cavity_levels = 10; + const std::map dims = {{0, 2}, {1, cavity_levels}}; + auto a = cudaq::matrix_operator::annihilate(1); + auto a_dag = cudaq::matrix_operator::create(1); + + auto sm = cudaq::matrix_operator::annihilate(0); + auto sm_dag = cudaq::matrix_operator::create(0); + + auto atom_occ_op = cudaq::matrix_operator::number(0); + auto cavity_occ_op = cudaq::matrix_operator::number(1); + auto hamiltonian = 2 * M_PI * atom_occ_op + 2 * M_PI * cavity_occ_op + + 2 * M_PI * 0.25 * (sm * a_dag + sm_dag * a); + // auto matrix = hamiltonian.to_matrix(dims); + // std::cout << "Matrix:\n" << matrix.dump() << "\n"; + Eigen::Vector2cd qubit_state; + qubit_state << 1.0, 0.0; + Eigen::VectorXcd cavity_state = Eigen::VectorXcd::Zero(cavity_levels); + const int num_photons = 5; + cavity_state[num_photons] = 1.0; + Eigen::VectorXcd initial_state_vec = + Eigen::kroneckerProduct(cavity_state, qubit_state); + std::cout << "Initial state: \n" << initial_state_vec << "\n"; + constexpr int num_steps = 201; + cudaq::Schedule schedule(cudaq::linspace(0.0, 10, num_steps)); + auto initialState = cudaq::state::from_data( + std::make_pair(initial_state_vec.data(), initial_state_vec.size())); + + auto result = cudaq::evolve_single(hamiltonian, dims, schedule, initialState, + {}, {&cavity_occ_op, &atom_occ_op}, true); + EXPECT_TRUE(result.get_expectation_values().has_value()); + EXPECT_EQ(result.get_expectation_values().value().size(), num_steps); + // std::vector theoryResults; + // for (const auto &t : schedule) { + // const double expected = std::cos(2 * 2.0 * M_PI * 0.1 * t); + // theoryResults.emplace_back(expected); + // } + + int count = 0; + for (auto expVals : result.get_expectation_values().value()) { + EXPECT_EQ(expVals.size(), 2); + std::cout << expVals[0] << "\n"; + // EXPECT_NEAR((double)expVals[0], theoryResults[count++], 1e-3); + } +} diff --git a/unittests/dynamics/test_helpers.cpp b/unittests/dynamics/test_helpers.cpp new file mode 100644 index 0000000000..dab8d441f4 --- /dev/null +++ b/unittests/dynamics/test_helpers.cpp @@ -0,0 +1,184 @@ +/******************************************************************************* + * 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::detail; + +TEST(OperatorHelpersTest, AggregateParameters_MultipleMappings) { + std::vector> mappings = { + {{"alpha", "Parameter A"}, {"beta", "Parameter B"}}, + {{"alpha", "Updated Parameter A"}, {"gamma", "New Parameter"}}}; + + auto result = 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 = 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 = parameter_docs("alpha", docstring); + EXPECT_EQ(result, "The first parameter."); + + result = 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 = parameter_docs("gamma", docstring); + EXPECT_EQ(result, ""); +} + +TEST(OperatorHelpersTest, ParameterDocs_EmptyDocString) { + std::string docstring = ""; + auto result = 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 = 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 = 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 = 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(generate_all_states(degrees, dimensions), std::out_of_range); +} + +TEST(OperatorHelpersTest, PermuteMatrix_SingleSwap) { + cudaq::matrix_2 matrix(2, 2); + matrix[{0, 0}] = 1; + matrix[{0, 1}] = 2; + matrix[{1, 0}] = 3; + matrix[{1, 1}] = 4; + + // Swap rows and columns + std::vector permutation = {1, 0}; + + auto permuted_matrix = permute_matrix(matrix, permutation); + + cudaq::matrix_2 expected(2, 2); + matrix[{0, 0}] = 4; + matrix[{0, 1}] = 3; + matrix[{1, 0}] = 2; + matrix[{1, 1}] = 1; + + EXPECT_EQ(permuted_matrix, expected); +} + +TEST(OperatorHelpersTest, PermuteMatrix_IdentityPermutation) { + cudaq::matrix_2 matrix(3, 3); + matrix[{0, 0}] = 1; + matrix[{0, 1}] = 2; + matrix[{0, 2}] = 3; + matrix[{1, 0}] = 4; + matrix[{1, 1}] = 5; + matrix[{1, 2}] = 6; + matrix[{2, 0}] = 7; + matrix[{2, 1}] = 8; + matrix[{2, 2}] = 9; + + // No change + std::vector permutation = {0, 1, 2}; + + auto permuted_matrix = permute_matrix(matrix, permutation); + + EXPECT_EQ(permuted_matrix, matrix); +} + +TEST(OperatorHelpersTest, CanonicalizeDegrees_SortedDescending) { + std::vector degrees = {3, 1, 2}; + auto sorted = 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 = canonicalize_degrees(degrees); + EXPECT_EQ(sorted, (std::vector{5, 4, 3, 2, 1})); +} + +TEST(OperatorHelpersTest, CanonicalizeDegrees_EmptyList) { + std::vector degrees; + auto sorted = 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] = 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(args_from_kwargs(kwargs, required_args, kwonly_args), + std::invalid_argument); +} diff --git a/unittests/dynamics/test_mocks.h b/unittests/dynamics/test_mocks.h new file mode 100644 index 0000000000..dcb7a20bf0 --- /dev/null +++ b/unittests/dynamics/test_mocks.h @@ -0,0 +1,85 @@ +/****************************************************************-*- 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 + +// Mock matrix_operator +inline cudaq::matrix_operator mock_matrix_operator(const std::string &op_id, + int qubit_index) { + try { + auto callback = [](std::vector dimensions, + std::map>) { + if (dimensions.size() != 1 || dimensions[0] != 2) { + throw std::invalid_argument("Invalid dimensions for operator."); + } + + cudaq::matrix_2 matrix(2, 2); + matrix[{0, 1}] = 1.0; + matrix[{1, 0}] = 1.0; + return matrix; + }; + + cudaq::matrix_operator::define(op_id, {-1}, callback); + } catch (...) { + } + + return cudaq::matrix_operator(op_id, {qubit_index}); +} + +// Mock cudensitymatHandle_t +inline cudensitymatHandle_t mock_handle() { + cudensitymatHandle_t handle; + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle)); + return handle; +} + +// Mock Liouvillian operator creation +inline cudensitymatOperator_t mock_liouvillian(cudensitymatHandle_t handle) { + cudensitymatOperator_t liouvillian = nullptr; + std::vector dimensions = {2, 2}; + HANDLE_CUDM_ERROR(cudensitymatCreateOperator( + handle, static_cast(dimensions.size()), dimensions.data(), + &liouvillian)); + + if (!liouvillian) { + throw std::runtime_error("Failed to create mock Liouvillian!"); + } + + return liouvillian; +} + +// Mock Hilbert space dimensions +inline std::vector> mock_initial_state_data() { + std::vector> data = { + {1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}; + + if (data.size() != 4) { + throw std::runtime_error("Mock initial state data has incorrect size!"); + } + + return data; +} + +// Mock initial raw state data +inline std::vector mock_hilbert_space_dims() { + std::vector dims = {2, 2}; + + if (dims.empty()) { + throw std::runtime_error("Mock Hilbert space dimensions are empty!"); + } + + return dims; +} diff --git a/unittests/dynamics/test_runge_kutta_integrator.cpp b/unittests/dynamics/test_runge_kutta_integrator.cpp new file mode 100644 index 0000000000..d596bf507f --- /dev/null +++ b/unittests/dynamics/test_runge_kutta_integrator.cpp @@ -0,0 +1,211 @@ +// /******************************************************************************* +// * 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 "test_mocks.h" +#include +#include +#include + +using namespace cudaq; + +class RungeKuttaIntegratorTest : public ::testing::Test { +protected: + cudensitymatHandle_t handle_; + cudensitymatOperator_t liouvillian_; + std::shared_ptr time_stepper_; + std::unique_ptr integrator_; + std::unique_ptr state_; + + void SetUp() override { + // Create library handle + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); + + // Create a mock Liouvillian + liouvillian_ = mock_liouvillian(handle_); + + // Initialize the time stepper + time_stepper_ = std::make_shared(handle_, liouvillian_); + ASSERT_NE(time_stepper_, nullptr); + + // Create initial state + state_ = std::make_unique(handle_, mock_initial_state_data(), + mock_hilbert_space_dims()); + ASSERT_NE(state_, nullptr); + ASSERT_TRUE(state_->is_initialized()); + + double t0 = 0.0; + // Initialize the integrator (using substeps = 4, for Runge-Kutta method) + ASSERT_NO_THROW(integrator_ = std::make_unique( + std::move(*state_), t0, time_stepper_, 4)); + ASSERT_NE(integrator_, nullptr); + } + + void TearDown() override { + // Clean up resources + HANDLE_CUDM_ERROR(cudensitymatDestroyOperator(liouvillian_)); + HANDLE_CUDM_ERROR(cudensitymatDestroy(handle_)); + } +}; + +// Test Initialization +TEST_F(RungeKuttaIntegratorTest, Initialization) { + ASSERT_NE(integrator_, nullptr); +} + +// Integration with Euler Method (substeps = 1) +TEST_F(RungeKuttaIntegratorTest, EulerIntegration) { + auto eulerIntegrator = std::make_unique( + cudm_state(handle_, mock_initial_state_data(), mock_hilbert_space_dims()), + 0.0, time_stepper_, 1); + eulerIntegrator->set_option("dt", 0.1); + EXPECT_NO_THROW(eulerIntegrator->integrate(1.0)); +} + +// Integration with Midpoint Rule (substeps = 2) +TEST_F(RungeKuttaIntegratorTest, MidpointIntegration) { + auto midpointIntegrator = std::make_unique( + cudm_state(handle_, mock_initial_state_data(), mock_hilbert_space_dims()), + 0.0, time_stepper_, 2); + midpointIntegrator->set_option("dt", 0.1); + EXPECT_NO_THROW(midpointIntegrator->integrate(1.0)); +} + +// Integration with Runge-Kutta 4 (substeps = 4, which is the default value) +TEST_F(RungeKuttaIntegratorTest, RungeKutta4Integration) { + integrator_->set_option("dt", 0.1); + EXPECT_NO_THROW(integrator_->integrate(1.0)); +} + +// Basic Integration Test +TEST_F(RungeKuttaIntegratorTest, BasicIntegration) { + auto [t_before, state_before] = integrator_->get_state(); + integrator_->set_option("dt", 0.1); + + EXPECT_NO_THROW(integrator_->integrate(1.0)); + + auto [t_after, state_after] = integrator_->get_state(); + EXPECT_GT(t_after, t_before); +} + +// Multiple Integration Steps +TEST_F(RungeKuttaIntegratorTest, MultipleIntegrationSteps) { + integrator_->set_option("dt", 0.1); + integrator_->integrate(0.5); + auto [t_mid, _] = integrator_->get_state(); + + EXPECT_EQ(t_mid, 0.5); + + integrator_->integrate(1.0); + auto [t_final, __] = integrator_->get_state(); + + EXPECT_EQ(t_final, 1.0); +} + +// Missing Time Step (dt) +TEST_F(RungeKuttaIntegratorTest, MissingTimeStepOption) { + auto integrator_missing_dt = std::make_unique( + cudm_state(handle_, mock_initial_state_data(), mock_hilbert_space_dims()), + 0.0, time_stepper_, 2); + + EXPECT_THROW(integrator_missing_dt->integrate(1.0), std::invalid_argument); +} + +// Invalid Time Step (dt <= 0) +TEST_F(RungeKuttaIntegratorTest, InvalidTimeStepSize) { + integrator_->set_option("dt", -0.1); + EXPECT_THROW(integrator_->integrate(1.0), std::invalid_argument); +} + +// Zero Integration Time +TEST_F(RungeKuttaIntegratorTest, ZeroIntegrationTime) { + auto [t_before, state_before] = integrator_->get_state(); + integrator_->set_option("dt", 0.1); + + EXPECT_NO_THROW(integrator_->integrate(0.0)); + + auto [t_after, state_after] = integrator_->get_state(); + EXPECT_EQ(t_before, t_after); +} + +// Large Time Step +TEST_F(RungeKuttaIntegratorTest, LargeTimeStep) { + integrator_->set_option("dt", 100); + EXPECT_NO_THROW(integrator_->integrate(0.0)); +} + +// Invalid Substeps +TEST_F(RungeKuttaIntegratorTest, InvalidSubsteps) { + EXPECT_THROW(std::make_unique( + cudm_state(handle_, mock_initial_state_data(), + mock_hilbert_space_dims()), + 0.0, time_stepper_, 3), + std::invalid_argument); +} + +TEST_F(RungeKuttaIntegratorTest, CheckEvolve) { + cudm_helper helper(handle_); + const std::vector> initialState = {{1.0, 0.0}, + {0.0, 0.0}}; + const std::vector dims = {2}; + const std::string op_id = "pauli_x"; + auto func = [](std::vector dimensions, + std::map> _none) { + if (dimensions.size() != 1) + throw std::invalid_argument("Must have a singe dimension"); + if (dimensions[0] != 2) + throw std::invalid_argument("Must have dimension 2"); + auto mat = matrix_2(2, 2); + mat[{1, 0}] = 1.0; + mat[{0, 1}] = 1.0; + return mat; + }; + cudaq::matrix_operator::define(op_id, {-1}, func); + + for (int integratorOrder : {1, 2, 4}) { + std::cout << "Test RK order " << integratorOrder << "\n"; + auto op = cudaq::product_operator( + std::complex{0.0, -1.0} * 2.0 * M_PI * 0.1, + cudaq::matrix_operator(op_id, {0})); + auto cudmOp = + helper.convert_to_cudensitymat_operator({}, op, + dims); + auto time_stepper = std::make_shared(handle_, cudmOp); + + auto eulerIntegrator = std::make_unique( + cudm_state(handle_, initialState, dims), 0.0, time_stepper, + integratorOrder); + eulerIntegrator->set_option("dt", 0.001); + + constexpr std::size_t numDataPoints = 10; + double t = 0.0; + std::vector> outputStateVec(2); + for (std::size_t i = 1; i < numDataPoints; ++i) { + eulerIntegrator->integrate(i); + auto [t, state] = eulerIntegrator->get_state(); + // std::cout << "Time = " << t << "\n"; + // state.dumpDeviceData(); + HANDLE_CUDA_ERROR( + cudaMemcpy(outputStateVec.data(), state.get_device_pointer(), + outputStateVec.size() * sizeof(std::complex), + cudaMemcpyDefault)); + // Check state vector norm + EXPECT_NEAR(std::norm(outputStateVec[0]) + std::norm(outputStateVec[1]), + 1.0, 1e-2); + const double expValZ = + std::norm(outputStateVec[0]) - std::norm(outputStateVec[1]); + // Analytical results + EXPECT_NEAR(outputStateVec[0].real(), std::cos(2.0 * M_PI * 0.1 * t), + 1e-2); + } + + HANDLE_CUDM_ERROR(cudensitymatDestroyOperator(cudmOp)); + } + + // Add test to test tensor_callback +} diff --git a/unittests/utils/Tensor.cpp b/unittests/utils/Tensor.cpp index feb7b05970..e996ee957a 100644 --- a/unittests/utils/Tensor.cpp +++ b/unittests/utils/Tensor.cpp @@ -162,3 +162,32 @@ TEST(Tensor, kroneckerOnList) { "{ { (3,3) (6,6) }\n { (4,4) (8,8) }\n { (5,5) (10,10) }\n }"); } } + +TEST(Tensor, exponential) { + { + cudaq::matrix_2 me({1., 1., 0.5, 0.0}, {2, 2}); + cudaq::matrix_2 mf({1., 0., 1., .5, .7, 0., 1., 0., 2.}, {3, 3}); + cudaq::matrix_2 mg( + {1., 0., .4, .6, .7, .8, .9, 0., .3, .1, .2, 1., 0., 0.5, 0.2, .5}, + {4, 4}); + + auto me_exp = me.exponential(); + auto mf_exp = mf.exponential(); + auto mg_exp = mg.exponential(); + + EXPECT_EQ( + me_exp.dump(), + "{ { (3.23795,0) (1.86268,0) }\n { (0.93134,0) (1.37527,0) }\n }"); + + EXPECT_EQ( + mf_exp.dump(), + "{ { (4.84921,0) (0,0) (5.4755,0) }\n { (1.46673,0) (2.01375,0) " + "(0.977708,0) }\n { (5.4755,0) (0,0) (10.3247,0) }\n }"); + + EXPECT_EQ(mg_exp.dump(), + "{ { (2.9751,0) (0.447969,0) (1.01977,0) (1.75551,0) }\n { " + "(2.10247,0) (2.55646,0) (1.97654,0) (1.39927,0) }\n { " + "(0.800451,0) (0.648569,0) (1.69099,0) (1.76597,0) }\n { " + "(0.498881,0) (1.05119,0) (0.753502,0) (2.03447,0) }\n }"); + } +}