From 70266387e239a5750965bde12746e55c32b71c55 Mon Sep 17 00:00:00 2001 From: jupfi Date: Wed, 29 May 2024 06:48:01 +0200 Subject: [PATCH] Made sequence parameters editable. --- src/quackseq/event.py | 30 +++-- src/quackseq/helpers.py | 2 + src/quackseq/pulsesequence.py | 119 ++++++++++++++++-- .../spectrometer/spectrometer_model.py | 14 --- tests/simulator.py | 36 +++--- 5 files changed, 154 insertions(+), 47 deletions(-) diff --git a/src/quackseq/event.py b/src/quackseq/event.py index ab8fd64..7a6c46a 100644 --- a/src/quackseq/event.py +++ b/src/quackseq/event.py @@ -1,6 +1,7 @@ import logging from collections import OrderedDict +from quackseq.pulsesequence import PulseSequence from quackseq.pulseparameters import Option from quackseq.helpers import UnitConverter @@ -13,26 +14,37 @@ class Event: Args: name (str): The name of the event duration (str): The duration of the event + pulse_sequence (PulseSequence): The pulse sequence the event belongs to Attributes: name (str): The name of the event duration (str): The duration of the event - parameters (OrderedDict): The parameters of the event + pulse_sequence (PulseSequence): The pulse sequence the event belongs to """ - def __init__(self, name: str, duration: str) -> None: + def __init__(self, name: str, duration: str, pulse_sequence : PulseSequence) -> None: """Initializes the event.""" self.parameters = OrderedDict() self.name = name self.duration = duration + self.pulse_sequence = pulse_sequence + self.parameters = OrderedDict() + self.init_pulse_parameters() - def add_parameter(self, parameter) -> None: - """Adds a parameter to the event. - - Args: - parameter: The parameter to add - """ - self.parameters.append(parameter) + def init_pulse_parameters(self) -> None: + """Initializes the pulse parameters of the event.""" + # Create a default instance of the pulse parameter options and add it to the event + pulse_parameters = self.pulse_sequence.pulse_parameter_options + for name, pulse_parameter_class in pulse_parameters.items(): + logger.debug("Adding pulse parameter %s to event %s", name, self.name) + self.parameters[name] = pulse_parameter_class( + name + ) + logger.debug( + "Created pulse parameter %s with object id %s", + name, + id(self.parameters[name]), + ) def on_duration_changed(self, duration: str) -> None: """This method is called when the duration of the event is changed. diff --git a/src/quackseq/helpers.py b/src/quackseq/helpers.py index 226c385..7580410 100644 --- a/src/quackseq/helpers.py +++ b/src/quackseq/helpers.py @@ -32,6 +32,8 @@ class UnitConverter: return decimal.Decimal(value) except TypeError: return decimal.Decimal(value) + except decimal.InvalidOperation: + raise ValueError(f"Invalid value: {value}, suffixes are n, u, m") @classmethod def to_float(cls, value: str) -> float: diff --git a/src/quackseq/pulsesequence.py b/src/quackseq/pulsesequence.py index 8966426..6497ba4 100644 --- a/src/quackseq/pulsesequence.py +++ b/src/quackseq/pulsesequence.py @@ -2,6 +2,10 @@ import logging import importlib.metadata +from collections import OrderedDict + +from quackseq.pulseparameters import PulseParameter, TXPulse, RXReadout +from quackseq.functions import Function logger = logging.getLogger(__name__) @@ -11,21 +15,36 @@ class PulseSequence: Args: name (str): The name of the pulse sequence + version (str): The version of the pulse sequence Attributes: name (str): The name of the pulse sequence events (list): The events of the pulse sequence + pulse_parameter_options (dict): The pulse parameter options """ - def __init__(self, name, version = None) -> None: + def __init__(self, name: str, version: str = None) -> None: """Initializes the pulse sequence.""" self.name = name - # Saving version to check for compatability of saved sequence + # Saving version to check for compatibility of saved sequence if version is not None: self.version = version else: - self.version = importlib.metadata.version("nqrduck_spectrometer") + self.version = importlib.metadata.version("quackseq") + self.events = list() + self.pulse_parameter_options = OrderedDict() + + def add_pulse_parameter_option( + self, name: str, pulse_parameter_class: "PulseParameter" + ) -> None: + """Adds a pulse parameter option to the spectrometer. + + Args: + name (str) : The name of the pulse parameter + pulse_parameter_class (PulseParameter) : The pulse parameter class + """ + self.pulse_parameter_options[name] = pulse_parameter_class def get_event_names(self) -> list: """Returns a list of the names of the events in the pulse sequence. @@ -34,16 +53,28 @@ class PulseSequence: list: The names of the events """ return [event.name for event in self.events] - - def add_event(self, event_name: str, duration: float) -> None: + + def add_event(self, event: "Event") -> None: """Add a new event to the pulse sequence. + Args: + event (Event): The event to add + """ + self.events.append(event) + + def create_event(self, event_name: str, duration: float) -> "Event": + """Create a new event and return it. + Args: event_name (str): The name of the event duration (float): The duration of the event + + Returns: + Event: The created event """ - self.events.append(self.Event(event_name, f"{float(duration):.16g}u")) - + event = self.Event(event_name, f"{float(duration):.16g}u") + self.events.append(event) + return event def delete_event(self, event_name: str) -> None: """Deletes an event from the pulse sequence. @@ -56,6 +87,8 @@ class PulseSequence: self.events.remove(event) break + # Loading and saving of pulse sequences + def to_json(self): """Returns a dict with all the data in the pulse sequence. @@ -63,7 +96,7 @@ class PulseSequence: dict: The dict with the sequence data """ # Get the versions of this package - data = {"name": self.name, "version" : self.version, "events": []} + data = {"name": self.name, "version": self.version, "events": []} for event in self.events: event_data = { "name": event.name, @@ -95,16 +128,17 @@ class PulseSequence: KeyError: If the pulse parameter options are not the same as the ones in the pulse sequence """ try: - obj = cls(sequence["name"], version = sequence["version"]) + obj = cls(sequence["name"], version=sequence["version"]) except KeyError: logger.error("Pulse sequence version not found") raise KeyError("Pulse sequence version not found") - + for event_data in sequence["events"]: obj.events.append(cls.Event.load_event(event_data, pulse_parameter_options)) return obj + # Automation of pulse sequences class Variable: """A variable is a parameter that can be used within a pulsesequence as a placeholder. @@ -163,3 +197,68 @@ class PulseSequence: if not isinstance(variables, list): raise TypeError("Variables needs to be a list") self._variables = variables + + +class QuackSequence(PulseSequence): + """This is the Pulse Sequence that is compatible with all types of spectrometers. + + If you want to implement your own spectrometer specific pulse sequence, you can inherit from the PulseSequence class. + """ + + TX_PULSE = "TXPulse" + RX_READOUT = "RXParameters" + + def __init__(self, name: str, version: str = None) -> None: + """Initializes the pulse sequence.""" + super().__init__(name, version) + + self.add_pulse_parameter_option(self.TX_PULSE, TXPulse) + self.add_pulse_parameter_option(self.RX_READOUT, RXReadout) + + # TX Specific functions + + def set_tx_amplitude(self, event, amplitude: float) -> None: + """Sets the amplitude of the transmitter. + + Args: + event (Event): The event to set the amplitude for + amplitude (float): The amplitude of the transmitter + """ + event.parameters[self.TX_PULSE].get_option_by_name( + TXPulse.RELATIVE_AMPLITUDE + ).value = amplitude + + def set_tx_phase(self, event, phase: float) -> None: + """Sets the phase of the transmitter. + + Args: + event (Event): The event to set the phase for + phase (float): The phase of the transmitter + """ + event.parameters[self.TX_PULSE].get_option_by_name( + TXPulse.TX_PHASE + ).value = phase + + def set_tx_shape(self, event, shape: Function) -> None: + """Sets the shape of the transmitter. + + Args: + event (Event): The event to set the shape for + shape (Any): The shape of the transmitter + """ + event.parameters[self.TX_PULSE].get_option_by_name( + TXPulse.TX_PULSE_SHAPE + ).value = shape + + # RX Specific functions + + def set_rx(self, event, rx: bool) -> None: + """Sets the receiver on or off. + + Args: + event (Event): The event to set the receiver for + rx (bool): The receiver state + """ + event.parameters[self.RX_READOUT].get_option_by_name( + RXReadout.RX + ).value = rx diff --git a/src/quackseq/spectrometer/spectrometer_model.py b/src/quackseq/spectrometer/spectrometer_model.py index 4a8fa9a..838e3f5 100644 --- a/src/quackseq/spectrometer/spectrometer_model.py +++ b/src/quackseq/spectrometer/spectrometer_model.py @@ -17,11 +17,9 @@ class SpectrometerModel(): Attributes: settings (OrderedDict) : The settings of the spectrometer - pulse_parameter_options (OrderedDict) : The pulse parameter options of the spectrometer """ settings: OrderedDict - pulse_parameter_options: OrderedDict def __init__(self, module): """Initializes the spectrometer model. @@ -31,7 +29,6 @@ class SpectrometerModel(): """ super().__init__(module) self.settings = OrderedDict() - self.pulse_parameter_options = OrderedDict() def add_setting(self, setting: Setting, category: str) -> None: """Adds a setting to the spectrometer. @@ -62,17 +59,6 @@ class SpectrometerModel(): return setting raise ValueError(f"Setting with name {name} not found") - def add_pulse_parameter_option( - self, name: str, pulse_parameter_class: "PulseParameter" - ) -> None: - """Adds a pulse parameter option to the spectrometer. - - Args: - name (str) : The name of the pulse parameter - pulse_parameter_class (PulseParameter) : The pulse parameter class - """ - self.pulse_parameter_options[name] = pulse_parameter_class - @property def target_frequency(self): """The target frequency of the spectrometer in Hz. This is the frequency where the magnetic resonance experiment is performed.""" diff --git a/tests/simulator.py b/tests/simulator.py index 75e750a..2e6f0d5 100644 --- a/tests/simulator.py +++ b/tests/simulator.py @@ -1,32 +1,40 @@ # Dummy test to communicate the structure -from quackseq.pulsesequence import PulseSequence +from quackseq.pulsesequence import QuackSequence from quackseq.event import Event from quackseq.functions import RectFunction -seq = PulseSequence("test") +seq = QuackSequence("test") -tx = Event("tx", "10u") +tx = Event("tx", "10u", seq) -# tx.set_tx_amplitude(1) -#tx.set_tx_phase(0) -#tx.set_shape(RectFunction()) +seq.add_event(tx) -#seq.add_event(tx) +seq.set_tx_amplitude(tx, 1) +seq.set_tx_phase(tx, 0) -#blank = Event("blank", "10u") +rect = RectFunction() -#seq.add_event(blank) +seq.set_tx_shape(tx, rect) -#rx = Event("rx", "10u") +blank = Event("blank", "10u", seq) + +seq.add_event(blank) + +rx = Event("rx", "10u", seq) #rx.set_rx_phase(0) -#rx.set_rx(True) -#seq.add_event(rx) +seq.set_rx(rx, True) -#TR = Event("TR", "1ms") +seq.add_event(rx) -#seq.add_event(TR) +TR = Event("TR", "1m", seq) + +seq.add_event(TR) + +json = seq.to_json() + +print(json) #sim = Simulator()