-
Notifications
You must be signed in to change notification settings - Fork 131
Description
Suggested feature
Performance of the composite analysis is of vital importance to support 100+ qubit calibration with Qiskit Experiments. As we have previously investigated, the most time consuming part of analysis is the figure generation, but this overhead can be easily alleviated by selectively generating the figures, e.g. generating figure for bad quality results.
Apart from the figure generation, we can further reduce computational time cost by introducing better fitting subroutine #1192. Lastly (in this proposal), we can completely remove nested experiment data container structure for more speedup.
%load_ext snakeviz
import numpy as np
from qiskit_experiments.framework import ParallelExperiment
from qiskit_experiments.library import T1
from qiskit.providers.fake_provider import FakePerth
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel
noise_model = NoiseModel.from_backend(
FakePerth(), thermal_relaxation=True, gate_error=False, readout_error=False
)
backend = AerSimulator.from_backend(FakePerth(), noise_model=noise_model)
delays = np.linspace(0, 500e-6, 20)
parallels = []
for q in range(backend.configuration().num_qubits):
exp = T1((q,), delays=delays)
exp.analysis.set_options(plot=False)
parallels.append(exp)
exp = ParallelExperiment(parallels, flatten_results=True, backend=backend)
exp_data = exp.run(analysis=None).block_for_results()
%snakeviz exp._analysis._run_analysis(exp_data)
Above benchmark is a typical pattern in system maintenance where we run same kind of experiment in parallel (Figure generation is disabled to focus on the rest of steps). In this situation, CompositeAnalysis
does
When flatten_results=False
- Initialize sub containers
- Marginalize count data and add to sub containers
- Run component analysis on corresponding sub container
When flatten_results=True
- Initialize sub containers
- Marginalize count data and add to sub containers
- Run component analysis on corresponding sub container
- Extract results and figures from sub container and add them to the outermost container
- Discard sub containers
As you can see, flatten_results
option is just a difference of how to deliver the outcomes, and sub containers are initialized anyways. According to the benchmark, sub container initialization consumes 30% of analysis time in total (wait block is mainly time for fitting, and this is not analyzed with this profiler because the process is in another thread).
Proposal
In #1133 , we support table representation of analysis results. So far nested container structure might be preferred to efficiently analyze the results. In above parallel experiment example, if we want T1 value of Qubit3,
print(exp_data.child_data(3).analysis_results("T1").value)
With table, user can get nice html overview of all analysis results.
res = exp_data.analysis_results("T1")
This makes me think we no longer have motivation to keep complicated nested data structure. Given we agree with this, we don't need to make sub containers at all. Note that we still need to marginalize the count data, but this does NOT need to be an expensive ExperimentData
container.
With this modification, BaseAnalysis
would look like
def run(experiment_data: ExperimentData, ...) -> ExperimentData:
def run_callback(exp_data):
results = self._process_data(exp_data.data())
analysis_results, figures = self._run_analysis(results)
...
experiment_data.add_analysis_callback(run_callback)
return experiment_data
@abstractmethod
def _process_data(data: List[Dict]) -> ExperimentResults: # Can be replaced by primitive
@abstractmethod
def _run_analysis(results: ExperimentResults) -> Tuple[List[AnalysisResultData], List[Figure]]:
and CompositeAnalysis
would become
@abstractmethod
def _run_analysis(results: ExperimentResults) -> Tuple[List[AnalysisResultData], List[Figure]]:
for component_analysis, component_results in zip(self._analyses, self._marginalize(results)):
component_analysis_results, component_figures = component_analysis._run_analysis(component_results)
...
The CompositeAnalysis._margianlize
is a function that consumes a composite ExperimentResults
and generates Itertor[ExperimentResults]
for each component data. The marginal operation is implemented with Rust, so this subroutine must be performant.
Note that _run_analysis
is an abstract method and this is API break for existing (including community) experiment libraries. However, we can safely introduce deprecation warning with
try:
self._run_analysis(results)
except AttributeError:
warnings.warn(..., DeprecationWarning)
self._run_analysis(experiment_data)
because the class must access experiment_data.data()
to perform analysis (assuming ExperimentResults
doesn't have this attribute).