From bed3ed09c781b1ae567f1938c32d8b75f5daf8ae Mon Sep 17 00:00:00 2001 From: Cmurilochem Date: Fri, 12 Jul 2024 18:07:53 +0200 Subject: [PATCH 1/8] Add initial wrapper to Estimators --- vqls_prototype/hadamard_test/hadamard_test.py | 35 ++++++--- vqls_prototype/primitives/__init__.py | 0 .../primitives/estimator_run_builder.py | 78 +++++++++++++++++++ 3 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 vqls_prototype/primitives/__init__.py create mode 100644 vqls_prototype/primitives/estimator_run_builder.py diff --git a/vqls_prototype/hadamard_test/hadamard_test.py b/vqls_prototype/hadamard_test/hadamard_test.py index 72e629f..6dbce7a 100644 --- a/vqls_prototype/hadamard_test/hadamard_test.py +++ b/vqls_prototype/hadamard_test/hadamard_test.py @@ -8,6 +8,10 @@ import numpy as np import numpy.typing as npt +from qiskit.primitives.estimator import EstimatorResult +from qiskit.primitives.containers import PrimitiveResult +from ..primitives.estimator_run_builder import EstimatorRunBuilder + class BatchHadammardTest: r"""Class that execute batches of Hadammard Test""" @@ -36,15 +40,19 @@ def get_values(self, primitive, parameter_sets: List, zne_strategy=None) -> List """ ncircuits = len(self.circuits) + all_parameter_sets = [parameter_sets] * ncircuits + + test = EstimatorRunBuilder( + primitive, + self.circuits, + self.observable, + all_parameter_sets, + options={"shots": self.shots} + ) try: if zne_strategy is None: - job = primitive.run( - self.circuits, - self.observable, - [parameter_sets] * ncircuits, - shots=self.shots, - ) + job = test.build_run() else: job = primitive.run( self.circuits, @@ -236,9 +244,18 @@ def post_processing(self, estimator_result) -> npt.NDArray[np.cdouble]: Returns: npt.NDArray[np.cdouble]: value of the test """ - return np.array([1.0 - 2.0 * val for val in estimator_result.values]).astype( - "complex128" - ) + if isinstance(estimator_result, EstimatorResult): + print(estimator_result.values) + return np.array([1.0 - 2.0 * val for val in estimator_result.values]).astype( + "complex128" + ) + + if isinstance(estimator_result, PrimitiveResult): + return np.array([1.0 - 2.0 * val.data.evs for val in estimator_result]).astype( + "complex128" + ) + + raise NotImplementedError(f"Not implemented for {type(estimator_result)} classes.") def get_value(self, estimator, parameter_sets: List, zne_strategy=None) -> List: """Compute the value of the test diff --git a/vqls_prototype/primitives/__init__.py b/vqls_prototype/primitives/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vqls_prototype/primitives/estimator_run_builder.py b/vqls_prototype/primitives/estimator_run_builder.py new file mode 100644 index 0000000..d158816 --- /dev/null +++ b/vqls_prototype/primitives/estimator_run_builder.py @@ -0,0 +1,78 @@ +from typing import Union, List, Callable, Tuple, Dict + +from qiskit import QuantumCircuit +from qiskit.quantum_info import SparsePauliOp +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager + +from qiskit.primitives import Estimator +from qiskit_aer.primitives import EstimatorV2 as aer_EstimatorV2 +from qiskit_aer.primitives import Estimator as aer_Estimator +from qiskit_ibm_runtime import Estimator as ibm_runtime_Estimator +from qiskit_ibm_runtime import EstimatorV2 as ibm_runtime_EstimatorV2 + +EstimatorValidType = Union[ + Estimator, + aer_Estimator, + aer_EstimatorV2, + ibm_runtime_Estimator, + ibm_runtime_EstimatorV2 +] + + +class EstimatorRunBuilder: + """Docs..""" + def __init__( + self, + estimator: EstimatorValidType, + circuits: List[QuantumCircuit], + observables: List[SparsePauliOp], + parameter_sets: List, + options: Dict + ): + """Docs...""" + self.estimator = estimator + self.provenance = self.find_estimator_provenance() + + self.circuits = circuits + self.observables = observables + self.parameter_sets = parameter_sets + + self.shots = options.pop("shots", None) + + def find_estimator_provenance(self) -> Tuple[str, str]: + """XXXX""" + return ( + self.estimator.__class__.__module__.split('.')[0], + self.estimator.__class__.__name__ + ) + + def build_run(self) -> Callable: + """XXX""" + if self.provenance == ('qiskit', 'Estimator'): + return self._build_qiskit_estimator_run() + + if self.provenance == ('qiskit_aer', 'EstimatorV2'): + return self._build_aer_qiskit_estimatorV2_run() + + raise NotImplementedError( + f"'EstimatorRunBuilder' not compatible with {self.provenance}." + ) + + def _build_qiskit_estimator_run(self) -> Callable: + """XXX""" + return self.estimator.run( + self.circuits, + self.observables, + self.parameter_sets, + shots=self.shots, + ) + + def _build_aer_qiskit_estimatorV2_run(self) -> Callable: + """XXX""" + pubs = [] + pm = generate_preset_pass_manager(optimization_level=1, backend=self.estimator._backend) + for qc, obs, param in zip(self.circuits, self.observables, self.parameter_sets): + isa_circuit = pm.run(qc) + isa_obs = obs.apply_layout(isa_circuit.layout) + pubs.append((isa_circuit, isa_obs, param)) + return self.estimator.run(pubs) From 30a552b1d908b989e76285c893a42da9cf747e34 Mon Sep 17 00:00:00 2001 From: Cmurilochem Date: Mon, 15 Jul 2024 09:04:08 +0200 Subject: [PATCH 2/8] Refactoring EstimatorRunBuilder and add test --- tests/test_vqls.py | 13 ++- vqls_prototype/hadamard_test/hadamard_test.py | 41 ++++---- .../primitives/estimator_run_builder.py | 99 ++++++++++++++----- 3 files changed, 107 insertions(+), 46 deletions(-) diff --git a/tests/test_vqls.py b/tests/test_vqls.py index 7504159..9fbd88a 100644 --- a/tests/test_vqls.py +++ b/tests/test_vqls.py @@ -22,6 +22,9 @@ from qiskit_algorithms.optimizers import ADAM from qiskit.primitives import Estimator, Sampler + +from qiskit_aer.primitives import EstimatorV2 as aer_EstimatorV2 + from vqls_prototype import VQLS # 8-11-2023 @@ -43,10 +46,12 @@ def setUp(self): self.estimators = ( Estimator(), + aer_EstimatorV2(), # AerEstimator(), ) self.samplers = ( + Sampler(), Sampler(), # AerSampler(), ) @@ -70,8 +75,8 @@ def test_numpy_input(self): zip(self.estimators, self.samplers) ): for iopt, opt in enumerate(self.options): - if iprim == 1 and iopt == 2: - continue + # if iprim == 1 and iopt == 2: + # continue vqls = VQLS( estimator, ansatz, @@ -105,8 +110,8 @@ def test_circuit_input_statevector(self): zip(self.estimators, self.samplers) ): for iopt, opt in enumerate(self.options): - if iprim == 1 and iopt == 2: - continue + # if iprim == 1 and iopt == 2: + # continue vqls = VQLS( estimator, ansatz, diff --git a/vqls_prototype/hadamard_test/hadamard_test.py b/vqls_prototype/hadamard_test/hadamard_test.py index 6dbce7a..3d061e5 100644 --- a/vqls_prototype/hadamard_test/hadamard_test.py +++ b/vqls_prototype/hadamard_test/hadamard_test.py @@ -42,17 +42,17 @@ def get_values(self, primitive, parameter_sets: List, zne_strategy=None) -> List ncircuits = len(self.circuits) all_parameter_sets = [parameter_sets] * ncircuits - test = EstimatorRunBuilder( + estimator_run_builder = EstimatorRunBuilder( primitive, self.circuits, self.observable, all_parameter_sets, - options={"shots": self.shots} + options={"shots": self.shots}, ) try: if zne_strategy is None: - job = test.build_run() + job = estimator_run_builder.build_run() else: job = primitive.run( self.circuits, @@ -245,17 +245,19 @@ def post_processing(self, estimator_result) -> npt.NDArray[np.cdouble]: npt.NDArray[np.cdouble]: value of the test """ if isinstance(estimator_result, EstimatorResult): - print(estimator_result.values) - return np.array([1.0 - 2.0 * val for val in estimator_result.values]).astype( - "complex128" - ) + return np.array( + [1.0 - 2.0 * val for val in estimator_result.values] + ).astype("complex128") if isinstance(estimator_result, PrimitiveResult): - return np.array([1.0 - 2.0 * val.data.evs for val in estimator_result]).astype( - "complex128" - ) + return np.array( + [1.0 - 2.0 * val.data.evs for val in estimator_result] + ).astype("complex128") - raise NotImplementedError(f"Not implemented for {type(estimator_result)} classes.") + raise NotImplementedError( + f"Cannot post processing for {type(estimator_result)} type class." + f"Please, refer to {self.__class__.__name__}.post_processing()." + ) def get_value(self, estimator, parameter_sets: List, zne_strategy=None) -> List: """Compute the value of the test @@ -269,15 +271,20 @@ def get_value(self, estimator, parameter_sets: List, zne_strategy=None) -> List: """ ncircuits = len(self.circuits) + all_parameter_sets = [parameter_sets] * ncircuits + all_observables = [self.observable] * ncircuits + + estimator_run_builder = EstimatorRunBuilder( + estimator, + self.circuits, + all_observables, + all_parameter_sets, + options={"shots": self.shots}, + ) try: if zne_strategy is None: - job = estimator.run( - self.circuits, - [self.observable] * ncircuits, - [parameter_sets] * ncircuits, - shots=self.shots, - ) + job = estimator_run_builder.build_run() else: job = estimator.run( self.circuits, diff --git a/vqls_prototype/primitives/estimator_run_builder.py b/vqls_prototype/primitives/estimator_run_builder.py index d158816..076b9d8 100644 --- a/vqls_prototype/primitives/estimator_run_builder.py +++ b/vqls_prototype/primitives/estimator_run_builder.py @@ -1,4 +1,4 @@ -from typing import Union, List, Callable, Tuple, Dict +from typing import Union, List, Callable, Tuple, Dict, Any from qiskit import QuantumCircuit from qiskit.quantum_info import SparsePauliOp @@ -15,64 +15,113 @@ aer_Estimator, aer_EstimatorV2, ibm_runtime_Estimator, - ibm_runtime_EstimatorV2 + ibm_runtime_EstimatorV2, ] class EstimatorRunBuilder: - """Docs..""" + """ + A class to build and configure estimator runs based on their provenance and options. + + Attributes: + estimator (EstimatorValidType): The quantum estimator instance. + circuits (List[QuantumCircuit]): List of quantum circuits. + observables (List[SparsePauliOp]): List of observables. + parameter_sets (List[List[float]]): List of parameter sets. + """ + def __init__( self, estimator: EstimatorValidType, circuits: List[QuantumCircuit], observables: List[SparsePauliOp], - parameter_sets: List, - options: Dict + parameter_sets: List[List[float]], + options: Dict[str, Any], ): - """Docs...""" - self.estimator = estimator - self.provenance = self.find_estimator_provenance() + """ + Initializes the EstimatorRunBuilder with the given estimator, circuits, observables, + parameter sets, and options. + Args: + estimator (EstimatorValidType): The estimator to use for runs. + circuits (List[QuantumCircuit]): The quantum circuits to run. + observables (List[SparsePauliOp]): The observables to measure. + parameter_sets (List[List[float]]): The parameters to vary in the circuits. + options (Dict[str, Any]): Configuration options such as number of shots. + """ + self.estimator = estimator self.circuits = circuits self.observables = observables self.parameter_sets = parameter_sets - self.shots = options.pop("shots", None) + self.seed = options.pop("seed", None) + self.provenance = self.find_estimator_provenance() def find_estimator_provenance(self) -> Tuple[str, str]: - """XXXX""" + """Determines the provenance of the estimator based on its class and module.""" return ( - self.estimator.__class__.__module__.split('.')[0], - self.estimator.__class__.__name__ + self.estimator.__class__.__module__.split(".")[0], + self.estimator.__class__.__name__, ) def build_run(self) -> Callable: - """XXX""" - if self.provenance == ('qiskit', 'Estimator'): - return self._build_qiskit_estimator_run() + """ + Configures and returns a callable function for estimator runs based on its provenance. - if self.provenance == ('qiskit_aer', 'EstimatorV2'): - return self._build_aer_qiskit_estimatorV2_run() + Raises: + NotImplementedError: If the estimator's provenance is not supported. - raise NotImplementedError( - f"'EstimatorRunBuilder' not compatible with {self.provenance}." - ) + Returns: + Callable: A configured callable function to execute the estimator run. + """ + builder_function = self._select_run_builder() + return builder_function() - def _build_qiskit_estimator_run(self) -> Callable: - """XXX""" + def _select_run_builder(self) -> Callable: + """Selects the appropriate builder function based on the estimator's provenance.""" + builders = { + ("qiskit", "Estimator"): self._build_native_qiskit_estimator_run, + ("qiskit_aer", "EstimatorV2"): self._build_estimatorv2_run, + ("qiskit_aer", "Estimator"): self._build_estimatorv1_run, + } + try: + return builders[self.provenance] + except KeyError: + raise NotImplementedError( + f"{self.__class__.__name__} not compatible with {self.provenance}." + ) + + def _build_native_qiskit_estimator_run(self) -> Callable: + """Builds a run function for a standard qiskit Estimator.""" return self.estimator.run( self.circuits, self.observables, self.parameter_sets, shots=self.shots, + seed=self.seed, ) - def _build_aer_qiskit_estimatorV2_run(self) -> Callable: - """XXX""" + def _build_estimatorv2_run(self) -> Callable: + """Builds a run function for qiskit-aer and qiskit-ibm-runtime EstimatorV2 with transpilation.""" + backend = self.estimator._backend + optimization_level = 1 + pm = generate_preset_pass_manager(optimization_level, backend) pubs = [] - pm = generate_preset_pass_manager(optimization_level=1, backend=self.estimator._backend) for qc, obs, param in zip(self.circuits, self.observables, self.parameter_sets): isa_circuit = pm.run(qc) isa_obs = obs.apply_layout(isa_circuit.layout) pubs.append((isa_circuit, isa_obs, param)) return self.estimator.run(pubs) + + def _build_estimatorv1_run(self): + """ + Attempts to build a run function for EstimatorV1, which will be soon deprecated. + + Raises: + NotImplementedError: + Indicates that EstimatorV1 will be soon deprecated and + suggests using EstimatorV2 instead. + """ + raise NotImplementedError( + "EstimatorV1 will be soon deprecated. Please, use EstimatorV2 implementation." + ) From c65099b62d3de6819aba8aad3e2dc0f9f00d1102 Mon Sep 17 00:00:00 2001 From: Cmurilochem Date: Mon, 15 Jul 2024 10:30:15 +0200 Subject: [PATCH 3/8] Add init path --- tests/test_vqls.py | 8 ++++---- vqls_prototype/hadamard_test/hadamard_test.py | 2 +- vqls_prototype/primitives/__init__.py | 8 ++++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/test_vqls.py b/tests/test_vqls.py index 9fbd88a..5d08ce1 100644 --- a/tests/test_vqls.py +++ b/tests/test_vqls.py @@ -75,8 +75,8 @@ def test_numpy_input(self): zip(self.estimators, self.samplers) ): for iopt, opt in enumerate(self.options): - # if iprim == 1 and iopt == 2: - # continue + if iprim == 1 and iopt == 2: + continue vqls = VQLS( estimator, ansatz, @@ -110,8 +110,8 @@ def test_circuit_input_statevector(self): zip(self.estimators, self.samplers) ): for iopt, opt in enumerate(self.options): - # if iprim == 1 and iopt == 2: - # continue + if iprim == 1 and iopt == 2: + continue vqls = VQLS( estimator, ansatz, diff --git a/vqls_prototype/hadamard_test/hadamard_test.py b/vqls_prototype/hadamard_test/hadamard_test.py index 3d061e5..bc042dc 100644 --- a/vqls_prototype/hadamard_test/hadamard_test.py +++ b/vqls_prototype/hadamard_test/hadamard_test.py @@ -10,7 +10,7 @@ from qiskit.primitives.estimator import EstimatorResult from qiskit.primitives.containers import PrimitiveResult -from ..primitives.estimator_run_builder import EstimatorRunBuilder +from vqls_prototype.primitives import EstimatorRunBuilder class BatchHadammardTest: diff --git a/vqls_prototype/primitives/__init__.py b/vqls_prototype/primitives/__init__.py index e69de29..afab4e2 100644 --- a/vqls_prototype/primitives/__init__.py +++ b/vqls_prototype/primitives/__init__.py @@ -0,0 +1,8 @@ +"""Primitive builder package.""" + +from .estimator_run_builder import EstimatorRunBuilder + + +__all__ = [ + "EstimatorRunBuilder", +] From ac3878faeddd138ae03c561712628f6dbd598063 Mon Sep 17 00:00:00 2001 From: Cmurilochem Date: Mon, 15 Jul 2024 10:58:25 +0200 Subject: [PATCH 4/8] black and pylint formatting --- vqls_prototype/primitives/estimator_run_builder.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vqls_prototype/primitives/estimator_run_builder.py b/vqls_prototype/primitives/estimator_run_builder.py index 076b9d8..9720a83 100644 --- a/vqls_prototype/primitives/estimator_run_builder.py +++ b/vqls_prototype/primitives/estimator_run_builder.py @@ -86,10 +86,10 @@ def _select_run_builder(self) -> Callable: } try: return builders[self.provenance] - except KeyError: + except KeyError as err: raise NotImplementedError( f"{self.__class__.__name__} not compatible with {self.provenance}." - ) + ) from err def _build_native_qiskit_estimator_run(self) -> Callable: """Builds a run function for a standard qiskit Estimator.""" @@ -102,8 +102,8 @@ def _build_native_qiskit_estimator_run(self) -> Callable: ) def _build_estimatorv2_run(self) -> Callable: - """Builds a run function for qiskit-aer and qiskit-ibm-runtime EstimatorV2 with transpilation.""" - backend = self.estimator._backend + """Builds a run function for qiskit-aer and qiskit-ibm-runtime EstimatorV2.""" + backend = self.estimator._backend # pylint: disable=protected-access optimization_level = 1 pm = generate_preset_pass_manager(optimization_level, backend) pubs = [] From 86706b58bf067cfaf18f267aef91badbc88462cf Mon Sep 17 00:00:00 2001 From: Cmurilochem Date: Mon, 15 Jul 2024 13:11:27 +0200 Subject: [PATCH 5/8] Fix in types --- vqls_prototype/primitives/estimator_run_builder.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/vqls_prototype/primitives/estimator_run_builder.py b/vqls_prototype/primitives/estimator_run_builder.py index 9720a83..bacede6 100644 --- a/vqls_prototype/primitives/estimator_run_builder.py +++ b/vqls_prototype/primitives/estimator_run_builder.py @@ -3,12 +3,14 @@ from qiskit import QuantumCircuit from qiskit.quantum_info import SparsePauliOp from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager +from qiskit.primitives import Estimator, PrimitiveJob -from qiskit.primitives import Estimator from qiskit_aer.primitives import EstimatorV2 as aer_EstimatorV2 from qiskit_aer.primitives import Estimator as aer_Estimator + from qiskit_ibm_runtime import Estimator as ibm_runtime_Estimator from qiskit_ibm_runtime import EstimatorV2 as ibm_runtime_EstimatorV2 +from qiskit_ibm_runtime import RuntimeJobV2 EstimatorValidType = Union[ Estimator, @@ -77,12 +79,14 @@ def build_run(self) -> Callable: builder_function = self._select_run_builder() return builder_function() - def _select_run_builder(self) -> Callable: + def _select_run_builder(self) -> Union[PrimitiveJob, RuntimeJobV2]: """Selects the appropriate builder function based on the estimator's provenance.""" builders = { ("qiskit", "Estimator"): self._build_native_qiskit_estimator_run, ("qiskit_aer", "EstimatorV2"): self._build_estimatorv2_run, ("qiskit_aer", "Estimator"): self._build_estimatorv1_run, + ("qiskit_ibm_runtime", "EstimatorV2"): self._build_estimatorv2_run, + ("qiskit_ibm_runtime", "EstimatorV1"): self._build_estimatorv1_run, } try: return builders[self.provenance] @@ -91,7 +95,7 @@ def _select_run_builder(self) -> Callable: f"{self.__class__.__name__} not compatible with {self.provenance}." ) from err - def _build_native_qiskit_estimator_run(self) -> Callable: + def _build_native_qiskit_estimator_run(self) -> PrimitiveJob: """Builds a run function for a standard qiskit Estimator.""" return self.estimator.run( self.circuits, @@ -101,7 +105,7 @@ def _build_native_qiskit_estimator_run(self) -> Callable: seed=self.seed, ) - def _build_estimatorv2_run(self) -> Callable: + def _build_estimatorv2_run(self) -> Union[PrimitiveJob, RuntimeJobV2]: """Builds a run function for qiskit-aer and qiskit-ibm-runtime EstimatorV2.""" backend = self.estimator._backend # pylint: disable=protected-access optimization_level = 1 From a9d7d2a92c783cbffae4de85b8904dee2c83dd63 Mon Sep 17 00:00:00 2001 From: Cmurilochem Date: Mon, 15 Jul 2024 13:38:59 +0200 Subject: [PATCH 6/8] Further fixes in type --- vqls_prototype/primitives/estimator_run_builder.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vqls_prototype/primitives/estimator_run_builder.py b/vqls_prototype/primitives/estimator_run_builder.py index bacede6..b840b0d 100644 --- a/vqls_prototype/primitives/estimator_run_builder.py +++ b/vqls_prototype/primitives/estimator_run_builder.py @@ -5,8 +5,8 @@ from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.primitives import Estimator, PrimitiveJob -from qiskit_aer.primitives import EstimatorV2 as aer_EstimatorV2 from qiskit_aer.primitives import Estimator as aer_Estimator +from qiskit_aer.primitives import EstimatorV2 as aer_EstimatorV2 from qiskit_ibm_runtime import Estimator as ibm_runtime_Estimator from qiskit_ibm_runtime import EstimatorV2 as ibm_runtime_EstimatorV2 @@ -66,15 +66,15 @@ def find_estimator_provenance(self) -> Tuple[str, str]: self.estimator.__class__.__name__, ) - def build_run(self) -> Callable: + def build_run(self) -> Union[PrimitiveJob, RuntimeJobV2]: """ - Configures and returns a callable function for estimator runs based on its provenance. + Configures and returns estimator runs based on its provenance. Raises: NotImplementedError: If the estimator's provenance is not supported. Returns: - Callable: A configured callable function to execute the estimator run. + Union[PrimitiveJob, RuntimeJobV2]: A configured callable function to execute the estimator run. """ builder_function = self._select_run_builder() return builder_function() From 66ab92368b7d42f761a2c04488d7edc2eb771e16 Mon Sep 17 00:00:00 2001 From: Cmurilochem Date: Tue, 16 Jul 2024 07:07:12 +0200 Subject: [PATCH 7/8] Refactor primitives run builder and add sampler run buider --- tests/test_vqls.py | 15 ++-- .../hadamard_test/direct_hadamard_test.py | 48 +++++++++--- .../hadamard_test/hadamard_overlap_test.py | 46 +++++++++-- vqls_prototype/hadamard_test/hadamard_test.py | 2 +- vqls_prototype/primitives/__init__.py | 8 -- .../primitives_run_builder/__init__.py | 10 +++ .../base_run_builder.py | 78 +++++++++++++++++++ .../estimator_run_builder.py | 71 ++++------------- .../sampler_run_builder.py | 76 ++++++++++++++++++ 9 files changed, 266 insertions(+), 88 deletions(-) delete mode 100644 vqls_prototype/primitives/__init__.py create mode 100644 vqls_prototype/primitives_run_builder/__init__.py create mode 100644 vqls_prototype/primitives_run_builder/base_run_builder.py rename vqls_prototype/{primitives => primitives_run_builder}/estimator_run_builder.py (55%) create mode 100644 vqls_prototype/primitives_run_builder/sampler_run_builder.py diff --git a/tests/test_vqls.py b/tests/test_vqls.py index 5d08ce1..86d04f2 100644 --- a/tests/test_vqls.py +++ b/tests/test_vqls.py @@ -19,11 +19,12 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import RealAmplitudes +from qiskit.primitives import Estimator, Sampler from qiskit_algorithms.optimizers import ADAM -from qiskit.primitives import Estimator, Sampler from qiskit_aer.primitives import EstimatorV2 as aer_EstimatorV2 +from qiskit_aer.primitives import SamplerV2 as aer_SamplerV2 from vqls_prototype import VQLS @@ -52,7 +53,7 @@ def setUp(self): self.samplers = ( Sampler(), - Sampler(), + aer_SamplerV2(), # AerSampler(), ) @@ -75,8 +76,9 @@ def test_numpy_input(self): zip(self.estimators, self.samplers) ): for iopt, opt in enumerate(self.options): - if iprim == 1 and iopt == 2: - continue + # if iprim == 1 and iopt == 2: + # continue + print(iprim, iopt) vqls = VQLS( estimator, ansatz, @@ -110,8 +112,9 @@ def test_circuit_input_statevector(self): zip(self.estimators, self.samplers) ): for iopt, opt in enumerate(self.options): - if iprim == 1 and iopt == 2: - continue + # if iprim == 1 and iopt == 2: + # continue + print(iprim, iopt) vqls = VQLS( estimator, ansatz, diff --git a/vqls_prototype/hadamard_test/direct_hadamard_test.py b/vqls_prototype/hadamard_test/direct_hadamard_test.py index 776009b..b67c00e 100644 --- a/vqls_prototype/hadamard_test/direct_hadamard_test.py +++ b/vqls_prototype/hadamard_test/direct_hadamard_test.py @@ -4,6 +4,10 @@ import numpy as np import numpy.typing as npt +from qiskit.primitives.sampler import SamplerResult +from qiskit.primitives.containers import PrimitiveResult +from vqls_prototype.primitives_run_builder import SamplerRunBuilder + class BatchDirectHadammardTest: r"""Class that execute batches of Hadammard Test""" @@ -31,14 +35,18 @@ def get_values(self, sampler, parameter_sets: List, zne_strategy=None) -> List: """ ncircuits = len(self.circuits) + all_parameter_sets = [parameter_sets] * ncircuits + + sampler_run_builder = SamplerRunBuilder( + sampler, + self.circuits, + all_parameter_sets, + options={"shots": self.shots}, + ) try: if zne_strategy is None: - job = sampler.run( - self.circuits, - [parameter_sets] * ncircuits, - shots=self.shots, - ) + job = sampler_run_builder.build_run() else: job = sampler.run( self.circuits, @@ -133,8 +141,24 @@ def post_processing(self, sampler_result) -> npt.NDArray[np.cdouble]: Returns: List: value of the overlap hadammard test """ + if isinstance(sampler_result, SamplerResult): + quasi_dist = sampler_result.quasi_dists + + elif isinstance(sampler_result, PrimitiveResult): + quasi_dist = [ + { + key: value / result.data.meas.num_shots + for key, value in result.data.meas.get_int_counts().items() + } + for result in sampler_result + ] + + else: + raise NotImplementedError( + f"Cannot post processing for {type(sampler_result)} type class." + f"Please, refer to {self.__class__.__name__}.post_processing()." + ) - quasi_dist = sampler_result.quasi_dists val = [] for qdist in quasi_dist: # add missing keys @@ -158,14 +182,16 @@ def get_value( Returns: List: value of the test """ + sampler_run_builder = SamplerRunBuilder( + sampler, + self.circuits, + parameter_sets, + options={"shots": self.shots}, + ) try: if zne_strategy is None: - job = sampler.run( - self.circuits, - parameter_sets, - shots=self.shots, - ) + job = sampler_run_builder.build_run() else: job = sampler.run( self.circuits, diff --git a/vqls_prototype/hadamard_test/hadamard_overlap_test.py b/vqls_prototype/hadamard_test/hadamard_overlap_test.py index 526f4ff..266ef8e 100644 --- a/vqls_prototype/hadamard_test/hadamard_overlap_test.py +++ b/vqls_prototype/hadamard_test/hadamard_overlap_test.py @@ -4,6 +4,10 @@ import numpy as np import numpy.typing as npt +from qiskit.primitives.sampler import SamplerResult +from qiskit.primitives.containers import PrimitiveResult +from vqls_prototype.primitives_run_builder import SamplerRunBuilder + class BatchHadammardOverlapTest: r"""Class that execute batches of Hadammard Test""" @@ -31,14 +35,18 @@ def get_values(self, sampler, parameter_sets: List, zne_strategy=None) -> List: """ ncircuits = len(self.circuits) + all_parameter_sets = [parameter_sets] * ncircuits + + sampler_run_builder = SamplerRunBuilder( + sampler, + self.circuits, + all_parameter_sets, + options={"shots": self.shots}, + ) try: if zne_strategy is None: - job = sampler.run( - self.circuits, - [parameter_sets] * ncircuits, - shots=self.shots, - ) + job = sampler_run_builder.build_run() else: job = sampler.run( self.circuits, @@ -240,8 +248,24 @@ def post_processing(self, sampler_result) -> npt.NDArray[np.cdouble]: Returns: List: value of the overlap hadammard test """ + if isinstance(sampler_result, SamplerResult): + quasi_dist = sampler_result.quasi_dists + + elif isinstance(sampler_result, PrimitiveResult): + quasi_dist = [ + { + key: value / result.data.meas.num_shots + for key, value in result.data.meas.get_int_counts().items() + } + for result in sampler_result + ] + + else: + raise NotImplementedError( + f"Cannot post processing for {type(sampler_result)} type class." + f"Please, refer to {self.__class__.__name__}.post_processing()." + ) - quasi_dist = sampler_result.quasi_dists output = [] for qdist in quasi_dist: @@ -269,7 +293,15 @@ def get_value(self, sampler, parameter_sets: List) -> float: float: value of the overlap hadammard test """ ncircuits = len(self.circuits) - job = sampler.run(self.circuits, [parameter_sets] * ncircuits, shots=self.shots) + all_parameter_sets = [parameter_sets] * ncircuits + + sampler_run_builder = SamplerRunBuilder( + sampler, + self.circuits, + all_parameter_sets, + options={"shots": self.shots}, + ) + job = sampler_run_builder.build_run() results = self.post_processing(job.result()) results *= np.array([1.0, 1.0j]) diff --git a/vqls_prototype/hadamard_test/hadamard_test.py b/vqls_prototype/hadamard_test/hadamard_test.py index bc042dc..34040c7 100644 --- a/vqls_prototype/hadamard_test/hadamard_test.py +++ b/vqls_prototype/hadamard_test/hadamard_test.py @@ -10,7 +10,7 @@ from qiskit.primitives.estimator import EstimatorResult from qiskit.primitives.containers import PrimitiveResult -from vqls_prototype.primitives import EstimatorRunBuilder +from vqls_prototype.primitives_run_builder import EstimatorRunBuilder class BatchHadammardTest: diff --git a/vqls_prototype/primitives/__init__.py b/vqls_prototype/primitives/__init__.py deleted file mode 100644 index afab4e2..0000000 --- a/vqls_prototype/primitives/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Primitive builder package.""" - -from .estimator_run_builder import EstimatorRunBuilder - - -__all__ = [ - "EstimatorRunBuilder", -] diff --git a/vqls_prototype/primitives_run_builder/__init__.py b/vqls_prototype/primitives_run_builder/__init__.py new file mode 100644 index 0000000..42bdaae --- /dev/null +++ b/vqls_prototype/primitives_run_builder/__init__.py @@ -0,0 +1,10 @@ +"""Primitives builder package.""" + +from .estimator_run_builder import EstimatorRunBuilder +from .sampler_run_builder import SamplerRunBuilder + + +__all__ = [ + "EstimatorRunBuilder", + "SamplerRunBuilder", +] diff --git a/vqls_prototype/primitives_run_builder/base_run_builder.py b/vqls_prototype/primitives_run_builder/base_run_builder.py new file mode 100644 index 0000000..b296ea7 --- /dev/null +++ b/vqls_prototype/primitives_run_builder/base_run_builder.py @@ -0,0 +1,78 @@ +"""This module defines a base class for primitive run builders.""" + +from typing import Union, List, Tuple, Dict, Any +from qiskit import QuantumCircuit +from qiskit.primitives import PrimitiveJob +from qiskit_ibm_runtime import RuntimeJobV2 + + +class BasePrimitiveRunBuilder: + """ + Base class for building and configuring primitive runs based on their provenance and options. + """ + + def __init__( + self, + primitive, + circuits: List[QuantumCircuit], + parameter_sets: List[List[float]], + options: Dict[str, Any], + ): + """ + Initializes BasePrimitiveRunBuilder for given primitive, circuits, parameters, and options. + + Args: + primitive (Union[SamplerValidType, EstimatorValidType]): The primitive to use for runs. + circuits (List[QuantumCircuit]): The quantum circuits to run. + parameter_sets (List[List[float]]): The parameters to vary in the circuits. + options (Dict[str, Any]): Configuration options such as number of shots. + """ + self.primitive = primitive + self.circuits = circuits + self.parameter_sets = parameter_sets + self.shots = options.pop("shots", None) + self.seed = options.pop("seed", None) + self.provenance = self.find_provenance() + + def find_provenance(self) -> Tuple[str, str]: + """Determines the provenance of the primitive based on its class and module.""" + return ( + self.primitive.__class__.__module__.split(".")[0], + self.primitive.__class__.__name__, + ) + + def build_run(self) -> Union[PrimitiveJob, RuntimeJobV2]: + """ + Configures and returns primitive runs based on its provenance. + + Raises: + NotImplementedError: If the primitive's provenance is not supported. + + Returns: + Union[PrimitiveJob, RuntimeJobV2]: A primitive job. + """ + primitive_job = self._select_run_builder() + return primitive_job() + + def _select_run_builder(self) -> Union[PrimitiveJob, RuntimeJobV2]: + """Selects the appropriate builder function based on the primitive's provenance.""" + raise NotImplementedError("This method should be implemented by subclasses.") + + def _build_native_qiskit_run(self) -> PrimitiveJob: + """Builds a run function for a standard qiskit primitive.""" + raise NotImplementedError("This method should be implemented by subclasses.") + + def _build_v2_run(self) -> Union[PrimitiveJob, RuntimeJobV2]: + """Builds a run function for qiskit-aer and qiskit-ibm-runtime V2 primitives.""" + raise NotImplementedError("This method should be implemented by subclasses.") + + def _build_v1_run(self): + """ + Attempts to build a run function for primitives V1, which will be soon deprecated. + + Raises: + NotImplementedError: Indicates that V1 will be soon deprecated. + """ + raise NotImplementedError( + "Primitives V1 will be soon deprecated. Please, use V2 implementation." + ) diff --git a/vqls_prototype/primitives/estimator_run_builder.py b/vqls_prototype/primitives_run_builder/estimator_run_builder.py similarity index 55% rename from vqls_prototype/primitives/estimator_run_builder.py rename to vqls_prototype/primitives_run_builder/estimator_run_builder.py index b840b0d..2875ea4 100644 --- a/vqls_prototype/primitives/estimator_run_builder.py +++ b/vqls_prototype/primitives_run_builder/estimator_run_builder.py @@ -1,16 +1,16 @@ -from typing import Union, List, Callable, Tuple, Dict, Any +"""This module defines the estimator run builder class.""" +from typing import Union, List, Dict, Any from qiskit import QuantumCircuit from qiskit.quantum_info import SparsePauliOp from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.primitives import Estimator, PrimitiveJob - from qiskit_aer.primitives import Estimator as aer_Estimator from qiskit_aer.primitives import EstimatorV2 as aer_EstimatorV2 - from qiskit_ibm_runtime import Estimator as ibm_runtime_Estimator from qiskit_ibm_runtime import EstimatorV2 as ibm_runtime_EstimatorV2 from qiskit_ibm_runtime import RuntimeJobV2 +from .base_run_builder import BasePrimitiveRunBuilder EstimatorValidType = Union[ Estimator, @@ -21,7 +21,7 @@ ] -class EstimatorRunBuilder: +class EstimatorRunBuilder(BasePrimitiveRunBuilder): # pylint: disable=abstract-method """ A class to build and configure estimator runs based on their provenance and options. @@ -32,7 +32,7 @@ class EstimatorRunBuilder: parameter_sets (List[List[float]]): List of parameter sets. """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, estimator: EstimatorValidType, circuits: List[QuantumCircuit], @@ -51,42 +51,16 @@ def __init__( parameter_sets (List[List[float]]): The parameters to vary in the circuits. options (Dict[str, Any]): Configuration options such as number of shots. """ - self.estimator = estimator - self.circuits = circuits + super().__init__(estimator, circuits, parameter_sets, options) self.observables = observables - self.parameter_sets = parameter_sets - self.shots = options.pop("shots", None) - self.seed = options.pop("seed", None) - self.provenance = self.find_estimator_provenance() - - def find_estimator_provenance(self) -> Tuple[str, str]: - """Determines the provenance of the estimator based on its class and module.""" - return ( - self.estimator.__class__.__module__.split(".")[0], - self.estimator.__class__.__name__, - ) - - def build_run(self) -> Union[PrimitiveJob, RuntimeJobV2]: - """ - Configures and returns estimator runs based on its provenance. - - Raises: - NotImplementedError: If the estimator's provenance is not supported. - - Returns: - Union[PrimitiveJob, RuntimeJobV2]: A configured callable function to execute the estimator run. - """ - builder_function = self._select_run_builder() - return builder_function() def _select_run_builder(self) -> Union[PrimitiveJob, RuntimeJobV2]: - """Selects the appropriate builder function based on the estimator's provenance.""" builders = { - ("qiskit", "Estimator"): self._build_native_qiskit_estimator_run, - ("qiskit_aer", "EstimatorV2"): self._build_estimatorv2_run, - ("qiskit_aer", "Estimator"): self._build_estimatorv1_run, - ("qiskit_ibm_runtime", "EstimatorV2"): self._build_estimatorv2_run, - ("qiskit_ibm_runtime", "EstimatorV1"): self._build_estimatorv1_run, + ("qiskit", "Estimator"): self._build_native_qiskit_run, + ("qiskit_aer", "EstimatorV2"): self._build_v2_run, + ("qiskit_aer", "Estimator"): self._build_v1_run, + ("qiskit_ibm_runtime", "EstimatorV2"): self._build_v2_run, + ("qiskit_ibm_runtime", "EstimatorV1"): self._build_v1_run, } try: return builders[self.provenance] @@ -95,9 +69,9 @@ def _select_run_builder(self) -> Union[PrimitiveJob, RuntimeJobV2]: f"{self.__class__.__name__} not compatible with {self.provenance}." ) from err - def _build_native_qiskit_estimator_run(self) -> PrimitiveJob: + def _build_native_qiskit_run(self) -> PrimitiveJob: """Builds a run function for a standard qiskit Estimator.""" - return self.estimator.run( + return self.primitive.run( self.circuits, self.observables, self.parameter_sets, @@ -105,9 +79,9 @@ def _build_native_qiskit_estimator_run(self) -> PrimitiveJob: seed=self.seed, ) - def _build_estimatorv2_run(self) -> Union[PrimitiveJob, RuntimeJobV2]: + def _build_v2_run(self) -> Union[PrimitiveJob, RuntimeJobV2]: """Builds a run function for qiskit-aer and qiskit-ibm-runtime EstimatorV2.""" - backend = self.estimator._backend # pylint: disable=protected-access + backend = self.primitive._backend # pylint: disable=protected-access optimization_level = 1 pm = generate_preset_pass_manager(optimization_level, backend) pubs = [] @@ -115,17 +89,4 @@ def _build_estimatorv2_run(self) -> Union[PrimitiveJob, RuntimeJobV2]: isa_circuit = pm.run(qc) isa_obs = obs.apply_layout(isa_circuit.layout) pubs.append((isa_circuit, isa_obs, param)) - return self.estimator.run(pubs) - - def _build_estimatorv1_run(self): - """ - Attempts to build a run function for EstimatorV1, which will be soon deprecated. - - Raises: - NotImplementedError: - Indicates that EstimatorV1 will be soon deprecated and - suggests using EstimatorV2 instead. - """ - raise NotImplementedError( - "EstimatorV1 will be soon deprecated. Please, use EstimatorV2 implementation." - ) + return self.primitive.run(pubs) diff --git a/vqls_prototype/primitives_run_builder/sampler_run_builder.py b/vqls_prototype/primitives_run_builder/sampler_run_builder.py new file mode 100644 index 0000000..b64e82b --- /dev/null +++ b/vqls_prototype/primitives_run_builder/sampler_run_builder.py @@ -0,0 +1,76 @@ +"""This module defines the sampler run builder class.""" + +from typing import Union, List, Dict, Any +from qiskit import QuantumCircuit +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager +from qiskit.primitives import Sampler, PrimitiveJob +from qiskit_aer.primitives import Sampler as aer_Sampler +from qiskit_aer.primitives import SamplerV2 as aer_SamplerV2 +from qiskit_ibm_runtime import Sampler as ibm_runtime_Sampler +from qiskit_ibm_runtime import SamplerV2 as ibm_runtime_SamplerV2 +from qiskit_ibm_runtime import RuntimeJobV2 + +from .base_run_builder import BasePrimitiveRunBuilder + +SamplerValidType = Union[ + Sampler, + aer_Sampler, + aer_SamplerV2, + ibm_runtime_Sampler, + ibm_runtime_SamplerV2, +] + + +class SamplerRunBuilder(BasePrimitiveRunBuilder): # pylint: disable=abstract-method + """ + A class to build and configure sampler runs based on their provenance and options. + + Attributes: + sampler (SamplerValidType): The quantum sampler instance. + circuits (List[QuantumCircuit]): List of quantum circuits. + parameter_sets (List[List[float]]): List of parameter sets. + """ + + def __init__( + self, + sampler: SamplerValidType, + circuits: List[QuantumCircuit], + parameter_sets: List[List[float]], + options: Dict[str, Any], + ): + super().__init__(sampler, circuits, parameter_sets, options) + + def _select_run_builder(self) -> Union[PrimitiveJob, RuntimeJobV2]: + builders = { + ("qiskit", "Sampler"): self._build_native_qiskit_run, + ("qiskit_aer", "SamplerV2"): self._build_v2_run, + ("qiskit_aer", "Sampler"): self._build_v1_run, + ("qiskit_ibm_runtime", "SamplerV2"): self._build_v2_run, + ("qiskit_ibm_runtime", "SamplerV1"): self._build_v1_run, + } + try: + return builders[self.provenance] + except KeyError as err: + raise NotImplementedError( + f"{self.__class__.__name__} not compatible with {self.provenance}." + ) from err + + def _build_native_qiskit_run(self) -> PrimitiveJob: + """Builds a run function for a standard qiskit Sampler.""" + return self.primitive.run( + self.circuits, + self.parameter_sets, + shots=self.shots, + seed=self.seed, + ) + + def _build_v2_run(self) -> Union[PrimitiveJob, RuntimeJobV2]: + """Builds a run function for qiskit-aer and qiskit-ibm-runtime SamplerV2.""" + backend = self.primitive._backend # pylint: disable=protected-access + optimization_level = 1 + pm = generate_preset_pass_manager(optimization_level, backend) + pubs = [] + for qc, param in zip(self.circuits, self.parameter_sets): + isa_circuit = pm.run(qc) + pubs.append((isa_circuit, param)) + return self.primitive.run(pubs) From 1660ba26163bffee2d522af6ae9fae898c1c4142 Mon Sep 17 00:00:00 2001 From: Cmurilochem Date: Wed, 17 Jul 2024 15:47:06 +0200 Subject: [PATCH 8/8] Remove unused variables and print --- tests/test_vqls.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/tests/test_vqls.py b/tests/test_vqls.py index 86d04f2..a386c2a 100644 --- a/tests/test_vqls.py +++ b/tests/test_vqls.py @@ -72,13 +72,8 @@ def test_numpy_input(self): rhs = np.array([0.1] * 4) ansatz = RealAmplitudes(num_qubits=2, reps=3, entanglement="full") - for iprim, (estimator, sampler) in enumerate( - zip(self.estimators, self.samplers) - ): - for iopt, opt in enumerate(self.options): - # if iprim == 1 and iopt == 2: - # continue - print(iprim, iopt) + for _, (estimator, sampler) in enumerate(zip(self.estimators, self.samplers)): + for _, opt in enumerate(self.options): vqls = VQLS( estimator, ansatz, @@ -108,13 +103,8 @@ def test_circuit_input_statevector(self): qc2.x(1) qc2.cx(0, 1) - for iprim, (estimator, sampler) in enumerate( - zip(self.estimators, self.samplers) - ): - for iopt, opt in enumerate(self.options): - # if iprim == 1 and iopt == 2: - # continue - print(iprim, iopt) + for _, (estimator, sampler) in enumerate(zip(self.estimators, self.samplers)): + for _, opt in enumerate(self.options): vqls = VQLS( estimator, ansatz,