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

Update v2 transpilation options #1373

Merged
merged 2 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def _validate_options(self, options: dict) -> None:
# TODO: Server should have different optimization/resilience levels for simulator

if (
options["resilience_level"] == 3
options.get("resilience", {}).get("pec_mitigation", False) is True
and self._backend is not None
and self._backend.configuration().simulator is True
and not options["simulator"]["coupling_map"]
Expand Down
39 changes: 7 additions & 32 deletions qiskit_ibm_runtime/options/estimator_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
skip_unset_validation,
)
from .execution_options import ExecutionOptionsV2
from .transpilation_options import TranspilationOptions
from .transpilation_options import TranspilationOptionsV2
from .resilience_options import ResilienceOptionsV2
from .twirling_options import TwirlingOptions
from .options import OptionsV2
Expand All @@ -32,24 +32,14 @@
from ..qiskit.primitives.options import primitive_dataclass

DDSequenceType = Literal["XX", "XpXm", "XY4"]
MAX_RESILIENCE_LEVEL: int = 2


@primitive_dataclass
class EstimatorOptions(OptionsV2):
"""Options for EstimatorV2.

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

resilience_level: How much resilience to build against errors.
Higher levels generate more accurate results,
at the expense of longer processing times. Default: 1.
Expand Down Expand Up @@ -88,40 +78,25 @@ class EstimatorOptions(OptionsV2):

"""

_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)
transpilation: Union[TranspilationOptionsV2, Dict] = Field(
default_factory=TranspilationOptionsV2
)
resilience: Union[ResilienceOptionsV2, Dict] = Field(default_factory=ResilienceOptionsV2)
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 <= EstimatorOptions._MAX_OPTIMIZATION_LEVEL:
raise ValueError(
"Invalid optimization_level. Valid range is "
f"0-{EstimatorOptions._MAX_OPTIMIZATION_LEVEL}"
)
return optimization_level

@field_validator("resilience_level")
@classmethod
@skip_unset_validation
def _validate_resilience_level(cls, resilience_level: int) -> int:
"""Validate resilience_level."""
if not 0 <= resilience_level <= EstimatorOptions._MAX_RESILIENCE_LEVEL:
if not 0 <= resilience_level <= MAX_RESILIENCE_LEVEL:
raise ValueError(
"Invalid optimization_level. Valid range is "
f"0-{EstimatorOptions._MAX_RESILIENCE_LEVEL}"
"Invalid optimization_level. Valid range is " f"0-{MAX_RESILIENCE_LEVEL}"
)
return resilience_level
1 change: 0 additions & 1 deletion qiskit_ibm_runtime/options/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ def _set_if_exists(name: str, _inputs: dict, _options: dict) -> None:
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),
}
Expand Down
35 changes: 5 additions & 30 deletions qiskit_ibm_runtime/options/sampler_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@

from typing import Union, Literal

from pydantic import Field, field_validator
from pydantic import Field

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

Expand All @@ -38,17 +37,6 @@ 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
Expand All @@ -68,24 +56,11 @@ class SamplerOptions(OptionsV2):

"""

_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)
transpilation: Union[TranspilationOptionsV2, Dict] = Field(
default_factory=TranspilationOptionsV2
)
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
32 changes: 32 additions & 0 deletions qiskit_ibm_runtime/options/transpilation_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"sabre",
"none",
]
MAX_OPTIMIZATION_LEVEL: int = 1


@primitive_dataclass
Expand Down Expand Up @@ -74,3 +75,34 @@ def _validate_approximation_degree(cls, degree: float) -> float:
"and 1.0 (no approximation)"
)
return degree


@primitive_dataclass
class TranspilationOptionsV2:
"""Transpilation options for v2 primitives.

Args:

skip_transpilation: Whether to skip transpilation. Default is False.

optimization_level: How much optimization to perform on the circuits.
Higher levels generate more optimized circuits,
at the expense of longer transpilation times.

