Commit 4394545c authored by Victor Negîrneac's avatar Victor Negîrneac
Browse files

Merge branch '131-refactor-controlstack' into 'develop'

refactor(controlstack): Make use of InstrumentRefParameters

Closes #131

See merge request quantify-os/quantify-scheduler!144
parents b35a1b7d 5f075bb3
Loading
Loading
Loading
Loading
Loading
+31 −3
Original line number Diff line number Diff line
@@ -5,13 +5,41 @@ from __future__ import annotations

from abc import abstractmethod
from typing import Any
from qcodes.instrument import parameter
from qcodes.instrument import base
from qcodes.utils import validators

from qcodes.instrument.base import AbstractInstrument


class AbstractControlStackComponent(AbstractInstrument):
class ControlStackComponentBase(base.Instrument):
    """The ControlStack component abstract interface."""

    def __init__(
        self,
        instrument: base.InstrumentBase,
        **kwargs,
    ) -> None:
        """Instantiates the ControlStackComponentBase base class."""
        super().__init__(f"cs_{instrument.name}", **kwargs)

        self.add_parameter(
            "instrument_ref",
            initial_value=instrument.name,
            parameter_class=parameter.InstrumentRefParameter,
            docstring="A reference of a instrument associated to this ControlStack "
            "component.",
            vals=validators.MultiType(validators.Strings(), validators.Enum(None)),
        )

    @property
    def instrument(self) -> base.InstrumentBase:
        """Returns the instrument referenced by `instrument_ref`."""
        return self.instrument_ref.get_instr()

    @instrument.setter
    def instrument(self, instrument: base.InstrumentBase) -> None:
        """Sets a new Instrument as reference."""
        self.instrument_ref(instrument.name)

    @property
    @abstractmethod
    def is_running(self) -> bool:
+86 −59
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
"""Module containing Qblox ControlStack Components."""
from __future__ import annotations

from typing import Any, Callable, Dict, Tuple
from typing import Any, Callable, Dict, Optional, Tuple

