Source code for qiskit_machine_learning.gradients.param_shift.param_shift_estimator_gradient

# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2022, 2024.
#
# 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

import numpy as np

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.primitives.base import BaseEstimatorV2
from qiskit.primitives import BaseEstimatorV1
from qiskit.providers.options import Options

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


[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]] | np.ndarray, parameters: Sequence[Sequence[Parameter]], **options, ) -> 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 ) results = self._run_unique( g_circuits, observables, g_parameter_values, g_parameters, **options ) 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]], **options, ) -> EstimatorGradientResult: """Compute the estimator gradients on the given circuits.""" job_circuits, job_observables, job_param_values, metadata = [], [], [], [] all_n = [] for circuit, observable, parameter_values_, parameters_ in zip( circuits, observables, parameter_values, parameters ): 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. n = len(param_shift_parameter_values) job_circuits.extend([circuit] * n) job_observables.extend([observable] * n) job_param_values.extend(param_shift_parameter_values) all_n.append(n) opt = Options(**options) # Determine how to run the estimator based on its version if isinstance(self._estimator, BaseEstimatorV1): # Run the single job with all circuits. job = self._estimator.run( job_circuits, job_observables, job_param_values, **options, ) results = job.result() # Compute the gradients. gradients = [] partial_sum_n = 0 for n in all_n: result = results.values[partial_sum_n : partial_sum_n + n] gradient_ = (result[: n // 2] - result[n // 2 :]) / 2 gradients.append(gradient_) partial_sum_n += n opt = self._get_local_options(options) elif isinstance(self._estimator, BaseEstimatorV2): if self._pass_manager is None: circs_ = job_circuits observables_ = job_observables else: circs_ = self._pass_manager.run(job_circuits) observables_ = [ op.apply_layout(circs_[i].layout) for i, op in enumerate(job_observables) ] # Prepare circuit-observable-parameter tuples (PUBs) circuit_observable_params = [] for pub in zip(circs_, observables_, job_param_values): circuit_observable_params.append(pub) # For BaseEstimatorV2, run the estimator using PUBs and specified precision job = self._estimator.run(circuit_observable_params) results = job.result() results = np.array([float(r.data.evs) for r in results]) # Compute the gradients. gradients = [] partial_sum_n = 0 for n in all_n: result = results[partial_sum_n : partial_sum_n + n] gradient_ = (result[: n // 2] - result[n // 2 :]) / 2 gradients.append(gradient_) partial_sum_n += n return EstimatorGradientResult(gradients=gradients, metadata=metadata, options=opt)