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] 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 99f7fc3..7e7e63d 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 8c7b3b9..0f208c3 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 db22652..0638d25 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 f1543b1..c430786 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 8783068..8b3fc1c 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 857fab8..62af3a0 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 4134825..431cabd 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 8e06e19..fc4db89 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 d314b25..43cffcc 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 279598e..0b27f22 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 9aba072..e9208a6 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 906f98f..2a29e47 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 0a70f4d..6c5d288 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 4a73829..c6c2de8 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 7ae337b..06019a4 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