Source code for quantify_scheduler.gettables_profiled

# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
r"""
This module represents the Q-Profile quantum control electronics profiler.

Profiling of the control electronics is enabled by using the
:class:`ProfiledScheduleGettable` in place of
:class:`~.ScheduleGettable`.

.. note::

    The :class:`ProfiledScheduleGettable` is currently only tested to support Qblox hardware.
"""
import json
import os
import time

import matplotlib.pyplot as plt
import numpy as np
from qcodes.instrument.instrument import Instrument
from xarray import Dataset

from quantify_scheduler.gettables import ScheduleGettable
from quantify_scheduler.instrument_coordinator.instrument_coordinator import (
    InstrumentCoordinator,
)


[docs] def profiler(func): """ Decorator that reports the execution time of the decorated function and stores this in ``ProfiledInstrumentCoordinator.profile``. Parameters ---------- func: Callable Target function to be profiled. """ def wrap(self, *args, **kwargs): start = time.time() result = func(self, *args, **kwargs) end = time.time() if func.__name__ not in self.profile: self.profile[func.__name__] = [] self.profile[func.__name__].append(end - start) return result return wrap
[docs] class ProfiledInstrumentCoordinator(InstrumentCoordinator): """ Subclass of :class:`~.InstrumentCoordinator` that implements a profiling tool to log timing results. Time results are stored in ``ProfiledInstrumentCoordinator.profile``. :class:`ProfiledInstrumentCoordinator` is set up to be used when using :class:`ProfiledScheduleGettable`, code example: .. code-block:: python ic = InstrumentCoordinator(name="instrument_coordinator") quantum_device = QuantumDevice(name="quantum_device") quantum_device.instr_instrument_coordinator(ic.name) profiled_gettable = ProfiledScheduleGettable( quantum_device=quantum_device, schedule_function=..., schedule_kwargs=..., ) profiled_gettable.initialize() profiled_ic = ( profiled_gettable.quantum_device.instr_instrument_coordinator.get_instr() ) Parameters ---------- name: str Name of :class:`ProfiledInstrumentCoordinator` instance. parent_ic: InstrumentCoordinator Original :class:`~.InstrumentCoordinator`. """ def __init__(self, name: str, parent_ic: InstrumentCoordinator):
[docs] self.profile = {"schedule": []}
super().__init__(name, add_default_generic_icc=False)
[docs] self.parent_ic = parent_ic
[docs] self.plot = None
@profiler
[docs] def add_component( self, component, ) -> None: self.parent_ic.add_component(component)
@profiler
[docs] def prepare( self, compiled_schedule, ) -> None: self.profile["schedule"].append(compiled_schedule.get_schedule_duration()) self.parent_ic.prepare(compiled_schedule)
@profiler
[docs] def start(self): self.parent_ic.start()
@profiler
[docs] def stop(self, allow_failure=False): self.parent_ic.stop()
@profiler
[docs] def retrieve_acquisition(self) -> Dataset: return self.parent_ic.retrieve_acquisition()
@profiler
[docs] def wait_done(self, timeout_sec: int = 10): self.parent_ic.wait_done(timeout_sec)
[docs] class ProfiledScheduleGettable(ScheduleGettable): """ To be used in place of :class:`~.ScheduleGettable` to enable profiling of the compilation. Logged execution times can be read from ``self.profile``, and plotted via :func:`plot_profile`. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
[docs] self.profile = {}
[docs] self.plot = None
# Overwrite linked IC to a profiled IC
[docs] self.instr_coordinator: InstrumentCoordinator = ( # type: ignore self.quantum_device.instr_instrument_coordinator.get_instr() )
[docs] self.profiled_instr_coordinator = ProfiledInstrumentCoordinator( name="profiled_ic", parent_ic=self.instr_coordinator )
self.quantum_device.instr_instrument_coordinator( self.profiled_instr_coordinator.name ) @profiler
[docs] def _compile(self, sched): """Overwrite compile step for profiling.""" super()._compile(sched)
[docs] def close(self): """Cleanup new profiling instruments to avoid future conflicts.""" self.profile.update(self.profiled_instr_coordinator.profile) self.quantum_device.instr_instrument_coordinator(self.instr_coordinator.name) prof_ic = Instrument.find_instrument("profiled_ic") Instrument.close(prof_ic)
[docs] def log_profile( self, obj=None, path="profiling_logs", filename=None, indent: int = 4, separators=None, ): """Store profiling logs to json file.""" if not obj: obj = self.profile if not separators: separators = (",", ": ") if filename: if not os.path.exists(path): os.makedirs(path) file_path = os.path.join(path, filename) with open(file_path, "w", encoding="utf-8") as file: json.dump(obj, file, indent=indent, separators=separators) return self.profile
[docs] def plot_profile(self, path=None, filename="average_runtimes.pdf"): """Create barplot of accumulated profiling data.""" profile = self.profile time_ax = list(profile.keys()) num_keys = len(time_ax) x_pos = np.arange(num_keys) means = [np.mean(x) for x in profile.values()] error = [np.std(x) for x in profile.values()] fig, ax = plt.subplots(figsize=(9, 6)) color = ["r", "b", "c", "m", "k", "g", "y"][:num_keys] ax.bar(x_pos, means, yerr=error, color=color) ax.bar(num_keys, means[0], color=color[0]) for i in range(1, num_keys): ax.bar(num_keys, means[i], color=color[i], bottom=sum(means[:i])) time_ax.append("total") ax.set_xticks(np.append(x_pos, num_keys)) ax.set_xticklabels(time_ax) self.plot = (fig, ax) plt.ylabel("runtime [s]") plt.title("Average runtimes") file_path = os.path.join(path, filename) if path else filename fig.savefig(file_path)