From 804f39b271f9f84087fe10fdf9e79cf9e40e8397 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 1 Apr 2025 09:56:30 +0200 Subject: [PATCH 01/59] Add Approximation.hpp --- include/mqt-core/dd/Approximation.hpp | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 include/mqt-core/dd/Approximation.hpp diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp new file mode 100644 index 000000000..de72d1f8d --- /dev/null +++ b/include/mqt-core/dd/Approximation.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Chair for Design Automation, TUM + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include + +namespace dd { +enum ApproximationStrategy { FidelityDriven, MemoryDriven }; + +template struct Approximation {}; + +template <> struct Approximation { + constexpr explicit Approximation(double finalFidelity) noexcept + : finalFidelity(finalFidelity) {} + double finalFidelity; +}; + +template <> struct Approximation { + constexpr Approximation(std::size_t maxNodes, double roundFidelity, + double factor = 2.) noexcept + : maxNodes(maxNodes), roundFidelity(roundFidelity), factor(factor) {} + + /** + * @brief Multiplies `roundFidelity` by `factor`. + * @details Used after each approx. round to increase `maxNodes` s.t. too many + * approximations are avoided. + */ + void increaseFidelity() noexcept { roundFidelity *= factor; } + + std::size_t maxNodes; + double roundFidelity; + double factor; +}; +} // namespace dd From 454d5bc49d7b8929fdab3176506f3153a5b0d665 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 3 Apr 2025 10:25:32 +0200 Subject: [PATCH 02/59] Add NodeContributions struct to Approximation.hpp --- include/mqt-core/dd/Approximation.hpp | 94 +++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index de72d1f8d..987d6d8e3 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -7,6 +7,12 @@ * Licensed under the MIT License */ +#pragma once + +#include "dd/ComplexValue.hpp" +#include "dd/Node.hpp" + +#include #include namespace dd { @@ -15,25 +21,101 @@ enum ApproximationStrategy { FidelityDriven, MemoryDriven }; template struct Approximation {}; template <> struct Approximation { - constexpr explicit Approximation(double finalFidelity) noexcept + explicit Approximation(double finalFidelity) noexcept : finalFidelity(finalFidelity) {} double finalFidelity; }; template <> struct Approximation { - constexpr Approximation(std::size_t maxNodes, double roundFidelity, - double factor = 2.) noexcept + Approximation(std::size_t maxNodes, double roundFidelity, + std::size_t factor = 2) noexcept : maxNodes(maxNodes), roundFidelity(roundFidelity), factor(factor) {} /** - * @brief Multiplies `roundFidelity` by `factor`. + * @brief Multiplies `maxNodes` by `factor`. * @details Used after each approx. round to increase `maxNodes` s.t. too many * approximations are avoided. */ - void increaseFidelity() noexcept { roundFidelity *= factor; } + void increaseMaxNodes() noexcept { maxNodes *= factor; } std::size_t maxNodes; double roundFidelity; - double factor; + std::size_t factor; +}; + +struct NodeContributions { + using Map = std::unordered_map; + using Vector = std::vector>; + + /** + * @brief Recursively compute contributions for each node. + * @return Vector of node-contribution pairs with ascending contribution. + */ + Vector operator()(const dd::vEdge& root) { + compute(root, dd::ComplexValue{root.w}); + return vectorize(); + } + +private: + /** + * @brief Recursively compute contributions for each node. + * @details Propagates downwards until it reaches a terminal. Then, return + * squared magnitude of amplitudes or 0. Sum these values for all + * edges of node. Since nodes can be visited multiple times by edges + * going inwards we add the sums to the value stored in the map for + * the respective node + * + * Uses a lookup table to avoid computing the contributions of the + * same node twice. + * @return Vector of node-contribution pairs with ascending contribution. + */ + double compute(const dd::vEdge& edge, const dd::ComplexValue acc) { + // Reached the end of a path. Either return 0 or squared magnitude. + if (edge.isZeroTerminal()) { + return 0.; + } + if (edge.isTerminal()) { + return acc.mag2(); + } + + double sum = 0.; + const dd::vNode* node = edge.p; + + // If the node has already been visited once, reuse value from lookup table. + // + // Otherwise compute the contribution of each node, which is the sum of + // squared magnitudes of amplitudes for each path passing through that node. + if (lookup.count(node) > 0) { + sum = lookup[node]; + } else { + for (const auto& e : node->e) { + sum += compute(e, acc * e.w); + } + lookup[node] = sum; + } + + contributions[node] += sum; + + return sum; + } + + /** + * @brief Vectorize `contributions` map and sort ascendingly by contribution. + */ + Vector vectorize() { + Vector v{}; + v.reserve(contributions.size()); + for (const auto& pair : contributions) { + v.emplace_back(pair); + } + + const auto comp = [](auto& a, auto& b) { return a.second < b.second; }; + std::sort(v.begin(), v.end(), comp); + + return v; + } + + Map contributions{}; + Map lookup{}; }; } // namespace dd From afb6b6fe78dcda86a2579a97f381674d74b290cd Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 6 Apr 2025 08:19:27 +0200 Subject: [PATCH 03/59] Update vectorize function to iterate breadth-first --- include/mqt-core/dd/Approximation.hpp | 84 +++++++++++++++++---------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 987d6d8e3..4d64189d3 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -14,62 +14,69 @@ #include #include +#include namespace dd { -enum ApproximationStrategy { FidelityDriven, MemoryDriven }; - -template struct Approximation {}; +enum ApproximationStrategy { None, FidelityDriven, MemoryDriven }; +template struct Approximation {}; +template <> struct Approximation {}; template <> struct Approximation { - explicit Approximation(double finalFidelity) noexcept - : finalFidelity(finalFidelity) {} - double finalFidelity; + explicit Approximation(double fidelity) noexcept : fidelity(fidelity) {} + double fidelity; }; - template <> struct Approximation { - Approximation(std::size_t maxNodes, double roundFidelity, + Approximation(std::size_t maxNodes, double fidelity, std::size_t factor = 2) noexcept - : maxNodes(maxNodes), roundFidelity(roundFidelity), factor(factor) {} + : maxNodes(maxNodes), fidelity(fidelity), factor(factor) {} /** - * @brief Multiplies `maxNodes` by `factor`. - * @details Used after each approx. round to increase `maxNodes` s.t. too many - * approximations are avoided. + * @brief Multiplies `maxNodes` by `factor`. + * @details Used after each approx. round to increase `maxNodes` s.t. too + * many approximations are avoided. */ void increaseMaxNodes() noexcept { maxNodes *= factor; } std::size_t maxNodes; - double roundFidelity; + double fidelity; std::size_t factor; }; struct NodeContributions { - using Map = std::unordered_map; - using Vector = std::vector>; + struct Pair { + const vNode* node; + double contribution; + }; + + using Vector = std::vector; /** - * @brief Recursively compute contributions for each node. + * @brief Recursively compute contributions for each node. * @return Vector of node-contribution pairs with ascending contribution. */ - Vector operator()(const dd::vEdge& root) { - compute(root, dd::ComplexValue{root.w}); - return vectorize(); + Vector operator()(const vEdge& root) { + compute(root, ComplexValue{root.w}); + return vectorize(root.p); } private: + using Map = std::unordered_map; + /** - * @brief Recursively compute contributions for each node. + * @brief Recursively compute contributions for each node. * @details Propagates downwards until it reaches a terminal. Then, return * squared magnitude of amplitudes or 0. Sum these values for all * edges of node. Since nodes can be visited multiple times by edges * going inwards we add the sums to the value stored in the map for * the respective node * + * The accumulator computes the amplitude. + * * Uses a lookup table to avoid computing the contributions of the * same node twice. - * @return Vector of node-contribution pairs with ascending contribution. + * @return Vector of node-contribution pairs with ascending contribution. */ - double compute(const dd::vEdge& edge, const dd::ComplexValue acc) { + double compute(const vEdge& edge, const ComplexValue acc) { // Reached the end of a path. Either return 0 or squared magnitude. if (edge.isZeroTerminal()) { return 0.; @@ -79,7 +86,7 @@ struct NodeContributions { } double sum = 0.; - const dd::vNode* node = edge.p; + const vNode* node = edge.p; // If the node has already been visited once, reuse value from lookup table. // @@ -100,22 +107,37 @@ struct NodeContributions { } /** - * @brief Vectorize `contributions` map and sort ascendingly by contribution. + * @brief Vectorize `contributions` map breadth-first. + * @details Uses iterative deepening search. */ - Vector vectorize() { + Vector vectorize(const vNode* node) { Vector v{}; - v.reserve(contributions.size()); - for (const auto& pair : contributions) { - v.emplace_back(pair); + v.reserve(contributions.size()); // The # of nodes. + v.push_back({node, contributions[node]}); + + std::deque q; + q.push_back(node); + while (!q.empty()) { + const vNode* curr = q.front(); + q.pop_front(); + + for (const auto& edge : curr->e) { + const vNode* nxt = edge.p; + if (!edge.isTerminal() && + std::find(q.begin(), q.end(), nxt) == q.end()) { + v.push_back({nxt, contributions[nxt]}); + q.push_back(nxt); + } + } } - const auto comp = [](auto& a, auto& b) { return a.second < b.second; }; - std::sort(v.begin(), v.end(), comp); - return v; } Map contributions{}; Map lookup{}; }; + +template +void applyApproximation(VectorDD& v, Approximation& approx); } // namespace dd From 182b48aa0c8d8aa81bcd423cf594ec9525179cc8 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 6 Apr 2025 08:53:00 +0200 Subject: [PATCH 04/59] Add Approximation to simulate function --- include/mqt-core/dd/Approximation.hpp | 2 +- include/mqt-core/dd/Simulation.hpp | 5 +- src/dd/Approximation.cpp | 38 ++++++++++++ src/dd/Simulation.cpp | 86 ++++++++++++++++++++------- 4 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 src/dd/Approximation.cpp diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 4d64189d3..1d38b6a32 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -43,7 +43,7 @@ template <> struct Approximation { }; struct NodeContributions { - struct Pair { + struct Pair { // TODO: Naming. const vNode* node; double contribution; }; diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp index e134893d1..742446f51 100644 --- a/include/mqt-core/dd/Simulation.hpp +++ b/include/mqt-core/dd/Simulation.hpp @@ -13,6 +13,7 @@ #pragma once +#include "dd/Approximation.hpp" #include "dd/Package_fwd.hpp" #include @@ -46,8 +47,10 @@ namespace dd { * @param dd The DD package to use for the simulation * @return A vector DD representing the output state of the simulation */ +template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, - Package& dd); + Package& dd, + Approximation approx = Approximation{}); /** * @brief Sample from the output distribution of a quantum computation diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp new file mode 100644 index 000000000..e3269397f --- /dev/null +++ b/src/dd/Approximation.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 Chair for Design Automation, TUM + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "dd/Approximation.hpp" + +#include + +template <> +void dd::applyApproximation( + VectorDD& v, Approximation& approx) { + NodeContributions::Vector contributions = NodeContributions{}(v); + + const auto it = + std::find_if(contributions.begin(), contributions.end(), + [f = approx.fidelity]([[maybe_unused]] const auto& p) { + return f < (1. - p.contribution); + }); + + if (it != contributions.end()) { + std::cout << it->contribution << '\n'; + } +}; + +template <> +void dd::applyApproximation( + VectorDD& v, Approximation& approx) { + Approximation approxFidelity(approx.fidelity); + if (v.size() > approx.maxNodes) { + applyApproximation(v, approxFidelity); + approx.increaseMaxNodes(); + } +}; diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp index 5e1d39639..a76703e3f 100644 --- a/src/dd/Simulation.cpp +++ b/src/dd/Simulation.cpp @@ -9,13 +9,18 @@ #include "dd/Simulation.hpp" +#include "dd/Approximation.hpp" +#include "dd/Complex.hpp" +#include "dd/Node.hpp" #include "dd/Operations.hpp" #include "dd/Package.hpp" #include "ir/Definitions.hpp" +#include "ir/Permutation.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/ClassicControlledOperation.hpp" #include "ir/operations/NonUnitaryOperation.hpp" #include "ir/operations/OpType.hpp" +#include "ir/operations/Operation.hpp" #include #include @@ -30,6 +35,40 @@ #include namespace dd { +/** + * @brief Returns `true` if the operation is virtually swappable. + */ +bool isVirtuallySwappable(const qc::Operation& op) noexcept { + return op.getType() == qc::SWAP && !op.isControlled(); +} + +/** + * @brief Virtually SWAP by permuting the layout. + */ +void virtualSwap(const qc::Operation& op, qc::Permutation& perm) noexcept { + const auto& targets = op.getTargets(); + std::swap(perm.at(targets[0U]), perm.at(targets[1U])); +} + +/** + * @brief Returns `true` if the circuit has a global phase. + */ +bool hasGlobalPhase(const fp& phase) noexcept { return std::abs(phase) > 0; } + +/** + * @brief Apply global phase to `out.w`. Decreases reference count of old `w` + * value. + */ +void applyGlobalPhase(const fp& phase, VectorDD& out, Package& dd) { + const Complex oldW = out.w; // create a temporary copy for reference counting + + out.w = dd.cn.lookup(out.w * ComplexValue{std::polar(1.0, phase)}); + + // adjust reference counts + dd.cn.incRef(out.w); + dd.cn.decRef(oldW); +} + std::map sample(const qc::QuantumComputation& qc, const VectorDD& in, Package& dd, const std::size_t shots, @@ -209,37 +248,40 @@ std::map sample(const qc::QuantumComputation& qc, return counts; } +template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, - Package& dd) { - auto permutation = qc.initialLayout; - auto e = in; + Package& dd, Approximation approx) { + qc::Permutation permutation = qc.initialLayout; + dd::VectorDD out = in; for (const auto& op : qc) { - // SWAP gates can be executed virtually by changing the permutation - if (op->getType() == qc::SWAP && !op->isControlled()) { - const auto& targets = op->getTargets(); - std::swap(permutation.at(targets[0U]), permutation.at(targets[1U])); - continue; + if (isVirtuallySwappable(*op)) { + virtualSwap(*op, permutation); + } else { + out = applyUnitaryOperation(*op, out, dd, permutation); + + // TODO: this applies approximation after each operation. + if constexpr (stgy != None) { + applyApproximation(out, approx); + } } - - e = applyUnitaryOperation(*op, e, dd, permutation); } - changePermutation(e, permutation, qc.outputPermutation, dd); - e = dd.reduceGarbage(e, qc.getGarbage()); + + changePermutation(out, permutation, qc.outputPermutation, dd); + out = dd.reduceGarbage(out, qc.getGarbage()); // properly account for the global phase of the circuit - if (std::abs(qc.getGlobalPhase()) > 0) { - // create a temporary copy for reference counting - auto oldW = e.w; - // adjust for global phase - const auto globalPhase = ComplexValue{std::polar(1.0, qc.getGlobalPhase())}; - e.w = dd.cn.lookup(e.w * globalPhase); - // adjust reference count - dd.cn.incRef(e.w); - dd.cn.decRef(oldW); + if (fp phase = qc.getGlobalPhase(); hasGlobalPhase(phase)) { + applyGlobalPhase(phase, out, dd); } - return e; + return out; } +template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, + Package& dd, Approximation approx); +template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, + Package& dd, Approximation approx); +template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, + Package& dd, Approximation approx); std::map sample(const qc::QuantumComputation& qc, const std::size_t shots, From 2a7b5cd0aaa10eb3ca70eff05ce612773aae0558 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 6 Apr 2025 17:47:35 +0200 Subject: [PATCH 05/59] Slim down NodeContributions class --- include/mqt-core/dd/Approximation.hpp | 61 +++++---------------------- 1 file changed, 11 insertions(+), 50 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 1d38b6a32..fcfbd76a2 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -11,10 +11,9 @@ #include "dd/ComplexValue.hpp" #include "dd/Node.hpp" +#include "dd/Package.hpp" -#include #include -#include namespace dd { enum ApproximationStrategy { None, FidelityDriven, MemoryDriven }; @@ -42,23 +41,15 @@ template <> struct Approximation { std::size_t factor; }; -struct NodeContributions { - struct Pair { // TODO: Naming. - const vNode* node; - double contribution; - }; - - using Vector = std::vector; - - /** - * @brief Recursively compute contributions for each node. - * @return Vector of node-contribution pairs with ascending contribution. - */ - Vector operator()(const vEdge& root) { - compute(root, ComplexValue{root.w}); - return vectorize(root.p); +class NodeContributions { +public: + explicit NodeContributions(const vEdge& root) { + Map lookup{}; + compute(root, ComplexValue{root.w}, lookup); } + double operator[](const vNode* key) noexcept { return contributions[key]; } + private: using Map = std::unordered_map; @@ -74,9 +65,8 @@ struct NodeContributions { * * Uses a lookup table to avoid computing the contributions of the * same node twice. - * @return Vector of node-contribution pairs with ascending contribution. */ - double compute(const vEdge& edge, const ComplexValue acc) { + double compute(const vEdge& edge, const ComplexValue acc, Map& lookup) { // Reached the end of a path. Either return 0 or squared magnitude. if (edge.isZeroTerminal()) { return 0.; @@ -96,7 +86,7 @@ struct NodeContributions { sum = lookup[node]; } else { for (const auto& e : node->e) { - sum += compute(e, acc * e.w); + sum += compute(e, acc * e.w, lookup); } lookup[node] = sum; } @@ -106,38 +96,9 @@ struct NodeContributions { return sum; } - /** - * @brief Vectorize `contributions` map breadth-first. - * @details Uses iterative deepening search. - */ - Vector vectorize(const vNode* node) { - Vector v{}; - v.reserve(contributions.size()); // The # of nodes. - v.push_back({node, contributions[node]}); - - std::deque q; - q.push_back(node); - while (!q.empty()) { - const vNode* curr = q.front(); - q.pop_front(); - - for (const auto& edge : curr->e) { - const vNode* nxt = edge.p; - if (!edge.isTerminal() && - std::find(q.begin(), q.end(), nxt) == q.end()) { - v.push_back({nxt, contributions[nxt]}); - q.push_back(nxt); - } - } - } - - return v; - } - Map contributions{}; - Map lookup{}; }; template -void applyApproximation(VectorDD& v, Approximation& approx); +void applyApproximation(VectorDD& v, Approximation& approx, Package& dd); } // namespace dd From 080774bd9dced1549e7b832de1469c909b2adc35 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 6 Apr 2025 17:48:34 +0200 Subject: [PATCH 06/59] Implement applyApproximation --- src/dd/Approximation.cpp | 76 ++++++++++++++++++++++++++++++++-------- src/dd/Simulation.cpp | 2 +- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index e3269397f..19844c7f1 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -9,30 +9,76 @@ #include "dd/Approximation.hpp" +#include "dd/Node.hpp" +#include "dd/Package.hpp" + #include +#include + +namespace dd { +namespace { +/** + * @brief Find next node to remove and delete its edge. + * @details Traverses breadth-first with iterative deepening algorithm. + * @return Fidelity lost due to removal of node. + */ +double findAndRemoveNext(VectorDD& v, Package& dd, + const Approximation& approx, + NodeContributions& contributions) { + std::deque q = {v.p}; + while (!q.empty()) { + vNode* curr = q.front(); + q.pop_front(); + + for (std::size_t i = 0; i < 2; i++) { + vEdge* edge = &curr->e[i]; + if (edge->isTerminal()) { + continue; + } + + vNode* nxt = edge->p; + if (std::find(q.begin(), q.end(), nxt) == q.end()) { + if (approx.fidelity < (1. - contributions[nxt])) { + v = dd.deleteEdge(*edge, curr->v, i); + return contributions[nxt]; + } + q.push_back(nxt); + } + } + } + + return 0; +} +}; // namespace template <> -void dd::applyApproximation( - VectorDD& v, Approximation& approx) { - NodeContributions::Vector contributions = NodeContributions{}(v); - - const auto it = - std::find_if(contributions.begin(), contributions.end(), - [f = approx.fidelity]([[maybe_unused]] const auto& p) { - return f < (1. - p.contribution); - }); - - if (it != contributions.end()) { - std::cout << it->contribution << '\n'; +void applyApproximation( + VectorDD& v, Approximation& approx, Package& dd) { + if (v.isTerminal()) { + return; + } + + NodeContributions contributions(v); + + double fidelityBudget = 1 - approx.fidelity; + while (fidelityBudget > 0) { + double fidelityLost = findAndRemoveNext(v, dd, approx, contributions); + if (fidelityLost == 0) { + break; + } + fidelityBudget -= fidelityLost; } }; template <> -void dd::applyApproximation( - VectorDD& v, Approximation& approx) { +void applyApproximation(VectorDD& v, + Approximation& approx, + Package& dd) { Approximation approxFidelity(approx.fidelity); if (v.size() > approx.maxNodes) { - applyApproximation(v, approxFidelity); + applyApproximation(v, approxFidelity, dd); approx.increaseMaxNodes(); } }; + +} // namespace dd diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp index a76703e3f..4d6d17d49 100644 --- a/src/dd/Simulation.cpp +++ b/src/dd/Simulation.cpp @@ -261,7 +261,7 @@ VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, // TODO: this applies approximation after each operation. if constexpr (stgy != None) { - applyApproximation(out, approx); + applyApproximation(out, approx, dd); } } } From 43b9569cc5687ae0a030ab2185819c42223d456f Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 6 Apr 2025 17:49:06 +0200 Subject: [PATCH 07/59] Add preliminary unit tests --- test/dd/test_approximations.cpp | 138 ++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 test/dd/test_approximations.cpp diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp new file mode 100644 index 000000000..1a6ad9378 --- /dev/null +++ b/test/dd/test_approximations.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2025 Chair for Design Automation, TUM + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "dd/Approximation.hpp" +#include "dd/Node.hpp" +#include "dd/Package.hpp" +#include "dd/Simulation.hpp" +#include "ir/QuantumComputation.hpp" + +#include + +using namespace dd; + +TEST(ApproximationTest, NodeContributionsSingleEdge) { + constexpr std::size_t nq = 1; + Package dd(nq); + + qc::QuantumComputation qc(nq); // i|1> + qc.x(0); + qc.s(0); + + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + NodeContributions contributions(root); + + EXPECT_NEAR(contributions[root.p], 1., 1e-8); +} + +TEST(ApproximationTest, NodeContributionsGHZ) { + constexpr std::size_t nq = 2; + Package dd(nq); + + qc::QuantumComputation qc(nq); // 1/sqrt(2) * (|00> + |11>) + qc.h(1); + qc.cx(1, 0); + + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + NodeContributions contributions(root); + + EXPECT_NEAR(contributions[root.p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p->e[0].p], .5, 1e-8); + EXPECT_NEAR(contributions[root.p->e[1].p], .5, 1e-8); +} + +TEST(ApproximationTest, NodeContributionsDoubleVisit) { + constexpr std::size_t nq = 2; + Package dd(nq); + + qc::QuantumComputation qc(nq); // 1/2 * (|00> - |01> + |10> - |11>) + qc.h(1); + qc.x(0); + qc.h(0); + + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + NodeContributions contributions(root); + + EXPECT_NEAR(contributions[root.p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-8); +} + +TEST(ApproximationTest, NodeContributions2Percent) { + constexpr std::size_t nq = 2; + Package dd(nq); + + qc::QuantumComputation qc(nq); // first qubit with prob < 2%. + qc.h(0); + qc.cry(qc::PI / 8, 0, 1); + + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + NodeContributions contributions(root); + + EXPECT_NEAR(contributions[root.p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p->e[0].p], .98096988, 1e-8); + EXPECT_NEAR(contributions[root.p->e[1].p], .01903012, 1e-8); +} + +TEST(ApproximationTest, NodeContributionsGrover) { + constexpr std::size_t nq = 3; + Package dd(nq); + + // Grover after 1st H in diffusion (Oracle |11>). + qc::QuantumComputation qc(nq); + qc.h(0); + qc.h(1); + qc.x(2); + qc.mcz(qc::Controls{0, 1}, 2); + qc.h(0); + qc.h(1); + + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + NodeContributions contributions(root); + + EXPECT_NEAR(contributions[root.p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p->e[1].p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p->e[1].p->e[0].p], .5, 1e-8); + EXPECT_NEAR(contributions[root.p->e[1].p->e[1].p], .5, 1e-8); +} + +TEST(ApproximationTest, FidelityDrivenF1) { + constexpr std::size_t nq = 2; + + std::mt19937_64 mt; + mt.seed(42); + + // Setup package. + Package dd(nq); + + // Circuit to simulate. + qc::QuantumComputation qc(nq); + qc.x(0); + + // Call simulate. + Approximation approx(1.); + VectorDD res = simulate(qc, dd.makeZeroState(nq), dd, approx); + const std::string m = dd.measureAll(res, false, mt, 0.001); + + EXPECT_EQ(res.size(), 3); // number of nodes + terminal. + EXPECT_EQ(m, "01"); // (kron(I, X))|00> = |01> +} + +TEST(ApproximationTest, FidelityDriven2Percent) { + constexpr std::size_t nq = 2; + Package dd(nq); + + qc::QuantumComputation qc(nq); // first qubit with prob < 2%. + qc.h(0); + qc.cry(qc::PI / 8, 0, 1); + + Approximation approx(.98); + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd, approx); + + EXPECT_EQ(root.size(), 2); +} From 82580a75b39e6932af775c7fe562b21cb698710f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 6 Apr 2025 15:56:25 +0000 Subject: [PATCH 08/59] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/mqt-core/dd/Approximation.hpp | 3 ++- src/dd/Approximation.cpp | 3 ++- test/dd/test_approximations.cpp | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index fcfbd76a2..b10e1c5e9 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -1,5 +1,6 @@ /* - * Copyright (c) 2025 Chair for Design Automation, TUM + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH * All rights reserved. * * SPDX-License-Identifier: MIT diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 19844c7f1..819f02533 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -1,5 +1,6 @@ /* - * Copyright (c) 2025 Chair for Design Automation, TUM + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH * All rights reserved. * * SPDX-License-Identifier: MIT diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 1a6ad9378..1eead892f 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -1,5 +1,6 @@ /* - * Copyright (c) 2025 Chair for Design Automation, TUM + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH * All rights reserved. * * SPDX-License-Identifier: MIT From 1f841adb342069b78c4a4cebc32ccbbee7d878d8 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 6 Apr 2025 18:06:21 +0200 Subject: [PATCH 09/59] Update doxygen for simulate function --- include/mqt-core/dd/Simulation.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp index 0cc6007c0..a8849c972 100644 --- a/include/mqt-core/dd/Simulation.hpp +++ b/include/mqt-core/dd/Simulation.hpp @@ -43,9 +43,11 @@ namespace dd { * mid-circuit measurements, see @ref sample(const QuantumComputation&, * std::size_t, std::size_t). * + * @tparam stgy The approximation strategy applied. * @param qc The quantum computation to simulate * @param in The input state to simulate. Represented as a vector DD. * @param dd The DD package to use for the simulation + * @param approx The object describing the approximation strategy. * @return A vector DD representing the output state of the simulation */ template From 22c4e55317fb1e8bdeabade9cdf9a397a7995715 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 7 Apr 2025 07:29:30 +0200 Subject: [PATCH 10/59] Make Approximation structs constexpr --- include/mqt-core/dd/Approximation.hpp | 20 +++++++++----------- include/mqt-core/dd/Simulation.hpp | 2 +- src/dd/Approximation.cpp | 8 +++----- src/dd/Simulation.cpp | 10 ++++++---- test/dd/test_approximations.cpp | 4 ++-- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index b10e1c5e9..38c88fa4e 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -20,23 +20,20 @@ namespace dd { enum ApproximationStrategy { None, FidelityDriven, MemoryDriven }; template struct Approximation {}; + template <> struct Approximation {}; + template <> struct Approximation { - explicit Approximation(double fidelity) noexcept : fidelity(fidelity) {} + constexpr explicit Approximation(double fidelity) noexcept + : fidelity(fidelity) {} double fidelity; }; + template <> struct Approximation { - Approximation(std::size_t maxNodes, double fidelity, - std::size_t factor = 2) noexcept + constexpr Approximation(std::size_t maxNodes, double fidelity, + std::size_t factor = 2) noexcept : maxNodes(maxNodes), fidelity(fidelity), factor(factor) {} - /** - * @brief Multiplies `maxNodes` by `factor`. - * @details Used after each approx. round to increase `maxNodes` s.t. too - * many approximations are avoided. - */ - void increaseMaxNodes() noexcept { maxNodes *= factor; } - std::size_t maxNodes; double fidelity; std::size_t factor; @@ -101,5 +98,6 @@ class NodeContributions { }; template -void applyApproximation(VectorDD& v, Approximation& approx, Package& dd); +void applyApproximation(VectorDD& v, const Approximation& approx, + Package& dd); } // namespace dd diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp index a8849c972..cc7083b1a 100644 --- a/include/mqt-core/dd/Simulation.hpp +++ b/include/mqt-core/dd/Simulation.hpp @@ -53,7 +53,7 @@ namespace dd { template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, Package& dd, - Approximation approx = Approximation{}); + const Approximation& approx = Approximation{}); /** * @brief Sample from the output distribution of a quantum computation diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 819f02533..2a2b3416c 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -54,7 +54,7 @@ double findAndRemoveNext(VectorDD& v, Package& dd, template <> void applyApproximation( - VectorDD& v, Approximation& approx, Package& dd) { + VectorDD& v, const Approximation& approx, Package& dd) { if (v.isTerminal()) { return; } @@ -72,13 +72,11 @@ void applyApproximation( }; template <> -void applyApproximation(VectorDD& v, - Approximation& approx, - Package& dd) { +void applyApproximation( + VectorDD& v, const Approximation& approx, Package& dd) { Approximation approxFidelity(approx.fidelity); if (v.size() > approx.maxNodes) { applyApproximation(v, approxFidelity, dd); - approx.increaseMaxNodes(); } }; diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp index 7a9a7469e..a1a63cb62 100644 --- a/src/dd/Simulation.cpp +++ b/src/dd/Simulation.cpp @@ -251,7 +251,7 @@ std::map sample(const qc::QuantumComputation& qc, template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, - Package& dd, Approximation approx) { + Package& dd, const Approximation& approx) { qc::Permutation permutation = qc.initialLayout; dd::VectorDD out = in; for (const auto& op : qc) { @@ -278,11 +278,13 @@ VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, return out; } template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, - Package& dd, Approximation approx); + Package& dd, const Approximation& approx); template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, - Package& dd, Approximation approx); + Package& dd, + const Approximation& approx); template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, - Package& dd, Approximation approx); + Package& dd, + const Approximation& approx); std::map sample(const qc::QuantumComputation& qc, const std::size_t shots, diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 1eead892f..2cfdb5224 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -116,7 +116,7 @@ TEST(ApproximationTest, FidelityDrivenF1) { qc.x(0); // Call simulate. - Approximation approx(1.); + constexpr Approximation approx(1.); VectorDD res = simulate(qc, dd.makeZeroState(nq), dd, approx); const std::string m = dd.measureAll(res, false, mt, 0.001); @@ -132,7 +132,7 @@ TEST(ApproximationTest, FidelityDriven2Percent) { qc.h(0); qc.cry(qc::PI / 8, 0, 1); - Approximation approx(.98); + constexpr Approximation approx(.98); VectorDD root = simulate(qc, dd.makeZeroState(nq), dd, approx); EXPECT_EQ(root.size(), 2); From 1d3a7e50840e699367913a30e05c5896d92f993b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 7 Apr 2025 07:46:28 +0200 Subject: [PATCH 11/59] Show in code that MemoryDriven is FidelityDriven + threshold --- include/mqt-core/dd/Approximation.hpp | 12 +++++------- src/dd/Approximation.cpp | 10 +++++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 38c88fa4e..8df92e61e 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -29,14 +29,12 @@ template <> struct Approximation { double fidelity; }; -template <> struct Approximation { - constexpr Approximation(std::size_t maxNodes, double fidelity, - std::size_t factor = 2) noexcept - : maxNodes(maxNodes), fidelity(fidelity), factor(factor) {} +template <> +struct Approximation : public Approximation { + constexpr Approximation(std::size_t threshold, double fidelity) noexcept + : Approximation(fidelity), threshold(threshold) {} - std::size_t maxNodes; - double fidelity; - std::size_t factor; + std::size_t threshold; }; class NodeContributions { diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 2a2b3416c..ca5f7c228 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -71,12 +71,16 @@ void applyApproximation( } }; +/** + * @brief Apply Memory-Driven Approximation. + * @details If the threshold is exceeded, apply Fidelity-Driven approximation. + * Inheritance allows us to simply downcast the Memory-Driven object. + */ template <> void applyApproximation( VectorDD& v, const Approximation& approx, Package& dd) { - Approximation approxFidelity(approx.fidelity); - if (v.size() > approx.maxNodes) { - applyApproximation(v, approxFidelity, dd); + if (v.size() > approx.threshold) { + applyApproximation(v, approx, dd); } }; From 472958c6b13e030a58f93ae30bda5b3759b11204 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 7 Apr 2025 16:49:14 +0200 Subject: [PATCH 12/59] Add rebuildWithout function --- include/mqt-core/dd/Approximation.hpp | 2 +- src/dd/Approximation.cpp | 70 ++++++++++++++++++--------- test/dd/test_approximations.cpp | 46 +++++++++++++++++- 3 files changed, 93 insertions(+), 25 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 8df92e61e..4dbe6dcea 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -96,6 +96,6 @@ class NodeContributions { }; template -void applyApproximation(VectorDD& v, const Approximation& approx, +void applyApproximation(VectorDD& state, const Approximation& approx, Package& dd); } // namespace dd diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index ca5f7c228..5be08d7df 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -10,6 +10,7 @@ #include "dd/Approximation.hpp" +#include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" @@ -19,14 +20,13 @@ namespace dd { namespace { /** - * @brief Find next node to remove and delete its edge. - * @details Traverses breadth-first with iterative deepening algorithm. - * @return Fidelity lost due to removal of node. + * @brief Find next node to remove and return its ingoing edge. + * @details Traverses breadth-first using the iterative deepening algorithm. + * @return Edge to remove or nullptr. */ -double findAndRemoveNext(VectorDD& v, Package& dd, - const Approximation& approx, - NodeContributions& contributions) { - std::deque q = {v.p}; +vEdge* findNext(const VectorDD& state, const double budget, + NodeContributions& contributions) { + std::deque q = {state.p}; while (!q.empty()) { vNode* curr = q.front(); q.pop_front(); @@ -39,36 +39,62 @@ double findAndRemoveNext(VectorDD& v, Package& dd, vNode* nxt = edge->p; if (std::find(q.begin(), q.end(), nxt) == q.end()) { - if (approx.fidelity < (1. - contributions[nxt])) { - v = dd.deleteEdge(*edge, curr->v, i); - return contributions[nxt]; + if (budget > contributions[nxt]) { + return edge; } q.push_back(nxt); } } } - return 0; + return nullptr; +} + +/** + * @brief Recursively rebuild `state` without `edge` edge. + * @return Edge to new `state`. + */ +vEdge rebuildWithout(const VectorDD& state, const vEdge& edge, Package& dd) { + if (state.isTerminal()) { + return state; + } + + if (state == edge) { + return vEdge::zero(); + } + + std::array edges{rebuildWithout(state.p->e[0], edge, dd), + rebuildWithout(state.p->e[1], edge, dd)}; + + return dd.makeDDNode(state.p->v, edges); + ; } }; // namespace template <> void applyApproximation( - VectorDD& v, const Approximation& approx, Package& dd) { - if (v.isTerminal()) { + VectorDD& state, const Approximation& approx, Package& dd) { + if (state.isTerminal()) { return; } - NodeContributions contributions(v); + double budget = 1 - approx.fidelity; - double fidelityBudget = 1 - approx.fidelity; - while (fidelityBudget > 0) { - double fidelityLost = findAndRemoveNext(v, dd, approx, contributions); - if (fidelityLost == 0) { + while (true) { + NodeContributions contributions(state); + vEdge* edge = findNext(state, budget, contributions); + if (edge == nullptr) { break; } - fidelityBudget -= fidelityLost; + + state = rebuildWithout(state, *edge, dd); + dd.incRef(state); + dd.decRef(*edge); + + budget -= contributions[edge->p]; } + + dd.garbageCollect(); }; /** @@ -78,9 +104,9 @@ void applyApproximation( */ template <> void applyApproximation( - VectorDD& v, const Approximation& approx, Package& dd) { - if (v.size() > approx.threshold) { - applyApproximation(v, approx, dd); + VectorDD& state, const Approximation& approx, Package& dd) { + if (state.size() > approx.threshold) { + applyApproximation(state, approx, dd); } }; diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 2cfdb5224..cee7ffdf1 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -18,6 +18,10 @@ using namespace dd; +///----------------------------------------------------------------------------- +/// \n compute node contributions \n +///----------------------------------------------------------------------------- + TEST(ApproximationTest, NodeContributionsSingleEdge) { constexpr std::size_t nq = 1; Package dd(nq); @@ -102,7 +106,11 @@ TEST(ApproximationTest, NodeContributionsGrover) { EXPECT_NEAR(contributions[root.p->e[1].p->e[1].p], .5, 1e-8); } -TEST(ApproximationTest, FidelityDrivenF1) { +///----------------------------------------------------------------------------- +/// \n simulate with approximation \n +///----------------------------------------------------------------------------- + +TEST(ApproximationTest, FidelityDrivenKeepAll) { constexpr std::size_t nq = 2; std::mt19937_64 mt; @@ -134,6 +142,40 @@ TEST(ApproximationTest, FidelityDriven2Percent) { constexpr Approximation approx(.98); VectorDD root = simulate(qc, dd.makeZeroState(nq), dd, approx); + NodeContributions contributions(root); + + EXPECT_EQ(root.size(), 3); + EXPECT_NEAR(contributions[root.p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-8); +} + +TEST(ApproximationTest, MemoryDriven2Percent) { + constexpr std::size_t nq = 2; + Package dd(nq); + + qc::QuantumComputation qc(nq); // first qubit with prob < 2%. + qc.h(0); + qc.cry(qc::PI / 8, 0, 1); + + constexpr Approximation approx(3, 0.98); + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd, approx); + NodeContributions contributions(root); + + EXPECT_EQ(root.size(), 3); + EXPECT_NEAR(contributions[root.p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-8); +} + +TEST(ApproximationTest, MemoryDrivenKeepAll) { + constexpr std::size_t nq = 2; + Package dd(nq); + + qc::QuantumComputation qc(nq); // first qubit with prob < 2%. + qc.h(0); + qc.cry(qc::PI / 8, 0, 1); + + constexpr Approximation approx(4, 0.98); + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd, approx); - EXPECT_EQ(root.size(), 2); + EXPECT_EQ(root.size(), 4); } From 5dbef6236efaaf29a8aef4b77697bd4898c24773 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 8 Apr 2025 08:13:48 +0200 Subject: [PATCH 13/59] Remove faulty lookup --- include/mqt-core/dd/Approximation.hpp | 25 ++---- test/dd/test_approximations.cpp | 110 ++++++++++++++++++-------- 2 files changed, 85 insertions(+), 50 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 4dbe6dcea..8fe7213e1 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -40,8 +40,7 @@ struct Approximation : public Approximation { class NodeContributions { public: explicit NodeContributions(const vEdge& root) { - Map lookup{}; - compute(root, ComplexValue{root.w}, lookup); + compute(root, ComplexValue{root.w}); } double operator[](const vNode* key) noexcept { return contributions[key]; } @@ -58,11 +57,8 @@ class NodeContributions { * the respective node * * The accumulator computes the amplitude. - * - * Uses a lookup table to avoid computing the contributions of the - * same node twice. */ - double compute(const vEdge& edge, const ComplexValue acc, Map& lookup) { + double compute(const vEdge& edge, const ComplexValue acc) { // Reached the end of a path. Either return 0 or squared magnitude. if (edge.isZeroTerminal()) { return 0.; @@ -71,22 +67,13 @@ class NodeContributions { return acc.mag2(); } + // Compute the contribution of each node, which is the sum of + // squared magnitudes of amplitudes for each path passing through that node. double sum = 0.; const vNode* node = edge.p; - - // If the node has already been visited once, reuse value from lookup table. - // - // Otherwise compute the contribution of each node, which is the sum of - // squared magnitudes of amplitudes for each path passing through that node. - if (lookup.count(node) > 0) { - sum = lookup[node]; - } else { - for (const auto& e : node->e) { - sum += compute(e, acc * e.w, lookup); - } - lookup[node] = sum; + for (const auto& e : node->e) { + sum += compute(e, acc * e.w); } - contributions[node] += sum; return sum; diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index cee7ffdf1..39297545a 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -33,7 +33,7 @@ TEST(ApproximationTest, NodeContributionsSingleEdge) { VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); NodeContributions contributions(root); - EXPECT_NEAR(contributions[root.p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p], 1., 1e-6); } TEST(ApproximationTest, NodeContributionsGHZ) { @@ -47,9 +47,9 @@ TEST(ApproximationTest, NodeContributionsGHZ) { VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); NodeContributions contributions(root); - EXPECT_NEAR(contributions[root.p], 1., 1e-8); - EXPECT_NEAR(contributions[root.p->e[0].p], .5, 1e-8); - EXPECT_NEAR(contributions[root.p->e[1].p], .5, 1e-8); + EXPECT_NEAR(contributions[root.p], 1., 1e-6); + EXPECT_NEAR(contributions[root.p->e[0].p], .5, 1e-6); + EXPECT_NEAR(contributions[root.p->e[1].p], .5, 1e-6); } TEST(ApproximationTest, NodeContributionsDoubleVisit) { @@ -64,8 +64,8 @@ TEST(ApproximationTest, NodeContributionsDoubleVisit) { VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); NodeContributions contributions(root); - EXPECT_NEAR(contributions[root.p], 1., 1e-8); - EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p], 1., 1e-6); + EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-6); } TEST(ApproximationTest, NodeContributions2Percent) { @@ -79,9 +79,9 @@ TEST(ApproximationTest, NodeContributions2Percent) { VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); NodeContributions contributions(root); - EXPECT_NEAR(contributions[root.p], 1., 1e-8); - EXPECT_NEAR(contributions[root.p->e[0].p], .98096988, 1e-8); - EXPECT_NEAR(contributions[root.p->e[1].p], .01903012, 1e-8); + EXPECT_NEAR(contributions[root.p], 1., 1e-6); + EXPECT_NEAR(contributions[root.p->e[0].p], .98096988, 1e-6); + EXPECT_NEAR(contributions[root.p->e[1].p], .01903012, 1e-6); } TEST(ApproximationTest, NodeContributionsGrover) { @@ -100,10 +100,31 @@ TEST(ApproximationTest, NodeContributionsGrover) { VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); NodeContributions contributions(root); - EXPECT_NEAR(contributions[root.p], 1., 1e-8); - EXPECT_NEAR(contributions[root.p->e[1].p], 1., 1e-8); - EXPECT_NEAR(contributions[root.p->e[1].p->e[0].p], .5, 1e-8); - EXPECT_NEAR(contributions[root.p->e[1].p->e[1].p], .5, 1e-8); + EXPECT_NEAR(contributions[root.p], 1., 1e-6); + EXPECT_NEAR(contributions[root.p->e[1].p], 1., 1e-6); + EXPECT_NEAR(contributions[root.p->e[1].p->e[0].p], .5, 1e-6); + EXPECT_NEAR(contributions[root.p->e[1].p->e[1].p], .5, 1e-6); +} + +TEST(ApproximationTest, NodeContributionsTwoCRY) { + constexpr std::size_t nq = 3; + Package dd(nq); + + qc::QuantumComputation qc(nq); + qc.h(0); + qc.cry(qc::PI / 8, 0, 1); + qc.h(1); + qc.cry(qc::PI / 8, 1, 2); + + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + NodeContributions contributions(root); + + EXPECT_NEAR(contributions[root.p], 1., 1e-6); + EXPECT_NEAR(contributions[root.p->e[0].p], .984611, 1e-6); + EXPECT_NEAR(contributions[root.p->e[1].p], .0153889, 1e-6); + EXPECT_NEAR(contributions[root.p->e[0].p->e[0].p], .595671, 1e-6); + EXPECT_NEAR(contributions[root.p->e[0].p->e[1].p], .404329, 1e-6); + EXPECT_NEAR(contributions[root.p->e[1].p->e[1].p], .404329, 1e-6); } ///----------------------------------------------------------------------------- @@ -112,24 +133,17 @@ TEST(ApproximationTest, NodeContributionsGrover) { TEST(ApproximationTest, FidelityDrivenKeepAll) { constexpr std::size_t nq = 2; - - std::mt19937_64 mt; - mt.seed(42); - - // Setup package. Package dd(nq); - // Circuit to simulate. qc::QuantumComputation qc(nq); qc.x(0); - // Call simulate. + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + constexpr Approximation approx(1.); - VectorDD res = simulate(qc, dd.makeZeroState(nq), dd, approx); - const std::string m = dd.measureAll(res, false, mt, 0.001); + applyApproximation(root, approx, dd); - EXPECT_EQ(res.size(), 3); // number of nodes + terminal. - EXPECT_EQ(m, "01"); // (kron(I, X))|00> = |01> + EXPECT_EQ(root.size(), 3); } TEST(ApproximationTest, FidelityDriven2Percent) { @@ -140,13 +154,16 @@ TEST(ApproximationTest, FidelityDriven2Percent) { qc.h(0); qc.cry(qc::PI / 8, 0, 1); + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + constexpr Approximation approx(.98); - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd, approx); + applyApproximation(root, approx, dd); + NodeContributions contributions(root); EXPECT_EQ(root.size(), 3); - EXPECT_NEAR(contributions[root.p], 1., 1e-8); - EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p], 1., 1e-6); + EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-6); } TEST(ApproximationTest, MemoryDriven2Percent) { @@ -157,13 +174,42 @@ TEST(ApproximationTest, MemoryDriven2Percent) { qc.h(0); qc.cry(qc::PI / 8, 0, 1); + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + constexpr Approximation approx(3, 0.98); - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd, approx); + applyApproximation(root, approx, dd); + NodeContributions contributions(root); EXPECT_EQ(root.size(), 3); - EXPECT_NEAR(contributions[root.p], 1., 1e-8); - EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-8); + EXPECT_NEAR(contributions[root.p], 1., 1e-6); + EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-6); +} + +TEST(ApproximationTest, MemoryDriven3Qubits) { + constexpr std::size_t nq = 3; + Package dd(nq); + + qc::QuantumComputation qc(nq); + qc.h(0); + qc.cry(qc::PI / 8, 0, 1); + qc.h(1); + qc.cry(qc::PI / 8, 1, 2); + + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + + constexpr Approximation approx(2, 0.98); + applyApproximation(root, approx, dd); + + // NodeContributions contributions(root); + + // std::cout << "contr: " << contributions[root.p] << '\n'; + // std::cout << "contr: " << contributions[root.p->e[0].p] << '\n'; + // std::cout << "contr: " << contributions[root.p->e[1].p] << '\n'; + + EXPECT_EQ(root.size(), 5); + // EXPECT_NEAR(contributions[root.p], 1., 1e-6); + // EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-6); } TEST(ApproximationTest, MemoryDrivenKeepAll) { @@ -174,8 +220,10 @@ TEST(ApproximationTest, MemoryDrivenKeepAll) { qc.h(0); qc.cry(qc::PI / 8, 0, 1); + VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + constexpr Approximation approx(4, 0.98); - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd, approx); + applyApproximation(root, approx, dd); EXPECT_EQ(root.size(), 4); } From 4a6a156e2e42fdee74627d94674c8b7c3282cb4e Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 8 Apr 2025 08:26:00 +0200 Subject: [PATCH 14/59] Add renormalization to root edge --- src/dd/Approximation.cpp | 6 +++-- test/dd/test_approximations.cpp | 41 ++++++++------------------------- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 5be08d7df..e60316009 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -10,6 +10,7 @@ #include "dd/Approximation.hpp" +#include "dd/ComplexNumbers.hpp" #include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" @@ -67,7 +68,6 @@ vEdge rebuildWithout(const VectorDD& state, const vEdge& edge, Package& dd) { rebuildWithout(state.p->e[1], edge, dd)}; return dd.makeDDNode(state.p->v, edges); - ; } }; // namespace @@ -79,15 +79,17 @@ void applyApproximation( } double budget = 1 - approx.fidelity; - while (true) { NodeContributions contributions(state); + vEdge* edge = findNext(state, budget, contributions); if (edge == nullptr) { break; } state = rebuildWithout(state, *edge, dd); + state.w = dd.cn.lookup(state.w / std::sqrt(ComplexNumbers::mag2(state.w))); + dd.incRef(state); dd.decRef(*edge); diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 39297545a..5b2a2dddd 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -146,26 +146,6 @@ TEST(ApproximationTest, FidelityDrivenKeepAll) { EXPECT_EQ(root.size(), 3); } -TEST(ApproximationTest, FidelityDriven2Percent) { - constexpr std::size_t nq = 2; - Package dd(nq); - - qc::QuantumComputation qc(nq); // first qubit with prob < 2%. - qc.h(0); - qc.cry(qc::PI / 8, 0, 1); - - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - - constexpr Approximation approx(.98); - applyApproximation(root, approx, dd); - - NodeContributions contributions(root); - - EXPECT_EQ(root.size(), 3); - EXPECT_NEAR(contributions[root.p], 1., 1e-6); - EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-6); -} - TEST(ApproximationTest, MemoryDriven2Percent) { constexpr std::size_t nq = 2; Package dd(nq); @@ -179,14 +159,15 @@ TEST(ApproximationTest, MemoryDriven2Percent) { constexpr Approximation approx(3, 0.98); applyApproximation(root, approx, dd); - NodeContributions contributions(root); - EXPECT_EQ(root.size(), 3); + EXPECT_EQ(root.p->e[1], vEdge::zero()); + + NodeContributions contributions(root); EXPECT_NEAR(contributions[root.p], 1., 1e-6); EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-6); } -TEST(ApproximationTest, MemoryDriven3Qubits) { +TEST(ApproximationTest, MemoryDrivenTwoCRY) { constexpr std::size_t nq = 3; Package dd(nq); @@ -198,18 +179,14 @@ TEST(ApproximationTest, MemoryDriven3Qubits) { VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - constexpr Approximation approx(2, 0.98); + constexpr Approximation approx(5, 0.98); applyApproximation(root, approx, dd); - // NodeContributions contributions(root); - - // std::cout << "contr: " << contributions[root.p] << '\n'; - // std::cout << "contr: " << contributions[root.p->e[0].p] << '\n'; - // std::cout << "contr: " << contributions[root.p->e[1].p] << '\n'; - EXPECT_EQ(root.size(), 5); - // EXPECT_NEAR(contributions[root.p], 1., 1e-6); - // EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-6); + EXPECT_EQ(root.p->e[1], vEdge::zero()); + + NodeContributions contributions(root); + EXPECT_NEAR(contributions[root.p], 1., 1e-6); } TEST(ApproximationTest, MemoryDrivenKeepAll) { From 378800ab78d4b6c0d673ada4afe143a9d74d876d Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 11 Apr 2025 14:01:39 +0200 Subject: [PATCH 15/59] 1-traversal approximation --- include/mqt-core/dd/Approximation.hpp | 51 +------- include/mqt-core/dd/Simulation.hpp | 6 +- src/dd/Approximation.cpp | 115 +++++------------ src/dd/Simulation.cpp | 1 - test/dd/test_approximations.cpp | 174 ++++---------------------- 5 files changed, 55 insertions(+), 292 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 8fe7213e1..50c2ca204 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -10,13 +10,13 @@ #pragma once -#include "dd/ComplexValue.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" #include namespace dd { + enum ApproximationStrategy { None, FidelityDriven, MemoryDriven }; template struct Approximation {}; @@ -37,52 +37,5 @@ struct Approximation : public Approximation { std::size_t threshold; }; -class NodeContributions { -public: - explicit NodeContributions(const vEdge& root) { - compute(root, ComplexValue{root.w}); - } - - double operator[](const vNode* key) noexcept { return contributions[key]; } - -private: - using Map = std::unordered_map; - - /** - * @brief Recursively compute contributions for each node. - * @details Propagates downwards until it reaches a terminal. Then, return - * squared magnitude of amplitudes or 0. Sum these values for all - * edges of node. Since nodes can be visited multiple times by edges - * going inwards we add the sums to the value stored in the map for - * the respective node - * - * The accumulator computes the amplitude. - */ - double compute(const vEdge& edge, const ComplexValue acc) { - // Reached the end of a path. Either return 0 or squared magnitude. - if (edge.isZeroTerminal()) { - return 0.; - } - if (edge.isTerminal()) { - return acc.mag2(); - } - - // Compute the contribution of each node, which is the sum of - // squared magnitudes of amplitudes for each path passing through that node. - double sum = 0.; - const vNode* node = edge.p; - for (const auto& e : node->e) { - sum += compute(e, acc * e.w); - } - contributions[node] += sum; - - return sum; - } - - Map contributions{}; -}; - -template -void applyApproximation(VectorDD& state, const Approximation& approx, - Package& dd); +VectorDD approximate(const VectorDD& state, double fidelity, Package& dd); } // namespace dd diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp index cc7083b1a..c4e0fdf00 100644 --- a/include/mqt-core/dd/Simulation.hpp +++ b/include/mqt-core/dd/Simulation.hpp @@ -43,17 +43,13 @@ namespace dd { * mid-circuit measurements, see @ref sample(const QuantumComputation&, * std::size_t, std::size_t). * - * @tparam stgy The approximation strategy applied. * @param qc The quantum computation to simulate * @param in The input state to simulate. Represented as a vector DD. * @param dd The DD package to use for the simulation - * @param approx The object describing the approximation strategy. * @return A vector DD representing the output state of the simulation */ -template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, - Package& dd, - const Approximation& approx = Approximation{}); + Package& dd); /** * @brief Sample from the output distribution of a quantum computation diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index e60316009..c42a5e003 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -11,105 +11,52 @@ #include "dd/Approximation.hpp" #include "dd/ComplexNumbers.hpp" -#include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" -#include -#include - namespace dd { namespace { -/** - * @brief Find next node to remove and return its ingoing edge. - * @details Traverses breadth-first using the iterative deepening algorithm. - * @return Edge to remove or nullptr. - */ -vEdge* findNext(const VectorDD& state, const double budget, - NodeContributions& contributions) { - std::deque q = {state.p}; - while (!q.empty()) { - vNode* curr = q.front(); - q.pop_front(); - - for (std::size_t i = 0; i < 2; i++) { - vEdge* edge = &curr->e[i]; - if (edge->isTerminal()) { - continue; - } - - vNode* nxt = edge->p; - if (std::find(q.begin(), q.end(), nxt) == q.end()) { - if (budget > contributions[nxt]) { - return edge; - } - q.push_back(nxt); - } - } +struct Approx { + vEdge edge; + double contrib; +}; +Approx approximate(const vEdge& curr, const ComplexValue& amplitude, + double budget, Package& dd) { + if (curr.isTerminal()) { + return {curr, curr.w.exactlyZero() ? 0 : amplitude.mag2()}; } - return nullptr; -} + double sum{}; + const vNode* node = curr.p; -/** - * @brief Recursively rebuild `state` without `edge` edge. - * @return Edge to new `state`. - */ -vEdge rebuildWithout(const VectorDD& state, const vEdge& edge, Package& dd) { - if (state.isTerminal()) { - return state; - } + std::array edges{}; + for (std::size_t i = 0; i < edges.size(); i++) { + const auto& edge = node->e[i]; - if (state == edge) { - return vEdge::zero(); + const auto& p = approximate(edge, amplitude * edge.w, budget, dd); + if (p.edge.isTerminal() || p.contrib > budget) { + edges[i] = p.edge; + } else { + edges[i] = vEdge::zero(); + std::cout << "budget: " << budget << " contrib: " << p.contrib << '\n'; + budget -= p.contrib; + } + sum += p.contrib; } - std::array edges{rebuildWithout(state.p->e[0], edge, dd), - rebuildWithout(state.p->e[1], edge, dd)}; - - return dd.makeDDNode(state.p->v, edges); + return {dd.makeDDNode(node->v, edges), sum}; } }; // namespace -template <> -void applyApproximation( - VectorDD& state, const Approximation& approx, Package& dd) { - if (state.isTerminal()) { - return; - } +VectorDD approximate(const VectorDD& state, const double fidelity, + Package& dd) { + const ComplexValue amplitude{state.w}; + const double budget = 1 - fidelity; + const Approx result = approximate(state, amplitude, budget, dd); - double budget = 1 - approx.fidelity; - while (true) { - NodeContributions contributions(state); - - vEdge* edge = findNext(state, budget, contributions); - if (edge == nullptr) { - break; - } - - state = rebuildWithout(state, *edge, dd); - state.w = dd.cn.lookup(state.w / std::sqrt(ComplexNumbers::mag2(state.w))); - - dd.incRef(state); - dd.decRef(*edge); - - budget -= contributions[edge->p]; - } - - dd.garbageCollect(); -}; - -/** - * @brief Apply Memory-Driven Approximation. - * @details If the threshold is exceeded, apply Fidelity-Driven approximation. - * Inheritance allows us to simply downcast the Memory-Driven object. - */ -template <> -void applyApproximation( - VectorDD& state, const Approximation& approx, Package& dd) { - if (state.size() > approx.threshold) { - applyApproximation(state, approx, dd); - } + VectorDD out = result.edge; + out.w = dd.cn.lookup(out.w / std::sqrt(ComplexNumbers::mag2(out.w))); + return out; }; } // namespace dd diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp index 2071cae8d..7df38ef97 100644 --- a/src/dd/Simulation.cpp +++ b/src/dd/Simulation.cpp @@ -211,7 +211,6 @@ std::map sample(const qc::QuantumComputation& qc, return counts; } -template VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, Package& dd) { auto permutation = qc.initialLayout; diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 5b2a2dddd..95c0abe0f 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -18,135 +18,24 @@ using namespace dd; -///----------------------------------------------------------------------------- -/// \n compute node contributions \n -///----------------------------------------------------------------------------- - -TEST(ApproximationTest, NodeContributionsSingleEdge) { - constexpr std::size_t nq = 1; - Package dd(nq); - - qc::QuantumComputation qc(nq); // i|1> - qc.x(0); - qc.s(0); - - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - NodeContributions contributions(root); - - EXPECT_NEAR(contributions[root.p], 1., 1e-6); -} - -TEST(ApproximationTest, NodeContributionsGHZ) { - constexpr std::size_t nq = 2; - Package dd(nq); - - qc::QuantumComputation qc(nq); // 1/sqrt(2) * (|00> + |11>) - qc.h(1); - qc.cx(1, 0); - - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - NodeContributions contributions(root); - - EXPECT_NEAR(contributions[root.p], 1., 1e-6); - EXPECT_NEAR(contributions[root.p->e[0].p], .5, 1e-6); - EXPECT_NEAR(contributions[root.p->e[1].p], .5, 1e-6); -} - -TEST(ApproximationTest, NodeContributionsDoubleVisit) { - constexpr std::size_t nq = 2; - Package dd(nq); - - qc::QuantumComputation qc(nq); // 1/2 * (|00> - |01> + |10> - |11>) - qc.h(1); - qc.x(0); - qc.h(0); - - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - NodeContributions contributions(root); - - EXPECT_NEAR(contributions[root.p], 1., 1e-6); - EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-6); -} - -TEST(ApproximationTest, NodeContributions2Percent) { - constexpr std::size_t nq = 2; - Package dd(nq); - - qc::QuantumComputation qc(nq); // first qubit with prob < 2%. - qc.h(0); - qc.cry(qc::PI / 8, 0, 1); - - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - NodeContributions contributions(root); - - EXPECT_NEAR(contributions[root.p], 1., 1e-6); - EXPECT_NEAR(contributions[root.p->e[0].p], .98096988, 1e-6); - EXPECT_NEAR(contributions[root.p->e[1].p], .01903012, 1e-6); -} - -TEST(ApproximationTest, NodeContributionsGrover) { - constexpr std::size_t nq = 3; - Package dd(nq); - - // Grover after 1st H in diffusion (Oracle |11>). - qc::QuantumComputation qc(nq); - qc.h(0); - qc.h(1); - qc.x(2); - qc.mcz(qc::Controls{0, 1}, 2); - qc.h(0); - qc.h(1); - - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - NodeContributions contributions(root); - - EXPECT_NEAR(contributions[root.p], 1., 1e-6); - EXPECT_NEAR(contributions[root.p->e[1].p], 1., 1e-6); - EXPECT_NEAR(contributions[root.p->e[1].p->e[0].p], .5, 1e-6); - EXPECT_NEAR(contributions[root.p->e[1].p->e[1].p], .5, 1e-6); -} - -TEST(ApproximationTest, NodeContributionsTwoCRY) { - constexpr std::size_t nq = 3; - Package dd(nq); - - qc::QuantumComputation qc(nq); - qc.h(0); - qc.cry(qc::PI / 8, 0, 1); - qc.h(1); - qc.cry(qc::PI / 8, 1, 2); - - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - NodeContributions contributions(root); - - EXPECT_NEAR(contributions[root.p], 1., 1e-6); - EXPECT_NEAR(contributions[root.p->e[0].p], .984611, 1e-6); - EXPECT_NEAR(contributions[root.p->e[1].p], .0153889, 1e-6); - EXPECT_NEAR(contributions[root.p->e[0].p->e[0].p], .595671, 1e-6); - EXPECT_NEAR(contributions[root.p->e[0].p->e[1].p], .404329, 1e-6); - EXPECT_NEAR(contributions[root.p->e[1].p->e[1].p], .404329, 1e-6); -} - ///----------------------------------------------------------------------------- /// \n simulate with approximation \n ///----------------------------------------------------------------------------- -TEST(ApproximationTest, FidelityDrivenKeepAll) { +TEST(ApproximationTest, KeepAll) { constexpr std::size_t nq = 2; Package dd(nq); qc::QuantumComputation qc(nq); qc.x(0); - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - - constexpr Approximation approx(1.); - applyApproximation(root, approx, dd); - - EXPECT_EQ(root.size(), 3); + auto root = simulate(qc, dd.makeZeroState(nq), dd); + auto approx = approximate(root, 0.5, dd); + EXPECT_EQ(root, approx); + EXPECT_EQ(dd.fidelity(root, approx), 1); } -TEST(ApproximationTest, MemoryDriven2Percent) { +TEST(ApproximationTest, RemoveOneBottom) { constexpr std::size_t nq = 2; Package dd(nq); @@ -154,20 +43,17 @@ TEST(ApproximationTest, MemoryDriven2Percent) { qc.h(0); qc.cry(qc::PI / 8, 0, 1); - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - - constexpr Approximation approx(3, 0.98); - applyApproximation(root, approx, dd); - - EXPECT_EQ(root.size(), 3); - EXPECT_EQ(root.p->e[1], vEdge::zero()); + auto root = simulate(qc, dd.makeZeroState(nq), dd); + auto approx = approximate(root, 0.98, dd); - NodeContributions contributions(root); - EXPECT_NEAR(contributions[root.p], 1., 1e-6); - EXPECT_NEAR(contributions[root.p->e[0].p], 1., 1e-6); + EXPECT_EQ(root.size(), 4); + EXPECT_EQ(approx.size(), 3); + EXPECT_NE(root, approx); + EXPECT_EQ(approx.p->e[1], vEdge::zero()); + EXPECT_NEAR(dd.fidelity(root, approx), 0.98, 1e-2); } -TEST(ApproximationTest, MemoryDrivenTwoCRY) { +TEST(ApproximationTest, RemoveOneMiddle) { constexpr std::size_t nq = 3; Package dd(nq); @@ -177,30 +63,12 @@ TEST(ApproximationTest, MemoryDrivenTwoCRY) { qc.h(1); qc.cry(qc::PI / 8, 1, 2); - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); - - constexpr Approximation approx(5, 0.98); - applyApproximation(root, approx, dd); - - EXPECT_EQ(root.size(), 5); - EXPECT_EQ(root.p->e[1], vEdge::zero()); - - NodeContributions contributions(root); - EXPECT_NEAR(contributions[root.p], 1., 1e-6); -} - -TEST(ApproximationTest, MemoryDrivenKeepAll) { - constexpr std::size_t nq = 2; - Package dd(nq); - - qc::QuantumComputation qc(nq); // first qubit with prob < 2%. - qc.h(0); - qc.cry(qc::PI / 8, 0, 1); - - VectorDD root = simulate(qc, dd.makeZeroState(nq), dd); + auto root = simulate(qc, dd.makeZeroState(nq), dd); + auto approx = approximate(root, 0.98, dd); - constexpr Approximation approx(4, 0.98); - applyApproximation(root, approx, dd); - - EXPECT_EQ(root.size(), 4); + EXPECT_EQ(root.size(), 6); + EXPECT_EQ(approx.size(), 5); + EXPECT_NE(root, approx); + EXPECT_EQ(approx.p->e[1], vEdge::zero()); + EXPECT_NEAR(dd.fidelity(root, approx), 0.98, 1e-2); } From 0c77295cb60ecc8414a2cc8d04b1f7723d95ba34 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sat, 12 Apr 2025 08:01:11 +0200 Subject: [PATCH 16/59] Apply correct edge weights and update tests --- include/mqt-core/dd/Approximation.hpp | 20 ----------- src/dd/Approximation.cpp | 28 +++++++-------- test/dd/test_approximations.cpp | 49 ++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 39 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 50c2ca204..42605b12a 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -13,29 +13,9 @@ #include "dd/Node.hpp" #include "dd/Package.hpp" -#include - namespace dd { enum ApproximationStrategy { None, FidelityDriven, MemoryDriven }; -template struct Approximation {}; - -template <> struct Approximation {}; - -template <> struct Approximation { - constexpr explicit Approximation(double fidelity) noexcept - : fidelity(fidelity) {} - double fidelity; -}; - -template <> -struct Approximation : public Approximation { - constexpr Approximation(std::size_t threshold, double fidelity) noexcept - : Approximation(fidelity), threshold(threshold) {} - - std::size_t threshold; -}; - VectorDD approximate(const VectorDD& state, double fidelity, Package& dd); } // namespace dd diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index c42a5e003..10e6a5859 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -26,25 +26,26 @@ Approx approximate(const vEdge& curr, const ComplexValue& amplitude, return {curr, curr.w.exactlyZero() ? 0 : amplitude.mag2()}; } - double sum{}; const vNode* node = curr.p; + double sum{}; std::array edges{}; for (std::size_t i = 0; i < edges.size(); i++) { - const auto& edge = node->e[i]; + const vEdge& edge = node->e[i]; - const auto& p = approximate(edge, amplitude * edge.w, budget, dd); - if (p.edge.isTerminal() || p.contrib > budget) { - edges[i] = p.edge; + const Approx& ap = approximate(edge, amplitude * edge.w, budget, dd); + if (ap.edge.isTerminal() || ap.contrib > budget) { + edges[i] = ap.edge; + sum += ap.contrib; } else { edges[i] = vEdge::zero(); - std::cout << "budget: " << budget << " contrib: " << p.contrib << '\n'; - budget -= p.contrib; + budget -= ap.contrib; } - sum += p.contrib; } - return {dd.makeDDNode(node->v, edges), sum}; + vEdge next = dd.makeDDNode(node->v, edges); + next.w = dd.cn.lookup(next.w * curr.w); + return {next, sum}; } }; // namespace @@ -52,11 +53,10 @@ VectorDD approximate(const VectorDD& state, const double fidelity, Package& dd) { const ComplexValue amplitude{state.w}; const double budget = 1 - fidelity; - const Approx result = approximate(state, amplitude, budget, dd); - - VectorDD out = result.edge; - out.w = dd.cn.lookup(out.w / std::sqrt(ComplexNumbers::mag2(out.w))); - return out; + Approx ap = approximate(state, amplitude, budget, dd); + ap.edge.w = + dd.cn.lookup(ap.edge.w / std::sqrt(ComplexNumbers::mag2(ap.edge.w))); + return ap.edge; }; } // namespace dd diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 95c0abe0f..257c9de2c 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -9,34 +9,53 @@ */ #include "dd/Approximation.hpp" +#include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" #include "dd/Simulation.hpp" #include "ir/QuantumComputation.hpp" +#include #include +#include using namespace dd; +std::complex getNorm(std::vector> vec) { + return std::inner_product(vec.begin(), vec.end(), vec.begin(), + std::complex()); +} + ///----------------------------------------------------------------------------- /// \n simulate with approximation \n ///----------------------------------------------------------------------------- TEST(ApproximationTest, KeepAll) { constexpr std::size_t nq = 2; + constexpr double fidelity = 1; Package dd(nq); qc::QuantumComputation qc(nq); qc.x(0); auto root = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(root, 0.5, dd); + auto approx = approximate(root, fidelity, dd); + + auto norm = getNorm(approx.getVector()); + + // no nodes deleted. must be the same. EXPECT_EQ(root, approx); + // final fidelity is correct. EXPECT_EQ(dd.fidelity(root, approx), 1); + // norm must be one. + EXPECT_NEAR(norm.real(), 1., 1e-6); + EXPECT_NEAR(norm.imag(), 0., 1e-6); } TEST(ApproximationTest, RemoveOneBottom) { constexpr std::size_t nq = 2; + constexpr double fidelity = 0.98; + Package dd(nq); qc::QuantumComputation qc(nq); // first qubit with prob < 2%. @@ -44,17 +63,28 @@ TEST(ApproximationTest, RemoveOneBottom) { qc.cry(qc::PI / 8, 0, 1); auto root = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(root, 0.98, dd); + auto approx = approximate(root, fidelity, dd); + + auto norm = getNorm(approx.getVector()); + // has correct number of nodes. EXPECT_EQ(root.size(), 4); EXPECT_EQ(approx.size(), 3); + // can't be the same. EXPECT_NE(root, approx); + // correct edge is deleted. EXPECT_EQ(approx.p->e[1], vEdge::zero()); - EXPECT_NEAR(dd.fidelity(root, approx), 0.98, 1e-2); + // final fidelity is correct. + EXPECT_NEAR(dd.fidelity(root, approx), fidelity, 1e-2); + // norm must be one. + EXPECT_NEAR(norm.real(), 1., 1e-6); + EXPECT_NEAR(norm.imag(), 0., 1e-6); } TEST(ApproximationTest, RemoveOneMiddle) { constexpr std::size_t nq = 3; + constexpr double fidelity = 0.98; + Package dd(nq); qc::QuantumComputation qc(nq); @@ -64,11 +94,20 @@ TEST(ApproximationTest, RemoveOneMiddle) { qc.cry(qc::PI / 8, 1, 2); auto root = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(root, 0.98, dd); + auto approx = approximate(root, fidelity, dd); + + auto norm = getNorm(approx.getVector()); + // has correct number of nodes. EXPECT_EQ(root.size(), 6); EXPECT_EQ(approx.size(), 5); + // can't be the same. EXPECT_NE(root, approx); + // correct edge is deleted. EXPECT_EQ(approx.p->e[1], vEdge::zero()); - EXPECT_NEAR(dd.fidelity(root, approx), 0.98, 1e-2); + // final fidelity is correct. + EXPECT_NEAR(dd.fidelity(root, approx), fidelity, 1e-2); + // norm must be one. + EXPECT_NEAR(norm.real(), 1., 1e-6); + EXPECT_NEAR(norm.imag(), 0., 1e-6); } From 7dce932b412ca5171238d59446f93ace63a8ea0e Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sat, 12 Apr 2025 08:06:56 +0200 Subject: [PATCH 17/59] Fix includes --- src/dd/Approximation.cpp | 5 +++++ test/dd/test_approximations.cpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 10e6a5859..38d38609d 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -11,9 +11,14 @@ #include "dd/Approximation.hpp" #include "dd/ComplexNumbers.hpp" +#include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" +#include +#include +#include + namespace dd { namespace { struct Approx { diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 257c9de2c..e63eeab62 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -13,9 +13,11 @@ #include "dd/Node.hpp" #include "dd/Package.hpp" #include "dd/Simulation.hpp" +#include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" #include +#include #include #include From 40f63c31aa9d10cda9d76fbe5960434c6feb042b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 20 Apr 2025 12:11:34 +0200 Subject: [PATCH 18/59] Add single traversal breadth-first algorithm --- include/mqt-core/dd/Approximation.hpp | 3 +- src/dd/Approximation.cpp | 77 ++++++++++++--------------- test/dd/test_approximations.cpp | 42 ++++++--------- 3 files changed, 52 insertions(+), 70 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 42605b12a..8f5543b05 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -15,7 +15,6 @@ namespace dd { -enum ApproximationStrategy { None, FidelityDriven, MemoryDriven }; +void approximate(VectorDD& state, double fidelity, Package& dd); -VectorDD approximate(const VectorDD& state, double fidelity, Package& dd); } // namespace dd diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 38d38609d..a9556a1ae 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -11,57 +11,50 @@ #include "dd/Approximation.hpp" #include "dd/ComplexNumbers.hpp" -#include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" -#include -#include -#include +#include +#include namespace dd { -namespace { -struct Approx { - vEdge edge; - double contrib; -}; -Approx approximate(const vEdge& curr, const ComplexValue& amplitude, - double budget, Package& dd) { - if (curr.isTerminal()) { - return {curr, curr.w.exactlyZero() ? 0 : amplitude.mag2()}; - } - - const vNode* node = curr.p; - double sum{}; - std::array edges{}; - for (std::size_t i = 0; i < edges.size(); i++) { - const vEdge& edge = node->e[i]; - - const Approx& ap = approximate(edge, amplitude * edge.w, budget, dd); - if (ap.edge.isTerminal() || ap.contrib > budget) { - edges[i] = ap.edge; - sum += ap.contrib; - } else { - edges[i] = vEdge::zero(); - budget -= ap.contrib; +void approximate(VectorDD& state, const double fidelity, Package& dd) { + constexpr auto mag2 = ComplexNumbers::mag2; + + double budget = 1 - fidelity; + + std::unordered_map probs{{&state, mag2(state.w)}}; + std::deque q{&state}; + while (!q.empty() && budget > 0) { + std::vector layer(q.begin(), q.end()); + + q.clear(); + for (vEdge* lEdge : layer) { + vNode* node = lEdge->p; + const double parent = probs[lEdge]; + + if (parent <= budget) { + dd.decRef(*lEdge); + *lEdge = vEdge::zero(); + budget -= parent; + } else { + for (auto& edge : node->e) { + if (!edge.isTerminal() && !edge.w.exactlyZero()) { + if (probs.find(&edge) == probs.end()) { + q.push_back(&edge); + } + + probs[&edge] += parent * mag2(edge.w); + } + } + } } } - vEdge next = dd.makeDDNode(node->v, edges); - next.w = dd.cn.lookup(next.w * curr.w); - return {next, sum}; + auto& mm = dd.getMemoryManager(); + state = vEdge::normalize(state.p, state.p->e, mm, dd.cn); + state.w = dd.cn.lookup(state.w / std::sqrt(mag2(state.w))); } -}; // namespace - -VectorDD approximate(const VectorDD& state, const double fidelity, - Package& dd) { - const ComplexValue amplitude{state.w}; - const double budget = 1 - fidelity; - Approx ap = approximate(state, amplitude, budget, dd); - ap.edge.w = - dd.cn.lookup(ap.edge.w / std::sqrt(ComplexNumbers::mag2(ap.edge.w))); - return ap.edge; -}; } // namespace dd diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index e63eeab62..bdc59166e 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -23,10 +23,12 @@ using namespace dd; +namespace { std::complex getNorm(std::vector> vec) { return std::inner_product(vec.begin(), vec.end(), vec.begin(), std::complex()); } +}; // namespace ///----------------------------------------------------------------------------- /// \n simulate with approximation \n @@ -40,15 +42,13 @@ TEST(ApproximationTest, KeepAll) { qc::QuantumComputation qc(nq); qc.x(0); - auto root = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(root, fidelity, dd); + auto state = simulate(qc, dd.makeZeroState(nq), dd); + approximate(state, fidelity, dd); - auto norm = getNorm(approx.getVector()); + auto norm = getNorm(state.getVector()); // no nodes deleted. must be the same. - EXPECT_EQ(root, approx); - // final fidelity is correct. - EXPECT_EQ(dd.fidelity(root, approx), 1); + EXPECT_EQ(state.size(), 3); // norm must be one. EXPECT_NEAR(norm.real(), 1., 1e-6); EXPECT_NEAR(norm.imag(), 0., 1e-6); @@ -64,20 +64,15 @@ TEST(ApproximationTest, RemoveOneBottom) { qc.h(0); qc.cry(qc::PI / 8, 0, 1); - auto root = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(root, fidelity, dd); + auto state = simulate(qc, dd.makeZeroState(nq), dd); + approximate(state, fidelity, dd); - auto norm = getNorm(approx.getVector()); + auto norm = getNorm(state.getVector()); // has correct number of nodes. - EXPECT_EQ(root.size(), 4); - EXPECT_EQ(approx.size(), 3); - // can't be the same. - EXPECT_NE(root, approx); + EXPECT_EQ(state.size(), 3); // correct edge is deleted. - EXPECT_EQ(approx.p->e[1], vEdge::zero()); - // final fidelity is correct. - EXPECT_NEAR(dd.fidelity(root, approx), fidelity, 1e-2); + EXPECT_EQ(state.p->e[1], vEdge::zero()); // norm must be one. EXPECT_NEAR(norm.real(), 1., 1e-6); EXPECT_NEAR(norm.imag(), 0., 1e-6); @@ -95,20 +90,15 @@ TEST(ApproximationTest, RemoveOneMiddle) { qc.h(1); qc.cry(qc::PI / 8, 1, 2); - auto root = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(root, fidelity, dd); + auto state = simulate(qc, dd.makeZeroState(nq), dd); + approximate(state, fidelity, dd); - auto norm = getNorm(approx.getVector()); + auto norm = getNorm(state.getVector()); // has correct number of nodes. - EXPECT_EQ(root.size(), 6); - EXPECT_EQ(approx.size(), 5); - // can't be the same. - EXPECT_NE(root, approx); + EXPECT_EQ(state.size(), 5); // correct edge is deleted. - EXPECT_EQ(approx.p->e[1], vEdge::zero()); - // final fidelity is correct. - EXPECT_NEAR(dd.fidelity(root, approx), fidelity, 1e-2); + EXPECT_EQ(state.p->e[1], vEdge::zero()); // norm must be one. EXPECT_NEAR(norm.real(), 1., 1e-6); EXPECT_NEAR(norm.imag(), 0., 1e-6); From 92dba38e7eef24866c31fa7e543edbb33c76dd8f Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 20 Apr 2025 12:31:16 +0200 Subject: [PATCH 19/59] Fix linting issues --- src/dd/Approximation.cpp | 4 +++- src/dd/Simulation.cpp | 2 -- test/dd/test_approximations.cpp | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index a9556a1ae..07237a061 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -14,8 +14,10 @@ #include "dd/Node.hpp" #include "dd/Package.hpp" +#include #include #include +#include namespace dd { @@ -27,7 +29,7 @@ void approximate(VectorDD& state, const double fidelity, Package& dd) { std::unordered_map probs{{&state, mag2(state.w)}}; std::deque q{&state}; while (!q.empty() && budget > 0) { - std::vector layer(q.begin(), q.end()); + const std::vector layer(q.begin(), q.end()); q.clear(); for (vEdge* lEdge : layer) { diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp index 7df38ef97..bd59db475 100644 --- a/src/dd/Simulation.cpp +++ b/src/dd/Simulation.cpp @@ -10,8 +10,6 @@ #include "dd/Simulation.hpp" -#include "dd/Approximation.hpp" -#include "dd/Complex.hpp" #include "dd/Node.hpp" #include "dd/Operations.hpp" #include "dd/Package.hpp" diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index bdc59166e..861cfacd1 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -20,6 +20,7 @@ #include #include #include +#include using namespace dd; From 052f6f6719b2a91bc43766b6c295f413231bab4e Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 20 Apr 2025 12:39:59 +0200 Subject: [PATCH 20/59] Add doxygen --- include/mqt-core/dd/Approximation.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 8f5543b05..7c9db6210 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -15,6 +15,16 @@ namespace dd { +/** + * @brief Approximate the input state to a given final fidelity. + * + * @details Traverses the decision diagram layer by layer in a breadth-first + * manner and eliminates edges greedily until the budget is exhausted. + * + * @param state The DD to approximate. + * @param fidelity The desired final fidelity after approximation. + * @param dd The DD package to use for the simulation + */ void approximate(VectorDD& state, double fidelity, Package& dd); } // namespace dd From 014f36973238aad9e198ba5559b2f4e95bdb4fc6 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 24 Apr 2025 08:28:04 +0200 Subject: [PATCH 21/59] Remove includes in Simulation.hpp and Simulation.cpp --- include/mqt-core/dd/Simulation.hpp | 1 - src/dd/Simulation.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp index c4e0fdf00..028414a7c 100644 --- a/include/mqt-core/dd/Simulation.hpp +++ b/include/mqt-core/dd/Simulation.hpp @@ -14,7 +14,6 @@ #pragma once -#include "dd/Approximation.hpp" #include "dd/Package_fwd.hpp" #include diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp index bd59db475..a2f099859 100644 --- a/src/dd/Simulation.cpp +++ b/src/dd/Simulation.cpp @@ -10,11 +10,9 @@ #include "dd/Simulation.hpp" -#include "dd/Node.hpp" #include "dd/Operations.hpp" #include "dd/Package.hpp" #include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/ClassicControlledOperation.hpp" #include "ir/operations/NonUnitaryOperation.hpp" From 37807399c39d9a433d45b153e71f4b9cffc3b138 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 24 Apr 2025 09:22:34 +0200 Subject: [PATCH 22/59] Use std::forward_list instead of std::deque --- src/dd/Approximation.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 07237a061..e82d4b4a4 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -15,43 +15,44 @@ #include "dd/Package.hpp" #include -#include +#include #include -#include namespace dd { void approximate(VectorDD& state, const double fidelity, Package& dd) { constexpr auto mag2 = ComplexNumbers::mag2; - double budget = 1 - fidelity; + std::unordered_map contributions{ + {&state, mag2(state.w)}}; + std::forward_list layer{&state}; - std::unordered_map probs{{&state, mag2(state.w)}}; - std::deque q{&state}; - while (!q.empty() && budget > 0) { - const std::vector layer(q.begin(), q.end()); + double budget = 1 - fidelity; + while (!layer.empty() && budget > 0) { + std::forward_list nextLayer{}; - q.clear(); for (vEdge* lEdge : layer) { vNode* node = lEdge->p; - const double parent = probs[lEdge]; + const double contribution = contributions[lEdge]; - if (parent <= budget) { + if (contribution <= budget) { dd.decRef(*lEdge); *lEdge = vEdge::zero(); - budget -= parent; + budget -= contribution; } else { for (auto& edge : node->e) { if (!edge.isTerminal() && !edge.w.exactlyZero()) { - if (probs.find(&edge) == probs.end()) { - q.push_back(&edge); + if (contributions.find(&edge) == contributions.end()) { + nextLayer.emplace_front(&edge); } - probs[&edge] += parent * mag2(edge.w); + contributions[&edge] += contribution * mag2(edge.w); } } } } + + layer = std::move(nextLayer); } auto& mm = dd.getMemoryManager(); From bf74fe71965209e198c62cc0ec8af31ccdc6f28a Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 24 Apr 2025 09:44:15 +0200 Subject: [PATCH 23/59] Add OneQubitApproximation test with ry gate --- src/dd/Approximation.cpp | 39 +++++++++++++++++++-------------- test/dd/test_approximations.cpp | 35 ++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index e82d4b4a4..4198c11ab 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -21,38 +21,43 @@ namespace dd { void approximate(VectorDD& state, const double fidelity, Package& dd) { + using ContributionMap = std::unordered_map; + using Layer = std::forward_list; + constexpr auto mag2 = ComplexNumbers::mag2; - std::unordered_map contributions{ - {&state, mag2(state.w)}}; - std::forward_list layer{&state}; + Layer l{&state}; + ContributionMap m{{&state, mag2(state.w)}}; double budget = 1 - fidelity; - while (!layer.empty() && budget > 0) { - std::forward_list nextLayer{}; + while (!l.empty() && budget > 0) { + Layer nextL{}; + ContributionMap nextM{}; - for (vEdge* lEdge : layer) { - vNode* node = lEdge->p; - const double contribution = contributions[lEdge]; + for (vEdge* edge : l) { + const double contribution = m[edge]; if (contribution <= budget) { - dd.decRef(*lEdge); - *lEdge = vEdge::zero(); + dd.decRef(*edge); + *edge = vEdge::zero(); budget -= contribution; - } else { - for (auto& edge : node->e) { - if (!edge.isTerminal() && !edge.w.exactlyZero()) { - if (contributions.find(&edge) == contributions.end()) { - nextLayer.emplace_front(&edge); + } else if (!edge->isTerminal()) { + vNode* node = edge->p; + for (auto& nextEdge : node->e) { + if (!nextEdge.w.exactlyZero()) { + + if (nextM.find(&nextEdge) == nextM.end()) { + nextL.emplace_front(&nextEdge); } - contributions[&edge] += contribution * mag2(edge.w); + nextM[&nextEdge] += contribution * mag2(nextEdge.w); } } } } - layer = std::move(nextLayer); + l = std::move(nextL); + m = std::move(nextM); } auto& mm = dd.getMemoryManager(); diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 861cfacd1..af8d8d82d 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -35,24 +35,43 @@ std::complex getNorm(std::vector> vec) { /// \n simulate with approximation \n ///----------------------------------------------------------------------------- -TEST(ApproximationTest, KeepAll) { - constexpr std::size_t nq = 2; +TEST(ApproximationTest, OneQubitKeepAll) { + constexpr std::size_t nq = 1; constexpr double fidelity = 1; + Package dd(nq); qc::QuantumComputation qc(nq); qc.x(0); + // |state⟩ = |1⟩ auto state = simulate(qc, dd.makeZeroState(nq), dd); + // fidelity is 1 → keep all nodes: |1⟩ approximate(state, fidelity, dd); - auto norm = getNorm(state.getVector()); + CVec expected{{0}, {1}}; // expected state vector for |1⟩: [0, 1] + EXPECT_EQ(state.getVector(), expected); + EXPECT_EQ(state.size(), 2); // no nodes deleted: root node + terminal. +} - // no nodes deleted. must be the same. - EXPECT_EQ(state.size(), 3); - // norm must be one. - EXPECT_NEAR(norm.real(), 1., 1e-6); - EXPECT_NEAR(norm.imag(), 0., 1e-6); +TEST(ApproximationTest, OneQubitApproximation) { + constexpr std::size_t nq = 1; + constexpr double fidelity = 1 - 0.25; + + Package dd(nq); + + qc::QuantumComputation qc(nq); + qc.ry(qc::PI / 3, 0); + + // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + auto state = simulate(qc, dd.makeZeroState(nq), dd); + + // eliminate |1⟩ with contrib 0.25 → |0⟩ + approximate(state, fidelity, dd); + + CVec expected{{1}, {0}}; // expected state vector for |0⟩: [1, 0] + EXPECT_EQ(state.getVector(), expected); + EXPECT_EQ(state.size(), 2); // no nodes deleted: root node + terminal. } TEST(ApproximationTest, RemoveOneBottom) { From 1543950f1e02a3f1bfe851652369e266d7724bf2 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 24 Apr 2025 14:11:53 +0200 Subject: [PATCH 24/59] Add rebuild function --- include/mqt-core/dd/Approximation.hpp | 2 +- src/dd/Approximation.cpp | 42 ++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 7c9db6210..3817a1862 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -25,6 +25,6 @@ namespace dd { * @param fidelity The desired final fidelity after approximation. * @param dd The DD package to use for the simulation */ -void approximate(VectorDD& state, double fidelity, Package& dd); +VectorDD approximate(VectorDD& state, double fidelity, Package& dd); } // namespace dd diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 4198c11ab..6651ffcea 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -19,14 +19,39 @@ #include namespace dd { +namespace { +/** + * @brief Recursively rebuild @p state. Exclude edges contained in @p exclude. + * @return Rebuilt VectorDD. + */ +VectorDD rebuild(const VectorDD& state, + const std::forward_list& exclude, Package& dd) { + const auto it = std::find(exclude.begin(), exclude.end(), &state); + if (it != exclude.end()) { + return vEdge::zero(); + } + + if (state.isTerminal()) { + return state; + } + + std::array edges{rebuild(state.p->e[0], exclude, dd), + rebuild(state.p->e[1], exclude, dd)}; -void approximate(VectorDD& state, const double fidelity, Package& dd) { + auto e = dd.makeDDNode(state.p->v, edges); + e.w = dd.cn.lookup(e.w * state.w); + return e; +} +}; // namespace + +VectorDD approximate(VectorDD& state, const double fidelity, Package& dd) { using ContributionMap = std::unordered_map; using Layer = std::forward_list; constexpr auto mag2 = ComplexNumbers::mag2; Layer l{&state}; + Layer exclude{}; ContributionMap m{{&state, mag2(state.w)}}; double budget = 1 - fidelity; @@ -36,10 +61,8 @@ void approximate(VectorDD& state, const double fidelity, Package& dd) { for (vEdge* edge : l) { const double contribution = m[edge]; - if (contribution <= budget) { - dd.decRef(*edge); - *edge = vEdge::zero(); + exclude.emplace_front(edge); budget -= contribution; } else if (!edge->isTerminal()) { vNode* node = edge->p; @@ -60,9 +83,14 @@ void approximate(VectorDD& state, const double fidelity, Package& dd) { m = std::move(nextM); } - auto& mm = dd.getMemoryManager(); - state = vEdge::normalize(state.p, state.p->e, mm, dd.cn); - state.w = dd.cn.lookup(state.w / std::sqrt(mag2(state.w))); + auto approx = rebuild(state, exclude, dd); + approx.w = dd.cn.lookup(approx.w / std::sqrt(mag2(approx.w))); + + dd.incRef(approx); + dd.decRef(state); + dd.garbageCollect(); + + return approx; } } // namespace dd From 544b81b0bc54048a76db4929bda45919b8164257 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 24 Apr 2025 14:28:34 +0200 Subject: [PATCH 25/59] Update tests --- test/dd/test_approximations.cpp | 133 ++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 40 deletions(-) diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index af8d8d82d..73bf8b5c2 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -10,24 +10,24 @@ #include "dd/Approximation.hpp" #include "dd/DDDefinitions.hpp" -#include "dd/Node.hpp" #include "dd/Package.hpp" #include "dd/Simulation.hpp" #include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" -#include +#include #include #include -#include #include using namespace dd; namespace { -std::complex getNorm(std::vector> vec) { - return std::inner_product(vec.begin(), vec.end(), vec.begin(), - std::complex()); +void vecNear(CVec a, CVec b, double delta = 1e-6) { + for (std::size_t i = 0; i < b.size(); ++i) { + EXPECT_NEAR(a[i].real(), b[i].real(), delta); + EXPECT_NEAR(b[i].imag(), b[i].imag(), delta); + } } }; // namespace @@ -44,14 +44,18 @@ TEST(ApproximationTest, OneQubitKeepAll) { qc::QuantumComputation qc(nq); qc.x(0); - // |state⟩ = |1⟩ + // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // + // Eliminate nothing (fidelity = 1). + // → |approx⟩ = |state⟩ + auto state = simulate(qc, dd.makeZeroState(nq), dd); - // fidelity is 1 → keep all nodes: |1⟩ - approximate(state, fidelity, dd); + auto approx = approximate(state, fidelity, dd); - CVec expected{{0}, {1}}; // expected state vector for |1⟩: [0, 1] - EXPECT_EQ(state.getVector(), expected); - EXPECT_EQ(state.size(), 2); // no nodes deleted: root node + terminal. + CVec expected{{0}, {1}}; + + EXPECT_EQ(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 2); } TEST(ApproximationTest, OneQubitApproximation) { @@ -64,41 +68,87 @@ TEST(ApproximationTest, OneQubitApproximation) { qc.ry(qc::PI / 3, 0); // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // + // Eliminate |1⟩ with contribution 0.25 + // → |approx⟩ = |0⟩ + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto approx = approximate(state, fidelity, dd); - // eliminate |1⟩ with contrib 0.25 → |0⟩ - approximate(state, fidelity, dd); + CVec expected{{1}, {0}}; - CVec expected{{1}, {0}}; // expected state vector for |0⟩: [1, 0] - EXPECT_EQ(state.getVector(), expected); - EXPECT_EQ(state.size(), 2); // no nodes deleted: root node + terminal. + EXPECT_EQ(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 2); + EXPECT_NEAR(dd.fidelity(state, approx), 0.75, 1e-3); + + dd.decRef(approx); + dd.garbageCollect(true); + + EXPECT_EQ(dd.vUniqueTable.getNumEntries(), 0); // correct ref counts. } -TEST(ApproximationTest, RemoveOneBottom) { +TEST(ApproximationTest, TwoQubitApproximation) { constexpr std::size_t nq = 2; - constexpr double fidelity = 0.98; + constexpr double fidelity = 1 - 0.2; Package dd(nq); - qc::QuantumComputation qc(nq); // first qubit with prob < 2%. + qc::QuantumComputation qc(nq); qc.h(0); - qc.cry(qc::PI / 8, 0, 1); + qc.cry(qc::PI / 3, 0, 1); + + // |state⟩ = 0.707|00⟩ + 0.612|01⟩ + 0.354|11⟩ + // + // Eliminate |11⟩ with contribution 0.125 + // → |approx⟩ = 0.756|00⟩ + 0.654|01⟩ auto state = simulate(qc, dd.makeZeroState(nq), dd); - approximate(state, fidelity, dd); + auto approx = approximate(state, fidelity, dd); - auto norm = getNorm(state.getVector()); + CVec expected{{0.755929}, {0.654654}, {0}, {0}}; - // has correct number of nodes. - EXPECT_EQ(state.size(), 3); - // correct edge is deleted. - EXPECT_EQ(state.p->e[1], vEdge::zero()); - // norm must be one. - EXPECT_NEAR(norm.real(), 1., 1e-6); - EXPECT_NEAR(norm.imag(), 0., 1e-6); + vecNear(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 3); + EXPECT_NEAR(dd.fidelity(state, approx), 0.875, 1e-3); } -TEST(ApproximationTest, RemoveOneMiddle) { +TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { + constexpr std::size_t nq = 2; + constexpr double fidelity = 1 - 0.1; + + Package dd(nq); + + qc::QuantumComputation qc(nq); + qc.h(0); + qc.h(1); + qc.ry(qc::PI / 3, 0); + + qc::QuantumComputation qcRef(nq); + qcRef.h(0); + qcRef.x(1); + qcRef.cx(0, 1); + qcRef.cx(1, 0); + + // |state⟩ = 0.183|00⟩ + 0.683|01⟩ + 0.183|10⟩ + 0.683|11⟩ + // + // Eliminate |00⟩ and |10⟩ with contributions ~ 0.0335. + // → |approx⟩ = (1/sqrt(2))(|01⟩ + |11⟩) + // + // |ref⟩ = (1/sqrt(2))(|01⟩ + |11⟩) + + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto approx = approximate(state, fidelity, dd); + auto ref = simulate(qcRef, dd.makeZeroState(nq), dd); + + CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; + + vecNear(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 3); + EXPECT_NEAR(dd.fidelity(state, approx), 0.933, 1e-3); + EXPECT_EQ(ref, approx); // points to same node and same edge weight +} + +TEST(ApproximationTest, ThreeQubitApproximation) { constexpr std::size_t nq = 3; constexpr double fidelity = 0.98; @@ -110,16 +160,19 @@ TEST(ApproximationTest, RemoveOneMiddle) { qc.h(1); qc.cry(qc::PI / 8, 1, 2); + // |state⟩ = 0.5|000⟩ + 0.588|001⟩ + 0.49|010⟩ + // + 0.385|011⟩ + 0.098|110⟩ + 0.077|111⟩ + // + // Eliminate parent of |110⟩ and |111> with contribution ~ 0.016. + // → |approx⟩ = 0.504|000⟩ + 0.593|001⟩ + 0.494|010⟩ + 0.388|011⟩ + auto state = simulate(qc, dd.makeZeroState(nq), dd); - approximate(state, fidelity, dd); + auto approx = approximate(state, fidelity, dd); - auto norm = getNorm(state.getVector()); + CVec expected{{0.503892}, {0.592515}, {0.49421}, {0.388298}, + {0}, {0}, {0}, {0}}; - // has correct number of nodes. - EXPECT_EQ(state.size(), 5); - // correct edge is deleted. - EXPECT_EQ(state.p->e[1], vEdge::zero()); - // norm must be one. - EXPECT_NEAR(norm.real(), 1., 1e-6); - EXPECT_NEAR(norm.imag(), 0., 1e-6); + vecNear(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 5); + EXPECT_NEAR(dd.fidelity(state, approx), 0.984, 1e-3); } From b2273d1253fa6e33e517957ab0f1e5fda1221cc2 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 25 Apr 2025 06:43:11 +0200 Subject: [PATCH 26/59] Fix linting issues --- src/dd/Approximation.cpp | 7 +++++-- test/dd/test_approximations.cpp | 13 ++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 6651ffcea..e9cc746be 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -11,9 +11,12 @@ #include "dd/Approximation.hpp" #include "dd/ComplexNumbers.hpp" +#include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" +#include +#include #include #include #include @@ -35,8 +38,8 @@ VectorDD rebuild(const VectorDD& state, return state; } - std::array edges{rebuild(state.p->e[0], exclude, dd), - rebuild(state.p->e[1], exclude, dd)}; + const std::array edges{rebuild(state.p->e[0], exclude, dd), + rebuild(state.p->e[1], exclude, dd)}; auto e = dd.makeDDNode(state.p->v, edges); e.w = dd.cn.lookup(e.w * state.w); diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 73bf8b5c2..08af5011f 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -18,7 +18,6 @@ #include #include #include -#include using namespace dd; @@ -52,7 +51,7 @@ TEST(ApproximationTest, OneQubitKeepAll) { auto state = simulate(qc, dd.makeZeroState(nq), dd); auto approx = approximate(state, fidelity, dd); - CVec expected{{0}, {1}}; + const CVec expected{{0}, {1}}; EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); @@ -75,7 +74,7 @@ TEST(ApproximationTest, OneQubitApproximation) { auto state = simulate(qc, dd.makeZeroState(nq), dd); auto approx = approximate(state, fidelity, dd); - CVec expected{{1}, {0}}; + const CVec expected{{1}, {0}}; EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); @@ -105,7 +104,7 @@ TEST(ApproximationTest, TwoQubitApproximation) { auto state = simulate(qc, dd.makeZeroState(nq), dd); auto approx = approximate(state, fidelity, dd); - CVec expected{{0.755929}, {0.654654}, {0}, {0}}; + const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; vecNear(approx.getVector(), expected); EXPECT_EQ(approx.size(), 3); @@ -140,7 +139,7 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { auto approx = approximate(state, fidelity, dd); auto ref = simulate(qcRef, dd.makeZeroState(nq), dd); - CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; + const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; vecNear(approx.getVector(), expected); EXPECT_EQ(approx.size(), 3); @@ -169,8 +168,8 @@ TEST(ApproximationTest, ThreeQubitApproximation) { auto state = simulate(qc, dd.makeZeroState(nq), dd); auto approx = approximate(state, fidelity, dd); - CVec expected{{0.503892}, {0.592515}, {0.49421}, {0.388298}, - {0}, {0}, {0}, {0}}; + const CVec expected{{0.503892}, {0.592515}, {0.49421}, {0.388298}, + {0}, {0}, {0}, {0}}; vecNear(approx.getVector(), expected); EXPECT_EQ(approx.size(), 5); From ce1ad5970dc45a2dd8e1276116b69e1d8ee8a20c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 25 Apr 2025 09:01:50 +0200 Subject: [PATCH 27/59] Update doxygen for approximate function --- include/mqt-core/dd/Approximation.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 3817a1862..304c1f70e 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -16,14 +16,16 @@ namespace dd { /** - * @brief Approximate the input state to a given final fidelity. + * @brief Approximate the @p state based on fidelity. The fidelity of the + * approximated state will be at least @p fidelity. * * @details Traverses the decision diagram layer by layer in a breadth-first - * manner and eliminates edges greedily until the budget is exhausted. + * manner (iterative deepening algorithm) and eliminates edges greedily until + * the budget (1 - @p fidelity) is exhausted. * * @param state The DD to approximate. - * @param fidelity The desired final fidelity after approximation. - * @param dd The DD package to use for the simulation + * @param fidelity The desired minimum fidelity after approximation. + * @param dd The DD package to use for the approximation. */ VectorDD approximate(VectorDD& state, double fidelity, Package& dd); From f415fb18be17b9bdfdfd44572ce58fa77cd04374 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 25 Apr 2025 09:33:48 +0200 Subject: [PATCH 28/59] Add to Approximation.cpp --- src/dd/Approximation.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index e9cc746be..42983029a 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace dd { namespace { From 964d2e253264b13f474cf16047265534a6efa837 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 25 Apr 2025 10:41:35 +0200 Subject: [PATCH 29/59] Adjust tests --- test/dd/test_approximations.cpp | 150 +++++++++++++++++++++++--------- 1 file changed, 111 insertions(+), 39 deletions(-) diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 08af5011f..8d2ec26b5 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -34,7 +34,15 @@ void vecNear(CVec a, CVec b, double delta = 1e-6) { /// \n simulate with approximation \n ///----------------------------------------------------------------------------- -TEST(ApproximationTest, OneQubitKeepAll) { +TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { + + // Test: If the budget is 0, no approximation will be applied. + // + // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // + // Eliminate nothing (fidelity = 1). + // → |approx⟩ = |state⟩ + constexpr std::size_t nq = 1; constexpr double fidelity = 1; @@ -43,11 +51,33 @@ TEST(ApproximationTest, OneQubitKeepAll) { qc::QuantumComputation qc(nq); qc.x(0); + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto approx = approximate(state, fidelity, dd); + + const CVec expected{{0}, {1}}; + + EXPECT_EQ(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 2); + EXPECT_EQ(state, approx); +} + +TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { + + // Test: If the budget is too small, no approximation will be applied. + // // |state⟩ = 0.866|0⟩ + 0.5|1⟩ // - // Eliminate nothing (fidelity = 1). + // Eliminate nothing: // → |approx⟩ = |state⟩ + constexpr std::size_t nq = 1; + constexpr double fidelity = 0.9; + + Package dd(nq); + + qc::QuantumComputation qc(nq); + qc.x(0); + auto state = simulate(qc, dd.makeZeroState(nq), dd); auto approx = approximate(state, fidelity, dd); @@ -55,9 +85,18 @@ TEST(ApproximationTest, OneQubitKeepAll) { EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); + EXPECT_EQ(state, approx); } -TEST(ApproximationTest, OneQubitApproximation) { +TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { + + // Test: Terminal edges can be removed (set to vEdge::zero) also. + // + // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // + // Eliminate |1⟩ with contribution 0.25 + // → |approx⟩ = |0⟩ + constexpr std::size_t nq = 1; constexpr double fidelity = 1 - 0.25; @@ -66,11 +105,6 @@ TEST(ApproximationTest, OneQubitApproximation) { qc::QuantumComputation qc(nq); qc.ry(qc::PI / 3, 0); - // |state⟩ = 0.866|0⟩ + 0.5|1⟩ - // - // Eliminate |1⟩ with contribution 0.25 - // → |approx⟩ = |0⟩ - auto state = simulate(qc, dd.makeZeroState(nq), dd); auto approx = approximate(state, fidelity, dd); @@ -79,14 +113,44 @@ TEST(ApproximationTest, OneQubitApproximation) { EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); EXPECT_NEAR(dd.fidelity(state, approx), 0.75, 1e-3); + EXPECT_NE(state, approx); +} + +TEST(ApproximationTest, OneQubitCorrectRefCount) { + + // Test: Correctly increase and decrease ref counts. + // + // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // + // Eliminate |1⟩ with contribution 0.25 + // → |approx⟩ = |0⟩ + + constexpr std::size_t nq = 1; + constexpr double fidelity = 1 - 0.25; + + Package dd(nq); + + qc::QuantumComputation qc(nq); + qc.ry(qc::PI / 3, 0); + + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto approx = approximate(state, fidelity, dd); dd.decRef(approx); dd.garbageCollect(true); - EXPECT_EQ(dd.vUniqueTable.getNumEntries(), 0); // correct ref counts. + EXPECT_EQ(dd.vUniqueTable.getNumEntries(), 0); } -TEST(ApproximationTest, TwoQubitApproximation) { +TEST(ApproximationTest, TwoQubitRemoveNode) { + + // Test: Remove node (its in-going edge) from decision diagram. + // + // |state⟩ = 0.707|00⟩ + 0.612|01⟩ + 0.354|11⟩ + // + // Eliminate |11⟩ with contribution 0.125 + // → |approx⟩ = 0.756|00⟩ + 0.654|01⟩ + constexpr std::size_t nq = 2; constexpr double fidelity = 1 - 0.2; @@ -96,22 +160,31 @@ TEST(ApproximationTest, TwoQubitApproximation) { qc.h(0); qc.cry(qc::PI / 3, 0, 1); - // |state⟩ = 0.707|00⟩ + 0.612|01⟩ + 0.354|11⟩ - // - // Eliminate |11⟩ with contribution 0.125 - // → |approx⟩ = 0.756|00⟩ + 0.654|01⟩ - auto state = simulate(qc, dd.makeZeroState(nq), dd); auto approx = approximate(state, fidelity, dd); const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; vecNear(approx.getVector(), expected); + EXPECT_EQ(state.size(), 4); EXPECT_EQ(approx.size(), 3); EXPECT_NEAR(dd.fidelity(state, approx), 0.875, 1e-3); + EXPECT_NE(state, approx); } TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { + + // Test: Compare the approximated source state with an equal constructed + // state. The root edge must point to the same node and have the same + // edge weight. + // + // |state⟩ = 0.183|00⟩ + 0.683|01⟩ + 0.183|10⟩ + 0.683|11⟩ + // + // Eliminate |00⟩ and |10⟩ with contributions ~ 0.0335. + // → |approx⟩ = (1/sqrt(2))(|01⟩ + |11⟩) + // + // |ref⟩ = (1/sqrt(2))(|01⟩ + |11⟩) + constexpr std::size_t nq = 2; constexpr double fidelity = 1 - 0.1; @@ -128,13 +201,6 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { qcRef.cx(0, 1); qcRef.cx(1, 0); - // |state⟩ = 0.183|00⟩ + 0.683|01⟩ + 0.183|10⟩ + 0.683|11⟩ - // - // Eliminate |00⟩ and |10⟩ with contributions ~ 0.0335. - // → |approx⟩ = (1/sqrt(2))(|01⟩ + |11⟩) - // - // |ref⟩ = (1/sqrt(2))(|01⟩ + |11⟩) - auto state = simulate(qc, dd.makeZeroState(nq), dd); auto approx = approximate(state, fidelity, dd); auto ref = simulate(qcRef, dd.makeZeroState(nq), dd); @@ -144,34 +210,40 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { vecNear(approx.getVector(), expected); EXPECT_EQ(approx.size(), 3); EXPECT_NEAR(dd.fidelity(state, approx), 0.933, 1e-3); - EXPECT_EQ(ref, approx); // points to same node and same edge weight + EXPECT_EQ(ref, approx); // implicit: utilize `==` operator. } -TEST(ApproximationTest, ThreeQubitApproximation) { +TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { + + // Test: Remove node that has a subtree attached (i.e. has children). + // Use exponential source state without redundancies. + // |state⟩ = 0+0.866j|000⟩ + 0-0.096j|100⟩ + 0+0.166j|101⟩ + 0+0.231j|110⟩ + + // + 0-0.4j|111⟩ + // + // Eliminate parent of |1xx⟩ with contribution ~ 0.25. + // → |approx⟩ = i|000⟩ + constexpr std::size_t nq = 3; - constexpr double fidelity = 0.98; + constexpr double fidelity = 1 - 0.25; Package dd(nq); qc::QuantumComputation qc(nq); - qc.h(0); - qc.cry(qc::PI / 8, 0, 1); - qc.h(1); - qc.cry(qc::PI / 8, 1, 2); - - // |state⟩ = 0.5|000⟩ + 0.588|001⟩ + 0.49|010⟩ - // + 0.385|011⟩ + 0.098|110⟩ + 0.077|111⟩ - // - // Eliminate parent of |110⟩ and |111> with contribution ~ 0.016. - // → |approx⟩ = 0.504|000⟩ + 0.593|001⟩ + 0.494|010⟩ + 0.388|011⟩ + qc.rx(qc::PI, 0); + qc.ry(2 * qc::PI / 3, 0); + qc.cx(0, 1); + qc.cx(1, 2); + qc.cry(qc::PI / 3, 2, 0); + qc.cry(qc::PI / 4, 2, 1); auto state = simulate(qc, dd.makeZeroState(nq), dd); auto approx = approximate(state, fidelity, dd); - const CVec expected{{0.503892}, {0.592515}, {0.49421}, {0.388298}, - {0}, {0}, {0}, {0}}; + const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; vecNear(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 5); - EXPECT_NEAR(dd.fidelity(state, approx), 0.984, 1e-3); + EXPECT_EQ(state.size(), 6); + EXPECT_EQ(approx.size(), 4); + EXPECT_NEAR(dd.fidelity(state, approx), 0.75, 1e-3); + EXPECT_NE(state, approx); } From 23bd1712442752d064281dc8901179945baf5493 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 25 Apr 2025 10:43:11 +0200 Subject: [PATCH 30/59] Update test description --- test/dd/test_approximations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 8d2ec26b5..88214239e 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -216,7 +216,7 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { // Test: Remove node that has a subtree attached (i.e. has children). - // Use exponential source state without redundancies. + // // |state⟩ = 0+0.866j|000⟩ + 0-0.096j|100⟩ + 0+0.166j|101⟩ + 0+0.231j|110⟩ + // + 0-0.4j|111⟩ // From 349f16631d708bc4dcf950f53ece4bbb01af020b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 28 Apr 2025 08:46:03 +0200 Subject: [PATCH 31/59] Return std::pair with fidelity as second --- include/mqt-core/dd/Approximation.hpp | 5 ++++- src/dd/Approximation.cpp | 17 ++++++++--------- test/dd/test_approximations.cpp | 18 +++++++++++------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 304c1f70e..5500d0c43 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -13,6 +13,8 @@ #include "dd/Node.hpp" #include "dd/Package.hpp" +#include + namespace dd { /** @@ -27,6 +29,7 @@ namespace dd { * @param fidelity The desired minimum fidelity after approximation. * @param dd The DD package to use for the approximation. */ -VectorDD approximate(VectorDD& state, double fidelity, Package& dd); +std::pair approximate(const VectorDD& state, double fidelity, + Package& dd); } // namespace dd diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 42983029a..d7becee36 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -29,7 +29,7 @@ namespace { * @return Rebuilt VectorDD. */ VectorDD rebuild(const VectorDD& state, - const std::forward_list& exclude, Package& dd) { + const std::forward_list& exclude, Package& dd) { const auto it = std::find(exclude.begin(), exclude.end(), &state); if (it != exclude.end()) { return vEdge::zero(); @@ -48,9 +48,10 @@ VectorDD rebuild(const VectorDD& state, } }; // namespace -VectorDD approximate(VectorDD& state, const double fidelity, Package& dd) { +std::pair approximate(const VectorDD& state, + const double fidelity, Package& dd) { using ContributionMap = std::unordered_map; - using Layer = std::forward_list; + using Layer = std::forward_list; constexpr auto mag2 = ComplexNumbers::mag2; @@ -63,20 +64,18 @@ VectorDD approximate(VectorDD& state, const double fidelity, Package& dd) { Layer nextL{}; ContributionMap nextM{}; - for (vEdge* edge : l) { + for (const vEdge* edge : l) { const double contribution = m[edge]; if (contribution <= budget) { exclude.emplace_front(edge); budget -= contribution; } else if (!edge->isTerminal()) { - vNode* node = edge->p; - for (auto& nextEdge : node->e) { + const vNode* node = edge->p; + for (const auto& nextEdge : node->e) { if (!nextEdge.w.exactlyZero()) { - if (nextM.find(&nextEdge) == nextM.end()) { nextL.emplace_front(&nextEdge); } - nextM[&nextEdge] += contribution * mag2(nextEdge.w); } } @@ -94,7 +93,7 @@ VectorDD approximate(VectorDD& state, const double fidelity, Package& dd) { dd.decRef(state); dd.garbageCollect(); - return approx; + return {approx, fidelity + budget}; } } // namespace dd diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 88214239e..b189b75bf 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -52,7 +52,7 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { qc.x(0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(state, fidelity, dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); const CVec expected{{0}, {1}}; @@ -79,7 +79,7 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { qc.x(0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(state, fidelity, dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); const CVec expected{{0}, {1}}; @@ -106,12 +106,13 @@ TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { qc.ry(qc::PI / 3, 0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(state, fidelity, dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); const CVec expected{{1}, {0}}; EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); + EXPECT_NEAR(postFidelity, 0.75, 1e-3); EXPECT_NEAR(dd.fidelity(state, approx), 0.75, 1e-3); EXPECT_NE(state, approx); } @@ -134,7 +135,7 @@ TEST(ApproximationTest, OneQubitCorrectRefCount) { qc.ry(qc::PI / 3, 0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(state, fidelity, dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); dd.decRef(approx); dd.garbageCollect(true); @@ -161,13 +162,14 @@ TEST(ApproximationTest, TwoQubitRemoveNode) { qc.cry(qc::PI / 3, 0, 1); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(state, fidelity, dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; vecNear(approx.getVector(), expected); EXPECT_EQ(state.size(), 4); EXPECT_EQ(approx.size(), 3); + EXPECT_NEAR(postFidelity, 0.875, 1e-3); EXPECT_NEAR(dd.fidelity(state, approx), 0.875, 1e-3); EXPECT_NE(state, approx); } @@ -202,13 +204,14 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { qcRef.cx(1, 0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(state, fidelity, dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); auto ref = simulate(qcRef, dd.makeZeroState(nq), dd); const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; vecNear(approx.getVector(), expected); EXPECT_EQ(approx.size(), 3); + EXPECT_NEAR(postFidelity, 0.933, 1e-3); EXPECT_NEAR(dd.fidelity(state, approx), 0.933, 1e-3); EXPECT_EQ(ref, approx); // implicit: utilize `==` operator. } @@ -237,13 +240,14 @@ TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { qc.cry(qc::PI / 4, 2, 1); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto approx = approximate(state, fidelity, dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; vecNear(approx.getVector(), expected); EXPECT_EQ(state.size(), 6); EXPECT_EQ(approx.size(), 4); + EXPECT_NEAR(postFidelity, 0.75, 1e-3); EXPECT_NEAR(dd.fidelity(state, approx), 0.75, 1e-3); EXPECT_NE(state, approx); } From 044a9d41cc66d8104561360a15a41ddced6e3def Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 28 Apr 2025 09:03:51 +0200 Subject: [PATCH 32/59] Clean up approximate function --- src/dd/Approximation.cpp | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index d7becee36..db5640ed2 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -42,30 +42,25 @@ VectorDD rebuild(const VectorDD& state, const std::array edges{rebuild(state.p->e[0], exclude, dd), rebuild(state.p->e[1], exclude, dd)}; - auto e = dd.makeDDNode(state.p->v, edges); - e.w = dd.cn.lookup(e.w * state.w); - return e; + VectorDD edge = dd.makeDDNode(state.p->v, edges); + edge.w = dd.cn.lookup(edge.w * state.w); + return edge; } }; // namespace std::pair approximate(const VectorDD& state, const double fidelity, Package& dd) { - using ContributionMap = std::unordered_map; - using Layer = std::forward_list; - - constexpr auto mag2 = ComplexNumbers::mag2; - - Layer l{&state}; - Layer exclude{}; - ContributionMap m{{&state, mag2(state.w)}}; + std::forward_list exclude{}; + std::forward_list layer{&state}; + std::unordered_map contributions{ + {&state, ComplexNumbers::mag2(state.w)}}; double budget = 1 - fidelity; - while (!l.empty() && budget > 0) { - Layer nextL{}; - ContributionMap nextM{}; + while (!layer.empty() && budget > 0) { + std::forward_list nextLayer{}; - for (const vEdge* edge : l) { - const double contribution = m[edge]; + for (const vEdge* edge : layer) { + const double contribution = contributions[edge]; if (contribution <= budget) { exclude.emplace_front(edge); budget -= contribution; @@ -73,21 +68,21 @@ std::pair approximate(const VectorDD& state, const vNode* node = edge->p; for (const auto& nextEdge : node->e) { if (!nextEdge.w.exactlyZero()) { - if (nextM.find(&nextEdge) == nextM.end()) { - nextL.emplace_front(&nextEdge); + if (contributions.find(&nextEdge) == contributions.end()) { + nextLayer.emplace_front(&nextEdge); } - nextM[&nextEdge] += contribution * mag2(nextEdge.w); + contributions[&nextEdge] += + contribution * ComplexNumbers::mag2(nextEdge.w); } } } } - l = std::move(nextL); - m = std::move(nextM); + layer = std::move(nextLayer); } auto approx = rebuild(state, exclude, dd); - approx.w = dd.cn.lookup(approx.w / std::sqrt(mag2(approx.w))); + approx.w = dd.cn.lookup(approx.w / std::sqrt(ComplexNumbers::mag2(approx.w))); dd.incRef(approx); dd.decRef(state); From 1c8c556bee4be43d091719e9ba9daff0fb4ae21f Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 28 Apr 2025 10:03:55 +0200 Subject: [PATCH 33/59] Add std::find for nextEdge --- src/dd/Approximation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index db5640ed2..9cf5f4c16 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -68,7 +68,8 @@ std::pair approximate(const VectorDD& state, const vNode* node = edge->p; for (const auto& nextEdge : node->e) { if (!nextEdge.w.exactlyZero()) { - if (contributions.find(&nextEdge) == contributions.end()) { + if (std::find(nextLayer.begin(), nextLayer.end(), &nextEdge) == + nextLayer.end()) { nextLayer.emplace_front(&nextEdge); } contributions[&nextEdge] += From 6b17acf4bc27c0ffbc136dbb7f89d299c3776dd1 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 28 Apr 2025 10:52:40 +0200 Subject: [PATCH 34/59] Remove checks with pre-approximation state after decRef and garbageCollect --- src/dd/Approximation.cpp | 5 +++-- test/dd/test_approximations.cpp | 12 ------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 9cf5f4c16..fafb8f87e 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -72,7 +72,8 @@ std::pair approximate(const VectorDD& state, nextLayer.end()) { nextLayer.emplace_front(&nextEdge); } - contributions[&nextEdge] += + contributions[&nextEdge] = + contributions[&nextEdge] + contribution * ComplexNumbers::mag2(nextEdge.w); } } @@ -82,7 +83,7 @@ std::pair approximate(const VectorDD& state, layer = std::move(nextLayer); } - auto approx = rebuild(state, exclude, dd); + VectorDD approx = rebuild(state, exclude, dd); approx.w = dd.cn.lookup(approx.w / std::sqrt(ComplexNumbers::mag2(approx.w))); dd.incRef(approx); diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index b189b75bf..89bed936d 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -55,10 +55,8 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { auto [approx, postFidelity] = approximate(state, fidelity, dd); const CVec expected{{0}, {1}}; - EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); - EXPECT_EQ(state, approx); } TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { @@ -85,7 +83,6 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); - EXPECT_EQ(state, approx); } TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { @@ -113,8 +110,6 @@ TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); EXPECT_NEAR(postFidelity, 0.75, 1e-3); - EXPECT_NEAR(dd.fidelity(state, approx), 0.75, 1e-3); - EXPECT_NE(state, approx); } TEST(ApproximationTest, OneQubitCorrectRefCount) { @@ -167,11 +162,8 @@ TEST(ApproximationTest, TwoQubitRemoveNode) { const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; vecNear(approx.getVector(), expected); - EXPECT_EQ(state.size(), 4); EXPECT_EQ(approx.size(), 3); EXPECT_NEAR(postFidelity, 0.875, 1e-3); - EXPECT_NEAR(dd.fidelity(state, approx), 0.875, 1e-3); - EXPECT_NE(state, approx); } TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { @@ -212,7 +204,6 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { vecNear(approx.getVector(), expected); EXPECT_EQ(approx.size(), 3); EXPECT_NEAR(postFidelity, 0.933, 1e-3); - EXPECT_NEAR(dd.fidelity(state, approx), 0.933, 1e-3); EXPECT_EQ(ref, approx); // implicit: utilize `==` operator. } @@ -245,9 +236,6 @@ TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; vecNear(approx.getVector(), expected); - EXPECT_EQ(state.size(), 6); EXPECT_EQ(approx.size(), 4); EXPECT_NEAR(postFidelity, 0.75, 1e-3); - EXPECT_NEAR(dd.fidelity(state, approx), 0.75, 1e-3); - EXPECT_NE(state, approx); } From 25f7de9d8f6b93b95a2f8bb6146e17cc2fbba3a2 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 28 Apr 2025 15:49:51 +0200 Subject: [PATCH 35/59] Update doxygen --- include/mqt-core/dd/Approximation.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 5500d0c43..274c18f9f 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -28,6 +28,8 @@ namespace dd { * @param state The DD to approximate. * @param fidelity The desired minimum fidelity after approximation. * @param dd The DD package to use for the approximation. + * @return Pair of approximated state and the fidelity between the source and + * approximated state. */ std::pair approximate(const VectorDD& state, double fidelity, Package& dd); From 84a9f0647c236e27785024cd8f19aa6522534b48 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 28 Apr 2025 16:06:01 +0200 Subject: [PATCH 36/59] Remove unused postFidelity in tests --- src/dd/Approximation.cpp | 6 +++--- test/dd/test_approximations.cpp | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index fafb8f87e..40d3ef383 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -86,9 +86,9 @@ std::pair approximate(const VectorDD& state, VectorDD approx = rebuild(state, exclude, dd); approx.w = dd.cn.lookup(approx.w / std::sqrt(ComplexNumbers::mag2(approx.w))); - dd.incRef(approx); - dd.decRef(state); - dd.garbageCollect(); + // dd.incRef(approx); + // dd.decRef(state); + // dd.garbageCollect(); return {approx, fidelity + budget}; } diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 89bed936d..062295bae 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -52,7 +52,8 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { qc.x(0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); + auto p = approximate(state, fidelity, dd); + auto approx = p.first; const CVec expected{{0}, {1}}; EXPECT_EQ(approx.getVector(), expected); @@ -77,7 +78,8 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { qc.x(0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); + auto p = approximate(state, fidelity, dd); + auto approx = p.first; const CVec expected{{0}, {1}}; @@ -130,9 +132,9 @@ TEST(ApproximationTest, OneQubitCorrectRefCount) { qc.ry(qc::PI / 3, 0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); + auto p = approximate(state, fidelity, dd); - dd.decRef(approx); + dd.decRef(p.first); dd.garbageCollect(true); EXPECT_EQ(dd.vUniqueTable.getNumEntries(), 0); From a8dc8b9be7187ba979da433a5b7071e42e455373 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 28 Apr 2025 16:22:30 +0200 Subject: [PATCH 37/59] Comment out rebuild function --- src/dd/Approximation.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 40d3ef383..f9d1f1bf0 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -83,14 +83,15 @@ std::pair approximate(const VectorDD& state, layer = std::move(nextLayer); } - VectorDD approx = rebuild(state, exclude, dd); - approx.w = dd.cn.lookup(approx.w / std::sqrt(ComplexNumbers::mag2(approx.w))); + // VectorDD approx = rebuild(state, exclude, dd); + // approx.w = dd.cn.lookup(approx.w / + // std::sqrt(ComplexNumbers::mag2(approx.w))); // dd.incRef(approx); // dd.decRef(state); // dd.garbageCollect(); - return {approx, fidelity + budget}; + return {state, fidelity + budget}; } } // namespace dd From ecdb2536e8feb8892ece546da18cbfba151819f8 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 28 Apr 2025 16:38:51 +0200 Subject: [PATCH 38/59] Uncomment contribution map --- src/dd/Approximation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index f9d1f1bf0..05e2a0ed5 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -72,9 +72,9 @@ std::pair approximate(const VectorDD& state, nextLayer.end()) { nextLayer.emplace_front(&nextEdge); } - contributions[&nextEdge] = - contributions[&nextEdge] + - contribution * ComplexNumbers::mag2(nextEdge.w); + // contributions[&nextEdge] = + // contributions[&nextEdge] + + // contribution * ComplexNumbers::mag2(nextEdge.w); } } } From 3b234b7a004ceb5c7b7e7d5db8dc645d4fd09500 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 29 Apr 2025 06:44:28 +0200 Subject: [PATCH 39/59] Comment all but simplest test --- src/dd/Approximation.cpp | 2 + test/dd/test_approximations.cpp | 278 ++++++++++++++++---------------- 2 files changed, 142 insertions(+), 138 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 05e2a0ed5..cddc8718c 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -65,7 +65,9 @@ std::pair approximate(const VectorDD& state, exclude.emplace_front(edge); budget -= contribution; } else if (!edge->isTerminal()) { + assert(edge != nullptr); const vNode* node = edge->p; + assert(node != nullptr); for (const auto& nextEdge : node->e) { if (!nextEdge.w.exactlyZero()) { if (std::find(nextLayer.begin(), nextLayer.end(), &nextEdge) == diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 062295bae..830d6b320 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -23,6 +23,7 @@ using namespace dd; namespace { void vecNear(CVec a, CVec b, double delta = 1e-6) { + assert(a.size() == b.size()); for (std::size_t i = 0; i < b.size(); ++i) { EXPECT_NEAR(a[i].real(), b[i].real(), delta); EXPECT_NEAR(b[i].imag(), b[i].imag(), delta); @@ -53,191 +54,192 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { auto state = simulate(qc, dd.makeZeroState(nq), dd); auto p = approximate(state, fidelity, dd); - auto approx = p.first; + // auto approx = p.first; - const CVec expected{{0}, {1}}; - EXPECT_EQ(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 2); + // const CVec expected{{0}, {1}}; + // EXPECT_EQ(approx.getVector(), expected); + // EXPECT_EQ(approx.size(), 2); } -TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { +// TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { - // Test: If the budget is too small, no approximation will be applied. - // - // |state⟩ = 0.866|0⟩ + 0.5|1⟩ - // - // Eliminate nothing: - // → |approx⟩ = |state⟩ - - constexpr std::size_t nq = 1; - constexpr double fidelity = 0.9; +// // Test: If the budget is too small, no approximation will be applied. +// // +// // |state⟩ = 0.866|0⟩ + 0.5|1⟩ +// // +// // Eliminate nothing: +// // → |approx⟩ = |state⟩ - Package dd(nq); +// constexpr std::size_t nq = 1; +// constexpr double fidelity = 0.9; - qc::QuantumComputation qc(nq); - qc.x(0); +// Package dd(nq); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto p = approximate(state, fidelity, dd); - auto approx = p.first; +// qc::QuantumComputation qc(nq); +// qc.x(0); - const CVec expected{{0}, {1}}; +// auto state = simulate(qc, dd.makeZeroState(nq), dd); +// auto p = approximate(state, fidelity, dd); +// auto approx = p.first; - EXPECT_EQ(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 2); -} +// const CVec expected{{0}, {1}}; -TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { +// EXPECT_EQ(approx.getVector(), expected); +// EXPECT_EQ(approx.size(), 2); +// } - // Test: Terminal edges can be removed (set to vEdge::zero) also. - // - // |state⟩ = 0.866|0⟩ + 0.5|1⟩ - // - // Eliminate |1⟩ with contribution 0.25 - // → |approx⟩ = |0⟩ +// TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { - constexpr std::size_t nq = 1; - constexpr double fidelity = 1 - 0.25; +// // Test: Terminal edges can be removed (set to vEdge::zero) also. +// // +// // |state⟩ = 0.866|0⟩ + 0.5|1⟩ +// // +// // Eliminate |1⟩ with contribution 0.25 +// // → |approx⟩ = |0⟩ - Package dd(nq); +// constexpr std::size_t nq = 1; +// constexpr double fidelity = 1 - 0.25; - qc::QuantumComputation qc(nq); - qc.ry(qc::PI / 3, 0); +// Package dd(nq); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); +// qc::QuantumComputation qc(nq); +// qc.ry(qc::PI / 3, 0); - const CVec expected{{1}, {0}}; +// auto state = simulate(qc, dd.makeZeroState(nq), dd); +// auto [approx, postFidelity] = approximate(state, fidelity, dd); - EXPECT_EQ(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 2); - EXPECT_NEAR(postFidelity, 0.75, 1e-3); -} +// const CVec expected{{1}, {0}}; -TEST(ApproximationTest, OneQubitCorrectRefCount) { +// EXPECT_EQ(approx.getVector(), expected); +// EXPECT_EQ(approx.size(), 2); +// EXPECT_NEAR(postFidelity, 0.75, 1e-3); +// } - // Test: Correctly increase and decrease ref counts. - // - // |state⟩ = 0.866|0⟩ + 0.5|1⟩ - // - // Eliminate |1⟩ with contribution 0.25 - // → |approx⟩ = |0⟩ +// TEST(ApproximationTest, OneQubitCorrectRefCount) { - constexpr std::size_t nq = 1; - constexpr double fidelity = 1 - 0.25; +// // Test: Correctly increase and decrease ref counts. +// // +// // |state⟩ = 0.866|0⟩ + 0.5|1⟩ +// // +// // Eliminate |1⟩ with contribution 0.25 +// // → |approx⟩ = |0⟩ - Package dd(nq); +// constexpr std::size_t nq = 1; +// constexpr double fidelity = 1 - 0.25; - qc::QuantumComputation qc(nq); - qc.ry(qc::PI / 3, 0); +// Package dd(nq); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto p = approximate(state, fidelity, dd); +// qc::QuantumComputation qc(nq); +// qc.ry(qc::PI / 3, 0); - dd.decRef(p.first); - dd.garbageCollect(true); +// auto state = simulate(qc, dd.makeZeroState(nq), dd); +// auto p = approximate(state, fidelity, dd); - EXPECT_EQ(dd.vUniqueTable.getNumEntries(), 0); -} +// dd.decRef(p.first); +// dd.garbageCollect(true); -TEST(ApproximationTest, TwoQubitRemoveNode) { +// EXPECT_EQ(dd.vUniqueTable.getNumEntries(), 0); +// } - // Test: Remove node (its in-going edge) from decision diagram. - // - // |state⟩ = 0.707|00⟩ + 0.612|01⟩ + 0.354|11⟩ - // - // Eliminate |11⟩ with contribution 0.125 - // → |approx⟩ = 0.756|00⟩ + 0.654|01⟩ +// TEST(ApproximationTest, TwoQubitRemoveNode) { - constexpr std::size_t nq = 2; - constexpr double fidelity = 1 - 0.2; +// // Test: Remove node (its in-going edge) from decision diagram. +// // +// // |state⟩ = 0.707|00⟩ + 0.612|01⟩ + 0.354|11⟩ +// // +// // Eliminate |11⟩ with contribution 0.125 +// // → |approx⟩ = 0.756|00⟩ + 0.654|01⟩ - Package dd(nq); +// constexpr std::size_t nq = 2; +// constexpr double fidelity = 1 - 0.2; - qc::QuantumComputation qc(nq); - qc.h(0); - qc.cry(qc::PI / 3, 0, 1); +// Package dd(nq); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); +// qc::QuantumComputation qc(nq); +// qc.h(0); +// qc.cry(qc::PI / 3, 0, 1); - const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; +// auto state = simulate(qc, dd.makeZeroState(nq), dd); +// auto [approx, postFidelity] = approximate(state, fidelity, dd); - vecNear(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 3); - EXPECT_NEAR(postFidelity, 0.875, 1e-3); -} +// const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; -TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { +// vecNear(approx.getVector(), expected); +// EXPECT_EQ(approx.size(), 3); +// EXPECT_NEAR(postFidelity, 0.875, 1e-3); +// } - // Test: Compare the approximated source state with an equal constructed - // state. The root edge must point to the same node and have the same - // edge weight. - // - // |state⟩ = 0.183|00⟩ + 0.683|01⟩ + 0.183|10⟩ + 0.683|11⟩ - // - // Eliminate |00⟩ and |10⟩ with contributions ~ 0.0335. - // → |approx⟩ = (1/sqrt(2))(|01⟩ + |11⟩) - // - // |ref⟩ = (1/sqrt(2))(|01⟩ + |11⟩) +// TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { - constexpr std::size_t nq = 2; - constexpr double fidelity = 1 - 0.1; +// // Test: Compare the approximated source state with an equal constructed +// // state. The root edge must point to the same node and have the same +// // edge weight. +// // +// // |state⟩ = 0.183|00⟩ + 0.683|01⟩ + 0.183|10⟩ + 0.683|11⟩ +// // +// // Eliminate |00⟩ and |10⟩ with contributions ~ 0.0335. +// // → |approx⟩ = (1/sqrt(2))(|01⟩ + |11⟩) +// // +// // |ref⟩ = (1/sqrt(2))(|01⟩ + |11⟩) - Package dd(nq); +// constexpr std::size_t nq = 2; +// constexpr double fidelity = 1 - 0.1; - qc::QuantumComputation qc(nq); - qc.h(0); - qc.h(1); - qc.ry(qc::PI / 3, 0); +// Package dd(nq); - qc::QuantumComputation qcRef(nq); - qcRef.h(0); - qcRef.x(1); - qcRef.cx(0, 1); - qcRef.cx(1, 0); +// qc::QuantumComputation qc(nq); +// qc.h(0); +// qc.h(1); +// qc.ry(qc::PI / 3, 0); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); - auto ref = simulate(qcRef, dd.makeZeroState(nq), dd); +// qc::QuantumComputation qcRef(nq); +// qcRef.h(0); +// qcRef.x(1); +// qcRef.cx(0, 1); +// qcRef.cx(1, 0); - const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; +// auto state = simulate(qc, dd.makeZeroState(nq), dd); +// auto [approx, postFidelity] = approximate(state, fidelity, dd); +// auto ref = simulate(qcRef, dd.makeZeroState(nq), dd); - vecNear(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 3); - EXPECT_NEAR(postFidelity, 0.933, 1e-3); - EXPECT_EQ(ref, approx); // implicit: utilize `==` operator. -} +// const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; -TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { +// vecNear(approx.getVector(), expected); +// EXPECT_EQ(approx.size(), 3); +// EXPECT_NEAR(postFidelity, 0.933, 1e-3); +// EXPECT_EQ(ref, approx); // implicit: utilize `==` operator. +// } - // Test: Remove node that has a subtree attached (i.e. has children). - // - // |state⟩ = 0+0.866j|000⟩ + 0-0.096j|100⟩ + 0+0.166j|101⟩ + 0+0.231j|110⟩ + - // + 0-0.4j|111⟩ - // - // Eliminate parent of |1xx⟩ with contribution ~ 0.25. - // → |approx⟩ = i|000⟩ +// TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { - constexpr std::size_t nq = 3; - constexpr double fidelity = 1 - 0.25; +// // Test: Remove node that has a subtree attached (i.e. has children). +// // +// // |state⟩ = 0+0.866j|000⟩ + 0-0.096j|100⟩ + 0+0.166j|101⟩ + 0+0.231j|110⟩ +// + +// // + 0-0.4j|111⟩ +// // +// // Eliminate parent of |1xx⟩ with contribution ~ 0.25. +// // → |approx⟩ = i|000⟩ - Package dd(nq); +// constexpr std::size_t nq = 3; +// constexpr double fidelity = 1 - 0.25; - qc::QuantumComputation qc(nq); - qc.rx(qc::PI, 0); - qc.ry(2 * qc::PI / 3, 0); - qc.cx(0, 1); - qc.cx(1, 2); - qc.cry(qc::PI / 3, 2, 0); - qc.cry(qc::PI / 4, 2, 1); +// Package dd(nq); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); +// qc::QuantumComputation qc(nq); +// qc.rx(qc::PI, 0); +// qc.ry(2 * qc::PI / 3, 0); +// qc.cx(0, 1); +// qc.cx(1, 2); +// qc.cry(qc::PI / 3, 2, 0); +// qc.cry(qc::PI / 4, 2, 1); - const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; +// auto state = simulate(qc, dd.makeZeroState(nq), dd); +// auto [approx, postFidelity] = approximate(state, fidelity, dd); - vecNear(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 4); - EXPECT_NEAR(postFidelity, 0.75, 1e-3); -} +// const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; + +// vecNear(approx.getVector(), expected); +// EXPECT_EQ(approx.size(), 4); +// EXPECT_NEAR(postFidelity, 0.75, 1e-3); +// } From a7cc5fdccb8a55e9acabf6782fd2b45a3bc02cf3 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 29 Apr 2025 06:49:33 +0200 Subject: [PATCH 40/59] Comment out approximate function in simplest test --- test/dd/test_approximations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 830d6b320..9380efd2c 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -53,7 +53,7 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { qc.x(0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto p = approximate(state, fidelity, dd); + // auto p = approximate(state, fidelity, dd); // auto approx = p.first; // const CVec expected{{0}, {1}}; From 4fa3524bbec694e4a6e53c2b74b86a1982fa52e1 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 29 Apr 2025 07:11:53 +0200 Subject: [PATCH 41/59] Initialize contributions map value explicitly --- src/dd/Approximation.cpp | 9 +++++---- test/dd/test_approximations.cpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index cddc8718c..73f0e6d2b 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -50,19 +50,19 @@ VectorDD rebuild(const VectorDD& state, std::pair approximate(const VectorDD& state, const double fidelity, Package& dd) { - std::forward_list exclude{}; + std::forward_list exclude; std::forward_list layer{&state}; std::unordered_map contributions{ {&state, ComplexNumbers::mag2(state.w)}}; double budget = 1 - fidelity; while (!layer.empty() && budget > 0) { - std::forward_list nextLayer{}; + std::forward_list nextLayer; for (const vEdge* edge : layer) { const double contribution = contributions[edge]; if (contribution <= budget) { - exclude.emplace_front(edge); + exclude.push_front(edge); budget -= contribution; } else if (!edge->isTerminal()) { assert(edge != nullptr); @@ -72,7 +72,8 @@ std::pair approximate(const VectorDD& state, if (!nextEdge.w.exactlyZero()) { if (std::find(nextLayer.begin(), nextLayer.end(), &nextEdge) == nextLayer.end()) { - nextLayer.emplace_front(&nextEdge); + nextLayer.push_front(&nextEdge); + contributions[&nextEdge] = 0.; } // contributions[&nextEdge] = // contributions[&nextEdge] + diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 9380efd2c..0f3c12fd1 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -53,7 +53,7 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { qc.x(0); auto state = simulate(qc, dd.makeZeroState(nq), dd); - // auto p = approximate(state, fidelity, dd); + [[maybe_unused]] auto p = approximate(state, fidelity, dd); // auto approx = p.first; // const CVec expected{{0}, {1}}; From e6428278e0c14c92c6e6f650158b06e585e7f045 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 29 Apr 2025 07:23:30 +0200 Subject: [PATCH 42/59] Comment approximate function --- test/dd/test_approximations.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 0f3c12fd1..63b3441c6 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -52,8 +52,8 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { qc::QuantumComputation qc(nq); qc.x(0); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - [[maybe_unused]] auto p = approximate(state, fidelity, dd); + [[maybe_unused]] auto state = simulate(qc, dd.makeZeroState(nq), dd); + // [[maybe_unused]] auto p = approximate(state, fidelity, dd); // auto approx = p.first; // const CVec expected{{0}, {1}}; From ab939deb4006aed55b3e7c824337a2f8eb0059f9 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 29 Apr 2025 07:40:58 +0200 Subject: [PATCH 43/59] Uncomment code --- src/dd/Approximation.cpp | 29 ++-- test/dd/test_approximations.cpp | 287 ++++++++++++++++---------------- 2 files changed, 155 insertions(+), 161 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 73f0e6d2b..f392dc2eb 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -50,34 +50,30 @@ VectorDD rebuild(const VectorDD& state, std::pair approximate(const VectorDD& state, const double fidelity, Package& dd) { - std::forward_list exclude; + std::forward_list exclude{}; std::forward_list layer{&state}; std::unordered_map contributions{ {&state, ComplexNumbers::mag2(state.w)}}; double budget = 1 - fidelity; while (!layer.empty() && budget > 0) { - std::forward_list nextLayer; + std::forward_list nextLayer{}; for (const vEdge* edge : layer) { const double contribution = contributions[edge]; if (contribution <= budget) { - exclude.push_front(edge); + exclude.emplace_front(edge); budget -= contribution; } else if (!edge->isTerminal()) { - assert(edge != nullptr); const vNode* node = edge->p; - assert(node != nullptr); for (const auto& nextEdge : node->e) { if (!nextEdge.w.exactlyZero()) { if (std::find(nextLayer.begin(), nextLayer.end(), &nextEdge) == nextLayer.end()) { - nextLayer.push_front(&nextEdge); - contributions[&nextEdge] = 0.; + nextLayer.emplace_front(&nextEdge); } - // contributions[&nextEdge] = - // contributions[&nextEdge] + - // contribution * ComplexNumbers::mag2(nextEdge.w); + contributions[&nextEdge] += + contribution * ComplexNumbers::mag2(nextEdge.w); } } } @@ -86,15 +82,14 @@ std::pair approximate(const VectorDD& state, layer = std::move(nextLayer); } - // VectorDD approx = rebuild(state, exclude, dd); - // approx.w = dd.cn.lookup(approx.w / - // std::sqrt(ComplexNumbers::mag2(approx.w))); + VectorDD approx = rebuild(state, exclude, dd); + approx.w = dd.cn.lookup(approx.w / std::sqrt(ComplexNumbers::mag2(approx.w))); - // dd.incRef(approx); - // dd.decRef(state); - // dd.garbageCollect(); + dd.incRef(approx); + dd.decRef(state); + dd.garbageCollect(); - return {state, fidelity + budget}; + return {approx, fidelity + budget}; } } // namespace dd diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 63b3441c6..067dcff25 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -52,194 +52,193 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { qc::QuantumComputation qc(nq); qc.x(0); - [[maybe_unused]] auto state = simulate(qc, dd.makeZeroState(nq), dd); - // [[maybe_unused]] auto p = approximate(state, fidelity, dd); - // auto approx = p.first; + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto p = approximate(state, fidelity, dd); + auto approx = p.first; - // const CVec expected{{0}, {1}}; - // EXPECT_EQ(approx.getVector(), expected); - // EXPECT_EQ(approx.size(), 2); + const CVec expected{{0}, {1}}; + EXPECT_EQ(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 2); } -// TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { +TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { -// // Test: If the budget is too small, no approximation will be applied. -// // -// // |state⟩ = 0.866|0⟩ + 0.5|1⟩ -// // -// // Eliminate nothing: -// // → |approx⟩ = |state⟩ - -// constexpr std::size_t nq = 1; -// constexpr double fidelity = 0.9; - -// Package dd(nq); - -// qc::QuantumComputation qc(nq); -// qc.x(0); + // Test: If the budget is too small, no approximation will be applied. + // + // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // + // Eliminate nothing: + // → |approx⟩ = |state⟩ -// auto state = simulate(qc, dd.makeZeroState(nq), dd); -// auto p = approximate(state, fidelity, dd); -// auto approx = p.first; + constexpr std::size_t nq = 1; + constexpr double fidelity = 0.9; -// const CVec expected{{0}, {1}}; + Package dd(nq); -// EXPECT_EQ(approx.getVector(), expected); -// EXPECT_EQ(approx.size(), 2); -// } + qc::QuantumComputation qc(nq); + qc.x(0); -// TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto p = approximate(state, fidelity, dd); + auto approx = p.first; -// // Test: Terminal edges can be removed (set to vEdge::zero) also. -// // -// // |state⟩ = 0.866|0⟩ + 0.5|1⟩ -// // -// // Eliminate |1⟩ with contribution 0.25 -// // → |approx⟩ = |0⟩ + const CVec expected{{0}, {1}}; -// constexpr std::size_t nq = 1; -// constexpr double fidelity = 1 - 0.25; + EXPECT_EQ(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 2); +} -// Package dd(nq); +TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { -// qc::QuantumComputation qc(nq); -// qc.ry(qc::PI / 3, 0); + // Test: Terminal edges can be removed (set to vEdge::zero) also. + // + // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // + // Eliminate |1⟩ with contribution 0.25 + // → |approx⟩ = |0⟩ -// auto state = simulate(qc, dd.makeZeroState(nq), dd); -// auto [approx, postFidelity] = approximate(state, fidelity, dd); + constexpr std::size_t nq = 1; + constexpr double fidelity = 1 - 0.25; -// const CVec expected{{1}, {0}}; + Package dd(nq); -// EXPECT_EQ(approx.getVector(), expected); -// EXPECT_EQ(approx.size(), 2); -// EXPECT_NEAR(postFidelity, 0.75, 1e-3); -// } + qc::QuantumComputation qc(nq); + qc.ry(qc::PI / 3, 0); -// TEST(ApproximationTest, OneQubitCorrectRefCount) { + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); -// // Test: Correctly increase and decrease ref counts. -// // -// // |state⟩ = 0.866|0⟩ + 0.5|1⟩ -// // -// // Eliminate |1⟩ with contribution 0.25 -// // → |approx⟩ = |0⟩ + const CVec expected{{1}, {0}}; -// constexpr std::size_t nq = 1; -// constexpr double fidelity = 1 - 0.25; + EXPECT_EQ(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 2); + EXPECT_NEAR(postFidelity, 0.75, 1e-3); +} -// Package dd(nq); +TEST(ApproximationTest, OneQubitCorrectRefCount) { -// qc::QuantumComputation qc(nq); -// qc.ry(qc::PI / 3, 0); + // Test: Correctly increase and decrease ref counts. + // + // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // + // Eliminate |1⟩ with contribution 0.25 + // → |approx⟩ = |0⟩ -// auto state = simulate(qc, dd.makeZeroState(nq), dd); -// auto p = approximate(state, fidelity, dd); + constexpr std::size_t nq = 1; + constexpr double fidelity = 1 - 0.25; -// dd.decRef(p.first); -// dd.garbageCollect(true); + Package dd(nq); -// EXPECT_EQ(dd.vUniqueTable.getNumEntries(), 0); -// } + qc::QuantumComputation qc(nq); + qc.ry(qc::PI / 3, 0); -// TEST(ApproximationTest, TwoQubitRemoveNode) { + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto p = approximate(state, fidelity, dd); -// // Test: Remove node (its in-going edge) from decision diagram. -// // -// // |state⟩ = 0.707|00⟩ + 0.612|01⟩ + 0.354|11⟩ -// // -// // Eliminate |11⟩ with contribution 0.125 -// // → |approx⟩ = 0.756|00⟩ + 0.654|01⟩ + dd.decRef(p.first); + dd.garbageCollect(true); -// constexpr std::size_t nq = 2; -// constexpr double fidelity = 1 - 0.2; + EXPECT_EQ(dd.vUniqueTable.getNumEntries(), 0); +} -// Package dd(nq); +TEST(ApproximationTest, TwoQubitRemoveNode) { -// qc::QuantumComputation qc(nq); -// qc.h(0); -// qc.cry(qc::PI / 3, 0, 1); + // Test: Remove node (its in-going edge) from decision diagram. + // + // |state⟩ = 0.707|00⟩ + 0.612|01⟩ + 0.354|11⟩ + // + // Eliminate |11⟩ with contribution 0.125 + // → |approx⟩ = 0.756|00⟩ + 0.654|01⟩ -// auto state = simulate(qc, dd.makeZeroState(nq), dd); -// auto [approx, postFidelity] = approximate(state, fidelity, dd); + constexpr std::size_t nq = 2; + constexpr double fidelity = 1 - 0.2; -// const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; + Package dd(nq); -// vecNear(approx.getVector(), expected); -// EXPECT_EQ(approx.size(), 3); -// EXPECT_NEAR(postFidelity, 0.875, 1e-3); -// } + qc::QuantumComputation qc(nq); + qc.h(0); + qc.cry(qc::PI / 3, 0, 1); -// TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); -// // Test: Compare the approximated source state with an equal constructed -// // state. The root edge must point to the same node and have the same -// // edge weight. -// // -// // |state⟩ = 0.183|00⟩ + 0.683|01⟩ + 0.183|10⟩ + 0.683|11⟩ -// // -// // Eliminate |00⟩ and |10⟩ with contributions ~ 0.0335. -// // → |approx⟩ = (1/sqrt(2))(|01⟩ + |11⟩) -// // -// // |ref⟩ = (1/sqrt(2))(|01⟩ + |11⟩) + const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; -// constexpr std::size_t nq = 2; -// constexpr double fidelity = 1 - 0.1; + vecNear(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 3); + EXPECT_NEAR(postFidelity, 0.875, 1e-3); +} -// Package dd(nq); +TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { -// qc::QuantumComputation qc(nq); -// qc.h(0); -// qc.h(1); -// qc.ry(qc::PI / 3, 0); + // Test: Compare the approximated source state with an equal constructed + // state. The root edge must point to the same node and have the same + // edge weight. + // + // |state⟩ = 0.183|00⟩ + 0.683|01⟩ + 0.183|10⟩ + 0.683|11⟩ + // + // Eliminate |00⟩ and |10⟩ with contributions ~ 0.0335. + // → |approx⟩ = (1/sqrt(2))(|01⟩ + |11⟩) + // + // |ref⟩ = (1/sqrt(2))(|01⟩ + |11⟩) -// qc::QuantumComputation qcRef(nq); -// qcRef.h(0); -// qcRef.x(1); -// qcRef.cx(0, 1); -// qcRef.cx(1, 0); + constexpr std::size_t nq = 2; + constexpr double fidelity = 1 - 0.1; -// auto state = simulate(qc, dd.makeZeroState(nq), dd); -// auto [approx, postFidelity] = approximate(state, fidelity, dd); -// auto ref = simulate(qcRef, dd.makeZeroState(nq), dd); + Package dd(nq); -// const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; + qc::QuantumComputation qc(nq); + qc.h(0); + qc.h(1); + qc.ry(qc::PI / 3, 0); + + qc::QuantumComputation qcRef(nq); + qcRef.h(0); + qcRef.x(1); + qcRef.cx(0, 1); + qcRef.cx(1, 0); + + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); + auto ref = simulate(qcRef, dd.makeZeroState(nq), dd); + + const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; + + vecNear(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 3); + EXPECT_NEAR(postFidelity, 0.933, 1e-3); + EXPECT_EQ(ref, approx); // implicit: utilize `==` operator. +} -// vecNear(approx.getVector(), expected); -// EXPECT_EQ(approx.size(), 3); -// EXPECT_NEAR(postFidelity, 0.933, 1e-3); -// EXPECT_EQ(ref, approx); // implicit: utilize `==` operator. -// } +TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { -// TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { + // Test: Remove node that has a subtree attached (i.e. has children). + // + // |state⟩ = 0+0.866j|000⟩ + 0-0.096j|100⟩ + 0+0.166j|101⟩ + 0+0.231j|110⟩ + // + 0-0.4j|111⟩ + // + // Eliminate parent of |1xx⟩ with contribution ~ 0.25. + // → |approx⟩ = i|000⟩ -// // Test: Remove node that has a subtree attached (i.e. has children). -// // -// // |state⟩ = 0+0.866j|000⟩ + 0-0.096j|100⟩ + 0+0.166j|101⟩ + 0+0.231j|110⟩ -// + -// // + 0-0.4j|111⟩ -// // -// // Eliminate parent of |1xx⟩ with contribution ~ 0.25. -// // → |approx⟩ = i|000⟩ + constexpr std::size_t nq = 3; + constexpr double fidelity = 1 - 0.25; -// constexpr std::size_t nq = 3; -// constexpr double fidelity = 1 - 0.25; + Package dd(nq); -// Package dd(nq); + qc::QuantumComputation qc(nq); + qc.rx(qc::PI, 0); + qc.ry(2 * qc::PI / 3, 0); + qc.cx(0, 1); + qc.cx(1, 2); + qc.cry(qc::PI / 3, 2, 0); + qc.cry(qc::PI / 4, 2, 1); -// qc::QuantumComputation qc(nq); -// qc.rx(qc::PI, 0); -// qc.ry(2 * qc::PI / 3, 0); -// qc.cx(0, 1); -// qc.cx(1, 2); -// qc.cry(qc::PI / 3, 2, 0); -// qc.cry(qc::PI / 4, 2, 1); + auto state = simulate(qc, dd.makeZeroState(nq), dd); + auto [approx, postFidelity] = approximate(state, fidelity, dd); -// auto state = simulate(qc, dd.makeZeroState(nq), dd); -// auto [approx, postFidelity] = approximate(state, fidelity, dd); + const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; -// const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; - -// vecNear(approx.getVector(), expected); -// EXPECT_EQ(approx.size(), 4); -// EXPECT_NEAR(postFidelity, 0.75, 1e-3); -// } + vecNear(approx.getVector(), expected); + EXPECT_EQ(approx.size(), 4); + EXPECT_NEAR(postFidelity, 0.75, 1e-3); +} From 90bb2424f73ffed8ee7770e6ed2aba3548412d0b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 29 Apr 2025 07:47:33 +0200 Subject: [PATCH 44/59] Use unique_ptr for dd::Package --- test/dd/test_approximations.cpp | 51 +++++++++++++++++---------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 067dcff25..261a8de07 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -18,6 +18,7 @@ #include #include #include +#include using namespace dd; @@ -47,13 +48,13 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { constexpr std::size_t nq = 1; constexpr double fidelity = 1; - Package dd(nq); + auto dd = std::make_unique(nq); qc::QuantumComputation qc(nq); qc.x(0); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto p = approximate(state, fidelity, dd); + auto state = simulate(qc, dd->makeZeroState(nq), *dd); + auto p = approximate(state, fidelity, *dd); auto approx = p.first; const CVec expected{{0}, {1}}; @@ -73,13 +74,13 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { constexpr std::size_t nq = 1; constexpr double fidelity = 0.9; - Package dd(nq); + auto dd = std::make_unique(nq); qc::QuantumComputation qc(nq); qc.x(0); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto p = approximate(state, fidelity, dd); + auto state = simulate(qc, dd->makeZeroState(nq), *dd); + auto p = approximate(state, fidelity, *dd); auto approx = p.first; const CVec expected{{0}, {1}}; @@ -100,13 +101,13 @@ TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { constexpr std::size_t nq = 1; constexpr double fidelity = 1 - 0.25; - Package dd(nq); + auto dd = std::make_unique(nq); qc::QuantumComputation qc(nq); qc.ry(qc::PI / 3, 0); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); + auto state = simulate(qc, dd->makeZeroState(nq), *dd); + auto [approx, postFidelity] = approximate(state, fidelity, *dd); const CVec expected{{1}, {0}}; @@ -127,18 +128,18 @@ TEST(ApproximationTest, OneQubitCorrectRefCount) { constexpr std::size_t nq = 1; constexpr double fidelity = 1 - 0.25; - Package dd(nq); + auto dd = std::make_unique(nq); qc::QuantumComputation qc(nq); qc.ry(qc::PI / 3, 0); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto p = approximate(state, fidelity, dd); + auto state = simulate(qc, dd->makeZeroState(nq), *dd); + auto p = approximate(state, fidelity, *dd); - dd.decRef(p.first); - dd.garbageCollect(true); + dd->decRef(p.first); + dd->garbageCollect(true); - EXPECT_EQ(dd.vUniqueTable.getNumEntries(), 0); + EXPECT_EQ(dd->vUniqueTable.getNumEntries(), 0); } TEST(ApproximationTest, TwoQubitRemoveNode) { @@ -153,14 +154,14 @@ TEST(ApproximationTest, TwoQubitRemoveNode) { constexpr std::size_t nq = 2; constexpr double fidelity = 1 - 0.2; - Package dd(nq); + auto dd = std::make_unique(nq); qc::QuantumComputation qc(nq); qc.h(0); qc.cry(qc::PI / 3, 0, 1); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); + auto state = simulate(qc, dd->makeZeroState(nq), *dd); + auto [approx, postFidelity] = approximate(state, fidelity, *dd); const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; @@ -185,7 +186,7 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { constexpr std::size_t nq = 2; constexpr double fidelity = 1 - 0.1; - Package dd(nq); + auto dd = std::make_unique(nq); qc::QuantumComputation qc(nq); qc.h(0); @@ -198,9 +199,9 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { qcRef.cx(0, 1); qcRef.cx(1, 0); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); - auto ref = simulate(qcRef, dd.makeZeroState(nq), dd); + auto state = simulate(qc, dd->makeZeroState(nq), *dd); + auto [approx, postFidelity] = approximate(state, fidelity, *dd); + auto ref = simulate(qcRef, dd->makeZeroState(nq), *dd); const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; @@ -223,7 +224,7 @@ TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { constexpr std::size_t nq = 3; constexpr double fidelity = 1 - 0.25; - Package dd(nq); + auto dd = std::make_unique(nq); qc::QuantumComputation qc(nq); qc.rx(qc::PI, 0); @@ -233,8 +234,8 @@ TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { qc.cry(qc::PI / 3, 2, 0); qc.cry(qc::PI / 4, 2, 1); - auto state = simulate(qc, dd.makeZeroState(nq), dd); - auto [approx, postFidelity] = approximate(state, fidelity, dd); + auto state = simulate(qc, dd->makeZeroState(nq), *dd); + auto [approx, postFidelity] = approximate(state, fidelity, *dd); const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; From ae89cdfaed22e82708f0812fe15ca4b24b286d7f Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 29 Apr 2025 08:50:44 +0200 Subject: [PATCH 45/59] Eliminate contribution map --- src/dd/Approximation.cpp | 44 +++++++++++++++++++-------------- test/dd/test_approximations.cpp | 1 - 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index f392dc2eb..07b1b73dc 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -50,36 +50,42 @@ VectorDD rebuild(const VectorDD& state, std::pair approximate(const VectorDD& state, const double fidelity, Package& dd) { - std::forward_list exclude{}; - std::forward_list layer{&state}; - std::unordered_map contributions{ - {&state, ComplexNumbers::mag2(state.w)}}; + using Layer = std::forward_list>; double budget = 1 - fidelity; - while (!layer.empty() && budget > 0) { - std::forward_list nextLayer{}; + std::forward_list exclude{}; + + Layer curr{{&state, ComplexNumbers::mag2(state.w)}}; + while (!curr.empty() && budget > 0) { + Layer next{}; - for (const vEdge* edge : layer) { - const double contribution = contributions[edge]; + for (const auto& [e, contribution] : curr) { if (contribution <= budget) { - exclude.emplace_front(edge); + exclude.emplace_front(e); budget -= contribution; - } else if (!edge->isTerminal()) { - const vNode* node = edge->p; - for (const auto& nextEdge : node->e) { - if (!nextEdge.w.exactlyZero()) { - if (std::find(nextLayer.begin(), nextLayer.end(), &nextEdge) == - nextLayer.end()) { - nextLayer.emplace_front(&nextEdge); + } else if (!e->isTerminal()) { + const vNode* n = e->p; + + for (const vEdge& eChildRef : n->e) { + const vEdge* eChild = &eChildRef; + + if (!eChild->w.exactlyZero()) { + const double childContribution = + contribution * ComplexNumbers::mag2(eChild->w); + const auto it = std::find_if( + next.begin(), next.end(), + [&eChild](const auto& p) { return p.first == eChild; }); + if (it == next.end()) { + next.emplace_front(eChild, childContribution); + } else { + (*it).second += childContribution; } - contributions[&nextEdge] += - contribution * ComplexNumbers::mag2(nextEdge.w); } } } } - layer = std::move(nextLayer); + curr = std::move(next); } VectorDD approx = rebuild(state, exclude, dd); diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 261a8de07..b7b93b195 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -24,7 +24,6 @@ using namespace dd; namespace { void vecNear(CVec a, CVec b, double delta = 1e-6) { - assert(a.size() == b.size()); for (std::size_t i = 0; i < b.size(); ++i) { EXPECT_NEAR(a[i].real(), b[i].real(), delta); EXPECT_NEAR(b[i].imag(), b[i].imag(), delta); From f95f3881c9528838879554b70c1a6ba4760cf2dd Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 29 Apr 2025 08:56:00 +0200 Subject: [PATCH 46/59] Remove unused import of unordered_map --- src/dd/Approximation.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 07b1b73dc..e09bae996 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include namespace dd { From 4e638b791626e1d941a1a0fa638c4e5e94171d06 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 30 Apr 2025 08:34:47 +0200 Subject: [PATCH 47/59] Formatting --- test/dd/test_approximations.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index b7b93b195..18f4d887b 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -83,7 +83,6 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { auto approx = p.first; const CVec expected{{0}, {1}}; - EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); } @@ -109,7 +108,6 @@ TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { auto [approx, postFidelity] = approximate(state, fidelity, *dd); const CVec expected{{1}, {0}}; - EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); EXPECT_NEAR(postFidelity, 0.75, 1e-3); @@ -163,7 +161,6 @@ TEST(ApproximationTest, TwoQubitRemoveNode) { auto [approx, postFidelity] = approximate(state, fidelity, *dd); const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; - vecNear(approx.getVector(), expected); EXPECT_EQ(approx.size(), 3); EXPECT_NEAR(postFidelity, 0.875, 1e-3); @@ -203,7 +200,6 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { auto ref = simulate(qcRef, dd->makeZeroState(nq), *dd); const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; - vecNear(approx.getVector(), expected); EXPECT_EQ(approx.size(), 3); EXPECT_NEAR(postFidelity, 0.933, 1e-3); @@ -237,7 +233,6 @@ TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { auto [approx, postFidelity] = approximate(state, fidelity, *dd); const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; - vecNear(approx.getVector(), expected); EXPECT_EQ(approx.size(), 4); EXPECT_NEAR(postFidelity, 0.75, 1e-3); From 7fa8900f9a1b2213787b4ea4cc625f883688d351 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 1 May 2025 07:43:32 +0200 Subject: [PATCH 48/59] Update tests, its descriptions, and add ascii dds --- test/dd/test_approximations.cpp | 96 ++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 30 deletions(-) diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 18f4d887b..ae59d840c 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -39,10 +39,17 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { // Test: If the budget is 0, no approximation will be applied. // - // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // |state⟩ = |1⟩ // // Eliminate nothing (fidelity = 1). // → |approx⟩ = |state⟩ + // + // 1│ 1│ + // ┌─┴─┐ ┌─┴─┐ + // ┌─│ q0│─┐ -(approx)→ ┌─│ q0│─┐ + // 0 └───┘ │1 0 └───┘ │1 + // □ □ + // constexpr std::size_t nq = 1; constexpr double fidelity = 1; @@ -53,22 +60,29 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { qc.x(0); auto state = simulate(qc, dd->makeZeroState(nq), *dd); - auto p = approximate(state, fidelity, *dd); - auto approx = p.first; + auto [approx, postFidelity] = approximate(state, fidelity, *dd); const CVec expected{{0}, {1}}; EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); + EXPECT_EQ(postFidelity, 1); } TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { // Test: If the budget is too small, no approximation will be applied. // - // |state⟩ = 0.866|0⟩ + 0.5|1⟩ + // |state⟩ = |1⟩ // // Eliminate nothing: // → |approx⟩ = |state⟩ + // + // 1│ 1│ + // ┌─┴─┐ ┌─┴─┐ + // ┌─│ q0│─┐ -(approx)→ ┌─│ q0│─┐ + // 0 └───┘ │1 0 └───┘ │1 + // □ □ + // constexpr std::size_t nq = 1; constexpr double fidelity = 0.9; @@ -79,12 +93,12 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { qc.x(0); auto state = simulate(qc, dd->makeZeroState(nq), *dd); - auto p = approximate(state, fidelity, *dd); - auto approx = p.first; + auto [approx, postFidelity] = approximate(state, fidelity, *dd); const CVec expected{{0}, {1}}; EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); + EXPECT_EQ(postFidelity, 1); } TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { @@ -95,6 +109,13 @@ TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { // // Eliminate |1⟩ with contribution 0.25 // → |approx⟩ = |0⟩ + // + // 1│ 1│ + // ┌─┴─┐ ┌─┴─┐ + // ┌─│ q0│─┐ -(approx)→ ┌─│ q0│─┐ + // .866│ └───┘ │.5 1| └───┘ 0 + // □ □ □ + // constexpr std::size_t nq = 1; constexpr double fidelity = 1 - 0.25; @@ -111,29 +132,10 @@ TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { EXPECT_EQ(approx.getVector(), expected); EXPECT_EQ(approx.size(), 2); EXPECT_NEAR(postFidelity, 0.75, 1e-3); -} - -TEST(ApproximationTest, OneQubitCorrectRefCount) { // Test: Correctly increase and decrease ref counts. - // - // |state⟩ = 0.866|0⟩ + 0.5|1⟩ - // - // Eliminate |1⟩ with contribution 0.25 - // → |approx⟩ = |0⟩ - constexpr std::size_t nq = 1; - constexpr double fidelity = 1 - 0.25; - - auto dd = std::make_unique(nq); - - qc::QuantumComputation qc(nq); - qc.ry(qc::PI / 3, 0); - - auto state = simulate(qc, dd->makeZeroState(nq), *dd); - auto p = approximate(state, fidelity, *dd); - - dd->decRef(p.first); + dd->decRef(approx); dd->garbageCollect(true); EXPECT_EQ(dd->vUniqueTable.getNumEntries(), 0); @@ -147,6 +149,17 @@ TEST(ApproximationTest, TwoQubitRemoveNode) { // // Eliminate |11⟩ with contribution 0.125 // → |approx⟩ = 0.756|00⟩ + 0.654|01⟩ + // + // 1│ 1│ + // ┌─┴─┐ ┌─┴─┐ + // ┌───│ q1│───┐ ┌─│ q1│─┐ + // .94│ └───┘ │1/(2√2) 1| └───┘ 0 + // ┌─┴─┐ ┌─┴─┐ -(approx)→ ┌─┴─┐ + // ┌─│ q0│─┐ ┌─│ q0│─┐ ┌─│ q0│─┐ + // | └───┘ | 0 └───┘ |1 .756| └───┘ |.654 + // |.76 |.65 | □ □ + // □ □ □ + // constexpr std::size_t nq = 2; constexpr double fidelity = 1 - 0.2; @@ -178,6 +191,17 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { // → |approx⟩ = (1/sqrt(2))(|01⟩ + |11⟩) // // |ref⟩ = (1/sqrt(2))(|01⟩ + |11⟩) + // + // 1│ 1│ + // ┌─┴─┐ ┌─┴─┐ + // ┌─│ q1│─┐ ┌─│ q1│─┐ + // 1/√2│ └───┘ │1/√2 1/√2│ └───┘ │1/√2 + // └───┬───┘ └───┬───┘ + // ┌─┴─┐ -(approx)→ ┌─┴─┐ + // ┌─│ q0│─┐ ┌─│ q0│─┐ + // .26│ └───┘ │.97 0 └───┘ │1 + // □ □ □ + // constexpr std::size_t nq = 2; constexpr double fidelity = 1 - 0.1; @@ -190,10 +214,8 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { qc.ry(qc::PI / 3, 0); qc::QuantumComputation qcRef(nq); - qcRef.h(0); - qcRef.x(1); - qcRef.cx(0, 1); - qcRef.cx(1, 0); + qcRef.x(0); + qcRef.h(1); auto state = simulate(qc, dd->makeZeroState(nq), *dd); auto [approx, postFidelity] = approximate(state, fidelity, *dd); @@ -215,6 +237,20 @@ TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { // // Eliminate parent of |1xx⟩ with contribution ~ 0.25. // → |approx⟩ = i|000⟩ + // + // i│ i│ + // ┌─┴─┐ ┌─┴─┐ + // ┌───│ q2│───┐ ┌─│ q2│─┐ + // 0.87│ └───┘ │-1/2 1| └───┘ 0 + // ┌─┴─┐ ┌─┴─┐ -(approx)→ ┌─┴─┐ + // ┌─│ q1│─┐ ┌─│ q1│─┐ ┌─│ q1│─┐ + // | └───┘ 0 | └───┘ | 1| └───┘ 0 + // |1 -0.38└───┬───┘0.92 | + // ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ + // ┌─│ q0│─┐ ┌─│ q0│─┐ ┌─│ q0│─┐ + // 1| └───┘ 0 -1/2| └───┘ |0.87 1| └───┘ 0 + // □ □ □ □ + // constexpr std::size_t nq = 3; constexpr double fidelity = 1 - 0.25; From 6015b6949ef9f4aac95e7814a13ca0388274eed9 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 1 May 2025 07:45:22 +0200 Subject: [PATCH 49/59] Remove 0. in dd vis --- test/dd/test_approximations.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index ae59d840c..e2c8b93ac 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -241,14 +241,14 @@ TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { // i│ i│ // ┌─┴─┐ ┌─┴─┐ // ┌───│ q2│───┐ ┌─│ q2│─┐ - // 0.87│ └───┘ │-1/2 1| └───┘ 0 + // .87│ └───┘ │-1/2 1| └───┘ 0 // ┌─┴─┐ ┌─┴─┐ -(approx)→ ┌─┴─┐ // ┌─│ q1│─┐ ┌─│ q1│─┐ ┌─│ q1│─┐ // | └───┘ 0 | └───┘ | 1| └───┘ 0 - // |1 -0.38└───┬───┘0.92 | + // |1 -.38└───┬───┘.92 | // ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ // ┌─│ q0│─┐ ┌─│ q0│─┐ ┌─│ q0│─┐ - // 1| └───┘ 0 -1/2| └───┘ |0.87 1| └───┘ 0 + // 1| └───┘ 0 -1/2| └───┘ |.87 1| └───┘ 0 // □ □ □ □ // From 42ac8dfbacba5f30d2b6ef128c174dcdab192288 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 1 May 2025 07:52:18 +0200 Subject: [PATCH 50/59] De-nest if statements --- src/dd/Approximation.cpp | 43 +++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index e09bae996..a244def3b 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -62,24 +62,31 @@ std::pair approximate(const VectorDD& state, if (contribution <= budget) { exclude.emplace_front(e); budget -= contribution; - } else if (!e->isTerminal()) { - const vNode* n = e->p; - - for (const vEdge& eChildRef : n->e) { - const vEdge* eChild = &eChildRef; - - if (!eChild->w.exactlyZero()) { - const double childContribution = - contribution * ComplexNumbers::mag2(eChild->w); - const auto it = std::find_if( - next.begin(), next.end(), - [&eChild](const auto& p) { return p.first == eChild; }); - if (it == next.end()) { - next.emplace_front(eChild, childContribution); - } else { - (*it).second += childContribution; - } - } + continue; + } + + if (e->isTerminal()) { + continue; + } + + const vNode* n = e->p; + for (const vEdge& eChildRef : n->e) { + const vEdge* eChild = &eChildRef; + + if (eChild->w.exactlyZero()) { + continue; + } + + const double childContribution = + contribution * ComplexNumbers::mag2(eChild->w); + const auto it = + std::find_if(next.begin(), next.end(), [&eChild](const auto& p) { + return p.first == eChild; + }); + if (it == next.end()) { + next.emplace_front(eChild, childContribution); + } else { + (*it).second += childContribution; } } } From 9d79391240074e13a276ec9b25ff84c15b8d0927 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 1 May 2025 07:55:40 +0200 Subject: [PATCH 51/59] Extract global phase --- src/dd/Approximation.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index a244def3b..0971a6c52 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -51,6 +51,8 @@ std::pair approximate(const VectorDD& state, const double fidelity, Package& dd) { using Layer = std::forward_list>; + const Complex phase = state.w; // global phase to apply after approximation. + double budget = 1 - fidelity; std::forward_list exclude{}; @@ -95,7 +97,7 @@ std::pair approximate(const VectorDD& state, } VectorDD approx = rebuild(state, exclude, dd); - approx.w = dd.cn.lookup(approx.w / std::sqrt(ComplexNumbers::mag2(approx.w))); + approx.w = phase; dd.incRef(approx); dd.decRef(state); From 4a8d67b6883cba35ada20b501ac40f63bea6693e Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 1 May 2025 08:05:16 +0200 Subject: [PATCH 52/59] Use unordered_map for layer --- src/dd/Approximation.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 0971a6c52..a3ec72913 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -17,8 +17,8 @@ #include #include -#include #include +#include #include namespace dd { @@ -49,7 +49,7 @@ VectorDD rebuild(const VectorDD& state, std::pair approximate(const VectorDD& state, const double fidelity, Package& dd) { - using Layer = std::forward_list>; + using Layer = std::unordered_map; const Complex phase = state.w; // global phase to apply after approximation. @@ -75,21 +75,14 @@ std::pair approximate(const VectorDD& state, for (const vEdge& eChildRef : n->e) { const vEdge* eChild = &eChildRef; + // Don't add zero contribution children. if (eChild->w.exactlyZero()) { continue; } - const double childContribution = - contribution * ComplexNumbers::mag2(eChild->w); - const auto it = - std::find_if(next.begin(), next.end(), [&eChild](const auto& p) { - return p.first == eChild; - }); - if (it == next.end()) { - next.emplace_front(eChild, childContribution); - } else { - (*it).second += childContribution; - } + // Implicit: If `next` doesn't contain `eChild`, it will be initialized + // with 0. See `operator[]`. + next[eChild] += contribution * ComplexNumbers::mag2(eChild->w); } } From 5676056c79ef4c4c546383b4183f6523ef333b02 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 5 May 2025 12:44:53 +0200 Subject: [PATCH 53/59] Remove edges without a second top-down traversal --- include/mqt-core/dd/Approximation.hpp | 8 +-- src/dd/Approximation.cpp | 82 ++++++++++++++++----------- test/dd/test_approximations.cpp | 55 +++++++++--------- 3 files changed, 78 insertions(+), 67 deletions(-) diff --git a/include/mqt-core/dd/Approximation.hpp b/include/mqt-core/dd/Approximation.hpp index 274c18f9f..0b650a2eb 100644 --- a/include/mqt-core/dd/Approximation.hpp +++ b/include/mqt-core/dd/Approximation.hpp @@ -13,8 +13,6 @@ #include "dd/Node.hpp" #include "dd/Package.hpp" -#include - namespace dd { /** @@ -28,10 +26,8 @@ namespace dd { * @param state The DD to approximate. * @param fidelity The desired minimum fidelity after approximation. * @param dd The DD package to use for the approximation. - * @return Pair of approximated state and the fidelity between the source and - * approximated state. + * @return The fidelity between the source and the approximated state. */ -std::pair approximate(const VectorDD& state, double fidelity, - Package& dd); +double approximate(VectorDD& state, double fidelity, Package& dd); } // namespace dd diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index a3ec72913..9f951da3f 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -15,7 +15,6 @@ #include "dd/Node.hpp" #include "dd/Package.hpp" -#include #include #include #include @@ -23,59 +22,60 @@ namespace dd { namespace { +using UpwardsItem = std::pair, std::size_t>; +using Upwards = std::unordered_map; +using Layer = std::unordered_map; + /** - * @brief Recursively rebuild @p state. Exclude edges contained in @p exclude. - * @return Rebuilt VectorDD. + * @brief Sets the edge specified by the @p item to `vEdge::zero()` and + * multiplies the weights of the parent's edges with the weight of the + * new edge. + * @note The resulting DD is not normalized. */ -VectorDD rebuild(const VectorDD& state, - const std::forward_list& exclude, Package& dd) { - const auto it = std::find(exclude.begin(), exclude.end(), &state); - if (it != exclude.end()) { - return vEdge::zero(); - } +void zeroEdge(UpwardsItem& item, Package& dd) { + const auto& [parents, i] = item; + const vNode* parentNode = parents.front()->p; - if (state.isTerminal()) { - return state; - } + std::array edges = parentNode->e; + edges[i] = vEdge::zero(); + + vEdge newEdge = dd.makeDDNode(parentNode->v, edges); + Complex w = newEdge.w; - const std::array edges{rebuild(state.p->e[0], exclude, dd), - rebuild(state.p->e[1], exclude, dd)}; + for (const auto& edge : parents) { + newEdge.w = dd.cn.lookup(w * edge->w); - VectorDD edge = dd.makeDDNode(state.p->v, edges); - edge.w = dd.cn.lookup(edge.w * state.w); - return edge; + dd.decRef(*edge); + dd.incRef(newEdge); + *edge = newEdge; + } } }; // namespace -std::pair approximate(const VectorDD& state, - const double fidelity, Package& dd) { - using Layer = std::unordered_map; +double approximate(VectorDD& state, const double fidelity, Package& dd) { + const Complex phase = state.w; - const Complex phase = state.w; // global phase to apply after approximation. + Upwards upwards{}; + Layer curr{{&state, ComplexNumbers::mag2(state.w)}}; double budget = 1 - fidelity; - std::forward_list exclude{}; - - Layer curr{{&state, ComplexNumbers::mag2(state.w)}}; while (!curr.empty() && budget > 0) { Layer next{}; + Upwards upwardsNext{}; - for (const auto& [e, contribution] : curr) { + for (const auto& [edge, contribution] : curr) { if (contribution <= budget) { - exclude.emplace_front(e); + zeroEdge(upwards[edge], dd); budget -= contribution; continue; } - if (e->isTerminal()) { + if (edge->isTerminal()) { continue; } - const vNode* n = e->p; - for (const vEdge& eChildRef : n->e) { - const vEdge* eChild = &eChildRef; - - // Don't add zero contribution children. + for (std::size_t i = 0; i < RADIX; ++i) { + vEdge* eChild = &edge->p->e[i]; if (eChild->w.exactlyZero()) { continue; } @@ -83,20 +83,34 @@ std::pair approximate(const VectorDD& state, // Implicit: If `next` doesn't contain `eChild`, it will be initialized // with 0. See `operator[]`. next[eChild] += contribution * ComplexNumbers::mag2(eChild->w); + + // Link the child with the parent's edge and its associated index. + if (upwardsNext.find(eChild) == upwardsNext.end()) { + upwardsNext[eChild] = {{edge}, i}; + continue; + } + upwardsNext[eChild].first.emplace_front(edge); } } curr = std::move(next); + upwards = std::move(upwardsNext); } - VectorDD approx = rebuild(state, exclude, dd); + // Rebuild only the root state to normalize all nodes of the DD. + // Then: Apply global phase. + VectorDD approx = dd.makeDDNode(state.p->v, state.p->e); approx.w = phase; + // Make sure to correctly update the reference counts and clean up. dd.incRef(approx); dd.decRef(state); dd.garbageCollect(); - return {approx, fidelity + budget}; + // Finally, apply approximation to source state. + state = approx; + + return fidelity + budget; } } // namespace dd diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index e2c8b93ac..58988036d 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -60,12 +60,12 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetZero) { qc.x(0); auto state = simulate(qc, dd->makeZeroState(nq), *dd); - auto [approx, postFidelity] = approximate(state, fidelity, *dd); + auto fidelityToSource = approximate(state, fidelity, *dd); const CVec expected{{0}, {1}}; - EXPECT_EQ(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 2); - EXPECT_EQ(postFidelity, 1); + EXPECT_EQ(state.getVector(), expected); + EXPECT_EQ(state.size(), 2); + EXPECT_EQ(fidelityToSource, 1); } TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { @@ -93,12 +93,12 @@ TEST(ApproximationTest, OneQubitKeepAllBudgetTooSmall) { qc.x(0); auto state = simulate(qc, dd->makeZeroState(nq), *dd); - auto [approx, postFidelity] = approximate(state, fidelity, *dd); + auto fidelityToSource = approximate(state, fidelity, *dd); const CVec expected{{0}, {1}}; - EXPECT_EQ(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 2); - EXPECT_EQ(postFidelity, 1); + EXPECT_EQ(state.getVector(), expected); + EXPECT_EQ(state.size(), 2); + EXPECT_EQ(fidelityToSource, 1); } TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { @@ -126,16 +126,16 @@ TEST(ApproximationTest, OneQubitRemoveTerminalEdge) { qc.ry(qc::PI / 3, 0); auto state = simulate(qc, dd->makeZeroState(nq), *dd); - auto [approx, postFidelity] = approximate(state, fidelity, *dd); + auto fidelityToSource = approximate(state, fidelity, *dd); const CVec expected{{1}, {0}}; - EXPECT_EQ(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 2); - EXPECT_NEAR(postFidelity, 0.75, 1e-3); + EXPECT_EQ(state.getVector(), expected); + EXPECT_EQ(state.size(), 2); + EXPECT_NEAR(fidelityToSource, 0.75, 1e-3); // Test: Correctly increase and decrease ref counts. - dd->decRef(approx); + dd->decRef(state); dd->garbageCollect(true); EXPECT_EQ(dd->vUniqueTable.getNumEntries(), 0); @@ -157,7 +157,7 @@ TEST(ApproximationTest, TwoQubitRemoveNode) { // ┌─┴─┐ ┌─┴─┐ -(approx)→ ┌─┴─┐ // ┌─│ q0│─┐ ┌─│ q0│─┐ ┌─│ q0│─┐ // | └───┘ | 0 └───┘ |1 .756| └───┘ |.654 - // |.76 |.65 | □ □ + // |.76 |.65 | □ □ // □ □ □ // @@ -171,12 +171,13 @@ TEST(ApproximationTest, TwoQubitRemoveNode) { qc.cry(qc::PI / 3, 0, 1); auto state = simulate(qc, dd->makeZeroState(nq), *dd); - auto [approx, postFidelity] = approximate(state, fidelity, *dd); + auto fidelityToSource = approximate(state, fidelity, *dd); const CVec expected{{0.755929}, {0.654654}, {0}, {0}}; - vecNear(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 3); - EXPECT_NEAR(postFidelity, 0.875, 1e-3); + + vecNear(state.getVector(), expected); + EXPECT_EQ(state.size(), 3); + EXPECT_NEAR(fidelityToSource, 0.875, 1e-3); } TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { @@ -218,14 +219,14 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { qcRef.h(1); auto state = simulate(qc, dd->makeZeroState(nq), *dd); - auto [approx, postFidelity] = approximate(state, fidelity, *dd); + auto fidelityToSource = approximate(state, fidelity, *dd); auto ref = simulate(qcRef, dd->makeZeroState(nq), *dd); const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; - vecNear(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 3); - EXPECT_NEAR(postFidelity, 0.933, 1e-3); - EXPECT_EQ(ref, approx); // implicit: utilize `==` operator. + vecNear(state.getVector(), expected); + EXPECT_EQ(state.size(), 3); + EXPECT_NEAR(fidelityToSource, 0.933, 1e-3); + EXPECT_EQ(ref, state); // implicit: utilize `==` operator. } TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { @@ -266,10 +267,10 @@ TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { qc.cry(qc::PI / 4, 2, 1); auto state = simulate(qc, dd->makeZeroState(nq), *dd); - auto [approx, postFidelity] = approximate(state, fidelity, *dd); + auto fidelityToSource = approximate(state, fidelity, *dd); const CVec expected{{0, 1}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; - vecNear(approx.getVector(), expected); - EXPECT_EQ(approx.size(), 4); - EXPECT_NEAR(postFidelity, 0.75, 1e-3); + vecNear(state.getVector(), expected); + EXPECT_EQ(state.size(), 4); + EXPECT_NEAR(fidelityToSource, 0.75, 1e-3); } From 238a84b762aa79fa79bd09470ec06bf8cd7234fd Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 5 May 2025 12:50:27 +0200 Subject: [PATCH 54/59] Fix linting issues --- src/dd/Approximation.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 9f951da3f..0df873f47 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -10,12 +10,14 @@ #include "dd/Approximation.hpp" +#include "dd/Complex.hpp" #include "dd/ComplexNumbers.hpp" #include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Package.hpp" #include +#include #include #include #include @@ -40,7 +42,7 @@ void zeroEdge(UpwardsItem& item, Package& dd) { edges[i] = vEdge::zero(); vEdge newEdge = dd.makeDDNode(parentNode->v, edges); - Complex w = newEdge.w; + const Complex w = newEdge.w; for (const auto& edge : parents) { newEdge.w = dd.cn.lookup(w * edge->w); From df3be9c6ad6852c4da64e93bc77df7aa87fa0ce3 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 8 May 2025 08:33:04 +0200 Subject: [PATCH 55/59] Correctly rebuild tree --- src/dd/Approximation.cpp | 99 +++++++++++++++++---------------- test/dd/test_approximations.cpp | 47 ++++++++++++++++ 2 files changed, 97 insertions(+), 49 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 0df873f47..3dfad82e0 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -18,99 +18,100 @@ #include #include -#include #include +#include #include namespace dd { namespace { -using UpwardsItem = std::pair, std::size_t>; -using Upwards = std::unordered_map; using Layer = std::unordered_map; /** - * @brief Sets the edge specified by the @p item to `vEdge::zero()` and - * multiplies the weights of the parent's edges with the weight of the - * new edge. - * @note The resulting DD is not normalized. + * @brief Given the l-th layer, compute the contributions for the (l-1)-th + * layer. + * @return The (l-1)-th layer. */ -void zeroEdge(UpwardsItem& item, Package& dd) { - const auto& [parents, i] = item; - const vNode* parentNode = parents.front()->p; - - std::array edges = parentNode->e; - edges[i] = vEdge::zero(); - - vEdge newEdge = dd.makeDDNode(parentNode->v, edges); - const Complex w = newEdge.w; +Layer computeLayer(const Layer& l) { + Layer lNext; + for (const auto& [edge, contribution] : l) { + if (edge->isTerminal()) { + continue; + } - for (const auto& edge : parents) { - newEdge.w = dd.cn.lookup(w * edge->w); + for (std::size_t i = 0; i < RADIX; ++i) { + vEdge* eChild = &edge->p->e[i]; + if (eChild->w.exactlyZero()) { + continue; + } - dd.decRef(*edge); - dd.incRef(newEdge); - *edge = newEdge; + lNext[eChild] += contribution * ComplexNumbers::mag2(eChild->w); + } } + + return lNext; } }; // namespace double approximate(VectorDD& state, const double fidelity, Package& dd) { - const Complex phase = state.w; - - Upwards upwards{}; - Layer curr{{&state, ComplexNumbers::mag2(state.w)}}; + Layer curr{{&state, 1.}}; double budget = 1 - fidelity; while (!curr.empty() && budget > 0) { - Layer next{}; - Upwards upwardsNext{}; + Layer candidates = computeLayer(curr); - for (const auto& [edge, contribution] : curr) { + std::unordered_set m{}; + for (const auto& [edge, contribution] : candidates) { if (contribution <= budget) { - zeroEdge(upwards[edge], dd); budget -= contribution; - continue; + m.emplace(edge); } + } + Layer next{}; + for (const auto& [edge, _] : curr) { if (edge->isTerminal()) { continue; } + vNode* node = edge->p; + std::array edges{}; for (std::size_t i = 0; i < RADIX; ++i) { - vEdge* eChild = &edge->p->e[i]; - if (eChild->w.exactlyZero()) { + vEdge* eChild = &(node->e[i]); + if (m.find(eChild) != m.end()) { + edges[i] = vEdge::zero(); continue; } - // Implicit: If `next` doesn't contain `eChild`, it will be initialized - // with 0. See `operator[]`. - next[eChild] += contribution * ComplexNumbers::mag2(eChild->w); + edges[i] = *eChild; + } + vEdge replacement = dd.makeDDNode(node->v, edges); + replacement.w = edge->w; + dd.incRef(replacement); + dd.decRef(*edge); + *edge = replacement; - // Link the child with the parent's edge and its associated index. - if (upwardsNext.find(eChild) == upwardsNext.end()) { - upwardsNext[eChild] = {{edge}, i}; - continue; - } - upwardsNext[eChild].first.emplace_front(edge); + for (std::size_t i = 0; i < RADIX; ++i) { + next[&replacement.p->e[i]] = candidates[&(node->e[i])]; } } curr = std::move(next); - upwards = std::move(upwardsNext); } // Rebuild only the root state to normalize all nodes of the DD. - // Then: Apply global phase. + // TODO: Necessary? VectorDD approx = dd.makeDDNode(state.p->v, state.p->e); - approx.w = phase; + approx.w = state.w; // Make sure to correctly update the reference counts and clean up. - dd.incRef(approx); - dd.decRef(state); - dd.garbageCollect(); - // Finally, apply approximation to source state. - state = approx; + if (approx != state) { + dd.incRef(approx); + dd.decRef(state); + state = approx; + } + + dd.garbageCollect(); return fidelity + budget; } diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index 58988036d..c892ba8f2 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -223,6 +223,7 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { auto ref = simulate(qcRef, dd->makeZeroState(nq), *dd); const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; + state.printVector(); vecNear(state.getVector(), expected); EXPECT_EQ(state.size(), 3); EXPECT_NEAR(fidelityToSource, 0.933, 1e-3); @@ -274,3 +275,49 @@ TEST(ApproximationTest, ThreeQubitRemoveNodeWithChildren) { EXPECT_EQ(state.size(), 4); EXPECT_NEAR(fidelityToSource, 0.75, 1e-3); } + +TEST(ApproximationTest, ThreeQubitRemoveUnconnected) { + + // Test: Remove multiple edges. + // |state⟩ = 0+0.131j|000⟩ + 0-0.190j|100⟩ + 0+0.329j|101⟩ + 0+0.458j|110⟩ + // + 0-0.793j|111⟩ + // + // Eliminate |000⟩ and |1x0⟩ with contributions ~0.017 and ~0.144. + // → |approx⟩ = 0-0.5j|110⟩ + 0+0.87j|111⟩ + // + // i│ i│ + // ┌─┴─┐ ┌─┴─┐ + // ┌───│ q2│───┐ ┌─│ q2│─┐ + // -.13│ └───┘ │.99 0 └───┘ |1 + // ┌─┴─┐ ┌─┴─┐ -(approx)→ ┌─┴─┐ + // ┌─│ q1│─┐ ┌─│ q1│─┐ ┌─│ q1│─┐ + // | └───┘ 0 | └───┘ | 0 └───┘ |1 + // |1 -.38└───┬───┘.9 ┌───┘ + // ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ + // ┌─│ q0│─┐ ┌─│ q0│─┐ ┌─│ q0│─┐ + // 1| └───┘ 0 -1/2| └───┘ |.87 -1/2| └───┘ |.87 + // □ □ □ □ □ + // + + constexpr std::size_t nq = 3; + constexpr double fidelity = 1 - 0.17; + + auto dd = std::make_unique(nq); + + qc::QuantumComputation qc(nq); + qc.rx(qc::PI, 0); + qc.ry(qc::PI / 12, 0); + qc.cx(0, 1); + qc.cx(1, 2); + qc.cry(qc::PI / 3, 2, 0); + qc.cry(qc::PI / 4, 2, 1); + + auto state = simulate(qc, dd->makeZeroState(nq), *dd); + auto fidelityToSource = approximate(state, fidelity, *dd); + + const CVec expected{{0}, {0}, {0}, {0}, + {0}, {0, 0.337652}, {0, -0.5}, {0, 0.866025}}; + vecNear(state.getVector(), expected); + EXPECT_EQ(state.size(), 4); + EXPECT_NEAR(fidelityToSource, 0.8390109, 1e-3); +} From b5f08102866cdf5529f93613a9bcbb65c3df875e Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 8 May 2025 13:18:28 +0200 Subject: [PATCH 56/59] Add rebuild based on paths --- src/dd/Approximation.cpp | 112 ++++++++++++++------------------ test/dd/test_approximations.cpp | 1 - 2 files changed, 50 insertions(+), 63 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 3dfad82e0..c6ae23204 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -17,102 +17,90 @@ #include "dd/Package.hpp" #include -#include +#include #include -#include #include namespace dd { namespace { -using Layer = std::unordered_map; +vEdge rebuild(const vEdge& curr, + std::unordered_map& removables, Package& dd) { + if (removables.find(&curr) == removables.end()) { + return curr; + } -/** - * @brief Given the l-th layer, compute the contributions for the (l-1)-th - * layer. - * @return The (l-1)-th layer. - */ -Layer computeLayer(const Layer& l) { - Layer lNext; - for (const auto& [edge, contribution] : l) { - if (edge->isTerminal()) { - continue; - } + if (removables[&curr]) { + return vEdge::zero(); + } - for (std::size_t i = 0; i < RADIX; ++i) { - vEdge* eChild = &edge->p->e[i]; - if (eChild->w.exactlyZero()) { - continue; - } + std::array edges{ + rebuild(curr.p->e[0], removables, dd), + rebuild(curr.p->e[1], removables, dd), + }; - lNext[eChild] += contribution * ComplexNumbers::mag2(eChild->w); - } - } + vEdge e = dd.makeDDNode(curr.p->v, edges); + e.w = curr.w; - return lNext; + return e; } }; // namespace double approximate(VectorDD& state, const double fidelity, Package& dd) { - Layer curr{{&state, 1.}}; + using Path = std::forward_list; + using Paths = std::forward_list; + using Queue = std::unordered_map>; + + std::unordered_map removables{}; double budget = 1 - fidelity; - while (!curr.empty() && budget > 0) { - Layer candidates = computeLayer(curr); - std::unordered_set m{}; - for (const auto& [edge, contribution] : candidates) { + Queue l{{&state, {1., {{}}}}}; + while (!l.empty() && budget > 0) { + Queue lNext{}; + + for (const auto& [edge, pair] : l) { + const auto [contribution, paths] = pair; + if (contribution <= budget) { + for (const auto& path : paths) { + for (const auto& e : path) { + removables[e] = false; + } + } + removables[edge] = true; budget -= contribution; - m.emplace(edge); + continue; } - } - Layer next{}; - for (const auto& [edge, _] : curr) { if (edge->isTerminal()) { continue; } - vNode* node = edge->p; - std::array edges{}; - for (std::size_t i = 0; i < RADIX; ++i) { - vEdge* eChild = &(node->e[i]); - if (m.find(eChild) != m.end()) { - edges[i] = vEdge::zero(); + for (const auto& eRef : edge->p->e) { + if (eRef.w.exactlyZero()) { continue; } - edges[i] = *eChild; - } - vEdge replacement = dd.makeDDNode(node->v, edges); - replacement.w = edge->w; - dd.incRef(replacement); - dd.decRef(*edge); - *edge = replacement; - - for (std::size_t i = 0; i < RADIX; ++i) { - next[&replacement.p->e[i]] = candidates[&(node->e[i])]; + lNext[&eRef].first += contribution * ComplexNumbers::mag2(eRef.w); + + Paths extended{paths}; + for (auto& path : extended) { + path.emplace_front(edge); + lNext[&eRef].second.emplace_front(path); + } } } - curr = std::move(next); - } - - // Rebuild only the root state to normalize all nodes of the DD. - // TODO: Necessary? - VectorDD approx = dd.makeDDNode(state.p->v, state.p->e); - approx.w = state.w; - - // Make sure to correctly update the reference counts and clean up. - // Finally, apply approximation to source state. - if (approx != state) { - dd.incRef(approx); - dd.decRef(state); - state = approx; + l = std::move(lNext); } + vEdge approx = rebuild(state, removables, dd); + dd.incRef(approx); + dd.decRef(state); dd.garbageCollect(); + state = approx; + return fidelity + budget; } diff --git a/test/dd/test_approximations.cpp b/test/dd/test_approximations.cpp index c892ba8f2..bb825b89d 100644 --- a/test/dd/test_approximations.cpp +++ b/test/dd/test_approximations.cpp @@ -223,7 +223,6 @@ TEST(ApproximationTest, TwoQubitCorrectlyRebuilt) { auto ref = simulate(qcRef, dd->makeZeroState(nq), *dd); const CVec expected{{0}, {1 / std::sqrt(2)}, {0}, {1 / std::sqrt(2)}}; - state.printVector(); vecNear(state.getVector(), expected); EXPECT_EQ(state.size(), 3); EXPECT_NEAR(fidelityToSource, 0.933, 1e-3); From edf4b1a172cbb21d9946f4f57cf9b07ae82f390e Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 9 May 2025 10:52:54 +0200 Subject: [PATCH 57/59] Simplify code --- src/dd/Approximation.cpp | 123 ++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 48 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index c6ae23204..5a5e366c0 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -18,87 +18,114 @@ #include #include +#include #include -#include namespace dd { namespace { -vEdge rebuild(const vEdge& curr, - std::unordered_map& removables, Package& dd) { - if (removables.find(&curr) == removables.end()) { - return curr; +// Queue for iterative-deepening search. +using Queue = std::queue; +// Map that holds edges required for rebuilding the DD. +// If the value of an edge is true it will be deleted. +using PathFlags = std::unordered_map; +// Maps edges to their respective contribution. +using Contributions = std::unordered_map; +// Maps edges to their respective parent edges. +using Parents = + std::unordered_map>; + +vEdge rebuild(const vEdge& e, PathFlags& f, Package& dd) { + // If the edge isn't contained in the pathflags, + // we keep the edge as it is. + if (f.find(&e) == f.end()) { + return e; } - if (removables[&curr]) { + // If the pathflag is true, delete the edge. + if (f[&e]) { return vEdge::zero(); } + // Otherwise, if the pathflag is false, traverse down. std::array edges{ - rebuild(curr.p->e[0], removables, dd), - rebuild(curr.p->e[1], removables, dd), + rebuild(e.p->e[0], f, dd), + rebuild(e.p->e[1], f, dd), }; - vEdge e = dd.makeDDNode(curr.p->v, edges); - e.w = curr.w; + vEdge eNew = dd.makeDDNode(e.p->v, edges); + eNew.w = e.w; - return e; + return eNew; +} + +/** + * @brief Flag (or mark) the path from edge @p e to the root node. + */ +void markParentEdges(const vEdge* e, Parents& m, PathFlags& f) { + Queue q{}; + q.emplace(e); + while (!q.empty()) { + const vEdge* eX = q.front(); + q.pop(); + for (const vEdge* eP : m[eX]) { + f[eP]; + q.emplace(eP); + } + } } }; // namespace double approximate(VectorDD& state, const double fidelity, Package& dd) { - using Path = std::forward_list; - using Paths = std::forward_list; - using Queue = std::unordered_map>; + Queue q{}; + q.emplace(&state); - std::unordered_map removables{}; + PathFlags f{}; + Contributions c{{&state, 1.}}; + Parents m{{&state, {}}}; double budget = 1 - fidelity; + while (!q.empty() && budget > 0) { + const vEdge* e = q.front(); + const double contribution = c[e]; + q.pop(); + + if (contribution <= budget) { + f[e] = true; + markParentEdges(e, m, f); + budget -= contribution; + continue; + } - Queue l{{&state, {1., {{}}}}}; - while (!l.empty() && budget > 0) { - Queue lNext{}; - - for (const auto& [edge, pair] : l) { - const auto [contribution, paths] = pair; - - if (contribution <= budget) { - for (const auto& path : paths) { - for (const auto& e : path) { - removables[e] = false; - } - } - removables[edge] = true; - budget -= contribution; - continue; - } + if (e->isTerminal()) { + continue; + } - if (edge->isTerminal()) { + const vNode* n = e->p; + for (const auto& eChildRef : n->e) { + const vEdge* eChild = &eChildRef; + + if (eChild->w.exactlyZero()) { // Don't add zero terminals. continue; } - for (const auto& eRef : edge->p->e) { - if (eRef.w.exactlyZero()) { - continue; - } + if (c.find(eChild) == c.cend()) { + q.emplace(eChild); // Add to queue. + c[eChild] = 0.; // Not necessary, but better than implicit. + } - lNext[&eRef].first += contribution * ComplexNumbers::mag2(eRef.w); + // An edge may have multiple parent edges, and hence, add instead of + // assign the full contribution. + const double childContribution = ComplexNumbers::mag2(eChild->w); + c[eChild] += contribution * childContribution; - Paths extended{paths}; - for (auto& path : extended) { - path.emplace_front(edge); - lNext[&eRef].second.emplace_front(path); - } - } + m[eChild].emplace_front(e); // Map child to parent. } - - l = std::move(lNext); } - vEdge approx = rebuild(state, removables, dd); + vEdge approx = rebuild(state, f, dd); dd.incRef(approx); dd.decRef(state); dd.garbageCollect(); - state = approx; return fidelity + budget; From 1ff3fefc6d922136dccf96e720f0c31b781e3b60 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 9 May 2025 11:21:41 +0200 Subject: [PATCH 58/59] Add doxygen comments --- src/dd/Approximation.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index 5a5e366c0..a502e220f 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -34,7 +34,13 @@ using Contributions = std::unordered_map; using Parents = std::unordered_map>; -vEdge rebuild(const vEdge& e, PathFlags& f, Package& dd) { +/** + * @brief Recursively rebuild DD depth-first. + + * @details Only visits the paths from the root edge + * to ∀e ∈ {e | f[e] = true} by using @p f. + */ +vEdge rebuild(const vEdge& e, const PathFlags& f, Package& dd) { // If the edge isn't contained in the pathflags, // we keep the edge as it is. if (f.find(&e) == f.end()) { @@ -42,7 +48,7 @@ vEdge rebuild(const vEdge& e, PathFlags& f, Package& dd) { } // If the pathflag is true, delete the edge. - if (f[&e]) { + if (f.at(&e)) { return vEdge::zero(); } @@ -61,14 +67,14 @@ vEdge rebuild(const vEdge& e, PathFlags& f, Package& dd) { /** * @brief Flag (or mark) the path from edge @p e to the root node. */ -void markParentEdges(const vEdge* e, Parents& m, PathFlags& f) { +void markParentEdges(const vEdge* e, const Parents& m, PathFlags& f) { Queue q{}; q.emplace(e); while (!q.empty()) { const vEdge* eX = q.front(); q.pop(); - for (const vEdge* eP : m[eX]) { - f[eP]; + for (const vEdge* eP : m.at(eX)) { + f[eP] = false; q.emplace(eP); } } @@ -80,8 +86,8 @@ double approximate(VectorDD& state, const double fidelity, Package& dd) { q.emplace(&state); PathFlags f{}; - Contributions c{{&state, 1.}}; Parents m{{&state, {}}}; + Contributions c{{&state, 1.}}; double budget = 1 - fidelity; while (!q.empty() && budget > 0) { @@ -113,8 +119,8 @@ double approximate(VectorDD& state, const double fidelity, Package& dd) { c[eChild] = 0.; // Not necessary, but better than implicit. } - // An edge may have multiple parent edges, and hence, add instead of - // assign the full contribution. + // An edge may have multiple parent edges, and hence, add (instead of + // assign) the full contribution. const double childContribution = ComplexNumbers::mag2(eChild->w); c[eChild] += contribution * childContribution; From b07d10068dfcf49b45e411349c02befc479afcc2 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 9 May 2025 12:38:06 +0200 Subject: [PATCH 59/59] Fix linting issues --- src/dd/Approximation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dd/Approximation.cpp b/src/dd/Approximation.cpp index a502e220f..88e817fe9 100644 --- a/src/dd/Approximation.cpp +++ b/src/dd/Approximation.cpp @@ -53,7 +53,7 @@ vEdge rebuild(const vEdge& e, const PathFlags& f, Package& dd) { } // Otherwise, if the pathflag is false, traverse down. - std::array edges{ + const std::array edges{ rebuild(e.p->e[0], f, dd), rebuild(e.p->e[1], f, dd), }; @@ -128,7 +128,7 @@ double approximate(VectorDD& state, const double fidelity, Package& dd) { } } - vEdge approx = rebuild(state, f, dd); + const vEdge approx = rebuild(state, f, dd); dd.incRef(approx); dd.decRef(state); dd.garbageCollect();