diff --git a/qiskit_ibm_runtime/estimator.py b/qiskit_ibm_runtime/estimator.py index 8a0e72720..d385f98c5 100644 --- a/qiskit_ibm_runtime/estimator.py +++ b/qiskit_ibm_runtime/estimator.py @@ -141,7 +141,7 @@ def __init__( skip_transpilation: (DEPRECATED) Transpilation is skipped if set to True. False by default. Ignored ``skip_transpilation`` is also specified in ``options``. """ - # `_options` in this class is an instance of qiskit_ibm_runtime.Options class. + # `self._options` in this class is a Dict. # The base class, however, uses a `_run_options` which is an instance of # qiskit.providers.Options. We largely ignore this _run_options because we use # a nested dictionary to categorize options. @@ -166,12 +166,12 @@ def __init__( self._session: Session = None if options is None: - _options = Options() + self._options = asdict(Options()) elif isinstance(options, Options): - _options = copy.deepcopy(options) skip_transpilation = ( - _options.transpilation.skip_transpilation # type: ignore[union-attr] + options.transpilation.skip_transpilation # type: ignore[union-attr] ) + self._options = asdict(copy.deepcopy(options)) else: options_copy = copy.deepcopy(options) backend = options_copy.pop("backend", None) @@ -181,24 +181,15 @@ def __init__( version="0.7", remedy="Please pass the backend when opening a session.", ) - skip_transpilation = options.get("transpilation", {}).get( + default_options = asdict(Options()) + self._options = Options._merge_options(default_options, options_copy) + skip_transpilation = self._options.get("transpilation", {}).get( "skip_transpilation", False ) - log_level = options_copy.pop("log_level", None) - _options = Options(**options_copy) - if log_level: - issue_deprecation_msg( - msg="The 'log_level' option has been moved to the 'environment' category", - version="0.7", - remedy="Please specify 'environment':{'log_level': log_level} instead.", - ) - _options.environment.log_level = log_level # type: ignore[union-attr] - - _options.transpilation.skip_transpilation = ( # type: ignore[union-attr] - skip_transpilation - ) - self._options: dict = asdict(_options) + self._options["transpilation"][ + "skip_transpilation" + ] = skip_transpilation # type: ignore[union-attr] self._initial_inputs = { "circuits": circuits, diff --git a/qiskit_ibm_runtime/sampler.py b/qiskit_ibm_runtime/sampler.py index b7f9ed632..e0dd62bd2 100644 --- a/qiskit_ibm_runtime/sampler.py +++ b/qiskit_ibm_runtime/sampler.py @@ -116,7 +116,7 @@ def __init__( skip_transpilation (DEPRECATED): Transpilation is skipped if set to True. False by default. Ignored if ``skip_transpilation`` is also specified in ``options``. """ - # `_options` in this class is an instance of qiskit_ibm_runtime.Options class. + # `self._options` in this class is a Dict. # The base class, however, uses a `_run_options` which is an instance of # qiskit.providers.Options. We largely ignore this _run_options because we use # a nested dictionary to categorize options. @@ -141,12 +141,12 @@ def __init__( self._session: Session = None if options is None: - _options = Options() + self._options = asdict(Options()) elif isinstance(options, Options): - _options = copy.deepcopy(options) skip_transpilation = ( - _options.transpilation.skip_transpilation # type: ignore[union-attr] + options.transpilation.skip_transpilation # type: ignore[union-attr] ) + self._options = asdict(copy.deepcopy(options)) else: options_copy = copy.deepcopy(options) backend = options_copy.pop("backend", None) @@ -156,24 +156,14 @@ def __init__( version="0.7", remedy="Please pass the backend when opening a session.", ) - skip_transpilation = options.get("transpilation", {}).get( + default_options = asdict(Options()) + self._options = Options._merge_options(default_options, options_copy) + skip_transpilation = self._options.get("transpilation", {}).get( "skip_transpilation", False ) - log_level = options_copy.pop("log_level", None) - _options = Options(**options_copy) - if log_level: - issue_deprecation_msg( - msg="The 'log_level' option has been moved to the 'environment' category", - version="0.7", - remedy="Please specify 'environment':{'log_level': log_level} instead.", - ) - _options.environment.log_level = log_level # type: ignore[union-attr] - - _options.transpilation.skip_transpilation = ( # type: ignore[union-attr] - skip_transpilation - ) - - self._options: dict = asdict(_options) + self._options["transpilation"][ + "skip_transpilation" + ] = skip_transpilation # type: ignore[union-attr] self._initial_inputs = {"circuits": circuits, "parameters": parameters} diff --git a/releasenotes/notes/accept_level_1_options-06c09a67a9c23feb.yaml b/releasenotes/notes/accept_level_1_options-06c09a67a9c23feb.yaml new file mode 100644 index 000000000..1384093ec --- /dev/null +++ b/releasenotes/notes/accept_level_1_options-06c09a67a9c23feb.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + Accept all options on given on level 1 and assign them to the appropriate hierarchical option type. + For example, if the user provides ``options = {"shots": 10}`` as input to Sampler/Estimator, this will + be interpreted as ``options = {"execution: {"shots": 10}}``. diff --git a/test/unit/test_ibm_primitives.py b/test/unit/test_ibm_primitives.py index e7d9e9967..ee2c426af 100644 --- a/test/unit/test_ibm_primitives.py +++ b/test/unit/test_ibm_primitives.py @@ -531,6 +531,46 @@ def test_set_options(self): f"inst_options={inst_options}, new_str={new_str}", ) + def test_accept_level_1_options(self): + """Test initializing options properly when given on level 1.""" + + options_dicts = [ + {}, + {"shots": 10}, + {"seed_simulator": 123}, + {"skip_transpilation": True, "log_level": "ERROR"}, + {"initial_layout": [1, 2], "shots": 100, "noise_amplifier": "CxAmplifier"}, + ] + + expected_list = [Options(), Options(), Options(), Options(), Options()] + 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].resilience.noise_amplifier = "CxAmplifier" + + session = MagicMock(spec=MockSession) + primitives = [Sampler, Estimator] + for cls in primitives: + for opts, expected in zip(options_dicts, expected_list): + with self.subTest(primitive=cls, options=opts): + inst1 = cls(session=session, options=opts) + inst2 = cls(session=session, options=expected) + # Make sure the values are equal. + inst1_options = inst1.options.__dict__ + expected_dict = inst2.options.__dict__ + self.assertTrue( + dict_paritally_equal(inst1_options, expected_dict), + f"inst_options={inst1_options}, options={opts}", + ) + # Make sure the structure didn't change. + self.assertTrue( + dict_keys_equal(inst1_options, expected_dict), + f"inst_options={inst1_options}, expected={expected_dict}", + ) + def test_default_error_levels(self): """Test the correct default error levels are used."""