diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index 5e376e58ca..df449e8e78 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -386,6 +386,14 @@ of the :math:`d`-dimensional array is sampled from a Gaussian distribution .. autofunction:: qibo.models.encodings.unary_encoder_random_gaussian +Entangling layer +"""""""""""""""" + +Generates a layer of nearest-neighbour two-qubit gates, assuming 1-dimensional connectivity. + +.. autofunction:: qibo.models.encodings.entangling_layer + + .. _error-mitigation: Error Mitigation diff --git a/src/qibo/models/__init__.py b/src/qibo/models/__init__.py index cf55bf7d52..71963b9b50 100644 --- a/src/qibo/models/__init__.py +++ b/src/qibo/models/__init__.py @@ -1,6 +1,12 @@ from qibo.models import hep, tsp from qibo.models.circuit import Circuit -from qibo.models.encodings import unary_encoder +from qibo.models.encodings import ( + comp_basis_encoder, + entangling_layer, + phase_encoder, + unary_encoder, + unary_encoder_random_gaussian, +) from qibo.models.error_mitigation import CDR, ICS, ZNE, vnCDR from qibo.models.evolution import AdiabaticEvolution, StateEvolution from qibo.models.grover import Grover diff --git a/src/qibo/models/encodings.py b/src/qibo/models/encodings.py index 8d37bb8922..cb6a1bc73f 100644 --- a/src/qibo/models/encodings.py +++ b/src/qibo/models/encodings.py @@ -1,10 +1,10 @@ """Module with functions that encode classical data into quantum circuits.""" import math +from inspect import signature from typing import Optional, Union import numpy as np -from scipy.stats import rv_continuous from qibo import gates from qibo.config import raise_error @@ -227,6 +227,10 @@ def unary_encoder_random_gaussian(nqubits: int, architecture: str = "tree", seed TypeError, "seed must be either type int or numpy.random.Generator." ) + from qibo.quantum_info.random_ensembles import ( # pylint: disable=C0415 + _ProbabilityDistributionGaussianLoader, + ) + local_state = ( np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed ) @@ -249,6 +253,115 @@ def unary_encoder_random_gaussian(nqubits: int, architecture: str = "tree", seed return circuit +def entangling_layer( + nqubits: int, + architecture: str = "diagonal", + entangling_gate: Union[str, gates.Gate] = "CNOT", + closed_boundary: bool = False, +): + """Creates a layer of two-qubit, entangling gates. + + If the chosen gate is a parametrized gate, all phases are set to :math:`0.0`. + + Args: + nqubits (int): Total number of qubits in the circuit. + architecture (str, optional): Architecture of the entangling layer. + Options are ``diagonal``, ``shifted``, ``even-layer``, and ``odd-layer``. + Defaults to ``"diagonal"``. + entangling_gate (str or :class:`qibo.gates.Gate`, optional): Two-qubit gate to be used + in the entangling layer. If ``entangling_gate`` is a parametrized gate, + all phases are initialized as :math:`0.0`. Defaults to ``"CNOT"``. + closed_boundary (bool, optional): If ``True`` adds a closed-boundary condition + to the entangling layer. Defaults to ``False``. + + Returns: + :class:`qibo.models.circuit.Circuit`: Circuit containing layer of two-qubit gates. + """ + + if not isinstance(nqubits, int): + raise_error( + TypeError, f"nqubits must be type int, but it is type {type(nqubits)}." + ) + + if nqubits <= 0.0: + raise_error( + ValueError, f"nqubits must be a positive integer, but it is {nqubits}." + ) + + if not isinstance(architecture, str): + raise_error( + TypeError, + f"``architecture`` must be type str, but it is type {type(architecture)}.", + ) + + if architecture not in ["diagonal", "shifted", "even-layer", "odd-layer"]: + raise_error( + NotImplementedError, + f"``architecture`` {architecture} not found.", + ) + + if not isinstance(closed_boundary, bool): + raise_error( + TypeError, + f"closed_boundary must be type bool, but it is type {type(closed_boundary)}.", + ) + + gate = ( + getattr(gates, entangling_gate) + if isinstance(entangling_gate, str) + else entangling_gate + ) + + if gate.__name__ == "GeneralizedfSim": + raise_error( + NotImplementedError, + "This function does not support the ``GeneralizedfSim`` gate.", + ) + + # Finds the number of correct number of parameters to initialize the gate class. + parameters = list(signature(gate).parameters) + + if "q2" in parameters: + raise_error( + NotImplementedError, f"This function does not accept three-qubit gates." + ) + + # If gate is parametrized, sets all angles to 0.0 + parameters = (0.0,) * (len(parameters) - 3) if len(parameters) > 2 else None + + circuit = Circuit(nqubits) + + if architecture == "diagonal": + circuit.add( + _parametrized_two_qubit_gate(gate, qubit, qubit + 1, parameters) + for qubit in range(nqubits - 1) + ) + elif architecture == "even-layer": + circuit.add( + _parametrized_two_qubit_gate(gate, qubit, qubit + 1, parameters) + for qubit in range(0, nqubits - 1, 2) + ) + elif architecture == "odd-layer": + circuit.add( + _parametrized_two_qubit_gate(gate, qubit, qubit + 1, parameters) + for qubit in range(1, nqubits - 1, 2) + ) + else: + circuit.add( + _parametrized_two_qubit_gate(gate, qubit, qubit + 1, parameters) + for qubit in range(0, nqubits - 1, 2) + ) + circuit.add( + _parametrized_two_qubit_gate(gate, qubit, qubit + 1, parameters) + for qubit in range(1, nqubits - 1, 2) + ) + + if closed_boundary: + circuit.add(_parametrized_two_qubit_gate(gate, nqubits - 1, 0, parameters)) + + return circuit + + def _generate_rbs_pairs(nqubits: int, architecture: str): """Generating list of indexes representing the RBS connections @@ -334,13 +447,9 @@ def _generate_rbs_angles(data, nqubits: int, architecture: str): return phases -class _ProbabilityDistributionGaussianLoader(rv_continuous): - """Probability density function for sampling phases of - the RBS gates as a function of circuit depth.""" - - def _pdf(self, theta: float, depth: int): - amplitude = 2 * math.gamma(2 ** (depth - 1)) / math.gamma(2 ** (depth - 2)) ** 2 - - probability = abs(math.sin(theta) * math.cos(theta)) ** (2 ** (depth - 1) - 1) +def _parametrized_two_qubit_gate(gate, q0, q1, params=None): + """Returns two-qubit gate initialized with or without phases.""" + if params is not None: + return gate(q0, q1, *params) - return amplitude * probability / 4 + return gate(q0, q1) diff --git a/src/qibo/quantum_info/random_ensembles.py b/src/qibo/quantum_info/random_ensembles.py index 94159cf1b2..e388baf62c 100644 --- a/src/qibo/quantum_info/random_ensembles.py +++ b/src/qibo/quantum_info/random_ensembles.py @@ -1,5 +1,6 @@ """Module with functions that create random quantum and classical objects.""" +import math import warnings from typing import Optional, Union @@ -20,6 +21,18 @@ ) +class _ProbabilityDistributionGaussianLoader(rv_continuous): + """Probability density function for sampling phases of + the RBS gates as a function of circuit depth.""" + + def _pdf(self, theta: float, depth: int): + amplitude = 2 * math.gamma(2 ** (depth - 1)) / math.gamma(2 ** (depth - 2)) ** 2 + + probability = abs(math.sin(theta) * math.cos(theta)) ** (2 ** (depth - 1) - 1) + + return amplitude * probability / 4 + + class _probability_distribution_sin(rv_continuous): # pragma: no cover def _pdf(self, theta: float): return 0.5 * np.sin(theta) diff --git a/tests/test_models_encodings.py b/tests/test_models_encodings.py index 63cae6836b..98e2ed7143 100644 --- a/tests/test_models_encodings.py +++ b/tests/test_models_encodings.py @@ -7,8 +7,10 @@ import pytest from scipy.optimize import curve_fit +from qibo import Circuit, gates from qibo.models.encodings import ( comp_basis_encoder, + entangling_layer, phase_encoder, unary_encoder, unary_encoder_random_gaussian, @@ -184,3 +186,75 @@ def test_unary_encoder_random_gaussian(backend, nqubits, seed): backend.assert_allclose(0.0, mean, atol=1e-1) backend.assert_allclose(stddev, theoretical_norm, atol=1e-1) + + +def test_entangling_layer_errors(): + with pytest.raises(TypeError): + entangling_layer(10.5) + with pytest.raises(ValueError): + entangling_layer(-4) + with pytest.raises(TypeError): + entangling_layer(10, architecture=True) + with pytest.raises(NotImplementedError): + entangling_layer(10, architecture="qibo") + with pytest.raises(TypeError): + entangling_layer(10, closed_boundary="True") + with pytest.raises(NotImplementedError): + entangling_layer(10, entangling_gate=gates.GeneralizedfSim) + with pytest.raises(NotImplementedError): + entangling_layer(10, entangling_gate=gates.TOFFOLI) + + +@pytest.mark.parametrize("closed_boundary", [False, True]) +@pytest.mark.parametrize("entangling_gate", ["CNOT", gates.CZ, gates.RBS]) +@pytest.mark.parametrize( + "architecture", ["diagonal", "shifted", "even-layer", "odd-layer"] +) +@pytest.mark.parametrize("nqubits", [4, 9]) +def test_entangling_layer(nqubits, architecture, entangling_gate, closed_boundary): + target_circuit = Circuit(nqubits) + if architecture == "diagonal": + target_circuit.add( + _helper_entangling_test(entangling_gate, qubit) + for qubit in range(nqubits - 1) + ) + elif architecture == "even-layer": + target_circuit.add( + _helper_entangling_test(entangling_gate, qubit) + for qubit in range(0, nqubits - 1, 2) + ) + elif architecture == "odd-layer": + target_circuit.add( + _helper_entangling_test(entangling_gate, qubit) + for qubit in range(1, nqubits - 1, 2) + ) + else: + target_circuit.add( + _helper_entangling_test(entangling_gate, qubit) + for qubit in range(0, nqubits - 1, 2) + ) + target_circuit.add( + _helper_entangling_test(entangling_gate, qubit) + for qubit in range(1, nqubits - 1, 2) + ) + + if closed_boundary: + target_circuit.add(_helper_entangling_test(entangling_gate, nqubits - 1, 0)) + + circuit = entangling_layer(nqubits, architecture, entangling_gate, closed_boundary) + for gate, target in zip(circuit.queue, target_circuit.queue): + assert gate.__class__.__name__ == target.__class__.__name__ + + +def _helper_entangling_test(gate, qubit_0, qubit_1=None): + """Creates two-qubit gate with of without parameters.""" + if qubit_1 is None: + qubit_1 = qubit_0 + 1 + + if callable(gate) and gate.__name__ == "RBS": + return gate(qubit_0, qubit_1, 0.0) + + if gate == "CNOT": + gate = gates.CNOT + + return gate(qubit_0, qubit_1)