From 2134c2e6285ceda1df55bc3f7ff96a468f0c157e Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 8 Dec 2025 16:40:12 +0100 Subject: [PATCH 01/73] Add multi-control functionality Add CRZ, MCX, MCZ, MCRZ functionality to ZX package. --- .../mqt-core/zx/FunctionalityConstruction.hpp | 12 ++ src/zx/FunctionalityConstruction.cpp | 185 +++++++++++++++++- test/zx/test_zx_functionality.cpp | 139 ++++++++++++- 3 files changed, 328 insertions(+), 8 deletions(-) diff --git a/include/mqt-core/zx/FunctionalityConstruction.hpp b/include/mqt-core/zx/FunctionalityConstruction.hpp index ac7817f313..7b8036403e 100644 --- a/include/mqt-core/zx/FunctionalityConstruction.hpp +++ b/include/mqt-core/zx/FunctionalityConstruction.hpp @@ -113,6 +113,18 @@ class FunctionalityConstruction { const std::optional& unconvertedBeta = std::nullopt); static void addCcx(ZXDiagram& diag, Qubit ctrl0, Qubit ctrl1, Qubit target, std::vector& qubits); + static void addCcz(ZXDiagram& diag, Qubit ctrl0, Qubit ctrl1, Qubit target, + std::vector& qubits); + static void addCrz(ZXDiagram& diag, const PiExpression& phase, + const Qubit control, const Qubit target, + std::vector& qubits); + static void addMcrz(ZXDiagram& diag, const PiExpression& phase, + std::vector controls, const Qubit target, + std::vector& qubits); + static void addMcx(ZXDiagram& diag, std::vector controls, + const Qubit target, std::vector& qubits); + static void addMcz(ZXDiagram& diag, std::vector controls, + const Qubit target, std::vector& qubits); static op_it parseOp(ZXDiagram& diag, op_it it, op_it end, std::vector& qubits, const qc::Permutation& p); static op_it parseCompoundOp(ZXDiagram& diag, op_it it, op_it end, diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index e69dbef87a..fe10750e14 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -307,6 +307,145 @@ void FunctionalityConstruction::addCcx(ZXDiagram& diag, const Qubit ctrl0, addCnot(diag, ctrl0, ctrl1, qubits); } +void FunctionalityConstruction::addCcz(ZXDiagram& diag, const Qubit ctrl0, + const Qubit ctrl1, const Qubit target, + std::vector& qubits) { + + addCnot(diag, ctrl1, target, qubits); + addZSpider(diag, target, qubits, PiExpression(PiRational(-1, 4))); + addCnot(diag, ctrl0, target, qubits); + addZSpider(diag, target, qubits, PiExpression(PiRational(1, 4))); + addCnot(diag, ctrl1, target, qubits); + addZSpider(diag, ctrl1, qubits, PiExpression(PiRational(1, 4))); + addZSpider(diag, target, qubits, PiExpression(PiRational(-1, 4))); + addCnot(diag, ctrl0, target, qubits); + addZSpider(diag, target, qubits, PiExpression(PiRational(1, 4))); + addCnot(diag, ctrl0, ctrl1, qubits); + addZSpider(diag, ctrl0, qubits, PiExpression(PiRational(1, 4))); + addZSpider(diag, ctrl1, qubits, PiExpression(PiRational(-1, 4))); + addZSpider(diag, target, qubits, PiExpression(PiRational(0, 1)), + EdgeType::Hadamard); + addCnot(diag, ctrl0, ctrl1, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); +} + +void FunctionalityConstruction::addCrz(ZXDiagram& diag, + const PiExpression& phase, + const Qubit control, const Qubit target, + std::vector& qubits) { + addCnot(diag, target, control, qubits); + addZSpider(diag, control, qubits, -phase / 2); + addZSpider(diag, target, qubits, phase / 2); + addCnot(diag, target, control, qubits); +} + +void FunctionalityConstruction::addMcrz(ZXDiagram& diag, + const PiExpression& phase, + std::vector controls, + const Qubit target, + std::vector& qubits) { + + switch (controls.size()) { + case 0: + addZSpider(diag, target, qubits, phase); + return; + case 1: + addCrz(diag, phase, controls.front(), target, qubits); + return; + default: + Qubit nextControl = controls.back(); + controls.pop_back(); + + addCrz(diag, phase / 2, nextControl, target, qubits); + addMcx(diag, controls, target, qubits); + addCrz(diag, -phase / 2, nextControl, target, qubits); + addMcx(diag, controls, target, qubits); + } +} + +void FunctionalityConstruction::addMcx(ZXDiagram& diag, + std::vector controls, + const Qubit target, + std::vector& qubits) { + + switch (controls.size()) { + case 0: + addXSpider(diag, target, qubits, PiExpression(PiRational(1, 1))); + return; + case 1: + addCnot(diag, controls.front(), target, qubits); + return; + case 2: + addCcx(diag, controls.front(), controls.back(), target, qubits); + return; + default: + size_t half = (controls.size() + 1) / 2; + std::vector first(controls.begin(), controls.begin() + half); + std::vector second(controls.begin() + half, controls.end()); + + if (qubits.size() > controls.size() + 1) { + controls.push_back(target); + Qubit anc; + for (int x : qubits) { + if (std::find(controls.begin(), controls.end(), + static_cast(x)) == controls.end()) { + anc = x; + break; + } + } + controls.pop_back(); + second.push_back(anc); + + addMcx(diag, first, anc, qubits); + addMcx(diag, second, target, qubits); + + addMcx(diag, first, anc, qubits); + addMcx(diag, second, target, qubits); + } else { + addRx(diag, PiExpression(PiRational(1, 4)), target, qubits); + addMcz(diag, second, target, qubits); + addRx(diag, PiExpression(-PiRational(1, 4)), target, qubits); + addMcx(diag, first, target, qubits); + + addRx(diag, PiExpression(PiRational(1, 4)), target, qubits); + addMcz(diag, second, target, qubits); + addRx(diag, PiExpression(-PiRational(1, 4)), target, qubits); + addMcx(diag, first, target, qubits); + Qubit lastControl = controls.back(); + controls.pop_back(); + addMcrz(diag, PiExpression(PiRational(1, 2)), controls, lastControl, + qubits); + } + } +} + +void FunctionalityConstruction::addMcz(ZXDiagram& diag, + std::vector controls, + const Qubit target, + std::vector& qubits) { + + switch (controls.size()) { + case 0: + addZSpider(diag, target, qubits, PiExpression(PiRational(1, 1))); + return; + case 1: + addCrz(diag, PiExpression(PiRational(1, 1)), controls.front(), target, + qubits); + return; + case 2: + addCcz(diag, controls.front(), controls.back(), target, qubits); + return; + default: + Qubit nextControl = controls.back(); + controls.pop_back(); + + addCrz(diag, PiExpression(PiRational(1, 2)), nextControl, target, qubits); + addMcx(diag, controls, target, qubits); + addCrz(diag, PiExpression(-PiRational(1, 2)), nextControl, target, qubits); + addMcx(diag, controls, target, qubits); + } +} + FunctionalityConstruction::op_it FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, std::vector& qubits, @@ -538,6 +677,9 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, qubits[static_cast(target)], EdgeType::Hadamard); break; + case qc::OpType::RZ: + addCrz(diag, parseParam(op.get(), 0), ctrl, target, qubits); + break; case qc::OpType::I: break; @@ -578,16 +720,42 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, ctrl1 = static_cast(p.at(ctrl.qubit)); } } + std::vector controls; + for (const auto& ctrl : op->getControls()) { + controls.push_back(p.at(ctrl.qubit)); + } switch (op->getType()) { case qc::OpType::X: addCcx(diag, ctrl0, ctrl1, target, qubits); break; - + case qc::OpType::Z: + addCcz(diag, ctrl0, ctrl1, target, qubits); + break; + case qc::OpType::RZ: + addMcrz(diag, parseParam(op.get(), 0), controls, target, qubits); + break; + default: + throw ZXException("Unsupported Multi-control operation: " + + qc::toString(op->getType())); + } + } else if (op->getNtargets() == 1) { + const auto target = static_cast(p.at(op->getTargets().front())); + std::vector controls; + for (const auto& ctrl : op->getControls()) { + controls.push_back(p.at(ctrl.qubit)); + } + switch (op->getType()) { + case qc::OpType::X: + addMcx(diag, controls, target, qubits); + break; case qc::OpType::Z: addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addCcx(diag, ctrl0, ctrl1, target, qubits); + addMcx(diag, controls, target, qubits); addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); break; + case qc::OpType::RZ: + addMcrz(diag, parseParam(op.get(), 0), controls, target, qubits); + break; default: throw ZXException("Unsupported Multi-control operation: " + qc::toString(op->getType())); @@ -701,6 +869,7 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { case qc::OpType::S: case qc::OpType::Tdg: case qc::OpType::Sdg: + case qc::OpType::RZ: return true; default: @@ -710,12 +879,22 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { switch (op->getType()) { case qc::OpType::X: case qc::OpType::Z: + case qc::OpType::RZ: + return true; + default: + return false; + } + } else if (op->getNtargets() == 1) { + switch (op->getType()) { + case qc::OpType::X: + case qc::OpType::Z: + case qc::OpType::RZ: return true; default: return false; } + return false; } - return false; } PiExpression FunctionalityConstruction::parseParam(const qc::Operation* op, diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 60ecc9132a..42c8d375f2 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -219,14 +219,143 @@ TEST_F(ZXFunctionalityTest, Compound) { EXPECT_TRUE(diag.isIdentity()); } -TEST_F(ZXFunctionalityTest, UnsupportedMultiControl) { +// TEST_F(ZXFunctionalityTest, UnsupportedMultiControl) { +// using namespace qc::literals; +// qc = qc::QuantumComputation(4); +// qc.mcx({1, 2, 3}, 0); +// EXPECT_FALSE(FunctionalityConstruction::transformableToZX(&qc)); +// EXPECT_THROW(const ZXDiagram diag = +// FunctionalityConstruction::buildFunctionality(&qc), +// ZXException); +// } + +TEST_F(ZXFunctionalityTest, CRZ) { + using namespace qc::literals; + qc = qc::QuantumComputation(2); + qc.crz(PI / 2, 0, 1); + + auto qcPrime = qc::QuantumComputation(2); + + qcPrime.cx(0, 1); + qcPrime.rz(-PI / 4, 1); + qcPrime.cx(0, 1); + qcPrime.rz(PI / 4, 1); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + +TEST_F(ZXFunctionalityTest, MultiControlX) { using namespace qc::literals; qc = qc::QuantumComputation(4); qc.mcx({1, 2, 3}, 0); - EXPECT_FALSE(FunctionalityConstruction::transformableToZX(&qc)); - EXPECT_THROW(const ZXDiagram diag = - FunctionalityConstruction::buildFunctionality(&qc), - ZXException); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mcx({1, 2, 3}, 0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + +TEST_F(ZXFunctionalityTest, MultiControlXLarger) { + using namespace qc::literals; + qc = qc::QuantumComputation(5); + qc.mcx({1, 2, 3}, 0); + + auto qcPrime = qc::QuantumComputation(5); + qcPrime.mcx({1, 2}, 4); + qcPrime.mcx({3, 4}, 0); + qcPrime.mcx({1, 2}, 4); + qcPrime.mcx({3, 4}, 0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + +TEST_F(ZXFunctionalityTest, MultiControlX0) { + using namespace qc::literals; + qc = qc::QuantumComputation(1); + qc.mcx({}, 0); + + auto qcPrime = qc::QuantumComputation(1); + + qcPrime.x(0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + +TEST_F(ZXFunctionalityTest, MultiControlX1) { + using namespace qc::literals; + qc = qc::QuantumComputation(2); + qc.mcx({1}, 0); + + auto qcPrime = qc::QuantumComputation(2); + + qcPrime.cx(1, 0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + +TEST_F(ZXFunctionalityTest, MultiCRZ) { + using namespace qc::literals; + qc = qc::QuantumComputation(3); + qc.mcrz(PI / 4, {1, 2}, 0); + qc.mcrz(-PI / 4, {1, 2}, 0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); } TEST_F(ZXFunctionalityTest, UnsupportedControl) { From f8c450306cb495f584a2c8c891e26f18dfdd1e70 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 8 Dec 2025 23:18:08 +0100 Subject: [PATCH 02/73] Apply minor updates to code Apply const to constant variables. Replace for loop with std::ranges::find_if implementation. Utilize static_cast to explicitly state conversion from unsigned to signed int. Shift return false of `transformableToZX` to be reachable again. --- src/zx/FunctionalityConstruction.cpp | 32 +++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index fe10750e14..fc4f6c0335 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -353,7 +353,7 @@ void FunctionalityConstruction::addMcrz(ZXDiagram& diag, addCrz(diag, phase, controls.front(), target, qubits); return; default: - Qubit nextControl = controls.back(); + const Qubit nextControl = controls.back(); controls.pop_back(); addCrz(diag, phase / 2, nextControl, target, qubits); @@ -379,19 +379,21 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, addCcx(diag, controls.front(), controls.back(), target, qubits); return; default: - size_t half = (controls.size() + 1) / 2; - std::vector first(controls.begin(), controls.begin() + half); + const auto half = static_cast((controls.size() + 1) / 2); + const std::vector first(controls.begin(), controls.begin() + half); std::vector second(controls.begin() + half, controls.end()); if (qubits.size() > controls.size() + 1) { controls.push_back(target); - Qubit anc; - for (int x : qubits) { - if (std::find(controls.begin(), controls.end(), - static_cast(x)) == controls.end()) { - anc = x; - break; - } + Qubit anc = -1; + auto it = std::ranges::find_if(qubits, [&](Vertex x) { + return std::ranges::find(controls, static_cast(x)) == + controls.end(); + }); + if (it == qubits.end()) { + throw ZXException("No ancilla qubit available for MCX decomposition"); + } else { + anc = static_cast(*it); } controls.pop_back(); second.push_back(anc); @@ -411,7 +413,7 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, addMcz(diag, second, target, qubits); addRx(diag, PiExpression(-PiRational(1, 4)), target, qubits); addMcx(diag, first, target, qubits); - Qubit lastControl = controls.back(); + const Qubit lastControl = controls.back(); controls.pop_back(); addMcrz(diag, PiExpression(PiRational(1, 2)), controls, lastControl, qubits); @@ -436,7 +438,7 @@ void FunctionalityConstruction::addMcz(ZXDiagram& diag, addCcz(diag, controls.front(), controls.back(), target, qubits); return; default: - Qubit nextControl = controls.back(); + const Qubit nextControl = controls.back(); controls.pop_back(); addCrz(diag, PiExpression(PiRational(1, 2)), nextControl, target, qubits); @@ -722,7 +724,7 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, } std::vector controls; for (const auto& ctrl : op->getControls()) { - controls.push_back(p.at(ctrl.qubit)); + controls.push_back(static_cast(p.at(ctrl.qubit))); } switch (op->getType()) { case qc::OpType::X: @@ -742,7 +744,7 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, const auto target = static_cast(p.at(op->getTargets().front())); std::vector controls; for (const auto& ctrl : op->getControls()) { - controls.push_back(p.at(ctrl.qubit)); + controls.push_back(static_cast(p.at(ctrl.qubit))); } switch (op->getType()) { case qc::OpType::X: @@ -893,8 +895,8 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { default: return false; } - return false; } + return false; } PiExpression FunctionalityConstruction::parseParam(const qc::Operation* op, From 894add1587aa4de2dc2463ad607fc712c6eeee19 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Tue, 9 Dec 2025 00:21:36 +0100 Subject: [PATCH 03/73] Remove old CCX test --- test/zx/test_zx_functionality.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 42c8d375f2..885c7b7c6e 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -219,16 +219,6 @@ TEST_F(ZXFunctionalityTest, Compound) { EXPECT_TRUE(diag.isIdentity()); } -// TEST_F(ZXFunctionalityTest, UnsupportedMultiControl) { -// using namespace qc::literals; -// qc = qc::QuantumComputation(4); -// qc.mcx({1, 2, 3}, 0); -// EXPECT_FALSE(FunctionalityConstruction::transformableToZX(&qc)); -// EXPECT_THROW(const ZXDiagram diag = -// FunctionalityConstruction::buildFunctionality(&qc), -// ZXException); -// } - TEST_F(ZXFunctionalityTest, CRZ) { using namespace qc::literals; qc = qc::QuantumComputation(2); From fb32afb53554821a08aded02e7739afb50765b71 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Tue, 9 Dec 2025 00:21:51 +0100 Subject: [PATCH 04/73] Add tests for CCZ, CRZ, MCRZ and MCZ --- src/zx/FunctionalityConstruction.cpp | 27 +++-- test/zx/test_zx_functionality.cpp | 165 ++++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 14 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index fc4f6c0335..67d4d77ad9 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -333,6 +333,7 @@ void FunctionalityConstruction::addCrz(ZXDiagram& diag, const PiExpression& phase, const Qubit control, const Qubit target, std::vector& qubits) { + // CRZ decomposition uses reversed CNOT direction addCnot(diag, target, control, qubits); addZSpider(diag, control, qubits, -phase / 2); addZSpider(diag, target, qubits, phase / 2); @@ -385,23 +386,25 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, if (qubits.size() > controls.size() + 1) { controls.push_back(target); - Qubit anc = -1; - auto it = std::ranges::find_if(qubits, [&](Vertex x) { - return std::ranges::find(controls, static_cast(x)) == - controls.end(); - }); - if (it == qubits.end()) { - throw ZXException("No ancilla qubit available for MCX decomposition"); - } else { - anc = static_cast(*it); + std::optional anc{}; + for (std::size_t q = 0; q < qubits.size(); ++q) { + const auto qb = static_cast(q); + if (std::ranges::find(controls, qb) == controls.end()) { + anc = qb; + break; + } } + if (!anc.has_value()) { + throw ZXException("No ancilla qubit available for MCX decomposition"); + } + controls.pop_back(); - second.push_back(anc); + second.push_back(*anc); - addMcx(diag, first, anc, qubits); + addMcx(diag, first, *anc, qubits); addMcx(diag, second, target, qubits); - addMcx(diag, first, anc, qubits); + addMcx(diag, first, *anc, qubits); addMcx(diag, second, target, qubits); } else { addRx(diag, PiExpression(PiRational(1, 4)), target, qubits); diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 885c7b7c6e..ef8dd33fe2 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -244,13 +244,63 @@ TEST_F(ZXFunctionalityTest, CRZ) { EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); } +TEST_F(ZXFunctionalityTest, MultiCZ) { + using namespace qc::literals; + qc = qc::QuantumComputation(3); + qc.mcz({1, 2}, 0); + + auto qcPrime = qc::QuantumComputation(3); + qcPrime.h(0); + qcPrime.mcx({1, 2}, 0); + qcPrime.h(0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} +TEST_F(ZXFunctionalityTest, CCZ) { + using namespace qc::literals; + const std::string testfile = "OPENQASM 2.0;" + "include \"qelib1.inc\";" + "qreg q[3];" + "ccz q[0],q[1],q[2];\n"; + + qc = qasm3::Importer::imports(testfile); + auto qcPrime = qc::QuantumComputation(3); + qcPrime.h(0); + qcPrime.mcx({1, 2}, 0); + qcPrime.h(0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + TEST_F(ZXFunctionalityTest, MultiControlX) { using namespace qc::literals; qc = qc::QuantumComputation(4); qc.mcx({1, 2, 3}, 0); auto qcPrime = qc::QuantumComputation(4); - qcPrime.mcx({1, 2, 3}, 0); + qcPrime.h(0); + qcPrime.mcz({1, 2, 3}, 0); + qcPrime.h(0); auto d = FunctionalityConstruction::buildFunctionality(&qc); @@ -333,7 +383,75 @@ TEST_F(ZXFunctionalityTest, MultiControlX1) { EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); } -TEST_F(ZXFunctionalityTest, MultiCRZ) { +TEST_F(ZXFunctionalityTest, MultiControlZ) { + using namespace qc::literals; + qc = qc::QuantumComputation(4); + qc.mcz({1, 2, 3}, 0); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.h(0); + qcPrime.mcx({1, 2, 3}, 0); + qcPrime.h(0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); +} + +TEST_F(ZXFunctionalityTest, MultiControlZ0) { + using namespace qc::literals; + qc = qc::QuantumComputation(1); + qc.mcz({}, 0); + + auto qcPrime = qc::QuantumComputation(1); + qcPrime.z(0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + +TEST_F(ZXFunctionalityTest, MultiControlZ1) { + using namespace qc::literals; + qc = qc::QuantumComputation(2); + qc.mcz({1}, 0); + + auto qcPrime = qc::QuantumComputation(2); + qcPrime.cz(1, 0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + + +TEST_F(ZXFunctionalityTest, MultiControlRZ) { using namespace qc::literals; qc = qc::QuantumComputation(3); qc.mcrz(PI / 4, {1, 2}, 0); @@ -348,6 +466,49 @@ TEST_F(ZXFunctionalityTest, MultiCRZ) { EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); } +TEST_F(ZXFunctionalityTest, MultiControlRZ0) { + using namespace qc::literals; + qc = qc::QuantumComputation(1); + qc.mcrz(PI / 4, {}, 0); + + auto qcPrime = qc::QuantumComputation(1); + qcPrime.rz(PI / 4, 0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + +TEST_F(ZXFunctionalityTest, MultiControlRZ1) { + using namespace qc::literals; + qc = qc::QuantumComputation(2); + qc.mcrz(PI / 4, {1}, 0); + + auto qcPrime = qc::QuantumComputation(2); + qcPrime.crz(PI / 4, 1, 0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); +} + + TEST_F(ZXFunctionalityTest, UnsupportedControl) { using namespace qc::literals; qc = qc::QuantumComputation(2); From 2375b7405143ef0e4a726456b8e2df40f6d0e411 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Tue, 9 Dec 2025 00:56:56 +0100 Subject: [PATCH 05/73] Rename control and target for addCrz --- src/zx/FunctionalityConstruction.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 67d4d77ad9..1f94779190 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -331,13 +331,13 @@ void FunctionalityConstruction::addCcz(ZXDiagram& diag, const Qubit ctrl0, void FunctionalityConstruction::addCrz(ZXDiagram& diag, const PiExpression& phase, - const Qubit control, const Qubit target, + const Qubit ctrl, const Qubit target, std::vector& qubits) { // CRZ decomposition uses reversed CNOT direction - addCnot(diag, target, control, qubits); - addZSpider(diag, control, qubits, -phase / 2); + addCnot(diag, target, ctrl, qubits); + addZSpider(diag, ctrl, qubits, -phase / 2); addZSpider(diag, target, qubits, phase / 2); - addCnot(diag, target, control, qubits); + addCnot(diag, target, ctrl, qubits); } void FunctionalityConstruction::addMcrz(ZXDiagram& diag, @@ -396,7 +396,7 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, } if (!anc.has_value()) { throw ZXException("No ancilla qubit available for MCX decomposition"); - } + } controls.pop_back(); second.push_back(*anc); From 275e8329d828dd7e2b1d1ea7ff10f1fd0b71ffe9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:59:53 +0000 Subject: [PATCH 06/73] =?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 --- test/zx/test_zx_functionality.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index ef8dd33fe2..19ed7bc255 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -415,7 +415,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ0) { auto qcPrime = qc::QuantumComputation(1); qcPrime.z(0); - + auto d = FunctionalityConstruction::buildFunctionality(&qc); auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); @@ -436,7 +436,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ1) { auto qcPrime = qc::QuantumComputation(2); qcPrime.cz(1, 0); - + auto d = FunctionalityConstruction::buildFunctionality(&qc); auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); @@ -450,7 +450,6 @@ TEST_F(ZXFunctionalityTest, MultiControlZ1) { EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); } - TEST_F(ZXFunctionalityTest, MultiControlRZ) { using namespace qc::literals; qc = qc::QuantumComputation(3); @@ -470,7 +469,7 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ0) { using namespace qc::literals; qc = qc::QuantumComputation(1); qc.mcrz(PI / 4, {}, 0); - + auto qcPrime = qc::QuantumComputation(1); qcPrime.rz(PI / 4, 0); @@ -508,7 +507,6 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ1) { EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); } - TEST_F(ZXFunctionalityTest, UnsupportedControl) { using namespace qc::literals; qc = qc::QuantumComputation(2); From c873318e3c9a05694e77bb1081011a12aaf9e04b Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 16:52:26 +0100 Subject: [PATCH 07/73] :sparkles: Qiskit to IQM JSON converter --- python/mqt/core/plugins/qiskit/__init__.py | 2 + python/mqt/core/plugins/qiskit/converters.py | 166 +++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 python/mqt/core/plugins/qiskit/converters.py diff --git a/python/mqt/core/plugins/qiskit/__init__.py b/python/mqt/core/plugins/qiskit/__init__.py index fda0e4729f..4d9bbb32cf 100644 --- a/python/mqt/core/plugins/qiskit/__init__.py +++ b/python/mqt/core/plugins/qiskit/__init__.py @@ -26,6 +26,7 @@ if TYPE_CHECKING or HAS_QISKIT: from .backend import QDMIBackend + from .converters import qiskit_to_iqm_json from .exceptions import ( CircuitValidationError, JobSubmissionError, @@ -50,5 +51,6 @@ "UnsupportedFormatError", "UnsupportedOperationError", "mqt_to_qiskit", + "qiskit_to_iqm_json", "qiskit_to_mqt", ] diff --git a/python/mqt/core/plugins/qiskit/converters.py b/python/mqt/core/plugins/qiskit/converters.py new file mode 100644 index 0000000000..1c1bd3424d --- /dev/null +++ b/python/mqt/core/plugins/qiskit/converters.py @@ -0,0 +1,166 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Circuit converters for various program formats. + +This module provides conversion functions from Qiskit QuantumCircuit +to various device-specific program formats. +""" + +from __future__ import annotations + +import json +from typing import TYPE_CHECKING, Any + +import numpy as np +from qiskit.circuit.library import Barrier, CZGate, Measure, RGate + +from .exceptions import TranslationError, UnsupportedOperationError + +if TYPE_CHECKING: + from qiskit.circuit import QuantumCircuit + + from ... import fomac + +__all__ = ["qiskit_to_iqm_json"] + + +def __dir__() -> list[str]: + return __all__ + + +def qiskit_to_iqm_json(circuit: QuantumCircuit, device: fomac.Device) -> str: + """Convert a Qiskit :class:`~qiskit.circuit.QuantumCircuit` to IQM JSON format. + + The IQM JSON format is a device-specific format that encodes quantum operations + as JSON objects with site names, operation names, and arguments. + + Note: + The conversion currently supports only operations that are natively + supported by the IQM hardware. Unsupported operations will raise + :class:`UnsupportedOperationError`. + + Args: + circuit: The Qiskit quantum circuit to convert. + device: The FoMaC device providing site mapping and metadata. + + Returns: + JSON string representation of the circuit in IQM format. + + Raises: + UnsupportedOperationError: If the circuit contains operations not supported + by IQM hardware. + TranslationError: If the conversion process fails. + + Examples: + >>> from qiskit import QuantumCircuit + >>> from mqt.core.plugins.qiskit.converters import qiskit_to_iqm_json + >>> qc = QuantumCircuit(2, 2) + >>> qc.h(0) + >>> qc.cx(0, 1) + >>> qc.measure_all() + >>> json_str = qiskit_to_iqm_json(qc, device) + """ + + def _raise_unsupported_operation(operation_name: str) -> None: + """Helper to raise UnsupportedOperationError (satisfies TRY301). + + Args: + operation_name: Name of the unsupported operation. + + Raises: + UnsupportedOperationError: Always raised for unsupported operations. + """ + msg = f"Operation '{operation_name}' is not supported in IQM JSON format" + raise UnsupportedOperationError(msg) + + try: + sites = device.sites() + instructions: list[dict[str, Any]] = [] + + for instruction in circuit.data: + operation, qargs, cargs = instruction.operation, instruction.qubits, instruction.clbits + + # R gate (PRX in IQM terminology) + if isinstance(operation, RGate): + angle_t = float(operation.params[0] / (2 * np.pi)) + phase_t = float(operation.params[1] / (2 * np.pi)) + qubit_loc = circuit.find_bit(qargs[0]) + qubit_index = qubit_loc.registers[0][1] + instructions.append({ + "name": "prx", + "qubits": [sites[qubit_index].name()], + "args": { + "angle_t": angle_t, + "phase_t": phase_t, + }, + }) + + # CZ gate + elif isinstance(operation, CZGate): + qubit_loc1 = circuit.find_bit(qargs[0]) + qubit_index1 = qubit_loc1.registers[0][1] + qubit_loc2 = circuit.find_bit(qargs[1]) + qubit_index2 = qubit_loc2.registers[0][1] + instructions.append({ + "name": "cz", + "qubits": [ + sites[qubit_index1].name(), + sites[qubit_index2].name(), + ], + "args": {}, + }) + + # Barrier + elif isinstance(operation, Barrier): + qubit_indices: list[int] = [] + for qubit in qargs: + qubit_loc = circuit.find_bit(qubit) + qubit_index = qubit_loc.registers[0][1] + qubit_indices.append(qubit_index) + instructions.append({ + "name": "barrier", + "qubits": [sites[i].name() for i in qubit_indices], + "args": {}, + }) + + # Measure + elif isinstance(operation, Measure): + clbit = cargs[0] + bitloc = circuit.find_bit(clbit) + creg = bitloc.registers[0][0] + creg_idx = circuit.cregs.index(creg) + clbit_index = bitloc.registers[0][1] + key = f"{creg.name}_{len(creg)}_{creg_idx}_{clbit_index}" + qubit_loc = circuit.find_bit(qargs[0]) + qubit_index = qubit_loc.registers[0][1] + instructions.append({ + "name": "measure", + "qubits": [sites[qubit_index].name()], + "args": { + "key": key, + }, + }) + + # Unsupported operation + else: + _raise_unsupported_operation(operation.name) + + program: dict[str, Any] = { + "name": circuit.name or "circuit", + "metadata": {}, + "instructions": instructions, + } + + return json.dumps(program) + + except UnsupportedOperationError: + raise + except Exception as exc: + msg = f"Failed to convert circuit to IQM JSON: {exc}" + raise TranslationError(msg) from exc From 068560ccc9d1c0124583bc602a49a467a37191f4 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 16:53:25 +0100 Subject: [PATCH 08/73] :white_check_mark: Test Qiskit to IQM JSON converter --- test/python/plugins/qiskit/conftest.py | 4 + test/python/plugins/qiskit/test_converters.py | 152 ++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 test/python/plugins/qiskit/test_converters.py diff --git a/test/python/plugins/qiskit/conftest.py b/test/python/plugins/qiskit/conftest.py index ae745923e3..960e3507cd 100644 --- a/test/python/plugins/qiskit/conftest.py +++ b/test/python/plugins/qiskit/conftest.py @@ -70,6 +70,10 @@ def index(self) -> int: """Return site index.""" return self._index + def name(self) -> str: + """Return site name.""" + return f"site_{self._index}" + @staticmethod def is_zone() -> bool: """Return whether site is a zone (always False for mock sites).""" diff --git a/test/python/plugins/qiskit/test_converters.py b/test/python/plugins/qiskit/test_converters.py new file mode 100644 index 0000000000..2642a1ab24 --- /dev/null +++ b/test/python/plugins/qiskit/test_converters.py @@ -0,0 +1,152 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Tests for circuit format converters.""" + +from __future__ import annotations + +import json +from typing import TYPE_CHECKING + +import numpy as np +import pytest +from qiskit import QuantumCircuit + +from mqt.core.plugins.qiskit import UnsupportedOperationError, qiskit_to_iqm_json + +if TYPE_CHECKING: + from test.python.plugins.qiskit.conftest import MockQDMIDevice + + +def test_qiskit_to_iqm_json_simple_circuit(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test conversion of a simple circuit to IQM JSON.""" + device = mock_qdmi_device_factory( + name="IQM Device", + num_qubits=2, + operations=["r", "cz", "measure", "barrier"], + ) + + qc = QuantumCircuit(2, 2) + qc.r(1.5708, 0.0, 0) + qc.cz(0, 1) + qc.measure_all() + + json_str = qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + program = json.loads(json_str) + + assert "name" in program + assert "metadata" in program + assert "instructions" in program + assert isinstance(program["instructions"], list) + assert len(program["instructions"]) == 5 # r, cz, barrier, measure, measure + instr_names = [instr["name"] for instr in program["instructions"]] + assert instr_names == ["prx", "cz", "barrier", "measure", "measure"] + + +def test_qiskit_to_iqm_json_prx_parameters(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that R gates are converted to PRX with correct parameters.""" + device = mock_qdmi_device_factory(num_qubits=1, operations=["r", "measure"]) + + angle = np.pi / 2 + phase = np.pi / 4 + qc = QuantumCircuit(1, 1) + qc.r(angle, phase, 0) + qc.measure_all() + + json_str = qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + program = json.loads(json_str) + + prx_instr = program["instructions"][0] + assert prx_instr["name"] == "prx" + assert "args" in prx_instr + assert "angle_t" in prx_instr["args"] + assert "phase_t" in prx_instr["args"] + + expected_angle_t = angle / (2 * np.pi) + expected_phase_t = phase / (2 * np.pi) + assert abs(prx_instr["args"]["angle_t"] - expected_angle_t) < 1e-10 + assert abs(prx_instr["args"]["phase_t"] - expected_phase_t) < 1e-10 + + +def test_qiskit_to_iqm_json_barrier(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that barriers are correctly converted.""" + device = mock_qdmi_device_factory(num_qubits=3, operations=["barrier"]) + + qc = QuantumCircuit(3) + qc.barrier([0, 1, 2]) + + json_str = qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + program = json.loads(json_str) + + barrier_instr = program["instructions"][0] + assert barrier_instr["name"] == "barrier" + assert len(barrier_instr["qubits"]) == 3 + assert barrier_instr["args"] == {} + + +def test_qiskit_to_iqm_json_cz_gate(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that CZ gates are correctly converted.""" + device = mock_qdmi_device_factory(num_qubits=2, operations=["cz"]) + + qc = QuantumCircuit(2) + qc.cz(0, 1) + + json_str = qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + program = json.loads(json_str) + + cz_instr = program["instructions"][0] + assert cz_instr["name"] == "cz" + assert len(cz_instr["qubits"]) == 2 + assert cz_instr["args"] == {} + + +def test_qiskit_to_iqm_json_measure_keys(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that measurements generate correct keys.""" + device = mock_qdmi_device_factory(num_qubits=2, operations=["measure"]) + + qc = QuantumCircuit(2, 2) + qc.measure_all() + + json_str = qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + program = json.loads(json_str) + + barr = program["instructions"][0] + meas0 = program["instructions"][1] + meas1 = program["instructions"][2] + + assert barr["name"] == "barrier" + assert barr["args"] == {} + assert meas0["name"] == "measure" + assert "key" in meas0["args"] + assert meas1["name"] == "measure" + assert "key" in meas1["args"] + assert meas0["args"]["key"] != meas1["args"]["key"] + + +def test_qiskit_to_iqm_json_unsupported_operation(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that unsupported operations raise UnsupportedOperationError.""" + device = mock_qdmi_device_factory(num_qubits=1, operations=[]) + + qc = QuantumCircuit(1) + qc.h(0) + + with pytest.raises(UnsupportedOperationError, match="not supported in IQM JSON format"): + qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + + +def test_qiskit_to_iqm_json_circuit_name(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that circuit name is preserved in IQM JSON.""" + device = mock_qdmi_device_factory(num_qubits=1, operations=["measure"]) + + qc = QuantumCircuit(1, 1, name="my_test_circuit") + qc.measure_all() + + json_str = qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + program = json.loads(json_str) + + assert program["name"] == "my_test_circuit" From 53032bb8f02810a11f5298a1f8501ca1df315d46 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 16:56:14 +0100 Subject: [PATCH 09/73] :sparkles: Support IQM JSON conversion in `QDMIBackend` --- python/mqt/core/plugins/qiskit/backend.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/python/mqt/core/plugins/qiskit/backend.py b/python/mqt/core/plugins/qiskit/backend.py index e78d12da91..1d59b40f8e 100644 --- a/python/mqt/core/plugins/qiskit/backend.py +++ b/python/mqt/core/plugins/qiskit/backend.py @@ -24,6 +24,7 @@ from qiskit.transpiler import InstructionProperties, Target from ... import fomac +from .converters import qiskit_to_iqm_json from .exceptions import ( CircuitValidationError, JobSubmissionError, @@ -373,20 +374,22 @@ def _get_operation_qargs(self, op: fomac.Device.Operation) -> list[tuple[int]] | # Operation has unspecified qubit count or 3+ qubits -> assume it applies to all qubits return [None] - @staticmethod def _convert_circuit( - circuit: QuantumCircuit, supported_program_formats: Iterable[fomac.ProgramFormat] + self, circuit: QuantumCircuit, supported_program_formats: Iterable[fomac.ProgramFormat] ) -> tuple[str, fomac.ProgramFormat]: """Convert a :class:`~qiskit.circuit.QuantumCircuit` to one of the supported program formats. - OpenQASM 3 takes precedence over OpenQASM 2 since it is a superset of the latter. + The conversion priority order is: + 1. IQM JSON (if supported) - device-specific format + 2. OpenQASM 3 (if supported) - superset of QASM 2 + 3. OpenQASM 2 (if supported) - legacy format Args: circuit: The quantum circuit to convert. supported_program_formats: Supported program formats. Returns: - String representation of the circuit in the specified format. + Tuple of (program string, program format). Raises: UnsupportedFormatError: If no supported program formats are found. @@ -396,6 +399,15 @@ def _convert_circuit( msg = "No supported program formats found" raise UnsupportedFormatError(msg) + # Try IQM JSON format first (device-specific) + if fomac.ProgramFormat.IQM_JSON in supported_program_formats: + try: + return qiskit_to_iqm_json(circuit, self._device), fomac.ProgramFormat.IQM_JSON + except Exception as exc: + msg = f"Failed to convert circuit to IQM JSON: {exc}" + raise TranslationError(msg) from exc + + # Try OpenQASM3 if fomac.ProgramFormat.QASM3 in supported_program_formats: try: return str(qasm3.dumps(circuit)), fomac.ProgramFormat.QASM3 @@ -403,6 +415,7 @@ def _convert_circuit( msg = f"Failed to convert circuit to QASM3: {exc}" raise TranslationError(msg) from exc + # Try OpenQASM2 (legacy) if fomac.ProgramFormat.QASM2 in supported_program_formats: try: return str(qasm2.dumps(circuit)), fomac.ProgramFormat.QASM2 From 5c68a2fb98de3dc38e8725247e121ad00daa43f3 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 16:56:27 +0100 Subject: [PATCH 10/73] :white_check_mark: Tests for new backend functionality --- test/python/plugins/qiskit/test_backend.py | 99 +++++++++++++++++++--- 1 file changed, 87 insertions(+), 12 deletions(-) diff --git a/test/python/plugins/qiskit/test_backend.py b/test/python/plugins/qiskit/test_backend.py index 078299ec89..e4ffcaf0c2 100644 --- a/test/python/plugins/qiskit/test_backend.py +++ b/test/python/plugins/qiskit/test_backend.py @@ -355,24 +355,30 @@ def test_backend_with_site_specific_properties( assert abs(props.error - expected_error) < 1e-10 -def test_backend_qasm_conversion_no_supported_formats() -> None: +def test_backend_qasm_conversion_no_supported_formats(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: """Backend should raise UnsupportedFormatError when no supported program formats exist.""" qc = QuantumCircuit(2, 2) qc.cz(0, 1) qc.measure_all() + device = mock_qdmi_device_factory(num_qubits=2, operations=["cz", "measure"]) + backend = QDMIBackend(device) # type: ignore[arg-type] + with pytest.raises(UnsupportedFormatError, match="No supported program formats found"): - QDMIBackend._convert_circuit(qc, []) # noqa: SLF001 + backend._convert_circuit(qc, []) # noqa: SLF001 -def test_backend_qasm3_conversion_success() -> None: +def test_backend_qasm3_conversion_success(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: """Backend should successfully convert circuit to QASM3.""" qc = QuantumCircuit(2, 2) qc.h(0) qc.cx(0, 1) qc.measure_all() - program, fmt = QDMIBackend._convert_circuit(qc, [fomac.ProgramFormat.QASM3]) # noqa: SLF001 + device = mock_qdmi_device_factory(num_qubits=2, operations=["h", "cx", "measure"]) + backend = QDMIBackend(device) # type: ignore[arg-type] + + program, fmt = backend._convert_circuit(qc, [fomac.ProgramFormat.QASM3]) # noqa: SLF001 assert fmt == fomac.ProgramFormat.QASM3 assert "OPENQASM 3" in program @@ -380,34 +386,96 @@ def test_backend_qasm3_conversion_success() -> None: assert "cx q[0], q[1]" in program -def test_backend_qasm2_conversion_success() -> None: +def test_backend_qasm2_conversion_success(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: """Backend should successfully convert circuit to QASM2.""" qc = QuantumCircuit(2, 2) qc.h(0) qc.cx(0, 1) qc.measure_all() - program, fmt = QDMIBackend._convert_circuit(qc, [fomac.ProgramFormat.QASM2]) # noqa: SLF001 + device = mock_qdmi_device_factory(num_qubits=2, operations=["h", "cx", "measure"]) + backend = QDMIBackend(device) # type: ignore[arg-type] + + program, fmt = backend._convert_circuit(qc, [fomac.ProgramFormat.QASM2]) # noqa: SLF001 assert fmt == fomac.ProgramFormat.QASM2 - assert "OPENQASM 2" in program + assert "OPENQASM 2.0" in program assert "h q[0]" in program assert "cx q[0],q[1]" in program -def test_backend_qasm3_preferred_over_qasm2() -> None: +def test_backend_qasm3_preferred_over_qasm2(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: """Backend should prefer QASM3 over QASM2 when both are available.""" qc = QuantumCircuit(2, 2) qc.h(0) qc.measure_all() + device = mock_qdmi_device_factory(num_qubits=2, operations=["h", "measure"]) + backend = QDMIBackend(device) # type: ignore[arg-type] + # When both formats are available, QASM3 should be chosen - program, fmt = QDMIBackend._convert_circuit(qc, [fomac.ProgramFormat.QASM2, fomac.ProgramFormat.QASM3]) # noqa: SLF001 + program, fmt = backend._convert_circuit(qc, [fomac.ProgramFormat.QASM2, fomac.ProgramFormat.QASM3]) # noqa: SLF001 assert fmt == fomac.ProgramFormat.QASM3 assert "OPENQASM 3" in program +def test_backend_uses_iqm_json_when_supported(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that backend uses IQM JSON format when supported.""" + device = mock_qdmi_device_factory(num_qubits=2, operations=["r", "cz", "measure"]) + + submitted_format: fomac.ProgramFormat | None = None + + def mock_supported_formats() -> list[fomac.ProgramFormat]: + return [fomac.ProgramFormat.IQM_JSON, fomac.ProgramFormat.QASM3] + + def mock_submit_job(program: str, program_format: fomac.ProgramFormat, num_shots: int) -> MockQDMIDevice.MockJob: # noqa: ARG001 + nonlocal submitted_format + submitted_format = program_format + return device.MockJob(num_clbits=2, shots=num_shots) + + device.supported_program_formats = mock_supported_formats # type: ignore[method-assign] + device.submit_job = mock_submit_job # type: ignore[method-assign] + + backend = QDMIBackend(device) # type: ignore[arg-type] + qc = QuantumCircuit(2, 2) + qc.r(1.5708, 0.0, 0) + qc.cz(0, 1) + qc.measure_all() + + backend.run(qc, shots=100) + + assert submitted_format == fomac.ProgramFormat.IQM_JSON + + +def test_backend_iqm_json_preferred_over_qasm(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that IQM JSON takes priority over QASM formats.""" + device = mock_qdmi_device_factory(num_qubits=2, operations=["r", "cz", "measure"]) + + submitted_format: fomac.ProgramFormat | None = None + + def mock_supported_formats() -> list[fomac.ProgramFormat]: + return [fomac.ProgramFormat.QASM2, fomac.ProgramFormat.QASM3, fomac.ProgramFormat.IQM_JSON] + + def mock_submit_job(program: str, program_format: fomac.ProgramFormat, num_shots: int) -> MockQDMIDevice.MockJob: # noqa: ARG001 + nonlocal submitted_format + submitted_format = program_format + return device.MockJob(num_clbits=2, shots=num_shots) + + device.supported_program_formats = mock_supported_formats # type: ignore[method-assign] + device.submit_job = mock_submit_job # type: ignore[method-assign] + + backend = QDMIBackend(device) # type: ignore[arg-type] + qc = QuantumCircuit(2, 2) + qc.r(1.5708, 0.0, 0) + qc.cz(0, 1) + qc.measure_all() + + backend.run(qc, shots=100) + + assert submitted_format == fomac.ProgramFormat.IQM_JSON + + @pytest.mark.parametrize( ("qasm_module_name", "program_format"), [ @@ -419,6 +487,7 @@ def test_backend_qasm_conversion_failure( monkeypatch: pytest.MonkeyPatch, qasm_module_name: str, program_format: fomac.ProgramFormat, + mock_qdmi_device_factory: type[MockQDMIDevice], ) -> None: """Backend should raise TranslationError when QASM conversion fails.""" qasm_module = qasm3 if qasm_module_name == "qasm3" else qasm2 @@ -434,21 +503,27 @@ def failing_dumps(circuit: object) -> NoReturn: # noqa: ARG001 qc.cz(0, 1) qc.measure_all() + device = mock_qdmi_device_factory(num_qubits=2, operations=["cz", "measure"]) + backend = QDMIBackend(device) # type: ignore[arg-type] + with pytest.raises(TranslationError, match=f"Failed to convert circuit to {qasm_module_name.upper()}"): - QDMIBackend._convert_circuit(qc, [program_format]) # noqa: SLF001 + backend._convert_circuit(qc, [program_format]) # noqa: SLF001 -def test_backend_unsupported_format_error() -> None: +def test_backend_unsupported_format_error(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: """Backend should raise UnsupportedFormatError when only unsupported formats available.""" qc = QuantumCircuit(2, 2) qc.cz(0, 1) qc.measure_all() + device = mock_qdmi_device_factory(num_qubits=2, operations=["cz", "measure"]) + backend = QDMIBackend(device) # type: ignore[arg-type] + # Test with QPY format which is not supported for conversion from Qiskit with pytest.raises( UnsupportedFormatError, match="No conversion from Qiskit to any of the supported program formats" ): - QDMIBackend._convert_circuit(qc, [fomac.ProgramFormat.QPY]) # noqa: SLF001 + backend._convert_circuit(qc, [fomac.ProgramFormat.QPY]) # noqa: SLF001 def test_backend_supports_cz_gate(backend_with_mock_jobs: QDMIBackend) -> None: From 3f46cd3f4f39fed83f6395b1ecc117460cbcae28 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 23:46:05 +0100 Subject: [PATCH 11/73] :art: Apply suggestions from GitHub Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Marcel Walter --- python/mqt/core/plugins/qiskit/backend.py | 3 +++ python/mqt/core/plugins/qiskit/converters.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/python/mqt/core/plugins/qiskit/backend.py b/python/mqt/core/plugins/qiskit/backend.py index 1d59b40f8e..f137e7e22e 100644 --- a/python/mqt/core/plugins/qiskit/backend.py +++ b/python/mqt/core/plugins/qiskit/backend.py @@ -403,6 +403,9 @@ def _convert_circuit( if fomac.ProgramFormat.IQM_JSON in supported_program_formats: try: return qiskit_to_iqm_json(circuit, self._device), fomac.ProgramFormat.IQM_JSON + except UnsupportedOperationError: + # Let this propagate so caller can handle fallback + raise except Exception as exc: msg = f"Failed to convert circuit to IQM JSON: {exc}" raise TranslationError(msg) from exc diff --git a/python/mqt/core/plugins/qiskit/converters.py b/python/mqt/core/plugins/qiskit/converters.py index 1c1bd3424d..2aebe00ec5 100644 --- a/python/mqt/core/plugins/qiskit/converters.py +++ b/python/mqt/core/plugins/qiskit/converters.py @@ -59,10 +59,11 @@ def qiskit_to_iqm_json(circuit: QuantumCircuit, device: fomac.Device) -> str: Examples: >>> from qiskit import QuantumCircuit + >>> import numpy as np >>> from mqt.core.plugins.qiskit.converters import qiskit_to_iqm_json >>> qc = QuantumCircuit(2, 2) - >>> qc.h(0) - >>> qc.cx(0, 1) + >>> qc.r(np.pi / 2, 0) + >>> qc.cz(0, 1) >>> qc.measure_all() >>> json_str = qiskit_to_iqm_json(qc, device) """ From 33b48a7b0979f236c8a4e8c20e984235ca965685 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 23:49:39 +0100 Subject: [PATCH 12/73] :art: Apply `pre-commit` --- python/mqt/core/plugins/qiskit/backend.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/mqt/core/plugins/qiskit/backend.py b/python/mqt/core/plugins/qiskit/backend.py index f137e7e22e..51cf81dd60 100644 --- a/python/mqt/core/plugins/qiskit/backend.py +++ b/python/mqt/core/plugins/qiskit/backend.py @@ -393,6 +393,7 @@ def _convert_circuit( Raises: UnsupportedFormatError: If no supported program formats are found. + UnsupportedOperationError: If the circuit contains operations not supported by IQM JSON. TranslationError: If conversion fails. """ if not supported_program_formats: From 453e00a882274a18fabe84f44541e45669da93da Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 23:51:34 +0100 Subject: [PATCH 13/73] :memo: Add to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f80b289c69..032c542e87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add IQM JSON support for job submission in Qiskit-QDMI Backend ([#1375]) ([**@marcelwa**]) - ✨ Add authentication support for QDMI sessions with token, username/password, auth file, auth URL, and project ID parameters ([#1355]) ([**@marcelwa**]) - ✨ Add a new QDMI device that represents a superconducting architecture featuring a coupling map ([#1328]) ([**@ystade**]) - ✨ Add bi-directional iterator that traverses the def-use chain of a qubit value ([#1310]) ([**@MatthiasReumann**]) @@ -278,6 +279,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1375]: https://github.com/munich-quantum-toolkit/core/pull/1375 [#1371]: https://github.com/munich-quantum-toolkit/core/pull/1371 [#1359]: https://github.com/munich-quantum-toolkit/core/pull/1359 [#1355]: https://github.com/munich-quantum-toolkit/core/pull/1355 From b81e3e31450aff9322560c20ed1341fe7751dc14 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Mon, 8 Dec 2025 00:07:11 +0100 Subject: [PATCH 14/73] :memo: Adjust documentation --- docs/qdmi/qdmi_backend.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/qdmi/qdmi_backend.md b/docs/qdmi/qdmi_backend.md index e6d0517e8c..fad26157ab 100644 --- a/docs/qdmi/qdmi_backend.md +++ b/docs/qdmi/qdmi_backend.md @@ -364,7 +364,7 @@ except JobSubmissionError as e: # Failed to submit job to device print(f"Job submission failed: {e}") except TranslationError as e: - # Failed to convert circuit to QASM + # Failed to convert circuit to supported program format print(f"Translation error: {e}") except UnsupportedFormatError as e: # No supported program format available @@ -378,9 +378,9 @@ except UnsupportedFormatError as e: When you run a circuit, the backend: 1. Validates the circuit (checks for unbound parameters, supported operations, valid options) -2. Converts the circuit to QASM (QASM3 by default, QASM2 optionally) -3. Submits the QASM program to the FoMaC (QDMI) device via `device.submit_job()` -4. Returns a {py:class}`~mqt.core.plugins.qiskit.QDMIJob` wrapping the FoMaC (QDMI) job +2. Converts the circuit to one of the program formats supported by the target device (IQM JSON, OpenQASM 2, OpenQASM 3) using {py:func}`~mqt.core.plugins.qiskit.convert_to_iqm_json` or Qiskit's built-in QASM exporters +3. Submits the program to the QDMI device via `device.submit_job()` +4. Returns a {py:class}`~mqt.core.plugins.qiskit.QDMIJob` ### Device Introspection From 2505f273d59c83aab75fea70f0692eac52c0f2a6 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Mon, 8 Dec 2025 00:16:35 +0100 Subject: [PATCH 15/73] :pencil2: Fix typo --- docs/qdmi/qdmi_backend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/qdmi/qdmi_backend.md b/docs/qdmi/qdmi_backend.md index fad26157ab..2d269db97a 100644 --- a/docs/qdmi/qdmi_backend.md +++ b/docs/qdmi/qdmi_backend.md @@ -378,7 +378,7 @@ except UnsupportedFormatError as e: When you run a circuit, the backend: 1. Validates the circuit (checks for unbound parameters, supported operations, valid options) -2. Converts the circuit to one of the program formats supported by the target device (IQM JSON, OpenQASM 2, OpenQASM 3) using {py:func}`~mqt.core.plugins.qiskit.convert_to_iqm_json` or Qiskit's built-in QASM exporters +2. Converts the circuit to one of the program formats supported by the target device (IQM JSON, OpenQASM 2, OpenQASM 3) using {py:func}`~mqt.core.plugins.qiskit.qiskit_to_iqm_json` or Qiskit's built-in QASM exporters 3. Submits the program to the QDMI device via `device.submit_job()` 4. Returns a {py:class}`~mqt.core.plugins.qiskit.QDMIJob` From 9ab82f378c611bcef1678e4277991097bdd16520 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Mon, 8 Dec 2025 00:48:29 +0100 Subject: [PATCH 16/73] :adhesive_bandage: Fix usage of `R` gate in docstring --- python/mqt/core/plugins/qiskit/converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mqt/core/plugins/qiskit/converters.py b/python/mqt/core/plugins/qiskit/converters.py index 2aebe00ec5..c50451a61e 100644 --- a/python/mqt/core/plugins/qiskit/converters.py +++ b/python/mqt/core/plugins/qiskit/converters.py @@ -62,7 +62,7 @@ def qiskit_to_iqm_json(circuit: QuantumCircuit, device: fomac.Device) -> str: >>> import numpy as np >>> from mqt.core.plugins.qiskit.converters import qiskit_to_iqm_json >>> qc = QuantumCircuit(2, 2) - >>> qc.r(np.pi / 2, 0) + >>> qc.r(np.pi / 2, 0, 0) >>> qc.cz(0, 1) >>> qc.measure_all() >>> json_str = qiskit_to_iqm_json(qc, device) From 06a40afabf2baae803477bbaaef8814e8af53f34 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Mon, 8 Dec 2025 00:52:00 +0100 Subject: [PATCH 17/73] :art: Apply CodeRabbit's suggestion --- python/mqt/core/plugins/qiskit/converters.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/python/mqt/core/plugins/qiskit/converters.py b/python/mqt/core/plugins/qiskit/converters.py index c50451a61e..512560c5fd 100644 --- a/python/mqt/core/plugins/qiskit/converters.py +++ b/python/mqt/core/plugins/qiskit/converters.py @@ -91,8 +91,7 @@ def _raise_unsupported_operation(operation_name: str) -> None: if isinstance(operation, RGate): angle_t = float(operation.params[0] / (2 * np.pi)) phase_t = float(operation.params[1] / (2 * np.pi)) - qubit_loc = circuit.find_bit(qargs[0]) - qubit_index = qubit_loc.registers[0][1] + qubit_index = circuit.find_bit(qargs[0]).index instructions.append({ "name": "prx", "qubits": [sites[qubit_index].name()], @@ -104,10 +103,8 @@ def _raise_unsupported_operation(operation_name: str) -> None: # CZ gate elif isinstance(operation, CZGate): - qubit_loc1 = circuit.find_bit(qargs[0]) - qubit_index1 = qubit_loc1.registers[0][1] - qubit_loc2 = circuit.find_bit(qargs[1]) - qubit_index2 = qubit_loc2.registers[0][1] + qubit_index1 = circuit.find_bit(qargs[0]).index + qubit_index2 = circuit.find_bit(qargs[1]).index instructions.append({ "name": "cz", "qubits": [ @@ -121,8 +118,7 @@ def _raise_unsupported_operation(operation_name: str) -> None: elif isinstance(operation, Barrier): qubit_indices: list[int] = [] for qubit in qargs: - qubit_loc = circuit.find_bit(qubit) - qubit_index = qubit_loc.registers[0][1] + qubit_index = circuit.find_bit(qubit).index qubit_indices.append(qubit_index) instructions.append({ "name": "barrier", @@ -138,8 +134,7 @@ def _raise_unsupported_operation(operation_name: str) -> None: creg_idx = circuit.cregs.index(creg) clbit_index = bitloc.registers[0][1] key = f"{creg.name}_{len(creg)}_{creg_idx}_{clbit_index}" - qubit_loc = circuit.find_bit(qargs[0]) - qubit_index = qubit_loc.registers[0][1] + qubit_index = circuit.find_bit(qargs[0]).index instructions.append({ "name": "measure", "qubits": [sites[qubit_index].name()], From 92ed95700819c55583c1d2e9bd48cb4c4f5fa4e3 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Mon, 8 Dec 2025 11:25:30 +0100 Subject: [PATCH 18/73] :art: Apply CodeRabbit's suggestion --- python/mqt/core/plugins/qiskit/converters.py | 35 +++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/python/mqt/core/plugins/qiskit/converters.py b/python/mqt/core/plugins/qiskit/converters.py index 512560c5fd..84da8691a7 100644 --- a/python/mqt/core/plugins/qiskit/converters.py +++ b/python/mqt/core/plugins/qiskit/converters.py @@ -68,19 +68,26 @@ def qiskit_to_iqm_json(circuit: QuantumCircuit, device: fomac.Device) -> str: >>> json_str = qiskit_to_iqm_json(qc, device) """ - def _raise_unsupported_operation(operation_name: str) -> None: - """Helper to raise UnsupportedOperationError (satisfies TRY301). + def _raise_error(exception_type: type[Exception], message: str) -> None: + """Helper to raise exceptions (satisfies TRY301). Args: - operation_name: Name of the unsupported operation. - - Raises: - UnsupportedOperationError: Always raised for unsupported operations. + exception_type: The type of exception to raise. + message: The error message. """ - msg = f"Operation '{operation_name}' is not supported in IQM JSON format" - raise UnsupportedOperationError(msg) + raise exception_type(message) try: + # Check for unbound parameters + if circuit.parameters: + param_names = ", ".join(sorted(p.name for p in circuit.parameters)) + msg = ( + f"Circuit contains unbound parameters: {param_names}. " + "All parameters must be bound to numeric values before conversion to IQM JSON. " + "Use circuit.assign_parameters() to bind parameters." + ) + _raise_error(UnsupportedOperationError, msg) + sites = device.sites() instructions: list[dict[str, Any]] = [] @@ -130,6 +137,15 @@ def _raise_unsupported_operation(operation_name: str) -> None: elif isinstance(operation, Measure): clbit = cargs[0] bitloc = circuit.find_bit(clbit) + + # Check if classical bit is part of a register + if not bitloc.registers: + msg = ( + "Measurement of unregistered classical bit is unsupported by IQM JSON export. " + "All classical bits must be part of a ClassicalRegister." + ) + _raise_error(TranslationError, msg) + creg = bitloc.registers[0][0] creg_idx = circuit.cregs.index(creg) clbit_index = bitloc.registers[0][1] @@ -145,7 +161,8 @@ def _raise_unsupported_operation(operation_name: str) -> None: # Unsupported operation else: - _raise_unsupported_operation(operation.name) + msg = f"Operation '{operation.name}' is not supported in IQM JSON format" + _raise_error(UnsupportedOperationError, msg) program: dict[str, Any] = { "name": circuit.name or "circuit", From 0911f7cfc998736b5df07b278a420139f34eb867 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Mon, 8 Dec 2025 11:25:43 +0100 Subject: [PATCH 19/73] :white_check_mark: Add tests to cover CodeRabbit's suggestions --- test/python/plugins/qiskit/test_converters.py | 103 +++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/test/python/plugins/qiskit/test_converters.py b/test/python/plugins/qiskit/test_converters.py index 2642a1ab24..e15479adff 100644 --- a/test/python/plugins/qiskit/test_converters.py +++ b/test/python/plugins/qiskit/test_converters.py @@ -16,8 +16,9 @@ import numpy as np import pytest from qiskit import QuantumCircuit +from qiskit.circuit import Clbit, Parameter -from mqt.core.plugins.qiskit import UnsupportedOperationError, qiskit_to_iqm_json +from mqt.core.plugins.qiskit import TranslationError, UnsupportedOperationError, qiskit_to_iqm_json if TYPE_CHECKING: from test.python.plugins.qiskit.conftest import MockQDMIDevice @@ -150,3 +151,103 @@ def test_qiskit_to_iqm_json_circuit_name(mock_qdmi_device_factory: type[MockQDMI program = json.loads(json_str) assert program["name"] == "my_test_circuit" + + +def test_qiskit_to_iqm_json_unbound_parameters(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that circuits with unbound parameters raise UnsupportedOperationError.""" + device = mock_qdmi_device_factory(num_qubits=2, operations=["r", "cz", "measure"]) + + # Create circuit with unbound parameters + theta = Parameter("theta") + phi = Parameter("phi") + qc = QuantumCircuit(2, 2) + qc.r(theta, phi, 0) + qc.cz(0, 1) + qc.measure_all() + + # Should raise UnsupportedOperationError with clear message + with pytest.raises(UnsupportedOperationError) as exc_info: + qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + + error_msg = str(exc_info.value) + assert "unbound parameters" in error_msg.lower() + assert "phi" in error_msg + assert "theta" in error_msg + assert "assign_parameters" in error_msg + + +def test_qiskit_to_iqm_json_bound_parameters(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that circuits with bound parameters work correctly.""" + device = mock_qdmi_device_factory(num_qubits=2, operations=["r", "cz", "measure"]) + + # Create circuit with parameters + theta = Parameter("theta") + phi = Parameter("phi") + qc = QuantumCircuit(2, 2) + qc.r(theta, phi, 0) + qc.cz(0, 1) + qc.measure_all() + + # Bind parameters + qc_bound = qc.assign_parameters({theta: np.pi / 2, phi: 0.0}) + + # Should convert successfully + json_str = qiskit_to_iqm_json(qc_bound, device) # type: ignore[arg-type] + program = json.loads(json_str) + + assert "instructions" in program + # r, cz, barrier (from measure_all), measure, measure + assert len(program["instructions"]) == 5 + + # Check PRX instruction has correct parameters + prx_instr = program["instructions"][0] + assert prx_instr["name"] == "prx" + expected_angle_t = (np.pi / 2) / (2 * np.pi) + expected_phase_t = 0.0 / (2 * np.pi) + assert abs(prx_instr["args"]["angle_t"] - expected_angle_t) < 1e-10 + assert abs(prx_instr["args"]["phase_t"] - expected_phase_t) < 1e-10 + + +def test_qiskit_to_iqm_json_unregistered_classical_bit(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that measurements to unregistered classical bits raise TranslationError.""" + device = mock_qdmi_device_factory(num_qubits=2, operations=["cz", "measure"]) + + # Create circuit with unregistered classical bit + qc = QuantumCircuit(2) + standalone_clbit = Clbit() + qc.add_bits([standalone_clbit]) + qc.cz(0, 1) + qc.measure(0, standalone_clbit) + + # Should raise TranslationError with clear message + with pytest.raises(TranslationError) as exc_info: + qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + + error_msg = str(exc_info.value) + assert "unregistered classical bit" in error_msg.lower() + assert "ClassicalRegister" in error_msg + + +def test_qiskit_to_iqm_json_registered_classical_bit(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Test that measurements to registered classical bits work correctly.""" + device = mock_qdmi_device_factory(num_qubits=2, operations=["cz", "measure"]) + + # Create circuit with registered classical bits (standard approach) + qc = QuantumCircuit(2, 2) + qc.cz(0, 1) + qc.measure(0, 0) + qc.measure(1, 1) + + # Should convert successfully + json_str = qiskit_to_iqm_json(qc, device) # type: ignore[arg-type] + program = json.loads(json_str) + + assert "instructions" in program + assert len(program["instructions"]) == 3 # cz, measure, measure + + # Check measurements have keys + measure_instrs = [instr for instr in program["instructions"] if instr["name"] == "measure"] + assert len(measure_instrs) == 2 + assert "key" in measure_instrs[0]["args"] + assert "key" in measure_instrs[1]["args"] + assert measure_instrs[0]["args"]["key"] != measure_instrs[1]["args"]["key"] From 3d74d1b68f3df1d4ca2ac4bcdee68718f661365f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:13:01 +0000 Subject: [PATCH 20/73] =?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 --- src/zx/FunctionalityConstruction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 1f94779190..a59c2fd892 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -397,7 +397,7 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, if (!anc.has_value()) { throw ZXException("No ancilla qubit available for MCX decomposition"); } - + controls.pop_back(); second.push_back(*anc); From ee9b27902b6b7d934f568355a81a6aacf5279862 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 15 Dec 2025 13:35:30 +0100 Subject: [PATCH 21/73] Rename variables in function definition of addCrz to remove linting warnings --- src/zx/FunctionalityConstruction.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index a59c2fd892..8de29aabd2 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -331,13 +331,13 @@ void FunctionalityConstruction::addCcz(ZXDiagram& diag, const Qubit ctrl0, void FunctionalityConstruction::addCrz(ZXDiagram& diag, const PiExpression& phase, - const Qubit ctrl, const Qubit target, + const Qubit q0, const Qubit q1, std::vector& qubits) { // CRZ decomposition uses reversed CNOT direction - addCnot(diag, target, ctrl, qubits); - addZSpider(diag, ctrl, qubits, -phase / 2); - addZSpider(diag, target, qubits, phase / 2); - addCnot(diag, target, ctrl, qubits); + addCnot(diag, q1, q0, qubits); + addZSpider(diag, q0, qubits, -phase / 2); + addZSpider(diag, q1, qubits, phase / 2); + addCnot(diag, q1, q0, qubits); } void FunctionalityConstruction::addMcrz(ZXDiagram& diag, From cbda9d6ac43cb06bd348b56fcd3998c058378fbe Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 15 Dec 2025 13:35:52 +0100 Subject: [PATCH 22/73] Remove unreachable branches in switch case in addMcrz --- src/zx/FunctionalityConstruction.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 8de29aabd2..1b6a87af46 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -346,14 +346,6 @@ void FunctionalityConstruction::addMcrz(ZXDiagram& diag, const Qubit target, std::vector& qubits) { - switch (controls.size()) { - case 0: - addZSpider(diag, target, qubits, phase); - return; - case 1: - addCrz(diag, phase, controls.front(), target, qubits); - return; - default: const Qubit nextControl = controls.back(); controls.pop_back(); @@ -361,7 +353,6 @@ void FunctionalityConstruction::addMcrz(ZXDiagram& diag, addMcx(diag, controls, target, qubits); addCrz(diag, -phase / 2, nextControl, target, qubits); addMcx(diag, controls, target, qubits); - } } void FunctionalityConstruction::addMcx(ZXDiagram& diag, From a8af5a70e757e23cf7911ab04e0c1307d4503f11 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 15 Dec 2025 13:36:11 +0100 Subject: [PATCH 23/73] Remove unreachable branches in switch cases for addMcz and format --- src/zx/FunctionalityConstruction.cpp | 38 +++++++++------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 1b6a87af46..7a5c16960c 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -346,13 +346,13 @@ void FunctionalityConstruction::addMcrz(ZXDiagram& diag, const Qubit target, std::vector& qubits) { - const Qubit nextControl = controls.back(); - controls.pop_back(); + const Qubit nextControl = controls.back(); + controls.pop_back(); - addCrz(diag, phase / 2, nextControl, target, qubits); - addMcx(diag, controls, target, qubits); - addCrz(diag, -phase / 2, nextControl, target, qubits); - addMcx(diag, controls, target, qubits); + addCrz(diag, phase / 2, nextControl, target, qubits); + addMcx(diag, controls, target, qubits); + addCrz(diag, -phase / 2, nextControl, target, qubits); + addMcx(diag, controls, target, qubits); } void FunctionalityConstruction::addMcx(ZXDiagram& diag, @@ -419,27 +419,13 @@ void FunctionalityConstruction::addMcz(ZXDiagram& diag, std::vector controls, const Qubit target, std::vector& qubits) { + const Qubit nextControl = controls.back(); + controls.pop_back(); - switch (controls.size()) { - case 0: - addZSpider(diag, target, qubits, PiExpression(PiRational(1, 1))); - return; - case 1: - addCrz(diag, PiExpression(PiRational(1, 1)), controls.front(), target, - qubits); - return; - case 2: - addCcz(diag, controls.front(), controls.back(), target, qubits); - return; - default: - const Qubit nextControl = controls.back(); - controls.pop_back(); - - addCrz(diag, PiExpression(PiRational(1, 2)), nextControl, target, qubits); - addMcx(diag, controls, target, qubits); - addCrz(diag, PiExpression(-PiRational(1, 2)), nextControl, target, qubits); - addMcx(diag, controls, target, qubits); - } + addCrz(diag, PiExpression(PiRational(1, 2)), nextControl, target, qubits); + addMcx(diag, controls, target, qubits); + addCrz(diag, PiExpression(-PiRational(1, 2)), nextControl, target, qubits); + addMcx(diag, controls, target, qubits); } FunctionalityConstruction::op_it From 8236fbf01a4044a01a91760423465256fc9f1fe2 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 15 Dec 2025 13:36:55 +0100 Subject: [PATCH 24/73] Use addMcz implementation --- src/zx/FunctionalityConstruction.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 7a5c16960c..8a716eb4cc 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -731,9 +731,7 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, addMcx(diag, controls, target, qubits); break; case qc::OpType::Z: - addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addMcx(diag, controls, target, qubits); - addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addMcz(diag, controls, target, qubits); break; case qc::OpType::RZ: addMcrz(diag, parseParam(op.get(), 0), controls, target, qubits); From 14e1955c131724e1b9ffe251d3e615cf501a6af7 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 15 Dec 2025 13:38:55 +0100 Subject: [PATCH 25/73] Add tests to increase codecov Add test for MultiControlZ with 3 controls. Add transformabletoZX tests for MCZ, MCX, MCrz --- test/zx/test_zx_functionality.cpp | 38 ++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 19ed7bc255..cc09f51334 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -239,6 +239,7 @@ TEST_F(ZXFunctionalityTest, CRZ) { fullReduce(d); + EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); @@ -298,10 +299,9 @@ TEST_F(ZXFunctionalityTest, MultiControlX) { qc.mcx({1, 2, 3}, 0); auto qcPrime = qc::QuantumComputation(4); - qcPrime.h(0); - qcPrime.mcz({1, 2, 3}, 0); - qcPrime.h(0); + qcPrime.mcx({1, 2, 3}, 0); + EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); auto d = FunctionalityConstruction::buildFunctionality(&qc); auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); @@ -326,6 +326,7 @@ TEST_F(ZXFunctionalityTest, MultiControlXLarger) { qcPrime.mcx({1, 2}, 4); qcPrime.mcx({3, 4}, 0); + EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); auto d = FunctionalityConstruction::buildFunctionality(&qc); auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); @@ -389,10 +390,9 @@ TEST_F(ZXFunctionalityTest, MultiControlZ) { qc.mcz({1, 2, 3}, 0); auto qcPrime = qc::QuantumComputation(4); - qcPrime.h(0); - qcPrime.mcx({1, 2, 3}, 0); - qcPrime.h(0); + qcPrime.mcz({1, 2, 3}, 0); + EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); auto d = FunctionalityConstruction::buildFunctionality(&qc); auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); @@ -450,12 +450,38 @@ TEST_F(ZXFunctionalityTest, MultiControlZ1) { EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); } +TEST_F(ZXFunctionalityTest, MultiControlZ2) { + using namespace qc::literals; + qc = qc::QuantumComputation(4); + qc.mcz({1, 2}, 0); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.h(0); + qcPrime.mcx({1, 2}, 0); + qcPrime.h(0); + + auto d = FunctionalityConstruction::buildFunctionality(&qc); + + auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + + d.concat(dPrime.invert()); + + fullReduce(d); + + EXPECT_TRUE(d.isIdentity()); + EXPECT_TRUE(d.globalPhaseIsZero()); + EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); +} + TEST_F(ZXFunctionalityTest, MultiControlRZ) { using namespace qc::literals; qc = qc::QuantumComputation(3); qc.mcrz(PI / 4, {1, 2}, 0); qc.mcrz(-PI / 4, {1, 2}, 0); + EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); auto d = FunctionalityConstruction::buildFunctionality(&qc); fullReduce(d); From 9de9ac92ad0d94ba73c88c42938d4b5f682057c1 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 15 Dec 2025 13:55:42 +0100 Subject: [PATCH 26/73] Add more connection assertions for tests --- test/zx/test_zx_functionality.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index cc09f51334..98b1249937 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -243,6 +243,7 @@ TEST_F(ZXFunctionalityTest, CRZ) { EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); } TEST_F(ZXFunctionalityTest, MultiCZ) { @@ -313,6 +314,9 @@ TEST_F(ZXFunctionalityTest, MultiControlX) { EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); + EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); } TEST_F(ZXFunctionalityTest, MultiControlXLarger) { @@ -406,6 +410,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ) { EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); + EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); } TEST_F(ZXFunctionalityTest, MultiControlZ0) { @@ -448,6 +453,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ1) { EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); } TEST_F(ZXFunctionalityTest, MultiControlZ2) { @@ -473,6 +479,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ2) { EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); + EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); } TEST_F(ZXFunctionalityTest, MultiControlRZ) { From 826829ece0549078322bf96e37c23cc1bb556023 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 15 Dec 2025 14:16:30 +0100 Subject: [PATCH 27/73] Remove one check in buildFunctionality as we only care if there is one target for controlled operations --- src/zx/FunctionalityConstruction.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 8a716eb4cc..9f004adf2c 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -849,16 +849,6 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { case qc::OpType::S: case qc::OpType::Tdg: case qc::OpType::Sdg: - case qc::OpType::RZ: - return true; - - default: - return false; - } - } else if (op->getNcontrols() == 2) { - switch (op->getType()) { - case qc::OpType::X: - case qc::OpType::Z: case qc::OpType::RZ: return true; default: From 93bf49d31599002bbeea9a427f67647b921236ef Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 8 Jan 2026 12:11:00 +0100 Subject: [PATCH 28/73] Add additional checks per coderabbit suggestions --- test/zx/test_zx_functionality.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 98b1249937..ec5ac201f1 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -342,6 +342,10 @@ TEST_F(ZXFunctionalityTest, MultiControlXLarger) { EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); + EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); + EXPECT_TRUE(d.connected(d.getInput(4), d.getOutput(3))); } TEST_F(ZXFunctionalityTest, MultiControlX0) { From 8cbe448aaada1bc883479baec35f034f6904100d Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 8 Jan 2026 12:29:14 +0100 Subject: [PATCH 29/73] Fix connectivity in test --- test/zx/test_zx_functionality.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index ec5ac201f1..81b25a42f5 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -345,7 +345,7 @@ TEST_F(ZXFunctionalityTest, MultiControlXLarger) { EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); - EXPECT_TRUE(d.connected(d.getInput(4), d.getOutput(3))); + EXPECT_TRUE(d.connected(d.getInput(4), d.getOutput(4))); } TEST_F(ZXFunctionalityTest, MultiControlX0) { From e10767e3cf0d0a2256fd7bf3941cdd961c58a258 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 8 Jan 2026 13:04:11 +0100 Subject: [PATCH 30/73] Add tests for connectivity of additional wires to meet coderabbit requests --- test/zx/test_zx_functionality.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 81b25a42f5..97cdf1b257 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -267,6 +267,8 @@ TEST_F(ZXFunctionalityTest, MultiCZ) { EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); } TEST_F(ZXFunctionalityTest, CCZ) { using namespace qc::literals; @@ -292,6 +294,8 @@ TEST_F(ZXFunctionalityTest, CCZ) { EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); } TEST_F(ZXFunctionalityTest, MultiControlX) { @@ -390,6 +394,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX1) { EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); } TEST_F(ZXFunctionalityTest, MultiControlZ) { @@ -500,6 +505,8 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ) { EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); } TEST_F(ZXFunctionalityTest, MultiControlRZ0) { From 981621895bc27ec1c85a3704deaf240c77118528 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 8 Jan 2026 13:24:47 +0100 Subject: [PATCH 31/73] Add connectivity test for qubit 1 --- test/zx/test_zx_functionality.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 97cdf1b257..a8ba5598c8 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -549,6 +549,7 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ1) { EXPECT_TRUE(d.isIdentity()); EXPECT_TRUE(d.globalPhaseIsZero()); EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); } TEST_F(ZXFunctionalityTest, UnsupportedControl) { From 77dae777b8d8abc372f0bb77d659642e6ad638e8 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 8 Jan 2026 13:43:50 +0100 Subject: [PATCH 32/73] Removed unneeded import --- test/zx/test_zx_functionality.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index a8ba5598c8..62eab24bda 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -271,7 +271,6 @@ TEST_F(ZXFunctionalityTest, MultiCZ) { EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); } TEST_F(ZXFunctionalityTest, CCZ) { - using namespace qc::literals; const std::string testfile = "OPENQASM 2.0;" "include \"qelib1.inc\";" "qreg q[3];" From 6460a92aa330de69d03642dc9b9c40befc9f5ba2 Mon Sep 17 00:00:00 2001 From: Keefe Huang <30915130+keefehuang@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:18:51 +0100 Subject: [PATCH 33/73] Apply suggestion from @burgholzer Co-authored-by: Lukas Burgholzer Signed-off-by: Keefe Huang <30915130+keefehuang@users.noreply.github.com> --- test/zx/test_zx_functionality.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 62eab24bda..676aa989ef 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -220,7 +220,6 @@ TEST_F(ZXFunctionalityTest, Compound) { } TEST_F(ZXFunctionalityTest, CRZ) { - using namespace qc::literals; qc = qc::QuantumComputation(2); qc.crz(PI / 2, 0, 1); From 4af8da712ca50fd29a41691150ee7ff4b5d3141b Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 5 Feb 2026 12:58:38 +0100 Subject: [PATCH 34/73] Remove unneeded import, remove repeated test --- test/zx/test_zx_functionality.cpp | 60 +++++++++---------------------- 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 676aa989ef..adc286cb21 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -187,7 +187,7 @@ TEST_F(ZXFunctionalityTest, nestedCompoundGate) { } TEST_F(ZXFunctionalityTest, Phase) { - using namespace qc::literals; + qc = qc::QuantumComputation(2); qc.p(PI / 4, 0); qc.cp(PI / 4, 1, 0); @@ -245,8 +245,8 @@ TEST_F(ZXFunctionalityTest, CRZ) { EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); } -TEST_F(ZXFunctionalityTest, MultiCZ) { - using namespace qc::literals; +TEST_F(ZXFunctionalityTest, CCZ) { + qc = qc::QuantumComputation(3); qc.mcz({1, 2}, 0); @@ -269,35 +269,9 @@ TEST_F(ZXFunctionalityTest, MultiCZ) { EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); } -TEST_F(ZXFunctionalityTest, CCZ) { - const std::string testfile = "OPENQASM 2.0;" - "include \"qelib1.inc\";" - "qreg q[3];" - "ccz q[0],q[1],q[2];\n"; - - qc = qasm3::Importer::imports(testfile); - auto qcPrime = qc::QuantumComputation(3); - qcPrime.h(0); - qcPrime.mcx({1, 2}, 0); - qcPrime.h(0); - - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); - EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); -} TEST_F(ZXFunctionalityTest, MultiControlX) { - using namespace qc::literals; + qc = qc::QuantumComputation(4); qc.mcx({1, 2, 3}, 0); @@ -322,7 +296,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX) { } TEST_F(ZXFunctionalityTest, MultiControlXLarger) { - using namespace qc::literals; + qc = qc::QuantumComputation(5); qc.mcx({1, 2, 3}, 0); @@ -351,7 +325,7 @@ TEST_F(ZXFunctionalityTest, MultiControlXLarger) { } TEST_F(ZXFunctionalityTest, MultiControlX0) { - using namespace qc::literals; + qc = qc::QuantumComputation(1); qc.mcx({}, 0); @@ -373,7 +347,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX0) { } TEST_F(ZXFunctionalityTest, MultiControlX1) { - using namespace qc::literals; + qc = qc::QuantumComputation(2); qc.mcx({1}, 0); @@ -396,7 +370,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX1) { } TEST_F(ZXFunctionalityTest, MultiControlZ) { - using namespace qc::literals; + qc = qc::QuantumComputation(4); qc.mcz({1, 2, 3}, 0); @@ -421,7 +395,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ) { } TEST_F(ZXFunctionalityTest, MultiControlZ0) { - using namespace qc::literals; + qc = qc::QuantumComputation(1); qc.mcz({}, 0); @@ -442,7 +416,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ0) { } TEST_F(ZXFunctionalityTest, MultiControlZ1) { - using namespace qc::literals; + qc = qc::QuantumComputation(2); qc.mcz({1}, 0); @@ -464,7 +438,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ1) { } TEST_F(ZXFunctionalityTest, MultiControlZ2) { - using namespace qc::literals; + qc = qc::QuantumComputation(4); qc.mcz({1, 2}, 0); @@ -490,7 +464,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ2) { } TEST_F(ZXFunctionalityTest, MultiControlRZ) { - using namespace qc::literals; + qc = qc::QuantumComputation(3); qc.mcrz(PI / 4, {1, 2}, 0); qc.mcrz(-PI / 4, {1, 2}, 0); @@ -508,7 +482,7 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ) { } TEST_F(ZXFunctionalityTest, MultiControlRZ0) { - using namespace qc::literals; + qc = qc::QuantumComputation(1); qc.mcrz(PI / 4, {}, 0); @@ -529,7 +503,7 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ0) { } TEST_F(ZXFunctionalityTest, MultiControlRZ1) { - using namespace qc::literals; + qc = qc::QuantumComputation(2); qc.mcrz(PI / 4, {1}, 0); @@ -551,7 +525,7 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ1) { } TEST_F(ZXFunctionalityTest, UnsupportedControl) { - using namespace qc::literals; + qc = qc::QuantumComputation(2); qc.cy(1, 0); EXPECT_FALSE(FunctionalityConstruction::transformableToZX(&qc)); @@ -561,7 +535,7 @@ TEST_F(ZXFunctionalityTest, UnsupportedControl) { } TEST_F(ZXFunctionalityTest, UnsupportedControl2) { - using namespace qc::literals; + qc = qc::QuantumComputation(3); qc.mcy({1, 2}, 0); EXPECT_FALSE(FunctionalityConstruction::transformableToZX(&qc)); @@ -624,7 +598,7 @@ TEST_F(ZXFunctionalityTest, RZ) { } TEST_F(ZXFunctionalityTest, ISWAP) { - using namespace qc::literals; + qc = qc::QuantumComputation(2); qc.iswap(0, 1); From 63f2325e1b70b06acaa339887b0e6c2cf5dd2131 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 5 Feb 2026 13:11:13 +0100 Subject: [PATCH 35/73] Extract checkEquivalence as separate function --- test/zx/test_zx_functionality.cpp | 251 +++++------------------------- 1 file changed, 36 insertions(+), 215 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index adc286cb21..cf9fee428a 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -35,6 +35,20 @@ class ZXFunctionalityTest : public ::testing::Test { qc::QuantumComputation qc; }; +void checkEquivalence(const qc::QuantumComputation& qc1, const qc::QuantumComputation& qc2, const std::vector& qubits) { + EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc1)); + EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc2)); + auto d1 = FunctionalityConstruction::buildFunctionality(&qc1); + auto d2 = FunctionalityConstruction::buildFunctionality(&qc2); + d1.concat(d2.invert()); + fullReduce(d1); + EXPECT_TRUE(d1.isIdentity()); + EXPECT_TRUE(d1.globalPhaseIsZero()); + for (std::size_t i = 0; i < qc1.getNqubits(); ++i) { + EXPECT_TRUE(d1.connected(d1.getInput(i), d1.getOutput(i))); + } +} + TEST_F(ZXFunctionalityTest, parseQasm) { const std::string testfile = "OPENQASM 2.0;" "include \"qelib1.inc\";" @@ -230,19 +244,7 @@ TEST_F(ZXFunctionalityTest, CRZ) { qcPrime.cx(0, 1); qcPrime.rz(PI / 4, 1); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + checkEquivalence(qc, qcPrime, {0, 1}); } TEST_F(ZXFunctionalityTest, CCZ) { @@ -255,19 +257,7 @@ TEST_F(ZXFunctionalityTest, CCZ) { qcPrime.mcx({1, 2}, 0); qcPrime.h(0); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); - EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); + checkEquivalence(qc, qcPrime, {0, 1, 2}); } TEST_F(ZXFunctionalityTest, MultiControlX) { @@ -278,21 +268,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX) { auto qcPrime = qc::QuantumComputation(4); qcPrime.mcx({1, 2, 3}, 0); - EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); - EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); - EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } TEST_F(ZXFunctionalityTest, MultiControlXLarger) { @@ -306,22 +282,7 @@ TEST_F(ZXFunctionalityTest, MultiControlXLarger) { qcPrime.mcx({1, 2}, 4); qcPrime.mcx({3, 4}, 0); - EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); - EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); - EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); - EXPECT_TRUE(d.connected(d.getInput(4), d.getOutput(4))); + checkEquivalence(qc, qcPrime, {0, 1, 2, 3, 4}); } TEST_F(ZXFunctionalityTest, MultiControlX0) { @@ -332,18 +293,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX0) { auto qcPrime = qc::QuantumComputation(1); qcPrime.x(0); - - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + checkEquivalence(qc, qcPrime, {0}); } TEST_F(ZXFunctionalityTest, MultiControlX1) { @@ -355,18 +305,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX1) { qcPrime.cx(1, 0); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + checkEquivalence(qc, qcPrime, {0, 1}); } TEST_F(ZXFunctionalityTest, MultiControlZ) { @@ -377,21 +316,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ) { auto qcPrime = qc::QuantumComputation(4); qcPrime.mcz({1, 2, 3}, 0); - EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); - EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); - EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } TEST_F(ZXFunctionalityTest, MultiControlZ0) { @@ -402,17 +327,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ0) { auto qcPrime = qc::QuantumComputation(1); qcPrime.z(0); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + checkEquivalence(qc, qcPrime, {0}); } TEST_F(ZXFunctionalityTest, MultiControlZ1) { @@ -423,18 +338,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ1) { auto qcPrime = qc::QuantumComputation(2); qcPrime.cz(1, 0); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + checkEquivalence(qc, qcPrime, {0, 1}); } TEST_F(ZXFunctionalityTest, MultiControlZ2) { @@ -446,39 +350,19 @@ TEST_F(ZXFunctionalityTest, MultiControlZ2) { qcPrime.h(0); qcPrime.mcx({1, 2}, 0); qcPrime.h(0); - - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); - EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); - EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } TEST_F(ZXFunctionalityTest, MultiControlRZ) { qc = qc::QuantumComputation(3); qc.mcrz(PI / 4, {1, 2}, 0); - qc.mcrz(-PI / 4, {1, 2}, 0); - EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - fullReduce(d); + auto qcPrime = qc::QuantumComputation(3); + qcPrime.mcrz(PI / 4, {1, 2}, 0); - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); - EXPECT_TRUE(d.connected(d.getInput(2), d.getOutput(2))); + checkEquivalence(qc, qcPrime, {0, 1, 2}); } TEST_F(ZXFunctionalityTest, MultiControlRZ0) { @@ -489,17 +373,7 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ0) { auto qcPrime = qc::QuantumComputation(1); qcPrime.rz(PI / 4, 0); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + checkEquivalence(qc, qcPrime, {0}); } TEST_F(ZXFunctionalityTest, MultiControlRZ1) { @@ -510,18 +384,7 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ1) { auto qcPrime = qc::QuantumComputation(2); qcPrime.crz(PI / 4, 1, 0); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); - EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); + checkEquivalence(qc, qcPrime, {0, 1}); } TEST_F(ZXFunctionalityTest, UnsupportedControl) { @@ -557,13 +420,7 @@ TEST_F(ZXFunctionalityTest, InitialLayout) { qcPrime.x(1); qcPrime.z(0); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime); - - fullReduce(d); - EXPECT_TRUE(d.isIdentity()); + checkEquivalence(qc, qcPrime, {0, 1}); } TEST_F(ZXFunctionalityTest, FromSymbolic) { @@ -584,17 +441,9 @@ TEST_F(ZXFunctionalityTest, RZ) { qc.rz(PI / 8, 0); auto qcPrime = qc::QuantumComputation(1); - qcPrime.p(PI / 8, 0); - - auto d = FunctionalityConstruction::buildFunctionality(&qc); - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); + qcPrime.rz(PI / 8, 0); - d.concat(dPrime.invert()); - - fullReduce(d); - EXPECT_FALSE(d.isIdentity()); - EXPECT_FALSE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + checkEquivalence(qc, qcPrime, {0}); } TEST_F(ZXFunctionalityTest, ISWAP) { @@ -610,15 +459,7 @@ TEST_F(ZXFunctionalityTest, ISWAP) { qcPrime.cx(1, 0); qc.h(1); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + checkEquivalence(qc, qcPrime, {0, 1}); } TEST_F(ZXFunctionalityTest, XXplusYY) { @@ -644,17 +485,7 @@ TEST_F(ZXFunctionalityTest, XXplusYY) { qcPrime.rz(qc::PI_2, 0); qcPrime.rz(-beta, 1); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + checkEquivalence(qc, qcPrime, {0, 1}); } TEST_F(ZXFunctionalityTest, XXminusYY) { @@ -680,16 +511,6 @@ TEST_F(ZXFunctionalityTest, XXminusYY) { qcPrime.rz(qc::PI_2, 0); qcPrime.rz(beta, 1); - auto d = FunctionalityConstruction::buildFunctionality(&qc); - - auto dPrime = FunctionalityConstruction::buildFunctionality(&qcPrime); - - d.concat(dPrime.invert()); - - fullReduce(d); - - EXPECT_TRUE(d.isIdentity()); - EXPECT_TRUE(d.globalPhaseIsZero()); - EXPECT_TRUE(d.connected(d.getInput(0), d.getOutput(0))); + checkEquivalence(qc, qcPrime, {0, 1}); } } // namespace zx From eed43bf8e2fbcbb98c3affa1bd5093c4f35e5943 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 5 Feb 2026 13:12:04 +0100 Subject: [PATCH 36/73] Remove unreachable code --- src/zx/FunctionalityConstruction.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 9f004adf2c..a37ac3b0a0 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -385,10 +385,6 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, break; } } - if (!anc.has_value()) { - throw ZXException("No ancilla qubit available for MCX decomposition"); - } - controls.pop_back(); second.push_back(*anc); From bf5e23e6708b37d7155778ab68096bdf3682b0ce Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 5 Feb 2026 13:30:02 +0100 Subject: [PATCH 37/73] Add set_difference implementation --- src/zx/FunctionalityConstruction.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index a37ac3b0a0..dab063dea6 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -376,22 +376,21 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, std::vector second(controls.begin() + half, controls.end()); if (qubits.size() > controls.size() + 1) { - controls.push_back(target); - std::optional anc{}; - for (std::size_t q = 0; q < qubits.size(); ++q) { - const auto qb = static_cast(q); - if (std::ranges::find(controls, qb) == controls.end()) { - anc = qb; - break; - } - } - controls.pop_back(); - second.push_back(*anc); - - addMcx(diag, first, *anc, qubits); + std::vector blocked = controls; + blocked.push_back(target); + std::ranges::sort(blocked); + std::ranges::sort(qubits); + + std::vector available; + available.reserve(qubits.size()); + std::ranges::set_difference(qubits, blocked, std::back_inserter(available)); + + second.push_back(available.front()); + + addMcx(diag, first, available.front(), qubits); addMcx(diag, second, target, qubits); - addMcx(diag, first, *anc, qubits); + addMcx(diag, first, available.front(), qubits); addMcx(diag, second, target, qubits); } else { addRx(diag, PiExpression(PiRational(1, 4)), target, qubits); From 3f1fa282db4259604cc0be3623b0db331d45bb3e Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 5 Feb 2026 13:32:02 +0100 Subject: [PATCH 38/73] Inline vector --- src/zx/FunctionalityConstruction.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index dab063dea6..9419a1eb76 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -697,10 +697,6 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, ctrl1 = static_cast(p.at(ctrl.qubit)); } } - std::vector controls; - for (const auto& ctrl : op->getControls()) { - controls.push_back(static_cast(p.at(ctrl.qubit))); - } switch (op->getType()) { case qc::OpType::X: addCcx(diag, ctrl0, ctrl1, target, qubits); @@ -709,7 +705,7 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, addCcz(diag, ctrl0, ctrl1, target, qubits); break; case qc::OpType::RZ: - addMcrz(diag, parseParam(op.get(), 0), controls, target, qubits); + addMcrz(diag, parseParam(op.get(), 0), {ctrl0, ctrl1}, target, qubits); break; default: throw ZXException("Unsupported Multi-control operation: " + From 340bad9f3a31e1c9b2ad24c84bbb7358faa5e963 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 5 Feb 2026 13:34:05 +0100 Subject: [PATCH 39/73] Reserve and switch to emplace_back --- src/zx/FunctionalityConstruction.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 9419a1eb76..bf3f5b2207 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -377,7 +377,7 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, if (qubits.size() > controls.size() + 1) { std::vector blocked = controls; - blocked.push_back(target); + blocked.emplace_back(target); std::ranges::sort(blocked); std::ranges::sort(qubits); @@ -385,7 +385,7 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, available.reserve(qubits.size()); std::ranges::set_difference(qubits, blocked, std::back_inserter(available)); - second.push_back(available.front()); + second.emplace_back(available.front()); addMcx(diag, first, available.front(), qubits); addMcx(diag, second, target, qubits); @@ -714,8 +714,9 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, } else if (op->getNtargets() == 1) { const auto target = static_cast(p.at(op->getTargets().front())); std::vector controls; + controls.reserve(op->getNcontrols()); for (const auto& ctrl : op->getControls()) { - controls.push_back(static_cast(p.at(ctrl.qubit))); + controls.emplace_back(static_cast(p.at(ctrl.qubit))); } switch (op->getType()) { case qc::OpType::X: From 4780212a68e0b7484f6c5477659ae2a31468fbd3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:35:03 +0000 Subject: [PATCH 40/73] =?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 --- src/zx/FunctionalityConstruction.cpp | 5 ++-- test/zx/test_zx_functionality.cpp | 42 +++++++++++++++------------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index bf3f5b2207..ab104082ba 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -380,10 +380,11 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, blocked.emplace_back(target); std::ranges::sort(blocked); std::ranges::sort(qubits); - + std::vector available; available.reserve(qubits.size()); - std::ranges::set_difference(qubits, blocked, std::back_inserter(available)); + std::ranges::set_difference(qubits, blocked, + std::back_inserter(available)); second.emplace_back(available.front()); diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index cf9fee428a..df56bac36e 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -35,10 +35,12 @@ class ZXFunctionalityTest : public ::testing::Test { qc::QuantumComputation qc; }; -void checkEquivalence(const qc::QuantumComputation& qc1, const qc::QuantumComputation& qc2, const std::vector& qubits) { +void checkEquivalence(const qc::QuantumComputation& qc1, + const qc::QuantumComputation& qc2, + const std::vector& qubits) { EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc1)); EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc2)); - auto d1 = FunctionalityConstruction::buildFunctionality(&qc1); + auto d1 = FunctionalityConstruction::buildFunctionality(&qc1); auto d2 = FunctionalityConstruction::buildFunctionality(&qc2); d1.concat(d2.invert()); fullReduce(d1); @@ -201,7 +203,7 @@ TEST_F(ZXFunctionalityTest, nestedCompoundGate) { } TEST_F(ZXFunctionalityTest, Phase) { - + qc = qc::QuantumComputation(2); qc.p(PI / 4, 0); qc.cp(PI / 4, 1, 0); @@ -248,7 +250,7 @@ TEST_F(ZXFunctionalityTest, CRZ) { } TEST_F(ZXFunctionalityTest, CCZ) { - + qc = qc::QuantumComputation(3); qc.mcz({1, 2}, 0); @@ -261,7 +263,7 @@ TEST_F(ZXFunctionalityTest, CCZ) { } TEST_F(ZXFunctionalityTest, MultiControlX) { - + qc = qc::QuantumComputation(4); qc.mcx({1, 2, 3}, 0); @@ -272,7 +274,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX) { } TEST_F(ZXFunctionalityTest, MultiControlXLarger) { - + qc = qc::QuantumComputation(5); qc.mcx({1, 2, 3}, 0); @@ -286,7 +288,7 @@ TEST_F(ZXFunctionalityTest, MultiControlXLarger) { } TEST_F(ZXFunctionalityTest, MultiControlX0) { - + qc = qc::QuantumComputation(1); qc.mcx({}, 0); @@ -297,7 +299,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX0) { } TEST_F(ZXFunctionalityTest, MultiControlX1) { - + qc = qc::QuantumComputation(2); qc.mcx({1}, 0); @@ -309,7 +311,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX1) { } TEST_F(ZXFunctionalityTest, MultiControlZ) { - + qc = qc::QuantumComputation(4); qc.mcz({1, 2, 3}, 0); @@ -320,7 +322,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ) { } TEST_F(ZXFunctionalityTest, MultiControlZ0) { - + qc = qc::QuantumComputation(1); qc.mcz({}, 0); @@ -331,7 +333,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ0) { } TEST_F(ZXFunctionalityTest, MultiControlZ1) { - + qc = qc::QuantumComputation(2); qc.mcz({1}, 0); @@ -342,7 +344,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ1) { } TEST_F(ZXFunctionalityTest, MultiControlZ2) { - + qc = qc::QuantumComputation(4); qc.mcz({1, 2}, 0); @@ -350,12 +352,12 @@ TEST_F(ZXFunctionalityTest, MultiControlZ2) { qcPrime.h(0); qcPrime.mcx({1, 2}, 0); qcPrime.h(0); - + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } TEST_F(ZXFunctionalityTest, MultiControlRZ) { - + qc = qc::QuantumComputation(3); qc.mcrz(PI / 4, {1, 2}, 0); @@ -366,18 +368,18 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ) { } TEST_F(ZXFunctionalityTest, MultiControlRZ0) { - + qc = qc::QuantumComputation(1); qc.mcrz(PI / 4, {}, 0); auto qcPrime = qc::QuantumComputation(1); qcPrime.rz(PI / 4, 0); - checkEquivalence(qc, qcPrime, {0}); + checkEquivalence(qc, qcPrime, {0}); } TEST_F(ZXFunctionalityTest, MultiControlRZ1) { - + qc = qc::QuantumComputation(2); qc.mcrz(PI / 4, {1}, 0); @@ -388,7 +390,7 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ1) { } TEST_F(ZXFunctionalityTest, UnsupportedControl) { - + qc = qc::QuantumComputation(2); qc.cy(1, 0); EXPECT_FALSE(FunctionalityConstruction::transformableToZX(&qc)); @@ -398,7 +400,7 @@ TEST_F(ZXFunctionalityTest, UnsupportedControl) { } TEST_F(ZXFunctionalityTest, UnsupportedControl2) { - + qc = qc::QuantumComputation(3); qc.mcy({1, 2}, 0); EXPECT_FALSE(FunctionalityConstruction::transformableToZX(&qc)); @@ -447,7 +449,7 @@ TEST_F(ZXFunctionalityTest, RZ) { } TEST_F(ZXFunctionalityTest, ISWAP) { - + qc = qc::QuantumComputation(2); qc.iswap(0, 1); From ff77fa4cc6a7d901f1481f2fb9f09ef3744015e4 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 5 Feb 2026 14:10:29 +0100 Subject: [PATCH 41/73] Run clang-format --- test/zx/test_zx_functionality.cpp | 87 ++++++++----------------------- 1 file changed, 23 insertions(+), 64 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index df56bac36e..ce66e0cb61 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -107,70 +107,29 @@ TEST_F(ZXFunctionalityTest, complexCircuit) { std::stringstream ss{}; ss << "// i 1 0 2\n" << "// o 0 1 2\n" - << "OPENQASM 2.0;" - << "include \"qelib1.inc\";" - << "qreg q[3];" - << "sx q[0];" - << "sxdg q[0];" - << "h q[0];" - << "cx q[0],q[1];" - << "z q[1];" - << "x q[2];" - << "y q[0];" - << "rx(pi/4) q[0];" - << "rz(0.1) q[1];" - << "p(0.1) q[1];" - << "ry(pi/4) q[2];" - << "t q[0];" - << "s q[2];" - << "u2(pi/4, pi/4) q[1];" - << "u3(pi/4, pi/4, pi/4) q[2];" - << "barrier q[0],q[1],q[2];" - << "swap q[0],q[1];" - << "cz q[1],q[2];" - << "cp(pi/4) q[0],q[1];" - << "ctrl(2) @ x q[0],q[1],q[2];" - << "ctrl(2) @ z q[1],q[2],q[0];" - << "cp(pi/2) q[0], q[1];" - << "cp(pi/4) q[0], q[1];" - << "cp(pi/8) q[0], q[1];" - << "rzz(pi/4) q[0], q[1];" - << "rxx(pi/4) q[0], q[1];" - << "ryy(pi/4) q[0], q[1];" - << "rzx(pi/4) q[0], q[1];" - << "ecr q[0], q[1];" - << "dcx q[0], q[1];" - << "r(pi/8, pi/4) q[2];" - << "r(-pi/8, pi/4) q[2];" - << "dcx q[1], q[0];" - << "ecr q[0], q[1];" - << "rzx(-pi/4) q[0], q[1];" - << "ryy(-pi/4) q[0], q[1];" - << "rxx(-pi/4) q[0], q[1];" - << "rzz(-pi/4) q[0], q[1];" - << "cp(-pi/8) q[0], q[1];" - << "cp(-pi/4) q[0], q[1];" - << "cp(-pi/2) q[0], q[1];" - << "ctrl(2) @ z q[1],q[2],q[0];" - << "ctrl(2) @ x q[0],q[1],q[2];" - << "cp(-pi/4) q[0],q[1];" - << "cz q[1],q[2];" - << "cx q[1],q[0];" - << "cx q[0],q[1];" - << "cx q[1],q[0];" - << "u3(-pi/4,-pi/4,-pi/4) q[2];" - << "u2(-5*pi/4,3*pi/4) q[1];" - << "sdg q[2];" - << "tdg q[0];" - << "ry(-pi/4) q[2];" - << "p(-0.1) q[1];" - << "rz(-0.1) q[1];" - << "rx(-pi/4) q[0];" - << "y q[0];" - << "x q[2];" - << "z q[1];" - << "cx q[0],q[1];" - << "h q[0];\n"; + << "OPENQASM 2.0;" << "include \"qelib1.inc\";" << "qreg q[3];" + << "sx q[0];" << "sxdg q[0];" << "h q[0];" << "cx q[0],q[1];" << "z q[1];" + << "x q[2];" << "y q[0];" << "rx(pi/4) q[0];" << "rz(0.1) q[1];" + << "p(0.1) q[1];" << "ry(pi/4) q[2];" << "t q[0];" << "s q[2];" + << "u2(pi/4, pi/4) q[1];" << "u3(pi/4, pi/4, pi/4) q[2];" + << "barrier q[0],q[1],q[2];" << "swap q[0],q[1];" << "cz q[1],q[2];" + << "cp(pi/4) q[0],q[1];" << "ctrl(2) @ x q[0],q[1],q[2];" + << "ctrl(2) @ z q[1],q[2],q[0];" << "cp(pi/2) q[0], q[1];" + << "cp(pi/4) q[0], q[1];" << "cp(pi/8) q[0], q[1];" + << "rzz(pi/4) q[0], q[1];" << "rxx(pi/4) q[0], q[1];" + << "ryy(pi/4) q[0], q[1];" << "rzx(pi/4) q[0], q[1];" << "ecr q[0], q[1];" + << "dcx q[0], q[1];" << "r(pi/8, pi/4) q[2];" << "r(-pi/8, pi/4) q[2];" + << "dcx q[1], q[0];" << "ecr q[0], q[1];" << "rzx(-pi/4) q[0], q[1];" + << "ryy(-pi/4) q[0], q[1];" << "rxx(-pi/4) q[0], q[1];" + << "rzz(-pi/4) q[0], q[1];" << "cp(-pi/8) q[0], q[1];" + << "cp(-pi/4) q[0], q[1];" << "cp(-pi/2) q[0], q[1];" + << "ctrl(2) @ z q[1],q[2],q[0];" << "ctrl(2) @ x q[0],q[1],q[2];" + << "cp(-pi/4) q[0],q[1];" << "cz q[1],q[2];" << "cx q[1],q[0];" + << "cx q[0],q[1];" << "cx q[1],q[0];" << "u3(-pi/4,-pi/4,-pi/4) q[2];" + << "u2(-5*pi/4,3*pi/4) q[1];" << "sdg q[2];" << "tdg q[0];" + << "ry(-pi/4) q[2];" << "p(-0.1) q[1];" << "rz(-0.1) q[1];" + << "rx(-pi/4) q[0];" << "y q[0];" << "x q[2];" << "z q[1];" + << "cx q[0],q[1];" << "h q[0];\n"; qc = qasm3::Importer::import(ss); EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc)); From 297078e3f018dc5be72664d8ef795b9cd594c3d0 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 5 Feb 2026 22:50:03 +0100 Subject: [PATCH 42/73] Implement Crx, Mcrz, Mcrzz, Mcrzx, Mcrxx, Mcswap --- .../mqt-core/zx/FunctionalityConstruction.hpp | 17 ++ src/zx/FunctionalityConstruction.cpp | 220 ++++++++++++++---- 2 files changed, 192 insertions(+), 45 deletions(-) diff --git a/include/mqt-core/zx/FunctionalityConstruction.hpp b/include/mqt-core/zx/FunctionalityConstruction.hpp index 7b8036403e..cf3af2f336 100644 --- a/include/mqt-core/zx/FunctionalityConstruction.hpp +++ b/include/mqt-core/zx/FunctionalityConstruction.hpp @@ -85,8 +85,14 @@ class FunctionalityConstruction { EdgeType type = EdgeType::Simple); static void addCphase(ZXDiagram& diag, const PiExpression& phase, Qubit ctrl, Qubit target, std::vector& qubits); + static void addMcphase(ZXDiagram& diag, const PiExpression& phase, + const std::vector& controls, Qubit target, + std::vector& qubits); static void addSwap(ZXDiagram& diag, Qubit target, Qubit target2, std::vector& qubits); + static void addMcswap(ZXDiagram& diag, const std::vector& controls, + Qubit target, Qubit target2, + std::vector& qubits); static void addRzz(ZXDiagram& diag, const PiExpression& phase, Qubit target, Qubit target2, std::vector& qubits, @@ -99,6 +105,11 @@ class FunctionalityConstruction { addRzx(ZXDiagram& diag, const PiExpression& phase, Qubit target, Qubit target2, std::vector& qubits, const std::optional& unconvertedPhase = std::nullopt); + static void + addMcrzz(ZXDiagram& diag, const PiExpression& phase, + const std::vector& controls, const Qubit target, + const Qubit target2, std::vector& qubits, + const std::optional& unconvertedPhase = std::nullopt); static void addDcx(ZXDiagram& diag, Qubit qubit1, Qubit qubit2, std::vector& qubits); static void @@ -115,6 +126,12 @@ class FunctionalityConstruction { std::vector& qubits); static void addCcz(ZXDiagram& diag, Qubit ctrl0, Qubit ctrl1, Qubit target, std::vector& qubits); + static void addCrx(ZXDiagram& diag, const PiExpression& phase, + const Qubit control, const Qubit target, + std::vector& qubits); + static void addMcrx(ZXDiagram& diag, const PiExpression& phase, + std::vector controls, const Qubit target, + std::vector& qubits); static void addCrz(ZXDiagram& diag, const PiExpression& phase, const Qubit control, const Qubit target, std::vector& qubits); diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index ab104082ba..e1b43b5096 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -140,6 +140,32 @@ void FunctionalityConstruction::addCphase(ZXDiagram& diag, addZSpider(diag, target, qubits, newPhase); } +void FunctionalityConstruction::addMcphase(ZXDiagram& diag, + const PiExpression& phase, + const std::vector& controls, + const Qubit target, + std::vector& qubits) { + auto newConst = phase.getConst() / 2; + auto newPhase = phase / 2.0; + newPhase.setConst(newConst); + addZSpider(diag, target, qubits, newPhase); + addMcx(diag, controls, target, qubits); + addZSpider(diag, target, qubits, -newPhase); + addMcx(diag, controls, target, qubits); + switch (controls.size()) { + case 1: + addZSpider(diag, controls[0], qubits, newPhase); + return; + case 2: + addCphase(diag, newPhase, controls[0], controls[1], qubits); + return; + default: + addMcphase(diag, newPhase, + std::vector(controls.begin(), controls.end() - 1), + controls.back(), qubits); + } +} + void FunctionalityConstruction::addRzz( ZXDiagram& diag, const PiExpression& phase, const Qubit target, const Qubit target2, std::vector& qubits, @@ -207,6 +233,17 @@ void FunctionalityConstruction::addRzx( } } +void FunctionalityConstruction::addMcrzz( + ZXDiagram& diag, const PiExpression& phase, + const std::vector& controls, const Qubit target, const Qubit target2, + std::vector& qubits, + const std::optional& unconvertedPhase) { + addRzz(diag, phase, target, target2, qubits, unconvertedPhase); + addMcx(diag, controls, target, qubits); + addRzz(diag, -phase, target, target2, qubits, unconvertedPhase); + addMcx(diag, controls, target, qubits); +} + void FunctionalityConstruction::addDcx(ZXDiagram& diag, const Qubit qubit1, const Qubit qubit2, std::vector& qubits) { @@ -286,6 +323,22 @@ void FunctionalityConstruction::addSwap(ZXDiagram& diag, const Qubit target, qubits[c] = t1; } +void FunctionalityConstruction::addMcswap(ZXDiagram& diag, + const std::vector& controls, + const Qubit target, + const Qubit target2, + std::vector& qubits) { + std::vector controls1 = controls; + std::vector controls2 = controls; + + controls1.emplace_back(target); + controls2.emplace_back(target2); + + addMcx(diag, controls1, target2, qubits); + addMcx(diag, controls2, target, qubits); + addMcx(diag, controls1, target2, qubits); +} + void FunctionalityConstruction::addCcx(ZXDiagram& diag, const Qubit ctrl0, const Qubit ctrl1, const Qubit target, std::vector& qubits) { @@ -329,6 +382,25 @@ void FunctionalityConstruction::addCcz(ZXDiagram& diag, const Qubit ctrl0, addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); } +void FunctionalityConstruction::addCrx(ZXDiagram& diag, + const PiExpression& phase, + const Qubit control, const Qubit target, + std::vector& qubits) { + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addCrz(diag, phase / 2, control, target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); +} + +void FunctionalityConstruction::addMcrx(ZXDiagram& diag, + const PiExpression& phase, + std::vector controls, + const Qubit target, + std::vector& qubits) { + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addMcrz(diag, phase, controls, target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); +} + void FunctionalityConstruction::addCrz(ZXDiagram& diag, const PiExpression& phase, const Qubit q0, const Qubit q1, @@ -375,39 +447,26 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, const std::vector first(controls.begin(), controls.begin() + half); std::vector second(controls.begin() + half, controls.end()); - if (qubits.size() > controls.size() + 1) { - std::vector blocked = controls; - blocked.emplace_back(target); - std::ranges::sort(blocked); - std::ranges::sort(qubits); - - std::vector available; - available.reserve(qubits.size()); - std::ranges::set_difference(qubits, blocked, - std::back_inserter(available)); - - second.emplace_back(available.front()); - - addMcx(diag, first, available.front(), qubits); - addMcx(diag, second, target, qubits); - - addMcx(diag, first, available.front(), qubits); - addMcx(diag, second, target, qubits); - } else { - addRx(diag, PiExpression(PiRational(1, 4)), target, qubits); - addMcz(diag, second, target, qubits); - addRx(diag, PiExpression(-PiRational(1, 4)), target, qubits); - addMcx(diag, first, target, qubits); - - addRx(diag, PiExpression(PiRational(1, 4)), target, qubits); - addMcz(diag, second, target, qubits); - addRx(diag, PiExpression(-PiRational(1, 4)), target, qubits); - addMcx(diag, first, target, qubits); - const Qubit lastControl = controls.back(); - controls.pop_back(); - addMcrz(diag, PiExpression(PiRational(1, 2)), controls, lastControl, - qubits); - } + addRx(diag, PiExpression(PiRational(1, 4)), target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addMcx(diag, first, target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addRx(diag, PiExpression(-PiRational(1, 4)), target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addMcx(diag, second, target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addRx(diag, PiExpression(PiRational(1, 4)), target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addMcx(diag, first, target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addRx(diag, PiExpression(-PiRational(1, 4)), target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addMcx(diag, second, target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + const Qubit lastControl = controls.back(); + controls.pop_back(); + addMcphase(diag, PiExpression(PiRational(1, 2)), controls, lastControl, + qubits); } } @@ -415,13 +474,9 @@ void FunctionalityConstruction::addMcz(ZXDiagram& diag, std::vector controls, const Qubit target, std::vector& qubits) { - const Qubit nextControl = controls.back(); - controls.pop_back(); - - addCrz(diag, PiExpression(PiRational(1, 2)), nextControl, target, qubits); - addMcx(diag, controls, target, qubits); - addCrz(diag, PiExpression(-PiRational(1, 2)), nextControl, target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); addMcx(diag, controls, target, qubits); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); } FunctionalityConstruction::op_it @@ -546,21 +601,27 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, case qc::OpType::RXX: { const auto target2 = static_cast(p.at(op->getTargets()[1])); const auto& phase = parseParam(op.get(), 0); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); if (phase.isConstant()) { addRxx(diag, phase, target, target2, qubits, op->getParameter().at(0)); } else { addRxx(diag, phase, target, target2, qubits); } + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); break; } case qc::OpType::RZX: { const auto target2 = static_cast(p.at(op->getTargets()[1])); const auto& phase = parseParam(op.get(), 0); + addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); if (phase.isConstant()) { addRzx(diag, phase, target, target2, qubits, op->getParameter().at(0)); } else { addRzx(diag, phase, target, target2, qubits); } + addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); break; } case qc::OpType::RYY: { @@ -659,9 +720,11 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, addCrz(diag, parseParam(op.get(), 0), ctrl, target, qubits); break; + case qc::OpType::RX: + addCrx(diag, parseParam(op.get(), 0), ctrl, target, qubits); + break; case qc::OpType::I: break; - case qc::OpType::P: addCphase(diag, parseParam(op.get(), 0), ctrl, target, qubits); break; @@ -685,7 +748,7 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, throw ZXException("Unsupported Controlled Operation: " + qc::toString(op->getType())); } - } else if (op->getNcontrols() == 2) { + } else if (op->getNcontrols() == 2 && op->getNtargets() == 1) { // three-qubit controlled gates (ccx or ccz) Qubit ctrl0 = 0; Qubit ctrl1 = 0; @@ -708,6 +771,9 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, case qc::OpType::RZ: addMcrz(diag, parseParam(op.get(), 0), {ctrl0, ctrl1}, target, qubits); break; + case qc::OpType::RX: + addMcrx(diag, parseParam(op.get(), 0), {ctrl0, ctrl1}, target, qubits); + break; default: throw ZXException("Unsupported Multi-control operation: " + qc::toString(op->getType())); @@ -729,14 +795,67 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, case qc::OpType::RZ: addMcrz(diag, parseParam(op.get(), 0), controls, target, qubits); break; + case qc::OpType::RX: + addMcrx(diag, parseParam(op.get(), 0), controls, target, qubits); + break; default: - throw ZXException("Unsupported Multi-control operation: " + + throw ZXException("Unsupported Multi-control operation (" + + std::to_string(op->getNcontrols()) + " ctrls)" + qc::toString(op->getType())); } } else { - throw ZXException("Unsupported Multi-control operation (" + - std::to_string(op->getNcontrols()) + " ctrls)" + - qc::toString(op->getType())); + const auto target = static_cast(p.at(op->getTargets().front())); + const auto target2 = static_cast(p.at(op->getTargets()[1])); + std::vector controls; + controls.reserve(op->getNcontrols()); + for (const auto& ctrl : op->getControls()) { + controls.emplace_back(static_cast(p.at(ctrl.qubit))); + } + switch (op->getType()) { + case qc::OpType::SWAP: + addMcswap(diag, controls, target, target2, qubits); + break; + case qc::OpType::RZZ: { + const auto& phase = parseParam(op.get(), 0); + if (phase.isConstant()) { + addMcrzz(diag, phase, controls, target, target2, qubits, + op->getParameter().at(0)); + } else { + addMcrzz(diag, phase, controls, target, target2, qubits); + } + break; + } + case qc::OpType::RXX: { + const auto& phase = parseParam(op.get(), 0); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); + if (phase.isConstant()) { + addMcrzz(diag, phase, controls, target, target2, qubits, + op->getParameter().at(0)); + } else { + addMcrzz(diag, phase, controls, target, target2, qubits); + } + addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); + addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + break; + } + case qc::OpType::RZX: { + const auto& phase = parseParam(op.get(), 0); + addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); + if (phase.isConstant()) { + addMcrzz(diag, phase, controls, target, target2, qubits, + op->getParameter().at(0)); + } else { + addMcrzz(diag, phase, controls, target, target2, qubits); + } + addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); + break; + } + default: + throw ZXException("Unsupported Multi-control operation (" + + std::to_string(op->getNcontrols()) + " ctrls)" + + qc::toString(op->getType())); + } } return it + 1; } @@ -852,6 +971,17 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { case qc::OpType::X: case qc::OpType::Z: case qc::OpType::RZ: + case qc::OpType::RX: + return true; + default: + return false; + } + } else if (op->getNtargets() == 2) { + switch (op->getType()) { + case qc::OpType::SWAP: + case qc::OpType::RZZ: + case qc::OpType::RXX: + case qc::OpType::RZX: return true; default: return false; From 9ab3177b99d12c17a713d861ee04380db842a602 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:50:35 +0000 Subject: [PATCH 43/73] =?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/zx/FunctionalityConstruction.hpp | 12 ++++++------ src/zx/FunctionalityConstruction.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/mqt-core/zx/FunctionalityConstruction.hpp b/include/mqt-core/zx/FunctionalityConstruction.hpp index cf3af2f336..0aa62f375b 100644 --- a/include/mqt-core/zx/FunctionalityConstruction.hpp +++ b/include/mqt-core/zx/FunctionalityConstruction.hpp @@ -86,12 +86,12 @@ class FunctionalityConstruction { static void addCphase(ZXDiagram& diag, const PiExpression& phase, Qubit ctrl, Qubit target, std::vector& qubits); static void addMcphase(ZXDiagram& diag, const PiExpression& phase, - const std::vector& controls, Qubit target, - std::vector& qubits); + const std::vector& controls, Qubit target, + std::vector& qubits); static void addSwap(ZXDiagram& diag, Qubit target, Qubit target2, std::vector& qubits); static void addMcswap(ZXDiagram& diag, const std::vector& controls, - Qubit target, Qubit target2, + Qubit target, Qubit target2, std::vector& qubits); static void addRzz(ZXDiagram& diag, const PiExpression& phase, Qubit target, @@ -107,9 +107,9 @@ class FunctionalityConstruction { const std::optional& unconvertedPhase = std::nullopt); static void addMcrzz(ZXDiagram& diag, const PiExpression& phase, - const std::vector& controls, const Qubit target, - const Qubit target2, std::vector& qubits, - const std::optional& unconvertedPhase = std::nullopt); + const std::vector& controls, const Qubit target, + const Qubit target2, std::vector& qubits, + const std::optional& unconvertedPhase = std::nullopt); static void addDcx(ZXDiagram& diag, Qubit qubit1, Qubit qubit2, std::vector& qubits); static void diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index e1b43b5096..1651636743 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -829,7 +829,7 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, const auto& phase = parseParam(op.get(), 0); addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); - if (phase.isConstant()) { + if (phase.isConstant()) { addMcrzz(diag, phase, controls, target, target2, qubits, op->getParameter().at(0)); } else { From 553ab8f218b4d0eb658b02d149a2eedabed1ed47 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 19 Feb 2026 14:16:49 +0100 Subject: [PATCH 44/73] Update checkEquivalance, tests, Mcrzz implementation --- src/zx/FunctionalityConstruction.cpp | 26 ++++- test/zx/test_zx_functionality.cpp | 139 ++++++++++++++++++++++----- 2 files changed, 141 insertions(+), 24 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 1651636743..7588c29fd7 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -240,7 +240,7 @@ void FunctionalityConstruction::addMcrzz( const std::optional& unconvertedPhase) { addRzz(diag, phase, target, target2, qubits, unconvertedPhase); addMcx(diag, controls, target, qubits); - addRzz(diag, -phase, target, target2, qubits, unconvertedPhase); + addRzz(diag, -phase, target, target2, qubits, unconvertedPhase.has_value() ? std::optional(-unconvertedPhase.value()) : std::nullopt); addMcx(diag, controls, target, qubits); } @@ -387,7 +387,7 @@ void FunctionalityConstruction::addCrx(ZXDiagram& diag, const Qubit control, const Qubit target, std::vector& qubits) { addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addCrz(diag, phase / 2, control, target, qubits); + addCrz(diag, phase, control, target, qubits); addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); } @@ -768,6 +768,16 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, case qc::OpType::Z: addCcz(diag, ctrl0, ctrl1, target, qubits); break; + case qc::OpType::P: + addMcphase(diag, parseParam(op.get(), 0), {ctrl0, ctrl1}, target, qubits); + break; + case qc::OpType::T: + addMcphase(diag, PiExpression{PiRational(1, 4)}, {ctrl0, ctrl1}, target, + qubits); + break; + case qc::OpType::S: + addMcphase(diag, PiExpression{PiRational(1, 2)}, {ctrl0, ctrl1}, target, qubits); + break; case qc::OpType::RZ: addMcrz(diag, parseParam(op.get(), 0), {ctrl0, ctrl1}, target, qubits); break; @@ -792,6 +802,15 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, case qc::OpType::Z: addMcz(diag, controls, target, qubits); break; + case qc::OpType::P: + addMcphase(diag, parseParam(op.get(), 0), controls, target, qubits); + break; + case qc::OpType::T: + addMcphase(diag, PiExpression{PiRational(1, 4)}, controls, target, qubits); + break; + case qc::OpType::S: + addMcphase(diag, PiExpression{PiRational(1, 2)}, controls, target, qubits); + break; case qc::OpType::RZ: addMcrz(diag, parseParam(op.get(), 0), controls, target, qubits); break; @@ -970,6 +989,9 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { switch (op->getType()) { case qc::OpType::X: case qc::OpType::Z: + case qc::OpType::P: + case qc::OpType::T: + case qc::OpType::S: case qc::OpType::RZ: case qc::OpType::RX: return true; diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index ce66e0cb61..2fd8eb6a2a 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -35,19 +35,22 @@ class ZXFunctionalityTest : public ::testing::Test { qc::QuantumComputation qc; }; -void checkEquivalence(const qc::QuantumComputation& qc1, - const qc::QuantumComputation& qc2, - const std::vector& qubits) { +static void checkEquivalence(const qc::QuantumComputation& qc1, + const qc::QuantumComputation& qc2, + const std::vector& qubits) { EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc1)); EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc2)); + EXPECT_EQ(qc1.getNqubits(), qc2.getNqubits()); + auto d1 = FunctionalityConstruction::buildFunctionality(&qc1); auto d2 = FunctionalityConstruction::buildFunctionality(&qc2); d1.concat(d2.invert()); fullReduce(d1); EXPECT_TRUE(d1.isIdentity()); EXPECT_TRUE(d1.globalPhaseIsZero()); - for (std::size_t i = 0; i < qc1.getNqubits(); ++i) { - EXPECT_TRUE(d1.connected(d1.getInput(i), d1.getOutput(i))); + for (const auto q : qubits) { + ASSERT_LT(q, qc1.getNqubits()); + EXPECT_TRUE(d1.connected(d1.getInput(q), d1.getOutput(q))); } } @@ -227,25 +230,11 @@ TEST_F(ZXFunctionalityTest, MultiControlX) { qc.mcx({1, 2, 3}, 0); auto qcPrime = qc::QuantumComputation(4); - qcPrime.mcx({1, 2, 3}, 0); + qcPrime.mcx({3, 2, 1}, 0); checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } -TEST_F(ZXFunctionalityTest, MultiControlXLarger) { - - qc = qc::QuantumComputation(5); - qc.mcx({1, 2, 3}, 0); - - auto qcPrime = qc::QuantumComputation(5); - qcPrime.mcx({1, 2}, 4); - qcPrime.mcx({3, 4}, 0); - qcPrime.mcx({1, 2}, 4); - qcPrime.mcx({3, 4}, 0); - - checkEquivalence(qc, qcPrime, {0, 1, 2, 3, 4}); -} - TEST_F(ZXFunctionalityTest, MultiControlX0) { qc = qc::QuantumComputation(1); @@ -321,8 +310,9 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ) { qc.mcrz(PI / 4, {1, 2}, 0); auto qcPrime = qc::QuantumComputation(3); - qcPrime.mcrz(PI / 4, {1, 2}, 0); - + qcPrime.h(0); + qcPrime.mcrx(PI / 4, {1, 2}, 0); + qcPrime.h(0); checkEquivalence(qc, qcPrime, {0, 1, 2}); } @@ -474,4 +464,109 @@ TEST_F(ZXFunctionalityTest, XXminusYY) { checkEquivalence(qc, qcPrime, {0, 1}); } + +TEST_F(ZXFunctionalityTest, SWAP) { + qc = qc::QuantumComputation(2); + qc.swap(0, 1); + + auto qcPrime = qc::QuantumComputation(2); + qcPrime.cx(1, 0); + qcPrime.cx(0, 1); + qcPrime.cx(1, 0); + + checkEquivalence(qc, qcPrime, {0, 1}); +} + +TEST_F(ZXFunctionalityTest, CSWAP) { + qc = qc::QuantumComputation(3); + qc.mcswap({0}, 1, 2); + + auto qcPrime = qc::QuantumComputation(3); + qcPrime.mcx({0, 1}, 2); + qcPrime.mcx({0, 2}, 1); + qcPrime.mcx({0, 1}, 2); + + checkEquivalence(qc, qcPrime, {0, 1, 2}); +} + +TEST_F(ZXFunctionalityTest, MCSWAP) { + qc = qc::QuantumComputation(4); + qc.mcswap({0, 1}, 2, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mcx({0, 1, 2}, 3); + qcPrime.mcx({0, 1, 3}, 2); + qcPrime.mcx({0, 1, 2}, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + +TEST_F(ZXFunctionalityTest, MCRzz) { + qc = qc::QuantumComputation(4); + qc.mcrzz(qc::PI_2/3, {0, 1}, 2, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mcrzz(qc::PI_2/3, {0, 1}, 2, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + +TEST_F(ZXFunctionalityTest, MCRxx) { + qc = qc::QuantumComputation(4); + qc.mcrxx(qc::PI_2, {0, 1}, 2, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mcrxx(qc::PI_2, {0, 1}, 2, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + +TEST_F(ZXFunctionalityTest, MCRzx) { + qc = qc::QuantumComputation(4); + qc.mcrzx(qc::PI_2, {0, 1}, 2, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mcrzx(qc::PI_2, {0, 1}, 2, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + +TEST_F(ZXFunctionalityTest, MCRx) { + qc = qc::QuantumComputation(3); + qc.mcrx(PI / 4, {0, 1}, 2); + + auto qcPrime = qc::QuantumComputation(3); + qcPrime.mcrx(PI / 4, {0, 1}, 2); + checkEquivalence(qc, qcPrime, {0, 1, 2}); +} + +TEST_F(ZXFunctionalityTest, MCPhase) { + qc = qc::QuantumComputation(4); + qc.mcp(PI / 2, {0, 1, 2}, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mcp(PI / 2, {0, 1, 2}, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + +TEST_F(ZXFunctionalityTest, MCS) { + qc = qc::QuantumComputation(4); + qc.mcs({0, 1, 2}, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mcs({0, 1, 2}, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + +TEST_F(ZXFunctionalityTest, MCT) { + qc = qc::QuantumComputation(4); + qc.mct({0, 1, 2}, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mct({0, 1, 2}, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} } // namespace zx From 67b8fc2d6f3ed82665cf07265e4f0f6547c71036 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:17:44 +0000 Subject: [PATCH 45/73] =?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 --- src/zx/FunctionalityConstruction.cpp | 14 ++++++++++---- test/zx/test_zx_functionality.cpp | 6 +++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 7588c29fd7..47f9163b33 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -240,7 +240,10 @@ void FunctionalityConstruction::addMcrzz( const std::optional& unconvertedPhase) { addRzz(diag, phase, target, target2, qubits, unconvertedPhase); addMcx(diag, controls, target, qubits); - addRzz(diag, -phase, target, target2, qubits, unconvertedPhase.has_value() ? std::optional(-unconvertedPhase.value()) : std::nullopt); + addRzz(diag, -phase, target, target2, qubits, + unconvertedPhase.has_value() + ? std::optional(-unconvertedPhase.value()) + : std::nullopt); addMcx(diag, controls, target, qubits); } @@ -776,7 +779,8 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, qubits); break; case qc::OpType::S: - addMcphase(diag, PiExpression{PiRational(1, 2)}, {ctrl0, ctrl1}, target, qubits); + addMcphase(diag, PiExpression{PiRational(1, 2)}, {ctrl0, ctrl1}, target, + qubits); break; case qc::OpType::RZ: addMcrz(diag, parseParam(op.get(), 0), {ctrl0, ctrl1}, target, qubits); @@ -806,10 +810,12 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, addMcphase(diag, parseParam(op.get(), 0), controls, target, qubits); break; case qc::OpType::T: - addMcphase(diag, PiExpression{PiRational(1, 4)}, controls, target, qubits); + addMcphase(diag, PiExpression{PiRational(1, 4)}, controls, target, + qubits); break; case qc::OpType::S: - addMcphase(diag, PiExpression{PiRational(1, 2)}, controls, target, qubits); + addMcphase(diag, PiExpression{PiRational(1, 2)}, controls, target, + qubits); break; case qc::OpType::RZ: addMcrz(diag, parseParam(op.get(), 0), controls, target, qubits); diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 2fd8eb6a2a..0a34aba4b1 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -37,7 +37,7 @@ class ZXFunctionalityTest : public ::testing::Test { static void checkEquivalence(const qc::QuantumComputation& qc1, const qc::QuantumComputation& qc2, - const std::vector& qubits) { + const std::vector& qubits) { EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc1)); EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc2)); EXPECT_EQ(qc1.getNqubits(), qc2.getNqubits()); @@ -503,10 +503,10 @@ TEST_F(ZXFunctionalityTest, MCSWAP) { TEST_F(ZXFunctionalityTest, MCRzz) { qc = qc::QuantumComputation(4); - qc.mcrzz(qc::PI_2/3, {0, 1}, 2, 3); + qc.mcrzz(qc::PI_2 / 3, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); - qcPrime.mcrzz(qc::PI_2/3, {0, 1}, 2, 3); + qcPrime.mcrzz(qc::PI_2 / 3, {0, 1}, 2, 3); checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } From 440ff42bb5ef53d018d17fb0d1e1bb40cdc7b0db Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 19 Feb 2026 16:39:18 +0100 Subject: [PATCH 46/73] Extend test coverage --- src/zx/FunctionalityConstruction.cpp | 1 + test/zx/test_zx_functionality.cpp | 96 +++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 17 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 47f9163b33..293f9f6937 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -986,6 +986,7 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { case qc::OpType::S: case qc::OpType::Tdg: case qc::OpType::Sdg: + case qc::OpType::RX: case qc::OpType::RZ: return true; default: diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 0a34aba4b1..585e7ec680 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -224,7 +224,7 @@ TEST_F(ZXFunctionalityTest, CCZ) { checkEquivalence(qc, qcPrime, {0, 1, 2}); } -TEST_F(ZXFunctionalityTest, MultiControlX) { +TEST_F(ZXFunctionalityTest, MCX) { qc = qc::QuantumComputation(4); qc.mcx({1, 2, 3}, 0); @@ -235,7 +235,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX) { checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } -TEST_F(ZXFunctionalityTest, MultiControlX0) { +TEST_F(ZXFunctionalityTest, MCX0) { qc = qc::QuantumComputation(1); qc.mcx({}, 0); @@ -246,7 +246,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX0) { checkEquivalence(qc, qcPrime, {0}); } -TEST_F(ZXFunctionalityTest, MultiControlX1) { +TEST_F(ZXFunctionalityTest, MCX1) { qc = qc::QuantumComputation(2); qc.mcx({1}, 0); @@ -258,7 +258,7 @@ TEST_F(ZXFunctionalityTest, MultiControlX1) { checkEquivalence(qc, qcPrime, {0, 1}); } -TEST_F(ZXFunctionalityTest, MultiControlZ) { +TEST_F(ZXFunctionalityTest, MCZ) { qc = qc::QuantumComputation(4); qc.mcz({1, 2, 3}, 0); @@ -269,7 +269,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ) { checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } -TEST_F(ZXFunctionalityTest, MultiControlZ0) { +TEST_F(ZXFunctionalityTest, MCZ0) { qc = qc::QuantumComputation(1); qc.mcz({}, 0); @@ -280,7 +280,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ0) { checkEquivalence(qc, qcPrime, {0}); } -TEST_F(ZXFunctionalityTest, MultiControlZ1) { +TEST_F(ZXFunctionalityTest, MCZ1) { qc = qc::QuantumComputation(2); qc.mcz({1}, 0); @@ -291,7 +291,7 @@ TEST_F(ZXFunctionalityTest, MultiControlZ1) { checkEquivalence(qc, qcPrime, {0, 1}); } -TEST_F(ZXFunctionalityTest, MultiControlZ2) { +TEST_F(ZXFunctionalityTest, MCZ2) { qc = qc::QuantumComputation(4); qc.mcz({1, 2}, 0); @@ -304,19 +304,19 @@ TEST_F(ZXFunctionalityTest, MultiControlZ2) { checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } -TEST_F(ZXFunctionalityTest, MultiControlRZ) { +TEST_F(ZXFunctionalityTest, MCRZ) { - qc = qc::QuantumComputation(3); - qc.mcrz(PI / 4, {1, 2}, 0); + qc = qc::QuantumComputation(4); + qc.mcrz(PI / 4, {1, 2, 3}, 0); - auto qcPrime = qc::QuantumComputation(3); + auto qcPrime = qc::QuantumComputation(4); qcPrime.h(0); - qcPrime.mcrx(PI / 4, {1, 2}, 0); + qcPrime.mcrx(PI / 4, {1, 2, 3}, 0); qcPrime.h(0); - checkEquivalence(qc, qcPrime, {0, 1, 2}); + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } -TEST_F(ZXFunctionalityTest, MultiControlRZ0) { +TEST_F(ZXFunctionalityTest, MCRZ0) { qc = qc::QuantumComputation(1); qc.mcrz(PI / 4, {}, 0); @@ -327,7 +327,7 @@ TEST_F(ZXFunctionalityTest, MultiControlRZ0) { checkEquivalence(qc, qcPrime, {0}); } -TEST_F(ZXFunctionalityTest, MultiControlRZ1) { +TEST_F(ZXFunctionalityTest, MCRZ1) { qc = qc::QuantumComputation(2); qc.mcrz(PI / 4, {1}, 0); @@ -503,7 +503,8 @@ TEST_F(ZXFunctionalityTest, MCSWAP) { TEST_F(ZXFunctionalityTest, MCRzz) { qc = qc::QuantumComputation(4); - qc.mcrzz(qc::PI_2 / 3, {0, 1}, 2, 3); + qc.mcrzz(qc::PI_2/3, {0, 1}, 2, 3); + qc.mcrzz(2*qc::PI, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); qcPrime.mcrzz(qc::PI_2 / 3, {0, 1}, 2, 3); @@ -514,6 +515,7 @@ TEST_F(ZXFunctionalityTest, MCRzz) { TEST_F(ZXFunctionalityTest, MCRxx) { qc = qc::QuantumComputation(4); qc.mcrxx(qc::PI_2, {0, 1}, 2, 3); + qc.mcrzx(2*qc::PI, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); qcPrime.mcrxx(qc::PI_2, {0, 1}, 2, 3); @@ -524,6 +526,7 @@ TEST_F(ZXFunctionalityTest, MCRxx) { TEST_F(ZXFunctionalityTest, MCRzx) { qc = qc::QuantumComputation(4); qc.mcrzx(qc::PI_2, {0, 1}, 2, 3); + qc.mcrzx(2*qc::PI, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); qcPrime.mcrzx(qc::PI_2, {0, 1}, 2, 3); @@ -531,7 +534,27 @@ TEST_F(ZXFunctionalityTest, MCRzx) { checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } -TEST_F(ZXFunctionalityTest, MCRx) { +TEST_F(ZXFunctionalityTest, MCRx0) { + qc = qc::QuantumComputation(1); + qc.mcrx(PI / 4, {}, 0); + + auto qcPrime = qc::QuantumComputation(1); + qcPrime.mcrx(PI / 4, {}, 0); + checkEquivalence(qc, qcPrime, {0}); +} + + +TEST_F(ZXFunctionalityTest, MCRx1) { + qc = qc::QuantumComputation(2); + qc.mcrx(PI / 4, {0}, 1); + + auto qcPrime = qc::QuantumComputation(2); + qcPrime.mcrx(PI / 4, {0}, 1); + checkEquivalence(qc, qcPrime, {0, 1}); +} + + +TEST_F(ZXFunctionalityTest, MCRx2) { qc = qc::QuantumComputation(3); qc.mcrx(PI / 4, {0, 1}, 2); @@ -540,6 +563,15 @@ TEST_F(ZXFunctionalityTest, MCRx) { checkEquivalence(qc, qcPrime, {0, 1, 2}); } +TEST_F(ZXFunctionalityTest, MCRx3) { + qc = qc::QuantumComputation(4); + qc.mcrx(PI / 4, {0, 1, 2}, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mcrx(PI / 4, {0, 1, 2}, 3); + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + TEST_F(ZXFunctionalityTest, MCPhase) { qc = qc::QuantumComputation(4); qc.mcp(PI / 2, {0, 1, 2}, 3); @@ -569,4 +601,34 @@ TEST_F(ZXFunctionalityTest, MCT) { checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } + +TEST_F(ZXFunctionalityTest, MCPhase2) { + qc = qc::QuantumComputation(3); + qc.mcp(PI / 2, {0, 1}, 2); + + auto qcPrime = qc::QuantumComputation(3); + qcPrime.mcp(PI / 2, {0, 1}, 2); + + checkEquivalence(qc, qcPrime, {0, 1, 2}); +} + +TEST_F(ZXFunctionalityTest, MCS2) { + qc = qc::QuantumComputation(3); + qc.mcs({0, 1}, 2); + + auto qcPrime = qc::QuantumComputation(3); + qcPrime.mcs({0, 1}, 2); + + checkEquivalence(qc, qcPrime, {0, 1, 2}); +} + +TEST_F(ZXFunctionalityTest, MCT2) { + qc = qc::QuantumComputation(3); + qc.mct({0, 1}, 2); + + auto qcPrime = qc::QuantumComputation(3); + qcPrime.mct({0, 1}, 2); + + checkEquivalence(qc, qcPrime, {0, 1, 2}); +} } // namespace zx From e82defd03019a67a55b61b74a948ecdd0bc4cacf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:40:29 +0000 Subject: [PATCH 47/73] =?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 --- test/zx/test_zx_functionality.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 585e7ec680..809ab6634c 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -503,8 +503,8 @@ TEST_F(ZXFunctionalityTest, MCSWAP) { TEST_F(ZXFunctionalityTest, MCRzz) { qc = qc::QuantumComputation(4); - qc.mcrzz(qc::PI_2/3, {0, 1}, 2, 3); - qc.mcrzz(2*qc::PI, {0, 1}, 2, 3); + qc.mcrzz(qc::PI_2 / 3, {0, 1}, 2, 3); + qc.mcrzz(2 * qc::PI, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); qcPrime.mcrzz(qc::PI_2 / 3, {0, 1}, 2, 3); @@ -515,7 +515,7 @@ TEST_F(ZXFunctionalityTest, MCRzz) { TEST_F(ZXFunctionalityTest, MCRxx) { qc = qc::QuantumComputation(4); qc.mcrxx(qc::PI_2, {0, 1}, 2, 3); - qc.mcrzx(2*qc::PI, {0, 1}, 2, 3); + qc.mcrzx(2 * qc::PI, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); qcPrime.mcrxx(qc::PI_2, {0, 1}, 2, 3); @@ -526,7 +526,7 @@ TEST_F(ZXFunctionalityTest, MCRxx) { TEST_F(ZXFunctionalityTest, MCRzx) { qc = qc::QuantumComputation(4); qc.mcrzx(qc::PI_2, {0, 1}, 2, 3); - qc.mcrzx(2*qc::PI, {0, 1}, 2, 3); + qc.mcrzx(2 * qc::PI, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); qcPrime.mcrzx(qc::PI_2, {0, 1}, 2, 3); @@ -543,7 +543,6 @@ TEST_F(ZXFunctionalityTest, MCRx0) { checkEquivalence(qc, qcPrime, {0}); } - TEST_F(ZXFunctionalityTest, MCRx1) { qc = qc::QuantumComputation(2); qc.mcrx(PI / 4, {0}, 1); @@ -553,7 +552,6 @@ TEST_F(ZXFunctionalityTest, MCRx1) { checkEquivalence(qc, qcPrime, {0, 1}); } - TEST_F(ZXFunctionalityTest, MCRx2) { qc = qc::QuantumComputation(3); qc.mcrx(PI / 4, {0, 1}, 2); From 1f224ce5786a3271343afe211c54259f670e132f Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 19 Feb 2026 17:01:48 +0100 Subject: [PATCH 48/73] Apply coderabbit suggestions --- src/zx/FunctionalityConstruction.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 293f9f6937..c494a0de8d 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -400,7 +400,7 @@ void FunctionalityConstruction::addMcrx(ZXDiagram& diag, const Qubit target, std::vector& qubits) { addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addMcrz(diag, phase, controls, target, qubits); + addMcrz(diag, phase, std::move(controls), target, qubits); addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); } @@ -448,7 +448,7 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, default: const auto half = static_cast((controls.size() + 1) / 2); const std::vector first(controls.begin(), controls.begin() + half); - std::vector second(controls.begin() + half, controls.end()); + const std::vector second(controls.begin() + half, controls.end()); addRx(diag, PiExpression(PiRational(1, 4)), target, qubits); addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); @@ -478,7 +478,7 @@ void FunctionalityConstruction::addMcz(ZXDiagram& diag, const Qubit target, std::vector& qubits) { addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addMcx(diag, controls, target, qubits); + addMcx(diag, std::move(controls), target, qubits); addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); } @@ -828,7 +828,9 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, std::to_string(op->getNcontrols()) + " ctrls)" + qc::toString(op->getType())); } - } else { + } else if (op->getNtargets() == 2) { + // At this point, op must have getNtargets() >= 2 (all 1-target cases + // handled above) const auto target = static_cast(p.at(op->getTargets().front())); const auto target2 = static_cast(p.at(op->getTargets()[1])); std::vector controls; @@ -881,6 +883,10 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, std::to_string(op->getNcontrols()) + " ctrls)" + qc::toString(op->getType())); } + } else { + throw ZXException("Unsupported Multi-control operation (" + + std::to_string(op->getNcontrols()) + " ctrls)" + + qc::toString(op->getType())); } return it + 1; } From 18a8aa1ee2e4852b4c30ecb3faf4cc3dba2f45dd Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 19 Feb 2026 19:53:12 +0100 Subject: [PATCH 49/73] Update per clang-tidy --- test/zx/test_zx_functionality.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 809ab6634c..299b878c09 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -28,13 +28,14 @@ #include #include #include +#include namespace zx { class ZXFunctionalityTest : public ::testing::Test { public: qc::QuantumComputation qc; }; - +namespace { static void checkEquivalence(const qc::QuantumComputation& qc1, const qc::QuantumComputation& qc2, const std::vector& qubits) { @@ -53,6 +54,7 @@ static void checkEquivalence(const qc::QuantumComputation& qc1, EXPECT_TRUE(d1.connected(d1.getInput(q), d1.getOutput(q))); } } +} // namespace TEST_F(ZXFunctionalityTest, parseQasm) { const std::string testfile = "OPENQASM 2.0;" From c4c524cb04ab28d5b27bd3c39ac70c6f04c98815 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 19 Feb 2026 19:58:00 +0100 Subject: [PATCH 50/73] Remove static from checkEquivalence --- test/zx/test_zx_functionality.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 299b878c09..ee49b7fe7c 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -36,9 +36,9 @@ class ZXFunctionalityTest : public ::testing::Test { qc::QuantumComputation qc; }; namespace { -static void checkEquivalence(const qc::QuantumComputation& qc1, - const qc::QuantumComputation& qc2, - const std::vector& qubits) { +void checkEquivalence(const qc::QuantumComputation& qc1, + const qc::QuantumComputation& qc2, + const std::vector& qubits) { EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc1)); EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc2)); EXPECT_EQ(qc1.getNqubits(), qc2.getNqubits()); From e8eb9ef2e8d30b5425fa9d16bdf2b990fb417fba Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:32:35 +0200 Subject: [PATCH 51/73] Swap order for consistency Co-authored-by: Lukas Burgholzer Signed-off-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> --- src/zx/FunctionalityConstruction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 8efea0e1bb..9934534c3d 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -409,9 +409,9 @@ void FunctionalityConstruction::addCrz(ZXDiagram& diag, const Qubit q0, const Qubit q1, std::vector& qubits) { // CRZ decomposition uses reversed CNOT direction + addZSpider(diag, q1, qubits, phase / 2); addCnot(diag, q1, q0, qubits); addZSpider(diag, q0, qubits, -phase / 2); - addZSpider(diag, q1, qubits, phase / 2); addCnot(diag, q1, q0, qubits); } From 9706f1dc56e8d372c24bc917470135cabc8b1718 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:34:13 +0200 Subject: [PATCH 52/73] Streamline empty lines --- src/zx/FunctionalityConstruction.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 9934534c3d..0f8b1b81f7 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -366,7 +366,6 @@ void FunctionalityConstruction::addCcx(ZXDiagram& diag, const Qubit ctrl0, void FunctionalityConstruction::addCcz(ZXDiagram& diag, const Qubit ctrl0, const Qubit ctrl1, const Qubit target, std::vector& qubits) { - addCnot(diag, ctrl1, target, qubits); addZSpider(diag, target, qubits, PiExpression(PiRational(-1, 4))); addCnot(diag, ctrl0, target, qubits); @@ -420,7 +419,6 @@ void FunctionalityConstruction::addMcrz(ZXDiagram& diag, std::vector controls, const Qubit target, std::vector& qubits) { - const Qubit nextControl = controls.back(); controls.pop_back(); @@ -434,7 +432,6 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, std::vector controls, const Qubit target, std::vector& qubits) { - switch (controls.size()) { case 0: addXSpider(diag, target, qubits, PiExpression(PiRational(1, 1))); @@ -925,6 +922,7 @@ ZXDiagram FunctionalityConstruction::buildFunctionality( } return diag; } + bool FunctionalityConstruction::transformableToZX( const qc::QuantumComputation* qc) { return std::ranges::all_of( @@ -1032,6 +1030,7 @@ PiExpression FunctionalityConstruction::parseParam(const qc::Operation* op, } return PiExpression{PiRational{op->getParameter().at(i)}}; } + PiExpression FunctionalityConstruction::toPiExpr(const qc::SymbolOrNumber& param) { if (std::holds_alternative(param)) { @@ -1039,4 +1038,5 @@ FunctionalityConstruction::toPiExpr(const qc::SymbolOrNumber& param) { } return std::get(param).convert(); } + } // namespace zx From 1ecc9300619d07a13e3e0fa380e1fa26955088ae Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:39:19 +0200 Subject: [PATCH 53/73] Avoid duplicate anonymous namespace --- test/zx/test_zx_functionality.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 652b2e4901..8a5faea04c 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -39,7 +39,7 @@ class ZXFunctionalityTest : public ::testing::Test { public: qc::QuantumComputation qc; }; -namespace { + void checkEquivalence(const qc::QuantumComputation& qc1, const qc::QuantumComputation& qc2, const std::vector& qubits) { @@ -58,7 +58,6 @@ void checkEquivalence(const qc::QuantumComputation& qc1, EXPECT_TRUE(d1.connected(d1.getInput(q), d1.getOutput(q))); } } -} // namespace } // namespace From f6497e16102639c3187880d6725b9fbac0eb156e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:43:56 +0200 Subject: [PATCH 54/73] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cf9efb0be..91cf031f82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add support for multi-controlled gates to ZX package ([#1380]) ([**@keefehuang**]) - ✨ Add Sampler and Estimator Primitives to the QDMI-Qiskit Interface ([#1507]) ([**@marcelwa**]) - ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637]) ([**@denialhaag**]) - ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588]) ([**@MatthiasReumann**]) @@ -403,6 +404,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1383]: https://github.com/munich-quantum-toolkit/core/pull/1383 [#1382]: https://github.com/munich-quantum-toolkit/core/pull/1382 [#1381]: https://github.com/munich-quantum-toolkit/core/pull/1381 +[#1380]: https://github.com/munich-quantum-toolkit/core/pull/1380 [#1378]: https://github.com/munich-quantum-toolkit/core/pull/1378 [#1375]: https://github.com/munich-quantum-toolkit/core/pull/1375 [#1371]: https://github.com/munich-quantum-toolkit/core/pull/1371 @@ -551,6 +553,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [**@lirem101**]: https://github.com/lirem101 [**@Ectras**]: https://github.com/Ectras [**@simon1hofmann**]: https://github.com/simon1hofmann +[**@keefehuang**]: https://github.com/keefehuang From 175b5da95762bf75788e18cd591104edb0ce0bce Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:55:48 +0200 Subject: [PATCH 55/73] Improve comment --- src/zx/FunctionalityConstruction.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 0f8b1b81f7..4440c94776 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -826,8 +826,8 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, qc::toString(op->getType())); } } else if (op->getNtargets() == 2) { - // At this point, op must have getNtargets() >= 2 (all 1-target cases - // handled above) + // at this point, op must have getNtargets() == 2 + // all 1-target cases handled above const auto target = static_cast(p.at(op->getTargets().front())); const auto target2 = static_cast(p.at(op->getTargets()[1])); std::vector controls; From 4220feaf08e3737942a9f96ed797c227dc009606 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 08:11:30 +0200 Subject: [PATCH 56/73] UPdate to simpler SWAP implementation --- src/zx/FunctionalityConstruction.cpp | 15 ++++++--------- test/zx/test_zx_functionality.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 4440c94776..89aaccb821 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -331,15 +331,12 @@ void FunctionalityConstruction::addMcswap(ZXDiagram& diag, const Qubit target, const Qubit target2, std::vector& qubits) { - std::vector controls1 = controls; - std::vector controls2 = controls; - - controls1.emplace_back(target); - controls2.emplace_back(target2); - - addMcx(diag, controls1, target2, qubits); - addMcx(diag, controls2, target, qubits); - addMcx(diag, controls1, target2, qubits); + + std::vector controls = controls; + controls.emplace_back(target2); + addCnot(diag, target, target2, qubits); + addMcx(diag, controls, target, qubits); + addCnot(diag, target, target2, qubits); } void FunctionalityConstruction::addCcx(ZXDiagram& diag, const Qubit ctrl0, diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 8a5faea04c..6bd3e210f0 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -489,9 +489,9 @@ TEST_F(ZXFunctionalityTest, CSWAP) { qc.mcswap({0}, 1, 2); auto qcPrime = qc::QuantumComputation(3); - qcPrime.mcx({0, 1}, 2); + qcPrime.cx(1, 2); qcPrime.mcx({0, 2}, 1); - qcPrime.mcx({0, 1}, 2); + qcPrime.cx(1, 2); checkEquivalence(qc, qcPrime, {0, 1, 2}); } @@ -501,9 +501,9 @@ TEST_F(ZXFunctionalityTest, MCSWAP) { qc.mcswap({0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); - qcPrime.mcx({0, 1, 2}, 3); + qcPrime.cx(2, 3); qcPrime.mcx({0, 1, 3}, 2); - qcPrime.mcx({0, 1, 2}, 3); + qcPrime.cx(2, 3); checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } From 0e59ff5c386f03ae5010ce509999b16dfa7f1a8f Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 08:11:43 +0200 Subject: [PATCH 57/73] Remove unnecessary H applications --- src/zx/FunctionalityConstruction.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 89aaccb821..f5c577a1fb 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -598,27 +598,21 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, case qc::OpType::RXX: { const auto target2 = static_cast(p.at(op->getTargets()[1])); const auto& phase = parseParam(op.get(), 0); - addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); if (phase.isConstant()) { addRxx(diag, phase, target, target2, qubits, op->getParameter().at(0)); } else { addRxx(diag, phase, target, target2, qubits); } - addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); break; } case qc::OpType::RZX: { const auto target2 = static_cast(p.at(op->getTargets()[1])); const auto& phase = parseParam(op.get(), 0); - addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); if (phase.isConstant()) { addRzx(diag, phase, target, target2, qubits, op->getParameter().at(0)); } else { addRzx(diag, phase, target, target2, qubits); } - addZSpider(diag, target2, qubits, PiExpression(), EdgeType::Hadamard); break; } case qc::OpType::RYY: { From 1fd0595e1a94ab956f1a3121c14b7e648e9a9b2a Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 08:12:13 +0200 Subject: [PATCH 58/73] Fix formatting --- src/zx/FunctionalityConstruction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index f5c577a1fb..8b206810ea 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -331,7 +331,7 @@ void FunctionalityConstruction::addMcswap(ZXDiagram& diag, const Qubit target, const Qubit target2, std::vector& qubits) { - + std::vector controls = controls; controls.emplace_back(target2); addCnot(diag, target, target2, qubits); From 8599775057aaa03591fe0d3bc4b2bd75a6638001 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 08:16:56 +0200 Subject: [PATCH 59/73] Rename to prevent shadowing --- src/zx/FunctionalityConstruction.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 8b206810ea..117cbca3fe 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -332,10 +332,11 @@ void FunctionalityConstruction::addMcswap(ZXDiagram& diag, const Qubit target2, std::vector& qubits) { - std::vector controls = controls; - controls.emplace_back(target2); + std::vector mcx_controls = controls; + mcx_controls.emplace_back(target2); + addCnot(diag, target, target2, qubits); - addMcx(diag, controls, target, qubits); + addMcx(diag, mcx_controls, target, qubits); addCnot(diag, target, target2, qubits); } From e9b3a8ec612d5743f8c0b8b0bffb4532a0a88838 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 08:48:38 +0200 Subject: [PATCH 60/73] Addressing code rabbit suggestions --- test/zx/test_zx_functionality.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 6bd3e210f0..721915fdf0 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -521,22 +521,22 @@ TEST_F(ZXFunctionalityTest, MCRzz) { TEST_F(ZXFunctionalityTest, MCRxx) { qc = qc::QuantumComputation(4); - qc.mcrxx(qc::PI_2, {0, 1}, 2, 3); + qc.mcrxx(qc::PI_2 / 3, {0, 1}, 2, 3); qc.mcrzx(2 * qc::PI, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); - qcPrime.mcrxx(qc::PI_2, {0, 1}, 2, 3); + qcPrime.mcrxx(qc::PI_2 / 3, {0, 1}, 2, 3); checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } TEST_F(ZXFunctionalityTest, MCRzx) { qc = qc::QuantumComputation(4); - qc.mcrzx(qc::PI_2, {0, 1}, 2, 3); + qc.mcrzx(qc::PI_2 / 3, {0, 1}, 2, 3); qc.mcrzx(2 * qc::PI, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); - qcPrime.mcrzx(qc::PI_2, {0, 1}, 2, 3); + qcPrime.mcrzx(qc::PI_2 / 3, {0, 1}, 2, 3); checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } From f493bb71dc5d72dfcc2ea819a99c8b62bc3587a3 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 08:48:56 +0200 Subject: [PATCH 61/73] Fix angle of MCRzz --- src/zx/FunctionalityConstruction.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 117cbca3fe..547030f735 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -238,9 +238,9 @@ void FunctionalityConstruction::addMcrzz( const std::vector& controls, const Qubit target, const Qubit target2, std::vector& qubits, const std::optional& unconvertedPhase) { - addRzz(diag, phase, target, target2, qubits, unconvertedPhase); + addRzz(diag, phase / 2, target, target2, qubits, unconvertedPhase); addMcx(diag, controls, target, qubits); - addRzz(diag, -phase, target, target2, qubits, + addRzz(diag, -phase / 2, target, target2, qubits, unconvertedPhase.has_value() ? std::optional(-unconvertedPhase.value()) : std::nullopt); From 676b19a4c54db844abf8f6e318df5981481f3ad6 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 08:51:30 +0200 Subject: [PATCH 62/73] Update test to be consistent --- test/zx/test_zx_functionality.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 721915fdf0..0d301d4c76 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -522,7 +522,7 @@ TEST_F(ZXFunctionalityTest, MCRzz) { TEST_F(ZXFunctionalityTest, MCRxx) { qc = qc::QuantumComputation(4); qc.mcrxx(qc::PI_2 / 3, {0, 1}, 2, 3); - qc.mcrzx(2 * qc::PI, {0, 1}, 2, 3); + qc.mcrxx(2 * qc::PI, {0, 1}, 2, 3); auto qcPrime = qc::QuantumComputation(4); qcPrime.mcrxx(qc::PI_2 / 3, {0, 1}, 2, 3); From e39cc6f774bf57c467d6c1135d62e81f23851ea1 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Sun, 19 Apr 2026 11:15:52 +0200 Subject: [PATCH 63/73] =?UTF-8?q?=F0=9F=9A=A8=20Address=20linter=20warning?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lukas Burgholzer --- .../mqt-core/zx/FunctionalityConstruction.hpp | 4 ++-- src/zx/FunctionalityConstruction.cpp | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/mqt-core/zx/FunctionalityConstruction.hpp b/include/mqt-core/zx/FunctionalityConstruction.hpp index caf8c6b1b9..422d47b5f5 100644 --- a/include/mqt-core/zx/FunctionalityConstruction.hpp +++ b/include/mqt-core/zx/FunctionalityConstruction.hpp @@ -88,10 +88,10 @@ class FunctionalityConstruction { static void addMcphase(ZXDiagram& diag, const PiExpression& phase, const std::vector& controls, Qubit target, std::vector& qubits); - static void addSwap(ZXDiagram& diag, Qubit target, Qubit target2, + static void addSwap(ZXDiagram& diag, Qubit target1, Qubit target2, std::vector& qubits); static void addMcswap(ZXDiagram& diag, const std::vector& controls, - Qubit target, Qubit target2, + Qubit target1, Qubit target2, std::vector& qubits); static void addRzz(ZXDiagram& diag, const PiExpression& phase, Qubit target, diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 547030f735..511e100443 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -303,10 +303,10 @@ void FunctionalityConstruction::addXXminusYY( addRz(diag, PiExpression(-PiRational(1, 2)), qubit1, qubits); } -void FunctionalityConstruction::addSwap(ZXDiagram& diag, const Qubit target, +void FunctionalityConstruction::addSwap(ZXDiagram& diag, const Qubit target1, const Qubit target2, std::vector& qubits) { - const auto c = static_cast(target); + const auto c = static_cast(target1); const auto t = static_cast(target2); const auto s0 = qubits[t]; @@ -319,7 +319,7 @@ void FunctionalityConstruction::addSwap(ZXDiagram& diag, const Qubit target, const auto col = vData->col + 1; const auto t0 = diag.addVertex(target2, col); - const auto t1 = diag.addVertex(target, col); + const auto t1 = diag.addVertex(target1, col); diag.addEdge(s0, t1); diag.addEdge(s1, t0); qubits[t] = t0; @@ -328,16 +328,16 @@ void FunctionalityConstruction::addSwap(ZXDiagram& diag, const Qubit target, void FunctionalityConstruction::addMcswap(ZXDiagram& diag, const std::vector& controls, - const Qubit target, + const Qubit target1, const Qubit target2, std::vector& qubits) { - std::vector mcx_controls = controls; - mcx_controls.emplace_back(target2); + std::vector mcxControls = controls; + mcxControls.emplace_back(target2); - addCnot(diag, target, target2, qubits); - addMcx(diag, mcx_controls, target, qubits); - addCnot(diag, target, target2, qubits); + addCnot(diag, target1, target2, qubits); + addMcx(diag, mcxControls, target1, qubits); + addCnot(diag, target1, target2, qubits); } void FunctionalityConstruction::addCcx(ZXDiagram& diag, const Qubit ctrl0, From 2cad394aac5e18ed4aab77b9c52822ebb876b480 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 19 Apr 2026 11:20:50 -0400 Subject: [PATCH 64/73] Address the Rabbit's comments --- .../mqt-core/zx/FunctionalityConstruction.hpp | 26 +++++++++---------- src/zx/FunctionalityConstruction.cpp | 11 +++----- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/include/mqt-core/zx/FunctionalityConstruction.hpp b/include/mqt-core/zx/FunctionalityConstruction.hpp index 422d47b5f5..ab4a9e6f14 100644 --- a/include/mqt-core/zx/FunctionalityConstruction.hpp +++ b/include/mqt-core/zx/FunctionalityConstruction.hpp @@ -107,8 +107,8 @@ class FunctionalityConstruction { const std::optional& unconvertedPhase = std::nullopt); static void addMcrzz(ZXDiagram& diag, const PiExpression& phase, - const std::vector& controls, const Qubit target, - const Qubit target2, std::vector& qubits, + const std::vector& controls, Qubit target, Qubit target2, + std::vector& qubits, const std::optional& unconvertedPhase = std::nullopt); static void addDcx(ZXDiagram& diag, Qubit qubit1, Qubit qubit2, std::vector& qubits); @@ -126,22 +126,20 @@ class FunctionalityConstruction { std::vector& qubits); static void addCcz(ZXDiagram& diag, Qubit ctrl0, Qubit ctrl1, Qubit target, std::vector& qubits); - static void addCrx(ZXDiagram& diag, const PiExpression& phase, - const Qubit control, const Qubit target, - std::vector& qubits); + static void addCrx(ZXDiagram& diag, const PiExpression& phase, Qubit control, + Qubit target, std::vector& qubits); static void addMcrx(ZXDiagram& diag, const PiExpression& phase, - std::vector controls, const Qubit target, + const std::vector& controls, Qubit target, std::vector& qubits); - static void addCrz(ZXDiagram& diag, const PiExpression& phase, - const Qubit control, const Qubit target, - std::vector& qubits); + static void addCrz(ZXDiagram& diag, const PiExpression& phase, Qubit control, + Qubit target, std::vector& qubits); static void addMcrz(ZXDiagram& diag, const PiExpression& phase, - std::vector controls, const Qubit target, + std::vector controls, Qubit target, std::vector& qubits); - static void addMcx(ZXDiagram& diag, std::vector controls, - const Qubit target, std::vector& qubits); - static void addMcz(ZXDiagram& diag, std::vector controls, - const Qubit target, std::vector& qubits); + static void addMcx(ZXDiagram& diag, std::vector controls, Qubit target, + std::vector& qubits); + static void addMcz(ZXDiagram& diag, const std::vector& controls, + Qubit target, std::vector& qubits); static op_it parseOp(ZXDiagram& diag, op_it it, op_it end, std::vector& qubits, const qc::Permutation& p); static op_it parseCompoundOp(ZXDiagram& diag, op_it it, op_it end, diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 511e100443..06f11fa1dc 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -376,10 +376,7 @@ void FunctionalityConstruction::addCcz(ZXDiagram& diag, const Qubit ctrl0, addCnot(diag, ctrl0, ctrl1, qubits); addZSpider(diag, ctrl0, qubits, PiExpression(PiRational(1, 4))); addZSpider(diag, ctrl1, qubits, PiExpression(PiRational(-1, 4))); - addZSpider(diag, target, qubits, PiExpression(PiRational(0, 1)), - EdgeType::Hadamard); addCnot(diag, ctrl0, ctrl1, qubits); - addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); } void FunctionalityConstruction::addCrx(ZXDiagram& diag, @@ -393,11 +390,11 @@ void FunctionalityConstruction::addCrx(ZXDiagram& diag, void FunctionalityConstruction::addMcrx(ZXDiagram& diag, const PiExpression& phase, - std::vector controls, + const std::vector& controls, const Qubit target, std::vector& qubits) { addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addMcrz(diag, phase, std::move(controls), target, qubits); + addMcrz(diag, phase, controls, target, qubits); addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); } @@ -469,11 +466,11 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, } void FunctionalityConstruction::addMcz(ZXDiagram& diag, - std::vector controls, + const std::vector& controls, const Qubit target, std::vector& qubits) { addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addMcx(diag, std::move(controls), target, qubits); + addMcx(diag, controls, target, qubits); addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); } From 53dbd132e316226c44f0ef36c36dedf9073b0d6d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:21:56 -0400 Subject: [PATCH 65/73] Address the Rabbit's comments --- src/zx/FunctionalityConstruction.cpp | 40 +++++++++++++++------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 06f11fa1dc..2974f8e94b 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -238,11 +238,14 @@ void FunctionalityConstruction::addMcrzz( const std::vector& controls, const Qubit target, const Qubit target2, std::vector& qubits, const std::optional& unconvertedPhase) { - addRzz(diag, phase / 2, target, target2, qubits, unconvertedPhase); + addRzz(diag, phase / 2, target, target2, qubits, + unconvertedPhase.has_value() + ? std::optional(unconvertedPhase.value() / 2) + : std::nullopt); addMcx(diag, controls, target, qubits); addRzz(diag, -phase / 2, target, target2, qubits, unconvertedPhase.has_value() - ? std::optional(-unconvertedPhase.value()) + ? std::optional(-unconvertedPhase.value() / 2) : std::nullopt); addMcx(diag, controls, target, qubits); } @@ -400,13 +403,13 @@ void FunctionalityConstruction::addMcrx(ZXDiagram& diag, void FunctionalityConstruction::addCrz(ZXDiagram& diag, const PiExpression& phase, - const Qubit q0, const Qubit q1, + const Qubit control, const Qubit target, std::vector& qubits) { // CRZ decomposition uses reversed CNOT direction - addZSpider(diag, q1, qubits, phase / 2); - addCnot(diag, q1, q0, qubits); - addZSpider(diag, q0, qubits, -phase / 2); - addCnot(diag, q1, q0, qubits); + addZSpider(diag, target, qubits, phase / 2); + addCnot(diag, target, control, qubits); + addZSpider(diag, control, qubits, -phase / 2); + addCnot(diag, target, control, qubits); } void FunctionalityConstruction::addMcrz(ZXDiagram& diag, @@ -775,8 +778,9 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, addMcrx(diag, parseParam(op.get(), 0), {ctrl0, ctrl1}, target, qubits); break; default: - throw ZXException("Unsupported Multi-control operation: " + - qc::toString(op->getType())); + throw ZXException("Unsupported multi-control operation (" + + std::to_string(op->getNcontrols()) + + " ctrls): " + qc::toString(op->getType())); } } else if (op->getNtargets() == 1) { const auto target = static_cast(p.at(op->getTargets().front())); @@ -810,9 +814,9 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, addMcrx(diag, parseParam(op.get(), 0), controls, target, qubits); break; default: - throw ZXException("Unsupported Multi-control operation (" + - std::to_string(op->getNcontrols()) + " ctrls)" + - qc::toString(op->getType())); + throw ZXException("Unsupported multi-control operation (" + + std::to_string(op->getNcontrols()) + + " ctrls): " + qc::toString(op->getType())); } } else if (op->getNtargets() == 2) { // at this point, op must have getNtargets() == 2 @@ -865,14 +869,14 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, break; } default: - throw ZXException("Unsupported Multi-control operation (" + - std::to_string(op->getNcontrols()) + " ctrls)" + - qc::toString(op->getType())); + throw ZXException("Unsupported multi-control operation (" + + std::to_string(op->getNcontrols()) + + " ctrls): " + qc::toString(op->getType())); } } else { - throw ZXException("Unsupported Multi-control operation (" + - std::to_string(op->getNcontrols()) + " ctrls)" + - qc::toString(op->getType())); + throw ZXException("Unsupported multi-control operation (" + + std::to_string(op->getNcontrols()) + + " ctrls): " + qc::toString(op->getType())); } return it + 1; } From 27759bf638984090ecb5d952373e1303776583ad Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 20:58:45 +0200 Subject: [PATCH 66/73] Add more tests to increase coverage --- test/zx/test_zx_functionality.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 0d301d4c76..7ba1f840e1 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -345,6 +345,7 @@ TEST_F(ZXFunctionalityTest, MCRZ1) { checkEquivalence(qc, qcPrime, {0, 1}); } + TEST_F(ZXFunctionalityTest, UnsupportedControl) { qc = qc::QuantumComputation(2); @@ -365,6 +366,27 @@ TEST_F(ZXFunctionalityTest, UnsupportedControl2) { ZXException); } +TEST_F(ZXFunctionalityTest, UnsupportedControl3) { + + qc = qc::QuantumComputation(4); + qc.mcy({1, 2, 3}, 0); + EXPECT_FALSE(FunctionalityConstruction::transformableToZX(&qc)); + EXPECT_THROW(const ZXDiagram diag = + FunctionalityConstruction::buildFunctionality(&qc), + ZXException); +} + +TEST_F(ZXFunctionalityTest, UnsupportedTwoTargetControl) { + + qc = qc::QuantumComputation(4); + qc.mcdcx({2, 3}, 0, 1); + EXPECT_FALSE(FunctionalityConstruction::transformableToZX(&qc)); + EXPECT_THROW(const ZXDiagram diag = + FunctionalityConstruction::buildFunctionality(&qc), + ZXException); +} + + TEST_F(ZXFunctionalityTest, InitialLayout) { qc = qc::QuantumComputation(2); qc::Permutation layout{}; From c8da130c83d725b62b10ca305a24d023474db136 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 20:59:10 +0200 Subject: [PATCH 67/73] Remove unreachable line in addMCx, this is tested and this code is not reached --- src/zx/FunctionalityConstruction.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 2974f8e94b..c7a4280df7 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -431,9 +431,6 @@ void FunctionalityConstruction::addMcx(ZXDiagram& diag, const Qubit target, std::vector& qubits) { switch (controls.size()) { - case 0: - addXSpider(diag, target, qubits, PiExpression(PiRational(1, 1))); - return; case 1: addCnot(diag, controls.front(), target, qubits); return; From 3002186acf615b5daa4bf0593fa031c2d22b101e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:02:06 +0000 Subject: [PATCH 68/73] =?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 --- test/zx/test_zx_functionality.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 7ba1f840e1..ce4da248db 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -345,7 +345,6 @@ TEST_F(ZXFunctionalityTest, MCRZ1) { checkEquivalence(qc, qcPrime, {0, 1}); } - TEST_F(ZXFunctionalityTest, UnsupportedControl) { qc = qc::QuantumComputation(2); @@ -386,7 +385,6 @@ TEST_F(ZXFunctionalityTest, UnsupportedTwoTargetControl) { ZXException); } - TEST_F(ZXFunctionalityTest, InitialLayout) { qc = qc::QuantumComputation(2); qc::Permutation layout{}; From e8efacf4950d4f37217ea8382c7ed34fc76a9c5d Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 19 Apr 2026 21:30:56 +0200 Subject: [PATCH 69/73] Update control and target names in addCrz --- src/zx/FunctionalityConstruction.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index c7a4280df7..4a5f4c90dc 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -403,13 +403,13 @@ void FunctionalityConstruction::addMcrx(ZXDiagram& diag, void FunctionalityConstruction::addCrz(ZXDiagram& diag, const PiExpression& phase, - const Qubit control, const Qubit target, + const Qubit qubit0, const Qubit qubit1, std::vector& qubits) { // CRZ decomposition uses reversed CNOT direction - addZSpider(diag, target, qubits, phase / 2); - addCnot(diag, target, control, qubits); - addZSpider(diag, control, qubits, -phase / 2); - addCnot(diag, target, control, qubits); + addZSpider(diag, qubit1, qubits, phase / 2); + addCnot(diag, qubit1, qubit0, qubits); + addZSpider(diag, qubit0, qubits, -phase / 2); + addCnot(diag, qubit1, qubit0, qubits); } void FunctionalityConstruction::addMcrz(ZXDiagram& diag, From c54c611ce8a72b1c322d14e1f0a4b3d918d2b653 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:08:50 -0400 Subject: [PATCH 70/73] Address the Rabbit's comments --- src/zx/FunctionalityConstruction.cpp | 22 +++++++++++++++++++--- test/zx/test_zx_functionality.cpp | 22 +++++++++++++++++++++- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index 4a5f4c90dc..b9e1391d88 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -683,7 +683,6 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, qc::toString(op->getType())); } } else if (op->getNcontrols() == 1 && op->getNtargets() == 1) { - // two-qubit controlled gates const auto target = static_cast(p.at(op->getTargets().front())); const auto ctrl = static_cast(p.at((*op->getControls().begin()).qubit)); @@ -738,7 +737,6 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, qc::toString(op->getType())); } } else if (op->getNcontrols() == 2 && op->getNtargets() == 1) { - // three-qubit controlled gates (ccx or ccz) Qubit ctrl0 = 0; Qubit ctrl1 = 0; const auto target = static_cast(p.at(op->getTargets().front())); @@ -764,10 +762,18 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, addMcphase(diag, PiExpression{PiRational(1, 4)}, {ctrl0, ctrl1}, target, qubits); break; + case qc::OpType::Tdg: + addMcphase(diag, PiExpression{PiRational(-1, 4)}, {ctrl0, ctrl1}, target, + qubits); + break; case qc::OpType::S: addMcphase(diag, PiExpression{PiRational(1, 2)}, {ctrl0, ctrl1}, target, qubits); break; + case qc::OpType::Sdg: + addMcphase(diag, PiExpression{PiRational(-1, 2)}, {ctrl0, ctrl1}, target, + qubits); + break; case qc::OpType::RZ: addMcrz(diag, parseParam(op.get(), 0), {ctrl0, ctrl1}, target, qubits); break; @@ -800,10 +806,18 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, addMcphase(diag, PiExpression{PiRational(1, 4)}, controls, target, qubits); break; + case qc::OpType::Tdg: + addMcphase(diag, PiExpression{PiRational(-1, 4)}, controls, target, + qubits); + break; case qc::OpType::S: addMcphase(diag, PiExpression{PiRational(1, 2)}, controls, target, qubits); break; + case qc::OpType::Sdg: + addMcphase(diag, PiExpression{PiRational(-1, 2)}, controls, target, + qubits); + break; case qc::OpType::RZ: addMcrz(diag, parseParam(op.get(), 0), controls, target, qubits); break; @@ -977,8 +991,8 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { case qc::OpType::I: case qc::OpType::P: case qc::OpType::T: - case qc::OpType::S: case qc::OpType::Tdg: + case qc::OpType::S: case qc::OpType::Sdg: case qc::OpType::RX: case qc::OpType::RZ: @@ -992,7 +1006,9 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { case qc::OpType::Z: case qc::OpType::P: case qc::OpType::T: + case qc::OpType::Tdg: case qc::OpType::S: + case qc::OpType::Sdg: case qc::OpType::RZ: case qc::OpType::RX: return true; diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index ce4da248db..9e4081afdc 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -435,7 +435,7 @@ TEST_F(ZXFunctionalityTest, ISWAP) { qcPrime.h(0); qcPrime.cx(0, 1); qcPrime.cx(1, 0); - qc.h(1); + qcPrime.h(1); checkEquivalence(qc, qcPrime, {0, 1}); } @@ -617,6 +617,16 @@ TEST_F(ZXFunctionalityTest, MCS) { checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } +TEST_F(ZXFunctionalityTest, MCSdg) { + qc = qc::QuantumComputation(4); + qc.mcsdg({0, 1, 2}, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mcsdg({0, 1, 2}, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + TEST_F(ZXFunctionalityTest, MCT) { qc = qc::QuantumComputation(4); qc.mct({0, 1, 2}, 3); @@ -627,6 +637,16 @@ TEST_F(ZXFunctionalityTest, MCT) { checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); } +TEST_F(ZXFunctionalityTest, MCTdg) { + qc = qc::QuantumComputation(4); + qc.mctdg({0, 1, 2}, 3); + + auto qcPrime = qc::QuantumComputation(4); + qcPrime.mctdg({0, 1, 2}, 3); + + checkEquivalence(qc, qcPrime, {0, 1, 2, 3}); +} + TEST_F(ZXFunctionalityTest, MCPhase2) { qc = qc::QuantumComputation(3); qc.mcp(PI / 2, {0, 1}, 2); From 348d4d0fe78302d036959518057148614fcc1b87 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:09:03 -0400 Subject: [PATCH 71/73] Revert "Update control and target names in addCrz" This reverts commit e8efacf4950d4f37217ea8382c7ed34fc76a9c5d. --- src/zx/FunctionalityConstruction.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index b9e1391d88..bf2c6cbad5 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -403,13 +403,13 @@ void FunctionalityConstruction::addMcrx(ZXDiagram& diag, void FunctionalityConstruction::addCrz(ZXDiagram& diag, const PiExpression& phase, - const Qubit qubit0, const Qubit qubit1, + const Qubit control, const Qubit target, std::vector& qubits) { // CRZ decomposition uses reversed CNOT direction - addZSpider(diag, qubit1, qubits, phase / 2); - addCnot(diag, qubit1, qubit0, qubits); - addZSpider(diag, qubit0, qubits, -phase / 2); - addCnot(diag, qubit1, qubit0, qubits); + addZSpider(diag, target, qubits, phase / 2); + addCnot(diag, target, control, qubits); + addZSpider(diag, control, qubits, -phase / 2); + addCnot(diag, target, control, qubits); } void FunctionalityConstruction::addMcrz(ZXDiagram& diag, From 8031ec026f7b15d81caaa90922aaf2294ae7a855 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:12:16 -0400 Subject: [PATCH 72/73] Ignore linter errors --- src/zx/FunctionalityConstruction.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index bf2c6cbad5..519fde6fd8 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -407,8 +407,10 @@ void FunctionalityConstruction::addCrz(ZXDiagram& diag, std::vector& qubits) { // CRZ decomposition uses reversed CNOT direction addZSpider(diag, target, qubits, phase / 2); + // NOLINTNEXTLINE(readability-suspicious-call-argument) addCnot(diag, target, control, qubits); addZSpider(diag, control, qubits, -phase / 2); + // NOLINTNEXTLINE(readability-suspicious-call-argument) addCnot(diag, target, control, qubits); } From 2966d29310312be8f276b8fa0436228359a5cccf Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:06:36 -0400 Subject: [PATCH 73/73] Address the Rabbit's comment --- test/zx/test_zx_functionality.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 9e4081afdc..3667a47e1b 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -43,9 +43,9 @@ class ZXFunctionalityTest : public ::testing::Test { void checkEquivalence(const qc::QuantumComputation& qc1, const qc::QuantumComputation& qc2, const std::vector& qubits) { - EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc1)); - EXPECT_TRUE(FunctionalityConstruction::transformableToZX(&qc2)); - EXPECT_EQ(qc1.getNqubits(), qc2.getNqubits()); + ASSERT_TRUE(FunctionalityConstruction::transformableToZX(&qc1)); + ASSERT_TRUE(FunctionalityConstruction::transformableToZX(&qc2)); + ASSERT_EQ(qc1.getNqubits(), qc2.getNqubits()); auto d1 = FunctionalityConstruction::buildFunctionality(&qc1); auto d2 = FunctionalityConstruction::buildFunctionality(&qc2);