1212
1313"""The QN-SPSA optimizer."""
1414
15- from typing import Any , Iterator , Optional , Union , Callable , Dict
15+ from __future__ import annotations
16+ from typing import Any , Iterator , Callable
17+ import warnings
1618
1719import numpy as np
1820from qiskit .providers import Backend
1921from qiskit .circuit import ParameterVector , QuantumCircuit
2022from qiskit .opflow import StateFn , CircuitSampler , ExpectationBase
2123from qiskit .utils import QuantumInstance
2224
25+ from qiskit .primitives import BaseSampler , Sampler
26+ from qiskit .algorithms .state_fidelities import ComputeUncompute
27+
2328from .spsa import SPSA , CALLBACK , TERMINATIONCHECKER , _batch_evaluate
2429
2530# the function to compute the fidelity
@@ -55,6 +60,37 @@ class QNSPSA(SPSA):
5560 This short example runs QN-SPSA for the ground state calculation of the ``Z ^ Z``
5661 observable where the ansatz is a ``PauliTwoDesign`` circuit.
5762
63+ .. code-block:: python
64+
65+ import numpy as np
66+ from qiskit.algorithms.optimizers import QNSPSA
67+ from qiskit.circuit.library import PauliTwoDesign
68+ from qiskit.primitives import Estimator, Sampler
69+ from qiskit.quantum_info import Pauli
70+
71+ # problem setup
72+ ansatz = PauliTwoDesign(2, reps=1, seed=2)
73+ observable = Pauli("ZZ")
74+ initial_point = np.random.random(ansatz.num_parameters)
75+
76+ # loss function
77+ estimator = Estimator()
78+
79+ def loss(x):
80+ result = estimator.run([ansatz], [observable], [x]).result()
81+ return np.real(result.values[0])
82+
83+ # fidelity for estimation of the geometric tensor
84+ sampler = Sampler()
85+ fidelity = QNSPSA.get_fidelity(ansatz, sampler)
86+
87+ # run QN-SPSA
88+ qnspsa = QNSPSA(fidelity, maxiter=300)
89+ result = qnspsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point)
90+
91+ This is a legacy version solving the same problem but using Qiskit Opflow instead
92+ of the Qiskit Primitives. Note however, that this usage is pending deprecation.
93+
5894 .. code-block:: python
5995
6096 import numpy as np
@@ -87,18 +123,17 @@ def __init__(
87123 fidelity : FIDELITY ,
88124 maxiter : int = 100 ,
89125 blocking : bool = True ,
90- allowed_increase : Optional [float ] = None ,
91- learning_rate : Optional [Union [float , Callable [[], Iterator ]]] = None ,
92- perturbation : Optional [Union [float , Callable [[], Iterator ]]] = None ,
93- last_avg : int = 1 ,
94- resamplings : Union [int , Dict [int , int ]] = 1 ,
95- perturbation_dims : Optional [int ] = None ,
96- regularization : Optional [float ] = None ,
126+ allowed_increase : float | None = None ,
127+ learning_rate : float | Callable [[], Iterator ] | None = None ,
128+ perturbation : float | Callable [[], Iterator ] | None = None ,
129+ resamplings : int | dict [int , int ] = 1 ,
130+ perturbation_dims : int | None = None ,
131+ regularization : float | None = None ,
97132 hessian_delay : int = 0 ,
98- lse_solver : Optional [ Callable [[np .ndarray , np .ndarray ], np .ndarray ]] = None ,
99- initial_hessian : Optional [ np .ndarray ] = None ,
100- callback : Optional [ CALLBACK ] = None ,
101- termination_checker : Optional [ TERMINATIONCHECKER ] = None ,
133+ lse_solver : Callable [[np .ndarray , np .ndarray ], np .ndarray ] | None = None ,
134+ initial_hessian : np .ndarray | None = None ,
135+ callback : CALLBACK | None = None ,
136+ termination_checker : TERMINATIONCHECKER | None = None ,
102137 ) -> None :
103138 r"""
104139 Args:
@@ -121,8 +156,6 @@ def __init__(
121156 approximation of the gradients. Can be either a float or a generator yielding
122157 the perturbation magnitudes per step.
123158 If ``perturbation`` is set ``learning_rate`` must also be provided.
124- last_avg: Return the average of the ``last_avg`` parameters instead of just the
125- last parameter values.
126159 resamplings: The number of times the gradient (and Hessian) is sampled using a random
127160 direction to construct a gradient estimate. Per default the gradient is estimated
128161 using only one random direction. If an integer, all iterations use the same number
@@ -206,7 +239,7 @@ def _point_sample(self, loss, x, eps, delta1, delta2):
206239 return np .mean (loss_values ), gradient_estimate , hessian_estimate
207240
208241 @property
209- def settings (self ) -> Dict [str , Any ]:
242+ def settings (self ) -> dict [str , Any ]:
210243 """The optimizer settings in a dictionary format."""
211244 # re-use serialization from SPSA
212245 settings = super ().settings
@@ -221,11 +254,82 @@ def settings(self) -> Dict[str, Any]:
221254 @staticmethod
222255 def get_fidelity (
223256 circuit : QuantumCircuit ,
224- backend : Optional [Union [Backend , QuantumInstance ]] = None ,
225- expectation : Optional [ExpectationBase ] = None ,
257+ backend : Backend | QuantumInstance | None = None ,
258+ expectation : ExpectationBase | None = None ,
259+ * ,
260+ sampler : BaseSampler | None = None ,
226261 ) -> Callable [[np .ndarray , np .ndarray ], float ]:
227262 r"""Get a function to compute the fidelity of ``circuit`` with itself.
228263
264+ .. note::
265+
266+ Using this function with a backend and expectation converter is pending deprecation,
267+ instead pass a Qiskit Primitive sampler, such as :class:`~.Sampler`.
268+ The sampler can be passed as keyword argument or, positionally, as second argument.
269+
270+ Let ``circuit`` be a parameterized quantum circuit performing the operation
271+ :math:`U(\theta)` given a set of parameters :math:`\theta`. Then this method returns
272+ a function to evaluate
273+
274+ .. math::
275+
276+ F(\theta, \phi) = \big|\langle 0 | U^\dagger(\theta) U(\phi) |0\rangle \big|^2.
277+
278+ The output of this function can be used as input for the ``fidelity`` to the
279+ :class:~`qiskit.algorithms.optimizers.QNSPSA` optimizer.
280+
281+ Args:
282+ circuit: The circuit preparing the parameterized ansatz.
283+ backend: *Pending deprecation.* A backend of quantum instance to evaluate the circuits.
284+ If None, plain matrix multiplication will be used.
285+ expectation: *Pending deprecation.* An expectation converter to specify how the expected
286+ value is computed. If a shot-based readout is used this should be set to
287+ ``PauliExpectation``.
288+ sampler: A sampler primitive to sample from a quantum state.
289+
290+ Returns:
291+ A handle to the function :math:`F`.
292+
293+ """
294+ # allow passing sampler by position
295+ if isinstance (backend , BaseSampler ):
296+ sampler = backend
297+ backend = None
298+
299+ if expectation is None and backend is None and sampler is None :
300+ sampler = Sampler ()
301+
302+ if expectation is not None or backend is not None :
303+ warnings .warn (
304+ "Passing a backend and expectation converter to QNSPSA.get_fidelity is pending "
305+ "deprecation and will be deprecated in a future release. Instead, pass a "
306+ "sampler primitive." ,
307+ stacklevel = 2 ,
308+ category = PendingDeprecationWarning ,
309+ )
310+ return QNSPSA ._legacy_get_fidelity (circuit , backend , expectation )
311+
312+ fid = ComputeUncompute (sampler )
313+
314+ def fidelity (values_x , values_y ):
315+ result = fid .run (circuit , circuit , values_x , values_y ).result ()
316+ return np .asarray (result .fidelities )
317+
318+ return fidelity
319+
320+ @staticmethod
321+ def _legacy_get_fidelity (
322+ circuit : QuantumCircuit ,
323+ backend : Backend | QuantumInstance | None = None ,
324+ expectation : ExpectationBase | None = None ,
325+ ) -> Callable [[np .ndarray , np .ndarray ], float ]:
326+ r"""PENDING DEPRECATION. Get a function to compute the fidelity of ``circuit`` with itself.
327+
328+ .. note::
329+
330+ This method is pending deprecation. Instead use the :class:`~.ComputeUncompute`
331+ class which implements the fidelity calculation in the same fashion as this method.
332+
229333 Let ``circuit`` be a parameterized quantum circuit performing the operation
230334 :math:`U(\theta)` given a set of parameters :math:`\theta`. Then this method returns
231335 a function to evaluate
0 commit comments