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 the code for decomposing MCX and MCPhase gates, so that the number of CX gates won't grow exponentially #11993

Merged
merged 8 commits into from
Mar 31, 2024
Merged
30 changes: 21 additions & 9 deletions qiskit/circuit/library/standard_gates/p.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,20 +354,32 @@ def _define(self):
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit

q = QuantumRegister(self.num_qubits, "q")
qc = QuantumCircuit(q, name=self.name)
qr = QuantumRegister(self.num_qubits, "q")
qc = QuantumCircuit(qr, name=self.name)

if self.num_ctrl_qubits == 0:
qc.p(self.params[0], 0)
if self.num_ctrl_qubits == 1:
qc.cp(self.params[0], 0, 1)
else:
from .u3 import _gray_code_chain

scaled_lam = self.params[0] / (2 ** (self.num_ctrl_qubits - 1))
bottom_gate = CPhaseGate(scaled_lam)
for operation, qubits, clbits in _gray_code_chain(q, self.num_ctrl_qubits, bottom_gate):
qc._append(operation, qubits, clbits)
lam = self.params[0]
if type(lam) in [float, int]:
q_controls = list(range(self.num_ctrl_qubits))
q_target = self.num_ctrl_qubits
new_target = q_target
for k in range(self.num_ctrl_qubits):
qc.mcrz(lam / (2**k), q_controls, new_target, use_basis_gates=True)
new_target = q_controls.pop()
qc.p(lam / (2**self.num_ctrl_qubits), new_target)
else: # in this case type(lam) is ParameterValueType
from .u3 import _gray_code_chain

scaled_lam = self.params[0] / (2 ** (self.num_ctrl_qubits - 1))
bottom_gate = CPhaseGate(scaled_lam)
for operation, qubits, clbits in _gray_code_chain(
qr, self.num_ctrl_qubits, bottom_gate
):
qc._append(operation, qubits, clbits)
self.definition = qc

def control(
Expand Down Expand Up @@ -407,5 +419,5 @@ def control(
return gate

def inverse(self, annotated: bool = False):
r"""Return inverted MCU1 gate (:math:`MCU1(\lambda)^{\dagger} = MCU1(-\lambda)`)"""
r"""Return inverted MCPhase gate (:math:`MCPhase(\lambda)^{\dagger} = MCPhase(-\lambda)`)"""
return MCPhaseGate(-self.params[0], self.num_ctrl_qubits)
19 changes: 15 additions & 4 deletions qiskit/circuit/library/standard_gates/x.py
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,7 @@ def __new__(
explicit CX, CCX, C3X or C4X instance or a generic MCX gate.
"""
# The CXGate and CCXGate will be implemented for all modes of the MCX, and
# the C3XGate and C4XGate will be implemented in the MCXGrayCode class.
# the C3XGate and C4XGate are handled in the gate definition.
explicit: dict[int, Type[ControlledGate]] = {1: CXGate, 2: CCXGate}
gate_class = explicit.get(num_ctrl_qubits, None)
if gate_class is not None:
Expand Down Expand Up @@ -1166,14 +1166,25 @@ def get_num_ancilla_qubits(num_ctrl_qubits: int, mode: str = "noancilla") -> int
raise AttributeError(f"Unsupported mode ({mode}) specified!")

def _define(self):
"""The standard definition used the Gray code implementation."""
"""This definition is based on MCPhaseGate implementation."""
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit

q = QuantumRegister(self.num_qubits, name="q")
qc = QuantumCircuit(q)
qc._append(MCXGrayCode(self.num_ctrl_qubits), q[:], [])
self.definition = qc
if self.num_qubits == 4:
qc._append(C3XGate(), q[:], [])
self.definition = qc
elif self.num_qubits == 5:
qc._append(C4XGate(), q[:], [])
self.definition = qc
else:
q_controls = list(range(self.num_ctrl_qubits))
q_target = self.num_ctrl_qubits
qc.h(q_target)
qc.mcp(numpy.pi, q_controls, q_target)
qc.h(q_target)
self.definition = qc

@property
def num_ancilla_qubits(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Improve the decomposition of the gates :class:`.MCXGate` and :class:`.MCPhaseGate`
without using ancilla qubits, so that the number of :class:`.CXGate` will grow
quadratically in the number of qubits and not exponentially.
13 changes: 13 additions & 0 deletions test/python/circuit/test_controlled_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,19 @@ def test_mcx_gates_yield_explicit_gates(self, num_ctrl_qubits):
explicit = {1: CXGate, 2: CCXGate}
self.assertEqual(cls, explicit[num_ctrl_qubits])

@data(1, 2, 3, 4)
def test_small_mcx_gates_yield_cx_count(self, num_ctrl_qubits):
"""Test the creating a MCX gate with small number of controls (with no ancillas)
yields the expected number of cx gates."""
qc = QuantumCircuit(num_ctrl_qubits + 1)
qc.append(MCXGate(num_ctrl_qubits), range(num_ctrl_qubits + 1))
from qiskit import transpile

cqc = transpile(qc, basis_gates=["u", "cx"])
cx_count = cqc.count_ops()["cx"]
expected = {1: 1, 2: 6, 3: 14, 4: 36}
self.assertEqual(cx_count, expected[num_ctrl_qubits])

@data(1, 2, 3, 4)
def test_mcxgraycode_gates_yield_explicit_gates(self, num_ctrl_qubits):
"""Test creating an mcx gate calls MCXGrayCode and yeilds explicit definition."""
Expand Down
Loading