15
15
from __future__ import annotations
16
16
17
17
import logging
18
+ import warnings
18
19
from copy import copy
19
20
from typing import Sequence
20
-
21
21
import numpy as np
22
+
22
23
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
24
26
from qiskit .quantum_info import SparsePauliOp
25
27
from qiskit .quantum_info .operators .base_operator import BaseOperator
28
+
26
29
from ..gradients import (
27
30
BaseEstimatorGradient ,
28
31
EstimatorGradientResult ,
29
32
ParamShiftEstimatorGradient ,
30
33
)
31
-
32
34
from ..circuit .library import QNNCircuit
33
35
from ..exceptions import QiskitMachineLearningError
34
36
@@ -64,7 +66,7 @@ class EstimatorQNN(NeuralNetwork):
64
66
num_qubits = 2
65
67
66
68
# 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
68
70
# and RealAmplitudes ansatz.
69
71
qnn_qc = QNNCircuit(num_qubits)
70
72
@@ -105,12 +107,14 @@ def __init__(
105
107
self ,
106
108
* ,
107
109
circuit : QuantumCircuit ,
108
- estimator : BaseEstimator | None = None ,
110
+ estimator : BaseEstimator | BaseEstimatorV2 | None = None ,
109
111
observables : Sequence [BaseOperator ] | BaseOperator | None = None ,
110
112
input_params : Sequence [Parameter ] | None = None ,
111
113
weight_params : Sequence [Parameter ] | None = None ,
112
114
gradient : BaseEstimatorGradient | None = None ,
113
115
input_gradients : bool = False ,
116
+ num_virtual_qubits : int | None = None ,
117
+ default_precision : float = 0.015625 ,
114
118
):
115
119
r"""
116
120
Args:
@@ -127,12 +131,12 @@ def __init__(
127
131
input_params: The parameters that correspond to the input data of the network.
128
132
If ``None``, the input data is not bound to any parameters.
129
133
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
131
135
:class:`~qiskit_machine_learning.circuit.library.QNNCircuit` input_parameters.
132
136
weight_params: The parameters that correspond to the trainable weights.
133
137
If ``None``, the weights are not bound to any parameters.
134
138
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
136
140
:class:`~qiskit_machine_learning.circuit.library.QNNCircuit` weight_parameters.
137
141
gradient: The estimator gradient to be used for the backward pass.
138
142
If None, a default instance of the estimator gradient,
@@ -141,6 +145,8 @@ def __init__(
141
145
Note that this parameter is ``False`` by default, and must be explicitly set to
142
146
``True`` for a proper gradient computation when using
143
147
: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.
144
150
145
151
Raises:
146
152
QiskitMachineLearningError: Invalid parameter values.
@@ -149,19 +155,46 @@ def __init__(
149
155
estimator = Estimator ()
150
156
self .estimator = estimator
151
157
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
+
152
171
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
+
154
177
if isinstance (observables , BaseOperator ):
155
178
observables = (observables ,)
179
+
156
180
self ._observables = observables
181
+
157
182
if isinstance (circuit , QNNCircuit ):
158
183
self ._input_params = list (circuit .input_parameters )
159
184
self ._weight_params = list (circuit .weight_parameters )
160
185
else :
161
186
self ._input_params = list (input_params ) if input_params is not None else []
162
187
self ._weight_params = list (weight_params ) if weight_params is not None else []
188
+
163
189
if gradient is None :
190
+ if isinstance (self .estimator , BaseEstimatorV2 ):
191
+ raise QiskitMachineLearningError (
192
+ "Please provide a gradient with pass manager initialised."
193
+ )
194
+
164
195
gradient = ParamShiftEstimatorGradient (self .estimator )
196
+
197
+ self ._default_precision = default_precision
165
198
self .gradient = gradient
166
199
self ._input_gradients = input_gradients
167
200
@@ -198,33 +231,52 @@ def weight_params(self) -> Sequence[Parameter] | None:
198
231
@property
199
232
def input_gradients (self ) -> bool :
200
233
"""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."""
202
235
return self ._input_gradients
203
236
204
237
@input_gradients .setter
205
238
def input_gradients (self , input_gradients : bool ) -> None :
206
239
"""Turn on/off computation of gradients with respect to input data."""
207
240
self ._input_gradients = input_gradients
208
241
242
+ @property
243
+ def default_precision (self ) -> float :
244
+ """Return the default precision"""
245
+ return self ._default_precision
246
+
209
247
def _forward_postprocess (self , num_samples : int , result : EstimatorResult ) -> np .ndarray :
210
248
"""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
212
250
213
251
def _forward (
214
252
self , input_data : np .ndarray | None , weights : np .ndarray | None
215
253
) -> np .ndarray | None :
216
254
"""Forward pass of the neural network."""
217
255
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
227
256
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
+ )
228
280
return self ._forward_postprocess (num_samples , results )
229
281
230
282
def _backward_postprocess (
@@ -269,8 +321,11 @@ def _backward(
269
321
param_values = np .tile (parameter_values , (num_observables , 1 ))
270
322
271
323
job = None
324
+
272
325
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]
274
329
elif len (parameter_values [0 ]) > self ._num_inputs :
275
330
params = [self ._circuit .parameters [self ._num_inputs :]] * num_circuits
276
331
job = self .gradient .run (
@@ -281,7 +336,7 @@ def _backward(
281
336
try :
282
337
results = job .result ()
283
338
except Exception as exc :
284
- raise QiskitMachineLearningError ("Estimator job failed." ) from exc
339
+ raise QiskitMachineLearningError (f "Estimator job failed. { exc } " ) from exc
285
340
286
341
input_grad , weights_grad = self ._backward_postprocess (num_samples , results )
287
342
0 commit comments