Skip to content

Instruments

Instrument

Bases: FunctionOverrideHandler, ABC

Base class for instruments with configurable behavior.

Supports overriding connect, setup, and disconnect methods via a configuration dictionary.

Source code in sqil_core/experiment/instruments/_instrument.py
@Pyro5.server.expose
class Instrument(FunctionOverrideHandler, ABC):
    """
    Base class for instruments with configurable behavior.

    Supports overriding `connect`, `setup`, and `disconnect` methods
    via a configuration dictionary.
    """

    def __init__(self, id: str, config: dict):
        """
        Initializes the instrument with an ID and configuration.

        If `connect`, `setup`, or `disconnect` are provided in `config`,
        they override the default implementations.
        """
        super().__init__()

        self._id = id
        self._type = config.get("type", "")
        self._model = config.get("model", "")
        self._name = config.get("name", "")
        self._address = config.get("address", "")
        self._variables = config.get("variables", {})
        self._config = config
        self._device = None

        self._default_functions = {
            "connect": self._default_connect,
            "setup": self._default_setup,
            "disconnect": self._default_disconnect,
            "on_before_experiment": self._default_on_before_experiment,
            "on_after_experiment": self._default_on_after_experiment,
            "on_before_sequence": self._default_on_before_sequence,
            "on_after_sequence": self._default_on_after_sequence,
        }
        self._functions = self._default_functions.copy()

        # Override functions if provided in config
        for method_name in self._default_functions:
            if method := config.get(method_name):
                self.override_function(method_name, method)

        self._default_functions = self._functions.copy()
        self._device = self.connect()  # Auto-connect on instantiation

        # Subscribe to events
        self._subscribe_to_events()

    def _subscribe_to_events(self):
        before_experiment.connect(
            lambda *a, **kw: self.call("on_before_experiment", *a, **kw), weak=False
        )
        before_sequence.connect(
            lambda *a, **kw: self.call("on_before_sequence", *a, **kw), weak=False
        )
        after_sequence.connect(
            lambda *a, **kw: self.call("on_after_sequence", *a, **kw), weak=False
        )
        after_experiment.connect(
            lambda *a, **kw: self.call("on_after_experiment", *a, **kw), weak=False
        )

    def connect(self, *args, **kwargs):
        """Calls the overridden or default `connect` method."""
        return self.call("connect", *args, **kwargs)

    @abstractmethod
    def _default_connect(self, *args, **kwargs):
        """Default `connect` implementation (must be overridden)."""
        pass

    def setup(self, *args, **kwargs):
        """Calls the overridden or default `setup` method."""
        return self.call("setup", *args, **kwargs)

    @abstractmethod
    def _default_setup(self, *args, **kwargs):
        """Default `setup` implementation (must be overridden)."""
        pass

    def disconnect(self, *args, **kwargs):
        """Calls the overridden or default `disconnect` method."""
        return self.call("disconnect", *args, **kwargs)

    @abstractmethod
    def _default_disconnect(self, *args, **kwargs):
        pass

    def on_before_experiment(self, *args, **kwargs):
        """Calls the overridden or default `on_before_experiment` method."""
        return self.call("on_before_experiment", *args, **kwargs)

    def _default_on_before_experiment(self, *args, **kwargs):
        pass

    def on_before_sequence(self, *args, **kwargs):
        """Calls the overridden or default `on_before_sequence` method."""
        return self.call("on_before_sequence", *args, **kwargs)

    def _default_on_before_sequence(self, *args, **kwargs):
        pass

    def on_after_experiment(self, *args, **kwargs):
        """Calls the overridden or default `on_after_experiment` method."""
        return self.call("on_after_experiment", *args, **kwargs)

    def _default_on_after_experiment(self, *args, **kwargs):
        pass

    def on_after_sequence(self, *args, **kwargs):
        """Calls the overridden or default `on_after_sequence` method."""
        return self.call("on_after_sequence", *args, **kwargs)

    def _default_on_after_sequence(self, *args, **kwargs):
        pass

    def __getattr__(self, name):
        """
        Dynamically expose all attributes to Pyro server.
        """
        if name in self.__dict__:
            return self.__dict__[name]
        raise AttributeError(
            f"'{self.__class__.__name__}' object has no attribute '{name}'"
        )

    def get_variable(self, key, *args, **kwargs):
        var = self._variables.get(key, None)
        if callable(var):
            var = var(*args, **kwargs)
        return var

    @property
    def id(self):
        """Instrument ID (read-only)."""
        return self._id

    @property
    def type(self):
        """Instrument type (read-only)."""
        return self._type

    @property
    def model(self):
        """Instrument model (read-only)."""
        return self._model

    @property
    def name(self):
        """Instrument name (read-only)."""
        return self._name

    @property
    def address(self):
        """Instrument address (read-only)."""
        return self._address

    @property
    def variables(self):
        """Instrument variables (read-only)."""
        return self._variables

    @property
    def config(self):
        """Instrument configuration dictionary (read-only)."""
        return self._config

    @property
    def device(self):
        """Raw instrument instance (read-only)."""
        return self._device

