From a9b7e70aaeb532fe8e1e31a7decca86d81eb523f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Jan 2025 22:18:38 +0100 Subject: [PATCH 01/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20algorithm?= =?UTF-8?q?s=20to=20use=20factory=20functions=20instead=20of=20inheritance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- eval/eval_dd_package.cpp | 277 ++++++++-------- .../mqt-core/algorithms/BernsteinVazirani.hpp | 34 +- .../{Entanglement.hpp => GHZState.hpp} | 8 +- include/mqt-core/algorithms/Grover.hpp | 29 +- include/mqt-core/algorithms/QFT.hpp | 19 +- include/mqt-core/algorithms/QPE.hpp | 22 +- .../algorithms/RandomCliffordCircuit.hpp | 22 +- include/mqt-core/algorithms/WState.hpp | 5 +- src/algorithms/BernsteinVazirani.cpp | 216 +++++++------ src/algorithms/Entanglement.cpp | 28 -- src/algorithms/GHZState.cpp | 29 ++ src/algorithms/Grover.cpp | 140 ++++---- src/algorithms/QFT.cpp | 149 ++++----- src/algorithms/QPE.cpp | 285 ++++++++-------- src/algorithms/RandomCliffordCircuit.cpp | 105 +++--- src/algorithms/WState.cpp | 13 +- test/algorithms/eval_dynamic_circuits.cpp | 305 +++++++++--------- test/algorithms/test_bernsteinvazirani.cpp | 34 +- test/algorithms/test_entanglement.cpp | 16 +- test/algorithms/test_grover.cpp | 64 ++-- test/algorithms/test_qft.cpp | 43 +-- test/algorithms/test_qpe.cpp | 22 +- test/algorithms/test_random_clifford.cpp | 8 +- test/algorithms/test_wstate.cpp | 4 +- .../test_flatten_operations.cpp | 24 +- 25 files changed, 946 insertions(+), 955 deletions(-) rename include/mqt-core/algorithms/{Entanglement.hpp => GHZState.hpp} (66%) delete mode 100644 src/algorithms/Entanglement.cpp create mode 100644 src/algorithms/GHZState.cpp diff --git a/eval/eval_dd_package.cpp b/eval/eval_dd_package.cpp index 8a2b2017e..beeccf6a5 100644 --- a/eval/eval_dd_package.cpp +++ b/eval/eval_dd_package.cpp @@ -8,7 +8,7 @@ */ #include "algorithms/BernsteinVazirani.hpp" -#include "algorithms/Entanglement.hpp" +#include "algorithms/GHZState.hpp" #include "algorithms/Grover.hpp" #include "algorithms/QFT.hpp" #include "algorithms/QPE.hpp" @@ -54,7 +54,7 @@ struct Experiment { struct SimulationExperiment final : public Experiment { SimulationExperiment() = default; - qc::VectorDD sim{}; + VectorDD sim{}; [[nodiscard]] bool success() const noexcept override { return sim.p != nullptr; @@ -62,7 +62,7 @@ struct SimulationExperiment final : public Experiment { }; struct FunctionalityConstructionExperiment final : public Experiment { - qc::MatrixDD func{}; + MatrixDD func{}; [[nodiscard]] bool success() const noexcept override { return func.p != nullptr; @@ -70,41 +70,92 @@ struct FunctionalityConstructionExperiment final : public Experiment { }; namespace { -template -MatrixDD buildFunctionality(const qc::Grover* qc, Package& dd) { - QuantumComputation groverIteration(qc->getNqubits()); - qc->oracle(groverIteration); - qc->diffusion(groverIteration); - - auto iteration = buildFunctionality(&groverIteration, dd); - - auto e = iteration; - dd.incRef(e); - for (std::size_t i = 0U; i < qc->iterations - 1U; ++i) { - e = dd.applyOperation(iteration, e); - } - dd.decRef(iteration); - - QuantumComputation setup(qc->getNqubits()); - qc->setup(setup); - const auto g = buildFunctionality(&setup, dd); - e = dd.applyOperation(e, g); - dd.decRef(g); - return e; +std::unique_ptr +benchmarkSimulate(const QuantumComputation& qc) { + auto exp = std::make_unique(); + const auto nq = qc.getNqubits(); + exp->dd = std::make_unique>(nq); + const auto start = std::chrono::high_resolution_clock::now(); + const auto in = exp->dd->makeZeroState(nq); + exp->sim = simulate(&qc, in, *(exp->dd)); + const auto end = std::chrono::high_resolution_clock::now(); + exp->runtime = + std::chrono::duration_cast>(end - start); + exp->stats = dd::getStatistics(exp->dd.get()); + return exp; +} + +std::unique_ptr +benchmarkFunctionalityConstruction(const QuantumComputation& qc) { + auto exp = std::make_unique(); + const auto nq = qc.getNqubits(); + exp->dd = std::make_unique>(nq); + const auto start = std::chrono::high_resolution_clock::now(); + exp->func = buildFunctionality(&qc, *(exp->dd)); + const auto end = std::chrono::high_resolution_clock::now(); + exp->runtime = + std::chrono::duration_cast>(end - start); + exp->stats = dd::getStatistics(exp->dd.get()); + return exp; } -template -MatrixDD buildFunctionalityRecursive(const qc::Grover* qc, - Package& dd) { - QuantumComputation groverIteration(qc->getNqubits()); - qc->oracle(groverIteration); - qc->diffusion(groverIteration); +std::unique_ptr +benchmarkSimulateGrover(const qc::Qubit nq, const BitString& targetValue) { + auto exp = std::make_unique(); + exp->dd = std::make_unique>(nq + 1); + auto& dd = *(exp->dd); + const auto start = std::chrono::high_resolution_clock::now(); + + // apply state preparation setup + QuantumComputation statePrep(nq + 1); + appendGroverInitialization(statePrep); + const auto s = buildFunctionality(&statePrep, dd); + auto e = dd.applyOperation(s, dd.makeZeroState(nq + 1)); + + QuantumComputation groverIteration(nq + 1); + appendGroverOracle(groverIteration, targetValue); + appendGroverDiffusion(groverIteration); auto iter = buildFunctionalityRecursive(&groverIteration, dd); + const auto iterations = computeNumberOfIterations(nq); + const std::bitset<128U> iterBits(iterations); + const auto msb = static_cast(std::floor(std::log2(iterations))); + auto f = iter; + dd.incRef(f); + for (std::size_t j = 0U; j <= msb; ++j) { + if (iterBits[j]) { + e = dd.applyOperation(f, e); + } + if (j < msb) { + f = dd.applyOperation(f, f); + } + } + dd.decRef(f); + exp->sim = e; + const auto end = std::chrono::high_resolution_clock::now(); + exp->runtime = + std::chrono::duration_cast>(end - start); + exp->stats = dd::getStatistics(exp->dd.get()); + return exp; +} + +std::unique_ptr +benchmarkFunctionalityConstructionGrover(const qc::Qubit nq, + const BitString& targetValue) { + auto exp = std::make_unique(); + exp->dd = std::make_unique>(nq + 1); + auto& dd = *(exp->dd); + const auto start = std::chrono::high_resolution_clock::now(); + + QuantumComputation groverIteration(nq + 1); + appendGroverOracle(groverIteration, targetValue); + appendGroverDiffusion(groverIteration); + + const auto iter = buildFunctionalityRecursive(&groverIteration, dd); auto e = iter; - std::bitset<128U> iterBits(qc->iterations); - const auto msb = - static_cast(std::floor(std::log2(qc->iterations))); + const auto iterations = computeNumberOfIterations(nq); + const std::bitset<128U> iterBits(iterations); + const auto msb = static_cast(std::floor(std::log2(iterations))); auto f = iter; dd.incRef(f); bool zero = !iterBits[0U]; @@ -124,48 +175,11 @@ MatrixDD buildFunctionalityRecursive(const qc::Grover* qc, dd.decRef(f); // apply state preparation setup - qc::QuantumComputation statePrep(qc->getNqubits()); - qc->setup(statePrep); + QuantumComputation statePrep(nq + 1); + appendGroverInitialization(statePrep); const auto s = buildFunctionality(&statePrep, dd); - return dd.applyOperation(e, s); -} - -std::unique_ptr -benchmarkSimulate(const QuantumComputation& qc) { - auto exp = std::make_unique(); - const auto nq = qc.getNqubits(); - exp->dd = std::make_unique>(nq); - const auto start = std::chrono::high_resolution_clock::now(); - const auto in = exp->dd->makeZeroState(nq); - exp->sim = simulate(&qc, in, *(exp->dd)); - const auto end = std::chrono::high_resolution_clock::now(); - exp->runtime = - std::chrono::duration_cast>(end - start); - exp->stats = dd::getStatistics(exp->dd.get()); - return exp; -} - -std::unique_ptr -benchmarkFunctionalityConstruction(const QuantumComputation& qc, - const bool recursive = false) { - auto exp = std::make_unique(); - const auto nq = qc.getNqubits(); - exp->dd = std::make_unique>(nq); - const auto start = std::chrono::high_resolution_clock::now(); + exp->func = dd.applyOperation(e, s); - if (const auto* grover = dynamic_cast(&qc)) { - if (recursive) { - exp->func = buildFunctionalityRecursive(grover, *(exp->dd)); - } else { - exp->func = buildFunctionality(grover, *(exp->dd)); - } - } else { - if (recursive) { - exp->func = buildFunctionalityRecursive(&qc, *(exp->dd)); - } else { - exp->func = buildFunctionality(&qc, *(exp->dd)); - } - } const auto end = std::chrono::high_resolution_clock::now(); exp->runtime = std::chrono::duration_cast>(end - start); @@ -179,7 +193,7 @@ static constexpr std::size_t SEED = 42U; class BenchmarkDDPackage { protected: void verifyAndSave(const std::string& name, const std::string& type, - qc::QuantumComputation& qc, const Experiment& exp) const { + QuantumComputation& qc, const Experiment& exp) const { const std::string& filename = "results_" + inputFilename + ".json"; @@ -215,13 +229,13 @@ class BenchmarkDDPackage { constexpr std::array nqubits = {256U, 512U, 1024U, 2048U, 4096U}; std::cout << "Running GHZ Simulation..." << '\n'; for (const auto& nq : nqubits) { - auto qc = qc::Entanglement(nq); + auto qc = createGHZState(nq); auto exp = benchmarkSimulate(qc); verifyAndSave("GHZ", "Simulation", qc, *exp); } std::cout << "Running GHZ Functionality..." << '\n'; for (const auto& nq : nqubits) { - auto qc = qc::Entanglement(nq); + auto qc = createGHZState(nq); auto exp = benchmarkFunctionalityConstruction(qc); verifyAndSave("GHZ", "Functionality", qc, *exp); } @@ -231,13 +245,13 @@ class BenchmarkDDPackage { constexpr std::array nqubits = {256U, 512U, 1024U, 2048U, 4096U}; std::cout << "Running WState Simulation..." << '\n'; for (const auto& nq : nqubits) { - auto qc = qc::WState(nq); + auto qc = createWState(nq); auto exp = benchmarkSimulate(qc); verifyAndSave("WState", "Simulation", qc, *exp); } std::cout << "Running WState Functionality..." << '\n'; for (const auto& nq : nqubits) { - auto qc = qc::WState(nq); + auto qc = createWState(nq); auto exp = benchmarkFunctionalityConstruction(qc); verifyAndSave("WState", "Functionality", qc, *exp); } @@ -247,15 +261,15 @@ class BenchmarkDDPackage { constexpr std::array nqubits = {255U, 511U, 1023U, 2047U, 4095U}; std::cout << "Running BV Simulation..." << '\n'; for (const auto& nq : nqubits) { - auto qc = qc::BernsteinVazirani(nq); - qc::CircuitOptimizer::removeFinalMeasurements(qc); + auto qc = createBernsteinVazirani(nq, SEED); + CircuitOptimizer::removeFinalMeasurements(qc); auto exp = benchmarkSimulate(qc); verifyAndSave("BV", "Simulation", qc, *exp); } std::cout << "Running BV Functionality..." << '\n'; for (const auto& nq : nqubits) { - auto qc = qc::BernsteinVazirani(nq); - qc::CircuitOptimizer::removeFinalMeasurements(qc); + auto qc = createBernsteinVazirani(nq, SEED); + CircuitOptimizer::removeFinalMeasurements(qc); auto exp = benchmarkFunctionalityConstruction(qc); verifyAndSave("BV", "Functionality", qc, *exp); } @@ -265,71 +279,37 @@ class BenchmarkDDPackage { constexpr std::array nqubitsSim = {256U, 512U, 1024U, 2048U, 4096U}; std::cout << "Running QFT Simulation..." << '\n'; for (const auto& nq : nqubitsSim) { - auto qc = qc::QFT(nq, false); + auto qc = createQFT(nq, false); auto exp = benchmarkSimulate(qc); verifyAndSave("QFT", "Simulation", qc, *exp); } constexpr std::array nqubitsFunc = {18U, 19U, 20U, 21U, 22U}; std::cout << "Running QFT Functionality..." << '\n'; for (const auto& nq : nqubitsFunc) { - auto qc = qc::QFT(nq, false); + auto qc = createQFT(nq, false); auto exp = benchmarkFunctionalityConstruction(qc); verifyAndSave("QFT", "Functionality", qc, *exp); } } - void runGrover() { + void runGrover() const { constexpr std::array nqubits = {27U, 31U, 35U, 39U, 41U}; std::cout << "Running Grover Simulation..." << '\n'; for (const auto& nq : nqubits) { - auto qc = std::make_unique(nq, SEED); - auto dd = std::make_unique>(qc->getNqubits()); - const auto start = std::chrono::high_resolution_clock::now(); - - // apply state preparation setup - qc::QuantumComputation statePrep(qc->getNqubits()); - qc->setup(statePrep); - auto s = buildFunctionality(&statePrep, *dd); - auto e = dd->applyOperation(s, dd->makeZeroState(qc->getNqubits())); - - qc::QuantumComputation groverIteration(qc->getNqubits()); - qc->oracle(groverIteration); - qc->diffusion(groverIteration); - - auto iter = buildFunctionalityRecursive(&groverIteration, *dd); - std::bitset<128U> iterBits(qc->iterations); - const auto msb = - static_cast(std::floor(std::log2(qc->iterations))); - auto f = iter; - dd->incRef(f); - for (std::size_t j = 0U; j <= msb; ++j) { - if (iterBits[j]) { - e = dd->applyOperation(f, e); - } - if (j < msb) { - f = dd->applyOperation(f, f); - } - } - dd->decRef(f); - const auto end = std::chrono::high_resolution_clock::now(); - const auto runtime = - std::chrono::duration_cast>(end - - start); - auto exp = std::make_unique(); - exp->dd = std::move(dd); - exp->sim = e; - exp->runtime = runtime; - exp->stats = dd::getStatistics(exp->dd.get()); - - verifyAndSave("Grover", "Simulation", *qc, *exp); + BitString targetValue; + targetValue.set(); + auto qc = createGrover(nq, targetValue); + auto exp = benchmarkSimulateGrover(nq, targetValue); + verifyAndSave("Grover", "Simulation", qc, *exp); } std::cout << "Running Grover Functionality..." << '\n'; for (const auto& nq : nqubits) { - - auto qc = std::make_unique(nq, SEED); - auto exp = benchmarkFunctionalityConstruction(*qc, true); - verifyAndSave("Grover", "Functionality", *qc, *exp); + BitString targetValue; + targetValue.set(); + auto qc = createGrover(nq, targetValue); + auto exp = benchmarkFunctionalityConstructionGrover(nq, targetValue); + verifyAndSave("Grover", "Functionality", qc, *exp); } } @@ -337,33 +317,52 @@ class BenchmarkDDPackage { constexpr std::array nqubitsSim = {14U, 15U, 16U, 17U, 18U}; std::cout << "Running QPE Simulation..." << '\n'; for (const auto& nq : nqubitsSim) { - auto qc = qc::QPE(nq, false); - qc::CircuitOptimizer::removeFinalMeasurements(qc); + auto qc = createQPE(nq, false, SEED); + CircuitOptimizer::removeFinalMeasurements(qc); auto exp = benchmarkSimulate(qc); verifyAndSave("QPE", "Simulation", qc, *exp); } std::cout << "Running QPE Functionality..." << '\n'; constexpr std::array nqubitsFunc = {7U, 8U, 9U, 10U, 11U}; for (const auto& nq : nqubitsFunc) { - auto qc = qc::QPE(nq, false); - qc::CircuitOptimizer::removeFinalMeasurements(qc); + auto qc = createQPE(nq, false, SEED); + CircuitOptimizer::removeFinalMeasurements(qc); auto exp = benchmarkFunctionalityConstruction(qc); verifyAndSave("QPE", "Functionality", qc, *exp); } } + void runExactQPE() const { + constexpr std::array nqubitsSim = {8U, 16U, 32U, 48U, 64U}; + std::cout << "Running QPE Simulation..." << '\n'; + for (const auto& nq : nqubitsSim) { + auto qc = createQPE(nq, true, SEED); + CircuitOptimizer::removeFinalMeasurements(qc); + auto exp = benchmarkSimulate(qc); + verifyAndSave("QPE_Exact", "Simulation", qc, *exp); + } + std::cout << "Running QPE Functionality..." << '\n'; + constexpr std::array nqubitsFunc = {7U, 8U, 9U, 10U, 11U}; + for (const auto& nq : nqubitsFunc) { + auto qc = createQPE(nq, true, SEED); + CircuitOptimizer::removeFinalMeasurements(qc); + auto exp = benchmarkFunctionalityConstruction(qc); + verifyAndSave("QPE_Exact", "Functionality", qc, *exp); + } + } + void runRandomClifford() const { - constexpr std::array nqubitsSim = {14U, 15U, 16U, 17U, 18U}; + constexpr std::array nqubitsSim = {14U, 15U, 16U, 17U, 18U}; std::cout << "Running RandomClifford Simulation..." << '\n'; for (const auto& nq : nqubitsSim) { - auto qc = qc::RandomCliffordCircuit(nq, nq * nq, SEED); + auto qc = createRandomCliffordCircuit(nq, nq * nq, SEED); auto exp = benchmarkSimulate(qc); verifyAndSave("RandomClifford", "Simulation", qc, *exp); } std::cout << "Running RandomClifford Functionality..." << '\n'; - constexpr std::array nqubitsFunc = {7U, 8U, 9U, 10U, 11U}; + constexpr std::array nqubitsFunc = {7U, 8U, 9U, 10U, 11U}; for (const auto& nq : nqubitsFunc) { - auto qc = qc::RandomCliffordCircuit(nq, nq * nq, SEED); + auto qc = createRandomCliffordCircuit(nq, nq * nq, SEED); auto exp = benchmarkFunctionalityConstruction(qc); verifyAndSave("RandomClifford", "Functionality", qc, *exp); } @@ -373,7 +372,7 @@ class BenchmarkDDPackage { explicit BenchmarkDDPackage(std::string filename) : inputFilename(std::move(filename)) {}; - void runAll() { + void runAll() const { runGHZ(); runWState(); runBV(); @@ -393,7 +392,7 @@ int main(const int argc, char** argv) { return 1; } try { - auto run = dd::BenchmarkDDPackage( + const auto run = dd::BenchmarkDDPackage( argv[1]); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) run.runAll(); } catch (const std::exception& e) { diff --git a/include/mqt-core/algorithms/BernsteinVazirani.hpp b/include/mqt-core/algorithms/BernsteinVazirani.hpp index 4f346f1a3..1464b11ed 100644 --- a/include/mqt-core/algorithms/BernsteinVazirani.hpp +++ b/include/mqt-core/algorithms/BernsteinVazirani.hpp @@ -12,26 +12,22 @@ #include "Definitions.hpp" #include "ir/QuantumComputation.hpp" -#include -#include -#include - namespace qc { -class BernsteinVazirani : public QuantumComputation { -public: - BitString s = 0; - std::size_t bitwidth = 1; - bool dynamic = false; - std::string expected; - - explicit BernsteinVazirani(const BitString& hiddenString, bool dyn = false); - explicit BernsteinVazirani(std::size_t nq, bool dyn = false); - BernsteinVazirani(const BitString& hiddenString, std::size_t nq, - bool dyn = false); - std::ostream& printStatistics(std::ostream& os) const override; +[[nodiscard]] auto createBernsteinVazirani(const BitString& hiddenString) + -> QuantumComputation; +[[nodiscard]] auto createBernsteinVazirani(Qubit nq, std::size_t seed = 0) + -> QuantumComputation; +[[nodiscard]] auto createBernsteinVazirani(const BitString& hiddenString, + Qubit nq) -> QuantumComputation; -protected: - void createCircuit(); -}; +[[nodiscard]] auto +createIterativeBernsteinVazirani(const BitString& hiddenString) + -> QuantumComputation; +[[nodiscard]] auto createIterativeBernsteinVazirani(Qubit nq, + std::size_t seed = 0) + -> QuantumComputation; +[[nodiscard]] auto +createIterativeBernsteinVazirani(const BitString& hiddenString, Qubit nq) + -> QuantumComputation; } // namespace qc diff --git a/include/mqt-core/algorithms/Entanglement.hpp b/include/mqt-core/algorithms/GHZState.hpp similarity index 66% rename from include/mqt-core/algorithms/Entanglement.hpp rename to include/mqt-core/algorithms/GHZState.hpp index 1dff44645..6d7bc063f 100644 --- a/include/mqt-core/algorithms/Entanglement.hpp +++ b/include/mqt-core/algorithms/GHZState.hpp @@ -9,13 +9,9 @@ #pragma once +#include "Definitions.hpp" #include "ir/QuantumComputation.hpp" -#include - namespace qc { -class Entanglement : public QuantumComputation { -public: - explicit Entanglement(std::size_t nq); -}; +[[nodiscard]] auto createGHZState(Qubit nq) -> QuantumComputation; } // namespace qc diff --git a/include/mqt-core/algorithms/Grover.hpp b/include/mqt-core/algorithms/Grover.hpp index 5d66b64ff..328c52175 100644 --- a/include/mqt-core/algorithms/Grover.hpp +++ b/include/mqt-core/algorithms/Grover.hpp @@ -13,28 +13,17 @@ #include "ir/QuantumComputation.hpp" #include -#include -#include namespace qc { -class Grover : public QuantumComputation { -public: - std::size_t seed = 0; - BitString targetValue = 0; - std::size_t iterations = 1; - std::string expected; - std::size_t nDataQubits{}; +auto appendGroverInitialization(QuantumComputation& qc) -> void; +auto appendGroverOracle(QuantumComputation& qc, const BitString& targetValue) + -> void; +auto appendGroverDiffusion(QuantumComputation& qc) -> void; - explicit Grover(std::size_t nq, std::size_t s = 0); +[[nodiscard]] auto computeNumberOfIterations(Qubit nq) -> std::size_t; - void setup(QuantumComputation& qc) const; - - void oracle(QuantumComputation& qc) const; - - void diffusion(QuantumComputation& qc) const; - - void fullGrover(QuantumComputation& qc) const; - - std::ostream& printStatistics(std::ostream& os) const override; -}; +[[nodiscard]] auto createGrover(Qubit nq, const BitString& targetValue) + -> QuantumComputation; +[[nodiscard]] auto createGrover(Qubit nq, std::size_t seed = 0) + -> QuantumComputation; } // namespace qc diff --git a/include/mqt-core/algorithms/QFT.hpp b/include/mqt-core/algorithms/QFT.hpp index 216611904..24e911ee9 100644 --- a/include/mqt-core/algorithms/QFT.hpp +++ b/include/mqt-core/algorithms/QFT.hpp @@ -9,23 +9,12 @@ #pragma once +#include "Definitions.hpp" #include "ir/QuantumComputation.hpp" -#include -#include - namespace qc { -class QFT : public QuantumComputation { -public: - explicit QFT(std::size_t nq, bool includeMeas = true, bool dyn = false); - - std::ostream& printStatistics(std::ostream& os) const override; - - std::size_t precision{}; - bool includeMeasurements; - bool dynamic; +[[nodiscard]] auto createQFT(Qubit nq, bool includeMeasurements = true) + -> QuantumComputation; -protected: - void createCircuit(); -}; +[[nodiscard]] auto createIterativeQFT(Qubit nq) -> QuantumComputation; } // namespace qc diff --git a/include/mqt-core/algorithms/QPE.hpp b/include/mqt-core/algorithms/QPE.hpp index 1b8b71dc1..95dfb4873 100644 --- a/include/mqt-core/algorithms/QPE.hpp +++ b/include/mqt-core/algorithms/QPE.hpp @@ -12,22 +12,16 @@ #include "Definitions.hpp" #include "ir/QuantumComputation.hpp" -#include -#include - namespace qc { -class QPE : public QuantumComputation { -public: - fp lambda = 0.; - std::size_t precision; - bool iterative; +[[nodiscard]] auto createQPE(Qubit nq, bool exact = true, std::size_t seed = 0) + -> QuantumComputation; - explicit QPE(std::size_t nq, bool exact = true, bool iter = false); - QPE(fp l, std::size_t prec, bool iter = false); +[[nodiscard]] auto createQPE(fp lambda, Qubit precision) -> QuantumComputation; - std::ostream& printStatistics(std::ostream& os) const override; +[[nodiscard]] auto createIterativeQPE(Qubit nq, bool exact = true, + std::size_t seed = 0) + -> QuantumComputation; -protected: - void createCircuit(); -}; +[[nodiscard]] auto createIterativeQPE(fp lambda, Qubit precision) + -> QuantumComputation; } // namespace qc diff --git a/include/mqt-core/algorithms/RandomCliffordCircuit.hpp b/include/mqt-core/algorithms/RandomCliffordCircuit.hpp index d72653ad6..b152640ef 100644 --- a/include/mqt-core/algorithms/RandomCliffordCircuit.hpp +++ b/include/mqt-core/algorithms/RandomCliffordCircuit.hpp @@ -13,25 +13,9 @@ #include "ir/QuantumComputation.hpp" #include -#include -#include -#include namespace qc { -class RandomCliffordCircuit : public QuantumComputation { -protected: - std::function cliffordGenerator; - - void append1QClifford(std::uint16_t idx, Qubit target); - void append2QClifford(std::uint16_t, Qubit control, Qubit target); - -public: - std::size_t depth = 1; - std::size_t seed = 0; - - explicit RandomCliffordCircuit(std::size_t nq, std::size_t depth = 1, - std::size_t seed = 0); - - std::ostream& printStatistics(std::ostream& os) const override; -}; +[[nodiscard]] auto createRandomCliffordCircuit(Qubit nq, std::size_t depth = 1, + std::size_t seed = 0) + -> QuantumComputation; } // namespace qc diff --git a/include/mqt-core/algorithms/WState.hpp b/include/mqt-core/algorithms/WState.hpp index 876fd64bb..1882c8194 100644 --- a/include/mqt-core/algorithms/WState.hpp +++ b/include/mqt-core/algorithms/WState.hpp @@ -13,8 +13,5 @@ #include "ir/QuantumComputation.hpp" namespace qc { -class WState : public QuantumComputation { -public: - explicit WState(Qubit nq); -}; +[[nodiscard]] auto createWState(Qubit nq) -> QuantumComputation; } // namespace qc diff --git a/src/algorithms/BernsteinVazirani.cpp b/src/algorithms/BernsteinVazirani.cpp index e88fe5d81..74f30283e 100644 --- a/src/algorithms/BernsteinVazirani.cpp +++ b/src/algorithms/BernsteinVazirani.cpp @@ -13,131 +13,165 @@ #include #include -#include +#include namespace qc { -BernsteinVazirani::BernsteinVazirani(const BitString& hiddenString, - const bool dyn) - : s(hiddenString), dynamic(dyn) { + +[[nodiscard]] auto getMostSignificantBit(const BitString& s) -> std::size_t { std::size_t msb = 0; for (std::size_t i = 0; i < s.size(); ++i) { if (s.test(i)) { msb = i; } } - bitwidth = msb + 1; - createCircuit(); + return msb; } -BernsteinVazirani::BernsteinVazirani(const std::size_t nq, const bool dyn) - : bitwidth(nq), dynamic(dyn) { +[[nodiscard]] auto generateBitstring(const std::size_t nq, std::mt19937_64& mt) + -> BitString { + BitString s; auto distribution = std::bernoulli_distribution(); for (std::size_t i = 0; i < nq; ++i) { if (distribution(mt)) { s.set(i); } } - createCircuit(); -} - -BernsteinVazirani::BernsteinVazirani(const BitString& hiddenString, - const std::size_t nq, const bool dyn) - : s(hiddenString), bitwidth(nq), dynamic(dyn) { - createCircuit(); -} - -std::ostream& BernsteinVazirani::printStatistics(std::ostream& os) const { - os << "BernsteinVazirani (" << bitwidth << ") Statistics:\n"; - os << "\tn: " << bitwidth + 1 << "\n"; - os << "\tm: " << getNindividualOps() << "\n"; - os << "\ts: " << expected << "\n"; - os << "\tdynamic: " << dynamic << "\n"; - os << "--------------" - << "\n"; - return os; + return s; } -void BernsteinVazirani::createCircuit() { - expected = s.to_string(); +[[nodiscard]] auto getExpected(const BitString& s, const Qubit nq) + -> std::string { + auto expected = s.to_string(); std::reverse(expected.begin(), expected.end()); - while (expected.length() > bitwidth) { + while (expected.length() > nq) { expected.pop_back(); } std::reverse(expected.begin(), expected.end()); - name = "bv_" + expected; + return expected; +} + +auto constructBernsteinVaziraniCircuit(QuantumComputation& qc, + const BitString& s, const Qubit nq) { + qc.setName("bv_" + getExpected(s, nq)); + qc.addQubitRegister(1, "flag"); + qc.addQubitRegister(nq, "q"); + qc.addClassicalRegister(nq, "c"); - addQubitRegister(1, "flag"); + // prepare flag qubit + qc.x(0); - if (dynamic) { - addQubitRegister(1, "q"); - } else { - addQubitRegister(bitwidth, "q"); + // set up initial layout + qc.initialLayout[0] = static_cast(nq); + for (Qubit i = 1; i <= nq; ++i) { + qc.initialLayout[i] = i - 1; } + qc.setLogicalQubitGarbage(nq); + qc.outputPermutation.erase(0); - addClassicalRegister(bitwidth, "c"); + // initial Hadamard transformation + for (Qubit i = 1; i <= nq; ++i) { + qc.h(i); + } - // prepare flag qubit - x(0); - - if (dynamic) { - // set up initial layout - initialLayout[0] = 1; - initialLayout[1] = 0; - setLogicalQubitGarbage(1); - outputPermutation.erase(0); - outputPermutation[1] = 0; - - for (std::size_t i = 0; i < bitwidth; ++i) { - // initial Hadamard - h(1); - - // apply controlled-Z gate according to secret bitstring - if (s.test(i)) { - cz(1, 0); - } - - // final Hadamard - h(1); - - // measure result - measure(1, i); - - // reset qubit if not finished - if (i < bitwidth - 1) { - reset(1); - } + // apply controlled-Z gates according to secret bitstring + for (Qubit i = 1; i <= nq; ++i) { + if (s.test(i - 1)) { + qc.cz(i, 0); } - } else { - // set up initial layout - initialLayout[0] = static_cast(bitwidth); - for (std::size_t i = 1; i <= bitwidth; ++i) { - initialLayout[static_cast(i)] = static_cast(i - 1); - } - setLogicalQubitGarbage(static_cast(bitwidth)); - outputPermutation.erase(0); + } - // initial Hadamard transformation - for (std::size_t i = 1; i <= bitwidth; ++i) { - h(static_cast(i)); - } + // final Hadamard transformation + for (Qubit i = 1; i <= nq; ++i) { + qc.h(i); + } - // apply controlled-Z gates according to secret bitstring - for (std::size_t i = 1; i <= bitwidth; ++i) { - if (s.test(i - 1)) { - cz(static_cast(i), 0); - } - } + // measure results + for (Qubit i = 1; i <= nq; i++) { + qc.measure(i, i - 1); + qc.outputPermutation[i] = i - 1; + } +} + +auto createBernsteinVazirani(const BitString& hiddenString) + -> QuantumComputation { + const auto msb = static_cast(getMostSignificantBit(hiddenString)); + return createBernsteinVazirani(hiddenString, msb + 1); +} - // final Hadamard transformation - for (std::size_t i = 1; i <= bitwidth; ++i) { - h(static_cast(i)); +auto createBernsteinVazirani(const Qubit nq, const std::size_t seed) + -> QuantumComputation { + auto qc = QuantumComputation(0, 0, seed); + const auto hiddenString = generateBitstring(nq, qc.getGenerator()); + constructBernsteinVaziraniCircuit(qc, hiddenString, nq); + return qc; +} + +auto createBernsteinVazirani(const BitString& hiddenString, const Qubit nq) + -> QuantumComputation { + auto qc = QuantumComputation(0, 0); + constructBernsteinVaziraniCircuit(qc, hiddenString, nq); + return qc; +} + +auto constructIterativeBernsteinVaziraniCircuit(QuantumComputation& qc, + const BitString& s, + const Qubit nq) { + qc.setName("iterative_bv_" + getExpected(s, nq)); + qc.addQubitRegister(1, "flag"); + qc.addQubitRegister(1, "q"); + qc.addClassicalRegister(nq, "c"); + + // prepare flag qubit + qc.x(0); + + // set up initial layout + qc.initialLayout[0] = 1; + qc.initialLayout[1] = 0; + qc.setLogicalQubitGarbage(1); + qc.outputPermutation.erase(0); + qc.outputPermutation[1] = 0; + + for (std::size_t i = 0; i < nq; ++i) { + // initial Hadamard + qc.h(1); + + // apply controlled-Z gate according to secret bitstring + if (s.test(i)) { + qc.cz(1, 0); } - // measure results - for (std::size_t i = 1; i <= bitwidth; i++) { - measure(static_cast(i), i - 1); - outputPermutation[static_cast(i)] = static_cast(i - 1); + // final Hadamard + qc.h(1); + + // measure result + qc.measure(1, i); + + // reset qubit if not finished + if (i < nq - 1) { + qc.reset(1); } } + return qc; +} + +auto createIterativeBernsteinVazirani(const BitString& hiddenString) + -> QuantumComputation { + const auto msb = static_cast(getMostSignificantBit(hiddenString)); + return createIterativeBernsteinVazirani(hiddenString, msb + 1); +} + +auto createIterativeBernsteinVazirani(const Qubit nq, const std::size_t seed) + -> QuantumComputation { + auto qc = QuantumComputation(0, 0, seed); + const auto hiddenString = generateBitstring(nq, qc.getGenerator()); + constructIterativeBernsteinVaziraniCircuit(qc, hiddenString, nq); + return qc; +} + +auto createIterativeBernsteinVazirani(const BitString& hiddenString, + const Qubit nq) -> QuantumComputation { + auto qc = QuantumComputation(0, 0); + constructIterativeBernsteinVaziraniCircuit(qc, hiddenString, nq); + return qc; } } // namespace qc diff --git a/src/algorithms/Entanglement.cpp b/src/algorithms/Entanglement.cpp deleted file mode 100644 index 46a7e7901..000000000 --- a/src/algorithms/Entanglement.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2025 Chair for Design Automation, TUM - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "algorithms/Entanglement.hpp" - -#include "Definitions.hpp" -#include "ir/QuantumComputation.hpp" - -#include -#include - -namespace qc { -Entanglement::Entanglement(const std::size_t nq) : QuantumComputation(nq, nq) { - name = "entanglement_" + std::to_string(nq); - const auto top = static_cast(nq - 1); - - h(top); - for (std::size_t i = 1; i < nq; i++) { - cx(top, static_cast(top - i)); - } -} -} // namespace qc diff --git a/src/algorithms/GHZState.cpp b/src/algorithms/GHZState.cpp new file mode 100644 index 000000000..4a2d2620c --- /dev/null +++ b/src/algorithms/GHZState.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Chair for Design Automation, TUM + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "algorithms/GHZState.hpp" + +#include "Definitions.hpp" +#include "ir/QuantumComputation.hpp" + +#include + +namespace qc { +auto createGHZState(const Qubit nq) -> QuantumComputation { + auto qc = QuantumComputation(nq, nq); + qc.setName("ghz_" + std::to_string(nq)); + + const auto top = nq - 1; + qc.h(top); + for (Qubit i = 0; i < top; i++) { + qc.cx(top, i); + } + return qc; +} +} // namespace qc diff --git a/src/algorithms/Grover.cpp b/src/algorithms/Grover.cpp index bffb66a82..8ec42cc14 100644 --- a/src/algorithms/Grover.cpp +++ b/src/algorithms/Grover.cpp @@ -16,48 +16,43 @@ #include #include #include -#include #include #include #include namespace qc { -/*** - * Private Methods - ***/ -void Grover::setup(QuantumComputation& qc) const { - qc.x(static_cast(nDataQubits)); - for (std::size_t i = 0; i < nDataQubits; ++i) { - qc.h(static_cast(i)); +auto appendGroverInitialization(QuantumComputation& qc) -> void { + const auto nDataQubits = static_cast(qc.getNqubits() - 1); + qc.x(nDataQubits); + for (Qubit i = 0; i < nDataQubits; ++i) { + qc.h(i); } } -void Grover::oracle(QuantumComputation& qc) const { +auto appendGroverOracle(QuantumComputation& qc, const BitString& targetValue) + -> void { + const auto nDataQubits = static_cast(qc.getNqubits() - 1); Controls controls{}; for (std::size_t i = 0; i < nDataQubits; ++i) { - controls.emplace(static_cast(i), targetValue.test(i) - ? Control::Type::Pos - : Control::Type::Neg); + controls.emplace(i, targetValue.test(i) ? Control::Type::Pos + : Control::Type::Neg); } - qc.mcz(controls, static_cast(nDataQubits)); + qc.mcz(controls, nDataQubits); } -void Grover::diffusion(QuantumComputation& qc) const { - for (std::size_t i = 0; i < nDataQubits; ++i) { - qc.h(static_cast(i)); +auto appendGroverDiffusion(QuantumComputation& qc) -> void { + const auto nDataQubits = static_cast(qc.getNqubits() - 1); + for (Qubit i = 0; i < nDataQubits; ++i) { + qc.h(i); } - for (std::size_t i = 0; i < nDataQubits; ++i) { - qc.x(static_cast(i)); + for (Qubit i = 0; i < nDataQubits; ++i) { + qc.x(i); } - - qc.h(0); Controls controls{}; for (Qubit j = 1; j < nDataQubits; ++j) { controls.emplace(j); } - qc.mcx(controls, 0); - qc.h(0); - + qc.mcz(controls, 0); for (auto i = static_cast>(nDataQubits - 1); i >= 0; --i) { qc.x(static_cast(i)); @@ -68,71 +63,78 @@ void Grover::diffusion(QuantumComputation& qc) const { } } -void Grover::fullGrover(QuantumComputation& qc) const { - // create initial superposition - setup(qc); - - // apply Grover iterations - for (std::size_t j = 0; j < iterations; ++j) { - oracle(qc); - diffusion(qc); +auto computeNumberOfIterations(const Qubit nq) -> std::size_t { + if (nq <= 2) { + return 1; } - - // measure the resulting state - for (std::size_t i = 0; i < nDataQubits; ++i) { - qc.measure(static_cast(i), i); + if (nq % 2 == 1) { + return static_cast( + std::round(PI_4 * std::pow(2., static_cast(nq + 1) / 2. - 1.) * + std::sqrt(2))); } + return static_cast( + std::round(PI_4 * std::pow(2., static_cast(nq) / 2.))); } -/*** - * Public Methods - ***/ -Grover::Grover(std::size_t nq, std::size_t s) : seed(s), nDataQubits(nq) { - name = "grover_" + std::to_string(nq); - - addQubitRegister(nDataQubits, "q"); - addQubitRegister(1, "flag"); - addClassicalRegister(nDataQubits); - - mt.seed(seed); - +[[nodiscard]] auto generateTargetValue(const std::size_t nDataQubits, + std::mt19937_64& mt) -> BitString { + BitString targetValue; std::bernoulli_distribution distribution{}; for (std::size_t i = 0; i < nDataQubits; i++) { if (distribution(mt)) { targetValue.set(i); } } + return targetValue; +} - expected = targetValue.to_string(); +[[nodiscard]] auto getGroverName(const BitString& s, const Qubit nq) + -> std::string { + auto expected = s.to_string(); std::reverse(expected.begin(), expected.end()); - while (expected.length() > nqubits - 1) { + while (expected.length() > nq) { expected.pop_back(); } std::reverse(expected.begin(), expected.end()); + return "grover_" + std::to_string(nq) + "_" + expected; +} + +auto constructGroverCircuit(QuantumComputation& qc, const Qubit nq, + const BitString& targetValue) { + qc.setName(getGroverName(targetValue, nq)); + qc.addQubitRegister(nq, "q"); + qc.addQubitRegister(1, "flag"); + qc.addClassicalRegister(nq); + + // create initial superposition + appendGroverInitialization(qc); - if (nDataQubits <= 2) { - iterations = 1; - } else if (nDataQubits % 2 == 1) { - iterations = static_cast(std::round( - PI_4 * std::pow(2., static_cast(nDataQubits + 1) / 2. - 1.) * - std::sqrt(2))); - } else { - iterations = static_cast( - std::round(PI_4 * std::pow(2., static_cast(nDataQubits) / 2.))); + // apply Grover iterations + const auto iterations = computeNumberOfIterations(nq); + for (std::size_t j = 0; j < iterations; ++j) { + appendGroverOracle(qc, targetValue); + appendGroverDiffusion(qc); } - fullGrover(*this); + // measure the resulting state + for (Qubit i = 0; i < nq; ++i) { + qc.measure(i, i); + } } -std::ostream& Grover::printStatistics(std::ostream& os) const { - os << "Grover (" << nqubits - 1 << ") Statistics:\n"; - os << "\tn: " << nqubits << "\n"; - os << "\tm: " << getNindividualOps() << "\n"; - os << "\tseed: " << seed << "\n"; - os << "\tx: " << expected << "\n"; - os << "\ti: " << iterations << "\n"; - os << "--------------" - << "\n"; - return os; +auto createGrover(const Qubit nq, const std::size_t seed) + -> QuantumComputation { + auto qc = QuantumComputation(0, 0, seed); + const auto targetValue = generateTargetValue(nq, qc.getGenerator()); + constructGroverCircuit(qc, nq, targetValue); + return qc; } + +auto createGrover(const Qubit nq, const BitString& targetValue) + -> QuantumComputation { + auto qc = QuantumComputation(); + constructGroverCircuit(qc, nq, targetValue); + return qc; +} + } // namespace qc diff --git a/src/algorithms/QFT.cpp b/src/algorithms/QFT.cpp index aa415e08b..875b18fa7 100644 --- a/src/algorithms/QFT.cpp +++ b/src/algorithms/QFT.cpp @@ -14,104 +14,91 @@ #include "ir/operations/OpType.hpp" #include -#include -#include #include -#include namespace qc { -QFT::QFT(const std::size_t nq, const bool includeMeas, const bool dyn) - : precision(nq), includeMeasurements(includeMeas), dynamic(dyn) { - name = "qft_" + std::to_string(nq); - if (precision == 0) { - return; + +auto createQFT(const Qubit nq, const bool includeMeasurements) + -> QuantumComputation { + auto qc = QuantumComputation(nq, nq); + qc.setName("qft_" + std::to_string(nq)); + if (nq == 0) { + return qc; + } + + // apply quantum Fourier transform + for (Qubit i = 0; i < nq; ++i) { + // apply controlled rotations + for (Qubit j = i; j > 0; --j) { + const auto d = i - j; + if (j == 1) { + qc.cs(i, d); + } else if (j == 2) { + qc.ct(i, d); + } else { + const auto powerOfTwo = std::pow(2., j); + const auto lambda = PI / powerOfTwo; + qc.cp(lambda, i, d); + } + } + + // apply Hadamard + qc.h(i); } - if (dynamic) { - addQubitRegister(1); + if (includeMeasurements) { + // measure qubits in reverse order + for (Qubit i = 0; i < nq; ++i) { + qc.measure(i, nq - 1 - i); + qc.outputPermutation[i] = nq - 1 - i; + } } else { - addQubitRegister(precision); + for (Qubit i = 0; i < nq / 2; ++i) { + qc.swap(i, nq - 1 - i); + } + for (Qubit i = 0; i < nq; ++i) { + qc.outputPermutation[i] = nq - 1 - i; + } } - addClassicalRegister(precision); - createCircuit(); -} -std::ostream& QFT::printStatistics(std::ostream& os) const { - os << "QFT (" << precision << ") Statistics:\n"; - os << "\tn: " << nqubits << "\n"; - os << "\tm: " << getNindividualOps() << "\n"; - os << "\tdynamic: " << dynamic << "\n"; - os << "--------------" - << "\n"; - return os; + return qc; } -void QFT::createCircuit() { - if (dynamic) { - for (std::size_t i = 0; i < precision; i++) { - // apply classically controlled phase rotations - for (std::size_t j = 1; j <= i; ++j) { - const auto d = static_cast(precision - j); - if (j == i) { - classicControlled(S, 0, {d, 1U}, 1U); - } else if (j == i - 1) { - classicControlled(T, 0, {d, 1U}, 1U); - } else { - const auto powerOfTwo = std::pow(2., i - j + 1); - const auto lambda = PI / powerOfTwo; - classicControlled(P, 0, {d, 1U}, 1U, Eq, std::vector{lambda}); - } - } - - // apply Hadamard - h(0); - // measure result - measure(0, precision - 1 - i); +auto createIterativeQFT(const Qubit nq) -> QuantumComputation { + auto qc = QuantumComputation(0, nq); + qc.setName("iterative_qft_" + std::to_string(nq)); + if (nq == 0) { + return qc; + } + qc.addQubitRegister(1U); - // reset qubit if not finished - if (i < precision - 1) { - reset(0); + for (Qubit i = 0; i < nq; ++i) { + // apply classically controlled phase rotations + for (Qubit j = 1; j <= i; ++j) { + const auto d = nq - j; + if (j == i) { + qc.classicControlled(S, 0, {d, 1U}, 1U); + } else if (j == i - 1) { + qc.classicControlled(T, 0, {d, 1U}, 1U); + } else { + const auto powerOfTwo = std::pow(2., i - j + 1); + const auto lambda = PI / powerOfTwo; + qc.classicControlled(P, 0, {d, 1U}, 1U, Eq, {lambda}); } } - } else { - // apply quantum Fourier transform - for (std::size_t i = 0; i < precision; ++i) { - const auto q = static_cast(i); - // apply controlled rotations - for (std::size_t j = i; j > 0; --j) { - const auto d = static_cast(q - j); - if (j == 1) { - cs(q, d); - } else if (j == 2) { - ct(q, d); - } else { - const auto powerOfTwo = std::pow(2., j); - const auto lambda = PI / powerOfTwo; - cp(lambda, q, d); - } - } + // apply Hadamard + qc.h(0); - // apply Hadamard - h(q); - } + // measure result + qc.measure(0, nq - 1 - i); - if (includeMeasurements) { - // measure qubits in reverse order - for (std::size_t i = 0; i < precision; ++i) { - measure(static_cast(i), precision - 1 - i); - outputPermutation[static_cast(i)] = - static_cast(precision - 1 - i); - } - } else { - for (Qubit i = 0; i < static_cast(precision / 2); ++i) { - swap(i, static_cast(precision - 1 - i)); - } - for (std::size_t i = 0; i < precision; ++i) { - outputPermutation[static_cast(i)] = - static_cast(precision - 1 - i); - } + // reset qubit if not finished + if (i < nq - 1) { + qc.reset(0); } } + + return qc; } } // namespace qc diff --git a/src/algorithms/QPE.cpp b/src/algorithms/QPE.cpp index 2d701499f..1122ad782 100644 --- a/src/algorithms/QPE.cpp +++ b/src/algorithms/QPE.cpp @@ -16,155 +16,188 @@ #include #include #include -#include +#include #include +#include namespace qc { -QPE::QPE(const std::size_t nq, const bool exact, const bool iter) - : precision(nq), iterative(iter) { - if (exact) { - // if an exact solution is wanted, generate a random n-bit number and - // convert it to an appropriate phase - const std::uint64_t max = 1ULL << nq; - auto distribution = - std::uniform_int_distribution(0, max - 1); - std::uint64_t theta = 0; - while (theta == 0) { - theta = distribution(mt); - } - lambda = 0.; - for (std::size_t i = 0; i < nq; ++i) { - if ((theta & (1ULL << (nq - i - 1))) != 0) { - lambda += 1. / static_cast(1ULL << i); - } - } - } else { - // if an inexact solution is wanted, generate a random n+1-bit number (that - // has its last bit set) and convert it to an appropriate phase - const std::uint64_t max = 1ULL << (nq + 1); - auto distribution = - std::uniform_int_distribution(0, max - 1); - std::uint64_t theta = 0; - while ((theta & 1) == 0) { - theta = distribution(mt); - } - lambda = 0.; - for (std::size_t i = 0; i <= nq; ++i) { - if ((theta & (1ULL << (nq - i))) != 0) { - lambda += 1. / static_cast(1ULL << i); - } + +// generate a random n-bit number and convert it to an appropriate phase +[[nodiscard]] auto createExactPhase(const Qubit nq, std::mt19937_64& mt) -> fp { + const std::uint64_t max = 1ULL << nq; + auto distribution = std::uniform_int_distribution(0, max - 1); + std::uint64_t theta = 0; + while (theta == 0) { + theta = distribution(mt); + } + fp lambda = 0.; + for (std::size_t i = 0; i < nq; ++i) { + if ((theta & (1ULL << (nq - i - 1))) != 0) { + lambda += 1. / static_cast(1ULL << i); } } - createCircuit(); + return lambda; } -QPE::QPE(const fp l, const std::size_t prec, const bool iter) - : lambda(l), precision(prec), iterative(iter) { - createCircuit(); +// generate a random n+1-bit number (that has its last bit set) and convert it +// to an appropriate phase +[[nodiscard]] auto createInexactPhase(const Qubit nq, std::mt19937_64& mt) + -> fp { + const std::uint64_t max = 1ULL << (nq + 1); + auto distribution = std::uniform_int_distribution(0, max - 1); + std::uint64_t theta = 0; + while ((theta & 1) == 0) { + theta = distribution(mt); + } + fp lambda = 0.; + for (std::size_t i = 0; i <= nq; ++i) { + if ((theta & (1ULL << (nq - i))) != 0) { + lambda += 1. / static_cast(1ULL << i); + } + } + return lambda; } -std::ostream& QPE::printStatistics(std::ostream& os) const { - os << "QPE Statistics:\n"; - os << "\tn: " << nqubits + 1 << "\n"; - os << "\tm: " << getNindividualOps() << "\n"; - os << "\tlambda: " << lambda << "π" - << "\n"; - os << "\tprecision: " << precision << "\n"; - os << "\titerative: " << iterative << "\n"; - os << "--------------" - << "\n"; - return os; +[[nodiscard]] auto getName(const bool iterative, const Qubit nq, + const fp lambda) -> std::string { + std::stringstream ss; + ss << (iterative ? "iterative_" : "") << "qpe_"; + ss << nq << "_"; + ss.precision(std::numeric_limits::digits10); + ss << lambda; + return ss.str(); } -void QPE::createCircuit() { - addQubitRegister(1, "psi"); +auto constructQPECircuit(QuantumComputation& qc, const fp lambda, + const Qubit nq) -> void { + qc.setName(getName(false, nq, lambda)); + qc.addQubitRegister(1, "psi"); + qc.addQubitRegister(nq, "q"); + qc.addClassicalRegister(nq, "c"); + // store lambda in global phase + qc.gphase(lambda); + + // prepare eigenvalue + qc.x(0); - if (iterative) { - addQubitRegister(1, "q"); - } else { - addQubitRegister(precision, "q"); + // set up initial layout + qc.initialLayout[0] = nq; + for (Qubit i = 1; i <= nq; ++i) { + qc.initialLayout[i] = i - 1; } + qc.setLogicalQubitGarbage(nq); + qc.outputPermutation.erase(0); - addClassicalRegister(precision, "c"); + // Hadamard Layer + for (Qubit i = 1; i <= nq; ++i) { + qc.h(i); + } - // prepare eigenvalue - x(0); - - if (iterative) { - // set up initial layout - initialLayout[0] = 1; - initialLayout[1] = 0; - setLogicalQubitGarbage(1); - outputPermutation.erase(0); - outputPermutation[1] = 0; - - for (std::size_t i = 0; i < precision; i++) { - // Hadamard - h(1); - - // normalize angle - const auto angle = std::remainder( - static_cast(1ULL << (precision - 1 - i)) * lambda, 2.0); - - // controlled phase rotation - cp(angle * PI, 1, 0); - - // hybrid quantum-classical inverse QFT - for (std::size_t j = 0; j < i; j++) { - auto iQFTLambda = -PI / static_cast(1ULL << (i - j)); - classicControlled(P, 1, {j, 1U}, 1U, Eq, {iQFTLambda}); + for (Qubit i = 0; i < nq; ++i) { + // normalize angle + const auto angle = + std::remainder(static_cast(1ULL << (nq - 1 - i)) * lambda, 2.0); + + // controlled phase rotation + qc.cp(angle * PI, 1 + i, 0); + + // inverse QFT + for (Qubit j = 1; j < 1 + i; j++) { + const auto iQFTLambda = -PI / static_cast(2ULL << (i - j)); + if (j == i) { + qc.csdg(i, 1 + i); + } else if (j == (i - 1)) { + qc.ctdg(i - 1, 1 + i); + } else { + qc.cp(iQFTLambda, j, 1 + i); } - h(1); + } + qc.h(1 + i); + } - // measure result - measure(1, i); + // measure results + for (Qubit i = 0; i < nq; i++) { + qc.measure(i + 1, i); + qc.outputPermutation[i + 1] = i; + } +} - // reset qubit if not finished - if (i < precision - 1) { - reset(1); - } - } - } else { - // set up initial layout - initialLayout[0] = static_cast(precision); - for (std::size_t i = 1; i <= precision; ++i) { - initialLayout[static_cast(i)] = static_cast(i - 1); - } - setLogicalQubitGarbage(static_cast(precision)); - outputPermutation.erase(0); +auto createQPE(const Qubit nq, const bool exact, const std::size_t seed) + -> QuantumComputation { + auto qc = QuantumComputation(0, 0, seed); + const auto lambda = exact ? createExactPhase(nq, qc.getGenerator()) + : createInexactPhase(nq, qc.getGenerator()); + constructQPECircuit(qc, lambda, nq); + return qc; +} - // Hadamard Layer - for (std::size_t i = 1; i <= precision; i++) { - h(static_cast(i)); - } +auto createQPE(const fp lambda, const Qubit precision) -> QuantumComputation { + auto qc = QuantumComputation(); - for (std::size_t i = 0; i < precision; i++) { - // normalize angle - const auto angle = std::remainder( - static_cast(1ULL << (precision - 1 - i)) * lambda, 2.0); - - // controlled phase rotation - cp(angle * PI, static_cast(1 + i), 0); - - // inverse QFT - for (std::size_t j = 1; j < 1 + i; j++) { - const auto iQFTLambda = -PI / static_cast(2ULL << (i - j)); - if (j == i) { - csdg(static_cast(i), static_cast(1 + i)); - } else if (j == (i - 1)) { - ctdg(static_cast(i - 1), static_cast(1 + i)); - } else { - cp(iQFTLambda, static_cast(j), static_cast(1 + i)); - } - } - h(static_cast(1 + i)); + constructQPECircuit(qc, lambda, precision); + return qc; +} + +auto constructIterativeQPECircuit(QuantumComputation& qc, const fp lambda, + const Qubit nq) -> void { + qc.setName(getName(true, nq, lambda)); + qc.addQubitRegister(1, "psi"); + qc.addQubitRegister(1, "q"); + qc.addClassicalRegister(nq, "c"); + // store lambda in global phase + qc.gphase(lambda); + + // prepare eigenvalue + qc.x(0); + + // set up initial layout + qc.initialLayout[0] = 1; + qc.initialLayout[1] = 0; + qc.setLogicalQubitGarbage(1); + qc.outputPermutation.erase(0); + qc.outputPermutation[1] = 0; + + for (Qubit i = 0; i < nq; i++) { + // Hadamard + qc.h(1); + + // normalize angle + const auto angle = + std::remainder(static_cast(1ULL << (nq - 1 - i)) * lambda, 2.0); + + // controlled phase rotation + qc.cp(angle * PI, 1, 0); + + // hybrid quantum-classical inverse QFT + for (std::size_t j = 0; j < i; j++) { + auto iQFTLambda = -PI / static_cast(1ULL << (i - j)); + qc.classicControlled(P, 1, {j, 1U}, 1U, Eq, {iQFTLambda}); } + qc.h(1); + + // measure result + qc.measure(1, i); - // measure results - for (std::size_t i = 0; i < nqubits - 1; i++) { - measure(static_cast(i + 1), i); - outputPermutation[static_cast(i + 1)] = static_cast(i); + // reset qubit if not finished + if (i < nq - 1) { + qc.reset(1); } } } + +auto createIterativeQPE(const Qubit nq, const bool exact, + const std::size_t seed) -> QuantumComputation { + auto qc = QuantumComputation(0, 0, seed); + const auto lambda = exact ? createExactPhase(nq, qc.getGenerator()) + : createInexactPhase(nq, qc.getGenerator()); + constructIterativeQPECircuit(qc, lambda, nq); + return qc; +} + +auto createIterativeQPE(const fp lambda, const Qubit precision) + -> QuantumComputation { + auto qc = QuantumComputation(); + constructIterativeQPECircuit(qc, lambda, precision); + return qc; +} } // namespace qc diff --git a/src/algorithms/RandomCliffordCircuit.cpp b/src/algorithms/RandomCliffordCircuit.cpp index 4aa718ab2..e61804876 100644 --- a/src/algorithms/RandomCliffordCircuit.cpp +++ b/src/algorithms/RandomCliffordCircuit.cpp @@ -13,74 +13,16 @@ #include "ir/QuantumComputation.hpp" #include "ir/operations/CompoundOperation.hpp" -#include -#include #include #include -#include -#include #include namespace qc { -RandomCliffordCircuit::RandomCliffordCircuit(const std::size_t nq, - const std::size_t d, - const std::size_t s) - : depth(d), seed(s) { - addQubitRegister(nq); - addClassicalRegister(nq); - - std::mt19937_64 generator; - if (seed == 0) { - // this is probably overkill but better safe than sorry - std::array - randomData{}; - std::random_device rd; - std::generate(std::begin(randomData), std::end(randomData), std::ref(rd)); - std::seed_seq seeds(std::begin(randomData), std::end(randomData)); - generator.seed(seeds); - } else { - generator.seed(seed); - } - std::uniform_int_distribution distribution(0, 11520); - cliffordGenerator = [&]() { return distribution(generator); }; - - for (std::size_t l = 0; l < depth; ++l) { - if (nqubits == 1) { - append1QClifford(cliffordGenerator(), 0); - } else if (nqubits == 2) { - append2QClifford(cliffordGenerator(), 0, 1); - } else { - if (l % 2 != 0) { - for (std::size_t i = 1; i < nqubits - 1; i += 2) { - append2QClifford(cliffordGenerator(), static_cast(i), - static_cast(i + 1)); - } - } else { - for (std::size_t i = 0; i < nqubits - 1; i += 2) { - append2QClifford(cliffordGenerator(), static_cast(i), - static_cast(i + 1)); - } - } - } - } -} - -std::ostream& RandomCliffordCircuit::printStatistics(std::ostream& os) const { - os << "Random Clifford circuit statistics:\n"; - os << "\tn: " << nqubits << "\n"; - os << "\tm: " << getNindividualOps() << "\n"; - os << "\tdepth: " << depth << "\n"; - os << "\tseed: " << seed << "\n"; - os << "--------------" - << "\n"; - return os; -} - -void RandomCliffordCircuit::append1QClifford(const std::uint16_t idx, - const Qubit target) { +auto append1QClifford(QuantumComputation& circ, const std::uint16_t idx, + const Qubit target) -> void { const auto id = static_cast(idx % 24); - auto qc = QuantumComputation(nqubits); + auto qc = QuantumComputation(circ.getNqubits()); // Hadamard if ((id / 12 % 2) != 0) { qc.h(target); @@ -103,17 +45,16 @@ void RandomCliffordCircuit::append1QClifford(const std::uint16_t idx, } else if (id % 4 == 3) { qc.y(target); } - emplace_back(qc.asCompoundOperation()); + circ.emplace_back(qc.asCompoundOperation()); } -void RandomCliffordCircuit::append2QClifford(const std::uint16_t idx, - const Qubit control, - const Qubit target) { +auto append2QClifford(QuantumComputation& circ, const std::uint16_t idx, + const Qubit control, const Qubit target) -> void { auto id = static_cast(idx % 11520); const auto pauliIdx = static_cast(id % 16); id /= 16; - auto qc = QuantumComputation(nqubits); + auto qc = QuantumComputation(circ.getNqubits()); if (id < 36) { // single-qubit Cliffords if ((id / 9 % 2) != 0) { @@ -257,6 +198,36 @@ void RandomCliffordCircuit::append2QClifford(const std::uint16_t idx, qc.y(target); } - emplace_back(qc.asCompoundOperation()); + circ.emplace_back(qc.asCompoundOperation()); +} + +auto createRandomCliffordCircuit(const Qubit nq, const std::size_t depth, + const std::size_t seed) -> QuantumComputation { + auto qc = QuantumComputation(nq, nq, seed); + qc.setName("random_clifford_" + std::to_string(nq) + "_" + + std::to_string(depth) + "_" + std::to_string(seed)); + + std::uniform_int_distribution distribution(0, 11520); + auto cliffordGenerator = [&]() { return distribution(qc.getGenerator()); }; + + for (std::size_t l = 0; l < depth; ++l) { + if (nq == 1) { + append1QClifford(qc, cliffordGenerator(), 0); + } else if (nq == 2) { + append2QClifford(qc, cliffordGenerator(), 0, 1); + } else { + if (l % 2 != 0) { + for (Qubit i = 1; i < nq - 1; i += 2) { + append2QClifford(qc, cliffordGenerator(), i, i + 1); + } + } else { + for (Qubit i = 0; i < nq - 1; i += 2) { + append2QClifford(qc, cliffordGenerator(), i, i + 1); + } + } + } + } + return qc; } + } // namespace qc diff --git a/src/algorithms/WState.cpp b/src/algorithms/WState.cpp index 157a98c85..1cbe7b12b 100644 --- a/src/algorithms/WState.cpp +++ b/src/algorithms/WState.cpp @@ -24,17 +24,20 @@ void fGate(QuantumComputation& qc, const Qubit i, const Qubit j, const Qubit k, qc.ry(theta, j); } -WState::WState(const Qubit nq) : QuantumComputation(nq, nq) { - name = "wstate_" + std::to_string(nq); +auto createWState(const Qubit nq) -> QuantumComputation { + auto qc = QuantumComputation(nq, nq); + qc.setName("wstate_" + std::to_string(nq)); - x(nq - 1); + qc.x(nq - 1); for (Qubit m = 1; m < nq; m++) { - fGate(*this, nq - m, nq - m - 1, nq, m); + fGate(qc, nq - m, nq - m - 1, nq, m); } for (Qubit k = nq - 1; k > 0; k--) { - cx(k - 1, k); + qc.cx(k - 1, k); } + + return qc; } } // namespace qc diff --git a/test/algorithms/eval_dynamic_circuits.cpp b/test/algorithms/eval_dynamic_circuits.cpp index 498236325..d90aaae4b 100644 --- a/test/algorithms/eval_dynamic_circuits.cpp +++ b/test/algorithms/eval_dynamic_circuits.cpp @@ -28,14 +28,14 @@ #include #include -class DynamicCircuitEvalExactQPE : public testing::TestWithParam { +class DynamicCircuitEvalExactQPE : public testing::TestWithParam { protected: - std::size_t precision{}; + qc::Qubit precision{}; qc::fp theta{}; std::size_t expectedResult{}; std::string expectedResultRepresentation; - std::unique_ptr qpe; - std::unique_ptr iqpe; + qc::QuantumComputation qpe; + qc::QuantumComputation iqpe; std::size_t qpeNgates{}; std::size_t iqpeNgates{}; std::unique_ptr> dd; @@ -47,14 +47,16 @@ class DynamicCircuitEvalExactQPE : public testing::TestWithParam { dd = std::make_unique>(precision + 1); - qpe = std::make_unique(precision); + qpe = qc::createQPE(precision); // remove final measurements so that the functionality is unitary - qc::CircuitOptimizer::removeFinalMeasurements(*qpe); - qpeNgates = qpe->getNindividualOps(); + qc::CircuitOptimizer::removeFinalMeasurements(qpe); + qpeNgates = qpe.getNindividualOps(); - const auto lambda = dynamic_cast(qpe.get())->lambda; - iqpe = std::make_unique(lambda, precision, true); - iqpeNgates = iqpe->getNindividualOps(); + // extract lambda from QPE global phase + const auto lambda = qpe.getGlobalPhase(); + + iqpe = qc::createIterativeQPE(lambda, precision); + iqpeNgates = iqpe.getNindividualOps(); std::cout << "Estimating lambda = " << lambda << "π up to " << precision << "-bit precision.\n"; @@ -98,7 +100,7 @@ class DynamicCircuitEvalExactQPE : public testing::TestWithParam { }; INSTANTIATE_TEST_SUITE_P( - Eval, DynamicCircuitEvalExactQPE, testing::Range(1U, 64U, 5U), + Eval, DynamicCircuitEvalExactQPE, testing::Range(1U, 64U, 5U), [](const testing::TestParamInfo& inf) { const auto nqubits = inf.param; @@ -113,28 +115,27 @@ INSTANTIATE_TEST_SUITE_P( }); TEST_P(DynamicCircuitEvalExactQPE, UnitaryTransformation) { - qpe->reorderOperations(); + qpe.reorderOperations(); const auto start = std::chrono::steady_clock::now(); // transform dynamic circuit to unitary circuit by first eliminating reset // operations and afterwards deferring measurements to the end of the circuit - qc::CircuitOptimizer::eliminateResets(*iqpe); - qc::CircuitOptimizer::deferMeasurements(*iqpe); + qc::CircuitOptimizer::eliminateResets(iqpe); + qc::CircuitOptimizer::deferMeasurements(iqpe); // remove final measurements in order to just obtain the unitary functionality - qc::CircuitOptimizer::removeFinalMeasurements(*iqpe); - iqpe->reorderOperations(); + qc::CircuitOptimizer::removeFinalMeasurements(iqpe); + iqpe.reorderOperations(); const auto finishedTransformation = std::chrono::steady_clock::now(); qc::MatrixDD e = dd->makeIdent(); dd->incRef(e); - auto leftIt = qpe->begin(); - auto rightIt = iqpe->begin(); + auto leftIt = qpe.begin(); + auto rightIt = iqpe.begin(); - while (leftIt != qpe->end() && rightIt != iqpe->end()) { - auto multLeft = dd->multiply(getDD((*leftIt).get(), *dd), e); - auto multRight = - dd->multiply(multLeft, getInverseDD((*rightIt).get(), *dd)); + while (leftIt != qpe.end() && rightIt != iqpe.end()) { + auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); + auto multRight = dd->multiply(multLeft, getInverseDD(rightIt->get(), *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -145,8 +146,8 @@ TEST_P(DynamicCircuitEvalExactQPE, UnitaryTransformation) { ++rightIt; } - while (leftIt != qpe->end()) { - auto multLeft = dd->multiply(getDD((*leftIt).get(), *dd), e); + while (leftIt != qpe.end()) { + auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); dd->incRef(multLeft); dd->decRef(e); e = multLeft; @@ -156,8 +157,8 @@ TEST_P(DynamicCircuitEvalExactQPE, UnitaryTransformation) { ++leftIt; } - while (rightIt != iqpe->end()) { - auto multRight = dd->multiply(e, getInverseDD((*rightIt).get(), *dd)); + while (rightIt != iqpe.end()) { + auto multRight = dd->multiply(e, getInverseDD(rightIt->get(), *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -175,7 +176,7 @@ TEST_P(DynamicCircuitEvalExactQPE, UnitaryTransformation) { .count(); std::stringstream ss{}; - ss << "qpe_exact,transformation," << qpe->getNqubits() << "," << qpeNgates + ss << "qpe_exact,transformation," << qpe.getNqubits() << "," << qpeNgates << ",2," << iqpeNgates << "," << preprocessing << "," << verification; std::cout << ss.str() << "\n"; @@ -185,18 +186,18 @@ TEST_P(DynamicCircuitEvalExactQPE, UnitaryTransformation) { TEST_P(DynamicCircuitEvalExactQPE, ProbabilityExtraction) { // generate DD of QPE circuit via simulation const auto start = std::chrono::steady_clock::now(); - auto e = simulate(qpe.get(), dd->makeZeroState(qpe->getNqubits()), *dd); + const auto e = simulate(&qpe, dd->makeZeroState(qpe.getNqubits()), *dd); const auto simulationEnd = std::chrono::steady_clock::now(); // extract measurement probabilities from IQPE simulations dd::SparsePVec probs{}; - extractProbabilityVector(iqpe.get(), dd->makeZeroState(iqpe->getNqubits()), - probs, *dd); + extractProbabilityVector(&iqpe, dd->makeZeroState(iqpe.getNqubits()), probs, + *dd); const auto extractionEnd = std::chrono::steady_clock::now(); // compare outcomes - auto fidelity = - dd->fidelityOfMeasurementOutcomes(e, probs, qpe->outputPermutation); + const auto fidelity = dd::Package<>::fidelityOfMeasurementOutcomes( + e, probs, qpe.outputPermutation); const auto comparisonEnd = std::chrono::steady_clock::now(); const auto simulation = @@ -209,25 +210,24 @@ TEST_P(DynamicCircuitEvalExactQPE, ProbabilityExtraction) { std::chrono::duration(comparisonEnd - start).count(); std::stringstream ss{}; - ss << "qpe_exact,extraction," << qpe->getNqubits() << "," << qpeNgates - << ",2," << iqpeNgates << "," << simulation << "," << extraction << "," + ss << "qpe_exact,extraction," << qpe.getNqubits() << "," << qpeNgates << ",2," + << iqpeNgates << "," << simulation << "," << extraction << "," << comparison << "," << total; std::cout << ss.str() << "\n"; EXPECT_NEAR(fidelity, 1.0, 1e-4); } -class DynamicCircuitEvalInexactQPE - : public testing::TestWithParam { +class DynamicCircuitEvalInexactQPE : public testing::TestWithParam { protected: - std::size_t precision{}; + qc::Qubit precision{}; dd::fp theta{}; std::size_t expectedResult{}; std::string expectedResultRepresentation; std::size_t secondExpectedResult{}; std::string secondExpectedResultRepresentation; - std::unique_ptr qpe; - std::unique_ptr iqpe; + qc::QuantumComputation qpe; + qc::QuantumComputation iqpe; std::size_t qpeNgates{}; std::size_t iqpeNgates{}; std::unique_ptr> dd; @@ -239,14 +239,16 @@ class DynamicCircuitEvalInexactQPE dd = std::make_unique>(precision + 1); - qpe = std::make_unique(precision, false); + qpe = qc::createQPE(precision, false); // remove final measurements so that the functionality is unitary - qc::CircuitOptimizer::removeFinalMeasurements(*qpe); - qpeNgates = qpe->getNindividualOps(); + qc::CircuitOptimizer::removeFinalMeasurements(qpe); + qpeNgates = qpe.getNindividualOps(); + + // extract lambda from QPE global phase + const auto lambda = qpe.getGlobalPhase(); - const auto lambda = dynamic_cast(qpe.get())->lambda; - iqpe = std::make_unique(lambda, precision, true); - iqpeNgates = iqpe->getNindividualOps(); + iqpe = qc::createIterativeQPE(lambda, precision); + iqpeNgates = iqpe.getNindividualOps(); std::cout << "Estimating lambda = " << lambda << "π up to " << precision << "-bit precision.\n"; @@ -301,44 +303,43 @@ class DynamicCircuitEvalInexactQPE } }; -INSTANTIATE_TEST_SUITE_P(Eval, DynamicCircuitEvalInexactQPE, - testing::Range(1U, 15U, 3U), - [](const testing::TestParamInfo< - DynamicCircuitEvalInexactQPE::ParamType>& inf) { - const auto nqubits = inf.param; - std::stringstream ss{}; - ss << nqubits; - if (nqubits == 1) { - ss << "_qubit"; - } else { - ss << "_qubits"; - } - return ss.str(); - }); +INSTANTIATE_TEST_SUITE_P( + Eval, DynamicCircuitEvalInexactQPE, testing::Range(1U, 15U, 3U), + [](const testing::TestParamInfo& + inf) { + const auto nqubits = inf.param; + std::stringstream ss{}; + ss << nqubits; + if (nqubits == 1) { + ss << "_qubit"; + } else { + ss << "_qubits"; + } + return ss.str(); + }); TEST_P(DynamicCircuitEvalInexactQPE, UnitaryTransformation) { - qpe->reorderOperations(); + qpe.reorderOperations(); const auto start = std::chrono::steady_clock::now(); // transform dynamic circuit to unitary circuit by first eliminating reset // operations and afterwards deferring measurements to the end of the circuit - qc::CircuitOptimizer::eliminateResets(*iqpe); - qc::CircuitOptimizer::deferMeasurements(*iqpe); + qc::CircuitOptimizer::eliminateResets(iqpe); + qc::CircuitOptimizer::deferMeasurements(iqpe); // remove final measurements in order to just obtain the unitary functionality - qc::CircuitOptimizer::removeFinalMeasurements(*iqpe); - iqpe->reorderOperations(); + qc::CircuitOptimizer::removeFinalMeasurements(iqpe); + iqpe.reorderOperations(); const auto finishedTransformation = std::chrono::steady_clock::now(); qc::MatrixDD e = dd->makeIdent(); dd->incRef(e); - auto leftIt = qpe->begin(); - auto rightIt = iqpe->begin(); + auto leftIt = qpe.begin(); + auto rightIt = iqpe.begin(); - while (leftIt != qpe->end() && rightIt != iqpe->end()) { - auto multLeft = dd->multiply(getDD((*leftIt).get(), *dd), e); - auto multRight = - dd->multiply(multLeft, getInverseDD((*rightIt).get(), *dd)); + while (leftIt != qpe.end() && rightIt != iqpe.end()) { + auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); + auto multRight = dd->multiply(multLeft, getInverseDD(rightIt->get(), *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -349,8 +350,8 @@ TEST_P(DynamicCircuitEvalInexactQPE, UnitaryTransformation) { ++rightIt; } - while (leftIt != qpe->end()) { - auto multLeft = dd->multiply(getDD((*leftIt).get(), *dd), e); + while (leftIt != qpe.end()) { + auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); dd->incRef(multLeft); dd->decRef(e); e = multLeft; @@ -360,8 +361,8 @@ TEST_P(DynamicCircuitEvalInexactQPE, UnitaryTransformation) { ++leftIt; } - while (rightIt != iqpe->end()) { - auto multRight = dd->multiply(e, getInverseDD((*rightIt).get(), *dd)); + while (rightIt != iqpe.end()) { + auto multRight = dd->multiply(e, getInverseDD(rightIt->get(), *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -379,7 +380,7 @@ TEST_P(DynamicCircuitEvalInexactQPE, UnitaryTransformation) { .count(); std::stringstream ss{}; - ss << "qpe_inexact,transformation," << qpe->getNqubits() << "," << qpeNgates + ss << "qpe_inexact,transformation," << qpe.getNqubits() << "," << qpeNgates << ",2," << iqpeNgates << "," << preprocessing << "," << verification; std::cout << ss.str() << "\n"; @@ -390,19 +391,19 @@ TEST_P(DynamicCircuitEvalInexactQPE, ProbabilityExtraction) { const auto start = std::chrono::steady_clock::now(); // extract measurement probabilities from IQPE simulations dd::SparsePVec probs{}; - extractProbabilityVector(iqpe.get(), dd->makeZeroState(iqpe->getNqubits()), - probs, *dd); + extractProbabilityVector(&iqpe, dd->makeZeroState(iqpe.getNqubits()), probs, + *dd); const auto extractionEnd = std::chrono::steady_clock::now(); std::cout << "---- extraction done ----\n"; // generate DD of QPE circuit via simulation - auto e = simulate(qpe.get(), dd->makeZeroState(qpe->getNqubits()), *dd); + auto e = simulate(&qpe, dd->makeZeroState(qpe.getNqubits()), *dd); const auto simulationEnd = std::chrono::steady_clock::now(); std::cout << "---- sim done ----\n"; // compare outcomes - auto fidelity = - dd->fidelityOfMeasurementOutcomes(e, probs, qpe->outputPermutation); + const auto fidelity = dd::Package<>::fidelityOfMeasurementOutcomes( + e, probs, qpe.outputPermutation); const auto comparisonEnd = std::chrono::steady_clock::now(); const auto extraction = @@ -415,7 +416,7 @@ TEST_P(DynamicCircuitEvalInexactQPE, ProbabilityExtraction) { std::chrono::duration(comparisonEnd - start).count(); std::stringstream ss{}; - ss << "qpe_inexact,extraction," << qpe->getNqubits() << "," << qpeNgates + ss << "qpe_inexact,extraction," << qpe.getNqubits() << "," << qpeNgates << ",2," << iqpeNgates << "," << simulation << "," << extraction << "," << comparison << "," << total; std::cout << ss.str() << "\n"; @@ -423,11 +424,11 @@ TEST_P(DynamicCircuitEvalInexactQPE, ProbabilityExtraction) { EXPECT_NEAR(fidelity, 1.0, 1e-4); } -class DynamicCircuitEvalBV : public testing::TestWithParam { +class DynamicCircuitEvalBV : public testing::TestWithParam { protected: - std::size_t bitwidth{}; - std::unique_ptr bv; - std::unique_ptr dbv; + qc::Qubit bitwidth{}; + qc::QuantumComputation bv; + qc::QuantumComputation dbv; std::size_t bvNgates{}; std::size_t dbvNgates{}; std::unique_ptr> dd; @@ -439,26 +440,23 @@ class DynamicCircuitEvalBV : public testing::TestWithParam { dd = std::make_unique>(bitwidth + 1); - bv = std::make_unique(bitwidth); + bv = qc::createBernsteinVazirani(bitwidth); // remove final measurements so that the functionality is unitary - qc::CircuitOptimizer::removeFinalMeasurements(*bv); - bvNgates = bv->getNindividualOps(); + qc::CircuitOptimizer::removeFinalMeasurements(bv); + bvNgates = bv.getNindividualOps(); - const auto s = dynamic_cast(bv.get())->s; - dbv = std::make_unique(s, bitwidth, true); - dbvNgates = dbv->getNindividualOps(); - - const auto expected = - dynamic_cast(bv.get())->expected; + const auto expected = bv.getName().substr(3); + dbv = + qc::createIterativeBernsteinVazirani(qc::BitString(expected), bitwidth); + dbvNgates = dbv.getNindividualOps(); std::cout << "Hidden bitstring: " << expected << " (" << bitwidth << " qubits)\n"; } }; INSTANTIATE_TEST_SUITE_P( - Eval, DynamicCircuitEvalBV, testing::Range(1U, 64U, 5U), - [](const testing::TestParamInfo& - inf) { + Eval, DynamicCircuitEvalBV, testing::Range(1U, 64U, 5U), + [](const testing::TestParamInfo& inf) { const auto nqubits = inf.param; std::stringstream ss{}; ss << nqubits; @@ -471,28 +469,27 @@ INSTANTIATE_TEST_SUITE_P( }); TEST_P(DynamicCircuitEvalBV, UnitaryTransformation) { - bv->reorderOperations(); + bv.reorderOperations(); const auto start = std::chrono::steady_clock::now(); // transform dynamic circuit to unitary circuit by first eliminating reset // operations and afterwards deferring measurements to the end of the circuit - qc::CircuitOptimizer::eliminateResets(*dbv); - qc::CircuitOptimizer::deferMeasurements(*dbv); + qc::CircuitOptimizer::eliminateResets(dbv); + qc::CircuitOptimizer::deferMeasurements(dbv); // remove final measurements in order to just obtain the unitary functionality - qc::CircuitOptimizer::removeFinalMeasurements(*dbv); - dbv->reorderOperations(); + qc::CircuitOptimizer::removeFinalMeasurements(dbv); + dbv.reorderOperations(); const auto finishedTransformation = std::chrono::steady_clock::now(); qc::MatrixDD e = dd->makeIdent(); dd->incRef(e); - auto leftIt = bv->begin(); - auto rightIt = dbv->begin(); + auto leftIt = bv.begin(); + auto rightIt = dbv.begin(); - while (leftIt != bv->end() && rightIt != dbv->end()) { - auto multLeft = dd->multiply(getDD((*leftIt).get(), *dd), e); - auto multRight = - dd->multiply(multLeft, getInverseDD((*rightIt).get(), *dd)); + while (leftIt != bv.end() && rightIt != dbv.end()) { + auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); + auto multRight = dd->multiply(multLeft, getInverseDD(rightIt->get(), *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -503,8 +500,8 @@ TEST_P(DynamicCircuitEvalBV, UnitaryTransformation) { ++rightIt; } - while (leftIt != bv->end()) { - auto multLeft = dd->multiply(getDD((*leftIt).get(), *dd), e); + while (leftIt != bv.end()) { + auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); dd->incRef(multLeft); dd->decRef(e); e = multLeft; @@ -514,8 +511,8 @@ TEST_P(DynamicCircuitEvalBV, UnitaryTransformation) { ++leftIt; } - while (rightIt != dbv->end()) { - auto multRight = dd->multiply(e, getInverseDD((*rightIt).get(), *dd)); + while (rightIt != dbv.end()) { + auto multRight = dd->multiply(e, getInverseDD(rightIt->get(), *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -533,7 +530,7 @@ TEST_P(DynamicCircuitEvalBV, UnitaryTransformation) { .count(); std::stringstream ss{}; - ss << "bv,transformation," << bv->getNqubits() << "," << bvNgates << ",2," + ss << "bv,transformation," << bv.getNqubits() << "," << bvNgates << ",2," << dbvNgates << "," << preprocessing << "," << verification; std::cout << ss.str() << "\n"; @@ -543,18 +540,18 @@ TEST_P(DynamicCircuitEvalBV, UnitaryTransformation) { TEST_P(DynamicCircuitEvalBV, ProbabilityExtraction) { // generate DD of QPE circuit via simulation const auto start = std::chrono::steady_clock::now(); - auto e = simulate(bv.get(), dd->makeZeroState(bv->getNqubits()), *dd); + const auto e = simulate(&bv, dd->makeZeroState(bv.getNqubits()), *dd); const auto simulationEnd = std::chrono::steady_clock::now(); // extract measurement probabilities from IQPE simulations dd::SparsePVec probs{}; - extractProbabilityVector(dbv.get(), dd->makeZeroState(dbv->getNqubits()), - probs, *dd); + extractProbabilityVector(&dbv, dd->makeZeroState(dbv.getNqubits()), probs, + *dd); const auto extractionEnd = std::chrono::steady_clock::now(); // compare outcomes - auto fidelity = - dd->fidelityOfMeasurementOutcomes(e, probs, bv->outputPermutation); + const auto fidelity = dd::Package<>::fidelityOfMeasurementOutcomes( + e, probs, bv.outputPermutation); const auto comparisonEnd = std::chrono::steady_clock::now(); const auto simulation = @@ -567,7 +564,7 @@ TEST_P(DynamicCircuitEvalBV, ProbabilityExtraction) { std::chrono::duration(comparisonEnd - start).count(); std::stringstream ss{}; - ss << "bv,extraction," << bv->getNqubits() << "," << bvNgates << ",2," + ss << "bv,extraction," << bv.getNqubits() << "," << bvNgates << ",2," << dbvNgates << "," << simulation << "," << extraction << "," << comparison << "," << total; std::cout << ss.str() << "\n"; @@ -575,11 +572,11 @@ TEST_P(DynamicCircuitEvalBV, ProbabilityExtraction) { EXPECT_NEAR(fidelity, 1.0, 1e-4); } -class DynamicCircuitEvalQFT : public testing::TestWithParam { +class DynamicCircuitEvalQFT : public testing::TestWithParam { protected: - std::size_t precision{}; - std::unique_ptr qft; - std::unique_ptr dqft; + qc::Qubit precision{}; + qc::QuantumComputation qft; + qc::QuantumComputation dqft; std::size_t qftNgates{}; std::size_t dqftNgates{}; std::unique_ptr> dd; @@ -591,20 +588,19 @@ class DynamicCircuitEvalQFT : public testing::TestWithParam { dd = std::make_unique>(precision); - qft = std::make_unique(precision); + qft = qc::createQFT(precision); // remove final measurements so that the functionality is unitary - qc::CircuitOptimizer::removeFinalMeasurements(*qft); - qftNgates = qft->getNindividualOps(); + qc::CircuitOptimizer::removeFinalMeasurements(qft); + qftNgates = qft.getNindividualOps(); - dqft = std::make_unique(precision, true, true); - dqftNgates = dqft->getNindividualOps(); + dqft = qc::createIterativeQFT(precision); + dqftNgates = dqft.getNindividualOps(); } }; INSTANTIATE_TEST_SUITE_P( - Eval, DynamicCircuitEvalQFT, testing::Range(1U, 65U, 5U), - [](const testing::TestParamInfo& - inf) { + Eval, DynamicCircuitEvalQFT, testing::Range(1U, 65U, 5U), + [](const testing::TestParamInfo& inf) { const auto nqubits = inf.param; std::stringstream ss{}; ss << nqubits; @@ -617,28 +613,27 @@ INSTANTIATE_TEST_SUITE_P( }); TEST_P(DynamicCircuitEvalQFT, UnitaryTransformation) { - qft->reorderOperations(); + qft.reorderOperations(); const auto start = std::chrono::steady_clock::now(); // transform dynamic circuit to unitary circuit by first eliminating reset // operations and afterwards deferring measurements to the end of the circuit - qc::CircuitOptimizer::eliminateResets(*dqft); - qc::CircuitOptimizer::deferMeasurements(*dqft); + qc::CircuitOptimizer::eliminateResets(dqft); + qc::CircuitOptimizer::deferMeasurements(dqft); // remove final measurements in order to just obtain the unitary functionality - qc::CircuitOptimizer::removeFinalMeasurements(*dqft); - dqft->reorderOperations(); + qc::CircuitOptimizer::removeFinalMeasurements(dqft); + dqft.reorderOperations(); const auto finishedTransformation = std::chrono::steady_clock::now(); qc::MatrixDD e = dd->makeIdent(); dd->incRef(e); - auto leftIt = qft->begin(); - auto rightIt = dqft->begin(); + auto leftIt = qft.begin(); + auto rightIt = dqft.begin(); - while (leftIt != qft->end() && rightIt != dqft->end()) { - auto multLeft = dd->multiply(getDD((*leftIt).get(), *dd), e); - auto multRight = - dd->multiply(multLeft, getInverseDD((*rightIt).get(), *dd)); + while (leftIt != qft.end() && rightIt != dqft.end()) { + auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); + auto multRight = dd->multiply(multLeft, getInverseDD(rightIt->get(), *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -649,8 +644,8 @@ TEST_P(DynamicCircuitEvalQFT, UnitaryTransformation) { ++rightIt; } - while (leftIt != qft->end()) { - auto multLeft = dd->multiply(getDD((*leftIt).get(), *dd), e); + while (leftIt != qft.end()) { + auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); dd->incRef(multLeft); dd->decRef(e); e = multLeft; @@ -660,8 +655,8 @@ TEST_P(DynamicCircuitEvalQFT, UnitaryTransformation) { ++leftIt; } - while (rightIt != dqft->end()) { - auto multRight = dd->multiply(e, getInverseDD((*rightIt).get(), *dd)); + while (rightIt != dqft.end()) { + auto multRight = dd->multiply(e, getInverseDD(rightIt->get(), *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -679,7 +674,7 @@ TEST_P(DynamicCircuitEvalQFT, UnitaryTransformation) { .count(); std::stringstream ss{}; - ss << "qft,transformation," << qft->getNqubits() << "," << qftNgates << ",1," + ss << "qft,transformation," << qft.getNqubits() << "," << qftNgates << ",1," << dqftNgates << "," << preprocessing << "," << verification; std::cout << ss.str() << "\n"; @@ -689,22 +684,22 @@ TEST_P(DynamicCircuitEvalQFT, UnitaryTransformation) { TEST_P(DynamicCircuitEvalQFT, ProbabilityExtraction) { // generate DD of QPE circuit via simulation const auto start = std::chrono::steady_clock::now(); - auto e = simulate(qft.get(), dd->makeZeroState(qft->getNqubits()), *dd); + auto e = simulate(&qft, dd->makeZeroState(qft.getNqubits()), *dd); const auto simulationEnd = std::chrono::steady_clock::now(); const auto simulation = std::chrono::duration(simulationEnd - start).count(); std::stringstream ss{}; // extract measurement probabilities from IQPE simulations - if (qft->getNqubits() <= 15) { + if (qft.getNqubits() <= 15) { dd::SparsePVec probs{}; - extractProbabilityVector(dqft.get(), dd->makeZeroState(dqft->getNqubits()), - probs, *dd); + extractProbabilityVector(&dqft, dd->makeZeroState(dqft.getNqubits()), probs, + *dd); const auto extractionEnd = std::chrono::steady_clock::now(); // compare outcomes - auto fidelity = - dd->fidelityOfMeasurementOutcomes(e, probs, qft->outputPermutation); + const auto fidelity = dd::Package<>::fidelityOfMeasurementOutcomes( + e, probs, qft.outputPermutation); const auto comparisonEnd = std::chrono::steady_clock::now(); const auto extraction = std::chrono::duration(extractionEnd - simulationEnd).count(); @@ -713,13 +708,13 @@ TEST_P(DynamicCircuitEvalQFT, ProbabilityExtraction) { const auto total = std::chrono::duration(comparisonEnd - start).count(); EXPECT_NEAR(fidelity, 1.0, 1e-4); - ss << "qft,extraction," << qft->getNqubits() << "," << qftNgates << ",1," + ss << "qft,extraction," << qft.getNqubits() << "," << qftNgates << ",1," << dqftNgates << "," << extraction << "," << simulation << "," << comparison << "," << total; std::cout << ss.str() << "\n"; } else { - ss << "qft,extraction," << qft->getNqubits() << "," << qftNgates << ",1," + ss << "qft,extraction," << qft.getNqubits() << "," << qftNgates << ",1," << dqftNgates << ",," << simulation << ",,,"; std::cout << ss.str() << "\n"; } diff --git a/test/algorithms/test_bernsteinvazirani.cpp b/test/algorithms/test_bernsteinvazirani.cpp index 831b88d82..014153913 100644 --- a/test/algorithms/test_bernsteinvazirani.cpp +++ b/test/algorithms/test_bernsteinvazirani.cpp @@ -47,15 +47,18 @@ TEST_P(BernsteinVazirani, FunctionTest) { auto s = qc::BitString(GetParam()); // construct Bernstein Vazirani circuit - const auto qc = qc::BernsteinVazirani(s); + const auto qc = qc::createBernsteinVazirani(s); qc.printStatistics(std::cout); // simulate the circuit constexpr std::size_t shots = 1024; const auto measurements = dd::sample(qc, shots); + // extract expected bitstring from circuit name + const auto expected = qc.getName().substr(3); + // expect to obtain the hidden bitstring with certainty - EXPECT_EQ(measurements.at(qc.expected), shots); + EXPECT_EQ(measurements.at(expected), shots); } TEST_P(BernsteinVazirani, FunctionTestDynamic) { @@ -63,47 +66,56 @@ TEST_P(BernsteinVazirani, FunctionTestDynamic) { const auto s = qc::BitString(GetParam()); // construct Bernstein Vazirani circuit - const auto qc = qc::BernsteinVazirani(s, true); + const auto qc = qc::createIterativeBernsteinVazirani(s); qc.printStatistics(std::cout); // simulate the circuit constexpr std::size_t shots = 1024; const auto measurements = dd::sample(qc, shots); + // extract expected bitstring from circuit name + const auto expected = qc.getName().substr(13); + // expect to obtain the hidden bitstring with certainty - EXPECT_EQ(measurements.at(qc.expected), shots); + EXPECT_EQ(measurements.at(expected), shots); } TEST_F(BernsteinVazirani, LargeCircuit) { constexpr std::size_t nq = 127; - const auto qc = qc::BernsteinVazirani(nq); + const auto qc = qc::createBernsteinVazirani(nq); // simulate the circuit constexpr std::size_t shots = 1024; const auto measurements = dd::sample(qc, shots); // expect to obtain the hidden bitstring with certainty - EXPECT_EQ(measurements.at(qc.expected), shots); + const auto expected = qc.getName().substr(3); + + // expect to obtain the hidden bitstring with certainty + EXPECT_EQ(measurements.at(expected), shots); } TEST_F(BernsteinVazirani, DynamicCircuit) { constexpr std::size_t nq = 127; - const auto qc = qc::BernsteinVazirani(nq, true); + const auto qc = qc::createIterativeBernsteinVazirani(nq); // simulate the circuit constexpr std::size_t shots = 1024; const auto measurements = dd::sample(qc, shots); + // extract expected bitstring from circuit name + const auto expected = qc.getName().substr(13); + // expect to obtain the hidden bitstring with certainty - EXPECT_EQ(measurements.at(qc.expected), shots); + EXPECT_EQ(measurements.at(expected), shots); } TEST_P(BernsteinVazirani, DynamicEquivalenceSimulation) { // get hidden bitstring - auto s = qc::BitString(GetParam()); + const auto s = qc::BitString(GetParam()); // create standard BV circuit - auto bv = qc::BernsteinVazirani(s); + auto bv = qc::createBernsteinVazirani(s); auto dd = std::make_unique>(bv.getNqubits()); @@ -114,7 +126,7 @@ TEST_P(BernsteinVazirani, DynamicEquivalenceSimulation) { auto e = dd::simulate(&bv, dd->makeZeroState(bv.getNqubits()), *dd); // create dynamic BV circuit - auto dbv = qc::BernsteinVazirani(s, true); + auto dbv = qc::createIterativeBernsteinVazirani(s); // transform dynamic circuits by first eliminating reset operations and // afterward deferring measurements diff --git a/test/algorithms/test_entanglement.cpp b/test/algorithms/test_entanglement.cpp index dbd96308c..2c09bcd09 100644 --- a/test/algorithms/test_entanglement.cpp +++ b/test/algorithms/test_entanglement.cpp @@ -7,32 +7,30 @@ * Licensed under the MIT License */ -#include "algorithms/Entanglement.hpp" +#include "Definitions.hpp" +#include "algorithms/GHZState.hpp" #include "dd/DDDefinitions.hpp" #include "dd/FunctionalityConstruction.hpp" #include "dd/Package.hpp" #include "dd/Simulation.hpp" -#include #include -#include #include -#include #include -class Entanglement : public testing::TestWithParam { +class Entanglement : public testing::TestWithParam { protected: void TearDown() override {} void SetUp() override { nq = GetParam(); dd = std::make_unique>(nq); } - std::size_t nq{}; + qc::Qubit nq{}; std::unique_ptr> dd; }; INSTANTIATE_TEST_SUITE_P( - Entanglement, Entanglement, testing::Range(2U, 90U, 7U), + Entanglement, Entanglement, testing::Range(2U, 90U, 7U), [](const testing::TestParamInfo& inf) { // Generate names for test cases const auto nqubits = inf.param; @@ -42,7 +40,7 @@ INSTANTIATE_TEST_SUITE_P( }); TEST_P(Entanglement, FunctionTest) { - const auto qc = qc::Entanglement(nq); + const auto qc = qc::createGHZState(nq); const auto e = dd::buildFunctionality(&qc, *dd); ASSERT_EQ(qc.getNops(), nq); const auto r = dd->multiply(e, dd->makeZeroState(nq)); @@ -51,7 +49,7 @@ TEST_P(Entanglement, FunctionTest) { } TEST_P(Entanglement, GHZRoutineFunctionTest) { - const auto qc = qc::Entanglement(nq); + const auto qc = qc::createGHZState(nq); const auto e = dd::simulate(&qc, dd->makeZeroState(nq), *dd); const auto f = dd->makeGHZState(nq); EXPECT_EQ(e, f); diff --git a/test/algorithms/test_grover.cpp b/test/algorithms/test_grover.cpp index a1b517739..fcb8eccc9 100644 --- a/test/algorithms/test_grover.cpp +++ b/test/algorithms/test_grover.cpp @@ -26,7 +26,7 @@ #include class Grover - : public testing::TestWithParam> { + : public testing::TestWithParam> { protected: void TearDown() override { dd->decRef(sim); @@ -39,19 +39,26 @@ class Grover void SetUp() override { std::tie(nqubits, seed) = GetParam(); dd = std::make_unique>(nqubits + 1); - qc = std::make_unique(nqubits, seed); - qc->printStatistics(std::cout); + qc = qc::createGrover(nqubits, seed); + qc.printStatistics(std::cout); + + // parse expected result from circuit name + const auto& name = qc.getName(); + expected = name.substr(name.find_last_of('_') + 1); + targetValue = qc::BitString(expected); } - std::size_t nqubits = 0; + qc::Qubit nqubits = 0; std::size_t seed = 0; std::unique_ptr> dd; - std::unique_ptr qc; + qc::QuantumComputation qc; qc::VectorDD sim{}; qc::MatrixDD func{}; + std::string expected{}; + qc::BitString targetValue{}; }; -constexpr std::size_t GROVER_MAX_QUBITS = 15; +constexpr qc::Qubit GROVER_MAX_QUBITS = 15; constexpr std::size_t GROVER_NUM_SEEDS = 5; constexpr dd::fp GROVER_ACCURACY = 1e-2; constexpr dd::fp GROVER_GOAL_PROBABILITY = 0.9; @@ -59,7 +66,7 @@ constexpr dd::fp GROVER_GOAL_PROBABILITY = 0.9; INSTANTIATE_TEST_SUITE_P( Grover, Grover, testing::Combine( - testing::Range(static_cast(2), GROVER_MAX_QUBITS + 1, 3), + testing::Range(static_cast(2), GROVER_MAX_QUBITS + 1, 3), testing::Range(static_cast(0), GROVER_NUM_SEEDS)), [](const testing::TestParamInfo& inf) { const auto nqubits = std::get<0>(inf.param); @@ -76,19 +83,20 @@ INSTANTIATE_TEST_SUITE_P( }); TEST_P(Grover, Functionality) { - auto x = '1' + qc->expected; + auto x = '1' + expected; std::reverse(x.begin(), x.end()); std::replace(x.begin(), x.end(), '1', '2'); - qc::QuantumComputation groverIteration(qc->getNqubits()); - qc->oracle(groverIteration); - qc->diffusion(groverIteration); + qc::QuantumComputation groverIteration(qc.getNqubits()); + qc::appendGroverOracle(groverIteration, targetValue); + qc::appendGroverDiffusion(groverIteration); const auto iteration = buildFunctionality(&groverIteration, *dd); auto e = iteration; dd->incRef(e); - for (std::size_t i = 0U; i < qc->iterations - 1U; ++i) { + const auto iterations = qc::computeNumberOfIterations(nqubits); + for (std::size_t i = 0U; i < iterations - 1U; ++i) { auto f = dd->multiply(iteration, e); dd->incRef(f); dd->decRef(e); @@ -96,8 +104,8 @@ TEST_P(Grover, Functionality) { dd->garbageCollect(); } - qc::QuantumComputation setup(qc->getNqubits()); - qc->setup(setup); + qc::QuantumComputation setup(qc.getNqubits()); + qc::appendGroverInitialization(setup); const auto g = buildFunctionality(&setup, *dd); const auto f = dd->multiply(e, g); dd->incRef(f); @@ -108,7 +116,7 @@ TEST_P(Grover, Functionality) { dd->decRef(iteration); // amplitude of the searched-for entry should be 1 - const auto c = func.getValueByPath(qc->getNqubits(), x); + const auto c = func.getValueByPath(qc.getNqubits(), x); EXPECT_NEAR(std::abs(c.real()), 1, GROVER_ACCURACY); EXPECT_NEAR(std::abs(c.imag()), 0, GROVER_ACCURACY); const auto prob = std::norm(c); @@ -116,19 +124,19 @@ TEST_P(Grover, Functionality) { } TEST_P(Grover, FunctionalityRecursive) { - auto x = '1' + qc->expected; + auto x = '1' + expected; std::reverse(x.begin(), x.end()); std::replace(x.begin(), x.end(), '1', '2'); - qc::QuantumComputation groverIteration(qc->getNqubits()); - qc->oracle(groverIteration); - qc->diffusion(groverIteration); + qc::QuantumComputation groverIteration(qc.getNqubits()); + qc::appendGroverOracle(groverIteration, targetValue); + qc::appendGroverDiffusion(groverIteration); const auto iter = buildFunctionalityRecursive(&groverIteration, *dd); auto e = iter; - std::bitset<128U> iterBits(qc->iterations); - const auto msb = - static_cast(std::floor(std::log2(qc->iterations))); + const auto iterations = qc::computeNumberOfIterations(nqubits); + const std::bitset<128U> iterBits(iterations); + const auto msb = static_cast(std::floor(std::log2(iterations))); auto f = iter; dd->incRef(f); bool zero = !iterBits[0U]; @@ -155,8 +163,8 @@ TEST_P(Grover, FunctionalityRecursive) { dd->decRef(f); // apply state preparation setup - qc::QuantumComputation statePrep(qc->getNqubits()); - qc->setup(statePrep); + qc::QuantumComputation statePrep(qc.getNqubits()); + qc::appendGroverInitialization(statePrep); const auto s = buildFunctionality(&statePrep, *dd); func = dd->multiply(e, s); dd->incRef(func); @@ -164,7 +172,7 @@ TEST_P(Grover, FunctionalityRecursive) { dd->decRef(e); // amplitude of the searched-for entry should be 1 - const auto c = func.getValueByPath(qc->getNqubits(), x); + const auto c = func.getValueByPath(qc.getNqubits(), x); EXPECT_NEAR(std::abs(c.real()), 1, GROVER_ACCURACY); EXPECT_NEAR(std::abs(c.imag()), 0, GROVER_ACCURACY); const auto prob = std::norm(c); @@ -173,9 +181,9 @@ TEST_P(Grover, FunctionalityRecursive) { TEST_P(Grover, Simulation) { constexpr std::size_t shots = 1024; - const auto measurements = dd::sample(*qc, shots); - ASSERT_TRUE(measurements.find(qc->expected) != measurements.end()); - const auto correctShots = measurements.at(qc->expected); + const auto measurements = dd::sample(qc, shots); + ASSERT_TRUE(measurements.find(expected) != measurements.end()); + const auto correctShots = measurements.at(expected); const auto probability = static_cast(correctShots) / static_cast(shots); diff --git a/test/algorithms/test_qft.cpp b/test/algorithms/test_qft.cpp index 1f4f37465..06aaf8c60 100644 --- a/test/algorithms/test_qft.cpp +++ b/test/algorithms/test_qft.cpp @@ -24,7 +24,7 @@ #include #include -class QFT : public testing::TestWithParam { +class QFT : public testing::TestWithParam { protected: void TearDown() override {} @@ -33,9 +33,9 @@ class QFT : public testing::TestWithParam { dd = std::make_unique>(nqubits); } - std::size_t nqubits = 0; + qc::Qubit nqubits = 0; std::unique_ptr> dd; - std::unique_ptr qc; + qc::QuantumComputation qc; qc::VectorDD sim{}; qc::MatrixDD func{}; }; @@ -51,13 +51,12 @@ class QFT : public testing::TestWithParam { /// The accuracy of double floating points allows for a minimal CN::TOLERANCE /// value of 10e-15 /// Utilizing more qubits requires the use of fp=long double -constexpr std::size_t QFT_MAX_QUBITS = 17U; +constexpr qc::Qubit QFT_MAX_QUBITS = 17U; constexpr size_t INITIAL_COMPLEX_COUNT = 1; INSTANTIATE_TEST_SUITE_P(QFT, QFT, - testing::Range(0U, QFT_MAX_QUBITS + 1U, - 3U), + testing::Range(0U, QFT_MAX_QUBITS + 1U, 3U), [](const testing::TestParamInfo& inf) { const auto nqubits = inf.param; std::stringstream ss{}; @@ -72,13 +71,13 @@ INSTANTIATE_TEST_SUITE_P(QFT, QFT, TEST_P(QFT, Functionality) { // there should be no error constructing the circuit - ASSERT_NO_THROW({ qc = std::make_unique(nqubits, false); }); + ASSERT_NO_THROW({ qc = qc::createQFT(nqubits, false); }); // there should be no error building the functionality // there should be no error building the functionality - ASSERT_NO_THROW({ func = buildFunctionality(qc.get(), *dd); }); + ASSERT_NO_THROW({ func = buildFunctionality(&qc, *dd); }); - qc->printStatistics(std::cout); + qc.printStatistics(std::cout); // QFT DD should consist of 2^n nodes ASSERT_EQ(func.size(), 1ULL << nqubits); @@ -118,12 +117,12 @@ TEST_P(QFT, Functionality) { TEST_P(QFT, FunctionalityRecursive) { // there should be no error constructing the circuit - ASSERT_NO_THROW({ qc = std::make_unique(nqubits, false); }); + ASSERT_NO_THROW({ qc = qc::createQFT(nqubits, false); }); // there should be no error building the functionality - ASSERT_NO_THROW({ func = buildFunctionalityRecursive(qc.get(), *dd); }); + ASSERT_NO_THROW({ func = buildFunctionalityRecursive(&qc, *dd); }); - qc->printStatistics(std::cout); + qc.printStatistics(std::cout); // QFT DD should consist of 2^n nodes ASSERT_EQ(func.size(), 1ULL << nqubits); @@ -164,14 +163,14 @@ TEST_P(QFT, FunctionalityRecursive) { TEST_P(QFT, Simulation) { // there should be no error constructing the circuit - ASSERT_NO_THROW({ qc = std::make_unique(nqubits, false); }); + ASSERT_NO_THROW({ qc = qc::createQFT(nqubits, false); }); // there should be no error simulating the circuit ASSERT_NO_THROW({ auto in = dd->makeZeroState(nqubits); - sim = simulate(qc.get(), in, *dd); + sim = simulate(&qc, in, *dd); }); - qc->printStatistics(std::cout); + qc.printStatistics(std::cout); // QFT DD |0...0> sim should consist of n nodes ASSERT_EQ(sim.size(), nqubits + 1); @@ -200,14 +199,14 @@ TEST_P(QFT, Simulation) { TEST_P(QFT, FunctionalityRecursiveEquality) { // there should be no error constructing the circuit - ASSERT_NO_THROW({ qc = std::make_unique(nqubits, false); }); + ASSERT_NO_THROW({ qc = qc::createQFT(nqubits, false); }); // there should be no error building the functionality recursively - ASSERT_NO_THROW({ func = buildFunctionalityRecursive(qc.get(), *dd); }); + ASSERT_NO_THROW({ func = buildFunctionalityRecursive(&qc, *dd); }); // there should be no error building the functionality regularly qc::MatrixDD funcRec{}; - ASSERT_NO_THROW({ funcRec = buildFunctionality(qc.get(), *dd); }); + ASSERT_NO_THROW({ funcRec = buildFunctionality(&qc, *dd); }); ASSERT_EQ(func, funcRec); dd->decRef(funcRec); @@ -221,11 +220,15 @@ TEST_P(QFT, FunctionalityRecursiveEquality) { TEST_P(QFT, SimulationSampling) { const auto dynamic = {false, true}; for (const auto dyn : dynamic) { - qc = std::make_unique(nqubits, false, dyn); + if (dyn) { + qc = qc::createIterativeQFT(nqubits); + } else { + qc = qc::createQFT(nqubits, false); + } // simulate the circuit constexpr std::size_t shots = 8192U; - const auto measurements = dd::sample(*qc, shots); + const auto measurements = dd::sample(qc, shots); const std::size_t unique = measurements.size(); const auto maxUnique = std::min(1ULL << nqubits, shots); diff --git a/test/algorithms/test_qpe.cpp b/test/algorithms/test_qpe.cpp index 6ca66e4cf..fc6002a6f 100644 --- a/test/algorithms/test_qpe.cpp +++ b/test/algorithms/test_qpe.cpp @@ -30,10 +30,10 @@ #include #include -class QPE : public testing::TestWithParam> { +class QPE : public testing::TestWithParam> { protected: qc::fp lambda{}; - std::uint64_t precision{}; + qc::Qubit precision{}; qc::fp theta{}; bool exactlyRepresentable{}; std::uint64_t expectedResult{}; @@ -130,7 +130,7 @@ INSTANTIATE_TEST_SUITE_P( TEST_P(QPE, QPETest) { auto dd = std::make_unique>(precision + 1); - auto qc = qc::QPE(lambda, precision); + auto qc = qc::createQPE(lambda, precision); qc.printStatistics(std::cout); ASSERT_EQ(qc.getNqubits(), precision + 1); ASSERT_NO_THROW({ qc::CircuitOptimizer::removeFinalMeasurements(qc); }); @@ -166,7 +166,7 @@ TEST_P(QPE, QPETest) { TEST_P(QPE, IQPETest) { auto dd = std::make_unique>(precision + 1); - auto qc = qc::QPE(lambda, precision, true); + auto qc = qc::createIterativeQPE(lambda, precision); ASSERT_EQ(qc.getNqubits(), 2U); constexpr auto shots = 8192U; @@ -213,7 +213,7 @@ TEST_P(QPE, DynamicEquivalenceSimulation) { auto dd = std::make_unique>(precision + 1); // create standard QPE circuit - auto qpe = qc::QPE(lambda, precision); + auto qpe = qc::createQPE(lambda, precision); // remove final measurements to obtain statevector qc::CircuitOptimizer::removeFinalMeasurements(qpe); @@ -222,7 +222,7 @@ TEST_P(QPE, DynamicEquivalenceSimulation) { auto e = dd::simulate(&qpe, dd->makeZeroState(qpe.getNqubits()), *dd); // create standard IQPE circuit - auto iqpe = qc::QPE(lambda, precision, true); + auto iqpe = qc::createIterativeQPE(lambda, precision); // transform dynamic circuits by first eliminating reset operations and // afterwards deferring measurements @@ -246,7 +246,7 @@ TEST_P(QPE, DynamicEquivalenceFunctionality) { auto dd = std::make_unique>(precision + 1); // create standard QPE circuit - auto qpe = qc::QPE(lambda, precision); + auto qpe = qc::createQPE(lambda, precision); // remove final measurements to obtain statevector qc::CircuitOptimizer::removeFinalMeasurements(qpe); @@ -255,7 +255,7 @@ TEST_P(QPE, DynamicEquivalenceFunctionality) { auto e = dd::buildFunctionality(&qpe, *dd); // create standard IQPE circuit - auto iqpe = qc::QPE(lambda, precision, true); + auto iqpe = qc::createIterativeQPE(lambda, precision); // transform dynamic circuits by first eliminating reset operations and // afterwards deferring measurements @@ -276,7 +276,7 @@ TEST_P(QPE, ProbabilityExtraction) { auto dd = std::make_unique>(precision + 1); // create standard QPE circuit - auto iqpe = qc::QPE(lambda, precision, true); + auto iqpe = qc::createIterativeQPE(lambda, precision); dd::SparsePVec probs{}; dd::extractProbabilityVector(&iqpe, dd->makeZeroState(iqpe.getNqubits()), @@ -301,7 +301,7 @@ TEST_P(QPE, DynamicEquivalenceSimulationProbabilityExtraction) { auto dd = std::make_unique>(precision + 1); // create standard QPE circuit - auto qpe = qc::QPE(lambda, precision); + auto qpe = qc::createQPE(lambda, precision); // remove final measurements to obtain statevector qc::CircuitOptimizer::removeFinalMeasurements(qpe); @@ -315,7 +315,7 @@ TEST_P(QPE, DynamicEquivalenceSimulationProbabilityExtraction) { } // create standard IQPE circuit - auto iqpe = qc::QPE(lambda, precision, true); + auto iqpe = qc::createIterativeQPE(lambda, precision); // extract measurement probabilities from IQPE simulations dd::SparsePVec probs{}; diff --git a/test/algorithms/test_random_clifford.cpp b/test/algorithms/test_random_clifford.cpp index 3e7d10e77..0f68f5e28 100644 --- a/test/algorithms/test_random_clifford.cpp +++ b/test/algorithms/test_random_clifford.cpp @@ -18,14 +18,14 @@ #include #include -class RandomClifford : public testing::TestWithParam { +class RandomClifford : public testing::TestWithParam { protected: void TearDown() override {} void SetUp() override {} }; INSTANTIATE_TEST_SUITE_P( - RandomClifford, RandomClifford, testing::Range(1U, 9U), + RandomClifford, RandomClifford, testing::Range(1U, 9U), [](const testing::TestParamInfo& inf) { // Generate names for test cases const auto nqubits = inf.param; @@ -38,7 +38,7 @@ TEST_P(RandomClifford, simulate) { const auto nq = GetParam(); auto dd = std::make_unique>(nq); - auto qc = qc::RandomCliffordCircuit(nq, nq * nq, 12345); + auto qc = qc::createRandomCliffordCircuit(nq, nq * nq, 12345); auto in = dd->makeZeroState(nq); ASSERT_NO_THROW({ dd::simulate(&qc, in, *dd); }); qc.printStatistics(std::cout); @@ -48,7 +48,7 @@ TEST_P(RandomClifford, buildFunctionality) { const auto nq = GetParam(); auto dd = std::make_unique>(nq); - auto qc = qc::RandomCliffordCircuit(nq, nq * nq, 12345); + auto qc = qc::createRandomCliffordCircuit(nq, nq * nq, 12345); ASSERT_NO_THROW({ dd::buildFunctionality(&qc, *dd); }); qc.printStatistics(std::cout); } diff --git a/test/algorithms/test_wstate.cpp b/test/algorithms/test_wstate.cpp index ccebe876c..387673437 100644 --- a/test/algorithms/test_wstate.cpp +++ b/test/algorithms/test_wstate.cpp @@ -51,7 +51,7 @@ INSTANTIATE_TEST_SUITE_P( TEST_P(WState, FunctionTest) { const auto nq = GetParam(); - const auto qc = qc::WState(nq); + const auto qc = qc::createWState(nq); constexpr std::size_t shots = 4096U; const auto measurements = dd::sample(qc, shots); for (const auto& result : generateWStateStrings(nq)) { @@ -62,7 +62,7 @@ TEST_P(WState, FunctionTest) { TEST_P(WState, RoutineFunctionTest) { const auto nq = GetParam(); - auto qc = qc::WState(nq); + auto qc = qc::createWState(nq); auto dd = std::make_unique>(qc.getNqubits()); const dd::VectorDD e = dd::simulate(&qc, dd->makeZeroState(qc.getNqubits()), *dd); diff --git a/test/circuit_optimizer/test_flatten_operations.cpp b/test/circuit_optimizer/test_flatten_operations.cpp index 8c2eb3f99..7532e291e 100644 --- a/test/circuit_optimizer/test_flatten_operations.cpp +++ b/test/circuit_optimizer/test_flatten_operations.cpp @@ -23,11 +23,11 @@ namespace qc { TEST(FlattenOperations, FlattenRandomClifford) { - qc::RandomCliffordCircuit rcs(2U, 3U, 0U); + auto rcs = createRandomCliffordCircuit(2U, 3U, 0U); std::cout << rcs << "\n"; const auto nops = rcs.getNindividualOps(); - qc::CircuitOptimizer::flattenOperations(rcs); + CircuitOptimizer::flattenOperations(rcs); std::cout << rcs << "\n"; for (const auto& op : rcs) { @@ -49,7 +49,7 @@ TEST(FlattenOperations, FlattenRecursive) { qc.emplace_back(op2.asCompoundOperation()); std::cout << qc << "\n"; - qc::CircuitOptimizer::flattenOperations(qc); + CircuitOptimizer::flattenOperations(qc); std::cout << qc << "\n"; for (const auto& g : qc) { @@ -58,11 +58,11 @@ TEST(FlattenOperations, FlattenRecursive) { ASSERT_EQ(qc.getNops(), 2U); auto& gate = qc.at(0); - EXPECT_EQ(gate->getType(), qc::X); + EXPECT_EQ(gate->getType(), X); EXPECT_EQ(gate->getTargets().at(0), 0U); EXPECT_TRUE(gate->getControls().empty()); auto& gate2 = qc.at(1); - EXPECT_EQ(gate2->getType(), qc::Z); + EXPECT_EQ(gate2->getType(), Z); EXPECT_EQ(gate2->getTargets().at(0), 0U); EXPECT_TRUE(gate2->getControls().empty()); } @@ -80,21 +80,21 @@ TEST(FlattenOperations, FlattenCustomOnly) { qc.emplace_back(op2.asCompoundOperation()); std::cout << qc << "\n"; - qc::CircuitOptimizer::flattenOperations(qc, true); + CircuitOptimizer::flattenOperations(qc, true); std::cout << qc << "\n"; ASSERT_EQ(qc.getNops(), 1U); auto& gate = qc.at(0); - EXPECT_EQ(gate->getType(), qc::Compound); + EXPECT_EQ(gate->getType(), Compound); std::vector> opsCompound; - opsCompound.push_back(std::make_unique(0, qc::X)); - opsCompound.push_back(std::make_unique(0, qc::Z)); + opsCompound.push_back(std::make_unique(0, X)); + opsCompound.push_back(std::make_unique(0, Z)); QuantumComputation qc2(nqubits); qc2.emplace_back(std::move(opsCompound), true); std::cout << qc2 << "\n"; - qc::CircuitOptimizer::flattenOperations(qc2, true); + CircuitOptimizer::flattenOperations(qc2, true); std::cout << qc2 << "\n"; for (const auto& g : qc2) { @@ -103,11 +103,11 @@ TEST(FlattenOperations, FlattenCustomOnly) { ASSERT_EQ(qc2.getNops(), 2U); auto& gate3 = qc2.at(0); - EXPECT_EQ(gate3->getType(), qc::X); + EXPECT_EQ(gate3->getType(), X); EXPECT_EQ(gate3->getTargets().at(0), 0U); EXPECT_TRUE(gate3->getControls().empty()); auto& gate4 = qc2.at(1); - EXPECT_EQ(gate4->getType(), qc::Z); + EXPECT_EQ(gate4->getType(), Z); EXPECT_EQ(gate4->getTargets().at(0), 0U); EXPECT_TRUE(gate4->getControls().empty()); } From 1aa3166efc614e725f18f24e480fabc72a12cd2f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Jan 2025 22:29:59 +0100 Subject: [PATCH 02/11] =?UTF-8?q?=F0=9F=8E=A8=20code=20quality=20improveme?= =?UTF-8?q?nts=20and=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - opting for references where null values are not expected or accepted. - moving code from headers to source files. - fixing new clang-tidy warnings - adding a little more noexcept were appropriate Signed-off-by: burgholzer --- .../circuit_optimizer/CircuitOptimizer.hpp | 4 +- include/mqt-core/dd/Simulation.hpp | 2 +- include/mqt-core/ir/Permutation.hpp | 6 +- include/mqt-core/ir/QuantumComputation.hpp | 795 +++++------------- .../mqt-core/ir/operations/AodOperation.hpp | 54 +- .../operations/ClassicControlledOperation.hpp | 106 +-- .../ir/operations/CompoundOperation.hpp | 28 +- include/mqt-core/ir/operations/Control.hpp | 6 +- include/mqt-core/ir/operations/Expression.hpp | 104 +-- .../ir/operations/NonUnitaryOperation.hpp | 26 +- include/mqt-core/ir/operations/Operation.hpp | 12 +- .../ir/operations/StandardOperation.hpp | 2 +- .../ir/operations/SymbolicOperation.hpp | 125 +-- .../mqt-core/ir/parsers/qasm3_parser/Gate.hpp | 4 +- .../qasm3_parser/NestedEnvironment.hpp | 5 +- .../ir/parsers/qasm3_parser/Statement.hpp | 87 +- .../ir/parsers/qasm3_parser/Token.hpp | 3 +- .../ir/parsers/qasm3_parser/Types.hpp | 8 +- .../qasm3_parser/passes/ConstEvalPass.hpp | 41 +- .../qasm3_parser/passes/TypeCheckPass.hpp | 24 +- src/circuit_optimizer/CircuitOptimizer.cpp | 68 +- src/ir/QuantumComputation.cpp | 767 ++++++++++++++--- src/ir/operations/AodOperation.cpp | 43 +- .../operations/ClassicControlledOperation.cpp | 89 +- src/ir/operations/CompoundOperation.cpp | 24 +- src/ir/operations/Expression.cpp | 5 +- src/ir/operations/SymbolicOperation.cpp | 48 +- .../qasm3_parser/passes/ConstEvalPass.cpp | 21 + .../qasm3_parser/passes/TypeCheckPass.cpp | 38 +- .../ir/register_quantum_computation.cpp | 12 +- src/zx/FunctionalityConstruction.cpp | 13 +- .../circuit_optimizer/test_collect_blocks.cpp | 6 +- .../test_elide_permutations.cpp | 2 + .../test_remove_operation.cpp | 4 +- test/ir/test_qfr_functionality.cpp | 1 + 35 files changed, 1368 insertions(+), 1215 deletions(-) diff --git a/include/mqt-core/circuit_optimizer/CircuitOptimizer.hpp b/include/mqt-core/circuit_optimizer/CircuitOptimizer.hpp index 6c289a08c..b637821a1 100644 --- a/include/mqt-core/circuit_optimizer/CircuitOptimizer.hpp +++ b/include/mqt-core/circuit_optimizer/CircuitOptimizer.hpp @@ -32,7 +32,7 @@ class CircuitOptimizer { static void removeIdentities(QuantumComputation& qc); - static void removeOperation(qc::QuantumComputation& qc, + static void removeOperation(QuantumComputation& qc, const std::unordered_set& opTypes, size_t opSize); @@ -59,7 +59,7 @@ class CircuitOptimizer { * target qubit) in the given circuit. * @param qc the quantum circuit */ - static void replaceMCXWithMCZ(qc::QuantumComputation& qc); + static void replaceMCXWithMCZ(QuantumComputation& qc); /** * @brief Backpropagates the output permutation through the circuit. diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp index 814930c9d..5d696d685 100644 --- a/include/mqt-core/dd/Simulation.hpp +++ b/include/mqt-core/dd/Simulation.hpp @@ -30,7 +30,7 @@ VectorDD simulate(const QuantumComputation* qc, const VectorDD& in, auto e = in; for (const auto& op : *qc) { // SWAP gates can be executed virtually by changing the permutation - if (op->getType() == OpType::SWAP && !op->isControlled()) { + if (op->getType() == SWAP && !op->isControlled()) { const auto& targets = op->getTargets(); std::swap(permutation.at(targets[0U]), permutation.at(targets[1U])); continue; diff --git a/include/mqt-core/ir/Permutation.hpp b/include/mqt-core/ir/Permutation.hpp index f6071a7eb..3490599ca 100644 --- a/include/mqt-core/ir/Permutation.hpp +++ b/include/mqt-core/ir/Permutation.hpp @@ -28,9 +28,8 @@ class Permutation : public std::map { } // namespace qc // define hash function for Permutation -namespace std { -template <> struct hash { - std::size_t operator()(const qc::Permutation& p) const { +template <> struct std::hash { + std::size_t operator()(const qc::Permutation& p) const noexcept { std::size_t seed = 0; for (const auto& [k, v] : p) { qc::hashCombine(seed, k); @@ -39,4 +38,3 @@ template <> struct hash { return seed; } }; -} // namespace std diff --git a/include/mqt-core/ir/QuantumComputation.hpp b/include/mqt-core/ir/QuantumComputation.hpp index 99676e55b..0a4b6b1cb 100644 --- a/include/mqt-core/ir/QuantumComputation.hpp +++ b/include/mqt-core/ir/QuantumComputation.hpp @@ -14,40 +14,30 @@ #include "operations/CompoundOperation.hpp" #include "operations/Control.hpp" #include "operations/Expression.hpp" -#include "operations/NonUnitaryOperation.hpp" #include "operations/OpType.hpp" -#include "operations/StandardOperation.hpp" -#include "operations/SymbolicOperation.hpp" -#include -#include #include #include #include #include #include -#include #include #include -#include #include #include #include -#include #include namespace qc { -class CircuitOptimizer; - class QuantumComputation { public: - using iterator = typename std::vector>::iterator; + using iterator = std::vector>::iterator; using const_iterator = - typename std::vector>::const_iterator; + std::vector>::const_iterator; using reverse_iterator = - typename std::vector>::reverse_iterator; + std::vector>::reverse_iterator; using const_reverse_iterator = - typename std::vector>::const_reverse_iterator; + std::vector>::const_reverse_iterator; protected: std::vector> ops; @@ -69,240 +59,20 @@ class QuantumComputation { std::unordered_set occurringVariables; - void importOpenQASM3(std::istream& is); - void importReal(std::istream& is); - int readRealHeader(std::istream& is); - void readRealGateDescriptions(std::istream& is, int line); - void importTFC(std::istream& is); - int readTFCHeader(std::istream& is, std::map& varMap); - void readTFCGateDescriptions(std::istream& is, int line, - std::map& varMap); - void importQC(std::istream& is); - int readQCHeader(std::istream& is, std::map& varMap); - void readQCGateDescriptions(std::istream& is, int line, - std::map& varMap); - - template - static void printSortedRegisters(const RegisterMap& regmap, - const std::string& identifier, - std::ostream& of, bool openQASM3 = false) { - // sort regs by start index - std::map> - sortedRegs{}; - for (const auto& reg : regmap) { - sortedRegs.insert({reg.second.first, reg}); - } - - for (const auto& reg : sortedRegs) { - if (openQASM3) { - of << identifier << "[" << reg.second.second.second << "] " - << reg.second.first << ";" << std::endl; - } else { - of << identifier << " " << reg.second.first << "[" - << reg.second.second.second << "];" << std::endl; - } - } - } - template - static void consolidateRegister(RegisterMap& regs) { - bool finished = regs.empty(); - while (!finished) { - for (const auto& qreg : regs) { - finished = true; - auto regname = qreg.first; - // check if lower part of register - if (regname.length() > 2 && - regname.compare(regname.size() - 2, 2, "_l") == 0) { - auto lowidx = qreg.second.first; - auto lownum = qreg.second.second; - // search for higher part of register - auto highname = regname.substr(0, regname.size() - 1) + 'h'; - auto it = regs.find(highname); - if (it != regs.end()) { - auto highidx = it->second.first; - auto highnum = it->second.second; - // fusion of registers possible - if (lowidx + lownum == highidx) { - finished = false; - auto targetname = regname.substr(0, regname.size() - 2); - auto targetidx = lowidx; - auto targetnum = lownum + highnum; - regs.insert({targetname, {targetidx, targetnum}}); - regs.erase(regname); - regs.erase(highname); - } - } - break; - } - } - } - } - - /** - * @brief Removes a certain qubit in a register from the register map - * @details If this was the last qubit in the register, the register is - * deleted. Removals at the beginning or the end of a register just modify the - * existing register. Removals in the middle of a register split the register - * into two new registers. The new registers are named by appending "_l" and - * "_h" to the original register name. - * @param regs A collection of all the registers - * @param reg The name of the register containing the qubit to be removed - * @param idx The index of the qubit in the register to be removed - */ - static void removeQubitfromQubitRegister(QuantumRegisterMap& regs, - const std::string& reg, Qubit idx); - - /** - * @brief Adds a qubit to a register in the register map - * @details If the register map is empty, a new register is created with the - * default name. If the qubit can be appended to the start or the end of an - * existing register, it is appended. Otherwise a new register is created with - * the default name and the qubit index appended. - * @param regs A collection of all the registers - * @param physicalQubitIndex The index of the qubit to be added - * @param defaultRegName The default name of the register to be created - */ - static void addQubitToQubitRegister(QuantumRegisterMap& regs, - Qubit physicalQubitIndex, - const std::string& defaultRegName); - - template - static void createRegisterArray(const RegisterMap& regs, - RegisterNames& regnames) { - regnames.clear(); - std::stringstream ss; - // sort regs by start index - std::map> - sortedRegs{}; - for (const auto& reg : regs) { - sortedRegs.insert({reg.second.first, reg}); - } - - for (const auto& reg : sortedRegs) { - for (decltype(RegisterType::second) i = 0; i < reg.second.second.second; - i++) { - ss << reg.second.first << "[" << i << "]"; - regnames.push_back(std::make_pair(reg.second.first, ss.str())); - ss.str(std::string()); - } - } - } - - [[nodiscard]] std::size_t getSmallestAncillary() const { - for (std::size_t i = 0; i < ancillary.size(); ++i) { - if (ancillary[i]) { - return i; - } - } - return ancillary.size(); - } - - [[nodiscard]] std::size_t getSmallestGarbage() const { - for (std::size_t i = 0; i < garbage.size(); ++i) { - if (garbage[i]) { - return i; - } - } - return garbage.size(); - } - [[nodiscard]] bool isLastOperationOnQubit(const const_iterator& opIt) const { - const auto end = ops.cend(); - return isLastOperationOnQubit(opIt, end); - } - void checkQubitRange(Qubit qubit) const; - void checkQubitRange(Qubit qubit, const Controls& controls) const; - void checkQubitRange(Qubit qubit0, Qubit qubit1, - const Controls& controls) const; - void checkQubitRange(const std::vector& qubits) const; - void checkBitRange(Bit bit) const; - void checkBitRange(const std::vector& bits) const; - void checkClassicalRegister(const ClassicalRegister& creg) const; - public: QuantumComputation() = default; - explicit QuantumComputation(const std::size_t nq, const std::size_t nc = 0U, - const std::size_t s = 0) - : seed(s) { - if (nq > 0) { - addQubitRegister(nq); - } - if (nc > 0) { - addClassicalRegister(nc); - } - if (seed != 0) { - mt.seed(seed); - } else { - // create and properly seed rng - std::array - randomData{}; - std::random_device rd; - std::generate(std::begin(randomData), std::end(randomData), - [&rd]() { return rd(); }); - std::seed_seq seeds(std::begin(randomData), std::end(randomData)); - mt.seed(seeds); - } - } - explicit QuantumComputation(const std::string& filename, - const std::size_t s = 0U) - : seed(s) { - import(filename); - if (seed != 0U) { - mt.seed(seed); - } else { - // create and properly seed rng - std::array - randomData{}; - std::random_device rd; - std::generate(std::begin(randomData), std::end(randomData), - [&rd]() { return rd(); }); - std::seed_seq seeds(std::begin(randomData), std::end(randomData)); - mt.seed(seeds); - } - } + explicit QuantumComputation(std::size_t nq, std::size_t nc = 0U, + std::size_t s = 0); + explicit QuantumComputation(const std::string& filename, std::size_t s = 0U); QuantumComputation(QuantumComputation&& qc) noexcept = default; QuantumComputation& operator=(QuantumComputation&& qc) noexcept = default; - QuantumComputation(const QuantumComputation& qc) - : nqubits(qc.nqubits), nclassics(qc.nclassics), nancillae(qc.nancillae), - name(qc.name), qregs(qc.qregs), cregs(qc.cregs), ancregs(qc.ancregs), - mt(qc.mt), seed(qc.seed), globalPhase(qc.globalPhase), - occurringVariables(qc.occurringVariables), - initialLayout(qc.initialLayout), - outputPermutation(qc.outputPermutation), ancillary(qc.ancillary), - garbage(qc.garbage) { - ops.reserve(qc.ops.size()); - for (const auto& op : qc.ops) { - emplace_back(op->clone()); - } - } - QuantumComputation& operator=(const QuantumComputation& qc) { - if (this != &qc) { - nqubits = qc.nqubits; - nclassics = qc.nclassics; - nancillae = qc.nancillae; - name = qc.name; - qregs = qc.qregs; - cregs = qc.cregs; - ancregs = qc.ancregs; - mt = qc.mt; - seed = qc.seed; - globalPhase = qc.globalPhase; - occurringVariables = qc.occurringVariables; - initialLayout = qc.initialLayout; - outputPermutation = qc.outputPermutation; - ancillary = qc.ancillary; - garbage = qc.garbage; - - ops.clear(); - ops.reserve(qc.ops.size()); - for (const auto& op : qc.ops) { - emplace_back(op->clone()); - } - } - return *this; - } - virtual ~QuantumComputation() = default; + QuantumComputation(const QuantumComputation& qc); + QuantumComputation& operator=(const QuantumComputation& qc); + ~QuantumComputation() = default; + + // physical qubits are used as keys, logical qubits as values + Permutation initialLayout{}; + Permutation outputPermutation{}; /** * @brief Construct a QuantumComputation from an OpenQASM string @@ -325,33 +95,44 @@ class QuantumComputation { [[nodiscard]] static QuantumComputation fromCompoundOperation(const CompoundOperation& op); - [[nodiscard]] virtual std::size_t getNops() const { return ops.size(); } - [[nodiscard]] std::size_t getNqubits() const { return nqubits + nancillae; } - [[nodiscard]] std::size_t getNancillae() const { return nancillae; } - [[nodiscard]] std::size_t getNqubitsWithoutAncillae() const { + [[nodiscard]] std::size_t getNops() const noexcept { return ops.size(); } + [[nodiscard]] std::size_t getNqubits() const noexcept { + return nqubits + nancillae; + } + [[nodiscard]] std::size_t getNancillae() const noexcept { return nancillae; } + [[nodiscard]] std::size_t getNqubitsWithoutAncillae() const noexcept { return nqubits; } - [[nodiscard]] std::size_t getNmeasuredQubits() const { - return getNqubits() - getNgarbageQubits(); + [[nodiscard]] const std::vector& getAncillary() const noexcept { + return ancillary; + } + [[nodiscard]] const std::vector& getGarbage() const noexcept { + return garbage; + } + [[nodiscard]] std::size_t getNcbits() const noexcept { return nclassics; } + [[nodiscard]] std::string getName() const noexcept { return name; } + [[nodiscard]] const QuantumRegisterMap& getQregs() const noexcept { + return qregs; + } + [[nodiscard]] const ClassicalRegisterMap& getCregs() const noexcept { + return cregs; } - [[nodiscard]] std::size_t getNgarbageQubits() const { - return static_cast( - std::count(getGarbage().begin(), getGarbage().end(), true)); + [[nodiscard]] const QuantumRegisterMap& getANCregs() const noexcept { + return ancregs; } - [[nodiscard]] std::size_t getNcbits() const { return nclassics; } - [[nodiscard]] std::string getName() const { return name; } - [[nodiscard]] const QuantumRegisterMap& getQregs() const { return qregs; } - [[nodiscard]] const ClassicalRegisterMap& getCregs() const { return cregs; } - [[nodiscard]] const QuantumRegisterMap& getANCregs() const { return ancregs; } - [[nodiscard]] decltype(mt)& getGenerator() { return mt; } + [[nodiscard]] decltype(mt)& getGenerator() noexcept { return mt; } - [[nodiscard]] fp getGlobalPhase() const { return globalPhase; } + [[nodiscard]] fp getGlobalPhase() const noexcept { return globalPhase; } - void setName(const std::string& n) { name = n; } + [[nodiscard]] const std::unordered_set& + getVariables() const noexcept { + return occurringVariables; + } - // physical qubits are used as keys, logical qubits as values - Permutation initialLayout{}; - Permutation outputPermutation{}; + [[nodiscard]] std::size_t getNmeasuredQubits() const noexcept; + [[nodiscard]] std::size_t getNgarbageQubits() const; + + void setName(const std::string& n) noexcept { name = n; } std::vector ancillary; std::vector garbage; @@ -423,10 +204,6 @@ class QuantumComputation { */ void setLogicalQubitsGarbage(Qubit minLogicalQubitIndex, Qubit maxLogicalQubitIndex); - [[nodiscard]] const std::vector& getAncillary() const { - return ancillary; - } - [[nodiscard]] const std::vector& getGarbage() const { return garbage; } /// checks whether the given logical qubit exists in the initial layout. /// \param logicalQubitIndex the logical qubit index to check @@ -435,238 +212,122 @@ class QuantumComputation { [[nodiscard]] std::pair> containsLogicalQubit(Qubit logicalQubitIndex) const; - /// Adds a global phase to the quantum circuit. - /// \param angle the angle to add - void gphase(const fp& angle) { - globalPhase += angle; - // normalize to [0, 2pi) - while (globalPhase < 0) { - globalPhase += 2 * PI; - } - while (globalPhase >= 2 * PI) { - globalPhase -= 2 * PI; - } - } - ///--------------------------------------------------------------------------- /// \n Operations \n ///--------------------------------------------------------------------------- -#define DEFINE_SINGLE_TARGET_OPERATION(op) \ - void op(const Qubit target) { mc##op(Controls{}, target); } \ - void c##op(const Control& control, const Qubit target) { \ - mc##op(Controls{control}, target); \ - } \ - void mc##op(const Controls& controls, const Qubit target) { \ - checkQubitRange(target, controls); \ - emplace_back(controls, target, \ - OP_NAME_TO_TYPE.at(#op)); \ - } - - DEFINE_SINGLE_TARGET_OPERATION(i) - DEFINE_SINGLE_TARGET_OPERATION(x) - DEFINE_SINGLE_TARGET_OPERATION(y) - DEFINE_SINGLE_TARGET_OPERATION(z) - DEFINE_SINGLE_TARGET_OPERATION(h) - DEFINE_SINGLE_TARGET_OPERATION(s) - DEFINE_SINGLE_TARGET_OPERATION(sdg) - DEFINE_SINGLE_TARGET_OPERATION(t) - DEFINE_SINGLE_TARGET_OPERATION(tdg) - DEFINE_SINGLE_TARGET_OPERATION(v) - DEFINE_SINGLE_TARGET_OPERATION(vdg) - DEFINE_SINGLE_TARGET_OPERATION(sx) - DEFINE_SINGLE_TARGET_OPERATION(sxdg) - -#define DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ - void op(const SymbolOrNumber&(param), const Qubit target) { \ - mc##op(param, Controls{}, target); \ - } \ + /// Adds a global phase to the quantum circuit. + /// \param angle the angle to add + void gphase(fp angle); + +#define DECLARE_SINGLE_TARGET_OPERATION(op) \ + void op(Qubit target); \ + void c##op(const Control& control, Qubit target); \ + void mc##op(const Controls& controls, const Qubit target); + + DECLARE_SINGLE_TARGET_OPERATION(i) + DECLARE_SINGLE_TARGET_OPERATION(x) + DECLARE_SINGLE_TARGET_OPERATION(y) + DECLARE_SINGLE_TARGET_OPERATION(z) + DECLARE_SINGLE_TARGET_OPERATION(h) + DECLARE_SINGLE_TARGET_OPERATION(s) + DECLARE_SINGLE_TARGET_OPERATION(sdg) + DECLARE_SINGLE_TARGET_OPERATION(t) + DECLARE_SINGLE_TARGET_OPERATION(tdg) + DECLARE_SINGLE_TARGET_OPERATION(v) + DECLARE_SINGLE_TARGET_OPERATION(vdg) + DECLARE_SINGLE_TARGET_OPERATION(sx) + DECLARE_SINGLE_TARGET_OPERATION(sxdg) + +#undef DECLARE_SINGLE_TARGET_OPERATION + +#define DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ + void op(const SymbolOrNumber&(param), Qubit target); \ void c##op(const SymbolOrNumber&(param), const Control& control, \ - const Qubit target) { \ - mc##op(param, Controls{control}, target); \ - } \ + Qubit target); \ void mc##op(const SymbolOrNumber&(param), const Controls& controls, \ - const Qubit target) { \ - checkQubitRange(target, controls); \ - if (std::holds_alternative(param)) { \ - emplace_back(controls, target, \ - OP_NAME_TO_TYPE.at(#op), \ - std::vector{std::get(param)}); \ - } else { \ - addVariables(param); \ - emplace_back( \ - controls, target, OP_NAME_TO_TYPE.at(#op), std::vector{param}); \ - } \ - } + Qubit target); + + DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rx, theta) + DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(ry, theta) + DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rz, theta) + DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(p, theta) - DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rx, theta) - DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(ry, theta) - DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rz, theta) - DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(p, theta) +#undef DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION -#define DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ +#define DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ void op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Qubit target) { \ - mc##op(param0, param1, Controls{}, target); \ - } \ + Qubit target); \ void c##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Control& control, const Qubit target) { \ - mc##op(param0, param1, Controls{control}, target); \ - } \ + const Control& control, const Qubit target); \ void mc##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Controls& controls, const Qubit target) { \ - checkQubitRange(target, controls); \ - if (std::holds_alternative(param0) && \ - std::holds_alternative(param1)) { \ - emplace_back( \ - controls, target, OP_NAME_TO_TYPE.at(#op), \ - std::vector{std::get(param0), std::get(param1)}); \ - } else { \ - addVariables(param0, param1); \ - emplace_back(controls, target, \ - OP_NAME_TO_TYPE.at(#op), \ - std::vector{param0, param1}); \ - } \ - } + const Controls& controls, const Qubit target); + + DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(u2, phi, lambda) - DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(u2, phi, lambda) +#undef DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION -#define DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(op, param0, param1, \ - param2) \ +#define DECLARE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(op, param0, param1, \ + param2) \ void op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const SymbolOrNumber&(param2), const Qubit target) { \ - mc##op(param0, param1, param2, Controls{}, target); \ - } \ + const SymbolOrNumber&(param2), Qubit target); \ void c##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ const SymbolOrNumber&(param2), const Control& control, \ - const Qubit target) { \ - mc##op(param0, param1, param2, Controls{control}, target); \ - } \ + Qubit target); \ void mc##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ const SymbolOrNumber&(param2), const Controls& controls, \ - const Qubit target) { \ - checkQubitRange(target, controls); \ - if (std::holds_alternative(param0) && \ - std::holds_alternative(param1) && \ - std::holds_alternative(param2)) { \ - emplace_back( \ - controls, target, OP_NAME_TO_TYPE.at(#op), \ - std::vector{std::get(param0), std::get(param1), \ - std::get(param2)}); \ - } else { \ - addVariables(param0, param1, param2); \ - emplace_back(controls, target, \ - OP_NAME_TO_TYPE.at(#op), \ - std::vector{param0, param1, param2}); \ - } \ - } + Qubit target); - DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(u, theta, phi, lambda) - -#define DEFINE_TWO_TARGET_OPERATION(op) \ - void op(const Qubit target0, const Qubit target1) { \ - mc##op(Controls{}, target0, target1); \ - } \ - void c##op(const Control& control, const Qubit target0, \ - const Qubit target1) { \ - mc##op(Controls{control}, target0, target1); \ - } \ - void mc##op(const Controls& controls, const Qubit target0, \ - const Qubit target1) { \ - checkQubitRange(target0, target1, controls); \ - emplace_back(controls, target0, target1, \ - OP_NAME_TO_TYPE.at(#op)); \ - } + DECLARE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(u, theta, phi, lambda) + +#undef DECLARE_SINGLE_TARGET_THREE_PARAMETER_OPERATION + +#define DECLARE_TWO_TARGET_OPERATION(op) \ + void op(const Qubit target0, const Qubit target1); \ + void c##op(const Control& control, Qubit target0, Qubit target1); \ + void mc##op(const Controls& controls, Qubit target0, Qubit target1); - DEFINE_TWO_TARGET_OPERATION(swap) // NOLINT: bugprone-exception-escape - DEFINE_TWO_TARGET_OPERATION(dcx) - DEFINE_TWO_TARGET_OPERATION(ecr) - DEFINE_TWO_TARGET_OPERATION(iswap) - DEFINE_TWO_TARGET_OPERATION(iswapdg) - DEFINE_TWO_TARGET_OPERATION(peres) - DEFINE_TWO_TARGET_OPERATION(peresdg) - DEFINE_TWO_TARGET_OPERATION(move) - -#define DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ - void op(const SymbolOrNumber&(param), const Qubit target0, \ - const Qubit target1) { \ - mc##op(param, Controls{}, target0, target1); \ - } \ + DECLARE_TWO_TARGET_OPERATION(swap) // NOLINT: bugprone-exception-escape + DECLARE_TWO_TARGET_OPERATION(dcx) + DECLARE_TWO_TARGET_OPERATION(ecr) + DECLARE_TWO_TARGET_OPERATION(iswap) + DECLARE_TWO_TARGET_OPERATION(iswapdg) + DECLARE_TWO_TARGET_OPERATION(peres) + DECLARE_TWO_TARGET_OPERATION(peresdg) + DECLARE_TWO_TARGET_OPERATION(move) + +#undef DECLARE_TWO_TARGET_OPERATION + +#define DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ + void op(const SymbolOrNumber&(param), Qubit target0, Qubit target1); \ void c##op(const SymbolOrNumber&(param), const Control& control, \ - const Qubit target0, const Qubit target1) { \ - mc##op(param, Controls{control}, target0, target1); \ - } \ + Qubit target0, Qubit target1); \ void mc##op(const SymbolOrNumber&(param), const Controls& controls, \ - const Qubit target0, const Qubit target1) { \ - checkQubitRange(target0, target1, controls); \ - if (std::holds_alternative(param)) { \ - emplace_back(controls, target0, target1, \ - OP_NAME_TO_TYPE.at(#op), \ - std::vector{std::get(param)}); \ - } else { \ - addVariables(param); \ - emplace_back(controls, target0, target1, \ - OP_NAME_TO_TYPE.at(#op), \ - std::vector{param}); \ - } \ - } + Qubit target0, Qubit target1); - DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rxx, theta) - DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(ryy, theta) - DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzz, theta) - DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzx, theta) + DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rxx, theta) + DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(ryy, theta) + DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzz, theta) + DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzx, theta) -#define DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ +#undef DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION + +#define DECLARE_TWO_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ void op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Qubit target0, const Qubit target1) { \ - mc##op(param0, param1, Controls{}, target0, target1); \ - } \ + Qubit target0, Qubit target1); \ void c##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Control& control, const Qubit target0, \ - const Qubit target1) { \ - mc##op(param0, param1, Controls{control}, target0, target1); \ - } \ + const Control& control, Qubit target0, Qubit target1); \ void mc##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Controls& controls, const Qubit target0, \ - const Qubit target1) { \ - checkQubitRange(target0, target1, controls); \ - if (std::holds_alternative(param0) && \ - std::holds_alternative(param1)) { \ - emplace_back( \ - controls, target0, target1, OP_NAME_TO_TYPE.at(#op), \ - std::vector{std::get(param0), std::get(param1)}); \ - } else { \ - addVariables(param0, param1); \ - emplace_back(controls, target0, target1, \ - OP_NAME_TO_TYPE.at(#op), \ - std::vector{param0, param1}); \ - } \ - } + const Controls& controls, Qubit target0, Qubit target1); - DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_minus_yy, theta, beta) - DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_plus_yy, theta, beta) - -#undef DEFINE_SINGLE_TARGET_OPERATION -#undef DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION -#undef DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION -#undef DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION -#undef DEFINE_TWO_TARGET_OPERATION -#undef DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION -#undef DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION - - void measure(const Qubit qubit, const std::size_t bit) { - checkQubitRange(qubit); - checkBitRange(bit); - emplace_back(qubit, bit); - } + DECLARE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_minus_yy, theta, beta) + DECLARE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_plus_yy, theta, beta) - void measure(Qubit qubit, const std::pair& registerBit); +#undef DECLARE_TWO_TARGET_TWO_PARAMETER_OPERATION - void measure(const Targets& qubits, const std::vector& bits) { - checkQubitRange(qubits); - checkBitRange(bits); - emplace_back(qubits, bits); - } + void measure(Qubit qubit, std::size_t bit); + void measure(Qubit qubit, const std::pair& registerBit); + void measure(const Targets& qubits, const std::vector& bits); /** * @brief Add measurements to all qubits @@ -677,59 +338,28 @@ class QuantumComputation { */ void measureAll(bool addBits = true); - void reset(const Qubit target) { - checkQubitRange(target); - emplace_back(std::vector{target}, qc::Reset); - } - void reset(const Targets& targets) { - checkQubitRange(targets); - emplace_back(targets, qc::Reset); - } + void reset(Qubit target); + void reset(const Targets& targets); - void barrier() { - std::vector targets(getNqubits()); - std::iota(targets.begin(), targets.end(), 0); - emplace_back(targets, qc::Barrier); - } - void barrier(const Qubit target) { - checkQubitRange(target); - emplace_back(target, qc::Barrier); - } - void barrier(const Targets& targets) { - checkQubitRange(targets); - emplace_back(targets, qc::Barrier); - } + void barrier(); + void barrier(Qubit target); + void barrier(const Targets& targets); - void classicControlled(const OpType op, const Qubit target, + void classicControlled(OpType op, Qubit target, const ClassicalRegister& controlRegister, - const std::uint64_t expectedValue = 1U, - const ComparisonKind cmp = ComparisonKind::Eq, - const std::vector& params = {}) { - classicControlled(op, target, Controls{}, controlRegister, expectedValue, - cmp, params); - } - void classicControlled(const OpType op, const Qubit target, - const Control control, + std::uint64_t expectedValue = 1U, + ComparisonKind cmp = Eq, + const std::vector& params = {}); + void classicControlled(OpType op, Qubit target, Control control, const ClassicalRegister& controlRegister, - const std::uint64_t expectedValue = 1U, - const ComparisonKind cmp = ComparisonKind::Eq, - const std::vector& params = {}) { - classicControlled(op, target, Controls{control}, controlRegister, - expectedValue, cmp, params); - } - void classicControlled(const OpType op, const Qubit target, - const Controls& controls, + std::uint64_t expectedValue = 1U, + ComparisonKind cmp = Eq, + const std::vector& params = {}); + void classicControlled(OpType op, Qubit target, const Controls& controls, const ClassicalRegister& controlRegister, - const std::uint64_t expectedValue = 1U, - const ComparisonKind cmp = ComparisonKind::Eq, - const std::vector& params = {}) { - checkQubitRange(target, controls); - checkClassicalRegister(controlRegister); - std::unique_ptr gate = - std::make_unique(controls, target, op, params); - emplace_back(std::move(gate), controlRegister, - expectedValue, cmp); - } + std::uint64_t expectedValue = 1U, + ComparisonKind cmp = Eq, + const std::vector& params = {}); /// strip away qubits with no operations applied to them and which do not pop /// up in the output permutation \param force if true, also strip away idle @@ -744,17 +374,6 @@ class QuantumComputation { // output permutation void appendMeasurementsAccordingToOutputPermutation( const std::string& registerName = "c"); - // search for current position of target value in map and afterward exchange - // it with the value at new position - static void findAndSWAP(Qubit targetValue, Qubit newPosition, - Permutation& map) { - for (const auto& q : map) { - if (q.second == targetValue) { - std::swap(map.at(newPosition), map.at(q.first)); - break; - } - } - } // this function augments a given circuit by additional registers void addQubitRegister(std::size_t nq, const std::string& regName = "q"); @@ -779,11 +398,7 @@ class QuantumComputation { void addQubit(Qubit logicalQubitIndex, Qubit physicalQubitIndex, std::optional outputQubitIndex); - QuantumComputation instantiate(const VariableAssignment& assignment) { - QuantumComputation result(*this); - result.instantiateInplace(assignment); - return result; - } + QuantumComputation instantiate(const VariableAssignment& assignment) const; void instantiateInplace(const VariableAssignment& assignment); void addVariable(const SymbolOrNumber& expr); @@ -792,15 +407,7 @@ class QuantumComputation { (addVariable(vars), ...); } - [[nodiscard]] bool isVariableFree() const { - return std::all_of(ops.begin(), ops.end(), [](const auto& op) { - return !op->isSymbolicOperation(); - }); - } - - [[nodiscard]] const std::unordered_set& getVariables() const { - return occurringVariables; - } + [[nodiscard]] bool isVariableFree() const; /** * @brief Invert the circuit @@ -810,26 +417,12 @@ class QuantumComputation { * layout and output permutation sizes, the initial layout and output * permutation will not be swapped. */ - void invert() { - for (auto& op : ops) { - op->invert(); - } - std::reverse(ops.begin(), ops.end()); - - if (initialLayout.size() == outputPermutation.size()) { - std::swap(initialLayout, outputPermutation); - } else { - std::cerr << "Warning: Inverting a circuit with different initial layout " - "and output permutation sizes. This is not supported yet.\n" - "The circuit will be inverted, but the initial layout and " - "output permutation will not be swapped.\n"; - } - } + void invert(); /** * printing */ - virtual std::ostream& print(std::ostream& os) const; + std::ostream& print(std::ostream& os) const; friend std::ostream& operator<<(std::ostream& os, const QuantumComputation& qc) { @@ -838,7 +431,7 @@ class QuantumComputation { static void printBin(std::size_t n, std::stringstream& ss); - virtual std::ostream& printStatistics(std::ostream& os) const; + std::ostream& printStatistics(std::ostream& os) const; std::ostream& printRegisters(std::ostream& os = std::cout) const; @@ -873,29 +466,9 @@ class QuantumComputation { } // this convenience method allows to turn a circuit into an operation. - std::unique_ptr asOperation() { - if (ops.empty()) { - return {}; - } - if (ops.size() == 1) { - auto op = std::move(ops.front()); - ops.clear(); - return op; - } - return asCompoundOperation(); - } + std::unique_ptr asOperation(); - virtual void reset() { - ops.clear(); - nqubits = 0; - nclassics = 0; - nancillae = 0; - qregs.clear(); - cregs.clear(); - ancregs.clear(); - initialLayout.clear(); - outputPermutation.clear(); - } + void reset(); /** * @brief Reorders the operations in the quantum computation to establish a @@ -915,10 +488,54 @@ class QuantumComputation { */ [[nodiscard]] bool isDynamic() const; +protected: + void importOpenQASM3(std::istream& is); + void importReal(std::istream& is); + int readRealHeader(std::istream& is); + void readRealGateDescriptions(std::istream& is, int line); + void importTFC(std::istream& is); + int readTFCHeader(std::istream& is, std::map& varMap); + void readTFCGateDescriptions(std::istream& is, int line, + std::map& varMap); + void importQC(std::istream& is); + int readQCHeader(std::istream& is, std::map& varMap); + void readQCGateDescriptions(std::istream& is, int line, + std::map& varMap); + + [[nodiscard]] std::size_t getSmallestAncillary() const { + for (std::size_t i = 0; i < ancillary.size(); ++i) { + if (ancillary[i]) { + return i; + } + } + return ancillary.size(); + } + + [[nodiscard]] std::size_t getSmallestGarbage() const { + for (std::size_t i = 0; i < garbage.size(); ++i) { + if (garbage[i]) { + return i; + } + } + return garbage.size(); + } + [[nodiscard]] bool isLastOperationOnQubit(const const_iterator& opIt) const { + const auto end = ops.cend(); + return isLastOperationOnQubit(opIt, end); + } + void checkQubitRange(Qubit qubit) const; + void checkQubitRange(Qubit qubit, const Controls& controls) const; + void checkQubitRange(Qubit qubit0, Qubit qubit1, + const Controls& controls) const; + void checkQubitRange(const std::vector& qubits) const; + void checkBitRange(Bit bit) const; + void checkBitRange(const std::vector& bits) const; + void checkClassicalRegister(const ClassicalRegister& creg) const; + /** * Pass-Through */ - +public: // Iterators (pass-through) auto begin() noexcept { return ops.begin(); } [[nodiscard]] auto begin() const noexcept { return ops.begin(); } @@ -948,18 +565,14 @@ class QuantumComputation { void clear() noexcept { ops.clear(); } // NOLINTNEXTLINE(readability-identifier-naming) void pop_back() { ops.pop_back(); } - void resize(std::size_t count) { ops.resize(count); } - iterator erase(const_iterator pos) { return ops.erase(pos); } - iterator erase(const_iterator first, const_iterator last) { + void resize(const std::size_t count) { ops.resize(count); } + iterator erase(const const_iterator pos) { return ops.erase(pos); } + iterator erase(const const_iterator first, const const_iterator last) { return ops.erase(first, last); } // NOLINTNEXTLINE(readability-identifier-naming) template void push_back(const T& op) { - if (!ops.empty() && !op.isControlled() && !ops.back()->isControlled()) { - std::cerr << op.getName() << std::endl; - } - ops.push_back(std::make_unique(op)); } @@ -988,6 +601,6 @@ class QuantumComputation { [[nodiscard]] const auto& back() const { return ops.back(); } // reverse - void reverse() { std::reverse(ops.begin(), ops.end()); } + void reverse(); }; } // namespace qc diff --git a/include/mqt-core/ir/operations/AodOperation.hpp b/include/mqt-core/ir/operations/AodOperation.hpp index e00fdfbae..85975dc0c 100644 --- a/include/mqt-core/ir/operations/AodOperation.hpp +++ b/include/mqt-core/ir/operations/AodOperation.hpp @@ -14,13 +14,10 @@ #include "OpType.hpp" #include "Operation.hpp" -#include -#include #include #include #include #include -#include #include #include #include @@ -36,13 +33,9 @@ struct SingleOperation { SingleOperation(const Dimension d, const qc::fp s, const qc::fp e) : dir(d), start(s), end(e) {} - [[nodiscard]] std::string toQASMString() const { - std::stringstream ss; - ss << static_cast(dir) << ", " << start << ", " << end << "; "; - return ss.str(); - } + [[nodiscard]] std::string toQASMString() const; }; -class AodOperation : public qc::Operation { +class AodOperation final : public qc::Operation { std::vector operations; static std::vector @@ -74,47 +67,18 @@ class AodOperation : public qc::Operation { void addControl([[maybe_unused]] qc::Control c) override {} void clearControls() override {} void removeControl([[maybe_unused]] qc::Control c) override {} - qc::Controls::iterator removeControl(qc::Controls::iterator it) override { - return it; + qc::Controls::iterator + removeControl(const qc::Controls::iterator it) override { + return controls.erase(it); } - [[nodiscard]] std::vector getEnds(Dimension dir) const { - std::vector ends; - for (const auto& op : operations) { - if (op.dir == dir) { - ends.push_back(op.end); - } - } - return ends; - } + [[nodiscard]] std::vector getEnds(Dimension dir) const; - [[nodiscard]] std::vector getStarts(Dimension dir) const { - std::vector starts; - for (const auto& op : operations) { - if (op.dir == dir) { - starts.push_back(op.start); - } - } - return starts; - } + [[nodiscard]] std::vector getStarts(Dimension dir) const; - [[nodiscard]] qc::fp getMaxDistance(Dimension dir) const { - const auto distances = getDistances(dir); - if (distances.empty()) { - return 0; - } - return *std::max_element(distances.begin(), distances.end()); - } + [[nodiscard]] qc::fp getMaxDistance(Dimension dir) const; - [[nodiscard]] std::vector getDistances(Dimension dir) const { - std::vector params; - for (const auto& op : operations) { - if (op.dir == dir) { - params.push_back(std::abs(op.end - op.start)); - } - } - return params; - } + [[nodiscard]] std::vector getDistances(Dimension dir) const; void dumpOpenQASM(std::ostream& of, const qc::RegisterNames& qreg, const qc::RegisterNames& creg, size_t indent, diff --git a/include/mqt-core/ir/operations/ClassicControlledOperation.hpp b/include/mqt-core/ir/operations/ClassicControlledOperation.hpp index 0c48a3065..cb27f5448 100644 --- a/include/mqt-core/ir/operations/ClassicControlledOperation.hpp +++ b/include/mqt-core/ir/operations/ClassicControlledOperation.hpp @@ -11,7 +11,6 @@ #include "Control.hpp" #include "Definitions.hpp" -#include "OpType.hpp" #include "Operation.hpp" #include "ir/Permutation.hpp" @@ -41,57 +40,33 @@ std::string toString(const ComparisonKind& kind); std::ostream& operator<<(std::ostream& os, const ComparisonKind& kind); class ClassicControlledOperation final : public Operation { -private: - std::unique_ptr op; - ClassicalRegister controlRegister; - std::uint64_t expectedValue = 1U; - ComparisonKind comparisonKind = ComparisonKind::Eq; - public: // Applies operation `_op` if the creg starting at index `control` has the // expected value - ClassicControlledOperation(std::unique_ptr&& operation, + ClassicControlledOperation(std::unique_ptr&& operation, ClassicalRegister controlReg, std::uint64_t expectedVal = 1U, - ComparisonKind kind = ComparisonKind::Eq) - : op(std::move(operation)), controlRegister(std::move(controlReg)), - expectedValue(expectedVal), comparisonKind(kind) { - name = "c_" + shortName(op->getType()); - parameter.reserve(3); - parameter.emplace_back(static_cast(controlRegister.first)); - parameter.emplace_back(static_cast(controlRegister.second)); - parameter.emplace_back(static_cast(expectedValue)); - type = ClassicControlled; - } + ComparisonKind kind = Eq); - ClassicControlledOperation(const ClassicControlledOperation& ccop) - : Operation(ccop), controlRegister(ccop.controlRegister), - expectedValue(ccop.expectedValue) { - op = ccop.op->clone(); - } + ClassicControlledOperation(const ClassicControlledOperation& ccop); - ClassicControlledOperation& - operator=(const ClassicControlledOperation& ccop) { - if (this != &ccop) { - Operation::operator=(ccop); - controlRegister = ccop.controlRegister; - expectedValue = ccop.expectedValue; - op = ccop.op->clone(); - } - return *this; - } + ClassicControlledOperation& operator=(const ClassicControlledOperation& ccop); [[nodiscard]] std::unique_ptr clone() const override { return std::make_unique(*this); } - [[nodiscard]] auto getControlRegister() const { return controlRegister; } + [[nodiscard]] auto getControlRegister() const noexcept { + return controlRegister; + } - [[nodiscard]] auto getExpectedValue() const { return expectedValue; } + [[nodiscard]] auto getExpectedValue() const noexcept { return expectedValue; } [[nodiscard]] auto getOperation() const { return op.get(); } - [[nodiscard]] auto getComparisonKind() const { return comparisonKind; } + [[nodiscard]] auto getComparisonKind() const noexcept { + return comparisonKind; + } [[nodiscard]] const Targets& getTargets() const override { return op->getTargets(); @@ -115,11 +90,13 @@ class ClassicControlledOperation final : public Operation { [[nodiscard]] bool isUnitary() const override { return false; } - [[nodiscard]] bool isClassicControlledOperation() const override { + [[nodiscard]] bool isClassicControlledOperation() const noexcept override { return true; } - [[nodiscard]] bool actsOn(Qubit i) const override { return op->actsOn(i); } + [[nodiscard]] bool actsOn(const Qubit i) const override { + return op->actsOn(i); + } void addControl(const Control c) override { op->addControl(c); } @@ -133,60 +110,26 @@ class ClassicControlledOperation final : public Operation { [[nodiscard]] bool equals(const Operation& operation, const Permutation& perm1, - const Permutation& perm2) const override { - if (const auto* classic = - dynamic_cast(&operation)) { - if (controlRegister != classic->controlRegister) { - return false; - } - - if (expectedValue != classic->expectedValue || - comparisonKind != classic->comparisonKind) { - return false; - } - - return op->equals(*classic->op, perm1, perm2); - } - return false; - } + const Permutation& perm2) const override; [[nodiscard]] bool equals(const Operation& operation) const override { return equals(operation, {}, {}); } void dumpOpenQASM(std::ostream& of, const RegisterNames& qreg, const RegisterNames& creg, std::size_t indent, - bool openQASM3) const override { - of << std::string(indent * OUTPUT_INDENT_SIZE, ' '); - of << "if ("; - if (isWholeQubitRegister(creg, controlRegister.first, - controlRegister.first + controlRegister.second - - 1)) { - of << creg[controlRegister.first].first; - } else { - // This might use slices in the future to address multiple bits. - if (controlRegister.second != 1) { - throw QFRException( - "Control register of classically controlled operation may either" - " be a single bit or a whole register."); - } - of << creg[controlRegister.first].second; - } - of << " " << comparisonKind << " " << expectedValue << ") "; - if (openQASM3) { - of << "{\n"; - } - op->dumpOpenQASM(of, qreg, creg, indent + 1, openQASM3); - if (openQASM3) { - of << "}\n"; - } - } + bool openQASM3) const override; void invert() override { op->invert(); } + +private: + std::unique_ptr op; + ClassicalRegister controlRegister; + std::uint64_t expectedValue = 1U; + ComparisonKind comparisonKind = Eq; }; } // namespace qc -namespace std { -template <> struct hash { +template <> struct std::hash { std::size_t operator()(qc::ClassicControlledOperation const& ccop) const noexcept { auto seed = qc::combineHash(ccop.getControlRegister().first, @@ -196,4 +139,3 @@ template <> struct hash { return seed; } }; -} // namespace std diff --git a/include/mqt-core/ir/operations/CompoundOperation.hpp b/include/mqt-core/ir/operations/CompoundOperation.hpp index 9a3323ff1..e06232647 100644 --- a/include/mqt-core/ir/operations/CompoundOperation.hpp +++ b/include/mqt-core/ir/operations/CompoundOperation.hpp @@ -9,10 +9,10 @@ #pragma once -#include "../Permutation.hpp" #include "Control.hpp" #include "Definitions.hpp" #include "Operation.hpp" +#include "ir/Permutation.hpp" #include #include @@ -25,9 +25,9 @@ namespace qc { class CompoundOperation final : public Operation { public: - using iterator = typename std::vector>::iterator; + using iterator = std::vector>::iterator; using const_iterator = - typename std::vector>::const_iterator; + std::vector>::const_iterator; private: std::vector> ops; @@ -46,13 +46,13 @@ class CompoundOperation final : public Operation { [[nodiscard]] std::unique_ptr clone() const override; - [[nodiscard]] bool isCompoundOperation() const override; + [[nodiscard]] bool isCompoundOperation() const noexcept override; [[nodiscard]] bool isNonUnitaryOperation() const override; [[nodiscard]] bool isSymbolicOperation() const override; - [[nodiscard]] bool isCustomGate() const; + [[nodiscard]] bool isCustomGate() const noexcept; void addControl(Control c) override; @@ -78,7 +78,7 @@ class CompoundOperation final : public Operation { const RegisterNames& creg, size_t indent, bool openQASM3) const override; - std::vector>& getOps() { return ops; } + std::vector>& getOps() noexcept { return ops; } [[nodiscard]] auto getUsedQubitsPermuted(const Permutation& perm) const -> std::set override; @@ -145,7 +145,7 @@ class CompoundOperation final : public Operation { [[nodiscard]] std::size_t max_size() const noexcept { return ops.max_size(); } [[nodiscard]] std::size_t capacity() const noexcept { return ops.capacity(); } - void reserve(std::size_t newCap) { ops.reserve(newCap); } + void reserve(const std::size_t newCap) { ops.reserve(newCap); } // NOLINTNEXTLINE(readability-identifier-naming) void shrink_to_fit() { ops.shrink_to_fit(); } @@ -153,9 +153,9 @@ class CompoundOperation final : public Operation { void clear() noexcept { ops.clear(); } // NOLINTNEXTLINE(readability-identifier-naming) void pop_back() { ops.pop_back(); } - void resize(std::size_t count) { ops.resize(count); } - iterator erase(const_iterator pos) { return ops.erase(pos); } - iterator erase(const_iterator first, const_iterator last) { + void resize(const std::size_t count) { ops.resize(count); } + iterator erase(const const_iterator pos) { return ops.erase(pos); } + iterator erase(const const_iterator first, const const_iterator last) { return ops.erase(first, last); } @@ -186,12 +186,10 @@ class CompoundOperation final : public Operation { return ops.insert(iter, std::forward(op)); } - [[nodiscard]] const auto& at(std::size_t i) const { return ops.at(i); } + [[nodiscard]] const auto& at(const std::size_t i) const { return ops.at(i); } }; } // namespace qc -namespace std { -template <> struct hash { +template <> struct std::hash { std::size_t operator()(const qc::CompoundOperation& co) const noexcept; -}; -} // namespace std +}; // namespace std diff --git a/include/mqt-core/ir/operations/Control.hpp b/include/mqt-core/ir/operations/Control.hpp index c66098d8f..f32a045d5 100644 --- a/include/mqt-core/ir/operations/Control.hpp +++ b/include/mqt-core/ir/operations/Control.hpp @@ -86,11 +86,9 @@ inline Control operator""_nc(unsigned long long int q) { } // namespace literals } // namespace qc -namespace std { -template <> struct hash { - std::size_t operator()(const qc::Control& c) const { +template <> struct std::hash { + std::size_t operator()(const qc::Control& c) const noexcept { return std::hash{}(c.qubit) ^ std::hash{}(c.type); } }; -} // namespace std diff --git a/include/mqt-core/ir/operations/Expression.hpp b/include/mqt-core/ir/operations/Expression.hpp index 78a77c569..8194dd0f3 100644 --- a/include/mqt-core/ir/operations/Expression.hpp +++ b/include/mqt-core/ir/operations/Expression.hpp @@ -30,7 +30,7 @@ namespace sym { static constexpr double TOLERANCE = 1e-9; -class SymbolicException : public std::invalid_argument { +class SymbolicException final : public std::invalid_argument { std::string msg; public: @@ -51,7 +51,7 @@ struct Variable { explicit Variable(const std::string& name); - [[nodiscard]] std::string getName() const; + [[nodiscard]] std::string getName() const noexcept; bool operator==(const Variable& rhs) const { return id == rhs.id; } @@ -66,13 +66,11 @@ struct Variable { }; } // namespace sym -namespace std { -template <> struct hash { - std::size_t operator()(const sym::Variable& var) const { +template <> struct std::hash { + std::size_t operator()(const sym::Variable& var) const noexcept { return std::hash()(var.getName()); } }; -} // namespace std namespace sym { using VariableAssignment = std::unordered_map; @@ -82,8 +80,8 @@ template >> class Term { public: - [[nodiscard]] Variable getVar() const { return var; } - [[nodiscard]] T getCoeff() const { return coeff; } + [[nodiscard]] Variable getVar() const noexcept { return var; } + [[nodiscard]] T getCoeff() const noexcept { return coeff; } [[nodiscard]] bool hasZeroCoeff() const { return std::abs(static_cast(coeff)) < TOLERANCE; @@ -126,46 +124,42 @@ class Term { }; template >> -inline Term operator*(Term lhs, const double rhs) { +Term operator*(Term lhs, const double rhs) { lhs *= rhs; return lhs; } template >> -inline Term operator/(Term lhs, const double rhs) { +Term operator/(Term lhs, const double rhs) { lhs /= rhs; return lhs; } template >> -inline Term operator*(double lhs, const Term& rhs) { +Term operator*(double lhs, const Term& rhs) { return rhs * lhs; } template >> -inline Term operator/(double lhs, const Term& rhs) { +Term operator/(double lhs, const Term& rhs) { return rhs / lhs; } -template -inline bool operator==(const Term& lhs, const Term& rhs) { +template bool operator==(const Term& lhs, const Term& rhs) { return lhs.getVar() == rhs.getVar() && std::abs(lhs.getCoeff() - rhs.getCoeff()) < TOLERANCE; } -template -inline bool operator!=(const Term& lhs, const Term& rhs) { +template bool operator!=(const Term& lhs, const Term& rhs) { return !(lhs == rhs); } } // namespace sym -namespace std { -template struct hash> { - std::size_t operator()(const sym::Term& term) const { +template struct std::hash> { + std::size_t operator()(const sym::Term& term) const noexcept { const auto h1 = std::hash{}(term.getVar()); const auto h2 = std::hash{}(term.getCoeff()); return qc::combineHash(h1, h2); } }; -} // namespace std namespace sym { template < @@ -272,8 +266,7 @@ class Expression { return *this; } - template , int> = 0> + template , int> = 0> Expression& operator*=(const U& rhs) { if (std::abs(static_cast(T{rhs})) < TOLERANCE) { terms.clear(); @@ -295,8 +288,7 @@ class Expression { return *this; } - template , int> = 0> + template , int> = 0> Expression& operator/=(const U& rhs) { if (std::abs(static_cast(T{rhs})) < TOLERANCE) { throw std::runtime_error("Trying to divide expression by 0!"); @@ -330,7 +322,7 @@ class Expression { [[nodiscard]] const Term& operator[](const std::size_t i) const { return terms[i]; } - [[nodiscard]] U getConst() const { return constant; } + [[nodiscard]] U getConst() const noexcept { return constant; } void setConst(const U& val) { constant = val; } [[nodiscard]] auto numTerms() const { return terms.size(); } @@ -345,7 +337,7 @@ class Expression { } template >* = nullptr> + std::enable_if_t>* = nullptr> Expression convert() const { return Expression(terms, V{constant}); } @@ -386,117 +378,114 @@ class Expression { }; template -inline Expression operator+(Expression lhs, - const Expression& rhs) { +Expression operator+(Expression lhs, const Expression& rhs) { lhs += rhs; return lhs; } template -inline Expression operator+(Expression lhs, const Term& rhs) { +Expression operator+(Expression lhs, const Term& rhs) { lhs += rhs; return lhs; } template -inline Expression operator+(const Term& lhs, Expression rhs) { +Expression operator+(const Term& lhs, Expression rhs) { rhs += lhs; return rhs; } template -inline Expression operator+(const U& lhs, Expression rhs) { +Expression operator+(const U& lhs, Expression rhs) { rhs += lhs; return rhs; } template -inline Expression operator+(Expression lhs, const U& rhs) { +Expression operator+(Expression lhs, const U& rhs) { lhs += rhs; return lhs; } template -inline Expression operator+([[maybe_unused]] const T& lhs, - Expression rhs) { +Expression operator+([[maybe_unused]] const T& lhs, + Expression rhs) { rhs += rhs; return rhs; } template -inline Expression operator-(Expression lhs, - const Expression& rhs) { +Expression operator-(Expression lhs, const Expression& rhs) { lhs -= rhs; return lhs; } template -inline Expression operator-(Expression lhs, const Term& rhs) { +Expression operator-(Expression lhs, const Term& rhs) { lhs -= rhs; return lhs; } template -inline Expression operator-(const Term& lhs, Expression rhs) { +Expression operator-(const Term& lhs, Expression rhs) { rhs -= lhs; return rhs; } template -inline Expression operator-(const U& lhs, Expression rhs) { +Expression operator-(const U& lhs, Expression rhs) { rhs -= lhs; return rhs; } template -inline Expression operator-(Expression lhs, const U& rhs) { +Expression operator-(Expression lhs, const U& rhs) { lhs -= rhs; return lhs; } template -inline Expression operator*(Expression lhs, const T& rhs) { +Expression operator*(Expression lhs, const T& rhs) { lhs *= rhs; return lhs; } template >* = nullptr> -inline Expression operator*(Expression lhs, const U& rhs) { + std::enable_if_t>* = nullptr> +Expression operator*(Expression lhs, const U& rhs) { lhs *= rhs; return lhs; } template -inline Expression operator/(Expression lhs, const T& rhs) { +Expression operator/(Expression lhs, const T& rhs) { lhs /= rhs; return lhs; } template >* = nullptr> -inline Expression operator/(Expression lhs, const U& rhs) { + std::enable_if_t>* = nullptr> +Expression operator/(Expression lhs, const U& rhs) { lhs /= rhs; return lhs; } template -inline Expression operator/(Expression lhs, int64_t rhs) { +Expression operator/(Expression lhs, int64_t rhs) { lhs /= rhs; return lhs; } template -inline Expression operator*(const T& lhs, Expression rhs) { +Expression operator*(const T& lhs, Expression rhs) { return rhs * lhs; } template >* = nullptr> -inline Expression operator*(const U& lhs, Expression rhs) { + std::enable_if_t>* = nullptr> +Expression operator*(const U& lhs, Expression rhs) { return rhs * lhs; } template -inline bool operator==(const Expression& lhs, - const Expression& rhs) { +bool operator==(const Expression& lhs, const Expression& rhs) { if (lhs.numTerms() != rhs.numTerms() || lhs.getConst() != rhs.getConst()) { return false; } @@ -511,8 +500,7 @@ inline bool operator==(const Expression& lhs, } template -inline bool operator!=(const Expression& lhs, - const Expression& rhs) { +bool operator!=(const Expression& lhs, const Expression& rhs) { return !(lhs == rhs); } @@ -533,9 +521,8 @@ std::ostream& operator<<(std::ostream& os, const Expression& expr) { } } // namespace sym -namespace std { -template struct hash> { - std::size_t operator()(const sym::Expression& expr) const { +template struct std::hash> { + std::size_t operator()(const sym::Expression& expr) const noexcept { std::size_t seed = 0U; for (const auto& term : expr) { qc::hashCombine(seed, std::hash>{}(term)); @@ -543,8 +530,7 @@ template struct hash> { qc::hashCombine(seed, std::hash{}(expr.getConst())); return seed; } -}; -} // namespace std +}; // namespace std namespace qc { using Symbolic = sym::Expression; diff --git a/include/mqt-core/ir/operations/NonUnitaryOperation.hpp b/include/mqt-core/ir/operations/NonUnitaryOperation.hpp index 900239d3b..9e2081047 100644 --- a/include/mqt-core/ir/operations/NonUnitaryOperation.hpp +++ b/include/mqt-core/ir/operations/NonUnitaryOperation.hpp @@ -9,11 +9,11 @@ #pragma once -#include "../Permutation.hpp" #include "Control.hpp" #include "Definitions.hpp" #include "OpType.hpp" #include "Operation.hpp" +#include "ir/Permutation.hpp" #include #include @@ -25,16 +25,6 @@ namespace qc { class NonUnitaryOperation final : public Operation { -protected: - std::vector classics; // vector for the classical bits to measure into - - static void printMeasurement(std::ostream& os, const std::vector& q, - const std::vector& c, - const Permutation& permutation, - std::size_t nqubits); - void printReset(std::ostream& os, const std::vector& q, - const Permutation& permutation, std::size_t nqubits) const; - public: // Measurement constructor NonUnitaryOperation(std::vector qubitRegister, @@ -93,11 +83,20 @@ class NonUnitaryOperation final : public Operation { } void apply(const Permutation& permutation) override; + +protected: + std::vector classics; // vector for the classical bits to measure into + + static void printMeasurement(std::ostream& os, const std::vector& q, + const std::vector& c, + const Permutation& permutation, + std::size_t nqubits); + void printReset(std::ostream& os, const std::vector& q, + const Permutation& permutation, std::size_t nqubits) const; }; } // namespace qc -namespace std { -template <> struct hash { +template <> struct std::hash { std::size_t operator()(qc::NonUnitaryOperation const& op) const noexcept { std::size_t seed = 0U; qc::hashCombine(seed, op.getType()); @@ -110,4 +109,3 @@ template <> struct hash { return seed; } }; -} // namespace std diff --git a/include/mqt-core/ir/operations/Operation.hpp b/include/mqt-core/ir/operations/Operation.hpp index f1db75750..12d81a652 100644 --- a/include/mqt-core/ir/operations/Operation.hpp +++ b/include/mqt-core/ir/operations/Operation.hpp @@ -9,10 +9,10 @@ #pragma once -#include "../Permutation.hpp" #include "Control.hpp" #include "Definitions.hpp" #include "OpType.hpp" +#include "ir/Permutation.hpp" #include #include @@ -129,11 +129,13 @@ class Operation { [[nodiscard]] virtual bool isStandardOperation() const { return false; } - [[nodiscard]] virtual bool isCompoundOperation() const { return false; } + [[nodiscard]] virtual bool isCompoundOperation() const noexcept { + return false; + } [[nodiscard]] virtual bool isNonUnitaryOperation() const { return false; } - [[nodiscard]] virtual bool isClassicControlledOperation() const { + [[nodiscard]] virtual bool isClassicControlledOperation() const noexcept { return false; } @@ -205,8 +207,7 @@ class Operation { }; } // namespace qc -namespace std { -template <> struct hash { +template <> struct std::hash { std::size_t operator()(const qc::Operation& op) const noexcept { std::size_t seed = 0U; qc::hashCombine(seed, hash{}(op.getType())); @@ -225,4 +226,3 @@ template <> struct hash { return seed; } }; -} // namespace std diff --git a/include/mqt-core/ir/operations/StandardOperation.hpp b/include/mqt-core/ir/operations/StandardOperation.hpp index df698eabb..7968aead9 100644 --- a/include/mqt-core/ir/operations/StandardOperation.hpp +++ b/include/mqt-core/ir/operations/StandardOperation.hpp @@ -9,11 +9,11 @@ #pragma once -#include "../Permutation.hpp" #include "Control.hpp" #include "Definitions.hpp" #include "OpType.hpp" #include "Operation.hpp" +#include "ir/Permutation.hpp" #include #include diff --git a/include/mqt-core/ir/operations/SymbolicOperation.hpp b/include/mqt-core/ir/operations/SymbolicOperation.hpp index e820810a5..99752ee69 100644 --- a/include/mqt-core/ir/operations/SymbolicOperation.hpp +++ b/include/mqt-core/ir/operations/SymbolicOperation.hpp @@ -9,14 +9,13 @@ #pragma once -#include "../Permutation.hpp" #include "Control.hpp" #include "Definitions.hpp" #include "Expression.hpp" #include "OpType.hpp" #include "StandardOperation.hpp" +#include "ir/Permutation.hpp" -#include #include #include #include @@ -27,75 +26,13 @@ namespace qc { -// Overload pattern for std::visit -template struct Overload : Ts... { - using Ts::operator()...; -}; -template Overload(Ts...) -> Overload; - class SymbolicOperation final : public StandardOperation { -protected: - std::vector> symbolicParameter; - - static OpType parseU3(const Symbolic& theta, fp& phi, fp& lambda); - static OpType parseU3(fp& theta, const Symbolic& phi, fp& lambda); - static OpType parseU3(fp& theta, fp& phi, const Symbolic& lambda); - static OpType parseU3(const Symbolic& theta, const Symbolic& phi, fp& lambda); - static OpType parseU3(const Symbolic& theta, fp& phi, const Symbolic& lambda); - static OpType parseU3(fp& theta, const Symbolic& phi, const Symbolic& lambda); - - static OpType parseU2(const Symbolic& phi, const Symbolic& lambda); - static OpType parseU2(const Symbolic& phi, fp& lambda); - static OpType parseU2(fp& phi, const Symbolic& lambda); - - static OpType parseU1(const Symbolic& lambda); - - void checkSymbolicUgate(); - - void storeSymbolOrNumber(const SymbolOrNumber& param, std::size_t i); - - [[nodiscard]] bool isSymbolicParameter(const std::size_t i) const { - return symbolicParameter.at(i).has_value(); - } - - static bool isSymbol(const SymbolOrNumber& param) { - return std::holds_alternative(param); - } - - static Symbolic& getSymbol(SymbolOrNumber& param) { - return std::get(param); - } - - static fp& getNumber(SymbolOrNumber& param) { return std::get(param); } - - void setup(const std::vector& params); - - [[nodiscard]] static fp - getInstantiation(const SymbolOrNumber& symOrNum, - const VariableAssignment& assignment); - - void negateSymbolicParameter(std::size_t index); - - void addToSymbolicParameter(std::size_t index, fp value); - public: SymbolicOperation() = default; - [[nodiscard]] SymbolOrNumber getParameter(const std::size_t i) const { - const auto& param = symbolicParameter.at(i); - if (param.has_value()) { - return *param; - } - return parameter.at(i); - } + [[nodiscard]] SymbolOrNumber getParameter(std::size_t i) const; - [[nodiscard]] std::vector getParameters() const { - std::vector params{}; - for (std::size_t i = 0; i < parameter.size(); ++i) { - params.emplace_back(getParameter(i)); - } - return params; - } + [[nodiscard]] std::vector getParameters() const; void setSymbolicParameter(const Symbolic& par, const std::size_t i) { symbolicParameter.at(i) = par; @@ -121,19 +58,11 @@ class SymbolicOperation final : public StandardOperation { SymbolicOperation(const Controls& c, Qubit target0, Qubit target1, OpType g, const std::vector& params = {}); - [[nodiscard]] std::unique_ptr clone() const override { - return std::make_unique(*this); - } + [[nodiscard]] std::unique_ptr clone() const override; - [[nodiscard]] bool isSymbolicOperation() const override { - return std::any_of(symbolicParameter.begin(), symbolicParameter.end(), - [](const auto& sym) { return sym.has_value(); }); - } + [[nodiscard]] bool isSymbolicOperation() const override; - [[nodiscard]] bool isStandardOperation() const override { - return std::all_of(symbolicParameter.begin(), symbolicParameter.end(), - [](const auto& sym) { return !sym.has_value(); }); - } + [[nodiscard]] bool isStandardOperation() const override; [[nodiscard]] bool equals(const Operation& op, const Permutation& perm1, const Permutation& perm2) const override; @@ -153,11 +82,48 @@ class SymbolicOperation final : public StandardOperation { void instantiate(const VariableAssignment& assignment); void invert() override; + +protected: + std::vector> symbolicParameter; + + static OpType parseU3(const Symbolic& theta, fp& phi, fp& lambda); + static OpType parseU3(fp& theta, const Symbolic& phi, fp& lambda); + static OpType parseU3(fp& theta, fp& phi, const Symbolic& lambda); + static OpType parseU3(const Symbolic& theta, const Symbolic& phi, fp& lambda); + static OpType parseU3(const Symbolic& theta, fp& phi, const Symbolic& lambda); + static OpType parseU3(fp& theta, const Symbolic& phi, const Symbolic& lambda); + + static OpType parseU2(const Symbolic& phi, const Symbolic& lambda); + static OpType parseU2(const Symbolic& phi, fp& lambda); + static OpType parseU2(fp& phi, const Symbolic& lambda); + + static OpType parseU1(const Symbolic& lambda); + + void checkSymbolicUgate(); + + void storeSymbolOrNumber(const SymbolOrNumber& param, std::size_t i); + + [[nodiscard]] bool isSymbolicParameter(std::size_t i) const; + + static bool isSymbol(const SymbolOrNumber& param); + + static Symbolic& getSymbol(SymbolOrNumber& param); + + static fp& getNumber(SymbolOrNumber& param); + + void setup(const std::vector& params); + + [[nodiscard]] static fp + getInstantiation(const SymbolOrNumber& symOrNum, + const VariableAssignment& assignment); + + void negateSymbolicParameter(std::size_t index); + + void addToSymbolicParameter(std::size_t index, fp value); }; } // namespace qc -namespace std { -template <> struct hash { +template <> struct std::hash { std::size_t operator()(qc::SymbolicOperation const& op) const noexcept { std::size_t seed = 0U; qc::hashCombine(seed, std::hash{}(op)); @@ -171,4 +137,3 @@ template <> struct hash { return seed; } }; -} // namespace std diff --git a/include/mqt-core/ir/parsers/qasm3_parser/Gate.hpp b/include/mqt-core/ir/parsers/qasm3_parser/Gate.hpp index f32d353cc..805524412 100644 --- a/include/mqt-core/ir/parsers/qasm3_parser/Gate.hpp +++ b/include/mqt-core/ir/parsers/qasm3_parser/Gate.hpp @@ -34,7 +34,7 @@ struct Gate { virtual size_t getNParameters() = 0; }; -struct StandardGate : Gate { +struct StandardGate final : Gate { GateInfo info; explicit StandardGate(const GateInfo& gateInfo) : info(gateInfo) {} @@ -45,7 +45,7 @@ struct StandardGate : Gate { size_t getNParameters() override { return info.nParameters; } }; -struct CompoundGate : Gate { +struct CompoundGate final : Gate { std::vector parameterNames; std::vector targetNames; std::vector> body; diff --git a/include/mqt-core/ir/parsers/qasm3_parser/NestedEnvironment.hpp b/include/mqt-core/ir/parsers/qasm3_parser/NestedEnvironment.hpp index 1faa331f1..0a3876628 100644 --- a/include/mqt-core/ir/parsers/qasm3_parser/NestedEnvironment.hpp +++ b/include/mqt-core/ir/parsers/qasm3_parser/NestedEnvironment.hpp @@ -15,13 +15,12 @@ #include template class NestedEnvironment { -private: std::vector> env{}; public: - NestedEnvironment() { env.push_back({}); }; + NestedEnvironment() { env.emplace_back(); }; - void push() { env.push_back({}); } + void push() { env.emplace_back(); } void pop() { env.pop_back(); } diff --git a/include/mqt-core/ir/parsers/qasm3_parser/Statement.hpp b/include/mqt-core/ir/parsers/qasm3_parser/Statement.hpp index 791c85dac..5aa030927 100644 --- a/include/mqt-core/ir/parsers/qasm3_parser/Statement.hpp +++ b/include/mqt-core/ir/parsers/qasm3_parser/Statement.hpp @@ -50,7 +50,7 @@ class Expression { virtual std::string getName() = 0; }; -class DeclarationExpression { +class DeclarationExpression final { public: std::shared_ptr expression; @@ -60,7 +60,7 @@ class DeclarationExpression { virtual ~DeclarationExpression() = default; }; -class Constant : public Expression { +class Constant final : public Expression { std::variant val; bool isSigned; bool isFp; @@ -80,12 +80,12 @@ class Constant : public Expression { [[nodiscard]] bool isUInt() const { return !isFp && !isSigned; } [[nodiscard]] bool isFP() const { return isFp; } [[nodiscard]] bool isBool() const { return isBoolean; } - [[nodiscard]] virtual int64_t getSInt() const { return std::get<0>(val); } - [[nodiscard]] virtual uint64_t getUInt() const { + [[nodiscard]] int64_t getSInt() const { return std::get<0>(val); } + [[nodiscard]] uint64_t getUInt() const { return static_cast(std::get<0>(val)); } - [[nodiscard]] virtual double getFP() const { return std::get<1>(val); } - [[nodiscard]] virtual double asFP() const { + [[nodiscard]] double getFP() const { return std::get<1>(val); } + [[nodiscard]] double asFP() const { if (isFp) { return getFP(); } @@ -94,13 +94,14 @@ class Constant : public Expression { } return static_cast(getUInt()); } - [[nodiscard]] virtual bool getBool() const { return std::get<2>(val); } + [[nodiscard]] bool getBool() const { return std::get<2>(val); } std::string getName() override { return "Constant"; } }; -class BinaryExpression : public Expression, - public std::enable_shared_from_this { +class BinaryExpression final + : public Expression, + public std::enable_shared_from_this { public: enum Op : uint8_t { Power, @@ -137,8 +138,9 @@ class BinaryExpression : public Expression, std::optional getComparisonKind(BinaryExpression::Op op); -class UnaryExpression : public Expression, - public std::enable_shared_from_this { +class UnaryExpression final + : public Expression, + public std::enable_shared_from_this { public: enum Op : uint8_t { BitwiseNot, @@ -162,7 +164,7 @@ class UnaryExpression : public Expression, std::string getName() override { return "UnaryExpr"; } }; -class IdentifierExpression +class IdentifierExpression final : public Expression, public std::enable_shared_from_this { public: @@ -175,8 +177,9 @@ class IdentifierExpression } }; -class IdentifierList : public Expression, - public std::enable_shared_from_this { +class IdentifierList final + : public Expression, + public std::enable_shared_from_this { public: std::vector> identifiers; @@ -199,7 +202,7 @@ class GateOperand { : identifier(std::move(id)), expression(std::move(expr)) {} }; -class MeasureExpression +class MeasureExpression final : public Expression, public std::enable_shared_from_this { public: @@ -229,8 +232,9 @@ class QuantumStatement : public Statement { : Statement(std::move(debug)) {} }; -class GateDeclaration : public Statement, - public std::enable_shared_from_this { +class GateDeclaration final + : public Statement, + public std::enable_shared_from_this { public: std::string identifier; std::shared_ptr parameters; @@ -256,7 +260,7 @@ class GateDeclaration : public Statement, } }; -class VersionDeclaration +class VersionDeclaration final : public Statement, public std::enable_shared_from_this { public: @@ -271,8 +275,8 @@ class VersionDeclaration } }; -class InitialLayout : public Statement, - public std::enable_shared_from_this { +class InitialLayout final : public Statement, + public std::enable_shared_from_this { public: qc::Permutation permutation; @@ -285,7 +289,7 @@ class InitialLayout : public Statement, } }; -class OutputPermutation +class OutputPermutation final : public Statement, public std::enable_shared_from_this { public: @@ -301,7 +305,7 @@ class OutputPermutation } }; -class DeclarationStatement +class DeclarationStatement final : public Statement, public std::enable_shared_from_this { public: @@ -322,21 +326,17 @@ class DeclarationStatement }; class GateModifier : public std::enable_shared_from_this { -protected: - GateModifier() {} - public: virtual ~GateModifier() = default; }; -class InvGateModifier : public GateModifier, - public std::enable_shared_from_this { -public: - explicit InvGateModifier() = default; -}; +class InvGateModifier final + : public GateModifier, + public std::enable_shared_from_this {}; -class PowGateModifier : public GateModifier, - public std::enable_shared_from_this { +class PowGateModifier final + : public GateModifier, + public std::enable_shared_from_this { public: std::shared_ptr expression; @@ -344,8 +344,9 @@ class PowGateModifier : public GateModifier, : expression(std::move(expr)) {} }; -class CtrlGateModifier : public GateModifier, - public std::enable_shared_from_this { +class CtrlGateModifier final + : public GateModifier, + public std::enable_shared_from_this { public: bool ctrlType; std::shared_ptr expression; @@ -354,7 +355,7 @@ class CtrlGateModifier : public GateModifier, : ctrlType(ty), expression(std::move(expr)) {} }; -class GateCallStatement +class GateCallStatement final : public QuantumStatement, public std::enable_shared_from_this { public: @@ -376,7 +377,7 @@ class GateCallStatement } }; -class AssignmentStatement +class AssignmentStatement final : public Statement, public std::enable_shared_from_this { public: @@ -411,8 +412,9 @@ class AssignmentStatement } }; -class BarrierStatement : public QuantumStatement, - public std::enable_shared_from_this { +class BarrierStatement final + : public QuantumStatement, + public std::enable_shared_from_this { public: std::vector> gates; @@ -425,8 +427,9 @@ class BarrierStatement : public QuantumStatement, } }; -class ResetStatement : public QuantumStatement, - public std::enable_shared_from_this { +class ResetStatement final + : public QuantumStatement, + public std::enable_shared_from_this { public: std::shared_ptr gate; @@ -439,8 +442,8 @@ class ResetStatement : public QuantumStatement, } }; -class IfStatement : public Statement, - public std::enable_shared_from_this { +class IfStatement final : public Statement, + public std::enable_shared_from_this { public: std::shared_ptr condition; std::vector> thenStatements; diff --git a/include/mqt-core/ir/parsers/qasm3_parser/Token.hpp b/include/mqt-core/ir/parsers/qasm3_parser/Token.hpp index 62cec06fd..ad8d248e1 100644 --- a/include/mqt-core/ir/parsers/qasm3_parser/Token.hpp +++ b/include/mqt-core/ir/parsers/qasm3_parser/Token.hpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include #include @@ -22,7 +22,6 @@ namespace qasm3 { struct Token { -public: enum class Kind : uint8_t { None, diff --git a/include/mqt-core/ir/parsers/qasm3_parser/Types.hpp b/include/mqt-core/ir/parsers/qasm3_parser/Types.hpp index 4ebe878b7..e5305a24f 100644 --- a/include/mqt-core/ir/parsers/qasm3_parser/Types.hpp +++ b/include/mqt-core/ir/parsers/qasm3_parser/Types.hpp @@ -48,7 +48,7 @@ template class Type { virtual bool isUint() { return false; } virtual bool isBit() { return false; } - virtual bool fits(const Type& other) { return *this == other; } + virtual bool fits(const Type& other) { return *this == other; } virtual std::string toString() = 0; }; @@ -62,7 +62,7 @@ enum DesignatedTy : uint8_t { Angle, }; -template class DesignatedType : public Type { +template class DesignatedType final : public Type { public: ~DesignatedType() override = default; @@ -146,7 +146,7 @@ template class DesignatedType : public Type { enum UnsizedTy : uint8_t { Bool, Duration }; -template class UnsizedType : public Type { +template class UnsizedType final : public Type { public: ~UnsizedType() override = default; @@ -190,7 +190,7 @@ template class UnsizedType : public Type { } }; -template class ArrayType : public Type { +template class ArrayType final : public Type { public: std::shared_ptr> type; T size; diff --git a/include/mqt-core/ir/parsers/qasm3_parser/passes/ConstEvalPass.hpp b/include/mqt-core/ir/parsers/qasm3_parser/passes/ConstEvalPass.hpp index c93329f52..c7ef84386 100644 --- a/include/mqt-core/ir/parsers/qasm3_parser/passes/ConstEvalPass.hpp +++ b/include/mqt-core/ir/parsers/qasm3_parser/passes/ConstEvalPass.hpp @@ -9,18 +9,17 @@ #pragma once -#include "../Exception.hpp" -#include "../InstVisitor.hpp" -#include "../NestedEnvironment.hpp" -#include "../Statement.hpp" #include "CompilerPass.hpp" #include "Definitions.hpp" +#include "ir/parsers/qasm3_parser/Exception.hpp" +#include "ir/parsers/qasm3_parser/InstVisitor.hpp" +#include "ir/parsers/qasm3_parser/NestedEnvironment.hpp" +#include "ir/parsers/qasm3_parser/Statement.hpp" #include #include #include #include -#include #include #include @@ -50,8 +49,7 @@ struct ConstEvalValue { case ConstFloat: return std::make_shared(Constant(std::get<1>(value))); case ConstBool: - return std::make_shared( - Constant(static_cast(std::get<2>(value)), false)); + return std::make_shared(Constant(std::get<2>(value), false)); default: qc::unreachable(); } @@ -77,31 +75,14 @@ struct ConstEvalValue { bool operator!=(const ConstEvalValue& rhs) const { return !(*this == rhs); } - [[nodiscard]] std::string toString() const { - std::stringstream ss{}; - switch (type) { - case ConstInt: - ss << "ConstInt(" << std::get<0>(value) << ")"; - break; - case ConstUint: - ss << "ConstUint(" << std::get<0>(value) << ")"; - break; - case ConstFloat: - ss << "ConstFloat(" << std::get<1>(value) << ")"; - break; - case ConstBool: - ss << "ConstBool(" << std::get<2>(value) << ")"; - break; - } - - return ss.str(); - } + [[nodiscard]] std::string toString() const; }; -class ConstEvalPass : public CompilerPass, - public DefaultInstVisitor, - public ExpressionVisitor>, - public TypeVisitor> { +class ConstEvalPass final + : public CompilerPass, + public DefaultInstVisitor, + public ExpressionVisitor>, + public TypeVisitor> { NestedEnvironment env; template static int64_t castToWidth(int64_t value) { diff --git a/include/mqt-core/ir/parsers/qasm3_parser/passes/TypeCheckPass.hpp b/include/mqt-core/ir/parsers/qasm3_parser/passes/TypeCheckPass.hpp index da8c4d6b9..cb15dff83 100644 --- a/include/mqt-core/ir/parsers/qasm3_parser/passes/TypeCheckPass.hpp +++ b/include/mqt-core/ir/parsers/qasm3_parser/passes/TypeCheckPass.hpp @@ -9,14 +9,13 @@ #pragma once -#include "../Exception.hpp" -#include "../InstVisitor.hpp" -#include "../Statement.hpp" -#include "../Types.hpp" #include "CompilerPass.hpp" #include "ConstEvalPass.hpp" +#include "ir/parsers/qasm3_parser/Exception.hpp" +#include "ir/parsers/qasm3_parser/InstVisitor.hpp" +#include "ir/parsers/qasm3_parser/Statement.hpp" +#include "ir/parsers/qasm3_parser/Types.hpp" -#include #include #include #include @@ -54,9 +53,9 @@ struct InferredType { } }; -class TypeCheckPass : public CompilerPass, - public InstVisitor, - public ExpressionVisitor { +class TypeCheckPass final : public CompilerPass, + public InstVisitor, + public ExpressionVisitor { bool hasError = false; std::map env; // We need a reference to a const eval pass to evaluate types before type @@ -64,14 +63,7 @@ class TypeCheckPass : public CompilerPass, ConstEvalPass* constEvalPass; InferredType error(const std::string& msg, - const std::shared_ptr& debugInfo = nullptr) { - std::cerr << "Type check error: " << msg << '\n'; - if (debugInfo) { - std::cerr << " " << debugInfo->toString() << '\n'; - } - hasError = true; - return InferredType::error(); - } + const std::shared_ptr& debugInfo = nullptr); public: explicit TypeCheckPass(ConstEvalPass* pass) : constEvalPass(pass) {} diff --git a/src/circuit_optimizer/CircuitOptimizer.cpp b/src/circuit_optimizer/CircuitOptimizer.cpp index 9dc552ca7..9a952ca99 100644 --- a/src/circuit_optimizer/CircuitOptimizer.cpp +++ b/src/circuit_optimizer/CircuitOptimizer.cpp @@ -57,23 +57,23 @@ void CircuitOptimizer::removeOperation( (opSize == 0 || it->get()->getNqubits() == opSize)) { it = qc.erase(it); } else if ((*it)->isCompoundOperation()) { - auto* compOp = dynamic_cast((*it).get()); - auto cit = compOp->cbegin(); - while (cit != compOp->cend()) { - const auto* cop = cit->get(); - if (opTypes.find(cop->getType()) != opTypes.end() && + auto& compOp = dynamic_cast(**it); + auto cit = compOp.cbegin(); + while (cit != compOp.cend()) { + if (const auto* cop = cit->get(); + opTypes.find(cop->getType()) != opTypes.end() && (opSize == 0 || cop->getNqubits() == opSize)) { - cit = compOp->erase(cit); + cit = compOp.erase(cit); } else { ++cit; } } - if (compOp->empty()) { + if (compOp.empty()) { it = qc.erase(it); } else { - if (compOp->size() == 1) { + if (compOp.size() == 1) { // CompoundOperation has degraded to single Operation - (*it) = std::move(*(compOp->begin())); + (*it) = std::move(*(compOp.begin())); } ++it; } @@ -607,27 +607,27 @@ void CircuitOptimizer::decomposeSWAP(QuantumComputation& qc, ++it; } } else if ((*it)->isCompoundOperation()) { - auto* compOp = dynamic_cast((*it).get()); - auto cit = compOp->begin(); - while (cit != compOp->end()) { + auto& compOp = dynamic_cast(**it); + auto cit = compOp.begin(); + while (cit != compOp.end()) { if ((*cit)->isStandardOperation() && (*cit)->getType() == qc::SWAP) { const auto targets = (*cit)->getTargets(); - cit = compOp->erase(cit); - cit = compOp->insert(cit, Control{targets[0]}, - targets[1], qc::X); + cit = compOp.erase(cit); + cit = compOp.insert(cit, Control{targets[0]}, + targets[1], qc::X); if (isDirectedArchitecture) { - cit = compOp->insert(cit, targets[0], qc::H); - cit = compOp->insert(cit, targets[1], qc::H); - cit = compOp->insert(cit, Control{targets[0]}, - targets[1], qc::X); - cit = compOp->insert(cit, targets[0], qc::H); - cit = compOp->insert(cit, targets[1], qc::H); + cit = compOp.insert(cit, targets[0], qc::H); + cit = compOp.insert(cit, targets[1], qc::H); + cit = compOp.insert(cit, Control{targets[0]}, + targets[1], qc::X); + cit = compOp.insert(cit, targets[0], qc::H); + cit = compOp.insert(cit, targets[1], qc::H); } else { - cit = compOp->insert(cit, Control{targets[1]}, - targets[0], qc::X); + cit = compOp.insert(cit, Control{targets[1]}, + targets[0], qc::X); } - cit = compOp->insert(cit, Control{targets[0]}, - targets[1], qc::X); + cit = compOp.insert(cit, Control{targets[0]}, + targets[1], qc::X); } else { ++cit; } @@ -694,21 +694,21 @@ void CircuitOptimizer::eliminateResets(QuantumComputation& qc) { it = qc.erase(it); } else if (!replacementMap.empty()) { if ((*it)->isCompoundOperation()) { - auto* compOp = dynamic_cast((*it).get()); - auto compOpIt = compOp->begin(); - while (compOpIt != compOp->end()) { + auto& compOp = dynamic_cast(**it); + auto compOpIt = compOp.begin(); + while (compOpIt != compOp.end()) { if ((*compOpIt)->getType() == qc::Reset) { for (const auto& compTarget : (*compOpIt)->getTargets()) { auto indexAddQubit = static_cast(qc.getNqubits()); qc.addQubit(indexAddQubit, indexAddQubit, indexAddQubit); - auto oldReset = replacementMap.find(compTarget); - if (oldReset != replacementMap.end()) { + if (auto oldReset = replacementMap.find(compTarget); + oldReset != replacementMap.end()) { oldReset->second = indexAddQubit; } else { replacementMap.try_emplace(compTarget, indexAddQubit); } } - compOpIt = compOp->erase(compOpIt); + compOpIt = compOp.erase(compOpIt); } else { if ((*compOpIt)->isStandardOperation() || (*compOpIt)->isClassicControlledOperation()) { @@ -720,7 +720,7 @@ void CircuitOptimizer::eliminateResets(QuantumComputation& qc) { auto& targets = (*compOpIt)->getTargets(); changeTargets(targets, replacementMap); } - compOpIt++; + ++compOpIt; } } } @@ -734,9 +734,9 @@ void CircuitOptimizer::eliminateResets(QuantumComputation& qc) { auto& targets = (*it)->getTargets(); changeTargets(targets, replacementMap); } - it++; + ++it; } else { - it++; + ++it; } } } diff --git a/src/ir/QuantumComputation.cpp b/src/ir/QuantumComputation.cpp index 4c106de19..be4a35c88 100644 --- a/src/ir/QuantumComputation.cpp +++ b/src/ir/QuantumComputation.cpp @@ -41,6 +41,171 @@ namespace qc { +namespace { +template +void printSortedRegisters(const RegisterMap& regmap, + const std::string& identifier, std::ostream& of, + const bool openQASM3) { + // sort regs by start index + std::map> + sortedRegs{}; + for (const auto& reg : regmap) { + sortedRegs.insert({reg.second.first, reg}); + } + + for (const auto& reg : sortedRegs) { + if (openQASM3) { + of << identifier << "[" << reg.second.second.second << "] " + << reg.second.first << ";" << std::endl; + } else { + of << identifier << " " << reg.second.first << "[" + << reg.second.second.second << "];" << std::endl; + } + } +} + +template +void consolidateRegister(RegisterMap& regs) { + bool finished = regs.empty(); + while (!finished) { + for (const auto& qreg : regs) { + finished = true; + auto regname = qreg.first; + // check if lower part of register + if (regname.length() > 2 && + regname.compare(regname.size() - 2, 2, "_l") == 0) { + auto lowidx = qreg.second.first; + auto lownum = qreg.second.second; + // search for higher part of register + auto highname = regname.substr(0, regname.size() - 1) + 'h'; + auto it = regs.find(highname); + if (it != regs.end()) { + auto highidx = it->second.first; + auto highnum = it->second.second; + // fusion of registers possible + if (lowidx + lownum == highidx) { + finished = false; + auto targetname = regname.substr(0, regname.size() - 2); + auto targetidx = lowidx; + auto targetnum = lownum + highnum; + regs.insert({targetname, {targetidx, targetnum}}); + regs.erase(regname); + regs.erase(highname); + } + } + break; + } + } + } +} + +template +void createRegisterArray(const RegisterMap& regs, + RegisterNames& regnames) { + regnames.clear(); + std::stringstream ss; + // sort regs by start index + std::map> + sortedRegs{}; + for (const auto& reg : regs) { + sortedRegs.insert({reg.second.first, reg}); + } + + for (const auto& reg : sortedRegs) { + for (decltype(RegisterType::second) i = 0; i < reg.second.second.second; + ++i) { + ss << reg.second.first << "[" << i << "]"; + regnames.push_back(std::make_pair(reg.second.first, ss.str())); + ss.str(std::string()); + } + } +} + +/** + * @brief Removes a certain qubit in a register from the register map + * @details If this was the last qubit in the register, the register is + * deleted. Removals at the beginning or the end of a register just modify the + * existing register. Removals in the middle of a register split the register + * into two new registers. The new registers are named by appending "_l" and + * "_h" to the original register name. + * @param regs A collection of all the registers + * @param reg The name of the register containing the qubit to be removed + * @param idx The index of the qubit in the register to be removed + */ +void removeQubitfromQubitRegister(QuantumRegisterMap& regs, + const std::string& reg, Qubit idx) { + if (idx == 0) { + // last remaining qubit of register + if (regs[reg].second == 1) { + // delete register + regs.erase(reg); + } + // first qubit of register + else { + regs[reg].first++; + regs[reg].second--; + } + // last index + } else if (idx == regs[reg].second - 1) { + // reduce count of register + regs[reg].second--; + } else { + auto qreg = regs.at(reg); + auto lowPart = reg + "_l"; + auto lowIndex = qreg.first; + auto lowCount = idx; + auto highPart = reg + "_h"; + auto highIndex = qreg.first + idx + 1; + auto highCount = qreg.second - idx - 1; + + regs.erase(reg); + regs.try_emplace(lowPart, lowIndex, lowCount); + regs.try_emplace(highPart, highIndex, highCount); + } +} + +/** + * @brief Adds a qubit to a register in the register map + * @details If the register map is empty, a new register is created with the + * default name. If the qubit can be appended to the start or the end of an + * existing register, it is appended. Otherwise a new register is created with + * the default name and the qubit index appended. + * @param regs A collection of all the registers + * @param physicalQubitIndex The index of the qubit to be added + * @param defaultRegName The default name of the register to be created + */ +void addQubitToQubitRegister(QuantumRegisterMap& regs, Qubit physicalQubitIndex, + const std::string& defaultRegName) { + auto fusionPossible = false; + for (auto& reg : regs) { + auto& startIndex = reg.second.first; + auto& count = reg.second.second; + // 1st case: can append to start of existing register + if (startIndex == physicalQubitIndex + 1) { + startIndex--; + count++; + fusionPossible = true; + break; + } + // 2nd case: can append to end of existing register + if (startIndex + count == physicalQubitIndex) { + count++; + fusionPossible = true; + break; + } + } + + consolidateRegister(regs); + + if (regs.empty()) { + regs.try_emplace(defaultRegName, physicalQubitIndex, 1); + } else if (!fusionPossible) { + auto newRegName = defaultRegName + "_" + std::to_string(physicalQubitIndex); + regs.try_emplace(newRegName, physicalQubitIndex, 1); + } +} +} // namespace + /*** * Public Methods ***/ @@ -322,71 +487,6 @@ void QuantumComputation::addAncillaryRegister(std::size_t nq, nancillae += nq; } -void QuantumComputation::removeQubitfromQubitRegister(QuantumRegisterMap& regs, - const std::string& reg, - const Qubit idx) { - if (idx == 0) { - // last remaining qubit of register - if (regs[reg].second == 1) { - // delete register - regs.erase(reg); - } - // first qubit of register - else { - regs[reg].first++; - regs[reg].second--; - } - // last index - } else if (idx == regs[reg].second - 1) { - // reduce count of register - regs[reg].second--; - } else { - auto qreg = regs.at(reg); - auto lowPart = reg + "_l"; - auto lowIndex = qreg.first; - auto lowCount = idx; - auto highPart = reg + "_h"; - auto highIndex = qreg.first + idx + 1; - auto highCount = qreg.second - idx - 1; - - regs.erase(reg); - regs.try_emplace(lowPart, lowIndex, lowCount); - regs.try_emplace(highPart, highIndex, highCount); - } -} - -void QuantumComputation::addQubitToQubitRegister( - QuantumRegisterMap& regs, const Qubit physicalQubitIndex, - const std::string& defaultRegName) { - bool fusionPossible = false; - for (auto& reg : regs) { - auto& startIndex = reg.second.first; - auto& count = reg.second.second; - // 1st case: can append to start of existing register - if (startIndex == physicalQubitIndex + 1) { - startIndex--; - count++; - fusionPossible = true; - break; - } - // 2nd case: can append to end of existing register - if (startIndex + count == physicalQubitIndex) { - count++; - fusionPossible = true; - break; - } - } - - consolidateRegister(regs); - - if (regs.empty()) { - regs.try_emplace(defaultRegName, physicalQubitIndex, 1); - } else if (!fusionPossible) { - auto newRegName = defaultRegName + "_" + std::to_string(physicalQubitIndex); - regs.try_emplace(newRegName, physicalQubitIndex, 1); - } -} - // removes the i-th logical qubit and returns the index j it was assigned to in // the initial layout i.e., initialLayout[j] = i std::pair> @@ -514,7 +614,28 @@ void QuantumComputation::addQubit(const Qubit logicalQubitIndex, ancillary[logicalQubitIndex] = false; garbage[logicalQubitIndex] = false; } +QuantumComputation +QuantumComputation::instantiate(const VariableAssignment& assignment) const { + QuantumComputation result(*this); + result.instantiateInplace(assignment); + return result; +} +void QuantumComputation::invert() { + for (const auto& op : ops) { + op->invert(); + } + std::reverse(ops.begin(), ops.end()); + + if (initialLayout.size() == outputPermutation.size()) { + std::swap(initialLayout, outputPermutation); + } else { + std::cerr << "Warning: Inverting a circuit with different initial layout " + "and output permutation sizes. This is not supported yet.\n" + "The circuit will be inverted, but the initial layout and " + "output permutation will not be swapped.\n"; + } +} std::ostream& QuantumComputation::print(std::ostream& os) const { os << name << "\n"; const auto width = @@ -650,6 +771,28 @@ std::string QuantumComputation::toQASM(const bool qasm3) const { dumpOpenQASM(ss, qasm3); return ss.str(); } +std::unique_ptr QuantumComputation::asOperation() { + if (ops.empty()) { + return {}; + } + if (ops.size() == 1) { + auto op = std::move(ops.front()); + ops.clear(); + return op; + } + return asCompoundOperation(); +} +void QuantumComputation::reset() { + ops.clear(); + nqubits = 0; + nclassics = 0; + nancillae = 0; + qregs.clear(); + cregs.clear(); + ancregs.clear(); + initialLayout.clear(); + outputPermutation.clear(); +} void QuantumComputation::dump(const std::string& filename, Format format) const { @@ -987,7 +1130,7 @@ void QuantumComputation::appendMeasurementsAccordingToOutputPermutation( cregs[registerName].second = outputPermutation.size(); } } - auto targets = std::vector{}; + auto targets = std::vector{}; for (std::size_t q = 0; q < getNqubits(); ++q) { targets.emplace_back(static_cast(q)); } @@ -1025,7 +1168,7 @@ void QuantumComputation::checkQubitRange( } } -void QuantumComputation::checkBitRange(const qc::Bit bit) const { +void QuantumComputation::checkBitRange(const Bit bit) const { if (bit >= nclassics) { std::stringstream ss{}; ss << "Classical bit index " << bit << " not found in any register"; @@ -1050,6 +1193,87 @@ void QuantumComputation::checkClassicalRegister( } } +void QuantumComputation::reverse() { std::reverse(ops.begin(), ops.end()); } + +QuantumComputation::QuantumComputation(const std::size_t nq, + const std::size_t nc, + const std::size_t s) + : seed(s) { + if (nq > 0) { + addQubitRegister(nq); + } + if (nc > 0) { + addClassicalRegister(nc); + } + if (seed != 0) { + mt.seed(seed); + } else { + // create and properly seed rng + std::array + randomData{}; + std::random_device rd; + std::generate(std::begin(randomData), std::end(randomData), + [&rd]() { return rd(); }); + std::seed_seq seeds(std::begin(randomData), std::end(randomData)); + mt.seed(seeds); + } +} +QuantumComputation::QuantumComputation(const std::string& filename, + const std::size_t s) + : seed(s) { + import(filename); + if (seed != 0U) { + mt.seed(seed); + } else { + // create and properly seed rng + std::array + randomData{}; + std::random_device rd; + std::generate(std::begin(randomData), std::end(randomData), + [&rd]() { return rd(); }); + std::seed_seq seeds(std::begin(randomData), std::end(randomData)); + mt.seed(seeds); + } +} +QuantumComputation::QuantumComputation(const QuantumComputation& qc) + : nqubits(qc.nqubits), nclassics(qc.nclassics), nancillae(qc.nancillae), + name(qc.name), qregs(qc.qregs), cregs(qc.cregs), ancregs(qc.ancregs), + ancillary(qc.ancillary), garbage(qc.garbage), mt(qc.mt), seed(qc.seed), + globalPhase(qc.globalPhase), occurringVariables(qc.occurringVariables), + initialLayout(qc.initialLayout), outputPermutation(qc.outputPermutation) { + ops.reserve(qc.ops.size()); + for (const auto& op : qc.ops) { + emplace_back(op->clone()); + } +} +QuantumComputation& +QuantumComputation::operator=(const QuantumComputation& qc) { + if (this != &qc) { + nqubits = qc.nqubits; + nclassics = qc.nclassics; + nancillae = qc.nancillae; + name = qc.name; + qregs = qc.qregs; + cregs = qc.cregs; + ancregs = qc.ancregs; + mt = qc.mt; + seed = qc.seed; + globalPhase = qc.globalPhase; + occurringVariables = qc.occurringVariables; + initialLayout = qc.initialLayout; + outputPermutation = qc.outputPermutation; + ancillary = qc.ancillary; + garbage = qc.garbage; + + ops.clear(); + ops.reserve(qc.ops.size()); + for (const auto& op : qc.ops) { + emplace_back(op->clone()); + } + } + return *this; +} + void QuantumComputation::addVariable(const SymbolOrNumber& expr) { if (std::holds_alternative(expr)) { const auto& sym = std::get(expr); @@ -1059,6 +1283,11 @@ void QuantumComputation::addVariable(const SymbolOrNumber& expr) { } } +bool QuantumComputation::isVariableFree() const { + return std::all_of(ops.begin(), ops.end(), + [](const auto& op) { return !op->isSymbolicOperation(); }); +} + // Instantiates this computation void QuantumComputation::instantiateInplace( const VariableAssignment& assignment) { @@ -1081,54 +1310,6 @@ void QuantumComputation::instantiateInplace( } } -void QuantumComputation::measure( - const Qubit qubit, const std::pair& registerBit) { - checkQubitRange(qubit); - if (const auto cRegister = cregs.find(registerBit.first); - cRegister != cregs.end()) { - if (registerBit.second >= cRegister->second.second) { - std::stringstream ss{}; - ss << "The classical register \"" << registerBit.first - << "\" is too small! (" << registerBit.second - << " >= " << cRegister->second.second << ")"; - throw QFRException(ss.str()); - } - emplace_back(qubit, cRegister->second.first + - registerBit.second); - - } else { - std::stringstream ss{}; - ss << "The classical register \"" << registerBit.first - << "\" does not exist!"; - throw QFRException(ss.str()); - } -} - -void QuantumComputation::measureAll(const bool addBits) { - if (addBits) { - addClassicalRegister(getNqubits(), "meas"); - } - - if (nclassics < getNqubits()) { - std::stringstream ss{}; - ss << "The number of classical bits (" << nclassics - << ") is smaller than the number of qubits (" << getNqubits() << ")!"; - throw QFRException(ss.str()); - } - - barrier(); - Qubit start = 0U; - if (addBits) { - start = static_cast(cregs.at("meas").first); - } - // measure i -> (start+i) in descending order - // (this is an optimization for the simulator) - for (std::size_t i = getNqubits(); i > 0; --i) { - const auto q = static_cast(i - 1); - measure(q, start + q); - } -} - void QuantumComputation::reorderOperations() { Qubit highestPhysicalQubit = 0; for (const auto& q : initialLayout) { @@ -1156,7 +1337,7 @@ void QuantumComputation::reorderOperations() { } } - std::vector> newOps{}; + std::vector> newOps{}; // iterate over DAG in depth-first fashion starting from the top-most qubit const auto msq = dag.size() - 1; @@ -1241,7 +1422,7 @@ bool isDynamicCircuit(const std::unique_ptr* op, [&measured](const auto& q) { return measured[q]; }); } - if (it->getType() == qc::Measure) { + if (it->getType() == Measure) { for (const auto& b : it->getTargets()) { measured[b] = true; } @@ -1249,9 +1430,9 @@ bool isDynamicCircuit(const std::unique_ptr* op, } assert(it->isCompoundOperation()); - auto* compOp = dynamic_cast(it.get()); + const auto& compOp = dynamic_cast(*it); return std::any_of( - compOp->cbegin(), compOp->cend(), + compOp.cbegin(), compOp.cend(), [&measured](const auto& g) { return isDynamicCircuit(&g, measured); }); } @@ -1289,8 +1470,8 @@ QuantumComputation::fromCompoundOperation(const CompoundOperation& op) { if (g->getType() == Measure) { // update the maximum classical bit index - const auto* measureOp = dynamic_cast(g.get()); - const auto& classics = measureOp->getClassics(); + const auto& measureOp = dynamic_cast(*g); + const auto& classics = measureOp.getClassics(); for (const auto& c : classics) { maxBitIndex = std::max(maxBitIndex, c); } @@ -1304,4 +1485,348 @@ QuantumComputation::fromCompoundOperation(const CompoundOperation& op) { return qc; } +///--------------------------------------------------------------------------- +/// \n Operations \n +///--------------------------------------------------------------------------- + +void QuantumComputation::gphase(const fp angle) { + globalPhase += angle; + // normalize to [0, 2pi) + while (globalPhase < 0) { + globalPhase += 2 * PI; + } + while (globalPhase >= 2 * PI) { + globalPhase -= 2 * PI; + } +} + +#define DEFINE_SINGLE_TARGET_OPERATION(op) \ + void QuantumComputation::op(const Qubit target) { \ + mc##op(Controls{}, target); \ + } \ + void QuantumComputation::c##op(const Control& control, const Qubit target) { \ + mc##op(Controls{control}, target); \ + } \ + void QuantumComputation::mc##op(const Controls& controls, \ + const Qubit target) { \ + checkQubitRange(target, controls); \ + emplace_back(controls, target, \ + OP_NAME_TO_TYPE.at(#op)); \ + } + +DEFINE_SINGLE_TARGET_OPERATION(i) +DEFINE_SINGLE_TARGET_OPERATION(x) +DEFINE_SINGLE_TARGET_OPERATION(y) +DEFINE_SINGLE_TARGET_OPERATION(z) +DEFINE_SINGLE_TARGET_OPERATION(h) +DEFINE_SINGLE_TARGET_OPERATION(s) +DEFINE_SINGLE_TARGET_OPERATION(sdg) +DEFINE_SINGLE_TARGET_OPERATION(t) +DEFINE_SINGLE_TARGET_OPERATION(tdg) +DEFINE_SINGLE_TARGET_OPERATION(v) +DEFINE_SINGLE_TARGET_OPERATION(vdg) +DEFINE_SINGLE_TARGET_OPERATION(sx) +DEFINE_SINGLE_TARGET_OPERATION(sxdg) + +#undef DEFINE_SINGLE_TARGET_OPERATION + +#define DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ + void QuantumComputation::op(const SymbolOrNumber&(param), \ + const Qubit target) { \ + mc##op(param, Controls{}, target); \ + } \ + void QuantumComputation::c##op(const SymbolOrNumber&(param), \ + const Control& control, const Qubit target) { \ + mc##op(param, Controls{control}, target); \ + } \ + void QuantumComputation::mc##op(const SymbolOrNumber&(param), \ + const Controls& controls, \ + const Qubit target) { \ + checkQubitRange(target, controls); \ + if (std::holds_alternative(param)) { \ + emplace_back(controls, target, \ + OP_NAME_TO_TYPE.at(#op), \ + std::vector{std::get(param)}); \ + } else { \ + addVariables(param); \ + emplace_back( \ + controls, target, OP_NAME_TO_TYPE.at(#op), std::vector{param}); \ + } \ + } + +DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rx, theta) +DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(ry, theta) +DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rz, theta) +DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(p, theta) + +#undef DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION + +#define DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ + void QuantumComputation::op(const SymbolOrNumber&(param0), \ + const SymbolOrNumber&(param1), \ + const Qubit target) { \ + mc##op(param0, param1, Controls{}, target); \ + } \ + void QuantumComputation::c##op(const SymbolOrNumber&(param0), \ + const SymbolOrNumber&(param1), \ + const Control& control, const Qubit target) { \ + mc##op(param0, param1, Controls{control}, target); \ + } \ + void QuantumComputation::mc##op( \ + const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ + const Controls& controls, const Qubit target) { \ + checkQubitRange(target, controls); \ + if (std::holds_alternative(param0) && \ + std::holds_alternative(param1)) { \ + emplace_back( \ + controls, target, OP_NAME_TO_TYPE.at(#op), \ + std::vector{std::get(param0), std::get(param1)}); \ + } else { \ + addVariables(param0, param1); \ + emplace_back(controls, target, \ + OP_NAME_TO_TYPE.at(#op), \ + std::vector{param0, param1}); \ + } \ + } + +DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(u2, phi, lambda) + +#undef DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION + +#define DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(op, param0, param1, \ + param2) \ + void QuantumComputation::op( \ + const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ + const SymbolOrNumber&(param2), const Qubit target) { \ + mc##op(param0, param1, param2, Controls{}, target); \ + } \ + void QuantumComputation::c##op(const SymbolOrNumber&(param0), \ + const SymbolOrNumber&(param1), \ + const SymbolOrNumber&(param2), \ + const Control& control, const Qubit target) { \ + mc##op(param0, param1, param2, Controls{control}, target); \ + } \ + void QuantumComputation::mc##op( \ + const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ + const SymbolOrNumber&(param2), const Controls& controls, \ + const Qubit target) { \ + checkQubitRange(target, controls); \ + if (std::holds_alternative(param0) && \ + std::holds_alternative(param1) && \ + std::holds_alternative(param2)) { \ + emplace_back( \ + controls, target, OP_NAME_TO_TYPE.at(#op), \ + std::vector{std::get(param0), std::get(param1), \ + std::get(param2)}); \ + } else { \ + addVariables(param0, param1, param2); \ + emplace_back(controls, target, \ + OP_NAME_TO_TYPE.at(#op), \ + std::vector{param0, param1, param2}); \ + } \ + } + +DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(u, theta, phi, lambda) + +#undef DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION + +#define DEFINE_TWO_TARGET_OPERATION(op) \ + void QuantumComputation::op(const Qubit target0, const Qubit target1) { \ + mc##op(Controls{}, target0, target1); \ + } \ + void QuantumComputation::c##op(const Control& control, const Qubit target0, \ + const Qubit target1) { \ + mc##op(Controls{control}, target0, target1); \ + } \ + void QuantumComputation::mc##op(const Controls& controls, \ + const Qubit target0, const Qubit target1) { \ + checkQubitRange(target0, target1, controls); \ + emplace_back(controls, target0, target1, \ + OP_NAME_TO_TYPE.at(#op)); \ + } + +DEFINE_TWO_TARGET_OPERATION(swap) // NOLINT: bugprone-exception-escape +DEFINE_TWO_TARGET_OPERATION(dcx) +DEFINE_TWO_TARGET_OPERATION(ecr) +DEFINE_TWO_TARGET_OPERATION(iswap) +DEFINE_TWO_TARGET_OPERATION(iswapdg) +DEFINE_TWO_TARGET_OPERATION(peres) +DEFINE_TWO_TARGET_OPERATION(peresdg) +DEFINE_TWO_TARGET_OPERATION(move) + +#undef DEFINE_TWO_TARGET_OPERATION + +#define DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ + void QuantumComputation::op(const SymbolOrNumber&(param), \ + const Qubit target0, const Qubit target1) { \ + mc##op(param, Controls{}, target0, target1); \ + } \ + void QuantumComputation::c##op(const SymbolOrNumber&(param), \ + const Control& control, const Qubit target0, \ + const Qubit target1) { \ + mc##op(param, Controls{control}, target0, target1); \ + } \ + void QuantumComputation::mc##op(const SymbolOrNumber&(param), \ + const Controls& controls, \ + const Qubit target0, const Qubit target1) { \ + checkQubitRange(target0, target1, controls); \ + if (std::holds_alternative(param)) { \ + emplace_back(controls, target0, target1, \ + OP_NAME_TO_TYPE.at(#op), \ + std::vector{std::get(param)}); \ + } else { \ + addVariables(param); \ + emplace_back(controls, target0, target1, \ + OP_NAME_TO_TYPE.at(#op), \ + std::vector{param}); \ + } \ + } + +DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rxx, theta) +DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(ryy, theta) +DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzz, theta) +DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzx, theta) + +#undef DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION + +#define DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ + void QuantumComputation::op(const SymbolOrNumber&(param0), \ + const SymbolOrNumber&(param1), \ + const Qubit target0, const Qubit target1) { \ + mc##op(param0, param1, Controls{}, target0, target1); \ + } \ + void QuantumComputation::c##op( \ + const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ + const Control& control, const Qubit target0, const Qubit target1) { \ + mc##op(param0, param1, Controls{control}, target0, target1); \ + } \ + void QuantumComputation::mc##op( \ + const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ + const Controls& controls, const Qubit target0, const Qubit target1) { \ + checkQubitRange(target0, target1, controls); \ + if (std::holds_alternative(param0) && \ + std::holds_alternative(param1)) { \ + emplace_back( \ + controls, target0, target1, OP_NAME_TO_TYPE.at(#op), \ + std::vector{std::get(param0), std::get(param1)}); \ + } else { \ + addVariables(param0, param1); \ + emplace_back(controls, target0, target1, \ + OP_NAME_TO_TYPE.at(#op), \ + std::vector{param0, param1}); \ + } \ + } + +DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_minus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_plus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION + +void QuantumComputation::measure(const Qubit qubit, const std::size_t bit) { + checkQubitRange(qubit); + checkBitRange(bit); + emplace_back(qubit, bit); +} + +void QuantumComputation::measure( + const Qubit qubit, const std::pair& registerBit) { + checkQubitRange(qubit); + if (const auto cRegister = cregs.find(registerBit.first); + cRegister != cregs.end()) { + if (registerBit.second >= cRegister->second.second) { + std::stringstream ss{}; + ss << "The classical register \"" << registerBit.first + << "\" is too small! (" << registerBit.second + << " >= " << cRegister->second.second << ")"; + throw QFRException(ss.str()); + } + emplace_back(qubit, cRegister->second.first + + registerBit.second); + + } else { + std::stringstream ss{}; + ss << "The classical register \"" << registerBit.first + << "\" does not exist!"; + throw QFRException(ss.str()); + } +} + +void QuantumComputation::measure(const Targets& qubits, + const std::vector& bits) { + checkQubitRange(qubits); + checkBitRange(bits); + emplace_back(qubits, bits); +} + +void QuantumComputation::measureAll(const bool addBits) { + if (addBits) { + addClassicalRegister(getNqubits(), "meas"); + } + + if (nclassics < getNqubits()) { + std::stringstream ss{}; + ss << "The number of classical bits (" << nclassics + << ") is smaller than the number of qubits (" << getNqubits() << ")!"; + throw QFRException(ss.str()); + } + + barrier(); + Qubit start = 0U; + if (addBits) { + start = static_cast(cregs.at("meas").first); + } + // measure i -> (start+i) in descending order + // (this is an optimization for the simulator) + for (std::size_t i = getNqubits(); i > 0; --i) { + const auto q = static_cast(i - 1); + measure(q, start + q); + } +} + +void QuantumComputation::reset(const Qubit target) { + checkQubitRange(target); + emplace_back(std::vector{target}, Reset); +} +void QuantumComputation::reset(const Targets& targets) { + checkQubitRange(targets); + emplace_back(targets, Reset); +} +void QuantumComputation::barrier() { + std::vector targets(getNqubits()); + std::iota(targets.begin(), targets.end(), 0); + emplace_back(targets, Barrier); +} +void QuantumComputation::barrier(const Qubit target) { + checkQubitRange(target); + emplace_back(target, Barrier); +} +void QuantumComputation::barrier(const Targets& targets) { + checkQubitRange(targets); + emplace_back(targets, Barrier); +} +void QuantumComputation::classicControlled( + const OpType op, const Qubit target, + const ClassicalRegister& controlRegister, const std::uint64_t expectedValue, + const ComparisonKind cmp, const std::vector& params) { + classicControlled(op, target, Controls{}, controlRegister, expectedValue, cmp, + params); +} +void QuantumComputation::classicControlled( + const OpType op, const Qubit target, const Control control, + const ClassicalRegister& controlRegister, const std::uint64_t expectedValue, + const ComparisonKind cmp, const std::vector& params) { + classicControlled(op, target, Controls{control}, controlRegister, + expectedValue, cmp, params); +} +void QuantumComputation::classicControlled( + const OpType op, const Qubit target, const Controls& controls, + const ClassicalRegister& controlRegister, const std::uint64_t expectedValue, + const ComparisonKind cmp, const std::vector& params) { + checkQubitRange(target, controls); + checkClassicalRegister(controlRegister); + std::unique_ptr gate = + std::make_unique(controls, target, op, params); + emplace_back(std::move(gate), controlRegister, + expectedValue, cmp); +} } // namespace qc diff --git a/src/ir/operations/AodOperation.cpp b/src/ir/operations/AodOperation.cpp index 85563895f..fd98f44bb 100644 --- a/src/ir/operations/AodOperation.cpp +++ b/src/ir/operations/AodOperation.cpp @@ -13,12 +13,14 @@ #include "ir/operations/OpType.hpp" #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -31,6 +33,11 @@ AodOperation::AodOperation(qc::OpType s, std::vector qubits, : AodOperation(s, std::move(qubits), convertToDimension(dirs), start, end) { } +std::string SingleOperation::toQASMString() const { + std::stringstream ss; + ss << static_cast(dir) << ", " << start << ", " << end << "; "; + return ss.str(); +} std::vector AodOperation::convertToDimension(const std::vector& dirs) { std::vector dirsEnum(dirs.size()); @@ -82,9 +89,43 @@ AodOperation::AodOperation(qc::OpType s, std::vector t, name = toString(type); } +std::vector AodOperation::getEnds(const Dimension dir) const { + std::vector ends; + for (const auto& op : operations) { + if (op.dir == dir) { + ends.emplace_back(op.end); + } + } + return ends; +} +std::vector AodOperation::getStarts(const Dimension dir) const { + std::vector starts; + for (const auto& op : operations) { + if (op.dir == dir) { + starts.emplace_back(op.start); + } + } + return starts; +} +qc::fp AodOperation::getMaxDistance(const Dimension dir) const { + const auto distances = getDistances(dir); + if (distances.empty()) { + return 0; + } + return *std::max_element(distances.begin(), distances.end()); +} +std::vector AodOperation::getDistances(const Dimension dir) const { + std::vector params; + for (const auto& op : operations) { + if (op.dir == dir) { + params.emplace_back(std::abs(op.end - op.start)); + } + } + return params; +} void AodOperation::dumpOpenQASM(std::ostream& of, const qc::RegisterNames& qreg, [[maybe_unused]] const qc::RegisterNames& creg, - size_t indent, bool /*openQASM3*/) const { + const size_t indent, bool /*openQASM3*/) const { of << std::setprecision(std::numeric_limits::digits10); of << std::string(indent * qc::OUTPUT_INDENT_SIZE, ' '); of << name; diff --git a/src/ir/operations/ClassicControlledOperation.cpp b/src/ir/operations/ClassicControlledOperation.cpp index 8a48a6e06..c72bf63f0 100644 --- a/src/ir/operations/ClassicControlledOperation.cpp +++ b/src/ir/operations/ClassicControlledOperation.cpp @@ -18,17 +18,17 @@ namespace qc { std::string toString(const ComparisonKind& kind) { switch (kind) { - case ComparisonKind::Eq: + case Eq: return "=="; - case ComparisonKind::Neq: + case Neq: return "!="; - case ComparisonKind::Lt: + case Lt: return "<"; - case ComparisonKind::Leq: + case Leq: return "<="; - case ComparisonKind::Gt: + case Gt: return ">"; - case ComparisonKind::Geq: + case Geq: return ">="; default: unreachable(); @@ -40,6 +40,83 @@ std::ostream& operator<<(std::ostream& os, const ComparisonKind& kind) { return os; } +ClassicControlledOperation::ClassicControlledOperation( + std::unique_ptr&& operation, ClassicalRegister controlReg, + const std::uint64_t expectedVal, ComparisonKind kind) + : op(std::move(operation)), controlRegister(std::move(controlReg)), + expectedValue(expectedVal), comparisonKind(kind) { + name = "c_" + shortName(op->getType()); + parameter.reserve(3); + parameter.emplace_back(static_cast(controlRegister.first)); + parameter.emplace_back(static_cast(controlRegister.second)); + parameter.emplace_back(static_cast(expectedValue)); + type = ClassicControlled; +} +ClassicControlledOperation::ClassicControlledOperation( + const ClassicControlledOperation& ccop) + : Operation(ccop), controlRegister(ccop.controlRegister), + expectedValue(ccop.expectedValue) { + op = ccop.op->clone(); +} +ClassicControlledOperation& +ClassicControlledOperation::operator=(const ClassicControlledOperation& ccop) { + if (this != &ccop) { + Operation::operator=(ccop); + controlRegister = ccop.controlRegister; + expectedValue = ccop.expectedValue; + op = ccop.op->clone(); + } + return *this; +} + +bool ClassicControlledOperation::equals(const Operation& operation, + const Permutation& perm1, + const Permutation& perm2) const { + if (const auto* classic = + dynamic_cast(&operation)) { + if (controlRegister != classic->controlRegister) { + return false; + } + + if (expectedValue != classic->expectedValue || + comparisonKind != classic->comparisonKind) { + return false; + } + + return op->equals(*classic->op, perm1, perm2); + } + return false; +} +void ClassicControlledOperation::dumpOpenQASM(std::ostream& of, + const RegisterNames& qreg, + const RegisterNames& creg, + const std::size_t indent, + const bool openQASM3) const { + of << std::string(indent * OUTPUT_INDENT_SIZE, ' '); + of << "if ("; + if (isWholeQubitRegister(creg, controlRegister.first, + controlRegister.first + controlRegister.second - + 1)) { + of << creg[controlRegister.first].first; + } else { + // This might use slices in the future to address multiple bits. + if (controlRegister.second != 1) { + throw QFRException( + "Control register of classically controlled operation may either" + " be a single bit or a whole register."); + } + of << creg[controlRegister.first].second; + } + of << " " << comparisonKind << " " << expectedValue << ") "; + if (openQASM3) { + of << "{\n"; + } + op->dumpOpenQASM(of, qreg, creg, indent + 1, openQASM3); + if (openQASM3) { + of << "}\n"; + } +} + ComparisonKind getInvertedComparisonKind(const ComparisonKind kind) { switch (kind) { case Lt: diff --git a/src/ir/operations/CompoundOperation.cpp b/src/ir/operations/CompoundOperation.cpp index cb3a469a0..f0955815c 100644 --- a/src/ir/operations/CompoundOperation.cpp +++ b/src/ir/operations/CompoundOperation.cpp @@ -68,9 +68,9 @@ bool CompoundOperation::isNonUnitaryOperation() const { }); } -bool CompoundOperation::isCompoundOperation() const { return true; } +bool CompoundOperation::isCompoundOperation() const noexcept { return true; } -bool CompoundOperation::isCustomGate() const { return customGate; } +bool CompoundOperation::isCustomGate() const noexcept { return customGate; } bool CompoundOperation::isSymbolicOperation() const { return std::any_of(ops.begin(), ops.end(), @@ -81,7 +81,7 @@ void CompoundOperation::addControl(const Control c) { controls.insert(c); // we can just add the controls to each operation, as the operations will // check if they already act on the control qubits. - for (auto& op : ops) { + for (const auto& op : ops) { op->addControl(c); } } @@ -99,14 +99,14 @@ void CompoundOperation::removeControl(const Control c) { "is not a control."); } - for (auto& op : ops) { + for (const auto& op : ops) { op->removeControl(c); } } Controls::iterator CompoundOperation::removeControl(const Controls::iterator it) { - for (auto& op : ops) { + for (const auto& op : ops) { op->removeControl(*it); } @@ -227,7 +227,7 @@ auto CompoundOperation::isInverseOf(const Operation& other) const -> bool { } void CompoundOperation::invert() { - for (auto& op : ops) { + for (const auto& op : ops) { op->invert(); } std::reverse(ops.begin(), ops.end()); @@ -235,7 +235,7 @@ void CompoundOperation::invert() { void CompoundOperation::apply(const Permutation& permutation) { Operation::apply(permutation); - for (auto& op : ops) { + for (const auto& op : ops) { op->apply(permutation); } } @@ -255,8 +255,8 @@ bool CompoundOperation::isConvertibleToSingleOperation() const { if (!ops.front()->isCompoundOperation()) { return true; } - return dynamic_cast(ops.front().get()) - ->isConvertibleToSingleOperation(); + return dynamic_cast(*ops.front()) + .isConvertibleToSingleOperation(); } std::unique_ptr CompoundOperation::collapseToSingleOperation() { @@ -264,13 +264,12 @@ std::unique_ptr CompoundOperation::collapseToSingleOperation() { if (!ops.front()->isCompoundOperation()) { return std::move(ops.front()); } - return dynamic_cast(ops.front().get()) - ->collapseToSingleOperation(); + return dynamic_cast(*ops.front()) + .collapseToSingleOperation(); } } // namespace qc -namespace std { std::size_t std::hash::operator()( const qc::CompoundOperation& co) const noexcept { std::size_t seed = 0U; @@ -279,4 +278,3 @@ std::size_t std::hash::operator()( } return seed; } -} // namespace std diff --git a/src/ir/operations/Expression.cpp b/src/ir/operations/Expression.cpp index cca49a0f0..e1951723a 100644 --- a/src/ir/operations/Expression.cpp +++ b/src/ir/operations/Expression.cpp @@ -15,8 +15,7 @@ namespace sym { Variable::Variable(const std::string& name) { - const auto it = registered.find(name); - if (it != registered.end()) { + if (const auto it = registered.find(name); it != registered.end()) { id = it->second; } else { registered[name] = nextId; @@ -26,7 +25,7 @@ Variable::Variable(const std::string& name) { } } -std::string Variable::getName() const { return names[id]; } +std::string Variable::getName() const noexcept { return names[id]; } std::ostream& operator<<(std::ostream& os, const Variable& var) { os << var.getName(); diff --git a/src/ir/operations/SymbolicOperation.cpp b/src/ir/operations/SymbolicOperation.cpp index 2707886f6..6b5b7592b 100644 --- a/src/ir/operations/SymbolicOperation.cpp +++ b/src/ir/operations/SymbolicOperation.cpp @@ -16,6 +16,7 @@ #include "ir/operations/OpType.hpp" #include "ir/operations/StandardOperation.hpp" +#include #include #include #include @@ -25,6 +26,12 @@ namespace qc { +// Overload pattern for std::visit +template struct Overload : Ts... { + using Ts::operator()...; +}; +template Overload(Ts...) -> Overload; + void SymbolicOperation::storeSymbolOrNumber(const SymbolOrNumber& param, const std::size_t i) { if (std::holds_alternative(param)) { @@ -33,6 +40,18 @@ void SymbolicOperation::storeSymbolOrNumber(const SymbolOrNumber& param, symbolicParameter.at(i) = std::get(param); } } +bool SymbolicOperation::isSymbolicParameter(const std::size_t i) const { + return symbolicParameter.at(i).has_value(); +} +bool SymbolicOperation::isSymbol(const SymbolOrNumber& param) { + return std::holds_alternative(param); +} +Symbolic& SymbolicOperation::getSymbol(SymbolOrNumber& param) { + return std::get(param); +} +fp& SymbolicOperation::getNumber(SymbolOrNumber& param) { + return std::get(param); +} OpType SymbolicOperation::parseU3([[maybe_unused]] const Symbolic& theta, fp& phi, fp& lambda) { @@ -138,7 +157,6 @@ OpType SymbolicOperation::parseU3(fp& theta, const Symbolic& phi, } // parse a real u3 gate - checkInteger(theta); checkFractionPi(theta); @@ -228,6 +246,19 @@ SymbolicOperation::getInstantiation(const SymbolOrNumber& symOrNum, symOrNum); } +SymbolOrNumber SymbolicOperation::getParameter(const std::size_t i) const { + if (const auto& param = symbolicParameter.at(i); param.has_value()) { + return *param; + } + return parameter.at(i); +} +std::vector SymbolicOperation::getParameters() const { + std::vector params{}; + for (std::size_t i = 0; i < parameter.size(); ++i) { + params.emplace_back(getParameter(i)); + } + return params; +} SymbolicOperation::SymbolicOperation( const Qubit target, const OpType g, const std::vector& params) { @@ -278,13 +309,24 @@ SymbolicOperation::SymbolicOperation(const Controls& c, const Qubit target0, const std::vector& params) : SymbolicOperation(c, {target0, target1}, g, params) {} +std::unique_ptr SymbolicOperation::clone() const { + return std::make_unique(*this); +} +bool SymbolicOperation::isSymbolicOperation() const { + return std::any_of(symbolicParameter.begin(), symbolicParameter.end(), + [](const auto& sym) { return sym.has_value(); }); +} +bool SymbolicOperation::isStandardOperation() const { + return std::all_of(symbolicParameter.begin(), symbolicParameter.end(), + [](const auto& sym) { return !sym.has_value(); }); +} + bool SymbolicOperation::equals(const Operation& op, const Permutation& perm1, const Permutation& perm2) const { if (!op.isSymbolicOperation() && !isStandardOperation()) { return false; } - if (isStandardOperation() && - qc::StandardOperation::equals(op, perm1, perm2)) { + if (isStandardOperation() && StandardOperation::equals(op, perm1, perm2)) { return true; } diff --git a/src/ir/parsers/qasm3_parser/passes/ConstEvalPass.cpp b/src/ir/parsers/qasm3_parser/passes/ConstEvalPass.cpp index f45d23902..e6f451266 100644 --- a/src/ir/parsers/qasm3_parser/passes/ConstEvalPass.cpp +++ b/src/ir/parsers/qasm3_parser/passes/ConstEvalPass.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace qasm3::const_eval { @@ -94,6 +95,26 @@ void ConstEvalPass::visitGateCallStatement( } } +std::string ConstEvalValue::toString() const { + std::stringstream ss{}; + switch (type) { + case ConstInt: + ss << "ConstInt(" << std::get<0>(value) << ")"; + break; + case ConstUint: + ss << "ConstUint(" << std::get<0>(value) << ")"; + break; + case ConstFloat: + ss << "ConstFloat(" << std::get<1>(value) << ")"; + break; + case ConstBool: + ss << "ConstBool(" << std::get<2>(value) << ")"; + break; + } + + return ss.str(); +} + ConstEvalValue ConstEvalPass::evalIntExpression(BinaryExpression::Op op, int64_t lhs, int64_t rhs, size_t width, bool isSigned) { diff --git a/src/ir/parsers/qasm3_parser/passes/TypeCheckPass.cpp b/src/ir/parsers/qasm3_parser/passes/TypeCheckPass.cpp index 3e6e24e51..6a2ed5a44 100644 --- a/src/ir/parsers/qasm3_parser/passes/TypeCheckPass.cpp +++ b/src/ir/parsers/qasm3_parser/passes/TypeCheckPass.cpp @@ -17,13 +17,24 @@ #include #include #include +#include #include #include namespace qasm3::type_checking { +InferredType TypeCheckPass::error(const std::string& msg, + const std::shared_ptr& debugInfo) { + std::cerr << "Type check error: " << msg << '\n'; + if (debugInfo) { + std::cerr << " " << debugInfo->toString() << '\n'; + } + hasError = true; + return InferredType::error(); +} + void TypeCheckPass::visitGateStatement( - std::shared_ptr gateStatement) { + const std::shared_ptr gateStatement) { // we save the current environment to restore it afterward const auto oldEnv = env; @@ -50,7 +61,7 @@ void TypeCheckPass::visitVersionDeclaration( std::shared_ptr /*versionDeclaration*/) {} void TypeCheckPass::visitDeclarationStatement( - std::shared_ptr declarationStatement) { + const std::shared_ptr declarationStatement) { // Type checking declarations is a bit involved. If the type contains a // designator expression, we need to resolve the statement in three steps. const auto typeExpr = std::get<0>(declarationStatement->type); @@ -95,14 +106,14 @@ void TypeCheckPass::visitOutputPermutation( std::shared_ptr /*outputPermutation*/) {} void TypeCheckPass::visitGateCallStatement( - std::shared_ptr gateCallStatement) { + const std::shared_ptr gateCallStatement) { for (const auto& arg : gateCallStatement->arguments) { visit(arg); } } void TypeCheckPass::visitAssignmentStatement( - std::shared_ptr assignmentStatement) { + const std::shared_ptr assignmentStatement) { if (assignmentStatement->indexExpression != nullptr) { auto indexTy = visit(assignmentStatement->indexExpression); if (!indexTy.isError && !indexTy.type->isUint()) { @@ -134,7 +145,7 @@ void TypeCheckPass::visitAssignmentStatement( } void TypeCheckPass::visitBarrierStatement( - std::shared_ptr barrierStatement) { + const std::shared_ptr barrierStatement) { for (auto& gate : barrierStatement->gates) { if (!gate->expression) { continue; @@ -144,12 +155,12 @@ void TypeCheckPass::visitBarrierStatement( } void TypeCheckPass::visitResetStatement( - std::shared_ptr resetStatement) { + const std::shared_ptr resetStatement) { checkGateOperand(*resetStatement->gate); } InferredType TypeCheckPass::visitBinaryExpression( - std::shared_ptr binaryExpression) { + const std::shared_ptr binaryExpression) { auto lhs = visit(binaryExpression->lhs); auto rhs = visit(binaryExpression->rhs); if (rhs.isError) { @@ -221,7 +232,7 @@ InferredType TypeCheckPass::visitBinaryExpression( } InferredType TypeCheckPass::visitUnaryExpression( - std::shared_ptr unaryExpression) { + const std::shared_ptr unaryExpression) { auto type = visit(unaryExpression->operand); switch (unaryExpression->op) { @@ -252,8 +263,8 @@ InferredType TypeCheckPass::visitUnaryExpression( return type; } -InferredType -TypeCheckPass::visitConstantExpression(std::shared_ptr constant) { +InferredType TypeCheckPass::visitConstantExpression( + const std::shared_ptr constant) { if (constant->isFP()) { return InferredType{std::dynamic_pointer_cast( DesignatedType::getFloatTy(64))}; @@ -280,7 +291,7 @@ TypeCheckPass::visitConstantExpression(std::shared_ptr constant) { } InferredType TypeCheckPass::visitIdentifierExpression( - std::shared_ptr identifierExpression) { + const std::shared_ptr identifierExpression) { const auto type = env.find(identifierExpression->identifier); if (type == env.end()) { error("Unknown identifier '" + identifierExpression->identifier + "'."); @@ -295,7 +306,7 @@ InferredType TypeCheckPass::visitIdentifierExpression( } InferredType TypeCheckPass::visitMeasureExpression( - std::shared_ptr measureExpression) { + const std::shared_ptr measureExpression) { size_t width = 1; if (measureExpression->gate->expression != nullptr) { visit(measureExpression->gate->expression); @@ -313,7 +324,8 @@ InferredType TypeCheckPass::visitMeasureExpression( DesignatedType::getBitTy(width))}; } -void TypeCheckPass::visitIfStatement(std::shared_ptr ifStatement) { +void TypeCheckPass::visitIfStatement( + const std::shared_ptr ifStatement) { // We support ifs on bits and bools const auto ty = visit(ifStatement->condition); if (!ty.isError && !ty.type->isBool()) { diff --git a/src/python/ir/register_quantum_computation.cpp b/src/python/ir/register_quantum_computation.cpp index e7c65a025..774e59c6a 100644 --- a/src/python/ir/register_quantum_computation.cpp +++ b/src/python/ir/register_quantum_computation.cpp @@ -250,27 +250,27 @@ void registerQuantumComputation(py::module& m) { ///--------------------------------------------------------------------------- qc.def("qasm2_str", - [](qc::QuantumComputation& circ) { return circ.toQASM(false); }); + [](const qc::QuantumComputation& circ) { return circ.toQASM(false); }); qc.def( "qasm2", - [](qc::QuantumComputation& circ, const std::string& filename) { + [](const qc::QuantumComputation& circ, const std::string& filename) { circ.dump(filename, qc::Format::OpenQASM2); }, "filename"_a); qc.def("qasm3_str", - [](qc::QuantumComputation& circ) { return circ.toQASM(true); }); + [](const qc::QuantumComputation& circ) { return circ.toQASM(true); }); qc.def( "qasm3", - [](qc::QuantumComputation& circ, const std::string& filename) { + [](const qc::QuantumComputation& circ, const std::string& filename) { circ.dump(filename, qc::Format::OpenQASM3); }, "filename"_a); - qc.def("__str__", [](qc::QuantumComputation& circ) { + qc.def("__str__", [](const qc::QuantumComputation& circ) { auto ss = std::stringstream(); circ.print(ss); return ss.str(); }); - qc.def("__repr__", [](qc::QuantumComputation& circ) { + qc.def("__repr__", [](const qc::QuantumComputation& circ) { auto ss = std::stringstream(); circ.print(ss); return ss.str(); diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index f0de45bb5..f076fb92e 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -588,15 +588,14 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, } FunctionalityConstruction::op_it FunctionalityConstruction::parseCompoundOp( - ZXDiagram& diag, op_it it, op_it end, std::vector& qubits, - const qc::Permutation& initialLayout) { + ZXDiagram& diag, const op_it it, const op_it end, + std::vector& qubits, const qc::Permutation& initialLayout) { const auto& op = *it; - - if (op->getType() == qc::OpType::Compound) { - const auto* compOp = dynamic_cast(op.get()); - for (auto subIt = compOp->cbegin(); subIt != compOp->cend();) { + if (op->isCompoundOperation()) { + const auto& compOp = dynamic_cast(*op); + for (auto subIt = compOp.cbegin(); subIt != compOp.cend();) { subIt = - parseCompoundOp(diag, subIt, compOp->cend(), qubits, initialLayout); + parseCompoundOp(diag, subIt, compOp.cend(), qubits, initialLayout); } return it + 1; } diff --git a/test/circuit_optimizer/test_collect_blocks.cpp b/test/circuit_optimizer/test_collect_blocks.cpp index 532aa5134..4934176c2 100644 --- a/test/circuit_optimizer/test_collect_blocks.cpp +++ b/test/circuit_optimizer/test_collect_blocks.cpp @@ -54,7 +54,7 @@ TEST(CollectBlocks, mergeBlocks) { std::cout << qc << "\n"; EXPECT_EQ(qc.size(), 1); EXPECT_TRUE(qc.front()->isCompoundOperation()); - EXPECT_EQ(dynamic_cast(qc.front().get())->size(), 3); + EXPECT_EQ(dynamic_cast(*qc.front()).size(), 3); } TEST(CollectBlocks, mergeBlocks2) { @@ -69,7 +69,7 @@ TEST(CollectBlocks, mergeBlocks2) { std::cout << qc << "\n"; EXPECT_EQ(qc.size(), 1); EXPECT_TRUE(qc.front()->isCompoundOperation()); - EXPECT_EQ(dynamic_cast(qc.front().get())->size(), 5); + EXPECT_EQ(dynamic_cast(*qc.front()).size(), 5); } TEST(CollectBlocks, addToMultiQubitBlock) { @@ -81,7 +81,7 @@ TEST(CollectBlocks, addToMultiQubitBlock) { std::cout << qc << "\n"; EXPECT_EQ(qc.size(), 1); EXPECT_TRUE(qc.front()->isCompoundOperation()); - EXPECT_EQ(dynamic_cast(qc.front().get())->size(), 2); + EXPECT_EQ(dynamic_cast(*qc.front()).size(), 2); } TEST(CollectBlocks, gateTooBig) { diff --git a/test/circuit_optimizer/test_elide_permutations.cpp b/test/circuit_optimizer/test_elide_permutations.cpp index 9558537de..57f089d9f 100644 --- a/test/circuit_optimizer/test_elide_permutations.cpp +++ b/test/circuit_optimizer/test_elide_permutations.cpp @@ -9,7 +9,9 @@ #include "circuit_optimizer/CircuitOptimizer.hpp" #include "ir/QuantumComputation.hpp" +#include "ir/operations/NonUnitaryOperation.hpp" #include "ir/operations/OpType.hpp" +#include "ir/operations/StandardOperation.hpp" #include #include diff --git a/test/circuit_optimizer/test_remove_operation.cpp b/test/circuit_optimizer/test_remove_operation.cpp index 4ebfc9aab..fb9f75d1d 100644 --- a/test/circuit_optimizer/test_remove_operation.cpp +++ b/test/circuit_optimizer/test_remove_operation.cpp @@ -91,7 +91,7 @@ TEST(RemoveOperation, removeGateInCompoundOperation) { qc.print(std::cout); EXPECT_EQ(qc.getNops(), 1); EXPECT_EQ(qc.front()->getType(), Compound); - auto* compoundOp = dynamic_cast(qc.front().get()); - EXPECT_EQ(compoundOp->size(), 2); + const auto& compoundOp = dynamic_cast(*qc.front()); + EXPECT_EQ(compoundOp.size(), 2); } } // namespace qc diff --git a/test/ir/test_qfr_functionality.cpp b/test/ir/test_qfr_functionality.cpp index 0119aadaf..a7f2e4946 100644 --- a/test/ir/test_qfr_functionality.cpp +++ b/test/ir/test_qfr_functionality.cpp @@ -17,6 +17,7 @@ #include "ir/operations/NonUnitaryOperation.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/StandardOperation.hpp" +#include "ir/operations/SymbolicOperation.hpp" #include #include From 2d43ea949a6acf5b16fe9e58d6b32bf5dea02173 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Jan 2025 22:46:04 +0100 Subject: [PATCH 03/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20replace=20pointers?= =?UTF-8?q?=20to=20QuantumComputation=20with=20references?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- eval/eval_dd_package.cpp | 12 ++-- .../mqt-core/dd/FunctionalityConstruction.hpp | 6 +- include/mqt-core/dd/Simulation.hpp | 28 ++++---- src/dd/FunctionalityConstruction.cpp | 64 ++++++++--------- src/dd/Simulation.cpp | 68 +++++++++---------- test/algorithms/eval_dynamic_circuits.cpp | 16 ++--- test/algorithms/test_bernsteinvazirani.cpp | 4 +- test/algorithms/test_entanglement.cpp | 4 +- test/algorithms/test_grover.cpp | 8 +-- test/algorithms/test_qft.cpp | 10 +-- test/algorithms/test_qpe.cpp | 20 +++--- test/algorithms/test_random_clifford.cpp | 4 +- test/algorithms/test_wstate.cpp | 2 +- test/dd/test_dd_functionality.cpp | 30 ++++---- 14 files changed, 138 insertions(+), 138 deletions(-) diff --git a/eval/eval_dd_package.cpp b/eval/eval_dd_package.cpp index beeccf6a5..ed0f345a0 100644 --- a/eval/eval_dd_package.cpp +++ b/eval/eval_dd_package.cpp @@ -77,7 +77,7 @@ benchmarkSimulate(const QuantumComputation& qc) { exp->dd = std::make_unique>(nq); const auto start = std::chrono::high_resolution_clock::now(); const auto in = exp->dd->makeZeroState(nq); - exp->sim = simulate(&qc, in, *(exp->dd)); + exp->sim = simulate(qc, in, *(exp->dd)); const auto end = std::chrono::high_resolution_clock::now(); exp->runtime = std::chrono::duration_cast>(end - start); @@ -91,7 +91,7 @@ benchmarkFunctionalityConstruction(const QuantumComputation& qc) { const auto nq = qc.getNqubits(); exp->dd = std::make_unique>(nq); const auto start = std::chrono::high_resolution_clock::now(); - exp->func = buildFunctionality(&qc, *(exp->dd)); + exp->func = buildFunctionality(qc, *(exp->dd)); const auto end = std::chrono::high_resolution_clock::now(); exp->runtime = std::chrono::duration_cast>(end - start); @@ -109,14 +109,14 @@ benchmarkSimulateGrover(const qc::Qubit nq, const BitString& targetValue) { // apply state preparation setup QuantumComputation statePrep(nq + 1); appendGroverInitialization(statePrep); - const auto s = buildFunctionality(&statePrep, dd); + const auto s = buildFunctionality(statePrep, dd); auto e = dd.applyOperation(s, dd.makeZeroState(nq + 1)); QuantumComputation groverIteration(nq + 1); appendGroverOracle(groverIteration, targetValue); appendGroverDiffusion(groverIteration); - auto iter = buildFunctionalityRecursive(&groverIteration, dd); + auto iter = buildFunctionalityRecursive(groverIteration, dd); const auto iterations = computeNumberOfIterations(nq); const std::bitset<128U> iterBits(iterations); const auto msb = static_cast(std::floor(std::log2(iterations))); @@ -151,7 +151,7 @@ benchmarkFunctionalityConstructionGrover(const qc::Qubit nq, appendGroverOracle(groverIteration, targetValue); appendGroverDiffusion(groverIteration); - const auto iter = buildFunctionalityRecursive(&groverIteration, dd); + const auto iter = buildFunctionalityRecursive(groverIteration, dd); auto e = iter; const auto iterations = computeNumberOfIterations(nq); const std::bitset<128U> iterBits(iterations); @@ -177,7 +177,7 @@ benchmarkFunctionalityConstructionGrover(const qc::Qubit nq, // apply state preparation setup QuantumComputation statePrep(nq + 1); appendGroverInitialization(statePrep); - const auto s = buildFunctionality(&statePrep, dd); + const auto s = buildFunctionality(statePrep, dd); exp->func = dd.applyOperation(e, s); const auto end = std::chrono::high_resolution_clock::now(); diff --git a/include/mqt-core/dd/FunctionalityConstruction.hpp b/include/mqt-core/dd/FunctionalityConstruction.hpp index 902c4d19c..4208872f3 100644 --- a/include/mqt-core/dd/FunctionalityConstruction.hpp +++ b/include/mqt-core/dd/FunctionalityConstruction.hpp @@ -25,14 +25,14 @@ namespace dd { using namespace qc; template -MatrixDD buildFunctionality(const QuantumComputation* qc, Package& dd); +MatrixDD buildFunctionality(const QuantumComputation& qc, Package& dd); template -MatrixDD buildFunctionalityRecursive(const QuantumComputation* qc, +MatrixDD buildFunctionalityRecursive(const QuantumComputation& qc, Package& dd); template -bool buildFunctionalityRecursive(const QuantumComputation* qc, +bool buildFunctionalityRecursive(const QuantumComputation& qc, std::size_t depth, std::size_t opIdx, std::stack& s, Permutation& permutation, Package& dd); diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp index 5d696d685..0b032c0a8 100644 --- a/include/mqt-core/dd/Simulation.hpp +++ b/include/mqt-core/dd/Simulation.hpp @@ -24,11 +24,11 @@ namespace dd { using namespace qc; template -VectorDD simulate(const QuantumComputation* qc, const VectorDD& in, +VectorDD simulate(const QuantumComputation& qc, const VectorDD& in, Package& dd) { - auto permutation = qc->initialLayout; + auto permutation = qc.initialLayout; auto e = in; - for (const auto& op : *qc) { + for (const auto& op : qc) { // SWAP gates can be executed virtually by changing the permutation if (op->getType() == SWAP && !op->isControlled()) { const auto& targets = op->getTargets(); @@ -38,14 +38,14 @@ VectorDD simulate(const QuantumComputation* qc, const VectorDD& in, e = applyUnitaryOperation(op.get(), e, dd, permutation); } - changePermutation(e, permutation, qc->outputPermutation, dd); - e = dd.reduceGarbage(e, qc->garbage); + changePermutation(e, permutation, qc.outputPermutation, dd); + e = dd.reduceGarbage(e, qc.garbage); return e; } template std::map -sample(const QuantumComputation* qc, const VectorDD& in, Package& dd, +sample(const QuantumComputation& qc, const VectorDD& in, Package& dd, std::size_t shots, std::size_t seed = 0U); /** @@ -65,13 +65,15 @@ std::map sample(const QuantumComputation& qc, std::size_t seed = 0U); template -void extractProbabilityVector(const QuantumComputation* qc, const VectorDD& in, - dd::SparsePVec& probVector, Package& dd); +void extractProbabilityVector(const QuantumComputation& qc, const VectorDD& in, + SparsePVec& probVector, Package& dd); template -void extractProbabilityVectorRecursive( - const QuantumComputation* qc, const VectorDD& currentState, - decltype(qc->begin()) currentIt, Permutation& permutation, - std::map measurements, dd::fp commonFactor, - dd::SparsePVec& probVector, Package& dd); +void extractProbabilityVectorRecursive(const QuantumComputation& qc, + const VectorDD& currentState, + decltype(qc.begin()) currentIt, + Permutation& permutation, + std::map measurements, + fp commonFactor, SparsePVec& probVector, + Package& dd); } // namespace dd diff --git a/src/dd/FunctionalityConstruction.cpp b/src/dd/FunctionalityConstruction.cpp index 275a1a656..0881fcf71 100644 --- a/src/dd/FunctionalityConstruction.cpp +++ b/src/dd/FunctionalityConstruction.cpp @@ -20,15 +20,15 @@ namespace dd { template -MatrixDD buildFunctionality(const QuantumComputation* qc, Package& dd) { - if (qc->getNqubits() == 0U) { +MatrixDD buildFunctionality(const QuantumComputation& qc, Package& dd) { + if (qc.getNqubits() == 0U) { return MatrixDD::one(); } - auto permutation = qc->initialLayout; - auto e = dd.createInitialMatrix(qc->ancillary); + auto permutation = qc.initialLayout; + auto e = dd.createInitialMatrix(qc.ancillary); - for (const auto& op : *qc) { + for (const auto& op : qc) { // SWAP gates can be executed virtually by changing the permutation if (op->getType() == OpType::SWAP && !op->isControlled()) { const auto& targets = op->getTargets(); @@ -39,44 +39,44 @@ MatrixDD buildFunctionality(const QuantumComputation* qc, Package& dd) { e = applyUnitaryOperation(op.get(), e, dd, permutation); } // correct permutation if necessary - changePermutation(e, permutation, qc->outputPermutation, dd); - e = dd.reduceAncillae(e, qc->ancillary); - e = dd.reduceGarbage(e, qc->garbage); + changePermutation(e, permutation, qc.outputPermutation, dd); + e = dd.reduceAncillae(e, qc.ancillary); + e = dd.reduceGarbage(e, qc.garbage); return e; } template -MatrixDD buildFunctionalityRecursive(const QuantumComputation* qc, +MatrixDD buildFunctionalityRecursive(const QuantumComputation& qc, Package& dd) { - if (qc->getNqubits() == 0U) { + if (qc.getNqubits() == 0U) { return MatrixDD::one(); } - auto permutation = qc->initialLayout; + auto permutation = qc.initialLayout; - if (qc->size() == 1U) { - auto e = getDD(qc->front().get(), dd, permutation); + if (qc.size() == 1U) { + auto e = getDD(qc.front().get(), dd, permutation); dd.incRef(e); return e; } std::stack s{}; - auto depth = static_cast(std::ceil(std::log2(qc->size()))); + auto depth = static_cast(std::ceil(std::log2(qc.size()))); buildFunctionalityRecursive(qc, depth, 0, s, permutation, dd); auto e = s.top(); s.pop(); // correct permutation if necessary - changePermutation(e, permutation, qc->outputPermutation, dd); - e = dd.reduceAncillae(e, qc->ancillary); - e = dd.reduceGarbage(e, qc->garbage); + changePermutation(e, permutation, qc.outputPermutation, dd); + e = dd.reduceAncillae(e, qc.ancillary); + e = dd.reduceGarbage(e, qc.garbage); return e; } template -bool buildFunctionalityRecursive(const QuantumComputation* qc, +bool buildFunctionalityRecursive(const QuantumComputation& qc, std::size_t depth, std::size_t opIdx, std::stack& s, Permutation& permutation, @@ -84,31 +84,31 @@ bool buildFunctionalityRecursive(const QuantumComputation* qc, // base case if (depth == 1U) { auto e = dd.makeIdent(); - if (const auto& op = qc->at(opIdx); + if (const auto& op = qc.at(opIdx); op->getType() == OpType::SWAP && !op->isControlled()) { const auto& targets = op->getTargets(); std::swap(permutation.at(targets[0U]), permutation.at(targets[1U])); } else { - e = getDD(qc->at(opIdx).get(), dd, permutation); + e = getDD(qc.at(opIdx).get(), dd, permutation); } ++opIdx; - if (opIdx == qc->size()) { + if (opIdx == qc.size()) { // only one element was left s.push(e); dd.incRef(e); return false; } auto f = dd.makeIdent(); - if (const auto& op = qc->at(opIdx); + if (const auto& op = qc.at(opIdx); op->getType() == OpType::SWAP && !op->isControlled()) { const auto& targets = op->getTargets(); std::swap(permutation.at(targets[0U]), permutation.at(targets[1U])); } else { - f = getDD(qc->at(opIdx).get(), dd, permutation); + f = getDD(qc.at(opIdx).get(), dd, permutation); } s.push(dd.multiply(f, e)); // ! reverse multiplication dd.incRef(s.top()); - return (opIdx != qc->size() - 1U); + return (opIdx != qc.size() - 1U); } // in case no operations are left after the first recursive call nothing has @@ -141,29 +141,29 @@ bool buildFunctionalityRecursive(const QuantumComputation* qc, return success; } -template MatrixDD buildFunctionality(const qc::QuantumComputation* qc, +template MatrixDD buildFunctionality(const qc::QuantumComputation& qc, Package& dd); template MatrixDD -buildFunctionality(const qc::QuantumComputation* qc, +buildFunctionality(const qc::QuantumComputation& qc, Package& dd); template MatrixDD -buildFunctionality(const qc::QuantumComputation* qc, +buildFunctionality(const qc::QuantumComputation& qc, Package& dd); -template MatrixDD buildFunctionality(const qc::QuantumComputation* qc, +template MatrixDD buildFunctionality(const qc::QuantumComputation& qc, UnitarySimulatorDDPackage& dd); -template MatrixDD buildFunctionalityRecursive(const qc::QuantumComputation* qc, +template MatrixDD buildFunctionalityRecursive(const qc::QuantumComputation& qc, Package& dd); -template bool buildFunctionalityRecursive(const qc::QuantumComputation* qc, +template bool buildFunctionalityRecursive(const qc::QuantumComputation& qc, const std::size_t depth, const std::size_t opIdx, std::stack& s, qc::Permutation& permutation, Package& dd); -template MatrixDD buildFunctionalityRecursive(const qc::QuantumComputation* qc, +template MatrixDD buildFunctionalityRecursive(const qc::QuantumComputation& qc, UnitarySimulatorDDPackage& dd); -template bool buildFunctionalityRecursive(const qc::QuantumComputation* qc, +template bool buildFunctionalityRecursive(const qc::QuantumComputation& qc, const std::size_t depth, const std::size_t opIdx, std::stack& s, diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp index 1f586bb67..e8d21a9e3 100644 --- a/src/dd/Simulation.cpp +++ b/src/dd/Simulation.cpp @@ -31,7 +31,7 @@ namespace dd { template std::map -sample(const QuantumComputation* qc, const VectorDD& in, Package& dd, +sample(const QuantumComputation& qc, const VectorDD& in, Package& dd, const std::size_t shots, const std::size_t seed) { auto isDynamicCircuit = false; auto hasMeasurements = false; @@ -54,7 +54,7 @@ sample(const QuantumComputation* qc, const VectorDD& in, Package& dd, std::map measurementMap{}; // rudimentary check whether circuit is dynamic - for (const auto& op : *qc) { + for (const auto& op : qc) { // if it contains any dynamic circuit primitives, it certainly is dynamic if (op->isClassicControlledOperation() || op->getType() == qc::Reset) { isDynamicCircuit = true; @@ -90,10 +90,10 @@ sample(const QuantumComputation* qc, const VectorDD& in, Package& dd, if (!isDynamicCircuit) { // if all gates are unitary (besides measurements at the end), we just // simulate once and measure all qubits repeatedly - auto permutation = qc->initialLayout; + auto permutation = qc.initialLayout; auto e = in; - for (const auto& op : *qc) { + for (const auto& op : qc) { // simply skip any non-unitary if (!op->isUnitary()) { continue; @@ -110,8 +110,8 @@ sample(const QuantumComputation* qc, const VectorDD& in, Package& dd, } // correct permutation if necessary - changePermutation(e, permutation, qc->outputPermutation, dd); - e = dd.reduceGarbage(e, qc->garbage); + changePermutation(e, permutation, qc.outputPermutation, dd); + e = dd.reduceGarbage(e, qc.garbage); // measure all qubits std::map counts{}; @@ -125,22 +125,21 @@ sample(const QuantumComputation* qc, const VectorDD& in, Package& dd, std::map actualCounts{}; for (const auto& [bitstring, count] : counts) { - std::string measurement(qc->getNcbits(), '0'); + std::string measurement(qc.getNcbits(), '0'); if (hasMeasurements) { // if the circuit contains measurements, we only want to return the // measured bits for (const auto& [qubit, bit] : measurementMap) { // measurement map specifies that the circuit `qubit` is measured into // a certain `bit` - measurement[qc->getNcbits() - 1U - bit] = - bitstring[bitstring.size() - 1U - - qc->outputPermutation.at(qubit)]; + measurement[qc.getNcbits() - 1U - bit] = + bitstring[bitstring.size() - 1U - qc.outputPermutation.at(qubit)]; } } else { // otherwise, we consider the output permutation for determining where // to measure the qubits to - for (const auto& [qubit, bit] : qc->outputPermutation) { - measurement[qc->getNcbits() - 1U - bit] = + for (const auto& [qubit, bit] : qc.outputPermutation) { + measurement[qc.getNcbits() - 1U - bit] = bitstring[bitstring.size() - 1U - qubit]; } } @@ -152,12 +151,12 @@ sample(const QuantumComputation* qc, const VectorDD& in, Package& dd, std::map counts{}; for (std::size_t i = 0U; i < shots; i++) { - std::vector measurements(qc->getNcbits(), false); + std::vector measurements(qc.getNcbits(), false); - auto permutation = qc->initialLayout; + auto permutation = qc.initialLayout; auto e = in; dd.incRef(e); - for (const auto& op : *qc) { + for (const auto& op : qc) { if (op->isUnitary()) { // SWAP gates can be executed virtually by changing the permutation if (op->getType() == OpType::SWAP && !op->isControlled()) { @@ -192,10 +191,10 @@ sample(const QuantumComputation* qc, const VectorDD& in, Package& dd, // reduce reference count of measured state dd.decRef(e); - std::string shot(qc->getNcbits(), '0'); - for (size_t bit = 0U; bit < qc->getNcbits(); ++bit) { + std::string shot(qc.getNcbits(), '0'); + for (size_t bit = 0U; bit < qc.getNcbits(); ++bit) { if (measurements[bit]) { - shot[qc->getNcbits() - bit - 1U] = '1'; + shot[qc.getNcbits() - bit - 1U] = '1'; } } counts[shot]++; @@ -208,29 +207,29 @@ std::map sample(const QuantumComputation& qc, const std::size_t seed) { const auto nqubits = qc.getNqubits(); auto dd = std::make_unique>(nqubits); - return sample(&qc, dd->makeZeroState(nqubits), *dd, shots, seed); + return sample(qc, dd->makeZeroState(nqubits), *dd, shots, seed); } template -void extractProbabilityVector(const QuantumComputation* qc, const VectorDD& in, +void extractProbabilityVector(const QuantumComputation& qc, const VectorDD& in, SparsePVec& probVector, Package& dd) { - auto permutation = qc->initialLayout; + auto permutation = qc.initialLayout; dd.incRef(in); - extractProbabilityVectorRecursive(qc, in, qc->begin(), permutation, + extractProbabilityVectorRecursive(qc, in, qc.begin(), permutation, std::map{}, 1., probVector, dd); } template -void extractProbabilityVectorRecursive(const QuantumComputation* qc, +void extractProbabilityVectorRecursive(const QuantumComputation& qc, const VectorDD& currentState, - decltype(qc->begin()) currentIt, + decltype(qc.begin()) currentIt, Permutation& permutation, std::map measurements, fp commonFactor, SparsePVec& probVector, Package& dd) { auto state = currentState; - for (auto it = currentIt; it != qc->end(); ++it) { + for (auto it = currentIt; it != qc.end(); ++it) { const auto& op = (*it); // any standard operation is applied here @@ -332,10 +331,10 @@ void extractProbabilityVectorRecursive(const QuantumComputation* qc, // base case -> determine the basis state from the measurement and safe // the probability - if (measurements.size() == qc->getNcbits() - 1) { + if (measurements.size() == qc.getNcbits() - 1) { std::size_t idx0 = 0U; std::size_t idx1 = 0U; - for (std::size_t i = 0U; i < qc->getNcbits(); ++i) { + for (std::size_t i = 0U; i < qc.getNcbits(); ++i) { // if this is the qubit being measured and the result is one if (i == static_cast(bit)) { idx1 |= (1ULL << i); @@ -421,15 +420,14 @@ void extractProbabilityVectorRecursive(const QuantumComputation* qc, } template std::map -sample(const QuantumComputation* qc, const VectorDD& in, - Package& dd, std::size_t shots, - std::size_t seed); +sample(const QuantumComputation& qc, const VectorDD& in, + Package<>& dd, std::size_t shots, std::size_t seed); template void extractProbabilityVector( - const QuantumComputation* qc, const VectorDD& in, SparsePVec& probVector, - Package& dd); + const QuantumComputation& qc, const VectorDD& in, SparsePVec& probVector, + Package<>& dd); template void extractProbabilityVectorRecursive( - const QuantumComputation* qc, const VectorDD& in, - decltype(qc->begin()) currentIt, Permutation& permutation, + const QuantumComputation& qc, const VectorDD& in, + decltype(qc.begin()) currentIt, Permutation& permutation, std::map measurements, fp commonFactor, - SparsePVec& probVector, Package& dd); + SparsePVec& probVector, Package<>& dd); } // namespace dd diff --git a/test/algorithms/eval_dynamic_circuits.cpp b/test/algorithms/eval_dynamic_circuits.cpp index d90aaae4b..30929d4e0 100644 --- a/test/algorithms/eval_dynamic_circuits.cpp +++ b/test/algorithms/eval_dynamic_circuits.cpp @@ -186,12 +186,12 @@ TEST_P(DynamicCircuitEvalExactQPE, UnitaryTransformation) { TEST_P(DynamicCircuitEvalExactQPE, ProbabilityExtraction) { // generate DD of QPE circuit via simulation const auto start = std::chrono::steady_clock::now(); - const auto e = simulate(&qpe, dd->makeZeroState(qpe.getNqubits()), *dd); + const auto e = simulate(qpe, dd->makeZeroState(qpe.getNqubits()), *dd); const auto simulationEnd = std::chrono::steady_clock::now(); // extract measurement probabilities from IQPE simulations dd::SparsePVec probs{}; - extractProbabilityVector(&iqpe, dd->makeZeroState(iqpe.getNqubits()), probs, + extractProbabilityVector(iqpe, dd->makeZeroState(iqpe.getNqubits()), probs, *dd); const auto extractionEnd = std::chrono::steady_clock::now(); @@ -391,13 +391,13 @@ TEST_P(DynamicCircuitEvalInexactQPE, ProbabilityExtraction) { const auto start = std::chrono::steady_clock::now(); // extract measurement probabilities from IQPE simulations dd::SparsePVec probs{}; - extractProbabilityVector(&iqpe, dd->makeZeroState(iqpe.getNqubits()), probs, + extractProbabilityVector(iqpe, dd->makeZeroState(iqpe.getNqubits()), probs, *dd); const auto extractionEnd = std::chrono::steady_clock::now(); std::cout << "---- extraction done ----\n"; // generate DD of QPE circuit via simulation - auto e = simulate(&qpe, dd->makeZeroState(qpe.getNqubits()), *dd); + auto e = simulate(qpe, dd->makeZeroState(qpe.getNqubits()), *dd); const auto simulationEnd = std::chrono::steady_clock::now(); std::cout << "---- sim done ----\n"; @@ -540,12 +540,12 @@ TEST_P(DynamicCircuitEvalBV, UnitaryTransformation) { TEST_P(DynamicCircuitEvalBV, ProbabilityExtraction) { // generate DD of QPE circuit via simulation const auto start = std::chrono::steady_clock::now(); - const auto e = simulate(&bv, dd->makeZeroState(bv.getNqubits()), *dd); + const auto e = simulate(bv, dd->makeZeroState(bv.getNqubits()), *dd); const auto simulationEnd = std::chrono::steady_clock::now(); // extract measurement probabilities from IQPE simulations dd::SparsePVec probs{}; - extractProbabilityVector(&dbv, dd->makeZeroState(dbv.getNqubits()), probs, + extractProbabilityVector(dbv, dd->makeZeroState(dbv.getNqubits()), probs, *dd); const auto extractionEnd = std::chrono::steady_clock::now(); @@ -684,7 +684,7 @@ TEST_P(DynamicCircuitEvalQFT, UnitaryTransformation) { TEST_P(DynamicCircuitEvalQFT, ProbabilityExtraction) { // generate DD of QPE circuit via simulation const auto start = std::chrono::steady_clock::now(); - auto e = simulate(&qft, dd->makeZeroState(qft.getNqubits()), *dd); + auto e = simulate(qft, dd->makeZeroState(qft.getNqubits()), *dd); const auto simulationEnd = std::chrono::steady_clock::now(); const auto simulation = std::chrono::duration(simulationEnd - start).count(); @@ -693,7 +693,7 @@ TEST_P(DynamicCircuitEvalQFT, ProbabilityExtraction) { // extract measurement probabilities from IQPE simulations if (qft.getNqubits() <= 15) { dd::SparsePVec probs{}; - extractProbabilityVector(&dqft, dd->makeZeroState(dqft.getNqubits()), probs, + extractProbabilityVector(dqft, dd->makeZeroState(dqft.getNqubits()), probs, *dd); const auto extractionEnd = std::chrono::steady_clock::now(); diff --git a/test/algorithms/test_bernsteinvazirani.cpp b/test/algorithms/test_bernsteinvazirani.cpp index 014153913..9a43a55f0 100644 --- a/test/algorithms/test_bernsteinvazirani.cpp +++ b/test/algorithms/test_bernsteinvazirani.cpp @@ -123,7 +123,7 @@ TEST_P(BernsteinVazirani, DynamicEquivalenceSimulation) { qc::CircuitOptimizer::removeFinalMeasurements(bv); // simulate circuit - auto e = dd::simulate(&bv, dd->makeZeroState(bv.getNqubits()), *dd); + auto e = dd::simulate(bv, dd->makeZeroState(bv.getNqubits()), *dd); // create dynamic BV circuit auto dbv = qc::createIterativeBernsteinVazirani(s); @@ -138,7 +138,7 @@ TEST_P(BernsteinVazirani, DynamicEquivalenceSimulation) { qc::CircuitOptimizer::removeFinalMeasurements(dbv); // simulate circuit - auto f = dd::simulate(&dbv, dd->makeZeroState(dbv.getNqubits()), *dd); + auto f = dd::simulate(dbv, dd->makeZeroState(dbv.getNqubits()), *dd); // calculate fidelity between both results auto fidelity = dd->fidelity(e, f); diff --git a/test/algorithms/test_entanglement.cpp b/test/algorithms/test_entanglement.cpp index 2c09bcd09..642f0e6cd 100644 --- a/test/algorithms/test_entanglement.cpp +++ b/test/algorithms/test_entanglement.cpp @@ -41,7 +41,7 @@ INSTANTIATE_TEST_SUITE_P( TEST_P(Entanglement, FunctionTest) { const auto qc = qc::createGHZState(nq); - const auto e = dd::buildFunctionality(&qc, *dd); + const auto e = dd::buildFunctionality(qc, *dd); ASSERT_EQ(qc.getNops(), nq); const auto r = dd->multiply(e, dd->makeZeroState(nq)); ASSERT_EQ(r.getValueByPath(nq, std::string(nq, '0')), dd::SQRT2_2); @@ -50,7 +50,7 @@ TEST_P(Entanglement, FunctionTest) { TEST_P(Entanglement, GHZRoutineFunctionTest) { const auto qc = qc::createGHZState(nq); - const auto e = dd::simulate(&qc, dd->makeZeroState(nq), *dd); + const auto e = dd::simulate(qc, dd->makeZeroState(nq), *dd); const auto f = dd->makeGHZState(nq); EXPECT_EQ(e, f); } diff --git a/test/algorithms/test_grover.cpp b/test/algorithms/test_grover.cpp index fcb8eccc9..62a5c19e6 100644 --- a/test/algorithms/test_grover.cpp +++ b/test/algorithms/test_grover.cpp @@ -91,7 +91,7 @@ TEST_P(Grover, Functionality) { qc::appendGroverOracle(groverIteration, targetValue); qc::appendGroverDiffusion(groverIteration); - const auto iteration = buildFunctionality(&groverIteration, *dd); + const auto iteration = buildFunctionality(groverIteration, *dd); auto e = iteration; dd->incRef(e); @@ -106,7 +106,7 @@ TEST_P(Grover, Functionality) { qc::QuantumComputation setup(qc.getNqubits()); qc::appendGroverInitialization(setup); - const auto g = buildFunctionality(&setup, *dd); + const auto g = buildFunctionality(setup, *dd); const auto f = dd->multiply(e, g); dd->incRef(f); dd->decRef(e); @@ -132,7 +132,7 @@ TEST_P(Grover, FunctionalityRecursive) { qc::appendGroverOracle(groverIteration, targetValue); qc::appendGroverDiffusion(groverIteration); - const auto iter = buildFunctionalityRecursive(&groverIteration, *dd); + const auto iter = buildFunctionalityRecursive(groverIteration, *dd); auto e = iter; const auto iterations = qc::computeNumberOfIterations(nqubits); const std::bitset<128U> iterBits(iterations); @@ -165,7 +165,7 @@ TEST_P(Grover, FunctionalityRecursive) { // apply state preparation setup qc::QuantumComputation statePrep(qc.getNqubits()); qc::appendGroverInitialization(statePrep); - const auto s = buildFunctionality(&statePrep, *dd); + const auto s = buildFunctionality(statePrep, *dd); func = dd->multiply(e, s); dd->incRef(func); dd->decRef(s); diff --git a/test/algorithms/test_qft.cpp b/test/algorithms/test_qft.cpp index 06aaf8c60..aa763d8c0 100644 --- a/test/algorithms/test_qft.cpp +++ b/test/algorithms/test_qft.cpp @@ -75,7 +75,7 @@ TEST_P(QFT, Functionality) { // there should be no error building the functionality // there should be no error building the functionality - ASSERT_NO_THROW({ func = buildFunctionality(&qc, *dd); }); + ASSERT_NO_THROW({ func = buildFunctionality(qc, *dd); }); qc.printStatistics(std::cout); // QFT DD should consist of 2^n nodes @@ -120,7 +120,7 @@ TEST_P(QFT, FunctionalityRecursive) { ASSERT_NO_THROW({ qc = qc::createQFT(nqubits, false); }); // there should be no error building the functionality - ASSERT_NO_THROW({ func = buildFunctionalityRecursive(&qc, *dd); }); + ASSERT_NO_THROW({ func = buildFunctionalityRecursive(qc, *dd); }); qc.printStatistics(std::cout); // QFT DD should consist of 2^n nodes @@ -168,7 +168,7 @@ TEST_P(QFT, Simulation) { // there should be no error simulating the circuit ASSERT_NO_THROW({ auto in = dd->makeZeroState(nqubits); - sim = simulate(&qc, in, *dd); + sim = simulate(qc, in, *dd); }); qc.printStatistics(std::cout); @@ -202,11 +202,11 @@ TEST_P(QFT, FunctionalityRecursiveEquality) { ASSERT_NO_THROW({ qc = qc::createQFT(nqubits, false); }); // there should be no error building the functionality recursively - ASSERT_NO_THROW({ func = buildFunctionalityRecursive(&qc, *dd); }); + ASSERT_NO_THROW({ func = buildFunctionalityRecursive(qc, *dd); }); // there should be no error building the functionality regularly qc::MatrixDD funcRec{}; - ASSERT_NO_THROW({ funcRec = buildFunctionality(&qc, *dd); }); + ASSERT_NO_THROW({ funcRec = buildFunctionality(qc, *dd); }); ASSERT_EQ(func, funcRec); dd->decRef(funcRec); diff --git a/test/algorithms/test_qpe.cpp b/test/algorithms/test_qpe.cpp index fc6002a6f..e6a966317 100644 --- a/test/algorithms/test_qpe.cpp +++ b/test/algorithms/test_qpe.cpp @@ -137,7 +137,7 @@ TEST_P(QPE, QPETest) { qc::VectorDD e{}; ASSERT_NO_THROW( - { e = dd::simulate(&qc, dd->makeZeroState(qc.getNqubits()), *dd); }); + { e = dd::simulate(qc, dd->makeZeroState(qc.getNqubits()), *dd); }); // account for the eigenstate qubit by adding an offset const auto offset = 1ULL << (e.p->v + 1); @@ -219,7 +219,7 @@ TEST_P(QPE, DynamicEquivalenceSimulation) { qc::CircuitOptimizer::removeFinalMeasurements(qpe); // simulate circuit - auto e = dd::simulate(&qpe, dd->makeZeroState(qpe.getNqubits()), *dd); + auto e = dd::simulate(qpe, dd->makeZeroState(qpe.getNqubits()), *dd); // create standard IQPE circuit auto iqpe = qc::createIterativeQPE(lambda, precision); @@ -233,7 +233,7 @@ TEST_P(QPE, DynamicEquivalenceSimulation) { qc::CircuitOptimizer::removeFinalMeasurements(iqpe); // simulate circuit - auto f = dd::simulate(&iqpe, dd->makeZeroState(iqpe.getNqubits()), *dd); + auto f = dd::simulate(iqpe, dd->makeZeroState(iqpe.getNqubits()), *dd); // calculate fidelity between both results auto fidelity = dd->fidelity(e, f); @@ -252,7 +252,7 @@ TEST_P(QPE, DynamicEquivalenceFunctionality) { qc::CircuitOptimizer::removeFinalMeasurements(qpe); // simulate circuit - auto e = dd::buildFunctionality(&qpe, *dd); + auto e = dd::buildFunctionality(qpe, *dd); // create standard IQPE circuit auto iqpe = qc::createIterativeQPE(lambda, precision); @@ -267,7 +267,7 @@ TEST_P(QPE, DynamicEquivalenceFunctionality) { qc::CircuitOptimizer::removeFinalMeasurements(iqpe); // simulate circuit - auto f = dd::buildFunctionality(&iqpe, *dd); + auto f = dd::buildFunctionality(iqpe, *dd); EXPECT_EQ(e, f); } @@ -279,7 +279,7 @@ TEST_P(QPE, ProbabilityExtraction) { auto iqpe = qc::createIterativeQPE(lambda, precision); dd::SparsePVec probs{}; - dd::extractProbabilityVector(&iqpe, dd->makeZeroState(iqpe.getNqubits()), + dd::extractProbabilityVector(iqpe, dd->makeZeroState(iqpe.getNqubits()), probs, *dd); for (const auto& [state, prob] : probs) { @@ -307,7 +307,7 @@ TEST_P(QPE, DynamicEquivalenceSimulationProbabilityExtraction) { qc::CircuitOptimizer::removeFinalMeasurements(qpe); // simulate circuit - auto e = dd::simulate(&qpe, dd->makeZeroState(qpe.getNqubits()), *dd); + auto e = dd::simulate(qpe, dd->makeZeroState(qpe.getNqubits()), *dd); const auto vec = e.getVector(); std::cout << "QPE:\n"; for (const auto& amp : vec) { @@ -319,7 +319,7 @@ TEST_P(QPE, DynamicEquivalenceSimulationProbabilityExtraction) { // extract measurement probabilities from IQPE simulations dd::SparsePVec probs{}; - dd::extractProbabilityVector(&iqpe, dd->makeZeroState(iqpe.getNqubits()), + dd::extractProbabilityVector(iqpe, dd->makeZeroState(iqpe.getNqubits()), probs, *dd); std::cout << "IQPE:\n"; @@ -330,8 +330,8 @@ TEST_P(QPE, DynamicEquivalenceSimulationProbabilityExtraction) { } // calculate fidelity between both results - auto fidelity = - dd->fidelityOfMeasurementOutcomes(e, probs, qpe.outputPermutation); + const auto fidelity = dd::Package<>::fidelityOfMeasurementOutcomes( + e, probs, qpe.outputPermutation); std::cout << "Fidelity of both circuits' measurement outcomes: " << fidelity << "\n"; diff --git a/test/algorithms/test_random_clifford.cpp b/test/algorithms/test_random_clifford.cpp index 0f68f5e28..4d7c34d56 100644 --- a/test/algorithms/test_random_clifford.cpp +++ b/test/algorithms/test_random_clifford.cpp @@ -40,7 +40,7 @@ TEST_P(RandomClifford, simulate) { auto dd = std::make_unique>(nq); auto qc = qc::createRandomCliffordCircuit(nq, nq * nq, 12345); auto in = dd->makeZeroState(nq); - ASSERT_NO_THROW({ dd::simulate(&qc, in, *dd); }); + ASSERT_NO_THROW({ dd::simulate(qc, in, *dd); }); qc.printStatistics(std::cout); } @@ -49,6 +49,6 @@ TEST_P(RandomClifford, buildFunctionality) { auto dd = std::make_unique>(nq); auto qc = qc::createRandomCliffordCircuit(nq, nq * nq, 12345); - ASSERT_NO_THROW({ dd::buildFunctionality(&qc, *dd); }); + ASSERT_NO_THROW({ dd::buildFunctionality(qc, *dd); }); qc.printStatistics(std::cout); } diff --git a/test/algorithms/test_wstate.cpp b/test/algorithms/test_wstate.cpp index 387673437..8676eb8dd 100644 --- a/test/algorithms/test_wstate.cpp +++ b/test/algorithms/test_wstate.cpp @@ -65,7 +65,7 @@ TEST_P(WState, RoutineFunctionTest) { auto qc = qc::createWState(nq); auto dd = std::make_unique>(qc.getNqubits()); const dd::VectorDD e = - dd::simulate(&qc, dd->makeZeroState(qc.getNqubits()), *dd); + dd::simulate(qc, dd->makeZeroState(qc.getNqubits()), *dd); const auto f = dd->makeWState(nq); EXPECT_EQ(e, f); diff --git a/test/dd/test_dd_functionality.cpp b/test/dd/test_dd_functionality.cpp index fd5f60734..2b18fe9ff 100644 --- a/test/dd/test_dd_functionality.cpp +++ b/test/dd/test_dd_functionality.cpp @@ -324,11 +324,11 @@ TEST_F(DDFunctionality, buildCircuit) { qc.swap(0, 1); qc.x(0); - e = buildFunctionality(&qc, *dd); + e = buildFunctionality(qc, *dd); EXPECT_EQ(ident, e); qc.x(0); - e = buildFunctionality(&qc, *dd); + e = buildFunctionality(qc, *dd); dd->incRef(e); EXPECT_NE(ident, e); } @@ -367,8 +367,8 @@ TEST_F(DDFunctionality, CircuitEquivalence) { qc2.sx(0); qc2.rz(PI_2, 0); - const qc::MatrixDD dd1 = buildFunctionality(&qc1, *dd); - const qc::MatrixDD dd2 = buildFunctionality(&qc2, *dd); + const qc::MatrixDD dd1 = buildFunctionality(qc1, *dd); + const qc::MatrixDD dd2 = buildFunctionality(qc2, *dd); EXPECT_EQ(dd1.p, dd2.p); } @@ -380,12 +380,12 @@ TEST_F(DDFunctionality, changePermutation) { "qreg q[2];" "x q[0];\n"; const auto qc = QuantumComputation::fromQASM(testfile); - auto sim = simulate(&qc, dd->makeZeroState(qc.getNqubits()), *dd); + const auto sim = simulate(qc, dd->makeZeroState(qc.getNqubits()), *dd); EXPECT_TRUE(sim.p->e[0].isZeroTerminal()); EXPECT_TRUE(sim.p->e[1].w.exactlyOne()); EXPECT_TRUE(sim.p->e[1].p->e[1].isZeroTerminal()); EXPECT_TRUE(sim.p->e[1].p->e[0].w.exactlyOne()); - auto func = buildFunctionality(&qc, *dd); + const auto func = buildFunctionality(qc, *dd); EXPECT_FALSE(func.p->e[0].isZeroTerminal()); EXPECT_FALSE(func.p->e[1].isZeroTerminal()); EXPECT_FALSE(func.p->e[2].isZeroTerminal()); @@ -465,9 +465,9 @@ TEST_F(DDFunctionality, FuseTwoSingleQubitGates) { qc.h(0); qc.print(std::cout); - e = buildFunctionality(&qc, *dd); + e = buildFunctionality(qc, *dd); CircuitOptimizer::singleQubitGateFusion(qc); - const auto f = buildFunctionality(&qc, *dd); + const auto f = buildFunctionality(qc, *dd); std::cout << "-----------------------------\n"; qc.print(std::cout); EXPECT_EQ(qc.getNops(), 1); @@ -481,11 +481,11 @@ TEST_F(DDFunctionality, FuseThreeSingleQubitGates) { qc.h(0); qc.y(0); - e = buildFunctionality(&qc, *dd); + e = buildFunctionality(qc, *dd); std::cout << "-----------------------------\n"; qc.print(std::cout); CircuitOptimizer::singleQubitGateFusion(qc); - const auto f = buildFunctionality(&qc, *dd); + const auto f = buildFunctionality(qc, *dd); std::cout << "-----------------------------\n"; qc.print(std::cout); EXPECT_EQ(qc.getNops(), 1); @@ -498,11 +498,11 @@ TEST_F(DDFunctionality, FuseNoSingleQubitGates) { qc.h(0); qc.cx(0, 1); qc.y(0); - e = buildFunctionality(&qc, *dd); + e = buildFunctionality(qc, *dd); std::cout << "-----------------------------\n"; qc.print(std::cout); CircuitOptimizer::singleQubitGateFusion(qc); - const auto f = buildFunctionality(&qc, *dd); + const auto f = buildFunctionality(qc, *dd); std::cout << "-----------------------------\n"; qc.print(std::cout); EXPECT_EQ(qc.getNops(), 3); @@ -515,11 +515,11 @@ TEST_F(DDFunctionality, FuseSingleQubitGatesAcrossOtherGates) { qc.h(0); qc.z(1); qc.y(0); - e = buildFunctionality(&qc, *dd); + e = buildFunctionality(qc, *dd); std::cout << "-----------------------------\n"; qc.print(std::cout); CircuitOptimizer::singleQubitGateFusion(qc); - const auto f = buildFunctionality(&qc, *dd); + const auto f = buildFunctionality(qc, *dd); std::cout << "-----------------------------\n"; qc.print(std::cout); EXPECT_EQ(qc.getNops(), 2); @@ -590,7 +590,7 @@ TEST_F(DDFunctionality, dynamicCircuitProbabilityVectorExtractionWithSWAP) { const auto zeroState = dd->makeZeroState(2); auto probVector = dd::SparsePVec{}; - extractProbabilityVector(&qc, zeroState, probVector, *dd); + extractProbabilityVector(qc, zeroState, probVector, *dd); EXPECT_EQ(probVector.size(), 1); const auto& [key, value] = *probVector.begin(); EXPECT_EQ(value, 1.); From 515023fd2c54cd5cfe65a5965b2fc9f7f8be6e3f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Jan 2025 16:41:04 +0100 Subject: [PATCH 04/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20to=20use?= =?UTF-8?q?=20references=20instead=20of=20pointers=20for=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced raw pointer usages with references across the codebase for improved clarity and type safety. This adjustment simplifies operations and ensures consistency, particularly when applying, measuring, or resetting operations in simulation and functionality logic. Corresponding tests have been updated to reflect this change. Signed-off-by: burgholzer --- include/mqt-core/dd/Operations.hpp | 103 ++++++++++------------ include/mqt-core/dd/Simulation.hpp | 2 +- src/dd/FunctionalityConstruction.cpp | 8 +- src/dd/Operations.cpp | 2 +- src/dd/Simulation.cpp | 33 +++---- test/algorithms/eval_dynamic_circuits.cpp | 32 +++---- test/dd/test_dd_functionality.cpp | 25 +++--- test/dd/test_dd_noise_functionality.cpp | 10 +-- 8 files changed, 103 insertions(+), 112 deletions(-) diff --git a/include/mqt-core/dd/Operations.hpp b/include/mqt-core/dd/Operations.hpp index 8f02a967d..ead9c48f2 100644 --- a/include/mqt-core/dd/Operations.hpp +++ b/include/mqt-core/dd/Operations.hpp @@ -37,13 +37,13 @@ namespace dd { // single-target Operations template qc::MatrixDD -getStandardOperationDD(const qc::StandardOperation* op, Package& dd, +getStandardOperationDD(const qc::StandardOperation& op, Package& dd, const qc::Controls& controls, const qc::Qubit target, const bool inverse) { GateMatrix gm; - const auto type = op->getType(); - const auto& parameter = op->getParameter(); + const auto type = op.getType(); + const auto& parameter = op.getParameter(); switch (type) { case qc::I: @@ -107,7 +107,7 @@ getStandardOperationDD(const qc::StandardOperation* op, Package& dd, break; default: std::ostringstream oss{}; - oss << "DD for gate" << op->getName() << " not available!"; + oss << "DD for gate" << op.getName() << " not available!"; throw qc::QFRException(oss.str()); } return dd.makeGateDD(gm, controls, target); @@ -116,11 +116,11 @@ getStandardOperationDD(const qc::StandardOperation* op, Package& dd, // two-target Operations template qc::MatrixDD -getStandardOperationDD(const qc::StandardOperation* op, Package& dd, +getStandardOperationDD(const qc::StandardOperation& op, Package& dd, const qc::Controls& controls, qc::Qubit target0, qc::Qubit target1, const bool inverse) { - const auto type = op->getType(); - const auto& parameter = op->getParameter(); + const auto type = op.getType(); + const auto& parameter = op.getParameter(); if (type == qc::DCX && inverse) { // DCX is not self-inverse, but the inverse is just swapping the targets @@ -172,7 +172,7 @@ getStandardOperationDD(const qc::StandardOperation* op, Package& dd, break; default: std::ostringstream oss{}; - oss << "DD for gate " << op->getName() << " not available!"; + oss << "DD for gate " << op.getName() << " not available!"; throw qc::QFRException(oss.str()); } @@ -186,17 +186,17 @@ getStandardOperationDD(const qc::StandardOperation* op, Package& dd, // An empty permutation marks the identity permutation. template -qc::MatrixDD getDD(const qc::Operation* op, Package& dd, +qc::MatrixDD getDD(const qc::Operation& op, Package& dd, const qc::Permutation& permutation = {}, const bool inverse = false) { - const auto type = op->getType(); + const auto type = op.getType(); if (type == qc::Barrier) { return dd.makeIdent(); } if (type == qc::GPhase) { - auto phase = op->getParameter()[0U]; + auto phase = op.getParameter()[0U]; if (inverse) { phase = -phase; } @@ -205,10 +205,10 @@ qc::MatrixDD getDD(const qc::Operation* op, Package& dd, return id; } - if (op->isStandardOperation()) { - const auto* standardOp = dynamic_cast(op); - const auto& targets = permutation.apply(op->getTargets()); - const auto& controls = permutation.apply(op->getControls()); + if (op.isStandardOperation()) { + const auto& standardOp = dynamic_cast(op); + const auto& targets = permutation.apply(standardOp.getTargets()); + const auto& controls = permutation.apply(standardOp.getControls()); if (qc::isTwoQubitGate(type)) { assert(targets.size() == 2); @@ -220,39 +220,39 @@ qc::MatrixDD getDD(const qc::Operation* op, Package& dd, inverse); } - if (op->isCompoundOperation()) { - const auto* compoundOp = dynamic_cast(op); + if (op.isCompoundOperation()) { + const auto& compoundOp = dynamic_cast(op); auto e = dd.makeIdent(); if (inverse) { - for (const auto& operation : *compoundOp) { - e = dd.multiply(e, getInverseDD(operation.get(), dd, permutation)); + for (const auto& operation : compoundOp) { + e = dd.multiply(e, getInverseDD(*operation, dd, permutation)); } } else { - for (const auto& operation : *compoundOp) { - e = dd.multiply(getDD(operation.get(), dd, permutation), e); + for (const auto& operation : compoundOp) { + e = dd.multiply(getDD(*operation, dd, permutation), e); } } return e; } - if (op->isClassicControlledOperation()) { - const auto* classicOp = - dynamic_cast(op); - return getDD(classicOp->getOperation(), dd, permutation, inverse); + if (op.isClassicControlledOperation()) { + const auto& classicOp = + dynamic_cast(op); + return getDD(*classicOp.getOperation(), dd, permutation, inverse); } - assert(op->isNonUnitaryOperation()); + assert(op.isNonUnitaryOperation()); throw qc::QFRException("DD for non-unitary operation not available!"); } template -qc::MatrixDD getInverseDD(const qc::Operation* op, Package& dd, +qc::MatrixDD getInverseDD(const qc::Operation& op, Package& dd, const qc::Permutation& permutation = {}) { return getDD(op, dd, permutation, true); } template -Edge applyUnitaryOperation(const qc::Operation* op, const Edge& in, +Edge applyUnitaryOperation(const qc::Operation& op, const Edge& in, Package& dd, const qc::Permutation& permutation = {}) { static_assert(std::is_same_v || @@ -261,17 +261,14 @@ Edge applyUnitaryOperation(const qc::Operation* op, const Edge& in, } template -qc::VectorDD applyMeasurement(const qc::Operation* op, qc::VectorDD in, - Package& dd, std::mt19937_64& rng, +qc::VectorDD applyMeasurement(const qc::NonUnitaryOperation& op, + qc::VectorDD in, Package& dd, + std::mt19937_64& rng, std::vector& measurements, const qc::Permutation& permutation = {}) { - assert(op->getType() == qc::Measure); - assert(op->isNonUnitaryOperation()); - const auto* measure = dynamic_cast(op); - assert(measure != nullptr); - - const auto& qubits = permutation.apply(measure->getTargets()); - const auto& bits = measure->getClassics(); + assert(op.getType() == qc::Measure); + const auto& qubits = permutation.apply(op.getTargets()); + const auto& bits = op.getClassics(); for (size_t j = 0U; j < qubits.size(); ++j) { measurements.at(bits.at(j)) = dd.measureOneCollapsing(in, static_cast(qubits.at(j)), @@ -281,38 +278,32 @@ qc::VectorDD applyMeasurement(const qc::Operation* op, qc::VectorDD in, } template -qc::VectorDD applyReset(const qc::Operation* op, qc::VectorDD in, +qc::VectorDD applyReset(const qc::NonUnitaryOperation& op, qc::VectorDD in, Package& dd, std::mt19937_64& rng, const qc::Permutation& permutation = {}) { - assert(op->getType() == qc::Reset); - assert(op->isNonUnitaryOperation()); - const auto* reset = dynamic_cast(op); - assert(reset != nullptr); - - const auto& qubits = permutation.apply(reset->getTargets()); + assert(op.getType() == qc::Reset); + const auto& qubits = permutation.apply(op.getTargets()); for (const auto& qubit : qubits) { const auto bit = dd.measureOneCollapsing(in, static_cast(qubit), rng); // apply an X operation whenever the measured result is one if (bit == '1') { const auto x = qc::StandardOperation(qubit, qc::X); - in = applyUnitaryOperation(&x, in, dd); + in = applyUnitaryOperation(x, in, dd); } } return in; } template -qc::VectorDD applyClassicControlledOperation( - const qc::Operation* op, const qc::VectorDD& in, Package& dd, - std::vector& measurements, const qc::Permutation& permutation = {}) { - assert(op->isClassicControlledOperation()); - const auto* classic = dynamic_cast(op); - assert(classic != nullptr); - - const auto& [regStart, regSize] = classic->getControlRegister(); - const auto& expectedValue = classic->getExpectedValue(); - const auto& comparisonKind = classic->getComparisonKind(); +qc::VectorDD +applyClassicControlledOperation(const qc::ClassicControlledOperation& op, + const qc::VectorDD& in, Package& dd, + std::vector& measurements, + const qc::Permutation& permutation = {}) { + const auto& [regStart, regSize] = op.getControlRegister(); + const auto& expectedValue = op.getExpectedValue(); + const auto& comparisonKind = op.getComparisonKind(); auto actualValue = 0ULL; // determine the actual value from measurements @@ -346,7 +337,7 @@ qc::VectorDD applyClassicControlledOperation( return in; } - return applyUnitaryOperation(classic, in, dd, permutation); + return applyUnitaryOperation(op, in, dd, permutation); } template diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp index 0b032c0a8..588347fcb 100644 --- a/include/mqt-core/dd/Simulation.hpp +++ b/include/mqt-core/dd/Simulation.hpp @@ -36,7 +36,7 @@ VectorDD simulate(const QuantumComputation& qc, const VectorDD& in, continue; } - e = applyUnitaryOperation(op.get(), e, dd, permutation); + e = applyUnitaryOperation(*op, e, dd, permutation); } changePermutation(e, permutation, qc.outputPermutation, dd); e = dd.reduceGarbage(e, qc.garbage); diff --git a/src/dd/FunctionalityConstruction.cpp b/src/dd/FunctionalityConstruction.cpp index 0881fcf71..e5ea94506 100644 --- a/src/dd/FunctionalityConstruction.cpp +++ b/src/dd/FunctionalityConstruction.cpp @@ -36,7 +36,7 @@ MatrixDD buildFunctionality(const QuantumComputation& qc, Package& dd) { continue; } - e = applyUnitaryOperation(op.get(), e, dd, permutation); + e = applyUnitaryOperation(*op, e, dd, permutation); } // correct permutation if necessary changePermutation(e, permutation, qc.outputPermutation, dd); @@ -56,7 +56,7 @@ MatrixDD buildFunctionalityRecursive(const QuantumComputation& qc, auto permutation = qc.initialLayout; if (qc.size() == 1U) { - auto e = getDD(qc.front().get(), dd, permutation); + auto e = getDD(*qc.front(), dd, permutation); dd.incRef(e); return e; } @@ -89,7 +89,7 @@ bool buildFunctionalityRecursive(const QuantumComputation& qc, const auto& targets = op->getTargets(); std::swap(permutation.at(targets[0U]), permutation.at(targets[1U])); } else { - e = getDD(qc.at(opIdx).get(), dd, permutation); + e = getDD(*qc.at(opIdx), dd, permutation); } ++opIdx; if (opIdx == qc.size()) { @@ -104,7 +104,7 @@ bool buildFunctionalityRecursive(const QuantumComputation& qc, const auto& targets = op->getTargets(); std::swap(permutation.at(targets[0U]), permutation.at(targets[1U])); } else { - f = getDD(qc.at(opIdx).get(), dd, permutation); + f = getDD(*qc.at(opIdx), dd, permutation); } s.push(dd.multiply(f, e)); // ! reverse multiplication dd.incRef(s.top()); diff --git a/src/dd/Operations.cpp b/src/dd/Operations.cpp index 3c88c1012..d4d4db31a 100644 --- a/src/dd/Operations.cpp +++ b/src/dd/Operations.cpp @@ -107,7 +107,7 @@ void dumpTensor(qc::Operation* op, std::ostream& of, auto localOp = op->clone(); localOp->setControls(localControls); localOp->setTargets(localTargets); - const auto localDD = getDD(localOp.get(), dd); + const auto localDD = getDD(*localOp, dd); // translate local DD to matrix const auto localMatrix = localDD.getMatrix(localQubits); diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp index e8d21a9e3..30c7289bf 100644 --- a/src/dd/Simulation.cpp +++ b/src/dd/Simulation.cpp @@ -106,7 +106,7 @@ sample(const QuantumComputation& qc, const VectorDD& in, Package& dd, continue; } - e = applyUnitaryOperation(op.get(), e, dd, permutation); + e = applyUnitaryOperation(*op, e, dd, permutation); } // correct permutation if necessary @@ -165,22 +165,26 @@ sample(const QuantumComputation& qc, const VectorDD& in, Package& dd, continue; } - e = applyUnitaryOperation(op.get(), e, dd, permutation); + e = applyUnitaryOperation(*op, e, dd, permutation); continue; } if (op->getType() == Measure) { - e = applyMeasurement(op.get(), e, dd, mt, measurements, permutation); + const auto& measure = dynamic_cast(*op); + e = applyMeasurement(measure, e, dd, mt, measurements, permutation); continue; } if (op->getType() == Reset) { - e = applyReset(op.get(), e, dd, mt, permutation); + const auto& reset = dynamic_cast(*op); + e = applyReset(reset, e, dd, mt, permutation); continue; } if (op->isClassicControlledOperation()) { - e = applyClassicControlledOperation(op.get(), e, dd, measurements, + const auto& classic = + dynamic_cast(*op); + e = applyClassicControlledOperation(classic, e, dd, measurements, permutation); continue; } @@ -240,16 +244,16 @@ void extractProbabilityVectorRecursive(const QuantumComputation& qc, std::swap(permutation.at(targets[0U]), permutation.at(targets[1U])); continue; } - state = applyUnitaryOperation(op.get(), state, dd, permutation); + state = applyUnitaryOperation(*op, state, dd, permutation); continue; } // check whether a classic controlled operations can be applied if (op->isClassicControlledOperation()) { - const auto* classicControlled = - dynamic_cast(op.get()); - const auto& [regStart, regSize] = classicControlled->getControlRegister(); - const auto& expectedValue = classicControlled->getExpectedValue(); + const auto& classicControlled = + dynamic_cast(*op); + const auto& [regStart, regSize] = classicControlled.getControlRegister(); + const auto& expectedValue = classicControlled.getExpectedValue(); qc::Bit actualValue = 0U; // determine the actual value from measurements for (std::size_t j = 0; j < regSize; ++j) { @@ -263,7 +267,7 @@ void extractProbabilityVectorRecursive(const QuantumComputation& qc, continue; } - state = applyUnitaryOperation(classicControlled->getOperation(), state, + state = applyUnitaryOperation(*classicControlled.getOperation(), state, dd, permutation); continue; } @@ -307,10 +311,9 @@ void extractProbabilityVectorRecursive(const QuantumComputation& qc, // measurements form splitting points in this extraction scheme if (op->getType() == qc::Measure) { - const auto* measurement = - dynamic_cast(op.get()); - const auto& targets = measurement->getTargets(); - const auto& classics = measurement->getClassics(); + const auto& measurement = dynamic_cast(*op); + const auto& targets = measurement.getTargets(); + const auto& classics = measurement.getClassics(); if (targets.size() != 1U || classics.size() != 1U) { throw qc::QFRException( "Measurements on multiple qubits are not supported right now. " diff --git a/test/algorithms/eval_dynamic_circuits.cpp b/test/algorithms/eval_dynamic_circuits.cpp index 30929d4e0..c90872f9d 100644 --- a/test/algorithms/eval_dynamic_circuits.cpp +++ b/test/algorithms/eval_dynamic_circuits.cpp @@ -134,8 +134,8 @@ TEST_P(DynamicCircuitEvalExactQPE, UnitaryTransformation) { auto rightIt = iqpe.begin(); while (leftIt != qpe.end() && rightIt != iqpe.end()) { - auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); - auto multRight = dd->multiply(multLeft, getInverseDD(rightIt->get(), *dd)); + auto multLeft = dd->multiply(getDD(**leftIt, *dd), e); + auto multRight = dd->multiply(multLeft, getInverseDD(**rightIt, *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -147,7 +147,7 @@ TEST_P(DynamicCircuitEvalExactQPE, UnitaryTransformation) { } while (leftIt != qpe.end()) { - auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); + auto multLeft = dd->multiply(getDD(**leftIt, *dd), e); dd->incRef(multLeft); dd->decRef(e); e = multLeft; @@ -158,7 +158,7 @@ TEST_P(DynamicCircuitEvalExactQPE, UnitaryTransformation) { } while (rightIt != iqpe.end()) { - auto multRight = dd->multiply(e, getInverseDD(rightIt->get(), *dd)); + auto multRight = dd->multiply(e, getInverseDD(**rightIt, *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -338,8 +338,8 @@ TEST_P(DynamicCircuitEvalInexactQPE, UnitaryTransformation) { auto rightIt = iqpe.begin(); while (leftIt != qpe.end() && rightIt != iqpe.end()) { - auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); - auto multRight = dd->multiply(multLeft, getInverseDD(rightIt->get(), *dd)); + auto multLeft = dd->multiply(getDD(**leftIt, *dd), e); + auto multRight = dd->multiply(multLeft, getInverseDD(**rightIt, *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -351,7 +351,7 @@ TEST_P(DynamicCircuitEvalInexactQPE, UnitaryTransformation) { } while (leftIt != qpe.end()) { - auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); + auto multLeft = dd->multiply(getDD(**leftIt, *dd), e); dd->incRef(multLeft); dd->decRef(e); e = multLeft; @@ -362,7 +362,7 @@ TEST_P(DynamicCircuitEvalInexactQPE, UnitaryTransformation) { } while (rightIt != iqpe.end()) { - auto multRight = dd->multiply(e, getInverseDD(rightIt->get(), *dd)); + auto multRight = dd->multiply(e, getInverseDD(**rightIt, *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -488,8 +488,8 @@ TEST_P(DynamicCircuitEvalBV, UnitaryTransformation) { auto rightIt = dbv.begin(); while (leftIt != bv.end() && rightIt != dbv.end()) { - auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); - auto multRight = dd->multiply(multLeft, getInverseDD(rightIt->get(), *dd)); + auto multLeft = dd->multiply(getDD(**leftIt, *dd), e); + auto multRight = dd->multiply(multLeft, getInverseDD(**rightIt, *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -501,7 +501,7 @@ TEST_P(DynamicCircuitEvalBV, UnitaryTransformation) { } while (leftIt != bv.end()) { - auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); + auto multLeft = dd->multiply(getDD(**leftIt, *dd), e); dd->incRef(multLeft); dd->decRef(e); e = multLeft; @@ -512,7 +512,7 @@ TEST_P(DynamicCircuitEvalBV, UnitaryTransformation) { } while (rightIt != dbv.end()) { - auto multRight = dd->multiply(e, getInverseDD(rightIt->get(), *dd)); + auto multRight = dd->multiply(e, getInverseDD(**rightIt, *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -632,8 +632,8 @@ TEST_P(DynamicCircuitEvalQFT, UnitaryTransformation) { auto rightIt = dqft.begin(); while (leftIt != qft.end() && rightIt != dqft.end()) { - auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); - auto multRight = dd->multiply(multLeft, getInverseDD(rightIt->get(), *dd)); + auto multLeft = dd->multiply(getDD(**leftIt, *dd), e); + auto multRight = dd->multiply(multLeft, getInverseDD(**rightIt, *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; @@ -645,7 +645,7 @@ TEST_P(DynamicCircuitEvalQFT, UnitaryTransformation) { } while (leftIt != qft.end()) { - auto multLeft = dd->multiply(getDD(leftIt->get(), *dd), e); + auto multLeft = dd->multiply(getDD(**leftIt, *dd), e); dd->incRef(multLeft); dd->decRef(e); e = multLeft; @@ -656,7 +656,7 @@ TEST_P(DynamicCircuitEvalQFT, UnitaryTransformation) { } while (rightIt != dqft.end()) { - auto multRight = dd->multiply(e, getInverseDD(rightIt->get(), *dd)); + auto multRight = dd->multiply(e, getInverseDD(**rightIt, *dd)); dd->incRef(multRight); dd->decRef(e); e = multRight; diff --git a/test/dd/test_dd_functionality.cpp b/test/dd/test_dd_functionality.cpp index 2b18fe9ff..25d5a636a 100644 --- a/test/dd/test_dd_functionality.cpp +++ b/test/dd/test_dd_functionality.cpp @@ -135,8 +135,7 @@ TEST_P(DDFunctionality, standardOpBuildInverseBuild) { op = qc::StandardOperation(0, gate); } - ASSERT_NO_THROW( - { e = dd->multiply(getDD(&op, *dd), getInverseDD(&op, *dd)); }); + ASSERT_NO_THROW({ e = dd->multiply(getDD(op, *dd), getInverseDD(op, *dd)); }); dd->incRef(e); EXPECT_EQ(ident, e); @@ -190,8 +189,7 @@ TEST_P(DDFunctionality, controlledStandardOpBuildInverseBuild) { op = qc::StandardOperation(0, 1, gate); } - ASSERT_NO_THROW( - { e = dd->multiply(getDD(&op, *dd), getInverseDD(&op, *dd)); }); + ASSERT_NO_THROW({ e = dd->multiply(getDD(op, *dd), getInverseDD(op, *dd)); }); dd->incRef(e); EXPECT_EQ(ident, e); @@ -247,8 +245,7 @@ TEST_P(DDFunctionality, controlledStandardNegOpBuildInverseBuild) { op = qc::StandardOperation(Controls{0_nc}, 1, gate); } - ASSERT_NO_THROW( - { e = dd->multiply(getDD(&op, *dd), getInverseDD(&op, *dd)); }); + ASSERT_NO_THROW({ e = dd->multiply(getDD(op, *dd), getInverseDD(op, *dd)); }); dd->incRef(e); EXPECT_EQ(ident, e); @@ -338,10 +335,10 @@ TEST_F(DDFunctionality, nonUnitary) { auto dummyMap = Permutation{}; auto op = qc::NonUnitaryOperation({0, 1, 2, 3}, {0, 1, 2, 3}); EXPECT_FALSE(op.isUnitary()); - EXPECT_THROW(getDD(&op, *dd), qc::QFRException); - EXPECT_THROW(getInverseDD(&op, *dd), qc::QFRException); - EXPECT_THROW(getDD(&op, *dd, dummyMap), qc::QFRException); - EXPECT_THROW(getInverseDD(&op, *dd, dummyMap), qc::QFRException); + EXPECT_THROW(getDD(op, *dd), qc::QFRException); + EXPECT_THROW(getInverseDD(op, *dd), qc::QFRException); + EXPECT_THROW(getDD(op, *dd, dummyMap), qc::QFRException); + EXPECT_THROW(getInverseDD(op, *dd, dummyMap), qc::QFRException); for (Qubit i = 0; i < nqubits; ++i) { EXPECT_TRUE(op.actsOn(i)); } @@ -350,10 +347,10 @@ TEST_F(DDFunctionality, nonUnitary) { dummyMap[i] = i; } auto barrier = qc::StandardOperation({0, 1, 2, 3}, qc::OpType::Barrier); - EXPECT_TRUE(getDD(&barrier, *dd).isIdentity()); - EXPECT_TRUE(getInverseDD(&barrier, *dd).isIdentity()); - EXPECT_TRUE(getDD(&barrier, *dd, dummyMap).isIdentity()); - EXPECT_TRUE(getInverseDD(&barrier, *dd, dummyMap).isIdentity()); + EXPECT_TRUE(getDD(barrier, *dd).isIdentity()); + EXPECT_TRUE(getInverseDD(barrier, *dd).isIdentity()); + EXPECT_TRUE(getDD(barrier, *dd, dummyMap).isIdentity()); + EXPECT_TRUE(getInverseDD(barrier, *dd, dummyMap).isIdentity()); } TEST_F(DDFunctionality, CircuitEquivalence) { diff --git a/test/dd/test_dd_noise_functionality.cpp b/test/dd/test_dd_noise_functionality.cpp index cf557d244..5e879cb14 100644 --- a/test/dd/test_dd_noise_functionality.cpp +++ b/test/dd/test_dd_noise_functionality.cpp @@ -93,7 +93,7 @@ TEST_F(DDNoiseFunctionalityTest, DetSimulateAdder4TrackAPD) { dd, qc.getNqubits(), 0.01, 0.02, 0.02, 0.04, noiseEffects); for (auto const& op : qc) { - dd->applyOperationToDensity(rootEdge, dd::getDD(op.get(), *dd)); + dd->applyOperationToDensity(rootEdge, dd::getDD(*op, *dd)); deterministicNoiseFunctionality.applyNoiseEffects(rootEdge, op); } @@ -124,7 +124,7 @@ TEST_F(DDNoiseFunctionalityTest, DetSimulateAdder4TrackD) { dd, qc.getNqubits(), 0.01, 0.02, 0.02, 0.04, noiseEffects); for (auto const& op : qc) { - dd->applyOperationToDensity(rootEdge, dd::getDD(op.get(), *dd)); + dd->applyOperationToDensity(rootEdge, dd::getDD(*op, *dd)); deterministicNoiseFunctionality.applyNoiseEffects(rootEdge, op); } @@ -153,7 +153,7 @@ TEST_F(DDNoiseFunctionalityTest, testingMeasure) { dd, qcOp.getNqubits(), 0.01, 0.02, 0.02, 0.04, {}); for (auto const& op : qcOp) { - dd->applyOperationToDensity(rootEdge, dd::getDD(op.get(), *dd)); + dd->applyOperationToDensity(rootEdge, dd::getDD(*op, *dd)); deterministicNoiseFunctionality.applyNoiseEffects(rootEdge, op); } @@ -216,7 +216,7 @@ TEST_F(DDNoiseFunctionalityTest, StochSimulateAdder4TrackAPD) { dd->incRef(rootEdge); for (auto const& op : qc) { - auto operation = dd::getDD(op.get(), *dd); + auto operation = dd::getDD(*op, *dd); auto usedQubits = op->getUsedQubits(); stochasticNoiseFunctionality.applyNoiseOperation( usedQubits, operation, rootEdge, qc.getGenerator()); @@ -268,7 +268,7 @@ TEST_F(DDNoiseFunctionalityTest, StochSimulateAdder4IdentityError) { dd->incRef(rootEdge); for (auto const& op : qc) { - auto operation = dd::getDD(op.get(), *dd); + auto operation = dd::getDD(*op, *dd); auto usedQubits = op->getUsedQubits(); stochasticNoiseFunctionality.applyNoiseOperation( op->getUsedQubits(), operation, rootEdge, qc.getGenerator()); From 6d222da905eb5194e336aa9c80226e92154b44cb Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Jan 2025 16:52:55 +0100 Subject: [PATCH 05/11] =?UTF-8?q?=F0=9F=94=A5=20removing=20tensor=20dump?= =?UTF-8?q?=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is only used in one part of DDSIM that is scheduled for removal. And removed code is debugged code. Signed-off-by: burgholzer --- .../mqt-core/dd/FunctionalityConstruction.hpp | 17 -- include/mqt-core/dd/Operations.hpp | 5 - src/dd/Operations.cpp | 160 ------------------ test/dd/test_dd_functionality.cpp | 62 ------- 4 files changed, 244 deletions(-) delete mode 100644 src/dd/Operations.cpp diff --git a/include/mqt-core/dd/FunctionalityConstruction.hpp b/include/mqt-core/dd/FunctionalityConstruction.hpp index 4208872f3..94d020230 100644 --- a/include/mqt-core/dd/FunctionalityConstruction.hpp +++ b/include/mqt-core/dd/FunctionalityConstruction.hpp @@ -37,21 +37,4 @@ bool buildFunctionalityRecursive(const QuantumComputation& qc, std::stack& s, Permutation& permutation, Package& dd); -inline void dumpTensorNetwork(std::ostream& of, const QuantumComputation& qc) { - of << "{\"tensors\": [\n"; - - // initialize an index for every qubit - auto inds = std::vector(qc.getNqubits(), 0U); - std::size_t gateIdx = 0U; - auto dd = std::make_unique>(qc.getNqubits()); - for (const auto& op : qc) { - const auto type = op->getType(); - if (op != qc.front() && (type != Measure && type != Barrier)) { - of << ",\n"; - } - dumpTensor(op.get(), of, inds, gateIdx, *dd); - } - of << "\n]}\n"; -} - } // namespace dd diff --git a/include/mqt-core/dd/Operations.hpp b/include/mqt-core/dd/Operations.hpp index ead9c48f2..e3fe3d275 100644 --- a/include/mqt-core/dd/Operations.hpp +++ b/include/mqt-core/dd/Operations.hpp @@ -340,11 +340,6 @@ applyClassicControlledOperation(const qc::ClassicControlledOperation& op, return applyUnitaryOperation(op, in, dd, permutation); } -template -void dumpTensor(qc::Operation* op, std::ostream& of, - std::vector& inds, std::size_t& gateIdx, - Package& dd); - // apply swaps 'on' DD in order to change 'from' to 'to' // where |from| >= |to| template diff --git a/src/dd/Operations.cpp b/src/dd/Operations.cpp deleted file mode 100644 index d4d4db31a..000000000 --- a/src/dd/Operations.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2025 Chair for Design Automation, TUM - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "dd/Operations.hpp" - -#include "Definitions.hpp" -#include "dd/DDDefinitions.hpp" -#include "dd/Package.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace dd { -template -void dumpTensor(qc::Operation* op, std::ostream& of, - std::vector& inds, size_t& gateIdx, - Package& dd) { - const auto type = op->getType(); - if (op->isStandardOperation()) { - const auto& controls = op->getControls(); - const auto& targets = op->getTargets(); - - // start of tensor - of << "["; - - // save tags including operation type, involved qubits, and gate index - of << "[\"" << op->getName() << "\", "; - - // obtain an ordered map of involved qubits and add corresponding tags - std::map> orderedQubits{}; - for (const auto& control : controls) { - orderedQubits.emplace(control.qubit, control); - of << "\"Q" << control.qubit << "\", "; - } - for (const auto& target : targets) { - orderedQubits.emplace(target, target); - of << "\"Q" << target << "\", "; - } - of << "\"GATE" << gateIdx << "\"], "; - ++gateIdx; - - // generate indices - // in order to conform to the DD variable ordering that later provides the - // tensor data the ordered map has to be traversed in reverse order in order - // to correctly determine the indices - std::stringstream ssIn{}; - std::stringstream ssOut{}; - auto iter = orderedQubits.rbegin(); - auto qubit = iter->first; - auto& idx = inds[qubit]; - ssIn << "\"q" << qubit << "_" << idx << "\""; - ++idx; - ssOut << "\"q" << qubit << "_" << idx << "\""; - ++iter; - while (iter != orderedQubits.rend()) { - qubit = iter->first; - auto& ind = inds[qubit]; - ssIn << ", \"q" << qubit << "_" << ind << "\""; - ++ind; - ssOut << ", \"q" << qubit << "_" << ind << "\""; - ++iter; - } - of << "[" << ssIn.str() << ", " << ssOut.str() << "], "; - - // write tensor dimensions - const std::size_t localQubits = targets.size() + controls.size(); - of << "["; - for (std::size_t q = 0U; q < localQubits; ++q) { - if (q != 0U) { - of << ", "; - } - of << 2 << ", " << 2; - } - of << "], "; - - // obtain a local representation of the underlying operation - qc::Qubit localIdx = 0; - qc::Controls localControls{}; - qc::Targets localTargets{}; - for (const auto& [q, var] : orderedQubits) { - if (std::holds_alternative(var)) { - localTargets.emplace_back(localIdx); - } else { - const auto* control = std::get_if(&var); - localControls.emplace(localIdx, control->type); - } - ++localIdx; - } - - // get DD for local operation - auto localOp = op->clone(); - localOp->setControls(localControls); - localOp->setTargets(localTargets); - const auto localDD = getDD(*localOp, dd); - - // translate local DD to matrix - const auto localMatrix = localDD.getMatrix(localQubits); - - // set appropriate precision for dumping numbers - const auto precision = of.precision(); - of.precision(std::numeric_limits::max_digits10); - - // write tensor data - of << "["; - for (std::size_t row = 0U; row < localMatrix.size(); ++row) { - const auto& r = localMatrix[row]; - for (std::size_t col = 0U; col < r.size(); ++col) { - if (row != 0U || col != 0U) { - of << ", "; - } - - const auto& elem = r[col]; - of << "[" << elem.real() << ", " << elem.imag() << "]"; - } - } - of << "]"; - - // restore old precision - of.precision(precision); - - // end of tensor - of << "]"; - } else if (auto* compoundOp = dynamic_cast(op)) { - for (const auto& operation : *compoundOp) { - if (operation != (*compoundOp->begin())) { - of << ",\n"; - } - dumpTensor(operation.get(), of, inds, gateIdx, dd); - } - } else if (type == qc::Barrier) { - return; - } else if (type == qc::Measure) { - std::clog << "Skipping measurement in tensor dump.\n"; - } else { - throw qc::QFRException("Dumping of tensors is currently only supported for " - "StandardOperations."); - } -} - -template void dumpTensor(qc::Operation* op, std::ostream& of, - std::vector& inds, - size_t& gateIdx, - Package& dd); -} // namespace dd diff --git a/test/dd/test_dd_functionality.cpp b/test/dd/test_dd_functionality.cpp index 25d5a636a..12d6b09b4 100644 --- a/test/dd/test_dd_functionality.cpp +++ b/test/dd/test_dd_functionality.cpp @@ -393,68 +393,6 @@ TEST_F(DDFunctionality, changePermutation) { EXPECT_TRUE(func.p->e[3].p->e[2].w.exactlyOne()); } -TEST_F(DDFunctionality, basicTensorDumpTest) { - QuantumComputation qc(2); - qc.h(1); - qc.cx(1, 0); - - std::stringstream ss{}; - dd::dumpTensorNetwork(ss, qc); - - const std::string reference = - "{\"tensors\": [\n" - "[[\"h\", \"Q1\", \"GATE0\"], [\"q1_0\", \"q1_1\"], [2, 2], " - "[[0.70710678118654757, 0], [0.70710678118654757, 0], " - "[0.70710678118654757, 0], [-0.70710678118654757, 0]]],\n" - "[[\"x\", \"Q1\", \"Q0\", \"GATE1\"], [\"q1_1\", \"q0_0\", \"q1_2\", " - "\"q0_1\"], [2, 2, 2, 2], [[1, 0], [0, 0], [0, 0], [0, 0], [0, 0], [1, " - "0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [1, 0], [0, 0], [0, 0], [1, " - "0], [0, 0]]]\n" - "]}\n"; - EXPECT_EQ(ss.str(), reference); -} - -TEST_F(DDFunctionality, compoundTensorDumpTest) { - QuantumComputation qc(2); - QuantumComputation comp(2); - comp.h(1); - comp.cx(1, 0); - qc.emplace_back(comp.asOperation()); - - std::stringstream ss{}; - dd::dumpTensorNetwork(ss, qc); - - const std::string reference = - "{\"tensors\": [\n" - "[[\"h\", \"Q1\", \"GATE0\"], [\"q1_0\", \"q1_1\"], [2, 2], " - "[[0.70710678118654757, 0], [0.70710678118654757, 0], " - "[0.70710678118654757, 0], [-0.70710678118654757, 0]]],\n" - "[[\"x\", \"Q1\", \"Q0\", \"GATE1\"], [\"q1_1\", \"q0_0\", \"q1_2\", " - "\"q0_1\"], [2, 2, 2, 2], [[1, 0], [0, 0], [0, 0], [0, 0], [0, 0], [1, " - "0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [1, 0], [0, 0], [0, 0], [1, " - "0], [0, 0]]]\n" - "]}\n"; - EXPECT_EQ(ss.str(), reference); -} - -TEST_F(DDFunctionality, errorTensorDumpTest) { - QuantumComputation qc(2U, 2U); - qc.classicControlled(qc::X, 0, {0, 1U}, 1U); - - std::stringstream ss{}; - EXPECT_THROW(dd::dumpTensorNetwork(ss, qc), qc::QFRException); - - ss.str(""); - qc.erase(qc.begin()); - qc.barrier(0); - qc.measure(0, 0); - EXPECT_NO_THROW(dd::dumpTensorNetwork(ss, qc)); - - ss.str(""); - qc.reset(0); - EXPECT_THROW(dd::dumpTensorNetwork(ss, qc), qc::QFRException); -} - TEST_F(DDFunctionality, FuseTwoSingleQubitGates) { nqubits = 1; QuantumComputation qc(nqubits); From 9ba2dbe27999c21d60b101a41faf2e1cf8b9da23 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Jan 2025 18:17:50 +0100 Subject: [PATCH 06/11] =?UTF-8?q?=E2=9A=A1=20enable=20LTO=20by=20default?= =?UTF-8?q?=20when=20not=20deploying?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- cmake/StandardProjectSettings.cmake | 32 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake index 088d19f96..496eedba2 100644 --- a/cmake/StandardProjectSettings.cmake +++ b/cmake/StandardProjectSettings.cmake @@ -28,20 +28,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Export compile commands" FORCE) -option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) -if(ENABLE_IPO) - include(CheckIPOSupported) - check_ipo_supported(RESULT ipo_supported OUTPUT ipo_output) - # enable inter-procedural optimization if it is supported - if(ipo_supported) - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION - TRUE - CACHE BOOL "Enable Interprocedural Optimization" FORCE) - else() - message(DEBUG "IPO is not supported: ${ipo_output}") - endif() -endif() - if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") add_compile_options(-fcolor-diagnostics) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -70,3 +56,21 @@ if(DEPLOY) "10.15" CACHE STRING "" FORCE) endif() + +if(NOT DEPLOY) + option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" ON) +else() + option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) +endif() +if(ENABLE_IPO) + include(CheckIPOSupported) + check_ipo_supported(RESULT ipo_supported OUTPUT ipo_output) + # enable inter-procedural optimization if it is supported + if(ipo_supported) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION + TRUE + CACHE BOOL "Enable Interprocedural Optimization" FORCE) + else() + message(DEBUG "IPO is not supported: ${ipo_output}") + endif() +endif() From 61abcad31ad54e2d1d23e8a1280ff5600ce71b33 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 10 Jan 2025 11:36:28 +0100 Subject: [PATCH 07/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20make=20`ancillary`?= =?UTF-8?q?=20and=20`garbage`=20protected=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit these members had getters since a while back. there is no need to keep them as public members. Signed-off-by: burgholzer --- include/mqt-core/dd/Simulation.hpp | 2 +- include/mqt-core/ir/QuantumComputation.hpp | 6 +++--- src/dd/FunctionalityConstruction.cpp | 10 +++++----- src/dd/Simulation.cpp | 2 +- src/ir/QuantumComputation.cpp | 8 ++++++++ src/python/ir/register_quantum_computation.cpp | 4 ++-- test/ir/test_qfr_functionality.cpp | 12 ++++++------ 7 files changed, 26 insertions(+), 18 deletions(-) diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp index 588347fcb..58742ecb9 100644 --- a/include/mqt-core/dd/Simulation.hpp +++ b/include/mqt-core/dd/Simulation.hpp @@ -39,7 +39,7 @@ VectorDD simulate(const QuantumComputation& qc, const VectorDD& in, e = applyUnitaryOperation(*op, e, dd, permutation); } changePermutation(e, permutation, qc.outputPermutation, dd); - e = dd.reduceGarbage(e, qc.garbage); + e = dd.reduceGarbage(e, qc.getGarbage()); return e; } diff --git a/include/mqt-core/ir/QuantumComputation.hpp b/include/mqt-core/ir/QuantumComputation.hpp index 0a4b6b1cb..f2a1301c4 100644 --- a/include/mqt-core/ir/QuantumComputation.hpp +++ b/include/mqt-core/ir/QuantumComputation.hpp @@ -52,6 +52,9 @@ class QuantumComputation { ClassicalRegisterMap cregs; QuantumRegisterMap ancregs; + std::vector ancillary; + std::vector garbage; + std::mt19937_64 mt; std::size_t seed = 0; @@ -134,9 +137,6 @@ class QuantumComputation { void setName(const std::string& n) noexcept { name = n; } - std::vector ancillary; - std::vector garbage; - [[nodiscard]] std::size_t getNindividualOps() const; [[nodiscard]] std::size_t getNsingleQubitOps() const; [[nodiscard]] std::size_t getDepth() const; diff --git a/src/dd/FunctionalityConstruction.cpp b/src/dd/FunctionalityConstruction.cpp index e5ea94506..484592260 100644 --- a/src/dd/FunctionalityConstruction.cpp +++ b/src/dd/FunctionalityConstruction.cpp @@ -26,7 +26,7 @@ MatrixDD buildFunctionality(const QuantumComputation& qc, Package& dd) { } auto permutation = qc.initialLayout; - auto e = dd.createInitialMatrix(qc.ancillary); + auto e = dd.createInitialMatrix(qc.getAncillary()); for (const auto& op : qc) { // SWAP gates can be executed virtually by changing the permutation @@ -40,8 +40,8 @@ MatrixDD buildFunctionality(const QuantumComputation& qc, Package& dd) { } // correct permutation if necessary changePermutation(e, permutation, qc.outputPermutation, dd); - e = dd.reduceAncillae(e, qc.ancillary); - e = dd.reduceGarbage(e, qc.garbage); + e = dd.reduceAncillae(e, qc.getAncillary()); + e = dd.reduceGarbage(e, qc.getGarbage()); return e; } @@ -69,8 +69,8 @@ MatrixDD buildFunctionalityRecursive(const QuantumComputation& qc, // correct permutation if necessary changePermutation(e, permutation, qc.outputPermutation, dd); - e = dd.reduceAncillae(e, qc.ancillary); - e = dd.reduceGarbage(e, qc.garbage); + e = dd.reduceAncillae(e, qc.getAncillary()); + e = dd.reduceGarbage(e, qc.getGarbage()); return e; } diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp index 30c7289bf..2f89478f7 100644 --- a/src/dd/Simulation.cpp +++ b/src/dd/Simulation.cpp @@ -111,7 +111,7 @@ sample(const QuantumComputation& qc, const VectorDD& in, Package& dd, // correct permutation if necessary changePermutation(e, permutation, qc.outputPermutation, dd); - e = dd.reduceGarbage(e, qc.garbage); + e = dd.reduceGarbage(e, qc.getGarbage()); // measure all qubits std::map counts{}; diff --git a/src/ir/QuantumComputation.cpp b/src/ir/QuantumComputation.cpp index be4a35c88..b75737a8a 100644 --- a/src/ir/QuantumComputation.cpp +++ b/src/ir/QuantumComputation.cpp @@ -1485,6 +1485,14 @@ QuantumComputation::fromCompoundOperation(const CompoundOperation& op) { return qc; } +std::size_t QuantumComputation::getNmeasuredQubits() const noexcept { + return getNqubits() - getNgarbageQubits(); +} +std::size_t QuantumComputation::getNgarbageQubits() const { + return static_cast( + std::count(getGarbage().cbegin(), getGarbage().cend(), true)); +} + ///--------------------------------------------------------------------------- /// \n Operations \n ///--------------------------------------------------------------------------- diff --git a/src/python/ir/register_quantum_computation.cpp b/src/python/ir/register_quantum_computation.cpp index 774e59c6a..cb4cec072 100644 --- a/src/python/ir/register_quantum_computation.cpp +++ b/src/python/ir/register_quantum_computation.cpp @@ -208,7 +208,7 @@ void registerQuantumComputation(py::module& m) { /// \n Ancillary and Garbage Handling \n ///--------------------------------------------------------------------------- - qc.def_readonly("ancillary", &qc::QuantumComputation::ancillary); + qc.def_property_readonly("ancillary", &qc::QuantumComputation::getAncillary); qc.def("set_circuit_qubit_ancillary", &qc::QuantumComputation::setLogicalQubitAncillary, "q"_a); qc.def("se_circuit_qubits_ancillary", @@ -216,7 +216,7 @@ void registerQuantumComputation(py::module& m) { "q_max"_a); qc.def("is_circuit_qubit_ancillary", &qc::QuantumComputation::logicalQubitIsAncillary, "q"_a); - qc.def_readonly("garbage", &qc::QuantumComputation::garbage); + qc.def_property_readonly("garbage", &qc::QuantumComputation::getGarbage); qc.def("set_circuit_qubit_garbage", &qc::QuantumComputation::setLogicalQubitGarbage, "q"_a); qc.def("set_circuit_qubits_garbage", diff --git a/test/ir/test_qfr_functionality.cpp b/test/ir/test_qfr_functionality.cpp index a7f2e4946..c0652c496 100644 --- a/test/ir/test_qfr_functionality.cpp +++ b/test/ir/test_qfr_functionality.cpp @@ -468,12 +468,12 @@ TEST_F(QFRFunctionality, AddAncillaryQubits) { qc.addAncillaryQubit(1, std::nullopt); EXPECT_EQ(qc.getNqubits(), 2); EXPECT_EQ(qc.getNancillae(), 1); - ASSERT_EQ(qc.ancillary.size(), 2U); - ASSERT_EQ(qc.garbage.size(), 2U); - EXPECT_FALSE(qc.ancillary[0]); - EXPECT_TRUE(qc.ancillary[1]); - EXPECT_FALSE(qc.garbage[0]); - EXPECT_TRUE(qc.garbage[1]); + ASSERT_EQ(qc.getAncillary().size(), 2U); + ASSERT_EQ(qc.getGarbage().size(), 2U); + EXPECT_FALSE(qc.getAncillary()[0]); + EXPECT_TRUE(qc.getAncillary()[1]); + EXPECT_FALSE(qc.getGarbage()[0]); + EXPECT_TRUE(qc.getGarbage()[1]); } TEST_F(QFRFunctionality, CircuitDepthEmptyCircuit) { From 63d5a113e9c6f7efb462dfe7eb7a69fd8c6beb72 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 12 Jan 2025 17:41:31 +0100 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=9A=A8=20address=20linter=20warning?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- eval/eval_dd_package.cpp | 8 +++++--- include/mqt-core/algorithms/BernsteinVazirani.hpp | 2 ++ include/mqt-core/algorithms/QPE.hpp | 2 ++ include/mqt-core/dd/FunctionalityConstruction.hpp | 5 ----- include/mqt-core/ir/QuantumComputation.hpp | 3 +-- .../parsers/qasm3_parser/passes/ConstEvalPass.hpp | 2 +- src/algorithms/BernsteinVazirani.cpp | 2 ++ src/algorithms/QFT.cpp | 1 + src/algorithms/QPE.cpp | 2 ++ src/algorithms/RandomCliffordCircuit.cpp | 1 + src/dd/FunctionalityConstruction.cpp | 1 + src/ir/QuantumComputation.cpp | 13 ++++++------- src/ir/operations/AodOperation.cpp | 2 ++ src/ir/operations/ClassicControlledOperation.cpp | 6 ++++++ src/ir/operations/SymbolicOperation.cpp | 1 + .../parsers/qasm3_parser/passes/TypeCheckPass.cpp | 1 + test/algorithms/test_entanglement.cpp | 2 ++ test/algorithms/test_grover.cpp | 5 +++-- test/algorithms/test_qft.cpp | 2 ++ test/algorithms/test_qpe.cpp | 13 +++++++++++-- test/algorithms/test_random_clifford.cpp | 7 +++++-- test/dd/test_dd_functionality.cpp | 1 - test/ir/test_io.cpp | 2 ++ 23 files changed, 59 insertions(+), 25 deletions(-) diff --git a/eval/eval_dd_package.cpp b/eval/eval_dd_package.cpp index ed0f345a0..982b1c41e 100644 --- a/eval/eval_dd_package.cpp +++ b/eval/eval_dd_package.cpp @@ -7,6 +7,7 @@ * Licensed under the MIT License */ +#include "Definitions.hpp" #include "algorithms/BernsteinVazirani.hpp" #include "algorithms/GHZState.hpp" #include "algorithms/Grover.hpp" @@ -28,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -355,14 +355,16 @@ class BenchmarkDDPackage { constexpr std::array nqubitsSim = {14U, 15U, 16U, 17U, 18U}; std::cout << "Running RandomClifford Simulation..." << '\n'; for (const auto& nq : nqubitsSim) { - auto qc = createRandomCliffordCircuit(nq, nq * nq, SEED); + auto qc = + createRandomCliffordCircuit(nq, static_cast(nq) * nq, SEED); auto exp = benchmarkSimulate(qc); verifyAndSave("RandomClifford", "Simulation", qc, *exp); } std::cout << "Running RandomClifford Functionality..." << '\n'; constexpr std::array nqubitsFunc = {7U, 8U, 9U, 10U, 11U}; for (const auto& nq : nqubitsFunc) { - auto qc = createRandomCliffordCircuit(nq, nq * nq, SEED); + auto qc = + createRandomCliffordCircuit(nq, static_cast(nq) * nq, SEED); auto exp = benchmarkFunctionalityConstruction(qc); verifyAndSave("RandomClifford", "Functionality", qc, *exp); } diff --git a/include/mqt-core/algorithms/BernsteinVazirani.hpp b/include/mqt-core/algorithms/BernsteinVazirani.hpp index 1464b11ed..0fff3a68a 100644 --- a/include/mqt-core/algorithms/BernsteinVazirani.hpp +++ b/include/mqt-core/algorithms/BernsteinVazirani.hpp @@ -12,6 +12,8 @@ #include "Definitions.hpp" #include "ir/QuantumComputation.hpp" +#include + namespace qc { [[nodiscard]] auto createBernsteinVazirani(const BitString& hiddenString) diff --git a/include/mqt-core/algorithms/QPE.hpp b/include/mqt-core/algorithms/QPE.hpp index 95dfb4873..2ca3c50c2 100644 --- a/include/mqt-core/algorithms/QPE.hpp +++ b/include/mqt-core/algorithms/QPE.hpp @@ -12,6 +12,8 @@ #include "Definitions.hpp" #include "ir/QuantumComputation.hpp" +#include + namespace qc { [[nodiscard]] auto createQPE(Qubit nq, bool exact = true, std::size_t seed = 0) -> QuantumComputation; diff --git a/include/mqt-core/dd/FunctionalityConstruction.hpp b/include/mqt-core/dd/FunctionalityConstruction.hpp index 94d020230..5f7d1ed75 100644 --- a/include/mqt-core/dd/FunctionalityConstruction.hpp +++ b/include/mqt-core/dd/FunctionalityConstruction.hpp @@ -9,17 +9,12 @@ #pragma once -#include "dd/Operations.hpp" #include "dd/Package_fwd.hpp" #include "ir/Permutation.hpp" #include "ir/QuantumComputation.hpp" -#include "ir/operations/OpType.hpp" #include -#include -#include #include -#include namespace dd { using namespace qc; diff --git a/include/mqt-core/ir/QuantumComputation.hpp b/include/mqt-core/ir/QuantumComputation.hpp index f2a1301c4..1c8494e4a 100644 --- a/include/mqt-core/ir/QuantumComputation.hpp +++ b/include/mqt-core/ir/QuantumComputation.hpp @@ -10,6 +10,7 @@ #pragma once #include "Definitions.hpp" +#include "Permutation.hpp" #include "operations/ClassicControlledOperation.hpp" #include "operations/CompoundOperation.hpp" #include "operations/Control.hpp" @@ -429,8 +430,6 @@ class QuantumComputation { return qc.print(os); } - static void printBin(std::size_t n, std::stringstream& ss); - std::ostream& printStatistics(std::ostream& os) const; std::ostream& printRegisters(std::ostream& os = std::cout) const; diff --git a/include/mqt-core/ir/parsers/qasm3_parser/passes/ConstEvalPass.hpp b/include/mqt-core/ir/parsers/qasm3_parser/passes/ConstEvalPass.hpp index c7ef84386..30c67df18 100644 --- a/include/mqt-core/ir/parsers/qasm3_parser/passes/ConstEvalPass.hpp +++ b/include/mqt-core/ir/parsers/qasm3_parser/passes/ConstEvalPass.hpp @@ -49,7 +49,7 @@ struct ConstEvalValue { case ConstFloat: return std::make_shared(Constant(std::get<1>(value))); case ConstBool: - return std::make_shared(Constant(std::get<2>(value), false)); + return std::make_shared(Constant(std::get<2>(value))); default: qc::unreachable(); } diff --git a/src/algorithms/BernsteinVazirani.cpp b/src/algorithms/BernsteinVazirani.cpp index 74f30283e..7ad81d9ad 100644 --- a/src/algorithms/BernsteinVazirani.cpp +++ b/src/algorithms/BernsteinVazirani.cpp @@ -10,10 +10,12 @@ #include "algorithms/BernsteinVazirani.hpp" #include "Definitions.hpp" +#include "ir/QuantumComputation.hpp" #include #include #include +#include namespace qc { diff --git a/src/algorithms/QFT.cpp b/src/algorithms/QFT.cpp index 875b18fa7..714c2661a 100644 --- a/src/algorithms/QFT.cpp +++ b/src/algorithms/QFT.cpp @@ -10,6 +10,7 @@ #include "algorithms/QFT.hpp" #include "Definitions.hpp" +#include "ir/QuantumComputation.hpp" #include "ir/operations/ClassicControlledOperation.hpp" #include "ir/operations/OpType.hpp" diff --git a/src/algorithms/QPE.cpp b/src/algorithms/QPE.cpp index 1122ad782..f7ed5ef05 100644 --- a/src/algorithms/QPE.cpp +++ b/src/algorithms/QPE.cpp @@ -10,6 +10,7 @@ #include "algorithms/QPE.hpp" #include "Definitions.hpp" +#include "ir/QuantumComputation.hpp" #include "ir/operations/ClassicControlledOperation.hpp" #include "ir/operations/OpType.hpp" @@ -19,6 +20,7 @@ #include #include #include +#include namespace qc { diff --git a/src/algorithms/RandomCliffordCircuit.cpp b/src/algorithms/RandomCliffordCircuit.cpp index e61804876..109e48a67 100644 --- a/src/algorithms/RandomCliffordCircuit.cpp +++ b/src/algorithms/RandomCliffordCircuit.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace qc { diff --git a/src/dd/FunctionalityConstruction.cpp b/src/dd/FunctionalityConstruction.cpp index 484592260..b36aec620 100644 --- a/src/dd/FunctionalityConstruction.cpp +++ b/src/dd/FunctionalityConstruction.cpp @@ -9,6 +9,7 @@ #include "dd/FunctionalityConstruction.hpp" +#include "dd/Operations.hpp" #include "dd/Package.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/OpType.hpp" diff --git a/src/ir/QuantumComputation.cpp b/src/ir/QuantumComputation.cpp index b75737a8a..de39f38fb 100644 --- a/src/ir/QuantumComputation.cpp +++ b/src/ir/QuantumComputation.cpp @@ -10,6 +10,7 @@ #include "ir/QuantumComputation.hpp" #include "Definitions.hpp" +#include "ir/operations/ClassicControlledOperation.hpp" #include "ir/operations/CompoundOperation.hpp" #include "ir/operations/Control.hpp" #include "ir/operations/Expression.hpp" @@ -19,18 +20,23 @@ #include "ir/operations/SymbolicOperation.hpp" #include +#include #include #include #include #include +#include #include #include #include #include #include +#include #include +#include #include #include +#include #include #include #include @@ -671,13 +677,6 @@ std::ostream& QuantumComputation::print(std::ostream& os) const { return os; } -void QuantumComputation::printBin(std::size_t n, std::stringstream& ss) { - if (n > 1) { - printBin(n / 2, ss); - } - ss << n % 2; -} - std::ostream& QuantumComputation::printStatistics(std::ostream& os) const { os << "QC Statistics:"; os << "\n\tn: " << static_cast(nqubits); diff --git a/src/ir/operations/AodOperation.cpp b/src/ir/operations/AodOperation.cpp index fd98f44bb..04ed6824f 100644 --- a/src/ir/operations/AodOperation.cpp +++ b/src/ir/operations/AodOperation.cpp @@ -12,6 +12,7 @@ #include "Definitions.hpp" #include "ir/operations/OpType.hpp" +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/src/ir/operations/ClassicControlledOperation.cpp b/src/ir/operations/ClassicControlledOperation.cpp index c72bf63f0..460d433b0 100644 --- a/src/ir/operations/ClassicControlledOperation.cpp +++ b/src/ir/operations/ClassicControlledOperation.cpp @@ -10,9 +10,15 @@ #include "ir/operations/ClassicControlledOperation.hpp" #include "Definitions.hpp" +#include "ir/Permutation.hpp" +#include "ir/operations/OpType.hpp" +#include +#include +#include #include #include +#include namespace qc { diff --git a/src/ir/operations/SymbolicOperation.cpp b/src/ir/operations/SymbolicOperation.cpp index 6b5b7592b..ffeec16ad 100644 --- a/src/ir/operations/SymbolicOperation.cpp +++ b/src/ir/operations/SymbolicOperation.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/src/ir/parsers/qasm3_parser/passes/TypeCheckPass.cpp b/src/ir/parsers/qasm3_parser/passes/TypeCheckPass.cpp index 6a2ed5a44..7a8d4dc38 100644 --- a/src/ir/parsers/qasm3_parser/passes/TypeCheckPass.cpp +++ b/src/ir/parsers/qasm3_parser/passes/TypeCheckPass.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace qasm3::type_checking { diff --git a/test/algorithms/test_entanglement.cpp b/test/algorithms/test_entanglement.cpp index 642f0e6cd..6da81cc2d 100644 --- a/test/algorithms/test_entanglement.cpp +++ b/test/algorithms/test_entanglement.cpp @@ -15,7 +15,9 @@ #include "dd/Simulation.hpp" #include +#include #include +#include #include class Entanglement : public testing::TestWithParam { diff --git a/test/algorithms/test_grover.cpp b/test/algorithms/test_grover.cpp index 62a5c19e6..daa8665f7 100644 --- a/test/algorithms/test_grover.cpp +++ b/test/algorithms/test_grover.cpp @@ -7,6 +7,7 @@ * Licensed under the MIT License */ +#include "Definitions.hpp" #include "algorithms/Grover.hpp" #include "dd/DDDefinitions.hpp" #include "dd/FunctionalityConstruction.hpp" @@ -54,8 +55,8 @@ class Grover qc::QuantumComputation qc; qc::VectorDD sim{}; qc::MatrixDD func{}; - std::string expected{}; - qc::BitString targetValue{}; + std::string expected; + qc::BitString targetValue; }; constexpr qc::Qubit GROVER_MAX_QUBITS = 15; diff --git a/test/algorithms/test_qft.cpp b/test/algorithms/test_qft.cpp index aa763d8c0..0a068a6c2 100644 --- a/test/algorithms/test_qft.cpp +++ b/test/algorithms/test_qft.cpp @@ -7,12 +7,14 @@ * Licensed under the MIT License */ +#include "Definitions.hpp" #include "algorithms/QFT.hpp" #include "dd/DDDefinitions.hpp" #include "dd/FunctionalityConstruction.hpp" #include "dd/Package.hpp" #include "dd/RealNumber.hpp" #include "dd/Simulation.hpp" +#include "ir/QuantumComputation.hpp" #include #include diff --git a/test/algorithms/test_qpe.cpp b/test/algorithms/test_qpe.cpp index e6a966317..eadb04e89 100644 --- a/test/algorithms/test_qpe.cpp +++ b/test/algorithms/test_qpe.cpp @@ -272,6 +272,15 @@ TEST_P(QPE, DynamicEquivalenceFunctionality) { EXPECT_EQ(e, f); } +namespace { +void printBin(const std::size_t n, std::stringstream& ss) { + if (n > 1) { + printBin(n / 2, ss); + } + ss << n % 2; +} +} // namespace + TEST_P(QPE, ProbabilityExtraction) { auto dd = std::make_unique>(precision + 1); @@ -284,7 +293,7 @@ TEST_P(QPE, ProbabilityExtraction) { for (const auto& [state, prob] : probs) { std::stringstream ss{}; - qc::QuantumComputation::printBin(state, ss); + printBin(state, ss); std::cout << ss.str() << ": " << prob << "\n"; } @@ -325,7 +334,7 @@ TEST_P(QPE, DynamicEquivalenceSimulationProbabilityExtraction) { std::cout << "IQPE:\n"; for (const auto& [state, prob] : probs) { std::stringstream ss{}; - qc::QuantumComputation::printBin(state, ss); + printBin(state, ss); std::cout << ss.str() << ": " << prob << "\n"; } diff --git a/test/algorithms/test_random_clifford.cpp b/test/algorithms/test_random_clifford.cpp index 4d7c34d56..464845148 100644 --- a/test/algorithms/test_random_clifford.cpp +++ b/test/algorithms/test_random_clifford.cpp @@ -7,6 +7,7 @@ * Licensed under the MIT License */ +#include "Definitions.hpp" #include "algorithms/RandomCliffordCircuit.hpp" #include "dd/FunctionalityConstruction.hpp" #include "dd/Package.hpp" @@ -38,7 +39,8 @@ TEST_P(RandomClifford, simulate) { const auto nq = GetParam(); auto dd = std::make_unique>(nq); - auto qc = qc::createRandomCliffordCircuit(nq, nq * nq, 12345); + auto qc = qc::createRandomCliffordCircuit( + nq, static_cast(nq) * nq, 12345); auto in = dd->makeZeroState(nq); ASSERT_NO_THROW({ dd::simulate(qc, in, *dd); }); qc.printStatistics(std::cout); @@ -48,7 +50,8 @@ TEST_P(RandomClifford, buildFunctionality) { const auto nq = GetParam(); auto dd = std::make_unique>(nq); - auto qc = qc::createRandomCliffordCircuit(nq, nq * nq, 12345); + auto qc = qc::createRandomCliffordCircuit( + nq, static_cast(nq) * nq, 12345); ASSERT_NO_THROW({ dd::buildFunctionality(qc, *dd); }); qc.printStatistics(std::cout); } diff --git a/test/dd/test_dd_functionality.cpp b/test/dd/test_dd_functionality.cpp index 12d6b09b4..59e48c5e7 100644 --- a/test/dd/test_dd_functionality.cpp +++ b/test/dd/test_dd_functionality.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include diff --git a/test/ir/test_io.cpp b/test/ir/test_io.cpp index 37255a51a..3740ee7cc 100644 --- a/test/ir/test_io.cpp +++ b/test/ir/test_io.cpp @@ -47,6 +47,7 @@ class IO : public testing::TestWithParam> { std::unique_ptr qc; }; +namespace { void compareFiles(const std::string& file1, const std::string& file2, bool stripWhitespaces = false) { std::ifstream fstream1(file1); @@ -61,6 +62,7 @@ void compareFiles(const std::string& file1, const std::string& file2, } ASSERT_EQ(str1, str2); } +} // namespace INSTANTIATE_TEST_SUITE_P( IO, IO, From 451d064300b4d476060a3f8a65193a66f2f227c5 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 12 Jan 2025 18:32:14 +0100 Subject: [PATCH 09/11] =?UTF-8?q?=E2=9C=85=20add=20tests=20for=20register?= =?UTF-8?q?=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- test/ir/test_qfr_functionality.cpp | 161 +++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/test/ir/test_qfr_functionality.cpp b/test/ir/test_qfr_functionality.cpp index c0652c496..229fd5588 100644 --- a/test/ir/test_qfr_functionality.cpp +++ b/test/ir/test_qfr_functionality.cpp @@ -1173,3 +1173,164 @@ TEST_F(QFRFunctionality, NoRegisterOnEmptyCircuit) { EXPECT_NO_THROW(qc.addQubitRegister(1U, "q")); EXPECT_EQ(qc.getQregs().size(), 2U); } + +TEST_F(QFRFunctionality, AddQubitAtFrontOfRegister) { + QuantumComputation qc{}; + qc.addQubitRegister(2, "q"); + const auto& qregs = qc.getQregs(); + EXPECT_EQ(qregs.size(), 1U); + const auto& [name, reg] = *qregs.begin(); + const auto& [start, size] = reg; + EXPECT_EQ(name, "q"); + EXPECT_EQ(start, 0U); + EXPECT_EQ(size, 2U); + + // first remove the qubit + const auto& [physicalIndex, outputIndex] = qc.removeQubit(0); + EXPECT_EQ(physicalIndex, 0U); + EXPECT_EQ(outputIndex, 0U); + + const auto& qregsAfter = qc.getQregs(); + EXPECT_EQ(qregsAfter.size(), 1U); + const auto& [nameAfter, regAfter] = *qregsAfter.begin(); + const auto& [startAfter, sizeAfter] = regAfter; + EXPECT_EQ(nameAfter, "q"); + EXPECT_EQ(startAfter, 1U); + EXPECT_EQ(sizeAfter, 1U); + + // add the qubit back at the front + qc.addQubit(0, physicalIndex, outputIndex); + const auto& qregsAfterAdd = qc.getQregs(); + EXPECT_EQ(qregsAfterAdd.size(), 1U); + const auto& [nameAfterAdd, regAfterAdd] = *qregsAfterAdd.begin(); + const auto& [startAfterAdd, sizeAfterAdd] = regAfterAdd; + EXPECT_EQ(nameAfterAdd, "q"); + EXPECT_EQ(startAfterAdd, 0U); + EXPECT_EQ(sizeAfterAdd, 2U); +} + +TEST_F(QFRFunctionality, AddQubitAtEndOfRegister) { + QuantumComputation qc{}; + qc.addQubitRegister(2, "q"); + const auto& qregs = qc.getQregs(); + EXPECT_EQ(qregs.size(), 1U); + const auto& [name, reg] = *qregs.begin(); + const auto& [start, size] = reg; + EXPECT_EQ(name, "q"); + EXPECT_EQ(start, 0U); + EXPECT_EQ(size, 2U); + + // first remove the qubit + const auto& [physicalIndex, outputIndex] = qc.removeQubit(1); + EXPECT_EQ(physicalIndex, 1U); + EXPECT_EQ(outputIndex, 1U); + + const auto& qregsAfter = qc.getQregs(); + EXPECT_EQ(qregsAfter.size(), 1U); + const auto& [nameAfter, regAfter] = *qregsAfter.begin(); + const auto& [startAfter, sizeAfter] = regAfter; + EXPECT_EQ(nameAfter, "q"); + EXPECT_EQ(startAfter, 0U); + EXPECT_EQ(sizeAfter, 1U); + + // add the qubit back at the end + qc.addQubit(1, physicalIndex, outputIndex); + const auto& qregsAfterAdd = qc.getQregs(); + EXPECT_EQ(qregsAfterAdd.size(), 1U); + const auto& [nameAfterAdd, regAfterAdd] = *qregsAfterAdd.begin(); + const auto& [startAfterAdd, sizeAfterAdd] = regAfterAdd; + EXPECT_EQ(nameAfterAdd, "q"); + EXPECT_EQ(startAfterAdd, 0U); + EXPECT_EQ(sizeAfterAdd, 2U); +} + +TEST_F(QFRFunctionality, AddQubitInMiddleOfSplitRegister) { + QuantumComputation qc{}; + qc.addQubitRegister(3, "q"); + const auto& qregs = qc.getQregs(); + EXPECT_EQ(qregs.size(), 1U); + const auto& [name, reg] = *qregs.begin(); + const auto& [start, size] = reg; + EXPECT_EQ(name, "q"); + EXPECT_EQ(start, 0U); + EXPECT_EQ(size, 3U); + + // remove the middle qubit -> splits the register into q_l and q_h + const auto& [physicalIndex, outputIndex] = qc.removeQubit(1); + EXPECT_EQ(physicalIndex, 1U); + EXPECT_EQ(outputIndex, 1U); + + const auto& qregsAfter = qc.getQregs(); + EXPECT_EQ(qregsAfter.size(), 2U); + const auto& [nameLow, regLow] = *qregsAfter.begin(); + const auto& [startLow, sizeLow] = regLow; + EXPECT_EQ(nameLow, "q_l"); + EXPECT_EQ(startLow, 0U); + EXPECT_EQ(sizeLow, 1U); + const auto& [nameHigh, regHigh] = *qregsAfter.rbegin(); + const auto& [startHigh, sizeHigh] = regHigh; + EXPECT_EQ(nameHigh, "q_h"); + EXPECT_EQ(startHigh, 2U); + EXPECT_EQ(sizeHigh, 1U); + + // add back the qubit. should consolidate the registers again + qc.addQubit(1, physicalIndex, outputIndex); + const auto& qregsAfterAdd = qc.getQregs(); + EXPECT_EQ(qregsAfterAdd.size(), 1U); + const auto& [nameConsolidated, regConsolidated] = *qregsAfterAdd.begin(); + const auto& [startConsolidated, sizeConsolidated] = regConsolidated; + EXPECT_EQ(nameConsolidated, "q"); + EXPECT_EQ(startConsolidated, 0U); + EXPECT_EQ(sizeConsolidated, 3U); +} + +TEST_F(QFRFunctionality, AddQubitWithoutNeighboringQubits) { + QuantumComputation qc{}; + qc.addQubitRegister(5, "q"); + const auto& qregs = qc.getQregs(); + EXPECT_EQ(qregs.size(), 1U); + const auto& [name, reg] = *qregs.begin(); + const auto& [start, size] = reg; + EXPECT_EQ(name, "q"); + EXPECT_EQ(start, 0U); + EXPECT_EQ(size, 5U); + + // remove the middle 3 qubits -> splits the register into q_l and q_h with one + // qubit each. + const auto& [physicalIndex1, outputIndex1] = qc.removeQubit(1); + EXPECT_EQ(physicalIndex1, 1U); + EXPECT_EQ(outputIndex1, 1U); + const auto& [physicalIndex2, outputIndex2] = qc.removeQubit(2); + EXPECT_EQ(physicalIndex2, 2U); + EXPECT_EQ(outputIndex2, 2U); + const auto& [physicalIndex3, outputIndex3] = qc.removeQubit(3); + EXPECT_EQ(physicalIndex3, 3U); + EXPECT_EQ(outputIndex3, 3U); + + const auto& qregsAfter = qc.getQregs(); + EXPECT_EQ(qregsAfter.size(), 2U); + const auto& [nameLow, regLow] = *qregsAfter.begin(); + const auto& [startLow, sizeLow] = regLow; + EXPECT_EQ(nameLow, "q_l"); + EXPECT_EQ(startLow, 0U); + EXPECT_EQ(sizeLow, 1U); + + const auto& [nameHigh, regHigh] = *qregsAfter.rbegin(); + const auto& [startHigh, sizeHigh] = regHigh; + EXPECT_EQ(nameHigh, "q_h"); + EXPECT_EQ(startHigh, 4U); + EXPECT_EQ(sizeHigh, 1U); + + // add back the middle qubit. should create a new register for the qubit + qc.addQubit(2, physicalIndex2, outputIndex2); + const auto& qregsAfterAdd = qc.getQregs(); + EXPECT_EQ(qregsAfterAdd.size(), 3U); + // expect to find a `q_2` register with 1 qubit starting at index 2 + const auto& it = qregsAfterAdd.find("q_2"); + ASSERT_NE(it, qregsAfterAdd.end()); + const auto& [nameMiddle, regMiddle] = *it; + const auto& [startMiddle, sizeMiddle] = regMiddle; + EXPECT_EQ(nameMiddle, "q_2"); + EXPECT_EQ(startMiddle, 2U); + EXPECT_EQ(sizeMiddle, 1U); +} From 24b80e26b5dd4199cbd6e5c700f5f9e841c19bc8 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 12 Jan 2025 18:37:37 +0100 Subject: [PATCH 10/11] =?UTF-8?q?=E2=9C=85=20adjust=20IO=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- test/ir/test_io.cpp | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/test/ir/test_io.cpp b/test/ir/test_io.cpp index 3740ee7cc..ae7410ec2 100644 --- a/test/ir/test_io.cpp +++ b/test/ir/test_io.cpp @@ -27,10 +27,9 @@ #include #include #include -#include #include -class IO : public testing::TestWithParam> { +class IO : public testing::Test { protected: void TearDown() override {} @@ -64,28 +63,9 @@ void compareFiles(const std::string& file1, const std::string& file2, } } // namespace -INSTANTIATE_TEST_SUITE_P( - IO, IO, - testing::Values(std::make_tuple( - "../circuits/test.qasm", - qc::Format::OpenQASM3)), // std::make_tuple("circuits/test.real", - // qc::Format::Real - [](const testing::TestParamInfo& inf) { - const qc::Format format = std::get<1>(inf.param); - - switch (format) { - case qc::Format::Real: - return "Real"; - case qc::Format::OpenQASM2: - case qc::Format::OpenQASM3: - return "OpenQasm"; - default: - return "Unknown format"; - } - }); - -TEST_P(IO, importAndDump) { - const auto& [input, format] = GetParam(); +TEST_F(IO, importAndDumpQASM) { + constexpr auto input = "../circuits/test.qasm"; + constexpr auto format = qc::Format::OpenQASM2; std::cout << "FILE: " << input << "\n"; ASSERT_NO_THROW(qc->import(input, format)); @@ -99,6 +79,22 @@ TEST_P(IO, importAndDump) { std::filesystem::remove(output2); } +TEST_F(IO, importAndDumpQASMFromConstructor) { + constexpr auto input = "../circuits/test.qasm"; + constexpr auto format = qc::Format::OpenQASM2; + std::cout << "FILE: " << input << "\n"; + + ASSERT_NO_THROW(qc = std::make_unique(input)); + ASSERT_NO_THROW(qc->dump(output, format)); + ASSERT_NO_THROW(qc->reset()); + ASSERT_NO_THROW(qc->import(output, format)); + ASSERT_NO_THROW(qc->dump(output2, format)); + + compareFiles(output, output2, true); + std::filesystem::remove(output); + std::filesystem::remove(output2); +} + TEST_F(IO, dumpValidFilenames) { ASSERT_NO_THROW(qc->dump(output3, qc::Format::OpenQASM2)); ASSERT_NO_THROW(qc->dump(output4, qc::Format::OpenQASM2)); From c381adb1a0d934351e6ab5e651d728e5082661e4 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 12 Jan 2025 18:58:18 +0100 Subject: [PATCH 11/11] =?UTF-8?q?=F0=9F=9A=B8=E2=9C=85=20add=20equality=20?= =?UTF-8?q?operator=20for=20`QuantumComputation`=20and=20respective=20test?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- include/mqt-core/ir/QuantumComputation.hpp | 5 + src/ir/QuantumComputation.cpp | 25 ++++ test/ir/test_qfr_functionality.cpp | 137 +++++++++++++++++++++ 3 files changed, 167 insertions(+) diff --git a/include/mqt-core/ir/QuantumComputation.hpp b/include/mqt-core/ir/QuantumComputation.hpp index 1c8494e4a..7580202d7 100644 --- a/include/mqt-core/ir/QuantumComputation.hpp +++ b/include/mqt-core/ir/QuantumComputation.hpp @@ -420,6 +420,11 @@ class QuantumComputation { */ void invert(); + [[nodiscard]] bool operator==(const QuantumComputation& rhs) const; + [[nodiscard]] bool operator!=(const QuantumComputation& rhs) const { + return !(*this == rhs); + } + /** * printing */ diff --git a/src/ir/QuantumComputation.cpp b/src/ir/QuantumComputation.cpp index de39f38fb..71bfffb46 100644 --- a/src/ir/QuantumComputation.cpp +++ b/src/ir/QuantumComputation.cpp @@ -642,6 +642,31 @@ void QuantumComputation::invert() { "output permutation will not be swapped.\n"; } } + +bool QuantumComputation::operator==(const QuantumComputation& rhs) const { + if (nqubits != rhs.nqubits || nancillae != rhs.nancillae || + nclassics != rhs.nclassics || qregs != rhs.qregs || cregs != rhs.cregs || + ancregs != rhs.ancregs || initialLayout != rhs.initialLayout || + outputPermutation != rhs.outputPermutation || + ancillary != rhs.ancillary || garbage != rhs.garbage || + seed != rhs.seed || globalPhase != rhs.globalPhase || + occurringVariables != rhs.occurringVariables) { + return false; + } + + if (ops.size() != rhs.ops.size()) { + return false; + } + + for (std::size_t i = 0; i < ops.size(); ++i) { + if (*ops[i] != *rhs.ops[i]) { + return false; + } + } + + return true; +} + std::ostream& QuantumComputation::print(std::ostream& os) const { os << name << "\n"; const auto width = diff --git a/test/ir/test_qfr_functionality.cpp b/test/ir/test_qfr_functionality.cpp index 229fd5588..58226bef5 100644 --- a/test/ir/test_qfr_functionality.cpp +++ b/test/ir/test_qfr_functionality.cpp @@ -1334,3 +1334,140 @@ TEST_F(QFRFunctionality, AddQubitWithoutNeighboringQubits) { EXPECT_EQ(startMiddle, 2U); EXPECT_EQ(sizeMiddle, 1U); } + +TEST_F(QFRFunctionality, CopyConstructor) { + QuantumComputation qc(2, 2); + qc.h(0); + qc.swap(0, 1); + qc.barrier(); + qc.measure(0, 0); + qc.classicControlled(X, 0, {0, 1}); + const sym::Variable theta{"theta"}; + qc.rx(Symbolic{theta}, 0); + + QuantumComputation compound(2, 2); + compound.h(0); + compound.cx(0, 1); + qc.emplace_back(compound.asOperation()); + + qc.initialLayout[0] = 1; + qc.initialLayout[1] = 0; + qc.outputPermutation[0] = 1; + qc.outputPermutation[1] = 0; + qc.gphase(0.25); + + qc.setLogicalQubitAncillary(1); + qc.setLogicalQubitGarbage(1); + + const auto qcCopy = qc; + EXPECT_EQ(qc, qcCopy); +} + +TEST_F(QFRFunctionality, InequalityDifferentNumberOfQubits) { + // Different number of qubits + const QuantumComputation qc1(2, 2); + const QuantumComputation qc2(3, 3); + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentInitialLayout) { + // Different initial layout + QuantumComputation qc1(2, 2); + QuantumComputation qc2(2, 2); + qc1.initialLayout[0] = 0; + qc1.initialLayout[1] = 1; + qc2.initialLayout[0] = 1; + qc2.initialLayout[1] = 0; + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentGateOperations) { + // Different gate operations + QuantumComputation qc1(2, 2); + QuantumComputation qc2(2, 2); + qc1.h(0); + qc2.cx(0, 1); // Different operation + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentGateOrder) { + // Same gates but different order + QuantumComputation qc1(2, 2); + QuantumComputation qc2(2, 2); + qc1.h(0); + qc1.cx(0, 1); + + qc2.cx(0, 1); + qc2.h(0); // Reversed order + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentAncillaryQubits) { + // Different ancillary qubits + QuantumComputation qc1(2, 2); + qc1.setLogicalQubitAncillary(1); + + const QuantumComputation qc2(2, 2); + // No ancillary qubits in qc2 + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentGarbageQubits) { + // Different garbage qubits + QuantumComputation qc1(2, 2); + qc1.setLogicalQubitGarbage(1); + + const QuantumComputation qc2(2, 2); + // No garbage qubits in qc2 + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentFinalLayout) { + // Different final layout + QuantumComputation qc1(2, 2); + QuantumComputation qc2(2, 2); + qc1.outputPermutation[0] = 1; + qc1.outputPermutation[1] = 0; + + qc2.outputPermutation[0] = 0; + qc2.outputPermutation[1] = 1; + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentQuantumPhase) { + // Different quantum phase + QuantumComputation qc1(2, 2); + qc1.gphase(0.1); // Add global phase + + const QuantumComputation qc2(2, 2); + // No global phase in qc2 + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentNumberOfClassicalBits) { + // Different number of classical bits + const QuantumComputation qc1(2, 3); // 2 qubits, 3 classical bits + const QuantumComputation qc2(2, 2); // 2 qubits, 2 classical bits + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentMeasurementMappings) { + // Different measurement mappings + QuantumComputation qc1(2, 2); + qc1.measure(0, 1); // Measure qubit 0 to classical bit 1 + + QuantumComputation qc2(2, 2); + qc2.measure(0, 0); // Measure qubit 0 to classical bit 0 + EXPECT_NE(qc1, qc2); +} + +TEST_F(QFRFunctionality, InequalityDifferentAdditionalOperations) { + // Different additional operations + QuantumComputation qc1(2, 2); + qc1.h(0); + + QuantumComputation qc2(2, 2); + qc2.h(0); + qc2.barrier(); // qc2 has an additional barrier + EXPECT_NE(qc1, qc2); +}