Skip to content

Commit

Permalink
Merge pull request #1218 from qiboteam/hellinger
Browse files Browse the repository at this point in the history
Move `hellinger_shot_error` from `noise_model` to `quantum_info.utils`
  • Loading branch information
renatomello authored Feb 27, 2024
2 parents 1eac403 + 19d3e14 commit 19368f2
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 28 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 @@ -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.
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

0 comments on commit 19368f2

Please sign in to comment.