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

SamplerV2 options #1223

Merged
merged 43 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2785023
Add experimental options (#1067)
jyu00 Sep 20, 2023
cf0d816
Merge branch 'main' of https://github.com/Qiskit/qiskit-ibm-runtime i…
jyu00 Oct 27, 2023
989a321
fix merge issues
jyu00 Oct 27, 2023
5ab8a93
add pydantic
jyu00 Oct 30, 2023
11c30cc
black
jyu00 Oct 30, 2023
e6ef47f
lint
jyu00 Oct 30, 2023
e998b81
Merge branch 'fast_forward' of https://github.com/jyu00/qiskit-ibm-ru…
jyu00 Oct 31, 2023
3e0b4af
Fast forward experimental to latest main (#1178)
jyu00 Oct 31, 2023
b5c7100
v2 options
jyu00 Nov 1, 2023
3671b46
estimator options
jyu00 Nov 3, 2023
bf5a677
update test
jyu00 Nov 3, 2023
1d4b36e
lint
jyu00 Nov 3, 2023
1790d63
Merge branch 'experimental' of https://github.com/Qiskit/qiskit-ibm-r…
jyu00 Nov 3, 2023
4198fad
fix merge issues
jyu00 Nov 3, 2023
9c3e359
black
jyu00 Nov 3, 2023
4ec1b9e
fix noise model type
jyu00 Nov 3, 2023
3c9261c
lint again
jyu00 Nov 3, 2023
6369191
fix header
jyu00 Nov 6, 2023
548005a
fix mypy
jyu00 Nov 6, 2023
d05ce59
use v2 as default
jyu00 Nov 6, 2023
6745067
cleanup terra options
jyu00 Nov 6, 2023
7370b5d
black
jyu00 Nov 6, 2023
a19ccd4
options need not be callable
jyu00 Nov 6, 2023
52febc9
fix doc
jyu00 Nov 6, 2023
b29a4e5
fix tests
jyu00 Nov 6, 2023
1f8bd7c
fix version
jyu00 Nov 6, 2023
027e6ca
add sampler option
jyu00 Nov 7, 2023
260f7c8
lint
jyu00 Nov 7, 2023
a56cdf0
freeze constants
jyu00 Nov 7, 2023
5574264
Merge branch 'options_v2' of https://github.com/jyu00/qiskit-ibm-runt…
jyu00 Nov 7, 2023
6b47aea
freeze constants
jyu00 Nov 7, 2023
0131789
remove is_simulator
jyu00 Nov 7, 2023
665b881
make image work
jyu00 Nov 8, 2023
be5b35e
Merge branch 'options_v2' of https://github.com/jyu00/qiskit-ibm-runt…
jyu00 Nov 8, 2023
d2692ff
fix merge issues
jyu00 Nov 8, 2023
ef332ba
fix tests
jyu00 Nov 8, 2023
9439047
Merge branch 'main' of https://github.com/Qiskit/qiskit-ibm-runtime i…
jyu00 Nov 16, 2023
9dd2c7d
fix tests
jyu00 Nov 16, 2023
d987572
lint
jyu00 Nov 16, 2023
64ccc42
Merge branch 'options_v2' of https://github.com/jyu00/qiskit-ibm-runt…
jyu00 Nov 16, 2023
17c3fb7
add seed_estimator
jyu00 Nov 16, 2023
4c96863
Merge branch 'experimental-0.2' of https://github.com/Qiskit/qiskit-i…
jyu00 Nov 16, 2023
ab493e5
resolve merge issues
jyu00 Nov 16, 2023
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
4 changes: 2 additions & 2 deletions qiskit_ibm_runtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ def result_callback(job_id, result):
from .version import __version__

from .estimator import EstimatorV2, EstimatorV1 as Estimator
from .sampler import SamplerV1 as Sampler
from .options import Options, EstimatorOptions
from .sampler import SamplerV2, SamplerV1 as Sampler
from .options import Options, EstimatorOptions, SamplerOptions

# Setup the logger for the IBM Quantum Provider package.
logger = logging.getLogger(__name__)
Expand Down
1 change: 0 additions & 1 deletion qiskit_ibm_runtime/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
class Estimator:
"""Base class for Qiskit Runtime Estimator."""

_PROGRAM_ID = "estimator"
version = 0


Expand Down
2 changes: 2 additions & 0 deletions qiskit_ibm_runtime/options/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
SimulatorOptions
TwirlingOptions
EstimatorOptions
SamplerOptions

"""

Expand All @@ -62,3 +63,4 @@
from .resilience_options import ResilienceOptionsV1 as ResilienceOptions
from .twirling_options import TwirlingOptions
from .estimator_options import EstimatorOptions
from .sampler_options import SamplerOptions
118 changes: 3 additions & 115 deletions qiskit_ibm_runtime/options/estimator_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,14 @@
"""Primitive options."""

from typing import Union, Literal
import copy

from qiskit.transpiler import CouplingMap
from pydantic.dataclasses import dataclass as pydantic_dataclass
from pydantic import Field, ConfigDict, field_validator

from .utils import (
Dict,
Unset,
UnsetType,
_remove_dict_unset_values,
merge_options,
skip_unset_validation,
)
from .execution_options import ExecutionOptionsV2
Expand Down Expand Up @@ -74,6 +70,8 @@ class EstimatorOptions(OptionsV2):
Allowed values are ``"XX"``, ``"XpXm"``, ``"XY4"``.
Default: None

seed_estimator: Seed used to control sampling.

transpilation: Transpilation options. See :class:`TranspilationOptions` for all
available options.

Expand All @@ -90,14 +88,14 @@ class EstimatorOptions(OptionsV2):

"""

_VERSION: int = Field(2, frozen=True) # pylint: disable=invalid-name
_MAX_OPTIMIZATION_LEVEL: int = Field(3, frozen=True) # pylint: disable=invalid-name
_MAX_RESILIENCE_LEVEL: int = Field(3, frozen=True) # pylint: disable=invalid-name

# Sadly we cannot use pydantic's built in validation because it won't work on Unset.
optimization_level: Union[UnsetType, int] = Unset
resilience_level: Union[UnsetType, int] = Unset
dynamical_decoupling: Union[UnsetType, DDSequenceType] = Unset
seed_estimator: Union[UnsetType, int] = Unset
transpilation: Union[TranspilationOptions, Dict] = Field(default_factory=TranspilationOptions)
resilience: Union[ResilienceOptionsV2, Dict] = Field(default_factory=ResilienceOptionsV2)
execution: Union[ExecutionOptionsV2, Dict] = Field(default_factory=ExecutionOptionsV2)
Expand Down Expand Up @@ -127,113 +125,3 @@ def _validate_resilience_level(cls, resilience_level: int) -> int:
f"0-{EstimatorOptions._MAX_RESILIENCE_LEVEL}"
)
return resilience_level

@staticmethod
def _get_program_inputs(options: dict) -> dict:
"""Convert the input options to program compatible inputs.

Returns:
Inputs acceptable by primitives.
"""

sim_options = options.get("simulator", {})
inputs = {}
inputs["transpilation"] = copy.copy(options.get("transpilation", {}))
inputs["skip_transpilation"] = inputs["transpilation"].pop("skip_transpilation")
coupling_map = sim_options.get("coupling_map", None)
# TODO: We can just move this to json encoder
if isinstance(coupling_map, CouplingMap):
coupling_map = list(map(list, coupling_map.get_edges()))
inputs["transpilation"].update(
{
"optimization_level": options.get("optimization_level"),
"coupling_map": coupling_map,
"basis_gates": sim_options.get("basis_gates", None),
}
)

inputs["resilience_level"] = options.get("resilience_level")
inputs["resilience"] = options.get("resilience", {})

inputs["twirling"] = options.get("twirling", {})

inputs["execution"] = options.get("execution", {})
inputs["execution"].update(
{
"noise_model": sim_options.get("noise_model", Unset),
"seed_simulator": sim_options.get("seed_simulator", Unset),
}
)

# Add arbitrary experimental options
if isinstance(options.get("experimental", None), dict):
inputs = merge_options(inputs, options.get("experimental"))

# Remove image
inputs.pop("image", None)

inputs["_experimental"] = True
inputs["version"] = EstimatorOptions._VERSION
_remove_dict_unset_values(inputs)

return inputs


# @dataclass(frozen=True)
# class _ResilienceLevel0Options:
# resilience_level: int = 0
# resilience: ResilienceOptions = field(
# default_factory=lambda: ResilienceOptions(
# measure_noise_mitigation=False, zne_mitigation=False, pec_mitigation=False
# )
# )
# twirling: TwirlingOptions = field(
# default_factory=lambda: TwirlingOptions(gates=False, measure=False)
# )


# @dataclass(frozen=True)
# class _ResilienceLevel1Options:
# resilience_level: int = 1
# resilience: ResilienceOptions = field(
# default_factory=lambda: ResilienceOptions(
# measure_noise_mitigation=True, zne_mitigation=False, pec_mitigation=False
# )
# )
# twirling: TwirlingOptions = field(
# default_factory=lambda: TwirlingOptions(gates=False, measure=True, strategy="active-accum")
# )


# @dataclass(frozen=True)
# class _ResilienceLevel2Options:
# resilience_level: int = 2
# resilience: ResilienceOptions = field(
# default_factory=lambda: ResilienceOptions(
# measure_noise_mitigation=True, pec_mitigation=False, **asdict(_ZneOptions())
# )
# )
# twirling: TwirlingOptions = field(
# default_factory=lambda: TwirlingOptions(gates=True, measure=True, strategy="active-accum")
# )


# @dataclass(frozen=True)
# class _ResilienceLevel3Options:
# resilience_level: int = 3
# resilience: ResilienceOptions = field(
# default_factory=lambda: ResilienceOptions(
# measure_noise_mitigation=True, zne_mitigation=False, **asdict(_PecOptions())
# )
# )
# twirling: TwirlingOptions = field(
# default_factory=lambda: TwirlingOptions(gates=True, measure=True, strategy="active")
# )


# _DEFAULT_RESILIENCE_LEVEL_OPTIONS = {
# 0: _ResilienceLevel0Options(),
# 1: _ResilienceLevel1Options(),
# 2: _ResilienceLevel2Options(),
# 3: _ResilienceLevel3Options(),
# }
69 changes: 68 additions & 1 deletion qiskit_ibm_runtime/options/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from pydantic.dataclasses import dataclass as pydantic_dataclass
from pydantic import Field, ConfigDict

from .utils import Dict, _to_obj, UnsetType, Unset, _remove_dict_unset_values
from .utils import Dict, _to_obj, UnsetType, Unset, _remove_dict_unset_values, merge_options
from .environment_options import EnvironmentOptions
from .execution_options import ExecutionOptionsV1 as ExecutionOptions
from .simulator_options import SimulatorOptions
Expand Down Expand Up @@ -93,10 +93,77 @@ class OptionsV2(BaseOptions):
:class:`SimulatorOptions` for all available options.
"""

_VERSION: int = Field(2, frozen=True) # pylint: disable=invalid-name

# Options not really related to primitives.
max_execution_time: Union[UnsetType, int] = Unset
environment: Union[EnvironmentOptions, Dict] = Field(default_factory=EnvironmentOptions)
simulator: Union[SimulatorOptions, Dict] = Field(default_factory=SimulatorOptions)

@staticmethod
def _get_program_inputs(options: dict) -> dict:
"""Convert the input options to program compatible inputs.

Returns:
Inputs acceptable by primitives.
"""

def _set_if_exists(name: str, _inputs: dict, _options: dict) -> None:
if name in _options:
_inputs[name] = _options[name]

options_copy = copy.deepcopy(options)
sim_options = options_copy.get("simulator", {})
inputs = {}
inputs["transpilation"] = options_copy.get("transpilation", {})
inputs["skip_transpilation"] = inputs["transpilation"].pop("skip_transpilation")
coupling_map = sim_options.get("coupling_map", Unset)
# TODO: We can just move this to json encoder
if isinstance(coupling_map, CouplingMap):
coupling_map = list(map(list, coupling_map.get_edges()))
inputs["transpilation"].update(
{
"optimization_level": options_copy.get("optimization_level", Unset),
"coupling_map": coupling_map,
"basis_gates": sim_options.get("basis_gates", Unset),
}
)

for fld in [
"resilience_level",
"resilience",
"twirling",
"dynamical_decoupling",
"seed_estimator",
]:
_set_if_exists(fld, inputs, options_copy)

inputs["execution"] = options_copy.get("execution", {})
inputs["execution"].update(
{
"noise_model": sim_options.get("noise_model", Unset),
"seed_simulator": sim_options.get("seed_simulator", Unset),
}
)

# Add arbitrary experimental options
if isinstance(options_copy.get("experimental", None), dict):
inputs = merge_options(inputs, options_copy.get("experimental"))

# Remove image
inputs.pop("image", None)

inputs["_experimental"] = True
inputs["version"] = OptionsV2._VERSION
_remove_dict_unset_values(inputs)

# Remove empty dictionaries
for key, val in list(inputs.items()):
if isinstance(val, dict) and not val:
del inputs[key]

return inputs


@dataclass
class Options(BaseOptions):
Expand Down
91 changes: 91 additions & 0 deletions qiskit_ibm_runtime/options/sampler_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 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.

"""Sampler options."""

from typing import Union, Literal

from pydantic.dataclasses import dataclass as pydantic_dataclass
from pydantic import Field, ConfigDict, field_validator

from .utils import (
Dict,
Unset,
UnsetType,
skip_unset_validation,
)
from .execution_options import ExecutionOptionsV2
from .transpilation_options import TranspilationOptions
from .twirling_options import TwirlingOptions
from .options import OptionsV2

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


@pydantic_dataclass(
config=ConfigDict(validate_assignment=True, arbitrary_types_allowed=True, extra="forbid")
)
class SamplerOptions(OptionsV2):
"""Options for v2 Sampler.

Args:
optimization_level: How much optimization to perform on the circuits.
Higher levels generate more optimized circuits,
at the expense of longer transpilation times. This is based on the
``optimization_level`` parameter in qiskit-terra but may include
backend-specific optimization. Default: 1.

* 0: no optimization
* 1: light optimization
* 2: heavy optimization
* 3: even heavier optimization

dynamical_decoupling: Optional, specify a dynamical decoupling sequence to use.
Allowed values are ``"XX"``, ``"XpXm"``, ``"XY4"``.
Default: None

transpilation: Transpilation options. See :class:`TranspilationOptions` for all
available options.

execution: Execution time options. See :class:`ExecutionOptionsV2` for all available options.

twirling: Pauli-twirling related options. See :class:`TwirlingOptions` for all available options.

environment: Options related to the execution environment. See
:class:`EnvironmentOptions` for all available options.

simulator: Simulator options. See
:class:`SimulatorOptions` for all available options.

"""

_MAX_OPTIMIZATION_LEVEL: int = Field(3, frozen=True) # pylint: disable=invalid-name

# Sadly we cannot use pydantic's built in validation because it won't work on Unset.
optimization_level: Union[UnsetType, int] = Unset
dynamical_decoupling: Union[UnsetType, DDSequenceType] = Unset
transpilation: Union[TranspilationOptions, Dict] = Field(default_factory=TranspilationOptions)
execution: Union[ExecutionOptionsV2, Dict] = Field(default_factory=ExecutionOptionsV2)
twirling: Union[TwirlingOptions, Dict] = Field(default_factory=TwirlingOptions)
experimental: Union[UnsetType, dict] = Unset

@field_validator("optimization_level")
@classmethod
@skip_unset_validation
def _validate_optimization_level(cls, optimization_level: int) -> int:
"""Validate optimization_leve."""
if not 0 <= optimization_level <= SamplerOptions._MAX_OPTIMIZATION_LEVEL:
raise ValueError(
"Invalid optimization_level. Valid range is "
f"0-{SamplerOptions._MAX_OPTIMIZATION_LEVEL}"
)
return optimization_level
1 change: 1 addition & 0 deletions qiskit_ibm_runtime/qiskit/primitives/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@

from .base_estimator import BaseEstimatorV2 # type: ignore
from .base_primitive import BasePrimitiveOptions # type: ignore
from .base_sampler import BaseSamplerV2 # type: ignore
Loading
Loading