diff --git a/src/qibolab/channel.py b/src/qibolab/channel.py new file mode 100644 index 000000000..80be31252 --- /dev/null +++ b/src/qibolab/channel.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass + + +@dataclass +class NamedChannel: + """Channel that has a name. This is part of the end-user API, i.e. is in the top layer. + + The idea is the following: + 1. End users do not know what are the types of channels in a platform, they just know names. + They use only NamedChannels to describe pulse sequences. They can create NamedChannels using the get_channel function below. + 2. User makes some assumptions about the types of channels. E.g. they assume that the channel NamedChannel("qubit_0/drive") + is an IQ channel, hence they play IQ pulses on it, and they provide IQChannelConfig for it. + 3. Upon receival of the execution request qibolab validates that the requested execution can be done, i.e. + channels with those names exist and user's assumptions are correct. + 4. qibolab proceeds with execution. + For the last two steps qibolab needs to replace generic NamedChannels with concrete channels (e.g. ZurichIQChannel, QbloxDCChannel, etc.), and + those should be available in the Platform description. + + TODO: I am not sure if having this class brings any benefit to this plan compared to the case where we just use naked str names, but I will figure + this out later during implementation. + + TODO: One might argue that it is reasonable to provide the end user the types of channels as well, and then do all the validation while constructing the pulse + sequence. I thought about this and failed to find real benefit, it just seems to complicate the code and the user-facing API for no real benefit. + Please comment if you have anything to say regarding this. + """ + + name: str + + def __str__(self) -> str: + return self.name + + +def get_channel(element: str, line: str) -> NamedChannel: + """Named channel for given element (qubit|qubit|coupler|etc.), for given line (drive|flux|readout|etc.) + + This method can be used by users to get the channel that they are interested in, and then use this in their pulse sequence description. + + FIXME: the function signature is just a mock/sketch. Needs to be designed properly. + """ + return NamedChannel(f"{element}/{line}") diff --git a/src/qibolab/channel_config.py b/src/qibolab/channel_config.py new file mode 100644 index 000000000..ede89d02d --- /dev/null +++ b/src/qibolab/channel_config.py @@ -0,0 +1,38 @@ +from dataclasses import dataclass +from enum import Enum +"""Definitions for common options for various types of channels. This is part of the end-user API, i.e. is in the top layer. +""" + + +class AcquisitionType(Enum): + RAW = "raw" + INTEGRATION = "integration" + CLASSIFICATION = "classification" + + +@dataclass +class DCChannelConfig: + sampling_rate: float + bias: float + + +@dataclass +class IQChannelConfig: + sampling_rate: float + frequency: float + mixer_g: float = 0.0 + mixer_phi: float = 0.0 + + +@dataclass +class OscillatorChannelConfig: + frequency: float + + +@dataclass +class AcquisitionChannelConfig: + type: AcquisitionType + integration_weights_i: list[float] + integration_weights_q: list[float] + classification_kernel: float # FIXME + diff --git a/src/qibolab/instruments/abstract_channels.py b/src/qibolab/instruments/abstract_channels.py new file mode 100644 index 000000000..466043ea4 --- /dev/null +++ b/src/qibolab/instruments/abstract_channels.py @@ -0,0 +1,28 @@ +from abc import ABC, abstractmethod + +""" +In the instrument layer we shall try to do as many things as possible in a unified manner. +This file defines the requrements on various types of channels that instrument-specific implementations should satisfy. +E.g. there could be methods returning the memory limitation on a channel, so that a unified unrolling-batching algorithm +can be written that is not instrument-specific. + +TODO: this needs proper design +""" + + +class IQChannel(ABC): + + @abstractmethod + def foo(self, args, kwargs): + ... + + @abstractmethod + def bar(self, args): + ... + + +class DCChannel(ABC): + + @abstractmethod + def baz(self, kwargs): + ... \ No newline at end of file diff --git a/src/qibolab/instruments/zi/channels.py b/src/qibolab/instruments/zi/channels.py new file mode 100644 index 000000000..3affc9402 --- /dev/null +++ b/src/qibolab/instruments/zi/channels.py @@ -0,0 +1,24 @@ +from qibolab.channel import NamedChannel +from qibolab.channel_config import IQChannelConfig +from qibolab.instruments.abstract_channels import IQChannel + +"""The glue part of the platform shall manually define all channels according to wiring, +then for each user request appropriate channels will be collected for handling the execution. +""" + + +class ZIIQChannel(IQChannel, NamedChannel): + """IQChannel using a from Zurich instruments + """ + + config: IQChannelConfig + + # FIXME: add all the necessary stuff needed to define a ZI IQ channel + + def foo(self, args, kwargs): + ... + + def bar(self, args): + ... + +# TODO: Similarly, other ZI channels can be implemented \ No newline at end of file diff --git a/src/qibolab/qubits.py b/src/qibolab/qubits.py index 867640401..1114ee1e4 100644 --- a/src/qibolab/qubits.py +++ b/src/qibolab/qubits.py @@ -77,11 +77,6 @@ class Qubit: threshold: Optional[float] = None iq_angle: float = 0.0 kernel: Optional[np.ndarray] = field(default=None, repr=False) - # required for mixers (not sure if it should be here) - mixer_drive_g: float = 0.0 - mixer_drive_phi: float = 0.0 - mixer_readout_g: float = 0.0 - mixer_readout_phi: float = 0.0 readout: Optional[Channel] = None feedback: Optional[Channel] = None