From f30735821f6acd091ea45fb38e8937745951da59 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 6 Jun 2024 15:09:20 +0200 Subject: [PATCH 01/56] Update RegisterManager to support a qubit and a bit register. --- include/qx/CompileTimeConfiguration.hpp | 6 +- include/qx/RegisterManager.hpp | 79 +++++++++++++++----- include/qx/V3xLibqasmInterface.hpp | 1 + src/qx/RegisterManager.cpp | 99 ++++++++++++++++++------- src/qx/V3xLibqasmInterface.cpp | 4 + 5 files changed, 141 insertions(+), 48 deletions(-) diff --git a/include/qx/CompileTimeConfiguration.hpp b/include/qx/CompileTimeConfiguration.hpp index 9d92b52b..2f815fdb 100644 --- a/include/qx/CompileTimeConfiguration.hpp +++ b/include/qx/CompileTimeConfiguration.hpp @@ -17,8 +17,10 @@ static constexpr std::uint64_t ZERO_CYCLE_SIZE = 100; // Maximum number of qubits that can be used. // Maybe memory-saving as a multiple of 64. -// In the future, make this a template parameter and change the data structures -// used based on the runtime number of qubits. static constexpr std::size_t MAX_QUBIT_NUMBER = 64; +// Maximum number of bits that can be used. +// Just for sanity, as we maintain vectors of the size of the number of used bits. +static constexpr std::size_t MAX_BIT_NUMBER = 1*1024*1024; // 1 MB + } // namespace qx::config diff --git a/include/qx/RegisterManager.hpp b/include/qx/RegisterManager.hpp index e11c52c2..7e0fead9 100644 --- a/include/qx/RegisterManager.hpp +++ b/include/qx/RegisterManager.hpp @@ -1,6 +1,7 @@ #pragma once #include "qx/V3xLibqasmInterface.hpp" +#include "qx/SimulationError.hpp" #include "v3x/cqasm-semantic-gen.hpp" #include // size_t @@ -11,33 +12,68 @@ namespace qx { -using VariableName = std::string; - -struct QubitRange { - std::size_t first; - std::size_t size; -}; - /* - * RegisterManager keeps track of a (virtual) qubit register, i.e., an array of consecutive qubits, - * and the mappings between the (logical) qubit variable names, as used in an input cQASM program, - * and the (virtual) qubit register. + * RegisterManager keeps track of a (virtual) qubit register and a (virtual) bit register. + * I.e., an array of consecutive qubits/bits, and the mappings between the (logical) qubit/bit variable names, + * as used in an input cQASM program, and the (virtual) qubit/bit register. * * For example, given an input program that defines 'qubit[3] q': * - variable 'q' is mapped to qubits 0 to 2 in the qubit register, and * - positions 0 to 2 in the qubit register are mapped to variable 'q'. * - * The mapping of qubit variable names to positions in the qubit register is an implementation detail, - * i.e., it is not guaranteed that qubit register indices are assigned to qubit variable names in the order + * The mapping of qubit/bit variable names to positions in the qubit/bit register is an implementation detail, + * i.e., it is not guaranteed that qubit/bit register indices are assigned to qubit/bit variable names in the order * these variables are defined in the input program. */ -class RegisterManager { - using VariableNameToQubitRangeMapT = std::unordered_map; - using QubitIndexToVariableNameMapT = std::vector; + + +using VariableName = std::string; + + +struct Range { + std::size_t first; + std::size_t size; +}; + + +struct RegisterManagerError : public SimulationError { + explicit RegisterManagerError(const std::string &message); +}; + + +class Register { + using VariableNameToRangeMapT = std::unordered_map; + using IndexToVariableNameMapT = std::vector; private: - std::size_t qubit_register_size_; - VariableNameToQubitRangeMapT variable_name_to_qubit_range_; - QubitIndexToVariableNameMapT qubit_index_to_variable_name_; + std::size_t register_size_; + VariableNameToRangeMapT variable_name_to_range_; + IndexToVariableNameMapT index_to_variable_name_; +public: + Register(const V3OneProgram &program, auto &&is_of_type, std::size_t max_register_size); + virtual ~Register() = 0; + [[nodiscard]] std::size_t size() const; + [[nodiscard]] virtual Range at(const VariableName &name) const; + [[nodiscard]] virtual VariableName at(const std::size_t &index) const; +}; + + +class QubitRegister : public Register { +public: + explicit QubitRegister(const V3OneProgram &program); + virtual ~QubitRegister() override; +}; + + +class BitRegister : public Register { +public: + explicit BitRegister(const V3OneProgram &program); + virtual ~BitRegister() override; +}; + + +class RegisterManager { + QubitRegister qubit_register_; + BitRegister bit_register_; public: RegisterManager(const RegisterManager&) = delete; RegisterManager(RegisterManager&&) noexcept = delete; @@ -46,8 +82,11 @@ class RegisterManager { public: explicit RegisterManager(const V3OneProgram &program); [[nodiscard]] std::size_t get_qubit_register_size() const; - [[nodiscard]] QubitRange get_qubit_range(const VariableName &name) const; - [[nodiscard]] VariableName get_variable_name(std::size_t index) const; + [[nodiscard]] std::size_t get_bit_register_size() const; + [[nodiscard]] Range get_qubit_range(const VariableName &name) const; + [[nodiscard]] Range get_bit_range(const VariableName &name) const; + [[nodiscard]] VariableName get_qubit_variable_name(const std::size_t &index) const; + [[nodiscard]] VariableName get_bit_variable_name(const std::size_t &index) const; }; } // namespace qx diff --git a/include/qx/V3xLibqasmInterface.hpp b/include/qx/V3xLibqasmInterface.hpp index 476e360a..bb77abf8 100644 --- a/include/qx/V3xLibqasmInterface.hpp +++ b/include/qx/V3xLibqasmInterface.hpp @@ -29,5 +29,6 @@ using V3Value = v3_values::Value; using V3Variable = v3_semantic::Variable; bool is_qubit_variable(const V3Variable &variable); +bool is_bit_variable(const V3Variable &variable); } // namespace qx diff --git a/src/qx/RegisterManager.cpp b/src/qx/RegisterManager.cpp index 5eed5474..906f98fb 100644 --- a/src/qx/RegisterManager.cpp +++ b/src/qx/RegisterManager.cpp @@ -1,9 +1,9 @@ #include "qx/CompileTimeConfiguration.hpp" #include "qx/RegisterManager.hpp" -#include "qx/SimulationError.hpp" #include "qx/V3xLibqasmInterface.hpp" #include // fill +#include #include #include #include @@ -11,47 +11,94 @@ namespace qx { -struct RegisterManagerError : public SimulationError { - explicit RegisterManagerError(const std::string &message) - : SimulationError{ message } - {} -}; +RegisterManagerError::RegisterManagerError(const std::string &message) + : SimulationError{ message } +{} -RegisterManager::RegisterManager(const V3OneProgram &program) { - auto &&qubit_variables = program->variables.get_vec() - | ranges::views::filter([&](const V3OneVariable &variable) { return is_qubit_variable(*variable); }); - auto &&qubit_variable_sizes = qubit_variables +Register::Register(const V3OneProgram &program, auto &&is_of_type, std::size_t max_register_size) { + auto &&variables = program->variables.get_vec() + | ranges::views::filter([&](const V3OneVariable &variable) { return is_of_type(*variable); }); + auto &&variable_sizes = variables | ranges::views::transform([](const V3OneVariable &variable) { return v3_types::size_of(variable->typ); }); - qubit_register_size_ = ranges::accumulate(qubit_variable_sizes, size_t{}); + register_size_ = ranges::accumulate(variable_sizes, size_t{}); - if (qubit_register_size_ > config::MAX_QUBIT_NUMBER) { - throw RegisterManagerError{ "Cannot run that many qubits in this version of QX-simulator" }; + if (register_size_ > max_register_size) { + throw RegisterManagerError{ fmt::format("{}", register_size_) }; } - variable_name_to_qubit_range_.reserve(qubit_register_size_); - qubit_index_to_variable_name_.resize(qubit_register_size_); + variable_name_to_range_.reserve(register_size_); + index_to_variable_name_.resize(register_size_); - auto current_qubit_index = size_t{}; - for (auto &&variable: qubit_variables) { + auto current_index = size_t{}; + for (auto &&variable: variables) { const auto &variable_size = static_cast(cqasm::v3x::types::size_of(variable->typ)); - variable_name_to_qubit_range_[variable->name] = QubitRange{ current_qubit_index, variable_size }; - std::fill(qubit_index_to_variable_name_.begin() + static_cast(current_qubit_index), - qubit_index_to_variable_name_.begin() + static_cast(current_qubit_index + variable_size), + variable_name_to_range_[variable->name] = Range{ current_index, variable_size }; + std::fill(index_to_variable_name_.begin() + static_cast(current_index), + index_to_variable_name_.begin() + static_cast(current_index + variable_size), variable->name); - current_qubit_index += variable_size; + current_index += variable_size; }; } +Register::~Register() = default; + +[[nodiscard]] std::size_t Register::size() const { + return register_size_; +} + +[[nodiscard]] Range Register::at(const VariableName &name) const { + return variable_name_to_range_.at(name); +} + +[[nodiscard]] VariableName Register::at(const std::size_t &index) const { + return index_to_variable_name_.at(index); +} + +QubitRegister::QubitRegister(const V3OneProgram &program) try + : Register(program, is_qubit_variable, config::MAX_QUBIT_NUMBER) { +} catch (const RegisterManagerError &e) { + throw RegisterManagerError{ fmt::format("Qubit register size exceeds maximum allowed: {} > {}", + e.what(), config::MAX_QUBIT_NUMBER) }; +} + +QubitRegister::~QubitRegister() = default; + +BitRegister::BitRegister(const V3OneProgram &program) try + : Register(program, is_bit_variable, config::MAX_BIT_NUMBER) { +} catch (const RegisterManagerError &e) { + throw RegisterManagerError{ fmt::format("Bit register size exceeds maximum allowed: {} > {}", + e.what(), config::MAX_BIT_NUMBER) }; +} + +BitRegister::~BitRegister() = default; + +RegisterManager::RegisterManager(const V3OneProgram &program) + : qubit_register_{ program } + , bit_register_{ program } { +} + [[nodiscard]] std::size_t RegisterManager::get_qubit_register_size() const { - return qubit_register_size_; + return qubit_register_.size(); +} + +[[nodiscard]] std::size_t RegisterManager::get_bit_register_size() const { + return bit_register_.size(); +} + +[[nodiscard]] Range RegisterManager::get_qubit_range(const VariableName &name) const { + return qubit_register_.at(name); +} + +[[nodiscard]] Range RegisterManager::get_bit_range(const VariableName &name) const { + return bit_register_.at(name); } -[[nodiscard]] QubitRange RegisterManager::get_qubit_range(const VariableName &name) const { - return variable_name_to_qubit_range_.at(name); +[[nodiscard]] VariableName RegisterManager::get_qubit_variable_name(const std::size_t &index) const { + return qubit_register_.at(index); } -[[nodiscard]] VariableName RegisterManager::get_variable_name(std::size_t index) const { - return qubit_index_to_variable_name_.at(index); +[[nodiscard]] VariableName RegisterManager::get_bit_variable_name(const std::size_t &index) const { + return bit_register_.at(index); } } // namespace qx diff --git a/src/qx/V3xLibqasmInterface.cpp b/src/qx/V3xLibqasmInterface.cpp index 402b6bc5..1fdbf841 100644 --- a/src/qx/V3xLibqasmInterface.cpp +++ b/src/qx/V3xLibqasmInterface.cpp @@ -7,4 +7,8 @@ bool is_qubit_variable(const V3Variable &variable) { return variable.typ->as_qubit() || variable.typ->as_qubit_array(); } +bool is_bit_variable(const V3Variable &variable) { + return variable.typ->as_bit() || variable.typ->as_bit_array(); +} + } // namespace qx From fed0070a9eaa105c8fb107de83199f3ea8bce4e5 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 6 Jun 2024 17:36:14 +0200 Subject: [PATCH 02/56] Refactor Core. Extract DensityUnitaryMatrix, QuantumState, and SparseArray to separate files. Remove include/qx/Common.hpp. Remove src/qx/Core.cpp. --- CMakeLists.txt | 3 +- include/qx/Circuit.hpp | 6 +- include/qx/Common.hpp | 8 - include/qx/Core.hpp | 301 ++---------------------------- include/qx/DenseUnitaryMatrix.hpp | 90 +++++++++ include/qx/ErrorModels.hpp | 2 +- include/qx/Gates.hpp | 2 +- include/qx/QuantumState.hpp | 170 +++++++++++++++++ include/qx/SimulationResult.hpp | 10 +- include/qx/SparseArray.hpp | 92 +++++++++ src/qx/Circuit.cpp | 2 +- src/qx/Core.cpp | 100 ---------- src/qx/QuantumState.cpp | 50 +++++ src/qx/SimulationResult.cpp | 7 +- src/qx/SparseArray.cpp | 61 ++++++ test/DenseUnitaryMatrixTest.cpp | 2 +- test/ErrorModelsTest.cpp | 10 +- test/QuantumStateTest.cpp | 3 +- test/SparseArrayTest.cpp | 2 +- 19 files changed, 503 insertions(+), 418 deletions(-) delete mode 100644 include/qx/Common.hpp create mode 100644 include/qx/DenseUnitaryMatrix.hpp create mode 100644 include/qx/QuantumState.hpp create mode 100644 include/qx/SparseArray.hpp delete mode 100644 src/qx/Core.cpp create mode 100644 src/qx/QuantumState.cpp create mode 100644 src/qx/SparseArray.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 88d39205..8c7b3b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,16 +120,17 @@ find_package(libqasm REQUIRED CONFIG) add_library(qx "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Circuit.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Core.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/ErrorModels.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/GateConvertor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Gates.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/OperandsHelper.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/QuantumState.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Qxelarator.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Random.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/RegisterManager.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/SimulationResult.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Simulator.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/SparseArray.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/V3xLibqasmInterface.cpp" ) diff --git a/include/qx/Circuit.hpp b/include/qx/Circuit.hpp index a45c5fe1..f1543b1f 100644 --- a/include/qx/Circuit.hpp +++ b/include/qx/Circuit.hpp @@ -1,6 +1,6 @@ #pragma once -#include "qx/Core.hpp" +#include "qx/Core.hpp" // BasisVector #include "qx/ErrorModels.hpp" #include "qx/RegisterManager.hpp" #include "qx/V3xLibqasmInterface.hpp" @@ -23,7 +23,7 @@ class Circuit { core::QubitIndex qubitIndex{}; }; struct MeasurementRegisterOperation { - std::function operation; + std::function operation; }; template struct Unitary { // Matrix is stored inline but could also be a pointer. @@ -53,7 +53,7 @@ class Circuit { // We could in the future add loops and if/else... Circuit(V3OneProgram &program, RegisterManager ®ister_manager); - RegisterManager& get_register_manager() const; + [[nodiscard]] RegisterManager& get_register_manager() const; void add_instruction(Instruction instruction, ControlBits control_bits); void execute(core::QuantumState &quantumState, error_models::ErrorModel const &errorModel) const; diff --git a/include/qx/Common.hpp b/include/qx/Common.hpp deleted file mode 100644 index f791ffc5..00000000 --- a/include/qx/Common.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "qx/CompileTimeConfiguration.hpp" -#include "qx/Utils.hpp" - -namespace qx { - -using BasisVector = utils::Bitset; - -} \ No newline at end of file diff --git a/include/qx/Core.hpp b/include/qx/Core.hpp index d6e2fdc8..258a89df 100644 --- a/include/qx/Core.hpp +++ b/include/qx/Core.hpp @@ -1,16 +1,25 @@ #pragma once -#include "absl/container/flat_hash_map.h" -#include +#include // size_t +#include // abs #include -#include -#include "qx/Common.hpp" -#include "qx/CompileTimeConfiguration.hpp" +#include "qx/CompileTimeConfiguration.hpp" // EPS, MAX_QUBIT_NUMBER +#include "qx/Utils.hpp" namespace qx::core { +struct QubitIndex { + std::size_t value; +}; + +struct BitIndex { + std::size_t value; +}; + +using BasisVector = utils::Bitset; + inline constexpr bool isNotNull(std::complex c) { #if defined(_MSC_VER) return c.real() > config::EPS || -c.real() > config::EPS || @@ -20,286 +29,4 @@ inline constexpr bool isNotNull(std::complex c) { #endif } -struct QubitIndex { - std::size_t value; -}; - -template class DenseUnitaryMatrix { -public: - using Matrix = std::array, N>, N>; - - static constexpr DenseUnitaryMatrix identity() { - Matrix m; - for (std::size_t i = 0; i < N; ++i) { - for (std::size_t j = 0; j < N; ++j) { - m[i][j] = (i == j) ? 1 : 0; - } - } - - return DenseUnitaryMatrix(m, false); - } - - explicit constexpr DenseUnitaryMatrix(Matrix const &m) - : DenseUnitaryMatrix(m, true) {} - - [[nodiscard]] inline constexpr const std::complex& at(std::size_t i, std::size_t j) const { - return matrix[i][j]; - } - - constexpr DenseUnitaryMatrix dagger() const { - Matrix m; - for (std::size_t i = 0; i < N; ++i) { - for (std::size_t j = 0; j < N; ++j) { - m[i][j] = std::conj(at(j, i)); - } - } - - return DenseUnitaryMatrix(m, false); - } - - constexpr bool operator==(DenseUnitaryMatrix const &other) const { - for (std::size_t i = 0; i < N; ++i) { - for (std::size_t j = 0; j < N; ++j) { - if (isNotNull(at(i, j) - other.at(i, j))) { - return false; - } - } - } - return true; - } - - constexpr DenseUnitaryMatrix - operator*(DenseUnitaryMatrix const &other) const { - Matrix m; - for (std::size_t i = 0; i < N; ++i) { - for (std::size_t j = 0; j < N; ++j) { - m[i][j] = 0; - for (std::size_t k = 0; k < N; ++k) { - m[i][j] += at(i, k) * other.at(k, j); - } - } - } - - return DenseUnitaryMatrix(m, false); - } - -private: - constexpr DenseUnitaryMatrix(Matrix const &m, bool checkIsUnitary) - : matrix(m) { - if (checkIsUnitary) { - checkUnitary(); - } - } - - constexpr void checkUnitary() const { - if (*this * dagger() != identity()) { - throw std::runtime_error("Matrix is not unitary"); - } - } - - std::array, N>, N> const matrix; -}; - -class QuantumState; - -class SparseArray { -public: - using Map = absl::flat_hash_map>; - using Iterator = Map::const_iterator; - - SparseArray() = delete; - - explicit SparseArray(std::size_t s) : size(s){}; - - [[nodiscard]] std::size_t getSize() const { return size; } - - [[nodiscard]] std::vector> testToVector() const { - std::vector> result(getSize(), 0); - - for (auto const &kv : *this) { - result[kv.first.toSizeT()] = kv.second; - } - - return result; - } - - [[nodiscard]] Iterator begin() const { return data.cbegin(); } - - [[nodiscard]] Iterator end() const { return data.cend(); } - - void set(BasisVector index, std::complex value); - - void clear() { data.clear(); } - - SparseArray &operator*=(double d) { - std::for_each(data.begin(), data.end(), - [d](auto &kv) { kv.second *= d; }); - return *this; - } - - template void forEach(F &&f) { - cleanupZeros(); - std::for_each(data.begin(), data.end(), f); - } - - template void forEachSorted(F &&f) { - cleanupZeros(); - std::vector>> sorted( - data.begin(), data.end()); - std::sort(sorted.begin(), sorted.end(), - [](auto const &left, auto const &right) { - return left.first < right.first; - }); - std::for_each(sorted.begin(), sorted.end(), f); - } - - template void eraseIf(F &&pred) { absl::erase_if(data, pred); } - -private: - friend QuantumState; - - // Let f build a new SparseArray to replace *this, assuming f is linear. - template void applyLinear(F &&f) { - // Every ZERO_CYCLE_SIZE gates, cleanup the 0s - if (zeroCounter >= config::ZERO_CYCLE_SIZE) { - cleanupZeros(); - } - ++zeroCounter; - - Map result; - - for (auto const &kv : data) { - f(kv.first, kv.second, result); - } - - data.swap(result); - } - - void cleanupZeros(); - - std::size_t const size = 0; - std::uint64_t zeroCounter = 0; - Map data; -}; - -class QuantumState { -public: - explicit QuantumState(std::size_t n) - : numberOfQubits(n), data(1 << numberOfQubits) { - assert(numberOfQubits > 0 && "QuantumState needs at least one qubit"); - assert(numberOfQubits <= config::MAX_QUBIT_NUMBER && - "QuantumState currently cannot support that many qubits with this version of QX-simulator"); - data.set(BasisVector{}, 1); // Start initialized in state 00...000 - }; - - [[nodiscard]] std::size_t getNumberOfQubits() const { return numberOfQubits; } - - void reset() { - data.clear(); - data.set(BasisVector{}, 1); // Start initialized in state 00...000 - measurementRegister.reset(); - } - - void testInitialize( - std::initializer_list>> values); - - template - QuantumState & - apply(DenseUnitaryMatrix<1 << NumberOfOperands> const &m, - std::array const &operands); - - template void forEach(F &&f) { data.forEachSorted(f); } - - [[nodiscard]] BasisVector getMeasurementRegister() const { return measurementRegister; } - - BasisVector &getMeasurementRegister() { return measurementRegister; } - - template - void measure(QubitIndex qubitIndex, F &&randomGenerator) { - auto rand = randomGenerator(); - double probabilityOfMeasuringOne = 0.; - - data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { - if (kv.first.test(qubitIndex.value)) { - probabilityOfMeasuringOne += std::norm(kv.second); - } - }); - - if (rand < probabilityOfMeasuringOne) { - data.eraseIf([qubitIndex](auto const &kv) { - return !kv.first.test(qubitIndex.value); - }); - data *= std::sqrt(1 / probabilityOfMeasuringOne); - measurementRegister.set(qubitIndex.value, true); - } else { - data.eraseIf([qubitIndex](auto const &kv) { - return kv.first.test(qubitIndex.value); - }); - data *= std::sqrt(1 / (1 - probabilityOfMeasuringOne)); - measurementRegister.set(qubitIndex.value, false); - } - } - - template void measureAll(F &&randomGenerator) { - auto rand = randomGenerator(); - double probability = 0.; - - auto measuredState = std::invoke([this, &probability, rand] { - for (auto const &kv : - data) { // Does this work with non-ordered iteration? - probability += std::norm(kv.second); - if (probability > rand) { - return kv; - } - } - throw std::runtime_error( - "Vector was not normalized at measurement location (a bug)"); - }); - - data.clear(); - data.set(measuredState.first, - measuredState.second / std::abs(measuredState.second)); - measurementRegister = measuredState.first; - } - - template - void prep(QubitIndex qubitIndex, F &&randomGenerator) { - // Measure + conditional X, and reset the measurement register. - auto rand = randomGenerator(); - double probabilityOfMeasuringOne = 0.; - - data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { - if (kv.first.test(qubitIndex.value)) { - probabilityOfMeasuringOne += std::norm(kv.second); - } - }); - - if (rand < probabilityOfMeasuringOne) { - data.eraseIf([qubitIndex](auto const &kv) { - return !kv.first.test(qubitIndex.value); - }); - SparseArray::Map newData; - for (auto kv : data.data) { - auto newKey = kv.first; - newKey.set(qubitIndex.value, false); - newData.insert(std::make_pair( - newKey, - kv.second * std::sqrt(1 / probabilityOfMeasuringOne))); - } - data.data = newData; // Could fix the interface - } else { - data.eraseIf([qubitIndex](auto const &kv) { - return kv.first.test(qubitIndex.value); - }); - data *= std::sqrt(1 / (1 - probabilityOfMeasuringOne)); - } - measurementRegister.set(qubitIndex.value, false); - }; - -private: - std::size_t const numberOfQubits = 1; - SparseArray data; - BasisVector measurementRegister{}; -}; - } // namespace qx::core diff --git a/include/qx/DenseUnitaryMatrix.hpp b/include/qx/DenseUnitaryMatrix.hpp new file mode 100644 index 00000000..cd1763af --- /dev/null +++ b/include/qx/DenseUnitaryMatrix.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include // size_t +#include // conj +#include // runtime_error + +#include "qx/Core.hpp" // isNotNull + + +namespace qx::core { + +template +class DenseUnitaryMatrix { +public: + using Matrix = std::array, N>, N>; + + static constexpr DenseUnitaryMatrix identity() { + Matrix m; + for (std::size_t i = 0; i < N; ++i) { + for (std::size_t j = 0; j < N; ++j) { + m[i][j] = (i == j) ? 1 : 0; + } + } + + return DenseUnitaryMatrix(m, false); + } + + explicit constexpr DenseUnitaryMatrix(Matrix const &m) + : DenseUnitaryMatrix(m, true) {} + + [[nodiscard]] inline constexpr const std::complex& at(std::size_t i, std::size_t j) const { + return matrix[i][j]; + } + + constexpr DenseUnitaryMatrix dagger() const { + Matrix m; + for (std::size_t i = 0; i < N; ++i) { + for (std::size_t j = 0; j < N; ++j) { + m[i][j] = std::conj(at(j, i)); + } + } + + return DenseUnitaryMatrix(m, false); + } + + constexpr bool operator==(DenseUnitaryMatrix const &other) const { + for (std::size_t i = 0; i < N; ++i) { + for (std::size_t j = 0; j < N; ++j) { + if (isNotNull(at(i, j) - other.at(i, j))) { + return false; + } + } + } + return true; + } + + constexpr DenseUnitaryMatrix + operator*(DenseUnitaryMatrix const &other) const { + Matrix m; + for (std::size_t i = 0; i < N; ++i) { + for (std::size_t j = 0; j < N; ++j) { + m[i][j] = 0; + for (std::size_t k = 0; k < N; ++k) { + m[i][j] += at(i, k) * other.at(k, j); + } + } + } + + return DenseUnitaryMatrix(m, false); + } + +private: + constexpr DenseUnitaryMatrix(Matrix const &m, bool checkIsUnitary) + : matrix(m) { + if (checkIsUnitary) { + checkUnitary(); + } + } + + constexpr void checkUnitary() const { + if (*this * dagger() != identity()) { + throw std::runtime_error("Matrix is not unitary"); + } + } + + std::array, N>, N> const matrix; +}; + +} // namespace qx::core diff --git a/include/qx/ErrorModels.hpp b/include/qx/ErrorModels.hpp index 1f93d5f7..19cec4d6 100644 --- a/include/qx/ErrorModels.hpp +++ b/include/qx/ErrorModels.hpp @@ -1,6 +1,6 @@ #pragma once -#include "qx/Core.hpp" +#include "qx/QuantumState.hpp" #include diff --git a/include/qx/Gates.hpp b/include/qx/Gates.hpp index f2f0683e..be2e63a0 100644 --- a/include/qx/Gates.hpp +++ b/include/qx/Gates.hpp @@ -1,6 +1,6 @@ #pragma once -#include "qx/Core.hpp" +#include "qx/DenseUnitaryMatrix.hpp" namespace qx::gates { diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp new file mode 100644 index 00000000..7f672ed9 --- /dev/null +++ b/include/qx/QuantumState.hpp @@ -0,0 +1,170 @@ +#pragma once + +#include +#include // size_t +#include // norm +#include +#include +#include // invoke, pair + +#include "qx/Core.hpp" // BasisVector, QubitIndex +#include "qx/CompileTimeConfiguration.hpp" // MAX_QUBIT_NUMBER +#include "qx/DenseUnitaryMatrix.hpp" +#include "qx/SparseArray.hpp" + + +namespace qx::core { + +class QuantumState { + + template + void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, + std::array const &operands, + BasisVector index, std::complex value, + SparseArray::Map &storage) { + utils::Bitset reducedIndex; + for (std::size_t i = 0; i < NumberOfOperands; ++i) { + reducedIndex.set(i, index.test(operands[NumberOfOperands - i - 1].value)); + } + + for (std::size_t i = 0; i < (1 << NumberOfOperands); ++i) { + std::complex addedValue = value * matrix.at(i, reducedIndex.toSizeT()); + + if (isNotNull(addedValue)) { + auto newIndex = index; + + for (std::size_t k = 0; k < NumberOfOperands; ++k) { + newIndex.set(operands[NumberOfOperands - k - 1].value, utils::getBit(i, k)); + } + + auto it = storage.try_emplace(newIndex, 0); + auto newValue = it.first->second + addedValue; + it.first->second = newValue; + } + } + } + +public: + explicit QuantumState(std::size_t n); + + [[nodiscard]] std::size_t getNumberOfQubits() const; + + void reset(); + + void testInitialize( + std::initializer_list>> values); + + template + QuantumState &apply(DenseUnitaryMatrix<1 << NumberOfOperands> const &m, + std::array const &operands) { + assert(NumberOfOperands <= numberOfQubits && + "Quantum gate has more operands than the number of qubits in this quantum state"); + assert(std::find_if(operands.begin(), operands.end(), + [this](auto qubitIndex) { + return qubitIndex.value >= numberOfQubits; + }) == operands.end() && + "Operand refers to a non-existing qubit"); + + data.applyLinear([&m, &operands, this](auto index, auto value, auto &storage) { + this->applyImpl(m, operands, index, value, storage); }); + + return *this; + } + + template + void forEach(F &&f) { + data.forEachSorted(f); + } + + [[nodiscard]] const BasisVector &getMeasurementRegister() const; + + template + void measure(QubitIndex qubitIndex, F &&randomGenerator) { + auto rand = randomGenerator(); + double probabilityOfMeasuringOne = 0.; + + data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { + if (kv.first.test(qubitIndex.value)) { + probabilityOfMeasuringOne += std::norm(kv.second); + } + }); + + if (rand < probabilityOfMeasuringOne) { + data.eraseIf([qubitIndex](auto const &kv) { + return !kv.first.test(qubitIndex.value); + }); + data *= std::sqrt(1 / probabilityOfMeasuringOne); + measurementRegister.set(qubitIndex.value, true); + } else { + data.eraseIf([qubitIndex](auto const &kv) { + return kv.first.test(qubitIndex.value); + }); + data *= std::sqrt(1 / (1 - probabilityOfMeasuringOne)); + measurementRegister.set(qubitIndex.value, false); + } + } + + template + void measureAll(F &&randomGenerator) { + auto rand = randomGenerator(); + double probability = 0.; + + auto measuredState = std::invoke([this, &probability, rand] { + for (auto const &kv : + data) { // Does this work with non-ordered iteration? + probability += std::norm(kv.second); + if (probability > rand) { + return kv; + } + } + throw std::runtime_error( + "Vector was not normalized at measurement location (a bug)"); + }); + + data.clear(); + data.set(measuredState.first, + measuredState.second / std::abs(measuredState.second)); + measurementRegister = measuredState.first; + } + + template + void prep(QubitIndex qubitIndex, F &&randomGenerator) { + // Measure + conditional X, and reset the measurement register. + auto rand = randomGenerator(); + double probabilityOfMeasuringOne = 0.; + + data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { + if (kv.first.test(qubitIndex.value)) { + probabilityOfMeasuringOne += std::norm(kv.second); + } + }); + + if (rand < probabilityOfMeasuringOne) { + data.eraseIf([qubitIndex](auto const &kv) { + return !kv.first.test(qubitIndex.value); + }); + SparseArray::Map newData; + for (auto kv : data.data) { + auto newKey = kv.first; + newKey.set(qubitIndex.value, false); + newData.insert(std::make_pair( + newKey, + kv.second * std::sqrt(1 / probabilityOfMeasuringOne))); + } + data.data = newData; // Could fix the interface + } else { + data.eraseIf([qubitIndex](auto const &kv) { + return kv.first.test(qubitIndex.value); + }); + data *= std::sqrt(1 / (1 - probabilityOfMeasuringOne)); + } + measurementRegister.set(qubitIndex.value, false); + }; + +private: + std::size_t const numberOfQubits = 1; + SparseArray data; + BasisVector measurementRegister{}; +}; + +} // namespace qx::core diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index e866cb39..e58958f9 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -1,6 +1,6 @@ #pragma once -#include "qx/Common.hpp" +#include "qx/Core.hpp" #include #include @@ -37,19 +37,19 @@ std::ostream &operator<<(std::ostream &os, SimulationResult const &r); class SimulationResultAccumulator { public: - explicit SimulationResultAccumulator(qx::core::QuantumState &s) : quantumState(s){}; + explicit SimulationResultAccumulator(core::QuantumState &s) : quantumState(s){}; - void append(BasisVector measuredState); + void append(core::BasisVector measuredState); SimulationResult get(); private: template void forAllNonZeroStates(F &&f); - std::string getStateString(BasisVector s); + std::string getStateString(core::BasisVector s); core::QuantumState &quantumState; - absl::btree_map measuredStates; + absl::btree_map measuredStates; std::uint64_t nMeasurements = 0; }; diff --git a/include/qx/SparseArray.hpp b/include/qx/SparseArray.hpp new file mode 100644 index 00000000..f68f27f9 --- /dev/null +++ b/include/qx/SparseArray.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include +#include // for_each, sort +#include +#include // size_t, uint64_t +#include // pair +#include + +#include "qx/Core.hpp" // BasisVector, QubitIndex +#include "qx/CompileTimeConfiguration.hpp" // ZERO_CYCLE_SIZE + + +namespace qx::core { + +class QuantumState; + +class SparseArray { +public: + using Map = absl::flat_hash_map>; + using Iterator = Map::const_iterator; + + SparseArray() = delete; + + explicit SparseArray(std::size_t s); + + [[nodiscard]] std::size_t getSize() const; + + [[nodiscard]] std::vector> testToVector() const; + + [[nodiscard]] Iterator begin() const; + + [[nodiscard]] Iterator end() const; + + void set(BasisVector index, std::complex value); + + void clear(); + + SparseArray &operator*=(double d); + + template + void forEach(F &&f) { + cleanupZeros(); + std::for_each(data.begin(), data.end(), f); + } + + template + void forEachSorted(F &&f) { + cleanupZeros(); + std::vector>> sorted( + data.begin(), data.end()); + std::sort(sorted.begin(), sorted.end(), + [](auto const &left, auto const &right) { + return left.first < right.first; + }); + std::for_each(sorted.begin(), sorted.end(), f); + } + + template + void eraseIf(F &&pred) { + absl::erase_if(data, pred); + } + +private: + friend QuantumState; + + // Let f build a new SparseArray to replace *this, assuming f is linear. + template + void applyLinear(F &&f) { + // Every ZERO_CYCLE_SIZE gates, cleanup the 0s + if (zeroCounter >= config::ZERO_CYCLE_SIZE) { + cleanupZeros(); + } + ++zeroCounter; + + Map result; + + for (auto const &kv : data) { + f(kv.first, kv.second, result); + } + + data.swap(result); + } + + void cleanupZeros(); + + std::size_t const size = 0; + std::uint64_t zeroCounter = 0; + Map data; +}; + +} // namespace qx::core diff --git a/src/qx/Circuit.cpp b/src/qx/Circuit.cpp index 6ba1bc37..8e06e199 100644 --- a/src/qx/Circuit.cpp +++ b/src/qx/Circuit.cpp @@ -44,7 +44,7 @@ Circuit::Circuit(V3OneProgram &program, RegisterManager ®ister_manager) } } -RegisterManager& Circuit::get_register_manager() const { +[[nodiscard]] RegisterManager& Circuit::get_register_manager() const { return register_manager_; } diff --git a/src/qx/Core.cpp b/src/qx/Core.cpp deleted file mode 100644 index acf8bb5d..00000000 --- a/src/qx/Core.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "qx/Core.hpp" - -namespace qx::core { - -namespace { - -template -void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, - std::array const &operands, - BasisVector index, std::complex value, - SparseArray::Map &storage) { - utils::Bitset reducedIndex; - for (std::size_t i = 0; i < NumberOfOperands; ++i) { - reducedIndex.set(i, index.test(operands[NumberOfOperands - i - 1].value)); - } - - for (std::size_t i = 0; i < (1 << NumberOfOperands); ++i) { - std::complex addedValue = value * matrix.at(i, reducedIndex.toSizeT()); - - if (isNotNull(addedValue)) { - auto newIndex = index; - - for (std::size_t k = 0; k < NumberOfOperands; ++k) { - newIndex.set(operands[NumberOfOperands - k - 1].value, utils::getBit(i, k)); - } - - auto it = storage.try_emplace(newIndex, 0); - auto newValue = it.first->second + addedValue; - it.first->second = newValue; - } - } -} - -} // namespace - -void SparseArray::set(BasisVector index, std::complex value) { -#ifndef NDEBUG - if (index.toSizeT() >= size) { - throw std::runtime_error("SparseArray::set index out of bounds"); - } -#endif - - if (std::abs(value) < config::EPS) { - return; - } - - data.try_emplace(index, value); -} - -void SparseArray::cleanupZeros() { - absl::erase_if(data, [](auto const &kv) { return !isNotNull(kv.second); }); - zeroCounter = 0; -} - -void QuantumState::testInitialize( - std::initializer_list>> values) { - data.clear(); - double norm = 0; - for (auto const &kv: values) { - BasisVector index(kv.first); - data.set(index, kv.second); - norm += std::norm(kv.second); - } - assert(!isNotNull(norm - 1)); -} - -template -QuantumState & -QuantumState::apply(DenseUnitaryMatrix<1 << NumberOfOperands> const &m, - std::array const &operands) { - assert(NumberOfOperands <= numberOfQubits && - "Quantum gate has more operands than the number of qubits in this " - "quantum state"); - assert(std::find_if(operands.begin(), operands.end(), - [this](auto qubitIndex) { - return qubitIndex.value >= numberOfQubits; - }) == operands.end() && - "Operand refers to a non-existing qubit"); - - data.applyLinear([&m, &operands](auto index, auto value, auto &storage) { - applyImpl(m, operands, index, value, storage); }); - - return *this; -} - -// Explicit instantiation for use in Circuit::execute, otherwise linking error. - -template QuantumState & -QuantumState::apply<1>(DenseUnitaryMatrix<1 << 1> const &m, - std::array const &operands); - -template QuantumState & -QuantumState::apply<2>(DenseUnitaryMatrix<1 << 2> const &m, - std::array const &operands); - -template QuantumState & -QuantumState::apply<3>(DenseUnitaryMatrix<1 << 3> const &m, - std::array const &operands); - -} // namespace qx::core diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp new file mode 100644 index 00000000..af77e5a8 --- /dev/null +++ b/src/qx/QuantumState.cpp @@ -0,0 +1,50 @@ +#include "qx/QuantumState.hpp" + +namespace qx::core { + +QuantumState::QuantumState(std::size_t n) + : numberOfQubits(n), data(1 << numberOfQubits) { + assert(numberOfQubits > 0 && "QuantumState needs at least one qubit"); + assert(numberOfQubits <= config::MAX_QUBIT_NUMBER && + "QuantumState currently cannot support that many qubits with this version of QX-simulator"); + data.set(BasisVector{}, 1); // Start initialized in state 00...000 +}; + +[[nodiscard]] std::size_t QuantumState::getNumberOfQubits() const { + return numberOfQubits; +} + +void QuantumState::reset() { + data.clear(); + data.set(BasisVector{}, 1); // Start initialized in state 00...000 + measurementRegister.reset(); +} + +void QuantumState::testInitialize( + std::initializer_list>> values) { + data.clear(); + double norm = 0; + for (auto const &kv: values) { + BasisVector index(kv.first); + data.set(index, kv.second); + norm += std::norm(kv.second); + } + assert(!isNotNull(norm - 1)); +} + +// Explicit instantiation for use in Circuit::execute, otherwise linking error. + +template QuantumState &QuantumState::apply<1>( + DenseUnitaryMatrix<1 << 1> const &m, std::array const &operands); + +template QuantumState &QuantumState::apply<2>( + DenseUnitaryMatrix<1 << 2> const &m, std::array const &operands); + +template QuantumState &QuantumState::apply<3>( + DenseUnitaryMatrix<1 << 3> const &m, std::array const &operands); + +[[nodiscard]] const BasisVector &QuantumState::getMeasurementRegister() const { + return measurementRegister; +} + +} // namespace qx::core diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index fd7d031f..f3529e2e 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -1,6 +1,7 @@ #include "qx/SimulationResult.hpp" -#include "qx/Core.hpp" +#include "qx/Core.hpp" // BasisVector +#include "qx/QuantumState.hpp" #include #include #include @@ -8,7 +9,7 @@ namespace qx { -void SimulationResultAccumulator::append(BasisVector measuredState) { +void SimulationResultAccumulator::append(core::BasisVector measuredState) { assert(measuredStates.size() <= (1u << quantumState.getNumberOfQubits())); measuredStates[measuredState]++; nMeasurements++; @@ -67,7 +68,7 @@ void SimulationResultAccumulator::forAllNonZeroStates(F &&f) { [&f, this](auto const &kv) { f(getStateString(kv.first), kv.second); }); } -std::string SimulationResultAccumulator::getStateString(BasisVector s) { +std::string SimulationResultAccumulator::getStateString(qx::core::BasisVector s) { auto str = s.toString(); return str.substr(str.size() - quantumState.getNumberOfQubits(), diff --git a/src/qx/SparseArray.cpp b/src/qx/SparseArray.cpp new file mode 100644 index 00000000..8547f7da --- /dev/null +++ b/src/qx/SparseArray.cpp @@ -0,0 +1,61 @@ +#include "qx/SparseArray.hpp" + +namespace qx::core { + +SparseArray::SparseArray(std::size_t s) + : size(s) +{} + +[[nodiscard]] std::size_t SparseArray::getSize() const { + return size; +} + +[[nodiscard]] std::vector> SparseArray::testToVector() const { + std::vector> result(getSize(), 0); + + for (auto const &kv : *this) { + result[kv.first.toSizeT()] = kv.second; + } + + return result; +} + +[[nodiscard]] SparseArray::Iterator SparseArray::begin() const { + return data.cbegin(); +} + +[[nodiscard]] SparseArray::Iterator SparseArray::end() const { + return data.cend(); +} + + +void SparseArray::set(BasisVector index, std::complex value) { +#ifndef NDEBUG + if (index.toSizeT() >= size) { + throw std::runtime_error("SparseArray::set index out of bounds"); + } +#endif + + if (std::abs(value) < config::EPS) { + return; + } + + data.try_emplace(index, value); +} + +void SparseArray::clear() { + data.clear(); +} + +SparseArray &SparseArray::operator*=(double d) { + std::for_each(data.begin(), data.end(), + [d](auto &kv) { kv.second *= d; }); + return *this; +} + +void SparseArray::cleanupZeros() { + absl::erase_if(data, [](auto const &kv) { return !isNotNull(kv.second); }); + zeroCounter = 0; +} + +} // namespace qx::core diff --git a/test/DenseUnitaryMatrixTest.cpp b/test/DenseUnitaryMatrixTest.cpp index 312f60f0..52b52575 100644 --- a/test/DenseUnitaryMatrixTest.cpp +++ b/test/DenseUnitaryMatrixTest.cpp @@ -1,4 +1,4 @@ -#include "qx/Core.hpp" +#include "qx/DenseUnitaryMatrix.hpp" #include // ThrowsMessage #include diff --git a/test/ErrorModelsTest.cpp b/test/ErrorModelsTest.cpp index eafa115d..f6255734 100644 --- a/test/ErrorModelsTest.cpp +++ b/test/ErrorModelsTest.cpp @@ -14,7 +14,7 @@ class ErrorModelsTest : public ::testing::Test { random::seed(123); } - void checkState(const std::map> &expected) { + void checkState(const std::map> &expected) { state.forEach([&expected](auto const &kv) { ASSERT_EQ(expected.count(kv.first), 1); EXPECT_EQ(expected.at(kv.first), kv.second); @@ -34,15 +34,15 @@ TEST_F(ErrorModelsTest, depolarizing_channel__probability_1) { DepolarizingChannel const channel(1.); addError(channel); // X is applied to qubit 1. - checkState({{BasisVector{"010"}, 1. + 0.i}}); + checkState({{core::BasisVector{"010"}, 1. + 0.i}}); addError(channel); // Z is applied to qubit 2. - checkState({{BasisVector{"010"}, 1. + 0.i}}); + checkState({{core::BasisVector{"010"}, 1. + 0.i}}); addError(channel); // X is applied to qubit 0. - checkState({{BasisVector{"011"}, 1. + 0.i}}); + checkState({{core::BasisVector{"011"}, 1. + 0.i}}); } TEST_F(ErrorModelsTest, depolarizing_channel__probability_0) { @@ -53,7 +53,7 @@ TEST_F(ErrorModelsTest, depolarizing_channel__probability_0) { addError(channel); addError(channel); - checkState({{BasisVector{"000"}, 1. + 0.i}}); + checkState({{core::BasisVector{"000"}, 1. + 0.i}}); } } // namespace qx::error_models diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index d53e2704..b8a8d4f4 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -1,5 +1,6 @@ -#include "qx/Core.hpp" +#include "qx/Core.hpp" // BasisVector #include "qx/Gates.hpp" +#include "qx/QuantumState.hpp" #include // count_if #include diff --git a/test/SparseArrayTest.cpp b/test/SparseArrayTest.cpp index 210c28f6..09775193 100644 --- a/test/SparseArrayTest.cpp +++ b/test/SparseArrayTest.cpp @@ -1,4 +1,4 @@ -#include "qx/Core.hpp" +#include "qx/SparseArray.hpp" #include // ThrowsMessageHasSubstr #include From 3f584b8ca4cc7c1e0cbbe7c6ec4b90a7579015ba Mon Sep 17 00:00:00 2001 From: rturrado Date: Fri, 7 Jun 2024 13:54:45 +0200 Subject: [PATCH 03/56] Change OUTPUT_DECIMALS to uint8_t. --- include/qx/CompileTimeConfiguration.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/qx/CompileTimeConfiguration.hpp b/include/qx/CompileTimeConfiguration.hpp index 2f815fdb..a93e47b9 100644 --- a/include/qx/CompileTimeConfiguration.hpp +++ b/include/qx/CompileTimeConfiguration.hpp @@ -10,7 +10,7 @@ namespace qx::config { static constexpr double EPS = 0.000000000001; // Number of decimals in output -static constexpr std::uint64_t const OUTPUT_DECIMALS = 8; +static constexpr std::uint8_t const OUTPUT_DECIMALS = 8; // How many gates between cleaning the zeros in the sparse array static constexpr std::uint64_t ZERO_CYCLE_SIZE = 100; From c9a01b912b646f7c9240ce70ad497fd8420bf6e4 Mon Sep 17 00:00:00 2001 From: rturrado Date: Fri, 7 Jun 2024 13:55:00 +0200 Subject: [PATCH 04/56] Minor aesthetic change. --- src/qx/QuantumState.cpp | 1 + src/qx/SparseArray.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index af77e5a8..fe31398d 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -1,5 +1,6 @@ #include "qx/QuantumState.hpp" + namespace qx::core { QuantumState::QuantumState(std::size_t n) diff --git a/src/qx/SparseArray.cpp b/src/qx/SparseArray.cpp index 8547f7da..b420eb9c 100644 --- a/src/qx/SparseArray.cpp +++ b/src/qx/SparseArray.cpp @@ -1,5 +1,6 @@ #include "qx/SparseArray.hpp" + namespace qx::core { SparseArray::SparseArray(std::size_t s) From 9e8ae89603452a7e8423bd02bea6f02822d73590 Mon Sep 17 00:00:00 2001 From: rturrado Date: Fri, 7 Jun 2024 13:55:54 +0200 Subject: [PATCH 05/56] Change operator<< to use fmt::print. --- src/qx/SimulationResult.cpp | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index f3529e2e..d68fc767 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -2,8 +2,7 @@ #include "qx/Core.hpp" // BasisVector #include "qx/QuantumState.hpp" -#include -#include +#include #include @@ -16,26 +15,20 @@ void SimulationResultAccumulator::append(core::BasisVector measuredState) { } std::ostream &operator<<(std::ostream &os, SimulationResult const &r) { - os << std::setprecision(config::OUTPUT_DECIMALS) << std::fixed; - os << "-------------------------------------------" << std::endl; - - os << "Final quantum state" << std::endl; - + fmt::print("-------------------------------------------\n"); + fmt::print("Final quantum state\n"); for (auto const &kv : r.state) { auto const &stateString = kv.first; auto const &litude = kv.second; - os << stateString << " " << amplitude.real << " + " - << amplitude.imag << "*i " - << " (p = " << amplitude.norm << ")" << std::endl; + fmt::print("{1} {2:.{0}f} + {3:.{0}f}*i (p = {4:.{0}f})\n", config::OUTPUT_DECIMALS, stateString, + amplitude.real, amplitude.imag, amplitude.norm); } - - os << std::endl << "Measurement register averaging" << std::endl; - + fmt::print("\nMeasurement register averaging\n"); for (const auto &kv : r.results) { const auto &stateString = kv.first; const auto &count = kv.second; - os << stateString << " " << count << "/" << r.shots_done << " (" - << static_cast(count) / static_cast(r.shots_done) << ")" << std::endl; + fmt::print("{1} {2}/{3} ({4:.{0}f})\n", config::OUTPUT_DECIMALS, stateString, count, + r.shots_done, static_cast(count) / static_cast(r.shots_done)); } return os; } @@ -46,33 +39,26 @@ SimulationResult SimulationResultAccumulator::get() { simulationResult.shots_done = nMeasurements; assert(nMeasurements > 0); - for (const auto &kv : measuredStates) { const auto &state = kv.first; const auto &count = kv.second; - simulationResult.results.emplace_back(getStateString(state), count); } - forAllNonZeroStates([&simulationResult](auto stateString, auto c) { simulationResult.state.push_back(std::make_pair( stateString, Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) })); }); - return simulationResult; } template void SimulationResultAccumulator::forAllNonZeroStates(F &&f) { - quantumState.forEach( - [&f, this](auto const &kv) { f(getStateString(kv.first), kv.second); }); + quantumState.forEach([&f, this](auto const &kv) { f(getStateString(kv.first), kv.second); }); } std::string SimulationResultAccumulator::getStateString(qx::core::BasisVector s) { auto str = s.toString(); - - return str.substr(str.size() - quantumState.getNumberOfQubits(), - str.size()); + return str.substr(str.size() - quantumState.getNumberOfQubits(), str.size()); } } // namespace qx \ No newline at end of file From 7fe26f3a6d18a3b032a3cbe6143c9f378fac3fac Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 10 Jun 2024 12:06:30 +0200 Subject: [PATCH 06/56] Add Result and State structs. Add QuantumStateError struct. Change SimulationResult to contain Results and States. --- include/qx/QuantumState.hpp | 32 +++++---------- include/qx/SimulationResult.hpp | 45 +++++++++++++++------ include/qx/SparseArray.hpp | 14 +------ include/qx/Utils.hpp | 16 +++++--- src/qx/QuantumState.cpp | 28 +++++++------ src/qx/SimulationResult.cpp | 61 +++++++++++++++------------- src/qx/SparseArray.cpp | 8 +--- test/IntegrationTest.cpp | 70 ++++++++++++++------------------- 8 files changed, 136 insertions(+), 138 deletions(-) diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 7f672ed9..99f9ca2a 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -4,6 +4,7 @@ #include // size_t #include // norm #include +#include #include #include // invoke, pair @@ -15,8 +16,11 @@ namespace qx::core { -class QuantumState { +struct QuantumStateError : public std::runtime_error { + explicit QuantumStateError(const std::string &message) : std::runtime_error{ message } {} +}; +class QuantumState { template void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, std::array const &operands, @@ -26,17 +30,13 @@ class QuantumState { for (std::size_t i = 0; i < NumberOfOperands; ++i) { reducedIndex.set(i, index.test(operands[NumberOfOperands - i - 1].value)); } - for (std::size_t i = 0; i < (1 << NumberOfOperands); ++i) { std::complex addedValue = value * matrix.at(i, reducedIndex.toSizeT()); - if (isNotNull(addedValue)) { auto newIndex = index; - for (std::size_t k = 0; k < NumberOfOperands; ++k) { newIndex.set(operands[NumberOfOperands - k - 1].value, utils::getBit(i, k)); } - auto it = storage.try_emplace(newIndex, 0); auto newValue = it.first->second + addedValue; it.first->second = newValue; @@ -46,13 +46,9 @@ class QuantumState { public: explicit QuantumState(std::size_t n); - [[nodiscard]] std::size_t getNumberOfQubits() const; - void reset(); - - void testInitialize( - std::initializer_list>> values); + void testInitialize(std::initializer_list>> values); template QuantumState &apply(DenseUnitaryMatrix<1 << NumberOfOperands> const &m, @@ -67,7 +63,6 @@ class QuantumState { data.applyLinear([&m, &operands, this](auto index, auto value, auto &storage) { this->applyImpl(m, operands, index, value, storage); }); - return *this; } @@ -82,13 +77,11 @@ class QuantumState { void measure(QubitIndex qubitIndex, F &&randomGenerator) { auto rand = randomGenerator(); double probabilityOfMeasuringOne = 0.; - data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { if (kv.first.test(qubitIndex.value)) { probabilityOfMeasuringOne += std::norm(kv.second); } }); - if (rand < probabilityOfMeasuringOne) { data.eraseIf([qubitIndex](auto const &kv) { return !kv.first.test(qubitIndex.value); @@ -108,22 +101,17 @@ class QuantumState { void measureAll(F &&randomGenerator) { auto rand = randomGenerator(); double probability = 0.; - auto measuredState = std::invoke([this, &probability, rand] { - for (auto const &kv : - data) { // Does this work with non-ordered iteration? + for (auto const &kv : data) { // Does this work with non-ordered iteration? probability += std::norm(kv.second); if (probability > rand) { return kv; } } - throw std::runtime_error( - "Vector was not normalized at measurement location (a bug)"); + throw std::runtime_error{ "Vector was not normalized at measurement location (a bug)" }; }); - data.clear(); - data.set(measuredState.first, - measuredState.second / std::abs(measuredState.second)); + data.set(measuredState.first, measuredState.second / std::abs(measuredState.second)); measurementRegister = measuredState.first; } @@ -132,13 +120,11 @@ class QuantumState { // Measure + conditional X, and reset the measurement register. auto rand = randomGenerator(); double probabilityOfMeasuringOne = 0.; - data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { if (kv.first.test(qubitIndex.value)) { probabilityOfMeasuringOne += std::norm(kv.second); } }); - if (rand < probabilityOfMeasuringOne) { data.eraseIf([qubitIndex](auto const &kv) { return !kv.first.test(qubitIndex.value); diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index e58958f9..c8419831 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -1,10 +1,14 @@ #pragma once +#include "qx/CompileTimeConfiguration.hpp" #include "qx/Core.hpp" #include +#include // strong_ordering #include +#include // uint64_t #include +#include #include #include @@ -19,37 +23,54 @@ struct Complex { double real = 0; double imag = 0; double norm = 0; + bool operator==(const Complex &other) const { + return std::abs(real - other.real) < config::EPS && + std::abs(imag - other.imag) < config::EPS && + std::abs(norm - other.norm) < config::EPS; + } + auto operator<=>(const Complex &other) const = default; }; -struct SimulationResult { - using Results = std::vector>; +struct Result { + std::string state; + std::uint64_t count; + std::strong_ordering operator<=>(const Result &other) const = default; +}; - using State = std::vector>; +struct State { + std::string value; + Complex amplitude; + bool operator==(const State &other) const = default; + std::strong_ordering operator<=>(const State &other) const = default; +}; +struct SimulationResult { + using Results = std::vector; + using States = std::vector; std::uint64_t shots_requested = 0; std::uint64_t shots_done = 0; - Results results; - State state; + States states; }; -std::ostream &operator<<(std::ostream &os, SimulationResult const &r); +std::ostream &operator<<(std::ostream &os, const SimulationResult &result); + +using state_t = core::BasisVector; +using count_t = std::uint64_t; class SimulationResultAccumulator { public: - explicit SimulationResultAccumulator(core::QuantumState &s) : quantumState(s){}; - + explicit SimulationResultAccumulator(core::QuantumState &s); void append(core::BasisVector measuredState); - SimulationResult get(); private: - template void forAllNonZeroStates(F &&f); - + template + void forAllNonZeroStates(F &&f); std::string getStateString(core::BasisVector s); core::QuantumState &quantumState; - absl::btree_map measuredStates; + absl::btree_map measuredStates; std::uint64_t nMeasurements = 0; }; diff --git a/include/qx/SparseArray.hpp b/include/qx/SparseArray.hpp index f68f27f9..7aeb66d2 100644 --- a/include/qx/SparseArray.hpp +++ b/include/qx/SparseArray.hpp @@ -21,21 +21,13 @@ class SparseArray { using Iterator = Map::const_iterator; SparseArray() = delete; - explicit SparseArray(std::size_t s); - [[nodiscard]] std::size_t getSize() const; - [[nodiscard]] std::vector> testToVector() const; - [[nodiscard]] Iterator begin() const; - [[nodiscard]] Iterator end() const; - void set(BasisVector index, std::complex value); - void clear(); - SparseArray &operator*=(double d); template @@ -47,8 +39,7 @@ class SparseArray { template void forEachSorted(F &&f) { cleanupZeros(); - std::vector>> sorted( - data.begin(), data.end()); + std::vector>> sorted(data.begin(), data.end()); std::sort(sorted.begin(), sorted.end(), [](auto const &left, auto const &right) { return left.first < right.first; @@ -72,13 +63,10 @@ class SparseArray { cleanupZeros(); } ++zeroCounter; - Map result; - for (auto const &kv : data) { f(kv.first, kv.second, result); } - data.swap(result); } diff --git a/include/qx/Utils.hpp b/include/qx/Utils.hpp index 6c700db2..c9a3e724 100644 --- a/include/qx/Utils.hpp +++ b/include/qx/Utils.hpp @@ -13,12 +13,15 @@ static inline bool getBit(std::size_t x, std::size_t index) { } static inline void setBit(std::size_t &x, std::size_t index, bool value) { - x = (x & ~(static_cast(1) << index)) | (static_cast(value) << index); + x = (~(static_cast(1) << index) // 111...101...111, all 1s and 0 at index + & x) // xxx...x0x...xxx, x with 0 at index + | (static_cast(value) << index); // xxx...xvx...xxx, x with value at index } // std::bitset is slightly slower than this. -template class Bitset { +template +class Bitset { private: static constexpr std::size_t BITS_IN_SIZE_T = CHAR_BIT * sizeof(std::size_t); @@ -28,7 +31,9 @@ template class Bitset { NumberOfBits / BITS_IN_SIZE_T + (NumberOfBits % BITS_IN_SIZE_T >= 1 ? 1 : 0); - static constexpr std::size_t getNumberOfBits() { return NumberOfBits; } + static constexpr std::size_t getNumberOfBits() { + return NumberOfBits; + } Bitset() = default; @@ -40,7 +45,9 @@ template class Bitset { } } - inline void reset() { data = {}; } + inline void reset() { + data = {}; + } [[nodiscard]] inline bool test(std::size_t index) const { assert(index < NumberOfBits && "Bitset::test bit index out of range"); @@ -94,7 +101,6 @@ template class Bitset { return compare(other); } } - return data[Index] < other.data[Index]; } diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index fe31398d..736759eb 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -1,14 +1,21 @@ +#include + #include "qx/QuantumState.hpp" namespace qx::core { QuantumState::QuantumState(std::size_t n) - : numberOfQubits(n), data(1 << numberOfQubits) { - assert(numberOfQubits > 0 && "QuantumState needs at least one qubit"); - assert(numberOfQubits <= config::MAX_QUBIT_NUMBER && - "QuantumState currently cannot support that many qubits with this version of QX-simulator"); - data.set(BasisVector{}, 1); // Start initialized in state 00...000 + : numberOfQubits(n), + data(1 << numberOfQubits) { + if (numberOfQubits == 0) { + throw QuantumStateError{ "quantum state needs at least one qubit" }; + } + if (numberOfQubits > config::MAX_QUBIT_NUMBER) { + throw QuantumStateError{ fmt::format("quantum state size exceeds maximum allowed: {} > {}", numberOfQubits, + config::MAX_QUBIT_NUMBER) }; + } + data.set(BasisVector{}, std::complex{ 1 }); // Start initialized in state 00...000 }; [[nodiscard]] std::size_t QuantumState::getNumberOfQubits() const { @@ -17,18 +24,17 @@ QuantumState::QuantumState(std::size_t n) void QuantumState::reset() { data.clear(); - data.set(BasisVector{}, 1); // Start initialized in state 00...000 + data.set(BasisVector{}, std::complex{ 1 }); // Start initialized in state 00...000 measurementRegister.reset(); } void QuantumState::testInitialize( std::initializer_list>> values) { data.clear(); - double norm = 0; - for (auto const &kv: values) { - BasisVector index(kv.first); - data.set(index, kv.second); - norm += std::norm(kv.second); + double norm{}; + for (auto const &[state_string, amplitude] : values) { + data.set(BasisVector{ state_string }, amplitude); + norm += std::norm(amplitude); } assert(!isNotNull(norm - 1)); } diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index d68fc767..914133e1 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -3,57 +3,64 @@ #include "qx/Core.hpp" // BasisVector #include "qx/QuantumState.hpp" #include -#include +#include namespace qx { -void SimulationResultAccumulator::append(core::BasisVector measuredState) { - assert(measuredStates.size() <= (1u << quantumState.getNumberOfQubits())); - measuredStates[measuredState]++; - nMeasurements++; -} - -std::ostream &operator<<(std::ostream &os, SimulationResult const &r) { - fmt::print("-------------------------------------------\n"); - fmt::print("Final quantum state\n"); - for (auto const &kv : r.state) { - auto const &stateString = kv.first; - auto const &litude = kv.second; - fmt::print("{1} {2:.{0}f} + {3:.{0}f}*i (p = {4:.{0}f})\n", config::OUTPUT_DECIMALS, stateString, - amplitude.real, amplitude.imag, amplitude.norm); +std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationResult) { + fmt::print("\nFinal quantum state\n"); + for (auto const &state : simulationResult.states) { + fmt::print("{1} {2:.{0}f} + {3:.{0}f}i (norm = {4:.{0}f})\n", + config::OUTPUT_DECIMALS, + state.value, + state.amplitude.real, + state.amplitude.imag, + state.amplitude.norm); } fmt::print("\nMeasurement register averaging\n"); - for (const auto &kv : r.results) { - const auto &stateString = kv.first; - const auto &count = kv.second; - fmt::print("{1} {2}/{3} ({4:.{0}f})\n", config::OUTPUT_DECIMALS, stateString, count, - r.shots_done, static_cast(count) / static_cast(r.shots_done)); + for (const auto &result : simulationResult.results) { + fmt::print("{1} {2}/{3} (count/shots % = {4:.{0}f})\n", + config::OUTPUT_DECIMALS, + result.state, + result.count, + simulationResult.shots_done, + static_cast(result.count) / static_cast(simulationResult.shots_done)); } return os; } +SimulationResultAccumulator::SimulationResultAccumulator(core::QuantumState &s) + : quantumState(s) +{} + +void SimulationResultAccumulator::append(core::BasisVector measuredState) { + assert(measuredStates.size() <= (1u << quantumState.getNumberOfQubits())); + measuredStates[measuredState]++; + nMeasurements++; +} + SimulationResult SimulationResultAccumulator::get() { SimulationResult simulationResult; simulationResult.shots_requested = nMeasurements; simulationResult.shots_done = nMeasurements; assert(nMeasurements > 0); - for (const auto &kv : measuredStates) { - const auto &state = kv.first; - const auto &count = kv.second; - simulationResult.results.emplace_back(getStateString(state), count); + for (const auto &[state, count] : measuredStates) { + simulationResult.results.push_back(Result{ getStateString(state), count}); } forAllNonZeroStates([&simulationResult](auto stateString, auto c) { - simulationResult.state.push_back(std::make_pair( - stateString, Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) })); + simulationResult.states.push_back( + State{ stateString, Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) } }); }); return simulationResult; } template void SimulationResultAccumulator::forAllNonZeroStates(F &&f) { - quantumState.forEach([&f, this](auto const &kv) { f(getStateString(kv.first), kv.second); }); + quantumState.forEach([&f, this](auto const &kv) { + f(getStateString(kv.first), kv.second); + }); } std::string SimulationResultAccumulator::getStateString(qx::core::BasisVector s) { diff --git a/src/qx/SparseArray.cpp b/src/qx/SparseArray.cpp index b420eb9c..f8d2543b 100644 --- a/src/qx/SparseArray.cpp +++ b/src/qx/SparseArray.cpp @@ -13,11 +13,9 @@ SparseArray::SparseArray(std::size_t s) [[nodiscard]] std::vector> SparseArray::testToVector() const { std::vector> result(getSize(), 0); - for (auto const &kv : *this) { result[kv.first.toSizeT()] = kv.second; } - return result; } @@ -29,18 +27,15 @@ SparseArray::SparseArray(std::size_t s) return data.cend(); } - void SparseArray::set(BasisVector index, std::complex value) { #ifndef NDEBUG if (index.toSizeT() >= size) { throw std::runtime_error("SparseArray::set index out of bounds"); } #endif - if (std::abs(value) < config::EPS) { return; } - data.try_emplace(index, value); } @@ -49,8 +44,7 @@ void SparseArray::clear() { } SparseArray &SparseArray::operator*=(double d) { - std::for_each(data.begin(), data.end(), - [d](auto &kv) { kv.second *= d; }); + std::for_each(data.begin(), data.end(), [d](auto &kv) { kv.second *= d; }); return *this; } diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 8c982144..3a64f9d5 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -1,4 +1,3 @@ -#include "qx/Random.hpp" #include "qx/Simulator.hpp" #include // abs @@ -19,11 +18,6 @@ class IntegrationTest : public ::testing::Test { } }; -bool operator==(Complex const &left, Complex const &right) { - return std::abs(left.real - right.real) < config::EPS && - std::abs(left.imag - right.imag) < config::EPS && - std::abs(left.norm - right.norm) < config::EPS; -} TEST_F(IntegrationTest, bell_pair) { auto cqasm = R"( @@ -38,9 +32,8 @@ CNOT q[0], q[1] EXPECT_EQ(actual.shots_requested, 1); EXPECT_EQ(actual.shots_done, 1); - EXPECT_EQ(actual.results, (SimulationResult::Results{ { "00", 1 } })); - EXPECT_EQ(actual.state, - (SimulationResult::State{ + EXPECT_EQ(actual.states, + (SimulationResult::States{ { "00", Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, { "11", Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } })); @@ -56,7 +49,8 @@ CNOT q[0:2], q[3:5] )"; auto actual = runFromString(cqasm, 2); - EXPECT_EQ(actual.state, (SimulationResult::State{ { "111111", Complex{ .real = 1, .imag = 0, .norm = 1 } } })); + EXPECT_EQ(actual.states, + (SimulationResult::States{ { "111111", Complex{ .real = 1, .imag = 0, .norm = 1 } } })); } TEST_F(IntegrationTest, too_many_qubits) { @@ -100,9 +94,8 @@ I q[1] EXPECT_EQ(actual.shots_requested, 1); EXPECT_EQ(actual.shots_done, 1); - EXPECT_EQ(actual.results, (SimulationResult::Results{ { "00", 1 } })); - EXPECT_EQ(actual.state, - (SimulationResult::State{ + EXPECT_EQ(actual.states, + (SimulationResult::States{ { "00", Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, { "11", Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } })); @@ -124,22 +117,19 @@ b = measure q )"; auto actual = runFromString(cqasm, iterations); - std::cout << "Simulation result: " << actual << "\n"; - auto error = static_cast(iterations/2 * 0.05); EXPECT_EQ(actual.results.size(), 2); - EXPECT_EQ(actual.results[0].first, "001"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].second)), error); - EXPECT_EQ(actual.results[1].first, "111"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].second)), error); + EXPECT_EQ(actual.results[0].state, "001"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); + EXPECT_EQ(actual.results[1].state, "111"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].count)), error); // State could be 001 or 111 - EXPECT_TRUE(actual.state[0].first.ends_with('1')); - EXPECT_EQ(actual.state[0].second, (Complex{ .real = 1, .imag = 0, .norm = 1 })); + EXPECT_TRUE(actual.states[0].value.ends_with('1')); + EXPECT_EQ(actual.states[0].amplitude, (Complex{ .real = 1, .imag = 0, .norm = 1 })); } TEST_F(IntegrationTest, multiple_measure_instructions) { - std::size_t iterations = 10'000; auto cqasm = R"( version 3.0 @@ -154,18 +144,19 @@ b[0] = measure q[0] b[1] = measure q[1] b[2] = measure q[2] )"; + std::size_t iterations = 10'000; auto actual = runFromString(cqasm, iterations); auto error = static_cast(iterations/2 * 0.05); EXPECT_EQ(actual.results.size(), 2); - EXPECT_EQ(actual.results[0].first, "001"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].second)), error); - EXPECT_EQ(actual.results[1].first, "111"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].second)), error); + EXPECT_EQ(actual.results[0].state, "001"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); + EXPECT_EQ(actual.results[1].state, "111"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].count)), error); // State could be 001 or 111 - EXPECT_TRUE(actual.state[0].first.ends_with('1')); - EXPECT_EQ(actual.state[0].second, (Complex{ .real = 1, .imag = 0, .norm = 1 })); + EXPECT_TRUE(actual.states[0].value.ends_with('1')); + EXPECT_EQ(actual.states[0].amplitude, (Complex{ .real = 1, .imag = 0, .norm = 1 })); } TEST_F(IntegrationTest, mid_circuit_measure_instruction) { @@ -188,10 +179,10 @@ b = measure q // Expected output state: |00>+|11> or |01>+|10> auto error = static_cast(iterations/2 * 0.05); EXPECT_EQ(actual.results.size(), 2); - EXPECT_TRUE(actual.results[0].first == "00" || actual.results[0].first == "01"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].second)), error); - EXPECT_TRUE(actual.results[1].first == "11" || actual.results[1].first == "10"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].second)), error); + EXPECT_TRUE(actual.results[0].state == "00" || actual.results[0].state == "01"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); + EXPECT_TRUE(actual.results[1].state == "11" || actual.results[1].state == "10"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].count)), error); } TEST_F(IntegrationTest, multiple_qubit_bit_definitions_and_mid_circuit_measure_instructions) { @@ -200,15 +191,14 @@ TEST_F(IntegrationTest, multiple_qubit_bit_definitions_and_mid_circuit_measure_i version 3.0 qubit q0 -qubit q1 -bit b0 -bit b1 - X q0 +bit b0 b0 = measure q0 +qubit q1 H q1 CNOT q1, q0 +bit b1 b0 = measure q0 b1 = measure q1 )"; @@ -217,10 +207,10 @@ b1 = measure q1 // Expected output state: |00>+|11> or |01>+|10> auto error = static_cast(iterations/2 * 0.05); EXPECT_EQ(actual.results.size(), 2); - EXPECT_TRUE(actual.results[0].first == "00" || actual.results[0].first == "01"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].second)), error); - EXPECT_TRUE(actual.results[1].first == "11" || actual.results[1].first == "10"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].second)), error); + EXPECT_TRUE(actual.results[0].state == "00" || actual.results[0].state == "01"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); + EXPECT_TRUE(actual.results[1].state == "11" || actual.results[1].state == "10"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].count)), error); } } // namespace qx From 8fe220aae5d07ca2b9a0eaff4f2eaa859ee4cd58 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 15:44:29 +0200 Subject: [PATCH 07/56] Add ComplexValue and SparseElement aliases. Add MapBasisVectorToSparseComplex and VectorOfSparseElements aliases. Add SparseArrayError struct. Move Complex to Core.hpp. --- include/qx/Core.hpp | 20 ++++-- include/qx/QuantumState.hpp | 92 +++++++++++++++------------- include/qx/SimulationResult.hpp | 16 +---- include/qx/SparseArray.hpp | 89 +++++++++++++++++---------- src/qx/QuantumState.cpp | 6 +- src/qx/SimulationResult.cpp | 5 +- src/qx/SparseArray.cpp | 105 ++++++++++++++++++++++++-------- test/ErrorModelsTest.cpp | 5 +- test/IntegrationTest.cpp | 22 +++---- test/QuantumStateTest.cpp | 7 ++- test/SparseArrayTest.cpp | 19 +++--- 11 files changed, 234 insertions(+), 152 deletions(-) diff --git a/include/qx/Core.hpp b/include/qx/Core.hpp index 258a89df..2cfd2d65 100644 --- a/include/qx/Core.hpp +++ b/include/qx/Core.hpp @@ -21,12 +21,20 @@ struct BitIndex { using BasisVector = utils::Bitset; inline constexpr bool isNotNull(std::complex c) { -#if defined(_MSC_VER) - return c.real() > config::EPS || -c.real() > config::EPS || - c.imag() > config::EPS || -c.imag() > config::EPS; -#else - return std::abs(c.real()) > config::EPS || std::abs(c.imag()) > config::EPS; -#endif + return std::abs(c.real()) > config::EPS || + std::abs(c.imag()) > config::EPS; } +struct Complex { + double real = 0; + double imag = 0; + double norm = 0; + bool operator==(const Complex &other) const { + return std::abs(real - other.real) < config::EPS && + std::abs(imag - other.imag) < config::EPS && + std::abs(norm - other.norm) < config::EPS; + } + auto operator<=>(const Complex &other) const = default; +}; + } // namespace qx::core diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 99f9ca2a..857fab82 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -20,30 +20,32 @@ struct QuantumStateError : public std::runtime_error { explicit QuantumStateError(const std::string &message) : std::runtime_error{ message } {} }; -class QuantumState { - template - void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, - std::array const &operands, - BasisVector index, std::complex value, - SparseArray::Map &storage) { - utils::Bitset reducedIndex; - for (std::size_t i = 0; i < NumberOfOperands; ++i) { - reducedIndex.set(i, index.test(operands[NumberOfOperands - i - 1].value)); - } - for (std::size_t i = 0; i < (1 << NumberOfOperands); ++i) { - std::complex addedValue = value * matrix.at(i, reducedIndex.toSizeT()); - if (isNotNull(addedValue)) { - auto newIndex = index; - for (std::size_t k = 0; k < NumberOfOperands; ++k) { - newIndex.set(operands[NumberOfOperands - k - 1].value, utils::getBit(i, k)); - } - auto it = storage.try_emplace(newIndex, 0); - auto newValue = it.first->second + addedValue; - it.first->second = newValue; + +template +void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, + std::array const &operands, + BasisVector index, + SparseComplex sparseComplex, + SparseArray::MapBasisVectorToSparseComplex &storage) { + utils::Bitset reducedIndex; + for (std::size_t i = 0; i < NumberOfOperands; ++i) { + reducedIndex.set(i, index.test(operands[NumberOfOperands - i - 1].value)); + } + for (std::size_t i = 0; i < (1 << NumberOfOperands); ++i) { + std::complex addedValue = sparseComplex.value * matrix.at(i, reducedIndex.toSizeT()); + if (isNotNull(addedValue)) { + auto newIndex = index; + for (std::size_t k = 0; k < NumberOfOperands; ++k) { + newIndex.set(operands[NumberOfOperands - k - 1].value, utils::getBit(i, k)); } + auto it = storage.try_emplace(newIndex, SparseComplex{ 0. }); + it.first->second.value += addedValue; } } +} + +class QuantumState { public: explicit QuantumState(std::size_t n); [[nodiscard]] std::size_t getNumberOfQubits() const; @@ -61,8 +63,9 @@ class QuantumState { }) == operands.end() && "Operand refers to a non-existing qubit"); - data.applyLinear([&m, &operands, this](auto index, auto value, auto &storage) { - this->applyImpl(m, operands, index, value, storage); }); + data.applyLinear([&m, &operands](auto index, auto value, auto &storage) { + applyImpl(m, operands, index, value, storage); + }); return *this; } @@ -78,19 +81,22 @@ class QuantumState { auto rand = randomGenerator(); double probabilityOfMeasuringOne = 0.; data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { - if (kv.first.test(qubitIndex.value)) { - probabilityOfMeasuringOne += std::norm(kv.second); + auto const &[basisVector, sparseComplex] = kv; + if (basisVector.test(qubitIndex.value)) { + probabilityOfMeasuringOne += std::norm(sparseComplex.value); } }); if (rand < probabilityOfMeasuringOne) { data.eraseIf([qubitIndex](auto const &kv) { - return !kv.first.test(qubitIndex.value); + auto const &[basisVector, _] = kv; + return !basisVector.test(qubitIndex.value); }); data *= std::sqrt(1 / probabilityOfMeasuringOne); measurementRegister.set(qubitIndex.value, true); } else { data.eraseIf([qubitIndex](auto const &kv) { - return kv.first.test(qubitIndex.value); + auto const &[basisVector, _] = kv; + return basisVector.test(qubitIndex.value); }); data *= std::sqrt(1 / (1 - probabilityOfMeasuringOne)); measurementRegister.set(qubitIndex.value, false); @@ -101,18 +107,18 @@ class QuantumState { void measureAll(F &&randomGenerator) { auto rand = randomGenerator(); double probability = 0.; - auto measuredState = std::invoke([this, &probability, rand] { - for (auto const &kv : data) { // Does this work with non-ordered iteration? - probability += std::norm(kv.second); + auto [basisVector, sparseComplex] = std::invoke([this, &probability, rand] { + for (auto const &[bv, sc] : data) { // does this work with non-ordered iteration? + probability += std::norm(sc.value); if (probability > rand) { - return kv; + return SparseElement{ bv, sc }; } } throw std::runtime_error{ "Vector was not normalized at measurement location (a bug)" }; }); data.clear(); - data.set(measuredState.first, measuredState.second / std::abs(measuredState.second)); - measurementRegister = measuredState.first; + data[basisVector] = SparseComplex{ sparseComplex.value / std::abs(sparseComplex.value) }; + measurementRegister = basisVector; } template @@ -121,26 +127,30 @@ class QuantumState { auto rand = randomGenerator(); double probabilityOfMeasuringOne = 0.; data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { - if (kv.first.test(qubitIndex.value)) { - probabilityOfMeasuringOne += std::norm(kv.second); + auto const &[basisVector, sparseComplex] = kv; + if (basisVector.test(qubitIndex.value)) { + probabilityOfMeasuringOne += std::norm(sparseComplex.value); } }); if (rand < probabilityOfMeasuringOne) { data.eraseIf([qubitIndex](auto const &kv) { - return !kv.first.test(qubitIndex.value); + auto const &[basisVector, _] = kv; + return !basisVector.test(qubitIndex.value); }); - SparseArray::Map newData; - for (auto kv : data.data) { - auto newKey = kv.first; + SparseArray::MapBasisVectorToSparseComplex newData; + for (auto [basisVector, sparseComplex] : data) { + auto newKey = basisVector; newKey.set(qubitIndex.value, false); newData.insert(std::make_pair( newKey, - kv.second * std::sqrt(1 / probabilityOfMeasuringOne))); + SparseComplex{ sparseComplex.value * std::sqrt(1 / probabilityOfMeasuringOne) } + )); } - data.data = newData; // Could fix the interface + data = newData; // could fix the interface } else { data.eraseIf([qubitIndex](auto const &kv) { - return kv.first.test(qubitIndex.value); + auto const &[basisVector, _] = kv; + return basisVector.test(qubitIndex.value); }); data *= std::sqrt(1 / (1 - probabilityOfMeasuringOne)); } diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index c8419831..df3e351d 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -1,7 +1,7 @@ #pragma once #include "qx/CompileTimeConfiguration.hpp" -#include "qx/Core.hpp" +#include "qx/Core.hpp" // Complex #include #include // strong_ordering @@ -19,18 +19,6 @@ namespace core { class QuantumState; } -struct Complex { - double real = 0; - double imag = 0; - double norm = 0; - bool operator==(const Complex &other) const { - return std::abs(real - other.real) < config::EPS && - std::abs(imag - other.imag) < config::EPS && - std::abs(norm - other.norm) < config::EPS; - } - auto operator<=>(const Complex &other) const = default; -}; - struct Result { std::string state; std::uint64_t count; @@ -39,7 +27,7 @@ struct Result { struct State { std::string value; - Complex amplitude; + core::Complex amplitude; bool operator==(const State &other) const = default; std::strong_ordering operator<=>(const State &other) const = default; }; diff --git a/include/qx/SparseArray.hpp b/include/qx/SparseArray.hpp index 7aeb66d2..0ac16b41 100644 --- a/include/qx/SparseArray.hpp +++ b/include/qx/SparseArray.hpp @@ -4,10 +4,11 @@ #include // for_each, sort #include #include // size_t, uint64_t +#include // runtime_error #include // pair #include -#include "qx/Core.hpp" // BasisVector, QubitIndex +#include "qx/Core.hpp" // BasisVector, Complex, QubitIndex #include "qx/CompileTimeConfiguration.hpp" // ZERO_CYCLE_SIZE @@ -15,66 +16,90 @@ namespace qx::core { class QuantumState; + +struct SparseArrayError : public std::runtime_error { + explicit SparseArrayError(const std::string &message); +}; + + +struct SparseComplex { + std::complex value; + + SparseComplex() = default; + explicit SparseComplex(std::complex value); + SparseComplex(const SparseComplex &other); + SparseComplex(SparseComplex &&other) noexcept; + SparseComplex& operator=(const SparseComplex &other); + SparseComplex& operator=(SparseComplex &&other) noexcept; +}; +using SparseElement = std::pair; +bool compareSparseElements(const SparseElement &lhs, const SparseElement &rhs); + + class SparseArray { public: - using Map = absl::flat_hash_map>; - using Iterator = Map::const_iterator; + using MapBasisVectorToSparseComplex = absl::flat_hash_map; + using VectorOfSparseElements = std::vector; + using Iterator = MapBasisVectorToSparseComplex::iterator; + using ConstIterator = MapBasisVectorToSparseComplex::const_iterator; +public: SparseArray() = delete; explicit SparseArray(std::size_t s); - [[nodiscard]] std::size_t getSize() const; - [[nodiscard]] std::vector> testToVector() const; - [[nodiscard]] Iterator begin() const; - [[nodiscard]] Iterator end() const; - void set(BasisVector index, std::complex value); + + [[nodiscard]] ConstIterator begin() const; + [[nodiscard]] ConstIterator end() const; + [[nodiscard]] Iterator begin(); + [[nodiscard]] Iterator end(); + + SparseArray& operator=(MapBasisVectorToSparseComplex map); + SparseArray& operator*=(double d); + SparseComplex& operator[](const BasisVector &index); + void clear(); - SparseArray &operator*=(double d); + [[nodiscard]] std::size_t size() const; + [[nodiscard]] std::vector> toVector() const; template void forEach(F &&f) { - cleanupZeros(); - std::for_each(data.begin(), data.end(), f); + cleanUpZeros(); + std::for_each(data_.begin(), data_.end(), f); } template void forEachSorted(F &&f) { - cleanupZeros(); - std::vector>> sorted(data.begin(), data.end()); - std::sort(sorted.begin(), sorted.end(), - [](auto const &left, auto const &right) { - return left.first < right.first; - }); + cleanUpZeros(); + VectorOfSparseElements sorted(data_.begin(), data_.end()); + std::sort(sorted.begin(), sorted.end(), compareSparseElements); std::for_each(sorted.begin(), sorted.end(), f); } template void eraseIf(F &&pred) { - absl::erase_if(data, pred); + absl::erase_if(data_, pred); } -private: - friend QuantumState; - // Let f build a new SparseArray to replace *this, assuming f is linear. template void applyLinear(F &&f) { // Every ZERO_CYCLE_SIZE gates, cleanup the 0s - if (zeroCounter >= config::ZERO_CYCLE_SIZE) { - cleanupZeros(); + if (zeroCounter_ >= config::ZERO_CYCLE_SIZE) { + cleanUpZeros(); } - ++zeroCounter; - Map result; - for (auto const &kv : data) { - f(kv.first, kv.second, result); + ++zeroCounter_; + MapBasisVectorToSparseComplex result; + for (auto const &[basisVector, complex_value] : data_) { + f(basisVector, complex_value, result); } - data.swap(result); + data_.swap(result); } - void cleanupZeros(); +private: + void cleanUpZeros(); - std::size_t const size = 0; - std::uint64_t zeroCounter = 0; - Map data; + std::size_t size_ = 0; + std::uint64_t zeroCounter_ = 0; + MapBasisVectorToSparseComplex data_; }; } // namespace qx::core diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index 736759eb..34a02bbd 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -15,7 +15,7 @@ QuantumState::QuantumState(std::size_t n) throw QuantumStateError{ fmt::format("quantum state size exceeds maximum allowed: {} > {}", numberOfQubits, config::MAX_QUBIT_NUMBER) }; } - data.set(BasisVector{}, std::complex{ 1 }); // Start initialized in state 00...000 + data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 }; [[nodiscard]] std::size_t QuantumState::getNumberOfQubits() const { @@ -24,7 +24,7 @@ QuantumState::QuantumState(std::size_t n) void QuantumState::reset() { data.clear(); - data.set(BasisVector{}, std::complex{ 1 }); // Start initialized in state 00...000 + data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 measurementRegister.reset(); } @@ -33,7 +33,7 @@ void QuantumState::testInitialize( data.clear(); double norm{}; for (auto const &[state_string, amplitude] : values) { - data.set(BasisVector{ state_string }, amplitude); + data[BasisVector{ state_string }] = SparseComplex{ amplitude }; norm += std::norm(amplitude); } assert(!isNotNull(norm - 1)); diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index 914133e1..918be16f 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -49,9 +49,10 @@ SimulationResult SimulationResultAccumulator::get() { for (const auto &[state, count] : measuredStates) { simulationResult.results.push_back(Result{ getStateString(state), count}); } - forAllNonZeroStates([&simulationResult](auto stateString, auto c) { + forAllNonZeroStates([&simulationResult](auto stateString, auto sparseComplex) { + auto c = sparseComplex.value; simulationResult.states.push_back( - State{ stateString, Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) } }); + State{ stateString, core::Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) } }); }); return simulationResult; } diff --git a/src/qx/SparseArray.cpp b/src/qx/SparseArray.cpp index f8d2543b..269d7a0c 100644 --- a/src/qx/SparseArray.cpp +++ b/src/qx/SparseArray.cpp @@ -1,56 +1,107 @@ #include "qx/SparseArray.hpp" +#include + namespace qx::core { -SparseArray::SparseArray(std::size_t s) - : size(s) +SparseArrayError::SparseArrayError(const std::string &message) + : std::runtime_error{ message } {} -[[nodiscard]] std::size_t SparseArray::getSize() const { - return size; +SparseComplex::SparseComplex(std::complex value) { + value = value; } -[[nodiscard]] std::vector> SparseArray::testToVector() const { - std::vector> result(getSize(), 0); - for (auto const &kv : *this) { - result[kv.first.toSizeT()] = kv.second; +SparseComplex::SparseComplex(const SparseComplex &other) { + value = other.value; +} + +SparseComplex::SparseComplex(SparseComplex &&other) noexcept { + value = std::move(other.value); +} + +SparseComplex& SparseComplex::operator=(const SparseComplex &other) { + if (std::abs(other.value) >= config::EPS) { + value = other.value; } - return result; + return *this; } -[[nodiscard]] SparseArray::Iterator SparseArray::begin() const { - return data.cbegin(); +SparseComplex& SparseComplex::operator=(SparseComplex &&other) noexcept { + if (std::abs(other.value) >= config::EPS) { + value = std::move(other.value); + } + return *this; +} + +bool compareSparseElements(const SparseElement &lhs, const SparseElement &rhs) { + return lhs.first < rhs.first; +} + +SparseArray::SparseArray(std::size_t s) + : size_(s) +{} + +SparseArray& SparseArray::operator=(MapBasisVectorToSparseComplex map) { + data_ = std::move(map); + return *this; } -[[nodiscard]] SparseArray::Iterator SparseArray::end() const { - return data.cend(); +SparseArray& SparseArray::operator*=(double d) { + for (auto &[_, sparseComplex] : data_) { + sparseComplex.value *= d; + } + return *this; } -void SparseArray::set(BasisVector index, std::complex value) { +SparseComplex& SparseArray::operator[](const BasisVector &index) { #ifndef NDEBUG - if (index.toSizeT() >= size) { - throw std::runtime_error("SparseArray::set index out of bounds"); + if (index.toSizeT() >= size_) { + throw SparseArrayError{ "index out of bounds" }; } #endif - if (std::abs(value) < config::EPS) { - return; - } - data.try_emplace(index, value); + return data_[index]; +} + +[[nodiscard]] SparseArray::ConstIterator SparseArray::begin() const { + return data_.cbegin(); +} + +[[nodiscard]] SparseArray::ConstIterator SparseArray::end() const { + return data_.cend(); +} + +[[nodiscard]] SparseArray::Iterator SparseArray::begin() { + return data_.begin(); +} + +[[nodiscard]] SparseArray::Iterator SparseArray::end() { + return data_.end(); } void SparseArray::clear() { - data.clear(); + data_.clear(); } -SparseArray &SparseArray::operator*=(double d) { - std::for_each(data.begin(), data.end(), [d](auto &kv) { kv.second *= d; }); - return *this; +[[nodiscard]] std::size_t SparseArray::size() const { + return size_; +} + +[[nodiscard]] std::vector> SparseArray::toVector() const { + auto result = std::vector>(size_, 0.); + for (auto const &[basisVector, sparseComplex] : data_) { + result[basisVector.toSizeT()] = sparseComplex.value; + } + return result; } -void SparseArray::cleanupZeros() { - absl::erase_if(data, [](auto const &kv) { return !isNotNull(kv.second); }); - zeroCounter = 0; +void SparseArray::cleanUpZeros() { + absl::erase_if(data_, [](auto const &kv) { + auto const &[_, sparseComplex] = kv; + return !isNotNull(sparseComplex.value); + }); + zeroCounter_ = 0; } } // namespace qx::core diff --git a/test/ErrorModelsTest.cpp b/test/ErrorModelsTest.cpp index f6255734..4a738298 100644 --- a/test/ErrorModelsTest.cpp +++ b/test/ErrorModelsTest.cpp @@ -16,8 +16,9 @@ class ErrorModelsTest : public ::testing::Test { void checkState(const std::map> &expected) { state.forEach([&expected](auto const &kv) { - ASSERT_EQ(expected.count(kv.first), 1); - EXPECT_EQ(expected.at(kv.first), kv.second); + auto const &[basisVector, sparseComplex] = kv; + ASSERT_EQ(expected.count(basisVector), 1); + EXPECT_EQ(expected.at(basisVector), sparseComplex.value); }); } diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 3a64f9d5..77181f9d 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -34,8 +34,8 @@ CNOT q[0], q[1] EXPECT_EQ(actual.shots_done, 1); EXPECT_EQ(actual.states, (SimulationResult::States{ - { "00", Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, - { "11", Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } + { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, + { "11", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } })); } @@ -50,7 +50,7 @@ CNOT q[0:2], q[3:5] auto actual = runFromString(cqasm, 2); EXPECT_EQ(actual.states, - (SimulationResult::States{ { "111111", Complex{ .real = 1, .imag = 0, .norm = 1 } } })); + (SimulationResult::States{ { "111111", core::Complex{ .real = 1, .imag = 0, .norm = 1 } } })); } TEST_F(IntegrationTest, too_many_qubits) { @@ -96,8 +96,8 @@ I q[1] EXPECT_EQ(actual.shots_done, 1); EXPECT_EQ(actual.states, (SimulationResult::States{ - { "00", Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, - { "11", Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } + { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, + { "11", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } })); } @@ -117,7 +117,7 @@ b = measure q )"; auto actual = runFromString(cqasm, iterations); - auto error = static_cast(iterations/2 * 0.05); + auto error = static_cast(static_cast(iterations)/2 * 0.05); EXPECT_EQ(actual.results.size(), 2); EXPECT_EQ(actual.results[0].state, "001"); EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); @@ -126,7 +126,7 @@ b = measure q // State could be 001 or 111 EXPECT_TRUE(actual.states[0].value.ends_with('1')); - EXPECT_EQ(actual.states[0].amplitude, (Complex{ .real = 1, .imag = 0, .norm = 1 })); + EXPECT_EQ(actual.states[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); } TEST_F(IntegrationTest, multiple_measure_instructions) { @@ -147,7 +147,7 @@ b[2] = measure q[2] std::size_t iterations = 10'000; auto actual = runFromString(cqasm, iterations); - auto error = static_cast(iterations/2 * 0.05); + auto error = static_cast(static_cast(iterations)/2 * 0.05); EXPECT_EQ(actual.results.size(), 2); EXPECT_EQ(actual.results[0].state, "001"); EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); @@ -156,7 +156,7 @@ b[2] = measure q[2] // State could be 001 or 111 EXPECT_TRUE(actual.states[0].value.ends_with('1')); - EXPECT_EQ(actual.states[0].amplitude, (Complex{ .real = 1, .imag = 0, .norm = 1 })); + EXPECT_EQ(actual.states[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); } TEST_F(IntegrationTest, mid_circuit_measure_instruction) { @@ -177,7 +177,7 @@ b = measure q auto actual = runFromString(cqasm, iterations); // Expected output state: |00>+|11> or |01>+|10> - auto error = static_cast(iterations/2 * 0.05); + auto error = static_cast(static_cast(iterations)/2 * 0.05); EXPECT_EQ(actual.results.size(), 2); EXPECT_TRUE(actual.results[0].state == "00" || actual.results[0].state == "01"); EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); @@ -205,7 +205,7 @@ b1 = measure q1 auto actual = runFromString(cqasm, iterations); // Expected output state: |00>+|11> or |01>+|10> - auto error = static_cast(iterations/2 * 0.05); + auto error = static_cast(static_cast(iterations)/2 * 0.05); EXPECT_EQ(actual.results.size(), 2); EXPECT_TRUE(actual.results[0].state == "00" || actual.results[0].state == "01"); EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index b8a8d4f4..7ae337bb 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -18,11 +18,12 @@ class QuantumStateTest : public ::testing::Test { std::size_t nonZeros = std::count_if(expected.begin(), expected.end(), isNotNull); - victim.forEach([&nonZeros, &expected](auto const &kv) { + victim.forEach([&nonZeros, &expected](auto const &sparseElement) { + auto const &[basisVector, sparseComplex] = sparseElement; EXPECT_GT(nonZeros, 0); - EXPECT_NEAR(expected[kv.first.toSizeT()].real(), kv.second.real(), + EXPECT_NEAR(expected[basisVector.toSizeT()].real(), sparseComplex.value.real(), .00000000000001); - EXPECT_NEAR(expected[kv.first.toSizeT()].imag(), kv.second.imag(), + EXPECT_NEAR(expected[basisVector.toSizeT()].imag(), sparseComplex.value.imag(), .00000000000001); --nonZeros; }); diff --git a/test/SparseArrayTest.cpp b/test/SparseArrayTest.cpp index 09775193..35a16982 100644 --- a/test/SparseArrayTest.cpp +++ b/test/SparseArrayTest.cpp @@ -1,8 +1,9 @@ #include "qx/SparseArray.hpp" +#include #include // ThrowsMessageHasSubstr #include -#include // runtime_error +#include namespace qx::core { @@ -10,23 +11,19 @@ namespace qx::core { using namespace std::complex_literals; TEST(sparse_array, set) { - SparseArray victim(5); - - EXPECT_EQ(victim.testToVector(), - std::vector>({0, 0, 0, 0, 0})); + SparseArray victim{ 5 }; + EXPECT_EQ(victim.toVector(), (std::vector>{ 0., 0., 0., 0., 0. })); BasisVector key; key.set(2); - victim.set(key, 1i); - - EXPECT_EQ(victim.testToVector(), - std::vector>({0, 0, 0, 0, 1i})); + victim[key] = SparseComplex{ 1i }; + EXPECT_EQ(victim.toVector(), (std::vector>{ 0., 0., 0., 0., 1i })); #ifndef NDEBUG key.set(1); ASSERT_EQ(key.toSizeT(), 6); - EXPECT_THAT(([&victim, &key]() { victim.set(key, 0.1); }), - ::testing::ThrowsMessage("SparseArray::set index out of bounds")); + EXPECT_THAT(([&victim, &key]() { victim[key] = SparseComplex{ 0.1 }; }), + ::testing::ThrowsMessage("index out of bounds")); #endif } From cc3c01b5637c236a269e26044fed3b6ed9827015 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 15:49:20 +0200 Subject: [PATCH 08/56] Add current branch to test.yml. --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8f2667a9..4411ff00 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,7 @@ name: Test on: push: branches: + - 154-code-manage-bit-register-variables - develop pull_request: From dcc9da14406bf626e682f96d058764f1aaa90dc2 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 16:03:58 +0200 Subject: [PATCH 09/56] Trying to fix tests. --- include/qx/SparseArray.hpp | 2 +- src/qx/SparseArray.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/qx/SparseArray.hpp b/include/qx/SparseArray.hpp index 0ac16b41..4134825a 100644 --- a/include/qx/SparseArray.hpp +++ b/include/qx/SparseArray.hpp @@ -26,7 +26,7 @@ struct SparseComplex { std::complex value; SparseComplex() = default; - explicit SparseComplex(std::complex value); + explicit SparseComplex(std::complex c); SparseComplex(const SparseComplex &other); SparseComplex(SparseComplex &&other) noexcept; SparseComplex& operator=(const SparseComplex &other); diff --git a/src/qx/SparseArray.cpp b/src/qx/SparseArray.cpp index 269d7a0c..1ad5d60a 100644 --- a/src/qx/SparseArray.cpp +++ b/src/qx/SparseArray.cpp @@ -9,8 +9,8 @@ SparseArrayError::SparseArrayError(const std::string &message) : std::runtime_error{ message } {} -SparseComplex::SparseComplex(std::complex value) { - value = value; +SparseComplex::SparseComplex(std::complex c) { + value = c; } SparseComplex::SparseComplex(const SparseComplex &other) { From 1f1f3f69759625d113e08c66dda8239af62e2ed8 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 16:27:29 +0200 Subject: [PATCH 10/56] Trying to fix Windows tests. --- include/qx/Core.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/qx/Core.hpp b/include/qx/Core.hpp index 2cfd2d65..8afb8bfa 100644 --- a/include/qx/Core.hpp +++ b/include/qx/Core.hpp @@ -21,8 +21,8 @@ struct BitIndex { using BasisVector = utils::Bitset; inline constexpr bool isNotNull(std::complex c) { - return std::abs(c.real()) > config::EPS || - std::abs(c.imag()) > config::EPS; + return std::abs(c.real()) > config::EPS || + std::abs(c.imag()) > config::EPS; } struct Complex { From 43a5e2e75008126182615e5c42afdcf20e7bcd28 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 16:30:33 +0200 Subject: [PATCH 11/56] Trying to fix Windows tests. --- include/qx/Core.hpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/include/qx/Core.hpp b/include/qx/Core.hpp index 8afb8bfa..87830686 100644 --- a/include/qx/Core.hpp +++ b/include/qx/Core.hpp @@ -21,8 +21,15 @@ struct BitIndex { using BasisVector = utils::Bitset; inline constexpr bool isNotNull(std::complex c) { - return std::abs(c.real()) > config::EPS || - std::abs(c.imag()) > config::EPS; +#if defined(_MSC_VER) + return c.real() > config::EPS || + -c.real() > config::EPS || + c.imag() > config::EPS || + -c.imag() > config::EPS; +#else + return std::abs(c.real()) > config::EPS || + std::abs(c.imag()) > config::EPS; +#endif } struct Complex { From 350863137518b19651241e7cd7ffe865510e0dcf Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 16:36:05 +0200 Subject: [PATCH 12/56] Fix warning. --- src/qx/QuantumState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index 34a02bbd..9aba072e 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -7,7 +7,7 @@ namespace qx::core { QuantumState::QuantumState(std::size_t n) : numberOfQubits(n), - data(1 << numberOfQubits) { + data(static_cast(1) << numberOfQubits) { if (numberOfQubits == 0) { throw QuantumStateError{ "quantum state needs at least one qubit" }; } From 6a0afeee23fe418ff677e2619decc6d71a9373bb Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 16:54:35 +0200 Subject: [PATCH 13/56] Trying to fix Windows tests. --- include/qx/SimulationResult.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index df3e351d..48d047b4 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -4,7 +4,7 @@ #include "qx/Core.hpp" // Complex #include -#include // strong_ordering +#include // partial_ordering #include #include // uint64_t #include @@ -22,14 +22,14 @@ class QuantumState; struct Result { std::string state; std::uint64_t count; - std::strong_ordering operator<=>(const Result &other) const = default; + std::partial_ordering operator<=>(const Result &other) const = default; }; struct State { std::string value; core::Complex amplitude; bool operator==(const State &other) const = default; - std::strong_ordering operator<=>(const State &other) const = default; + std::partial_ordering operator<=>(const State &other) const = default; }; struct SimulationResult { From 305d71ef66b8d9a061c67b08271987e407029927 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 17:16:11 +0200 Subject: [PATCH 14/56] Fix warning. --- src/qx/SimulationResult.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index 918be16f..63323341 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -35,7 +35,7 @@ SimulationResultAccumulator::SimulationResultAccumulator(core::QuantumState &s) {} void SimulationResultAccumulator::append(core::BasisVector measuredState) { - assert(measuredStates.size() <= (1u << quantumState.getNumberOfQubits())); + assert(measuredStates.size() <= (static_cast(1) << quantumState.getNumberOfQubits())); measuredStates[measuredState]++; nMeasurements++; } From a8789ac0269be422a54af34ca14f851c8d886469 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 17:24:33 +0200 Subject: [PATCH 15/56] Trying to fix Python tests. --- python/Qxelarator.i | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/Qxelarator.i b/python/Qxelarator.i index 5ed80708..ffcccec1 100644 --- a/python/Qxelarator.i +++ b/python/Qxelarator.i @@ -30,14 +30,14 @@ PyObject_SetAttrString(simulationResult, "shots_requested", PyLong_FromUnsignedLongLong(cppSimulationResult->shots_requested)); auto results = PyDict_New(); - for(auto const& x: cppSimulationResult->results) { - PyDict_SetItemString(results, x.first.c_str(), PyLong_FromUnsignedLongLong(x.second)); + for(auto const& [state, count]: cppSimulationResult->results) { + PyDict_SetItemString(results, state.c_str(), PyLong_FromUnsignedLongLong(count)); } PyObject_SetAttrString(simulationResult, "results", results); auto state = PyDict_New(); - for(auto const& x: cppSimulationResult->state) { - PyDict_SetItemString(state, x.first.c_str(), PyComplex_FromCComplex({ .real = x.second.real, .imag = x.second.imag })); + for(auto const& [value, amplitude]: cppSimulationResult->states) { + PyDict_SetItemString(state, value.c_str(), PyComplex_FromCComplex({ .real = amplitude.real, .imag = amplitude.imag })); } PyObject_SetAttrString(simulationResult, "state", state); From 18c22f478f46b6ea474366209a236fe5fd278d19 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 11 Jun 2024 18:40:26 +0200 Subject: [PATCH 16/56] Remove current branch from test.yml. [skip ci] --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4411ff00..8f2667a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,6 @@ name: Test on: push: branches: - - 154-code-manage-bit-register-variables - develop pull_request: From 42ca4e7f8622c888ee3bcdf19fa77ba6adaa0349 Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 12 Jun 2024 00:23:20 +0200 Subject: [PATCH 17/56] Rename nMeasurements, shots_requested, shots_done. --- docs/manual/output.rst | 2 +- include/qx/SimulationResult.hpp | 6 +++--- src/qx/SimulationResult.cpp | 12 ++++++------ test/IntegrationTest.cpp | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/manual/output.rst b/docs/manual/output.rst index e3e12aa7..bdf32de3 100644 --- a/docs/manual/output.rst +++ b/docs/manual/output.rst @@ -84,7 +84,7 @@ For example: When simulating this circuit, the final quantum state in the ``State`` section is non-deterministic. However, the aggregated measurement register is very useful and the ratios like -``results["001"] / shots_done`` converge as the number of iterations grow. +``results["001"] / shotsDone`` converge as the number of iterations grow. For 100 iterations: :: diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index 48d047b4..b3083607 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -35,8 +35,8 @@ struct State { struct SimulationResult { using Results = std::vector; using States = std::vector; - std::uint64_t shots_requested = 0; - std::uint64_t shots_done = 0; + std::uint64_t shotsRequested = 0; + std::uint64_t shotsDone = 0; Results results; States states; }; @@ -59,7 +59,7 @@ class SimulationResultAccumulator { core::QuantumState &quantumState; absl::btree_map measuredStates; - std::uint64_t nMeasurements = 0; + std::uint64_t measuredStatesCount = 0; }; } // namespace qx diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index 63323341..93050987 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -24,8 +24,8 @@ std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationRes config::OUTPUT_DECIMALS, result.state, result.count, - simulationResult.shots_done, - static_cast(result.count) / static_cast(simulationResult.shots_done)); + simulationResult.shotsDone, + static_cast(result.count) / static_cast(simulationResult.shotsDone)); } return os; } @@ -37,15 +37,15 @@ SimulationResultAccumulator::SimulationResultAccumulator(core::QuantumState &s) void SimulationResultAccumulator::append(core::BasisVector measuredState) { assert(measuredStates.size() <= (static_cast(1) << quantumState.getNumberOfQubits())); measuredStates[measuredState]++; - nMeasurements++; + measuredStatesCount++; } SimulationResult SimulationResultAccumulator::get() { SimulationResult simulationResult; - simulationResult.shots_requested = nMeasurements; - simulationResult.shots_done = nMeasurements; + simulationResult.shotsRequested = measuredStatesCount; + simulationResult.shotsDone = measuredStatesCount; - assert(nMeasurements > 0); + assert(measuredStatesCount > 0); for (const auto &[state, count] : measuredStates) { simulationResult.results.push_back(Result{ getStateString(state), count}); } diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 77181f9d..bc1566d5 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -30,8 +30,8 @@ CNOT q[0], q[1] )"; auto actual = runFromString(cqasm, 1, "3.0"); - EXPECT_EQ(actual.shots_requested, 1); - EXPECT_EQ(actual.shots_done, 1); + EXPECT_EQ(actual.shotsRequested, 1); + EXPECT_EQ(actual.shotsDone, 1); EXPECT_EQ(actual.states, (SimulationResult::States{ { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, @@ -92,8 +92,8 @@ I q[1] )"; auto actual = runFromString(cqasm, 1, "3.0"); - EXPECT_EQ(actual.shots_requested, 1); - EXPECT_EQ(actual.shots_done, 1); + EXPECT_EQ(actual.shotsRequested, 1); + EXPECT_EQ(actual.shotsDone, 1); EXPECT_EQ(actual.states, (SimulationResult::States{ { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, From c29527fe602002820af37d52b065322bc51bac3c Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 12 Jun 2024 00:31:31 +0200 Subject: [PATCH 18/56] Rename nMeasurements, shots_requested, shots_done. --- python/Qxelarator.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/Qxelarator.i b/python/Qxelarator.i index ffcccec1..ea2327db 100644 --- a/python/Qxelarator.i +++ b/python/Qxelarator.i @@ -26,8 +26,8 @@ auto const* cppSimulationResult = std::get_if(&$1); - PyObject_SetAttrString(simulationResult, "shots_done", PyLong_FromUnsignedLongLong(cppSimulationResult->shots_done)); - PyObject_SetAttrString(simulationResult, "shots_requested", PyLong_FromUnsignedLongLong(cppSimulationResult->shots_requested)); + PyObject_SetAttrString(simulationResult, "shots_done", PyLong_FromUnsignedLongLong(cppSimulationResult->shotsDone)); + PyObject_SetAttrString(simulationResult, "shots_requested", PyLong_FromUnsignedLongLong(cppSimulationResult->shotsRequested)); auto results = PyDict_New(); for(auto const& [state, count]: cppSimulationResult->results) { From ac2cae9506cab9af3b5fb23456c3b3975b71a6f6 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:43:44 +0200 Subject: [PATCH 19/56] Add BitMeasurementRegister to Core. Add bitIndex to Measure instruction. Add bit register size to QuantumState. Add bit measurement register to QuantumState. Add SparseArray.accumulate. Add boost/1.85.0 to conanfile.py. Change OperandsHelper.get_register_operand to work with bit operands. Change GateConvertor.addGates to accept bit indices in a Measure instruction. Refactor QuantumState.measure. Change CMakeLists.txt to work with Boost. Update CHANGELOG.md with minor aesthetic changes. Remove QuantumState.{measureAll, prep}. Remove InstructionExecutor.{MeasureAll, PrepZ}. --- CHANGELOG.md | 4 +- CMakeLists.txt | 25 ++++++++-- conanfile.py | 1 + include/qx/Circuit.hpp | 1 + include/qx/Core.hpp | 4 +- include/qx/QuantumState.hpp | 97 +++++++------------------------------ include/qx/SparseArray.hpp | 7 +++ src/qx/Circuit.cpp | 12 +---- src/qx/GateConvertor.cpp | 14 ++++-- src/qx/OperandsHelper.cpp | 39 ++++++++++----- src/qx/QuantumState.cpp | 49 +++++++++++++++++-- src/qx/RegisterManager.cpp | 4 +- src/qx/Simulator.cpp | 5 +- test/ErrorModelsTest.cpp | 3 +- test/QuantumStateTest.cpp | 59 ++++------------------ 15 files changed, 151 insertions(+), 173 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f7fc32..7e7e63d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,8 +74,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Removed submodules and `deps` folder, replaced with CMake FetchContent. - C++23. +- Manage dependencies via CMake FetchContent. - gtest instead of doctest. - Various fixes to GitHub actions. - Fixed bug in bitset due to bool automatic casting to 32 bits. @@ -84,7 +84,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Removed -- +- Submodules and `deps` folder. ## [ 0.6.2 ] - [ 2023-02-21 ] diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c7b3b94..0f208c3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,7 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) #=============================================================================# find_package(absl) +find_package(Boost REQUIRED) find_package(libqasm REQUIRED CONFIG) @@ -138,8 +139,9 @@ target_compile_features(qx PRIVATE cxx_std_23 ) -target_include_directories(qx - PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/" +target_include_directories(qx PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include/" + "${Boost_INCLUDE_DIRS}" ) if(CMAKE_COMPILER_IS_GNUCXX) @@ -207,8 +209,12 @@ target_link_libraries(qx PUBLIC # Executables # #=============================================================================# -add_executable("qx-simulator" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx-simulator/Simulator.cpp") -target_link_libraries(qx-simulator qx) +add_executable(qx-simulator + "${CMAKE_CURRENT_SOURCE_DIR}/src/qx-simulator/Simulator.cpp" +) +target_link_libraries(qx-simulator PRIVATE + qx +) #=============================================================================# # Testing # @@ -232,6 +238,17 @@ if(QX_BUILD_PYTHON) endif() +#=============================================================================# +# Debug info # +#=============================================================================# + +message(STATUS + "[${PROJECT_NAME}] Target include directories:\n" + " CMAKE_CURRENT_SOURCE_DIR/include/: ${CMAKE_CURRENT_SOURCE_DIR}/include/\n" + " Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}\n" +) + + #=============================================================================# # Installation # #=============================================================================# diff --git a/conanfile.py b/conanfile.py index db226524..0638d250 100644 --- a/conanfile.py +++ b/conanfile.py @@ -48,6 +48,7 @@ def build_requirements(self): def requirements(self): self.requires("abseil/20230125.3", transitive_headers=True) + self.requires("boost/1.85.0") self.requires("fmt/10.2.1", transitive_headers=True) self.requires("libqasm/0.6.6", transitive_headers=True) self.requires("range-v3/0.12.0", transitive_headers=True) diff --git a/include/qx/Circuit.hpp b/include/qx/Circuit.hpp index f1543b1f..c430786f 100644 --- a/include/qx/Circuit.hpp +++ b/include/qx/Circuit.hpp @@ -15,6 +15,7 @@ namespace qx { class Circuit { public: struct Measure { + core::BitIndex bitIndex{}; core::QubitIndex qubitIndex{}; }; struct MeasureAll { diff --git a/include/qx/Core.hpp b/include/qx/Core.hpp index 87830686..8b3fc1ca 100644 --- a/include/qx/Core.hpp +++ b/include/qx/Core.hpp @@ -1,6 +1,7 @@ #pragma once -#include // size_t +#include +#include // size_t, uint32_t #include // abs #include @@ -19,6 +20,7 @@ struct BitIndex { }; using BasisVector = utils::Bitset; +using BitMeasurementRegister = boost::dynamic_bitset; inline constexpr bool isNotNull(std::complex c) { #if defined(_MSC_VER) diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 857fab82..62af3a0b 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -11,6 +11,7 @@ #include "qx/Core.hpp" // BasisVector, QubitIndex #include "qx/CompileTimeConfiguration.hpp" // MAX_QUBIT_NUMBER #include "qx/DenseUnitaryMatrix.hpp" +#include "qx/RegisterManager.hpp" #include "qx/SparseArray.hpp" @@ -25,7 +26,7 @@ template void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, std::array const &operands, BasisVector index, - SparseComplex sparseComplex, + SparseComplex const &sparseComplex, SparseArray::MapBasisVectorToSparseComplex &storage) { utils::Bitset reducedIndex; for (std::size_t i = 0; i < NumberOfOperands; ++i) { @@ -47,8 +48,9 @@ void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, class QuantumState { public: - explicit QuantumState(std::size_t n); + explicit QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size); [[nodiscard]] std::size_t getNumberOfQubits() const; + [[nodiscard]] std::size_t getNumberOfBits() const; void reset(); void testInitialize(std::initializer_list>> values); @@ -75,92 +77,27 @@ class QuantumState { } [[nodiscard]] const BasisVector &getMeasurementRegister() const; + [[nodiscard]] const BitMeasurementRegister &getBitMeasurementRegister() const; + [[nodiscard]] double getProbabilityOfMeasuringOne(QubitIndex qubitIndex); + [[nodiscard]] double getProbabilityOfMeasuringZero(QubitIndex qubitIndex); + void collapseQubit(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne); template - void measure(QubitIndex qubitIndex, F &&randomGenerator) { - auto rand = randomGenerator(); - double probabilityOfMeasuringOne = 0.; - data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { - auto const &[basisVector, sparseComplex] = kv; - if (basisVector.test(qubitIndex.value)) { - probabilityOfMeasuringOne += std::norm(sparseComplex.value); - } - }); - if (rand < probabilityOfMeasuringOne) { - data.eraseIf([qubitIndex](auto const &kv) { - auto const &[basisVector, _] = kv; - return !basisVector.test(qubitIndex.value); - }); - data *= std::sqrt(1 / probabilityOfMeasuringOne); - measurementRegister.set(qubitIndex.value, true); - } else { - data.eraseIf([qubitIndex](auto const &kv) { - auto const &[basisVector, _] = kv; - return basisVector.test(qubitIndex.value); - }); - data *= std::sqrt(1 / (1 - probabilityOfMeasuringOne)); - measurementRegister.set(qubitIndex.value, false); - } - } - - template - void measureAll(F &&randomGenerator) { - auto rand = randomGenerator(); - double probability = 0.; - auto [basisVector, sparseComplex] = std::invoke([this, &probability, rand] { - for (auto const &[bv, sc] : data) { // does this work with non-ordered iteration? - probability += std::norm(sc.value); - if (probability > rand) { - return SparseElement{ bv, sc }; - } - } - throw std::runtime_error{ "Vector was not normalized at measurement location (a bug)" }; - }); - data.clear(); - data[basisVector] = SparseComplex{ sparseComplex.value / std::abs(sparseComplex.value) }; - measurementRegister = basisVector; + void measure(BitIndex bitIndex, QubitIndex qubitIndex, F &&randomGenerator) { + auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); + // measuredState will be true if we measured a 1, or false if we measured a 0 + auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); + collapseQubit(qubitIndex, measuredState, probabilityOfMeasuringOne); + measurementRegister.set(qubitIndex.value, measuredState); + bitMeasurementRegister.set(bitIndex.value, measuredState); } - template - void prep(QubitIndex qubitIndex, F &&randomGenerator) { - // Measure + conditional X, and reset the measurement register. - auto rand = randomGenerator(); - double probabilityOfMeasuringOne = 0.; - data.forEach([qubitIndex, &probabilityOfMeasuringOne](auto const &kv) { - auto const &[basisVector, sparseComplex] = kv; - if (basisVector.test(qubitIndex.value)) { - probabilityOfMeasuringOne += std::norm(sparseComplex.value); - } - }); - if (rand < probabilityOfMeasuringOne) { - data.eraseIf([qubitIndex](auto const &kv) { - auto const &[basisVector, _] = kv; - return !basisVector.test(qubitIndex.value); - }); - SparseArray::MapBasisVectorToSparseComplex newData; - for (auto [basisVector, sparseComplex] : data) { - auto newKey = basisVector; - newKey.set(qubitIndex.value, false); - newData.insert(std::make_pair( - newKey, - SparseComplex{ sparseComplex.value * std::sqrt(1 / probabilityOfMeasuringOne) } - )); - } - data = newData; // could fix the interface - } else { - data.eraseIf([qubitIndex](auto const &kv) { - auto const &[basisVector, _] = kv; - return basisVector.test(qubitIndex.value); - }); - data *= std::sqrt(1 / (1 - probabilityOfMeasuringOne)); - } - measurementRegister.set(qubitIndex.value, false); - }; - private: std::size_t const numberOfQubits = 1; + std::size_t const numberOfBits = 1; SparseArray data; BasisVector measurementRegister{}; + BitMeasurementRegister bitMeasurementRegister{}; }; } // namespace qx::core diff --git a/include/qx/SparseArray.hpp b/include/qx/SparseArray.hpp index 4134825a..431cabd3 100644 --- a/include/qx/SparseArray.hpp +++ b/include/qx/SparseArray.hpp @@ -4,6 +4,7 @@ #include // for_each, sort #include #include // size_t, uint64_t +#include // accumulate #include // runtime_error #include // pair #include @@ -60,6 +61,12 @@ class SparseArray { [[nodiscard]] std::size_t size() const; [[nodiscard]] std::vector> toVector() const; + template + T accumulate(T init, F &&f) { + cleanUpZeros(); + return std::accumulate(data_.begin(), data_.end(), init, f); + } + template void forEach(F &&f) { cleanUpZeros(); diff --git a/src/qx/Circuit.cpp b/src/qx/Circuit.cpp index 8e06e199..fc4db898 100644 --- a/src/qx/Circuit.cpp +++ b/src/qx/Circuit.cpp @@ -14,13 +14,7 @@ struct InstructionExecutor { explicit InstructionExecutor(core::QuantumState &s) : quantumState(s){}; void operator()(Circuit::Measure const &m) { - quantumState.measure(m.qubitIndex, &random::randomZeroOneDouble); - } - void operator()(Circuit::MeasureAll const &) { - quantumState.measureAll(&random::randomZeroOneDouble); - } - void operator()(Circuit::PrepZ const &r) { - quantumState.prep(r.qubitIndex, &random::randomZeroOneDouble); + quantumState.measure(m.bitIndex, m.qubitIndex, &random::randomZeroOneDouble); } void operator()(Circuit::MeasurementRegisterOperation const &op) { op.operation(quantumState.getMeasurementRegister()); @@ -78,10 +72,6 @@ void Circuit::execute(core::QuantumState &quantumState, error_models::ErrorModel // std::visit(instructionExecutor, instruction); if (auto *measure = std::get_if(&instruction)) { instruction_executor(*measure); - } else if (auto *measureAll = std::get_if(&instruction)) { - instruction_executor(*measureAll); - } else if (auto *prepZ = std::get_if(&instruction)) { - instruction_executor(*prepZ); } else if (auto *classicalOp = std::get_if(&instruction)) { instruction_executor(*classicalOp); } else if (auto *instruction1 = std::get_if>(&instruction)) { diff --git a/src/qx/GateConvertor.cpp b/src/qx/GateConvertor.cpp index d314b255..43cffcc0 100644 --- a/src/qx/GateConvertor.cpp +++ b/src/qx/GateConvertor.cpp @@ -131,10 +131,16 @@ void GateConvertor::addGates(const V3Instruction &instruction) { // A measure statement has the following syntax: b = measure q // The left-hand side operand, b, is the operand 0 // The right-hand side operand, q, is the operand 1 - for (const auto &q: operands_helper.get_register_operand(1)) { - auto controlBits = std::make_shared>(); - circuit_.add_instruction(Circuit::Measure{ core::QubitIndex{ static_cast(q->value) } }, - controlBits); + const auto &bit_indices = operands_helper.get_register_operand(0); + const auto &qubit_indices = operands_helper.get_register_operand(1); + auto controlBits = std::make_shared>(); + for (size_t i{ 0 }; i < bit_indices.size(); ++i) { + circuit_.add_instruction( + Circuit::Measure{ + core::BitIndex{ static_cast(bit_indices[i]->value) }, + core::QubitIndex{ static_cast(qubit_indices[i]->value) } + }, + controlBits); } } else if (name == "CR") { addGates<2>( diff --git a/src/qx/OperandsHelper.cpp b/src/qx/OperandsHelper.cpp index 279598e2..0b27f220 100644 --- a/src/qx/OperandsHelper.cpp +++ b/src/qx/OperandsHelper.cpp @@ -15,25 +15,38 @@ OperandsHelper::OperandsHelper(const V3Instruction &instruction, const RegisterM [[nodiscard]] V3Many OperandsHelper::get_register_operand(int id) const { if (auto variable_ref = instruction_.operands[id]->as_variable_ref()) { - if (!is_qubit_variable(*variable_ref->variable)) { + auto ret = V3Many{}; + if (is_qubit_variable(*variable_ref->variable)) { + auto qubit_range = register_manager_.get_qubit_range(variable_ref->variable->name); + ret.get_vec().resize(qubit_range.size); + std::generate_n(ret.get_vec().begin(), qubit_range.size, [qubit_range, i=0]() mutable { + return v3_tree::make(static_cast(qubit_range.first + i++)); + }); + } else if (is_bit_variable(*variable_ref->variable)) { + auto bit_range = register_manager_.get_bit_range(variable_ref->variable->name); + ret.get_vec().resize(bit_range.size); + std::generate_n(ret.get_vec().begin(), bit_range.size, [bit_range, i=0]() mutable { + return v3_tree::make(static_cast(bit_range.first + i++)); + }); + } else { return {}; } - auto qubit_range = register_manager_.get_qubit_range(variable_ref->variable->name); - auto ret = V3Many{}; - ret.get_vec().resize(qubit_range.size); - std::generate_n(ret.get_vec().begin(), qubit_range.size, [qubit_range, i=0]() mutable { - return v3_tree::make(static_cast(qubit_range.first + i++)); - }); return ret; } else if (auto index_ref = instruction_.operands[id]->as_index_ref()) { - if (!is_qubit_variable(*index_ref->variable)) { + auto ret = index_ref->indices; + if (is_qubit_variable(*index_ref->variable)) { + auto qubit_range = register_manager_.get_qubit_range(index_ref->variable->name); + std::for_each(ret.get_vec().begin(), ret.get_vec().end(), [qubit_range](const auto &index) { + index->value += qubit_range.first; + }); + } else if (is_bit_variable(*index_ref->variable)) { + auto bit_range = register_manager_.get_bit_range(index_ref->variable->name); + std::for_each(ret.get_vec().begin(), ret.get_vec().end(), [bit_range](const auto &index) { + index->value += bit_range.first; + }); + } else { return {}; } - auto qubit_range = register_manager_.get_qubit_range(index_ref->variable->name); - auto ret = index_ref->indices; - std::for_each(ret.get_vec().begin(), ret.get_vec().end(), [qubit_range](const auto &index) { - index->value += qubit_range.first; - }); return ret; } assert(false && "operand is neither a variable reference nor an index reference"); diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index 9aba072e..e9208a6b 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -5,9 +5,11 @@ namespace qx::core { -QuantumState::QuantumState(std::size_t n) - : numberOfQubits(n), - data(static_cast(1) << numberOfQubits) { +QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size) + : numberOfQubits(qubit_register_size) + , numberOfBits(bit_register_size) + , data(static_cast(1) << numberOfQubits) + , bitMeasurementRegister{ numberOfBits } { if (numberOfQubits == 0) { throw QuantumStateError{ "quantum state needs at least one qubit" }; } @@ -15,6 +17,10 @@ QuantumState::QuantumState(std::size_t n) throw QuantumStateError{ fmt::format("quantum state size exceeds maximum allowed: {} > {}", numberOfQubits, config::MAX_QUBIT_NUMBER) }; } + if (numberOfQubits > config::MAX_BIT_NUMBER) { + throw QuantumStateError{ fmt::format("quantum state size exceeds maximum allowed: {} > {}", numberOfQubits, + config::MAX_QUBIT_NUMBER) }; + } data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 }; @@ -22,6 +28,10 @@ QuantumState::QuantumState(std::size_t n) return numberOfQubits; } +[[nodiscard]] std::size_t QuantumState::getNumberOfBits() const { + return numberOfBits; +} + void QuantumState::reset() { data.clear(); data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 @@ -50,8 +60,39 @@ template QuantumState &QuantumState::apply<2>( template QuantumState &QuantumState::apply<3>( DenseUnitaryMatrix<1 << 3> const &m, std::array const &operands); -[[nodiscard]] const BasisVector &QuantumState::getMeasurementRegister() const { +[[nodiscard]] const BasisVector& QuantumState::getMeasurementRegister() const { return measurementRegister; } +[[nodiscard]] const BitMeasurementRegister& QuantumState::getBitMeasurementRegister() const { + return bitMeasurementRegister; +} + +[[nodiscard]] double QuantumState::getProbabilityOfMeasuringOne(QubitIndex qubitIndex) { + return data.accumulate(double{}, [qubitIndex](auto total, auto const &kv) { + auto const &[basisVector, sparseComplex] = kv; + if (basisVector.test(qubitIndex.value)) { + total += std::norm(sparseComplex.value); + } + return total; + }); +} + +[[nodiscard]] double QuantumState::getProbabilityOfMeasuringZero(QubitIndex qubitIndex) { + return 1.0 - getProbabilityOfMeasuringOne(qubitIndex); +} + +void QuantumState::collapseQubit(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne) { + data.eraseIf([qubitIndex, measuredState](auto const &kv) { + auto const &[basisVector, _] = kv; + auto currentState = basisVector.test(qubitIndex.value); + return currentState != measuredState; + }); + + auto probability = measuredState + ? probabilityOfMeasuringOne + : (1 - probabilityOfMeasuringOne); + data *= std::sqrt(1 / probability); + +} } // namespace qx::core diff --git a/src/qx/RegisterManager.cpp b/src/qx/RegisterManager.cpp index 906f98fb..2a29e47c 100644 --- a/src/qx/RegisterManager.cpp +++ b/src/qx/RegisterManager.cpp @@ -57,7 +57,7 @@ Register::~Register() = default; QubitRegister::QubitRegister(const V3OneProgram &program) try : Register(program, is_qubit_variable, config::MAX_QUBIT_NUMBER) { } catch (const RegisterManagerError &e) { - throw RegisterManagerError{ fmt::format("Qubit register size exceeds maximum allowed: {} > {}", + throw RegisterManagerError{ fmt::format("qubit register size exceeds maximum allowed: {} > {}", e.what(), config::MAX_QUBIT_NUMBER) }; } @@ -66,7 +66,7 @@ QubitRegister::~QubitRegister() = default; BitRegister::BitRegister(const V3OneProgram &program) try : Register(program, is_bit_variable, config::MAX_BIT_NUMBER) { } catch (const RegisterManagerError &e) { - throw RegisterManagerError{ fmt::format("Bit register size exceeds maximum allowed: {} > {}", + throw RegisterManagerError{ fmt::format("bit register size exceeds maximum allowed: {} > {}", e.what(), config::MAX_BIT_NUMBER) }; } diff --git a/src/qx/Simulator.cpp b/src/qx/Simulator.cpp index 0a70f4d2..6c5d2880 100644 --- a/src/qx/Simulator.cpp +++ b/src/qx/Simulator.cpp @@ -66,7 +66,10 @@ execute( try { auto register_manager = RegisterManager{ program }; - auto quantumState = core::QuantumState{ register_manager.get_qubit_register_size() }; + auto quantumState = core::QuantumState{ + register_manager.get_qubit_register_size(), + register_manager.get_bit_register_size() + }; auto circuit = Circuit{ program, register_manager }; auto simulationResultAccumulator = SimulationResultAccumulator{ quantumState }; diff --git a/test/ErrorModelsTest.cpp b/test/ErrorModelsTest.cpp index 4a738298..c6c2de8c 100644 --- a/test/ErrorModelsTest.cpp +++ b/test/ErrorModelsTest.cpp @@ -27,8 +27,7 @@ class ErrorModelsTest : public ::testing::Test { } private: - core::QuantumState state{ - 3}; // Using a mock or a TestQuantumState would be beneficial here. + core::QuantumState state{ 3, 3, }; // Using a mock or a TestQuantumState would be beneficial here. }; TEST_F(ErrorModelsTest, depolarizing_channel__probability_1) { diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index 7ae337bb..06019a41 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -32,7 +32,7 @@ class QuantumStateTest : public ::testing::Test { }; TEST_F(QuantumStateTest, apply_identity) { - QuantumState victim(3); + QuantumState victim{ 3, 3 }; EXPECT_EQ(victim.getNumberOfQubits(), 3); checkEq(victim, {1, 0, 0, 0, 0, 0, 0, 0}); @@ -44,7 +44,7 @@ TEST_F(QuantumStateTest, apply_identity) { } TEST_F(QuantumStateTest, apply_hadamard) { - QuantumState victim(3); + QuantumState victim{ 3, 3 }; EXPECT_EQ(victim.getNumberOfQubits(), 3); checkEq(victim, {1, 0, 0, 0, 0, 0, 0, 0}); @@ -58,76 +58,37 @@ TEST_F(QuantumStateTest, apply_hadamard) { } TEST_F(QuantumStateTest, apply_cnot) { - QuantumState victim(2); + QuantumState victim{ 2, 2 }; victim.testInitialize({{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); - checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); - victim.apply<2>(gates::CNOT, std::array{QubitIndex{1}, QubitIndex{0}}); - checkEq(victim, {0, 0, std::sqrt(1 - std::pow(0.123, 2)), 0.123}); } TEST_F(QuantumStateTest, measure_on_non_superposed_state) { - QuantumState victim(2); + QuantumState victim{ 2, 2 }; victim.testInitialize({{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); - - victim.measure(QubitIndex{1}, []() { return 0.9485; }); + victim.measure(BitIndex{1}, QubitIndex{1}, []() { return 0.9485; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); - victim.measure(QubitIndex{1}, []() { return 0.045621; }); + victim.measure(BitIndex{1}, QubitIndex{1}, []() { return 0.045621; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); - EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("10")); } TEST_F(QuantumStateTest, measure_on_superposed_state__case_0) { - QuantumState victim(2); + QuantumState victim{ 2, 2 }; victim.testInitialize({{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); - - victim.measure(QubitIndex{0}, []() { return 0.994; }); + victim.measure(BitIndex{0}, QubitIndex{0}, []() { return 0.994; }); checkEq(victim, {0, 0, 1, 0}); - EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } TEST_F(QuantumStateTest, measure_on_superposed_state__case_1) { - QuantumState victim(2); + QuantumState victim{ 2, 2 }; victim.testInitialize({{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); - - victim.measure(QubitIndex{0}, []() { return 0.254; }); + victim.measure(BitIndex{0}, QubitIndex{0}, []() { return 0.254; }); checkEq(victim, {0, 0, 0, 1}); - EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("01")); } -TEST_F(QuantumStateTest, measure_all) { - // measureAll cannot really be tested with superposition since it currently - // relies on the iteration order of flat_hash_map which is unspecified. - QuantumState victim(3); - victim.testInitialize({{"110", 1i}}); - - victim.measureAll([]() { return 0.994; }); - checkEq(victim, {0, 0, 0, 0, 0, 0, 1i, 0}); - - EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("110")); -} - -TEST_F(QuantumStateTest, prep__case_0) { - // Prep leads to a non-deterministic global quantum state because of the state collapse. - QuantumState victim(2); - victim.testInitialize({{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); - - victim.prep(QubitIndex{0}, []() { return 0.994; }); - checkEq(victim, {1, 0, 0, 0}); -} - -TEST_F(QuantumStateTest, prep__case_1) { - // Prep leads to a non-deterministic global quantum state because of the state collapse. - QuantumState victim(2); - victim.testInitialize({{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); - - victim.prep(QubitIndex{0}, []() { return 0.245; }); - checkEq(victim, {0, 0, 1, 0}); -} - } // namespace qx::core From 1cd759775fe9ebcd0253f44e59d8ecc29999c18c Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 18 Jun 2024 15:06:05 +0200 Subject: [PATCH 20/56] A SimulationResultAccumulator now has a state and bitMeasurements (temporarily also measurements). A SimulationResult has a state and a bitRegisterMeasurement (temporarily also measurements). A SimulationResult also has a qubit and a bit register. State is a vector of SuperposedState. Measurements is a vector of Measurement. SuperposedState is a state string and an amplitude. Measurement is a state string and a count. Add SimulationResultAccumulator bitMeasurementRegister member. Add QuantumState constructor receiving an initializer list. Add QuantumState checkQuantumState and isNormalized methods. Add SparseArray constructor receiving an initializer list. Add SparseArray norm method. Add Core isNull(complex) method. Add Core fmt::formatter for BitMeasurementRegister. Rename SimulationResult.results to measurements. Rename SimulationResult.states to state. Extract InstructionExecutor and Instructions to separate files. Remove QuantumState::testInitialize. Remove explicit instantiations of QuantumState::apply<1,2,3>. TODO: bit_measurement_register test. --- CMakeLists.txt | 1 + docs/manual/output.rst | 37 ++++++++------ docs/manual/usage.rst | 16 +++--- include/qx/Circuit.hpp | 40 +-------------- include/qx/Core.hpp | 40 ++++++++------- include/qx/DenseUnitaryMatrix.hpp | 7 ++- include/qx/InstructionExecutor.hpp | 27 ++++++++++ include/qx/Instructions.hpp | 54 ++++++++++++++++++++ include/qx/QuantumState.hpp | 21 +++++--- include/qx/RegisterManager.hpp | 13 +++-- include/qx/SimulationResult.hpp | 66 ++++++++++++++++++------- include/qx/SparseArray.hpp | 2 + include/qx/Utils.hpp | 9 ++++ python/qxelarator/__init__.py | 2 +- src/qx/Circuit.cpp | 35 +++---------- src/qx/GateConvertor.cpp | 5 +- src/qx/InstructionExecutor.cpp | 19 +++++++ src/qx/QuantumState.cpp | 69 +++++++++++++------------- src/qx/RegisterManager.cpp | 16 ++++++ src/qx/SimulationResult.cpp | 72 +++++++++++++++------------ src/qx/Simulator.cpp | 5 +- src/qx/SparseArray.cpp | 23 ++++++++- test/IntegrationTest.cpp | 79 ++++++++++++++++++------------ test/QuantumStateTest.cpp | 12 ++--- 24 files changed, 419 insertions(+), 251 deletions(-) create mode 100644 include/qx/InstructionExecutor.hpp create mode 100644 include/qx/Instructions.hpp create mode 100644 src/qx/InstructionExecutor.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f208c3d..92bcda3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,7 @@ add_library(qx "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/ErrorModels.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/GateConvertor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Gates.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/InstructionExecutor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/OperandsHelper.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/QuantumState.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Qxelarator.cpp" diff --git a/docs/manual/output.rst b/docs/manual/output.rst index bdf32de3..8c6d4c44 100644 --- a/docs/manual/output.rst +++ b/docs/manual/output.rst @@ -25,12 +25,12 @@ This will result in: Shots requested: 100 Shots done: 100 - Results: {'00': 100} + Measurements: {'00': 100} State: {'00': (0.7071067811865475+0j), '11': (0.7071067811865475+0j)} - ``Shots requested`` and ``Shots done`` are always equal to the number of iterations. -- The ``Results`` section contains how many times a given measurement register is captured when running the iterations. In this case, the circuit doesn't contain any measurement, and therefore the measurement registers are always 00, and this occurred 100 times. +- The ``Measurements`` section contains how many times a given measurement register is captured when running the iterations. In this case, the circuit doesn't contain any measurement, and therefore the measurement registers are always 00, and this occurred 100 times. - The ``State`` section contains the full quantum state at the end of the very last iteration. It maps quantum kets to complex amplitudes. Here you can recognize the usual bell pair state: ``1/sqrt(2) ( |00> + |11> )``. @@ -50,26 +50,30 @@ The result is now: Shots requested: 100 Shots done: 100 - Results: {'00': 49, '11': 51} + Measurements: {'00': 49, '11': 51} State: {'00': (0.9999999999999998+0j)} -You can there notice that the final quantum state is collapsed to "00", because of the measurements. The ket ``|00>`` doesn't have amplitude exactly 1 because of the -approximation of real numbers done by floating point arithmetic inside the simulator, something to keep in mind when interpreting the results. -Again, the ``State`` section is the quantum state at the end of the 100th iteration. Some other iterations ended up in the state ``|11>``. +You can there notice that the final quantum state is collapsed to "00", because of the measurements. +The ket ``|00>`` doesn't have amplitude exactly 1 because of the approximation of real numbers +done by floating point arithmetic inside the simulator, something to keep in mind when interpreting the results. +Again, the ``State`` section is the quantum state at the end of the 100th iteration. +Some other iterations ended up in the state ``|11>``. -The ``Results`` section can here be interpreted as: in 49 iterations out of 100, the final measurement register was "00" and in the remaining 51 iterations -it was "11". +The ``Measurements`` section can here be interpreted as: in 49 iterations out of 100, +the final measurement register was "00" and in the remaining 51 iterations it was "11". Iterating a simulation ---------------------- -For those circuits that contain only final measurements, running multiple iterations and aggregating measuremeng registers in the ``Results`` section is not very useful, since you can directly obtain the -probability of each measurement by taking the squared modulus of the complex numbers in the ``State`` section. +For those circuits that contain only final measurements, +running multiple iterations and aggregating measurement registers in the ``Measurements`` section is not very useful, +since you can directly obtain the probability of each measurement +by taking the squared modulus of the complex numbers in the ``State`` section. -However, the ``Results`` section takes all its meaning when using for instance conditional gates, since then the quantum state varies per iteration. -For example: +However, the ``Measurements`` section takes all its meaning when using for instance conditional gates, +since then the quantum state varies per iteration. For example: :: @@ -83,15 +87,16 @@ For example: cond(b[1]) x q[2] -When simulating this circuit, the final quantum state in the ``State`` section is non-deterministic. However, the aggregated measurement register is very useful and the ratios like -``results["001"] / shotsDone`` converge as the number of iterations grow. +When simulating this circuit, the final quantum state in the ``State`` section is non-deterministic. +However, the aggregated measurement register is very useful and the ratios like ``measurements["001"] / shotsDone`` +converge as the number of iterations grow. For 100 iterations: :: Shots requested: 100 Shots done: 100 - Results: {'000': 48, '001': 27, '011': 25} + Measurements: {'000': 48, '001': 27, '011': 25} State: {'000': (0.9999999999999998+0j)} For 10000 iterations: @@ -100,5 +105,5 @@ For 10000 iterations: Shots requested: 10000 Shots done: 10000 - Results: {'000': 4995, '001': 2517, '011': 2488} + Measurements: {'000': 4995, '001': 2517, '011': 2488} State: {'001': (0.9999999999999998+0j)} \ No newline at end of file diff --git a/docs/manual/usage.rst b/docs/manual/usage.rst index ee99545d..8cc79b15 100644 --- a/docs/manual/usage.rst +++ b/docs/manual/usage.rst @@ -21,17 +21,18 @@ The most straightforward way to execute a cQasm file is using the ``qxelarator.e >>> r Shots requested: 1 Shots done: 1 - Results: {'00': 1} + Measurements: {'00': 1} State: {'00': (0.7071067811865475+0j), '01': (0.7071067811865475+0j)} -The return value is a ``qxelarator.SimulationResult`` object and offers access to aggregated measurement register results and final quantum state: +The return value is a ``qxelarator.SimulationResult`` object and offers access to +aggregated measurement register results and final quantum state: .. code-block:: pycon >>> isinstance(r, qxelarator.SimulationResult) True - >>> r.results + >>> r.measurements {'00': 1} >>> r.state["00"] (0.7071067811865475+0j) @@ -44,7 +45,7 @@ You can also execute a cQasm file: >>> qxelarator.execute_file("bell_pair.qc") Shots requested: 1 Shots done: 1 - Results: {'11': 1} + Measurements: {'11': 1} State: {'11': (1+0j)} @@ -69,7 +70,7 @@ To simulate a quantum circuit multiple times, pass an integer number of iteratio >>> qxelarator.execute_file("bell_pair.qc", iterations = 10) Shots requested: 10 Shots done: 10 - Results: {'00': 3, '11': 7} + Measurements: {'00': 3, '11': 7} State: {'11': (1+0j)} @@ -79,9 +80,10 @@ Using a constant seed for random number generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default QX-simulator will generate different random numbers for different executions of a given circuit. -This means that ``measure``, ``prep_z`` and error models will make simulation results non-deterministic. +This means that ``measure`` and error models will make simulation results non-deterministic. -In some cases this is not desired. To make the output of the simulator deterministic over different runs, you can pass a constant ``seed`` parameter: +In some cases this is not desired. +To make the output of the simulator deterministic over different runs, you can pass a constant ``seed`` parameter: .. code-block:: python diff --git a/include/qx/Circuit.hpp b/include/qx/Circuit.hpp index c430786f..abbe9184 100644 --- a/include/qx/Circuit.hpp +++ b/include/qx/Circuit.hpp @@ -2,6 +2,7 @@ #include "qx/Core.hpp" // BasisVector #include "qx/ErrorModels.hpp" +#include "qx/Instructions.hpp" #include "qx/RegisterManager.hpp" #include "qx/V3xLibqasmInterface.hpp" @@ -14,45 +15,6 @@ namespace qx { class Circuit { public: - struct Measure { - core::BitIndex bitIndex{}; - core::QubitIndex qubitIndex{}; - }; - struct MeasureAll { - }; - struct PrepZ { - core::QubitIndex qubitIndex{}; - }; - struct MeasurementRegisterOperation { - std::function operation; - }; - template struct Unitary { - // Matrix is stored inline but could also be a pointer. - core::DenseUnitaryMatrix<1 << NumberOfOperands> matrix{}; - std::array operands{}; - }; - using Instruction = - std::variant< - Measure, - MeasureAll, - PrepZ, - MeasurementRegisterOperation, - Unitary<1>, - Unitary<2>, - Unitary<3> - >; - using ControlBits = std::shared_ptr>; - - struct ControlledInstruction { - ControlledInstruction(Instruction instruction, ControlBits control_bits) - : instruction(std::move(instruction)), control_bits(std::move(control_bits)){}; - - Instruction instruction; - std::shared_ptr> control_bits; - }; - - // We could in the future add loops and if/else... - Circuit(V3OneProgram &program, RegisterManager ®ister_manager); [[nodiscard]] RegisterManager& get_register_manager() const; void add_instruction(Instruction instruction, ControlBits control_bits); diff --git a/include/qx/Core.hpp b/include/qx/Core.hpp index 8b3fc1ca..78b270ff 100644 --- a/include/qx/Core.hpp +++ b/include/qx/Core.hpp @@ -4,6 +4,7 @@ #include // size_t, uint32_t #include // abs #include +#include #include "qx/CompileTimeConfiguration.hpp" // EPS, MAX_QUBIT_NUMBER #include "qx/Utils.hpp" @@ -11,6 +12,18 @@ namespace qx::core { +struct Complex { + double real = 0; + double imag = 0; + double norm = 0; + bool operator==(const Complex &other) const { + return std::abs(real - other.real) < config::EPS && + std::abs(imag - other.imag) < config::EPS && + std::abs(norm - other.norm) < config::EPS; + } + auto operator<=>(const Complex &other) const = default; +}; + struct QubitIndex { std::size_t value; }; @@ -20,30 +33,23 @@ struct BitIndex { }; using BasisVector = utils::Bitset; + using BitMeasurementRegister = boost::dynamic_bitset; inline constexpr bool isNotNull(std::complex c) { #if defined(_MSC_VER) - return c.real() > config::EPS || - -c.real() > config::EPS || - c.imag() > config::EPS || - -c.imag() > config::EPS; + return + c.real() > config::EPS || -c.real() > config::EPS || + c.imag() > config::EPS || -c.imag() > config::EPS; #else - return std::abs(c.real()) > config::EPS || - std::abs(c.imag()) > config::EPS; + return std::abs(c.real()) > config::EPS || std::abs(c.imag()) > config::EPS; #endif } -struct Complex { - double real = 0; - double imag = 0; - double norm = 0; - bool operator==(const Complex &other) const { - return std::abs(real - other.real) < config::EPS && - std::abs(imag - other.imag) < config::EPS && - std::abs(norm - other.norm) < config::EPS; - } - auto operator<=>(const Complex &other) const = default; -}; +inline constexpr bool isNull(std::complex c) { + return not isNotNull(c); +} } // namespace qx::core + +template <> struct fmt::formatter : fmt::ostream_formatter {}; diff --git a/include/qx/DenseUnitaryMatrix.hpp b/include/qx/DenseUnitaryMatrix.hpp index cd1763af..6f53ff6f 100644 --- a/include/qx/DenseUnitaryMatrix.hpp +++ b/include/qx/DenseUnitaryMatrix.hpp @@ -5,7 +5,7 @@ #include // conj #include // runtime_error -#include "qx/Core.hpp" // isNotNull +#include "qx/Core.hpp" // isNull namespace qx::core { @@ -47,7 +47,7 @@ class DenseUnitaryMatrix { constexpr bool operator==(DenseUnitaryMatrix const &other) const { for (std::size_t i = 0; i < N; ++i) { for (std::size_t j = 0; j < N; ++j) { - if (isNotNull(at(i, j) - other.at(i, j))) { + if (not isNull(at(i, j) - other.at(i, j))) { return false; } } @@ -55,8 +55,7 @@ class DenseUnitaryMatrix { return true; } - constexpr DenseUnitaryMatrix - operator*(DenseUnitaryMatrix const &other) const { + constexpr DenseUnitaryMatrix operator*(DenseUnitaryMatrix const &other) const { Matrix m; for (std::size_t i = 0; i < N; ++i) { for (std::size_t j = 0; j < N; ++j) { diff --git a/include/qx/InstructionExecutor.hpp b/include/qx/InstructionExecutor.hpp new file mode 100644 index 00000000..a5ec969c --- /dev/null +++ b/include/qx/InstructionExecutor.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "qx/Instructions.hpp" +#include "qx/QuantumState.hpp" + +#include // size_t + + +namespace qx{ + +class InstructionExecutor { +public: + explicit InstructionExecutor(core::QuantumState &s); + + void operator()(Measure const &m); + void operator()(MeasurementRegisterOperation const &op); + + template + void operator()(Unitary const &u) { + quantumState.apply(u.matrix, u.operands); + } + +private: + core::QuantumState &quantumState; +}; + +} // namespace qx diff --git a/include/qx/Instructions.hpp b/include/qx/Instructions.hpp new file mode 100644 index 00000000..fe0233e8 --- /dev/null +++ b/include/qx/Instructions.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "qx/Core.hpp" // BasisVector, BitIndex, QubitIndex +#include "qx/DenseUnitaryMatrix.hpp" + +#include +#include // size_t +#include // function +#include // shared_ptr +#include +#include + + +namespace qx { + +struct Measure { + core::BitIndex bitIndex{}; + core::QubitIndex qubitIndex{}; +}; + +struct MeasurementRegisterOperation { + std::function operation; +}; + +template +struct Unitary { + // Matrix is stored inline but could also be a pointer. + core::DenseUnitaryMatrix<1 << NumberOfOperands> matrix{}; + std::array operands{}; +}; + +using Instruction = std::variant< + Measure, + MeasurementRegisterOperation, + Unitary<1>, + Unitary<2>, + Unitary<3> +>; + +using ControlBits = std::shared_ptr>; + +struct ControlledInstruction { + ControlledInstruction(Instruction instruction, ControlBits control_bits) + : instruction(std::move(instruction)) + , control_bits(std::move(control_bits)) + {} + + Instruction instruction; + ControlBits control_bits; +}; + +// We could in the future add loops and if/else... + +} // namespace qx diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 62af3a0b..9ba972a9 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -34,7 +34,7 @@ void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, } for (std::size_t i = 0; i < (1 << NumberOfOperands); ++i) { std::complex addedValue = sparseComplex.value * matrix.at(i, reducedIndex.toSizeT()); - if (isNotNull(addedValue)) { + if (not isNull(addedValue)) { auto newIndex = index; for (std::size_t k = 0; k < NumberOfOperands; ++k) { newIndex.set(operands[NumberOfOperands - k - 1].value, utils::getBit(i, k)); @@ -47,16 +47,22 @@ void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, class QuantumState { + void checkQuantumState(); + public: - explicit QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size); + QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size); + QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size, + std::initializer_list>> values); [[nodiscard]] std::size_t getNumberOfQubits() const; [[nodiscard]] std::size_t getNumberOfBits() const; + [[nodiscard]] bool isNormalized(); void reset(); - void testInitialize(std::initializer_list>> values); template - QuantumState &apply(DenseUnitaryMatrix<1 << NumberOfOperands> const &m, + QuantumState &apply( + DenseUnitaryMatrix<1 << NumberOfOperands> const &m, std::array const &operands) { + assert(NumberOfOperands <= numberOfQubits && "Quantum gate has more operands than the number of qubits in this quantum state"); assert(std::find_if(operands.begin(), operands.end(), @@ -93,9 +99,12 @@ class QuantumState { } private: - std::size_t const numberOfQubits = 1; - std::size_t const numberOfBits = 1; + std::size_t numberOfQubits = 1; + std::size_t numberOfBits = 1; SparseArray data; + // TODO: we are keeping a "double-entry bookkeeping" until we can get rid of measurements + // measurements needs to be replaced with bitMeasurements with the introduction of bit variables, + // but this replacement cannot be executed until all the QX simulator clients start using bitMeasurements BasisVector measurementRegister{}; BitMeasurementRegister bitMeasurementRegister{}; }; diff --git a/include/qx/RegisterManager.hpp b/include/qx/RegisterManager.hpp index 7e0fead9..32719190 100644 --- a/include/qx/RegisterManager.hpp +++ b/include/qx/RegisterManager.hpp @@ -40,11 +40,10 @@ struct RegisterManagerError : public SimulationError { explicit RegisterManagerError(const std::string &message); }; +using VariableNameToRangeMapT = std::unordered_map; +using IndexToVariableNameMapT = std::vector; class Register { - using VariableNameToRangeMapT = std::unordered_map; - using IndexToVariableNameMapT = std::vector; -private: std::size_t register_size_; VariableNameToRangeMapT variable_name_to_range_; IndexToVariableNameMapT index_to_variable_name_; @@ -54,20 +53,22 @@ class Register { [[nodiscard]] std::size_t size() const; [[nodiscard]] virtual Range at(const VariableName &name) const; [[nodiscard]] virtual VariableName at(const std::size_t &index) const; + [[nodiscard]] virtual VariableNameToRangeMapT const& getVariableNameToRangeMap() const; + [[nodiscard]] virtual IndexToVariableNameMapT const& getIndexToVariableNameMap() const; }; class QubitRegister : public Register { public: explicit QubitRegister(const V3OneProgram &program); - virtual ~QubitRegister() override; + ~QubitRegister() override; }; class BitRegister : public Register { public: explicit BitRegister(const V3OneProgram &program); - virtual ~BitRegister() override; + ~BitRegister() override; }; @@ -87,6 +88,8 @@ class RegisterManager { [[nodiscard]] Range get_bit_range(const VariableName &name) const; [[nodiscard]] VariableName get_qubit_variable_name(const std::size_t &index) const; [[nodiscard]] VariableName get_bit_variable_name(const std::size_t &index) const; + [[nodiscard]] QubitRegister const& get_qubit_register() const; + [[nodiscard]] BitRegister const& get_bit_register() const; }; } // namespace qx diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index b3083607..9e76dd48 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -1,7 +1,9 @@ #pragma once #include "qx/CompileTimeConfiguration.hpp" -#include "qx/Core.hpp" // Complex +#include "qx/Core.hpp" // BasisVector, BitMeasurementRegister Complex +#include "qx/QuantumState.hpp" +#include "qx/RegisterManager.hpp" #include #include // partial_ordering @@ -15,53 +17,79 @@ namespace qx { + namespace core { class QuantumState; } -struct Result { + +struct Measurement { std::string state; std::uint64_t count; - std::partial_ordering operator<=>(const Result &other) const = default; + + std::partial_ordering operator<=>(const Measurement &other) const = default; }; -struct State { + +struct SuperposedState { std::string value; core::Complex amplitude; - bool operator==(const State &other) const = default; - std::partial_ordering operator<=>(const State &other) const = default; + + bool operator==(const SuperposedState &other) const = default; + std::partial_ordering operator<=>(const SuperposedState &other) const = default; }; + struct SimulationResult { - using Results = std::vector; - using States = std::vector; + using Measurements = std::vector; + using State = std::vector; + std::uint64_t shotsRequested = 0; std::uint64_t shotsDone = 0; - Results results; - States states; + + State state; + + Measurements measurements; + Measurements bitRegisterMeasurements; + + VariableNameToRangeMapT qubitRegister; + VariableNameToRangeMapT bitRegister; }; std::ostream &operator<<(std::ostream &os, const SimulationResult &result); -using state_t = core::BasisVector; -using count_t = std::uint64_t; class SimulationResultAccumulator { + using state_string_t = std::string; + using count_t = std::uint64_t; + public: explicit SimulationResultAccumulator(core::QuantumState &s); - void append(core::BasisVector measuredState); - SimulationResult get(); + void appendMeasurement(core::BasisVector const& measurement); + void appendBitMeasurement(core::BitMeasurementRegister const& bitMeasurement); + SimulationResult getSimulationResult(const RegisterManager ®isterManager); private: template - void forAllNonZeroStates(F &&f); - std::string getStateString(core::BasisVector s); + void forAllNonZeroStates(F &&f) { + state.forEach([&f, this](auto const &kv) { + f(kv.first, kv.second); + }); + } - core::QuantumState &quantumState; - absl::btree_map measuredStates; - std::uint64_t measuredStatesCount = 0; + core::QuantumState &state; + + // TODO: we are keeping a "double-entry bookkeeping" until we can get rid of measurements + // measurements needs to be replaced with bitMeasurements with the introduction of bit variables, + // but this replacement cannot be executed until all the QX simulator clients start using bitMeasurements + absl::btree_map measurements; + absl::btree_map bitMeasurements; + + std::uint64_t measurementsCount = 0; + std::uint64_t bitMeasurementsCount = 0; }; + } // namespace qx template <> struct fmt::formatter : fmt::ostream_formatter {}; diff --git a/include/qx/SparseArray.hpp b/include/qx/SparseArray.hpp index 431cabd3..aeb34b31 100644 --- a/include/qx/SparseArray.hpp +++ b/include/qx/SparseArray.hpp @@ -47,6 +47,7 @@ class SparseArray { public: SparseArray() = delete; explicit SparseArray(std::size_t s); + SparseArray(std::size_t s, std::initializer_list>> values); [[nodiscard]] ConstIterator begin() const; [[nodiscard]] ConstIterator end() const; @@ -59,6 +60,7 @@ class SparseArray { void clear(); [[nodiscard]] std::size_t size() const; + [[nodiscard]] double norm(); [[nodiscard]] std::vector> toVector() const; template diff --git a/include/qx/Utils.hpp b/include/qx/Utils.hpp index c9a3e724..98f9c68a 100644 --- a/include/qx/Utils.hpp +++ b/include/qx/Utils.hpp @@ -84,6 +84,8 @@ class Bitset { return data[0]; } + // Convert to string + // Notice the least significant bits go to the right [[nodiscard]] std::string toString() const { std::string result; for (std::size_t i = 0; i < NumberOfBits; ++i) { @@ -93,6 +95,13 @@ class Bitset { return result; } + // Convert to string, then return the rightmost n bits + // Notice the least significant bits go to the right + [[nodiscard]] std::string toSubstring(size_t n) const { + auto ret = toString(); + return ret.substr(ret.size() - n, ret.size()); + } + private: template [[nodiscard]] inline bool compare(Bitset const &other) const { diff --git a/python/qxelarator/__init__.py b/python/qxelarator/__init__.py index 80087b12..9ca2b048 100644 --- a/python/qxelarator/__init__.py +++ b/python/qxelarator/__init__.py @@ -19,7 +19,7 @@ def __init__(self): def __repr__(self): return f"""Shots requested: {self.shots_requested} Shots done: {self.shots_done} -Results: {self.results} +Measurements: {self.results} State: {self.state}""" class SimulationError: diff --git a/src/qx/Circuit.cpp b/src/qx/Circuit.cpp index fc4db898..bbbd79da 100644 --- a/src/qx/Circuit.cpp +++ b/src/qx/Circuit.cpp @@ -1,5 +1,7 @@ #include "qx/Circuit.hpp" #include "qx/GateConvertor.hpp" +#include "qx/InstructionExecutor.hpp" +#include "qx/Instructions.hpp" #include "qx/Random.hpp" #include @@ -7,31 +9,10 @@ namespace qx { -namespace { - -struct InstructionExecutor { -public: - explicit InstructionExecutor(core::QuantumState &s) : quantumState(s){}; - - void operator()(Circuit::Measure const &m) { - quantumState.measure(m.bitIndex, m.qubitIndex, &random::randomZeroOneDouble); - } - void operator()(Circuit::MeasurementRegisterOperation const &op) { - op.operation(quantumState.getMeasurementRegister()); - } - template void operator()(Circuit::Unitary const &u) { - quantumState.apply(u.matrix, u.operands); - } - -private: - core::QuantumState &quantumState; -}; - -} // namespace - Circuit::Circuit(V3OneProgram &program, RegisterManager ®ister_manager) : program_{ program } , register_manager_{ register_manager } { + GateConvertor gateConvertor{ *this }; for (const auto &statement: program_->block->statements) { statement->visit(gateConvertor); @@ -70,15 +51,15 @@ void Circuit::execute(core::QuantumState &quantumState, error_models::ErrorModel // AppleClang doesn't support std::visit // std::visit(instructionExecutor, instruction); - if (auto *measure = std::get_if(&instruction)) { + if (auto *measure = std::get_if(&instruction)) { instruction_executor(*measure); - } else if (auto *classicalOp = std::get_if(&instruction)) { + } else if (auto *classicalOp = std::get_if(&instruction)) { instruction_executor(*classicalOp); - } else if (auto *instruction1 = std::get_if>(&instruction)) { + } else if (auto *instruction1 = std::get_if>(&instruction)) { instruction_executor(*instruction1); - } else if (auto *instruction2 = std::get_if>(&instruction)) { + } else if (auto *instruction2 = std::get_if>(&instruction)) { instruction_executor(*instruction2); - } else if (auto *instruction3 = std::get_if>(&instruction)) { + } else if (auto *instruction3 = std::get_if>(&instruction)) { instruction_executor(*instruction3); } else { assert(false && "Unimplemented circuit instruction"); diff --git a/src/qx/GateConvertor.cpp b/src/qx/GateConvertor.cpp index 43cffcc0..31e3ebfa 100644 --- a/src/qx/GateConvertor.cpp +++ b/src/qx/GateConvertor.cpp @@ -1,6 +1,7 @@ #include "qx/Core.hpp" #include "qx/GateConvertor.hpp" #include "qx/Gates.hpp" +#include "qx/Instructions.hpp" // Measure, Unitary #include "qx/OperandsHelper.hpp" #include @@ -40,7 +41,7 @@ void GateConvertor::addGates( static_cast(operands[op][i]->value)}; } - Circuit::Unitary unitary{matrix, ops}; + Unitary unitary{matrix, ops}; auto controlBits = std::make_shared>(); circuit_.add_instruction(unitary, controlBits); @@ -136,7 +137,7 @@ void GateConvertor::addGates(const V3Instruction &instruction) { auto controlBits = std::make_shared>(); for (size_t i{ 0 }; i < bit_indices.size(); ++i) { circuit_.add_instruction( - Circuit::Measure{ + Measure{ core::BitIndex{ static_cast(bit_indices[i]->value) }, core::QubitIndex{ static_cast(qubit_indices[i]->value) } }, diff --git a/src/qx/InstructionExecutor.cpp b/src/qx/InstructionExecutor.cpp new file mode 100644 index 00000000..18e19ca2 --- /dev/null +++ b/src/qx/InstructionExecutor.cpp @@ -0,0 +1,19 @@ +#include "qx/InstructionExecutor.hpp" +#include "qx/Random.hpp" + + +namespace qx { + +InstructionExecutor::InstructionExecutor(core::QuantumState &s) + : quantumState(s) +{} + +void InstructionExecutor::operator()(Measure const &m) { + quantumState.measure(m.bitIndex, m.qubitIndex, &random::randomZeroOneDouble); +} + +void InstructionExecutor::operator()(MeasurementRegisterOperation const &op) { + op.operation(quantumState.getMeasurementRegister()); +} + +} // namespace qx diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index e9208a6b..c69c9b6d 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -5,24 +5,42 @@ namespace qx::core { -QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size) - : numberOfQubits(qubit_register_size) - , numberOfBits(bit_register_size) - , data(static_cast(1) << numberOfQubits) - , bitMeasurementRegister{ numberOfBits } { +void QuantumState::checkQuantumState() { if (numberOfQubits == 0) { - throw QuantumStateError{ "quantum state needs at least one qubit" }; + throw QuantumStateError{ "number of qubits needs to be at least 1" }; } if (numberOfQubits > config::MAX_QUBIT_NUMBER) { - throw QuantumStateError{ fmt::format("quantum state size exceeds maximum allowed: {} > {}", numberOfQubits, + throw QuantumStateError{ fmt::format("number of qubits exceeds maximum allowed: {} > {}", numberOfQubits, config::MAX_QUBIT_NUMBER) }; } - if (numberOfQubits > config::MAX_BIT_NUMBER) { - throw QuantumStateError{ fmt::format("quantum state size exceeds maximum allowed: {} > {}", numberOfQubits, - config::MAX_QUBIT_NUMBER) }; + if (numberOfBits > config::MAX_BIT_NUMBER) { + throw QuantumStateError{ fmt::format("number of bits exceeds maximum allowed: {} > {}", numberOfBits, + config::MAX_BIT_NUMBER) }; + } + if (not isNormalized()) { + throw QuantumStateError{ "quantum state is not normalized" }; } +} + +QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size) + : numberOfQubits{ qubit_register_size } + , numberOfBits{ bit_register_size } + , data{ static_cast(1) << numberOfQubits } + , bitMeasurementRegister{ numberOfBits } { + data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 -}; + checkQuantumState(); +} + +QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size, + std::initializer_list>> values) + : numberOfQubits{ qubit_register_size } + , numberOfBits{ bit_register_size } + , data{ static_cast(1) << numberOfQubits, values } + , bitMeasurementRegister{ numberOfBits } { + + checkQuantumState(); +} [[nodiscard]] std::size_t QuantumState::getNumberOfQubits() const { return numberOfQubits; @@ -32,37 +50,21 @@ QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_regi return numberOfBits; } +[[nodiscard]] bool QuantumState::isNormalized() { + return isNull(data.norm() - 1.); +} + void QuantumState::reset() { data.clear(); data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 measurementRegister.reset(); + bitMeasurementRegister.reset(); } -void QuantumState::testInitialize( - std::initializer_list>> values) { - data.clear(); - double norm{}; - for (auto const &[state_string, amplitude] : values) { - data[BasisVector{ state_string }] = SparseComplex{ amplitude }; - norm += std::norm(amplitude); - } - assert(!isNotNull(norm - 1)); -} - -// Explicit instantiation for use in Circuit::execute, otherwise linking error. - -template QuantumState &QuantumState::apply<1>( - DenseUnitaryMatrix<1 << 1> const &m, std::array const &operands); - -template QuantumState &QuantumState::apply<2>( - DenseUnitaryMatrix<1 << 2> const &m, std::array const &operands); - -template QuantumState &QuantumState::apply<3>( - DenseUnitaryMatrix<1 << 3> const &m, std::array const &operands); - [[nodiscard]] const BasisVector& QuantumState::getMeasurementRegister() const { return measurementRegister; } + [[nodiscard]] const BitMeasurementRegister& QuantumState::getBitMeasurementRegister() const { return bitMeasurementRegister; } @@ -92,7 +94,6 @@ void QuantumState::collapseQubit(QubitIndex qubitIndex, bool measuredState, doub ? probabilityOfMeasuringOne : (1 - probabilityOfMeasuringOne); data *= std::sqrt(1 / probability); - } } // namespace qx::core diff --git a/src/qx/RegisterManager.cpp b/src/qx/RegisterManager.cpp index 2a29e47c..1eb5b4e1 100644 --- a/src/qx/RegisterManager.cpp +++ b/src/qx/RegisterManager.cpp @@ -54,6 +54,14 @@ Register::~Register() = default; return index_to_variable_name_.at(index); } +[[nodiscard]] VariableNameToRangeMapT const& Register::getVariableNameToRangeMap() const { + return variable_name_to_range_; +} + +[[nodiscard]] IndexToVariableNameMapT const& Register::getIndexToVariableNameMap() const { + return index_to_variable_name_; +} + QubitRegister::QubitRegister(const V3OneProgram &program) try : Register(program, is_qubit_variable, config::MAX_QUBIT_NUMBER) { } catch (const RegisterManagerError &e) { @@ -101,4 +109,12 @@ RegisterManager::RegisterManager(const V3OneProgram &program) return bit_register_.at(index); } +[[nodiscard]] QubitRegister const& RegisterManager::get_qubit_register() const { + return qubit_register_; +} + +[[nodiscard]] BitRegister const& RegisterManager::get_bit_register() const { + return bit_register_; +} + } // namespace qx diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index 93050987..3fbab2f6 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -10,16 +10,16 @@ namespace qx { std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationResult) { fmt::print("\nFinal quantum state\n"); - for (auto const &state : simulationResult.states) { + for (auto const &superposedState : simulationResult.state) { fmt::print("{1} {2:.{0}f} + {3:.{0}f}i (norm = {4:.{0}f})\n", config::OUTPUT_DECIMALS, - state.value, - state.amplitude.real, - state.amplitude.imag, - state.amplitude.norm); + superposedState.value, + superposedState.amplitude.real, + superposedState.amplitude.imag, + superposedState.amplitude.norm); } fmt::print("\nMeasurement register averaging\n"); - for (const auto &result : simulationResult.results) { + for (auto const &result : simulationResult.measurements) { fmt::print("{1} {2}/{3} (count/shots % = {4:.{0}f})\n", config::OUTPUT_DECIMALS, result.state, @@ -31,42 +31,50 @@ std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationRes } SimulationResultAccumulator::SimulationResultAccumulator(core::QuantumState &s) - : quantumState(s) + : state(s) {} -void SimulationResultAccumulator::append(core::BasisVector measuredState) { - assert(measuredStates.size() <= (static_cast(1) << quantumState.getNumberOfQubits())); - measuredStates[measuredState]++; - measuredStatesCount++; +void SimulationResultAccumulator::appendMeasurement(core::BasisVector const& measurement) { + assert(measurements.size() < (static_cast(1) << state.getNumberOfQubits())); + auto measuredStateString{ measurement.toSubstring(state.getNumberOfQubits()) }; + measurements[measuredStateString]++; + measurementsCount++; } -SimulationResult SimulationResultAccumulator::get() { +void SimulationResultAccumulator::appendBitMeasurement(core::BitMeasurementRegister const& bitMeasurement) { + assert(bitMeasurements.size() < (static_cast(1) << state.getNumberOfQubits())); + auto bitMeasuredStateString{ fmt::format("{}", bitMeasurement) }; + bitMeasurements[bitMeasuredStateString]++; + bitMeasurementsCount++; +} + +SimulationResult SimulationResultAccumulator::getSimulationResult(const RegisterManager ®isterManager) { + assert(measurementsCount > 0); + SimulationResult simulationResult; - simulationResult.shotsRequested = measuredStatesCount; - simulationResult.shotsDone = measuredStatesCount; - assert(measuredStatesCount > 0); - for (const auto &[state, count] : measuredStates) { - simulationResult.results.push_back(Result{ getStateString(state), count}); - } - forAllNonZeroStates([&simulationResult](auto stateString, auto sparseComplex) { + simulationResult.shotsRequested = measurementsCount; + simulationResult.shotsDone = measurementsCount; + + forAllNonZeroStates([this, &simulationResult](core::BasisVector const& superposedState, core::SparseComplex const& sparseComplex) { + auto stateString = superposedState.toSubstring(state.getNumberOfQubits()); auto c = sparseComplex.value; - simulationResult.states.push_back( - State{ stateString, core::Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) } }); + auto amplitude = core::Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) }; + simulationResult.state.push_back(SuperposedState{ stateString, amplitude }); }); - return simulationResult; -} -template -void SimulationResultAccumulator::forAllNonZeroStates(F &&f) { - quantumState.forEach([&f, this](auto const &kv) { - f(getStateString(kv.first), kv.second); - }); -} + for (auto const& [stateString, count] : measurements) { + simulationResult.measurements.push_back(Measurement{ stateString, count }); + } -std::string SimulationResultAccumulator::getStateString(qx::core::BasisVector s) { - auto str = s.toString(); - return str.substr(str.size() - quantumState.getNumberOfQubits(), str.size()); + for (auto const& [stateString, count] : bitMeasurements) { + simulationResult.bitRegisterMeasurements.push_back(Measurement{ stateString, count }); + } + + simulationResult.qubitRegister = registerManager.get_qubit_register().getVariableNameToRangeMap(); + simulationResult.bitRegister = registerManager.get_bit_register().getVariableNameToRangeMap(); + + return simulationResult; } } // namespace qx \ No newline at end of file diff --git a/src/qx/Simulator.cpp b/src/qx/Simulator.cpp index 6c5d2880..a2f78339 100644 --- a/src/qx/Simulator.cpp +++ b/src/qx/Simulator.cpp @@ -76,10 +76,11 @@ execute( while (iterations--) { quantumState.reset(); circuit.execute(quantumState, std::monostate{}); - simulationResultAccumulator.append(quantumState.getMeasurementRegister()); + simulationResultAccumulator.appendMeasurement(quantumState.getMeasurementRegister()); + simulationResultAccumulator.appendBitMeasurement(quantumState.getBitMeasurementRegister()); } - return simulationResultAccumulator.get(); + return simulationResultAccumulator.getSimulationResult(register_manager); } catch (const SimulationError &err) { return err; } diff --git a/src/qx/SparseArray.cpp b/src/qx/SparseArray.cpp index 1ad5d60a..bcab4a61 100644 --- a/src/qx/SparseArray.cpp +++ b/src/qx/SparseArray.cpp @@ -1,6 +1,7 @@ #include "qx/SparseArray.hpp" #include +#include namespace qx::core { @@ -40,9 +41,20 @@ bool compareSparseElements(const SparseElement &lhs, const SparseElement &rhs) { } SparseArray::SparseArray(std::size_t s) - : size_(s) + : size_{ s } {} +SparseArray::SparseArray(std::size_t s, std::initializer_list>> values) + : size_{ s } { + for (auto const &[basis_vector_string, complex_value] : values) { + if ((static_cast(1) << basis_vector_string.size()) > s) { + throw SparseArrayError{ + fmt::format("found value '{}' for a sparse array of size {}", basis_vector_string, s) }; + } + data_[BasisVector{ basis_vector_string }] = SparseComplex{ complex_value }; + } +} + SparseArray& SparseArray::operator=(MapBasisVectorToSparseComplex map) { data_ = std::move(map); return *this; @@ -88,6 +100,13 @@ void SparseArray::clear() { return size_; } +[[nodiscard]] double SparseArray::norm() { + return accumulate(0., [](double total, auto const &kv){ + auto const& [basisVector, sparseComplex] = kv; + return total + std::norm(sparseComplex.value); + }); +} + [[nodiscard]] std::vector> SparseArray::toVector() const { auto result = std::vector>(size_, 0.); for (auto const &[basisVector, sparseComplex] : data_) { @@ -99,7 +118,7 @@ void SparseArray::clear() { void SparseArray::cleanUpZeros() { absl::erase_if(data_, [](auto const &kv) { auto const &[_, sparseComplex] = kv; - return !isNotNull(sparseComplex.value); + return isNull(sparseComplex.value); }); zeroCounter_ = 0; } diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index bc1566d5..eaa07861 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -32,8 +32,8 @@ CNOT q[0], q[1] EXPECT_EQ(actual.shotsRequested, 1); EXPECT_EQ(actual.shotsDone, 1); - EXPECT_EQ(actual.states, - (SimulationResult::States{ + EXPECT_EQ(actual.state, + (SimulationResult::State{ { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, { "11", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } })); @@ -49,8 +49,8 @@ CNOT q[0:2], q[3:5] )"; auto actual = runFromString(cqasm, 2); - EXPECT_EQ(actual.states, - (SimulationResult::States{ { "111111", core::Complex{ .real = 1, .imag = 0, .norm = 1 } } })); + EXPECT_EQ(actual.state, + (SimulationResult::State{ { "111111", core::Complex{ .real = 1, .imag = 0, .norm = 1 } } })); } TEST_F(IntegrationTest, too_many_qubits) { @@ -94,8 +94,8 @@ I q[1] EXPECT_EQ(actual.shotsRequested, 1); EXPECT_EQ(actual.shotsDone, 1); - EXPECT_EQ(actual.states, - (SimulationResult::States{ + EXPECT_EQ(actual.state, + (SimulationResult::State{ { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, { "11", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } })); @@ -118,15 +118,15 @@ b = measure q auto actual = runFromString(cqasm, iterations); auto error = static_cast(static_cast(iterations)/2 * 0.05); - EXPECT_EQ(actual.results.size(), 2); - EXPECT_EQ(actual.results[0].state, "001"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); - EXPECT_EQ(actual.results[1].state, "111"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].count)), error); + EXPECT_EQ(actual.measurements.size(), 2); + EXPECT_EQ(actual.measurements[0].state, "001"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); + EXPECT_EQ(actual.measurements[1].state, "111"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); // State could be 001 or 111 - EXPECT_TRUE(actual.states[0].value.ends_with('1')); - EXPECT_EQ(actual.states[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); + EXPECT_TRUE(actual.state[0].value.ends_with('1')); + EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); } TEST_F(IntegrationTest, multiple_measure_instructions) { @@ -148,15 +148,15 @@ b[2] = measure q[2] auto actual = runFromString(cqasm, iterations); auto error = static_cast(static_cast(iterations)/2 * 0.05); - EXPECT_EQ(actual.results.size(), 2); - EXPECT_EQ(actual.results[0].state, "001"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); - EXPECT_EQ(actual.results[1].state, "111"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].count)), error); + EXPECT_EQ(actual.measurements.size(), 2); + EXPECT_EQ(actual.measurements[0].state, "001"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); + EXPECT_EQ(actual.measurements[1].state, "111"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); // State could be 001 or 111 - EXPECT_TRUE(actual.states[0].value.ends_with('1')); - EXPECT_EQ(actual.states[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); + EXPECT_TRUE(actual.state[0].value.ends_with('1')); + EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); } TEST_F(IntegrationTest, mid_circuit_measure_instruction) { @@ -178,11 +178,11 @@ b = measure q // Expected output state: |00>+|11> or |01>+|10> auto error = static_cast(static_cast(iterations)/2 * 0.05); - EXPECT_EQ(actual.results.size(), 2); - EXPECT_TRUE(actual.results[0].state == "00" || actual.results[0].state == "01"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); - EXPECT_TRUE(actual.results[1].state == "11" || actual.results[1].state == "10"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].count)), error); + EXPECT_EQ(actual.measurements.size(), 2); + EXPECT_TRUE(actual.measurements[0].state == "00" || actual.measurements[0].state == "01"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); + EXPECT_TRUE(actual.measurements[1].state == "11" || actual.measurements[1].state == "10"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); } TEST_F(IntegrationTest, multiple_qubit_bit_definitions_and_mid_circuit_measure_instructions) { @@ -206,11 +206,30 @@ b1 = measure q1 // Expected output state: |00>+|11> or |01>+|10> auto error = static_cast(static_cast(iterations)/2 * 0.05); - EXPECT_EQ(actual.results.size(), 2); - EXPECT_TRUE(actual.results[0].state == "00" || actual.results[0].state == "01"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[0].count)), error); - EXPECT_TRUE(actual.results[1].state == "11" || actual.results[1].state == "10"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.results[1].count)), error); + EXPECT_EQ(actual.measurements.size(), 2); + EXPECT_TRUE(actual.measurements[0].state == "00" || actual.measurements[0].state == "01"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); + EXPECT_TRUE(actual.measurements[1].state == "11" || actual.measurements[1].state == "10"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); +} + +TEST_F(IntegrationTest, bit_measurement_register) { + std::size_t iterations = 10'000; + auto cqasm = R"( +version 3.0 + +qubit[2] qq +X qq[0] +bit[2] bb +bb[0] = measure qq[0] + +qubit q +H q +CNOT q, qq[0] +bit b +b = measure qq[0] +)"; + auto actual = runFromString(cqasm, iterations); } } // namespace qx diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index 06019a41..67e44076 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -58,16 +58,14 @@ TEST_F(QuantumStateTest, apply_hadamard) { } TEST_F(QuantumStateTest, apply_cnot) { - QuantumState victim{ 2, 2 }; - victim.testInitialize({{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); + QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); victim.apply<2>(gates::CNOT, std::array{QubitIndex{1}, QubitIndex{0}}); checkEq(victim, {0, 0, std::sqrt(1 - std::pow(0.123, 2)), 0.123}); } TEST_F(QuantumStateTest, measure_on_non_superposed_state) { - QuantumState victim{ 2, 2 }; - victim.testInitialize({{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); + QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; victim.measure(BitIndex{1}, QubitIndex{1}, []() { return 0.9485; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); victim.measure(BitIndex{1}, QubitIndex{1}, []() { return 0.045621; }); @@ -76,16 +74,14 @@ TEST_F(QuantumStateTest, measure_on_non_superposed_state) { } TEST_F(QuantumStateTest, measure_on_superposed_state__case_0) { - QuantumState victim{ 2, 2 }; - victim.testInitialize({{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); + QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; victim.measure(BitIndex{0}, QubitIndex{0}, []() { return 0.994; }); checkEq(victim, {0, 0, 1, 0}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } TEST_F(QuantumStateTest, measure_on_superposed_state__case_1) { - QuantumState victim{ 2, 2 }; - victim.testInitialize({{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}}); + QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; victim.measure(BitIndex{0}, QubitIndex{0}, []() { return 0.254; }); checkEq(victim, {0, 0, 0, 1}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("01")); From 1c6c4a6af33b3a2c461ae4a4322820b4bb47586d Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:13:49 +0200 Subject: [PATCH 21/56] Fix GitHub Actions clang jobs. --- include/qx/SimulationResult.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index 9e76dd48..b22131d9 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -72,7 +72,7 @@ class SimulationResultAccumulator { private: template void forAllNonZeroStates(F &&f) { - state.forEach([&f, this](auto const &kv) { + state.forEach([&f](auto const &kv) { f(kv.first, kv.second); }); } From 668e0818a090f25b3a1b383d40228963e678e839 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:21:51 +0200 Subject: [PATCH 22/56] Fix GitHub Actions Python jobs. --- python/Qxelarator.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/Qxelarator.i b/python/Qxelarator.i index ea2327db..201aa74c 100644 --- a/python/Qxelarator.i +++ b/python/Qxelarator.i @@ -30,13 +30,13 @@ PyObject_SetAttrString(simulationResult, "shots_requested", PyLong_FromUnsignedLongLong(cppSimulationResult->shotsRequested)); auto results = PyDict_New(); - for(auto const& [state, count]: cppSimulationResult->results) { + for(auto const& [state, count]: cppSimulationResult->measurements) { PyDict_SetItemString(results, state.c_str(), PyLong_FromUnsignedLongLong(count)); } PyObject_SetAttrString(simulationResult, "results", results); auto state = PyDict_New(); - for(auto const& [value, amplitude]: cppSimulationResult->states) { + for(auto const& [value, amplitude]: cppSimulationResult->state) { PyDict_SetItemString(state, value.c_str(), PyComplex_FromCComplex({ .real = amplitude.real, .imag = amplitude.imag })); } PyObject_SetAttrString(simulationResult, "state", state); From 73c24ea077a9220e12732c0a7663c20f4cb700d4 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Tue, 18 Jun 2024 19:34:15 +0200 Subject: [PATCH 23/56] Implement bit_measurement_register integration test. --- include/qx/RegisterManager.hpp | 15 ++++++---- include/qx/SimulationResult.hpp | 31 +++++++++++++++----- src/qx/RegisterManager.cpp | 23 +++++++++------ src/qx/SimulationResult.cpp | 50 ++++++++++++++++++++++----------- test/IntegrationTest.cpp | 8 ++++++ 5 files changed, 89 insertions(+), 38 deletions(-) diff --git a/include/qx/RegisterManager.hpp b/include/qx/RegisterManager.hpp index 32719190..5be95ea8 100644 --- a/include/qx/RegisterManager.hpp +++ b/include/qx/RegisterManager.hpp @@ -5,6 +5,7 @@ #include "v3x/cqasm-semantic-gen.hpp" #include // size_t +#include #include #include #include @@ -29,10 +30,11 @@ namespace qx { using VariableName = std::string; +using Index = std::size_t; struct Range { - std::size_t first; - std::size_t size; + Index first; + Index size; }; @@ -52,9 +54,8 @@ class Register { virtual ~Register() = 0; [[nodiscard]] std::size_t size() const; [[nodiscard]] virtual Range at(const VariableName &name) const; + [[nodiscard]] virtual Index at(const VariableName &name, const std::optional &subIndex) const; [[nodiscard]] virtual VariableName at(const std::size_t &index) const; - [[nodiscard]] virtual VariableNameToRangeMapT const& getVariableNameToRangeMap() const; - [[nodiscard]] virtual IndexToVariableNameMapT const& getIndexToVariableNameMap() const; }; @@ -86,8 +87,10 @@ class RegisterManager { [[nodiscard]] std::size_t get_bit_register_size() const; [[nodiscard]] Range get_qubit_range(const VariableName &name) const; [[nodiscard]] Range get_bit_range(const VariableName &name) const; - [[nodiscard]] VariableName get_qubit_variable_name(const std::size_t &index) const; - [[nodiscard]] VariableName get_bit_variable_name(const std::size_t &index) const; + [[nodiscard]] Index get_qubit_index(const VariableName &name, const std::optional &subIndex) const; + [[nodiscard]] Index get_bit_index(const VariableName &name, const std::optional &subIndex) const; + [[nodiscard]] VariableName get_qubit_variable_name(const Index &index) const; + [[nodiscard]] VariableName get_bit_variable_name(const Index &index) const; [[nodiscard]] QubitRegister const& get_qubit_register() const; [[nodiscard]] BitRegister const& get_bit_register() const; }; diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index b22131d9..6a726d17 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -44,16 +44,33 @@ struct SimulationResult { using Measurements = std::vector; using State = std::vector; - std::uint64_t shotsRequested = 0; - std::uint64_t shotsDone = 0; +public: + SimulationResult() = delete; + SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots, RegisterManager const ®isterManager); + + // Given a state string from the State vector, a qubit variable name, and an optional sub index, + // return the value of that qubit in the state string + // The sub index is used to access a given qubit when the qubit variable is of array type + // Notice that the final index in the state string is determined by the qubit register + std::uint8_t getQubitState(std::string const& stateString, std::string const& qubitVariableName, + std::optional subIndex); + // Given a state string from the State vector, a bit variable name, and an optional sub index, + // return the value of that bit in the state string + // The sub index is used to access a given bit when the bit variable is of array type + // Notice that the final index in the state string is determined by the bit register + std::uint8_t getBitMeasurement(std::string const& stateString, std::string const& bitVariableName, + std::optional subIndex); - State state; +public: + std::uint64_t shotsRequested; + std::uint64_t shotsDone; + QubitRegister qubitRegister; + BitRegister bitRegister; + + State state; Measurements measurements; Measurements bitRegisterMeasurements; - - VariableNameToRangeMapT qubitRegister; - VariableNameToRangeMapT bitRegister; }; std::ostream &operator<<(std::ostream &os, const SimulationResult &result); @@ -67,7 +84,7 @@ class SimulationResultAccumulator { explicit SimulationResultAccumulator(core::QuantumState &s); void appendMeasurement(core::BasisVector const& measurement); void appendBitMeasurement(core::BitMeasurementRegister const& bitMeasurement); - SimulationResult getSimulationResult(const RegisterManager ®isterManager); + SimulationResult getSimulationResult(RegisterManager const& registerManager); private: template diff --git a/src/qx/RegisterManager.cpp b/src/qx/RegisterManager.cpp index 1eb5b4e1..c506e343 100644 --- a/src/qx/RegisterManager.cpp +++ b/src/qx/RegisterManager.cpp @@ -50,16 +50,13 @@ Register::~Register() = default; return variable_name_to_range_.at(name); } -[[nodiscard]] VariableName Register::at(const std::size_t &index) const { - return index_to_variable_name_.at(index); +[[nodiscard]] Index Register::at(const VariableName &name, const std::optional &subIndex) const { + auto range = variable_name_to_range_.at(name); + return range.first + subIndex.value_or(0); } -[[nodiscard]] VariableNameToRangeMapT const& Register::getVariableNameToRangeMap() const { - return variable_name_to_range_; -} - -[[nodiscard]] IndexToVariableNameMapT const& Register::getIndexToVariableNameMap() const { - return index_to_variable_name_; +[[nodiscard]] VariableName Register::at(const std::size_t &index) const { + return index_to_variable_name_.at(index); } QubitRegister::QubitRegister(const V3OneProgram &program) try @@ -101,6 +98,16 @@ RegisterManager::RegisterManager(const V3OneProgram &program) return bit_register_.at(name); } +[[nodiscard]] Index RegisterManager::get_qubit_index(const VariableName &name, + const std::optional &subIndex) const { + return qubit_register_.at(name, subIndex); +} + +[[nodiscard]] Index RegisterManager::get_bit_index(const VariableName &name, + const std::optional &subIndex) const { + return bit_register_.at(name, subIndex); +} + [[nodiscard]] VariableName RegisterManager::get_qubit_variable_name(const std::size_t &index) const { return qubit_register_.at(index); } diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index 3fbab2f6..32011165 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -1,13 +1,34 @@ -#include "qx/SimulationResult.hpp" - #include "qx/Core.hpp" // BasisVector #include "qx/QuantumState.hpp" +#include "qx/SimulationResult.hpp" + +#include // uint8_t #include #include namespace qx { +SimulationResult::SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots, + RegisterManager const& registerManager) + : shotsRequested{ requestedShots } + , shotsDone{ doneShots } + , qubitRegister{ registerManager.get_qubit_register() } + , bitRegister{ registerManager.get_bit_register() } +{} + +std::uint8_t SimulationResult::getQubitState(std::string const& stateString, std::string const& qubitVariableName, + std::optional subIndex) { + auto index = qubitRegister.at(qubitVariableName, subIndex); + return static_cast(stateString[stateString.size() - index - 1] - '0'); +} + +std::uint8_t SimulationResult::getBitMeasurement(std::string const& stateString, std::string const& bitVariableName, + std::optional subIndex) { + auto index = bitRegister.at(bitVariableName, subIndex); + return static_cast(stateString[stateString.size() - index - 1] - '0'); +} + std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationResult) { fmt::print("\nFinal quantum state\n"); for (auto const &superposedState : simulationResult.state) { @@ -48,32 +69,27 @@ void SimulationResultAccumulator::appendBitMeasurement(core::BitMeasurementRegis bitMeasurementsCount++; } -SimulationResult SimulationResultAccumulator::getSimulationResult(const RegisterManager ®isterManager) { +SimulationResult SimulationResultAccumulator::getSimulationResult(RegisterManager const& registerManager) { assert(measurementsCount > 0); - SimulationResult simulationResult; + SimulationResult simulationResult{ measurementsCount, measurementsCount, registerManager }; - simulationResult.shotsRequested = measurementsCount; - simulationResult.shotsDone = measurementsCount; - - forAllNonZeroStates([this, &simulationResult](core::BasisVector const& superposedState, core::SparseComplex const& sparseComplex) { - auto stateString = superposedState.toSubstring(state.getNumberOfQubits()); - auto c = sparseComplex.value; - auto amplitude = core::Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) }; - simulationResult.state.push_back(SuperposedState{ stateString, amplitude }); - }); + forAllNonZeroStates( + [this, &simulationResult](core::BasisVector const& superposedState, core::SparseComplex const& sparseComplex) { + auto stateString = superposedState.toSubstring(state.getNumberOfQubits()); + auto c = sparseComplex.value; + auto amplitude = core::Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) }; + simulationResult.state.push_back(SuperposedState{ stateString, amplitude }); + } + ); for (auto const& [stateString, count] : measurements) { simulationResult.measurements.push_back(Measurement{ stateString, count }); } - for (auto const& [stateString, count] : bitMeasurements) { simulationResult.bitRegisterMeasurements.push_back(Measurement{ stateString, count }); } - simulationResult.qubitRegister = registerManager.get_qubit_register().getVariableNameToRangeMap(); - simulationResult.bitRegister = registerManager.get_bit_register().getVariableNameToRangeMap(); - return simulationResult; } diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index eaa07861..29b6f5fd 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -3,6 +3,7 @@ #include // abs #include #include +#include // nullopt namespace qx { @@ -230,6 +231,13 @@ bit b b = measure qq[0] )"; auto actual = runFromString(cqasm, iterations); + + auto error = static_cast(static_cast(iterations)/2 * 0.05); + EXPECT_EQ(actual.bitRegisterMeasurements.size(), 2); + for (auto const& bitMeasurement : actual.bitRegisterMeasurements) { + EXPECT_EQ(actual.getBitMeasurement(bitMeasurement.state, "bb", 0), 1); + EXPECT_LT(std::abs(static_cast(iterations/2 - bitMeasurement.count)), error); + } } } // namespace qx From 7caffc337e7cd1850b0d49e2cc46458b7532f2b8 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Wed, 19 Jun 2024 00:22:00 +0200 Subject: [PATCH 24/56] Fix GitHub Actions Python jobs. --- include/qx/Qxelarator.hpp | 7 +++++-- include/qx/SimulationResult.hpp | 1 - include/qx/Simulator.hpp | 6 +++--- python/Qxelarator.i | 2 +- src/qx/Simulator.cpp | 7 ++++--- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/include/qx/Qxelarator.hpp b/include/qx/Qxelarator.hpp index bfa8498e..10cc1672 100644 --- a/include/qx/Qxelarator.hpp +++ b/include/qx/Qxelarator.hpp @@ -2,9 +2,12 @@ #include "qx/Simulator.hpp" +#include // monostate + + namespace qxelarator { -std::variant +std::variant execute_string( std::string const &s, std::size_t iterations = 1, @@ -14,7 +17,7 @@ execute_string( return qx::executeString(s, iterations, seed, version); } -std::variant +std::variant execute_file( std::string const &filePath, std::size_t iterations = 1, diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index 6a726d17..2e8ad4ee 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -45,7 +45,6 @@ struct SimulationResult { using State = std::vector; public: - SimulationResult() = delete; SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots, RegisterManager const ®isterManager); // Given a state string from the State vector, a qubit variable name, and an optional sub index, diff --git a/include/qx/Simulator.hpp b/include/qx/Simulator.hpp index a4b13c80..cf38987e 100644 --- a/include/qx/Simulator.hpp +++ b/include/qx/Simulator.hpp @@ -5,19 +5,19 @@ #include #include -#include +#include // monostate namespace qx { -std::variant +std::variant executeString( std::string const &s, std::size_t iterations = 1, std::optional seed = std::nullopt, std::string cqasm_version = "3.0"); -std::variant +std::variant executeFile( std::string const &filePath, std::size_t iterations = 1, diff --git a/python/Qxelarator.i b/python/Qxelarator.i index 201aa74c..93ac4a11 100644 --- a/python/Qxelarator.i +++ b/python/Qxelarator.i @@ -15,7 +15,7 @@ } // Map the output of execute_string/execute_file to a simple Python class for user-friendliness. -%typemap(out) std::variant { +%typemap(out) std::variant { if (std::holds_alternative($1)) { auto pmod = PyImport_ImportModule("qxelarator"); auto pclass = PyObject_GetAttrString(pmod, "SimulationResult"); diff --git a/src/qx/Simulator.cpp b/src/qx/Simulator.cpp index a2f78339..12637e08 100644 --- a/src/qx/Simulator.cpp +++ b/src/qx/Simulator.cpp @@ -14,6 +14,7 @@ #include #include #include +#include // monostate #include @@ -44,7 +45,7 @@ std::variant getV3ProgramOrError(V3AnalysisResult return program; } -std::variant +std::variant execute( V3AnalysisResult const& analysisResult, std::size_t iterations, @@ -88,7 +89,7 @@ execute( } // namespace -std::variant +std::variant executeString( std::string const &s, std::size_t iterations, @@ -103,7 +104,7 @@ executeString( } } -std::variant +std::variant executeFile( std::string const &filePath, std::size_t iterations, From a49b97ffd8dc67c3874d9b29abc0e0c3a3f7ff34 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Wed, 19 Jun 2024 00:41:36 +0200 Subject: [PATCH 25/56] Change collapseQubit signature. --- include/qx/QuantumState.hpp | 9 ++++++--- src/qx/QuantumState.cpp | 13 +++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 9ba972a9..bde6e86d 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -86,14 +86,17 @@ class QuantumState { [[nodiscard]] const BitMeasurementRegister &getBitMeasurementRegister() const; [[nodiscard]] double getProbabilityOfMeasuringOne(QubitIndex qubitIndex); [[nodiscard]] double getProbabilityOfMeasuringZero(QubitIndex qubitIndex); - void collapseQubit(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne); + void collapseQubit(QubitIndex qubitIndex, bool measuredState, double measuredStateProbability); + // measuredState will be true if we measured a 1, or false if we measured a 0 + // measuredStateProbability will be the probability of measuring 1 if we measured a 1, + // or the probability of measuring 0 if we measured a 0 template void measure(BitIndex bitIndex, QubitIndex qubitIndex, F &&randomGenerator) { auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); - // measuredState will be true if we measured a 1, or false if we measured a 0 auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); - collapseQubit(qubitIndex, measuredState, probabilityOfMeasuringOne); + auto measuredStateProbability = measuredState ? probabilityOfMeasuringOne : (1 - probabilityOfMeasuringOne); + collapseQubit(qubitIndex, measuredState, measuredStateProbability); measurementRegister.set(qubitIndex.value, measuredState); bitMeasurementRegister.set(bitIndex.value, measuredState); } diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index c69c9b6d..94def51f 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -83,17 +83,18 @@ void QuantumState::reset() { return 1.0 - getProbabilityOfMeasuringOne(qubitIndex); } -void QuantumState::collapseQubit(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne) { +// Update data after a measurement +// +// measuredState will be true if we measured a 1, or false if we measured a 0 +// measuredStateProbability will be the probability of measuring 1 if we measured a 1, +// or the probability of measuring 0 if we measured a 0 +void QuantumState::collapseQubit(QubitIndex qubitIndex, bool measuredState, double measuredStateProbability) { data.eraseIf([qubitIndex, measuredState](auto const &kv) { auto const &[basisVector, _] = kv; auto currentState = basisVector.test(qubitIndex.value); return currentState != measuredState; }); - - auto probability = measuredState - ? probabilityOfMeasuringOne - : (1 - probabilityOfMeasuringOne); - data *= std::sqrt(1 / probability); + data *= std::sqrt(1 / measuredStateProbability); } } // namespace qx::core From dab9a4eb3aeb1452208f4a285e8d92efc0d60919 Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 19 Jun 2024 13:23:15 +0200 Subject: [PATCH 26/56] Change Boost dependency to header only. Add conan/profiles/base. Add boost/*:header_only=True to conan/profiles/base. --- conan/profiles/base | 4 ++++ conan/profiles/debug | 2 +- conan/profiles/release | 2 +- conan/profiles/tests-debug | 2 +- conan/profiles/tests-release | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 conan/profiles/base diff --git a/conan/profiles/base b/conan/profiles/base new file mode 100644 index 00000000..a5bffa3a --- /dev/null +++ b/conan/profiles/base @@ -0,0 +1,4 @@ +include(default) + +[options] +boost/*:header_only=True diff --git a/conan/profiles/debug b/conan/profiles/debug index 86259bf0..acdcf409 100644 --- a/conan/profiles/debug +++ b/conan/profiles/debug @@ -1,4 +1,4 @@ -include(default) +include(base) [settings] compiler.cppstd=23 diff --git a/conan/profiles/release b/conan/profiles/release index 37e08db9..1dc80926 100644 --- a/conan/profiles/release +++ b/conan/profiles/release @@ -1,4 +1,4 @@ -include(default) +include(base) [settings] compiler.cppstd=23 diff --git a/conan/profiles/tests-debug b/conan/profiles/tests-debug index b5e51b52..37f88a7a 100644 --- a/conan/profiles/tests-debug +++ b/conan/profiles/tests-debug @@ -1,4 +1,4 @@ -include(default) +include(base) [settings] compiler.cppstd=23 diff --git a/conan/profiles/tests-release b/conan/profiles/tests-release index 3aafb1ed..10f3d336 100644 --- a/conan/profiles/tests-release +++ b/conan/profiles/tests-release @@ -1,4 +1,4 @@ -include(default) +include(base) [settings] compiler.cppstd=23 From 08c76b87f64140d6dac223e64d8314d5255ac55b Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 19 Jun 2024 14:10:22 +0200 Subject: [PATCH 27/56] Add boost/*:header_only=True to setup.py. --- setup.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index d539f132..4d7614de 100755 --- a/setup.py +++ b/setup.py @@ -91,19 +91,18 @@ def run(self): cmd = (local['conan']['create']['.'] ['--version'][get_version()] - ['-s:h']['compiler.cppstd=23'] - ['-s:b']['compiler.cppstd=23'] - ['-s:h']['build_type=' + build_type] - ['-s:b']['build_type=' + build_type] + ['-s:a']['compiler.cppstd=23'] + ['-s:a']['build_type=' + build_type] - ['-o']['qx/*:build_python=True'] - ['-o']['qx/*:cpu_compatibility_mode=' + cpu_compatibility_mode] + ['-o:a']['boost/*:header_only=True'] + ['-o:a']['qx/*:build_python=True'] + ['-o:a']['qx/*:cpu_compatibility_mode=' + cpu_compatibility_mode] # The Python library needs the compatibility headers - ['-o']['qx/*:python_dir=' + re.escape(os.path.dirname(target))] - ['-o']['qx/*:python_ext=' + re.escape(os.path.basename(target))] + ['-o:a']['qx/*:python_dir=' + re.escape(os.path.dirname(target))] + ['-o:a']['qx/*:python_ext=' + re.escape(os.path.basename(target))] # (Ab)use static libs for the intermediate libraries # to avoid dealing with R(UN)PATH nonsense on Linux/OSX as much as possible - ['-o']["qx/*:shared=False"] + ['-o:a']["qx/*:shared=False"] ['-b']['missing'] ['-tf'][''] From 80f5e69ad39bf662f10c797ec8b631636dd43b29 Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 19 Jun 2024 14:10:38 +0200 Subject: [PATCH 28/56] Change SimulationResult structs to use state_string_t, count_t, and amplitude_t. Change SimulationResult.bitRegisterMeasuremnents to bitMeasurements. Change SimulationResult.operator<< to print bit measurements, qubit register, and bit register. --- include/qx/RegisterManager.hpp | 13 ++++++++++++ include/qx/SimulationResult.hpp | 24 +++++++++++----------- src/qx/RegisterManager.cpp | 26 ++++++++++++++++++++++++ src/qx/SimulationResult.cpp | 35 ++++++++++++++++++++++----------- test/IntegrationTest.cpp | 4 ++-- 5 files changed, 77 insertions(+), 25 deletions(-) diff --git a/include/qx/RegisterManager.hpp b/include/qx/RegisterManager.hpp index 5be95ea8..64173beb 100644 --- a/include/qx/RegisterManager.hpp +++ b/include/qx/RegisterManager.hpp @@ -5,6 +5,7 @@ #include "v3x/cqasm-semantic-gen.hpp" #include // size_t +#include #include #include #include @@ -56,6 +57,7 @@ class Register { [[nodiscard]] virtual Range at(const VariableName &name) const; [[nodiscard]] virtual Index at(const VariableName &name, const std::optional &subIndex) const; [[nodiscard]] virtual VariableName at(const std::size_t &index) const; + [[nodiscard]] virtual std::string toString() const; }; @@ -95,4 +97,15 @@ class RegisterManager { [[nodiscard]] BitRegister const& get_bit_register() const; }; + +std::ostream& operator<<(std::ostream& os, Range const& range); +std::ostream& operator<<(std::ostream& os, QubitRegister const& qubitRegister); +std::ostream& operator<<(std::ostream& os, BitRegister const& bitRegister); + + } // namespace qx + + +template <> struct fmt::formatter : fmt::ostream_formatter {}; +template <> struct fmt::formatter : fmt::ostream_formatter {}; +template <> struct fmt::formatter : fmt::ostream_formatter {}; diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index 2e8ad4ee..1e53364c 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -23,17 +23,22 @@ class QuantumState; } +using state_string_t = std::string; +using count_t = std::uint64_t; +using amplitude_t = core::Complex; + + struct Measurement { - std::string state; - std::uint64_t count; + state_string_t state; + count_t count; std::partial_ordering operator<=>(const Measurement &other) const = default; }; struct SuperposedState { - std::string value; - core::Complex amplitude; + state_string_t value; + amplitude_t amplitude; bool operator==(const SuperposedState &other) const = default; std::partial_ordering operator<=>(const SuperposedState &other) const = default; @@ -45,19 +50,19 @@ struct SimulationResult { using State = std::vector; public: - SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots, RegisterManager const ®isterManager); + SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots, RegisterManager const& registerManager); // Given a state string from the State vector, a qubit variable name, and an optional sub index, // return the value of that qubit in the state string // The sub index is used to access a given qubit when the qubit variable is of array type // Notice that the final index in the state string is determined by the qubit register - std::uint8_t getQubitState(std::string const& stateString, std::string const& qubitVariableName, + std::uint8_t getQubitState(state_string_t const& stateString, std::string const& qubitVariableName, std::optional subIndex); // Given a state string from the State vector, a bit variable name, and an optional sub index, // return the value of that bit in the state string // The sub index is used to access a given bit when the bit variable is of array type // Notice that the final index in the state string is determined by the bit register - std::uint8_t getBitMeasurement(std::string const& stateString, std::string const& bitVariableName, + std::uint8_t getBitMeasurement(state_string_t const& stateString, std::string const& bitVariableName, std::optional subIndex); public: @@ -69,16 +74,13 @@ struct SimulationResult { State state; Measurements measurements; - Measurements bitRegisterMeasurements; + Measurements bitMeasurements; }; std::ostream &operator<<(std::ostream &os, const SimulationResult &result); class SimulationResultAccumulator { - using state_string_t = std::string; - using count_t = std::uint64_t; - public: explicit SimulationResultAccumulator(core::QuantumState &s); void appendMeasurement(core::BasisVector const& measurement); diff --git a/src/qx/RegisterManager.cpp b/src/qx/RegisterManager.cpp index c506e343..561b38f7 100644 --- a/src/qx/RegisterManager.cpp +++ b/src/qx/RegisterManager.cpp @@ -4,6 +4,7 @@ #include // fill #include +#include #include #include #include @@ -59,6 +60,17 @@ Register::~Register() = default; return index_to_variable_name_.at(index); } +[[nodiscard]] std::string Register::toString() const { + auto entries = ranges::accumulate(variable_name_to_range_, std::string{}, + [first=true](auto total, auto const& kv) mutable { + auto const& [variableName, range] = kv; + total += fmt::format("{}{}: {}", first ? "" : ", ", variableName, range); + first = false; + return total; + }); + return fmt::format("{{ {0} }}", entries); +} + QubitRegister::QubitRegister(const V3OneProgram &program) try : Register(program, is_qubit_variable, config::MAX_QUBIT_NUMBER) { } catch (const RegisterManagerError &e) { @@ -124,4 +136,18 @@ RegisterManager::RegisterManager(const V3OneProgram &program) return bit_register_; } +std::ostream& operator<<(std::ostream& os, Range const& range) { + return os << fmt::format("[{}{}]", + range.first, + range.size == 1 ? "" : fmt::format("..{}", range.first + range.size - 1)); +} + +std::ostream& operator<<(std::ostream& os, QubitRegister const& qubitRegister) { + return os << qubitRegister.toString(); +} + +std::ostream& operator<<(std::ostream& os, BitRegister const& bitRegister) { + return os << bitRegister.toString(); +} + } // namespace qx diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index 32011165..1a8b518f 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -17,37 +17,48 @@ SimulationResult::SimulationResult(std::uint64_t requestedShots, std::uint64_t d , bitRegister{ registerManager.get_bit_register() } {} -std::uint8_t SimulationResult::getQubitState(std::string const& stateString, std::string const& qubitVariableName, +std::uint8_t SimulationResult::getQubitState(state_string_t const& stateString, std::string const& qubitVariableName, std::optional subIndex) { auto index = qubitRegister.at(qubitVariableName, subIndex); return static_cast(stateString[stateString.size() - index - 1] - '0'); } -std::uint8_t SimulationResult::getBitMeasurement(std::string const& stateString, std::string const& bitVariableName, +std::uint8_t SimulationResult::getBitMeasurement(state_string_t const& stateString, std::string const& bitVariableName, std::optional subIndex) { auto index = bitRegister.at(bitVariableName, subIndex); return static_cast(stateString[stateString.size() - index - 1] - '0'); } std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationResult) { - fmt::print("\nFinal quantum state\n"); + fmt::print(os, "State:\n"); for (auto const &superposedState : simulationResult.state) { - fmt::print("{1} {2:.{0}f} + {3:.{0}f}i (norm = {4:.{0}f})\n", + fmt::print(os, "\t{1} {2:.{0}f} + {3:.{0}f}i (norm = {4:.{0}f})\n", config::OUTPUT_DECIMALS, superposedState.value, superposedState.amplitude.real, superposedState.amplitude.imag, superposedState.amplitude.norm); } - fmt::print("\nMeasurement register averaging\n"); - for (auto const &result : simulationResult.measurements) { - fmt::print("{1} {2}/{3} (count/shots % = {4:.{0}f})\n", + fmt::print(os, "Measurements:\n"); + for (auto const &measurement : simulationResult.measurements) { + fmt::print(os, "\t{1} {2}/{3} (count/shots % = {4:.{0}f})\n", config::OUTPUT_DECIMALS, - result.state, - result.count, + measurement.state, + measurement.count, simulationResult.shotsDone, - static_cast(result.count) / static_cast(simulationResult.shotsDone)); + static_cast(measurement.count) / static_cast(simulationResult.shotsDone)); } + fmt::print(os, "Bit measurements:\n"); + for (auto const &bitMeasurement : simulationResult.bitMeasurements) { + fmt::print(os, "\t{1} {2}/{3} (count/shots % = {4:.{0}f})\n", + config::OUTPUT_DECIMALS, + bitMeasurement.state, + bitMeasurement.count, + simulationResult.shotsDone, + static_cast(bitMeasurement.count) / static_cast(simulationResult.shotsDone)); + } + fmt::print(os, "Qubit register:\n\t{}\n", simulationResult.qubitRegister); + fmt::print(os, "Bit register:\n\t{}", simulationResult.bitRegister); return os; } @@ -78,7 +89,7 @@ SimulationResult SimulationResultAccumulator::getSimulationResult(RegisterManage [this, &simulationResult](core::BasisVector const& superposedState, core::SparseComplex const& sparseComplex) { auto stateString = superposedState.toSubstring(state.getNumberOfQubits()); auto c = sparseComplex.value; - auto amplitude = core::Complex{ .real = c.real(), .imag = c.imag(), .norm = std::norm(c) }; + auto amplitude = amplitude_t{ c.real(), c.imag(), std::norm(c) }; simulationResult.state.push_back(SuperposedState{ stateString, amplitude }); } ); @@ -87,7 +98,7 @@ SimulationResult SimulationResultAccumulator::getSimulationResult(RegisterManage simulationResult.measurements.push_back(Measurement{ stateString, count }); } for (auto const& [stateString, count] : bitMeasurements) { - simulationResult.bitRegisterMeasurements.push_back(Measurement{ stateString, count }); + simulationResult.bitMeasurements.push_back(Measurement{ stateString, count }); } return simulationResult; diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 29b6f5fd..4cb12786 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -233,8 +233,8 @@ b = measure qq[0] auto actual = runFromString(cqasm, iterations); auto error = static_cast(static_cast(iterations)/2 * 0.05); - EXPECT_EQ(actual.bitRegisterMeasurements.size(), 2); - for (auto const& bitMeasurement : actual.bitRegisterMeasurements) { + EXPECT_EQ(actual.bitMeasurements.size(), 2); + for (auto const& bitMeasurement : actual.bitMeasurements) { EXPECT_EQ(actual.getBitMeasurement(bitMeasurement.state, "bb", 0), 1); EXPECT_LT(std::abs(static_cast(iterations/2 - bitMeasurement.count)), error); } From 876f5212a8985c5f0a4d240b44237633642ee29f Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 19 Jun 2024 14:27:33 +0200 Subject: [PATCH 29/56] Change Qxelarator/state and value to stateString. --- python/Qxelarator.i | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/Qxelarator.i b/python/Qxelarator.i index 93ac4a11..9d66054a 100644 --- a/python/Qxelarator.i +++ b/python/Qxelarator.i @@ -30,14 +30,14 @@ PyObject_SetAttrString(simulationResult, "shots_requested", PyLong_FromUnsignedLongLong(cppSimulationResult->shotsRequested)); auto results = PyDict_New(); - for(auto const& [state, count]: cppSimulationResult->measurements) { - PyDict_SetItemString(results, state.c_str(), PyLong_FromUnsignedLongLong(count)); + for(auto const& [stateString, count]: cppSimulationResult->measurements) { + PyDict_SetItemString(results, stateString.c_str(), PyLong_FromUnsignedLongLong(count)); } PyObject_SetAttrString(simulationResult, "results", results); auto state = PyDict_New(); - for(auto const& [value, amplitude]: cppSimulationResult->state) { - PyDict_SetItemString(state, value.c_str(), PyComplex_FromCComplex({ .real = amplitude.real, .imag = amplitude.imag })); + for(auto const& [stateString, amplitude]: cppSimulationResult->state) { + PyDict_SetItemString(state, stateString.c_str(), PyComplex_FromCComplex({ .real = amplitude.real, .imag = amplitude.imag })); } PyObject_SetAttrString(simulationResult, "state", state); From e7dc38d81e7dac2f70933119f9244a9cd5be93fe Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 20 Jun 2024 11:07:48 +0200 Subject: [PATCH 30/56] Remove unused header. --- src/qx/RegisterManager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/qx/RegisterManager.cpp b/src/qx/RegisterManager.cpp index 561b38f7..f0b6824b 100644 --- a/src/qx/RegisterManager.cpp +++ b/src/qx/RegisterManager.cpp @@ -4,7 +4,6 @@ #include // fill #include -#include #include #include #include @@ -91,8 +90,8 @@ BitRegister::~BitRegister() = default; RegisterManager::RegisterManager(const V3OneProgram &program) : qubit_register_{ program } - , bit_register_{ program } { -} + , bit_register_{ program } +{} [[nodiscard]] std::size_t RegisterManager::get_qubit_register_size() const { return qubit_register_.size(); From a5af5996c18a8aad0ba5170c18608cd46d486150 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:55:39 +0200 Subject: [PATCH 31/56] Start from 154-code-manage-bit-register-variables. Remove all "manage bit register variables" code. Leave refactoring code only. --- CMakeLists.txt | 3 - conan/profiles/base | 4 - conan/profiles/debug | 2 +- conan/profiles/release | 2 +- conan/profiles/tests-debug | 2 +- conan/profiles/tests-release | 2 +- conanfile.py | 1 - include/qx/CompileTimeConfiguration.hpp | 4 - include/qx/Core.hpp | 10 -- include/qx/Instructions.hpp | 1 - include/qx/QuantumState.hpp | 14 +-- include/qx/RegisterManager.hpp | 103 +++++------------- include/qx/SimulationResult.hpp | 32 +----- include/qx/V3xLibqasmInterface.hpp | 1 - setup.py | 1 - src/qx/GateConvertor.cpp | 4 +- src/qx/InstructionExecutor.cpp | 2 +- src/qx/OperandsHelper.cpp | 39 +++---- src/qx/QuantumState.cpp | 25 +---- src/qx/RegisterManager.cpp | 137 ++++-------------------- src/qx/SimulationResult.cpp | 44 +------- src/qx/Simulator.cpp | 8 +- src/qx/V3xLibqasmInterface.cpp | 4 - test/ErrorModelsTest.cpp | 2 +- test/IntegrationTest.cpp | 26 ----- test/QuantumStateTest.cpp | 20 ++-- 26 files changed, 91 insertions(+), 402 deletions(-) delete mode 100644 conan/profiles/base diff --git a/CMakeLists.txt b/CMakeLists.txt index 92bcda3e..b72a2636 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,7 +111,6 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) #=============================================================================# find_package(absl) -find_package(Boost REQUIRED) find_package(libqasm REQUIRED CONFIG) @@ -142,7 +141,6 @@ target_compile_features(qx PRIVATE target_include_directories(qx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/" - "${Boost_INCLUDE_DIRS}" ) if(CMAKE_COMPILER_IS_GNUCXX) @@ -246,7 +244,6 @@ endif() message(STATUS "[${PROJECT_NAME}] Target include directories:\n" " CMAKE_CURRENT_SOURCE_DIR/include/: ${CMAKE_CURRENT_SOURCE_DIR}/include/\n" - " Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}\n" ) diff --git a/conan/profiles/base b/conan/profiles/base deleted file mode 100644 index a5bffa3a..00000000 --- a/conan/profiles/base +++ /dev/null @@ -1,4 +0,0 @@ -include(default) - -[options] -boost/*:header_only=True diff --git a/conan/profiles/debug b/conan/profiles/debug index acdcf409..86259bf0 100644 --- a/conan/profiles/debug +++ b/conan/profiles/debug @@ -1,4 +1,4 @@ -include(base) +include(default) [settings] compiler.cppstd=23 diff --git a/conan/profiles/release b/conan/profiles/release index 1dc80926..37e08db9 100644 --- a/conan/profiles/release +++ b/conan/profiles/release @@ -1,4 +1,4 @@ -include(base) +include(default) [settings] compiler.cppstd=23 diff --git a/conan/profiles/tests-debug b/conan/profiles/tests-debug index 37f88a7a..b5e51b52 100644 --- a/conan/profiles/tests-debug +++ b/conan/profiles/tests-debug @@ -1,4 +1,4 @@ -include(base) +include(default) [settings] compiler.cppstd=23 diff --git a/conan/profiles/tests-release b/conan/profiles/tests-release index 10f3d336..3aafb1ed 100644 --- a/conan/profiles/tests-release +++ b/conan/profiles/tests-release @@ -1,4 +1,4 @@ -include(base) +include(default) [settings] compiler.cppstd=23 diff --git a/conanfile.py b/conanfile.py index 0638d250..db226524 100644 --- a/conanfile.py +++ b/conanfile.py @@ -48,7 +48,6 @@ def build_requirements(self): def requirements(self): self.requires("abseil/20230125.3", transitive_headers=True) - self.requires("boost/1.85.0") self.requires("fmt/10.2.1", transitive_headers=True) self.requires("libqasm/0.6.6", transitive_headers=True) self.requires("range-v3/0.12.0", transitive_headers=True) diff --git a/include/qx/CompileTimeConfiguration.hpp b/include/qx/CompileTimeConfiguration.hpp index a93e47b9..669a76b2 100644 --- a/include/qx/CompileTimeConfiguration.hpp +++ b/include/qx/CompileTimeConfiguration.hpp @@ -19,8 +19,4 @@ static constexpr std::uint64_t ZERO_CYCLE_SIZE = 100; // Maybe memory-saving as a multiple of 64. static constexpr std::size_t MAX_QUBIT_NUMBER = 64; -// Maximum number of bits that can be used. -// Just for sanity, as we maintain vectors of the size of the number of used bits. -static constexpr std::size_t MAX_BIT_NUMBER = 1*1024*1024; // 1 MB - } // namespace qx::config diff --git a/include/qx/Core.hpp b/include/qx/Core.hpp index 78b270ff..3d7844dd 100644 --- a/include/qx/Core.hpp +++ b/include/qx/Core.hpp @@ -1,10 +1,8 @@ #pragma once -#include #include // size_t, uint32_t #include // abs #include -#include #include "qx/CompileTimeConfiguration.hpp" // EPS, MAX_QUBIT_NUMBER #include "qx/Utils.hpp" @@ -28,14 +26,8 @@ struct QubitIndex { std::size_t value; }; -struct BitIndex { - std::size_t value; -}; - using BasisVector = utils::Bitset; -using BitMeasurementRegister = boost::dynamic_bitset; - inline constexpr bool isNotNull(std::complex c) { #if defined(_MSC_VER) return @@ -51,5 +43,3 @@ inline constexpr bool isNull(std::complex c) { } } // namespace qx::core - -template <> struct fmt::formatter : fmt::ostream_formatter {}; diff --git a/include/qx/Instructions.hpp b/include/qx/Instructions.hpp index fe0233e8..bb845e08 100644 --- a/include/qx/Instructions.hpp +++ b/include/qx/Instructions.hpp @@ -14,7 +14,6 @@ namespace qx { struct Measure { - core::BitIndex bitIndex{}; core::QubitIndex qubitIndex{}; }; diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index bde6e86d..34aae24c 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -50,11 +50,10 @@ class QuantumState { void checkQuantumState(); public: - QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size); - QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size, + QuantumState(std::size_t qubit_register_size); + QuantumState(std::size_t qubit_register_size, std::initializer_list>> values); [[nodiscard]] std::size_t getNumberOfQubits() const; - [[nodiscard]] std::size_t getNumberOfBits() const; [[nodiscard]] bool isNormalized(); void reset(); @@ -83,7 +82,6 @@ class QuantumState { } [[nodiscard]] const BasisVector &getMeasurementRegister() const; - [[nodiscard]] const BitMeasurementRegister &getBitMeasurementRegister() const; [[nodiscard]] double getProbabilityOfMeasuringOne(QubitIndex qubitIndex); [[nodiscard]] double getProbabilityOfMeasuringZero(QubitIndex qubitIndex); void collapseQubit(QubitIndex qubitIndex, bool measuredState, double measuredStateProbability); @@ -92,24 +90,18 @@ class QuantumState { // measuredStateProbability will be the probability of measuring 1 if we measured a 1, // or the probability of measuring 0 if we measured a 0 template - void measure(BitIndex bitIndex, QubitIndex qubitIndex, F &&randomGenerator) { + void measure(QubitIndex qubitIndex, F &&randomGenerator) { auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); auto measuredStateProbability = measuredState ? probabilityOfMeasuringOne : (1 - probabilityOfMeasuringOne); collapseQubit(qubitIndex, measuredState, measuredStateProbability); measurementRegister.set(qubitIndex.value, measuredState); - bitMeasurementRegister.set(bitIndex.value, measuredState); } private: std::size_t numberOfQubits = 1; - std::size_t numberOfBits = 1; SparseArray data; - // TODO: we are keeping a "double-entry bookkeeping" until we can get rid of measurements - // measurements needs to be replaced with bitMeasurements with the introduction of bit variables, - // but this replacement cannot be executed until all the QX simulator clients start using bitMeasurements BasisVector measurementRegister{}; - BitMeasurementRegister bitMeasurementRegister{}; }; } // namespace qx::core diff --git a/include/qx/RegisterManager.hpp b/include/qx/RegisterManager.hpp index 64173beb..dd013b6c 100644 --- a/include/qx/RegisterManager.hpp +++ b/include/qx/RegisterManager.hpp @@ -1,12 +1,10 @@ #pragma once -#include "qx/V3xLibqasmInterface.hpp" #include "qx/SimulationError.hpp" +#include "qx/V3xLibqasmInterface.hpp" #include "v3x/cqasm-semantic-gen.hpp" #include // size_t -#include -#include #include #include #include @@ -14,70 +12,37 @@ namespace qx { -/* - * RegisterManager keeps track of a (virtual) qubit register and a (virtual) bit register. - * I.e., an array of consecutive qubits/bits, and the mappings between the (logical) qubit/bit variable names, - * as used in an input cQASM program, and the (virtual) qubit/bit register. - * - * For example, given an input program that defines 'qubit[3] q': - * - variable 'q' is mapped to qubits 0 to 2 in the qubit register, and - * - positions 0 to 2 in the qubit register are mapped to variable 'q'. - * - * The mapping of qubit/bit variable names to positions in the qubit/bit register is an implementation detail, - * i.e., it is not guaranteed that qubit/bit register indices are assigned to qubit/bit variable names in the order - * these variables are defined in the input program. - */ - - using VariableName = std::string; -using Index = std::size_t; - -struct Range { - Index first; - Index size; +struct QubitRange { + std::size_t first; + std::size_t size; }; - struct RegisterManagerError : public SimulationError { explicit RegisterManagerError(const std::string &message); }; -using VariableNameToRangeMapT = std::unordered_map; -using IndexToVariableNameMapT = std::vector; - -class Register { - std::size_t register_size_; - VariableNameToRangeMapT variable_name_to_range_; - IndexToVariableNameMapT index_to_variable_name_; -public: - Register(const V3OneProgram &program, auto &&is_of_type, std::size_t max_register_size); - virtual ~Register() = 0; - [[nodiscard]] std::size_t size() const; - [[nodiscard]] virtual Range at(const VariableName &name) const; - [[nodiscard]] virtual Index at(const VariableName &name, const std::optional &subIndex) const; - [[nodiscard]] virtual VariableName at(const std::size_t &index) const; - [[nodiscard]] virtual std::string toString() const; -}; - - -class QubitRegister : public Register { -public: - explicit QubitRegister(const V3OneProgram &program); - ~QubitRegister() override; -}; - - -class BitRegister : public Register { -public: - explicit BitRegister(const V3OneProgram &program); - ~BitRegister() override; -}; - - +/* + * RegisterManager keeps track of a (virtual) qubit register, i.e., an array of consecutive qubits, + * and the mappings between the (logical) qubit variable names, as used in an input cQASM program, + * and the (virtual) qubit register. + * + * For example, given an input program that defines 'qubit[3] q': + * - variable 'q' is mapped to qubits 0 to 2 in the qubit register, and + * - positions 0 to 2 in the qubit register are mapped to variable 'q'. + * + * The mapping of qubit variable names to positions in the qubit register is an implementation detail, + * i.e., it is not guaranteed that qubit register indices are assigned to qubit variable names in the order + * these variables are defined in the input program. + */ class RegisterManager { - QubitRegister qubit_register_; - BitRegister bit_register_; + using VariableNameToQubitRangeMapT = std::unordered_map; + using QubitIndexToVariableNameMapT = std::vector; +private: + std::size_t qubit_register_size_; + VariableNameToQubitRangeMapT variable_name_to_qubit_range_; + QubitIndexToVariableNameMapT qubit_index_to_variable_name_; public: RegisterManager(const RegisterManager&) = delete; RegisterManager(RegisterManager&&) noexcept = delete; @@ -86,26 +51,8 @@ class RegisterManager { public: explicit RegisterManager(const V3OneProgram &program); [[nodiscard]] std::size_t get_qubit_register_size() const; - [[nodiscard]] std::size_t get_bit_register_size() const; - [[nodiscard]] Range get_qubit_range(const VariableName &name) const; - [[nodiscard]] Range get_bit_range(const VariableName &name) const; - [[nodiscard]] Index get_qubit_index(const VariableName &name, const std::optional &subIndex) const; - [[nodiscard]] Index get_bit_index(const VariableName &name, const std::optional &subIndex) const; - [[nodiscard]] VariableName get_qubit_variable_name(const Index &index) const; - [[nodiscard]] VariableName get_bit_variable_name(const Index &index) const; - [[nodiscard]] QubitRegister const& get_qubit_register() const; - [[nodiscard]] BitRegister const& get_bit_register() const; + [[nodiscard]] QubitRange get_qubit_range(const VariableName &name) const; + [[nodiscard]] VariableName get_variable_name(std::size_t index) const; }; - -std::ostream& operator<<(std::ostream& os, Range const& range); -std::ostream& operator<<(std::ostream& os, QubitRegister const& qubitRegister); -std::ostream& operator<<(std::ostream& os, BitRegister const& bitRegister); - - } // namespace qx - - -template <> struct fmt::formatter : fmt::ostream_formatter {}; -template <> struct fmt::formatter : fmt::ostream_formatter {}; -template <> struct fmt::formatter : fmt::ostream_formatter {}; diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index 1e53364c..7b343dd8 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -1,9 +1,8 @@ #pragma once #include "qx/CompileTimeConfiguration.hpp" -#include "qx/Core.hpp" // BasisVector, BitMeasurementRegister Complex +#include "qx/Core.hpp" // BasisVector, Complex #include "qx/QuantumState.hpp" -#include "qx/RegisterManager.hpp" #include #include // partial_ordering @@ -50,31 +49,14 @@ struct SimulationResult { using State = std::vector; public: - SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots, RegisterManager const& registerManager); - - // Given a state string from the State vector, a qubit variable name, and an optional sub index, - // return the value of that qubit in the state string - // The sub index is used to access a given qubit when the qubit variable is of array type - // Notice that the final index in the state string is determined by the qubit register - std::uint8_t getQubitState(state_string_t const& stateString, std::string const& qubitVariableName, - std::optional subIndex); - // Given a state string from the State vector, a bit variable name, and an optional sub index, - // return the value of that bit in the state string - // The sub index is used to access a given bit when the bit variable is of array type - // Notice that the final index in the state string is determined by the bit register - std::uint8_t getBitMeasurement(state_string_t const& stateString, std::string const& bitVariableName, - std::optional subIndex); + SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots); public: std::uint64_t shotsRequested; std::uint64_t shotsDone; - QubitRegister qubitRegister; - BitRegister bitRegister; - State state; Measurements measurements; - Measurements bitMeasurements; }; std::ostream &operator<<(std::ostream &os, const SimulationResult &result); @@ -84,8 +66,7 @@ class SimulationResultAccumulator { public: explicit SimulationResultAccumulator(core::QuantumState &s); void appendMeasurement(core::BasisVector const& measurement); - void appendBitMeasurement(core::BitMeasurementRegister const& bitMeasurement); - SimulationResult getSimulationResult(RegisterManager const& registerManager); + SimulationResult getSimulationResult(); private: template @@ -96,15 +77,8 @@ class SimulationResultAccumulator { } core::QuantumState &state; - - // TODO: we are keeping a "double-entry bookkeeping" until we can get rid of measurements - // measurements needs to be replaced with bitMeasurements with the introduction of bit variables, - // but this replacement cannot be executed until all the QX simulator clients start using bitMeasurements absl::btree_map measurements; - absl::btree_map bitMeasurements; - std::uint64_t measurementsCount = 0; - std::uint64_t bitMeasurementsCount = 0; }; diff --git a/include/qx/V3xLibqasmInterface.hpp b/include/qx/V3xLibqasmInterface.hpp index bb77abf8..476e360a 100644 --- a/include/qx/V3xLibqasmInterface.hpp +++ b/include/qx/V3xLibqasmInterface.hpp @@ -29,6 +29,5 @@ using V3Value = v3_values::Value; using V3Variable = v3_semantic::Variable; bool is_qubit_variable(const V3Variable &variable); -bool is_bit_variable(const V3Variable &variable); } // namespace qx diff --git a/setup.py b/setup.py index 4d7614de..2a096ada 100755 --- a/setup.py +++ b/setup.py @@ -94,7 +94,6 @@ def run(self): ['-s:a']['compiler.cppstd=23'] ['-s:a']['build_type=' + build_type] - ['-o:a']['boost/*:header_only=True'] ['-o:a']['qx/*:build_python=True'] ['-o:a']['qx/*:cpu_compatibility_mode=' + cpu_compatibility_mode] # The Python library needs the compatibility headers diff --git a/src/qx/GateConvertor.cpp b/src/qx/GateConvertor.cpp index 31e3ebfa..f41a9735 100644 --- a/src/qx/GateConvertor.cpp +++ b/src/qx/GateConvertor.cpp @@ -132,13 +132,11 @@ void GateConvertor::addGates(const V3Instruction &instruction) { // A measure statement has the following syntax: b = measure q // The left-hand side operand, b, is the operand 0 // The right-hand side operand, q, is the operand 1 - const auto &bit_indices = operands_helper.get_register_operand(0); const auto &qubit_indices = operands_helper.get_register_operand(1); auto controlBits = std::make_shared>(); - for (size_t i{ 0 }; i < bit_indices.size(); ++i) { + for (size_t i{ 0 }; i < qubit_indices.size(); ++i) { circuit_.add_instruction( Measure{ - core::BitIndex{ static_cast(bit_indices[i]->value) }, core::QubitIndex{ static_cast(qubit_indices[i]->value) } }, controlBits); diff --git a/src/qx/InstructionExecutor.cpp b/src/qx/InstructionExecutor.cpp index 18e19ca2..7addf2bd 100644 --- a/src/qx/InstructionExecutor.cpp +++ b/src/qx/InstructionExecutor.cpp @@ -9,7 +9,7 @@ InstructionExecutor::InstructionExecutor(core::QuantumState &s) {} void InstructionExecutor::operator()(Measure const &m) { - quantumState.measure(m.bitIndex, m.qubitIndex, &random::randomZeroOneDouble); + quantumState.measure(m.qubitIndex, &random::randomZeroOneDouble); } void InstructionExecutor::operator()(MeasurementRegisterOperation const &op) { diff --git a/src/qx/OperandsHelper.cpp b/src/qx/OperandsHelper.cpp index 0b27f220..279598e2 100644 --- a/src/qx/OperandsHelper.cpp +++ b/src/qx/OperandsHelper.cpp @@ -15,38 +15,25 @@ OperandsHelper::OperandsHelper(const V3Instruction &instruction, const RegisterM [[nodiscard]] V3Many OperandsHelper::get_register_operand(int id) const { if (auto variable_ref = instruction_.operands[id]->as_variable_ref()) { - auto ret = V3Many{}; - if (is_qubit_variable(*variable_ref->variable)) { - auto qubit_range = register_manager_.get_qubit_range(variable_ref->variable->name); - ret.get_vec().resize(qubit_range.size); - std::generate_n(ret.get_vec().begin(), qubit_range.size, [qubit_range, i=0]() mutable { - return v3_tree::make(static_cast(qubit_range.first + i++)); - }); - } else if (is_bit_variable(*variable_ref->variable)) { - auto bit_range = register_manager_.get_bit_range(variable_ref->variable->name); - ret.get_vec().resize(bit_range.size); - std::generate_n(ret.get_vec().begin(), bit_range.size, [bit_range, i=0]() mutable { - return v3_tree::make(static_cast(bit_range.first + i++)); - }); - } else { + if (!is_qubit_variable(*variable_ref->variable)) { return {}; } + auto qubit_range = register_manager_.get_qubit_range(variable_ref->variable->name); + auto ret = V3Many{}; + ret.get_vec().resize(qubit_range.size); + std::generate_n(ret.get_vec().begin(), qubit_range.size, [qubit_range, i=0]() mutable { + return v3_tree::make(static_cast(qubit_range.first + i++)); + }); return ret; } else if (auto index_ref = instruction_.operands[id]->as_index_ref()) { - auto ret = index_ref->indices; - if (is_qubit_variable(*index_ref->variable)) { - auto qubit_range = register_manager_.get_qubit_range(index_ref->variable->name); - std::for_each(ret.get_vec().begin(), ret.get_vec().end(), [qubit_range](const auto &index) { - index->value += qubit_range.first; - }); - } else if (is_bit_variable(*index_ref->variable)) { - auto bit_range = register_manager_.get_bit_range(index_ref->variable->name); - std::for_each(ret.get_vec().begin(), ret.get_vec().end(), [bit_range](const auto &index) { - index->value += bit_range.first; - }); - } else { + if (!is_qubit_variable(*index_ref->variable)) { return {}; } + auto qubit_range = register_manager_.get_qubit_range(index_ref->variable->name); + auto ret = index_ref->indices; + std::for_each(ret.get_vec().begin(), ret.get_vec().end(), [qubit_range](const auto &index) { + index->value += qubit_range.first; + }); return ret; } assert(false && "operand is neither a variable reference nor an index reference"); diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index 94def51f..a395256e 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -13,31 +13,23 @@ void QuantumState::checkQuantumState() { throw QuantumStateError{ fmt::format("number of qubits exceeds maximum allowed: {} > {}", numberOfQubits, config::MAX_QUBIT_NUMBER) }; } - if (numberOfBits > config::MAX_BIT_NUMBER) { - throw QuantumStateError{ fmt::format("number of bits exceeds maximum allowed: {} > {}", numberOfBits, - config::MAX_BIT_NUMBER) }; - } if (not isNormalized()) { throw QuantumStateError{ "quantum state is not normalized" }; } } -QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size) +QuantumState::QuantumState(std::size_t qubit_register_size) : numberOfQubits{ qubit_register_size } - , numberOfBits{ bit_register_size } - , data{ static_cast(1) << numberOfQubits } - , bitMeasurementRegister{ numberOfBits } { + , data{ static_cast(1) << numberOfQubits } { data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 checkQuantumState(); } -QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size, +QuantumState::QuantumState(std::size_t qubit_register_size, std::initializer_list>> values) : numberOfQubits{ qubit_register_size } - , numberOfBits{ bit_register_size } - , data{ static_cast(1) << numberOfQubits, values } - , bitMeasurementRegister{ numberOfBits } { + , data{ static_cast(1) << numberOfQubits, values } { checkQuantumState(); } @@ -46,10 +38,6 @@ QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_regi return numberOfQubits; } -[[nodiscard]] std::size_t QuantumState::getNumberOfBits() const { - return numberOfBits; -} - [[nodiscard]] bool QuantumState::isNormalized() { return isNull(data.norm() - 1.); } @@ -58,17 +46,12 @@ void QuantumState::reset() { data.clear(); data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 measurementRegister.reset(); - bitMeasurementRegister.reset(); } [[nodiscard]] const BasisVector& QuantumState::getMeasurementRegister() const { return measurementRegister; } -[[nodiscard]] const BitMeasurementRegister& QuantumState::getBitMeasurementRegister() const { - return bitMeasurementRegister; -} - [[nodiscard]] double QuantumState::getProbabilityOfMeasuringOne(QubitIndex qubitIndex) { return data.accumulate(double{}, [qubitIndex](auto total, auto const &kv) { auto const &[basisVector, sparseComplex] = kv; diff --git a/src/qx/RegisterManager.cpp b/src/qx/RegisterManager.cpp index f0b6824b..53e08f8a 100644 --- a/src/qx/RegisterManager.cpp +++ b/src/qx/RegisterManager.cpp @@ -15,138 +15,41 @@ RegisterManagerError::RegisterManagerError(const std::string &message) : SimulationError{ message } {} -Register::Register(const V3OneProgram &program, auto &&is_of_type, std::size_t max_register_size) { - auto &&variables = program->variables.get_vec() - | ranges::views::filter([&](const V3OneVariable &variable) { return is_of_type(*variable); }); - auto &&variable_sizes = variables +RegisterManager::RegisterManager(const V3OneProgram &program) { + auto &&qubit_variables = program->variables.get_vec() + | ranges::views::filter([&](const V3OneVariable &variable) { return is_qubit_variable(*variable); }); + auto &&qubit_variable_sizes = qubit_variables | ranges::views::transform([](const V3OneVariable &variable) { return v3_types::size_of(variable->typ); }); - register_size_ = ranges::accumulate(variable_sizes, size_t{}); + qubit_register_size_ = ranges::accumulate(qubit_variable_sizes, size_t{}); - if (register_size_ > max_register_size) { - throw RegisterManagerError{ fmt::format("{}", register_size_) }; + if (qubit_register_size_ > config::MAX_QUBIT_NUMBER) { + throw RegisterManagerError{ "Cannot run that many qubits in this version of QX-simulator" }; } - variable_name_to_range_.reserve(register_size_); - index_to_variable_name_.resize(register_size_); + variable_name_to_qubit_range_.reserve(qubit_register_size_); + qubit_index_to_variable_name_.resize(qubit_register_size_); - auto current_index = size_t{}; - for (auto &&variable: variables) { + auto current_qubit_index = size_t{}; + for (auto &&variable: qubit_variables) { const auto &variable_size = static_cast(cqasm::v3x::types::size_of(variable->typ)); - variable_name_to_range_[variable->name] = Range{ current_index, variable_size }; - std::fill(index_to_variable_name_.begin() + static_cast(current_index), - index_to_variable_name_.begin() + static_cast(current_index + variable_size), + variable_name_to_qubit_range_[variable->name] = QubitRange{ current_qubit_index, variable_size }; + std::fill(qubit_index_to_variable_name_.begin() + static_cast(current_qubit_index), + qubit_index_to_variable_name_.begin() + static_cast(current_qubit_index + variable_size), variable->name); - current_index += variable_size; + current_qubit_index += variable_size; }; } -Register::~Register() = default; - -[[nodiscard]] std::size_t Register::size() const { - return register_size_; -} - -[[nodiscard]] Range Register::at(const VariableName &name) const { - return variable_name_to_range_.at(name); -} - -[[nodiscard]] Index Register::at(const VariableName &name, const std::optional &subIndex) const { - auto range = variable_name_to_range_.at(name); - return range.first + subIndex.value_or(0); -} - -[[nodiscard]] VariableName Register::at(const std::size_t &index) const { - return index_to_variable_name_.at(index); -} - -[[nodiscard]] std::string Register::toString() const { - auto entries = ranges::accumulate(variable_name_to_range_, std::string{}, - [first=true](auto total, auto const& kv) mutable { - auto const& [variableName, range] = kv; - total += fmt::format("{}{}: {}", first ? "" : ", ", variableName, range); - first = false; - return total; - }); - return fmt::format("{{ {0} }}", entries); -} - -QubitRegister::QubitRegister(const V3OneProgram &program) try - : Register(program, is_qubit_variable, config::MAX_QUBIT_NUMBER) { -} catch (const RegisterManagerError &e) { - throw RegisterManagerError{ fmt::format("qubit register size exceeds maximum allowed: {} > {}", - e.what(), config::MAX_QUBIT_NUMBER) }; -} - -QubitRegister::~QubitRegister() = default; - -BitRegister::BitRegister(const V3OneProgram &program) try - : Register(program, is_bit_variable, config::MAX_BIT_NUMBER) { -} catch (const RegisterManagerError &e) { - throw RegisterManagerError{ fmt::format("bit register size exceeds maximum allowed: {} > {}", - e.what(), config::MAX_BIT_NUMBER) }; -} - -BitRegister::~BitRegister() = default; - -RegisterManager::RegisterManager(const V3OneProgram &program) - : qubit_register_{ program } - , bit_register_{ program } -{} - [[nodiscard]] std::size_t RegisterManager::get_qubit_register_size() const { - return qubit_register_.size(); -} - -[[nodiscard]] std::size_t RegisterManager::get_bit_register_size() const { - return bit_register_.size(); -} - -[[nodiscard]] Range RegisterManager::get_qubit_range(const VariableName &name) const { - return qubit_register_.at(name); -} - -[[nodiscard]] Range RegisterManager::get_bit_range(const VariableName &name) const { - return bit_register_.at(name); -} - -[[nodiscard]] Index RegisterManager::get_qubit_index(const VariableName &name, - const std::optional &subIndex) const { - return qubit_register_.at(name, subIndex); -} - -[[nodiscard]] Index RegisterManager::get_bit_index(const VariableName &name, - const std::optional &subIndex) const { - return bit_register_.at(name, subIndex); -} - -[[nodiscard]] VariableName RegisterManager::get_qubit_variable_name(const std::size_t &index) const { - return qubit_register_.at(index); -} - -[[nodiscard]] VariableName RegisterManager::get_bit_variable_name(const std::size_t &index) const { - return bit_register_.at(index); -} - -[[nodiscard]] QubitRegister const& RegisterManager::get_qubit_register() const { - return qubit_register_; -} - -[[nodiscard]] BitRegister const& RegisterManager::get_bit_register() const { - return bit_register_; -} - -std::ostream& operator<<(std::ostream& os, Range const& range) { - return os << fmt::format("[{}{}]", - range.first, - range.size == 1 ? "" : fmt::format("..{}", range.first + range.size - 1)); + return qubit_register_size_; } -std::ostream& operator<<(std::ostream& os, QubitRegister const& qubitRegister) { - return os << qubitRegister.toString(); +[[nodiscard]] QubitRange RegisterManager::get_qubit_range(const VariableName &name) const { + return variable_name_to_qubit_range_.at(name); } -std::ostream& operator<<(std::ostream& os, BitRegister const& bitRegister) { - return os << bitRegister.toString(); +[[nodiscard]] VariableName RegisterManager::get_variable_name(std::size_t index) const { + return qubit_index_to_variable_name_.at(index); } } // namespace qx diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index 1a8b518f..85fde410 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -9,26 +9,11 @@ namespace qx { -SimulationResult::SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots, - RegisterManager const& registerManager) +SimulationResult::SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots) : shotsRequested{ requestedShots } , shotsDone{ doneShots } - , qubitRegister{ registerManager.get_qubit_register() } - , bitRegister{ registerManager.get_bit_register() } {} -std::uint8_t SimulationResult::getQubitState(state_string_t const& stateString, std::string const& qubitVariableName, - std::optional subIndex) { - auto index = qubitRegister.at(qubitVariableName, subIndex); - return static_cast(stateString[stateString.size() - index - 1] - '0'); -} - -std::uint8_t SimulationResult::getBitMeasurement(state_string_t const& stateString, std::string const& bitVariableName, - std::optional subIndex) { - auto index = bitRegister.at(bitVariableName, subIndex); - return static_cast(stateString[stateString.size() - index - 1] - '0'); -} - std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationResult) { fmt::print(os, "State:\n"); for (auto const &superposedState : simulationResult.state) { @@ -48,17 +33,6 @@ std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationRes simulationResult.shotsDone, static_cast(measurement.count) / static_cast(simulationResult.shotsDone)); } - fmt::print(os, "Bit measurements:\n"); - for (auto const &bitMeasurement : simulationResult.bitMeasurements) { - fmt::print(os, "\t{1} {2}/{3} (count/shots % = {4:.{0}f})\n", - config::OUTPUT_DECIMALS, - bitMeasurement.state, - bitMeasurement.count, - simulationResult.shotsDone, - static_cast(bitMeasurement.count) / static_cast(simulationResult.shotsDone)); - } - fmt::print(os, "Qubit register:\n\t{}\n", simulationResult.qubitRegister); - fmt::print(os, "Bit register:\n\t{}", simulationResult.bitRegister); return os; } @@ -73,17 +47,10 @@ void SimulationResultAccumulator::appendMeasurement(core::BasisVector const& mea measurementsCount++; } -void SimulationResultAccumulator::appendBitMeasurement(core::BitMeasurementRegister const& bitMeasurement) { - assert(bitMeasurements.size() < (static_cast(1) << state.getNumberOfQubits())); - auto bitMeasuredStateString{ fmt::format("{}", bitMeasurement) }; - bitMeasurements[bitMeasuredStateString]++; - bitMeasurementsCount++; -} - -SimulationResult SimulationResultAccumulator::getSimulationResult(RegisterManager const& registerManager) { +SimulationResult SimulationResultAccumulator::getSimulationResult() { assert(measurementsCount > 0); - SimulationResult simulationResult{ measurementsCount, measurementsCount, registerManager }; + SimulationResult simulationResult{ measurementsCount, measurementsCount }; forAllNonZeroStates( [this, &simulationResult](core::BasisVector const& superposedState, core::SparseComplex const& sparseComplex) { @@ -97,11 +64,8 @@ SimulationResult SimulationResultAccumulator::getSimulationResult(RegisterManage for (auto const& [stateString, count] : measurements) { simulationResult.measurements.push_back(Measurement{ stateString, count }); } - for (auto const& [stateString, count] : bitMeasurements) { - simulationResult.bitMeasurements.push_back(Measurement{ stateString, count }); - } return simulationResult; } -} // namespace qx \ No newline at end of file +} // namespace qx diff --git a/src/qx/Simulator.cpp b/src/qx/Simulator.cpp index 12637e08..b4bec56d 100644 --- a/src/qx/Simulator.cpp +++ b/src/qx/Simulator.cpp @@ -67,10 +67,7 @@ execute( try { auto register_manager = RegisterManager{ program }; - auto quantumState = core::QuantumState{ - register_manager.get_qubit_register_size(), - register_manager.get_bit_register_size() - }; + auto quantumState = core::QuantumState{ register_manager.get_qubit_register_size() }; auto circuit = Circuit{ program, register_manager }; auto simulationResultAccumulator = SimulationResultAccumulator{ quantumState }; @@ -78,10 +75,9 @@ execute( quantumState.reset(); circuit.execute(quantumState, std::monostate{}); simulationResultAccumulator.appendMeasurement(quantumState.getMeasurementRegister()); - simulationResultAccumulator.appendBitMeasurement(quantumState.getBitMeasurementRegister()); } - return simulationResultAccumulator.getSimulationResult(register_manager); + return simulationResultAccumulator.getSimulationResult(); } catch (const SimulationError &err) { return err; } diff --git a/src/qx/V3xLibqasmInterface.cpp b/src/qx/V3xLibqasmInterface.cpp index 1fdbf841..402b6bc5 100644 --- a/src/qx/V3xLibqasmInterface.cpp +++ b/src/qx/V3xLibqasmInterface.cpp @@ -7,8 +7,4 @@ bool is_qubit_variable(const V3Variable &variable) { return variable.typ->as_qubit() || variable.typ->as_qubit_array(); } -bool is_bit_variable(const V3Variable &variable) { - return variable.typ->as_bit() || variable.typ->as_bit_array(); -} - } // namespace qx diff --git a/test/ErrorModelsTest.cpp b/test/ErrorModelsTest.cpp index c6c2de8c..d837c281 100644 --- a/test/ErrorModelsTest.cpp +++ b/test/ErrorModelsTest.cpp @@ -27,7 +27,7 @@ class ErrorModelsTest : public ::testing::Test { } private: - core::QuantumState state{ 3, 3, }; // Using a mock or a TestQuantumState would be beneficial here. + core::QuantumState state{ 3 }; // Using a mock or a TestQuantumState would be beneficial here. }; TEST_F(ErrorModelsTest, depolarizing_channel__probability_1) { diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 4cb12786..173bd965 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -214,30 +214,4 @@ b1 = measure q1 EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); } -TEST_F(IntegrationTest, bit_measurement_register) { - std::size_t iterations = 10'000; - auto cqasm = R"( -version 3.0 - -qubit[2] qq -X qq[0] -bit[2] bb -bb[0] = measure qq[0] - -qubit q -H q -CNOT q, qq[0] -bit b -b = measure qq[0] -)"; - auto actual = runFromString(cqasm, iterations); - - auto error = static_cast(static_cast(iterations)/2 * 0.05); - EXPECT_EQ(actual.bitMeasurements.size(), 2); - for (auto const& bitMeasurement : actual.bitMeasurements) { - EXPECT_EQ(actual.getBitMeasurement(bitMeasurement.state, "bb", 0), 1); - EXPECT_LT(std::abs(static_cast(iterations/2 - bitMeasurement.count)), error); - } -} - } // namespace qx diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index 67e44076..d6ea9e6a 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -32,7 +32,7 @@ class QuantumStateTest : public ::testing::Test { }; TEST_F(QuantumStateTest, apply_identity) { - QuantumState victim{ 3, 3 }; + QuantumState victim{ 3 }; EXPECT_EQ(victim.getNumberOfQubits(), 3); checkEq(victim, {1, 0, 0, 0, 0, 0, 0, 0}); @@ -44,7 +44,7 @@ TEST_F(QuantumStateTest, apply_identity) { } TEST_F(QuantumStateTest, apply_hadamard) { - QuantumState victim{ 3, 3 }; + QuantumState victim{ 3 }; EXPECT_EQ(victim.getNumberOfQubits(), 3); checkEq(victim, {1, 0, 0, 0, 0, 0, 0, 0}); @@ -58,31 +58,31 @@ TEST_F(QuantumStateTest, apply_hadamard) { } TEST_F(QuantumStateTest, apply_cnot) { - QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); victim.apply<2>(gates::CNOT, std::array{QubitIndex{1}, QubitIndex{0}}); checkEq(victim, {0, 0, std::sqrt(1 - std::pow(0.123, 2)), 0.123}); } TEST_F(QuantumStateTest, measure_on_non_superposed_state) { - QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.measure(BitIndex{1}, QubitIndex{1}, []() { return 0.9485; }); + QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + victim.measure(QubitIndex{1}, []() { return 0.9485; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); - victim.measure(BitIndex{1}, QubitIndex{1}, []() { return 0.045621; }); + victim.measure(QubitIndex{1}, []() { return 0.045621; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("10")); } TEST_F(QuantumStateTest, measure_on_superposed_state__case_0) { - QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.measure(BitIndex{0}, QubitIndex{0}, []() { return 0.994; }); + QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + victim.measure(QubitIndex{0}, []() { return 0.994; }); checkEq(victim, {0, 0, 1, 0}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } TEST_F(QuantumStateTest, measure_on_superposed_state__case_1) { - QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.measure(BitIndex{0}, QubitIndex{0}, []() { return 0.254; }); + QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + victim.measure(QubitIndex{0}, []() { return 0.254; }); checkEq(victim, {0, 0, 0, 1}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("01")); } From 761e633846021a21187db30b5bcc5921ae7c2818 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Tue, 25 Jun 2024 10:30:59 +0200 Subject: [PATCH 32/56] Change measuredStateProbability parameter to probabilityOfMeasuringOne. --- include/qx/QuantumState.hpp | 7 ++----- src/qx/QuantumState.cpp | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 34aae24c..7f02d7c8 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -84,17 +84,14 @@ class QuantumState { [[nodiscard]] const BasisVector &getMeasurementRegister() const; [[nodiscard]] double getProbabilityOfMeasuringOne(QubitIndex qubitIndex); [[nodiscard]] double getProbabilityOfMeasuringZero(QubitIndex qubitIndex); - void collapseQubit(QubitIndex qubitIndex, bool measuredState, double measuredStateProbability); + void collapseQubit(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne); // measuredState will be true if we measured a 1, or false if we measured a 0 - // measuredStateProbability will be the probability of measuring 1 if we measured a 1, - // or the probability of measuring 0 if we measured a 0 template void measure(QubitIndex qubitIndex, F &&randomGenerator) { auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); - auto measuredStateProbability = measuredState ? probabilityOfMeasuringOne : (1 - probabilityOfMeasuringOne); - collapseQubit(qubitIndex, measuredState, measuredStateProbability); + collapseQubit(qubitIndex, measuredState, probabilityOfMeasuringOne); measurementRegister.set(qubitIndex.value, measuredState); } diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index a395256e..a353a624 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -69,15 +69,13 @@ void QuantumState::reset() { // Update data after a measurement // // measuredState will be true if we measured a 1, or false if we measured a 0 -// measuredStateProbability will be the probability of measuring 1 if we measured a 1, -// or the probability of measuring 0 if we measured a 0 -void QuantumState::collapseQubit(QubitIndex qubitIndex, bool measuredState, double measuredStateProbability) { +void QuantumState::collapseQubit(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne) { data.eraseIf([qubitIndex, measuredState](auto const &kv) { auto const &[basisVector, _] = kv; auto currentState = basisVector.test(qubitIndex.value); return currentState != measuredState; }); - data *= std::sqrt(1 / measuredStateProbability); + data *= std::sqrt(1 / (measuredState ? probabilityOfMeasuringOne : (1 - probabilityOfMeasuringOne))); } } // namespace qx::core From bfa9c3335d7a59377d4b715353a955a74a1f3e7c Mon Sep 17 00:00:00 2001 From: rturrado Date: Sat, 10 Aug 2024 13:50:44 +0200 Subject: [PATCH 33/56] Address changes from Juan's code review. --- docs/manual/output.rst | 2 +- include/qx/QuantumState.hpp | 4 ++-- src/qx/QuantumState.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/manual/output.rst b/docs/manual/output.rst index 8c6d4c44..dc4bc522 100644 --- a/docs/manual/output.rst +++ b/docs/manual/output.rst @@ -54,7 +54,7 @@ The result is now: State: {'00': (0.9999999999999998+0j)} -You can there notice that the final quantum state is collapsed to "00", because of the measurements. +You can notice that the final quantum state is collapsed to "00", because of the measurements. The ket ``|00>`` doesn't have amplitude exactly 1 because of the approximation of real numbers done by floating point arithmetic inside the simulator, something to keep in mind when interpreting the results. Again, the ``State`` section is the quantum state at the end of the 100th iteration. diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 7f02d7c8..22c18e63 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -84,14 +84,14 @@ class QuantumState { [[nodiscard]] const BasisVector &getMeasurementRegister() const; [[nodiscard]] double getProbabilityOfMeasuringOne(QubitIndex qubitIndex); [[nodiscard]] double getProbabilityOfMeasuringZero(QubitIndex qubitIndex); - void collapseQubit(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne); + void collapseQubitState(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne); // measuredState will be true if we measured a 1, or false if we measured a 0 template void measure(QubitIndex qubitIndex, F &&randomGenerator) { auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); - collapseQubit(qubitIndex, measuredState, probabilityOfMeasuringOne); + collapseQubitState(qubitIndex, measuredState, probabilityOfMeasuringOne); measurementRegister.set(qubitIndex.value, measuredState); } diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index a353a624..725b6bc8 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -69,7 +69,7 @@ void QuantumState::reset() { // Update data after a measurement // // measuredState will be true if we measured a 1, or false if we measured a 0 -void QuantumState::collapseQubit(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne) { +void QuantumState::collapseQubitState(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne) { data.eraseIf([qubitIndex, measuredState](auto const &kv) { auto const &[basisVector, _] = kv; auto currentState = basisVector.test(qubitIndex.value); From 5518a4526dcdffe1433163e6f49a987c4cb032f2 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 13 Aug 2024 12:46:09 +0200 Subject: [PATCH 34/56] Add back all "manage bit register variables" code. This code had been previously removed from qx-simulator/154-refactorings-manage-bit-register-variables branch at a5af5996c18a8aad0ba5170c18608cd46d486150 (2024/Jun/25). --- CMakeLists.txt | 3 + conan/profiles/base | 4 + conan/profiles/debug | 2 +- conan/profiles/release | 2 +- conan/profiles/tests-debug | 2 +- conan/profiles/tests-release | 2 +- conanfile.py | 1 + include/qx/CompileTimeConfiguration.hpp | 4 + include/qx/Core.hpp | 10 ++ include/qx/Instructions.hpp | 1 + include/qx/QuantumState.hpp | 14 ++- include/qx/RegisterManager.hpp | 101 ++++++++++++----- include/qx/SimulationResult.hpp | 32 +++++- include/qx/V3xLibqasmInterface.hpp | 1 + setup.py | 1 + src/qx/GateConvertor.cpp | 4 +- src/qx/InstructionExecutor.cpp | 2 +- src/qx/OperandsHelper.cpp | 39 ++++--- src/qx/QuantumState.cpp | 25 ++++- src/qx/RegisterManager.cpp | 137 ++++++++++++++++++++---- src/qx/SimulationResult.cpp | 44 +++++++- src/qx/Simulator.cpp | 8 +- src/qx/V3xLibqasmInterface.cpp | 4 + test/ErrorModelsTest.cpp | 2 +- test/IntegrationTest.cpp | 26 +++++ test/QuantumStateTest.cpp | 20 ++-- 26 files changed, 401 insertions(+), 90 deletions(-) create mode 100644 conan/profiles/base diff --git a/CMakeLists.txt b/CMakeLists.txt index b72a2636..92bcda3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,7 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) #=============================================================================# find_package(absl) +find_package(Boost REQUIRED) find_package(libqasm REQUIRED CONFIG) @@ -141,6 +142,7 @@ target_compile_features(qx PRIVATE target_include_directories(qx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/" + "${Boost_INCLUDE_DIRS}" ) if(CMAKE_COMPILER_IS_GNUCXX) @@ -244,6 +246,7 @@ endif() message(STATUS "[${PROJECT_NAME}] Target include directories:\n" " CMAKE_CURRENT_SOURCE_DIR/include/: ${CMAKE_CURRENT_SOURCE_DIR}/include/\n" + " Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}\n" ) diff --git a/conan/profiles/base b/conan/profiles/base new file mode 100644 index 00000000..a5bffa3a --- /dev/null +++ b/conan/profiles/base @@ -0,0 +1,4 @@ +include(default) + +[options] +boost/*:header_only=True diff --git a/conan/profiles/debug b/conan/profiles/debug index 86259bf0..acdcf409 100644 --- a/conan/profiles/debug +++ b/conan/profiles/debug @@ -1,4 +1,4 @@ -include(default) +include(base) [settings] compiler.cppstd=23 diff --git a/conan/profiles/release b/conan/profiles/release index 37e08db9..1dc80926 100644 --- a/conan/profiles/release +++ b/conan/profiles/release @@ -1,4 +1,4 @@ -include(default) +include(base) [settings] compiler.cppstd=23 diff --git a/conan/profiles/tests-debug b/conan/profiles/tests-debug index b5e51b52..37f88a7a 100644 --- a/conan/profiles/tests-debug +++ b/conan/profiles/tests-debug @@ -1,4 +1,4 @@ -include(default) +include(base) [settings] compiler.cppstd=23 diff --git a/conan/profiles/tests-release b/conan/profiles/tests-release index 3aafb1ed..10f3d336 100644 --- a/conan/profiles/tests-release +++ b/conan/profiles/tests-release @@ -1,4 +1,4 @@ -include(default) +include(base) [settings] compiler.cppstd=23 diff --git a/conanfile.py b/conanfile.py index db226524..0638d250 100644 --- a/conanfile.py +++ b/conanfile.py @@ -48,6 +48,7 @@ def build_requirements(self): def requirements(self): self.requires("abseil/20230125.3", transitive_headers=True) + self.requires("boost/1.85.0") self.requires("fmt/10.2.1", transitive_headers=True) self.requires("libqasm/0.6.6", transitive_headers=True) self.requires("range-v3/0.12.0", transitive_headers=True) diff --git a/include/qx/CompileTimeConfiguration.hpp b/include/qx/CompileTimeConfiguration.hpp index 669a76b2..a93e47b9 100644 --- a/include/qx/CompileTimeConfiguration.hpp +++ b/include/qx/CompileTimeConfiguration.hpp @@ -19,4 +19,8 @@ static constexpr std::uint64_t ZERO_CYCLE_SIZE = 100; // Maybe memory-saving as a multiple of 64. static constexpr std::size_t MAX_QUBIT_NUMBER = 64; +// Maximum number of bits that can be used. +// Just for sanity, as we maintain vectors of the size of the number of used bits. +static constexpr std::size_t MAX_BIT_NUMBER = 1*1024*1024; // 1 MB + } // namespace qx::config diff --git a/include/qx/Core.hpp b/include/qx/Core.hpp index 3d7844dd..78b270ff 100644 --- a/include/qx/Core.hpp +++ b/include/qx/Core.hpp @@ -1,8 +1,10 @@ #pragma once +#include #include // size_t, uint32_t #include // abs #include +#include #include "qx/CompileTimeConfiguration.hpp" // EPS, MAX_QUBIT_NUMBER #include "qx/Utils.hpp" @@ -26,8 +28,14 @@ struct QubitIndex { std::size_t value; }; +struct BitIndex { + std::size_t value; +}; + using BasisVector = utils::Bitset; +using BitMeasurementRegister = boost::dynamic_bitset; + inline constexpr bool isNotNull(std::complex c) { #if defined(_MSC_VER) return @@ -43,3 +51,5 @@ inline constexpr bool isNull(std::complex c) { } } // namespace qx::core + +template <> struct fmt::formatter : fmt::ostream_formatter {}; diff --git a/include/qx/Instructions.hpp b/include/qx/Instructions.hpp index bb845e08..f0303ce8 100644 --- a/include/qx/Instructions.hpp +++ b/include/qx/Instructions.hpp @@ -15,6 +15,7 @@ namespace qx { struct Measure { core::QubitIndex qubitIndex{}; + core::BitIndex bitIndex{}; }; struct MeasurementRegisterOperation { diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 22c18e63..d989daef 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -50,10 +50,11 @@ class QuantumState { void checkQuantumState(); public: - QuantumState(std::size_t qubit_register_size); - QuantumState(std::size_t qubit_register_size, + QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size); + QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size, std::initializer_list>> values); [[nodiscard]] std::size_t getNumberOfQubits() const; + [[nodiscard]] std::size_t getNumberOfBits() const; [[nodiscard]] bool isNormalized(); void reset(); @@ -82,23 +83,30 @@ class QuantumState { } [[nodiscard]] const BasisVector &getMeasurementRegister() const; + [[nodiscard]] const BitMeasurementRegister &getBitMeasurementRegister() const; [[nodiscard]] double getProbabilityOfMeasuringOne(QubitIndex qubitIndex); [[nodiscard]] double getProbabilityOfMeasuringZero(QubitIndex qubitIndex); void collapseQubitState(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne); // measuredState will be true if we measured a 1, or false if we measured a 0 template - void measure(QubitIndex qubitIndex, F &&randomGenerator) { + void measure(QubitIndex qubitIndex, BitIndex bitIndex, F &&randomGenerator) { auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); collapseQubitState(qubitIndex, measuredState, probabilityOfMeasuringOne); measurementRegister.set(qubitIndex.value, measuredState); + bitMeasurementRegister.set(bitIndex.value, measuredState); } private: std::size_t numberOfQubits = 1; + std::size_t numberOfBits = 1; SparseArray data; + // TODO: we are keeping a "double-entry bookkeeping" until we can get rid of measurements + // measurements needs to be replaced with bitMeasurements with the introduction of bit variables, + // but this replacement cannot be executed until all the QX simulator clients start using bitMeasurements BasisVector measurementRegister{}; + BitMeasurementRegister bitMeasurementRegister{}; }; } // namespace qx::core diff --git a/include/qx/RegisterManager.hpp b/include/qx/RegisterManager.hpp index dd013b6c..2ebc5e8d 100644 --- a/include/qx/RegisterManager.hpp +++ b/include/qx/RegisterManager.hpp @@ -5,6 +5,8 @@ #include "v3x/cqasm-semantic-gen.hpp" #include // size_t +#include +#include #include #include #include @@ -12,37 +14,70 @@ namespace qx { -using VariableName = std::string; - -struct QubitRange { - std::size_t first; - std::size_t size; -}; - -struct RegisterManagerError : public SimulationError { - explicit RegisterManagerError(const std::string &message); -}; - /* - * RegisterManager keeps track of a (virtual) qubit register, i.e., an array of consecutive qubits, - * and the mappings between the (logical) qubit variable names, as used in an input cQASM program, - * and the (virtual) qubit register. + * RegisterManager keeps track of a (virtual) qubit register and a (virtual) bit register. + * I.e., an array of consecutive qubits/bits, and the mappings between the (logical) qubit/bit variable names, + * as used in an input cQASM program, and the (virtual) qubit/bit register. * * For example, given an input program that defines 'qubit[3] q': * - variable 'q' is mapped to qubits 0 to 2 in the qubit register, and * - positions 0 to 2 in the qubit register are mapped to variable 'q'. * - * The mapping of qubit variable names to positions in the qubit register is an implementation detail, - * i.e., it is not guaranteed that qubit register indices are assigned to qubit variable names in the order + * The mapping of qubit/bit variable names to positions in the qubit/bit register is an implementation detail, + * i.e., it is not guaranteed that qubit/bit register indices are assigned to qubit/bit variable names in the order * these variables are defined in the input program. */ + + +using VariableName = std::string; + +using Index = std::size_t; + +struct Range { + Index first; + Index size; +}; + + +struct RegisterManagerError : public SimulationError { + explicit RegisterManagerError(const std::string &message); +}; + +using VariableNameToRangeMapT = std::unordered_map; +using IndexToVariableNameMapT = std::vector; + +class Register { + std::size_t register_size_; + VariableNameToRangeMapT variable_name_to_range_; + IndexToVariableNameMapT index_to_variable_name_; +public: + Register(const V3OneProgram &program, auto &&is_of_type, std::size_t max_register_size); + virtual ~Register() = 0; + [[nodiscard]] std::size_t size() const; + [[nodiscard]] virtual Range at(const VariableName &name) const; + [[nodiscard]] virtual Index at(const VariableName &name, const std::optional &subIndex) const; + [[nodiscard]] virtual VariableName at(const std::size_t &index) const; + [[nodiscard]] virtual std::string toString() const; +}; + + +class QubitRegister : public Register { +public: + explicit QubitRegister(const V3OneProgram &program); + ~QubitRegister() override; +}; + + +class BitRegister : public Register { +public: + explicit BitRegister(const V3OneProgram &program); + ~BitRegister() override; +}; + + class RegisterManager { - using VariableNameToQubitRangeMapT = std::unordered_map; - using QubitIndexToVariableNameMapT = std::vector; -private: - std::size_t qubit_register_size_; - VariableNameToQubitRangeMapT variable_name_to_qubit_range_; - QubitIndexToVariableNameMapT qubit_index_to_variable_name_; + QubitRegister qubit_register_; + BitRegister bit_register_; public: RegisterManager(const RegisterManager&) = delete; RegisterManager(RegisterManager&&) noexcept = delete; @@ -51,8 +86,26 @@ class RegisterManager { public: explicit RegisterManager(const V3OneProgram &program); [[nodiscard]] std::size_t get_qubit_register_size() const; - [[nodiscard]] QubitRange get_qubit_range(const VariableName &name) const; - [[nodiscard]] VariableName get_variable_name(std::size_t index) const; + [[nodiscard]] std::size_t get_bit_register_size() const; + [[nodiscard]] Range get_qubit_range(const VariableName &name) const; + [[nodiscard]] Range get_bit_range(const VariableName &name) const; + [[nodiscard]] Index get_qubit_index(const VariableName &name, const std::optional &subIndex) const; + [[nodiscard]] Index get_bit_index(const VariableName &name, const std::optional &subIndex) const; + [[nodiscard]] VariableName get_qubit_variable_name(const Index &index) const; + [[nodiscard]] VariableName get_bit_variable_name(const Index &index) const; + [[nodiscard]] QubitRegister const& get_qubit_register() const; + [[nodiscard]] BitRegister const& get_bit_register() const; }; + +std::ostream& operator<<(std::ostream& os, Range const& range); +std::ostream& operator<<(std::ostream& os, QubitRegister const& qubitRegister); +std::ostream& operator<<(std::ostream& os, BitRegister const& bitRegister); + + } // namespace qx + + +template <> struct fmt::formatter : fmt::ostream_formatter {}; +template <> struct fmt::formatter : fmt::ostream_formatter {}; +template <> struct fmt::formatter : fmt::ostream_formatter {}; diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index 7b343dd8..1e53364c 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -1,8 +1,9 @@ #pragma once #include "qx/CompileTimeConfiguration.hpp" -#include "qx/Core.hpp" // BasisVector, Complex +#include "qx/Core.hpp" // BasisVector, BitMeasurementRegister Complex #include "qx/QuantumState.hpp" +#include "qx/RegisterManager.hpp" #include #include // partial_ordering @@ -49,14 +50,31 @@ struct SimulationResult { using State = std::vector; public: - SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots); + SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots, RegisterManager const& registerManager); + + // Given a state string from the State vector, a qubit variable name, and an optional sub index, + // return the value of that qubit in the state string + // The sub index is used to access a given qubit when the qubit variable is of array type + // Notice that the final index in the state string is determined by the qubit register + std::uint8_t getQubitState(state_string_t const& stateString, std::string const& qubitVariableName, + std::optional subIndex); + // Given a state string from the State vector, a bit variable name, and an optional sub index, + // return the value of that bit in the state string + // The sub index is used to access a given bit when the bit variable is of array type + // Notice that the final index in the state string is determined by the bit register + std::uint8_t getBitMeasurement(state_string_t const& stateString, std::string const& bitVariableName, + std::optional subIndex); public: std::uint64_t shotsRequested; std::uint64_t shotsDone; + QubitRegister qubitRegister; + BitRegister bitRegister; + State state; Measurements measurements; + Measurements bitMeasurements; }; std::ostream &operator<<(std::ostream &os, const SimulationResult &result); @@ -66,7 +84,8 @@ class SimulationResultAccumulator { public: explicit SimulationResultAccumulator(core::QuantumState &s); void appendMeasurement(core::BasisVector const& measurement); - SimulationResult getSimulationResult(); + void appendBitMeasurement(core::BitMeasurementRegister const& bitMeasurement); + SimulationResult getSimulationResult(RegisterManager const& registerManager); private: template @@ -77,8 +96,15 @@ class SimulationResultAccumulator { } core::QuantumState &state; + + // TODO: we are keeping a "double-entry bookkeeping" until we can get rid of measurements + // measurements needs to be replaced with bitMeasurements with the introduction of bit variables, + // but this replacement cannot be executed until all the QX simulator clients start using bitMeasurements absl::btree_map measurements; + absl::btree_map bitMeasurements; + std::uint64_t measurementsCount = 0; + std::uint64_t bitMeasurementsCount = 0; }; diff --git a/include/qx/V3xLibqasmInterface.hpp b/include/qx/V3xLibqasmInterface.hpp index 476e360a..bb77abf8 100644 --- a/include/qx/V3xLibqasmInterface.hpp +++ b/include/qx/V3xLibqasmInterface.hpp @@ -29,5 +29,6 @@ using V3Value = v3_values::Value; using V3Variable = v3_semantic::Variable; bool is_qubit_variable(const V3Variable &variable); +bool is_bit_variable(const V3Variable &variable); } // namespace qx diff --git a/setup.py b/setup.py index 2a096ada..4d7614de 100755 --- a/setup.py +++ b/setup.py @@ -94,6 +94,7 @@ def run(self): ['-s:a']['compiler.cppstd=23'] ['-s:a']['build_type=' + build_type] + ['-o:a']['boost/*:header_only=True'] ['-o:a']['qx/*:build_python=True'] ['-o:a']['qx/*:cpu_compatibility_mode=' + cpu_compatibility_mode] # The Python library needs the compatibility headers diff --git a/src/qx/GateConvertor.cpp b/src/qx/GateConvertor.cpp index f41a9735..3ba1ce82 100644 --- a/src/qx/GateConvertor.cpp +++ b/src/qx/GateConvertor.cpp @@ -132,12 +132,14 @@ void GateConvertor::addGates(const V3Instruction &instruction) { // A measure statement has the following syntax: b = measure q // The left-hand side operand, b, is the operand 0 // The right-hand side operand, q, is the operand 1 + const auto &bit_indices = operands_helper.get_register_operand(0); const auto &qubit_indices = operands_helper.get_register_operand(1); auto controlBits = std::make_shared>(); for (size_t i{ 0 }; i < qubit_indices.size(); ++i) { circuit_.add_instruction( Measure{ - core::QubitIndex{ static_cast(qubit_indices[i]->value) } + core::QubitIndex{ static_cast(qubit_indices[i]->value) }, + core::BitIndex{ static_cast(bit_indices[i]->value) } }, controlBits); } diff --git a/src/qx/InstructionExecutor.cpp b/src/qx/InstructionExecutor.cpp index 7addf2bd..d29f8f1a 100644 --- a/src/qx/InstructionExecutor.cpp +++ b/src/qx/InstructionExecutor.cpp @@ -9,7 +9,7 @@ InstructionExecutor::InstructionExecutor(core::QuantumState &s) {} void InstructionExecutor::operator()(Measure const &m) { - quantumState.measure(m.qubitIndex, &random::randomZeroOneDouble); + quantumState.measure(m.qubitIndex, m.bitIndex, &random::randomZeroOneDouble); } void InstructionExecutor::operator()(MeasurementRegisterOperation const &op) { diff --git a/src/qx/OperandsHelper.cpp b/src/qx/OperandsHelper.cpp index 279598e2..0b27f220 100644 --- a/src/qx/OperandsHelper.cpp +++ b/src/qx/OperandsHelper.cpp @@ -15,25 +15,38 @@ OperandsHelper::OperandsHelper(const V3Instruction &instruction, const RegisterM [[nodiscard]] V3Many OperandsHelper::get_register_operand(int id) const { if (auto variable_ref = instruction_.operands[id]->as_variable_ref()) { - if (!is_qubit_variable(*variable_ref->variable)) { + auto ret = V3Many{}; + if (is_qubit_variable(*variable_ref->variable)) { + auto qubit_range = register_manager_.get_qubit_range(variable_ref->variable->name); + ret.get_vec().resize(qubit_range.size); + std::generate_n(ret.get_vec().begin(), qubit_range.size, [qubit_range, i=0]() mutable { + return v3_tree::make(static_cast(qubit_range.first + i++)); + }); + } else if (is_bit_variable(*variable_ref->variable)) { + auto bit_range = register_manager_.get_bit_range(variable_ref->variable->name); + ret.get_vec().resize(bit_range.size); + std::generate_n(ret.get_vec().begin(), bit_range.size, [bit_range, i=0]() mutable { + return v3_tree::make(static_cast(bit_range.first + i++)); + }); + } else { return {}; } - auto qubit_range = register_manager_.get_qubit_range(variable_ref->variable->name); - auto ret = V3Many{}; - ret.get_vec().resize(qubit_range.size); - std::generate_n(ret.get_vec().begin(), qubit_range.size, [qubit_range, i=0]() mutable { - return v3_tree::make(static_cast(qubit_range.first + i++)); - }); return ret; } else if (auto index_ref = instruction_.operands[id]->as_index_ref()) { - if (!is_qubit_variable(*index_ref->variable)) { + auto ret = index_ref->indices; + if (is_qubit_variable(*index_ref->variable)) { + auto qubit_range = register_manager_.get_qubit_range(index_ref->variable->name); + std::for_each(ret.get_vec().begin(), ret.get_vec().end(), [qubit_range](const auto &index) { + index->value += qubit_range.first; + }); + } else if (is_bit_variable(*index_ref->variable)) { + auto bit_range = register_manager_.get_bit_range(index_ref->variable->name); + std::for_each(ret.get_vec().begin(), ret.get_vec().end(), [bit_range](const auto &index) { + index->value += bit_range.first; + }); + } else { return {}; } - auto qubit_range = register_manager_.get_qubit_range(index_ref->variable->name); - auto ret = index_ref->indices; - std::for_each(ret.get_vec().begin(), ret.get_vec().end(), [qubit_range](const auto &index) { - index->value += qubit_range.first; - }); return ret; } assert(false && "operand is neither a variable reference nor an index reference"); diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index 725b6bc8..718a4af6 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -13,23 +13,31 @@ void QuantumState::checkQuantumState() { throw QuantumStateError{ fmt::format("number of qubits exceeds maximum allowed: {} > {}", numberOfQubits, config::MAX_QUBIT_NUMBER) }; } + if (numberOfBits > config::MAX_BIT_NUMBER) { + throw QuantumStateError{ fmt::format("number of bits exceeds maximum allowed: {} > {}", numberOfBits, + config::MAX_BIT_NUMBER) }; + } if (not isNormalized()) { throw QuantumStateError{ "quantum state is not normalized" }; } } -QuantumState::QuantumState(std::size_t qubit_register_size) +QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size) : numberOfQubits{ qubit_register_size } - , data{ static_cast(1) << numberOfQubits } { + , numberOfBits{ bit_register_size } + , data{ static_cast(1) << numberOfQubits } + , bitMeasurementRegister{ numberOfBits } { data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 checkQuantumState(); } -QuantumState::QuantumState(std::size_t qubit_register_size, +QuantumState::QuantumState(std::size_t qubit_register_size, std::size_t bit_register_size, std::initializer_list>> values) : numberOfQubits{ qubit_register_size } - , data{ static_cast(1) << numberOfQubits, values } { + , numberOfBits{ bit_register_size } + , data{ static_cast(1) << numberOfQubits, values } + , bitMeasurementRegister{ numberOfBits } { checkQuantumState(); } @@ -38,6 +46,10 @@ QuantumState::QuantumState(std::size_t qubit_register_size, return numberOfQubits; } +[[nodiscard]] std::size_t QuantumState::getNumberOfBits() const { + return numberOfBits; +} + [[nodiscard]] bool QuantumState::isNormalized() { return isNull(data.norm() - 1.); } @@ -46,12 +58,17 @@ void QuantumState::reset() { data.clear(); data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 measurementRegister.reset(); + bitMeasurementRegister.reset(); } [[nodiscard]] const BasisVector& QuantumState::getMeasurementRegister() const { return measurementRegister; } +[[nodiscard]] const BitMeasurementRegister& QuantumState::getBitMeasurementRegister() const { + return bitMeasurementRegister; +} + [[nodiscard]] double QuantumState::getProbabilityOfMeasuringOne(QubitIndex qubitIndex) { return data.accumulate(double{}, [qubitIndex](auto total, auto const &kv) { auto const &[basisVector, sparseComplex] = kv; diff --git a/src/qx/RegisterManager.cpp b/src/qx/RegisterManager.cpp index 53e08f8a..f0b6824b 100644 --- a/src/qx/RegisterManager.cpp +++ b/src/qx/RegisterManager.cpp @@ -15,41 +15,138 @@ RegisterManagerError::RegisterManagerError(const std::string &message) : SimulationError{ message } {} -RegisterManager::RegisterManager(const V3OneProgram &program) { - auto &&qubit_variables = program->variables.get_vec() - | ranges::views::filter([&](const V3OneVariable &variable) { return is_qubit_variable(*variable); }); - auto &&qubit_variable_sizes = qubit_variables +Register::Register(const V3OneProgram &program, auto &&is_of_type, std::size_t max_register_size) { + auto &&variables = program->variables.get_vec() + | ranges::views::filter([&](const V3OneVariable &variable) { return is_of_type(*variable); }); + auto &&variable_sizes = variables | ranges::views::transform([](const V3OneVariable &variable) { return v3_types::size_of(variable->typ); }); - qubit_register_size_ = ranges::accumulate(qubit_variable_sizes, size_t{}); + register_size_ = ranges::accumulate(variable_sizes, size_t{}); - if (qubit_register_size_ > config::MAX_QUBIT_NUMBER) { - throw RegisterManagerError{ "Cannot run that many qubits in this version of QX-simulator" }; + if (register_size_ > max_register_size) { + throw RegisterManagerError{ fmt::format("{}", register_size_) }; } - variable_name_to_qubit_range_.reserve(qubit_register_size_); - qubit_index_to_variable_name_.resize(qubit_register_size_); + variable_name_to_range_.reserve(register_size_); + index_to_variable_name_.resize(register_size_); - auto current_qubit_index = size_t{}; - for (auto &&variable: qubit_variables) { + auto current_index = size_t{}; + for (auto &&variable: variables) { const auto &variable_size = static_cast(cqasm::v3x::types::size_of(variable->typ)); - variable_name_to_qubit_range_[variable->name] = QubitRange{ current_qubit_index, variable_size }; - std::fill(qubit_index_to_variable_name_.begin() + static_cast(current_qubit_index), - qubit_index_to_variable_name_.begin() + static_cast(current_qubit_index + variable_size), + variable_name_to_range_[variable->name] = Range{ current_index, variable_size }; + std::fill(index_to_variable_name_.begin() + static_cast(current_index), + index_to_variable_name_.begin() + static_cast(current_index + variable_size), variable->name); - current_qubit_index += variable_size; + current_index += variable_size; }; } +Register::~Register() = default; + +[[nodiscard]] std::size_t Register::size() const { + return register_size_; +} + +[[nodiscard]] Range Register::at(const VariableName &name) const { + return variable_name_to_range_.at(name); +} + +[[nodiscard]] Index Register::at(const VariableName &name, const std::optional &subIndex) const { + auto range = variable_name_to_range_.at(name); + return range.first + subIndex.value_or(0); +} + +[[nodiscard]] VariableName Register::at(const std::size_t &index) const { + return index_to_variable_name_.at(index); +} + +[[nodiscard]] std::string Register::toString() const { + auto entries = ranges::accumulate(variable_name_to_range_, std::string{}, + [first=true](auto total, auto const& kv) mutable { + auto const& [variableName, range] = kv; + total += fmt::format("{}{}: {}", first ? "" : ", ", variableName, range); + first = false; + return total; + }); + return fmt::format("{{ {0} }}", entries); +} + +QubitRegister::QubitRegister(const V3OneProgram &program) try + : Register(program, is_qubit_variable, config::MAX_QUBIT_NUMBER) { +} catch (const RegisterManagerError &e) { + throw RegisterManagerError{ fmt::format("qubit register size exceeds maximum allowed: {} > {}", + e.what(), config::MAX_QUBIT_NUMBER) }; +} + +QubitRegister::~QubitRegister() = default; + +BitRegister::BitRegister(const V3OneProgram &program) try + : Register(program, is_bit_variable, config::MAX_BIT_NUMBER) { +} catch (const RegisterManagerError &e) { + throw RegisterManagerError{ fmt::format("bit register size exceeds maximum allowed: {} > {}", + e.what(), config::MAX_BIT_NUMBER) }; +} + +BitRegister::~BitRegister() = default; + +RegisterManager::RegisterManager(const V3OneProgram &program) + : qubit_register_{ program } + , bit_register_{ program } +{} + [[nodiscard]] std::size_t RegisterManager::get_qubit_register_size() const { - return qubit_register_size_; + return qubit_register_.size(); +} + +[[nodiscard]] std::size_t RegisterManager::get_bit_register_size() const { + return bit_register_.size(); +} + +[[nodiscard]] Range RegisterManager::get_qubit_range(const VariableName &name) const { + return qubit_register_.at(name); +} + +[[nodiscard]] Range RegisterManager::get_bit_range(const VariableName &name) const { + return bit_register_.at(name); +} + +[[nodiscard]] Index RegisterManager::get_qubit_index(const VariableName &name, + const std::optional &subIndex) const { + return qubit_register_.at(name, subIndex); +} + +[[nodiscard]] Index RegisterManager::get_bit_index(const VariableName &name, + const std::optional &subIndex) const { + return bit_register_.at(name, subIndex); +} + +[[nodiscard]] VariableName RegisterManager::get_qubit_variable_name(const std::size_t &index) const { + return qubit_register_.at(index); +} + +[[nodiscard]] VariableName RegisterManager::get_bit_variable_name(const std::size_t &index) const { + return bit_register_.at(index); +} + +[[nodiscard]] QubitRegister const& RegisterManager::get_qubit_register() const { + return qubit_register_; +} + +[[nodiscard]] BitRegister const& RegisterManager::get_bit_register() const { + return bit_register_; +} + +std::ostream& operator<<(std::ostream& os, Range const& range) { + return os << fmt::format("[{}{}]", + range.first, + range.size == 1 ? "" : fmt::format("..{}", range.first + range.size - 1)); } -[[nodiscard]] QubitRange RegisterManager::get_qubit_range(const VariableName &name) const { - return variable_name_to_qubit_range_.at(name); +std::ostream& operator<<(std::ostream& os, QubitRegister const& qubitRegister) { + return os << qubitRegister.toString(); } -[[nodiscard]] VariableName RegisterManager::get_variable_name(std::size_t index) const { - return qubit_index_to_variable_name_.at(index); +std::ostream& operator<<(std::ostream& os, BitRegister const& bitRegister) { + return os << bitRegister.toString(); } } // namespace qx diff --git a/src/qx/SimulationResult.cpp b/src/qx/SimulationResult.cpp index 85fde410..1a8b518f 100644 --- a/src/qx/SimulationResult.cpp +++ b/src/qx/SimulationResult.cpp @@ -9,11 +9,26 @@ namespace qx { -SimulationResult::SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots) +SimulationResult::SimulationResult(std::uint64_t requestedShots, std::uint64_t doneShots, + RegisterManager const& registerManager) : shotsRequested{ requestedShots } , shotsDone{ doneShots } + , qubitRegister{ registerManager.get_qubit_register() } + , bitRegister{ registerManager.get_bit_register() } {} +std::uint8_t SimulationResult::getQubitState(state_string_t const& stateString, std::string const& qubitVariableName, + std::optional subIndex) { + auto index = qubitRegister.at(qubitVariableName, subIndex); + return static_cast(stateString[stateString.size() - index - 1] - '0'); +} + +std::uint8_t SimulationResult::getBitMeasurement(state_string_t const& stateString, std::string const& bitVariableName, + std::optional subIndex) { + auto index = bitRegister.at(bitVariableName, subIndex); + return static_cast(stateString[stateString.size() - index - 1] - '0'); +} + std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationResult) { fmt::print(os, "State:\n"); for (auto const &superposedState : simulationResult.state) { @@ -33,6 +48,17 @@ std::ostream &operator<<(std::ostream &os, const SimulationResult &simulationRes simulationResult.shotsDone, static_cast(measurement.count) / static_cast(simulationResult.shotsDone)); } + fmt::print(os, "Bit measurements:\n"); + for (auto const &bitMeasurement : simulationResult.bitMeasurements) { + fmt::print(os, "\t{1} {2}/{3} (count/shots % = {4:.{0}f})\n", + config::OUTPUT_DECIMALS, + bitMeasurement.state, + bitMeasurement.count, + simulationResult.shotsDone, + static_cast(bitMeasurement.count) / static_cast(simulationResult.shotsDone)); + } + fmt::print(os, "Qubit register:\n\t{}\n", simulationResult.qubitRegister); + fmt::print(os, "Bit register:\n\t{}", simulationResult.bitRegister); return os; } @@ -47,10 +73,17 @@ void SimulationResultAccumulator::appendMeasurement(core::BasisVector const& mea measurementsCount++; } -SimulationResult SimulationResultAccumulator::getSimulationResult() { +void SimulationResultAccumulator::appendBitMeasurement(core::BitMeasurementRegister const& bitMeasurement) { + assert(bitMeasurements.size() < (static_cast(1) << state.getNumberOfQubits())); + auto bitMeasuredStateString{ fmt::format("{}", bitMeasurement) }; + bitMeasurements[bitMeasuredStateString]++; + bitMeasurementsCount++; +} + +SimulationResult SimulationResultAccumulator::getSimulationResult(RegisterManager const& registerManager) { assert(measurementsCount > 0); - SimulationResult simulationResult{ measurementsCount, measurementsCount }; + SimulationResult simulationResult{ measurementsCount, measurementsCount, registerManager }; forAllNonZeroStates( [this, &simulationResult](core::BasisVector const& superposedState, core::SparseComplex const& sparseComplex) { @@ -64,8 +97,11 @@ SimulationResult SimulationResultAccumulator::getSimulationResult() { for (auto const& [stateString, count] : measurements) { simulationResult.measurements.push_back(Measurement{ stateString, count }); } + for (auto const& [stateString, count] : bitMeasurements) { + simulationResult.bitMeasurements.push_back(Measurement{ stateString, count }); + } return simulationResult; } -} // namespace qx +} // namespace qx \ No newline at end of file diff --git a/src/qx/Simulator.cpp b/src/qx/Simulator.cpp index b4bec56d..12637e08 100644 --- a/src/qx/Simulator.cpp +++ b/src/qx/Simulator.cpp @@ -67,7 +67,10 @@ execute( try { auto register_manager = RegisterManager{ program }; - auto quantumState = core::QuantumState{ register_manager.get_qubit_register_size() }; + auto quantumState = core::QuantumState{ + register_manager.get_qubit_register_size(), + register_manager.get_bit_register_size() + }; auto circuit = Circuit{ program, register_manager }; auto simulationResultAccumulator = SimulationResultAccumulator{ quantumState }; @@ -75,9 +78,10 @@ execute( quantumState.reset(); circuit.execute(quantumState, std::monostate{}); simulationResultAccumulator.appendMeasurement(quantumState.getMeasurementRegister()); + simulationResultAccumulator.appendBitMeasurement(quantumState.getBitMeasurementRegister()); } - return simulationResultAccumulator.getSimulationResult(); + return simulationResultAccumulator.getSimulationResult(register_manager); } catch (const SimulationError &err) { return err; } diff --git a/src/qx/V3xLibqasmInterface.cpp b/src/qx/V3xLibqasmInterface.cpp index 402b6bc5..1fdbf841 100644 --- a/src/qx/V3xLibqasmInterface.cpp +++ b/src/qx/V3xLibqasmInterface.cpp @@ -7,4 +7,8 @@ bool is_qubit_variable(const V3Variable &variable) { return variable.typ->as_qubit() || variable.typ->as_qubit_array(); } +bool is_bit_variable(const V3Variable &variable) { + return variable.typ->as_bit() || variable.typ->as_bit_array(); +} + } // namespace qx diff --git a/test/ErrorModelsTest.cpp b/test/ErrorModelsTest.cpp index d837c281..13ed6938 100644 --- a/test/ErrorModelsTest.cpp +++ b/test/ErrorModelsTest.cpp @@ -27,7 +27,7 @@ class ErrorModelsTest : public ::testing::Test { } private: - core::QuantumState state{ 3 }; // Using a mock or a TestQuantumState would be beneficial here. + core::QuantumState state{ 3, 3 }; // Using a mock or a TestQuantumState would be beneficial here. }; TEST_F(ErrorModelsTest, depolarizing_channel__probability_1) { diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 173bd965..4cb12786 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -214,4 +214,30 @@ b1 = measure q1 EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); } +TEST_F(IntegrationTest, bit_measurement_register) { + std::size_t iterations = 10'000; + auto cqasm = R"( +version 3.0 + +qubit[2] qq +X qq[0] +bit[2] bb +bb[0] = measure qq[0] + +qubit q +H q +CNOT q, qq[0] +bit b +b = measure qq[0] +)"; + auto actual = runFromString(cqasm, iterations); + + auto error = static_cast(static_cast(iterations)/2 * 0.05); + EXPECT_EQ(actual.bitMeasurements.size(), 2); + for (auto const& bitMeasurement : actual.bitMeasurements) { + EXPECT_EQ(actual.getBitMeasurement(bitMeasurement.state, "bb", 0), 1); + EXPECT_LT(std::abs(static_cast(iterations/2 - bitMeasurement.count)), error); + } +} + } // namespace qx diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index d6ea9e6a..2fe59a3c 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -32,7 +32,7 @@ class QuantumStateTest : public ::testing::Test { }; TEST_F(QuantumStateTest, apply_identity) { - QuantumState victim{ 3 }; + QuantumState victim{ 3, 3 }; EXPECT_EQ(victim.getNumberOfQubits(), 3); checkEq(victim, {1, 0, 0, 0, 0, 0, 0, 0}); @@ -44,7 +44,7 @@ TEST_F(QuantumStateTest, apply_identity) { } TEST_F(QuantumStateTest, apply_hadamard) { - QuantumState victim{ 3 }; + QuantumState victim{ 3, 3 }; EXPECT_EQ(victim.getNumberOfQubits(), 3); checkEq(victim, {1, 0, 0, 0, 0, 0, 0, 0}); @@ -58,31 +58,31 @@ TEST_F(QuantumStateTest, apply_hadamard) { } TEST_F(QuantumStateTest, apply_cnot) { - QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); victim.apply<2>(gates::CNOT, std::array{QubitIndex{1}, QubitIndex{0}}); checkEq(victim, {0, 0, std::sqrt(1 - std::pow(0.123, 2)), 0.123}); } TEST_F(QuantumStateTest, measure_on_non_superposed_state) { - QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.measure(QubitIndex{1}, []() { return 0.9485; }); + QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + victim.measure(QubitIndex{1}, BitIndex{1}, []() { return 0.9485; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); - victim.measure(QubitIndex{1}, []() { return 0.045621; }); + victim.measure(QubitIndex{1}, BitIndex{0}, []() { return 0.045621; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("10")); } TEST_F(QuantumStateTest, measure_on_superposed_state__case_0) { - QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.measure(QubitIndex{0}, []() { return 0.994; }); + QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + victim.measure(QubitIndex{0}, BitIndex{0}, []() { return 0.994; }); checkEq(victim, {0, 0, 1, 0}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } TEST_F(QuantumStateTest, measure_on_superposed_state__case_1) { - QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.measure(QubitIndex{0}, []() { return 0.254; }); + QuantumState victim{ 2, 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + victim.measure(QubitIndex{0}, BitIndex{0}, []() { return 0.254; }); checkEq(victim, {0, 0, 0, 1}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("01")); } From cd8898cc521ce6b2474fb37f4bbab110b8ec966a Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 13 Aug 2024 18:13:50 +0200 Subject: [PATCH 35/56] Fix compilation warning. --- include/qx/Gates.hpp | 33 ++++++--------------------------- src/qx/Gates.cpp | 32 +++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/include/qx/Gates.hpp b/include/qx/Gates.hpp index be2e63a0..95a513f8 100644 --- a/include/qx/Gates.hpp +++ b/include/qx/Gates.hpp @@ -37,30 +37,15 @@ static __CONSTEXPR__ UnitaryMatrix<2> static __CONSTEXPR__ UnitaryMatrix<2> TDAG = T.dagger(); -static __CONSTEXPR__ UnitaryMatrix<2> RX(double theta) { - return UnitaryMatrix<2>( - {{{std::cos(theta / 2), -1i * std::sin(theta / 2)}, - {-1i * std::sin(theta / 2), std::cos(theta / 2)}}}); -} +__CONSTEXPR__ UnitaryMatrix<2> RX(double theta); +__CONSTEXPR__ UnitaryMatrix<2> RY(double theta); +__CONSTEXPR__ UnitaryMatrix<2> RZ(double theta); static __CONSTEXPR__ auto X90 = RX(PI / 2); -static __CONSTEXPR__ auto MX90 = RX(-PI / 2); - -static __CONSTEXPR__ UnitaryMatrix<2> RY(double theta) { - return UnitaryMatrix<2>({{{std::cos(theta / 2), -std::sin(theta / 2)}, - {std::sin(theta / 2), std::cos(theta / 2)}}}); -} - static __CONSTEXPR__ auto Y90 = RY(PI / 2); -static __CONSTEXPR__ auto MY90 = RY(-PI / 2); - -static __CONSTEXPR__ UnitaryMatrix<2> RZ(double theta) { - return UnitaryMatrix<2>( - {{{std::cos(theta / 2) - 1i * std::sin(theta / 2), 0}, - {0, std::cos(theta / 2) + 1i * std::sin(theta / 2)}}}); -} - static __CONSTEXPR__ auto Z90 = RZ(PI / 2); +static __CONSTEXPR__ auto MX90 = RX(-PI / 2); +static __CONSTEXPR__ auto MY90 = RY(-PI / 2); static __CONSTEXPR__ auto MZ90 = RZ(-PI / 2); static __CONSTEXPR__ UnitaryMatrix<2> @@ -75,13 +60,7 @@ static __CONSTEXPR__ UnitaryMatrix<4> static __CONSTEXPR__ UnitaryMatrix<4> CZ({{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, -1}}}); -static __CONSTEXPR__ UnitaryMatrix<4> CR(double theta) { - return UnitaryMatrix<4>( - {{{1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {0, 0, 0, std::cos(theta) + 1i * std::sin(theta)}}}); -} +__CONSTEXPR__ UnitaryMatrix<4> CR(double theta); static __CONSTEXPR__ UnitaryMatrix<8> TOFFOLI({{{1, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 0, 0}, diff --git a/src/qx/Gates.cpp b/src/qx/Gates.cpp index 5af553af..64a7ed90 100644 --- a/src/qx/Gates.cpp +++ b/src/qx/Gates.cpp @@ -1 +1,31 @@ -#include "qx/Gates.hpp" \ No newline at end of file +#include "qx/Gates.hpp" + + +namespace qx::gates { + +__CONSTEXPR__ UnitaryMatrix<2> RX(double theta) { + return UnitaryMatrix<2>( + {{{std::cos(theta / 2), -1i * std::sin(theta / 2)}, + {-1i * std::sin(theta / 2), std::cos(theta / 2)}}}); +} + +__CONSTEXPR__ UnitaryMatrix<2> RY(double theta) { + return UnitaryMatrix<2>({{{std::cos(theta / 2), -std::sin(theta / 2)}, + {std::sin(theta / 2), std::cos(theta / 2)}}}); +} + +__CONSTEXPR__ UnitaryMatrix<2> RZ(double theta) { + return UnitaryMatrix<2>( + {{{std::cos(theta / 2) - 1i * std::sin(theta / 2), 0}, + {0, std::cos(theta / 2) + 1i * std::sin(theta / 2)}}}); +} + +__CONSTEXPR__ UnitaryMatrix<4> CR(double theta) { + return UnitaryMatrix<4>( + {{{1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, std::cos(theta) + 1i * std::sin(theta)}}}); +} + +} // namespace qx::gates From 8857a044b0d36712d7ec7e99c2bc3c536406aad2 Mon Sep 17 00:00:00 2001 From: rturrado Date: Tue, 13 Aug 2024 18:27:49 +0200 Subject: [PATCH 36/56] Add reset and reset all instructions. Rename QuantumState.measure to QuantumState.apply_measure. Change QuantumState.apply_measure to return the measured state. Add QuantumState.apply_reset and QuantumState.apply_reset_all. QuantumState.apply_reset(qubitIndex): - invokes apply_measure(qubitIndex), and - if the outcome of the measurement was 1, applies a Pauli X on the state. QuantumState.apply_reset_all just invokes QuantumState.reset, which: - sets all the qubits in state 00...000, - and resets the measurement register. Add QuantumStateTest.reset__case_0 and reset__case_1. TODO: check tests. TODO: add reset_all tests. --- docs/manual/usage.rst | 2 +- include/qx/InstructionExecutor.hpp | 2 ++ include/qx/Instructions.hpp | 9 +++++++++ include/qx/QuantumState.hpp | 20 +++++++++++++++++++- src/qx/InstructionExecutor.cpp | 10 +++++++++- src/qx/QuantumState.cpp | 10 +++++++++- test/QuantumStateTest.cpp | 22 ++++++++++++++++++---- 7 files changed, 67 insertions(+), 8 deletions(-) diff --git a/docs/manual/usage.rst b/docs/manual/usage.rst index 8cc79b15..04d0a337 100644 --- a/docs/manual/usage.rst +++ b/docs/manual/usage.rst @@ -80,7 +80,7 @@ Using a constant seed for random number generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default QX-simulator will generate different random numbers for different executions of a given circuit. -This means that ``measure`` and error models will make simulation results non-deterministic. +This means that ``measure``, ``reset``, and error models will make simulation results non-deterministic. In some cases this is not desired. To make the output of the simulator deterministic over different runs, you can pass a constant ``seed`` parameter: diff --git a/include/qx/InstructionExecutor.hpp b/include/qx/InstructionExecutor.hpp index a5ec969c..a285b794 100644 --- a/include/qx/InstructionExecutor.hpp +++ b/include/qx/InstructionExecutor.hpp @@ -13,6 +13,8 @@ class InstructionExecutor { explicit InstructionExecutor(core::QuantumState &s); void operator()(Measure const &m); + void operator()(Reset const &m); + void operator()(ResetAll const &m); void operator()(MeasurementRegisterOperation const &op); template diff --git a/include/qx/Instructions.hpp b/include/qx/Instructions.hpp index bb845e08..3664b8c6 100644 --- a/include/qx/Instructions.hpp +++ b/include/qx/Instructions.hpp @@ -17,6 +17,13 @@ struct Measure { core::QubitIndex qubitIndex{}; }; +struct Reset { + core::QubitIndex qubitIndex{}; +}; + +struct ResetAll { +}; + struct MeasurementRegisterOperation { std::function operation; }; @@ -30,6 +37,8 @@ struct Unitary { using Instruction = std::variant< Measure, + Reset, + ResetAll, MeasurementRegisterOperation, Unitary<1>, Unitary<2>, diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 22c18e63..8ebd50f6 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -11,6 +11,7 @@ #include "qx/Core.hpp" // BasisVector, QubitIndex #include "qx/CompileTimeConfiguration.hpp" // MAX_QUBIT_NUMBER #include "qx/DenseUnitaryMatrix.hpp" +#include "qx/Gates.hpp" #include "qx/RegisterManager.hpp" #include "qx/SparseArray.hpp" @@ -49,6 +50,9 @@ void applyImpl(DenseUnitaryMatrix<1 << NumberOfOperands> const &matrix, class QuantumState { void checkQuantumState(); + void resetData(); + void resetMeasurementRegister(); + public: QuantumState(std::size_t qubit_register_size); QuantumState(std::size_t qubit_register_size, @@ -88,11 +92,25 @@ class QuantumState { // measuredState will be true if we measured a 1, or false if we measured a 0 template - void measure(QubitIndex qubitIndex, F &&randomGenerator) { + bool apply_measure(QubitIndex qubitIndex, F &&randomGenerator) { auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); collapseQubitState(qubitIndex, measuredState, probabilityOfMeasuringOne); measurementRegister.set(qubitIndex.value, measuredState); + return measuredState; + } + + // reset performs a measurement and a conditional Pauli X based on the outcome of the measurement + template + void apply_reset(QubitIndex qubitIndex, F &&randomGenerator) { + if (apply_measure(qubitIndex, randomGenerator)) { + auto operand = std::array{ { qubitIndex } }; + apply(gates::X, operand); + } + } + + void apply_reset_all() { + resetData(); } private: diff --git a/src/qx/InstructionExecutor.cpp b/src/qx/InstructionExecutor.cpp index 7addf2bd..6f5d960b 100644 --- a/src/qx/InstructionExecutor.cpp +++ b/src/qx/InstructionExecutor.cpp @@ -9,7 +9,15 @@ InstructionExecutor::InstructionExecutor(core::QuantumState &s) {} void InstructionExecutor::operator()(Measure const &m) { - quantumState.measure(m.qubitIndex, &random::randomZeroOneDouble); + quantumState.apply_measure(m.qubitIndex, &random::randomZeroOneDouble); +} + +void InstructionExecutor::operator()(Reset const &r) { + quantumState.apply_reset(r.qubitIndex, &random::randomZeroOneDouble); +} + +void InstructionExecutor::operator()(ResetAll const &/* r */) { + quantumState.apply_reset_all(); } void InstructionExecutor::operator()(MeasurementRegisterOperation const &op) { diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index 725b6bc8..c88613c1 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -42,12 +42,20 @@ QuantumState::QuantumState(std::size_t qubit_register_size, return isNull(data.norm() - 1.); } -void QuantumState::reset() { +void QuantumState::resetData() { data.clear(); data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 +} + +void QuantumState::resetMeasurementRegister() { measurementRegister.reset(); } +void QuantumState::reset() { + resetData(); + resetMeasurementRegister(); +} + [[nodiscard]] const BasisVector& QuantumState::getMeasurementRegister() const { return measurementRegister; } diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index d6ea9e6a..86f359f2 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -66,25 +66,39 @@ TEST_F(QuantumStateTest, apply_cnot) { TEST_F(QuantumStateTest, measure_on_non_superposed_state) { QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.measure(QubitIndex{1}, []() { return 0.9485; }); + victim.apply_measure(QubitIndex{ 1 }, []() { return 0.9485; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); - victim.measure(QubitIndex{1}, []() { return 0.045621; }); + victim.apply_measure(QubitIndex{ 1 }, []() { return 0.045621; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("10")); } TEST_F(QuantumStateTest, measure_on_superposed_state__case_0) { QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.measure(QubitIndex{0}, []() { return 0.994; }); + victim.apply_measure(QubitIndex{ 0 }, []() { return 0.994; }); checkEq(victim, {0, 0, 1, 0}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } TEST_F(QuantumStateTest, measure_on_superposed_state__case_1) { QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.measure(QubitIndex{0}, []() { return 0.254; }); + victim.apply_measure(QubitIndex{ 0 }, []() { return 0.254; }); checkEq(victim, {0, 0, 0, 1}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("01")); } +TEST_F(QuantumStateTest, reset__case_0) { + // Reset leads to a non-deterministic global quantum state because of the state collapse + QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + victim.apply_reset(QubitIndex{ 0 }, []() { return 0.994; }); + checkEq(victim, {1, 0, 0, 0}); +} + +TEST_F(QuantumStateTest, reset__case_1) { + // Prep leads to a non-deterministic global quantum state because of the state collapse + QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + victim.apply_reset(QubitIndex{ 0 }, []() { return 0.245; }); + checkEq(victim, {0, 0, 1, 0}); +} + } // namespace qx::core From 542f5db98ffca684f016f436e7b08ba3448ec8ab Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 14 Aug 2024 19:03:03 +0200 Subject: [PATCH 37/56] Change QuantumState.apply_reset so that it does not update the measurement register. Add QuantumStateTest.reset__all. Add IntegrationTest.reset__*. Add QuantumState.toVector and operator<<(QuantumState). Add SparseArray.toVector and operator<<(SparseArray). TODO: update to fmt/11.0.2 (to print vector of complex numbers). TODO: update to libqasm/0.6.7 (so that integration tests understand the reset instruction). --- include/qx/QuantumState.hpp | 16 ++++++-- include/qx/SparseArray.hpp | 7 ++++ src/qx/QuantumState.cpp | 13 +++++- src/qx/SparseArray.cpp | 7 +++- test/IntegrationTest.cpp | 81 +++++++++++++++++++++++++++++++++++++ test/QuantumStateTest.cpp | 23 +++++------ 6 files changed, 129 insertions(+), 18 deletions(-) diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 8ebd50f6..84c2a388 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -3,7 +3,9 @@ #include #include // size_t #include // norm +#include #include +#include #include #include #include // invoke, pair @@ -59,6 +61,7 @@ class QuantumState { std::initializer_list>> values); [[nodiscard]] std::size_t getNumberOfQubits() const; [[nodiscard]] bool isNormalized(); + [[nodiscard]] std::vector> toVector() const; void reset(); template @@ -92,18 +95,21 @@ class QuantumState { // measuredState will be true if we measured a 1, or false if we measured a 0 template - bool apply_measure(QubitIndex qubitIndex, F &&randomGenerator) { + void apply_measure(QubitIndex qubitIndex, F &&randomGenerator) { auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); collapseQubitState(qubitIndex, measuredState, probabilityOfMeasuringOne); measurementRegister.set(qubitIndex.value, measuredState); - return measuredState; } // reset performs a measurement and a conditional Pauli X based on the outcome of the measurement + // reset does not modify the measurement register template void apply_reset(QubitIndex qubitIndex, F &&randomGenerator) { - if (apply_measure(qubitIndex, randomGenerator)) { + auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); + auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); + collapseQubitState(qubitIndex, measuredState, probabilityOfMeasuringOne); + if (measuredState) { auto operand = std::array{ { qubitIndex } }; apply(gates::X, operand); } @@ -119,4 +125,8 @@ class QuantumState { BasisVector measurementRegister{}; }; +std::ostream& operator<<(std::ostream &os, const QuantumState &array); + } // namespace qx::core + +template <> struct fmt::formatter : fmt::ostream_formatter {}; diff --git a/include/qx/SparseArray.hpp b/include/qx/SparseArray.hpp index aeb34b31..8ede4fbd 100644 --- a/include/qx/SparseArray.hpp +++ b/include/qx/SparseArray.hpp @@ -4,7 +4,9 @@ #include // for_each, sort #include #include // size_t, uint64_t +#include #include // accumulate +#include #include // runtime_error #include // pair #include @@ -111,4 +113,9 @@ class SparseArray { MapBasisVectorToSparseComplex data_; }; +std::ostream& operator<<(std::ostream &os, const SparseArray &array); + } // namespace qx::core + +template <> struct fmt::formatter> : fmt::ostream_formatter {}; +template <> struct fmt::formatter : fmt::ostream_formatter {}; diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index c88613c1..f077650f 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include "qx/QuantumState.hpp" @@ -22,7 +23,7 @@ QuantumState::QuantumState(std::size_t qubit_register_size) : numberOfQubits{ qubit_register_size } , data{ static_cast(1) << numberOfQubits } { - data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 + resetData(); checkQuantumState(); } @@ -42,6 +43,10 @@ QuantumState::QuantumState(std::size_t qubit_register_size, return isNull(data.norm() - 1.); } +[[nodiscard]] std::vector> QuantumState::toVector() const { + return data.toVector(); +} + void QuantumState::resetData() { data.clear(); data[BasisVector{}] = SparseComplex{ 1. }; // start initialized in state 00...000 @@ -86,4 +91,8 @@ void QuantumState::collapseQubitState(QubitIndex qubitIndex, bool measuredState, data *= std::sqrt(1 / (measuredState ? probabilityOfMeasuringOne : (1 - probabilityOfMeasuringOne))); } +std::ostream& operator<<(std::ostream &os, const QuantumState &state) { + return os << fmt::format("[{}]", fmt::join(state.toVector(), ", ")); +} + } // namespace qx::core diff --git a/src/qx/SparseArray.cpp b/src/qx/SparseArray.cpp index bcab4a61..041f9fd0 100644 --- a/src/qx/SparseArray.cpp +++ b/src/qx/SparseArray.cpp @@ -1,7 +1,8 @@ #include "qx/SparseArray.hpp" #include -#include +#include +#include namespace qx::core { @@ -123,4 +124,8 @@ void SparseArray::cleanUpZeros() { zeroCounter_ = 0; } +std::ostream& operator<<(std::ostream &os, const SparseArray &array) { + return os << fmt::format("[{}]", fmt::join(array.toVector(), ", ")); +} + } // namespace qx::core diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 173bd965..3e8b9222 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -214,4 +214,85 @@ b1 = measure q1 EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); } +TEST_F(IntegrationTest, reset__x_measure_reset) { + std::size_t iterations = 10'000; + auto cqasm = R"( +version 3.0 + +qubit q +bit b +X q +b = measure q +reset q +)"; + auto actual = runFromString(cqasm, iterations); + + // b value should always be "1" because reset does not modify the measurement register + EXPECT_EQ(actual.measurements.size(), 1); + EXPECT_TRUE(actual.measurements[0].state == "1"); + EXPECT_EQ(actual.measurements[0].count, iterations); + + // q state should always be |0> because reset modifies the qubit state + EXPECT_EQ(actual.state[0].value, "1"); + EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); +} + +TEST_F(IntegrationTest, reset__x_cnot_reset_measure) { + std::size_t iterations = 10'000; + auto cqasm = R"( +version 3.0 + +qubit[2] q +bit[2] b +X q[0] +CNOT q[1], q[0] +reset q[0] +b = measure q +)"; + auto actual = runFromString(cqasm, iterations); + + // b value should always be "00" because q0 will always be in |0> state and q1 is entangled with it + EXPECT_EQ(actual.measurements.size(), 1); + EXPECT_TRUE(actual.measurements[0].state == "00"); + EXPECT_EQ(actual.measurements[0].count, iterations); + + // q state should always be |00> because reset sets q0 state to |0>, and q1 is entangled with it + EXPECT_EQ(actual.state.size(), 1); + EXPECT_EQ(actual.state[0].value, "00"); + EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); +} + +TEST_F(IntegrationTest, reset__x_cnot_measure_reset) { + std::size_t iterations = 10'000; + auto cqasm = R"( +version 3.0 + +qubit[2] q +bit[2] b +X q[0] +CNOT q[1], q[0] +b = measure q +reset q[0] +)"; + auto actual = runFromString(cqasm, iterations); + + // Expected b value should be "11" 50% of the cases and "00" 50% of the cases + // because the measure happens right after creating the Bell state, + // and reset does not modify the measurement register + auto error = static_cast(static_cast(iterations)/2 * 0.05); + EXPECT_EQ(actual.measurements.size(), 2); + EXPECT_TRUE(actual.measurements[0].state == "00" || actual.measurements[0].state == "10"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); + + // Expected q state should be |00>+|10> + // because the measure collapses the qubit states while at superposition, + // leaving them as |11>+|00>, but then reset sets q0 state to |0>. + EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); + EXPECT_EQ(actual.state, + (SimulationResult::State{ + { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, + { "10", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } + })); +} + } // namespace qx diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index 86f359f2..29559f62 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -33,27 +33,18 @@ class QuantumStateTest : public ::testing::Test { TEST_F(QuantumStateTest, apply_identity) { QuantumState victim{ 3 }; - EXPECT_EQ(victim.getNumberOfQubits(), 3); checkEq(victim, {1, 0, 0, 0, 0, 0, 0, 0}); - victim.apply<3>(DenseUnitaryMatrix<8>::identity(), std::array{QubitIndex{0}, QubitIndex{1}, QubitIndex{2}}); - checkEq(victim, {1, 0, 0, 0, 0, 0, 0, 0}); } TEST_F(QuantumStateTest, apply_hadamard) { QuantumState victim{ 3 }; - EXPECT_EQ(victim.getNumberOfQubits(), 3); checkEq(victim, {1, 0, 0, 0, 0, 0, 0, 0}); - - victim.apply<1>( - DenseUnitaryMatrix<2>({{{1 / std::sqrt(2), 1 / std::sqrt(2)}, - {1 / std::sqrt(2), -1 / std::sqrt(2)}}}), - std::array{QubitIndex{1}}); - + victim.apply<1>(gates::H, std::array{QubitIndex{1}}); checkEq(victim, {1 / std::sqrt(2), 0, 1 / std::sqrt(2), 0, 0, 0, 0, 0}); } @@ -87,18 +78,26 @@ TEST_F(QuantumStateTest, measure_on_superposed_state__case_1) { EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("01")); } +// Reset leads to a non-deterministic global quantum state because of the state collapse TEST_F(QuantumStateTest, reset__case_0) { - // Reset leads to a non-deterministic global quantum state because of the state collapse QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; victim.apply_reset(QubitIndex{ 0 }, []() { return 0.994; }); checkEq(victim, {1, 0, 0, 0}); + EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } TEST_F(QuantumStateTest, reset__case_1) { - // Prep leads to a non-deterministic global quantum state because of the state collapse QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; victim.apply_reset(QubitIndex{ 0 }, []() { return 0.245; }); checkEq(victim, {0, 0, 1, 0}); + EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); +} + +TEST_F(QuantumStateTest, reset__all) { + QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + victim.apply_reset_all(); + checkEq(victim, QuantumState{ 2 }.toVector()); + EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } } // namespace qx::core From d68752e4ae2b74e2f225977aa4514c50e51f0773 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 15 Aug 2024 11:18:06 +0200 Subject: [PATCH 38/56] Updated integration tests. --- test/IntegrationTest.cpp | 58 +++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 3e8b9222..767aeed2 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -227,14 +227,14 @@ reset q )"; auto actual = runFromString(cqasm, iterations); - // b value should always be "1" because reset does not modify the measurement register + // Expected q state should always be |0> because reset modifies the qubit state + EXPECT_EQ(actual.state[0].value, "1"); + EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); + + // Expected b value should always be "1" because reset does not modify the measurement register EXPECT_EQ(actual.measurements.size(), 1); EXPECT_TRUE(actual.measurements[0].state == "1"); EXPECT_EQ(actual.measurements[0].count, iterations); - - // q state should always be |0> because reset modifies the qubit state - EXPECT_EQ(actual.state[0].value, "1"); - EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); } TEST_F(IntegrationTest, reset__x_cnot_reset_measure) { @@ -245,21 +245,28 @@ version 3.0 qubit[2] q bit[2] b X q[0] -CNOT q[1], q[0] +CNOT q[0], q[1] reset q[0] b = measure q )"; auto actual = runFromString(cqasm, iterations); - // b value should always be "00" because q0 will always be in |0> state and q1 is entangled with it - EXPECT_EQ(actual.measurements.size(), 1); - EXPECT_TRUE(actual.measurements[0].state == "00"); - EXPECT_EQ(actual.measurements[0].count, iterations); - - // q state should always be |00> because reset sets q0 state to |0>, and q1 is entangled with it - EXPECT_EQ(actual.state.size(), 1); - EXPECT_EQ(actual.state[0].value, "00"); + // Expected q state should be |00>+|10> + // State is |00>+|11> after creating the Bell state + // Then reset sets q to |0>, leaving the state as |00>+|10> + // And that is what is measured EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); + EXPECT_EQ(actual.state, + (SimulationResult::State{ + { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, + { "10", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } + })); + + // Expected b value should be "00" 50% of the cases and "10" 50% of the cases + auto error = static_cast(static_cast(iterations)/2 * 0.05); + EXPECT_EQ(actual.measurements.size(), 2); + EXPECT_TRUE(actual.measurements[0].state == "00" || actual.measurements[0].state == "10"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); } TEST_F(IntegrationTest, reset__x_cnot_measure_reset) { @@ -270,29 +277,30 @@ version 3.0 qubit[2] q bit[2] b X q[0] -CNOT q[1], q[0] +CNOT q[0], q[1] b = measure q reset q[0] )"; auto actual = runFromString(cqasm, iterations); - // Expected b value should be "11" 50% of the cases and "00" 50% of the cases - // because the measure happens right after creating the Bell state, - // and reset does not modify the measurement register - auto error = static_cast(static_cast(iterations)/2 * 0.05); - EXPECT_EQ(actual.measurements.size(), 2); - EXPECT_TRUE(actual.measurements[0].state == "00" || actual.measurements[0].state == "10"); - EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); - // Expected q state should be |00>+|10> - // because the measure collapses the qubit states while at superposition, - // leaving them as |11>+|00>, but then reset sets q0 state to |0>. + // State is |00>+|11> after creating the Bell state + // And that is what is measured + // Then reset sets q0 to |0>, leaving the state as |00>+|10> EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); EXPECT_EQ(actual.state, (SimulationResult::State{ { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, { "10", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } })); + + // Expected b value should be "00" 50% of the cases and "11" 50% of the cases + // because the measure happens right after creating the Bell state, + // and reset does not modify the measurement register + auto error = static_cast(static_cast(iterations)/2 * 0.05); + EXPECT_EQ(actual.measurements.size(), 2); + EXPECT_TRUE(actual.measurements[0].state == "00" || actual.measurements[0].state == "11"); + EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); } } // namespace qx From 3a821691631f3433a299a0371f578e03213bd60f Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 15 Aug 2024 11:22:44 +0200 Subject: [PATCH 39/56] Temporarily disable integration tests. Since they need a libqasm library that supports the reset instruction. --- test/IntegrationTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 767aeed2..e65eac35 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -214,7 +214,7 @@ b1 = measure q1 EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); } -TEST_F(IntegrationTest, reset__x_measure_reset) { +TEST_F(IntegrationTest, DISABLED_reset__x_measure_reset) { std::size_t iterations = 10'000; auto cqasm = R"( version 3.0 @@ -237,7 +237,7 @@ reset q EXPECT_EQ(actual.measurements[0].count, iterations); } -TEST_F(IntegrationTest, reset__x_cnot_reset_measure) { +TEST_F(IntegrationTest, DISABLED_reset__x_cnot_reset_measure) { std::size_t iterations = 10'000; auto cqasm = R"( version 3.0 @@ -269,7 +269,7 @@ b = measure q EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); } -TEST_F(IntegrationTest, reset__x_cnot_measure_reset) { +TEST_F(IntegrationTest, DISABLED_reset__x_cnot_measure_reset) { std::size_t iterations = 10'000; auto cqasm = R"( version 3.0 From e7759346850c5fc6543b5e8210fea7a3f52d373d Mon Sep 17 00:00:00 2001 From: rturrado Date: Fri, 16 Aug 2024 00:51:19 +0200 Subject: [PATCH 40/56] Remove Gates.cpp. Change Gates.hpp back to its original form, with some aesthetic changes. Fix CR compilation warning by using [[maybe_unused]]. Add some debug info to CMakeLists.txt. --- CMakeLists.txt | 7 ++- include/qx/Gates.hpp | 119 +++++++++++++++++++++++++++++++------------ src/qx/Gates.cpp | 31 ----------- 3 files changed, 92 insertions(+), 65 deletions(-) delete mode 100644 src/qx/Gates.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b72a2636..b1f98960 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,7 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) # Dependencies # #=============================================================================# -find_package(absl) +find_package(absl REQUIRED) find_package(libqasm REQUIRED CONFIG) @@ -122,7 +122,6 @@ add_library(qx "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Circuit.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/ErrorModels.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/GateConvertor.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/Gates.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/InstructionExecutor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/OperandsHelper.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/qx/QuantumState.cpp" @@ -244,6 +243,10 @@ endif() message(STATUS "[${PROJECT_NAME}] Target include directories:\n" " CMAKE_CURRENT_SOURCE_DIR/include/: ${CMAKE_CURRENT_SOURCE_DIR}/include/\n" + " absl_INCLUDE_DIRS: ${absl_INCLUDE_DIRS}\n" + " fmt_INCLUDE_DIRS: ${fmt_INCLUDE_DIRS}\n" + " libqasm_INCLUDE_DIRS: ${libqasm_INCLUDE_DIRS}\n" + " range-v3_INCLUDE_DIRS: ${range-v3_INCLUDE_DIRS}\n" ) diff --git a/include/qx/Gates.hpp b/include/qx/Gates.hpp index 95a513f8..ae8db58f 100644 --- a/include/qx/Gates.hpp +++ b/include/qx/Gates.hpp @@ -5,7 +5,8 @@ namespace qx::gates { -template using UnitaryMatrix = core::DenseUnitaryMatrix; +template +using UnitaryMatrix = core::DenseUnitaryMatrix; using namespace std::complex_literals; @@ -22,24 +23,55 @@ static __CONSTEXPR__ double SQRT_2 = 1.414213562373095048801688724209698078L; static __CONSTEXPR__ UnitaryMatrix<2> IDENTITY = UnitaryMatrix<2>::identity(); -static __CONSTEXPR__ UnitaryMatrix<2> X({{{0, 1}, {1, 0}}}); +static __CONSTEXPR__ UnitaryMatrix<2> X{{{ + {0, 1}, + {1, 0} +}}}; -static __CONSTEXPR__ UnitaryMatrix<2> Y({{{0, -1i}, {1i, 0}}}); +static __CONSTEXPR__ UnitaryMatrix<2> Y{{{ + {0, -1i}, + {1i, 0} +}}}; -static __CONSTEXPR__ UnitaryMatrix<2> Z({{{1, 0}, {0, -1}}}); +static __CONSTEXPR__ UnitaryMatrix<2> Z{{{ + {1, 0}, + {0, -1} +}}}; -static __CONSTEXPR__ UnitaryMatrix<2> S({{{1, 0}, {0, 1i}}}); +static __CONSTEXPR__ UnitaryMatrix<2> S{{{ + {1, 0}, + {0, 1i} +}}}; static __CONSTEXPR__ UnitaryMatrix<2> SDAG = S.dagger(); -static __CONSTEXPR__ UnitaryMatrix<2> - T({{{1, 0}, {0, 1 / SQRT_2 + 1i / SQRT_2}}}); +static __CONSTEXPR__ UnitaryMatrix<2> T{{{ + {1, 0}, + {0, 1 / SQRT_2 + 1i / SQRT_2} +}}}; static __CONSTEXPR__ UnitaryMatrix<2> TDAG = T.dagger(); -__CONSTEXPR__ UnitaryMatrix<2> RX(double theta); -__CONSTEXPR__ UnitaryMatrix<2> RY(double theta); -__CONSTEXPR__ UnitaryMatrix<2> RZ(double theta); +static __CONSTEXPR__ UnitaryMatrix<2> RX(double theta) { + return UnitaryMatrix<2>{{{ + {std::cos(theta / 2), -1i * std::sin(theta / 2)}, + {-1i * std::sin(theta / 2), std::cos(theta / 2)} + }}}; +} + +static __CONSTEXPR__ UnitaryMatrix<2> RY(double theta) { + return UnitaryMatrix<2>{{{ + {std::cos(theta / 2), -std::sin(theta / 2)}, + {std::sin(theta / 2), std::cos(theta / 2)} + }}}; +} + +static __CONSTEXPR__ UnitaryMatrix<2> RZ(double theta) { + return UnitaryMatrix<2>{{{ + {std::cos(theta / 2) - 1i * std::sin(theta / 2), 0}, + {0, std::cos(theta / 2) + 1i * std::sin(theta / 2)} + }}}; +} static __CONSTEXPR__ auto X90 = RX(PI / 2); static __CONSTEXPR__ auto Y90 = RY(PI / 2); @@ -48,28 +80,51 @@ static __CONSTEXPR__ auto MX90 = RX(-PI / 2); static __CONSTEXPR__ auto MY90 = RY(-PI / 2); static __CONSTEXPR__ auto MZ90 = RZ(-PI / 2); -static __CONSTEXPR__ UnitaryMatrix<2> - H({{{1 / SQRT_2, 1 / SQRT_2}, {1 / SQRT_2, -1 / SQRT_2}}}); - -static __CONSTEXPR__ UnitaryMatrix<4> - CNOT({{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}}}); - -static __CONSTEXPR__ UnitaryMatrix<4> - SWAP({{{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}}); - -static __CONSTEXPR__ UnitaryMatrix<4> - CZ({{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, -1}}}); - -__CONSTEXPR__ UnitaryMatrix<4> CR(double theta); - -static __CONSTEXPR__ UnitaryMatrix<8> TOFFOLI({{{1, 0, 0, 0, 0, 0, 0, 0}, - {0, 1, 0, 0, 0, 0, 0, 0}, - {0, 0, 1, 0, 0, 0, 0, 0}, - {0, 0, 0, 1, 0, 0, 0, 0}, - {0, 0, 0, 0, 1, 0, 0, 0}, - {0, 0, 0, 0, 0, 1, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 1}, - {0, 0, 0, 0, 0, 0, 1, 0}}}); +static __CONSTEXPR__ UnitaryMatrix<2> H{{{ + {1 / SQRT_2, 1 / SQRT_2}, + {1 / SQRT_2, -1 / SQRT_2} +}}}; + +static __CONSTEXPR__ UnitaryMatrix<4> CNOT{{{ + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 0, 1}, + {0, 0, 1, 0} +}}}; + +static __CONSTEXPR__ UnitaryMatrix<4> SWAP{{{ + {1, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 1, 0, 0}, + {0, 0, 0, 1} +}}}; + +static __CONSTEXPR__ UnitaryMatrix<4> CZ{{{ + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, -1} +}}}; + +[[maybe_unused]] static __CONSTEXPR__ UnitaryMatrix<4> CR(double theta) { + return UnitaryMatrix<4>{{{ + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, std::cos(theta) + 1i * std::sin(theta)} + }}}; +} + +static __CONSTEXPR__ UnitaryMatrix<8> TOFFOLI{{{ + {1, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 0, 0, 0}, + {0, 0, 0, 0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 1}, + {0, 0, 0, 0, 0, 0, 1, 0} +}}}; // TODO: make this even more user-friendly with ctrl operator and multiplication by double. diff --git a/src/qx/Gates.cpp b/src/qx/Gates.cpp deleted file mode 100644 index 64a7ed90..00000000 --- a/src/qx/Gates.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "qx/Gates.hpp" - - -namespace qx::gates { - -__CONSTEXPR__ UnitaryMatrix<2> RX(double theta) { - return UnitaryMatrix<2>( - {{{std::cos(theta / 2), -1i * std::sin(theta / 2)}, - {-1i * std::sin(theta / 2), std::cos(theta / 2)}}}); -} - -__CONSTEXPR__ UnitaryMatrix<2> RY(double theta) { - return UnitaryMatrix<2>({{{std::cos(theta / 2), -std::sin(theta / 2)}, - {std::sin(theta / 2), std::cos(theta / 2)}}}); -} - -__CONSTEXPR__ UnitaryMatrix<2> RZ(double theta) { - return UnitaryMatrix<2>( - {{{std::cos(theta / 2) - 1i * std::sin(theta / 2), 0}, - {0, std::cos(theta / 2) + 1i * std::sin(theta / 2)}}}); -} - -__CONSTEXPR__ UnitaryMatrix<4> CR(double theta) { - return UnitaryMatrix<4>( - {{{1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {0, 0, 0, std::cos(theta) + 1i * std::sin(theta)}}}); -} - -} // namespace qx::gates From 1c0e083bb6fbce6010bb79ec490f4ee4c30a8032 Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 21 Aug 2024 11:17:56 +0200 Subject: [PATCH 41/56] Remove TODO at Gates.hpp. --- include/qx/Gates.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/qx/Gates.hpp b/include/qx/Gates.hpp index ae8db58f..d1bec182 100644 --- a/include/qx/Gates.hpp +++ b/include/qx/Gates.hpp @@ -126,8 +126,6 @@ static __CONSTEXPR__ UnitaryMatrix<8> TOFFOLI{{{ {0, 0, 0, 0, 0, 0, 1, 0} }}}; -// TODO: make this even more user-friendly with ctrl operator and multiplication by double. - #if !defined(_MSC_VER) && !defined(__clang__) static_assert(T * T == S); static_assert(H * H == UnitaryMatrix<2>::identity()); From 1850a92d68fe5daf8de92d1053c363ac108b33e1 Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 21 Aug 2024 18:35:25 +0200 Subject: [PATCH 42/56] Add fmt, libqasm, and range-v3 dependencies to CMakeLists.txt. --- CMakeLists.txt | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92bcda3e..90bb39c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,9 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) find_package(absl) find_package(Boost REQUIRED) -find_package(libqasm REQUIRED CONFIG) +find_package(fmt REQUIRED) +find_package(libqasm REQUIRED) +find_package(range-v3 REQUIRED) #=============================================================================# @@ -143,6 +145,9 @@ target_compile_features(qx PRIVATE target_include_directories(qx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/" "${Boost_INCLUDE_DIRS}" + "${fmt_INCLUDE_DIRS}" + "${libqasm_INCLUDE_DIRS}" + "${range-v3_INCLUDE_DIRS}" ) if(CMAKE_COMPILER_IS_GNUCXX) @@ -201,9 +206,11 @@ endif() # Configure, build, and link dependencies # #=============================================================================# -target_link_libraries(qx PUBLIC - absl::flat_hash_map - libqasm::libqasm +target_link_libraries(qx + PUBLIC absl::flat_hash_map + PUBLIC fmt::fmt + PUBLIC libqasm::libqasm + PUBLIC range-v3::range-v3 ) #=============================================================================# @@ -213,8 +220,8 @@ target_link_libraries(qx PUBLIC add_executable(qx-simulator "${CMAKE_CURRENT_SOURCE_DIR}/src/qx-simulator/Simulator.cpp" ) -target_link_libraries(qx-simulator PRIVATE - qx +target_link_libraries(qx-simulator + PRIVATE qx ) #=============================================================================# @@ -247,6 +254,9 @@ message(STATUS "[${PROJECT_NAME}] Target include directories:\n" " CMAKE_CURRENT_SOURCE_DIR/include/: ${CMAKE_CURRENT_SOURCE_DIR}/include/\n" " Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}\n" + " fmt_INCLUDE_DIRS: ${fmt_INCLUDE_DIRS}\n" + " libqasm_INCLUDE_DIRS: ${libqasm_INCLUDE_DIRS}\n" + " range-v3_INCLUDE_DIRS: ${range-v3_INCLUDE_DIRS}\n" ) From 2c634ef9f8fcbc60b5720d6443649fb574fef103 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 26 Aug 2024 17:44:17 +0200 Subject: [PATCH 43/56] Remove TODOs. They are now part of CQT-145 (Manage measurements and bitMeasurements). --- include/qx/QuantumState.hpp | 3 --- include/qx/SimulationResult.hpp | 4 ---- 2 files changed, 7 deletions(-) diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index d989daef..83fd02c8 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -102,9 +102,6 @@ class QuantumState { std::size_t numberOfQubits = 1; std::size_t numberOfBits = 1; SparseArray data; - // TODO: we are keeping a "double-entry bookkeeping" until we can get rid of measurements - // measurements needs to be replaced with bitMeasurements with the introduction of bit variables, - // but this replacement cannot be executed until all the QX simulator clients start using bitMeasurements BasisVector measurementRegister{}; BitMeasurementRegister bitMeasurementRegister{}; }; diff --git a/include/qx/SimulationResult.hpp b/include/qx/SimulationResult.hpp index 1e53364c..8a127822 100644 --- a/include/qx/SimulationResult.hpp +++ b/include/qx/SimulationResult.hpp @@ -96,10 +96,6 @@ class SimulationResultAccumulator { } core::QuantumState &state; - - // TODO: we are keeping a "double-entry bookkeeping" until we can get rid of measurements - // measurements needs to be replaced with bitMeasurements with the introduction of bit variables, - // but this replacement cannot be executed until all the QX simulator clients start using bitMeasurements absl::btree_map measurements; absl::btree_map bitMeasurements; From 8f15b590e2e8426f4bbcf8ba6636813abca4d90e Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 5 Sep 2024 02:54:49 +0200 Subject: [PATCH 44/56] Update Conan package versions to fmt/11.0.2, gtest/1.15.0, and libqasm/0.6.7. --- conanfile.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conanfile.py b/conanfile.py index db226524..1d42454d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -42,14 +42,14 @@ def _should_build_test(self): return not self.conf.get("tools.build:skip_test", default=True, check_type=bool) def build_requirements(self): - self.tool_requires("libqasm/0.6.6") + self.tool_requires("libqasm/0.6.7") if self._should_build_test: - self.test_requires("gtest/1.14.0") + self.test_requires("gtest/1.15.0") def requirements(self): self.requires("abseil/20230125.3", transitive_headers=True) - self.requires("fmt/10.2.1", transitive_headers=True) - self.requires("libqasm/0.6.6", transitive_headers=True) + self.requires("fmt/11.0.2", transitive_headers=True) + self.requires("libqasm/0.6.7", transitive_headers=True) self.requires("range-v3/0.12.0", transitive_headers=True) def config_options(self): From 9fe7f82bd95216aa4600bf7b9343d748530c03d5 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 5 Sep 2024 02:55:48 +0200 Subject: [PATCH 45/56] Add absl, fmt, libqasm, and range-v3 both to find_package and targe_link_libraries. --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1f98960..998d5933 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,7 +111,9 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) #=============================================================================# find_package(absl REQUIRED) -find_package(libqasm REQUIRED CONFIG) +find_package(fmt REQUIRED) +find_package(libqasm REQUIRED) +find_package(range-v3 REQUIRED) #=============================================================================# @@ -200,7 +202,9 @@ endif() target_link_libraries(qx PUBLIC absl::flat_hash_map + fmt::fmt libqasm::libqasm + range-v3::range-v3 ) #=============================================================================# From 1f9e5f3029c483d64ae87edbb5076758a7cab683 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 5 Sep 2024 02:57:35 +0200 Subject: [PATCH 46/56] Fix compilation errors when using fmt/11.0.2. Remove CLion warnings regarding unnecessary use of std::move. --- src/qx/SparseArray.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qx/SparseArray.cpp b/src/qx/SparseArray.cpp index 041f9fd0..422498df 100644 --- a/src/qx/SparseArray.cpp +++ b/src/qx/SparseArray.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -20,7 +21,7 @@ SparseComplex::SparseComplex(const SparseComplex &other) { } SparseComplex::SparseComplex(SparseComplex &&other) noexcept { - value = std::move(other.value); + value = other.value; } SparseComplex& SparseComplex::operator=(const SparseComplex &other) { @@ -32,7 +33,7 @@ SparseComplex& SparseComplex::operator=(const SparseComplex &other) { SparseComplex& SparseComplex::operator=(SparseComplex &&other) noexcept { if (std::abs(other.value) >= config::EPS) { - value = std::move(other.value); + value = other.value; } return *this; } From 1ce6b67d78d219363d55cb70526778411c718f1d Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 5 Sep 2024 03:03:49 +0200 Subject: [PATCH 47/56] Update Circuit::execute to execute reset and reset_all. Update GateConvertor::addGates to add Reset and ResetAll. Rename QuantumState::apply_reset and apply_reset_all to ApplyReset and ApplyResetAll. Rename QuantumState::collapseQubitState to updateDataAfterMeasurement. Add QuantumState::updateDataAfterReset. Add comments to QuantumState::updateDataAfterMeasurement and updateDataAfterReset. --- include/qx/QuantumState.hpp | 21 ++++-------- src/qx/Circuit.cpp | 4 +++ src/qx/GateConvertor.cpp | 42 ++++++++++++++++-------- src/qx/InstructionExecutor.cpp | 7 ++-- src/qx/QuantumState.cpp | 60 ++++++++++++++++++++++++++++++++-- 5 files changed, 99 insertions(+), 35 deletions(-) diff --git a/include/qx/QuantumState.hpp b/include/qx/QuantumState.hpp index 84c2a388..d349934b 100644 --- a/include/qx/QuantumState.hpp +++ b/include/qx/QuantumState.hpp @@ -91,31 +91,24 @@ class QuantumState { [[nodiscard]] const BasisVector &getMeasurementRegister() const; [[nodiscard]] double getProbabilityOfMeasuringOne(QubitIndex qubitIndex); [[nodiscard]] double getProbabilityOfMeasuringZero(QubitIndex qubitIndex); - void collapseQubitState(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne); + void updateDataAfterMeasurement(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne); + void updateDataAfterReset(QubitIndex qubitIndex); // measuredState will be true if we measured a 1, or false if we measured a 0 template - void apply_measure(QubitIndex qubitIndex, F &&randomGenerator) { + void applyMeasure(QubitIndex qubitIndex, F &&randomGenerator) { auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); - collapseQubitState(qubitIndex, measuredState, probabilityOfMeasuringOne); + updateDataAfterMeasurement(qubitIndex, measuredState, probabilityOfMeasuringOne); measurementRegister.set(qubitIndex.value, measuredState); } - // reset performs a measurement and a conditional Pauli X based on the outcome of the measurement // reset does not modify the measurement register - template - void apply_reset(QubitIndex qubitIndex, F &&randomGenerator) { - auto probabilityOfMeasuringOne = getProbabilityOfMeasuringOne(qubitIndex); - auto measuredState = (randomGenerator() < probabilityOfMeasuringOne); - collapseQubitState(qubitIndex, measuredState, probabilityOfMeasuringOne); - if (measuredState) { - auto operand = std::array{ { qubitIndex } }; - apply(gates::X, operand); - } + void applyReset(QubitIndex qubitIndex) { + updateDataAfterReset(qubitIndex); } - void apply_reset_all() { + void applyResetAll() { resetData(); } diff --git a/src/qx/Circuit.cpp b/src/qx/Circuit.cpp index bbbd79da..0bd39373 100644 --- a/src/qx/Circuit.cpp +++ b/src/qx/Circuit.cpp @@ -53,6 +53,10 @@ void Circuit::execute(core::QuantumState &quantumState, error_models::ErrorModel // std::visit(instructionExecutor, instruction); if (auto *measure = std::get_if(&instruction)) { instruction_executor(*measure); + } else if (auto *reset = std::get_if(&instruction)) { + instruction_executor(*reset); + } else if (auto *reset_all = std::get_if(&instruction)) { + instruction_executor(*reset_all); } else if (auto *classicalOp = std::get_if(&instruction)) { instruction_executor(*classicalOp); } else if (auto *instruction1 = std::get_if>(&instruction)) { diff --git a/src/qx/GateConvertor.cpp b/src/qx/GateConvertor.cpp index f41a9735..6c647d88 100644 --- a/src/qx/GateConvertor.cpp +++ b/src/qx/GateConvertor.cpp @@ -103,6 +103,16 @@ void GateConvertor::addGates(const V3Instruction &instruction) { gates::CZ, { operands_helper.get_register_operand(0), operands_helper.get_register_operand(1) } ); + } else if (name == "CR") { + addGates<2>( + gates::CR(operands_helper.get_float_operand(2)), + { operands_helper.get_register_operand(0), operands_helper.get_register_operand(1) } + ); + } else if (name == "CRk") { + addGates<2>( + gates::CR(static_cast(gates::PI) / std::pow(2, operands_helper.get_int_operand(2) - 1)), + { operands_helper.get_register_operand(0), operands_helper.get_register_operand(1) } + ); } else if (name == "SWAP") { addGates<2>( gates::SWAP, @@ -113,14 +123,14 @@ void GateConvertor::addGates(const V3Instruction &instruction) { gates::X90, { operands_helper.get_register_operand(0) } ); - } else if (name == "mX90") { + } else if (name == "Y90") { return addGates<1>( - gates::MX90, + gates::Y90, { operands_helper.get_register_operand(0) } ); - } else if (name == "Y90") { + } else if (name == "mX90") { return addGates<1>( - gates::Y90, + gates::MX90, { operands_helper.get_register_operand(0) } ); } else if (name == "mY90") { @@ -141,16 +151,20 @@ void GateConvertor::addGates(const V3Instruction &instruction) { }, controlBits); } - } else if (name == "CR") { - addGates<2>( - gates::CR(operands_helper.get_float_operand(2)), - { operands_helper.get_register_operand(0), operands_helper.get_register_operand(1) } - ); - } else if (name == "CRk") { - addGates<2>( - gates::CR(static_cast(gates::PI) / std::pow(2, operands_helper.get_int_operand(2) - 1)), - { operands_helper.get_register_operand(0), operands_helper.get_register_operand(1) } - ); + } else if (name == "reset") { + auto controlBits = std::make_shared>(); + if (instruction.operands.empty()) { + circuit_.add_instruction(ResetAll{}, controlBits); + } else { + const auto &qubit_indices = operands_helper.get_register_operand(0); + for (size_t i{ 0 }; i < qubit_indices.size(); ++i) { + circuit_.add_instruction( + Reset{ + core::QubitIndex{ static_cast(qubit_indices[i]->value) } + }, + controlBits); + } + } } else { throw GateConvertorError{ fmt::format("Unsupported gate or instruction: {}", name) }; } diff --git a/src/qx/InstructionExecutor.cpp b/src/qx/InstructionExecutor.cpp index 6f5d960b..c24088b2 100644 --- a/src/qx/InstructionExecutor.cpp +++ b/src/qx/InstructionExecutor.cpp @@ -9,15 +9,14 @@ InstructionExecutor::InstructionExecutor(core::QuantumState &s) {} void InstructionExecutor::operator()(Measure const &m) { - quantumState.apply_measure(m.qubitIndex, &random::randomZeroOneDouble); + quantumState.applyMeasure(m.qubitIndex, &random::randomZeroOneDouble); } void InstructionExecutor::operator()(Reset const &r) { - quantumState.apply_reset(r.qubitIndex, &random::randomZeroOneDouble); + quantumState.applyReset(r.qubitIndex); } -void InstructionExecutor::operator()(ResetAll const &/* r */) { - quantumState.apply_reset_all(); +void InstructionExecutor::operator()(ResetAll const &/* r */) { quantumState.applyResetAll(); } void InstructionExecutor::operator()(MeasurementRegisterOperation const &op) { diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index f077650f..a0344257 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -1,8 +1,10 @@ +#include "qx/QuantumState.hpp" + +#include #include +#include #include -#include "qx/QuantumState.hpp" - namespace qx::core { @@ -82,7 +84,23 @@ void QuantumState::reset() { // Update data after a measurement // // measuredState will be true if we measured a 1, or false if we measured a 0 -void QuantumState::collapseQubitState(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne) { +// +// Example: +// Given the following data representing a quantum state, +// where data is a map of basis vectors to amplitudes, +// basis vectors are shown below with qubit indices growing from right to left, +// and amplitudes are complex numbers: +// Basis vector: amplitude +// q1 q0: (real, imag) +// 0 0: ( 0.7, 0) +// 0 1: ( 0, 0) +// 1 0: ( 0, 0) +// 1 1: ( 0.7, 0) +// And considering qubit 0 was measured as 0. +// This function: +// 1) Erases all the entries in data for which qubit 0 is not 0, i.e., entry 11. +// 2) Normalizes data (all the squares of all the amplitudes add up to 1). +void QuantumState::updateDataAfterMeasurement(QubitIndex qubitIndex, bool measuredState, double probabilityOfMeasuringOne) { data.eraseIf([qubitIndex, measuredState](auto const &kv) { auto const &[basisVector, _] = kv; auto currentState = basisVector.test(qubitIndex.value); @@ -91,6 +109,42 @@ void QuantumState::collapseQubitState(QubitIndex qubitIndex, bool measuredState, data *= std::sqrt(1 / (measuredState ? probabilityOfMeasuringOne : (1 - probabilityOfMeasuringOne))); } +// Update data after a reset of a single qubit +// +// Example: +// Given the following data representing a quantum state, +// where data is a map of basis vectors to amplitudes, +// basis vectors are shown below with qubit indices growing from right to left, +// and amplitudes are complex numbers: +// Basis vector: amplitude +// q1 q0: (real, imag) +// 0 0: ( 0.7, 0) +// 0 1: ( 0, 0) +// 1 0: ( 0, 0) +// 1 1: ( 0.7, 0) +// And considering qubit 0 is the one being reset. +// This function would update data as follows: +// Basis vector: amplitude +// q1 q0: (real, imag) +// 0 0: ( 0.7, 0) +// 0 1: ( 0, 0) <-- 01 is reset to 00, 01 amplitude set to 0, old 01 amplitude added to 00 +// 1 0: ( 0.7, 0) +// 1 1: ( 0, 0) <-- 11 is reset to 10, 11 amplitude set to 0, old 11 amplitude added to 10 +void QuantumState::updateDataAfterReset(QubitIndex qubitIndex) { + auto newData = data; + for (auto const &[basisVector, amplitude]: data) { + if (basisVector.test(qubitIndex.value)) { + auto basisVectorAfterReset = basisVector; + basisVectorAfterReset.set(qubitIndex.value, false); + newData[basisVectorAfterReset].value = std::sqrt( + std::norm(newData[basisVectorAfterReset].value) + std::norm(amplitude.value) + ); + newData[basisVector].value = 0; + } + } + data = std::move(newData); +} + std::ostream& operator<<(std::ostream &os, const QuantumState &state) { return os << fmt::format("[{}]", fmt::join(state.toVector(), ", ")); } From 37457f1119847fcf36e7574568f81ebb9403375c Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 5 Sep 2024 03:30:57 +0200 Subject: [PATCH 48/56] Implement reset integration tests. Update reset quantum state tests. Rename measure quantum state tests. Minor changes and comments to integration and quantum state tests. --- test/IntegrationTest.cpp | 106 ++++++++++++++++++++++++-------------- test/QuantumStateTest.cpp | 38 ++++++-------- 2 files changed, 84 insertions(+), 60 deletions(-) diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index e65eac35..705664dd 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -29,10 +29,14 @@ qubit[2] q H q[0] CNOT q[0], q[1] )"; - auto actual = runFromString(cqasm, 1, "3.0"); + std::size_t iterations = 1; + auto actual = runFromString(cqasm, iterations, "3.0"); - EXPECT_EQ(actual.shotsRequested, 1); - EXPECT_EQ(actual.shotsDone, 1); + EXPECT_EQ(actual.shotsRequested, iterations); + EXPECT_EQ(actual.shotsDone, iterations); + + // Expected q state should be |00>+|11> + // State is |00>+|11> after creating the Bell state EXPECT_EQ(actual.state, (SimulationResult::State{ { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, @@ -48,10 +52,12 @@ qubit[6] q X q[0:2] CNOT q[0:2], q[3:5] )"; - auto actual = runFromString(cqasm, 2); + std::size_t iterations = 2; + auto actual = runFromString(cqasm, iterations); + // Expected q state should be |111111> EXPECT_EQ(actual.state, - (SimulationResult::State{ { "111111", core::Complex{ .real = 1, .imag = 0, .norm = 1 } } })); + (SimulationResult::State{ { "111111", core::Complex{ .real = 1, .imag = 0, .norm = 1 } } })); } TEST_F(IntegrationTest, too_many_qubits) { @@ -91,10 +97,15 @@ I q[0] CNOT q[0], q[1] I q[1] )"; - auto actual = runFromString(cqasm, 1, "3.0"); + std::size_t iterations = 1; + auto actual = runFromString(cqasm, iterations, "3.0"); + + EXPECT_EQ(actual.shotsRequested, iterations); + EXPECT_EQ(actual.shotsDone, iterations); - EXPECT_EQ(actual.shotsRequested, 1); - EXPECT_EQ(actual.shotsDone, 1); + // Expected q state should be |00>+|11> + // State is |00>+|11> after creating the Bell state + // Identity gates do not modify the state of the qubits EXPECT_EQ(actual.state, (SimulationResult::State{ { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, @@ -103,7 +114,6 @@ I q[1] } TEST_F(IntegrationTest, measure) { - std::size_t iterations = 10'000; auto cqasm = R"( version 3.0 @@ -116,18 +126,20 @@ CNOT q[1], q[2] b = measure q )"; + std::size_t iterations = 10'000; auto actual = runFromString(cqasm, iterations); + // Expected q state should be |001> or |111> + EXPECT_TRUE(actual.state[0].value.ends_with('1')); + EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); + + // Expected b value should be "001" 50% of the cases and "111" 50% of the cases auto error = static_cast(static_cast(iterations)/2 * 0.05); EXPECT_EQ(actual.measurements.size(), 2); EXPECT_EQ(actual.measurements[0].state, "001"); EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); EXPECT_EQ(actual.measurements[1].state, "111"); EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); - - // State could be 001 or 111 - EXPECT_TRUE(actual.state[0].value.ends_with('1')); - EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); } TEST_F(IntegrationTest, multiple_measure_instructions) { @@ -148,20 +160,20 @@ b[2] = measure q[2] std::size_t iterations = 10'000; auto actual = runFromString(cqasm, iterations); + // Expected q state should be |001> or |111> + EXPECT_TRUE(actual.state[0].value.ends_with('1')); + EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); + + // Expected b value should be "001" 50% of the cases and "111" 50% of the cases auto error = static_cast(static_cast(iterations)/2 * 0.05); EXPECT_EQ(actual.measurements.size(), 2); EXPECT_EQ(actual.measurements[0].state, "001"); EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); EXPECT_EQ(actual.measurements[1].state, "111"); EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); - - // State could be 001 or 111 - EXPECT_TRUE(actual.state[0].value.ends_with('1')); - EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); } TEST_F(IntegrationTest, mid_circuit_measure_instruction) { - std::size_t iterations = 10'000; auto cqasm = R"( version 3.0 @@ -175,9 +187,10 @@ H q[1] CNOT q[1], q[0] b = measure q )"; + std::size_t iterations = 10'000; auto actual = runFromString(cqasm, iterations); - // Expected output state: |00>+|11> or |01>+|10> + // Expected q state should be |00>+|11> or |01>+|10> auto error = static_cast(static_cast(iterations)/2 * 0.05); EXPECT_EQ(actual.measurements.size(), 2); EXPECT_TRUE(actual.measurements[0].state == "00" || actual.measurements[0].state == "01"); @@ -187,7 +200,6 @@ b = measure q } TEST_F(IntegrationTest, multiple_qubit_bit_definitions_and_mid_circuit_measure_instructions) { - std::size_t iterations = 10'000; auto cqasm = R"( version 3.0 @@ -203,9 +215,10 @@ bit b1 b0 = measure q0 b1 = measure q1 )"; + std::size_t iterations = 10'000; auto actual = runFromString(cqasm, iterations); - // Expected output state: |00>+|11> or |01>+|10> + // Expected q1-q0 state should be |00>+|11> or |01>+|10> auto error = static_cast(static_cast(iterations)/2 * 0.05); EXPECT_EQ(actual.measurements.size(), 2); EXPECT_TRUE(actual.measurements[0].state == "00" || actual.measurements[0].state == "01"); @@ -214,8 +227,7 @@ b1 = measure q1 EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[1].count)), error); } -TEST_F(IntegrationTest, DISABLED_reset__x_measure_reset) { - std::size_t iterations = 10'000; +TEST_F(IntegrationTest, reset__x_measure_reset) { auto cqasm = R"( version 3.0 @@ -225,10 +237,11 @@ X q b = measure q reset q )"; + std::size_t iterations = 10'000; auto actual = runFromString(cqasm, iterations); // Expected q state should always be |0> because reset modifies the qubit state - EXPECT_EQ(actual.state[0].value, "1"); + EXPECT_EQ(actual.state[0].value, "0"); EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); // Expected b value should always be "1" because reset does not modify the measurement register @@ -237,30 +250,49 @@ reset q EXPECT_EQ(actual.measurements[0].count, iterations); } -TEST_F(IntegrationTest, DISABLED_reset__x_cnot_reset_measure) { +TEST_F(IntegrationTest, reset__bell_state_then_reset) { std::size_t iterations = 10'000; auto cqasm = R"( version 3.0 qubit[2] q bit[2] b -X q[0] +H q[0] CNOT q[0], q[1] reset q[0] -b = measure q )"; auto actual = runFromString(cqasm, iterations); // Expected q state should be |00>+|10> // State is |00>+|11> after creating the Bell state // Then reset sets q to |0>, leaving the state as |00>+|10> - // And that is what is measured - EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); EXPECT_EQ(actual.state, (SimulationResult::State{ { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, { "10", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } })); +} + +TEST_F(IntegrationTest, reset__bell_state_then_reset_and_measure) { + std::size_t iterations = 10'000; + auto cqasm = R"( +version 3.0 + +qubit[2] q +bit[2] b +H q[0] +CNOT q[0], q[1] +reset q[0] +b = measure q +)"; + auto actual = runFromString(cqasm, iterations); + + // Expected q state should be |00>+|10> + // State is |00>+|11> after creating the Bell state + // Then reset sets q to |0>, leaving the state as |00>+|10> + // The measure provokes a collapse of the state to either |00> or |11> + EXPECT_TRUE(actual.state[0].value.ends_with('0')); + EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); // Expected b value should be "00" 50% of the cases and "10" 50% of the cases auto error = static_cast(static_cast(iterations)/2 * 0.05); @@ -269,30 +301,26 @@ b = measure q EXPECT_LT(std::abs(static_cast(iterations/2 - actual.measurements[0].count)), error); } -TEST_F(IntegrationTest, DISABLED_reset__x_cnot_measure_reset) { +TEST_F(IntegrationTest, reset__bell_state_then_measure_and_reset) { std::size_t iterations = 10'000; auto cqasm = R"( version 3.0 qubit[2] q bit[2] b -X q[0] +H q[0] CNOT q[0], q[1] b = measure q reset q[0] )"; auto actual = runFromString(cqasm, iterations); - // Expected q state should be |00>+|10> + // Expected q state should be |00> or |10> // State is |00>+|11> after creating the Bell state - // And that is what is measured - // Then reset sets q0 to |0>, leaving the state as |00>+|10> + // The measure provokes a collapse of the state to either |00> or |11> + // Then reset sets q0 to |0>, leaving the state as |00> or |10> + EXPECT_TRUE(actual.state[0].value.ends_with('0')); EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); - EXPECT_EQ(actual.state, - (SimulationResult::State{ - { "00", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } }, - { "10", core::Complex{ .real = 1 / std::sqrt(2), .imag = 0, .norm = 0.5 } } - })); // Expected b value should be "00" 50% of the cases and "11" 50% of the cases // because the measure happens right after creating the Bell state, diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index 29559f62..b09cc714 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -57,45 +57,41 @@ TEST_F(QuantumStateTest, apply_cnot) { TEST_F(QuantumStateTest, measure_on_non_superposed_state) { QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.apply_measure(QubitIndex{ 1 }, []() { return 0.9485; }); + victim.applyMeasure(QubitIndex{ 1 }, []() { return 0.9485; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); - victim.apply_measure(QubitIndex{ 1 }, []() { return 0.045621; }); + victim.applyMeasure(QubitIndex{ 1 }, []() { return 0.045621; }); checkEq(victim, {0, 0, 0.123, std::sqrt(1 - std::pow(0.123, 2))}); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("10")); } -TEST_F(QuantumStateTest, measure_on_superposed_state__case_0) { +TEST_F(QuantumStateTest, measure_on_superposed_state__measured_state_is_0) { + // The random generator function returns a number bigger than the probability of measuring 1, so we measure 0 + // 0.994 > 1 - 0.123^2 QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.apply_measure(QubitIndex{ 0 }, []() { return 0.994; }); - checkEq(victim, {0, 0, 1, 0}); + victim.applyMeasure(QubitIndex{ 0 }, []() { return 0.994; }); + checkEq(victim, {0, 0, 1, 0}); // 10 EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } -TEST_F(QuantumStateTest, measure_on_superposed_state__case_1) { +TEST_F(QuantumStateTest, measure_on_superposed_state__measured_state_is_1) { + // The random generator function returns a number smaller than the probability of measuring 1, so we measure 1 + // 0.254 < 1 - 0.123^2 QuantumState victim{ 2, {{"10", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.apply_measure(QubitIndex{ 0 }, []() { return 0.254; }); - checkEq(victim, {0, 0, 0, 1}); + victim.applyMeasure(QubitIndex{ 0 }, []() { return 0.254; }); + checkEq(victim, {0, 0, 0, 1}); // 11 EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("01")); } -// Reset leads to a non-deterministic global quantum state because of the state collapse -TEST_F(QuantumStateTest, reset__case_0) { +TEST_F(QuantumStateTest, reset) { QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.apply_reset(QubitIndex{ 0 }, []() { return 0.994; }); - checkEq(victim, {1, 0, 0, 0}); + victim.applyReset(QubitIndex{ 0 }); + checkEq(victim, {0.123, 0, std::sqrt(1 - std::pow(0.123, 2)), 0}); // 00 and 10 EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } -TEST_F(QuantumStateTest, reset__case_1) { +TEST_F(QuantumStateTest, reset_all) { QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.apply_reset(QubitIndex{ 0 }, []() { return 0.245; }); - checkEq(victim, {0, 0, 1, 0}); - EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); -} - -TEST_F(QuantumStateTest, reset__all) { - QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; - victim.apply_reset_all(); + victim.applyResetAll(); checkEq(victim, QuantumState{ 2 }.toVector()); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } From bce83531a9b5a348b9a49ef0fc959e18695b06ca Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 5 Sep 2024 14:02:53 +0200 Subject: [PATCH 49/56] Fix and update a couple of comments. --- src/qx/QuantumState.cpp | 4 ++-- test/IntegrationTest.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index a0344257..e5658798 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -127,9 +127,9 @@ void QuantumState::updateDataAfterMeasurement(QubitIndex qubitIndex, bool measur // Basis vector: amplitude // q1 q0: (real, imag) // 0 0: ( 0.7, 0) -// 0 1: ( 0, 0) <-- 01 is reset to 00, 01 amplitude set to 0, old 01 amplitude added to 00 +// 0 1: ( 0, 0) <-- 01 is reset to 00, old 01 amplitude added to 00, 01 amplitude set to 0 // 1 0: ( 0.7, 0) -// 1 1: ( 0, 0) <-- 11 is reset to 10, 11 amplitude set to 0, old 11 amplitude added to 10 +// 1 1: ( 0, 0) <-- 11 is reset to 10, old 11 amplitude added to 10, 11 amplitude set to 0 void QuantumState::updateDataAfterReset(QubitIndex qubitIndex) { auto newData = data; for (auto const &[basisVector, amplitude]: data) { diff --git a/test/IntegrationTest.cpp b/test/IntegrationTest.cpp index 705664dd..a001efd3 100644 --- a/test/IntegrationTest.cpp +++ b/test/IntegrationTest.cpp @@ -290,7 +290,7 @@ b = measure q // Expected q state should be |00>+|10> // State is |00>+|11> after creating the Bell state // Then reset sets q to |0>, leaving the state as |00>+|10> - // The measure provokes a collapse of the state to either |00> or |11> + // The measure provokes a collapse of the state to either |00> or |10> EXPECT_TRUE(actual.state[0].value.ends_with('0')); EXPECT_EQ(actual.state[0].amplitude, (core::Complex{ .real = 1, .imag = 0, .norm = 1 })); From 54ef0aa3158d0bb8576b4e75133f46a4508b5720 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 5 Sep 2024 14:11:34 +0200 Subject: [PATCH 50/56] Use SparseArray::forEach in QuantumState::updateDataAfterReset implementation. --- src/qx/QuantumState.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qx/QuantumState.cpp b/src/qx/QuantumState.cpp index e5658798..24021229 100644 --- a/src/qx/QuantumState.cpp +++ b/src/qx/QuantumState.cpp @@ -132,7 +132,8 @@ void QuantumState::updateDataAfterMeasurement(QubitIndex qubitIndex, bool measur // 1 1: ( 0, 0) <-- 11 is reset to 10, old 11 amplitude added to 10, 11 amplitude set to 0 void QuantumState::updateDataAfterReset(QubitIndex qubitIndex) { auto newData = data; - for (auto const &[basisVector, amplitude]: data) { + data.forEach([qubitIndex, &newData](auto const &kv) { + auto const &[basisVector, amplitude] = kv; if (basisVector.test(qubitIndex.value)) { auto basisVectorAfterReset = basisVector; basisVectorAfterReset.set(qubitIndex.value, false); @@ -141,7 +142,7 @@ void QuantumState::updateDataAfterReset(QubitIndex qubitIndex) { ); newData[basisVector].value = 0; } - } + }); data = std::move(newData); } From 31760686b0aaf074dfa4ee3c58f463f5fb1a942c Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 9 Sep 2024 16:59:12 +0200 Subject: [PATCH 51/56] Fix QuantumStateTest. --- test/QuantumStateTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/QuantumStateTest.cpp b/test/QuantumStateTest.cpp index 535dc26e..3f1d4875 100644 --- a/test/QuantumStateTest.cpp +++ b/test/QuantumStateTest.cpp @@ -83,16 +83,16 @@ TEST_F(QuantumStateTest, measure_on_superposed_state__measured_state_is_1) { } TEST_F(QuantumStateTest, reset) { - QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + QuantumState victim{ 2, 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; victim.applyReset(QubitIndex{ 0 }); checkEq(victim, {0.123, 0, std::sqrt(1 - std::pow(0.123, 2)), 0}); // 00 and 10 EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } TEST_F(QuantumStateTest, reset_all) { - QuantumState victim{ 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; + QuantumState victim{ 2, 2, {{"00", 0.123}, {"11", std::sqrt(1 - std::pow(0.123, 2))}} }; victim.applyResetAll(); - checkEq(victim, QuantumState{ 2 }.toVector()); + checkEq(victim, QuantumState{ 2, 2 }.toVector()); EXPECT_EQ(victim.getMeasurementRegister(), BasisVector("00")); } From ee06fdf80b852b9f0fcc861bf88b86eb8dc6d282 Mon Sep 17 00:00:00 2001 From: rturrado Date: Wed, 20 Nov 2024 14:18:17 +0100 Subject: [PATCH 52/56] Release version 0.7.2. --- CHANGELOG.md | 88 ++++++++++++++---------------------------- include/qx/Version.hpp | 2 +- 2 files changed, 29 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e7e63d9..f4b448a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [ 0.7.2 ] - [ 2024-11-20 ] + +### Added + +- Integrate with libqasm 0.6.7 release. +- Add `reset` instruction. +- Make QX simulator aware of bit (register) variables. + +### Changed + +### Removed + + ## [ 0.7.1 ] - [ 2024-05-22 ] ### Added @@ -13,9 +26,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Update `conanfile.py` to request `libqasm/0.6.6` and use `tools.build.skip_test`. -- Update GitHub workflows with some improvements taken from `libqasm`. - ### Removed @@ -50,7 +60,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Removed -- ## [ 0.6.4 ] - [ 2023-09-21 ] @@ -64,7 +73,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Removed -- ## [ 0.6.3 ] - [ 2023-09-19 ] @@ -86,6 +94,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Submodules and `deps` folder. + ## [ 0.6.2 ] - [ 2023-02-21 ] ### Added @@ -102,7 +111,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Removed -- ## [ 0.6.1 ] - [ 2023-02-01 ] @@ -129,6 +137,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Most Python tests. Already covered in C++ integration test, and they didn't check much anyway. + ## [ 0.6.0 ] - [ 2023-01-19 ] Almost a complete rewrite. No functional change except it's faster. @@ -154,6 +163,7 @@ Almost a complete rewrite. No functional change except it's faster. - SSE, AVX and other intrinsics. - `get_measurement_outcome` and `get_state` methods. Now accessible via JSON output. + ## [ 0.5.5 ] - [ 2023-01-12 ] ### Added @@ -165,8 +175,8 @@ Almost a complete rewrite. No functional change except it's faster. - Fixed issue #118: quantum state is always displayed, as well as measurement register averaging. ### Removed -- + ## [ 0.5.4 ] - [ 2023-01-06 ] ### Added @@ -186,6 +196,7 @@ Almost a complete rewrite. No functional change except it's faster. - Parallel gates in `gate.h` (they were executed sequentially). - Some non-needed and/or obsolete code. + ## [ 0.5.3 ] - [ 2023-01-02 ] ### Added @@ -194,25 +205,19 @@ Almost a complete rewrite. No functional change except it's faster. ### Changed -- - ### Removed -- ## [ 0.5.2 ] - [ 2022-12-7 ] ### Added -- - ### Changed - Fixed version number incorrectly set in previous release. ### Removed -- ## [ 0.5.1 ] - [ 2022-12-6 ] @@ -230,16 +235,17 @@ Almost a complete rewrite. No functional change except it's faster. - Python 3.6 support. + ## [ 0.5.0 ] - [ 2022-11-15 ] ### Added +- `measure_all` gate. Does no longer measure each qubit separately. - `-j ` CLI option to output JSON to a file. - Possible to use the simulator without OpenMP. ### Changed -- `measure_all` gate added. Does no longer measure each qubit separately. - Move to C++11: smart pointers, etc. - Format of output has been changed. You no longer need to add a `display` gate to output the quantum state at the end of the circuit. @@ -260,24 +266,20 @@ Almost a complete rewrite. No functional change except it's faster. - Headers no longer include `.cc` file. - Moved implementations to `.cc` files instead of headers. + ## [ 0.4.2 ] - [ 2021-06-01 ] ### Added -- - ### Changed -- - ### Removed -- - ### Fixed - Wheels no longer require exotic CPU extensions to work. + ## [ 0.4.1 ] - [ 2021-05-20 ] ### Added @@ -289,36 +291,30 @@ Almost a complete rewrite. No functional change except it's faster. ### Changed -- - ### Removed -- - ### Fixed - Build on MacOS. - Build on Windows using MSVC. - Python build process on various platforms. + ## [ 0.4.0 ] - [ 2021-05-20 ] ### Added -- - ### Changed - Replaced XPU-based threading with OpenMP. ### Removed -- - ### Fixed - Various bugs and inefficiencies related to multithreading. + ## [ 0.3.0 ] - [ 2019-05-01 ] ### Added @@ -328,44 +324,30 @@ Almost a complete rewrite. No functional change except it's faster. ### Changed -- - ### Removed -- - ### Fixed -- ## [ 0.2.5 ] - [ 2018-12-21 ] ### Added -- - ### Changed -- - ### Removed -- - ### Fixed - Fixed issue with libqasm submodule, this release is now with the correct version. + ## [ 0.2.4 ] - [ 2018-12-14 ] ### Added -- - ### Changed -- - ### Removed - Removed `-u 0` in Jenkins file again. @@ -374,43 +356,36 @@ Almost a complete rewrite. No functional change except it's faster. - Single qubit operations now accept integers as arguments, previously it required floats. + ## [ 0.2.3 ] - [ 2018-11-05 ] ### Added -- - ### Changed - Updated the libqasm to get the hotfix. ### Removed -- - ### Fixed - Fixed pthread mutex locking bug in XPU worker. + ## [ 0.2.2 ] - [ 2018-09-25 ] ### Added -- - ### Changed -- - ### Removed -- - ### Fixed - Fixed bug in measurement and preparation implementation for large number of qubits. - Fixed verbose GNU Compiler warnings under Linux. + ## [ 0.2.1 ] - [ 2018-09-04 ] ### Added @@ -421,15 +396,10 @@ Almost a complete rewrite. No functional change except it's faster. ### Changed -- - ### Removed -- - ### Fixed -- ## [ 0.2.0 ] - [ 2018-08-23 ] @@ -449,8 +419,6 @@ Almost a complete rewrite. No functional change except it's faster. ### Removed -- - ### Fixed - Fixed overflow bug when simulating more than 31 qubits. diff --git a/include/qx/Version.hpp b/include/qx/Version.hpp index 6e913044..ec30f435 100644 --- a/include/qx/Version.hpp +++ b/include/qx/Version.hpp @@ -1,4 +1,4 @@ #ifndef QX_VERSION -#define QX_VERSION "0.7.1" +#define QX_VERSION "0.7.2" #define QX_RELEASE_YEAR "2024" #endif From c6a4656a88fb847f058e05a600f681b3e2f717de Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 21 Nov 2024 12:21:02 +0100 Subject: [PATCH 53/56] Update assets workflow (from that of libQASM). --- .github/workflows/assets.yml | 291 +++++------------------------------ pyproject.toml | 22 +++ setup.py | 194 +++++++++-------------- 3 files changed, 135 insertions(+), 372 deletions(-) create mode 100644 pyproject.toml diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml index 1eccd54d..3f63e23f 100644 --- a/.github/workflows/assets.yml +++ b/.github/workflows/assets.yml @@ -9,275 +9,67 @@ on: - "release**" jobs: - macos-x64: - name: PyPI wheels for macOS/x64 - runs-on: macos-13 # x64 + cibw-wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} strategy: matrix: - python: - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" + # macos-13 is an x64 runner, macos-14 is an arm64 runner + os: [ubuntu-latest, windows-latest, macos-13, macos-14] steps: - name: Checkout uses: actions/checkout@v4 - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip conan setuptools wheel - brew install swig - - name: Build wheel - env: - QX_CPU_COMPATIBILITY_MODE: "True" - NPROCS: 10 - run: python setup.py bdist_wheel - - name: Wheel path - id: wheel - working-directory: pybuild/dist/ - run: | - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_OUTPUT - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_ENV + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==2.18.1 + - name: Build wheels + run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 with: - name: pypi-macos-x64-py${{ matrix.python }} - path: pybuild/dist/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: pybuild/dist/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip + name: cibw-wheels-${{ matrix.os }} + path: ./wheelhouse/*.whl - macos-arm64: - name: PyPI wheels for macOS/arm64 - runs-on: macos-14 # arm64 - strategy: - matrix: - python: - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" + cibw-wheels-linux-arm64: + name: Build wheels on linux-arm64 + runs-on: [self-hosted, ARM64, Linux] + container: + image: python:3.12-slim + volumes: + - /var/run/docker.sock:/var/run/docker.sock steps: - name: Checkout uses: actions/checkout@v4 - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip conan setuptools wheel - brew install swig - - name: Build wheel - env: - QX_CPU_COMPATIBILITY_MODE: "True" - NPROCS: 10 - run: python setup.py bdist_wheel - - name: Wheel path - id: wheel - working-directory: pybuild/dist/ + - name: Install docker run: | - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_OUTPUT - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_ENV - - uses: actions/upload-artifact@v4 - with: - name: pypi-macos-arm64-py${{ matrix.python }} - path: pybuild/dist/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: pybuild/dist/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip + apt-get update + apt-get install -y ca-certificates curl + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc - manylinux-x64: - name: PyPI wheels for Manylinux (x64) - runs-on: ubuntu-latest - container: quay.io/pypa/manylinux${{ matrix.manylinux }}_x86_64:latest - env: - SWIG_VERSION: ${{ matrix.swig_version }} - strategy: - matrix: - manylinux: - - "_2_28" - cpython_version: - - "cp38-cp38" - - "cp39-cp39" - - "cp310-cp310" - - "cp311-cp311" - - "cp312-cp312" - include: - - manylinux: _2_28 - swig_version: 'swig-3.0.12-19.module_el8.3.0+6167+838326ab' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install dependencies + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null + apt-get update + apt-get install -y docker-ce-cli + - name: Install cibuildwheel and build wheels run: | - dnf install -y $SWIG_VERSION - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - python -m pip install --upgrade pip conan wheel auditwheel - - name: Build wheel + pip install cibuildwheel==2.18.1 + cibuildwheel --output-dir wheelhouse env: - QX_CPU_COMPATIBILITY_MODE: "True" - NPROCS: 5 - run: | - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - conan remove -c "*/*" - /opt/python/${{ matrix.cpython_version }}/bin/python setup.py bdist_wheel - /opt/python/${{ matrix.cpython_version }}/bin/python -m auditwheel repair pybuild/dist/*.whl - - name: Wheel path - id: wheel - working-directory: wheelhouse - run: | - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_OUTPUT - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_ENV + CIBW_BEFORE_ALL_LINUX: yum install -y java-11-openjdk - uses: actions/upload-artifact@v4 with: - name: pypi-linux-x64-${{ matrix.cpython_version }} - path: wheelhouse/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: wheelhouse/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip + name: cibw-wheels-linux-arm64 + path: ./wheelhouse/*.whl - manylinux-arm64: - name: PyPI wheels for Manylinux (arm64) - runs-on: - - "self-hosted" - - "ARM64" - - "Linux" - container: quay.io/pypa/manylinux${{ matrix.manylinux }}_aarch64:latest - env: - JAVA_VERSION: ${{ matrix.java_version }} - SWIG_VERSION: ${{ matrix.swig_version }} - strategy: - matrix: - manylinux: - - "_2_28" - cpython_version: - - "cp38-cp38" - - "cp39-cp39" - - "cp310-cp310" - - "cp311-cp311" - - "cp312-cp312" - # We are having problems when zulu-opendjk Conan package on an armv8 architecture. - # zulu-openjdk provides the Java JRE required by the ANTLR generator. - # So, for the time being, we are installing Java manually for this platform - include: - - manylinux: _2_28 - java_version: 'java-11-openjdk-11.0.21.0.9-2.el8' - swig_version: 'swig-3.0.12-19.module_el8.4.0+2254+838326ab' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install dependencies - run: | - dnf install -y $JAVA_VERSION $SWIG_VERSION - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - python -m pip install --upgrade pip conan wheel auditwheel - - name: Build wheel - env: - QX_CPU_COMPATIBILITY_MODE: "True" - NPROCS: 5 - # We clean the Conan cache as a preventive measure for our runs in self-hosted runners - # Self-hosted runners use containers that cache Conan packages from previous runs, - # and that can cause different type of problems - run: | - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - conan remove -c "*/*" - python setup.py bdist_wheel - python -m auditwheel repair pybuild/dist/*.whl - - name: Wheel path - id: wheel - working-directory: wheelhouse - run: | - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_OUTPUT - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_ENV - - uses: actions/upload-artifact@v4 - with: - name: pypi-linux-arm64-${{ matrix.cpython_version }} - path: wheelhouse/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: wheelhouse/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip - - windows-x64: - name: PyPI wheels for Windows - runs-on: windows-latest - strategy: - matrix: - python: - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" - steps: - - name: Checkout - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - - name: Install dependencies - run: python -m pip install --upgrade pip conan setuptools wheel - - name: Build wheel - env: - QX_CPU_COMPATIBILITY_MODE: "True" - NPROCS: 5 - run: python setup.py bdist_wheel - - name: Wheel path - id: wheel - working-directory: pybuild/dist/ - run: | - echo "WHEEL_NAME=$(Get-ChildItem -name *.whl)" >> $env:GITHUB_OUTPUT - echo "WHEEL_NAME=$(Get-ChildItem -name *.whl)" >> $env:GITHUB_ENV - shell: powershell - - uses: actions/upload-artifact@v4 - with: - name: pypi-windows-py${{ matrix.python }} - path: pybuild/dist/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: pybuild/dist/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip - - publish: - name: Publish - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} + publish-python-packages: + name: Publish Python packages needs: - - macos-x64 - - macos-arm64 - - manylinux-x64 - - manylinux-arm64 - - windows-x64 + - cibw-wheels + - cibw-wheels-linux-arm64 runs-on: ubuntu-latest steps: - name: Download artifacts @@ -285,7 +77,8 @@ jobs: id: download - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@v1.8.14 + if: ${{ github.event_name == 'release' && github.event.action == 'created' }} with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} - packages_dir: ${{ steps.download.outputs.download-path }}/pypi-* + packages_dir: ${{ steps.download.outputs.download-path }}/cibw-* diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d40c08ac --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = [ + "setuptools", + "swig" +] +build-backend = "setuptools.build_meta" + +[tool.cibuildwheel] +before-all = "uname -a" +build-verbosity = 3 +skip = [ + "cp36-*", "cp37-*", "cp38-*", + "*i686*", "*ppc64le*", "*pypy*", "*s390x*", "*musllinux*", + "pp*", + "*-win32", +] +test-requires = "pytest" +test-command = "pytest {project}/test" + +[tool.cibuildwheel.linux] +manylinux-x86_64-image = "manylinux_2_28" +manylinux-aarch64-image = "manylinux_2_28" diff --git a/setup.py b/setup.py index 4d7614de..20d637e2 100755 --- a/setup.py +++ b/setup.py @@ -2,34 +2,44 @@ import os import platform -import shutil -import sys import re +import shutil from distutils.dir_util import copy_tree from setuptools import setup, Extension -from distutils.command.bdist import bdist as _bdist -from distutils.command.build import build as _build from distutils.command.clean import clean as _clean -from distutils.command.sdist import sdist as _sdist from setuptools.command.build_ext import build_ext as _build_ext -from setuptools.command.egg_info import egg_info as _egg_info +from distutils.command.build import build as _build from setuptools.command.install import install as _install -from wheel.bdist_wheel import bdist_wheel as _bdist_wheel -from version import get_version root_dir = os.getcwd() # root of the repository -src_dir = root_dir + os.sep + "src" # C++ source directory -pysrc_dir = root_dir + os.sep + "python" # Python source files -target_dir = root_dir + os.sep + "pybuild" # python-specific build directory -build_dir = target_dir + os.sep + "build" # directory for setuptools to dump various files into -dist_dir = target_dir + os.sep + "dist" # wheel output directory -cbuild_dir = target_dir + os.sep + "cbuild" # cmake build directory -prefix_dir = target_dir + os.sep + "prefix" # cmake install prefix -srcmod_dir = pysrc_dir + os.sep + "qxelarator" # qxelarator Python module directory, source files only -module_dir = target_dir + os.sep + "qxelarator" # qxelarator Python module directory for editable install +src_dir = root_dir + os.sep + 'src' # C++ source directory +pysrc_dir = root_dir + os.sep + 'python' # Python source files +target_dir = root_dir + os.sep + 'pybuild' # python-specific build directory +build_dir = target_dir + os.sep + 'build' # directory for setuptools to dump various files into +cbuild_dir = target_dir + os.sep + 'cbuild' # cmake build directory +prefix_dir = target_dir + os.sep + 'prefix' # cmake install prefix +srcmod_dir = pysrc_dir + os.sep + 'qxelarator' # qxelarator Python module directory, source files only +module_dir = target_dir + os.sep + 'qxelarator' # qxelarator Python module directory for editable install + + +def get_version(verbose=False): + """Extract version information from source code""" + inc_dir = os.path.join(root_dir, 'include') # C++ include directory + matcher = re.compile('[\t ]*#define[\t ]+QX_VERSION[\t ]+"(.*)"') + version = None + with open(os.path.join(inc_dir, 'qx', 'Version.hpp'), 'r') as f: + for ln in f: + m = matcher.match(ln) + if m: + version = m.group(1) + break + if verbose: + print("get_version: %s" % version) + return version + # Copy the handwritten Python sources into the module directory that we're telling setuptools is our source directory, # because setuptools insists on spamming output files into that directory. @@ -54,27 +64,26 @@ def run(self): class build_ext(_build_ext): def run(self): - from plumbum import local, FG, ProcessExecutionError + from plumbum import local, FG # If we were previously built in a different directory, # nuke the cbuild dir to prevent inane CMake errors. # This happens when the user does pip install . after building locally - if os.path.exists(cbuild_dir + os.sep + "CMakeCache.txt"): - with open(cbuild_dir + os.sep + "CMakeCache.txt", "r") as f: - for line in f.read().split("\n"): - line = line.split("#")[0].strip() + if os.path.exists(cbuild_dir + os.sep + 'CMakeCache.txt'): + with open(cbuild_dir + os.sep + 'CMakeCache.txt', 'r') as f: + for line in f.read().split('\n'): + line = line.split('#')[0].strip() if not line: continue - if line.startswith("QX_BINARY_DIR:STATIC"): - config_dir = line.split("=", maxsplit=1)[1] + if line.startswith('QX_BINARY_DIR:STATIC'): + config_dir = line.split('=', maxsplit=1)[1] if os.path.realpath(config_dir) != os.path.realpath(cbuild_dir): - print("removing pybuild/cbuild to avoid CMakeCache error") + print('removing pybuild/cbuild to avoid CMakeCache error') shutil.rmtree(cbuild_dir) break - # Figure out how setuptools wants to name the extension file and where - # it wants to place it. - target = os.path.abspath(self.get_ext_fullpath("qxelarator._qxelarator")) + # Figure out how setuptools wants to name the extension file and where it wants to place it. + target = os.path.abspath(self.get_ext_fullpath('qxelarator._qxelarator')) # Build the Python extension and "install" it where setuptools expects it. if not os.path.exists(cbuild_dir): @@ -82,9 +91,9 @@ def run(self): # Configure and build using Conan with local.cwd(root_dir): - build_type = os.environ.get("CMAKE_BUILD_TYPE", "Release") - build_tests = os.environ.get("QX_BUILD_TESTS", "False") - cpu_compatibility_mode = os.environ.get("QX_CPU_COMPATIBILITY_MODE", "False") + build_type = os.environ.get('CMAKE_BUILD_TYPE', 'Release') + build_tests = os.environ.get('QX_BUILD_TESTS', 'False') + cpu_compatibility_mode = os.environ.get('QX_CPU_COMPATIBILITY_MODE', 'False') cmd = local['conan']['profile']['detect']['--force'] cmd & FG @@ -102,53 +111,18 @@ def run(self): ['-o:a']['qx/*:python_ext=' + re.escape(os.path.basename(target))] # (Ab)use static libs for the intermediate libraries # to avoid dealing with R(UN)PATH nonsense on Linux/OSX as much as possible - ['-o:a']["qx/*:shared=False"] + ['-o:a']['qx/*:shared=False'] ['-b']['missing'] ['-tf'][''] ) - if build_tests == "True": + if build_tests == 'True': cmd = cmd['-c']['tools.build:skip_test=False'] - if platform.system() == "Darwin": + if platform.system() == 'Darwin': cmd = cmd['-c']['tools.build:defines=["_LIBCPP_DISABLE_AVAILABILITY"]'] cmd & FG # Update data_files for the installed files. - file_dict = {} - for directory in ("bin", "include", "lib", "lib64"): - real_directory = os.path.join(prefix_dir, directory) - if not os.path.isdir(real_directory): - continue - file_list = [ - os.path.join(path, name) - for path, _, filenames in os.walk(real_directory) - for name in filenames - ] - file_list = filter( - lambda f: os.path.isfile(f) and not os.path.islink(f), file_list - ) - file_list = list( - map(lambda f: os.path.relpath(f, real_directory), file_list) - ) - if file_list: - install_directories = [directory] - if directory.startswith("lib"): - if sys.platform == "linux" or sys.platform == "linux2": - install_directories = ["lib", "lib64"] - else: - install_directories = ["lib"] - for install_directory in install_directories: - for filename in file_list: - install_path = os.path.join( - install_directory, os.path.dirname(filename) - ) - if install_path not in file_dict: - file_dict[install_path] = [] - file_dict[install_path].append( - os.path.join(real_directory, filename) - ) - for install_path, files in file_dict.items(): - self.distribution.data_files.append((install_path, files)) class build(_build): @@ -160,82 +134,56 @@ def run(self): # Make sure the extension is built before the Python module is "built", # otherwise SWIG's generated module isn't included. # See https://stackoverflow.com/questions/12491328 - self.run_command("build_ext") + self.run_command('build_ext') _build.run(self) class install(_install): def run(self): # See https://stackoverflow.com/questions/12491328 - self.run_command("build_ext") + self.run_command('build_ext') _install.run(self) -class bdist(_bdist): - def finalize_options(self): - _bdist.finalize_options(self) - self.dist_dir = os.path.relpath(dist_dir) - - -class bdist_wheel(_bdist_wheel): - def run(self): - if platform.system() == "Darwin": - os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.10" - _bdist_wheel.run(self) - - -class sdist(_sdist): - def finalize_options(self): - _sdist.finalize_options(self) - self.dist_dir = os.path.relpath(dist_dir) - - -class egg_info(_egg_info): - def initialize_options(self): - _egg_info.initialize_options(self) - self.egg_base = os.path.relpath(target_dir) - - setup( - name="qxelarator", + name='qxelarator', version=get_version(), - description="qxelarator Python Package", - long_description=read("README.md"), - long_description_content_type="text/markdown", - author="QuTech, TU Delft", - url="https://github.com/QuTech-Delft/qx-simulator", + description='qxelarator Python Package', + long_description=read('README.md'), + long_description_content_type='text/markdown', + author='QuTech, TU Delft', + url='https://github.com/QuTech-Delft/qx-simulator', classifiers=[ - "License :: OSI Approved :: Apache Software License", - "Operating System :: POSIX :: Linux", - "Operating System :: MacOS", - "Operating System :: Microsoft :: Windows", - "Programming Language :: Python :: 3 :: Only", - "Topic :: Scientific/Engineering", + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX :: Linux', + 'Operating System :: MacOS', + 'Operating System :: Microsoft :: Windows', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Topic :: Scientific/Engineering' ], - packages=["qxelarator"], - package_dir={"": "pybuild"}, + packages=['qxelarator'], + package_dir={'': 'pybuild'}, # This will be populated during the build. - data_files=[], # NOTE: the library build process is completely overridden to let CMake handle it. # setuptools implementation is horribly broken. # This is here just to have the rest of setuptools understand that this is a Python module with an extension in it. - ext_modules=[Extension("qxelarator._qxelarator", [])], + ext_modules=[Extension('qxelarator._qxelarator', [])], cmdclass={ - "bdist": bdist, - "bdist_wheel": bdist_wheel, - "build_ext": build_ext, - "build": build, - "install": install, - "clean": clean, - "egg_info": egg_info, - "sdist": sdist, + 'build_ext': build_ext, + 'build': build, + 'install': install, + 'clean': clean, }, setup_requires=[ 'conan', - "plumbum", + 'plumbum', 'delocate; platform_system == "Darwin"', ], install_requires=['msvc-runtime; platform_system == "Windows"'], - tests_require=["pytest"], - zip_safe=False, + tests_require=['pytest'], + zip_safe=False ) From 97b468f787e3a21ada3010ad6ae2b9fa03c177f4 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 21 Nov 2024 12:33:39 +0100 Subject: [PATCH 54/56] Update test workflow (from that of libQASM). --- .github/workflows/test.yml | 103 +++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8f2667a9..9bfd4656 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: push: branches: - develop + - master pull_request: jobs: @@ -26,12 +27,15 @@ jobs: with: build_type: ${{ matrix.build_type }} conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-${{ matrix.compiler }}-linux-x64 - conan_profile_build: conan/profiles/tests-${{ matrix.build_type }}-${{ matrix.compiler }}-linux-x64 shell: bash - cpp-macos-x64: - name: "C++ tests (clang/MacOS/x64)" - runs-on: macos-13 # x64 + cpp-linux-arm64: + name: "C++ tests (gcc/Linux/ARM64)" + runs-on: [self-hosted, ARM64, Linux] + container: python:3.11 + # Run only when merging to develop + # (until we have a GitHub-hosted runner for Linux/ARM64) + if: github.ref == 'refs/heads/develop' strategy: fail-fast: false matrix: @@ -41,16 +45,26 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + # We are having problems when using zulu-opendjk Conan package on an armv8 architecture. + # zulu-openjdk provides the Java JRE required by the ANTLR generator. + # So, for the time being, we are installing Java manually for this platform + - name: Install dependencies + run: | + apt-get update + apt-get install -y default-jre pipx + # Add Conan to path (even before it is installed) + - name: Add Conan to path + run: | + echo "${HOME}/.local/bin" >> $GITHUB_PATH - uses: ./.github/actions/cpp-tests with: build_type: ${{ matrix.build_type }} - conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-apple_clang-macos-x64 - conan_profile_build: conan/profiles/tests-${{ matrix.build_type }}-apple_clang-macos-x64 + conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-gcc-linux-arm64 shell: bash - cpp-windows-x64: - name: "C++ tests (msvc/Windows/x64)" - runs-on: windows-latest + cpp-macos-x64: + name: "C++ tests (clang/MacOS/x64)" + runs-on: macos-13 # x64 strategy: fail-fast: false matrix: @@ -63,54 +77,44 @@ jobs: - uses: ./.github/actions/cpp-tests with: build_type: ${{ matrix.build_type }} - conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-msvc-windows-x64 - conan_profile_build: conan/profiles/tests-${{ matrix.build_type }}-msvc-windows-x64 + conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-apple_clang-macos-x64 shell: bash - cpp-linux-arm64: - name: "C++ tests (gcc/Linux/ARM64)" - runs-on: [self-hosted, ARM64, Linux] - container: python:3.11 + cpp-macos-arm64: + name: "C++ tests (clang/macos/ARM64)" + runs-on: macos-14 # arm64 + strategy: + fail-fast: false + matrix: + build_type: + - Debug + - Release steps: - name: Checkout uses: actions/checkout@v4 - # We are having problems when using zulu-opendjk Conan package on an armv8 architecture. - # zulu-openjdk provides the Java JRE required by the ANTLR generator. - # So, for the time being, we are installing Java manually for this platform - - name: Install dependencies - run: | - apt-get update - apt-get install -y default-jre pipx - # Add Conan to path (even before it is installed) - - name: Add Conan to path - run: | - echo "${HOME}/.local/bin" >> $GITHUB_PATH - uses: ./.github/actions/cpp-tests with: - build_type: Release - conan_profile_host: conan/profiles/tests-release-gcc-linux-arm64 - conan_profile_build: conan/profiles/tests-release-gcc-linux-arm64 + build_type: ${{ matrix.build_type }} + conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-apple_clang-macos-arm64 shell: bash - cpp-macos-arm64: - name: "C++ tests (clang/macos/ARM64)" - runs-on: macos-14 # arm64 + cpp-windows-x64: + name: "C++ tests (msvc/Windows/x64)" + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + build_type: + - Debug + - Release steps: - name: Checkout uses: actions/checkout@v4 - # We are having problems when using zulu-opendjk Conan package on an armv8 architecture. - # zulu-openjdk provides the Java JRE required by the ANTLR generator. - # So, for the time being, we are installing Java manually for this platform - - name: Install dependencies - run: | - brew install java - echo "$(brew --prefix java)/bin" >> $GITHUB_PATH - shell: bash - uses: ./.github/actions/cpp-tests with: - build_type: Release - conan_profile_host: conan/profiles/tests-release-apple_clang-macos-arm64 - conan_profile_build: conan/profiles/tests-release-apple_clang-macos-arm64 + build_type: ${{ matrix.build_type }} + conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-msvc-windows-x64 + conan_profile_build: conan/profiles/tests-${{ matrix.build_type }}-msvc-windows-x64 shell: bash python-linux-x64: @@ -147,7 +151,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Install SWIG + - name: Install dependencies run: | brew install swig shell: bash @@ -188,6 +192,15 @@ jobs: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') - || contains(needs.*.result, 'skipped') + || ( + github.ref != 'refs/heads/develop' + && contains(needs.*.result, 'skipped') + && !contains(needs.cpp-linux-arm64.result, 'skipped') + ) + || + ( + github.ref == 'refs/heads/develop' + && contains(needs.*.result, 'skipped') + ) }} run: exit 1 From c7f4e94404fb9ee42b7a3a595afd2a340be8af47 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 21 Nov 2024 12:43:44 +0100 Subject: [PATCH 55/56] Trying to fix C++ jobs in test workflow. --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9bfd4656..ef05df9d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,6 +27,7 @@ jobs: with: build_type: ${{ matrix.build_type }} conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-${{ matrix.compiler }}-linux-x64 + conan_profile_build: conan/profiles/tests-${{ matrix.build_type }}-${{ matrix.compiler }}-linux-x64 shell: bash cpp-linux-arm64: @@ -60,6 +61,7 @@ jobs: with: build_type: ${{ matrix.build_type }} conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-gcc-linux-arm64 + conan_profile_build: conan/profiles/tests-${{ matrix.build_type }}-gcc-linux-arm64 shell: bash cpp-macos-x64: @@ -78,6 +80,7 @@ jobs: with: build_type: ${{ matrix.build_type }} conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-apple_clang-macos-x64 + conan_profile_build: conan/profiles/tests-${{ matrix.build_type }}-apple_clang-macos-x64 shell: bash cpp-macos-arm64: @@ -96,6 +99,7 @@ jobs: with: build_type: ${{ matrix.build_type }} conan_profile_host: conan/profiles/tests-${{ matrix.build_type }}-apple_clang-macos-arm64 + conan_profile_build: conan/profiles/tests-${{ matrix.build_type }}-apple_clang-macos-arm64 shell: bash cpp-windows-x64: From 84fd165230c21138862239ae74d433f8ae23c4d7 Mon Sep 17 00:00:00 2001 From: rturrado Date: Fri, 22 Nov 2024 15:46:38 +0100 Subject: [PATCH 56/56] Update CHANGELOG.md. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b448a4..0d98c202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Integrate with libqasm 0.6.7 release. - Add `reset` instruction. - Make QX simulator aware of bit (register) variables. +- Update `test` and `assets` workflows. ### Changed