Implemented saving and loading of sequences.

This commit is contained in:
jupfi 2023-07-23 17:52:21 +02:00
parent 26a2a85828
commit 46c167841b
3 changed files with 154 additions and 59 deletions

View file

@ -43,17 +43,23 @@ class BaseSpectrometerModel(ModuleModel):
""" """
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
self.options = OrderedDict() self.options = list()
def get_pixmap(self): def get_pixmap(self):
raise NotImplementedError raise NotImplementedError
def add_option(self, name, option): def add_option(self, option):
self.options[name] = option self.options.append(option)
def get_options(self): def get_options(self):
return self.options return self.options
def get_option_by_name(self, name : str) -> "Option":
for option in self.options:
if option.name == name:
return option
raise ValueError("Option with name %s not found" % name)
def __init__(self, module): def __init__(self, module):
super().__init__(module) super().__init__(module)

View file

@ -9,9 +9,10 @@ from .base_spectrometer_model import BaseSpectrometerModel
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Function():
class Function:
name: str name: str
parameters : list parameters: list
expression: str | sympy.Expr expression: str | sympy.Expr
resolution: float resolution: float
start_x: float start_x: float
@ -20,17 +21,17 @@ class Function():
def __init__(self, expr) -> None: def __init__(self, expr) -> None:
self.parameters = [] self.parameters = []
self.expr = expr self.expr = expr
self.resolution = 22e-9 * 16# 1e-6 self.resolution = 22e-9 * 16 # 1e-6
self.start_x = -1 self.start_x = -1
self.end_x = 1 self.end_x = 1
def get_time_points(self, pulse_length : float) -> np.ndarray: def get_time_points(self, pulse_length: float) -> np.ndarray:
"""Returns the time domain points for the function with the given pulse length.""" """Returns the time domain points for the function with the given pulse length."""
# Get the time domain points # Get the time domain points
n = int(pulse_length / self.resolution) n = int(pulse_length / self.resolution)
t = np.linspace(0, pulse_length, n) t = np.linspace(0, pulse_length, n)
return t return t
def evaluate(self, pulse_length: float) -> np.ndarray: def evaluate(self, pulse_length: float) -> np.ndarray:
"""Evaluates the function for the given pulse length.""" """Evaluates the function for the given pulse length."""
n = int(pulse_length / self.resolution) n = int(pulse_length / self.resolution)
@ -47,15 +48,15 @@ class Function():
found_variables[parameter.symbol] = parameter.value found_variables[parameter.symbol] = parameter.value
final_expr = self.expr.subs(found_variables) final_expr = self.expr.subs(found_variables)
# If the expression is a number (does not depend on x), return an array of that number # If the expression is a number (does not depend on x), return an array of that number
if final_expr.is_number: if final_expr.is_number:
return np.full(t.shape, float(final_expr)) return np.full(t.shape, float(final_expr))
f = sympy.lambdify([x], final_expr, "numpy") f = sympy.lambdify([x], final_expr, "numpy")
return f(t) return f(t)
def frequency_domain_plot(self, pulse_length : float) -> MplWidget: def frequency_domain_plot(self, pulse_length: float) -> MplWidget:
mpl_widget = MplWidget() mpl_widget = MplWidget()
td = self.get_time_points(pulse_length) td = self.get_time_points(pulse_length)
yd = self.evaluate(pulse_length) yd = self.evaluate(pulse_length)
@ -65,37 +66,83 @@ class Function():
mpl_widget.canvas.ax.set_ylabel("Magnitude") mpl_widget.canvas.ax.set_ylabel("Magnitude")
return mpl_widget return mpl_widget
def time_domain_plot(self, pulse_length : float) -> MplWidget: def time_domain_plot(self, pulse_length: float) -> MplWidget:
mpl_widget = MplWidget() mpl_widget = MplWidget()
td = self.get_time_points(pulse_length) td = self.get_time_points(pulse_length)
mpl_widget.canvas.ax.plot(td, self.evaluate(pulse_length)) mpl_widget.canvas.ax.plot(td, self.evaluate(pulse_length))
mpl_widget.canvas.ax.set_xlabel("Time in s") mpl_widget.canvas.ax.set_xlabel("Time in s")
mpl_widget.canvas.ax.set_ylabel("Magnitude") mpl_widget.canvas.ax.set_ylabel("Magnitude")
return mpl_widget return mpl_widget
def add_parameter(self, parameter : "Function.Parameter"): def add_parameter(self, parameter: "Function.Parameter"):
self.parameters.append(parameter) self.parameters.append(parameter)
def to_json(self):
return {
"name": self.name,
"parameters": [parameter.to_json() for parameter in self.parameters],
"expression": str(self.expr),
"resolution": self.resolution,
"start_x": self.start_x,
"end_x": self.end_x,
}
@classmethod
def from_json(cls, data):
for subclass in cls.__subclasses__():
if subclass.name == data["name"]:
cls = subclass
break
obj = cls()
obj.expr = data["expression"]
obj.name = data["name"]
obj.resolution = data["resolution"]
obj.start_x = data["start_x"]
obj.end_x = data["end_x"]
obj.parameters = []
for parameter in data["parameters"]:
obj.add_parameter(Function.Parameter.from_json(parameter))
return obj
class Parameter: class Parameter:
def __init__(self, name : str, symbol : str, value : float) -> None: def __init__(self, name: str, symbol: str, value: float) -> None:
self.name = name self.name = name
self.symbol = symbol self.symbol = symbol
self.value = value self.value = value
self.default = value self.default = value
def set_value(self, value : float): def set_value(self, value: float):
self.value = value self.value = value
logger.debug("Parameter %s set to %s", self.name, self.value) logger.debug("Parameter %s set to %s", self.name, self.value)
def to_json(self):
return {
"name": self.name,
"symbol": self.symbol,
"value": self.value,
"default": self.default,
}
@classmethod
def from_json(cls, data):
obj = cls(data["name"], data["symbol"], data["value"])
obj.default = data["default"]
return obj
class RectFunction(Function): class RectFunction(Function):
name = "Rectangular" name = "Rectangular"
def __init__(self) -> None: def __init__(self) -> None:
expr = sympy.sympify("1") expr = sympy.sympify("1")
super().__init__(expr) super().__init__(expr)
class SincFunction(Function): class SincFunction(Function):
name = "Sinc" name = "Sinc"
def __init__(self) -> None: def __init__(self) -> None:
expr = sympy.sympify("sin(x * l)/ (x * l)") expr = sympy.sympify("sin(x * l)/ (x * l)")
super().__init__(expr) super().__init__(expr)
@ -103,36 +150,64 @@ class SincFunction(Function):
self.start_x = -np.pi self.start_x = -np.pi
self.end_x = np.pi self.end_x = np.pi
class GaussianFunction(Function): class GaussianFunction(Function):
name = "Gaussian" name = "Gaussian"
def __init__(self) -> None: def __init__(self) -> None:
expr = sympy.sympify("exp(-0.5 * ((x - mu) / sigma)**2)") expr = sympy.sympify("exp(-0.5 * ((x - mu) / sigma)**2)")
super().__init__(expr) super().__init__(expr)
self.add_parameter(Function.Parameter("Mean", "mu", 0)) self.add_parameter(Function.Parameter("Mean", "mu", 0))
self.add_parameter(Function.Parameter("Standard Deviation", "sigma", 1)) self.add_parameter(Function.Parameter("Standard Deviation", "sigma", 1))
self.start_x = -np.pi
self.end_x = np.pi
#class TriangleFunction(Function):
# class TriangleFunction(Function):
# def __init__(self) -> None: # def __init__(self) -> None:
# expr = sympy.sympify("triang(x)") # expr = sympy.sympify("triang(x)")
# super().__init__(lambda x: triang(x)) # super().__init__(lambda x: triang(x))
class CustomFunction(Function): class CustomFunction(Function):
name = "Custom"
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() expr = sympy.sympify(" 2 * x**2 + 3 * x + 1")
super().__init__(expr)
class Option: class Option:
"""Defines options for the pulse parameters which can then be set accordingly.""" """Defines options for the pulse parameters which can then be set accordingly."""
def __init__(self, name: str, value) -> None:
self.name = name
self.value = value
def set_value(self): def set_value(self):
raise NotImplementedError raise NotImplementedError
def to_json(self):
return {"name": self.name, "value": self.value, "type": self.TYPE}
@classmethod
def from_json(cls, data) -> "Option":
for subclass in cls.__subclasses__():
if subclass.TYPE == data["type"]:
cls = subclass
break
# Check if from_json is implemented for the subclass
if cls.from_json.__func__ == Option.from_json.__func__:
obj = cls(data["name"], data["value"])
else:
obj = cls.from_json(data)
return obj
class BooleanOption(Option): class BooleanOption(Option):
"""Defines a boolean option for a pulse parameter option.""" """Defines a boolean option for a pulse parameter option."""
TYPE = "Boolean"
def __init__(self, value) -> None:
super().__init__()
self.value = value
def set_value(self, value): def set_value(self, value):
self.value = value self.value = value
@ -140,10 +215,7 @@ class BooleanOption(Option):
class NumericOption(Option): class NumericOption(Option):
"""Defines a numeric option for a pulse parameter option.""" """Defines a numeric option for a pulse parameter option."""
TYPE = "Numeric"
def __init__(self, value) -> None:
super().__init__()
self.value = value
def set_value(self, value): def set_value(self, value):
self.value = float(value) self.value = float(value)
@ -152,63 +224,82 @@ class NumericOption(Option):
class FunctionOption(Option): class FunctionOption(Option):
"""Defines a selection option for a pulse parameter option. """Defines a selection option for a pulse parameter option.
It takes different function objects.""" It takes different function objects."""
TYPE = "Function"
def __init__(self, functions) -> None:
super().__init__() def __init__(self, name, functions) -> None:
super().__init__(name, functions[0])
self.functions = functions self.functions = functions
self.value = functions[0]
def set_value(self, value): def set_value(self, value):
self.value = value self.value = value
def get_function_by_name(self, name):
for function in self.functions:
if function.name == name:
return function
raise ValueError("Function with name %s not found" % name)
def to_json(self):
return {"name": self.name, "value": self.value.to_json(), "type": self.TYPE}
@classmethod
def from_json(cls, data):
functions = [function() for function in Function.__subclasses__()]
obj = cls(data["name"], functions)
obj.value = Function.from_json(data["value"])
return obj
class TXPulse(BaseSpectrometerModel.PulseParameter): class TXPulse(BaseSpectrometerModel.PulseParameter):
RELATIVE_AMPLITUDE = "Relative TX Amplitude"
TX_PHASE = "TX Phase"
TX_PULSE_SHAPE = "TX Pulse Shape"
def __init__(self, name) -> None: def __init__(self, name) -> None:
super().__init__(name) super().__init__(name)
self.add_option("TX Amplitude", NumericOption(0)) self.add_option(NumericOption(self.RELATIVE_AMPLITUDE, 0))
self.add_option("TX Phase", NumericOption(0)) self.add_option(NumericOption(self.TX_PHASE, 0))
self.add_option("TX Pulse Shape", FunctionOption([RectFunction(), SincFunction(), GaussianFunction()])) self.add_option(
FunctionOption(self.TX_PULSE_SHAPE, [RectFunction(), SincFunction(), GaussianFunction()]),
)
def get_pixmap(self): def get_pixmap(self):
self_path = Path(__file__).parent self_path = Path(__file__).parent
if self.options["TX Amplitude"].value > 0: if self.get_option_by_name(self.RELATIVE_AMPLITUDE).value > 0:
image_path = self_path / "resources/pulseparameter/TXOn.png" image_path = self_path / "resources/pulseparameter/TXOn.png"
else: else:
image_path = self_path / "resources/pulseparameter/TXOff.png" image_path = self_path / "resources/pulseparameter/TXOff.png"
pixmap = QPixmap(str(image_path)) pixmap = QPixmap(str(image_path))
return pixmap return pixmap
class RXReadout(BaseSpectrometerModel.PulseParameter): class RXReadout(BaseSpectrometerModel.PulseParameter):
RX = "RX"
def __init__(self, name) -> None: def __init__(self, name) -> None:
super().__init__(name) super().__init__(name)
self.add_option("RX", BooleanOption(False)) self.add_option(BooleanOption(self.RX, False))
def get_pixmap(self): def get_pixmap(self):
self_path = Path(__file__).parent self_path = Path(__file__).parent
if self.options["RX"].value == False: if self.get_option_by_name(self.RX).value == False:
image_path = self_path / "resources/pulseparameter/RXOff.png" image_path = self_path / "resources/pulseparameter/RXOff.png"
else: else:
image_path = self_path / "resources/pulseparameter/RXOn.png" image_path = self_path / "resources/pulseparameter/RXOn.png"
pixmap = QPixmap(str(image_path)) pixmap = QPixmap(str(image_path))
return pixmap return pixmap
def set_options(self, options):
self.state = options
class Gate(BaseSpectrometerModel.PulseParameter): class Gate(BaseSpectrometerModel.PulseParameter):
GATE_STATE = "Gate State"
def __init__(self, name) -> None: def __init__(self, name) -> None:
super().__init__(name) super().__init__(name)
self.add_option("Gate State", BooleanOption(False)) self.add_option(BooleanOption(self.GATE_STATE, False))
def get_pixmap(self): def get_pixmap(self):
self_path = Path(__file__).parent self_path = Path(__file__).parent
if self.options["Gate State"].state == False: if self.get_option_by_name(self.GATE_STATE).value == False:
image_path = self_path / "resources/pulseparameter/GateOff.png" image_path = self_path / "resources/pulseparameter/GateOff.png"
else: else:
image_path = self_path / "resources/pulseparameter/GateOn.png" image_path = self_path / "resources/pulseparameter/GateOn.png"
pixmap = QPixmap(str(image_path)) pixmap = QPixmap(str(image_path))
return pixmap return pixmap
def set_options(self, options):
self.state = options

