Skip to content

Commit c7e7016

Browse files
authored
Move mcx synthesis methods with ancillas to the synthesis library (Qiskit#12904)
* move mcx synthesis method with dirty ancillas to the synthesis library * minor lint updates * move mcx synthesis method with several clean ancillas to the synthesis library * move handling up to 3 controls to the synthesis code * move handling up to 3 controls to the synthesis code * handle cyclic imports * add mcx synthesis method with one clean ancilla to the synthesis library * update input to synth_mcx functions * refactor test for mcx method modes * update circuit names. add references * reduce num_controls in tests * revert circuit names to old ones * refactor functions names * add docstrings * update year * add synthesis functions to API docs * add release notes * fix docs * update docs and release notes following review * update imports following review
1 parent 6aa933c commit c7e7016

File tree

6 files changed

+330
-213
lines changed

6 files changed

+330
-213
lines changed

qiskit/circuit/library/standard_gates/x.py

Lines changed: 16 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"""X, CX, CCX and multi-controlled X gates."""
1414
from __future__ import annotations
1515
from typing import Optional, Union, Type
16-
from math import ceil, pi
16+
from math import pi
1717
import numpy
1818
from qiskit.circuit.controlledgate import ControlledGate
1919
from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
@@ -1371,47 +1371,12 @@ def inverse(self, annotated: bool = False):
13711371

13721372
def _define(self):
13731373
"""Define the MCX gate using recursion."""
1374-
# pylint: disable=cyclic-import
1375-
from qiskit.circuit.quantumcircuit import QuantumCircuit
13761374

1377-
q = QuantumRegister(self.num_qubits, name="q")
1378-
qc = QuantumCircuit(q, name=self.name)
1379-
if self.num_qubits == 4:
1380-
qc._append(C3XGate(), q[:], [])
1381-
self.definition = qc
1382-
elif self.num_qubits == 5:
1383-
qc._append(C4XGate(), q[:], [])
1384-
self.definition = qc
1385-
else:
1386-
num_ctrl_qubits = len(q) - 1
1387-
q_ancilla = q[-1]
1388-
q_target = q[-2]
1389-
middle = ceil(num_ctrl_qubits / 2)
1390-
first_half = [*q[:middle]]
1391-
second_half = [*q[middle : num_ctrl_qubits - 1], q_ancilla]
1392-
1393-
qc._append(
1394-
MCXVChain(num_ctrl_qubits=len(first_half), dirty_ancillas=True),
1395-
qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]],
1396-
cargs=[],
1397-
)
1398-
qc._append(
1399-
MCXVChain(num_ctrl_qubits=len(second_half), dirty_ancillas=True),
1400-
qargs=[*second_half, q_target, *q[: len(second_half) - 2]],
1401-
cargs=[],
1402-
)
1403-
qc._append(
1404-
MCXVChain(num_ctrl_qubits=len(first_half), dirty_ancillas=True),
1405-
qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]],
1406-
cargs=[],
1407-
)
1408-
qc._append(
1409-
MCXVChain(num_ctrl_qubits=len(second_half), dirty_ancillas=True),
1410-
qargs=[*second_half, q_target, *q[: len(second_half) - 2]],
1411-
cargs=[],
1412-
)
1375+
# pylint: disable=cyclic-import
1376+
from qiskit.synthesis.multi_controlled import synth_mcx_1_clean_b95
14131377

1414-
self.definition = qc
1378+
qc = synth_mcx_1_clean_b95(self.num_ctrl_qubits)
1379+
self.definition = qc
14151380

14161381

14171382
class MCXVChain(MCXGate):
@@ -1513,92 +1478,21 @@ def get_num_ancilla_qubits(num_ctrl_qubits: int, mode: str = "v-chain"):
15131478

15141479
def _define(self):
15151480
"""Define the MCX gate using a V-chain of CX gates."""
1516-
# pylint: disable=cyclic-import
1517-
from qiskit.circuit.quantumcircuit import QuantumCircuit
1518-
1519-
q = QuantumRegister(self.num_qubits, name="q")
1520-
qc = QuantumCircuit(q, name=self.name)
1521-
q_controls = q[: self.num_ctrl_qubits]
1522-
q_target = q[self.num_ctrl_qubits]
1523-
q_ancillas = q[self.num_ctrl_qubits + 1 :]
15241481

