Skip to content

Commit 66b1a0f

Browse files
authored
Merge pull request #1559 from qiboteam/orthogonal_encoders
Add nontrivial entangling layers to `models.encodings.entangling_layer`
2 parents e7d93e6 + 9f8e725 commit 66b1a0f

File tree

6 files changed

+303
-84
lines changed

6 files changed

+303
-84
lines changed

doc/source/api-reference/qibo.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,9 @@ any of the two-qubit gates implemented in ``qibo`` can be selected to customize
377377
If the chosen gate is parametrized, all phases are set to :math:`0.0`.
378378
Note that these phases can be updated a posterior by using
379379
:meth:`qibo.models.Circuit.set_parameters`.
380-
There are four possible choices of layer ``architecture``:
381-
``diagonal``, ``shifted``, ``even-layer``, and ``odd-layer``.
382-
For instance, we show below an example of each architecture for ``nqubits = 6``.
380+
The possible choices of layer ``architecture`` are the following, in alphabetical order:
381+
``diagonal``, ``even_layer``, ``next_nearest``, ``pyramid``, ``odd_layer``, ``shifted``, ``v``, and ``x``.
382+
For instance, we show below an example of four of those architectures for ``nqubits = 6`` and ``entangling_gate = "CNOT"``.
383383

384384

385385
.. image:: ../_static/entangling_layer.png

src/qibo/models/encodings.py

+225-39
Original file line numberDiff line numberDiff line change
@@ -427,20 +427,23 @@ def entangling_layer(
427427
closed_boundary: bool = False,
428428
**kwargs,
429429
):
430-
"""Create a layer of two-qubit, entangling gates.
430+
"""Create a layer of two-qubit entangling gates.
431431
432432
If the chosen gate is a parametrized gate, all phases are set to :math:`0.0`.
433433
434434
Args:
435435
nqubits (int): Total number of qubits in the circuit.
436436
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"``.
439441
entangling_gate (str or :class:`qibo.gates.Gate`, optional): Two-qubit gate to be used
440442
in the entangling layer. If ``entangling_gate`` is a parametrized gate,
441443
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``.
444447
kwargs (dict, optional): Additional arguments used to initialize a Circuit object.
445448
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.
446449
@@ -464,16 +467,30 @@ def entangling_layer(
464467
f"``architecture`` must be type str, but it is type {type(architecture)}.",
465468
)
466469

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+
]:
468480
raise_error(
469481
NotImplementedError,
470482
f"``architecture`` {architecture} not found.",
471483
)
472484

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+
473490
if not isinstance(closed_boundary, bool):
474491
raise_error(
475492
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)}.",
477494
)
478495

479496
gate = (
@@ -488,46 +505,44 @@ def entangling_layer(
488505
"This function does not support the ``GeneralizedfSim`` gate.",
489506
)
490507

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,
497515
)
516+
else:
517+
# Finds the correct number of parameters to initialize the gate class.
518+
parameters = list(signature(gate).parameters)
498519

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+
)
501524

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))
503538

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-
)
524539
circuit.add(
525540
_parametrized_two_qubit_gate(gate, qubit, qubit + 1, parameters)
526-
for qubit in range(1, nqubits - 1, 2)
541+
for qubit in qubits
527542
)
528543

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))
531546

532547
return circuit
533548

@@ -657,11 +672,153 @@ def _parametrized_two_qubit_gate(gate, q0, q1, params=None):
657672
return gate(q0, q1)
658673

659674

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+
660815
def _angle_mod_two_pi(angle):
816+
"""Return angle mod 2pi."""
661817
return angle % (2 * np.pi)
662818

663819

664820
def _get_markers(bitstring, last_run: bool = False):
821+
"""Subroutine of the Ehrlich algorithm."""
665822
nqubits = len(bitstring)
666823
markers = [len(bitstring) - 1]
667824
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):
679836

680837

681838
def _get_next_bistring(bitstring, markers, hamming_weight):
839+
"""Subroutine of the Ehrlich algorithm."""
682840
if len(markers) == 0: # pragma: no cover
683841
return bitstring
684842

@@ -716,6 +874,32 @@ def _get_next_bistring(bitstring, markers, hamming_weight):
716874

717875

718876
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+
"""
719903
k = np.unique(initial_string, return_counts=True)
720904
if len(k[1]) == 1: # pragma: no cover
721905
return ["".join([str(item) for item in initial_string])]
@@ -805,6 +989,8 @@ def _get_gate(
805989

806990

807991
def _get_phase_gate_correction(last_string, phase: float):
992+
"""Return final gate of HW-k circuits that encode complex data."""
993+
808994
# to avoid circular import error
809995
from qibo.quantum_info.utils import hamming_weight
810996

src/qibo/transpiler/asserts.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def assert_placement(circuit: Circuit, connectivity: nx.Graph):
137137
raise_error(
138138
PlacementError,
139139
f"Number of qubits in the circuit ({circuit.nqubits}) "
140-
+ f"does not match the number of qubits in the layout ({len(circuit.wire_names)}) "
140+
+ f"does not match either the number of qubits in the layout ({len(circuit.wire_names)}) "
141141
+ f"or the connectivity graph ({len(connectivity.nodes)}).",
142142
)
143143
if set(circuit.wire_names) != set(connectivity.nodes):

0 commit comments

Comments
 (0)