From 81c482b4b1e67d32f5e3e80171ad0c98e352659c Mon Sep 17 00:00:00 2001 From: Ben Howe <141149032+bmhowe23@users.noreply.github.com> Date: Tue, 1 Oct 2024 08:29:56 -0700 Subject: [PATCH] Add Clifford-only simulator (Stim) (#2235) * Add Clifford-only simulator (Stim) (#2193) * Constrain x86-64 build to AVX2 * Compilation update for #2168 --------- Co-authored-by: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> --- .gitmodules | 3 + NOTICE | 8 + docs/sphinx/using/backends/backends.rst | 1 + docs/sphinx/using/backends/simulators.rst | 34 +++ python/tests/backends/test_stim.py | 91 ++++++++ runtime/nvqir/CMakeLists.txt | 1 + runtime/nvqir/CircuitSimulator.h | 12 +- runtime/nvqir/stim/CMakeLists.txt | 47 ++++ runtime/nvqir/stim/StimCircuitSimulator.cpp | 202 ++++++++++++++++++ runtime/nvqir/stim/stim.yml | 13 ++ scripts/validate_container.sh | 5 +- tpls/Stim | 1 + unittests/CMakeLists.txt | 4 + unittests/integration/adjoint_tester.cpp | 10 + unittests/integration/async_tester.cpp | 4 + .../integration/bug67_vqe_then_sample.cpp | 3 +- .../integration/bug77_vqe_with_shots.cpp | 2 +- unittests/integration/builder_tester.cpp | 30 ++- unittests/integration/ccnot_tester.cpp | 3 + .../deuteron_variational_tester.cpp | 4 + unittests/integration/gate_library_tester.cpp | 2 +- unittests/integration/get_state_tester.cpp | 5 + unittests/integration/ghz_nisq_tester.cpp | 6 +- unittests/integration/gradient_tester.cpp | 4 + unittests/integration/grover_test.cpp | 5 + unittests/integration/kernels_tester.cpp | 2 +- .../integration/negative_controls_tester.cpp | 2 + unittests/integration/nlopt_tester.cpp | 5 + .../integration/observe_result_tester.cpp | 4 + unittests/integration/qpe_ftqc.cpp | 5 + unittests/integration/qpe_nisq.cpp | 5 + unittests/integration/qubit_allocation.cpp | 5 + unittests/integration/vqe_tester.cpp | 5 + unittests/qir/NVQIRTester.cpp | 5 + unittests/qis/QubitQISTester.cpp | 15 ++ 35 files changed, 542 insertions(+), 11 deletions(-) create mode 100644 python/tests/backends/test_stim.py create mode 100644 runtime/nvqir/stim/CMakeLists.txt create mode 100644 runtime/nvqir/stim/StimCircuitSimulator.cpp create mode 100644 runtime/nvqir/stim/stim.yml create mode 160000 tpls/Stim diff --git a/.gitmodules b/.gitmodules index 2106b04b45..622993890c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -45,3 +45,6 @@ path = tpls/Crow url = https://github.com/CrowCpp/Crow.git ignore = dirty +[submodule "tpls/Stim"] + path = tpls/Stim + url = https://github.com/quantumlib/Stim diff --git a/NOTICE b/NOTICE index 7649a8dce4..c4431e8023 100644 --- a/NOTICE +++ b/NOTICE @@ -172,3 +172,11 @@ Tweedledum - MIT License License at ---------------------------------------------------------------- + +Stim - Apache License 2.0 + + +The incorporated source code and its license can be found as a submodule on the CUDA-Q repository. +License at + +---------------------------------------------------------------- diff --git a/docs/sphinx/using/backends/backends.rst b/docs/sphinx/using/backends/backends.rst index a21d0f59e4..56b628e8fd 100644 --- a/docs/sphinx/using/backends/backends.rst +++ b/docs/sphinx/using/backends/backends.rst @@ -26,6 +26,7 @@ CUDA-Q Backends * :ref:`qpp-cpu ` * :ref:`quantinuum ` * :ref:`remote-mqpu ` +* :ref:`stim ` * :ref:`tensornet ` * :ref:`tensornet-mps ` diff --git a/docs/sphinx/using/backends/simulators.rst b/docs/sphinx/using/backends/simulators.rst index b41b734be3..dab80d3243 100644 --- a/docs/sphinx/using/backends/simulators.rst +++ b/docs/sphinx/using/backends/simulators.rst @@ -301,6 +301,40 @@ use the following commands: ./program.x +Clifford-Only Simulation (CPU) +++++++++++++++++++++++++++++++++++ + +.. _stim-backend: + +This target provides a fast simulator for circuits containing *only* Clifford +gates. Any non-Clifford gates (such as T gates and Toffoli gates) are not +supported. This simulator is based on the `Stim `_ +library. + +To execute a program on the :code:`stim` target, use the following commands: + +.. tab:: Python + + .. code:: bash + + python3 program.py [...] --target stim + + The target can also be defined in the application code by calling + + .. code:: python + + cudaq.set_target('stim') + + If a target is set in the application code, this target will override the :code:`--target` command line flag given during program invocation. + +.. tab:: C++ + + .. code:: bash + + nvq++ --target stim program.cpp [...] -o program.x + ./program.x + + Tensor Network Simulators ================================== diff --git a/python/tests/backends/test_stim.py b/python/tests/backends/test_stim.py new file mode 100644 index 0000000000..13b1280fd3 --- /dev/null +++ b/python/tests/backends/test_stim.py @@ -0,0 +1,91 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +import os +from typing import List +import pytest + +import cudaq +import numpy as np + + +@pytest.fixture(scope="session", autouse=True) +def setTarget(): + old_target = cudaq.get_target() + cudaq.set_target('stim') + yield + cudaq.set_target(old_target) + + +def test_stim_non_clifford(): + + @cudaq.kernel + def kernel(): + qubits = cudaq.qvector(10) + rx(0.1, qubits[0]) + + with pytest.raises(RuntimeError) as e: + # Cannot perform non-Clifford gates in Stim simulator + cudaq.sample(kernel) + + +def test_stim_toffoli_gates(): + + @cudaq.kernel + def kernel(): + qubits = cudaq.qvector(10) + cx(qubits[0:9], qubits[9]) + + with pytest.raises(RuntimeError) as e: + # Cannot perform Toffoli gates in Stim simulator + cudaq.sample(kernel) + + +def test_stim_sample(): + # Create the kernel we'd like to execute on Stim + @cudaq.kernel + def kernel(): + qubits = cudaq.qvector(250) + h(qubits[0]) + # Stim is a Clifford-only simulator, so it can do many qubits. + for i in range(1, 250): + cx(qubits[i - 1], qubits[i]) + mz(qubits) + + counts = cudaq.sample(kernel) + assert (len(counts) == 2) + assert ('0' * 250 in counts) + assert ('1' * 250 in counts) + + +def test_stim_state_preparation(): + + @cudaq.kernel + def kernel(vec: List[complex]): + qubits = cudaq.qvector(vec) + + with pytest.raises(RuntimeError) as e: + # Cannot initialize qubits from state data in this simulator + state = [1. / np.sqrt(2.), 1. / np.sqrt(2.), 0., 0.] + cudaq.sample(kernel, state) + + +def test_stim_state_preparation_builder(): + kernel, state = cudaq.make_kernel(List[complex]) + qubits = kernel.qalloc(state) + + with pytest.raises(RuntimeError) as e: + # Cannot initialize qubits from state data in this simulator + state = [1. / np.sqrt(2.), 1. / np.sqrt(2.), 0., 0.] + cudaq.sample(kernel, state) + + +# leave for gdb debugging +if __name__ == "__main__": + loc = os.path.abspath(__file__) + pytest.main([loc, "-s"]) diff --git a/runtime/nvqir/CMakeLists.txt b/runtime/nvqir/CMakeLists.txt index b660f2bfd7..15f0abfe86 100644 --- a/runtime/nvqir/CMakeLists.txt +++ b/runtime/nvqir/CMakeLists.txt @@ -38,6 +38,7 @@ install(TARGETS ${LIBRARY_NAME} INCLUDES DESTINATION include/nvqir) add_subdirectory(qpp) +add_subdirectory(stim) if (CUSTATEVEC_ROOT AND CUDA_FOUND) add_subdirectory(custatevec) diff --git a/runtime/nvqir/CircuitSimulator.h b/runtime/nvqir/CircuitSimulator.h index 29a0196a26..0611294d4a 100644 --- a/runtime/nvqir/CircuitSimulator.h +++ b/runtime/nvqir/CircuitSimulator.h @@ -752,7 +752,17 @@ class CircuitSimulatorBase : public CircuitSimulator { summaryData.svGateUpdate( next.controls.size(), next.targets.size(), stateDimension, stateDimension * sizeof(std::complex)); - applyGate(next); + try { + applyGate(next); + } catch (std::exception &e) { + while (!gateQueue.empty()) + gateQueue.pop(); + throw e; + } catch (...) { + while (!gateQueue.empty()) + gateQueue.pop(); + throw std::runtime_error("Unknown exception in applyGate"); + } if (executionContext && executionContext->noiseModel) { std::vector params(next.parameters.begin(), next.parameters.end()); diff --git a/runtime/nvqir/stim/CMakeLists.txt b/runtime/nvqir/stim/CMakeLists.txt new file mode 100644 index 0000000000..db5e18f72a --- /dev/null +++ b/runtime/nvqir/stim/CMakeLists.txt @@ -0,0 +1,47 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +set(LIBRARY_NAME nvqir-stim) +set(INTERFACE_POSITION_INDEPENDENT_CODE ON) + +set(STIM_SOURCE_DIR ${CMAKE_SOURCE_DIR}/tpls/Stim) +set(STIM_BINARY_DIR ${CMAKE_BINARY_DIR}/tpls/Stim) + +if(${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL "x86_64") + # Constrain to AVX-2 to keep ourselves compatible with x86-64-v3. + set(SIMD_WIDTH 256 CACHE INTERNAL "Pass SIMD width to Stim subproject") +endif() + +# The EXCLUDE_FROM_ALL makes it so that only libstim is built. If other targets +# are desired (like the command-line tool), remove EXCLUDE_FROM_ALL below. +add_subdirectory(${STIM_SOURCE_DIR} ${STIM_BINARY_DIR} EXCLUDE_FROM_ALL) + +add_library(${LIBRARY_NAME} SHARED StimCircuitSimulator.cpp) +set_property(GLOBAL APPEND PROPERTY CUDAQ_RUNTIME_LIBS ${LIBRARY_NAME}) + +set (STIM_DEPENDENCIES libstim fmt::fmt-header-only cudaq-common) + +# If -Wall is enabled (as is done in parent directories), Stim will not compile. +# So override that here. +target_compile_options(libstim PRIVATE -Wno-all) + +target_include_directories(${LIBRARY_NAME} + PUBLIC + $ + $ + $) + +target_link_libraries(${LIBRARY_NAME} + PRIVATE ${STIM_DEPENDENCIES}) + +set_target_properties(${LIBRARY_NAME} + PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_RPATH}:${LLVM_BINARY_DIR}/lib") + +install(TARGETS ${LIBRARY_NAME} DESTINATION lib) + +add_target_config(stim) diff --git a/runtime/nvqir/stim/StimCircuitSimulator.cpp b/runtime/nvqir/stim/StimCircuitSimulator.cpp new file mode 100644 index 0000000000..1cc86fcebe --- /dev/null +++ b/runtime/nvqir/stim/StimCircuitSimulator.cpp @@ -0,0 +1,202 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "nvqir/CircuitSimulator.h" +#include "nvqir/Gates.h" +#include "stim.h" + +#include +#include +#include +#include + +using namespace cudaq; + +namespace nvqir { + +/// @brief The StimCircuitSimulator implements the CircuitSimulator +/// base class to provide a simulator delegating to the Stim library from +/// https://github.com/quantumlib/Stim. +class StimCircuitSimulator : public nvqir::CircuitSimulatorBase { +protected: + stim::Circuit stimCircuit; + std::mt19937_64 randomEngine; + + /// @brief Grow the state vector by one qubit. + void addQubitToState() override { addQubitsToState(1); } + + /// @brief Override the default sized allocation of qubits + /// here to be a bit more efficient than the default implementation + void addQubitsToState(std::size_t qubitCount, + const void *stateDataIn = nullptr) override { + if (stateDataIn) + throw std::runtime_error("The Stim simulator does not support " + "initialization of qubits from state data."); + return; + } + + /// @brief Reset the qubit state. + void deallocateStateImpl() override { stimCircuit.clear(); } + + /// @brief Apply the noise channel on \p qubits + void applyNoiseChannel(const std::string_view gateName, + const std::vector &controls, + const std::vector &targets, + const std::vector ¶ms) override { + // Do nothing if no execution context + if (!executionContext) + return; + + // Do nothing if no noise model + if (!executionContext->noiseModel) + return; + + // Get the name as a string + std::string gName(gateName); + + // Cast size_t to uint32_t + std::vector stimTargets; + stimTargets.reserve(controls.size() + targets.size()); + for (auto q : controls) + stimTargets.push_back(static_cast(q)); + for (auto q : targets) + stimTargets.push_back(static_cast(q)); + + // Get the Kraus channels specified for this gate and qubits + auto krausChannels = executionContext->noiseModel->get_channels( + gName, controls, targets, params); + + // If none, do nothing + if (krausChannels.empty()) + return; + + // TODO + return; + } + + void applyGate(const GateApplicationTask &task) override { + std::string gateName(task.operationName); + std::transform(gateName.begin(), gateName.end(), gateName.begin(), + ::toupper); + std::vector stimTargets; + + // These CUDA-Q rotation gates have the same name as Stim "reset" gates. + // Stim is a Clifford simulator, so it doesn't actually support rotational + // gates. Throw exceptions if they are encountered here. + // TODO - consider adding support for specific rotations (e.g. pi/2). + if (gateName == "RX" || gateName == "RY" || gateName == "RZ") + throw std::runtime_error( + fmt::format("Gate not supported by Stim simulator: {}. Note that " + "Stim can only simulate Clifford gates.", + task.operationName)); + + if (task.controls.size() > 1) + throw std::runtime_error( + "Gates with >1 controls not supported by stim simulator"); + if (task.controls.size() >= 1) + gateName = "C" + gateName; + for (auto c : task.controls) + stimTargets.push_back(c); + for (auto t : task.targets) + stimTargets.push_back(t); + try { + stimCircuit.safe_append_u(gateName, stimTargets); + } catch (std::out_of_range &e) { + throw std::runtime_error( + fmt::format("Gate not supported by Stim simulator: {}. Note that " + "Stim can only simulate Clifford gates.", + e.what())); + } + } + + /// @brief Set the current state back to the |0> state. + void setToZeroState() override { return; } + + /// @brief Override the calculateStateDim because this is not a state vector + /// simulator. + std::size_t calculateStateDim(const std::size_t numQubits) override { + return 0; + } + + /// @brief Measure the qubit and return the result. Collapse the + /// state vector. + bool measureQubit(const std::size_t index) override { return false; } + + QubitOrdering getQubitOrdering() const override { return QubitOrdering::msb; } + +public: + StimCircuitSimulator() { + // Populate the correct name so it is printed correctly during + // deconstructor. + summaryData.name = name(); + } + virtual ~StimCircuitSimulator() = default; + + void setRandomSeed(std::size_t seed) override { + randomEngine = std::mt19937_64(seed); + } + + bool canHandleObserve() override { return false; } + + /// @brief Reset the qubit + /// @param index 0-based index of qubit to reset + void resetQubit(const std::size_t index) override { + flushGateQueue(); + stimCircuit.safe_append_u( + "R", std::vector{static_cast(index)}); + } + + /// @brief Sample the multi-qubit state. + cudaq::ExecutionResult sample(const std::vector &qubits, + const int shots) override { + std::vector stimTargetQubits(qubits.begin(), qubits.end()); + stimCircuit.safe_append_u("M", stimTargetQubits); + if (false) { + std::stringstream ss; + ss << stimCircuit << '\n'; + cudaq::log("Stim circuit is\n{}", ss.str()); + } + auto ref_sample = stim::TableauSimulator< + stim::MAX_BITWORD_WIDTH>::reference_sample_circuit(stimCircuit); + stim::simd_bit_table sample = + stim::sample_batch_measurements(stimCircuit, ref_sample, shots, + randomEngine, false); + size_t bits_per_sample = stimCircuit.count_measurements(); + std::vector sequentialData; + sequentialData.reserve(shots); + // Only retain the final "qubits.size()" measurements. All other + // measurements were mid-circuit measurements that have been previously + // accounted for and saved. + assert(bits_per_sample >= qubits.size()); + std::size_t first_bit_to_save = bits_per_sample - qubits.size(); + CountsDictionary counts; + for (std::size_t shot = 0; shot < shots; shot++) { + std::string aShot(qubits.size(), '0'); + for (std::size_t b = first_bit_to_save; b < bits_per_sample; b++) { + aShot[b - first_bit_to_save] = sample[b][shot] ? '1' : '0'; + } + counts[aShot]++; + sequentialData.push_back(std::move(aShot)); + } + ExecutionResult result(counts); + result.sequentialData = std::move(sequentialData); + return result; + } + + bool isStateVectorSimulator() const override { return false; } + + std::string name() const override { return "stim"; } + NVQIR_SIMULATOR_CLONE_IMPL(StimCircuitSimulator) +}; + +} // namespace nvqir + +#ifndef __NVQIR_QPP_TOGGLE_CREATE +/// Register this Simulator with NVQIR. +NVQIR_REGISTER_SIMULATOR(nvqir::StimCircuitSimulator, stim) +#endif diff --git a/runtime/nvqir/stim/stim.yml b/runtime/nvqir/stim/stim.yml new file mode 100644 index 0000000000..6518d169ff --- /dev/null +++ b/runtime/nvqir/stim/stim.yml @@ -0,0 +1,13 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +name: stim +description: "Stim-based CPU-only backend target" +config: + nvqir-simulation-backend: stim + preprocessor-defines: ["-D CUDAQ_SIMULATION_SCALAR_FP64"] diff --git a/scripts/validate_container.sh b/scripts/validate_container.sh index 1fce287da0..b37feec885 100644 --- a/scripts/validate_container.sh +++ b/scripts/validate_container.sh @@ -60,7 +60,7 @@ installed_backends=`\ done` # remote_rest targets are automatically filtered, -# so is execution on the photonics backend +# so is execution on the photonics backend and the stim backend # This will test all NVIDIA-derivative targets in the legacy mode, # i.e., nvidia-fp64, nvidia-mgpu, nvidia-mqpu, etc., are treated as standalone targets. available_backends=`\ @@ -74,6 +74,9 @@ available_backends=`\ if [[ $file == *"opt-test.yml" ]]; then continue fi + if grep -q "nvqir-simulation-backend: stim" $file ; then + continue + fi platform=$(cat $file | grep "platform-qpu:") qpu=${platform##* } requirements=$(cat $file | grep "gpu-requirements:") diff --git a/tpls/Stim b/tpls/Stim new file mode 160000 index 0000000000..b01e423915 --- /dev/null +++ b/tpls/Stim @@ -0,0 +1 @@ +Subproject commit b01e42391583d03db4266b387d907eda1d7ae488 diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 079a7a47ea..8a3ce01b50 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -79,6 +79,9 @@ macro (create_tests_with_backend NVQIR_BACKEND EXTRA_BACKEND_TESTER) if (${NVQIR_BACKEND} STREQUAL "dm") target_compile_definitions(${TEST_EXE_NAME} PRIVATE -DCUDAQ_BACKEND_DM -DCUDAQ_SIMULATION_SCALAR_FP64) endif() + if (${NVQIR_BACKEND} STREQUAL "stim") + target_compile_definitions(${TEST_EXE_NAME} PRIVATE -DCUDAQ_BACKEND_STIM -DCUDAQ_SIMULATION_SCALAR_FP64) + endif() if (${NVQIR_BACKEND} STREQUAL "tensornet") target_compile_definitions(${TEST_EXE_NAME} PRIVATE -DCUDAQ_BACKEND_TENSORNET -DCUDAQ_SIMULATION_SCALAR_FP64) set(TEST_LABELS "gpu_required") @@ -102,6 +105,7 @@ endmacro() # We will always have the QPP backend, create a tester for it create_tests_with_backend(qpp backends/QPPTester.cpp) create_tests_with_backend(dm backends/QPPDMTester.cpp) +create_tests_with_backend(stim "") if (CUSTATEVEC_ROOT AND CUDA_FOUND) create_tests_with_backend(custatevec-fp32 "") diff --git a/unittests/integration/adjoint_tester.cpp b/unittests/integration/adjoint_tester.cpp index 4b1273114e..a44e275347 100644 --- a/unittests/integration/adjoint_tester.cpp +++ b/unittests/integration/adjoint_tester.cpp @@ -101,22 +101,27 @@ CUDAQ_TEST(AdjointTester, checkSimple) { EXPECT_EQ(1, counts2.size()); EXPECT_TRUE(counts2.begin()->first == "00000"); +#ifndef CUDAQ_BACKEND_STIM auto counts3 = cudaq::sample(rotation_adjoint_test{}); counts3.dump(); EXPECT_EQ(1, counts3.size()); EXPECT_TRUE(counts3.begin()->first == "0"); +#endif auto counts4 = cudaq::sample(twoqbit_adjoint_test{}); counts4.dump(); EXPECT_EQ(1, counts4.size()); EXPECT_TRUE(counts4.begin()->first == "00"); +#ifndef CUDAQ_BACKEND_STIM auto counts5 = cudaq::sample(test_cudaq_adjoint{}); counts5.dump(); EXPECT_EQ(1, counts5.size()); EXPECT_TRUE(counts5.begin()->first == "101"); +#endif } +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(AdjointTester, checkNestedAdjoint) { struct xxxh_gates { @@ -218,6 +223,7 @@ CUDAQ_TEST(AdjointTester, checkNestedAdjoint) { // ctrl ry pi / 4 1 2 // } } +#endif #ifndef CUDAQ_BACKEND_DM @@ -251,12 +257,14 @@ static __qpu__ void bar() { cudaq::adjoint(foo, q); } +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(AdjointTester, checkEvenAdjointNesting) { auto result = cudaq::get_state(bar); std::array, 2> expected = {1., 0}; EXPECT_TRUE(essentially_equal(expected[0], result[0])); EXPECT_TRUE(essentially_equal(expected[1], result[1])); } +#endif static __qpu__ void zaz(cudaq::qubit &q) { rz(M_PI_2, q); } @@ -268,11 +276,13 @@ static __qpu__ void bar_2() { cudaq::adjoint(foo_2, q); } +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(AdjointTester, checkOddAdjointNesting) { auto result = cudaq::get_state(bar_2); std::array, 2> expected = {1., 0}; EXPECT_TRUE(essentially_equal(expected[0], result[0])); EXPECT_TRUE(essentially_equal(expected[1], result[1])); } +#endif #endif diff --git a/unittests/integration/async_tester.cpp b/unittests/integration/async_tester.cpp index dc1103b3f0..c70c7af4c5 100644 --- a/unittests/integration/async_tester.cpp +++ b/unittests/integration/async_tester.cpp @@ -11,6 +11,7 @@ #ifndef CUDAQ_BACKEND_DM +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(AsyncTester, checkObserveAsync) { using namespace cudaq::spin; @@ -45,6 +46,7 @@ CUDAQ_TEST(AsyncTester, checkObserveAsync) { i++; } } +#endif CUDAQ_TEST(AsyncTester, checkSampleAsync) { struct ghz { @@ -71,6 +73,7 @@ CUDAQ_TEST(AsyncTester, checkSampleAsync) { cc3.get().dump(); } +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(AsyncTester, checkGetStateAsync) { struct ghz { auto operator()(int NQubits) __qpu__ { @@ -107,3 +110,4 @@ CUDAQ_TEST(AsyncTester, checkGetStateAsync) { } } #endif +#endif diff --git a/unittests/integration/bug67_vqe_then_sample.cpp b/unittests/integration/bug67_vqe_then_sample.cpp index 2dbac5ba53..53132f1683 100644 --- a/unittests/integration/bug67_vqe_then_sample.cpp +++ b/unittests/integration/bug67_vqe_then_sample.cpp @@ -12,7 +12,8 @@ #include #include -#if !defined CUDAQ_BACKEND_DM && !defined CUDAQ_BACKEND_TENSORNET +#if !defined(CUDAQ_BACKEND_DM) && !defined(CUDAQ_BACKEND_TENSORNET) && \ + !defined(CUDAQ_BACKEND_STIM) CUDAQ_TEST(VqeThenSample, checkBug67) { diff --git a/unittests/integration/bug77_vqe_with_shots.cpp b/unittests/integration/bug77_vqe_with_shots.cpp index 882dd1f866..c76ac407e4 100644 --- a/unittests/integration/bug77_vqe_with_shots.cpp +++ b/unittests/integration/bug77_vqe_with_shots.cpp @@ -12,7 +12,7 @@ #include #include -#ifndef CUDAQ_BACKEND_DM +#if !defined(CUDAQ_BACKEND_DM) && !defined(CUDAQ_BACKEND_STIM) CUDAQ_TEST(VqeWithShots, checkBug77) { struct ansatz { diff --git a/unittests/integration/builder_tester.cpp b/unittests/integration/builder_tester.cpp index d15e02e8f2..d4053441b8 100644 --- a/unittests/integration/builder_tester.cpp +++ b/unittests/integration/builder_tester.cpp @@ -13,6 +13,7 @@ #include #include +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkSimple) { { using namespace cudaq::spin; @@ -217,7 +218,9 @@ CUDAQ_TEST(BuilderTester, checkSimple) { EXPECT_TRUE(counts.begin()->first == "1"); } } +#endif +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkRotations) { // rx: entire qvector @@ -422,6 +425,7 @@ CUDAQ_TEST(BuilderTester, checkRotations) { EXPECT_EQ(counts.count("0111"), 1000); } } +#endif CUDAQ_TEST(BuilderTester, checkSwap) { cudaq::set_random_seed(13); @@ -456,6 +460,7 @@ CUDAQ_TEST(BuilderTester, checkSwap) { EXPECT_NEAR(counts.count("10"), 1000, 0); } +#ifndef CUDAQ_BACKEND_STIM // Single qubit controlled-SWAP. { auto kernel = cudaq::make_kernel(); @@ -565,11 +570,12 @@ CUDAQ_TEST(BuilderTester, checkSwap) { auto want_state = ctrls_state + want_target; EXPECT_NEAR(counts.count(want_state), 1000, 0); } +#endif } // Conditional execution on the tensornet backend is slow for a large number of // shots. -#ifndef CUDAQ_BACKEND_TENSORNET +#if !defined(CUDAQ_BACKEND_TENSORNET) && !defined(CUDAQ_BACKEND_STIM) CUDAQ_TEST(BuilderTester, checkConditional) { { cudaq::set_random_seed(13); @@ -651,6 +657,7 @@ CUDAQ_TEST(BuilderTester, checkQvecArg) { EXPECT_EQ(counts.to_map().begin()->first.length(), 5); } +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkSlice) { auto [kernel, params] = cudaq::make_kernel>(); auto q = kernel.qalloc(4); @@ -674,7 +681,9 @@ CUDAQ_TEST(BuilderTester, checkSlice) { // Should throw since we have 2 qubits and asked for 3 EXPECT_ANY_THROW({ auto sliced = q2.slice(0, 3); }); } +#endif +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkStdVecValidate) { auto [kernel, thetas] = cudaq::make_kernel>(); auto q = kernel.qalloc(2); @@ -689,6 +698,7 @@ CUDAQ_TEST(BuilderTester, checkStdVecValidate) { // This is not ok EXPECT_ANY_THROW({ kernel(std::vector{M_PI}); }); } +#endif CUDAQ_TEST(BuilderTester, checkIsArgStdVec) { auto [kernel, one, two, thetas, four] = @@ -698,6 +708,8 @@ CUDAQ_TEST(BuilderTester, checkIsArgStdVec) { EXPECT_FALSE(kernel.isArgStdVec(1)); } +// Stim does not currently support a controlled H gate. +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkKernelControl) { cudaq::set_random_seed(13); @@ -754,7 +766,9 @@ CUDAQ_TEST(BuilderTester, checkKernelControl) { EXPECT_EQ(1, counts.size()); EXPECT_TRUE(counts.begin()->first == "101"); } +#endif +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkAdjointOp) { auto kernel = cudaq::make_kernel(); auto q = kernel.qalloc(); @@ -803,6 +817,7 @@ CUDAQ_TEST(BuilderTester, checkKernelAdjoint) { EXPECT_EQ(counts.size(), 1); EXPECT_EQ(counts.begin()->first, "1"); } +#endif // Conditional execution (including reset) on the tensornet backend is slow for // a large number of shots. @@ -815,6 +830,7 @@ CUDAQ_TEST(BuilderTester, checkReset) { entryPoint.reset(q); entryPoint.mz(q); auto counts = cudaq::sample(entryPoint); + counts.dump(); EXPECT_EQ(counts.size(), 1); EXPECT_EQ(counts.begin()->first, "0"); } @@ -845,6 +861,7 @@ CUDAQ_TEST(BuilderTester, checkReset) { } #endif +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkForLoop) { { @@ -927,6 +944,7 @@ CUDAQ_TEST(BuilderTester, checkForLoop) { counts.dump(); } } +#endif // Conditional execution (including reset) on the tensornet backend is slow for // a large number of shots. @@ -967,6 +985,7 @@ CUDAQ_TEST(BuilderTester, checkMidCircuitMeasure) { EXPECT_EQ(counts.count("0", "c1"), 1000); EXPECT_EQ(counts.count("1", "c0"), 1000); + return; } { @@ -989,6 +1008,7 @@ CUDAQ_TEST(BuilderTester, checkMidCircuitMeasure) { } #endif +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkNestedKernelCall) { auto [kernel1, qubit1] = cudaq::make_kernel(); auto [kernel2, qubit2] = cudaq::make_kernel(); @@ -1012,6 +1032,7 @@ CUDAQ_TEST(BuilderTester, checkNestedKernelCall) { EXPECT_EQ(count(quake, "func.func"), 3); EXPECT_EQ(count(quake, "call @__nvqpp__"), 2); } +#endif CUDAQ_TEST(BuilderTester, checkEntryPointAttribute) { auto kernel = cudaq::make_kernel(); @@ -1023,6 +1044,7 @@ CUDAQ_TEST(BuilderTester, checkEntryPointAttribute) { EXPECT_TRUE(std::regex_search(quake, functionDecleration)); } +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkExpPauli) { std::vector h2_data{ 3, 1, 1, 3, 0.0454063, 0, 2, 0, 0, 0, 0.17028, 0, @@ -1138,7 +1160,9 @@ CUDAQ_TEST(BuilderTester, checkExpPauli) { } } } +#endif +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(BuilderTester, checkControlledRotations) { // rx: pi { @@ -1253,8 +1277,10 @@ CUDAQ_TEST(BuilderTester, checkControlledRotations) { EXPECT_EQ(counts.count("11111111"), 1000); } } +#endif -#if !defined(CUDAQ_BACKEND_DM) && !defined(CUDAQ_BACKEND_TENSORNET) +#if !defined(CUDAQ_BACKEND_DM) && !defined(CUDAQ_BACKEND_TENSORNET) && \ + !defined(CUDAQ_BACKEND_STIM) TEST(BuilderTester, checkFromStateVector) { std::vector vec{M_SQRT1_2, 0., 0., M_SQRT1_2}; diff --git a/unittests/integration/ccnot_tester.cpp b/unittests/integration/ccnot_tester.cpp index 4c1030fe34..29ffa799c9 100644 --- a/unittests/integration/ccnot_tester.cpp +++ b/unittests/integration/ccnot_tester.cpp @@ -54,6 +54,7 @@ struct nested_ctrl { } }; +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(CCNOTTester, checkSimple) { auto ccnot = []() { cudaq::qvector q(3); @@ -94,3 +95,5 @@ CUDAQ_TEST(FredkinTester, checkTruth) { EXPECT_EQ(counts.size(), 1); EXPECT_EQ(counts.begin()->first, "110"); } + +#endif diff --git a/unittests/integration/deuteron_variational_tester.cpp b/unittests/integration/deuteron_variational_tester.cpp index 5f96059930..98f566cb4e 100644 --- a/unittests/integration/deuteron_variational_tester.cpp +++ b/unittests/integration/deuteron_variational_tester.cpp @@ -18,6 +18,8 @@ struct ansatz2 { } }; +#ifndef CUDAQ_BACKEND_STIM + CUDAQ_TEST(D2VariationalTester, checkSimple) { using namespace cudaq::spin; @@ -101,3 +103,5 @@ CUDAQ_TEST(D2VariationalTester, checkBroadcast) { cudaq::make_argset(params, std::vector(params.size() + 1, 2))); }); } + +#endif diff --git a/unittests/integration/gate_library_tester.cpp b/unittests/integration/gate_library_tester.cpp index b6b04a124a..ab61332edb 100644 --- a/unittests/integration/gate_library_tester.cpp +++ b/unittests/integration/gate_library_tester.cpp @@ -12,7 +12,7 @@ #include using namespace cudaq; -#ifndef CUDAQ_BACKEND_DM +#if !defined(CUDAQ_BACKEND_DM) && !defined(CUDAQ_BACKEND_STIM) namespace { // These tests are meant to validate the correctness of custom kernels. // Hence, reduce the test load on tensornet backends (slow for these small diff --git a/unittests/integration/get_state_tester.cpp b/unittests/integration/get_state_tester.cpp index 452c55c8bb..8136cf3177 100644 --- a/unittests/integration/get_state_tester.cpp +++ b/unittests/integration/get_state_tester.cpp @@ -14,6 +14,9 @@ using namespace cudaq; +// State operations not supported in Stim. +#ifndef CUDAQ_BACKEND_STIM + CUDAQ_TEST(GetStateTester, checkSimple) { auto kernel = []() __qpu__ { cudaq::qubit q, r; @@ -184,3 +187,5 @@ CUDAQ_TEST(GetStateTester, checkKron) { EXPECT_EQ(counts.begin()->first, "0" + std::string(num_qubits_input_state, '1')); } + +#endif diff --git a/unittests/integration/ghz_nisq_tester.cpp b/unittests/integration/ghz_nisq_tester.cpp index c9535424cc..004b9a4c1f 100644 --- a/unittests/integration/ghz_nisq_tester.cpp +++ b/unittests/integration/ghz_nisq_tester.cpp @@ -101,13 +101,13 @@ CUDAQ_TEST(GHZSampleTester, checkBroadcastRepeatability) { std::vector sizeVals(8); std::iota(sizeVals.begin(), sizeVals.end(), 3); - cudaq::set_random_seed(13); + cudaq::set_random_seed(130); auto allCounts1 = cudaq::sample(2000, ghz{}, cudaq::make_argset(sizeVals)); - cudaq::set_random_seed(13); + cudaq::set_random_seed(130); auto allCounts2 = cudaq::sample(2000, ghz{}, cudaq::make_argset(sizeVals)); - cudaq::set_random_seed(14); + cudaq::set_random_seed(140); auto allCounts3 = cudaq::sample(2000, ghz{}, cudaq::make_argset(sizeVals)); EXPECT_EQ(allCounts1, allCounts2); // these should match diff --git a/unittests/integration/gradient_tester.cpp b/unittests/integration/gradient_tester.cpp index 49e03ecf28..c6508524d4 100644 --- a/unittests/integration/gradient_tester.cpp +++ b/unittests/integration/gradient_tester.cpp @@ -11,6 +11,8 @@ #include #include +#ifndef CUDAQ_BACKEND_STIM + // Skip these gradient tests for slow backends to reduce test time. // Note: CUDA-Q API level tests (e.g., `cudaq::observe`) should cover all // backend-specific functionalities required to interface gradient modules. @@ -74,3 +76,5 @@ CUDAQ_TEST(GradientTester, checkSimple) { } #endif + +#endif diff --git a/unittests/integration/grover_test.cpp b/unittests/integration/grover_test.cpp index 3e64a749c7..361c53511c 100644 --- a/unittests/integration/grover_test.cpp +++ b/unittests/integration/grover_test.cpp @@ -48,6 +48,9 @@ struct oracle { } }; +// Multi-control gates not supported in stim. +#ifndef CUDAQ_BACKEND_STIM + CUDAQ_TEST(GroverTester, checkNISQ) { using namespace cudaq; auto counts = cudaq::sample(1000, run_grover{}, 3, 1, oracle{}); @@ -60,3 +63,5 @@ CUDAQ_TEST(GroverTester, checkNISQ) { } EXPECT_EQ(counter, 1000); } + +#endif diff --git a/unittests/integration/kernels_tester.cpp b/unittests/integration/kernels_tester.cpp index 15fcabd649..dcc40b3b88 100644 --- a/unittests/integration/kernels_tester.cpp +++ b/unittests/integration/kernels_tester.cpp @@ -108,7 +108,7 @@ CUDAQ_TEST(KernelsTester, checkGetAlphaZ) { } } -#ifndef CUDAQ_BACKEND_DM +#if !defined(CUDAQ_BACKEND_DM) && !defined(CUDAQ_BACKEND_STIM) CUDAQ_TEST(KernelsTester, checkFromState) { { diff --git a/unittests/integration/negative_controls_tester.cpp b/unittests/integration/negative_controls_tester.cpp index c46e8ac48c..1db60165f2 100644 --- a/unittests/integration/negative_controls_tester.cpp +++ b/unittests/integration/negative_controls_tester.cpp @@ -27,6 +27,7 @@ CUDAQ_TEST(NegativeControlsTester, checkSimple) { EXPECT_EQ(counter, 1000); +#ifndef CUDAQ_BACKEND_STIM auto kernel2 = []() __qpu__ { cudaq::qarray<4> q; x(!q[0], !q[1], !q[2], q[3]); @@ -43,6 +44,7 @@ CUDAQ_TEST(NegativeControlsTester, checkSimple) { } EXPECT_EQ(counter, 1000); +#endif auto kernel3 = []() __qpu__ { cudaq::qarray<2> q; diff --git a/unittests/integration/nlopt_tester.cpp b/unittests/integration/nlopt_tester.cpp index 197d6ffc7e..e28ebf93d5 100644 --- a/unittests/integration/nlopt_tester.cpp +++ b/unittests/integration/nlopt_tester.cpp @@ -13,6 +13,9 @@ #include #include +// Rotational gates not supported in Stim. +#ifndef CUDAQ_BACKEND_STIM + // Skip these Nlopt optimizer tests for slow backends to reduce test time. // Note: CUDA-Q API level tests (e.g., `cudaq::observe`) should cover all // backend-specific functionalities required to interface with optimizers. @@ -98,3 +101,5 @@ CUDAQ_TEST(NloptTester, checkOtherSignatures) { } #endif + +#endif diff --git a/unittests/integration/observe_result_tester.cpp b/unittests/integration/observe_result_tester.cpp index d33357c76e..0a6783627c 100644 --- a/unittests/integration/observe_result_tester.cpp +++ b/unittests/integration/observe_result_tester.cpp @@ -9,6 +9,9 @@ #include "CUDAQTestUtils.h" #include +// Rotational gates not supported in Stim. +#ifndef CUDAQ_BACKEND_STIM + struct deuteron_n3_ansatz { void operator()(double x0, double x1) __qpu__ { cudaq::qvector q(3); @@ -110,3 +113,4 @@ CUDAQ_TEST(ObserveResult, checkExpValBug) { EXPECT_NEAR(exp, .79, 1e-1); } #endif +#endif diff --git a/unittests/integration/qpe_ftqc.cpp b/unittests/integration/qpe_ftqc.cpp index 2a777ecfb4..7a8f219a8a 100644 --- a/unittests/integration/qpe_ftqc.cpp +++ b/unittests/integration/qpe_ftqc.cpp @@ -63,8 +63,13 @@ struct qpe { } }; +// Rotational gates not supported in Stim. +#ifndef CUDAQ_BACKEND_STIM + CUDAQ_TEST(QPEFTQCTester, checkSimple) { double phase = qpe{}(3, 1); EXPECT_NEAR(phase, .125, 1e-4); printf("Phase = %lf\n", phase); } + +#endif diff --git a/unittests/integration/qpe_nisq.cpp b/unittests/integration/qpe_nisq.cpp index c39b4974b0..bfb36f481e 100644 --- a/unittests/integration/qpe_nisq.cpp +++ b/unittests/integration/qpe_nisq.cpp @@ -12,6 +12,9 @@ #include +// Rotational gates not supported in Stim. +#ifndef CUDAQ_BACKEND_STIM + struct iqft { void operator()(cudaq::qview<> &q) __qpu__ { int N = q.size(); @@ -125,3 +128,5 @@ CUDAQ_TEST(QPENisqTester, checkPerfectForwardingBug) { EXPECT_EQ(1, counts.size()); EXPECT_TRUE(counts.begin()->first == "100"); } + +#endif diff --git a/unittests/integration/qubit_allocation.cpp b/unittests/integration/qubit_allocation.cpp index c368fb03af..a95d6b6788 100644 --- a/unittests/integration/qubit_allocation.cpp +++ b/unittests/integration/qubit_allocation.cpp @@ -10,6 +10,9 @@ #include #include +// Stim does not support arbitrary state vectors. +#ifndef CUDAQ_BACKEND_STIM + std::vector randomState(int numQubits) { std::vector stateVec(1ULL << numQubits); std::generate(stateVec.begin(), stateVec.end(), []() -> cudaq::complex { @@ -331,3 +334,5 @@ CUDAQ_TEST(AllocationTester, checkStateFromMpsData) { } } #endif + +#endif diff --git a/unittests/integration/vqe_tester.cpp b/unittests/integration/vqe_tester.cpp index 5e4b5b9389..06152a8463 100644 --- a/unittests/integration/vqe_tester.cpp +++ b/unittests/integration/vqe_tester.cpp @@ -13,6 +13,9 @@ #include #include +// Stim does not support rotational gates +#ifndef CUDAQ_BACKEND_STIM + // Skip these VQE tests for slow backends to reduce test time. // Note: CUDA-Q API level tests (e.g., `cudaq::observe`) should cover all // backend-specific functionalities required for the `cudaq::vqe` wrapper. @@ -250,3 +253,5 @@ CUDAQ_TEST_F(VQETester, checkThrowInvalidRuntimeArgs) { } #endif + +#endif diff --git a/unittests/qir/NVQIRTester.cpp b/unittests/qir/NVQIRTester.cpp index 8bee83701a..236c782257 100644 --- a/unittests/qir/NVQIRTester.cpp +++ b/unittests/qir/NVQIRTester.cpp @@ -119,6 +119,9 @@ CUDAQ_TEST(NVQIRTester, checkSimple) { __quantum__rt__finalize(); } +// Stim does not support many of the gates used in these tests. +#ifndef CUDAQ_BACKEND_STIM + CUDAQ_TEST(NVQIRTester, checkQuantumIntrinsics) { __quantum__rt__initialize(0, nullptr); auto qubits = __quantum__rt__qubit_allocate_array(3); @@ -581,3 +584,5 @@ CUDAQ_TEST(NVQIRTester, checkQubitAllocationFromRetrievedStateExpand) { __quantum__rt__finalize(); } + +#endif diff --git a/unittests/qis/QubitQISTester.cpp b/unittests/qis/QubitQISTester.cpp index ccb4e2acf6..796c4471f0 100644 --- a/unittests/qis/QubitQISTester.cpp +++ b/unittests/qis/QubitQISTester.cpp @@ -142,6 +142,7 @@ CUDAQ_TEST(QubitQISTester, checkCommonKernel) { } EXPECT_EQ(counter, 1000); +#ifndef CUDAQ_BACKEND_STIM auto ansatz = [](double theta) { cudaq::qvector q(2); x(q[0]); @@ -155,8 +156,10 @@ CUDAQ_TEST(QubitQISTester, checkCommonKernel) { .21829 * z(0) - 6.125 * z(1); auto energy = cudaq::observe(ansatz, h, .59); EXPECT_NEAR(energy, -1.7487, 1e-3); +#endif } +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(QubitQISTester, checkCtrlRegion) { auto ccnot = []() { @@ -227,7 +230,9 @@ CUDAQ_TEST(QubitQISTester, checkCtrlRegion) { EXPECT_EQ(1, counts3.size()); EXPECT_TRUE(counts3.begin()->first == "101"); } +#endif +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(QubitQISTester, checkAdjointRegions) { struct single_adjoint_test { void operator()() __qpu__ { @@ -334,6 +339,7 @@ CUDAQ_TEST(QubitQISTester, checkAdjointRegions) { EXPECT_EQ(1, counts5.size()); EXPECT_TRUE(counts5.begin()->first == "101"); } +#endif CUDAQ_TEST(QubitQISTester, checkMeasureResetFence) { { @@ -367,6 +373,7 @@ CUDAQ_TEST(QubitQISTester, checkMeasureResetFence) { } } +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(QubitQISTester, checkU3Op) { auto check_x = []() { cudaq::qubit q; @@ -391,7 +398,9 @@ CUDAQ_TEST(QubitQISTester, checkU3Op) { EXPECT_TRUE(bits == "00" || bits == "11"); } } +#endif +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(QubitQISTester, checkU3Ctrl) { auto another_bell_pair = []() { cudaq::qvector qubits(2); @@ -404,7 +413,9 @@ CUDAQ_TEST(QubitQISTester, checkU3Ctrl) { EXPECT_TRUE(bits == "00" || bits == "11"); } } +#endif +#ifndef CUDAQ_BACKEND_STIM CUDAQ_TEST(QubitQISTester, checkU3Adj) { auto rotation_adjoint_test = []() { cudaq::qubit q; @@ -423,9 +434,12 @@ CUDAQ_TEST(QubitQISTester, checkU3Adj) { EXPECT_TRUE(bits == "0"); } } +#endif using namespace std::complex_literals; +#ifndef CUDAQ_BACKEND_STIM + // Test someone can build a library of custom operations CUDAQ_REGISTER_OPERATION( /* Name */ CustomHadamard, /*NumTargets*/ 1, /*NumParameters*/ 0, @@ -644,3 +658,4 @@ CUDAQ_TEST(CustomUnitaryTester, checkMultiQubitOps) { } #endif +#endif