Skip to content

Commit

Permalink
added unittests
Browse files Browse the repository at this point in the history
Co-authored-by: Ikko Hamamura <[email protected]>
Co-authored-by: Takashi Imamichi <[email protected]>
  • Loading branch information
3 people committed Aug 22, 2022
1 parent c1423bb commit 7ee8f28
Show file tree
Hide file tree
Showing 13 changed files with 901 additions and 49 deletions.
74 changes: 74 additions & 0 deletions qiskit/algorithms/gradients/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# 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.

"""
=====================================
Gradients (:mod:`qiskit.algorithms.gradients`)
=====================================
.. currentmodule:: qiskit.algorithms.gradients
Base Classes
============
.. autosummary::
:toctree: ../stubs/
BaseSamplerGradient
BaseEstimatorGradient
Gradients
=========
.. autosummary::
:toctree: ../stubs/
FiniteDiffEstimatorGradient
FiniteDiffSamplerGradient
LinCombEstimatorGradient
LinCombSamplerGradient
ParamShiftEstimatorGradient
ParamShiftSamplerGradient
Results
=======
.. autosummary::
:toctree: ../stubs/
EstimatorResult
SamplerResult
"""

from .base_estimator_gradient import BaseEstimatorGradient
from .base_sampler_gradient import BaseSamplerGradient
from .estimator_gradient_result import EstimatorGradientResult
from .finite_diff_estimator_gradient import FiniteDiffEstimatorGradient
from .finite_diff_sampler_gradient import FiniteDiffSamplerGradient
from .lin_comb_estimator_gradient import LinCombEstimatorGradient
from .lin_comb_sampler_gradient import LinCombSamplerGradient
from .param_shift_estimator_gradient import ParamShiftEstimatorGradient
from .param_shift_sampler_gradient import ParamShiftSamplerGradient
from .sampler_gradient_result import SamplerGradientResult

__all__ = [
"BaseEstimatorGradient",
"BaseSamplerGradient",
"EstimatorGradientResult",
"FiniteDiffEstimatorGradient",
"FiniteDiffSamplerGradient",
"LinCombEstimatorGradient",
"LinCombSamplerGradient",
"ParamShiftEstimatorGradient",
"ParamShiftSamplerGradient",
"SamplerGradientResult",
]
51 changes: 45 additions & 6 deletions qiskit/algorithms/gradients/base_estimator_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
from collections.abc import Sequence

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

from .estimator_gradient_job import EstimatorGradientJob
from .estimator_gradient_result import EstimatorGradientResult


