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

Fix ComputeUncompute State Fidelity threading issue #92

Merged
merged 5 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 6 additions & 7 deletions qiskit_algorithms/state_fidelities/base_state_fidelity.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from qiskit.primitives.utils import _circuit_key

from ..algorithm_job import AlgorithmJob
from .state_fidelity_result import StateFidelityResult


class BaseStateFidelity(ABC):
Expand Down Expand Up @@ -247,7 +246,7 @@ def _run(
values_1: Sequence[float] | Sequence[Sequence[float]] | None = None,
values_2: Sequence[float] | Sequence[Sequence[float]] | None = None,
**options,
) -> StateFidelityResult:
) -> AlgorithmJob:
r"""
Computes the state overlap (fidelity) calculation between two
(parametrized) circuits (first and second) for a specific set of parameter
Expand All @@ -264,7 +263,7 @@ def _run(
Higher priority setting overrides lower priority setting.

Returns:
The result of the fidelity calculation.
A newly constructed algorithm job instance to get the fidelity result.
"""
raise NotImplementedError

Expand Down Expand Up @@ -294,15 +293,15 @@ def run(

Returns:
Primitive job for the fidelity calculation.
The job's result is an instance of ``StateFidelityResult``.
The job's result is an instance of :class:`.StateFidelityResult`.
"""

job = AlgorithmJob(self._run, circuits_1, circuits_2, values_1, values_2, **options)
job = self._run(circuits_1, circuits_2, values_1, values_2, **options)

job.submit()
return job

def _truncate_fidelities(self, fidelities: Sequence[float]) -> Sequence[float]:
@staticmethod
def _truncate_fidelities(fidelities: Sequence[float]) -> Sequence[float]:
"""
Ensures fidelity result in [0,1].

Expand Down
33 changes: 21 additions & 12 deletions qiskit_algorithms/state_fidelities/compute_uncompute.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@

from qiskit import QuantumCircuit
from qiskit.primitives import BaseSampler
from qiskit.primitives.primitive_job import PrimitiveJob
from qiskit.providers import Options

from ..exceptions import AlgorithmError
from .base_state_fidelity import BaseStateFidelity
from .state_fidelity_result import StateFidelityResult
from ..algorithm_job import AlgorithmJob


class ComputeUncompute(BaseStateFidelity):
Expand Down Expand Up @@ -118,7 +120,7 @@ def _run(
values_1: Sequence[float] | Sequence[Sequence[float]] | None = None,
values_2: Sequence[float] | Sequence[Sequence[float]] | None = None,
**options,
) -> StateFidelityResult:
) -> AlgorithmJob:
r"""
Computes the state overlap (fidelity) calculation between two
(parametrized) circuits (first and second) for a specific set of parameter
Expand All @@ -135,7 +137,7 @@ def _run(
Higher priority setting overrides lower priority setting.

Returns:
The result of the fidelity calculation.
An AlgorithmJob for the fidelity calculation.

Raises:
ValueError: At least one pair of circuits must be defined.
Expand All @@ -155,29 +157,36 @@ def _run(
opts = copy(self._default_options)
opts.update_options(**options)

job = self._sampler.run(circuits=circuits, parameter_values=values, **opts.__dict__)
sampler_job = self._sampler.run(circuits=circuits, parameter_values=values, **opts.__dict__)

local_opts = self._get_local_options(opts.__dict__)
return AlgorithmJob(ComputeUncompute._call, sampler_job, circuits, self._local, local_opts)

@staticmethod
def _call(
job: PrimitiveJob, circuits: Sequence[QuantumCircuit], local: bool, local_opts: Options
) -> StateFidelityResult:
try:
result = job.result()
except Exception as exc:
raise AlgorithmError("Sampler job failed!") from exc

if self._local:
if local:
raw_fidelities = [
self._get_local_fidelity(prob_dist, circuit.num_qubits)
ComputeUncompute._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
ComputeUncompute._get_global_fidelity(prob_dist) for prob_dist in result.quasi_dists
]
fidelities = self._truncate_fidelities(raw_fidelities)
fidelities = ComputeUncompute._truncate_fidelities(raw_fidelities)

return StateFidelityResult(
fidelities=fidelities,
raw_fidelities=raw_fidelities,
metadata=result.metadata,
options=self._get_local_options(opts.__dict__),
options=local_opts,
)

@property
Expand Down Expand Up @@ -216,7 +225,8 @@ def _get_local_options(self, options: Options) -> Options:
opts.update_options(**options)
return opts

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

Expand All @@ -228,9 +238,8 @@ def _get_global_fidelity(self, probability_distribution: dict[int, float]) -> fl
"""
return probability_distribution.get(0, 0)

def _get_local_fidelity(
self, probability_distribution: dict[int, float], num_qubits: int
) -> float:
@staticmethod
def _get_local_fidelity(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.

Expand Down
7 changes: 7 additions & 0 deletions releasenotes/notes/fix-fidelity-00fbfa9919e860de.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
fixes:
- |
Fixes state fidelity :class:`.ComputeUncompute` to correct an issue arising from
the threading used for the job result. This issue could be seen sometimes if more
than one job was created and their results fetched back-to-back, such that more than
one internal thread was active processing these results.