Source code for qiskit_qulacs.qulacs_estimator_gradient

"""QulacsEstimatorGradient class."""

from __future__ import annotations

import time
from collections.abc import Sequence
from typing import Any

import numpy as np
import sympy as sp
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.primitives import Estimator
from qiskit.providers import Options
from qiskit.quantum_info import SparsePauliOp
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit_algorithms.gradients import BaseEstimatorGradient
from qiskit_algorithms.gradients.base.estimator_gradient_result import (
    EstimatorGradientResult,
)

from qiskit_qulacs.adapter import (
    convert_qiskit_to_qulacs_circuit,
    convert_sparse_pauliop_to_qulacs_obs,
)


[docs] class QulacsEstimatorGradient(BaseEstimatorGradient): """QulacsEstimatorGradient class.""" def __init__( self, options: Options | None = None, ): # this is required by the base class, but not used dummy_estimator = Estimator() super().__init__(dummy_estimator, options) def _run( self, circuits: Sequence[QuantumCircuit], observables: Sequence[BaseOperator | SparsePauliOp], parameter_values: Sequence[Sequence[float]], parameters: Sequence[Sequence[Parameter]], **options, ) -> EstimatorGradientResult: """Compute the estimator gradients on the given circuits.""" gradients = [] metadata: list[dict[str, Any]] = [{} for _ in range(len(circuits))] for circuit, observable, parameter_values_, parameters_, metadatum in zip( circuits, observables, parameter_values, parameters, metadata ): qulacs_circuit = convert_qiskit_to_qulacs_circuit(circuit) qulacs_obs = convert_sparse_pauliop_to_qulacs_obs(observable) start = time.time() # Start timer params_values = np.array(parameter_values_) circ, metadata = qulacs_circuit(params_values) parameter_mapping = metadata["parameter_mapping"] # type: ignore parameter_exprs = metadata["parameter_exprs"] # type: ignore # Compute gradient using qulacs # `np.negative` is used because the gradients computed # differ by minus sign gradient = np.negative(circ.backprop(qulacs_obs)) # Evaluate the parameter expressions differentiation and # multiply it by the gradient to take into account the # expression's differentiation for i, idx in enumerate(parameter_mapping): f_params, f_expr = parameter_exprs[i] f = sp.lambdify(f_params, sp.diff(f_expr)) gradient[i] = f(params_values[idx]) * gradient[i] # The ordering of parameters is changed during circuit conversion. # `parameter_mapping` holds this mapping # Permute the obtained gradients to match with qiskit's ordering gradient[parameter_mapping] = gradient[range(len(parameter_mapping))] # Indices of parameters to be differentiated indices = [circuit.parameters.data.index(p) for p in parameters_] gradients.append(gradient[indices]) metadatum["time_taken"] = time.time() - start # End timer opt = self._get_local_options(options) return EstimatorGradientResult( gradients=gradients, metadata=metadata, options=opt )