15251482
if self._dirty_ancillas:
1526-
if self.num_ctrl_qubits < 3:
1527-
qc.mcx(q_controls, q_target)
1528-
elif not self._relative_phase and self.num_ctrl_qubits == 3:
1529-
qc._append(C3XGate(), [*q_controls, q_target], [])
1530-
else:
1531-
num_ancillas = self.num_ctrl_qubits - 2
1532-
targets = [q_target] + q_ancillas[:num_ancillas][::-1]
1533-
1534-
for j in range(2):
1535-
for i in range(self.num_ctrl_qubits): # action part
1536-
if i < self.num_ctrl_qubits - 2:
1537-
if targets[i] != q_target or self._relative_phase:
1538-
# gate cancelling
1539-
1540-
# cancel rightmost gates of action part
1541-
# with leftmost gates of reset part
1542-
if self._relative_phase and targets[i] == q_target and j == 1:
1543-
qc.cx(q_ancillas[num_ancillas - i - 1], targets[i])
1544-
qc.t(targets[i])
1545-
qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i])
1546-
qc.tdg(targets[i])
1547-
qc.h(targets[i])
1548-
else:
1549-
qc.h(targets[i])
1550-
qc.t(targets[i])
1551-
qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i])
1552-
qc.tdg(targets[i])
1553-
qc.cx(q_ancillas[num_ancillas - i - 1], targets[i])
1554-
else:
1555-
controls = [
1556-
q_controls[self.num_ctrl_qubits - i - 1],
1557-
q_ancillas[num_ancillas - i - 1],
1558-
]
1559-
1560-
qc.ccx(controls[0], controls[1], targets[i])
1561-
else:
1562-
# implements an optimized toffoli operation
1563-
# up to a diagonal gate, akin to lemma 6 of arXiv:1501.06911
1564-
qc.h(targets[i])
1565-
qc.t(targets[i])
1566-
qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i])
1567-
qc.tdg(targets[i])
1568-
qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i])
1569-
qc.t(targets[i])
1570-
qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i])
1571-
qc.tdg(targets[i])
1572-
qc.h(targets[i])
1573-
1574-
break
1575-
1576-
for i in range(num_ancillas - 1): # reset part
1577-
qc.cx(q_ancillas[i], q_ancillas[i + 1])
1578-
qc.t(q_ancillas[i + 1])
1579-
qc.cx(q_controls[2 + i], q_ancillas[i + 1])
1580-
qc.tdg(q_ancillas[i + 1])
1581-
qc.h(q_ancillas[i + 1])
1582-
1583-
if self._action_only:
1584-
qc.ccx(q_controls[-1], q_ancillas[-1], q_target)
1585-
1586-
break
1587-
else:
1588-
qc.rccx(q_controls[0], q_controls[1], q_ancillas[0])
1589-
i = 0
1590-
for j in range(2, self.num_ctrl_qubits - 1):
1591-
qc.rccx(q_controls[j], q_ancillas[i], q_ancillas[i + 1])
1483+
# pylint: disable=cyclic-import
1484+
from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_i15
15921485

1593-
i += 1
1594-
1595-
qc.ccx(q_controls[-1], q_ancillas[i], q_target)
1596-
1597-
for j in reversed(range(2, self.num_ctrl_qubits - 1)):
1598-
qc.rccx(q_controls[j], q_ancillas[i - 1], q_ancillas[i])
1486+
qc = synth_mcx_n_dirty_i15(
1487+
self.num_ctrl_qubits,
1488+
self._relative_phase,
1489+
self._action_only,
1490+
)
15991491

1600-
i -= 1
1492+
else: # use clean ancillas
1493+
# pylint: disable=cyclic-import
1494+
from qiskit.synthesis.multi_controlled import synth_mcx_n_clean_m15
16011495

1602-
qc.rccx(q_controls[0], q_controls[1], q_ancillas[i])
1496+
qc = synth_mcx_n_clean_m15(self.num_ctrl_qubits)
16031497

16041498
self.definition = qc

qiskit/synthesis/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@
122122
123123
.. autofunction:: two_qubit_cnot_decompose
124124
125+
Multi Controlled Synthesis
126+
==========================
127+
128+
.. autofunction:: synth_mcx_n_dirty_i15
129+
.. autofunction:: synth_mcx_n_clean_m15
130+
.. autofunction:: synth_mcx_1_clean_b95
131+
125132
"""
126133

127134
from .evolution import (
@@ -173,3 +180,8 @@
173180
two_qubit_cnot_decompose,
174181
TwoQubitWeylDecomposition,
175182
)
183+
from .multi_controlled.mcx_with_ancillas_synth import (
184+
synth_mcx_n_dirty_i15,
185+
synth_mcx_n_clean_m15,
186+
synth_mcx_1_clean_b95,
187+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This code is part of Qiskit.
2+
#
3+
# (C) Copyright IBM 2024.
4+
#
5+
# This code is licensed under the Apache License, Version 2.0. You may
6+
# obtain a copy of this license in the LICENSE.txt file in the root directory
7+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
#
9+
# Any modifications or derivative works of this code must retain this
10+
# copyright notice, and modified files need to carry a notice indicating
11+
# that they have been altered from the originals.
12+
13+
"""Module containing multi-controlled circuits synthesis"""
14+
15+
from .mcx_with_ancillas_synth import (
16+
synth_mcx_n_dirty_i15,
17+
synth_mcx_n_clean_m15,
18+
synth_mcx_1_clean_b95,
19+
)

0 commit comments

Comments
 (0)