-
Notifications
You must be signed in to change notification settings - Fork 134
Add ExperimentConfig dataclass #469
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
Merged
chriseclectic
merged 5 commits into
qiskit-community:main
from
chriseclectic:exp-config
Oct 28, 2021
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
79ffd04
Add ExperimentConfig dataclass
chriseclectic 7cd5a18
Add ExperimentConfig tests
chriseclectic 7f808f8
Update config to only store custom opts
chriseclectic 37ff4e9
Update error msg
chriseclectic 420178b
Fix class docs from base class __new__
chriseclectic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,17 +15,76 @@ | |
|
|
||
| from abc import ABC, abstractmethod | ||
| import copy | ||
| import inspect | ||
| import dataclasses | ||
| from functools import wraps | ||
| from collections import OrderedDict | ||
| from numbers import Integral | ||
| from typing import Sequence, Optional, Tuple, List, Dict, Union | ||
| from typing import Sequence, Optional, Tuple, List, Dict, Union, Any | ||
|
|
||
| from qiskit import transpile, assemble, QuantumCircuit | ||
| from qiskit.providers import BaseJob | ||
| from qiskit.providers.backend import Backend | ||
| from qiskit.providers import Backend, BaseBackend | ||
| from qiskit.providers.basebackend import BaseBackend as LegacyBackend | ||
| from qiskit.exceptions import QiskitError | ||
| from qiskit.qobj.utils import MeasLevel | ||
| from qiskit_experiments.framework import Options | ||
| from qiskit.providers.options import Options | ||
| from qiskit_experiments.framework.experiment_data import ExperimentData | ||
| from qiskit_experiments.version import __version__ | ||
|
|
||
|
|
||
| @dataclasses.dataclass(frozen=True) | ||
| class ExperimentConfig: | ||
| """Store configuration settings for an Experiment | ||
|
|
||
| This stores the current configuration of a | ||
| :class:~qiskit_experiments.framework.BaseExperiment` and | ||
| can be used to reconstruct the experiment using either the | ||
| :meth:`experiment` property if the experiment class type is | ||
| currently stored, or the | ||
| :meth:~qiskit_experiments.framework.BaseExperiment.from_config` | ||
| class method of the appropriate experiment. | ||
| """ | ||
|
|
||
| cls: type = None | ||
| args: Tuple[Any] = dataclasses.field(default_factory=tuple) | ||
| kwargs: Dict[str, Any] = dataclasses.field(default_factory=dict) | ||
| experiment_options: Dict[str, Any] = dataclasses.field(default_factory=dict) | ||
| transpile_options: Dict[str, Any] = dataclasses.field(default_factory=dict) | ||
| run_options: Dict[str, Any] = dataclasses.field(default_factory=dict) | ||
|
nkanazawa1989 marked this conversation as resolved.
|
||
| version: str = __version__ | ||
|
|
||
| @property | ||
| def experiment(self) -> "BaseExperiment": | ||
| """Return the experiment constructed from this config. | ||
|
|
||
| Returns: | ||
| The experiment reconstructed from the config. | ||
|
|
||
| Raises: | ||
| QiskitError: if the experiment class is not stored, | ||
| was not successful deserialized, or reconstruction | ||
| of the experiment fails. | ||
| """ | ||
| cls = self.cls | ||
| if cls is None: | ||
| raise QiskitError("No experiment class in experiment config") | ||
| if isinstance(cls, dict): | ||
|
nkanazawa1989 marked this conversation as resolved.
|
||
| raise QiskitError( | ||
| "Unable to load experiment class. Try manually loading " | ||
| "experiment using `Experiment.from_config(config)` instead." | ||
| ) | ||
| try: | ||
| return cls.from_config(self) | ||
| except Exception as ex: | ||
| msg = "Unable to construct experiments from config." | ||
| if cls.version != __version__: | ||
| msg += ( | ||
| f" Note that config version ({cls.version}) differs from the current" | ||
| f" qiskit-experiments version ({__version__}). You could try" | ||
| " installing a compatible qiskit-experiments version." | ||
| ) | ||
| raise QiskitError("{}\nError Message:\n{}".format(msg, str(ex))) from ex | ||
|
|
||
|
|
||
| class BaseExperiment(ABC): | ||
|
|
@@ -68,7 +127,7 @@ def __init__( | |
|
|
||
| # Backend | ||
| self._backend = None | ||
| if backend is not None: | ||
| if isinstance(backend, (Backend, BaseBackend)): | ||
| self._set_backend(backend) | ||
|
|
||
| # Circuit parameters | ||
|
|
@@ -87,6 +146,45 @@ def __init__( | |
| self._run_options = self._default_run_options() | ||
| self._analysis_options = self._default_analysis_options() | ||
|
|
||
| # Store keys of non-default options | ||
| self._set_experiment_options = set() | ||
| self._set_transpile_options = set() | ||
| self._set_run_options = set() | ||
| self._set_analysis_options = set() | ||
|
|
||
| def __new__(cls, *args, **kwargs): | ||
|
nkanazawa1989 marked this conversation as resolved.
|
||
| """Store init args and kwargs for subclass __init__ methods""" | ||
| # This method automatically stores all arg and kwargs from subclass | ||
|
nkanazawa1989 marked this conversation as resolved.
|
||
| # init methods for use in converting an experiment to config | ||
|
|
||
| # Get all non-self init args and kwarg names for subclass | ||
| spec = inspect.getfullargspec(cls.__init__) | ||
| init_arg_names = spec.args[1:] | ||
| num_init_kwargs = len(spec.defaults) if spec.defaults else 0 | ||
| num_init_args = len(init_arg_names) - num_init_kwargs | ||
|
|
||
| # Convert passed values for args and kwargs into an ordered dict | ||
| # This will sort args passed as kwargs and kwargs passed as | ||
| # positional args in the function call | ||
| num_call_args = len(args) | ||
| ord_args = OrderedDict() | ||
| ord_kwargs = OrderedDict() | ||
| for i, argname in enumerate(init_arg_names): | ||
| if i < num_init_args: | ||
| update = ord_args | ||
| else: | ||
| update = ord_kwargs | ||
| if i < num_call_args: | ||
| update[argname] = args[i] | ||
| elif argname in kwargs: | ||
| update[argname] = kwargs[argname] | ||
|
|
||
| # pylint: disable = attribute-defined-outside-init | ||
| instance = super(BaseExperiment, cls).__new__(cls) | ||
| instance.__init_args__ = ord_args | ||
| instance.__init_kwargs__ = ord_kwargs | ||
| return instance | ||
|
|
||
| @property | ||
| def experiment_type(self) -> str: | ||
| """Return experiment type.""" | ||
|
|
@@ -132,6 +230,42 @@ def copy(self) -> "BaseExperiment": | |
| ret._analysis_options = copy.copy(self._analysis_options) | ||
| return ret | ||
|
|
||
| @property | ||
| def config(self) -> ExperimentConfig: | ||
| """Return the config dataclass for this experiment""" | ||
| args = tuple(getattr(self, "__init_args__", OrderedDict()).values()) | ||
| kwargs = dict(getattr(self, "__init_kwargs__", OrderedDict())) | ||
| # Only store non-default valued options | ||
| experiment_options = dict( | ||
| (key, getattr(self._experiment_options, key)) for key in self._set_experiment_options | ||
| ) | ||
| transpile_options = dict( | ||
| (key, getattr(self._transpile_options, key)) for key in self._set_transpile_options | ||
| ) | ||
| run_options = dict((key, getattr(self._run_options, key)) for key in self._set_run_options) | ||
| return ExperimentConfig( | ||
| cls=type(self), | ||
| args=args, | ||
| kwargs=kwargs, | ||
| experiment_options=experiment_options, | ||
| transpile_options=transpile_options, | ||
| run_options=run_options, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def from_config(cls, config: Union[ExperimentConfig, Dict]) -> "BaseExperiment": | ||
| """Initialize an experiment from experiment config""" | ||
| if isinstance(config, dict): | ||
| config = ExperimentConfig(**dict) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @chriseclectic This probably was supposed to be |
||
| ret = cls(*config.args, **config.kwargs) | ||
| if config.experiment_options: | ||
| ret.set_experiment_options(**config.experiment_options) | ||
| if config.transpile_options: | ||
| ret.set_transpile_options(**config.transpile_options) | ||
| if config.run_options: | ||
| ret.set_run_options(**config.run_options) | ||
| return ret | ||
|
nkanazawa1989 marked this conversation as resolved.
|
||
|
|
||
| def run( | ||
| self, | ||
| backend: Optional[Backend] = None, | ||
|
|
@@ -295,6 +429,7 @@ def set_experiment_options(self, **fields): | |
| f"Options field {field} is not valid for {type(self).__name__}" | ||
| ) | ||
| self._experiment_options.update_options(**fields) | ||
| self._set_experiment_options = self._set_experiment_options.union(fields) | ||
|
|
||
| @classmethod | ||
| def _default_transpile_options(cls) -> Options: | ||
|
|
@@ -324,6 +459,7 @@ def set_transpile_options(self, **fields): | |
| " as it is determined by the experiment physical qubits." | ||
| ) | ||
| self._transpile_options.update_options(**fields) | ||
| self._set_transpile_options = self._set_transpile_options.union(fields) | ||
|
|
||
| @classmethod | ||
| def _default_run_options(cls) -> Options: | ||
|
|
@@ -342,6 +478,7 @@ def set_run_options(self, **fields): | |
| fields: The fields to update the options | ||
| """ | ||
| self._run_options.update_options(**fields) | ||
| self._set_run_options = self._set_run_options.union(fields) | ||
|
|
||
| @classmethod | ||
| def _default_analysis_options(cls) -> Options: | ||
|
|
@@ -365,6 +502,7 @@ def set_analysis_options(self, **fields): | |
| fields: The fields to update the options | ||
| """ | ||
| self._analysis_options.update_options(**fields) | ||
| self._set_analysis_options = self._set_analysis_options.union(fields) | ||
|
|
||
| def _postprocess_transpiled_circuits(self, circuits: List[QuantumCircuit], **run_options): | ||
| """Additional post-processing of transpiled circuits before running on backend""" | ||
|
|
@@ -412,3 +550,20 @@ def _add_job_metadata(self, experiment_data: ExperimentData, jobs: BaseJob, **ru | |
| "run_options": copy.copy(run_options), | ||
| } | ||
| experiment_data._metadata["job_metadata"].append(metadata) | ||
|
|
||
|
|
||
| def fix_class_docs(wrapped_cls): | ||
| """Experiment class decorator to fix class doc formatting. | ||
|
|
||
| This fixes the BaseExperiment subclass documentation so that | ||
| the correct init arg and kwargs are shown for the class documentation, | ||
| rather than the generic args of the BaseExperiment.__new__ method. | ||
| """ | ||
|
|
||
| @wraps(wrapped_cls.__init__, assigned=("__annotations__",)) | ||
| def __new__(cls, *args, **kwargs): | ||
| return super(wrapped_cls, cls).__new__(cls, *args, **kwargs) | ||
|
|
||
| wrapped_cls.__new__ = __new__ | ||
|
|
||
| return wrapped_cls | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.