Skip to content

Commit

Permalink
Merge branch 'master' into quantum_network
Browse files Browse the repository at this point in the history
  • Loading branch information
renatomello committed Mar 14, 2024
2 parents 1a5e282 + f0548cd commit 5909b94
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 34 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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "qibo"
version = "0.2.6"
version = "0.2.7"
description = "A framework for quantum computing with hardware acceleration."
authors = ["The Qibo team"]
license = "Apache License 2.0"
Expand Down
3 changes: 2 additions & 1 deletion src/qibo/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def construct_backend(backend, **kwargs):
elif backend == "qibolab": # pragma: no cover
from qibolab.backends import QibolabBackend # pylint: disable=E0401

return QibolabBackend(**kwargs)

elif backend == "qibotn": # pragma: no cover

platform = kwargs.get("platform")
Expand All @@ -46,7 +48,6 @@ def construct_backend(backend, **kwargs):

return QuimbBackend(kwargs["runcard"])

return QibolabBackend(**kwargs)
elif backend == "clifford":
return CliffordBackend(kwargs["platform"])
elif backend == "qibo-client": # pragma: no cover
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
45 changes: 24 additions & 21 deletions src/qibo/models/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ class _Queue(list):
def __init__(self, nqubits):
super().__init__(self)
self.nqubits = nqubits
self.moments = [nqubits * [None]]
self.moment_index = nqubits * [0]
self.nmeasurements = 0

def to_fused(self):
"""Transform all gates in queue to :class:`qibo.gates.FusedGate`."""
Expand Down Expand Up @@ -88,24 +85,30 @@ def from_fused(self):
queue.append(gate.gates[0])
return queue

def append(self, gate: gates.Gate):
super().append(gate)
if gate.qubits:
qubits = gate.qubits
else: # special gate acting on all qubits
qubits = tuple(range(self.nqubits))

if isinstance(gate, gates.M):
self.nmeasurements += 1

# calculate moment index for this gate
idx = max(self.moment_index[q] for q in qubits)
for q in qubits:
if idx >= len(self.moments):
# Add a moment
self.moments.append(len(self.moments[-1]) * [None])
self.moments[idx][q] = gate
self.moment_index[q] = idx + 1
@property
def nmeasurements(self):
return len(list(filter(lambda gate: isinstance(gate, gates.M), self)))

@property
def moments(self):
moments = [self.nqubits * [None]]
moment_index = self.nqubits * [0]
for gate in self:
qubits = (
gate.qubits
if not isinstance(gate, gates.CallbackGate)
else tuple(range(self.nqubits)) # special gate acting on all qubits
)

# calculate moment index for this gate
idx = max(moment_index[q] for q in qubits)
for q in qubits:
if idx >= len(moments):
# Add a moment
moments.append(len(moments[-1]) * [None])
moments[idx][q] = gate
moment_index[q] = idx + 1
return moments


class Circuit:
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
4 changes: 4 additions & 0 deletions tests/test_models_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ def test_parametrizedgates_class():


def test_queue_class():
from qibo.callbacks import EntanglementEntropy
from qibo.models.circuit import _Queue

entropy = EntanglementEntropy([0])
queue = _Queue(4)
gatelist = [
gates.H(0),
Expand All @@ -29,12 +31,14 @@ def test_queue_class():
gates.H(2),
gates.CNOT(1, 2),
gates.Y(3),
gates.CallbackGate(entropy),
]
for g in gatelist:
queue.append(g)
assert queue.moments == [
[gatelist[0], gatelist[1], gatelist[3], gatelist[5]],
[gatelist[2], gatelist[4], gatelist[4], None],
[gatelist[6] for _ in range(4)],
]


Expand Down
Loading

0 comments on commit 5909b94

Please sign in to comment.