Source code for qeg_nmr_qua.config.controller

"""
OPX-1000 Controller Configuration Module.

This module provides configuration utilities for the OPX-1000 LF-FEM
for solid state NMR experiments.
"""

from dataclasses import dataclass, field
from typing import Any


[docs] @dataclass class AnalogOutput: """Configuration for an analog output channel of the OPX. Analog outputs are used to generate control signals (pulses, RF signals, etc.) for driving quantum elements. Each output has an independent offset, sampling rate, and output mode. Attributes: offset (float): DC offset applied to the output in volts (default: 0.0). Used for calibration and bias adjustment. sampling_rate (int): Output sampling rate in Hz (default: 1 GHz). Typically 1e9 for high-speed operations. output_mode (str): Output mode, typically "direct" for direct waveform output (default: "direct"). """ offset: float = 0.0 sampling_rate: int = 1_000_000_000 # 1e9 output_mode: str = "direct"
[docs] def to_dict(self) -> dict[str, Any]: return { "offset": self.offset, "sampling_rate": self.sampling_rate, "output_mode": self.output_mode, }
[docs] @classmethod def from_dict(cls, d: dict[str, Any]) -> "AnalogOutput": return cls( offset=d.get("offset", 0.0), sampling_rate=d.get("sampling_rate", 1_000_000_000), output_mode=d.get("output_mode", "direct"), )
[docs] def to_opx_config(self) -> dict[str, Any]: return self.to_dict()
def __repr__(self) -> str: # concise one-line description return f"<AnalogOutput offset={self.offset} samp={self.sampling_rate} mode={self.output_mode}>"
[docs] @dataclass class AnalogInput: """Configuration for an analog input channel of the OPX. Analog inputs measure signals from quantum elements (e.g., resonator readout). Each input has independent offset, gain, and sampling rate for signal conditioning. Attributes: offset (float): Input offset in volts (default: 0.0). Compensates for DC bias in the measurement. gain_db (int): Programmable gain amplifier setting in dB (default: 0). Amplifies or attenuates the input signal. sampling_rate (int): Input sampling rate in Hz (default: 1 GHz). Must match the acquisition requirements. """ offset: float = 0.0 gain_db: int = 0 sampling_rate: int = int(1e9)
[docs] def to_dict(self) -> dict[str, Any]: return { "offset": self.offset, "gain_db": self.gain_db, "sampling_rate": self.sampling_rate, }
[docs] @classmethod def from_dict(cls, d: dict[str, Any]) -> "AnalogInput": return cls( offset=d.get("offset", 0.0), gain_db=d.get("gain_db", 0), sampling_rate=d.get("sampling_rate", int(1e9)), )
[docs] def to_opx_config(self) -> dict[str, Any]: return self.to_dict()
def __repr__(self) -> str: return f"<AnalogInput offset={self.offset} gain_db={self.gain_db} samp={self.sampling_rate}>"
[docs] @dataclass class DigitalIO: """Configuration for digital input/output lines. Digital I/O is used for control signals like RF switching, marker pulses, and external triggering. Digital signals are binary (0 or 1) and can be synchronized with analog operations. Attributes: name (str): Descriptive name for this digital line (default: "TTL"). Example names: "RF_switch", "marker1", "trigger". direction (str): Signal direction, either "input" or "output" (default: "output"). Determines whether this line reads or drives digital signals. inverted (bool): Whether the signal logic is inverted (default: False). If True, a logical 0 is represented as a high voltage and vice versa. """ name: str = "TTL" direction: str = "output" # 'input' or 'output' inverted: bool = False # Default i/o state is 0 (not inverted)
[docs] def to_opx_config(self) -> dict[str, Any]: return {"inverted": self.inverted} if self.inverted else {}
[docs] def to_dict(self) -> dict[str, Any]: return { "name": self.name, "direction": self.direction, "inverted": self.inverted, }
def __repr__(self) -> str: return f"<DigitalIO {self.name} dir={self.direction} inv={self.inverted}>"
[docs] @classmethod def from_dict(cls, d: dict[str, Any]) -> "DigitalIO": return cls( name=d.get("name", "TTL"), direction=d.get("direction", "output"), inverted=d.get("inverted", False), )
[docs] @dataclass class FEModuleConfig: """Configuration for the OPX-1000 controller.""" # name: str = "con1" # controller name in OPX config slot: int = 1 # physical slot number in chasis fem_type: str = "LF" # Low Frequency Front-End Module analog_outputs: dict[int, AnalogOutput] = field(default_factory=dict) analog_inputs: dict[int, AnalogInput] = field(default_factory=dict) digital_outputs: dict[int, DigitalIO] = field(default_factory=dict) # digital_inputs: dict[int, DigitalIO] = field( # default_factory=dict # ) # not used currently
[docs] def add_digital_output( self, port: int, name: str = "TTL", inverted: bool = False ) -> None: """Add a digital output channel configuration.""" assert 1 <= port <= 8, "Digital output port must be between 1 and 8." if port in self.digital_outputs: raise Warning( f"Digital output port {port} is already configured. Overwriting." ) self.digital_outputs[port] = DigitalIO( name=name, direction="output", inverted=inverted )
[docs] def add_analog_output( self, port: int, offset: float = 0.0, sampling_rate: int = int(1e9), output_mode: str = "direct", ) -> None: """Add an analog output channel configuration.""" assert port == 1 or port == 2, "Analog output port must be 1 or 2." if port in self.analog_outputs: raise Warning( f"Analog output port {port} is already configured. Overwriting." ) self.analog_outputs[port] = AnalogOutput( offset=offset, sampling_rate=sampling_rate, output_mode=output_mode )
[docs] def add_analog_input( self, port: int, offset: float = 0.0, gain_db: int = 0, sampling_rate: int = int(1e9), ) -> None: """Add an analog input channel configuration.""" assert port == 1 or port == 2, "Analog input port must be 1 or 2." if port in self.analog_inputs: raise Warning( f"Analog input port {port} is already configured. Overwriting." ) self.analog_inputs[port] = AnalogInput( offset=offset, gain_db=gain_db, sampling_rate=sampling_rate )
[docs] def to_opx_config(self) -> dict[str, Any]: return { "type": self.fem_type, "analog_outputs": { port: ao.to_opx_config() for port, ao in self.analog_outputs.items() }, "analog_inputs": { port: ai.to_opx_config() for port, ai in self.analog_inputs.items() }, "digital_outputs": { port: do.to_opx_config() for port, do in self.digital_outputs.items() }, # "digital_inputs": { # port: di.to_opx_config() for port, di in self.digital_inputs.items() # }, }
[docs] def to_dict(self) -> dict[str, Any]: return { "slot": self.slot, "type": self.fem_type, "analog_outputs": { port: ao.to_dict() for port, ao in self.analog_outputs.items() }, "analog_inputs": { port: ai.to_dict() for port, ai in self.analog_inputs.items() }, "digital_outputs": { port: do.to_dict() for port, do in self.digital_outputs.items() }, # "digital_inputs": { # port: di.to_dict() for port, di in self.digital_inputs.items() # }, }
def __repr__(self) -> str: ao = len(self.analog_outputs) ai = len(self.analog_inputs) do = len(self.digital_outputs) return ( f"<FEModule slot={self.slot} type={self.fem_type} AO={ao} AI={ai} DO={do}>" )
[docs] @classmethod def from_dict(cls, d: dict[str, Any]) -> "FEModuleConfig": fm = cls(slot=d.get("slot", 1), fem_type=d.get("type", "LF")) for port, ao in (d.get("analog_outputs") or {}).items(): try: fm.analog_outputs[int(port)] = AnalogOutput.from_dict(ao) except Exception: pass for port, ai in (d.get("analog_inputs") or {}).items(): try: fm.analog_inputs[int(port)] = AnalogInput.from_dict(ai) except Exception: pass for port, do in (d.get("digital_outputs") or {}).items(): try: fm.digital_outputs[int(port)] = DigitalIO.from_dict(do) except Exception: pass return fm
[docs] @dataclass class ControllerConfig: """Overall OPX Chassis configuration.""" model: str = "opx1000" controller_name: str = "con1" modules: dict[int, FEModuleConfig] = field( default_factory=dict ) # this could support multiple modules in future
[docs] def add_module(self, chasis_slot: int, module: FEModuleConfig) -> None: """Add a front-end module configuration.""" self.modules[chasis_slot] = module
[docs] def to_opx_config(self) -> dict[str, Any]: return { self.controller_name: { "type": self.model, "fems": { slot: module.to_opx_config() for slot, module in self.modules.items() }, } }
[docs] def to_dict(self) -> dict[str, Any]: return { "model": self.model, "controller_name": self.controller_name, "modules": { slot: module.to_dict() for slot, module in self.modules.items() }, }
def __repr__(self) -> str: return f"<Controller {self.controller_name} model={self.model} modules={len(self.modules)}>"
[docs] @classmethod def from_dict(cls, d: dict[str, Any]) -> "ControllerConfig": cc = cls( model=d.get("model", "opx1000"), controller_name=d.get("controller_name", "con1"), ) for slot, md in (d.get("modules") or {}).items(): try: cc.modules[int(slot)] = FEModuleConfig.from_dict(md) except Exception: pass return cc