Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EstimatorV2 updates #1321

Merged
merged 26 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5c2aa09
WIP EstimatorV2
kt474 Jan 17, 2024
cf82488
update encoder & fix linting
kt474 Jan 18, 2024
bb8125b
fix mypy & lint
kt474 Jan 18, 2024
3f28c19
update unit tests
kt474 Jan 18, 2024
b5f5b36
pubsresult -> pubresult
kt474 Jan 22, 2024
2b1334a
Update estimator result
kt474 Jan 22, 2024
e4dcf3a
remove old pub_result file
kt474 Jan 22, 2024
78d87ad
add integration test
kt474 Jan 23, 2024
ebe74e3
use qiskit main & remove copied files
kt474 Jan 23, 2024
fb0c5ea
fix lint, remove more copied files
kt474 Jan 23, 2024
814ef5e
lint
kt474 Jan 23, 2024
bf65cda
improve integration test
kt474 Jan 23, 2024
c67f61e
remove mthree from requirements
kt474 Jan 25, 2024
fb53b34
Merge branch 'experimental-0.2' into estimatorv2-updates
kt474 Jan 25, 2024
edd056b
Merge branch 'experimental-0.2' into estimatorv2-updates
kt474 Jan 31, 2024
cd69d94
use qiskit classes
jyu00 Feb 7, 2024
9e2a2ce
Merge branch 'experimental-0.2' of https://github.com/Qiskit/qiskit-i…
jyu00 Feb 7, 2024
f8f1250
delete program source
jyu00 Feb 7, 2024
eb68bb3
Merge branch 'experimental-0.2' of https://github.com/Qiskit/qiskit-i…
jyu00 Feb 7, 2024
ff82bc1
Merge branch 'experimental-0.2' into estimatorv2-updates
kt474 Feb 7, 2024
74e4108
lint/typing
kt474 Feb 7, 2024
37cf9a8
Fix imports
kt474 Feb 8, 2024
f1f08f5
Merge branch 'experimental-0.2' into estimatorv2-updates
kt474 Feb 8, 2024
e50aa42
fix bindingsarray
jyu00 Feb 8, 2024
0afc8d0
Merge branch 'estimatorv2-updates' of https://github.com/kt474/qiskit…
jyu00 Feb 8, 2024
c36bcac
fix tests
jyu00 Feb 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ disable=arguments-renamed, # more readable and clear
too-many-public-methods, # too verbose
too-many-statements, # too verbose
unnecessary-pass, # allow for methods with just "pass", for clarity
no-name-in-module, # remove after qiskit 1.0 release

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
Expand Down
13 changes: 9 additions & 4 deletions qiskit_ibm_runtime/base_primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from dataclasses import asdict, replace
import warnings

from qiskit.primitives.containers.estimator_pub import EstimatorPub
from qiskit.primitives.containers.sampler_pub import SamplerPub
from qiskit.providers.options import Options as TerraOptions

from .provider_session import get_cm_session as get_cm_provider_session
Expand All @@ -33,8 +35,6 @@
from .constants import DEFAULT_DECODERS
from .qiskit_runtime_service import QiskitRuntimeService

# TODO: remove when we have real v2 base estimator
from .qiskit.primitives import EstimatorPub, SamplerPub

# pylint: disable=unused-import,cyclic-import
from .session import Session
Expand Down Expand Up @@ -127,7 +127,7 @@ def _run(self, pubs: Union[list[EstimatorPub], list[SamplerPub]]) -> RuntimeJob:
Returns:
Submitted job.
"""
primitive_inputs = {"tasks": pubs}
primitive_inputs = {"pubs": pubs}
options_dict = asdict(self.options)
self._validate_options(options_dict)
primitive_inputs.update(self._options_class._get_program_inputs(options_dict))
Expand Down Expand Up @@ -170,13 +170,18 @@ def session(self) -> Optional[Session]:
"""
return self._session

@property
def options(self) -> BaseOptions:
"""Return options"""
return self._options

