Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions include/mqt-core/zx/FunctionalityConstruction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ class FunctionalityConstruction {
const std::optional<double>& unconvertedBeta = std::nullopt);
static void addCcx(ZXDiagram& diag, Qubit ctrl0, Qubit ctrl1, Qubit target,
std::vector<Vertex>& qubits);
static void addCcz(ZXDiagram& diag, Qubit ctrl0, Qubit ctrl1, Qubit target,
std::vector<Vertex>& qubits);
static void addCrz(ZXDiagram& diag, const PiExpression& phase,
const Qubit control, const Qubit target,
std::vector<Vertex>& qubits);
static void addMcrz(ZXDiagram& diag, const PiExpression& phase,
std::vector<Qubit> controls, const Qubit target,
std::vector<Vertex>& qubits);
static void addMcx(ZXDiagram& diag, std::vector<Qubit> controls,
const Qubit target, std::vector<Vertex>& qubits);
static void addMcz(ZXDiagram& diag, std::vector<Qubit> controls,
const Qubit target, std::vector<Vertex>& qubits);
static op_it parseOp(ZXDiagram& diag, op_it it, op_it end,
std::vector<Vertex>& qubits, const qc::Permutation& p);
static op_it parseCompoundOp(ZXDiagram& diag, op_it it, op_it end,
Expand Down
188 changes: 186 additions & 2 deletions src/zx/FunctionalityConstruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,150 @@
addCnot(diag, ctrl0, ctrl1, qubits);
}

void FunctionalityConstruction::addCcz(ZXDiagram& diag, const Qubit ctrl0,
const Qubit ctrl1, const Qubit target,
std::vector<Vertex>& 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 ctrl, const Qubit target,
std::vector<Vertex>& qubits) {
// CRZ decomposition uses reversed CNOT direction
addCnot(diag, target, ctrl, qubits);

Check warning on line 337 in src/zx/FunctionalityConstruction.cpp

View workflow job for this annotation

GitHub Actions / 🇨‌ Lint / 🚨 Lint

src/zx/FunctionalityConstruction.cpp:337:3 [readability-suspicious-call-argument]

2nd argument 'target' (passed to 'ctrl') looks like it might be swapped with the 3rd, 'ctrl' (passed to 'target')
addZSpider(diag, ctrl, qubits, -phase / 2);
addZSpider(diag, target, qubits, phase / 2);
addCnot(diag, target, ctrl, qubits);

Check warning on line 340 in src/zx/FunctionalityConstruction.cpp

View workflow job for this annotation

GitHub Actions / 🇨‌ Lint / 🚨 Lint

src/zx/FunctionalityConstruction.cpp:340:3 [readability-suspicious-call-argument]

2nd argument 'target' (passed to 'ctrl') looks like it might be swapped with the 3rd, 'ctrl' (passed to 'target')
}

void FunctionalityConstruction::addMcrz(ZXDiagram& diag,
const PiExpression& phase,
std::vector<Qubit> controls,
const Qubit target,
std::vector<Vertex>& 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();

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<Qubit> controls,
const Qubit target,
std::vector<Vertex>& 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<std::ptrdiff_t>((controls.size() + 1) / 2);
const std::vector<Qubit> first(controls.begin(), controls.begin() + half);
std::vector<Qubit> second(controls.begin() + half, controls.end());

if (qubits.size() > controls.size() + 1) {
controls.push_back(target);
std::optional<Qubit> anc{};
for (std::size_t q = 0; q < qubits.size(); ++q) {
const auto qb = static_cast<Qubit>(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<Qubit> controls,
const Qubit target,
std::vector<Vertex>& 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:
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<Vertex>& qubits,
Expand Down Expand Up @@ -538,6 +682,9 @@
qubits[static_cast<std::size_t>(target)],
EdgeType::Hadamard);
break;
case qc::OpType::RZ:
addCrz(diag, parseParam(op.get(), 0), ctrl, target, qubits);
break;

case qc::OpType::I:
break;
Expand Down Expand Up @@ -578,16 +725,42 @@
ctrl1 = static_cast<Qubit>(p.at(ctrl.qubit));
}
}
std::vector<Qubit> controls;
for (const auto& ctrl : op->getControls()) {
controls.push_back(static_cast<Qubit>(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()));
}
Comment on lines +728 to +745
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Unused controls vector in 2-control branch.

The controls vector is constructed at lines 728-731 but is only used for the RZ case at line 740. For X and Z cases, ctrl0 and ctrl1 are used directly. This is not a bug but introduces slight redundancy.

Consider constructing controls only when needed, or using it consistently for all cases:

+    // Build controls vector for multi-control dispatch
     std::vector<Qubit> controls;
     for (const auto& ctrl : op->getControls()) {
       controls.push_back(static_cast<Qubit>(p.at(ctrl.qubit)));
     }
     switch (op->getType()) {
     case qc::OpType::X:
-      addCcx(diag, ctrl0, ctrl1, target, qubits);
+      addCcx(diag, controls.front(), controls.back(), target, qubits);
       break;
     case qc::OpType::Z:
-      addCcz(diag, ctrl0, ctrl1, target, qubits);
+      addCcz(diag, controls.front(), controls.back(), target, qubits);
       break;
🤖 Prompt for AI Agents
In src/zx/FunctionalityConstruction.cpp around lines 728-745 the vector controls
is populated unconditionally but only used in the RZ branch; avoid the
redundancy by moving the construction into the RZ case (or alternately use the
controls vector for all branches). Update the code so that controls is
declared/filled only inside the qc::OpType::RZ case before calling addMcrz(diag,
parseParam(op.get(), 0), controls, target, qubits), and remove the unused
controls construction from before the switch; ensure no other branches depend on
controls.

} else if (op->getNtargets() == 1) {
const auto target = static_cast<Qubit>(p.at(op->getTargets().front()));
std::vector<Qubit> controls;
for (const auto& ctrl : op->getControls()) {
controls.push_back(static_cast<Qubit>(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()));
Expand Down Expand Up @@ -701,15 +874,26 @@
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) {

Check warning on line 883 in src/zx/FunctionalityConstruction.cpp

View workflow job for this annotation

GitHub Actions / 🇨‌ Lint / 🚨 Lint

src/zx/FunctionalityConstruction.cpp:883:39 [bugprone-branch-clone]

repeated branch body in conditional chain
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;
Expand Down
Loading
Loading