View file

@ -1,5 +1,6 @@
import logging import logging
from collections import OrderedDict from collections import OrderedDict
from nqrduck_spectrometer.pulseparameters import Option
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -47,20 +48,22 @@ class PulseSequence:
for pulse_parameter_option in pulse_parameter_options.keys(): for pulse_parameter_option in pulse_parameter_options.keys():
# This checks if the pulse paramter options are the same as the ones in the pulse sequence # This checks if the pulse paramter options are the same as the ones in the pulse sequence
if pulse_parameter_option == parameter["name"]: if pulse_parameter_option == parameter["name"]:
pulse_paramter_class = pulse_parameter_options[ pulse_parameter_class = pulse_parameter_options[
pulse_parameter_option pulse_parameter_option
] ]
obj.parameters[pulse_parameter_option] = pulse_paramter_class( obj.parameters[pulse_parameter_option] = pulse_parameter_class(
parameter["name"] parameter["name"]
) )
# Delete the default instances of the pulse parameter options
obj.parameters[pulse_parameter_option].options = []
for option in parameter["value"]: for option in parameter["value"]:
obj.parameters[pulse_parameter_option].options[ obj.parameters[pulse_parameter_option].options.append(
option["name"] Option.from_json(option)
].value = option["value"] )
return obj return obj
def dump_sequence_data(self): def to_json(self):
"""Returns a dict with all the data in the pulse sequence """Returns a dict with all the data in the pulse sequence
Returns: Returns:
@ -74,13 +77,8 @@ class PulseSequence:
} }
for parameter in event.parameters.keys(): for parameter in event.parameters.keys():
event_data["parameters"].append({"name": parameter, "value": []}) event_data["parameters"].append({"name": parameter, "value": []})
for option in event.parameters[parameter].options.keys(): for option in event.parameters[parameter].options:
event_data["parameters"][-1]["value"].append( event_data["parameters"][-1]["value"].append(option.to_json())
{
"name": option,
"value": event.parameters[parameter].options[option].value,
}
)
data["events"].append(event_data) data["events"].append(event_data)
return data return data