address property

Instrument address (read-only).

config property

Instrument configuration dictionary (read-only).

device property

Raw instrument instance (read-only).

id property

Instrument ID (read-only).

model property

Instrument model (read-only).

name property

Instrument name (read-only).

type property

Instrument type (read-only).

variables property

Instrument variables (read-only).

__getattr__(name)

Dynamically expose all attributes to Pyro server.

Source code in sqil_core/experiment/instruments/_instrument.py
def __getattr__(self, name):
    """
    Dynamically expose all attributes to Pyro server.
    """
    if name in self.__dict__:
        return self.__dict__[name]
    raise AttributeError(
        f"'{self.__class__.__name__}' object has no attribute '{name}'"
    )

__init__(id, config)

Initializes the instrument with an ID and configuration.

If connect, setup, or disconnect are provided in config, they override the default implementations.

Source code in sqil_core/experiment/instruments/_instrument.py
def __init__(self, id: str, config: dict):
    """
    Initializes the instrument with an ID and configuration.

    If `connect`, `setup`, or `disconnect` are provided in `config`,
    they override the default implementations.
    """
    super().__init__()

    self._id = id
    self._type = config.get("type", "")
    self._model = config.get("model", "")
    self._name = config.get("name", "")
    self._address = config.get("address", "")
    self._variables = config.get("variables", {})
    self._config = config
    self._device = None

    self._default_functions = {
        "connect": self._default_connect,
        "setup": self._default_setup,
        "disconnect": self._default_disconnect,
        "on_before_experiment": self._default_on_before_experiment,
        "on_after_experiment": self._default_on_after_experiment,
        "on_before_sequence": self._default_on_before_sequence,
        "on_after_sequence": self._default_on_after_sequence,
    }
    self._functions = self._default_functions.copy()

    # Override functions if provided in config
    for method_name in self._default_functions:
        if method := config.get(method_name):
            self.override_function(method_name, method)

    self._default_functions = self._functions.copy()
    self._device = self.connect()  # Auto-connect on instantiation

    # Subscribe to events
    self._subscribe_to_events()

connect(*args, **kwargs)

Calls the overridden or default connect method.

Source code in sqil_core/experiment/instruments/_instrument.py
def connect(self, *args, **kwargs):
    """Calls the overridden or default `connect` method."""
    return self.call("connect", *args, **kwargs)

disconnect(*args, **kwargs)

Calls the overridden or default disconnect method.

Source code in sqil_core/experiment/instruments/_instrument.py
def disconnect(self, *args, **kwargs):
    """Calls the overridden or default `disconnect` method."""
    return self.call("disconnect", *args, **kwargs)

on_after_experiment(*args, **kwargs)

Calls the overridden or default on_after_experiment method.

Source code in sqil_core/experiment/instruments/_instrument.py
def on_after_experiment(self, *args, **kwargs):
    """Calls the overridden or default `on_after_experiment` method."""
    return self.call("on_after_experiment", *args, **kwargs)

on_after_sequence(*args, **kwargs)

Calls the overridden or default on_after_sequence method.

Source code in sqil_core/experiment/instruments/_instrument.py
def on_after_sequence(self, *args, **kwargs):
    """Calls the overridden or default `on_after_sequence` method."""
    return self.call("on_after_sequence", *args, **kwargs)

on_before_experiment(*args, **kwargs)

Calls the overridden or default on_before_experiment method.

Source code in sqil_core/experiment/instruments/_instrument.py
def on_before_experiment(self, *args, **kwargs):
    """Calls the overridden or default `on_before_experiment` method."""
    return self.call("on_before_experiment", *args, **kwargs)

