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..9f004adf2c 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -307,6 +307,127 @@ 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 q0, const Qubit q1, + std::vector& qubits) { + // CRZ decomposition uses reversed CNOT direction + 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, + const PiExpression& phase, + std::vector controls, + const Qubit target, + std::vector& qubits) { + + 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); +} + +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: + 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); + 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); + + 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); + const 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) { + 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); +} + FunctionalityConstruction::op_it FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, std::vector& qubits, @@ -538,6 +659,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,15 +702,39 @@ 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); break; - case qc::OpType::Z: - addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); - addCcx(diag, ctrl0, ctrl1, target, qubits); - addZSpider(diag, target, qubits, PiExpression(), EdgeType::Hadamard); + 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(static_cast(p.at(ctrl.qubit))); + } + switch (op->getType()) { + case qc::OpType::X: + addMcx(diag, controls, target, qubits); + break; + case qc::OpType::Z: + addMcz(diag, controls, target, qubits); + break; + case qc::OpType::RZ: + addMcrz(diag, parseParam(op.get(), 0), controls, target, qubits); break; default: throw ZXException("Unsupported Multi-control operation: " + @@ -701,15 +849,16 @@ 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) { + } 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; diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 60ecc9132a..62eab24bda 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -219,14 +219,336 @@ TEST_F(ZXFunctionalityTest, Compound) { EXPECT_TRUE(diag.isIdentity()); } -TEST_F(ZXFunctionalityTest, UnsupportedMultiControl) { +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(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))); +} + +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))); + 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); - 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); + + 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))); +} + +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); + + 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))); +} + +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))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); +} + +TEST_F(ZXFunctionalityTest, MultiControlZ) { + using namespace qc::literals; + qc = qc::QuantumComputation(4); + qc.mcz({1, 2, 3}, 0); + + 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))); +} + +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))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); +} + +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))); + EXPECT_TRUE(d.connected(d.getInput(3), d.getOutput(3))); +} + +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); + + 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) { + 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))); + EXPECT_TRUE(d.connected(d.getInput(1), d.getOutput(1))); } TEST_F(ZXFunctionalityTest, UnsupportedControl) {