"""
OPX-1000 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
from collections.abc import Iterable
from qeg_nmr_qua.config.controller import ControllerConfig
from qeg_nmr_qua.config.element import ElementConfig, Element
from qeg_nmr_qua.config.pulse import PulseConfig, ControlPulse, MeasPulse
from qeg_nmr_qua.config.waveform import AnalogWaveformConfig, DigitalWaveformConfig
from qeg_nmr_qua.config.integration import IntegrationWeights
[docs]
@dataclass
class OPXConfig:
"""
Complete configuration for the OPX-1000 low-frequency front-end module.
This is the main configuration class that aggregates all sub-configurations needed
to operate the OPX-1000 for NMR experiments. It manages controllers, elements (physical
connections), pulses, waveforms, digital waveforms, and integration weights.
**Connection Settings:**
- ``qop_ip``: IP address of the Quantum Orchestration Platform (default: "192.168.88.253")
- ``cluster``: Name of the OPX cluster to use (default: "lex")
**Configuration Components:**
Each component is a nested configuration object:
- ``controllers``: :class:`~ControllerConfig` - Hardware controllers (analog/digital I/O)
- ``elements``: :class:`~ElementConfig` - Physical elements (resonators, amplifiers, etc.)
- ``pulses``: :class:`~PulseConfig` - Pulse definitions (control and measurement)
- ``waveforms``: :class:`~AnalogWaveformConfig` - Analog waveform shapes
- ``digital_waveforms``: :class:`~DigitalWaveformConfig` - Digital marker waveforms
- ``integration_weights``: :class:`~IntegrationWeights` - Integration weight sets for demodulation
**Serialization:**
Use :meth:`to_dict` and :meth:`from_dict` for programmatic serialization, or use
:meth:`save_to_file` and :meth:`load_from_file` for JSON file I/O.
**OPX Format:**
Use :meth:`to_opx_config` to generate the configuration in OPX-compatible format
for passing to the Quantum Orchestration Platform.
Example:
>>> config = OPXConfig(qop_ip="192.168.1.100")
>>> config.elements.add_element("resonator", frequency=282.1901e6)
>>> opx_config_dict = config.to_opx_config()
"""
qop_ip: str = "192.168.88.253"
cluster: str = "lex"
controllers: ControllerConfig = field(default_factory=ControllerConfig)
elements: ElementConfig = field(default_factory=ElementConfig)
pulses: PulseConfig = field(default_factory=PulseConfig)
waveforms: AnalogWaveformConfig = field(default_factory=AnalogWaveformConfig)
digital_waveforms: DigitalWaveformConfig = field(
default_factory=DigitalWaveformConfig
)
integration_weights: IntegrationWeights = field(default_factory=IntegrationWeights)
[docs]
def to_dict(self) -> dict[str, Any]:
"""Convert the OPX configuration to a dictionary."""
return {
"qop_ip": self.qop_ip,
"cluster": self.cluster,
"controllers": self.controllers.to_dict(),
"elements": self.elements.to_dict(),
"pulses": self.pulses.to_dict(),
"waveforms": self.waveforms.to_dict(),
"digital_waveforms": self.digital_waveforms.to_dict(),
"integration_weights": self.integration_weights.to_dict(),
}
[docs]
@classmethod
def from_dict(cls, d: dict[str, Any]) -> "OPXConfig":
# Import here to avoid circular imports at module import time
controllers = ControllerConfig.from_dict(d.get("controllers", {}))
elements = ElementConfig.from_dict(d.get("elements", {}))
pulses = PulseConfig.from_dict(d.get("pulses", {}))
waveforms = AnalogWaveformConfig.from_dict(d.get("waveforms", {}))
digital_waveforms = DigitalWaveformConfig.from_dict(
d.get("digital_waveforms", {})
)
integration_weights = IntegrationWeights.from_dict(
d.get("integration_weights", {})
)
return cls(
qop_ip=d.get("qop_ip", "192.168.88.253"),
cluster=d.get("cluster", "lex"),
controllers=controllers,
elements=elements,
pulses=pulses,
waveforms=waveforms,
digital_waveforms=digital_waveforms,
integration_weights=integration_weights,
)
[docs]
def save_to_file(self, filepath: str) -> None:
"""Save the OPXConfig to a JSON file at `filepath`."""
import json
with open(filepath, "w", encoding="utf-8") as f:
json.dump(self.to_dict(), f, indent=2)
[docs]
@classmethod
def load_from_file(cls, filepath: str) -> "OPXConfig":
"""Load an OPXConfig from a JSON file at `filepath`."""
import json
with open(filepath, "r", encoding="utf-8") as f:
d = json.load(f)
return cls.from_dict(d)
[docs]
def to_opx_config(self) -> dict[str, Any]:
"""Convert to OPX configuration format."""
return {
"controllers": self.controllers.to_opx_config(),
"elements": self.elements.to_opx_config(),
"pulses": self.pulses.to_opx_config(),
"waveforms": self.waveforms.to_opx_config(),
"digital_waveforms": self.digital_waveforms.to_opx_config(),
"integration_weights": self.integration_weights.to_opx_config(),
}
[docs]
def add_controller(self, controller_config: ControllerConfig):
"""Add a controller configuration."""
self.controllers = controller_config
[docs]
def add_element(self, name: str, element: Element):
self.elements.add_element(name, element)
[docs]
def add_pulse(self, name: str, pulse: ControlPulse | MeasPulse):
"""Add a pulse configuration."""
self.pulses.add_pulse(name, pulse)
[docs]
def add_control_pulse(self, pulse_name: str, element_name: str, amplitude: float, length: int, waveform: list[float]):
"""
Add a control pulse configuration.
"""
self.pulses.add_control_pulse(
pulse_name, element_name, amplitude, length, waveform
)
[docs]
def add_integration_weight(
self, name: str, length: int, real_weight: float = 1, imag_weight: float = 0
):
"""Add an integration weight configuration."""
self.integration_weights.add_weight(
name, length, real_weight=real_weight, imag_weight=imag_weight
)
def __repr__(self) -> str:
return f"OPXConfig(qop_ip={self.qop_ip}, cluster='{self.cluster}', num_modules={len(self.controllers.modules.keys())}, num_elements={len(self.elements.elements)})"