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

Move hellinger_shot_error from noise_model to quantum_info.utils #1218

Merged
merged 11 commits into from
Feb 27, 2024
6 changes: 6 additions & 0 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,12 @@ Hellinger fidelity
.. autofunction:: qibo.quantum_info.hellinger_fidelity


Hellinger shot error
""""""""""""""""""""

.. autofunction:: qibo.quantum_info.hellinger_fidelity


Haar integral
"""""""""""""

Expand Down
24 changes: 1 addition & 23 deletions src/qibo/noise_model.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import numpy as np

from qibo import gates, models
from qibo.quantum_info import hellinger_fidelity
from qibo.quantum_info.utils import hellinger_fidelity, hellinger_shot_error


def noisy_circuit(circuit, params):
Expand Down Expand Up @@ -200,28 +200,6 @@ def freq_to_prob(freq):
return prob


def hellinger_shot_error(p, q, nshots):
"""Hellinger fidelity error caused by using two probability distributions estimated using a finite number of shots.
It is calculated propagating the probability error of each state of the system. The complete formula is:
:math:`(1 - H^{2}(p, q))/\\sqrt{nshots} * \\sum_{i=1}^{n}(\\sqrt{p_i(1-q_i)}+\\sqrt{q_i(1-p_i)})`
where the sum is made all over the possible states and :math:`H(p, q)` is the Hellinger distance.

Args:
p (numpy.ndarray): (discrete) probability distribution :math:`p`.
q (numpy.ndarray): (discrete) probability distribution :math:`q`.
nshots (int): the number of shots we used to run the circuit to obtain :math:`p` and :math:`q`.

Returns:
(float): The Hellinger fidelity error.

"""
hellinger_fid = hellinger_fidelity(p, q)
hellinger_fid_e = np.sqrt(hellinger_fid / nshots) * np.sum(
np.sqrt(q * (1 - p)) + np.sqrt(p * (1 - q))
)
return hellinger_fid_e


def loss(parameters, *args):
"""The loss function used to be maximized in the fit method of the :class:`qibo.noise_model.CompositeNoiseModel`.
It is the hellinger fidelity calculated between the probability distribution of the noise model and the experimental target distribution using the :func:`qibo.quantum_info.hellinger_fidelity`.
Expand Down
58 changes: 54 additions & 4 deletions src/qibo/quantum_info/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,13 @@ def hellinger_fidelity(prob_dist_p, prob_dist_q, validate: bool = False, backend
.. math::
(1 - H^{2}(p, q))^{2} \\, ,

where :math:`H(p, q)` is the Hellinger distance
(:func:`qibo.quantum_info.utils.hellinger_distance`).
where :math:`H(p, q)` is the :func:`qibo.quantum_info.utils.hellinger_distance`.

Args:
prob_dist_p (ndarray or list): discrete probability distribution :math:`p`.
prob_dist_q (ndarray or list): discrete probability distribution :math:`q`.
validate (bool, optional): if True, checks if :math:`p` and :math:`q` are proper
probability distributions. Default: False.
validate (bool, optional): if ``True``, checks if :math:`p` and :math:`q` are proper
probability distributions. Defaults to ``False``.
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``.
Expand All @@ -274,11 +273,62 @@ def hellinger_fidelity(prob_dist_p, prob_dist_q, validate: bool = False, backend
(float): Hellinger fidelity.

"""
backend = _check_backend(backend)

distance = hellinger_distance(prob_dist_p, prob_dist_q, validate, backend=backend)

return (1 - distance**2) ** 2


def hellinger_shot_error(
prob_dist_p, prob_dist_q, nshots: int, validate: bool = False, backend=None
):
"""Calculates the Hellinger fidelity error between two discrete probability distributions estimated from finite statistics.
renatomello marked this conversation as resolved.
Show resolved Hide resolved

It is calculated propagating the probability error of each state of the system.
The complete formula is:

.. math::
\\frac{1 - H^{2}(p, q)}{\\sqrt{nshots}} \\, \\sum_{k} \\,
\\left(\\sqrt{p_{k} \\, (1 - q_{k})} + \\sqrt{q_{k} \\, (1 - p_{k})}\\right)

where :math:`H(p, q)` is the :func:`qibo.quantum_info.utils.hellinger_distance`,
and :math:`1 - H^{2}(p, q)` is the square root of the
:func:`qibo.quantum_info.utils.hellinger_fidelity`.

Args:
prob_dist_p (ndarray or list): discrete probability distribution :math:`p`.
prob_dist_q (ndarray or list): discrete probability distribution :math:`q`.
nshots (int): number of shots we used to run the circuit to obtain :math:`p` and :math:`q`.
validate (bool, optional): if ``True``, checks if :math:`p` and :math:`q` are proper
probability distributions. Defaults to ``False``.
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:
(float): Hellinger fidelity error.

"""
backend = _check_backend(backend)

if isinstance(prob_dist_p, list):
prob_dist_p = backend.cast(prob_dist_p, dtype=np.float64)

if isinstance(prob_dist_q, list):
prob_dist_q = backend.cast(prob_dist_q, dtype=np.float64)

hellinger_error = hellinger_fidelity(
prob_dist_p, prob_dist_q, validate=validate, backend=backend
)
hellinger_error = np.sqrt(hellinger_error / nshots) * np.sum(
np.sqrt(prob_dist_q * (1 - prob_dist_p))
+ np.sqrt(prob_dist_p * (1 - prob_dist_q))
)

return hellinger_error


def haar_integral(
nqubits: int,
power_t: int,
Expand Down
32 changes: 31 additions & 1 deletion tests/test_quantum_info_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
import numpy as np
import pytest

from qibo import Circuit, matrices
from qibo import Circuit, gates, matrices
from qibo.config import PRECISION_TOL
from qibo.quantum_info.metrics import fidelity
from qibo.quantum_info.random_ensembles import random_clifford
from qibo.quantum_info.utils import (
haar_integral,
hadamard_transform,
hamming_distance,
hamming_weight,
hellinger_distance,
hellinger_fidelity,
hellinger_shot_error,
pqc_integral,
)

Expand Down Expand Up @@ -175,6 +177,34 @@ def test_hellinger(backend, validate, kind):
assert fidelity == (1 - target**2) ** 2


@pytest.mark.parametrize("kind", [None, list])
@pytest.mark.parametrize("validate", [False, True])
def test_hellinger_shot_error(backend, validate, kind):
nqubits, nshots = 5, 1000

circuit = random_clifford(nqubits, seed=1, backend=backend)
circuit.add(gates.M(qubit) for qubit in range(nqubits))

circuit_2 = random_clifford(nqubits, seed=2, backend=backend)
circuit_2.add(gates.M(qubit) for qubit in range(nqubits))

prob_dist_p = backend.execute_circuit(circuit, nshots=nshots).probabilities()
prob_dist_q = backend.execute_circuit(circuit_2, nshots=nshots).probabilities()

if kind is not None:
prob_dist_p = kind(prob_dist_p)
prob_dist_q = kind(prob_dist_q)

hellinger_error = hellinger_shot_error(
prob_dist_p, prob_dist_q, nshots, validate=validate, backend=backend
)
hellinger_fid = hellinger_fidelity(
prob_dist_p, prob_dist_q, validate=validate, backend=backend
)

assert 2 * hellinger_error < hellinger_fid


def test_haar_integral_errors(backend):
with pytest.raises(TypeError):
nqubits, power_t, samples = 0.5, 2, 10
Expand Down
Loading