mirror of https://github.com/Qiskit/qiskit.git
Switch to primitive support in QNSPSA (#8682)
* switch to primitive support in QNSPSA * attempt to fix sphinx & cyclic import * add comment about sampler passed positionally * Update typehints * revert import * Apply suggestions from code review Co-authored-by: ElePT <epenatap@gmail.com> Co-authored-by: ElePT <57907331+ElePT@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
This commit is contained in:
parent
6beab09bf2
commit
259e14e08b
|
@ -12,7 +12,9 @@
|
|||
|
||||
"""The QN-SPSA optimizer."""
|
||||
|
||||
from typing import Any, Iterator, Optional, Union, Callable, Dict
|
||||
from __future__ import annotations
|
||||
from typing import Any, Iterator, Callable
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
from qiskit.providers import Backend
|
||||
|
@ -20,6 +22,9 @@ from qiskit.circuit import ParameterVector, QuantumCircuit
|
|||
from qiskit.opflow import StateFn, CircuitSampler, ExpectationBase
|
||||
from qiskit.utils import QuantumInstance
|
||||
|
||||
from qiskit.primitives import BaseSampler, Sampler
|
||||
from qiskit.algorithms.state_fidelities import ComputeUncompute
|
||||
|
||||
from .spsa import SPSA, CALLBACK, TERMINATIONCHECKER, _batch_evaluate
|
||||
|
||||
# the function to compute the fidelity
|
||||
|
@ -55,6 +60,37 @@ class QNSPSA(SPSA):
|
|||
This short example runs QN-SPSA for the ground state calculation of the ``Z ^ Z``
|
||||
observable where the ansatz is a ``PauliTwoDesign`` circuit.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import numpy as np
|
||||
from qiskit.algorithms.optimizers import QNSPSA
|
||||
from qiskit.circuit.library import PauliTwoDesign
|
||||
from qiskit.primitives import Estimator, Sampler
|
||||
from qiskit.quantum_info import Pauli
|
||||
|
||||
# problem setup
|
||||
ansatz = PauliTwoDesign(2, reps=1, seed=2)
|
||||
observable = Pauli("ZZ")
|
||||
initial_point = np.random.random(ansatz.num_parameters)
|
||||
|
||||
# loss function
|
||||
estimator = Estimator()
|
||||
|
||||
def loss(x):
|
||||
result = estimator.run([ansatz], [observable], [x]).result()
|
||||
return np.real(result.values[0])
|
||||
|
||||
# fidelity for estimation of the geometric tensor
|
||||
sampler = Sampler()
|
||||
fidelity = QNSPSA.get_fidelity(ansatz, sampler)
|
||||
|
||||
# run QN-SPSA
|
||||
qnspsa = QNSPSA(fidelity, maxiter=300)
|
||||
result = qnspsa.optimize(ansatz.num_parameters, loss, initial_point=initial_point)
|
||||
|
||||
This is a legacy version solving the same problem but using Qiskit Opflow instead
|
||||
of the Qiskit Primitives. Note however, that this usage is pending deprecation.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import numpy as np
|
||||
|
@ -87,18 +123,17 @@ class QNSPSA(SPSA):
|
|||
fidelity: FIDELITY,
|
||||
maxiter: int = 100,
|
||||
blocking: bool = True,
|
||||
allowed_increase: Optional[float] = None,
|
||||
learning_rate: Optional[Union[float, Callable[[], Iterator]]] = None,
|
||||
perturbation: Optional[Union[float, Callable[[], Iterator]]] = None,
|
||||
last_avg: int = 1,
|
||||
resamplings: Union[int, Dict[int, int]] = 1,
|
||||
perturbation_dims: Optional[int] = None,
|
||||
regularization: Optional[float] = None,
|
||||
allowed_increase: float | None = None,
|
||||
learning_rate: float | Callable[[], Iterator] | None = None,
|
||||
perturbation: float | Callable[[], Iterator] | None = None,
|
||||
resamplings: int | dict[int, int] = 1,
|
||||
perturbation_dims: int | None = None,
|
||||
regularization: float | None = None,
|
||||
hessian_delay: int = 0,
|
||||
lse_solver: Optional[Callable[[np.ndarray, np.ndarray], np.ndarray]] = None,
|
||||
initial_hessian: Optional[np.ndarray] = None,
|
||||
callback: Optional[CALLBACK] = None,
|
||||
termination_checker: Optional[TERMINATIONCHECKER] = None,
|
||||
lse_solver: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None,
|
||||
initial_hessian: np.ndarray | None = None,
|
||||
callback: CALLBACK | None = None,
|
||||
termination_checker: TERMINATIONCHECKER | None = None,
|
||||
) -> None:
|
||||
r"""
|
||||
Args:
|
||||
|
@ -121,8 +156,6 @@ class QNSPSA(SPSA):
|
|||
approximation of the gradients. Can be either a float or a generator yielding
|
||||
the perturbation magnitudes per step.
|
||||
If ``perturbation`` is set ``learning_rate`` must also be provided.
|
||||
last_avg: Return the average of the ``last_avg`` parameters instead of just the
|
||||
last parameter values.
|
||||
resamplings: The number of times the gradient (and Hessian) is sampled using a random
|
||||
direction to construct a gradient estimate. Per default the gradient is estimated
|
||||
using only one random direction. If an integer, all iterations use the same number
|
||||
|
@ -206,7 +239,7 @@ class QNSPSA(SPSA):
|
|||
return np.mean(loss_values), gradient_estimate, hessian_estimate
|
||||
|
||||
@property
|
||||
def settings(self) -> Dict[str, Any]:
|
||||
def settings(self) -> dict[str, Any]:
|
||||
"""The optimizer settings in a dictionary format."""
|
||||
# re-use serialization from SPSA
|
||||
settings = super().settings
|
||||
|
@ -221,11 +254,82 @@ class QNSPSA(SPSA):
|
|||
@staticmethod
|
||||
def get_fidelity(
|
||||
circuit: QuantumCircuit,
|
||||
backend: Optional[Union[Backend, QuantumInstance]] = None,
|
||||
expectation: Optional[ExpectationBase] = None,
|
||||
backend: Backend | QuantumInstance | None = None,
|
||||
expectation: ExpectationBase | None = None,
|
||||
*,
|
||||
sampler: BaseSampler | None = None,
|
||||
) -> Callable[[np.ndarray, np.ndarray], float]:
|
||||
r"""Get a function to compute the fidelity of ``circuit`` with itself.
|
||||
|
||||
.. note::
|
||||
|
||||
Using this function with a backend and expectation converter is pending deprecation,
|
||||
instead pass a Qiskit Primitive sampler, such as :class:`~.Sampler`.
|
||||
The sampler can be passed as keyword argument or, positionally, as second argument.
|
||||
|
||||
Let ``circuit`` be a parameterized quantum circuit performing the operation
|
||||
:math:`U(\theta)` given a set of parameters :math:`\theta`. Then this method returns
|
||||
a function to evaluate
|
||||
|
||||
.. math::
|
||||
|
||||
F(\theta, \phi) = \big|\langle 0 | U^\dagger(\theta) U(\phi) |0\rangle \big|^2.
|
||||
|
||||
The output of this function can be used as input for the ``fidelity`` to the
|
||||
:class:~`qiskit.algorithms.optimizers.QNSPSA` optimizer.
|
||||
|
||||
Args:
|
||||
circuit: The circuit preparing the parameterized ansatz.
|
||||
backend: *Pending deprecation.* A backend of quantum instance to evaluate the circuits.
|
||||
If None, plain matrix multiplication will be used.
|
||||
expectation: *Pending deprecation.* An expectation converter to specify how the expected
|
||||
value is computed. If a shot-based readout is used this should be set to
|
||||
``PauliExpectation``.
|
||||
sampler: A sampler primitive to sample from a quantum state.
|
||||
|
||||
Returns:
|
||||
A handle to the function :math:`F`.
|
||||
|
||||
"""
|
||||
# allow passing sampler by position
|
||||
if isinstance(backend, BaseSampler):
|
||||
sampler = backend
|
||||
backend = None
|
||||
|
||||
if expectation is None and backend is None and sampler is None:
|
||||
sampler = Sampler()
|
||||
|
||||
if expectation is not None or backend is not None:
|
||||
warnings.warn(
|
||||
"Passing a backend and expectation converter to QNSPSA.get_fidelity is pending "
|
||||
"deprecation and will be deprecated in a future release. Instead, pass a "
|
||||
"sampler primitive.",
|
||||
stacklevel=2,
|
||||
category=PendingDeprecationWarning,
|
||||
)
|
||||
return QNSPSA._legacy_get_fidelity(circuit, backend, expectation)
|
||||
|
||||
fid = ComputeUncompute(sampler)
|
||||
|
||||
def fidelity(values_x, values_y):
|
||||
result = fid.run(circuit, circuit, values_x, values_y).result()
|
||||
return np.asarray(result.fidelities)
|
||||
|
||||
return fidelity
|
||||
|
||||
@staticmethod
|
||||
def _legacy_get_fidelity(
|
||||
circuit: QuantumCircuit,
|
||||
backend: Backend | QuantumInstance | None = None,
|
||||
expectation: ExpectationBase | None = None,
|
||||
) -> Callable[[np.ndarray, np.ndarray], float]:
|
||||
r"""PENDING DEPRECATION. Get a function to compute the fidelity of ``circuit`` with itself.
|
||||
|
||||
.. note::
|
||||
|
||||
This method is pending deprecation. Instead use the :class:`~.ComputeUncompute`
|
||||
class which implements the fidelity calculation in the same fashion as this method.
|
||||
|
||||
Let ``circuit`` be a parameterized quantum circuit performing the operation
|
||||
:math:`U(\theta)` given a set of parameters :math:`\theta`. Then this method returns
|
||||
a function to evaluate
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Add support for the :class:`.BaseSampler` primitive in :func:`.QNSPSA.get_fidelity`.
|
||||
Now, the fidelity function can be constructed as::
|
||||
|
||||
from qiskit.primitives import Sampler
|
||||
from qiskit.algorithms.optimizers import QNSPSA
|
||||
|
||||
fidelity = QNSPSA.get_fidelity(my_circuit, Sampler())
|
||||
|
||||
deprecations:
|
||||
- |
|
||||
Using a backend and expectation converter in :func:`.QNSPSA.get_fidelity` is
|
||||
pending deprecation and will be deprecated in a future release. Instead, use
|
||||
a :class:`.BaseSampler` to evaluate circuits, see also the features of this release.
|
|
@ -19,7 +19,9 @@ import numpy as np
|
|||
|
||||
from qiskit.algorithms.optimizers import SPSA, QNSPSA
|
||||
from qiskit.circuit.library import PauliTwoDesign
|
||||
from qiskit.opflow import I, Z, StateFn
|
||||
from qiskit.primitives import Sampler
|
||||
from qiskit.providers.basicaer import StatevectorSimulatorPy
|
||||
from qiskit.opflow import I, Z, StateFn, MatrixExpectation
|
||||
from qiskit.utils import algorithm_globals
|
||||
|
||||
|
||||
|
@ -195,3 +197,28 @@ class TestSPSA(QiskitAlgorithmsTestCase):
|
|||
point = np.ones(5)
|
||||
result = SPSA.estimate_stddev(objective, point, avg=10, max_evals_grouped=max_evals_grouped)
|
||||
self.assertAlmostEqual(result, 0)
|
||||
|
||||
def test_qnspsa_fidelity_deprecation(self):
|
||||
"""Test using a backend and expectation converter in get_fidelity warns."""
|
||||
ansatz = PauliTwoDesign(2, reps=1, seed=2)
|
||||
|
||||
with self.assertWarns(PendingDeprecationWarning):
|
||||
_ = QNSPSA.get_fidelity(ansatz, StatevectorSimulatorPy(), MatrixExpectation())
|
||||
|
||||
def test_qnspsa_fidelity_primitives(self):
|
||||
"""Test the primitives can be used in get_fidelity."""
|
||||
ansatz = PauliTwoDesign(2, reps=1, seed=2)
|
||||
initial_point = np.random.random(ansatz.num_parameters)
|
||||
|
||||
with self.subTest(msg="pass as kwarg"):
|
||||
fidelity = QNSPSA.get_fidelity(ansatz, sampler=Sampler())
|
||||
result = fidelity(initial_point, initial_point)
|
||||
|
||||
self.assertAlmostEqual(result[0], 1)
|
||||
|
||||
# this test can be removed once backend and expectation are removed
|
||||
with self.subTest(msg="pass positionally"):
|
||||
fidelity = QNSPSA.get_fidelity(ansatz, Sampler())
|
||||
result = fidelity(initial_point, initial_point)
|
||||
|
||||
self.assertAlmostEqual(result[0], 1)
|
||||
|
|
Loading…
Reference in New Issue