on_before_sequence(*args, **kwargs)

Calls the overridden or default on_before_sequence method.

Source code in sqil_core/experiment/instruments/_instrument.py
def on_before_sequence(self, *args, **kwargs):
    """Calls the overridden or default `on_before_sequence` method."""
    return self.call("on_before_sequence", *args, **kwargs)

setup(*args, **kwargs)

Calls the overridden or default setup method.

Source code in sqil_core/experiment/instruments/_instrument.py
def setup(self, *args, **kwargs):
    """Calls the overridden or default `setup` method."""
    return self.call("setup", *args, **kwargs)

ZI_Instrument

Bases: Instrument

Source code in sqil_core/experiment/instruments/zurich_instruments.py
class ZI_Instrument(Instrument):
    _descriptor = ""
    _generate_setup = None
    _generate_qpu = None

    def __init__(self, id, config):
        super().__init__(id, config)
        self._descriptor = config.get("descriptor", "")

        self._generate_setup = config.get("generate_setup", None)
        if not self._generate_setup:
            raise NotImplementedError(
                "get_setup is not implemented in your setup file.\n"
                + "You should define it as part of the zi section of your instruments dictionary.\n"
                + "instruments['zi']['generate_setup']"
            )

        self._generate_qpu = config.get("generate_qpu", None)
        if not self._generate_qpu:
            raise NotImplementedError(
                "get_qpu is not implemented in your setup file.\n"
                + "You should define it as part of the zi section of your instruments dictionary.\n"
                + "instruments['zi']['generate_qpu']"
            )

    def generate_setup(self, *params, **kwargs) -> DeviceSetup:
        return self._generate_setup(*params, **kwargs)

    def generate_qpu(self, *params, **kwargs) -> QPU:
        return self._generate_qpu(*params, **kwargs)

    def _default_connect(self):
        pass
        # setup = self.config.get("setup_obj", None)
        # if setup is not None:
        #     self._session = Session(setup)
        #     return self.session
        # raise "Zuirch instruments needs a 'setup_obj' field in your setup file"

    def _default_setup(self):
        pass

    def _default_disconnect(self):
        pass

    @property
    def descriptor(self):
        """LaboneQ descriptor (read-only) - deprecated."""
        return self._descriptor

descriptor property

LaboneQ descriptor (read-only) - deprecated.

LocalOscillatorBase

Bases: Instrument, ABC

Source code in sqil_core/experiment/instruments/local_oscillator.py
class LocalOscillatorBase(Instrument, ABC):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    @abstractmethod
    def set_frequency(self, value) -> None:
        """Set the frequency of the local oscillator."""
        pass

    @abstractmethod
    def set_power(self, value) -> None:
        """Set the power of the local oscillator."""
        pass

    @abstractmethod
    def turn_on(self) -> None:
        """Turn the local oscillator on."""
        pass

    @abstractmethod
    def turn_off(self) -> None:
        """Turn the local oscillator off."""
        pass

set_frequency(value) abstractmethod

Set the frequency of the local oscillator.

Source code in sqil_core/experiment/instruments/local_oscillator.py
@abstractmethod
def set_frequency(self, value) -> None:
    """Set the frequency of the local oscillator."""
    pass

set_power(value) abstractmethod

Set the power of the local oscillator.

Source code in sqil_core/experiment/instruments/local_oscillator.py
@abstractmethod
def set_power(self, value) -> None:
    """Set the power of the local oscillator."""
    pass

turn_off() abstractmethod

Turn the local oscillator off.

Source code in sqil_core/experiment/instruments/local_oscillator.py
@abstractmethod
def turn_off(self) -> None:
    """Turn the local oscillator off."""
    pass

turn_on() abstractmethod

Turn the local oscillator on.

Source code in sqil_core/experiment/instruments/local_oscillator.py
@abstractmethod
def turn_on(self) -> None:
    """Turn the local oscillator on."""
    pass

SqilRohdeSchwarzSGS100A

Bases: LocalOscillatorBase

Frequency: [1 MHz, 20 GHz], resolution 0.001 Hz Power: [-120 dB, 25 dB], resolution 0.01 dB

