Skip to content

Commit

Permalink
Add BaseEstimatorV2 (#11527)
Browse files Browse the repository at this point in the history
* Add BaseEstimatorV2 class

Taken from 11227, but modified to
- remove options from BaseEstimatorV2
- add precision attribute to BaseEstimatorV2 and EstimatorPub
- remove BasePrimitiveV2 and BasePub

Co-Authored-By: Ikko Hamamura <[email protected]>
Co-Authored-By: Ian Hincks <[email protected]>

* Fix removal of BasePub

* Remove precision from EstimatorPub

* Add BaseEstimator._make_data_bin() static method

* Apply suggestions from code review

Co-authored-by: Ian Hincks <[email protected]>

* Apply suggestions from code review

Co-authored-by: Ian Hincks <[email protected]>

* linting

* Move precision to EstimatorPub and Estimator.run

* Update EstimatorV2 run return type, fix some typos

* Fix some minor problems

* add tests

* Add `*` arg blocker, np.float -> np.float64

* linting

* add test for line of code changed in observables_array.py

---------

Co-authored-by: Ikko Hamamura <[email protected]>
Co-authored-by: Ian Hincks <[email protected]>
Co-authored-by: Ian Hincks <[email protected]>
Co-authored-by: Ian Hincks <[email protected]>
  • Loading branch information
5 people authored Jan 17, 2024
1 parent bfc6b0d commit e16336b
Show file tree
Hide file tree
Showing 11 changed files with 766 additions and 14 deletions.
11 changes: 10 additions & 1 deletion qiskit/primitives/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
BaseEstimator
Estimator
BackendEstimator
EstimatorPub
Sampler
=======
Expand All @@ -41,6 +42,7 @@
BaseSampler
Sampler
BackendSampler
SamplerPub
Results
=======
Expand All @@ -59,6 +61,13 @@
from .base import BaseEstimator, BaseSampler
from .base.estimator_result import EstimatorResult
from .base.sampler_result import SamplerResult
from .containers import BindingsArray, ObservablesArray, PrimitiveResult, PubResult, SamplerPub
from .containers import (
BindingsArray,
ObservablesArray,
PrimitiveResult,
PubResult,
SamplerPub,
EstimatorPub,
)
from .estimator import Estimator
from .sampler import Sampler
2 changes: 1 addition & 1 deletion qiskit/primitives/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
Abstract base classes for primitives module.
"""

from .base_estimator import BaseEstimator
from .base_sampler import BaseSampler, BaseSamplerV2
from .base_estimator import BaseEstimator, BaseEstimatorV2
from .estimator_result import EstimatorResult
from .sampler_result import SamplerResult
140 changes: 132 additions & 8 deletions qiskit/primitives/base/base_estimator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
# (C) Copyright IBM 2022, 2023, 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
Expand All @@ -14,9 +14,79 @@
.. estimator-desc:
=====================
Overview of Estimator
=====================
========================
Overview of EstimatorV2
========================
:class:`~BaseEstimatorV2` is a primitive that estimates expectation values for provided quantum
circuit and observable combinations.
Following construction, an estimator is used by calling its :meth:`~.BaseEstimatorV2.run` method
with a list of pubs (Primitive Unified Blocs). Each pub contains three values that, together,
define a computation unit of work for the estimator to complete:
* a single :class:`~qiskit.circuit.QuantumCircuit`, possibly parametrized, whose final state we
define as :math:`\psi(\theta)`,
* one or more observables (specified as any :class:`~.ObservablesArrayLike`, including
:class:`~.Pauli`, :class:`~.SparsePauliOp`, ``str``) that specify which expectation values to
estimate, denoted :math:`H_j`, and
* a collection parameter value sets to bind the circuit against, :math:`\theta_k`.
Running an estimator returns a :class:`~qiskit.providers.JobV1` object, where calling
the method :meth:`qiskit.providers.JobV1.result` results in expectation value estimates and metadata
for each pub:
.. math::
\langle\psi(\theta_k)|H_j|\psi(\theta_k)\rangle
The observables and parameter values portion of a pub can be array-valued with arbitrary dimensions,
where standard broadcasting rules are applied, so that, in turn, the estimated result for each pub
is in general array-valued as well. For more information, please check
`here <https://github.com/Qiskit/RFCs/blob/master/0015-estimator-interface.md#arrays-and
-broadcasting->`_.
Here is an example of how the estimator is used.
.. code-block:: python
from qiskit.primitives.statevector_estimator import Estimator
from qiskit.circuit.library import RealAmplitudes
from qiskit.quantum_info import SparsePauliOp
psi1 = RealAmplitudes(num_qubits=2, reps=2)
psi2 = RealAmplitudes(num_qubits=2, reps=3)
H1 = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)])
H2 = SparsePauliOp.from_list([("IZ", 1)])
H3 = SparsePauliOp.from_list([("ZI", 1), ("ZZ", 1)])
theta1 = [0, 1, 1, 2, 3, 5]
theta2 = [0, 1, 1, 2, 3, 5, 8, 13]
theta3 = [1, 2, 3, 4, 5, 6]
estimator = Estimator()
# calculate [ <psi1(theta1)|H1|psi1(theta1)> ]
job = estimator.run([(psi1, hamiltonian1, [theta1])])
job_result = job.result() # It will block until the job finishes.
print(f"The primitive-job finished with result {job_result}"))
# calculate [ [<psi1(theta1)|H1|psi1(theta1)>,
# <psi1(theta3)|H3|psi1(theta3)>],
# [<psi2(theta2)|H2|psi2(theta2)>] ]
job2 = estimator.run(
[(psi1, [hamiltonian1, hamiltonian3], [theta1, theta3]), (psi2, hamiltonian2, theta2)]
)
job_result = job2.result()
print(f"The primitive-job finished with result {job_result}")
========================
Overview of EstimatorV1
========================
Estimator class estimates expectation values of quantum circuits and observables.
Expand Down Expand Up @@ -82,24 +152,36 @@

import warnings
from abc import abstractmethod
from collections.abc import Sequence
from collections.abc import Iterable, Sequence
from copy import copy
from typing import Generic, TypeVar

from qiskit.utils.deprecation import deprecate_func
import numpy as np
from numpy.typing import NDArray

from qiskit.circuit import QuantumCircuit
from qiskit.circuit.parametertable import ParameterView
from qiskit.providers import JobV1 as Job
from qiskit.quantum_info.operators import SparsePauliOp
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.utils.deprecation import deprecate_func

from .base_primitive import BasePrimitive
from ..containers import (
make_data_bin,
DataBin,
EstimatorPub,
EstimatorPubLike,
PrimitiveResult,
PubResult,
)
from . import validation
from .base_primitive import BasePrimitive
from .base_primitive_job import BasePrimitiveJob

T = TypeVar("T", bound=Job)


class BaseEstimator(BasePrimitive, Generic[T]):
class BaseEstimatorV1(BasePrimitive, Generic[T]):
"""Estimator base class.
Base class for Estimator that estimates expectation values of quantum circuits and observables.
Expand Down Expand Up @@ -254,3 +336,45 @@ def parameters(self) -> tuple[ParameterView, ...]:
Parameters, where ``parameters[i][j]`` is the j-th parameter of the i-th circuit.
"""
return tuple(self._parameters)


BaseEstimator = BaseEstimatorV1


class BaseEstimatorV2:
"""Estimator base class version 2.
An estimator estimates expectation values for provided quantum circuit and
observable combinations.
An Estimator implementation must treat the :meth:`.run` method ``precision=None``
kwarg as using a default ``precision`` value. The default value and methods to
set it can be determined by the Estimator implementor.
"""

@staticmethod
def _make_data_bin(pub: EstimatorPub) -> DataBin:
# provide a standard way to construct estimator databins to ensure that names match
# across implementations
return make_data_bin(
(("evs", NDArray[np.float64]), ("stds", NDArray[np.float64])), pub.shape
)

@abstractmethod
def run(
self, pubs: Iterable[EstimatorPubLike], *, precision: float | None = None
) -> BasePrimitiveJob[PrimitiveResult[PubResult]]:
"""Estimate expectation values for each provided pub (Primitive Unified Bloc).
Args:
pubs: An iterable of pub-like objects, such as tuples ``(circuit, observables)`` or
``(circuit, observables, parameter_values)``.
precision: The target precision for expectation value estimates of each
run :class:`.EstimatorPub` that does not specify its own
precision. If None the estimator's default precision value
will be used.
Returns:
A job object that contains results.
"""
pass
2 changes: 1 addition & 1 deletion qiskit/primitives/base/base_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ class BaseSamplerV2:

@abstractmethod
def run(
self, pubs: Iterable[SamplerPubLike], shots: int | None = None
self, pubs: Iterable[SamplerPubLike], *, shots: int | None = None
) -> BasePrimitiveJob[PrimitiveResult[PubResult]]:
"""Run and collect samples from each pub.
Expand Down
3 changes: 2 additions & 1 deletion qiskit/primitives/containers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

from .bindings_array import BindingsArray
from .bit_array import BitArray
from .data_bin import make_data_bin
from .data_bin import make_data_bin, DataBin
from .estimator_pub import EstimatorPub, EstimatorPubLike
from .observables_array import ObservablesArray
from .primitive_result import PrimitiveResult
from .pub_result import PubResult
Expand Down
2 changes: 1 addition & 1 deletion qiskit/primitives/containers/data_bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def make_data_bin(
.. code-block:: python
my_bin = make_data_bin([("alpha", np.NDArray[np.float])], shape=(20, 30))
my_bin = make_data_bin([("alpha", np.NDArray[np.float64])], shape=(20, 30))
# behaves like a dataclass
my_bin(alpha=np.empty((20, 30)))
Expand Down
Loading

0 comments on commit e16336b

Please sign in to comment.