Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add entangling_layer to models.encodings #1260

Merged
merged 7 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you use this to define a Circuit, I think a similar error will be raised directly from line 332.

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