Skip to content

Commit a55cf51

Browse files
committed
Simply analysis options
* Remove `set_options` from `BaseAnalysis` * Add `run_analysis` method to `BaseExperiment` that uses experiment `analysis_options`. * Rename `backend_options` to `run_options`
1 parent b26d579 commit a55cf51

File tree

5 files changed

+92
-74
lines changed

5 files changed

+92
-74
lines changed

qiskit_experiments/base_analysis.py

Lines changed: 20 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,56 +24,34 @@
2424

2525

2626
class BaseAnalysis(ABC):
27-
"""Base Analysis class for analyzing Experiment data."""
27+
"""Base Analysis class for analyzing Experiment data.
28+
29+
When designing Analysis subclasses default values for any kwarg
30+
analysis options of the `run` method should be set by overriding
31+
the `_default_options` class method. When calling `run` these
32+
default values will combined with all other option kwargs in the
33+
run method and passed to the `_run_analysis` function.
34+
"""
2835

2936
# Expected experiment data container for analysis
3037
__experiment_data__ = ExperimentData
3138

32-
def __init__(self, **options):
33-
"""Initialize a base analysis class
34-
35-
Args:
36-
options: kwarg options for analysis.
37-
"""
38-
self._options = self._default_options()
39-
self.set_options(**options)
40-
4139
@classmethod
4240
def _default_options(cls) -> Options:
4341
return Options()
4442

45-
def set_options(self, **fields):
46-
"""Set the analysis options.
47-
48-
Args:
49-
fields: The fields to update the options
50-
"""
51-
self._options.update_options(**fields)
52-
53-
@property
54-
def options(self) -> Options:
55-
"""Return the analysis options.
56-
57-
The options of an analysis class are used to provide kwarg values for
58-
the :meth:`run` method.
59-
"""
60-
return self._options
61-
62-
def run(self,
63-
experiment_data: ExperimentData,
64-
save: bool = True,
65-
return_figures: bool = False,
66-
**options):
67-
"""Run analysis and update stored ExperimentData with analysis result.
43+
def run(self, experiment_data, save=True, return_figures=False, **options):
44+
"""Run analysis and update ExperimentData with analysis result.
6845
6946
Args:
70-
experiment_data: the experiment data to analyze.
71-
save: if True save analysis results and figures to the
72-
:class:`ExperimentData`.
73-
return_figures: if true return a pair of ``(analysis_results, figures)``,
74-
otherwise return only analysis_results.
75-
options: additional analysis options. Any values set here will
76-
override the value from :meth:`options` for the current run.
47+
experiment_data (ExperimentData): the experiment data to analyze.
48+
save (bool): if True save analysis results and figures to the
49+
:class:`ExperimentData`.
50+
return_figures (bool): if true return a pair of
51+
``(analysis_results, figures)``,
52+
otherwise return only analysis_results.
53+
options: additional analysis options. See class documentation for
54+
supported options.
7755
7856
Returns:
7957
AnalysisResult: the output of the analysis that produces a
@@ -93,9 +71,8 @@ def run(self,
9371
f"Invalid experiment data type, expected {self.__experiment_data__.__name__}"
9472
f" but received {type(experiment_data).__name__}"
9573
)
96-
97-
# Get runtime analysis options
98-
analysis_options = copy.copy(self.options)
74+
# Get analysis options
75+
analysis_options = self._default_options()
9976
analysis_options.update_options(**options)
10077
analysis_options = analysis_options.__dict__
10178

qiskit_experiments/base_experiment.py

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,13 @@ class BaseExperiment(ABC):
4848
# ExperimentData class for experiment
4949
__experiment_data__ = ExperimentData
5050