* 0: no optimization
* 1: light optimization
"""

skip_transpilation: bool = False
optimization_level: Union[UnsetType, int] = 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 <= MAX_OPTIMIZATION_LEVEL:
raise ValueError(
"Invalid optimization_level. Valid range is " f"0-{MAX_OPTIMIZATION_LEVEL}"
)
return optimization_level
9 changes: 9 additions & 0 deletions test/unit/mock/fake_runtime_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ def __init__(
self._job_kwargs = job_kwargs or {}
self._channel = channel
self.session_time = 0
self._sessions = set()

# Setup the available backends
if not backend_specs:
Expand Down Expand Up @@ -342,6 +343,8 @@ def program_run(
)
self.session_time = session_time
self._jobs[job_id] = job
if start_session:
self._sessions.add(job_id)
return {"id": job_id, "backend": backend_name}

def job_get(self, job_id: str, exclude_params: bool = True) -> Any:
Expand Down Expand Up @@ -455,6 +458,12 @@ def backend_pulse_defaults(self, backend_name: str) -> Dict[str, Any]:
"""Return the pulse defaults of a backend."""
return self._find_backend(backend_name).defaults

def close_session(self, session_id: str) -> None:
"""Close the session."""
if session_id not in self._sessions:
raise ValueError(f"Session {session_id} not found.")
self._sessions.remove(session_id)

def _find_backend(self, backend_name):
for back in self._backends:
if back.name == backend_name:
Expand Down
18 changes: 7 additions & 11 deletions test/unit/test_estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ def test_run_program_inputs(self, in_pubs):
def test_unsupported_values_for_estimator_options(self):
"""Test exception when options levels are not supported."""
options_bad = [
{"resilience_level": 4, "optimization_level": 3},
{"optimization_level": 4, "resilience_level": 2},
{"resilience_level": 3},
{"resilience_level": 4},
]

with Session(
Expand All @@ -108,13 +108,13 @@ def test_unsupported_values_for_estimator_options(self):
inst.options.update(**bad_opt)
self.assertIn(list(bad_opt.keys())[0], str(exc.exception))

def test_res_level3_simulator(self):
"""Test the correct default error levels are used."""
def test_pec_simulator(self):
"""Test error is raised when using pec on simulator without coupling map."""

session = MagicMock(spec=MockSession)
session.service.backend().configuration().simulator = True

inst = EstimatorV2(session=session, options={"resilience_level": 3})
inst = EstimatorV2(session=session, options={"resilience": {"pec_mitigation": True}})
with self.assertRaises(ValueError) as exc:
inst.run((self.circuit, self.observables))
self.assertIn("coupling map", str(exc.exception))
Expand All @@ -127,17 +127,13 @@ def test_run_default_options(self):
EstimatorOptions(resilience_level=1), # pylint: disable=unexpected-keyword-arg
{"resilience_level": 1},
),
(
EstimatorOptions(optimization_level=3), # pylint: disable=unexpected-keyword-arg
{"transpilation": {"optimization_level": 3}},
),
(
{
"transpilation": {"initial_layout": [1, 2]},
"transpilation": {"optimization_level": 1},
"execution": {"shots": 100},
},
{
"transpilation": {"initial_layout": [1, 2]},
"transpilation": {"optimization_level": 1},
"execution": {"shots": 100},
},
),
Expand Down
1 change: 0 additions & 1 deletion test/unit/test_estimator_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class TestEStimatorOptions(IBMTestCase):
"""Class for testing the Sampler class."""

@data(
{"optimization_level": 99},
{"resilience_level": 99},
{"dynamical_decoupling": "foo"},
{"transpilation": {"skip_transpilation": "foo"}},
Expand Down
27 changes: 8 additions & 19 deletions test/unit/test_ibm_primitives_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,9 @@ def test_dict_options(self, primitive):
{},
{
"max_execution_time": 100,
"transpilation": {"initial_layout": [1, 2]},
"transpilation": {"optimization_level": 1},
"execution": {"shots": 100, "init_qubits": True},
},
{"optimization_level": 2},
{"transpilation": {}},
]
for options in options_vars:
Expand Down Expand Up @@ -363,17 +362,8 @@ def test_run_updated_options(self, primitive):
({"dynamical_decoupling": "XY4"}, {"dynamical_decoupling": "XY4"}),
({"shots": 200}, {"execution": {"shots": 200}}),
(
{"optimization_level": 3},
{"transpilation": {"optimization_level": 3}},
),
(
{"initial_layout": [1, 2], "optimization_level": 2},
{
"transpilation": {
"optimization_level": 2,
"initial_layout": [1, 2],
}
},
{"optimization_level": 1},
{"transpilation": {"optimization_level": 1}},
),
(
{"skip_transpilation": True},
Expand Down Expand Up @@ -458,14 +448,14 @@ def test_run_same_session(self):
@combine(
primitive=[EstimatorV2],
new_opts=[
{"optimization_level": 2},
{"optimization_level": 3, "shots": 200},
{"optimization_level": 0},
{"optimization_level": 1, "shots": 200},
],
)
def test_set_options(self, primitive, new_opts):
"""Test set options."""
opt_cls = primitive._options_class
options = opt_cls(optimization_level=1, execution={"shots": 100})
options = opt_cls(transpilation={"optimization_level": 1}, execution={"shots": 100})

session = MagicMock(spec=MockSession)
inst = primitive(session=session, options=options)
Expand All @@ -492,17 +482,16 @@ def test_accept_level_1_options(self, primitive):
{"shots": 10},
{"seed_simulator": 123},
{"skip_transpilation": True, "log_level": "ERROR"},
{"initial_layout": [1, 2], "shots": 100, "optimization_level": 2},
{"shots": 100, "optimization_level": 1},
]

expected_list = [opt_cls() for _ in range(len(options_dicts))]
expected_list[1].execution.shots = 10
expected_list[2].simulator.seed_simulator = 123
expected_list[3].transpilation.skip_transpilation = True
expected_list[3].environment.log_level = "ERROR"
expected_list[4].transpilation.initial_layout = [1, 2]
expected_list[4].execution.shots = 100
expected_list[4].optimization_level = 2
expected_list[4].transpilation.optimization_level = 1

session = MagicMock(spec=MockSession)
for opts, expected in zip(options_dicts, expected_list):
Expand Down
Loading
Loading