Skip to content
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
24 changes: 18 additions & 6 deletions qiskit_experiments/framework/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,30 +245,33 @@ def run(
stacklevel=2,
)

if backend is not None or analysis != "default":
if backend is not None or analysis != "default" or run_options:
# Make a copy to update analysis or backend if one is provided at runtime
experiment = self.copy()
if backend:
experiment._set_backend(backend)
if isinstance(analysis, BaseAnalysis):
experiment.analysis = analysis
if run_options:
experiment.set_run_options(**run_options)
else:
experiment = self

if experiment.backend is None:
raise QiskitError("Cannot run experiment, no backend has been set.")

# Finalize experiment before executions
experiment._finalize()

# Initialize result container
experiment_data = experiment._initialize_experiment_data()

# Run options
run_opts = copy.copy(experiment.run_options)
run_opts.update_options(**run_options)
run_opts = run_opts.__dict__

# Generate and transpile circuits
transpiled_circuits = experiment._transpiled_circuits()

# Run options
run_opts = experiment.run_options.__dict__

# Run jobs
jobs = experiment._run_jobs(transpiled_circuits, **run_opts)
experiment_data.add_jobs(jobs, timeout=timeout)
Expand Down Expand Up @@ -319,6 +322,15 @@ def run_analysis(
)
return self.analysis.run(experiment_data, replace_results=replace_results, **options)

def _finalize(self):
"""Finalize experiment object before running jobs.

Subclasses can override this method to set any final option
values derived from other options or attributes of the
experiment before `_run` is called.
"""
pass

def _run_jobs(self, circuits: List[QuantumCircuit], **run_options) -> List[BaseJob]:
"""Run circuits on backend as 1 or more jobs."""
# Run experiment jobs
Expand Down
44 changes: 39 additions & 5 deletions qiskit_experiments/framework/composite/composite_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,50 @@ def copy(self) -> "BaseExperiment":
ret.analysis._analyses[i] = ret._experiments[i].analysis
return ret

def set_run_options(self, **fields):
super().set_run_options(**fields)
for subexp in self._experiments:
subexp.set_run_options(**fields)

def _set_backend(self, backend):
super()._set_backend(backend)
for subexp in self._experiments:
subexp._set_backend(backend)

def _finalize(self):
# NOTE: When CompositeAnalysis is updated to support level-1
# measurements this method should be updated to validate that all
# sub-experiments have the same meas level and meas return types,
# and update the composite experiment run option to that value.

for i, subexp in enumerate(self._experiments):
# Validate set and default run options in component experiment
# against and component experiment run options and raise a
# warning if any are different and will be overridden
overridden_keys = []
sub_vals = []
comp_vals = []
for key, sub_val in subexp.run_options.__dict__.items():
comp_val = getattr(self.run_options, key, None)
if sub_val != comp_val:
overridden_keys.append(key)
sub_vals.append(sub_val)
comp_vals.append(comp_val)

if overridden_keys:
warnings.warn(
f"Component {i} {subexp.experiment_type} experiment run options"
f" {overridden_keys} values {sub_vals} will be overridden with"
f" {self.experiment_type} values {comp_vals}.",
UserWarning,
)
# Update sub-experiment options with actual run option values so
# they can be used by that sub experiments _finalize method.
subexp.set_run_options(**dict(zip(overridden_keys, comp_vals)))

# Call sub-experiments finalize method
subexp._finalize()

def _initialize_experiment_data(self):
"""Initialize the return data container for the experiment run"""
experiment_data = ExperimentData(experiment=self)
Expand All @@ -125,9 +164,4 @@ def _add_job_metadata(self, metadata, jobs, **run_options):
for sub_metadata, sub_exp in zip(
metadata["component_metadata"], self.component_experiment()
):
# Run and transpile options are always overridden
if sub_exp.run_options != sub_exp._default_run_options():
warnings.warn(
"Sub-experiment run options" " are overridden by composite experiment options."
)
sub_exp._add_job_metadata(sub_metadata, jobs, **run_options)
12 changes: 12 additions & 0 deletions releasenotes/notes/exp-finalize-b7ca0a139ad5f872.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
features:
- |
Adds a :meth:`.BaseExperiment._finalize` method to :class:`.BaseExperiment`
which is after configuring any runtime options, backend, or analysis
classes but before generation and execution of experiment
circuits during :class:`.BaseExperiment.run`.

This method is intended to be overridden in experiment subclasses if they
need to configure any analysis or runtime options based on a combination
of properties of the experiment, for example some combination of backend,
experiment and run options.
15 changes: 6 additions & 9 deletions test/test_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,14 @@ def test_parallel_options(self):

par_exp = ParallelExperiment([exp0, exp2])

with self.assertWarnsRegex(
Warning,
"Sub-experiment run options" " are overridden by composite experiment options.",
):
self.assertEqual(par_exp.experiment_options, Options())
self.assertEqual(par_exp.run_options, Options(meas_level=2))
self.assertEqual(par_exp.transpile_options, Options(optimization_level=0))
self.assertEqual(par_exp.analysis.options, Options())
self.assertEqual(par_exp.experiment_options, Options())
self.assertEqual(par_exp.run_options, Options(meas_level=2))
self.assertEqual(par_exp.transpile_options, Options(optimization_level=0))
self.assertEqual(par_exp.analysis.options, Options())

with self.assertWarns(UserWarning):
expdata = par_exp.run(FakeBackend())
self.assertExperimentDone(expdata)
self.assertExperimentDone(expdata)

def test_experiment_config(self):
"""Test converting to and from config works"""
Expand Down