Skip to content

Commit fbdf950

Browse files
patelvyomCryorisalexanderivrii
authored
Better MCX Decomposition with logarithmic Toffoli Depth (Qiskit#13922)
* add empty stubs * add mcx_linear_depth implementation, add docstrings to all 4 * add mcx_log_depth implementation * typo in function name * remove gate.definition uses, work with separate qregs * fix linter errors * fix type hints, whitespace * replace assert with valueerror * fix linter errors * another linter error * add new test file for mcx_synthesis; test statevector correctness * add test for cx count * simplify loop logic * add test for depth * add module docstring * add header * replace assert with valueerror * minor linter error * bugfix: typo in dirty anc. case * minor refactoring + docstrings * add releasenote * add Raises to dosctrings * Apply suggestions from code review minor suggestions Co-authored-by: Julien Gacon <[email protected]> * add empty stubs * add mcx_linear_depth implementation, add docstrings to all 4 * add mcx_log_depth implementation * typo in function name * remove gate.definition uses, work with separate qregs * fix linter errors * fix type hints, whitespace * replace assert with valueerror * fix linter errors * another linter error * add new test file for mcx_synthesis; test statevector correctness * add test for cx count * simplify loop logic * add test for depth * add module docstring * add header * replace assert with valueerror * minor linter error * bugfix: typo in dirty anc. case * rebase main * add releasenote * add Raises to dosctrings * Apply suggestions from code review minor suggestions Co-authored-by: Julien Gacon <[email protected]> * whitespace * throw error when n_ctrls <=2 and linear_ladder_ops now accepts int instead of list * move tests to new file * black formatting * small ctrl vals, large n_ctrls timeout on windows CI * Update releasenotes/notes/better_mcx_synthesis-7e6e265147bc1d33.yaml Co-authored-by: Alexander Ivrii <[email protected]> * spelling * whitespace --------- Co-authored-by: Julien Gacon <[email protected]> Co-authored-by: Alexander Ivrii <[email protected]>
1 parent d67c818 commit fbdf950

File tree

5 files changed

+460
-2
lines changed

5 files changed

+460
-2
lines changed

qiskit/synthesis/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@
127127
==========================
128128
129129
.. autofunction:: synth_mcmt_vchain
130+
.. autofunction:: synth_mcx_1_clean_kg24
131+
.. autofunction:: synth_mcx_1_dirty_kg24
132+
.. autofunction:: synth_mcx_2_clean_kg24
133+
.. autofunction:: synth_mcx_2_dirty_kg24
130134
.. autofunction:: synth_mcx_n_dirty_i15
131135
.. autofunction:: synth_mcx_n_clean_m15
132136
.. autofunction:: synth_mcx_1_clean_b95
@@ -220,6 +224,10 @@
220224
)
221225
from .multi_controlled import (
222226
synth_mcmt_vchain,
227+
synth_mcx_1_clean_kg24,
228+
synth_mcx_1_dirty_kg24,
229+
synth_mcx_2_clean_kg24,
230+
synth_mcx_2_dirty_kg24,
223231
synth_mcx_n_dirty_i15,
224232
synth_mcx_n_clean_m15,
225233
synth_mcx_1_clean_b95,

qiskit/synthesis/multi_controlled/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
from .mcmt_vchain import synth_mcmt_vchain
1616
from .mcx_synthesis import (
17+
synth_mcx_1_clean_kg24,
18+
synth_mcx_1_dirty_kg24,
19+
synth_mcx_2_clean_kg24,
20+
synth_mcx_2_dirty_kg24,
1721
synth_mcx_n_dirty_i15,
1822
synth_mcx_n_clean_m15,
1923
synth_mcx_1_clean_b95,

qiskit/synthesis/multi_controlled/mcx_synthesis.py

Lines changed: 347 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212

1313
"""Module containing multi-controlled circuits synthesis with and without ancillary qubits."""
1414

15+
from __future__ import annotations
1516
from math import ceil
1617
import numpy as np
1718

18-
from qiskit.circuit import QuantumRegister
19-
from qiskit.circuit.quantumcircuit import QuantumCircuit
19+
from qiskit.exceptions import QiskitError
20+
from qiskit.circuit.quantumcircuit import QuantumCircuit, QuantumRegister, AncillaRegister
2021
from qiskit.circuit.library.standard_gates import (
2122
HGate,
2223
CU1Gate,
@@ -305,6 +306,350 @@ def synth_mcx_noaux_v24(num_ctrl_qubits: int) -> QuantumCircuit:
305306
return qc
306307

307308

309+
def _linear_depth_ladder_ops(num_ladder_qubits: int) -> tuple[QuantumCircuit, list[int]]:
310+
r"""
311+
Helper function to create linear-depth ladder operations used in Khattar and Gidney's MCX synthesis.
312+
In particular, this implements Step-1 and Step-2 on Fig. 3 of [1] except for the first and last
313+
CCX gates.
314+
315+
Args:
316+
num_ladder_qubits: No. of qubits involved in the ladder operation.
317+
318+
Returns:
319+
A tuple consisting of the linear-depth ladder circuit and the index of control qubit to
320+
apply the final CCX gate.
321+
322+
Raises:
323+
QiskitError: If num_ladder_qubits <= 2.
324+
325+
References:
326+
1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
327+
`arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
328+
"""
329+
330+
if num_ladder_qubits <= 2:
331+
raise QiskitError("n_ctrls >= 3 to use MCX ladder. Otherwise, use CCX")
332+
333+
n = num_ladder_qubits + 1
334+
qc = QuantumCircuit(n)
335+
qreg = list(range(n))
336+
337+
# up-ladder
338+
for i in range(2, n - 2, 2):
339+
qc.ccx(qreg[i + 1], qreg[i + 2], qreg[i])
340+
qc.x(qreg[i])
341+
342+
# down-ladder
343+
if n % 2 != 0:
344+
a, b, target = n - 3, n - 5, n - 6
345+
else:
346+
a, b, target = n - 1, n - 4, n - 5
347+
348+
if target > 0:
349+
qc.ccx(qreg[a], qreg[b], qreg[target])
350+
qc.x(qreg[target])
351+
352+
for i in range(target, 2, -2):
353+
qc.ccx(qreg[i], qreg[i - 1], qreg[i - 2])
354+
qc.x(qreg[i - 2])
355+
356+
mid_second_ctrl = 1 + max(0, 6 - n)
357+
final_ctrl = qreg[mid_second_ctrl] - 1
358+
return qc, final_ctrl
359+
360+
361+
def synth_mcx_1_kg24(num_ctrl_qubits: int, clean: bool = True) -> QuantumCircuit:
362+
r"""
363+
Synthesize a multi-controlled X gate with :math:`k` controls using :math:`1` ancillary qubit as
364+
described in Sec. 5 of [1].
365+
366+
Args:
367+
num_ctrl_qubits: The number of control qubits.
368+
clean: If True, the ancilla is clean, otherwise it is dirty.
369+
370+
Returns:
371+
The synthesized quantum circuit.
372+
373+
Raises:
374+
QiskitError: If num_ctrl_qubits <= 2.
375+
376+
References:
377+
1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
378+
`arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
379+
"""
380+
381+
if num_ctrl_qubits <= 2:
382+
raise QiskitError("kg24 synthesis requires at least 3 control qubits. Use CCX directly.")
383+
384+
q_controls = QuantumRegister(num_ctrl_qubits, name="ctrl")
385+
q_target = QuantumRegister(1, name="targ")
386+
q_ancilla = AncillaRegister(1, name="anc")
387+
qc = QuantumCircuit(q_controls, q_target, q_ancilla, name="mcx_linear_depth")
388+
389+
ladder_ops, final_ctrl = _linear_depth_ladder_ops(num_ctrl_qubits)
390+
qc.ccx(q_controls[0], q_controls[1], q_ancilla) # # create cond. clean ancilla
391+
qc.compose(ladder_ops, q_ancilla[:] + q_controls[:], inplace=True) # up-ladder
392+
qc.ccx(q_ancilla, q_controls[final_ctrl], q_target) # # target
393+
qc.compose( # # down-ladder
394+
ladder_ops.inverse(),
395+
q_ancilla[:] + q_controls[:],
396+
inplace=True,
397+
)
398+
qc.ccx(q_controls[0], q_controls[1], q_ancilla)
399+
400+
if not clean:
401+
# perform toggle-detection if ancilla is dirty
402+
qc.compose(ladder_ops, q_ancilla[:] + q_controls[:], inplace=True)
403+
qc.ccx(q_ancilla, q_controls[final_ctrl], q_target)
404+
qc.compose(ladder_ops.inverse(), q_ancilla[:] + q_controls[:], inplace=True)
405+
406+
return qc
407+
408+
409+
def synth_mcx_1_clean_kg24(num_ctrl_qubits: int) -> QuantumCircuit:
410+
r"""
411+
Synthesize a multi-controlled X gate with :math:`k` controls using :math:`1` clean ancillary qubit
412+
producing a circuit with :math:`2k-3` Toffoli gates and depth :math:`O(k)` as described in
413+
Sec. 5.1 of [1].
414+
415+
Args:
416+
num_ctrl_qubits: The number of control qubits.
417+
418+
Returns:
419+
The synthesized quantum circuit.
420+
421+
Raises:
422+
QiskitError: If num_ctrl_qubits <= 2.
423+
424+
References:
425+
1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
426+
`arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
427+
"""
428+
429+
return synth_mcx_1_kg24(num_ctrl_qubits, clean=True)
430+
431+
432+
def synth_mcx_1_dirty_kg24(num_ctrl_qubits: int) -> QuantumCircuit:
433+
r"""
434+
Synthesize a multi-controlled X gate with :math:`k` controls using :math:`1` dirty ancillary qubit
435+
producing a circuit with :math:`4k-8` Toffoli gates and depth :math:`O(k)` as described in
436+
Sec. 5.3 of [1].
437+
438+
Args:
439+
num_ctrl_qubits: The number of control qubits.
440+
441+
Returns:
442+
The synthesized quantum circuit.
443+
444+
Raises:
445+
QiskitError: If num_ctrl_qubits <= 2.
446+
447+
References:
448+
1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
449+
`arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
450+
"""
451+
452+
return synth_mcx_1_kg24(num_ctrl_qubits, clean=False)
453+
454+
455+
def _n_parallel_ccx_x(n: int) -> QuantumCircuit:
456+
r"""
457+
Construct a quantum circuit for creating n-condionally clean ancillae using 3n qubits. This
458+
implements Fig. 4a of [1]. The order of returned qubits is qr_a, qr_a, qr_target.
459+
460+
Args:
461+
n: Number of conditionally clean ancillae to create.
462+
463+
Returns:
464+
QuantumCircuit: The quantum circuit for creating n-condionally clean ancillae.
465+
466+
References:
467+
1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
468+
`arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
469+
"""
470+
471+
n_qubits = 3 * n
472+
q = QuantumRegister(n_qubits, name="q")
473+
qc = QuantumCircuit(q, name=f"ccxn_{n}")
474+
qr_a, qr_b, qr_target = q[:n], q[n : 2 * n], q[2 * n :]
475+
qc.x(qr_target)
476+
qc.ccx(qr_a, qr_b, qr_target)
477+
478+
return qc
479+
480+
481+
def _build_logn_depth_ccx_ladder(
482+
ancilla_idx: int, ctrls: list[int], skip_cond_clean: bool = False
483+
) -> tuple[QuantumCircuit, list[int]]:
484+
r"""
485+
Helper function to build a log-depth ladder compose of CCX and X gates as shown in Fig. 4b of [1].
486+
487+
Args:
488+
ancilla_idx: Index of the ancillary qubit.
489+
ctrls: List of control qubits.
490+
skip_cond_clean: If True, do not include the conditionally clean ancilla (step 1 and 5 in
491+
Fig. 4b of [1]).
492+
493+
Returns:
494+
A tuple consisting of the log-depth ladder circuit of conditionally clean ancillae and the
495+
list of indices of control qubit to apply the linear-depth MCX gate.
496+
497+
Raises:
498+
QiskitError: If no. of qubits in parallel CCX + X gates are not the same.
499+
500+
References:
501+
1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
502+
`arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
503+
"""
504+
505+
qc = QuantumCircuit(len(ctrls) + 1)
506+
anc = [ancilla_idx]
507+
final_ctrls = []
508+
509+
while len(ctrls) > 1:
510+
next_batch_len = min(len(anc) + 1, len(ctrls))
511+
ctrls, nxt_batch = ctrls[next_batch_len:], ctrls[:next_batch_len]
512+
new_anc = []
513+
while len(nxt_batch) > 1:
514+
ccx_n = len(nxt_batch) // 2
515+
st = int(len(nxt_batch) % 2)
516+
ccx_x, ccx_y, ccx_t = (
517+
nxt_batch[st : st + ccx_n],
518+
nxt_batch[st + ccx_n :],
519+
anc[-ccx_n:],
520+
)
521+
if not len(ccx_x) == len(ccx_y) == ccx_n >= 1:
522+
raise QiskitError(
523+
f"Invalid CCX gate parameters: {len(ccx_x)=} != {len(ccx_y)=} != {len(ccx_n)=}"
524+
)
525+
if ccx_t != [ancilla_idx]:
526+
qc.compose(_n_parallel_ccx_x(ccx_n), ccx_x + ccx_y + ccx_t, inplace=True)
527+
else:
528+
if not skip_cond_clean:
529+
qc.ccx(ccx_x[0], ccx_y[0], ccx_t[0]) # # create conditionally clean ancilla
530+
new_anc += nxt_batch[st:] # # newly created cond. clean ancilla
531+
nxt_batch = ccx_t + nxt_batch[:st]
532+
anc = anc[:-ccx_n]
533+
534+
anc = sorted(anc + new_anc)
535+
final_ctrls += nxt_batch
536+
537+
final_ctrls += ctrls
538+
final_ctrls = sorted(final_ctrls)
539+
return qc, final_ctrls[:-1] # exclude ancilla
540+
541+
542+
def synth_mcx_2_kg24(num_ctrl_qubits: int, clean: bool = True) -> QuantumCircuit:
543+
r"""
544+
Synthesize a multi-controlled X gate with :math:`k` controls using :math:`2` ancillary qubits.
545+
as described in Sec. 5 of [1].
546+
547+
Args:
548+
num_ctrl_qubits: The number of control qubits.
549+
clean: If True, the ancilla is clean, otherwise it is dirty.
550+
551+
Returns:
552+
The synthesized quantum circuit.
553+
554+
Raises:
555+
QiskitError: If num_ctrl_qubits <= 2.
556+
557+
References:
558+
1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
559+
`arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
560+
"""
561+
562+
if num_ctrl_qubits <= 2:
563+
raise QiskitError("kg24 synthesis requires at least 3 control qubits. Use CCX directly.")
564+
565+
q_control = QuantumRegister(num_ctrl_qubits, name="ctrl")
566+
q_target = QuantumRegister(1, name="targ")
567+
q_ancilla = AncillaRegister(2, name="anc")
568+
qc = QuantumCircuit(q_control, q_target, q_ancilla, name="mcx_logn_depth")
569+
570+
ladder_ops, final_ctrls = _build_logn_depth_ccx_ladder(
571+
num_ctrl_qubits, list(range(num_ctrl_qubits))
572+
)
573+
qc.compose(ladder_ops, q_control[:] + [q_ancilla[0]], inplace=True)
574+
if len(final_ctrls) == 1: # Already a toffoli
575+
qc.ccx(q_ancilla[0], q_control[final_ctrls[0]], q_target)
576+
else:
577+
mid_mcx = synth_mcx_1_clean_kg24(len(final_ctrls) + 1)
578+
qc.compose(
579+
mid_mcx,
580+
[q_ancilla[0]]
581+
+ q_control[final_ctrls]
582+
+ q_target[:]
583+
+ [q_ancilla[1]], # ctrls, targ, anc
584+
inplace=True,
585+
)
586+
qc.compose(ladder_ops.inverse(), q_control[:] + [q_ancilla[0]], inplace=True)
587+
588+
if not clean:
589+
# perform toggle-detection if ancilla is dirty
590+
ladder_ops_new, final_ctrls = _build_logn_depth_ccx_ladder(
591+
num_ctrl_qubits, list(range(num_ctrl_qubits)), skip_cond_clean=True
592+
)
593+
qc.compose(ladder_ops_new, q_control[:] + [q_ancilla[0]], inplace=True)
594+
if len(final_ctrls) == 1:
595+
qc.ccx(q_ancilla[0], q_control[final_ctrls[0]], q_target)
596+
else:
597+
qc.compose(
598+
mid_mcx,
599+
[q_ancilla[0]] + q_control[final_ctrls] + q_target[:] + [q_ancilla[1]],
600+
inplace=True,
601+
)
602+
qc.compose(ladder_ops_new.inverse(), q_control[:] + [q_ancilla[0]], inplace=True)
603+
604+
return qc
605+
606+
607+
def synth_mcx_2_clean_kg24(num_ctrl_qubits: int) -> QuantumCircuit:
608+
r"""
609+
Synthesize a multi-controlled X gate with :math:`k` controls using :math:`2` clean ancillary qubits
610+
producing a circuit with :math:`2k-3` Toffoli gates and depth :math:`O(\log(k))` as described in
611+
Sec. 5.2 of [1].
612+
613+
Args:
614+
num_ctrl_qubits: The number of control qubits.
615+
616+
Returns:
617+
The synthesized quantum circuit.
618+
619+
Raises:
620+
QiskitError: If num_ctrl_qubits <= 2.
621+
622+
References:
623+
1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
624+
`arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
625+
"""
626+
627+
return synth_mcx_2_kg24(num_ctrl_qubits, clean=True)
628+
629+
630+
def synth_mcx_2_dirty_kg24(num_ctrl_qubits: int) -> QuantumCircuit:
631+
r"""
632+
Synthesize a multi-controlled X gate with :math:`k` controls using :math:`2` dirty ancillary qubits
633+
producing a circuit with :math:`4k-8` Toffoli gates and depth :math:`O(\log(k))` as described in
634+
Sec. 5.4 of [1].
635+
636+
Args:
637+
num_ctrl_qubits: The number of control qubits.
638+
639+
Returns:
640+
The synthesized quantum circuit.
641+
642+
Raises:
643+
QiskitError: If num_ctrl_qubits <= 2.
644+
645+
References:
646+
1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
647+
`arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
648+
"""
649+
650+
return synth_mcx_2_kg24(num_ctrl_qubits, clean=False)
651+
652+
308653
def synth_c3x() -> QuantumCircuit:
309654
"""Efficient synthesis of 3-controlled X-gate."""
310655

0 commit comments

Comments
 (0)