Skip to content

Commit

Permalink
Merge branch 'gradients-primitives' into grad-test
Browse files Browse the repository at this point in the history
  • Loading branch information
a-matsuo authored Sep 2, 2022
2 parents f73b009 + c849f39 commit d26fa9b
Show file tree
Hide file tree
Showing 40 changed files with 475 additions and 293 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/randomized_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Run randomized tests
run: make test_randomized
env:
RUST_BACKTRACE=1
RUST_BACKTRACE: 1
- name: Create comment on failed test run
if: ${{ failure() }}
uses: peter-evans/create-or-update-comment@v1
Expand Down
4 changes: 2 additions & 2 deletions qiskit/algorithms/gradients/base_estimator_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ def run(
circuits: The list of quantum circuits to compute the gradients.
observables: The list of observables.
parameter_values: The list of parameter values to be bound to the circuit.
parameters: The Sequence of Sequence of Parameters to calculate only the gradients of
the specified parameters. Each Sequence of Parameters corresponds to a circuit in
parameters: The sequence of parameters to calculate only the gradients of
the specified parameters. Each sequence of parameters corresponds to a circuit in
``circuits``. Defaults to None, which means that the gradients of all parameters in
each circuit are calculated.
run_options: Backend runtime options used for circuit execution. The order of priority is:
Expand Down
6 changes: 3 additions & 3 deletions qiskit/algorithms/gradients/base_sampler_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ def run(
Args:
circuits: The list of quantum circuits to compute the gradients.
parameter_values: The list of parameter values to be bound to the circuit.
parameters: The Sequence of Sequence of Parameters to calculate only the gradients of
the specified parameters. Each Sequence of Parameters corresponds to a circuit in
parameters: The sequence of parameters to calculate only the gradients of
the specified parameters. Each sequence of parameters corresponds to a circuit in
``circuits``. Defaults to None, which means that the gradients of all parameters in
each circuit are calculated.
run_options: Backend runtime options used for circuit execution. The order of priority is:
Expand Down Expand Up @@ -105,7 +105,7 @@ def _validate_arguments(
circuits: Sequence[QuantumCircuit],
parameter_values: Sequence[Sequence[float]],
parameters: Sequence[Sequence[Parameter] | None] | None = None,
):
) -> None:
"""Validate the arguments of the ``run`` method.
Args:
Expand Down
13 changes: 5 additions & 8 deletions qiskit/algorithms/gradients/estimator_gradient_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,12 @@

@dataclass(frozen=True)
class EstimatorGradientResult:
"""Result of ``EstimatorGradient``.
Args:
gradients: The gradients of the expectation values.
metadata: Additional information about the job.
run_options: run_options for the estimator. Currently, estimator's default run_options is not
included.
"""
"""Result of EstimatorGradient."""

gradients: list[np.ndarray]
"""The gradients of the expectation values."""
metadata: list[dict[str, Any]]
"""Additional information about the job."""
run_options: dict[str, Any]
"""run_options for the estimator. Currently, estimator's default run_options is not
included."""
5 changes: 5 additions & 0 deletions qiskit/algorithms/gradients/finite_diff_estimator_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import numpy as np

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.exceptions import QiskitError
from qiskit.opflow import PauliSumOp
from qiskit.primitives import BaseEstimator
from qiskit.providers import JobStatus
from qiskit.quantum_info.operators.base_operator import BaseOperator

from .base_estimator_gradient import BaseEstimatorGradient
Expand Down Expand Up @@ -81,6 +83,9 @@ def _run(
jobs.append(job)

# combine the results
if any(job.status() is not JobStatus.DONE for job in jobs):
raise QiskitError("The gradient job was not completed successfully. ")

results = [job.result() for job in jobs]
gradients = []
for result in results:
Expand Down
4 changes: 4 additions & 0 deletions qiskit/algorithms/gradients/finite_diff_sampler_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import numpy as np

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.exceptions import QiskitError
from qiskit.primitives import BaseSampler
from qiskit.providers import JobStatus

from .base_sampler_gradient import BaseSamplerGradient
from .sampler_gradient_result import SamplerGradientResult
Expand Down Expand Up @@ -74,6 +76,8 @@ def _run(
jobs.append(job)

# combine the results
if any(job.status() is not JobStatus.DONE for job in jobs):
raise QiskitError("The gradient job was not completed successfully. ")
results = [job.result() for job in jobs]
gradients = []
for i, result in enumerate(results):
Expand Down
12 changes: 10 additions & 2 deletions qiskit/algorithms/gradients/lin_comb_estimator_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
import numpy as np

from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit
from qiskit.exceptions import QiskitError
from qiskit.opflow import PauliSumOp
from qiskit.primitives import BaseEstimator
from qiskit.primitives.utils import init_observable
from qiskit.providers import JobStatus
from qiskit.quantum_info import Pauli
from qiskit.quantum_info.operators.base_operator import BaseOperator

from .base_estimator_gradient import BaseEstimatorGradient
from .estimator_gradient_result import EstimatorGradientResult
from .utils import make_lin_comb_gradient_circuit
from .utils import _make_lin_comb_gradient_circuit


Pauli_Z = Pauli("Z")

Expand Down Expand Up @@ -65,6 +69,8 @@ def _run(
for circuit, observable, parameter_values_, parameters_ in zip(
circuits, observables, parameter_values, parameters
):
# Make the observable as observable as :class:`~qiskit.quantum_info.SparsePauliOp`.
observables = init_observable(observable)
# a set of parameters to be differentiated
if parameters_ is None:
param_set = set(circuit.parameters)
Expand All @@ -76,7 +82,7 @@ def _run(
observable_ = observable.expand(Pauli_Z)
gradient_circuits_ = self._gradient_circuits.get(id(circuit))
if gradient_circuits_ is None:
gradient_circuits_ = make_lin_comb_gradient_circuit(circuit)
gradient_circuits_ = _make_lin_comb_gradient_circuit(circuit)
self._gradient_circuits[id(circuit)] = gradient_circuits_

# only compute the gradients for parameters in the parameter set
Expand Down Expand Up @@ -112,6 +118,8 @@ def _run(
coeffs_all.append(coeffs)

# combine the results
if any(job.status() is not JobStatus.DONE for job in jobs):
raise QiskitError("The gradient job was not completed successfully. ")
results = [job.result() for job in jobs]
gradients = []
for i, result in enumerate(results):
Expand Down
8 changes: 6 additions & 2 deletions qiskit/algorithms/gradients/lin_comb_sampler_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
import numpy as np

from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit
from qiskit.exceptions import QiskitError
from qiskit.primitives import BaseSampler
from qiskit.providers import JobStatus

from .base_sampler_gradient import BaseSamplerGradient
from .sampler_gradient_result import SamplerGradientResult
from .utils import make_lin_comb_gradient_circuit
from .utils import _make_lin_comb_gradient_circuit


class LinCombSamplerGradient(BaseSamplerGradient):
Expand Down Expand Up @@ -68,7 +70,7 @@ def _run(
# TODO: support measurement in different basis (Y and Z+iY)
gradient_circuits_ = self._gradient_circuits.get(id(circuit))
if gradient_circuits_ is None:
gradient_circuits_ = make_lin_comb_gradient_circuit(circuit, add_measurement=True)
gradient_circuits_ = _make_lin_comb_gradient_circuit(circuit, add_measurement=True)
self._gradient_circuits[id(circuit)] = gradient_circuits_

# only compute the gradients for parameters in the parameter set
Expand Down Expand Up @@ -101,6 +103,8 @@ def _run(
coeffs_all.append(coeffs)

# combine the results
if any(job.status() is not JobStatus.DONE for job in jobs):
raise QiskitError("The gradient job was not completed successfully. ")
results = [job.result() for job in jobs]
gradients = []
for i, result in enumerate(results):
Expand Down
10 changes: 7 additions & 3 deletions qiskit/algorithms/gradients/param_shift_estimator_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@
import numpy as np

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.exceptions import QiskitError
from qiskit.opflow import PauliSumOp
from qiskit.primitives import BaseEstimator
from qiskit.providers import JobStatus
from qiskit.quantum_info.operators.base_operator import BaseOperator

from .base_estimator_gradient import BaseEstimatorGradient
from .estimator_gradient_result import EstimatorGradientResult

from .utils import param_shift_preprocessing, make_param_shift_parameter_values
from .utils import _param_shift_preprocessing, _make_param_shift_parameter_values


class ParamShiftEstimatorGradient(BaseEstimatorGradient):
Expand Down Expand Up @@ -67,7 +69,7 @@ def _run(
if self._gradient_circuits.get(id(circuit)):
gradient_circuit, base_parameter_values_all = self._gradient_circuits[id(circuit)]
else:
gradient_circuit, base_parameter_values_all = param_shift_preprocessing(circuit)
gradient_circuit, base_parameter_values_all = _param_shift_preprocessing(circuit)
self._gradient_circuits[id(circuit)] = (
gradient_circuit,
base_parameter_values_all,
Expand All @@ -78,7 +80,7 @@ def _run(
gradient_parameter_values_minus,
result_indices,
coeffs,
) = make_param_shift_parameter_values(
) = _make_param_shift_parameter_values(
gradient_circuit_data=gradient_circuit,
base_parameter_values=base_parameter_values_all,
parameter_values=parameter_values_,
Expand All @@ -96,6 +98,8 @@ def _run(
coeffs_all.append(coeffs)

# combine the results
if any(job.status() is not JobStatus.DONE for job in jobs):
raise QiskitError("The gradient job was not completed successfully. ")
results = [job.result() for job in jobs]
gradients = []
for i, result in enumerate(results):
Expand Down
10 changes: 7 additions & 3 deletions qiskit/algorithms/gradients/param_shift_sampler_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
import numpy as np

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.exceptions import QiskitError
from qiskit.primitives import BaseSampler
from qiskit.providers import JobStatus

from .base_sampler_gradient import BaseSamplerGradient
from .sampler_gradient_result import SamplerGradientResult
from .utils import param_shift_preprocessing, make_param_shift_parameter_values
from .utils import _param_shift_preprocessing, _make_param_shift_parameter_values


class ParamShiftSamplerGradient(BaseSamplerGradient):
Expand Down Expand Up @@ -61,7 +63,7 @@ def _run(
if self._gradient_circuits.get(id(circuit)):
gradient_circuit, base_parameter_values_all = self._gradient_circuits[id(circuit)]
else:
gradient_circuit, base_parameter_values_all = param_shift_preprocessing(circuit)
gradient_circuit, base_parameter_values_all = _param_shift_preprocessing(circuit)
self._gradient_circuits[id(circuit)] = (
gradient_circuit,
base_parameter_values_all,
Expand All @@ -72,7 +74,7 @@ def _run(
gradient_parameter_values_minus,
result_indices,
coeffs,
) = make_param_shift_parameter_values(
) = _make_param_shift_parameter_values(
gradient_circuit_data=gradient_circuit,
base_parameter_values=base_parameter_values_all,
parameter_values=parameter_values_,
Expand All @@ -90,6 +92,8 @@ def _run(
coeffs_all.append(coeffs)

# combine the results
if any(job.status() is not JobStatus.DONE for job in jobs):
raise QiskitError("The gradient job was not completed successfully. ")
results = [job.result() for job in jobs]
gradients = []
for i, result in enumerate(results):
Expand Down
16 changes: 5 additions & 11 deletions qiskit/algorithms/gradients/sampler_gradient_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,14 @@
from typing import Any
from dataclasses import dataclass

from qiskit.result import QuasiDistribution


@dataclass(frozen=True)
class SamplerGradientResult:
"""Result of ``SamplerGradient``.
Args:
gradients: The gradients of the quasi distributions.
metadata: Additional information about the job.
run_options: run_options for the sampler. Currently, sampler's default run_options is not
included.
"""
"""Result of SamplerGradient."""

gradients: list[list[QuasiDistribution]]
gradients: list[list[dict[int, float]]]
"""The gradients of the sample probabilities."""
metadata: list[dict[str, Any]]
"""Additional information about the job."""
run_options: dict[str, Any]
"""run_options for the sampler. Currently, sampler's default run_options is not included"""
30 changes: 25 additions & 5 deletions qiskit/algorithms/gradients/spsa_estimator_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import numpy as np

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.exceptions import QiskitError
from qiskit.opflow import PauliSumOp
from qiskit.primitives import BaseEstimator
from qiskit.providers import JobStatus
from qiskit.quantum_info.operators.base_operator import BaseOperator

from .base_estimator_gradient import BaseEstimatorGradient
Expand All @@ -37,13 +39,15 @@ def __init__(
self,
estimator: BaseEstimator,
epsilon: float,
batch_size: int = 1,
seed: int | None = None,
**run_options,
):
"""
Args:
estimator: The estimator used to compute the gradients.
epsilon: The offset size for the SPSA gradients.
batch_size: The number of gradients to average.
seed: The seed for a random perturbation vector.
run_options: Backend runtime options used for circuit execution. The order of priority is:
run_options in ``run`` method > gradient's default run_options > primitive's default
Expand All @@ -55,6 +59,7 @@ def __init__(
if epsilon <= 0:
raise ValueError(f"epsilon ({epsilon}) should be positive.")
self._epsilon = epsilon
self._batch_size = batch_size
self._seed = np.random.default_rng(seed) if seed else np.random.default_rng()

super().__init__(estimator, **run_options)
Expand All @@ -79,20 +84,35 @@ def _run(
indices = [circuit.parameters.data.index(p) for p in parameters_]
metadata_.append({"parameters": [circuit.parameters[idx] for idx in indices]})

offset = (-1) ** (self._seed.integers(0, 2, len(circuit.parameters)))
plus = parameter_values_ + self._epsilon * offset
minus = parameter_values_ - self._epsilon * offset
offset = [
(-1) ** (self._seed.integers(0, 2, len(circuit.parameters)))
for _ in range(self._batch_size)
]

plus = [parameter_values_ + self._epsilon * offset_ for offset_ in offset]
minus = [parameter_values_ - self._epsilon * offset_ for offset_ in offset]
offsets.append(offset)

job = self._estimator.run([circuit] * 2, [observable] * 2, [plus, minus], **run_options)
job = self._estimator.run(
[circuit] * 2 * self._batch_size,
[observable] * 2 * self._batch_size,
plus + minus,
**run_options,
)
jobs.append(job)

# combine the results
if any(job.status() is not JobStatus.DONE for job in jobs):
raise QiskitError("The gradient job was not completed successfully. ")
results = [job.result() for job in jobs]
gradients = []
for i, result in enumerate(results):
n = len(result.values) // 2 # is always a multiple of 2
gradient = (result.values[:n] - result.values[n:]) / (2 * self._epsilon * offsets[i])
gradient_ = (result.values[:n] - result.values[n:]) / (2 * self._epsilon)
# take the average of the spsa gradients
gradient = np.average(
np.array([grad / offset for grad, offset in zip(gradient_, offsets[i])]), axis=0
)
indices = [circuits[i].parameters.data.index(p) for p in metadata_[i]["parameters"]]
gradients.append(gradient[indices])

Expand Down
Loading

0 comments on commit d26fa9b

Please sign in to comment.