Source code in sqil_core/experiment/instruments/local_oscillator.py
class SqilRohdeSchwarzSGS100A(LocalOscillatorBase):
    """
    Frequency:
        [1 MHz, 20 GHz], resolution 0.001 Hz
    Power:
        [-120 dB, 25 dB], resolution 0.01 dB
    """

    def _default_connect(self, *args, **kwargs):
        logger.info(f"Connecting to {self.name} ({self.model})")
        return RohdeSchwarzSGS100A(self.name, self.address)

    def _default_disconnect(self, *args, **kwargs):
        logger.info(f"Disconnecting from {self.name} ({self.model})")
        self.turn_off()

    def _default_setup(self, *args, **kwargs):
        logger.info(f"Setting up {self.name}")
        self.turn_off()
        self.set_power(-60)

    def set_frequency(self, value) -> None:
        self.device.frequency(value)

    def set_power(self, value) -> None:
        self.device.power(value)

    def turn_on(self) -> None:
        self.device.on()

    def turn_off(self) -> None:
        self.device.off()

SqilSignalCoreSC5511A

Bases: LocalOscillatorBase

PORT 1 specifications Frequency: [100 MHz, 20 GHz], resolution 1 Hz Power: @ freq < 18 GHz: [-20 dBm, 15 dBm], resolution 0.01 dBm @ freq > 18 GHz: [-20 dBm, 10 dBm], resolution 0.01 dBm

Source code in sqil_core/experiment/instruments/local_oscillator.py
class SqilSignalCoreSC5511A(LocalOscillatorBase):
    """
    PORT 1 specifications
    Frequency:
        [100 MHz, 20 GHz], resolution 1 Hz
    Power:
        @ freq < 18 GHz: [-20 dBm, 15 dBm], resolution 0.01 dBm
        @ freq > 18 GHz: [-20 dBm, 10 dBm], resolution 0.01 dBm
    """

    def _default_connect(self, *args, **kwargs):
        logger.info(f"Connecting to {self.name} ({self.model})")
        return SignalCore_SC5511A(self.name, self.address)

    def _default_disconnect(self, *args, **kwargs):
        logger.info(f"Disconnecting from {self.name} ({self.model})")
        self.turn_off()

    def _default_setup(self, *args, **kwargs):
        logger.info(f"Setting up {self.name}")
        self.turn_off()
        self.set_power(-40)
        self.device.do_set_reference_source(1)  # to enable phase locking
        self.device.do_set_standby(True)  # update PLL locking
        self.device.do_set_standby(False)

    def set_frequency(self, value) -> None:
        self.device.do_set_ref_out_freq(value)

    def set_power(self, value) -> None:
        self.device.power(value)

    def turn_on(self) -> None:
        self.device.do_set_output_status(1)

    def turn_off(self) -> None:
        self.device.do_set_output_status(0)

SqilSignalCoreSC5521A

Bases: LocalOscillatorBase

Frequency: [160 MHz, 40 GHz], resolution 1 Hz Power: @ freq < 30 GHz: [-10 dBm, 15 dBm], resolution 0.1 dBm @ freq < 35 GHz: [-10 dBm, 10 dBm], resolution 0.1 dBm @ freq > 35 GHz: [-10 dBm, 3 dBm], resolution 0.1 dBm

Source code in sqil_core/experiment/instruments/local_oscillator.py
class SqilSignalCoreSC5521A(LocalOscillatorBase):
    """
    Frequency:
        [160 MHz, 40 GHz], resolution 1 Hz
    Power:
        @ freq < 30 GHz: [-10 dBm, 15 dBm], resolution 0.1 dBm
        @ freq < 35 GHz: [-10 dBm, 10 dBm], resolution 0.1 dBm
        @ freq > 35 GHz: [-10 dBm, 3 dBm], resolution 0.1 dBm
    """

    def _default_connect(self, *args, **kwargs):
        logger.info(f"Connecting to {self.name} ({self.model})")
        return SC5521A(self.name)

    def _default_disconnect(self, *args, **kwargs):
        logger.info(f"Disconnecting from {self.name} ({self.model})")
        self.turn_off()

    def _default_setup(self, *args, **kwargs):
        logger.info(f"Setting up {self.name}")
        self.turn_off()
        self.set_power(-40)

    def set_frequency(self, value) -> None:
        self.device.clock_frequency(value)

    def set_power(self, value) -> None:
        self.device.power(value)

    def turn_on(self) -> None:
        self.device.status("on")

    def turn_off(self) -> None:
        self.device.status("off")