def _set_options(self, options: Optional[Union[Dict, BaseOptions]] = None) -> None:
"""Set options."""
if options is None:
self._options = self._options_class()
elif isinstance(options, dict):
default_options = self._options_class()
self.options = self._options_class(**merge_options(default_options, options))
self._options = self._options_class(**merge_options(default_options, options))
elif isinstance(options, self._options_class):
self._options = replace(options)
else:
Expand Down
89 changes: 56 additions & 33 deletions qiskit_ibm_runtime/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@

from __future__ import annotations
import os
from typing import Optional, Dict, Sequence, Any, Union
from typing import Optional, Dict, Sequence, Any, Union, Iterable
import logging

from qiskit.circuit import QuantumCircuit
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info.operators import SparsePauliOp
from qiskit.primitives import BaseEstimator
from qiskit.primitives.base import BaseEstimatorV2
from qiskit.primitives.containers import EstimatorPubLike
from qiskit.primitives.containers.estimator_pub import EstimatorPub

from .runtime_job import RuntimeJob
from .ibm_backend import IBMBackend
Expand All @@ -28,8 +32,6 @@
from .base_primitive import BasePrimitiveV1, BasePrimitiveV2
from .utils.qctrl import validate as qctrl_validate

# TODO: remove when we have real v2 base estimator
from .qiskit.primitives import BaseEstimatorV2

# pylint: disable=unused-import,cyclic-import
from .session import Session
Expand All @@ -44,49 +46,51 @@ class Estimator:


class EstimatorV2(BasePrimitiveV2, Estimator, BaseEstimatorV2):
"""Class for interacting with Qiskit Runtime Estimator primitive service.
r"""Class for interacting with Qiskit Runtime Estimator primitive service.

Qiskit Runtime Estimator primitive service estimates expectation values of quantum circuits and
observables.

The :meth:`run` can be used to submit circuits, observables, and parameters
to the Estimator primitive.

You are encouraged to use :class:`~qiskit_ibm_runtime.Session` to open a session,
during which you can invoke one or more primitives. Jobs submitted within a session
are prioritized by the scheduler, and data is cached for efficiency.
Following construction, an estimator is used by calling its :meth:`run` method
with a list of PUBs (Primitive Unified Blocs). Each PUB contains four values that, together,
define a computation unit of work for the estimator to complete:

Example::
* a single :class:`~qiskit.circuit.QuantumCircuit`, possibly parametrized, whose final state we
define as :math:`\psi(\theta)`,

from qiskit.circuit.library import RealAmplitudes
from qiskit.quantum_info import SparsePauliOp
* 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

from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
* a collection parameter value sets to bind the circuit against, :math:`\theta_k`.

service = QiskitRuntimeService(channel="ibm_cloud")
* an optional target precision for expectation value estimates.

psi1 = RealAmplitudes(num_qubits=2, reps=2)
Here is an example of how the estimator is used.

H1 = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)])
H2 = SparsePauliOp.from_list([("IZ", 1)])
H3 = SparsePauliOp.from_list([("ZI", 1), ("ZZ", 1)])
.. code-block:: python

with Session(service=service, backend="ibmq_qasm_simulator") as session:
estimator = Estimator(session=session)
from qiskit.circuit.library import RealAmplitudes
from qiskit.quantum_info import SparsePauliOp

theta1 = [0, 1, 1, 2, 3, 5]
from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator

# calculate [ <psi1(theta1)|H1|psi1(theta1)> ]
psi1_H1 = estimator.run(circuits=[psi1], observables=[H1], parameter_values=[theta1])
print(psi1_H1.result())
service = QiskitRuntimeService()
backend = service.backend("ibmq_qasm_simulator")

# calculate [ <psi1(theta1)|H2|psi1(theta1)>, <psi1(theta1)|H3|psi1(theta1)> ]
psi1_H23 = estimator.run(
circuits=[psi1, psi1],
observables=[H2, H3],
parameter_values=[theta1]*2
)
print(psi1_H23.result())
psi = RealAmplitudes(num_qubits=2, reps=2)
hamiltonian = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)])
theta = [0, 1, 1, 2, 3, 5]

