diff --git a/CMakeLists.txt b/CMakeLists.txt index 05f3e7d..3eb2675 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #----------------------------------------------------------------------------# -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.18) # Set QIREE_VERSION using git tags using the following format set(CGV_TAG_REGEX "v([0-9.]+)(-dev|-rc.[0-9]+)?") @@ -12,12 +12,13 @@ include("${CMAKE_CURRENT_LIST_DIR}/cmake/CgvFindVersion.cmake") cgv_find_version(QIREE) project(QIREE VERSION "${QIREE_VERSION}" LANGUAGES CXX) -cmake_policy(VERSION 3.12...3.22) +cmake_policy(VERSION 3.18...3.30) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") include(GNUInstallDirs) include(CMakePackageConfigHelpers) +include(FetchContent) include("${CMAKE_CURRENT_LIST_DIR}/cmake/QIREEUtils.cmake") macro(qiree_set_default name value) @@ -34,9 +35,11 @@ endmacro() # Components option(QIREE_BUILD_DOCS "Build QIR-EE documentation" OFF) -option(QIREE_BUILD_TESTS "Build QIR-EE unit tests" OFF) +option(QIREE_BUILD_TESTS "Build QIR-EE unit tests" ON) option(QIREE_BUILD_EXAMPLES "Build QIR-EE examples" OFF) +option(QIREE_USE_QSIM "Download and build Google qsim backend" OFF) option(QIREE_USE_XACC "Build XACC interface" ON) + qiree_set_default(BUILD_TESTING ${QIREE_BUILD_TESTS}) # Assertion handling @@ -111,8 +114,28 @@ set(QIREE_RUNTIME_OUTPUT_DIRECTORY enable_language(C) # Needed for LLVM find_package(LLVM REQUIRED) if((LLVM_VERSION VERSION_LESS 14) - OR (LLVM_VERSION VERSION_GREATER_EQUAL 19)) - message(WARNING "QIR-EE is only tested with LLVM 14-18: found version ${LLVM_VERSION}") + OR (LLVM_VERSION VERSION_GREATER_EQUAL 20)) + message(WARNING "QIR-EE is only tested with LLVM 14-19: found version ${LLVM_VERSION}") +endif() + +if(QIREE_USE_QSIM) + # Declare and download qsim: it's header-only and the code is in "lib", + # so download it into "external/qsim" directory and include "external" + FetchContent_Declare( + qsim_content + QUIET + GIT_REPOSITORY https://github.com/quantumlib/qsim.git + GIT_TAG 55b4d0e7ea8f085a1709c2c06ff1e28b3aa93357 # 'main' on 22 Nov 2024 + SOURCE_SUBDIR "lib" # Don't load top-level cmake file + SOURCE_DIR "external/qsim" + ) + FetchContent_MakeAvailable(qsim_content) + qiree_add_library(qiree_qsim INTERFACE) + add_library(QIREE::qsim ALIAS qiree_qsim) + target_include_directories(qiree_qsim SYSTEM INTERFACE + "$" + "$" + ) endif() if(QIREE_USE_XACC) @@ -185,7 +208,7 @@ add_subdirectory(app) #----------------------------------------------------------------------------# if(QIREE_BUILD_EXAMPLES) - add_subdirectory(examples) + add_subdirectory(examples) endif() #----------------------------------------------------------------------------# diff --git a/CMakePresets.json b/CMakePresets.json index 287e268..4d9e63b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,33 +1,61 @@ { - "version": 3, - "cmakeMinimumRequired": {"major": 3, "minor": 21, "patch": 0}, - "configurePresets": [ - { - "name": "default", - "displayName": "Automatic options (debug with tests)", - "description": "Dependencies are enabled based on environment probing", - "binaryDir": "${sourceDir}/build-${presetName}", - "generator": "Ninja", - "cacheVariables": { - "BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"}, - "CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Debug"}, - "CMAKE_INSTALL_PREFIX": "${sourceDir}/install-${presetName}" - } - } - ], - "buildPresets": [ - { - "name": "default", - "jobs": 0, - "configurePreset": "default" - } - ], - "testPresets": [ - { - "name": "default", - "configurePreset": "default", - "output": {"outputOnFailure": true}, - "execution": {"noTestsAction": "error", "stopOnFailure": false, "jobs": 8} - } - ] -} +"version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "displayName": "Automatic options (debug with tests)", + "description": "Dependencies are enabled based on environment probing", + "binaryDir": "${sourceDir}/build-${presetName}", + "generator": "Ninja", + "cacheVariables": { + "BUILD_SHARED_LIBS": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": { + "type": "STRING", + "value": "Debug" + }, + "CMAKE_INSTALL_PREFIX": "${sourceDir}/install-${presetName}" + } + }, + { + "name": "default", + "displayName": "Clang 16.0.6 x86_64-pc-linux-gnu", + "description": "Using compilers: C = /usr/bin/clang-16, CXX = /usr/bin/clang++-16", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "CMAKE_C_COMPILER": "/usr/bin/clang-16", + "CMAKE_CXX_COMPILER": "/usr/bin/clang++-16", + "CMAKE_BUILD_TYPE": "Debug" + } + } + ], + "buildPresets": [ + { + "name": "default", + "jobs": 0, + "configurePreset": "default" + } + ], + "testPresets": [ + { + "name": "default", + "configurePreset": "default", + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error", + "stopOnFailure": false, + "jobs": 8 + } + } + ] +} \ No newline at end of file diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 5b1939b..4bf7330 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -4,8 +4,8 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #-----------------------------------------------------------------------------# -include(FetchContent) FetchContent_Declare( + # Command Line Parser for C++ programs cli11_proj QUIET GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git @@ -14,6 +14,24 @@ FetchContent_Declare( FetchContent_MakeAvailable(cli11_proj) +#-----------------------------------------------------------------------------# +# QSIM FRONT END +#-----------------------------------------------------------------------------# + +if(QIREE_USE_QSIM) + qiree_add_executable(qir-qsim + qir-qsim.cc + ) + target_link_libraries(qir-qsim + PUBLIC QIREE::qiree QIREE::qirqsim + PRIVATE CLI11::CLI11 + ) +endif() + +#-----------------------------------------------------------------------------# +# XACC FRONT END +#-----------------------------------------------------------------------------# + if(QIREE_USE_XACC) qiree_add_executable(qir-xacc qir-xacc.cc diff --git a/app/qir-qsim.cc b/app/qir-qsim.cc new file mode 100644 index 0000000..92f4669 --- /dev/null +++ b/app/qir-qsim.cc @@ -0,0 +1,98 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qir-xacc/qir-xacc.cc +//---------------------------------------------------------------------------// +#include +#include +#include +#include +#include + +#include "qiree_version.h" + +#include "qiree/Executor.hh" +#include "qiree/Module.hh" +#include "qiree/QuantumNotImpl.hh" +#include "qirqsim/QsimDefaultRuntime.hh" +#include "qirqsim/QsimQuantum.hh" + +using namespace std::string_view_literals; + +namespace qiree +{ +namespace app +{ +void run(std::string const& filename, + int num_shots) + // bool group_tuples = false) +{ + // Load the input + Executor execute{Module{filename}}; + + // Set up qsim + QsimQuantum sim(std::cout, 0); + + // Collect the statistics + std::unique_ptr rt; + rt = std::make_unique(std::cout, sim); + + // Run several time = shots (default 1) + for (int i = 0; i < num_shots; i++) + { + execute(sim, *rt); + } + + std::cout << std::endl; + std::cout << "Measurement output:" << std::endl; + std::cout << "-------------------" << std::endl; + std::cout << "Number of shots: " << num_shots << std::endl; + std::cout << "Number of qubits: " << sim.num_qubits() << std::endl; + + for(int q_index = 0; q_index < sim.num_qubits(); q_index++){ + int value_0 = 0; + int value_1 = 0; + if (auto value = sim.manager.getBufferValue("q"+std::to_string(q_index), "0"); value.has_value()){ value_0 = value.value();} + if (auto value = sim.manager.getBufferValue("q"+std::to_string(q_index), "1"); value.has_value()){ value_1 = value.value();} + std::cout << "q" << q_index << " {0: " << value_0 << "," << " 1: " << value_1 << "}\n"; + } +} + +//---------------------------------------------------------------------------// +} // namespace app +} // namespace qiree + +//---------------------------------------------------------------------------// +/*! + * Execute and run. + */ +int main(int argc, char* argv[]) +{ + int num_shots{1}; + std::string filename; + //bool group_tuples{false}; + + CLI::App app; + + auto* filename_opt + = app.add_option("--input,-i,input", filename, "QIR input file"); + filename_opt->required(); + + auto* nshot_opt + = app.add_option("-s,--shots", num_shots, "Number of shots"); + nshot_opt->capture_default_str(); + + //app.add_flag("--group-tuples,!--no-group-tuples", + // group_tuples, + // "Print per-tuple measurement statistics rather than " + // "per-qubit"); + + CLI11_PARSE(app, argc, argv); + + //qiree::app::run(filename, num_shots, group_tuples); + qiree::app::run(filename, num_shots); + + return EXIT_SUCCESS; +} diff --git a/examples/bell_ccx.ll b/examples/bell_ccx.ll new file mode 100644 index 0000000..e5b2ea7 --- /dev/null +++ b/examples/bell_ccx.ll @@ -0,0 +1,43 @@ +; ModuleID = 'Bell_ccx' +source_filename = "Bell_ccx" + +%Qubit = type opaque +%Result = type opaque + +define void @main() #0 { +entry: + call void @__quantum__qis__h__body(%Qubit* null) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__ccx__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) + call void @__quantum__rt__array_record_output(i64 3, i8* null) + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) + ret void +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__x__body(%Qubit*) + +declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*) + +declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 + +declare void @__quantum__rt__array_record_output(i64, i8*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +attributes #0 = { "entry_point" "num_required_qubits"="3" "num_required_results"="3" "output_labeling_schema" "qir_profiles"="custom" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} + diff --git a/examples/dynamicbv.ll b/examples/dynamicbv.ll new file mode 100644 index 0000000..6d48157 --- /dev/null +++ b/examples/dynamicbv.ll @@ -0,0 +1,101 @@ +; ModuleID = 'dynamicbv' +source_filename = "dynamicbv" + +; ModuleID = 'BernsteinVazirani' +source_filename = "bv_algorithm" + +%Qubit = type opaque +%Result = type opaque + +define void @main() #0 { +entry: + ; Initialize qubits + call void @__quantum__qis__h__body(%Qubit* null) ; apply Hadamard to query qubit + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; set ancillary qubit + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; + + + ; Apply CNOT for bit '1' + call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) ; kickback phase on q0 + call void @__quantum__qis__h__body(%Qubit* null) ; correcting eigenvalue + + ; Mid-circuit measurement + call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) ; from this we get the first bit + call i1 @__quantum__qis__read_result__body(%Result* null) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) ; just to reset ancillary qubit + call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) + + ; Output the results + call void @__quantum__rt__array_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + + ; Initialize qubits + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; set ancillary qubit + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; + call void @__quantum__qis__h__body(%Qubit* null) ; apply Hadamard to query qubit + + ; Apply Identiry for bit '0' + ; Nothing + + ; Mid-circuit measurement + call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) ; from this we get the first bit + call i1 @__quantum__qis__read_result__body(%Result* null) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) ; just to reset ancillary qubit + call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) + + ; Output the results + call void @__quantum__rt__array_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + + ; Initialize qubits + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; set ancillary qubit + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; + call void @__quantum__qis__h__body(%Qubit* null) ; apply Hadamard to query qubit + + ; Apply CNOT for bit '1' + call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) ; kickback phase on q0 + call void @__quantum__qis__h__body(%Qubit* null) ; correcting eigenvalue + + ; Mid-circuit measurement + call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) ; from this we get the first bit + call i1 @__quantum__qis__read_result__body(%Result* null) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) ; just to reset ancillary qubit + call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) + + ; Output the results + call void @__quantum__rt__array_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + + ret void +} + +; Declaration of quantum operations +declare void @__quantum__qis__h__body(%Qubit*) +declare void @__quantum__qis__x__body(%Qubit*) +declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) +declare void @__quantum__qis__mz__body(%Qubit*, %Result*) +declare i1 @__quantum__qis__read_result__body(%Result*) + +; Quantum runtime functions for managing qubits and results +declare %Qubit* @__quantum__rt__qubit_allocate() +declare %Result* @__quantum__rt__result_allocate() +declare void @__quantum__rt__qubit_release(%Qubit*) +declare void @__quantum__rt__result_release(%Result*) +declare void @__quantum__rt__result_record_output(%Result*, i8*) +declare void @__quantum__rt__array_record_output(i64, i8*) + + + +attributes #0 = { "entry_point" "num_required_qubits"="2" "num_required_results"="2" "output_labeling_schema" "qir_profiles"="custom" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} + diff --git a/examples/teleport.ll b/examples/teleport.ll index 3bf36e9..6fcb74e 100644 --- a/examples/teleport.ll +++ b/examples/teleport.ll @@ -6,6 +6,7 @@ source_filename = "teleport" define void @main() #0 { entry: + call void @__quantum__qis__x__body(%Qubit* null) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) @@ -37,6 +38,7 @@ else2: ; preds = %continue continue3: ; preds = %else2, %then1 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) + %2 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 2 to %Result*)) call void @__quantum__rt__array_record_output(i64 3, i8* null) call void @__quantum__rt__result_record_output(%Result* null, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) diff --git a/scripts/cmake-presets/goldfinger.json b/scripts/cmake-presets/goldfinger.json index 0e21a45..a056420 100644 --- a/scripts/cmake-presets/goldfinger.json +++ b/scripts/cmake-presets/goldfinger.json @@ -9,7 +9,7 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Debug"}, "CMAKE_EXPORT_COMPILE_COMMANDS": {"type": "BOOL", "value": "ON"}, - "CMAKE_OSX_DEPLOYMENT_TARGET": {"type": "STRING", "value": "14"}, + "CMAKE_OSX_DEPLOYMENT_TARGET": {"type": "STRING", "value": "15"}, "CMAKE_CXX_STANDARD": {"type": "STRING", "value": "17"}, "CMAKE_CXX_EXTENSIONS": {"type": "BOOL", "value": "OFF"}, "CMAKE_FIND_FRAMEWORK": {"type": "STRING", "value": "LAST"}, @@ -17,7 +17,7 @@ "CMAKE_CXX_FLAGS": "-Wall -Wextra -Werror -Wno-error=deprecated -pedantic -fdiagnostics-color=always" }, "environment": { - "CMAKE_PREFIX_PATH": "/opt/homebrew/Cellar/llvm/18.1.8:/opt/spack/var/spack/environments/xacc/.spack-env/view:$env{HOME}/Code/xacc/install" + "CMAKE_PREFIX_PATH": "/opt/homebrew/opt/llvm:/opt/spack/var/spack/environments/xacc/.spack-env/view:$env{HOME}/Code/xacc/install" } }, { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 845a29a..b01bf2f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,8 +12,13 @@ qiree_configure_file("qiree_config.h.in" "qiree_config.h" @ONLY) #----------------------------------------------------------------------------# add_subdirectory(qiree) + if(QIREE_USE_XACC) add_subdirectory(qirxacc) endif() +if(QIREE_USE_QSIM) + add_subdirectory(qirqsim) +endif() + #---------------------------------------------------------------------------## diff --git a/src/qiree/CMakeLists.txt b/src/qiree/CMakeLists.txt index 1c2be37..b19ff14 100644 --- a/src/qiree/CMakeLists.txt +++ b/src/qiree/CMakeLists.txt @@ -19,6 +19,7 @@ qiree_add_library(qiree Module.cc Executor.cc QuantumNotImpl.cc + OutputDistribution.cc ) target_compile_features(qiree PUBLIC cxx_std_17) target_link_libraries(qiree diff --git a/src/qiree/OutputDistribution.cc b/src/qiree/OutputDistribution.cc new file mode 100644 index 0000000..0d0297f --- /dev/null +++ b/src/qiree/OutputDistribution.cc @@ -0,0 +1,64 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qiree/Buffer.hh +//---------------------------------------------------------------------------// + +#include "OutputDistribution.hh" + +#include +#include +#include + +namespace qiree +{ + + + +void Buffer::updateBuffer(std::string const& qubit, + std::string const& state, + int const& value) +{ + // Insert or update the key-value pair in the buffer + std::pair searchKey = {qubit, state}; + int current_frequency = 0; + auto it = buffer.find(searchKey); + if (it != buffer.end()) + { + current_frequency = it->second; + } + // Accumulate counts with every shot + buffer[{qubit, state}] = value + current_frequency; +} + +void Buffer::updateBuffer(std::string const& key, int const& value) +{ + // Insert or update the key-value pair in the buffer + simple_buffer[key] = value; +} + +std::optional Buffer::getBufferValue(std::string const& qubit, + std::string const& state) const +{ + std::pair searchKey = {qubit, state}; + auto it = buffer.find(searchKey); + if (it != buffer.end()) + { + return it->second; // Key found + } + return std::nullopt; // Key not found +} + +std::optional Buffer::getBufferValue(std::string const& key) const +{ + auto it = simple_buffer.find(key); + if (it != simple_buffer.end()) + { + return it->second; // Key found + } + return std::nullopt; // Key not found +} + +} // namespace qiree \ No newline at end of file diff --git a/src/qiree/OutputDistribution.hh b/src/qiree/OutputDistribution.hh new file mode 100644 index 0000000..398c88e --- /dev/null +++ b/src/qiree/OutputDistribution.hh @@ -0,0 +1,65 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qiree/Buffer.hh +//---------------------------------------------------------------------------// + +#pragma once + +#include +#include +#include +#include +#include + +namespace qiree +{ + +// Define a hash function for std::pair + +struct pair_hash +{ + template + std::size_t operator()(std::pair const& pair) const + { + auto hash1 = std::hash{}(pair.first); + auto hash2 = std::hash{}(pair.second); + // Combine the two hash values + return hash1 ^ (hash2 << 1); // Shift and XOR + } +}; + +class Buffer +{ + public: + // Method to update the buffer with a key-value pair + // TODO: Don't use strings here + void updateBuffer(std::string const& qubit, + std::string const& state, + int const& value); + void updateBuffer(std::string const& key, int const& value); + + // Retrieve buffer value for storage or evaluation + std::optional + getBufferValue(std::string const& qubit, std::string const& state) const; + std::optional getBufferValue(std::string const& key) const; + + private: + // Dictionary to store key-value pairs + std::unordered_map, int, pair_hash> buffer; + std::unordered_map simple_buffer; +}; + +// BUFFER_H + +} // namespace qiree + + + + + + + + diff --git a/src/qiree_config.h.in b/src/qiree_config.h.in index 475c792..d46b752 100644 --- a/src/qiree_config.h.in +++ b/src/qiree_config.h.in @@ -10,5 +10,7 @@ #define qiree_config_h #cmakedefine01 QIREE_DEBUG +#cmakedefine01 QIREE_USE_QSIM +#cmakedefine01 QIREE_USE_XACC #endif /* qiree_config_h */ diff --git a/src/qirqsim/CMakeLists.txt b/src/qirqsim/CMakeLists.txt new file mode 100644 index 0000000..0d81dec --- /dev/null +++ b/src/qirqsim/CMakeLists.txt @@ -0,0 +1,28 @@ +#---------------------------------*-CMake-*----------------------------------# +# Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +# See the top-level COPYRIGHT file for details. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#----------------------------------------------------------------------------# + +# Adding qsim as a library to qiree +qiree_add_library(qirqsim + QsimQuantum.cc + QsimDefaultRuntime.cc +) + +#Link the qsim library to qiree and any other relevant libraries +target_link_libraries(qirqsim + PUBLIC QIREE::qiree # Link to qiree + PRIVATE QIREE::qsim +) + +#----------------------------------------------------------------------------# +# HEADERS +#----------------------------------------------------------------------------# + +# Install headers, matching the relevant .hh files for qsim integration +install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/qirqsim" + COMPONENT development + FILES_MATCHING REGEX ".*\\.hh?$" +) diff --git a/src/qirqsim/QsimDefaultRuntime.cc b/src/qirqsim/QsimDefaultRuntime.cc new file mode 100644 index 0000000..4ece7c1 --- /dev/null +++ b/src/qirqsim/QsimDefaultRuntime.cc @@ -0,0 +1,73 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirqsim/QsimDefaultRuntime.cc +//---------------------------------------------------------------------------// +#include "QsimDefaultRuntime.hh" + +#include + +#include "qiree/Assert.hh" + +namespace qiree +{ +//---------------------------------------------------------------------------// +/*! + * Initialize the execution environment, resetting qubits. + */ + +void QsimDefaultRuntime::initialize(OptionalCString env) +{ + if (env) + { + output_ << "Argument to initialize: " << env << std::endl; + } +} + +//---------------------------------------------------------------------------// +/*! + * Execute circuit and mark the following N results as being part of an array + * named tag + */ + +void QsimDefaultRuntime::array_record_output(size_type s, OptionalCString tag) +{ + // this->execute_if_needed(); + // output_ << "array " << (tag ? tag : "") << " length " << s + // << std::endl; +} + +//---------------------------------------------------------------------------// +/*! + * Execute circuit and mark the following N results as being part of a tuple + * named tag + */ + +void QsimDefaultRuntime::tuple_record_output(size_type s, OptionalCString tag) +{ + // this->execute_if_needed(); + // output_ << "tuple " << (tag ? tag : "") << " length " << s + // << std::endl; +} + +//---------------------------------------------------------------------------// +/*! + * Execute circuit and report a single measurement result + */ +void QsimDefaultRuntime::result_record_output(Result r, OptionalCString tag) +{ + // Access values through the getter + // This prints results every time result_record_output is called + // Can comment out if only want to see final results + + if (auto value = sim_.manager.getBufferValue("q" + std::to_string(r.value)); + value.has_value()) + { + std::cout << "q" << std::to_string(r.value) << " : " << value.value() + << "\n"; + } +} + +} // namespace qiree diff --git a/src/qirqsim/QsimDefaultRuntime.hh b/src/qirqsim/QsimDefaultRuntime.hh new file mode 100644 index 0000000..fb0b7a3 --- /dev/null +++ b/src/qirqsim/QsimDefaultRuntime.hh @@ -0,0 +1,62 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirqsim/QsimDefaultRuntime.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "QsimQuantum.hh" + +namespace qiree +{ + +/*! + * Print per-qubit measurement statistics. + * + * Example for three qubits: + * \code + * Measurement output: + * ------------------- + * Number of shots: 1024 + * Number of qubits: 3 + * q0 {0: 542, 1: 482} + * q1 {0: 521, 1: 503} + * q2 {0: 0, 1: 1024} + * + * \endcode + */ + +class QsimDefaultRuntime final : virtual public RuntimeInterface +{ + public: + /*! + * Construct \c QsimDefaultRuntime. + */ + QsimDefaultRuntime(std::ostream& output, QsimQuantum& sim) + : output_(output), sim_(sim) + { + } + + //!@{ + //! \name Runtime interface + // Initialize the execution environment, resetting qubits + void initialize(OptionalCString env) override; + + //! Mark the following N results as being part of an array named tag + void array_record_output(size_type, OptionalCString tag) final; + + //! Mark the following N results as being part of a tuple named tag + void tuple_record_output(size_type, OptionalCString) final; + + // Save one result + void result_record_output(Result result, OptionalCString tag) final; + //!@} + + private: + std::ostream& output_; + QsimQuantum& sim_; +}; + +} // namespace qiree diff --git a/src/qirqsim/QsimQuantum.cc b/src/qirqsim/QsimQuantum.cc new file mode 100644 index 0000000..b71ccf4 --- /dev/null +++ b/src/qirqsim/QsimQuantum.cc @@ -0,0 +1,302 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirxacc/QsimQuantum.cc +//---------------------------------------------------------------------------// + +#include "QsimQuantum.hh" + +#include +#include +#include +#include +#include +#include + +#include "qiree/Assert.hh" + +// Qsim +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// + +namespace qiree +{ +//---------------------------------------------------------------------------// +/*! + * Factory class for creating simulators in qsim. + */ +struct QsimQuantum::Factory +{ + Factory(unsigned num_threads) : num_threads(num_threads) {} + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + + StateSpace CreateStateSpace() const { return StateSpace(num_threads); } + Simulator CreateSimulator() const { return Simulator(num_threads); } + unsigned num_threads; +}; + +//---------------------------------------------------------------------------// +/*! + * Quantum state and circuit. + */ +struct QsimQuantum::State +{ + qsim::Circuit> circuit; + std::optional state; +}; + +//---------------------------------------------------------------------------// +/*! + * Initialize the qsim simulator + */ +QsimQuantum::QsimQuantum(std::ostream& os, unsigned long int seed) + : output_(os), seed_(seed), state_{std::make_unique()} +{ +} + +//---------------------------------------------------------------------------// +//! Default destructor +QsimQuantum::~QsimQuantum() = default; + +//---------------------------------------------------------------------------// +/*! + * Prepare to build a quantum circuit for an entry point + */ +void QsimQuantum::set_up(EntryPointAttrs const& attrs) +{ + QIREE_VALIDATE(attrs.required_num_qubits > 0, + << "input is not a quantum program"); + // Resize the result_to_qubit_ vector, based on the required number of + // results... the idea is to have as many classical registers as qubits + // (probably not true in general) + result_to_qubit_.resize(attrs.required_num_results); + num_qubits_ = attrs.required_num_qubits; // Set the number of qubits + + // Get the number of threads + num_threads_ + = std::max(1, static_cast(std::thread::hardware_concurrency())); + + // Initialize the qsim simulator + auto state_space = Factory(num_threads_).CreateStateSpace(); // Create the + // state + // space + + // Create the state + state_->state = state_space.Create(this->num_qubits()); + // Check if the state is null + QIREE_VALIDATE(!state_space.IsNull(*state_->state), + << "not enough memory: is the number of qubits too large?"); + + // TODO: initial states shouldn't necessarily be zero + state_space.SetStateZero(*state_->state); + + // Allocate the number of qubits in the circuit + state_->circuit.num_qubits = num_qubits_; + gate_index_ = 0; // Initialize execution time +} + +//---------------------------------------------------------------------------// +/*! + * Complete an execution + */ +void QsimQuantum::tear_down() +{ + state_->circuit = {}; +} + +//---------------------------------------------------------------------------// +/*! + * Reset the qubit + */ +void QsimQuantum::reset(Qubit q) +{ + q.value = 0; +} + +//----------------------------------------------------------------------------// +/*! + * Read the value of a result. This utilizes the new BufferManager. + */ +QState QsimQuantum::read_result(Result r) +{ + using Fuser = qsim::MultiQubitGateFuser>; + using Runner = qsim::QSimRunner; + using StateSpace = Factory::StateSpace; + + // Vector to hold measurement results, this must be empty before running + std::vector meas_results; + std::string stringResult; + + Runner::Parameter qsimParam; // Parameters for qsim + qsimParam.seed = seed_; + seed_++; + qsimParam.max_fused_size = 2; // Set the maximum size of fused gates + qsimParam.verbosity = 0; // see verbosity in run_qsim.h + + // Run the simulation + bool const run_success = Runner::Run(qsimParam, + Factory(num_threads_), + state_->circuit, + *state_->state, + meas_results); + + QIREE_ASSERT(run_success); // Ensure the run was successful + // reset circuit here + state_->circuit = {}; + state_->circuit.num_qubits = num_qubits_; + + if (meas_results.size() == 1 && meas_results[0].bitstring.size() == 1) + { + auto const bitResult = meas_results[0].bitstring[0]; + QIREE_ASSERT(bitResult == 0 || bitResult == 1); + std::string stringResult = std::to_string(bitResult); + std::string q_index_string = std::to_string(r.value); + if (stringResult == "1") + { + manager.updateBuffer("q" + q_index_string, "1", 1); + manager.updateBuffer("q" + q_index_string, 1); + } + else + { + manager.updateBuffer("q" + q_index_string, "0", 1); + manager.updateBuffer("q" + q_index_string, 0); + } + } + else + { + qsim::IO::errorf("Unexpected measurement results encountered."); + } + return static_cast(meas_results[0].bitstring[0]); +} + +//---------------------------------------------------------------------------// +/*! + * Map a qubit to a result index. + * + * (TODO: find how to link the classical register to the quantum register in + * qsim) + */ +void QsimQuantum::mz(Qubit q, Result r) +{ // we don't classical register yet. + QIREE_EXPECT(q.value < this->num_qubits()); // TODO: q must be in the set + // of qubits, e.g., what + // happens if q=5 and qubits + // are {2,3,4,5}, q is less + // than num_qubits but not it + // is in the set of qubits. + // TODO: maybe not what we want long term + QIREE_EXPECT(q.value == r.value); + // Add measurement instruction + state_->circuit.gates.push_back( + qsim::gate::Measurement>::Create( + gate_index_++, {static_cast(q.value)})); +} + +//---------------------------------------------------------------------------// +/* + * Quantum Instruction Mapping + */ + +// 1. Entangling gates +void QsimQuantum::cx(Qubit q1, Qubit q2) +{ + state_->circuit.gates.push_back( + qsim::GateCNot::Create(gate_index_++, q1.value, q2.value)); +} +void QsimQuantum::cnot(Qubit q1, Qubit q2) +{ + state_->circuit.gates.push_back( + qsim::GateCNot::Create(gate_index_++, q1.value, q2.value)); +} +void QsimQuantum::cz(Qubit q1, Qubit q2) +{ + state_->circuit.gates.push_back( + qsim::GateCZ::Create(gate_index_++, q1.value, q2.value)); +} +// 2. Local gates +void QsimQuantum::h(Qubit q) +{ + state_->circuit.gates.push_back( + qsim::GateHd::Create(gate_index_++, q.value)); +} +void QsimQuantum::s(Qubit q) +{ + state_->circuit.gates.push_back( + qsim::GateS::Create(gate_index_++, q.value)); +} +void QsimQuantum::t(Qubit q) +{ + state_->circuit.gates.push_back( + qsim::GateT::Create(gate_index_++, q.value)); +} +// 2.1 Pauli gates +void QsimQuantum::x(Qubit q) +{ + state_->circuit.gates.push_back( + qsim::GateX::Create(gate_index_++, q.value)); +} +void QsimQuantum::y(Qubit q) +{ + state_->circuit.gates.push_back( + qsim::GateY::Create(gate_index_++, q.value)); +} +void QsimQuantum::z(Qubit q) +{ + state_->circuit.gates.push_back( + qsim::GateZ::Create(gate_index_++, q.value)); +} +// 2.2 rotation gates +void QsimQuantum::rx(double theta, Qubit q) +{ + state_->circuit.gates.push_back( + qsim::GateRX::Create(gate_index_++, q.value, theta)); +} +void QsimQuantum::ry(double theta, Qubit q) +{ + state_->circuit.gates.push_back( + qsim::GateRY::Create(gate_index_++, q.value, theta)); +} +void QsimQuantum::rz(double theta, Qubit q) +{ + state_->circuit.gates.push_back( + qsim::GateRZ::Create(gate_index_++, q.value, theta)); +} + +Qubit QsimQuantum::result_to_qubit(Result r) +{ + // TODO: This function is not working. Giving 0 every time. Maybe not + // needed. + QIREE_EXPECT(r.value < this->num_results()); + return result_to_qubit_[r.value]; // just copied this from the qirxacc, I + // have no idea if we need to do + // something else here +} + +void QsimQuantum::print_accelbuf() +{ + // TODO: to be implemented, we can create a buffer class to store the + // results +} + +void QsimQuantum::execute_if_needed() +{ + QIREE_EXPECT(false); +} + +} // namespace qiree diff --git a/src/qirqsim/QsimQuantum.hh b/src/qirqsim/QsimQuantum.hh new file mode 100644 index 0000000..cde10a8 --- /dev/null +++ b/src/qirqsim/QsimQuantum.hh @@ -0,0 +1,114 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirqsim/QsimQuantum.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include +#include + +#include "qiree/Macros.hh" +#include "qiree/QuantumNotImpl.hh" +#include "qiree/RuntimeInterface.hh" +#include "qiree/Types.hh" +#include "qiree/OutputDistribution.hh" + +namespace qiree +{ +//---------------------------------------------------------------------------// +/*! + * Create and execute quantum circuits using google Qsim. + */ +class QsimQuantum final : virtual public QuantumNotImpl +{ + public: + // Construct with number of shots + QsimQuantum(std::ostream& os, unsigned long int shots); + ~QsimQuantum(); + + QIREE_DELETE_COPY_MOVE(QsimQuantum); // Delete copy and move constructors + + //!@{ + //! \name Accessors + size_type num_results() const { return result_to_qubit_.size(); } + size_type num_qubits() const { return num_qubits_; } + //!@} + + //!@{ + //! \name Quantum interface + // Prepare to build a quantum circuit for an entry point + void set_up(EntryPointAttrs const&) override; + + // Complete an execution + void tear_down() override; + + // Map a qubit to a result index + void mz(Qubit, Result) final; + + // Read the value of a result. + QState read_result(Result) final; + //!@} + + //!@{ + //! \name Utilities for runtime + // Get runtime qubit corresponding to a runtime result + Qubit result_to_qubit(Result); + + // Run the circuit on the accelerator if we have not already. Returns true + // if the circuit was executed. + void execute_if_needed(); + + void print_accelbuf(); + //!@} + + //!@{ + //! \name Circuit construction + // void ccx(Qubit, Qubit) final; + void ccnot(Qubit, Qubit, Qubit); // TODO: not in examples or qir runner + void cnot(Qubit, Qubit) final; + void cx(Qubit, Qubit) final; + // void cy(Qubit, Qubit) final; + void cz(Qubit, Qubit) final; + void h(Qubit) final; + void reset(Qubit) final; + void rx(double, Qubit) final; + void ry(double, Qubit) final; + void rz(double, Qubit) final; + // void rzz(double, Qubit, Qubit) final; + void s(Qubit) final; + // void s_adj(Qubit) final; + // void swap(Qubit, Qubit) final; + void t(Qubit) final; + // void t_adj(Qubit) final; + void x(Qubit) final; + void y(Qubit) final; + void z(Qubit) final; + //!@} + + // Update the buffer + Buffer manager; + + private: + + //// TYPES //// + + struct Factory; + struct State; + + //// DATA //// + + std::ostream& output_; + unsigned long int seed_{}; + std::unique_ptr state_; + + unsigned num_threads_{}; // Number of threads to use + size_t gate_index_; // when the quantum operation will be executed + size_type num_qubits_{}; + std::vector result_to_qubit_; +}; + +} // namespace qiree diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c4191cc..af87510 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -55,3 +55,13 @@ if(QIREE_USE_XACC) endif() #---------------------------------------------------------------------------## + +#---------------------------------------------------------------------------## +# QIRQSIM TESTS +#---------------------------------------------------------------------------## + +if(QIREE_USE_QSIM) + qiree_add_test(qirqsim QsimQuantum) +endif() + +#---------------------------------------------------------------------------## diff --git a/test/data/dynamicbv.ll b/test/data/dynamicbv.ll new file mode 100644 index 0000000..6d48157 --- /dev/null +++ b/test/data/dynamicbv.ll @@ -0,0 +1,101 @@ +; ModuleID = 'dynamicbv' +source_filename = "dynamicbv" + +; ModuleID = 'BernsteinVazirani' +source_filename = "bv_algorithm" + +%Qubit = type opaque +%Result = type opaque + +define void @main() #0 { +entry: + ; Initialize qubits + call void @__quantum__qis__h__body(%Qubit* null) ; apply Hadamard to query qubit + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; set ancillary qubit + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; + + + ; Apply CNOT for bit '1' + call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) ; kickback phase on q0 + call void @__quantum__qis__h__body(%Qubit* null) ; correcting eigenvalue + + ; Mid-circuit measurement + call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) ; from this we get the first bit + call i1 @__quantum__qis__read_result__body(%Result* null) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) ; just to reset ancillary qubit + call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) + + ; Output the results + call void @__quantum__rt__array_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + + ; Initialize qubits + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; set ancillary qubit + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; + call void @__quantum__qis__h__body(%Qubit* null) ; apply Hadamard to query qubit + + ; Apply Identiry for bit '0' + ; Nothing + + ; Mid-circuit measurement + call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) ; from this we get the first bit + call i1 @__quantum__qis__read_result__body(%Result* null) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) ; just to reset ancillary qubit + call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) + + ; Output the results + call void @__quantum__rt__array_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + + ; Initialize qubits + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; set ancillary qubit + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) ; + call void @__quantum__qis__h__body(%Qubit* null) ; apply Hadamard to query qubit + + ; Apply CNOT for bit '1' + call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*)) ; kickback phase on q0 + call void @__quantum__qis__h__body(%Qubit* null) ; correcting eigenvalue + + ; Mid-circuit measurement + call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) ; from this we get the first bit + call i1 @__quantum__qis__read_result__body(%Result* null) + call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) ; just to reset ancillary qubit + call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) + + ; Output the results + call void @__quantum__rt__array_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + + ret void +} + +; Declaration of quantum operations +declare void @__quantum__qis__h__body(%Qubit*) +declare void @__quantum__qis__x__body(%Qubit*) +declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) +declare void @__quantum__qis__mz__body(%Qubit*, %Result*) +declare i1 @__quantum__qis__read_result__body(%Result*) + +; Quantum runtime functions for managing qubits and results +declare %Qubit* @__quantum__rt__qubit_allocate() +declare %Result* @__quantum__rt__result_allocate() +declare void @__quantum__rt__qubit_release(%Qubit*) +declare void @__quantum__rt__result_release(%Result*) +declare void @__quantum__rt__result_record_output(%Result*, i8*) +declare void @__quantum__rt__array_record_output(i64, i8*) + + + +attributes #0 = { "entry_point" "num_required_qubits"="2" "num_required_results"="2" "output_labeling_schema" "qir_profiles"="custom" } +attributes #1 = { "irreversible" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} + diff --git a/test/qirqsim/QsimQuantum.test.cc b/test/qirqsim/QsimQuantum.test.cc new file mode 100644 index 0000000..21395cc --- /dev/null +++ b/test/qirqsim/QsimQuantum.test.cc @@ -0,0 +1,101 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2024 UT-Battelle, LLC, and other QIR-EE developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//---------------------------------------------------------------------------// +//! \file qirxacc/QsimQuantum.test.cc +//---------------------------------------------------------------------------// +#include "qirqsim/QsimQuantum.hh" + +#include + +#include "qiree/Types.hh" +#include "qiree_test.hh" +#include "qirqsim/QsimDefaultRuntime.hh" + +namespace qiree +{ +namespace test +{ +//---------------------------------------------------------------------------// + +class QsimQuantumTest : public ::qiree::test::Test +{ + protected: + void SetUp() override {} + + static std::string clean_output(std::string&& s) + { + std::string result = std::move(s); + static std::regex const subs_ptr("0x[0-9a-f]+"); + result = std::regex_replace(result, subs_ptr, "0x0"); + return result; + } +}; + + +TEST_F(QsimQuantumTest, sim_dynamicbv) +{ + using Q = Qubit; + using R = Result; + + std::ostringstream os; + os << '\n'; + + // Create a simulator that will write to the string stream + QsimQuantum qsim_sim{os, 0}; + QsimDefaultRuntime qsim_rt{os, qsim_sim}; + + // Call functions in the same sequence that dynamicbv.ll would + qsim_sim.set_up([] { + EntryPointAttrs attrs; + attrs.required_num_qubits = 2; + attrs.required_num_results = 2; + return attrs; + }()); + qsim_sim.h(Q{0}); + qsim_sim.x(Q{1}); + qsim_sim.h(Q{1}); + qsim_sim.cnot(Q{0},Q{1}); + qsim_sim.h(Q{0}); + qsim_sim.mz(Q{0}, R{0}); + qsim_sim.read_result(R{0}); + qsim_sim.mz(Q{1}, R{1}); + qsim_sim.read_result(R{1}); + qsim_rt.array_record_output(2,""); + qsim_rt.result_record_output(R{0},""); + qsim_rt.result_record_output(R{1},""); + qsim_sim.h(Q{0}); + qsim_sim.x(Q{1}); + qsim_sim.h(Q{1}); + qsim_sim.mz(Q{0}, R{0}); + qsim_sim.read_result(R{0}); + qsim_sim.mz(Q{1}, R{1}); + qsim_sim.read_result(R{1}); + qsim_rt.array_record_output(2,""); + qsim_rt.result_record_output(R{0},""); + qsim_rt.result_record_output(R{1},""); + qsim_sim.h(Q{0}); + qsim_sim.x(Q{1}); + qsim_sim.h(Q{1}); + qsim_sim.cnot(Q{0},Q{1}); + qsim_sim.h(Q{0}); + qsim_sim.mz(Q{0}, R{0}); + qsim_sim.read_result(R{0}); + qsim_sim.mz(Q{1}, R{1}); + qsim_sim.read_result(R{1}); + qsim_rt.array_record_output(2,""); + qsim_rt.result_record_output(R{0},""); + qsim_rt.result_record_output(R{1},""); + qsim_sim.tear_down(); + + ASSERT_EQ(2, qsim_sim.num_qubits()); + EXPECT_EQ(1, qsim_sim.manager.getBufferValue("q0", "0").value()); + EXPECT_EQ(2, qsim_sim.manager.getBufferValue("q0", "1").value()); + EXPECT_EQ(2, qsim_sim.manager.getBufferValue("q1", "0").value()); + EXPECT_EQ(1, qsim_sim.manager.getBufferValue("q1", "1").value()); +} + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace qiree