1919
2020import numpy as np
2121
22+ import pennylane as qml
2223from pennylane import allocation , control_flow , math , ops , queuing
2324from pennylane .decomposition import (
2425 adjoint_resource_rep ,
@@ -417,12 +418,13 @@ def _mcx_many_workers_condition(num_control_wires, num_work_wires, **__):
417418
418419
419420def _mcx_many_workers_resource (num_control_wires , work_wire_type , ** __ ):
421+
422+ if work_wire_type == "borrowed" :
423+ return {ops .Toffoli : 4 * (num_control_wires - 2 )}
420424 return {
421- ops .Toffoli : (
422- 4 * (num_control_wires - 2 )
423- if work_wire_type == "borrowed"
424- else 2 * (num_control_wires - 2 ) + 1
425- )
425+ qml .TemporaryAND : num_control_wires - 2 ,
426+ adjoint_resource_rep (qml .TemporaryAND ): num_control_wires - 2 ,
427+ ops .Toffoli : 1 ,
426428 }
427429
428430
@@ -433,27 +435,32 @@ def _mcx_many_workers(wires, work_wires, work_wire_type, **__):
433435 """Decomposes the multi-controlled PauliX gate using the approach in Lemma 7.2 of
434436 https://arxiv.org/abs/quant-ph/9503016, which requires a suitably large register of
435437 work wires"""
436-
437438 target_wire , control_wires = wires [- 1 ], wires [:- 1 ]
438439 work_wires = work_wires [: len (control_wires ) - 2 ]
439440
441+ if work_wire_type == "borrowed" :
442+ up_gate = down_gate = ops .Toffoli
443+ else :
444+ down_gate = qml .TemporaryAND
445+ up_gate = ops .adjoint (qml .TemporaryAND )
446+
440447 @control_flow .for_loop (1 , len (work_wires ), 1 )
441448 def loop_up (i ):
442- ops . Toffoli (wires = [control_wires [i ], work_wires [i ], work_wires [i - 1 ]])
449+ up_gate (wires = [control_wires [i ], work_wires [i ], work_wires [i - 1 ]])
443450
444451 @control_flow .for_loop (len (work_wires ) - 1 , 0 , - 1 )
445452 def loop_down (i ):
446- ops . Toffoli (wires = [control_wires [i ], work_wires [i ], work_wires [i - 1 ]])
453+ down_gate (wires = [control_wires [i ], work_wires [i ], work_wires [i - 1 ]])
447454
448455 if work_wire_type == "borrowed" :
449456 ops .Toffoli (wires = [control_wires [0 ], work_wires [0 ], target_wire ])
450457 loop_up ()
451458
452- ops . Toffoli (wires = [control_wires [- 1 ], control_wires [- 2 ], work_wires [- 1 ]])
459+ down_gate (wires = [control_wires [- 1 ], control_wires [- 2 ], work_wires [- 1 ]])
453460 loop_down ()
454461 ops .Toffoli (wires = [control_wires [0 ], work_wires [0 ], target_wire ])
455462 loop_up ()
456- ops . Toffoli (wires = [control_wires [- 1 ], control_wires [- 2 ], work_wires [- 1 ]])
463+ up_gate (wires = [control_wires [- 1 ], control_wires [- 2 ], work_wires [- 1 ]])
457464
458465 if work_wire_type == "borrowed" :
459466 loop_down ()
@@ -497,16 +504,27 @@ def _mcx_many_borrowed_workers(wires, **kwargs):
497504
498505
499506def _mcx_two_workers_condition (num_control_wires , num_work_wires , ** __ ):
500- return num_control_wires > 2 and num_work_wires >= 2
507+ return num_control_wires > 2 and (
508+ num_work_wires >= 2 or (num_work_wires == 1 and num_control_wires < 6 )
509+ )
501510
502511
503512def _mcx_two_workers_resource (num_control_wires , work_wire_type , ** __ ):
513+
514+ is_small_mcx = num_control_wires < 6
515+
504516 if work_wire_type == "zeroed" :
505517 n_ccx = 2 * num_control_wires - 3
506- return {ops .Toffoli : n_ccx , ops .X : n_ccx - 3 if num_control_wires < 6 else n_ccx - 5 }
518+ n_temporary_ccx_pairs = 2 - is_small_mcx
519+ return {
520+ ops .Toffoli : n_ccx - 2 * n_temporary_ccx_pairs ,
521+ ops .X : n_ccx - 3 if is_small_mcx else n_ccx - 5 ,
522+ qml .TemporaryAND : n_temporary_ccx_pairs ,
523+ adjoint_resource_rep (qml .TemporaryAND ): n_temporary_ccx_pairs ,
524+ }
507525 # Otherwise, we assume the work wires are borrowed
508526 n_ccx = 4 * num_control_wires - 8
509- return {ops .Toffoli : n_ccx , ops .X : n_ccx - 4 if num_control_wires < 6 else n_ccx - 8 }
527+ return {ops .Toffoli : n_ccx , ops .X : n_ccx - 4 if is_small_mcx else n_ccx - 8 }
510528
511529
512530@register_condition (_mcx_two_workers_condition )
@@ -523,30 +541,48 @@ def _mcx_two_workers(wires, work_wires, work_wire_type, **__):
523541 `arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
524542
525543 """
526-
544+ # Unpack work wires for readability. There might just be one of them if it is a "small" MCX
545+ # (less than 6 controls)
546+ work0 , * work1 = work_wires
527547 # First use the work wire to prepare the first two control wires as conditionally clean.
528- ops .Toffoli ([wires [0 ], wires [1 ], work_wires [0 ]])
548+ left_elbow = ops .Toffoli if work_wire_type == "borrowed" else qml .TemporaryAND
549+ left_elbow ([wires [0 ], wires [1 ], work0 ])
550+
529551 middle_ctrl_indices = _build_log_n_depth_ccx_ladder (wires [:- 1 ])
530552
531- # Apply the MCX in the middle
553+ # Apply the MCX in the middle. This is just a single Toffoli without work wires for "small" MCX
532554 if len (middle_ctrl_indices ) == 1 :
533- ops .Toffoli ([work_wires [ 0 ] , wires [middle_ctrl_indices [0 ]], wires [- 1 ]])
555+ ops .Toffoli ([work0 , wires [middle_ctrl_indices [0 ]], wires [- 1 ]])
534556 else :
535557 middle_wires = [wires [i ] for i in middle_ctrl_indices ]
536- _mcx_one_worker (work_wires [:1 ] + middle_wires + wires [- 1 :], work_wires [1 :])
558+ # No toggle detection needed for the inner MCX decomposition, even for borrowed work wires
559+ _mcx_one_worker (
560+ [work0 ] + middle_wires + wires [- 1 :],
561+ work1 ,
562+ work_wire_type = work_wire_type ,
563+ _skip_toggle_detection = True ,
564+ )
537565
538566 # Uncompute the first ladder
539567 ops .adjoint (_build_log_n_depth_ccx_ladder , lazy = False )(wires [:- 1 ])
540- ops .Toffoli ([wires [0 ], wires [1 ], work_wires [0 ]])
568+
569+ right_elbow = ops .Toffoli if work_wire_type == "borrowed" else qml .adjoint (qml .TemporaryAND )
570+ right_elbow ([wires [0 ], wires [1 ], work0 ])
541571
542572 if work_wire_type == "borrowed" :
543- # Perform toggle-detection of the work wire is borrowed
573+ # Perform toggle-detection if the work wire is borrowed
544574 middle_ctrl_indices = _build_log_n_depth_ccx_ladder (wires [:- 1 ])
545575 if len (middle_ctrl_indices ) == 1 :
546- ops .Toffoli ([work_wires [ 0 ] , wires [middle_ctrl_indices [0 ]], wires [- 1 ]])
576+ ops .Toffoli ([work0 , wires [middle_ctrl_indices [0 ]], wires [- 1 ]])
547577 else :
548578 middle_wires = [wires [i ] for i in middle_ctrl_indices ]
549- _mcx_one_worker (work_wires [:1 ] + middle_wires + wires [- 1 :], work_wires [1 :])
579+ _mcx_one_worker (
580+ [work0 ] + middle_wires + wires [- 1 :],
581+ work1 ,
582+ work_wire_type = work_wire_type ,
583+ _skip_toggle_detection = True ,
584+ )
585+
550586 ops .adjoint (_build_log_n_depth_ccx_ladder , lazy = False )(wires [:- 1 ])
551587
552588
@@ -557,10 +593,11 @@ def _mcx_two_workers(wires, work_wires, work_wire_type, **__):
557593@register_condition (lambda num_control_wires , ** _ : num_control_wires > 2 )
558594@register_resources (
559595 lambda num_control_wires , ** _ : _mcx_two_workers_resource (num_control_wires , "zeroed" ),
560- work_wires = {"zeroed" : 2 },
596+ work_wires = lambda num_control_wires , ** _ : {"zeroed" : 1 + ( num_control_wires >= 6 ) },
561597)
562598def _mcx_two_zeroed_workers (wires , ** kwargs ):
563- with allocation .allocate (2 , state = "zero" , restored = True ) as work_wires :
599+ is_small_mcx = (len (wires ) - 1 ) < 6
600+ with allocation .allocate (2 - is_small_mcx , state = "zero" , restored = True ) as work_wires :
564601 kwargs .update ({"work_wires" : work_wires , "work_wire_type" : "zeroed" })
565602 _mcx_two_workers (wires , ** kwargs )
566603
@@ -572,10 +609,11 @@ def _mcx_two_zeroed_workers(wires, **kwargs):
572609@register_condition (lambda num_control_wires , ** _ : num_control_wires > 2 )
573610@register_resources (
574611 lambda num_control_wires , ** _ : _mcx_two_workers_resource (num_control_wires , "borrowed" ),
575- work_wires = {"borrowed" : 2 },
612+ work_wires = lambda num_control_wires , ** _ : {"borrowed" : 2 - ( num_control_wires < 6 ) },
576613)
577614def _mcx_two_borrowed_workers (wires , ** kwargs ):
578- with allocation .allocate (2 , state = "any" , restored = True ) as work_wires :
615+ is_small_mcx = (len (wires ) - 1 ) < 6
616+ with allocation .allocate (2 - is_small_mcx , state = "any" , restored = True ) as work_wires :
579617 kwargs .update ({"work_wires" : work_wires , "work_wire_type" : "borrowed" })
580618 _mcx_two_workers (wires , ** kwargs )
581619
@@ -589,37 +627,57 @@ def _mcx_one_worker_condition(num_control_wires, num_work_wires, **__):
589627
590628def _mcx_one_worker_resource (num_control_wires , work_wire_type , ** __ ):
591629 if work_wire_type == "zeroed" :
592- n_ccx = 2 * num_control_wires - 3
593- return {ops .Toffoli : n_ccx , ops .X : n_ccx - 3 }
630+ n_ccx = 2 * num_control_wires - 5
631+ return {
632+ ops .Toffoli : n_ccx ,
633+ qml .TemporaryAND : 1 ,
634+ adjoint_resource_rep (qml .TemporaryAND ): 1 ,
635+ ops .X : n_ccx - 1 ,
636+ }
594637 # Otherwise, we assume the work wire is borrowed
595638 n_ccx = 4 * num_control_wires - 8
596639 return {ops .Toffoli : n_ccx , ops .X : n_ccx - 4 }
597640
598641
599642@register_condition (_mcx_one_worker_condition )
600643@register_resources (_mcx_one_worker_resource )
601- def _mcx_one_worker (wires , work_wires , work_wire_type = "zeroed" , ** __ ):
644+ def _mcx_one_worker (wires , work_wires , work_wire_type = "zeroed" , _skip_toggle_detection = False , ** __ ):
602645 r"""
603646 Synthesise a multi-controlled X gate with :math:`k` controls using :math:`1` auxiliary qubit. It
604647 produces a circuit with :math:`2k-3` Toffoli gates and depth :math:`O(k)` if the auxiliary is zeroed
605648 and :math:`4k-3` Toffoli gates and depth :math:`O(k)` if the auxiliary is borrowed as described in
606649 Sec. 5.1 of [1].
607650
651+ .. note::
652+
653+ The keyword argument ``_skip_toggle_detection`` is only supposed to be used when utilizing
654+ ``_mcx_one_worker`` as a subroutine within a decomposition rule, but not when using
655+ it as a decomposition rule itself. This is because ``_mcx_one_worker_resource`` does not
656+ support/take into account this keyword argument.
657+
608658 References:
609659 1. Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits
610660 `arXiv:2407.17966 <https://arxiv.org/abs/2407.17966>`__
611661
612662 """
613-
614- ops .Toffoli ([wires [0 ], wires [1 ], work_wires [0 ]])
663+ if work_wire_type == "borrowed" :
664+ ops .Toffoli ([wires [0 ], wires [1 ], work_wires [0 ]])
665+ else :
666+ _skip_toggle_detection = True
667+ qml .TemporaryAND ([wires [0 ], wires [1 ], work_wires [0 ]])
615668
616669 final_ctrl_index = _build_linear_depth_ladder (wires [:- 1 ])
617670 ops .Toffoli ([work_wires [0 ], wires [final_ctrl_index ], wires [- 1 ]])
618671 ops .adjoint (_build_linear_depth_ladder , lazy = False )(wires [:- 1 ])
619- ops .Toffoli ([wires [0 ], wires [1 ], work_wires [0 ]])
620672
621673 if work_wire_type == "borrowed" :
622- # Perform toggle-detection of the work wire is borrowed
674+ ops .Toffoli ([wires [0 ], wires [1 ], work_wires [0 ]])
675+ else :
676+ ops .adjoint (qml .TemporaryAND ([wires [0 ], wires [1 ], work_wires [0 ]]))
677+
678+ if not _skip_toggle_detection :
679+ # Perform toggle-detection unless skipped explicitly. By default, toggle detection
680+ # is skipped for `work_wire_type="zeroed"` but not for `work_wire_type="borrowed"`.
623681 _build_linear_depth_ladder (wires [:- 1 ])
624682 ops .Toffoli ([work_wires [0 ], wires [final_ctrl_index ], wires [- 1 ]])
625683 ops .adjoint (_build_linear_depth_ladder , lazy = False )(wires [:- 1 ])
0 commit comments