diff --git a/extern/mqt-core b/extern/mqt-core index d88ad6c0..8294bc9a 160000 --- a/extern/mqt-core +++ b/extern/mqt-core @@ -1 +1 @@ -Subproject commit d88ad6c0ce9d126692687ba623a55160bfeed70f +Subproject commit 8294bc9aef2a2022300ef61a272f27062df33c41 diff --git a/pyproject.toml b/pyproject.toml index 03d194b9..5465b410 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,6 +132,7 @@ filterwarnings = [ 'ignore:.*qiskit.utils.algorithm_globals.QiskitAlgorithmGlobals*:DeprecationWarning:qiskit', 'ignore:.*Building a flow controller with keyword arguments is going to be deprecated*:PendingDeprecationWarning:qiskit', 'ignore:.*encountered in det.*:RuntimeWarning:numpy.linalg:', + 'ignore:.*datetime\.datetime\.utcfromtimestamp.*:DeprecationWarning:', ] [tool.coverage] diff --git a/src/checker/dd/DDAlternatingChecker.cpp b/src/checker/dd/DDAlternatingChecker.cpp index c5699ac3..3a8c9136 100644 --- a/src/checker/dd/DDAlternatingChecker.cpp +++ b/src/checker/dd/DDAlternatingChecker.cpp @@ -93,39 +93,29 @@ void DDAlternatingChecker::postprocess() { if (isDone()) { return; } - - // sum up the contributions of garbage qubits - taskManager1.reduceGarbage(functionality); - if (isDone()) { - return; - } - taskManager2.reduceGarbage(functionality); - if (isDone()) { - return; - } } EquivalenceCriterion DDAlternatingChecker::checkEquivalence() { - // create the full identity matrix - auto goalMatrix = dd->makeIdent(nqubits); - dd->incRef(goalMatrix); - - // account for any garbage - taskManager1.reduceGarbage(goalMatrix); - taskManager2.reduceGarbage(goalMatrix); - - taskManager1.reduceAncillae(goalMatrix); - taskManager2.reduceAncillae(goalMatrix); + std::vector garbage(nqubits); + for (qc::Qubit q = 0U; q < nqubits; ++q) { + garbage[static_cast(q)] = + qc1.logicalQubitIsGarbage(q) && qc2.logicalQubitIsGarbage(q); + } - // the resulting goal matrix is - // [1 0] if the qubit is no ancillary - // [0 1] - // - // [1 0] (= |0><0>|) for an ancillary that is present in either circuit - // [0 0] + // if partial equivalence is being checked instead of total equivalence, it + // suffices to change the last parameter of isCloseToIdentity to `false` + const bool isClose = dd->isCloseToIdentity( + functionality, configuration.functionality.traceThreshold, garbage, true); - // compare the obtained functionality to the goal matrix - return equals(functionality, goalMatrix); + if (isClose) { + // whenever the top edge weight is not one, both decision diagrams are only + // equivalent up to a global phase + if (!functionality.w.approximatelyEquals(dd::Complex::one())) { + return EquivalenceCriterion::EquivalentUpToGlobalPhase; + } + return EquivalenceCriterion::Equivalent; + } + return EquivalenceCriterion::NotEquivalent; } [[nodiscard]] bool DDAlternatingChecker::gatesAreIdentical() const { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c7b537ad..8b65e5de 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,4 +11,5 @@ package_add_test( test_gate_cost_application_scheme.cpp test_equality.cpp test_zx.cpp - test_symbolic.cpp) + test_symbolic.cpp + test_partial_equivalence.cpp) diff --git a/test/test_partial_equivalence.cpp b/test/test_partial_equivalence.cpp new file mode 100644 index 00000000..626636f3 --- /dev/null +++ b/test/test_partial_equivalence.cpp @@ -0,0 +1,140 @@ +// +// This file is part of the MQT QCEC library released under the MIT license. +// See README.md or go to https://github.com/cda-tum/qcec for more information. +// + +#include "EquivalenceCheckingManager.hpp" + +#include "gtest/gtest.h" + +class PartialEqualityTest : public testing::Test { + void SetUp() override { + qc1 = qc::QuantumComputation(nqubits); + qc2 = qc::QuantumComputation(nqubits); + config.optimizations.fuseSingleQubitGates = false; + config.optimizations.reorderOperations = false; + config.optimizations.reconstructSWAPs = false; + config.optimizations.fixOutputPermutationMismatch = true; + + config.execution.runSimulationChecker = false; + config.execution.runAlternatingChecker = false; + config.execution.runConstructionChecker = false; + config.execution.runZXChecker = false; + } + +protected: + std::size_t nqubits = 3U; + qc::QuantumComputation qc1; + qc::QuantumComputation qc2; + ec::Configuration config{}; +}; + +TEST_F(PartialEqualityTest, AlternatingCheckerGarbage) { + // these circuits have the same gates acting on the measured qubit + // and random gates acting on the two garbage qubits + qc1.cswap(1, 0, 2); + qc1.cx(2, 0); + qc1.h(0); + qc1.tdg(1); + qc1.s(1); + qc1.z(2); + + qc2.cswap(1, 0, 2); + qc2.cx(2, 0); + qc2.h(0); + qc2.h(2); + qc2.rz(dd::PI_4, 1); + qc2.ry(0.1, 1); + qc2.cx(1, 2); + + qc1.setLogicalQubitGarbage(2); + qc1.setLogicalQubitGarbage(1); + qc2.setLogicalQubitGarbage(2); + qc2.setLogicalQubitGarbage(1); + + config.execution.runAlternatingChecker = true; + ec::EquivalenceCheckingManager ecm(qc1, qc2, config); + ecm.setApplicationScheme(ec::ApplicationSchemeType::Proportional); + ecm.run(); + EXPECT_EQ(ecm.equivalence(), + ec::EquivalenceCriterion::EquivalentUpToGlobalPhase); +} + +TEST_F(PartialEqualityTest, AlternatingCheckerGarbage2) { + // measured qubit: 1 + qc1.setLogicalQubitGarbage(2); + qc1.setLogicalQubitGarbage(0); + + qc1.h(1); + qc1.tdg(0); + qc1.s(0); + qc1.z(2); + + // measured qubit: 1 + qc2.setLogicalQubitGarbage(0); + qc2.setLogicalQubitGarbage(2); + + qc2.h(1); + qc2.h(0); + qc2.rz(dd::PI_4, 2); + qc2.ry(0.1, 2); + qc2.cx(2, 0); + + config.execution.runAlternatingChecker = true; + ec::EquivalenceCheckingManager ecm(qc1, qc2, config); + ecm.setApplicationScheme(ec::ApplicationSchemeType::Proportional); + ecm.run(); + EXPECT_EQ(ecm.equivalence(), + ec::EquivalenceCriterion::EquivalentUpToGlobalPhase); +} + +TEST_F(PartialEqualityTest, AlternatingCheckerGarbageAndAncillary) { + qc1.setLogicalQubitGarbage(2); + qc1.setLogicalQubitGarbage(1); + qc1.setLogicalQubitAncillary(2); + + qc1.h(0); + qc1.tdg(1); + qc1.s(1); + // ancillary qubits are initialized to zero, therefore this gate doesn't + // change the functionality of the circuit + qc1.cx(2, 0); + + qc::QuantumComputation qc3(nqubits - 1); + qc3.setLogicalQubitGarbage(1); + + qc3.h(0); + qc3.rz(dd::PI_4, 1); + qc3.ry(0.1, 1); + + config.execution.runAlternatingChecker = true; + ec::EquivalenceCheckingManager ecm(qc1, qc3, config); + ecm.setApplicationScheme(ec::ApplicationSchemeType::Proportional); + ecm.run(); + EXPECT_EQ(ecm.equivalence(), + ec::EquivalenceCriterion::EquivalentUpToGlobalPhase); +} + +TEST_F(PartialEqualityTest, AlternatingCheckerGarbageNotEquivalent) { + // example from the paper https://arxiv.org/abs/2208.07564 + // these two circuits are only partially equivalent, + // therefore the equivalence checker returns NotEquivalent + qc1.cswap(1, 0, 2); + qc1.h(0); + qc1.z(2); + qc1.cswap(1, 0, 2); + + qc2.x(1); + qc2.ch(1, 0); + + qc1.setLogicalQubitGarbage(2); + qc1.setLogicalQubitGarbage(1); + qc2.setLogicalQubitGarbage(2); + qc2.setLogicalQubitGarbage(1); + + config.execution.runAlternatingChecker = true; + ec::EquivalenceCheckingManager ecm(qc1, qc2, config); + ecm.setApplicationScheme(ec::ApplicationSchemeType::Proportional); + ecm.run(); + EXPECT_EQ(ecm.equivalence(), ec::EquivalenceCriterion::NotEquivalent); +}