51-
def __init__(self, qubits: Iterable[int], experiment_type: Optional[str] = None, **options):
51+
def __init__(self, qubits: Iterable[int], experiment_type: Optional[str] = None):
5252
"""Initialize the experiment object.
5353
5454
Args:
5555
qubits: the number of qubits or list of physical qubits for
5656
the experiment.
5757
experiment_type: Optional, the experiment type string.
58-
options: kwarg options for experiment circuits.
5958
6059
Raises:
6160
QiskitError: if qubits is a list and contains duplicates.
@@ -76,11 +75,8 @@ def __init__(self, qubits: Iterable[int], experiment_type: Optional[str] = None,
7675

7776
# Experiment options
7877
self._options = self._default_options()
79-
self.set_options(**options)
80-
81-
# Execution and analysis options
8278
self._transpile_options = self._default_transpile_options()
83-
self._backend_options = self._default_backend_options()
79+
self._run_options = self._default_run_options()
8480
self._analysis_options = self._default_analysis_options()
8581

8682
# Set initial layout from qubits
@@ -91,7 +87,7 @@ def run(
9187
backend: Backend,
9288
analysis: bool = True,
9389
experiment_data: Optional[ExperimentData] = None,
94-
**kwargs,
90+
**run_options,
9591
) -> ExperimentData:
9692
"""Run an experiment and perform analysis.
9793
@@ -101,7 +97,7 @@ def run(
10197
experiment_data: Optional, add results to existing
10298
experiment data. If None a new ExperimentData object will be
10399
returned.
104-
kwargs: runtime keyword arguments for backend.run.
100+
run_options: backend runtime options used for circuit execution.
105101
106102
Returns:
107103
The experiment data object.
@@ -116,27 +112,61 @@ def run(
116112
)
117113

118114
# Run circuits on backend
119-
run_options = copy.copy(self.backend_options)
120-
run_options.update_options(**kwargs)
121-
run_options = run_options.__dict__
115+
run_opts = copy.copy(self.run_options)
116+
run_opts.update_options(**run_options)
117+
run_opts = run_opts.__dict__
122118

123119
if isinstance(backend, LegacyBackend):
124-
qobj = assemble(circuits, backend=backend, **run_options)
120+
qobj = assemble(circuits, backend=backend, **run_opts)
125121
job = backend.run(qobj)
126122
else:
127-
job = backend.run(circuits, **run_options)
123+
job = backend.run(circuits, **run_opts)
128124

129125
# Add Job to ExperimentData
130126
experiment_data.add_data(job)
131127

132128
# Queue analysis of data for when job is finished
133129
if analysis and self.__analysis_class__ is not None:
134-
# pylint: disable = not-callable
135-
self.analysis(**self.analysis_options.__dict__).run(experiment_data)
130+
self.run_analysis(experiment_data)
136131

137132
# Return the ExperimentData future
138133
return experiment_data
139134

135+
def run_analysis(
136+
self, experiment_data, save=True, return_figures=False, **options
137+
) -> ExperimentData:
138+
"""Run analysis and update ExperimentData with analysis result.
139+
140+
Args:
141+
experiment_data (ExperimentData): the experiment data to analyze.
142+
save (bool): if True save analysis results and figures to the
143+
:class:`ExperimentData`.
144+
return_figures (bool): if true return a pair of
145+
``(analysis_results, figures)``,
146+
otherwise return only analysis_results.
147+
options: additional analysis options. Any values set here will
148+
override the value from :meth:`analysis_options`
149+
for the current run.
150+
151+
Returns:
152+
The updated experiment data containing the analysis results and figures.
153+
154+
Raises:
155+
QiskitError: if experiment_data container is not valid for analysis.
156+
"""
157+
if self.__analysis_class__ is None:
158+
raise QiskitError(f"Experiment {self._type} does not have a default Analysis class")
159+
160+
# Get analysis options
161+
analysis_options = copy.copy(self.analysis_options)
162+
analysis_options.update_options(**options)
163+
analysis_options = analysis_options.__dict__
164+
165+
# Run analysis
166+
analysis = self.__analysis_class__()
167+
analysis.run(experiment_data, **analysis_options)
168+
return experiment_data
169+
140170
@property
141171
def num_qubits(self) -> int:
142172
"""Return the number of qubits for this experiment."""
@@ -148,12 +178,12 @@ def physical_qubits(self) -> Tuple[int]:
148178
return self._physical_qubits
149179

150180
@classmethod
151-
def analysis(cls, **analysis_options) -> "BaseAnalysis":
181+
def analysis(cls, **kwargs) -> "BaseAnalysis":
152182
"""Return the default Analysis class for the experiment."""
153183
if cls.__analysis_class__ is None:
154184
raise QiskitError(f"Experiment {cls.__name__} does not have a default Analysis class")
155185
# pylint: disable = not-callable
156-
return cls.__analysis_class__(**analysis_options)
186+
return cls.__analysis_class__(**kwargs)
157187

158188
@abstractmethod
159189
def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]:
@@ -248,22 +278,22 @@ def set_transpile_options(self, **fields):
248278
self._transpile_options.update_options(**fields)
249279

