From a7ca0078fc18c1a7875af91d5cf3b45e92a8ed68 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Sun, 19 Jun 2022 16:53:41 +0200 Subject: [PATCH 01/92] fidelity_poc --- qiskit/primitives/fidelity/fidelity.py | 127 +++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 qiskit/primitives/fidelity/fidelity.py diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py new file mode 100644 index 000000000000..64252d11c99f --- /dev/null +++ b/qiskit/primitives/fidelity/fidelity.py @@ -0,0 +1,127 @@ +from abc import abstractmethod +import numpy as np + +from qiskit import QiskitError, QuantumCircuit +from qiskit.primitives import Sampler +from qiskit.circuit import ParameterVector + +from typing import Optional, Callable, List, Union + +from qiskit_machine_learning import QiskitMachineLearningError + +SamplerFactory = Callable[List[QuantumCircuit], Sampler] + + +class BaseFidelity: + """ + Implements the interface to calculate fidelities. + """ + + def __init__( + self, + left_circuit: Optional[QuantumCircuit] = None, + right_circuit: Optional[QuantumCircuit] = None, + sampler_factory: Optional[SamplerFactory] = None, + ): + """ + Initializes the class to evaluate the fidelities defined as the state overlap + ||^2, + where x and y are parametrizations of the circuits. + Args: + - left_circuit: (Parametrized) quantum circuit + - right_circuit: (Parametrized) quantum circuit + - sampler_factory: Optional partial sampler used as a backend + Raises: + - ValueError: left_circuit and right_circuit don't have the same number of qubits + """ + if left_circuit is None or right_circuit is None: + self._left_circuit = None + self._right_circuit = None + else: + self.set_circuits(left_circuit, right_circuit) + + if sampler_factory is not None: + self.sampler_from_factory(sampler_factory) + else: + self.sampler = None + + def set_circuits(self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit): + """ + Fix the circuits for the fidelity to be computed of. + Args: + - left_circuit: (Parametrized) quantum circuit + - right_circuit: (Parametrized) quantum circuit + """ + if left_circuit.num_qubits != right_circuit.num_qubits: + raise ValueError( + f"The number of qubits for the left circuit ({left_circuit.num_qubits}) and right circuit ({right_circuit.num_qubits}) do not coincide." + ) + # Assigning parameter arrays to the two circuits + self._left_parameters = ParameterVector("x", left_circuit.num_parameters) + self._left_circuit = left_circuit.assign_parameters(self._left_parameters) + + self._right_parameters = ParameterVector("y", right_circuit.num_parameters) + self._right_circuit = right_circuit.assign_parameters(self._right_parameters) + + @abstractmethod + def sampler_from_factory(self, sampler_factory: SamplerFactory): + """ + Create a sampler instance from the sampler factory. + """ + raise NotImplementedError + + @abstractmethod + def compute( + self, + values_left: Union[np.ndarray, List[np.ndarray]], + values_right: Union[np.ndarray, List[np.ndarray]], + ) -> Union[float, List[float]]: + """Compute the overlap of two quantum states bound by the parametrizations values_left and values_right. + + Args: + values_left: Numerical parameters to be bound to the left circuit + values_right: Numerical parameters to be bound to the right circuit + + Returns: + The overlap of two quantum states defined by two parametrized circuits. + """ + raise NotImplementedError + + +class Fidelity(BaseFidelity): + """ + Calculates the fidelity of two quantum circuits by measuring the zero probability outcome. + """ + + def sampler_from_factory(self, sampler_factory: SamplerFactory): + circuit = self._left_circuit.compose(self._right_circuit.inverse()) + circuit.measure_all() + + self.sampler = sampler_factory([circuit]) + + def compute( + self, + values_left: Union[np.ndarray, List[np.ndarray]], + values_right: Union[np.ndarray, List[np.ndarray]], + ) -> Union[float, List[float]]: + if self._left_circuit is None or self._right_circuit is None: + raise QiskitError( + "The left and right circuits have to be set before computing the fidelity." + ) + if self.sampler is None: + raise QiskitError( + "A sampler has to be instantiated by adding a sampler factory in order to compute the fidelity." + ) + values_left = np.atleast_2d(values_left) + values_right = np.atleast_2d(values_right) + if values_left.shape[0] != values_right.shape[0]: + raise ValueError( + f"The number of left parameters (currently {values_left.shape[0]}) has to be equal to the number of right parameters (currently {values_right.shape[0]})" + ) + values = np.hstack([values_left, values_right]) + result = self.sampler( + circuit_indices=[0] * len(values), parameter_values=values + ) + + overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] + return np.array(overlaps) From 8d3e65fafc8e7b818aa874b06032672fe0018a91 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Thu, 30 Jun 2022 17:54:57 +0200 Subject: [PATCH 02/92] init stuff --- qiskit/primitives/fidelity/__init__.py | 35 ++++++++++++++++++++++++++ qiskit/primitives/fidelity/fidelity.py | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 qiskit/primitives/fidelity/__init__.py diff --git a/qiskit/primitives/fidelity/__init__.py b/qiskit/primitives/fidelity/__init__.py new file mode 100644 index 000000000000..d99669581302 --- /dev/null +++ b/qiskit/primitives/fidelity/__init__.py @@ -0,0 +1,35 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +===================================== +Fidelity Primitives (:mod:`qiskit.primitives.fidelity`) +===================================== + +.. currentmodule:: qiskit.primitives.fidelity + + + +Fidelity +========= + +.. autosummary:: + :toctree: ../stubs/ + + BaseFidelity + Fidelity + +""" + +from .fidelity import BaseFidelity, Fidelity + +__all__ = ["BaseFidelity", "Fidelity"] \ No newline at end of file diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index 64252d11c99f..06617b60e659 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -9,7 +9,7 @@ from qiskit_machine_learning import QiskitMachineLearningError -SamplerFactory = Callable[List[QuantumCircuit], Sampler] +SamplerFactory = Callable[[List[QuantumCircuit]], Sampler] class BaseFidelity: From 12a9746c883d50b1643f68b492d970812382970c Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Mon, 4 Jul 2022 11:34:25 +0200 Subject: [PATCH 03/92] simplified design --- qiskit/primitives/fidelity/__init__.py | 3 +- qiskit/primitives/fidelity/base_fidelity.py | 82 +++++++++++++++ qiskit/primitives/fidelity/fidelity.py | 107 +++++--------------- 3 files changed, 112 insertions(+), 80 deletions(-) create mode 100644 qiskit/primitives/fidelity/base_fidelity.py diff --git a/qiskit/primitives/fidelity/__init__.py b/qiskit/primitives/fidelity/__init__.py index d99669581302..2eddda7bae8b 100644 --- a/qiskit/primitives/fidelity/__init__.py +++ b/qiskit/primitives/fidelity/__init__.py @@ -30,6 +30,7 @@ """ -from .fidelity import BaseFidelity, Fidelity +from .base_fidelity import BaseFidelity +from .fidelity import Fidelity __all__ = ["BaseFidelity", "Fidelity"] \ No newline at end of file diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py new file mode 100644 index 000000000000..c3d774cf141e --- /dev/null +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -0,0 +1,82 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Base fidelity primitive +""" + +from abc import abstractmethod +import numpy as np + +from qiskit import QuantumCircuit +from qiskit.circuit import ParameterVector + +from typing import List, Union + + +class BaseFidelity: + """ + Implements the interface to calculate fidelities. + """ + + def __init__( + self, + left_circuit: QuantumCircuit, + right_circuit: QuantumCircuit, + ): + """ + Initializes the class to evaluate the fidelities defined as the state overlap + ||^2, + where x and y are parametrizations of the circuits. + Args: + - left_circuit: (Parametrized) quantum circuit + - right_circuit: (Parametrized) quantum circuit + Raises: + - ValueError: left_circuit and right_circuit don't have the same number of qubits + """ + + if left_circuit.num_qubits != right_circuit.num_qubits: + raise ValueError( + f"The number of qubits for the left circuit ({left_circuit.num_qubits}) and right circuit ({right_circuit.num_qubits}) do not coincide." + ) + + self._left_circuit = left_circuit + self._right_circuit = right_circuit + + # Assigning parameter arrays to the two circuits + self._left_parameters = ParameterVector("x", left_circuit.num_parameters) + self._left_circuit = left_circuit.assign_parameters(self._left_parameters) + + self._right_parameters = ParameterVector("y", right_circuit.num_parameters) + self._right_circuit = right_circuit.assign_parameters(self._right_parameters) + + @abstractmethod + def compute( + self, + values_left: Union[np.ndarray, List[np.ndarray]], + values_right: Union[np.ndarray, List[np.ndarray]], + ) -> Union[float, List[float]]: + """Compute the overlap of two quantum states bound by the parametrizations values_left and values_right. + + Args: + values_left: Numerical parameters to be bound to the left circuit + values_right: Numerical parameters to be bound to the right circuit + + Returns: + The overlap of two quantum states defined by two parametrized circuits. + """ + raise NotImplementedError + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + pass diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index 06617b60e659..07c57c7460d3 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -1,27 +1,41 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Zero probability fidelity primitive +""" + from abc import abstractmethod import numpy as np -from qiskit import QiskitError, QuantumCircuit +from qiskit import QuantumCircuit from qiskit.primitives import Sampler -from qiskit.circuit import ParameterVector +from qiskit.primitives.fidelity import BaseFidelity -from typing import Optional, Callable, List, Union -from qiskit_machine_learning import QiskitMachineLearningError +from typing import Callable, List, Union SamplerFactory = Callable[[List[QuantumCircuit]], Sampler] -class BaseFidelity: +class Fidelity(BaseFidelity): """ - Implements the interface to calculate fidelities. + Calculates the fidelity of two quantum circuits by measuring the zero probability outcome. """ def __init__( self, - left_circuit: Optional[QuantumCircuit] = None, - right_circuit: Optional[QuantumCircuit] = None, - sampler_factory: Optional[SamplerFactory] = None, + left_circuit: QuantumCircuit, + right_circuit: QuantumCircuit, + sampler_factory: SamplerFactory, ): """ Initializes the class to evaluate the fidelities defined as the state overlap @@ -34,66 +48,8 @@ def __init__( Raises: - ValueError: left_circuit and right_circuit don't have the same number of qubits """ - if left_circuit is None or right_circuit is None: - self._left_circuit = None - self._right_circuit = None - else: - self.set_circuits(left_circuit, right_circuit) + super().__init__(left_circuit, right_circuit) - if sampler_factory is not None: - self.sampler_from_factory(sampler_factory) - else: - self.sampler = None - - def set_circuits(self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit): - """ - Fix the circuits for the fidelity to be computed of. - Args: - - left_circuit: (Parametrized) quantum circuit - - right_circuit: (Parametrized) quantum circuit - """ - if left_circuit.num_qubits != right_circuit.num_qubits: - raise ValueError( - f"The number of qubits for the left circuit ({left_circuit.num_qubits}) and right circuit ({right_circuit.num_qubits}) do not coincide." - ) - # Assigning parameter arrays to the two circuits - self._left_parameters = ParameterVector("x", left_circuit.num_parameters) - self._left_circuit = left_circuit.assign_parameters(self._left_parameters) - - self._right_parameters = ParameterVector("y", right_circuit.num_parameters) - self._right_circuit = right_circuit.assign_parameters(self._right_parameters) - - @abstractmethod - def sampler_from_factory(self, sampler_factory: SamplerFactory): - """ - Create a sampler instance from the sampler factory. - """ - raise NotImplementedError - - @abstractmethod - def compute( - self, - values_left: Union[np.ndarray, List[np.ndarray]], - values_right: Union[np.ndarray, List[np.ndarray]], - ) -> Union[float, List[float]]: - """Compute the overlap of two quantum states bound by the parametrizations values_left and values_right. - - Args: - values_left: Numerical parameters to be bound to the left circuit - values_right: Numerical parameters to be bound to the right circuit - - Returns: - The overlap of two quantum states defined by two parametrized circuits. - """ - raise NotImplementedError - - -class Fidelity(BaseFidelity): - """ - Calculates the fidelity of two quantum circuits by measuring the zero probability outcome. - """ - - def sampler_from_factory(self, sampler_factory: SamplerFactory): circuit = self._left_circuit.compose(self._right_circuit.inverse()) circuit.measure_all() @@ -104,24 +60,17 @@ def compute( values_left: Union[np.ndarray, List[np.ndarray]], values_right: Union[np.ndarray, List[np.ndarray]], ) -> Union[float, List[float]]: - if self._left_circuit is None or self._right_circuit is None: - raise QiskitError( - "The left and right circuits have to be set before computing the fidelity." - ) - if self.sampler is None: - raise QiskitError( - "A sampler has to be instantiated by adding a sampler factory in order to compute the fidelity." - ) + values_left = np.atleast_2d(values_left) values_right = np.atleast_2d(values_right) + if values_left.shape[0] != values_right.shape[0]: raise ValueError( f"The number of left parameters (currently {values_left.shape[0]}) has to be equal to the number of right parameters (currently {values_right.shape[0]})" ) + values = np.hstack([values_left, values_right]) - result = self.sampler( - circuit_indices=[0] * len(values), parameter_values=values - ) + result = self.sampler(circuit_indices=[0] * len(values), parameter_values=values) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] return np.array(overlaps) From 1c84828de6fe001bd33996705f75311275233975 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Mon, 4 Jul 2022 13:44:06 +0200 Subject: [PATCH 04/92] unittests --- test/python/primitives/fidelity/__init__.py | 13 ++++ .../primitives/fidelity/test_fidelity.py | 68 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 test/python/primitives/fidelity/__init__.py create mode 100644 test/python/primitives/fidelity/test_fidelity.py diff --git a/test/python/primitives/fidelity/__init__.py b/test/python/primitives/fidelity/__init__.py new file mode 100644 index 000000000000..69bf9271c711 --- /dev/null +++ b/test/python/primitives/fidelity/__init__.py @@ -0,0 +1,13 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for the fidelity primitives.""" diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/primitives/fidelity/test_fidelity.py new file mode 100644 index 000000000000..d75a48cfb0d8 --- /dev/null +++ b/test/python/primitives/fidelity/test_fidelity.py @@ -0,0 +1,68 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for Sampler.""" + +import unittest +from test import combine + +import numpy as np +from functools import partial +from ddt import ddt + +from qiskit import QuantumCircuit +from qiskit.circuit import ParameterVector +from qiskit.primitives import Sampler +from qiskit.primitives.fidelity import Fidelity +from qiskit.test import QiskitTestCase + + +@ddt +class TestFidelity(QiskitTestCase): + """Test Fidelity""" + + def setUp(self): + super().setUp() + parameters = ParameterVector("x", 2) + + rx_rotations = QuantumCircuit(2) + rx_rotations.rx(parameters[0], 0) + rx_rotations.rx(parameters[1], 1) + + ry_rotations = QuantumCircuit(2) + ry_rotations.ry(parameters[0], 0) + ry_rotations.ry(parameters[1], 1) + self._circuit = [rx_rotations, ry_rotations] + self._sampler_factory = partial(Sampler) + self._params_left = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) + self._params_right = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) + + def test_fidelity_1param_pair(self): + """test for fidelity with one pai""" + + with Fidelity(self._circuit[0], self._circuit[1], self._sampler_factory) as fidelity: + results = fidelity.compute(self._params_left[0], self._params_right[0]) + np.testing.assert_allclose(results, np.array([1.0])) + + def test_fidelity_4param_pairs(self): + """test for fidelity with one pai""" + + with Fidelity(self._circuit[0], self._circuit[1], self._sampler_factory) as fidelity: + results = fidelity.compute(self._params_left, self._params_right) + np.testing.assert_allclose( + results, + np.array([1.0, 0.5, 0.25, 0.0]), + ) + + +if __name__ == "__main__": + unittest.main() From ded7d16a87c81ce974bcfa4099ba3d1cd35e0267 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Mon, 4 Jul 2022 14:39:09 +0200 Subject: [PATCH 05/92] unittest working --- qiskit/primitives/fidelity/fidelity.py | 2 +- .../primitives/fidelity/test_fidelity.py | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index 07c57c7460d3..a500feb6ea3c 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -70,7 +70,7 @@ def compute( ) values = np.hstack([values_left, values_right]) - result = self.sampler(circuit_indices=[0] * len(values), parameter_values=values) + result = self.sampler(circuits=[0] * len(values), parameter_values=values) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] return np.array(overlaps) diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/primitives/fidelity/test_fidelity.py index d75a48cfb0d8..c2598c561ade 100644 --- a/test/python/primitives/fidelity/test_fidelity.py +++ b/test/python/primitives/fidelity/test_fidelity.py @@ -13,7 +13,6 @@ """Tests for Sampler.""" import unittest -from test import combine import numpy as np from functools import partial @@ -47,21 +46,26 @@ def setUp(self): self._params_right = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) def test_fidelity_1param_pair(self): - """test for fidelity with one pai""" + """test for fidelity with one pair of parameters""" with Fidelity(self._circuit[0], self._circuit[1], self._sampler_factory) as fidelity: results = fidelity.compute(self._params_left[0], self._params_right[0]) np.testing.assert_allclose(results, np.array([1.0])) def test_fidelity_4param_pairs(self): - """test for fidelity with one pai""" + """test for fidelity with four pairs of parameters""" with Fidelity(self._circuit[0], self._circuit[1], self._sampler_factory) as fidelity: results = fidelity.compute(self._params_left, self._params_right) - np.testing.assert_allclose( - results, - np.array([1.0, 0.5, 0.25, 0.0]), - ) + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + + def test_fidelity_symmetry(self): + """test for fidelity with the same circuit""" + + with Fidelity(self._circuit[0], self._circuit[0], self._sampler_factory) as fidelity: + results_1 = fidelity.compute(self._params_left, self._params_right) + results_2 = fidelity.compute(self._params_right, self._params_left) + np.testing.assert_allclose(results_1, results_2, atol=1e-16) if __name__ == "__main__": From e362b5ec365a9c542f1537081d3a207c8d5e7ea3 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Mon, 4 Jul 2022 16:38:24 +0200 Subject: [PATCH 06/92] optional parameters --- qiskit/primitives/fidelity/base_fidelity.py | 35 ++++++------ qiskit/primitives/fidelity/fidelity.py | 54 ++++++++++++------- .../primitives/fidelity/test_fidelity.py | 27 +++++++++- 3 files changed, 80 insertions(+), 36 deletions(-) diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index c3d774cf141e..6e17c9bd8227 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -13,16 +13,16 @@ Base fidelity primitive """ -from abc import abstractmethod +from abc import ABC, abstractmethod import numpy as np from qiskit import QuantumCircuit from qiskit.circuit import ParameterVector -from typing import List, Union +from typing import List, Union, Optional -class BaseFidelity: +class BaseFidelity(ABC): """ Implements the interface to calculate fidelities. """ @@ -31,14 +31,14 @@ def __init__( self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit, - ): + ) -> None: """ - Initializes the class to evaluate the fidelities defined as the state overlap - ||^2, - where x and y are parametrizations of the circuits. + Initializes the class to evaluate the fidelities defined as the state fidelity + :math:`|\braket{\psi(x)} {\phi(y)}|^2`, + where x and y are parametrizations of the circuits :math:`\psi` and :math:`\phi`. Args: - - left_circuit: (Parametrized) quantum circuit - - right_circuit: (Parametrized) quantum circuit + - left_circuit: (Parametrized) quantum circuit :math:`\psi` + - right_circuit: (Parametrized) quantum circuit :math:`\phi` Raises: - ValueError: left_circuit and right_circuit don't have the same number of qubits """ @@ -51,19 +51,20 @@ def __init__( self._left_circuit = left_circuit self._right_circuit = right_circuit - # Assigning parameter arrays to the two circuits - self._left_parameters = ParameterVector("x", left_circuit.num_parameters) - self._left_circuit = left_circuit.assign_parameters(self._left_parameters) + # Reassigning parameters to make sure that left and right parameters are independent + # even in case that left_circuit == right_circuit + left_parameters = ParameterVector("x", left_circuit.num_parameters) + self._left_circuit = left_circuit.assign_parameters(left_parameters) - self._right_parameters = ParameterVector("y", right_circuit.num_parameters) - self._right_circuit = right_circuit.assign_parameters(self._right_parameters) + right_parameters = ParameterVector("y", right_circuit.num_parameters) + self._right_circuit = right_circuit.assign_parameters(right_parameters) @abstractmethod def compute( self, - values_left: Union[np.ndarray, List[np.ndarray]], - values_right: Union[np.ndarray, List[np.ndarray]], - ) -> Union[float, List[float]]: + values_left: Optional[Union[np.ndarray, List[np.ndarray]]] = None, + values_right: Optional[Union[np.ndarray, List[np.ndarray]]] = None, + ) -> np.ndarray: """Compute the overlap of two quantum states bound by the parametrizations values_left and values_right. Args: diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index a500feb6ea3c..2d6c4960332f 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -21,7 +21,7 @@ from qiskit.primitives.fidelity import BaseFidelity -from typing import Callable, List, Union +from typing import Callable, List, Union, Optional SamplerFactory = Callable[[List[QuantumCircuit]], Sampler] @@ -36,15 +36,15 @@ def __init__( left_circuit: QuantumCircuit, right_circuit: QuantumCircuit, sampler_factory: SamplerFactory, - ): + ) -> None: """ Initializes the class to evaluate the fidelities defined as the state overlap - ||^2, - where x and y are parametrizations of the circuits. + :math:`|\braket{\psi(x)} {\phi(y)}|^2`, + where x and y are parametrizations of the circuits :math:`\psi` and :math:`\phi`. Args: - - left_circuit: (Parametrized) quantum circuit - - right_circuit: (Parametrized) quantum circuit - - sampler_factory: Optional partial sampler used as a backend + - left_circuit: (Parametrized) quantum circuit :math:`\psi` + - right_circuit: (Parametrized) quantum circuit :math:`\phi` + - sampler_factory: Partial sampler used as a backend Raises: - ValueError: left_circuit and right_circuit don't have the same number of qubits """ @@ -57,20 +57,38 @@ def __init__( def compute( self, - values_left: Union[np.ndarray, List[np.ndarray]], - values_right: Union[np.ndarray, List[np.ndarray]], - ) -> Union[float, List[float]]: + values_left: Optional[Union[np.ndarray, List[np.ndarray]]] = None, + values_right: Optional[Union[np.ndarray, List[np.ndarray]]] = None, + ) -> np.ndarray: - values_left = np.atleast_2d(values_left) - values_right = np.atleast_2d(values_right) + values_list = [] - if values_left.shape[0] != values_right.shape[0]: - raise ValueError( - f"The number of left parameters (currently {values_left.shape[0]}) has to be equal to the number of right parameters (currently {values_right.shape[0]})" - ) + if values_left is None: + if self._left_circuit.num_parameters != 0: + raise ValueError( + f"`values_left` cannot be `None` because the left circuit has {self._left_circuit.num_parameters} free parameters." + ) + else: + values_list.append(np.atleast_2d(values_left)) - values = np.hstack([values_left, values_right]) - result = self.sampler(circuits=[0] * len(values), parameter_values=values) + if values_right is None: + if self._right_circuit.num_parameters != 0: + raise ValueError( + f"`values_left` cannot be `None` because the left circuit has {self._left_circuit.num_parameters} free parameters." + ) + else: + values_list.append(np.atleast_2d(values_right)) + + if len(values_list) > 0: + if len(values_list) == 2 and values_list[0].shape[0] != values_list[1].shape[0]: + raise ValueError( + f"The number of left parameters (currently {values_list[0].shape[0]}) has to be equal to the number of right parameters (currently {values_list[1].shape[0]})" + ) + values = np.hstack(values_list) + result = self.sampler(circuits=[0] * len(values), parameter_values=values) + else: + result = self.sampler(circuits=[0]) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] + return np.array(overlaps) diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/primitives/fidelity/test_fidelity.py index c2598c561ade..83975725adeb 100644 --- a/test/python/primitives/fidelity/test_fidelity.py +++ b/test/python/primitives/fidelity/test_fidelity.py @@ -40,7 +40,14 @@ def setUp(self): ry_rotations = QuantumCircuit(2) ry_rotations.ry(parameters[0], 0) ry_rotations.ry(parameters[1], 1) - self._circuit = [rx_rotations, ry_rotations] + + plus = QuantumCircuit(2) + plus.h(0) + plus.h(1) + + zero = QuantumCircuit(2) + + self._circuit = [rx_rotations, ry_rotations, plus, zero] self._sampler_factory = partial(Sampler) self._params_left = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) self._params_right = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) @@ -67,6 +74,24 @@ def test_fidelity_symmetry(self): results_2 = fidelity.compute(self._params_right, self._params_left) np.testing.assert_allclose(results_1, results_2, atol=1e-16) + def test_fidelity_no_params(self): + """test for fidelity without parameters""" + with Fidelity(self._circuit[2], self._circuit[3], self._sampler_factory) as fidelity: + results = fidelity.compute() + np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) + + def test_fidelity_left_param(self): + """test for fidelity with only left parameters""" + with Fidelity(self._circuit[1], self._circuit[3], self._sampler_factory) as fidelity: + results = fidelity.compute(values_left=self._params_left) + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + + def test_fidelity_right_param(self): + """test for fidelity with only right parameters""" + with Fidelity(self._circuit[3], self._circuit[1], self._sampler_factory) as fidelity: + results = fidelity.compute(values_right=self._params_left) + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + if __name__ == "__main__": unittest.main() From 78e83efbff3066d32f297dcd97229639e3825426 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Mon, 4 Jul 2022 16:41:37 +0200 Subject: [PATCH 07/92] typo --- qiskit/primitives/fidelity/fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index 2d6c4960332f..33c93c9fbb02 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -74,7 +74,7 @@ def compute( if values_right is None: if self._right_circuit.num_parameters != 0: raise ValueError( - f"`values_left` cannot be `None` because the left circuit has {self._left_circuit.num_parameters} free parameters." + f"`values_right` cannot be `None` because the right circuit has {self._right_circuit.num_parameters} free parameters." ) else: values_list.append(np.atleast_2d(values_right)) From 4be8015cedc8de1d9d08b9575448cd1d76f5646b Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Tue, 5 Jul 2022 16:59:22 +0200 Subject: [PATCH 08/92] documentation --- qiskit/primitives/fidelity/__init__.py | 2 +- qiskit/primitives/fidelity/base_fidelity.py | 21 +++++++------- qiskit/primitives/fidelity/fidelity.py | 31 +++++++++++---------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/qiskit/primitives/fidelity/__init__.py b/qiskit/primitives/fidelity/__init__.py index 2eddda7bae8b..ef98dc3a50d7 100644 --- a/qiskit/primitives/fidelity/__init__.py +++ b/qiskit/primitives/fidelity/__init__.py @@ -33,4 +33,4 @@ from .base_fidelity import BaseFidelity from .fidelity import Fidelity -__all__ = ["BaseFidelity", "Fidelity"] \ No newline at end of file +__all__ = ["BaseFidelity", "Fidelity"] diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index 6e17c9bd8227..a16d825e7236 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -14,13 +14,12 @@ """ from abc import ABC, abstractmethod +from typing import List, Union, Optional import numpy as np from qiskit import QuantumCircuit from qiskit.circuit import ParameterVector -from typing import List, Union, Optional - class BaseFidelity(ABC): """ @@ -32,20 +31,21 @@ def __init__( left_circuit: QuantumCircuit, right_circuit: QuantumCircuit, ) -> None: - """ - Initializes the class to evaluate the fidelities defined as the state fidelity - :math:`|\braket{\psi(x)} {\phi(y)}|^2`, + r""" + Initializes the class to evaluate the fidelities defined as + :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, where x and y are parametrizations of the circuits :math:`\psi` and :math:`\phi`. Args: - - left_circuit: (Parametrized) quantum circuit :math:`\psi` - - right_circuit: (Parametrized) quantum circuit :math:`\phi` + left_circuit: (Parametrized) quantum circuit :math:`|\psi\rangle` + right_circuit: (Parametrized) quantum circuit :math:`|\phi\rangle` Raises: - - ValueError: left_circuit and right_circuit don't have the same number of qubits + ValueError: left_circuit and right_circuit don't have the same number of qubits """ if left_circuit.num_qubits != right_circuit.num_qubits: raise ValueError( - f"The number of qubits for the left circuit ({left_circuit.num_qubits}) and right circuit ({right_circuit.num_qubits}) do not coincide." + f"The number of qubits for the left circuit ({left_circuit.num_qubits})" + f"and right circuit ({right_circuit.num_qubits}) do not coincide." ) self._left_circuit = left_circuit @@ -65,7 +65,8 @@ def compute( values_left: Optional[Union[np.ndarray, List[np.ndarray]]] = None, values_right: Optional[Union[np.ndarray, List[np.ndarray]]] = None, ) -> np.ndarray: - """Compute the overlap of two quantum states bound by the parametrizations values_left and values_right. + """Compute the overlap of two quantum states bound by the + parametrizations values_left and values_right. Args: values_left: Numerical parameters to be bound to the left circuit diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index 33c93c9fbb02..b18f441434a9 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -12,17 +12,14 @@ """ Zero probability fidelity primitive """ - -from abc import abstractmethod +from typing import Callable, List, Union, Optional import numpy as np from qiskit import QuantumCircuit from qiskit.primitives import Sampler -from qiskit.primitives.fidelity import BaseFidelity +from .base_fidelity import BaseFidelity -from typing import Callable, List, Union, Optional - SamplerFactory = Callable[[List[QuantumCircuit]], Sampler] @@ -37,16 +34,16 @@ def __init__( right_circuit: QuantumCircuit, sampler_factory: SamplerFactory, ) -> None: - """ + r""" Initializes the class to evaluate the fidelities defined as the state overlap - :math:`|\braket{\psi(x)} {\phi(y)}|^2`, + :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, where x and y are parametrizations of the circuits :math:`\psi` and :math:`\phi`. Args: - - left_circuit: (Parametrized) quantum circuit :math:`\psi` - - right_circuit: (Parametrized) quantum circuit :math:`\phi` - - sampler_factory: Partial sampler used as a backend + left_circuit: (Parametrized) quantum circuit :math:`|\psi\rangle` + right_circuit: (Parametrized) quantum circuit :math:`|\phi\rangle` + sampler_factory: Partial sampler used as a backend Raises: - - ValueError: left_circuit and right_circuit don't have the same number of qubits + ValueError: left_circuit and right_circuit don't have the same number of qubits """ super().__init__(left_circuit, right_circuit) @@ -66,7 +63,8 @@ def compute( if values_left is None: if self._left_circuit.num_parameters != 0: raise ValueError( - f"`values_left` cannot be `None` because the left circuit has {self._left_circuit.num_parameters} free parameters." + "`values_left` cannot be `None` because the left circuit has" + f"{self._left_circuit.num_parameters} free parameters." ) else: values_list.append(np.atleast_2d(values_left)) @@ -74,7 +72,8 @@ def compute( if values_right is None: if self._right_circuit.num_parameters != 0: raise ValueError( - f"`values_right` cannot be `None` because the right circuit has {self._right_circuit.num_parameters} free parameters." + "`values_right` cannot be `None` because the right circuit has" + f"{self._right_circuit.num_parameters} free parameters." ) else: values_list.append(np.atleast_2d(values_right)) @@ -82,13 +81,17 @@ def compute( if len(values_list) > 0: if len(values_list) == 2 and values_list[0].shape[0] != values_list[1].shape[0]: raise ValueError( - f"The number of left parameters (currently {values_list[0].shape[0]}) has to be equal to the number of right parameters (currently {values_list[1].shape[0]})" + f"The number of left parameters (currently {values_list[0].shape[0]})" + "has to be equal to the number of right parameters" + f"(currently {values_list[1].shape[0]})" ) values = np.hstack(values_list) result = self.sampler(circuits=[0] * len(values), parameter_values=values) else: result = self.sampler(circuits=[0]) + # if error mititgation is added in the future, we will have to handle + # negative values in some way (e.g. clipping to zero) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] return np.array(overlaps) From 26754be4d17e9850bb57134742ce6c7f00efa3f5 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta <31244916+gentinettagian@users.noreply.github.com> Date: Wed, 6 Jul 2022 14:36:53 +0200 Subject: [PATCH 09/92] Update test/python/primitives/fidelity/test_fidelity.py Co-authored-by: Julien Gacon --- test/python/primitives/fidelity/test_fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/primitives/fidelity/test_fidelity.py index 83975725adeb..54a2c4912a15 100644 --- a/test/python/primitives/fidelity/test_fidelity.py +++ b/test/python/primitives/fidelity/test_fidelity.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Tests for Sampler.""" +"""Tests for Fidelity.""" import unittest From b34694506fbd544748241481aa9e176f97298409 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta <31244916+gentinettagian@users.noreply.github.com> Date: Wed, 6 Jul 2022 14:37:02 +0200 Subject: [PATCH 10/92] Update qiskit/primitives/fidelity/base_fidelity.py Co-authored-by: Julien Gacon --- qiskit/primitives/fidelity/base_fidelity.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index a16d825e7236..c603a4e203e1 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -31,15 +31,20 @@ def __init__( left_circuit: QuantumCircuit, right_circuit: QuantumCircuit, ) -> None: - r""" - Initializes the class to evaluate the fidelities defined as + r"""Initializes the class to evaluate the fidelities defined as + :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, - where x and y are parametrizations of the circuits :math:`\psi` and :math:`\phi`. + + where :math:`x` and :math:`y` are optional parametrizations of the + states :math:`\psi` and :math:`\phi` prepared by the circuits + ``left_circuit`` and ``right_circuit``, respectively. + Args: - left_circuit: (Parametrized) quantum circuit :math:`|\psi\rangle` - right_circuit: (Parametrized) quantum circuit :math:`|\phi\rangle` + left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + Raises: - ValueError: left_circuit and right_circuit don't have the same number of qubits + ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ if left_circuit.num_qubits != right_circuit.num_qubits: From 282c301144b95f5c3dca845dd5510635a308d879 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Thu, 7 Jul 2022 10:54:14 +0200 Subject: [PATCH 11/92] black --- qiskit/primitives/fidelity/base_fidelity.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index c603a4e203e1..a033f66f1cf0 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -32,17 +32,17 @@ def __init__( right_circuit: QuantumCircuit, ) -> None: r"""Initializes the class to evaluate the fidelities defined as - + :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, - + where :math:`x` and :math:`y` are optional parametrizations of the states :math:`\psi` and :math:`\phi` prepared by the circuits ``left_circuit`` and ``right_circuit``, respectively. - + Args: left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. - + Raises: ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ From a8a08318eeaac15d5296d60468c8959a45156eb3 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Thu, 7 Jul 2022 11:34:42 +0200 Subject: [PATCH 12/92] import order in test fixed --- test/python/primitives/fidelity/test_fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/primitives/fidelity/test_fidelity.py index 54a2c4912a15..53b27f7b77b3 100644 --- a/test/python/primitives/fidelity/test_fidelity.py +++ b/test/python/primitives/fidelity/test_fidelity.py @@ -14,8 +14,8 @@ import unittest -import numpy as np from functools import partial +import numpy as np from ddt import ddt from qiskit import QuantumCircuit From d2d3e807bf3662a194348438469f28954045903f Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Fri, 8 Jul 2022 14:07:07 +0200 Subject: [PATCH 13/92] docstring fixed --- qiskit/primitives/fidelity/fidelity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index b18f441434a9..c898474054f5 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -37,7 +37,9 @@ def __init__( r""" Initializes the class to evaluate the fidelities defined as the state overlap :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, - where x and y are parametrizations of the circuits :math:`\psi` and :math:`\phi`. + where :math:`x` and :math:`y` are optional parametrizations of the + states :math:`\psi` and :math:`\phi` prepared by the circuits + ``left_circuit`` and ``right_circuit``, respectively. Args: left_circuit: (Parametrized) quantum circuit :math:`|\psi\rangle` right_circuit: (Parametrized) quantum circuit :math:`|\phi\rangle` From cfa470cec3efa22e53f3798e565d9142ab80117d Mon Sep 17 00:00:00 2001 From: Gian Gentinetta <31244916+gentinettagian@users.noreply.github.com> Date: Sat, 16 Jul 2022 11:09:54 +0200 Subject: [PATCH 14/92] Update qiskit/primitives/fidelity/base_fidelity.py Co-authored-by: dlasecki --- qiskit/primitives/fidelity/base_fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index a033f66f1cf0..8e5950339d3f 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -23,7 +23,7 @@ class BaseFidelity(ABC): """ - Implements the interface to calculate fidelities. + Defines the interface to calculate fidelities. """ def __init__( From c6534c7a9badca66299701bf90ead9925bfd2f4b Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Mon, 18 Jul 2022 14:41:18 +0200 Subject: [PATCH 15/92] context removed --- qiskit/primitives/fidelity/base_fidelity.py | 10 +-- qiskit/primitives/fidelity/fidelity.py | 70 +++++++++++-------- .../primitives/fidelity/test_fidelity.py | 44 +++++++----- 3 files changed, 67 insertions(+), 57 deletions(-) diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index 8e5950339d3f..a8aba83b573a 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -74,16 +74,10 @@ def compute( parametrizations values_left and values_right. Args: - values_left: Numerical parameters to be bound to the left circuit - values_right: Numerical parameters to be bound to the right circuit + values_left: Numerical parameters to be bound to the left circuit. + values_right: Numerical parameters to be bound to the right circuit. Returns: The overlap of two quantum states defined by two parametrized circuits. """ raise NotImplementedError - - def __enter__(self): - return self - - def __exit__(self, *exc_info): - pass diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index c898474054f5..1db9f4cbd00d 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -12,7 +12,8 @@ """ Zero probability fidelity primitive """ -from typing import Callable, List, Union, Optional +from __future__ import annotations +from typing import List import numpy as np from qiskit import QuantumCircuit @@ -20,9 +21,6 @@ from .base_fidelity import BaseFidelity -SamplerFactory = Callable[[List[QuantumCircuit]], Sampler] - - class Fidelity(BaseFidelity): """ Calculates the fidelity of two quantum circuits by measuring the zero probability outcome. @@ -32,7 +30,7 @@ def __init__( self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit, - sampler_factory: SamplerFactory, + sampler: Sampler, ) -> None: r""" Initializes the class to evaluate the fidelities defined as the state overlap @@ -41,50 +39,37 @@ def __init__( states :math:`\psi` and :math:`\phi` prepared by the circuits ``left_circuit`` and ``right_circuit``, respectively. Args: - left_circuit: (Parametrized) quantum circuit :math:`|\psi\rangle` - right_circuit: (Parametrized) quantum circuit :math:`|\phi\rangle` - sampler_factory: Partial sampler used as a backend + left_circuit: (Parametrized) quantum circuit :math:`|\psi\rangle`. + right_circuit: (Parametrized) quantum circuit :math:`|\phi\rangle`. + sampler_factory: Partial sampler used as a backend. Raises: - ValueError: left_circuit and right_circuit don't have the same number of qubits + ValueError: left_circuit and right_circuit don't have the same number of qubits. """ super().__init__(left_circuit, right_circuit) circuit = self._left_circuit.compose(self._right_circuit.inverse()) circuit.measure_all() - self.sampler = sampler_factory([circuit]) + self.sampler = sampler([circuit]) def compute( self, - values_left: Optional[Union[np.ndarray, List[np.ndarray]]] = None, - values_right: Optional[Union[np.ndarray, List[np.ndarray]]] = None, + values_left: np.ndarray | List[np.ndarray] | None = None, + values_right: np.ndarray | List[np.ndarray] | None = None, ) -> np.ndarray: values_list = [] - if values_left is None: - if self._left_circuit.num_parameters != 0: - raise ValueError( - "`values_left` cannot be `None` because the left circuit has" - f"{self._left_circuit.num_parameters} free parameters." - ) - else: - values_list.append(np.atleast_2d(values_left)) - - if values_right is None: - if self._right_circuit.num_parameters != 0: - raise ValueError( - "`values_right` cannot be `None` because the right circuit has" - f"{self._right_circuit.num_parameters} free parameters." - ) - else: - values_list.append(np.atleast_2d(values_right)) + for values, side in zip([values_left, values_right], ["left", "right"]): + values = self._check_values(values, side) + if values is not None: + values_list.append(values) if len(values_list) > 0: if len(values_list) == 2 and values_list[0].shape[0] != values_list[1].shape[0]: raise ValueError( f"The number of left parameters (currently {values_list[0].shape[0]})" - "has to be equal to the number of right parameters" + "has to be equal to the number of right parameters." f"(currently {values_list[1].shape[0]})" ) values = np.hstack(values_list) @@ -97,3 +82,28 @@ def compute( overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] return np.array(overlaps) + + def _check_values( + self, values: np.ndarray | List[np.ndarray] | None, side: str + ) -> np.ndarray | None: + """ + Check whether the passed values match the shape of the parameters of the circuit on the side + provided. + + Returns a 2D-Array if values match, `None` if no parameters are passed and raises an error if + the shapes don't match. + """ + if side == "left": + circuit = self._left_circuit + else: + circuit = self._right_circuit + + if values is None: + if circuit.num_parameters != 0: + raise ValueError( + f"`values_{side}` cannot be `None` because the {side} circuit has" + f"{circuit.num_parameters} free parameters." + ) + return None + else: + return np.atleast_2d(values) diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/primitives/fidelity/test_fidelity.py index 53b27f7b77b3..00be69983433 100644 --- a/test/python/primitives/fidelity/test_fidelity.py +++ b/test/python/primitives/fidelity/test_fidelity.py @@ -55,42 +55,48 @@ def setUp(self): def test_fidelity_1param_pair(self): """test for fidelity with one pair of parameters""" - with Fidelity(self._circuit[0], self._circuit[1], self._sampler_factory) as fidelity: - results = fidelity.compute(self._params_left[0], self._params_right[0]) - np.testing.assert_allclose(results, np.array([1.0])) + fidelity = Fidelity(self._circuit[0], self._circuit[1], self._sampler_factory) + results = fidelity.compute(self._params_left[0], self._params_right[0]) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0])) def test_fidelity_4param_pairs(self): """test for fidelity with four pairs of parameters""" - with Fidelity(self._circuit[0], self._circuit[1], self._sampler_factory) as fidelity: - results = fidelity.compute(self._params_left, self._params_right) - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + fidelity = Fidelity(self._circuit[0], self._circuit[1], self._sampler_factory) + results = fidelity.compute(self._params_left, self._params_right) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_fidelity_symmetry(self): """test for fidelity with the same circuit""" - with Fidelity(self._circuit[0], self._circuit[0], self._sampler_factory) as fidelity: - results_1 = fidelity.compute(self._params_left, self._params_right) - results_2 = fidelity.compute(self._params_right, self._params_left) - np.testing.assert_allclose(results_1, results_2, atol=1e-16) + fidelity = Fidelity(self._circuit[0], self._circuit[0], self._sampler_factory) + results_1 = fidelity.compute(self._params_left, self._params_right) + results_2 = fidelity.compute(self._params_right, self._params_left) + fidelity.sampler.close() + np.testing.assert_allclose(results_1, results_2, atol=1e-16) def test_fidelity_no_params(self): """test for fidelity without parameters""" - with Fidelity(self._circuit[2], self._circuit[3], self._sampler_factory) as fidelity: - results = fidelity.compute() - np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) + fidelity = Fidelity(self._circuit[2], self._circuit[3], self._sampler_factory) + results = fidelity.compute() + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) def test_fidelity_left_param(self): """test for fidelity with only left parameters""" - with Fidelity(self._circuit[1], self._circuit[3], self._sampler_factory) as fidelity: - results = fidelity.compute(values_left=self._params_left) - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + fidelity = Fidelity(self._circuit[1], self._circuit[3], self._sampler_factory) + results = fidelity.compute(values_left=self._params_left) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_fidelity_right_param(self): """test for fidelity with only right parameters""" - with Fidelity(self._circuit[3], self._circuit[1], self._sampler_factory) as fidelity: - results = fidelity.compute(values_right=self._params_left) - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + fidelity = Fidelity(self._circuit[3], self._circuit[1], self._sampler_factory) + results = fidelity.compute(values_right=self._params_left) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) if __name__ == "__main__": From de2f6d905d082330e7fecc9748d7ec8e4168f4a2 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 18 Jul 2022 18:33:03 +0200 Subject: [PATCH 16/92] Reset branch --- qiskit/primitives/fidelity/__init__.py | 0 qiskit/primitives/fidelity/base_fidelity.py | 121 ++++++++++++++++++ qiskit/primitives/fidelity/fidelity.py | 107 ++++++++++++++++ .../primitives/fidelity/test_fidelity.py | 116 +++++++++++++++++ 4 files changed, 344 insertions(+) create mode 100644 qiskit/primitives/fidelity/__init__.py create mode 100644 qiskit/primitives/fidelity/base_fidelity.py create mode 100644 qiskit/primitives/fidelity/fidelity.py create mode 100644 test/python/primitives/fidelity/test_fidelity.py diff --git a/qiskit/primitives/fidelity/__init__.py b/qiskit/primitives/fidelity/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py new file mode 100644 index 000000000000..df831aaced3d --- /dev/null +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -0,0 +1,121 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Base fidelity primitive +""" + +from abc import ABC, abstractmethod +from typing import List, Union, Optional +import numpy as np + +from qiskit import QuantumCircuit +from qiskit.circuit import ParameterVector + + +class BaseFidelity(ABC): + """ + Defines the interface to calculate fidelities. + """ + + def __init__( + self, + left_circuit: QuantumCircuit | None = None, + right_circuit: QuantumCircuit | None = None, + ) -> None: + r"""Initializes the class to evaluate the fidelities defined as + + :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, + + where :math:`x` and :math:`y` are optional parametrizations of the + states :math:`\psi` and :math:`\phi` prepared by the circuits + ``left_circuit`` and ``right_circuit``, respectively. + + Args: + left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + + Raises: + ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. + """ + + if left_circuit is None or right_circuit is None: + self._left_circuit = None + self._right_circuit = None + self._left_parameters = None + self._right_parameters = None + else: + self.set_circuits(left_circuit, right_circuit) + + @abstractmethod + def __call__( + self, + values_left: Optional[Union[np.ndarray, List[np.ndarray]]] = None, + values_right: Optional[Union[np.ndarray, List[np.ndarray]]] = None, + ) -> np.ndarray: + """Compute the overlap of two quantum states bound by the + parametrizations values_left and values_right. + + Args: + values_left: Numerical parameters to be bound to the left circuit. + values_right: Numerical parameters to be bound to the right circuit. + + Returns: + The overlap of two quantum states defined by two parametrized circuits. + """ + raise NotImplementedError + + def _check_values( + self, values: np.ndarray | List[np.ndarray] | None, side: str + ) -> np.ndarray | None: + """ + Check whether the passed values match the shape of the parameters of the circuit on the side + provided. + + Returns a 2D-Array if values match, `None` if no parameters are passed and raises an error if + the shapes don't match. + """ + if side == "left": + circuit = self._left_circuit + else: + circuit = self._right_circuit + + if values is None: + if circuit.num_parameters != 0: + raise ValueError( + f"`values_{side}` cannot be `None` because the {side} circuit has" + f"{circuit.num_parameters} free parameters." + ) + return None + else: + return np.atleast_2d(values) + + @abstractmethod + def set_circuits(self, + left_circuit: QuantumCircuit, + right_circuit: QuantumCircuit): + """ + Fix the circuits for the fidelity to be computed of. + Args: + - left_circuit: (Parametrized) quantum circuit + - right_circuit: (Parametrized) quantum circuit + """ + if left_circuit.num_qubits != right_circuit.num_qubits: + raise ValueError( + f"The number of qubits for the left circuit ({left_circuit.num_qubits}) \ + and right circuit ({right_circuit.num_qubits}) do not coincide." + ) + # Assigning parameter arrays to the two circuits + self._left_parameters = ParameterVector("x", left_circuit.num_parameters) + self._left_circuit = left_circuit.assign_parameters(self._left_parameters) + + self._right_parameters = ParameterVector("y", right_circuit.num_parameters) + self._right_circuit = right_circuit.assign_parameters(self._right_parameters) diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py new file mode 100644 index 000000000000..e0093ca85fd3 --- /dev/null +++ b/qiskit/primitives/fidelity/fidelity.py @@ -0,0 +1,107 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Zero probability fidelity primitive +""" +from __future__ import annotations +from typing import List +import numpy as np + +from qiskit import QuantumCircuit +from qiskit.primitives import Sampler + +from .base_fidelity import BaseFidelity + + +class Fidelity(BaseFidelity): + """ + Calculates the fidelity of two quantum circuits by measuring the zero probability outcome. + """ + + def __init__( + self, + sampler: Sampler, + left_circuit: QuantumCircuit | None = None, + right_circuit: QuantumCircuit | None = None, + ) -> None: + r""" + Initializes the class to evaluate the fidelities defined as the state overlap + :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, + where :math:`x` and :math:`y` are optional parametrizations of the + states :math:`\psi` and :math:`\phi` prepared by the circuits + ``left_circuit`` and ``right_circuit``, respectively. + Args: + left_circuit: (Parametrized) quantum circuit :math:`|\psi\rangle`. + right_circuit: (Parametrized) quantum circuit :math:`|\phi\rangle`. + sampler_factory: Partial sampler used as a backend. + Raises: + ValueError: left_circuit and right_circuit don't have the same number of qubits. + """ + self.sampler = sampler + super().__init__(left_circuit, right_circuit) + + def __call__( + self, + values_left: np.ndarray | List[np.ndarray] | None = None, + values_right: np.ndarray | List[np.ndarray] | None = None, + ) -> np.ndarray: + + values_list = [] + + if self._left_circuit is None or self._right_circuit is None: + raise ValueError( + f"The left and right circuits must be defined to" + f"calculate the state overlap. " + f"Please use .set_circuits(left_circuit, right_circuit)" + ) + + for values, side in zip([values_left, values_right], ["left", "right"]): + values = self._check_values(values, side) + if values is not None: + values_list.append(values) + + if len(values_list) > 0: + if len(values_list) == 2 and values_list[0].shape[0] != values_list[1].shape[0]: + raise ValueError( + f"The number of left parameters (currently {values_list[0].shape[0]})" + "has to be equal to the number of right parameters." + f"(currently {values_list[1].shape[0]})" + ) + values = np.hstack(values_list) + result = self.sampler(circuits=[0] * len(values), parameter_values=values) + else: + result = self.sampler(circuits=[0]) + + # if error mititgation is added in the future, we will have to handle + # negative values in some way (e.g. clipping to zero) + overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] + + return np.array(overlaps) + + def set_circuits(self, + left_circuit: QuantumCircuit, + right_circuit: QuantumCircuit): + """ + Fix the circuits for the fidelity to be computed of. + Args: + - left_circuit: (Parametrized) quantum circuit + - right_circuit: (Parametrized) quantum circuit + """ + super().set_circuits(left_circuit=left_circuit, right_circuit=right_circuit) + + circuit = self._left_circuit.compose(self._right_circuit.inverse()) + circuit.measure_all() + + # in the future this should be self.sampler.add_circuit(circuit) + # Careful! because add_circuits doesn't exist yet, calling this method + # twice will make it store the result of a sampler call in self.sampler. + self.sampler = self.sampler([circuit]) diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/primitives/fidelity/test_fidelity.py new file mode 100644 index 000000000000..da9f202537a0 --- /dev/null +++ b/test/python/primitives/fidelity/test_fidelity.py @@ -0,0 +1,116 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for Fidelity.""" + +import unittest + +from functools import partial +import numpy as np +from ddt import ddt + +from qiskit import QuantumCircuit +from qiskit.circuit import ParameterVector +from qiskit.primitives import Sampler +from qiskit.primitives.fidelity import Fidelity +from qiskit.test import QiskitTestCase + + +@ddt +class TestFidelity(QiskitTestCase): + """Test Fidelity""" + + def setUp(self): + super().setUp() + parameters = ParameterVector("x", 2) + + rx_rotations = QuantumCircuit(2) + rx_rotations.rx(parameters[0], 0) + rx_rotations.rx(parameters[1], 1) + + ry_rotations = QuantumCircuit(2) + ry_rotations.ry(parameters[0], 0) + ry_rotations.ry(parameters[1], 1) + + plus = QuantumCircuit(2) + plus.h(0) + plus.h(1) + + zero = QuantumCircuit(2) + + self._circuit = [rx_rotations, ry_rotations, plus, zero] + self._sampler_factory = partial(Sampler) + self._params_left = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) + self._params_right = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) + + def test_fidelity_1param_pair(self): + """test for fidelity with one pair of parameters""" + + fidelity = Fidelity(self._sampler_factory, self._circuit[0], self._circuit[1]) + results = fidelity(self._params_left[0], self._params_right[0]) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0])) + + def test_fidelity_4param_pairs(self): + """test for fidelity with four pairs of parameters""" + + fidelity = Fidelity(self._sampler_factory, self._circuit[0], self._circuit[1]) + results = fidelity(self._params_left, self._params_right) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + + def test_fidelity_symmetry(self): + """test for fidelity with the same circuit""" + + fidelity = Fidelity(self._sampler_factory, self._circuit[0], self._circuit[0]) + results_1 = fidelity(self._params_left, self._params_right) + results_2 = fidelity(self._params_right, self._params_left) + fidelity.sampler.close() + np.testing.assert_allclose(results_1, results_2, atol=1e-16) + + def test_fidelity_no_params(self): + """test for fidelity without parameters""" + fidelity = Fidelity(self._sampler_factory, self._circuit[2], self._circuit[3]) + results = fidelity() + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) + + def test_fidelity_left_param(self): + """test for fidelity with only left parameters""" + fidelity = Fidelity(self._sampler_factory, self._circuit[1], self._circuit[3]) + results = fidelity(values_left=self._params_left) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + + def test_fidelity_right_param(self): + """test for fidelity with only right parameters""" + fidelity = Fidelity(self._sampler_factory, self._circuit[3], self._circuit[1]) + results = fidelity(values_right=self._params_left) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + + def test_fidelity_not_set_circuits(self): + "test for fidelity with no circuits during init." + fidelity = Fidelity(self._sampler_factory) + with self.assertRaises(ValueError): + _ = fidelity(self._params_left, self._params_right) + + def test_fidelity_set_circuits(self): + "test for fidelity with no circuits during init." + fidelity = Fidelity(self._sampler_factory) + fidelity.set_circuits(self._circuit[0], self._circuit[1]) + results = fidelity(self._params_left, self._params_right) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + +if __name__ == "__main__": + unittest.main() From b57b86e5e571b9326b7dfbac57de7127a5c89373 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 18 Jul 2022 18:33:29 +0200 Subject: [PATCH 17/92] Add init --- test/python/primitives/__init__.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/python/primitives/__init__.py b/test/python/primitives/__init__.py index 25355329c123..eeb4bede9c7c 100644 --- a/test/python/primitives/__init__.py +++ b/test/python/primitives/__init__.py @@ -9,5 +9,18 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. - -"""Tests for the primitives.""" +""" +===================================== +Fidelity Primitives (:mod:`qiskit.primitives.fidelity`) +===================================== +.. currentmodule:: qiskit.primitives.fidelity +Fidelity +========= +.. autosummary:: + :toctree: ../stubs/ + BaseFidelity + Fidelity +""" +from .base_fidelity import BaseFidelity +from .fidelity import Fidelity +__all__ = ["BaseFidelity", "Fidelity"] \ No newline at end of file From 4504de3cf5b483cc4e214b2f4da9362c03c4aa9d Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 18 Jul 2022 18:43:12 +0200 Subject: [PATCH 18/92] Fix inits --- qiskit/primitives/fidelity/__init__.py | 26 ++++++++++++++++++++++++++ test/python/primitives/__init__.py | 17 ++--------------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/qiskit/primitives/fidelity/__init__.py b/qiskit/primitives/fidelity/__init__.py index e69de29bb2d1..eeb4bede9c7c 100644 --- a/qiskit/primitives/fidelity/__init__.py +++ b/qiskit/primitives/fidelity/__init__.py @@ -0,0 +1,26 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +===================================== +Fidelity Primitives (:mod:`qiskit.primitives.fidelity`) +===================================== +.. currentmodule:: qiskit.primitives.fidelity +Fidelity +========= +.. autosummary:: + :toctree: ../stubs/ + BaseFidelity + Fidelity +""" +from .base_fidelity import BaseFidelity +from .fidelity import Fidelity +__all__ = ["BaseFidelity", "Fidelity"] \ No newline at end of file diff --git a/test/python/primitives/__init__.py b/test/python/primitives/__init__.py index eeb4bede9c7c..25355329c123 100644 --- a/test/python/primitives/__init__.py +++ b/test/python/primitives/__init__.py @@ -9,18 +9,5 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -===================================== -Fidelity Primitives (:mod:`qiskit.primitives.fidelity`) -===================================== -.. currentmodule:: qiskit.primitives.fidelity -Fidelity -========= -.. autosummary:: - :toctree: ../stubs/ - BaseFidelity - Fidelity -""" -from .base_fidelity import BaseFidelity -from .fidelity import Fidelity -__all__ = ["BaseFidelity", "Fidelity"] \ No newline at end of file + +"""Tests for the primitives.""" From 45767dfdcc8148be656d8461bba937f93b7037c1 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Tue, 19 Jul 2022 08:51:12 +0200 Subject: [PATCH 19/92] lint fixes --- qiskit/primitives/fidelity/base_fidelity.py | 7 +++++-- qiskit/primitives/fidelity/fidelity.py | 6 +++--- test/python/primitives/fidelity/test_fidelity.py | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index 28251aabb62b..3a6e6e92c812 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -103,8 +103,11 @@ def set_circuits(self, left_circuit: QuantumCircuit, right_circuit: QuantumCircu """ Fix the circuits for the fidelity to be computed of. Args: - - left_circuit: (Parametrized) quantum circuit - - right_circuit: (Parametrized) quantum circuit + left_circuit: (Parametrized) quantum circuit + right_circuit: (Parametrized) quantum circuit + + Raises: + ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ if left_circuit.num_qubits != right_circuit.num_qubits: raise ValueError( diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index b5a7a4c4008a..3a450973b778 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -57,9 +57,9 @@ def __call__( if self._left_circuit is None or self._right_circuit is None: raise ValueError( - f"The left and right circuits must be defined to" - f"calculate the state overlap. " - f"Please use .set_circuits(left_circuit, right_circuit)" + "The left and right circuits must be defined to" + "calculate the state overlap. " + "Please use .set_circuits(left_circuit, right_circuit)" ) for values, side in zip([values_left, values_right], ["left", "right"]): diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/primitives/fidelity/test_fidelity.py index 32e20da521f0..eb7f37109dbc 100644 --- a/test/python/primitives/fidelity/test_fidelity.py +++ b/test/python/primitives/fidelity/test_fidelity.py @@ -99,13 +99,13 @@ def test_fidelity_right_param(self): np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_fidelity_not_set_circuits(self): - "test for fidelity with no circuits during init." + """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler_factory) with self.assertRaises(ValueError): _ = fidelity(self._params_left, self._params_right) def test_fidelity_set_circuits(self): - "test for fidelity with no circuits during init." + """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler_factory) fidelity.set_circuits(self._circuit[0], self._circuit[1]) results = fidelity(self._params_left, self._params_right) From 07b33a1d00b235aa92f1cdc52b63cb3394b4a13e Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Thu, 21 Jul 2022 14:01:44 +0200 Subject: [PATCH 20/92] removed abstractmethod label for set_circuits --- qiskit/primitives/fidelity/base_fidelity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index 3a6e6e92c812..5e0977a2ddac 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -98,7 +98,6 @@ def _check_values( else: return np.atleast_2d(values) - @abstractmethod def set_circuits(self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit): """ Fix the circuits for the fidelity to be computed of. From c192e3fe281bd89daaa63bf93cdee57ed2c62101 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Wed, 27 Jul 2022 11:27:15 +0200 Subject: [PATCH 21/92] added support for setting only one circuit --- qiskit/primitives/fidelity/base_fidelity.py | 46 +++++++++++++++++---- qiskit/primitives/fidelity/fidelity.py | 22 ++++++---- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index 5e0977a2ddac..5fc618f6c4c2 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -98,7 +98,9 @@ def _check_values( else: return np.atleast_2d(values) - def set_circuits(self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit): + def set_circuits( + self, left_circuit: QuantumCircuit | None, right_circuit: QuantumCircuit | None + ): """ Fix the circuits for the fidelity to be computed of. Args: @@ -108,14 +110,42 @@ def set_circuits(self, left_circuit: QuantumCircuit, right_circuit: QuantumCircu Raises: ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ + if left_circuit is not None and right_circuit is not None: + self._check_qubits_mismatch(left_circuit, right_circuit) + self._set_left_circuit(left_circuit) + self._set_right_circuit(right_circuit) + elif left_circuit is not None: + self._check_qubits_mismatch(left_circuit, self._right_circuit) + self._set_left_circuit(left_circuit) + elif right_circuit is not None: + self._check_qubits_mismatch(self._left_circuit, right_circuit) + self._set_right_circuit(right_circuit) + else: + raise ValueError( + "At least one of the arguments `left_circuit` or `right_circuit` must not be `None`." + ) + + def _set_left_circuit(self, circuit: QuantumCircuit) -> None: + """ + Fix the left circuit. If `check_num_qubits` the number of qubits are compared + to the right circuit. + """ + self._left_parameters = ParameterVector("x", circuit.num_parameters) + self._left_circuit = circuit.assign_parameters(self._left_parameters) + + def _set_right_circuit(self, circuit: QuantumCircuit) -> None: + """ + Fix the right circuit. If `check_num_qubits` the number of qubits are compared + to the left circuit. + """ + self._right_parameters = ParameterVector("y", circuit.num_parameters) + self._right_circuit = circuit.assign_parameters(self._right_parameters) + + def _check_qubits_mismatch( + self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit + ) -> None: if left_circuit.num_qubits != right_circuit.num_qubits: raise ValueError( f"The number of qubits for the left circuit ({left_circuit.num_qubits}) \ - and right circuit ({right_circuit.num_qubits}) do not coincide." + and right circuit ({right_circuit.num_qubits}) do not coincide." ) - # Assigning parameter arrays to the two circuits - self._left_parameters = ParameterVector("x", left_circuit.num_parameters) - self._left_circuit = left_circuit.assign_parameters(self._left_parameters) - - self._right_parameters = ParameterVector("y", right_circuit.num_parameters) - self._right_circuit = right_circuit.assign_parameters(self._right_parameters) diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index 3a450973b778..321b4af2a1d8 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -85,19 +85,23 @@ def __call__( return np.array(overlaps) - def set_circuits(self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit): + def set_circuits( + self, left_circuit: QuantumCircuit | None, right_circuit: QuantumCircuit | None + ): """ Fix the circuits for the fidelity to be computed of. Args: - - left_circuit: (Parametrized) quantum circuit - - right_circuit: (Parametrized) quantum circuit + left_circuit: (Parametrized) quantum circuit + right_circuit: (Parametrized) quantum circuit """ super().set_circuits(left_circuit=left_circuit, right_circuit=right_circuit) - circuit = self._left_circuit.compose(self._right_circuit.inverse()) - circuit.measure_all() + if self._left_circuit is not None and self._right_circuit is not None: + # If both circuits have been set, we can construct the overlap circuit. + circuit = self._left_circuit.compose(self._right_circuit.inverse()) + circuit.measure_all() - # in the future this should be self.sampler.add_circuit(circuit) - # Careful! because add_circuits doesn't exist yet, calling this method - # twice will make it store the result of a sampler call in self.sampler. - self.sampler = self.sampler([circuit]) + # in the future this should be self.sampler.add_circuit(circuit) + # Careful! because add_circuits doesn't exist yet, calling this method + # twice will make it store the result of a sampler call in self.sampler. + self.sampler = self.sampler([circuit]) From e44305c8eb7cee9ef24eedbafa732a09e12e0e23 Mon Sep 17 00:00:00 2001 From: Gian Gentinetta Date: Wed, 27 Jul 2022 11:39:16 +0200 Subject: [PATCH 22/92] new unittest --- qiskit/primitives/fidelity/base_fidelity.py | 15 +++++++++------ qiskit/primitives/fidelity/fidelity.py | 7 ++++++- test/python/primitives/fidelity/test_fidelity.py | 11 +++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/primitives/fidelity/base_fidelity.py index 5fc618f6c4c2..1f7776cb64c6 100644 --- a/qiskit/primitives/fidelity/base_fidelity.py +++ b/qiskit/primitives/fidelity/base_fidelity.py @@ -99,7 +99,9 @@ def _check_values( return np.atleast_2d(values) def set_circuits( - self, left_circuit: QuantumCircuit | None, right_circuit: QuantumCircuit | None + self, + left_circuit: QuantumCircuit | None = None, + right_circuit: QuantumCircuit | None = None, ): """ Fix the circuits for the fidelity to be computed of. @@ -144,8 +146,9 @@ def _set_right_circuit(self, circuit: QuantumCircuit) -> None: def _check_qubits_mismatch( self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit ) -> None: - if left_circuit.num_qubits != right_circuit.num_qubits: - raise ValueError( - f"The number of qubits for the left circuit ({left_circuit.num_qubits}) \ - and right circuit ({right_circuit.num_qubits}) do not coincide." - ) + if left_circuit is not None and right_circuit is not None: + if left_circuit.num_qubits != right_circuit.num_qubits: + raise ValueError( + f"The number of qubits for the left circuit ({left_circuit.num_qubits}) \ + and right circuit ({right_circuit.num_qubits}) do not coincide." + ) diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/primitives/fidelity/fidelity.py index 321b4af2a1d8..fc6c693e6a04 100644 --- a/qiskit/primitives/fidelity/fidelity.py +++ b/qiskit/primitives/fidelity/fidelity.py @@ -86,13 +86,18 @@ def __call__( return np.array(overlaps) def set_circuits( - self, left_circuit: QuantumCircuit | None, right_circuit: QuantumCircuit | None + self, + left_circuit: QuantumCircuit | None = None, + right_circuit: QuantumCircuit | None = None, ): """ Fix the circuits for the fidelity to be computed of. Args: left_circuit: (Parametrized) quantum circuit right_circuit: (Parametrized) quantum circuit + + Raises: + ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ super().set_circuits(left_circuit=left_circuit, right_circuit=right_circuit) diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/primitives/fidelity/test_fidelity.py index eb7f37109dbc..5c741fce2715 100644 --- a/test/python/primitives/fidelity/test_fidelity.py +++ b/test/python/primitives/fidelity/test_fidelity.py @@ -112,6 +112,17 @@ def test_fidelity_set_circuits(self): fidelity.sampler.close() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + def test_fidelity_set_single_circuit(self): + """test for fidelity with no circuits during init.""" + fidelity = Fidelity(self._sampler_factory) + fidelity.set_circuits(right_circuit=self._circuit[1]) + with self.assertRaises(ValueError): + _ = fidelity(self._params_left, self._params_right) + fidelity.set_circuits(left_circuit=self._circuit[0]) + results = fidelity(self._params_left, self._params_right) + fidelity.sampler.close() + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + if __name__ == "__main__": unittest.main() From 6d6fbc2ea94683267db1347c714ace143dbecb94 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 28 Jul 2022 12:14:08 +0200 Subject: [PATCH 23/92] Move fidelities to algorithms --- .../fidelity => algorithms/fidelities}/__init__.py | 4 ++-- .../fidelity => algorithms/fidelities}/base_fidelity.py | 0 .../fidelity => algorithms/fidelities}/fidelity.py | 0 .../fidelity => algorithms/fidelities}/__init__.py | 2 +- .../fidelity => algorithms/fidelities}/test_fidelity.py | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename qiskit/{primitives/fidelity => algorithms/fidelities}/__init__.py (86%) rename qiskit/{primitives/fidelity => algorithms/fidelities}/base_fidelity.py (100%) rename qiskit/{primitives/fidelity => algorithms/fidelities}/fidelity.py (100%) rename test/python/{primitives/fidelity => algorithms/fidelities}/__init__.py (89%) rename test/python/{primitives/fidelity => algorithms/fidelities}/test_fidelity.py (99%) diff --git a/qiskit/primitives/fidelity/__init__.py b/qiskit/algorithms/fidelities/__init__.py similarity index 86% rename from qiskit/primitives/fidelity/__init__.py rename to qiskit/algorithms/fidelities/__init__.py index fc4ac4bd4ff2..55d73df11112 100644 --- a/qiskit/primitives/fidelity/__init__.py +++ b/qiskit/algorithms/fidelities/__init__.py @@ -11,9 +11,9 @@ # that they have been altered from the originals. """ ===================================== -Fidelity Primitives (:mod:`qiskit.primitives.fidelity`) +Primitive-based Fidelity Interfaces (:mod:`qiskit.algorithms.fidelities`) ===================================== -.. currentmodule:: qiskit.primitives.fidelity +.. currentmodule:: qiskit.algorithms.fidelities Fidelity ========= .. autosummary:: diff --git a/qiskit/primitives/fidelity/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py similarity index 100% rename from qiskit/primitives/fidelity/base_fidelity.py rename to qiskit/algorithms/fidelities/base_fidelity.py diff --git a/qiskit/primitives/fidelity/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py similarity index 100% rename from qiskit/primitives/fidelity/fidelity.py rename to qiskit/algorithms/fidelities/fidelity.py diff --git a/test/python/primitives/fidelity/__init__.py b/test/python/algorithms/fidelities/__init__.py similarity index 89% rename from test/python/primitives/fidelity/__init__.py rename to test/python/algorithms/fidelities/__init__.py index 69bf9271c711..d8b7d587c4cc 100644 --- a/test/python/primitives/fidelity/__init__.py +++ b/test/python/algorithms/fidelities/__init__.py @@ -10,4 +10,4 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Tests for the fidelity primitives.""" +"""Tests for the primitive-based fidelity interfaces.""" diff --git a/test/python/primitives/fidelity/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py similarity index 99% rename from test/python/primitives/fidelity/test_fidelity.py rename to test/python/algorithms/fidelities/test_fidelity.py index 5c741fce2715..5cbe2c3eb54b 100644 --- a/test/python/primitives/fidelity/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -21,7 +21,7 @@ from qiskit import QuantumCircuit from qiskit.circuit import ParameterVector from qiskit.primitives import Sampler -from qiskit.primitives.fidelity import Fidelity +from qiskit.algorithms.fidelities import Fidelity from qiskit.test import QiskitTestCase From 0153e5c6f8281654d9be2b17d29ea0209e51e7fc Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 16 Aug 2022 16:06:15 +0200 Subject: [PATCH 24/92] Update interface with new design doc --- qiskit/algorithms/fidelities/base_fidelity.py | 54 +++++++++++++---- qiskit/algorithms/fidelities/fidelity.py | 59 +++++++------------ qiskit/algorithms/fidelities/fidelity_job.py | 32 ++++++++++ .../algorithms/fidelities/test_fidelity.py | 41 ++++++------- 4 files changed, 117 insertions(+), 69 deletions(-) create mode 100644 qiskit/algorithms/fidelities/fidelity_job.py diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index 1f7776cb64c6..78102ac57f4e 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -47,31 +47,59 @@ def __init__( ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ + self._circuit = None if left_circuit is None or right_circuit is None: self._left_circuit = None self._right_circuit = None self._left_parameters = None self._right_parameters = None else: - self.set_circuits(left_circuit, right_circuit) + self._set_circuits(left_circuit, right_circuit) - @abstractmethod - def __call__( + def run( self, - values_left: np.ndarray | list[np.ndarray] | None = None, - values_right: np.ndarray | list[np.ndarray] | None = None, - ) -> np.ndarray: + left_values: np.ndarray | list[np.ndarray] | None = None, + right_values: np.ndarray | list[np.ndarray] | None = None, + left_circuit: QuantumCircuit = None, + right_circuit: QuantumCircuit = None, + **run_options, + ) -> FidelityJob: """Compute the overlap of two quantum states bound by the - parametrizations values_left and values_right. + parametrizations left_values and right_values. Args: - values_left: Numerical parameters to be bound to the left circuit. - values_right: Numerical parameters to be bound to the right circuit. + left_values: Numerical parameters to be bound to the left circuit. + right_values: Numerical parameters to be bound to the right circuit. + left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. Returns: The overlap of two quantum states defined by two parametrized circuits. """ - raise NotImplementedError + return self._run(left_values, right_values, left_circuit, right_circuit, **run_options) + + @abstractmethod + def _run( + self, + left_values: np.ndarray | list[np.ndarray] | None = None, + right_values: np.ndarray | list[np.ndarray] | None = None, + left_circuit: QuantumCircuit = None, + right_circuit: QuantumCircuit = None, + **run_options, + ) -> FidelityJob: + """Compute the overlap of two quantum states bound by the + parametrizations left_values and right_values. + + Args: + left_values: Numerical parameters to be bound to the left circuit. + right_values: Numerical parameters to be bound to the right circuit. + left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + + Returns: + The overlap of two quantum states defined by two parametrized circuits. + """ + raise NotImplementedError() def _check_values( self, values: np.ndarray | list[np.ndarray] | None, side: str @@ -98,7 +126,7 @@ def _check_values( else: return np.atleast_2d(values) - def set_circuits( + def _set_circuits( self, left_circuit: QuantumCircuit | None = None, right_circuit: QuantumCircuit | None = None, @@ -127,6 +155,10 @@ def set_circuits( "At least one of the arguments `left_circuit` or `right_circuit` must not be `None`." ) + circuit = self._left_circuit.compose(self._right_circuit.inverse()) + circuit.measure_all() + self._circuit = circuit + def _set_left_circuit(self, circuit: QuantumCircuit) -> None: """ Fix the left circuit. If `check_num_qubits` the number of qubits are compared diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index fc6c693e6a04..0192ea1b8d29 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -18,7 +18,7 @@ from qiskit import QuantumCircuit from qiskit.primitives import Sampler from .base_fidelity import BaseFidelity - +from .fidelity_job import FidelityJob class Fidelity(BaseFidelity): """ @@ -40,29 +40,35 @@ def __init__( Args: left_circuit: (Parametrized) quantum circuit :math:`|\psi\rangle`. right_circuit: (Parametrized) quantum circuit :math:`|\phi\rangle`. - sampler_factory: Partial sampler used as a backend. + sampler: Sampler primitive instance. Raises: ValueError: left_circuit and right_circuit don't have the same number of qubits. """ self.sampler = sampler super().__init__(left_circuit, right_circuit) - def __call__( + def _run( self, - values_left: np.ndarray | list[np.ndarray] | None = None, - values_right: np.ndarray | list[np.ndarray] | None = None, - ) -> np.ndarray: + left_values: np.ndarray | list[np.ndarray] | None = None, + right_values: np.ndarray | list[np.ndarray] | None = None, + left_circuit: QuantumCircuit = None, + right_circuit: QuantumCircuit = None, + **run_options, + ) -> FidelityJob: - values_list = [] + if left_circuit is not None: + self._set_circuits(left_circuit = left_circuit) + if right_circuit is not None: + self._set_circuits(right_circuit = right_circuit) + values_list = [] if self._left_circuit is None or self._right_circuit is None: raise ValueError( "The left and right circuits must be defined to" "calculate the state overlap. " - "Please use .set_circuits(left_circuit, right_circuit)" ) - for values, side in zip([values_left, values_right], ["left", "right"]): + for values, side in zip([left_values, right_values], ["left", "right"]): values = self._check_values(values, side) if values is not None: values_list.append(values) @@ -75,38 +81,15 @@ def __call__( f"(currently {values_list[1].shape[0]})" ) values = np.hstack(values_list) - result = self.sampler(circuits=[0] * len(values), parameter_values=values) + job = self.sampler.run(circuits=[self._circuit] * len(values), parameter_values=values) else: - result = self.sampler(circuits=[0]) + job = self.sampler.run(circuits=self._circuit) + + result = job.result() - # if error mititgation is added in the future, we will have to handle + # if error mitigation is added in the future, we will have to handle # negative values in some way (e.g. clipping to zero) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] - return np.array(overlaps) - - def set_circuits( - self, - left_circuit: QuantumCircuit | None = None, - right_circuit: QuantumCircuit | None = None, - ): - """ - Fix the circuits for the fidelity to be computed of. - Args: - left_circuit: (Parametrized) quantum circuit - right_circuit: (Parametrized) quantum circuit - - Raises: - ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. - """ - super().set_circuits(left_circuit=left_circuit, right_circuit=right_circuit) - - if self._left_circuit is not None and self._right_circuit is not None: - # If both circuits have been set, we can construct the overlap circuit. - circuit = self._left_circuit.compose(self._right_circuit.inverse()) - circuit.measure_all() + return FidelityJob(result= np.array(overlaps), status=job.status()) - # in the future this should be self.sampler.add_circuit(circuit) - # Careful! because add_circuits doesn't exist yet, calling this method - # twice will make it store the result of a sampler call in self.sampler. - self.sampler = self.sampler([circuit]) diff --git a/qiskit/algorithms/fidelities/fidelity_job.py b/qiskit/algorithms/fidelities/fidelity_job.py new file mode 100644 index 000000000000..8e2ee568fb71 --- /dev/null +++ b/qiskit/algorithms/fidelities/fidelity_job.py @@ -0,0 +1,32 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Sampler result class +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from qiskit.providers import JobStatus + + +@dataclass(frozen=True) +class FidelityJob: + """Result of Fidelity class. + Args: + results: np.array with calculated state overlaps + status: JobStatus for the Fidelity job + """ + + result: np.array + status: JobStatus diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index 5cbe2c3eb54b..d98a26b9f8a7 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -14,7 +14,7 @@ import unittest -from functools import partial +# from functools import partial import numpy as np from ddt import ddt @@ -48,7 +48,7 @@ def setUp(self): zero = QuantumCircuit(2) self._circuit = [rx_rotations, ry_rotations, plus, zero] - self._sampler_factory = partial(Sampler) + self._sampler_factory = Sampler() self._params_left = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) self._params_right = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) @@ -56,59 +56,60 @@ def test_fidelity_1param_pair(self): """test for fidelity with one pair of parameters""" fidelity = Fidelity(self._sampler_factory, self._circuit[0], self._circuit[1]) - results = fidelity(self._params_left[0], self._params_right[0]) - fidelity.sampler.close() - np.testing.assert_allclose(results, np.array([1.0])) + job = fidelity.run(self._params_left[0], self._params_right[0]) + result = job.result + np.testing.assert_allclose(result, np.array([1.0])) def test_fidelity_4param_pairs(self): """test for fidelity with four pairs of parameters""" fidelity = Fidelity(self._sampler_factory, self._circuit[0], self._circuit[1]) - results = fidelity(self._params_left, self._params_right) - fidelity.sampler.close() + job = fidelity.run(self._params_left, self._params_right) + results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_fidelity_symmetry(self): """test for fidelity with the same circuit""" fidelity = Fidelity(self._sampler_factory, self._circuit[0], self._circuit[0]) - results_1 = fidelity(self._params_left, self._params_right) - results_2 = fidelity(self._params_right, self._params_left) - fidelity.sampler.close() + job_1 = fidelity.run(self._params_left, self._params_right) + job_2 = fidelity.run(self._params_right, self._params_left) + results_1 = job_1.result + results_2 = job_2.result np.testing.assert_allclose(results_1, results_2, atol=1e-16) def test_fidelity_no_params(self): """test for fidelity without parameters""" fidelity = Fidelity(self._sampler_factory, self._circuit[2], self._circuit[3]) - results = fidelity() - fidelity.sampler.close() + job = fidelity.run() + results = job.result np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) def test_fidelity_left_param(self): """test for fidelity with only left parameters""" fidelity = Fidelity(self._sampler_factory, self._circuit[1], self._circuit[3]) - results = fidelity(values_left=self._params_left) - fidelity.sampler.close() + job = fidelity.run(values_left=self._params_left) + results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_fidelity_right_param(self): """test for fidelity with only right parameters""" fidelity = Fidelity(self._sampler_factory, self._circuit[3], self._circuit[1]) - results = fidelity(values_right=self._params_left) - fidelity.sampler.close() + job = fidelity.run(values_right=self._params_left) + results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_fidelity_not_set_circuits(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler_factory) with self.assertRaises(ValueError): - _ = fidelity(self._params_left, self._params_right) + _ = fidelity.run(self._params_left, self._params_right) def test_fidelity_set_circuits(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler_factory) fidelity.set_circuits(self._circuit[0], self._circuit[1]) - results = fidelity(self._params_left, self._params_right) + results = fidelity.run(self._params_left, self._params_right) fidelity.sampler.close() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) @@ -117,9 +118,9 @@ def test_fidelity_set_single_circuit(self): fidelity = Fidelity(self._sampler_factory) fidelity.set_circuits(right_circuit=self._circuit[1]) with self.assertRaises(ValueError): - _ = fidelity(self._params_left, self._params_right) + _ = fidelity.run(self._params_left, self._params_right) fidelity.set_circuits(left_circuit=self._circuit[0]) - results = fidelity(self._params_left, self._params_right) + results = fidelity.run(self._params_left, self._params_right) fidelity.sampler.close() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) From 87a75783bc5a4fb0f1e2a38bcf89e1df52f23576 Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 16 Aug 2022 16:47:51 +0200 Subject: [PATCH 25/92] Fix docstrings and unit tests --- qiskit/algorithms/fidelities/base_fidelity.py | 31 +++++----- qiskit/algorithms/fidelities/fidelity.py | 25 ++++++-- .../algorithms/fidelities/test_fidelity.py | 60 +++++++++---------- 3 files changed, 64 insertions(+), 52 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index 78102ac57f4e..6ba03f365f7c 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -88,17 +88,17 @@ def _run( **run_options, ) -> FidelityJob: """Compute the overlap of two quantum states bound by the - parametrizations left_values and right_values. - - Args: - left_values: Numerical parameters to be bound to the left circuit. - right_values: Numerical parameters to be bound to the right circuit. - left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. - right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. - - Returns: - The overlap of two quantum states defined by two parametrized circuits. - """ + parametrizations left_values and right_values. + Args: + left_values: Numerical parameters to be bound to the left circuit. + right_values: Numerical parameters to be bound to the right circuit. + left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + run_options: Backend runtime options used for circuit execution. + + Returns: + The job object for the fidelity calculation. + """ raise NotImplementedError() def _check_values( @@ -119,8 +119,8 @@ def _check_values( if values is None: if circuit.num_parameters != 0: raise ValueError( - f"`values_{side}` cannot be `None` because the {side} circuit has" - f"{circuit.num_parameters} free parameters." + f"`values_{side}` cannot be `None` because the {side} circuit has " + f"{circuit.num_parameters} free parameters. {circuit}" ) return None else: @@ -144,6 +144,7 @@ def _set_circuits( self._check_qubits_mismatch(left_circuit, right_circuit) self._set_left_circuit(left_circuit) self._set_right_circuit(right_circuit) + elif left_circuit is not None: self._check_qubits_mismatch(left_circuit, self._right_circuit) self._set_left_circuit(left_circuit) @@ -155,10 +156,6 @@ def _set_circuits( "At least one of the arguments `left_circuit` or `right_circuit` must not be `None`." ) - circuit = self._left_circuit.compose(self._right_circuit.inverse()) - circuit.measure_all() - self._circuit = circuit - def _set_left_circuit(self, circuit: QuantumCircuit) -> None: """ Fix the left circuit. If `check_num_qubits` the number of qubits are compared diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index 0192ea1b8d29..b75cc3f5536a 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -31,7 +31,7 @@ def __init__( left_circuit: QuantumCircuit | None = None, right_circuit: QuantumCircuit | None = None, ) -> None: - r""" + """ Initializes the class to evaluate the fidelities defined as the state overlap :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, where :math:`x` and :math:`y` are optional parametrizations of the @@ -55,19 +55,35 @@ def _run( right_circuit: QuantumCircuit = None, **run_options, ) -> FidelityJob: + """Run the job of the state overlap (fidelity) calculation between 2 + parametrized circuits (left and right) for a specific set of parameter + values (left and right). + Args: + left_values: Numerical parameters to be bound to the left circuit. + right_values: Numerical parameters to be bound to the right circuit. + left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + run_options: Backend runtime options used for circuit execution. + Returns: + The job object for the fidelity calculation. + """ if left_circuit is not None: self._set_circuits(left_circuit = left_circuit) if right_circuit is not None: self._set_circuits(right_circuit = right_circuit) - values_list = [] if self._left_circuit is None or self._right_circuit is None: raise ValueError( - "The left and right circuits must be defined to" + "The left and right circuits must be defined to " "calculate the state overlap. " ) + circuit = self._left_circuit.compose(self._right_circuit.inverse()) + circuit.measure_all() + self._circuit = circuit + + values_list = [] for values, side in zip([left_values, right_values], ["left", "right"]): values = self._check_values(values, side) if values is not None: @@ -83,7 +99,7 @@ def _run( values = np.hstack(values_list) job = self.sampler.run(circuits=[self._circuit] * len(values), parameter_values=values) else: - job = self.sampler.run(circuits=self._circuit) + job = self.sampler.run(circuits=[self._circuit]) result = job.result() @@ -92,4 +108,3 @@ def _run( overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] return FidelityJob(result= np.array(overlaps), status=job.status()) - diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index d98a26b9f8a7..902868b2fcec 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -14,7 +14,6 @@ import unittest -# from functools import partial import numpy as np from ddt import ddt @@ -48,80 +47,81 @@ def setUp(self): zero = QuantumCircuit(2) self._circuit = [rx_rotations, ry_rotations, plus, zero] - self._sampler_factory = Sampler() - self._params_left = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) - self._params_right = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) + self._sampler = Sampler() + self._left_params = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) + self._right_params = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) def test_fidelity_1param_pair(self): """test for fidelity with one pair of parameters""" - fidelity = Fidelity(self._sampler_factory, self._circuit[0], self._circuit[1]) - job = fidelity.run(self._params_left[0], self._params_right[0]) + fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) + job = fidelity.run(self._left_params[0], self._right_params[0]) result = job.result np.testing.assert_allclose(result, np.array([1.0])) def test_fidelity_4param_pairs(self): """test for fidelity with four pairs of parameters""" - fidelity = Fidelity(self._sampler_factory, self._circuit[0], self._circuit[1]) - job = fidelity.run(self._params_left, self._params_right) + fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) + job = fidelity.run(self._left_params, self._right_params) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_fidelity_symmetry(self): """test for fidelity with the same circuit""" - fidelity = Fidelity(self._sampler_factory, self._circuit[0], self._circuit[0]) - job_1 = fidelity.run(self._params_left, self._params_right) - job_2 = fidelity.run(self._params_right, self._params_left) + fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[0]) + job_1 = fidelity.run(self._left_params, self._right_params) + job_2 = fidelity.run(self._right_params, self._left_params) results_1 = job_1.result results_2 = job_2.result np.testing.assert_allclose(results_1, results_2, atol=1e-16) def test_fidelity_no_params(self): """test for fidelity without parameters""" - fidelity = Fidelity(self._sampler_factory, self._circuit[2], self._circuit[3]) + fidelity = Fidelity(self._sampler, self._circuit[2], self._circuit[3]) job = fidelity.run() results = job.result np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) def test_fidelity_left_param(self): """test for fidelity with only left parameters""" - fidelity = Fidelity(self._sampler_factory, self._circuit[1], self._circuit[3]) - job = fidelity.run(values_left=self._params_left) + fidelity = Fidelity(self._sampler, self._circuit[1], self._circuit[3]) + job = fidelity.run(left_values=self._left_params) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_fidelity_right_param(self): """test for fidelity with only right parameters""" - fidelity = Fidelity(self._sampler_factory, self._circuit[3], self._circuit[1]) - job = fidelity.run(values_right=self._params_left) + fidelity = Fidelity(self._sampler, self._circuit[3], self._circuit[1]) + job = fidelity.run(right_values=self._left_params) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_fidelity_not_set_circuits(self): """test for fidelity with no circuits during init.""" - fidelity = Fidelity(self._sampler_factory) + fidelity = Fidelity(self._sampler) with self.assertRaises(ValueError): - _ = fidelity.run(self._params_left, self._params_right) + _ = fidelity.run(self._left_params, self._right_params) - def test_fidelity_set_circuits(self): + def test_fidelity_set_circuits_during_run(self): """test for fidelity with no circuits during init.""" - fidelity = Fidelity(self._sampler_factory) - fidelity.set_circuits(self._circuit[0], self._circuit[1]) - results = fidelity.run(self._params_left, self._params_right) - fidelity.sampler.close() + fidelity = Fidelity(self._sampler) + job = fidelity.run(self._left_params, + self._right_params, + left_circuit = self._circuit[0], + right_circuit = self._circuit[1]) + results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) - def test_fidelity_set_single_circuit(self): + def test_fidelity_set_single_circuit_during_run(self): """test for fidelity with no circuits during init.""" - fidelity = Fidelity(self._sampler_factory) - fidelity.set_circuits(right_circuit=self._circuit[1]) + fidelity = Fidelity(self._sampler) with self.assertRaises(ValueError): - _ = fidelity.run(self._params_left, self._params_right) - fidelity.set_circuits(left_circuit=self._circuit[0]) - results = fidelity.run(self._params_left, self._params_right) - fidelity.sampler.close() + _ = fidelity.run(self._left_params, self._right_params, right_circuit=self._circuit[1]) + job = fidelity.run(self._left_params, self._right_params, + left_circuit=self._circuit[0], right_circuit=self._circuit[1]) + results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) From 6b2186ff336498baf3373d4eda0e1ac6e9dfaeb6 Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 16 Aug 2022 16:57:56 +0200 Subject: [PATCH 26/92] Add evaluate --- qiskit/algorithms/fidelities/fidelity.py | 23 +++++++++++++++++++ .../algorithms/fidelities/test_fidelity.py | 23 +++++++++++-------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index b75cc3f5536a..a7574d520ba5 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -108,3 +108,26 @@ def _run( overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] return FidelityJob(result= np.array(overlaps), status=job.status()) + + def evaluate(self, + left_values: np.ndarray | list[np.ndarray] | None = None, + right_values: np.ndarray | list[np.ndarray] | None = None, + left_circuit: QuantumCircuit = None, + right_circuit: QuantumCircuit = None, + **run_options, + ) -> FidelityJob: + """Run the result of the state overlap (fidelity) calculation between 2 + parametrized circuits (left and right) for a specific set of parameter + values (left and right). + Args: + left_values: Numerical parameters to be bound to the left circuit. + right_values: Numerical parameters to be bound to the right circuit. + left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + run_options: Backend runtime options used for circuit execution. + + Returns: + The result of the fidelity calculation. + """ + fidelity_job = self.run(left_values, right_values, left_circuit, right_circuit, **run_options) + return fidelity_job.result diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index 902868b2fcec..11bb739f7314 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -51,7 +51,7 @@ def setUp(self): self._left_params = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) self._right_params = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) - def test_fidelity_1param_pair(self): + def test_1param_pair(self): """test for fidelity with one pair of parameters""" fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) @@ -59,7 +59,7 @@ def test_fidelity_1param_pair(self): result = job.result np.testing.assert_allclose(result, np.array([1.0])) - def test_fidelity_4param_pairs(self): + def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) @@ -67,7 +67,7 @@ def test_fidelity_4param_pairs(self): results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) - def test_fidelity_symmetry(self): + def test_symmetry(self): """test for fidelity with the same circuit""" fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[0]) @@ -77,34 +77,34 @@ def test_fidelity_symmetry(self): results_2 = job_2.result np.testing.assert_allclose(results_1, results_2, atol=1e-16) - def test_fidelity_no_params(self): + def test_no_params(self): """test for fidelity without parameters""" fidelity = Fidelity(self._sampler, self._circuit[2], self._circuit[3]) job = fidelity.run() results = job.result np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) - def test_fidelity_left_param(self): + def test_left_param(self): """test for fidelity with only left parameters""" fidelity = Fidelity(self._sampler, self._circuit[1], self._circuit[3]) job = fidelity.run(left_values=self._left_params) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - def test_fidelity_right_param(self): + def test_right_param(self): """test for fidelity with only right parameters""" fidelity = Fidelity(self._sampler, self._circuit[3], self._circuit[1]) job = fidelity.run(right_values=self._left_params) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - def test_fidelity_not_set_circuits(self): + def test_not_set_circuits(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler) with self.assertRaises(ValueError): _ = fidelity.run(self._left_params, self._right_params) - def test_fidelity_set_circuits_during_run(self): + def test_set_circuits_during_run(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler) job = fidelity.run(self._left_params, @@ -114,7 +114,7 @@ def test_fidelity_set_circuits_during_run(self): results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) - def test_fidelity_set_single_circuit_during_run(self): + def test_set_single_circuit_during_run(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler) with self.assertRaises(ValueError): @@ -124,6 +124,11 @@ def test_fidelity_set_single_circuit_during_run(self): results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + def test_evaluate(self): + """test for evaluate method.""" + fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) + result = fidelity.evaluate(self._left_params[0], self._right_params[0]) + np.testing.assert_allclose(result, np.array([1.0])) if __name__ == "__main__": unittest.main() From 1ebdbb1daee675a09c4c1fe42f311376ab36f990 Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 16 Aug 2022 18:08:00 +0200 Subject: [PATCH 27/92] Adapt signature run --- qiskit/algorithms/fidelities/base_fidelity.py | 30 ++++++++----- qiskit/algorithms/fidelities/fidelity.py | 44 ++++++------------- .../algorithms/fidelities/test_fidelity.py | 44 ++++++++++--------- 3 files changed, 56 insertions(+), 62 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index 6ba03f365f7c..10e49939311f 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -58,10 +58,10 @@ def __init__( def run( self, - left_values: np.ndarray | list[np.ndarray] | None = None, - right_values: np.ndarray | list[np.ndarray] | None = None, - left_circuit: QuantumCircuit = None, - right_circuit: QuantumCircuit = None, + left_circuit: Sequence[QuantumCircuit] | None = None, + right_circuit: Sequence[QuantumCircuit] | None = None, + left_parameter_values: Sequence[Sequence[float]] | None = None, + right_parameter_values: Sequence[Sequence[float]] | None = None, **run_options, ) -> FidelityJob: """Compute the overlap of two quantum states bound by the @@ -76,24 +76,32 @@ def run( Returns: The overlap of two quantum states defined by two parametrized circuits. """ - return self._run(left_values, right_values, left_circuit, right_circuit, **run_options) + return self._run(left_circuit, + right_circuit, + left_parameter_values, + right_parameter_values, + **run_options) @abstractmethod def _run( self, - left_values: np.ndarray | list[np.ndarray] | None = None, - right_values: np.ndarray | list[np.ndarray] | None = None, - left_circuit: QuantumCircuit = None, - right_circuit: QuantumCircuit = None, + left_circuit: Sequence[QuantumCircuit] | None = None, + right_circuit: Sequence[QuantumCircuit] | None = None, + left_parameter_values: Sequence[Sequence[float]] | None = None, + right_parameter_values: Sequence[Sequence[float]] | None = None, **run_options, ) -> FidelityJob: """Compute the overlap of two quantum states bound by the parametrizations left_values and right_values. Args: - left_values: Numerical parameters to be bound to the left circuit. - right_values: Numerical parameters to be bound to the right circuit. left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + If a list of circuits is sent, only the first circuit will be + taken into account. right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + If a list of circuits is sent, only the first circuit will be + taken into account. + left_parameter_values: Numerical parameters to be bound to the left circuit. + right_parameter_values: Numerical parameters to be bound to the right circuit. run_options: Backend runtime options used for circuit execution. Returns: diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index a7574d520ba5..cd7aab42b4bc 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -49,29 +49,33 @@ def __init__( def _run( self, - left_values: np.ndarray | list[np.ndarray] | None = None, - right_values: np.ndarray | list[np.ndarray] | None = None, - left_circuit: QuantumCircuit = None, - right_circuit: QuantumCircuit = None, + left_circuit: Sequence[QuantumCircuit] | None = None, + right_circuit: Sequence[QuantumCircuit] | None = None, + left_parameter_values: Sequence[Sequence[float]] | None = None, + right_parameter_values: Sequence[Sequence[float]] | None = None, **run_options, ) -> FidelityJob: """Run the job of the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter values (left and right). Args: - left_values: Numerical parameters to be bound to the left circuit. - right_values: Numerical parameters to be bound to the right circuit. left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + If a list of circuits is sent, only the first circuit will be + taken into account. right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + If a list of circuits is sent, only the first circuit will be + taken into account. + left_parameter_values: Numerical parameters to be bound to the left circuit. + right_parameter_values: Numerical parameters to be bound to the right circuit. run_options: Backend runtime options used for circuit execution. Returns: The job object for the fidelity calculation. """ if left_circuit is not None: - self._set_circuits(left_circuit = left_circuit) + self._set_circuits(left_circuit = left_circuit[0]) if right_circuit is not None: - self._set_circuits(right_circuit = right_circuit) + self._set_circuits(right_circuit = right_circuit[0]) if self._left_circuit is None or self._right_circuit is None: raise ValueError( @@ -84,7 +88,7 @@ def _run( self._circuit = circuit values_list = [] - for values, side in zip([left_values, right_values], ["left", "right"]): + for values, side in zip([left_parameter_values, right_parameter_values], ["left", "right"]): values = self._check_values(values, side) if values is not None: values_list.append(values) @@ -109,25 +113,3 @@ def _run( return FidelityJob(result= np.array(overlaps), status=job.status()) - def evaluate(self, - left_values: np.ndarray | list[np.ndarray] | None = None, - right_values: np.ndarray | list[np.ndarray] | None = None, - left_circuit: QuantumCircuit = None, - right_circuit: QuantumCircuit = None, - **run_options, - ) -> FidelityJob: - """Run the result of the state overlap (fidelity) calculation between 2 - parametrized circuits (left and right) for a specific set of parameter - values (left and right). - Args: - left_values: Numerical parameters to be bound to the left circuit. - right_values: Numerical parameters to be bound to the right circuit. - left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. - right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. - run_options: Backend runtime options used for circuit execution. - - Returns: - The result of the fidelity calculation. - """ - fidelity_job = self.run(left_values, right_values, left_circuit, right_circuit, **run_options) - return fidelity_job.result diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index 11bb739f7314..10df6b292169 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -55,7 +55,8 @@ def test_1param_pair(self): """test for fidelity with one pair of parameters""" fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) - job = fidelity.run(self._left_params[0], self._right_params[0]) + job = fidelity.run(left_parameter_values = self._left_params[0], + right_parameter_values = self._right_params[0]) result = job.result np.testing.assert_allclose(result, np.array([1.0])) @@ -63,7 +64,8 @@ def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) - job = fidelity.run(self._left_params, self._right_params) + job = fidelity.run(left_parameter_values = self._left_params, + right_parameter_values = self._right_params) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) @@ -71,8 +73,10 @@ def test_symmetry(self): """test for fidelity with the same circuit""" fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[0]) - job_1 = fidelity.run(self._left_params, self._right_params) - job_2 = fidelity.run(self._right_params, self._left_params) + job_1 = fidelity.run(left_parameter_values = self._left_params, + right_parameter_values = self._right_params) + job_2 = fidelity.run(left_parameter_values = self._right_params, + right_parameter_values = self._left_params) results_1 = job_1.result results_2 = job_2.result np.testing.assert_allclose(results_1, results_2, atol=1e-16) @@ -87,14 +91,14 @@ def test_no_params(self): def test_left_param(self): """test for fidelity with only left parameters""" fidelity = Fidelity(self._sampler, self._circuit[1], self._circuit[3]) - job = fidelity.run(left_values=self._left_params) + job = fidelity.run(left_parameter_values = self._left_params) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_right_param(self): """test for fidelity with only right parameters""" fidelity = Fidelity(self._sampler, self._circuit[3], self._circuit[1]) - job = fidelity.run(right_values=self._left_params) + job = fidelity.run(right_parameter_values = self._left_params) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) @@ -102,15 +106,16 @@ def test_not_set_circuits(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler) with self.assertRaises(ValueError): - _ = fidelity.run(self._left_params, self._right_params) + _ = fidelity.run(left_parameter_values = self._left_params, + right_parameter_values = self._right_params) def test_set_circuits_during_run(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler) - job = fidelity.run(self._left_params, - self._right_params, - left_circuit = self._circuit[0], - right_circuit = self._circuit[1]) + job = fidelity.run(left_circuit = [self._circuit[0]], + right_circuit = [self._circuit[1]], + left_parameter_values = self._left_params, + right_parameter_values = self._right_params) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) @@ -118,17 +123,16 @@ def test_set_single_circuit_during_run(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler) with self.assertRaises(ValueError): - _ = fidelity.run(self._left_params, self._right_params, right_circuit=self._circuit[1]) - job = fidelity.run(self._left_params, self._right_params, - left_circuit=self._circuit[0], right_circuit=self._circuit[1]) + _ = fidelity.run(right_circuit=[self._circuit[1]], + left_parameter_values =self._left_params, + right_parameter_values =self._right_params + ) + job = fidelity.run(left_circuit=[self._circuit[0]], right_circuit=[self._circuit[1]], + left_parameter_values =self._left_params, + right_parameter_values =self._right_params + ) results = job.result np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) - def test_evaluate(self): - """test for evaluate method.""" - fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) - result = fidelity.evaluate(self._left_params[0], self._right_params[0]) - np.testing.assert_allclose(result, np.array([1.0])) - if __name__ == "__main__": unittest.main() From 2552ececa9b750f86f4d96500ef9cbbb130ce1d0 Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 17 Aug 2022 13:03:03 +0200 Subject: [PATCH 28/92] Make job async --- qiskit/algorithms/fidelities/base_fidelity.py | 16 ++-- qiskit/algorithms/fidelities/fidelity.py | 53 ++++++++++--- qiskit/algorithms/fidelities/fidelity_job.py | 32 -------- .../algorithms/fidelities/test_fidelity.py | 78 +++++++++++-------- 4 files changed, 94 insertions(+), 85 deletions(-) delete mode 100644 qiskit/algorithms/fidelities/fidelity_job.py diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index 10e49939311f..a3bb2d686f64 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -60,8 +60,8 @@ def run( self, left_circuit: Sequence[QuantumCircuit] | None = None, right_circuit: Sequence[QuantumCircuit] | None = None, - left_parameter_values: Sequence[Sequence[float]] | None = None, - right_parameter_values: Sequence[Sequence[float]] | None = None, + left_values: Sequence[Sequence[float]] | None = None, + right_values: Sequence[Sequence[float]] | None = None, **run_options, ) -> FidelityJob: """Compute the overlap of two quantum states bound by the @@ -78,8 +78,8 @@ def run( """ return self._run(left_circuit, right_circuit, - left_parameter_values, - right_parameter_values, + left_values, + right_values, **run_options) @abstractmethod @@ -87,8 +87,8 @@ def _run( self, left_circuit: Sequence[QuantumCircuit] | None = None, right_circuit: Sequence[QuantumCircuit] | None = None, - left_parameter_values: Sequence[Sequence[float]] | None = None, - right_parameter_values: Sequence[Sequence[float]] | None = None, + left_values: Sequence[Sequence[float]] | None = None, + right_values: Sequence[Sequence[float]] | None = None, **run_options, ) -> FidelityJob: """Compute the overlap of two quantum states bound by the @@ -100,8 +100,8 @@ def _run( right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. If a list of circuits is sent, only the first circuit will be taken into account. - left_parameter_values: Numerical parameters to be bound to the left circuit. - right_parameter_values: Numerical parameters to be bound to the right circuit. + left_values: Numerical parameters to be bound to the left circuit. + right_values: Numerical parameters to be bound to the right circuit. run_options: Backend runtime options used for circuit execution. Returns: diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index cd7aab42b4bc..3dec68734078 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -17,8 +17,9 @@ from qiskit import QuantumCircuit from qiskit.primitives import Sampler +from qiskit.primitives.primitive_job import PrimitiveJob from .base_fidelity import BaseFidelity -from .fidelity_job import FidelityJob +# from .fidelity_job import FidelityJob class Fidelity(BaseFidelity): """ @@ -47,14 +48,14 @@ def __init__( self.sampler = sampler super().__init__(left_circuit, right_circuit) - def _run( + def _call( self, left_circuit: Sequence[QuantumCircuit] | None = None, right_circuit: Sequence[QuantumCircuit] | None = None, - left_parameter_values: Sequence[Sequence[float]] | None = None, - right_parameter_values: Sequence[Sequence[float]] | None = None, + left_values: Sequence[Sequence[float]] | None = None, + right_values: Sequence[Sequence[float]] | None = None, **run_options, - ) -> FidelityJob: + ) -> PrimitiveJob: """Run the job of the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter values (left and right). @@ -65,17 +66,17 @@ def _run( right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. If a list of circuits is sent, only the first circuit will be taken into account. - left_parameter_values: Numerical parameters to be bound to the left circuit. - right_parameter_values: Numerical parameters to be bound to the right circuit. + left_values: Numerical parameters to be bound to the left circuit. + right_values: Numerical parameters to be bound to the right circuit. run_options: Backend runtime options used for circuit execution. Returns: The job object for the fidelity calculation. """ if left_circuit is not None: - self._set_circuits(left_circuit = left_circuit[0]) + self._set_circuits(left_circuit=left_circuit[0]) if right_circuit is not None: - self._set_circuits(right_circuit = right_circuit[0]) + self._set_circuits(right_circuit=right_circuit[0]) if self._left_circuit is None or self._right_circuit is None: raise ValueError( @@ -88,7 +89,7 @@ def _run( self._circuit = circuit values_list = [] - for values, side in zip([left_parameter_values, right_parameter_values], ["left", "right"]): + for values, side in zip([left_values, right_values], ["left", "right"]): values = self._check_values(values, side) if values is not None: values_list.append(values) @@ -110,6 +111,36 @@ def _run( # if error mitigation is added in the future, we will have to handle # negative values in some way (e.g. clipping to zero) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] + return np.array(overlaps) - return FidelityJob(result= np.array(overlaps), status=job.status()) + def _run( + self, + left_circuit: Sequence[QuantumCircuit] | None = None, + right_circuit: Sequence[QuantumCircuit] | None = None, + left_values: Sequence[Sequence[float]] | None = None, + right_values: Sequence[Sequence[float]] | None = None, + **run_options, + ) -> PrimitiveJob: + """Run the job of the state overlap (fidelity) calculation between 2 + parametrized circuits (left and right) for a specific set of parameter + values (left and right). + Args: + left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + If a list of circuits is sent, only the first circuit will be + taken into account. + right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + If a list of circuits is sent, only the first circuit will be + taken into account. + left_values: Numerical parameters to be bound to the left circuit. + right_values: Numerical parameters to be bound to the right circuit. + run_options: Backend runtime options used for circuit execution. + + Returns: + The job object for the fidelity calculation. + """ + job = PrimitiveJob( + self._call, left_circuit, right_circuit, left_values, right_values, **run_options + ) + job.submit() + return job diff --git a/qiskit/algorithms/fidelities/fidelity_job.py b/qiskit/algorithms/fidelities/fidelity_job.py deleted file mode 100644 index 8e2ee568fb71..000000000000 --- a/qiskit/algorithms/fidelities/fidelity_job.py +++ /dev/null @@ -1,32 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Sampler result class -""" - -from __future__ import annotations - -from dataclasses import dataclass - -from qiskit.providers import JobStatus - - -@dataclass(frozen=True) -class FidelityJob: - """Result of Fidelity class. - Args: - results: np.array with calculated state overlaps - status: JobStatus for the Fidelity job - """ - - result: np.array - status: JobStatus diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index 10df6b292169..d0c31acaa4dc 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -55,83 +55,93 @@ def test_1param_pair(self): """test for fidelity with one pair of parameters""" fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) - job = fidelity.run(left_parameter_values = self._left_params[0], - right_parameter_values = self._right_params[0]) - result = job.result + job = fidelity.run(left_values = self._left_params[0], + right_values = self._right_params[0]) + result = job.result() np.testing.assert_allclose(result, np.array([1.0])) def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) - job = fidelity.run(left_parameter_values = self._left_params, - right_parameter_values = self._right_params) - results = job.result + job = fidelity.run(left_values = self._left_params, + right_values = self._right_params) + results = job.result() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_symmetry(self): """test for fidelity with the same circuit""" fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[0]) - job_1 = fidelity.run(left_parameter_values = self._left_params, - right_parameter_values = self._right_params) - job_2 = fidelity.run(left_parameter_values = self._right_params, - right_parameter_values = self._left_params) - results_1 = job_1.result - results_2 = job_2.result + job_1 = fidelity.run(left_values = self._left_params, + right_values = self._right_params) + job_2 = fidelity.run(left_values = self._right_params, + right_values = self._left_params) + results_1 = job_1.result() + results_2 = job_2.result() np.testing.assert_allclose(results_1, results_2, atol=1e-16) def test_no_params(self): """test for fidelity without parameters""" fidelity = Fidelity(self._sampler, self._circuit[2], self._circuit[3]) job = fidelity.run() - results = job.result + results = job.result() np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) def test_left_param(self): """test for fidelity with only left parameters""" fidelity = Fidelity(self._sampler, self._circuit[1], self._circuit[3]) - job = fidelity.run(left_parameter_values = self._left_params) - results = job.result + job = fidelity.run(left_values = self._left_params) + results = job.result() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_right_param(self): """test for fidelity with only right parameters""" fidelity = Fidelity(self._sampler, self._circuit[3], self._circuit[1]) - job = fidelity.run(right_parameter_values = self._left_params) - results = job.result + job = fidelity.run(right_values = self._left_params) + results = job.result() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - def test_not_set_circuits(self): - """test for fidelity with no circuits during init.""" - fidelity = Fidelity(self._sampler) - with self.assertRaises(ValueError): - _ = fidelity.run(left_parameter_values = self._left_params, - right_parameter_values = self._right_params) + # def test_not_set_circuits(self): + # """test for fidelity with no circuits during init.""" + # fidelity = Fidelity(self._sampler) + # with self.assertRaises(ValueError): + # _ = fidelity.run(left_values = self._left_params, + # right_values = self._right_params) def test_set_circuits_during_run(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler) job = fidelity.run(left_circuit = [self._circuit[0]], right_circuit = [self._circuit[1]], - left_parameter_values = self._left_params, - right_parameter_values = self._right_params) - results = job.result + left_values = self._left_params, + right_values = self._right_params) + results = job.result() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_set_single_circuit_during_run(self): """test for fidelity with no circuits during init.""" fidelity = Fidelity(self._sampler) - with self.assertRaises(ValueError): - _ = fidelity.run(right_circuit=[self._circuit[1]], - left_parameter_values =self._left_params, - right_parameter_values =self._right_params - ) + # with self.assertRaises(ValueError): + # _ = fidelity.run(right_circuit=[self._circuit[1]], + # left_values =self._left_params, + # right_values =self._right_params + # ) job = fidelity.run(left_circuit=[self._circuit[0]], right_circuit=[self._circuit[1]], - left_parameter_values =self._left_params, - right_parameter_values =self._right_params + left_values =self._left_params, + right_values =self._right_params + ) + results = job.result() + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + + def test_circuit_list_during_run(self): + fidelity = Fidelity(self._sampler) + job = fidelity.run(left_circuit=[self._circuit[0], self._circuit[1], self._circuit[2]], + right_circuit=[self._circuit[1]], + left_values=self._left_params, + right_values=self._right_params ) - results = job.result + results = job.result() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) if __name__ == "__main__": From edb3bf94a82cc7ef8cfdbbbe59705ea8d323bab4 Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 17 Aug 2022 13:14:07 +0200 Subject: [PATCH 29/92] Fix types --- qiskit/algorithms/fidelities/fidelity.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index 3dec68734078..cf36df971f4c 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -53,10 +53,9 @@ def _call( left_circuit: Sequence[QuantumCircuit] | None = None, right_circuit: Sequence[QuantumCircuit] | None = None, left_values: Sequence[Sequence[float]] | None = None, - right_values: Sequence[Sequence[float]] | None = None, - **run_options, - ) -> PrimitiveJob: - """Run the job of the state overlap (fidelity) calculation between 2 + right_values: Sequence[Sequence[float]] | None = None + ) -> np.ndarray: + """Run the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter values (left and right). Args: @@ -71,7 +70,7 @@ def _call( run_options: Backend runtime options used for circuit execution. Returns: - The job object for the fidelity calculation. + The result for the fidelity calculation. """ if left_circuit is not None: self._set_circuits(left_circuit=left_circuit[0]) @@ -121,7 +120,7 @@ def _run( right_values: Sequence[Sequence[float]] | None = None, **run_options, ) -> PrimitiveJob: - """Run the job of the state overlap (fidelity) calculation between 2 + """Run the asynchronous job of the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter values (left and right). Args: From 53d67d71cae3e183882401f9e85ff9a3a76d4f96 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 18 Aug 2022 18:10:59 +0200 Subject: [PATCH 30/92] Remove async, cache circuits --- qiskit/algorithms/fidelities/base_fidelity.py | 171 ++++++------------ qiskit/algorithms/fidelities/fidelity.py | 85 ++++----- .../algorithms/fidelities/test_fidelity.py | 116 +++++------- 3 files changed, 139 insertions(+), 233 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index a3bb2d686f64..9d027ef4672b 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -28,8 +28,8 @@ class BaseFidelity(ABC): def __init__( self, - left_circuit: QuantumCircuit | None = None, - right_circuit: QuantumCircuit | None = None, + left_circuits: Sequence[QuantumCircuit] | None = None, + right_circuits: Sequence[QuantumCircuit] | None = None, ) -> None: r"""Initializes the class to evaluate the fidelities defined as @@ -47,70 +47,19 @@ def __init__( ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ - self._circuit = None - if left_circuit is None or right_circuit is None: - self._left_circuit = None - self._right_circuit = None - self._left_parameters = None - self._right_parameters = None - else: - self._set_circuits(left_circuit, right_circuit) + self._circuits = [] + self._circuit_ids = {} - def run( - self, - left_circuit: Sequence[QuantumCircuit] | None = None, - right_circuit: Sequence[QuantumCircuit] | None = None, - left_values: Sequence[Sequence[float]] | None = None, - right_values: Sequence[Sequence[float]] | None = None, - **run_options, - ) -> FidelityJob: - """Compute the overlap of two quantum states bound by the - parametrizations left_values and right_values. + self._left_parameters = [] + self._right_parameters = [] - Args: - left_values: Numerical parameters to be bound to the left circuit. - right_values: Numerical parameters to be bound to the right circuit. - left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. - right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. - - Returns: - The overlap of two quantum states defined by two parametrized circuits. - """ - return self._run(left_circuit, - right_circuit, - left_values, - right_values, - **run_options) - - @abstractmethod - def _run( - self, - left_circuit: Sequence[QuantumCircuit] | None = None, - right_circuit: Sequence[QuantumCircuit] | None = None, - left_values: Sequence[Sequence[float]] | None = None, - right_values: Sequence[Sequence[float]] | None = None, - **run_options, - ) -> FidelityJob: - """Compute the overlap of two quantum states bound by the - parametrizations left_values and right_values. - Args: - left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. - If a list of circuits is sent, only the first circuit will be - taken into account. - right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. - If a list of circuits is sent, only the first circuit will be - taken into account. - left_values: Numerical parameters to be bound to the left circuit. - right_values: Numerical parameters to be bound to the right circuit. - run_options: Backend runtime options used for circuit execution. - - Returns: - The job object for the fidelity calculation. - """ - raise NotImplementedError() + if left_circuits is not None and right_circuits is not None: + self._set_circuits(left_circuits, right_circuits) def _check_values( - self, values: np.ndarray | list[np.ndarray] | None, side: str + self, values: np.ndarray | list[np.ndarray] | None, + side: str, + circuits: QuantumCircuit ) -> np.ndarray | None: """ Check whether the passed values match the shape of the parameters of the circuit on the side @@ -119,26 +68,33 @@ def _check_values( Returns a 2D-Array if values match, `None` if no parameters are passed and raises an error if the shapes don't match. """ - if side == "left": - circuit = self._left_circuit - else: - circuit = self._right_circuit if values is None: - if circuit.num_parameters != 0: - raise ValueError( - f"`values_{side}` cannot be `None` because the {side} circuit has " - f"{circuit.num_parameters} free parameters. {circuit}" - ) + for circuit in circuits: + if circuit.num_parameters != 0: + raise ValueError( + f"`values_{side}` cannot be `None` because the {side} circuit has " + f"{circuit.num_parameters} free parameters." + ) return None else: return np.atleast_2d(values) + def _check_qubits_mismatch( + self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit + ) -> None: + if left_circuit is not None and right_circuit is not None: + if left_circuit.num_qubits != right_circuit.num_qubits: + raise ValueError( + f"The number of qubits for the left circuit ({left_circuit.num_qubits}) \ + and right circuit ({right_circuit.num_qubits}) do not coincide." + ) + def _set_circuits( self, - left_circuit: QuantumCircuit | None = None, - right_circuit: QuantumCircuit | None = None, - ): + left_circuits: Sequence[QuantumCircuit] | None = None, + right_circuits: Sequence[QuantumCircuit] | None = None, + ) -> np.ndarray: """ Fix the circuits for the fidelity to be computed of. Args: @@ -148,44 +104,35 @@ def _set_circuits( Raises: ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ - if left_circuit is not None and right_circuit is not None: - self._check_qubits_mismatch(left_circuit, right_circuit) - self._set_left_circuit(left_circuit) - self._set_right_circuit(right_circuit) - - elif left_circuit is not None: - self._check_qubits_mismatch(left_circuit, self._right_circuit) - self._set_left_circuit(left_circuit) - elif right_circuit is not None: - self._check_qubits_mismatch(self._left_circuit, right_circuit) - self._set_right_circuit(right_circuit) - else: - raise ValueError( - "At least one of the arguments `left_circuit` or `right_circuit` must not be `None`." - ) - def _set_left_circuit(self, circuit: QuantumCircuit) -> None: - """ - Fix the left circuit. If `check_num_qubits` the number of qubits are compared - to the right circuit. - """ - self._left_parameters = ParameterVector("x", circuit.num_parameters) - self._left_circuit = circuit.assign_parameters(self._left_parameters) + if not len(left_circuits) == len(right_circuits): + raise ValueError - def _set_right_circuit(self, circuit: QuantumCircuit) -> None: - """ - Fix the right circuit. If `check_num_qubits` the number of qubits are compared - to the left circuit. - """ - self._right_parameters = ParameterVector("y", circuit.num_parameters) - self._right_circuit = circuit.assign_parameters(self._right_parameters) + circuit_indices = [] + for i, (left_circuit, right_circuit) in enumerate(zip(left_circuits, right_circuits)): - def _check_qubits_mismatch( - self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit - ) -> None: - if left_circuit is not None and right_circuit is not None: - if left_circuit.num_qubits != right_circuit.num_qubits: - raise ValueError( - f"The number of qubits for the left circuit ({left_circuit.num_qubits}) \ - and right circuit ({right_circuit.num_qubits}) do not coincide." - ) + index = self._circuit_ids.get((id(left_circuit), id(right_circuit))) + + if index is not None: + # the composed circuit already exists + circuit_indices.append(index) + else: + # create new circuit + self._check_qubits_mismatch(left_circuit, right_circuit) + + left_parameters = ParameterVector("x", left_circuit.num_parameters) + self._left_parameters.append(left_parameters) + parametrized_left_circuit = left_circuit.assign_parameters(left_parameters) + + right_parameters = ParameterVector("y", right_circuit.num_parameters) + self._right_parameters.append(right_parameters) + parametrized_right_circuit = right_circuit.assign_parameters(right_parameters) + + circuit = parametrized_left_circuit.compose(parametrized_right_circuit.inverse()) + circuit.measure_all() + + self._circuit_ids[id(left_circuit), id(right_circuit)] = len(self._circuits) + self._circuits.append(circuit) + circuit_indices.append(len(self._circuits) - 1) + + return circuit_indices diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index cf36df971f4c..bb65cf583f77 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -29,8 +29,8 @@ class Fidelity(BaseFidelity): def __init__( self, sampler: Sampler, - left_circuit: QuantumCircuit | None = None, - right_circuit: QuantumCircuit | None = None, + left_circuits: Sequence[QuantumCircuit] | None = None, + right_circuits: Sequence[QuantumCircuit]| None = None, ) -> None: """ Initializes the class to evaluate the fidelities defined as the state overlap @@ -46,23 +46,21 @@ def __init__( ValueError: left_circuit and right_circuit don't have the same number of qubits. """ self.sampler = sampler - super().__init__(left_circuit, right_circuit) + super().__init__(left_circuits, right_circuits) def _call( self, - left_circuit: Sequence[QuantumCircuit] | None = None, - right_circuit: Sequence[QuantumCircuit] | None = None, - left_values: Sequence[Sequence[float]] | None = None, - right_values: Sequence[Sequence[float]] | None = None + circuits_list, + values_list ) -> np.ndarray: """Run the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter values (left and right). Args: - left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + left_circuits: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. If a list of circuits is sent, only the first circuit will be taken into account. - right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + right_circuits: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. If a list of circuits is sent, only the first circuit will be taken into account. left_values: Numerical parameters to be bound to the left circuit. @@ -72,27 +70,13 @@ def _call( Returns: The result for the fidelity calculation. """ - if left_circuit is not None: - self._set_circuits(left_circuit=left_circuit[0]) - if right_circuit is not None: - self._set_circuits(right_circuit=right_circuit[0]) - if self._left_circuit is None or self._right_circuit is None: + if circuits_list is None: raise ValueError( "The left and right circuits must be defined to " "calculate the state overlap. " ) - circuit = self._left_circuit.compose(self._right_circuit.inverse()) - circuit.measure_all() - self._circuit = circuit - - values_list = [] - for values, side in zip([left_values, right_values], ["left", "right"]): - values = self._check_values(values, side) - if values is not None: - values_list.append(values) - if len(values_list) > 0: if len(values_list) == 2 and values_list[0].shape[0] != values_list[1].shape[0]: raise ValueError( @@ -101,9 +85,9 @@ def _call( f"(currently {values_list[1].shape[0]})" ) values = np.hstack(values_list) - job = self.sampler.run(circuits=[self._circuit] * len(values), parameter_values=values) + job = self.sampler.run(circuits=circuits_list, parameter_values=values) else: - job = self.sampler.run(circuits=[self._circuit]) + job = self.sampler.run(circuits=circuits_list) result = job.result() @@ -112,34 +96,27 @@ def _call( overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] return np.array(overlaps) - def _run( - self, - left_circuit: Sequence[QuantumCircuit] | None = None, - right_circuit: Sequence[QuantumCircuit] | None = None, + def evaluate(self, + left_circuits: Sequence[QuantumCircuit], + right_circuits: Sequence[QuantumCircuit], left_values: Sequence[Sequence[float]] | None = None, - right_values: Sequence[Sequence[float]] | None = None, - **run_options, - ) -> PrimitiveJob: - """Run the asynchronous job of the state overlap (fidelity) calculation between 2 - parametrized circuits (left and right) for a specific set of parameter - values (left and right). - Args: - left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. - If a list of circuits is sent, only the first circuit will be - taken into account. - right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. - If a list of circuits is sent, only the first circuit will be - taken into account. - left_values: Numerical parameters to be bound to the left circuit. - right_values: Numerical parameters to be bound to the right circuit. - run_options: Backend runtime options used for circuit execution. + right_values: Sequence[Sequence[float]] | None = None + ) -> np.ndarray: + + circuit_indices = self._set_circuits(left_circuits, right_circuits) + circuit_mapping = map(self._circuits.__getitem__, circuit_indices) + circuits_list = list(circuit_mapping) + + values_list = [] + for values, side, circuits in zip([left_values, right_values], ["left", "right"], + [left_circuits, right_circuits]): + values = self._check_values(values, side, circuits) + if values is not None: + values_list.append(values) + + overlaps = self._call(circuits_list, values_list) + return overlaps + + - Returns: - The job object for the fidelity calculation. - """ - job = PrimitiveJob( - self._call, left_circuit, right_circuit, left_values, right_values, **run_options - ) - job.submit() - return job diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index d0c31acaa4dc..ed3e3fc1b73e 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -15,14 +15,12 @@ import unittest import numpy as np -from ddt import ddt -from qiskit import QuantumCircuit -from qiskit.circuit import ParameterVector +from qiskit.circuit import QuantumCircuit, ParameterVector from qiskit.primitives import Sampler from qiskit.algorithms.fidelities import Fidelity from qiskit.test import QiskitTestCase - +from qiskit import QiskitError @ddt class TestFidelity(QiskitTestCase): @@ -53,96 +51,80 @@ def setUp(self): def test_1param_pair(self): """test for fidelity with one pair of parameters""" - - fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) - job = fidelity.run(left_values = self._left_params[0], - right_values = self._right_params[0]) - result = job.result() + fidelity = Fidelity(self._sampler) + result = fidelity.evaluate([self._circuit[0]], [self._circuit[1]], + self._left_params[0], self._right_params[0]) np.testing.assert_allclose(result, np.array([1.0])) def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" - - fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[1]) - job = fidelity.run(left_values = self._left_params, - right_values = self._right_params) - results = job.result() + fidelity = Fidelity(self._sampler) + n = len(self._left_params) + results = fidelity.evaluate([self._circuit[0]] * n, + [self._circuit[1]] * n, + self._left_params, self._right_params) np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_symmetry(self): """test for fidelity with the same circuit""" - fidelity = Fidelity(self._sampler, self._circuit[0], self._circuit[0]) - job_1 = fidelity.run(left_values = self._left_params, - right_values = self._right_params) - job_2 = fidelity.run(left_values = self._right_params, - right_values = self._left_params) - results_1 = job_1.result() - results_2 = job_2.result() + fidelity = Fidelity(self._sampler, [self._circuit[0]], [self._circuit[0]]) + n = len(self._left_params) + results_1 = fidelity.evaluate([self._circuit[0]] * n, [self._circuit[0]] * n, + self._left_params, self._right_params) + results_2 = fidelity.evaluate([self._circuit[0]] * n, [self._circuit[0]] * n, + self._right_params, self._left_params) np.testing.assert_allclose(results_1, results_2, atol=1e-16) def test_no_params(self): """test for fidelity without parameters""" - fidelity = Fidelity(self._sampler, self._circuit[2], self._circuit[3]) - job = fidelity.run() - results = job.result() + fidelity = Fidelity(self._sampler) + results = fidelity.evaluate([self._circuit[2]], [self._circuit[3]]) np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) def test_left_param(self): """test for fidelity with only left parameters""" - fidelity = Fidelity(self._sampler, self._circuit[1], self._circuit[3]) - job = fidelity.run(left_values = self._left_params) - results = job.result() + fidelity = Fidelity(self._sampler) + n = len(self._left_params) + results = fidelity.evaluate([self._circuit[1]] * n, [self._circuit[3]] * n, + left_values = self._left_params) np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_right_param(self): """test for fidelity with only right parameters""" - fidelity = Fidelity(self._sampler, self._circuit[3], self._circuit[1]) - job = fidelity.run(right_values = self._left_params) - results = job.result() + fidelity = Fidelity(self._sampler) + n = len(self._left_params) + results = fidelity.evaluate([self._circuit[3]] * n, [self._circuit[1]] * n, + right_values = self._left_params) np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) - # def test_not_set_circuits(self): - # """test for fidelity with no circuits during init.""" - # fidelity = Fidelity(self._sampler) - # with self.assertRaises(ValueError): - # _ = fidelity.run(left_values = self._left_params, - # right_values = self._right_params) - - def test_set_circuits_during_run(self): - """test for fidelity with no circuits during init.""" + def test_not_set_circuits(self): + """test for fidelity with no circuits.""" fidelity = Fidelity(self._sampler) - job = fidelity.run(left_circuit = [self._circuit[0]], - right_circuit = [self._circuit[1]], - left_values = self._left_params, - right_values = self._right_params) - results = job.result() - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + with self.assertRaises(TypeError): + _ = fidelity.evaluate(left_values = self._left_params, + right_values = self._right_params) - def test_set_single_circuit_during_run(self): - """test for fidelity with no circuits during init.""" + def test_circuit_mismatch(self): + """test for fidelity with different number of left/right circuits.""" fidelity = Fidelity(self._sampler) - # with self.assertRaises(ValueError): - # _ = fidelity.run(right_circuit=[self._circuit[1]], - # left_values =self._left_params, - # right_values =self._right_params - # ) - job = fidelity.run(left_circuit=[self._circuit[0]], right_circuit=[self._circuit[1]], - left_values =self._left_params, - right_values =self._right_params - ) - results = job.result() - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + n = len(self._left_params) + with self.assertRaises(ValueError): + _ = fidelity.evaluate([self._circuit[0]] * n, + [self._circuit[1]] * (n + 1), + self._left_params, self._right_params) - def test_circuit_list_during_run(self): + def test_param_mismatch(self): fidelity = Fidelity(self._sampler) - job = fidelity.run(left_circuit=[self._circuit[0], self._circuit[1], self._circuit[2]], - right_circuit=[self._circuit[1]], - left_values=self._left_params, - right_values=self._right_params - ) - results = job.result() - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) - + n = len(self._left_params) + with self.assertRaises(ValueError): + _ = fidelity.evaluate([self._circuit[0]] * n, + [self._circuit[1]] * n, + self._left_params, self._right_params[:-2]) + + with self.assertRaises(QiskitError): + _ = fidelity.evaluate([self._circuit[0]] * n, + [self._circuit[1]] * n, + self._left_params[:-2], self._right_params[:-2]) if __name__ == "__main__": unittest.main() From ad19d1423e66360009e48db2f1893ef68bf8cc8d Mon Sep 17 00:00:00 2001 From: ElePT Date: Fri, 19 Aug 2022 14:16:27 +0200 Subject: [PATCH 31/92] Update evaluate method, replace use of np --- qiskit/algorithms/fidelities/base_fidelity.py | 35 +++-- qiskit/algorithms/fidelities/fidelity.py | 125 +++++++++++------- .../algorithms/fidelities/test_fidelity.py | 70 ++++++---- 3 files changed, 149 insertions(+), 81 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index 9d027ef4672b..dae2517d6cd0 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -14,7 +14,8 @@ """ from __future__ import annotations -from abc import ABC, abstractmethod +from abc import ABC +from typing import Sequence import numpy as np from qiskit import QuantumCircuit @@ -57,10 +58,8 @@ def __init__( self._set_circuits(left_circuits, right_circuits) def _check_values( - self, values: np.ndarray | list[np.ndarray] | None, - side: str, - circuits: QuantumCircuit - ) -> np.ndarray | None: + self, values: Sequence[Sequence[float]] | None, side: str, circuits: QuantumCircuit + ) -> list[list[float]] | None: """ Check whether the passed values match the shape of the parameters of the circuit on the side provided. @@ -69,6 +68,10 @@ def _check_values( the shapes don't match. """ + # Support ndarray + if isinstance(values, np.ndarray): + values = values.tolist() + if values is None: for circuit in circuits: if circuit.num_parameters != 0: @@ -78,11 +81,26 @@ def _check_values( ) return None else: - return np.atleast_2d(values) + # ensure 2d list + if not isinstance(values, list): + values = [values] + if len(values) > 0 and not isinstance(values[0], list): + values = [values] + return values def _check_qubits_mismatch( self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit ) -> None: + """ + Check that the number of qubits of the left and right circuit matches + Args: + left_circuit: (Parametrized) quantum circuit + right_circuit: (Parametrized) quantum circuit + + Raises: + ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. + """ + if left_circuit is not None and right_circuit is not None: if left_circuit.num_qubits != right_circuit.num_qubits: raise ValueError( @@ -100,16 +118,13 @@ def _set_circuits( Args: left_circuit: (Parametrized) quantum circuit right_circuit: (Parametrized) quantum circuit - - Raises: - ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ if not len(left_circuits) == len(right_circuits): raise ValueError circuit_indices = [] - for i, (left_circuit, right_circuit) in enumerate(zip(left_circuits, right_circuits)): + for (left_circuit, right_circuit) in zip(left_circuits, right_circuits): index = self._circuit_ids.get((id(left_circuit), id(right_circuit))) diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index bb65cf583f77..296b68826941 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -13,13 +13,13 @@ Zero probability fidelity primitive """ from __future__ import annotations +from typing import Sequence import numpy as np from qiskit import QuantumCircuit from qiskit.primitives import Sampler -from qiskit.primitives.primitive_job import PrimitiveJob from .base_fidelity import BaseFidelity -# from .fidelity_job import FidelityJob + class Fidelity(BaseFidelity): """ @@ -30,7 +30,7 @@ def __init__( self, sampler: Sampler, left_circuits: Sequence[QuantumCircuit] | None = None, - right_circuits: Sequence[QuantumCircuit]| None = None, + right_circuits: Sequence[QuantumCircuit] | None = None, ) -> None: """ Initializes the class to evaluate the fidelities defined as the state overlap @@ -39,8 +39,8 @@ def __init__( states :math:`\psi` and :math:`\phi` prepared by the circuits ``left_circuit`` and ``right_circuit``, respectively. Args: - left_circuit: (Parametrized) quantum circuit :math:`|\psi\rangle`. - right_circuit: (Parametrized) quantum circuit :math:`|\phi\rangle`. + left_circuits: (Parametrized) quantum circuit :math:`|\psi\rangle`. + right_circuits: (Parametrized) quantum circuit :math:`|\phi\rangle`. sampler: Sampler primitive instance. Raises: ValueError: left_circuit and right_circuit don't have the same number of qubits. @@ -48,15 +48,73 @@ def __init__( self.sampler = sampler super().__init__(left_circuits, right_circuits) - def _call( + def _preprocess_inputs( + self, + left_circuits: Sequence[QuantumCircuit], + right_circuits: Sequence[QuantumCircuit], + left_values: Sequence[Sequence[float]] | None = None, + right_values: Sequence[Sequence[float]] | None = None, + ) -> tuple(list[QuantumCircuit], list[list[float]]): + """Preprocess circuits and parameter values. + + Args: + left_circuits: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. + If a list of circuits is sent, only the first circuit will be + taken into account. + right_circuits: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. + If a list of circuits is sent, only the first circuit will be + taken into account. + left_values: Numerical parameters to be bound to the left circuit. + right_values: Numerical parameters to be bound to the right circuit. + + Returns: + Preprocessed circuits and parameter values. + + Raises: + ValueError: The number of left parameters has to be equal to the number of + right parameters. + """ + + # _set_circuits returns indices in list of cached circuits + circuit_indices = self._set_circuits(left_circuits, right_circuits) + circuit_mapping = map(self._circuits.__getitem__, circuit_indices) + # final list of circuits that will be evaluated + circuits_list = list(circuit_mapping) + + left_values = self._check_values(left_values, "left", left_circuits) + right_values = self._check_values(right_values, "right", right_circuits) + + values_list = [] + if right_values is not None or left_values is not None: + if right_values is None: + values_list = left_values + elif left_values is None: + values_list = right_values + else: + for (left_val, right_val) in zip(left_values, right_values): + if len(left_val) != len(right_val): + raise ValueError( + f"The number of left parameters (currently {len(left_val)})" + f"has to be equal to the number of right parameters." + f"(currently {len(right_val)})" + ) + values_list.append(left_val + right_val) + + return circuits_list, values_list + + def evaluate( self, - circuits_list, - values_list + left_circuits: Sequence[QuantumCircuit], + right_circuits: Sequence[QuantumCircuit], + left_values: Sequence[Sequence[float]] | None = None, + right_values: Sequence[Sequence[float]] | None = None, + **run_options, ) -> np.ndarray: """Run the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter values (left and right). - Args: + + Args: left_circuits: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. If a list of circuits is sent, only the first circuit will be taken into account. @@ -68,26 +126,28 @@ def _call( run_options: Backend runtime options used for circuit execution. Returns: - The result for the fidelity calculation. + The result of the fidelity calculation. + + Raises: + ValueError: At least one left and right circuit must be defined. """ + circuits_list, values_list = self._preprocess_inputs( + left_circuits, right_circuits, left_values, right_values + ) + if circuits_list is None: raise ValueError( - "The left and right circuits must be defined to " + "At least one left and right circuit must be defined to " "calculate the state overlap. " ) if len(values_list) > 0: - if len(values_list) == 2 and values_list[0].shape[0] != values_list[1].shape[0]: - raise ValueError( - f"The number of left parameters (currently {values_list[0].shape[0]})" - "has to be equal to the number of right parameters." - f"(currently {values_list[1].shape[0]})" - ) - values = np.hstack(values_list) - job = self.sampler.run(circuits=circuits_list, parameter_values=values) + job = self.sampler.run( + circuits=circuits_list, parameter_values=values_list, **run_options + ) else: - job = self.sampler.run(circuits=circuits_list) + job = self.sampler.run(circuits=circuits_list, **run_options) result = job.result() @@ -95,28 +155,3 @@ def _call( # negative values in some way (e.g. clipping to zero) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] return np.array(overlaps) - - def evaluate(self, - left_circuits: Sequence[QuantumCircuit], - right_circuits: Sequence[QuantumCircuit], - left_values: Sequence[Sequence[float]] | None = None, - right_values: Sequence[Sequence[float]] | None = None - ) -> np.ndarray: - - circuit_indices = self._set_circuits(left_circuits, right_circuits) - circuit_mapping = map(self._circuits.__getitem__, circuit_indices) - circuits_list = list(circuit_mapping) - - values_list = [] - for values, side, circuits in zip([left_values, right_values], ["left", "right"], - [left_circuits, right_circuits]): - values = self._check_values(values, side, circuits) - if values is not None: - values_list.append(values) - - overlaps = self._call(circuits_list, values_list) - return overlaps - - - - diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index ed3e3fc1b73e..ca7f58149ba5 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -22,7 +22,7 @@ from qiskit.test import QiskitTestCase from qiskit import QiskitError -@ddt + class TestFidelity(QiskitTestCase): """Test Fidelity""" @@ -52,17 +52,18 @@ def setUp(self): def test_1param_pair(self): """test for fidelity with one pair of parameters""" fidelity = Fidelity(self._sampler) - result = fidelity.evaluate([self._circuit[0]], [self._circuit[1]], - self._left_params[0], self._right_params[0]) + result = fidelity.evaluate( + [self._circuit[0]], [self._circuit[1]], self._left_params[0], self._right_params[0] + ) np.testing.assert_allclose(result, np.array([1.0])) def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" fidelity = Fidelity(self._sampler) n = len(self._left_params) - results = fidelity.evaluate([self._circuit[0]] * n, - [self._circuit[1]] * n, - self._left_params, self._right_params) + results = fidelity.evaluate( + [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params + ) np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_symmetry(self): @@ -70,10 +71,12 @@ def test_symmetry(self): fidelity = Fidelity(self._sampler, [self._circuit[0]], [self._circuit[0]]) n = len(self._left_params) - results_1 = fidelity.evaluate([self._circuit[0]] * n, [self._circuit[0]] * n, - self._left_params, self._right_params) - results_2 = fidelity.evaluate([self._circuit[0]] * n, [self._circuit[0]] * n, - self._right_params, self._left_params) + results_1 = fidelity.evaluate( + [self._circuit[0]] * n, [self._circuit[0]] * n, self._left_params, self._right_params + ) + results_2 = fidelity.evaluate( + [self._circuit[0]] * n, [self._circuit[0]] * n, self._right_params, self._left_params + ) np.testing.assert_allclose(results_1, results_2, atol=1e-16) def test_no_params(self): @@ -86,45 +89,60 @@ def test_left_param(self): """test for fidelity with only left parameters""" fidelity = Fidelity(self._sampler) n = len(self._left_params) - results = fidelity.evaluate([self._circuit[1]] * n, [self._circuit[3]] * n, - left_values = self._left_params) + results = fidelity.evaluate( + [self._circuit[1]] * n, [self._circuit[3]] * n, left_values=self._left_params + ) np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_right_param(self): """test for fidelity with only right parameters""" fidelity = Fidelity(self._sampler) n = len(self._left_params) - results = fidelity.evaluate([self._circuit[3]] * n, [self._circuit[1]] * n, - right_values = self._left_params) + results = fidelity.evaluate( + [self._circuit[3]] * n, [self._circuit[1]] * n, right_values=self._left_params + ) np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_not_set_circuits(self): """test for fidelity with no circuits.""" fidelity = Fidelity(self._sampler) with self.assertRaises(TypeError): - _ = fidelity.evaluate(left_values = self._left_params, - right_values = self._right_params) + _ = fidelity.evaluate(left_values=self._left_params, right_values=self._right_params) def test_circuit_mismatch(self): """test for fidelity with different number of left/right circuits.""" fidelity = Fidelity(self._sampler) n = len(self._left_params) with self.assertRaises(ValueError): - _ = fidelity.evaluate([self._circuit[0]] * n, - [self._circuit[1]] * (n + 1), - self._left_params, self._right_params) + _ = fidelity.evaluate( + [self._circuit[0]] * n, + [self._circuit[1]] * (n + 1), + self._left_params, + self._right_params, + ) def test_param_mismatch(self): fidelity = Fidelity(self._sampler) n = len(self._left_params) - with self.assertRaises(ValueError): - _ = fidelity.evaluate([self._circuit[0]] * n, - [self._circuit[1]] * n, - self._left_params, self._right_params[:-2]) + with self.assertRaises(QiskitError): + _ = fidelity.evaluate( + [self._circuit[0]] * n, + [self._circuit[1]] * n, + self._left_params, + self._right_params[:-2], + ) with self.assertRaises(QiskitError): - _ = fidelity.evaluate([self._circuit[0]] * n, - [self._circuit[1]] * n, - self._left_params[:-2], self._right_params[:-2]) + _ = fidelity.evaluate( + [self._circuit[0]] * n, + [self._circuit[1]] * n, + self._left_params[:-2], + self._right_params[:-2], + ) + + with self.assertRaises(ValueError): + _ = fidelity.evaluate([self._circuit[0]] * n, [self._circuit[1]] * n) + + if __name__ == "__main__": unittest.main() From 945fdfc39475fadb8e6b0151810cba1f589079cc Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 23 Aug 2022 11:12:05 +0200 Subject: [PATCH 32/92] Remove circuits from init --- qiskit/algorithms/fidelities/base_fidelity.py | 17 ++++------------- qiskit/algorithms/fidelities/fidelity.py | 14 ++++---------- .../algorithms/fidelities/test_fidelity.py | 2 +- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index dae2517d6cd0..c13a0ff7b881 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -29,8 +29,6 @@ class BaseFidelity(ABC): def __init__( self, - left_circuits: Sequence[QuantumCircuit] | None = None, - right_circuits: Sequence[QuantumCircuit] | None = None, ) -> None: r"""Initializes the class to evaluate the fidelities defined as @@ -39,13 +37,6 @@ def __init__( where :math:`x` and :math:`y` are optional parametrizations of the states :math:`\psi` and :math:`\phi` prepared by the circuits ``left_circuit`` and ``right_circuit``, respectively. - - Args: - left_circuit: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. - right_circuit: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. - - Raises: - ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. """ self._circuits = [] @@ -54,9 +45,6 @@ def __init__( self._left_parameters = [] self._right_parameters = [] - if left_circuits is not None and right_circuits is not None: - self._set_circuits(left_circuits, right_circuits) - def _check_values( self, values: Sequence[Sequence[float]] | None, side: str, circuits: QuantumCircuit ) -> list[list[float]] | None: @@ -121,7 +109,10 @@ def _set_circuits( """ if not len(left_circuits) == len(right_circuits): - raise ValueError + raise ValueError( + f"The number of left ({len(left_circuits)}) \ + and right circuits ({len(right_circuits)}) do not coincide." + ) circuit_indices = [] for (left_circuit, right_circuit) in zip(left_circuits, right_circuits): diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index 296b68826941..8d179c029d0f 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -29,8 +29,6 @@ class Fidelity(BaseFidelity): def __init__( self, sampler: Sampler, - left_circuits: Sequence[QuantumCircuit] | None = None, - right_circuits: Sequence[QuantumCircuit] | None = None, ) -> None: """ Initializes the class to evaluate the fidelities defined as the state overlap @@ -39,14 +37,10 @@ def __init__( states :math:`\psi` and :math:`\phi` prepared by the circuits ``left_circuit`` and ``right_circuit``, respectively. Args: - left_circuits: (Parametrized) quantum circuit :math:`|\psi\rangle`. - right_circuits: (Parametrized) quantum circuit :math:`|\phi\rangle`. sampler: Sampler primitive instance. - Raises: - ValueError: left_circuit and right_circuit don't have the same number of qubits. """ - self.sampler = sampler - super().__init__(left_circuits, right_circuits) + self._sampler = sampler + super().__init__() def _preprocess_inputs( self, @@ -143,11 +137,11 @@ def evaluate( ) if len(values_list) > 0: - job = self.sampler.run( + job = self._sampler.run( circuits=circuits_list, parameter_values=values_list, **run_options ) else: - job = self.sampler.run(circuits=circuits_list, **run_options) + job = self._sampler.run(circuits=circuits_list, **run_options) result = job.result() diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index ca7f58149ba5..b3eb77992613 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -69,7 +69,7 @@ def test_4param_pairs(self): def test_symmetry(self): """test for fidelity with the same circuit""" - fidelity = Fidelity(self._sampler, [self._circuit[0]], [self._circuit[0]]) + fidelity = Fidelity(self._sampler) n = len(self._left_params) results_1 = fidelity.evaluate( [self._circuit[0]] * n, [self._circuit[0]] * n, self._left_params, self._right_params From 198a3afb40d67474737190d339c59118d6b77981 Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 23 Aug 2022 11:33:57 +0200 Subject: [PATCH 33/92] Add async, tests, update docstrings --- qiskit/algorithms/fidelities/base_fidelity.py | 4 +- qiskit/algorithms/fidelities/fidelity.py | 71 +++++++++++++------ .../algorithms/fidelities/test_fidelity.py | 19 ++++- 3 files changed, 71 insertions(+), 23 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index c13a0ff7b881..b70bcd21c743 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -110,9 +110,9 @@ def _set_circuits( if not len(left_circuits) == len(right_circuits): raise ValueError( - f"The number of left ({len(left_circuits)}) \ + f"The number of left ({len(left_circuits)}) \ and right circuits ({len(right_circuits)}) do not coincide." - ) + ) circuit_indices = [] for (left_circuit, right_circuit) in zip(left_circuits, right_circuits): diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index 8d179c029d0f..1c05001d6b3d 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -18,6 +18,7 @@ from qiskit import QuantumCircuit from qiskit.primitives import Sampler +from qiskit.primitives.primitive_job import PrimitiveJob from .base_fidelity import BaseFidelity @@ -52,14 +53,10 @@ def _preprocess_inputs( """Preprocess circuits and parameter values. Args: - left_circuits: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. - If a list of circuits is sent, only the first circuit will be - taken into account. - right_circuits: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. - If a list of circuits is sent, only the first circuit will be - taken into account. - left_values: Numerical parameters to be bound to the left circuit. - right_values: Numerical parameters to be bound to the right circuit. + left_circuits: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. + right_circuits: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. + left_values: Numerical parameters to be bound to the left circuits. + right_values: Numerical parameters to be bound to the right circuits. Returns: Preprocessed circuits and parameter values. @@ -103,20 +100,16 @@ def evaluate( left_values: Sequence[Sequence[float]] | None = None, right_values: Sequence[Sequence[float]] | None = None, **run_options, - ) -> np.ndarray: - """Run the state overlap (fidelity) calculation between 2 + ) -> np.ndarray | float: + """Compute the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter values (left and right). Args: - left_circuits: (Parametrized) quantum circuit preparing :math:`|\psi\rangle`. - If a list of circuits is sent, only the first circuit will be - taken into account. - right_circuits: (Parametrized) quantum circuit preparing :math:`|\phi\rangle`. - If a list of circuits is sent, only the first circuit will be - taken into account. - left_values: Numerical parameters to be bound to the left circuit. - right_values: Numerical parameters to be bound to the right circuit. + left_circuits: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. + right_circuits: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. + left_values: Numerical parameters to be bound to the left circuits. + right_values: Numerical parameters to be bound to the right circuits. run_options: Backend runtime options used for circuit execution. Returns: @@ -147,5 +140,43 @@ def evaluate( # if error mitigation is added in the future, we will have to handle # negative values in some way (e.g. clipping to zero) - overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] - return np.array(overlaps) + overlaps = np.array([prob_dist.get(0, 0) for prob_dist in result.quasi_dists]) + + # return float if only 1 element in array + if len(overlaps) > 1: + return overlaps + else: + return overlaps[0] + + def run( + self, + left_circuits: Sequence[QuantumCircuit], + right_circuits: Sequence[QuantumCircuit], + left_values: Sequence[Sequence[float]] | None = None, + right_values: Sequence[Sequence[float]] | None = None, + **run_options, + ) -> PrimitiveJob: + """Run asynchronously the state overlap (fidelity) calculation between 2 + parametrized circuits (left and right) for a specific set of parameter + values (left and right). + + Args: + left_circuits: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. + right_circuits: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. + left_values: Numerical parameters to be bound to the left circuits. + right_values: Numerical parameters to be bound to the right circuits. + run_options: Backend runtime options used for circuit execution. + + Returns: + Primitive job for the fidelity calculation + + Raises: + ValueError: At least one left and right circuit must be defined. + """ + + job = PrimitiveJob( + self.evaluate, left_circuits, right_circuits, left_values, right_values, **run_options + ) + + job.submit() + return job diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index b3eb77992613..29b004229422 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -68,7 +68,6 @@ def test_4param_pairs(self): def test_symmetry(self): """test for fidelity with the same circuit""" - fidelity = Fidelity(self._sampler) n = len(self._left_params) results_1 = fidelity.evaluate( @@ -143,6 +142,24 @@ def test_param_mismatch(self): with self.assertRaises(ValueError): _ = fidelity.evaluate([self._circuit[0]] * n, [self._circuit[1]] * n) + def test_async_result(self): + fidelity = Fidelity(self._sampler) + n = len(self._left_params) + job = fidelity.run( + [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params + ) + np.testing.assert_allclose(job.result(), np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + + def test_async_join(self): + fidelity = Fidelity(self._sampler) + jobs = [] + for left_param, right_param in zip(self._left_params, self._right_params): + job = fidelity.run([self._circuit[0]], [self._circuit[1]], left_param, right_param) + jobs.append(job) + + results = [job.result() for job in jobs] + np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + if __name__ == "__main__": unittest.main() From d449e16268b05d561e3e8feb4bdfc8f45e98bb96 Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 23 Aug 2022 14:50:23 +0200 Subject: [PATCH 34/92] Add default run options --- qiskit/algorithms/fidelities/fidelity.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index 1c05001d6b3d..67b6f59eda11 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -30,6 +30,7 @@ class Fidelity(BaseFidelity): def __init__( self, sampler: Sampler, + **run_options ) -> None: """ Initializes the class to evaluate the fidelities defined as the state overlap @@ -39,8 +40,11 @@ def __init__( ``left_circuit`` and ``right_circuit``, respectively. Args: sampler: Sampler primitive instance. + run_options: Backend runtime options used for circuit execution. + """ self._sampler = sampler + self._default_run_options = run_options super().__init__() def _preprocess_inputs( @@ -129,6 +133,10 @@ def evaluate( "calculate the state overlap. " ) + # The priority of run options is as follows: + # run_options in `evaluate` method > fidelity's default run_options > primitive's default run_options. + run_options = run_options or self._default_run_options + if len(values_list) > 0: job = self._sampler.run( circuits=circuits_list, parameter_values=values_list, **run_options From 5f99811172a1c586f812e11ce08a6896e00f62e3 Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 23 Aug 2022 18:39:08 +0200 Subject: [PATCH 35/92] change names, abstract methods, improve docstrings --- qiskit/algorithms/fidelities/base_fidelity.py | 215 +++++++++++++----- qiskit/algorithms/fidelities/fidelity.py | 135 ++++------- .../algorithms/fidelities/test_fidelity.py | 8 +- 3 files changed, 203 insertions(+), 155 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index b70bcd21c743..d7155f36292d 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -14,7 +14,7 @@ """ from __future__ import annotations -from abc import ABC +from abc import ABC, abstractmethod from typing import Sequence import numpy as np @@ -24,36 +24,45 @@ class BaseFidelity(ABC): """ - Defines the interface to calculate fidelities. + An interface to calculate fidelities (state overlaps) for pairs of + (parametrized) quantum circuits. """ def __init__( self, ) -> None: - r"""Initializes the class to evaluate the fidelities defined as + """Initializes the class to evaluate fidelities.""" - :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, + self._circuits: Sequence[QuantumCircuit] = [] + self._parameter_values: Sequence[Sequence[float]] = [] - where :math:`x` and :math:`y` are optional parametrizations of the - states :math:`\psi` and :math:`\phi` prepared by the circuits - ``left_circuit`` and ``right_circuit``, respectively. - """ - - self._circuits = [] - self._circuit_ids = {} + # use cache for preventing unnecessary circuit compositions + self._circuit_cache: dict[(int, int), QuantumCircuit] = {} self._left_parameters = [] self._right_parameters = [] - def _check_values( - self, values: Sequence[Sequence[float]] | None, side: str, circuits: QuantumCircuit + def _check_values_shape( + self, + values: Sequence[Sequence[float]] | None, + circuits: QuantumCircuit, + label: str | None = " ", ) -> list[list[float]] | None: """ - Check whether the passed values match the shape of the parameters of the circuit on the side - provided. + Checks whether the passed values match the shape of the parameters + of the corresponding circuits and formats values to 2D list. + + Args: + values: parameter values corresponding to the circuits to be checked + circuits: list of circuits to be checked + label: optional label to allow for circuit identification in error message - Returns a 2D-Array if values match, `None` if no parameters are passed and raises an error if - the shapes don't match. + Returns: + Returns a 2D list if values match, `None` if no parameters are passed + + Raises: + ValueError: if the number of parameter values doesn't match the number of + circuit parameters """ # Support ndarray @@ -64,7 +73,7 @@ def _check_values( for circuit in circuits: if circuit.num_parameters != 0: raise ValueError( - f"`values_{side}` cannot be `None` because the {side} circuit has " + f"`values_{label}` cannot be `None` because circuit_{label} has " f"{circuit.num_parameters} free parameters." ) return None @@ -76,69 +85,163 @@ def _check_values( values = [values] return values - def _check_qubits_mismatch( - self, left_circuit: QuantumCircuit, right_circuit: QuantumCircuit - ) -> None: + def _check_qubits_mismatch(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit) -> None: """ - Check that the number of qubits of the left and right circuit matches + Checks that the number of qubits of 2 circuits matches. Args: - left_circuit: (Parametrized) quantum circuit - right_circuit: (Parametrized) quantum circuit + circuit_1: (Parametrized) quantum circuit + circuit_2: (Parametrized) quantum circuit Raises: - ValueError: ``left_circuit`` and ``right_circuit`` don't have the same number of qubits. + ValueError: when ``circuit_1`` and ``circuit_2`` don't have the same number of qubits. """ - if left_circuit is not None and right_circuit is not None: - if left_circuit.num_qubits != right_circuit.num_qubits: + if circuit_1 is not None and circuit_2 is not None: + if circuit_1.num_qubits != circuit_2.num_qubits: raise ValueError( - f"The number of qubits for the left circuit ({left_circuit.num_qubits}) \ - and right circuit ({right_circuit.num_qubits}) do not coincide." + f"The number of qubits for the left circuit ({circuit_1.num_qubits}) \ + and right circuit ({circuit_2.num_qubits}) do not coincide." ) + @abstractmethod + def _create_fidelity_circuit( + self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit + ) -> QuantumCircuit: + """ + Implementation-dependent method to create a fidelity circuit + from 2 circuit inputs. + Args: + circuit_1: (Parametrized) quantum circuit + circuit_2: (Parametrized) quantum circuit + + Returns: + The fidelity quantum circuit corresponding to circuit_1 and circuit_2. + """ + raise NotImplementedError + def _set_circuits( self, - left_circuits: Sequence[QuantumCircuit] | None = None, - right_circuits: Sequence[QuantumCircuit] | None = None, - ) -> np.ndarray: + circuits_1: Sequence[QuantumCircuit] | None = None, + circuits_2: Sequence[QuantumCircuit] | None = None, + ) -> None: """ - Fix the circuits for the fidelity to be computed of. + Update the list of fidelity circuits to be evaluated. + These circuits represent the state overlap between pairs of input circuits, + and their construction depends on the fidelity method implementations. + Args: - left_circuit: (Parametrized) quantum circuit - right_circuit: (Parametrized) quantum circuit + circuits_1: (Parametrized) quantum circuits. + circuits_2: (Parametrized) quantum circuits. + + Raises: + ValueError: if the length of the input circuit lists doesn't match. """ - if not len(left_circuits) == len(right_circuits): + if not len(circuits_1) == len(circuits_2): raise ValueError( - f"The number of left ({len(left_circuits)}) \ - and right circuits ({len(right_circuits)}) do not coincide." + f"The length of the first circuit list({len(circuits_1)}) \ + and second circuit list ({len(circuits_2)}) does not coincide." ) - circuit_indices = [] - for (left_circuit, right_circuit) in zip(left_circuits, right_circuits): + circuits = [] + for (circuit_1, circuit_2) in zip(circuits_1, circuits_2): - index = self._circuit_ids.get((id(left_circuit), id(right_circuit))) + circuit = self._circuit_cache.get((id(circuit_1), id(circuit_2))) - if index is not None: - # the composed circuit already exists - circuit_indices.append(index) + if circuit is not None: + circuits.append(circuit) else: - # create new circuit - self._check_qubits_mismatch(left_circuit, right_circuit) + self._check_qubits_mismatch(circuit_1, circuit_2) - left_parameters = ParameterVector("x", left_circuit.num_parameters) + # re-parametrize input circuits + left_parameters = ParameterVector("x", circuit_1.num_parameters) self._left_parameters.append(left_parameters) - parametrized_left_circuit = left_circuit.assign_parameters(left_parameters) + parametrized_circuit_1 = circuit_1.assign_parameters(left_parameters) - right_parameters = ParameterVector("y", right_circuit.num_parameters) + right_parameters = ParameterVector("y", circuit_2.num_parameters) self._right_parameters.append(right_parameters) - parametrized_right_circuit = right_circuit.assign_parameters(right_parameters) + parametrized_circuit_2 = circuit_2.assign_parameters(right_parameters) - circuit = parametrized_left_circuit.compose(parametrized_right_circuit.inverse()) - circuit.measure_all() + circuit = self._create_fidelity_circuit( + parametrized_circuit_1, parametrized_circuit_2 + ) + circuits.append(circuit) + # update cache + self._circuit_cache[id(circuit_1), id(circuit_2)] = circuit - self._circuit_ids[id(left_circuit), id(right_circuit)] = len(self._circuits) - self._circuits.append(circuit) - circuit_indices.append(len(self._circuits) - 1) + # set circuits + self._circuits = circuits - return circuit_indices + def _set_values( + self, values_1: Sequence[Sequence[float]] | None, values_2: Sequence[Sequence[float]] | None + ) -> None: + """ + Update the list of parameter values to evaluate the corresponding + fidelity circuits with. + + Args: + values_1: Numerical parameters to be bound to the first circuits. + values_2: Numerical parameters to be bound to the second circuits. + + Raises: + ValueError: if the length of the input value lists doesn't match. + """ + + values = [] + if values_2 is not None or values_1 is not None: + if values_2 is None: + values = values_1 + elif values_1 is None: + values = values_2 + else: + for (val_1, val_2) in zip(values_1, values_2): + if len(val_1) != len(val_2): + raise ValueError( + f"The number of left parameters (currently {len(val_1)})" + f"has to be equal to the number of right parameters." + f"(currently {len(val_2)})" + ) + values.append(val_1 + val_2) + + # set values + self._parameter_values = values + + def _preprocess_inputs( + self, + circuits_1: Sequence[QuantumCircuit], + circuits_2: Sequence[QuantumCircuit], + values_1: Sequence[Sequence[float]] | None = None, + values_2: Sequence[Sequence[float]] | None = None, + ) -> None: + """Preprocess input circuits and parameter values and update corresponding lists. + + Args: + circuits_1: (Parametrized) quantum circuits preparing the first list of quantum states. + circuits_2: (Parametrized) quantum circuits preparing the second list of quantum states. + values_1: Numerical parameters to be bound to the first circuits. + values_2: Numerical parameters to be bound to the second circuits. + + """ + + self._set_circuits(circuits_1, circuits_2) + + values_1 = self._check_values_shape(values_1, circuits_1, "1") + values_2 = self._check_values_shape(values_2, circuits_2, "2") + + self._set_values(values_1, values_2) + + @abstractmethod + def evaluate( + self, + circuits_1: Sequence[QuantumCircuit], + circuits_2: Sequence[QuantumCircuit], + values_1: Sequence[Sequence[float]] | None = None, + values_2: Sequence[Sequence[float]] | None = None, + **run_options, + ) -> list[float]: + """Compute the state overlap (fidelity) calculation between 2 + parametrized circuits (first and second) for a specific set of parameter + values (first and second). This calculation depends on the particular + fidelity method implementation. + """ + return NotImplementedError diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index 67b6f59eda11..6118652352a9 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -14,7 +14,6 @@ """ from __future__ import annotations from typing import Sequence -import numpy as np from qiskit import QuantumCircuit from qiskit.primitives import Sampler @@ -24,20 +23,18 @@ class Fidelity(BaseFidelity): """ - Calculates the fidelity of two quantum circuits by measuring the zero probability outcome. + This class leverages the sampler primitive to calculate the fidelity of two quantum circuits + by measuring the zero probability outcome (compute-uncompute method). """ - def __init__( - self, - sampler: Sampler, - **run_options - ) -> None: - """ - Initializes the class to evaluate the fidelities defined as the state overlap + def __init__(self, sampler: Sampler, **run_options) -> None: + """Initializes the class to evaluate the fidelities defined as the state overlap + :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, + where :math:`x` and :math:`y` are optional parametrizations of the states :math:`\psi` and :math:`\phi` prepared by the circuits - ``left_circuit`` and ``right_circuit``, respectively. + ``circuit_1`` and ``circuit_2``, respectively. Args: sampler: Sampler primitive instance. run_options: Backend runtime options used for circuit execution. @@ -47,73 +44,30 @@ def __init__( self._default_run_options = run_options super().__init__() - def _preprocess_inputs( - self, - left_circuits: Sequence[QuantumCircuit], - right_circuits: Sequence[QuantumCircuit], - left_values: Sequence[Sequence[float]] | None = None, - right_values: Sequence[Sequence[float]] | None = None, - ) -> tuple(list[QuantumCircuit], list[list[float]]): - """Preprocess circuits and parameter values. - - Args: - left_circuits: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - right_circuits: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - left_values: Numerical parameters to be bound to the left circuits. - right_values: Numerical parameters to be bound to the right circuits. - - Returns: - Preprocessed circuits and parameter values. - - Raises: - ValueError: The number of left parameters has to be equal to the number of - right parameters. - """ - - # _set_circuits returns indices in list of cached circuits - circuit_indices = self._set_circuits(left_circuits, right_circuits) - circuit_mapping = map(self._circuits.__getitem__, circuit_indices) - # final list of circuits that will be evaluated - circuits_list = list(circuit_mapping) - - left_values = self._check_values(left_values, "left", left_circuits) - right_values = self._check_values(right_values, "right", right_circuits) - - values_list = [] - if right_values is not None or left_values is not None: - if right_values is None: - values_list = left_values - elif left_values is None: - values_list = right_values - else: - for (left_val, right_val) in zip(left_values, right_values): - if len(left_val) != len(right_val): - raise ValueError( - f"The number of left parameters (currently {len(left_val)})" - f"has to be equal to the number of right parameters." - f"(currently {len(right_val)})" - ) - values_list.append(left_val + right_val) - - return circuits_list, values_list + def _create_fidelity_circuit(self, circuit_1, circuit_2): + circuit = circuit_1.compose(circuit_2.inverse()) + circuit.measure_all() + return circuit def evaluate( self, - left_circuits: Sequence[QuantumCircuit], - right_circuits: Sequence[QuantumCircuit], - left_values: Sequence[Sequence[float]] | None = None, - right_values: Sequence[Sequence[float]] | None = None, + circuits_1: Sequence[QuantumCircuit], + circuits_2: Sequence[QuantumCircuit], + values_1: Sequence[Sequence[float]] | None = None, + values_2: Sequence[Sequence[float]] | None = None, **run_options, - ) -> np.ndarray | float: + ) -> list[float]: """Compute the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter - values (left and right). + values (left and right) following the compute-uncompute method, where + the fidelity corresponds to: + :math:`|\langle\psi(x)|\phi(y)\rangle|^2` Args: - left_circuits: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - right_circuits: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - left_values: Numerical parameters to be bound to the left circuits. - right_values: Numerical parameters to be bound to the right circuits. + circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. + circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. + values_1: Numerical parameters to be bound to the first circuits. + values_2: Numerical parameters to be bound to the second circuits. run_options: Backend runtime options used for circuit execution. Returns: @@ -123,45 +77,38 @@ def evaluate( ValueError: At least one left and right circuit must be defined. """ - circuits_list, values_list = self._preprocess_inputs( - left_circuits, right_circuits, left_values, right_values - ) + self._preprocess_inputs(circuits_1, circuits_2, values_1, values_2) - if circuits_list is None: + if len(self._circuits) == 0: raise ValueError( - "At least one left and right circuit must be defined to " - "calculate the state overlap. " + "At least one pair of circuits must be defined to " "calculate the state overlap. " ) # The priority of run options is as follows: # run_options in `evaluate` method > fidelity's default run_options > primitive's default run_options. run_options = run_options or self._default_run_options - if len(values_list) > 0: + if len(self._parameter_values) > 0: job = self._sampler.run( - circuits=circuits_list, parameter_values=values_list, **run_options + circuits=self._circuits, parameter_values=self._parameter_values, **run_options ) else: - job = self._sampler.run(circuits=circuits_list, **run_options) + job = self._sampler.run(circuits=self._circuits, **run_options) result = job.result() # if error mitigation is added in the future, we will have to handle # negative values in some way (e.g. clipping to zero) - overlaps = np.array([prob_dist.get(0, 0) for prob_dist in result.quasi_dists]) + overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] - # return float if only 1 element in array - if len(overlaps) > 1: - return overlaps - else: - return overlaps[0] + return overlaps def run( self, - left_circuits: Sequence[QuantumCircuit], - right_circuits: Sequence[QuantumCircuit], - left_values: Sequence[Sequence[float]] | None = None, - right_values: Sequence[Sequence[float]] | None = None, + circuits_1: Sequence[QuantumCircuit], + circuits_2: Sequence[QuantumCircuit], + values_1: Sequence[Sequence[float]] | None = None, + values_2: Sequence[Sequence[float]] | None = None, **run_options, ) -> PrimitiveJob: """Run asynchronously the state overlap (fidelity) calculation between 2 @@ -169,10 +116,10 @@ def run( values (left and right). Args: - left_circuits: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - right_circuits: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - left_values: Numerical parameters to be bound to the left circuits. - right_values: Numerical parameters to be bound to the right circuits. + circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. + circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. + values_1: Numerical parameters to be bound to the left circuits. + values_2: Numerical parameters to be bound to the right circuits. run_options: Backend runtime options used for circuit execution. Returns: @@ -182,9 +129,7 @@ def run( ValueError: At least one left and right circuit must be defined. """ - job = PrimitiveJob( - self.evaluate, left_circuits, right_circuits, left_values, right_values, **run_options - ) + job = PrimitiveJob(self.evaluate, circuits_1, circuits_2, values_1, values_2, **run_options) job.submit() return job diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index 29b004229422..7d4e38e424ab 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -89,7 +89,7 @@ def test_left_param(self): fidelity = Fidelity(self._sampler) n = len(self._left_params) results = fidelity.evaluate( - [self._circuit[1]] * n, [self._circuit[3]] * n, left_values=self._left_params + [self._circuit[1]] * n, [self._circuit[3]] * n, values_1=self._left_params ) np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) @@ -98,7 +98,7 @@ def test_right_param(self): fidelity = Fidelity(self._sampler) n = len(self._left_params) results = fidelity.evaluate( - [self._circuit[3]] * n, [self._circuit[1]] * n, right_values=self._left_params + [self._circuit[3]] * n, [self._circuit[1]] * n, values_2=self._left_params ) np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) @@ -106,7 +106,7 @@ def test_not_set_circuits(self): """test for fidelity with no circuits.""" fidelity = Fidelity(self._sampler) with self.assertRaises(TypeError): - _ = fidelity.evaluate(left_values=self._left_params, right_values=self._right_params) + _ = fidelity.evaluate(values_1=self._left_params, values_2=self._right_params) def test_circuit_mismatch(self): """test for fidelity with different number of left/right circuits.""" @@ -157,7 +157,7 @@ def test_async_join(self): job = fidelity.run([self._circuit[0]], [self._circuit[1]], left_param, right_param) jobs.append(job) - results = [job.result() for job in jobs] + results = [job.result()[0] for job in jobs] np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) From dedc3aee47e2c2e6066d958c5bd0493cb94f24ed Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 24 Aug 2022 10:44:52 +0200 Subject: [PATCH 36/92] Update docstrings, typing --- qiskit/algorithms/fidelities/base_fidelity.py | 6 +++--- qiskit/algorithms/fidelities/fidelity.py | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index d7155f36292d..fe622ac92a2e 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -10,12 +10,12 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """ -Base fidelity primitive +Base fidelity interface """ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Sequence +from typing import Sequence, Mapping import numpy as np from qiskit import QuantumCircuit @@ -37,7 +37,7 @@ def __init__( self._parameter_values: Sequence[Sequence[float]] = [] # use cache for preventing unnecessary circuit compositions - self._circuit_cache: dict[(int, int), QuantumCircuit] = {} + self._circuit_cache: Mapping[(int, int), QuantumCircuit] = {} self._left_parameters = [] self._right_parameters = [] diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index 6118652352a9..a4dd95d2c6f5 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -10,8 +10,9 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """ -Zero probability fidelity primitive +Compute-uncompute fidelity interface using primitives """ + from __future__ import annotations from typing import Sequence @@ -24,7 +25,7 @@ class Fidelity(BaseFidelity): """ This class leverages the sampler primitive to calculate the fidelity of two quantum circuits - by measuring the zero probability outcome (compute-uncompute method). + with the compute-uncompute method. """ def __init__(self, sampler: Sampler, **run_options) -> None: @@ -61,6 +62,7 @@ def evaluate( parametrized circuits (left and right) for a specific set of parameter values (left and right) following the compute-uncompute method, where the fidelity corresponds to: + :math:`|\langle\psi(x)|\phi(y)\rangle|^2` Args: From 4cd400710339f1d75367ab5261f5f40a8ca108b0 Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 24 Aug 2022 11:08:55 +0200 Subject: [PATCH 37/92] Fix style --- qiskit/algorithms/fidelities/__init__.py | 4 ++- qiskit/algorithms/fidelities/base_fidelity.py | 8 ++++- qiskit/algorithms/fidelities/fidelity.py | 35 ++++++++++++------- .../algorithms/fidelities/test_fidelity.py | 13 ++++++- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/qiskit/algorithms/fidelities/__init__.py b/qiskit/algorithms/fidelities/__init__.py index 55d73df11112..abb1482dcc2f 100644 --- a/qiskit/algorithms/fidelities/__init__.py +++ b/qiskit/algorithms/fidelities/__init__.py @@ -11,9 +11,10 @@ # that they have been altered from the originals. """ ===================================== -Primitive-based Fidelity Interfaces (:mod:`qiskit.algorithms.fidelities`) +Fidelity Interfaces (:mod:`qiskit.algorithms.fidelities`) ===================================== .. currentmodule:: qiskit.algorithms.fidelities + Fidelity ========= .. autosummary:: @@ -21,6 +22,7 @@ BaseFidelity Fidelity """ + from .base_fidelity import BaseFidelity from .fidelity import Fidelity diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index fe622ac92a2e..4157776189b9 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -243,5 +243,11 @@ def evaluate( parametrized circuits (first and second) for a specific set of parameter values (first and second). This calculation depends on the particular fidelity method implementation. + Args: + circuits_1: (Parametrized) quantum circuits preparing one set of states + circuits_2: (Parametrized) quantum circuits preparing another set of states + values_1: Numerical parameters to be bound to the first circuits + values_2: Numerical parameters to be bound to the second circuits. + run_options: Backend runtime options used for circuit execution. """ - return NotImplementedError + ... diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index a4dd95d2c6f5..74d54b4f7c38 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -29,7 +29,8 @@ class Fidelity(BaseFidelity): """ def __init__(self, sampler: Sampler, **run_options) -> None: - """Initializes the class to evaluate the fidelities defined as the state overlap + r""" + Initializes the class to evaluate the fidelities defined as the state overlap :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, @@ -39,13 +40,21 @@ def __init__(self, sampler: Sampler, **run_options) -> None: Args: sampler: Sampler primitive instance. run_options: Backend runtime options used for circuit execution. - """ self._sampler = sampler self._default_run_options = run_options super().__init__() - def _create_fidelity_circuit(self, circuit_1, circuit_2): + def _create_fidelity_circuit(self, circuit_1, circuit_2) -> QuantumCircuit: + """ + Creates fidelity circuit following the compute-uncompute method. + Args: + circuit_1: (Parametrized) quantum circuit + circuit_2: (Parametrized) quantum circuit + + Returns: + The fidelity quantum circuit corresponding to circuit_1 and circuit_2. + """ circuit = circuit_1.compose(circuit_2.inverse()) circuit.measure_all() return circuit @@ -58,14 +67,15 @@ def evaluate( values_2: Sequence[Sequence[float]] | None = None, **run_options, ) -> list[float]: - """Compute the state overlap (fidelity) calculation between 2 + r""" + Compute the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter values (left and right) following the compute-uncompute method, where the fidelity corresponds to: :math:`|\langle\psi(x)|\phi(y)\rangle|^2` - Args: + Args: circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. values_1: Numerical parameters to be bound to the first circuits. @@ -76,18 +86,19 @@ def evaluate( The result of the fidelity calculation. Raises: - ValueError: At least one left and right circuit must be defined. + ValueError: At least one pair of circuits must be defined. """ self._preprocess_inputs(circuits_1, circuits_2, values_1, values_2) if len(self._circuits) == 0: raise ValueError( - "At least one pair of circuits must be defined to " "calculate the state overlap. " + "At least one pair of circuits must be defined to calculate the state overlap." ) # The priority of run options is as follows: - # run_options in `evaluate` method > fidelity's default run_options > primitive's default run_options. + # run_options in `evaluate` method > fidelity's default run_options > + # primitive's default run_options. run_options = run_options or self._default_run_options if len(self._parameter_values) > 0: @@ -113,11 +124,12 @@ def run( values_2: Sequence[Sequence[float]] | None = None, **run_options, ) -> PrimitiveJob: - """Run asynchronously the state overlap (fidelity) calculation between 2 + r""" + Run asynchronously the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter values (left and right). - Args: + Args: circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. values_1: Numerical parameters to be bound to the left circuits. @@ -126,9 +138,6 @@ def run( Returns: Primitive job for the fidelity calculation - - Raises: - ValueError: At least one left and right circuit must be defined. """ job = PrimitiveJob(self.evaluate, circuits_1, circuits_2, values_1, values_2, **run_options) diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index 7d4e38e424ab..557209330049 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -106,7 +106,12 @@ def test_not_set_circuits(self): """test for fidelity with no circuits.""" fidelity = Fidelity(self._sampler) with self.assertRaises(TypeError): - _ = fidelity.evaluate(values_1=self._left_params, values_2=self._right_params) + _ = fidelity.evaluate( + circuits_1=None, + circuits_2=None, + values_1=self._left_params, + values_2=self._right_params, + ) def test_circuit_mismatch(self): """test for fidelity with different number of left/right circuits.""" @@ -121,6 +126,8 @@ def test_circuit_mismatch(self): ) def test_param_mismatch(self): + """test for fidelity with different number of left/right parameters.""" + fidelity = Fidelity(self._sampler) n = len(self._left_params) with self.assertRaises(QiskitError): @@ -143,6 +150,8 @@ def test_param_mismatch(self): _ = fidelity.evaluate([self._circuit[0]] * n, [self._circuit[1]] * n) def test_async_result(self): + """test for run method.""" + fidelity = Fidelity(self._sampler) n = len(self._left_params) job = fidelity.run( @@ -151,6 +160,8 @@ def test_async_result(self): np.testing.assert_allclose(job.result(), np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_async_join(self): + """test for run method using join.""" + fidelity = Fidelity(self._sampler) jobs = [] for left_param, right_param in zip(self._left_params, self._right_params): From 20f63b03dad369e72cf9ec795b80682008ff08d2 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Wed, 24 Aug 2022 14:36:32 +0200 Subject: [PATCH 38/92] Update qiskit/algorithms/fidelities/base_fidelity.py Co-authored-by: Declan Millar --- qiskit/algorithms/fidelities/base_fidelity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index 4157776189b9..dfcd5432b603 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -99,8 +99,8 @@ def _check_qubits_mismatch(self, circuit_1: QuantumCircuit, circuit_2: QuantumCi if circuit_1 is not None and circuit_2 is not None: if circuit_1.num_qubits != circuit_2.num_qubits: raise ValueError( - f"The number of qubits for the left circuit ({circuit_1.num_qubits}) \ - and right circuit ({circuit_2.num_qubits}) do not coincide." + f"The number of qubits for the left circuit ({circuit_1.num_qubits}) " + f"and right circuit ({circuit_2.num_qubits}) do not coincide." ) @abstractmethod From cb4fda17b7d2112ae1ec2341bbf7d194ad638d16 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Wed, 24 Aug 2022 14:36:41 +0200 Subject: [PATCH 39/92] Update qiskit/algorithms/fidelities/base_fidelity.py Co-authored-by: Declan Millar --- qiskit/algorithms/fidelities/base_fidelity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index dfcd5432b603..deed64ebe27d 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -139,8 +139,8 @@ def _set_circuits( if not len(circuits_1) == len(circuits_2): raise ValueError( - f"The length of the first circuit list({len(circuits_1)}) \ - and second circuit list ({len(circuits_2)}) does not coincide." + f"The length of the first circuit list({len(circuits_1)}) " + f"and second circuit list ({len(circuits_2)}) does not coincide." ) circuits = [] From 1ab8025f94a625050a9e1e2c15ef526a9405e2cd Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 24 Aug 2022 17:32:21 +0200 Subject: [PATCH 40/92] Make async --- qiskit/algorithms/fidelities/base_fidelity.py | 8 ++-- qiskit/algorithms/fidelities/fidelity.py | 4 +- .../algorithms/fidelities/test_fidelity.py | 46 ++++++++++--------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/fidelities/base_fidelity.py index 4157776189b9..3f346b3d1fca 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/fidelities/base_fidelity.py @@ -85,7 +85,7 @@ def _check_values_shape( values = [values] return values - def _check_qubits_mismatch(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit) -> None: + def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit) -> None: """ Checks that the number of qubits of 2 circuits matches. Args: @@ -140,7 +140,7 @@ def _set_circuits( if not len(circuits_1) == len(circuits_2): raise ValueError( f"The length of the first circuit list({len(circuits_1)}) \ - and second circuit list ({len(circuits_2)}) does not coincide." + and second circuit list ({len(circuits_2)}) do not coincide." ) circuits = [] @@ -151,7 +151,7 @@ def _set_circuits( if circuit is not None: circuits.append(circuit) else: - self._check_qubits_mismatch(circuit_1, circuit_2) + self._check_qubits_match(circuit_1, circuit_2) # re-parametrize input circuits left_parameters = ParameterVector("x", circuit_1.num_parameters) @@ -231,7 +231,7 @@ def _preprocess_inputs( self._set_values(values_1, values_2) @abstractmethod - def evaluate( + def _run( self, circuits_1: Sequence[QuantumCircuit], circuits_2: Sequence[QuantumCircuit], diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index 74d54b4f7c38..54821af95e0e 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -59,7 +59,7 @@ def _create_fidelity_circuit(self, circuit_1, circuit_2) -> QuantumCircuit: circuit.measure_all() return circuit - def evaluate( + def _run( self, circuits_1: Sequence[QuantumCircuit], circuits_2: Sequence[QuantumCircuit], @@ -140,7 +140,7 @@ def run( Primitive job for the fidelity calculation """ - job = PrimitiveJob(self.evaluate, circuits_1, circuits_2, values_1, values_2, **run_options) + job = PrimitiveJob(self._run, circuits_1, circuits_2, values_1, values_2, **run_options) job.submit() return job diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index 557209330049..32a7dd6f54ae 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -52,78 +52,87 @@ def setUp(self): def test_1param_pair(self): """test for fidelity with one pair of parameters""" fidelity = Fidelity(self._sampler) - result = fidelity.evaluate( + 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, np.array([1.0])) def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" fidelity = Fidelity(self._sampler) n = len(self._left_params) - results = fidelity.evaluate( + job = fidelity.run( [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params ) + results = job.result() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_symmetry(self): """test for fidelity with the same circuit""" fidelity = Fidelity(self._sampler) n = len(self._left_params) - results_1 = fidelity.evaluate( + job_1 = fidelity.run( [self._circuit[0]] * n, [self._circuit[0]] * n, self._left_params, self._right_params ) - results_2 = fidelity.evaluate( + job_2 = fidelity.run( [self._circuit[0]] * n, [self._circuit[0]] * n, self._right_params, self._left_params ) + results_1 = job_1.result() + results_2 = job_2.result() np.testing.assert_allclose(results_1, results_2, atol=1e-16) def test_no_params(self): """test for fidelity without parameters""" fidelity = Fidelity(self._sampler) - results = fidelity.evaluate([self._circuit[2]], [self._circuit[3]]) + job = fidelity.run([self._circuit[2]], [self._circuit[3]]) + results = job.result() np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) def test_left_param(self): """test for fidelity with only left parameters""" fidelity = Fidelity(self._sampler) n = len(self._left_params) - results = fidelity.evaluate( + job = fidelity.run( [self._circuit[1]] * n, [self._circuit[3]] * n, values_1=self._left_params ) + results = job.result() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_right_param(self): """test for fidelity with only right parameters""" fidelity = Fidelity(self._sampler) n = len(self._left_params) - results = fidelity.evaluate( + job = fidelity.run( [self._circuit[3]] * n, [self._circuit[1]] * n, values_2=self._left_params ) + results = job.result() np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_not_set_circuits(self): """test for fidelity with no circuits.""" fidelity = Fidelity(self._sampler) with self.assertRaises(TypeError): - _ = fidelity.evaluate( + job = fidelity.run( circuits_1=None, circuits_2=None, values_1=self._left_params, values_2=self._right_params, ) + job.result() def test_circuit_mismatch(self): """test for fidelity with different number of left/right circuits.""" fidelity = Fidelity(self._sampler) n = len(self._left_params) with self.assertRaises(ValueError): - _ = fidelity.evaluate( + job = fidelity.run( [self._circuit[0]] * n, [self._circuit[1]] * (n + 1), self._left_params, self._right_params, ) + job.result() def test_param_mismatch(self): """test for fidelity with different number of left/right parameters.""" @@ -131,33 +140,26 @@ def test_param_mismatch(self): fidelity = Fidelity(self._sampler) n = len(self._left_params) with self.assertRaises(QiskitError): - _ = fidelity.evaluate( + job = fidelity.run( [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params[:-2], ) + job.result() with self.assertRaises(QiskitError): - _ = fidelity.evaluate( + job = fidelity.run( [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params[:-2], self._right_params[:-2], ) + job.result() with self.assertRaises(ValueError): - _ = fidelity.evaluate([self._circuit[0]] * n, [self._circuit[1]] * n) - - def test_async_result(self): - """test for run method.""" - - fidelity = Fidelity(self._sampler) - n = len(self._left_params) - job = fidelity.run( - [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params - ) - np.testing.assert_allclose(job.result(), np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + job = fidelity.run([self._circuit[0]] * n, [self._circuit[1]] * n) + job.result() def test_async_join(self): """test for run method using join.""" From 925c7533d636bd38ecb421fafb598be48a6853e1 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 25 Aug 2022 15:10:01 +0200 Subject: [PATCH 41/92] Add FidelityResult --- qiskit/algorithms/fidelities/__init__.py | 9 +++++- qiskit/algorithms/fidelities/fidelity.py | 6 ++-- .../algorithms/fidelities/fidelity_result.py | 32 +++++++++++++++++++ .../algorithms/fidelities/test_fidelity.py | 14 ++++---- 4 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 qiskit/algorithms/fidelities/fidelity_result.py diff --git a/qiskit/algorithms/fidelities/__init__.py b/qiskit/algorithms/fidelities/__init__.py index abb1482dcc2f..3d57c4c2cd50 100644 --- a/qiskit/algorithms/fidelities/__init__.py +++ b/qiskit/algorithms/fidelities/__init__.py @@ -21,9 +21,16 @@ :toctree: ../stubs/ BaseFidelity Fidelity + +Results +======= + .. autosummary:: + :toctree: ../stubs/ + Fidelity """ from .base_fidelity import BaseFidelity from .fidelity import Fidelity +from .fidelity_result import FidelityResult -__all__ = ["BaseFidelity", "Fidelity"] +__all__ = ["BaseFidelity", "Fidelity", "FidelityResult"] diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/fidelities/fidelity.py index 54821af95e0e..e69fdc179552 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/fidelities/fidelity.py @@ -20,7 +20,7 @@ from qiskit.primitives import Sampler from qiskit.primitives.primitive_job import PrimitiveJob from .base_fidelity import BaseFidelity - +from .fidelity_result import FidelityResult class Fidelity(BaseFidelity): """ @@ -66,7 +66,7 @@ def _run( values_1: Sequence[Sequence[float]] | None = None, values_2: Sequence[Sequence[float]] | None = None, **run_options, - ) -> list[float]: + ) -> FidelityResult: r""" Compute the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter @@ -114,7 +114,7 @@ def _run( # negative values in some way (e.g. clipping to zero) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] - return overlaps + return FidelityResult(values=overlaps, metadata=run_options) def run( self, diff --git a/qiskit/algorithms/fidelities/fidelity_result.py b/qiskit/algorithms/fidelities/fidelity_result.py new file mode 100644 index 000000000000..05700a84a4c7 --- /dev/null +++ b/qiskit/algorithms/fidelities/fidelity_result.py @@ -0,0 +1,32 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Fidelity result class +""" + +from __future__ import annotations + +from typing import Any +from dataclasses import dataclass + + +@dataclass(frozen=True) +class FidelityResult: + """Result of Fidelity computation. + + Args: + values (list[float]): List of fidelity values for each pair of input circuits. + metadata: Additional information on the fidelity calculations. + """ + + values: list[float] + metadata: list[dict[str, Any]] diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/fidelities/test_fidelity.py index 32a7dd6f54ae..6fe36de0b990 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/fidelities/test_fidelity.py @@ -56,7 +56,7 @@ def test_1param_pair(self): [self._circuit[0]], [self._circuit[1]], self._left_params[0], self._right_params[0] ) result = job.result() - np.testing.assert_allclose(result, np.array([1.0])) + np.testing.assert_allclose(result.values, np.array([1.0])) def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" @@ -66,7 +66,7 @@ def test_4param_pairs(self): [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params ) results = job.result() - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + np.testing.assert_allclose(results.values, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_symmetry(self): """test for fidelity with the same circuit""" @@ -80,14 +80,14 @@ def test_symmetry(self): ) results_1 = job_1.result() results_2 = job_2.result() - np.testing.assert_allclose(results_1, results_2, atol=1e-16) + np.testing.assert_allclose(results_1.values, results_2.values, atol=1e-16) def test_no_params(self): """test for fidelity without parameters""" fidelity = Fidelity(self._sampler) job = fidelity.run([self._circuit[2]], [self._circuit[3]]) results = job.result() - np.testing.assert_allclose(results, np.array([0.25]), atol=1e-16) + np.testing.assert_allclose(results.values, np.array([0.25]), atol=1e-16) def test_left_param(self): """test for fidelity with only left parameters""" @@ -97,7 +97,7 @@ def test_left_param(self): [self._circuit[1]] * n, [self._circuit[3]] * n, values_1=self._left_params ) results = job.result() - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + np.testing.assert_allclose(results.values, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_right_param(self): """test for fidelity with only right parameters""" @@ -107,7 +107,7 @@ def test_right_param(self): [self._circuit[3]] * n, [self._circuit[1]] * n, values_2=self._left_params ) results = job.result() - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + np.testing.assert_allclose(results.values, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_not_set_circuits(self): """test for fidelity with no circuits.""" @@ -170,7 +170,7 @@ def test_async_join(self): job = fidelity.run([self._circuit[0]], [self._circuit[1]], left_param, right_param) jobs.append(job) - results = [job.result()[0] for job in jobs] + results = [job.result().values[0] for job in jobs] np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) From f5d574573a2759ebf66fb037d9e362443065e8c1 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Fri, 26 Aug 2022 15:51:47 +0200 Subject: [PATCH 42/92] Update qiskit/algorithms/fidelities/__init__.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- qiskit/algorithms/fidelities/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/fidelities/__init__.py b/qiskit/algorithms/fidelities/__init__.py index 3d57c4c2cd50..6ce4cf43eaf7 100644 --- a/qiskit/algorithms/fidelities/__init__.py +++ b/qiskit/algorithms/fidelities/__init__.py @@ -26,7 +26,7 @@ ======= .. autosummary:: :toctree: ../stubs/ - Fidelity + FidelityResult """ from .base_fidelity import BaseFidelity From a19450ee405ae17793b2fb3400eb6261d57d0c38 Mon Sep 17 00:00:00 2001 From: ElePT Date: Fri, 26 Aug 2022 15:56:56 +0200 Subject: [PATCH 43/92] Add to algorithms init --- qiskit/algorithms/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index 30d71a59e59c..7ac747bb86a9 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -200,6 +200,15 @@ PhaseEstimationResult IterativePhaseEstimation +State Fidelities +---------- + +Algorithms that compute the fidelity of pairs of quantum staties. + +.. autosummary:: + :toctree: ../stubs/ + + state_fidelities Exceptions ========== From dfc09f236f96ed8ead314de245795d4381f9fd5d Mon Sep 17 00:00:00 2001 From: ElePT Date: Fri, 26 Aug 2022 16:08:16 +0200 Subject: [PATCH 44/92] Rename classes --- .../__init__.py | 20 +++++++------- .../base_state_fidelity.py} | 6 ++--- .../compute_uncompute.py} | 12 ++++----- .../state_fidelity_result.py} | 2 +- .../__init__.py | 0 .../test_compute_uncompute.py} | 26 +++++++++---------- 6 files changed, 33 insertions(+), 33 deletions(-) rename qiskit/algorithms/{fidelities => state_fidelities}/__init__.py (60%) rename qiskit/algorithms/{fidelities/base_fidelity.py => state_fidelities/base_state_fidelity.py} (98%) rename qiskit/algorithms/{fidelities/fidelity.py => state_fidelities/compute_uncompute.py} (93%) rename qiskit/algorithms/{fidelities/fidelity_result.py => state_fidelities/state_fidelity_result.py} (97%) rename test/python/algorithms/{fidelities => state_fidelities}/__init__.py (100%) rename test/python/algorithms/{fidelities/test_fidelity.py => state_fidelities/test_compute_uncompute.py} (89%) diff --git a/qiskit/algorithms/fidelities/__init__.py b/qiskit/algorithms/state_fidelities/__init__.py similarity index 60% rename from qiskit/algorithms/fidelities/__init__.py rename to qiskit/algorithms/state_fidelities/__init__.py index 3d57c4c2cd50..c9542ecd0310 100644 --- a/qiskit/algorithms/fidelities/__init__.py +++ b/qiskit/algorithms/state_fidelities/__init__.py @@ -11,26 +11,26 @@ # that they have been altered from the originals. """ ===================================== -Fidelity Interfaces (:mod:`qiskit.algorithms.fidelities`) +State Fidelity Interfaces (:mod:`qiskit.algorithms.state_fidelities`) ===================================== -.. currentmodule:: qiskit.algorithms.fidelities +.. currentmodule:: qiskit.algorithms.state_fidelities -Fidelity +State Fidelities ========= .. autosummary:: :toctree: ../stubs/ - BaseFidelity - Fidelity + BaseStateFidelity + ComputeUncompute Results ======= .. autosummary:: :toctree: ../stubs/ - Fidelity + StateFidelityResult """ -from .base_fidelity import BaseFidelity -from .fidelity import Fidelity -from .fidelity_result import FidelityResult +from .base_state_fidelity import BaseStateFidelity +from .compute_uncompute import ComputeUncompute +from .state_fidelity_result import StateFidelityResult -__all__ = ["BaseFidelity", "Fidelity", "FidelityResult"] +__all__ = ["BaseStateFidelity", "ComputeUncompute", "StateFidelityResult"] diff --git a/qiskit/algorithms/fidelities/base_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py similarity index 98% rename from qiskit/algorithms/fidelities/base_fidelity.py rename to qiskit/algorithms/state_fidelities/base_state_fidelity.py index 2ff2d911b9d6..27adadb1bc16 100644 --- a/qiskit/algorithms/fidelities/base_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -22,16 +22,16 @@ from qiskit.circuit import ParameterVector -class BaseFidelity(ABC): +class BaseStateFidelity(ABC): """ - An interface to calculate fidelities (state overlaps) for pairs of + An interface to calculate state_fidelities (state overlaps) for pairs of (parametrized) quantum circuits. """ def __init__( self, ) -> None: - """Initializes the class to evaluate fidelities.""" + """Initializes the class to evaluate state_fidelities.""" self._circuits: Sequence[QuantumCircuit] = [] self._parameter_values: Sequence[Sequence[float]] = [] diff --git a/qiskit/algorithms/fidelities/fidelity.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py similarity index 93% rename from qiskit/algorithms/fidelities/fidelity.py rename to qiskit/algorithms/state_fidelities/compute_uncompute.py index e69fdc179552..dedc9e0c931a 100644 --- a/qiskit/algorithms/fidelities/fidelity.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -19,10 +19,10 @@ from qiskit import QuantumCircuit from qiskit.primitives import Sampler from qiskit.primitives.primitive_job import PrimitiveJob -from .base_fidelity import BaseFidelity -from .fidelity_result import FidelityResult +from .base_state_fidelity import BaseStateFidelity +from .state_fidelity_result import StateFidelityResult -class Fidelity(BaseFidelity): +class ComputeUncompute(BaseStateFidelity): """ This class leverages the sampler primitive to calculate the fidelity of two quantum circuits with the compute-uncompute method. @@ -30,7 +30,7 @@ class Fidelity(BaseFidelity): def __init__(self, sampler: Sampler, **run_options) -> None: r""" - Initializes the class to evaluate the fidelities defined as the state overlap + Initializes the class to evaluate the state_fidelities defined as the state overlap :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, @@ -66,7 +66,7 @@ def _run( values_1: Sequence[Sequence[float]] | None = None, values_2: Sequence[Sequence[float]] | None = None, **run_options, - ) -> FidelityResult: + ) -> StateFidelityResult: r""" Compute the state overlap (fidelity) calculation between 2 parametrized circuits (left and right) for a specific set of parameter @@ -114,7 +114,7 @@ def _run( # negative values in some way (e.g. clipping to zero) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] - return FidelityResult(values=overlaps, metadata=run_options) + return StateFidelityResult(values=overlaps, metadata=run_options) def run( self, diff --git a/qiskit/algorithms/fidelities/fidelity_result.py b/qiskit/algorithms/state_fidelities/state_fidelity_result.py similarity index 97% rename from qiskit/algorithms/fidelities/fidelity_result.py rename to qiskit/algorithms/state_fidelities/state_fidelity_result.py index 05700a84a4c7..6a6d3b9e9b5b 100644 --- a/qiskit/algorithms/fidelities/fidelity_result.py +++ b/qiskit/algorithms/state_fidelities/state_fidelity_result.py @@ -20,7 +20,7 @@ @dataclass(frozen=True) -class FidelityResult: +class StateFidelityResult: """Result of Fidelity computation. Args: diff --git a/test/python/algorithms/fidelities/__init__.py b/test/python/algorithms/state_fidelities/__init__.py similarity index 100% rename from test/python/algorithms/fidelities/__init__.py rename to test/python/algorithms/state_fidelities/__init__.py diff --git a/test/python/algorithms/fidelities/test_fidelity.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py similarity index 89% rename from test/python/algorithms/fidelities/test_fidelity.py rename to test/python/algorithms/state_fidelities/test_compute_uncompute.py index 6fe36de0b990..83858f7ee57d 100644 --- a/test/python/algorithms/fidelities/test_fidelity.py +++ b/test/python/algorithms/state_fidelities/test_compute_uncompute.py @@ -18,13 +18,13 @@ from qiskit.circuit import QuantumCircuit, ParameterVector from qiskit.primitives import Sampler -from qiskit.algorithms.fidelities import Fidelity +from qiskit.algorithms.state_fidelities import ComputeUncompute from qiskit.test import QiskitTestCase from qiskit import QiskitError -class TestFidelity(QiskitTestCase): - """Test Fidelity""" +class TestComputeUncompute(QiskitTestCase): + """Test Compute-Uncompute Fidelity class""" def setUp(self): super().setUp() @@ -51,7 +51,7 @@ def setUp(self): def test_1param_pair(self): """test for fidelity with one pair of parameters""" - fidelity = Fidelity(self._sampler) + fidelity = ComputeUncompute(self._sampler) job = fidelity.run( [self._circuit[0]], [self._circuit[1]], self._left_params[0], self._right_params[0] ) @@ -60,7 +60,7 @@ def test_1param_pair(self): def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" - fidelity = Fidelity(self._sampler) + 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 @@ -70,7 +70,7 @@ def test_4param_pairs(self): def test_symmetry(self): """test for fidelity with the same circuit""" - fidelity = Fidelity(self._sampler) + 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 @@ -84,14 +84,14 @@ def test_symmetry(self): def test_no_params(self): """test for fidelity without parameters""" - fidelity = Fidelity(self._sampler) + fidelity = ComputeUncompute(self._sampler) job = fidelity.run([self._circuit[2]], [self._circuit[3]]) results = job.result() np.testing.assert_allclose(results.values, np.array([0.25]), atol=1e-16) def test_left_param(self): """test for fidelity with only left parameters""" - fidelity = Fidelity(self._sampler) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) job = fidelity.run( [self._circuit[1]] * n, [self._circuit[3]] * n, values_1=self._left_params @@ -101,7 +101,7 @@ def test_left_param(self): def test_right_param(self): """test for fidelity with only right parameters""" - fidelity = Fidelity(self._sampler) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) job = fidelity.run( [self._circuit[3]] * n, [self._circuit[1]] * n, values_2=self._left_params @@ -111,7 +111,7 @@ def test_right_param(self): def test_not_set_circuits(self): """test for fidelity with no circuits.""" - fidelity = Fidelity(self._sampler) + fidelity = ComputeUncompute(self._sampler) with self.assertRaises(TypeError): job = fidelity.run( circuits_1=None, @@ -123,7 +123,7 @@ def test_not_set_circuits(self): def test_circuit_mismatch(self): """test for fidelity with different number of left/right circuits.""" - fidelity = Fidelity(self._sampler) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) with self.assertRaises(ValueError): job = fidelity.run( @@ -137,7 +137,7 @@ def test_circuit_mismatch(self): def test_param_mismatch(self): """test for fidelity with different number of left/right parameters.""" - fidelity = Fidelity(self._sampler) + fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) with self.assertRaises(QiskitError): job = fidelity.run( @@ -164,7 +164,7 @@ def test_param_mismatch(self): def test_async_join(self): """test for run method using join.""" - fidelity = Fidelity(self._sampler) + fidelity = ComputeUncompute(self._sampler) jobs = [] for left_param, right_param in zip(self._left_params, self._right_params): job = fidelity.run([self._circuit[0]], [self._circuit[1]], left_param, right_param) From d563479e5a49720444fcff466791f38c1f969726 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 11:58:53 +0200 Subject: [PATCH 45/92] Apply review comments --- .../state_fidelities/base_state_fidelity.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 27adadb1bc16..b104c2c8464d 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -42,12 +42,12 @@ def __init__( self._left_parameters = [] self._right_parameters = [] - def _check_values_shape( - self, - values: Sequence[Sequence[float]] | None, + @staticmethod + def _preprocess_values( circuits: QuantumCircuit, - label: str | None = " ", - ) -> list[list[float]] | None: + values: Sequence[Sequence[float]] | None = None, + label: str = " ", + ) -> Sequence[Sequence[float]] | None: """ Checks whether the passed values match the shape of the parameters of the corresponding circuits and formats values to 2D list. @@ -96,12 +96,11 @@ def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircu ValueError: when ``circuit_1`` and ``circuit_2`` don't have the same number of qubits. """ - if circuit_1 is not None and circuit_2 is not None: - if circuit_1.num_qubits != circuit_2.num_qubits: - raise ValueError( - f"The number of qubits for the left circuit ({circuit_1.num_qubits}) " - f"and right circuit ({circuit_2.num_qubits}) do not coincide." - ) + if circuit_1.num_qubits != circuit_2.num_qubits: + raise ValueError( + f"The number of qubits for the left circuit ({circuit_1.num_qubits}) " + f"and right circuit ({circuit_2.num_qubits}) do not coincide." + ) @abstractmethod def _create_fidelity_circuit( @@ -121,8 +120,8 @@ def _create_fidelity_circuit( def _set_circuits( self, - circuits_1: Sequence[QuantumCircuit] | None = None, - circuits_2: Sequence[QuantumCircuit] | None = None, + circuits_1: Sequence[QuantumCircuit], + circuits_2: Sequence[QuantumCircuit], ) -> None: """ Update the list of fidelity circuits to be evaluated. @@ -173,7 +172,9 @@ def _set_circuits( self._circuits = circuits def _set_values( - self, values_1: Sequence[Sequence[float]] | None, values_2: Sequence[Sequence[float]] | None + self, + values_1: Sequence[Sequence[float]] | None = None, + values_2: Sequence[Sequence[float]] | None = None, ) -> None: """ Update the list of parameter values to evaluate the corresponding @@ -225,8 +226,8 @@ def _preprocess_inputs( self._set_circuits(circuits_1, circuits_2) - values_1 = self._check_values_shape(values_1, circuits_1, "1") - values_2 = self._check_values_shape(values_2, circuits_2, "2") + values_1 = self._preprocess_values(values_1, circuits_1, "1") + values_2 = self._preprocess_values(values_2, circuits_2, "2") self._set_values(values_1, values_2) @@ -238,7 +239,7 @@ def _run( values_1: Sequence[Sequence[float]] | None = None, values_2: Sequence[Sequence[float]] | None = None, **run_options, - ) -> list[float]: + ) -> Sequence[float]: """Compute the state overlap (fidelity) calculation between 2 parametrized circuits (first and second) for a specific set of parameter values (first and second). This calculation depends on the particular From 7cafe1282443f2332bedce4bc601e5b5cdaac17a Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 13:00:54 +0200 Subject: [PATCH 46/92] Update preprocessing --- .../state_fidelities/base_state_fidelity.py | 59 ++++++------------- .../state_fidelities/compute_uncompute.py | 11 ++-- 2 files changed, 23 insertions(+), 47 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index b104c2c8464d..b1f156927d0e 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -46,16 +46,14 @@ def __init__( def _preprocess_values( circuits: QuantumCircuit, values: Sequence[Sequence[float]] | None = None, - label: str = " ", ) -> Sequence[Sequence[float]] | None: """ Checks whether the passed values match the shape of the parameters of the corresponding circuits and formats values to 2D list. Args: - values: parameter values corresponding to the circuits to be checked circuits: list of circuits to be checked - label: optional label to allow for circuit identification in error message + values: parameter values corresponding to the circuits to be checked Returns: Returns a 2D list if values match, `None` if no parameters are passed @@ -73,7 +71,7 @@ def _preprocess_values( for circuit in circuits: if circuit.num_parameters != 0: raise ValueError( - f"`values_{label}` cannot be `None` because circuit_{label} has " + f"`values` cannot be `None` because circuit <{circuit.name}> has " f"{circuit.num_parameters} free parameters." ) return None @@ -118,7 +116,7 @@ def _create_fidelity_circuit( """ raise NotImplementedError - def _set_circuits( + def _construct_circuits( self, circuits_1: Sequence[QuantumCircuit], circuits_2: Sequence[QuantumCircuit], @@ -168,26 +166,28 @@ def _set_circuits( # update cache self._circuit_cache[id(circuit_1), id(circuit_2)] = circuit - # set circuits - self._circuits = circuits + return circuits - def _set_values( + def _construct_value_list( self, + circuits_1: Sequence[QuantumCircuit], + circuits_2: Sequence[QuantumCircuit], values_1: Sequence[Sequence[float]] | None = None, values_2: Sequence[Sequence[float]] | None = None, - ) -> None: - """ - Update the list of parameter values to evaluate the corresponding - fidelity circuits with. + ) -> Sequence[Sequence[float]]: + """Preprocess input parameter values and return in list format. Args: - values_1: Numerical parameters to be bound to the first circuits. - values_2: Numerical parameters to be bound to the second circuits. + circuits_1: (Parametrized) quantum circuits preparing the first list of quantum states. + circuits_2: (Parametrized) quantum circuits preparing the second list of quantum states. + values_1: Numerical parameters to be bound to the first circuits. + values_2: Numerical parameters to be bound to the second circuits. - Raises: - ValueError: if the length of the input value lists doesn't match. """ + values_1 = self._preprocess_values(circuits_1, values_1) + values_2 = self._preprocess_values(circuits_2, values_2) + values = [] if values_2 is not None or values_1 is not None: if values_2 is None: @@ -204,32 +204,7 @@ def _set_values( ) values.append(val_1 + val_2) - # set values - self._parameter_values = values - - def _preprocess_inputs( - self, - circuits_1: Sequence[QuantumCircuit], - circuits_2: Sequence[QuantumCircuit], - values_1: Sequence[Sequence[float]] | None = None, - values_2: Sequence[Sequence[float]] | None = None, - ) -> None: - """Preprocess input circuits and parameter values and update corresponding lists. - - Args: - circuits_1: (Parametrized) quantum circuits preparing the first list of quantum states. - circuits_2: (Parametrized) quantum circuits preparing the second list of quantum states. - values_1: Numerical parameters to be bound to the first circuits. - values_2: Numerical parameters to be bound to the second circuits. - - """ - - self._set_circuits(circuits_1, circuits_2) - - values_1 = self._preprocess_values(values_1, circuits_1, "1") - values_2 = self._preprocess_values(values_2, circuits_2, "2") - - self._set_values(values_1, values_2) + return values @abstractmethod def _run( diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index dedc9e0c931a..26bce03886c8 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -89,9 +89,10 @@ def _run( ValueError: At least one pair of circuits must be defined. """ - self._preprocess_inputs(circuits_1, circuits_2, values_1, values_2) + circuits = self._construct_circuits(circuits_1, circuits_2) + values = self._construct_value_list(circuits_1, circuits_2, values_1, values_2) - if len(self._circuits) == 0: + if len(circuits) == 0: raise ValueError( "At least one pair of circuits must be defined to calculate the state overlap." ) @@ -101,12 +102,12 @@ def _run( # primitive's default run_options. run_options = run_options or self._default_run_options - if len(self._parameter_values) > 0: + if len(values) > 0: job = self._sampler.run( - circuits=self._circuits, parameter_values=self._parameter_values, **run_options + circuits=circuits, parameter_values=values, **run_options ) else: - job = self._sampler.run(circuits=self._circuits, **run_options) + job = self._sampler.run(circuits=circuits, **run_options) result = job.result() From e97b551f5dc11986ebe7a81652ae530d43f98d95 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 14:47:56 +0200 Subject: [PATCH 47/92] Update docstrings --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 4 ++-- qiskit/algorithms/state_fidelities/compute_uncompute.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index b1f156927d0e..f4c14b09ccfc 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -122,7 +122,7 @@ def _construct_circuits( circuits_2: Sequence[QuantumCircuit], ) -> None: """ - Update the list of fidelity circuits to be evaluated. + Construct the list of fidelity circuits to be evaluated. These circuits represent the state overlap between pairs of input circuits, and their construction depends on the fidelity method implementations. @@ -216,7 +216,7 @@ def _run( **run_options, ) -> Sequence[float]: """Compute the state overlap (fidelity) calculation between 2 - parametrized circuits (first and second) for a specific set of parameter + (parametrized) circuits (first and second) for a specific set of parameter values (first and second). This calculation depends on the particular fidelity method implementation. Args: diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 26bce03886c8..eb684dc54900 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -69,7 +69,7 @@ def _run( ) -> StateFidelityResult: r""" Compute the state overlap (fidelity) calculation between 2 - parametrized circuits (left and right) for a specific set of parameter + (parametrized) circuits (left and right) for a specific set of parameter values (left and right) following the compute-uncompute method, where the fidelity corresponds to: @@ -127,7 +127,7 @@ def run( ) -> PrimitiveJob: r""" Run asynchronously the state overlap (fidelity) calculation between 2 - parametrized circuits (left and right) for a specific set of parameter + (parametrized) circuits (left and right) for a specific set of parameter values (left and right). Args: From 06a35ff684db71c5ec5969ebe58af76955f657a6 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 14:49:05 +0200 Subject: [PATCH 48/92] Move run to base class --- .../state_fidelities/base_state_fidelity.py | 30 +++++++++++++++++++ .../state_fidelities/compute_uncompute.py | 30 ------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index f4c14b09ccfc..888c8f0282e4 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -20,6 +20,7 @@ from qiskit import QuantumCircuit from qiskit.circuit import ParameterVector +from qiskit.primitives.primitive_job import PrimitiveJob class BaseStateFidelity(ABC): @@ -227,3 +228,32 @@ def _run( run_options: Backend runtime options used for circuit execution. """ ... + + def run( + self, + circuits_1: Sequence[QuantumCircuit], + circuits_2: Sequence[QuantumCircuit], + values_1: Sequence[Sequence[float]] | None = None, + values_2: Sequence[Sequence[float]] | None = None, + **run_options, + ) -> PrimitiveJob: + r""" + Run asynchronously the state overlap (fidelity) calculation between 2 + (parametrized) circuits (left and right) for a specific set of parameter + values (left and right). + + Args: + circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. + circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. + values_1: Numerical parameters to be bound to the left circuits. + values_2: Numerical parameters to be bound to the right circuits. + run_options: Backend runtime options used for circuit execution. + + Returns: + Primitive job for the fidelity calculation + """ + + job = PrimitiveJob(self._run, circuits_1, circuits_2, values_1, values_2, **run_options) + + job.submit() + return job diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index eb684dc54900..35507e29e867 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -18,7 +18,6 @@ from qiskit import QuantumCircuit from qiskit.primitives import Sampler -from qiskit.primitives.primitive_job import PrimitiveJob from .base_state_fidelity import BaseStateFidelity from .state_fidelity_result import StateFidelityResult @@ -116,32 +115,3 @@ def _run( overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] return StateFidelityResult(values=overlaps, metadata=run_options) - - def run( - self, - circuits_1: Sequence[QuantumCircuit], - circuits_2: Sequence[QuantumCircuit], - values_1: Sequence[Sequence[float]] | None = None, - values_2: Sequence[Sequence[float]] | None = None, - **run_options, - ) -> PrimitiveJob: - r""" - Run asynchronously the state overlap (fidelity) calculation between 2 - (parametrized) circuits (left and right) for a specific set of parameter - values (left and right). - - Args: - circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. - circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - values_1: Numerical parameters to be bound to the left circuits. - values_2: Numerical parameters to be bound to the right circuits. - run_options: Backend runtime options used for circuit execution. - - Returns: - Primitive job for the fidelity calculation - """ - - job = PrimitiveJob(self._run, circuits_1, circuits_2, values_1, values_2, **run_options) - - job.submit() - return job From 1dfdd25e3dfef2c907780cd88b054fc7a3ac88ea Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 14:50:05 +0200 Subject: [PATCH 49/92] Remove left/right params --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 888c8f0282e4..7578036f2bc3 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -40,9 +40,6 @@ def __init__( # use cache for preventing unnecessary circuit compositions self._circuit_cache: Mapping[(int, int), QuantumCircuit] = {} - self._left_parameters = [] - self._right_parameters = [] - @staticmethod def _preprocess_values( circuits: QuantumCircuit, @@ -153,11 +150,9 @@ def _construct_circuits( # re-parametrize input circuits left_parameters = ParameterVector("x", circuit_1.num_parameters) - self._left_parameters.append(left_parameters) parametrized_circuit_1 = circuit_1.assign_parameters(left_parameters) right_parameters = ParameterVector("y", circuit_2.num_parameters) - self._right_parameters.append(right_parameters) parametrized_circuit_2 = circuit_2.assign_parameters(right_parameters) circuit = self._create_fidelity_circuit( From 6f89e6b7b03cae92eac37580f2d24e94154d7701 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 16:12:45 +0200 Subject: [PATCH 50/92] Apply reviews steve/julien/hamamura --- .../state_fidelities/base_state_fidelity.py | 30 ++++++++++++------- .../state_fidelities/compute_uncompute.py | 16 +++++----- .../state_fidelities/state_fidelity_result.py | 7 +++-- .../test_compute_uncompute.py | 14 ++++----- 4 files changed, 39 insertions(+), 28 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 7578036f2bc3..f55487065cee 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -15,13 +15,13 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Sequence, Mapping +from collections.abc import Sequence, Mapping import numpy as np from qiskit import QuantumCircuit from qiskit.circuit import ParameterVector from qiskit.primitives.primitive_job import PrimitiveJob - +from .state_fidelity_result import StateFidelityResult class BaseStateFidelity(ABC): """ @@ -54,7 +54,7 @@ def _preprocess_values( values: parameter values corresponding to the circuits to be checked Returns: - Returns a 2D list if values match, `None` if no parameters are passed + Returns a 2D list if values match, ``None`` if no parameters are passed Raises: ValueError: if the number of parameter values doesn't match the number of @@ -89,7 +89,8 @@ def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircu circuit_2: (Parametrized) quantum circuit Raises: - ValueError: when ``circuit_1`` and ``circuit_2`` don't have the same number of qubits. + ValueError: when ``circuit_1`` and ``circuit_2`` don't have the + same number of qubits. """ if circuit_1.num_qubits != circuit_2.num_qubits: @@ -110,7 +111,7 @@ def _create_fidelity_circuit( circuit_2: (Parametrized) quantum circuit Returns: - The fidelity quantum circuit corresponding to circuit_1 and circuit_2. + The fidelity quantum circuit corresponding to ``circuit_1`` and ``circuit_2``. """ raise NotImplementedError @@ -149,9 +150,9 @@ def _construct_circuits( self._check_qubits_match(circuit_1, circuit_2) # re-parametrize input circuits + # TODO: make smarter checks to avoid unnecesary reparametrizations left_parameters = ParameterVector("x", circuit_1.num_parameters) parametrized_circuit_1 = circuit_1.assign_parameters(left_parameters) - right_parameters = ParameterVector("y", circuit_2.num_parameters) parametrized_circuit_2 = circuit_2.assign_parameters(right_parameters) @@ -174,8 +175,10 @@ def _construct_value_list( """Preprocess input parameter values and return in list format. Args: - circuits_1: (Parametrized) quantum circuits preparing the first list of quantum states. - circuits_2: (Parametrized) quantum circuits preparing the second list of quantum states. + circuits_1: (Parametrized) quantum circuits preparing the + first list of quantum states. + circuits_2: (Parametrized) quantum circuits preparing the + second list of quantum states. values_1: Numerical parameters to be bound to the first circuits. values_2: Numerical parameters to be bound to the second circuits. @@ -210,7 +213,7 @@ def _run( values_1: Sequence[Sequence[float]] | None = None, values_2: Sequence[Sequence[float]] | None = None, **run_options, - ) -> Sequence[float]: + ) -> StateFidelityResult: """Compute the state overlap (fidelity) calculation between 2 (parametrized) circuits (first and second) for a specific set of parameter values (first and second). This calculation depends on the particular @@ -221,6 +224,9 @@ def _run( values_1: Numerical parameters to be bound to the first circuits values_2: Numerical parameters to be bound to the second circuits. run_options: Backend runtime options used for circuit execution. + + Returns: + The result of the fidelity calculation. """ ... @@ -245,10 +251,12 @@ def run( run_options: Backend runtime options used for circuit execution. Returns: - Primitive job for the fidelity calculation + Primitive job for the fidelity calculation. + The job's result is an instance of ``StateFidelityResult``. """ - job = PrimitiveJob(self._run, circuits_1, circuits_2, values_1, values_2, **run_options) + job = PrimitiveJob(self._run, circuits_1, circuits_2, + values_1, values_2, **run_options) job.submit() return job diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 35507e29e867..3d3845c0cb60 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -14,10 +14,11 @@ """ from __future__ import annotations -from typing import Sequence +from collections.abc import Sequence +from copy import copy from qiskit import QuantumCircuit -from qiskit.primitives import Sampler +from qiskit.primitives import BaseSampler from .base_state_fidelity import BaseStateFidelity from .state_fidelity_result import StateFidelityResult @@ -27,7 +28,7 @@ class ComputeUncompute(BaseStateFidelity): with the compute-uncompute method. """ - def __init__(self, sampler: Sampler, **run_options) -> None: + def __init__(self, sampler: BaseSampler, **run_options) -> None: r""" Initializes the class to evaluate the state_fidelities defined as the state overlap @@ -99,14 +100,15 @@ def _run( # The priority of run options is as follows: # run_options in `evaluate` method > fidelity's default run_options > # primitive's default run_options. - run_options = run_options or self._default_run_options + run_opts = copy(self._default_run_options) + run_opts.update(**run_options) if len(values) > 0: job = self._sampler.run( - circuits=circuits, parameter_values=values, **run_options + circuits=circuits, parameter_values=values, **run_opts ) else: - job = self._sampler.run(circuits=circuits, **run_options) + job = self._sampler.run(circuits=circuits, **run_opts) result = job.result() @@ -114,4 +116,4 @@ def _run( # negative values in some way (e.g. clipping to zero) overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] - return StateFidelityResult(values=overlaps, metadata=run_options) + return StateFidelityResult(fidelities=overlaps, metadata=run_opts) diff --git a/qiskit/algorithms/state_fidelities/state_fidelity_result.py b/qiskit/algorithms/state_fidelities/state_fidelity_result.py index 6a6d3b9e9b5b..35a307a8eb75 100644 --- a/qiskit/algorithms/state_fidelities/state_fidelity_result.py +++ b/qiskit/algorithms/state_fidelities/state_fidelity_result.py @@ -15,6 +15,7 @@ from __future__ import annotations +from collections.abc import Sequence, Mapping from typing import Any from dataclasses import dataclass @@ -24,9 +25,9 @@ class StateFidelityResult: """Result of Fidelity computation. Args: - values (list[float]): List of fidelity values for each pair of input circuits. + fidelities: List of fidelity values for each pair of input circuits. metadata: Additional information on the fidelity calculations. """ - values: list[float] - metadata: list[dict[str, Any]] + fidelities: Sequence[float] + metadata: Sequence[Mapping[str, Any]] diff --git a/test/python/algorithms/state_fidelities/test_compute_uncompute.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py index 83858f7ee57d..a3417dd62a0d 100644 --- a/test/python/algorithms/state_fidelities/test_compute_uncompute.py +++ b/test/python/algorithms/state_fidelities/test_compute_uncompute.py @@ -56,7 +56,7 @@ def test_1param_pair(self): [self._circuit[0]], [self._circuit[1]], self._left_params[0], self._right_params[0] ) result = job.result() - np.testing.assert_allclose(result.values, np.array([1.0])) + np.testing.assert_allclose(result.fidelities, np.array([1.0])) def test_4param_pairs(self): """test for fidelity with four pairs of parameters""" @@ -66,7 +66,7 @@ def test_4param_pairs(self): [self._circuit[0]] * n, [self._circuit[1]] * n, self._left_params, self._right_params ) results = job.result() - np.testing.assert_allclose(results.values, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) def test_symmetry(self): """test for fidelity with the same circuit""" @@ -80,14 +80,14 @@ def test_symmetry(self): ) results_1 = job_1.result() results_2 = job_2.result() - np.testing.assert_allclose(results_1.values, results_2.values, atol=1e-16) + np.testing.assert_allclose(results_1.fidelities, results_2.fidelities, atol=1e-16) def test_no_params(self): """test for fidelity without parameters""" fidelity = ComputeUncompute(self._sampler) job = fidelity.run([self._circuit[2]], [self._circuit[3]]) results = job.result() - np.testing.assert_allclose(results.values, np.array([0.25]), atol=1e-16) + np.testing.assert_allclose(results.fidelities, np.array([0.25]), atol=1e-16) def test_left_param(self): """test for fidelity with only left parameters""" @@ -97,7 +97,7 @@ def test_left_param(self): [self._circuit[1]] * n, [self._circuit[3]] * n, values_1=self._left_params ) results = job.result() - np.testing.assert_allclose(results.values, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_right_param(self): """test for fidelity with only right parameters""" @@ -107,7 +107,7 @@ def test_right_param(self): [self._circuit[3]] * n, [self._circuit[1]] * n, values_2=self._left_params ) results = job.result() - np.testing.assert_allclose(results.values, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) + np.testing.assert_allclose(results.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16) def test_not_set_circuits(self): """test for fidelity with no circuits.""" @@ -170,7 +170,7 @@ def test_async_join(self): job = fidelity.run([self._circuit[0]], [self._circuit[1]], left_param, right_param) jobs.append(job) - results = [job.result().values[0] for job in jobs] + results = [job.result().fidelities[0] for job in jobs] np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) From 147005db4f91623210c41deab80df37edae28484 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 16:15:53 +0200 Subject: [PATCH 51/92] Fix typo --- qiskit/algorithms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index 7ac747bb86a9..6bb0bcdeb08f 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -203,7 +203,7 @@ State Fidelities ---------- -Algorithms that compute the fidelity of pairs of quantum staties. +Algorithms that compute the fidelity of pairs of quantum states. .. autosummary:: :toctree: ../stubs/ From dd470432cb4566ec198c65f2f8ba6152b3428b11 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 16:26:19 +0200 Subject: [PATCH 52/92] Style changes --- .../state_fidelities/base_state_fidelity.py | 29 ++++++++++++++----- .../state_fidelities/compute_uncompute.py | 5 ++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index f55487065cee..1ee29bebecb1 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -23,6 +23,7 @@ from qiskit.primitives.primitive_job import PrimitiveJob from .state_fidelity_result import StateFidelityResult + class BaseStateFidelity(ABC): """ An interface to calculate state_fidelities (state overlaps) for pairs of @@ -119,7 +120,7 @@ def _construct_circuits( self, circuits_1: Sequence[QuantumCircuit], circuits_2: Sequence[QuantumCircuit], - ) -> None: + ) -> Sequence[QuantumCircuit]: """ Construct the list of fidelity circuits to be evaluated. These circuits represent the state overlap between pairs of input circuits, @@ -129,6 +130,9 @@ def _construct_circuits( circuits_1: (Parametrized) quantum circuits. circuits_2: (Parametrized) quantum circuits. + Returns: + List of constructed fidelity circuits. + Raises: ValueError: if the length of the input circuit lists doesn't match. """ @@ -171,8 +175,10 @@ def _construct_value_list( circuits_2: Sequence[QuantumCircuit], values_1: Sequence[Sequence[float]] | None = None, values_2: Sequence[Sequence[float]] | None = None, - ) -> Sequence[Sequence[float]]: - """Preprocess input parameter values and return in list format. + ) -> Sequence[float]: + """ + Preprocess input parameter values to match the fidelity + circuit parametrization, and return in list format. Args: circuits_1: (Parametrized) quantum circuits preparing the @@ -182,6 +188,13 @@ def _construct_value_list( values_1: Numerical parameters to be bound to the first circuits. values_2: Numerical parameters to be bound to the second circuits. + Returns: + 2D List of parameter values for fidelity circuit + + Raises: + ValueError: If the number of parameters in the first circuit list + do not match the number of parameters in the second + circuit list. """ values_1 = self._preprocess_values(circuits_1, values_1) @@ -197,9 +210,10 @@ def _construct_value_list( for (val_1, val_2) in zip(values_1, values_2): if len(val_1) != len(val_2): raise ValueError( - f"The number of left parameters (currently {len(val_1)})" - f"has to be equal to the number of right parameters." - f"(currently {len(val_2)})" + f"The number of parameters in the first circuit " + f"(currently {len(val_1)}) " + f"has to be equal to the number of parameters in " + f"the second circuit, (currently {len(val_2)})." ) values.append(val_1 + val_2) @@ -255,8 +269,7 @@ def run( The job's result is an instance of ``StateFidelityResult``. """ - job = PrimitiveJob(self._run, circuits_1, circuits_2, - values_1, values_2, **run_options) + job = PrimitiveJob(self._run, circuits_1, circuits_2, values_1, values_2, **run_options) job.submit() return job diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 3d3845c0cb60..58cf55687b47 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -22,6 +22,7 @@ from .base_state_fidelity import BaseStateFidelity from .state_fidelity_result import StateFidelityResult + class ComputeUncompute(BaseStateFidelity): """ This class leverages the sampler primitive to calculate the fidelity of two quantum circuits @@ -104,9 +105,7 @@ def _run( run_opts.update(**run_options) if len(values) > 0: - job = self._sampler.run( - circuits=circuits, parameter_values=values, **run_opts - ) + job = self._sampler.run(circuits=circuits, parameter_values=values, **run_opts) else: job = self._sampler.run(circuits=circuits, **run_opts) From e4275a41118323e9aeb6929db644536bcbf15ac2 Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 16:37:42 +0200 Subject: [PATCH 53/92] Allow different num.params in circuit 1 and 2 --- .../state_fidelities/base_state_fidelity.py | 13 +-------- .../test_compute_uncompute.py | 29 +++++++++++++++++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 1ee29bebecb1..f1982ccb9181 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -189,12 +189,8 @@ def _construct_value_list( values_2: Numerical parameters to be bound to the second circuits. Returns: - 2D List of parameter values for fidelity circuit + List of parameter values for fidelity circuit. - Raises: - ValueError: If the number of parameters in the first circuit list - do not match the number of parameters in the second - circuit list. """ values_1 = self._preprocess_values(circuits_1, values_1) @@ -208,13 +204,6 @@ def _construct_value_list( values = values_2 else: for (val_1, val_2) in zip(values_1, values_2): - if len(val_1) != len(val_2): - raise ValueError( - f"The number of parameters in the first circuit " - f"(currently {len(val_1)}) " - f"has to be equal to the number of parameters in " - f"the second circuit, (currently {len(val_2)})." - ) values.append(val_1 + val_2) return values diff --git a/test/python/algorithms/state_fidelities/test_compute_uncompute.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py index a3417dd62a0d..d7dde2a30d0c 100644 --- a/test/python/algorithms/state_fidelities/test_compute_uncompute.py +++ b/test/python/algorithms/state_fidelities/test_compute_uncompute.py @@ -44,7 +44,11 @@ def setUp(self): zero = QuantumCircuit(2) - self._circuit = [rx_rotations, ry_rotations, plus, zero] + rx_rotation = QuantumCircuit(2) + rx_rotation.rx(parameters[0], 0) + rx_rotation.h(1) + + self._circuit = [rx_rotations, ry_rotations, plus, zero, rx_rotation] self._sampler = Sampler() self._left_params = np.array([[0, 0], [np.pi / 2, 0], [0, np.pi / 2], [np.pi, np.pi]]) self._right_params = np.array([[0, 0], [0, 0], [np.pi / 2, 0], [0, 0]]) @@ -135,7 +139,8 @@ def test_circuit_mismatch(self): job.result() def test_param_mismatch(self): - """test for fidelity with different number of left/right parameters.""" + """test for fidelity with different number of left/right parameters that + do not match the circuits'.""" fidelity = ComputeUncompute(self._sampler) n = len(self._left_params) @@ -159,7 +164,25 @@ def test_param_mismatch(self): with self.assertRaises(ValueError): job = fidelity.run([self._circuit[0]] * n, [self._circuit[1]] * n) - job.result() + result = job.result() + np.testing.assert_allclose( + result.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16 + ) + + def test_asymmetric_params(self): + """test for fidelity when the 2 circuits have different number of + left/right parameters.""" + + fidelity = ComputeUncompute(self._sampler) + n = len(self._left_params) + right_params = [[p] for p in self._right_params[:, 0]] + job = fidelity.run( + [self._circuit[0]] * n, + [self._circuit[4]] * n, + self._left_params, + right_params, + ) + job.result() def test_async_join(self): """test for run method using join.""" From c654e79ee8554ed1b9fd9b8620c4733078bb333b Mon Sep 17 00:00:00 2001 From: ElePT Date: Mon, 29 Aug 2022 16:41:27 +0200 Subject: [PATCH 54/92] Fix unittest typo --- .../state_fidelities/test_compute_uncompute.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/python/algorithms/state_fidelities/test_compute_uncompute.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py index d7dde2a30d0c..0c6e802c3dc3 100644 --- a/test/python/algorithms/state_fidelities/test_compute_uncompute.py +++ b/test/python/algorithms/state_fidelities/test_compute_uncompute.py @@ -164,10 +164,7 @@ def test_param_mismatch(self): with self.assertRaises(ValueError): job = fidelity.run([self._circuit[0]] * n, [self._circuit[1]] * n) - result = job.result() - np.testing.assert_allclose( - result.fidelities, np.array([1.0, 0.5, 0.5, 0.0]), atol=1e-16 - ) + job.result() def test_asymmetric_params(self): """test for fidelity when the 2 circuits have different number of @@ -182,7 +179,10 @@ def test_asymmetric_params(self): self._left_params, right_params, ) - job.result() + result = job.result() + np.testing.assert_allclose( + result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-16 + ) def test_async_join(self): """test for run method using join.""" From 5df3d290c5f04d3307d118f50de03526a26fb042 Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 30 Aug 2022 08:00:09 +0200 Subject: [PATCH 55/92] Fix black --- .../algorithms/state_fidelities/test_compute_uncompute.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/python/algorithms/state_fidelities/test_compute_uncompute.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py index 0c6e802c3dc3..b3a4653f3263 100644 --- a/test/python/algorithms/state_fidelities/test_compute_uncompute.py +++ b/test/python/algorithms/state_fidelities/test_compute_uncompute.py @@ -180,9 +180,7 @@ def test_asymmetric_params(self): right_params, ) result = job.result() - np.testing.assert_allclose( - result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-16 - ) + np.testing.assert_allclose(result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-16) def test_async_join(self): """test for run method using join.""" From 6f2167906f339d33aeeb40beb0f94211a52adfd3 Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 30 Aug 2022 08:09:11 +0200 Subject: [PATCH 56/92] Fix docs --- qiskit/algorithms/__init__.py | 5 +++-- qiskit/algorithms/state_fidelities/__init__.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index 6bb0bcdeb08f..3949b7ddae66 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -200,8 +200,9 @@ PhaseEstimationResult IterativePhaseEstimation + State Fidelities ----------- +---------------- Algorithms that compute the fidelity of pairs of quantum states. @@ -211,7 +212,7 @@ state_fidelities Exceptions -========== +---------- .. autosummary:: :toctree: ../stubs/ diff --git a/qiskit/algorithms/state_fidelities/__init__.py b/qiskit/algorithms/state_fidelities/__init__.py index c9542ecd0310..8e6ced7d2a05 100644 --- a/qiskit/algorithms/state_fidelities/__init__.py +++ b/qiskit/algorithms/state_fidelities/__init__.py @@ -10,22 +10,27 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """ -===================================== +===================================================================== State Fidelity Interfaces (:mod:`qiskit.algorithms.state_fidelities`) -===================================== +===================================================================== + .. currentmodule:: qiskit.algorithms.state_fidelities State Fidelities -========= +================ + .. autosummary:: :toctree: ../stubs/ + BaseStateFidelity ComputeUncompute Results ======= + .. autosummary:: :toctree: ../stubs/ + StateFidelityResult """ From 342a6b30e48e9a90bc08c97a140582c3298676f0 Mon Sep 17 00:00:00 2001 From: ElePT Date: Tue, 30 Aug 2022 08:27:29 +0200 Subject: [PATCH 57/92] Fix style/docstrings --- .../state_fidelities/base_state_fidelity.py | 37 +++++++++++++------ .../state_fidelities/compute_uncompute.py | 16 ++++---- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index f1982ccb9181..31d4a57dcd25 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -217,15 +217,23 @@ def _run( values_2: Sequence[Sequence[float]] | None = None, **run_options, ) -> StateFidelityResult: - """Compute the state overlap (fidelity) calculation between 2 + r""" + Compute the state overlap (fidelity) calculation between two (parametrized) circuits (first and second) for a specific set of parameter values (first and second). This calculation depends on the particular - fidelity method implementation. + fidelity method implementation, but always represents: + + :math:`|\langle\psi(x)|\phi(y)\rangle|^2` + + where :math:`x` and :math:`y` are optional parametrizations of the + states :math:`\psi` and :math:`\phi` prepared by the circuits + ``circuit_1`` and ``circuit_2``, respectively. + Args: - circuits_1: (Parametrized) quantum circuits preparing one set of states - circuits_2: (Parametrized) quantum circuits preparing another set of states - values_1: Numerical parameters to be bound to the first circuits - values_2: Numerical parameters to be bound to the second circuits. + circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. + circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. + values_1: Numerical parameters to be bound to the first set of circuits + values_2: Numerical parameters to be bound to the second set of circuits. run_options: Backend runtime options used for circuit execution. Returns: @@ -242,15 +250,22 @@ def run( **run_options, ) -> PrimitiveJob: r""" - Run asynchronously the state overlap (fidelity) calculation between 2 - (parametrized) circuits (left and right) for a specific set of parameter - values (left and right). + Run asynchronously the state overlap (fidelity) calculation between two + (parametrized) circuits (first and second) for a specific set of parameter + values (first and second).This calculation depends on the particular + fidelity method implementation, but always represents: + + :math:`|\langle\psi(x)|\phi(y)\rangle|^2` + + where :math:`x` and :math:`y` are optional parametrizations of the + states :math:`\psi` and :math:`\phi` prepared by the circuits + ``circuit_1`` and ``circuit_2``, respectively. Args: circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. - values_1: Numerical parameters to be bound to the left circuits. - values_2: Numerical parameters to be bound to the right circuits. + values_1: Numerical parameters to be bound to the first set of circuits. + values_2: Numerical parameters to be bound to the second set of circuits. run_options: Backend runtime options used for circuit execution. Returns: diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 58cf55687b47..7db15c40bb44 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -25,13 +25,14 @@ class ComputeUncompute(BaseStateFidelity): """ - This class leverages the sampler primitive to calculate the fidelity of two quantum circuits - with the compute-uncompute method. + This class leverages the sampler primitive to calculate the fidelity of + two quantum circuits with the compute-uncompute method. """ def __init__(self, sampler: BaseSampler, **run_options) -> None: r""" - Initializes the class to evaluate the state_fidelities defined as the state overlap + Initializes the class to evaluate the compute-uncompute state fidelity using + the sampler primitive. This fidelity is defined as the state overlap :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, @@ -69,10 +70,11 @@ def _run( **run_options, ) -> StateFidelityResult: r""" - Compute the state overlap (fidelity) calculation between 2 - (parametrized) circuits (left and right) for a specific set of parameter - values (left and right) following the compute-uncompute method, where - the fidelity corresponds to: + Compute the state overlap (fidelity) calculation between two + (parametrized) circuits (first and second) for a specific set of parameter + values (first and second) following the compute-uncompute method + + The fidelity corresponds to: :math:`|\langle\psi(x)|\phi(y)\rangle|^2` From a3d3e59e2815be8acda8e9f8158d622942bbfe4b Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 31 Aug 2022 13:59:07 +0200 Subject: [PATCH 58/92] Add AlgorithmJob --- qiskit/algorithms/__init__.py | 3 ++- qiskit/algorithms/algorithm_job.py | 18 ++++++++++++++++++ .../state_fidelities/base_state_fidelity.py | 6 +++--- 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 qiskit/algorithms/algorithm_job.py diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index 3949b7ddae66..65f7b77a5e16 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -230,7 +230,7 @@ eval_observables """ - +from .algorithm_job import AlgorithmJob from .algorithm_result import AlgorithmResult from .evolvers import EvolutionResult, EvolutionProblem from .evolvers.real_evolver import RealEvolver @@ -277,6 +277,7 @@ from .evolvers.pvqd import PVQD, PVQDResult __all__ = [ + "AlgorithmJob", "AlgorithmResult", "VariationalAlgorithm", "VariationalResult", diff --git a/qiskit/algorithms/algorithm_job.py b/qiskit/algorithms/algorithm_job.py new file mode 100644 index 000000000000..1a6079422b08 --- /dev/null +++ b/qiskit/algorithms/algorithm_job.py @@ -0,0 +1,18 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020, 2021. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +This module introduces the AlgorithmJob class for typing purposes +""" +from qiskit.primitives.primitive_job import PrimitiveJob + +AlgorithmJob = PrimitiveJob diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 31d4a57dcd25..446efce211e5 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -19,8 +19,8 @@ import numpy as np from qiskit import QuantumCircuit +from qiskit.algorithms import AlgorithmJob from qiskit.circuit import ParameterVector -from qiskit.primitives.primitive_job import PrimitiveJob from .state_fidelity_result import StateFidelityResult @@ -248,7 +248,7 @@ def run( values_1: Sequence[Sequence[float]] | None = None, values_2: Sequence[Sequence[float]] | None = None, **run_options, - ) -> PrimitiveJob: + ) -> AlgorithmJob: r""" Run asynchronously the state overlap (fidelity) calculation between two (parametrized) circuits (first and second) for a specific set of parameter @@ -273,7 +273,7 @@ def run( The job's result is an instance of ``StateFidelityResult``. """ - job = PrimitiveJob(self._run, circuits_1, circuits_2, values_1, values_2, **run_options) + job = AlgorithmJob(self._run, circuits_1, circuits_2, values_1, values_2, **run_options) job.submit() return job From 5efcd71e872168131c3bebfb1679135ac1429b06 Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 31 Aug 2022 14:13:20 +0200 Subject: [PATCH 59/92] Add truncate, fix result docstring --- .../state_fidelities/base_state_fidelity.py | 13 +++++++++++++ .../state_fidelities/compute_uncompute.py | 9 +++++---- .../state_fidelities/state_fidelity_result.py | 12 ++++++------ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 446efce211e5..7c6022b0299b 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -277,3 +277,16 @@ def run( job.submit() return job + + def _truncate_fidelities(self, fidelities: Sequence[float]) -> Sequence[float]: + """ + Ensure fidelity result in [0,1] + + Args: + fidelities: sequence of raw fidelity results + + Returns: + List of truncated fidelities. + + """ + return [0 if f < 0 else 1 if f > 1 else f for f in fidelities] diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 7db15c40bb44..046309350a7b 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -113,8 +113,9 @@ def _run( result = job.result() - # if error mitigation is added in the future, we will have to handle - # negative values in some way (e.g. clipping to zero) - overlaps = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] + raw_fidelities = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] + fidelities = self._truncate_fidelities(raw_fidelities) - return StateFidelityResult(fidelities=overlaps, metadata=run_opts) + return StateFidelityResult( + fidelities=fidelities, raw_fidelities=raw_fidelities, metadata=run_opts + ) diff --git a/qiskit/algorithms/state_fidelities/state_fidelity_result.py b/qiskit/algorithms/state_fidelities/state_fidelity_result.py index 35a307a8eb75..fadb1a393bf4 100644 --- a/qiskit/algorithms/state_fidelities/state_fidelity_result.py +++ b/qiskit/algorithms/state_fidelities/state_fidelity_result.py @@ -22,12 +22,12 @@ @dataclass(frozen=True) class StateFidelityResult: - """Result of Fidelity computation. - - Args: - fidelities: List of fidelity values for each pair of input circuits. - metadata: Additional information on the fidelity calculations. - """ + """This class stores the result of StateFidelity computations.""" fidelities: Sequence[float] + """List of truncated fidelity values for each pair of input circuits, ensured to be in [0,1].""" + raw_fidelities: Sequence[float] + """List of raw fidelity values for each pair of input circuits, which might not be in [0,1] + depending on the error mitigation method used.""" metadata: Sequence[Mapping[str, Any]] + """Metadata values""" From 35b858dc7d4eddee7dbddafe1599049f19961381 Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 31 Aug 2022 14:18:48 +0200 Subject: [PATCH 60/92] Completely remove left-right --- .../state_fidelities/base_state_fidelity.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 7c6022b0299b..54b5abe75efe 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -96,8 +96,8 @@ def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircu if circuit_1.num_qubits != circuit_2.num_qubits: raise ValueError( - f"The number of qubits for the left circuit ({circuit_1.num_qubits}) " - f"and right circuit ({circuit_2.num_qubits}) do not coincide." + f"The number of qubits for the first circuit ({circuit_1.num_qubits}) " + f"and second circuit ({circuit_2.num_qubits}) do not coincide." ) @abstractmethod @@ -155,10 +155,10 @@ def _construct_circuits( # re-parametrize input circuits # TODO: make smarter checks to avoid unnecesary reparametrizations - left_parameters = ParameterVector("x", circuit_1.num_parameters) - parametrized_circuit_1 = circuit_1.assign_parameters(left_parameters) - right_parameters = ParameterVector("y", circuit_2.num_parameters) - parametrized_circuit_2 = circuit_2.assign_parameters(right_parameters) + parameters_1 = ParameterVector("x", circuit_1.num_parameters) + parametrized_circuit_1 = circuit_1.assign_parameters(parameters_1) + parameters_2 = ParameterVector("y", circuit_2.num_parameters) + parametrized_circuit_2 = circuit_2.assign_parameters(parameters_2) circuit = self._create_fidelity_circuit( parametrized_circuit_1, parametrized_circuit_2 From 66dbce6f04ec82ba8422f9af784c89dbafb09b2b Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 31 Aug 2022 14:22:29 +0200 Subject: [PATCH 61/92] Add comments --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 54b5abe75efe..7f48034c5bd1 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -33,7 +33,7 @@ class BaseStateFidelity(ABC): def __init__( self, ) -> None: - """Initializes the class to evaluate state_fidelities.""" + """Initializes the class to evaluate state fidelities.""" self._circuits: Sequence[QuantumCircuit] = [] self._parameter_values: Sequence[Sequence[float]] = [] @@ -146,6 +146,7 @@ def _construct_circuits( circuits = [] for (circuit_1, circuit_2) in zip(circuits_1, circuits_2): + # TODO: improve caching, what if the circuit is modified without changing the id? circuit = self._circuit_cache.get((id(circuit_1), id(circuit_2))) if circuit is not None: From b8b18cd0f87ba6e60e08164878cacc23e3f72bc1 Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 31 Aug 2022 14:24:53 +0200 Subject: [PATCH 62/92] Make create circuit public --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 6 +++--- qiskit/algorithms/state_fidelities/compute_uncompute.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 7f48034c5bd1..3e7d6d373983 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -101,7 +101,7 @@ def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircu ) @abstractmethod - def _create_fidelity_circuit( + def create_fidelity_circuit( self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit ) -> QuantumCircuit: """ @@ -161,7 +161,7 @@ def _construct_circuits( parameters_2 = ParameterVector("y", circuit_2.num_parameters) parametrized_circuit_2 = circuit_2.assign_parameters(parameters_2) - circuit = self._create_fidelity_circuit( + circuit = self.create_fidelity_circuit( parametrized_circuit_1, parametrized_circuit_2 ) circuits.append(circuit) @@ -240,7 +240,7 @@ def _run( Returns: The result of the fidelity calculation. """ - ... + raise NotImplementedError def run( self, diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 046309350a7b..bfd1e74a65d2 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -47,7 +47,7 @@ def __init__(self, sampler: BaseSampler, **run_options) -> None: self._default_run_options = run_options super().__init__() - def _create_fidelity_circuit(self, circuit_1, circuit_2) -> QuantumCircuit: + def create_fidelity_circuit(self, circuit_1, circuit_2) -> QuantumCircuit: """ Creates fidelity circuit following the compute-uncompute method. Args: From dcf306e596cdf86107c9227f38b011aa72e7c472 Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 31 Aug 2022 14:36:37 +0200 Subject: [PATCH 63/92] Remove empty param distinction --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 3 +++ qiskit/algorithms/state_fidelities/compute_uncompute.py | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 3e7d6d373983..405139ad0ef1 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -206,6 +206,9 @@ def _construct_value_list( else: for (val_1, val_2) in zip(values_1, values_2): values.append(val_1 + val_2) + else: + # ensure 2d list even if it is empty + values = [values] return values diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index bfd1e74a65d2..cdaafd9b1118 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -106,10 +106,7 @@ def _run( run_opts = copy(self._default_run_options) run_opts.update(**run_options) - if len(values) > 0: - job = self._sampler.run(circuits=circuits, parameter_values=values, **run_opts) - else: - job = self._sampler.run(circuits=circuits, **run_opts) + job = self._sampler.run(circuits=circuits, parameter_values=values, **run_opts) result = job.result() From 65dbefafca54b386bacf24d95c92facc0ee30287 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Wed, 31 Aug 2022 15:29:54 +0200 Subject: [PATCH 64/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 405139ad0ef1..a068c9e087c9 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -293,4 +293,4 @@ def _truncate_fidelities(self, fidelities: Sequence[float]) -> Sequence[float]: List of truncated fidelities. """ - return [0 if f < 0 else 1 if f > 1 else f for f in fidelities] + return np.clip(fidelities, 0, 1).tolist() From 6f30d0f1fb5ec913b38987768ccad698080f8a4e Mon Sep 17 00:00:00 2001 From: ElePT Date: Wed, 31 Aug 2022 16:04:59 +0200 Subject: [PATCH 65/92] change list preprocessing --- .../state_fidelities/base_state_fidelity.py | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 405139ad0ef1..73f2051d24b5 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -55,7 +55,8 @@ def _preprocess_values( values: parameter values corresponding to the circuits to be checked Returns: - Returns a 2D list if values match, ``None`` if no parameters are passed + Returns a 2D value list if values match, and an empty 2D list + if no parameters are passed. Raises: ValueError: if the number of parameter values doesn't match the number of @@ -73,7 +74,7 @@ def _preprocess_values( f"`values` cannot be `None` because circuit <{circuit.name}> has " f"{circuit.num_parameters} free parameters." ) - return None + return [[]] else: # ensure 2d list if not isinstance(values, list): @@ -193,22 +194,17 @@ def _construct_value_list( List of parameter values for fidelity circuit. """ - values_1 = self._preprocess_values(circuits_1, values_1) values_2 = self._preprocess_values(circuits_2, values_2) values = [] - if values_2 is not None or values_1 is not None: - if values_2 is None: - values = values_1 - elif values_1 is None: - values = values_2 - else: - for (val_1, val_2) in zip(values_1, values_2): - values.append(val_1 + val_2) + if len(values_2[0]) == 0: + values = values_1 + elif len(values_1[0]) == 0: + values = values_2 else: - # ensure 2d list even if it is empty - values = [values] + for (val_1, val_2) in zip(values_1, values_2): + values.append(val_1 + val_2) return values From ab4b1d01c7774e01d7c95fbc162b0d214cd5d866 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Wed, 31 Aug 2022 17:30:13 +0200 Subject: [PATCH 66/92] Update qiskit/algorithms/algorithm_job.py Co-authored-by: Ikko Hamamura --- qiskit/algorithms/algorithm_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/algorithm_job.py b/qiskit/algorithms/algorithm_job.py index 1a6079422b08..f25c49689c8b 100644 --- a/qiskit/algorithms/algorithm_job.py +++ b/qiskit/algorithms/algorithm_job.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020, 2021. +# (C) Copyright IBM 2022. # # 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 From 8549ce3c9fc350343501c7fc0daa10de80909626 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:02:53 +0200 Subject: [PATCH 67/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 11dae74b05be..4fe78194ad3d 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """ -Base fidelity interface +Base state fidelity interface """ from __future__ import annotations From 061f17b1fc8bbd7eb2578b135e0145f2bf21727f Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:03:04 +0200 Subject: [PATCH 68/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 4fe78194ad3d..c83b47f646fc 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -141,7 +141,7 @@ def _construct_circuits( if not len(circuits_1) == len(circuits_2): raise ValueError( f"The length of the first circuit list({len(circuits_1)}) " - f"and second circuit list ({len(circuits_2)}) does not coincide." + f"and second circuit list ({len(circuits_2)}) is not the same." ) circuits = [] From 789f9db9a342b56ed58a4982466aa60a55f9b5dc Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 1 Sep 2022 12:20:37 +0200 Subject: [PATCH 69/92] Apply comments Steve --- .../state_fidelities/base_state_fidelity.py | 10 ++++-- .../state_fidelities/compute_uncompute.py | 34 +++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 11dae74b05be..26a35c9b7b00 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -234,7 +234,10 @@ def _run( circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. values_1: Numerical parameters to be bound to the first set of circuits values_2: Numerical parameters to be bound to the second set of circuits. - run_options: Backend runtime options used for circuit execution. + run_options: Backend runtime options used for circuit execution. The order + of priority is: run_options in ``run`` method > fidelity's default + run_options > primitive's default setting. + Higher priority setting overrides lower priority setting. Returns: The result of the fidelity calculation. @@ -266,7 +269,10 @@ def run( circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. values_1: Numerical parameters to be bound to the first set of circuits. values_2: Numerical parameters to be bound to the second set of circuits. - run_options: Backend runtime options used for circuit execution. + run_options: Backend runtime options used for circuit execution. The order + of priority is: run_options in ``run`` method > fidelity's default + run_options > primitive's default setting. + Higher priority setting overrides lower priority setting. Returns: Primitive job for the fidelity calculation. diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index cdaafd9b1118..0fddb74f4d8c 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -18,7 +18,10 @@ from copy import copy from qiskit import QuantumCircuit +from qiskit.exceptions import QiskitError from qiskit.primitives import BaseSampler +from qiskit.providers import JobStatus + from .base_state_fidelity import BaseStateFidelity from .state_fidelity_result import StateFidelityResult @@ -47,7 +50,9 @@ def __init__(self, sampler: BaseSampler, **run_options) -> None: self._default_run_options = run_options super().__init__() - def create_fidelity_circuit(self, circuit_1, circuit_2) -> QuantumCircuit: + def create_fidelity_circuit( + self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit + ) -> QuantumCircuit: """ Creates fidelity circuit following the compute-uncompute method. Args: @@ -83,22 +88,25 @@ def _run( circuits_2: (Parametrized) quantum circuits preparing :math:`|\phi\rangle`. values_1: Numerical parameters to be bound to the first circuits. values_2: Numerical parameters to be bound to the second circuits. - run_options: Backend runtime options used for circuit execution. + run_options: Backend runtime options used for circuit execution. The order + of priority is: run_options in ``run`` method > fidelity's default + run_options > primitive's default setting. + Higher priority setting overrides lower priority setting. Returns: The result of the fidelity calculation. Raises: ValueError: At least one pair of circuits must be defined. + QiskitError: If the sampler job is not completed successfully. """ circuits = self._construct_circuits(circuits_1, circuits_2) - values = self._construct_value_list(circuits_1, circuits_2, values_1, values_2) - if len(circuits) == 0: raise ValueError( "At least one pair of circuits must be defined to calculate the state overlap." ) + values = self._construct_value_list(circuits_1, circuits_2, values_1, values_2) # The priority of run options is as follows: # run_options in `evaluate` method > fidelity's default run_options > @@ -109,10 +117,16 @@ def _run( job = self._sampler.run(circuits=circuits, parameter_values=values, **run_opts) result = job.result() + status = job.status() - raw_fidelities = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] - fidelities = self._truncate_fidelities(raw_fidelities) - - return StateFidelityResult( - fidelities=fidelities, raw_fidelities=raw_fidelities, metadata=run_opts - ) + if status is JobStatus.DONE: + raw_fidelities = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] + fidelities = self._truncate_fidelities(raw_fidelities) + return StateFidelityResult( + fidelities=fidelities, raw_fidelities=raw_fidelities, metadata=run_opts + ) + else: + raise QiskitError( + f"The sampler fidelity job was not completed succesfully. " + f"Job status = {status}." + ) From 30bf3acb7dd94545e08bd79cb42e6977e8ce5df9 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Thu, 1 Sep 2022 12:21:20 +0200 Subject: [PATCH 70/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Julien Gacon --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index c83b47f646fc..3bc447399b08 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -35,9 +35,6 @@ def __init__( ) -> None: """Initializes the class to evaluate state fidelities.""" - self._circuits: Sequence[QuantumCircuit] = [] - self._parameter_values: Sequence[Sequence[float]] = [] - # use cache for preventing unnecessary circuit compositions self._circuit_cache: Mapping[(int, int), QuantumCircuit] = {} From 4e7df618cbb129df50d9a7099f4da769d06621cd Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Thu, 1 Sep 2022 12:22:12 +0200 Subject: [PATCH 71/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Julien Gacon --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 3bc447399b08..36f8c613d922 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -249,7 +249,7 @@ def run( r""" Run asynchronously the state overlap (fidelity) calculation between two (parametrized) circuits (first and second) for a specific set of parameter - values (first and second).This calculation depends on the particular + values (first and second). This calculation depends on the particular fidelity method implementation, but always represents: :math:`|\langle\psi(x)|\phi(y)\rangle|^2` From 05c0f1f17bc38323ca05876e76bd822f0b967f99 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Thu, 1 Sep 2022 12:23:05 +0200 Subject: [PATCH 72/92] Update test/python/algorithms/state_fidelities/test_compute_uncompute.py Co-authored-by: Julien Gacon --- .../algorithms/state_fidelities/test_compute_uncompute.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/python/algorithms/state_fidelities/test_compute_uncompute.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py index b3a4653f3263..2a57f9d99673 100644 --- a/test/python/algorithms/state_fidelities/test_compute_uncompute.py +++ b/test/python/algorithms/state_fidelities/test_compute_uncompute.py @@ -39,8 +39,7 @@ def setUp(self): ry_rotations.ry(parameters[1], 1) plus = QuantumCircuit(2) - plus.h(0) - plus.h(1) + plus.h([0, 1]) zero = QuantumCircuit(2) From 1ff78486c75bce0522c983da2004a9b85bf95aa2 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 1 Sep 2022 14:04:44 +0200 Subject: [PATCH 73/92] Style changes --- qiskit/algorithms/__init__.py | 11 +++++++++++ .../state_fidelities/base_state_fidelity.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/qiskit/algorithms/__init__.py b/qiskit/algorithms/__init__.py index 65f7b77a5e16..b1909b0acf42 100644 --- a/qiskit/algorithms/__init__.py +++ b/qiskit/algorithms/__init__.py @@ -229,6 +229,17 @@ :toctree: ../stubs/ eval_observables + +Utility classes +--------------- + +Utility classes used by algorithms (mainly for type-hinting purposes). + +.. autosummary:: + :toctree: ../stubs/ + + AlgorithmJob + """ from .algorithm_job import AlgorithmJob from .algorithm_result import AlgorithmResult diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index db1b7aa29b81..0b4f888f1981 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -45,7 +45,7 @@ def __init__( def _preprocess_values( circuits: QuantumCircuit, values: Sequence[Sequence[float]] | None = None, - ) -> Sequence[Sequence[float]] | None: + ) -> Sequence[Sequence[float]]: """ Checks whether the passed values match the shape of the parameters of the corresponding circuits and formats values to 2D list. From 567d31c76cc93a190fa403e387c98405b0649c34 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Thu, 1 Sep 2022 14:07:10 +0200 Subject: [PATCH 74/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Julien Gacon --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 36f8c613d922..ed846da5cdfa 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -252,7 +252,9 @@ def run( values (first and second). This calculation depends on the particular fidelity method implementation, but always represents: - :math:`|\langle\psi(x)|\phi(y)\rangle|^2` + .. math:: + + |\langle\psi(x)|\phi(y)\rangle|^2 where :math:`x` and :math:`y` are optional parametrizations of the states :math:`\psi` and :math:`\phi` prepared by the circuits From e0c653ae221245b223307601c1644b6b0e17328d Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 1 Sep 2022 14:37:27 +0200 Subject: [PATCH 75/92] Apply comments Julien --- .../state_fidelities/base_state_fidelity.py | 35 ++++++-------- .../state_fidelities/compute_uncompute.py | 48 ++++++++++--------- .../state_fidelities/state_fidelity_result.py | 4 +- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index d487c1848324..72c5e45800af 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -25,15 +25,23 @@ class BaseStateFidelity(ABC): - """ + r""" An interface to calculate state_fidelities (state overlaps) for pairs of - (parametrized) quantum circuits. + (parametrized) quantum circuits. The calculation depends on the particular + fidelity method implementation, but can be always defined as the state overlap + + .. math:: + |\langle\psi(x)|\phi(y)\rangle|^2 + + where :math:`x` and :math:`y` are optional parametrizations of the + states :math:`\psi` and :math:`\phi` prepared by the circuits + ``circuit_1`` and ``circuit_2``, respectively. + """ def __init__( self, ) -> None: - """Initializes the class to evaluate state fidelities.""" # use cache for preventing unnecessary circuit compositions self._circuit_cache: Mapping[(int, int), QuantumCircuit] = {} @@ -95,7 +103,7 @@ def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircu if circuit_1.num_qubits != circuit_2.num_qubits: raise ValueError( f"The number of qubits for the first circuit ({circuit_1.num_qubits}) " - f"and second circuit ({circuit_2.num_qubits}) do not coincide." + f"and second circuit ({circuit_2.num_qubits}) are not the same." ) @abstractmethod @@ -217,14 +225,7 @@ def _run( r""" Compute the state overlap (fidelity) calculation between two (parametrized) circuits (first and second) for a specific set of parameter - values (first and second). This calculation depends on the particular - fidelity method implementation, but always represents: - - :math:`|\langle\psi(x)|\phi(y)\rangle|^2` - - where :math:`x` and :math:`y` are optional parametrizations of the - states :math:`\psi` and :math:`\phi` prepared by the circuits - ``circuit_1`` and ``circuit_2``, respectively. + values (first and second). Args: circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. @@ -253,15 +254,7 @@ def run( Run asynchronously the state overlap (fidelity) calculation between two (parametrized) circuits (first and second) for a specific set of parameter values (first and second). This calculation depends on the particular - fidelity method implementation, but always represents: - - .. math:: - - |\langle\psi(x)|\phi(y)\rangle|^2 - - where :math:`x` and :math:`y` are optional parametrizations of the - states :math:`\psi` and :math:`\phi` prepared by the circuits - ``circuit_1`` and ``circuit_2``, respectively. + fidelity method implementation. Args: circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 0fddb74f4d8c..7bf251508a03 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -27,25 +27,27 @@ class ComputeUncompute(BaseStateFidelity): - """ - This class leverages the sampler primitive to calculate the fidelity of - two quantum circuits with the compute-uncompute method. + r""" + This class leverages the sampler primitive to calculate the state + fidelity of two quantum circuits following the compute-uncompute + method (see [1] for further reference). + The fidelity can be defined as the state overlap + .. math:: + |\langle\psi(x)|\phi(y)\rangle|^2 + + where :math:`x` and :math:`y` are optional parametrizations of the + states :math:`\psi` and :math:`\phi` prepared by the circuits + ``circuit_1`` and ``circuit_2``, respectively. + + References: + 1. Havlíček, V., Córcoles, A. D., Temme, K., Harrow, A. W., Kandala, + A., Chow, J. M., & Gambetta, J. M. (2019). Supervised learning + with quantum-enhanced feature spaces. Nature, 567(7747), 209-212. + `arXiv:1804.11326v2 [quant-ph] `_ """ def __init__(self, sampler: BaseSampler, **run_options) -> None: - r""" - Initializes the class to evaluate the compute-uncompute state fidelity using - the sampler primitive. This fidelity is defined as the state overlap - - :math:`|\langle\psi(x)|\phi(y)\rangle|^2`, - where :math:`x` and :math:`y` are optional parametrizations of the - states :math:`\psi` and :math:`\phi` prepared by the circuits - ``circuit_1`` and ``circuit_2``, respectively. - Args: - sampler: Sampler primitive instance. - run_options: Backend runtime options used for circuit execution. - """ self._sampler = sampler self._default_run_options = run_options super().__init__() @@ -54,7 +56,9 @@ def create_fidelity_circuit( self, circuit_1: QuantumCircuit, circuit_2: QuantumCircuit ) -> QuantumCircuit: """ - Creates fidelity circuit following the compute-uncompute method. + Combines ``circuit_1`` and ``circuit_2`` to create the + fidelity circuit following the compute-uncompute method. + Args: circuit_1: (Parametrized) quantum circuit circuit_2: (Parametrized) quantum circuit @@ -77,11 +81,7 @@ def _run( r""" Compute the state overlap (fidelity) calculation between two (parametrized) circuits (first and second) for a specific set of parameter - values (first and second) following the compute-uncompute method - - The fidelity corresponds to: - - :math:`|\langle\psi(x)|\phi(y)\rangle|^2` + values (first and second) following the compute-uncompute method. Args: circuits_1: (Parametrized) quantum circuits preparing :math:`|\psi\rangle`. @@ -122,8 +122,12 @@ def _run( if status is JobStatus.DONE: raw_fidelities = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] fidelities = self._truncate_fidelities(raw_fidelities) + return StateFidelityResult( - fidelities=fidelities, raw_fidelities=raw_fidelities, metadata=run_opts + fidelities=fidelities, + raw_fidelities=raw_fidelities, + metadata=result.metadata, + run_options=run_opts, ) else: raise QiskitError( diff --git a/qiskit/algorithms/state_fidelities/state_fidelity_result.py b/qiskit/algorithms/state_fidelities/state_fidelity_result.py index fadb1a393bf4..04d4aa0ca411 100644 --- a/qiskit/algorithms/state_fidelities/state_fidelity_result.py +++ b/qiskit/algorithms/state_fidelities/state_fidelity_result.py @@ -30,4 +30,6 @@ class StateFidelityResult: """List of raw fidelity values for each pair of input circuits, which might not be in [0,1] depending on the error mitigation method used.""" metadata: Sequence[Mapping[str, Any]] - """Metadata values""" + """Additional information about the fidelity calculation.""" + run_options: Mapping[str, Any] + """Runtime options for the execution of the fidelity job.""" From 8d220e423edd12a9aa39126fef6721a26a16a608 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 1 Sep 2022 15:10:06 +0200 Subject: [PATCH 76/92] Style fix --- .../state_fidelities/base_state_fidelity.py | 32 +++++++++---------- .../state_fidelities/compute_uncompute.py | 25 +++++++++++---- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 72c5e45800af..84535e79f93b 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -26,9 +26,9 @@ class BaseStateFidelity(ABC): r""" - An interface to calculate state_fidelities (state overlaps) for pairs of + An interface to calculate state fidelities (state overlaps) for pairs of (parametrized) quantum circuits. The calculation depends on the particular - fidelity method implementation, but can be always defined as the state overlap + fidelity method implementation, but can be always defined as the state overlap: .. math:: |\langle\psi(x)|\phi(y)\rangle|^2 @@ -56,12 +56,12 @@ def _preprocess_values( of the corresponding circuits and formats values to 2D list. Args: - circuits: list of circuits to be checked - values: parameter values corresponding to the circuits to be checked + circuits: List of circuits to be checked. + values: Parameter values corresponding to the circuits to be checked. Returns: - Returns a 2D value list if values match, and an empty 2D list - if no parameters are passed. + A 2D value list if the values match the circuits, or an empty 2D list + if values is None. Raises: ValueError: if the number of parameter values doesn't match the number of @@ -92,8 +92,8 @@ def _check_qubits_match(self, circuit_1: QuantumCircuit, circuit_2: QuantumCircu """ Checks that the number of qubits of 2 circuits matches. Args: - circuit_1: (Parametrized) quantum circuit - circuit_2: (Parametrized) quantum circuit + circuit_1: (Parametrized) quantum circuit. + circuit_2: (Parametrized) quantum circuit. Raises: ValueError: when ``circuit_1`` and ``circuit_2`` don't have the @@ -114,8 +114,8 @@ def create_fidelity_circuit( Implementation-dependent method to create a fidelity circuit from 2 circuit inputs. Args: - circuit_1: (Parametrized) quantum circuit - circuit_2: (Parametrized) quantum circuit + circuit_1: (Parametrized) quantum circuit. + circuit_2: (Parametrized) quantum circuit. Returns: The fidelity quantum circuit corresponding to ``circuit_1`` and ``circuit_2``. @@ -128,7 +128,7 @@ def _construct_circuits( circuits_2: Sequence[QuantumCircuit], ) -> Sequence[QuantumCircuit]: """ - Construct the list of fidelity circuits to be evaluated. + Constructs the list of fidelity circuits to be evaluated. These circuits represent the state overlap between pairs of input circuits, and their construction depends on the fidelity method implementations. @@ -184,7 +184,7 @@ def _construct_value_list( values_2: Sequence[Sequence[float]] | None = None, ) -> Sequence[float]: """ - Preprocess input parameter values to match the fidelity + Preprocesses input parameter values to match the fidelity circuit parametrization, and return in list format. Args: @@ -223,7 +223,7 @@ def _run( **run_options, ) -> StateFidelityResult: r""" - Compute the state overlap (fidelity) calculation between two + Computes the state overlap (fidelity) calculation between two (parametrized) circuits (first and second) for a specific set of parameter values (first and second). @@ -251,7 +251,7 @@ def run( **run_options, ) -> AlgorithmJob: r""" - Run asynchronously the state overlap (fidelity) calculation between two + Runs asynchronously the state overlap (fidelity) calculation between two (parametrized) circuits (first and second) for a specific set of parameter values (first and second). This calculation depends on the particular fidelity method implementation. @@ -278,10 +278,10 @@ def run( def _truncate_fidelities(self, fidelities: Sequence[float]) -> Sequence[float]: """ - Ensure fidelity result in [0,1] + Ensures fidelity result in [0,1]. Args: - fidelities: sequence of raw fidelity results + fidelities: Sequence of raw fidelity results. Returns: List of truncated fidelities. diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 7bf251508a03..3bbf75c14e9c 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -31,7 +31,8 @@ class ComputeUncompute(BaseStateFidelity): This class leverages the sampler primitive to calculate the state fidelity of two quantum circuits following the compute-uncompute method (see [1] for further reference). - The fidelity can be defined as the state overlap + The fidelity can be defined as the state overlap: + .. math:: |\langle\psi(x)|\phi(y)\rangle|^2 @@ -39,7 +40,7 @@ class ComputeUncompute(BaseStateFidelity): states :math:`\psi` and :math:`\phi` prepared by the circuits ``circuit_1`` and ``circuit_2``, respectively. - References: + **Reference:** 1. Havlíček, V., Córcoles, A. D., Temme, K., Harrow, A. W., Kandala, A., Chow, J. M., & Gambetta, J. M. (2019). Supervised learning with quantum-enhanced feature spaces. Nature, 567(7747), 209-212. @@ -47,8 +48,18 @@ class ComputeUncompute(BaseStateFidelity): """ def __init__(self, sampler: BaseSampler, **run_options) -> None: - - self._sampler = sampler + """ + Args: + sampler: Sampler primitive instance. + run_options: Backend runtime options used for circuit execution. + Raises: + ValueError: If the sampler is not an instance of ``BaseSampler``. + """ + if not isinstance(sampler, BaseSampler): + raise ValueError( + f"The sampler should be an instance of BaseSampler, " f"but got {type(sampler)}" + ) + self._sampler: BaseSampler = sampler self._default_run_options = run_options super().__init__() @@ -60,8 +71,8 @@ def create_fidelity_circuit( fidelity circuit following the compute-uncompute method. Args: - circuit_1: (Parametrized) quantum circuit - circuit_2: (Parametrized) quantum circuit + circuit_1: (Parametrized) quantum circuit. + circuit_2: (Parametrized) quantum circuit. Returns: The fidelity quantum circuit corresponding to circuit_1 and circuit_2. @@ -79,7 +90,7 @@ def _run( **run_options, ) -> StateFidelityResult: r""" - Compute the state overlap (fidelity) calculation between two + Computes the state overlap (fidelity) calculation between two (parametrized) circuits (first and second) for a specific set of parameter values (first and second) following the compute-uncompute method. From 7de5e2d788d7ccf0e9464d3e10b6e604fb0e9582 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 1 Sep 2022 16:47:27 +0200 Subject: [PATCH 77/92] Fix bug for lists of numpy values --- .../state_fidelities/base_state_fidelity.py | 10 ++++++---- .../state_fidelities/test_compute_uncompute.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 84535e79f93b..3e42cbffe404 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -68,10 +68,6 @@ def _preprocess_values( circuit parameters """ - # Support ndarray - if isinstance(values, np.ndarray): - values = values.tolist() - if values is None: for circuit in circuits: if circuit.num_parameters != 0: @@ -81,6 +77,12 @@ def _preprocess_values( ) return [[]] else: + # Support ndarray + if isinstance(values, np.ndarray): + values = values.tolist() + if len(values) > 0 and isinstance(values[0], np.ndarray): + values = [v.tolist() for v in values] + # ensure 2d list if not isinstance(values, list): values = [values] diff --git a/test/python/algorithms/state_fidelities/test_compute_uncompute.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py index 2a57f9d99673..1b294f354df3 100644 --- a/test/python/algorithms/state_fidelities/test_compute_uncompute.py +++ b/test/python/algorithms/state_fidelities/test_compute_uncompute.py @@ -17,6 +17,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit, ParameterVector +from qiskit.circuit.library import RealAmplitudes from qiskit.primitives import Sampler from qiskit.algorithms.state_fidelities import ComputeUncompute from qiskit.test import QiskitTestCase @@ -193,6 +194,22 @@ def test_async_join(self): results = [job.result().fidelities[0] for job in jobs] np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) + def test_values_types(self): + + fidelity = ComputeUncompute(self._sampler) + circuit = RealAmplitudes(2) + values = np.random.random(circuit.num_parameters) + shift = np.ones_like(values) * 0.01 + + job = fidelity.run([circuit], [circuit], [values], [values + shift]) + result_1 = job.result() + + shift_val = values + shift + job = fidelity.run([circuit], [circuit], [values.tolist()], [shift_val.tolist()]) + result_2 = job.result() + + np.testing.assert_allclose(result_1.fidelities, result_2.fidelities, atol=1e-16) + if __name__ == "__main__": unittest.main() From 817c8f74eb9d43c5b119b837d7a5affc8d9eb5f3 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 1 Sep 2022 17:26:32 +0200 Subject: [PATCH 78/92] Support single circuits --- .../state_fidelities/base_state_fidelity.py | 33 +++++++++++-------- .../state_fidelities/compute_uncompute.py | 8 ++--- .../test_compute_uncompute.py | 30 +++++++++-------- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 3e42cbffe404..d7d39062a674 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -48,8 +48,8 @@ def __init__( @staticmethod def _preprocess_values( - circuits: QuantumCircuit, - values: Sequence[Sequence[float]] | None = None, + circuits: Sequence[QuantumCircuit], + values: Sequence[float] | Sequence[Sequence[float]] | None = None, ) -> Sequence[Sequence[float]]: """ Checks whether the passed values match the shape of the parameters @@ -126,8 +126,8 @@ def create_fidelity_circuit( def _construct_circuits( self, - circuits_1: Sequence[QuantumCircuit], - circuits_2: Sequence[QuantumCircuit], + circuits_1: QuantumCircuit | Sequence[QuantumCircuit], + circuits_2: QuantumCircuit | Sequence[QuantumCircuit], ) -> Sequence[QuantumCircuit]: """ Constructs the list of fidelity circuits to be evaluated. @@ -145,6 +145,11 @@ def _construct_circuits( ValueError: if the length of the input circuit lists doesn't match. """ + if isinstance(circuits_1, QuantumCircuit): + circuits_1 = [circuits_1] + if isinstance(circuits_2, QuantumCircuit): + circuits_2 = [circuits_2] + if not len(circuits_1) == len(circuits_2): raise ValueError( f"The length of the first circuit list({len(circuits_1)}) " @@ -182,8 +187,8 @@ def _construct_value_list( self, circuits_1: Sequence[QuantumCircuit], circuits_2: Sequence[QuantumCircuit], - values_1: Sequence[Sequence[float]] | None = None, - values_2: Sequence[Sequence[float]] | None = None, + values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, + values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, ) -> Sequence[float]: """ Preprocesses input parameter values to match the fidelity @@ -218,10 +223,10 @@ def _construct_value_list( @abstractmethod def _run( self, - circuits_1: Sequence[QuantumCircuit], - circuits_2: Sequence[QuantumCircuit], - values_1: Sequence[Sequence[float]] | None = None, - values_2: Sequence[Sequence[float]] | None = None, + circuits_1: QuantumCircuit | Sequence[QuantumCircuit], + circuits_2: QuantumCircuit | Sequence[QuantumCircuit], + values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, + values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, **run_options, ) -> StateFidelityResult: r""" @@ -246,10 +251,10 @@ def _run( def run( self, - circuits_1: Sequence[QuantumCircuit], - circuits_2: Sequence[QuantumCircuit], - values_1: Sequence[Sequence[float]] | None = None, - values_2: Sequence[Sequence[float]] | None = None, + circuits_1: QuantumCircuit | Sequence[QuantumCircuit], + circuits_2: QuantumCircuit | Sequence[QuantumCircuit], + values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, + values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, **run_options, ) -> AlgorithmJob: r""" diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 3bbf75c14e9c..a6c4febbdd23 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -83,10 +83,10 @@ def create_fidelity_circuit( def _run( self, - circuits_1: Sequence[QuantumCircuit], - circuits_2: Sequence[QuantumCircuit], - values_1: Sequence[Sequence[float]] | None = None, - values_2: Sequence[Sequence[float]] | None = None, + circuits_1: QuantumCircuit | Sequence[QuantumCircuit], + circuits_2: QuantumCircuit | Sequence[QuantumCircuit], + values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, + values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, **run_options, ) -> StateFidelityResult: r""" diff --git a/test/python/algorithms/state_fidelities/test_compute_uncompute.py b/test/python/algorithms/state_fidelities/test_compute_uncompute.py index 1b294f354df3..d4a605dbb964 100644 --- a/test/python/algorithms/state_fidelities/test_compute_uncompute.py +++ b/test/python/algorithms/state_fidelities/test_compute_uncompute.py @@ -57,7 +57,7 @@ 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])) @@ -182,33 +182,35 @@ def test_asymmetric_params(self): result = job.result() np.testing.assert_allclose(result.fidelities, np.array([0.5, 0.25, 0.25, 0.0]), atol=1e-16) - def test_async_join(self): - """test for run method using join.""" - - fidelity = ComputeUncompute(self._sampler) - jobs = [] - for left_param, right_param in zip(self._left_params, self._right_params): - job = fidelity.run([self._circuit[0]], [self._circuit[1]], left_param, right_param) - jobs.append(job) - - results = [job.result().fidelities[0] for job in jobs] - np.testing.assert_allclose(results, np.array([1.0, 0.5, 0.25, 0.0]), atol=1e-16) - - def test_values_types(self): + def test_input_format(self): + """test for different input format variations""" fidelity = ComputeUncompute(self._sampler) circuit = RealAmplitudes(2) values = np.random.random(circuit.num_parameters) shift = np.ones_like(values) * 0.01 + # lists of circuits, lists of numpy arrays job = fidelity.run([circuit], [circuit], [values], [values + shift]) result_1 = job.result() + # lists of circuits, lists of lists shift_val = values + shift job = fidelity.run([circuit], [circuit], [values.tolist()], [shift_val.tolist()]) result_2 = job.result() + # circuits, lists + shift_val = values + shift + job = fidelity.run(circuit, circuit, values.tolist(), shift_val.tolist()) + result_3 = job.result() + + # circuits, np.arrays + job = fidelity.run(circuit, circuit, values, values + shift) + result_4 = job.result() + np.testing.assert_allclose(result_1.fidelities, result_2.fidelities, atol=1e-16) + np.testing.assert_allclose(result_1.fidelities, result_3.fidelities, atol=1e-16) + np.testing.assert_allclose(result_1.fidelities, result_4.fidelities, atol=1e-16) if __name__ == "__main__": From d5bbba6cec409e26cc025fe58624154932aa1444 Mon Sep 17 00:00:00 2001 From: ElePT Date: Thu, 1 Sep 2022 17:31:48 +0200 Subject: [PATCH 79/92] Support single circuits --- .../algorithms/state_fidelities/base_state_fidelity.py | 10 ++++++---- .../algorithms/state_fidelities/compute_uncompute.py | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index d7d39062a674..fb989aabbf5a 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -31,6 +31,7 @@ class BaseStateFidelity(ABC): fidelity method implementation, but can be always defined as the state overlap: .. math:: + |\langle\psi(x)|\phi(y)\rangle|^2 where :math:`x` and :math:`y` are optional parametrizations of the @@ -39,16 +40,14 @@ class BaseStateFidelity(ABC): """ - def __init__( - self, - ) -> None: + def __init__(self) -> None: # use cache for preventing unnecessary circuit compositions self._circuit_cache: Mapping[(int, int), QuantumCircuit] = {} @staticmethod def _preprocess_values( - circuits: Sequence[QuantumCircuit], + circuits: QuantumCircuit | Sequence[QuantumCircuit], values: Sequence[float] | Sequence[Sequence[float]] | None = None, ) -> Sequence[Sequence[float]]: """ @@ -68,6 +67,9 @@ def _preprocess_values( circuit parameters """ + if isinstance(circuits, QuantumCircuit): + circuits = [circuits] + if values is None: for circuit in circuits: if circuit.num_parameters != 0: diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index a6c4febbdd23..3ae63452dd8b 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -34,6 +34,7 @@ class ComputeUncompute(BaseStateFidelity): The fidelity can be defined as the state overlap: .. math:: + |\langle\psi(x)|\phi(y)\rangle|^2 where :math:`x` and :math:`y` are optional parametrizations of the @@ -45,6 +46,7 @@ class ComputeUncompute(BaseStateFidelity): A., Chow, J. M., & Gambetta, J. M. (2019). Supervised learning with quantum-enhanced feature spaces. Nature, 567(7747), 209-212. `arXiv:1804.11326v2 [quant-ph] `_ + """ def __init__(self, sampler: BaseSampler, **run_options) -> None: From 5e5a01203eef973cb19aa79ffe1d4ffb43e648e7 Mon Sep 17 00:00:00 2001 From: ElePT Date: Fri, 2 Sep 2022 11:19:49 +0200 Subject: [PATCH 80/92] Add fidelity interface using primitives --- ...interface-primitives-dc543d079ecaa8dd.yaml | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 releasenotes/notes/add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml diff --git a/releasenotes/notes/add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml b/releasenotes/notes/add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml new file mode 100644 index 000000000000..47dc2cfcaf67 --- /dev/null +++ b/releasenotes/notes/add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml @@ -0,0 +1,27 @@ +--- +features: + - | + Add new interfaces to calculate state fidelities/overlaps + for pairs of quantum circuits (that can be parametrized). Apart from + the base class (:class:`qiskit.algorithms.state_fidelities.BaseStateFidelity`), + there is now an implementation of the compute-uncompute method that leverages + the sampler primitive (:class:`qiskit.algorithms.state_fidelities.ComputeUncompute`). + + Example:: + .. code-block:: python + + import numpy as np + from qiskit.primitives import Sampler + from qiskit.algorithms.state_fidelities import ComputeUncompute + from qiskit. import RealAmplitudes + + sampler = Sampler(...) + fidelity = ComputeUncompute(sampler) + circuit = RealAmplitudes(2) + values = np.random.random(circuit.num_parameters) + shift = np.ones_like(values) * 0.01 + + job = fidelity.run([circuit], [circuit], [values], [values+shift]) + fidelities = job.result().fidelities + + From 9809e51f2a1a2fbd307d4a5926a17fd80654c97d Mon Sep 17 00:00:00 2001 From: ElePT Date: Fri, 2 Sep 2022 13:33:17 +0200 Subject: [PATCH 81/92] Apply reviews --- qiskit/algorithms/algorithm_job.py | 10 ++++-- .../state_fidelities/base_state_fidelity.py | 20 +++++++---- .../state_fidelities/compute_uncompute.py | 33 ++++++++----------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/qiskit/algorithms/algorithm_job.py b/qiskit/algorithms/algorithm_job.py index f25c49689c8b..16db4df93dfc 100644 --- a/qiskit/algorithms/algorithm_job.py +++ b/qiskit/algorithms/algorithm_job.py @@ -11,8 +11,14 @@ # that they have been altered from the originals. """ -This module introduces the AlgorithmJob class for typing purposes +AlgorithmJob class """ from qiskit.primitives.primitive_job import PrimitiveJob -AlgorithmJob = PrimitiveJob + +class AlgorithmJob(PrimitiveJob): + """ + This empty class is introduced for typing purposes. + """ + + pass diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index fb989aabbf5a..a14235f5f4ad 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -65,6 +65,7 @@ def _preprocess_values( Raises: ValueError: if the number of parameter values doesn't match the number of circuit parameters + TypeError: if the input values are not a sequence. """ if isinstance(circuits, QuantumCircuit): @@ -79,16 +80,21 @@ def _preprocess_values( ) return [[]] else: + # Support ndarray if isinstance(values, np.ndarray): values = values.tolist() if len(values) > 0 and isinstance(values[0], np.ndarray): values = [v.tolist() for v in values] - # ensure 2d list - if not isinstance(values, list): - values = [values] - if len(values) > 0 and not isinstance(values[0], list): + if not isinstance(values, Sequence): + raise TypeError( + f"Expected a sequence of numerical parameter values, " + f"but got input type {type(values)} instead." + ) + + # ensure 2d + if len(values) > 0 and not isinstance(values[0], Sequence): values = [values] return values @@ -191,7 +197,7 @@ def _construct_value_list( circuits_2: Sequence[QuantumCircuit], values_1: Sequence[float] | Sequence[Sequence[float]] | None = None, values_2: Sequence[float] | Sequence[Sequence[float]] | None = None, - ) -> Sequence[float]: + ) -> list[float]: """ Preprocesses input parameter values to match the fidelity circuit parametrization, and return in list format. @@ -213,9 +219,9 @@ def _construct_value_list( values = [] if len(values_2[0]) == 0: - values = values_1 + values = list(values_1) elif len(values_1[0]) == 0: - values = values_2 + values = list(values_2) else: for (val_1, val_2) in zip(values_1, values_2): values.append(val_1 + val_2) diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 3ae63452dd8b..2ac4e656fa3b 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -18,9 +18,8 @@ from copy import copy from qiskit import QuantumCircuit -from qiskit.exceptions import QiskitError +from qiskit.algorithms import AlgorithmError from qiskit.primitives import BaseSampler -from qiskit.providers import JobStatus from .base_state_fidelity import BaseStateFidelity from .state_fidelity_result import StateFidelityResult @@ -111,7 +110,7 @@ def _run( Raises: ValueError: At least one pair of circuits must be defined. - QiskitError: If the sampler job is not completed successfully. + AlgorithmError: If the sampler job is not completed successfully. """ circuits = self._construct_circuits(circuits_1, circuits_2) @@ -129,21 +128,17 @@ def _run( job = self._sampler.run(circuits=circuits, parameter_values=values, **run_opts) - result = job.result() - status = job.status() + try: + result = job.result() + except Exception as exc: + raise AlgorithmError("Sampler job failed!") from exc - if status is JobStatus.DONE: - raw_fidelities = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] - fidelities = self._truncate_fidelities(raw_fidelities) + raw_fidelities = [prob_dist.get(0, 0) for prob_dist in result.quasi_dists] + fidelities = self._truncate_fidelities(raw_fidelities) - return StateFidelityResult( - fidelities=fidelities, - raw_fidelities=raw_fidelities, - metadata=result.metadata, - run_options=run_opts, - ) - else: - raise QiskitError( - f"The sampler fidelity job was not completed succesfully. " - f"Job status = {status}." - ) + return StateFidelityResult( + fidelities=fidelities, + raw_fidelities=raw_fidelities, + metadata=result.metadata, + run_options=run_opts, + ) From 21960b3b28bcabebeecd661f38c4b1973e12f799 Mon Sep 17 00:00:00 2001 From: ElePT Date: Fri, 2 Sep 2022 14:03:10 +0200 Subject: [PATCH 82/92] Fix docs --- qiskit/algorithms/state_fidelities/compute_uncompute.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 2ac4e656fa3b..e17073cb883e 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -53,6 +53,7 @@ def __init__(self, sampler: BaseSampler, **run_options) -> None: Args: sampler: Sampler primitive instance. run_options: Backend runtime options used for circuit execution. + Raises: ValueError: If the sampler is not an instance of ``BaseSampler``. """ From 2a32785c47776d3920214804d044def96c17a265 Mon Sep 17 00:00:00 2001 From: ElePT Date: Fri, 2 Sep 2022 14:51:15 +0200 Subject: [PATCH 83/92] Fix docs --- qiskit/algorithms/state_fidelities/compute_uncompute.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index e17073cb883e..093f14fa92d2 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -41,11 +41,10 @@ class ComputeUncompute(BaseStateFidelity): ``circuit_1`` and ``circuit_2``, respectively. **Reference:** - 1. Havlíček, V., Córcoles, A. D., Temme, K., Harrow, A. W., Kandala, - A., Chow, J. M., & Gambetta, J. M. (2019). Supervised learning - with quantum-enhanced feature spaces. Nature, 567(7747), 209-212. - `arXiv:1804.11326v2 [quant-ph] `_ - + [1] Havlíček, V., Córcoles, A. D., Temme, K., Harrow, A. W., Kandala, + A., Chow, J. M., & Gambetta, J. M. (2019). Supervised learning + with quantum-enhanced feature spaces. Nature, 567(7747), 209-212. + `arXiv:1804.11326v2 [quant-ph] `_ """ def __init__(self, sampler: BaseSampler, **run_options) -> None: From 3f3d5d96e232ca1fcb61e7fb50ee9f2fe752c1a8 Mon Sep 17 00:00:00 2001 From: ElePT Date: Fri, 2 Sep 2022 14:52:55 +0200 Subject: [PATCH 84/92] Fix docs --- qiskit/algorithms/state_fidelities/compute_uncompute.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 093f14fa92d2..a286008d51a9 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -30,7 +30,7 @@ class ComputeUncompute(BaseStateFidelity): This class leverages the sampler primitive to calculate the state fidelity of two quantum circuits following the compute-uncompute method (see [1] for further reference). - The fidelity can be defined as the state overlap: + The fidelity can be defined as the state overlap. .. math:: @@ -45,6 +45,7 @@ class ComputeUncompute(BaseStateFidelity): A., Chow, J. M., & Gambetta, J. M. (2019). Supervised learning with quantum-enhanced feature spaces. Nature, 567(7747), 209-212. `arXiv:1804.11326v2 [quant-ph] `_ + """ def __init__(self, sampler: BaseSampler, **run_options) -> None: From 637e63efcab0b190efb3fe96331fdcba95408451 Mon Sep 17 00:00:00 2001 From: ElePT Date: Fri, 2 Sep 2022 15:24:03 +0200 Subject: [PATCH 85/92] Fix lint --- qiskit/algorithms/state_fidelities/compute_uncompute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index a286008d51a9..6e0723e21f1f 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -45,7 +45,7 @@ class ComputeUncompute(BaseStateFidelity): A., Chow, J. M., & Gambetta, J. M. (2019). Supervised learning with quantum-enhanced feature spaces. Nature, 567(7747), 209-212. `arXiv:1804.11326v2 [quant-ph] `_ - + """ def __init__(self, sampler: BaseSampler, **run_options) -> None: From 78d6b796b9c4e60396c185965bc3c93ac1762f59 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:59:30 +0200 Subject: [PATCH 86/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index a14235f5f4ad..f745dadd949e 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -248,9 +248,9 @@ def _run( values_1: Numerical parameters to be bound to the first set of circuits values_2: Numerical parameters to be bound to the second set of circuits. run_options: Backend runtime options used for circuit execution. The order - of priority is: run_options in ``run`` method > fidelity's default - run_options > primitive's default setting. - Higher priority setting overrides lower priority setting. + of priority is\: run_options in ``run`` method > fidelity's default + run_options > primitive's default setting. + Higher priority setting overrides lower priority setting. Returns: The result of the fidelity calculation. From f17c92fc485dc03c52e3fc97b9a1c578a2e9c4d4 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:59:42 +0200 Subject: [PATCH 87/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index f745dadd949e..47bfcee5623f 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -277,9 +277,9 @@ def run( values_1: Numerical parameters to be bound to the first set of circuits. values_2: Numerical parameters to be bound to the second set of circuits. run_options: Backend runtime options used for circuit execution. The order - of priority is: run_options in ``run`` method > fidelity's default - run_options > primitive's default setting. - Higher priority setting overrides lower priority setting. + of priority is\: run_options in ``run`` method > fidelity's default + run_options > primitive's default setting. + Higher priority setting overrides lower priority setting. Returns: Primitive job for the fidelity calculation. From 5cda299aaab98767bfa5377bb7f5f4710dce3d18 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Fri, 2 Sep 2022 20:59:51 +0200 Subject: [PATCH 88/92] Update qiskit/algorithms/state_fidelities/compute_uncompute.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- qiskit/algorithms/state_fidelities/compute_uncompute.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/algorithms/state_fidelities/compute_uncompute.py b/qiskit/algorithms/state_fidelities/compute_uncompute.py index 6e0723e21f1f..ff9080e5d3ba 100644 --- a/qiskit/algorithms/state_fidelities/compute_uncompute.py +++ b/qiskit/algorithms/state_fidelities/compute_uncompute.py @@ -102,9 +102,9 @@ def _run( values_1: Numerical parameters to be bound to the first circuits. values_2: Numerical parameters to be bound to the second circuits. run_options: Backend runtime options used for circuit execution. The order - of priority is: run_options in ``run`` method > fidelity's default - run_options > primitive's default setting. - Higher priority setting overrides lower priority setting. + of priority is\: run_options in ``run`` method > fidelity's default + run_options > primitive's default setting. + Higher priority setting overrides lower priority setting. Returns: The result of the fidelity calculation. From f812476a0e9512611dfff454b61bf801488bc6a1 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Fri, 2 Sep 2022 21:00:09 +0200 Subject: [PATCH 89/92] Update releasenotes/notes/add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- .../add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml b/releasenotes/notes/add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml index 47dc2cfcaf67..96e1977096ba 100644 --- a/releasenotes/notes/add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml +++ b/releasenotes/notes/add-fidelity-interface-primitives-dc543d079ecaa8dd.yaml @@ -1,7 +1,7 @@ --- features: - | - Add new interfaces to calculate state fidelities/overlaps + Add new algorithms to calculate state fidelities/overlaps for pairs of quantum circuits (that can be parametrized). Apart from the base class (:class:`qiskit.algorithms.state_fidelities.BaseStateFidelity`), there is now an implementation of the compute-uncompute method that leverages From c6f25c18105bb93f8d208e3d9e42a20a6a7b1912 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Fri, 2 Sep 2022 21:02:11 +0200 Subject: [PATCH 90/92] Update qiskit/algorithms/state_fidelities/__init__.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- qiskit/algorithms/state_fidelities/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/algorithms/state_fidelities/__init__.py b/qiskit/algorithms/state_fidelities/__init__.py index 8e6ced7d2a05..ea8e4e03bf89 100644 --- a/qiskit/algorithms/state_fidelities/__init__.py +++ b/qiskit/algorithms/state_fidelities/__init__.py @@ -32,6 +32,7 @@ :toctree: ../stubs/ StateFidelityResult + """ from .base_state_fidelity import BaseStateFidelity From d745b4e11f9fe61760324f9c284525d9037613e9 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Fri, 2 Sep 2022 21:02:21 +0200 Subject: [PATCH 91/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 47bfcee5623f..40e08373e86c 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -123,6 +123,7 @@ def create_fidelity_circuit( """ Implementation-dependent method to create a fidelity circuit from 2 circuit inputs. + Args: circuit_1: (Parametrized) quantum circuit. circuit_2: (Parametrized) quantum circuit. From 9fc65ce5eb32374a10717257199f6e3ddf4d7182 Mon Sep 17 00:00:00 2001 From: ElePT <57907331+ElePT@users.noreply.github.com> Date: Fri, 2 Sep 2022 21:37:10 +0200 Subject: [PATCH 92/92] Update qiskit/algorithms/state_fidelities/base_state_fidelity.py Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com> --- qiskit/algorithms/state_fidelities/base_state_fidelity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/algorithms/state_fidelities/base_state_fidelity.py b/qiskit/algorithms/state_fidelities/base_state_fidelity.py index 40e08373e86c..75f4d632396d 100644 --- a/qiskit/algorithms/state_fidelities/base_state_fidelity.py +++ b/qiskit/algorithms/state_fidelities/base_state_fidelity.py @@ -159,7 +159,7 @@ def _construct_circuits( if isinstance(circuits_2, QuantumCircuit): circuits_2 = [circuits_2] - if not len(circuits_1) == len(circuits_2): + if len(circuits_1) != len(circuits_2): raise ValueError( f"The length of the first circuit list({len(circuits_1)}) " f"and second circuit list ({len(circuits_2)}) is not the same."