# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# 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.
"""Container class for backend options."""
import io
from collections.abc import Mapping
[ドキュメント]class Options(Mapping):
"""Base options object
This class is what all backend options are based
on. The properties of the class are intended to be all dynamically
adjustable so that a user can reconfigure the backend on demand. If a
property is immutable to the user (eg something like number of qubits)
that should be a configuration of the backend class itself instead of the
options.
Instances of this class behave like dictionaries. Accessing an
option with a default value can be done with the `get()` method:
>>> options = Options(opt1=1, opt2=2)
>>> options.get("opt1")
1
>>> options.get("opt3", default="hello")
'hello'
Key-value pairs for all options can be retrieved using the `items()` method:
>>> list(options.items())
[('opt1', 1), ('opt2', 2)]
Options can be updated by name:
>>> options["opt1"] = 3
>>> options.get("opt1")
3
Runtime validators can be registered. See `set_validator`.
Updates through `update_options` and indexing (`__setitem__`) validate
the new value before performing the update and raise `ValueError` if
the new value is invalid.
>>> options.set_validator("opt1", (1, 5))
>>> options["opt1"] = 4
>>> options["opt1"]
4
>>> options["opt1"] = 10 # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ValueError: ...
"""
# Here there are dragons.
# This class preamble is an abhorrent hack to make `Options` work similarly to a
# SimpleNamespace, but with its instance methods and attributes in a separate namespace. This
# is required to make the initial release of Qiskit Terra 0.19 compatible with already released
# versions of Qiskit Experiments, which rely on both of
# options.my_key = my_value
# transpile(qc, **options.__dict__)
# working.
#
# Making `__dict__` a property which gets a slotted attribute solves the second line. The
# slotted attributes are not stored in a `__dict__` anyway, and `__slots__` classes suppress the
# creation of `__dict__`. That leaves it free for us to override it with a property, which
# returns the options namespace `_fields`.
#
# We need to make attribute setting simply set options as well, to support statements of the
# form `options.key = value`. We also need to ensure that existing uses do not override any new
# methods. We do this by overriding `__setattr__` to purely write into our `_fields` dict
# instead. This has the highly unusual behavior that
# >>> options = Options()
# >>> options.validator = "my validator option setting"
# >>> options.validator
# {}
# >>> options.get("validator")
# "my validator option setting"
# This is the most we can do to support the old interface; _getting_ attributes must return the
# new forms where appropriate, but setting will work with anything. All options can always be
# returned by `Options.get`. To initialise the attributes in `__init__`, we need to dodge the
# overriding of `__setattr__`, and upcall to `object.__setattr__`.
#
# To support copying and pickling, we also have to define how to set our state, because Python's
# normal way of trying to get attributes in the unpickle will fail.
#
# This is a terrible hack, and is purely to ensure that Terra 0.19 does not break versions of
# other Qiskit-family packages that are already deployed. It should be removed as soon as
# possible.
__slots__ = ("_fields", "validator")
# implementation of the Mapping ABC:
def __getitem__(self, key):
return self._fields[key]
def __iter__(self):
return iter(self._fields)
def __len__(self):
return len(self._fields)
# Allow modifying the options (validated)
def __setitem__(self, key, value):
self.update_options(**{key: value})
# backwards-compatibilty with Qiskit Experiments:
@property
def __dict__(self):
return self._fields
# SimpleNamespace-like access to options:
def __getattr__(self, name):
# This does not interrupt the normal lookup of things like methods or `_fields`, because
# those are successfully resolved by the normal Python lookup apparatus. If we are here,
# then lookup has failed, so we must be looking for an option. If the user has manually
# called `self.__getattr__("_fields")` then they'll get the option not the full dict, but
# that's not really our fault. `getattr(self, "_fields")` will still find the dict.
try:
return self._fields[name]
except KeyError as ex:
raise AttributeError(f"Option {name} is not defined") from ex
# setting options with the namespace interface is not validated
def __setattr__(self, key, value):
self._fields[key] = value
# custom pickling:
def __getstate__(self):
return (self._fields, self.validator)
def __setstate__(self, state):
_fields, validator = state
super().__setattr__("_fields", _fields)
super().__setattr__("validator", validator)
def __copy__(self):
"""Return a copy of the Options.
The returned option and validator values are shallow copies of the originals.
"""
out = self.__new__(type(self))
out.__setstate__((self._fields.copy(), self.validator.copy()))
return out
def __init__(self, **kwargs):
super().__setattr__("_fields", kwargs)
super().__setattr__("validator", {})
# The eldritch horrors are over, and normal service resumes below. Beware that while
# `__setattr__` is overridden, you cannot do `self.x = y` (but `self.x[key] = y` is fine). This
# should not be necessary, but if _absolutely_ required, you must do
# super().__setattr__("x", y)
# to avoid just setting a value in `_fields`.
def __repr__(self):
items = (f"{k}={v!r}" for k, v in self._fields.items())
return "{}({})".format(type(self).__name__, ", ".join(items))
def __eq__(self, other):
if isinstance(self, Options) and isinstance(other, Options):
return self._fields == other._fields
return NotImplemented
[ドキュメント] def set_validator(self, field, validator_value):
"""Set an optional validator for a field in the options
Setting a validator enables changes to an options values to be
validated for correctness when :meth:`~qiskit.providers.Options.update_options`
is called. For example if you have a numeric field like
``shots`` you can specify a bounds tuple that set an upper and lower
bound on the value such as::
options.set_validator("shots", (1, 4096))
In this case whenever the ``"shots"`` option is updated by the user
it will enforce that the value is >=1 and <=4096. A ``ValueError`` will
be raised if it's outside those bounds. If a validator is already present
for the specified field it will be silently overridden.
Args:
field (str): The field name to set the validator on
validator_value (list or tuple or type): The value to use for the
validator depending on the type indicates on how the value for
a field is enforced. If a tuple is passed in it must have a
length of two and will enforce the min and max value
(inclusive) for an integer or float value option. If it's a
list it will list the valid values for a field. If it's a
``type`` the validator will just enforce the value is of a
certain type.
Raises:
KeyError: If field is not present in the options object
ValueError: If the ``validator_value`` has an invalid value for a
given type
TypeError: If ``validator_value`` is not a valid type
"""
if field not in self._fields:
raise KeyError("Field '%s' is not present in this options object" % field)
if isinstance(validator_value, tuple):
if len(validator_value) != 2:
raise ValueError(
"A tuple validator must be of the form '(lower, upper)' "
"where lower and upper are the lower and upper bounds "
"inclusive of the numeric value"
)
elif isinstance(validator_value, list):
if len(validator_value) == 0:
raise ValueError("A list validator must have at least one entry")
elif isinstance(validator_value, type):
pass
else:
raise TypeError(
f"{type(validator_value)} is not a valid validator type, it "
"must be a tuple, list, or class/type"
)
self.validator[field] = validator_value
[ドキュメント] def update_options(self, **fields):
"""Update options with kwargs"""
for field in fields:
field_validator = self.validator.get(field, None)
if isinstance(field_validator, tuple):
if fields[field] > field_validator[1] or fields[field] < field_validator[0]:
raise ValueError(
f"Specified value for '{field}' is not a valid value, "
f"must be >={field_validator[0]} or <={field_validator[1]}"
)
elif isinstance(field_validator, list):
if fields[field] not in field_validator:
raise ValueError(
f"Specified value for {field} is not a valid choice, "
f"must be one of {field_validator}"
)
elif isinstance(field_validator, type):
if not isinstance(fields[field], field_validator):
raise TypeError(
f"Specified value for {field} is not of required type {field_validator}"
)
self._fields.update(fields)
def __str__(self):
no_validator = super().__str__()
if not self.validator:
return no_validator
else:
out_str = io.StringIO()
out_str.write(no_validator)
out_str.write("\nWhere:\n")
for field, value in self.validator.items():
if isinstance(value, tuple):
out_str.write(f"\t{field} is >= {value[0]} and <= {value[1]}\n")
elif isinstance(value, list):
out_str.write(f"\t{field} is one of {value}\n")
elif isinstance(value, type):
out_str.write(f"\t{field} is of type {value}\n")
return out_str.getvalue()