Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update MCX recursive synthesis method to reduce number of CX gates #12772

Merged
merged 19 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 38 additions & 30 deletions qiskit/circuit/library/standard_gates/x.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,8 +952,8 @@ class C4XGate(SingletonControlledGate):
of the relative phase version of c3x, the rc3x [2].

References:
[1] Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf
[2] Maslov, 2015. https://arxiv.org/abs/1508.03273
1. Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf
2. Maslov, 2015. https://arxiv.org/abs/1508.03273
"""

def __init__(
Expand Down Expand Up @@ -1320,9 +1320,14 @@ def _define(self):
class MCXRecursive(MCXGate):
"""Implement the multi-controlled X gate using recursion.

Using a single ancilla qubit, the multi-controlled X gate is recursively split onto
four sub-registers. This is done until we reach the 3- or 4-controlled X gate since
for these we have a concrete implementation that do not require ancillas.
Using a single clean ancilla qubit, the multi-controlled X gate is split into
four sub-registers, each one of them uses the V-chain method.

The method is based on Lemma 9 of [2], first shown in Lemma 7.3 of [1].

References:
1. Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf
2. Iten et al., 2015. https://arxiv.org/abs/1501.06911
"""

def __init__(
Expand Down Expand Up @@ -1378,32 +1383,35 @@ def _define(self):
qc._append(C4XGate(), q[:], [])
self.definition = qc
else:
for instr, qargs, cargs in self._recurse(q[:-1], q_ancilla=q[-1]):
qc._append(instr, qargs, cargs)
self.definition = qc
num_ctrl_qubits = len(q) - 1
q_ancilla = q[-1]
q_target = q[-2]
middle = ceil(num_ctrl_qubits / 2)
first_half = [*q[:middle]]
second_half = [*q[middle : num_ctrl_qubits - 1], q_ancilla]

qc._append(
MCXVChain(num_ctrl_qubits=len(first_half), dirty_ancillas=True),
qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]],
cargs=[],
)
qc._append(
MCXVChain(num_ctrl_qubits=len(second_half), dirty_ancillas=True),
qargs=[*second_half, q_target, *q[: len(second_half) - 2]],
cargs=[],
)
qc._append(
MCXVChain(num_ctrl_qubits=len(first_half), dirty_ancillas=True),
qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]],
cargs=[],
)
qc._append(
MCXVChain(num_ctrl_qubits=len(second_half), dirty_ancillas=True),
qargs=[*second_half, q_target, *q[: len(second_half) - 2]],
cargs=[],
)

def _recurse(self, q, q_ancilla=None):
# recursion stop
if len(q) == 4:
return [(C3XGate(), q[:], [])]
if len(q) == 5:
return [(C4XGate(), q[:], [])]
if len(q) < 4:
raise AttributeError("Something went wrong in the recursion, have less than 4 qubits.")

# recurse
num_ctrl_qubits = len(q) - 1
middle = ceil(num_ctrl_qubits / 2)
first_half = [*q[:middle], q_ancilla]
second_half = [*q[middle:num_ctrl_qubits], q_ancilla, q[num_ctrl_qubits]]

rule = []
rule += self._recurse(first_half, q_ancilla=q[middle])
rule += self._recurse(second_half, q_ancilla=q[middle - 1])
rule += self._recurse(first_half, q_ancilla=q[middle])
rule += self._recurse(second_half, q_ancilla=q[middle - 1])

return rule
self.definition = qc


class MCXVChain(MCXGate):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features_synthesis:
- |
:class:`.MCXRecursive` with :math:`k` control qubits and a single clean auxiliary qubit
now requires at most :math:`16k-8` CX gates.
16 changes: 9 additions & 7 deletions test/python/circuit/test_circuit_qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,20 +412,22 @@ def test_circuit_qasm_with_mcx_gate_variants(self):
qc.append(cl.MCXGrayCode(n), range(n + 1))
qc.append(cl.MCXRecursive(n), range(n + 2))
qc.append(cl.MCXVChain(n), range(2 * n - 1))
mcx_vchain_id = id(qc.data[-1].operation)

# qasm output doesn't support parameterized gate yet.
# param0 for "gate mcuq(param0) is not used inside the definition
expected_qasm = """OPENQASM 2.0;
expected_qasm = f"""OPENQASM 2.0;
include "qelib1.inc";
gate mcu1(param0) q0,q1,q2,q3,q4,q5 { cu1(pi/16) q4,q5; cx q4,q3; cu1(-pi/16) q3,q5; cx q4,q3; cu1(pi/16) q3,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; }
gate mcx_gray q0,q1,q2,q3,q4,q5 { h q5; mcu1(pi) q0,q1,q2,q3,q4,q5; h q5; }
gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }
gate mcx_recursive q0,q1,q2,q3,q4,q5,q6 { mcx q0,q1,q2,q6; mcx q3,q4,q6,q5; mcx q0,q1,q2,q6; mcx q3,q4,q6,q5; }
gate mcx_vchain q0,q1,q2,q3,q4,q5,q6,q7,q8 { rccx q0,q1,q6; rccx q2,q6,q7; rccx q3,q7,q8; ccx q4,q8,q5; rccx q3,q7,q8; rccx q2,q6,q7; rccx q0,q1,q6; }
gate mcu1(param0) q0,q1,q2,q3,q4,q5 {{ cu1(pi/16) q4,q5; cx q4,q3; cu1(-pi/16) q3,q5; cx q4,q3; cu1(pi/16) q3,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; }}
gate mcx_gray q0,q1,q2,q3,q4,q5 {{ h q5; mcu1(pi) q0,q1,q2,q3,q4,q5; h q5; }}
gate mcx q0,q1,q2,q3 {{ h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }}
gate mcx_vchain q0,q1,q2,q3,q4 {{ mcx q0,q1,q2,q3; }}
gate mcx_recursive q0,q1,q2,q3,q4,q5,q6 {{ mcx_vchain q0,q1,q2,q6,q3; mcx_vchain q3,q4,q6,q5,q0; mcx_vchain q0,q1,q2,q6,q3; mcx_vchain q3,q4,q6,q5,q0; }}
gate mcx_vchain_{mcx_vchain_id} q0,q1,q2,q3,q4,q5,q6,q7,q8 {{ rccx q0,q1,q6; rccx q2,q6,q7; rccx q3,q7,q8; ccx q4,q8,q5; rccx q3,q7,q8; rccx q2,q6,q7; rccx q0,q1,q6; }}
qreg q[9];
mcx_gray q[0],q[1],q[2],q[3],q[4],q[5];
mcx_recursive q[0],q[1],q[2],q[3],q[4],q[5],q[6];
mcx_vchain q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];"""
mcx_vchain_{mcx_vchain_id} q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];"""

self.assertEqual(dumps(qc), expected_qasm)

Expand Down
16 changes: 16 additions & 0 deletions test/python/circuit/test_controlled_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,22 @@ def test_mcxvchain_dirty_ancilla_cx_count(self, num_ctrl_qubits):

self.assertLessEqual(cx_count, 8 * num_ctrl_qubits - 6)

@data(7, 10, 15)
def test_mcxrecursive_clean_ancilla_cx_count(self, num_ctrl_qubits):
"""Test if cx count of the mcx with one clean ancilla
is less than upper bound."""
from qiskit import transpile

mcx_recursive = MCXRecursive(num_ctrl_qubits)
qc = QuantumCircuit(mcx_recursive.num_qubits)

qc.append(mcx_recursive, list(range(mcx_recursive.num_qubits)))

tr_mcx_rec = transpile(qc, basis_gates=["u", "cx"])
cx_count = tr_mcx_rec.count_ops()["cx"]

self.assertLessEqual(cx_count, 16 * num_ctrl_qubits - 8)

def test_mcxvchain_dirty_ancilla_action_only(self):
"""Test the v-chain mcx with dirty auxiliary qubits
with gate cancelling with mirrored circuit."""
Expand Down
16 changes: 9 additions & 7 deletions test/python/qasm2/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,21 +402,23 @@ def test_mcx_gate_variants(self):
qc.append(lib.MCXGrayCode(n), range(n + 1))
qc.append(lib.MCXRecursive(n), range(n + 2))
qc.append(lib.MCXVChain(n), range(2 * n - 1))
mcx_vchain_id = id(qc.data[-1].operation)

# qasm output doesn't support parameterized gate yet.
# param0 for "gate mcuq(param0) is not used inside the definition
expected_qasm = """\
expected_qasm = f"""\
OPENQASM 2.0;
include "qelib1.inc";
gate mcu1(param0) q0,q1,q2,q3,q4,q5 { cu1(pi/16) q4,q5; cx q4,q3; cu1(-pi/16) q3,q5; cx q4,q3; cu1(pi/16) q3,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; }
gate mcx_gray q0,q1,q2,q3,q4,q5 { h q5; mcu1(pi) q0,q1,q2,q3,q4,q5; h q5; }
gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }
gate mcx_recursive q0,q1,q2,q3,q4,q5,q6 { mcx q0,q1,q2,q6; mcx q3,q4,q6,q5; mcx q0,q1,q2,q6; mcx q3,q4,q6,q5; }
gate mcx_vchain q0,q1,q2,q3,q4,q5,q6,q7,q8 { rccx q0,q1,q6; rccx q2,q6,q7; rccx q3,q7,q8; ccx q4,q8,q5; rccx q3,q7,q8; rccx q2,q6,q7; rccx q0,q1,q6; }
gate mcu1(param0) q0,q1,q2,q3,q4,q5 {{ cu1(pi/16) q4,q5; cx q4,q3; cu1(-pi/16) q3,q5; cx q4,q3; cu1(pi/16) q3,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q3,q2; cu1(-pi/16) q2,q5; cx q4,q2; cu1(pi/16) q2,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q2,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q3,q1; cu1(-pi/16) q1,q5; cx q4,q1; cu1(pi/16) q1,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q1,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q2,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; cx q3,q0; cu1(-pi/16) q0,q5; cx q4,q0; cu1(pi/16) q0,q5; }}
gate mcx_gray q0,q1,q2,q3,q4,q5 {{ h q5; mcu1(pi) q0,q1,q2,q3,q4,q5; h q5; }}
gate mcx q0,q1,q2,q3 {{ h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; }}
gate mcx_vchain q0,q1,q2,q3,q4 {{ mcx q0,q1,q2,q3; }}
gate mcx_recursive q0,q1,q2,q3,q4,q5,q6 {{ mcx_vchain q0,q1,q2,q6,q3; mcx_vchain q3,q4,q6,q5,q0; mcx_vchain q0,q1,q2,q6,q3; mcx_vchain q3,q4,q6,q5,q0; }}
gate mcx_vchain_{mcx_vchain_id} q0,q1,q2,q3,q4,q5,q6,q7,q8 {{ rccx q0,q1,q6; rccx q2,q6,q7; rccx q3,q7,q8; ccx q4,q8,q5; rccx q3,q7,q8; rccx q2,q6,q7; rccx q0,q1,q6; }}
qreg q[9];
mcx_gray q[0],q[1],q[2],q[3],q[4],q[5];
mcx_recursive q[0],q[1],q[2],q[3],q[4],q[5],q[6];
mcx_vchain q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];"""
mcx_vchain_{mcx_vchain_id} q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];"""

self.assertEqual(qasm2.dumps(qc), expected_qasm)

Expand Down
Loading