From f77475084f93453ed687c407299b70a8fbcee858 Mon Sep 17 00:00:00 2001 From: "A.M. Santana" <39563805+anthony-santana@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:37:13 -0500 Subject: [PATCH 01/40] Merge in dynamics work and squash commit history (#12) * add skeleton of new API functions without connecting to docs yet Signed-off-by: A.M. Santana * updates Signed-off-by: A.M. Santana * boilerplate Signed-off-by: A.M. Santana * clean up linking bug Signed-off-by: A.M. Santana * adding schedule implementation * fixing typo * small updates Signed-off-by: A.M. Santana * Cleaning up docs preview for PR #6. * push initial tests that show memory leak in current translation to complex matrix Signed-off-by: A.M. Santana * completely work around eigen in default elementary ops Signed-off-by: A.M. Santana * storing changes Signed-off-by: A.M. Santana * start to build out callback function class Signed-off-by: A.M. Santana * working function wrapper implementation Signed-off-by: A.M. Santana * implement complex matrix equality operator Signed-off-by: A.M. Santana * push with broken to matrix overload Signed-off-by: A.M. Santana * fix scoping issue found in to matrix Signed-off-by: A.M. Santana * fill out unit tests for to matrix overload Signed-off-by: A.M. Santana * add skeleton of new API functions without connecting to docs yet Signed-off-by: A.M. Santana * updates Signed-off-by: A.M. Santana * boilerplate Signed-off-by: A.M. Santana * clean up linking bug Signed-off-by: A.M. Santana * initial scalar value support and tests Signed-off-by: A.M. Santana * update to_value to evaluate to amtch python api Signed-off-by: A.M. Santana * adding kwargs capability in C++ by using std::variant and std::bind * renaming VariantArg to NumericType to match Python * adding a std::variant returntype nad adjusting the test accordingly * Cleaning up docs preview for PR #7. * commit first draft of arithmetic against complex doubles Signed-off-by: A.M. Santana * little build errors Signed-off-by: A.M. Santana * call generator directly instead of evaluate in operator overloads Signed-off-by: A.M. Santana * remove old constructor Signed-off-by: A.M. Santana * copy constructor Signed-off-by: A.M. Santana * push partially working arithmetic operations Signed-off-by: A.M. Santana * comment back in tests Signed-off-by: A.M. Santana * remove constructor that takes removed parameters member Signed-off-by: A.M. Santana * still having memory issues Signed-off-by: A.M. Santana * implement and test remaining pre defined elementary ops except displace and squeeze. implement complex matrix exponential Signed-off-by: A.M. Santana * fix for segfault in copy constructor but still have memory issues from arithmetic Signed-off-by: A.M. Santana * temp patch to get scalar arithmetic against complex doubles working. against doubles wrapped in functions still broken Signed-off-by: A.M. Santana * potential fix for callback function going out of scope Signed-off-by: A.M. Santana * confirm fix for scalar ops from functions and reenable tests Signed-off-by: A.M. Santana * begin to support scalar against scalar ops and simplify other arithmetic definitions with macros Signed-off-by: A.M. Santana * support for remaining arithmetic against other scalar ops except for assignment operators Signed-off-by: A.M. Santana * add checks to ensure local variables are picked up fine in generator functions Signed-off-by: A.M. Santana * clean up test file Signed-off-by: A.M. Santana * full refactor to take a [arameter map with elementary operator implementation back under construction Signed-off-by: A.M. Santana * fix elementary ops Signed-off-by: A.M. Santana * start to manually merge in operator sum changes Signed-off-by: A.M. Santana * camel case some things and underscore some others Signed-off-by: A.M. Santana * fix build errors from header file Signed-off-by: A.M. Santana * refactor implementation file names, delete unused dynamics.h, refactor unittests with new dynamics folder Signed-off-by: A.M. Santana * storing large set of changes to dynamics folder structure and implementing more arithmetic Signed-off-by: A.M. Santana * add cudaq tensor type Signed-off-by: Alex McCaskey * add uint8 tensor to python api Signed-off-by: Alex McCaskey * Update unittests/utils/tensor_tester.cpp Signed-off-by: Eric Schweitz * Update unittests/utils/tensor_tester.cpp Signed-off-by: Eric Schweitz * Update unittests/utils/tensor_tester.cpp Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/details/impls/xtensor_impl.cpp Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/details/impls/xtensor_impl.cpp Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/details/impls/xtensor_impl.cpp Signed-off-by: Eric Schweitz * Update python/runtime/utils/py_tensor.cpp Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/extension_point.h Co-authored-by: Ben Howe <141149032+bmhowe23@users.noreply.github.com> Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/extension_point.h Co-authored-by: Ben Howe <141149032+bmhowe23@users.noreply.github.com> Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/tensor.h Co-authored-by: Ben Howe <141149032+bmhowe23@users.noreply.github.com> Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/tensor.h Co-authored-by: Ben Howe <141149032+bmhowe23@users.noreply.github.com> Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/details/impls/xtensor_impl.cpp Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/details/tensor_impl.h Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/details/tensor_impl.h Signed-off-by: Eric Schweitz * Update runtime/cudaq/utils/details/tensor_impl.h Signed-off-by: Eric Schweitz * Spelling fixes. Signed-off-by: Eric Schweitz * push current state Signed-off-by: A.M. Santana * Format Signed-off-by: Anna Gringauze * Format Signed-off-by: Anna Gringauze * Drop debug code. Signed-off-by: Eric Schweitz * Cleanup tensor types a tad. Signed-off-by: Eric Schweitz * Fix spelling Signed-off-by: Anna Gringauze * Fix spelling and format python code Signed-off-by: Anna Gringauze * Sort spelling allowlist Signed-off-by: Anna Gringauze * Cleaning up docs preview for PR #9. * more arithmetic support Signed-off-by: A.M. Santana * Update spelling and formatting Signed-off-by: Anna Gringauze * more implementation and more tests Signed-off-by: A.M. Santana * Update spelling and formatting Signed-off-by: Anna Gringauze * This is an attempt to sort out the different ownership models of tensor data. The python stub for "take" needs to be implemented however. The semantics are: - copy : the tensor object makes a copy of the data and owns the copy. i.e. there is a unique_ptr. - take : the tensor object steals a copy of the data from the caller. In order for this case to make sense, we want the caller to guarantee that the data is unique before we steal it. This can be done by forcing the client code to wrap the tensor data in a unique_ptr *before* the take call. - borrow : In this case, the tensor object has no ownership of the tensor data and just naively assumes the client will manage the data correctly. In this case, a raw pointer to the client's data is used. Signed-off-by: Eric Schweitz * Update the missing py_tensor code. Signed-off-by: Eric Schweitz * Add handling for empty shape case. Signed-off-by: Eric Schweitz * Add more python tests Signed-off-by: Anna Gringauze * Add a take() with move semantics. Signed-off-by: Eric Schweitz * fix product operator constructor issue and tests Signed-off-by: A.M. Santana * fix more test more Signed-off-by: A.M. Santana * cover all arithmetic Signed-off-by: A.M. Santana * update before merging in tensor pr Signed-off-by: A.M. Santana * Fix __init__ argument order so tests pass. The shape must come first. Signed-off-by: Eric Schweitz * Remove StateTensor. (Not used.) Signed-off-by: Eric Schweitz * Add more comments on how to use this stuff. Signed-off-by: Eric Schweitz * Added python tests and fixed some issues * Merge with tensor Signed-off-by: Anna Gringauze * DCO Remediation Commit for Anna Gringauze I, Anna Gringauze , hereby add my Signed-off-by to this commit: e78def4e1370e18f497ca891884d69e9f4421f3b Signed-off-by: Anna Gringauze * Add some boilerplate for tensor operators. Signed-off-by: Eric Schweitz * Make the compiler work a bit harder. Signed-off-by: Eric Schweitz * Add move constructor. Signed-off-by: Eric Schweitz * Make sure the return values for operators work as expected. Signed-off-by: Eric Schweitz * clang-format Signed-off-by: Eric Schweitz * Add override to dtor. Signed-off-by: Eric Schweitz * Add forward decls. Signed-off-by: Eric Schweitz * More fussy templates. Signed-off-by: Eric Schweitz * Workaround warnings from g++. Signed-off-by: Eric Schweitz * Fix typos. Signed-off-by: Eric Schweitz * Support copy semantics for Numpy 2.0 * DCO Remediation Commit for Anna Gringauze I, Anna Gringauze , hereby add my Signed-off-by to this commit: 85fae3771663c4c2e8659fa5c2110ef87d362efd Signed-off-by: Anna Gringauze * Try fixing doc gen and c++ compilation errors Signed-off-by: Anna Gringauze * Add compilation test for nvcc Signed-off-by: Anna Gringauze * Remove temp file Signed-off-by: Anna Gringauze * store changes Signed-off-by: A.M. Santana * first pass of updating return types to cudaq tensor Signed-off-by: A.M. Santana * store working version with tests before rebase Signed-off-by: A.M. Santana * fix improper merge resolution issues Signed-off-by: A.M. Santana * more issues with resolving merge Signed-off-by: A.M. Santana * first pass of translation to matrix_2 with build errors fixed. now double checking tests Signed-off-by: A.M. Santana * fix remaining artifacts from rebase Signed-off-by: A.M. Santana * fix matrix checks for simple elementary op unit tests Signed-off-by: A.M. Santana * first pass of implementing deeper matrix checks in tests. Have two files left to finish Signed-off-by: A.M. Santana * fix copyright headers Signed-off-by: A.M. Santana * minor change to check verified commit Signed-off-by: A.M. Santana --------- Signed-off-by: A.M. Santana Signed-off-by: Alex McCaskey Signed-off-by: Eric Schweitz Signed-off-by: Anna Gringauze Co-authored-by: Sachin Pisal Co-authored-by: cuda-quantum-bot Co-authored-by: Alex McCaskey Co-authored-by: Eric Schweitz Co-authored-by: Ben Howe <141149032+bmhowe23@users.noreply.github.com> Co-authored-by: Anna Gringauze --- docs/sphinx/api/languages/cpp_api.rst | 47 ++ runtime/cudaq/CMakeLists.txt | 5 +- runtime/cudaq/definition.h | 136 ++++ runtime/cudaq/dynamics/CMakeLists.txt | 37 ++ runtime/cudaq/dynamics/definition.cpp | 37 ++ .../cudaq/dynamics/elementary_operators.cpp | 469 ++++++++++++++ runtime/cudaq/dynamics/operator_sum.cpp | 368 +++++++++++ runtime/cudaq/dynamics/product_operators.cpp | 271 ++++++++ runtime/cudaq/dynamics/scalar_operators.cpp | 275 ++++++++ runtime/cudaq/dynamics/schedule.cpp | 81 +++ runtime/cudaq/operator_utils.h | 40 ++ runtime/cudaq/operators.h | 464 ++++++++++++++ runtime/cudaq/schedule.h | 105 +++ runtime/cudaq/utils/tensor.h | 3 + unittests/CMakeLists.txt | 26 + .../dynamics/elementary_ops_arithmetic.cpp | 604 ++++++++++++++++++ unittests/dynamics/elementary_ops_simple.cpp | 208 ++++++ unittests/dynamics/operator_sum.cpp | 572 +++++++++++++++++ .../dynamics/product_operators_arithmetic.cpp | 591 +++++++++++++++++ unittests/dynamics/scalar_ops_arithmetic.cpp | 505 +++++++++++++++ unittests/dynamics/scalar_ops_simple.cpp | 119 ++++ 21 files changed, 4961 insertions(+), 2 deletions(-) create mode 100644 runtime/cudaq/definition.h create mode 100644 runtime/cudaq/dynamics/CMakeLists.txt create mode 100644 runtime/cudaq/dynamics/definition.cpp create mode 100644 runtime/cudaq/dynamics/elementary_operators.cpp create mode 100644 runtime/cudaq/dynamics/operator_sum.cpp create mode 100644 runtime/cudaq/dynamics/product_operators.cpp create mode 100644 runtime/cudaq/dynamics/scalar_operators.cpp create mode 100644 runtime/cudaq/dynamics/schedule.cpp create mode 100644 runtime/cudaq/operator_utils.h create mode 100644 runtime/cudaq/operators.h create mode 100644 runtime/cudaq/schedule.h create mode 100644 unittests/dynamics/elementary_ops_arithmetic.cpp create mode 100644 unittests/dynamics/elementary_ops_simple.cpp create mode 100644 unittests/dynamics/operator_sum.cpp create mode 100644 unittests/dynamics/product_operators_arithmetic.cpp create mode 100644 unittests/dynamics/scalar_ops_arithmetic.cpp create mode 100644 unittests/dynamics/scalar_ops_simple.cpp diff --git a/docs/sphinx/api/languages/cpp_api.rst b/docs/sphinx/api/languages/cpp_api.rst index 34487dbefb..b4c12fac2b 100644 --- a/docs/sphinx/api/languages/cpp_api.rst +++ b/docs/sphinx/api/languages/cpp_api.rst @@ -223,6 +223,53 @@ Utilities .. doxygentypedef:: cudaq::real .. doxygenfunction:: cudaq::range(std::size_t) + +Dynamics +========= + +.. .. doxygenclass:: cudaq::EvolveResult + :members: + +.. .. doxygenclass:: cudaq::AsyncEvolveResult + :members: + +.. doxygenclass:: cudaq::operator_sum + :members: + +.. doxygenclass:: cudaq::product_operator + :members: + +.. doxygenclass:: cudaq::scalar_operator + :members: + +.. doxygenclass:: cudaq::elementary_operator + :members: + +.. doxygenclass:: cudaq::OperatorArithmetics + :members: + +.. doxygenclass:: cudaq::MatrixArithmetics + :members: + +.. doxygenclass:: cudaq::Schedule + :members: + +.. doxygenclass:: cudaq::operators + :members: + +.. doxygenclass:: cudaq::pauli + :members: + +.. .. doxygenfunction:: cudaq::evolve(Operator hamiltonian, std::map dimensions, Schedule schedule, bool store_intermediate_states) + +.. .. doxygenfunction:: cudaq::evolve(Operator hamiltonian, std::map dimensions, Schedule schedule, std::vector collapse_operators, std::vector observables, bool store_intermediate_states) + +.. .. doxygenfunction:: cudaq::evolve(Operator hamiltonian, std::map dimensions, Schedule schedule, state initial_state, std::vector collapse_operators, std::vector observables, bool store_intermediate_states) + +.. .. doxygenfunction:: cudaq::evolve(Operator hamiltonian, std::map dimensions, Schedule schedule, std::vector initial_states, std::vector collapse_operators, std::vector observables, bool store_intermediate_states) + +.. .. doxygenfunction:: cudaq::evolve_async + Namespaces =========== diff --git a/runtime/cudaq/CMakeLists.txt b/runtime/cudaq/CMakeLists.txt index a4eac4f89d..d2f23bd0f9 100644 --- a/runtime/cudaq/CMakeLists.txt +++ b/runtime/cudaq/CMakeLists.txt @@ -40,7 +40,7 @@ if (CUDA_FOUND) PRIVATE .) target_link_libraries(${LIBRARY_NAME} - PUBLIC dl cudaq-spin cudaq-common cudaq-nlopt cudaq-ensmallen + PUBLIC dl cudaq-spin cudaq-operators cudaq-common cudaq-nlopt cudaq-ensmallen PRIVATE nvqir fmt::fmt-header-only CUDA::cudart_static) target_compile_definitions(${LIBRARY_NAME} PRIVATE CUDAQ_HAS_CUDA) @@ -52,7 +52,7 @@ else() PRIVATE .) target_link_libraries(${LIBRARY_NAME} - PUBLIC dl cudaq-spin cudaq-common cudaq-nlopt cudaq-ensmallen + PUBLIC dl cudaq-spin cudaq-operators cudaq-common cudaq-nlopt cudaq-ensmallen PRIVATE nvqir fmt::fmt-header-only) endif() @@ -61,6 +61,7 @@ add_subdirectory(algorithms) add_subdirectory(platform) add_subdirectory(builder) add_subdirectory(domains) +add_subdirectory(dynamics) install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-targets DESTINATION lib) diff --git a/runtime/cudaq/definition.h b/runtime/cudaq/definition.h new file mode 100644 index 0000000000..bdf5af8ab5 --- /dev/null +++ b/runtime/cudaq/definition.h @@ -0,0 +1,136 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/qis/state.h" +#include "cudaq/utils/tensor.h" + +#include +#include +#include +#include +#include +#include + +namespace cudaq { + +// Limit the signature of the users callback function to accept a vector of ints +// for the degree of freedom dimensions, and a vector of complex doubles for the +// concrete parameter values. +using Func = std::function, std::map>)>; + +class CallbackFunction { +private: + // The user provided callback function that takes the degrees of + // freedom and a vector of complex parameters. + Func _callback_func; + +public: + CallbackFunction() = default; + + template + CallbackFunction(Callable &&callable) { + static_assert( + std::is_invocable_r_v, + std::map>>, + "Invalid callback function. Must have signature " + "matrix_2(" + "std::map, " + "std::map>)"); + _callback_func = std::forward(callable); + } + + // Copy constructor. + CallbackFunction(CallbackFunction &other) { + _callback_func = other._callback_func; + } + + CallbackFunction(const CallbackFunction &other) { + _callback_func = other._callback_func; + } + + matrix_2 + operator()(std::map degrees, + std::map> parameters) const { + return _callback_func(std::move(degrees), std::move(parameters)); + } +}; + +using ScalarFunc = std::function( + std::map>)>; + +// A scalar callback function does not need to accept the dimensions, +// therefore we will use a different function type for this specific class. +class ScalarCallbackFunction : CallbackFunction { +private: + // The user provided callback function that takes a vector of parameters. + ScalarFunc _callback_func; + +public: + ScalarCallbackFunction() = default; + + template + ScalarCallbackFunction(Callable &&callable) { + static_assert( + std::is_invocable_r_v, Callable, + std::map>>, + "Invalid callback function. Must have signature std::complex(" + "std::map>)"); + _callback_func = std::forward(callable); + } + + // Copy constructor. + ScalarCallbackFunction(ScalarCallbackFunction &other) { + _callback_func = other._callback_func; + } + + ScalarCallbackFunction(const ScalarCallbackFunction &other) { + _callback_func = other._callback_func; + } + + bool operator!() { return (!_callback_func); } + + std::complex + operator()(std::map> parameters) const { + return _callback_func(std::move(parameters)); + } +}; + +/// @brief Object used to give an error if a Definition of an elementary +/// or scalar operator is instantiated by other means than the `define` +/// class method. +class Definition { +public: + std::string id; + + // The user-provided generator function should take a variable number of + // complex doubles for the parameters. It should return a + // `cudaq::tensor` type representing the operator + // matrix. + CallbackFunction generator; + + // Constructor. + Definition(); + + // Destructor. + ~Definition(); + + void create_definition(const std::string &operator_id, + std::map expected_dimensions, + CallbackFunction &&create); + + // To call the generator function + matrix_2 generate_matrix( + const std::map °rees, + const std::map> ¶meters) const; + +private: + // Member variables + std::map m_expected_dimensions; +}; +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/CMakeLists.txt b/runtime/cudaq/dynamics/CMakeLists.txt new file mode 100644 index 0000000000..9709cd9a71 --- /dev/null +++ b/runtime/cudaq/dynamics/CMakeLists.txt @@ -0,0 +1,37 @@ +# ============================================================================ # +# 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. # +# ============================================================================ # + +set(LIBRARY_NAME cudaq-operators) +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") +set(INTERFACE_POSITION_INDEPENDENT_CODE ON) + +set(CUDAQ_OPS_SRC + scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp +) + +add_library(${LIBRARY_NAME} SHARED ${CUDAQ_OPS_SRC}) +set_property(GLOBAL APPEND PROPERTY CUDAQ_RUNTIME_LIBS ${LIBRARY_NAME}) +target_include_directories(${LIBRARY_NAME} + PUBLIC + $ + $ + $ + PRIVATE .) + +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/definition.cpp b/runtime/cudaq/dynamics/definition.cpp new file mode 100644 index 0000000000..cc357fbeab --- /dev/null +++ b/runtime/cudaq/dynamics/definition.cpp @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/definition.h" +#include "cudaq/qis/state.h" + +#include +#include +#include +#include + +namespace cudaq { + +Definition::Definition() = default; + +// Convenience setter +void Definition::create_definition(const std::string &operator_id, + std::map expected_dimensions, + CallbackFunction &&create) { + id = operator_id; + generator = std::move(create); + m_expected_dimensions = std::move(expected_dimensions); +} + +matrix_2 Definition::generate_matrix( + const std::map °rees, + const std::map> ¶meters) const { + return generator(degrees, parameters); +} + +Definition::~Definition() = default; +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/elementary_operators.cpp b/runtime/cudaq/dynamics/elementary_operators.cpp new file mode 100644 index 0000000000..137dde02cc --- /dev/null +++ b/runtime/cudaq/dynamics/elementary_operators.cpp @@ -0,0 +1,469 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "common/EigenDense.h" +#include "cudaq/operators.h" + +#include +#include + +namespace cudaq { + +/// Elementary Operator constructor. +elementary_operator::elementary_operator(std::string operator_id, + std::vector degrees) + : id(operator_id), degrees(degrees) {} +elementary_operator::elementary_operator(const elementary_operator &other) + : m_ops(other.m_ops), expected_dimensions(other.expected_dimensions), + degrees(other.degrees), id(other.id) {} +elementary_operator::elementary_operator(elementary_operator &other) + : m_ops(other.m_ops), expected_dimensions(other.expected_dimensions), + degrees(other.degrees), id(other.id) {} + +elementary_operator elementary_operator::identity(int degree) { + std::string op_id = "identity"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + int degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + + // Build up the identity matrix. + for (std::size_t i = 0; i < dimension; i++) { + mat[{i, i}] = 1.0 + 0.0 * 'j'; + } + + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::zero(int degree) { + std::string op_id = "zero"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + // Need to set the degree via the op itself because the + // argument to the outer function goes out of scope when + // the user invokes this later on via, e.g, `to_matrix()`. + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::annihilate(int degree) { + std::string op_id = "annihilate"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + for (std::size_t i = 0; i + 1 < dimension; i++) { + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::create(int degree) { + std::string op_id = "create"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + for (std::size_t i = 0; i + 1 < dimension; i++) { + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::position(int degree) { + std::string op_id = "position"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + // position = 0.5 * (create + annihilate) + for (std::size_t i = 0; i + 1 < dimension; i++) { + mat[{i + 1, i}] = + 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::momentum(int degree) { + std::string op_id = "momentum"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + // momentum = 0.5j * (create - annihilate) + for (std::size_t i = 0; i + 1 < dimension; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::number(int degree) { + std::string op_id = "number"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + for (std::size_t i = 0; i < dimension; i++) { + mat[{i, i}] = static_cast(i) + 0.0j; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator elementary_operator::parity(int degree) { + std::string op_id = "parity"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + if (op.m_ops.find(op_id) == op.m_ops.end()) { + auto func = [&](std::map dimensions, + std::map> _none) { + auto degree = op.degrees[0]; + std::size_t dimension = dimensions[degree]; + auto mat = matrix_2(dimension, dimension); + for (std::size_t i = 0; i < dimension; i++) { + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + } + std::cout << "dumping the complex mat: \n"; + std::cout << mat.dump(); + std::cout << "\ndone\n\n"; + return mat; + }; + op.define(op_id, op.expected_dimensions, func); + } + return op; +} + +elementary_operator +elementary_operator::displace(int degree, std::complex amplitude) { + std::string op_id = "displace"; + std::vector degrees = {degree}; + auto op = elementary_operator(op_id, degrees); + // A dimension of -1 indicates this operator can act on any dimension. + op.expected_dimensions[degree] = -1; + // if (op.m_ops.find(op_id) == op.m_ops.end()) { + // auto func = [&](std::map dimensions, + // std::map> _none) { + // auto degree = op.degrees[0]; + // std::size_t dimension = dimensions[degree]; + // auto temp_mat = matrix_2(dimension, dimension); + // // // displace = exp[ (amplitude * create) - (conj(amplitude) * + // annihilate) ] + // // for (std::size_t i = 0; i + 1 < dimension; i++) { + // // temp_mat[{i + 1, i}] = + // // amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + // // temp_mat[{i, i + 1}] = + // // -1. * std::conj(amplitude) * std::sqrt(static_cast(i + + // 1)) + + // // 0.0 * 'j'; + // // } + // // Not ideal that our method of computing the matrix exponential + // // requires copies here. Maybe we can just use eigen directly here + // // to limit to one copy, but we can address that later. + // auto mat = temp_mat.exp(); + // std::cout << "dumping the complex mat: \n"; + // mat.dump(); + // std::cout << "\ndone\n\n"; + // return mat; + // }; + // op.define(op_id, op.expected_dimensions, func); + // } + throw std::runtime_error("currently have a bug in implementation."); + return op; +} + +elementary_operator +elementary_operator::squeeze(int degree, std::complex amplitude) { + throw std::runtime_error("Not yet implemented."); +} + +matrix_2 elementary_operator::to_matrix( + std::map dimensions, + std::map> parameters) { + return m_ops[id].generator(dimensions, parameters); +} + +/// Elementary Operator Arithmetic. + +operator_sum elementary_operator::operator+(scalar_operator other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + std::vector> _this = { + *this}; + std::vector> _other = { + other}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +operator_sum elementary_operator::operator-(scalar_operator other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + std::vector> _this = { + *this}; + std::vector> _other = { + -1. * other}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +product_operator elementary_operator::operator*(scalar_operator other) { + std::vector> _args = { + *this, other}; + return product_operator(_args); +} + +operator_sum elementary_operator::operator+(std::complex other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + auto other_scalar = scalar_operator(other); + std::vector> _this = { + *this}; + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +operator_sum elementary_operator::operator-(std::complex other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + auto other_scalar = scalar_operator((-1. * other)); + std::vector> _this = { + *this}; + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +product_operator elementary_operator::operator*(std::complex other) { + auto other_scalar = scalar_operator(other); + std::vector> _args = { + *this, other_scalar}; + return product_operator(_args); +} + +operator_sum elementary_operator::operator+(double other) { + std::complex value(other, 0.0); + return *this + value; +} + +operator_sum elementary_operator::operator-(double other) { + std::complex value(other, 0.0); + return *this - value; +} + +product_operator elementary_operator::operator*(double other) { + std::complex value(other, 0.0); + return *this * value; +} + +operator_sum operator+(std::complex other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _self = { + self}; + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_other), product_operator(_self)}); +} + +operator_sum operator-(std::complex other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_other), (-1. * self)}); +} + +product_operator operator*(std::complex other, + elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _args = { + other_scalar, self}; + return product_operator(_args); +} + +operator_sum operator+(double other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _self = { + self}; + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_other), product_operator(_self)}); +} + +operator_sum operator-(double other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _other = { + other_scalar}; + return operator_sum({product_operator(_other), (-1. * self)}); +} + +product_operator operator*(double other, elementary_operator self) { + auto other_scalar = scalar_operator(other); + std::vector> _args = { + other_scalar, self}; + return product_operator(_args); +} + +product_operator elementary_operator::operator*(elementary_operator other) { + std::vector> _args = { + *this, other}; + return product_operator(_args); +} + +operator_sum elementary_operator::operator+(elementary_operator other) { + std::vector> _this = { + *this}; + std::vector> _other = { + other}; + return operator_sum({product_operator(_this), product_operator(_other)}); +} + +operator_sum elementary_operator::operator-(elementary_operator other) { + std::vector> _this = { + *this}; + return operator_sum({product_operator(_this), (-1. * other)}); +} + +operator_sum elementary_operator::operator+(operator_sum other) { + std::vector> _this = { + *this}; + std::vector _prods = {product_operator(_this)}; + auto selfOpSum = operator_sum(_prods); + return selfOpSum + other; +} + +operator_sum elementary_operator::operator-(operator_sum other) { + std::vector> _this = { + *this}; + std::vector _prods = {product_operator(_this)}; + auto selfOpSum = operator_sum(_prods); + return selfOpSum - other; +} + +operator_sum elementary_operator::operator*(operator_sum other) { + std::vector> _this = { + *this}; + std::vector _prods = {product_operator(_this)}; + auto selfOpSum = operator_sum(_prods); + return selfOpSum * other; +} + +operator_sum elementary_operator::operator+(product_operator other) { + std::vector> _this = { + *this}; + return operator_sum({product_operator(_this), other}); +} + +operator_sum elementary_operator::operator-(product_operator other) { + return *this + (-1. * other); +} + +product_operator elementary_operator::operator*(product_operator other) { + std::vector> other_terms = + other.get_terms(); + /// Insert this elementary operator to the front of the terms list. + other_terms.insert(other_terms.begin(), *this); + return product_operator(other_terms); +} + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/operator_sum.cpp b/runtime/cudaq/dynamics/operator_sum.cpp new file mode 100644 index 0000000000..a0ba70cb2b --- /dev/null +++ b/runtime/cudaq/dynamics/operator_sum.cpp @@ -0,0 +1,368 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "common/EigenDense.h" +#include "cudaq/operators.h" + +#include +#include + +namespace cudaq { + +/// Operator sum constructor given a vector of product operators. +operator_sum::operator_sum(const std::vector &terms) + : m_terms(terms) {} + +// std::vector> +// operator_sum::canonicalize_product(product_operator &prod) const { +// std::vector> +// canonicalized_terms; + +// std::vector all_degrees; +// std::vector scalars; +// std::vector non_scalars; + +// for (const auto &op : prod.get_terms()) { +// if (std::holds_alternative(op)) { +// scalars.push_back(*std::get(op)); +// } else { +// non_scalars.push_back(*std::get(op)); +// all_degrees.insert(all_degrees.end(), +// std::get(op).degrees.begin(), +// std::get(op).degrees.end()); +// } +// } + +// if (all_degrees.size() == +// std::set(all_degrees.begin(), all_degrees.end()).size()) { +// std::sort(non_scalars.begin(), non_scalars.end(), +// [](const elementary_operator &a, const elementary_operator &b) { +// return a.degrees < b.degrees; +// }); +// } + +// for (size_t i = 0; std::min(scalars.size(), non_scalars.size()); i++) { +// canonicalized_terms.push_back(std::make_tuple(scalars[i], non_scalars[i])); +// } + +// return canonicalized_terms; +// } + +// std::vector> +// operator_sum::_canonical_terms() const { +// std::vector> terms; +// // for (const auto &term : m_terms) { +// // auto canonicalized = canonicalize_product(term); +// // terms.insert(terms.end(), canonicalized.begin(), canonicalized.end()); +// // } + +// // std::sort(terms.begin(), terms.end(), [](const auto &a, const auto &b) { +// // // return std::to_string(product_operator(a)) < +// // // std::to_string(product_operator(b)); +// // return product_operator(a).to_string() < +// product_operator(b).to_string(); +// // }); + +// return terms; +// } + +// operator_sum operator_sum::canonicalize() const { +// std::vector canonical_terms; +// for (const auto &term : _canonical_terms()) { +// canonical_terms.push_back(product_operator(term)); +// } +// return operator_sum(canonical_terms); +// } + +// bool operator_sum::operator==(const operator_sum &other) const { +// return _canonical_terms() == other._canonical_terms(); +// } + +// // Degrees property +// std::vector operator_sum::degrees() const { +// std::set unique_degrees; +// for (const auto &term : m_terms) { +// for (const auto &op : term.get_terms()) { +// unique_degrees.insert(op.get_degrees().begin(), +// op.get_degrees().end()); +// } +// } + +// return std::vector(unique_degrees.begin(), unique_degrees.end()); +// } + +// // Parameters property +// std::map operator_sum::parameters() const { +// std::map param_map; +// for (const auto &term : m_terms) { +// for (const auto &op : term.get_terms()) { +// auto op_params = op.parameters(); +// param_map.insert(op_params.begin(), op.params.end()); +// } +// } + +// return param_map; +// } + +// // Check if all terms are spin operators +// bool operator_sum::_is_spinop() const { +// return std::all_of( +// m_terms.begin(), m_terms.end(), [](product_operator &term) { +// return std::all_of(term.get_terms().begin(), +// term.get_terms().end(), +// [](const Operator &op) { return op.is_spinop(); +// }); +// }); +// } + +// Arithmetic operators +operator_sum operator_sum::operator+(const operator_sum &other) const { + std::vector combined_terms = m_terms; + combined_terms.insert(combined_terms.end(), + std::make_move_iterator(other.m_terms.begin()), + std::make_move_iterator(other.m_terms.end())); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator-(const operator_sum &other) const { + return *this + (-1 * other); +} + +operator_sum operator_sum::operator-=(const operator_sum &other) { + *this = *this - other; + return *this; +} + +operator_sum operator_sum::operator+=(const operator_sum &other) { + *this = *this + other; + return *this; +} + +operator_sum operator_sum::operator*(operator_sum &other) const { + auto self_terms = m_terms; + std::vector product_terms; + auto other_terms = other.get_terms(); + for (auto &term : self_terms) { + for (auto &other_term : other_terms) { + product_terms.push_back(term * other_term); + } + } + return operator_sum(product_terms); +} + +operator_sum operator_sum::operator*=(operator_sum &other) { + *this = *this * other; + return *this; +} + +operator_sum operator_sum::operator*(const scalar_operator &other) const { + std::vector combined_terms = m_terms; + for (auto &term : combined_terms) { + term *= other; + } + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator+(const scalar_operator &other) const { + std::vector combined_terms = m_terms; + std::vector> _other = { + other}; + combined_terms.push_back(product_operator(_other)); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator-(const scalar_operator &other) const { + return *this + (-1.0 * other); +} + +operator_sum operator_sum::operator*=(const scalar_operator &other) { + *this = *this * other; + return *this; +} + +operator_sum operator_sum::operator+=(const scalar_operator &other) { + *this = *this + other; + return *this; +} + +operator_sum operator_sum::operator-=(const scalar_operator &other) { + *this = *this - other; + return *this; +} + +operator_sum operator_sum::operator*(std::complex other) const { + return *this * scalar_operator(other); +} + +operator_sum operator_sum::operator+(std::complex other) const { + return *this + scalar_operator(other); +} + +operator_sum operator_sum::operator-(std::complex other) const { + return *this - scalar_operator(other); +} + +operator_sum operator_sum::operator*=(std::complex other) { + *this *= scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator+=(std::complex other) { + *this += scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator-=(std::complex other) { + *this -= scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator*(double other) const { + return *this * scalar_operator(other); +} + +operator_sum operator_sum::operator+(double other) const { + return *this + scalar_operator(other); +} + +operator_sum operator_sum::operator-(double other) const { + return *this - scalar_operator(other); +} + +operator_sum operator_sum::operator*=(double other) { + *this *= scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator+=(double other) { + *this += scalar_operator(other); + return *this; +} + +operator_sum operator_sum::operator-=(double other) { + *this -= scalar_operator(other); + return *this; +} + +operator_sum operator*(std::complex other, operator_sum self) { + return scalar_operator(other) * self; +} + +operator_sum operator+(std::complex other, operator_sum self) { + return scalar_operator(other) + self; +} + +operator_sum operator-(std::complex other, operator_sum self) { + return scalar_operator(other) - self; +} + +operator_sum operator*(double other, operator_sum self) { + return scalar_operator(other) * self; +} + +operator_sum operator+(double other, operator_sum self) { + return scalar_operator(other) + self; +} + +operator_sum operator-(double other, operator_sum self) { + return scalar_operator(other) - self; +} + +operator_sum operator_sum::operator+(const product_operator &other) const { + std::vector combined_terms = m_terms; + combined_terms.push_back(other); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator+=(const product_operator &other) { + *this = *this + other; + return *this; +} + +operator_sum operator_sum::operator-(const product_operator &other) const { + return *this + (-1. * other); +} + +operator_sum operator_sum::operator-=(const product_operator &other) { + *this = *this - other; + return *this; +} + +operator_sum operator_sum::operator*(const product_operator &other) const { + std::vector combined_terms = m_terms; + for (auto &term : combined_terms) { + term *= other; + } + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator*=(const product_operator &other) { + *this = *this * other; + return *this; +} + +operator_sum operator_sum::operator+(const elementary_operator &other) const { + std::vector combined_terms = m_terms; + std::vector> _other = { + other}; + combined_terms.push_back(product_operator(_other)); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator-(const elementary_operator &other) const { + std::vector combined_terms = m_terms; + combined_terms.push_back((-1. * other)); + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator*(const elementary_operator &other) const { + std::vector combined_terms = m_terms; + for (auto &term : combined_terms) { + term *= other; + } + return operator_sum(combined_terms); +} + +operator_sum operator_sum::operator+=(const elementary_operator &other) { + std::vector> _other = { + other}; + *this = *this + product_operator(_other); + return *this; +} + +operator_sum operator_sum::operator-=(const elementary_operator &other) { + std::vector> _other = { + other}; + *this = *this - product_operator(_other); + return *this; +} + +operator_sum operator_sum::operator*=(const elementary_operator &other) { + *this = *this * other; + return *this; +} + +/// FIXME: +// tensor +// operator_sum::to_matrix(const std::map &dimensions, +// const std::map ¶ms) const { +// // todo +// } + +// std::string operator_sum::to_string() const { +// std::string result; +// // for (const auto &term : m_terms) { +// // result += term.to_string() + " + "; +// // } +// // // Remove last " + " +// // if (!result.empty()) +// // result.pop_back(); +// return result; +// } + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/product_operators.cpp b/runtime/cudaq/dynamics/product_operators.cpp new file mode 100644 index 0000000000..0c8b411b1a --- /dev/null +++ b/runtime/cudaq/dynamics/product_operators.cpp @@ -0,0 +1,271 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "common/EigenDense.h" +#include "cudaq/operators.h" + +#include +#include +#include +#include + +namespace cudaq { + +/// Product Operator constructors. +product_operator::product_operator( + std::vector> + atomic_operators) + : m_terms(atomic_operators) {} + +// tensor kroneckerHelper(std::vector &matrices) { +// // essentially we pass in the list of elementary operators to +// // this function -- with lowest degree being leftmost -- then it computes +// the +// // kronecker product of all of them. +// auto kronecker = [](tensor self, tensor other) { +// return self.kronecker(other); +// }; + +// return std::accumulate(begin(matrices), end(matrices), +// tensor::identity(1, 1), kronecker); +// } + +// /// IMPLEMENT: +// tensor product_operator::to_matrix( +// std::map dimensions, +// std::map> parameters) { + +// /// TODO: This initial logic may not be needed. +// // std::vector degrees, levels; +// // for(std::map::iterator it = dimensions.begin(); it != +// // dimensions.end(); ++it) { +// // degrees.push_back(it->first); +// // levels.push_back(it->second); +// // } +// // // Calculate the size of the full Hilbert space of the given product +// // operator. int fullSize = std::accumulate(begin(levels), end(levels), 1, +// // std::multiplies()); +// std::cout << "here 49\n"; +// auto getDegrees = [](auto &&t) { return t.degrees; }; +// auto getMatrix = [&](auto &&t) { +// auto outMatrix = t.to_matrix(dimensions, parameters); +// std::cout << "dumping the outMatrix : \n"; +// outMatrix.dump(); +// return outMatrix; +// }; +// std::vector matricesFullVectorSpace; +// for (auto &term : m_terms) { +// auto op_degrees = std::visit(getDegrees, term); +// std::cout << "here 58\n"; +// // Keeps track of if we've already inserted the operator matrix +// // into the full list of matrices. +// bool alreadyInserted = false; +// std::vector matrixWithIdentities; +// /// General procedure for inserting identities: +// // * check if the operator acts on this degree by looking through +// // `op_degrees` +// // * if not, insert an identity matrix of the proper level size +// // * if so, insert the matrix itself +// for (auto [degree, level] : dimensions) { +// std::cout << "here 68\n"; +// auto it = std::find(op_degrees.begin(), op_degrees.end(), degree); +// if (it != op_degrees.end() && !alreadyInserted) { +// std::cout << "here 71\n"; +// auto matrix = std::visit(getMatrix, term); +// std::cout << "here 75\n"; +// matrixWithIdentities.push_back(matrix); +// std::cout << "here 77\n"; +// } else { +// std::cout << "here 80\n"; +// matrixWithIdentities.push_back(tensor::identity(level, +// level)); +// } +// } +// std::cout << "here 84\n"; +// matricesFullVectorSpace.push_back(kroneckerHelper(matrixWithIdentities)); +// } +// // Now just need to accumulate with matrix multiplication all of the +// // matrices in `matricesFullVectorSpace` -- they should all be the same +// size +// // already. +// std::cout << "here 89\n"; + +// // temporary +// auto out = tensor::identity(1, 1); +// std::cout << "here 93\n"; +// return out; +// } + +// Degrees property +std::vector product_operator::degrees() const { + std::set unique_degrees; + // The variant type makes it difficult + auto beginFunc = [](auto &&t) { return t.degrees.begin(); }; + auto endFunc = [](auto &&t) { return t.degrees.end(); }; + for (const auto &term : m_terms) { + unique_degrees.insert(std::visit(beginFunc, term), + std::visit(endFunc, term)); + } + // Erase any `-1` degree values that may have come from scalar operators. + auto it = unique_degrees.find(-1); + if (it != unique_degrees.end()) { + unique_degrees.erase(it); + } + return std::vector(unique_degrees.begin(), unique_degrees.end()); +} + +operator_sum product_operator::operator+(scalar_operator other) { + std::vector> _other = { + other}; + return operator_sum({*this, product_operator(_other)}); +} + +operator_sum product_operator::operator-(scalar_operator other) { + std::vector> _other = { + other}; + return operator_sum({*this, -1. * product_operator(_other)}); +} + +product_operator product_operator::operator*(scalar_operator other) { + std::vector> + combined_terms = m_terms; + combined_terms.push_back(other); + return product_operator(combined_terms); +} + +product_operator product_operator::operator*=(scalar_operator other) { + *this = *this * other; + return *this; +} + +operator_sum product_operator::operator+(std::complex other) { + return *this + scalar_operator(other); +} + +operator_sum product_operator::operator-(std::complex other) { + return *this - scalar_operator(other); +} + +product_operator product_operator::operator*(std::complex other) { + return *this * scalar_operator(other); +} + +product_operator product_operator::operator*=(std::complex other) { + *this = *this * scalar_operator(other); + return *this; +} + +operator_sum operator+(std::complex other, product_operator self) { + return operator_sum({scalar_operator(other), self}); +} + +operator_sum operator-(std::complex other, product_operator self) { + return scalar_operator(other) - self; +} + +product_operator operator*(std::complex other, product_operator self) { + return scalar_operator(other) * self; +} + +operator_sum product_operator::operator+(double other) { + return *this + scalar_operator(other); +} + +operator_sum product_operator::operator-(double other) { + return *this - scalar_operator(other); +} + +product_operator product_operator::operator*(double other) { + return *this * scalar_operator(other); +} + +product_operator product_operator::operator*=(double other) { + *this = *this * scalar_operator(other); + return *this; +} + +operator_sum operator+(double other, product_operator self) { + return operator_sum({scalar_operator(other), self}); +} + +operator_sum operator-(double other, product_operator self) { + return scalar_operator(other) - self; +} + +product_operator operator*(double other, product_operator self) { + return scalar_operator(other) * self; +} + +operator_sum product_operator::operator+(product_operator other) { + return operator_sum({*this, other}); +} + +operator_sum product_operator::operator-(product_operator other) { + return operator_sum({*this, (-1. * other)}); +} + +product_operator product_operator::operator*(product_operator other) { + std::vector> + combined_terms = m_terms; + combined_terms.insert(combined_terms.end(), + std::make_move_iterator(other.m_terms.begin()), + std::make_move_iterator(other.m_terms.end())); + return product_operator(combined_terms); +} + +product_operator product_operator::operator*=(product_operator other) { + *this = *this * other; + return *this; +} + +operator_sum product_operator::operator+(elementary_operator other) { + std::vector> _other = { + other}; + return operator_sum({*this, product_operator(_other)}); +} + +operator_sum product_operator::operator-(elementary_operator other) { + std::vector> _other = { + other}; + return operator_sum({*this, -1. * product_operator(_other)}); +} + +product_operator product_operator::operator*(elementary_operator other) { + std::vector> + combined_terms = m_terms; + combined_terms.push_back(other); + return product_operator(combined_terms); +} + +product_operator product_operator::operator*=(elementary_operator other) { + *this = *this * other; + return *this; +} + +operator_sum product_operator::operator+(operator_sum other) { + std::vector other_terms = other.get_terms(); + other_terms.insert(other_terms.begin(), *this); + return operator_sum(other_terms); +} + +operator_sum product_operator::operator-(operator_sum other) { + auto negative_other = (-1. * other); + std::vector other_terms = negative_other.get_terms(); + other_terms.insert(other_terms.begin(), *this); + return operator_sum(other_terms); +} + +operator_sum product_operator::operator*(operator_sum other) { + std::vector other_terms = other.get_terms(); + for (auto &term : other_terms) { + term = *this * term; + } + return operator_sum(other_terms); +} + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/scalar_operators.cpp b/runtime/cudaq/dynamics/scalar_operators.cpp new file mode 100644 index 0000000000..1be54ea9ee --- /dev/null +++ b/runtime/cudaq/dynamics/scalar_operators.cpp @@ -0,0 +1,275 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "common/EigenDense.h" +#include "cudaq/operators.h" + +#include +#include + +namespace cudaq { + +/// Constructors. +scalar_operator::scalar_operator(const scalar_operator &other) + : generator(other.generator), m_constant_value(other.m_constant_value) {} +scalar_operator::scalar_operator(scalar_operator &other) + : generator(other.generator), m_constant_value(other.m_constant_value) {} + +/// @brief Constructor that just takes and returns a complex double value. +scalar_operator::scalar_operator(std::complex value) { + m_constant_value = value; + auto func = [&](std::map> _none) { + return m_constant_value; + }; + generator = ScalarCallbackFunction(func); +} + +/// @brief Constructor that just takes a double and returns a complex double. +scalar_operator::scalar_operator(double value) { + std::complex castValue(value, 0.0); + m_constant_value = castValue; + auto func = [&](std::map> _none) { + return m_constant_value; + }; + generator = ScalarCallbackFunction(func); +} + +std::complex scalar_operator::evaluate( + std::map> parameters) { + return generator(parameters); +} + +matrix_2 scalar_operator::to_matrix( + std::map dimensions, + std::map> parameters) { + auto returnOperator = matrix_2(1, 1); + returnOperator[{0, 0}] = evaluate(parameters); + return returnOperator; +} + +#define ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(op) \ + scalar_operator operator op(std::complex other, \ + scalar_operator self) { \ + /* Create an operator for the complex double value. */ \ + auto otherOperator = scalar_operator(other); \ + /* Create an operator that we will store the result in and return to the \ + * user. */ \ + scalar_operator returnOperator; \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + returnOperator._operators_to_compose.push_back(self); \ + returnOperator._operators_to_compose.push_back(otherOperator); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + /* FIXME: I have to use this hacky `.get_val()` on the newly created \ + * operator for the given complex double -- because calling the \ + * evaluate function returns 0.0 . I have no clue why??? */ \ + return returnOperator._operators_to_compose[0] \ + .evaluate(parameters) op returnOperator._operators_to_compose[1] \ + .get_val(); \ + }; \ + returnOperator.generator = ScalarCallbackFunction(newGenerator); \ + return returnOperator; \ + } + +#define ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(op) \ + scalar_operator operator op(scalar_operator self, \ + std::complex other) { \ + /* Create an operator for the complex double value. */ \ + auto otherOperator = scalar_operator(other); \ + /* Create an operator that we will store the result in and return to the \ + * user. */ \ + scalar_operator returnOperator; \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + returnOperator._operators_to_compose.push_back(self); \ + returnOperator._operators_to_compose.push_back(otherOperator); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + /* FIXME: I have to use this hacky `.get_val()` on the newly created \ + * operator for the given complex double -- because calling the \ + * evaluate function returns 0.0 . I have no clue why??? */ \ + return returnOperator._operators_to_compose[1] \ + .get_val() op returnOperator._operators_to_compose[0] \ + .evaluate(parameters); \ + }; \ + returnOperator.generator = ScalarCallbackFunction(newGenerator); \ + return returnOperator; \ + } + +#define ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(op) \ + void operator op(scalar_operator &self, std::complex other) { \ + /* Create an operator for the complex double value. */ \ + auto otherOperator = scalar_operator(other); \ + /* Need to move the existing generating function to a new operator so that \ + * we can modify the generator in `self` in-place. */ \ + scalar_operator copy(self); \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + self._operators_to_compose.push_back(copy); \ + self._operators_to_compose.push_back(otherOperator); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + /* FIXME: I have to use this hacky `.get_val()` on the newly created \ + * operator for the given complex double -- because calling the \ + * evaluate function returns 0.0 . I have no clue why??? */ \ + return self._operators_to_compose[0] \ + .evaluate(parameters) op self._operators_to_compose[1] \ + .get_val(); \ + }; \ + self.generator = ScalarCallbackFunction(newGenerator); \ + } + +#define ARITHMETIC_OPERATIONS_DOUBLES(op) \ + scalar_operator operator op(double other, scalar_operator self) { \ + std::complex value(other, 0.0); \ + return self op value; \ + } + +#define ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(op) \ + scalar_operator operator op(scalar_operator self, double other) { \ + std::complex value(other, 0.0); \ + return value op self; \ + } + +#define ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(op) \ + void operator op(scalar_operator &self, double other) { \ + std::complex value(other, 0.0); \ + self op value; \ + } + +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(+); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(-); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(*); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES(/); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(+); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(-); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(*); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_REVERSE(/); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(+=); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(-=); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(*=); +ARITHMETIC_OPERATIONS_COMPLEX_DOUBLES_ASSIGNMENT(/=); +ARITHMETIC_OPERATIONS_DOUBLES(+); +ARITHMETIC_OPERATIONS_DOUBLES(-); +ARITHMETIC_OPERATIONS_DOUBLES(*); +ARITHMETIC_OPERATIONS_DOUBLES(/); +ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(+); +ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(-); +ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(*); +ARITHMETIC_OPERATIONS_DOUBLES_REVERSE(/); +ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(+=); +ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(-=); +ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(*=); +ARITHMETIC_OPERATIONS_DOUBLES_ASSIGNMENT(/=); + +#define ARITHMETIC_OPERATIONS_SCALAR_OPS(op) \ + scalar_operator scalar_operator::operator op(scalar_operator other) { \ + /* Create an operator that we will store the result in and return to the \ + * user. */ \ + scalar_operator returnOperator; \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + returnOperator._operators_to_compose.push_back(*this); \ + returnOperator._operators_to_compose.push_back(other); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + return returnOperator._operators_to_compose[0] \ + .evaluate(parameters) op returnOperator._operators_to_compose[1] \ + .evaluate(parameters); \ + }; \ + returnOperator.generator = ScalarCallbackFunction(newGenerator); \ + return returnOperator; \ + } + +#define ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(op) \ + void operator op(scalar_operator &self, scalar_operator other) { \ + /* Need to move the existing generating function to a new operator so \ + * that we can modify the generator in `self` in-place. */ \ + scalar_operator selfCopy(self); \ + /* Store the previous generator functions in the new operator. This is \ + * needed as the old generator functions would effectively be lost once we \ + * leave this function scope. */ \ + self._operators_to_compose.push_back(selfCopy); \ + self._operators_to_compose.push_back(other); \ + auto newGenerator = \ + [&](std::map> parameters) { \ + return self._operators_to_compose[0] \ + .evaluate(parameters) op self._operators_to_compose[1] \ + .evaluate(parameters); \ + }; \ + self.generator = ScalarCallbackFunction(newGenerator); \ + } + +ARITHMETIC_OPERATIONS_SCALAR_OPS(+); +ARITHMETIC_OPERATIONS_SCALAR_OPS(-); +ARITHMETIC_OPERATIONS_SCALAR_OPS(*); +ARITHMETIC_OPERATIONS_SCALAR_OPS(/); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(+=); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(-=); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(*=); +ARITHMETIC_OPERATIONS_SCALAR_OPS_ASSIGNMENT(/=); + +operator_sum scalar_operator::operator+(elementary_operator other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + return operator_sum({product_operator({*this}), product_operator({other})}); +} + +operator_sum scalar_operator::operator-(elementary_operator other) { + // Operator sum is composed of product operators, so we must convert + // both underlying types to `product_operators` to perform the arithmetic. + return operator_sum( + {product_operator({*this}), product_operator({-1. * other})}); +} + +product_operator scalar_operator::operator*(elementary_operator other) { + return product_operator({*this, other}); +} + +operator_sum scalar_operator::operator+(product_operator other) { + return operator_sum({product_operator({*this}), other}); +} + +operator_sum scalar_operator::operator-(product_operator other) { + return operator_sum({product_operator({*this}), (-1. * other)}); +} + +product_operator scalar_operator::operator*(product_operator other) { + std::vector> other_terms = + other.get_terms(); + /// Insert this scalar operator to the front of the terms list. + other_terms.insert(other_terms.begin(), *this); + return product_operator(other_terms); +} + +operator_sum scalar_operator::operator+(operator_sum other) { + std::vector other_terms = other.get_terms(); + other_terms.insert(other_terms.begin(), *this); + return operator_sum(other_terms); +} + +operator_sum scalar_operator::operator-(operator_sum other) { + auto negative_other = (-1. * other); + std::vector other_terms = negative_other.get_terms(); + other_terms.insert(other_terms.begin(), *this); + return operator_sum(other_terms); +} + +operator_sum scalar_operator::operator*(operator_sum other) { + std::vector other_terms = other.get_terms(); + for (auto &term : other_terms) + term = *this * term; + return operator_sum(other_terms); +} + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/schedule.cpp b/runtime/cudaq/dynamics/schedule.cpp new file mode 100644 index 0000000000..6e833acb4f --- /dev/null +++ b/runtime/cudaq/dynamics/schedule.cpp @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/schedule.h" +#include +#include + +namespace cudaq { + +// Constructor +Schedule::Schedule(std::vector> steps, + std::vector parameters, + std::function( + const std::string &, const std::complex &)> + value_function) + : _steps(steps), _parameters(parameters), _value_function(value_function), + _current_idx(-1) { + if (!_steps.empty()) { + m_ptr = &_steps[0]; + } else { + m_ptr = nullptr; + } +} + +// Dereference operator +Schedule::reference Schedule::operator*() const { return *m_ptr; } + +// Arrow operator +Schedule::pointer Schedule::operator->() { return m_ptr; } + +// Prefix increment +Schedule &Schedule::operator++() { + if (_current_idx + 1 < static_cast(_steps.size())) { + _current_idx++; + m_ptr = &_steps[_current_idx]; + } else { + throw std::out_of_range("No more steps in the schedule."); + } + return *this; +} + +// Postfix increment +Schedule Schedule::operator++(int) { + Schedule tmp = *this; + ++(*this); + return tmp; +} + +// Comparison operators +bool operator==(const Schedule &a, const Schedule &b) { + return a.m_ptr == b.m_ptr; +}; + +bool operator!=(const Schedule &a, const Schedule &b) { + return a.m_ptr != b.m_ptr; +}; + +// Reset schedule +void Schedule::reset() { + _current_idx = -1; + if (!_steps.empty()) { + m_ptr = &_steps[0]; + } else { + m_ptr = nullptr; + } +} + +// Get the current step +std::optional> Schedule::current_step() const { + if (_current_idx >= 0 && _current_idx < static_cast(_steps.size())) { + return _steps[_current_idx]; + } + return std::nullopt; +} + +} // namespace cudaq diff --git a/runtime/cudaq/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..77ee4703bf --- /dev/null +++ b/runtime/cudaq/operators.h @@ -0,0 +1,464 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "definition.h" +#include "utils/tensor.h" + +#include +#include +#include +#include + +namespace cudaq { + +class operator_sum; +class product_operator; +class scalar_operator; +class elementary_operator; + +/// @brief Represents an operator expression consisting of a sum of terms, where +/// each term is a product of elementary and scalar operators. Operator +/// expressions cannot be used within quantum kernels, but they provide methods +/// to convert them to data types that can. +class operator_sum { +private: + std::vector m_terms; + + std::vector> + canonicalize_product(product_operator &prod) const; + + std::vector> + _canonical_terms() const; + +public: + /// @brief Empty constructor that a user can aggregate terms into. + operator_sum() = default; + + /// @brief Construct a `cudaq::operator_sum` given a sequence of + /// `cudaq::product_operator`'s. + /// This operator expression represents a sum of terms, where each term + /// is a product of elementary and scalar operators. + operator_sum(const std::vector &terms); + + operator_sum canonicalize() const; + + /// @brief The degrees of freedom that the operator acts on in canonical + /// order. + std::vector degrees() const; + + bool _is_spinop() const; + + /// TODO: implement + // template + // TEval _evaluate(OperatorArithmetics &arithmetics) const; + + /// @brief Return the `operator_sum` as a matrix. + /// @arg `dimensions` : A mapping that specifies the number of levels, + /// that is, the dimension of each degree of freedom + /// that the operator acts on. Example for two, 2-level + /// degrees of freedom: `{0:2, 1:2}`. + /// @arg `parameters` : A map of the paramter names to their concrete, complex + /// values. + matrix_2 to_matrix(const std::map &dimensions, + const std::map ¶ms = {}) const; + + // Arithmetic operators + operator_sum operator+(const operator_sum &other) const; + operator_sum operator-(const operator_sum &other) const; + operator_sum operator*(operator_sum &other) const; + operator_sum operator*=(operator_sum &other); + operator_sum operator+=(const operator_sum &other); + operator_sum operator-=(const operator_sum &other); + operator_sum operator*(const scalar_operator &other) const; + operator_sum operator+(const scalar_operator &other) const; + operator_sum operator-(const scalar_operator &other) const; + operator_sum operator*=(const scalar_operator &other); + operator_sum operator+=(const scalar_operator &other); + operator_sum operator-=(const scalar_operator &other); + operator_sum operator*(std::complex other) const; + operator_sum operator+(std::complex other) const; + operator_sum operator-(std::complex other) const; + operator_sum operator*=(std::complex other); + operator_sum operator+=(std::complex other); + operator_sum operator-=(std::complex other); + operator_sum operator*(double other) const; + operator_sum operator+(double other) const; + operator_sum operator-(double other) const; + operator_sum operator*=(double other); + operator_sum operator+=(double other); + operator_sum operator-=(double other); + operator_sum operator*(const product_operator &other) const; + operator_sum operator+(const product_operator &other) const; + operator_sum operator-(const product_operator &other) const; + operator_sum operator*=(const product_operator &other); + operator_sum operator+=(const product_operator &other); + operator_sum operator-=(const product_operator &other); + operator_sum operator+(const elementary_operator &other) const; + operator_sum operator-(const elementary_operator &other) const; + operator_sum operator*(const elementary_operator &other) const; + operator_sum operator*=(const elementary_operator &other); + operator_sum operator+=(const elementary_operator &other); + operator_sum operator-=(const elementary_operator &other); + + /// @brief Return the operator_sum as a string. + std::string to_string() const; + + /// @brief Return the number of operator terms that make up this operator sum. + int term_count() const { return m_terms.size(); } + + /// @brief True, if the other value is an operator_sum with equivalent terms, + /// and False otherwise. The equality takes into account that operator + /// addition is commutative, as is the product of two operators if they + /// act on different degrees of freedom. + /// The equality comparison does *not* take commutation relations into + /// account, and does not try to reorder terms blockwise; it may hence + /// evaluate to False, even if two operators in reality are the same. + /// If the equality evaluates to True, on the other hand, the operators + /// are guaranteed to represent the same transformation for all arguments. + bool operator==(const operator_sum &other) const; + + /// FIXME: Protect this once I can do deeper testing in unittests. + // protected: + std::vector get_terms() { return m_terms; } +}; +operator_sum operator*(std::complex other, operator_sum self); +operator_sum operator+(std::complex other, operator_sum self); +operator_sum operator-(std::complex other, operator_sum self); +operator_sum operator*(double other, operator_sum self); +operator_sum operator+(double other, operator_sum self); +operator_sum operator-(double other, operator_sum self); + +/// @brief Represents an operator expression consisting of a product of +/// elementary and scalar operators. Operator expressions cannot be used within +/// quantum kernels, but they provide methods to convert them to data types +/// that can. +class product_operator : public operator_sum { +private: + std::vector> m_terms; + +public: + product_operator() = default; + ~product_operator() = default; + + // Constructor for an operator expression that represents a product + // of scalar and elementary operators. + // arg atomic_operators : The operators of which to compute the product when + // evaluating the operator expression. + product_operator( + std::vector> + atomic_operators); + + // Arithmetic overloads against all other operator types. + operator_sum operator+(std::complex other); + operator_sum operator-(std::complex other); + product_operator operator*(std::complex other); + product_operator operator*=(std::complex other); + operator_sum operator+(double other); + operator_sum operator-(double other); + product_operator operator*(double other); + product_operator operator*=(double other); + operator_sum operator+(scalar_operator other); + operator_sum operator-(scalar_operator other); + product_operator operator*(scalar_operator other); + product_operator operator*=(scalar_operator other); + operator_sum operator+(product_operator other); + operator_sum operator-(product_operator other); + product_operator operator*(product_operator other); + product_operator operator*=(product_operator other); + operator_sum operator+(elementary_operator other); + operator_sum operator-(elementary_operator other); + product_operator operator*(elementary_operator other); + product_operator operator*=(elementary_operator other); + operator_sum operator+(operator_sum other); + operator_sum operator-(operator_sum other); + operator_sum operator*(operator_sum other); + + /// @brief True, if the other value is an operator_sum with equivalent terms, + /// and False otherwise. The equality takes into account that operator + /// addition is commutative, as is the product of two operators if they + /// act on different degrees of freedom. + /// The equality comparison does *not* take commutation relations into + /// account, and does not try to reorder terms blockwise; it may hence + /// evaluate to False, even if two operators in reality are the same. + /// If the equality evaluates to True, on the other hand, the operators + /// are guaranteed to represent the same transformation for all arguments. + bool operator==(product_operator other); + + /// @brief Return the `product_operator` as a string. + std::string to_string() const; + + /// @brief Return the `operator_sum` as a matrix. + /// @arg `dimensions` : A mapping that specifies the number of levels, + /// that is, the dimension of each degree of freedom + /// that the operator acts on. Example for two, 2-level + /// degrees of freedom: `{0:2, 1:2}`. + /// @arg `parameters` : A map of the paramter names to their concrete, complex + /// values. + matrix_2 to_matrix(std::map dimensions, + std::map> parameters); + + /// @brief Creates a representation of the operator as a `cudaq::pauli_word` + /// that can be passed as an argument to quantum kernels. + // pauli_word to_pauli_word(); + + /// @brief The degrees of freedom that the operator acts on in canonical + /// order. + std::vector degrees() const; + + /// @brief Return the number of operator terms that make up this product + /// operator. + int term_count() const { return m_terms.size(); } + + /// FIXME: Protect this once I can do deeper testing in unittests. + // protected: + std::vector> get_terms() { + return m_terms; + }; +}; +operator_sum operator+(std::complex other, product_operator self); +operator_sum operator-(std::complex other, product_operator self); +product_operator operator*(std::complex other, product_operator self); +operator_sum operator+(double other, product_operator self); +operator_sum operator-(double other, product_operator self); +product_operator operator*(double other, product_operator self); + +class elementary_operator : public product_operator { +private: + std::map m_ops; + +public: + // The constructor should never be called directly by the user: + // Keeping it internally documentd for now, however. + /// @brief Constructor. + /// @arg operator_id : The ID of the operator as specified when it was + /// defined. + /// @arg degrees : the degrees of freedom that the operator acts upon. + elementary_operator(std::string operator_id, std::vector degrees); + + // Copy constructor. + elementary_operator(const elementary_operator &other); + elementary_operator(elementary_operator &other); + + // Arithmetic overloads against all other operator types. + operator_sum operator+(std::complex other); + operator_sum operator-(std::complex other); + product_operator operator*(std::complex other); + operator_sum operator+(double other); + operator_sum operator-(double other); + product_operator operator*(double other); + operator_sum operator+(scalar_operator other); + operator_sum operator-(scalar_operator other); + product_operator operator*(scalar_operator other); + operator_sum operator+(elementary_operator other); + operator_sum operator-(elementary_operator other); + product_operator operator*(elementary_operator other); + operator_sum operator+(product_operator other); + operator_sum operator-(product_operator other); + product_operator operator*(product_operator other); + operator_sum operator+(operator_sum other); + operator_sum operator-(operator_sum other); + operator_sum operator+=(operator_sum other); + operator_sum operator-=(operator_sum other); + operator_sum operator*(operator_sum other); + + /// @brief True, if the other value is an elementary operator with the same id + /// acting on the same degrees of freedom, and False otherwise. + bool operator==(elementary_operator other); + + /// @brief Return the `elementary_operator` as a string. + std::string to_string() const; + + /// @brief Return the `elementary_operator` as a matrix. + /// @arg `dimensions` : A map specifying the number of levels, + /// that is, the dimension of each degree of freedom + /// that the operator acts on. Example for two, 2-level + /// degrees of freedom: `{0 : 2, 1 : 2}`. + matrix_2 to_matrix(std::map dimensions, + std::map> parameters); + + // Predefined operators. + static elementary_operator identity(int degree); + static elementary_operator zero(int degree); + static elementary_operator annihilate(int degree); + static elementary_operator create(int degree); + static elementary_operator momentum(int degree); + static elementary_operator number(int degree); + static elementary_operator parity(int degree); + static elementary_operator position(int degree); + /// FIXME: + static elementary_operator squeeze(int degree, + std::complex amplitude); + static elementary_operator displace(int degree, + std::complex amplitude); + + /// @brief Adds the definition of an elementary operator with the given id to + /// the class. After definition, an the defined elementary operator can be + /// instantiated by providing the operator id as well as the degree(s) of + /// freedom that it acts on. An elementary operator is a parameterized object + /// acting on certain degrees of freedom. To evaluate an operator, for example + /// to compute its matrix, the level, that is the dimension, for each degree + /// of freedom it acts on must be provided, as well as all additional + /// parameters. Additional parameters must be provided in the form of keyword + /// arguments. Note: The dimensions passed during operator evaluation are + /// automatically validated against the expected dimensions specified during + /// definition - the `create` function does not need to do this. + /// @arg operator_id : A string that uniquely identifies the defined operator. + /// @arg expected_dimensions : Defines the number of levels, that is the + /// dimension, + /// for each degree of freedom in canonical (that is sorted) order. A + /// negative or zero value for one (or more) of the expected dimensions + /// indicates that the operator is defined for any dimension of the + /// corresponding degree of freedom. + /// @arg create : Takes any number of complex-valued arguments and returns the + /// matrix representing the operator in canonical order. If the matrix + /// can be defined for any number of levels for one or more degree of + /// freedom, the `create` function must take an argument called + /// `dimension` (or `dim` for short), if the operator acts on a single + /// degree of freedom, and an argument called `dimensions` (or `dims` for + /// short), if the operator acts + /// on multiple degrees of freedom. + template + void define(std::string operator_id, std::map expected_dimensions, + Func create) { + if (m_ops.find(operator_id) != m_ops.end()) { + // todo: make a nice error message to say op already exists + throw; + } + auto defn = Definition(); + defn.create_definition(operator_id, expected_dimensions, create); + m_ops[operator_id] = defn; + } + + // Attributes. + + /// @brief The number of levels, that is the dimension, for each degree of + /// freedom in canonical order that the operator acts on. A value of zero or + /// less indicates that the operator is defined for any dimension of that + /// degree. + std::map expected_dimensions; + /// @brief The degrees of freedom that the operator acts on in canonical + /// order. + std::vector degrees; + std::string id; + + // /// @brief Creates a representation of the operator as `pauli_word` that + // can be passed as an argument to quantum kernels. + // pauli_word to_pauli_word ovveride(); +}; +// Reverse order arithmetic for elementary operators against pure scalars. +operator_sum operator+(std::complex other, elementary_operator self); +operator_sum operator-(std::complex other, elementary_operator self); +product_operator operator*(std::complex other, + elementary_operator self); +operator_sum operator+(double other, elementary_operator self); +operator_sum operator-(double other, elementary_operator self); +product_operator operator*(double other, elementary_operator self); + +class scalar_operator : public product_operator { +private: + // If someone gave us a constant value, we will just return that + // directly to them when they call `evaluate`. + std::complex m_constant_value; + +public: + /// @brief Constructor that just takes a callback function with no + /// arguments. + + scalar_operator(ScalarCallbackFunction &&create) { + generator = ScalarCallbackFunction(create); + } + + /// @brief Constructor that just takes and returns a complex double value. + /// @NOTE: This replicates the behavior of the python `scalar_operator::const` + /// without the need for an extra member function. + scalar_operator(std::complex value); + scalar_operator(double value); + + // Arithmetic overloads against other operator types. + scalar_operator operator+(scalar_operator other); + scalar_operator operator-(scalar_operator other); + scalar_operator operator*(scalar_operator other); + scalar_operator operator/(scalar_operator other); + /// TODO: implement and test pow + scalar_operator pow(scalar_operator other); + operator_sum operator+(elementary_operator other); + operator_sum operator-(elementary_operator other); + product_operator operator*(elementary_operator other); + operator_sum operator+(product_operator other); + operator_sum operator-(product_operator other); + product_operator operator*(product_operator other); + operator_sum operator+(operator_sum other); + operator_sum operator-(operator_sum other); + operator_sum operator*(operator_sum other); + + /// @brief Return the scalar operator as a concrete complex value. + std::complex + evaluate(std::map> parameters); + + // Return the scalar operator as a 1x1 matrix. This is needed for + // compatability with the other inherited classes. + matrix_2 to_matrix(std::map dimensions, + std::map> parameters); + + // /// @brief Returns true if other is a scalar operator with the same + // /// generator. + // bool operator==(scalar_operator other); + + /// @brief The function that generates the value of the scalar operator. + /// The function can take a vector of complex-valued arguments + /// and returns a number. + ScalarCallbackFunction generator; + + // Only populated when we've performed arithmetic between various + // scalar operators. + std::vector _operators_to_compose; + + /// NOTE: We should revisit these constructors and remove any that have + /// become unecessary as the implementation improves. + scalar_operator() = default; + // Copy constructor. + scalar_operator(const scalar_operator &other); + scalar_operator(scalar_operator &other); + + ~scalar_operator() = default; + + // Need this property for consistency with other inherited types. + // Particularly, to be used when the scalar operator is held within + // a variant type next to elementary operators. + std::vector degrees = {-1}; + + // REMOVEME: just using this as a temporary patch: + std::complex get_val() { return m_constant_value; }; +}; + +scalar_operator operator+(scalar_operator self, std::complex other); +scalar_operator operator-(scalar_operator self, std::complex other); +scalar_operator operator*(scalar_operator self, std::complex other); +scalar_operator operator/(scalar_operator self, std::complex other); +scalar_operator operator+(std::complex other, scalar_operator self); +scalar_operator operator-(std::complex other, scalar_operator self); +scalar_operator operator*(std::complex other, scalar_operator self); +scalar_operator operator/(std::complex other, scalar_operator self); +scalar_operator operator+(scalar_operator self, double other); +scalar_operator operator-(scalar_operator self, double other); +scalar_operator operator*(scalar_operator self, double other); +scalar_operator operator/(scalar_operator self, double other); +scalar_operator operator+(double other, scalar_operator self); +scalar_operator operator-(double other, scalar_operator self); +scalar_operator operator*(double other, scalar_operator self); +scalar_operator operator/(double other, scalar_operator self); +void operator+=(scalar_operator &self, std::complex other); +void operator-=(scalar_operator &self, std::complex other); +void operator*=(scalar_operator &self, std::complex other); +void operator/=(scalar_operator &self, std::complex other); +void operator+=(scalar_operator &self, scalar_operator other); +void operator-=(scalar_operator &self, scalar_operator other); +void operator*=(scalar_operator &self, scalar_operator other); +void operator/=(scalar_operator &self, scalar_operator other); + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/schedule.h b/runtime/cudaq/schedule.h new file mode 100644 index 0000000000..c9610cc75b --- /dev/null +++ b/runtime/cudaq/schedule.h @@ -0,0 +1,105 @@ +/****************************************************************-*- 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 +#include +#include +#include +#include +#include +#include + +namespace cudaq { + +/// @brief Create a schedule for evaluating an operator expression at different +/// steps. +class Schedule { +public: + /// Iterator tags. May be superfluous. + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = std::complex; + using pointer = std::complex *; + using reference = std::complex &; + +private: + pointer m_ptr; + std::vector> _steps; + std::vector _parameters; + std::function(const std::string &, + const std::complex &)> + _value_function; + int _current_idx; + +public: + Schedule(pointer ptr) : m_ptr(ptr){}; + + /// @brief Constructor. + /// @arg steps: The sequence of steps in the schedule. Restricted to a vector + /// of complex values. + /// @arg parameters: A sequence of strings representing the parameter names of + /// an operator expression. + /// @arg value_function: A function that takes the name of a parameter as well + /// as an additional value ("step") of arbitrary type as argument and returns + /// the complex value for that parameter at the given step. + /// @details current_idx: Intializes the current index (_current_idx) to -1 to + /// indicate that iteration has not yet begun. Once iteration starts, + /// _current_idx will be used to track the position in the sequence of steps. + Schedule(const std::vector> steps, + const std::vector parameters, + std::function(const std::string &, + const std::complex &)> + value_function); + + /// Below, I define what I believe are the minimal necessary methods needed + /// for this to behave like an iterable. This should be revisited in the + /// implementation phase. + + // Pointers. + /// @brief Dereference operator to access the current step value. + /// @return Reference to current complex step value. + reference operator*() const; + + /// @brief Arrow operator to access the pointer the current step value. + /// @return Pointer to the current complex step value. + pointer operator->(); + + // Prefix increment. + /// @brief Prefix increment operator to move to the next step in the schedule. + /// @return Reference to the updated Schedule object. + Schedule &operator++(); + + // Postfix increment. + /// @brief Postfix increment operator to move to the next step in the + /// schedule. + /// @return Copy of the previous Schedule state. + Schedule operator++(int); + + // Comparison. + /// @brief Equality comparison operator. + /// @param a: First Schedule object. + /// @param b: Second Schedule object. + /// @return True if both schedules point to the same step, false otherwise + friend bool operator==(const Schedule &a, const Schedule &b); + + /// @brief Inequality comparison operator. + /// @param a: First Schedule object. + /// @param b: Second Schedule object. + /// @return True if both schedules point to different steps, false otherwise + friend bool operator!=(const Schedule &a, const Schedule &b); + + /// @brief Reset the schedule iterator to the beginning. + void reset(); + + /// @brief Get the current step in the schedule. + /// @return The current complex step value as an optional. If no valid step, + /// returns std::nullopt. + std::optional> current_step() const; +}; +} // namespace cudaq diff --git a/runtime/cudaq/utils/tensor.h b/runtime/cudaq/utils/tensor.h index d9f9099264..8287ab93de 100644 --- a/runtime/cudaq/utils/tensor.h +++ b/runtime/cudaq/utils/tensor.h @@ -38,6 +38,9 @@ class matrix_2 { using Dimensions = std::pair; matrix_2() = default; + matrix_2(std::size_t rows, std::size_t cols) + : dimensions(std::make_pair(rows, cols)), + data{new std::complex[rows * cols]} {} matrix_2(const matrix_2 &other) : dimensions{other.dimensions}, data{new std::complex[get_size(other.dimensions)]} { diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 81b7202ae6..6e2213f083 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -44,6 +44,11 @@ set(CUDAQ_RUNTIME_TEST_SOURCES common/NoiseModelTester.cpp integration/tracer_tester.cpp integration/gate_library_tester.cpp + dynamics/scalar_ops_simple.cpp + dynamics/scalar_ops_arithmetic.cpp + dynamics/elementary_ops_simple.cpp + dynamics/elementary_ops_arithmetic.cpp + dynamics/product_operators_arithmetic.cpp ) # Make it so we can get function symbols @@ -257,6 +262,27 @@ target_link_libraries(test_spin gtest_main) gtest_discover_tests(test_spin) +# Create an executable for operators UnitTests +set(CUDAQ_OPERATOR_TEST_SOURCES + dynamics/operator_sum.cpp + dynamics/elementary_ops_simple.cpp + dynamics/elementary_ops_arithmetic.cpp + dynamics/scalar_ops_simple.cpp + dynamics/scalar_ops_arithmetic.cpp + dynamics/product_operators_arithmetic.cpp +) +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) +gtest_discover_tests(test_operators) + add_subdirectory(plugin) # build the test qudit execution manager diff --git a/unittests/dynamics/elementary_ops_arithmetic.cpp b/unittests/dynamics/elementary_ops_arithmetic.cpp new file mode 100644 index 0000000000..fb5c85ac49 --- /dev/null +++ b/unittests/dynamics/elementary_ops_arithmetic.cpp @@ -0,0 +1,604 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/operators.h" +#include + +/// STATUS: +/// 1. I've generated all of the `want` matrices for each test, and prepared +/// the test to check against the `got` matrix. Now waiting on finishing the +/// full `to_matrix` conversion to be able to do so. +/// + +namespace utils_0 { +void checkEqual(cudaq::matrix_2 a, cudaq::matrix_2 b) { + ASSERT_EQ(a.get_rank(), b.get_rank()); + ASSERT_EQ(a.get_rows(), b.get_rows()); + ASSERT_EQ(a.get_columns(), b.get_columns()); + ASSERT_EQ(a.get_size(), b.get_size()); + for (std::size_t i = 0; i < a.get_rows(); i++) { + for (std::size_t j = 0; j < a.get_columns(); j++) { + double a_val = a[{i, j}].real(); + double b_val = b[{i, j}].real(); + EXPECT_NEAR(a_val, b_val, 1e-8); + } + } +} + +cudaq::matrix_2 zero_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + return mat; +} + +cudaq::matrix_2 id_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = 1.0 + 0.0j; + return mat; +} + +cudaq::matrix_2 annihilate_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 create_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 position_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 momentum_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 number_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = static_cast(i) + 0.0j; + return mat; +} + +cudaq::matrix_2 parity_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + return mat; +} + +// cudaq::matrix_2 displace_matrix(std::size_t size, +// std::complex amplitude) { +// auto mat = cudaq::matrix_2(size, size); +// for (std::size_t i = 0; i + 1 < size; i++) { +// mat[{i + 1, i}] = +// amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; +// mat[{i, i + 1}] = -1. * std::conj(amplitude) * (0.5 * 'j') * +// std::sqrt(static_cast(i + 1)) + +// 0.0 * 'j'; +// } +// return mat.exp(); +// } + +} // namespace utils_0 + +TEST(ExpressionTester, checkPreBuiltElementaryOpsScalars) { + + auto function = [](std::map> parameters) { + return parameters["value"]; + }; + + /// Keeping these fixed for these more simple tests. + int level_count = 3; + int degree_index = 0; + double const_scale_factor = 2.0; + + // `elementary_operator + scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(const_scale_factor); + + // Produces an `operator_sum` type. + auto sum = self + other; + auto reverse = other + self; + + // Check the `operator_sum` attributes. + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = sum.to_matrix({{degree_index, level_count}}, {}); + // auto got_reverse_matrix = reverse.to_matrix({{degree_index, + // level_count}}, {}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) + scaled_identity; + auto want_reverse_matrix = + scaled_identity + utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator + scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(function); + + // Produces an `operator_sum` type. + auto sum = self + other; + auto reverse = other + self; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = sum.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); auto got_reverse_matrix = + // reverse.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) + scaled_identity; + auto want_reverse_matrix = + scaled_identity + utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator - scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(const_scale_factor); + + // Produces an `operator_sum` type. + auto sum = self - other; + auto reverse = other - self; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = sum.to_matrix({{degree_index, level_count}}, {}); + // auto got_reverse_matrix = reverse.to_matrix({{degree_index, + // level_count}}, {}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) - scaled_identity; + auto want_reverse_matrix = + scaled_identity - utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator - scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(function); + + // Produces an `operator_sum` type. + auto sum = self - other; + auto reverse = other - self; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = sum.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); auto got_reverse_matrix = + // reverse.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) + scaled_identity; + auto want_reverse_matrix = + scaled_identity + utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator * scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(const_scale_factor); + + // Produces an `product_operator` type. + auto product = self * other; + auto reverse = other * self; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = product.to_matrix({{degree_index, level_count}}, {}); + // auto got_reverse_matrix = reverse.to_matrix({{degree_index, + // level_count}}, {}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) * scaled_identity; + auto want_reverse_matrix = + scaled_identity * utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } + + // `elementary_operator * scalar_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::scalar_operator(function); + + // Produces an `product_operator` type. + auto product = self * other; + auto reverse = other * self; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto scaled_identity = const_scale_factor * utils_0::id_matrix(level_count); + // auto got_matrix = product.to_matrix({{degree_index, level_count}}, + // {"value", const_scale_factor}); auto got_reverse_matrix = + // reverse.to_matrix({{degree_index, level_count}}, {"value", + // const_scale_factor}); + auto want_matrix = + utils_0::annihilate_matrix(level_count) * scaled_identity; + auto want_reverse_matrix = + scaled_identity * utils_0::annihilate_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverese_matrix); + } +} + +/// Prebuilt elementary ops against one another. +TEST(ExpressionTester, checkPreBuiltElementaryOpsSelf) { + + /// Keeping this fixed throughout. + int level_count = 3; + + // Addition, same DOF. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(0); + + // Produces an `operator_sum` type. + auto sum = self + other; + ASSERT_TRUE(sum.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + // auto got_matrix = sum.to_matrix({{0, level_count}}, {}); + auto want_matrix = utils_0::annihilate_matrix(level_count) + + utils_0::create_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Addition, different DOF's. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(1); + + // Produces an `operator_sum` type. + auto sum = self + other; + ASSERT_TRUE(sum.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto annihilate_full = + cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto create_full = cudaq::kronecker(utils_0::create_matrix(level_count), + utils_0::id_matrix(level_count)); + // auto got_matrix = sum.to_matrix({{0, level_count}}, {}); + auto want_matrix = annihilate_full + create_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Subtraction, same DOF. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(0); + + // Produces an `operator_sum` type. + auto sum = self - other; + ASSERT_TRUE(sum.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + // auto got_matrix = sum.to_matrix({{0, level_count}}, {}); + auto want_matrix = utils_0::annihilate_matrix(level_count) - + utils_0::create_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Subtraction, different DOF's. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(1); + + // Produces an `operator_sum` type. + auto sum = self - other; + ASSERT_TRUE(sum.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto annihilate_full = + cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto create_full = cudaq::kronecker(utils_0::create_matrix(level_count), + utils_0::id_matrix(level_count)); + // auto got_matrix = sum.to_matrix({{0, level_count}}, {}); + auto want_matrix = annihilate_full - create_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Multiplication, same DOF. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(0); + + // Produces an `product_operator` type. + auto product = self * other; + ASSERT_TRUE(product.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + // auto got_matrix = product.to_matrix({{0, level_count}}, {}); + auto want_matrix = utils_0::annihilate_matrix(level_count) * + utils_0::create_matrix(level_count); + // utils_0::checkEqual(want_matrix, got_matrix); + } + + // Multiplication, different DOF's. + { + auto self = cudaq::elementary_operator::annihilate(0); + auto other = cudaq::elementary_operator::create(1); + + // Produces an `product_operator` type. + auto product = self * other; + ASSERT_TRUE(product.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto annihilate_full = + cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto create_full = cudaq::kronecker(utils_0::create_matrix(level_count), + utils_0::id_matrix(level_count)); + // auto got_matrix = product.to_matrix({{0, level_count}}, {}); + auto want_matrix = annihilate_full * create_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } +} + +/// Testing arithmetic between elementary operators and operator +/// sums. +TEST(ExpressionTester, checkElementaryOpsAgainstOpSum) { + + /// Keeping this fixed throughout. + int level_count = 3; + + /// `elementary_operator + operator_sum` and `operator_sum + + /// elementary_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + /// Creating an arbitrary operator sum to work against. + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + + auto got = self + operator_sum; + auto reverse = operator_sum + self; + + ASSERT_TRUE(got.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + + // auto got_matrix = got.to_matrix({{0, level_count}, {1, level_count}}, + // {}); auto got_reverse_matrix = reverse.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = self_full + term_0_full + term_1_full; + auto want_reverse_matrix = term_0_full + term_1_full + self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + /// `elementary_operator - operator_sum` and `operator_sum - + /// elementary_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + /// Creating an arbitrary operator sum to work against. + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + + auto got = self - operator_sum; + auto reverse = operator_sum - self; + + ASSERT_TRUE(got.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + + // auto got_matrix = got.to_matrix({{0, level_count}, {1, level_count}}, + // {}); auto got_reverse_matrix = reverse.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = self_full - term_0_full - term_1_full; + auto want_reverse_matrix = term_0_full + term_1_full - self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + /// `elementary_operator * operator_sum` and `operator_sum * + /// elementary_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + /// Creating an arbitrary operator sum to work against. + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + + auto got = self * operator_sum; + auto reverse = operator_sum * self; + + ASSERT_TRUE(got.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto &term : got.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + + for (auto &term : reverse.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + auto sum_full = term_0_full + term_1_full; + + // auto got_matrix = got.to_matrix({{0, level_count}, {1, level_count}}, + // {}); auto got_reverse_matrix = reverse.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = self_full * sum_full; + auto want_reverse_matrix = sum_full * self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + // utils_0::checkEqual(want_reverse_matrix, got_reverse_matrix); + } + + /// `operator_sum += elementary_operator` + { + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + operator_sum += cudaq::elementary_operator::annihilate(0); + + ASSERT_TRUE(operator_sum.term_count() == 3); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + + // auto got_matrix = operator_sum.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = term_0_full + term_1_full + self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } + + /// `operator_sum -= elementary_operator` + { + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + operator_sum -= cudaq::elementary_operator::annihilate(0); + + ASSERT_TRUE(operator_sum.term_count() == 3); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + + // auto got_matrix = operator_sum.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = term_0_full + term_1_full - self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } + + /// `operator_sum *= elementary_operator` + { + auto self = cudaq::elementary_operator::annihilate(0); + /// Creating an arbitrary operator sum to work against. + auto operator_sum = cudaq::elementary_operator::create(0) + + cudaq::elementary_operator::identity(1); + + operator_sum *= self; + + ASSERT_TRUE(operator_sum.term_count() == 2); + + for (auto &term : operator_sum.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + + /// Check the matrices. + /// FIXME: Comment me back in when `to_matrix` is implemented. + + auto self_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::annihilate_matrix(level_count)); + auto term_0_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::create_matrix(level_count)); + auto term_1_full = cudaq::kronecker(utils_0::id_matrix(level_count), + utils_0::id_matrix(level_count)); + auto sum_full = term_0_full + term_1_full; + + // auto got_matrix = operator_sum.to_matrix({{0, level_count}, {1, + // level_count}}, {}); + auto want_matrix = sum_full * self_full; + // utils_0::checkEqual(want_matrix, got_matrix); + } +} diff --git a/unittests/dynamics/elementary_ops_simple.cpp b/unittests/dynamics/elementary_ops_simple.cpp new file mode 100644 index 0000000000..172beeffe8 --- /dev/null +++ b/unittests/dynamics/elementary_ops_simple.cpp @@ -0,0 +1,208 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/operators.h" +#include + +namespace utils { +void checkEqual(cudaq::matrix_2 a, cudaq::matrix_2 b) { + ASSERT_EQ(a.get_rank(), b.get_rank()); + ASSERT_EQ(a.get_rows(), b.get_rows()); + ASSERT_EQ(a.get_columns(), b.get_columns()); + ASSERT_EQ(a.get_size(), b.get_size()); + for (std::size_t i = 0; i < a.get_rows(); i++) { + for (std::size_t j = 0; j < a.get_columns(); j++) { + double a_val = a[{i, j}].real(); + double b_val = b[{i, j}].real(); + EXPECT_NEAR(a_val, b_val, 1e-8); + } + } +} + +cudaq::matrix_2 zero_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + return mat; +} + +cudaq::matrix_2 id_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = 1.0 + 0.0j; + return mat; +} + +cudaq::matrix_2 annihilate_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 create_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 position_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 momentum_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 number_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = static_cast(i) + 0.0j; + return mat; +} + +cudaq::matrix_2 parity_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + return mat; +} + +// cudaq::matrix_2 displace_matrix(std::size_t size, +// std::complex amplitude) { +// auto mat = cudaq::matrix_2(size, size); +// for (std::size_t i = 0; i + 1 < size; i++) { +// mat[{i + 1, i}] = +// amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; +// mat[{i, i + 1}] = -1. * std::conj(amplitude) * (0.5 * 'j') * +// std::sqrt(static_cast(i + 1)) + +// 0.0 * 'j'; +// } +// return mat.exp(); +// } + +} // namespace utils + +TEST(ExpressionTester, checkPreBuiltElementaryOps) { + std::vector levels = {2, 3, 4, 5}; + + // Keeping this fixed throughout. + int degree_index = 0; + + // Identity operator. + { + for (auto level_count : levels) { + // cudaq::operators::identity(int degree) + auto id = cudaq::elementary_operator::identity(degree_index); + auto got_id = id.to_matrix({{degree_index, level_count}}, {}); + auto want_id = utils::id_matrix(level_count); + utils::checkEqual(want_id, got_id); + } + } + + // Zero operator. + { + for (auto level_count : levels) { + auto zero = cudaq::elementary_operator::zero(degree_index); + auto got_zero = zero.to_matrix({{degree_index, level_count}}, {}); + auto want_zero = utils::zero_matrix(level_count); + utils::checkEqual(want_zero, got_zero); + } + } + + // Annihilation operator. + { + for (auto level_count : levels) { + auto annihilate = cudaq::elementary_operator::annihilate(degree_index); + auto got_annihilate = + annihilate.to_matrix({{degree_index, level_count}}, {}); + auto want_annihilate = utils::annihilate_matrix(level_count); + utils::checkEqual(want_annihilate, got_annihilate); + } + } + + // Creation operator. + { + for (auto level_count : levels) { + auto create = cudaq::elementary_operator::create(degree_index); + auto got_create = create.to_matrix({{degree_index, level_count}}, {}); + auto want_create = utils::create_matrix(level_count); + utils::checkEqual(want_create, got_create); + } + } + + // Position operator. + { + for (auto level_count : levels) { + auto position = cudaq::elementary_operator::position(degree_index); + auto got_position = position.to_matrix({{degree_index, level_count}}, {}); + auto want_position = utils::position_matrix(level_count); + utils::checkEqual(want_position, got_position); + } + } + + // Momentum operator. + { + for (auto level_count : levels) { + auto momentum = cudaq::elementary_operator::momentum(degree_index); + auto got_momentum = momentum.to_matrix({{degree_index, level_count}}, {}); + auto want_momentum = utils::momentum_matrix(level_count); + utils::checkEqual(want_momentum, got_momentum); + } + } + + // Number operator. + { + for (auto level_count : levels) { + auto number = cudaq::elementary_operator::number(degree_index); + auto got_number = number.to_matrix({{degree_index, level_count}}, {}); + auto want_number = utils::number_matrix(level_count); + utils::checkEqual(want_number, got_number); + } + } + + // Parity operator. + { + for (auto level_count : levels) { + auto parity = cudaq::elementary_operator::parity(degree_index); + auto got_parity = parity.to_matrix({{degree_index, level_count}}, {}); + auto want_parity = utils::parity_matrix(level_count); + utils::checkEqual(want_parity, got_parity); + } + } + + // // // Displacement operator. + // // { + // // for (auto level_count : levels) { + // // auto amplitude = 1.0 + 1.0j; + // // auto displace = cudaq::elementary_operator::displace(degree_index, + // // amplitude); auto got_displace = + // utils::displace.to_matrix({{degree_index, + // // level_count}}, {}); auto want_displace = + // displace_matrix(level_count, + // // amplitude); utils::checkEqual(want_displace, got_displace); + // // } + // // } + + // TODO: Squeeze operator. +} + +// TEST(ExpressionTester, checkCustomElementaryOps) { +// // pass +// } \ No newline at end of file diff --git a/unittests/dynamics/operator_sum.cpp b/unittests/dynamics/operator_sum.cpp new file mode 100644 index 0000000000..c68779ef45 --- /dev/null +++ b/unittests/dynamics/operator_sum.cpp @@ -0,0 +1,572 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/matrix.h" +#include "cudaq/operators.h" +#include + +namespace utils_2 { +void checkEqual(cudaq::matrix_2 a, cudaq::matrix_2 b) { + ASSERT_EQ(a.get_rank(), b.get_rank()); + ASSERT_EQ(a.get_rows(), b.get_rows()); + ASSERT_EQ(a.get_columns(), b.get_columns()); + ASSERT_EQ(a.get_size(), b.get_size()); + for (std::size_t i = 0; i < a.get_rows(); i++) { + for (std::size_t j = 0; j < a.get_columns(); j++) { + double a_val = a[{i, j}].real(); + double b_val = b[{i, j}].real(); + EXPECT_NEAR(a_val, b_val, 1e-8); + } + } +} + +cudaq::matrix_2 zero_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + return mat; +} + +cudaq::matrix_2 id_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = 1.0 + 0.0j; + return mat; +} + +cudaq::matrix_2 annihilate_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 create_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 position_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 momentum_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 number_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = static_cast(i) + 0.0j; + return mat; +} + +cudaq::matrix_2 parity_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + return mat; +} + +// cudaq::matrix_2 displace_matrix(std::size_t size, +// std::complex amplitude) { +// auto mat = cudaq::matrix_2(size, size); +// for (std::size_t i = 0; i + 1 < size; i++) { +// mat[{i + 1, i}] = +// amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; +// mat[{i, i + 1}] = -1. * std::conj(amplitude) * (0.5 * 'j') * +// std::sqrt(static_cast(i + 1)) + +// 0.0 * 'j'; +// } +// return mat.exp(); +// } + +} // namespace utils_2 + +// TEST(ExpressionTester, checkProductOperatorSimple) { +// std::vector levels = {2, 3, 4}; + +// // std::set uniqueDegrees; +// // std::copy(this->degrees.begin(), this->degrees.end(), +// // std::inserter(uniqueDegrees, uniqueDegrees.begin())); +// // std::copy(other.degrees.begin(), other.degrees.end(), +// // std::inserter(uniqueDegrees, uniqueDegrees.begin())); + +// // Arithmetic only between elementary operators with +// // same number of levels. +// { +// // Same degrees of freedom. +// { +// for (auto level_count : levels) { +// auto op0 = cudaq::elementary_operator::annihilate(0); +// auto op1 = cudaq::elementary_operator::create(0); + +// cudaq::product_operator got = op0 * op1; +// auto got_matrix = got.to_matrix({{0, level_count}}, {}); + +// auto matrix0 = _annihilate_matrix(level_count); +// auto matrix1 = _create_matrix(level_count); +// auto want_matrix = matrix0 * matrix1; + +// // ASSERT_TRUE(want_matrix == got_matrix); +// } +// } + +// // // Different degrees of freedom. +// // { +// // for (auto level_count : levels) { +// // auto op0 = cudaq::elementary_operator::annihilate(0); +// // auto op1 = cudaq::elementary_operator::create(1); + +// // cudaq::product_operator got = op0 * op1; +// // auto got_matrix = +// // got.to_matrix({{0, level_count}, {1, level_count}}, {}); + +// // cudaq::product_operator got_reverse = op1 * op0; +// // auto got_matrix_reverse = +// // got_reverse.to_matrix({{0, level_count}, {1, level_count}}, +// {}); + +// // auto identity = _id_matrix(level_count); +// // auto matrix0 = _annihilate_matrix(level_count); +// // auto matrix1 = _create_matrix(level_count); + +// // auto fullHilbert0 = identity.kronecker(matrix0); +// // auto fullHilbert1 = matrix1.kronecker(identity); +// // auto want_matrix = fullHilbert0 * fullHilbert1; +// // auto want_matrix_reverse = fullHilbert1 * fullHilbert0; + +// // // ASSERT_TRUE(want_matrix == got_matrix); +// // // ASSERT_TRUE(want_matrix_reverse == got_matrix_reverse); +// // } +// // } + +// // // Different degrees of freedom, non-consecutive. +// // { +// // for (auto level_count : levels) { +// // auto op0 = cudaq::elementary_operator::annihilate(0); +// // auto op1 = cudaq::elementary_operator::create(2); + +// // // cudaq::product_operator got = op0 * op1; +// // // auto got_matrix = +// got.to_matrix({{0,level_count},{2,level_count}}, +// // // {}); +// // } +// // } + +// // // Different degrees of freedom, non-consecutive but all dimensions +// // // provided. +// // { +// // for (auto level_count : levels) { +// // auto op0 = cudaq::elementary_operator::annihilate(0); +// // auto op1 = cudaq::elementary_operator::create(2); + +// // // cudaq::product_operator got = op0 * op1; +// // // auto got_matrix = +// // // +// got.to_matrix({{0,level_count},{1,level_count},{2,level_count}}, +// // {}); +// // } +// // } +// } +// } + +// TEST(ExpressionTester, checkProductOperatorSimple) { + +// std::complex value_0 = 0.1 + 0.1; +// std::complex value_1 = 0.1 + 1.0; +// std::complex value_2 = 2.0 + 0.1; +// std::complex value_3 = 2.0 + 1.0; + +// auto local_variable = true; +// auto function = [&](std::map> parameters) +// { +// if (!local_variable) +// throw std::runtime_error("Local variable not detected."); +// return parameters["value"]; +// }; + +// // Scalar Ops against Elementary Ops +// { +// // Identity against constant. +// { +// auto id_op = cudaq::elementary_operator::identity(0); +// auto scalar_op = cudaq::scalar_operator(value_0); + +// // auto multiplication = scalar_op * id_op; +// // auto addition = scalar_op + id_op; +// // auto subtraction = scalar_op - id_op; +// } + +// // Identity against constant from lambda. +// { +// auto id_op = cudaq::elementary_operator::identity(0); +// auto scalar_op = cudaq::scalar_operator(function); + +// // auto multiplication = scalar_op * id_op; +// // auto addition = scalar_op + id_op; +// // auto subtraction = scalar_op - id_op; +// } +// } +// } + +TEST(ExpressionTester, checkOperatorSumAgainstScalarOperator) { + + // `operator_sum * scalar_operator` and `scalar_operator * operator_sum` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto product = sum * cudaq::scalar_operator(1.0); + auto reverse = cudaq::scalar_operator(1.0) * sum; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum + scalar_operator` and `scalar_operator + operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto sum = original + cudaq::scalar_operator(1.0); + auto reverse = cudaq::scalar_operator(1.0) + original; + + ASSERT_TRUE(sum.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum - scalar_operator` and `scalar_operator - operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto difference = original - cudaq::scalar_operator(1.0); + auto reverse = cudaq::scalar_operator(1.0) - original; + + ASSERT_TRUE(difference.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum *= scalar_operator` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum *= cudaq::scalar_operator(1.0); + + ASSERT_TRUE(sum.term_count() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum += scalar_operator` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum += cudaq::scalar_operator(1.0); + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum -= scalar_operator` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum -= cudaq::scalar_operator(1.0); + + ASSERT_TRUE(sum.term_count() == 3); + } +} + +TEST(ExpressionTester, checkOperatorSumAgainstScalars) { + std::complex value = 0.1 + 0.1; + + // `operator_sum * double` and `double * operator_sum` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto product = sum * 2.0; + auto reverse = 2.0 * sum; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum + double` and `double + operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto sum = original + 2.0; + auto reverse = 2.0 + original; + + ASSERT_TRUE(sum.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum - double` and `double - operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto difference = original - 2.0; + auto reverse = 2.0 - original; + + ASSERT_TRUE(difference.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum *= double` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum *= 2.0; + + ASSERT_TRUE(sum.term_count() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum += double` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum += 2.0; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum -= double` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum -= 2.0; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum * std::complex` and `std::complex * + // operator_sum` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto product = sum * value; + auto reverse = value * sum; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum + std::complex` and `std::complex + + // operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto sum = original + value; + auto reverse = value + original; + + ASSERT_TRUE(sum.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum - std::complex` and `std::complex - + // operator_sum` + { + auto original = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto difference = original - value; + auto reverse = value - original; + + ASSERT_TRUE(difference.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `operator_sum *= std::complex` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum *= value; + + ASSERT_TRUE(sum.term_count() == 2); + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.term_count() == 2); + } + } + + // `operator_sum += std::complex` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum += value; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum -= std::complex` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum -= value; + + ASSERT_TRUE(sum.term_count() == 3); + } +} + +TEST(ExpressionTester, checkOperatorSumAgainstOperatorSum) { + // `operator_sum + operator_sum` + { + auto sum_0 = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + auto sum_1 = cudaq::elementary_operator::identity(0) + + cudaq::elementary_operator::annihilate(1) + + cudaq::elementary_operator::create(3); + + auto sum = sum_0 + sum_1; + + ASSERT_TRUE(sum.term_count() == 5); + } + + // `operator_sum - operator_sum` + { + auto sum_0 = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + auto sum_1 = cudaq::elementary_operator::identity(0) + + cudaq::elementary_operator::annihilate(1) + + cudaq::elementary_operator::create(2); + + auto difference = sum_0 - sum_1; + + ASSERT_TRUE(difference.term_count() == 5); + } + + // `operator_sum * operator_sum` + { + auto sum_0 = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + auto sum_1 = cudaq::elementary_operator::identity(0) + + cudaq::elementary_operator::annihilate(1) + + cudaq::elementary_operator::create(2); + + auto sum_product = sum_0 * sum_1; + + ASSERT_TRUE(sum_product.term_count() == 6); + for (auto term : sum_product.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + } + + // `operator_sum *= operator_sum` + { + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + auto sum_1 = cudaq::elementary_operator::identity(0) + + cudaq::elementary_operator::annihilate(1) + + cudaq::elementary_operator::create(2); + + sum *= sum_1; + + ASSERT_TRUE(sum.term_count() == 6); + for (auto term : sum.get_terms()) + ASSERT_TRUE(term.term_count() == 2); + } +} + +/// NOTE: Much of the simpler arithmetic between the two is tested in the +/// product operator test file. This mainly just tests the assignment operators +/// between the two types. +TEST(ExpressionTester, checkOperatorSumAgainstProduct) { + // `operator_sum += product_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum += product; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum -= product_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum -= product; + + ASSERT_TRUE(sum.term_count() == 3); + } + + // `operator_sum *= product_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + sum *= product; + + ASSERT_TRUE(sum.term_count() == 2); + + for (auto term : sum.get_terms()) { + ASSERT_TRUE(term.term_count() == 3); + } + } +} diff --git a/unittests/dynamics/product_operators_arithmetic.cpp b/unittests/dynamics/product_operators_arithmetic.cpp new file mode 100644 index 0000000000..f8b6be7742 --- /dev/null +++ b/unittests/dynamics/product_operators_arithmetic.cpp @@ -0,0 +1,591 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/matrix.h" +#include "cudaq/operators.h" +#include + +#include + +namespace utils_1 { +void checkEqual(cudaq::matrix_2 a, cudaq::matrix_2 b) { + ASSERT_EQ(a.get_rank(), b.get_rank()); + ASSERT_EQ(a.get_rows(), b.get_rows()); + ASSERT_EQ(a.get_columns(), b.get_columns()); + ASSERT_EQ(a.get_size(), b.get_size()); + for (std::size_t i = 0; i < a.get_rows(); i++) { + for (std::size_t j = 0; j < a.get_columns(); j++) { + double a_val = a[{i, j}].real(); + double b_val = b[{i, j}].real(); + EXPECT_NEAR(a_val, b_val, 1e-8); + } + } +} + +cudaq::matrix_2 zero_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + return mat; +} + +cudaq::matrix_2 id_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = 1.0 + 0.0j; + return mat; +} + +cudaq::matrix_2 annihilate_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i, i + 1}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 create_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) + mat[{i + 1, i}] = std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + return mat; +} + +cudaq::matrix_2 position_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = 0.5 * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 momentum_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i + 1 < size; i++) { + mat[{i + 1, i}] = + (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + mat[{i, i + 1}] = + -1. * (0.5j) * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; + } + return mat; +} + +cudaq::matrix_2 number_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = static_cast(i) + 0.0j; + return mat; +} + +cudaq::matrix_2 parity_matrix(std::size_t size) { + auto mat = cudaq::matrix_2(size, size); + for (std::size_t i = 0; i < size; i++) + mat[{i, i}] = std::pow(-1., static_cast(i)) + 0.0j; + return mat; +} + +// cudaq::matrix_2 displace_matrix(std::size_t size, +// std::complex amplitude) { +// auto mat = cudaq::matrix_2(size, size); +// for (std::size_t i = 0; i + 1 < size; i++) { +// mat[{i + 1, i}] = +// amplitude * std::sqrt(static_cast(i + 1)) + 0.0 * 'j'; +// mat[{i, i + 1}] = -1. * std::conj(amplitude) * (0.5 * 'j') * +// std::sqrt(static_cast(i + 1)) + +// 0.0 * 'j'; +// } +// return mat.exp(); +// } + +} // namespace utils_1 + +/// TODO: Not yet testing the output matrices coming from this arithmetic. + +TEST(ExpressionTester, checkProductOperatorSimpleMatrixChecks) { + std::vector levels = {2, 3, 4}; + + { + // Same degrees of freedom. + { + for (auto level_count : levels) { + auto op0 = cudaq::elementary_operator::annihilate(0); + auto op1 = cudaq::elementary_operator::create(0); + + cudaq::product_operator got = op0 * op1; + + // auto got_matrix = got.to_matrix({{0, level_count}}, {}); + + // auto matrix0 = _annihilate_matrix(level_count); + // auto matrix1 = _create_matrix(level_count); + // auto want_matrix = matrix0 * matrix1; + + // ASSERT_TRUE(want_matrix == got_matrix); + + std::vector want_degrees = {0}; + ASSERT_TRUE(got.degrees() == want_degrees); + } + } + + // Different degrees of freedom. + { + for (auto level_count : levels) { + auto op0 = cudaq::elementary_operator::annihilate(0); + auto op1 = cudaq::elementary_operator::create(1); + + cudaq::product_operator got = op0 * op1; + // auto got_matrix = + // got.to_matrix({{0, level_count}, {1, level_count}}, {}); + + cudaq::product_operator got_reverse = op1 * op0; + // auto got_matrix_reverse = + // got_reverse.to_matrix({{0, level_count}, {1, level_count}}, {}); + + // auto identity = _id_matrix(level_count); + // auto matrix0 = _annihilate_matrix(level_count); + // auto matrix1 = _create_matrix(level_count); + + // auto fullHilbert0 = identity.kronecker(matrix0); + // auto fullHilbert1 = matrix1.kronecker(identity); + // auto want_matrix = fullHilbert0 * fullHilbert1; + // auto want_matrix_reverse = fullHilbert1 * fullHilbert0; + + // ASSERT_TRUE(want_matrix == got_matrix); + // ASSERT_TRUE(want_matrix_reverse == got_matrix_reverse); + + std::vector want_degrees = {0, 1}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + } + + // Different degrees of freedom, non-consecutive. + { + for (auto level_count : levels) { + auto op0 = cudaq::elementary_operator::annihilate(0); + auto op1 = cudaq::elementary_operator::create(2); + + cudaq::product_operator got = op0 * op1; + // auto got_matrix = got.to_matrix({{0,level_count},{2,level_count}}, + // {}); + + cudaq::product_operator got_reverse = op1 * op0; + + std::vector want_degrees = {0, 2}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + } + + // Different degrees of freedom, non-consecutive but all dimensions + // provided. + { + for (auto level_count : levels) { + auto op0 = cudaq::elementary_operator::annihilate(0); + auto op1 = cudaq::elementary_operator::create(2); + + cudaq::product_operator got = op0 * op1; + // auto got_matrix = + // got.to_matrix({{0,level_count},{1,level_count},{2,level_count}}, {}); + + cudaq::product_operator got_reverse = op1 * op0; + + std::vector want_degrees = {0, 2}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + } + } +} + +TEST(ExpressionTester, checkProductOperatorSimpleContinued) { + + std::complex value_0 = 0.1 + 0.1; + std::complex value_1 = 0.1 + 1.0; + std::complex value_2 = 2.0 + 0.1; + std::complex value_3 = 2.0 + 1.0; + + auto local_variable = true; + auto function = [&](std::map> parameters) { + if (!local_variable) + throw std::runtime_error("Local variable not detected."); + return parameters["value"]; + }; + + // Scalar Ops against Elementary Ops + { + // Annihilation against constant. + { + auto id_op = cudaq::elementary_operator::annihilate(0); + auto scalar_op = cudaq::scalar_operator(value_0); + + auto got = scalar_op * id_op; + auto got_reverse = scalar_op * id_op; + + std::vector want_degrees = {0}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + + // Annihilation against constant from lambda. + { + auto id_op = cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(function); + + auto got = scalar_op * id_op; + auto got_reverse = scalar_op * id_op; + + std::vector want_degrees = {1}; + ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_TRUE(got_reverse.degrees() == want_degrees); + } + } +} + +TEST(ExpressionTester, checkProductOperatorAgainstScalars) { + std::complex value_0 = 0.1 + 0.1; + + /// `product_operator + complex` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto sum = value_0 + product_op; + auto reverse = product_op + value_0; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(sum.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator + double` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto sum = 2.0 + product_op; + auto reverse = product_op + 2.0; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(sum.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator + scalar_operator` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(1.0); + + auto sum = scalar_op + product_op; + auto reverse = product_op + scalar_op; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(sum.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator - complex` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto difference = value_0 - product_op; + auto reverse = product_op - value_0; + + ASSERT_TRUE(difference.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(difference.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator - double` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto difference = 2.0 - product_op; + auto reverse = product_op - 2.0; + + ASSERT_TRUE(difference.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(difference.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator - scalar_operator` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(1.0); + + auto difference = scalar_op - product_op; + auto reverse = product_op - scalar_op; + + ASSERT_TRUE(difference.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(difference.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator * complex` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto product = value_0 * product_op; + auto reverse = product_op * value_0; + + ASSERT_TRUE(product.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator * double` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + + auto product = 2.0 * product_op; + auto reverse = product_op * 2.0; + + ASSERT_TRUE(product.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator * scalar_operator` + { + auto product_op = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(1.0); + + auto product = scalar_op * product_op; + auto reverse = product_op * scalar_op; + + ASSERT_TRUE(product.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + // ASSERT_TRUE(reverse.degrees() == want_degrees); + } + + /// `product_operator *= complex` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + product *= value_0; + + ASSERT_TRUE(product.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + } + + /// `product_operator *= double` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + product *= 2.0; + + ASSERT_TRUE(product.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + } + + /// `product_operator *= scalar_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto scalar_op = cudaq::scalar_operator(1.0); + + product *= scalar_op; + + ASSERT_TRUE(product.term_count() == 3); + + std::vector want_degrees = {0, 1}; + // ASSERT_TRUE(product.degrees() == want_degrees); + } +} + +TEST(ExpressionTester, checkProductOperatorAgainstProduct) { + + // `product_operator + product_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto term_1 = cudaq::elementary_operator::create(1) * + cudaq::elementary_operator::annihilate(2); + + auto sum = term_0 + term_1; + + ASSERT_TRUE(sum.term_count() == 2); + } + + // `product_operator - product_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto term_1 = cudaq::elementary_operator::create(1) * + cudaq::elementary_operator::annihilate(2); + + auto difference = term_0 - term_1; + + ASSERT_TRUE(difference.term_count() == 2); + } + + // `product_operator * product_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto term_1 = cudaq::elementary_operator::create(1) * + cudaq::elementary_operator::annihilate(2); + + auto product = term_0 * term_1; + + ASSERT_TRUE(product.term_count() == 4); + } + + // `product_operator *= product_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto term_1 = cudaq::elementary_operator::create(1) * + cudaq::elementary_operator::annihilate(2); + + term_0 *= term_1; + + ASSERT_TRUE(term_0.term_count() == 4); + } +} + +TEST(ExpressionTester, checkProductOperatorAgainstElementary) { + + // `product_operator + elementary_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto elementary = cudaq::elementary_operator::create(1); + + auto sum = product + elementary; + auto reverse = elementary + product; + + ASSERT_TRUE(sum.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + } + + // `product_operator - elementary_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto elementary = cudaq::elementary_operator::create(1); + + auto difference = product - elementary; + auto reverse = elementary - product; + + ASSERT_TRUE(difference.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + } + + // `product_operator * elementary_operator` + { + auto term_0 = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto elementary = cudaq::elementary_operator::create(1); + + auto product = term_0 * elementary; + auto reverse = elementary * term_0; + + ASSERT_TRUE(product.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `product_operator *= elementary_operator` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto elementary = cudaq::elementary_operator::create(1); + + product *= elementary; + + ASSERT_TRUE(product.term_count() == 3); + } +} + +TEST(ExpressionTester, checkProductOperatorAgainstOperatorSum) { + + // `product_operator + operator_sum` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto original_sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto sum = product + original_sum; + auto reverse = original_sum + product; + + ASSERT_TRUE(sum.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `product_operator - operator_sum` + { + auto product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto original_sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto difference = product - original_sum; + auto reverse = original_sum - product; + + ASSERT_TRUE(difference.term_count() == 3); + ASSERT_TRUE(reverse.term_count() == 3); + } + + // `product_operator * operator_sum` + { + auto original_product = cudaq::elementary_operator::annihilate(0) * + cudaq::elementary_operator::annihilate(1); + auto sum = cudaq::elementary_operator::create(1) + + cudaq::elementary_operator::create(2); + + auto product = original_product * sum; + auto reverse = sum * original_product; + + ASSERT_TRUE(product.term_count() == 2); + ASSERT_TRUE(reverse.term_count() == 2); + + for (auto term : product.get_terms()) { + ASSERT_TRUE(term.term_count() == 3); + } + + for (auto term : reverse.get_terms()) { + ASSERT_TRUE(term.term_count() == 3); + } + } +} diff --git a/unittests/dynamics/scalar_ops_arithmetic.cpp b/unittests/dynamics/scalar_ops_arithmetic.cpp new file mode 100644 index 0000000000..a8d6054a32 --- /dev/null +++ b/unittests/dynamics/scalar_ops_arithmetic.cpp @@ -0,0 +1,505 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/matrix.h" +#include "cudaq/operators.h" +#include + +TEST(ExpressionTester, checkScalarOpsArithmeticDoubles) { + // Arithmetic overloads against complex doubles. + std::complex value_0 = 0.1 + 0.1; + std::complex value_1 = 0.1 + 1.0; + std::complex value_2 = 2.0 + 0.1; + std::complex value_3 = 2.0 + 1.0; + + auto local_variable = true; + auto function = [&](std::map> parameters) { + if (!local_variable) + throw std::runtime_error("Local variable not detected."); + return parameters["value"]; + }; + + // + : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + + auto new_scalar_op = value_1 + scalar_op; + // function + scalar_op; + auto reverse_order_op = scalar_op + value_1; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_1 + value_0; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(want_value), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op + reverse_order_op; + auto got_value_third = third_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value + want_value), + 1e-5); + } + + // + : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + + auto new_scalar_op = value_0 + scalar_op; + auto reverse_order_op = scalar_op + value_0; + + auto got_value = new_scalar_op.evaluate({{"value", value_1}}); + auto got_value_1 = reverse_order_op.evaluate({{"value", value_1}}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 + value_0), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op + reverse_order_op; + auto got_value_third = third_op.evaluate({{"value", value_1}}); + auto want_value = value_0 + value_1 + value_1 + value_0; + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // - : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_1); + + auto new_scalar_op = value_3 - scalar_op; + auto reverse_order_op = scalar_op - value_3; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 - value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 - value_3), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op - reverse_order_op; + auto got_value_third = third_op.evaluate({}); + auto want_value = (value_3 - value_1) - (value_1 - value_3); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // - : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + + auto new_scalar_op = value_2 - scalar_op; + auto reverse_order_op = scalar_op - value_2; + + auto got_value = new_scalar_op.evaluate({{"value", value_1}}); + auto got_value_1 = reverse_order_op.evaluate({{"value", value_1}}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 - value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 - value_2), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op - reverse_order_op; + auto got_value_third = third_op.evaluate({{"value", value_1}}); + auto want_value = (value_2 - value_1) - (value_1 - value_2); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // * : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + + auto new_scalar_op = value_3 * scalar_op; + auto reverse_order_op = scalar_op * value_3; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 * value_2), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_2 * value_3), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op * reverse_order_op; + auto got_value_third = third_op.evaluate({}); + auto want_value = (value_3 * value_2) * (value_2 * value_3); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // * : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + + auto new_scalar_op = value_3 * scalar_op; + auto reverse_order_op = scalar_op * value_3; + + auto got_value = new_scalar_op.evaluate({{"value", value_2}}); + auto got_value_1 = reverse_order_op.evaluate({{"value", value_2}}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 * value_2), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_2 * value_3), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op * reverse_order_op; + auto got_value_third = third_op.evaluate({{"value", value_2}}); + auto want_value = (value_3 * value_2) * (value_2 * value_3); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // / : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + + auto new_scalar_op = value_3 / scalar_op; + auto reverse_order_op = scalar_op / value_3; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_3 / value_2), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op / reverse_order_op; + auto got_value_third = third_op.evaluate({}); + auto want_value = (value_2 / value_3) / (value_3 / value_2); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // / : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + + auto new_scalar_op = value_3 / scalar_op; + auto reverse_order_op = scalar_op / value_3; + + auto got_value = new_scalar_op.evaluate({{"value", value_1}}); + auto got_value_1 = reverse_order_op.evaluate({{"value", value_1}}); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_1 / value_3), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_3 / value_1), 1e-5); + + // Checking composition of many scalar operators. + auto third_op = new_scalar_op / reverse_order_op; + auto got_value_third = third_op.evaluate({{"value", value_1}}); + auto want_value = (value_1 / value_3) / (value_3 / value_1); + EXPECT_NEAR(std::abs(got_value_third), std::abs(want_value), 1e-5); + } + + // += : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + scalar_op += value_0; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_0), 1e-5); + } + + // += : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op += value_1; + + auto got_value = scalar_op.evaluate({{"value", value_0}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_1), 1e-5); + } + + // -= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + scalar_op -= value_0; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 - value_0), 1e-5); + } + + // -= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op -= value_1; + + auto got_value = scalar_op.evaluate({{"value", value_0}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 - value_1), 1e-5); + } + + // *= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + scalar_op *= value_3; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 * value_3), 1e-5); + } + + // *= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op *= value_3; + + auto got_value = scalar_op.evaluate({{"value", value_2}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 * value_3), 1e-5); + } + + // /= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + scalar_op /= value_3; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + } + + // /= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op /= value_3; + + auto got_value = scalar_op.evaluate({{"value", value_2}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + } +} + +TEST(ExpressionTester, checkScalarOpsArithmeticScalarOps) { + // Arithmetic overloads against other scalar ops. + std::complex value_0 = 0.1 + 0.1; + std::complex value_1 = 0.1 + 1.0; + std::complex value_2 = 2.0 + 0.1; + std::complex value_3 = 2.0 + 1.0; + + auto local_variable = true; + auto function = [&](std::map> parameters) { + if (!local_variable) + throw std::runtime_error("Local variable not detected."); + return parameters["value"]; + }; + + // I use another function here to make sure that local variables + // that may be unique to each ScalarOp's generators are both kept + // track of when we merge the generators. + auto alternative_local_variable = true; + auto alternative_function = + [&](std::map> parameters) { + if (!alternative_local_variable) + throw std::runtime_error("Local variable not detected."); + return parameters["other"]; + }; + + // + : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + auto other_scalar_op = cudaq::scalar_operator(value_1); + + auto new_scalar_op = other_scalar_op + scalar_op; + auto reverse_order_op = scalar_op + other_scalar_op; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_1 + value_0; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(want_value), 1e-5); + } + + // + : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other_scalar_op = cudaq::scalar_operator(alternative_function); + + auto new_scalar_op = other_scalar_op + scalar_op; + auto reverse_order_op = scalar_op + other_scalar_op; + + std::map> parameter_map = { + {"value", value_1}, {"other", value_0}}; + + auto got_value = new_scalar_op.evaluate(parameter_map); + auto got_value_1 = reverse_order_op.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 + value_0), 1e-5); + } + + // - : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + auto other_scalar_op = cudaq::scalar_operator(value_1); + + auto new_scalar_op = other_scalar_op - scalar_op; + auto reverse_order_op = scalar_op - other_scalar_op; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_1 - value_2; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(want_value), 1e-5); + } + + // - : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other_scalar_op = cudaq::scalar_operator(alternative_function); + + auto new_scalar_op = other_scalar_op - scalar_op; + auto reverse_order_op = scalar_op - other_scalar_op; + + std::map> parameter_map = { + {"value", value_1}, {"other", value_3}}; + + auto got_value = new_scalar_op.evaluate(parameter_map); + auto got_value_1 = reverse_order_op.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 - value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 - value_3), 1e-5); + } + + // * : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + auto other_scalar_op = cudaq::scalar_operator(value_3); + + auto new_scalar_op = other_scalar_op * scalar_op; + auto reverse_order_op = scalar_op * other_scalar_op; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_3 * value_2; + auto reverse_want_value = value_2 * value_3; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(reverse_want_value), 1e-5); + } + + // * : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other_scalar_op = cudaq::scalar_operator(alternative_function); + + auto new_scalar_op = other_scalar_op * scalar_op; + auto reverse_order_op = scalar_op * other_scalar_op; + + std::map> parameter_map = { + {"value", value_1}, {"other", value_3}}; + + auto got_value = new_scalar_op.evaluate(parameter_map); + auto got_value_1 = reverse_order_op.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 * value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_1 * value_3), 1e-5); + } + + // / : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + auto other_scalar_op = cudaq::scalar_operator(value_2); + + auto new_scalar_op = other_scalar_op / scalar_op; + auto reverse_order_op = scalar_op / other_scalar_op; + + auto got_value = new_scalar_op.evaluate({}); + auto got_value_1 = reverse_order_op.evaluate({}); + auto want_value = value_2 / value_0; + auto reverse_want_value = value_0 / value_2; + + EXPECT_NEAR(std::abs(got_value), std::abs(want_value), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(reverse_want_value), 1e-5); + } + + // / : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other_scalar_op = cudaq::scalar_operator(alternative_function); + + auto new_scalar_op = other_scalar_op / scalar_op; + auto reverse_order_op = scalar_op / other_scalar_op; + + std::map> parameter_map = { + {"value", value_0}, {"other", value_3}}; + + auto got_value = new_scalar_op.evaluate(parameter_map); + auto got_value_1 = reverse_order_op.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(got_value), std::abs(value_3 / value_0), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_0 / value_3), 1e-5); + } + + // += : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + auto other = cudaq::scalar_operator(value_0); + scalar_op += other; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_0), 1e-5); + } + + // += : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + auto other = cudaq::scalar_operator(value_1); + scalar_op += other; + + auto scalar_op_1 = cudaq::scalar_operator(function); + auto other_function = cudaq::scalar_operator(alternative_function); + scalar_op_1 += other_function; + + auto got_value = scalar_op.evaluate({{"value", value_0}}); + auto got_value_1 = + scalar_op_1.evaluate({{"value", value_0}, {"other", value_1}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 + value_1), 1e-5); + EXPECT_NEAR(std::abs(got_value_1), std::abs(value_0 + value_1), 1e-5); + } + + // -= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_0); + scalar_op -= value_0; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 - value_0), 1e-5); + } + + // -= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op -= value_1; + + auto got_value = scalar_op.evaluate({{"value", value_0}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_0 - value_1), 1e-5); + } + + // *= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + scalar_op *= value_3; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 * value_3), 1e-5); + } + + // *= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op *= value_3; + + auto got_value = scalar_op.evaluate({{"value", value_2}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 * value_3), 1e-5); + } + + // /= : Constant scalar operator. + { + auto scalar_op = cudaq::scalar_operator(value_2); + scalar_op /= value_3; + + auto got_value = scalar_op.evaluate({}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + } + + // /= : Scalar operator from lambda. + { + auto scalar_op = cudaq::scalar_operator(function); + scalar_op /= value_3; + + auto got_value = scalar_op.evaluate({{"value", value_2}}); + EXPECT_NEAR(std::abs(got_value), std::abs(value_2 / value_3), 1e-5); + } +} \ No newline at end of file diff --git a/unittests/dynamics/scalar_ops_simple.cpp b/unittests/dynamics/scalar_ops_simple.cpp new file mode 100644 index 0000000000..c60414d705 --- /dev/null +++ b/unittests/dynamics/scalar_ops_simple.cpp @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/matrix.h" +#include "cudaq/operators.h" +#include + +TEST(ExpressionTester, checkScalarOpsSimpleComplex) { + + std::complex value_0 = 0.1 + 0.1; + std::complex value_1 = 0.1 + 1.0; + std::complex value_2 = 2.0 + 0.1; + std::complex value_3 = 2.0 + 1.0; + + // From concrete values. + { + auto operator_0 = cudaq::scalar_operator(value_0); + auto operator_1 = cudaq::scalar_operator(value_1); + auto operator_2 = cudaq::scalar_operator(value_2); + auto operator_3 = cudaq::scalar_operator(value_3); + + auto got_value_0 = operator_0.evaluate({}); + auto got_value_1 = operator_1.evaluate({}); + auto got_value_2 = operator_2.evaluate({}); + auto got_value_3 = operator_3.evaluate({}); + + EXPECT_NEAR(std::abs(value_0), std::abs(got_value_0), 1e-5); + EXPECT_NEAR(std::abs(value_1), std::abs(got_value_1), 1e-5); + EXPECT_NEAR(std::abs(value_2), std::abs(got_value_2), 1e-5); + EXPECT_NEAR(std::abs(value_3), std::abs(got_value_3), 1e-5); + } + + // From a lambda function. + { + auto function = [](std::map> parameters) { + return parameters["value"]; + }; + + std::map> parameter_map; + + auto operator_0 = cudaq::scalar_operator(function); + auto operator_1 = cudaq::scalar_operator(function); + auto operator_2 = cudaq::scalar_operator(function); + auto operator_3 = cudaq::scalar_operator(function); + + parameter_map["value"] = value_0; + auto got_value_0 = operator_0.evaluate(parameter_map); + parameter_map["value"] = value_1; + auto got_value_1 = operator_1.evaluate(parameter_map); + parameter_map["value"] = value_2; + auto got_value_2 = operator_2.evaluate(parameter_map); + parameter_map["value"] = value_3; + auto got_value_3 = operator_3.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(value_0), std::abs(got_value_0), 1e-5); + EXPECT_NEAR(std::abs(value_1), std::abs(got_value_1), 1e-5); + EXPECT_NEAR(std::abs(value_2), std::abs(got_value_2), 1e-5); + EXPECT_NEAR(std::abs(value_3), std::abs(got_value_3), 1e-5); + } +} + +TEST(ExpressionTester, checkScalarOpsSimpleDouble) { + + double value_0 = 0.1; + double value_1 = 0.2; + double value_2 = 2.1; + double value_3 = 2.2; + + // From concrete values. + { + auto operator_0 = cudaq::scalar_operator(value_0); + auto operator_1 = cudaq::scalar_operator(value_1); + auto operator_2 = cudaq::scalar_operator(value_2); + auto operator_3 = cudaq::scalar_operator(value_3); + + auto got_value_0 = operator_0.evaluate({}); + auto got_value_1 = operator_1.evaluate({}); + auto got_value_2 = operator_2.evaluate({}); + auto got_value_3 = operator_3.evaluate({}); + + EXPECT_NEAR(std::abs(value_0), std::abs(got_value_0), 1e-5); + EXPECT_NEAR(std::abs(value_1), std::abs(got_value_1), 1e-5); + EXPECT_NEAR(std::abs(value_2), std::abs(got_value_2), 1e-5); + EXPECT_NEAR(std::abs(value_3), std::abs(got_value_3), 1e-5); + } + + // From a lambda function. + { + auto function = [](std::map> parameters) { + return parameters["value"]; + }; + + std::map> parameter_map; + + auto operator_0 = cudaq::scalar_operator(function); + auto operator_1 = cudaq::scalar_operator(function); + auto operator_2 = cudaq::scalar_operator(function); + auto operator_3 = cudaq::scalar_operator(function); + + parameter_map["value"] = value_0; + auto got_value_0 = operator_0.evaluate(parameter_map); + parameter_map["value"] = value_1; + auto got_value_1 = operator_1.evaluate(parameter_map); + parameter_map["value"] = value_2; + auto got_value_2 = operator_2.evaluate(parameter_map); + parameter_map["value"] = value_3; + auto got_value_3 = operator_3.evaluate(parameter_map); + + EXPECT_NEAR(std::abs(value_0), std::abs(got_value_0), 1e-5); + EXPECT_NEAR(std::abs(value_1), std::abs(got_value_1), 1e-5); + EXPECT_NEAR(std::abs(value_2), std::abs(got_value_2), 1e-5); + EXPECT_NEAR(std::abs(value_3), std::abs(got_value_3), 1e-5); + } +} \ No newline at end of file From 6473062deadf60c0c6f27489e0a2786e36b9d4af Mon Sep 17 00:00:00 2001 From: cuda-quantum-bot Date: Thu, 9 Jan 2025 17:37:24 +0000 Subject: [PATCH 02/40] Cleaning up docs preview for PR #12. From e0b3bf8a8da245f95ba0f629e8eae038ca3bdb78 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Thu, 9 Jan 2025 10:05:51 -0800 Subject: [PATCH 03/40] fixing spellings Signed-off-by: Sachin Pisal --- runtime/cudaq/operators.h | 18 +++++++++--------- runtime/cudaq/schedule.h | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/runtime/cudaq/operators.h b/runtime/cudaq/operators.h index 77ee4703bf..d668909daa 100644 --- a/runtime/cudaq/operators.h +++ b/runtime/cudaq/operators.h @@ -62,8 +62,8 @@ class operator_sum { /// 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 paramter names to their concrete, complex - /// values. + /// @arg `parameters` : A map of the parameter names to their concrete, + /// complex values. matrix_2 to_matrix(const std::map &dimensions, const std::map ¶ms = {}) const; @@ -116,13 +116,13 @@ class operator_sum { /// 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 + /// account, and does not try to reorder terms `blockwise`; it may hence /// evaluate to False, even if two operators in reality are the same. /// If the equality evaluates to True, on the other hand, the operators /// are guaranteed to represent the same transformation for all arguments. bool operator==(const operator_sum &other) const; - /// FIXME: Protect this once I can do deeper testing in unittests. + /// FIXME: Protect this once I can do deeper testing in `unittests`. // protected: std::vector get_terms() { return m_terms; } }; @@ -183,7 +183,7 @@ class product_operator : public operator_sum { /// 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 + /// 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. @@ -197,8 +197,8 @@ class product_operator : public operator_sum { /// 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 paramter names to their concrete, complex - /// values. + /// @arg `parameters` : A map of the parameter names to their concrete, + /// complex values. matrix_2 to_matrix(std::map dimensions, std::map> parameters); @@ -214,7 +214,7 @@ class product_operator : public operator_sum { /// operator. int term_count() const { return m_terms.size(); } - /// FIXME: Protect this once I can do deeper testing in unittests. + /// FIXME: Protect this once I can do deeper testing in `unittests`. // protected: std::vector> get_terms() { return m_terms; @@ -419,7 +419,7 @@ class scalar_operator : public product_operator { std::vector _operators_to_compose; /// NOTE: We should revisit these constructors and remove any that have - /// become unecessary as the implementation improves. + /// become unnecessary as the implementation improves. scalar_operator() = default; // Copy constructor. scalar_operator(const scalar_operator &other); diff --git a/runtime/cudaq/schedule.h b/runtime/cudaq/schedule.h index c9610cc75b..67d68fd951 100644 --- a/runtime/cudaq/schedule.h +++ b/runtime/cudaq/schedule.h @@ -1,5 +1,5 @@ /****************************************************************-*- C++ -*-**** - * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * * This source code and the accompanying materials are made available under * @@ -62,7 +62,7 @@ class Schedule { /// implementation phase. // Pointers. - /// @brief Dereference operator to access the current step value. + /// @brief `Dereference` operator to access the current step value. /// @return Reference to current complex step value. reference operator*() const; @@ -76,7 +76,7 @@ class Schedule { Schedule &operator++(); // Postfix increment. - /// @brief Postfix increment operator to move to the next step in the + /// @brief `Postfix` increment operator to move to the next step in the /// schedule. /// @return Copy of the previous Schedule state. Schedule operator++(int); From 749c1154b2b9c4c11c6e0bd2cfbf97723489cadf Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Thu, 9 Jan 2025 10:21:04 -0800 Subject: [PATCH 04/40] Adding base_integrator and base_time_stepper Signed-off-by: Sachin Pisal --- runtime/cudaq/base_integrator.h | 58 +++++++++++++++++++++++++++++++ runtime/cudaq/base_time_stepper.h | 19 ++++++++++ 2 files changed, 77 insertions(+) create mode 100644 runtime/cudaq/base_integrator.h create mode 100644 runtime/cudaq/base_time_stepper.h diff --git a/runtime/cudaq/base_integrator.h b/runtime/cudaq/base_integrator.h new file mode 100644 index 0000000000..a4dc982752 --- /dev/null +++ b/runtime/cudaq/base_integrator.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 +#include +#include +#include "schedule.h" +#include "operators.h" + +namespace cudaq { +template +class BaseIntegrator { +protected: + std::map integrator_options; + TState state; + double t; + std::map dimensions; + std::shared_ptr schedule; + std::shared_ptr hamiltonian; + std::shared_ptr> stepper; + std::vector> collapse_operators; + + virtual void post_init() = 0; + +public: + virtual ~BaseIntegrator() = default; + + void set_state(const TState &initial_state, double t0 = 0.0) { + state = initial_state; + t = t0; + } + + void set_system( + const std::map &dimensions, + std::shared_ptr schedule, + std::shared_ptr hamiltonian, + std::vector> collapse_operators = {} + ) { + this->dimensions = dimensions; + this->schedule = schedule; + this->hamiltonian = hamiltonian; + this->collapse_operators = collapse_operators; + } + + virtual void integrate(double t) = 0; + + std::pair get_state() const { + return {t, state}; + } +}; +} diff --git a/runtime/cudaq/base_time_stepper.h b/runtime/cudaq/base_time_stepper.h new file mode 100644 index 0000000000..d92184825b --- /dev/null +++ b/runtime/cudaq/base_time_stepper.h @@ -0,0 +1,19 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +namespace cudaq { +template +class BaseTimeStepper { +public: + virtual ~BaseTimeStepper() = default; + + virtual void compute(TState &state, double t) = 0; +}; +} \ No newline at end of file From a83d6d18b57613e030eb122dd4f6f8ff1588333a Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Mon, 13 Jan 2025 08:21:09 -0800 Subject: [PATCH 05/40] adding base_operators interface Signed-off-by: Sachin Pisal --- runtime/cudaq/base_operator.h | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 runtime/cudaq/base_operator.h diff --git a/runtime/cudaq/base_operator.h b/runtime/cudaq/base_operator.h new file mode 100644 index 0000000000..fe604e067d --- /dev/null +++ b/runtime/cudaq/base_operator.h @@ -0,0 +1,36 @@ +/****************************************************************-*- 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/matrix.h" +#include "cudaq/utils/tensor.h" +#include +#include +#include +#include + +namespace cudaq { +/// @brief Base class for all operator types. +class base_operator { +public: + virtual ~base_operator() = default; + + /// @brief Evaluate the operator with given parameters + virtual std::complex evaluate(const std::map> ¶meters) const = 0; + + /// @brief Convert the operator to a matrix representation. + virtual matrix_2 to_matrix(const std::map &dimensions, const std::map> ¶meters = {}) const = 0; + + /// @brief Convert the operator to a string representation. + virtual std::string to_string() const = 0; + + /// @brief Return the degrees of freedom that the operator acts on. + virtual std::vector get_degrees() const = 0; +}; +} From 49c559714ec222c6ee134d496347c2e0817f3af2 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Mon, 13 Jan 2025 10:29:33 -0800 Subject: [PATCH 06/40] keeping degrees to match the current implementation Signed-off-by: Sachin Pisal --- runtime/cudaq/base_operator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/cudaq/base_operator.h b/runtime/cudaq/base_operator.h index fe604e067d..ea3475a5ee 100644 --- a/runtime/cudaq/base_operator.h +++ b/runtime/cudaq/base_operator.h @@ -31,6 +31,6 @@ class base_operator { virtual std::string to_string() const = 0; /// @brief Return the degrees of freedom that the operator acts on. - virtual std::vector get_degrees() const = 0; + virtual std::vector degrees() const = 0; }; } From dc8a5f25a786264abae45a273962c77f12394b8a Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Mon, 13 Jan 2025 10:37:39 -0800 Subject: [PATCH 07/40] adding base integrator and time stepper Signed-off-by: Sachin Pisal --- runtime/cudaq/base_integrator.h | 71 +++++++++++++++---------------- runtime/cudaq/base_time_stepper.h | 6 +-- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/runtime/cudaq/base_integrator.h b/runtime/cudaq/base_integrator.h index a4dc982752..15c7c2f7e0 100644 --- a/runtime/cudaq/base_integrator.h +++ b/runtime/cudaq/base_integrator.h @@ -8,51 +8,48 @@ #pragma once +#include "base_time_stepper.h" +#include "operators.h" +#include "schedule.h" #include -#include #include -#include "schedule.h" -#include "operators.h" +#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; + 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; + virtual void post_init() = 0; public: - virtual ~BaseIntegrator() = default; - - void set_state(const TState &initial_state, double t0 = 0.0) { - state = initial_state; - t = t0; - } - - void set_system( - const std::map &dimensions, - std::shared_ptr schedule, - std::shared_ptr hamiltonian, - std::vector> collapse_operators = {} - ) { - this->dimensions = dimensions; - this->schedule = schedule; - this->hamiltonian = hamiltonian; - this->collapse_operators = collapse_operators; - } - - virtual void integrate(double t) = 0; - - std::pair get_state() const { - return {t, state}; - } + virtual ~BaseIntegrator() = default; + + void set_state(const TState &initial_state, double t0 = 0.0) { + state = initial_state; + t = t0; + } + + void set_system( + const std::map &dimensions, std::shared_ptr schedule, + std::shared_ptr hamiltonian, + std::vector> collapse_operators = {}) { + this->dimensions = dimensions; + this->schedule = schedule; + this->hamiltonian = hamiltonian; + this->collapse_operators = collapse_operators; + } + + virtual void integrate(double t) = 0; + + std::pair get_state() const { return {t, state}; } }; -} +} // namespace cudaq diff --git a/runtime/cudaq/base_time_stepper.h b/runtime/cudaq/base_time_stepper.h index d92184825b..ce1502269e 100644 --- a/runtime/cudaq/base_time_stepper.h +++ b/runtime/cudaq/base_time_stepper.h @@ -12,8 +12,8 @@ namespace cudaq { template class BaseTimeStepper { public: - virtual ~BaseTimeStepper() = default; + virtual ~BaseTimeStepper() = default; - virtual void compute(TState &state, double t) = 0; + virtual void compute(TState &state, double t) = 0; }; -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file From 73e8cb0b272d93d6cc2ecb3160a180631acb7f56 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Mon, 13 Jan 2025 20:33:12 -0800 Subject: [PATCH 08/40] * Adding base test cases for Runge-Kutta stepper and integrator * Removing base_operator class, as operator_sum will act as a base class Signed-off-by: Sachin Pisal --- runtime/cudaq/base_integrator.h | 8 +- runtime/cudaq/base_operator.h | 36 ----- runtime/cudaq/base_time_stepper.h | 2 +- runtime/cudaq/runge_kutta_integrator.h | 46 ++++++ runtime/cudaq/runge_kutta_time_stepper.h | 33 ++++ unittests/CMakeLists.txt | 2 + unittests/dynamics/runge_kutta_test_helpers.h | 24 +++ .../dynamics/test_runge_kutta_integrator.cpp | 132 ++++++++++++++++ .../test_runge_kutta_time_stepper.cpp | 148 ++++++++++++++++++ 9 files changed, 390 insertions(+), 41 deletions(-) delete mode 100644 runtime/cudaq/base_operator.h create mode 100644 runtime/cudaq/runge_kutta_integrator.h create mode 100644 runtime/cudaq/runge_kutta_time_stepper.h create mode 100644 unittests/dynamics/runge_kutta_test_helpers.h create mode 100644 unittests/dynamics/test_runge_kutta_integrator.cpp create mode 100644 unittests/dynamics/test_runge_kutta_time_stepper.cpp diff --git a/runtime/cudaq/base_integrator.h b/runtime/cudaq/base_integrator.h index 15c7c2f7e0..e3196bd52d 100644 --- a/runtime/cudaq/base_integrator.h +++ b/runtime/cudaq/base_integrator.h @@ -24,9 +24,9 @@ class BaseIntegrator { double t; std::map dimensions; std::shared_ptr schedule; - std::shared_ptr hamiltonian; + std::shared_ptr hamiltonian; std::shared_ptr> stepper; - std::vector> collapse_operators; + std::vector> collapse_operators; virtual void post_init() = 0; @@ -40,8 +40,8 @@ class BaseIntegrator { void set_system( const std::map &dimensions, std::shared_ptr schedule, - std::shared_ptr hamiltonian, - std::vector> collapse_operators = {}) { + std::shared_ptr hamiltonian, + std::vector> collapse_operators = {}) { this->dimensions = dimensions; this->schedule = schedule; this->hamiltonian = hamiltonian; diff --git a/runtime/cudaq/base_operator.h b/runtime/cudaq/base_operator.h deleted file mode 100644 index ea3475a5ee..0000000000 --- a/runtime/cudaq/base_operator.h +++ /dev/null @@ -1,36 +0,0 @@ -/****************************************************************-*- 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/matrix.h" -#include "cudaq/utils/tensor.h" -#include -#include -#include -#include - -namespace cudaq { -/// @brief Base class for all operator types. -class base_operator { -public: - virtual ~base_operator() = default; - - /// @brief Evaluate the operator with given parameters - virtual std::complex evaluate(const std::map> ¶meters) const = 0; - - /// @brief Convert the operator to a matrix representation. - virtual matrix_2 to_matrix(const std::map &dimensions, const std::map> ¶meters = {}) const = 0; - - /// @brief Convert the operator to a string representation. - virtual std::string to_string() const = 0; - - /// @brief Return the degrees of freedom that the operator acts on. - virtual std::vector degrees() const = 0; -}; -} diff --git a/runtime/cudaq/base_time_stepper.h b/runtime/cudaq/base_time_stepper.h index ce1502269e..4488de8b44 100644 --- a/runtime/cudaq/base_time_stepper.h +++ b/runtime/cudaq/base_time_stepper.h @@ -14,6 +14,6 @@ class BaseTimeStepper { public: virtual ~BaseTimeStepper() = default; - virtual void compute(TState &state, double t) = 0; + virtual void compute(TState &state, double t, double step_size) = 0; }; } // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/runge_kutta_integrator.h b/runtime/cudaq/runge_kutta_integrator.h new file mode 100644 index 0000000000..41d3da1715 --- /dev/null +++ b/runtime/cudaq/runge_kutta_integrator.h @@ -0,0 +1,46 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "base_integrator.h" +#include "runge_kutta_time_stepper.h" +#include + +namespace cudaq { +template +class RungeKuttaIntegrator : public BaseIntegrator { +public: + using DerivativeFunction = std::function; + + explicit RungeKuttaIntegrator(DerivativeFunction f) : stepper(std::make_shared>(f)) {} + + // Initializes the integrator + void post_init() override { + if (!this->stepper) { + throw std::runtime_error("Time stepper is not set"); + } + } + + // Advances the system's state from current time to `t` + void integrate(double target_t) override { + if (!this->schedule || !this->hamiltonian) { + throw std::runtime_error("System is not properly set!"); + } + + while (this->t < target_t) { + stepper->compute(this->state, this->t); + // Time step size + this->t += 0.01; + } + } + +private: + std::shared_ptr> stepper; +}; +} \ No newline at end of file diff --git a/runtime/cudaq/runge_kutta_time_stepper.h b/runtime/cudaq/runge_kutta_time_stepper.h new file mode 100644 index 0000000000..7718251a07 --- /dev/null +++ b/runtime/cudaq/runge_kutta_time_stepper.h @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "base_time_stepper.h" +#include + +namespace cudaq { +template +class RungeKuttaTimeStepper : public BaseTimeStepper { +public: + using DerivativeFunction = std::function; + + RungeKuttaTimeStepper(DerivativeFunction f) : derivativeFunc(f) {} + + void compute(TState &state, double t, double dt = 0.01) override { + // 4th order Runge-Kutta method + TState k1 = derivativeFunc(state, t); + TState k2 = derivativeFunc(state + (dt / 2.0) * k1, t + dt / 2.0); + TState k3 = derivativeFunc(state + (dt / 2.0) * k2, t + dt / 2.0); + TState k4 = derivativeFunc(state + dt * k3, t + dt); + + state = state + (dt / 6.0) * (k1 + 2 * k2 + 2 * k3 + k4); + } + +private: + DerivativeFunction derivativeFunc; +}; +} \ No newline at end of file diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 6e2213f083..c4517d1d16 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -49,6 +49,8 @@ set(CUDAQ_RUNTIME_TEST_SOURCES dynamics/elementary_ops_simple.cpp dynamics/elementary_ops_arithmetic.cpp dynamics/product_operators_arithmetic.cpp + dynamics/test_runge_kutta_time_stepper.cpp + dynamics/test_runge_kutta_integrator.cpp ) # Make it so we can get function symbols diff --git a/unittests/dynamics/runge_kutta_test_helpers.h b/unittests/dynamics/runge_kutta_test_helpers.h new file mode 100644 index 0000000000..28ec5c7723 --- /dev/null +++ b/unittests/dynamics/runge_kutta_test_helpers.h @@ -0,0 +1,24 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include + +// A simple state type +using TestState = double; + +// Simple derivative function: dx/dt = -x (exponential decay) +inline TestState simple_derivative(const TestState &state, double t) { + return -state; +} + +// A complex function: dx/dt = sin(t) +inline TestState sine_derivative(const TestState &state, double t) { + return std::sin(t); +} diff --git a/unittests/dynamics/test_runge_kutta_integrator.cpp b/unittests/dynamics/test_runge_kutta_integrator.cpp new file mode 100644 index 0000000000..431ea1457a --- /dev/null +++ b/unittests/dynamics/test_runge_kutta_integrator.cpp @@ -0,0 +1,132 @@ +// /******************************************************************************* +// * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * +// * All rights reserved. * +// * * +// * This source code and the accompanying materials are made available under * +// * the terms of the Apache License 2.0 which accompanies this distribution. * +// ******************************************************************************/ + +#include "runge_kutta_test_helpers.h" +#include "cudaq/runge_kutta_integrator.h" +#include +#include +#include + +using namespace cudaq; + +// Test fixture class +class RungeKuttaIntegratorTest : public ::testing::Test { +protected: + RungeKuttaIntegrator *integrator; + std::shared_ptr schedule; + std::shared_ptr hamiltonian; + + void SetUp() override { + integrator = new RungeKuttaIntegrator(simple_derivative); + // Initial state and time + integrator->set_state(1.0, 0.0); + + // A simple step sequence for the schedule + std::vector> steps = {0.1, 0.2, 0.3, 0.4, 0.5}; + + // Dummy parameters + std::vector parameters = {"param1"}; + + // A simple parameter function + auto value_function = [](const std::string ¶m, const std::complex &step) { + return step; + }; + + // A valid schedule instance + schedule = std::make_shared(steps, parameters, value_function); + + // A simple hamiltonian as an operator_sum + hamiltonian = std::make_shared(); + *hamiltonian += 0.5 * elementary_operator::identity(0); + *hamiltonian += 0.5 * elementary_operator::number(0); + + // System with valid components + integrator->set_system({{0, 2}}, schedule, hamiltonian); + } + + void TearDown() override { + delete integrator; + } +}; + +// Basic integration +TEST_F(RungeKuttaIntegratorTest, BasicIntegration) { + integrator->integrate(1.0); + + // Expected result: x(t) = e^(-t) + double expected = std::exp(-1.0); + + EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) << "Basic Runge-Kutta integration failed!"; +} + +// Time evolution +TEST_F(RungeKuttaIntegratorTest, TimeEvolution) { + integrator->integrate(2.0); + + double expected = 2.0; + + EXPECT_NEAR(integrator->get_state().first, expected, 1e-5) << "Integrator did not correctly update time!"; +} + +// Large step size +TEST_F(RungeKuttaIntegratorTest, LargeStepSize) { + integrator->integrate(5.0); + + double expected = std::exp(-5.0); + + EXPECT_NEAR(integrator->get_state().second, expected, 1e-1) << "Runge-Kutta integration failed for large step size!!"; +} + + +// // Integrating Sine function +// TEST_F(RungeKuttaIntegratorTest, SineFunction) { +// integrator = new RungeKuttaIntegrator(sine_derivative); +// integrator->set_state(1.0, 0.0); +// integrator->set_system({{0, 2}}, schedule, hamiltonian); + +// integrator->integrate(M_PI / 2); + +// double expected = std::cos(M_PI / 2); + +// EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) << "Runge-Kutta integration for sine function failed!"; +// } + + +// Small step size +TEST_F(RungeKuttaIntegratorTest, SmallStepIntegration) { + integrator->set_state(1.0, 0.0); + integrator->set_system({{0, 2}}, schedule, hamiltonian); + + double step_size = 0.001; + while (integrator->get_state().first < 1.0) { + integrator->integrate(integrator->get_state().first + step_size); + } + + double expected = std::exp(-1.0); + + EXPECT_NEAR(integrator->get_state().second, expected, 5e-4) << "Runge-Kutta integration for small step size failed!"; +} + + +// Large step size +TEST_F(RungeKuttaIntegratorTest, LargeStepIntegration) { + integrator->set_state(1.0, 0.0); + integrator->set_system({{0, 2}}, schedule, hamiltonian); + + double step_size = 0.5; + double t = 0.0; + double target_t = 1.0; + while (t < target_t) { + integrator->integrate(std::min(t + step_size, target_t)); + t += step_size; + } + + double expected = std::exp(-1.0); + + EXPECT_NEAR(integrator->get_state().second, expected, 1e-2) << "Runge-Kutta integration for large step size failed!"; +} diff --git a/unittests/dynamics/test_runge_kutta_time_stepper.cpp b/unittests/dynamics/test_runge_kutta_time_stepper.cpp new file mode 100644 index 0000000000..bc32bb587c --- /dev/null +++ b/unittests/dynamics/test_runge_kutta_time_stepper.cpp @@ -0,0 +1,148 @@ +/******************************************************************************* + * 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 "runge_kutta_test_helpers.h" +#include "cudaq/runge_kutta_time_stepper.h" +#include +#include +#include + +// Test fixture class +class RungeKuttaTimeStepperTest : public ::testing::Test { +protected: + std::shared_ptr> stepper; + + void SetUp() override { + stepper = std::make_shared>(simple_derivative); + } +}; + +// Single step integration +TEST_F(RungeKuttaTimeStepperTest, SingleStep) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 0.1; + + stepper->compute(state, t, dt); + + // Expected result using analytical solution: x(t) = e^(-t) + double expected = std::exp(-dt); + + EXPECT_NEAR(state, expected, 1e-3) << "Single step Runge-Kutta integration failed!"; +} + +// Multiple step integration +TEST_F(RungeKuttaTimeStepperTest, MultipleSteps) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 0.1; + int steps = 10; + + for (int i = 0; i < steps; i++) { + stepper->compute(state, t, dt); + } + + // Expected result: x(t) = e^(-t) + double expected = std::exp(-1.0); + + EXPECT_NEAR(state, expected, 1e-2) << "Multiple step Runge-Kutta integration failed!"; +} + +// Convergence to Analytical Solution +TEST_F(RungeKuttaTimeStepperTest, Convergence) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 0.01; + int steps = 100; + + for (int i = 0; i < steps; i++) { + stepper->compute(state, t, dt); + } + + double expected = std::exp(-1.0); + + EXPECT_NEAR(state, expected, 1e-3) << "Runge-Kutta integration does not converge!"; +} + +// // Integrating Sine function +// TEST_F(RungeKuttaTimeStepperTest, SineFunction) { +// auto sine_stepper = std::make_shared>(sine_derivative); + +// // Initial values +// double state = 0.0; +// double t = 0.0; +// double dt = 0.1; +// int steps = 10; + +// for (int i = 0; i < steps; i++) { +// sine_stepper->compute(state, t, dt); +// } + +// // Expected integral of sin(t) over [0, 1] is 1 - cos(1) +// double expected = 1 - std::cos(1); + +// EXPECT_NEAR(state, expected, 1e-2) << "Runge-Kutta integration for sine function failed!"; +// } + +// Handling small steps sizes +TEST_F(RungeKuttaTimeStepperTest, SmallStepSize) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 1e-5; + int steps = 100000; + + for (int i = 0; i < steps; i++) { + stepper->compute(state, t, dt); + } + + double expected = std::exp(-1.0); + + EXPECT_NEAR(state, expected, 1e-3) << "Runge-Kutta fails with small step sizes!"; +} + +// Handling large steps sizes +TEST_F(RungeKuttaTimeStepperTest, LargeStepSize) { + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 1.0; + + stepper->compute(state, t, dt); + + double expected = std::exp(-1.0); + + EXPECT_NEAR(state, expected, 1e-1) << "Runge-Kutta is unstable with large step sizes!"; +} + +// Constant derivative (dx/dt = 0) +TEST_F(RungeKuttaTimeStepperTest, ConstantFunction) { + auto constant_stepper = std::make_shared>( + [](const TestState &state, double t) { + return 0.0; + } + ); + + // Initial values + double state = 5.0; + double t = 0.0; + double dt = 0.1; + int steps = 10; + + for (int i = 0; i < steps; i++) { + constant_stepper->compute(state, t, dt); + } + + EXPECT_NEAR(state, 5.0, 1e-6) << "Runge-Kutta should not change a constant function!"; +} + + + From dbc04c7509ea193bef2000a1258339f879150c68 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Tue, 14 Jan 2025 15:12:20 -0800 Subject: [PATCH 09/40] * Adding helpers functionlity * Aggregating parameters * Extracting documentation * Extracting positional and keyword arguments * Generating all quantum states for given degrees and dimensions * Permuting a given matrix * Canonicalizing degrees * Adding test cases for the above functions * Formatting Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/CMakeLists.txt | 4 +- runtime/cudaq/dynamics/helpers.cpp | 135 +++++++++++++ runtime/cudaq/helpers.h | 42 ++++ runtime/cudaq/runge_kutta_integrator.h | 41 ++-- runtime/cudaq/runge_kutta_time_stepper.h | 24 +-- unittests/CMakeLists.txt | 1 + unittests/dynamics/runge_kutta_test_helpers.h | 4 +- unittests/dynamics/test_helpers.cpp | 189 ++++++++++++++++++ .../dynamics/test_runge_kutta_integrator.cpp | 134 ++++++------- .../test_runge_kutta_time_stepper.cpp | 152 +++++++------- 10 files changed, 549 insertions(+), 177 deletions(-) create mode 100644 runtime/cudaq/dynamics/helpers.cpp create mode 100644 runtime/cudaq/helpers.h create mode 100644 unittests/dynamics/test_helpers.cpp diff --git a/runtime/cudaq/dynamics/CMakeLists.txt b/runtime/cudaq/dynamics/CMakeLists.txt index 9709cd9a71..d608aba2c3 100644 --- a/runtime/cudaq/dynamics/CMakeLists.txt +++ b/runtime/cudaq/dynamics/CMakeLists.txt @@ -1,5 +1,5 @@ # ============================================================================ # -# Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. # +# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # # All rights reserved. # # # # This source code and the accompanying materials are made available under # @@ -11,7 +11,7 @@ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") set(INTERFACE_POSITION_INDEPENDENT_CODE ON) set(CUDAQ_OPS_SRC - scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp + scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp ) add_library(${LIBRARY_NAME} SHARED ${CUDAQ_OPS_SRC}) diff --git a/runtime/cudaq/dynamics/helpers.cpp b/runtime/cudaq/dynamics/helpers.cpp new file mode 100644 index 0000000000..ad86a2c26b --- /dev/null +++ b/runtime/cudaq/dynamics/helpers.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 "cudaq/helpers.h" +#include +#include + +namespace cudaq { +// Aggregate parameters from multiple mappings. +std::map OperatorHelpers::aggregate_parameters(const std::vector> ¶meter_mappings) { + std::map parameter_descriptions; + + for (const auto &descriptions : parameter_mappings) { + for (const auto &[key, new_desc] : descriptions) { + if (!parameter_descriptions[key].empty() && !new_desc.empty()) { + parameter_descriptions[key] += "\n---\n" + new_desc; + } else { + parameter_descriptions[key] = new_desc; + } + } + } + + return parameter_descriptions; +} + +// Extract documentation for a specific parameter from docstring. +std::string OperatorHelpers::parameter_docs(const std::string ¶m_name, const std::string &docs) { + if (param_name.empty() || docs.empty()) { + return ""; + } + + try { + std::regex keyword_pattern(R"(^\s*(Arguments|Args):\s*$)", std::regex::multiline); + std::regex param_pattern(R"(^\s*)" + param_name + R"(\s*(\(.*\))?:\s*(.*)$)", std::regex::multiline); + + std::smatch match; + std::sregex_iterator it(docs.begin(), docs.end(), keyword_pattern); + std::sregex_iterator end; + + if (it != end) { + std::string params_section = docs.substr(it->position() + it->length()); + if(std::regex_search(params_section, match, param_pattern)) { + std::string param_docs = match.str(2); + return std::regex_replace(param_docs, std::regex(R"(\s+)"), " "); + } + } + } catch (...) { + return ""; + } + + return ""; +} + +// Extract positional arguments and keyword-only arguments. +std::pair, std::map> OperatorHelpers::args_from_kwargs(const std::map &kwargs, + const std::vector &required_args, const std::vector &kwonly_args) { + std::vector extracted_args; + std::map kwonly_dict; + + for (const auto &arg : required_args) { + if (kwargs.count(arg)) { + extracted_args.push_back(kwargs.at(arg)); + } else { + throw std::invalid_argument("Missing required argument: " + arg); + } + } + + for (const auto &arg : kwonly_args) { + if (kwargs.count(arg)) { + kwonly_dict[arg] = kwargs.at(arg); + } + } + + return {extracted_args, kwonly_dict}; +} + +// Generate all possible quantum states for given degrees and dimensions +std::vector OperatorHelpers::generate_all_states(const std::vector °rees, const std::map &dimensions) { + if (degrees.empty()) { + return {}; + } + + std::vector> states; + for (int state = 0; state < dimensions.at(degrees[0]); state++) { + states.push_back({std::to_string(state)}); + } + + for (size_t i = 1; i < degrees.size(); i++) { + std::vector> new_states; + for (const auto ¤t : states) { + for (int state = 0; state < dimensions.at(degrees[i]); state++) { + auto new_entry = current; + new_entry.push_back(std::to_string(state)); + new_states.push_back(new_entry); + } + } + states = new_states; + } + + std::vector result; + for (const auto &state : states) { + std::ostringstream joined; + for (const auto &s : state) { + joined << s; + } + result.push_back(joined.str()); + } + return result; +} + +// Permute a given eigen matrix +void OperatorHelpers::permute_matrix(Eigen::MatrixXcd &matrix, const std::vector &permutation) { + Eigen::MatrixXcd permuted_matrix(matrix.rows(), matrix.cols()); + + for (size_t i = 0; i < permutation.size(); i++) { + for (size_t j = 0; j < permutation.size(); j++) { + permuted_matrix(i, j) = matrix(permutation[i], permutation[j]); + } + } + + matrix = permuted_matrix; +} + +// Canonicalize degrees by sorting in descending order +std::vector OperatorHelpers::canonicalize_degrees(const std::vector °rees) { + std::vector sorted_degrees = degrees; + std::sort(sorted_degrees.rbegin(), sorted_degrees.rend()); + return sorted_degrees; +} +} \ No newline at end of file diff --git a/runtime/cudaq/helpers.h b/runtime/cudaq/helpers.h new file mode 100644 index 0000000000..488ff3bf0c --- /dev/null +++ b/runtime/cudaq/helpers.h @@ -0,0 +1,42 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cudaq { +class OperatorHelpers { +public: + // Aggregate parameters from multiple mappings. + static std::map aggregate_parameters(const std::vector> ¶meter_mappings); + + // Extract documentation for a specific parameter from docstring. + static std::string parameter_docs(const std::string ¶m_name, const std::string &docs); + + // Extract positional arguments and keyword-only arguments. + static std::pair, std::map> args_from_kwargs(const std::map &kwargs, + const std::vector &required_args, const std::vector &kwonly_args); + + // Generate all possible quantum states for given degrees and dimensions. + static std::vector generate_all_states(const std::vector °rees, const std::map &dimensions); + + // Permute a given Eigen matrix. + static void permute_matrix(Eigen::MatrixXcd &matrix, const std::vector &permutation); + + // Canonicalize degrees by sorting in descending order. + static std::vector canonicalize_degrees(const std::vector °rees); +}; +} \ No newline at end of file diff --git a/runtime/cudaq/runge_kutta_integrator.h b/runtime/cudaq/runge_kutta_integrator.h index 41d3da1715..9914258386 100644 --- a/runtime/cudaq/runge_kutta_integrator.h +++ b/runtime/cudaq/runge_kutta_integrator.h @@ -16,31 +16,32 @@ namespace cudaq { template class RungeKuttaIntegrator : public BaseIntegrator { public: - using DerivativeFunction = std::function; + using DerivativeFunction = std::function; - explicit RungeKuttaIntegrator(DerivativeFunction f) : stepper(std::make_shared>(f)) {} + explicit RungeKuttaIntegrator(DerivativeFunction f) + : stepper(std::make_shared>(f)) {} - // Initializes the integrator - void post_init() override { - if (!this->stepper) { - throw std::runtime_error("Time stepper is not set"); - } + // Initializes the integrator + void post_init() override { + if (!this->stepper) { + throw std::runtime_error("Time stepper is not set"); } + } - // Advances the system's state from current time to `t` - void integrate(double target_t) override { - if (!this->schedule || !this->hamiltonian) { - throw std::runtime_error("System is not properly set!"); - } - - while (this->t < target_t) { - stepper->compute(this->state, this->t); - // Time step size - this->t += 0.01; - } + // Advances the system's state from current time to `t` + void integrate(double target_t) override { + if (!this->schedule || !this->hamiltonian) { + throw std::runtime_error("System is not properly set!"); } + while (this->t < target_t) { + stepper->compute(this->state, this->t); + // Time step size + this->t += 0.01; + } + } + private: - std::shared_ptr> stepper; + std::shared_ptr> stepper; }; -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/runge_kutta_time_stepper.h b/runtime/cudaq/runge_kutta_time_stepper.h index 7718251a07..1dcd1f69cc 100644 --- a/runtime/cudaq/runge_kutta_time_stepper.h +++ b/runtime/cudaq/runge_kutta_time_stepper.h @@ -13,21 +13,21 @@ namespace cudaq { template class RungeKuttaTimeStepper : public BaseTimeStepper { public: - using DerivativeFunction = std::function; + using DerivativeFunction = std::function; - RungeKuttaTimeStepper(DerivativeFunction f) : derivativeFunc(f) {} + RungeKuttaTimeStepper(DerivativeFunction f) : derivativeFunc(f) {} - void compute(TState &state, double t, double dt = 0.01) override { - // 4th order Runge-Kutta method - TState k1 = derivativeFunc(state, t); - TState k2 = derivativeFunc(state + (dt / 2.0) * k1, t + dt / 2.0); - TState k3 = derivativeFunc(state + (dt / 2.0) * k2, t + dt / 2.0); - TState k4 = derivativeFunc(state + dt * k3, t + dt); + void compute(TState &state, double t, double dt = 0.01) override { + // 4th order Runge-Kutta method + TState k1 = derivativeFunc(state, t); + TState k2 = derivativeFunc(state + (dt / 2.0) * k1, t + dt / 2.0); + TState k3 = derivativeFunc(state + (dt / 2.0) * k2, t + dt / 2.0); + TState k4 = derivativeFunc(state + dt * k3, t + dt); - state = state + (dt / 6.0) * (k1 + 2 * k2 + 2 * k3 + k4); - } + state = state + (dt / 6.0) * (k1 + 2 * k2 + 2 * k3 + k4); + } private: - DerivativeFunction derivativeFunc; + DerivativeFunction derivativeFunc; }; -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index c4517d1d16..3091dd6a4b 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -51,6 +51,7 @@ set(CUDAQ_RUNTIME_TEST_SOURCES dynamics/product_operators_arithmetic.cpp dynamics/test_runge_kutta_time_stepper.cpp dynamics/test_runge_kutta_integrator.cpp + dynamics/test_helpers.cpp ) # Make it so we can get function symbols diff --git a/unittests/dynamics/runge_kutta_test_helpers.h b/unittests/dynamics/runge_kutta_test_helpers.h index 28ec5c7723..4f93ffa242 100644 --- a/unittests/dynamics/runge_kutta_test_helpers.h +++ b/unittests/dynamics/runge_kutta_test_helpers.h @@ -15,10 +15,10 @@ using TestState = double; // Simple derivative function: dx/dt = -x (exponential decay) inline TestState simple_derivative(const TestState &state, double t) { - return -state; + return -state; } // A complex function: dx/dt = sin(t) inline TestState sine_derivative(const TestState &state, double t) { - return std::sin(t); + return std::sin(t); } diff --git a/unittests/dynamics/test_helpers.cpp b/unittests/dynamics/test_helpers.cpp new file mode 100644 index 0000000000..9e471a29bd --- /dev/null +++ b/unittests/dynamics/test_helpers.cpp @@ -0,0 +1,189 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/helpers.h" +#include +#include + +using namespace cudaq; + +TEST(OperatorHelpersTest, AggregateParameters_MultipleMappings) { + std::vector> mappings = { + {{"alpha", "Parameter A"}, {"beta", "Parameter B"}}, {{"alpha", "Updated Parameter A"}, {"gamma", "New Parameter"}} + }; + + auto result = OperatorHelpers::aggregate_parameters(mappings); + + EXPECT_EQ(result["alpha"], "Parameter A\n---\nUpdated Parameter A"); + EXPECT_EQ(result["beta"], "Parameter B"); + EXPECT_EQ(result["gamma"], "New Parameter"); +} + +TEST(OperatorHelpersTest, AggregateParameters_EmptyMappings) { + std::vector> mappings; + auto result = OperatorHelpers::aggregate_parameters(mappings); + + EXPECT_TRUE(result.empty()); +} + +TEST(OperatorHelpersTest, ParameterDocs_ValidExtraction) { + std::string docstring = + "Description of function.\n" + "Arguments:\n" + " alpha (float): The first parameter.\n" + " beta (int): The second parameter."; + + auto result = OperatorHelpers::parameter_docs("alpha", docstring); + EXPECT_EQ(result, "The first parameter."); + + result = OperatorHelpers::parameter_docs("beta", docstring); + EXPECT_EQ(result, "The second parameter."); +} + +TEST(OperatorHelpersTest, ParameterDocs_InvalidParam) { + std::string docstring = + "Description of function.\n" + "Arguments:\n" + " alpha (float): The first parameter.\n" + " beta (int): The second parameter."; + + auto result = OperatorHelpers::parameter_docs("gamma", docstring); + EXPECT_EQ(result, ""); +} + +TEST(OperatorHelpersTest, ParameterDocs_EmptyDocString) { + std::string docstring = ""; + auto result = OperatorHelpers::parameter_docs("alpha", docstring); + EXPECT_EQ(result, ""); +} + +TEST(OperatorHelpersTest, GenerateAllStates_TwoQubits) { + std::vector degrees = {0, 1}; + std::map dimensions = {{0, 2}, {1, 2}}; + + auto states = OperatorHelpers::generate_all_states(degrees, dimensions); + std::vector expected_states = {"00", "01", "10", "11"}; + + EXPECT_EQ(states, expected_states); +} + +TEST(OperatorHelpersTest, GenerateAllStates_ThreeQubits) { + std::vector degrees = {0, 1, 2}; + std::map dimensions = {{0, 2}, {1, 2}, {2, 2}}; + + auto states = OperatorHelpers::generate_all_states(degrees, dimensions); + std::vector expected_states = {"000", "001", "010", "011", "100", "101", "110", "111"}; + + EXPECT_EQ(states, expected_states); +} + +TEST(OperatorHelpersTest, GenerateAllStates_EmptyDegrees) { + std::vector degrees; + std::map dimensions; + + auto states = OperatorHelpers::generate_all_states(degrees, dimensions); + EXPECT_TRUE(states.empty()); +} + +TEST(OperatorHelpersTest, GenerateAllStates_MissingDegreesInMap) { + std::vector degrees = {0, 1, 2}; + std::map dimensions = {{0, 2}, {1, 2}}; + + EXPECT_THROW(OperatorHelpers::generate_all_states(degrees, dimensions), std::out_of_range); +} + +TEST(OperatorHelpersTest, PermuteMatrix_SingleSwap) { + Eigen::MatrixXcd matrix(2, 2); + matrix << 1, 2, + 3, 4; + + // Swap rows and columns + std::vector permutation = {1, 0}; + + OperatorHelpers::permute_matrix(matrix, permutation); + + Eigen::MatrixXcd expected(2, 2); + expected << 4, 3, + 2, 1; + + EXPECT_EQ(matrix, expected); +} + +TEST(OperatorHelpersTest, PermuteMatrix_IdentityPermutation) { + Eigen::MatrixXcd matrix(3, 3); + matrix << 1, 2, 3, + 4, 5, 6, + 7, 8, 9; + + // No change + std::vector permutation = {0, 1, 2}; + + OperatorHelpers::permute_matrix(matrix, permutation); + + Eigen::MatrixXcd expected(3, 3); + expected << 1, 2, 3, + 4, 5, 6, + 7, 8, 9; + + EXPECT_EQ(matrix, expected); +} + +TEST(OperatorHelpersTest, CanonicalizeDegrees_SortedDescending) { + std::vector degrees = {3, 1, 2}; + auto sorted = OperatorHelpers::canonicalize_degrees(degrees); + EXPECT_EQ(sorted, (std::vector{3, 2, 1})); +} + +TEST(OperatorHelpersTest, CanonicalizeDegrees_AlreadySorted) { + std::vector degrees = {5, 4, 3, 2, 1}; + auto sorted = OperatorHelpers::canonicalize_degrees(degrees); + EXPECT_EQ(sorted, (std::vector{5, 4, 3, 2, 1})); +} + +TEST(OperatorHelpersTest, CanonicalizeDegrees_EmptyList) { + std::vector degrees; + auto sorted = OperatorHelpers::canonicalize_degrees(degrees); + EXPECT_TRUE(sorted.empty()); +} + +TEST(OperatorHelpersTest, ArgsFromKwargs_ValidArgs) { + std::map kwargs = { + {"alpha", "0.5"}, + {"beta", "1.0"}, + {"gamma", "2.0"} + }; + + std::vector required_args = {"alpha", "beta"}; + std::vector kwonly_args = {"gamma"}; + + auto [args, kwonly] = OperatorHelpers::args_from_kwargs(kwargs, required_args, kwonly_args); + + EXPECT_EQ(args.size(), 2); + EXPECT_EQ(args[0], "0.5"); + EXPECT_EQ(args[1], "1.0"); + + EXPECT_EQ(kwonly.size(), 1); + EXPECT_EQ(kwonly["gamma"], "2.0"); +} + + +TEST(OperatorHelpersTest, ArgsFromKwargs_MissingRequiredArgs) { + std::map kwargs = { + {"beta", "1.0"}, + {"gamma", "2.0"} + }; + + std::vector required_args = {"alpha", "beta"}; + std::vector kwonly_args = {"gamma"}; + + EXPECT_THROW(OperatorHelpers::args_from_kwargs(kwargs, required_args, kwonly_args), std::invalid_argument); +} + + + + diff --git a/unittests/dynamics/test_runge_kutta_integrator.cpp b/unittests/dynamics/test_runge_kutta_integrator.cpp index 431ea1457a..c75a7c8d6d 100644 --- a/unittests/dynamics/test_runge_kutta_integrator.cpp +++ b/unittests/dynamics/test_runge_kutta_integrator.cpp @@ -1,88 +1,87 @@ // /******************************************************************************* -// * 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. * +// * 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 "runge_kutta_test_helpers.h" #include "cudaq/runge_kutta_integrator.h" +#include "runge_kutta_test_helpers.h" +#include #include #include -#include using namespace cudaq; // Test fixture class class RungeKuttaIntegratorTest : public ::testing::Test { protected: - RungeKuttaIntegrator *integrator; - std::shared_ptr schedule; - std::shared_ptr hamiltonian; + RungeKuttaIntegrator *integrator; + std::shared_ptr schedule; + std::shared_ptr hamiltonian; - void SetUp() override { - integrator = new RungeKuttaIntegrator(simple_derivative); - // Initial state and time - integrator->set_state(1.0, 0.0); + void SetUp() override { + integrator = new RungeKuttaIntegrator(simple_derivative); + // Initial state and time + integrator->set_state(1.0, 0.0); - // A simple step sequence for the schedule - std::vector> steps = {0.1, 0.2, 0.3, 0.4, 0.5}; + // A simple step sequence for the schedule + std::vector> steps = {0.1, 0.2, 0.3, 0.4, 0.5}; - // Dummy parameters - std::vector parameters = {"param1"}; + // Dummy parameters + std::vector parameters = {"param1"}; - // A simple parameter function - auto value_function = [](const std::string ¶m, const std::complex &step) { - return step; - }; + // A simple parameter function + auto value_function = [](const std::string ¶m, + const std::complex &step) { return step; }; - // A valid schedule instance - schedule = std::make_shared(steps, parameters, value_function); + // A valid schedule instance + schedule = std::make_shared(steps, parameters, value_function); - // A simple hamiltonian as an operator_sum - hamiltonian = std::make_shared(); - *hamiltonian += 0.5 * elementary_operator::identity(0); - *hamiltonian += 0.5 * elementary_operator::number(0); + // A simple hamiltonian as an operator_sum + hamiltonian = std::make_shared(); + *hamiltonian += 0.5 * elementary_operator::identity(0); + *hamiltonian += 0.5 * elementary_operator::number(0); - // System with valid components - integrator->set_system({{0, 2}}, schedule, hamiltonian); - } + // System with valid components + integrator->set_system({{0, 2}}, schedule, hamiltonian); + } - void TearDown() override { - delete integrator; - } + void TearDown() override { delete integrator; } }; // Basic integration TEST_F(RungeKuttaIntegratorTest, BasicIntegration) { - integrator->integrate(1.0); + integrator->integrate(1.0); - // Expected result: x(t) = e^(-t) - double expected = std::exp(-1.0); + // Expected result: x(t) = e^(-t) + double expected = std::exp(-1.0); - EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) << "Basic Runge-Kutta integration failed!"; + EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) + << "Basic Runge-Kutta integration failed!"; } // Time evolution TEST_F(RungeKuttaIntegratorTest, TimeEvolution) { - integrator->integrate(2.0); + integrator->integrate(2.0); - double expected = 2.0; + double expected = 2.0; - EXPECT_NEAR(integrator->get_state().first, expected, 1e-5) << "Integrator did not correctly update time!"; + EXPECT_NEAR(integrator->get_state().first, expected, 1e-5) + << "Integrator did not correctly update time!"; } // Large step size TEST_F(RungeKuttaIntegratorTest, LargeStepSize) { - integrator->integrate(5.0); + integrator->integrate(5.0); - double expected = std::exp(-5.0); + double expected = std::exp(-5.0); - EXPECT_NEAR(integrator->get_state().second, expected, 1e-1) << "Runge-Kutta integration failed for large step size!!"; + EXPECT_NEAR(integrator->get_state().second, expected, 1e-1) + << "Runge-Kutta integration failed for large step size!!"; } - // // Integrating Sine function // TEST_F(RungeKuttaIntegratorTest, SineFunction) { // integrator = new RungeKuttaIntegrator(sine_derivative); @@ -93,40 +92,41 @@ TEST_F(RungeKuttaIntegratorTest, LargeStepSize) { // double expected = std::cos(M_PI / 2); -// EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) << "Runge-Kutta integration for sine function failed!"; +// EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) << +// "Runge-Kutta integration for sine function failed!"; // } - // Small step size TEST_F(RungeKuttaIntegratorTest, SmallStepIntegration) { - integrator->set_state(1.0, 0.0); - integrator->set_system({{0, 2}}, schedule, hamiltonian); + integrator->set_state(1.0, 0.0); + integrator->set_system({{0, 2}}, schedule, hamiltonian); - double step_size = 0.001; - while (integrator->get_state().first < 1.0) { - integrator->integrate(integrator->get_state().first + step_size); - } + double step_size = 0.001; + while (integrator->get_state().first < 1.0) { + integrator->integrate(integrator->get_state().first + step_size); + } - double expected = std::exp(-1.0); + double expected = std::exp(-1.0); - EXPECT_NEAR(integrator->get_state().second, expected, 5e-4) << "Runge-Kutta integration for small step size failed!"; + EXPECT_NEAR(integrator->get_state().second, expected, 5e-4) + << "Runge-Kutta integration for small step size failed!"; } - // Large step size TEST_F(RungeKuttaIntegratorTest, LargeStepIntegration) { - integrator->set_state(1.0, 0.0); - integrator->set_system({{0, 2}}, schedule, hamiltonian); + integrator->set_state(1.0, 0.0); + integrator->set_system({{0, 2}}, schedule, hamiltonian); - double step_size = 0.5; - double t = 0.0; - double target_t = 1.0; - while (t < target_t) { - integrator->integrate(std::min(t + step_size, target_t)); - t += step_size; - } + double step_size = 0.5; + double t = 0.0; + double target_t = 1.0; + while (t < target_t) { + integrator->integrate(std::min(t + step_size, target_t)); + t += step_size; + } - double expected = std::exp(-1.0); + double expected = std::exp(-1.0); - EXPECT_NEAR(integrator->get_state().second, expected, 1e-2) << "Runge-Kutta integration for large step size failed!"; + EXPECT_NEAR(integrator->get_state().second, expected, 1e-2) + << "Runge-Kutta integration for large step size failed!"; } diff --git a/unittests/dynamics/test_runge_kutta_time_stepper.cpp b/unittests/dynamics/test_runge_kutta_time_stepper.cpp index bc32bb587c..4c4c7b7588 100644 --- a/unittests/dynamics/test_runge_kutta_time_stepper.cpp +++ b/unittests/dynamics/test_runge_kutta_time_stepper.cpp @@ -6,75 +6,80 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -#include "runge_kutta_test_helpers.h" #include "cudaq/runge_kutta_time_stepper.h" +#include "runge_kutta_test_helpers.h" +#include #include #include -#include // Test fixture class class RungeKuttaTimeStepperTest : public ::testing::Test { protected: - std::shared_ptr> stepper; + std::shared_ptr> stepper; - void SetUp() override { - stepper = std::make_shared>(simple_derivative); - } + void SetUp() override { + stepper = std::make_shared>( + simple_derivative); + } }; // Single step integration TEST_F(RungeKuttaTimeStepperTest, SingleStep) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 0.1; + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 0.1; - stepper->compute(state, t, dt); + stepper->compute(state, t, dt); - // Expected result using analytical solution: x(t) = e^(-t) - double expected = std::exp(-dt); + // Expected result using analytical solution: x(t) = e^(-t) + double expected = std::exp(-dt); - EXPECT_NEAR(state, expected, 1e-3) << "Single step Runge-Kutta integration failed!"; + EXPECT_NEAR(state, expected, 1e-3) + << "Single step Runge-Kutta integration failed!"; } // Multiple step integration TEST_F(RungeKuttaTimeStepperTest, MultipleSteps) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 0.1; - int steps = 10; + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 0.1; + int steps = 10; - for (int i = 0; i < steps; i++) { - stepper->compute(state, t, dt); - } + for (int i = 0; i < steps; i++) { + stepper->compute(state, t, dt); + } - // Expected result: x(t) = e^(-t) - double expected = std::exp(-1.0); + // Expected result: x(t) = e^(-t) + double expected = std::exp(-1.0); - EXPECT_NEAR(state, expected, 1e-2) << "Multiple step Runge-Kutta integration failed!"; + EXPECT_NEAR(state, expected, 1e-2) + << "Multiple step Runge-Kutta integration failed!"; } // Convergence to Analytical Solution TEST_F(RungeKuttaTimeStepperTest, Convergence) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 0.01; - int steps = 100; + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 0.01; + int steps = 100; - for (int i = 0; i < steps; i++) { - stepper->compute(state, t, dt); - } + for (int i = 0; i < steps; i++) { + stepper->compute(state, t, dt); + } - double expected = std::exp(-1.0); + double expected = std::exp(-1.0); - EXPECT_NEAR(state, expected, 1e-3) << "Runge-Kutta integration does not converge!"; + EXPECT_NEAR(state, expected, 1e-3) + << "Runge-Kutta integration does not converge!"; } // // Integrating Sine function // TEST_F(RungeKuttaTimeStepperTest, SineFunction) { -// auto sine_stepper = std::make_shared>(sine_derivative); +// auto sine_stepper = +// std::make_shared>(sine_derivative); // // Initial values // double state = 0.0; @@ -89,60 +94,59 @@ TEST_F(RungeKuttaTimeStepperTest, Convergence) { // // Expected integral of sin(t) over [0, 1] is 1 - cos(1) // double expected = 1 - std::cos(1); -// EXPECT_NEAR(state, expected, 1e-2) << "Runge-Kutta integration for sine function failed!"; +// EXPECT_NEAR(state, expected, 1e-2) << "Runge-Kutta integration for sine +// function failed!"; // } // Handling small steps sizes TEST_F(RungeKuttaTimeStepperTest, SmallStepSize) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 1e-5; - int steps = 100000; + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 1e-5; + int steps = 100000; - for (int i = 0; i < steps; i++) { - stepper->compute(state, t, dt); - } + for (int i = 0; i < steps; i++) { + stepper->compute(state, t, dt); + } - double expected = std::exp(-1.0); + double expected = std::exp(-1.0); - EXPECT_NEAR(state, expected, 1e-3) << "Runge-Kutta fails with small step sizes!"; + EXPECT_NEAR(state, expected, 1e-3) + << "Runge-Kutta fails with small step sizes!"; } // Handling large steps sizes TEST_F(RungeKuttaTimeStepperTest, LargeStepSize) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 1.0; + // Initial values + double state = 1.0; + double t = 0.0; + double dt = 1.0; - stepper->compute(state, t, dt); + stepper->compute(state, t, dt); - double expected = std::exp(-1.0); + double expected = std::exp(-1.0); - EXPECT_NEAR(state, expected, 1e-1) << "Runge-Kutta is unstable with large step sizes!"; + EXPECT_NEAR(state, expected, 1e-1) + << "Runge-Kutta is unstable with large step sizes!"; } // Constant derivative (dx/dt = 0) TEST_F(RungeKuttaTimeStepperTest, ConstantFunction) { - auto constant_stepper = std::make_shared>( - [](const TestState &state, double t) { - return 0.0; - } - ); - - // Initial values - double state = 5.0; - double t = 0.0; - double dt = 0.1; - int steps = 10; - - for (int i = 0; i < steps; i++) { - constant_stepper->compute(state, t, dt); - } - - EXPECT_NEAR(state, 5.0, 1e-6) << "Runge-Kutta should not change a constant function!"; + auto constant_stepper = + std::make_shared>( + [](const TestState &state, double t) { return 0.0; }); + + // Initial values + double state = 5.0; + double t = 0.0; + double dt = 0.1; + int steps = 10; + + for (int i = 0; i < steps; i++) { + constant_stepper->compute(state, t, dt); + } + + EXPECT_NEAR(state, 5.0, 1e-6) + << "Runge-Kutta should not change a constant function!"; } - - - From 6ac5a073978b4d727fc873f6c9ab0eec951d867f Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Tue, 14 Jan 2025 15:18:55 -0800 Subject: [PATCH 10/40] Formatting Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/helpers.cpp | 198 +++++++++++++++------------- runtime/cudaq/helpers.h | 56 ++++---- unittests/dynamics/test_helpers.cpp | 193 +++++++++++++-------------- 3 files changed, 227 insertions(+), 220 deletions(-) diff --git a/runtime/cudaq/dynamics/helpers.cpp b/runtime/cudaq/dynamics/helpers.cpp index ad86a2c26b..e6c50d14a0 100644 --- a/runtime/cudaq/dynamics/helpers.cpp +++ b/runtime/cudaq/dynamics/helpers.cpp @@ -7,129 +7,141 @@ ******************************************************************************/ #include "cudaq/helpers.h" -#include #include +#include namespace cudaq { // Aggregate parameters from multiple mappings. -std::map OperatorHelpers::aggregate_parameters(const std::vector> ¶meter_mappings) { - std::map parameter_descriptions; - - for (const auto &descriptions : parameter_mappings) { - for (const auto &[key, new_desc] : descriptions) { - if (!parameter_descriptions[key].empty() && !new_desc.empty()) { - parameter_descriptions[key] += "\n---\n" + new_desc; - } else { - parameter_descriptions[key] = new_desc; - } - } +std::map OperatorHelpers::aggregate_parameters( + const std::vector> ¶meter_mappings) { + std::map parameter_descriptions; + + for (const auto &descriptions : parameter_mappings) { + for (const auto &[key, new_desc] : descriptions) { + if (!parameter_descriptions[key].empty() && !new_desc.empty()) { + parameter_descriptions[key] += "\n---\n" + new_desc; + } else { + parameter_descriptions[key] = new_desc; + } } + } - return parameter_descriptions; + return parameter_descriptions; } // Extract documentation for a specific parameter from docstring. -std::string OperatorHelpers::parameter_docs(const std::string ¶m_name, const std::string &docs) { - if (param_name.empty() || docs.empty()) { - return ""; - } - - try { - std::regex keyword_pattern(R"(^\s*(Arguments|Args):\s*$)", std::regex::multiline); - std::regex param_pattern(R"(^\s*)" + param_name + R"(\s*(\(.*\))?:\s*(.*)$)", std::regex::multiline); - - std::smatch match; - std::sregex_iterator it(docs.begin(), docs.end(), keyword_pattern); - std::sregex_iterator end; - - if (it != end) { - std::string params_section = docs.substr(it->position() + it->length()); - if(std::regex_search(params_section, match, param_pattern)) { - std::string param_docs = match.str(2); - return std::regex_replace(param_docs, std::regex(R"(\s+)"), " "); - } - } - } catch (...) { - return ""; +std::string OperatorHelpers::parameter_docs(const std::string ¶m_name, + const std::string &docs) { + if (param_name.empty() || docs.empty()) { + return ""; + } + + try { + std::regex keyword_pattern(R"(^\s*(Arguments|Args):\s*$)", + std::regex::multiline); + std::regex param_pattern(R"(^\s*)" + param_name + + R"(\s*(\(.*\))?:\s*(.*)$)", + std::regex::multiline); + + std::smatch match; + std::sregex_iterator it(docs.begin(), docs.end(), keyword_pattern); + std::sregex_iterator end; + + if (it != end) { + std::string params_section = docs.substr(it->position() + it->length()); + if (std::regex_search(params_section, match, param_pattern)) { + std::string param_docs = match.str(2); + return std::regex_replace(param_docs, std::regex(R"(\s+)"), " "); + } } - + } catch (...) { return ""; + } + + return ""; } // Extract positional arguments and keyword-only arguments. -std::pair, std::map> OperatorHelpers::args_from_kwargs(const std::map &kwargs, - const std::vector &required_args, const std::vector &kwonly_args) { - std::vector extracted_args; - std::map kwonly_dict; - - for (const auto &arg : required_args) { - if (kwargs.count(arg)) { - extracted_args.push_back(kwargs.at(arg)); - } else { - throw std::invalid_argument("Missing required argument: " + arg); - } +std::pair, std::map> +OperatorHelpers::args_from_kwargs( + const std::map &kwargs, + const std::vector &required_args, + const std::vector &kwonly_args) { + std::vector extracted_args; + std::map kwonly_dict; + + for (const auto &arg : required_args) { + if (kwargs.count(arg)) { + extracted_args.push_back(kwargs.at(arg)); + } else { + throw std::invalid_argument("Missing required argument: " + arg); } + } - for (const auto &arg : kwonly_args) { - if (kwargs.count(arg)) { - kwonly_dict[arg] = kwargs.at(arg); - } + for (const auto &arg : kwonly_args) { + if (kwargs.count(arg)) { + kwonly_dict[arg] = kwargs.at(arg); } + } - return {extracted_args, kwonly_dict}; + return {extracted_args, kwonly_dict}; } // Generate all possible quantum states for given degrees and dimensions -std::vector OperatorHelpers::generate_all_states(const std::vector °rees, const std::map &dimensions) { - if (degrees.empty()) { - return {}; - } - - std::vector> states; - for (int state = 0; state < dimensions.at(degrees[0]); state++) { - states.push_back({std::to_string(state)}); +std::vector +OperatorHelpers::generate_all_states(const std::vector °rees, + const std::map &dimensions) { + if (degrees.empty()) { + return {}; + } + + std::vector> states; + for (int state = 0; state < dimensions.at(degrees[0]); state++) { + states.push_back({std::to_string(state)}); + } + + for (size_t i = 1; i < degrees.size(); i++) { + std::vector> new_states; + for (const auto ¤t : states) { + for (int state = 0; state < dimensions.at(degrees[i]); state++) { + auto new_entry = current; + new_entry.push_back(std::to_string(state)); + new_states.push_back(new_entry); + } } - - for (size_t i = 1; i < degrees.size(); i++) { - std::vector> new_states; - for (const auto ¤t : states) { - for (int state = 0; state < dimensions.at(degrees[i]); state++) { - auto new_entry = current; - new_entry.push_back(std::to_string(state)); - new_states.push_back(new_entry); - } - } - states = new_states; - } - - std::vector result; - for (const auto &state : states) { - std::ostringstream joined; - for (const auto &s : state) { - joined << s; - } - result.push_back(joined.str()); + states = new_states; + } + + std::vector result; + for (const auto &state : states) { + std::ostringstream joined; + for (const auto &s : state) { + joined << s; } - return result; + result.push_back(joined.str()); + } + return result; } // Permute a given eigen matrix -void OperatorHelpers::permute_matrix(Eigen::MatrixXcd &matrix, const std::vector &permutation) { - Eigen::MatrixXcd permuted_matrix(matrix.rows(), matrix.cols()); +void OperatorHelpers::permute_matrix(Eigen::MatrixXcd &matrix, + const std::vector &permutation) { + Eigen::MatrixXcd permuted_matrix(matrix.rows(), matrix.cols()); - for (size_t i = 0; i < permutation.size(); i++) { - for (size_t j = 0; j < permutation.size(); j++) { - permuted_matrix(i, j) = matrix(permutation[i], permutation[j]); - } + for (size_t i = 0; i < permutation.size(); i++) { + for (size_t j = 0; j < permutation.size(); j++) { + permuted_matrix(i, j) = matrix(permutation[i], permutation[j]); } + } - matrix = permuted_matrix; + matrix = permuted_matrix; } // Canonicalize degrees by sorting in descending order -std::vector OperatorHelpers::canonicalize_degrees(const std::vector °rees) { - std::vector sorted_degrees = degrees; - std::sort(sorted_degrees.rbegin(), sorted_degrees.rend()); - return sorted_degrees; +std::vector +OperatorHelpers::canonicalize_degrees(const std::vector °rees) { + std::vector sorted_degrees = degrees; + std::sort(sorted_degrees.rbegin(), sorted_degrees.rend()); + return sorted_degrees; } -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/helpers.h b/runtime/cudaq/helpers.h index 488ff3bf0c..52bb131d0c 100644 --- a/runtime/cudaq/helpers.h +++ b/runtime/cudaq/helpers.h @@ -8,35 +8,43 @@ #pragma once -#include -#include -#include +#include #include #include -#include +#include #include -#include +#include +#include +#include namespace cudaq { class OperatorHelpers { public: - // Aggregate parameters from multiple mappings. - static std::map aggregate_parameters(const std::vector> ¶meter_mappings); - - // Extract documentation for a specific parameter from docstring. - static std::string parameter_docs(const std::string ¶m_name, const std::string &docs); - - // Extract positional arguments and keyword-only arguments. - static std::pair, std::map> args_from_kwargs(const std::map &kwargs, - const std::vector &required_args, const std::vector &kwonly_args); - - // Generate all possible quantum states for given degrees and dimensions. - static std::vector generate_all_states(const std::vector °rees, const std::map &dimensions); - - // Permute a given Eigen matrix. - static void permute_matrix(Eigen::MatrixXcd &matrix, const std::vector &permutation); - - // Canonicalize degrees by sorting in descending order. - static std::vector canonicalize_degrees(const std::vector °rees); + // Aggregate parameters from multiple mappings. + static std::map + aggregate_parameters(const std::vector> + ¶meter_mappings); + + // Extract documentation for a specific parameter from docstring. + static std::string parameter_docs(const std::string ¶m_name, + const std::string &docs); + + // Extract positional arguments and keyword-only arguments. + static std::pair, std::map> + args_from_kwargs(const std::map &kwargs, + const std::vector &required_args, + const std::vector &kwonly_args); + + // Generate all possible quantum states for given degrees and dimensions. + static std::vector + generate_all_states(const std::vector °rees, + const std::map &dimensions); + + // Permute a given Eigen matrix. + static void permute_matrix(Eigen::MatrixXcd &matrix, + const std::vector &permutation); + + // Canonicalize degrees by sorting in descending order. + static std::vector canonicalize_degrees(const std::vector °rees); }; -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/unittests/dynamics/test_helpers.cpp b/unittests/dynamics/test_helpers.cpp index 9e471a29bd..aa14cb5c10 100644 --- a/unittests/dynamics/test_helpers.cpp +++ b/unittests/dynamics/test_helpers.cpp @@ -13,177 +13,164 @@ using namespace cudaq; TEST(OperatorHelpersTest, AggregateParameters_MultipleMappings) { - std::vector> mappings = { - {{"alpha", "Parameter A"}, {"beta", "Parameter B"}}, {{"alpha", "Updated Parameter A"}, {"gamma", "New Parameter"}} - }; + std::vector> mappings = { + {{"alpha", "Parameter A"}, {"beta", "Parameter B"}}, + {{"alpha", "Updated Parameter A"}, {"gamma", "New Parameter"}}}; - auto result = OperatorHelpers::aggregate_parameters(mappings); + auto result = OperatorHelpers::aggregate_parameters(mappings); - EXPECT_EQ(result["alpha"], "Parameter A\n---\nUpdated Parameter A"); - EXPECT_EQ(result["beta"], "Parameter B"); - EXPECT_EQ(result["gamma"], "New Parameter"); + EXPECT_EQ(result["alpha"], "Parameter A\n---\nUpdated Parameter A"); + EXPECT_EQ(result["beta"], "Parameter B"); + EXPECT_EQ(result["gamma"], "New Parameter"); } TEST(OperatorHelpersTest, AggregateParameters_EmptyMappings) { - std::vector> mappings; - auto result = OperatorHelpers::aggregate_parameters(mappings); + std::vector> mappings; + auto result = OperatorHelpers::aggregate_parameters(mappings); - EXPECT_TRUE(result.empty()); + 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."; + std::string docstring = "Description of function.\n" + "Arguments:\n" + " alpha (float): The first parameter.\n" + " beta (int): The second parameter."; - auto result = OperatorHelpers::parameter_docs("alpha", docstring); - EXPECT_EQ(result, "The first parameter."); + auto result = OperatorHelpers::parameter_docs("alpha", docstring); + EXPECT_EQ(result, "The first parameter."); - result = OperatorHelpers::parameter_docs("beta", docstring); - EXPECT_EQ(result, "The second parameter."); + result = OperatorHelpers::parameter_docs("beta", docstring); + EXPECT_EQ(result, "The second parameter."); } TEST(OperatorHelpersTest, ParameterDocs_InvalidParam) { - std::string docstring = - "Description of function.\n" - "Arguments:\n" - " alpha (float): The first parameter.\n" - " beta (int): The second parameter."; + std::string docstring = "Description of function.\n" + "Arguments:\n" + " alpha (float): The first parameter.\n" + " beta (int): The second parameter."; - auto result = OperatorHelpers::parameter_docs("gamma", docstring); - EXPECT_EQ(result, ""); + auto result = OperatorHelpers::parameter_docs("gamma", docstring); + EXPECT_EQ(result, ""); } TEST(OperatorHelpersTest, ParameterDocs_EmptyDocString) { - std::string docstring = ""; - auto result = OperatorHelpers::parameter_docs("alpha", docstring); - EXPECT_EQ(result, ""); + std::string docstring = ""; + auto result = OperatorHelpers::parameter_docs("alpha", docstring); + EXPECT_EQ(result, ""); } TEST(OperatorHelpersTest, GenerateAllStates_TwoQubits) { - std::vector degrees = {0, 1}; - std::map dimensions = {{0, 2}, {1, 2}}; + std::vector degrees = {0, 1}; + std::map dimensions = {{0, 2}, {1, 2}}; - auto states = OperatorHelpers::generate_all_states(degrees, dimensions); - std::vector expected_states = {"00", "01", "10", "11"}; + auto states = OperatorHelpers::generate_all_states(degrees, dimensions); + std::vector expected_states = {"00", "01", "10", "11"}; - EXPECT_EQ(states, expected_states); + EXPECT_EQ(states, expected_states); } TEST(OperatorHelpersTest, GenerateAllStates_ThreeQubits) { - std::vector degrees = {0, 1, 2}; - std::map dimensions = {{0, 2}, {1, 2}, {2, 2}}; + std::vector degrees = {0, 1, 2}; + std::map dimensions = {{0, 2}, {1, 2}, {2, 2}}; - auto states = OperatorHelpers::generate_all_states(degrees, dimensions); - std::vector expected_states = {"000", "001", "010", "011", "100", "101", "110", "111"}; + auto states = OperatorHelpers::generate_all_states(degrees, dimensions); + std::vector expected_states = {"000", "001", "010", "011", + "100", "101", "110", "111"}; - EXPECT_EQ(states, expected_states); + EXPECT_EQ(states, expected_states); } TEST(OperatorHelpersTest, GenerateAllStates_EmptyDegrees) { - std::vector degrees; - std::map dimensions; + std::vector degrees; + std::map dimensions; - auto states = OperatorHelpers::generate_all_states(degrees, dimensions); - EXPECT_TRUE(states.empty()); + auto states = OperatorHelpers::generate_all_states(degrees, dimensions); + EXPECT_TRUE(states.empty()); } TEST(OperatorHelpersTest, GenerateAllStates_MissingDegreesInMap) { - std::vector degrees = {0, 1, 2}; - std::map dimensions = {{0, 2}, {1, 2}}; + std::vector degrees = {0, 1, 2}; + std::map dimensions = {{0, 2}, {1, 2}}; - EXPECT_THROW(OperatorHelpers::generate_all_states(degrees, dimensions), std::out_of_range); + EXPECT_THROW(OperatorHelpers::generate_all_states(degrees, dimensions), + std::out_of_range); } TEST(OperatorHelpersTest, PermuteMatrix_SingleSwap) { - Eigen::MatrixXcd matrix(2, 2); - matrix << 1, 2, - 3, 4; + Eigen::MatrixXcd matrix(2, 2); + matrix << 1, 2, 3, 4; - // Swap rows and columns - std::vector permutation = {1, 0}; + // Swap rows and columns + std::vector permutation = {1, 0}; - OperatorHelpers::permute_matrix(matrix, permutation); + OperatorHelpers::permute_matrix(matrix, permutation); - Eigen::MatrixXcd expected(2, 2); - expected << 4, 3, - 2, 1; + Eigen::MatrixXcd expected(2, 2); + expected << 4, 3, 2, 1; - EXPECT_EQ(matrix, expected); + EXPECT_EQ(matrix, expected); } TEST(OperatorHelpersTest, PermuteMatrix_IdentityPermutation) { - Eigen::MatrixXcd matrix(3, 3); - matrix << 1, 2, 3, - 4, 5, 6, - 7, 8, 9; + Eigen::MatrixXcd matrix(3, 3); + matrix << 1, 2, 3, 4, 5, 6, 7, 8, 9; - // No change - std::vector permutation = {0, 1, 2}; + // No change + std::vector permutation = {0, 1, 2}; - OperatorHelpers::permute_matrix(matrix, permutation); + OperatorHelpers::permute_matrix(matrix, permutation); - Eigen::MatrixXcd expected(3, 3); - expected << 1, 2, 3, - 4, 5, 6, - 7, 8, 9; + Eigen::MatrixXcd expected(3, 3); + expected << 1, 2, 3, 4, 5, 6, 7, 8, 9; - EXPECT_EQ(matrix, expected); + EXPECT_EQ(matrix, expected); } TEST(OperatorHelpersTest, CanonicalizeDegrees_SortedDescending) { - std::vector degrees = {3, 1, 2}; - auto sorted = OperatorHelpers::canonicalize_degrees(degrees); - EXPECT_EQ(sorted, (std::vector{3, 2, 1})); + std::vector degrees = {3, 1, 2}; + auto sorted = OperatorHelpers::canonicalize_degrees(degrees); + EXPECT_EQ(sorted, (std::vector{3, 2, 1})); } TEST(OperatorHelpersTest, CanonicalizeDegrees_AlreadySorted) { - std::vector degrees = {5, 4, 3, 2, 1}; - auto sorted = OperatorHelpers::canonicalize_degrees(degrees); - EXPECT_EQ(sorted, (std::vector{5, 4, 3, 2, 1})); + std::vector degrees = {5, 4, 3, 2, 1}; + auto sorted = OperatorHelpers::canonicalize_degrees(degrees); + EXPECT_EQ(sorted, (std::vector{5, 4, 3, 2, 1})); } TEST(OperatorHelpersTest, CanonicalizeDegrees_EmptyList) { - std::vector degrees; - auto sorted = OperatorHelpers::canonicalize_degrees(degrees); - EXPECT_TRUE(sorted.empty()); + std::vector degrees; + auto sorted = OperatorHelpers::canonicalize_degrees(degrees); + EXPECT_TRUE(sorted.empty()); } TEST(OperatorHelpersTest, ArgsFromKwargs_ValidArgs) { - std::map kwargs = { - {"alpha", "0.5"}, - {"beta", "1.0"}, - {"gamma", "2.0"} - }; + std::map kwargs = { + {"alpha", "0.5"}, {"beta", "1.0"}, {"gamma", "2.0"}}; - std::vector required_args = {"alpha", "beta"}; - std::vector kwonly_args = {"gamma"}; + std::vector required_args = {"alpha", "beta"}; + std::vector kwonly_args = {"gamma"}; - auto [args, kwonly] = OperatorHelpers::args_from_kwargs(kwargs, required_args, kwonly_args); + auto [args, kwonly] = + OperatorHelpers::args_from_kwargs(kwargs, required_args, kwonly_args); - EXPECT_EQ(args.size(), 2); - EXPECT_EQ(args[0], "0.5"); - EXPECT_EQ(args[1], "1.0"); - - EXPECT_EQ(kwonly.size(), 1); - EXPECT_EQ(kwonly["gamma"], "2.0"); -} + 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::map kwargs = {{"beta", "1.0"}, + {"gamma", "2.0"}}; - std::vector required_args = {"alpha", "beta"}; - std::vector kwonly_args = {"gamma"}; + std::vector required_args = {"alpha", "beta"}; + std::vector kwonly_args = {"gamma"}; - EXPECT_THROW(OperatorHelpers::args_from_kwargs(kwargs, required_args, kwonly_args), std::invalid_argument); + EXPECT_THROW( + OperatorHelpers::args_from_kwargs(kwargs, required_args, kwonly_args), + std::invalid_argument); } - - - - From 8f423bb06df6197ccb435f0eadb278b372d544d0 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Tue, 14 Jan 2025 17:56:42 -0800 Subject: [PATCH 11/40] Adding evolution API header Signed-off-by: Sachin Pisal --- runtime/cudaq/evolution.h | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 runtime/cudaq/evolution.h diff --git a/runtime/cudaq/evolution.h b/runtime/cudaq/evolution.h new file mode 100644 index 0000000000..3b86c448ce --- /dev/null +++ b/runtime/cudaq/evolution.h @@ -0,0 +1,46 @@ +/****************************************************************-*- 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/schedule.h" +#include "cudaq/base_integrator.h" +#include "common/EvolveResult.h" + +#include +#include +#include +#include +#include + +namespace cudaq { +class Evolution { +public: + /// Computes the Taylor series expansion of the matrix exponential. + static Eigen::MatrixXcd taylor_series_expm(const Eigen::MatrixXcd &op_matrix, int order = 20); + + /// Computes the evolution step matrix + static Eigen::MatrixXcd 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 operator_sum &hamiltonian, const std::shared_ptr &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> &, 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. + 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); +}; +} \ No newline at end of file From 64b7393f833f6a397e8d56eb05d353cce13b1c39 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Tue, 14 Jan 2025 17:57:19 -0800 Subject: [PATCH 12/40] Formatting Signed-off-by: Sachin Pisal --- runtime/cudaq/evolution.h | 69 +++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/runtime/cudaq/evolution.h b/runtime/cudaq/evolution.h index 3b86c448ce..c2915319e2 100644 --- a/runtime/cudaq/evolution.h +++ b/runtime/cudaq/evolution.h @@ -8,10 +8,10 @@ #pragma once +#include "common/EvolveResult.h" +#include "cudaq/base_integrator.h" #include "cudaq/operators.h" #include "cudaq/schedule.h" -#include "cudaq/base_integrator.h" -#include "common/EvolveResult.h" #include #include @@ -22,25 +22,60 @@ namespace cudaq { class Evolution { public: - /// Computes the Taylor series expansion of the matrix exponential. - static Eigen::MatrixXcd taylor_series_expm(const Eigen::MatrixXcd &op_matrix, int order = 20); + /// Computes the Taylor series expansion of the matrix exponential. + static Eigen::MatrixXcd taylor_series_expm(const Eigen::MatrixXcd &op_matrix, + int order = 20); - /// Computes the evolution step matrix - static Eigen::MatrixXcd compute_step_matrix(const operator_sum &hamiltonian, const std::map &dimensions, const std::map> ¶meters, double dt, bool use_gpu = false); + /// Computes the evolution step matrix + static Eigen::MatrixXcd 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); + /// 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 operator_sum &hamiltonian, const std::shared_ptr &schedule, int shots_count, bool is_async = false); + /// Launches an analog Hamiltonian kernel for quantum simulations. + static evolve_result + launch_analog_hamiltonian_kernel(const std::string &target_name, + const operator_sum &hamiltonian, + const std::shared_ptr &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> &, double)> &compute_step_matrix, const std::vector tlist, const std::vector>> &schedule_parameters); + /// Generates evolution kernels for the simulation. + static std::vector evolution_kernel( + int num_qubits, + const std::function> &, 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 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. - 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); + /// Evolves a single or multiple quantum states under a given hamiltonian. + 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); }; -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file From 8c7a88465a0111c637ef43c4ad0b79fe3a36c4df Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Wed, 15 Jan 2025 08:58:11 -0800 Subject: [PATCH 13/40] Replacing Eigen::MatrixXcd with matrix_2 Signed-off-by: Sachin Pisal --- runtime/cudaq/evolution.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/runtime/cudaq/evolution.h b/runtime/cudaq/evolution.h index c2915319e2..36b1afa4f5 100644 --- a/runtime/cudaq/evolution.h +++ b/runtime/cudaq/evolution.h @@ -12,8 +12,8 @@ #include "cudaq/base_integrator.h" #include "cudaq/operators.h" #include "cudaq/schedule.h" +#include "cudaq/utils/tensor.h" -#include #include #include #include @@ -23,11 +23,10 @@ namespace cudaq { class Evolution { public: /// Computes the Taylor series expansion of the matrix exponential. - static Eigen::MatrixXcd taylor_series_expm(const Eigen::MatrixXcd &op_matrix, - int order = 20); + static matrix_2 taylor_series_expm(const matrix_2 &op_matrix, int order = 20); /// Computes the evolution step matrix - static Eigen::MatrixXcd compute_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); @@ -49,8 +48,8 @@ class Evolution { /// Generates evolution kernels for the simulation. static std::vector evolution_kernel( int num_qubits, - const std::function> &, double)> + const std::function< + matrix_2(const std::map> &, double)> &compute_step_matrix, const std::vector tlist, const std::vector>> From 95fc4c5bd8db209866bd44d9ad6b8495588f21a6 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Wed, 15 Jan 2025 16:25:04 -0800 Subject: [PATCH 14/40] * Adding Rydberg hamiltonian operator * Adding unitests for Rydberg hamiltonian operator * Making evaluate function in scalar_operator const Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/CMakeLists.txt | 2 +- .../cudaq/dynamics/rydberg_hamiltonian.cpp | 55 ++++++++ runtime/cudaq/dynamics/scalar_operators.cpp | 2 +- runtime/cudaq/operators.h | 53 +++++++- unittests/CMakeLists.txt | 1 + unittests/dynamics/rydberg_hamiltonian.cpp | 124 ++++++++++++++++++ 6 files changed, 232 insertions(+), 5 deletions(-) create mode 100644 runtime/cudaq/dynamics/rydberg_hamiltonian.cpp create mode 100644 unittests/dynamics/rydberg_hamiltonian.cpp diff --git a/runtime/cudaq/dynamics/CMakeLists.txt b/runtime/cudaq/dynamics/CMakeLists.txt index d608aba2c3..be4f65799c 100644 --- a/runtime/cudaq/dynamics/CMakeLists.txt +++ b/runtime/cudaq/dynamics/CMakeLists.txt @@ -11,7 +11,7 @@ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") set(INTERFACE_POSITION_INDEPENDENT_CODE ON) set(CUDAQ_OPS_SRC - scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp + scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp rydberg_hamiltonian.cpp ) add_library(${LIBRARY_NAME} SHARED ${CUDAQ_OPS_SRC}) 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 index 1be54ea9ee..1d9716c9f3 100644 --- a/runtime/cudaq/dynamics/scalar_operators.cpp +++ b/runtime/cudaq/dynamics/scalar_operators.cpp @@ -40,7 +40,7 @@ scalar_operator::scalar_operator(double value) { } std::complex scalar_operator::evaluate( - std::map> parameters) { + std::map> parameters) const { return generator(parameters); } diff --git a/runtime/cudaq/operators.h b/runtime/cudaq/operators.h index d668909daa..121a088b49 100644 --- a/runtime/cudaq/operators.h +++ b/runtime/cudaq/operators.h @@ -64,8 +64,9 @@ class operator_sum { /// degrees of freedom: `{0:2, 1:2}`. /// @arg `parameters` : A map of the parameter names to their concrete, /// complex values. - matrix_2 to_matrix(const std::map &dimensions, - const std::map ¶ms = {}) const; + matrix_2 to_matrix( + const std::map &dimensions, + const std::map> ¶ms = {}) const; // Arithmetic operators operator_sum operator+(const operator_sum &other) const; @@ -398,7 +399,7 @@ class scalar_operator : public product_operator { /// @brief Return the scalar operator as a concrete complex value. std::complex - evaluate(std::map> parameters); + evaluate(std::map> parameters) const; // Return the scalar operator as a 1x1 matrix. This is needed for // compatability with the other inherited classes. @@ -461,4 +462,50 @@ void operator-=(scalar_operator &self, scalar_operator other); void operator*=(scalar_operator &self, scalar_operator other); void operator/=(scalar_operator &self, scalar_operator other); +/// @brief Representation of a time-dependent Hamiltonian for Rydberg system +class rydberg_hamiltonian : public operator_sum { +public: + using Coordinate = std::pair; + + /// @brief Constructor. + /// @param atom_sites List of 2D coordinates for trap sites. + /// @param amplitude Time-dependant driving amplitude, Omega(t). + /// @param phase Time-dependant driving phase, phi(t). + /// @param delta_global Time-dependant 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 dependant + /// local detuning factors. + rydberg_hamiltonian( + const std::vector &atom_sites, + const scalar_operator &litude, const scalar_operator &phase, + const scalar_operator &delta_global, + const std::vector &atom_filling = {}, + const std::optional>> + &delta_local = std::nullopt); + + /// @brief Get atom sites. + const std::vector &get_atom_sites() const; + + /// @brief Get atom filling. + const std::vector &get_atom_filling() const; + + /// @brief Get amplitude operator. + const scalar_operator &get_amplitude() const; + + /// @brief Get phase operator. + const scalar_operator &get_phase() const; + + /// @brief Get global detuning operator. + const scalar_operator &get_delta_global() const; + +private: + std::vector atom_sites; + std::vector atom_filling; + scalar_operator amplitude; + scalar_operator phase; + scalar_operator delta_global; + std::optional>> delta_local; +}; + } // namespace cudaq \ No newline at end of file diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 3091dd6a4b..546de1192e 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -52,6 +52,7 @@ set(CUDAQ_RUNTIME_TEST_SOURCES dynamics/test_runge_kutta_time_stepper.cpp dynamics/test_runge_kutta_integrator.cpp dynamics/test_helpers.cpp + dynamics/rydberg_hamiltonian.cpp ) # Make it so we can get function symbols diff --git a/unittests/dynamics/rydberg_hamiltonian.cpp b/unittests/dynamics/rydberg_hamiltonian.cpp new file mode 100644 index 0000000000..2e64cb0666 --- /dev/null +++ b/unittests/dynamics/rydberg_hamiltonian.cpp @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/operators.h" +#include + +using namespace cudaq; + +TEST(RydbergHamiltonianTest, ConstructorValidInputs) { + // Valid atom sites + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}, {1.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + // Valid atom filling + rydberg_hamiltonian hamiltonian(atom_sites, amplitude, phase, delta_global); + + EXPECT_EQ(hamiltonian.get_atom_sites().size(), atom_sites.size()); + EXPECT_EQ(hamiltonian.get_atom_filling().size(), atom_sites.size()); + EXPECT_EQ(hamiltonian.get_amplitude().evaluate({}), + std::complex(1.0, 0.0)); + EXPECT_EQ(hamiltonian.get_phase().evaluate({}), + std::complex(0.0, 0.0)); + EXPECT_EQ(hamiltonian.get_delta_global().evaluate({}), + std::complex(-0.5, 0.0)); +} + +TEST(RydbergHamiltonianTest, ConstructorWithAtomFilling) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + // Valid atom filling + std::vector atom_filling = {1, 0, 1}; + + rydberg_hamiltonian hamiltonian(atom_sites, amplitude, phase, delta_global, + atom_filling); + + EXPECT_EQ(hamiltonian.get_atom_sites().size(), atom_sites.size()); + EXPECT_EQ(hamiltonian.get_atom_filling(), atom_filling); +} + +TEST(RydbergHamiltonianTest, InvalidAtomFillingSize) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + // Invalid atom filling size + std::vector atom_filling = {1, 0}; + + EXPECT_THROW(rydberg_hamiltonian(atom_sites, amplitude, phase, delta_global, + atom_filling), + std::invalid_argument); +} + +TEST(RydbergHamiltonianTest, UnsupportedLocalDetuning) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + // Invalid delta_local + auto delta_local = + std::make_pair(scalar_operator(0.5), std::vector{0.1, 0.2, 0.3}); + + EXPECT_THROW(rydberg_hamiltonian(atom_sites, amplitude, phase, delta_global, + {}, delta_local), + std::runtime_error); +} + +TEST(RydbergHamiltonianTest, Accessors) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + rydberg_hamiltonian hamiltonian(atom_sites, amplitude, phase, delta_global); + + EXPECT_EQ(hamiltonian.get_atom_sites(), atom_sites); + EXPECT_EQ(hamiltonian.get_amplitude().evaluate({}), + std::complex(1.0, 0.0)); + EXPECT_EQ(hamiltonian.get_phase().evaluate({}), + std::complex(0.0, 0.0)); + EXPECT_EQ(hamiltonian.get_delta_global().evaluate({}), + std::complex(-0.5, 0.0)); +} + +TEST(RydbergHamiltonianTest, DefaultAtomFilling) { + std::vector atom_sites = { + {0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}, {1.0, 1.0}}; + + // Valid operators + scalar_operator amplitude(1.0); + scalar_operator phase(0.0); + scalar_operator delta_global(-0.5); + + rydberg_hamiltonian hamiltonian(atom_sites, amplitude, phase, delta_global); + + std::vector expected_filling(atom_sites.size(), 1); + EXPECT_EQ(hamiltonian.get_atom_filling(), expected_filling); +} From 4c79dcf833b1df3d97a7f98b1e0125e2cbb10201 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Thu, 16 Jan 2025 14:10:02 -0800 Subject: [PATCH 15/40] Adding interface for cudm_helpers Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_helpers.h | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 runtime/cudaq/cudm_helpers.h diff --git a/runtime/cudaq/cudm_helpers.h b/runtime/cudaq/cudm_helpers.h new file mode 100644 index 0000000000..d706d5ed91 --- /dev/null +++ b/runtime/cudaq/cudm_helpers.h @@ -0,0 +1,43 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "cudaq/utils/tensor.h" +#include +#include +#include +#include +#include + +namespace cudaq { +cudensitymatState_t initialize_state(cudensitymatHandle_t handle, + cudensitymatStatePurity_t purity, + int num_modes, + const std::vector &mode_extents); + +void scale_state(cudensitymatHandle_t handle, cudensitymatState_t state, + double scale_factor, cudaStream_t stream); + +void destroy_state(cudensitymatState_t state); + +cudensitymatOperator_t +compute_lindblad_operator(cudensitymatHandle_t handle, + const std::vector &c_ops, + const std::vector &mode_extents); + +cudensitymatOperator_t convert_to_cudensitymat_operator( + cudensitymatHandle_t handle, + const std::map ¶meters, const matrix_2 &matrix, + const std::vector &mode_extents); + +cudensitymatOperator_t construct_liovillian( + cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, + const std::vector &collapse_operators, + double gamma); +} // namespace cudaq \ No newline at end of file From 07fb8c1c618658e6f120c32547afbf932e2cc06d Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Thu, 16 Jan 2025 14:21:12 -0800 Subject: [PATCH 16/40] Changing matrix to operator_sum Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_helpers.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/cudaq/cudm_helpers.h b/runtime/cudaq/cudm_helpers.h index d706d5ed91..6e1418f13e 100644 --- a/runtime/cudaq/cudm_helpers.h +++ b/runtime/cudaq/cudm_helpers.h @@ -9,6 +9,7 @@ #pragma once #include "cudaq/utils/tensor.h" +#include "cudaq/operators.h" #include #include #include @@ -33,7 +34,7 @@ compute_lindblad_operator(cudensitymatHandle_t handle, cudensitymatOperator_t convert_to_cudensitymat_operator( cudensitymatHandle_t handle, - const std::map ¶meters, const matrix_2 &matrix, + const std::map ¶meters, const operator_sum &op, const std::vector &mode_extents); cudensitymatOperator_t construct_liovillian( From cf84bef8c905486cc03d1f86bdc93f19a3eb664e Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Tue, 21 Jan 2025 09:10:44 -0800 Subject: [PATCH 17/40] cudm_helpers implementation Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_helpers.h | 8 +- runtime/cudaq/dynamics/CMakeLists.txt | 13 +- runtime/cudaq/dynamics/cudm_helpers.cpp | 201 ++++++++++++++++++ runtime/cudaq/dynamics/cudm_state.h | 27 +++ .../cudaq/dynamics/elementary_operators.cpp | 6 +- runtime/cudaq/dynamics/evolution.cpp | 99 +++++++++ runtime/cudaq/dynamics/operator_sum.cpp | 24 ++- runtime/cudaq/dynamics/product_operators.cpp | 47 ++++ runtime/cudaq/dynamics/scalar_operators.cpp | 4 +- runtime/cudaq/evolution.h | 9 +- runtime/cudaq/operators.h | 15 +- unittests/CMakeLists.txt | 15 +- unittests/dynamics/test_cudm_helpers.cpp | 111 ++++++++++ 13 files changed, 548 insertions(+), 31 deletions(-) create mode 100644 runtime/cudaq/dynamics/cudm_helpers.cpp create mode 100644 runtime/cudaq/dynamics/cudm_state.h create mode 100644 runtime/cudaq/dynamics/evolution.cpp create mode 100644 unittests/dynamics/test_cudm_helpers.cpp diff --git a/runtime/cudaq/cudm_helpers.h b/runtime/cudaq/cudm_helpers.h index 6e1418f13e..de4b4760ab 100644 --- a/runtime/cudaq/cudm_helpers.h +++ b/runtime/cudaq/cudm_helpers.h @@ -8,8 +8,8 @@ #pragma once -#include "cudaq/utils/tensor.h" #include "cudaq/operators.h" +#include "cudaq/utils/tensor.h" #include #include #include @@ -30,12 +30,12 @@ void destroy_state(cudensitymatState_t state); cudensitymatOperator_t compute_lindblad_operator(cudensitymatHandle_t handle, const std::vector &c_ops, - const std::vector &mode_extents); + const std::vector &mode_extents); cudensitymatOperator_t convert_to_cudensitymat_operator( cudensitymatHandle_t handle, - const std::map ¶meters, const operator_sum &op, - const std::vector &mode_extents); + const std::map> ¶meters, + const operator_sum &op, const std::vector &mode_extents); cudensitymatOperator_t construct_liovillian( cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, diff --git a/runtime/cudaq/dynamics/CMakeLists.txt b/runtime/cudaq/dynamics/CMakeLists.txt index be4f65799c..283e77e7ff 100644 --- a/runtime/cudaq/dynamics/CMakeLists.txt +++ b/runtime/cudaq/dynamics/CMakeLists.txt @@ -7,22 +7,31 @@ # ============================================================================ # set(LIBRARY_NAME cudaq-operators) -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") set(INTERFACE_POSITION_INDEPENDENT_CODE ON) set(CUDAQ_OPS_SRC - scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp rydberg_hamiltonian.cpp + scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp rydberg_hamiltonian.cpp cudm_helpers.cpp ) +set(CUQUANTUM_INSTALL_PREFIX "/usr/local/lib/python3.10/dist-packages/cuquantum") +if (NOT DEFINED CUQUANTUM_INSTALL_PREFIX) + message(FATAL_ERROR "CUQUANTUM_INSTALL_PREFIX is not defined.") +endif() + add_library(${LIBRARY_NAME} SHARED ${CUDAQ_OPS_SRC}) set_property(GLOBAL APPEND PROPERTY CUDAQ_RUNTIME_LIBS ${LIBRARY_NAME}) target_include_directories(${LIBRARY_NAME} PUBLIC $ $ + $ + /usr/local/cuda/targets/x86_64-linux/include $ PRIVATE .) +target_link_libraries(${LIBRARY_NAME} PRIVATE ${CUQUANTUM_INSTALL_PREFIX}/lib/libcudensitymat.so.0) + set (OPERATOR_DEPENDENCIES "") list(APPEND OPERATOR_DEPENDENCIES fmt::fmt-header-only) add_openmp_configurations(${LIBRARY_NAME} OPERATOR_DEPENDENCIES) diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp new file mode 100644 index 0000000000..c9248720f9 --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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" + +namespace cudaq { +cudensitymatState_t initialize_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, int num_modes, const std::vector &mode_extents) { + try { + cudensitymatState_t state; + auto status = cudensitymatCreateState(handle, purity, num_modes, mode_extents.data(), 1, CUDA_R_64F, &state); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to initialize quantum state."); + } + return state; + } catch (const std::exception &e) { + std::cerr << "Error in initialize_state: " << e.what() < &c_ops, const std::vector &mode_extents) { + try { + if (c_ops.empty()) { + throw std::invalid_argument("Collapse operators cannot be empty."); + } + + cudensitymatOperator_t lindblad_op; + auto status = cudensitymatCreateOperator(handle, static_cast(mode_extents.size()), mode_extents.data(), &lindblad_op); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to create lindblad operator."); + } + + 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."); + } + + std::vector> flat_matrix(dim * dim); + for (size_t i = 0; i < dim; i++) { + for (size_t j = 0; j < dim; j++) { + flat_matrix[i * dim + j] = c_op[{i, j}]; + } + } + + // Create Operator term for LtL and add to lindblad_op + cudensitymatOperatorTerm_t term; + status = cudensitymatCreateOperatorTerm(handle, static_cast(mode_extents.size()), mode_extents.data(), &term); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(lindblad_op); + throw std::runtime_error("Failed to create operator term."); + } + + // Attach terms and cleanup + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + status = cudensitymatOperatorAppendTerm(handle, lindblad_op, term, 0, {1.0}, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to append operator term."); + } + + cudensitymatDestroyOperatorTerm(term); + } + + return lindblad_op; + } catch (const std::exception &e) { + std::cerr << "Error in compute_lindblad_op: " << e.what() << std::endl; + throw; + } +} + +cudensitymatOperator_t convert_to_cudensitymat_operator(cudensitymatHandle_t handle, const std::map> ¶meters, const operator_sum &op, const std::vector &mode_extents) { + try { + cudensitymatOperator_t operator_handle; + auto status = cudensitymatCreateOperator(handle, static_cast(mode_extents.size()), mode_extents.data(), &operator_handle); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to create operator."); + } + + // Define dimensions for the operator + std::map dimensions; + for (size_t i = 0; i < mode_extents.size(); i++) { + dimensions[static_cast(i)] = static_cast(mode_extents[i]); + } + + auto matrix = op.to_matrix(dimensions, parameters); + size_t dim = matrix.get_rows(); + if (matrix.get_columns() != dim) { + throw std::invalid_argument("Matrix must be a square."); + } + + std::vector> flat_matrix; + for (size_t i = 0; i < matrix.get_rows(); i++) { + for (size_t j = 0; j < matrix.get_columns(); j++) { + flat_matrix.push_back(matrix[{i, j}]); + } + } + + cudensitymatOperatorTerm_t term; + status = cudensitymatCreateOperatorTerm(handle, static_cast(mode_extents.size()), mode_extents.data(), &term); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(operator_handle); + throw std::runtime_error("Failed to create operator term."); + } + + // Attach flat_matrix to the term + int32_t num_elem_operators = 1; + int32_t num_operator_modes = static_cast(mode_extents.size()); + const int64_t *operator_mode_extents = mode_extents.data(); + const int64_t *operator_mode_strides = nullptr; + int32_t state_modes_acted_on[static_cast(mode_extents.size())]; + for (int32_t i = 0; i < num_operator_modes; i++) { + state_modes_acted_on[i] = i; + } + + cudensitymatWrappedTensorCallback_t tensorCallback = {nullptr, nullptr}; + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + + void *tensor_data = flat_matrix.data(); + cuDoubleComplex coefficient = make_cuDoubleComplex(1.0, 0.0); + + status = cudensitymatOperatorTermAppendGeneralProduct(handle, term, + num_elem_operators, &num_operator_modes, &operator_mode_extents, + &operator_mode_strides, state_modes_acted_on, nullptr, + CUDA_C_64F, &tensor_data, &tensorCallback, coefficient, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperatorTerm(term); + cudensitymatDestroyOperator(operator_handle); + throw std::runtime_error("Failed to attach flat_matrix to operator term."); + } + + status = cudensitymatOperatorAppendTerm(handle, operator_handle, term, 0, coefficient, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperatorTerm(term); + cudensitymatDestroyOperator(operator_handle); + throw std::runtime_error("Failed to attach term to operator."); + } + + cudensitymatDestroyOperatorTerm(term); + return operator_handle; + } catch (const std::exception &e) { + std::cerr << "Error in convert_to_cudensitymat_operator: " << e.what() << std::endl; + throw; + } +} + +cudensitymatOperator_t construct_liovillian(cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, const std::vector &collapse_operators, double gamma) { + try { + cudensitymatOperator_t liouvillian; + auto status = cudensitymatCreateOperator(handle, 0, nullptr, &liouvillian); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to create Liouvillian operator."); + } + + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + status = cudensitymatOperatorAppendTerm(handle, liouvillian, hamiltonian, 0, {1.0, 0.0}, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(liouvillian); + throw std::runtime_error("Failed to add hamiltonian term."); + } + + cuDoubleComplex coefficient = make_cuDoubleComplex(gamma, 0.0); + for (const auto &c_op : collapse_operators) { + status = cudensitymatOperatorAppendTerm(handle, liouvillian, c_op, 0, coefficient, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(liouvillian); + throw std::runtime_error("Failed to add collapse operator term."); + } + } + + return liouvillian; + } catch (const std::exception &e) { + std::cerr << "Error in construct_liovillian: " << e.what() << std::endl; + throw; + } +} +} diff --git a/runtime/cudaq/dynamics/cudm_state.h b/runtime/cudaq/dynamics/cudm_state.h new file mode 100644 index 0000000000..1bdba17782 --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_state.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 +#include + +namespace cudaq { +class cudm_mat_state { +public: + cudm_mat_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, int num_modes, const std::vector &mode_extents); + ~cudm_mat_state(); + + void scale(double factor, cudaStream_t stream); + cudensitymatState_t get() const; + +private: + cudensitymatState_t state; + cudensitymatHandle_t handle; +}; +} \ No newline at end of file diff --git a/runtime/cudaq/dynamics/elementary_operators.cpp b/runtime/cudaq/dynamics/elementary_operators.cpp index 137dde02cc..574891033f 100644 --- a/runtime/cudaq/dynamics/elementary_operators.cpp +++ b/runtime/cudaq/dynamics/elementary_operators.cpp @@ -280,9 +280,9 @@ elementary_operator::squeeze(int degree, std::complex amplitude) { } matrix_2 elementary_operator::to_matrix( - std::map dimensions, - std::map> parameters) { - return m_ops[id].generator(dimensions, parameters); + const std::map dimensions, + const std::map> parameters) const { + return m_ops.at(id).generator(dimensions, parameters); } /// Elementary Operator Arithmetic. diff --git a/runtime/cudaq/dynamics/evolution.cpp b/runtime/cudaq/dynamics/evolution.cpp new file mode 100644 index 0000000000..69a8209607 --- /dev/null +++ b/runtime/cudaq/dynamics/evolution.cpp @@ -0,0 +1,99 @@ +// /****************************************************************-*- C++ -*-**** +// * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * +// * All rights reserved. * +// * * +// * This source code and the accompanying materials are made available under * +// * the terms of the Apache License 2.0 which accompanies this distribution. * +// ******************************************************************************/ + +// #include "cudaq/evolution.h" +// #include +// #include +// #include +// #include + +// namespace cudaq { + +// // Can be removed +// matrix_2 taylor_series_expm(const matrix_2 &op_matrix, +// int order = 20) { +// matrix_2 result = matrix_2(op_matrix.get_rows(), op_matrix.get_columns()); +// matrix_2 op_matrix_n = matrix_2(op_matrix.get_rows(), op_matrix.get_columns()); + +// for (size_t i = 0; i < op_matrix.get_rows(); i++) { +// result[{i, i}] = std::complex(1.0, 0.0); +// op_matrix_n[{i, i}] = std::complex(1.0, 0.0); +// } + +// double factorial = 1.0; +// for (int n = 1; n <= order; n++) { +// op_matrix_n *= op_matrix; +// factorial *= n; +// result += std::complex(1.0 / factorial, 0.0) * op_matrix_n; +// } + +// return result; +// } + +// matrix_2 compute_step_matrix( +// const operator_sum &hamiltonian, const std::map &dimensions, +// const std::map> ¶meters, double dt, +// bool use_gpu) { +// matrix_2 op_matrix = hamiltonian.to_matrix(dimensions, parameters); +// op_matrix = dt * std::complex(0, -1) * op_matrix; + +// if (use_gpu) { +// // TODO: Implement GPU matrix exponential using CuPy or cuQuantum +// throw std::runtime_error("GPU-based matrix exponentiation not implemented."); +// } else { +// return taylor_series_expm(op_matrix); +// } +// } + +// void add_noise_channel_for_step( +// const std::string &step_kernel_name, cudaq::noise_model &noise_model, +// const std::vector &collapse_operators, +// const std::map &dimensions, +// const std::map> ¶meters, double dt) { +// for (const auto &collapse_op : collapse_operators) { +// matrix_2 L = collapse_op.to_matrix(dimensions, parameters); +// matrix_2 G = std::complex(-0.5, 0.0) * (L * L); + +// // Kraus operators +// matrix_2 M0 = (dt * G) + matrix_2(L.get_rows(), L.get_columns()); +// matrix_2 M1 = std::sqrt(dt) * L; + +// try { +// noise_model.add_all_qubit_channel(step_kernel_name, kraus_channel({std::move(M0), std::move(M1)})); +// } catch (const std::exception &e) { +// std::cerr << "Error adding noise channel: " << e.what() << std::endl; +// throw; +// } +// } +// } + +// // evolve_result launch_analog_hamiltonian_kernel(const std::string &target_name, +// // const rydberg_hamiltonian &hamiltonian, +// // const Schedule &schedule, +// // int shots_count, bool is_async = false) { +// // // Generate the time series +// // std::vector> amp_ts, ph_ts, dg_ts; + +// // auto current_schedule = schedule; +// // current_schedule.reset(); + +// // while(auto t = current_schedule.current_step()) { +// // std::map> parameters = {{"time", t.value()}}; + +// // amp_ts.emplace_back(hamiltonian.get_amplitude().evaluate(parameters).real(), t.value().real()); +// // ph_ts.emplace_back(hamiltonian.get_phase().evaluate(parameters).real(), t.value().real()); +// // dg_ts.emplace_back(hamiltonian.get_delta_global().evaluate(parameters).real(), t.value().real()); + +// // ++schedule; +// // } + +// // // Atom arrangement and physical fields +// // cudaq::ahs::AtomArrangement atoms; + +// // } +// } \ No newline at end of file diff --git a/runtime/cudaq/dynamics/operator_sum.cpp b/runtime/cudaq/dynamics/operator_sum.cpp index a0ba70cb2b..dd3227784d 100644 --- a/runtime/cudaq/dynamics/operator_sum.cpp +++ b/runtime/cudaq/dynamics/operator_sum.cpp @@ -347,12 +347,24 @@ operator_sum operator_sum::operator*=(const elementary_operator &other) { return *this; } -/// FIXME: -// tensor -// operator_sum::to_matrix(const std::map &dimensions, -// const std::map ¶ms) const { -// // todo -// } +matrix_2 operator_sum::to_matrix( + const std::map &dimensions, + const std::map> ¶ms) const { + std::size_t total_dimension = 1; + for (const auto &[_, dim] : dimensions) { + total_dimension *= dim; + } + + matrix_2 result(total_dimension, total_dimension); + + for (const auto &term : m_terms) { + matrix_2 term_matrix = term.to_matrix(dimensions, params); + + result += term_matrix; + } + + return result; +} // std::string operator_sum::to_string() const { // std::string result; diff --git a/runtime/cudaq/dynamics/product_operators.cpp b/runtime/cudaq/dynamics/product_operators.cpp index 0c8b411b1a..32adcaa339 100644 --- a/runtime/cudaq/dynamics/product_operators.cpp +++ b/runtime/cudaq/dynamics/product_operators.cpp @@ -35,6 +35,53 @@ product_operator::product_operator( // tensor::identity(1, 1), kronecker); // } +matrix_2 product_operator::to_matrix( + const std::map dimensions, + const std::map> parameters) const { + // Lambda functions to retrieve degrees and matrices + auto getDegrees = [](auto &&term) { return term.degrees; }; + auto getMatrix = [&](auto &&term) { + return term.to_matrix(dimensions, parameters); + }; + + // Initialize a result matrix with a single identity element + matrix_2 result(1, 1); + result[{0, 0}] = 1.0; + + // Iterate over all terms in the product operator + for (const auto &term : m_terms) { + // Get the degrees for the current term + auto termDegrees = std::visit(getDegrees, term); + bool inserted = false; + + matrix_2 termMatrix(1, 1); + termMatrix[{0, 0}] = 1.0; + + // Build the matrix list with identities or operator matrices + for (const auto &[degree, dim] : dimensions) { + if (std::find(termDegrees.begin(), termDegrees.end(), degree) != + termDegrees.end() && + !inserted) { + // Use the operator matrix for the active degree + termMatrix.kronecker_inplace(std::visit(getMatrix, term)); + inserted = true; + } else { + // Use identity matrix for other degrees + matrix_2 identityMatrix(dim, dim); + for (std::size_t i = 0; i < dim; i++) { + identityMatrix[{i, i}] = 1.0; + } + termMatrix.kronecker_inplace(identityMatrix); + } + } + + // Multiply the result matrix by the term matrix + result *= termMatrix; + } + + return result; +} + // /// IMPLEMENT: // tensor product_operator::to_matrix( // std::map dimensions, diff --git a/runtime/cudaq/dynamics/scalar_operators.cpp b/runtime/cudaq/dynamics/scalar_operators.cpp index 1d9716c9f3..4d640b50b5 100644 --- a/runtime/cudaq/dynamics/scalar_operators.cpp +++ b/runtime/cudaq/dynamics/scalar_operators.cpp @@ -45,8 +45,8 @@ std::complex scalar_operator::evaluate( } matrix_2 scalar_operator::to_matrix( - std::map dimensions, - std::map> parameters) { + const std::map dimensions, + const std::map> parameters) const { auto returnOperator = matrix_2(1, 1); returnOperator[{0, 0}] = evaluate(parameters); return returnOperator; diff --git a/runtime/cudaq/evolution.h b/runtime/cudaq/evolution.h index 36b1afa4f5..7b5f717806 100644 --- a/runtime/cudaq/evolution.h +++ b/runtime/cudaq/evolution.h @@ -39,11 +39,9 @@ class Evolution { 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 operator_sum &hamiltonian, - const std::shared_ptr &schedule, - int shots_count, bool is_async = false); + 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( @@ -67,6 +65,7 @@ class Evolution { 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, diff --git a/runtime/cudaq/operators.h b/runtime/cudaq/operators.h index 121a088b49..8924dbb861 100644 --- a/runtime/cudaq/operators.h +++ b/runtime/cudaq/operators.h @@ -200,8 +200,9 @@ class product_operator : public operator_sum { /// 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); + matrix_2 + to_matrix(const std::map dimensions, + const std::map> parameters) const; /// @brief Creates a representation of the operator as a `cudaq::pauli_word` /// that can be passed as an argument to quantum kernels. @@ -279,8 +280,9 @@ class elementary_operator : public product_operator { /// 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); + matrix_2 + to_matrix(const std::map dimensions, + const std::map> parameters) const; // Predefined operators. static elementary_operator identity(int degree); @@ -403,8 +405,9 @@ class scalar_operator : public product_operator { // Return the scalar operator as a 1x1 matrix. This is needed for // compatability with the other inherited classes. - matrix_2 to_matrix(std::map dimensions, - std::map> parameters); + matrix_2 + to_matrix(const std::map dimensions, + const std::map> parameters) const; // /// @brief Returns true if other is a scalar operator with the same // /// generator. diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 546de1192e..5218bb5027 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -53,11 +53,14 @@ set(CUDAQ_RUNTIME_TEST_SOURCES dynamics/test_runge_kutta_integrator.cpp dynamics/test_helpers.cpp dynamics/rydberg_hamiltonian.cpp + dynamics/test_cudm_helpers.cpp ) # Make it so we can get function symbols set (CMAKE_ENABLE_EXPORTS TRUE) +include_directories(/usr/local/cuda/targets/x86_64-linux/include) + ## This Macro allows us to create a test_runtime executable for ## the sources in CUDAQ_RUNTIME_TEST_SOURCE for a specific backend simulator macro (create_tests_with_backend NVQIR_BACKEND EXTRA_BACKEND_TESTER) @@ -80,7 +83,9 @@ macro (create_tests_with_backend NVQIR_BACKEND EXTRA_BACKEND_TESTER) cudaq fmt::fmt-header-only cudaq-platform-default cudaq-builder - gtest_main) + gtest_main + $ENV{CUQUANTUM_INSTALL_PREFIX}/lib/libcudensitymat.so.0 + /usr/local/cuda-12.0/targets/x86_64-linux/lib/libcudart.so.12) set(TEST_LABELS "") if (${NVQIR_BACKEND} STREQUAL "qpp") target_compile_definitions(${TEST_EXE_NAME} PRIVATE -DCUDAQ_SIMULATION_SCALAR_FP64) @@ -274,6 +279,7 @@ set(CUDAQ_OPERATOR_TEST_SOURCES dynamics/scalar_ops_simple.cpp dynamics/scalar_ops_arithmetic.cpp dynamics/product_operators_arithmetic.cpp + dynamics/test_cudm_helpers.cpp ) add_executable(test_operators main.cpp ${CUDAQ_OPERATOR_TEST_SOURCES}) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) @@ -284,7 +290,9 @@ target_link_libraries(test_operators cudaq-spin cudaq-operators cudaq - gtest_main) + gtest_main + $ENV{CUQUANTUM_INSTALL_PREFIX}/lib/libcudensitymat.so.0 + /usr/local/cuda-12.0/targets/x86_64-linux/lib/libcudart.so.12) gtest_discover_tests(test_operators) add_subdirectory(plugin) @@ -405,7 +413,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}) diff --git a/unittests/dynamics/test_cudm_helpers.cpp b/unittests/dynamics/test_cudm_helpers.cpp new file mode 100644 index 0000000000..4e1a21306e --- /dev/null +++ b/unittests/dynamics/test_cudm_helpers.cpp @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include +#include + +// Initialize operator_sum +cudaq::operator_sum initialize_operator_sum() { + std::vector degrees = {0, 1}; + + // Elementary operators + cudaq::elementary_operator pauli_x("pauli_x", {0}); + cudaq::elementary_operator pauli_z("pauli_z", {1}); + cudaq::elementary_operator identity = cudaq::elementary_operator::identity(0); + + auto prod_op_1 = + cudaq::scalar_operator(std::complex(1.0, 0.0)) * pauli_x * pauli_z; + + auto prod_op_2 = + cudaq::scalar_operator(std::complex(0.5, -0.5)) * identity; + + cudaq::operator_sum op_sum({prod_op_1, prod_op_2}); + + return op_sum; +} + +class CuDensityMatTestFixture : public ::testing::Test { +protected: + cudensitymatHandle_t handle; + + void SetUp() override { + auto status = cudensitymatCreate(&handle); + ASSERT_EQ(status, CUDENSITYMAT_STATUS_SUCCESS); + } + + void TearDown() override { + cudensitymatDestroy(handle); + } +}; + +// Test for convert_to_cudensitymat_operator +TEST_F(CuDensityMatTestFixture, ConvertToCuDensityMatOperator) { + std::vector mode_extents = {2, 2}; + + auto op_sum = initialize_operator_sum(); + + auto result = cudaq::convert_to_cudensitymat_operator(handle, {}, op_sum, mode_extents); + + ASSERT_NE(result, nullptr); + + cudensitymatDestroyOperator(result); +} + +// Test for compute_lindblad_op +TEST_F(CuDensityMatTestFixture, ComputeLindbladOp) { + std::vector mode_extents = {2, 2}; + + cudaq::matrix_2 c_op1({{1.0, 0.0}, {0.0, 0.0}}, {2, 2}); + cudaq::matrix_2 c_op2({{0.0, 0.0}, {0.0, 1.0}}, {2, 2}); + std::vector c_ops = {c_op1, c_op2}; + + auto result = cudaq::compute_lindblad_operator(handle, c_ops, mode_extents); + + ASSERT_NE(result, nullptr); + + cudensitymatDestroyOperator(result); +} + +// Test for initialize_state +TEST_F(CuDensityMatTestFixture, InitializeState) { + std::vector mode_extents = {2, 2}; + + auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, 2, mode_extents); + + ASSERT_NE(state, nullptr); + + cudaq::destroy_state(state); +} + +// Test for scale_state +TEST_F(CuDensityMatTestFixture, ScaleState) { + std::vector mode_extents = {2, 2}; + + ASSERT_NO_THROW({ + auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, 2, mode_extents); + ASSERT_NE(state, nullptr); + + cudaStream_t stream; + cudaStreamCreate(&stream); + + EXPECT_NO_THROW(cudaq::scale_state(handle, state, 2.0, stream)); + + cudaStreamDestroy(stream); + cudaq::destroy_state(state); + }); +} + +// Test invalid handle +TEST_F(CuDensityMatTestFixture, InvalidHandle) { + cudensitymatHandle_t invalid_handle = nullptr; + + std::vector mode_extents = {2, 2}; + auto op_sum = initialize_operator_sum(); + + EXPECT_THROW(cudaq::convert_to_cudensitymat_operator(invalid_handle, {}, op_sum, mode_extents), std::runtime_error); +} From 8442d6543325b9ac7d6176a27a7a4e5bf84c3ade Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Tue, 21 Jan 2025 09:13:36 -0800 Subject: [PATCH 18/40] Formatting Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/cudm_helpers.cpp | 355 ++++++++++++----------- runtime/cudaq/dynamics/cudm_state.h | 15 +- runtime/cudaq/dynamics/evolution.cpp | 197 +++++++------ unittests/dynamics/test_cudm_helpers.cpp | 101 +++---- 4 files changed, 355 insertions(+), 313 deletions(-) diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp index c9248720f9..9689d06975 100644 --- a/runtime/cudaq/dynamics/cudm_helpers.cpp +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -9,193 +9,224 @@ #include "cudaq/cudm_helpers.h" namespace cudaq { -cudensitymatState_t initialize_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, int num_modes, const std::vector &mode_extents) { - try { - cudensitymatState_t state; - auto status = cudensitymatCreateState(handle, purity, num_modes, mode_extents.data(), 1, CUDA_R_64F, &state); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to initialize quantum state."); - } - return state; - } catch (const std::exception &e) { - std::cerr << "Error in initialize_state: " << e.what() < &mode_extents) { + try { + cudensitymatState_t state; + auto status = cudensitymatCreateState( + handle, purity, num_modes, mode_extents.data(), 1, CUDA_R_64F, &state); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to initialize quantum state."); } + return state; + } catch (const std::exception &e) { + std::cerr << "Error in initialize_state: " << e.what() << std::endl; + throw; + } } -void scale_state(cudensitymatHandle_t handle, cudensitymatState_t state, double scale_factor, cudaStream_t stream) { - try { - auto status = cudensitymatStateComputeScaling(handle, state, &scale_factor, stream); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to scale quantum state."); - } - } catch (const std::exception &e) { - std::cerr << "Error in scale_state: " << e.what() < &c_ops, const std::vector &mode_extents) { - try { - if (c_ops.empty()) { - throw std::invalid_argument("Collapse operators cannot be empty."); - } - - cudensitymatOperator_t lindblad_op; - auto status = cudensitymatCreateOperator(handle, static_cast(mode_extents.size()), mode_extents.data(), &lindblad_op); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to create lindblad operator."); - } - - 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."); - } - - std::vector> flat_matrix(dim * dim); - for (size_t i = 0; i < dim; i++) { - for (size_t j = 0; j < dim; j++) { - flat_matrix[i * dim + j] = c_op[{i, j}]; - } - } - - // Create Operator term for LtL and add to lindblad_op - cudensitymatOperatorTerm_t term; - status = cudensitymatCreateOperatorTerm(handle, static_cast(mode_extents.size()), mode_extents.data(), &term); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperator(lindblad_op); - throw std::runtime_error("Failed to create operator term."); - } - - // Attach terms and cleanup - cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; - status = cudensitymatOperatorAppendTerm(handle, lindblad_op, term, 0, {1.0}, scalarCallback); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to append operator term."); - } - - cudensitymatDestroyOperatorTerm(term); - } - - return lindblad_op; - } catch (const std::exception &e) { - std::cerr << "Error in compute_lindblad_op: " << e.what() << std::endl; - throw; +cudensitymatOperator_t +compute_lindblad_operator(cudensitymatHandle_t handle, + const std::vector &c_ops, + const std::vector &mode_extents) { + try { + if (c_ops.empty()) { + throw std::invalid_argument("Collapse operators cannot be empty."); } -} -cudensitymatOperator_t convert_to_cudensitymat_operator(cudensitymatHandle_t handle, const std::map> ¶meters, const operator_sum &op, const std::vector &mode_extents) { - try { - cudensitymatOperator_t operator_handle; - auto status = cudensitymatCreateOperator(handle, static_cast(mode_extents.size()), mode_extents.data(), &operator_handle); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to create operator."); - } + cudensitymatOperator_t lindblad_op; + auto status = cudensitymatCreateOperator( + handle, static_cast(mode_extents.size()), mode_extents.data(), + &lindblad_op); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to create lindblad operator."); + } - // Define dimensions for the operator - std::map dimensions; - for (size_t i = 0; i < mode_extents.size(); i++) { - dimensions[static_cast(i)] = static_cast(mode_extents[i]); - } + 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."); + } + + std::vector> flat_matrix(dim * dim); + for (size_t i = 0; i < dim; i++) { + for (size_t j = 0; j < dim; j++) { + flat_matrix[i * dim + j] = c_op[{i, j}]; + } + } + + // Create Operator term for LtL and add to lindblad_op + cudensitymatOperatorTerm_t term; + status = cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), + mode_extents.data(), &term); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(lindblad_op); + throw std::runtime_error("Failed to create operator term."); + } + + // Attach terms and cleanup + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + status = cudensitymatOperatorAppendTerm(handle, lindblad_op, term, 0, + {1.0}, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to append operator term."); + } + + cudensitymatDestroyOperatorTerm(term); + } - auto matrix = op.to_matrix(dimensions, parameters); - size_t dim = matrix.get_rows(); - if (matrix.get_columns() != dim) { - throw std::invalid_argument("Matrix must be a square."); - } + return lindblad_op; + } catch (const std::exception &e) { + std::cerr << "Error in compute_lindblad_op: " << e.what() << std::endl; + throw; + } +} - std::vector> flat_matrix; - for (size_t i = 0; i < matrix.get_rows(); i++) { - for (size_t j = 0; j < matrix.get_columns(); j++) { - flat_matrix.push_back(matrix[{i, j}]); - } - } +cudensitymatOperator_t convert_to_cudensitymat_operator( + cudensitymatHandle_t handle, + const std::map> ¶meters, + const operator_sum &op, const std::vector &mode_extents) { + try { + cudensitymatOperator_t operator_handle; + auto status = cudensitymatCreateOperator( + handle, static_cast(mode_extents.size()), mode_extents.data(), + &operator_handle); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to create operator."); + } - cudensitymatOperatorTerm_t term; - status = cudensitymatCreateOperatorTerm(handle, static_cast(mode_extents.size()), mode_extents.data(), &term); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperator(operator_handle); - throw std::runtime_error("Failed to create operator term."); - } + // Define dimensions for the operator + std::map dimensions; + for (size_t i = 0; i < mode_extents.size(); i++) { + dimensions[static_cast(i)] = static_cast(mode_extents[i]); + } - // Attach flat_matrix to the term - int32_t num_elem_operators = 1; - int32_t num_operator_modes = static_cast(mode_extents.size()); - const int64_t *operator_mode_extents = mode_extents.data(); - const int64_t *operator_mode_strides = nullptr; - int32_t state_modes_acted_on[static_cast(mode_extents.size())]; - for (int32_t i = 0; i < num_operator_modes; i++) { - state_modes_acted_on[i] = i; - } + auto matrix = op.to_matrix(dimensions, parameters); + size_t dim = matrix.get_rows(); + if (matrix.get_columns() != dim) { + throw std::invalid_argument("Matrix must be a square."); + } - cudensitymatWrappedTensorCallback_t tensorCallback = {nullptr, nullptr}; - cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + std::vector> flat_matrix; + for (size_t i = 0; i < matrix.get_rows(); i++) { + for (size_t j = 0; j < matrix.get_columns(); j++) { + flat_matrix.push_back(matrix[{i, j}]); + } + } - void *tensor_data = flat_matrix.data(); - cuDoubleComplex coefficient = make_cuDoubleComplex(1.0, 0.0); + cudensitymatOperatorTerm_t term; + status = cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), mode_extents.data(), + &term); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(operator_handle); + throw std::runtime_error("Failed to create operator term."); + } - status = cudensitymatOperatorTermAppendGeneralProduct(handle, term, - num_elem_operators, &num_operator_modes, &operator_mode_extents, - &operator_mode_strides, state_modes_acted_on, nullptr, - CUDA_C_64F, &tensor_data, &tensorCallback, coefficient, scalarCallback); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperatorTerm(term); - cudensitymatDestroyOperator(operator_handle); - throw std::runtime_error("Failed to attach flat_matrix to operator term."); - } + // Attach flat_matrix to the term + int32_t num_elem_operators = 1; + int32_t num_operator_modes = static_cast(mode_extents.size()); + const int64_t *operator_mode_extents = mode_extents.data(); + const int64_t *operator_mode_strides = nullptr; + int32_t state_modes_acted_on[static_cast(mode_extents.size())]; + for (int32_t i = 0; i < num_operator_modes; i++) { + state_modes_acted_on[i] = i; + } - status = cudensitymatOperatorAppendTerm(handle, operator_handle, term, 0, coefficient, scalarCallback); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperatorTerm(term); - cudensitymatDestroyOperator(operator_handle); - throw std::runtime_error("Failed to attach term to operator."); - } + cudensitymatWrappedTensorCallback_t tensorCallback = {nullptr, nullptr}; + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + + void *tensor_data = flat_matrix.data(); + cuDoubleComplex coefficient = make_cuDoubleComplex(1.0, 0.0); + + status = cudensitymatOperatorTermAppendGeneralProduct( + handle, term, num_elem_operators, &num_operator_modes, + &operator_mode_extents, &operator_mode_strides, state_modes_acted_on, + nullptr, CUDA_C_64F, &tensor_data, &tensorCallback, coefficient, + scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperatorTerm(term); + cudensitymatDestroyOperator(operator_handle); + throw std::runtime_error( + "Failed to attach flat_matrix to operator term."); + } - cudensitymatDestroyOperatorTerm(term); - return operator_handle; - } catch (const std::exception &e) { - std::cerr << "Error in convert_to_cudensitymat_operator: " << e.what() << std::endl; - throw; + status = cudensitymatOperatorAppendTerm(handle, operator_handle, term, 0, + coefficient, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperatorTerm(term); + cudensitymatDestroyOperator(operator_handle); + throw std::runtime_error("Failed to attach term to operator."); } -} -cudensitymatOperator_t construct_liovillian(cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, const std::vector &collapse_operators, double gamma) { - try { - cudensitymatOperator_t liouvillian; - auto status = cudensitymatCreateOperator(handle, 0, nullptr, &liouvillian); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to create Liouvillian operator."); - } + cudensitymatDestroyOperatorTerm(term); + return operator_handle; + } catch (const std::exception &e) { + std::cerr << "Error in convert_to_cudensitymat_operator: " << e.what() + << std::endl; + throw; + } +} - cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; - status = cudensitymatOperatorAppendTerm(handle, liouvillian, hamiltonian, 0, {1.0, 0.0}, scalarCallback); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperator(liouvillian); - throw std::runtime_error("Failed to add hamiltonian term."); - } +cudensitymatOperator_t construct_liovillian( + cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, + const std::vector &collapse_operators, + double gamma) { + try { + cudensitymatOperator_t liouvillian; + auto status = cudensitymatCreateOperator(handle, 0, nullptr, &liouvillian); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + throw std::runtime_error("Failed to create Liouvillian operator."); + } - cuDoubleComplex coefficient = make_cuDoubleComplex(gamma, 0.0); - for (const auto &c_op : collapse_operators) { - status = cudensitymatOperatorAppendTerm(handle, liouvillian, c_op, 0, coefficient, scalarCallback); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperator(liouvillian); - throw std::runtime_error("Failed to add collapse operator term."); - } - } + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + status = cudensitymatOperatorAppendTerm(handle, liouvillian, hamiltonian, 0, + {1.0, 0.0}, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(liouvillian); + throw std::runtime_error("Failed to add hamiltonian term."); + } - return liouvillian; - } catch (const std::exception &e) { - std::cerr << "Error in construct_liovillian: " << e.what() << std::endl; - throw; + cuDoubleComplex coefficient = make_cuDoubleComplex(gamma, 0.0); + for (const auto &c_op : collapse_operators) { + status = cudensitymatOperatorAppendTerm(handle, liouvillian, c_op, 0, + coefficient, scalarCallback); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + cudensitymatDestroyOperator(liouvillian); + throw std::runtime_error("Failed to add collapse operator term."); + } } + + return liouvillian; + } catch (const std::exception &e) { + std::cerr << "Error in construct_liovillian: " << e.what() << std::endl; + throw; + } } -} +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/cudm_state.h b/runtime/cudaq/dynamics/cudm_state.h index 1bdba17782..1f20f9483f 100644 --- a/runtime/cudaq/dynamics/cudm_state.h +++ b/runtime/cudaq/dynamics/cudm_state.h @@ -14,14 +14,15 @@ namespace cudaq { class cudm_mat_state { public: - cudm_mat_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, int num_modes, const std::vector &mode_extents); - ~cudm_mat_state(); + cudm_mat_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, + int num_modes, const std::vector &mode_extents); + ~cudm_mat_state(); - void scale(double factor, cudaStream_t stream); - cudensitymatState_t get() const; + void scale(double factor, cudaStream_t stream); + cudensitymatState_t get() const; private: - cudensitymatState_t state; - cudensitymatHandle_t handle; + cudensitymatState_t state; + cudensitymatHandle_t handle; }; -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/evolution.cpp b/runtime/cudaq/dynamics/evolution.cpp index 69a8209607..75e5ca0875 100644 --- a/runtime/cudaq/dynamics/evolution.cpp +++ b/runtime/cudaq/dynamics/evolution.cpp @@ -1,99 +1,106 @@ -// /****************************************************************-*- C++ -*-**** -// * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * -// * All rights reserved. * -// * * -// * This source code and the accompanying materials are made available under * -// * the terms of the Apache License 2.0 which accompanies this distribution. * -// ******************************************************************************/ - -// #include "cudaq/evolution.h" -// #include -// #include -// #include -// #include - -// namespace cudaq { - -// // Can be removed -// matrix_2 taylor_series_expm(const matrix_2 &op_matrix, -// int order = 20) { -// matrix_2 result = matrix_2(op_matrix.get_rows(), op_matrix.get_columns()); -// matrix_2 op_matrix_n = matrix_2(op_matrix.get_rows(), op_matrix.get_columns()); - -// for (size_t i = 0; i < op_matrix.get_rows(); i++) { -// result[{i, i}] = std::complex(1.0, 0.0); -// op_matrix_n[{i, i}] = std::complex(1.0, 0.0); +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/evolution.h" +#include +#include +#include +#include + +namespace cudaq { + +// Can be removed +matrix_2 taylor_series_expm(const matrix_2 &op_matrix, int order = 20) { + matrix_2 result = matrix_2(op_matrix.get_rows(), op_matrix.get_columns()); + matrix_2 op_matrix_n = + matrix_2(op_matrix.get_rows(), op_matrix.get_columns()); + + for (size_t i = 0; i < op_matrix.get_rows(); i++) { + result[{i, i}] = std::complex(1.0, 0.0); + op_matrix_n[{i, i}] = std::complex(1.0, 0.0); + } + + double factorial = 1.0; + for (int n = 1; n <= order; n++) { + op_matrix_n *= op_matrix; + factorial *= n; + result += std::complex(1.0 / factorial, 0.0) * op_matrix_n; + } + + return result; +} + +matrix_2 compute_step_matrix( + const operator_sum &hamiltonian, const std::map &dimensions, + const std::map> ¶meters, double dt, + bool use_gpu) { + matrix_2 op_matrix = hamiltonian.to_matrix(dimensions, parameters); + op_matrix = dt * std::complex(0, -1) * op_matrix; + + if (use_gpu) { + // TODO: Implement GPU matrix exponential using CuPy or cuQuantum + throw std::runtime_error( + "GPU-based matrix exponentiation not implemented."); + } else { + return taylor_series_expm(op_matrix); + } +} + +void add_noise_channel_for_step( + const std::string &step_kernel_name, cudaq::noise_model &noise_model, + const std::vector &collapse_operators, + const std::map &dimensions, + const std::map> ¶meters, double dt) { + for (const auto &collapse_op : collapse_operators) { + matrix_2 L = collapse_op.to_matrix(dimensions, parameters); + matrix_2 G = std::complex(-0.5, 0.0) * (L * L); + + // Kraus operators + matrix_2 M0 = (dt * G) + matrix_2(L.get_rows(), L.get_columns()); + matrix_2 M1 = std::sqrt(dt) * L; + + try { + noise_model.add_all_qubit_channel( + step_kernel_name, kraus_channel({std::move(M0), std::move(M1)})); + } catch (const std::exception &e) { + std::cerr << "Error adding noise channel: " << e.what() << std::endl; + throw; + } + } +} + +// evolve_result launch_analog_hamiltonian_kernel(const std::string +// &target_name, +// const rydberg_hamiltonian &hamiltonian, +// const Schedule &schedule, +// int shots_count, bool is_async = false) { +// // Generate the time series +// std::vector> amp_ts, ph_ts, dg_ts; + +// auto current_schedule = schedule; +// current_schedule.reset(); + +// while(auto t = current_schedule.current_step()) { +// std::map> parameters = {{"time", +// t.value()}}; + +// amp_ts.emplace_back(hamiltonian.get_amplitude().evaluate(parameters).real(), +// t.value().real()); +// ph_ts.emplace_back(hamiltonian.get_phase().evaluate(parameters).real(), +// t.value().real()); +// dg_ts.emplace_back(hamiltonian.get_delta_global().evaluate(parameters).real(), +// t.value().real()); + +// ++schedule; // } -// 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; -// } +// // Atom arrangement and physical fields +// cudaq::ahs::AtomArrangement atoms; -// 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; - -// // } -// } \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/unittests/dynamics/test_cudm_helpers.cpp b/unittests/dynamics/test_cudm_helpers.cpp index 4e1a21306e..4ec8f100ba 100644 --- a/unittests/dynamics/test_cudm_helpers.cpp +++ b/unittests/dynamics/test_cudm_helpers.cpp @@ -11,101 +11,104 @@ // Initialize operator_sum cudaq::operator_sum initialize_operator_sum() { - std::vector degrees = {0, 1}; + std::vector degrees = {0, 1}; - // Elementary operators - cudaq::elementary_operator pauli_x("pauli_x", {0}); - cudaq::elementary_operator pauli_z("pauli_z", {1}); - cudaq::elementary_operator identity = cudaq::elementary_operator::identity(0); + // Elementary operators + cudaq::elementary_operator pauli_x("pauli_x", {0}); + cudaq::elementary_operator pauli_z("pauli_z", {1}); + cudaq::elementary_operator identity = cudaq::elementary_operator::identity(0); - auto prod_op_1 = - cudaq::scalar_operator(std::complex(1.0, 0.0)) * pauli_x * pauli_z; + auto prod_op_1 = cudaq::scalar_operator(std::complex(1.0, 0.0)) * + pauli_x * pauli_z; - auto prod_op_2 = - cudaq::scalar_operator(std::complex(0.5, -0.5)) * identity; + auto prod_op_2 = + cudaq::scalar_operator(std::complex(0.5, -0.5)) * identity; - cudaq::operator_sum op_sum({prod_op_1, prod_op_2}); + cudaq::operator_sum op_sum({prod_op_1, prod_op_2}); - return op_sum; + return op_sum; } class CuDensityMatTestFixture : public ::testing::Test { protected: - cudensitymatHandle_t handle; + cudensitymatHandle_t handle; - void SetUp() override { - auto status = cudensitymatCreate(&handle); - ASSERT_EQ(status, CUDENSITYMAT_STATUS_SUCCESS); - } + void SetUp() override { + auto status = cudensitymatCreate(&handle); + ASSERT_EQ(status, CUDENSITYMAT_STATUS_SUCCESS); + } - void TearDown() override { - cudensitymatDestroy(handle); - } + void TearDown() override { cudensitymatDestroy(handle); } }; // Test for convert_to_cudensitymat_operator TEST_F(CuDensityMatTestFixture, ConvertToCuDensityMatOperator) { - std::vector mode_extents = {2, 2}; + std::vector mode_extents = {2, 2}; - auto op_sum = initialize_operator_sum(); + auto op_sum = initialize_operator_sum(); - auto result = cudaq::convert_to_cudensitymat_operator(handle, {}, op_sum, mode_extents); + auto result = + cudaq::convert_to_cudensitymat_operator(handle, {}, op_sum, mode_extents); - ASSERT_NE(result, nullptr); + ASSERT_NE(result, nullptr); - cudensitymatDestroyOperator(result); + cudensitymatDestroyOperator(result); } // Test for compute_lindblad_op TEST_F(CuDensityMatTestFixture, ComputeLindbladOp) { - std::vector mode_extents = {2, 2}; + std::vector mode_extents = {2, 2}; - cudaq::matrix_2 c_op1({{1.0, 0.0}, {0.0, 0.0}}, {2, 2}); - cudaq::matrix_2 c_op2({{0.0, 0.0}, {0.0, 1.0}}, {2, 2}); - std::vector c_ops = {c_op1, c_op2}; + cudaq::matrix_2 c_op1({{1.0, 0.0}, {0.0, 0.0}}, {2, 2}); + cudaq::matrix_2 c_op2({{0.0, 0.0}, {0.0, 1.0}}, {2, 2}); + std::vector c_ops = {c_op1, c_op2}; - auto result = cudaq::compute_lindblad_operator(handle, c_ops, mode_extents); + auto result = cudaq::compute_lindblad_operator(handle, c_ops, mode_extents); - ASSERT_NE(result, nullptr); + ASSERT_NE(result, nullptr); - cudensitymatDestroyOperator(result); + cudensitymatDestroyOperator(result); } // Test for initialize_state TEST_F(CuDensityMatTestFixture, InitializeState) { - std::vector mode_extents = {2, 2}; + std::vector mode_extents = {2, 2}; - auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, 2, mode_extents); + auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, + 2, mode_extents); - ASSERT_NE(state, nullptr); + ASSERT_NE(state, nullptr); - cudaq::destroy_state(state); + cudaq::destroy_state(state); } // Test for scale_state TEST_F(CuDensityMatTestFixture, ScaleState) { - std::vector mode_extents = {2, 2}; + std::vector mode_extents = {2, 2}; - ASSERT_NO_THROW({ - auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, 2, mode_extents); - ASSERT_NE(state, nullptr); + ASSERT_NO_THROW({ + auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, + 2, mode_extents); + ASSERT_NE(state, nullptr); - cudaStream_t stream; - cudaStreamCreate(&stream); + cudaStream_t stream; + cudaStreamCreate(&stream); - EXPECT_NO_THROW(cudaq::scale_state(handle, state, 2.0, stream)); + EXPECT_NO_THROW(cudaq::scale_state(handle, state, 2.0, stream)); - cudaStreamDestroy(stream); - cudaq::destroy_state(state); - }); + cudaStreamDestroy(stream); + cudaq::destroy_state(state); + }); } // Test invalid handle TEST_F(CuDensityMatTestFixture, InvalidHandle) { - cudensitymatHandle_t invalid_handle = nullptr; + cudensitymatHandle_t invalid_handle = nullptr; - std::vector mode_extents = {2, 2}; - auto op_sum = initialize_operator_sum(); + std::vector mode_extents = {2, 2}; + auto op_sum = initialize_operator_sum(); - EXPECT_THROW(cudaq::convert_to_cudensitymat_operator(invalid_handle, {}, op_sum, mode_extents), std::runtime_error); + EXPECT_THROW(cudaq::convert_to_cudensitymat_operator(invalid_handle, {}, + op_sum, mode_extents), + std::runtime_error); } From 57c6b26783016f8453afc713526e1adf99cca828 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Tue, 21 Jan 2025 14:37:29 -0800 Subject: [PATCH 19/40] * Adding macro for handling error * Using CUDA_C_64F data type for complex Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_error_handling.h | 29 +++++++++++++++++++++++++ runtime/cudaq/cudm_helpers.h | 1 - runtime/cudaq/dynamics/cudm_helpers.cpp | 9 +++----- 3 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 runtime/cudaq/cudm_error_handling.h diff --git a/runtime/cudaq/cudm_error_handling.h b/runtime/cudaq/cudm_error_handling.h new file mode 100644 index 0000000000..d1b8ac734f --- /dev/null +++ b/runtime/cudaq/cudm_error_handling.h @@ -0,0 +1,29 @@ +/****************************************************************-*- 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 + +#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__)); \ + } \ +} \ No newline at end of file diff --git a/runtime/cudaq/cudm_helpers.h b/runtime/cudaq/cudm_helpers.h index de4b4760ab..c2968229fb 100644 --- a/runtime/cudaq/cudm_helpers.h +++ b/runtime/cudaq/cudm_helpers.h @@ -19,7 +19,6 @@ namespace cudaq { cudensitymatState_t initialize_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, - int num_modes, const std::vector &mode_extents); void scale_state(cudensitymatHandle_t handle, cudensitymatState_t state, diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp index 9689d06975..785057fbc6 100644 --- a/runtime/cudaq/dynamics/cudm_helpers.cpp +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -11,15 +11,12 @@ namespace cudaq { cudensitymatState_t initialize_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, - int num_modes, const std::vector &mode_extents) { try { cudensitymatState_t state; - auto status = cudensitymatCreateState( - handle, purity, num_modes, mode_extents.data(), 1, CUDA_R_64F, &state); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to initialize quantum state."); - } + HANDLE_ERROR(cudensitymatCreateState(handle, purity, mode_extents.size(), + mode_extents.data(), 1, CUDA_C_64F, + &state)); return state; } catch (const std::exception &e) { std::cerr << "Error in initialize_state: " << e.what() << std::endl; From 06ded0f0014cc38e38dd7130015a51937444a102 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Tue, 21 Jan 2025 18:09:29 -0800 Subject: [PATCH 20/40] Adding MACRO for error handling Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_error_handling.h | 33 +++---- runtime/cudaq/dynamics/cudm_helpers.cpp | 110 ++++++++--------------- unittests/dynamics/test_cudm_helpers.cpp | 4 +- 3 files changed, 58 insertions(+), 89 deletions(-) diff --git a/runtime/cudaq/cudm_error_handling.h b/runtime/cudaq/cudm_error_handling.h index d1b8ac734f..d72be33906 100644 --- a/runtime/cudaq/cudm_error_handling.h +++ b/runtime/cudaq/cudm_error_handling.h @@ -8,22 +8,23 @@ #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_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__)); \ - } \ -} \ No newline at end of file +#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/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp index 785057fbc6..914a02914b 100644 --- a/runtime/cudaq/dynamics/cudm_helpers.cpp +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -7,102 +7,70 @@ ******************************************************************************/ #include "cudaq/cudm_helpers.h" +#include "cudaq/cudm_error_handling.h" namespace cudaq { cudensitymatState_t initialize_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, const std::vector &mode_extents) { - try { - cudensitymatState_t state; - HANDLE_ERROR(cudensitymatCreateState(handle, purity, mode_extents.size(), - mode_extents.data(), 1, CUDA_C_64F, - &state)); - return state; - } catch (const std::exception &e) { - std::cerr << "Error in initialize_state: " << e.what() << std::endl; - throw; - } + cudensitymatState_t state; + HANDLE_CUDM_ERROR(cudensitymatCreateState(handle, purity, mode_extents.size(), + mode_extents.data(), 1, CUDA_C_64F, + &state)); + return state; } void scale_state(cudensitymatHandle_t handle, cudensitymatState_t state, double scale_factor, cudaStream_t stream) { - try { - auto status = - cudensitymatStateComputeScaling(handle, state, &scale_factor, stream); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to scale quantum state."); - } - } catch (const std::exception &e) { - std::cerr << "Error in scale_state: " << e.what() << std::endl; - throw; - } + HANDLE_CUDM_ERROR( + cudensitymatStateComputeScaling(handle, state, &scale_factor, stream)); } void destroy_state(cudensitymatState_t state) { - try { - cudensitymatDestroyState(state); - } catch (const std::exception &e) { - std::cerr << "Error in destroy_state: " << e.what() << std::endl; - } + cudensitymatDestroyState(state); } cudensitymatOperator_t compute_lindblad_operator(cudensitymatHandle_t handle, const std::vector &c_ops, const std::vector &mode_extents) { - try { - if (c_ops.empty()) { - throw std::invalid_argument("Collapse operators cannot be empty."); - } - - cudensitymatOperator_t lindblad_op; - auto status = cudensitymatCreateOperator( - handle, static_cast(mode_extents.size()), mode_extents.data(), - &lindblad_op); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to create lindblad operator."); - } - - 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."); - } + if (c_ops.empty()) { + throw std::invalid_argument("Collapse operators cannot be empty."); + } - std::vector> flat_matrix(dim * dim); - for (size_t i = 0; i < dim; i++) { - for (size_t j = 0; j < dim; j++) { - flat_matrix[i * dim + j] = c_op[{i, j}]; - } - } + cudensitymatOperator_t lindblad_op; + HANDLE_CUDM_ERROR(cudensitymatCreateOperator( + handle, static_cast(mode_extents.size()), mode_extents.data(), + &lindblad_op)); - // Create Operator term for LtL and add to lindblad_op - cudensitymatOperatorTerm_t term; - status = cudensitymatCreateOperatorTerm( - handle, static_cast(mode_extents.size()), - mode_extents.data(), &term); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperator(lindblad_op); - throw std::runtime_error("Failed to create operator term."); - } + 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."); + } - // Attach terms and cleanup - cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; - status = cudensitymatOperatorAppendTerm(handle, lindblad_op, term, 0, - {1.0}, scalarCallback); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to append operator term."); + std::vector> flat_matrix(dim * dim); + for (size_t i = 0; i < dim; i++) { + for (size_t j = 0; j < dim; j++) { + flat_matrix[i * dim + j] = c_op[{i, j}]; } - - cudensitymatDestroyOperatorTerm(term); } - return lindblad_op; - } catch (const std::exception &e) { - std::cerr << "Error in compute_lindblad_op: " << e.what() << std::endl; - throw; + // Create Operator term for LtL and add to lindblad_op + cudensitymatOperatorTerm_t term; + HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), mode_extents.data(), + &term)); + cudensitymatDestroyOperator(lindblad_op); + + // Attach terms and cleanup + cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm(handle, lindblad_op, term, + 0, {1.0}, scalarCallback)); + cudensitymatDestroyOperatorTerm(term); } + + return lindblad_op; } cudensitymatOperator_t convert_to_cudensitymat_operator( diff --git a/unittests/dynamics/test_cudm_helpers.cpp b/unittests/dynamics/test_cudm_helpers.cpp index 4ec8f100ba..ddceb989b9 100644 --- a/unittests/dynamics/test_cudm_helpers.cpp +++ b/unittests/dynamics/test_cudm_helpers.cpp @@ -75,7 +75,7 @@ TEST_F(CuDensityMatTestFixture, InitializeState) { std::vector mode_extents = {2, 2}; auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, - 2, mode_extents); + mode_extents); ASSERT_NE(state, nullptr); @@ -88,7 +88,7 @@ TEST_F(CuDensityMatTestFixture, ScaleState) { ASSERT_NO_THROW({ auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, - 2, mode_extents); + mode_extents); ASSERT_NE(state, nullptr); cudaStream_t stream; From 8f7ff08a52051e9b42a3980cebc972e7aed2f33c Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Wed, 22 Jan 2025 08:59:06 -0800 Subject: [PATCH 21/40] Fixing convert_to_cudensitymat_operator as per the cudensitymat APIs Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/cudm_helpers.cpp | 125 ++++++++++++------------ runtime/cudaq/operators.h | 5 +- 2 files changed, 68 insertions(+), 62 deletions(-) diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp index 914a02914b..1fd7567f51 100644 --- a/runtime/cudaq/dynamics/cudm_helpers.cpp +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -73,6 +73,15 @@ compute_lindblad_operator(cudensitymatHandle_t handle, return lindblad_op; } +std::map +convert_dimensions(const std::vector &mode_extents) { + std::map dimensions; + for (size_t i = 0; i < mode_extents.size(); i++) { + dimensions[static_cast(i)] = static_cast(mode_extents[i]); + } + return dimensions; +} + cudensitymatOperator_t convert_to_cudensitymat_operator( cudensitymatHandle_t handle, const std::map> ¶meters, @@ -86,71 +95,67 @@ cudensitymatOperator_t convert_to_cudensitymat_operator( throw std::runtime_error("Failed to create operator."); } - // Define dimensions for the operator - std::map dimensions; - for (size_t i = 0; i < mode_extents.size(); i++) { - dimensions[static_cast(i)] = static_cast(mode_extents[i]); - } - - auto matrix = op.to_matrix(dimensions, parameters); - size_t dim = matrix.get_rows(); - if (matrix.get_columns() != dim) { - throw std::invalid_argument("Matrix must be a square."); - } - - std::vector> flat_matrix; - for (size_t i = 0; i < matrix.get_rows(); i++) { - for (size_t j = 0; j < matrix.get_columns(); j++) { - flat_matrix.push_back(matrix[{i, j}]); + for (const auto &product_op : op.get_terms()) { + cudensitymatOperatorTerm_t term; + + HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( + handle, static_cast(mode_extents.size()), + mode_extents.data(), &term)); + + for (const auto &component : product_op.get_terms()) { + if (std::holds_alternative(component)) { + const auto &elem_op = std::get(component); + + // Create a cudensitymat elementary operator + cudensitymatElementaryOperator_t cudm_elem_op; + + // Get the matrix representation of elementary operator + auto dimensions = convert_dimensions(mode_extents); + auto matrix = elem_op.to_matrix(dimensions, parameters); + + // Flatten the matrix into a single-dimensional array + std::vector> flat_matrix; + for (size_t i = 0; i < matrix.get_rows(); i++) { + for (size_t j = 0; j < matrix.get_columns(); j++) { + flat_matrix.push_back(matrix[{i, j}]); + } + } + + // Create a cudensitymat elementary operator + HANDLE_CUDM_ERROR(cudensitymatCreateElementaryOperator( + handle, 1, mode_extents.data(), + CUDENSITYMAT_OPERATOR_SPARSITY_NONE, 0, nullptr, CUDA_C_64F, + flat_matrix.data(), {nullptr, nullptr}, &cudm_elem_op)); + + // Append the elementary operator to the term + HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( + handle, term, 1, &cudm_elem_op, &elem_op.degrees[0], nullptr, + make_cuDoubleComplex(1.0, 0.0), {nullptr, nullptr})); + + // Destroy the elementary operator after appending + HANDLE_CUDM_ERROR( + cudensitymatDestroyElementaryOperator(cudm_elem_op)); + } else if (std::holds_alternative(component)) { + const auto &scalar_op = std::get(component); + + // Use the scalar coefficient + auto coeff = scalar_op.evaluate(parameters); + HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( + handle, term, 0, nullptr, nullptr, nullptr, + {make_cuDoubleComplex(coeff.real(), coeff.imag())}, + {nullptr, nullptr})); + } } - } - - cudensitymatOperatorTerm_t term; - status = cudensitymatCreateOperatorTerm( - handle, static_cast(mode_extents.size()), mode_extents.data(), - &term); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperator(operator_handle); - throw std::runtime_error("Failed to create operator term."); - } - - // Attach flat_matrix to the term - int32_t num_elem_operators = 1; - int32_t num_operator_modes = static_cast(mode_extents.size()); - const int64_t *operator_mode_extents = mode_extents.data(); - const int64_t *operator_mode_strides = nullptr; - int32_t state_modes_acted_on[static_cast(mode_extents.size())]; - for (int32_t i = 0; i < num_operator_modes; i++) { - state_modes_acted_on[i] = i; - } - - cudensitymatWrappedTensorCallback_t tensorCallback = {nullptr, nullptr}; - cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; - void *tensor_data = flat_matrix.data(); - cuDoubleComplex coefficient = make_cuDoubleComplex(1.0, 0.0); + // Append the product operator term to the top-level operator + HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( + handle, operator_handle, term, 0, make_cuDoubleComplex(1.0, 0.0), + {nullptr, nullptr})); - status = cudensitymatOperatorTermAppendGeneralProduct( - handle, term, num_elem_operators, &num_operator_modes, - &operator_mode_extents, &operator_mode_strides, state_modes_acted_on, - nullptr, CUDA_C_64F, &tensor_data, &tensorCallback, coefficient, - scalarCallback); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperatorTerm(term); - cudensitymatDestroyOperator(operator_handle); - throw std::runtime_error( - "Failed to attach flat_matrix to operator term."); + // Destroy the term + HANDLE_CUDM_ERROR(cudensitymatDestroyOperatorTerm(term)); } - status = cudensitymatOperatorAppendTerm(handle, operator_handle, term, 0, - coefficient, scalarCallback); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - cudensitymatDestroyOperatorTerm(term); - cudensitymatDestroyOperator(operator_handle); - throw std::runtime_error("Failed to attach term to operator."); - } - - cudensitymatDestroyOperatorTerm(term); return operator_handle; } catch (const std::exception &e) { std::cerr << "Error in convert_to_cudensitymat_operator: " << e.what() diff --git a/runtime/cudaq/operators.h b/runtime/cudaq/operators.h index 8924dbb861..fe088dbd79 100644 --- a/runtime/cudaq/operators.h +++ b/runtime/cudaq/operators.h @@ -125,7 +125,7 @@ class operator_sum { /// FIXME: Protect this once I can do deeper testing in `unittests`. // protected: - std::vector get_terms() { return m_terms; } + std::vector get_terms() const { return m_terms; } }; operator_sum operator*(std::complex other, operator_sum self); operator_sum operator+(std::complex other, operator_sum self); @@ -218,7 +218,8 @@ class product_operator : public operator_sum { /// FIXME: Protect this once I can do deeper testing in `unittests`. // protected: - std::vector> get_terms() { + std::vector> + get_terms() const { return m_terms; }; }; From 9bd987f2a012a18fcb2d0ae7f8c99e02345406ed Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Wed, 22 Jan 2025 11:36:52 -0800 Subject: [PATCH 22/40] Updating CMakeLists and unittests Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/cudm_helpers.cpp | 13 +++- unittests/CMakeLists.txt | 4 +- unittests/dynamics/test_cudm_helpers.cpp | 75 ++++++++++++------------ 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp index 1fd7567f51..dbd830e92c 100644 --- a/runtime/cudaq/dynamics/cudm_helpers.cpp +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -14,14 +14,21 @@ cudensitymatState_t initialize_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, const std::vector &mode_extents) { cudensitymatState_t state; - HANDLE_CUDM_ERROR(cudensitymatCreateState(handle, purity, mode_extents.size(), - mode_extents.data(), 1, CUDA_C_64F, - &state)); + cudensitymatStatus_t status = + cudensitymatCreateState(handle, purity, mode_extents.size(), + mode_extents.data(), 1, CUDA_C_64F, &state); + if (status != CUDENSITYMAT_STATUS_SUCCESS) { + std::cerr << "Error in cudensitymatCreateState: " << status << std::endl; + } return state; } void scale_state(cudensitymatHandle_t handle, cudensitymatState_t state, double scale_factor, cudaStream_t stream) { + if (!state) { + throw std::invalid_argument("Invalid state provided to scale_state."); + } + HANDLE_CUDM_ERROR( cudensitymatStateComputeScaling(handle, state, &scale_factor, stream)); } diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 5218bb5027..2a142153b4 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -292,7 +292,8 @@ target_link_libraries(test_operators cudaq gtest_main $ENV{CUQUANTUM_INSTALL_PREFIX}/lib/libcudensitymat.so.0 - /usr/local/cuda-12.0/targets/x86_64-linux/lib/libcudart.so.12) + /usr/local/cuda-12.0/targets/x86_64-linux/lib/libcudart.so.12 + fmt::fmt-header-only) gtest_discover_tests(test_operators) add_subdirectory(plugin) @@ -456,4 +457,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/test_cudm_helpers.cpp b/unittests/dynamics/test_cudm_helpers.cpp index ddceb989b9..9b86448bc6 100644 --- a/unittests/dynamics/test_cudm_helpers.cpp +++ b/unittests/dynamics/test_cudm_helpers.cpp @@ -6,6 +6,7 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ +#include #include #include @@ -32,27 +33,41 @@ cudaq::operator_sum initialize_operator_sum() { class CuDensityMatTestFixture : public ::testing::Test { protected: cudensitymatHandle_t handle; + cudaStream_t stream; void SetUp() override { - auto status = cudensitymatCreate(&handle); - ASSERT_EQ(status, CUDENSITYMAT_STATUS_SUCCESS); + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle)); + HANDLE_CUDA_ERROR(cudaStreamCreate(&stream)); } - void TearDown() override { cudensitymatDestroy(handle); } + void TearDown() override { + HANDLE_CUDA_ERROR(cudaStreamDestroy(stream)); + HANDLE_CUDM_ERROR(cudensitymatDestroy(handle)); + } }; -// Test for convert_to_cudensitymat_operator -TEST_F(CuDensityMatTestFixture, ConvertToCuDensityMatOperator) { +// Test for initialize_state +TEST_F(CuDensityMatTestFixture, InitializeState) { std::vector mode_extents = {2, 2}; - auto op_sum = initialize_operator_sum(); + auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, + mode_extents); + ASSERT_NE(state, nullptr); - auto result = - cudaq::convert_to_cudensitymat_operator(handle, {}, op_sum, mode_extents); + cudaq::destroy_state(state); +} - ASSERT_NE(result, nullptr); +// Test for scale_state +TEST_F(CuDensityMatTestFixture, ScaleState) { + std::vector mode_extents = {2}; - cudensitymatDestroyOperator(result); + auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, + mode_extents); + ASSERT_NE(state, nullptr); + + EXPECT_NO_THROW(cudaq::scale_state(handle, state, 2.0, stream)); + + cudaq::destroy_state(state); } // Test for compute_lindblad_op @@ -63,42 +78,24 @@ TEST_F(CuDensityMatTestFixture, ComputeLindbladOp) { cudaq::matrix_2 c_op2({{0.0, 0.0}, {0.0, 1.0}}, {2, 2}); std::vector c_ops = {c_op1, c_op2}; - auto result = cudaq::compute_lindblad_operator(handle, c_ops, mode_extents); - - ASSERT_NE(result, nullptr); - - cudensitymatDestroyOperator(result); -} - -// Test for initialize_state -TEST_F(CuDensityMatTestFixture, InitializeState) { - std::vector mode_extents = {2, 2}; - - auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, - mode_extents); - - ASSERT_NE(state, nullptr); + auto lindblad_op = + cudaq::compute_lindblad_operator(handle, c_ops, mode_extents); + ASSERT_NE(lindblad_op, nullptr); - cudaq::destroy_state(state); + cudensitymatDestroyOperator(lindblad_op); } -// Test for scale_state -TEST_F(CuDensityMatTestFixture, ScaleState) { +// Test for convert_to_cudensitymat_operator +TEST_F(CuDensityMatTestFixture, ConvertToCuDensityMatOperator) { std::vector mode_extents = {2, 2}; - ASSERT_NO_THROW({ - auto state = cudaq::initialize_state(handle, CUDENSITYMAT_STATE_PURITY_PURE, - mode_extents); - ASSERT_NE(state, nullptr); - - cudaStream_t stream; - cudaStreamCreate(&stream); + auto op_sum = initialize_operator_sum(); - EXPECT_NO_THROW(cudaq::scale_state(handle, state, 2.0, stream)); + auto result = + cudaq::convert_to_cudensitymat_operator(handle, {}, op_sum, mode_extents); + ASSERT_NE(result, nullptr); - cudaStreamDestroy(stream); - cudaq::destroy_state(state); - }); + cudensitymatDestroyOperator(result); } // Test invalid handle From a60fadd42b9e933a5b1985b36adb4c637983f257 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Thu, 23 Jan 2025 09:06:53 -0800 Subject: [PATCH 23/40] * Adding vector of cudensitymatElementaryOperator_t to store and destroy the cudensitymatElementaryOperator_t * Fetching subspace_extents using elementary_op degrees * Adding a vector with correct action duality Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/cudm_helpers.cpp | 38 +++++++++++++++++++------ 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp index dbd830e92c..ea6972f715 100644 --- a/runtime/cudaq/dynamics/cudm_helpers.cpp +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -102,6 +102,8 @@ cudensitymatOperator_t convert_to_cudensitymat_operator( throw std::runtime_error("Failed to create operator."); } + std::vector elementary_operators; + for (const auto &product_op : op.get_terms()) { cudensitymatOperatorTerm_t term; @@ -113,6 +115,14 @@ cudensitymatOperator_t convert_to_cudensitymat_operator( if (std::holds_alternative(component)) { const auto &elem_op = std::get(component); + std::vector subspace_extents; + for (int degree : elem_op.degrees) { + if (degree > mode_extents.size()) { + throw std::out_of_range("Degree exceeds mode_extents size."); + } + subspace_extents.push_back(mode_extents[degree]); + } + // Create a cudensitymat elementary operator cudensitymatElementaryOperator_t cudm_elem_op; @@ -130,23 +140,28 @@ cudensitymatOperator_t convert_to_cudensitymat_operator( // Create a cudensitymat elementary operator HANDLE_CUDM_ERROR(cudensitymatCreateElementaryOperator( - handle, 1, mode_extents.data(), - CUDENSITYMAT_OPERATOR_SPARSITY_NONE, 0, nullptr, CUDA_C_64F, - flat_matrix.data(), {nullptr, nullptr}, &cudm_elem_op)); + handle, static_cast(subspace_extents.size()), + subspace_extents.data(), CUDENSITYMAT_OPERATOR_SPARSITY_NONE, 0, + nullptr, CUDA_C_64F, flat_matrix.data(), {nullptr, nullptr}, + &cudm_elem_op)); + + elementary_operators.push_back(cudm_elem_op); + + // Default to left action + std::vector modeActionDuality(elem_op.degrees.size(), 0); // Append the elementary operator to the term HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( - handle, term, 1, &cudm_elem_op, &elem_op.degrees[0], nullptr, - make_cuDoubleComplex(1.0, 0.0), {nullptr, nullptr})); - - // Destroy the elementary operator after appending - HANDLE_CUDM_ERROR( - cudensitymatDestroyElementaryOperator(cudm_elem_op)); + handle, term, 1, &cudm_elem_op, elem_op.degrees.data(), + modeActionDuality.data(), make_cuDoubleComplex(1.0, 0.0), + {nullptr, nullptr})); } else if (std::holds_alternative(component)) { const auto &scalar_op = std::get(component); // Use the scalar coefficient auto coeff = scalar_op.evaluate(parameters); + // TODO: Implement handling for time-dependent scalars using + // cudensitymatScalarCallback_t HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( handle, term, 0, nullptr, nullptr, nullptr, {make_cuDoubleComplex(coeff.real(), coeff.imag())}, @@ -161,6 +176,11 @@ cudensitymatOperator_t convert_to_cudensitymat_operator( // Destroy the term HANDLE_CUDM_ERROR(cudensitymatDestroyOperatorTerm(term)); + + // Cleanup + for (auto &elem_op : elementary_operators) { + HANDLE_CUDM_ERROR(cudensitymatDestroyElementaryOperator(elem_op)); + } } return operator_handle; From 35b434a653522c467539a52d932d15f05a14cfcb Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Thu, 23 Jan 2025 09:42:11 -0800 Subject: [PATCH 24/40] Refactoring the code Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/cudm_helpers.cpp | 157 ++++++++++++++---------- 1 file changed, 91 insertions(+), 66 deletions(-) diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp index ea6972f715..0993fa7f7d 100644 --- a/runtime/cudaq/dynamics/cudm_helpers.cpp +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -10,6 +10,82 @@ #include "cudaq/cudm_error_handling.h" namespace cudaq { +// Function to flatten a matrix into a 1D array +std::vector> flatten_matrix(const matrix_2 &matrix) { + std::vector> flat_matrix; + + for (size_t i = 0; i < matrix.get_rows(); i++) { + for (size_t j = 0; j < matrix.get_columns(); j++) { + flat_matrix.push_back(matrix[{i, j}]); + } + } + + return flat_matrix; +} + +// Function to extract sub-space extents based on degrees +std::vector +get_subspace_extents(const std::vector &mode_extents, + const std::vector °rees) { + std::vector subspace_extents; + + for (int degree : degrees) { + if (degree >= mode_extents.size()) { + throw std::out_of_range("Degree exceeds mode_extents size."); + } + subspace_extents.push_back(mode_extents[degree]); + } + + return subspace_extents; +} + +// Function to create a cudensitymat elementary operator +cudensitymatElementaryOperator_t create_elementary_operator( + cudensitymatHandle_t handle, const std::vector &subspace_extents, + const std::vector> &flat_matrix) { + cudensitymatElementaryOperator_t cudm_elem_op; + + std::vector interleaved_matrix; + interleaved_matrix.reserve(flat_matrix.size() * 2); + + for (const auto &value : flat_matrix) { + interleaved_matrix.push_back(value.real()); + interleaved_matrix.push_back(value.imag()); + } + + HANDLE_CUDM_ERROR(cudensitymatCreateElementaryOperator( + handle, static_cast(subspace_extents.size()), + subspace_extents.data(), CUDENSITYMAT_OPERATOR_SPARSITY_NONE, 0, nullptr, + CUDA_C_64F, static_cast(interleaved_matrix.data()), + {nullptr, nullptr}, &cudm_elem_op)); + + return cudm_elem_op; +} + +// Function to append an elementary operator to a term +void append_elementary_operator_to_term( + cudensitymatHandle_t handle, cudensitymatOperatorTerm_t term, + cudensitymatElementaryOperator_t &elem_op, + const std::vector °rees) { + std::vector modeActionDuality(degrees.size(), 0); + + HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( + handle, term, static_cast(degrees.size()), &elem_op, + degrees.data(), modeActionDuality.data(), make_cuDoubleComplex(1.0, 0.0), + {nullptr, nullptr})); +} + +// Function to create and append a scalar to a term +void append_scalar_to_term(cudensitymatHandle_t handle, + cudensitymatOperatorTerm_t term, + const std::complex &coeff) { + // TODO: Implement handling for time-dependent scalars using + // cudensitymatScalarCallback_t + HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( + handle, term, 0, nullptr, nullptr, nullptr, + {make_cuDoubleComplex(coeff.real(), coeff.imag())}, {nullptr, nullptr})); +} + cudensitymatState_t initialize_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, const std::vector &mode_extents) { @@ -51,26 +127,15 @@ compute_lindblad_operator(cudensitymatHandle_t handle, &lindblad_op)); for (const auto &c_op : c_ops) { - size_t dim = c_op.get_rows(); - if (dim == 0 || c_op.get_columns() != dim) { - throw std::invalid_argument("Collapse operator must be a square matrix."); - } + auto flat_matrix = flatten_matrix(c_op); - std::vector> flat_matrix(dim * dim); - for (size_t i = 0; i < dim; i++) { - for (size_t j = 0; j < dim; j++) { - flat_matrix[i * dim + j] = c_op[{i, j}]; - } - } - - // Create Operator term for LtL and add to lindblad_op cudensitymatOperatorTerm_t term; HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( handle, static_cast(mode_extents.size()), mode_extents.data(), &term)); cudensitymatDestroyOperator(lindblad_op); - // Attach terms and cleanup + // Add term to lindblad operator cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm(handle, lindblad_op, term, 0, {1.0}, scalarCallback)); @@ -95,12 +160,9 @@ cudensitymatOperator_t convert_to_cudensitymat_operator( const operator_sum &op, const std::vector &mode_extents) { try { cudensitymatOperator_t operator_handle; - auto status = cudensitymatCreateOperator( + HANDLE_CUDM_ERROR(cudensitymatCreateOperator( handle, static_cast(mode_extents.size()), mode_extents.data(), - &operator_handle); - if (status != CUDENSITYMAT_STATUS_SUCCESS) { - throw std::runtime_error("Failed to create operator."); - } + &operator_handle)); std::vector elementary_operators; @@ -115,57 +177,20 @@ cudensitymatOperator_t convert_to_cudensitymat_operator( if (std::holds_alternative(component)) { const auto &elem_op = std::get(component); - std::vector subspace_extents; - for (int degree : elem_op.degrees) { - if (degree > mode_extents.size()) { - throw std::out_of_range("Degree exceeds mode_extents size."); - } - subspace_extents.push_back(mode_extents[degree]); - } - - // Create a cudensitymat elementary operator - cudensitymatElementaryOperator_t cudm_elem_op; - - // Get the matrix representation of elementary operator - auto dimensions = convert_dimensions(mode_extents); - auto matrix = elem_op.to_matrix(dimensions, parameters); - - // Flatten the matrix into a single-dimensional array - std::vector> flat_matrix; - for (size_t i = 0; i < matrix.get_rows(); i++) { - for (size_t j = 0; j < matrix.get_columns(); j++) { - flat_matrix.push_back(matrix[{i, j}]); - } - } - - // Create a cudensitymat elementary operator - HANDLE_CUDM_ERROR(cudensitymatCreateElementaryOperator( - handle, static_cast(subspace_extents.size()), - subspace_extents.data(), CUDENSITYMAT_OPERATOR_SPARSITY_NONE, 0, - nullptr, CUDA_C_64F, flat_matrix.data(), {nullptr, nullptr}, - &cudm_elem_op)); + auto subspace_extents = + get_subspace_extents(mode_extents, elem_op.degrees); + auto flat_matrix = flatten_matrix( + elem_op.to_matrix(convert_dimensions(mode_extents), parameters)); + auto cudm_elem_op = + create_elementary_operator(handle, subspace_extents, flat_matrix); elementary_operators.push_back(cudm_elem_op); - - // Default to left action - std::vector modeActionDuality(elem_op.degrees.size(), 0); - - // Append the elementary operator to the term - HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( - handle, term, 1, &cudm_elem_op, elem_op.degrees.data(), - modeActionDuality.data(), make_cuDoubleComplex(1.0, 0.0), - {nullptr, nullptr})); + append_elementary_operator_to_term(handle, term, cudm_elem_op, + elem_op.degrees); } else if (std::holds_alternative(component)) { - const auto &scalar_op = std::get(component); - - // Use the scalar coefficient - auto coeff = scalar_op.evaluate(parameters); - // TODO: Implement handling for time-dependent scalars using - // cudensitymatScalarCallback_t - HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( - handle, term, 0, nullptr, nullptr, nullptr, - {make_cuDoubleComplex(coeff.real(), coeff.imag())}, - {nullptr, nullptr})); + auto coeff = + std::get(component).evaluate(parameters); + append_scalar_to_term(handle, term, coeff); } } From 9222f173f60e22b419fca06dc1a479f975ca47dd Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Thu, 23 Jan 2025 15:41:54 -0800 Subject: [PATCH 25/40] Fixing the matrix in the unittest and other code in compute_lindblad apart for temp vector Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/cudm_helpers.cpp | 21 +++++++++++++++++++-- runtime/cudaq/utils/tensor.cpp | 1 + unittests/dynamics/test_cudm_helpers.cpp | 4 ++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp index 0993fa7f7d..999ba90ff5 100644 --- a/runtime/cudaq/dynamics/cudm_helpers.cpp +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -127,19 +127,36 @@ compute_lindblad_operator(cudensitymatHandle_t handle, &lindblad_op)); for (const auto &c_op : c_ops) { + size_t dim = c_op.get_rows(); + if (dim == 0 || c_op.get_columns() != dim) { + throw std::invalid_argument("Collapse operator must be a square matrix"); + } + auto flat_matrix = flatten_matrix(c_op); + // Create Operator term for LtL and add to lindblad_op cudensitymatOperatorTerm_t term; HANDLE_CUDM_ERROR(cudensitymatCreateOperatorTerm( handle, static_cast(mode_extents.size()), mode_extents.data(), &term)); - cudensitymatDestroyOperator(lindblad_op); + + // Create elementary operator form c_op + auto cudm_elem_op = + create_elementary_operator(handle, mode_extents, flat_matrix); + + // Add the elementary operator to the term + // TODO: Fix temp vector below + std::vector temp; + append_elementary_operator_to_term(handle, term, cudm_elem_op, temp); // Add term to lindblad operator cudensitymatWrappedScalarCallback_t scalarCallback = {nullptr, nullptr}; HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm(handle, lindblad_op, term, 0, {1.0}, scalarCallback)); - cudensitymatDestroyOperatorTerm(term); + + // Destroy intermediate resources + HANDLE_CUDM_ERROR(cudensitymatDestroyOperatorTerm(term)); + HANDLE_CUDM_ERROR(cudensitymatDestroyElementaryOperator(cudm_elem_op)); } return lindblad_op; diff --git a/runtime/cudaq/utils/tensor.cpp b/runtime/cudaq/utils/tensor.cpp index f37f959090..6d4aaa9764 100644 --- a/runtime/cudaq/utils/tensor.cpp +++ b/runtime/cudaq/utils/tensor.cpp @@ -7,6 +7,7 @@ ******************************************************************************/ #include "cudaq/utils/tensor.h" +#include #include inline std::complex &access(std::complex *p, diff --git a/unittests/dynamics/test_cudm_helpers.cpp b/unittests/dynamics/test_cudm_helpers.cpp index 9b86448bc6..734470365a 100644 --- a/unittests/dynamics/test_cudm_helpers.cpp +++ b/unittests/dynamics/test_cudm_helpers.cpp @@ -74,8 +74,8 @@ TEST_F(CuDensityMatTestFixture, ScaleState) { TEST_F(CuDensityMatTestFixture, ComputeLindbladOp) { std::vector mode_extents = {2, 2}; - cudaq::matrix_2 c_op1({{1.0, 0.0}, {0.0, 0.0}}, {2, 2}); - cudaq::matrix_2 c_op2({{0.0, 0.0}, {0.0, 1.0}}, {2, 2}); + cudaq::matrix_2 c_op1({1.0, 0.0, 0.0, 0.0}, {2, 2}); + cudaq::matrix_2 c_op2({0.0, 0.0, 0.0, 1.0}, {2, 2}); std::vector c_ops = {c_op1, c_op2}; auto lindblad_op = From eee6da49f04da2ce80764013581f6e402a27b83d Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Fri, 24 Jan 2025 21:39:13 -0800 Subject: [PATCH 26/40] Adding cudm_state with unittests Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_state.h | 76 +++++++++++ runtime/cudaq/dynamics/CMakeLists.txt | 2 +- runtime/cudaq/dynamics/cudm_state.cpp | 173 +++++++++++++++++++++++++ runtime/cudaq/dynamics/cudm_state.h | 28 ---- unittests/CMakeLists.txt | 2 + unittests/dynamics/test_cudm_state.cpp | 117 +++++++++++++++++ 6 files changed, 369 insertions(+), 29 deletions(-) create mode 100644 runtime/cudaq/cudm_state.h create mode 100644 runtime/cudaq/dynamics/cudm_state.cpp delete mode 100644 runtime/cudaq/dynamics/cudm_state.h create mode 100644 unittests/dynamics/test_cudm_state.cpp diff --git a/runtime/cudaq/cudm_state.h b/runtime/cudaq/cudm_state.h new file mode 100644 index 0000000000..814a7ad342 --- /dev/null +++ b/runtime/cudaq/cudm_state.h @@ -0,0 +1,76 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace cudaq { +class cudm_mat_state { +public: + /// @brief To initialize state with raw data. + explicit cudm_mat_state(std::vector> rawData); + + /// @brief Destructor to clean up resources + ~cudm_mat_state(); + + /// @brief Initialize the state as a density matrix or state vector based on + /// dimensions. + /// @param hilbertSpaceDims Vector representing the Hilbert Space dimensions. + void init_state(const std::vector &hilbertSpaceDims); + + /// @brief Check if the state is initialized. + /// @return True if the state is initialized, false otherwise. + bool is_initialized() const; + + /// @brief Check if the state is a density matrix. + /// @return True if the state is a density matrix, false otherwise. + bool is_density_matrix() const; + + /// @brief Dump the state data to a string for debugging purposes. + /// @return String representation of the state data. + std::string dump() const; + + /// @brief Convert the state vector to a density matrix. + /// @return A new cudm_mat_state representing the density matrix. + cudm_mat_state to_density_matrix() const; + + /// @brief Get the underlying implementation (if any). + /// @return The underlying state implementation. + cudensitymatState_t get_impl() const; + + /// @brief Attach raw data to the internal state representation + void attach_storage(); + +private: + std::vector> rawData_; + cudensitymatState_t state_; + cudensitymatHandle_t handle_; + std::vector hilbertSpaceDims_; + + /// @brief Calculate the size of the state vector for the given Hilbert space + /// dimensions. + /// @param hilbertSpaceDims Hilbert space dimensions. + /// @return Size of the state vector. + size_t calculate_state_vector_size( + const std::vector &hilbertSpaceDims) const; + + /// @brief Calculate the size of the density matrix for the given Hilbert + /// space dimensions. + /// @param hilbertSpaceDims Hilbert space dimensions. + /// @return Size of the density matrix. + size_t calculate_density_matrix_size( + const std::vector &hilbertSpaceDims) const; +}; + +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/CMakeLists.txt b/runtime/cudaq/dynamics/CMakeLists.txt index 283e77e7ff..90354c7e96 100644 --- a/runtime/cudaq/dynamics/CMakeLists.txt +++ b/runtime/cudaq/dynamics/CMakeLists.txt @@ -11,7 +11,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") set(INTERFACE_POSITION_INDEPENDENT_CODE ON) set(CUDAQ_OPS_SRC - scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp rydberg_hamiltonian.cpp cudm_helpers.cpp + scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp rydberg_hamiltonian.cpp cudm_helpers.cpp cudm_state.cpp ) set(CUQUANTUM_INSTALL_PREFIX "/usr/local/lib/python3.10/dist-packages/cuquantum") diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp new file mode 100644 index 0000000000..dfbac25057 --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include +#include +#include + +namespace cudaq { + +cudm_mat_state::cudm_mat_state(std::vector> rawData) + : rawData_(rawData), state_(nullptr), handle_(nullptr), + hilbertSpaceDims_() { + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); +} + +cudm_mat_state::~cudm_mat_state() { + if (state_) { + cudensitymatDestroyState(state_); + } + if (handle_) { + cudensitymatDestroy(handle_); + } +} + +void cudm_mat_state::init_state(const std::vector &hilbertSpaceDims) { + if (state_) { + throw std::runtime_error("State is already initialized."); + } + + hilbertSpaceDims_ = hilbertSpaceDims; + cudensitymatStatePurity_t purity; + + if (rawData_.size() == calculate_density_matrix_size(hilbertSpaceDims)) { + purity = CUDENSITYMAT_STATE_PURITY_MIXED; + } else if (rawData_.size() == calculate_state_vector_size(hilbertSpaceDims)) { + purity = CUDENSITYMAT_STATE_PURITY_PURE; + } else { + throw std::invalid_argument( + "Invalid rawData size for the given Hilbert space dimensions."); + } + + HANDLE_CUDM_ERROR(cudensitymatCreateState( + handle_, purity, static_cast(hilbertSpaceDims.size()), + hilbertSpaceDims.data(), 1, CUDA_C_64F, &state_)); + + attach_storage(); +} + +bool cudm_mat_state::is_initialized() const { return state_ != nullptr; } + +bool cudm_mat_state::is_density_matrix() const { + if (!is_initialized()) { + return false; + } + + return rawData_.size() == calculate_density_matrix_size(hilbertSpaceDims_); +} + +std::string cudm_mat_state::dump() const { + if (!is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + + std::ostringstream oss; + oss << "State data: ["; + for (size_t i = 0; i < rawData_.size(); i++) { + oss << rawData_[i]; + if (i < rawData_.size() - 1) { + oss << ", "; + } + } + oss << "]"; + return oss.str(); +} + +cudm_mat_state cudm_mat_state::to_density_matrix() const { + if (!is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + + if (is_density_matrix()) { + throw std::runtime_error("State is already a density matrix."); + } + + std::vector> densityMatrix; + size_t vectorSize = calculate_state_vector_size(hilbertSpaceDims_); + size_t expectedDensityMatrixSize = vectorSize * vectorSize; + densityMatrix.resize(expectedDensityMatrixSize); + + for (size_t i = 0; i < vectorSize; i++) { + for (size_t j = 0; j < vectorSize; j++) { + densityMatrix[i * vectorSize + j] = rawData_[i] * std::conj(rawData_[j]); + } + } + + cudm_mat_state densityMatrixState(densityMatrix); + densityMatrixState.init_state(hilbertSpaceDims_); + return densityMatrixState; +} + +cudensitymatState_t cudm_mat_state::get_impl() const { + if (!is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + return state_; +} + +void cudm_mat_state::attach_storage() { + if (!state_) { + throw std::runtime_error("State is not initialized."); + } + + if (rawData_.empty()) { + throw std::runtime_error("Raw data is empty. Cannot attach storage."); + } + + // Retrieve the number of state components + int32_t numStateComponents; + HANDLE_CUDM_ERROR( + cudensitymatStateGetNumComponents(handle_, state_, &numStateComponents)); + + // Retrieve the storage size for each component + std::vector componentBufferSizes(numStateComponents); + HANDLE_CUDM_ERROR(cudensitymatStateGetComponentStorageSize( + handle_, state_, numStateComponents, componentBufferSizes.data())); + + // Validate that rawData_ has sufficient space for all components + size_t totalSize = 0; + for (size_t size : componentBufferSizes) { + totalSize += size; + } + if (rawData_.size() * sizeof(std::complex) < totalSize) { + throw std::invalid_argument( + "Raw data size is insufficient to cover all components."); + } + + // Attach storage for each component + std::vector componentBuffers(numStateComponents); + std::vector *> rawComponentData(numStateComponents); + + size_t offset = 0; + for (int32_t i = 0; i < numStateComponents; i++) { + rawComponentData[i] = + reinterpret_cast *>(rawData_.data()) + offset; + componentBuffers[i] = static_cast(rawComponentData[i]); + offset += componentBufferSizes[i] / sizeof(std::complex); + } + + HANDLE_CUDM_ERROR(cudensitymatStateAttachComponentStorage( + handle_, state_, numStateComponents, componentBuffers.data(), + componentBufferSizes.data())); +} + +size_t cudm_mat_state::calculate_state_vector_size( + const std::vector &hilbertSpaceDims) const { + size_t size = 1; + for (auto dim : hilbertSpaceDims) { + size *= dim; + } + return size; +} + +size_t cudm_mat_state::calculate_density_matrix_size( + const std::vector &hilbertSpaceDims) const { + size_t vectorSize = calculate_state_vector_size(hilbertSpaceDims); + return vectorSize * vectorSize; +} +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/cudm_state.h b/runtime/cudaq/dynamics/cudm_state.h deleted file mode 100644 index 1f20f9483f..0000000000 --- a/runtime/cudaq/dynamics/cudm_state.h +++ /dev/null @@ -1,28 +0,0 @@ -/****************************************************************-*- 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_mat_state { -public: - cudm_mat_state(cudensitymatHandle_t handle, cudensitymatStatePurity_t purity, - int num_modes, const std::vector &mode_extents); - ~cudm_mat_state(); - - void scale(double factor, cudaStream_t stream); - cudensitymatState_t get() const; - -private: - cudensitymatState_t state; - cudensitymatHandle_t handle; -}; -} // namespace cudaq \ No newline at end of file diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 2a142153b4..e2baef400c 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -54,6 +54,7 @@ set(CUDAQ_RUNTIME_TEST_SOURCES dynamics/test_helpers.cpp dynamics/rydberg_hamiltonian.cpp dynamics/test_cudm_helpers.cpp + dynamics/test_cudm_state.cpp ) # Make it so we can get function symbols @@ -280,6 +281,7 @@ set(CUDAQ_OPERATOR_TEST_SOURCES dynamics/scalar_ops_arithmetic.cpp dynamics/product_operators_arithmetic.cpp dynamics/test_cudm_helpers.cpp + dynamics/test_cudm_state.cpp ) add_executable(test_operators main.cpp ${CUDAQ_OPERATOR_TEST_SOURCES}) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) diff --git a/unittests/dynamics/test_cudm_state.cpp b/unittests/dynamics/test_cudm_state.cpp new file mode 100644 index 0000000000..237142fcd0 --- /dev/null +++ b/unittests/dynamics/test_cudm_state.cpp @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace cudaq; + +class CuDensityMatStateTest : public ::testing::Test { +protected: + void SetUp() override { + // Set up test data for a single 2-qubit system + hilbertSpaceDims = {2, 2}; + + // State vector (pure state) for |00> + stateVectorData = { + std::complex(1.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0)}; + + // Density matrix for |00><00| + densityMatrixData = { + std::complex(1.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0), + std::complex(0.0, 0.0), std::complex(0.0, 0.0)}; + } + + void TearDown() override {} + + std::vector hilbertSpaceDims; + std::vector> stateVectorData; + std::vector> densityMatrixData; +}; + +TEST_F(CuDensityMatStateTest, InitializeWithStateVector) { + cudm_mat_state state(stateVectorData); + EXPECT_FALSE(state.is_initialized()); + + EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); + EXPECT_TRUE(state.is_initialized()); + EXPECT_FALSE(state.is_density_matrix()); + + EXPECT_NO_THROW(state.dump()); +} + +TEST_F(CuDensityMatStateTest, InitializeWithDensityMatrix) { + cudm_mat_state state(densityMatrixData); + EXPECT_FALSE(state.is_initialized()); + + EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); + EXPECT_TRUE(state.is_initialized()); + EXPECT_TRUE(state.is_density_matrix()); + + EXPECT_NO_THROW(state.dump()); +} + +TEST_F(CuDensityMatStateTest, InvalidInitialization) { + // Data size mismatch for hilbertSpaceDims (2x2 system expects size 4 or 16) + std::vector> invalidData = { + std::complex(1.0, 0.0), std::complex(0.0, 0.0)}; + + cudm_mat_state state(invalidData); + EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); +} + +TEST_F(CuDensityMatStateTest, ToDensityMatrixConversion) { + cudm_mat_state state(stateVectorData); + state.init_state(hilbertSpaceDims); + + EXPECT_FALSE(state.is_density_matrix()); + + cudm_mat_state densityMatrixState = state.to_density_matrix(); + + EXPECT_TRUE(densityMatrixState.is_density_matrix()); + EXPECT_TRUE(densityMatrixState.is_initialized()); + + EXPECT_NO_THROW(densityMatrixState.dump()); +} + +TEST_F(CuDensityMatStateTest, AlreadyDensityMatrixConversion) { + cudm_mat_state state(densityMatrixData); + state.init_state(hilbertSpaceDims); + + EXPECT_TRUE(state.is_density_matrix()); + EXPECT_THROW(state.to_density_matrix(), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, DumpUninitializedState) { + cudm_mat_state state(stateVectorData); + EXPECT_THROW(state.dump(), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, AttachStorageErrorHandling) { + cudm_mat_state state(stateVectorData); + + EXPECT_THROW(state.attach_storage(), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, DestructorCleansUp) { + cudm_mat_state state(stateVectorData); + + EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); +} From 66f29e7c5875dd5728106c65ce435c66e6b1ffe4 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Sat, 25 Jan 2025 11:57:24 -0800 Subject: [PATCH 27/40] Addign few more unit tests for cudm_state Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/cudm_state.cpp | 20 ++++++-- unittests/dynamics/test_cudm_state.cpp | 68 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp index dfbac25057..b456bef45d 100644 --- a/runtime/cudaq/dynamics/cudm_state.cpp +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -33,15 +33,25 @@ void cudm_mat_state::init_state(const std::vector &hilbertSpaceDims) { } hilbertSpaceDims_ = hilbertSpaceDims; + + size_t rawDataSize = rawData_.size(); + size_t expectedDensityMatrixSize = + calculate_density_matrix_size(hilbertSpaceDims); + size_t expectedStateVectorSize = + calculate_state_vector_size(hilbertSpaceDims); + + if (rawDataSize != expectedDensityMatrixSize && + rawDataSize != expectedStateVectorSize) { + throw std::invalid_argument( + "Invalid rawData size for the given Hilbert space dimensions."); + } + cudensitymatStatePurity_t purity; - if (rawData_.size() == calculate_density_matrix_size(hilbertSpaceDims)) { + if (rawDataSize == expectedDensityMatrixSize) { purity = CUDENSITYMAT_STATE_PURITY_MIXED; - } else if (rawData_.size() == calculate_state_vector_size(hilbertSpaceDims)) { + } else if (rawDataSize == expectedStateVectorSize) { purity = CUDENSITYMAT_STATE_PURITY_PURE; - } else { - throw std::invalid_argument( - "Invalid rawData size for the given Hilbert space dimensions."); } HANDLE_CUDM_ERROR(cudensitymatCreateState( diff --git a/unittests/dynamics/test_cudm_state.cpp b/unittests/dynamics/test_cudm_state.cpp index 237142fcd0..d8ef07ffc4 100644 --- a/unittests/dynamics/test_cudm_state.cpp +++ b/unittests/dynamics/test_cudm_state.cpp @@ -115,3 +115,71 @@ TEST_F(CuDensityMatStateTest, DestructorCleansUp) { EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); } + +TEST_F(CuDensityMatStateTest, InitializeWithEmptyRawData) { + std::vector> emptyData; + cudm_mat_state state(emptyData); + + EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); +} + +TEST_F(CuDensityMatStateTest, ConversionForSingleQubitSystem) { + hilbertSpaceDims = {2}; + stateVectorData = {std::complex(1.0, 0.0), + std::complex(0.0, 0.0)}; + cudm_mat_state state(stateVectorData); + + state.init_state(hilbertSpaceDims); + + EXPECT_FALSE(state.is_density_matrix()); + + cudm_mat_state densityMatrixState = state.to_density_matrix(); + EXPECT_TRUE(densityMatrixState.is_density_matrix()); + EXPECT_TRUE(densityMatrixState.is_initialized()); + + EXPECT_NO_THROW(densityMatrixState.dump()); +} + +TEST_F(CuDensityMatStateTest, InvalidHilbertSpaceDims) { + // 3x3 space is not supported by the provided rawData size + hilbertSpaceDims = {3, 3}; + cudm_mat_state state(stateVectorData); + + EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); +} + +TEST_F(CuDensityMatStateTest, ToDensityMatrixFromUninitializedState) { + cudm_mat_state state(stateVectorData); + + EXPECT_THROW(state.to_density_matrix(), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, MultipleInitialization) { + cudm_mat_state state(stateVectorData); + state.init_state(hilbertSpaceDims); + + EXPECT_TRUE(state.is_initialized()); + + EXPECT_THROW(state.init_state(hilbertSpaceDims), std::runtime_error); +} + +TEST_F(CuDensityMatStateTest, ValidDensityMatrixState) { + cudm_mat_state state(densityMatrixData); + state.init_state(hilbertSpaceDims); + + EXPECT_TRUE(state.is_density_matrix()); + EXPECT_TRUE(state.is_initialized()); +} + +TEST_F(CuDensityMatStateTest, DumpWorksForInitializedState) { + cudm_mat_state state(stateVectorData); + state.init_state(hilbertSpaceDims); + + EXPECT_NO_THROW(state.dump()); +} + +TEST_F(CuDensityMatStateTest, DumpFailsForUninitializedState) { + cudm_mat_state state(stateVectorData); + + EXPECT_THROW(state.dump(), std::runtime_error); +} From cf937f27ee5f958316b948a76960ea9b71ba27de Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Sat, 25 Jan 2025 15:04:22 -0800 Subject: [PATCH 28/40] Adding an enum with initial quantum state and initilize_state based on InitialStateArgT Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/helpers.cpp | 25 +++++++++++++++++++++++++ runtime/cudaq/helpers.h | 11 +++++++++++ 2 files changed, 36 insertions(+) diff --git a/runtime/cudaq/dynamics/helpers.cpp b/runtime/cudaq/dynamics/helpers.cpp index e6c50d14a0..0c03a81d97 100644 --- a/runtime/cudaq/dynamics/helpers.cpp +++ b/runtime/cudaq/dynamics/helpers.cpp @@ -7,6 +7,7 @@ ******************************************************************************/ #include "cudaq/helpers.h" +#include #include #include @@ -144,4 +145,28 @@ OperatorHelpers::canonicalize_degrees(const std::vector °rees) { std::sort(sorted_degrees.rbegin(), sorted_degrees.rend()); return sorted_degrees; } + +// Initialize state based on InitialStateArgT +// TODO: Implement quantum state initialization +void OperatorHelpers::initialize_state(const InitialStateArgT &stateArg, + const std::vector &dimensions) { + if (std::holds_alternative(stateArg)) { + InitialState initState = std::get(stateArg); + switch (initState) { + case InitialState::ZERO: + // ZERO quantum state initialization + std::cout << "Initialized to ZERO state." << std::endl; + break; + case InitialState::UNIFORM: + // UNIFORM quantum state initialization + std::cout << "Initialized to UNIFORM state." << std::endl; + break; + } + } else if (std::holds_alternative(stateArg)) { + // Initialization from runtime state + std::cout << "Initialized using runtime state." << std::endl; + } else { + throw std::invalid_argument("Unsupported InitialStateArgT type."); + } +} } // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/helpers.h b/runtime/cudaq/helpers.h index 52bb131d0c..39f73f7969 100644 --- a/runtime/cudaq/helpers.h +++ b/runtime/cudaq/helpers.h @@ -15,9 +15,16 @@ #include #include #include +#include #include namespace cudaq { + +// Enum to specify the initial quantum state. +enum class InitialState { ZERO, UNIFORM }; + +using InitialStateArgT = std::variant; + class OperatorHelpers { public: // Aggregate parameters from multiple mappings. @@ -46,5 +53,9 @@ class OperatorHelpers { // Canonicalize degrees by sorting in descending order. static std::vector canonicalize_degrees(const std::vector °rees); + + // Initialize state based on InitialStateArgT. + static void initialize_state(const InitialStateArgT &stateArg, + const std::vector &dimensions); }; } // namespace cudaq \ No newline at end of file From 13c9472848fc1bcfbe36d614efce4e0572569d76 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Sat, 25 Jan 2025 16:03:10 -0800 Subject: [PATCH 29/40] Fixing spelling Signed-off-by: Sachin Pisal --- runtime/cudaq/evolution.h | 4 ++-- runtime/cudaq/operators.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/runtime/cudaq/evolution.h b/runtime/cudaq/evolution.h index 7b5f717806..85379f998a 100644 --- a/runtime/cudaq/evolution.h +++ b/runtime/cudaq/evolution.h @@ -53,7 +53,7 @@ class Evolution { const std::vector>> &schedule_parameters); - /// Evolves a single quantum state under a given hamiltonian. + /// Evolves a single quantum state under a given `hamiltonian`. static evolve_result evolve_single(const operator_sum &hamiltonian, const std::map &dimensions, @@ -64,7 +64,7 @@ class Evolution { std::shared_ptr> integrator = nullptr, std::optional shots_count = std::nullopt); - /// Evolves a single or multiple quantum states under a given hamiltonian. + /// 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, diff --git a/runtime/cudaq/operators.h b/runtime/cudaq/operators.h index fe088dbd79..5f07f5dd49 100644 --- a/runtime/cudaq/operators.h +++ b/runtime/cudaq/operators.h @@ -473,12 +473,12 @@ class rydberg_hamiltonian : public operator_sum { /// @brief Constructor. /// @param atom_sites List of 2D coordinates for trap sites. - /// @param amplitude Time-dependant driving amplitude, Omega(t). - /// @param phase Time-dependant driving phase, phi(t). - /// @param delta_global Time-dependant driving detuning, Delta_global(t). + /// @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 dependant + /// @param delta_local Optional. A tuple of Delta_local(t) and site dependent /// local detuning factors. rydberg_hamiltonian( const std::vector &atom_sites, From 5ea49a5503eb3996da723c1d3545808bf2359aa5 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Mon, 27 Jan 2025 16:09:57 -0800 Subject: [PATCH 30/40] * Renaming cudm_mat_state -> cudm_state * Adding create_initial_state method to create initial state based on the passed InitialStateArgT Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_state.h | 25 ++++++-- runtime/cudaq/dynamics/cudm_state.cpp | 85 +++++++++++++++++++++++---- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/runtime/cudaq/cudm_state.h b/runtime/cudaq/cudm_state.h index 814a7ad342..e7f858c8b7 100644 --- a/runtime/cudaq/cudm_state.h +++ b/runtime/cudaq/cudm_state.h @@ -16,13 +16,28 @@ #include namespace cudaq { -class cudm_mat_state { +// 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_mat_state(std::vector> rawData); + explicit cudm_state(std::vector> rawData); /// @brief Destructor to clean up resources - ~cudm_mat_state(); + ~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(const InitialStateArgT &initialStateArg, + const std::vector &hilbertSpaceDims, + bool hasCollapseOps); /// @brief Initialize the state as a density matrix or state vector based on /// dimensions. @@ -42,8 +57,8 @@ class cudm_mat_state { std::string dump() const; /// @brief Convert the state vector to a density matrix. - /// @return A new cudm_mat_state representing the density matrix. - cudm_mat_state to_density_matrix() const; + /// @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. diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp index b456bef45d..40d6d49796 100644 --- a/runtime/cudaq/dynamics/cudm_state.cpp +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -6,19 +6,22 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ +#include #include #include +#include #include +#include namespace cudaq { -cudm_mat_state::cudm_mat_state(std::vector> rawData) +cudm_state::cudm_state(std::vector> rawData) : rawData_(rawData), state_(nullptr), handle_(nullptr), hilbertSpaceDims_() { HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); } -cudm_mat_state::~cudm_mat_state() { +cudm_state::~cudm_state() { if (state_) { cudensitymatDestroyState(state_); } @@ -27,7 +30,7 @@ cudm_mat_state::~cudm_mat_state() { } } -void cudm_mat_state::init_state(const std::vector &hilbertSpaceDims) { +void cudm_state::init_state(const std::vector &hilbertSpaceDims) { if (state_) { throw std::runtime_error("State is already initialized."); } @@ -61,9 +64,9 @@ void cudm_mat_state::init_state(const std::vector &hilbertSpaceDims) { attach_storage(); } -bool cudm_mat_state::is_initialized() const { return state_ != nullptr; } +bool cudm_state::is_initialized() const { return state_ != nullptr; } -bool cudm_mat_state::is_density_matrix() const { +bool cudm_state::is_density_matrix() const { if (!is_initialized()) { return false; } @@ -71,7 +74,7 @@ bool cudm_mat_state::is_density_matrix() const { return rawData_.size() == calculate_density_matrix_size(hilbertSpaceDims_); } -std::string cudm_mat_state::dump() const { +std::string cudm_state::dump() const { if (!is_initialized()) { throw std::runtime_error("State is not initialized."); } @@ -88,7 +91,7 @@ std::string cudm_mat_state::dump() const { return oss.str(); } -cudm_mat_state cudm_mat_state::to_density_matrix() const { +cudm_state cudm_state::to_density_matrix() const { if (!is_initialized()) { throw std::runtime_error("State is not initialized."); } @@ -108,19 +111,19 @@ cudm_mat_state cudm_mat_state::to_density_matrix() const { } } - cudm_mat_state densityMatrixState(densityMatrix); + cudm_state densityMatrixState(densityMatrix); densityMatrixState.init_state(hilbertSpaceDims_); return densityMatrixState; } -cudensitymatState_t cudm_mat_state::get_impl() const { +cudensitymatState_t cudm_state::get_impl() const { if (!is_initialized()) { throw std::runtime_error("State is not initialized."); } return state_; } -void cudm_mat_state::attach_storage() { +void cudm_state::attach_storage() { if (!state_) { throw std::runtime_error("State is not initialized."); } @@ -166,7 +169,7 @@ void cudm_mat_state::attach_storage() { componentBufferSizes.data())); } -size_t cudm_mat_state::calculate_state_vector_size( +size_t cudm_state::calculate_state_vector_size( const std::vector &hilbertSpaceDims) const { size_t size = 1; for (auto dim : hilbertSpaceDims) { @@ -175,9 +178,67 @@ size_t cudm_mat_state::calculate_state_vector_size( return size; } -size_t cudm_mat_state::calculate_density_matrix_size( +size_t cudm_state::calculate_density_matrix_size( const std::vector &hilbertSpaceDims) const { size_t vectorSize = calculate_state_vector_size(hilbertSpaceDims); return vectorSize * vectorSize; } + +// Initialize state based on InitialStateArgT +cudm_state +cudm_state::create_initial_state(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(rawData); + state.init_state(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 From d823f6cc82ecec00b6026eb24f768b24a13ae454 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Mon, 27 Jan 2025 17:07:25 -0800 Subject: [PATCH 31/40] Exposing state's rawData to be used in the stepper Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_state.h | 4 ++++ runtime/cudaq/dynamics/cudm_state.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/runtime/cudaq/cudm_state.h b/runtime/cudaq/cudm_state.h index e7f858c8b7..0425e6db7c 100644 --- a/runtime/cudaq/cudm_state.h +++ b/runtime/cudaq/cudm_state.h @@ -67,6 +67,10 @@ class cudm_state { /// @brief Attach raw data to the internal state representation void attach_storage(); + /// @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; + private: std::vector> rawData_; cudensitymatState_t state_; diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp index 40d6d49796..11b643d177 100644 --- a/runtime/cudaq/dynamics/cudm_state.cpp +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -74,6 +74,10 @@ bool cudm_state::is_density_matrix() const { return rawData_.size() == calculate_density_matrix_size(hilbertSpaceDims_); } +std::vector> cudm_state::get_raw_data() const { + return rawData_; +} + std::string cudm_state::dump() const { if (!is_initialized()) { throw std::runtime_error("State is not initialized."); From 68574c0a8fdbd277da1c7b933f5c8d8d655fecf4 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Tue, 28 Jan 2025 15:02:31 -0800 Subject: [PATCH 32/40] Adding cudm_time_stepper with unittests Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_solver.h | 52 +++++++++++ runtime/cudaq/cudm_state.h | 4 + runtime/cudaq/cudm_time_stepper.h | 26 ++++++ runtime/cudaq/dynamics/CMakeLists.txt | 2 +- runtime/cudaq/dynamics/cudm_solver.cpp | 52 +++++++++++ runtime/cudaq/dynamics/cudm_state.cpp | 4 + runtime/cudaq/dynamics/cudm_time_stepper.cpp | 57 ++++++++++++ runtime/cudaq/dynamics/helpers.cpp | 25 +----- runtime/cudaq/helpers.h | 12 +-- unittests/CMakeLists.txt | 3 +- unittests/dynamics/test_cudm_state.cpp | 36 ++++---- unittests/dynamics/test_cudm_time_stepper.cpp | 86 +++++++++++++++++++ 12 files changed, 303 insertions(+), 56 deletions(-) create mode 100644 runtime/cudaq/cudm_solver.h create mode 100644 runtime/cudaq/cudm_time_stepper.h create mode 100644 runtime/cudaq/dynamics/cudm_solver.cpp create mode 100644 runtime/cudaq/dynamics/cudm_time_stepper.cpp create mode 100644 unittests/dynamics/test_cudm_time_stepper.cpp diff --git a/runtime/cudaq/cudm_solver.h b/runtime/cudaq/cudm_solver.h new file mode 100644 index 0000000000..2c7e14bfca --- /dev/null +++ b/runtime/cudaq/cudm_solver.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 +#include +#include +#include +#include +#include +#include "runtime/common/EvolveResult.h" +#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_; +}; +} \ No newline at end of file diff --git a/runtime/cudaq/cudm_state.h b/runtime/cudaq/cudm_state.h index 0425e6db7c..ad7eb22cae 100644 --- a/runtime/cudaq/cudm_state.h +++ b/runtime/cudaq/cudm_state.h @@ -71,6 +71,10 @@ class cudm_state { /// @return A copy of the raw data as a vector of complex numbers. std::vector> get_raw_data() 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; + private: std::vector> rawData_; cudensitymatState_t state_; diff --git a/runtime/cudaq/cudm_time_stepper.h b/runtime/cudaq/cudm_time_stepper.h new file mode 100644 index 0000000000..59b0d942d8 --- /dev/null +++ b/runtime/cudaq/cudm_time_stepper.h @@ -0,0 +1,26 @@ +/****************************************************************-*- 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); + + void compute(cudm_state &state, double t, double step_size) override; + +private: + cudensitymatHandle_t handle_; + cudensitymatOperator_t liouvillian_; +}; +} \ No newline at end of file diff --git a/runtime/cudaq/dynamics/CMakeLists.txt b/runtime/cudaq/dynamics/CMakeLists.txt index 90354c7e96..636d8b4096 100644 --- a/runtime/cudaq/dynamics/CMakeLists.txt +++ b/runtime/cudaq/dynamics/CMakeLists.txt @@ -11,7 +11,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") set(INTERFACE_POSITION_INDEPENDENT_CODE ON) set(CUDAQ_OPS_SRC - scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp rydberg_hamiltonian.cpp cudm_helpers.cpp cudm_state.cpp + scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp rydberg_hamiltonian.cpp cudm_helpers.cpp cudm_state.cpp cudm_time_stepper.cpp ) set(CUQUANTUM_INSTALL_PREFIX "/usr/local/lib/python3.10/dist-packages/cuquantum") diff --git a/runtime/cudaq/dynamics/cudm_solver.cpp b/runtime/cudaq/dynamics/cudm_solver.cpp new file mode 100644 index 0000000000..4f76d4afc8 --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_solver.cpp @@ -0,0 +1,52 @@ +/******************************************************************************* + * 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/base_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 + BaseTimeStepper timeStepper(liouvillian, handle); +} +} \ No newline at end of file diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp index 11b643d177..1e84683b0e 100644 --- a/runtime/cudaq/dynamics/cudm_state.cpp +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -78,6 +78,10 @@ std::vector> cudm_state::get_raw_data() const { return rawData_; } +std::vector cudm_state::get_hilbert_space_dims() const { + return hilbertSpaceDims_; +} + std::string cudm_state::dump() const { if (!is_initialized()) { throw std::runtime_error("State is not initialized."); diff --git a/runtime/cudaq/dynamics/cudm_time_stepper.cpp b/runtime/cudaq/dynamics/cudm_time_stepper.cpp new file mode 100644 index 0000000000..86de154442 --- /dev/null +++ b/runtime/cudaq/dynamics/cudm_time_stepper.cpp @@ -0,0 +1,57 @@ +/******************************************************************************* + * 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 + +namespace cudaq { +cudm_time_stepper::cudm_time_stepper(cudensitymatHandle_t handle, cudensitymatOperator_t liouvillian) : handle_(handle), liouvillian_(liouvillian) {} + +void cudm_time_stepper::compute(cudm_state &state, double t, double step_size) { + if (!state.is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + + std::cout << "Preparing workspace ..." << std::endl; + // Prepare workspace + cudensitymatWorkspaceDescriptor_t workspace; + HANDLE_CUDM_ERROR(cudensitymatCreateWorkspace(handle_, &workspace)); + if (!workspace) { + throw std::runtime_error("Failed to create workspace for the operator."); + } + + std::cout << "Create a new state for the next step ..." << std::endl; + // Create a new state for the next step + cudm_state next_state(state.to_density_matrix().get_raw_data()); + next_state.init_state(state.get_hilbert_space_dims()); + + if (!next_state.is_initialized()) { + throw std::runtime_error("Next state failed to initialize."); + } + + if (!handle_) { + throw std::runtime_error("cudm_time_stepper handle is not initializes."); + } + + if (!liouvillian_) { + throw std::runtime_error("Liouvillian is not initialized."); + } + + std::cout << "cudensitymatOperatorComputeAction ..." << std::endl; + HANDLE_CUDM_ERROR(cudensitymatOperatorComputeAction(handle_, liouvillian_, t, 0, nullptr, state.get_impl(), next_state.get_impl(), workspace, 0)); + + std::cout << "Update the state ..." << std::endl; + // Update the state + state = std::move(next_state); + + std::cout << "Clean up workspace ..." << std::endl; + // Clean up workspace + HANDLE_CUDM_ERROR(cudensitymatDestroyWorkspace(workspace)); +} +} \ No newline at end of file diff --git a/runtime/cudaq/dynamics/helpers.cpp b/runtime/cudaq/dynamics/helpers.cpp index 0c03a81d97..ae455098f5 100644 --- a/runtime/cudaq/dynamics/helpers.cpp +++ b/runtime/cudaq/dynamics/helpers.cpp @@ -146,27 +146,4 @@ OperatorHelpers::canonicalize_degrees(const std::vector °rees) { return sorted_degrees; } -// Initialize state based on InitialStateArgT -// TODO: Implement quantum state initialization -void OperatorHelpers::initialize_state(const InitialStateArgT &stateArg, - const std::vector &dimensions) { - if (std::holds_alternative(stateArg)) { - InitialState initState = std::get(stateArg); - switch (initState) { - case InitialState::ZERO: - // ZERO quantum state initialization - std::cout << "Initialized to ZERO state." << std::endl; - break; - case InitialState::UNIFORM: - // UNIFORM quantum state initialization - std::cout << "Initialized to UNIFORM state." << std::endl; - break; - } - } else if (std::holds_alternative(stateArg)) { - // Initialization from runtime state - std::cout << "Initialized using runtime state." << std::endl; - } else { - throw std::invalid_argument("Unsupported InitialStateArgT type."); - } -} -} // namespace cudaq \ No newline at end of file +} // namespace cudaq diff --git a/runtime/cudaq/helpers.h b/runtime/cudaq/helpers.h index 39f73f7969..48e7454adf 100644 --- a/runtime/cudaq/helpers.h +++ b/runtime/cudaq/helpers.h @@ -19,12 +19,6 @@ #include namespace cudaq { - -// Enum to specify the initial quantum state. -enum class InitialState { ZERO, UNIFORM }; - -using InitialStateArgT = std::variant; - class OperatorHelpers { public: // Aggregate parameters from multiple mappings. @@ -53,9 +47,5 @@ class OperatorHelpers { // Canonicalize degrees by sorting in descending order. static std::vector canonicalize_degrees(const std::vector °rees); - - // Initialize state based on InitialStateArgT. - static void initialize_state(const InitialStateArgT &stateArg, - const std::vector &dimensions); }; -} // namespace cudaq \ No newline at end of file +} // namespace cudaq diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index e2baef400c..0d99fd5bb2 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -55,6 +55,7 @@ set(CUDAQ_RUNTIME_TEST_SOURCES dynamics/rydberg_hamiltonian.cpp dynamics/test_cudm_helpers.cpp dynamics/test_cudm_state.cpp + dynamics/test_cudm_time_stepper.cpp ) # Make it so we can get function symbols @@ -280,8 +281,6 @@ set(CUDAQ_OPERATOR_TEST_SOURCES dynamics/scalar_ops_simple.cpp dynamics/scalar_ops_arithmetic.cpp dynamics/product_operators_arithmetic.cpp - dynamics/test_cudm_helpers.cpp - dynamics/test_cudm_state.cpp ) add_executable(test_operators main.cpp ${CUDAQ_OPERATOR_TEST_SOURCES}) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) diff --git a/unittests/dynamics/test_cudm_state.cpp b/unittests/dynamics/test_cudm_state.cpp index d8ef07ffc4..c5e0e264cf 100644 --- a/unittests/dynamics/test_cudm_state.cpp +++ b/unittests/dynamics/test_cudm_state.cpp @@ -47,7 +47,7 @@ class CuDensityMatStateTest : public ::testing::Test { }; TEST_F(CuDensityMatStateTest, InitializeWithStateVector) { - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); EXPECT_FALSE(state.is_initialized()); EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); @@ -58,7 +58,7 @@ TEST_F(CuDensityMatStateTest, InitializeWithStateVector) { } TEST_F(CuDensityMatStateTest, InitializeWithDensityMatrix) { - cudm_mat_state state(densityMatrixData); + cudm_state state(densityMatrixData); EXPECT_FALSE(state.is_initialized()); EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); @@ -73,17 +73,17 @@ TEST_F(CuDensityMatStateTest, InvalidInitialization) { std::vector> invalidData = { std::complex(1.0, 0.0), std::complex(0.0, 0.0)}; - cudm_mat_state state(invalidData); + cudm_state state(invalidData); EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); } TEST_F(CuDensityMatStateTest, ToDensityMatrixConversion) { - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); state.init_state(hilbertSpaceDims); EXPECT_FALSE(state.is_density_matrix()); - cudm_mat_state densityMatrixState = state.to_density_matrix(); + cudm_state densityMatrixState = state.to_density_matrix(); EXPECT_TRUE(densityMatrixState.is_density_matrix()); EXPECT_TRUE(densityMatrixState.is_initialized()); @@ -92,7 +92,7 @@ TEST_F(CuDensityMatStateTest, ToDensityMatrixConversion) { } TEST_F(CuDensityMatStateTest, AlreadyDensityMatrixConversion) { - cudm_mat_state state(densityMatrixData); + cudm_state state(densityMatrixData); state.init_state(hilbertSpaceDims); EXPECT_TRUE(state.is_density_matrix()); @@ -100,25 +100,25 @@ TEST_F(CuDensityMatStateTest, AlreadyDensityMatrixConversion) { } TEST_F(CuDensityMatStateTest, DumpUninitializedState) { - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); EXPECT_THROW(state.dump(), std::runtime_error); } TEST_F(CuDensityMatStateTest, AttachStorageErrorHandling) { - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); EXPECT_THROW(state.attach_storage(), std::runtime_error); } TEST_F(CuDensityMatStateTest, DestructorCleansUp) { - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); } TEST_F(CuDensityMatStateTest, InitializeWithEmptyRawData) { std::vector> emptyData; - cudm_mat_state state(emptyData); + cudm_state state(emptyData); EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); } @@ -127,13 +127,13 @@ TEST_F(CuDensityMatStateTest, ConversionForSingleQubitSystem) { hilbertSpaceDims = {2}; stateVectorData = {std::complex(1.0, 0.0), std::complex(0.0, 0.0)}; - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); state.init_state(hilbertSpaceDims); EXPECT_FALSE(state.is_density_matrix()); - cudm_mat_state densityMatrixState = state.to_density_matrix(); + cudm_state densityMatrixState = state.to_density_matrix(); EXPECT_TRUE(densityMatrixState.is_density_matrix()); EXPECT_TRUE(densityMatrixState.is_initialized()); @@ -143,19 +143,19 @@ TEST_F(CuDensityMatStateTest, ConversionForSingleQubitSystem) { TEST_F(CuDensityMatStateTest, InvalidHilbertSpaceDims) { // 3x3 space is not supported by the provided rawData size hilbertSpaceDims = {3, 3}; - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); } TEST_F(CuDensityMatStateTest, ToDensityMatrixFromUninitializedState) { - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); EXPECT_THROW(state.to_density_matrix(), std::runtime_error); } TEST_F(CuDensityMatStateTest, MultipleInitialization) { - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); state.init_state(hilbertSpaceDims); EXPECT_TRUE(state.is_initialized()); @@ -164,7 +164,7 @@ TEST_F(CuDensityMatStateTest, MultipleInitialization) { } TEST_F(CuDensityMatStateTest, ValidDensityMatrixState) { - cudm_mat_state state(densityMatrixData); + cudm_state state(densityMatrixData); state.init_state(hilbertSpaceDims); EXPECT_TRUE(state.is_density_matrix()); @@ -172,14 +172,14 @@ TEST_F(CuDensityMatStateTest, ValidDensityMatrixState) { } TEST_F(CuDensityMatStateTest, DumpWorksForInitializedState) { - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); state.init_state(hilbertSpaceDims); EXPECT_NO_THROW(state.dump()); } TEST_F(CuDensityMatStateTest, DumpFailsForUninitializedState) { - cudm_mat_state state(stateVectorData); + cudm_state state(stateVectorData); EXPECT_THROW(state.dump(), std::runtime_error); } diff --git a/unittests/dynamics/test_cudm_time_stepper.cpp b/unittests/dynamics/test_cudm_time_stepper.cpp new file mode 100644 index 0000000000..d0693fec18 --- /dev/null +++ b/unittests/dynamics/test_cudm_time_stepper.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 +#include +#include +#include +#include + +using namespace cudaq; + +// Mock Liouvillian operator creation +cudensitymatOperator_t mock_liouvillian(cudensitymatHandle_t handle) { + cudensitymatOperator_t liouvillian; + std::vector dimensions = {2, 2}; + HANDLE_CUDM_ERROR(cudensitymatCreateOperator(handle, static_cast(dimensions.size()), dimensions.data(), &liouvillian)); + return liouvillian; +} + +// Mock Hilbert space dimensions +std::vector> mock_initial_state_data() { + return { + {1.0, 0.0}, {0.0, 0.0}, + {0.0, 0.0}, {0.0, 0.0} + }; +} + +// Mock initial raw state data +std::vector mock_hilbert_space_dims() { + return {2, 2}; +} + +class CuDensityMatTimeStepperTest : public ::testing::Test { +protected: + cudensitymatHandle_t handle_; + cudensitymatOperator_t liouvillian_; + cudm_time_stepper *time_stepper_; + cudm_state state_; + + CuDensityMatTimeStepperTest() : state_(mock_initial_state_data()) {}; + + 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_ = new cudm_time_stepper(liouvillian_, handle_); + + // Initialize the state + state_.init_state(mock_hilbert_space_dims()); + + ASSERT_TRUE(state_.is_initialized()); + } + + void TearDown() override { + // Clean up + HANDLE_CUDM_ERROR(cudensitymatDestroyOperator(liouvillian_)); + HANDLE_CUDM_ERROR(cudensitymatDestroy(handle_)); + delete time_stepper_; + } +}; + +// 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()); +} + + + From a0b48d1acd6a2ab8afd97e48b1784fca30c9d479 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Wed, 29 Jan 2025 16:00:45 -0800 Subject: [PATCH 33/40] Refactoring time_stepper compute method to align with cuquantum workflow Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_helpers.h | 6 + runtime/cudaq/cudm_solver.h | 50 ++++--- runtime/cudaq/cudm_state.h | 11 +- runtime/cudaq/cudm_time_stepper.h | 11 +- runtime/cudaq/dynamics/cudm_helpers.cpp | 21 +++ runtime/cudaq/dynamics/cudm_solver.cpp | 54 ++++--- runtime/cudaq/dynamics/cudm_state.cpp | 54 +++---- runtime/cudaq/dynamics/cudm_time_stepper.cpp | 135 ++++++++++++------ runtime/cudaq/dynamics/helpers.cpp | 1 + unittests/dynamics/test_cudm_state.cpp | 38 ++--- unittests/dynamics/test_cudm_time_stepper.cpp | 93 ++++++------ 11 files changed, 288 insertions(+), 186 deletions(-) diff --git a/runtime/cudaq/cudm_helpers.h b/runtime/cudaq/cudm_helpers.h index c2968229fb..08e7a6c7d3 100644 --- a/runtime/cudaq/cudm_helpers.h +++ b/runtime/cudaq/cudm_helpers.h @@ -40,4 +40,10 @@ cudensitymatOperator_t construct_liovillian( cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, const std::vector &collapse_operators, double gamma); + +// Function for creating an array copy in GPU memory +void *create_array_gpu(const std::vector> &cpu_array); + +// Function to detsroy a previously created array copy in GPU memory +void destroy_array_gpu(void *gpu_array); } // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/cudm_solver.h b/runtime/cudaq/cudm_solver.h index 2c7e14bfca..32fc7b00a8 100644 --- a/runtime/cudaq/cudm_solver.h +++ b/runtime/cudaq/cudm_solver.h @@ -8,45 +8,51 @@ #pragma once -#include -#include -#include -#include -#include -#include #include "runtime/common/EvolveResult.h" -#include #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 + 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); + cudm_solver(const Config &config); - void validate_config(); + void validate_config(); - cudm_state initialize_state(); + cudm_state initialize_state(); - void evolve(cudm_state &state, cudensitymatOperator_t &liouvillian, const std::vector &obervable_ops, evolve_result &result); + void evolve(cudm_state &state, cudensitymatOperator_t &liouvillian, + const std::vector &obervable_ops, + evolve_result &result); - evolve_result evolve_dynamics(); + evolve_result evolve_dynamics(); - cudensitymatOperator_t construct_liouvillian(cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, const std::vector &collapse_operators, bool me_solve); + cudensitymatOperator_t construct_liouvillian( + cudensitymatHandle_t handle, const cudensitymatOperator_t &hamiltonian, + const std::vector &collapse_operators, + bool me_solve); private: - Config config_; + Config config_; }; -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/cudm_state.h b/runtime/cudaq/cudm_state.h index ad7eb22cae..220136d1f8 100644 --- a/runtime/cudaq/cudm_state.h +++ b/runtime/cudaq/cudm_state.h @@ -24,7 +24,8 @@ using InitialStateArgT = std::variant; class cudm_state { public: /// @brief To initialize state with raw data. - explicit cudm_state(std::vector> rawData); + explicit cudm_state(cudensitymatHandle_t handle, + std::vector> rawData); /// @brief Destructor to clean up resources ~cudm_state(); @@ -34,10 +35,9 @@ class cudm_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(const InitialStateArgT &initialStateArg, - const std::vector &hilbertSpaceDims, - bool hasCollapseOps); + static cudm_state create_initial_state( + cudensitymatHandle_t handle, const InitialStateArgT &initialStateArg, + const std::vector &hilbertSpaceDims, bool hasCollapseOps); /// @brief Initialize the state as a density matrix or state vector based on /// dimensions. @@ -77,6 +77,7 @@ class cudm_state { private: std::vector> rawData_; + std::complex *gpuData_; cudensitymatState_t state_; cudensitymatHandle_t handle_; std::vector hilbertSpaceDims_; diff --git a/runtime/cudaq/cudm_time_stepper.h b/runtime/cudaq/cudm_time_stepper.h index 59b0d942d8..a245b42c80 100644 --- a/runtime/cudaq/cudm_time_stepper.h +++ b/runtime/cudaq/cudm_time_stepper.h @@ -15,12 +15,13 @@ namespace cudaq { class cudm_time_stepper : public BaseTimeStepper { public: - explicit cudm_time_stepper(cudensitymatHandle_t handle, cudensitymatOperator_t liouvillian); + explicit cudm_time_stepper(cudensitymatHandle_t handle, + cudensitymatOperator_t liouvillian); - void compute(cudm_state &state, double t, double step_size) override; + void compute(cudm_state &state, double t, double step_size) override; private: - cudensitymatHandle_t handle_; - cudensitymatOperator_t liouvillian_; + cudensitymatHandle_t handle_; + cudensitymatOperator_t liouvillian_; }; -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/cudm_helpers.cpp b/runtime/cudaq/dynamics/cudm_helpers.cpp index 999ba90ff5..6325189452 100644 --- a/runtime/cudaq/dynamics/cudm_helpers.cpp +++ b/runtime/cudaq/dynamics/cudm_helpers.cpp @@ -268,4 +268,25 @@ cudensitymatOperator_t construct_liovillian( throw; } } + +// Function for creating an array copy in GPU memory +void *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 destroy_array_gpu(void *gpu_array) { + if (gpu_array) { + HANDLE_CUDA_ERROR(cudaFree(gpu_array)); + } +} } // namespace cudaq diff --git a/runtime/cudaq/dynamics/cudm_solver.cpp b/runtime/cudaq/dynamics/cudm_solver.cpp index 4f76d4afc8..dc35b7ce97 100644 --- a/runtime/cudaq/dynamics/cudm_solver.cpp +++ b/runtime/cudaq/dynamics/cudm_solver.cpp @@ -7,46 +7,54 @@ ******************************************************************************/ #include "cudaq/cudm_solver.h" +#include "cudaq/base_time_stepper.h" #include "cudaq/cudm_helpers.h" #include "cudaq/cudm_state.h" -#include "cudaq/base_time_stepper.h" namespace cudaq { cudm_solver::cudm_solver(const Config &config) : config_(config) { - validate_config(); + validate_config(); } void cudm_solver::validate_config() { - if (config_.dimensions.empty()) { - throw std::invalid_argument("Dimensions map cannot be empty."); - } + 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_.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."); - } + 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); - } + 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()); + 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); +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(); +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 - BaseTimeStepper timeStepper(liouvillian, handle); + // Initialize the stepper + BaseTimeStepper timeStepper(liouvillian, handle); } -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp index 1e84683b0e..6d1174dfd1 100644 --- a/runtime/cudaq/dynamics/cudm_state.cpp +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -15,18 +15,24 @@ namespace cudaq { -cudm_state::cudm_state(std::vector> rawData) - : rawData_(rawData), state_(nullptr), handle_(nullptr), - hilbertSpaceDims_() { - HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); +cudm_state::cudm_state(cudensitymatHandle_t handle, + std::vector> rawData) + : rawData_(rawData), state_(nullptr), handle_(handle), hilbertSpaceDims_() { + // Allocate device memory + size_t dataSize = rawData_.size() * sizeof(std::complex); + cudaMalloc(reinterpret_cast(&gpuData_), dataSize); + + // Copy data from host to device + HANDLE_CUDA_ERROR( + cudaMemcpy(gpuData_, rawData_.data(), dataSize, cudaMemcpyHostToDevice)); } cudm_state::~cudm_state() { if (state_) { cudensitymatDestroyState(state_); } - if (handle_) { - cudensitymatDestroy(handle_); + if (gpuData_) { + cudaFree(gpuData_); } } @@ -119,7 +125,7 @@ cudm_state cudm_state::to_density_matrix() const { } } - cudm_state densityMatrixState(densityMatrix); + cudm_state densityMatrixState(handle_, densityMatrix); densityMatrixState.init_state(hilbertSpaceDims_); return densityMatrixState; } @@ -136,8 +142,9 @@ void cudm_state::attach_storage() { throw std::runtime_error("State is not initialized."); } - if (rawData_.empty()) { - throw std::runtime_error("Raw data is empty. Cannot attach storage."); + 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 @@ -150,25 +157,19 @@ void cudm_state::attach_storage() { HANDLE_CUDM_ERROR(cudensitymatStateGetComponentStorageSize( handle_, state_, numStateComponents, componentBufferSizes.data())); - // Validate that rawData_ has sufficient space for all components - size_t totalSize = 0; - for (size_t size : componentBufferSizes) { - totalSize += size; - } - if (rawData_.size() * sizeof(std::complex) < totalSize) { + // Validate device memory + size_t totalSize = std::accumulate(componentBufferSizes.begin(), + componentBufferSizes.end(), 0); + if (totalSize > rawData_.size() * sizeof(std::complex)) { throw std::invalid_argument( - "Raw data size is insufficient to cover all components."); + "Device memory size is insufficient to cover all components."); } - // Attach storage for each component + // Attach storage for using device memory (gpuData_) std::vector componentBuffers(numStateComponents); - std::vector *> rawComponentData(numStateComponents); - size_t offset = 0; for (int32_t i = 0; i < numStateComponents; i++) { - rawComponentData[i] = - reinterpret_cast *>(rawData_.data()) + offset; - componentBuffers[i] = static_cast(rawComponentData[i]); + componentBuffers[i] = static_cast(gpuData_ + offset); offset += componentBufferSizes[i] / sizeof(std::complex); } @@ -193,10 +194,9 @@ size_t cudm_state::calculate_density_matrix_size( } // Initialize state based on InitialStateArgT -cudm_state -cudm_state::create_initial_state(const InitialStateArgT &initialStateArg, - const std::vector &hilbertSpaceDims, - bool hasCollapseOps) { +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<>{}); @@ -239,7 +239,7 @@ cudm_state::create_initial_state(const InitialStateArgT &initialStateArg, throw std::invalid_argument("Unsupported InitialStateArgT type."); } - cudm_state state(rawData); + cudm_state state(handle, rawData); state.init_state(hilbertSpaceDims); // Convert to a density matrix if collapse operators are present. diff --git a/runtime/cudaq/dynamics/cudm_time_stepper.cpp b/runtime/cudaq/dynamics/cudm_time_stepper.cpp index 86de154442..8a1d01fed4 100644 --- a/runtime/cudaq/dynamics/cudm_time_stepper.cpp +++ b/runtime/cudaq/dynamics/cudm_time_stepper.cpp @@ -8,50 +8,103 @@ #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_time_stepper::cudm_time_stepper(cudensitymatHandle_t handle, + cudensitymatOperator_t liouvillian) + : handle_(handle), liouvillian_(liouvillian) {} void cudm_time_stepper::compute(cudm_state &state, double t, double step_size) { - if (!state.is_initialized()) { - throw std::runtime_error("State is not initialized."); - } - - std::cout << "Preparing workspace ..." << std::endl; - // Prepare workspace - cudensitymatWorkspaceDescriptor_t workspace; - HANDLE_CUDM_ERROR(cudensitymatCreateWorkspace(handle_, &workspace)); - if (!workspace) { - throw std::runtime_error("Failed to create workspace for the operator."); - } - - std::cout << "Create a new state for the next step ..." << std::endl; - // Create a new state for the next step - cudm_state next_state(state.to_density_matrix().get_raw_data()); - next_state.init_state(state.get_hilbert_space_dims()); - - if (!next_state.is_initialized()) { - throw std::runtime_error("Next state failed to initialize."); - } - - if (!handle_) { - throw std::runtime_error("cudm_time_stepper handle is not initializes."); - } - - if (!liouvillian_) { - throw std::runtime_error("Liouvillian is not initialized."); - } - - std::cout << "cudensitymatOperatorComputeAction ..." << std::endl; - HANDLE_CUDM_ERROR(cudensitymatOperatorComputeAction(handle_, liouvillian_, t, 0, nullptr, state.get_impl(), next_state.get_impl(), workspace, 0)); - - std::cout << "Update the state ..." << std::endl; - // Update the state - state = std::move(next_state); - - std::cout << "Clean up workspace ..." << std::endl; - // Clean up workspace - HANDLE_CUDM_ERROR(cudensitymatDestroyWorkspace(workspace)); + if (!state.is_initialized()) { + throw std::runtime_error("State is not initialized."); + } + + if (!handle_) { + throw std::runtime_error("cudm_time_stepper handle is not initializes."); + } + + if (!liouvillian_) { + throw std::runtime_error("Liouvillian is not initialized."); + } + + std::cout << "Preparing workspace ..." << std::endl; + // 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); + + std::cout << "Max workspace buffer size (bytes): " << freeMem << std::endl; + + std::cout << "Create a new state for the next step ..." << std::endl; + // Create a new state for the next step + cudm_state next_state(handle_, state.get_raw_data()); + next_state.init_state(state.get_hilbert_space_dims()); + + 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 + std::cout << "Preparing the operator for action ..." << std::endl; + HANDLE_CUDM_ERROR(cudensitymatOperatorPrepareAction( + handle_, liouvillian_, state.get_impl(), next_state.get_impl(), + CUDENSITYMAT_COMPUTE_64F, freeMem, workspace, 0x0)); + + std::cout << "Querying required workspace buffer size ..." << std::endl; + // Query required workspace buffer size + std::size_t requiredBufferSize = 0; + HANDLE_CUDM_ERROR(cudensitymatWorkspaceGetMemorySize( + handle_, workspace, CUDENSITYMAT_MEMSPACE_DEVICE, + CUDENSITYMAT_WORKSPACE_SCRATCH, &requiredBufferSize)); + + std::cout << "Required workspace buffer size (bytes): " << requiredBufferSize + << std::endl; + + // Allocate GPU storage for workspace buffer + const std::size_t bufferVolume = + requiredBufferSize / sizeof(std::complex); + auto *workspaceBuffer = create_array_gpu( + std::vector>(bufferVolume, {0.0, 0.0})); + + std::cout << "Allocated workspace buffer of size (bytes): " + << requiredBufferSize << std::endl; + + // Attach workspace buffer + HANDLE_CUDM_ERROR(cudensitymatWorkspaceSetMemory( + handle_, workspace, CUDENSITYMAT_MEMSPACE_DEVICE, + CUDENSITYMAT_WORKSPACE_SCRATCH, workspaceBuffer, requiredBufferSize)); + + std::cout << "Attached workspace buffer" << std::endl; + + // Apply the operator action + HANDLE_CUDA_ERROR(cudaDeviceSynchronize()); + HANDLE_CUDM_ERROR(cudensitymatOperatorComputeAction( + handle_, liouvillian_, t, 1, std::vector({step_size}).data(), + state.get_impl(), next_state.get_impl(), workspace, 0x0)); + HANDLE_CUDA_ERROR(cudaDeviceSynchronize()); + + std::cout << "Updated quantum state" << std::endl; + + // Swap states: Move next_state into state + state = std::move(next_state); + + // Cleanup + HANDLE_CUDM_ERROR(cudensitymatDestroyWorkspace(workspace)); + destroy_array_gpu(workspaceBuffer); + + std::cout << "Cleaned up workspace" << std::endl; } -} \ No newline at end of file +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/helpers.cpp b/runtime/cudaq/dynamics/helpers.cpp index ae455098f5..be7906a30b 100644 --- a/runtime/cudaq/dynamics/helpers.cpp +++ b/runtime/cudaq/dynamics/helpers.cpp @@ -7,6 +7,7 @@ ******************************************************************************/ #include "cudaq/helpers.h" +#include "cudaq/cudm_error_handling.h" #include #include #include diff --git a/unittests/dynamics/test_cudm_state.cpp b/unittests/dynamics/test_cudm_state.cpp index c5e0e264cf..163ecd3f59 100644 --- a/unittests/dynamics/test_cudm_state.cpp +++ b/unittests/dynamics/test_cudm_state.cpp @@ -18,7 +18,11 @@ 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}; @@ -39,7 +43,7 @@ class CuDensityMatStateTest : public ::testing::Test { std::complex(0.0, 0.0), std::complex(0.0, 0.0)}; } - void TearDown() override {} + void TearDown() override { cudensitymatDestroy(handle); } std::vector hilbertSpaceDims; std::vector> stateVectorData; @@ -47,7 +51,7 @@ class CuDensityMatStateTest : public ::testing::Test { }; TEST_F(CuDensityMatStateTest, InitializeWithStateVector) { - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); EXPECT_FALSE(state.is_initialized()); EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); @@ -58,7 +62,7 @@ TEST_F(CuDensityMatStateTest, InitializeWithStateVector) { } TEST_F(CuDensityMatStateTest, InitializeWithDensityMatrix) { - cudm_state state(densityMatrixData); + cudm_state state(handle, densityMatrixData); EXPECT_FALSE(state.is_initialized()); EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); @@ -73,12 +77,12 @@ TEST_F(CuDensityMatStateTest, InvalidInitialization) { std::vector> invalidData = { std::complex(1.0, 0.0), std::complex(0.0, 0.0)}; - cudm_state state(invalidData); + cudm_state state(handle, invalidData); EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); } TEST_F(CuDensityMatStateTest, ToDensityMatrixConversion) { - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); state.init_state(hilbertSpaceDims); EXPECT_FALSE(state.is_density_matrix()); @@ -92,7 +96,7 @@ TEST_F(CuDensityMatStateTest, ToDensityMatrixConversion) { } TEST_F(CuDensityMatStateTest, AlreadyDensityMatrixConversion) { - cudm_state state(densityMatrixData); + cudm_state state(handle, densityMatrixData); state.init_state(hilbertSpaceDims); EXPECT_TRUE(state.is_density_matrix()); @@ -100,25 +104,25 @@ TEST_F(CuDensityMatStateTest, AlreadyDensityMatrixConversion) { } TEST_F(CuDensityMatStateTest, DumpUninitializedState) { - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); EXPECT_THROW(state.dump(), std::runtime_error); } TEST_F(CuDensityMatStateTest, AttachStorageErrorHandling) { - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); EXPECT_THROW(state.attach_storage(), std::runtime_error); } TEST_F(CuDensityMatStateTest, DestructorCleansUp) { - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); } TEST_F(CuDensityMatStateTest, InitializeWithEmptyRawData) { std::vector> emptyData; - cudm_state state(emptyData); + cudm_state state(handle, emptyData); EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); } @@ -127,7 +131,7 @@ TEST_F(CuDensityMatStateTest, ConversionForSingleQubitSystem) { hilbertSpaceDims = {2}; stateVectorData = {std::complex(1.0, 0.0), std::complex(0.0, 0.0)}; - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); state.init_state(hilbertSpaceDims); @@ -143,19 +147,19 @@ TEST_F(CuDensityMatStateTest, ConversionForSingleQubitSystem) { TEST_F(CuDensityMatStateTest, InvalidHilbertSpaceDims) { // 3x3 space is not supported by the provided rawData size hilbertSpaceDims = {3, 3}; - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); } TEST_F(CuDensityMatStateTest, ToDensityMatrixFromUninitializedState) { - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); EXPECT_THROW(state.to_density_matrix(), std::runtime_error); } TEST_F(CuDensityMatStateTest, MultipleInitialization) { - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); state.init_state(hilbertSpaceDims); EXPECT_TRUE(state.is_initialized()); @@ -164,7 +168,7 @@ TEST_F(CuDensityMatStateTest, MultipleInitialization) { } TEST_F(CuDensityMatStateTest, ValidDensityMatrixState) { - cudm_state state(densityMatrixData); + cudm_state state(handle, densityMatrixData); state.init_state(hilbertSpaceDims); EXPECT_TRUE(state.is_density_matrix()); @@ -172,14 +176,14 @@ TEST_F(CuDensityMatStateTest, ValidDensityMatrixState) { } TEST_F(CuDensityMatStateTest, DumpWorksForInitializedState) { - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); state.init_state(hilbertSpaceDims); EXPECT_NO_THROW(state.dump()); } TEST_F(CuDensityMatStateTest, DumpFailsForUninitializedState) { - cudm_state state(stateVectorData); + cudm_state state(handle, stateVectorData); EXPECT_THROW(state.dump(), std::runtime_error); } diff --git a/unittests/dynamics/test_cudm_time_stepper.cpp b/unittests/dynamics/test_cudm_time_stepper.cpp index d0693fec18..03807d99af 100644 --- a/unittests/dynamics/test_cudm_time_stepper.cpp +++ b/unittests/dynamics/test_cudm_time_stepper.cpp @@ -6,81 +6,82 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -#include +#include +#include #include #include -#include -#include +#include using namespace cudaq; // Mock Liouvillian operator creation cudensitymatOperator_t mock_liouvillian(cudensitymatHandle_t handle) { - cudensitymatOperator_t liouvillian; - std::vector dimensions = {2, 2}; - HANDLE_CUDM_ERROR(cudensitymatCreateOperator(handle, static_cast(dimensions.size()), dimensions.data(), &liouvillian)); - return liouvillian; + cudensitymatOperator_t liouvillian; + std::vector dimensions = {2, 2}; + HANDLE_CUDM_ERROR(cudensitymatCreateOperator( + handle, static_cast(dimensions.size()), dimensions.data(), + &liouvillian)); + return liouvillian; } // Mock Hilbert space dimensions std::vector> mock_initial_state_data() { - return { - {1.0, 0.0}, {0.0, 0.0}, - {0.0, 0.0}, {0.0, 0.0} - }; + return {{1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}; } // Mock initial raw state data -std::vector mock_hilbert_space_dims() { - return {2, 2}; -} +std::vector mock_hilbert_space_dims() { return {2, 2}; } class CuDensityMatTimeStepperTest : public ::testing::Test { protected: - cudensitymatHandle_t handle_; - cudensitymatOperator_t liouvillian_; - cudm_time_stepper *time_stepper_; - cudm_state state_; + cudensitymatHandle_t handle_; + cudensitymatOperator_t liouvillian_; + cudm_time_stepper *time_stepper_; + cudm_state *state_; + + // CuDensityMatTimeStepperTest() : state_(mock_initial_state_data()) {}; - CuDensityMatTimeStepperTest() : state_(mock_initial_state_data()) {}; + void SetUp() override { + // Create library handle + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); - void SetUp() override { - // Create library handle - HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); + // Create a mock Liouvillian + liouvillian_ = mock_liouvillian(handle_); - // Create a mock Liouvillian - liouvillian_ = mock_liouvillian(handle_); + // Initialize the time stepper + time_stepper_ = new cudm_time_stepper(handle_, liouvillian_); - // Initialize the time stepper - time_stepper_ = new cudm_time_stepper(liouvillian_, handle_); + state_ = new cudm_state(handle_, mock_initial_state_data()); - // Initialize the state - state_.init_state(mock_hilbert_space_dims()); + // Initialize the state + state_->init_state(mock_hilbert_space_dims()); - ASSERT_TRUE(state_.is_initialized()); - } + ASSERT_TRUE(state_->is_initialized()); + } - void TearDown() override { - // Clean up - HANDLE_CUDM_ERROR(cudensitymatDestroyOperator(liouvillian_)); - HANDLE_CUDM_ERROR(cudensitymatDestroy(handle_)); - delete time_stepper_; - } + void TearDown() override { + // Clean up + HANDLE_CUDM_ERROR(cudensitymatDestroyOperator(liouvillian_)); + HANDLE_CUDM_ERROR(cudensitymatDestroy(handle_)); + delete time_stepper_; + delete state_; + } }; // 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_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()); + ASSERT_TRUE(state_->is_initialized()); + EXPECT_NO_THROW(time_stepper_->compute(*state_, 0.0, 1.0)); + ASSERT_TRUE(state_->is_initialized()); } - - +// // Add test to use construct_liouvillian and then use compute to step using +// this liouvillian +// // z0 * z1 From c094244311aac9528b28a7193703c80968620bed Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Fri, 31 Jan 2025 08:27:16 -0800 Subject: [PATCH 34/40] Exposing handle and adding operator overloading for + and * Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_state.h | 12 ++++++++++++ runtime/cudaq/dynamics/cudm_state.cpp | 28 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/runtime/cudaq/cudm_state.h b/runtime/cudaq/cudm_state.h index 220136d1f8..0ee57cb67b 100644 --- a/runtime/cudaq/cudm_state.h +++ b/runtime/cudaq/cudm_state.h @@ -75,6 +75,18 @@ class cudm_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 Scalar multiplication operator + /// @return The new state after multiplying scalar with the current state. + cudm_state operator*(double scalar) const; + private: std::vector> rawData_; std::complex *gpuData_; diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp index 6d1174dfd1..b326f5a301 100644 --- a/runtime/cudaq/dynamics/cudm_state.cpp +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -88,6 +88,34 @@ 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."); + } + + std::vector> resultData(rawData_.size()); + for (size_t i = 0; i < rawData_.size(); i++) { + resultData[i] = rawData_[i] + other.rawData_[i]; + } + + cudm_state result(handle_, resultData); + result.init_state({static_cast(resultData.size())}); + return result; +} + +cudm_state cudm_state::operator*(double scalar) const { + std::vector> resultData(rawData_.size()); + for (size_t i = 0; i < rawData_.size(); i++) { + resultData[i] = rawData_[i] * scalar; + } + + cudm_state result(handle_, resultData); + result.init_state({static_cast(resultData.size())}); + return result; +} + std::string cudm_state::dump() const { if (!is_initialized()) { throw std::runtime_error("State is not initialized."); From ea44ea91be94c44db664cbab6142979138104ddd Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Fri, 31 Jan 2025 09:23:49 -0800 Subject: [PATCH 35/40] Removing redundant constructor and updating tests for cudm_state Signed-off-by: Sachin Pisal --- runtime/cudaq/cudm_state.h | 14 ++-- runtime/cudaq/dynamics/cudm_state.cpp | 57 ++++++++--------- unittests/dynamics/test_cudm_state.cpp | 88 +++++--------------------- 3 files changed, 47 insertions(+), 112 deletions(-) diff --git a/runtime/cudaq/cudm_state.h b/runtime/cudaq/cudm_state.h index 0ee57cb67b..3b9f6ec05d 100644 --- a/runtime/cudaq/cudm_state.h +++ b/runtime/cudaq/cudm_state.h @@ -25,7 +25,8 @@ class cudm_state { public: /// @brief To initialize state with raw data. explicit cudm_state(cudensitymatHandle_t handle, - std::vector> rawData); + const std::vector> rawData, + const std::vector &hilbertSpaceDims); /// @brief Destructor to clean up resources ~cudm_state(); @@ -39,11 +40,6 @@ class cudm_state { cudensitymatHandle_t handle, const InitialStateArgT &initialStateArg, const std::vector &hilbertSpaceDims, bool hasCollapseOps); - /// @brief Initialize the state as a density matrix or state vector based on - /// dimensions. - /// @param hilbertSpaceDims Vector representing the Hilbert Space dimensions. - void init_state(const std::vector &hilbertSpaceDims); - /// @brief Check if the state is initialized. /// @return True if the state is initialized, false otherwise. bool is_initialized() const; @@ -64,9 +60,6 @@ class cudm_state { /// @return The underlying state implementation. cudensitymatState_t get_impl() const; - /// @brief Attach raw data to the internal state representation - void attach_storage(); - /// @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; @@ -94,6 +87,9 @@ class cudm_state { cudensitymatHandle_t handle_; std::vector hilbertSpaceDims_; + /// @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. diff --git a/runtime/cudaq/dynamics/cudm_state.cpp b/runtime/cudaq/dynamics/cudm_state.cpp index b326f5a301..fc881313c2 100644 --- a/runtime/cudaq/dynamics/cudm_state.cpp +++ b/runtime/cudaq/dynamics/cudm_state.cpp @@ -16,8 +16,15 @@ namespace cudaq { cudm_state::cudm_state(cudensitymatHandle_t handle, - std::vector> rawData) - : rawData_(rawData), state_(nullptr), handle_(handle), hilbertSpaceDims_() { + const std::vector> rawData, + const std::vector &hilbertSpaceDims) + : rawData_(rawData), 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); cudaMalloc(reinterpret_cast(&gpuData_), dataSize); @@ -25,24 +32,8 @@ cudm_state::cudm_state(cudensitymatHandle_t handle, // Copy data from host to device HANDLE_CUDA_ERROR( cudaMemcpy(gpuData_, rawData_.data(), dataSize, cudaMemcpyHostToDevice)); -} - -cudm_state::~cudm_state() { - if (state_) { - cudensitymatDestroyState(state_); - } - if (gpuData_) { - cudaFree(gpuData_); - } -} - -void cudm_state::init_state(const std::vector &hilbertSpaceDims) { - if (state_) { - throw std::runtime_error("State is already initialized."); - } - - hilbertSpaceDims_ = hilbertSpaceDims; + // Determine if this is a denisty matrix or state vector size_t rawDataSize = rawData_.size(); size_t expectedDensityMatrixSize = calculate_density_matrix_size(hilbertSpaceDims); @@ -70,6 +61,15 @@ void cudm_state::init_state(const std::vector &hilbertSpaceDims) { attach_storage(); } +cudm_state::~cudm_state() { + if (state_) { + cudensitymatDestroyState(state_); + } + if (gpuData_) { + cudaFree(gpuData_); + } +} + bool cudm_state::is_initialized() const { return state_ != nullptr; } bool cudm_state::is_density_matrix() const { @@ -100,8 +100,7 @@ cudm_state cudm_state::operator+(const cudm_state &other) const { resultData[i] = rawData_[i] + other.rawData_[i]; } - cudm_state result(handle_, resultData); - result.init_state({static_cast(resultData.size())}); + cudm_state result(handle_, resultData, hilbertSpaceDims_); return result; } @@ -111,8 +110,7 @@ cudm_state cudm_state::operator*(double scalar) const { resultData[i] = rawData_[i] * scalar; } - cudm_state result(handle_, resultData); - result.init_state({static_cast(resultData.size())}); + cudm_state result(handle_, resultData, hilbertSpaceDims_); return result; } @@ -153,8 +151,7 @@ cudm_state cudm_state::to_density_matrix() const { } } - cudm_state densityMatrixState(handle_, densityMatrix); - densityMatrixState.init_state(hilbertSpaceDims_); + cudm_state densityMatrixState(handle_, densityMatrix, hilbertSpaceDims_); return densityMatrixState; } @@ -208,11 +205,8 @@ void cudm_state::attach_storage() { size_t cudm_state::calculate_state_vector_size( const std::vector &hilbertSpaceDims) const { - size_t size = 1; - for (auto dim : hilbertSpaceDims) { - size *= dim; - } - return size; + return std::accumulate(hilbertSpaceDims.begin(), hilbertSpaceDims.end(), 1, + std::multiplies<>()); } size_t cudm_state::calculate_density_matrix_size( @@ -267,8 +261,7 @@ cudm_state cudm_state::create_initial_state( throw std::invalid_argument("Unsupported InitialStateArgT type."); } - cudm_state state(handle, rawData); - state.init_state(hilbertSpaceDims); + cudm_state state(handle, rawData, hilbertSpaceDims); // Convert to a density matrix if collapse operators are present. if (hasCollapseOps && !state.is_density_matrix()) { diff --git a/unittests/dynamics/test_cudm_state.cpp b/unittests/dynamics/test_cudm_state.cpp index 163ecd3f59..8ac6bffee2 100644 --- a/unittests/dynamics/test_cudm_state.cpp +++ b/unittests/dynamics/test_cudm_state.cpp @@ -51,139 +51,85 @@ class CuDensityMatStateTest : public ::testing::Test { }; TEST_F(CuDensityMatStateTest, InitializeWithStateVector) { - cudm_state state(handle, stateVectorData); - EXPECT_FALSE(state.is_initialized()); + cudm_state state(handle, stateVectorData, hilbertSpaceDims); - EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); EXPECT_TRUE(state.is_initialized()); EXPECT_FALSE(state.is_density_matrix()); - EXPECT_NO_THROW(state.dump()); } TEST_F(CuDensityMatStateTest, InitializeWithDensityMatrix) { - cudm_state state(handle, densityMatrixData); - EXPECT_FALSE(state.is_initialized()); + cudm_state state(handle, densityMatrixData, hilbertSpaceDims); - EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); EXPECT_TRUE(state.is_initialized()); EXPECT_TRUE(state.is_density_matrix()); - EXPECT_NO_THROW(state.dump()); } TEST_F(CuDensityMatStateTest, InvalidInitialization) { // Data size mismatch for hilbertSpaceDims (2x2 system expects size 4 or 16) - std::vector> invalidData = { - std::complex(1.0, 0.0), std::complex(0.0, 0.0)}; + std::vector> invalidData = {{1.0, 0.0}, {0.0, 0.0}}; - cudm_state state(handle, invalidData); - EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); + EXPECT_THROW(cudm_state state(handle, invalidData, hilbertSpaceDims), + std::invalid_argument); } TEST_F(CuDensityMatStateTest, ToDensityMatrixConversion) { - cudm_state state(handle, stateVectorData); - state.init_state(hilbertSpaceDims); - + 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); - state.init_state(hilbertSpaceDims); + cudm_state state(handle, densityMatrixData, hilbertSpaceDims); EXPECT_TRUE(state.is_density_matrix()); EXPECT_THROW(state.to_density_matrix(), std::runtime_error); } -TEST_F(CuDensityMatStateTest, DumpUninitializedState) { - cudm_state state(handle, stateVectorData); - EXPECT_THROW(state.dump(), std::runtime_error); -} - -TEST_F(CuDensityMatStateTest, AttachStorageErrorHandling) { - cudm_state state(handle, stateVectorData); - - EXPECT_THROW(state.attach_storage(), std::runtime_error); -} - TEST_F(CuDensityMatStateTest, DestructorCleansUp) { - cudm_state state(handle, stateVectorData); - - EXPECT_NO_THROW(state.init_state(hilbertSpaceDims)); + EXPECT_NO_THROW( + { cudm_state state(handle, stateVectorData, hilbertSpaceDims); }); } TEST_F(CuDensityMatStateTest, InitializeWithEmptyRawData) { std::vector> emptyData; - cudm_state state(handle, emptyData); - EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); + EXPECT_THROW(cudm_state state(handle, emptyData, hilbertSpaceDims), + std::invalid_argument); } TEST_F(CuDensityMatStateTest, ConversionForSingleQubitSystem) { hilbertSpaceDims = {2}; - stateVectorData = {std::complex(1.0, 0.0), - std::complex(0.0, 0.0)}; - cudm_state state(handle, stateVectorData); - - state.init_state(hilbertSpaceDims); + 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}; - cudm_state state(handle, stateVectorData); - - EXPECT_THROW(state.init_state(hilbertSpaceDims), std::invalid_argument); -} - -TEST_F(CuDensityMatStateTest, ToDensityMatrixFromUninitializedState) { - cudm_state state(handle, stateVectorData); - - EXPECT_THROW(state.to_density_matrix(), std::runtime_error); -} - -TEST_F(CuDensityMatStateTest, MultipleInitialization) { - cudm_state state(handle, stateVectorData); - state.init_state(hilbertSpaceDims); - - EXPECT_TRUE(state.is_initialized()); - - EXPECT_THROW(state.init_state(hilbertSpaceDims), std::runtime_error); + EXPECT_THROW(cudm_state state(handle, stateVectorData, hilbertSpaceDims), + std::invalid_argument); } TEST_F(CuDensityMatStateTest, ValidDensityMatrixState) { - cudm_state state(handle, densityMatrixData); - state.init_state(hilbertSpaceDims); - + 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); - state.init_state(hilbertSpaceDims); - + cudm_state state(handle, stateVectorData, hilbertSpaceDims); EXPECT_NO_THROW(state.dump()); } - -TEST_F(CuDensityMatStateTest, DumpFailsForUninitializedState) { - cudm_state state(handle, stateVectorData); - - EXPECT_THROW(state.dump(), std::runtime_error); -} From ac98d3d697ab101fc26bd1c2cc8a60943d1f3d58 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Fri, 31 Jan 2025 09:53:08 -0800 Subject: [PATCH 36/40] Implementing compute function in cudm_time_stepper Signed-off-by: Sachin Pisal --- runtime/cudaq/base_time_stepper.h | 7 ++- runtime/cudaq/cudm_time_stepper.h | 2 +- runtime/cudaq/dynamics/cudm_time_stepper.cpp | 57 ++++++++------------ 3 files changed, 29 insertions(+), 37 deletions(-) diff --git a/runtime/cudaq/base_time_stepper.h b/runtime/cudaq/base_time_stepper.h index 4488de8b44..2c8150de0f 100644 --- a/runtime/cudaq/base_time_stepper.h +++ b/runtime/cudaq/base_time_stepper.h @@ -14,6 +14,11 @@ class BaseTimeStepper { public: virtual ~BaseTimeStepper() = default; - virtual void compute(TState &state, double t, double step_size) = 0; + /// @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_time_stepper.h b/runtime/cudaq/cudm_time_stepper.h index a245b42c80..6037ee00f1 100644 --- a/runtime/cudaq/cudm_time_stepper.h +++ b/runtime/cudaq/cudm_time_stepper.h @@ -18,7 +18,7 @@ class cudm_time_stepper : public BaseTimeStepper { explicit cudm_time_stepper(cudensitymatHandle_t handle, cudensitymatOperator_t liouvillian); - void compute(cudm_state &state, double t, double step_size) override; + cudm_state compute(cudm_state &state, double t, double step_size); private: cudensitymatHandle_t handle_; diff --git a/runtime/cudaq/dynamics/cudm_time_stepper.cpp b/runtime/cudaq/dynamics/cudm_time_stepper.cpp index 8a1d01fed4..90f1277f12 100644 --- a/runtime/cudaq/dynamics/cudm_time_stepper.cpp +++ b/runtime/cudaq/dynamics/cudm_time_stepper.cpp @@ -16,7 +16,8 @@ cudm_time_stepper::cudm_time_stepper(cudensitymatHandle_t handle, cudensitymatOperator_t liouvillian) : handle_(handle), liouvillian_(liouvillian) {} -void cudm_time_stepper::compute(cudm_state &state, double t, double step_size) { +cudm_state cudm_time_stepper::compute(cudm_state &state, double t, + double step_size) { if (!state.is_initialized()) { throw std::runtime_error("State is not initialized."); } @@ -29,7 +30,6 @@ void cudm_time_stepper::compute(cudm_state &state, double t, double step_size) { throw std::runtime_error("Liouvillian is not initialized."); } - std::cout << "Preparing workspace ..." << std::endl; // Prepare workspace cudensitymatWorkspaceDescriptor_t workspace; HANDLE_CUDM_ERROR(cudensitymatCreateWorkspace(handle_, &workspace)); @@ -40,13 +40,12 @@ void cudm_time_stepper::compute(cudm_state &state, double t, double step_size) { // Take 80% of free memory freeMem = static_cast(static_cast(freeMem) * 0.80); - std::cout << "Max workspace buffer size (bytes): " << freeMem << std::endl; - - std::cout << "Create a new state for the next step ..." << std::endl; // Create a new state for the next step - cudm_state next_state(handle_, state.get_raw_data()); - next_state.init_state(state.get_hilbert_space_dims()); - + std::vector> zero_initiailized_data( + state.get_raw_data().size(), {0.0, 0.0}); + cudm_state next_state(handle_, zero_initiailized_data, + state.get_hilbert_space_dims()); + if (!next_state.is_initialized()) { throw std::runtime_error("Next state failed to initialize."); } @@ -58,36 +57,29 @@ void cudm_time_stepper::compute(cudm_state &state, double t, double step_size) { } // Prepare the operator for action - std::cout << "Preparing the operator for action ..." << std::endl; HANDLE_CUDM_ERROR(cudensitymatOperatorPrepareAction( handle_, liouvillian_, state.get_impl(), next_state.get_impl(), CUDENSITYMAT_COMPUTE_64F, freeMem, workspace, 0x0)); - std::cout << "Querying required workspace buffer size ..." << std::endl; // Query required workspace buffer size std::size_t requiredBufferSize = 0; HANDLE_CUDM_ERROR(cudensitymatWorkspaceGetMemorySize( handle_, workspace, CUDENSITYMAT_MEMSPACE_DEVICE, CUDENSITYMAT_WORKSPACE_SCRATCH, &requiredBufferSize)); - std::cout << "Required workspace buffer size (bytes): " << requiredBufferSize - << std::endl; - - // Allocate GPU storage for workspace buffer - const std::size_t bufferVolume = - requiredBufferSize / sizeof(std::complex); - auto *workspaceBuffer = create_array_gpu( - std::vector>(bufferVolume, {0.0, 0.0})); - - std::cout << "Allocated workspace buffer of size (bytes): " - << requiredBufferSize << std::endl; - - // Attach workspace buffer - HANDLE_CUDM_ERROR(cudensitymatWorkspaceSetMemory( - handle_, workspace, CUDENSITYMAT_MEMSPACE_DEVICE, - CUDENSITYMAT_WORKSPACE_SCRATCH, workspaceBuffer, requiredBufferSize)); - - std::cout << "Attached workspace buffer" << std::endl; + void *workspaceBuffer = nullptr; + if (requiredBufferSize > 0) { + // Allocate GPU storage for workspace buffer + const std::size_t bufferVolume = + requiredBufferSize / sizeof(std::complex); + workspaceBuffer = 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()); @@ -96,15 +88,10 @@ void cudm_time_stepper::compute(cudm_state &state, double t, double step_size) { state.get_impl(), next_state.get_impl(), workspace, 0x0)); HANDLE_CUDA_ERROR(cudaDeviceSynchronize()); - std::cout << "Updated quantum state" << std::endl; - - // Swap states: Move next_state into state - state = std::move(next_state); - // Cleanup - HANDLE_CUDM_ERROR(cudensitymatDestroyWorkspace(workspace)); destroy_array_gpu(workspaceBuffer); + HANDLE_CUDM_ERROR(cudensitymatDestroyWorkspace(workspace)); - std::cout << "Cleaned up workspace" << std::endl; + return next_state; } } // namespace cudaq \ No newline at end of file From 3ccc0d14a83181dca8cd7a647cec0e78ac6636b8 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Fri, 31 Jan 2025 09:58:26 -0800 Subject: [PATCH 37/40] Adding test_mocks for unittests Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/cudm_time_stepper.cpp | 2 +- unittests/dynamics/test_cudm_time_stepper.cpp | 51 +++++-------------- unittests/dynamics/test_mocks.h | 33 ++++++++++++ 3 files changed, 47 insertions(+), 39 deletions(-) create mode 100644 unittests/dynamics/test_mocks.h diff --git a/runtime/cudaq/dynamics/cudm_time_stepper.cpp b/runtime/cudaq/dynamics/cudm_time_stepper.cpp index 90f1277f12..c803b9488b 100644 --- a/runtime/cudaq/dynamics/cudm_time_stepper.cpp +++ b/runtime/cudaq/dynamics/cudm_time_stepper.cpp @@ -45,7 +45,7 @@ cudm_state cudm_time_stepper::compute(cudm_state &state, double t, state.get_raw_data().size(), {0.0, 0.0}); cudm_state next_state(handle_, zero_initiailized_data, state.get_hilbert_space_dims()); - + if (!next_state.is_initialized()) { throw std::runtime_error("Next state failed to initialize."); } diff --git a/unittests/dynamics/test_cudm_time_stepper.cpp b/unittests/dynamics/test_cudm_time_stepper.cpp index 03807d99af..fbde6b089c 100644 --- a/unittests/dynamics/test_cudm_time_stepper.cpp +++ b/unittests/dynamics/test_cudm_time_stepper.cpp @@ -6,40 +6,23 @@ * 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; -// Mock Liouvillian operator creation -cudensitymatOperator_t mock_liouvillian(cudensitymatHandle_t handle) { - cudensitymatOperator_t liouvillian; - std::vector dimensions = {2, 2}; - HANDLE_CUDM_ERROR(cudensitymatCreateOperator( - handle, static_cast(dimensions.size()), dimensions.data(), - &liouvillian)); - return liouvillian; -} - -// Mock Hilbert space dimensions -std::vector> mock_initial_state_data() { - return {{1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}; -} - -// Mock initial raw state data -std::vector mock_hilbert_space_dims() { return {2, 2}; } - class CuDensityMatTimeStepperTest : public ::testing::Test { protected: cudensitymatHandle_t handle_; cudensitymatOperator_t liouvillian_; - cudm_time_stepper *time_stepper_; - cudm_state *state_; - - // CuDensityMatTimeStepperTest() : state_(mock_initial_state_data()) {}; + std::unique_ptr time_stepper_; + std::unique_ptr state_; void SetUp() override { // Create library handle @@ -49,12 +32,10 @@ class CuDensityMatTimeStepperTest : public ::testing::Test { liouvillian_ = mock_liouvillian(handle_); // Initialize the time stepper - time_stepper_ = new cudm_time_stepper(handle_, liouvillian_); - - state_ = new cudm_state(handle_, mock_initial_state_data()); + time_stepper_ = std::make_unique(handle_, liouvillian_); - // Initialize the state - state_->init_state(mock_hilbert_space_dims()); + state_ = std::make_unique(handle_, mock_initial_state_data(), + mock_hilbert_space_dims()); ASSERT_TRUE(state_->is_initialized()); } @@ -63,17 +44,15 @@ class CuDensityMatTimeStepperTest : public ::testing::Test { // Clean up HANDLE_CUDM_ERROR(cudensitymatDestroyOperator(liouvillian_)); HANDLE_CUDM_ERROR(cudensitymatDestroy(handle_)); - delete time_stepper_; - delete state_; } }; // 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_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) { @@ -81,7 +60,3 @@ TEST_F(CuDensityMatTimeStepperTest, ComputeStep) { EXPECT_NO_THROW(time_stepper_->compute(*state_, 0.0, 1.0)); ASSERT_TRUE(state_->is_initialized()); } - -// // Add test to use construct_liouvillian and then use compute to step using -// this liouvillian -// // z0 * z1 diff --git a/unittests/dynamics/test_mocks.h b/unittests/dynamics/test_mocks.h new file mode 100644 index 0000000000..6b81e26d91 --- /dev/null +++ b/unittests/dynamics/test_mocks.h @@ -0,0 +1,33 @@ +/****************************************************************-*- 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 + +// Mock Liouvillian operator creation +inline cudensitymatOperator_t mock_liouvillian(cudensitymatHandle_t handle) { + cudensitymatOperator_t liouvillian; + std::vector dimensions = {2, 2}; + HANDLE_CUDM_ERROR(cudensitymatCreateOperator( + handle, static_cast(dimensions.size()), dimensions.data(), + &liouvillian)); + return liouvillian; +} + +// Mock Hilbert space dimensions +inline std::vector> mock_initial_state_data() { + return {{1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}; +} + +// Mock initial raw state data +inline std::vector mock_hilbert_space_dims() { return {2, 2}; } From 5ba80417a65748f00a2e854e30491155acff5112 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Fri, 31 Jan 2025 13:36:55 -0800 Subject: [PATCH 38/40] Adding check for step_size=0 condition and few more unittests Signed-off-by: Sachin Pisal --- runtime/cudaq/dynamics/cudm_time_stepper.cpp | 6 +++- unittests/dynamics/test_cudm_time_stepper.cpp | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/runtime/cudaq/dynamics/cudm_time_stepper.cpp b/runtime/cudaq/dynamics/cudm_time_stepper.cpp index c803b9488b..271689d639 100644 --- a/runtime/cudaq/dynamics/cudm_time_stepper.cpp +++ b/runtime/cudaq/dynamics/cudm_time_stepper.cpp @@ -18,12 +18,16 @@ cudm_time_stepper::cudm_time_stepper(cudensitymatHandle_t handle, 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 initializes."); + throw std::runtime_error("cudm_time_stepper handle is not initialized."); } if (!liouvillian_) { diff --git a/unittests/dynamics/test_cudm_time_stepper.cpp b/unittests/dynamics/test_cudm_time_stepper.cpp index fbde6b089c..1bfe69c79d 100644 --- a/unittests/dynamics/test_cudm_time_stepper.cpp +++ b/unittests/dynamics/test_cudm_time_stepper.cpp @@ -60,3 +60,34 @@ TEST_F(CuDensityMatTimeStepperTest, ComputeStep) { 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)); +} From b74e2bfafa9d85c1833281c807834c282f4f3682 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Fri, 31 Jan 2025 13:39:04 -0800 Subject: [PATCH 39/40] Adding #pragma once so that header files are included only once during compilation Signed-off-by: Sachin Pisal --- runtime/cudaq/definition.h | 4 +++- runtime/cudaq/operators.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/runtime/cudaq/definition.h b/runtime/cudaq/definition.h index bdf5af8ab5..d5013ffc9c 100644 --- a/runtime/cudaq/definition.h +++ b/runtime/cudaq/definition.h @@ -1,11 +1,13 @@ /****************************************************************-*- C++ -*-**** - * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * 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" diff --git a/runtime/cudaq/operators.h b/runtime/cudaq/operators.h index 5f07f5dd49..6543c5fb72 100644 --- a/runtime/cudaq/operators.h +++ b/runtime/cudaq/operators.h @@ -6,6 +6,8 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ +#pragma once + #include "definition.h" #include "utils/tensor.h" From 97486c16d511f838863d4efb93b25a3ab2136788 Mon Sep 17 00:00:00 2001 From: Sachin Pisal Date: Fri, 31 Jan 2025 14:19:27 -0800 Subject: [PATCH 40/40] * Adding partial implementation of runge-kutta integrator * Removing time_stepper implemented specifically only for runge-kutta integrator as we now have a general cudm_time_stepper * Updating CMakelists.txt accordingly * Removing runge_kutta_test_helpers as we will be using test_mocks instead Signed-off-by: Sachin Pisal --- runtime/cudaq/base_integrator.h | 18 ++- runtime/cudaq/dynamics/CMakeLists.txt | 13 +- .../cudaq/dynamics/runge_kutta_integrator.cpp | 65 ++++++++ runtime/cudaq/runge_kutta_integrator.h | 49 +++--- runtime/cudaq/runge_kutta_time_stepper.h | 33 ---- unittests/CMakeLists.txt | 1 - unittests/dynamics/runge_kutta_test_helpers.h | 24 --- .../dynamics/test_runge_kutta_integrator.cpp | 131 ++++----------- .../test_runge_kutta_time_stepper.cpp | 152 ------------------ 9 files changed, 147 insertions(+), 339 deletions(-) create mode 100644 runtime/cudaq/dynamics/runge_kutta_integrator.cpp delete mode 100644 runtime/cudaq/runge_kutta_time_stepper.h delete mode 100644 unittests/dynamics/runge_kutta_test_helpers.h delete mode 100644 unittests/dynamics/test_runge_kutta_time_stepper.cpp diff --git a/runtime/cudaq/base_integrator.h b/runtime/cudaq/base_integrator.h index e3196bd52d..0d63acf4d5 100644 --- a/runtime/cudaq/base_integrator.h +++ b/runtime/cudaq/base_integrator.h @@ -31,13 +31,27 @@ class BaseIntegrator { 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(const TState &initial_state, double t0, + std::shared_ptr> stepper) + : state(initial_state), t(t0), stepper(std::move(stepper)) {} + 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 the system parameters (dimensions, schedule, and operators) void set_system( const std::map &dimensions, std::shared_ptr schedule, std::shared_ptr hamiltonian, @@ -48,8 +62,10 @@ class BaseIntegrator { this->collapse_operators = collapse_operators; } - virtual void integrate(double t) = 0; + /// @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/dynamics/CMakeLists.txt b/runtime/cudaq/dynamics/CMakeLists.txt index 636d8b4096..63129f246f 100644 --- a/runtime/cudaq/dynamics/CMakeLists.txt +++ b/runtime/cudaq/dynamics/CMakeLists.txt @@ -11,7 +11,18 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") set(INTERFACE_POSITION_INDEPENDENT_CODE ON) set(CUDAQ_OPS_SRC - scalar_operators.cpp elementary_operators.cpp product_operators.cpp operator_sum.cpp schedule.cpp definition.cpp helpers.cpp rydberg_hamiltonian.cpp cudm_helpers.cpp cudm_state.cpp cudm_time_stepper.cpp + scalar_operators.cpp + elementary_operators.cpp + product_operators.cpp + operator_sum.cpp + schedule.cpp + definition.cpp + helpers.cpp + rydberg_hamiltonian.cpp + cudm_helpers.cpp + cudm_state.cpp + cudm_time_stepper.cpp + runge_kutta_integrator.cpp ) set(CUQUANTUM_INSTALL_PREFIX "/usr/local/lib/python3.10/dist-packages/cuquantum") diff --git a/runtime/cudaq/dynamics/runge_kutta_integrator.cpp b/runtime/cudaq/dynamics/runge_kutta_integrator.cpp new file mode 100644 index 0000000000..62633bc172 --- /dev/null +++ b/runtime/cudaq/dynamics/runge_kutta_integrator.cpp @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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 (!stepper) { + throw std::runtime_error("Time stepper is not initialized."); + } + + double dt = integrator_options["dt"]; + if (dt <= 0) { + throw std::invalid_argument("Invalid time step size for integration."); + } + + auto handle = state.get_handle(); + auto hilbertSpaceDims = state.get_hilbert_space_dims(); + + while (t < target_time) { + double step_size = std::min(dt, target_time - 1); + + std::cout << "Runge-Kutta step at time " << t << " with step size: " << step_size << std::endl; + + // Empty vectors of same size as state.get_raw_data() + std::vector> zero_state(state.get_raw_data().size(), {0.0, 0.0}); + + cudm_state k1(handle, zero_state, hilbertSpaceDims); + cudm_state k2(handle, zero_state, hilbertSpaceDims); + cudm_state k3(handle, zero_state, hilbertSpaceDims); + cudm_state k4(handle, zero_state, hilbertSpaceDims); + + if (substeps_ == 1) { + // Euler method (1st order) + k1 = stepper->compute(state, t, step_size); + state = k1; + } else if (substeps_ == 2) { + // Midpoint method (2nd order) + k1 = stepper->compute(state, t, step_size / 2.0); + k2 = stepper->compute(k1, t + step_size / 2.0, step_size); + state = (k1 + k2) * 0.5; + } else if (substeps_ == 4) { + // Runge-Kutta method (4th order) + k1 = stepper->compute(state, t, step_size / 2.0); + k2 = stepper->compute(k1, t + step_size / 2.0, step_size / 2.0); + k3 = stepper->compute(k2, t + step_size / 2.0, step_size); + k4 = stepper->compute(k3, t + step_size, step_size); + state = (k1 + k2 * 2.0 + k3 * 2.0 + k4) * (1.0 / 6.0); + } + + // Update time + t += step_size; + } + + std::cout << "Integration complete. Final time: " << t << std::endl; +} +} diff --git a/runtime/cudaq/runge_kutta_integrator.h b/runtime/cudaq/runge_kutta_integrator.h index 9914258386..0b98bb4d86 100644 --- a/runtime/cudaq/runge_kutta_integrator.h +++ b/runtime/cudaq/runge_kutta_integrator.h @@ -9,39 +9,38 @@ #pragma once #include "base_integrator.h" -#include "runge_kutta_time_stepper.h" +#include "cudaq/cudm_state.h" +#include "cudaq/cudm_time_stepper.h" #include namespace cudaq { -template -class RungeKuttaIntegrator : public BaseIntegrator { +class runge_kutta_integrator : public BaseIntegrator { public: - using DerivativeFunction = std::function; - - explicit RungeKuttaIntegrator(DerivativeFunction f) - : stepper(std::make_shared>(f)) {} - - // Initializes the integrator - void post_init() override { - if (!this->stepper) { - throw std::runtime_error("Time stepper is not set"); + /// @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(const cudm_state &initial_state, double t0, + std::shared_ptr stepper, + int substeps = 4) + : BaseIntegrator(initial_state, t0, stepper), substeps_(substeps) { + if (substeps_ != 1 && substeps_ != 2 && substeps_ != 4) { + throw std::invalid_argument("Runge-Kutta substeps must be 1, 2, or 4."); } + post_init(); } - // Advances the system's state from current time to `t` - void integrate(double target_t) override { - if (!this->schedule || !this->hamiltonian) { - throw std::runtime_error("System is not properly set!"); - } + /// @brief Perform Runge-Kutta integration until the target time. + /// @param target_time The final time to integrate to. + void integrate(double t) override; - while (this->t < target_t) { - stepper->compute(this->state, this->t); - // Time step size - this->t += 0.01; - } - } +protected: + /// @brief Any post-initialization setup + void post_init() override {} private: - std::shared_ptr> stepper; + // Number of substeps in RK integration (1, 2, or 4) + int substeps_; }; -} // namespace cudaq \ No newline at end of file +} // namespace cudaq diff --git a/runtime/cudaq/runge_kutta_time_stepper.h b/runtime/cudaq/runge_kutta_time_stepper.h deleted file mode 100644 index 1dcd1f69cc..0000000000 --- a/runtime/cudaq/runge_kutta_time_stepper.h +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -#include "base_time_stepper.h" -#include - -namespace cudaq { -template -class RungeKuttaTimeStepper : public BaseTimeStepper { -public: - using DerivativeFunction = std::function; - - RungeKuttaTimeStepper(DerivativeFunction f) : derivativeFunc(f) {} - - void compute(TState &state, double t, double dt = 0.01) override { - // 4th order Runge-Kutta method - TState k1 = derivativeFunc(state, t); - TState k2 = derivativeFunc(state + (dt / 2.0) * k1, t + dt / 2.0); - TState k3 = derivativeFunc(state + (dt / 2.0) * k2, t + dt / 2.0); - TState k4 = derivativeFunc(state + dt * k3, t + dt); - - state = state + (dt / 6.0) * (k1 + 2 * k2 + 2 * k3 + k4); - } - -private: - DerivativeFunction derivativeFunc; -}; -} // namespace cudaq \ No newline at end of file diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 0d99fd5bb2..c422dfa8ee 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -49,7 +49,6 @@ set(CUDAQ_RUNTIME_TEST_SOURCES dynamics/elementary_ops_simple.cpp dynamics/elementary_ops_arithmetic.cpp dynamics/product_operators_arithmetic.cpp - dynamics/test_runge_kutta_time_stepper.cpp dynamics/test_runge_kutta_integrator.cpp dynamics/test_helpers.cpp dynamics/rydberg_hamiltonian.cpp diff --git a/unittests/dynamics/runge_kutta_test_helpers.h b/unittests/dynamics/runge_kutta_test_helpers.h deleted file mode 100644 index 4f93ffa242..0000000000 --- a/unittests/dynamics/runge_kutta_test_helpers.h +++ /dev/null @@ -1,24 +0,0 @@ -/****************************************************************-*- C++ -*-**** - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -#pragma once - -#include - -// A simple state type -using TestState = double; - -// Simple derivative function: dx/dt = -x (exponential decay) -inline TestState simple_derivative(const TestState &state, double t) { - return -state; -} - -// A complex function: dx/dt = sin(t) -inline TestState sine_derivative(const TestState &state, double t) { - return std::sin(t); -} diff --git a/unittests/dynamics/test_runge_kutta_integrator.cpp b/unittests/dynamics/test_runge_kutta_integrator.cpp index c75a7c8d6d..407c211210 100644 --- a/unittests/dynamics/test_runge_kutta_integrator.cpp +++ b/unittests/dynamics/test_runge_kutta_integrator.cpp @@ -7,7 +7,7 @@ // ******************************************************************************/ #include "cudaq/runge_kutta_integrator.h" -#include "runge_kutta_test_helpers.h" +#include "test_mocks.h" #include #include #include @@ -17,116 +17,43 @@ using namespace cudaq; // Test fixture class class RungeKuttaIntegratorTest : public ::testing::Test { protected: - RungeKuttaIntegrator *integrator; - std::shared_ptr schedule; - std::shared_ptr hamiltonian; + cudensitymatHandle_t handle_; + cudensitymatOperator_t liouvillian_; + std::shared_ptr time_stepper_; + std::unique_ptr integrator_; + std::unique_ptr state_; void SetUp() override { - integrator = new RungeKuttaIntegrator(simple_derivative); - // Initial state and time - integrator->set_state(1.0, 0.0); + // Create library handle + HANDLE_CUDM_ERROR(cudensitymatCreate(&handle_)); - // A simple step sequence for the schedule - std::vector> steps = {0.1, 0.2, 0.3, 0.4, 0.5}; + // Create a mock Liouvillian + liouvillian_ = mock_liouvillian(handle_); - // Dummy parameters - std::vector parameters = {"param1"}; + // Initialize the time stepper + time_stepper_ = std::make_shared(handle_, liouvillian_); - // A simple parameter function - auto value_function = [](const std::string ¶m, - const std::complex &step) { return step; }; + // Create initial state + state_ = std::make_unique(handle_, mock_initial_state_data(), + mock_hilbert_space_dims()); - // A valid schedule instance - schedule = std::make_shared(steps, parameters, value_function); + double t0 = 0.0; + // Initialize the integrator (using substeps = 2, for mid-point rule) + integrator_ = + std::make_unique(*state_, t0, time_stepper_, 2); - // A simple hamiltonian as an operator_sum - hamiltonian = std::make_shared(); - *hamiltonian += 0.5 * elementary_operator::identity(0); - *hamiltonian += 0.5 * elementary_operator::number(0); - - // System with valid components - integrator->set_system({{0, 2}}, schedule, hamiltonian); - } - - void TearDown() override { delete integrator; } -}; - -// Basic integration -TEST_F(RungeKuttaIntegratorTest, BasicIntegration) { - integrator->integrate(1.0); - - // Expected result: x(t) = e^(-t) - double expected = std::exp(-1.0); - - EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) - << "Basic Runge-Kutta integration failed!"; -} - -// Time evolution -TEST_F(RungeKuttaIntegratorTest, TimeEvolution) { - integrator->integrate(2.0); - - double expected = 2.0; - - EXPECT_NEAR(integrator->get_state().first, expected, 1e-5) - << "Integrator did not correctly update time!"; -} - -// Large step size -TEST_F(RungeKuttaIntegratorTest, LargeStepSize) { - integrator->integrate(5.0); - - double expected = std::exp(-5.0); - - EXPECT_NEAR(integrator->get_state().second, expected, 1e-1) - << "Runge-Kutta integration failed for large step size!!"; -} - -// // Integrating Sine function -// TEST_F(RungeKuttaIntegratorTest, SineFunction) { -// integrator = new RungeKuttaIntegrator(sine_derivative); -// integrator->set_state(1.0, 0.0); -// integrator->set_system({{0, 2}}, schedule, hamiltonian); - -// integrator->integrate(M_PI / 2); - -// double expected = std::cos(M_PI / 2); - -// EXPECT_NEAR(integrator->get_state().second, expected, 1e-3) << -// "Runge-Kutta integration for sine function failed!"; -// } - -// Small step size -TEST_F(RungeKuttaIntegratorTest, SmallStepIntegration) { - integrator->set_state(1.0, 0.0); - integrator->set_system({{0, 2}}, schedule, hamiltonian); - - double step_size = 0.001; - while (integrator->get_state().first < 1.0) { - integrator->integrate(integrator->get_state().first + step_size); + ASSERT_TRUE(state_->is_initialized()); } - double expected = std::exp(-1.0); - - EXPECT_NEAR(integrator->get_state().second, expected, 5e-4) - << "Runge-Kutta integration for small step size failed!"; -} - -// Large step size -TEST_F(RungeKuttaIntegratorTest, LargeStepIntegration) { - integrator->set_state(1.0, 0.0); - integrator->set_system({{0, 2}}, schedule, hamiltonian); - - double step_size = 0.5; - double t = 0.0; - double target_t = 1.0; - while (t < target_t) { - integrator->integrate(std::min(t + step_size, target_t)); - t += step_size; + void TearDown() override { + // Clean up resources + HANDLE_CUDM_ERROR(cudensitymatDestroyOperator(liouvillian_)); + HANDLE_CUDM_ERROR(cudensitymatDestroy(handle_)); } +}; - double expected = std::exp(-1.0); - - EXPECT_NEAR(integrator->get_state().second, expected, 1e-2) - << "Runge-Kutta integration for large step size failed!"; +// Test Initialization +TEST_F(RungeKuttaIntegratorTest, Initialization) { + ASSERT_NE(integrator_, nullptr); + // ASSERT_TRUE(state_->is_initialized()); } diff --git a/unittests/dynamics/test_runge_kutta_time_stepper.cpp b/unittests/dynamics/test_runge_kutta_time_stepper.cpp deleted file mode 100644 index 4c4c7b7588..0000000000 --- a/unittests/dynamics/test_runge_kutta_time_stepper.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -#include "cudaq/runge_kutta_time_stepper.h" -#include "runge_kutta_test_helpers.h" -#include -#include -#include - -// Test fixture class -class RungeKuttaTimeStepperTest : public ::testing::Test { -protected: - std::shared_ptr> stepper; - - void SetUp() override { - stepper = std::make_shared>( - simple_derivative); - } -}; - -// Single step integration -TEST_F(RungeKuttaTimeStepperTest, SingleStep) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 0.1; - - stepper->compute(state, t, dt); - - // Expected result using analytical solution: x(t) = e^(-t) - double expected = std::exp(-dt); - - EXPECT_NEAR(state, expected, 1e-3) - << "Single step Runge-Kutta integration failed!"; -} - -// Multiple step integration -TEST_F(RungeKuttaTimeStepperTest, MultipleSteps) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 0.1; - int steps = 10; - - for (int i = 0; i < steps; i++) { - stepper->compute(state, t, dt); - } - - // Expected result: x(t) = e^(-t) - double expected = std::exp(-1.0); - - EXPECT_NEAR(state, expected, 1e-2) - << "Multiple step Runge-Kutta integration failed!"; -} - -// Convergence to Analytical Solution -TEST_F(RungeKuttaTimeStepperTest, Convergence) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 0.01; - int steps = 100; - - for (int i = 0; i < steps; i++) { - stepper->compute(state, t, dt); - } - - double expected = std::exp(-1.0); - - EXPECT_NEAR(state, expected, 1e-3) - << "Runge-Kutta integration does not converge!"; -} - -// // Integrating Sine function -// TEST_F(RungeKuttaTimeStepperTest, SineFunction) { -// auto sine_stepper = -// std::make_shared>(sine_derivative); - -// // Initial values -// double state = 0.0; -// double t = 0.0; -// double dt = 0.1; -// int steps = 10; - -// for (int i = 0; i < steps; i++) { -// sine_stepper->compute(state, t, dt); -// } - -// // Expected integral of sin(t) over [0, 1] is 1 - cos(1) -// double expected = 1 - std::cos(1); - -// EXPECT_NEAR(state, expected, 1e-2) << "Runge-Kutta integration for sine -// function failed!"; -// } - -// Handling small steps sizes -TEST_F(RungeKuttaTimeStepperTest, SmallStepSize) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 1e-5; - int steps = 100000; - - for (int i = 0; i < steps; i++) { - stepper->compute(state, t, dt); - } - - double expected = std::exp(-1.0); - - EXPECT_NEAR(state, expected, 1e-3) - << "Runge-Kutta fails with small step sizes!"; -} - -// Handling large steps sizes -TEST_F(RungeKuttaTimeStepperTest, LargeStepSize) { - // Initial values - double state = 1.0; - double t = 0.0; - double dt = 1.0; - - stepper->compute(state, t, dt); - - double expected = std::exp(-1.0); - - EXPECT_NEAR(state, expected, 1e-1) - << "Runge-Kutta is unstable with large step sizes!"; -} - -// Constant derivative (dx/dt = 0) -TEST_F(RungeKuttaTimeStepperTest, ConstantFunction) { - auto constant_stepper = - std::make_shared>( - [](const TestState &state, double t) { return 0.0; }); - - // Initial values - double state = 5.0; - double t = 0.0; - double dt = 0.1; - int steps = 10; - - for (int i = 0; i < steps; i++) { - constant_stepper->compute(state, t, dt); - } - - EXPECT_NEAR(state, 5.0, 1e-6) - << "Runge-Kutta should not change a constant function!"; -}