From c9301da0ed9b2a9650d8519f8b6180a98378c6ca Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 13 Mar 2024 12:41:11 +0400 Subject: [PATCH 1/7] move distribution --- src/qibo/quantum_info/random_ensembles.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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) From 9e8519d56d0332a8dffd17e6d1411fdbd1a00d0d Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 13 Mar 2024 12:41:21 +0400 Subject: [PATCH 2/7] `entangling_layer` --- src/qibo/models/encodings.py | 104 ++++++++++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 9 deletions(-) diff --git a/src/qibo/models/encodings.py b/src/qibo/models/encodings.py index 8d37bb8922..ceb5a390fe 100644 --- a/src/qibo/models/encodings.py +++ b/src/qibo/models/encodings.py @@ -1,6 +1,7 @@ """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 @@ -9,6 +10,7 @@ from qibo import gates from qibo.config import raise_error from qibo.models.circuit import Circuit +from qibo.quantum_info.random_ensembles import _ProbabilityDistributionGaussianLoader def comp_basis_encoder( @@ -249,6 +251,94 @@ 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, +): + + 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(entangling_gate, (str, gates.Gate)): + raise_error( + TypeError, + "``entangling_gate`` must be either str or ``qibo.gates.Gate``, " + + f"but it is type {type(entangling_gate)}.", + ) + + if not isinstance(closed_boundary, bool): + raise_error( + TypeError, + f"closed_boundary must be type bool, but it is type {type(closed_boundary)}.", + ) + + if isinstance(entangling_gate, str): + gate = getattr(gates, entangling_gate) + + if isinstance(gate, gates.GeneralizedfSim): + raise_error( + ValueError, "This function does not support the ``GeneralizedfSim`` gate." + ) + + # Finds the number of correct number of parameters to initialize the gate class. + # If gate is parametrized, sets all angles to 0.0 + parameters = list(signature(gate).parameters) + parameters = (0.0,) * (len(parameters) - 3) if len(parameters) > 2 else None + + circuit = Circuit(nqubits) + + if architecture == "diagonal": + circuit.add( + _parametrized_two_qubit_gate(qubit, qubit + 1, parameters) + for qubit in range(nqubits - 1) + ) + elif architecture == "even-layer": + circuit.add( + _parametrized_two_qubit_gate(qubit, qubit + 1, parameters) + for qubit in range(0, nqubits - 1, 2) + ) + elif architecture == "odd-layer": + circuit.add( + _parametrized_two_qubit_gate(qubit, qubit + 1, parameters) + for qubit in range(1, nqubits - 1, 2) + ) + else: + circuit.add( + _parametrized_two_qubit_gate(qubit, qubit + 1, parameters) + for qubit in range(0, nqubits - 1, 2) + ) + circuit.add( + _parametrized_two_qubit_gate(qubit, qubit + 1, parameters) + for qubit in range(1, nqubits - 1, 2) + ) + + if closed_boundary: + circuit.add(_parametrized_two_qubit_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 +424,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) From 7a461bd4c325277dd65ac828cc8487b03e72f8d3 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 13 Mar 2024 12:51:02 +0400 Subject: [PATCH 3/7] docstring --- src/qibo/models/encodings.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/qibo/models/encodings.py b/src/qibo/models/encodings.py index ceb5a390fe..1f1134da0b 100644 --- a/src/qibo/models/encodings.py +++ b/src/qibo/models/encodings.py @@ -257,6 +257,24 @@ def entangling_layer( 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( @@ -302,8 +320,12 @@ def entangling_layer( ) # Finds the number of correct number of parameters to initialize the gate class. - # If gate is parametrized, sets all angles to 0.0 parameters = list(signature(gate).parameters) + + if "q2" in parameters: + raise_error(ValueError, 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) From d48c421ecceff70e2dbb89d94ef7e5e418656bfc Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 13 Mar 2024 14:34:58 +0400 Subject: [PATCH 4/7] update `__init__` --- src/qibo/models/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 From d1f124c17ccf4e6b787c6e3c17a4c233ca65ed1c Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 13 Mar 2024 14:35:14 +0400 Subject: [PATCH 5/7] fix bugs --- src/qibo/models/encodings.py | 41 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/qibo/models/encodings.py b/src/qibo/models/encodings.py index 1f1134da0b..cb6a1bc73f 100644 --- a/src/qibo/models/encodings.py +++ b/src/qibo/models/encodings.py @@ -5,12 +5,10 @@ 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 from qibo.models.circuit import Circuit -from qibo.quantum_info.random_ensembles import _ProbabilityDistributionGaussianLoader def comp_basis_encoder( @@ -229,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 ) @@ -298,32 +300,31 @@ def entangling_layer( f"``architecture`` {architecture} not found.", ) - if not isinstance(entangling_gate, (str, gates.Gate)): - raise_error( - TypeError, - "``entangling_gate`` must be either str or ``qibo.gates.Gate``, " - + f"but it is type {type(entangling_gate)}.", - ) - if not isinstance(closed_boundary, bool): raise_error( TypeError, f"closed_boundary must be type bool, but it is type {type(closed_boundary)}.", ) - if isinstance(entangling_gate, str): - gate = getattr(gates, entangling_gate) + gate = ( + getattr(gates, entangling_gate) + if isinstance(entangling_gate, str) + else entangling_gate + ) - if isinstance(gate, gates.GeneralizedfSim): + if gate.__name__ == "GeneralizedfSim": raise_error( - ValueError, "This function does not support the ``GeneralizedfSim`` gate." + 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(ValueError, f"This function does not accept three-qubit gates.") + 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 @@ -332,31 +333,31 @@ def entangling_layer( if architecture == "diagonal": circuit.add( - _parametrized_two_qubit_gate(qubit, qubit + 1, parameters) + _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(qubit, qubit + 1, parameters) + _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(qubit, qubit + 1, parameters) + _parametrized_two_qubit_gate(gate, qubit, qubit + 1, parameters) for qubit in range(1, nqubits - 1, 2) ) else: circuit.add( - _parametrized_two_qubit_gate(qubit, qubit + 1, parameters) + _parametrized_two_qubit_gate(gate, qubit, qubit + 1, parameters) for qubit in range(0, nqubits - 1, 2) ) circuit.add( - _parametrized_two_qubit_gate(qubit, qubit + 1, parameters) + _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(nqubits - 1, 0, parameters)) + circuit.add(_parametrized_two_qubit_gate(gate, nqubits - 1, 0, parameters)) return circuit From 5391cb55818917d71d056c3637f85d0067db26cc Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 13 Mar 2024 14:35:19 +0400 Subject: [PATCH 6/7] tests --- tests/test_models_encodings.py | 74 ++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) 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) From 0b9355f5be4e87e8c3f075680879ba5a9b8afff1 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Wed, 13 Mar 2024 14:38:28 +0400 Subject: [PATCH 7/7] api ref --- doc/source/api-reference/qibo.rst | 8 ++++++++ 1 file changed, 8 insertions(+) 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