class BaseEstimatorGradient(ABC):
    
    def __init__(
        self,
        estimator: BaseEstimator,
        options: Options | None = None,
    ):
        ...

    def run(
        self,
        circuits: Sequence[QuantumCircuit],
        observables: Sequence[BaseOperator | PauliSumOp],
        parameter_values: Sequence[Sequence[float]],
        parameters: Sequence[Sequence[Parameter] | None] | None = None,
        **options,
    ) -> AlgorithmJob:
        ...

@dataclass(frozen=True)
class EstimatorGradientResult:
    """Result of EstimatorGradient."""

    gradients: list[np.ndarray]
    """The gradients of the expectation values."""
    metadata: list[dict[str, Any]]
    """Additional information about the job."""
    options: Options
    """Primitive runtime options for the execution of the job."""

from qiskit.algorithms.gradients import ParamShiftEstimatorGradient
from qiskit.circuit import Parameter
from qiskit.circuit.library import RealAmplitudes
from qiskit.primitives import Estimator
from qiskit.quantum_info import SparsePauliOp

qc = RealAmplitudes(num_qubits=2, reps=1)
op = SparsePauliOp.from_list([("ZI", 1)])
qc.decompose().draw('mpl')

# How to use the Estimator
estimator = Estimator()
values = [1, 2, 3, 4]

# run a job
job = estimator.run([qc], [op], [values])

# get results
result = job.result().values
result

# How to use the ParamShiftEstimatorGradient
param_shift_grad = ParamShiftEstimatorGradient(estimator)

# run a job
job = param_shift_grad.run([qc], [op], [values])

# get results
result = job.result().gradients
result

estimator = Estimator()

# Instantiate a grdient class
param_shift_grad = ParamShiftEstimatorGradient(estimator)

values1 = [1, 2, 3, 4]
values2 = [5, 6, 7, 8]

# run a job
job = param_shift_grad.run([qc]*2, [op]*2, [values1, values2])

# get results
result = job.result().gradients
result

# EstimatorGradient
param_shift_grad = ParamShiftEstimatorGradient(estimator)

param = [p for p in qc.parameters]

# run a job
# param = list of parameters in the quantum circuit
job = param_shift_grad.run([qc]*2, [op]*2, [values, values], [param, param[:2]])

# get results
result = job.result()
result.gradients

# metadata in EstimatorGradientResult stores which parameters' graidents were caluclated
print("result.metadata")
print(result.metadata[0])
print(result.metadata[1])