diff --git a/.github/workflows/config/gitlab_commits.txt b/.github/workflows/config/gitlab_commits.txt index 9176f70126f..cd53ef6df24 100644 --- a/.github/workflows/config/gitlab_commits.txt +++ b/.github/workflows/config/gitlab_commits.txt @@ -1,2 +1,2 @@ -nvidia-mgpu-repo: cuda-quantum/cuquantum-mgpu.git -nvidia-mgpu-commit: ddfaacf2ffd7dc1a9a4333e06474b213887d437c +nvidia-mgpu-repo: cuda-quantum/cuquantum-mgpu.git +nvidia-mgpu-commit: 966ff7b2f775128fa3339e19672892d8ff1a4e74 diff --git a/cmake/Modules/CMakeLists.txt b/cmake/Modules/CMakeLists.txt index 8c26addf34a..d19411197fa 100644 --- a/cmake/Modules/CMakeLists.txt +++ b/cmake/Modules/CMakeLists.txt @@ -10,7 +10,6 @@ set(CONFIG_FILES CUDAQCommonConfig.cmake CUDAQEmDefaultConfig.cmake CUDAQNloptConfig.cmake - CUDAQSpinConfig.cmake CUDAQOperatorConfig.cmake CUDAQConfig.cmake CUDAQEnsmallenConfig.cmake diff --git a/cmake/Modules/CUDAQConfig.cmake b/cmake/Modules/CUDAQConfig.cmake index 041c607344d..2e9fcb03a7f 100644 --- a/cmake/Modules/CUDAQConfig.cmake +++ b/cmake/Modules/CUDAQConfig.cmake @@ -11,9 +11,6 @@ get_filename_component(CUDAQ_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) include(CMakeFindDependencyMacro) list(APPEND CMAKE_MODULE_PATH "${CUDAQ_CMAKE_DIR}") -set (CUDAQSpin_DIR "${CUDAQ_CMAKE_DIR}") -find_dependency(CUDAQSpin REQUIRED) - set (CUDAQOperator_DIR "${CUDAQ_CMAKE_DIR}") find_dependency(CUDAQOperator REQUIRED) diff --git a/cmake/Modules/CUDAQEmDefaultConfig.cmake b/cmake/Modules/CUDAQEmDefaultConfig.cmake index a591cd002d0..ebd79e83934 100644 --- a/cmake/Modules/CUDAQEmDefaultConfig.cmake +++ b/cmake/Modules/CUDAQEmDefaultConfig.cmake @@ -8,9 +8,6 @@ get_filename_component(CUDAQ_EM_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) -set (CUDAQSpin_DIR "${CUDAQ_EM_CMAKE_DIR}") -find_dependency(CUDAQSpin REQUIRED) - set (CUDAQOperator_DIR "${CUDAQ_EM_CMAKE_DIR}") find_dependency(CUDAQOperator REQUIRED) diff --git a/cmake/Modules/CUDAQPlatformDefaultConfig.cmake b/cmake/Modules/CUDAQPlatformDefaultConfig.cmake index 7731250facd..6c53786ae62 100644 --- a/cmake/Modules/CUDAQPlatformDefaultConfig.cmake +++ b/cmake/Modules/CUDAQPlatformDefaultConfig.cmake @@ -14,9 +14,6 @@ find_dependency(CUDAQEmDefault REQUIRED) set (CUDAQOperator_DIR "${CUDAQ_CMAKE_DIR}") find_dependency(CUDAQOperator REQUIRED) -set (CUDAQSpin_DIR "${CUDAQ_CMAKE_DIR}") -find_dependency(CUDAQSpin REQUIRED) - set (CUDAQCommon_DIR "${CUDAQ_CMAKE_DIR}") find_dependency(CUDAQCommon REQUIRED) diff --git a/cmake/Modules/CUDAQSpinConfig.cmake b/cmake/Modules/CUDAQSpinConfig.cmake deleted file mode 100644 index 1920dc623dd..00000000000 --- a/cmake/Modules/CUDAQSpinConfig.cmake +++ /dev/null @@ -1,13 +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. # -# ============================================================================ # - -get_filename_component(CUDAQ_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) - -if(NOT TARGET cudaq::cudaq-spin) - include("${CUDAQ_CMAKE_DIR}/CUDAQSpinTargets.cmake") -endif() diff --git a/cmake/Modules/NVQIRConfig.cmake.in b/cmake/Modules/NVQIRConfig.cmake.in index 17cd691cbc8..214ab370a70 100644 --- a/cmake/Modules/NVQIRConfig.cmake.in +++ b/cmake/Modules/NVQIRConfig.cmake.in @@ -11,7 +11,6 @@ include(CMakeFindDependencyMacro) get_filename_component(PARENT_DIRECTORY ${NVQIR_CMAKE_DIR} DIRECTORY) -find_dependency(CUDAQSpin REQUIRED HINTS "${PARENT_DIRECTORY}/cudaq") find_dependency(CUDAQOperator REQUIRED HINTS "${PARENT_DIRECTORY}/cudaq") find_dependency(CUDAQCommon REQUIRED HINTS "${PARENT_DIRECTORY}/cudaq") find_package(fmt QUIET) diff --git a/docs/sphinx/api/languages/cpp_api.rst b/docs/sphinx/api/languages/cpp_api.rst index 42e5e3c5240..0a407dd975f 100644 --- a/docs/sphinx/api/languages/cpp_api.rst +++ b/docs/sphinx/api/languages/cpp_api.rst @@ -1,12 +1,6 @@ CUDA-Q C++ API ****************************** -Operators -============= - -.. doxygenclass:: cudaq::spin_op - :members: - Quantum ========= @@ -38,9 +32,9 @@ Common .. doxygenstruct:: cudaq::observe_options :members: -.. doxygenfunction:: cudaq::observe(const observe_options &options, QuantumKernel &&kernel, spin_op H, Args &&...args) -.. doxygenfunction:: cudaq::observe(std::size_t shots, QuantumKernel &&kernel, spin_op H, Args &&...args) -.. doxygenfunction:: cudaq::observe(QuantumKernel &&kernel, spin_op H, Args &&...args) +.. doxygenfunction:: cudaq::observe(const observe_options &options, QuantumKernel &&kernel, const spin_op &H, Args &&...args) +.. doxygenfunction:: cudaq::observe(std::size_t shots, QuantumKernel &&kernel, const spin_op &H, Args &&...args) +.. doxygenfunction:: cudaq::observe(QuantumKernel &&kernel, const spin_op &H, Args &&...args) .. doxygenfunction:: cudaq::observe(QuantumKernel &&kernel, const SpinOpContainer &termList, Args &&...args) .. doxygenclass:: cudaq::ExecutionContext diff --git a/docs/sphinx/applications/cpp/qaoa_maxcut.cpp b/docs/sphinx/applications/cpp/qaoa_maxcut.cpp index 0c14b578c60..aaa116a3d40 100644 --- a/docs/sphinx/applications/cpp/qaoa_maxcut.cpp +++ b/docs/sphinx/applications/cpp/qaoa_maxcut.cpp @@ -8,7 +8,6 @@ #include #include #include -#include // Here we build up a CUDA-Q kernel for QAOA with p layers, with each // layer containing the alternating set of unitaries corresponding to the @@ -51,13 +50,13 @@ struct ansatz { int main() { - using namespace cudaq::spin; - cudaq::set_random_seed(13); // set for repeatability // Problem Hamiltonian - const cudaq::spin_op Hp = 0.5 * z(0) * z(1) + 0.5 * z(1) * z(2) + - 0.5 * z(0) * z(3) + 0.5 * z(2) * z(3); + const cudaq::spin_op Hp = 0.5 * cudaq::spin_op::z(0) * cudaq::spin_op::z(1) + + 0.5 * cudaq::spin_op::z(1) * cudaq::spin_op::z(2) + + 0.5 * cudaq::spin_op::z(0) * cudaq::spin_op::z(3) + + 0.5 * cudaq::spin_op::z(2) * cudaq::spin_op::z(3); // Problem parameters const int n_qubits = 4; diff --git a/docs/sphinx/applications/cpp/trotter_kernel_mode.cpp b/docs/sphinx/applications/cpp/trotter_kernel_mode.cpp index cbd64042bb7..3424e164414 100644 --- a/docs/sphinx/applications/cpp/trotter_kernel_mode.cpp +++ b/docs/sphinx/applications/cpp/trotter_kernel_mode.cpp @@ -57,19 +57,22 @@ struct initState { } }; -std::vector term_coefficients(cudaq::spin_op op) { +std::vector term_coefficients(const cudaq::spin_op &op) { std::vector result{}; - op.for_each_term([&](cudaq::spin_op &term) { - const auto coeff = term.get_coefficient().real(); + for (const auto &term : op) { + const auto coeff = term.evaluate_coefficient().real(); result.push_back(coeff); - }); + } return result; } -std::vector term_words(cudaq::spin_op op) { - std::vector result{}; - op.for_each_term( - [&](cudaq::spin_op &term) { result.push_back(term.to_string(false)); }); +std::vector term_words(const cudaq::spin_op &op) { + // Our kernel uses these words to apply exp_pauli to the entire state. + // we hence ensure that each pauli word covers the entire space. + auto n_spins = op.num_qubits(); + std::vector result; + for (const auto &term : op) + result.push_back(term.get_pauli_word(n_spins)); return result; } @@ -93,22 +96,22 @@ int run_steps(int steps, int spins) { const double Jz = g; const double dt = 0.05; const int n_steps = steps; - const int n_spins = spins; + const std::size_t n_spins = spins; const double omega = 2 * M_PI; const auto heisenbergModelHam = [&](double t) -> cudaq::spin_op { cudaq::spin_op tdOp(n_spins); - for (int i = 0; i < n_spins - 1; ++i) { + for (std::size_t i = 0; i < n_spins - 1; ++i) { tdOp += (Jx * cudaq::spin::x(i) * cudaq::spin::x(i + 1)); tdOp += (Jy * cudaq::spin::y(i) * cudaq::spin::y(i + 1)); tdOp += (Jz * cudaq::spin::z(i) * cudaq::spin::z(i + 1)); } - for (int i = 0; i < n_spins; ++i) + for (std::size_t i = 0; i < n_spins; ++i) tdOp += (std::cos(omega * t) * cudaq::spin::x(i)); return tdOp; }; // Observe the average magnetization of all spins () cudaq::spin_op average_magnetization(n_spins); - for (int i = 0; i < n_spins; ++i) + for (std::size_t i = 0; i < n_spins; ++i) average_magnetization += ((1.0 / n_spins) * cudaq::spin::z(i)); average_magnetization -= 1.0; diff --git a/docs/sphinx/applications/python/adapt_qaoa.ipynb b/docs/sphinx/applications/python/adapt_qaoa.ipynb index e080fb1d57c..801c2ba48cf 100644 --- a/docs/sphinx/applications/python/adapt_qaoa.ipynb +++ b/docs/sphinx/applications/python/adapt_qaoa.ipynb @@ -102,7 +102,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -118,23 +118,28 @@ " # Add a term to the Hamiltonian for the edge (u,v)\n", " ham += 0.5 * (weight[count] * cudaq.spin.z(qubitu) * cudaq.spin.z(qubitv) -\n", " weight[count] * cudaq.spin.i(qubitu) * cudaq.spin.i(qubitv))\n", - " count+=1\n", + " count += 1\n", "\n", " return ham\n", "\n", "# Collect coefficients from a spin operator so we can pass them to a kernel\n", "def term_coefficients(ham: cudaq.SpinOperator) -> list[complex]:\n", " result = []\n", - " ham.for_each_term(lambda term: result.append(term.get_coefficient()))\n", + " for term in ham:\n", + " result.append(term.get_coefficient())\n", " return result\n", "\n", "# Collect Pauli words from a spin operator so we can pass them to a kernel\n", "def term_words(ham: cudaq.SpinOperator) -> list[str]:\n", + " # Our kernel uses these words to apply exp_pauli to the entire state.\n", + " # we hence ensure that each pauli word covers the entire space.\n", + " n_spins = ham.get_qubit_count()\n", " result = []\n", - " ham.for_each_term(lambda term: result.append(term.to_string(False)))\n", + " for term in ham:\n", + " result.append(term.get_pauli_word(n_spins))\n", " return result\n", "\n", - "# Generate the Spin Hmiltonian:\n", + "# Generate the Spin Hamiltonian:\n", "\n", "ham = spin_ham(edges, weight)\n", "ham_coef = term_coefficients(ham)\n", @@ -185,7 +190,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -200,7 +205,6 @@ "# Generate the mixer pool\n", "\n", "pools = mixer_pool(qubits_num)\n", - "#print([op.to_string(False) for op in pools])\n", "print('Number of pool operator: ', len(pools))" ] }, @@ -289,7 +293,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -402,7 +406,7 @@ " layer.append(1) \n", " random_mixer = random.choice(temp_pool)\n", " \n", - " mixer_pool = mixer_pool + [random_mixer.to_string(False)]\n", + " mixer_pool = mixer_pool + [random_mixer.get_pauli_word(qubits_num)]\n", " \n", " print('Mixer pool at step', istep)\n", " print(mixer_pool)\n", diff --git a/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb b/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb index a3043b7a1be..5449305b6ee 100644 --- a/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb +++ b/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb @@ -122,14 +122,14 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Generate the Hamiltonian\n", "def ham_clique(penalty, nodes, weights, non_edges) -> cudaq.SpinOperator:\n", "\n", - " spin_ham = 0.0\n", + " spin_ham = 0\n", " for wt, node in zip(weights, nodes):\n", " #print(wt,node)\n", " spin_ham += 0.5 * wt * spin.z(node)\n", @@ -153,7 +153,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -180,15 +180,20 @@ "# Collect coefficients from a spin operator so we can pass them to a kernel\n", "def term_coefficients(ham: cudaq.SpinOperator) -> list[complex]:\n", " result = []\n", - " ham.for_each_term(lambda term: result.append(term.get_coefficient()))\n", + " for term in ham:\n", + " result.append(term.get_coefficient())\n", " return result\n", "\n", " # Collect Pauli words from a spin operator so we can pass them to a kernel\n", "\n", "\n", "def term_words(ham: cudaq.SpinOperator) -> list[str]:\n", + " # Our kernel uses these words to apply exp_pauli to the entire state.\n", + " # we hence ensure that each pauli word covers the entire space.\n", + " n_spins = ham.get_qubit_count()\n", " result = []\n", - " ham.for_each_term(lambda term: result.append(term.to_string(False)))\n", + " for term in ham:\n", + " result.append(term.get_pauli_word(n_spins))\n", " return result\n", "\n", "\n", diff --git a/docs/sphinx/applications/python/divisive_clustering_coresets.ipynb b/docs/sphinx/applications/python/divisive_clustering_coresets.ipynb index 0cffbd7110b..01e2b003e72 100644 --- a/docs/sphinx/applications/python/divisive_clustering_coresets.ipynb +++ b/docs/sphinx/applications/python/divisive_clustering_coresets.ipynb @@ -223,7 +223,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ diff --git a/docs/sphinx/applications/python/hamiltonian_simulation.ipynb b/docs/sphinx/applications/python/hamiltonian_simulation.ipynb index 146527985e6..66e9dff2de2 100644 --- a/docs/sphinx/applications/python/hamiltonian_simulation.ipynb +++ b/docs/sphinx/applications/python/hamiltonian_simulation.ipynb @@ -191,14 +191,14 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "heisenberg-code", "metadata": {}, "outputs": [], "source": [ "def create_hamiltonian_heisenberg(n_spins: int, Jx: float, Jy: float, Jz: float, h_x: list[float], h_y: list[float], h_z: list[float]):\n", " \"\"\"Create the Hamiltonian operator\"\"\"\n", - " ham = cudaq.SpinOperator(num_qubits=n_spins)\n", + " ham = 0\n", "\n", " # Add two-qubit interaction terms for Heisenberg Hamiltonian\n", " for i in range(0, n_spins - 1):\n", @@ -221,14 +221,14 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "tfim-code", "metadata": {}, "outputs": [], "source": [ "def create_hamiltonian_tfim(n_spins: int, h_field: float):\n", " \"\"\"Create the Hamiltonian operator\"\"\"\n", - " ham = cudaq.SpinOperator(num_qubits=n_spins)\n", + " ham = 0\n", " \n", " # Add single-qubit terms\n", " for i in range(0, n_spins):\n", @@ -253,19 +253,24 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "extract-code", "metadata": {}, "outputs": [], "source": [ "def extractCoefficients(hamiltonian: cudaq.SpinOperator):\n", " result = []\n", - " hamiltonian.for_each_term(lambda term: result.append(term.get_coefficient()))\n", + " for term in hamiltonian:\n", + " result.append(term.get_coefficient())\n", " return result\n", "\n", "def extractWords(hamiltonian: cudaq.SpinOperator):\n", + " # Our kernel uses these words to apply exp_pauli to the entire state.\n", + " # we hence ensure that each pauli word covers the entire space.\n", + " n_spins = hamiltonian.get_qubit_count()\n", " result = []\n", - " hamiltonian.for_each_term(lambda term: result.append(term.to_string(False)))\n", + " for term in hamiltonian:\n", + " result.append(term.get_pauli_word(n_spins))\n", " return result" ] }, diff --git a/docs/sphinx/applications/python/krylov.ipynb b/docs/sphinx/applications/python/krylov.ipynb index 183a13d6919..ea3459d355f 100644 --- a/docs/sphinx/applications/python/krylov.ipynb +++ b/docs/sphinx/applications/python/krylov.ipynb @@ -135,14 +135,19 @@ "# Collect coefficients from a spin operator so they can pass to a kernel\n", "def termCoefficients(ham: cudaq.SpinOperator) -> list[complex]:\n", " result = []\n", - " ham.for_each_term(lambda term: result.append(term.get_coefficient()))\n", + " for term in ham:\n", + " result.append(term.get_coefficient())\n", " return result\n", "\n", "\n", "# Collect Pauli words from a spin operator so they can pass to a kernel\n", "def termWords(ham: cudaq.SpinOperator) -> list[str]:\n", + " # Our kernel uses these words to apply exp_pauli to the entire state.\n", + " # we hence ensure that each pauli word covers the entire space.\n", + " n_spins = ham.get_qubit_count()\n", " result = []\n", - " ham.for_each_term(lambda term: result.append(term.to_string(False)))\n", + " for term in ham:\n", + " result.append(term.get_pauli_word(n_spins))\n", " return result\n", "\n", "\n", @@ -315,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "875124e8-dba8-4ace-828b-d4b03352f9d8", "metadata": {}, "outputs": [], @@ -327,7 +332,7 @@ " observe_op = 1.0\n", " for m in range(qubits_num):\n", " observe_op *= cudaq.spin.i(m)\n", - " identity_word = observe_op.to_string(False)\n", + " identity_word = observe_op.get_pauli_word()\n", " pauli_list = pauli_str(identity_word, qubits_num)\n", "\n", " # Empty overlap matrix S\n", @@ -355,7 +360,7 @@ " for m in range(qubits_num):\n", " observe_op *= cudaq.spin.i(m)\n", "\n", - " identity_word = observe_op.to_string(False)\n", + " identity_word = observe_op.get_pauli_word()\n", " pauli_list = pauli_str(identity_word, qubits_num)\n", "\n", " # Empty overlap matrix S\n", diff --git a/docs/sphinx/applications/python/trotter.ipynb b/docs/sphinx/applications/python/trotter.ipynb index b508dd155c9..8ca91d02043 100644 --- a/docs/sphinx/applications/python/trotter.ipynb +++ b/docs/sphinx/applications/python/trotter.ipynb @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "e409539e-e97b-4815-8021-477308726155", "metadata": {}, "outputs": [], @@ -66,7 +66,7 @@ "\n", "\n", "def heisenbergModelHam(t: float) -> cudaq.SpinOperator:\n", - " tdOp = cudaq.SpinOperator(num_qubits=n_spins)\n", + " tdOp = cudaq.SpinOperator.empty()\n", " for i in range(0, n_spins - 1):\n", " tdOp += (Jx * cudaq.spin.x(i) * cudaq.spin.x(i + 1))\n", " tdOp += (Jy * cudaq.spin.y(i) * cudaq.spin.y(i + 1))\n", @@ -133,20 +133,25 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "0aeba077-3a12-4c1d-919e-efc521f531fe", "metadata": {}, "outputs": [], "source": [ "def termCoefficients(op: cudaq.SpinOperator) -> List[complex]:\n", " result = []\n", - " ham.for_each_term(lambda term: result.append(term.get_coefficient()))\n", + " for term in op:\n", + " result.append(term.get_coefficient())\n", " return result\n", "\n", "\n", "def termWords(op: cudaq.SpinOperator) -> List[str]:\n", + " # Our kernel uses these words to apply exp_pauli to the entire state.\n", + " # we hence ensure that each pauli word covers the entire space.\n", + " n_spins = op.get_qubit_count()\n", " result = []\n", - " ham.for_each_term(lambda term: result.append(term.to_string(False)))\n", + " for term in op:\n", + " result.append(term.get_pauli_word(n_spins))\n", " return result" ] }, @@ -160,15 +165,14 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "e0be9949-0d1a-44f5-bdeb-6be751dfe136", "metadata": {}, "outputs": [], "source": [ - "average_magnetization = cudaq.SpinOperator(num_qubits=n_spins)\n", + "average_magnetization = cudaq.SpinOperator.empty()\n", "for i in range(0, n_spins):\n", - " average_magnetization += ((1.0 / n_spins) * cudaq.spin.z(i))\n", - "average_magnetization -= 1.0" + " average_magnetization += ((1.0 / n_spins) * cudaq.spin.z(i))" ] }, { diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py index 4127d91fa33..ddd430e98fb 100644 --- a/docs/sphinx/conf.py +++ b/docs/sphinx/conf.py @@ -210,6 +210,13 @@ def setup(app): ('py:class', 'type'), ('py:class', 'cudaq::spin_op'), ('py:class', 'numpy.ndarray[]'), + # FIXME: remove these after adding proper documentation + # (also reexamine why some of the ones above are ignored) + ('py:class', 'cudaq::sum_op'), + ('py:class', 'SpinOperatorTerm'), + ('cpp:identifier', 'cudaq::spin_op'), + ('cpp:identifier', 'spin_op'), + ('cpp:identifier', 'spin_op_term'), ] napoleon_google_docstring = True diff --git a/docs/sphinx/examples/cpp/basics/expectation_values.cpp b/docs/sphinx/examples/cpp/basics/expectation_values.cpp index 41eea1f156d..79e7e77a36b 100644 --- a/docs/sphinx/examples/cpp/basics/expectation_values.cpp +++ b/docs/sphinx/examples/cpp/basics/expectation_values.cpp @@ -21,9 +21,10 @@ struct ansatz { int main() { // Build up your spin op algebraically - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); // Observe takes the kernel, the spin_op, and the concrete // parameters for the kernel diff --git a/docs/sphinx/examples/cpp/dynamics/cavity_qed.cpp b/docs/sphinx/examples/cpp/dynamics/cavity_qed.cpp index 43ea3a1f8b1..a81119f19a9 100644 --- a/docs/sphinx/examples/cpp/dynamics/cavity_qed.cpp +++ b/docs/sphinx/examples/cpp/dynamics/cavity_qed.cpp @@ -12,7 +12,7 @@ // ``` #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include "cudaq/operators.h" #include "export_csv_helper.h" #include diff --git a/docs/sphinx/examples/cpp/dynamics/cross_resonance.cpp b/docs/sphinx/examples/cpp/dynamics/cross_resonance.cpp index 02df481de62..185dea3efeb 100644 --- a/docs/sphinx/examples/cpp/dynamics/cross_resonance.cpp +++ b/docs/sphinx/examples/cpp/dynamics/cross_resonance.cpp @@ -12,7 +12,7 @@ // ``` #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include "cudaq/operators.h" #include "export_csv_helper.h" #include diff --git a/docs/sphinx/examples/cpp/dynamics/heisenberg_model.cpp b/docs/sphinx/examples/cpp/dynamics/heisenberg_model.cpp index 8f0b55f2bd9..eb96a9ab01c 100644 --- a/docs/sphinx/examples/cpp/dynamics/heisenberg_model.cpp +++ b/docs/sphinx/examples/cpp/dynamics/heisenberg_model.cpp @@ -12,7 +12,7 @@ // ``` #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include "cudaq/operators.h" #include "export_csv_helper.h" #include diff --git a/docs/sphinx/examples/cpp/dynamics/qubit_control.cpp b/docs/sphinx/examples/cpp/dynamics/qubit_control.cpp index c2c38ea9ce5..06bd63f8914 100644 --- a/docs/sphinx/examples/cpp/dynamics/qubit_control.cpp +++ b/docs/sphinx/examples/cpp/dynamics/qubit_control.cpp @@ -12,7 +12,7 @@ // ``` #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include "cudaq/operators.h" #include "export_csv_helper.h" #include diff --git a/docs/sphinx/examples/cpp/dynamics/qubit_dynamics.cpp b/docs/sphinx/examples/cpp/dynamics/qubit_dynamics.cpp index 256dbf9ada7..950d7dc6a5e 100644 --- a/docs/sphinx/examples/cpp/dynamics/qubit_dynamics.cpp +++ b/docs/sphinx/examples/cpp/dynamics/qubit_dynamics.cpp @@ -12,7 +12,7 @@ // ``` #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include "cudaq/operators.h" #include "export_csv_helper.h" #include diff --git a/docs/sphinx/examples/cpp/other/builder/builder.cpp b/docs/sphinx/examples/cpp/other/builder/builder.cpp index f8a9dd990c4..0f5f6d92046 100644 --- a/docs/sphinx/examples/cpp/other/builder/builder.cpp +++ b/docs/sphinx/examples/cpp/other/builder/builder.cpp @@ -39,9 +39,10 @@ int main() { { // Create a Hamiltonian as a `cudaq::spin_op`. - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); // Build a quantum kernel dynamically // Start by creating the Builder, the kernel argument types @@ -66,11 +67,13 @@ int main() { { // Build up a 2 parameter circuit using a vector parameter // Run the CUDA-Q optimizer to find optimal value. - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); // Create the kernel with signature void(std::vector) auto [ansatz, thetas] = cudaq::make_kernel>(); diff --git a/docs/sphinx/examples/cpp/other/builder/qaoa_maxcut_builder.cpp b/docs/sphinx/examples/cpp/other/builder/qaoa_maxcut_builder.cpp index 42110a44671..02419035276 100644 --- a/docs/sphinx/examples/cpp/other/builder/qaoa_maxcut_builder.cpp +++ b/docs/sphinx/examples/cpp/other/builder/qaoa_maxcut_builder.cpp @@ -15,7 +15,6 @@ #include #include #include -#include // This example demonstrates the same code as in `qaoa_maxcut.cpp`, // but with the use of dynamic kernels. Here we have QAOA with `p` layers, @@ -34,13 +33,13 @@ int main() { - using namespace cudaq::spin; - cudaq::set_random_seed(13); // set for repeatability // Problem Hamiltonian - const cudaq::spin_op Hp = 0.5 * z(0) * z(1) + 0.5 * z(1) * z(2) + - 0.5 * z(0) * z(3) + 0.5 * z(2) * z(3); + const cudaq::spin_op Hp = 0.5 * cudaq::spin_op::z(0) * cudaq::spin_op::z(1) + + 0.5 * cudaq::spin_op::z(1) * cudaq::spin_op::z(2) + + 0.5 * cudaq::spin_op::z(0) * cudaq::spin_op::z(3) + + 0.5 * cudaq::spin_op::z(2) * cudaq::spin_op::z(3); // Specify problem parameters const int n_qubits = 4; diff --git a/docs/sphinx/examples/cpp/other/builder/vqe_h2_builder.cpp b/docs/sphinx/examples/cpp/other/builder/vqe_h2_builder.cpp index f291e6e8bf3..a9f22cf2d1e 100644 --- a/docs/sphinx/examples/cpp/other/builder/vqe_h2_builder.cpp +++ b/docs/sphinx/examples/cpp/other/builder/vqe_h2_builder.cpp @@ -57,23 +57,24 @@ void so4(cudaq::kernel_builder> &builder, QuakeValue &&q, int main() { // Read in the spin op from file - std::vector h2_data{0, 0, 0, 0, -0.10647701149499994, 0.0, - 1, 1, 1, 1, 0.0454063328691, 0.0, - 1, 1, 3, 3, 0.0454063328691, 0.0, - 3, 3, 1, 1, 0.0454063328691, 0.0, - 3, 3, 3, 3, 0.0454063328691, 0.0, - 2, 0, 0, 0, 0.170280101353, 0.0, - 2, 2, 0, 0, 0.120200490713, 0.0, - 2, 0, 2, 0, 0.168335986252, 0.0, - 2, 0, 0, 2, 0.165606823582, 0.0, - 0, 2, 0, 0, -0.22004130022499996, 0.0, - 0, 2, 2, 0, 0.165606823582, 0.0, - 0, 2, 0, 2, 0.174072892497, 0.0, - 0, 0, 2, 0, 0.17028010135300004, 0.0, - 0, 0, 2, 2, 0.120200490713, 0.0, - 0, 0, 0, 2, -0.22004130022499999, 0.0, - 15}; - cudaq::spin_op H(h2_data, /*nQubits*/ 4); + std::vector h2_data{ + 15, -0.10647701149499994, 0, 4, 0, 0, 1, 0, 2, 0, 3, + 0, 0.0454063328691, 0, 4, 0, 2, 1, 2, 2, 2, 3, + 2, 0.0454063328691, 0, 4, 0, 2, 1, 2, 2, 3, 3, + 3, 0.0454063328691, 0, 4, 0, 3, 1, 3, 2, 2, 3, + 2, 0.0454063328691, 0, 4, 0, 3, 1, 3, 2, 3, 3, + 3, 0.170280101353, 0, 4, 0, 1, 1, 0, 2, 0, 3, + 0, 0.120200490713, 0, 4, 0, 1, 1, 1, 2, 0, 3, + 0, 0.168335986252, 0, 4, 0, 1, 1, 0, 2, 1, 3, + 0, 0.165606823582, 0, 4, 0, 1, 1, 0, 2, 0, 3, + 1, -0.22004130022499996, 0, 4, 0, 0, 1, 1, 2, 0, 3, + 0, 0.165606823582, 0, 4, 0, 0, 1, 1, 2, 1, 3, + 0, 0.174072892497, 0, 4, 0, 0, 1, 1, 2, 0, 3, + 1, 0.170280101353, 0, 4, 0, 0, 1, 0, 2, 1, 3, + 0, 0.120200490713, 0, 4, 0, 0, 1, 0, 2, 1, 3, + 1, -0.22004130022499996, 0, 4, 0, 0, 1, 0, 2, 0, 3, + 1}; + cudaq::spin_op H(h2_data); int layers = 2, n_qubits = H.num_qubits(), block_size = 2, p_counter = 0; int n_blocks_per_layer = 2 * (n_qubits / block_size) - 1; diff --git a/docs/sphinx/examples/cpp/other/compute_actions.cpp b/docs/sphinx/examples/cpp/other/compute_actions.cpp index 4aa359ffbf7..9d379c7ca1f 100644 --- a/docs/sphinx/examples/cpp/other/compute_actions.cpp +++ b/docs/sphinx/examples/cpp/other/compute_actions.cpp @@ -67,24 +67,24 @@ struct ansatz_compute_action { int main(int argc, char **argv) { - std::vector h2_data{0, 0, 0, 0, -0.10647701149499994, 0.0, - 1, 1, 1, 1, 0.0454063328691, 0.0, - 1, 1, 3, 3, 0.0454063328691, 0.0, - 3, 3, 1, 1, 0.0454063328691, 0.0, - 3, 3, 3, 3, 0.0454063328691, 0.0, - 2, 0, 0, 0, 0.170280101353, 0.0, - 2, 2, 0, 0, 0.120200490713, 0.0, - 2, 0, 2, 0, 0.168335986252, 0.0, - 2, 0, 0, 2, 0.165606823582, 0.0, - 0, 2, 0, 0, -0.22004130022499996, 0.0, - 0, 2, 2, 0, 0.165606823582, 0.0, - 0, 2, 0, 2, 0.174072892497, 0.0, - 0, 0, 2, 0, 0.17028010135300004, 0.0, - 0, 0, 2, 2, 0.120200490713, 0.0, - 0, 0, 0, 2, -0.22004130022499999, 0.0, - 15}; - cudaq::spin_op H(h2_data, /*nQubits*/ 4); - + std::vector h2_data{ + 15, -0.10647701149499994, 0, 4, 0, 0, 1, 0, 2, 0, 3, + 0, 0.0454063328691, 0, 4, 0, 2, 1, 2, 2, 2, 3, + 2, 0.0454063328691, 0, 4, 0, 2, 1, 2, 2, 3, 3, + 3, 0.0454063328691, 0, 4, 0, 3, 1, 3, 2, 2, 3, + 2, 0.0454063328691, 0, 4, 0, 3, 1, 3, 2, 3, 3, + 3, 0.170280101353, 0, 4, 0, 1, 1, 0, 2, 0, 3, + 0, 0.120200490713, 0, 4, 0, 1, 1, 1, 2, 0, 3, + 0, 0.168335986252, 0, 4, 0, 1, 1, 0, 2, 1, 3, + 0, 0.165606823582, 0, 4, 0, 1, 1, 0, 2, 0, 3, + 1, -0.22004130022499996, 0, 4, 0, 0, 1, 1, 2, 0, 3, + 0, 0.165606823582, 0, 4, 0, 0, 1, 1, 2, 1, 3, + 0, 0.174072892497, 0, 4, 0, 0, 1, 1, 2, 0, 3, + 1, 0.170280101353, 0, 4, 0, 0, 1, 0, 2, 1, 3, + 0, 0.120200490713, 0, 4, 0, 0, 1, 0, 2, 1, 3, + 1, -0.22004130022499996, 0, 4, 0, 0, 1, 0, 2, 0, 3, + 1}; + cudaq::spin_op H(h2_data); const auto param_space = cudaq::linspace(-M_PI, M_PI, 10); printf("Using the hand-coded kernel\n"); for (const auto ¶m : param_space) { diff --git a/docs/sphinx/examples/cpp/other/distributed/mpi.cpp b/docs/sphinx/examples/cpp/other/distributed/mpi.cpp index bb2653d84a2..1b3fcf0de19 100644 --- a/docs/sphinx/examples/cpp/other/distributed/mpi.cpp +++ b/docs/sphinx/examples/cpp/other/distributed/mpi.cpp @@ -29,9 +29,10 @@ int main(int argc, char **argv) { if (cudaq::mpi::rank() == 0) printf("Running MPI example with %d processes.\n", cudaq::mpi::num_ranks()); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto ansatz = [](double theta) __qpu__ { cudaq::qubit q, r; x(q); diff --git a/docs/sphinx/examples/cpp/other/gradients.cpp b/docs/sphinx/examples/cpp/other/gradients.cpp index 206b3e00874..8b56091faee 100644 --- a/docs/sphinx/examples/cpp/other/gradients.cpp +++ b/docs/sphinx/examples/cpp/other/gradients.cpp @@ -31,12 +31,14 @@ struct deuteron_n3_ansatz { }; int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); // Default here is COBYLA // Should see many more iterations diff --git a/docs/sphinx/examples/cpp/other/remote_sim/simple.cpp b/docs/sphinx/examples/cpp/other/remote_sim/simple.cpp index 3a08cbcecae..4a03b9d89f6 100644 --- a/docs/sphinx/examples/cpp/other/remote_sim/simple.cpp +++ b/docs/sphinx/examples/cpp/other/remote_sim/simple.cpp @@ -22,9 +22,10 @@ // its features and command line options. int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto [ansatz, theta] = cudaq::make_kernel(); // Allocate some qubits diff --git a/docs/sphinx/snippets/cpp/using/cudaq/nvqc/nvqc_mqpu.cpp b/docs/sphinx/snippets/cpp/using/cudaq/nvqc/nvqc_mqpu.cpp index 24b7563147d..5480ff5600e 100644 --- a/docs/sphinx/snippets/cpp/using/cudaq/nvqc/nvqc_mqpu.cpp +++ b/docs/sphinx/snippets/cpp/using/cudaq/nvqc/nvqc_mqpu.cpp @@ -14,9 +14,10 @@ #include int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto [ansatz, theta] = cudaq::make_kernel(); auto q = ansatz.qalloc(); diff --git a/docs/sphinx/snippets/cpp/using/cudaq/platform/observe_mqpu.cpp b/docs/sphinx/snippets/cpp/using/cudaq/platform/observe_mqpu.cpp index a19a6643bab..35c0dfe2619 100644 --- a/docs/sphinx/snippets/cpp/using/cudaq/platform/observe_mqpu.cpp +++ b/docs/sphinx/snippets/cpp/using/cudaq/platform/observe_mqpu.cpp @@ -15,9 +15,10 @@ int main() { // [Begin Documentation] - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); // Get the quantum_platform singleton auto &platform = cudaq::get_platform(); diff --git a/docs/sphinx/snippets/cpp/using/cudaq/platform/observe_mqpu_mpi.cpp b/docs/sphinx/snippets/cpp/using/cudaq/platform/observe_mqpu_mpi.cpp index 238ed758392..894f520f6c7 100644 --- a/docs/sphinx/snippets/cpp/using/cudaq/platform/observe_mqpu_mpi.cpp +++ b/docs/sphinx/snippets/cpp/using/cudaq/platform/observe_mqpu_mpi.cpp @@ -16,9 +16,10 @@ int main() { // [Begin Documentation] cudaq::mpi::initialize(); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto ansatz = [](double theta) __qpu__ { cudaq::qubit q, r; diff --git a/docs/sphinx/snippets/cpp/using/first_observe.cpp b/docs/sphinx/snippets/cpp/using/first_observe.cpp index dc899396efb..767ab49edc7 100644 --- a/docs/sphinx/snippets/cpp/using/first_observe.cpp +++ b/docs/sphinx/snippets/cpp/using/first_observe.cpp @@ -13,16 +13,13 @@ #include -using namespace cudaq::spin; - __qpu__ void kernel() { cudaq::qubit qubit; h(qubit); } int main() { - cudaq::spin_op spin_operator = z(0); - // Prints: [1+0j] Z + auto spin_operator = cudaq::spin_op::z(0); std::cout << spin_operator.to_string() << "\n"; // [End Observe1] diff --git a/docs/sphinx/specification/cudaq/algorithmic_primitives.rst b/docs/sphinx/specification/cudaq/algorithmic_primitives.rst index 040d18dcc3f..8cd792c554a 100644 --- a/docs/sphinx/specification/cudaq/algorithmic_primitives.rst +++ b/docs/sphinx/specification/cudaq/algorithmic_primitives.rst @@ -440,11 +440,10 @@ Here is an example of the utility of the :code:`cudaq::observe` function: } }; - int main() { - using namespace cudaq::spin; // make it easier to use pauli X,Y,Z below - - spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + int main() { + spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); double energy = cudaq::observe(ansatz{}, h, .59); printf("Energy is %lf\n", energy); diff --git a/docs/sphinx/specification/cudaq/examples.rst b/docs/sphinx/specification/cudaq/examples.rst index c67874b4c1a..b60816c1fdb 100644 --- a/docs/sphinx/specification/cudaq/examples.rst +++ b/docs/sphinx/specification/cudaq/examples.rst @@ -183,9 +183,9 @@ Deuteron Binding Energy Parameter Sweep }; int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); // Perform parameter sweep for deuteron N=2 Hamiltonian const auto param_space = cudaq::linspace(-M_PI, M_PI, 25); diff --git a/docs/sphinx/specification/cudaq/operators.rst b/docs/sphinx/specification/cudaq/operators.rst index 2645b861fea..c361a098c38 100644 --- a/docs/sphinx/specification/cudaq/operators.rst +++ b/docs/sphinx/specification/cudaq/operators.rst @@ -17,7 +17,7 @@ for :math:`a = {x,y,z}`, :math:`j` the qubit index, and :math:`N` the number of **[3]** The :code:`spin_op` exposes common C++ operator overloads for algebraic expressions. -**[4]** CUDA-Q defines convenience functions in :code:`cudaq::spin` namespace that produce +**[4]** CUDA-Q defines static functions to create the primitive X, Y, and Z Pauli operators on specified qubit indices which can subsequently be used in algebraic expressions to build up more complicated Pauli tensor products and their sums. @@ -26,9 +26,9 @@ more complicated Pauli tensor products and their sums. .. code-block:: cpp - using namespace cudaq::spin; - auto h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + \ - .21829 * z(0) - 6.125 * z(1); + auto h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - \ + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + \ + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); .. tab:: Python @@ -43,39 +43,8 @@ more complicated Pauli tensor products and their sums. synthesis tasks within quantum kernel code. Specifically, operations that encode :math:`N`\ :sup:`th`\ order trotterization of exponentiated :code:`spin_op` rotations, e.g. :math:`U = \exp(-i H t)`, where :math:`H` is the provided :code:`spin_op`. +Currently, H is limited to a single product term. **[6]** The :code:`spin_op` can be created within classical host code and quantum kernel code, and can also be passed by value to quantum kernel code from host code. -The :code:`spin_op` should take on the following structure: - -.. code-block:: cpp - - namespace cudaq { - class spin_op { - public: - spin_op(); - spin_op(const spin_op&); - bool empty() const; - std::size_t num_qubits() const; - std::size_t num_terms() const; - std::complex get_coefficient(); - bool is_identity() const; - void for_each_term(std::function &&) const; - void for_each_pauli(std::function &&) const; - spin_op& operator=(const spin_op&); - spin_op& operator+=(const spin_op&); - spin_op& operator-=(const spin_op&); - spin_op& operator*=(const spin_op&); - bool operator==(const spin_op&); - spin_op& operator*=(const double); - spin_op& operator*=(const std::complex) - }; - - namespace spin { - spin_op x(const std::size_t); - spin_op y(const std::size_t); - spin_op z(const std::size_t); - } - } - diff --git a/docs/sphinx/targets/cpp/nvqc_vqe.cpp b/docs/sphinx/targets/cpp/nvqc_vqe.cpp index 88c32c5051e..72b70767843 100644 --- a/docs/sphinx/targets/cpp/nvqc_vqe.cpp +++ b/docs/sphinx/targets/cpp/nvqc_vqe.cpp @@ -16,9 +16,10 @@ #include int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto [ansatz, theta] = cudaq::make_kernel(); auto q = ansatz.qalloc(); diff --git a/docs/sphinx/targets/cpp/pasqal.cpp b/docs/sphinx/targets/cpp/pasqal.cpp index aa86dc9a114..a0d304b707d 100644 --- a/docs/sphinx/targets/cpp/pasqal.cpp +++ b/docs/sphinx/targets/cpp/pasqal.cpp @@ -8,7 +8,7 @@ // password, use pasqal_auth.py in this folder. #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include "cudaq/operators.h" #include "cudaq/schedule.h" #include diff --git a/docs/sphinx/targets/cpp/quera_basic.cpp b/docs/sphinx/targets/cpp/quera_basic.cpp index 9d1202d4894..84f2e7d325c 100644 --- a/docs/sphinx/targets/cpp/quera_basic.cpp +++ b/docs/sphinx/targets/cpp/quera_basic.cpp @@ -6,7 +6,7 @@ // Assumes a valid set of credentials have been stored. #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include "cudaq/schedule.h" #include #include diff --git a/docs/sphinx/targets/cpp/quera_intro.cpp b/docs/sphinx/targets/cpp/quera_intro.cpp index 15d6bbfe86f..481def73a1a 100644 --- a/docs/sphinx/targets/cpp/quera_intro.cpp +++ b/docs/sphinx/targets/cpp/quera_intro.cpp @@ -6,7 +6,7 @@ // Assumes a valid set of credentials have been stored. #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include "cudaq/operators.h" #include "cudaq/schedule.h" #include diff --git a/docs/sphinx/using/basics/run_kernel.rst b/docs/sphinx/using/basics/run_kernel.rst index 05856258844..0a931cac373 100644 --- a/docs/sphinx/using/basics/run_kernel.rst +++ b/docs/sphinx/using/basics/run_kernel.rst @@ -134,8 +134,8 @@ The observe function allows us to calculate expectation values for a defined qua The `cudaq::observe` method takes a kernel and its arguments as inputs, along with a `cudaq::spin_op`. - Within the `cudaq::spin` namespace, operators may be defined as a linear combination of Pauli strings. Functions, such - as `cudaq::spin::i`, `cudaq::spin::x`, `cudaq::spin::y`, `cudaq::spin::z` may be used to construct more + Operators may be defined as a linear combination of Pauli strings. Functions, such + as `cudaq::spin_op::i`, `cudaq::spin_op::x`, `cudaq::spin_op::y`, `cudaq::spin_op::z` may be used to construct more complex spin Hamiltonians on multiple qubits. Below is an example of a spin operator object consisting of a `Z(0)` operator, or a Pauli Z-operator on the qubit zero. diff --git a/docs/sphinx/using/extending/cudaq_ir.rst b/docs/sphinx/using/extending/cudaq_ir.rst index da32cb9233f..d4e309f6604 100644 --- a/docs/sphinx/using/extending/cudaq_ir.rst +++ b/docs/sphinx/using/extending/cudaq_ir.rst @@ -32,7 +32,7 @@ Let's see the output of :code:`nvq++` in verbose mode. Consider a simple code li llc --relocation-model=pic --filetype=obj -O2 simple.ll.p3De4L -o simple.qke.o llc --relocation-model=pic --filetype=obj -O2 simple.ll -o simple.classic.o clang++ -L/usr/lib/gcc/x86_64-linux-gnu/12 -L/usr/lib64 -L/lib/x86_64-linux-gnu -L/lib64 -L/usr/lib/x86_64-linux-gnu -L/lib -L/usr/lib -L/usr/local/cuda/lib64/stubs -r simple.qke.o simple.classic.o -o simple.o - clang++ -Wl,-rpath,lib -Llib -L/usr/lib/gcc/x86_64-linux-gnu/12 -L/usr/lib64 -L/lib/x86_64-linux-gnu -L/lib64 -L/usr/lib/x86_64-linux-gnu -L/lib -L/usr/lib -L/usr/local/cuda/lib64/stubs simple.o -lcudaq -lcudaq-common -lcudaq-mlir-runtime -lcudaq-builder -lcudaq-ensmallen -lcudaq-nlopt -lcudaq-spin -lcudaq-em-default -lcudaq-platform-default -lnvqir -lnvqir-qpp + clang++ -Wl,-rpath,lib -Llib -L/usr/lib/gcc/x86_64-linux-gnu/12 -L/usr/lib64 -L/lib/x86_64-linux-gnu -L/lib64 -L/usr/lib/x86_64-linux-gnu -L/lib -L/usr/lib -L/usr/local/cuda/lib64/stubs simple.o -lcudaq -lcudaq-common -lcudaq-mlir-runtime -lcudaq-builder -lcudaq-ensmallen -lcudaq-nlopt -lcudaq-operator -lcudaq-em-default -lcudaq-platform-default -lnvqir -lnvqir-qpp This workflow orchestration is represented in the figure below: diff --git a/python/cudaq/kernel/kernel_builder.py b/python/cudaq/kernel/kernel_builder.py index 6d7a112796e..77d0b115add 100644 --- a/python/cudaq/kernel/kernel_builder.py +++ b/python/cudaq/kernel/kernel_builder.py @@ -852,13 +852,17 @@ def exp_pauli(self, theta, *args): qubitsList = [] pauliWordVal = None for arg in args: - if isinstance(arg, cudaq_runtime.SpinOperator) or hasattr( - arg, "_to_spinop"): + if isinstance(arg, cudaq_runtime.SpinOperatorTerm): + arg = arg.get_pauli_word() + elif hasattr(arg, "_to_spinop"): + arg = arg._to_spinop() + if isinstance(arg, cudaq_runtime.SpinOperator): if arg.get_term_count() > 1: emitFatalError( 'exp_pauli operation requires a SpinOperator composed of a single term.' ) - arg = arg.to_string(False) + arg, *_ = arg + arg = arg.get_pauli_word() if isinstance(arg, str): retTy = cc.PointerType.get( diff --git a/python/cudaq/operator/expressions.py b/python/cudaq/operator/expressions.py index 016095966ff..d5a8fa50eed 100644 --- a/python/cudaq/operator/expressions.py +++ b/python/cudaq/operator/expressions.py @@ -226,9 +226,11 @@ def _to_spinop(self: OperatorSum, "incorrect dimensions - conversion to spin operator can only be done for qubits" ) converted = self._evaluate(_SpinArithmetics(**kwargs), False) - if not isinstance(converted, cudaq_runtime.SpinOperator): + if isinstance(converted, cudaq_runtime.SpinOperatorTerm): + return cudaq_runtime.SpinOperator(converted) + elif not isinstance(converted, cudaq_runtime.SpinOperator): if converted == 0: - return cudaq_runtime.SpinOperator.empty_op() + return cudaq_runtime.SpinOperator.empty() else: return converted * cudaq_runtime.SpinOperator() else: @@ -308,33 +310,38 @@ def to_json(self: OperatorSum): Converts the representation to JSON string: `[[d1, d2, d3, ...], numQubits]` """ as_spin_op = self._to_spinop() - tuple_data = (as_spin_op.serialize(), as_spin_op.get_qubit_count()) - return json.dumps(tuple_data) + data = as_spin_op.serialize() + return json.dumps(data) # Convert from `SpinOperator` to an `Operator` @staticmethod def _from_spin_op(spin_op: cudaq_runtime.SpinOperator) -> OperatorSum: result_ops = [] - - def term_to_operator(term): - nonlocal result_ops - coeff = term.get_coefficient() - pauliWord = term.to_string(False) - result_ops.append(ProductOperator._from_word(pauliWord) * coeff) - - spin_op.for_each_term(lambda term: term_to_operator(term)) + for term in spin_op: + prod = ScalarOperator.const(term.get_coefficient()) + + def pauli_to_operator(pauli, idx): + nonlocal prod + if pauli == cudaq_runtime.Pauli.X: + prod *= ElementaryOperator("pauli_x", [idx]) + elif pauli == cudaq_runtime.Pauli.Y: + prod *= ElementaryOperator("pauli_y", [idx]) + elif pauli == cudaq_runtime.Pauli.Z: + prod *= ElementaryOperator("pauli_z", [idx]) + else: + prod *= ElementaryOperator.identity(idx) + + term.for_each_pauli(pauli_to_operator) + result_ops.append(prod) return OperatorSum(result_ops) @staticmethod def from_json(json_str: str): """ - Convert JSON string (`[[d1, d2, d3, ...], numQubits]`) to operator + Convert JSON string (`[d1, d2, d3, ...]`) to operator """ - tuple_data = json.loads(json_str) - if len(tuple_data) != 2: - raise ValueError("Invalid JSON string for spin_op") - - spin_op = cudaq_runtime.SpinOperator(tuple_data[0], tuple_data[1]) + data = json.loads(json_str) + spin_op = cudaq_runtime.SpinOperator(data) return OperatorSum._from_spin_op(spin_op) def to_sparse_matrix(self: OperatorSum): @@ -349,24 +356,11 @@ def __iter__(self: OperatorSum) -> OperatorSum: return self def __next__(self: OperatorSum) -> ProductOperator: - degrees = frozenset((degree for term in self._terms - for op in term._operators - for degree in op._degrees)) - - def padded_term(term: ProductOperator) -> ProductOperator: - op_degrees = [ - op_degree for op in term._operators for op_degree in op._degrees - ] - for degree in degrees: - if not degree in op_degrees: - term *= ElementaryOperator.identity(degree) - return term - if self._terms is None or self._iter_idx >= len(self._terms): raise StopIteration value = self._terms[self._iter_idx] self._iter_idx += 1 - return padded_term(value) + return value def get_coefficient(self): """ @@ -606,6 +600,20 @@ def char_to_op(c, degree): ops = [char_to_op(c, idx) for idx, c in enumerate(pauli_word)] return ProductOperator(ops) + def get_pauli_word(self: ProductOperator, pad_identities: int = 0) -> str: + spin_op = self._to_spinop() + if spin_op.get_term_count() == 0: + return 'I' * pad_identities + op, *_ = spin_op + return op.get_pauli_word(pad_identities) + + def get_term_id(self: ProductOperator) -> str: + spin_op = self._to_spinop() + if spin_op.get_term_count() == 0: + return '' + op, *_ = spin_op + return op.get_term_id() + # These are `cudaq_runtime.SpinOperator` methods that we provide shims (to maintain compatibility). # FIXME(OperatorCpp): Review these APIs: drop/deprecate or make them general (not `SpinOperator`-specific). def for_each_pauli(self: ProductOperator, call_back): diff --git a/python/cudaq/operator/manipulation.py b/python/cudaq/operator/manipulation.py index dc6d31c819b..5b6f4a5a404 100644 --- a/python/cudaq/operator/manipulation.py +++ b/python/cudaq/operator/manipulation.py @@ -253,10 +253,18 @@ def add( # FIXME(OperatorCpp): `SpinOperator` only exposes `+` operator for `double`, needs to multiply with an identity operator before adding. if isinstance(op1, NumericType) and isinstance( op2, cudaq_runtime.SpinOperator): - return op1 * cudaq_runtime.SpinOperator() + op2 + return op1 * cudaq_runtime.SpinOperatorTerm() + op2 + if isinstance(op1, NumericType) and isinstance( + op2, cudaq_runtime.SpinOperatorTerm): + return op1 * cudaq_runtime.SpinOperatorTerm( + ) + cudaq_runtime.SpinOperator(op2) if isinstance(op2, NumericType) and isinstance( op1, cudaq_runtime.SpinOperator): - return op2 * cudaq_runtime.SpinOperator() + op1 + return op2 * cudaq_runtime.SpinOperatorTerm() + op1 + if isinstance(op2, NumericType) and isinstance( + op1, cudaq_runtime.SpinOperatorTerm): + return op2 * cudaq_runtime.SpinOperatorTerm( + ) + cudaq_runtime.SpinOperator(op1) return op1 + op2 def evaluate( diff --git a/python/cudaq/runtime/observe.py b/python/cudaq/runtime/observe.py index befd1c0e29f..b4afcf12b40 100644 --- a/python/cudaq/runtime/observe.py +++ b/python/cudaq/runtime/observe.py @@ -86,7 +86,10 @@ def observe(kernel, spin_operator = to_spin_op(spin_operator) if isinstance(spin_operator, list): for idx, op in enumerate(spin_operator): - spin_operator[idx] = to_spin_op(op) + spin_operator[idx] = to_spin_op(op).canonicalize() + else: + spin_operator.canonicalize() + # Handle parallel execution use cases if execution != None: return cudaq_runtime.observe_parallel(kernel, @@ -100,12 +103,12 @@ def observe(kernel, cudaq_runtime.set_noise(noise_model) # Process spin_operator if its a list - localOp = spin_operator - localOp = cudaq_runtime.SpinOperator() - if isinstance(spin_operator, list): + if isinstance(spin_operator, cudaq_runtime.SpinOperatorTerm): + localOp = cudaq_runtime.SpinOperator(spin_operator) + elif isinstance(spin_operator, list): + localOp = cudaq_runtime.SpinOperator.empty() for o in spin_operator: localOp += o - localOp -= cudaq_runtime.SpinOperator() else: localOp = spin_operator @@ -145,9 +148,10 @@ def computeExpVal(term): sum += term.get_coefficient().real else: sum += res.expectation( - term.to_string(False)) * term.get_coefficient().real + term.get_term_id()) * term.get_coefficient().real - localOp.for_each_term(computeExpVal) + for term in localOp: + computeExpVal(term) expVal = sum observeResult = cudaq_runtime.ObserveResult(expVal, localOp, res) diff --git a/python/extension/CUDAQuantumExtension.cpp b/python/extension/CUDAQuantumExtension.cpp index 1b787b9277c..dbd0f414a08 100644 --- a/python/extension/CUDAQuantumExtension.cpp +++ b/python/extension/CUDAQuantumExtension.cpp @@ -233,7 +233,7 @@ PYBIND11_MODULE(_quakeDialects, m) { targetInfo.emplace_back(t[0], t[1]); } cudaq::getExecutionManager()->apply(name, params, {}, targetInfo, false, - cudaq::spin_op()); + cudaq::spin_op::identity()); }, "Apply the input photonics operation on the target qudits.", py::arg("name"), py::arg("params"), py::arg("targets")); diff --git a/python/runtime/common/py_ExecutionContext.cpp b/python/runtime/common/py_ExecutionContext.cpp index 27294fa8257..3ba168c78ed 100644 --- a/python/runtime/common/py_ExecutionContext.cpp +++ b/python/runtime/common/py_ExecutionContext.cpp @@ -32,8 +32,11 @@ void bindExecutionContext(py::module &mod) { &cudaq::ExecutionContext::numberTrajectories) .def_readwrite("explicitMeasurements", &cudaq::ExecutionContext::explicitMeasurements) - .def("setSpinOperator", [](cudaq::ExecutionContext &ctx, - cudaq::spin_op &spin) { ctx.spin = &spin; }) + .def("setSpinOperator", + [](cudaq::ExecutionContext &ctx, cudaq::spin_op &spin) { + ctx.spin = spin; + assert(cudaq::spin_op::canonicalize(spin) == spin); + }) .def("getExpectationValue", [](cudaq::ExecutionContext &ctx) { return ctx.expectationValue; }); mod.def( diff --git a/python/runtime/common/py_ObserveResult.cpp b/python/runtime/common/py_ObserveResult.cpp index 6ac7087c550..92383551f3c 100644 --- a/python/runtime/common/py_ObserveResult.cpp +++ b/python/runtime/common/py_ObserveResult.cpp @@ -20,8 +20,28 @@ cudaq::spin_op to_spin_op(py::object &obj) { return obj.attr("_to_spinop")().cast(); return obj.cast(); } +cudaq::spin_op to_spin_op_term(py::object &obj) { + auto op = cudaq::spin_op::empty(); + if (py::hasattr(obj, "_to_spinop")) + op = obj.attr("_to_spinop")().cast(); + else + op = obj.cast(); + if (op.num_terms() != 1) + throw std::invalid_argument("expecting a spin op with a single term"); + return *op.begin(); +} } // namespace +// FIXME: add proper deprecation warnings to the bindings +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + namespace cudaq { /// @brief Bind the `cudaq::observe_result` and `cudaq::async_observe_result` /// data classes to python as `cudaq.ObserveResult` and @@ -33,6 +53,10 @@ void bindObserveResult(py::module &mod) { "This includes any measurement counts data, as well as the global " "expectation value of the user-defined `spin_operator`.\n") .def(py::init()) + .def(py::init( + [](double exp_val, const spin_op &spin_op, sample_result result) { + return observe_result(exp_val, spin_op, result); + })) .def(py::init( [](double exp_val, py::object spin_op, sample_result result) { return observe_result(exp_val, to_spin_op(spin_op), result); @@ -50,20 +74,16 @@ void bindObserveResult(py::module &mod) { "the `spin_operator` is stored in its own measurement register. " "Each register name corresponds to the string representation of the " "spin term (without any coefficients).\n") - .def("counts", &observe_result::counts, py::arg("sub_term"), - R"#(Given a `sub_term` of the global `spin_operator` that was passed -to :func:`observe`, return its measurement counts. - -Args: - sub_term (:class:`SpinOperator`): An individual sub-term of the - `spin_operator`. - -Returns: - :class:`SampleResult`: The measurement counts data for the individual `sub_term`.)#") + .def( + "counts", + [](observe_result &self, const spin_op_term &sub_term) { + return self.counts(sub_term); + }, + py::arg("sub_term"), "") .def( "counts", [](observe_result &self, py::object sub_term) { - return self.counts(to_spin_op(sub_term)); + return self.counts(to_spin_op_term(sub_term)); }, py::arg("sub_term"), R"#(Given a `sub_term` of the global `spin_operator` that was passed @@ -75,67 +95,47 @@ to :func:`observe`, return its measurement counts. Returns: :class:`SampleResult`: The measurement counts data for the individual `sub_term`.)#") + // FIXME: deprecate + .def( + "counts", + [](observe_result &self, const spin_op &sub_term) { + return self.counts(sub_term); + }, + py::arg("sub_term"), "") .def( "expectation", [](observe_result &self) { return self.expectation(); }, "Return the expectation value of the `spin_operator` that was " "provided in :func:`observe`.") + .def( + "expectation", + [](observe_result &self, const spin_op_term &spin_term) { + return self.expectation(spin_term); + }, + py::arg("sub_term"), "") .def( "expectation", [](observe_result &self, py::object spin_term) { - return self.expectation(to_spin_op(spin_term)); + return self.expectation(to_spin_op_term(spin_term)); }, py::arg("sub_term"), R"#(Return the expectation value of an individual `sub_term` of the global `spin_operator` that was passed to :func:`observe`. Args: - sub_term (:class:`SpinOperator`): An individual sub-term of the + sub_term (:class:`SpinOperatorTerm`): An individual sub-term of the `spin_operator`. Returns: float : The expectation value of the `sub_term` with respect to the :class:`Kernel` that was passed to :func:`observe`.)#") - - .def( - "expectation_z", - [](observe_result &self) { - PyErr_WarnEx(PyExc_DeprecationWarning, - "expectation_z() is deprecated, use expectation() " - "with the same " - "argument structure.", - 1); - return self.expectation(); - }, - R"#(Return the expectation value of the `spin_operator` that was -provided in :func:`observe`. - -Note: - `expectation_z` has been deprecated in favor of `expectation`.)#") + // FIXME: deprecate .def( - "expectation_z", - [](observe_result &self, py::object spin_term) { - PyErr_WarnEx(PyExc_DeprecationWarning, - "expectation_z() is deprecated, use expectation() " - "with the same " - "argument structure.", - 1); - return self.expectation(to_spin_op(spin_term)); + "expectation", + [](observe_result &self, const spin_op &spin_term) { + return self.expectation(spin_term); }, - py::arg("sub_term"), - R"#(Return the expectation value of an individual `sub_term` of the -global `spin_operator` that was passed to :func:`observe`. - -Note: - `expectation_z` has been deprecated in favor of `expectation`. - -Args: - sub_term (:class:`SpinOperator`): An individual sub-term of the - `spin_operator`. - -Returns: - float : The expectation value of the `sub_term` with respect to the - :class:`Kernel` that was passed to :func:`observe`.)#"); + py::arg("sub_term"), ""); py::class_( mod, "AsyncObserveResult", @@ -148,6 +148,12 @@ This kicks off a wait on the current thread until the results are available. See `future `_ for more information on this programming pattern.)#") + .def(py::init([](std::string inJson, spin_op op) { + async_observe_result f(&op); + std::istringstream is(inJson); + is >> f; + return f; + })) .def(py::init([](std::string inJson, py::object op) { auto as_spin_op = to_spin_op(op); async_observe_result f(&as_spin_op); @@ -166,4 +172,11 @@ for more information on this programming pattern.)#") }); } +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } // namespace cudaq diff --git a/python/runtime/common/py_SampleResult.cpp b/python/runtime/common/py_SampleResult.cpp index 5346be0dc6b..c4f414acbbb 100644 --- a/python/runtime/common/py_SampleResult.cpp +++ b/python/runtime/common/py_SampleResult.cpp @@ -148,7 +148,7 @@ experiment. int : The number of times the given bitstring was measured during the experiment.)#") .def("get_marginal_counts", static_cast &, const std::string_view)>( + const std::vector &, const std::string_view) const>( &sample_result::get_marginal), py::arg("marginal_indices"), py::kw_only(), py::arg("register_name") = GlobalRegisterName, diff --git a/python/runtime/cudaq/algorithms/py_observe_async.cpp b/python/runtime/cudaq/algorithms/py_observe_async.cpp index d3fcbb07e55..00253c2971c 100644 --- a/python/runtime/cudaq/algorithms/py_observe_async.cpp +++ b/python/runtime/cudaq/algorithms/py_observe_async.cpp @@ -70,7 +70,8 @@ std::tuple isValidObserveKernel(py::object &kernel) { void pyAltLaunchKernel(const std::string &, MlirModule, OpaqueArguments &, const std::vector &); -async_observe_result pyObserveAsync(py::object &kernel, spin_op &spin_operator, +async_observe_result pyObserveAsync(py::object &kernel, + const spin_op &spin_operator, py::args &args, std::size_t qpu_id, int shots) { if (py::hasattr(kernel, "compile")) @@ -142,7 +143,7 @@ observe_result pyObservePar(const PyParType &type, py::object &kernel, "[cudaq::observe warning] distributed observe requested but only 1 " "QPU available. no speedup expected.\n"); return details::distributeComputations( - [&](std::size_t i, spin_op &op) { + [&](std::size_t i, const spin_op &op) { return pyObserveAsync(kernel, op, args, i, shots); }, spin_operator, nQpus); @@ -165,7 +166,7 @@ observe_result pyObservePar(const PyParType &type, py::object &kernel, // Distribute locally, i.e. to the local nodes QPUs auto localRankResult = details::distributeComputations( - [&](std::size_t i, spin_op &op) { + [&](std::size_t i, const spin_op &op) { return pyObserveAsync(kernel, op, args, i, shots); }, localH, nQpus); diff --git a/python/runtime/cudaq/algorithms/py_vqe.cpp b/python/runtime/cudaq/algorithms/py_vqe.cpp index 3c8bcd04b64..ba31292a385 100644 --- a/python/runtime/cudaq/algorithms/py_vqe.cpp +++ b/python/runtime/cudaq/algorithms/py_vqe.cpp @@ -139,7 +139,7 @@ pyVQE_remote_cpp(cudaq::quantum_platform &platform, py::object &kernel, auto [kernelName, kernelMod] = getKernelNameAndModule(kernel); auto ctx = std::make_unique("observe", /*shots=*/0); ctx->kernelName = kernelName; - ctx->spin = &hamiltonian; + ctx->spin = cudaq::spin_op::canonicalize(hamiltonian); platform.set_exec_ctx(ctx.get()); constexpr std::size_t startingArgIdx = 1; @@ -165,8 +165,8 @@ pyVQE_remote_cpp(cudaq::quantum_platform &platform, py::object &kernel, std::vector names; auto *wrapper = new cudaq::ArgWrapper{unwrap(kernelMod), names, kernelArgs}; - platform.launchVQE(kernelName, wrapper, gradient, hamiltonian, optimizer, - n_params, shots); + platform.launchVQE(kernelName, wrapper, gradient, ctx->spin.value(), + optimizer, n_params, shots); platform.reset_exec_ctx(); delete wrapper; if (kernelArgs) @@ -210,7 +210,9 @@ pyVQE_remote(cudaq::quantum_platform &platform, py::object &kernel, VAR_NAME.get_type().attr("__name__").cast())] = \ json.attr("loads")(VAR_NAME.attr("to_json")()); \ } while (0) - LOAD_VAR(hamiltonian); + + auto spin = cudaq::spin_op::canonicalize(hamiltonian); + LOAD_VAR(spin); LOAD_VAR(optimizer); LOAD_VAR_NO_CAST(kernel); if (gradient) @@ -236,7 +238,7 @@ pyVQE_remote(cudaq::quantum_platform &platform, py::object &kernel, os << "kernel=__kernel, "; if (gradient) os << "gradient_strategy=__gradient, "; - os << "spin_operator=__hamiltonian, "; + os << "spin_operator=__spin, "; os << "optimizer=__optimizer, "; os << "parameter_count=" << n_params << ", "; if (argumentMapper) diff --git a/python/runtime/cudaq/domains/plugins/CMakeLists.txt b/python/runtime/cudaq/domains/plugins/CMakeLists.txt index 9e98ceb76f9..89b9a66b868 100644 --- a/python/runtime/cudaq/domains/plugins/CMakeLists.txt +++ b/python/runtime/cudaq/domains/plugins/CMakeLists.txt @@ -11,12 +11,12 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/plugins) SET(CMAKE_INSTALL_RPATH "$ORIGIN:$ORIGIN/..") add_library(cudaq-pyscf SHARED PySCFDriver.cpp) if (SKBUILD) - target_link_libraries(cudaq-pyscf PRIVATE pybind11::pybind11 Python::Module cudaq-chemistry cudaq-spin cudaq cudaq-py-utils) + target_link_libraries(cudaq-pyscf PRIVATE pybind11::pybind11 Python::Module cudaq-chemistry cudaq-operator cudaq cudaq-py-utils) target_link_options(cudaq-pyscf PRIVATE -Wl,--unresolved-symbols=ignore-in-object-files) else() if (NOT Python_FOUND) message(FATAL_ERROR "find_package(Python) not run?") endif() - target_link_libraries(cudaq-pyscf PRIVATE Python::Python pybind11::pybind11 cudaq-chemistry cudaq-spin cudaq cudaq-py-utils) + target_link_libraries(cudaq-pyscf PRIVATE Python::Python pybind11::pybind11 cudaq-chemistry cudaq-operator cudaq cudaq-py-utils) endif() install(TARGETS cudaq-pyscf DESTINATION lib/plugins) diff --git a/python/runtime/cudaq/domains/plugins/PySCFDriver.cpp b/python/runtime/cudaq/domains/plugins/PySCFDriver.cpp index 05ee94ba45c..976ea18ca94 100644 --- a/python/runtime/cudaq/domains/plugins/PySCFDriver.cpp +++ b/python/runtime/cudaq/domains/plugins/PySCFDriver.cpp @@ -24,22 +24,21 @@ spin_op fromOpenFermionQubitOperator(const py::object &op) { if (!py::hasattr(op, "terms")) throw std::runtime_error( "This is not an openfermion operator, must have 'terms' attribute."); - std::map> creatorMap{ - {"X", [](std::size_t i) { return spin::x(i); }}, - {"Y", [](std::size_t i) { return spin::y(i); }}, - {"Z", [](std::size_t i) { return spin::z(i); }}}; + std::map> creatorMap{ + {"X", [](std::size_t i) { return spin_op::x(i); }}, + {"Y", [](std::size_t i) { return spin_op::y(i); }}, + {"Z", [](std::size_t i) { return spin_op::z(i); }}}; auto terms = op.attr("terms"); - spin_op H; + auto H = spin_op::empty(); for (auto term : terms) { auto termTuple = term.cast(); - spin_op localTerm; + auto localTerm = spin_op::identity(); for (auto &element : termTuple) { auto casted = element.cast>(); localTerm *= creatorMap[casted.second](casted.first); } H += terms[term].cast() * localTerm; } - H -= spin::i(H.num_qubits() - 1); return H; } diff --git a/python/runtime/cudaq/qis/py_execution_manager.cpp b/python/runtime/cudaq/qis/py_execution_manager.cpp index 4d35bdf0ae7..f71c6743297 100644 --- a/python/runtime/cudaq/qis/py_execution_manager.cpp +++ b/python/runtime/cudaq/qis/py_execution_manager.cpp @@ -21,7 +21,7 @@ void bindExecutionManager(py::module &mod) { "applyQuantumOperation", [](const std::string &name, std::vector ¶ms, std::vector &controls, std::vector &targets, - bool isAdjoint, cudaq::spin_op &op) { + bool isAdjoint, cudaq::spin_op_term &op) { std::vector c, t; std::transform(controls.begin(), controls.end(), std::back_inserter(c), [](auto &&el) { return cudaq::QuditInfo(2, el); }); @@ -31,7 +31,7 @@ void bindExecutionManager(py::module &mod) { }, py::arg("name"), py::arg("params"), py::arg("controls"), py::arg("targets"), py::arg("isAdjoint") = false, - py::arg("op") = cudaq::spin_op()); + py::arg("op") = cudaq::spin_op::identity()); mod.def("startAdjointRegion", []() { cudaq::getExecutionManager()->startAdjointRegion(); }); diff --git a/python/runtime/cudaq/spin/py_spin_op.cpp b/python/runtime/cudaq/spin/py_spin_op.cpp index d2396b48e05..3679f97b1e6 100644 --- a/python/runtime/cudaq/spin/py_spin_op.cpp +++ b/python/runtime/cudaq/spin/py_spin_op.cpp @@ -5,11 +5,13 @@ * 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 "cudaq/spin_op.h" +#include "cudaq/operators.h" +#include "cudaq/operators/serialization.h" #include "py_spin_op.h" #include @@ -21,41 +23,59 @@ spin_op fromOpenFermionQubitOperator(py::object &op) { if (!py::hasattr(op, "terms")) throw std::runtime_error( "This is not an openfermion operator, must have 'terms' attribute."); - std::map> creatorMap{ - {"X", [](std::size_t i) { return spin::x(i); }}, - {"Y", [](std::size_t i) { return spin::y(i); }}, - {"Z", [](std::size_t i) { return spin::z(i); }}}; + std::map> creatorMap{ + {"X", [](std::size_t i) { return spin_op::x(i); }}, + {"Y", [](std::size_t i) { return spin_op::y(i); }}, + {"Z", [](std::size_t i) { return spin_op::z(i); }}}; auto terms = op.attr("terms"); - spin_op H; + auto H = spin_op::empty(); for (auto term : terms) { auto termTuple = term.cast(); - spin_op localTerm; + auto localTerm = spin_op::identity(); for (auto &element : termTuple) { auto casted = element.cast>(); localTerm *= creatorMap[casted.second](casted.first); } H += terms[term].cast() * localTerm; } - H -= spin::i(H.num_qubits() - 1); return H; } +// FIXME: add proper deprecation warnings to the bindings +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + void bindSpinClass(py::module &mod) { // Binding the `cudaq::spin` class to `_pycudaq` as a submodule // so it's accessible directly in the cudaq namespace. auto spin_submodule = mod.def_submodule("spin"); - spin_submodule.def("i", &cudaq::spin::i, py::arg("target"), + spin_submodule.def("i", &cudaq::spin_op::i, + py::arg("target"), "Return an identity :class:`SpinOperator` on the given " "target qubit index."); spin_submodule.def( - "x", &cudaq::spin::x, py::arg("target"), + "x", &cudaq::spin_op::x, py::arg("target"), "Return an X :class:`SpinOperator` on the given target qubit index."); spin_submodule.def( - "y", &cudaq::spin::y, py::arg("target"), + "y", &cudaq::spin_op::y, py::arg("target"), "Return a Y :class:`SpinOperator` on the given target qubit index."); spin_submodule.def( - "z", &cudaq::spin::z, py::arg("target"), + "z", &cudaq::spin_op::z, py::arg("target"), "Return a Z :class:`SpinOperator` on the given target qubit index."); + spin_submodule.def("plus", &cudaq::spin_op::plus, + py::arg("target"), + "Return a sigma plus :class:`SpinOperator` on the given " + "target qubit index."); + spin_submodule.def("minus", &cudaq::spin_op::minus, + py::arg("target"), + "Return a sigma minus :class:`SpinOperator` on the given " + "target qubit index."); } void bindSpinOperator(py::module &mod) { @@ -66,15 +86,232 @@ void bindSpinOperator(py::module &mod) { .value("Z", pauli::Z) .value("I", pauli::I); + py::class_(mod, "SpinOperatorTerm") + .def(py::init([]() { return cudaq::spin_op::identity(); })) + .def(py::init(), py::arg("spin_operator"), + "Copy constructor, given another :class:`SpinOperatorTerm`.") + + /// @brief Bind the member functions. + + .def("get_ops_count", &cudaq::spin_op_term::num_ops, + "Return the number of terms in this :class:`SpinOperator`.") + .def( + "get_coefficient", + [](cudaq::spin_op_term &op) { return op.evaluate_coefficient(); }, + "Return the coefficient of this :class:`SpinOperatorTerm`.") + .def("is_identity", &cudaq::spin_op_term::is_identity, + "Returns a bool indicating if this :class:`SpinOperatorTerm` is " + "equal " + "to the " + "identity.") + // FIXME: deprecate + .def( + "to_string", + [](cudaq::spin_op_term &op, bool print_coefficient) { + return op.to_string(print_coefficient); + }, + py::arg("print_coefficient") = true, + "Return a string representation of this :class:`SpinOperatorTerm`.") + .def( + "__str__", [](cudaq::spin_op_term &op) { return op.to_string(); }, + "Return a string representation of this :class:`SpinOperatorTerm`.") + .def("dump", &cudaq::spin_op_term::dump, + "Print a string representation of this :class:`SpinOperatorTerm`.") + .def( + "canonicalize", + [](cudaq::spin_op_term &op) { return op.canonicalize(); }, + "Removes all identity operators from the operator.") + .def("get_term_id", &cudaq::spin_op_term::get_term_id, + "Gets the id with which counts and expectation values for this term " + "can be retrieved.") + .def( + "get_pauli_word", + [](cudaq::spin_op_term &op, std::size_t pad_identities) { + return op.get_pauli_word(pad_identities); + }, + py::arg("pad_identities") = 0, + "Gets the Pauli word representation of this " + ":class:`SpinOperatorTerm`.") + .def( + "get_binary_symplectic_form", + &cudaq::spin_op_term::get_binary_symplectic_form, + "Gets the binary symplectic representation of this " + ":class:`SpinOperatorTerm`.") + .def( + "to_matrix", + [](spin_op_term &self) { + return self.to_matrix(); // can't bind function ref since it has + // additional (optional) args + }, + "Return `self` as a :class:`ComplexMatrix`.") + // iteration over terms is not bound here since we didn't bind the + // handlers + .def("__eq__", &cudaq::spin_op_term::operator==, py::arg("other"), + "Return true if the two :class:`SpinOperatorTerm`'s are equal. " + "Equality " + "does " + "not consider the coefficients.") + // not sure if we should consider a sum_op and a product_op equal if the + // content matches... + .def( + "__eq__", + [](spin_op_term &self, spin_op other) { + return spin_op(self) == other; + }, + py::arg("other"), "Return true if the two operators are equal.") + + // FIXME: deprecate these + // The functions below are supported on SpinOperatorTerm for backwards + // compatiblity, but are only supported by spin_op in C++ (and also only + // for backward compatiblity). + .def( + "get_raw_data", + [](cudaq::spin_op_term &op) { + return cudaq::spin_op(op).get_raw_data(); + }, + "Return the raw data of this :class:`SpinOperatorTerm`.") + .def( + "to_json", + [](const cudaq::spin_op_term &p) { + cudaq::spin_op op(p); + py::object json = py::module_::import("json"); + auto data = op.get_data_representation(); + return json.attr("dumps")(data); + }, + "Convert spin_op to JSON string: '[d1, d2, d3, ...]'") + .def( + "get_qubit_count", + [](cudaq::spin_op_term &op) { + return cudaq::spin_op(op).num_qubits(); + }, + "Return the number of qubits this :class:`SpinOperatorTerm` acts on.") + .def( + "get_term_count", [](cudaq::spin_op_term &op) { return 1; }, + "Return the number of terms (always 1).") + .def( + "for_each_pauli", + [](spin_op_term &self, py::function functor) { + return cudaq::spin_op(self).for_each_pauli(functor); + }, + py::arg("function"), + "For a single :class:`SpinOperator` term, apply the given function " + "to each pauli element in the term. The function must have " + "`void(pauli, int)` signature where `pauli` is the Pauli matrix " + "type and the `int` is the qubit index.") + .def( + "distribute_terms", + [](cudaq::spin_op_term &op, std::size_t chunks) { + return cudaq::spin_op(op).distribute_terms(chunks); + }, + py::arg("chunk_count"), + "Return a list of :class:`SpinOperator` representing a distribution " + "of the " + "terms in this :class:`SpinOperator` into `chunk_count` sized " + "chunks.") + + /// @brief Arithmetic operators between different data types + + .def( + "__add__", + [](spin_op_term &self, spin_op other) { return self + other; }, + py::arg("other"), + "Adds a :class:`SpinOperatorTerm` and a :class:`SpinOperator`.") + .def( + "__sub__", + [](spin_op_term &self, spin_op other) { return self - other; }, + py::arg("other"), + "Subtracts a :class:`SpinOperatorTerm` and a :class:`SpinOperator`.") + .def( + "__mul__", + [](spin_op_term &self, spin_op other) { return self * other; }, + py::arg("other"), + "Multiplies a :class:`SpinOperatorTerm` and a :class:`SpinOperator`.") + + /// @brief Bind overloaded operators that are in-place on + /// :class:`SpinOperatorTerm`. + + // `this_spin_op_term` *= :class:`SpinOperatorTerm` + .def(py::self *= py::self, py::arg("other"), + "Multiply the given :class:`SpinOperatorTerm` with this one and " + "return " + "*this.") + // `this_spin_op_term` *= `float` + .def(py::self *= float(), py::arg("other"), + "Multiply the :class:`SpinOperatorTerm` by the given " + "float value and return *this.") + // `this_spin_op_term` *= `double` + .def(py::self *= double(), py::arg("other"), + "Multiply the :class:`SpinOperatorTerm` by the given " + "double value and return *this.") + // `this_spin_op_term` *= `complex` + .def(py::self *= std::complex(), py::arg("other"), + "Multiply the :class:`SpinOperatorTerm` by the given complex value " + "and " + "return " + "*this.") + + /// @brief Bind overloaded operators that return a new + /// :class:`SpinOperatorTerm`. + + // :class:`SpinOperatorTerm` + :class:`SpinOperatorTerm` + .def(py::self + py::self, py::arg("other"), + "Add the given :class:`SpinOperatorTerm` to this one and " + "return result as a new :class:`SpinOperator`.") + // :class:`SpinOperatorTerm` + `double` + .def(py::self + double(), py::arg("other"), + "Add a double to the given :class:`SpinOperatorTerm` and " + "return result as a new :class:`SpinOperator`.") + // `double` + :class:`SpinOperatorTerm` + .def(double() + py::self, py::arg("other"), + "Add a :class:`SpinOperatorTerm` to the given double and " + "return result as a new :class:`SpinOperator`.") + // :class:`SpinOperatorTerm` - :class:`SpinOperatorTerm` + .def(py::self - py::self, py::arg("other"), + "Subtract the given :class:`SpinOperatorTerm` from this one " + "and return result as a new :class:`SpinOperator`.") + // :class:`SpinOperatorTerm` - `double` + .def(py::self - double(), py::arg("other"), + "Subtract a double from the given :class:`SpinOperatorTerm` " + "and return result as a new :class:`SpinOperator`.") + // `double` - :class:`SpinOperatorTerm` + .def(double() - py::self, py::arg("other"), + "Subtract a :class:`SpinOperatorTerm` from the given double " + "and return result as a new :class:`SpinOperator`.") + // :class:`SpinOperatorTerm` * :class:`SpinOperatorTerm` + .def(py::self * py::self, py::arg("other"), + "Multiply the given :class:`SpinOperatorTerm`'s together " + "and return result as a new :class:`SpinOperatorTerm`.") + // :class:`SpinOperatorTerm` * `double` + .def(py::self * double(), py::arg("other"), + "Multiply the :class:`SpinOperatorTerm` by the given double " + "and return result as a new :class:`SpinOperatorTerm`.") + // `double` * :class:`SpinOperatorTerm` + .def(double() * py::self, py::arg("other"), + "Multiply the double by the given :class:`SpinOperatorTerm` " + "and return result as a new :class:`SpinOperatorTerm`.") + // :class:`SpinOperatorTerm` * `complex` + .def(py::self * std::complex(), py::arg("other"), + "Multiply the :class:`SpinOperatorTerm` by the given complex value " + "and " + "return " + "result as a new :class:`SpinOperatorTerm`.") + // `complex` * :class:`SpinOperatorTerm` + .def(std::complex() * py::self, py::arg("other"), + "Multiply the complex value by the given :class:`SpinOperatorTerm` " + "and " + "return " + "result as a new :class:`SpinOperatorTerm`."); + py::class_(mod, "SpinOperator") - /// @brief Bind the constructors. - .def(py::init<>(), "Empty constructor, creates the identity term.") - .def_static("empty_op", - []() { - return cudaq::spin_op( - std::unordered_map>{}); - }) + // FIXME: deprecate this one + .def(py::init([]() { return cudaq::spin_op::identity(); }), + "Empty constructor, creates the identity term.") + .def(py::init([](std::size_t size) { return cudaq::spin_op(size); }), + "Empty constructor, creates a sum operator with no terms, reserving " + "memory for the given number of terms.") + // FIXME: deprecate name + .def_static("empty_op", &cudaq::spin_op::empty) + .def_static("empty", &cudaq::spin_op::empty) .def(py::init([](std::string fileName) { cudaq::binary_spin_op_reader reader; return reader.read(fileName); @@ -82,9 +319,10 @@ void bindSpinOperator(py::module &mod) { "Read in :class:`SpinOperator` from file.") .def(py::init(), py::arg("spin_operator"), "Copy constructor, given another :class:`SpinOperator`.") - .def(py::init( - [](py::object o) { return fromOpenFermionQubitOperator(o); }), - "Create from OpenFermion QubitOperator.") + .def(py::init(), py::arg("spin_operator"), + "Constructor given a :class:`SpinOperatorTerm`.") + .def(py::init &>(), py::arg("data"), + "Construct a :class:`SpinOperator` from a list of numeric values.") .def(py::init &, std::size_t>(), py::arg("data"), py::arg("num_qubits"), "Construct a :class:`SpinOperator` from a list of numeric values. " @@ -93,46 +331,59 @@ void bindSpinOperator(py::module &mod) { "`Z` on qubit `i`, followed by the real and imaginary part of the " "coefficient. Each set of term elements is appended to the one " "list. The list is ended with the total number of terms.") + .def(py::init( + [](py::object o) { return fromOpenFermionQubitOperator(o); }), + "Create from OpenFermion QubitOperator.") + /* .def(py::init(), py::arg("num_qubits"), "Construct the identity term on the given number of qubits.") + */ .def( "to_json", [](const cudaq::spin_op &p) { py::object json = py::module_::import("json"); - auto cpp_tup = p.getDataTuple(); - py::tuple py_tup = - py::make_tuple(std::get<0>(cpp_tup), std::get<1>(cpp_tup)); - return json.attr("dumps")(py_tup); + auto data = p.get_data_representation(); + return json.attr("dumps")(data); }, - "Convert spin_op to JSON string: '[[d1, d2, d3, ...], numQubits]'") + "Convert spin_op to JSON string: '[d1, d2, d3, ...]]'") .def_static( "from_json", [](const std::string &j) { py::object json = py::module_::import("json"); - auto list = py::list(json.attr("loads")(j)); - if (py::len(list) != 2) - throw std::runtime_error("Invalid JSON string for spin_op"); - - cudaq::spin_op p(list[0].cast>(), - list[1].cast()); + auto data = py::list(json.attr("loads")(j)); + cudaq::spin_op p(data.cast>()); return p; }, "Convert JSON string ('[[d1, d2, d3, ...], numQubits]') to spin_op") /// @brief Bind the member functions. - .def("get_raw_data", &cudaq::spin_op::get_raw_data, + + // FIXME: deprecate + .def("get_raw_data", &cudaq::spin_op::get_raw_data, "Return the raw data of this :class:`SpinOperator`.") .def("get_term_count", &cudaq::spin_op::num_terms, "Return the number of terms in this :class:`SpinOperator`.") - .def("get_qubit_count", &cudaq::spin_op::num_qubits, + .def("get_qubit_count", &cudaq::spin_op::num_qubits, "Return the number of qubits this :class:`SpinOperator` is on.") - .def("get_coefficient", &cudaq::spin_op::get_coefficient, - "Return the coefficient of this :class:`SpinOperator`. Must be a " - "`SpinOperator` with one term, otherwise an exception is thrown.") - .def("is_identity", &cudaq::spin_op::is_identity, + // FIXME: deprecate + .def( + "get_coefficient", + [](cudaq::spin_op &op) { + if (op.num_terms() == 0) + return std::complex(0.); + if (op.num_terms() != 1) + throw std::runtime_error( + "expecting a spin op with at most one term"); + return op.begin()->get_coefficient().evaluate(); + }, + "Return the coefficient of this :class:`SpinOperator`. Must be a " + "`SpinOperator` with one term, otherwise an exception is thrown.") + // FIXME: deprecate + .def("is_identity", &cudaq::spin_op::is_identity, "Returns a bool indicating if this :class:`SpinOperator` is equal " "to the " "identity.") + // FIXME: deprecate .def( "to_string", [](cudaq::spin_op &op, bool print_coefficient) { @@ -145,21 +396,25 @@ void bindSpinOperator(py::module &mod) { "Return a string representation of this :class:`SpinOperator`.") .def("dump", &cudaq::spin_op::dump, "Print a string representation of this :class:`SpinOperator`.") + .def( + "canonicalize", [](cudaq::spin_op &op) { return op.canonicalize(); }, + "Removes all identity operators from the operator.") .def("distribute_terms", &cudaq::spin_op::distribute_terms, py::arg("chunk_count"), "Return a list of :class:`SpinOperator` representing a distribution " "of the " "terms in this :class:`SpinOperator` into `chunk_count` sized " "chunks.") - .def_static("random", &cudaq::spin_op::random, py::arg("qubit_count"), - py::arg("term_count"), + .def_static("random", &cudaq::spin_op::random, + py::arg("qubit_count"), py::arg("term_count"), py::arg("seed") = std::random_device{}(), "Return a random :class:`SpinOperator` on the given number " "of qubits (`qubit_count`) and " "composed of the given number of terms (`term_count`). An " "optional seed value may also be provided.") .def_static( - "from_word", &cudaq::spin_op::from_word, py::arg("word"), + "from_word", &cudaq::spin_op::from_word, + py::arg("word"), R"#(Return a :class:`SpinOperator` corresponding to the provided Pauli `word`. .. code-block:: python @@ -190,7 +445,7 @@ void bindSpinOperator(py::module &mod) { "to each pauli element in the term. The function must have " "`void(pauli, int)` signature where `pauli` is the Pauli matrix " "type and the `int` is the qubit index.") - .def("serialize", &spin_op::getDataRepresentation, + .def("serialize", &spin_op::get_data_representation, "Return a serialized representation of the :class:`SpinOperator`. " "Specifically, this encoding is via a vector of doubles. The " "encoding is as follows: for each term, a list of doubles where the " @@ -200,9 +455,14 @@ void bindSpinOperator(py::module &mod) { "Each term is appended to the array forming one large 1d array of " "doubles. The array is ended with the total number of terms " "represented as a double.") - .def("to_matrix", &spin_op::to_matrix, - "Return `self` as a :class:`ComplexMatrix`.") - .def("to_sparse_matrix", &spin_op::to_sparse_matrix, + .def( + "to_matrix", + [](spin_op &self) { + return self.to_matrix(); // can't bind function ref since it has + // additional (optional) args + }, + "Return `self` as a :class:`ComplexMatrix`.") + .def("to_sparse_matrix", &spin_op::to_sparse_matrix, "Return `self` as a sparse matrix. This representation is a " "`Tuple[list[complex], list[int], list[int]]`, encoding the " "non-zero values, rows, and columns of the matrix. " @@ -214,8 +474,57 @@ void bindSpinOperator(py::module &mod) { }, py::keep_alive<0, 1>(), "Loop through each term of this :class:`SpinOperator`.") + // :class:`SpinOperator` == :class:`SpinOperator` + .def("__eq__", &cudaq::spin_op::operator==, py::arg("other"), + "Return true if the two :class:`SpinOperator`'s are equal.") + // not sure if we should consider a sum_op and a product_op equal if the + // content matches... + .def( + "__eq__", + [](spin_op &self, spin_op_term other) { + return self == spin_op(other); + }, + py::arg("other"), "Return true if the two operators are equal.") + + /// @brief Arithmetic operators between different data types + + .def( + "__add__", + [](spin_op &self, spin_op_term other) { return self + other; }, + py::arg("other"), + "Adds a :class:`SpinOperator` and a :class:`SpinOperatorTerm`.") + .def( + "__iadd__", + [](spin_op &self, spin_op_term other) { return self += other; }, + py::arg("other"), + "Adds a :class:`SpinOperator` and a :class:`SpinOperatorTerm` and " + "assigns it to self.") + .def( + "__sub__", + [](spin_op &self, spin_op_term other) { return self - other; }, + py::arg("other"), + "Subtracts a :class:`SpinOperator` and a :class:`SpinOperatorTerm`.") + .def( + "__isub__", + [](spin_op &self, spin_op_term other) { return self -= other; }, + py::arg("other"), + "Subtracts a :class:`SpinOperator` and a :class:`SpinOperatorTerm` " + "and assigns it to self.") + .def( + "__mul__", + [](spin_op &self, spin_op_term other) { return self * other; }, + py::arg("other"), + "Multiplies a :class:`SpinOperator` and a :class:`SpinOperatorTerm`.") + .def( + "__imul__", + [](spin_op &self, spin_op_term other) { return self *= other; }, + py::arg("other"), + "Multiplies a :class:`SpinOperator` and a :class:`SpinOperatorTerm` " + "and assigns it to self.") + /// @brief Bind overloaded operators that are in-place on /// :class:`SpinOperator`. + // `this_spin_op` += :class:`SpinOperator` .def(py::self += py::self, "Add the given :class:`SpinOperator` to this one and return *this.") @@ -246,11 +555,6 @@ void bindSpinOperator(py::module &mod) { "Multiply the :class:`SpinOperator` by the given complex value and " "return " "*this.") - // :class:`SpinOperator` == :class:`SpinOperator` - .def("__eq__", &cudaq::spin_op::operator==, py::arg("other"), - "Return true if the two :class:`SpinOperator`'s are equal. Equality " - "does " - "not consider the coefficients.") /// @brief Bind overloaded operators that return a new /// :class:`SpinOperator`. @@ -303,9 +607,26 @@ void bindSpinOperator(py::module &mod) { "result as a new :class:`SpinOperator`."); } +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + void bindSpinWrapper(py::module &mod) { bindSpinClass(mod); bindSpinOperator(mod); + // If the spin op in the execution context is a pointer + // rather than an actual copy, then we may run into trouble + // that the pointer points to a temporary object due to + // spin_op_term -> spin_op automatic conversion. + // I am not sure why I didn't see a similar issue in C++, + // but I am concerned it might be there as well. + // I hence decided to have the context own it's spin - + // I *think* is should only be one additional copy and seems + // less likely to have any hidden issues. + py::implicitly_convertible(); } } // namespace cudaq diff --git a/python/runtime/utils/PyRemoteSimulatorQPU.cpp b/python/runtime/utils/PyRemoteSimulatorQPU.cpp index f2ffc47202e..4724979d06b 100644 --- a/python/runtime/utils/PyRemoteSimulatorQPU.cpp +++ b/python/runtime/utils/PyRemoteSimulatorQPU.cpp @@ -20,7 +20,7 @@ static void launchVqeImpl(cudaq::ExecutionContext *executionContextPtr, std::unique_ptr &m_client, const std::string &m_simName, const std::string &name, const void *kernelArgs, cudaq::gradient *gradient, - cudaq::spin_op &H, cudaq::optimizer &optimizer, + const cudaq::spin_op &H, cudaq::optimizer &optimizer, const int n_params, const std::size_t shots) { auto *wrapper = reinterpret_cast(kernelArgs); auto m_module = wrapper->mod; @@ -31,7 +31,7 @@ static void launchVqeImpl(cudaq::ExecutionContext *executionContextPtr, auto ctx = std::make_unique("observe", shots); ctx->kernelName = name; - ctx->spin = &H; + ctx->spin = cudaq::spin_op::canonicalize(H); if (shots > 0) ctx->shots = shots; @@ -119,7 +119,7 @@ class PyRemoteSimulatorQPU : public cudaq::BaseRemoteSimulatorQPU { virtual bool isEmulated() override { return true; } void launchVQE(const std::string &name, const void *kernelArgs, - cudaq::gradient *gradient, cudaq::spin_op H, + cudaq::gradient *gradient, const cudaq::spin_op &H, cudaq::optimizer &optimizer, const int n_params, const std::size_t shots) override { cudaq::info( @@ -170,7 +170,7 @@ class PyNvcfSimulatorQPU : public cudaq::BaseNvcfSimulatorQPU { virtual bool isEmulated() override { return true; } void launchVQE(const std::string &name, const void *kernelArgs, - cudaq::gradient *gradient, cudaq::spin_op H, + cudaq::gradient *gradient, const cudaq::spin_op &H, cudaq::optimizer &optimizer, const int n_params, const std::size_t shots) override { cudaq::info( diff --git a/python/tests/builder/test_SpinOperator.py b/python/tests/builder/test_SpinOperator.py index 8cb06a9e463..d3e9d6aa8df 100644 --- a/python/tests/builder/test_SpinOperator.py +++ b/python/tests/builder/test_SpinOperator.py @@ -58,8 +58,8 @@ def test_spin_op_operators(): that we can verify against. We are not fully testing the accuracy of each individual operator at the moment. """ - # Test the empty (identity) constructor. - spin_a = cudaq.SpinOperator() + # Test the empty constructor. + spin_a = cudaq.SpinOperator.empty() spin_b = spin.x(0) # Test the copy constructor. spin_b_copy = cudaq.SpinOperator(spin_operator=spin_b) @@ -109,7 +109,9 @@ def test_spin_op_operators(): spin_p = 3.0 - spin_a data, coeffs = spin_a.get_raw_data() - assert (len(data) == 3) + # this was 3 due to the (incorrect) identity that the default constructor used to create + # same goes for all other len check adjustments in this test + assert (len(data) == 2) assert (len(data[0]) == 6) expected = [[0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1]] assert (all([d in expected for d in data])) @@ -141,7 +143,7 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_f.get_raw_data() - assert (len(data) == 4) + assert (len(data) == 3) assert (len(data[0]) == 6) expected = [[0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1], [1, 0, 0, 0, 0, 0]] @@ -150,7 +152,7 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_g.get_raw_data() - assert (len(data) == 4) + assert (len(data) == 3) assert (len(data[0]) == 6) expected = [[0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1], [1, 0, 0, 0, 0, 0]] @@ -159,7 +161,7 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_h.get_raw_data() - assert (len(data) == 3) + assert (len(data) == 2) assert (len(data[0]) == 6) expected = [[1, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [1, 1, 0, 0, 1, 1]] assert (all([d in expected for d in data])) @@ -167,7 +169,7 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_i.get_raw_data() - assert (len(data) == 3) + assert (len(data) == 2) assert (len(data[0]) == 6) expected = [[1, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1]] assert (all([d in expected for d in data])) @@ -175,7 +177,7 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_j.get_raw_data() - assert (len(data) == 3) + assert (len(data) == 2) assert (len(data[0]) == 6) expected = [[1, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1]] assert (all([d in expected for d in data])) @@ -183,7 +185,7 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_k.get_raw_data() - assert (len(data) == 3) + assert (len(data) == 2) assert (len(data[0]) == 6) expected = [[1, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1]] assert (all([d in expected for d in data])) @@ -191,7 +193,7 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_l.get_raw_data() - assert (len(data) == 3) + assert (len(data) == 2) assert (len(data[0]) == 6) expected = [[1, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1]] assert (all([d in expected for d in data])) @@ -199,7 +201,16 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_m.get_raw_data() - assert (len(data) == 4) + assert (len(data) == 3) + assert (len(data[0]) == 6) + expected = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], + [0, 1, 0, 0, 1, 1]] + assert (all([d in expected for d in data])) + expected = [3, 5 + 5j, 5 + 5j, -5 - 5j] + assert (all([c in expected for c in coeffs])) + + data, coeffs = spin_n.get_raw_data() + assert (len(data) == 3) assert (len(data[0]) == 6) expected = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1]] @@ -208,7 +219,7 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_o.get_raw_data() - assert (len(data) == 4) + assert (len(data) == 3) assert (len(data[0]) == 6) expected = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1]] @@ -217,7 +228,7 @@ def test_spin_op_operators(): assert (all([c in expected for c in coeffs])) data, coeffs = spin_p.get_raw_data() - assert (len(data) == 4) + assert (len(data) == 3) assert (len(data[0]) == 6) expected = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [0, 1, 0, 0, 1, 1]] @@ -230,22 +241,21 @@ def test_spin_op_members(): """ Test all of the bound member functions on the `cudaq.SpinOperator` class. """ - spin_operator = cudaq.SpinOperator() - # Assert that it's the identity. + spin_operator = cudaq.SpinOperator.empty() + # (is_identity on sum is deprecated, kept for backwards compatibility) assert spin_operator.is_identity() - # Only have 1 term and 1 qubit. + # Sum is empty. + assert spin_operator.get_term_count() == 0 + assert spin_operator.get_qubit_count() == 0 + spin_operator += -1.0 * spin.x(1) + # Should now have 1 term acting on 1 qubit. assert spin_operator.get_term_count() == 1 assert spin_operator.get_qubit_count() == 1 - spin_operator += -1.0 * spin.x(1) - # Should now have 2 terms and 2 qubits. - assert spin_operator.get_term_count() == 2 - assert spin_operator.get_qubit_count() == 2 # No longer identity. assert not spin_operator.is_identity() - for term in spin_operator: - # Second term should have a coefficient of -1.0 - assert term.get_coefficient() == -1.0 or term.get_coefficient() == 1.0 - assert term.get_coefficient() == -1.0 or term.get_coefficient() == 1.0 + # Term should have a coefficient -1 + term, *_ = spin_operator + assert term.get_coefficient() == -1.0 def test_spin_op_vqe(): @@ -333,7 +343,8 @@ def test_spin_op_iter(): count += 1 assert count == 5 - +# FIXME +@pytest.mark.skip("to sparse matrix not yet supported") def test_spin_op_sparse_matrix(): """ Test that the `cudaq.SpinOperator` can produce its sparse matrix representation @@ -373,17 +384,18 @@ def test_spin_op_from_word(): # Test serialization and deserialization for all term/qubit combinations up to # 30 qubits -def test_spin_op_serdes(): +def test_spin_op_serialization(): for nq in range(1, 31): for nt in range(1, nq + 1): + # random will product terms that each act on all + # qubits in the range [0, nq) h1 = cudaq.SpinOperator.random(qubit_count=nq, term_count=nt, seed=13) h2 = h1.serialize() - h3 = cudaq.SpinOperator(h2, nq) + h3 = cudaq.SpinOperator(h2) assert (h1 == h3) - def test_spin_op_random(): # Make sure that the user gets all the random terms that they ask for. qubit_count = 5 diff --git a/python/tests/builder/test_observe.py b/python/tests/builder/test_observe.py index feba6dfdab0..22b0ed93395 100644 --- a/python/tests/builder/test_observe.py +++ b/python/tests/builder/test_observe.py @@ -46,7 +46,7 @@ def test_observe_result(): for index, sub_term in enumerate(hamiltonian): print(sub_term) # Extract the register name from the spin term. - name = str(sub_term).split(" ")[1].rstrip() + name = sub_term.get_term_id() # Does the register exist in the measurement results? assert name in register_names # Check `cudaq.ObserveResult::counts(sub_term)` @@ -93,7 +93,7 @@ def test_observe_no_params(want_state, want_expectation, shots_count): kernel.x(qubit) # Measuring in the Z-basis. - hamiltonian = spin.z(0) + hamiltonian = cudaq.SpinOperator(spin.z(0)) # test is written assuming this is a sum # Call `cudaq.observe()` at the specified number of shots. observe_result = cudaq.observe(kernel=kernel, @@ -111,7 +111,7 @@ def test_observe_no_params(want_state, want_expectation, shots_count): # Check that each register is in the proper state. for index, sub_term in enumerate(hamiltonian): # Extract the register name from the spin term. - got_name = str(sub_term).split(" ")[1].rstrip() + got_name = sub_term.get_term_id() # Pull the counts for that hamiltonian sub term from the # `ObserveResult::counts` overload. sub_term_counts = observe_result.counts(sub_term=sub_term) @@ -176,7 +176,7 @@ def test_observe_single_param(angle, want_state, want_expectation, shots_count): # Check that each register is in the proper state. for index, sub_term in enumerate(hamiltonian): # Extract the register name from the spin term. - got_name = str(sub_term).split(" ")[1].rstrip() + got_name = sub_term.get_term_id() # Pull the counts for that hamiltonian sub term from the # `ObserveResult::counts` overload. sub_term_counts = observe_result.counts(sub_term=sub_term) @@ -255,7 +255,7 @@ def test_observe_multi_param(angle_0, angle_1, angles, want_state, # Check that each register is in the proper state. for index, sub_term in enumerate(hamiltonian): # Extract the register name from the spin term. - got_name = str(sub_term).split(" ")[1].rstrip() + got_name = sub_term.get_term_id() # Pull the counts for that hamiltonian sub term from the # `ObserveResult::counts` overload. sub_term_counts = observe_result.counts(sub_term=sub_term) @@ -371,7 +371,7 @@ def test_observe_async_single_param(angle, want_state, want_expectation, # Check that each register is in the proper state. for index, sub_term in enumerate(hamiltonian): # Extract the register name from the spin term. - got_name = str(sub_term).split(" ")[1].rstrip() + got_name = sub_term.get_term_id() # Pull the counts for that hamiltonian sub term from the # `ObserveResult::counts` overload. sub_term_counts = observe_result.counts(sub_term=sub_term) @@ -454,7 +454,7 @@ def test_observe_async_multi_param(angle_0, angle_1, angles, want_state, # Check that each register is in the proper state. for index, sub_term in enumerate(hamiltonian): # Extract the register name from the spin term. - got_name = str(sub_term).split(" ")[1].rstrip() + got_name = sub_term.get_term_id() # Pull the counts for that hamiltonian sub term from the # `ObserveResult::counts` overload. sub_term_counts = observe_result.counts(sub_term=sub_term) @@ -541,7 +541,7 @@ def test_observe_numpy_array(angles, want_state, want_expectation): # Check that each register is in the proper state. for index, sub_term in enumerate(hamiltonian): # Extract the register name from the spin term. - got_name = str(sub_term).split(" ")[1].rstrip() + got_name = sub_term.get_term_id() # Pull the counts for that hamiltonian sub term from the # `ObserveResult::counts` overload. sub_term_counts = observe_result.counts(sub_term=sub_term) diff --git a/python/tests/builder/test_sample.py b/python/tests/builder/test_sample.py index 961366d8685..699b3bcb873 100644 --- a/python/tests/builder/test_sample.py +++ b/python/tests/builder/test_sample.py @@ -339,7 +339,7 @@ def test_sample_result_observe(shots_count): sample_result = observe_result.counts() sample_result.dump() # Should just have 3 measurement registers, one for each spin term. - want_register_names = ["IIZ", "IZI", "ZII"] + want_register_names = ["Z0", "Z1", "Z2"] got_register_names = sample_result.register_names if '__global__' in got_register_names: got_register_names.remove('__global__') @@ -349,7 +349,7 @@ def test_sample_result_observe(shots_count): # Check that each register is in the proper state. for index, sub_term in enumerate(hamiltonian): # Extract the register name from the spin term. - got_name = str(sub_term).split(" ")[1].rstrip() + got_name = sub_term.get_term_id() # Pull the counts for that hamiltonian sub term from the # `ObserveResult::counts` overload. sub_term_counts = observe_result.counts(sub_term=sub_term) diff --git a/python/tests/kernel/test_observe_kernel.py b/python/tests/kernel/test_observe_kernel.py index c405d11d2ab..dd34a816f22 100644 --- a/python/tests/kernel/test_observe_kernel.py +++ b/python/tests/kernel/test_observe_kernel.py @@ -308,10 +308,10 @@ def generateRandomPauliStrings(numQubits, numPaulis): ] def build_cudaq_obs(hs, paulis): - observable = cudaq.SpinOperator() + observable = cudaq.SpinOperator.empty() for h, p in zip(hs, paulis): observable += h * cudaq.SpinOperator.from_word(p) - return observable - cudaq.SpinOperator() + return observable @cudaq.kernel def gqeCirc1(N: int, thetas: list[float], one_pauli: cudaq.pauli_word): diff --git a/python/tests/kernel/test_trotter.py b/python/tests/kernel/test_trotter.py index 58b6f0f7fe1..1995093f22e 100644 --- a/python/tests/kernel/test_trotter.py +++ b/python/tests/kernel/test_trotter.py @@ -48,31 +48,38 @@ def run_steps(steps: int, spins: int): omega = 2 * np.pi def heisenbergModelHam(t: float) -> cudaq.SpinOperator: - tdOp = cudaq.SpinOperator(num_qubits=n_spins) + tdOp = cudaq.SpinOperator.empty() for i in range(0, n_spins - 1): tdOp += (Jx * cudaq.spin.x(i) * cudaq.spin.x(i + 1)) tdOp += (Jy * cudaq.spin.y(i) * cudaq.spin.y(i + 1)) tdOp += (Jz * cudaq.spin.z(i) * cudaq.spin.z(i + 1)) for i in range(0, n_spins): tdOp += (np.cos(omega * t) * cudaq.spin.x(i)) + print(tdOp) return tdOp def termCoefficients(op: cudaq.SpinOperator) -> list[complex]: result = [] - ham.for_each_term( - lambda term: result.append(term.get_coefficient())) + for term in op: + result.append(term.get_coefficient()) return result def termWords(op: cudaq.SpinOperator) -> list[str]: result = [] - ham.for_each_term(lambda term: result.append(term.to_string(False))) + for term in op: + # The way the trotter kernel is written, it + # wants exp pauli to act on the entire state. + # That means we need to make it explicit that each term + # in this Hamiltonian indeed is supposed to act on each qubit. + for i in range(0, n_spins): + term *= cudaq.spin.i(i) + result.append(term.get_pauli_word()) return result # Observe the average magnetization of all spins () - average_magnetization = cudaq.SpinOperator(num_qubits=n_spins) + average_magnetization = cudaq.SpinOperator.empty() for i in range(0, n_spins): average_magnetization += ((1.0 / n_spins) * cudaq.spin.z(i)) - average_magnetization -= 1.0 # Run loop state = cudaq.get_state(getInitState, n_spins) diff --git a/python/tests/operator/test_operator.py b/python/tests/operator/test_operator.py index 4e2748731cf..3703c7cc168 100644 --- a/python/tests/operator/test_operator.py +++ b/python/tests/operator/test_operator.py @@ -309,7 +309,7 @@ def test_operator_equality(): # Spin operators assert (spin.x(1) + spin.y(1)) == ((spin.y(1) + spin.x(1))) - assert (spin.x(1) * spin.y(1)) == ((spin.y(1) * spin.x(1))) + assert (spin.x(1) * spin.y(1)) != ((spin.y(1) * spin.x(1))) assert (spin.x(0) + spin.y(1)) == ((spin.y(1) + spin.x(0))) assert (spin.x(0) * spin.y(1)) == ((spin.y(1) * spin.x(0))) @@ -324,7 +324,7 @@ def test_operator_equality(): spinzy = lambda i, j: spin.z(i) * spin.y(j) spinxy = lambda i, j: spin.x(i) * spin.y(j) assert (spinxy(0, 0) + spinzy(0, 0)) == (spinzy(0, 0) + spinxy(0, 0)) - assert (spinxy(0, 0) * spinzy(0, 0)) == (spinzy(0, 0) * spinxy(0, 0)) + assert (spinxy(0, 0) * spinzy(0, 0)) != (spinzy(0, 0) * spinxy(0, 0)) assert (spinxy(1, 1) * spinzy(0, 0)) == (spinzy(0, 0) * spinxy(1, 1)) assert (spinxy(1, 2) * spinzy(3, 4)) == (spinzy(3, 4) * spinxy(1, 2)) @@ -358,6 +358,8 @@ def test_operator_equality(): def test_arithmetics_operations(): dims = {0: 2, 1: 2} scop = operators.const(2) + # the correct behavior here for to_matrix is that the matrix + # should act on a single degree of freedom, regardless of the target index elop = operators.identity(1) assert np.allclose((scop + elop).to_matrix(dims), (elop + scop).to_matrix(dims)) @@ -367,89 +369,89 @@ def test_arithmetics_operations(): (elop * scop).to_matrix(dims)) assert np.allclose( (elop / scop).to_matrix(dims), - [[0.5, 0, 0, 0], [0, 0.5, 0, 0], [0, 0, 0.5, 0], [0, 0, 0, 0.5]]) + [[0.5, 0], [0, 0.5]]) assert np.allclose(((scop * elop) / scop).to_matrix(dims), - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + [[1, 0], [0, 1]]) assert np.allclose( ((elop / scop) * elop).to_matrix(dims), - [[0.5, 0, 0, 0], [0, 0.5, 0, 0], [0, 0, 0.5, 0], [0, 0, 0, 0.5]]) + [[0.5, 0], [0, 0.5]]) assert np.allclose( ((elop / scop) + elop).to_matrix(dims), - [[1.5, 0, 0, 0], [0, 1.5, 0, 0], [0, 0, 1.5, 0], [0, 0, 0, 1.5]]) + [[1.5, 0], [0, 1.5]]) assert np.allclose( (elop * (elop / scop)).to_matrix(dims), - [[0.5, 0, 0, 0], [0, 0.5, 0, 0], [0, 0, 0.5, 0], [0, 0, 0, 0.5]]) + [[0.5, 0], [0, 0.5]]) assert np.allclose( (elop + (elop / scop)).to_matrix(dims), - [[1.5, 0, 0, 0], [0, 1.5, 0, 0], [0, 0, 1.5, 0], [0, 0, 0, 1.5]]) + [[1.5, 0], [0, 1.5]]) assert np.allclose( ((scop + elop) / scop).to_matrix(dims), - [[1.5, 0, 0, 0], [0, 1.5, 0, 0], [0, 0, 1.5, 0], [0, 0, 0, 1.5]]) + [[1.5, 0], [0, 1.5]]) assert np.allclose( (scop + (elop / scop)).to_matrix(dims), - [[2.5, 0, 0, 0], [0, 2.5, 0, 0], [0, 0, 2.5, 0], [0, 0, 0, 2.5]]) + [[2.5, 0], [0, 2.5]]) assert np.allclose(((scop * elop) / scop).to_matrix(dims), - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + [[1, 0], [0, 1]]) assert np.allclose((scop * (elop / scop)).to_matrix(dims), - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + [[1, 0], [0, 1]]) assert np.allclose(((scop * elop) * scop).to_matrix(dims), - [[4, 0, 0, 0], [0, 4, 0, 0], [0, 0, 4, 0], [0, 0, 0, 4]]) + [[4, 0], [0, 4]]) assert np.allclose((scop * (scop * elop)).to_matrix(dims), - [[4, 0, 0, 0], [0, 4, 0, 0], [0, 0, 4, 0], [0, 0, 0, 4]]) + [[4, 0], [0, 4]]) assert np.allclose(((scop * elop) * elop).to_matrix(dims), - [[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]) + [[2, 0], [0, 2]]) assert np.allclose((elop * (scop * elop)).to_matrix(dims), - [[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]) + [[2, 0], [0, 2]]) assert np.allclose(((scop * elop) + scop).to_matrix(dims), - [[4, 0, 0, 0], [0, 4, 0, 0], [0, 0, 4, 0], [0, 0, 0, 4]]) + [[4, 0], [0, 4]]) assert np.allclose((scop + (scop * elop)).to_matrix(dims), - [[4, 0, 0, 0], [0, 4, 0, 0], [0, 0, 4, 0], [0, 0, 0, 4]]) + [[4, 0], [0, 4]]) assert np.allclose(((scop * elop) + elop).to_matrix(dims), - [[3, 0, 0, 0], [0, 3, 0, 0], [0, 0, 3, 0], [0, 0, 0, 3]]) + [[3, 0], [0, 3]]) assert np.allclose((elop + (scop * elop)).to_matrix(dims), - [[3, 0, 0, 0], [0, 3, 0, 0], [0, 0, 3, 0], [0, 0, 0, 3]]) + [[3, 0], [0, 3]]) assert np.allclose(((scop * elop) - scop).to_matrix(dims), - [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) + [[0, 0], [0, 0]]) assert np.allclose((scop - (scop * elop)).to_matrix(dims), - [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) + [[0, 0], [0, 0]]) assert np.allclose(((scop * elop) - elop).to_matrix(dims), - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + [[1, 0], [0, 1]]) assert np.allclose( (elop - (scop * elop)).to_matrix(dims), - [[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1]]) + [[-1, 0], [0, -1]]) assert np.allclose(((scop + elop) * scop).to_matrix(dims), - [[6, 0, 0, 0], [0, 6, 0, 0], [0, 0, 6, 0], [0, 0, 0, 6]]) + [[6, 0], [0, 6]]) assert np.allclose((scop * (scop + elop)).to_matrix(dims), - [[6, 0, 0, 0], [0, 6, 0, 0], [0, 0, 6, 0], [0, 0, 0, 6]]) + [[6, 0], [0, 6]]) assert np.allclose(((scop + elop) * elop).to_matrix(dims), - [[3, 0, 0, 0], [0, 3, 0, 0], [0, 0, 3, 0], [0, 0, 0, 3]]) + [[3, 0], [0, 3]]) assert np.allclose((elop * (scop + elop)).to_matrix(dims), - [[3, 0, 0, 0], [0, 3, 0, 0], [0, 0, 3, 0], [0, 0, 0, 3]]) + [[3, 0], [0, 3]]) assert np.allclose(((scop - elop) * scop).to_matrix(dims), - [[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]) + [[2, 0], [0, 2]]) assert np.allclose((scop * (scop - elop)).to_matrix(dims), - [[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]) + [[2, 0], [0, 2]]) assert np.allclose(((scop - elop) * elop).to_matrix(dims), - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + [[1, 0], [0, 1]]) assert np.allclose((elop * (scop - elop)).to_matrix(dims), - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + [[1, 0], [0, 1]]) assert np.allclose(((scop + elop) + scop).to_matrix(dims), - [[5, 0, 0, 0], [0, 5, 0, 0], [0, 0, 5, 0], [0, 0, 0, 5]]) + [[5, 0], [0, 5]]) assert np.allclose((scop + (scop + elop)).to_matrix(dims), - [[5, 0, 0, 0], [0, 5, 0, 0], [0, 0, 5, 0], [0, 0, 0, 5]]) + [[5, 0], [0, 5]]) assert np.allclose(((scop + elop) + elop).to_matrix(dims), - [[4, 0, 0, 0], [0, 4, 0, 0], [0, 0, 4, 0], [0, 0, 0, 4]]) + [[4, 0], [0, 4]]) assert np.allclose((elop + (scop + elop)).to_matrix(dims), - [[4, 0, 0, 0], [0, 4, 0, 0], [0, 0, 4, 0], [0, 0, 0, 4]]) + [[4, 0], [0, 4]]) assert np.allclose( ((scop - elop) - scop).to_matrix(dims), - [[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1]]) + [[-1, 0], [0, -1]]) assert np.allclose((scop - (scop - elop)).to_matrix(dims), - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + [[1, 0], [0, 1]]) assert np.allclose(((scop - elop) - elop).to_matrix(dims), - [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) + [[0, 0], [0, 0]]) assert np.allclose((elop - (scop - elop)).to_matrix(dims), - [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) + [[0, 0], [0, 0]]) assert np.allclose(operator.add(scop, 2).to_matrix(dims), [4. + 0.j]) assert np.allclose(operator.add(scop, 2.5).to_matrix(dims), [4.5 + 0j]) @@ -493,74 +495,69 @@ def test_arithmetics_operations(): assert np.allclose( operator.add(elop, 2).to_matrix(dims), - [[3, 0, 0, 0], [0, 3, 0, 0], [0, 0, 3, 0], [0, 0, 0, 3]]) + [[3, 0], [0, 3]]) assert np.allclose( operator.add(elop, 2.5).to_matrix(dims), - [[3.5, 0, 0, 0], [0, 3.5, 0, 0], [0, 0, 3.5, 0], [0, 0, 0, 3.5]]) + [[3.5, 0], [0, 3.5]]) assert np.allclose( operator.add(elop, 2j).to_matrix(dims), - [[1 + 2j, 0, 0, 0], [0, 1 + 2j, 0, 0], [0, 0, 1 + 2j, 0], - [0, 0, 0, 1 + 2j]]) + [[1 + 2j, 0], [0, 1 + 2j]]) assert np.allclose( operator.add(2, elop).to_matrix(dims), - [[3, 0, 0, 0], [0, 3, 0, 0], [0, 0, 3, 0], [0, 0, 0, 3]]) + [[3, 0], [0, 3]]) assert np.allclose( operator.add(2.5, elop).to_matrix(dims), - [[3.5, 0, 0, 0], [0, 3.5, 0, 0], [0, 0, 3.5, 0], [0, 0, 0, 3.5]]) + [[3.5, 0], [0, 3.5]]) assert np.allclose( operator.add(2j, elop).to_matrix(dims), - [[1 + 2j, 0, 0, 0], [0, 1 + 2j, 0, 0], [0, 0, 1 + 2j, 0], - [0, 0, 0, 1 + 2j]]) + [[1 + 2j, 0], [0, 1 + 2j]]) assert np.allclose( operator.sub(elop, 2).to_matrix(dims), - [[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1]]) + [[-1, 0], [0, -1]]) assert np.allclose( operator.sub(elop, 2.5).to_matrix(dims), - [[-1.5, 0, 0, 0], [0, -1.5, 0, 0], [0, 0, -1.5, 0], [0, 0, 0, -1.5]]) + [[-1.5, 0], [0, -1.5]]) assert np.allclose( operator.sub(elop, 2j).to_matrix(dims), - [[1 - 2j, 0, 0, 0], [0, 1 - 2j, 0, 0], [0, 0, 1 - 2j, 0], - [0, 0, 0, 1 - 2j]]) + [[1 - 2j, 0], [0, 1 - 2j]]) assert np.allclose( operator.sub(2, elop).to_matrix(dims), - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + [[1, 0], [0, 1]]) assert np.allclose( operator.sub(2.5, elop).to_matrix(dims), - [[1.5, 0, 0, 0], [0, 1.5, 0, 0], [0, 0, 1.5, 0], [0, 0, 0, 1.5]]) + [[1.5, 0], [0, 1.5]]) assert np.allclose( operator.sub(2j, elop).to_matrix(dims), - [[-1 + 2j, 0, 0, 0], [0, -1 + 2j, 0, 0], [0, 0, -1 + 2j, 0], - [0, 0, 0, -1 + 2j]]) + [[-1 + 2j, 0], [0, -1 + 2j]]) assert np.allclose( operator.mul(elop, 2).to_matrix(dims), - [[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]) + [[2, 0], [0, 2]]) assert np.allclose( operator.mul(elop, 2.5).to_matrix(dims), - [[2.5, 0, 0, 0], [0, 2.5, 0, 0], [0, 0, 2.5, 0], [0, 0, 0, 2.5]]) + [[2.5, 0], [0, 2.5]]) assert np.allclose( operator.mul(elop, 2j).to_matrix(dims), - [[2j, 0, 0, 0], [0, 2j, 0, 0], [0, 0, 2j, 0], [0, 0, 0, 2j]]) + [[2j, 0], [0, 2j]]) assert np.allclose( operator.mul(2, elop).to_matrix(dims), - [[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]) + [[2, 0], [0, 2]]) assert np.allclose( operator.mul(2.5, elop).to_matrix(dims), - [[2.5, 0, 0, 0], [0, 2.5, 0, 0], [0, 0, 2.5, 0], [0, 0, 0, 2.5]]) + [[2.5, 0], [0, 2.5]]) assert np.allclose( operator.mul(2j, elop).to_matrix(dims), - [[2j, 0, 0, 0], [0, 2j, 0, 0], [0, 0, 2j, 0], [0, 0, 0, 2j]]) + [[2j, 0], [0, 2j]]) assert np.allclose( (elop / 2).to_matrix(dims), - [[0.5, 0, 0, 0], [0, 0.5, 0, 0], [0, 0, 0.5, 0], [0, 0, 0, 0.5]]) + [[0.5, 0], [0, 0.5]]) assert np.allclose( (elop / 2.5).to_matrix(dims), - [[0.4, 0, 0, 0], [0, 0.4, 0, 0], [0, 0, 0.4, 0], [0, 0, 0, 0.4]]) + [[0.4, 0], [0, 0.4]]) assert np.allclose((elop / 2j).to_matrix(dims), - [[-0.5j, 0, 0, 0], [0, -0.5j, 0, 0], [0, 0, -0.5j, 0], - [0, 0, 0, -0.5j]]) + [[-0.5j, 0], [0, -0.5j]]) assert np.allclose( operator.add(opprod, 2).to_matrix(dims), [[2, 0], [0, 3]]) diff --git a/runtime/common/BaseRemoteRESTQPU.h b/runtime/common/BaseRemoteRESTQPU.h index 60070249615..e4a2e1dd0ef 100644 --- a/runtime/common/BaseRemoteRESTQPU.h +++ b/runtime/common/BaseRemoteRESTQPU.h @@ -30,9 +30,9 @@ #include "cudaq/Optimizer/Transforms/Passes.h" #include "cudaq/Support/Plugin.h" #include "cudaq/Support/TargetConfig.h" +#include "cudaq/operators.h" #include "cudaq/platform/qpu.h" #include "cudaq/platform/quantum_platform.h" -#include "cudaq/spin_op.h" #include "nvqpp_config.h" #include "llvm/Bitcode/BitcodeReader.h" #include "llvm/Bitcode/BitcodeWriter.h" @@ -537,7 +537,7 @@ class BaseRemoteRESTQPU : public cudaq::QPU { if (executionContext && executionContext->name == "observe") { mapping_reorder_idx.clear(); runPassPipeline("canonicalize,cse", moduleOp); - cudaq::spin_op &spin = *executionContext->spin.value(); + cudaq::spin_op &spin = executionContext->spin.value(); for (const auto &term : spin) { if (term.is_identity()) continue; @@ -551,14 +551,12 @@ class BaseRemoteRESTQPU : public cudaq::QPU { // Create a new Module to clone the ansatz into it auto tmpModuleOp = moduleOp.clone(); - // Extract the binary symplectic encoding - auto [binarySymplecticForm, coeffs] = term.get_raw_data(); - // Create the pass manager, add the quake observe ansatz pass and run it // followed by the canonicalizer mlir::PassManager pm(&context); pm.addNestedPass( - cudaq::opt::createObserveAnsatzPass(binarySymplecticForm[0])); + cudaq::opt::createObserveAnsatzPass( + term.get_binary_symplectic_form())); if (disableMLIRthreading || enablePrintMLIREachPass) tmpModuleOp.getContext()->disableMultithreading(); if (enablePrintMLIREachPass) @@ -575,7 +573,7 @@ class BaseRemoteRESTQPU : public cudaq::QPU { runPassPipeline(pass, tmpModuleOp); if (!emulate && combineMeasurements) runPassPipeline("func.func(combine-measurements)", tmpModuleOp); - modules.emplace_back(term.to_string(false), tmpModuleOp); + modules.emplace_back(term.get_term_id(), tmpModuleOp); } } else modules.emplace_back(kernelName, moduleOp); diff --git a/runtime/common/BaseRemoteSimulatorQPU.h b/runtime/common/BaseRemoteSimulatorQPU.h index 79d41392194..789d38608ef 100644 --- a/runtime/common/BaseRemoteSimulatorQPU.h +++ b/runtime/common/BaseRemoteSimulatorQPU.h @@ -81,7 +81,7 @@ class BaseRemoteSimulatorQPU : public cudaq::QPU { } void launchVQE(const std::string &name, const void *kernelArgs, - cudaq::gradient *gradient, cudaq::spin_op H, + cudaq::gradient *gradient, const cudaq::spin_op &H, cudaq::optimizer &optimizer, const int n_params, const std::size_t shots) override { cudaq::ExecutionContext *executionContextPtr = @@ -92,7 +92,7 @@ class BaseRemoteSimulatorQPU : public cudaq::QPU { auto ctx = std::make_unique("observe", shots); ctx->kernelName = name; - ctx->spin = &H; + ctx->spin = cudaq::spin_op::canonicalize(H); if (shots > 0) ctx->shots = shots; diff --git a/runtime/common/CMakeLists.txt b/runtime/common/CMakeLists.txt index 3d6061f4ef0..943e252f037 100644 --- a/runtime/common/CMakeLists.txt +++ b/runtime/common/CMakeLists.txt @@ -37,7 +37,7 @@ target_include_directories(${LIBRARY_NAME} ${CMAKE_SOURCE_DIR}/runtime) # Link privately to all dependencies -target_link_libraries(${LIBRARY_NAME} PUBLIC cudaq-spin PRIVATE spdlog::spdlog) +target_link_libraries(${LIBRARY_NAME} PUBLIC cudaq-operator PRIVATE spdlog::spdlog) # Bug in GCC 12 leads to spurious warnings (-Wrestrict) # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329 diff --git a/runtime/common/EvolveResult.h b/runtime/common/EvolveResult.h index ac1ac4c3037..43570a5303d 100644 --- a/runtime/common/EvolveResult.h +++ b/runtime/common/EvolveResult.h @@ -9,6 +9,7 @@ #pragma once #include "common/ObserveResult.h" +#include "cudaq/operators.h" #include "cudaq/qis/state.h" #include #include @@ -50,8 +51,7 @@ class evolve_result { : states(std::make_optional>( std::vector{std::move(state)})) { std::vector result; - const spin_op emptyOp( - std::unordered_map>{}); + const spin_op emptyOp = spin_op::empty(); for (auto e : expectations) { result.push_back(observe_result(e, emptyOp)); } @@ -77,8 +77,7 @@ class evolve_result { const std::vector> &expectations) : states(std::make_optional>(states)) { std::vector> result; - const spin_op emptyOp( - std::unordered_map>{}); + const spin_op emptyOp = spin_op::empty(); for (const auto &vec : expectations) { std::vector subResult; for (auto e : vec) { diff --git a/runtime/common/ExecutionContext.h b/runtime/common/ExecutionContext.h index af50bb69d36..1da8bb9407d 100644 --- a/runtime/common/ExecutionContext.h +++ b/runtime/common/ExecutionContext.h @@ -14,11 +14,11 @@ #include "SimulationState.h" #include "Trace.h" #include "cudaq/algorithms/optimizer.h" +#include "cudaq/operators.h" #include #include namespace cudaq { -class spin_op; /// @brief The ExecutionContext is an abstraction to indicate /// how a CUDA-Q kernel should be executed. @@ -31,7 +31,7 @@ class ExecutionContext { std::size_t shots = 0; /// @brief An optional spin operator - std::optional spin; + std::optional spin; /// @brief Measurement counts for a CUDA-Q kernel invocation sample_result result; diff --git a/runtime/common/Future.h b/runtime/common/Future.h index 6fbb37ff817..c7fa93f9520 100644 --- a/runtime/common/Future.h +++ b/runtime/common/Future.h @@ -106,14 +106,18 @@ class async_result { public: async_result() = default; - async_result(spin_op *s) { - if (s) + async_result(const spin_op *s) { + if (s) { spinOp = *s; + spinOp.value().canonicalize(); + } } - async_result(details::future &&f, spin_op *op = nullptr) + async_result(details::future &&f, const spin_op *op = nullptr) : result(std::move(f)) { - if (op) + if (op) { spinOp = *op; + spinOp.value().canonicalize(); + } } /// @brief Return the asynchronously computed data, will @@ -129,20 +133,30 @@ class async_result { throw std::runtime_error( "Returning an observe_result requires a spin_op."); - auto checkRegName = spinOp->to_string(false); + auto checkRegName = spinOp->to_string(); if (data.has_expectation(checkRegName)) return observe_result(data.expectation(checkRegName), *spinOp, data); // this assumes we ran in shots mode. double sum = 0.0; - spinOp->for_each_term([&](spin_op &term) { + for (const auto &term : spinOp.value()) { if (term.is_identity()) - sum += term.get_coefficient().real(); + // FIXME: simply taking real here is very unclean at best, + // and might be wrong/hiding a user error that should cause a failure + // at worst. It would be good to not store a general spin op for the + // result, but instead store the term ids and the evaluated + // (double-valued) coefficient. Similarly, evaluate would fail if + // the operator was parameterized. In general, both parameters, and + // complex coefficients are valid for a spin-op term. + // The code here (and in all other places that do something similar) + // will work perfectly fine as long as there is no user error, but + // the passed observable should really be validated properly and not + // processed here as is making assumptions about correctness. + sum += term.evaluate_coefficient().real(); else - sum += data.expectation(term.to_string(false)) * - term.get_coefficient().real(); - }); - + sum += data.expectation(term.get_term_id()) * + term.evaluate_coefficient().real(); + } return observe_result(sum, *spinOp, data); } diff --git a/runtime/common/JsonConvert.h b/runtime/common/JsonConvert.h index 2a8703deb4b..bf5c101988a 100644 --- a/runtime/common/JsonConvert.h +++ b/runtime/common/JsonConvert.h @@ -139,12 +139,10 @@ inline void to_json(json &j, const ExecutionContext &context) { } } - if (context.spin.has_value() && context.spin.value() != nullptr) { + if (context.spin.has_value()) { const std::vector spinOpRepr = - context.spin.value()->getDataRepresentation(); - const auto spinOpN = context.spin.value()->num_qubits(); + context.spin.value().get_data_representation(); j["spin"] = json(); - j["spin"]["num_qubits"] = spinOpN; j["spin"]["data"] = spinOpRepr; } j["registerNames"] = context.registerNames; @@ -178,9 +176,10 @@ inline void from_json(const json &j, ExecutionContext &context) { if (j.contains("spin")) { std::vector spinData; j["spin"]["data"].get_to(spinData); - const std::size_t nQubits = j["spin"]["num_qubits"]; - auto serializedSpinOps = std::make_unique(spinData, nQubits); - context.spin = serializedSpinOps.release(); + auto serializedSpinOps = spin_op(spinData); + context.spin = std::move(serializedSpinOps); + assert(cudaq::spin_op::canonicalize(context.spin.value()) == + context.spin.value()); } if (j.contains("simulationData")) { @@ -578,9 +577,6 @@ class RestRequest { std::make_unique(j["executionContext"]["name"])), executionContext(*m_deserializedContext) { from_json(j, *this); - // Take the ownership of the spin_op pointer for proper cleanup. - if (executionContext.spin.has_value() && executionContext.spin.value()) - m_deserializedSpinOp.reset(executionContext.spin.value()); } // Underlying code (IR) payload as a Base64 string. diff --git a/runtime/common/MeasureCounts.cpp b/runtime/common/MeasureCounts.cpp index 2f0240930b2..6fe0f130227 100644 --- a/runtime/common/MeasureCounts.cpp +++ b/runtime/common/MeasureCounts.cpp @@ -7,6 +7,7 @@ ******************************************************************************/ #include "MeasureCounts.h" +#include "cudaq/spin_op.h" #include #include @@ -257,94 +258,98 @@ sample_result &sample_result::operator+=(const sample_result &other) { return *this; } -std::vector -sample_result::sequential_data(const std::string_view registerName) const { - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) - throw std::runtime_error( - "There is no sample result for the given registerName (" + - std::string{registerName.begin(), registerName.end()} + ")"); +std::pair +sample_result::try_retrieve_result(const std::string ®isterName) const { + auto iter = sampleResults.find(registerName); + if (iter == sampleResults.end()) { + auto invalid_char = registerName.find_first_not_of("XYZI"); + if (invalid_char == std::string::npos) { + auto spin = spin_op::from_word(registerName); + iter = sampleResults.find(spin.canonicalize().get_term_id()); + if (iter != sampleResults.end()) + return {true, iter->second}; + } + return {false, ExecutionResult()}; + } + return {true, iter->second}; +} - auto data = iter->second.getSequentialData(); - return data; +const cudaq::ExecutionResult & +sample_result::retrieve_result(const std::string ®isterName) const { + auto [found, result] = try_retrieve_result(registerName); + if (!found) + throw std::runtime_error("no results stored for " + registerName); + return result; } -CountsDictionary::iterator sample_result::begin() { - auto iter = sampleResults.find(GlobalRegisterName); +cudaq::ExecutionResult & +sample_result::retrieve_result(const std::string ®isterName) { + auto iter = sampleResults.find(registerName); if (iter == sampleResults.end()) { - throw std::runtime_error( - "There is no global counts dictionary in this sample_result."); + auto invalid_char = registerName.find_first_not_of("XYZI"); + if (invalid_char == std::string::npos) { + auto spin = spin_op::from_word(registerName); + iter = sampleResults.find(spin.canonicalize().get_term_id()); + if (iter != sampleResults.end()) + return iter->second; + } + throw std::runtime_error("no results stored for " + registerName); } + return iter->second; +} - return iter->second.counts.begin(); +std::vector +sample_result::sequential_data(const std::string_view registerName) const { + return retrieve_result(registerName.data()).getSequentialData(); } -CountsDictionary::iterator sample_result::end() { - auto iter = sampleResults.find(GlobalRegisterName); - if (iter == sampleResults.end()) { - throw std::runtime_error( - "There is no global counts dictionary in this sample_result."); - } +CountsDictionary::iterator sample_result::begin() { + return retrieve_result(GlobalRegisterName).counts.begin(); +} - return iter->second.counts.end(); +CountsDictionary::iterator sample_result::end() { + return retrieve_result(GlobalRegisterName).counts.end(); } CountsDictionary::const_iterator sample_result::cbegin() const { - auto iter = sampleResults.find(GlobalRegisterName); - if (iter == sampleResults.end()) { - throw std::runtime_error( - "There is no global counts dictionary in this sample_result."); - } - - return iter->second.counts.cbegin(); + return retrieve_result(GlobalRegisterName).counts.cbegin(); } CountsDictionary::const_iterator sample_result::cend() const { - auto iter = sampleResults.find(GlobalRegisterName); - if (iter == sampleResults.end()) { - throw std::runtime_error( - "There is no global counts dictionary in this sample_result."); - } - - return iter->second.counts.cend(); + return retrieve_result(GlobalRegisterName).counts.cend(); } -std::size_t sample_result::size(const std::string_view registerName) noexcept { - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) +std::size_t +sample_result::size(const std::string_view registerName) const noexcept { + auto [found, result] = try_retrieve_result(registerName.data()); + if (found) + return result.counts.size(); + else return 0; - - return iter->second.counts.size(); } double sample_result::probability(std::string_view bitStr, const std::string_view registerName) const { - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) - return 0.0; - - const auto countIter = iter->second.counts.find(bitStr.data()); - return (countIter == iter->second.counts.end()) + const auto &result = retrieve_result(registerName.data()); + const auto countIter = result.counts.find(bitStr.data()); + return (countIter == result.counts.end()) ? 0.0 : (double)countIter->second / totalShots; } std::size_t sample_result::count(std::string_view bitStr, - const std::string_view registerName) { - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) + const std::string_view registerName) const { + const auto &counts = retrieve_result(registerName.data()).counts; + auto it = counts.find(bitStr.data()); + if (it == counts.cend()) return 0; - - return iter->second.counts[bitStr.data()]; + else + return it->second; } -std::string sample_result::most_probable(const std::string_view registerName) { - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) - throw std::runtime_error( - "[sample_result::most_probable] invalid sample result register name (" + - std::string(registerName) + ")"); - auto counts = iter->second.counts; +std::string +sample_result::most_probable(const std::string_view registerName) const { + const auto &counts = retrieve_result(registerName.data()).counts; return std::max_element(counts.begin(), counts.end(), [](const auto &el1, const auto &el2) { return el1.second < el2.second; @@ -353,45 +358,20 @@ std::string sample_result::most_probable(const std::string_view registerName) { } bool sample_result::has_expectation(const std::string_view registerName) const { - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) + auto [found, result] = try_retrieve_result(registerName.data()); + if (found) + return result.expectationValue.has_value(); + else return false; - - return iter->second.expectationValue.has_value(); } double sample_result::expectation(const std::string_view registerName) const { - double aver = 0.0; - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) - return 0.0; - - if (iter->second.expectationValue.has_value()) - return iter->second.expectationValue.value(); - - auto counts = iter->second.counts; - for (auto &kv : counts) { - auto par = has_even_parity(kv.first); - auto p = probability(kv.first, registerName); - if (!par) { - p = -p; - } - aver += p; - } - - return aver; -} + const auto &result = retrieve_result(registerName.data()); + if (result.expectationValue.has_value()) + return result.expectationValue.value(); -double sample_result::exp_val_z(const std::string_view registerName) { double aver = 0.0; - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) - return 0.0; - - if (iter->second.expectationValue.has_value()) - return iter->second.expectationValue.value(); - - auto counts = iter->second.counts; + const auto &counts = result.counts; for (auto &kv : counts) { auto par = has_even_parity(kv.first); auto p = probability(kv.first, registerName); @@ -415,21 +395,13 @@ std::vector sample_result::register_names() const { CountsDictionary sample_result::to_map(const std::string_view registerName) const { - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) - return CountsDictionary(); - - return iter->second.counts; + return retrieve_result(registerName.data()).counts; } sample_result sample_result::get_marginal(const std::vector &marginalIndices, - const std::string_view registerName) { - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) - return sample_result(); - - auto counts = iter->second.counts; + const std::string_view registerName) const { + const auto &counts = retrieve_result(registerName.data()).counts; auto mutableIndices = marginalIndices; std::sort(mutableIndices.begin(), mutableIndices.end()); @@ -489,9 +461,9 @@ void sample_result::dump(std::ostream &os) const { } else if (sampleResults.size() == 1) { CountsDictionary counts; - auto iter = sampleResults.find(GlobalRegisterName); - if (iter != sampleResults.end()) - counts = iter->second.counts; + auto [found, result] = try_retrieve_result(GlobalRegisterName); + if (found) + counts = result.counts; else { auto first = sampleResults.begin(); os << "\n " << first->first << " : { "; @@ -502,7 +474,7 @@ void sample_result::dump(std::ostream &os) const { os << kv->first << ":" << kv->second << " "; } - if (iter == sampleResults.end()) + if (!found) os << "}\n"; } os << "}\n"; @@ -517,13 +489,10 @@ bool sample_result::has_even_parity(std::string_view bitString) { void sample_result::reorder(const std::vector &idx, const std::string_view registerName) { - auto iter = sampleResults.find(registerName.data()); - if (iter == sampleResults.end()) - return; - // First process the counts + auto &result = retrieve_result(registerName.data()); CountsDictionary newCounts; - for (auto [bits, count] : iter->second.counts) { + for (auto [bits, count] : result.counts) { if (idx.size() != bits.size()) throw std::runtime_error("Calling reorder() with invalid parameter idx"); @@ -533,10 +502,10 @@ void sample_result::reorder(const std::vector &idx, newBits[i++] = bits[oldIdx]; newCounts[newBits] = count; } - iter->second.counts = newCounts; + result.counts = newCounts; // Now process the sequential data - for (auto &s : iter->second.sequentialData) { + for (auto &s : result.sequentialData) { std::string newBits(s); int i = 0; for (auto oldIdx : idx) diff --git a/runtime/common/MeasureCounts.h b/runtime/common/MeasureCounts.h index b5609a70ef0..7191b35c462 100644 --- a/runtime/common/MeasureCounts.h +++ b/runtime/common/MeasureCounts.h @@ -112,6 +112,11 @@ class sample_result { /// here so we don't have to keep recomputing it. std::size_t totalShots = 0; + std::pair + try_retrieve_result(const std::string ®isterName) const; + const ExecutionResult &retrieve_result(const std::string ®isterName) const; + ExecutionResult &retrieve_result(const std::string ®isterName); + public: /// @brief Nullary constructor sample_result() = default; @@ -190,10 +195,6 @@ class sample_result { /// @return double expectation(const std::string_view registerName = GlobalRegisterName) const; - /// @brief Deprecated: Return the expected value - [[deprecated("`exp_val_z()` is deprecated. Use `expectation()` with the same " - "argument structure.")]] double - exp_val_z(const std::string_view registerName = GlobalRegisterName); /// @brief Return the probability of observing the given bit string /// @param bitString @@ -206,13 +207,14 @@ class sample_result { /// @param registerName /// @return std::string - most_probable(const std::string_view registerName = GlobalRegisterName); + most_probable(const std::string_view registerName = GlobalRegisterName) const; /// @brief Return the number of times the given bitstring was observed /// @param bitString /// @return - std::size_t count(std::string_view bitString, - const std::string_view registerName = GlobalRegisterName); + std::size_t + count(std::string_view bitString, + const std::string_view registerName = GlobalRegisterName) const; std::vector sequential_data( const std::string_view registerName = GlobalRegisterName) const; @@ -220,7 +222,7 @@ class sample_result { /// @brief Return the number of observed bit strings /// @return std::size_t - size(const std::string_view registerName = GlobalRegisterName) noexcept; + size(const std::string_view registerName = GlobalRegisterName) const noexcept; /// @brief Dump this sample_result to standard out. void dump() const; @@ -245,7 +247,7 @@ class sample_result { /// @return sample_result get_marginal(const std::vector &&marginalIndices, - const std::string_view registerName = GlobalRegisterName) { + const std::string_view registerName = GlobalRegisterName) const { return get_marginal(marginalIndices, registerName); } @@ -256,7 +258,7 @@ class sample_result { /// @return sample_result get_marginal(const std::vector &marginalIndices, - const std::string_view registerName = GlobalRegisterName); + const std::string_view registerName = GlobalRegisterName) const; /// @brief Reorder the bits in an ExecutionResult /// @param index Vector of indices such that diff --git a/runtime/common/ObserveResult.h b/runtime/common/ObserveResult.h index 18261297b76..07acb055001 100644 --- a/runtime/common/ObserveResult.h +++ b/runtime/common/ObserveResult.h @@ -9,7 +9,7 @@ #pragma once #include "MeasureCounts.h" -#include "cudaq/spin_op.h" +#include "cudaq/operators.h" #include @@ -35,14 +35,18 @@ class observe_result { /// @brief Constructor, takes the precomputed expectation value for /// for the given spin_op. - observe_result(double e, const spin_op &H) : expVal(e), spinOp(H) {} + observe_result(double e, const spin_op &H) : expVal(e), spinOp(H) { + assert(cudaq::spin_op::canonicalize(spinOp) == spinOp); + } /// @brief Constructor, takes the precomputed expectation value for /// for the given spin_op. If this execution /// was shots based, also provide the sample_result data containing counts /// for each term in H. observe_result(double e, const spin_op &H, sample_result counts) - : expVal(e), spinOp(H), data(counts) {} + : expVal(e), spinOp(H), data(counts) { + assert(cudaq::spin_op::canonicalize(spinOp) == spinOp); + } /// @brief Return the raw counts data for all terms /// @return @@ -59,74 +63,49 @@ class observe_result { /// @brief Return the expected value for the provided spin_op /// @return double expectation() { return expVal; } - // Deprecated: - [[deprecated("`exp_val_z()` is deprecated. Use `expectation()` with the same " - "argument structure.")]] double - exp_val_z() { - return expVal; - } /// @brief Return the expectation value for a sub-term in the provided /// spin_op. - template - double expectation(SpinOpType term) { - static_assert(std::is_same_v>, - "Must provide a one term spin_op"); - // Pauli == Pauli II..III - // e.g. someone might check for , which - // on more than 1 qubit can be - auto numQubits = spinOp.num_qubits(); - auto termStr = term.to_string(false); - // Expand the string representation of the term to match the number of - // qubits of the overall spin_op this result represents by appending - // identity ops. - if (!data.has_expectation(termStr)) - for (std::size_t i = termStr.size(); i < numQubits; i++) - termStr += "I"; + double expectation(const spin_op_term &term) { + auto termStr = cudaq::spin_op_term::canonicalize(term).get_term_id(); return data.expectation(termStr); } - // Deprecated: - template - [[deprecated("`exp_val_z()` is deprecated. Use `expectation()` with the same " - "argument structure.")]] double - exp_val_z(SpinOpType term) { - static_assert(std::is_same_v>, - "Must provide a one term spin_op"); - // Pauli == Pauli II..III - // e.g. someone might check for , which - // on more than 1 qubit can be - auto numQubits = spinOp.num_qubits(); - auto termStr = term.to_string(false); - if (!data.has_expectation(termStr) && termStr.size() == 1 && numQubits > 1) - for (std::size_t i = 1; i < numQubits; i++) - termStr += "I"; + + [[deprecated("passing a spin_op to expectation is deprecated - please pass a " + "spin_op_term instead")]] double + expectation(const spin_op &op) { + if (op.num_terms() != 1) + throw std::runtime_error("expecting a spin op with exactly one term"); + auto termStr = cudaq::spin_op_term::canonicalize(*op.begin()).get_term_id(); return data.expectation(termStr); } /// @brief Return the counts data for the given spin_op - /// @param term - /// @return - template - sample_result counts(SpinOpType term) { - static_assert(std::is_same_v>, - "Must provide a one term spin_op"); - assert(term.num_terms() == 1 && "Must provide a one term spin_op"); - auto numQubits = spinOp.num_qubits(); - auto termStr = term.to_string(false); - if (!data.has_expectation(termStr) && termStr.size() == 1 && numQubits > 1) - for (std::size_t i = 1; i < numQubits; i++) - termStr += "I"; + sample_result counts(const spin_op_term &term) { + auto termStr = cudaq::spin_op_term::canonicalize(term).get_term_id(); + auto counts = data.to_map(termStr); + ExecutionResult result(counts); + return sample_result(result); + } + + [[deprecated("passing a spin_op to counts is deprecated - please pass a " + "spin_op_term instead")]] sample_result + counts(const spin_op &op) { + if (op.num_terms() != 1) + throw std::runtime_error("expecting a spin op with exactly one term"); + auto termStr = cudaq::spin_op_term::canonicalize(*op.begin()).get_term_id(); auto counts = data.to_map(termStr); ExecutionResult result(counts); return sample_result(result); } /// @brief Return the coefficient of the identity term. - /// @return + /// Assumes there is at more one identity term. + /// Returns 0 if no identity term exists. double id_coefficient() { for (const auto &term : spinOp) if (term.is_identity()) - return term.get_coefficient().real(); + return term.evaluate_coefficient().real(); return 0.0; } diff --git a/runtime/cudaq/CMakeLists.txt b/runtime/cudaq/CMakeLists.txt index 80d8bff1a03..3793f677735 100644 --- a/runtime/cudaq/CMakeLists.txt +++ b/runtime/cudaq/CMakeLists.txt @@ -6,8 +6,6 @@ # the terms of the Apache License 2.0 which accompanies this distribution. # # ============================================================================ # -add_subdirectory(spin) - set(LIBRARY_NAME cudaq) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") set(INTERFACE_POSITION_INDEPENDENT_CODE ON) @@ -17,6 +15,8 @@ add_library(${LIBRARY_NAME} SHARED cudaq.cpp target_control.cpp algorithms/draw.cpp + algorithms/evolve.cpp + algorithms/schedule.cpp platform/qpu_state.cpp platform/quantum_platform.cpp qis/execution_manager_c_api.cpp @@ -41,7 +41,7 @@ if (CUDA_FOUND) PRIVATE .) target_link_libraries(${LIBRARY_NAME} - PUBLIC dl cudaq-spin cudaq-operator cudaq-common cudaq-nlopt cudaq-ensmallen + PUBLIC dl cudaq-operator cudaq-common cudaq-nlopt cudaq-ensmallen PRIVATE nvqir fmt::fmt-header-only CUDA::cudart_static) target_compile_definitions(${LIBRARY_NAME} PRIVATE CUDAQ_HAS_CUDA) @@ -53,7 +53,7 @@ else() PRIVATE .) target_link_libraries(${LIBRARY_NAME} - PUBLIC dl cudaq-spin cudaq-operator cudaq-common cudaq-nlopt cudaq-ensmallen + PUBLIC dl cudaq-operator cudaq-common cudaq-nlopt cudaq-ensmallen PRIVATE nvqir fmt::fmt-header-only) endif() @@ -62,7 +62,7 @@ add_subdirectory(algorithms) add_subdirectory(platform) add_subdirectory(builder) add_subdirectory(domains) -add_subdirectory(dynamics) +add_subdirectory(operators) install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-targets DESTINATION lib) diff --git a/runtime/cudaq/base_integrator.h b/runtime/cudaq/algorithms/base_integrator.h similarity index 97% rename from runtime/cudaq/base_integrator.h rename to runtime/cudaq/algorithms/base_integrator.h index 8943d509c80..cf24f5dc1b6 100644 --- a/runtime/cudaq/base_integrator.h +++ b/runtime/cudaq/algorithms/base_integrator.h @@ -8,8 +8,8 @@ #pragma once -#include "operators.h" -#include "schedule.h" +#include "cudaq/operators.h" +#include "cudaq/schedule.h" #include #include #include diff --git a/runtime/cudaq/base_time_stepper.h b/runtime/cudaq/algorithms/base_time_stepper.h similarity index 100% rename from runtime/cudaq/base_time_stepper.h rename to runtime/cudaq/algorithms/base_time_stepper.h diff --git a/runtime/cudaq/dynamics/evolution.cpp b/runtime/cudaq/algorithms/evolve.cpp similarity index 100% rename from runtime/cudaq/dynamics/evolution.cpp rename to runtime/cudaq/algorithms/evolve.cpp diff --git a/runtime/cudaq/algorithms/evolve.h b/runtime/cudaq/algorithms/evolve.h index b7103a7944f..5c0885cd465 100644 --- a/runtime/cudaq/algorithms/evolve.h +++ b/runtime/cudaq/algorithms/evolve.h @@ -10,11 +10,11 @@ #include "common/EvolveResult.h" #include "common/KernelWrapper.h" +#include "cudaq/algorithms/base_integrator.h" #include "cudaq/algorithms/get_state.h" -#include "cudaq/base_integrator.h" -#include "cudaq/dynamics/operator_type.h" #include "cudaq/host_config.h" #include "cudaq/operators.h" +#include "cudaq/operators/operator_type.h" #include "cudaq/platform.h" #include "cudaq/platform/QuantumExecutionQueue.h" #include "cudaq/schedule.h" diff --git a/runtime/cudaq/algorithms/gradient.h b/runtime/cudaq/algorithms/gradient.h index b0bf79bf9fa..c689b2587a9 100644 --- a/runtime/cudaq/algorithms/gradient.h +++ b/runtime/cudaq/algorithms/gradient.h @@ -9,7 +9,7 @@ #pragma once #include "observe.h" #include -#include +#include #include namespace cudaq { diff --git a/runtime/cudaq/algorithms/integrator.h b/runtime/cudaq/algorithms/integrator.h new file mode 100644 index 00000000000..8dbd3bfc988 --- /dev/null +++ b/runtime/cudaq/algorithms/integrator.h @@ -0,0 +1,52 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "cudaq/algorithms/base_integrator.h" +#include "cudaq/algorithms/base_time_stepper.h" +#include "cudaq/operators.h" +#include + +namespace cudaq { +namespace integrators { + +class runge_kutta : public cudaq::base_integrator { +public: + /// @brief The default `Runge-Kutta` integration order. + // Note: we use 4th order as the default since (1) it produces better + // convergence/stability and (2) that is the typical order that the name + // `Runge-Kutta` is associated with (e.g., RK method can be generalized to + // cover 1st order Euler method) + static constexpr int default_order = 4; + /// @brief Constructor + // (1) Integration order + // (2) Max step size: if none provided, the schedule of time points where we + // want to compute and save intermediate results will be used. If provided, + // the integrator will make sub-steps no larger than this value to integrate + // toward scheduled time points. + runge_kutta(int order = default_order, + const std::optional &max_step_size = {}); + /// @brief Integrate toward a specified time point. + void integrate(double targetTime) override; + /// @brief Set the initial state of the integration + void setState(const cudaq::state &initialState, double t0) override; + /// @brief Get the current state of the integrator + // Returns the current time point and state. + std::pair getState() override; + /// @brief Clone the current integrator. + std::shared_ptr clone() override; + +private: + double m_t; + std::shared_ptr m_state; + int m_order; + std::optional m_dt; +}; +} // namespace integrators +} // namespace cudaq diff --git a/runtime/cudaq/algorithms/observe.h b/runtime/cudaq/algorithms/observe.h index 263eb6983ef..77bed4bd867 100644 --- a/runtime/cudaq/algorithms/observe.h +++ b/runtime/cudaq/algorithms/observe.h @@ -14,7 +14,7 @@ #include "cudaq/algorithms/broadcast.h" #include "cudaq/concepts.h" #include "cudaq/host_config.h" -#include "cudaq/spin_op.h" +#include "cudaq/operators.h" #include #if CUDAQ_USE_STD20 #include @@ -76,14 +76,15 @@ namespace details { /// observation process. template std::optional -runObservation(KernelFunctor &&k, cudaq::spin_op &h, quantum_platform &platform, - int shots, const std::string &kernelName, std::size_t qpu_id = 0, +runObservation(KernelFunctor &&k, const cudaq::spin_op &H, + quantum_platform &platform, int shots, + const std::string &kernelName, std::size_t qpu_id = 0, details::future *futureResult = nullptr, std::size_t batchIteration = 0, std::size_t totalBatchIters = 0, std::optional numTrajectories = {}) { auto ctx = std::make_unique("observe", shots); ctx->kernelName = kernelName; - ctx->spin = &h; + ctx->spin = cudaq::spin_op::canonicalize(H); if (shots > 0) ctx->shots = shots; @@ -122,25 +123,24 @@ runObservation(KernelFunctor &&k, cudaq::spin_op &h, quantum_platform &platform, else { // If not, we have everything we need to compute it. double sum = 0.0; - h.for_each_term([&](spin_op &term) { + for (const auto &term : ctx->spin.value()) { if (term.is_identity()) - sum += term.get_coefficient().real(); + sum += term.evaluate_coefficient().real(); else - sum += data.expectation(term.to_string(false)) * - term.get_coefficient().real(); - }); - + sum += data.expectation(term.get_term_id()) * + term.evaluate_coefficient().real(); + } expectationValue = sum; } - return observe_result(expectationValue, h, data); + return observe_result(expectationValue, ctx->spin.value(), data); } /// @brief Take the input KernelFunctor (a lambda that captures runtime /// arguments and invokes the quantum kernel) and invoke the `spin_op` /// observation process asynchronously template -auto runObservationAsync(KernelFunctor &&wrappedKernel, spin_op &H, +auto runObservationAsync(KernelFunctor &&wrappedKernel, const spin_op &H, quantum_platform &platform, int shots, const std::string &kernelName, std::size_t qpu_id = 0) { @@ -181,11 +181,13 @@ auto runObservationAsync(KernelFunctor &&wrappedKernel, spin_op &H, /// available platform QPUs. The `asyncLauncher` functor takes as input the /// QPU index and the `spin_op` chunk and returns an `async_observe_result`. inline auto distributeComputations( - std::function &&asyncLauncher, - spin_op &H, std::size_t nQpus) { + std::function + &&asyncLauncher, + const spin_op &H, std::size_t nQpus) { + auto op = cudaq::spin_op::canonicalize(H); // Distribute the given spin_op into subsets for each QPU - auto spins = H.distribute_terms(nQpus); + auto spins = op.distribute_terms(nQpus); // Observe each sub-spin_op asynchronously std::vector asyncResults; @@ -210,7 +212,7 @@ inline auto distributeComputations( data += incomingData; } - return observe_result(result, H, data); + return observe_result(result, op, data); } } // namespace details @@ -225,7 +227,8 @@ template >> #endif -observe_result observe(QuantumKernel &&kernel, spin_op H, Args &&...args) { +observe_result observe(QuantumKernel &&kernel, const spin_op &H, + Args &&...args) { // Run this SHOTS times auto &platform = cudaq::get_platform(); auto shots = platform.get_shots().value_or(-1); @@ -254,17 +257,21 @@ template observe(QuantumKernel &&kernel, const SpinOpContainer &termList, Args &&...args) { + // Here to give a more comprehensive error if the container does not contain + // values of type spin_op_term. + typedef typename SpinOpContainer::value_type value_type; + static_assert(std::is_same_v, + "term list must be a container of spin_op_term"); + // Run this SHOTS times auto &platform = cudaq::get_platform(); auto shots = platform.get_shots().value_or(-1); auto kernelName = cudaq::getKernelName(kernel); // Convert all spin_ops to a single summed spin_op - cudaq::spin_op op; + auto op = cudaq::spin_op::empty(); for (auto &o : termList) - op += o; - // the constructor for spin_op starts the op as the identity, remove that - op -= spin_op(); + op += cudaq::spin_op_term::canonicalize(o); // Run the observation auto result = details::runObservation( @@ -277,8 +284,8 @@ std::vector observe(QuantumKernel &&kernel, // Convert back to a vector of results std::vector results; - for (auto &o : termList) - results.emplace_back(result.expectation(o), o, result.counts(o)); + for (const auto &term : op) + results.emplace_back(result.expectation(term), term, result.counts(term)); return results; } @@ -300,8 +307,8 @@ template >> #endif -observe_result observe(std::size_t shots, QuantumKernel &&kernel, spin_op H, - Args &&...args) { +observe_result observe(std::size_t shots, QuantumKernel &&kernel, + const spin_op &H, Args &&...args) { // Run this SHOTS times auto &platform = cudaq::get_platform(); // Does platform support parallelism? Need a check here @@ -320,7 +327,7 @@ observe_result observe(std::size_t shots, QuantumKernel &&kernel, spin_op H, // Let's distribute the work among the QPUs on this node. return details::distributeComputations( [&kernel, shots, ... args = std::forward(args)]( - std::size_t i, spin_op &op) mutable { + std::size_t i, const spin_op &op) mutable { return observe_async(shots, i, std::forward(kernel), op, std::forward(args)...); }, @@ -329,7 +336,7 @@ observe_result observe(std::size_t shots, QuantumKernel &&kernel, spin_op H, return details::distributeComputations( [&kernel, shots, args = std::forward_as_tuple(std::forward(args)...)]( - std::size_t i, spin_op &op) mutable { + std::size_t i, const spin_op &op) mutable { return std::apply( [&](auto &&...args) { observe_async(shots, i, std::forward(kernel), op, @@ -360,20 +367,20 @@ observe_result observe(std::size_t shots, QuantumKernel &&kernel, spin_op H, auto spins = H.distribute_terms(nRanks); // Get this rank's set of spins to compute - auto localH = spins[rank]; + auto localH = spins[rank].canonicalize(); // Distribute locally, i.e. to the local nodes QPUs auto localRankResult = details::distributeComputations( #if CUDAQ_USE_STD20 [&kernel, shots, ... args = std::forward(args)]( - std::size_t i, spin_op &op) mutable { + std::size_t i, const spin_op &op) mutable { return observe_async(shots, i, std::forward(kernel), op, std::forward(args)...); }, #else [&kernel, shots, args = std::forward_as_tuple(std::forward(args)...)]( - std::size_t i, spin_op &op) mutable { + std::size_t i, const spin_op &op) mutable { return std::apply( [&](auto &&...args) { observe_async(shots, i, std::forward(kernel), op, @@ -387,7 +394,12 @@ observe_result observe(std::size_t shots, QuantumKernel &&kernel, spin_op H, // combine all the data via an all_reduce auto exp_val = localRankResult.expectation(); auto globalExpVal = mpi::all_reduce(exp_val, std::plus()); - return observe_result(globalExpVal, H); + // we need the canonicalized version of H - + // maybe we can get it from the context instead? + cudaq::spin_op canonH; + for (auto &&terms : spins) + canonH += std::move(terms); + return observe_result(globalExpVal, canonH); } else throw std::runtime_error("Invalid cudaq::par execution type."); @@ -401,7 +413,8 @@ template >> #endif -observe_result observe(QuantumKernel &&kernel, spin_op H, Args &&...args) { +observe_result observe(QuantumKernel &&kernel, const spin_op &H, + Args &&...args) { auto &platform = cudaq::get_platform(); auto shots = platform.get_shots().value_or(-1); return observe(shots, std::forward(kernel), @@ -420,8 +433,8 @@ template >> #endif -observe_result observe(std::size_t shots, QuantumKernel &&kernel, spin_op H, - Args &&...args) { +observe_result observe(std::size_t shots, QuantumKernel &&kernel, + const spin_op &H, Args &&...args) { // Run this SHOTS times auto &platform = cudaq::get_platform(); auto kernelName = cudaq::getKernelName(kernel); @@ -432,14 +445,14 @@ observe_result observe(std::size_t shots, QuantumKernel &&kernel, spin_op H, return details::distributeComputations( #if CUDAQ_USE_STD20 [&kernel, shots, ... args = std::forward(args)]( - std::size_t i, spin_op &op) mutable { + std::size_t i, const spin_op &op) mutable { return observe_async(shots, i, std::forward(kernel), op, std::forward(args)...); }, #else [&kernel, shots, args = std::forward_as_tuple(std::forward(args)...)]( - std::size_t i, spin_op &op) mutable { + std::size_t i, const spin_op &op) mutable { return std::apply( [&](auto &&...args) { observe_async(shots, i, std::forward(kernel), op, @@ -470,7 +483,7 @@ template >> #endif observe_result observe(const observe_options &options, QuantumKernel &&kernel, - spin_op H, Args &&...args) { + const spin_op &H, Args &&...args) { auto &platform = cudaq::get_platform(); auto kernelName = cudaq::getKernelName(kernel); auto shots = options.shots; @@ -502,8 +515,8 @@ template >> #endif -auto observe_async(const std::size_t qpu_id, QuantumKernel &&kernel, spin_op &H, - Args &&...args) { +auto observe_async(const std::size_t qpu_id, QuantumKernel &&kernel, + const spin_op &H, Args &&...args) { // Run this SHOTS times auto &platform = cudaq::get_platform(); auto shots = platform.get_shots().value_or(-1); @@ -543,7 +556,7 @@ template >> #endif auto observe_async(std::size_t shots, std::size_t qpu_id, - QuantumKernel &&kernel, spin_op &H, Args &&...args) { + QuantumKernel &&kernel, const spin_op &H, Args &&...args) { // Run this SHOTS times auto &platform = cudaq::get_platform(); auto kernelName = cudaq::getKernelName(kernel); @@ -581,7 +594,7 @@ template >> #endif -auto observe_async(QuantumKernel &&kernel, spin_op &H, Args &&...args) { +auto observe_async(QuantumKernel &&kernel, const spin_op &H, Args &&...args) { return observe_async(0, std::forward(kernel), H, std::forward(args)...); } @@ -602,7 +615,7 @@ template >> #endif -std::vector observe(QuantumKernel &&kernel, spin_op H, +std::vector observe(QuantumKernel &&kernel, const spin_op &H, ArgumentSet &¶ms) { // Get the platform and query the number of quantum computers auto &platform = cudaq::get_platform(); @@ -647,7 +660,8 @@ template >> #endif std::vector observe(std::size_t shots, QuantumKernel &&kernel, - spin_op H, ArgumentSet &¶ms) { + const spin_op &H, + ArgumentSet &¶ms) { // Get the platform and query the number of quantum computers auto &platform = cudaq::get_platform(); auto numQpus = platform.num_qpus(); @@ -689,7 +703,7 @@ template >> #endif std::vector observe(cudaq::observe_options &options, - QuantumKernel &&kernel, spin_op H, + QuantumKernel &&kernel, const spin_op &H, ArgumentSet &¶ms) { // Get the platform and query the number of quantum computers auto &platform = cudaq::get_platform(); @@ -721,89 +735,4 @@ std::vector observe(cudaq::observe_options &options, platform.reset_noise(); return ret; } - -/// @brief Run the standard observe functionality over a set of `N` -/// argument packs. For a kernel with signature `void(Args...)`, this -/// function takes as input a set of `vector...`, a vector for -/// each argument type in the kernel signature. The vectors must be of -/// equal length, and the `i-th` element of each vector is used `i-th` -/// execution of the standard observe function. Results are collected -/// from the execution of every argument set and returned. -#if CUDAQ_USE_STD20 -template - requires ObserveCallValid -#else -template >> -#endif -[[deprecated("Use observe() overload instead")]] std::vector -observe_n(QuantumKernel &&kernel, spin_op H, ArgumentSet &¶ms) { - // Get the platform and query the number of quantum computers - auto &platform = cudaq::get_platform(); - auto numQpus = platform.num_qpus(); - - // Create the functor that will broadcast the observations across - // all requested argument sets provided. - details::BroadcastFunctorType functor = - [&](std::size_t qpuId, std::size_t counter, std::size_t N, - Args &...singleIterParameters) -> observe_result { - auto shots = platform.get_shots().value_or(-1); - auto kernelName = cudaq::getKernelName(kernel); - auto ret = details::runObservation( - [&kernel, &singleIterParameters...]() mutable { - kernel(std::forward(singleIterParameters)...); - }, - H, platform, shots, kernelName, qpuId, nullptr, counter, N) - .value(); - return ret; - }; - - // Broadcast the executions and return the results. - return details::broadcastFunctionOverArguments( - numQpus, platform, functor, params); -} - -/// @brief Run the standard observe functionality over a set of N -/// argument packs. For a kernel with signature `void(Args...)`, this -/// function takes as input a set of `vector...`, a vector for -/// each argument type in the kernel signature. The vectors must be of -/// equal length, and the `i-th` element of each vector is used `i-th` -/// execution of the standard observe function. Results are collected -/// from the execution of every argument set and returned. This overload -/// allows the number of circuit executions (shots) to be specified. -#if CUDAQ_USE_STD20 -template - requires ObserveCallValid -#else -template >> -#endif -[[deprecated("Use observe() overload instead")]] std::vector -observe_n(std::size_t shots, QuantumKernel &&kernel, spin_op H, - ArgumentSet &¶ms) { - // Get the platform and query the number of quantum computers - auto &platform = cudaq::get_platform(); - auto numQpus = platform.num_qpus(); - - // Create the functor that will broadcast the observations across - // all requested argument sets provided. - details::BroadcastFunctorType functor = - [&](std::size_t qpuId, std::size_t counter, std::size_t N, - Args &...singleIterParameters) -> observe_result { - auto kernelName = cudaq::getKernelName(kernel); - auto ret = details::runObservation( - [&kernel, &singleIterParameters...]() mutable { - kernel(std::forward(singleIterParameters)...); - }, - H, platform, shots, kernelName, qpuId, nullptr, counter, N) - .value(); - return ret; - }; - - // Broadcast the executions and return the results. - return details::broadcastFunctionOverArguments( - numQpus, platform, functor, params); -} } // namespace cudaq diff --git a/runtime/cudaq/dynamics/schedule.cpp b/runtime/cudaq/algorithms/schedule.cpp similarity index 95% rename from runtime/cudaq/dynamics/schedule.cpp rename to runtime/cudaq/algorithms/schedule.cpp index 03cb58d59d3..b64e244ad99 100644 --- a/runtime/cudaq/dynamics/schedule.cpp +++ b/runtime/cudaq/algorithms/schedule.cpp @@ -34,7 +34,7 @@ schedule::pointer schedule::operator->() { return ptr; } // Prefix increment schedule &schedule::operator++() { - if (current_idx + 1 < static_cast(steps.size())) { + if (current_idx + 1 < steps.size()) { current_idx++; ptr = &steps[current_idx]; } else { @@ -71,7 +71,7 @@ void schedule::reset() { // Get the current step std::optional> schedule::current_step() const { - if (current_idx >= 0 && current_idx < static_cast(steps.size())) + if (current_idx >= 0 && current_idx < steps.size()) return steps[current_idx]; return std::nullopt; diff --git a/runtime/cudaq/algorithms/vqe.h b/runtime/cudaq/algorithms/vqe.h index 8adf37d561f..a989aac849b 100644 --- a/runtime/cudaq/algorithms/vqe.h +++ b/runtime/cudaq/algorithms/vqe.h @@ -24,12 +24,12 @@ template , Args...>>> static inline optimization_result remote_vqe(cudaq::quantum_platform &platform, QuantumKernel &&kernel, - cudaq::spin_op &H, cudaq::optimizer &optimizer, + const cudaq::spin_op &H, cudaq::optimizer &optimizer, cudaq::gradient *gradient, const int n_params, const std::size_t shots, Args &&...args) { auto ctx = std::make_unique("observe", shots); ctx->kernelName = cudaq::getKernelName(kernel); - ctx->spin = &H; + ctx->spin = cudaq::spin_op::canonicalize(H); platform.set_exec_ctx(ctx.get()); auto serializedArgsBuffer = serializeArgs(args...); platform.launchVQE(ctx->kernelName, serializedArgsBuffer.data(), gradient, H, diff --git a/runtime/cudaq/dynamics/boson_operators.h b/runtime/cudaq/boson_op.h similarity index 69% rename from runtime/cudaq/dynamics/boson_operators.h rename to runtime/cudaq/boson_op.h index 2a0e5935038..d5579636efa 100644 --- a/runtime/cudaq/dynamics/boson_operators.h +++ b/runtime/cudaq/boson_op.h @@ -12,11 +12,10 @@ #include #include -#include "cudaq/operators.h" +#include "cudaq/operators/operator_leafs.h" #include "cudaq/utils/matrix.h" namespace cudaq { - class boson_handler : public operator_handler { template friend class product_op; @@ -30,14 +29,14 @@ class boson_handler : public operator_handler { // representation allows us to perform a perfect in-place multiplication. int additional_terms; std::vector number_offsets; - int degree; + std::size_t degree; // 0 = I, Ad = 1, A = 2, AdA = 3 - boson_handler(int target, int op_code); + boson_handler(std::size_t target, int op_code); std::string op_code_to_string() const; - virtual std::string - op_code_to_string(std::unordered_map &dimensions) const override; + virtual std::string op_code_to_string( + std::unordered_map &dimensions) const override; void inplace_mult(const boson_handler &other); @@ -46,11 +45,11 @@ class boson_handler : public operator_handler { virtual std::string unique_id() const override; - virtual std::vector degrees() const override; + virtual std::vector degrees() const override; // constructors and destructors - boson_handler(int target); + boson_handler(std::size_t target); ~boson_handler() = default; @@ -61,7 +60,7 @@ class boson_handler : public operator_handler { /// @arg `dimensions` : A map specifying the dimension, that is the number of /// eigenstates, for each degree of freedom. virtual complex_matrix - to_matrix(std::unordered_map &dimensions, + to_matrix(std::unordered_map &dimensions, const std::unordered_map> ¶meters = {}) const override; @@ -75,12 +74,20 @@ class boson_handler : public operator_handler { // defined operators - static product_op create(int degree); - static product_op annihilate(int degree); - static product_op number(int degree); - - static sum_op position(int degree); - static sum_op momentum(int degree); + static boson_handler create(std::size_t degree); + static boson_handler annihilate(std::size_t degree); + static boson_handler number(std::size_t degree); }; +} // namespace cudaq + +// needs to be down here such that the handler is defined +// before we include the template declarations that depend on it +#include "cudaq/operators.h" -} // namespace cudaq \ No newline at end of file +namespace cudaq::boson { +product_op create(std::size_t target); +product_op annihilate(std::size_t target); +product_op number(std::size_t target); +sum_op position(std::size_t target); +sum_op momentum(std::size_t target); +} // namespace cudaq::boson diff --git a/runtime/cudaq/builder/kernel_builder.h b/runtime/cudaq/builder/kernel_builder.h index c04c392b522..c0f45e31550 100644 --- a/runtime/cudaq/builder/kernel_builder.h +++ b/runtime/cudaq/builder/kernel_builder.h @@ -415,14 +415,10 @@ class kernel_builder : public details::kernel_builder_base { std::vector arguments; /// @brief Return a string representation of the given spin operator. - /// Throw an exception if the spin operator provided is not a single term. - auto toPauliWord(const std::variant &term) { - if (term.index()) { - auto op = std::get(term); - if (op.num_terms() > 1) - throw std::runtime_error( - "exp_pauli requires a spin operator with a single term."); - return op.to_string(false); + auto toPauliWord(const std::variant &term) { + if (std::holds_alternative(term)) { + auto op = std::get(term); + return op.get_pauli_word(); } return std::get(term); } @@ -721,7 +717,9 @@ class kernel_builder : public details::kernel_builder_base { /// representing a register of qubits. template void exp_pauli(const ParamT &theta, const QuakeValue &qubits, - const std::variant &op) { + const std::variant &op) { + // FIXME: this ignores the coefficient defined in the spin_op_term - + // it would be better to just force passing the pauli word only auto pauliWord = toPauliWord(op); std::vector qubitValues{qubits}; if constexpr (std::is_floating_point_v) @@ -735,8 +733,10 @@ class kernel_builder : public details::kernel_builder_base { /// list of QuakeValues representing a individual qubits. template void exp_pauli(const ParamT &theta, - const std::variant &op, + const std::variant &op, QubitArgs &&...qubits) { + // FIXME: this ignores the coefficient defined in the spin_op_term - + // it would be better to just force passing the pauli word only auto pauliWord = toPauliWord(op); std::vector qubitValues{qubits...}; if constexpr (std::is_floating_point_v) diff --git a/runtime/cudaq/domains/chemistry/CMakeLists.txt b/runtime/cudaq/domains/chemistry/CMakeLists.txt index 7f8f9b86e56..d89681f9341 100644 --- a/runtime/cudaq/domains/chemistry/CMakeLists.txt +++ b/runtime/cudaq/domains/chemistry/CMakeLists.txt @@ -9,7 +9,7 @@ add_library(cudaq-chemistry SHARED molecule.cpp) set (CHEMISTRY_DEPENDENCIES "") -list(APPEND CHEMISTRY_DEPENDENCIES cudaq-spin) +list(APPEND CHEMISTRY_DEPENDENCIES cudaq-operator) add_openmp_configurations(cudaq-chemistry CHEMISTRY_DEPENDENCIES) target_link_libraries(cudaq-chemistry PRIVATE ${CHEMISTRY_DEPENDENCIES}) diff --git a/runtime/cudaq/domains/chemistry/molecule.h b/runtime/cudaq/domains/chemistry/molecule.h index 396c1f3c3d3..1b770e657cd 100644 --- a/runtime/cudaq/domains/chemistry/molecule.h +++ b/runtime/cudaq/domains/chemistry/molecule.h @@ -8,7 +8,7 @@ #pragma once -#include "cudaq/spin_op.h" +#include "cudaq/operators.h" namespace cudaq { diff --git a/runtime/cudaq/dynamics/evaluation.h b/runtime/cudaq/dynamics/evaluation.h deleted file mode 100644 index f526c684d05..00000000000 --- a/runtime/cudaq/dynamics/evaluation.h +++ /dev/null @@ -1,213 +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/utils/matrix.h" -#include -#include -#include -#include - -#include "helpers.h" -#include "operator_leafs.h" - -namespace cudaq { - -template -class operator_arithmetics { -public: - operator_arithmetics( - std::unordered_map &dimensions, - const std::unordered_map> ¶meters); - - /// Whether to inject tensor products with identity to each term in the - /// sum to ensure that all terms are acting on the same degrees of freedom - /// by the time they are added. - const bool pad_sum_terms; - /// Whether to inject tensor products with identity to each term in the - /// product to ensure that each term has its full size by the time they - /// are multiplied. - const bool pad_product_terms; - - /// @brief Accesses the relevant data to evaluate an operator expression - /// in the leaf nodes, that is in elementary and scalar operators. - EvalTy evaluate(const operator_handler &op); - EvalTy evaluate(const scalar_operator &op); - - /// @brief Computes the tensor product of two operators that act on different - /// degrees of freedom. - EvalTy tensor(EvalTy &&val1, EvalTy &&val2); - - /// @brief Multiplies two operators that act on the same degrees of freedom. - EvalTy mul(EvalTy &&val1, EvalTy &&val2); - - /// @brief Multiplies an evaluated operator with a scalar. - EvalTy mul(const scalar_operator &scalar, EvalTy &&op); - - /// @brief Adds two operators that act on the same degrees of freedom. - EvalTy add(EvalTy &&val1, EvalTy &&val2); -}; - -template <> -class operator_arithmetics { - -private: - std::unordered_map &dimensions; // may be updated during evaluation - const std::unordered_map> ¶meters; - - // Given a matrix representation that acts on the given degrees or freedom, - // sorts the degrees and permutes the matrix to match that canonical order. - void canonicalize(complex_matrix &matrix, std::vector °rees) { - auto current_degrees = degrees; - std::sort(degrees.begin(), degrees.end(), - operator_handler::canonical_order); - if (current_degrees != degrees) { - auto permutation = cudaq::detail::compute_permutation( - current_degrees, degrees, this->dimensions); - cudaq::detail::permute_matrix(matrix, permutation); - } - } - -public: - const bool pad_sum_terms = true; - const bool pad_product_terms = true; - - operator_arithmetics( - std::unordered_map &dimensions, - const std::unordered_map> ¶meters) - : dimensions(dimensions), parameters(parameters) {} - - operator_handler::matrix_evaluation evaluate(const operator_handler &op) { - return operator_handler::matrix_evaluation( - op.degrees(), op.to_matrix(this->dimensions, this->parameters)); - } - - operator_handler::matrix_evaluation evaluate(const scalar_operator &op) { - return operator_handler::matrix_evaluation({}, - op.to_matrix(this->parameters)); - } - - operator_handler::matrix_evaluation - tensor(operator_handler::matrix_evaluation &&op1, - operator_handler::matrix_evaluation &&op2) { - op1.degrees.reserve(op1.degrees.size() + op2.degrees.size()); - for (auto d : op2.degrees) { - assert(std::find(op1.degrees.cbegin(), op1.degrees.cend(), d) == - op1.degrees.cend()); - op1.degrees.push_back(d); - } - - auto matrix = // matrix order needs to be reversed to be consistent - cudaq::kronecker(std::move(op2.matrix), std::move(op1.matrix)); - this->canonicalize(matrix, op1.degrees); - return operator_handler::matrix_evaluation(std::move(op1.degrees), - std::move(matrix)); - } - - operator_handler::matrix_evaluation - mul(const scalar_operator &scalar, operator_handler::matrix_evaluation &&op) { - auto matrix = scalar.evaluate(this->parameters) * std::move(op.matrix); - return operator_handler::matrix_evaluation(std::move(op.degrees), - std::move(matrix)); - } - - operator_handler::matrix_evaluation - mul(operator_handler::matrix_evaluation &&op1, - operator_handler::matrix_evaluation &&op2) { - // Elementary operators have sorted degrees such that we have a unique - // convention for how to define the matrix. Tensor products permute the - // computed matrix if necessary to guarantee that all operators always have - // sorted degrees. - assert(op1.degrees == op2.degrees); - op1.matrix *= std::move(op2.matrix); - return operator_handler::matrix_evaluation(std::move(op1.degrees), - std::move(op1.matrix)); - } - - operator_handler::matrix_evaluation - add(operator_handler::matrix_evaluation &&op1, - operator_handler::matrix_evaluation &&op2) { - // Elementary operators have sorted degrees such that we have a unique - // convention for how to define the matrix. Tensor products permute the - // computed matrix if necessary to guarantee that all operators always have - // sorted degrees. - assert(op1.degrees == op2.degrees); - op1.matrix += std::move(op2.matrix); - return operator_handler::matrix_evaluation(std::move(op1.degrees), - std::move(op1.matrix)); - } -}; - -template <> -class operator_arithmetics { - -private: - std::unordered_map &dimensions; // may be updated during evaluation - const std::unordered_map> ¶meters; - -public: - const bool pad_sum_terms = true; - const bool pad_product_terms = false; - - operator_arithmetics( - std::unordered_map &dimensions, - const std::unordered_map> ¶meters) - : dimensions(dimensions), parameters(parameters) {} - - operator_handler::canonical_evaluation evaluate(const operator_handler &op) { - auto canon_str = op.op_code_to_string(this->dimensions); - operator_handler::canonical_evaluation eval; - eval.push_back( - std::make_pair(std::complex(1.), std::move(canon_str))); - return eval; - } - - operator_handler::canonical_evaluation - evaluate(const scalar_operator &scalar) { - operator_handler::canonical_evaluation eval; - eval.push_back(std::make_pair(scalar.evaluate(this->parameters), "")); - return eval; - } - - operator_handler::canonical_evaluation - tensor(operator_handler::canonical_evaluation &&val1, - operator_handler::canonical_evaluation &&val2) { - assert(val1.terms.size() == 1 && val2.terms.size() == 1); - assert(val2.terms[0].first == - std::complex(1.)); // should be trivial - val1.push_back(val2.terms[0].second); - return std::move(val1); - } - - operator_handler::canonical_evaluation - mul(const scalar_operator &scalar, - operator_handler::canonical_evaluation &&op) { - throw std::runtime_error( - "multiplication should never be called on canonicalized operator - " - "product padding is disabled"); - } - - operator_handler::canonical_evaluation - mul(operator_handler::canonical_evaluation &&val1, - operator_handler::canonical_evaluation &&val2) { - throw std::runtime_error( - "multiplication should never be called on canonicalized operator - " - "product padding is disabled"); - } - - operator_handler::canonical_evaluation - add(operator_handler::canonical_evaluation &&val1, - operator_handler::canonical_evaluation &&val2) { - assert(val2.terms.size() == 1); - val1.push_back(std::move(val2.terms[0])); - return std::move(val1); - } -}; - -} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/helpers.h b/runtime/cudaq/dynamics/helpers.h deleted file mode 100644 index fa89eadcdc7..00000000000 --- a/runtime/cudaq/dynamics/helpers.h +++ /dev/null @@ -1,40 +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/utils/matrix.h" -#include -#include - -namespace cudaq { -namespace detail { - -/// Generates all possible states for the given dimensions ordered according -/// to the sequence of degrees (ordering is relevant if dimensions differ). -std::vector -generate_all_states(const std::vector °rees, - const std::unordered_map &dimensions); - -/// Computes a vector describing the permutation to reorder a matrix that is -/// ordered according to `op_degrees` to apply to `canon_degrees` instead. -/// The dimensions define the number of levels for each degree of freedom. -/// The degrees of freedom in `op_degrees` and `canon_degrees` have to match. -std::vector -compute_permutation(const std::vector &op_degrees, - const std::vector &canon_degrees, - const std::unordered_map dimensions); - -// Permutes the given matrix according to the given permutation. -// If states is the current order of vector entries on which the given matrix -// acts, and permuted_states is the desired order of an array on which the -// permuted matrix should act, then the permutation is defined such that -// [states[i] for i in permutation] produces permuted_states. -void permute_matrix(cudaq::complex_matrix &matrix, - const std::vector &permutation); - -} // namespace detail -} // namespace cudaq diff --git a/runtime/cudaq/dynamics/spin_operators.h b/runtime/cudaq/dynamics/spin_operators.h deleted file mode 100644 index b86b47e5bc3..00000000000 --- a/runtime/cudaq/dynamics/spin_operators.h +++ /dev/null @@ -1,94 +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. * - ******************************************************************************/ - -#pragma once - -#include -#include -#include - -#include "cudaq/operators.h" -#include "cudaq/spin_op.h" // for pauli -#include "cudaq/utils/matrix.h" - -namespace cudaq { - -class spin_handler : public operator_handler { - template - friend class product_op; - -private: - // I = 0, Z = 1, X = 2, Y = 3 - int op_code; - int degree; - - spin_handler(int target, int op_code); - - // private helpers - - std::string op_code_to_string() const; - virtual std::string - op_code_to_string(std::unordered_map &dimensions) const override; - - std::complex inplace_mult(const spin_handler &other); - -public: - // read-only properties - - pauli as_pauli() const; - - virtual std::string unique_id() const override; - - virtual std::vector degrees() const override; - - int target() const; - - // constructors and destructors - - spin_handler(int target); - - spin_handler(pauli p, int target); - - ~spin_handler() = default; - - // evaluations - - /// @brief Computes the matrix representation of a Pauli string. - /// By default, the ordering of the matrix matches the ordering of the Pauli - /// string, - static complex_matrix to_matrix(std::string pauli, - std::complex coeff = 1., - bool invert_order = false); - - /// @brief Return the `matrix_handler` 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}`. - virtual complex_matrix - to_matrix(std::unordered_map &dimensions, - const std::unordered_map> - ¶meters = {}) const override; - - virtual std::string to_string(bool include_degrees) const override; - - // comparisons - - bool operator==(const spin_handler &other) const; - - // defined operators - - static product_op i(int degree); - static product_op z(int degree); - static product_op x(int degree); - static product_op y(int degree); - static sum_op plus(int degree); - static sum_op minus(int degree); -}; - -} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics_integrators.h b/runtime/cudaq/dynamics_integrators.h index 588e7d580f1..97e2eb7b762 100644 --- a/runtime/cudaq/dynamics_integrators.h +++ b/runtime/cudaq/dynamics_integrators.h @@ -8,45 +8,12 @@ #pragma once -#include "cudaq/base_integrator.h" -#include "cudaq/base_time_stepper.h" -#include "cudaq/operators.h" -#include +#include "cudaq/algorithms/integrator.h" -namespace cudaq { -namespace integrators { - -class runge_kutta : public cudaq::base_integrator { -public: - /// @brief The default `Runge-Kutta` integration order. - // Note: we use 4th order as the default since (1) it produces better - // convergence/stability and (2) that is the typical order that the name - // `Runge-Kutta` is associated with (e.g., RK method can be generalized to - // cover 1st order Euler method) - static constexpr int default_order = 4; - /// @brief Constructor - // (1) Integration order - // (2) Max step size: if none provided, the schedule of time points where we - // want to compute and save intermediate results will be used. If provided, - // the integrator will make sub-steps no larger than this value to integrate - // toward scheduled time points. - runge_kutta(int order = default_order, - const std::optional &max_step_size = {}); - /// @brief Integrate toward a specified time point. - void integrate(double targetTime) override; - /// @brief Set the initial state of the integration - void setState(const cudaq::state &initialState, double t0) override; - /// @brief Get the current state of the integrator - // Returns the current time point and state. - std::pair getState() override; - /// @brief Clone the current integrator. - std::shared_ptr clone() override; - -private: - double m_t; - std::shared_ptr m_state; - int m_order; - std::optional m_dt; -}; -} // namespace integrators -} // namespace cudaq +namespace { +[[deprecated("This header is deprecated - please use " + "cudaq/algorithms/integrator.h instead.")]] constexpr static int + dynamics_integrator_header_is_deprecated = 0; +constexpr static int please_use_integrator_header = + dynamics_integrator_header_is_deprecated; +} // namespace diff --git a/runtime/cudaq/dynamics/fermion_operators.h b/runtime/cudaq/fermion_op.h similarity index 76% rename from runtime/cudaq/dynamics/fermion_operators.h rename to runtime/cudaq/fermion_op.h index 2de095754f8..c76d25a1596 100644 --- a/runtime/cudaq/dynamics/fermion_operators.h +++ b/runtime/cudaq/fermion_op.h @@ -12,7 +12,7 @@ #include #include -#include "cudaq/operators.h" +#include "cudaq/operators/operator_leafs.h" #include "cudaq/utils/matrix.h" namespace cudaq { @@ -36,16 +36,16 @@ class fermion_handler : public operator_handler { // 9 = 1001 = I int8_t op_code; bool commutes; - int degree; + std::size_t degree; // Note that this constructor is chosen to be independent // on the internal encoding; to be less critic, we here use the usual // 0 = I, Ad = 1, A = 2, AdA = 3 - fermion_handler(int target, int op_id); + fermion_handler(std::size_t target, int op_id); std::string op_code_to_string() const; - virtual std::string - op_code_to_string(std::unordered_map &dimensions) const override; + virtual std::string op_code_to_string( + std::unordered_map &dimensions) const override; #if !defined(NDEBUG) // Here to check if my reasoning regarding only ever needing the operators @@ -65,11 +65,11 @@ class fermion_handler : public operator_handler { virtual std::string unique_id() const override; - virtual std::vector degrees() const override; + virtual std::vector degrees() const override; // constructors and destructors - fermion_handler(int target); + fermion_handler(std::size_t target); fermion_handler(const fermion_handler &other); @@ -86,7 +86,7 @@ class fermion_handler : public operator_handler { /// @arg `dimensions` : A map specifying the dimension, that is the number of /// eigenstates, for each degree of freedom. virtual complex_matrix - to_matrix(std::unordered_map &dimensions, + to_matrix(std::unordered_map &dimensions, const std::unordered_map> ¶meters = {}) const override; @@ -100,13 +100,22 @@ class fermion_handler : public operator_handler { // defined operators - static product_op create(int degree); - static product_op annihilate(int degree); - static product_op number(int degree); + static fermion_handler create(std::size_t degree); + static fermion_handler annihilate(std::size_t degree); + static fermion_handler number(std::size_t degree); // Note that we don't define position and momentum here, since physically they // do not make much sense; see e.g. // https://physics.stackexchange.com/questions/319296/why-does-a-fermionic-hamiltonian-always-obey-fermionic-parity-symmetry }; +} // namespace cudaq + +// needs to be down here such that the handler is defined +// before we include the template declarations that depend on it +#include "cudaq/operators.h" -} // namespace cudaq \ No newline at end of file +namespace cudaq::fermion { +product_op create(std::size_t target); +product_op annihilate(std::size_t target); +product_op number(std::size_t target); +} // namespace cudaq::fermion diff --git a/runtime/cudaq/dynamics/matrix_operators.h b/runtime/cudaq/matrix_op.h similarity index 80% rename from runtime/cudaq/dynamics/matrix_operators.h rename to runtime/cudaq/matrix_op.h index 8d96ee9f237..d3d0cd02f4c 100644 --- a/runtime/cudaq/dynamics/matrix_operators.h +++ b/runtime/cudaq/matrix_op.h @@ -12,7 +12,7 @@ #include #include -#include "cudaq/operators.h" +#include "cudaq/operators/operator_leafs.h" #include "cudaq/utils/matrix.h" namespace cudaq { @@ -43,18 +43,19 @@ class matrix_handler : public operator_handler { template static std::string type_prefix(); - virtual std::string - op_code_to_string(std::unordered_map &dimensions) const override; + virtual std::string op_code_to_string( + std::unordered_map &dimensions) const override; protected: std::string op_code; commutation_relations group; bool commutes; - std::vector targets; + std::vector targets; - matrix_handler(std::string operator_id, const std::vector °rees, + matrix_handler(std::string operator_id, + const std::vector °rees, const commutation_behavior &behavior = commutation_behavior()); - matrix_handler(std::string operator_id, std::vector &°rees, + matrix_handler(std::string operator_id, std::vector &°rees, const commutation_behavior &behavior = commutation_behavior()); public: @@ -93,7 +94,7 @@ class matrix_handler : public operator_handler { /// the operator acts on, and an unordered map from string to complex /// double that contains additional parameters the operator may use. static void define(std::string operator_id, - std::vector expected_dimensions, + std::vector expected_dimensions, matrix_callback &&create); /// @brief Instantiates a custom operator. @@ -101,7 +102,7 @@ class matrix_handler : public operator_handler { /// defined. /// @arg degrees : the degrees of freedom that the operator acts upon. static product_op - instantiate(std::string operator_id, const std::vector °rees, + instantiate(std::string operator_id, const std::vector °rees, const commutation_behavior &behavior = commutation_behavior()); /// @brief Instantiates a custom operator. @@ -109,7 +110,7 @@ class matrix_handler : public operator_handler { /// defined. /// @arg degrees : the degrees of freedom that the operator acts upon. static product_op - instantiate(std::string operator_id, std::vector &°rees, + instantiate(std::string operator_id, std::vector &°rees, const commutation_behavior &behavior = commutation_behavior()); // read-only properties @@ -119,11 +120,11 @@ class matrix_handler : public operator_handler { virtual std::string unique_id() const override; - virtual std::vector degrees() const override; + virtual std::vector degrees() const override; // constructors and destructors - matrix_handler(int target); + matrix_handler(std::size_t target); template , bool> = true> @@ -161,7 +162,7 @@ class matrix_handler : public operator_handler { /// that the operator acts on. Example for two, 2-level /// degrees of freedom: `{0 : 2, 1 : 2}`. virtual complex_matrix - to_matrix(std::unordered_map &dimensions, + to_matrix(std::unordered_map &dimensions, const std::unordered_map> ¶meters = {}) const override; @@ -175,13 +176,25 @@ class matrix_handler : public operator_handler { // predefined operators - static product_op number(int degree); - static product_op parity(int degree); - static product_op position(int degree); - static product_op momentum(int degree); + static matrix_handler number(std::size_t degree); + static matrix_handler parity(std::size_t degree); + static matrix_handler position(std::size_t degree); + static matrix_handler momentum(std::size_t degree); /// Operators that accept parameters at runtime. - static product_op squeeze(int degree); - static product_op displace(int degree); + static matrix_handler squeeze(std::size_t degree); + static matrix_handler displace(std::size_t degree); }; +} // namespace cudaq + +// needs to be down here such that the handler is defined +// before we include the template declarations that depend on it +#include "cudaq/operators.h" -} // namespace cudaq \ No newline at end of file +namespace cudaq::operators { +product_op number(std::size_t target); +product_op parity(std::size_t target); +product_op position(std::size_t target); +product_op momentum(std::size_t target); +product_op squeeze(std::size_t target); +product_op displace(std::size_t target); +} // namespace cudaq::operators diff --git a/runtime/cudaq/operators.h b/runtime/cudaq/operators.h index 7d378deefe7..e6a02cf9cd0 100644 --- a/runtime/cudaq/operators.h +++ b/runtime/cudaq/operators.h @@ -14,9 +14,9 @@ #include #include -#include "dynamics/evaluation.h" -#include "dynamics/operator_leafs.h" -#include "dynamics/templates.h" +#include "operators/evaluation.h" +#include "operators/operator_leafs.h" +#include "operators/templates.h" #include "utils/cudaq_utils.h" #include "utils/matrix.h" @@ -33,11 +33,12 @@ enum class pauli; // utility functions for backward compatibility -#define SPIN_OPS_BACKWARD_COMPATIBILITY \ +#define SPIN_OPS_BACKWARD_COMPATIBILITY(deprecation_message) \ template ::value && \ std::is_same::value, \ - bool> = true> + bool> = true> \ + [[deprecated(deprecation_message)]] /// @brief Represents an operator expression consisting of a sum of terms, where /// each term is a product of elementary and scalar operators. Operator @@ -64,15 +65,16 @@ class sum_op { EvalTy evaluate(operator_arithmetics arithmetics) const; protected: - std::unordered_map + std::unordered_map term_map; // quick access to term index given its id (used for aggregating // terms) std::vector> terms; std::vector coefficients; + bool is_default = false; - constexpr sum_op(){}; - sum_op(const sum_op &other, bool sized, int size); - sum_op(sum_op &&other, bool sized, int size); + constexpr sum_op(bool is_default) : is_default(is_default){}; + sum_op(const sum_op &other, bool is_default, std::size_t size); + sum_op(sum_op &&other, bool is_default, std::size_t size); public: // called const_iterator because it will *not* modify the sum, @@ -80,8 +82,12 @@ class sum_op { struct const_iterator { private: const sum_op *sum; - typename std::unordered_map::const_iterator iter; product_op current_val; + std::size_t current_idx; + + const_iterator(const sum_op *sum, std::size_t idx, + product_op &&value) + : sum(sum), current_val(std::move(value)), current_idx(idx) {} public: using iterator_category = std::forward_iterator_tag; @@ -90,19 +96,17 @@ class sum_op { using pointer = product_op *; using reference = product_op &; - const_iterator(const sum_op *sum) - : const_iterator(sum, sum->term_map.begin()) {} + const_iterator(const sum_op *sum) : const_iterator(sum, 0) {} - const_iterator(const sum_op *sum, - std::unordered_map::const_iterator &&it) - : sum(sum), iter(std::move(it)), current_val(1.) { - if (iter != sum->term_map.end()) - current_val = product_op(sum->coefficients[iter->second], - sum->terms[iter->second]); + const_iterator(const sum_op *sum, std::size_t idx) + : sum(sum), current_val(1.), current_idx(idx) { + if (sum && current_idx < sum->num_terms()) + current_val = product_op(sum->coefficients[current_idx], + sum->terms[current_idx]); } bool operator==(const const_iterator &other) const { - return sum == other.sum && iter == other.iter; + return sum == other.sum && current_idx == other.current_idx; } bool operator!=(const const_iterator &other) const { @@ -116,36 +120,51 @@ class sum_op { // prefix const_iterator &operator++() { - if (++iter != sum->term_map.end()) - current_val = product_op(sum->coefficients[iter->second], - sum->terms[iter->second]); + if (++current_idx < sum->num_terms()) + current_val = product_op(sum->coefficients[current_idx], + sum->terms[current_idx]); return *this; } // postfix - const_iterator operator++(int) { return const_iterator(sum, iter++); } + const_iterator operator++(int) { + auto iter = const_iterator(sum, current_idx, std::move(current_val)); + ++(*this); + return iter; + } }; /// @brief Get iterator to beginning of operator terms const_iterator begin() const { return const_iterator(this); } /// @brief Get iterator to end of operator terms - const_iterator end() const { - return const_iterator(this, this->term_map.cend()); - } + const_iterator end() const { return const_iterator(this, this->num_terms()); } // read-only properties /// @brief The degrees of freedom that the operator acts on. - /// By default, degrees reflect the ordering convention (endianness) used in - /// CUDA-Q, and the ordering of the matrix returned by default by `to_matrix`. - std::vector degrees(bool application_order = true) const; + /// The order of degrees is from smallest to largest and reflect + /// the ordering of the matrix returned by `to_matrix`. + /// Specifically, the indices of a statevector with two qubits are {00, 01, + /// 10, 11}. An ordering of degrees {0, 1} then indicates that a state where + /// the qubit with index 0 equals 1 with probability 1 is given by + /// the vector {0., 1., 0., 0.}. + std::vector degrees() const; + std::size_t min_degree() const; + std::size_t max_degree() const; /// @brief Return the number of operator terms that make up this operator sum. std::size_t num_terms() const; // constructors and destructors + // A default initialized sum will act as both the additive + // and multiplicative identity. To construct a true "0" value + // (neutral element for addition only), use sum_op::empty(). + constexpr sum_op() : is_default(true){}; + + sum_op(std::size_t size); + template , Args>...>::value && @@ -203,23 +222,22 @@ class sum_op { // evaluations - /// @brief Return the sum_op as a string. - std::string to_string() const; - /// @brief Return the matrix representation of the operator. - /// By default, the matrix is ordered according to the convention (endianness) - /// used in CUDA-Q, and the ordering returned by default by `degrees`. + /// The matrix is ordered according to the convention (endianness) + /// used in CUDA-Q, and the ordering returned by `degrees`. See + /// the documentation for `degrees` for more detail. /// @arg `dimensions` : A mapping that specifies the number of levels, /// that is, the dimension of each degree of freedom /// that the operator acts on. Example for two, 2-level /// degrees of freedom: `{0:2, 1:2}`. /// @arg `parameters` : A map of the parameter names to their concrete, /// complex values. + /// @arg `invert_order`: if set to true, the ordering convention is reversed. complex_matrix - to_matrix(std::unordered_map dimensions = {}, + to_matrix(std::unordered_map dimensions = {}, const std::unordered_map> ¶meters = {}, - bool application_order = true) const; + bool invert_order = false) const; // comparisons @@ -347,74 +365,97 @@ class sum_op { static sum_op empty(); static product_op identity(); - static product_op identity(int target); + static product_op identity(std::size_t target); // handler specific operators HANDLER_SPECIFIC_TEMPLATE(matrix_handler) - static product_op number(int target); + static product_op number(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(matrix_handler) - static product_op parity(int target); + static product_op parity(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(matrix_handler) - static product_op position(int target); + static product_op position(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(matrix_handler) - static product_op momentum(int target); + static product_op momentum(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(matrix_handler) - static product_op squeeze(int target); + static product_op squeeze(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(matrix_handler) - static product_op displace(int target); + static product_op displace(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(spin_handler) - static product_op i(int target); + static product_op i(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(spin_handler) - static product_op x(int target); + static product_op x(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(spin_handler) - static product_op y(int target); + static product_op y(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(spin_handler) - static product_op z(int target); + static product_op z(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(spin_handler) - static sum_op plus(int target); + static sum_op plus(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(spin_handler) - static sum_op minus(int target); + static sum_op minus(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(boson_handler) - static product_op create(int target); + static product_op create(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(boson_handler) - static product_op annihilate(int target); + static product_op annihilate(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(boson_handler) - static product_op number(int target); + static product_op number(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(boson_handler) - static sum_op position(int target); + static sum_op position(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(boson_handler) - static sum_op momentum(int target); + static sum_op momentum(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(fermion_handler) - static product_op create(int target); + static product_op create(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(fermion_handler) - static product_op annihilate(int target); + static product_op annihilate(std::size_t target); HANDLER_SPECIFIC_TEMPLATE(fermion_handler) - static product_op number(int target); + static product_op number(std::size_t target); // general utility functions + /// @brief Return the string representation of the operator. + std::string to_string() const; + + /// @brief Print the string representation of the operator to the standard + /// output. void dump() const; + /// Removes all terms from the sum for which the absolute value of + /// the coefficient is below the given tolerance + sum_op & + trim(double tol = 0.0, + const std::unordered_map> ¶meters = + {}); + + /// Removes all identity operators from the operator. + sum_op &canonicalize(); + static sum_op canonicalize(const sum_op &orig); + + /// Expands the operator to act on all given degrees, applying identities as + /// needed. If an empty set is passed, canonicalizes all terms in the sum to + /// act on the same degrees of freedom. + sum_op &canonicalize(const std::set °rees); + static sum_op canonicalize(const sum_op &orig, + const std::set °rees); + std::vector> distribute_terms(std::size_t numChunks) const; // handler specific utility functions @@ -430,35 +471,75 @@ class sum_op { HANDLER_SPECIFIC_TEMPLATE(spin_handler) static sum_op random(std::size_t nQubits, std::size_t nTerms, - unsigned int seed); + unsigned int seed = std::random_device{}()); + + /// @brief Return the matrix representation of the operator. + /// By default, the matrix is ordered according to the convention (endianness) + /// used in CUDA-Q, and the ordering returned by `degrees`. See + /// the documentation for `degrees` for more detail. + /// @arg `dimensions` : A mapping that specifies the number of levels, + /// that is, the dimension of each degree of freedom + /// that the operator acts on. Example for two, 2-level + /// degrees of freedom: `{0:2, 1:2}`. + /// @arg `parameters` : A map of the parameter names to their concrete, + /// complex values. + /// @arg `invert_order`: if set to true, the ordering convention is reversed. + HANDLER_SPECIFIC_TEMPLATE(spin_handler) + csr_spmatrix + to_sparse_matrix(std::unordered_map dimensions = {}, + const std::unordered_map> + ¶meters = {}, + bool invert_order = false) const; + + HANDLER_SPECIFIC_TEMPLATE(spin_handler) + std::vector get_data_representation() const; // utility functions for backward compatibility + /// @cond - SPIN_OPS_BACKWARD_COMPATIBILITY + SPIN_OPS_BACKWARD_COMPATIBILITY( + "serialization format changed - use the constructor without a size_t " + "argument to create a spin_op from the new format") sum_op(const std::vector &input_vec, std::size_t nQubits); - SPIN_OPS_BACKWARD_COMPATIBILITY + SPIN_OPS_BACKWARD_COMPATIBILITY( + "construction from binary symplectic form will no longer be supported") sum_op(const std::vector> &bsf_terms, const std::vector> &coeffs); - SPIN_OPS_BACKWARD_COMPATIBILITY + SPIN_OPS_BACKWARD_COMPATIBILITY( + "serialization format changed - use get_data_representation instead") std::vector getDataRepresentation() const; - SPIN_OPS_BACKWARD_COMPATIBILITY + SPIN_OPS_BACKWARD_COMPATIBILITY( + "data tuple is no longer used for serialization - use " + "get_data_representation instead") + std::tuple, std::size_t> getDataTuple() const; + + SPIN_OPS_BACKWARD_COMPATIBILITY("raw data access will no longer be supported") std::pair>, std::vector>> get_raw_data() const; - SPIN_OPS_BACKWARD_COMPATIBILITY + SPIN_OPS_BACKWARD_COMPATIBILITY( + "use to_string(), get_term_id or get_pauli_word depending on your use " + "case - see release notes for more detail") std::string to_string(bool printCoeffs) const; - SPIN_OPS_BACKWARD_COMPATIBILITY + SPIN_OPS_BACKWARD_COMPATIBILITY( + "iterate over the operator instead to access each term") void for_each_term(std::function &)> &&functor) const; - SPIN_OPS_BACKWARD_COMPATIBILITY + SPIN_OPS_BACKWARD_COMPATIBILITY( + "iterate over each term in the operator instead and use as_pauli to " + "access each pauli") void for_each_pauli(std::function &&functor) const; - SPIN_OPS_BACKWARD_COMPATIBILITY + SPIN_OPS_BACKWARD_COMPATIBILITY( + "is_identity will no longer be supported on an entire sum_op, but will " + "continue to be supported on each term") bool is_identity() const; + + /// @endcond }; /// @brief Represents an operator expression consisting of a product of @@ -485,10 +566,6 @@ class product_op { !std::is_same(0)), std::false_type>::value; -#if !defined(NDEBUG) - bool is_canonicalized() const; -#endif - typename std::vector::const_iterator find_insert_at(const HandlerTy &other); @@ -525,12 +602,13 @@ class product_op { // keep this constructor protected (otherwise it needs to ensure canonical // order) product_op(scalar_operator coefficient, - const std::vector &atomic_operators, int size = 0); + const std::vector &atomic_operators, + std::size_t size = 0); // keep this constructor protected (otherwise it needs to ensure canonical // order) product_op(scalar_operator coefficient, - std::vector &&atomic_operators, int size = 0); + std::vector &&atomic_operators, std::size_t size = 0); public: struct const_iterator { @@ -588,15 +666,20 @@ class product_op { // read-only properties +#if !defined(NDEBUG) + bool is_canonicalized() const; +#endif + /// @brief The degrees of freedom that the operator acts on. - /// By default, degrees reflect the ordering convention (endianness) used in - /// CUDA-Q, and the ordering of the matrix returned by default by `to_matrix`. - /// + /// The order of degrees is from smallest to largest and reflect + /// the ordering of the matrix returned by `to_matrix`. /// Specifically, the indices of a statevector with two qubits are {00, 01, /// 10, 11}. An ordering of degrees {0, 1} then indicates that a state where /// the qubit with index 0 equals 1 with probability 1 is given by /// the vector {0., 1., 0., 0.}. - std::vector degrees(bool application_order = true) const; + std::vector degrees() const; + std::size_t min_degree() const; + std::size_t max_degree() const; /// @brief Return the number of operator terms that make up this product /// operator. @@ -615,6 +698,17 @@ class product_op { constexpr product_op() {} + constexpr product_op(std::size_t first_degree, std::size_t last_degree) { + static_assert(std::is_constructible_v, + "operator handlers must have a constructor that take a " + "single degree of " + "freedom and returns the identity operator on that degree."); + if (last_degree > first_degree) // being a bit permissive here + this->operators.reserve(last_degree - first_degree); + for (auto degree = first_degree; degree < last_degree; ++degree) + this->operators.push_back(HandlerTy(degree)); + } + product_op(double coefficient); product_op(std::complex coefficient); @@ -636,10 +730,10 @@ class product_op { const matrix_handler::commutation_behavior &behavior); // copy constructor - product_op(const product_op &other, int size = 0); + product_op(const product_op &other, std::size_t size = 0); // move constructor - product_op(product_op &&other, int size = 0); + product_op(product_op &&other, std::size_t size = 0); ~product_op() = default; @@ -659,23 +753,26 @@ class product_op { // evaluations - /// @brief Return the `product_op` as a string. - std::string to_string() const; + std::complex evaluate_coefficient( + const std::unordered_map> ¶meters = + {}) const; /// @brief Return the matrix representation of the operator. /// By default, the matrix is ordered according to the convention (endianness) - /// used in CUDA-Q, and the ordering returned by default by `degrees`. + /// used in CUDA-Q, and the ordering returned by `degrees`. See + /// the documentation for `degrees` for more detail. /// @arg `dimensions` : A mapping that specifies the number of levels, /// that is, the dimension of each degree of freedom /// that the operator acts on. Example for two, 2-level /// degrees of freedom: `{0:2, 1:2}`. /// @arg `parameters` : A map of the parameter names to their concrete, /// complex values. + /// @arg `invert_order`: if set to true, the ordering convention is reversed. complex_matrix - to_matrix(std::unordered_map dimensions = {}, + to_matrix(std::unordered_map dimensions = {}, const std::unordered_map> ¶meters = {}, - bool application_order = true) const; + bool invert_order = false) const; // comparisons @@ -816,23 +913,62 @@ class product_op { // of the coefficient. bool is_identity() const; + /// @brief Return the string representation of the operator. + std::string to_string() const; + + /// @brief Print the string representation of the operator to the standard + /// output. void dump() const; + /// Removes all identity operators from the operator. + product_op &canonicalize(); + static product_op canonicalize(const product_op &orig); + + /// Expands the operator to act on all given degrees, applying identities as + /// needed. + product_op &canonicalize(const std::set °rees); + static product_op + canonicalize(const product_op &orig, + const std::set °rees); + // handler specific utility functions HANDLER_SPECIFIC_TEMPLATE(spin_handler) // naming is not very general std::size_t num_qubits() const; HANDLER_SPECIFIC_TEMPLATE(spin_handler) - std::string get_pauli_word() const; + std::string get_pauli_word(std::size_t pad_identities = 0) const; HANDLER_SPECIFIC_TEMPLATE(spin_handler) std::vector get_binary_symplectic_form() const; + /// @brief Return the matrix representation of the operator. + /// By default, the matrix is ordered according to the convention (endianness) + /// used in CUDA-Q, and the ordering returned by `degrees`. See + /// the documentation for `degrees` for more detail. + /// @arg `dimensions` : A mapping that specifies the number of levels, + /// that is, the dimension of each degree of freedom + /// that the operator acts on. Example for two, 2-level + /// degrees of freedom: `{0:2, 1:2}`. + /// @arg `parameters` : A map of the parameter names to their concrete, + /// complex values. + /// @arg `invert_order`: if set to true, the ordering convention is reversed. + HANDLER_SPECIFIC_TEMPLATE(spin_handler) + csr_spmatrix + to_sparse_matrix(std::unordered_map dimensions = {}, + const std::unordered_map> + ¶meters = {}, + bool invert_order = false) const; + // utility functions for backward compatibility + /// @cond - SPIN_OPS_BACKWARD_COMPATIBILITY + SPIN_OPS_BACKWARD_COMPATIBILITY( + "use to_string(), get_term_id or get_pauli_word depending on your use " + "case - see release notes for more detail") std::string to_string(bool printCoeffs) const; + + /// @endcond }; /// @brief Representation of a time-dependent Hamiltonian for Rydberg system @@ -883,13 +1019,11 @@ class rydberg_hamiltonian { // type aliases for convenience typedef std::unordered_map> parameter_map; -typedef std::unordered_map dimension_map; +typedef std::unordered_map dimension_map; typedef sum_op matrix_op; typedef product_op matrix_op_term; -// commented out since this will require the complete replacement of spin ops -// everywhere -// typedef sum_op spin_op; -// typedef product_op spin_op_term; +typedef sum_op spin_op; +typedef product_op spin_op_term; typedef sum_op boson_op; typedef product_op boson_op_term; typedef sum_op fermion_op; @@ -906,4 +1040,5 @@ extern template class sum_op; extern template class sum_op; extern template class sum_op; #endif + } // namespace cudaq diff --git a/runtime/cudaq/dynamics/CMakeLists.txt b/runtime/cudaq/operators/CMakeLists.txt similarity index 89% rename from runtime/cudaq/dynamics/CMakeLists.txt rename to runtime/cudaq/operators/CMakeLists.txt index 968a87cb44a..adaf6990884 100644 --- a/runtime/cudaq/dynamics/CMakeLists.txt +++ b/runtime/cudaq/operators/CMakeLists.txt @@ -12,17 +12,16 @@ set(INTERFACE_POSITION_INDEPENDENT_CODE ON) set(CUDAQ_OPS_SRC callback.cpp - scalar_operators.cpp - spin_operators.cpp - boson_operators.cpp - fermion_operators.cpp - matrix_operators.cpp - product_operators.cpp - operator_sum.cpp + scalar_op.cpp + spin_op.cpp + boson_op.cpp + fermion_op.cpp + matrix_op.cpp + product_op.cpp + sum_op.cpp rydberg_hamiltonian.cpp - evolution.cpp + evaluation.cpp handler.cpp - schedule.cpp helpers.cpp ) diff --git a/runtime/cudaq/dynamics/boson_operators.cpp b/runtime/cudaq/operators/boson_op.cpp similarity index 83% rename from runtime/cudaq/dynamics/boson_operators.cpp rename to runtime/cudaq/operators/boson_op.cpp index f78bf6374e3..2bfb2332838 100644 --- a/runtime/cudaq/dynamics/boson_operators.cpp +++ b/runtime/cudaq/operators/boson_op.cpp @@ -14,7 +14,7 @@ #include "cudaq/operators.h" #include "cudaq/utils/matrix.h" -#include "boson_operators.h" +#include "cudaq/boson_op.h" namespace cudaq { @@ -51,7 +51,7 @@ std::string boson_handler::op_code_to_string() const { } std::string boson_handler::op_code_to_string( - std::unordered_map &dimensions) const { + std::unordered_map &dimensions) const { auto it = dimensions.find(this->degree); if (it == dimensions.end()) throw std::runtime_error("missing dimension for degree " + @@ -127,14 +127,16 @@ std::string boson_handler::unique_id() const { return this->op_code_to_string() + std::to_string(this->degree); } -std::vector boson_handler::degrees() const { return {this->degree}; } +std::vector boson_handler::degrees() const { + return {this->degree}; +} // constructors -boson_handler::boson_handler(int target) +boson_handler::boson_handler(std::size_t target) : degree(target), additional_terms(0) {} -boson_handler::boson_handler(int target, int op_id) +boson_handler::boson_handler(std::size_t target, int op_id) : degree(target), additional_terms(0) { assert(0 <= op_id && op_id < 4); if (op_id == 1) // create @@ -148,7 +150,7 @@ boson_handler::boson_handler(int target, int op_id) // evaluations complex_matrix boson_handler::to_matrix( - std::unordered_map &dimensions, + std::unordered_map &dimensions, const std::unordered_map> ¶meters) const { auto it = dimensions.find(this->degree); @@ -189,7 +191,8 @@ complex_matrix boson_handler::to_matrix( std::string boson_handler::to_string(bool include_degrees) const { if (include_degrees) - return this->op_code_to_string() + "(" + std::to_string(this->degree) + ")"; + return this->unique_id(); // unique id for consistency with keys in some + // user facing maps else return this->op_code_to_string(); } @@ -204,26 +207,36 @@ bool boson_handler::operator==(const boson_handler &other) const { // defined operators -product_op boson_handler::create(int degree) { - return product_op(boson_handler(degree, 1)); +boson_handler boson_handler::create(std::size_t degree) { + return boson_handler(degree, 1); } -product_op boson_handler::annihilate(int degree) { - return product_op(boson_handler(degree, 2)); +boson_handler boson_handler::annihilate(std::size_t degree) { + return boson_handler(degree, 2); } -product_op boson_handler::number(int degree) { - return product_op(boson_handler(degree, 3)); +boson_handler boson_handler::number(std::size_t degree) { + return boson_handler(degree, 3); } -sum_op boson_handler::position(int degree) { - return 0.5 * - (boson_handler::create(degree) + boson_handler::annihilate(degree)); +namespace boson { +product_op create(std::size_t target) { + return product_op(boson_handler::create(target)); } - -sum_op boson_handler::momentum(int degree) { - return std::complex(0., 0.5) * - (boson_handler::create(degree) - boson_handler::annihilate(degree)); +product_op annihilate(std::size_t target) { + return product_op(boson_handler::annihilate(target)); +} +product_op number(std::size_t target) { + return product_op(boson_handler::number(target)); +} +sum_op position(std::size_t target) { + return sum_op(0.5 * create(target), 0.5 * annihilate(target)); +} +sum_op momentum(std::size_t target) { + return sum_op(std::complex(0., 0.5) * create(target), + std::complex(0., -0.5) * + annihilate(target)); } +} // namespace boson } // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/callback.cpp b/runtime/cudaq/operators/callback.cpp similarity index 90% rename from runtime/cudaq/dynamics/callback.cpp rename to runtime/cudaq/operators/callback.cpp index b5328180431..a3da0186a44 100644 --- a/runtime/cudaq/dynamics/callback.cpp +++ b/runtime/cudaq/operators/callback.cpp @@ -27,7 +27,7 @@ std::complex scalar_callback::operator()( // matrix_callback complex_matrix matrix_callback::operator()( - const std::vector &relevant_dimensions, + const std::vector &relevant_dimensions, const std::unordered_map> ¶meters) const { return this->callback_func(relevant_dimensions, parameters); @@ -36,7 +36,7 @@ complex_matrix matrix_callback::operator()( // Definition Definition::Definition(std::string operator_id, - const std::vector &expected_dimensions, + const std::vector &expected_dimensions, matrix_callback &&create) : id(operator_id), generator(std::move(create)), required_dimensions(expected_dimensions) {} @@ -46,7 +46,7 @@ Definition::Definition(Definition &&def) required_dimensions(std::move(def.expected_dimensions)) {} complex_matrix Definition::generate_matrix( - const std::vector &relevant_dimensions, + const std::vector &relevant_dimensions, const std::unordered_map> ¶meters) const { return generator(relevant_dimensions, parameters); diff --git a/runtime/cudaq/dynamics/callback.h b/runtime/cudaq/operators/callback.h similarity index 88% rename from runtime/cudaq/dynamics/callback.h rename to runtime/cudaq/operators/callback.h index 6d0b8e8bf6a..0d3704929a1 100644 --- a/runtime/cudaq/dynamics/callback.h +++ b/runtime/cudaq/operators/callback.h @@ -55,7 +55,7 @@ class matrix_callback { // dimension for each degree of freedom it acts on, and a map of complex // parameters. std::function &, + const std::vector &, const std::unordered_map> &)> callback_func; @@ -64,7 +64,7 @@ class matrix_callback { typename Callable, std::enable_if_t< std::is_invocable_r_v< - complex_matrix, Callable, const std::vector &, + complex_matrix, Callable, const std::vector &, const std::unordered_map> &>, bool> = true> matrix_callback(Callable &&callable) { @@ -78,7 +78,7 @@ class matrix_callback { matrix_callback &operator=(matrix_callback &&other) = default; complex_matrix - operator()(const std::vector &relevant_dimensions, + operator()(const std::vector &relevant_dimensions, const std::unordered_map> ¶meters) const; }; @@ -88,20 +88,20 @@ class Definition { private: std::string id; matrix_callback generator; - std::vector required_dimensions; + std::vector required_dimensions; public: - const std::vector &expected_dimensions = this->required_dimensions; + const std::vector &expected_dimensions = this->required_dimensions; Definition(std::string operator_id, - const std::vector &expected_dimensions, + const std::vector &expected_dimensions, matrix_callback &&create); Definition(Definition &&def); ~Definition(); // To call the generator function complex_matrix - generate_matrix(const std::vector &relevant_dimensions, + generate_matrix(const std::vector &relevant_dimensions, const std::unordered_map> ¶meters) const; }; diff --git a/runtime/cudaq/operators/evaluation.cpp b/runtime/cudaq/operators/evaluation.cpp new file mode 100644 index 00000000000..cbf21ebdf77 --- /dev/null +++ b/runtime/cudaq/operators/evaluation.cpp @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "evaluation.h" + +namespace cudaq { + +// operator_arithmetics + +void operator_arithmetics::canonicalize( + complex_matrix &matrix, std::vector °rees) const { + auto current_degrees = degrees; + std::sort(degrees.begin(), degrees.end(), operator_handler::canonical_order); + if (current_degrees != degrees) { + auto permutation = cudaq::detail::compute_permutation( + current_degrees, degrees, this->dimensions); + cudaq::detail::permute_matrix(matrix, permutation); + } +} + +operator_handler::matrix_evaluation +operator_arithmetics::evaluate( + const operator_handler &op) { + return operator_handler::matrix_evaluation( + op.degrees(), op.to_matrix(this->dimensions, this->parameters)); +} + +operator_handler::matrix_evaluation +operator_arithmetics::evaluate( + const scalar_operator &op) const { + return operator_handler::matrix_evaluation({}, + op.to_matrix(this->parameters)); +} + +operator_handler::matrix_evaluation +operator_arithmetics::tensor( + operator_handler::matrix_evaluation &&op1, + operator_handler::matrix_evaluation &&op2) const { + op1.degrees.reserve(op1.degrees.size() + op2.degrees.size()); + for (auto d : op2.degrees) { + assert(std::find(op1.degrees.cbegin(), op1.degrees.cend(), d) == + op1.degrees.cend()); + op1.degrees.push_back(d); + } + + auto matrix = // matrix order needs to be reversed to be consistent + cudaq::kronecker(std::move(op2.matrix), std::move(op1.matrix)); + this->canonicalize(matrix, op1.degrees); + return operator_handler::matrix_evaluation(std::move(op1.degrees), + std::move(matrix)); +} + +operator_handler::matrix_evaluation +operator_arithmetics::mul( + const scalar_operator &scalar, + operator_handler::matrix_evaluation &&op) const { + auto matrix = scalar.evaluate(this->parameters) * std::move(op.matrix); + return operator_handler::matrix_evaluation(std::move(op.degrees), + std::move(matrix)); +} + +operator_handler::matrix_evaluation +operator_arithmetics::mul( + operator_handler::matrix_evaluation &&op1, + operator_handler::matrix_evaluation &&op2) const { + // Elementary operators have sorted degrees such that we have a unique + // convention for how to define the matrix. Tensor products permute the + // computed matrix if necessary to guarantee that all operators always have + // sorted degrees. + assert(op1.degrees == op2.degrees); + op1.matrix *= std::move(op2.matrix); + return operator_handler::matrix_evaluation(std::move(op1.degrees), + std::move(op1.matrix)); +} + +operator_handler::matrix_evaluation +operator_arithmetics::add( + operator_handler::matrix_evaluation &&op1, + operator_handler::matrix_evaluation &&op2) const { + // Elementary operators have sorted degrees such that we have a unique + // convention for how to define the matrix. Tensor products permute the + // computed matrix if necessary to guarantee that all operators always have + // sorted degrees. + assert(op1.degrees == op2.degrees); + op1.matrix += std::move(op2.matrix); + return operator_handler::matrix_evaluation(std::move(op1.degrees), + std::move(op1.matrix)); +} + +// operator_arithmetics + +operator_handler::canonical_evaluation +operator_arithmetics::evaluate( + const operator_handler &op) { + auto canon_str = op.op_code_to_string(this->dimensions); + operator_handler::canonical_evaluation eval; + eval.push_back( + std::make_pair(std::complex(1.), std::move(canon_str))); + return eval; +} + +operator_handler::canonical_evaluation +operator_arithmetics::evaluate( + const scalar_operator &scalar) const { + operator_handler::canonical_evaluation eval; + eval.push_back(std::make_pair(scalar.evaluate(this->parameters), "")); + return eval; +} + +operator_handler::canonical_evaluation +operator_arithmetics::tensor( + operator_handler::canonical_evaluation &&val1, + operator_handler::canonical_evaluation &&val2) const { + assert(val1.terms.size() == 1 && val2.terms.size() == 1); + assert(val2.terms[0].first == std::complex(1.)); // should be trivial + val1.push_back(val2.terms[0].second); + return std::move(val1); +} + +operator_handler::canonical_evaluation +operator_arithmetics::mul( + const scalar_operator &scalar, + operator_handler::canonical_evaluation &&op) const { + throw std::runtime_error( + "multiplication should never be called on canonicalized operator - " + "product padding is disabled"); +} + +operator_handler::canonical_evaluation +operator_arithmetics::mul( + operator_handler::canonical_evaluation &&val1, + operator_handler::canonical_evaluation &&val2) const { + throw std::runtime_error( + "multiplication should never be called on canonicalized operator - " + "product padding is disabled"); +} + +operator_handler::canonical_evaluation +operator_arithmetics::add( + operator_handler::canonical_evaluation &&val1, + operator_handler::canonical_evaluation &&val2) const { + assert(val2.terms.size() == 1); + val1.push_back(std::move(val2.terms[0])); + return std::move(val1); +} + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/operators/evaluation.h b/runtime/cudaq/operators/evaluation.h new file mode 100644 index 00000000000..e926abb2def --- /dev/null +++ b/runtime/cudaq/operators/evaluation.h @@ -0,0 +1,138 @@ +/****************************************************************-*- 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/matrix.h" +#include +#include +#include +#include + +#include "helpers.h" +#include "operator_leafs.h" + +namespace cudaq { + +template +class operator_arithmetics { +public: + operator_arithmetics( + std::unordered_map &dimensions, + const std::unordered_map> ¶meters); + + /// Whether to inject tensor products with identity to each term in the + /// sum to ensure that all terms are acting on the same degrees of freedom + /// by the time they are added. + const bool pad_sum_terms; + /// Whether to inject tensor products with identity to each term in the + /// product to ensure that each term has its full size by the time they + /// are multiplied. + const bool pad_product_terms; + + /// @brief Accesses the relevant data to evaluate an operator expression + /// in the leaf nodes, that is in elementary and scalar operators. + EvalTy evaluate(const operator_handler &op); + EvalTy evaluate(const scalar_operator &op); + + /// @brief Computes the tensor product of two operators that act on different + /// degrees of freedom. + EvalTy tensor(EvalTy &&val1, EvalTy &&val2); + + /// @brief Multiplies two operators that act on the same degrees of freedom. + EvalTy mul(EvalTy &&val1, EvalTy &&val2); + + /// @brief Multiplies an evaluated operator with a scalar. + EvalTy mul(const scalar_operator &scalar, EvalTy &&op); + + /// @brief Adds two operators that act on the same degrees of freedom. + EvalTy add(EvalTy &&val1, EvalTy &&val2); +}; + +template <> +class operator_arithmetics { + +private: + std::unordered_map + &dimensions; // may be updated during evaluation + const std::unordered_map> ¶meters; + + // Given a matrix representation that acts on the given degrees or freedom, + // sorts the degrees and permutes the matrix to match that canonical order. + void canonicalize(complex_matrix &matrix, + std::vector °rees) const; + +public: + const bool pad_sum_terms = true; + const bool pad_product_terms = true; + + constexpr operator_arithmetics( + std::unordered_map &dimensions, + const std::unordered_map> ¶meters) + : dimensions(dimensions), parameters(parameters) {} + + operator_handler::matrix_evaluation evaluate(const operator_handler &op); + operator_handler::matrix_evaluation evaluate(const scalar_operator &op) const; + + operator_handler::matrix_evaluation + tensor(operator_handler::matrix_evaluation &&op1, + operator_handler::matrix_evaluation &&op2) const; + + operator_handler::matrix_evaluation + mul(const scalar_operator &scalar, + operator_handler::matrix_evaluation &&op) const; + + operator_handler::matrix_evaluation + mul(operator_handler::matrix_evaluation &&op1, + operator_handler::matrix_evaluation &&op2) const; + + operator_handler::matrix_evaluation + add(operator_handler::matrix_evaluation &&op1, + operator_handler::matrix_evaluation &&op2) const; +}; + +template <> +class operator_arithmetics { + +private: + std::unordered_map + &dimensions; // may be updated during evaluation + const std::unordered_map> ¶meters; + +public: + const bool pad_sum_terms = true; + const bool pad_product_terms = false; + + constexpr operator_arithmetics( + std::unordered_map &dimensions, + const std::unordered_map> ¶meters) + : dimensions(dimensions), parameters(parameters) {} + + operator_handler::canonical_evaluation evaluate(const operator_handler &op); + + operator_handler::canonical_evaluation + evaluate(const scalar_operator &scalar) const; + + operator_handler::canonical_evaluation + tensor(operator_handler::canonical_evaluation &&val1, + operator_handler::canonical_evaluation &&val2) const; + + operator_handler::canonical_evaluation + mul(const scalar_operator &scalar, + operator_handler::canonical_evaluation &&op) const; + + operator_handler::canonical_evaluation + mul(operator_handler::canonical_evaluation &&val1, + operator_handler::canonical_evaluation &&val2) const; + + operator_handler::canonical_evaluation + add(operator_handler::canonical_evaluation &&val1, + operator_handler::canonical_evaluation &&val2) const; +}; + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/fermion_operators.cpp b/runtime/cudaq/operators/fermion_op.cpp similarity index 82% rename from runtime/cudaq/dynamics/fermion_operators.cpp rename to runtime/cudaq/operators/fermion_op.cpp index 5a48a3f9944..4b088fce742 100644 --- a/runtime/cudaq/dynamics/fermion_operators.cpp +++ b/runtime/cudaq/operators/fermion_op.cpp @@ -14,7 +14,8 @@ #include "cudaq/operators.h" #include "cudaq/utils/matrix.h" -#include "fermion_operators.h" + +#include "cudaq/fermion_op.h" namespace cudaq { @@ -56,7 +57,7 @@ std::string fermion_handler::op_code_to_string() const { } std::string fermion_handler::op_code_to_string( - std::unordered_map &dimensions) const { + std::unordered_map &dimensions) const { auto it = dimensions.find(this->degree); if (it == dimensions.end()) dimensions[this->degree] = 2; @@ -98,14 +99,16 @@ std::string fermion_handler::unique_id() const { return this->op_code_to_string() + std::to_string(this->degree); } -std::vector fermion_handler::degrees() const { return {this->degree}; } +std::vector fermion_handler::degrees() const { + return {this->degree}; +} // constructors -fermion_handler::fermion_handler(int target) +fermion_handler::fermion_handler(std::size_t target) : degree(target), op_code(9), commutes(true) {} -fermion_handler::fermion_handler(int target, int op_id) +fermion_handler::fermion_handler(std::size_t target, int op_id) : degree(target), op_code(9), commutes(true) { assert(0 <= op_id && op_id < 4); if (op_id == 1) { // create @@ -135,7 +138,7 @@ fermion_handler &fermion_handler::operator=(const fermion_handler &other) { // evaluations complex_matrix fermion_handler::to_matrix( - std::unordered_map &dimensions, + std::unordered_map &dimensions, const std::unordered_map> ¶meters) const { auto it = dimensions.find(this->degree); @@ -162,7 +165,8 @@ complex_matrix fermion_handler::to_matrix( std::string fermion_handler::to_string(bool include_degrees) const { if (include_degrees) - return this->op_code_to_string() + "(" + std::to_string(this->degree) + ")"; + return this->unique_id(); // unique id for consistency with keys in some + // user facing maps else return this->op_code_to_string(); } @@ -177,16 +181,28 @@ bool fermion_handler::operator==(const fermion_handler &other) const { // defined operators -product_op fermion_handler::create(int degree) { - return product_op(fermion_handler(degree, 1)); +fermion_handler fermion_handler::create(std::size_t degree) { + return fermion_handler(degree, 1); +} + +fermion_handler fermion_handler::annihilate(std::size_t degree) { + return fermion_handler(degree, 2); } -product_op fermion_handler::annihilate(int degree) { - return product_op(fermion_handler(degree, 2)); +fermion_handler fermion_handler::number(std::size_t degree) { + return fermion_handler(degree, 3); } -product_op fermion_handler::number(int degree) { - return product_op(fermion_handler(degree, 3)); +namespace fermion { +product_op create(std::size_t target) { + return product_op(fermion_handler::create(target)); +} +product_op annihilate(std::size_t target) { + return product_op(fermion_handler::annihilate(target)); +} +product_op number(std::size_t target) { + return product_op(fermion_handler::number(target)); } +} // namespace fermion } // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/handler.cpp b/runtime/cudaq/operators/handler.cpp similarity index 97% rename from runtime/cudaq/dynamics/handler.cpp rename to runtime/cudaq/operators/handler.cpp index dc55a177bea..5ea90e56183 100644 --- a/runtime/cudaq/dynamics/handler.cpp +++ b/runtime/cudaq/operators/handler.cpp @@ -59,10 +59,10 @@ commutation_relations operator_handler::custom_commutation_relations(uint id) { operator_handler::matrix_evaluation::matrix_evaluation() = default; operator_handler::matrix_evaluation::matrix_evaluation( - std::vector &°rees, complex_matrix &&matrix) + std::vector &°rees, complex_matrix &&matrix) : degrees(std::move(degrees)), matrix(std::move(matrix)) { #if !defined(NDEBUG) - std::set unique_degrees; + std::set unique_degrees; for (auto d : this->degrees) unique_degrees.insert(d); assert(unique_degrees.size() == this->degrees.size()); diff --git a/runtime/cudaq/dynamics/helpers.cpp b/runtime/cudaq/operators/helpers.cpp similarity index 65% rename from runtime/cudaq/dynamics/helpers.cpp rename to runtime/cudaq/operators/helpers.cpp index 098ea51b4d8..77f7a66ea0b 100644 --- a/runtime/cudaq/dynamics/helpers.cpp +++ b/runtime/cudaq/operators/helpers.cpp @@ -7,15 +7,16 @@ ******************************************************************************/ #include "helpers.h" +#include "common/EigenSparse.h" #include #include namespace cudaq { namespace detail { -std::vector -generate_all_states(const std::vector °rees, - const std::unordered_map &dimensions) { +std::vector generate_all_states( + const std::vector °rees, + const std::unordered_map &dimensions) { if (degrees.size() == 0) return {}; auto dit = degrees.crbegin(); @@ -42,14 +43,15 @@ generate_all_states(const std::vector °rees, return states; } -std::vector -compute_permutation(const std::vector &op_degrees, - const std::vector &canon_degrees, - const std::unordered_map dimensions) { +std::vector +compute_permutation(const std::vector &op_degrees, + const std::vector &canon_degrees, + const std::unordered_map dimensions) { assert(op_degrees.size() == canon_degrees.size()); auto states = cudaq::detail::generate_all_states(canon_degrees, dimensions); - std::vector reordering; + std::vector reordering; + reordering.reserve(op_degrees.size()); for (auto degree : op_degrees) { auto it = std::find(canon_degrees.cbegin(), canon_degrees.cend(), degree); reordering.push_back(it - canon_degrees.cbegin()); @@ -58,7 +60,8 @@ compute_permutation(const std::vector &op_degrees, std::vector op_states = cudaq::detail::generate_all_states(op_degrees, dimensions); - std::vector permutation; + std::vector permutation; + permutation.reserve(states.size()); for (const auto &state : states) { std::string term; for (auto i : reordering) { @@ -68,23 +71,24 @@ compute_permutation(const std::vector &op_degrees, permutation.push_back(it - op_states.cbegin()); } - return std::move(permutation); + return permutation; } void permute_matrix(cudaq::complex_matrix &matrix, - const std::vector &permutation) { + const std::vector &permutation) { if (permutation.size() == 0) { assert(matrix.rows() == matrix.cols() == 1); return; } std::vector> sorted_values; + sorted_values.reserve(permutation.size() * permutation.size()); for (std::size_t permuted : permutation) { for (std::size_t permuted_again : permutation) { sorted_values.push_back(matrix[{permuted, permuted_again}]); } } - int idx = 0; + std::size_t idx = 0; for (std::size_t row = 0; row < matrix.rows(); row++) { for (std::size_t col = 0; col < matrix.cols(); col++) { matrix[{row, col}] = sorted_values[idx]; @@ -93,5 +97,21 @@ void permute_matrix(cudaq::complex_matrix &matrix, } } +cudaq::csr_spmatrix to_csr_spmatrix(const EigenSparseMatrix &matrix, + std::size_t estimated_num_entries) { + std::vector> values; + std::vector rows, cols; + values.reserve(estimated_num_entries); + rows.reserve(estimated_num_entries); + cols.reserve(estimated_num_entries); + for (std::size_t k = 0; k < matrix.outerSize(); ++k) + for (EigenSparseMatrix::InnerIterator it(matrix, k); it; ++it) { + values.emplace_back(it.value()); + rows.emplace_back(it.row()); + cols.emplace_back(it.col()); + } + return std::make_tuple(values, rows, cols); +} + } // namespace detail } // namespace cudaq diff --git a/runtime/cudaq/operators/helpers.h b/runtime/cudaq/operators/helpers.h new file mode 100644 index 00000000000..4bf8b32c28b --- /dev/null +++ b/runtime/cudaq/operators/helpers.h @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "cudaq/utils/matrix.h" +#include +#include + +namespace Eigen { +// forward declared here so that this header can be used even if the Eigen is +// not used/found +template +class SparseMatrix; +} // namespace Eigen + +namespace cudaq { +using csr_spmatrix = + std::tuple>, std::vector, + std::vector>; + +namespace detail { + +// SparseMatrix really wants a *signed* type +using EigenSparseMatrix = + Eigen::SparseMatrix, 0x1, long>; // row major + +/// Generates all possible states for the given dimensions ordered according +/// to the sequence of degrees (ordering is relevant if dimensions differ). +std::vector +generate_all_states(const std::vector °rees, + const std::unordered_map &dimensions); + +/// Computes a vector describing the permutation to reorder a matrix that is +/// ordered according to `op_degrees` to apply to `canon_degrees` instead. +/// The dimensions define the number of levels for each degree of freedom. +/// The degrees of freedom in `op_degrees` and `canon_degrees` have to match. +std::vector +compute_permutation(const std::vector &op_degrees, + const std::vector &canon_degrees, + const std::unordered_map dimensions); + +/// Permutes the given matrix according to the given permutation. +/// If states is the current order of vector entries on which the given matrix +/// acts, and permuted_states is the desired order of an array on which the +/// permuted matrix should act, then the permutation is defined such that +/// [states[i] for i in permutation] produces permuted_states. +void permute_matrix(cudaq::complex_matrix &matrix, + const std::vector &permutation); + +// FIXME: do we really want to stick with this tuple or should we rather switch +// to just using the Eigen sparse matrix? Depends on our general usage of Eigen. +/// Converts and Eigen sparse matrix to the `csr_spmatrix` format used in +/// CUDA-Q. +cudaq::csr_spmatrix to_csr_spmatrix(const EigenSparseMatrix &matrix, + std::size_t estimated_num_entries); + +} // namespace detail +} // namespace cudaq diff --git a/runtime/cudaq/dynamics/matrix_operators.cpp b/runtime/cudaq/operators/matrix_op.cpp similarity index 84% rename from runtime/cudaq/dynamics/matrix_operators.cpp rename to runtime/cudaq/operators/matrix_op.cpp index 2f3f3cfca6a..6410bb2d0f5 100644 --- a/runtime/cudaq/dynamics/matrix_operators.cpp +++ b/runtime/cudaq/operators/matrix_op.cpp @@ -13,10 +13,10 @@ #include "cudaq/operators.h" #include "cudaq/utils/matrix.h" -#include "boson_operators.h" -#include "fermion_operators.h" -#include "matrix_operators.h" -#include "spin_operators.h" +#include "cudaq/boson_op.h" +#include "cudaq/fermion_op.h" +#include "cudaq/matrix_op.h" +#include "cudaq/spin_op.h" namespace cudaq { @@ -49,7 +49,7 @@ std::string matrix_handler::type_prefix() { } void matrix_handler::define(std::string operator_id, - std::vector expected_dimensions, + std::vector expected_dimensions, matrix_callback &&create) { auto defn = Definition(operator_id, expected_dimensions, std::forward(create)); @@ -62,7 +62,7 @@ void matrix_handler::define(std::string operator_id, product_op matrix_handler::instantiate(std::string operator_id, - const std::vector °rees, + const std::vector °rees, const commutation_behavior &commutation_behavior) { auto it = matrix_handler::defined_ops.find(operator_id); if (it == matrix_handler::defined_ops.end()) @@ -70,7 +70,7 @@ matrix_handler::instantiate(std::string operator_id, "' has been defined"); auto application_degrees = degrees; std::sort(application_degrees.begin(), application_degrees.end(), - operator_handler::user_facing_order); + operator_handler::canonical_order); if (application_degrees != degrees) { std::stringstream err_msg; err_msg << "incorrect ordering of degrees (expected order {" @@ -84,7 +84,8 @@ matrix_handler::instantiate(std::string operator_id, } product_op -matrix_handler::instantiate(std::string operator_id, std::vector &°rees, +matrix_handler::instantiate(std::string operator_id, + std::vector &°rees, const commutation_behavior &commutation_behavior) { auto it = matrix_handler::defined_ops.find(operator_id); if (it == matrix_handler::defined_ops.end()) @@ -92,7 +93,7 @@ matrix_handler::instantiate(std::string operator_id, std::vector &°rees, "' has been defined"); auto application_degrees = degrees; std::sort(application_degrees.begin(), application_degrees.end(), - operator_handler::user_facing_order); + operator_handler::canonical_order); if (application_degrees != degrees) { std::stringstream err_msg; err_msg << "incorrect ordering of degrees (expected order {" @@ -109,7 +110,7 @@ matrix_handler::instantiate(std::string operator_id, std::vector &°rees, // private helpers std::string matrix_handler::op_code_to_string( - std::unordered_map &dimensions) const { + std::unordered_map &dimensions) const { auto it = matrix_handler::defined_ops.find(this->op_code); assert(it != matrix_handler::defined_ops .end()); // should be validated upon instantiation @@ -136,25 +137,29 @@ std::string matrix_handler::op_code_to_string( // read-only properties std::string matrix_handler::unique_id() const { + if (this->targets.size() == 0) + return this->op_code; auto it = this->targets.cbegin(); - auto str = this->op_code + std::to_string(*it); + std::string str = this->op_code + "(" + std::to_string(*it); while (++it != this->targets.cend()) - str += "." + std::to_string(*it); - return std::move(str); + str += "," + std::to_string(*it); + return str + ")"; } -std::vector matrix_handler::degrees() const { return this->targets; } +std::vector matrix_handler::degrees() const { + return this->targets; +} // constructors -matrix_handler::matrix_handler(int degree) +matrix_handler::matrix_handler(std::size_t degree) : op_code("I"), commutes(true), group(operator_handler::default_commutation_relations) { this->targets.push_back(degree); if (matrix_handler::defined_ops.find(this->op_code) == matrix_handler::defined_ops.end()) { auto func = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { std::size_t dimension = dimensions[0]; auto mat = complex_matrix(dimension, dimension); @@ -170,12 +175,11 @@ matrix_handler::matrix_handler(int degree) } matrix_handler::matrix_handler(std::string operator_id, - const std::vector °rees, + const std::vector °rees, const commutation_behavior &commutation_behavior) : op_code(operator_id), commutes(commutation_behavior.commutes_across_degrees), group(commutation_behavior.group), targets(degrees) { - assert(this->targets.size() > 0); if (!commutation_behavior.commutes_across_degrees && this->targets.size() > 1) // We cannot support this with the current mechanism for achieving // non-trivial commutation relations for operators acting on different @@ -185,7 +189,7 @@ matrix_handler::matrix_handler(std::string operator_id, // anti-commutation via phase operator instead. It should be fine, however, // for a multi-qubit operator to belong to a non-zero commutation set as // long as the operator itself commutes with all operators acting on - // different degrees (as indicated by teh boolean value of + // different degrees (as indicated by the boolean value of // commutation_behavior); this effectively "marks" the degrees that the // operator acts on as being a certain kind of particles. throw std::runtime_error("non-trivial commutation behavior is not " @@ -193,12 +197,11 @@ matrix_handler::matrix_handler(std::string operator_id, } matrix_handler::matrix_handler(std::string operator_id, - std::vector &°rees, + std::vector &°rees, const commutation_behavior &commutation_behavior) : op_code(operator_id), commutes(commutation_behavior.commutes_across_degrees), group(commutation_behavior.group), targets(std::move(degrees)) { - assert(this->targets.size() > 0); if (!commutation_behavior.commutes_across_degrees && this->targets.size() > 1) // We cannot support this with the current mechanism for achieving // non-trivial commutation relations for operators acting on different @@ -208,7 +211,7 @@ matrix_handler::matrix_handler(std::string operator_id, // anti-commutation via phase operator instead. It should be fine, however, // for a multi-qubit operator to belong to a non-zero commutation set as // long as the operator itself commutes with all operators acting on - // different degrees (as indicated by teh boolean value of + // different degrees (as indicated by the boolean value of // commutation_behavior); this effectively "marks" the degrees that the // operator acts on as being a certain kind of particles. throw std::runtime_error("non-trivial commutation behavior is not " @@ -231,10 +234,10 @@ matrix_handler::matrix_handler(const T &other, targets(other.degrees()) { if (matrix_handler::defined_ops.find(this->op_code) == matrix_handler::defined_ops.end()) { - auto func = [other](const std::vector &dimensions, + auto func = [other](const std::vector &dimensions, const std::unordered_map> &_none) { - std::unordered_map dims; + std::unordered_map dims; auto targets = other.degrees(); for (auto i = 0; i < dimensions.size(); ++i) dims[targets[i]] = dimensions[i]; @@ -242,7 +245,7 @@ matrix_handler::matrix_handler(const T &other, }; // the to_matrix method on the spin op will check the dimensions, so we // allow arbitrary here - std::vector required_dimensions(this->targets.size(), -1); + std::vector required_dimensions(this->targets.size(), -1); matrix_handler::define(this->op_code, std::move(required_dimensions), func); } } @@ -305,14 +308,14 @@ matrix_handler::operator=(const fermion_handler &other); // evaluations complex_matrix matrix_handler::to_matrix( - std::unordered_map &dimensions, + std::unordered_map &dimensions, const std::unordered_map> ¶meters) const { auto it = matrix_handler::defined_ops.find(this->op_code); assert(it != matrix_handler::defined_ops .end()); // should be validated upon instantiation - std::vector relevant_dimensions; + std::vector relevant_dimensions; relevant_dimensions.reserve(this->targets.size()); for (auto i = 0; i < this->targets.size(); ++i) { auto entry = dimensions.find(this->targets[i]); @@ -337,15 +340,11 @@ complex_matrix matrix_handler::to_matrix( } std::string matrix_handler::to_string(bool include_degrees) const { - if (!include_degrees) + if (include_degrees) + return this->unique_id(); // unique id for consistency with keys in some + // user facing maps + else return this->op_code; - else if (this->targets.size() == 0) - return this->op_code + "()"; - auto it = this->targets.cbegin(); - std::string str = this->op_code + "(" + std::to_string(*it); - while (++it != this->targets.cend()) - str += ", " + std::to_string(*it); - return str + ")"; } // comparisons @@ -359,12 +358,12 @@ bool matrix_handler::operator==(const matrix_handler &other) const { // predefined operators -product_op matrix_handler::number(int degree) { +matrix_handler matrix_handler::number(std::size_t degree) { std::string op_code = "number"; if (matrix_handler::defined_ops.find(op_code) == matrix_handler::defined_ops.end()) { auto func = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { std::size_t dimension = dimensions[0]; auto mat = complex_matrix(dimension, dimension); @@ -375,16 +374,15 @@ product_op matrix_handler::number(int degree) { }; matrix_handler::define(op_code, {-1}, func); } - auto op = matrix_handler(op_code, {degree}); - return product_op(std::move(op)); + return matrix_handler(op_code, {degree}); } -product_op matrix_handler::parity(int degree) { +matrix_handler matrix_handler::parity(std::size_t degree) { std::string op_code = "parity"; if (matrix_handler::defined_ops.find(op_code) == matrix_handler::defined_ops.end()) { auto func = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { std::size_t dimension = dimensions[0]; auto mat = complex_matrix(dimension, dimension); @@ -395,16 +393,15 @@ product_op matrix_handler::parity(int degree) { }; matrix_handler::define(op_code, {-1}, func); } - auto op = matrix_handler(op_code, {degree}); - return product_op(std::move(op)); + return matrix_handler(op_code, {degree}); } -product_op matrix_handler::position(int degree) { +matrix_handler matrix_handler::position(std::size_t degree) { std::string op_code = "position"; if (matrix_handler::defined_ops.find(op_code) == matrix_handler::defined_ops.end()) { auto func = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { std::size_t dimension = dimensions[0]; auto mat = complex_matrix(dimension, dimension); @@ -417,16 +414,15 @@ product_op matrix_handler::position(int degree) { }; matrix_handler::define(op_code, {-1}, func); } - auto op = matrix_handler(op_code, {degree}); - return product_op(std::move(op)); + return matrix_handler(op_code, {degree}); } -product_op matrix_handler::momentum(int degree) { +matrix_handler matrix_handler::momentum(std::size_t degree) { std::string op_code = "momentum"; if (matrix_handler::defined_ops.find(op_code) == matrix_handler::defined_ops.end()) { auto func = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { std::size_t dimension = dimensions[0]; auto mat = complex_matrix(dimension, dimension); @@ -441,15 +437,14 @@ product_op matrix_handler::momentum(int degree) { }; matrix_handler::define(op_code, {-1}, func); } - auto op = matrix_handler(op_code, {degree}); - return product_op(std::move(op)); + return matrix_handler(op_code, {degree}); } -product_op matrix_handler::displace(int degree) { +matrix_handler matrix_handler::displace(std::size_t degree) { std::string op_code = "displace"; if (matrix_handler::defined_ops.find(op_code) == matrix_handler::defined_ops.end()) { - auto func = [](const std::vector &dimensions, + auto func = [](const std::vector &dimensions, const std::unordered_map> ¶meters) { std::size_t dimension = dimensions[0]; @@ -469,15 +464,14 @@ product_op matrix_handler::displace(int degree) { }; matrix_handler::define(op_code, {-1}, func); } - auto op = matrix_handler(op_code, {degree}); - return product_op(std::move(op)); + return matrix_handler(op_code, {degree}); } -product_op matrix_handler::squeeze(int degree) { +matrix_handler matrix_handler::squeeze(std::size_t degree) { std::string op_code = "squeeze"; if (matrix_handler::defined_ops.find(op_code) == matrix_handler::defined_ops.end()) { - auto func = [](const std::vector &dimensions, + auto func = [](const std::vector &dimensions, const std::unordered_map> ¶meters) { std::size_t dimension = dimensions[0]; @@ -498,10 +492,28 @@ product_op matrix_handler::squeeze(int degree) { }; matrix_handler::define(op_code, {-1}, func); } - auto op = matrix_handler(op_code, {degree}); - return product_op(std::move(op)); + return matrix_handler(op_code, {degree}); } -// tools for custom operators +namespace operators { +product_op number(std::size_t target) { + return product_op(matrix_handler::number(target)); +} +product_op parity(std::size_t target) { + return product_op(matrix_handler::parity(target)); +} +product_op position(std::size_t target) { + return product_op(matrix_handler::position(target)); +} +product_op momentum(std::size_t target) { + return product_op(matrix_handler::momentum(target)); +} +product_op squeeze(std::size_t target) { + return product_op(matrix_handler::squeeze(target)); +} +product_op displace(std::size_t target) { + return product_op(matrix_handler::displace(target)); +} +} // namespace operators } // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/operator_leafs.h b/runtime/cudaq/operators/operator_leafs.h similarity index 95% rename from runtime/cudaq/dynamics/operator_leafs.h rename to runtime/cudaq/operators/operator_leafs.h index 374806aefb2..723d962f92a 100644 --- a/runtime/cudaq/dynamics/operator_leafs.h +++ b/runtime/cudaq/operators/operator_leafs.h @@ -212,6 +212,10 @@ class product_op; template class sum_op; +using csr_spmatrix = + std::tuple>, std::vector, + std::vector>; + class operator_handler { template friend class product_op; @@ -224,8 +228,8 @@ class operator_handler { // Validate or populate the dimension defined for the degree(s) of freedom the // operator acts on, and return a string that identifies the operator but not // what degrees it acts on. - virtual std::string - op_code_to_string(std::unordered_map &dimensions) const = 0; + virtual std::string op_code_to_string( + std::unordered_map &dimensions) const = 0; // data storage classes for evaluation @@ -238,12 +242,13 @@ class operator_handler { friend class operator_arithmetics; private: - std::vector degrees; + std::vector degrees; complex_matrix matrix; public: matrix_evaluation(); - matrix_evaluation(std::vector &°rees, complex_matrix &&matrix); + matrix_evaluation(std::vector &°rees, + complex_matrix &&matrix); matrix_evaluation(matrix_evaluation &&other); matrix_evaluation &operator=(matrix_evaluation &&other); // delete copy constructor and copy assignment to avoid unnecessary copies @@ -281,12 +286,10 @@ class operator_handler { // Individual handlers should *not* override this but rather adhere to it. // The canonical ordering is the ordering used internally by the operator - // classes. The user facing ordering is the ordering that matches CUDA-Q - // convention, i.e. the order in which custom matrix operators are defined, + // classes. It is the order in which custom matrix operators are defined, // the order returned by to_matrix and degree, and the order in which a user // would define a state vector. - static constexpr auto canonical_order = std::less(); - static constexpr auto user_facing_order = std::less(); + static constexpr auto canonical_order = std::less(); /// Default commutation relations mean that two operator always commute as /// long as they act on different degrees of freedom. @@ -314,7 +317,7 @@ class operator_handler { // returns a unique string id for the operator virtual std::string unique_id() const = 0; - virtual std::vector degrees() const = 0; + virtual std::vector degrees() const = 0; /// @brief Return the `matrix_handler` as a matrix. /// @arg `dimensions` : A map specifying the number of levels, @@ -322,7 +325,7 @@ class operator_handler { /// that the operator acts on. Example for two, 2-level /// degrees of freedom: `{0 : 2, 1 : 2}`. virtual complex_matrix - to_matrix(std::unordered_map &dimensions, + to_matrix(std::unordered_map &dimensions, const std::unordered_map> ¶meters = {}) const = 0; diff --git a/runtime/cudaq/dynamics/operator_type.h b/runtime/cudaq/operators/operator_type.h similarity index 100% rename from runtime/cudaq/dynamics/operator_type.h rename to runtime/cudaq/operators/operator_type.h diff --git a/runtime/cudaq/dynamics/product_operators.cpp b/runtime/cudaq/operators/product_op.cpp similarity index 84% rename from runtime/cudaq/dynamics/product_operators.cpp rename to runtime/cudaq/operators/product_op.cpp index 33876fc1292..44a3e484f6e 100644 --- a/runtime/cudaq/dynamics/product_operators.cpp +++ b/runtime/cudaq/operators/product_op.cpp @@ -14,6 +14,7 @@ #include #include +#include "common/EigenSparse.h" #include "cudaq/operators.h" #include "evaluation.h" #include "helpers.h" @@ -26,20 +27,6 @@ namespace cudaq { // check canonicalization by default, individual handlers can set it to false to // disable the check bool operator_handler::can_be_canonicalized = true; - -// returns true if and only if applying the operators in sequence acts only once -// on each degree of freedom and in canonical order -template -bool product_op::is_canonicalized() const { - auto canon_degrees = this->degrees(false); - std::vector degrees; - degrees.reserve(canon_degrees.size()); - for (const auto &op : this->operators) { - for (auto d : op.degrees()) - degrees.push_back(d); - } - return degrees == canon_degrees; -} #endif template @@ -84,17 +71,18 @@ product_op::find_insert_at(const matrix_handler &other) { // general is having an non-trivial commutation relation for multi- target // operators. The matrix operator class should fail to construct such an // operator. - int nr_permutations = 0; + std::size_t nr_permutations = 0; auto rit = std::find_if( this->operators.crbegin(), this->operators.crend(), [&nr_permutations, - &other_degrees = static_cast &>(other.degrees()), + &other_degrees = + static_cast &>(other.degrees()), &other](const matrix_handler &self_op) { - const std::vector &self_op_degrees = self_op.degrees(); + const std::vector &self_op_degrees = self_op.degrees(); for (auto other_degree : other_degrees) { auto item_it = std::find_if(self_op_degrees.crbegin(), self_op_degrees.crend(), - [other_degree](int self_degree) { + [other_degree](std::size_t self_degree) { return !operator_handler::canonical_order( other_degree, self_degree); }); @@ -104,7 +92,7 @@ product_op::find_insert_at(const matrix_handler &other) { // the degree somewhere item_it = std::find_if(self_op_degrees.crbegin(), self_op_degrees.crend(), - [other_degree](int self_degree) { + [other_degree](std::size_t self_degree) { return other_degree == self_degree; }); if (item_it != self_op_degrees.crend() && @@ -206,13 +194,37 @@ void product_op::aggregate_terms(HandlerTy &&head, Args &&...args) { aggregate_terms(std::forward(args)...); } +template +std::vector product_op::degrees() const { + assert(this->is_canonicalized()); + // Once we move to C++20 only, it would be nice if degrees was just a view. + std::vector degrees; + degrees.reserve(this->operators.size()); + for (const auto &op : this->operators) + degrees.push_back(op.degree); + return degrees; +} + +template <> +std::vector product_op::degrees() const { + std::set unsorted_degrees; + for (const auto &term : this->operators) { + auto term_degrees = term.degrees(); + unsorted_degrees.insert(term_degrees.cbegin(), term_degrees.cend()); + } + std::vector degrees(unsorted_degrees.cbegin(), + unsorted_degrees.cend()); + std::sort(degrees.begin(), degrees.end(), operator_handler::canonical_order); + return degrees; +} + template template EvalTy product_op::evaluate( operator_arithmetics arithmetics) const { assert(!HandlerTy::can_be_canonicalized || this->is_canonicalized()); - auto degrees = this->degrees(false); // keep in canonical order + auto degrees = this->degrees(); auto padded_op = [&arithmetics, °rees = std::as_const(degrees)](const HandlerTy &op) { @@ -318,23 +330,42 @@ INSTANTIATE_PRODUCT_EVALUATE_METHODS(fermion_handler, // read-only properties +#if !defined(NDEBUG) +// returns true if and only if applying the operators in sequence acts only once +// on each degree of freedom and in canonical order template -std::vector -product_op::degrees(bool application_order) const { - std::set unsorted_degrees; - for (const HandlerTy &term : this->operators) { - auto term_degrees = term.degrees(); - unsorted_degrees.insert(term_degrees.cbegin(), term_degrees.cend()); +bool product_op::is_canonicalized() const { + std::set unique_degrees; + std::vector degrees; + degrees.reserve(this->operators.size()); + for (const auto &op : this->operators) { + for (auto d : op.degrees()) { + degrees.push_back(d); + unique_degrees.insert(d); + } } - auto degrees = std::vector(unsorted_degrees.cbegin(), - unsorted_degrees.cend()); - if (application_order) - std::sort(degrees.begin(), degrees.end(), - operator_handler::user_facing_order); - else - std::sort(degrees.begin(), degrees.end(), - operator_handler::canonical_order); - return std::move(degrees); + std::vector canon_degrees(unique_degrees.begin(), + unique_degrees.end()); + std::sort(canon_degrees.begin(), canon_degrees.end(), + operator_handler::canonical_order); + return degrees == canon_degrees; +} +#endif + +template +std::size_t product_op::min_degree() const { + auto degrees = this->degrees(); + if (degrees.size() == 0) + throw std::runtime_error("operator is not acting on any degrees"); + return operator_handler::canonical_order(0, 1) ? degrees[0] : degrees.back(); +} + +template +std::size_t product_op::max_degree() const { + auto degrees = this->degrees(); + if (degrees.size() == 0) + throw std::runtime_error("operator is not acting on any degrees"); + return operator_handler::canonical_order(0, 1) ? degrees.back() : degrees[0]; } template @@ -357,8 +388,11 @@ scalar_operator product_op::get_coefficient() const { #define INSTANTIATE_PRODUCT_PROPERTIES(HandlerTy) \ \ - template std::vector product_op::degrees( \ - bool application_order) const; \ + template std::vector product_op::degrees() const; \ + \ + template std::size_t product_op::min_degree() const; \ + \ + template std::size_t product_op::max_degree() const; \ \ template std::size_t product_op::num_ops() const; \ \ @@ -406,7 +440,7 @@ product_op::product_op(scalar_operator coefficient, Args &&...args) template product_op::product_op( scalar_operator coefficient, const std::vector &atomic_operators, - int size) + std::size_t size) : coefficient(std::move(coefficient)) { if (size <= 0) this->operators = atomic_operators; @@ -422,7 +456,7 @@ product_op::product_op( template product_op::product_op(scalar_operator coefficient, std::vector &&atomic_operators, - int size) + std::size_t size) : coefficient(std::move(coefficient)), operators(std::move(atomic_operators)) { if (size > 0) @@ -462,7 +496,8 @@ product_op::product_op( } template -product_op::product_op(const product_op &other, int size) +product_op::product_op(const product_op &other, + std::size_t size) : coefficient(other.coefficient) { if (size <= 0) this->operators = other.operators; @@ -474,7 +509,8 @@ product_op::product_op(const product_op &other, int size) } template -product_op::product_op(product_op &&other, int size) +product_op::product_op(product_op &&other, + std::size_t size) : coefficient(std::move(other.coefficient)), operators(std::move(other.operators)) { if (size > 0) @@ -504,17 +540,17 @@ product_op::product_op(product_op &&other, int size) \ template product_op::product_op( \ scalar_operator coefficient, \ - const std::vector &atomic_operators, int size); \ + const std::vector &atomic_operators, std::size_t size); \ \ template product_op::product_op( \ scalar_operator coefficient, std::vector &&atomic_operators, \ - int size); \ + std::size_t size); \ \ template product_op::product_op( \ - const product_op &other, int size); \ + const product_op &other, std::size_t size); \ \ template product_op::product_op(product_op &&other, \ - int size); + std::size_t size); // Note: // These are the private constructors needed by friend classes and functions @@ -613,65 +649,56 @@ INSTANTIATE_PRODUCT_ASSIGNMENTS(fermion_handler); // evaluations template -std::string product_op::to_string() const { - std::stringstream str; - str << this->coefficient.to_string(); - if (this->operators.size() > 0) - str << " * "; - for (const auto &op : this->operators) - str << op.to_string(true); - return str.str(); +std::complex product_op::evaluate_coefficient( + const std::unordered_map> ¶meters) + const { + return this->coefficient.evaluate(parameters); } template complex_matrix product_op::to_matrix( - std::unordered_map dimensions, + std::unordered_map dimensions, const std::unordered_map> ¶meters, - bool application_order) const { + bool invert_order) const { auto evaluated = this->evaluate(operator_arithmetics( dimensions, parameters)); - auto matrix = std::move(evaluated.matrix); - if (!application_order || operator_handler::canonical_order(1, 0) == - operator_handler::user_facing_order(1, 0)) - return std::move(matrix); - - auto degrees = evaluated.degrees; - std::sort(degrees.begin(), degrees.end(), - operator_handler::user_facing_order); - auto permutation = cudaq::detail::compute_permutation(evaluated.degrees, - degrees, dimensions); - cudaq::detail::permute_matrix(matrix, permutation); - return std::move(matrix); + if (invert_order) { + auto reverse_degrees = evaluated.degrees; + std::reverse(reverse_degrees.begin(), reverse_degrees.end()); + auto permutation = cudaq::detail::compute_permutation( + evaluated.degrees, reverse_degrees, dimensions); + cudaq::detail::permute_matrix(evaluated.matrix, permutation); + } + return std::move(evaluated.matrix); } template <> complex_matrix product_op::to_matrix( - std::unordered_map dimensions, + std::unordered_map dimensions, const std::unordered_map> ¶meters, - bool application_order) const { + bool invert_order) const { auto terms = std::move( this->evaluate( operator_arithmetics( dimensions, parameters)) .terms); assert(terms.size() == 1); - bool invert_order = - application_order && operator_handler::canonical_order(1, 0) != - operator_handler::user_facing_order(1, 0); auto matrix = spin_handler::to_matrix(terms[0].second, terms[0].first, invert_order); - return std::move(matrix); + return matrix; } #define INSTANTIATE_PRODUCT_EVALUATIONS(HandlerTy) \ \ - template std::string product_op::to_string() const; \ + template std::complex product_op::evaluate_coefficient( \ + const std::unordered_map> ¶meters) \ + const; \ \ template complex_matrix product_op::to_matrix( \ - std::unordered_map dimensions, \ + std::unordered_map dimensions, \ const std::unordered_map> ¶meters, \ - bool application_order) const; + bool invert_order) const; #if !defined(__clang__) INSTANTIATE_PRODUCT_EVALUATIONS(matrix_handler); @@ -921,8 +948,10 @@ PRODUCT_ADDITION_PRODUCT(-) template sum_op product_op::operator*(const sum_op &other) const { - sum_op - sum; // everything needs to be updated, so creating a new sum makes sense + if (other.is_default) + return *this; + sum_op sum(false); // everything needs to be updated, so creating a + // new sum makes sense sum.coefficients.reserve(other.coefficients.size()); sum.term_map.reserve(other.terms.size()); sum.terms.reserve(other.terms.size()); @@ -939,7 +968,9 @@ product_op::operator*(const sum_op &other) const { template \ sum_op product_op::operator op( \ const sum_op &other) const & { \ - sum_op sum; \ + if (other.is_default) \ + return *this; \ + sum_op sum(false); \ sum.coefficients.reserve(other.coefficients.size() + 1); \ sum.term_map = other.term_map; \ sum.terms = other.terms; \ @@ -952,7 +983,9 @@ product_op::operator*(const sum_op &other) const { template \ sum_op product_op::operator op( \ const sum_op &other) && { \ - sum_op sum; \ + if (other.is_default) \ + return *this; \ + sum_op sum(false); \ sum.coefficients.reserve(other.coefficients.size() + 1); \ sum.term_map = other.term_map; \ sum.terms = other.terms; \ @@ -964,6 +997,8 @@ product_op::operator*(const sum_op &other) const { template \ sum_op product_op::operator op( \ sum_op &&other) const & { \ + if (other.is_default) \ + return *this; \ sum_op sum(op std::move(other)); \ sum.insert(*this); \ return std::move(sum); \ @@ -972,6 +1007,8 @@ product_op::operator*(const sum_op &other) const { template \ sum_op product_op::operator op( \ sum_op &&other) && { \ + if (other.is_default) \ + return *this; \ sum_op sum(op std::move(other)); \ sum.insert(std::move(*this)); \ return std::move(sum); \ @@ -1239,21 +1276,120 @@ bool product_op::is_identity() const { return true; } +template +std::string product_op::to_string() const { + std::stringstream str; + str << this->coefficient.to_string(); + if (this->operators.size() > 0) + str << " * "; + for (const auto &op : this->operators) + str << op.to_string(true); + return str.str(); +} + template void product_op::dump() const { auto str = to_string(); std::cout << str; } +template +product_op &product_op::canonicalize() { + for (auto it = this->operators.begin(); it != this->operators.end();) { + if (*it == HandlerTy(it->degree)) + it = this->operators.erase(it); + else + ++it; + } + return *this; +} + +template <> +product_op &product_op::canonicalize() { + for (auto it = this->operators.begin(); it != this->operators.end();) { + if (*it == matrix_handler(it->degrees()[0])) + it = this->operators.erase(it); + else + ++it; + } + return *this; +} + +template +product_op +product_op::canonicalize(const product_op &orig) { + product_op canon(orig.coefficient); + canon.operators.reserve(orig.operators.size()); + for (const auto &op : orig.operators) + if (op != HandlerTy(op.degree)) + canon.operators.push_back(op); + return canon; +} + +template <> +product_op product_op::canonicalize( + const product_op &orig) { + product_op canon(orig.coefficient); + canon.operators.reserve(orig.operators.size()); + for (const auto &op : orig.operators) + if (op != matrix_handler(op.degrees()[0])) + canon.operators.push_back(op); + return canon; +} + +template +product_op & +product_op::canonicalize(const std::set °rees) { + this->operators.reserve(degrees.size()); + std::set have_degrees; + for (const auto &op : this->operators) { + auto op_degrees = op.degrees(); + have_degrees.insert(op_degrees.cbegin(), op_degrees.cend()); + } + for (auto degree : degrees) { + auto res = have_degrees.insert(degree); + if (res.second) + this->insert(HandlerTy(degree)); + } + if (have_degrees.size() != degrees.size()) + throw std::runtime_error("missing degree in canonicalization"); + return *this; +} + +template +product_op +product_op::canonicalize(const product_op &orig, + const std::set °rees) { + product_op canon(orig, degrees.size()); + canon.canonicalize(degrees); // could be made more efficient... + return canon; +} + +#define INSTANTIATE_PRODUCT_UTILITY_FUNCTIONS(HandlerTy) \ + \ + template bool product_op::is_identity() const; \ + \ + template std::string product_op::to_string() const; \ + \ + template void product_op::dump() const; \ + \ + template product_op &product_op::canonicalize(); \ + \ + template product_op product_op::canonicalize( \ + const product_op &orig); \ + \ + template product_op &product_op::canonicalize( \ + const std::set °rees); \ + \ + template product_op product_op::canonicalize( \ + const product_op &orig, \ + const std::set °rees); + #if !defined(__clang__) -template bool product_op::is_identity() const; -template bool product_op::is_identity() const; -template bool product_op::is_identity() const; -template bool product_op::is_identity() const; -template void product_op::dump() const; -template void product_op::dump() const; -template void product_op::dump() const; -template void product_op::dump() const; +INSTANTIATE_PRODUCT_UTILITY_FUNCTIONS(matrix_handler); +INSTANTIATE_PRODUCT_UTILITY_FUNCTIONS(spin_handler); +INSTANTIATE_PRODUCT_UTILITY_FUNCTIONS(boson_handler); +INSTANTIATE_PRODUCT_UTILITY_FUNCTIONS(fermion_handler); #endif // handler specific utility functions @@ -1267,25 +1403,39 @@ template void product_op::dump() const; HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) std::size_t product_op::num_qubits() const { - return this->degrees(false).size(); + return this->degrees().size(); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) -std::string product_op::get_pauli_word() const { - // No padding here (only covers the operators we have), - // and does not include the coefficient - std::unordered_map dims; +std::string +product_op::get_pauli_word(std::size_t pad_identities) const { + std::unordered_map dims; auto terms = std::move( this->evaluate( operator_arithmetics(dims, {})) .terms); assert(terms.size() == 1); - auto str = std::move(terms[0].second); - if (operator_handler::canonical_order(1, 0) != - operator_handler::user_facing_order(1, 0)) - std::reverse(str.begin(), str.end()); - return str; + if (pad_identities == 0) { + // No padding here (only covers the operators we have), + // and does not include the coefficient + return std::move(terms[0].second); + } else { + auto degrees = this->degrees(); + if (degrees.size() != 0) { + auto max_target = + operator_handler::canonical_order(0, 1) ? degrees.back() : degrees[0]; + if (pad_identities <= max_target) + throw std::invalid_argument( + "requested padding must be larger than the largest degree the " + "operator is defined for; the largest degree is " + + std::to_string(max_target)); + } + std::string str(pad_identities, 'I'); + for (std::size_t i = 0; i < degrees.size(); ++i) + str[degrees[i]] = terms[0].second[i]; + return str; + } } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) @@ -1293,9 +1443,8 @@ std::vector product_op::get_binary_symplectic_form() const { if (this->operators.size() == 0) return {}; - std::unordered_map dims; - auto degrees = this->degrees( - false); // degrees in canonical order to match the evaluation + std::unordered_map dims; + auto degrees = this->degrees(); auto evaluated = this->evaluate( operator_arithmetics(dims, {})); @@ -1325,12 +1474,31 @@ std::vector product_op::get_binary_symplectic_form() const { return bsf; // always little endian order by definition of the bsf } -#if !defined(__clang__) +HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) +csr_spmatrix product_op::to_sparse_matrix( + std::unordered_map dimensions, + const std::unordered_map> ¶meters, + bool invert_order) const { + auto terms = std::move( + this->evaluate( + operator_arithmetics( + dimensions, parameters)) + .terms); + assert(terms.size() == 1); + auto matrix = spin_handler::to_sparse_matrix(terms[0].second, terms[0].first, + invert_order); + return cudaq::detail::to_csr_spmatrix(matrix, 1ul << terms[0].second.size()); +} + template std::size_t product_op::num_qubits() const; -template std::string product_op::get_pauli_word() const; +template std::string +product_op::get_pauli_word(std::size_t pad_identities) const; template std::vector product_op::get_binary_symplectic_form() const; -#endif +template csr_spmatrix product_op::to_sparse_matrix( + std::unordered_map dimensions, + const std::unordered_map> ¶meters, + bool invert_order) const; // utility functions for backwards compatibility @@ -1343,13 +1511,18 @@ product_op::get_binary_symplectic_form() const; SPIN_OPS_BACKWARD_COMPATIBILITY_DEFINITION std::string product_op::to_string(bool printCoeffs) const { +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif return sum_op(*this).to_string(printCoeffs); +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic pop +#endif } -#if !defined(__clang__) template std::string product_op::to_string(bool printCoeffs) const; -#endif #if defined(CUDAQ_INSTANTIATE_TEMPLATES) template class product_op; diff --git a/runtime/cudaq/dynamics/rydberg_hamiltonian.cpp b/runtime/cudaq/operators/rydberg_hamiltonian.cpp similarity index 100% rename from runtime/cudaq/dynamics/rydberg_hamiltonian.cpp rename to runtime/cudaq/operators/rydberg_hamiltonian.cpp diff --git a/runtime/cudaq/dynamics/scalar_operators.cpp b/runtime/cudaq/operators/scalar_op.cpp similarity index 100% rename from runtime/cudaq/dynamics/scalar_operators.cpp rename to runtime/cudaq/operators/scalar_op.cpp diff --git a/runtime/cudaq/operators/serialization.h b/runtime/cudaq/operators/serialization.h new file mode 100644 index 00000000000..a19f1e99e9f --- /dev/null +++ b/runtime/cudaq/operators/serialization.h @@ -0,0 +1,62 @@ +/****************************************************************-*- 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/operators.h" +#include + +namespace cudaq { +class spin_op_reader { +public: + virtual ~spin_op_reader() = default; + virtual spin_op read(const std::string &data_filename) = 0; +}; + +class binary_spin_op_reader : public spin_op_reader { +public: + spin_op read(const std::string &data_filename) override { + std::ifstream input(data_filename, std::ios::binary); + if (input.fail()) + throw std::runtime_error(data_filename + " does not exist."); + + input.seekg(0, std::ios_base::end); + std::size_t size = input.tellg(); + input.seekg(0, std::ios_base::beg); + std::vector input_vec(size / sizeof(double)); + input.read((char *)&input_vec[0], size); + return spin_op(input_vec); + } + + [[deprecated("overload provided for compatibility with the deprecated " + "serialization format - please migrate to the new format and " + "use the overload read(const std::string &)")]] spin_op + read(const std::string &data_filename, bool legacy_format) { + if (!legacy_format) + return read(data_filename); + + std::ifstream input(data_filename, std::ios::binary); + if (input.fail()) + throw std::runtime_error(data_filename + " does not exist."); + + input.seekg(0, std::ios_base::end); + std::size_t size = input.tellg(); + input.seekg(0, std::ios_base::beg); + std::vector input_vec(size / sizeof(double)); + input.read((char *)&input_vec[0], size); +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + auto n_terms = (std::size_t)input_vec.back(); + auto nQubits = (input_vec.size() - 1 - 2 * n_terms) / n_terms; + return spin_op(input_vec, nQubits); +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic pop +#endif + } +}; +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/spin_operators.cpp b/runtime/cudaq/operators/spin_op.cpp similarity index 56% rename from runtime/cudaq/dynamics/spin_operators.cpp rename to runtime/cudaq/operators/spin_op.cpp index bcbbc117221..a290e1ca2e3 100644 --- a/runtime/cudaq/dynamics/spin_operators.cpp +++ b/runtime/cudaq/operators/spin_op.cpp @@ -10,8 +10,11 @@ #include #include +#include "common/EigenSparse.h" +#include "cudaq/operators.h" #include "cudaq/utils/matrix.h" -#include "spin_operators.h" + +#include "cudaq/spin_op.h" namespace cudaq { @@ -28,7 +31,7 @@ std::string spin_handler::op_code_to_string() const { } std::string spin_handler::op_code_to_string( - std::unordered_map &dimensions) const { + std::unordered_map &dimensions) const { auto it = dimensions.find(this->degree); if (it == dimensions.end()) dimensions[this->degree] = 2; @@ -69,15 +72,18 @@ std::string spin_handler::unique_id() const { return this->op_code_to_string() + std::to_string(this->degree); } -std::vector spin_handler::degrees() const { return {this->degree}; } +std::vector spin_handler::degrees() const { + return {this->degree}; +} -int spin_handler::target() const { return this->degree; } +std::size_t spin_handler::target() const { return this->degree; } // constructors -spin_handler::spin_handler(int target) : op_code(0), degree(target) {} +spin_handler::spin_handler(std::size_t target) : op_code(0), degree(target) {} -spin_handler::spin_handler(pauli p, int target) : op_code(0), degree(target) { +spin_handler::spin_handler(pauli p, std::size_t target) + : op_code(0), degree(target) { if (p == pauli::Z) this->op_code = 1; else if (p == pauli::X) @@ -86,17 +92,19 @@ spin_handler::spin_handler(pauli p, int target) : op_code(0), degree(target) { this->op_code = 3; } -spin_handler::spin_handler(int target, int op_id) +spin_handler::spin_handler(std::size_t target, int op_id) : op_code(op_id), degree(target) { assert(0 <= op_id && op_id < 4); } // evaluations -complex_matrix spin_handler::to_matrix(std::string pauli_word, - std::complex coeff, - bool invert_order) { - auto map_state = [&pauli_word](char pauli, bool state) { +void spin_handler::create_matrix( + const std::string &pauli_word, + const std::function)> + &process_element, + bool invert_order) { + auto map_state = [](char pauli, bool state) { if (state) { if (pauli == 'Z') return std::make_pair(std::complex(-1., 0.), bool(state)); @@ -116,28 +124,57 @@ complex_matrix spin_handler::to_matrix(std::string pauli_word, } }; - auto dim = 1 << pauli_word.size(); + auto dim = 1ul << pauli_word.size(); auto nr_deg = pauli_word.size(); - complex_matrix matrix(dim, dim); for (std::size_t old_state = 0; old_state < dim; ++old_state) { std::size_t new_state = 0; std::complex entry = 1.; for (auto degree = 0; degree < nr_deg; ++degree) { - auto canon_degree = degree; - auto state = (old_state & (1 << canon_degree)) >> canon_degree; + auto state = (old_state & (1ul << degree)) >> degree; auto op = pauli_word[invert_order ? nr_deg - 1 - degree : degree]; auto mapped = map_state(op, state); entry *= mapped.first; - new_state |= (mapped.second << canon_degree); + new_state |= (mapped.second << degree); } - matrix[{new_state, old_state}] = coeff * entry; + process_element(new_state, old_state, entry); } - return std::move(matrix); +} + +cudaq::detail::EigenSparseMatrix +spin_handler::to_sparse_matrix(const std::string &pauli_word, + std::complex coeff, bool invert_order) { + using Triplet = Eigen::Triplet>; + auto dim = 1ul << pauli_word.size(); + std::vector triplets; + triplets.reserve(dim); + auto process_entry = [&triplets, &coeff](std::size_t new_state, + std::size_t old_state, + std::complex entry) { + triplets.push_back(Triplet(new_state, old_state, coeff * entry)); + }; + create_matrix(pauli_word, process_entry, invert_order); + cudaq::detail::EigenSparseMatrix matrix(dim, dim); + matrix.setFromTriplets(triplets.begin(), triplets.end()); + return matrix; +} + +complex_matrix spin_handler::to_matrix(const std::string &pauli_word, + std::complex coeff, + bool invert_order) { + auto dim = 1ul << pauli_word.size(); + complex_matrix matrix(dim, dim); + auto process_entry = [&matrix, &coeff](std::size_t new_state, + std::size_t old_state, + std::complex entry) { + matrix[{new_state, old_state}] = coeff * entry; + }; + create_matrix(pauli_word, process_entry, invert_order); + return matrix; } complex_matrix spin_handler::to_matrix( - std::unordered_map &dimensions, + std::unordered_map &dimensions, const std::unordered_map> ¶meters) const { auto it = dimensions.find(this->degree); @@ -150,7 +187,8 @@ complex_matrix spin_handler::to_matrix( std::string spin_handler::to_string(bool include_degrees) const { if (include_degrees) - return this->op_code_to_string() + "(" + std::to_string(this->degree) + ")"; + return this->unique_id(); // unique id for consistency with keys in some + // user facing maps else return this->op_code_to_string(); } @@ -163,28 +201,39 @@ bool spin_handler::operator==(const spin_handler &other) const { // defined operators -product_op spin_handler::i(int degree) { - return product_op(spin_handler(degree)); +spin_handler spin_handler::z(std::size_t degree) { + return spin_handler(degree, 1); } -product_op spin_handler::z(int degree) { - return product_op(spin_handler(degree, 1)); +spin_handler spin_handler::x(std::size_t degree) { + return spin_handler(degree, 2); } -product_op spin_handler::x(int degree) { - return product_op(spin_handler(degree, 2)); +spin_handler spin_handler::y(std::size_t degree) { + return spin_handler(degree, 3); } -product_op spin_handler::y(int degree) { - return product_op(spin_handler(degree, 3)); +namespace spin { +product_op i(std::size_t target) { + return product_op(spin_handler(target)); } - -sum_op spin_handler::plus(int degree) { - return 0.5 * x(degree) + std::complex(0., 0.5) * y(degree); +product_op x(std::size_t target) { + return product_op(spin_handler::x(target)); } - -sum_op spin_handler::minus(int degree) { - return 0.5 * x(degree) - std::complex(0., 0.5) * y(degree); +product_op y(std::size_t target) { + return product_op(spin_handler::y(target)); +} +product_op z(std::size_t target) { + return product_op(spin_handler::z(target)); +} +sum_op plus(std::size_t target) { + return sum_op(0.5 * x(target), + std::complex(0., 0.5) * y(target)); +} +sum_op minus(std::size_t target) { + return sum_op(0.5 * x(target), + std::complex(0., -0.5) * y(target)); } +} // namespace spin } // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/dynamics/operator_sum.cpp b/runtime/cudaq/operators/sum_op.cpp similarity index 78% rename from runtime/cudaq/dynamics/operator_sum.cpp rename to runtime/cudaq/operators/sum_op.cpp index dd1964b56ed..2cf54564105 100644 --- a/runtime/cudaq/dynamics/operator_sum.cpp +++ b/runtime/cudaq/operators/sum_op.cpp @@ -10,9 +10,11 @@ #include #include #include +#include #include #include +#include "common/EigenSparse.h" #include "cudaq/operators.h" #include "evaluation.h" #include "helpers.h" @@ -21,8 +23,10 @@ namespace cudaq { // private methods +/// expects is_default to be false template void sum_op::insert(const product_op &other) { + assert(!this->is_default); auto term_id = other.get_term_id(); auto it = this->term_map.find(term_id); if (it == this->term_map.cend()) { @@ -35,8 +39,10 @@ void sum_op::insert(const product_op &other) { } } +/// expects is_default to be false template void sum_op::insert(product_op &&other) { + assert(!this->is_default); auto term_id = other.get_term_id(); auto it = this->term_map.find(term_id); if (it == this->term_map.cend()) { @@ -52,6 +58,7 @@ void sum_op::insert(product_op &&other) { template void sum_op::aggregate_terms() {} +/// expects is_default to be false template template void sum_op::aggregate_terms(product_op &&head, @@ -64,31 +71,18 @@ template template EvalTy sum_op::evaluate(operator_arithmetics arithmetics) const { - if (terms.size() == 0) return EvalTy(); - // Adding a tensor product with the identity for degrees that an operator - // doesn't act on. Needed e.g. to make sure all matrices are of the same size - // before summing them up. - auto degrees = this->degrees(false); // keep in canonical order - auto paddedTerm = [&arithmetics, °rees = std::as_const(degrees)]( - product_op &&term) { - std::vector prod_ops; - prod_ops.reserve(degrees.size()); - auto term_degrees = - term.degrees(false); // ordering does not really matter here - for (auto degree : degrees) { - auto it = std::find(term_degrees.begin(), term_degrees.end(), degree); - if (it == term_degrees.end()) { - HandlerTy identity(degree); - prod_ops.push_back(std::move(identity)); - } + // Canonicalizing a term adds a tensor product with the identity for degrees + // that an operator doesn't act on. Needed e.g. to make sure all matrices are + // of the same size before summing them up. + std::set degrees; + for (const auto &term : this->terms) + for (const auto &op : term) { + auto op_degrees = op.degrees(); + degrees.insert(op_degrees.cbegin(), op_degrees.cend()); } - product_op prod(1, std::move(prod_ops)); - prod *= term; // ensures canonical ordering (if possible) - return prod; - }; // NOTE: It is important that we evaluate the terms in a specific order, // otherwise the evaluation is not consistent with other methods. @@ -97,10 +91,10 @@ sum_op::evaluate(operator_arithmetics arithmetics) const { auto it = this->begin(); auto end = this->end(); if (arithmetics.pad_sum_terms) { - product_op padded_term = paddedTerm(std::move(*it)); + product_op padded_term = it->canonicalize(degrees); EvalTy sum = padded_term.template evaluate(arithmetics); while (++it != end) { - padded_term = paddedTerm(std::move(*it)); + padded_term = it->canonicalize(degrees); EvalTy term_eval = padded_term.template evaluate(arithmetics); sum = arithmetics.add(std::move(sum), std::move(term_eval)); } @@ -157,8 +151,7 @@ INSTANTIATE_SUM_EVALUATE_METHODS(fermion_handler, // read-only properties template -std::vector -sum_op::degrees(bool application_order) const { +std::vector sum_op::degrees() const { std::set unsorted_degrees; for (const std::vector &term : this->terms) { for (const HandlerTy &op : term) { @@ -168,13 +161,24 @@ sum_op::degrees(bool application_order) const { } auto degrees = std::vector(unsorted_degrees.cbegin(), unsorted_degrees.cend()); - if (application_order) - std::sort(degrees.begin(), degrees.end(), - operator_handler::user_facing_order); - else - std::sort(degrees.begin(), degrees.end(), - operator_handler::canonical_order); - return std::move(degrees); + std::sort(degrees.begin(), degrees.end(), operator_handler::canonical_order); + return degrees; +} + +template +std::size_t sum_op::min_degree() const { + auto degrees = this->degrees(); + if (degrees.size() == 0) + throw std::runtime_error("operator is not acting on any degrees"); + return operator_handler::canonical_order(0, 1) ? degrees[0] : degrees.back(); +} + +template +std::size_t sum_op::max_degree() const { + auto degrees = this->degrees(); + if (degrees.size() == 0) + throw std::runtime_error("operator is not acting on any degrees"); + return operator_handler::canonical_order(0, 1) ? degrees.back() : degrees[0]; } template @@ -184,8 +188,11 @@ std::size_t sum_op::num_terms() const { #define INSTANTIATE_SUM_PROPERTIES(HandlerTy) \ \ - template std::vector sum_op::degrees( \ - bool application_order) const; \ + template std::vector sum_op::degrees() const; \ + \ + template std::size_t sum_op::min_degree() const; \ + \ + template std::size_t sum_op::max_degree() const; \ \ template std::size_t sum_op::num_terms() const; @@ -199,7 +206,15 @@ INSTANTIATE_SUM_PROPERTIES(fermion_handler); // constructors template -sum_op::sum_op(const product_op &prod) { +sum_op::sum_op(std::size_t size) : is_default(true) { + this->coefficients.reserve(size); + this->term_map.reserve(size); + this->terms.reserve(size); +} + +template +sum_op::sum_op(const product_op &prod) + : is_default(false) { this->insert(prod); } @@ -210,7 +225,7 @@ template < std::conjunction, Args>...>::value && sizeof...(Args), bool>> -sum_op::sum_op(Args &&...args) { +sum_op::sum_op(Args &&...args) : is_default(false) { this->coefficients.reserve(sizeof...(Args)); this->term_map.reserve(sizeof...(Args)); this->terms.reserve(sizeof...(Args)); @@ -223,7 +238,7 @@ template ::value, bool>> sum_op::sum_op(const sum_op &other) - : coefficients(other.coefficients) { + : is_default(other.is_default), coefficients(other.coefficients) { this->term_map.reserve(other.terms.size()); this->terms.reserve(other.terms.size()); for (const auto &operators : other.terms) { @@ -244,7 +259,7 @@ template > sum_op::sum_op(const sum_op &other, const matrix_handler::commutation_behavior &behavior) - : coefficients(other.coefficients) { + : is_default(other.is_default), coefficients(other.coefficients) { this->term_map.reserve(other.terms.size()); this->terms.reserve(other.terms.size()); for (const auto &operators : other.terms) { @@ -258,9 +273,10 @@ sum_op::sum_op(const sum_op &other, } template -sum_op::sum_op(const sum_op &other, bool sized, - int size) { - if (!sized) { +sum_op::sum_op(const sum_op &other, bool is_default, + std::size_t size) + : is_default(is_default && other.is_default) { + if (size <= 0) { this->coefficients = other.coefficients; this->term_map = other.term_map; this->terms = other.terms; @@ -279,13 +295,15 @@ sum_op::sum_op(const sum_op &other, bool sized, template sum_op::sum_op(const sum_op &other) - : sum_op(other, false, 0) {} + : sum_op(other, other.is_default, 0) {} template -sum_op::sum_op(sum_op &&other, bool sized, int size) - : coefficients(std::move(other.coefficients)), +sum_op::sum_op(sum_op &&other, bool is_default, + std::size_t size) + : is_default(is_default && other.is_default), + coefficients(std::move(other.coefficients)), term_map(std::move(other.term_map)), terms(std::move(other.terms)) { - if (sized) { + if (size > 0) { this->coefficients.reserve(size); this->term_map.reserve(size); this->terms.reserve(size); @@ -294,11 +312,13 @@ sum_op::sum_op(sum_op &&other, bool sized, int size) template sum_op::sum_op(sum_op &&other) - : sum_op(std::move(other), false, 0) {} + : sum_op(std::move(other), other.is_default, 0) {} #define INSTANTIATE_SUM_CONSTRUCTORS(HandlerTy) \ \ - template sum_op::sum_op(); \ + template sum_op::sum_op(bool is_default); \ + \ + template sum_op::sum_op(std::size_t size); \ \ template sum_op::sum_op(const product_op &item2); \ \ @@ -312,12 +332,12 @@ sum_op::sum_op(sum_op &&other) product_op &&item3); \ \ template sum_op::sum_op(const sum_op &other, \ - bool sized, int size); \ + bool is_default, std::size_t size); \ \ template sum_op::sum_op(const sum_op &other); \ \ - template sum_op::sum_op(sum_op &&other, bool sized, \ - int size); \ + template sum_op::sum_op(sum_op &&other, \ + bool is_default, std::size_t size); \ \ template sum_op::sum_op(sum_op &&other); @@ -327,8 +347,14 @@ sum_op::sum_op(sum_op &&other) // to be available to those. #define INSTANTIATE_SUM_PRIVATE_FRIEND_CONSTRUCTORS(HandlerTy) \ \ + template sum_op::sum_op(product_op &&item2); \ + \ template sum_op::sum_op(product_op &&item1, \ - product_op &&item2); + product_op &&item2); \ + \ + template sum_op::sum_op(product_op &&item1, \ + product_op &&item2, \ + product_op &&item3); template sum_op::sum_op(const sum_op &other); template sum_op::sum_op(const sum_op &other); @@ -370,6 +396,7 @@ sum_op &sum_op::operator=(const product_op &other) { template sum_op & sum_op::operator=(const product_op &other) { + this->is_default = false; this->coefficients.clear(); this->term_map.clear(); this->terms.clear(); @@ -382,6 +409,7 @@ sum_op::operator=(const product_op &other) { template sum_op &sum_op::operator=(product_op &&other) { + this->is_default = false; this->coefficients.clear(); this->term_map.clear(); this->terms.clear(); @@ -406,6 +434,7 @@ template sum_op & sum_op::operator=(const sum_op &other) { if (this != &other) { + this->is_default = other.is_default; this->coefficients = other.coefficients; this->term_map = other.term_map; this->terms = other.terms; @@ -416,6 +445,7 @@ sum_op::operator=(const sum_op &other) { template sum_op &sum_op::operator=(sum_op &&other) { if (this != &other) { + this->is_default = other.is_default; this->coefficients = std::move(other.coefficients); this->term_map = std::move(other.term_map); this->terms = std::move(other.terms); @@ -459,68 +489,49 @@ INSTANTIATE_SUM_ASSIGNMENTS(fermion_handler); // evaluations -template -std::string sum_op::to_string() const { - if (this->terms.size() == 0) - return ""; - auto it = this->begin(); - std::string str = it->to_string(); - while (++it != this->end()) - str += " + " + it->to_string(); - return std::move(str); -} - template complex_matrix sum_op::to_matrix( - std::unordered_map dimensions, + std::unordered_map dimensions, const std::unordered_map> ¶meters, - bool application_order) const { + bool invert_order) const { auto evaluated = this->evaluate(operator_arithmetics( dimensions, parameters)); - if (!application_order || operator_handler::canonical_order(1, 0) == - operator_handler::user_facing_order(1, 0)) - return std::move(evaluated.matrix); - - auto degrees = evaluated.degrees; - std::sort(degrees.begin(), degrees.end(), - operator_handler::user_facing_order); - auto permutation = cudaq::detail::compute_permutation(evaluated.degrees, - degrees, dimensions); - cudaq::detail::permute_matrix(evaluated.matrix, permutation); + if (invert_order) { + auto reverse_degrees = evaluated.degrees; + std::reverse(reverse_degrees.begin(), reverse_degrees.end()); + auto permutation = cudaq::detail::compute_permutation( + evaluated.degrees, reverse_degrees, dimensions); + cudaq::detail::permute_matrix(evaluated.matrix, permutation); + } return std::move(evaluated.matrix); } template <> complex_matrix sum_op::to_matrix( - std::unordered_map dimensions, + std::unordered_map dimensions, const std::unordered_map> ¶meters, - bool application_order) const { + bool invert_order) const { auto evaluated = this->evaluate( operator_arithmetics(dimensions, parameters)); if (evaluated.terms.size() == 0) return cudaq::complex_matrix(0, 0); - bool invert_order = - application_order && operator_handler::canonical_order(1, 0) != - operator_handler::user_facing_order(1, 0); auto matrix = spin_handler::to_matrix(evaluated.terms[0].second, evaluated.terms[0].first, invert_order); for (auto i = 1; i < terms.size(); ++i) matrix += spin_handler::to_matrix(evaluated.terms[i].second, evaluated.terms[i].first, invert_order); - return std::move(matrix); + return matrix; } #define INSTANTIATE_SUM_EVALUATIONS(HandlerTy) \ \ - template std::string sum_op::to_string() const; \ - \ template complex_matrix sum_op::to_matrix( \ - std::unordered_map dimensions, \ + std::unordered_map dimensions, \ const std::unordered_map> ¶ms, \ - bool application_order) const; + bool invert_order) const; #if !defined(__clang__) INSTANTIATE_SUM_EVALUATIONS(matrix_handler); @@ -533,7 +544,8 @@ INSTANTIATE_SUM_EVALUATIONS(fermion_handler); template bool sum_op::operator==(const sum_op &other) const { - if (this->terms.size() != other.terms.size()) + if (this->terms.size() != other.terms.size() || + this->is_default != other.is_default) return false; std::vector self_keys; std::vector other_keys; @@ -571,7 +583,10 @@ INSTANTIATE_SUM_COMPARISONS(fermion_handler); template sum_op sum_op::operator-() const & { - sum_op sum; + if (this->is_default) + throw std::runtime_error( + "cannot apply unary operator on uninitialized sum_op"); + sum_op sum(false); sum.coefficients.reserve(this->coefficients.size()); sum.term_map = this->term_map; sum.terms = this->terms; @@ -582,6 +597,9 @@ sum_op sum_op::operator-() const & { template sum_op sum_op::operator-() && { + if (this->is_default) + throw std::runtime_error( + "cannot apply unary operator on uninitialized sum_op"); for (auto &coeff : this->coefficients) coeff *= -1.; return std::move(*this); @@ -616,37 +634,62 @@ INSTANTIATE_SUM_UNARY_OPS(fermion_handler); // right-hand arithmetics -#define SUM_MULTIPLICATION_SCALAR(op) \ - \ - template \ - sum_op sum_op::operator op( \ - const scalar_operator &other) const & { \ - sum_op sum; \ - sum.coefficients.reserve(this->coefficients.size()); \ - sum.term_map = this->term_map; \ - sum.terms = this->terms; \ - for (const auto &coeff : this->coefficients) \ - sum.coefficients.push_back(coeff op other); \ - return std::move(sum); \ - } \ - \ - template \ - sum_op sum_op::operator op( \ - const scalar_operator &other) && { \ - for (auto &coeff : this->coefficients) \ - coeff op## = other; \ - return std::move(*this); \ - } +template +sum_op +sum_op::operator*(const scalar_operator &other) const & { + if (this->is_default) + // scalars are just a special product operator + return product_op(other); + sum_op sum(false); + sum.coefficients.reserve(this->coefficients.size()); + sum.term_map = this->term_map; + sum.terms = this->terms; + for (const auto &coeff : this->coefficients) + sum.coefficients.push_back(coeff * other); + return std::move(sum); +} -SUM_MULTIPLICATION_SCALAR(*); -SUM_MULTIPLICATION_SCALAR(/); +template +sum_op +sum_op::operator*(const scalar_operator &other) && { + if (this->is_default) + // scalars are just a special product operator + return product_op(other); + for (auto &coeff : this->coefficients) + coeff *= other; + return std::move(*this); +} + +template +sum_op +sum_op::operator/(const scalar_operator &other) const & { + if (this->is_default) + throw std::runtime_error("cannot divide uninitialized sum_op by scalar"); + sum_op sum(false); + sum.coefficients.reserve(this->coefficients.size()); + sum.term_map = this->term_map; + sum.terms = this->terms; + for (const auto &coeff : this->coefficients) + sum.coefficients.push_back(coeff / other); + return std::move(sum); +} + +template +sum_op +sum_op::operator/(const scalar_operator &other) && { + if (this->is_default) + throw std::runtime_error("cannot divide uninitialized sum_op by scalar"); + for (auto &coeff : this->coefficients) + coeff /= other; + return std::move(*this); +} #define SUM_ADDITION_SCALAR(op) \ \ template \ sum_op sum_op::operator op( \ const scalar_operator &other) const & { \ - sum_op sum(*this, true, this->terms.size() + 1); \ + sum_op sum(*this, false, this->terms.size() + 1); \ sum.insert(product_op(op other)); \ return std::move(sum); \ } \ @@ -654,7 +697,7 @@ SUM_MULTIPLICATION_SCALAR(/); template \ sum_op sum_op::operator op(scalar_operator &&other) \ const & { \ - sum_op sum(*this, true, this->terms.size() + 1); \ + sum_op sum(*this, false, this->terms.size() + 1); \ sum.insert(product_op(op std::move(other))); \ return std::move(sum); \ } \ @@ -662,6 +705,7 @@ SUM_MULTIPLICATION_SCALAR(/); template \ sum_op sum_op::operator op( \ const scalar_operator &other) && { \ + this->is_default = false; \ this->insert(product_op(op other)); \ return std::move(*this); \ } \ @@ -669,6 +713,7 @@ SUM_MULTIPLICATION_SCALAR(/); template \ sum_op sum_op::operator op( \ scalar_operator &&other) && { \ + this->is_default = false; \ this->insert(product_op(op std::move(other))); \ return std::move(*this); \ } @@ -713,7 +758,9 @@ INSTANTIATE_SUM_RHSIMPLE_OPS(fermion_handler); template sum_op sum_op::operator*(const product_op &other) const { - sum_op sum; // the entire sum needs to be rebuilt + if (this->is_default) + return other; + sum_op sum(false); // the entire sum needs to be rebuilt sum.coefficients.reserve(this->coefficients.size()); sum.term_map.reserve(this->terms.size()); sum.terms.reserve(this->terms.size()); @@ -733,7 +780,7 @@ sum_op::operator*(const product_op &other) const { template \ sum_op sum_op::operator op( \ const product_op &other) const & { \ - sum_op sum(*this, true, this->terms.size() + 1); \ + sum_op sum(*this, false, this->terms.size() + 1); \ sum.insert(op other); \ return std::move(sum); \ } \ @@ -741,6 +788,7 @@ sum_op::operator*(const product_op &other) const { template \ sum_op sum_op::operator op( \ const product_op &other) && { \ + this->is_default = false; \ this->insert(op other); \ return std::move(*this); \ } \ @@ -748,7 +796,7 @@ sum_op::operator*(const product_op &other) const { template \ sum_op sum_op::operator op( \ product_op &&other) const & { \ - sum_op sum(*this, true, this->terms.size() + 1); \ + sum_op sum(*this, false, this->terms.size() + 1); \ sum.insert(op std::move(other)); \ return std::move(sum); \ } \ @@ -756,6 +804,7 @@ sum_op::operator*(const product_op &other) const { template \ sum_op sum_op::operator op( \ product_op &&other) && { \ + this->is_default = false; \ this->insert(op std::move(other)); \ return std::move(*this); \ } @@ -766,7 +815,12 @@ SUM_ADDITION_PRODUCT(-) template sum_op sum_op::operator*(const sum_op &other) const { - sum_op sum; // the entire sum needs to be rebuilt + if (other.is_default) + return *this; + if (this->is_default) + return other; + + sum_op sum(false); // the entire sum needs to be rebuilt auto max_size = this->terms.size() * other.terms.size(); sum.coefficients.reserve(max_size); sum.term_map.reserve(max_size); @@ -789,7 +843,7 @@ sum_op::operator*(const sum_op &other) const { template \ sum_op sum_op::operator op( \ const sum_op &other) const & { \ - sum_op sum(*this, true, \ + sum_op sum(*this, this->is_default &&other.is_default, \ this->terms.size() + other.terms.size()); \ for (auto i = 0; i < other.terms.size(); ++i) { \ product_op prod(op other.coefficients[i], other.terms[i]); \ @@ -801,6 +855,8 @@ sum_op::operator*(const sum_op &other) const { template \ sum_op sum_op::operator op( \ const sum_op &other) && { \ + /* in case other is not default but does not have terms: */ \ + this->is_default = this->is_default && other.is_default; \ auto max_size = this->terms.size() + other.terms.size(); \ this->coefficients.reserve(max_size); \ this->term_map.reserve(max_size); \ @@ -814,7 +870,7 @@ sum_op::operator*(const sum_op &other) const { template \ sum_op sum_op::operator op(sum_op &&other) \ const & { \ - sum_op sum(*this, true, \ + sum_op sum(*this, this->is_default &&other.is_default, \ this->terms.size() + other.terms.size()); \ for (auto i = 0; i < other.terms.size(); ++i) { \ product_op prod(op std::move(other.coefficients[i]), \ @@ -827,6 +883,8 @@ sum_op::operator*(const sum_op &other) const { template \ sum_op sum_op::operator op( \ sum_op &&other) && { \ + /* in case other is not default but does not have terms: */ \ + this->is_default = this->is_default && other.is_default; \ auto max_size = this->terms.size() + other.terms.size(); \ this->coefficients.reserve(max_size); \ this->term_map.reserve(max_size); \ @@ -886,24 +944,32 @@ INSTANTIATE_SUM_RHCOMPOSITE_OPS(boson_handler); INSTANTIATE_SUM_RHCOMPOSITE_OPS(fermion_handler); #endif -#define SUM_MULTIPLICATION_SCALAR_ASSIGNMENT(op) \ - \ - template \ - sum_op &sum_op::operator op( \ - const scalar_operator &other) { \ - for (auto &coeff : this->coefficients) \ - coeff op other; \ - return *this; \ - } +template +sum_op &sum_op::operator*=(const scalar_operator &other) { + if (this->is_default) + // scalars are just a special product operator + *this = product_op(other); + else + for (auto &coeff : this->coefficients) + coeff *= other; + return *this; +} -SUM_MULTIPLICATION_SCALAR_ASSIGNMENT(*=); -SUM_MULTIPLICATION_SCALAR_ASSIGNMENT(/=); +template +sum_op &sum_op::operator/=(const scalar_operator &other) { + if (this->is_default) + throw std::runtime_error("cannot divide uninitialized sum_op by scalar"); + for (auto &coeff : this->coefficients) + coeff /= other; + return *this; +} #define SUM_ADDITION_SCALAR_ASSIGNMENT(op) \ \ template \ sum_op &sum_op::operator op##=( \ const scalar_operator &other) { \ + this->is_default = false; \ this->insert(product_op(op other)); \ return *this; \ } \ @@ -911,6 +977,7 @@ SUM_MULTIPLICATION_SCALAR_ASSIGNMENT(/=); template \ sum_op &sum_op::operator op##=( \ scalar_operator &&other) { \ + this->is_default = false; \ this->insert(product_op(op std::move(other))); \ return *this; \ } @@ -921,7 +988,11 @@ SUM_ADDITION_SCALAR_ASSIGNMENT(-); template sum_op & sum_op::operator*=(const product_op &other) { - sum_op sum; + if (this->is_default) { + *this = sum_op(other); + return *this; + } + sum_op sum(false); sum.coefficients.reserve(this->coefficients.size()); sum.term_map.reserve(this->terms.size()); sum.terms.reserve(this->terms.size()); @@ -942,6 +1013,7 @@ sum_op::operator*=(const product_op &other) { template \ sum_op &sum_op::operator op##=( \ const product_op &other) { \ + this->is_default = false; \ this->insert(op other); \ return *this; \ } \ @@ -949,6 +1021,7 @@ sum_op::operator*=(const product_op &other) { template \ sum_op &sum_op::operator op##=( \ product_op &&other) { \ + this->is_default = false; \ this->insert(op std::move(other)); \ return *this; \ } @@ -959,7 +1032,14 @@ SUM_ADDITION_PRODUCT_ASSIGNMENT(-) template sum_op & sum_op::operator*=(const sum_op &other) { - sum_op sum; // the entire sum needs to be rebuilt + if (other.is_default) + return *this; + if (this->is_default) { + *this = other; + return *this; + } + + sum_op sum(false); // the entire sum needs to be rebuilt auto max_size = this->terms.size() * other.terms.size(); sum.coefficients.reserve(max_size); sum.term_map.reserve(max_size); @@ -983,6 +1063,8 @@ sum_op::operator*=(const sum_op &other) { template \ sum_op &sum_op::operator op##=( \ const sum_op &other) { \ + /* in case other is not default but does not have terms: */ \ + this->is_default = this->is_default && other.is_default; \ auto max_size = this->terms.size() + other.terms.size(); \ this->coefficients.reserve(max_size); \ this->term_map.reserve(max_size); \ @@ -996,6 +1078,8 @@ sum_op::operator*=(const sum_op &other) { template \ sum_op &sum_op::operator op##=( \ sum_op &&other) { \ + /* in case other is not default but does not have terms: */ \ + this->is_default = this->is_default && other.is_default; \ auto max_size = this->terms.size() + other.terms.size(); \ this->coefficients.reserve(max_size); \ this->term_map.reserve(max_size); \ @@ -1056,7 +1140,10 @@ INSTANTIATE_SUM_OPASSIGNMENTS(fermion_handler); template sum_op operator*(const scalar_operator &other, const sum_op &self) { - sum_op sum; + if (self.is_default) + // scalars are just a special product operator + return product_op(other); + sum_op sum(false); sum.coefficients.reserve(self.coefficients.size()); sum.terms = self.terms; sum.term_map = self.term_map; @@ -1068,6 +1155,9 @@ sum_op operator*(const scalar_operator &other, template sum_op operator*(const scalar_operator &other, sum_op &&self) { + if (self.is_default) + // scalars are just a special product operator + return product_op(other); for (auto &&coeff : self.coefficients) coeff *= other; return std::move(self); @@ -1078,7 +1168,10 @@ sum_op operator*(const scalar_operator &other, template \ sum_op operator op(const scalar_operator &other, \ const sum_op &self) { \ + if (self.is_default) \ + return product_op(other); \ sum_op sum(op self); \ + sum.is_default = false; \ sum.insert(product_op(other)); \ return std::move(sum); \ } \ @@ -1086,7 +1179,10 @@ sum_op operator*(const scalar_operator &other, template \ sum_op operator op(scalar_operator &&other, \ const sum_op &self) { \ + if (self.is_default) \ + return product_op(other); \ sum_op sum(op self); \ + sum.is_default = false; \ sum.insert(product_op(std::move(other))); \ return std::move(sum); \ } \ @@ -1096,6 +1192,7 @@ sum_op operator*(const scalar_operator &other, sum_op &&self) { \ for (auto &&coeff : self.coefficients) \ coeff = std::move(op coeff); \ + self.is_default = false; \ self.insert(product_op(other)); \ return std::move(self); \ } \ @@ -1105,6 +1202,7 @@ sum_op operator*(const scalar_operator &other, sum_op &&self) { \ for (auto &&coeff : self.coefficients) \ coeff = std::move(op coeff); \ + self.is_default = false; \ self.insert(product_op(std::move(other))); \ return std::move(self); \ } @@ -1257,7 +1355,11 @@ INSTANTIATE_SUM_CONVERSION_OPS(-); template sum_op sum_op::empty() { - return sum_op(); + // The empty sum is explicitly intended to be the 0 + // element of the algebra, i.e. it is the neutral + // element for addition, whereas multiplication with an + // empty sum must always result in an emtpy sum. + return sum_op(false); } template @@ -1266,9 +1368,9 @@ product_op sum_op::identity() { } template -product_op sum_op::identity(int target) { +product_op sum_op::identity(std::size_t target) { static_assert( - std::is_constructible_v, + std::is_constructible_v, "operator handlers must have a constructor that take a single degree of " "freedom and returns the identity operator on that degree."); return product_op(1.0, HandlerTy(target)); @@ -1284,11 +1386,13 @@ template product_op sum_op::identity(); template product_op sum_op::identity(); template product_op sum_op::identity(); template product_op -sum_op::identity(int target); -template product_op sum_op::identity(int target); -template product_op sum_op::identity(int target); +sum_op::identity(std::size_t target); +template product_op +sum_op::identity(std::size_t target); +template product_op +sum_op::identity(std::size_t target); template product_op -sum_op::identity(int target); +sum_op::identity(std::size_t target); #endif // handler specific operators @@ -1301,144 +1405,224 @@ sum_op::identity(int target); bool>> HANDLER_SPECIFIC_TEMPLATE_DEFINITION(matrix_handler) -product_op sum_op::number(int target) { - return matrix_handler::number(target); +product_op sum_op::number(std::size_t target) { + return cudaq::operators::number(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(matrix_handler) -product_op sum_op::parity(int target) { - return matrix_handler::parity(target); +product_op sum_op::parity(std::size_t target) { + return cudaq::operators::parity(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(matrix_handler) -product_op sum_op::position(int target) { - return matrix_handler::position(target); +product_op sum_op::position(std::size_t target) { + return cudaq::operators::position(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(matrix_handler) -product_op sum_op::momentum(int target) { - return matrix_handler::momentum(target); +product_op sum_op::momentum(std::size_t target) { + return cudaq::operators::momentum(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(matrix_handler) -product_op sum_op::squeeze(int target) { - return matrix_handler::squeeze(target); +product_op sum_op::squeeze(std::size_t target) { + return cudaq::operators::squeeze(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(matrix_handler) -product_op sum_op::displace(int target) { - return matrix_handler::displace(target); +product_op sum_op::displace(std::size_t target) { + return cudaq::operators::displace(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) -product_op sum_op::i(int target) { - return spin_handler::i(target); +product_op sum_op::i(std::size_t target) { + return cudaq::spin::i(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) -product_op sum_op::x(int target) { - return spin_handler::x(target); +product_op sum_op::x(std::size_t target) { + return cudaq::spin::x(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) -product_op sum_op::y(int target) { - return spin_handler::y(target); +product_op sum_op::y(std::size_t target) { + return cudaq::spin::y(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) -product_op sum_op::z(int target) { - return spin_handler::z(target); +product_op sum_op::z(std::size_t target) { + return cudaq::spin::z(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) -sum_op sum_op::plus(int target) { - return spin_handler::plus(target); +sum_op sum_op::plus(std::size_t target) { + return cudaq::spin::plus(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) -sum_op sum_op::minus(int target) { - return spin_handler::minus(target); +sum_op sum_op::minus(std::size_t target) { + return cudaq::spin::minus(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(boson_handler) -product_op sum_op::create(int target) { - return boson_handler::create(target); +product_op sum_op::create(std::size_t target) { + return cudaq::boson::create(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(boson_handler) -product_op sum_op::annihilate(int target) { - return boson_handler::annihilate(target); +product_op sum_op::annihilate(std::size_t target) { + return cudaq::boson::annihilate(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(boson_handler) -product_op sum_op::number(int target) { - return boson_handler::number(target); +product_op sum_op::number(std::size_t target) { + return cudaq::boson::number(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(boson_handler) -sum_op sum_op::position(int target) { - return boson_handler::position(target); +sum_op sum_op::position(std::size_t target) { + return cudaq::boson::position(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(boson_handler) -sum_op sum_op::momentum(int target) { - return boson_handler::momentum(target); +sum_op sum_op::momentum(std::size_t target) { + return cudaq::boson::momentum(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(fermion_handler) -product_op sum_op::create(int target) { - return fermion_handler::create(target); +product_op sum_op::create(std::size_t target) { + return cudaq::fermion::create(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(fermion_handler) -product_op sum_op::annihilate(int target) { - return fermion_handler::annihilate(target); +product_op sum_op::annihilate(std::size_t target) { + return cudaq::fermion::annihilate(target); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(fermion_handler) -product_op sum_op::number(int target) { - return fermion_handler::number(target); +product_op sum_op::number(std::size_t target) { + return cudaq::fermion::number(target); } -template product_op sum_op::number(int target); -template product_op sum_op::parity(int target); template product_op -sum_op::position(int target); +sum_op::number(std::size_t target); +template product_op +sum_op::parity(std::size_t target); +template product_op +sum_op::position(std::size_t target); template product_op -sum_op::momentum(int target); -template product_op sum_op::squeeze(int target); +sum_op::momentum(std::size_t target); template product_op -sum_op::displace(int target); +sum_op::squeeze(std::size_t target); +template product_op +sum_op::displace(std::size_t target); -template product_op sum_op::i(int target); -template product_op sum_op::x(int target); -template product_op sum_op::y(int target); -template product_op sum_op::z(int target); -template sum_op sum_op::plus(int target); -template sum_op sum_op::minus(int target); +template product_op sum_op::i(std::size_t target); +template product_op sum_op::x(std::size_t target); +template product_op sum_op::y(std::size_t target); +template product_op sum_op::z(std::size_t target); +template sum_op sum_op::plus(std::size_t target); +template sum_op sum_op::minus(std::size_t target); -template product_op sum_op::create(int target); template product_op -sum_op::annihilate(int target); -template product_op sum_op::number(int target); -template sum_op sum_op::position(int target); -template sum_op sum_op::momentum(int target); +sum_op::create(std::size_t target); +template product_op +sum_op::annihilate(std::size_t target); +template product_op +sum_op::number(std::size_t target); +template sum_op +sum_op::position(std::size_t target); +template sum_op +sum_op::momentum(std::size_t target); template product_op -sum_op::create(int target); +sum_op::create(std::size_t target); template product_op -sum_op::annihilate(int target); +sum_op::annihilate(std::size_t target); template product_op -sum_op::number(int target); +sum_op::number(std::size_t target); // general utility functions +template +std::string sum_op::to_string() const { + if (this->terms.size() == 0) + return ""; + auto it = this->begin(); + std::string str = it->to_string(); + while (++it != this->end()) + str += " + " + it->to_string(); + return std::move(str); +} + template void sum_op::dump() const { auto str = to_string(); std::cout << str; } +template +sum_op &sum_op::trim( + double tol, + const std::unordered_map> ¶meters) { + sum_op trimmed(false); + trimmed.term_map.reserve(this->terms.size()); + trimmed.terms.reserve(this->terms.size()); + trimmed.coefficients.reserve(this->coefficients.size()); + for (const auto &prod : *this) + if (std::abs(prod.evaluate_coefficient(parameters)) > tol) + trimmed.insert(std::move(prod)); + *this = trimmed; + return *this; +} + +template +sum_op &sum_op::canonicalize() { + // If we make any updates, we it's best to completely rebuild the operator, + // since this may lead to the combination of terms and therefore + // change the structure/term_map of the operator. + *this = canonicalize(std::move(*this)); + return *this; +} + +template +sum_op +sum_op::canonicalize(const sum_op &orig) { + sum_op canonicalized(false); + for (auto &&prod : orig) + canonicalized.insert(prod.canonicalize()); + return canonicalized; +} + +template +sum_op & +sum_op::canonicalize(const std::set °rees) { + // If we make any updates, we it's best to completely rebuild the operator, + // since this may lead to the combination of terms and therefore + // change the structure/term_map of the operator. + *this = canonicalize(std::move(*this), degrees); + return *this; +} + +template +sum_op +sum_op::canonicalize(const sum_op &orig, + const std::set °rees) { + std::set all_degrees; + if (degrees.size() == 0) { + for (const auto &term : orig.terms) + for (const auto &op : term) { + auto op_degrees = op.degrees(); + all_degrees.insert(op_degrees.cbegin(), op_degrees.cend()); + } + } + sum_op canonicalized(false); + for (auto &&prod : orig) + canonicalized.insert( + prod.canonicalize(degrees.size() == 0 ? all_degrees : degrees)); + return canonicalized; +} + template std::vector> sum_op::distribute_terms(std::size_t numChunks) const { @@ -1450,7 +1634,7 @@ sum_op::distribute_terms(std::size_t numChunks) const { std::vector> chunks; for (auto it = this->term_map.cbegin(); it != this->term_map.cend();) { // order does not matter here - sum_op chunk; + sum_op chunk(false); // Evenly distribute any leftovers across the early chunks for (auto count = nTermsPerChunk + (chunks.size() < leftover ? 1 : 0); count > 0; --count, ++it) @@ -1461,14 +1645,35 @@ sum_op::distribute_terms(std::size_t numChunks) const { // Not sure if we need this - we might need this when parallelizing a spin_op // over QPUs when the system has more processors than we have terms. while (chunks.size() < numChunks) - chunks.push_back(sum_op()); + // needs to be empty (is_default = false), since it should zero out any + // multiplication + chunks.push_back(sum_op(false)); return std::move(chunks); } #define INSTANTIATE_SUM_UTILITY_FUNCTIONS(HandlerTy) \ + \ + template std::string sum_op::to_string() const; \ + \ + template void sum_op::dump() const; \ + \ + template sum_op &sum_op::canonicalize(); \ + \ + template sum_op sum_op::canonicalize( \ + const sum_op &orig); \ + \ + template sum_op &sum_op::canonicalize( \ + const std::set °rees); \ + \ + template sum_op sum_op::canonicalize( \ + const sum_op &orig, const std::set °rees); \ + \ + template sum_op &sum_op::trim( \ + double tol, const std::unordered_map> \ + ¶meters); \ + \ template std::vector> sum_op::distribute_terms( \ - std::size_t numChunks) const; \ - template void sum_op::dump() const; + std::size_t numChunks) const; #if !defined(__clang__) INSTANTIATE_SUM_UTILITY_FUNCTIONS(matrix_handler); @@ -1488,11 +1693,14 @@ INSTANTIATE_SUM_UTILITY_FUNCTIONS(fermion_handler); HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) std::size_t sum_op::num_qubits() const { - return this->degrees(false).size(); + return this->degrees().size(); } HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) sum_op::sum_op(const std::vector &input_vec) { + if (input_vec.size() == 0) + throw std::runtime_error("input vector must not be empty"); + auto it = input_vec.cbegin(); auto next_int = [&it, &input_vec]() { if (it == input_vec.end()) @@ -1501,7 +1709,7 @@ sum_op::sum_op(const std::vector &input_vec) { if (std::modf(*it, &intPart) != 0.0) throw std::runtime_error( "Invalid pauli data element, must be integer value."); - return (int)*it++; + return (std::size_t)*it++; }; auto next_double = [&it, &input_vec]() { if (it == input_vec.end()) @@ -1581,7 +1789,7 @@ sum_op sum_op::random(std::size_t nQubits, std::to_string(nQubits) + " qubits"); } - auto get_spin_op = [](int target, int kind) { + auto get_spin_op = [](std::size_t target, int kind) { if (kind == 1) return sum_op::z(target); if (kind == 2) @@ -1592,7 +1800,7 @@ sum_op sum_op::random(std::size_t nQubits, }; std::mt19937 gen(seed); - auto sum = sum_op::empty(); + auto sum = sum_op(true); // make sure the number of terms matches the requested number... while (sum.terms.size() < nTerms) { std::vector termData(2 * nQubits); @@ -1601,7 +1809,7 @@ sum_op sum_op::random(std::size_t nQubits, // ... but allow for duplicates (will be a single term with coefficient != // 1) auto prod = sum_op::identity(); - for (int qubit_idx = 0; qubit_idx < nQubits; ++qubit_idx) { + for (std::size_t qubit_idx = 0; qubit_idx < nQubits; ++qubit_idx) { auto kind = (termData[qubit_idx << 1] << 1) | termData[(qubit_idx << 1) + 1]; // keep identities so that we act on the requested number of qubits @@ -1612,7 +1820,59 @@ sum_op sum_op::random(std::size_t nQubits, return std::move(sum); } -#if !defined(__clang__) +HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) +csr_spmatrix sum_op::to_sparse_matrix( + std::unordered_map dimensions, + const std::unordered_map> ¶meters, + bool invert_order) const { + auto evaluated = this->evaluate( + operator_arithmetics(dimensions, + parameters)); + + if (evaluated.terms.size() == 0) + return std::make_tuple>, + std::vector, std::vector>( + {}, {}, {}); + + auto matrix = spin_handler::to_sparse_matrix( + evaluated.terms[0].second, evaluated.terms[0].first, invert_order); + for (auto i = 1; i < terms.size(); ++i) + matrix += spin_handler::to_sparse_matrix( + evaluated.terms[i].second, evaluated.terms[i].first, invert_order); + return cudaq::detail::to_csr_spmatrix( + matrix, 1ul << evaluated.terms[0].second.size()); +} + +HANDLER_SPECIFIC_TEMPLATE_DEFINITION(spin_handler) +std::vector sum_op::get_data_representation() const { + auto nr_ops = 0; + for (const auto &term : *this) + nr_ops += term.operators.size(); + std::vector dataVec; + dataVec.reserve(2 * nr_ops + 3 * this->terms.size() + 1); + dataVec.push_back(this->terms.size()); + for (std::size_t i = 0; i < this->terms.size(); ++i) { + auto coeff = this->coefficients[i].evaluate(); + dataVec.push_back(coeff.real()); + dataVec.push_back(coeff.imag()); + dataVec.push_back(this->terms[i].size()); + for (std::size_t j = 0; j < this->terms[i].size(); ++j) { + auto op = this->terms[i][j]; + dataVec.push_back(op.degrees()[0]); + auto pauli = op.as_pauli(); + if (pauli == pauli::Z) + dataVec.push_back(1.); + else if (pauli == pauli::X) + dataVec.push_back(2.); + else if (pauli == pauli::Y) + dataVec.push_back(3.); + else + dataVec.push_back(0.); + } + } + return dataVec; +} + template std::size_t sum_op::num_qubits() const; template sum_op::sum_op(const std::vector &input_vec); template product_op @@ -1620,7 +1880,12 @@ sum_op::from_word(const std::string &word); template sum_op sum_op::random(std::size_t nQubits, std::size_t nTerms, unsigned int seed); -#endif +template csr_spmatrix sum_op::to_sparse_matrix( + std::unordered_map dimensions, + const std::unordered_map> ¶meters, + bool invert_order) const; +template std::vector +sum_op::get_data_representation() const; // utility functions for backwards compatibility @@ -1634,8 +1899,11 @@ template sum_op sum_op::random(std::size_t nQubits, SPIN_OPS_BACKWARD_COMPATIBILITY_DEFINITION sum_op::sum_op(const std::vector &input_vec, std::size_t nQubits) { - auto n_terms = (int)input_vec.back(); - if (nQubits != (((input_vec.size() - 1) - 2 * n_terms) / n_terms)) + if (input_vec.size() == 0) + throw std::runtime_error("input vector must not be empty"); + auto n_terms = (std::size_t)input_vec.back(); + if (n_terms == 0 || + nQubits != (((input_vec.size() - 1) - 2 * n_terms) / n_terms)) throw std::runtime_error("Invalid data representation for construction " "spin_op. Number of data elements is incorrect."); @@ -1649,7 +1917,7 @@ sum_op::sum_op(const std::vector &input_vec, throw std::runtime_error( "Invalid pauli data element, must be integer value."); - int val = (int)input_vec[j + i]; + int val = (std::size_t)input_vec[j + i]; if (val == 1) // X prod *= sum_op::x(j); else if (val == 2) // Z @@ -1671,7 +1939,8 @@ sum_op::sum_op(const std::vector> &bsf_terms, if (bsf_terms.size() != coeffs.size()) throw std::invalid_argument( "size of the coefficient and bsf_terms must match"); - this->coefficients.reserve(coeffs.size()); + this->is_default = bsf_terms.size() == 0; + this->coefficients.reserve(bsf_terms.size()); this->terms.reserve(bsf_terms.size()); for (const auto &term : bsf_terms) { @@ -1697,39 +1966,70 @@ sum_op::sum_op(const std::vector> &bsf_terms, SPIN_OPS_BACKWARD_COMPATIBILITY_DEFINITION std::vector sum_op::getDataRepresentation() const { - // This function prints a data representing the operator sum + // This function prints a data representing the operator sum that + // includes the full representation for any degree in [0, max_degree), + // padding identities if necessary. + // NOTE: this is an imperfect representation that we will want to + // deprecate because it does not capture targets accurately. + auto degrees = this->degrees(); + auto le_order = std::less(); + auto get_le_index = [°rees, &le_order](std::size_t idx) { + // For compatibility with existing code, the ordering for the term ops + // always needs to be from smallest to largest degree. + return (operator_handler::canonical_order(1, 0) == le_order(1, 0)) + ? idx + : degrees.size() - 1 - idx; + }; + + // number of degrees including the ones for any injected identities + auto n_targets = operator_handler::canonical_order(0, 1) ? degrees.back() + 1 + : degrees[0] + 1; + auto padded = *this; // copy for identity padding + for (std::size_t j = 0; j < n_targets; ++j) + padded *= sum_op::identity(j); + std::vector dataVec; - // dataVec.reserve(n_targets * padded.terms.size() + 2 * padded.terms.size() + - // 1); - dataVec.push_back(this->terms.size()); - for (std::size_t i = 0; i < this->terms.size(); ++i) { - auto coeff = this->coefficients[i].evaluate(); - dataVec.push_back(coeff.real()); - dataVec.push_back(coeff.imag()); - dataVec.push_back(this->terms[i].size()); - for (std::size_t j = 0; j < this->terms[i].size(); ++j) { - auto op = this->terms[i][j]; - dataVec.push_back(op.degrees()[0]); - auto pauli = op.as_pauli(); - if (pauli == pauli::Z) + dataVec.reserve(n_targets * padded.terms.size() + 2 * padded.terms.size() + + 1); + for (std::size_t i = 0; i < padded.terms.size(); ++i) { + for (std::size_t j = 0; j < padded.terms[i].size(); ++j) { + auto pauli = padded.terms[i][get_le_index(j)].as_pauli(); + if (pauli == pauli::X) dataVec.push_back(1.); - else if (pauli == pauli::X) + else if (pauli == pauli::Z) dataVec.push_back(2.); else if (pauli == pauli::Y) dataVec.push_back(3.); else dataVec.push_back(0.); } + auto coeff = padded.coefficients[i].evaluate(); + dataVec.push_back(coeff.real()); + dataVec.push_back(coeff.imag()); } + dataVec.push_back(padded.terms.size()); return dataVec; } +SPIN_OPS_BACKWARD_COMPATIBILITY_DEFINITION +std::tuple, std::size_t> +sum_op::getDataTuple() const { +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + return std::make_tuple, std::size_t>( + this->getDataRepresentation(), this->num_qubits()); +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic pop +#endif +} + SPIN_OPS_BACKWARD_COMPATIBILITY_DEFINITION std::pair>, std::vector>> sum_op::get_raw_data() const { - std::unordered_map dims; - auto degrees = this->degrees( - false); // degrees in canonical order to match the evaluation + std::unordered_map dims; + auto degrees = this->degrees(); auto evaluated = this->evaluate( operator_arithmetics( dims, {})); // fails if we have parameters @@ -1775,12 +2075,11 @@ std::string sum_op::to_string(bool printCoeffs) const { // This function prints a string representing the operator sum that // includes the full representation for any degree in [0, max_degree), // padding identities if necessary (opposed to pauli_word). - std::unordered_map dims; - auto degrees = this->degrees( - false); // degrees in canonical order to match the evaluation + std::unordered_map dims; + auto degrees = this->degrees(); auto evaluated = this->evaluate( operator_arithmetics(dims, {})); - auto le_order = std::less(); + auto le_order = std::less(); auto get_le_index = [°rees, &le_order](std::size_t idx) { // For compatibility with existing code, the ordering for the term string // always needs to be from smallest to largest degree, and it necessarily @@ -1847,7 +2146,6 @@ bool sum_op::is_identity() const { return true; } -#if !defined(__clang__) template sum_op::sum_op(const std::vector &input_vec, std::size_t nQubits); template sum_op::sum_op( @@ -1855,6 +2153,8 @@ template sum_op::sum_op( const std::vector> &coeffs); template std::vector sum_op::getDataRepresentation() const; +template std::tuple, std::size_t> +sum_op::getDataTuple() const; template std::pair>, std::vector>> sum_op::get_raw_data() const; @@ -1864,7 +2164,6 @@ template void sum_op::for_each_term( template void sum_op::for_each_pauli( std::function &&functor) const; template bool sum_op::is_identity() const; -#endif #if defined(CUDAQ_INSTANTIATE_TEMPLATES) template class sum_op; diff --git a/runtime/cudaq/dynamics/templates.h b/runtime/cudaq/operators/templates.h similarity index 99% rename from runtime/cudaq/dynamics/templates.h rename to runtime/cudaq/operators/templates.h index be90ea79d55..2a3b7676863 100644 --- a/runtime/cudaq/dynamics/templates.h +++ b/runtime/cudaq/operators/templates.h @@ -14,14 +14,14 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ +#include "operator_leafs.h" #include #include -#include "boson_operators.h" -#include "fermion_operators.h" -#include "matrix_operators.h" -#include "operator_leafs.h" -#include "spin_operators.h" +#include "cudaq/boson_op.h" +#include "cudaq/fermion_op.h" +#include "cudaq/matrix_op.h" +#include "cudaq/spin_op.h" namespace cudaq { diff --git a/runtime/cudaq/platform/default/CMakeLists.txt b/runtime/cudaq/platform/default/CMakeLists.txt index 7df1644c1a8..59450fb8434 100644 --- a/runtime/cudaq/platform/default/CMakeLists.txt +++ b/runtime/cudaq/platform/default/CMakeLists.txt @@ -25,7 +25,7 @@ target_include_directories(${LIBRARY_NAME} target_link_libraries(${LIBRARY_NAME} PUBLIC - pthread cudaq-em-default cudaq-spin cudaq-common + pthread cudaq-em-default cudaq-operator cudaq-common PRIVATE fmt::fmt-header-only cudaq CUDAQTargetConfigUtil) diff --git a/runtime/cudaq/platform/default/rest/CMakeLists.txt b/runtime/cudaq/platform/default/rest/CMakeLists.txt index dfd2c2a479d..11b50ba4087 100644 --- a/runtime/cudaq/platform/default/rest/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/CMakeLists.txt @@ -28,7 +28,7 @@ endif() target_link_libraries(cudaq-rest-qpu PUBLIC - cudaq-spin + cudaq-operator cudaq-common PRIVATE cudaq-mlir-runtime diff --git a/runtime/cudaq/platform/default/rest_server/CMakeLists.txt b/runtime/cudaq/platform/default/rest_server/CMakeLists.txt index 6175bd52fc8..68c7ad61ca7 100644 --- a/runtime/cudaq/platform/default/rest_server/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest_server/CMakeLists.txt @@ -90,6 +90,7 @@ target_link_libraries(rest-remote-platform-server PRIVATE rest_server_impl cudaq + cudaq-operator cudaq-em-default cudaq-mlir-runtime cudaq-platform-default diff --git a/runtime/cudaq/platform/default/rest_server/helpers/RestRemoteServer.cpp b/runtime/cudaq/platform/default/rest_server/helpers/RestRemoteServer.cpp index 177f90c3bf8..241af552c58 100644 --- a/runtime/cudaq/platform/default/rest_server/helpers/RestRemoteServer.cpp +++ b/runtime/cudaq/platform/default/rest_server/helpers/RestRemoteServer.cpp @@ -325,7 +325,8 @@ class RemoteRestRuntimeServer : public cudaq::RemoteRuntimeServer { gradient->setKernel(fnWrapper); bool requiresGrad = optimizer.requiresGradients(); - auto theSpin = **io_context.spin; + auto theSpin = *io_context.spin; + assert(cudaq::spin_op::canonicalize(theSpin) == theSpin); result = optimizer.optimize(n_params, [&](const std::vector &x, std::vector &grad_vec) { diff --git a/runtime/cudaq/platform/fermioniq/CMakeLists.txt b/runtime/cudaq/platform/fermioniq/CMakeLists.txt index 69b32aeb83b..30c8ef8ddd9 100644 --- a/runtime/cudaq/platform/fermioniq/CMakeLists.txt +++ b/runtime/cudaq/platform/fermioniq/CMakeLists.txt @@ -18,7 +18,7 @@ target_include_directories(${LIBRARY_NAME} PRIVATE . target_link_libraries(${LIBRARY_NAME} PUBLIC - cudaq-spin + cudaq-operator cudaq-common PRIVATE pthread diff --git a/runtime/cudaq/platform/fermioniq/FermioniqBaseQPU.h b/runtime/cudaq/platform/fermioniq/FermioniqBaseQPU.h index 94e5f100805..88f57f4b714 100644 --- a/runtime/cudaq/platform/fermioniq/FermioniqBaseQPU.h +++ b/runtime/cudaq/platform/fermioniq/FermioniqBaseQPU.h @@ -78,16 +78,16 @@ class FermioniqBaseQPU : public BaseRemoteRESTQPU { auto user_data = nlohmann::json::object(); auto obs = nlohmann::json::array(); - spin->for_each_term([&](spin_op &term) { + for (const auto &term : spin) { auto spin_op = nlohmann::json::object(); auto terms = nlohmann::json::array(); - auto termStr = term.to_string(false); + auto termStr = term.get_term_id(); terms.push_back(termStr); - auto coeff = term.get_coefficient(); + auto coeff = term.evaluate_coefficient(); auto coeff_str = fmt::format("{}{}{}j", coeff.real(), coeff.imag() < 0.0 ? "-" : "+", std::fabs(coeff.imag())); @@ -95,7 +95,7 @@ class FermioniqBaseQPU : public BaseRemoteRESTQPU { terms.push_back(coeff_str); obs.push_back(terms); - }); + } user_data["observable"] = obs; diff --git a/runtime/cudaq/platform/mqpu/CMakeLists.txt b/runtime/cudaq/platform/mqpu/CMakeLists.txt index 7d2ad9089a6..47ef0ab14b3 100644 --- a/runtime/cudaq/platform/mqpu/CMakeLists.txt +++ b/runtime/cudaq/platform/mqpu/CMakeLists.txt @@ -25,7 +25,7 @@ target_include_directories(${LIBRARY_NAME} target_link_libraries(${LIBRARY_NAME} PUBLIC cudaq-em-default - cudaq-spin + cudaq-operator cudaq-common PRIVATE cudaq diff --git a/runtime/cudaq/platform/orca/CMakeLists.txt b/runtime/cudaq/platform/orca/CMakeLists.txt index 66bfaaa34ff..f6f99ae3e78 100644 --- a/runtime/cudaq/platform/orca/CMakeLists.txt +++ b/runtime/cudaq/platform/orca/CMakeLists.txt @@ -24,7 +24,7 @@ target_include_directories(${LIBRARY_NAME} PRIVATE . target_link_libraries(${LIBRARY_NAME} PUBLIC - cudaq-spin + cudaq-operator cudaq-common PRIVATE pthread diff --git a/runtime/cudaq/platform/pasqal/CMakeLists.txt b/runtime/cudaq/platform/pasqal/CMakeLists.txt index 327e23eb37d..95912c95fe2 100644 --- a/runtime/cudaq/platform/pasqal/CMakeLists.txt +++ b/runtime/cudaq/platform/pasqal/CMakeLists.txt @@ -23,7 +23,7 @@ target_include_directories(${LIBRARY_NAME} PRIVATE . target_link_libraries(${LIBRARY_NAME} PUBLIC - cudaq-spin + cudaq-operator cudaq-common PRIVATE pthread diff --git a/runtime/cudaq/platform/qpu.h b/runtime/cudaq/platform/qpu.h index cc530a7b809..b15124c0369 100644 --- a/runtime/cudaq/platform/qpu.h +++ b/runtime/cudaq/platform/qpu.h @@ -73,7 +73,8 @@ class QPU : public registry::RegisteredType { "without a cudaq::spin_op."); std::vector results; - cudaq::spin_op &H = *localContext->spin.value(); + cudaq::spin_op &H = localContext->spin.value(); + assert(cudaq::spin_op::canonicalize(H) == H); // If the backend supports the observe task, // let it compute the expectation value instead of @@ -86,17 +87,17 @@ class QPU : public registry::RegisteredType { } else { // Loop over each term and compute coeff * - H.for_each_term([&](cudaq::spin_op &term) { + for (const auto &term : H) { if (term.is_identity()) - sum += term.get_coefficient().real(); + sum += term.evaluate_coefficient().real(); else { // This takes a longer time for the first iteration unless // flushGateQueue() is called above. auto [exp, data] = cudaq::measure(term); - results.emplace_back(data.to_map(), term.to_string(false), exp); - sum += term.get_coefficient().real() * exp; + results.emplace_back(data.to_map(), term.get_term_id(), exp); + sum += term.evaluate_coefficient().real() * exp; } - }); + }; localContext->expectationValue = sum; localContext->result = cudaq::sample_result(sum, results); @@ -169,7 +170,7 @@ class QPU : public registry::RegisteredType { virtual void setTargetBackend(const std::string &backend) {} virtual void launchVQE(const std::string &name, const void *kernelArgs, - cudaq::gradient *gradient, cudaq::spin_op H, + cudaq::gradient *gradient, const cudaq::spin_op &H, cudaq::optimizer &optimizer, const int n_params, const std::size_t shots) {} diff --git a/runtime/cudaq/platform/quantum_platform.cpp b/runtime/cudaq/platform/quantum_platform.cpp index 0956b2bc866..78ad77c6c42 100644 --- a/runtime/cudaq/platform/quantum_platform.cpp +++ b/runtime/cudaq/platform/quantum_platform.cpp @@ -143,7 +143,7 @@ bool quantum_platform::supports_explicit_measurements( void quantum_platform::launchVQE(const std::string kernelName, const void *kernelArgs, gradient *gradient, - spin_op H, optimizer &optimizer, + const spin_op &H, optimizer &optimizer, const int n_params, const std::size_t shots) { std::size_t qpu_id = 0; diff --git a/runtime/cudaq/platform/quantum_platform.h b/runtime/cudaq/platform/quantum_platform.h index a96259e338a..19c42686ad8 100644 --- a/runtime/cudaq/platform/quantum_platform.h +++ b/runtime/cudaq/platform/quantum_platform.h @@ -142,7 +142,7 @@ class quantum_platform { /// @brief Launch a VQE operation on the platform. void launchVQE(const std::string kernelName, const void *kernelArgs, - cudaq::gradient *gradient, cudaq::spin_op H, + cudaq::gradient *gradient, const cudaq::spin_op &H, cudaq::optimizer &optimizer, const int n_params, const std::size_t shots); diff --git a/runtime/cudaq/platform/quera/CMakeLists.txt b/runtime/cudaq/platform/quera/CMakeLists.txt index e57dd3f1ae2..5725f142f7d 100644 --- a/runtime/cudaq/platform/quera/CMakeLists.txt +++ b/runtime/cudaq/platform/quera/CMakeLists.txt @@ -19,7 +19,7 @@ target_include_directories(${LIBRARY_NAME} PRIVATE . target_link_libraries(${LIBRARY_NAME} PUBLIC cudaq-serverhelper-braket - cudaq-spin + cudaq-operator cudaq-common PRIVATE pthread diff --git a/runtime/cudaq/qis/execution_manager.h b/runtime/cudaq/qis/execution_manager.h index ed539018d45..98fe593ca2a 100644 --- a/runtime/cudaq/qis/execution_manager.h +++ b/runtime/cudaq/qis/execution_manager.h @@ -12,7 +12,7 @@ #include "common/NoiseModel.h" #include "common/QuditIdTracker.h" #include "cudaq/host_config.h" -#include "cudaq/spin_op.h" +#include "cudaq/operators.h" #include #include #include @@ -131,7 +131,8 @@ class ExecutionManager { const std::vector ¶ms, const std::vector &controls, const std::vector &targets, - bool isAdjoint = false, const spin_op op = spin_op()) = 0; + bool isAdjoint = false, + const spin_op_term op = cudaq::spin_op::identity()) = 0; /// @brief Apply a fine-grain noise operation within a kernel. virtual void applyNoise(const kraus_channel &channelName, @@ -157,8 +158,8 @@ class ExecutionManager { virtual int measure(const QuditInfo &target, const std::string registerName = "") = 0; - /// Measure the current state in the given Pauli basis, return the expectation - /// value . + /// Measure the current state in the respective basis given by each term in + /// the spin op, return the expectation value . virtual SpinMeasureResult measure(const cudaq::spin_op &op) = 0; /// Synchronize - run all queue-ed instructions diff --git a/runtime/cudaq/qis/managers/BasicExecutionManager.h b/runtime/cudaq/qis/managers/BasicExecutionManager.h index 762cb6013cc..6edcfd1bf64 100644 --- a/runtime/cudaq/qis/managers/BasicExecutionManager.h +++ b/runtime/cudaq/qis/managers/BasicExecutionManager.h @@ -8,6 +8,7 @@ #include "common/ExecutionContext.h" #include "common/Logger.h" +#include "cudaq/operators.h" #include "cudaq/qis/execution_manager.h" #include @@ -38,7 +39,7 @@ class BasicExecutionManager : public cudaq::ExecutionManager { /// target qudits, and an optional spin_op. using Instruction = std::tuple, std::vector, - std::vector, spin_op>; + std::vector, spin_op_term>; /// @brief `typedef` for a queue of instructions using InstructionQueue = std::vector; @@ -88,7 +89,8 @@ class BasicExecutionManager : public cudaq::ExecutionManager { virtual int measureQudit(const cudaq::QuditInfo &q, const std::string ®isterName) = 0; - /// @brief Measure the state in the basis described by the given `spin_op`. + /// @brief Measure the state in the respective basis described each term in + /// the given `spin_op`. virtual void measureSpinOp(const cudaq::spin_op &op) = 0; /// @brief Subtype-specific method for performing qudit reset. @@ -178,7 +180,8 @@ class BasicExecutionManager : public cudaq::ExecutionManager { void apply(const std::string_view gateName, const std::vector ¶ms, const std::vector &controls, const std::vector &targets, - bool isAdjoint = false, spin_op op = spin_op()) override { + bool isAdjoint = false, + spin_op_term op = cudaq::spin_op::identity()) override { // Make a copy of the name that we can mutate if necessary std::string mutable_name(gateName); diff --git a/runtime/cudaq/qis/managers/default/CMakeLists.txt b/runtime/cudaq/qis/managers/default/CMakeLists.txt index d40d4f40a50..915d18f9175 100644 --- a/runtime/cudaq/qis/managers/default/CMakeLists.txt +++ b/runtime/cudaq/qis/managers/default/CMakeLists.txt @@ -17,7 +17,7 @@ target_include_directories(${LIBRARY_NAME} PRIVATE .) target_link_libraries(${LIBRARY_NAME} - PUBLIC cudaq-spin PRIVATE nvqir cudaq-common fmt::fmt-header-only LLVMSupport) + PUBLIC cudaq-operator PRIVATE nvqir cudaq-common fmt::fmt-header-only LLVMSupport) install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-em-default-targets DESTINATION lib) diff --git a/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp b/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp index c4491d3e876..2a00bfae064 100644 --- a/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp +++ b/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp @@ -7,9 +7,9 @@ ******************************************************************************/ #include "common/Logger.h" +#include "cudaq/operators.h" #include "cudaq/qis/managers/BasicExecutionManager.h" #include "cudaq/qis/qudit.h" -#include "cudaq/spin_op.h" #include "cudaq/utils/cudaq_utils.h" #include "nvqir/CircuitSimulator.h" #include "llvm/ADT/StringSwitch.h" diff --git a/runtime/cudaq/qis/managers/photonics/CMakeLists.txt b/runtime/cudaq/qis/managers/photonics/CMakeLists.txt index f121b7a6317..2bb76915224 100644 --- a/runtime/cudaq/qis/managers/photonics/CMakeLists.txt +++ b/runtime/cudaq/qis/managers/photonics/CMakeLists.txt @@ -21,7 +21,7 @@ list(APPEND PHOTONICS_DEPENDENCIES cudaq-common libqpp fmt::fmt-header-only) add_openmp_configurations(${LIBRARY_NAME} PHOTONICS_DEPENDENCIES) target_link_libraries(${LIBRARY_NAME} - PUBLIC cudaq-spin + PUBLIC cudaq-operator PRIVATE ${PHOTONICS_DEPENDENCIES} ) diff --git a/runtime/cudaq/qis/managers/photonics/PhotonicsExecutionManager.cpp b/runtime/cudaq/qis/managers/photonics/PhotonicsExecutionManager.cpp index 6ebed996d80..122ffa74f5e 100644 --- a/runtime/cudaq/qis/managers/photonics/PhotonicsExecutionManager.cpp +++ b/runtime/cudaq/qis/managers/photonics/PhotonicsExecutionManager.cpp @@ -6,8 +6,8 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ #include "common/Logger.h" +#include "cudaq/operators.h" #include "cudaq/qis/managers/BasicExecutionManager.h" -#include "cudaq/spin_op.h" #include "cudaq/utils/cudaq_utils.h" #include "qpp.h" #include diff --git a/runtime/cudaq/qis/qubit_qis.h b/runtime/cudaq/qis/qubit_qis.h index 2ee2cf21f3c..defeadbae43 100644 --- a/runtime/cudaq/qis/qubit_qis.h +++ b/runtime/cudaq/qis/qubit_qis.h @@ -10,6 +10,7 @@ #include "common/MeasureCounts.h" #include "cudaq/host_config.h" +#include "cudaq/operators.h" #include "cudaq/platform.h" #include "cudaq/qis/modifiers.h" #include "cudaq/qis/pauli_word.h" @@ -17,7 +18,6 @@ #include "cudaq/qis/qkernel.h" #include "cudaq/qis/qreg.h" #include "cudaq/qis/qvector.h" -#include "cudaq/spin_op.h" #include #include #include @@ -696,6 +696,7 @@ void exp_pauli(double theta, QubitRange &&qubits, const char *pauliWord) { std::vector quditInfos; std::transform(qubits.begin(), qubits.end(), std::back_inserter(quditInfos), [](auto &q) { return cudaq::qubitToQuditInfo(q); }); + // FIXME: it would be cleaner if we just kept it as a pauli word here getExecutionManager()->apply("exp_pauli", {theta}, {}, quditInfos, false, spin_op::from_word(pauliWord)); } @@ -760,20 +761,20 @@ void exp_pauli(QuantumRegister &ctrls, double theta, const char *pauliWord, /// @brief Measure an individual qubit, return 0,1 as `bool` inline measure_result mz(qubit &q) { - return getExecutionManager()->measure({q.n_levels(), q.id()}); + return getExecutionManager()->measure(QuditInfo{q.n_levels(), q.id()}); } /// @brief Measure an individual qubit in `x` basis, return 0,1 as `bool` inline measure_result mx(qubit &q) { h(q); - return getExecutionManager()->measure({q.n_levels(), q.id()}); + return getExecutionManager()->measure(QuditInfo{q.n_levels(), q.id()}); } // Measure an individual qubit in `y` basis, return 0,1 as `bool` inline measure_result my(qubit &q) { r1(-M_PI_2, q); h(q); - return getExecutionManager()->measure({q.n_levels(), q.id()}); + return getExecutionManager()->measure(QuditInfo{q.n_levels(), q.id()}); } inline void reset(qubit &q) { diff --git a/runtime/cudaq/schedule.h b/runtime/cudaq/schedule.h index b07eb4c2e4d..027d0b86d22 100644 --- a/runtime/cudaq/schedule.h +++ b/runtime/cudaq/schedule.h @@ -35,7 +35,7 @@ class schedule { std::function(const std::string &, const std::complex &)> value_function; - int current_idx; + std::size_t current_idx; static std::vector> toComplex(const std::vector &vec) { @@ -62,8 +62,8 @@ class schedule { /// @arg value_function: A function that takes the name of a parameter as well /// as an additional value ("step") of std::complex 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, + /// @details current_idx: Initializes 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, diff --git a/runtime/cudaq/spin/CMakeLists.txt b/runtime/cudaq/spin/CMakeLists.txt deleted file mode 100644 index ab3465bd15c..00000000000 --- a/runtime/cudaq/spin/CMakeLists.txt +++ /dev/null @@ -1,37 +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. # -# ============================================================================ # - -set(LIBRARY_NAME cudaq-spin) -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") -set(INTERFACE_POSITION_INDEPENDENT_CODE ON) - -set(CUDAQ_SPIN_SRC - spin_op.cpp -) - -add_library(${LIBRARY_NAME} SHARED ${CUDAQ_SPIN_SRC}) -set_property(GLOBAL APPEND PROPERTY CUDAQ_RUNTIME_LIBS ${LIBRARY_NAME}) -target_include_directories(${LIBRARY_NAME} - PUBLIC - $ - $ - $ - PRIVATE .) - -set (SPIN_DEPENDENCIES "") -list(APPEND SPIN_DEPENDENCIES fmt::fmt-header-only) -add_openmp_configurations(${LIBRARY_NAME} SPIN_DEPENDENCIES) - -target_link_libraries(${LIBRARY_NAME} PRIVATE ${SPIN_DEPENDENCIES}) - -install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-spin-targets DESTINATION lib) - -install(EXPORT cudaq-spin-targets - FILE CUDAQSpinTargets.cmake - NAMESPACE cudaq:: - DESTINATION lib/cmake/cudaq) diff --git a/runtime/cudaq/spin/spin_op.cpp b/runtime/cudaq/spin/spin_op.cpp deleted file mode 100644 index ba4e334aa09..00000000000 --- a/runtime/cudaq/spin/spin_op.cpp +++ /dev/null @@ -1,713 +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 "common/EigenDense.h" -#include "common/EigenSparse.h" -#include "common/FmtCore.h" -#include -#include -#include -#if defined(_OPENMP) -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cudaq { - -namespace details { - -/// @brief Helper function to convert ordering of matrix elements -/// to match internal simulator state ordering. -std::size_t convertOrdering(std::size_t numQubits, std::size_t idx) { - std::size_t newIdx = 0; - for (std::size_t i = 0; i < numQubits; ++i) - if (idx & (1ULL << i)) - newIdx |= (1ULL << ((numQubits - 1) - i)); - return newIdx; -} - -/// @brief Compute the action -std::pair> -actionOnBra(spin_op &term, const std::string &bitConfiguration) { - auto coeff = term.get_coefficient(); - auto newConfiguration = bitConfiguration; - std::complex i(0, 1); - - term.for_each_pauli([&](pauli p, std::size_t idx) { - if (p == pauli::Z) { - coeff *= (newConfiguration[idx] == '1' ? -1 : 1); - } else if (p == pauli::X) { - newConfiguration[idx] = newConfiguration[idx] == '1' ? '0' : '1'; - } else if (p == pauli::Y) { - coeff *= (newConfiguration[idx] == '1' ? i : -i); - newConfiguration[idx] = (newConfiguration[idx] == '1' ? '0' : '1'); - } - }); - - return std::make_pair(newConfiguration, coeff); -} - -std::pair, std::complex> -mult(const std::vector &p1, const std::vector &p2, - const std::complex &p1Coeff, const std::complex &p2Coeff) { - auto [minSize, maxSize] = std::minmax({p1.size(), p2.size()}); - std::size_t minNumQubits = minSize / 2; - std::size_t maxNumQubits = maxSize / 2; - std::vector result(maxSize, false); - int yCount = 0; - int cPhase = 0; - - for (std::size_t i = 0; i < minNumQubits; ++i) { - bool p1_x = p1[i]; - bool p1_z = p1[i + (p1.size() / 2)]; - bool p2_x = p2[i]; - bool p2_z = p2[i + (p2.size() / 2)]; - - // Compute the resulting Pauli operator - result[i] = p1_x ^ p2_x; - result[i + maxNumQubits] = p1_z ^ p2_z; - - yCount += - (p1_x & p1_z) + (p2_x & p2_z) - (result[i] & result[i + maxNumQubits]); - cPhase += p1_x & p2_z; - } - - const std::vector &big = p1.size() < p2.size() ? p2 : p1; - for (std::size_t i = minNumQubits; i < maxNumQubits; ++i) { - result[i] = big[i]; - result[i + maxNumQubits] = big[i + maxNumQubits]; - } - - // Normalize the phase to a value in the range [0, 3] - int phaseFactor = (2 * cPhase + yCount) % 4; - if (phaseFactor < 0) - phaseFactor += 4; - - // Phase correction factors based on the total phase - using namespace std::complex_literals; - std::array, 4> phase{1.0, -1i, -1.0, 1i}; - std::complex resultCoeff = p1Coeff * phase[phaseFactor] * p2Coeff; - - // Handle the "-0" issue - if (std::abs(resultCoeff.real()) < 1e-12) - resultCoeff.real(0); - - return {result, resultCoeff}; -} -} // namespace details - -spin_op::spin_op() { - std::vector init(2); - terms.emplace(init, 1.0); -} - -spin_op::spin_op( - const std::unordered_map> &_terms) - : terms(_terms) {} - -spin_op::spin_op(std::size_t numQubits) { - std::vector init(2 * numQubits); - terms.emplace(init, 1.0); -} - -spin_op::spin_op(const spin_op_term &term, const std::complex &coeff) { - terms.emplace(term, coeff); -} - -spin_op::spin_op(const std::vector &bsf, - const std::vector> &coeffs) { - for (std::size_t i = 0; auto &t : bsf) - terms.emplace(t, coeffs[i++]); -} - -spin_op::spin_op(pauli type, const std::size_t idx, - std::complex coeff) { - auto numQubits = idx + 1; - std::vector d(2 * numQubits); - - if (type == pauli::X) - d[idx] = 1; - else if (type == pauli::Y) { - d[idx] = 1; - d[idx + numQubits] = 1; - } else if (type == pauli::Z) - d[idx + numQubits] = 1; - - terms.emplace(d, coeff); -} - -spin_op::spin_op(const spin_op &o) : terms(o.terms) {} - -spin_op::spin_op( - std::pair> &termData) { - terms.insert(termData); -} -spin_op::spin_op( - const std::pair> &termData) { - terms.insert(termData); -} - -spin_op::iterator spin_op::begin() { - auto startIter = terms.begin(); - return iterator(startIter); -} - -spin_op::iterator spin_op::end() { - auto endIter = terms.end(); - return iterator(endIter); -} - -spin_op::iterator spin_op::begin() const { - auto startIter = terms.cbegin(); - return iterator(startIter); -} - -spin_op::iterator spin_op::end() const { - auto endIter = terms.cend(); - return iterator(endIter); -} - -complex_matrix spin_op::to_matrix() const { - auto n = num_qubits(); - auto dim = 1UL << n; - auto getBitStrForIdx = [&](std::size_t i) { - std::stringstream s; - for (int k = n - 1; k >= 0; k--) - s << ((i >> k) & 1); - return s.str(); - }; - - // To construct the matrix, we are looping over every - // row, computing the binary representation for that index, - // e.g <100110|, and then we will compute the action of - // each pauli term on that binary configuration, returning a new - // product state and coefficient. Call this new state and set it in the matrix - // data. - - complex_matrix A(dim, dim); - auto rawData = A.data; -#if defined(_OPENMP) -#pragma omp parallel for shared(rawData) -#endif - for (std::size_t rowIdx = 0; rowIdx < dim; rowIdx++) { - auto rowBitStr = getBitStrForIdx(rowIdx); - for_each_term([&](spin_op &term) { - auto [res, coeff] = details::actionOnBra(term, rowBitStr); - auto colIdx = std::stol(res, nullptr, 2); - rawData[details::convertOrdering(n, rowIdx) * dim + - details::convertOrdering(n, colIdx)] += coeff; - }); - } - return A; -} - -spin_op::csr_spmatrix spin_op::to_sparse_matrix() const { - auto n = num_qubits(); - auto dim = 1UL << n; - using Triplet = Eigen::Triplet>; - using SpMat = Eigen::SparseMatrix>; - std::vector xT{Triplet{0, 1, 1}, Triplet{1, 0, 1}}, - iT{Triplet{0, 0, 1}, Triplet{1, 1, 1}}, - yT{{0, 1, std::complex{0, -1}}, - {1, 0, std::complex{0, 1}}}, - zT{Triplet{0, 0, 1}, Triplet{1, 1, -1}}; - SpMat x(2, 2), y(2, 2), z(2, 2), i(2, 2), mat(dim, dim); - x.setFromTriplets(xT.begin(), xT.end()); - y.setFromTriplets(yT.begin(), yT.end()); - z.setFromTriplets(zT.begin(), zT.end()); - i.setFromTriplets(iT.begin(), iT.end()); - - auto kronProd = [](const std::vector &ops) -> SpMat { - SpMat ret = ops[0]; - for (std::size_t k = 1; k < ops.size(); ++k) - ret = Eigen::kroneckerProduct(ret, ops[k]).eval(); - return ret; - }; - - for_each_term([&](spin_op &term) { - auto termStr = term.to_string(false); - std::vector operations; - for (std::size_t k = 0; k < termStr.length(); ++k) { - auto pauli = termStr[k]; - if (pauli == 'X') - operations.emplace_back(x); - else if (pauli == 'Y') - operations.emplace_back(y); - else if (pauli == 'Z') - operations.emplace_back(z); - else - operations.emplace_back(i); - } - - mat += term.get_coefficient() * kronProd(operations); - }); - - std::vector> values; - std::vector rows, cols; - for (int k = 0; k < mat.outerSize(); ++k) - for (SpMat::InnerIterator it(mat, k); it; ++it) { - values.emplace_back(it.value()); - rows.emplace_back(details::convertOrdering(n, it.row())); - cols.emplace_back(details::convertOrdering(n, it.col())); - } - - return std::make_tuple(values, rows, cols); -} - -std::complex spin_op::get_coefficient() const { - if (terms.size() != 1) - throw std::runtime_error( - "spin_op::get_coefficient called on spin_op with > 1 terms."); - return terms.begin()->second; -} - -std::tuple, std::size_t> spin_op::getDataTuple() const { - return std::tuple(getDataRepresentation(), num_qubits()); -} - -void spin_op::for_each_term(std::function &&functor) const { - for (auto iter = terms.begin(), e = terms.end(); iter != e; ++iter) { - const auto &pair = *iter; - spin_op tmp(pair); - functor(tmp); - } -} -void spin_op::for_each_pauli( - std::function &&functor) const { - if (num_terms() != 1) - throw std::runtime_error( - "spin_op::for_each_pauli on valid for spin_op with n_terms == 1."); - - auto nQ = num_qubits(); - auto bsf = terms.begin()->first; - for (std::size_t i = 0; i < nQ; i++) { - if (bsf[i] && bsf[i + nQ]) { - functor(pauli::Y, i); - } else if (bsf[i]) { - functor(pauli::X, i); - } else if (bsf[i + nQ]) { - functor(pauli::Z, i); - } else { - functor(pauli::I, i); - } - } -} - -spin_op spin_op::random(std::size_t nQubits, std::size_t nTerms, - unsigned int seed) { - std::mt19937 gen(seed); - std::vector> coeffs(nTerms, 1.0); - std::vector randomTerms; - // Make sure we don't put duplicates into randomTerms by using dupCheckSet - std::set> dupCheckSet; - if (nQubits <= 30) { - // For the given algorithm below that sets bool=true for 1/2 of the the - // termData, the maximum number of unique terms is n choose k, where n = - // 2*nQubits, and k=nQubits. For up to 30 qubits, we can calculate n choose - // k without overflows (i.e. 60 choose 30 = 118264581564861424) to validate - // that nTerms is reasonable. For anything larger, the user can't set nTerms - // large enough to run into actual problems because they would encounter - // memory limitations long before anything else. - // Note: use the multiplicative formula to evaluate n-choose-k. The - // arrangement of multiplications and divisions do not truncate any division - // remainders. - std::size_t maxTerms = 1; - for (std::size_t i = 1; i <= nQubits; i++) { - maxTerms *= 2 * nQubits + 1 - i; - maxTerms /= i; - } - if (nTerms > maxTerms) - throw std::runtime_error( - fmt::format("Unable to produce {} unique random terms for {} qubits", - nTerms, nQubits)); - } - for (std::size_t i = 0; i < nTerms; i++) { - std::vector termData(2 * nQubits); - while (true) { - std::fill_n(termData.begin(), nQubits, true); - std::shuffle(termData.begin(), termData.end(), gen); - if (dupCheckSet.contains(termData)) { - // Prepare to loop again - std::fill(termData.begin(), termData.end(), false); - } else { - dupCheckSet.insert(termData); - break; - } - } - randomTerms.push_back(std::move(termData)); - } - - return spin_op(randomTerms, coeffs); -} - -spin_op spin_op::from_word(const std::string &word) { - auto numQubits = word.length(); - spin_op_term term(2 * numQubits); - for (std::size_t i = 0; i < numQubits; i++) { - auto letter = word[i]; - if (std::islower(letter)) - letter = std::toupper(letter); - - if (letter == 'Y') { - term[i] = true; - term[i + numQubits] = true; - } else if (letter == 'X') { - term[i] = true; - } else if (letter == 'Z') { - term[i + numQubits] = true; - } else { - - if (letter != 'I') - throw std::runtime_error( - "Invalid Pauli for spin_op::from_word, must be X, Y, Z, or I."); - } - } - - return spin_op(term, 1.0); -} - -void spin_op::expandToNQubits(const std::size_t numQubits) { - auto iter = terms.begin(); - while (iter != terms.end()) { - auto coeff = iter->second; - std::vector tmp = iter->first; - if (tmp.size() == numQubits * 2) { - iter++; - continue; - } - - auto newSize = numQubits * 2 - tmp.size(); - for (std::size_t i = 0; i < newSize / 2; i++) { - tmp.insert(tmp.begin() + tmp.size() / 2, 0); - tmp.insert(tmp.begin() + tmp.size(), 0); - } - - terms.erase(iter++); - terms.emplace(tmp, coeff); - } -} - -spin_op &spin_op::operator+=(const spin_op &v) noexcept { - auto otherNumQubits = v.num_qubits(); - - spin_op tmpv = v; - if (otherNumQubits > num_qubits()) - expandToNQubits(otherNumQubits); - else if (otherNumQubits < num_qubits()) - tmpv.expandToNQubits(num_qubits()); - - for (auto [term, coeff] : tmpv.terms) { - auto iter = terms.find(term); - if (iter != terms.end()) - iter->second += coeff; - else - terms.emplace(term, coeff); - } - - return *this; -} - -spin_op &spin_op::operator-=(const spin_op &v) noexcept { - return operator+=(-1.0 * v); -} - -spin_op &spin_op::operator*=(const spin_op &v) noexcept { - using term_and_coeff = std::pair, std::complex>; - std::size_t numTerms = num_terms() * v.num_terms(); - std::vector result(numTerms); - std::size_t min = std::min(num_terms(), v.num_terms()); - - // Put the `unordered_map` iterators into vectors to minimize pointer chasing - // when doing the cartesian product of the spin operators' terms. - using Iter = - std::unordered_map>::const_iterator; - std::vector thisTermIt; - std::vector otherTermIt; - thisTermIt.reserve(terms.size()); - otherTermIt.reserve(v.terms.size()); - for (auto it = terms.begin(); it != terms.end(); ++it) - thisTermIt.push_back(it); - for (auto it = v.terms.begin(); it != v.terms.end(); ++it) - otherTermIt.push_back(it); - -#if defined(_OPENMP) - // Threshold to start OpenMP parallelization. - // 16 ~ 4-term * 4-term - constexpr std::size_t spin_op_omp_threshold = 16; -#pragma omp parallel for shared(result) if (numTerms > spin_op_omp_threshold) -#endif - for (std::size_t i = 0; i < numTerms; ++i) { - Iter s = thisTermIt[i % min]; - Iter t = otherTermIt[i / min]; - if (terms.size() > v.terms.size()) { - s = thisTermIt[i / min]; - t = otherTermIt[i % min]; - } - result[i] = details::mult(s->first, t->first, s->second, t->second); - } - - terms.clear(); - terms.reserve(numTerms); - for (auto &&[term, coeff] : result) { - auto [it, created] = terms.emplace(term, coeff); - if (!created) - it->second += coeff; - } - return *this; -} - -bool spin_op::is_identity() const { - for (auto &[row, c] : terms) - for (auto e : row) - if (e) - return false; - - return true; -} - -bool spin_op::operator==(const spin_op &v) const noexcept { - // Could be that the term is identity with all zeros - bool isId1 = true, isId2 = true; - for (auto &[row, c] : terms) - for (auto e : row) - if (e) { - isId1 = false; - break; - } - - for (auto &[row, c] : v.terms) - for (auto e : row) - if (e) { - isId2 = false; - break; - } - - if (isId1 && isId2) - return true; - - for (auto &[k, c] : terms) { - if (v.terms.find(k) == v.terms.end()) - return false; - } - return true; -} - -spin_op &spin_op::operator*=(const double v) noexcept { - for (auto &[term, coeff] : terms) - coeff *= v; - - return *this; -} - -spin_op &spin_op::operator*=(const std::complex v) noexcept { - for (auto &[term, coeff] : terms) - coeff *= v; - - return *this; -} - -std::size_t spin_op::num_qubits() const { - if (terms.empty()) - return 0; - return terms.begin()->first.size() / 2; -} - -std::size_t spin_op::num_terms() const { return terms.size(); } - -std::vector spin_op::distribute_terms(std::size_t numChunks) const { - // Calculate how many terms we can equally divide amongst the chunks - auto nTermsPerChunk = num_terms() / numChunks; - auto leftover = num_terms() % numChunks; - - // Slice the given spin_op into subsets for each chunk - std::vector spins; - auto termIt = terms.begin(); - for (std::size_t chunkIx = 0; chunkIx < numChunks; chunkIx++) { - // Evenly distribute any leftovers across the early chunks - auto count = nTermsPerChunk + (chunkIx < leftover ? 1 : 0); - - // Get the chunk from the terms list. - std::unordered_map> sliced; - std::copy_n(termIt, count, std::inserter(sliced, sliced.end())); - - // Add to the return vector - spins.emplace_back(sliced); - - // Get ready for the next loop - std::advance(termIt, count); - } - - // return the terms. - return spins; -} - -std::string spin_op::to_string(bool printCoeffs) const { - std::stringstream ss; - const auto termToStr = [](const std::vector &term) { - std::string printOut; - printOut.reserve(term.size() / 2); - for (std::size_t i = 0; i < term.size() / 2; i++) { - if (term[i] && term[i + term.size() / 2]) - printOut.push_back('Y'); - else if (term[i]) - printOut.push_back('X'); - else if (term[i + term.size() / 2]) - printOut.push_back('Z'); - else - printOut.push_back('I'); - } - return printOut; - }; - - if (!printCoeffs) { - std::vector printOut; - printOut.reserve(terms.size()); - for (auto &[term, coeff] : terms) - printOut.emplace_back(termToStr(term)); - // IMPORTANT: For a printing without coefficients, we want to - // sort the terms to get a consistent order for printing. - // This is necessary because unordered_map does not maintain order and our - // code relies on the full string representation as the key to look up full - // expectation of the whole `spin_op`. - // FIXME: Make the logic to look up whole expectation value from - // `sample_result` more robust. - std::sort(printOut.begin(), printOut.end()); - ss << fmt::format("{}", fmt::join(printOut, "")); - } else { - for (auto &[term, coeff] : terms) { - ss << fmt::format("[{}{}{}j]", coeff.real(), - coeff.imag() < 0.0 ? "-" : "+", std::fabs(coeff.imag())) - << " "; - ss << termToStr(term); - ss << "\n"; - } - } - - return ss.str(); -} - -void spin_op::dump() const { - auto str = to_string(); - std::cout << str; -} - -spin_op::spin_op(const std::vector &input_vec, std::size_t nQubits) { - auto n_terms = (int)input_vec.back(); - if (nQubits != (((input_vec.size() - 1) - 2 * n_terms) / n_terms)) - throw std::runtime_error("Invalid data representation for construction " - "spin_op. Number of data elements is incorrect."); - - for (std::size_t i = 0; i < input_vec.size() - 1; i += nQubits + 2) { - std::vector tmpv(2 * nQubits); - for (std::size_t j = 0; j < nQubits; j++) { - double intPart; - if (std::modf(input_vec[j + i], &intPart) != 0.0) - throw std::runtime_error( - "Invalid pauli data element, must be integer value."); - - int val = (int)input_vec[j + i]; - if (val == 1) { // X - tmpv[j] = 1; - } else if (val == 2) { // Z - tmpv[j + nQubits] = 1; - } else if (val == 3) { // Y - tmpv[j + nQubits] = 1; - tmpv[j] = 1; - } - } - auto el_real = input_vec[i + nQubits]; - auto el_imag = input_vec[i + nQubits + 1]; - terms.emplace(tmpv, std::complex{el_real, el_imag}); - } -} - -std::pair, std::vector>> -spin_op::get_raw_data() const { - std::vector data; - std::vector> coeffs; - for (auto &[term, c] : terms) { - data.push_back(term); - coeffs.push_back(c); - } - - return std::make_pair(data, coeffs); -} - -spin_op &spin_op::operator=(const spin_op &other) { - terms = other.terms; - return *this; -} - -spin_op operator+(double coeff, spin_op op) { - return spin_op(op.num_qubits()) * coeff + op; -} -spin_op operator+(spin_op op, double coeff) { - return op + spin_op(op.num_qubits()) * coeff; -} -spin_op operator-(double coeff, spin_op op) { - return spin_op(op.num_qubits()) * coeff - op; -} -spin_op operator-(spin_op op, double coeff) { - return op - spin_op(op.num_qubits()) * coeff; -} - -namespace spin { -spin_op i(const std::size_t idx) { return spin_op(pauli::I, idx); } -spin_op x(const std::size_t idx) { return spin_op(pauli::X, idx); } -spin_op y(const std::size_t idx) { return spin_op(pauli::Y, idx); } -spin_op z(const std::size_t idx) { return spin_op(pauli::Z, idx); } -} // namespace spin - -std::vector spin_op::getDataRepresentation() const { - std::vector dataVec; - for (auto &[term, coeff] : terms) { - auto nq = term.size() / 2; - for (std::size_t i = 0; i < nq; i++) { - if (term[i] && term[i + nq]) { - dataVec.push_back(3.); - } else if (term[i]) { - dataVec.push_back(1.); - } else if (term[i + nq]) { - dataVec.push_back(2.); - } else { - dataVec.push_back(0.); - } - } - dataVec.push_back(coeff.real()); - dataVec.push_back(coeff.imag()); - } - dataVec.push_back(num_terms()); - return dataVec; -} - -spin_op binary_spin_op_reader::read(const std::string &data_filename) { - std::ifstream input(data_filename, std::ios::binary); - if (input.fail()) - throw std::runtime_error(data_filename + " does not exist."); - - input.seekg(0, std::ios_base::end); - std::size_t size = input.tellg(); - input.seekg(0, std::ios_base::beg); - std::vector input_vec(size / sizeof(double)); - input.read((char *)&input_vec[0], size); - auto n_terms = (int)input_vec.back(); - auto nQubits = (input_vec.size() - 1 - 2 * n_terms) / n_terms; - spin_op s(input_vec, nQubits); - return s; -} -} // namespace cudaq diff --git a/runtime/cudaq/spin_op.h b/runtime/cudaq/spin_op.h index d1d0a0749f4..edbd7a9feb4 100644 --- a/runtime/cudaq/spin_op.h +++ b/runtime/cudaq/spin_op.h @@ -8,387 +8,112 @@ #pragma once -#include "cudaq/utils/matrix.h" -#include "utils/cudaq_utils.h" #include -#include -#include -#include #include +#include -// Define friend functions for operations between spin_op and scalars. -#define CUDAQ_SPIN_SCALAR_OPERATIONS(op, U) \ - friend spin_op operator op(const spin_op &lhs, const U &rhs) noexcept { \ - spin_op nrv(lhs); \ - nrv op## = rhs; \ - return nrv; \ - } \ - friend spin_op operator op(const spin_op &lhs, U &&rhs) noexcept { \ - spin_op nrv(lhs); \ - nrv op## = std::move(rhs); \ - return nrv; \ - } \ - friend spin_op &&operator op(spin_op &&lhs, const U &rhs) noexcept { \ - lhs op## = rhs; \ - return std::move(lhs); \ - } \ - friend spin_op &&operator op(spin_op &&lhs, U &&rhs) noexcept { \ - lhs op## = std::move(rhs); \ - return std::move(lhs); \ - } \ - friend spin_op operator op(const U &lhs, const spin_op &rhs) noexcept { \ - spin_op nrv(rhs); \ - nrv op## = lhs; \ - return nrv; \ - } \ - friend spin_op &&operator op(const U &lhs, spin_op &&rhs) noexcept { \ - rhs op## = lhs; \ - return std::move(rhs); \ - } \ - friend spin_op operator op(U &&lhs, const spin_op &rhs) noexcept { \ - spin_op nrv(rhs); \ - nrv op## = std::move(lhs); \ - return nrv; \ - } \ - friend spin_op &&operator op(U &&lhs, spin_op &&rhs) noexcept { \ - rhs op## = std::move(lhs); \ - return std::move(rhs); \ - } - -// Define friend functions for operations between two spin_ops -#define CUDAQ_SPIN_OPERATORS(op) \ - friend spin_op operator op(const spin_op &lhs, \ - const spin_op &rhs) noexcept { \ - spin_op nrv(lhs); \ - nrv op## = rhs; \ - return nrv; \ - } \ - \ - friend spin_op &&operator op(const spin_op &lhs, spin_op &&rhs) noexcept { \ - rhs op## = lhs; \ - return std::move(rhs); \ - } \ - \ - friend spin_op &&operator op(spin_op &&lhs, const spin_op &rhs) noexcept { \ - lhs op## = rhs; \ - return std::move(lhs); \ - } \ - \ - friend spin_op &&operator op(spin_op &&lhs, spin_op &&rhs) noexcept { \ - lhs op## = std::move(rhs); \ - return std::move(lhs); \ - } +#include "cudaq/operators/helpers.h" +#include "cudaq/operators/operator_leafs.h" +#include "cudaq/utils/matrix.h" namespace cudaq { -class spin_op; -/// @brief Utility enum representing Paulis. enum class pauli { I, X, Y, Z }; -namespace spin { - -/// @brief Return a spin_op == to I on the `idx` qubit -spin_op i(const std::size_t idx); - -/// @brief Return a spin_op == X on the `idx` qubit -spin_op x(const std::size_t idx); - -/// @brief Return a spin_op == Y on the `idx` qubit -spin_op y(const std::size_t idx); - -/// @brief Return a spin_op == Z on the `idx` qubit -spin_op z(const std::size_t idx); -} // namespace spin - -/// @brief The spin_op represents a general sum of Pauli tensor products. -/// It exposes the typical algebraic operations that allow programmers to -/// define primitive Pauli operators and use them to compose larger, more -/// complex Pauli tensor products and sums thereof. -class spin_op { -public: - /// @brief We represent the spin_op terms in binary symplectic form, - /// i.e. each term is a vector of 1s and 0s of size 2 * nQubits, - /// where the first n elements represent X, the next n elements - /// represent Z, and X=Z=1 -> Y on site i, X=1, Z=0 -> X on site i, - /// and X=0, Z=1 -> Z on site i. - using spin_op_term = std::vector; - using key_type = spin_op_term; - using mapped_type = std::complex; - - bool empty() const { return terms.empty(); } - - template - struct iterator { - - using _iter_type = - std::unordered_map>::iterator; - using _const_iter_type = - std::unordered_map>::const_iterator; - using iter_type = - std::conditional_t, _iter_type, - _const_iter_type>; - iterator(iterator &&) = default; - - iterator(iterator const &other) : iter(other.iter) {} - iterator(iter_type i) : iter(i) {} - ~iterator() { - for (auto &c : created) { - auto *ptr = c.release(); - delete ptr; - } - created.clear(); - } - - QualifiedSpinOp &operator*() { - // We have to store pointers to spin_op terms here - // so that we can return references or pointers to them - // based on the current state of the unordered_map iterator. - created.emplace_back(std::make_unique(*iter)); - return *created.back(); - } - - QualifiedSpinOp *operator->() { - created.emplace_back(std::make_unique(*iter)); - return created.back().get(); - } - - iterator &operator++() { - iter++; - return *this; - } - iterator &operator++(int) { - iterator &tmp = *this; - ++(*this); - return tmp; - } - - friend bool operator==(const iterator &a, const iterator &b) { - return a.iter == b.iter; - }; - friend bool operator!=(const iterator &a, const iterator &b) { - return a.iter != b.iter; - }; - - private: - iter_type iter; - std::vector> created; - }; +class spin_handler : public operator_handler { + template + friend class product_op; private: - /// We want these creation functions to have access to - /// spin_op constructors that programmers don't need - friend spin_op spin::i(const std::size_t); - friend spin_op spin::x(const std::size_t); - friend spin_op spin::y(const std::size_t); - friend spin_op spin::z(const std::size_t); - - /// @brief The spin_op representation. The spin_op is equivalent - /// to a mapping of unique terms to their term coefficient. - std::unordered_map> terms; - - /// @brief Utility map that takes the Pauli enum to a string representation - std::map pauli_to_str{ - {pauli::I, "I"}, {pauli::X, "X"}, {pauli::Y, "Y"}, {pauli::Z, "Z"}}; - - /// @brief Expand this spin_op binary symplectic representation to - /// a larger number of qubits. - void expandToNQubits(const std::size_t nQubits); - -public: - /// @brief The constructor, takes a single term / coefficient pair - spin_op(std::pair> &termData); - - /// @brief The constructor, takes a single term / coefficient constant pair - spin_op(const std::pair> &termData); - - /// @brief Constructor, takes the Pauli type, the qubit site, and the - /// term coefficient. Constructs a `spin_op` of one Pauli on one qubit. - spin_op(pauli, const std::size_t id, std::complex coeff = 1.0); - - /// @brief Constructor, takes the binary representation of a single term and - /// its coefficient. - spin_op(const spin_op_term &term, const std::complex &coeff); - - /// @brief Constructor, takes a full set of terms for the composite spin op - /// as an unordered_map mapping individual terms to their coefficient. - spin_op(const std::unordered_map> &_terms); - - /// @brief Construct from a vector of term data. - spin_op(const std::vector &bsf, - const std::vector> &coeffs); - - /// @brief Return a random spin operator acting on the specified number of - /// qubits and composed of the given number of terms. Override `seed` for - /// repeatability. - static spin_op random(std::size_t nQubits, std::size_t nTerms, - unsigned int seed = std::random_device{}()); - - /// @brief Return a `spin_op` representative of the input - /// Pauli word, e.g. XYX for a `spin_op` on 3 qubits with - /// a X support on the first and 3rd and Y support on the second. - static spin_op from_word(const std::string &pauliWord); - - /// @brief Constructor, creates the identity term - spin_op(); - - /// @brief Construct the identity term on the given number of qubits. - spin_op(std::size_t numQubits); - - /// @brief Copy constructor - spin_op(const spin_op &o); + // I = 0, Z = 1, X = 2, Y = 3 + int op_code; + std::size_t degree; - /// @brief Construct this spin_op from a serialized representation. - /// Specifically, this encoding is via a vector of doubles. The encoding is - /// as follows: for each term, a list of doubles where element `i` is - /// a 3.0 for a Y, a 1.0 for a X, and a 2.0 for a Z on qubit i, followed by - /// the real and imaginary part of the coefficient. Each term is appended to - /// the array forming one large 1d array of doubles. The array is ended with - /// the total number of terms represented as a double. - spin_op(const std::vector &data_rep, std::size_t nQubits); + spin_handler(std::size_t target, int op_code); - /// The destructor - ~spin_op() = default; + // private helpers - /// @brief Return iterator to start of spin_op terms. - iterator begin(); + std::string op_code_to_string() const; + virtual std::string op_code_to_string( + std::unordered_map &dimensions) const override; - /// @brief Return iterator to end of spin_op terms. - iterator end(); + std::complex inplace_mult(const spin_handler &other); - /// @brief Return constant iterator to start of `spin_op` terms. - iterator begin() const; + // helper function for matrix creations + static void create_matrix( + const std::string &pauli_word, + const std::function)> + &process_element, + bool invert_order); - /// @brief Return constant iterator to end of `spin_op` terms. - iterator end() const; - - /// @brief Set the provided spin_op equal to this one and return *this. - spin_op &operator=(const spin_op &); - - /// @brief Add the given spin_op to this one and return *this - spin_op &operator+=(const spin_op &v) noexcept; - - /// @brief Subtract the given spin_op from this one and return *this - spin_op &operator-=(const spin_op &v) noexcept; - - /// @brief Multiply the given spin_op with this one and return *this - spin_op &operator*=(const spin_op &v) noexcept; - - /// @brief Return true if this spin_op is equal to the given one. Equality - /// here does not consider the coefficients. - bool operator==(const spin_op &v) const noexcept; - - /// @brief Multiply this spin_op by the given double. - spin_op &operator*=(const double v) noexcept; - - /// @brief Multiply this spin_op by the given complex value - spin_op &operator*=(const std::complex v) noexcept; - - CUDAQ_SPIN_SCALAR_OPERATIONS(*, double) - CUDAQ_SPIN_SCALAR_OPERATIONS(*, std::complex) - CUDAQ_SPIN_OPERATORS(+) - CUDAQ_SPIN_OPERATORS(*) - - // Define the subtraction operators - friend spin_op operator-(const spin_op &lhs, const spin_op &rhs) noexcept { - spin_op nrv(lhs); - nrv -= rhs; - return nrv; - } - - friend spin_op operator-(const spin_op &lhs, spin_op &&rhs) noexcept { - spin_op nrv(lhs); - nrv -= std::move(rhs); - return nrv; - } - - friend spin_op &&operator-(spin_op &&lhs, const spin_op &rhs) noexcept { - lhs -= rhs; - return std::move(lhs); - } - - friend spin_op &&operator-(spin_op &&lhs, spin_op &&rhs) noexcept { - lhs -= std::move(rhs); - return std::move(lhs); - } - - /// @brief Return the number of qubits this spin_op is on - std::size_t num_qubits() const; - - /// @brief Return the number of terms in this spin_op - std::size_t num_terms() const; - - /// @brief For a spin_op with 1 term, get that terms' coefficient. - /// Throws an exception for spin_ops with > 1 terms. - std::complex get_coefficient() const; +public: + // read-only properties - /// @brief Return the binary symplectic form data - std::pair, std::vector>> - get_raw_data() const; + pauli as_pauli() const; - /// @brief Is this spin_op == to the identity - bool is_identity() const; + virtual std::string unique_id() const override; - /// @brief Dump a string representation of this spin_op to standard out. - void dump() const; + virtual std::vector degrees() const override; - /// @brief Return a string representation of this spin_op - std::string to_string(bool printCoefficients = true) const; + std::size_t target() const; - /// @brief Return the vector serialized representation of this - /// spin_op. (see the constructor for the encoding) - std::vector getDataRepresentation() const; + // constructors and destructors - /// @brief Return the serialized representation of this spin_op, such that it - /// is fully constructible with a spin_op() constructor. - std::tuple, std::size_t> getDataTuple() const; + spin_handler(std::size_t target); - /// @brief Return a vector of spin_op representing a distribution of the - /// terms in this spin_op into equally sized chunks. - std::vector distribute_terms(std::size_t numChunks) const; + spin_handler(pauli p, std::size_t target); - /// @brief Apply the give functor on each term of this spin_op. This method - /// can enable general reductions via lambda capture variables. - void for_each_term(std::function &&) const; + ~spin_handler() = default; - /// @brief Apply the functor on each Pauli in this 1-term spin_op. An - /// exception is thrown if there are more than 1 terms. Users should pass a - /// functor that takes the `pauli` type and the qubit index. - void for_each_pauli(std::function &&) const; + // evaluations - /// @brief Return a dense matrix representation of this - /// spin_op. - complex_matrix to_matrix() const; + /// @brief Computes the sparse matrix representation of a Pauli string. + /// By default, the ordering of the matrix matches the ordering of the Pauli + /// string, + static cudaq::detail::EigenSparseMatrix + to_sparse_matrix(const std::string &pauli, std::complex coeff = 1., + bool invert_order = false); - /// @brief Typedef for a vector of non-zero sparse matrix elements. - using csr_spmatrix = - std::tuple>, std::vector, - std::vector>; + /// @brief Computes the matrix representation of a Pauli string. + /// By default, the ordering of the matrix matches the ordering of the Pauli + /// string, + static complex_matrix to_matrix(const std::string &pauli, + std::complex coeff = 1., + bool invert_order = false); - /// @brief Return a sparse matrix representation of this `spin_op`. The - /// return type encodes all non-zero `(row, col, value)` elements. - csr_spmatrix to_sparse_matrix() const; -}; + /// @brief Return the `matrix_handler` 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}`. + virtual complex_matrix + to_matrix(std::unordered_map &dimensions, + const std::unordered_map> + ¶meters = {}) const override; -/// @brief Add a double and a spin_op -spin_op operator+(double coeff, spin_op op); + virtual std::string to_string(bool include_degrees) const override; -/// @brief Add a double and a spin_op -spin_op operator+(spin_op op, double coeff); + // comparisons -/// @brief Subtract a double and a spin_op -spin_op operator-(double coeff, spin_op op); + bool operator==(const spin_handler &other) const; -/// @brief Subtract a spin_op and a double -spin_op operator-(spin_op op, double coeff); + // defined operators -class spin_op_reader { -public: - virtual ~spin_op_reader() = default; - virtual spin_op read(const std::string &data_filename) = 0; -}; - -class binary_spin_op_reader : public spin_op_reader { -public: - spin_op read(const std::string &data_filename) override; + static spin_handler z(std::size_t degree); + static spin_handler x(std::size_t degree); + static spin_handler y(std::size_t degree); }; } // namespace cudaq + +// needs to be down here such that the handler is defined +// before we include the template declarations that depend on it +#include "cudaq/operators.h" + +namespace cudaq::spin { +product_op i(std::size_t target); +product_op x(std::size_t target); +product_op y(std::size_t target); +product_op z(std::size_t target); +sum_op plus(std::size_t target); +sum_op minus(std::size_t target); +} // namespace cudaq::spin diff --git a/runtime/cudaq/utils/matrix.h b/runtime/cudaq/utils/matrix.h index 9e116da8232..1f56bf27921 100644 --- a/runtime/cudaq/utils/matrix.h +++ b/runtime/cudaq/utils/matrix.h @@ -197,7 +197,6 @@ class complex_matrix { EigenMatrix as_eigen() const; friend void bindComplexMatrix(pybind11::module_ &mod); - friend class spin_op; private: complex_matrix(const complex_matrix::value_type *v, diff --git a/runtime/nvqir/CMakeLists.txt b/runtime/nvqir/CMakeLists.txt index 97388e27112..ebfb4ca6be0 100644 --- a/runtime/nvqir/CMakeLists.txt +++ b/runtime/nvqir/CMakeLists.txt @@ -30,7 +30,7 @@ set_property(GLOBAL APPEND PROPERTY CUDAQ_RUNTIME_LIBS ${LIBRARY_NAME}) # and by keeping it private, others can specify the # backend library later on target_link_libraries(${LIBRARY_NAME} - PUBLIC cudaq-common cudaq-spin dl fmt::fmt-header-only) + PUBLIC cudaq-common cudaq-operator dl fmt::fmt-header-only) install(TARGETS ${LIBRARY_NAME} EXPORT nvqir-targets diff --git a/runtime/nvqir/CircuitSimulator.h b/runtime/nvqir/CircuitSimulator.h index f1dd2dc7387..036f358a893 100644 --- a/runtime/nvqir/CircuitSimulator.h +++ b/runtime/nvqir/CircuitSimulator.h @@ -141,8 +141,8 @@ class CircuitSimulator { virtual void applyExpPauli(double theta, const std::vector &controls, const std::vector &qubitIds, - const cudaq::spin_op &op) { - if (op.is_identity()) { + const cudaq::spin_op_term &term) { + if (term.is_identity()) { if (controls.empty()) { // exp(i*theta*Id) is noop if this is not a controlled gate. return; @@ -157,21 +157,29 @@ class CircuitSimulator { } flushGateQueue(); cudaq::info(" [CircuitSimulator decomposing] exp_pauli({}, {})", theta, - op.to_string(false)); + term.to_string()); std::vector qubitSupport; std::vector> basisChange; - op.for_each_pauli([&](cudaq::pauli type, std::size_t qubitIdx) { - auto qId = qubitIds[qubitIdx]; - if (type != cudaq::pauli::I) + if (term.num_ops() != qubitIds.size()) + throw std::runtime_error( + "incorrect number of qubits in exp_pauli - expecting " + + std::to_string(term.num_ops()) + " qubits"); + + std::size_t idx = 0; + for (const auto &op : term) { + auto pauli = op.as_pauli(); + // operator targets are relative to the qubit argument vector + auto qId = qubitIds[idx++]; + if (pauli != cudaq::pauli::I) qubitSupport.push_back(qId); - if (type == cudaq::pauli::Y) + if (pauli == cudaq::pauli::Y) basisChange.emplace_back([this, qId](bool reverse) { rx(!reverse ? M_PI_2 : -M_PI_2, qId); }); - else if (type == cudaq::pauli::X) + else if (pauli == cudaq::pauli::X) basisChange.emplace_back([this, qId](bool) { h(qId); }); - }); + } if (!basisChange.empty()) for (auto &basis : basisChange) @@ -1409,32 +1417,45 @@ class CircuitSimulatorBase : public CircuitSimulator { return measureResult; } + // FIXME: it would be cleaner and more consistent (with exp_pauli) if + // this function explicitly received a vector of qubit indices such that + // only the relative order of the target in the spin op is relevant. void measureSpinOp(const cudaq::spin_op &op) override { flushGateQueue(); if (executionContext->canHandleObserve) { - auto result = observe(*executionContext->spin.value()); + auto result = observe(executionContext->spin.value()); executionContext->expectationValue = result.expectation(); executionContext->result = result.raw_data(); return; } - assert(op.num_terms() == 1 && "Number of terms is not 1."); + if (op.num_terms() != 1) + // more than one term needs to be directly supported by the backend + throw std::runtime_error( + "measuring a sum of spin operators is not supported"); - cudaq::info("Measure {}", op.to_string(false)); + cudaq::info("Measure {}", op.to_string()); std::vector qubitsToMeasure; std::vector> basisChange; - op.for_each_pauli([&](cudaq::pauli type, std::size_t qubitIdx) { - if (type != cudaq::pauli::I) - qubitsToMeasure.push_back(qubitIdx); - if (type == cudaq::pauli::Y) - basisChange.emplace_back([&, qubitIdx](bool reverse) { - rx(!reverse ? M_PI_2 : -M_PI_2, qubitIdx); + auto term = *op.begin(); + for (const auto &p : term) { + auto pauli = p.as_pauli(); + // Note: qubit index is necessarily defined by target here + // since we don't explicitly pass the qubits the measurement + // applies to + auto target = p.target(); + if (pauli != cudaq::pauli::I) + qubitsToMeasure.push_back(target); + + if (pauli == cudaq::pauli::Y) + basisChange.emplace_back([&, target](bool reverse) { + rx(!reverse ? M_PI_2 : -M_PI_2, target); }); - else if (type == cudaq::pauli::X) - basisChange.emplace_back([&, qubitIdx](bool) { h(qubitIdx); }); - }); + else if (pauli == cudaq::pauli::X) + basisChange.emplace_back([&, target](bool) { h(target); }); + } // Change basis, flush the queue if (!basisChange.empty()) { diff --git a/runtime/nvqir/NVQIR.cpp b/runtime/nvqir/NVQIR.cpp index ed44a9e6758..b30190a73e9 100644 --- a/runtime/nvqir/NVQIR.cpp +++ b/runtime/nvqir/NVQIR.cpp @@ -853,7 +853,7 @@ Result *__quantum__qis__measure__body(Array *pauli_arr, Array *qubits) { // Let's give them that opportunity. if (currentContext->canHandleObserve) { circuitSimulator->flushGateQueue(); - auto result = circuitSimulator->observe(*currentContext->spin.value()); + auto result = circuitSimulator->observe(currentContext->spin.value()); currentContext->expectationValue = result.expectation(); currentContext->result = result.raw_data(); return ResultZero; diff --git a/runtime/nvqir/cudensitymat/CuDensityMatEvolution.cpp b/runtime/nvqir/cudensitymat/CuDensityMatEvolution.cpp index ff791fc63d8..66e7d54fc60 100644 --- a/runtime/nvqir/cudensitymat/CuDensityMatEvolution.cpp +++ b/runtime/nvqir/cudensitymat/CuDensityMatEvolution.cpp @@ -12,7 +12,7 @@ #include "CuDensityMatState.h" #include "CuDensityMatTimeStepper.h" #include "cudaq/algorithms/evolve_internal.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include #include #include @@ -43,7 +43,8 @@ evolve_result evolveSingle( bool storeIntermediateResults, std::optional shotsCount) { cudensitymatHandle_t handle = dynamics::Context::getCurrentContext()->getHandle(); - std::map dimensions = convertToOrderedMap(dimensionsMap); + std::map dimensions = + convertToOrderedMap(dimensionsMap); std::vector dims; for (const auto &[id, dim] : dimensions) dims.emplace_back(dim); diff --git a/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.cpp b/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.cpp index 22c0a80cfa7..2bf8dc81c4b 100644 --- a/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.cpp +++ b/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.cpp @@ -13,11 +13,12 @@ #include namespace { -std::vector getSubspaceExtents(const std::vector &modeExtents, - const std::vector °rees) { +std::vector +getSubspaceExtents(const std::vector &modeExtents, + const std::vector °rees) { std::vector subspaceExtents; - for (int degree : degrees) { + for (std::size_t degree : degrees) { if (degree >= modeExtents.size()) throw std::out_of_range("Degree exceeds modeExtents size."); @@ -27,12 +28,12 @@ std::vector getSubspaceExtents(const std::vector &modeExtents, return subspaceExtents; } -std::unordered_map +cudaq::dimension_map convertDimensions(const std::vector &modeExtents) { - std::unordered_map dimensions; + cudaq::dimension_map dimensions; for (size_t i = 0; i < modeExtents.size(); ++i) - dimensions[static_cast(i)] = static_cast(modeExtents[i]); + dimensions[i] = static_cast(modeExtents[i]); return dimensions; } @@ -73,14 +74,14 @@ cudaq::product_op computeDagger(const cudaq::matrix_handler &op) { const std::string daggerOpName = op.to_string(false) + "_dagger"; try { - auto func = [op](const std::vector &dimensions, + auto func = [op](const std::vector &dimensions, const std::unordered_map> ¶ms) { - std::unordered_map dims; + cudaq::dimension_map dims; if (dimensions.size() != op.degrees().size()) throw std::runtime_error("Dimension mismatched"); - for (int i = 0; i < dimensions.size(); ++i) { + for (std::size_t i = 0; i < dimensions.size(); ++i) { dims[op.degrees()[i]] = dimensions[i]; } auto originalMat = op.to_matrix(dims, params); @@ -228,7 +229,7 @@ cudaq::dynamics::CuDensityMatOpConverter::createElementaryOperator( const std::unordered_map> ¶meters, const std::vector &modeExtents) { auto subspaceExtents = getSubspaceExtents(modeExtents, elemOp.degrees()); - std::unordered_map dimensions = convertDimensions(modeExtents); + cudaq::dimension_map dimensions = convertDimensions(modeExtents); cudensitymatWrappedTensorCallback_t wrappedTensorCallback = cudensitymatTensorCallbackNone; @@ -245,17 +246,13 @@ cudaq::dynamics::CuDensityMatOpConverter::createElementaryOperator( opNames.emplace_back(opNames.back() + "_dagger"); opNames.emplace_back(cudaq::boson_op::number(0).begin()->to_string(false)); opNames.emplace_back(opNames.back() + "_dagger"); - opNames.emplace_back( - cudaq::sum_op::i(0).begin()->to_string(false)); + opNames.emplace_back(cudaq::spin_op::i(0).begin()->to_string(false)); opNames.emplace_back(opNames.back() + "_dagger"); - opNames.emplace_back( - cudaq::sum_op::x(0).begin()->to_string(false)); + opNames.emplace_back(cudaq::spin_op::x(0).begin()->to_string(false)); opNames.emplace_back(opNames.back() + "_dagger"); - opNames.emplace_back( - cudaq::sum_op::y(0).begin()->to_string(false)); + opNames.emplace_back(cudaq::spin_op::y(0).begin()->to_string(false)); opNames.emplace_back(opNames.back() + "_dagger"); - opNames.emplace_back( - cudaq::sum_op::z(0).begin()->to_string(false)); + opNames.emplace_back(cudaq::spin_op::z(0).begin()->to_string(false)); return opNames; }(); @@ -302,7 +299,7 @@ cudensitymatOperatorTerm_t cudaq::dynamics::CuDensityMatOpConverter::createProductOperatorTerm( const std::vector &elemOps, const std::vector &modeExtents, - const std::vector> °rees, + const std::vector> °rees, const std::vector> &dualModalities) { cudensitymatOperatorTerm_t term; @@ -342,7 +339,7 @@ cudaq::dynamics::CuDensityMatOpConverter::createProductOperatorTerm( "Elementary operator must act on a single degree."); for (size_t j = 0; j < sub_degrees.size(); j++) { - int degree = sub_degrees[j]; + std::size_t degree = sub_degrees[j]; int modality = modalities[j]; if (sub_degrees[i] < 0) @@ -413,7 +410,7 @@ cudaq::dynamics::CuDensityMatOpConverter::convertToCudensitymat( for (const auto &productOp : op) { std::vector elemOps; - std::vector> allDegrees; + std::vector> allDegrees; for (const auto &component : productOp) { // No need to check type // just call to_matrix on it @@ -454,7 +451,7 @@ cudaq::dynamics::CuDensityMatOpConverter::computeLindbladTerms( { // L * rho * L_dag std::vector elemOps; - std::vector> allDegrees; + std::vector> allDegrees; std::vector> all_action_dual_modalities; for (const auto &component : l_op) { @@ -495,7 +492,7 @@ cudaq::dynamics::CuDensityMatOpConverter::computeLindbladTerms( product_op L_daggerTimesL = -0.5 * ldag * l_op; { std::vector elemOps; - std::vector> allDegrees; + std::vector> allDegrees; std::vector> all_action_dual_modalities_left; std::vector> all_action_dual_modalities_right; for (const auto &component : L_daggerTimesL) { @@ -519,7 +516,7 @@ cudaq::dynamics::CuDensityMatOpConverter::computeLindbladTerms( // For left side, we need to reverse the order std::vector d2Ops(elemOps); std::reverse(d2Ops.begin(), d2Ops.end()); - std::vector> d2Degrees(allDegrees); + std::vector> d2Degrees(allDegrees); std::reverse(d2Degrees.begin(), d2Degrees.end()); cudensitymatOperatorTerm_t D2_term = createProductOperatorTerm( d2Ops, modeExtents, d2Degrees, all_action_dual_modalities_left); @@ -641,9 +638,9 @@ cudaq::dynamics::CuDensityMatOpConverter::wrapTensorCallback( context->paramNames[i], param_map[context->paramNames[i]]); } - std::unordered_map dimensions; - for (int i = 0; i < num_modes; ++i) { - dimensions[i] = static_cast(modeExtents[i]); + cudaq::dimension_map dimensions; + for (std::size_t i = 0; i < num_modes; ++i) { + dimensions[i] = modeExtents[i]; } if (dimensions.empty()) { diff --git a/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.h b/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.h index 56cc219c36d..db75fbed659 100644 --- a/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.h +++ b/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.h @@ -58,7 +58,7 @@ class CuDensityMatOpConverter { cudensitymatOperatorTerm_t createProductOperatorTerm( const std::vector &elemOps, const std::vector &modeExtents, - const std::vector> °rees, + const std::vector> °rees, const std::vector> &dualModalities); std::vector> diff --git a/runtime/nvqir/cudensitymat/CuDensityMatTimeStepper.h b/runtime/nvqir/cudensitymat/CuDensityMatTimeStepper.h index da0406a8bf2..69bed2e2742 100644 --- a/runtime/nvqir/cudensitymat/CuDensityMatTimeStepper.h +++ b/runtime/nvqir/cudensitymat/CuDensityMatTimeStepper.h @@ -9,7 +9,7 @@ #pragma once #include "CuDensityMatState.h" -#include "cudaq/base_time_stepper.h" +#include "cudaq/algorithms/base_time_stepper.h" #include namespace cudaq { diff --git a/runtime/nvqir/cudensitymat/RungeKuttaIntegrator.cpp b/runtime/nvqir/cudensitymat/RungeKuttaIntegrator.cpp index 9097f415714..76f91eaf2a5 100644 --- a/runtime/nvqir/cudensitymat/RungeKuttaIntegrator.cpp +++ b/runtime/nvqir/cudensitymat/RungeKuttaIntegrator.cpp @@ -11,7 +11,7 @@ #include "CuDensityMatState.h" #include "CuDensityMatTimeStepper.h" #include "common/Logger.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" namespace cudaq { namespace integrators { diff --git a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cpp b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cpp index 580c7d602ed..b21cd2df087 100644 --- a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cpp +++ b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cpp @@ -455,30 +455,37 @@ class CuStateVecCircuitSimulator /// rotation to delegate to the performant custatevecApplyPauliRotation. void applyExpPauli(double theta, const std::vector &controlIds, const std::vector &qubits, - const cudaq::spin_op &op) override { + const cudaq::spin_op_term &term) override { if (this->isInTracerMode()) { - nvqir::CircuitSimulator::applyExpPauli(theta, controlIds, qubits, op); + nvqir::CircuitSimulator::applyExpPauli(theta, controlIds, qubits, term); return; } flushGateQueue(); cudaq::info(" [cusv decomposing] exp_pauli({}, {})", theta, - op.to_string(false)); + term.to_string()); std::vector controls, targets; for (const auto &bit : controlIds) controls.emplace_back(static_cast(bit)); std::vector paulis; - op.for_each_pauli([&](cudaq::pauli p, std::size_t i) { - if (p == cudaq::pauli::I) + if (term.num_ops() != qubits.size()) + throw std::runtime_error( + "incorrect number of qubits for exp_pauli - expecting " + + std::to_string(term.num_ops()) + " qubits"); + + std::size_t idx = 0; + for (const auto &op : term) { + auto pauli = op.as_pauli(); + if (pauli == cudaq::pauli::I) paulis.push_back(custatevecPauli_t::CUSTATEVEC_PAULI_I); - else if (p == cudaq::pauli::X) + else if (pauli == cudaq::pauli::X) paulis.push_back(custatevecPauli_t::CUSTATEVEC_PAULI_X); - else if (p == cudaq::pauli::Y) + else if (pauli == cudaq::pauli::Y) paulis.push_back(custatevecPauli_t::CUSTATEVEC_PAULI_Y); else paulis.push_back(custatevecPauli_t::CUSTATEVEC_PAULI_Z); - targets.push_back(qubits[i]); - }); + targets.push_back(qubits[idx++]); + } HANDLE_ERROR(custatevecApplyPauliRotation( handle, deviceStateVector, cuStateVecCudaDataType, nQubitsAllocated, @@ -549,6 +556,7 @@ class CuStateVecCircuitSimulator // Use batched custatevecComputeExpectationsOnPauliBasis to compute all term // expectation values in one go uint32_t nPauliOperatorArrays = op.num_terms(); + assert(cudaq::spin_op::canonicalize(op) == op); // custatevecComputeExpectationsOnPauliBasis will throw errors if // nPauliOperatorArrays is 0, so catch that case early. @@ -585,18 +593,20 @@ class CuStateVecCircuitSimulator // Contruct data to send on to custatevec std::vector termStrs; termStrs.reserve(nPauliOperatorArrays); - op.for_each_term([&](cudaq::spin_op &term) { - coeffs.emplace_back(term.get_coefficient()); + for (const auto &term : op) { + coeffs.emplace_back(term.evaluate_coefficient()); std::vector paulis; std::vector idxs; - paulis.reserve(term.num_qubits()); - idxs.reserve(term.num_qubits()); - term.for_each_pauli([&](cudaq::pauli p, std::size_t idx) { - if (p != cudaq::pauli::I) { - paulis.emplace_back(cudaqToCustateVec(p)); - idxs.emplace_back(idx); + paulis.reserve(term.num_ops()); + idxs.reserve(term.num_ops()); + for (const auto &p : term) { + auto pauli = p.as_pauli(); + if (pauli != cudaq::pauli::I) { + auto target = p.target(); + paulis.emplace_back(cudaqToCustateVec(pauli)); + idxs.emplace_back(target); // Only X and Y pauli's translate to applied gates - if (p != cudaq::pauli::Z) { + if (pauli != cudaq::pauli::Z) { // One operation for applying the term summaryData.svGateUpdate(/*nControls=*/0, /*nTargets=*/1, stateDimension, @@ -607,14 +617,14 @@ class CuStateVecCircuitSimulator stateDimension * sizeof(DataType)); } } - }); + } pauliOperatorsArrayHolder.emplace_back(std::move(paulis)); basisBitsArrayHolder.emplace_back(std::move(idxs)); pauliOperatorsArray.emplace_back(pauliOperatorsArrayHolder.back().data()); basisBitsArray.emplace_back(basisBitsArrayHolder.back().data()); nBasisBitsArray.emplace_back(pauliOperatorsArrayHolder.back().size()); - termStrs.emplace_back(term.to_string(false)); - }); + termStrs.emplace_back(term.get_term_id()); + } std::vector expectationValues(nPauliOperatorArrays); HANDLE_ERROR(custatevecComputeExpectationsOnPauliBasis( handle, deviceStateVector, cuStateVecCudaDataType, nQubitsAllocated, diff --git a/runtime/nvqir/cutensornet/simulator_cutensornet.cpp b/runtime/nvqir/cutensornet/simulator_cutensornet.cpp index 4a0c8b3bf12..c4e690ed1da 100644 --- a/runtime/nvqir/cutensornet/simulator_cutensornet.cpp +++ b/runtime/nvqir/cutensornet/simulator_cutensornet.cpp @@ -344,10 +344,9 @@ SimulatorTensorNetBase::sample(const std::vector &measuredBits, LOG_API_TIME(); std::vector measuredBitIds(measuredBits.begin(), measuredBits.end()); if (shots < 1) { - cudaq::spin_op::spin_op_term allZTerm(2 * m_state->getNumQubits(), 0); - for (const auto &m : measuredBits) - allZTerm.at(m_state->getNumQubits() + m) = 1; - cudaq::spin_op allZ(allZTerm, 1.0); + auto allZ = cudaq::spin_op::identity(); + for (std::size_t t = 0; t < m_state->getNumQubits(); ++t) + allZ *= cudaq::spin_op::z(t); // Just compute the expected value on return cudaq::ExecutionResult({}, observe(allZ).expectation()); } @@ -401,6 +400,7 @@ bool SimulatorTensorNetBase::canHandleObserve() { /// @brief Evaluate the expectation value of a given observable cudaq::observe_result SimulatorTensorNetBase::observe(const cudaq::spin_op &ham) { + assert(cudaq::spin_op::canonicalize(ham) == ham); LOG_API_TIME(); prepareQubitTensorState(); if (!m_reuseContractionPathObserve) { @@ -413,41 +413,28 @@ SimulatorTensorNetBase::observe(const cudaq::spin_op &ham) { expVal += spinOp.getIdentityTermOffset(); return cudaq::observe_result(expVal.real(), ham, cudaq::sample_result(cudaq::ExecutionResult( - {}, ham.to_string(false), expVal.real()))); + {}, ham.to_string(), expVal.real()))); } std::vector termStrs; - std::vector terms; - std::vector> coeffs; + std::vector prods; termStrs.reserve(ham.num_terms()); - terms.reserve(ham.num_terms()); - coeffs.reserve(ham.num_terms()); - - // Note: as the spin_op terms are stored as an unordered map, we need to - // iterate in one loop to collect all the data (string, symplectic data, and - // coefficient). - ham.for_each_term([&](cudaq::spin_op &term) { - termStrs.emplace_back(term.to_string(false)); - auto [symplecticRep, coeff] = term.get_raw_data(); - if (symplecticRep.size() != 1 || coeff.size() != 1) - throw std::runtime_error(fmt::format( - "Unexpected data encountered when iterating spin operator terms: " - "expecting a single term, got {} terms.", - symplecticRep.size())); - terms.emplace_back(symplecticRep[0]); - coeffs.emplace_back(coeff[0]); - }); + prods.reserve(ham.num_terms()); + for (auto &&term : ham) { + termStrs.emplace_back(term.get_term_id()); + prods.push_back(std::move(term)); + } // Compute the expectation value for all terms const auto termExpVals = m_state->computeExpVals( - terms, this->executionContext->numberTrajectories); + prods, this->executionContext->numberTrajectories); std::complex expVal = 0.0; // Construct per-term data in the final observe_result std::vector results; - results.reserve(terms.size()); + results.reserve(prods.size()); - for (std::size_t i = 0; i < terms.size(); ++i) { - expVal += (coeffs[i] * termExpVals[i]); + for (std::size_t i = 0; i < prods.size(); ++i) { + expVal += termExpVals[i]; results.emplace_back( cudaq::ExecutionResult({}, termStrs[i], termExpVals[i].real())); } diff --git a/runtime/nvqir/cutensornet/simulator_mps_register.cpp b/runtime/nvqir/cutensornet/simulator_mps_register.cpp index 53d03d55da9..f2d72570652 100644 --- a/runtime/nvqir/cutensornet/simulator_mps_register.cpp +++ b/runtime/nvqir/cutensornet/simulator_mps_register.cpp @@ -111,7 +111,7 @@ class SimulatorMPS : public SimulatorTensorNetBase { virtual void applyExpPauli(double theta, const std::vector &controls, const std::vector &qubitIds, - const cudaq::spin_op &op) override { + const cudaq::spin_op_term &op) override { if (this->isInTracerMode()) { nvqir::CircuitSimulator::applyExpPauli(theta, controls, qubitIds, op); return; @@ -123,28 +123,32 @@ class SimulatorMPS : public SimulatorTensorNetBase { // Hence, we check if this is a Rxx(theta), Ryy(theta), or Rzz(theta), which // are commonly-used gates and apply the operation directly (the base // decomposition will result in 2 CNOT gates). - const auto shouldHandlePauliOp = - [](const cudaq::spin_op &opToCheck) -> bool { - const std::string opStr = opToCheck.to_string(false); - return opStr == "XX" || opStr == "YY" || opStr == "ZZ"; + const auto shouldHandlePauliOp = [](const std::string &pauli_word) -> bool { + return pauli_word == "XX" || pauli_word == "YY" || pauli_word == "ZZ"; }; - if (controls.empty() && qubitIds.size() == 2 && shouldHandlePauliOp(op)) { + + // FIXME: the implementation here assumes that the spin op term is not + // a general spin op term, but really just a pauli word; it's coefficient is + // silently ignored. This works because it was actually constructed from a + // pauli word - we should just pass that one along. + auto pauli_word = op.get_pauli_word(); + if (controls.empty() && qubitIds.size() == 2 && + shouldHandlePauliOp(pauli_word)) { flushGateQueue(); cudaq::info("[SimulatorMPS] (apply) exp(i*{}*{}) ({}, {}).", theta, - op.to_string(false), qubitIds[0], qubitIds[1]); + op.to_string(), qubitIds[0], qubitIds[1]); const GateApplicationTask task = [&]() { - const std::string opStr = op.to_string(false); // Note: Rxx(angle) == exp(-i*angle/2 XX) // i.e., exp(i*theta XX) == Rxx(-2 * theta) - if (opStr == "XX") { + if (pauli_word == "XX") { // Note: use a special name so that the gate matrix caching procedure // works properly. return GateApplicationTask("Rxx", generateXX(-2.0 * theta), {}, qubitIds, {theta}); - } else if (opStr == "YY") { + } else if (pauli_word == "YY") { return GateApplicationTask("Ryy", generateYY(-2.0 * theta), {}, qubitIds, {theta}); - } else if (opStr == "ZZ") { + } else if (pauli_word == "ZZ") { return GateApplicationTask("Rzz", generateZZ(-2.0 * theta), {}, qubitIds, {theta}); } @@ -248,35 +252,21 @@ class SimulatorMPS : public SimulatorTensorNetBase { } // Helper to prepare term-by-term data from a spin op - static std::tuple, - std::vector, - std::vector>> + static std::tuple, std::vector> prepareSpinOpTermData(const cudaq::spin_op &ham) { std::vector termStrs; - std::vector terms; - std::vector> coeffs; + std::vector prods; termStrs.reserve(ham.num_terms()); - terms.reserve(ham.num_terms()); - coeffs.reserve(ham.num_terms()); - - // Note: as the spin_op terms are stored as an unordered map, we need to - // iterate in one loop to collect all the data (string, symplectic data, and - // coefficient). - ham.for_each_term([&](cudaq::spin_op &term) { - termStrs.emplace_back(term.to_string(false)); - auto [symplecticRep, coeff] = term.get_raw_data(); - if (symplecticRep.size() != 1 || coeff.size() != 1) - throw std::runtime_error(fmt::format( - "Unexpected data encountered when iterating spin operator terms: " - "expecting a single term, got {} terms.", - symplecticRep.size())); - terms.emplace_back(symplecticRep[0]); - coeffs.emplace_back(coeff[0]); - }); - return std::make_tuple(termStrs, terms, coeffs); + prods.reserve(ham.num_terms()); + for (auto &&term : prods) { + termStrs.emplace_back(term.get_term_id()); + prods.push_back(std::move(term)); + } + return std::make_tuple(termStrs, prods); } cudaq::observe_result observe(const cudaq::spin_op &ham) override { + assert(cudaq::spin_op::canonicalize(ham) == ham); LOG_API_TIME(); const bool hasNoise = executionContext && executionContext->noiseModel; // If no noise, just use base class implementation. @@ -289,7 +279,7 @@ class SimulatorMPS : public SimulatorTensorNetBase { ? this->executionContext->numberTrajectories.value() : TensorNetState::g_numberTrajectoriesForObserve; - auto [termStrs, terms, coeffs] = prepareSpinOpTermData(ham); + auto [termStrs, terms] = prepareSpinOpTermData(ham); std::vector> termExpVals(terms.size(), 0.0); for (std::size_t i = 0; i < numObserveTrajectories; ++i) { @@ -311,7 +301,6 @@ class SimulatorMPS : public SimulatorTensorNetBase { results.reserve(terms.size()); for (std::size_t i = 0; i < terms.size(); ++i) { - expVal += (coeffs[i] * termExpVals[i]); results.emplace_back( cudaq::ExecutionResult({}, termStrs[i], termExpVals[i].real())); } diff --git a/runtime/nvqir/cutensornet/tensornet_spin_op.cpp b/runtime/nvqir/cutensornet/tensornet_spin_op.cpp index 913f416f0e6..ce963ac8ffa 100644 --- a/runtime/nvqir/cutensornet/tensornet_spin_op.cpp +++ b/runtime/nvqir/cutensornet/tensornet_spin_op.cpp @@ -16,16 +16,17 @@ TensorNetworkSpinOp::TensorNetworkSpinOp(const cudaq::spin_op &spinOp, cutensornetHandle_t handle) : m_cutnHandle(handle) { LOG_API_TIME(); - const std::vector qubitDims(spinOp.num_qubits(), 2); + auto degrees = spinOp.degrees(); + const std::vector qubitDims(degrees.size(), 2); HANDLE_CUTN_ERROR(cutensornetCreateNetworkOperator( - m_cutnHandle, spinOp.num_qubits(), qubitDims.data(), CUDA_C_64F, + m_cutnHandle, degrees.size(), qubitDims.data(), CUDA_C_64F, &m_cutnNetworkOperator)); // Heuristic threshold to perform direct observable calculation. // If the number of qubits in the spin_op is small, it is more efficient to // directly convert the spin_op into a matrix and perform the contraction // in one go rather than summing over term-by-term contractions. constexpr std::size_t NUM_QUBITS_THRESHOLD_DIRECT_OBS = 10; - if (spinOp.num_qubits() < NUM_QUBITS_THRESHOLD_DIRECT_OBS) { + if (degrees.size() < NUM_QUBITS_THRESHOLD_DIRECT_OBS) { const auto spinMat = spinOp.to_matrix(); std::vector> opMat; opMat.reserve(spinMat.rows() * spinMat.cols()); @@ -41,10 +42,9 @@ TensorNetworkSpinOp::TensorNetworkSpinOp(const cudaq::spin_op &spinOp, cudaMemcpyHostToDevice)); m_mat_d.emplace_back(opMat_d); const std::vector numModes = { - static_cast(spinOp.num_qubits())}; + static_cast(degrees.size())}; std::vector pauliTensorData = {opMat_d}; - std::vector stateModes(spinOp.num_qubits()); - std::iota(stateModes.begin(), stateModes.end(), 0); + std::vector stateModes(degrees.begin(), degrees.end()); std::vector dataStateModes = {stateModes.data()}; HANDLE_CUTN_ERROR(cutensornetNetworkOperatorAppendProduct( m_cutnHandle, m_cutnNetworkOperator, @@ -88,24 +88,29 @@ TensorNetworkSpinOp::TensorNetworkSpinOp(const cudaq::spin_op &spinOp, m_pauli_d[pauli] = d_mat; } - spinOp.for_each_term([&](cudaq::spin_op &term) { + for (const auto &term : spinOp) { + auto coeff = term.evaluate_coefficient(); if (term.is_identity()) { // Note: we don't add I Pauli. - m_identityCoeff = term.get_coefficient(); - return; + m_identityCoeff = coeff; + continue; } - const cuDoubleComplex termCoeff{term.get_coefficient().real(), - term.get_coefficient().imag()}; + const cuDoubleComplex termCoeff{coeff.real(), coeff.imag()}; std::vector pauliTensorData; std::vector> stateModes; - term.for_each_pauli([&](cudaq::pauli p, std::size_t idx) { - if (p != cudaq::pauli::I) { + for (const auto &p : term) { + auto pauli = p.as_pauli(); + // FIXME: used (only) for observe - + // matches the behavior in CircuitSimulator.h to require + // the target to match the intended qubit index. + auto target = p.target(); + if (pauli != cudaq::pauli::I) { stateModes.emplace_back( - std::vector{static_cast(idx)}); - pauliTensorData.emplace_back(m_pauli_d[p]); + std::vector{static_cast(target)}); + pauliTensorData.emplace_back(m_pauli_d[pauli]); } - }); + } std::vector numModes(pauliTensorData.size(), 1); std::vector dataStateModes; @@ -117,7 +122,7 @@ TensorNetworkSpinOp::TensorNetworkSpinOp(const cudaq::spin_op &spinOp, pauliTensorData.size(), numModes.data(), dataStateModes.data(), /*tensorModeStrides*/ nullptr, pauliTensorData.data(), /*componentId*/ nullptr)); - }); + } } } diff --git a/runtime/nvqir/cutensornet/tensornet_spin_op.h b/runtime/nvqir/cutensornet/tensornet_spin_op.h index 79d362d3119..9e069aec5ff 100644 --- a/runtime/nvqir/cutensornet/tensornet_spin_op.h +++ b/runtime/nvqir/cutensornet/tensornet_spin_op.h @@ -7,7 +7,7 @@ ******************************************************************************/ #pragma once -#include "cudaq/spin_op.h" +#include "cudaq/operators.h" #include "cutensornet.h" namespace nvqir { diff --git a/runtime/nvqir/cutensornet/tensornet_state.cpp b/runtime/nvqir/cutensornet/tensornet_state.cpp index 895df764ab6..9e2bdc4c0a6 100644 --- a/runtime/nvqir/cutensornet/tensornet_state.cpp +++ b/runtime/nvqir/cutensornet/tensornet_state.cpp @@ -8,6 +8,7 @@ #include "tensornet_state.h" #include "common/EigenDense.h" +#include #include #include @@ -682,16 +683,13 @@ TensorNetState::factorizeMPS(int64_t maxExtent, double absCutoff, } std::vector> TensorNetState::computeExpVals( - const std::vector> &symplecticRepr, + const std::vector &product_terms, const std::optional &numberTrajectories) { LOG_API_TIME(); - if (symplecticRepr.empty()) + if (product_terms.empty()) return {}; const std::size_t numQubits = getNumQubits(); - const auto numSpinOps = symplecticRepr[0].size() / 2; - std::vector> allExpVals; - allExpVals.reserve(symplecticRepr.size()); constexpr int ALIGNMENT_BYTES = 256; const int placeHolderArraySize = ALIGNMENT_BYTES * numQubits; @@ -791,36 +789,62 @@ std::vector> TensorNetState::computeExpVals( return numberTrajectories.value(); return g_numberTrajectoriesForObserve; }(); - for (const auto &term : symplecticRepr) { + + std::vector> allExpVals; + allExpVals.reserve(product_terms.size()); + + // NOTE: The logic in the loop below relies on the following: + // Spin operator terms are canonically ordered. Specifically, we can + // assume that every operator does not act on the same target more than + // once. That assumption is only checked via an assertion. + // Additionally, the loops that inject identities rely on the ordering + // starting with the smallest index/degree. We could write it agnostic + // by querying cudaq::operator_handler::canonical_order, but I kept it + // at putting an assert in for that one, too. + assert(cudaq::operator_handler::canonical_order(0, 1)); + constexpr int PAULI_ARRAY_SIZE_BYTES = 4 * sizeof(std::complex); + for (const auto &prod : product_terms) { + assert(prod.is_canonicalized()); bool allIdOps = true; - for (std::size_t i = 0; i < numQubits; ++i) { - // Memory address of this Pauli term in the placeholder array. - auto *address = static_cast(pauliMats_h) + i * ALIGNMENT_BYTES; - constexpr int PAULI_ARRAY_SIZE_BYTES = 4 * sizeof(std::complex); + auto offset = 0; + for (const auto &p : prod) { // The Pauli matrix data that we want to load to this slot. // Default is the Identity matrix. const std::complex *pauliMatrixPtr = PauliI_h; - if (i < numSpinOps) { - if (term[i] && term[i + numSpinOps]) { - // Y - allIdOps = false; - pauliMatrixPtr = PauliY_h; - } else if (term[i]) { - // X - allIdOps = false; - pauliMatrixPtr = PauliX_h; - } else if (term[i + numSpinOps]) { - // Z - allIdOps = false; - pauliMatrixPtr = PauliZ_h; - } + // We need to make sure to populate the identity for all qubits + // that are not part of this term + while (offset < p.target()) { + auto *address = + static_cast(pauliMats_h) + offset++ * ALIGNMENT_BYTES; + std::memcpy(address, pauliMatrixPtr, PAULI_ARRAY_SIZE_BYTES); + } + // Memory address of this Pauli term in the placeholder array. + auto *address = + static_cast(pauliMats_h) + offset++ * ALIGNMENT_BYTES; + auto pauli = p.as_pauli(); + if (pauli == cudaq::pauli::Y) { + allIdOps = false; + pauliMatrixPtr = PauliY_h; + } else if (pauli == cudaq::pauli::X) { + allIdOps = false; + pauliMatrixPtr = PauliX_h; + } else if (pauli == cudaq::pauli::Z) { + allIdOps = false; + pauliMatrixPtr = PauliZ_h; } // Copy the Pauli matrix data to the placeholder array at the appropriate // slot. std::memcpy(address, pauliMatrixPtr, PAULI_ARRAY_SIZE_BYTES); } + // Populate the remaining identities. + const std::complex *pauliMatrixPtr = PauliI_h; + while (offset < numQubits) { + auto *address = + static_cast(pauliMats_h) + offset++ * ALIGNMENT_BYTES; + std::memcpy(address, pauliMatrixPtr, PAULI_ARRAY_SIZE_BYTES); + } if (allIdOps) { - allExpVals.emplace_back(1.0); + allExpVals.emplace_back(prod.evaluate_coefficient()); } else { HANDLE_CUDA_ERROR(cudaMemcpy(pauliMats_d, pauliMats_h, placeHolderArraySize, @@ -834,7 +858,7 @@ std::vector> TensorNetState::computeExpVals( /*cudaStream*/ 0)); expVal += (result / static_cast(numObserveTrajectories)); } - allExpVals.emplace_back(expVal); + allExpVals.emplace_back(expVal * prod.evaluate_coefficient()); } } diff --git a/runtime/nvqir/cutensornet/tensornet_state.h b/runtime/nvqir/cutensornet/tensornet_state.h index 443225c4678..a861d67cf97 100644 --- a/runtime/nvqir/cutensornet/tensornet_state.h +++ b/runtime/nvqir/cutensornet/tensornet_state.h @@ -9,6 +9,7 @@ #pragma once #include "common/EigenDense.h" #include "common/SimulationState.h" +#include "cudaq/operators.h" #include "cutensornet.h" #include "tensornet_utils.h" #include "timing_utils.h" @@ -187,10 +188,10 @@ class TensorNetState { cutensornetTensorSVDAlgo_t algo); /// @brief Compute the expectation value of an observable - /// @param symplecticRepr The symplectic representation of the observable - /// @return + /// @param product_terms the terms of the observable (operator sum) + /// @param numberTrajectories the number of trajectories to use std::vector> - computeExpVals(const std::vector> &symplecticRepr, + computeExpVals(const std::vector &product_terms, const std::optional &numberTrajectories); /// @brief Evaluate the expectation value of a given diff --git a/runtime/nvqir/qpp/QppCircuitSimulator.cpp b/runtime/nvqir/qpp/QppCircuitSimulator.cpp index 84981935684..de136b4acf3 100644 --- a/runtime/nvqir/qpp/QppCircuitSimulator.cpp +++ b/runtime/nvqir/qpp/QppCircuitSimulator.cpp @@ -313,19 +313,11 @@ class QppCircuitSimulator : public nvqir::CircuitSimulatorBase { } cudaq::observe_result observe(const cudaq::spin_op &op) override { - + assert(cudaq::spin_op::canonicalize(op) == op); flushGateQueue(); // The op is on the following target bits. - std::vector targets; - op.for_each_term([&](cudaq::spin_op &term) { - term.for_each_pauli( - [&](cudaq::pauli p, std::size_t idx) { targets.push_back(idx); }); - }); - - std::sort(targets.begin(), targets.end()); - const auto last_iter = std::unique(targets.begin(), targets.end()); - targets.erase(last_iter, targets.end()); + auto targets = op.degrees(); // Get the matrix as an Eigen matrix auto matrix = op.to_matrix(); @@ -340,9 +332,9 @@ class QppCircuitSimulator : public nvqir::CircuitSimulatorBase { ee = qpp::apply(asEigen, state, targets).trace().real(); } - return cudaq::observe_result(ee, op, - cudaq::sample_result(cudaq::ExecutionResult( - {}, op.to_string(false), ee))); + return cudaq::observe_result( + ee, op, + cudaq::sample_result(cudaq::ExecutionResult({}, op.to_string(), ee))); } /// @brief Reset the qubit diff --git a/targettests/Remote-Sim/free_func-cpp17.cpp b/targettests/Remote-Sim/free_func-cpp17.cpp index b798558c46c..25ba951f35e 100644 --- a/targettests/Remote-Sim/free_func-cpp17.cpp +++ b/targettests/Remote-Sim/free_func-cpp17.cpp @@ -37,9 +37,9 @@ int main() { auto counts = cudaq::sample(ghz, 10); counts.dump(); REMOTE_TEST_ASSERT(counts.size() == 2); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); double energy = cudaq::observe(ansatz, h, .59); printf("Energy is %lf\n", energy); diff --git a/targettests/Remote-Sim/free_func.cpp b/targettests/Remote-Sim/free_func.cpp index debf8afdbf5..97abe5cd4a1 100644 --- a/targettests/Remote-Sim/free_func.cpp +++ b/targettests/Remote-Sim/free_func.cpp @@ -37,9 +37,9 @@ int main() { auto counts = cudaq::sample(ghz, 10); counts.dump(); REMOTE_TEST_ASSERT(counts.size() == 2); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); double energy = cudaq::observe(ansatz, h, .59); printf("Energy is %lf\n", energy); diff --git a/targettests/Remote-Sim/kernel_builder.cpp b/targettests/Remote-Sim/kernel_builder.cpp index a7eeabe148d..8e6d7ae16bf 100644 --- a/targettests/Remote-Sim/kernel_builder.cpp +++ b/targettests/Remote-Sim/kernel_builder.cpp @@ -17,9 +17,9 @@ #include int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); { auto [ansatz, theta] = cudaq::make_kernel(); @@ -54,10 +54,12 @@ int main() { } { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1) + 9.625 - 9.625 * z(2) - - 3.913119 * x(1) * x(2) - 3.913119 * y(1) * y(2); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1) + + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); auto [ansatz, theta, beta] = cudaq::make_kernel(); // Allocate some qubits auto q = ansatz.qalloc(3); diff --git a/targettests/Remote-Sim/observe-cpp17.cpp b/targettests/Remote-Sim/observe-cpp17.cpp index 121777ad9b6..c82d390205e 100644 --- a/targettests/Remote-Sim/observe-cpp17.cpp +++ b/targettests/Remote-Sim/observe-cpp17.cpp @@ -31,9 +31,9 @@ struct ansatz { }; int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); { // Simple `cudaq::observe` test double energy = cudaq::observe(ansatz{}, h, .59); diff --git a/targettests/Remote-Sim/observe.cpp b/targettests/Remote-Sim/observe.cpp index 5e4933e7fe7..96a5fad35a7 100644 --- a/targettests/Remote-Sim/observe.cpp +++ b/targettests/Remote-Sim/observe.cpp @@ -31,9 +31,9 @@ struct ansatz { }; int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); { // Simple `cudaq::observe` test double energy = cudaq::observe(ansatz{}, h, .59); diff --git a/targettests/Remote-Sim/observe_async-cpp17.cpp b/targettests/Remote-Sim/observe_async-cpp17.cpp index 99810fd69f2..adb84f7b0ff 100644 --- a/targettests/Remote-Sim/observe_async-cpp17.cpp +++ b/targettests/Remote-Sim/observe_async-cpp17.cpp @@ -27,9 +27,9 @@ struct ansatz { }; int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); // Observe takes the kernel, the spin_op, and the concrete // parameters for the kernel auto energyFuture = cudaq::observe_async(/*qpu_id=*/0, ansatz{}, h, .59); diff --git a/targettests/Remote-Sim/observe_async.cpp b/targettests/Remote-Sim/observe_async.cpp index 10bb35363ac..0c4e8e90fd1 100644 --- a/targettests/Remote-Sim/observe_async.cpp +++ b/targettests/Remote-Sim/observe_async.cpp @@ -27,9 +27,9 @@ struct ansatz { }; int main() { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); // Observe takes the kernel, the spin_op, and the concrete // parameters for the kernel auto energyFuture = cudaq::observe_async(/*qpu_id=*/0, ansatz{}, h, .59); diff --git a/targettests/Remote-Sim/pauli_word.cpp b/targettests/Remote-Sim/pauli_word.cpp index 7e2caaee77e..84bef812892 100644 --- a/targettests/Remote-Sim/pauli_word.cpp +++ b/targettests/Remote-Sim/pauli_word.cpp @@ -38,7 +38,6 @@ struct kernelVector { }; int main() { - using namespace cudaq::spin; const std::vector h2_data{ 3, 1, 1, 3, 0.0454063, 0, 2, 0, 0, 0, 0.17028, 0, 0, 0, 2, 0, -0.220041, -0, 1, 3, 3, 1, 0.0454063, 0, diff --git a/targettests/Remote-Sim/test_trotter.cpp b/targettests/Remote-Sim/test_trotter.cpp index 17e16bda8b1..5f51a1f771a 100644 --- a/targettests/Remote-Sim/test_trotter.cpp +++ b/targettests/Remote-Sim/test_trotter.cpp @@ -59,17 +59,20 @@ struct initState { std::vector term_coefficients(cudaq::spin_op op) { std::vector result{}; - op.for_each_term([&](cudaq::spin_op &term) { - const auto coeff = term.get_coefficient().real(); + for (const auto &term : op) { + const auto coeff = term.evaluate_coefficient().real(); result.push_back(coeff); - }); + } return result; } -std::vector term_words(cudaq::spin_op op) { +std::vector pauli_words(cudaq::spin_op op) { + // The kernel code above is written to assume that each term acts on + // all qubits. We hence expand each pauli word to extend to all qubits. + auto nr_qubits = op.num_qubits(); // relying on qubits being consecutive std::vector result{}; - op.for_each_term( - [&](cudaq::spin_op &term) { result.push_back(term.to_string(false)); }); + for (const auto &term : op) + result.push_back(term.get_pauli_word(nr_qubits)); return result; } @@ -93,24 +96,23 @@ int run_steps(int steps, int spins) { const double Jz = g; const double dt = 0.05; const int n_steps = steps; - const int n_spins = spins; + const std::size_t n_spins = spins; const double omega = 2 * M_PI; const auto heisenbergModelHam = [&](double t) -> cudaq::spin_op { cudaq::spin_op tdOp(n_spins); - for (int i = 0; i < n_spins - 1; ++i) { - tdOp += (Jx * cudaq::spin::x(i) * cudaq::spin::x(i + 1)); - tdOp += (Jy * cudaq::spin::y(i) * cudaq::spin::y(i + 1)); - tdOp += (Jz * cudaq::spin::z(i) * cudaq::spin::z(i + 1)); + for (std::size_t i = 0; i < n_spins - 1; ++i) { + tdOp += (Jx * cudaq::spin_op::x(i) * cudaq::spin_op::x(i + 1)); + tdOp += (Jy * cudaq::spin_op::y(i) * cudaq::spin_op::y(i + 1)); + tdOp += (Jz * cudaq::spin_op::z(i) * cudaq::spin_op::z(i + 1)); } - for (int i = 0; i < n_spins; ++i) - tdOp += (std::cos(omega * t) * cudaq::spin::x(i)); + for (std::size_t i = 0; i < n_spins; ++i) + tdOp += (std::cos(omega * t) * cudaq::spin_op::x(i)); return tdOp; }; // Observe the average magnetization of all spins () cudaq::spin_op average_magnetization(n_spins); - for (int i = 0; i < n_spins; ++i) - average_magnetization += ((1.0 / n_spins) * cudaq::spin::z(i)); - average_magnetization -= 1.0; + for (std::size_t i = 0; i < n_spins; ++i) + average_magnetization += ((1.0 / n_spins) * cudaq::spin_op::z(i)); // Run loop auto state = cudaq::get_state(initState{}, n_spins); @@ -120,7 +122,7 @@ int run_steps(int steps, int spins) { const auto start = std::chrono::high_resolution_clock::now(); auto ham = heisenbergModelHam(i * dt); auto coefficients = term_coefficients(ham); - auto words = term_words(ham); + auto words = pauli_words(ham); auto magnetization_exp_val = cudaq::observe( trotter{}, average_magnetization, &state, coefficients, words, dt); auto result = magnetization_exp_val.expectation(); diff --git a/targettests/Remote-Sim/vqe_h2.cpp b/targettests/Remote-Sim/vqe_h2.cpp index 812d41492c6..642deb4f686 100644 --- a/targettests/Remote-Sim/vqe_h2.cpp +++ b/targettests/Remote-Sim/vqe_h2.cpp @@ -151,7 +151,7 @@ int main() { // needs to sometimes run against a server without the remote VQE // capability, so the handling of RNG seeds for back-and-forth iterations of // observe's behave slightly differently than a fully remote VQE. - REMOTE_TEST_ASSERT(std::abs(opt_val - -1.0906868832471321) < 0.015); + REMOTE_TEST_ASSERT(std::abs(opt_val - -1.0987425678504421) < 0.015); } return 0; } diff --git a/targettests/execution/cudaq_observe-cpp17.cpp b/targettests/execution/cudaq_observe-cpp17.cpp index a58eeec05fb..d3b2ea0601a 100644 --- a/targettests/execution/cudaq_observe-cpp17.cpp +++ b/targettests/execution/cudaq_observe-cpp17.cpp @@ -35,9 +35,9 @@ struct ansatz { int main() { // Build up your spin op algebraically - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); // Make repeatable for shots-based emulation cudaq::set_random_seed(13); diff --git a/targettests/execution/cudaq_observe.cpp b/targettests/execution/cudaq_observe.cpp index edb11895072..b59a3c35802 100644 --- a/targettests/execution/cudaq_observe.cpp +++ b/targettests/execution/cudaq_observe.cpp @@ -36,9 +36,9 @@ struct ansatz { int main() { // Build up your spin op algebraically - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); // Make repeatable for shots-based emulation cudaq::set_random_seed(13); diff --git a/targettests/execution/cudaq_observe_qvector.cpp b/targettests/execution/cudaq_observe_qvector.cpp index fd542b7cb0c..61bcd594a27 100644 --- a/targettests/execution/cudaq_observe_qvector.cpp +++ b/targettests/execution/cudaq_observe_qvector.cpp @@ -33,8 +33,7 @@ struct ansatz { int main() { // Build up your spin op algebraically - using namespace cudaq::spin; - cudaq::spin_op h = 5.0 - 1.0 * x(0); + cudaq::spin_op h = 5.0 - 1.0 * cudaq::spin_op::x(0); // Make repeatable for shots-based emulation cudaq::set_random_seed(13); diff --git a/targettests/execution/cudaq_observe_term.cpp b/targettests/execution/cudaq_observe_term.cpp index 66cb420d3fd..89bd0dfb54c 100644 --- a/targettests/execution/cudaq_observe_term.cpp +++ b/targettests/execution/cudaq_observe_term.cpp @@ -52,9 +52,8 @@ struct ansatz_z { int main() { - using namespace cudaq::spin; { - cudaq::spin_op h = x(0); + auto h = cudaq::spin_op::x(0); cudaq::set_random_seed(13); // Observe the kernel and make sure we get the expected energy @@ -67,7 +66,7 @@ int main() { } { - cudaq::spin_op h = y(3); + auto h = cudaq::spin_op::y(3); cudaq::set_random_seed(13); // Observe the kernel and make sure we get the expected energy @@ -80,7 +79,7 @@ int main() { } { - cudaq::spin_op h = z(0) * z(1); + auto h = cudaq::spin_op::z(0) * cudaq::spin_op::z(1); cudaq::set_random_seed(13); // Observe the kernel and make sure we get the expected energy. diff --git a/targettests/execution/obserse_one_term_spin_op.cpp b/targettests/execution/obserse_one_term_spin_op.cpp index 3babe4f5d5a..5efaa5faa77 100644 --- a/targettests/execution/obserse_one_term_spin_op.cpp +++ b/targettests/execution/obserse_one_term_spin_op.cpp @@ -25,8 +25,7 @@ struct ansatz { int main() { // Build up your spin op algebraically - using namespace cudaq::spin; - cudaq::spin_op h = i(0) * z(1); + auto h = cudaq::spin_op::i(0) * cudaq::spin_op::z(1); // Make repeatable for shots-based emulation cudaq::set_random_seed(13); diff --git a/targettests/execution/test_trotter.cpp b/targettests/execution/test_trotter.cpp index 341594ecefb..227bb2d633d 100644 --- a/targettests/execution/test_trotter.cpp +++ b/targettests/execution/test_trotter.cpp @@ -56,7 +56,7 @@ int STEPS = 4; // set to around 100 for `nvidia` target // ``` // nvq++ --enable-mlir -v trotter_kernel_mode.cpp -o trotter.x --target nvidia && ./trotter.x // ``` -// clang-format off +// clang-format on // Alternating up/down spins struct initState { @@ -69,17 +69,18 @@ struct initState { std::vector term_coefficients(cudaq::spin_op op) { std::vector result{}; - op.for_each_term([&](cudaq::spin_op &term) { - const auto coeff = term.get_coefficient().real(); + for (const auto &term : op) { + const auto coeff = term.evaluate_coefficient().real(); result.push_back(coeff); - }); + } return result; } -std::vector term_words(cudaq::spin_op op) { +std::vector term_words(cudaq::spin_op op, + std::size_t num_qubits) { std::vector result{}; - op.for_each_term( - [&](cudaq::spin_op &term) { result.push_back(term.to_string(false)); }); + for (const auto &term : op) + result.push_back(term.get_pauli_word(num_qubits)); return result; } @@ -106,7 +107,7 @@ int run_steps(int steps, int spins) { const int n_spins = spins; const double omega = 2 * M_PI; const auto heisenbergModelHam = [&](double t) -> cudaq::spin_op { - cudaq::spin_op tdOp(n_spins); + cudaq::spin_op tdOp = cudaq::spin_op::identity(); for (int i = 0; i < n_spins - 1; ++i) { tdOp += (Jx * cudaq::spin::x(i) * cudaq::spin::x(i + 1)); tdOp += (Jy * cudaq::spin::y(i) * cudaq::spin::y(i + 1)); @@ -117,10 +118,9 @@ int run_steps(int steps, int spins) { return tdOp; }; // Observe the average magnetization of all spins () - cudaq::spin_op average_magnetization(n_spins); + cudaq::spin_op average_magnetization; for (int i = 0; i < n_spins; ++i) average_magnetization += ((1.0 / n_spins) * cudaq::spin::z(i)); - average_magnetization -= 1.0; // Run loop auto state = cudaq::get_state(initState{}, n_spins); @@ -130,7 +130,7 @@ int run_steps(int steps, int spins) { const auto start = std::chrono::high_resolution_clock::now(); auto ham = heisenbergModelHam(i * dt); auto coefficients = term_coefficients(ham); - auto words = term_words(ham); + auto words = term_words(ham, n_spins); auto magnetization_exp_val = cudaq::observe( trotter{}, average_magnetization, &state, coefficients, words, dt); auto result = magnetization_exp_val.expectation(); diff --git a/tools/nvqpp/nvq++.in b/tools/nvqpp/nvq++.in index edce8967c3f..64a948c3094 100644 --- a/tools/nvqpp/nvq++.in +++ b/tools/nvqpp/nvq++.in @@ -303,7 +303,7 @@ LIBRARY_MODE_EXECUTION_MANAGER="default" PLATFORM_LIBRARY="default" LLVM_QUANTUM_TARGET="qir" LINKDIRS="-L${install_dir}/lib -L${install_dir}/lib/plugins @CUDAQ_CXX_NVQPP_LINK_STR@" -LINKLIBS="-lcudaq -lcudaq-common -lcudaq-ensmallen -lcudaq-nlopt -lcudaq-spin -lcudaq-operator" +LINKLIBS="-lcudaq -lcudaq-common -lcudaq-ensmallen -lcudaq-nlopt -lcudaq-operator" # Add any plugin libraries to the link stage CUDAQ_PLUGIN_DIR=${install_dir}/lib/plugins diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 510597e58fa..2855e3b8df7 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -257,23 +257,23 @@ add_executable(test_spin main.cpp ${CUDAQ_SPIN_TEST_SOURCES}) target_link_libraries(test_spin PRIVATE cudaq - cudaq-spin + cudaq-operator gtest_main) gtest_discover_tests(test_spin) # Create an executable for operators UnitTests set(CUDAQ_OPERATOR_TEST_SOURCES - dynamics/utils.cpp - dynamics/scalar_operator.cpp - dynamics/matrix_operator.cpp - dynamics/spin_operator.cpp - dynamics/boson_operator.cpp - dynamics/fermion_operator.cpp - dynamics/operator_conversions.cpp - dynamics/product_operator.cpp - dynamics/operator_sum.cpp - dynamics/rydberg_hamiltonian.cpp - dynamics/test_Helpers.cpp + operators/utils.cpp + operators/scalar_op.cpp + operators/matrix_op.cpp + operators/spin_op.cpp + operators/boson_op.cpp + operators/fermion_op.cpp + operators/conversions.cpp + operators/product_op.cpp + operators/sum_op.cpp + operators/rydberg_hamiltonian.cpp + operators/manipulation.cpp ) add_executable(test_operators main.cpp ${CUDAQ_OPERATOR_TEST_SOURCES}) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) @@ -281,7 +281,6 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) endif() target_link_libraries(test_operators PRIVATE - cudaq-spin cudaq-operator cudaq gtest_main) @@ -315,7 +314,6 @@ if (CUDA_FOUND) endif() target_link_libraries(test_dynamics PRIVATE - cudaq-spin cudaq-operator cudaq nvqir-dynamics @@ -336,7 +334,6 @@ if (CUDA_FOUND) target_compile_definitions(test_evolve_async PRIVATE -DCUDAQ_ANALOG_TARGET) target_link_libraries(test_evolve_async PRIVATE - cudaq-spin cudaq-operator cudaq cudaq-platform-mqpu @@ -459,7 +456,7 @@ if (CUDAQ_ENABLE_PYTHON) cudaq cudaq-platform-default nvqir nvqir-qpp - cudaq-spin + cudaq-operator cudaq-chemistry cudaq-pyscf gtest_main) diff --git a/unittests/Optimizer/CMakeLists.txt b/unittests/Optimizer/CMakeLists.txt index 52903288664..96f8fb0b5d4 100644 --- a/unittests/Optimizer/CMakeLists.txt +++ b/unittests/Optimizer/CMakeLists.txt @@ -22,7 +22,7 @@ add_executable(test_quake_synth QuakeSynthTester.cpp) target_link_libraries(test_quake_synth PRIVATE cudaq - cudaq-spin + cudaq-operator cudaq-mlir-runtime cudaq-builder cudaq-common diff --git a/unittests/Optimizer/QuakeSynthTester.cpp b/unittests/Optimizer/QuakeSynthTester.cpp index 75fdd1a38c0..8a157a5bd50 100644 --- a/unittests/Optimizer/QuakeSynthTester.cpp +++ b/unittests/Optimizer/QuakeSynthTester.cpp @@ -178,11 +178,13 @@ TEST(QuakeSynthTests, checkDoubleInput) { // Set the proper name for the kernel auto properName = cudaq::runtime::cudaqGenPrefixName + kernel.name(); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); double energy = cudaq::observe(kernel, h3, .3591, .2569); EXPECT_NEAR(energy, -2.045375, 1e-3); @@ -235,11 +237,13 @@ TEST(QuakeSynthTests, checkVectorOfDouble) { // Set the proper name for the kernel auto properName = cudaq::runtime::cudaqGenPrefixName + kernel.name(); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); double energy = cudaq::observe(kernel, h3, std::vector{.3591, .2569}); EXPECT_NEAR(energy, -2.045375, 1e-3); @@ -345,9 +349,10 @@ TEST(QuakeSynthTests, checkCallable) { auto properName = cudaq::runtime::cudaqGenPrefixName + kernel.name(); std::vector argsValue = {0.0}; - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); double energy = cudaq::observe(kernel, h, argsValue); std::cout << "Energy = " << energy << "\n"; // Map the kernel_builder to_quake output to MLIR diff --git a/unittests/backends/anyon/AnyonTester.cpp b/unittests/backends/anyon/AnyonTester.cpp index 4e5f999ac9e..8556cd8bab1 100644 --- a/unittests/backends/anyon/AnyonTester.cpp +++ b/unittests/backends/anyon/AnyonTester.cpp @@ -157,9 +157,10 @@ CUDAQ_TEST(AnyonTester, checkObserveSync) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto result = cudaq::observe(10000, kernel, h, .59); result.dump(); @@ -184,9 +185,10 @@ CUDAQ_TEST(AnyonTester, checkObserveSyncEmulate) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto result = cudaq::observe(100000, kernel, h, .59); result.dump(); @@ -209,9 +211,10 @@ CUDAQ_TEST(AnyonTester, checkObserveAsync) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(kernel, h, .59); auto result = future.get(); @@ -238,9 +241,10 @@ CUDAQ_TEST(AnyonTester, checkObserveAsyncEmulate) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(100000, 0, kernel, h, .59); auto result = future.get(); @@ -266,9 +270,10 @@ CUDAQ_TEST(AnyonTester, checkObserveAsyncLoadFromFile) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(kernel, h, .59); { diff --git a/unittests/backends/anyon/CMakeLists.txt b/unittests/backends/anyon/CMakeLists.txt index 4a5dba6bbd2..7487bc07d30 100644 --- a/unittests/backends/anyon/CMakeLists.txt +++ b/unittests/backends/anyon/CMakeLists.txt @@ -19,7 +19,7 @@ target_link_libraries(test_anyon cudaq-builder cudaq-mlir-runtime cudaq-rest-qpu - cudaq-spin + cudaq-operator nvqir nvqir-qpp cudaq-platform-default gtest_main) diff --git a/unittests/backends/braket/BraketTester.cpp b/unittests/backends/braket/BraketTester.cpp index ebba0c9b1dd..04ac297f43a 100644 --- a/unittests/backends/braket/BraketTester.cpp +++ b/unittests/backends/braket/BraketTester.cpp @@ -162,9 +162,10 @@ CUDAQ_TEST(BraketTester, checkObserveSync) { kernel.x(qubit[1], qubit[0]); kernel.mz(qubit); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto result = cudaq::observe(10000, kernel, h, .59); result.dump(); @@ -189,9 +190,10 @@ CUDAQ_TEST(BraketTester, checkObserveSyncEmulate) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto result = cudaq::observe(100000, kernel, h, .59); result.dump(); @@ -217,9 +219,10 @@ CUDAQ_TEST(BraketTester, checkObserveAsync) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(kernel, h, .59); auto result = future.get(); diff --git a/unittests/backends/braket/CMakeLists.txt b/unittests/backends/braket/CMakeLists.txt index c6e067b6fbe..d587db5bb48 100644 --- a/unittests/backends/braket/CMakeLists.txt +++ b/unittests/backends/braket/CMakeLists.txt @@ -19,7 +19,7 @@ target_link_libraries(test_braket cudaq-builder cudaq-mlir-runtime cudaq-rest-qpu - cudaq-spin + cudaq-operator nvqir nvqir-qpp cudaq-platform-default gtest_main) diff --git a/unittests/backends/infleqtion/CMakeLists.txt b/unittests/backends/infleqtion/CMakeLists.txt index 2c0d991eb0d..1ca9983a32b 100644 --- a/unittests/backends/infleqtion/CMakeLists.txt +++ b/unittests/backends/infleqtion/CMakeLists.txt @@ -19,7 +19,7 @@ target_link_libraries(test_infleqtion cudaq-builder cudaq-mlir-runtime cudaq-rest-qpu - cudaq-spin + cudaq-operator cudaq-platform-default gtest_main) diff --git a/unittests/backends/ionq/CMakeLists.txt b/unittests/backends/ionq/CMakeLists.txt index 72dd71586a0..54c61b1611f 100644 --- a/unittests/backends/ionq/CMakeLists.txt +++ b/unittests/backends/ionq/CMakeLists.txt @@ -19,7 +19,7 @@ target_link_libraries(test_ionq cudaq-builder cudaq-mlir-runtime cudaq-rest-qpu - cudaq-spin + cudaq-operator cudaq-platform-default gtest_main) diff --git a/unittests/backends/ionq/IonQTester.cpp b/unittests/backends/ionq/IonQTester.cpp index 0fac02111cb..51486704d1b 100644 --- a/unittests/backends/ionq/IonQTester.cpp +++ b/unittests/backends/ionq/IonQTester.cpp @@ -102,9 +102,10 @@ CUDAQ_TEST(IonQTester, checkObserveSync) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto result = cudaq::observe(kernel, h, .59); result.dump(); @@ -125,9 +126,10 @@ CUDAQ_TEST(IonQTester, checkObserveAsync) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(kernel, h, .59); auto result = future.get(); @@ -150,9 +152,10 @@ CUDAQ_TEST(IonQTester, checkObserveAsyncLoadFromFile) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(kernel, h, .59); { diff --git a/unittests/backends/iqm/CMakeLists.txt b/unittests/backends/iqm/CMakeLists.txt index dddffe083ad..10b54fdc4c2 100644 --- a/unittests/backends/iqm/CMakeLists.txt +++ b/unittests/backends/iqm/CMakeLists.txt @@ -19,7 +19,7 @@ target_link_libraries(test_iqm cudaq-builder cudaq-mlir-runtime cudaq-rest-qpu - cudaq-spin + cudaq-operator cudaq-platform-default gmock_main gtest_main) diff --git a/unittests/backends/oqc/CMakeLists.txt b/unittests/backends/oqc/CMakeLists.txt index 05db02e767f..963e4aee848 100644 --- a/unittests/backends/oqc/CMakeLists.txt +++ b/unittests/backends/oqc/CMakeLists.txt @@ -17,7 +17,7 @@ target_link_libraries(test_oqc cudaq-builder cudaq-mlir-runtime cudaq-rest-qpu - cudaq-spin + cudaq-operator cudaq-platform-default gtest_main) diff --git a/unittests/backends/oqc/OQCTester.cpp b/unittests/backends/oqc/OQCTester.cpp index fcdc206e752..868b937f45d 100644 --- a/unittests/backends/oqc/OQCTester.cpp +++ b/unittests/backends/oqc/OQCTester.cpp @@ -104,9 +104,10 @@ CUDAQ_TEST(OQCTester, checkObserveSync) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto result = cudaq::observe(kernel, h, .59); result.dump(); @@ -127,9 +128,10 @@ CUDAQ_TEST(OQCTester, checkObserveAsync) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(kernel, h, .59); auto result = future.get(); @@ -152,9 +154,10 @@ CUDAQ_TEST(OQCTester, checkObserveAsyncLoadFromFile) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(kernel, h, .59); { diff --git a/unittests/backends/qpp_observe/CMakeLists.txt b/unittests/backends/qpp_observe/CMakeLists.txt index cfe65b542c1..fcdc69206e4 100644 --- a/unittests/backends/qpp_observe/CMakeLists.txt +++ b/unittests/backends/qpp_observe/CMakeLists.txt @@ -8,7 +8,7 @@ add_library(nvqir-qpp-observe-test SHARED QPPObserveBackend.cpp) set (QPP_TEST_DEPENDENCIES "") -list(APPEND QPP_TEST_DEPENDENCIES fmt::fmt-header-only cudaq-common cudaq-spin) +list(APPEND QPP_TEST_DEPENDENCIES fmt::fmt-header-only cudaq-common cudaq-operator) add_openmp_configurations(nvqir-qpp-observe-test QPP_TEST_DEPENDENCIES) target_include_directories(nvqir-qpp-observe-test @@ -34,7 +34,7 @@ target_link_libraries(test_observe_backend cudaq nvqir nvqir-qpp-observe-test - cudaq-spin + cudaq-operator cudaq-platform-default cudaq-em-default gtest_main) diff --git a/unittests/backends/qpp_observe/QPPObserveBackend.cpp b/unittests/backends/qpp_observe/QPPObserveBackend.cpp index 4f9f854f811..5e998ad3ac3 100644 --- a/unittests/backends/qpp_observe/QPPObserveBackend.cpp +++ b/unittests/backends/qpp_observe/QPPObserveBackend.cpp @@ -9,7 +9,7 @@ #define __NVQIR_QPP_TOGGLE_CREATE #include "qpp/QppCircuitSimulator.cpp" #undef __NVQIR_QPP_TOGGLE_CREATE -#include "cudaq/spin_op.h" +#include "cudaq/operators.h" namespace cudaq { @@ -20,13 +20,13 @@ class QppObserveTester : public nvqir::QppCircuitSimulator { NVQIR_SIMULATOR_CLONE_IMPL(QppObserveTester) bool canHandleObserve() override { return true; } cudaq::observe_result observe(const cudaq::spin_op &op) override { + assert(cudaq::spin_op::canonicalize(op) == op); flushGateQueue(); ::qpp::cmat X = ::qpp::Gates::get_instance().X; ::qpp::cmat Y = ::qpp::Gates::get_instance().Y; ::qpp::cmat Z = ::qpp::Gates::get_instance().Z; - auto nQ = op.num_qubits(); double sum = 0.0; // Want to loop over all terms in op and @@ -35,25 +35,27 @@ class QppObserveTester : public nvqir::QppCircuitSimulator { for (const auto &term : op) { if (!term.is_identity()) { ::qpp::ket cached = state; - auto [bsf, coeffs] = term.get_raw_data(); - for (std::size_t i = 0; i < nQ; i++) { - if (bsf[0][i] && bsf[0][i + nQ]) + auto bsf = term.get_binary_symplectic_form(); + auto max_target = bsf.size() / 2; + for (std::size_t i = 0; i < max_target; i++) { + if (bsf[i] && bsf[i + max_target]) cached = ::qpp::apply(cached, Y, {convertQubitIndex(i)}); - else if (bsf[0][i]) + else if (bsf[i]) cached = ::qpp::apply(cached, X, {convertQubitIndex(i)}); - else if (bsf[0][i + nQ]) + else if (bsf[i + max_target]) cached = ::qpp::apply(cached, Z, {convertQubitIndex(i)}); } - sum += coeffs[0].real() * state.transpose().dot(cached).real(); + sum += term.evaluate_coefficient().real() * + state.transpose().dot(cached).real(); } else { - sum += term.get_coefficient().real(); + sum += term.evaluate_coefficient().real(); } } - return cudaq::observe_result(sum, op, - cudaq::sample_result(cudaq::ExecutionResult( - {}, op.to_string(false), sum))); + return cudaq::observe_result( + sum, op, + cudaq::sample_result(cudaq::ExecutionResult({}, op.to_string(), sum))); } std::string name() const override { return "qpp-test"; } diff --git a/unittests/backends/qpp_observe/QppObserveTester.cpp b/unittests/backends/qpp_observe/QppObserveTester.cpp index 7f17b2e3801..3cfd5e46d71 100644 --- a/unittests/backends/qpp_observe/QppObserveTester.cpp +++ b/unittests/backends/qpp_observe/QppObserveTester.cpp @@ -21,14 +21,14 @@ CUDAQ_TEST(QPPBackendTester, checkBackendObserve) { qpp.ry(.59, q1); qpp.x({q1}, q0); - using namespace cudaq::spin; - - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto expVal = qpp.observe(h); EXPECT_NEAR(expVal.expectation(), -1.74, 1e-2); - EXPECT_NEAR(expVal.raw_data().expectation(h.to_string(false)), -1.74, 1e-2); + EXPECT_NEAR(expVal.raw_data().expectation(h.to_string()), -1.74, 1e-2); struct ansatzTest { auto operator()(double theta) __qpu__ { diff --git a/unittests/backends/quantinuum/CMakeLists.txt b/unittests/backends/quantinuum/CMakeLists.txt index 185fc095d51..2abfcd6738e 100644 --- a/unittests/backends/quantinuum/CMakeLists.txt +++ b/unittests/backends/quantinuum/CMakeLists.txt @@ -19,7 +19,7 @@ target_link_libraries(test_quantinuum cudaq-builder cudaq-mlir-runtime cudaq-rest-qpu - cudaq-spin + cudaq-operator nvqir nvqir-qpp cudaq-platform-default gtest_main) diff --git a/unittests/backends/quantinuum/QuantinuumTester.cpp b/unittests/backends/quantinuum/QuantinuumTester.cpp index 22ba7d1ee87..97d5ca1f856 100644 --- a/unittests/backends/quantinuum/QuantinuumTester.cpp +++ b/unittests/backends/quantinuum/QuantinuumTester.cpp @@ -155,9 +155,10 @@ CUDAQ_TEST(QuantinuumTester, checkObserveSync) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto result = cudaq::observe(10000, kernel, h, .59); result.dump(); @@ -182,9 +183,10 @@ CUDAQ_TEST(QuantinuumTester, checkObserveSyncEmulate) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto result = cudaq::observe(100000, kernel, h, .59); result.dump(); @@ -207,9 +209,10 @@ CUDAQ_TEST(QuantinuumTester, checkObserveAsync) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(kernel, h, .59); auto result = future.get(); @@ -236,9 +239,10 @@ CUDAQ_TEST(QuantinuumTester, checkObserveAsyncEmulate) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(100000, 0, kernel, h, .59); auto result = future.get(); @@ -264,9 +268,10 @@ CUDAQ_TEST(QuantinuumTester, checkObserveAsyncLoadFromFile) { kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto future = cudaq::observe_async(kernel, h, .59); { diff --git a/unittests/dynamics/test_CuDensityMatExpectation.cpp b/unittests/dynamics/test_CuDensityMatExpectation.cpp index 68059e37dd8..63585e368fc 100644 --- a/unittests/dynamics/test_CuDensityMatExpectation.cpp +++ b/unittests/dynamics/test_CuDensityMatExpectation.cpp @@ -37,7 +37,7 @@ class CuDensityMatExpectationTest : public ::testing::Test { TEST_F(CuDensityMatExpectationTest, checkCompute) { const std::vector dims = {10}; // Check number operator on boson Fock space - auto op = cudaq::matrix_handler::number(0); + auto op = cudaq::matrix_op::number(0); auto cudmOp = cudaq::dynamics::Context::getCurrentContext() ->getOpConverter() .convertToCudensitymatOperator({}, op, dims); @@ -59,7 +59,7 @@ TEST_F(CuDensityMatExpectationTest, checkCompute) { TEST_F(CuDensityMatExpectationTest, checkCompositeSystem) { const std::vector dims = {2, 10}; // Check number operator on boson Fock space - auto op = cudaq::matrix_handler::number(1); + auto op = cudaq::matrix_op::number(1); auto cudmOp = cudaq::dynamics::Context::getCurrentContext() ->getOpConverter() .convertToCudensitymatOperator({}, op, dims); diff --git a/unittests/dynamics/test_CuDensityMatTimeStepper.cpp b/unittests/dynamics/test_CuDensityMatTimeStepper.cpp index 1666f671376..a4f33de587f 100644 --- a/unittests/dynamics/test_CuDensityMatTimeStepper.cpp +++ b/unittests/dynamics/test_CuDensityMatTimeStepper.cpp @@ -229,7 +229,7 @@ TEST_F(CuDensityMatTimeStepperTest, CheckTensorCallback) { {paramName, paramValue}}; auto tensorFunction = - [paramName](const std::vector &dimensions, + [paramName](const std::vector &dimensions, const std::unordered_map> ¶meters) -> complex_matrix { if (dimensions.empty()) diff --git a/unittests/dynamics/test_EvolveApi.cpp b/unittests/dynamics/test_EvolveApi.cpp index aa52bc2fe6c..2b852db800f 100644 --- a/unittests/dynamics/test_EvolveApi.cpp +++ b/unittests/dynamics/test_EvolveApi.cpp @@ -7,7 +7,7 @@ // ******************************************************************************/ #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include #include #include @@ -19,14 +19,14 @@ TEST(EvolveAPITester, checkSimple) { const cudaq::dimension_map dims = {{0, 2}}; - auto ham = 2.0 * M_PI * 0.1 * cudaq::sum_op::x(0); + auto ham = 2.0 * M_PI * 0.1 * cudaq::spin_op::x(0); constexpr int numSteps = 10; cudaq::schedule schedule(cudaq::linspace(0.0, 1.0, numSteps), {"t"}); auto initialState = cudaq::state::from_data(std::vector>{1.0, 0.0}); cudaq::integrators::runge_kutta integrator(1, 0.001); auto result = cudaq::evolve(ham, dims, schedule, initialState, integrator, {}, - {cudaq::sum_op::z(0)}, true); + {cudaq::spin_op::z(0)}, true); EXPECT_TRUE(result.expectation_values.has_value()); EXPECT_EQ(result.expectation_values.value().size(), numSteps); diff --git a/unittests/dynamics/test_EvolveSingle.cpp b/unittests/dynamics/test_EvolveSingle.cpp index 409588fc929..2b534529df6 100644 --- a/unittests/dynamics/test_EvolveSingle.cpp +++ b/unittests/dynamics/test_EvolveSingle.cpp @@ -9,7 +9,7 @@ #include "CuDensityMatState.h" #include "common/EigenDense.h" #include "cudaq/algorithms/evolve_internal.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include #include #include @@ -18,15 +18,14 @@ TEST(EvolveTester, checkSimple) { const cudaq::dimension_map dims = {{0, 2}}; cudaq::product_op ham1 = - (2.0 * M_PI * 0.1 * cudaq::sum_op::x(0)); + (2.0 * M_PI * 0.1 * cudaq::spin_op::x(0)); cudaq::sum_op ham(ham1); constexpr int numSteps = 10; std::vector steps = cudaq::linspace(0.0, 1.0, numSteps); cudaq::schedule schedule(steps, {"t"}); - cudaq::product_op pauliZ_t = - cudaq::sum_op::z(0); + cudaq::product_op pauliZ_t = cudaq::spin_op::z(0); cudaq::sum_op pauliZ(pauliZ_t); auto initialState = cudaq::state::from_data(std::vector>{1.0, 0.0}); @@ -52,15 +51,14 @@ TEST(EvolveTester, checkSimple) { TEST(EvolveTester, checkSimpleRK4) { const cudaq::dimension_map dims = {{0, 2}}; cudaq::product_op ham1 = - (2.0 * M_PI * 0.1 * cudaq::sum_op::x(0)); + (2.0 * M_PI * 0.1 * cudaq::spin_op::x(0)); cudaq::sum_op ham(ham1); constexpr int numSteps = 10; std::vector steps = cudaq::linspace(0.0, 1.0, numSteps); cudaq::schedule schedule(steps, {"t"}); - cudaq::product_op pauliZ_t = - cudaq::sum_op::z(0); + cudaq::product_op pauliZ_t = cudaq::spin_op::z(0); cudaq::sum_op pauliZ(pauliZ_t); auto initialState = cudaq::state::from_data(std::vector>{1.0, 0.0}); @@ -86,15 +84,14 @@ TEST(EvolveTester, checkSimpleRK4) { TEST(EvolveTester, checkDensityMatrixSimple) { const cudaq::dimension_map dims = {{0, 2}}; cudaq::product_op ham1 = - (2.0 * M_PI * 0.1 * cudaq::sum_op::x(0)); + (2.0 * M_PI * 0.1 * cudaq::spin_op::x(0)); cudaq::sum_op ham(ham1); constexpr int numSteps = 10; std::vector steps = cudaq::linspace(0.0, 1.0, numSteps); cudaq::schedule schedule(steps, {"t"}); - cudaq::product_op pauliZ_t = - cudaq::sum_op::z(0); + cudaq::product_op pauliZ_t = cudaq::spin_op::z(0); cudaq::sum_op pauliZ(pauliZ_t); auto initialState = cudaq::state::from_data( std::vector>{1.0, 0.0, 0.0, 0.0}); diff --git a/unittests/dynamics/test_RungeKuttaIntegrator.cpp b/unittests/dynamics/test_RungeKuttaIntegrator.cpp index 5f0fcc6ba51..d432fec30f3 100644 --- a/unittests/dynamics/test_RungeKuttaIntegrator.cpp +++ b/unittests/dynamics/test_RungeKuttaIntegrator.cpp @@ -8,7 +8,7 @@ #include "CuDensityMatState.h" #include "CuDensityMatTimeStepper.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include "test_Mocks.h" #include #include @@ -59,7 +59,7 @@ TEST_F(RungeKuttaIntegratorTest, CheckEvolve) { const std::vector> initialStateVec = {{1.0, 0.0}, {0.0, 0.0}}; const std::vector dims = {2}; - auto spin_op_x = cudaq::sum_op::x(0); + auto spin_op_x = cudaq::spin_op::x(0); cudaq::product_op ham1 = 2.0 * M_PI * 0.1 * spin_op_x; cudaq::sum_op ham(ham1); SystemDynamics system(dims, ham); diff --git a/unittests/integration/async_tester.cpp b/unittests/integration/async_tester.cpp index 198613a7715..56e6c8e294d 100644 --- a/unittests/integration/async_tester.cpp +++ b/unittests/integration/async_tester.cpp @@ -14,11 +14,10 @@ #ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(AsyncTester, checkObserveAsync) { - using namespace cudaq::spin; - - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - h.dump(); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto params = cudaq::linspace(-M_PI, M_PI, 20); diff --git a/unittests/integration/bug67_vqe_then_sample.cpp b/unittests/integration/bug67_vqe_then_sample.cpp index b40f5c0b127..78c942dda42 100644 --- a/unittests/integration/bug67_vqe_then_sample.cpp +++ b/unittests/integration/bug67_vqe_then_sample.cpp @@ -21,7 +21,6 @@ CUDAQ_TEST(VqeThenSample, checkBug67) { const int n_qubits; const int n_layers; void operator()(std::vector theta) __qpu__ { - using namespace cudaq::spin; cudaq::qvector q(n_qubits); // Prepare the initial state by superposition @@ -46,9 +45,10 @@ CUDAQ_TEST(VqeThenSample, checkBug67) { } }; - using namespace cudaq::spin; - cudaq::spin_op Hp = 0.5 * z(0) * z(1) + 0.5 * z(1) * z(2) + - 0.5 * z(0) * z(3) + 0.5 * z(2) * z(3); + cudaq::spin_op Hp = 0.5 * cudaq::spin_op::z(0) * cudaq::spin_op::z(1) + + 0.5 * cudaq::spin_op::z(1) * cudaq::spin_op::z(2) + + 0.5 * cudaq::spin_op::z(0) * cudaq::spin_op::z(3) + + 0.5 * cudaq::spin_op::z(2) * cudaq::spin_op::z(3); int n_qubits = 4; int n_layers = 2; diff --git a/unittests/integration/bug77_vqe_with_shots.cpp b/unittests/integration/bug77_vqe_with_shots.cpp index c6bc262a3ac..9a89500b8e1 100644 --- a/unittests/integration/bug77_vqe_with_shots.cpp +++ b/unittests/integration/bug77_vqe_with_shots.cpp @@ -19,7 +19,7 @@ CUDAQ_TEST(VqeWithShots, checkBug77) { const int n_qubits; const int n_layers; void operator()(std::vector theta) __qpu__ { - using namespace cudaq::spin; + cudaq::qvector q(n_qubits); // Prepare the initial state by superposition @@ -48,11 +48,11 @@ CUDAQ_TEST(VqeWithShots, checkBug77) { int n_layers = 2; int n_params = 2 * n_layers; - using namespace cudaq::spin; - // Problem Hamiltonian - cudaq::spin_op Hp = 0.5 * z(0) * z(1) + 0.5 * z(1) * z(2) + - 0.5 * z(0) * z(3) + 0.5 * z(2) * z(3); + cudaq::spin_op Hp = 0.5 * cudaq::spin_op::z(0) * cudaq::spin_op::z(1) + + 0.5 * cudaq::spin_op::z(1) * cudaq::spin_op::z(2) + + 0.5 * cudaq::spin_op::z(0) * cudaq::spin_op::z(3) + + 0.5 * cudaq::spin_op::z(2) * cudaq::spin_op::z(3); // Optimizer cudaq::optimizers::cobyla optimizer; // gradient-free diff --git a/unittests/integration/builder_tester.cpp b/unittests/integration/builder_tester.cpp index e63b3d58726..23747b6779a 100644 --- a/unittests/integration/builder_tester.cpp +++ b/unittests/integration/builder_tester.cpp @@ -16,9 +16,10 @@ #ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkSimple) { { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto [ansatz, theta] = cudaq::make_kernel(); @@ -41,11 +42,13 @@ CUDAQ_TEST(BuilderTester, checkSimple) { { // Build up a 2 parameter circuit using a vector parameter // Run the cudaq optimizer to find optimal value. - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); auto [ansatz, theta, phi] = cudaq::make_kernel(); @@ -77,11 +80,13 @@ CUDAQ_TEST(BuilderTester, checkSimple) { { // Build up a 2 parameter circuit using a vector parameter // Run the cudaq optimizer to find optimal value. - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); auto [ansatz, thetas] = cudaq::make_kernel>(); @@ -1075,7 +1080,22 @@ CUDAQ_TEST(BuilderTester, checkExpPauli) { 0, 2, 0, 2, 0.1202, 0, 2, 0, 0, 2, 0.165607, 0, 0, 2, 2, 0, 0.165607, 0, 0, 0, 2, 2, 0.174073, 0, 1, 1, 3, 3, -0.0454063, -0, 15}; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif cudaq::spin_op h(h2_data, 4); +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + { auto [kernel, theta] = cudaq::make_kernel(); auto qubits = kernel.qalloc(4); diff --git a/unittests/integration/deuteron_variational_tester.cpp b/unittests/integration/deuteron_variational_tester.cpp index 9db792a8dd7..517d272f4ae 100644 --- a/unittests/integration/deuteron_variational_tester.cpp +++ b/unittests/integration/deuteron_variational_tester.cpp @@ -22,43 +22,51 @@ struct ansatz2 { CUDAQ_TEST(D2VariationalTester, checkSimple) { - using namespace cudaq::spin; - cudaq::set_random_seed(13); - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - h.dump(); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); double energy = cudaq::observe(ansatz2{}, h, .59); printf("Energy is %.16lf\n", energy); EXPECT_NEAR(energy, -1.7487, 1e-3); - std::vector asList; - h.for_each_term([&](cudaq::spin_op &term) { + std::vector asList; + for (auto &&term : h) { if (!term.is_identity()) asList.push_back(term); - }); + } + +// tensornet backends do not store detail results for small numbers of qubits +#if !defined(CUDAQ_BACKEND_TENSORNET) && !defined(CUDAQ_BACKEND_TENSORNET_MPS) - // Test that we can osberve a list. + // Test that we can observe a list. auto results = cudaq::observe(ansatz2{}, asList, .59); double test = 5.907; for (auto &r : results) { - test += r.expectation() * r.get_spin().get_coefficient().real(); + auto spin = r.get_spin(); + EXPECT_EQ(spin.num_terms(), 1); + test += r.expectation(); // expectation should include the coefficient } printf("TEST: %.16lf\n", test); + // FIXME: preexisting bug; this test was never testing properly... + // it got unnoticed because .expectation silently returns 1. EXPECT_NEAR(energy, -1.7487, 1e-3); + +#endif } CUDAQ_TEST(D2VariationalTester, checkBroadcast) { - using namespace cudaq::spin; - cudaq::set_random_seed(13); - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); #if defined CUDAQ_BACKEND_TENSORNET // Reduce test time by reducing the broadcast size. diff --git a/unittests/integration/gradient_tester.cpp b/unittests/integration/gradient_tester.cpp index 6cbca8b9cf5..ab5816ed1e7 100644 --- a/unittests/integration/gradient_tester.cpp +++ b/unittests/integration/gradient_tester.cpp @@ -32,12 +32,14 @@ struct deuteron_n3_ansatz { }; CUDAQ_TEST(GradientTester, checkSimple) { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); // Use l-bfgs optimizer which requires gradient calc // Since we have gradients, it should converge rather quickly (small number of diff --git a/unittests/integration/kernels_tester.cpp b/unittests/integration/kernels_tester.cpp index 55d26bd53cd..5030f41fec2 100644 --- a/unittests/integration/kernels_tester.cpp +++ b/unittests/integration/kernels_tester.cpp @@ -129,9 +129,10 @@ CUDAQ_TEST(KernelsTester, checkFromState) { auto qubits = kernel.qalloc(2); cudaq::from_state(kernel, qubits, state); std::cout << kernel << "\n"; - using namespace cudaq::spin; - auto H = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + + auto H = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto energy = cudaq::observe(kernel, H).expectation(); EXPECT_NEAR(-1.748, energy, 1e-3); diff --git a/unittests/integration/nlopt_tester.cpp b/unittests/integration/nlopt_tester.cpp index 0c8be97dcb2..2222184b0f8 100644 --- a/unittests/integration/nlopt_tester.cpp +++ b/unittests/integration/nlopt_tester.cpp @@ -36,12 +36,14 @@ struct deuteron_n3_ansatz { }; CUDAQ_TEST(NloptTester, checkSimple) { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); printf("\nOptimize with gradients.\n"); @@ -62,12 +64,14 @@ CUDAQ_TEST(NloptTester, checkSimple) { } CUDAQ_TEST(NloptTester, checkOtherSignatures) { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); printf("\nOptimize with gradients.\n"); diff --git a/unittests/integration/noise_tester.cpp b/unittests/integration/noise_tester.cpp index 2e86e74abcd..f1dcdf92648 100644 --- a/unittests/integration/noise_tester.cpp +++ b/unittests/integration/noise_tester.cpp @@ -994,9 +994,11 @@ CUDAQ_TEST(NoiseTest, checkMeasurementNoise) { #if defined(CUDAQ_BACKEND_DM) || defined(CUDAQ_BACKEND_TENSORNET) CUDAQ_TEST(NoiseTest, checkObserveHamiltonianWithNoise) { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); cudaq::set_random_seed(13); cudaq::depolarization_channel depol(0.1); cudaq::noise_model noise; diff --git a/unittests/integration/observe_result_tester.cpp b/unittests/integration/observe_result_tester.cpp index 56e96a8651f..e5fd5d3e38b 100644 --- a/unittests/integration/observe_result_tester.cpp +++ b/unittests/integration/observe_result_tester.cpp @@ -28,9 +28,10 @@ struct deuteron_n3_ansatz { CUDAQ_TEST(ObserveResult, checkSimple) { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto ansatz = [](double theta) __qpu__ { cudaq::qubit q, r; @@ -73,12 +74,13 @@ CUDAQ_TEST(ObserveResult, checkSimple) { printf("Energy from observe_result with shots %lf\n", obs_res2.expectation()); obs_res2.dump(); - for (const auto &term : h) // td::size_t i = 0; i < h.num_terms(); i++) + for (const auto &term : h) if (!term.is_identity()) printf("Fine-grain data access: %s = %lf\n", term.to_string().data(), obs_res2.expectation(term)); - auto x0x1Counts = obs_res2.counts(x(0) * x(1)); + auto observable = cudaq::spin_op::x(0) * cudaq::spin_op::x(1); + auto x0x1Counts = obs_res2.counts(observable); x0x1Counts.dump(); EXPECT_TRUE(x0x1Counts.size() == 4); } @@ -97,22 +99,29 @@ CUDAQ_TEST(ObserveResult, checkExpValBug) { ry(-0.4, qubits[0]); cz(qubits[1], qubits[2]); }; - using namespace cudaq::spin; - auto hamiltonian = z(0) + z(1); + auto hamiltonian = cudaq::spin_op::z(0) + cudaq::spin_op::z(1); auto result = cudaq::observe(kernel, hamiltonian); - auto exp = result.expectation(z(0)); + auto observable = cudaq::spin_op::z(0); + auto exp = result.expectation(observable); printf("exp %lf \n", exp); EXPECT_NEAR(exp, .79, 1e-1); - exp = result.expectation(z(1)); + observable = cudaq::spin_op::z(1); + exp = result.expectation(observable); printf("exp %lf \n", exp); EXPECT_NEAR(exp, .62, 1e-1); - exp = result.expectation(z(0) * i(1)); - printf("exp %lf \n", exp); - EXPECT_NEAR(exp, .79, 1e-1); + // We support retrieval of terms as long as they are equal to the + // terms defined in the spin op passed to observe. A term/operator + // that acts on two degrees is never the same as an operator that + // acts on one degree, even if it only acts with an identity on the + // second degree. While the expectation values generally should be + // the same in this case, the operators are not (e.g. the respective + // kernels/gates defined by the two operators is not the same since + // it acts on a different number of qubits). This is in particular + // also relevant for noise modeling. } #endif #endif diff --git a/unittests/integration/vqe_tester.cpp b/unittests/integration/vqe_tester.cpp index 2c6f6800057..6841f221008 100644 --- a/unittests/integration/vqe_tester.cpp +++ b/unittests/integration/vqe_tester.cpp @@ -187,11 +187,14 @@ CUDAQ_TEST_F(VQETester, checkBuilderVqe) { } CUDAQ_TEST_F(VQETester, checkArgMapper) { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); auto ansatz = [](double theta, double phi) __qpu__ { cudaq::qvector q(3); @@ -222,11 +225,13 @@ CUDAQ_TEST_F(VQETester, checkThrowInvalidRuntimeArgs) { // Build up a 2 parameter circuit using a vector parameter // Run the cudaq optimizer to find optimal value. - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - cudaq::spin_op h3 = h + 9.625 - 9.625 * z(2) - 3.913119 * x(1) * x(2) - - 3.913119 * y(1) * y(2); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + cudaq::spin_op h3 = h + 9.625 - 9.625 * cudaq::spin_op::z(2) - + 3.913119 * cudaq::spin_op::x(1) * cudaq::spin_op::x(2) - + 3.913119 * cudaq::spin_op::y(1) * cudaq::spin_op::y(2); auto [ansatz, theta, phi] = cudaq::make_kernel(); diff --git a/unittests/mqpu/dynamics_async_tester.cpp b/unittests/mqpu/dynamics_async_tester.cpp index 31dca49653f..c2e5d49b9a1 100644 --- a/unittests/mqpu/dynamics_async_tester.cpp +++ b/unittests/mqpu/dynamics_async_tester.cpp @@ -7,7 +7,7 @@ ******************************************************************************/ #include "cudaq.h" #include "cudaq/algorithms/evolve.h" -#include "cudaq/dynamics_integrators.h" +#include "cudaq/algorithms/integrator.h" #include TEST(DynamicsAsyncTester, checkSimple) { @@ -15,7 +15,7 @@ TEST(DynamicsAsyncTester, checkSimple) { printf("Num QPUs %lu\n", platform.num_qpus()); auto jobHandle1 = []() { const cudaq::dimension_map dims = {{0, 2}}; - auto ham = 2.0 * M_PI * 0.1 * cudaq::sum_op::x(0); + auto ham = 2.0 * M_PI * 0.1 * cudaq::spin_op::x(0); constexpr int numSteps = 10; std::vector> steps; for (double t : cudaq::linspace(0.0, 1.0, numSteps)) { @@ -31,10 +31,8 @@ TEST(DynamicsAsyncTester, checkSimple) { /*max_step_size*/ 0.001); auto resultFuture1 = cudaq::evolve_async( ham, dims, schedule, initialState, integrator, - std::vector>{}, - std::vector>{ - cudaq::sum_op::z(0)}, - true, {}, 0); + std::vector{}, + std::vector{cudaq::spin_op::z(0)}, true, {}, 0); std::cout << "Launched evolve job on QPU 0\n"; return resultFuture1; }(); @@ -60,10 +58,9 @@ TEST(DynamicsAsyncTester, checkSimple) { /*max_step_size*/ 0.01); auto resultFuture = cudaq::evolve_async( hamiltonian, dimensions, schedule, psi0, integrator, - std::vector>{ - std::sqrt(decay_rate) * cudaq::boson_op::annihilate(0)}, - std::vector>{hamiltonian}, true, - {}, 1); + std::vector{std::sqrt(decay_rate) * + cudaq::boson_op::annihilate(0)}, + std::vector{hamiltonian}, true, {}, 1); std::cout << "Launched evolve job on QPU 1\n"; return resultFuture; }(); @@ -115,7 +112,7 @@ TEST(DynamicsAsyncTester, checkInitializerArgs) { printf("Num QPUs %lu\n", platform.num_qpus()); auto jobHandle1 = []() { const cudaq::dimension_map dims = {{0, 2}}; - auto ham = 2.0 * M_PI * 0.1 * cudaq::sum_op::x(0); + auto ham = 2.0 * M_PI * 0.1 * cudaq::spin_op::x(0); constexpr int numSteps = 10; std::vector> steps; for (double t : cudaq::linspace(0.0, 1.0, numSteps)) { @@ -129,9 +126,9 @@ TEST(DynamicsAsyncTester, checkInitializerArgs) { auto initialState = cudaq::state::from_data(std::vector>{1.0, 0.0}); cudaq::integrators::runge_kutta integrator(1, 0.001); - auto resultFuture1 = cudaq::evolve_async( - ham, dims, schedule, initialState, integrator, {}, - {cudaq::sum_op::z(0)}, true, {}, 0); + auto resultFuture1 = + cudaq::evolve_async(ham, dims, schedule, initialState, integrator, {}, + {cudaq::spin_op::z(0)}, true, {}, 0); std::cout << "Launched evolve job on QPU 0\n"; return resultFuture1; }(); diff --git a/unittests/mqpu/mpi_mqpu_tester.cpp b/unittests/mqpu/mpi_mqpu_tester.cpp index 383851b2cab..4637a290d1e 100644 --- a/unittests/mqpu/mpi_mqpu_tester.cpp +++ b/unittests/mqpu/mpi_mqpu_tester.cpp @@ -20,9 +20,10 @@ ::testing::Environment *const foo_env = AddGlobalTestEnvironment(new TestEnvironment); TEST(MPIObserveTester, checkSimple) { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto ansatz = [](double theta) __qpu__ { cudaq::qubit q, r; diff --git a/unittests/mqpu/mqpu_tester.cpp b/unittests/mqpu/mqpu_tester.cpp index ad959e03403..768f31d0de0 100644 --- a/unittests/mqpu/mqpu_tester.cpp +++ b/unittests/mqpu/mqpu_tester.cpp @@ -11,9 +11,11 @@ #include TEST(MQPUTester, checkSimple) { - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto ansatz = [](double theta) __qpu__ { cudaq::qubit q, r; diff --git a/unittests/dynamics/boson_operator.cpp b/unittests/operators/boson_op.cpp similarity index 99% rename from unittests/dynamics/boson_operator.cpp rename to unittests/operators/boson_op.cpp index ad1d563a788..b5044064361 100644 --- a/unittests/dynamics/boson_operator.cpp +++ b/unittests/operators/boson_op.cpp @@ -527,7 +527,7 @@ TEST(OperatorExpressions, checkBosonOpsWithScalars) { } TEST(OperatorExpressions, checkBosonOpsSimpleArithmetics) { - std::unordered_map dimensions = {{0, 3}, {1, 2}, {2, 4}}; + cudaq::dimension_map dimensions = {{0, 3}, {1, 2}, {2, 4}}; // Addition, same DOF. { @@ -631,7 +631,7 @@ TEST(OperatorExpressions, checkBosonOpsAdvancedArithmetics) { // Keeping this fixed throughout. std::complex value = std::complex(0.125, 0.5); - std::unordered_map dimensions = {{0, 3}, {1, 2}, {2, 4}, {3, 2}}; + cudaq::dimension_map dimensions = {{0, 3}, {1, 2}, {2, 4}, {3, 2}}; // `boson_handler + sum_op` { @@ -789,7 +789,7 @@ TEST(OperatorExpressions, checkBosonOpsAdvancedArithmetics) { TEST(OperatorExpressions, checkBosonOpsDegreeVerification) { auto op1 = cudaq::boson_op::create(2); auto op2 = cudaq::boson_op::annihilate(0); - std::unordered_map dimensions = {{0, 2}, {1, 2}, {2, 3}, {3, 3}}; + cudaq::dimension_map dimensions = {{0, 2}, {1, 2}, {2, 3}, {3, 3}}; ASSERT_ANY_THROW(op1.to_matrix({})); ASSERT_ANY_THROW(op1.to_matrix({{1, 2}})); @@ -829,7 +829,7 @@ TEST(OperatorExpressions, checkCommutationRelations) { // [a(k), a†(q)] = δkq // [a†(k), a†(q)] = [a(k), a(q)] = 0 - std::unordered_map dimensions = {{0, 4}, {1, 4}}; + cudaq::dimension_map dimensions = {{0, 4}, {1, 4}}; auto commutator = [](cudaq::product_op ad, cudaq::product_op a) { return a * ad - ad * a; diff --git a/unittests/dynamics/operator_conversions.cpp b/unittests/operators/conversions.cpp similarity index 97% rename from unittests/dynamics/operator_conversions.cpp rename to unittests/operators/conversions.cpp index 6fd63096d26..5088899cc95 100644 --- a/unittests/dynamics/operator_conversions.cpp +++ b/unittests/operators/conversions.cpp @@ -14,11 +14,11 @@ TEST(OperatorExpressions, checkElementaryOpsConversions) { std::unordered_map> parameters = { {"squeezing", 0.5}, {"displacement", 0.25}}; - std::unordered_map dimensions = {{0, 2}, {1, 2}}; + cudaq::dimension_map dimensions = {{0, 2}, {1, 2}}; auto matrix_elementary = cudaq::matrix_op::parity(1); auto matrix_elementary_expected = utils::parity_matrix(2); - auto spin_elementary = cudaq::sum_op::y(1); + auto spin_elementary = cudaq::spin_op::y(1); auto spin_elementary_expected = utils::PauliY_matrix(); auto boson_elementary = cudaq::boson_op::annihilate(1); auto boson_elementary_expected = utils::annihilate_matrix(2); @@ -142,13 +142,12 @@ TEST(OperatorExpressions, checkProductOperatorConversions) { std::unordered_map> parameters = { {"squeezing", 0.5}, {"displacement", 0.25}}; - std::unordered_map dimensions = {{0, 2}, {1, 2}}; + cudaq::dimension_map dimensions = {{0, 2}, {1, 2}}; auto matrix_product = cudaq::matrix_op::squeeze(0) * cudaq::matrix_op::displace(1); auto matrix_product_expected = cudaq::kronecker( utils::displace_matrix(2, 0.25), utils::squeeze_matrix(2, 0.5)); - auto spin_product = cudaq::sum_op::y(1) * - cudaq::sum_op::x(0); + auto spin_product = cudaq::spin_op::y(1) * cudaq::spin_op::x(0); auto spin_product_expected = cudaq::kronecker(utils::PauliY_matrix(), utils::PauliX_matrix()); auto boson_product = @@ -273,14 +272,13 @@ TEST(OperatorExpressions, checkOperatorSumConversions) { std::unordered_map> parameters = { {"squeezing", 0.5}, {"displacement", 0.25}}; - std::unordered_map dimensions = {{0, 2}, {1, 2}}; + cudaq::dimension_map dimensions = {{0, 2}, {1, 2}}; auto matrix_product = cudaq::matrix_op::squeeze(0) * cudaq::matrix_op::displace(1); auto matrix_product_expected = cudaq::kronecker( utils::displace_matrix(2, 0.25), utils::squeeze_matrix(2, 0.5)); - auto spin_product = cudaq::sum_op::y(1) * - cudaq::sum_op::x(0); + auto spin_product = cudaq::spin_op::y(1) * cudaq::spin_op::x(0); auto spin_product_expected = cudaq::kronecker(utils::PauliY_matrix(), utils::PauliX_matrix()); auto boson_product = @@ -293,8 +291,7 @@ TEST(OperatorExpressions, checkOperatorSumConversions) { auto matrix_sum_expected = cudaq::kronecker(utils::displace_matrix(2, 0.25), utils::id_matrix(2)) + cudaq::kronecker(utils::id_matrix(2), utils::squeeze_matrix(2, 0.5)); - auto spin_sum = cudaq::sum_op::y(1) + - cudaq::sum_op::x(0); + auto spin_sum = cudaq::spin_op::y(1) + cudaq::spin_op::x(0); auto spin_sum_expected = cudaq::kronecker(utils::PauliY_matrix(), utils::id_matrix(2)) + cudaq::kronecker(utils::id_matrix(2), utils::PauliX_matrix()); diff --git a/unittests/dynamics/fermion_operator.cpp b/unittests/operators/fermion_op.cpp similarity index 100% rename from unittests/dynamics/fermion_operator.cpp rename to unittests/operators/fermion_op.cpp diff --git a/unittests/dynamics/test_Helpers.cpp b/unittests/operators/manipulation.cpp similarity index 89% rename from unittests/dynamics/test_Helpers.cpp rename to unittests/operators/manipulation.cpp index cf200af21b5..4171d554a2a 100644 --- a/unittests/dynamics/test_Helpers.cpp +++ b/unittests/operators/manipulation.cpp @@ -6,14 +6,14 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -#include "cudaq/dynamics/helpers.h" #include "cudaq/operators.h" +#include "cudaq/operators/helpers.h" #include using namespace cudaq::detail; TEST(OperatorHelpersTest, GenerateAllStates_TwoQubits) { - std::vector degrees = {0, 1}; + std::vector degrees = {0, 1}; cudaq::dimension_map dimensions = {{0, 2}, {1, 2}}; auto states = generate_all_states(degrees, dimensions); @@ -23,7 +23,7 @@ TEST(OperatorHelpersTest, GenerateAllStates_TwoQubits) { } TEST(OperatorHelpersTest, GenerateAllStates_ThreeQubits) { - std::vector degrees = {0, 1, 2}; + std::vector degrees = {0, 1, 2}; cudaq::dimension_map dimensions = {{0, 2}, {1, 2}, {2, 2}}; auto states = generate_all_states(degrees, dimensions); @@ -34,7 +34,7 @@ TEST(OperatorHelpersTest, GenerateAllStates_ThreeQubits) { } TEST(OperatorHelpersTest, GenerateAllStates_EmptyDegrees) { - std::vector degrees; + std::vector degrees; cudaq::dimension_map dimensions; auto states = generate_all_states(degrees, dimensions); @@ -49,7 +49,7 @@ TEST(OperatorHelpersTest, PermuteMatrix_SingleSwap) { matrix[{1, 1}] = 4; // Swap rows and columns - std::vector permutation = {1, 0}; + std::vector permutation = {1, 0}; permute_matrix(matrix, permutation); @@ -75,7 +75,7 @@ TEST(OperatorHelpersTest, PermuteMatrix_IdentityPermutation) { matrix[{2, 2}] = 9; // No change - std::vector permutation = {0, 1, 2}; + std::vector permutation = {0, 1, 2}; permute_matrix(matrix, permutation); diff --git a/unittests/dynamics/matrix_operator.cpp b/unittests/operators/matrix_op.cpp similarity index 98% rename from unittests/dynamics/matrix_operator.cpp rename to unittests/operators/matrix_op.cpp index 4177fce07a0..a42317f6155 100644 --- a/unittests/dynamics/matrix_operator.cpp +++ b/unittests/operators/matrix_op.cpp @@ -147,19 +147,19 @@ TEST(OperatorExpressions, checkPreBuiltMatrixOps) { TEST(OperatorExpressions, checkCustomMatrixOps) { auto level_count = 2; - std::unordered_map dimensions = { + cudaq::dimension_map dimensions = { {0, level_count + 1}, {1, level_count + 2}, {3, level_count}}; { auto func0 = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { return cudaq::kronecker(utils::position_matrix(dimensions[1]), utils::momentum_matrix(dimensions[0])); ; }; auto func1 = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { return cudaq::kronecker(utils::number_matrix(dimensions[1]), utils::position_matrix(dimensions[0])); @@ -449,8 +449,7 @@ TEST(OperatorExpressions, checkMatrixOpsSimpleArithmetics) { /// Keeping this fixed throughout. int level_count = 3; - std::unordered_map dimensions = {{0, level_count}, - {1, level_count}}; + cudaq::dimension_map dimensions = {{0, level_count}, {1, level_count}}; // Addition, same DOF. { @@ -714,18 +713,18 @@ TEST(OperatorExpressions, checkMatrixOpsAdvancedArithmetics) { TEST(OperatorExpressions, checkMatrixOpsDegreeVerification) { auto op1 = cudaq::matrix_op::position(2); auto op2 = cudaq::matrix_op::momentum(0); - std::unordered_map dimensions = {{0, 2}, {1, 2}, {2, 3}, {3, 3}}; + cudaq::dimension_map dimensions = {{0, 2}, {1, 2}, {2, 3}, {3, 3}}; { auto func0 = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { return cudaq::kronecker(utils::momentum_matrix(dimensions[0]), utils::position_matrix(dimensions[1])); ; }; auto func1 = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { return cudaq::kronecker(utils::position_matrix(dimensions[0]), utils::number_matrix(dimensions[1])); @@ -773,7 +772,7 @@ TEST(OperatorExpressions, checkMatrixOpsParameterVerification) { std::unordered_map> parameters = { {"squeezing", 0.5}, {"displacement", 0.25}}; - std::unordered_map dimensions = {{0, 2}, {1, 2}}; + cudaq::dimension_map dimensions = {{0, 2}, {1, 2}}; auto squeeze = cudaq::matrix_op::squeeze(1); auto displace = cudaq::matrix_op::displace(0); diff --git a/unittests/dynamics/product_operator.cpp b/unittests/operators/product_op.cpp similarity index 90% rename from unittests/dynamics/product_operator.cpp rename to unittests/operators/product_op.cpp index c0e20c2f45c..71b9b9bee7f 100644 --- a/unittests/dynamics/product_operator.cpp +++ b/unittests/operators/product_op.cpp @@ -13,22 +13,74 @@ #include TEST(OperatorExpressions, checkProductOperatorBasics) { - std::vector levels = {2, 3, 4}; + // checking some utility functions + { + std::vector all_degrees = {0, 1, 2, 3}; + for (auto id_target : all_degrees) { + cudaq::spin_op_term op; + cudaq::spin_op_term expected; + for (std::size_t target : all_degrees) { + if (target == id_target) + op *= cudaq::spin_op::i(target); + else if (target & 2) { + op *= cudaq::spin_op::z(target); + expected *= cudaq::spin_op::z(target); + } else if (target & 1) { + op *= cudaq::spin_op::x(target); + expected *= cudaq::spin_op::x(target); + } else { + op *= cudaq::spin_op::y(target); + expected *= cudaq::spin_op::y(target); + } + } + ASSERT_NE(op, expected); + op.canonicalize(); + ASSERT_EQ(op, expected); + ASSERT_EQ(op.degrees(), expected.degrees()); + ASSERT_EQ(op.to_matrix(), expected.to_matrix()); + ASSERT_NE(op.degrees(), all_degrees); + op.canonicalize( + std::set(all_degrees.begin(), all_degrees.end())); + ASSERT_EQ(op.degrees(), all_degrees); + } + } + + // checking some constructors + { + cudaq::product_op ids(2, 5); + std::vector expected_degrees = {2, 3, 4}; + ASSERT_EQ(ids.degrees(), expected_degrees); + ASSERT_EQ(ids.num_ops(), expected_degrees.size()); + for (std::size_t idx = 2; const auto &op : ids) + ASSERT_EQ(op, cudaq::matrix_handler(idx++)); + } + { + cudaq::product_op ids(2, 5); + std::vector expected_degrees = {2, 3, 4}; + ASSERT_EQ(ids.degrees(), expected_degrees); + ASSERT_EQ(ids.num_ops(), expected_degrees.size()); + for (std::size_t idx = 2; const auto &op : ids) + ASSERT_EQ(op, cudaq::spin_handler(idx++)); + } + + std::vector levels = {2, 3, 4}; 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; {// Same degrees of freedom. - {auto spin0 = cudaq::sum_op::x(5); - auto spin1 = cudaq::sum_op::z(5); + {auto spin0 = cudaq::spin_op::x(5); + auto spin1 = cudaq::spin_op::z(5); auto spin_prod = spin0 * spin1; std::vector want_degrees = {5}; auto spin_matrix = utils::PauliX_matrix() * utils::PauliZ_matrix(); ASSERT_TRUE(spin_prod.degrees() == want_degrees); + ASSERT_EQ(spin_prod.min_degree(), 5); + ASSERT_EQ(spin_prod.max_degree(), 5); utils::checkEqual(spin_matrix, spin_prod.to_matrix()); for (auto level_count : levels) { @@ -38,6 +90,8 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { auto got = op0 * op1; utils::assert_product_equal(got, 1., {*op0.begin(), *op1.begin()}); ASSERT_TRUE(got.degrees() == want_degrees); + ASSERT_EQ(got.min_degree(), 5); + ASSERT_EQ(got.max_degree(), 5); auto got_matrix = got.to_matrix({{5, level_count}}); auto matrix0 = utils::position_matrix(level_count); @@ -49,8 +103,8 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { // Different degrees of freedom. { - auto spin0 = cudaq::sum_op::x(0); - auto spin1 = cudaq::sum_op::z(1); + auto spin0 = cudaq::spin_op::x(0); + auto spin1 = cudaq::spin_op::z(1); auto spin_prod = spin0 * spin1; std::vector want_degrees = {0, 1}; @@ -58,6 +112,8 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { cudaq::kronecker(utils::PauliZ_matrix(), utils::PauliX_matrix()); ASSERT_TRUE(spin_prod.degrees() == want_degrees); + ASSERT_EQ(spin_prod.min_degree(), 0); + ASSERT_EQ(spin_prod.max_degree(), 1); utils::checkEqual(spin_matrix, spin_prod.to_matrix()); for (auto level_count : levels) { @@ -69,6 +125,8 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { ASSERT_TRUE(got.degrees() == want_degrees); ASSERT_TRUE(got_reverse.degrees() == want_degrees); + ASSERT_EQ(got.min_degree(), 0); + ASSERT_EQ(got.max_degree(), 1); auto got_matrix = got.to_matrix({{0, level_count}, {1, level_count}}); auto got_matrix_reverse = @@ -91,8 +149,8 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { // Different degrees of freedom, non-consecutive. // Should produce the same matrices as the above test. { - auto spin0 = cudaq::sum_op::x(0); - auto spin1 = cudaq::sum_op::z(2); + auto spin0 = cudaq::spin_op::x(0); + auto spin1 = cudaq::spin_op::z(2); auto spin_prod = spin0 * spin1; std::vector want_degrees = {0, 2}; @@ -100,6 +158,8 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { cudaq::kronecker(utils::PauliZ_matrix(), utils::PauliX_matrix()); ASSERT_TRUE(spin_prod.degrees() == want_degrees); + ASSERT_EQ(spin_prod.min_degree(), 0); + ASSERT_EQ(spin_prod.max_degree(), 2); utils::checkEqual(spin_matrix, spin_prod.to_matrix()); for (auto level_count : levels) { @@ -111,6 +171,8 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { ASSERT_TRUE(got.degrees() == want_degrees); ASSERT_TRUE(got_reverse.degrees() == want_degrees); + ASSERT_EQ(got.min_degree(), 0); + ASSERT_EQ(got.max_degree(), 2); auto got_matrix = got.to_matrix({{0, level_count}, {2, level_count}}); auto got_matrix_reverse = @@ -133,14 +195,14 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { // Different degrees of freedom, non-consecutive but all dimensions // provided. { - auto spin0 = cudaq::sum_op::x(0); - auto spin1 = cudaq::sum_op::z(2); + auto spin0 = cudaq::spin_op::x(0); + auto spin1 = cudaq::spin_op::z(2); auto spin_prod = spin0 * spin1; std::vector want_degrees = {0, 2}; auto spin_matrix = cudaq::kronecker(utils::PauliZ_matrix(), utils::PauliX_matrix()); - std::unordered_map dimensions = {{0, 2}, {1, 2}, {2, 2}}; + cudaq::dimension_map dimensions = {{0, 2}, {1, 2}, {2, 2}}; ASSERT_TRUE(spin_prod.degrees() == want_degrees); utils::checkEqual(spin_matrix, spin_prod.to_matrix(dimensions)); @@ -208,7 +270,7 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { // spin operator against constant { - auto op = cudaq::sum_op::x(0); + auto op = cudaq::spin_op::x(0); auto scalar_op = cudaq::scalar_operator(value_0); auto product = scalar_op * op; auto reverse = op * scalar_op; @@ -242,7 +304,7 @@ TEST(OperatorExpressions, checkProductOperatorBasics) { // spin operator against constant from lambda { - auto op = cudaq::sum_op::x(1); + auto op = cudaq::spin_op::x(1); auto scalar_op = cudaq::scalar_operator(function); auto product = scalar_op * op; auto reverse = op * scalar_op; @@ -333,8 +395,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { /// `spin product + complex` { - auto product_op = cudaq::sum_op::x(0) * - cudaq::sum_op::y(1); + auto product_op = cudaq::spin_op::x(0) * cudaq::spin_op::y(1); auto sum = value_0 + product_op; auto reverse = product_op + value_0; @@ -432,8 +493,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { /// `spin product - double` { - auto product_op = cudaq::sum_op::i(0) * - cudaq::sum_op::z(1); + auto product_op = cudaq::spin_op::i(0) * cudaq::spin_op::z(1); auto sum = 2.0 - product_op; auto reverse = product_op - 2.0; @@ -535,18 +595,15 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { { auto product_op = cudaq::matrix_op::parity(0) * cudaq::matrix_op::parity(1); ASSERT_TRUE(product_op.num_ops() == 2); - ASSERT_TRUE(product_op.get_coefficient().evaluate() == - std::complex(1.)); + ASSERT_TRUE(product_op.evaluate_coefficient() == std::complex(1.)); auto product = 2.0 * product_op; auto reverse = product_op * 2.0; ASSERT_TRUE(product.num_ops() == 2); ASSERT_TRUE(reverse.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == - std::complex(2.)); - ASSERT_TRUE(reverse.get_coefficient().evaluate() == - std::complex(2.)); + ASSERT_TRUE(product.evaluate_coefficient() == std::complex(2.)); + ASSERT_TRUE(reverse.evaluate_coefficient() == std::complex(2.)); std::vector want_degrees = {0, 1}; ASSERT_TRUE(product.degrees() == want_degrees); @@ -574,16 +631,15 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { { auto product_op = cudaq::matrix_op::number(0) * cudaq::matrix_op::number(1); ASSERT_TRUE(product_op.num_ops() == 2); - ASSERT_TRUE(product_op.get_coefficient().evaluate() == - std::complex(1.)); + ASSERT_TRUE(product_op.evaluate_coefficient() == std::complex(1.)); auto product = value_0 * product_op; auto reverse = product_op * value_0; ASSERT_TRUE(product.num_ops() == 2); ASSERT_TRUE(reverse.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == value_0); - ASSERT_TRUE(reverse.get_coefficient().evaluate() == value_0); + ASSERT_TRUE(product.evaluate_coefficient() == value_0); + ASSERT_TRUE(reverse.evaluate_coefficient() == value_0); std::vector want_degrees = {0, 1}; ASSERT_TRUE(product.degrees() == want_degrees); @@ -619,8 +675,8 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { ASSERT_TRUE(product.num_ops() == 2); ASSERT_TRUE(reverse.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == scalar_op.evaluate()); - ASSERT_TRUE(reverse.get_coefficient().evaluate() == scalar_op.evaluate()); + ASSERT_TRUE(product.evaluate_coefficient() == scalar_op.evaluate()); + ASSERT_TRUE(reverse.evaluate_coefficient() == scalar_op.evaluate()); std::vector want_degrees = {0, 1}; ASSERT_TRUE(product.degrees() == want_degrees); @@ -647,8 +703,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { /// `spin product * scalar_operator` { - auto product_op = cudaq::sum_op::z(0) * - cudaq::sum_op::y(1); + auto product_op = cudaq::spin_op::z(0) * cudaq::spin_op::y(1); auto scalar_op = cudaq::scalar_operator(value_0); auto product = scalar_op * product_op; @@ -656,8 +711,8 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { ASSERT_TRUE(product.num_ops() == 2); ASSERT_TRUE(reverse.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == scalar_op.evaluate()); - ASSERT_TRUE(reverse.get_coefficient().evaluate() == scalar_op.evaluate()); + ASSERT_TRUE(product.evaluate_coefficient() == scalar_op.evaluate()); + ASSERT_TRUE(reverse.evaluate_coefficient() == scalar_op.evaluate()); std::vector want_degrees = {0, 1}; ASSERT_TRUE(product.degrees() == want_degrees); @@ -682,13 +737,12 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { { auto product_op = cudaq::matrix_op::parity(0) * cudaq::matrix_op::parity(1); ASSERT_TRUE(product_op.num_ops() == 2); - ASSERT_TRUE(product_op.get_coefficient().evaluate() == - std::complex(1.)); + ASSERT_TRUE(product_op.evaluate_coefficient() == std::complex(1.)); auto reverse = product_op / 2.0; ASSERT_TRUE(reverse.num_ops() == 2); - ASSERT_TRUE(reverse.get_coefficient().evaluate() == + ASSERT_TRUE(reverse.evaluate_coefficient() == std::complex(1. / 2.)); std::vector want_degrees = {0, 1}; @@ -713,13 +767,12 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { { auto product_op = cudaq::matrix_op::number(0) * cudaq::matrix_op::number(1); ASSERT_TRUE(product_op.num_ops() == 2); - ASSERT_TRUE(product_op.get_coefficient().evaluate() == - std::complex(1.)); + ASSERT_TRUE(product_op.evaluate_coefficient() == std::complex(1.)); auto reverse = product_op / value_0; ASSERT_TRUE(reverse.num_ops() == 2); - ASSERT_TRUE(reverse.get_coefficient().evaluate() == 1. / value_0); + ASSERT_TRUE(reverse.evaluate_coefficient() == 1. / value_0); std::vector want_degrees = {0, 1}; ASSERT_TRUE(reverse.degrees() == want_degrees); @@ -749,8 +802,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { auto reverse = product_op / scalar_op; ASSERT_TRUE(reverse.num_ops() == 2); - ASSERT_TRUE(reverse.get_coefficient().evaluate() == - 1. / scalar_op.evaluate()); + ASSERT_TRUE(reverse.evaluate_coefficient() == 1. / scalar_op.evaluate()); std::vector want_degrees = {0, 1}; ASSERT_TRUE(reverse.degrees() == want_degrees); @@ -773,15 +825,13 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { /// `spin product / scalar_operator` { - auto product_op = cudaq::sum_op::z(0) * - cudaq::sum_op::y(1); + auto product_op = cudaq::spin_op::z(0) * cudaq::spin_op::y(1); auto scalar_op = cudaq::scalar_operator(value_0); auto reverse = product_op / scalar_op; ASSERT_TRUE(reverse.num_ops() == 2); - ASSERT_TRUE(reverse.get_coefficient().evaluate() == - 1. / scalar_op.evaluate()); + ASSERT_TRUE(reverse.evaluate_coefficient() == 1. / scalar_op.evaluate()); std::vector want_degrees = {0, 1}; ASSERT_TRUE(reverse.degrees() == want_degrees); @@ -805,8 +855,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { product *= 2.0; ASSERT_TRUE(product.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == - std::complex(2.)); + ASSERT_TRUE(product.evaluate_coefficient() == std::complex(2.)); std::vector want_degrees = {0, 1}; ASSERT_TRUE(product.degrees() == want_degrees); @@ -827,13 +876,11 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { /// `spin product *= double` { - auto product = cudaq::sum_op::y(0) * - cudaq::sum_op::i(1); + auto product = cudaq::spin_op::y(0) * cudaq::spin_op::i(1); product *= 2.0; ASSERT_TRUE(product.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == - std::complex(2.)); + ASSERT_TRUE(product.evaluate_coefficient() == std::complex(2.)); std::vector want_degrees = {0, 1}; ASSERT_TRUE(product.degrees() == want_degrees); @@ -856,7 +903,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { product *= value_0; ASSERT_TRUE(product.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == value_0); + ASSERT_TRUE(product.evaluate_coefficient() == value_0); std::vector want_degrees = {0, 1}; ASSERT_TRUE(product.degrees() == want_degrees); @@ -884,7 +931,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { product *= scalar_op; ASSERT_TRUE(product.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == scalar_op.evaluate()); + ASSERT_TRUE(product.evaluate_coefficient() == scalar_op.evaluate()); ASSERT_TRUE(scalar_op.evaluate() == value_0); std::vector want_degrees = {0, 1}; @@ -911,7 +958,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { product /= 2.0; ASSERT_TRUE(product.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == + ASSERT_TRUE(product.evaluate_coefficient() == std::complex(1. / 2.)); std::vector want_degrees = {0, 1}; @@ -933,12 +980,11 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { /// `spin product /= double` { - auto product = cudaq::sum_op::y(0) * - cudaq::sum_op::i(1); + auto product = cudaq::spin_op::y(0) * cudaq::spin_op::i(1); product /= 2.0; ASSERT_TRUE(product.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == + ASSERT_TRUE(product.evaluate_coefficient() == std::complex(1. / 2.)); std::vector want_degrees = {0, 1}; @@ -962,7 +1008,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { product /= value_0; ASSERT_TRUE(product.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == 1. / value_0); + ASSERT_TRUE(product.evaluate_coefficient() == 1. / value_0); std::vector want_degrees = {0, 1}; ASSERT_TRUE(product.degrees() == want_degrees); @@ -990,8 +1036,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { product /= scalar_op; ASSERT_TRUE(product.num_ops() == 2); - ASSERT_TRUE(product.get_coefficient().evaluate() == - 1. / scalar_op.evaluate()); + ASSERT_TRUE(product.evaluate_coefficient() == 1. / scalar_op.evaluate()); ASSERT_TRUE(scalar_op.evaluate() == value_0); std::vector want_degrees = {0, 1}; @@ -1015,7 +1060,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstScalars) { TEST(OperatorExpressions, checkProductOperatorAgainstProduct) { int level_count = 3; - std::unordered_map dimensions = { + cudaq::dimension_map dimensions = { {0, level_count}, {1, level_count}, {2, level_count + 1}}; // `product_op + product_op` @@ -1063,10 +1108,8 @@ TEST(OperatorExpressions, checkProductOperatorAgainstProduct) { // `spin product + spin product` { - auto term_0 = cudaq::sum_op::z(0) * - cudaq::sum_op::y(2); - auto term_1 = cudaq::sum_op::x(2) * - cudaq::sum_op::z(4); + auto term_0 = cudaq::spin_op::z(0) * cudaq::spin_op::y(2); + auto term_1 = cudaq::spin_op::x(2) * cudaq::spin_op::z(4); auto sum = term_0 + term_1; @@ -1144,9 +1187,8 @@ TEST(OperatorExpressions, checkProductOperatorAgainstProduct) { // `spin product - spin product` { - auto term_0 = cudaq::sum_op::i(0); - auto term_1 = cudaq::sum_op::x(1) * - cudaq::sum_op::y(2); + auto term_0 = cudaq::spin_op::i(0); + auto term_1 = cudaq::spin_op::x(1) * cudaq::spin_op::y(2); auto difference = term_0 - term_1; auto reverse = term_1 - term_0; @@ -1222,10 +1264,8 @@ TEST(OperatorExpressions, checkProductOperatorAgainstProduct) { // `spin product * spin product` { - auto term_0 = cudaq::sum_op::y(0) * - cudaq::sum_op::x(1); - auto term_1 = cudaq::sum_op::z(1) * - cudaq::sum_op::i(3); + auto term_0 = cudaq::spin_op::y(0) * cudaq::spin_op::x(1); + auto term_1 = cudaq::spin_op::z(1) * cudaq::spin_op::i(3); auto product = term_0 * term_1; auto reverse = term_1 * term_0; @@ -1311,10 +1351,8 @@ TEST(OperatorExpressions, checkProductOperatorAgainstProduct) { // `spin product *= spin product` { - auto term_0 = cudaq::sum_op::y(3) * - cudaq::sum_op::y(1); - auto term_1 = cudaq::sum_op::z(1) * - cudaq::sum_op::x(0); + auto term_0 = cudaq::spin_op::y(3) * cudaq::spin_op::y(1); + auto term_1 = cudaq::spin_op::z(1) * cudaq::spin_op::x(0); term_0 *= term_1; @@ -1354,7 +1392,7 @@ TEST(OperatorExpressions, checkProductOperatorAgainstProduct) { TEST(OperatorExpressions, checkProductOperatorAgainstOperatorSum) { int level_count = 3; - std::unordered_map dimensions = { + cudaq::dimension_map dimensions = { {0, level_count}, {1, level_count}, {2, level_count + 1}}; // `product_op + sum_op` @@ -1401,10 +1439,8 @@ TEST(OperatorExpressions, checkProductOperatorAgainstOperatorSum) { // `spin product + spin sum` { - auto product = cudaq::sum_op::x(0) * - cudaq::sum_op::y(1); - auto original_sum = cudaq::sum_op::z(1) + - cudaq::sum_op::i(2); + auto product = cudaq::spin_op::x(0) * cudaq::spin_op::y(1); + auto original_sum = cudaq::spin_op::z(1) + cudaq::spin_op::i(2); auto sum = product + original_sum; auto reverse = original_sum + product; @@ -1481,10 +1517,8 @@ TEST(OperatorExpressions, checkProductOperatorAgainstOperatorSum) { // `spin product - spin sum` { - auto product = cudaq::sum_op::y(0) * - cudaq::sum_op::z(1); - auto original_difference = cudaq::sum_op::x(1) - - cudaq::sum_op::i(2); + auto product = cudaq::spin_op::y(0) * cudaq::spin_op::z(1); + auto original_difference = cudaq::spin_op::x(1) - cudaq::spin_op::i(2); auto difference = product - original_difference; auto reverse = original_difference - product; @@ -1560,10 +1594,8 @@ TEST(OperatorExpressions, checkProductOperatorAgainstOperatorSum) { // `spin product * spin sum` { - auto original_product = cudaq::sum_op::z(0) * - cudaq::sum_op::y(1); - auto sum = cudaq::sum_op::i(1) + - cudaq::sum_op::x(2); + auto original_product = cudaq::spin_op::z(0) * cudaq::spin_op::y(1); + auto sum = cudaq::spin_op::i(1) + cudaq::spin_op::x(2); auto product = original_product * sum; auto reverse = sum * original_product; @@ -1599,20 +1631,20 @@ TEST(OperatorExpressions, checkProductOperatorAgainstOperatorSum) { TEST(OperatorExpressions, checkCustomProductOps) { auto level_count = 2; - std::unordered_map dimensions = {{0, level_count + 1}, - {1, level_count + 2}, - {2, level_count}, - {3, level_count + 3}}; + cudaq::dimension_map dimensions = {{0, level_count + 1}, + {1, level_count + 2}, + {2, level_count}, + {3, level_count + 3}}; { auto func0 = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { return cudaq::kronecker(utils::momentum_matrix(dimensions[1]), utils::position_matrix(dimensions[0])); }; auto func1 = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { return cudaq::kronecker(utils::momentum_matrix(dimensions[1]), utils::number_matrix(dimensions[0])); diff --git a/unittests/dynamics/rydberg_hamiltonian.cpp b/unittests/operators/rydberg_hamiltonian.cpp similarity index 100% rename from unittests/dynamics/rydberg_hamiltonian.cpp rename to unittests/operators/rydberg_hamiltonian.cpp diff --git a/unittests/dynamics/scalar_operator.cpp b/unittests/operators/scalar_op.cpp similarity index 100% rename from unittests/dynamics/scalar_operator.cpp rename to unittests/operators/scalar_op.cpp diff --git a/unittests/dynamics/spin_operator.cpp b/unittests/operators/spin_op.cpp similarity index 82% rename from unittests/dynamics/spin_operator.cpp rename to unittests/operators/spin_op.cpp index ec8004e9fe6..dd9a1d18c10 100644 --- a/unittests/dynamics/spin_operator.cpp +++ b/unittests/operators/spin_op.cpp @@ -11,14 +11,14 @@ #include TEST(OperatorExpressions, checkSpinOpsUnary) { - auto op = cudaq::sum_op::x(0); + auto op = cudaq::spin_op::x(0); utils::checkEqual((+op).to_matrix(), utils::PauliX_matrix()); utils::checkEqual((-op).to_matrix(), -1.0 * utils::PauliX_matrix()); utils::checkEqual(op.to_matrix(), utils::PauliX_matrix()); } TEST(OperatorExpressions, checkSpinOpsConstruction) { - auto prod = cudaq::sum_op::identity(); + auto prod = cudaq::spin_op::identity(); cudaq::complex_matrix expected(1, 1); expected[{0, 0}] = 1.; @@ -28,36 +28,35 @@ TEST(OperatorExpressions, checkSpinOpsConstruction) { expected[{0, 0}] = std::complex(0., -1.); utils::checkEqual(prod.to_matrix(), expected); - prod *= cudaq::sum_op::x(0); + prod *= cudaq::spin_op::x(0); expected = cudaq::complex_matrix(2, 2); expected[{0, 1}] = std::complex(0., -1.); expected[{1, 0}] = std::complex(0., -1.); utils::checkEqual(prod.to_matrix(), expected); - auto sum = cudaq::sum_op::empty(); + auto sum = cudaq::spin_op::empty(); expected = cudaq::complex_matrix(0, 0); utils::checkEqual(sum.to_matrix(), expected); - sum *= cudaq::sum_op::x( - 1); // empty times something is still empty + sum *= cudaq::spin_op::x(1); // empty times something is still empty std::vector expected_degrees = {}; ASSERT_EQ(sum.degrees(), expected_degrees); utils::checkEqual(sum.to_matrix(), expected); - sum += cudaq::sum_op::i(1); + sum += cudaq::spin_op::i(1); expected = cudaq::complex_matrix(2, 2); for (size_t i = 0; i < 2; ++i) expected[{i, i}] = 1.; utils::checkEqual(sum.to_matrix(), expected); - sum *= cudaq::sum_op::x(1); + sum *= cudaq::spin_op::x(1); expected = cudaq::complex_matrix(2, 2); expected[{0, 1}] = 1.; expected[{1, 0}] = 1.; utils::checkEqual(sum.to_matrix(), expected); - sum = cudaq::sum_op::empty(); - sum -= cudaq::sum_op::i(0); + sum = cudaq::spin_op::empty(); + sum -= cudaq::spin_op::i(0); expected = cudaq::complex_matrix(2, 2); for (size_t i = 0; i < 2; ++i) expected[{i, i}] = -1.; @@ -72,7 +71,7 @@ TEST(OperatorExpressions, checkPreBuiltSpinOps) { // Identity operator. { - auto op = cudaq::sum_op::i(degree_index); + auto op = cudaq::spin_op::i(degree_index); auto got = op.to_matrix(); auto want = utils::id_matrix(2); utils::checkEqual(want, got); @@ -81,7 +80,7 @@ TEST(OperatorExpressions, checkPreBuiltSpinOps) { // Z operator. { - auto op = cudaq::sum_op::z(degree_index); + auto op = cudaq::spin_op::z(degree_index); auto got = op.to_matrix(); auto want = utils::PauliZ_matrix(); utils::checkEqual(want, got); @@ -90,7 +89,7 @@ TEST(OperatorExpressions, checkPreBuiltSpinOps) { // X operator. { - auto op = cudaq::sum_op::x(degree_index); + auto op = cudaq::spin_op::x(degree_index); auto got = op.to_matrix(); auto want = utils::PauliX_matrix(); utils::checkEqual(want, got); @@ -99,7 +98,7 @@ TEST(OperatorExpressions, checkPreBuiltSpinOps) { // Y operator. { - auto op = cudaq::sum_op::y(degree_index); + auto op = cudaq::spin_op::y(degree_index); auto got = op.to_matrix(); auto want = utils::PauliY_matrix(); utils::checkEqual(want, got); @@ -109,11 +108,10 @@ TEST(OperatorExpressions, checkPreBuiltSpinOps) { std::complex onej(0., 1.); // plus operator. { - auto op = cudaq::sum_op::plus(degree_index); - auto composite = - (cudaq::sum_op::x(degree_index) + - onej * cudaq::sum_op::y(degree_index)) / - 2.; + auto op = cudaq::spin_op::plus(degree_index); + auto composite = (cudaq::spin_op::x(degree_index) + + onej * cudaq::spin_op::y(degree_index)) / + 2.; auto composite_mat = 0.5 * utils::PauliX_matrix() + 0.5 * onej * utils::PauliY_matrix(); auto got = op.to_matrix(); @@ -125,11 +123,10 @@ TEST(OperatorExpressions, checkPreBuiltSpinOps) { // minus operator. { - auto op = cudaq::sum_op::minus(degree_index); - auto composite = - (cudaq::sum_op::x(degree_index) - - onej * cudaq::sum_op::y(degree_index)) / - 2.; + auto op = cudaq::spin_op::minus(degree_index); + auto composite = (cudaq::spin_op::x(degree_index) - + onej * cudaq::spin_op::y(degree_index)) / + 2.; auto composite_mat = 0.5 * utils::PauliX_matrix() - 0.5 * onej * utils::PauliY_matrix(); auto got = op.to_matrix(); @@ -145,7 +142,7 @@ TEST(OperatorExpressions, checkSpinOpsWithComplex) { // `spin_handler` + `complex` { - auto elementary = cudaq::sum_op::y(0); + auto elementary = cudaq::spin_op::y(0); auto sum = value + elementary; auto reverse = elementary + value; @@ -163,7 +160,7 @@ TEST(OperatorExpressions, checkSpinOpsWithComplex) { // `spin_handler` - `complex` { - auto elementary = cudaq::sum_op::x(0); + auto elementary = cudaq::spin_op::x(0); auto difference = value - elementary; auto reverse = elementary - value; @@ -181,7 +178,7 @@ TEST(OperatorExpressions, checkSpinOpsWithComplex) { // `spin_handler` * `complex` { - auto elementary = cudaq::sum_op::z(0); + auto elementary = cudaq::spin_op::z(0); auto product = value * elementary; auto reverse = elementary * value; @@ -214,7 +211,7 @@ TEST(OperatorExpressions, checkSpinOpsWithScalars) { // `spin_handler + scalar_operator` { - auto self = cudaq::sum_op::x(0); + auto self = cudaq::spin_op::x(0); auto other = cudaq::scalar_operator(const_scale_factor); auto sum = self + other; @@ -234,7 +231,7 @@ TEST(OperatorExpressions, checkSpinOpsWithScalars) { // `spin_handler + scalar_operator` { - auto self = cudaq::sum_op::y(0); + auto self = cudaq::spin_op::y(0); auto other = cudaq::scalar_operator(function); auto sum = self + other; @@ -255,7 +252,7 @@ TEST(OperatorExpressions, checkSpinOpsWithScalars) { // `spin_handler - scalar_operator` { - auto self = cudaq::sum_op::i(0); + auto self = cudaq::spin_op::i(0); auto other = cudaq::scalar_operator(const_scale_factor); auto sum = self - other; @@ -275,7 +272,7 @@ TEST(OperatorExpressions, checkSpinOpsWithScalars) { // `spin_handler - scalar_operator` { - auto self = cudaq::sum_op::z(0); + auto self = cudaq::spin_op::z(0); auto other = cudaq::scalar_operator(function); auto sum = self - other; @@ -296,7 +293,7 @@ TEST(OperatorExpressions, checkSpinOpsWithScalars) { // `spin_handler * scalar_operator` { - auto self = cudaq::sum_op::y(0); + auto self = cudaq::spin_op::y(0); auto other = cudaq::scalar_operator(const_scale_factor); auto product = self * other; @@ -317,7 +314,7 @@ TEST(OperatorExpressions, checkSpinOpsWithScalars) { // `spin_handler * scalar_operator` { - auto self = cudaq::sum_op::z(0); + auto self = cudaq::spin_op::z(0); auto other = cudaq::scalar_operator(function); auto product = self * other; @@ -342,8 +339,8 @@ TEST(OperatorExpressions, checkSpinOpsSimpleArithmetics) { // Addition, same DOF. { - auto self = cudaq::sum_op::x(0); - auto other = cudaq::sum_op::y(0); + auto self = cudaq::spin_op::x(0); + auto other = cudaq::spin_op::y(0); auto sum = self + other; ASSERT_TRUE(sum.num_terms() == 2); @@ -355,8 +352,8 @@ TEST(OperatorExpressions, checkSpinOpsSimpleArithmetics) { // Addition, different DOF's. { - auto self = cudaq::sum_op::z(0); - auto other = cudaq::sum_op::y(1); + auto self = cudaq::spin_op::z(0); + auto other = cudaq::spin_op::y(1); auto sum = self + other; ASSERT_TRUE(sum.num_terms() == 2); @@ -372,8 +369,8 @@ TEST(OperatorExpressions, checkSpinOpsSimpleArithmetics) { // Subtraction, same DOF. { - auto self = cudaq::sum_op::z(0); - auto other = cudaq::sum_op::x(0); + auto self = cudaq::spin_op::z(0); + auto other = cudaq::spin_op::x(0); auto sum = self - other; ASSERT_TRUE(sum.num_terms() == 2); @@ -385,8 +382,8 @@ TEST(OperatorExpressions, checkSpinOpsSimpleArithmetics) { // Subtraction, different DOF's. { - auto self = cudaq::sum_op::y(0); - auto other = cudaq::sum_op::x(1); + auto self = cudaq::spin_op::y(0); + auto other = cudaq::spin_op::x(1); auto sum = self - other; ASSERT_TRUE(sum.num_terms() == 2); @@ -402,8 +399,8 @@ TEST(OperatorExpressions, checkSpinOpsSimpleArithmetics) { // Multiplication, same DOF. { - auto self = cudaq::sum_op::y(0); - auto other = cudaq::sum_op::z(0); + auto self = cudaq::spin_op::y(0); + auto other = cudaq::spin_op::z(0); auto product = self * other; ASSERT_TRUE(product.num_ops() == 1); @@ -418,8 +415,8 @@ TEST(OperatorExpressions, checkSpinOpsSimpleArithmetics) { // Multiplication, different DOF's. { - auto self = cudaq::sum_op::x(0); - auto other = cudaq::sum_op::z(1); + auto self = cudaq::spin_op::x(0); + auto other = cudaq::spin_op::z(1); auto product = self * other; ASSERT_TRUE(product.num_ops() == 2); @@ -444,9 +441,8 @@ TEST(OperatorExpressions, checkSpinOpsAdvancedArithmetics) { // `spin_handler + sum_op` { - auto self = cudaq::sum_op::y(2); - auto sum_op = cudaq::sum_op::y(2) + - cudaq::sum_op::x(1); + auto self = cudaq::spin_op::y(2); + auto sum_op = cudaq::spin_op::y(2) + cudaq::spin_op::x(1); auto got = self + sum_op; auto reverse = sum_op + self; @@ -471,9 +467,8 @@ TEST(OperatorExpressions, checkSpinOpsAdvancedArithmetics) { // `spin_handler - sum_op` { - auto self = cudaq::sum_op::i(0); - auto sum_op = cudaq::sum_op::x(0) + - cudaq::sum_op::z(1); + auto self = cudaq::spin_op::i(0); + auto sum_op = cudaq::spin_op::x(0) + cudaq::spin_op::z(1); auto got = self - sum_op; auto reverse = sum_op - self; @@ -497,9 +492,8 @@ TEST(OperatorExpressions, checkSpinOpsAdvancedArithmetics) { // `spin_handler * sum_op` { - auto self = cudaq::sum_op::y(0); - auto sum_op = cudaq::sum_op::x(0) + - cudaq::sum_op::y(2); + auto self = cudaq::spin_op::y(0); + auto sum_op = cudaq::spin_op::x(0) + cudaq::spin_op::y(2); auto got = self * sum_op; auto reverse = sum_op * self; @@ -529,9 +523,8 @@ TEST(OperatorExpressions, checkSpinOpsAdvancedArithmetics) { // `sum_op += spin_handler` { - auto sum_op = cudaq::sum_op::z(0) + - cudaq::sum_op::x(2); - sum_op += cudaq::sum_op::y(0); + auto sum_op = cudaq::spin_op::z(0) + cudaq::spin_op::x(2); + sum_op += cudaq::spin_op::y(0); ASSERT_TRUE(sum_op.num_terms() == 3); @@ -549,9 +542,8 @@ TEST(OperatorExpressions, checkSpinOpsAdvancedArithmetics) { // `sum_op -= spin_handler` { - auto sum_op = cudaq::sum_op::x(0) + - cudaq::sum_op::i(1); - sum_op -= cudaq::sum_op::x(0); + auto sum_op = cudaq::spin_op::x(0) + cudaq::spin_op::i(1); + sum_op -= cudaq::spin_op::x(0); ASSERT_TRUE(sum_op.num_terms() == 2); @@ -569,9 +561,8 @@ TEST(OperatorExpressions, checkSpinOpsAdvancedArithmetics) { // `sum_op *= spin_handler` { - auto self = cudaq::sum_op::i(0); - auto sum_op = cudaq::sum_op::y(0) + - cudaq::sum_op::z(1); + auto self = cudaq::spin_op::i(0); + auto sum_op = cudaq::spin_op::y(0) + cudaq::spin_op::z(1); sum_op *= self; @@ -593,8 +584,8 @@ TEST(OperatorExpressions, checkSpinOpsAdvancedArithmetics) { } TEST(OperatorExpressions, checkSpinOpsDegreeVerification) { - auto op1 = cudaq::sum_op::z(1); - auto op2 = cudaq::sum_op::x(0); + auto op1 = cudaq::spin_op::z(1); + auto op2 = cudaq::spin_op::x(0); std::map dimensions = {{0, 1}, {1, 3}}; ASSERT_ANY_THROW(op1.to_matrix({{1, 3}})); diff --git a/unittests/dynamics/operator_sum.cpp b/unittests/operators/sum_op.cpp similarity index 76% rename from unittests/dynamics/operator_sum.cpp rename to unittests/operators/sum_op.cpp index 08deee04f3f..e8eaede9569 100644 --- a/unittests/dynamics/operator_sum.cpp +++ b/unittests/operators/sum_op.cpp @@ -11,22 +11,135 @@ #include TEST(OperatorExpressions, checkOperatorSumBasics) { - std::vector levels = {2, 3, 4}; + // testing some utility functions + { + cudaq::spin_op previous; + cudaq::spin_op expected; + std::vector all_degrees = {0, 1, 2, 3}; + for (auto id_target : all_degrees) { + cudaq::spin_op_term op; + cudaq::spin_op_term expected_term; + for (std::size_t target : all_degrees) { + if (target == id_target) + op *= cudaq::spin_op::i(target); + else if (target & 2) { + op *= cudaq::spin_op::z(target); + expected_term *= cudaq::spin_op::z(target); + } else if (target & 1) { + op *= cudaq::spin_op::x(target); + expected_term *= cudaq::spin_op::x(target); + } else { + op *= cudaq::spin_op::y(target); + expected_term *= cudaq::spin_op::y(target); + } + } + previous += op; + expected += expected_term; + auto got = previous; + + ASSERT_NE(got, expected); + got.canonicalize(); + ASSERT_EQ(got, expected); + ASSERT_EQ(got.degrees(), expected.degrees()); + ASSERT_EQ(got.to_matrix(), expected.to_matrix()); + + auto check_expansion = + [&got, &all_degrees](const std::set &want_degrees) { + auto canon = got; + auto term_with_missing_degrees = false; + for (const auto &term : canon) + if (term.degrees() != all_degrees) + term_with_missing_degrees = true; + ASSERT_TRUE(term_with_missing_degrees); + canon.canonicalize(want_degrees); + ASSERT_EQ(canon.degrees(), all_degrees); + for (const auto &term : canon) + ASSERT_EQ(term.degrees(), all_degrees); + }; + + check_expansion( + std::set(all_degrees.begin(), all_degrees.end())); + if (id_target > 0) + // for id_target = 0, there is only one term in the sum, which does not + // act on a degrees + check_expansion({}); + auto have_degrees = got.degrees(); + ASSERT_ANY_THROW(got.canonicalize( + std::set(have_degrees.begin() + 1, have_degrees.end()))); + } + } + { + srand(10); + for (auto rep = 0; rep < 10; ++rep) { + auto bit_mask = rand(); + cudaq::sum_op expected; + std::vector degrees; + for (std::size_t d = 1; d <= 10; ++d) + degrees.push_back(d); + + std::vector> terms; + for (auto d : degrees) { + auto coeff = (bit_mask >> d) & 1 ? 1.0 : 0.0; + auto prod = coeff * cudaq::spin_op::identity(d); + if (coeff > 0.) + expected += prod; + // randomize the order in which we add terms + auto rnd_idx = (rand() % d); + terms.insert(terms.begin() + rnd_idx, std::move(prod)); + } + + cudaq::sum_op orig; + for (auto &&term : terms) + orig += term; + + ASSERT_EQ(orig.num_terms(), degrees.size()); + ASSERT_EQ(orig.degrees(), degrees); + + std::cout << "original: " << std::endl; + std::cout << orig.to_string() << std::endl; + orig.trim(); + std::cout << "trimmed: " << std::endl; + std::cout << orig.to_string() << std::endl; + std::cout << "expected: " << std::endl; + std::cout << expected.to_string() << std::endl; + + ASSERT_NE(orig.num_terms(), degrees.size()); + ASSERT_EQ(orig.num_terms(), expected.num_terms()); + ASSERT_EQ(orig.degrees(), expected.degrees()); + ASSERT_EQ(orig.to_sparse_matrix(), expected.to_sparse_matrix()); + + // check that our term map seems accurate + for (const auto &term : expected) + orig += term.degrees()[0] * 1.0 * term; + ASSERT_EQ(orig.num_terms(), expected.num_terms()); + ASSERT_EQ(orig.degrees(), expected.degrees()); + for (const auto &term : orig) { + auto got_coeff = term.evaluate_coefficient(); + auto want_coeff = std::complex(term.degrees()[0] + 1.); + ASSERT_EQ(got_coeff.real(), want_coeff.real()); + ASSERT_EQ(got_coeff.imag(), want_coeff.imag()); + } + } + } + + std::vector levels = {2, 3, 4}; 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; {// Same degrees of freedom. - {auto spin0 = cudaq::sum_op::x(5); - auto spin1 = cudaq::sum_op::z(5); + {auto spin0 = cudaq::spin_op::x(5); + auto spin1 = cudaq::spin_op::z(5); auto spin_sum = spin0 + spin1; std::vector want_degrees = {5}; auto spin_matrix = utils::PauliX_matrix() + utils::PauliZ_matrix(); ASSERT_TRUE(spin_sum.degrees() == want_degrees); + ASSERT_EQ(spin_sum.min_degree(), 5); + ASSERT_EQ(spin_sum.max_degree(), 5); utils::checkEqual(spin_matrix, spin_sum.to_matrix()); for (auto level_count : levels) { @@ -35,6 +148,8 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { auto sum = op0 + op1; ASSERT_TRUE(sum.degrees() == want_degrees); + ASSERT_EQ(sum.min_degree(), 5); + ASSERT_EQ(sum.max_degree(), 5); auto got_matrix = sum.to_matrix({{5, level_count}}); auto matrix0 = utils::number_matrix(level_count); @@ -46,8 +161,8 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { // Different degrees of freedom. { - auto spin0 = cudaq::sum_op::x(0); - auto spin1 = cudaq::sum_op::z(1); + auto spin0 = cudaq::spin_op::x(0); + auto spin1 = cudaq::spin_op::z(1); auto spin_sum = spin0 + spin1; std::vector want_degrees = {0, 1}; @@ -56,6 +171,8 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { cudaq::kronecker(utils::PauliZ_matrix(), utils::id_matrix(2)); ASSERT_TRUE(spin_sum.degrees() == want_degrees); + ASSERT_EQ(spin_sum.min_degree(), 0); + ASSERT_EQ(spin_sum.max_degree(), 1); utils::checkEqual(spin_matrix, spin_sum.to_matrix()); for (auto level_count : levels) { @@ -67,6 +184,8 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { ASSERT_TRUE(got.degrees() == want_degrees); ASSERT_TRUE(got_reverse.degrees() == want_degrees); + ASSERT_EQ(got.min_degree(), 0); + ASSERT_EQ(got.max_degree(), 1); auto got_matrix = got.to_matrix({{0, level_count}, {1, level_count}}); auto got_matrix_reverse = @@ -88,8 +207,8 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { // Different degrees of freedom, non-consecutive. // Should produce the same matrices as the above test. { - auto spin0 = cudaq::sum_op::x(0); - auto spin1 = cudaq::sum_op::z(2); + auto spin0 = cudaq::spin_op::x(0); + auto spin1 = cudaq::spin_op::z(2); auto spin_sum = spin0 + spin1; std::vector want_degrees = {0, 2}; @@ -98,6 +217,8 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { cudaq::kronecker(utils::PauliZ_matrix(), utils::id_matrix(2)); ASSERT_TRUE(spin_sum.degrees() == want_degrees); + ASSERT_EQ(spin_sum.min_degree(), 0); + ASSERT_EQ(spin_sum.max_degree(), 2); utils::checkEqual(spin_matrix, spin_sum.to_matrix()); for (auto level_count : levels) { @@ -109,6 +230,8 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { ASSERT_TRUE(got.degrees() == want_degrees); ASSERT_TRUE(got_reverse.degrees() == want_degrees); + ASSERT_EQ(got.min_degree(), 0); + ASSERT_EQ(got.max_degree(), 2); auto got_matrix = got.to_matrix({{0, level_count}, {2, level_count}}); auto got_matrix_reverse = @@ -130,15 +253,15 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { // Different degrees of freedom, non-consecutive but all dimensions // provided. { - auto spin0 = cudaq::sum_op::x(0); - auto spin1 = cudaq::sum_op::z(2); + auto spin0 = cudaq::spin_op::x(0); + auto spin1 = cudaq::spin_op::z(2); auto spin_sum = spin0 + spin1; std::vector want_degrees = {0, 2}; auto spin_matrix = cudaq::kronecker(utils::id_matrix(2), utils::PauliX_matrix()) + cudaq::kronecker(utils::PauliZ_matrix(), utils::id_matrix(2)); - std::unordered_map dimensions = {{0, 2}, {1, 2}, {2, 2}}; + cudaq::dimension_map dimensions = {{0, 2}, {1, 2}, {2, 2}}; ASSERT_TRUE(spin_sum.degrees() == want_degrees); utils::checkEqual(spin_matrix, spin_sum.to_matrix(dimensions)); @@ -204,7 +327,7 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { // spin operator against constant { - auto op = cudaq::sum_op::x(0); + auto op = cudaq::spin_op::x(0); auto scalar_op = cudaq::scalar_operator(value_0); auto sum = scalar_op + op; auto reverse = op + scalar_op; @@ -241,7 +364,7 @@ TEST(OperatorExpressions, checkOperatorSumBasics) { // spin operator against constant from lambda { - auto op = cudaq::sum_op::x(1); + auto op = cudaq::spin_op::x(1); auto scalar_op = cudaq::scalar_operator(function); auto sum = scalar_op + op; auto reverse = op + scalar_op; @@ -321,8 +444,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { // `spin sum + std::complex` { - auto original = cudaq::sum_op::x(1) + - cudaq::sum_op::y(2); + auto original = cudaq::spin_op::x(1) + cudaq::spin_op::y(2); auto sum = original + value; auto reverse = value + original; @@ -404,8 +526,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { // `spin sum - double` { - auto original = cudaq::sum_op::x(1) + - cudaq::sum_op::z(2); + auto original = cudaq::spin_op::x(1) + cudaq::spin_op::z(2); auto difference = original - double_value; auto reverse = double_value - original; @@ -499,13 +620,13 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { for (const auto &term : product) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == + ASSERT_TRUE(term.evaluate_coefficient() == std::complex(double_value)); } for (const auto &term : reverse) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == + ASSERT_TRUE(term.evaluate_coefficient() == std::complex(double_value)); } @@ -538,12 +659,12 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { for (const auto &term : product) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == value); + ASSERT_TRUE(term.evaluate_coefficient() == value); } for (const auto &term : reverse) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == value); + ASSERT_TRUE(term.evaluate_coefficient() == value); } auto got_matrix = @@ -575,12 +696,12 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { for (const auto &term : product) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == value); + ASSERT_TRUE(term.evaluate_coefficient() == value); } for (const auto &term : reverse) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == value); + ASSERT_TRUE(term.evaluate_coefficient() == value); } auto got_matrix = @@ -604,8 +725,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { // `spin sum * scalar_operator` { - auto sum = cudaq::sum_op::i(1) + - cudaq::sum_op::y(2); + auto sum = cudaq::spin_op::i(1) + cudaq::spin_op::y(2); auto product = sum * cudaq::scalar_operator(value); auto reverse = cudaq::scalar_operator(value) * sum; @@ -615,12 +735,12 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { for (const auto &term : product) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == value); + ASSERT_TRUE(term.evaluate_coefficient() == value); } for (const auto &term : reverse) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == value); + ASSERT_TRUE(term.evaluate_coefficient() == value); } auto got_matrix = product.to_matrix(); @@ -647,7 +767,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { auto expected_coeff = std::complex(1. / double_value); for (const auto &term : product) { ASSERT_TRUE(term.num_ops() == 1); - auto coeff = term.get_coefficient().evaluate(); + auto coeff = term.evaluate_coefficient(); EXPECT_NEAR(coeff.real(), expected_coeff.real(), 1e-8); EXPECT_NEAR(coeff.imag(), expected_coeff.imag(), 1e-8); } @@ -677,7 +797,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { auto expected_coeff = std::complex(1. / value); for (const auto &term : product) { ASSERT_TRUE(term.num_ops() == 1); - auto coeff = term.get_coefficient().evaluate(); + auto coeff = term.evaluate_coefficient(); EXPECT_NEAR(coeff.real(), expected_coeff.real(), 1e-8); EXPECT_NEAR(coeff.imag(), expected_coeff.imag(), 1e-8); } @@ -707,7 +827,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { auto expected_coeff = std::complex(1. / value); for (const auto &term : product) { ASSERT_TRUE(term.num_ops() == 1); - auto coeff = term.get_coefficient().evaluate(); + auto coeff = term.evaluate_coefficient(); EXPECT_NEAR(coeff.real(), expected_coeff.real(), 1e-8); EXPECT_NEAR(coeff.imag(), expected_coeff.imag(), 1e-8); } @@ -730,8 +850,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { // `spin sum / scalar_operator` { - auto sum = cudaq::sum_op::i(1) + - cudaq::sum_op::y(2); + auto sum = cudaq::spin_op::i(1) + cudaq::spin_op::y(2); auto product = sum / cudaq::scalar_operator(value); @@ -740,7 +859,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { auto expected_coeff = std::complex(1. / value); for (const auto &term : product) { ASSERT_TRUE(term.num_ops() == 1); - auto coeff = term.get_coefficient().evaluate(); + auto coeff = term.evaluate_coefficient(); EXPECT_NEAR(coeff.real(), expected_coeff.real(), 1e-8); EXPECT_NEAR(coeff.imag(), expected_coeff.imag(), 1e-8); } @@ -779,8 +898,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { // `spin sum += double` { - auto sum = cudaq::sum_op::y(1) + - cudaq::sum_op::y(2); + auto sum = cudaq::spin_op::y(1) + cudaq::spin_op::y(2); sum += double_value; ASSERT_TRUE(sum.num_terms() == 3); @@ -913,8 +1031,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { // `spin sum -= scalar_operator` { - auto sum = cudaq::sum_op::z(1) + - cudaq::sum_op::y(2); + auto sum = cudaq::spin_op::z(1) + cudaq::spin_op::y(2); sum -= cudaq::scalar_operator(value); ASSERT_TRUE(sum.num_terms() == 3); @@ -942,7 +1059,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { ASSERT_TRUE(sum.num_terms() == 2); for (const auto &term : sum) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == + ASSERT_TRUE(term.evaluate_coefficient() == std::complex(double_value)); } @@ -964,15 +1081,14 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { // `spin sum *= double` { - auto sum = cudaq::sum_op::y(1) + - cudaq::sum_op::i(2); + auto sum = cudaq::spin_op::y(1) + cudaq::spin_op::i(2); sum *= double_value; ASSERT_TRUE(sum.num_terms() == 2); for (const auto &term : sum) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == + ASSERT_TRUE(term.evaluate_coefficient() == std::complex(double_value)); } @@ -995,7 +1111,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { ASSERT_TRUE(sum.num_terms() == 2); for (const auto &term : sum) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == value); + ASSERT_TRUE(term.evaluate_coefficient() == value); } auto got_matrix = sum.to_matrix({{1, level_count}, {2, level_count + 1}}, @@ -1021,7 +1137,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { ASSERT_TRUE(sum.num_terms() == 2); for (const auto &term : sum) { ASSERT_TRUE(term.num_ops() == 1); - ASSERT_TRUE(term.get_coefficient().evaluate() == value); + ASSERT_TRUE(term.evaluate_coefficient() == value); } auto got_matrix = sum.to_matrix( @@ -1050,7 +1166,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { auto expected_coeff = std::complex(1. / double_value); for (const auto &term : sum) { ASSERT_TRUE(term.num_ops() == 1); - auto coeff = term.get_coefficient().evaluate(); + auto coeff = term.evaluate_coefficient(); EXPECT_NEAR(coeff.real(), expected_coeff.real(), 1e-8); EXPECT_NEAR(coeff.imag(), expected_coeff.imag(), 1e-8); } @@ -1073,8 +1189,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { // `spin sum /= double` { - auto sum = cudaq::sum_op::y(1) + - cudaq::sum_op::i(2); + auto sum = cudaq::spin_op::y(1) + cudaq::spin_op::i(2); sum /= double_value; @@ -1082,7 +1197,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { auto expected_coeff = std::complex(1. / double_value); for (const auto &term : sum) { ASSERT_TRUE(term.num_ops() == 1); - auto coeff = term.get_coefficient().evaluate(); + auto coeff = term.evaluate_coefficient(); EXPECT_NEAR(coeff.real(), expected_coeff.real(), 1e-8); EXPECT_NEAR(coeff.imag(), expected_coeff.imag(), 1e-8); } @@ -1107,7 +1222,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { auto expected_coeff = std::complex(1. / value); for (const auto &term : sum) { ASSERT_TRUE(term.num_ops() == 1); - auto coeff = term.get_coefficient().evaluate(); + auto coeff = term.evaluate_coefficient(); EXPECT_NEAR(coeff.real(), expected_coeff.real(), 1e-8); EXPECT_NEAR(coeff.imag(), expected_coeff.imag(), 1e-8); } @@ -1136,7 +1251,7 @@ TEST(OperatorExpressions, checkOperatorSumAgainstScalars) { auto expected_coeff = std::complex(1. / value); for (const auto &term : sum) { ASSERT_TRUE(term.num_ops() == 1); - auto coeff = term.get_coefficient().evaluate(); + auto coeff = term.evaluate_coefficient(); EXPECT_NEAR(coeff.real(), expected_coeff.real(), 1e-8); EXPECT_NEAR(coeff.imag(), expected_coeff.imag(), 1e-8); } @@ -1505,20 +1620,20 @@ TEST(OperatorExpressions, checkOperatorSumAgainstOperatorSum) { TEST(OperatorExpressions, checkCustomOperatorSum) { auto level_count = 2; - std::unordered_map dimensions = {{0, level_count + 1}, - {1, level_count + 2}, - {2, level_count}, - {3, level_count + 3}}; + cudaq::dimension_map dimensions = {{0, level_count + 1}, + {1, level_count + 2}, + {2, level_count}, + {3, level_count + 3}}; { auto func0 = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { return cudaq::kronecker(utils::momentum_matrix(dimensions[1]), utils::position_matrix(dimensions[0])); }; auto func1 = - [](const std::vector &dimensions, + [](const std::vector &dimensions, const std::unordered_map> &_none) { return cudaq::kronecker(utils::parity_matrix(dimensions[1]), utils::number_matrix(dimensions[0])); @@ -1581,3 +1696,323 @@ TEST(OperatorExpressions, checkCustomOperatorSum) { utils::checkEqual(difference_reverse.to_matrix(dimensions), diff_reverse_expected); } + +TEST(OperatorExpressions, checkDefaultValue) { + cudaq::dimension_map dims = {{0, 2}}; + cudaq::complex_matrix empty; + auto matrix_term = cudaq::matrix_op::identity(0); + auto boson_term = cudaq::boson_op::number(0); + auto matrix_sum = cudaq::matrix_op::number(0) + matrix_term; + auto boson_sum = cudaq::boson_op::number(0) + boson_term; + + auto matrix_default = cudaq::sum_op(); + auto matrix_empty = cudaq::sum_op::empty(); + auto boson_empty = cudaq::sum_op::empty(); + + // matrix default + matrix sum + { + cudaq::sum_op sum_default; + auto res1 = sum_default + matrix_sum; + auto res2 = matrix_sum + sum_default; + utils::checkEqual(res1.to_matrix(dims), matrix_sum.to_matrix(dims)); + utils::checkEqual(res2.to_matrix(dims), matrix_sum.to_matrix(dims)); + } + // matrix default - matrix sum + { + cudaq::sum_op sum_default; + auto res1 = sum_default - matrix_sum; + auto res2 = matrix_sum - sum_default; + utils::checkEqual(res1.to_matrix(dims), (-matrix_sum).to_matrix(dims)); + utils::checkEqual(res2.to_matrix(dims), matrix_sum.to_matrix(dims)); + } + // matrix default * matrix sum + { + cudaq::sum_op sum_default; + auto res1 = sum_default * matrix_sum; + auto res2 = matrix_sum * sum_default; + utils::checkEqual(res1.to_matrix(dims), matrix_sum.to_matrix(dims)); + utils::checkEqual(res2.to_matrix(dims), matrix_sum.to_matrix(dims)); + } + // matrix default += matrix sum + { + cudaq::sum_op sum_default; + sum_default += matrix_sum; + auto res = matrix_sum; + res += matrix_default; + utils::checkEqual(sum_default.to_matrix(dims), matrix_sum.to_matrix(dims)); + utils::checkEqual(res.to_matrix(dims), matrix_sum.to_matrix(dims)); + } + // matrix default -= matrix sum + { + cudaq::sum_op sum_default; + sum_default -= matrix_sum; + auto res = matrix_sum; + res -= matrix_default; + utils::checkEqual(sum_default.to_matrix(dims), + (-matrix_sum).to_matrix(dims)); + utils::checkEqual(res.to_matrix(dims), matrix_sum.to_matrix(dims)); + } + // matrix default *= matrix sum + { + cudaq::sum_op sum_default; + sum_default *= matrix_sum; + auto res = matrix_sum; + res *= matrix_default; + utils::checkEqual(sum_default.to_matrix(dims), matrix_sum.to_matrix(dims)); + utils::checkEqual(res.to_matrix(dims), matrix_sum.to_matrix(dims)); + } + + // matrix default + boson sum + { + cudaq::sum_op sum_default; + auto res = sum_default + boson_sum; + utils::checkEqual(res.to_matrix(dims), boson_sum.to_matrix(dims)); + } + // matrix default - boson sum + { + cudaq::sum_op sum_default; + auto res = sum_default - boson_sum; + utils::checkEqual(res.to_matrix(dims), (-boson_sum).to_matrix(dims)); + } + // matrix default * boson sum + { + cudaq::sum_op sum_default; + auto res = sum_default * boson_sum; + utils::checkEqual(res.to_matrix(dims), boson_sum.to_matrix(dims)); + } + // matrix default += boson sum + { + cudaq::sum_op sum_default; + sum_default += boson_sum; + utils::checkEqual(sum_default.to_matrix(dims), boson_sum.to_matrix(dims)); + } + // matrix default -= boson sum + { + cudaq::sum_op sum_default; + sum_default -= boson_sum; + utils::checkEqual(sum_default.to_matrix(dims), + (-boson_sum).to_matrix(dims)); + } + // matrix default *= boson sum + { + cudaq::sum_op sum_default; + sum_default *= boson_sum; + utils::checkEqual(sum_default.to_matrix(dims), boson_sum.to_matrix(dims)); + } + + // matrix default + matrix term + { + cudaq::sum_op sum_default; + auto res = sum_default + matrix_term; + utils::checkEqual(res.to_matrix(dims), matrix_term.to_matrix(dims)); + } + // matrix default - matrix term + { + cudaq::sum_op sum_default; + auto res = sum_default - matrix_term; + utils::checkEqual(res.to_matrix(dims), (-matrix_term).to_matrix(dims)); + } + // matrix default * matrix term + { + cudaq::sum_op sum_default; + auto res = sum_default * matrix_term; + utils::checkEqual(res.to_matrix(dims), matrix_term.to_matrix(dims)); + } + // matrix default += matrix term + { + cudaq::sum_op sum_default; + sum_default += matrix_term; + utils::checkEqual(sum_default.to_matrix(dims), matrix_term.to_matrix(dims)); + } + // matrix default -= matrix term + { + cudaq::sum_op sum_default; + sum_default -= matrix_term; + utils::checkEqual(sum_default.to_matrix(dims), + (-matrix_term).to_matrix(dims)); + } + // matrix default *= matrix term + { + cudaq::sum_op sum_default; + sum_default *= matrix_term; + utils::checkEqual(sum_default.to_matrix(dims), matrix_term.to_matrix(dims)); + } + + // matrix default + boson term + { + cudaq::sum_op sum_default; + auto res = sum_default + boson_term; + utils::checkEqual(res.to_matrix(dims), boson_term.to_matrix(dims)); + } + // matrix default - boson term + { + cudaq::sum_op sum_default; + auto res = sum_default - boson_term; + utils::checkEqual(res.to_matrix(dims), (-boson_term).to_matrix(dims)); + } + // matrix default * boson term + { + cudaq::sum_op sum_default; + auto res = sum_default * boson_term; + utils::checkEqual(res.to_matrix(dims), boson_term.to_matrix(dims)); + } + // matrix default += boson term + { + cudaq::sum_op sum_default; + sum_default += boson_term; + utils::checkEqual(sum_default.to_matrix(dims), boson_term.to_matrix(dims)); + } + // matrix default -= boson term + { + cudaq::sum_op sum_default; + sum_default -= boson_term; + utils::checkEqual(sum_default.to_matrix(dims), + (-boson_term).to_matrix(dims)); + } + // matrix default *= boson term + { + cudaq::sum_op sum_default; + sum_default *= boson_term; + utils::checkEqual(sum_default.to_matrix(dims), boson_term.to_matrix(dims)); + } + + // matrix default + matrix empty + { + cudaq::sum_op sum_default; + auto res1 = sum_default + matrix_empty; + auto res2 = res1 * matrix_term; + auto res3 = matrix_empty + sum_default; + auto res4 = res3 * matrix_term; + utils::checkEqual(res1.to_matrix(dims), empty); + utils::checkEqual(res2.to_matrix(dims), empty); + utils::checkEqual(res3.to_matrix(dims), empty); + utils::checkEqual(res4.to_matrix(dims), empty); + } + // matrix default - matrix empty + { + cudaq::sum_op sum_default; + auto res1 = sum_default - matrix_empty; + auto res2 = res1 * matrix_term; + auto res3 = matrix_empty - sum_default; + auto res4 = res3 * matrix_term; + utils::checkEqual(res1.to_matrix(dims), empty); + utils::checkEqual(res2.to_matrix(dims), empty); + utils::checkEqual(res3.to_matrix(dims), empty); + utils::checkEqual(res4.to_matrix(dims), empty); + } + // matrix default * matrix empty + { + cudaq::sum_op sum_default; + auto res1 = sum_default * matrix_empty; + auto res2 = res1 * matrix_term; + auto res3 = matrix_empty * sum_default; + auto res4 = res3 * matrix_term; + utils::checkEqual(res1.to_matrix(dims), empty); + utils::checkEqual(res2.to_matrix(dims), empty); + utils::checkEqual(res3.to_matrix(dims), empty); + utils::checkEqual(res4.to_matrix(dims), empty); + } + // matrix default += matrix empty + { + cudaq::sum_op sum_default; + auto res1 = sum_default; + res1 += matrix_empty; + auto res2 = res1 * matrix_term; + auto res3 = matrix_empty; + res3 += sum_default; + auto res4 = res3 * matrix_term; + utils::checkEqual(res1.to_matrix(dims), empty); + utils::checkEqual(res2.to_matrix(dims), empty); + utils::checkEqual(res3.to_matrix(dims), empty); + utils::checkEqual(res4.to_matrix(dims), empty); + } + // matrix default -= matrix empty + { + cudaq::sum_op sum_default; + auto res1 = sum_default; + res1 -= matrix_empty; + auto res2 = res1 * matrix_term; + auto res3 = matrix_empty; + res3 -= sum_default; + auto res4 = res3 * matrix_term; + utils::checkEqual(res1.to_matrix(dims), empty); + utils::checkEqual(res2.to_matrix(dims), empty); + utils::checkEqual(res3.to_matrix(dims), empty); + utils::checkEqual(res4.to_matrix(dims), empty); + } + // matrix default *= matrix empty + { + cudaq::sum_op sum_default; + auto res1 = sum_default; + res1 *= matrix_empty; + auto res2 = res1 * matrix_term; + auto res3 = matrix_empty; + res3 *= sum_default; + auto res4 = res3 * matrix_term; + utils::checkEqual(res1.to_matrix(dims), empty); + utils::checkEqual(res2.to_matrix(dims), empty); + utils::checkEqual(res3.to_matrix(dims), empty); + utils::checkEqual(res4.to_matrix(dims), empty); + } + + auto scalar_val = 5.; + std::complex minus_one = -1.; + cudaq::complex_matrix scalar_mat(1, 1); + scalar_mat[{0, 0}] = scalar_val; + + // matrix default + scalar + { + cudaq::sum_op sum_default; + auto res1 = sum_default + scalar_val; + auto res2 = scalar_val + sum_default; + utils::checkEqual(res1.to_matrix(dims), scalar_mat); + utils::checkEqual(res2.to_matrix(dims), scalar_mat); + } + // matrix default - scalar + { + cudaq::sum_op sum_default; + auto res1 = sum_default - scalar_val; + auto res2 = scalar_val - sum_default; + utils::checkEqual(res1.to_matrix(dims), minus_one * scalar_mat); + utils::checkEqual(res2.to_matrix(dims), scalar_mat); + } + // matrix default * scalar + { + cudaq::sum_op sum_default; + auto res1 = sum_default * scalar_val; + auto res2 = scalar_val * sum_default; + utils::checkEqual(res1.to_matrix(dims), scalar_mat); + utils::checkEqual(res2.to_matrix(dims), scalar_mat); + } + // matrix default += scalar + { + cudaq::sum_op sum_default; + auto res1 = sum_default; + res1 += scalar_val; + utils::checkEqual(res1.to_matrix(dims), scalar_mat); + } + // matrix default -= scalar + { + cudaq::sum_op sum_default; + auto res1 = sum_default; + res1 -= scalar_val; + utils::checkEqual(res1.to_matrix(dims), minus_one * scalar_mat); + } + // matrix default *= scalar + { + cudaq::sum_op sum_default; + auto res1 = sum_default; + res1 *= scalar_val; + utils::checkEqual(res1.to_matrix(dims), scalar_mat); + } + + // error cases + { + cudaq::sum_op sum_default; + utils::checkEqual((+sum_default).to_matrix(dims), empty); + + ASSERT_ANY_THROW(-sum_default); + ASSERT_ANY_THROW(sum_default / scalar_val); + ASSERT_ANY_THROW(sum_default /= scalar_val); + } +} \ No newline at end of file diff --git a/unittests/dynamics/utils.cpp b/unittests/operators/utils.cpp similarity index 98% rename from unittests/dynamics/utils.cpp rename to unittests/operators/utils.cpp index e78fd91084b..3b7a9de17b9 100644 --- a/unittests/dynamics/utils.cpp +++ b/unittests/operators/utils.cpp @@ -29,7 +29,7 @@ void assert_product_equal( const std::vector &expected_terms) { cudaq::sum_op sum = got; ASSERT_TRUE(sum.num_terms() == 1); - ASSERT_TRUE(got.get_coefficient().evaluate() == expected_coefficient); + ASSERT_TRUE(got.evaluate_coefficient() == expected_coefficient); std::size_t idx = 0; for (const auto &op : got) ASSERT_EQ(op, expected_terms[idx++]); diff --git a/unittests/dynamics/utils.h b/unittests/operators/utils.h similarity index 100% rename from unittests/dynamics/utils.h rename to unittests/operators/utils.h diff --git a/unittests/qis/QubitQISTester.cpp b/unittests/qis/QubitQISTester.cpp index e11110472ba..dd39fe25f2f 100644 --- a/unittests/qis/QubitQISTester.cpp +++ b/unittests/qis/QubitQISTester.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #ifndef CUDAQ_BACKEND_DM CUDAQ_TEST(QubitQISTester, checkAllocateDeallocateSubRegister) { @@ -150,10 +150,10 @@ CUDAQ_TEST(QubitQISTester, checkCommonKernel) { x(q[1], q[0]); }; - using namespace cudaq::spin; - - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); + cudaq::spin_op h = + 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto energy = cudaq::observe(ansatz, h, .59); EXPECT_NEAR(energy, -1.7487, 1e-3); #endif diff --git a/unittests/qudit/simple_qudit/CMakeLists.txt b/unittests/qudit/simple_qudit/CMakeLists.txt index f7b9dad6e3d..57ac38824e7 100644 --- a/unittests/qudit/simple_qudit/CMakeLists.txt +++ b/unittests/qudit/simple_qudit/CMakeLists.txt @@ -17,6 +17,6 @@ target_include_directories(${LIBRARY_NAME} $) target_link_libraries(${LIBRARY_NAME} - PUBLIC cudaq-spin PRIVATE cudaq-common fmt::fmt-header-only libqpp) + PUBLIC cudaq-operator PRIVATE cudaq-common fmt::fmt-header-only libqpp) install(TARGETS ${LIBRARY_NAME} DESTINATION lib) diff --git a/unittests/qudit/simple_qudit/SimpleQuditExecutionManager.cpp b/unittests/qudit/simple_qudit/SimpleQuditExecutionManager.cpp index 78f74567ff8..bc86912963d 100644 --- a/unittests/qudit/simple_qudit/SimpleQuditExecutionManager.cpp +++ b/unittests/qudit/simple_qudit/SimpleQuditExecutionManager.cpp @@ -9,9 +9,9 @@ #include "common/ExecutionContext.h" #include "common/Logger.h" +#include "cudaq/operators.h" #include "cudaq/qis/managers/BasicExecutionManager.h" #include "cudaq/qis/qudit.h" -#include "cudaq/spin_op.h" #include "cudaq/utils/cudaq_utils.h" #include "qpp.h" #include diff --git a/unittests/spin_op/SpinOpTester.cpp b/unittests/spin_op/SpinOpTester.cpp index a9a401a8a47..e5f698800c0 100644 --- a/unittests/spin_op/SpinOpTester.cpp +++ b/unittests/spin_op/SpinOpTester.cpp @@ -6,10 +6,9 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ +#include "cudaq/operators.h" #include -#include "cudaq/spin_op.h" - enum Pauli : int8_t { I = 0, X, Y, Z }; constexpr Pauli paulis[4] = {Pauli::I, Pauli::X, Pauli::Y, Pauli::Z}; @@ -68,24 +67,24 @@ static std::string generate_pauli_string(const std::vector &word) { return result; } -static cudaq::spin_op generate_cudaq_spin(int64_t id, int64_t num_qubits, - bool addI = true) { +static cudaq::spin_op_term generate_cudaq_spin(int64_t id, int64_t num_qubits, + bool addI = true) { constexpr int64_t mask = 0x3; - cudaq::spin_op result; + auto result = cudaq::spin_op::identity(); for (int64_t i = 0; i < num_qubits; ++i) { switch (paulis[id & mask]) { case Pauli::I: if (addI) - result *= cudaq::spin::i(i); + result *= cudaq::spin_op::i(i); break; case Pauli::X: - result *= cudaq::spin::x(i); + result *= cudaq::spin_op::x(i); break; case Pauli::Y: - result *= cudaq::spin::y(i); + result *= cudaq::spin_op::y(i); break; case Pauli::Z: - result *= cudaq::spin::z(i); + result *= cudaq::spin_op::z(i); break; } id >>= 2; @@ -93,16 +92,23 @@ static cudaq::spin_op generate_cudaq_spin(int64_t id, int64_t num_qubits, return result; } -using namespace cudaq::spin; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif TEST(SpinOpTester, checkConstruction) { - cudaq::spin_op op = x(10); - EXPECT_EQ(11, op.num_qubits()); + cudaq::spin_op op = cudaq::spin_op::x(10); + EXPECT_EQ(1, op.num_qubits()); EXPECT_EQ(1, op.num_terms()); } TEST(SpinOpTester, checkEquality) { - cudaq::spin_op xx = x(5); + auto xx = cudaq::spin_op::x(5); EXPECT_EQ(xx, xx); } @@ -110,53 +116,60 @@ TEST(SpinOpTester, checkFromWord) { { auto s = cudaq::spin_op::from_word("ZZZ"); std::cout << s.to_string() << "\n"; - EXPECT_EQ(z(0) * z(1) * z(2), s); + EXPECT_EQ( + cudaq::spin_op::z(0) * cudaq::spin_op::z(1) * cudaq::spin_op::z(2), s); } { auto s = cudaq::spin_op::from_word("XYX"); std::cout << s.to_string() << "\n"; - EXPECT_EQ(x(0) * y(1) * x(2), s); + EXPECT_EQ( + cudaq::spin_op::x(0) * cudaq::spin_op::y(1) * cudaq::spin_op::x(2), s); } { auto s = cudaq::spin_op::from_word("IZY"); std::cout << s.to_string() << "\n"; - EXPECT_EQ(i(0) * z(1) * y(2), s); + EXPECT_EQ( + cudaq::spin_op::i(0) * cudaq::spin_op::z(1) * cudaq::spin_op::y(2), s); } } TEST(SpinOpTester, checkAddition) { - cudaq::spin_op op = x(10); + cudaq::spin_op op = cudaq::spin_op::x(10); auto added = op + op; - EXPECT_EQ(11, added.num_qubits()); + EXPECT_EQ(1, added.num_qubits()); EXPECT_EQ(1, added.num_terms()); - EXPECT_EQ(2.0, added.get_coefficient()); - - op.dump(); - added.dump(); + EXPECT_EQ(2.0, added.begin()->get_coefficient()); - auto added2 = x(0) + y(1) + z(2); - added2.dump(); + auto added2 = + cudaq::spin_op::x(0) + cudaq::spin_op::y(1) + cudaq::spin_op::z(2); EXPECT_EQ(3, added2.num_terms()); EXPECT_EQ(3, added2.num_qubits()); for (int i = 0; i < 3; i++) { EXPECT_EQ(1.0, added2.begin()->get_coefficient()); } - auto subtracted = x(0) - y(2); - subtracted.dump(); + auto subtracted = cudaq::spin_op::x(0) - cudaq::spin_op::y(2); } TEST(SpinOpTester, checkBug178) { - cudaq::spin_op op = 1.0 + 2.0 * x(0); - op.dump(); + cudaq::spin_op op = 1.0 + 2.0 * cudaq::spin_op::x(0); auto [bsf, coeffs] = op.get_raw_data(); std::vector> expected{std::vector(2), std::vector{1, 0}}; cudaq::spin_op exp(expected, {1., 2.}); EXPECT_EQ(op, exp); + EXPECT_EQ(2, op.num_terms()); + EXPECT_EQ(2, exp.num_terms()); + auto op_it = op.begin(); + auto exp_it = exp.begin(); + EXPECT_EQ(*op_it++, *exp_it++); + EXPECT_EQ(*op_it++, *exp_it++); + EXPECT_EQ(op_it, op.end()); + EXPECT_EQ(exp_it, exp.end()); + EXPECT_EQ(op, exp); EXPECT_TRUE(std::find(expected.begin(), expected.end(), bsf[0]) != expected.end()); @@ -175,9 +188,9 @@ TEST(SpinOpTester, checkMultiplication) { auto [phase, result] = multiply_pauli_words(a_word, b_word); // Result: - cudaq::spin_op a_spin = generate_cudaq_spin(i, num_qubits); - cudaq::spin_op b_spin = generate_cudaq_spin(j, num_qubits, false); - cudaq::spin_op result_spin = a_spin * b_spin; + auto a_spin = generate_cudaq_spin(i, num_qubits); + auto b_spin = generate_cudaq_spin(j, num_qubits, false); + auto result_spin = a_spin * b_spin; // Check result EXPECT_EQ(generate_pauli_string(result), result_spin.to_string(false)); @@ -188,18 +201,19 @@ TEST(SpinOpTester, checkMultiplication) { } TEST(SpinOpTester, canBuildDeuteron) { - auto H = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + .21829 * z(0) - - 6.125 * z(1); - - H.dump(); + auto H = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); EXPECT_EQ(5, H.num_terms()); EXPECT_EQ(2, H.num_qubits()); } TEST(SpinOpTester, checkGetSparseMatrix) { - auto H = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + .21829 * z(0) - - 6.125 * z(1); + auto H = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + auto matrix = H.to_matrix(); matrix.dump(); auto [values, rows, cols] = H.to_sparse_matrix(); @@ -211,8 +225,10 @@ TEST(SpinOpTester, checkGetSparseMatrix) { } TEST(SpinOpTester, checkGetMatrix) { - auto H = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + .21829 * z(0) - - 6.125 * z(1); + auto H = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); + auto matrix = H.to_matrix(); matrix.dump(); auto groundEnergy = matrix.minimal_eigenvalue(); @@ -269,8 +285,9 @@ TEST(SpinOpTester, checkGetMatrix) { } TEST(SpinOpTester, checkIterator) { - auto H = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + .21829 * z(0) - - 6.125 * z(1); + auto H = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); std::size_t count = 0; for (auto term : H) { @@ -282,8 +299,9 @@ TEST(SpinOpTester, checkIterator) { } TEST(SpinOpTester, checkDistributeTerms) { - auto H = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + .21829 * z(0) - - 6.125 * z(1); + auto H = 5.907 - 2.1433 * cudaq::spin_op::x(0) * cudaq::spin_op::x(1) - + 2.1433 * cudaq::spin_op::y(0) * cudaq::spin_op::y(1) + + .21829 * cudaq::spin_op::z(0) - 6.125 * cudaq::spin_op::z(1); auto distributed = H.distribute_terms(2); @@ -291,3 +309,10 @@ TEST(SpinOpTester, checkDistributeTerms) { EXPECT_EQ(distributed[0].num_terms(), 3); EXPECT_EQ(distributed[1].num_terms(), 2); } + +#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif