Skip to content

Commit

Permalink
Merge pull request #1260 from qiboteam/encoding_entangling
Browse files Browse the repository at this point in the history
Add `entangling_layer` to `models.encodings`
  • Loading branch information
renatomello authored Mar 14, 2024
2 parents d72f449 + 0b9355f commit f0548cd
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 11 deletions.
8 changes: 8 additions & 0 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion src/qibo/models/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
129 changes: 119 additions & 10 deletions src/qibo/models/encodings.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
)
Expand All @@ -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
Expand Down Expand Up @@ -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)
13 changes: 13 additions & 0 deletions src/qibo/quantum_info/random_ensembles.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Module with functions that create random quantum and classical objects."""

import math
import warnings
from typing import Optional, Union

Expand All @@ -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)
Expand Down
74 changes: 74 additions & 0 deletions tests/test_models_encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

0 comments on commit f0548cd

Please sign in to comment.