Skip to content

Commit

Permalink
Merge pull request #1057 from qiboteam/uniform_distribution
Browse files Browse the repository at this point in the history
Implementation of function that samples `U3` phases from Haar distribution
  • Loading branch information
renatomello authored Nov 6, 2023
2 parents dbdbd07 + c0895a6 commit d279895
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 2 deletions.
6 changes: 6 additions & 0 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1499,6 +1499,12 @@ Random Ensembles
Functions that can generate random quantum objects.


Haar-random :math:`U_{3}`
"""""""""""""""""""""""""

.. autofunction:: qibo.quantum_info.uniform_sampling_U3


Random Gaussian matrix
""""""""""""""""""""""

Expand Down
61 changes: 61 additions & 0 deletions src/qibo/quantum_info/random_ensembles.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Optional, Union

import numpy as np
from scipy.stats import rv_continuous

from qibo import Circuit, gates
from qibo.backends import GlobalBackend, NumpyBackend
Expand All @@ -19,6 +20,66 @@
)


class _probability_distribution_sin(rv_continuous):
def _pdf(self, theta: float):
return 0.5 * np.sin(theta)

def _cdf(self, theta: float):
return np.sin(theta / 2) ** 2

def _ppf(self, theta: float):
return 2 * np.arcsin(np.sqrt(theta))


def uniform_sampling_U3(ngates: int, seed=None, backend=None):
"""Samples parameters for Haar-random :math:`U_{3}`s (:class:`qibo.gates.U3`).
Args:
ngates (int): Total number of :math:`U_{3}`s to be sampled.
seed (int or :class:`numpy.random.Generator`, optional): Either a generator of random
numbers or a fixed seed to initialize a generator. If ``None``, initializes
a generator with a random seed. Default: ``None``.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses :class:`qibo.backends.GlobalBackend`.
Defaults to ``None``.
Returns:
(ndarray): array of shape (``ngates``, :math:`3`).
"""
if not isinstance(ngates, int):
raise_error(
TypeError, f"ngates must be type int, but it is type {type(ngates)}."
)
elif ngates <= 0:
raise_error(ValueError, f"ngates must be non-negative, but it is {ngates}.")

if (
seed is not None
and not isinstance(seed, int)
and not isinstance(seed, np.random.Generator)
):
raise_error(
TypeError, "seed must be either type int or numpy.random.Generator."
)

if backend is None: # pragma: no cover
backend = GlobalBackend()

local_state = (
np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed
)

sampler = _probability_distribution_sin(a=0, b=np.pi, seed=local_state)
phases = local_state.random((ngates, 3))
phases[:, 0] = sampler.rvs(size=len(phases[:, 0]))
phases[:, 1] = phases[:, 1] * 2 * np.pi
phases[:, 2] = phases[:, 2] * 2 * np.pi

phases = backend.cast(phases, dtype=phases.dtype)

return phases


def random_gaussian_matrix(
dims: int,
rank: Optional[int] = None,
Expand Down
49 changes: 47 additions & 2 deletions tests/test_quantum_info_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
import numpy as np
import pytest

from qibo import matrices
from qibo import Circuit, gates, matrices
from qibo.config import PRECISION_TOL
from qibo.quantum_info.metrics import purity
from qibo.quantum_info.random_ensembles import (
_probability_distribution_sin,
random_clifford,
random_density_matrix,
random_gaussian_matrix,
Expand All @@ -19,10 +20,54 @@
random_statevector,
random_stochastic_matrix,
random_unitary,
uniform_sampling_U3,
)


@pytest.mark.parametrize("seed", [None, 10, np.random.Generator(np.random.MT19937(10))])
@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)])
def test_uniform_sampling_U3(backend, seed):
with pytest.raises(TypeError):
uniform_sampling_U3("1", seed=seed, backend=backend)
with pytest.raises(ValueError):
uniform_sampling_U3(0, seed=seed, backend=backend)
with pytest.raises(TypeError):
uniform_sampling_U3(2, seed="1", backend=backend)

X = backend.cast(matrices.X, dtype=matrices.X.dtype)
Y = backend.cast(matrices.Y, dtype=matrices.Y.dtype)
Z = backend.cast(matrices.Z, dtype=matrices.Z.dtype)

ngates = int(1e4)
phases = uniform_sampling_U3(ngates, seed=seed, backend=backend)

# expectation values in the 3 directions should be the same
expectation_values = []
for row in phases:
row = [float(phase) for phase in row]
circuit = Circuit(1)
circuit.add(gates.U3(0, *row))
state = backend.execute_circuit(circuit).state()

expectation_values.append(
[
np.conj(state) @ X @ state,
np.conj(state) @ Y @ state,
np.conj(state) @ Z @ state,
]
)
expectation_values = backend.cast(expectation_values)
expectation_values = np.mean(expectation_values, axis=0)

backend.assert_allclose(expectation_values[0], expectation_values[1], atol=1e-1)
backend.assert_allclose(expectation_values[0], expectation_values[2], atol=1e-1)

# execution for coverage
sampler = _probability_distribution_sin(a=0, b=np.pi, seed=seed)
sampler.pdf(1)
sampler.cdf(1)


@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)])
def test_random_gaussian_matrix(backend, seed):
with pytest.raises(TypeError):
dims = np.array([2])
Expand Down

0 comments on commit d279895

Please sign in to comment.