@@ -427,20 +427,23 @@ def entangling_layer(
427
427
closed_boundary : bool = False ,
428
428
** kwargs ,
429
429
):
430
- """Create a layer of two-qubit, entangling gates.
430
+ """Create a layer of two-qubit entangling gates.
431
431
432
432
If the chosen gate is a parametrized gate, all phases are set to :math:`0.0`.
433
433
434
434
Args:
435
435
nqubits (int): Total number of qubits in the circuit.
436
436
architecture (str, optional): Architecture of the entangling layer.
437
- Options are ``diagonal``, ``shifted``, ``even-layer``, and ``odd-layer``.
438
- Defaults to ``"diagonal"``.
437
+ In alphabetical order, options are ``"diagonal"``, ``"even_layer"``,
438
+ ``"next_nearest"``, ``"odd_layer"``, ``"pyramid"``, ``"shifted"``,
439
+ ``"v"``, and ``"x"``. The ``"x"`` architecture is only defined for an even number
440
+ of qubits. Defaults to ``"diagonal"``.
439
441
entangling_gate (str or :class:`qibo.gates.Gate`, optional): Two-qubit gate to be used
440
442
in the entangling layer. If ``entangling_gate`` is a parametrized gate,
441
443
all phases are initialized as :math:`0.0`. Defaults to ``"CNOT"``.
442
- closed_boundary (bool, optional): If ``True`` adds a closed-boundary condition
443
- to the entangling layer. Defaults to ``False``.
444
+ closed_boundary (bool, optional): If ``True`` and ``architecture not in
445
+ ["pyramid", "v", "x"]``, adds a closed-boundary condition to the entangling layer.
446
+ Defaults to ``False``.
444
447
kwargs (dict, optional): Additional arguments used to initialize a Circuit object.
445
448
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.
446
449
@@ -464,16 +467,30 @@ def entangling_layer(
464
467
f"``architecture`` must be type str, but it is type { type (architecture )} ." ,
465
468
)
466
469
467
- if architecture not in ["diagonal" , "shifted" , "even-layer" , "odd-layer" ]:
470
+ if architecture not in [
471
+ "diagonal" ,
472
+ "even_layer" ,
473
+ "next_nearest" ,
474
+ "odd_layer" ,
475
+ "pyramid" ,
476
+ "shifted" ,
477
+ "v" ,
478
+ "x" ,
479
+ ]:
468
480
raise_error (
469
481
NotImplementedError ,
470
482
f"``architecture`` { architecture } not found." ,
471
483
)
472
484
485
+ if architecture == "x" and nqubits % 2 != 0.0 :
486
+ raise_error (
487
+ ValueError , "``x`` architecture only defined for an even number of qubits."
488
+ )
489
+
473
490
if not isinstance (closed_boundary , bool ):
474
491
raise_error (
475
492
TypeError ,
476
- f"closed_boundary must be type bool, but it is type { type (closed_boundary )} ." ,
493
+ f"`` closed_boundary`` must be type bool, but it is type { type (closed_boundary )} ." ,
477
494
)
478
495
479
496
gate = (
@@ -488,46 +505,44 @@ def entangling_layer(
488
505
"This function does not support the ``GeneralizedfSim`` gate." ,
489
506
)
490
507
491
- # Finds the number of correct number of parameters to initialize the gate class.
492
- parameters = list (signature (gate ).parameters )
493
-
494
- if "q2" in parameters :
495
- raise_error (
496
- NotImplementedError , f"This function does not accept three-qubit gates."
508
+ if architecture in ["next_nearest" , "pyramid" , "v" , "x" ]:
509
+ circuit = _non_trivial_layers (
510
+ nqubits ,
511
+ architecture = architecture ,
512
+ entangling_gate = entangling_gate ,
513
+ closed_boundary = closed_boundary ,
514
+ ** kwargs ,
497
515
)
516
+ else :
517
+ # Finds the correct number of parameters to initialize the gate class.
518
+ parameters = list (signature (gate ).parameters )
498
519
499
- # If gate is parametrized, sets all angles to 0.0
500
- parameters = (0.0 ,) * (len (parameters ) - 3 ) if len (parameters ) > 2 else None
520
+ if "q2" in parameters :
521
+ raise_error (
522
+ NotImplementedError , f"This function does not accept three-qubit gates."
523
+ )
501
524
502
- circuit = Circuit (nqubits , ** kwargs )
525
+ # If gate is parametrized, sets all angles to 0.0
526
+ parameters = (0.0 ,) * (len (parameters ) - 3 ) if len (parameters ) > 2 else None
527
+
528
+ circuit = Circuit (nqubits , ** kwargs )
529
+
530
+ if architecture == "diagonal" :
531
+ qubits = range (nqubits - 1 )
532
+ elif architecture == "even_layer" :
533
+ qubits = range (0 , nqubits - 1 , 2 )
534
+ elif architecture == "odd_layer" :
535
+ qubits = range (1 , nqubits - 1 , 2 )
536
+ else :
537
+ qubits = tuple (range (0 , nqubits - 1 , 2 )) + tuple (range (1 , nqubits - 1 , 2 ))
503
538
504
- if architecture == "diagonal" :
505
- circuit .add (
506
- _parametrized_two_qubit_gate (gate , qubit , qubit + 1 , parameters )
507
- for qubit in range (nqubits - 1 )
508
- )
509
- elif architecture == "even-layer" :
510
- circuit .add (
511
- _parametrized_two_qubit_gate (gate , qubit , qubit + 1 , parameters )
512
- for qubit in range (0 , nqubits - 1 , 2 )
513
- )
514
- elif architecture == "odd-layer" :
515
- circuit .add (
516
- _parametrized_two_qubit_gate (gate , qubit , qubit + 1 , parameters )
517
- for qubit in range (1 , nqubits - 1 , 2 )
518
- )
519
- else :
520
- circuit .add (
521
- _parametrized_two_qubit_gate (gate , qubit , qubit + 1 , parameters )
522
- for qubit in range (0 , nqubits - 1 , 2 )
523
- )
524
539
circuit .add (
525
540
_parametrized_two_qubit_gate (gate , qubit , qubit + 1 , parameters )
526
- for qubit in range ( 1 , nqubits - 1 , 2 )
541
+ for qubit in qubits
527
542
)
528
543
529
- if closed_boundary :
530
- circuit .add (_parametrized_two_qubit_gate (gate , nqubits - 1 , 0 , parameters ))
544
+ if closed_boundary :
545
+ circuit .add (_parametrized_two_qubit_gate (gate , nqubits - 1 , 0 , parameters ))
531
546
532
547
return circuit
533
548
@@ -657,11 +672,153 @@ def _parametrized_two_qubit_gate(gate, q0, q1, params=None):
657
672
return gate (q0 , q1 )
658
673
659
674
675
+ def _next_nearest_layer (
676
+ nqubits : int , gate , parameters , closed_boundary : bool , ** kwargs
677
+ ):
678
+ """Create entangling layer with next-nearest-neighbour connectivity."""
679
+ circuit = Circuit (nqubits , ** kwargs )
680
+ circuit .add (
681
+ _parametrized_two_qubit_gate (gate , qubit , qubit + 2 , parameters )
682
+ for qubit in range (nqubits - 2 )
683
+ )
684
+
685
+ if closed_boundary :
686
+ circuit .add (_parametrized_two_qubit_gate (gate , nqubits - 1 , 0 , parameters ))
687
+
688
+ return circuit
689
+
690
+
691
+ def _pyramid_layer (nqubits : int , gate , parameters , ** kwargs ):
692
+ """Create entangling layer in triangular shape."""
693
+ _ , pairs_gates = _generate_rbs_pairs (nqubits , architecture = "diagonal" )
694
+ pairs_gates = pairs_gates [::- 1 ]
695
+
696
+ circuit = Circuit (nqubits , ** kwargs )
697
+ circuit .add (
698
+ _parametrized_two_qubit_gate (gate , pair [0 ][1 ], pair [0 ][0 ], parameters )
699
+ for pair in pairs_gates
700
+ )
701
+ circuit .add (
702
+ _parametrized_two_qubit_gate (gate , pair [0 ][1 ], pair [0 ][0 ], parameters )
703
+ for k in range (1 , len (pairs_gates ))
704
+ for pair in pairs_gates [:- k ]
705
+ )
706
+
707
+ return circuit
708
+
709
+
710
+ def _v_layer (nqubits : int , gate , parameters , ** kwargs ):
711
+ """Create entangling layer in V shape."""
712
+ _ , pairs_gates = _generate_rbs_pairs (nqubits , architecture = "diagonal" )
713
+ pairs_gates = pairs_gates [::- 1 ]
714
+
715
+ circuit = Circuit (nqubits , ** kwargs )
716
+ circuit .add (
717
+ _parametrized_two_qubit_gate (gate , pair [0 ][1 ], pair [0 ][0 ], parameters )
718
+ for pair in pairs_gates
719
+ )
720
+ circuit .add (
721
+ _parametrized_two_qubit_gate (gate , pair [0 ][1 ], pair [0 ][0 ], parameters )
722
+ for pair in pairs_gates [::- 1 ][1 :]
723
+ )
724
+
725
+ return circuit
726
+
727
+
728
+ def _x_layer (nqubits , gate , parameters , ** kwargs ):
729
+ """Create entangling layer in X shape."""
730
+ _ , pairs_gates = _generate_rbs_pairs (nqubits , architecture = "diagonal" )
731
+ pairs_gates = pairs_gates [::- 1 ]
732
+
733
+ middle = int (np .floor (len (pairs_gates ) / 2 ))
734
+ pairs_1 = pairs_gates [:middle ]
735
+ pairs_2 = pairs_gates [- middle :]
736
+
737
+ circuit = Circuit (nqubits , ** kwargs )
738
+
739
+ for first , second in zip (pairs_1 , pairs_2 [::- 1 ]):
740
+ circuit .add (
741
+ _parametrized_two_qubit_gate (gate , first [0 ][1 ], first [0 ][0 ], parameters )
742
+ )
743
+ circuit .add (
744
+ _parametrized_two_qubit_gate (gate , second [0 ][1 ], second [0 ][0 ], parameters )
745
+ )
746
+
747
+ circuit .add (
748
+ _parametrized_two_qubit_gate (
749
+ gate ,
750
+ pairs_gates [middle ][0 ][1 ],
751
+ pairs_gates [middle ][0 ][0 ],
752
+ parameters ,
753
+ )
754
+ )
755
+
756
+ for first , second in zip (pairs_1 [::- 1 ], pairs_2 ):
757
+ circuit .add (
758
+ _parametrized_two_qubit_gate (gate , first [0 ][1 ], first [0 ][0 ], parameters )
759
+ )
760
+ circuit .add (
761
+ _parametrized_two_qubit_gate (gate , second [0 ][1 ], second [0 ][0 ], parameters )
762
+ )
763
+
764
+ return circuit
765
+
766
+
767
+ def _non_trivial_layers (
768
+ nqubits : int ,
769
+ architecture : str = "pyramid" ,
770
+ entangling_gate : Union [str , gates .Gate ] = "RBS" ,
771
+ closed_boundary : bool = False ,
772
+ ** kwargs ,
773
+ ):
774
+ """Create more intricate entangling layers of different shapes.
775
+
776
+ Args:
777
+ nqubits (int): number of qubits.
778
+ architecture (str, optional): Architecture of the entangling layer.
779
+ In alphabetical order, options are ``"next_nearest"``, ``"pyramid"``,
780
+ ``"v"``, and ``"x"``. The ``"x"`` architecture is only defined for
781
+ an even number of qubits. Defaults to ``"pyramid"``.
782
+ entangling_gate (str or :class:`qibo.gates.Gate`, optional): Two-qubit gate to be used
783
+ in the entangling layer. If ``entangling_gate`` is a parametrized gate,
784
+ all phases are initialized as :math:`0.0`. Defaults to ``"CNOT"``.
785
+ closed_boundary (bool, optional): If ``True`` and ``architecture="next_nearest"``,
786
+ adds a closed-boundary condition to the entangling layer. Defaults to ``False``.
787
+ kwargs (dict, optional): Additional arguments used to initialize a Circuit object.
788
+ For details, see the documentation of :class:`qibo.models.circuit.Circuit`.
789
+
790
+ Returns:
791
+ :class:`qibo.models.circuit.Circuit`: Circuit containing layer of two-qubit gates.
792
+ """
793
+
794
+ gate = (
795
+ getattr (gates , entangling_gate )
796
+ if isinstance (entangling_gate , str )
797
+ else entangling_gate
798
+ )
799
+
800
+ parameters = list (signature (gate ).parameters )
801
+ parameters = (0.0 ,) * (len (parameters ) - 3 ) if len (parameters ) > 2 else None
802
+
803
+ if architecture == "next_nearest" :
804
+ return _next_nearest_layer (nqubits , gate , parameters , closed_boundary , ** kwargs )
805
+
806
+ if architecture == "v" :
807
+ return _v_layer (nqubits , gate , parameters , ** kwargs )
808
+
809
+ if architecture == "x" :
810
+ return _x_layer (nqubits , gate , parameters , ** kwargs )
811
+
812
+ return _pyramid_layer (nqubits , gate , parameters , ** kwargs )
813
+
814
+
660
815
def _angle_mod_two_pi (angle ):
816
+ """Return angle mod 2pi."""
661
817
return angle % (2 * np .pi )
662
818
663
819
664
820
def _get_markers (bitstring , last_run : bool = False ):
821
+ """Subroutine of the Ehrlich algorithm."""
665
822
nqubits = len (bitstring )
666
823
markers = [len (bitstring ) - 1 ]
667
824
for ind , value in zip (range (nqubits - 2 , - 1 , - 1 ), bitstring [::- 1 ][1 :]):
@@ -679,6 +836,7 @@ def _get_markers(bitstring, last_run: bool = False):
679
836
680
837
681
838
def _get_next_bistring (bitstring , markers , hamming_weight ):
839
+ """Subroutine of the Ehrlich algorithm."""
682
840
if len (markers ) == 0 : # pragma: no cover
683
841
return bitstring
684
842
@@ -716,6 +874,32 @@ def _get_next_bistring(bitstring, markers, hamming_weight):
716
874
717
875
718
876
def _ehrlich_algorithm (initial_string , return_indices : bool = True ):
877
+ """Return list of bitstrings with mininal Hamming distance between consecutive strings.
878
+
879
+ Based on the Gray code called Ehrlich algorithm. For more details, please see Ref. [1].
880
+
881
+ Args:
882
+ initial_string (ndarray): initial bitstring as an :math:`1`-dimensional array
883
+ of size :math:`n`. All ones in the bitstring need to be consecutive.
884
+ For instance, for :math:`n = 6` and :math:`k = 2`, the bistrings
885
+ :math:`000011` and :math:`001100` are examples of acceptable inputs.
886
+ In contrast, :math:`001001` is not an acceptable input.
887
+ return_indices (bool, optional): if ``True``, returns the list of indices of
888
+ qubits that act like controls and targets of the circuit to be created.
889
+ Defaults to ``True``.
890
+
891
+ Returns:
892
+ list or tuple(list, list): If ``return_indices=False``, returns list containing
893
+ sequence of bistrings in the order generated by the Gray code.
894
+ If ``return_indices=True`` returns tuple with the aforementioned list and the list
895
+ of control anf target qubits of gates to be implemented based on the sequence of
896
+ bitstrings.
897
+
898
+ References:
899
+ 1. R. M. S. Farias, T. O. Maciel, G. Camilo, R. Lin, S. Ramos-Calderer, and L. Aolita,
900
+ *Quantum encoder for fixed Hamming-weight subspaces*
901
+ `arXiv:2405.20408 [quant-ph] <https://arxiv.org/abs/2405.20408>`_.
902
+ """
719
903
k = np .unique (initial_string , return_counts = True )
720
904
if len (k [1 ]) == 1 : # pragma: no cover
721
905
return ["" .join ([str (item ) for item in initial_string ])]
@@ -805,6 +989,8 @@ def _get_gate(
805
989
806
990
807
991
def _get_phase_gate_correction (last_string , phase : float ):
992
+ """Return final gate of HW-k circuits that encode complex data."""
993
+
808
994
# to avoid circular import error
809
995
from qibo .quantum_info .utils import hamming_weight
810
996
0 commit comments