250280
@classmethod
251-
def _default_backend_options(cls) -> Options:
252-
"""Default backend options for running experiment"""
281+
def _default_run_options(cls) -> Options:
282+
"""Default options values for the experiment :meth:`run` method."""
253283
return Options()
254284

255285
@property
256-
def backend_options(self) -> Options:
257-
"""Return the backend options for the :meth:`run` method."""
258-
return self._backend_options
286+
def run_options(self) -> Options:
287+
"""Return options values for the experiment :meth:`run` method."""
288+
return self._run_options
259289

260-
def set_backend_options(self, **fields):
261-
"""Set the backend options for the :meth:`run` method.
290+
def set_run_options(self, **fields):
291+
"""Set options values for the experiment :meth:`run` method.
262292
263293
Args:
264294
fields: The fields to update the options
265295
"""
266-
self._backend_options.update_options(**fields)
296+
self._run_options.update_options(**fields)
267297

268298
@classmethod
269299
def _default_analysis_options(cls) -> Options:
@@ -273,7 +303,7 @@ def _default_analysis_options(cls) -> Options:
273303
# from the Analysis subclass `_default_options` values.
274304
if cls.__analysis_class__:
275305
return cls.__analysis_class__._default_options()
276-
return None
306+
return Options()
277307

278308
@property
279309
def analysis_options(self) -> Options:

qiskit_experiments/characterization/t1_experiment.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,12 @@ def __init__(
230230
"""
231231
if len(delays) < 3:
232232
raise ValueError("T1 experiment: number of delays must be at least 3")
233-
super().__init__([qubit], delays=delays, unit=unit)
233+
234+
# Initialize base experiment
235+
super().__init__([qubit])
236+
237+
# Set experiment options
238+
self.set_options(delays=delays, unit=unit)
234239

235240
def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]:
236241
"""

qiskit_experiments/composite/composite_analysis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def _run_analysis(self, experiment_data: CompositeExperimentData, **options):
4747
for expr, expr_data in zip(
4848
experiment_data._experiment._experiments, experiment_data._composite_expdata
4949
):
50-
expr.analysis(**expr.analysis_options.__dict__).run(expr_data, **options)
50+
expr.run_analysis(expr_data, **options)
5151

5252
# Add sub-experiment metadata as result of batch experiment
5353
# Note: if Analysis results had ID's these should be included here

qiskit_experiments/randomized_benchmarking/rb_experiment.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
class RBExperiment(BaseExperiment):
3030
"""RB Experiment class.
31-
31+
3232
Experiment Options:
3333
lengths: A list of RB sequences lengths.
3434
num_samples: number of samples to generate for each sequence length.
@@ -59,12 +59,18 @@ def __init__(
5959
sequences are constructed by appending additional
6060
Clifford samples to shorter sequences.
6161
"""
62+
# Initialize base experiment
63+
super().__init__(qubits)
64+
65+
# Set configurable options
66+
self.set_options(lengths=list(lengths), num_samples=num_samples)
67+
68+
# Set fixed options
69+
self._full_sampling = full_sampling
6270
if not isinstance(seed, Generator):
6371
self._rng = default_rng(seed=seed)
6472
else:
6573
self._rng = seed
66-
self._full_sampling = full_sampling
67-
super().__init__(qubits, lenghs=list(lengths), num_samples=num_samples)
6874

6975
@classmethod
7076
def _default_options(cls):

0 commit comments

Comments
 (0)