Source code for qiskit_algorithms.gradients.param_shift.param_shift_estimator_gradient

# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2022, 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
Gradient of probabilities with parameter shift
"""

from __future__ import annotations

from collections.abc import Sequence

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info.operators.base_operator import BaseOperator

from ..base.base_estimator_gradient import BaseEstimatorGradient
from ..base.estimator_gradient_result import EstimatorGradientResult
from ..utils import _make_param_shift_parameter_values

from ...exceptions import AlgorithmError


[docs] class ParamShiftEstimatorGradient(BaseEstimatorGradient): """ Compute the gradients of the expectation values by the parameter shift rule [1]. **Reference:** [1] Schuld, M., Bergholm, V., Gogolin, C., Izaac, J., and Killoran, N. Evaluating analytic gradients on quantum hardware, `DOI <https://doi.org/10.1103/PhysRevA.99.032331>`_ """ SUPPORTED_GATES = [ "x", "y", "z", "h", "rx", "ry", "rz", "p", "cx", "cy", "cz", "ryy", "rxx", "rzz", "rzx", ] def _run( self, circuits: Sequence[QuantumCircuit], observables: Sequence[BaseOperator], parameter_values: Sequence[Sequence[float]], parameters: Sequence[Sequence[Parameter]], *, precision: float | Sequence[float] | None, ) -> EstimatorGradientResult: """Compute the gradients of the expectation values by the parameter shift rule.""" g_circuits, g_parameter_values, g_parameters = self._preprocess( circuits, parameter_values, parameters, self.SUPPORTED_GATES ) if self._transpiler is not None: g_circuits = self._transpiler.run(g_circuits, **self._transpiler_options) observables = [ obs.apply_layout(circuit.layout) for (circuit, obs) in zip(g_circuits, observables) ] results = self._run_unique( g_circuits, observables, g_parameter_values, g_parameters, precision=precision ) return self._postprocess(results, circuits, parameter_values, parameters) def _run_unique( self, circuits: Sequence[QuantumCircuit], observables: Sequence[BaseOperator], parameter_values: Sequence[Sequence[float]], parameters: Sequence[Sequence[Parameter]], *, precision: float | Sequence[float] | None, ) -> EstimatorGradientResult: """Compute the estimator gradients on the given circuits.""" has_transformed_precision = False if isinstance(precision, float) or precision is None: precision = [precision] * len(circuits) has_transformed_precision = True metadata = [] pubs = [] if not ( len(circuits) == len(observables) == len(parameters) == len(parameter_values) == len(precision) ): raise ValueError( f"circuits, observables, parameters, parameter_values and precision must have the same" f"length, but have respective lengths {len(circuits)}, {len(observables)}, " f"{len(parameters)}, {len(parameter_values)} and {len(precision)}." ) for circuit, observable, parameter_values_, parameters_, precision_ in zip( circuits, observables, parameter_values, parameters, precision ): metadata.append({"parameters": parameters_}) # Make parameter values for the parameter shift rule. param_shift_parameter_values = _make_param_shift_parameter_values( circuit, parameter_values_, parameters_ ) # Combine inputs into a single job to reduce overhead. pubs.append((circuit, observable, param_shift_parameter_values, precision_)) # Run the single job with all circuits. job = self._estimator.run(pubs) try: results = job.result() except Exception as exc: raise AlgorithmError("Estimator job failed.") from exc # Compute the gradients. gradients = [] for result in results: evs = result.data.evs n = evs.shape[0] gradient_ = (evs[: n // 2] - evs[n // 2 :]) / 2 gradients.append(gradient_) if has_transformed_precision: precision = precision[0] if precision is None: precision = results[0].metadata["target_precision"] else: for i, (precision_, result) in enumerate(zip(precision, results)): if precision_ is None: precision[i] = results[i].metadata["target_precision"] return EstimatorGradientResult(gradients=gradients, metadata=metadata, precision=precision)