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

Local fidelity #9504

Merged
merged 9 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
64 changes: 60 additions & 4 deletions qiskit/algorithms/state_fidelities/compute_uncompute.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
# (C) Copyright IBM 2022, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -49,10 +49,25 @@ class ComputeUncompute(BaseStateFidelity):

"""

def __init__(self, sampler: BaseSampler, options: Options | None = None) -> None:
"""
def __init__(
self,
sampler: BaseSampler,
average_local: bool = False,
gentinettagian marked this conversation as resolved.
Show resolved Hide resolved
gentinettagian marked this conversation as resolved.
Show resolved Hide resolved
options: Options | None = None,
) -> None:
r"""
Args:
sampler: Sampler primitive instance.
average_local: If set to ``True``, the fidelity is averaged over
single-qubit projectors
.. math::

\hat{O} = \frac{1}{N}\sum_{i=1}^N|0_i\rangle\langle 0_i|,

Comment on lines +68 to +71
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not formatted correctly:

image

I think you have to add an additional linebreak before the .. math -- but if that doesn't work then you could also put this info in the class docstring 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, adding the extra line break worked 👍

instead of the global projector :math:`|0\rangle\langle 0|^{\otimes n}`.
This coincides with the standard (global) fidelity in the limit of
the fidelity approaching 1. Might be used to increase the variance
to improve trainability in algorithms such as p-VQD.
gentinettagian marked this conversation as resolved.
Show resolved Hide resolved
options: Primitive backend runtime options used for circuit execution.
The order of priority is: options in ``run`` method > fidelity's
default options > primitive's default setting.
Expand All @@ -66,6 +81,7 @@ def __init__(self, sampler: BaseSampler, options: Options | None = None) -> None
f"The sampler should be an instance of BaseSampler, " f"but got {type(sampler)}"
)
self._sampler: BaseSampler = sampler
self._average_local = average_local
self._default_options = Options()
if options is not None:
self._default_options.update_options(**options)
Expand Down Expand Up @@ -145,7 +161,15 @@ def _run(
except Exception as exc:
raise AlgorithmError("Sampler job failed!") from exc

raw_fidelities = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists]
if self._average_local:
raw_fidelities = [
self._get_local_fidelity(prob_dist, circuit.num_qubits)
for prob_dist, circuit in zip(result.quasi_dists, circuits)
]
else:
raw_fidelities = [
self._get_global_fidelity(prob_dist) for prob_dist in result.quasi_dists
]
fidelities = self._truncate_fidelities(raw_fidelities)

return StateFidelityResult(
Expand Down Expand Up @@ -190,3 +214,35 @@ def _get_local_options(self, options: Options) -> Options:
opts = copy(self._sampler.options)
opts.update_options(**options)
return opts

def _get_global_fidelity(self, probability_distribution: dict[int, float]) -> float:
"""Process the probability distribution of a measurement to determine the
global fidelity.

Args:
probability_distribution: Obtained from the measurement result

Returns:
The global fidelity.
"""
return probability_distribution.get(0, 0)

def _get_local_fidelity(
self, probability_distribution: dict[int, float], num_qubits: int
) -> float:
"""Process the probability distribution of a measurement to determine the
local fidelity by averaging over single-qubit projectors.

Args:
probability_distribution: Obtained from the measurement result

Returns:
The local fidelity.
"""
fidelity = 0.0
for qubit in range(num_qubits):
for bitstring, prob in probability_distribution.items():
# Check whether the bit representing the current qubit is 0
if not bitstring >> qubit & 1:
fidelity += prob / num_qubits
return fidelity
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
# (C) Copyright IBM 2022, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -56,7 +56,22 @@ def test_1param_pair(self):
"""test for fidelity with one pair of parameters"""
fidelity = ComputeUncompute(self._sampler)
job = fidelity.run(
self._circuit[0], self._circuit[1], self._left_params[0], self._right_params[0]
self._circuit[0],
self._circuit[1],
self._left_params[0],
self._right_params[0],
)
result = job.result()
np.testing.assert_allclose(result.fidelities, np.array([1.0]))
gentinettagian marked this conversation as resolved.
Show resolved Hide resolved

def test_1param_pair_local(self):
"""test for fidelity with one pair of parameters"""
fidelity = ComputeUncompute(self._sampler, average_local=True)
job = fidelity.run(
self._circuit[0],
self._circuit[1],
self._left_params[0],
self._right_params[0],
)
result = job.result()
np.testing.assert_allclose(result.fidelities, np.array([1.0]))
Expand All @@ -66,7 +81,10 @@ def test_4param_pairs(self):
fidelity = ComputeUncompute(self._sampler)
n = len(self._left_params)
job = fidelity.run(
[self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params
gentinettagian marked this conversation as resolved.
Show resolved Hide resolved
[self._circuit[0]] * n,
[self._circuit[1]] * n,
self._left_params,
self._right_params,
)
results = job.result()
np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16)
Expand All @@ -76,10 +94,16 @@ def test_symmetry(self):
fidelity = ComputeUncompute(self._sampler)
n = len(self._left_params)
job_1 = fidelity.run(
[self._circuit[0]] * n, [self._circuit[0]] * n, self._left_params, self._right_params
[self._circuit[0]] * n,
[self._circuit[0]] * n,
self._left_params,
self._right_params,
)
job_2 = fidelity.run(
[self._circuit[0]] * n, [self._circuit[0]] * n, self._right_params, self._left_params
[self._circuit[0]] * n,
[self._circuit[0]] * n,
self._right_params,
self._left_params,
)
results_1 = job_1.result()
results_2 = job_2.result()
Expand Down