Skip to content

Commit 1712ebe

Browse files
OkuyanBogaedoaltamuraFrancescaSchiavdeclanmillarwoodsp-ibm
authored
Added support for EstimatorV2 primitives (#48)
* Migrating `qiskit_algorithms` (qiskit-community#817) * Update README.md * Generalize the Einstein summation signature * Add reno * Update Copyright * Rename and add test * Update Copyright * Add docstring for `test_get_einsum_signature` * Correct spelling * Disable spellcheck for comments * Add `docstring` in pylint dict * Delete example in docstring * Add Einstein in pylint dict * Add full use case in einsum dict * Spelling and type ignore * Spelling and type ignore * Spelling and type ignore * Spelling and type ignore * Spelling and type ignore * Remove for loop in einsum function and remove Literal arguments (1/2) * Remove for loop in einsum function and remove Literal arguments (1/2) * Remove for loop in einsum function and remove Literal arguments (2/2) * Update RuntimeError msg * Update RuntimeError msg - line too long * Trigger CI * Merge algos, globals.random to fix * Fixed `algorithms_globals` * Import /tests and run CI locally * Fix copyrights and some spellings * Ignore mypy in 8 instances * Merge spell dicts * Black reformatting * Black reformatting * Add reno * Lint sanitize * Pylint * Pylint * Pylint * Pylint * Fix relative imports in tutorials * Fix relative imports in tutorials * Remove algorithms from Jupyter magic methods * Temporarily disable "Run stable tutorials" tests * Change the docstrings with imports from qiskit_algorithms * Styling * Update qiskit_machine_learning/optimizers/gradient_descent.py Co-authored-by: Declan Millar <[email protected]> * Update qiskit_machine_learning/optimizers/optimizer_utils/learning_rate.py Co-authored-by: Declan Millar <[email protected]> * Add more tests for utils * Add more tests for optimizers: adam, bobyqa, gsls and imfil * Fix random seed for volatile optimizers * Fix random seed for volatile optimizers * Add more tests * Pylint dict * Activate scikit-quant-0.8.2 * Remove scikit-quant methods * Remove scikit-quant methods (2) * Edit the release notes and Qiskit version 1+ * Edit the release notes and Qiskit version 1+ * Add Qiskit 1.0 upgrade in reno * Add Qiskit 1.0 upgrade in reno * Add Qiskit 1.0 upgrade in reno * Apply line breaks * Restructure line breaks --------- Co-authored-by: FrancescaSchiav <[email protected]> Co-authored-by: M. Emre Sahin <[email protected]> Co-authored-by: Declan Millar <[email protected]> * Revamp readme pt2 (qiskit-community#822) * Restructure README.md --------- Co-authored-by: Steve Wood <[email protected]> * Added support for EstimatorV2 primitives * Update qiskit_machine_learning/neural_networks/estimator_qnn.py Co-authored-by: Edoardo Altamura <[email protected]> * Update qiskit_machine_learning/neural_networks/estimator_qnn.py Co-authored-by: Edoardo Altamura <[email protected]> * Update qiskit_machine_learning/neural_networks/estimator_qnn.py Co-authored-by: Edoardo Altamura <[email protected]> * Update qiskit_machine_learning/neural_networks/estimator_qnn.py Co-authored-by: Edoardo Altamura <[email protected]> * Update qiskit_machine_learning/gradients/param_shift/param_shift_estimator_gradient.py Co-authored-by: Edoardo Altamura <[email protected]> * Update qiskit_machine_learning/gradients/spsa/spsa_estimator_gradient.py Co-authored-by: Edoardo Altamura <[email protected]> * Update qiskit_machine_learning/gradients/param_shift/param_shift_estimator_gradient.py Co-authored-by: Edoardo Altamura <[email protected]> * Update qiskit_machine_learning/neural_networks/estimator_qnn.py Co-authored-by: Edoardo Altamura <[email protected]> * Update qiskit_machine_learning/neural_networks/estimator_qnn.py Co-authored-by: Edoardo Altamura <[email protected]> * Update qiskit_machine_learning/gradients/param_shift/param_shift_estimator_gradient.py Co-authored-by: Edoardo Altamura <[email protected]> * Fix lint errors due to Pylint 3.3.0 update in CI (qiskit-community#833) * disable=too-many-positional-arguments * Transfer pylint rc to toml * Transfer pylint rc to toml * Cleaner statements * Remove Python 3.8 from CI (qiskit-community#824) (qiskit-community#826) * Remove Python 3.8 in CI (qiskit-community#824) * Correct `tmp` dirs (qiskit-community#818) * Correct unit py version (qiskit-community#818) * Add reno (qiskit-community#818) * Finalze removal of py38 (qiskit-community#818) * Spelling * Remove duplicate tmp folder * Updated the release note * Bump min pyversion in toml * Remove ipython constraints * Update reno * Added unit tests for estimatorqnnV2 and minor fixes * Make black * Make lint and changes to V1/2 choice logics * Update requirements * Add default precision * Update estimator tests * Change num qubits in backend * Allow for num_qubits=None * Fix shape in parameter shift * Fix shape in parameter shift * Fix shape observables * Fix shape observables * Change default precision to match base estimator * Fix remaining shape issues * Estimator seed has no effect in local testing * fix argnames and supress error tolerance for test_estimator_qnn_v2 * Added pass manager the gradients. * quick bugfix for isa_circuits * Updating PUBs for estimatorqnn, updating test_estimator_qnn_v2 for ISA circs and relaxing tolerances * Lint and formatting * Tranpiling observables for isa g circs * fixing apply_layout --------- Co-authored-by: Edoardo Altamura <[email protected]> Co-authored-by: FrancescaSchiav <[email protected]> Co-authored-by: Declan Millar <[email protected]> Co-authored-by: Steve Wood <[email protected]> Co-authored-by: oscar-wallis <[email protected]>
1 parent 2bbb57c commit 1712ebe

File tree

7 files changed

+718
-50
lines changed

7 files changed

+718
-50
lines changed

qiskit_machine_learning/gradients/base/base_estimator_gradient.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424

2525
from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit
2626
from qiskit.primitives import BaseEstimator
27+
from qiskit.primitives.base import BaseEstimatorV2
2728
from qiskit.primitives.utils import _circuit_key
2829
from qiskit.providers import Options
2930
from qiskit.quantum_info.operators.base_operator import BaseOperator
3031
from qiskit.transpiler.passes import TranslateParameterizedGates
32+
from qiskit.transpiler.passmanager import BasePassManager
3133

3234
from .estimator_gradient_result import EstimatorGradientResult
3335
from ..utils import (
@@ -46,13 +48,15 @@ class BaseEstimatorGradient(ABC):
4648

4749
def __init__(
4850
self,
49-
estimator: BaseEstimator,
51+
estimator: BaseEstimator | BaseEstimatorV2,
52+
pass_manager: BasePassManager | None = None,
5053
options: Options | None = None,
5154
derivative_type: DerivativeType = DerivativeType.REAL,
5255
):
5356
r"""
5457
Args:
5558
estimator: The estimator used to compute the gradients.
59+
pass_manager: pass manager for isa_circuit transpilation.
5660
options: Primitive backend runtime options used for circuit execution.
5761
The order of priority is: options in ``run`` method > gradient's
5862
default options > primitive's default setting.
@@ -68,7 +72,8 @@ def __init__(
6872
gradient and this type is the only supported type for function-level schemes like
6973
finite difference.
7074
"""
71-
self._estimator: BaseEstimator = estimator
75+
self._estimator = estimator
76+
self._pass_manager = pass_manager
7277
self._default_options = Options()
7378
if options is not None:
7479
self._default_options.update_options(**options)

qiskit_machine_learning/gradients/param_shift/param_shift_estimator_gradient.py

+58-22
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,19 @@
1717

1818
from collections.abc import Sequence
1919

20+
import numpy as np
21+
2022
from qiskit.circuit import Parameter, QuantumCircuit
2123
from qiskit.quantum_info.operators.base_operator import BaseOperator
24+
from qiskit.primitives.base import BaseEstimatorV2
25+
from qiskit.primitives import BaseEstimatorV1
26+
from qiskit.providers.options import Options
2227

2328
from ..base.base_estimator_gradient import BaseEstimatorGradient
2429
from ..base.estimator_gradient_result import EstimatorGradientResult
2530
from ..utils import _make_param_shift_parameter_values
2631

27-
from ...exceptions import AlgorithmError
32+
from ...exceptions import QiskitMachineLearningError
2833

2934

3035
class ParamShiftEstimatorGradient(BaseEstimatorGradient):
@@ -97,26 +102,57 @@ def _run_unique(
97102
job_param_values.extend(param_shift_parameter_values)
98103
all_n.append(n)
99104

100-
# Run the single job with all circuits.
101-
job = self._estimator.run(
102-
job_circuits,
103-
job_observables,
104-
job_param_values,
105-
**options,
106-
)
107-
try:
105+
# Determine how to run the estimator based on its version
106+
if isinstance(self._estimator, BaseEstimatorV1):
107+
# Run the single job with all circuits.
108+
job = self._estimator.run(
109+
job_circuits,
110+
job_observables,
111+
job_param_values,
112+
**options,
113+
)
108114
results = job.result()
109-
except Exception as exc:
110-
raise AlgorithmError("Estimator job failed.") from exc
111-
112-
# Compute the gradients.
113-
gradients = []
114-
partial_sum_n = 0
115-
for n in all_n:
116-
result = results.values[partial_sum_n : partial_sum_n + n]
117-
gradient_ = (result[: n // 2] - result[n // 2 :]) / 2
118-
gradients.append(gradient_)
119-
partial_sum_n += n
120-
121-
opt = self._get_local_options(options)
115+
116+
# Compute the gradients.
117+
gradients = []
118+
partial_sum_n = 0
119+
for n in all_n:
120+
result = results.values[partial_sum_n : partial_sum_n + n]
121+
gradient_ = (result[: n // 2] - result[n // 2 :]) / 2
122+
gradients.append(gradient_)
123+
partial_sum_n += n
124+
125+
opt = self._get_local_options(options)
126+
127+
elif isinstance(self._estimator, BaseEstimatorV2):
128+
isa_g_circs = self._pass_manager.run(job_circuits)
129+
isa_g_observables = [op.apply_layout(isa_g_circs[i].layout) for i, op in enumerate(job_observables)]
130+
# Prepare circuit-observable-parameter tuples (PUBs)
131+
circuit_observable_params = []
132+
for pub in zip(isa_g_circs, isa_g_observables, job_param_values):
133+
circuit_observable_params.append(pub)
134+
135+
# For BaseEstimatorV2, run the estimator using PUBs and specified precision
136+
job = self._estimator.run(circuit_observable_params)
137+
results = job.result()
138+
results = np.array([float(r.data.evs) for r in results])
139+
140+
# Compute the gradients.
141+
gradients = []
142+
partial_sum_n = 0
143+
for n in all_n:
144+
result = results[partial_sum_n : partial_sum_n + n]
145+
gradient_ = (result[: n // 2] - result[n // 2 :]) / 2
146+
gradients.append(gradient_)
147+
partial_sum_n += n
148+
149+
opt = Options(**options)
150+
151+
else:
152+
raise QiskitMachineLearningError(
153+
"The accepted estimators are BaseEstimatorV1 and BaseEstimatorV2; got "
154+
+ f"{type(self._estimator)} instead. Note that BaseEstimatorV1 is deprecated in"
155+
+ "Qiskit and removed in Qiskit IBM Runtime."
156+
)
157+
122158
return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt)

qiskit_machine_learning/neural_networks/estimator_qnn.py

+76-21
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,22 @@
1515
from __future__ import annotations
1616

1717
import logging
18+
import warnings
1819
from copy import copy
1920
from typing import Sequence
20-
2121
import numpy as np
22+
2223
from qiskit.circuit import Parameter, QuantumCircuit
23-
from qiskit.primitives import BaseEstimator, Estimator, EstimatorResult
24+
from qiskit.primitives.base import BaseEstimatorV2
25+
from qiskit.primitives import BaseEstimator, BaseEstimatorV1, Estimator, EstimatorResult
2426
from qiskit.quantum_info import SparsePauliOp
2527
from qiskit.quantum_info.operators.base_operator import BaseOperator
28+
2629
from ..gradients import (
2730
BaseEstimatorGradient,
2831
EstimatorGradientResult,
2932
ParamShiftEstimatorGradient,
3033
)
31-
3234
from ..circuit.library import QNNCircuit
3335
from ..exceptions import QiskitMachineLearningError
3436

@@ -64,7 +66,7 @@ class EstimatorQNN(NeuralNetwork):
6466
num_qubits = 2
6567
6668
# Using the QNNCircuit:
67-
# Create a parameterized 2 qubit circuit composed of the default ZZFeatureMap feature map
69+
# Create a parametrrized 2 qubit circuit composed of the default ZZFeatureMap feature map
6870
# and RealAmplitudes ansatz.
6971
qnn_qc = QNNCircuit(num_qubits)
7072
@@ -105,12 +107,14 @@ def __init__(
105107
self,
106108
*,
107109
circuit: QuantumCircuit,
108-
estimator: BaseEstimator | None = None,
110+
estimator: BaseEstimator | BaseEstimatorV2 | None = None,
109111
observables: Sequence[BaseOperator] | BaseOperator | None = None,
110112
input_params: Sequence[Parameter] | None = None,
111113
weight_params: Sequence[Parameter] | None = None,
112114
gradient: BaseEstimatorGradient | None = None,
113115
input_gradients: bool = False,
116+
num_virtual_qubits: int | None = None,
117+
default_precision: float = 0.015625,
114118
):
115119
r"""
116120
Args:
@@ -127,12 +131,12 @@ def __init__(
127131
input_params: The parameters that correspond to the input data of the network.
128132
If ``None``, the input data is not bound to any parameters.
129133
If a :class:`~qiskit_machine_learning.circuit.library.QNNCircuit` is provided the
130-
`input_params` value here is ignored. Instead the value is taken from the
134+
`input_params` value here is ignored. Instead, the value is taken from the
131135
:class:`~qiskit_machine_learning.circuit.library.QNNCircuit` input_parameters.
132136
weight_params: The parameters that correspond to the trainable weights.
133137
If ``None``, the weights are not bound to any parameters.
134138
If a :class:`~qiskit_machine_learning.circuit.library.QNNCircuit` is provided the
135-
`weight_params` value here is ignored. Instead the value is taken from the
139+
`weight_params` value here is ignored. Instead, the value is taken from the
136140
:class:`~qiskit_machine_learning.circuit.library.QNNCircuit` weight_parameters.
137141
gradient: The estimator gradient to be used for the backward pass.
138142
If None, a default instance of the estimator gradient,
@@ -141,6 +145,8 @@ def __init__(
141145
Note that this parameter is ``False`` by default, and must be explicitly set to
142146
``True`` for a proper gradient computation when using
143147
:class:`~qiskit_machine_learning.connectors.TorchConnector`.
148+
num_virtual_qubits: Number of virtual qubits.
149+
default_precision: The default precision for the estimator if not specified during run.
144150
145151
Raises:
146152
QiskitMachineLearningError: Invalid parameter values.
@@ -149,19 +155,46 @@ def __init__(
149155
estimator = Estimator()
150156
self.estimator = estimator
151157
self._org_circuit = circuit
158+
159+
if num_virtual_qubits is None:
160+
self.num_virtual_qubits = circuit.num_qubits
161+
warnings.warn(
162+
f"No number of qubits was not specified ({num_virtual_qubits}) and was retrieved from "
163+
+ f"`circuit` ({self.num_virtual_qubits:d}). If `circuit` is transpiled, this may cause "
164+
+ "unstable behaviour.",
165+
UserWarning,
166+
stacklevel=2,
167+
)
168+
else:
169+
self.num_virtual_qubits = num_virtual_qubits
170+
152171
if observables is None:
153-
observables = SparsePauliOp.from_list([("Z" * circuit.num_qubits, 1)])
172+
observables = SparsePauliOp.from_sparse_list(
173+
[("Z" * self.num_virtual_qubits, range(self.num_virtual_qubits), 1)],
174+
num_qubits=self.circuit.num_qubits,
175+
)
176+
154177
if isinstance(observables, BaseOperator):
155178
observables = (observables,)
179+
156180
self._observables = observables
181+
157182
if isinstance(circuit, QNNCircuit):
158183
self._input_params = list(circuit.input_parameters)
159184
self._weight_params = list(circuit.weight_parameters)
160185
else:
161186
self._input_params = list(input_params) if input_params is not None else []
162187
self._weight_params = list(weight_params) if weight_params is not None else []
188+
163189
if gradient is None:
190+
if isinstance(self.estimator, BaseEstimatorV2):
191+
raise QiskitMachineLearningError(
192+
"Please provide a gradient with pass manager initialised."
193+
)
194+
164195
gradient = ParamShiftEstimatorGradient(self.estimator)
196+
197+
self._default_precision = default_precision
165198
self.gradient = gradient
166199
self._input_gradients = input_gradients
167200

@@ -198,33 +231,52 @@ def weight_params(self) -> Sequence[Parameter] | None:
198231
@property
199232
def input_gradients(self) -> bool:
200233
"""Returns whether gradients with respect to input data are computed by this neural network
201-
in the ``backward`` method or not. By default such gradients are not computed."""
234+
in the ``backward`` method or not. By default, such gradients are not computed."""
202235
return self._input_gradients
203236

204237
@input_gradients.setter
205238
def input_gradients(self, input_gradients: bool) -> None:
206239
"""Turn on/off computation of gradients with respect to input data."""
207240
self._input_gradients = input_gradients
208241

242+
@property
243+
def default_precision(self) -> float:
244+
"""Return the default precision"""
245+
return self._default_precision
246+
209247
def _forward_postprocess(self, num_samples: int, result: EstimatorResult) -> np.ndarray:
210248
"""Post-processing during forward pass of the network."""
211-
return np.reshape(result.values, (-1, num_samples)).T
249+
return np.reshape(result, (-1, num_samples)).T
212250

213251
def _forward(
214252
self, input_data: np.ndarray | None, weights: np.ndarray | None
215253
) -> np.ndarray | None:
216254
"""Forward pass of the neural network."""
217255
parameter_values_, num_samples = self._preprocess_forward(input_data, weights)
218-
job = self.estimator.run(
219-
[self._circuit] * num_samples * self.output_shape[0],
220-
[op for op in self._observables for _ in range(num_samples)],
221-
np.tile(parameter_values_, (self.output_shape[0], 1)),
222-
)
223-
try:
224-
results = job.result()
225-
except Exception as exc:
226-
raise QiskitMachineLearningError("Estimator job failed.") from exc
227256

257+
# Determine how to run the estimator based on its version
258+
if isinstance(self.estimator, BaseEstimatorV1):
259+
job = self.estimator.run(
260+
[self._circuit] * num_samples * self.output_shape[0],
261+
[op for op in self._observables for _ in range(num_samples)],
262+
np.tile(parameter_values_, (self.output_shape[0], 1)),
263+
)
264+
results = job.result().values
265+
266+
elif isinstance(self.estimator, BaseEstimatorV2):
267+
# Prepare circuit-observable-parameter tuples (PUBs)
268+
circuit_observable_params = []
269+
for observable in self._observables:
270+
circuit_observable_params.append((self._circuit, observable, parameter_values_))
271+
# For BaseEstimatorV2, run the estimator using PUBs and specified precision
272+
job = self.estimator.run(circuit_observable_params, precision=self._default_precision)
273+
results = [result.data.evs for result in job.result()]
274+
else:
275+
raise QiskitMachineLearningError(
276+
"The accepted estimators are BaseEstimatorV1 and BaseEstimatorV2; got "
277+
+ f"{type(self.estimator)} instead. Note that BaseEstimatorV1 is deprecated in"
278+
+ "Qiskit and removed in Qiskit IBM Runtime."
279+
)
228280
return self._forward_postprocess(num_samples, results)
229281

230282
def _backward_postprocess(
@@ -269,8 +321,11 @@ def _backward(
269321
param_values = np.tile(parameter_values, (num_observables, 1))
270322

271323
job = None
324+
272325
if self._input_gradients:
273-
job = self.gradient.run(circuits, observables, param_values) # type: ignore[arg-type]
326+
job = self.gradient.run(
327+
circuits, observables, param_values
328+
) # type: ignore[arg-type]
274329
elif len(parameter_values[0]) > self._num_inputs:
275330
params = [self._circuit.parameters[self._num_inputs :]] * num_circuits
276331
job = self.gradient.run(
@@ -281,7 +336,7 @@ def _backward(
281336
try:
282337
results = job.result()
283338
except Exception as exc:
284-
raise QiskitMachineLearningError("Estimator job failed.") from exc
339+
raise QiskitMachineLearningError(f"Estimator job failed. {exc}") from exc
285340

286341
input_grad, weights_grad = self._backward_postprocess(num_samples, results)
287342

qiskit_machine_learning/neural_networks/neural_network.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,9 @@ def _reparameterize_circuit(
293293

294294
if len(parameters) != (self.num_inputs + self.num_weights):
295295
raise ValueError(
296-
f"Number of circuit parameters {len(parameters)}"
297-
f" mismatch with sum of num inputs and weights"
298-
f" {self.num_inputs + self.num_weights}"
296+
f"Number of circuit parameters ({len(parameters)})"
297+
f" does not match the sum of number of inputs and weights"
298+
f" ({self.num_inputs + self.num_weights})."
299299
)
300300

301301
new_input_params = ParameterVector("inputs", self.num_inputs)

requirements-dev.txt

+1
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ mypy>=0.981
1717
mypy-extensions>=0.4.3
1818
nbsphinx
1919
qiskit_sphinx_theme~=1.16.0
20+
qiskit-ibm-runtime>=0.21

test/neural_networks/test_estimator_qnn.py renamed to test/neural_networks/test_estimator_qnn_v1.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
from qiskit.circuit import Parameter, QuantumCircuit
2121
from qiskit.circuit.library import ZZFeatureMap, RealAmplitudes, ZFeatureMap
2222
from qiskit.quantum_info import SparsePauliOp
23-
from qiskit_machine_learning.circuit.library import QNNCircuit
2423

24+
from qiskit_machine_learning.circuit.library import QNNCircuit
2525
from qiskit_machine_learning.neural_networks.estimator_qnn import EstimatorQNN
26+
from qiskit_machine_learning.utils import algorithm_globals
2627

2728
CASE_DATA = {
2829
"shape_1_1": {
@@ -178,6 +179,7 @@ def _test_network_passes(
178179
estimator_qnn,
179180
case_data,
180181
):
182+
algorithm_globals.random_seed = 52
181183
test_data = case_data["test_data"]
182184
weights = case_data["weights"]
183185
correct_forwards = case_data["correct_forwards"]
@@ -407,7 +409,7 @@ def test_setters_getters(self):
407409
estimator_qnn.input_gradients = True
408410
self.assertTrue(estimator_qnn.input_gradients)
409411

410-
def test_qnn_qc_circui_construction(self):
412+
def test_qnn_qc_circuit_construction(self):
411413
"""Test Estimator QNN properties and forward/backward pass for QNNCircuit construction"""
412414
num_qubits = 2
413415
feature_map = ZZFeatureMap(feature_dimension=num_qubits)

0 commit comments

Comments
 (0)