Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2134c2e
Add multi-control functionality
Dec 8, 2025
0ae2b61
Merge branch 'main' into multi_control
keefehuang Dec 8, 2025
f8c4503
Apply minor updates to code
Dec 8, 2025
894add1
Remove old CCX test
Dec 8, 2025
fb32afb
Add tests for CCZ, CRZ, MCRZ and MCZ
Dec 8, 2025
2375b74
Rename control and target for addCrz
Dec 8, 2025
275e832
🎨 pre-commit fixes
pre-commit-ci[bot] Dec 8, 2025
c873318
:sparkles: Qiskit to IQM JSON converter
marcelwa Dec 7, 2025
068560c
:white_check_mark: Test Qiskit to IQM JSON converter
marcelwa Dec 7, 2025
53032bb
:sparkles: Support IQM JSON conversion in `QDMIBackend`
marcelwa Dec 7, 2025
5c68a2f
:white_check_mark: Tests for new backend functionality
marcelwa Dec 7, 2025
3f46cd3
:art: Apply suggestions from GitHub Copilot
marcelwa Dec 7, 2025
33b48a7
:art: Apply `pre-commit`
marcelwa Dec 7, 2025
453e00a
:memo: Add to CHANGELOG
marcelwa Dec 7, 2025
b81e3e3
:memo: Adjust documentation
marcelwa Dec 7, 2025
2505f27
:pencil2: Fix typo
marcelwa Dec 7, 2025
9ab82f3
:adhesive_bandage: Fix usage of `R` gate in docstring
marcelwa Dec 7, 2025
06a40af
:art: Apply CodeRabbit's suggestion
marcelwa Dec 7, 2025
92ed957
:art: Apply CodeRabbit's suggestion
marcelwa Dec 8, 2025
0911f7c
:white_check_mark: Add tests to cover CodeRabbit's suggestions
marcelwa Dec 8, 2025
3d74d1b
🎨 pre-commit fixes
pre-commit-ci[bot] Dec 9, 2025
9915b6a
Merge branch 'main' into multi_control
keefehuang Dec 9, 2025
cdc0ec3
Merge branch 'main' into multi_control
keefehuang Dec 15, 2025
ee9b279
Rename variables in function definition of addCrz to remove linting w…
Dec 15, 2025
cbda9d6
Remove unreachable branches in switch case in addMcrz
Dec 15, 2025
a8af5a7
Remove unreachable branches in switch cases for
Dec 15, 2025
8236fbf
Use addMcz implementation
Dec 15, 2025
14e1955
Add tests to increase codecov
Dec 15, 2025
9de9ac9
Add more connection assertions for tests
Dec 15, 2025
826829e
Remove one check in buildFunctionality as we only care if there is on…
Dec 15, 2025
93bf49d
Add additional checks per coderabbit suggestions
Jan 8, 2026
48caa99
Merge branch 'main' into multi_control
keefehuang Jan 8, 2026
8cbe448
Fix connectivity in test
Jan 8, 2026
e10767e
Add tests for connectivity of additional wires to meet coderabbit req…
Jan 8, 2026
9816218
Add connectivity test for qubit 1
Jan 8, 2026
ef2f343
Merge branch 'main' into multi_control
keefehuang Jan 8, 2026
77dae77
Removed unneeded import
Jan 8, 2026
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
161 changes: 155 additions & 6 deletions src/zx/FunctionalityConstruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 q0, const Qubit q1,
std::vector<Vertex>& qubits) {
// CRZ decomposition uses reversed CNOT direction
addCnot(diag, q1, q0, qubits);
addZSpider(diag, q0, qubits, -phase / 2);
addZSpider(diag, q1, qubits, phase / 2);
Comment on lines +337 to +339
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
addCnot(diag, q1, q0, qubits);
addZSpider(diag, q0, qubits, -phase / 2);
addZSpider(diag, q1, qubits, phase / 2);
addZSpider(diag, q1, qubits, phase / 2);
addCnot(diag, q1, q0, qubits);
addZSpider(diag, q0, qubits, -phase / 2);

These commute, so it does not really matter too much, but for consistency with the mcrz patter, I'd prefer this order.

addCnot(diag, q1, q0, qubits);
}

void FunctionalityConstruction::addMcrz(ZXDiagram& diag,
const PiExpression& phase,
std::vector<Qubit> controls,
const Qubit target,
std::vector<Vertex>& 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<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");
}
Comment on lines +388 to +390
Copy link
Member

Choose a reason for hiding this comment

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

This is unreachable code, right? There are more than nctrl+1 qubits available, so there has to be one that is not in the list of controls+target. So anc does not need to be an optional.

I also think that you could be using https://en.cppreference.com/w/cpp/algorithm/set_difference.html here.


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) {
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);
Comment on lines +425 to +428
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't it be easier to either:

  • have mcz implement the logic as H -- mcx -- H, or
  • have mcx be implemented as H -- mcz -- H

Does one of these yield a shorter decomposition?

}

FunctionalityConstruction::op_it
FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end,
std::vector<Vertex>& qubits,
Expand Down Expand Up @@ -538,6 +659,9 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end,
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,15 +702,39 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end,
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)));
}
Comment on lines +705 to +708
Copy link
Member

Choose a reason for hiding this comment

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

This can just be inlined as {ctrl0, ctrl1} in the addMcrz call below.

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<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)));
}
Comment on lines +725 to +728
Copy link
Member

Choose a reason for hiding this comment

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

Always good to use reserve to preallocate the right size for the container. And to use emplace_back over push_back.

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: " +
Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading