Source code for qeg_nmr_qua.config.pulse

from dataclasses import dataclass, field
from typing import Dict, Optional, Any, Type, TypeVar, Literal
from qeg_nmr_qua.config.integration import IntegrationWeightMapping


[docs] @dataclass class ControlPulse: """Configuration for a control (drive) pulse. Defines a pulse used to drive/manipulate quantum states. Control pulses output a waveform to an element and may optionally trigger a digital marker for synchronization or external monitoring. **Current Limitations:** - Single waveform only (no waveform mixing) - See :class:`MeasPulse` for measurement pulses with integration. Attributes: length (int): Pulse duration in nanoseconds (default: 0). Must be a multiple of 4 ns (1 clock cycle). waveform (str): Name of the waveform to play (default: "zero_wf"). Must be defined in the waveform configuration. digital_marker (Literal["ON", "OFF"] | None): Whether to set a digital marker during pulse execution (default: "OFF"). "ON" activates the marker, "OFF" deactivates it. Use None for no marker. """ length: int = 0 # in nanoseconds waveform: str = "zero_wf" # waveform name digital_marker: Literal["ON", "OFF"] | None = ( "OFF" # set digital marker during pulse )
[docs] def to_opx_config(self) -> Dict[str, Any]: return { "operation": "control", "length": self.length, "waveforms": {"single": self.waveform}, "digital_marker": self.digital_marker, }
[docs] def to_dict(self) -> Dict[str, Any]: return { "type": "control", "length": self.length, "waveform": self.waveform, "digital_marker": self.digital_marker, }
[docs] @classmethod def from_dict(cls, d: Dict[str, Any]) -> "ControlPulse": return cls( length=d.get("length", 0), waveform=d.get("waveform", "zero_wf"), digital_marker=d.get("digital_marker", "OFF"), )
def __repr__(self) -> str: return f"<ControlPulse len={self.length} wf={self.waveform} marker={self.digital_marker}>"
[docs] @dataclass class MeasPulse: """Configuration for a measurement pulse. Defines a pulse used for measurement/readout of quantum states. Measurement pulses output a waveform, acquire data, and apply integration weights for demodulation and signal extraction. Optionally triggers a digital marker. **Current Limitations:** - Single waveform only (no waveform mixing) - See :class:`ControlPulse` for simple control pulses. **Demodulation:** Integration weights are applied to the acquired signal to extract in-phase (I) and quadrature (Q) components. Common weight sets include cosine/sine, rotated, and optimized variants. Attributes: length (int): Measurement duration in nanoseconds (default: 1000). Must be a multiple of 4 ns (1 clock cycle). waveform (str): Name of the readout waveform (default: "readout_wf"). Must be defined in the waveform configuration. integration_weights (IntegrationWeightMapping): Mapping of demodulation weights for extracting I/Q components (default: default mapping). digital_marker (Literal["ON", "OFF"] | None): Whether to trigger a digital marker during measurement (default: None). None means no marker action. """ length: int = 1000 # in nanoseconds waveform: str = "readout_wf" # waveform name integration_weights: IntegrationWeightMapping = field( default_factory=IntegrationWeightMapping ) digital_marker: Literal["ON", "OFF"] | None = ( None # set digital marker during pulse )
[docs] def to_opx_config(self) -> Dict[str, Any]: dct = { "operation": "measurement", "length": self.length, "waveforms": {"single": self.waveform}, "integration_weights": self.integration_weights.to_opx_config(), } if self.digital_marker is not None: dct["digital_marker"] = self.digital_marker return dct
[docs] def to_dict(self) -> Dict[str, Any]: return { "type": "measurement", "length": self.length, "waveform": self.waveform, "integration_weights": self.integration_weights.to_dict(), "digital_marker": self.digital_marker, }
[docs] @classmethod def from_dict(cls, d: Dict[str, Any]) -> "MeasPulse": from qeg_nmr_qua.config.integration import IntegrationWeightMapping iw = IntegrationWeightMapping() if isinstance(d.get("integration_weights"), dict): vals = d.get("integration_weights", {}) iw = IntegrationWeightMapping() for k, v in vals.items(): if hasattr(iw, k): setattr(iw, k, v) return cls( length=d.get("length", 1000), waveform=d.get("waveform", "readout_wf"), integration_weights=iw, digital_marker=d.get("digital_marker"), )
def __repr__(self) -> str: # integration_weights may be an IntegrationWeights (has .weights) or a # IntegrationWeightMapping (has .to_dict()). Be robust. try: n = len(self.integration_weights.weights) except Exception: try: n = len(self.integration_weights.to_dict()) except Exception: n = 0 iw_summary = f"weights={n}" return f"<MeasPulse len={self.length} wf={self.waveforms} {iw_summary} marker={self.digital_marker}>"
[docs] @dataclass class PulseConfig: """ Configuration for a pulse, which can be either a control pulse or a measurement. """ pulses: dict[str, ControlPulse | MeasPulse] = field(default_factory=dict)
[docs] def add_pulse( self, name: str, pulse: ControlPulse | MeasPulse, ) -> None: """ Add a pulse configuration from one of the pulse types. Args: name: Name of the pulse. pulse: ControlPulse or MeasPulse instance. """ self.pulses[name] = pulse
[docs] def add_control_pulse( self, name: str, length: int, waveform: str = "const", digital_marker: Literal["ON", "OFF"] | None = "OFF", ) -> None: """ Add a control pulse configuration. Args: name: Name of the control pulse. length: Pulse length in nanoseconds. waveform: Waveform name. digital_marker: Whether to set the digital marker during the pulse. """ self.pulses[name] = ControlPulse( length=length, waveform=waveform, digital_marker=digital_marker, )
[docs] def add_measurement_pulse( self, name: str, length: int, waveform: str = "readout_wf", digital_marker: Literal["ON", "OFF"] | None = None, integration_weights: IntegrationWeightMapping = IntegrationWeightMapping(), ) -> None: """ Add a measurement pulse configuration. Args: name: Name of the measurement pulse. length: Pulse length in nanoseconds. waveform: Waveform name. integration_weights: Integration weight mapping. """ self.pulses[name] = MeasPulse( length=length, waveform=waveform, integration_weights=integration_weights, digital_marker=digital_marker, )
[docs] def to_dict(self) -> Dict[str, Any]: return {name: pulse.to_dict() for name, pulse in self.pulses.items()}
[docs] @classmethod def from_dict(cls, d: Dict[str, Any]) -> "PulseConfig": pc = cls() for name, pd in d.items(): if not isinstance(pd, dict): continue ptype = pd.get("type") if ptype == "control": pulse = ControlPulse.from_dict(pd) elif ptype == "measurement": pulse = MeasPulse.from_dict(pd) else: pulse = ControlPulse.from_dict(pd) pc.pulses[name] = pulse return pc
def __repr__(self) -> str: return f"<PulseConfig pulses={len(self.pulses)}>"
[docs] def to_opx_config(self) -> Dict[str, Any]: return {name: pulse.to_opx_config() for name, pulse in self.pulses.items()}