class BaseEstimatorGradient(ABC):
Expand All @@ -44,14 +45,14 @@ def __init__(
self._circuit_ids: dict[int, int] = {}
self._default_run_options = run_options

def run(
def evaluate(
self,
circuits: Sequence[QuantumCircuit],
observables: Sequence[BaseOperator | PauliSumOp],
parameter_values: Sequence[Sequence[float]],
partial: Sequence[Sequence[Parameter]] | None = None,
**run_options,
) -> EstimatorGradientJob:
) -> EstimatorGradientResult:
"""Run the job of the gradients of expectation values.
Args:
Expand All @@ -65,19 +66,57 @@ def run(
Returns:
The job object of the gradients of the expectation values. The i-th result corresponds to
``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``.
Raises:
QiskitError: Invalid arguments are given.
"""
# Validation
if len(circuits) != len(parameter_values):
raise QiskitError(
f"The number of circuits ({len(circuits)}) does not match "
f"the number of parameter value sets ({len(parameter_values)})."
)

if len(circuits) != len(observables):
raise QiskitError(
f"The number of circuits ({len(circuits)}) does not match "
f"the number of observables ({len(partial)})."
)

if partial is not None:
if len(circuits) != len(partial):
raise QiskitError(
f"The number of circuits ({len(circuits)}) does not match "
f"the number of partial parameter sets ({len(partial)})."
)

for i, (circuit, parameter_value) in enumerate(zip(circuits, parameter_values)):
if len(parameter_value) != circuit.num_parameters:
raise QiskitError(
f"The number of values ({len(parameter_value)}) does not match "
f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit."
)

for i, (circuit, observable) in enumerate(zip(circuits, observables)):
if circuit.num_qubits != observable.num_qubits:
raise QiskitError(
f"The number of qubits of the {i}-th circuit ({circuit.num_qubits}) does "
f"not match the number of qubits of the {i}-th observable "
f"({observable.num_qubits})."
)

# The priority of run option is as follows:
# run_options in `run` method > gradient's default run_options > primitive's default setting.
run_options = run_options or self._default_run_options
return self._run(circuits, observables, parameter_values, partial, **run_options)
return self._evaluate(circuits, observables, parameter_values, partial, **run_options)

@abstractmethod
def _run(
def _evaluate(
self,
circuits: Sequence[QuantumCircuit],
observables: Sequence[BaseOperator | PauliSumOp],
parameter_values: Sequence[Sequence[float]],
partial: Sequence[Sequence[Parameter]] | None = None,
**run_options,
) -> EstimatorGradientJob:
) -> EstimatorGradientResult:
raise NotImplementedError()
36 changes: 30 additions & 6 deletions qiskit/algorithms/gradients/base_sampler_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
from collections.abc import Sequence

from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.exceptions import QiskitError
from qiskit.primitives import BaseSampler
from .sampler_gradient_job import SamplerGradientJob
from .sampler_gradient_result import SamplerGradientResult


class BaseSamplerGradient(ABC):
Expand All @@ -37,13 +38,13 @@ def __init__(self, sampler: BaseSampler, **run_options):
self._circuit_ids: dict[int, int] = {}
self._default_run_options = run_options

def run(
def evaluate(
self,
circuits: Sequence[QuantumCircuit],
parameter_values: Sequence[Sequence[float]],
partial: Sequence[Sequence[Parameter]] | None = None,
**run_options,
) -> SamplerGradientJob:
) -> SamplerGradientResult:
"""Run the job of the gradients of the sampling probability.
Args:
Expand All @@ -58,18 +59,41 @@ def run(
``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``. The j-th
quasi-probability distribution in the i-th result corresponds to the gradients of the
sampling probability for the j-th parameter in ``circuits[i]``.
Raises:
QiskitError: Invalid arguments are given.
"""
# Validation
if len(circuits) != len(parameter_values):
raise QiskitError(
f"The number of circuits ({len(circuits)}) does not match "
f"the number of parameter value sets ({len(parameter_values)})."
)
if partial is not None:
if len(circuits) != len(partial):
raise QiskitError(
f"The number of circuits ({len(circuits)}) does not match "
f"the number of partial parameter sets ({len(partial)})."
)

for i, (circuit, parameter_value) in enumerate(zip(circuits, parameter_values)):
if len(parameter_value) != circuit.num_parameters:
raise QiskitError(
f"The number of values ({len(parameter_value)}) does not match "
f"the number of parameters ({circuit.num_parameters}) for the {i}-th circuit."
)

# The priority of run option is as follows:
# run_options in `run` method > gradient's default run_options > primitive's default run_options.
run_options = run_options or self._default_run_options
return self._run(circuits, parameter_values, partial, **run_options)
return self._evaluate(circuits, parameter_values, partial, **run_options)

@abstractmethod
def _run(
def _evaluate(
self,
circuits: Sequence[QuantumCircuit],
parameter_values: Sequence[Sequence[float]],
partial: Sequence[Sequence[Parameter]] | None = None,
**run_options,
) -> SamplerGradientJob:
) -> SamplerGradientResult:
raise NotImplementedError()
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@

from __future__ import annotations

import numpy as np
from typing import Any
from dataclasses import dataclass

from qiskit.primitives import EstimatorResult
from qiskit.providers import JobStatus


@dataclass(frozen=True)
class EstimatorGradientJob:
class EstimatorGradientResult:
"""Result of EstimatorGradient.
Args:
Expand All @@ -32,5 +33,6 @@ class EstimatorGradientJob:
status: List of JobStatus for each EstimatorResult.
"""

results: list[EstimatorResult]
values: list[np.ndarray]
status: list[JobStatus]
metadata: list[dict[str, Any]]
10 changes: 5 additions & 5 deletions qiskit/algorithms/gradients/finite_diff_estimator_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from qiskit.quantum_info.operators.base_operator import BaseOperator

from .base_estimator_gradient import BaseEstimatorGradient
from .estimator_gradient_job import EstimatorGradientJob
from .estimator_gradient_result import EstimatorGradientResult
from .utils import make_fin_diff_base_parameter_values


Expand All @@ -48,14 +48,14 @@ def __init__(
self._base_parameter_values_dict = {}
super().__init__(estimator, **run_options)

def _run(
def _evaluate(
self,
circuits: Sequence[QuantumCircuit],
observables: Sequence[BaseOperator | PauliSumOp],
parameter_values: Sequence[Sequence[float]],
partial: Sequence[Sequence[Parameter]] | None = None,
**run_options,
) -> EstimatorGradientJob:
) -> EstimatorGradientResult:
partial = partial or [[] for _ in range(len(circuits))]
gradients = []
status = []
Expand Down Expand Up @@ -122,6 +122,6 @@ def _run(
# minus
values[i] -= results.values[result_index_map[param] * 2 + 1] / (2 * self._epsilon)

gradients.append(EstimatorResult(values, metadata=run_options))
gradients.append(values)
status.append(job.status())
return EstimatorGradientJob(results=gradients, status=status)
return EstimatorGradientResult(values=gradients, status=status,metadata=run_options)
10 changes: 5 additions & 5 deletions qiskit/algorithms/gradients/finite_diff_sampler_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from qiskit.result import QuasiDistribution

from .base_sampler_gradient import BaseSamplerGradient
from .sampler_gradient_job import SamplerGradientJob
from .sampler_gradient_result import SamplerGradientResult
from .utils import make_fin_diff_base_parameter_values


Expand All @@ -47,13 +47,13 @@ def __init__(
self._base_parameter_values_dict = {}
super().__init__(sampler, **run_options)

def _run(
def _evaluate(
self,
circuits: Sequence[QuantumCircuit],
parameter_values: Sequence[Sequence[float]],
partial: Sequence[Sequence[Parameter]] | None = None,
**run_options,
) -> SamplerGradientJob:
) -> SamplerGradientResult:
partial = partial or [[] for _ in range(len(circuits))]
gradients = []
status = []
Expand Down Expand Up @@ -132,7 +132,7 @@ def _run(
)

gradients.append(
SamplerResult([QuasiDistribution(dist) for dist in dists], metadata=run_options)
[QuasiDistribution(dist) for dist in dists]
)
status.append(job.status())
return SamplerGradientJob(results=gradients, status=status)
return SamplerGradientResult(quasi_dists=gradients, status=status, metadata=run_options)
10 changes: 5 additions & 5 deletions qiskit/algorithms/gradients/lin_comb_estimator_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from qiskit.quantum_info.operators.base_operator import BaseOperator

from .base_estimator_gradient import BaseEstimatorGradient
from .estimator_gradient_job import EstimatorGradientJob
from .estimator_gradient_result import EstimatorGradientResult
from .utils import make_lin_comb_gradient_circuit

Pauli_Z = Pauli("Z")
Expand All @@ -47,14 +47,14 @@ def __init__(self, estimator: BaseEstimator, **run_options):
self._gradient_circuit_data_dict = {}
super().__init__(estimator, **run_options)

def _run(
def _evaluate(
self,
circuits: Sequence[QuantumCircuit],
observables: Sequence[BaseOperator | PauliSumOp],
parameter_values: Sequence[Sequence[float]],
partial: Sequence[Sequence[Parameter]] | None = None,
**run_options,
) -> EstimatorGradientJob:
) -> EstimatorGradientResult:
partial = partial or [[] for _ in range(len(circuits))]
gradients = []
status = []
Expand Down Expand Up @@ -121,6 +121,6 @@ def _run(
bound_coeff = coeff
values[i] += bound_coeff * results.values[result_index_map[param][j]]

gradients.append(EstimatorResult(values, metadata=run_options))
gradients.append(values)
status.append(job.status())
return EstimatorGradientJob(results=gradients, status=status)
return EstimatorGradientResult(values=gradients, status=status, metadata=run_options)
Loading

0 comments on commit 7ee8f28

Please sign in to comment.