estimator = Estimator(backend=backend)

# calculate [ <psi(theta1)|hamiltonian|psi(theta)> ]
job = estimator.run([(psi, hamiltonian, [theta])])
job_result = job.result()
print(f"The primitive-job finished with result {job_result}"))
"""

_options_class = EstimatorOptions
Expand Down Expand Up @@ -126,6 +130,25 @@ def __init__(
if self._service._channel_strategy == "q-ctrl":
raise NotImplementedError("EstimatorV2 is not supported with q-ctrl channel strategy.")

def run(
self, pubs: Iterable[EstimatorPubLike], *, precision: float | None = None
) -> RuntimeJob:
"""Submit a request to the estimator primitive.

Args:
pubs: An iterable of pub-like (primitive unified bloc) objects, such as
tuples ``(circuit, observables)`` or ``(circuit, observables, parameter_values)``.
precision: The target precision for expectation value estimates of each
run Estimator Pub that does not specify its own precision. If None
the estimator's default precision value will be used.

Returns:
Submitted job.

"""
coerced_pubs = [EstimatorPub.coerce(pub, precision) for pub in pubs]
return self._run(coerced_pubs) # type: ignore[arg-type]

def _validate_options(self, options: dict) -> None:
"""Validate that program inputs (options) are valid

Expand Down Expand Up @@ -232,7 +255,7 @@ def __init__(
def run( # pylint: disable=arguments-differ
self,
circuits: QuantumCircuit | Sequence[QuantumCircuit],
observables: BaseOperator | Sequence[BaseOperator],
observables: Sequence[BaseOperator | str] | BaseOperator | str,
parameter_values: Sequence[float] | Sequence[Sequence[float]] | None = None,
**kwargs: Any,
) -> RuntimeJob:
Expand Down Expand Up @@ -267,9 +290,9 @@ def run( # pylint: disable=arguments-differ

def _run( # pylint: disable=arguments-differ
self,
circuits: Sequence[QuantumCircuit],
observables: Sequence[BaseOperator],
parameter_values: Sequence[Sequence[float]],
circuits: tuple[QuantumCircuit, ...],
observables: tuple[SparsePauliOp, ...],
parameter_values: tuple[tuple[float, ...], ...],
**kwargs: Any,
) -> RuntimeJob:
"""Submit a request to the estimator primitive.
Expand Down
3 changes: 1 addition & 2 deletions qiskit_ibm_runtime/options/environment_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

from typing import Optional, Callable, List, Literal

# TODO use real base options when available
from ..qiskit.primitives.options import primitive_dataclass
from .utils import primitive_dataclass

LogLevelType = Literal[
"DEBUG",
Expand Down
4 changes: 1 addition & 3 deletions qiskit_ibm_runtime/options/estimator_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@
from .resilience_options import ResilienceOptionsV2
from .twirling_options import TwirlingOptions
from .options import OptionsV2

# TODO use real base options when available
from ..qiskit.primitives.options import primitive_dataclass
from .utils import primitive_dataclass

DDSequenceType = Literal["XX", "XpXm", "XY4"]
MAX_RESILIENCE_LEVEL: int = 2
Expand Down
5 changes: 1 addition & 4 deletions qiskit_ibm_runtime/options/execution_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@

from pydantic import model_validator, field_validator, ValidationInfo

from .utils import Unset, UnsetType, skip_unset_validation

# TODO use real base options when available
from ..qiskit.primitives.options import primitive_dataclass
from .utils import Unset, UnsetType, skip_unset_validation, primitive_dataclass


@primitive_dataclass
Expand Down
15 changes: 10 additions & 5 deletions qiskit_ibm_runtime/options/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@
from qiskit.transpiler import CouplingMap
from pydantic import Field

from .utils import Dict, _to_obj, UnsetType, Unset, _remove_dict_unset_values, merge_options
from .utils import (
Dict,
_to_obj,
UnsetType,
Unset,
_remove_dict_unset_values,
merge_options,
primitive_dataclass,
)
from .environment_options import EnvironmentOptions
from .execution_options import ExecutionOptionsV1 as ExecutionOptions
from .simulator_options import SimulatorOptions
from .transpilation_options import TranspilationOptions
from .resilience_options import ResilienceOptionsV1 as ResilienceOptions
from ..runtime_options import RuntimeOptions

# TODO use real base options when available
from ..qiskit.primitives.options import BasePrimitiveOptions, primitive_dataclass


@dataclass
class BaseOptions:
Expand Down Expand Up @@ -68,7 +73,7 @@ def _get_runtime_options(options: dict) -> dict:


@primitive_dataclass
class OptionsV2(BaseOptions, BasePrimitiveOptions):
class OptionsV2(BaseOptions):
"""Base primitive options, used by v2 primitives.

Args:
Expand Down
5 changes: 1 addition & 4 deletions qiskit_ibm_runtime/options/resilience_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@

from pydantic import field_validator, model_validator

from .utils import Unset, UnsetType, skip_unset_validation

# TODO use real base options when available
from ..qiskit.primitives.options import primitive_dataclass
from .utils import Unset, UnsetType, skip_unset_validation, primitive_dataclass


ResilienceSupportedOptions = Literal[
Expand Down
4 changes: 1 addition & 3 deletions qiskit_ibm_runtime/options/sampler_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
from .transpilation_options import TranspilationOptionsV2
from .twirling_options import TwirlingOptions
from .options import OptionsV2

# TODO use real base options when available
from ..qiskit.primitives.options import primitive_dataclass
from .utils import primitive_dataclass

DDSequenceType = Literal["XX", "XpXm", "XY4"]

Expand Down
5 changes: 1 addition & 4 deletions qiskit_ibm_runtime/options/simulator_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@

from pydantic import field_validator

from .utils import Unset, UnsetType, skip_unset_validation

# TODO use real base options when available
from ..qiskit.primitives.options import primitive_dataclass
from .utils import Unset, UnsetType, skip_unset_validation, primitive_dataclass


class NoiseModel:
Expand Down
4 changes: 1 addition & 3 deletions qiskit_ibm_runtime/options/transpilation_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@

from pydantic import field_validator

from .utils import Unset, UnsetType, skip_unset_validation
from .utils import Unset, UnsetType, skip_unset_validation, primitive_dataclass

# TODO use real base options when available
from ..qiskit.primitives.options import primitive_dataclass

LayoutMethodType = Literal[
"trivial",
Expand Down
5 changes: 1 addition & 4 deletions qiskit_ibm_runtime/options/twirling_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@

from typing import Literal, Union

from .utils import Unset, UnsetType

# TODO use real base options when available
from ..qiskit.primitives.options import primitive_dataclass
from .utils import Unset, UnsetType, primitive_dataclass


TwirlingStrategyType = Literal[
Expand Down
8 changes: 8 additions & 0 deletions qiskit_ibm_runtime/options/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import copy
from dataclasses import is_dataclass, asdict

from pydantic import ConfigDict
from pydantic.dataclasses import dataclass

from ..ibm_backend import IBMBackend

if TYPE_CHECKING:
Expand Down Expand Up @@ -170,3 +173,8 @@ def __bool__(self) -> bool:


Unset = UnsetType()


primitive_dataclass = dataclass(
config=ConfigDict(validate_assignment=True, arbitrary_types_allowed=True, extra="forbid")
)
13 changes: 0 additions & 13 deletions qiskit_ibm_runtime/qiskit/__init__.py

This file was deleted.

20 changes: 0 additions & 20 deletions qiskit_ibm_runtime/qiskit/primitives/__init__.py

This file was deleted.

Loading
Loading