from dataclasses import dataclass
import logging
@@ -14,6 +14,7 @@ from typing_extensions import Literal
import numpy as np
from pulsar_qcm import pulsar_qcm
from pulsar_qrm import pulsar_qrm
from qcodes.instrument.base import Instrument
from quantify_scheduler.controlstack.components import base
from quantify_scheduler.helpers.waveforms import modulate_waveform
from quantify_scheduler.backends.types.qblox import PulsarSettings, SequencerSettings
@@ -24,10 +25,21 @@ from quantify_scheduler.backends.qblox.constants import (

logger = logging.getLogger(__name__)


class PulsarControlStackComponent(base.ControlStackComponentBase):
    """Qblox Pulsar ControlStack component base class."""

    def __init__(self, instrument: Instrument, **kwargs) -> None:
        """Create a new instance of PulsarControlStackComponent base class."""
        super().__init__(instrument, **kwargs)

    @property
    def is_running(self) -> bool:
        raise NotImplementedError()


# pylint: disable=too-many-ancestors
class PulsarQCMComponent(
    pulsar_qcm.pulsar_qcm_qcodes, base.AbstractControlStackComponent
):
class PulsarQCMComponent(PulsarControlStackComponent):
    """
    Pulsar QCM specific control stack component.
    """
@@ -35,17 +47,14 @@ class PulsarQCMComponent(
    number_of_sequencers: int = NUMBER_OF_SEQUENCERS_QCM
    """Specifies the amount of sequencers available to this QCM."""

    def __init__(self, name, host, port=5025, debug=0) -> None:
        if host == "dummy":
            debug = 1
            transport_inst = pulsar_qcm.pulsar_dummy_transport(
                pulsar_qcm.pulsar_qcm_ifc._get_sequencer_cfg_format()
            )
            self._dummy_instr = True
        else:
            transport_inst = pulsar_qcm.ip_transport(host=host, port=port)
            self._dummy_instr = False
        super().__init__(name, transport_inst, debug=debug)
    def __init__(self, instrument: pulsar_qcm.pulsar_qcm_qcodes, **kwargs) -> None:
        """Create a new instance of PulsarQCMComponent."""
        assert isinstance(instrument, pulsar_qcm.pulsar_qcm_qcodes)
        super().__init__(instrument, **kwargs)

    @property
    def instrument(self) -> pulsar_qcm.pulsar_qcm_qcodes:
        return super().instrument

    @property
    def is_running(self) -> bool:
@@ -101,21 +110,23 @@ class PulsarQCMComponent(
                    seq_idx=seq_idx, settings=seq_settings
                )

            self.set(f"sequencer{seq_idx}_waveforms_and_program", seq_cfg["seq_fn"])
            self.instrument.set(
                f"sequencer{seq_idx}_waveforms_and_program", seq_cfg["seq_fn"]
            )

            self.arm_sequencer(sequencer=seq_idx)
            self.instrument.arm_sequencer(sequencer=seq_idx)

    def start(self) -> None:
        """
        Starts execution of the schedule.
        """
        self.start_sequencer()
        self.instrument.start_sequencer()

    def stop(self) -> None:
        """
        Stops all execution.
        """
        self.stop_sequencer()
        self.instrument.stop_sequencer()

    def _configure_global_settings(self, settings: PulsarSettings):
        """
@@ -126,7 +137,7 @@ class PulsarQCMComponent(
        settings
            The settings to configure it to.
        """
        self.set("reference_source", settings.ref)
        self.instrument.set("reference_source", settings.ref)

    def _configure_sequencer_settings(self, seq_idx: int, settings: SequencerSettings):
        """
@@ -139,42 +150,42 @@ class PulsarQCMComponent(
        settings
            The settings to configure it to.
        """
        self.set(f"sequencer{seq_idx}_sync_en", settings.sync_en)
        self.set(f"sequencer{seq_idx}_offset_awg_path0", settings.awg_offset_path_0)
        self.set(f"sequencer{seq_idx}_offset_awg_path1", settings.awg_offset_path_1)
        self.instrument.set(f"sequencer{seq_idx}_sync_en", settings.sync_en)
        self.instrument.set(
            f"sequencer{seq_idx}_offset_awg_path0", settings.awg_offset_path_0
        )
        self.instrument.set(
            f"sequencer{seq_idx}_offset_awg_path1", settings.awg_offset_path_1
        )

        nco_en: bool = settings.nco_en
        self.set(f"sequencer{seq_idx}_mod_en_awg", nco_en)
        self.instrument.set(f"sequencer{seq_idx}_mod_en_awg", nco_en)
        if nco_en:
            self.set(f"sequencer{seq_idx}_nco_freq", settings.modulation_freq)
            self.instrument.set(
                f"sequencer{seq_idx}_nco_freq", settings.modulation_freq
            )

    def wait_done(self, timeout_sec: int = 10) -> None:
        pass


# pylint: disable=too-many-ancestors
class PulsarQRMComponent(
    pulsar_qrm.pulsar_qrm_qcodes, base.AbstractControlStackComponent
):
class PulsarQRMComponent(PulsarControlStackComponent):
    """
    Pulsar QRM specific stack component.
    """

    number_of_sequencers: int = NUMBER_OF_SEQUENCERS_QRM

    def __init__(self, name, host, port=5025, debug=0) -> None:
        if host == "dummy":
            debug = 1
            transport_inst = pulsar_qcm.pulsar_dummy_transport(
                pulsar_qrm.pulsar_qrm_ifc._get_sequencer_cfg_format()
            )
            self._dummy_instr = True
        else:
            transport_inst = pulsar_qrm.ip_transport(host=host, port=port)
            self._dummy_instr = False
        super().__init__(name, transport_inst, debug=debug)
    def __init__(self, instrument: pulsar_qrm.pulsar_qrm_qcodes, **kwargs) -> None:
        """Create a new instance of PulsarQRMComponent."""
        assert isinstance(instrument, pulsar_qrm.pulsar_qrm_qcodes)
        super().__init__(instrument, **kwargs)
        self._acq_settings: Optional[_AcquisitionSettings] = None

        self._acq_settings = None
    @property
    def instrument(self) -> pulsar_qrm.pulsar_qrm_qcodes:
        return super().instrument

    @property
    def is_running(self) -> bool:
@@ -198,13 +209,15 @@ class PulsarQRMComponent(

        msmt_id: str = "msmt_00000"

        self.delete_acquisitions(sequencer=0)
        if not self._dummy_instr:
            self.get_sequencer_state(0, 10, 0.01)
            self.get_acquisition_state(sequencer=0, timeout=10, timeout_poll_res=0.1)
        self.instrument.delete_acquisitions(sequencer=0)
        if not isinstance(self.instrument, pulsar_qrm.pulsar_qrm_dummy):
            self.instrument.get_sequencer_state(0, 10, 0.01)
            self.instrument.get_acquisition_state(
                sequencer=0, timeout=10, timeout_poll_res=0.1
            )

        self.store_acquisition(0, msmt_id, num_of_samples)
        acq: Dict[str, dict] = self.get_acquisitions(0)
        self.instrument.store_acquisition(0, msmt_id, num_of_samples)
        acq: Dict[str, dict] = self.instrument.get_acquisitions(0)

        hardware_averages: int = self._acq_settings.hardware_averages
        duration: int = self._acq_settings.duration_ns
@@ -213,7 +226,9 @@ class PulsarQRMComponent(
        traces = [None] * len(path_labels)
        for path_idx, label in enumerate(path_labels):
            if acq[msmt_id][label]["out-of-range"]:
                logger.warning(f"ADC out-of-range of {self.name} on {label}.")
                logger.warning(
                    f"ADC out-of-range of {self.instrument.name} on {label}."
                )

            traces[path_idx] = (
                np.array(acq[msmt_id][label]["data"][:duration]) / hardware_averages
@@ -301,17 +316,23 @@ class PulsarQRMComponent(
                acq_settings.duration_ns = seq_settings.duration

            for path in [0, 1]:
                self.set(f"sequencer{seq_idx}_trigger_mode_acq_path{path}", "sequencer")
                self.set(f"sequencer{seq_idx}_avg_mode_en_acq_path{path}", True)
                self.instrument.set(
                    f"sequencer{seq_idx}_trigger_mode_acq_path{path}", "sequencer"
                )
                self.instrument.set(
                    f"sequencer{seq_idx}_avg_mode_en_acq_path{path}", True
                )

            self._acq_settings = acq_settings

            self.set(f"sequencer{seq_idx}_waveforms_and_program", seq_cfg["seq_fn"])
            self.instrument.set(
                f"sequencer{seq_idx}_waveforms_and_program", seq_cfg["seq_fn"]
            )

            self.arm_sequencer(sequencer=seq_idx)
            self.instrument.arm_sequencer(sequencer=seq_idx)

    def _configure_global_settings(self, settings: PulsarSettings):
        self.set("reference_source", settings.ref)
        self.instrument.set("reference_source", settings.ref)

    def _configure_sequencer_settings(self, seq_idx: int, settings: SequencerSettings):
        """
@@ -324,26 +345,32 @@ class PulsarQRMComponent(
        settings
            The settings to configure it to.
        """
        self.set(f"sequencer{seq_idx}_sync_en", settings.sync_en)
        self.set(f"sequencer{seq_idx}_offset_awg_path0", settings.awg_offset_path_0)
        self.set(f"sequencer{seq_idx}_offset_awg_path1", settings.awg_offset_path_1)
        self.instrument.set(f"sequencer{seq_idx}_sync_en", settings.sync_en)
        self.instrument.set(
            f"sequencer{seq_idx}_offset_awg_path0", settings.awg_offset_path_0
        )
        self.instrument.set(
            f"sequencer{seq_idx}_offset_awg_path1", settings.awg_offset_path_1
        )

        nco_en: bool = settings.nco_en
        self.set(f"sequencer{seq_idx}_mod_en_awg", nco_en)
        self.instrument.set(f"sequencer{seq_idx}_mod_en_awg", nco_en)
        if nco_en:
            self.set(f"sequencer{seq_idx}_nco_freq", settings.modulation_freq)
            self.instrument.set(
                f"sequencer{seq_idx}_nco_freq", settings.modulation_freq
            )

    def start(self) -> None:
        """
        Starts execution of the schedule.
        """
        self.start_sequencer()
        self.instrument.start_sequencer()

    def stop(self) -> None:
        """
        Stops all execution.
        """
        self.stop_sequencer()
        self.instrument.stop_sequencer()

    def wait_done(self, timeout_sec: int = 10) -> None:
        pass
+37 −42
Original line number Diff line number Diff line
@@ -17,21 +17,26 @@ from quantify_scheduler.backends.zhinst import helpers as zi_helpers
from quantify_scheduler.controlstack.components import base

if TYPE_CHECKING:
    import numpy as np
    from zhinst.qcodes.base import ZIBaseInstrument
    from quantify_scheduler.backends.zhinst_backend import ZIDeviceConfig
    from quantify_scheduler.backends.zhinst.settings import ZISettings
    import numpy as np


class ZIControlStackComponent(base.AbstractControlStackComponent):
class ZIControlStackComponent(base.ControlStackComponentBase):
    """Zurich Instruments ControlStack component base class."""

    def __init__(self) -> None:
        """Create a new instance on ZIControlStackComponent."""
        super().__init__()
    def __init__(self, instrument: ZIBaseInstrument, **kwargs) -> None:
        """Create a new instance of ZIControlStackComponent."""
        super().__init__(instrument, **kwargs)
        self.device_config: Optional[ZIDeviceConfig] = None
        self.zi_settings: Optional[ZISettings] = None
        self._data_path: Path = Path(".")

    @property
    def is_running(self) -> bool:
        raise NotImplementedError()

    def prepare(self, options: ZIDeviceConfig) -> None:
        """
        Prepare the ControlStack component with configuration
@@ -48,31 +53,26 @@ class ZIControlStackComponent(base.AbstractControlStackComponent):

        # Writes settings to filestorage
        self._data_path = Path(handling.get_datadir())
        self.zi_settings.serialize(self._data_path, self)
        self.zi_settings.serialize(self._data_path, self.instrument)

        # Upload settings, seqc and waveforms
        self.zi_settings.apply(self)
        self.zi_settings.apply(self.instrument)

    def retrieve_acquisition(self) -> Any:
        return None


class HDAWGControlStackComponent(qcodes.HDAWG, ZIControlStackComponent):
class HDAWGControlStackComponent(ZIControlStackComponent):
    """Zurich Instruments HDAWG ControlStack Component class."""

    def __init__(
        self,
        name: str,
        serial: str,
        interface: str = "1gbe",
        host: str = "localhost",
        port: int = 8004,
        api: int = 6,
        **kwargs,
    ) -> None:
        super().__init__(  # pylint: disable=too-many-function-args
            name, serial, interface, host, port, api, **kwargs
        )
    def __init__(self, instrument: qcodes.HDAWG, **kwargs) -> None:
        """Create a new instance of HDAWGControlStackComponent."""
        assert isinstance(instrument, qcodes.HDAWG)
        super().__init__(instrument, **kwargs)

    @property
    def instrument(self) -> qcodes.HDAWG:
        return super().instrument

    @property
    def is_running(self) -> bool:
@@ -95,7 +95,7 @@ class HDAWGControlStackComponent(qcodes.HDAWG, ZIControlStackComponent):
        :
            The HDAWG AWG instance.
        """
        return self.awgs[index]
        return self.instrument.awgs[index]

    def start(self) -> None:
        """Starts all HDAWG AWG(s) in reversed order by index."""
@@ -118,40 +118,35 @@ class HDAWGControlStackComponent(qcodes.HDAWG, ZIControlStackComponent):
            self.get_awg(awg_index).wait_done(timeout_sec)


class UHFQAControlStackComponent(qcodes.UHFQA, ZIControlStackComponent):
class UHFQAControlStackComponent(ZIControlStackComponent):
    """Zurich Instruments UHFQA ControlStack Component class."""

    def __init__(
        self,
        name: str,
        serial: str,
        interface: str = "1gbe",
        host: str = "localhost",
        port: int = 8004,
        api: int = 6,
        **kwargs,
    ) -> None:
        super().__init__(  # pylint: disable=too-many-function-args
            name, serial, interface, host, port, api, **kwargs
        )
    def __init__(self, instrument: qcodes.UHFQA, **kwargs) -> None:
        """Create a new instance of UHFQAControlStackComponent."""
        assert isinstance(instrument, qcodes.UHFQA)
        super().__init__(instrument, **kwargs)

    @property
    def instrument(self) -> qcodes.UHFQA:
        return super().instrument

    @property
    def is_running(self) -> bool:
        return self.awg.is_running
        return self.instrument.awg.is_running

    def start(self) -> None:
        self.awg.run()
        self.instrument.awg.run()

    def stop(self) -> None:
        self.awg.stop()
        self.instrument.awg.stop()

    def prepare(self, options: ZIDeviceConfig) -> None:
        self._data_path = Path(handling.get_datadir())

        # Copy the UHFQA waveforms to the waves directory
        # This is required before compilation.
        waves_path: Path = zi_helpers.get_waves_directory(self.awg)
        wave_files = self._data_path.cwd().glob(f"{self.name}*.csv")
        waves_path: Path = zi_helpers.get_waves_directory(self.instrument.awg)
        wave_files = self._data_path.cwd().glob(f"{self.instrument.name}*.csv")
        for file in wave_files:
            shutil.copy2(str(file), str(waves_path))

@@ -170,4 +165,4 @@ class UHFQAControlStackComponent(qcodes.UHFQA, ZIControlStackComponent):
        return acq_channel_results

    def wait_done(self, timeout_sec: int = 10) -> None:
        self.awg.wait_done(timeout_sec)
        self.instrument.awg.wait_done(timeout_sec)
+69 −51
Original line number Diff line number Diff line
@@ -3,21 +3,19 @@
"""Module containing the main ControlStack Component."""
from __future__ import annotations

from typing import Any, Dict, Optional
from typing import Any, Dict, List
from collections import OrderedDict

from qcodes.instrument.base import Instrument
from qcodes.station import Station
from qcodes.utils import validators
from qcodes.instrument import parameter
from qcodes.instrument import base as qcodes_base
from quantify_scheduler.controlstack.components import base


class ControlStack(Station):
class ControlStack(qcodes_base.Instrument):
    """
    The ControlStack class is a collection of ControlStack components.

    Each component in the collection is a QCoDeS
    :class:`~qcodes.instrument.base.Instrument` required to execute the experiment.

    This class provides a high level interface to:

    1. Arm instruments with sequence programs,
@@ -26,7 +24,16 @@ class ControlStack(Station):
    3. Get the results.
    """

    components: Dict[str, base.AbstractControlStackComponent]
    def __init__(self, name: str) -> None:
        super().__init__(name)
        self.add_parameter(
            "components",
            initial_value=list(),
            parameter_class=parameter.ManualParameter,
            vals=validators.Lists(validators.Strings()),
            docstring="A list containing the names of all components that"
            " are part of this ControlStack.",
        )

    @property
    def is_running(self) -> bool:
@@ -38,16 +45,19 @@ class ControlStack(Station):
        :
            The ControlStack's running state.
        """
        return any(c.is_running is True for c in self.components.values())
        return any(
            self.find_instrument(c_name).is_running is True
            for c_name in self.components()
        )

    def get_component(self, name: str) -> base.AbstractControlStackComponent:
    def get_component(self, name: str) -> base.ControlStackComponentBase:
        """
        Returns the ControlStack component by name.

        Parameters
        ----------
        name :
            The QCoDeS instrument name.
        name
            The ControlStack component name.

        Returns
        -------
@@ -59,52 +69,56 @@ class ControlStack(Station):
        KeyError
            If key `name` is not present in `self.components`.
        """
        if name not in self.components:
            raise KeyError(f"Device '{name}' not added to '{self.__class__.__name__}'")
        return self.components[name]
        if name in self.components():
            return self.find_instrument(name)
        raise KeyError(f"'{name}' is not a component of {self.name}!")

    def add_component(
        self,
        component: base.AbstractControlStackComponent,
        name: Optional[str] = None,
        update_snapshot: bool = True,
    ) -> str:
        component: base.ControlStackComponentBase,
    ) -> None:
        """
        Adds a ControlStack component to the ControlStack collection.
        Adds a ControlStack component to the ControlStack components collection.

        Parameters
        ----------
        component
            The control stack component to add.
        name
            Optionally set the name of this component.
        update_snapshot
            Immediately update the snapshot of each component as it is added to the
            Station.

        Returns
        -------
        :
            The QCoDeS name assigned to this component, the name might be changed to
            make it unique among previously added components.
            The ControlStack component to add.

        Raises
        ------
        ValueError
            If a component with a duplicated name is added to the collection.
        TypeError
            If `component` is not an instance of `AbstractControlStackComponent` or
            `Instrument`.
            If 'component' is not an instance of 'ControlStackComponentBase'.
        """
        if not isinstance(component, (base.AbstractControlStackComponent, Instrument)):
        if component.name in self.components():
            raise ValueError(f"'{component.name}' has already been added!")

        if not isinstance(component, base.ControlStackComponentBase):
            raise TypeError(
                (
                    "Expected AbstractControlStackComponent and Instrument for "
                    f"component argument, instead got {type(component)}"
                )
            )
        return super().add_component(
            component, name=name, update_snapshot=update_snapshot
                f"{repr(component)} is not "
                f"{base.__name__}.{base.ControlStackComponentBase.__name__}."
            )

        components: List[str] = self.components()
        # add the component by name
        components.append(component.name)
        self.components.set(components)

    def remove_component(self, name: str) -> None:
        """
        Removes a ControlStack component by name.

        Parameters
        ----------
        name
            The ControlStack component name.
        """

        # list gets updated in place
        self.components().remove(name)

    def prepare(
        self,
        options: Dict[str, Any],
@@ -138,8 +152,9 @@ class ControlStack(Station):
        The components are started in the order in which they were added
        to the ControlStack.
        """
        for instrument_name in self.components:
            self.get_component(instrument_name).start()
        for instr_name in self.components():
            instrument = self.find_instrument(instr_name)
            instrument.start()

    def stop(self) -> None:
        """
@@ -148,8 +163,9 @@ class ControlStack(Station):
        The components are stopped in the order in which they were added
        to the ControlStack.
        """
        for instrument_name in self.components:
            self.get_component(instrument_name).stop()
        for instr_name in self.components():
            instrument = self.find_instrument(instr_name)
            instrument.stop()

    def retrieve_acquisition(self) -> Dict[str, Any]:
        """
@@ -162,10 +178,11 @@ class ControlStack(Station):
            The acquisition data per ControlStack component.
        """
        acq_dict = OrderedDict()
        for instrument_name in self.components:
            acq = self.get_component(instrument_name).retrieve_acquisition()
        for instr_name in self.components():
            instrument = self.find_instrument(instr_name)
            acq = instrument.retrieve_acquisition()
            if acq is not None:
                acq_dict[instrument_name] = acq
                acq_dict[instrument.name] = acq
        return acq_dict

    def wait_done(self, timeout_sec: int = 10) -> None:
@@ -181,5 +198,6 @@ class ControlStack(Station):
        timeout_sec :
            The maximum amount of time in seconds before a timeout.
        """
        for instrument_name in self.components:
            self.get_component(instrument_name).wait_done(timeout_sec)
        for instr_name in self.components():
            instrument = self.find_instrument(instr_name)
            self.get_component(instrument.name).wait_done(timeout_sec)
+128 −72

File changed.

Preview size limit exceeded, changes collapsed.

Loading