How to add a new instrument in Qibolab?#

Currently, Qibolab supports various controller instruments:

  • Quantum Machines

  • Zurich instruments

and the following local oscillators:

  • Rhode&Schwartz

  • Erasynth++

If you need to add a new driver, to support a new instruments in your setup, we encourage you to have a look at qibolab.instruments for complete examples. In this section, anyway, a basic example will be given.

For clarity, we divide the instruments in two different groups: the controllers and the standard instruments, where the controller is an instrument that can execute pulse sequences. For example, a local oscillator is just an instrument, while Quantum Machines is a controller.

Add an instrument#

The base of an instrument is qibolab._core.instruments.abstract.Instrument, which is a pydantic Model. To accomodate different kind of instruments, a flexible interface is implemented with two abstract methods (connect() and disconnect()) that are required to be implemented in the child instrument. Optionally, a setup() method can also be implemented to upload settings, such as local oscillator frequency or power, to the instrument after connection. If setup() is not implemented it will be an empty function.

In the execution of an experiment these functions are called sequentially, so first a connection is established, the instrument is set up with the required parameters, the instrument starts operating, then stops and gets disconnected. Note that it’s perfectly fine to leave the majority of these functions empty if not needed.

Here, let’s write a basic example of instrument whose job is to deliver a fixed bias for the duration of the experiment:

from typing import Optional

# let's suppose that there is already available a base driver for connection
# and control of the device, provided by the following library
from proprietary_instruments import BiaserType, biaser_driver

from qibolab.instruments.abstract import Instrument


class Biaser(Instrument):
    """An instrument that delivers constand biases."""
    name: str
    address: str
    min_value: float = -1.0
    max_value: float = 1.0
    bias: float = 0.0
    device: Optional[BiaserType] = None


    def connect(self):
        """Check if a connection is avaiable."""
        if self.device is None:
            self.device = biaser_driver(self.address)
        self.device.on(self.bias)

    def disconnect(self):
        self.device.off(self.bias)
        self.device.disconnect()

    def setup(self):
        """Set biaser parameters."""
        self.device.set_range(self.min_value, self.max_value)

Add a controller#

The controller is an instrument that has the additional method play, which allows it to execute arbitrary pulse sequences and perform sweeps. Its abstract implementation can be found in qibolab._core.instruments.abstract.Controller.

Let’s see a minimal example:

from typing import Optional

from proprietary_instruments import ControllerType, controller_driver

from qibolab._core.components import Config
from qibolab._core.execution_parameters import ExecutionParameters
from qibolab._core.identifier import Result
from qibolab._core.sequence import PulseSequence
from qibolab._core.sweeper import ParallelSweepers
from qibolab._core.instruments.abstract import Controller


class MyController(Controller):

    def connect(self):
        if self.device is None:
            self.device = controller_driver(address)

    def disconnect(self):
        self.device.disconnect()

    def play(
            self,
            configs: dict[str, Config],
            sequences: list[PulseSequence],
            options: ExecutionParameters,
            sweepers: list[ParallelSweepers],
        ) -> dict[int, Result]:
        """Executes a PulseSequence."""
        if len(sweepers) > 0:
            raise NotImplementedError("MyController does not support sweeps.")

        if len(sequences) == 0:
            return {}
        elif len(sequences) == 1:
            sequence = sequences[0]
        else:
            sequence, _ = unroll_sequences(sequences, options.relaxation_time)

        # usually, some modification on the sequence, channel configs, or
        # parameters is needed so that the qibolab interface comply with the
        # interface of the device. Here these are assumed to be equal for simplicity.
        results = self.device.run_experiment(qubits, sequence, options)

        # also the results are, in qibolab, specific objects that need some kind
        # of conversion. Refer to the results section in the documentation.
        return results