diff --git a/include/mqt-core/QuantumComputation.hpp b/include/mqt-core/QuantumComputation.hpp index f0cc939ac..c2d86408e 100644 --- a/include/mqt-core/QuantumComputation.hpp +++ b/include/mqt-core/QuantumComputation.hpp @@ -306,6 +306,13 @@ class QuantumComputation { [[nodiscard]] std::size_t getNqubitsWithoutAncillae() const { return nqubits; } + [[nodiscard]] std::size_t getNmeasuredQubits() const { + return getNqubits() - getNgarbageQubits(); + } + [[nodiscard]] std::size_t getNgarbageQubits() const { + return static_cast( + std::count(getGarbage().begin(), getGarbage().end(), true)); + } [[nodiscard]] std::size_t getNcbits() const { return nclassics; } [[nodiscard]] std::string getName() const { return name; } [[nodiscard]] const QuantumRegisterMap& getQregs() const { return qregs; } @@ -367,11 +374,30 @@ class QuantumComputation { * @param logicalQubitIndex */ void setLogicalQubitAncillary(Qubit logicalQubitIndex); + /** + * @brief Sets all logical qubits in the range [minLogicalQubitIndex, + * maxLogicalQubitIndex] to be ancillary + * @details Removes the qubits from the qubit register and adds it to the + * ancillary register, if such a register exists. Otherwise a new ancillary + * register is created. + * @param minLogicalQubitIndex first qubit that is set to be ancillary + * @param maxLogicalQubitIndex last qubit that is set to be ancillary + */ + void setLogicalQubitsAncillary(Qubit minLogicalQubitIndex, + Qubit maxLogicalQubitIndex); [[nodiscard]] bool logicalQubitIsGarbage(const Qubit logicalQubitIndex) const { return garbage[logicalQubitIndex]; } void setLogicalQubitGarbage(Qubit logicalQubitIndex); + /** + * @brief Sets all logical qubits in the range [minLogicalQubitIndex, + * maxLogicalQubitIndex] to be garbage + * @param minLogicalQubitIndex first qubit that is set to be garbage + * @param maxLogicalQubitIndex last qubit that is set to be garbage + */ + void setLogicalQubitsGarbage(Qubit minLogicalQubitIndex, + Qubit maxLogicalQubitIndex); [[nodiscard]] const std::vector& getAncillary() const { return ancillary; } diff --git a/include/mqt-core/dd/FunctionalityConstruction.hpp b/include/mqt-core/dd/FunctionalityConstruction.hpp index 93ae66488..e88d235f4 100644 --- a/include/mqt-core/dd/FunctionalityConstruction.hpp +++ b/include/mqt-core/dd/FunctionalityConstruction.hpp @@ -8,11 +8,15 @@ namespace dd { using namespace qc; template -MatrixDD buildFunctionality(const QuantumComputation* qc, Package& dd); +MatrixDD buildFunctionality(const QuantumComputation* qc, Package& dd, + bool reduceAncillaryQubits = true, + bool reduceGarbageQubits = true); template MatrixDD buildFunctionalityRecursive(const QuantumComputation* qc, - Package& dd); + Package& dd, + bool reduceAncillaryQubits = true, + bool reduceGarbageQubits = true); template bool buildFunctionalityRecursive(const QuantumComputation* qc, diff --git a/src/QuantumComputation.cpp b/src/QuantumComputation.cpp index be1093b27..f6322b1f0 100644 --- a/src/QuantumComputation.cpp +++ b/src/QuantumComputation.cpp @@ -898,6 +898,13 @@ void QuantumComputation::setLogicalQubitAncillary( ancillary[logicalQubitIndex] = true; } +void QuantumComputation::setLogicalQubitsAncillary( + const Qubit minLogicalQubitIndex, const Qubit maxLogicalQubitIndex) { + for (Qubit i = minLogicalQubitIndex; i <= maxLogicalQubitIndex; i++) { + setLogicalQubitAncillary(i); + } +} + void QuantumComputation::setLogicalQubitGarbage(const Qubit logicalQubitIndex) { garbage[logicalQubitIndex] = true; // setting a logical qubit garbage also means removing it from the output @@ -911,6 +918,13 @@ void QuantumComputation::setLogicalQubitGarbage(const Qubit logicalQubitIndex) { } } +void QuantumComputation::setLogicalQubitsGarbage( + const Qubit minLogicalQubitIndex, const Qubit maxLogicalQubitIndex) { + for (Qubit i = minLogicalQubitIndex; i <= maxLogicalQubitIndex; i++) { + setLogicalQubitGarbage(i); + } +} + [[nodiscard]] std::pair> QuantumComputation::containsLogicalQubit(const Qubit logicalQubitIndex) const { if (const auto it = std::find_if(initialLayout.cbegin(), initialLayout.cend(), diff --git a/src/dd/FunctionalityConstruction.cpp b/src/dd/FunctionalityConstruction.cpp index 7a4f06f7b..90052e594 100644 --- a/src/dd/FunctionalityConstruction.cpp +++ b/src/dd/FunctionalityConstruction.cpp @@ -2,7 +2,9 @@ namespace dd { template -MatrixDD buildFunctionality(const QuantumComputation* qc, Package& dd) { +MatrixDD buildFunctionality(const QuantumComputation* qc, Package& dd, + bool reduceAncillaryQubits, + bool reduceGarbageQubits) { const auto nq = qc->getNqubits(); if (nq == 0U) { return MatrixDD::one(); @@ -26,15 +28,21 @@ MatrixDD buildFunctionality(const QuantumComputation* qc, Package& dd) { } // correct permutation if necessary changePermutation(e, permutation, qc->outputPermutation, dd); - e = dd.reduceAncillae(e, qc->ancillary); - e = dd.reduceGarbage(e, qc->garbage); + if (reduceAncillaryQubits) { + e = dd.reduceAncillae(e, qc->ancillary); + } + if (reduceGarbageQubits) { + e = dd.reduceGarbage(e, qc->garbage); + } return e; } template MatrixDD buildFunctionalityRecursive(const QuantumComputation* qc, - Package& dd) { + Package& dd, + bool reduceAncillaryQubits, + bool reduceGarbageQubits) { if (qc->getNqubits() == 0U) { return MatrixDD::one(); } @@ -59,8 +67,13 @@ MatrixDD buildFunctionalityRecursive(const QuantumComputation* qc, // correct permutation if necessary changePermutation(e, permutation, qc->outputPermutation, dd); - e = dd.reduceAncillae(e, qc->ancillary); - e = dd.reduceGarbage(e, qc->garbage); + + if (reduceAncillaryQubits) { + e = dd.reduceAncillae(e, qc->ancillary); + } + if (reduceGarbageQubits) { + e = dd.reduceGarbage(e, qc->garbage); + } return e; } @@ -198,16 +211,22 @@ MatrixDD buildFunctionalityRecursive(const qc::Grover* qc, } template MatrixDD buildFunctionality(const qc::QuantumComputation* qc, - Package& dd); + Package& dd, + bool reduceAncillaryQubits, + bool reduceGarbageQubits); template MatrixDD buildFunctionality(const qc::QuantumComputation* qc, - Package& dd); + Package& dd, + bool reduceAncillaryQubits, bool reduceGarbageQubits); template MatrixDD buildFunctionality(const qc::QuantumComputation* qc, - Package& dd); + Package& dd, + bool reduceAncillaryQubits, bool reduceGarbageQubits); template MatrixDD buildFunctionalityRecursive(const qc::QuantumComputation* qc, - Package& dd); + Package& dd, + bool reduceAncillaryQubits, + bool reduceGarbageQubits); template bool buildFunctionalityRecursive(const qc::QuantumComputation* qc, const std::size_t depth, const std::size_t opIdx, diff --git a/test/dd/test_package.cpp b/test/dd/test_package.cpp index a1c234d52..242622651 100644 --- a/test/dd/test_package.cpp +++ b/test/dd/test_package.cpp @@ -1222,6 +1222,62 @@ TEST(DDPackageTest, CloseToIdentityWithGarbageInTheMiddle) { {true, false, true}, false)); } +TEST(DDPackageTest, CloseToIdentityFalse) { + const auto nqubits = 2U; + const auto dd = std::make_unique>(nqubits); + // the first qubit has differing gates in the two circuits, + // therefore they should not be equivalent if we only measure the first qubit + const auto hGate = dd->makeGateDD(dd::H_MAT, nqubits, 0); + const auto xGate = dd->makeGateDD(dd::X_MAT, nqubits, 0); + const auto circuit1 = dd->multiply(xGate, hGate); + const auto circuit2 = dd->makeIdent(2); + auto u1u2 = dd->multiply(circuit1, dd->conjugateTranspose(circuit2)); + std::vector garbage(nqubits, false); + garbage[1] = true; + EXPECT_FALSE(dd->isCloseToIdentity(u1u2, 1.0E-10, garbage, false)); +} + +TEST(DDPackageTest, CloseToIdentityExamplePaper) { + const auto nqubits = 3U; + const auto dd = std::make_unique>(nqubits); + const auto controlledSwapGate = + dd->makeTwoQubitGateDD(dd::SWAP_MAT, nqubits, qc::Controls{1}, 0, 2); + const auto hGate = dd->makeGateDD(dd::H_MAT, nqubits, 0); + const auto zGate = dd->makeGateDD(dd::Z_MAT, nqubits, 2); + const auto xGate = dd->makeGateDD(dd::X_MAT, nqubits, 1); + const auto controlledHGate = + dd->makeGateDD(dd::H_MAT, nqubits, qc::Controls{1}, 0); + + const auto c1 = dd->multiply( + controlledSwapGate, + dd->multiply(hGate, dd->multiply(zGate, controlledSwapGate))); + const auto c2 = dd->multiply(controlledHGate, xGate); + + auto c1c2 = dd->multiply(c1, dd->conjugateTranspose(c2)); + std::vector garbage(nqubits, false); + // only the last qubit is garbage + garbage[2] = true; + EXPECT_FALSE(dd->isCloseToIdentity(c1c2, 1.0E-10, garbage, false)); + // the last two qubits are garbage + garbage[1] = true; + EXPECT_TRUE(dd->isCloseToIdentity(c1c2, 1.0E-10, garbage, false)); + + const auto hGate2 = dd->makeGateDD(dd::H_MAT, nqubits, 2); + const auto zGate2 = dd->makeGateDD(dd::Z_MAT, nqubits, 0); + const auto controlledHGate2 = + dd->makeGateDD(dd::H_MAT, nqubits, qc::Controls{1}, 0); + + const auto c3 = dd->multiply( + controlledSwapGate, + dd->multiply(hGate2, dd->multiply(zGate2, controlledSwapGate))); + const auto c4 = dd->multiply(controlledHGate2, xGate); + + auto c3c4 = dd->multiply(c3, dd->conjugateTranspose(c4)); + + // the last two qubits are garbage + EXPECT_FALSE(dd->isCloseToIdentity(c3c4, 1.0E-10, garbage, false)); +} + struct DensityMatrixSimulatorDDPackageConfigTesting : public dd::DDPackageConfig { static constexpr std::size_t UT_DM_NBUCKET = 65536U; @@ -2254,8 +2310,8 @@ TEST(DDPackageTest, DDStatistics) { EXPECT_GT(uniqueTableStats["total"]["num_buckets"], 0); } -TEST(DDPackageTest, ReduceAncillaRegression) { - auto dd = std::make_unique>(2); +TEST(DDPackageTest, ReduceAncillaeRegression) { + const auto dd = std::make_unique>(2); const auto inputMatrix = dd::CMat{{1, 1, 1, 1}, {1, -1, 1, -1}, {1, 1, -1, -1}, {1, -1, -1, 1}}; auto inputDD = dd->makeDDFromMatrix(inputMatrix); diff --git a/test/unittests/test_qfr_functionality.cpp b/test/unittests/test_qfr_functionality.cpp index 8d1029551..40f5e0022 100644 --- a/test/unittests/test_qfr_functionality.cpp +++ b/test/unittests/test_qfr_functionality.cpp @@ -2424,6 +2424,24 @@ TEST_F(QFRFunctionality, testSettingAncillariesProperlyCreatesRegisters) { ASSERT_EQ(qc.getNancillae(), 0U); } +TEST_F(QFRFunctionality, testSettingSetMultipleAncillariesAndGarbage) { + // create an empty circuit and assert some properties about its registers + qc::QuantumComputation qc(3U); + const auto& ancRegs = qc.getANCregs(); + ASSERT_TRUE(ancRegs.empty()); + ASSERT_EQ(qc.getNqubitsWithoutAncillae(), 3U); + ASSERT_EQ(qc.getNancillae(), 0U); + + // set some ancillaries garbage and assert that the registers are created + // properly + qc.setLogicalQubitsAncillary(1U, 2U); + ASSERT_EQ(qc.getNqubitsWithoutAncillae(), 1U); + ASSERT_EQ(qc.getNancillae(), 2U); + qc.setLogicalQubitsGarbage(1U, 2U); + ASSERT_EQ(qc.getNgarbageQubits(), 2U); + ASSERT_EQ(qc.getNmeasuredQubits(), 1U); +} + TEST_F(QFRFunctionality, StripIdleQubitsInMiddleOfCircuit) { qc::QuantumComputation qc(5U); qc.setLogicalQubitAncillary(3U);