From a1bf3df72c123aa3b95e70f2b60889f04e8d5da2 Mon Sep 17 00:00:00 2001 From: jupfi Date: Sat, 29 Jul 2023 16:34:03 +0200 Subject: [PATCH 1/4] Added check for valid function expression. --- src/nqrduck_spectrometer/pulseparameters.py | 22 ++++++++++++++++----- src/nqrduck_spectrometer/pulsesequence.py | 13 ++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index 662c456..92a5ba6 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -21,7 +21,7 @@ class Function: def __init__(self, expr) -> None: self.parameters = [] self.expr = expr - self.resolution = 16/30.72e6 + self.resolution = 1/30.72e6 self.start_x = -1 self.end_x = 1 @@ -38,10 +38,6 @@ class Function: t = np.linspace(self.start_x, self.end_x, n) x = sympy.symbols("x") - # If the expression is a string, convert it to a sympy expression - if isinstance(self.expr, str): - self.expr = sympy.sympify(self.expr) - found_variables = dict() # Create a dictionary of the parameters and their values for parameter in self.parameters: @@ -110,6 +106,22 @@ class Function: obj.add_parameter(Function.Parameter.from_json(parameter)) return obj + + @property + def expr(self): + return self._expr + + @expr.setter + def expr(self, expr): + if isinstance(expr, str): + try: + self._expr = sympy.sympify(expr) + except: + logger.error("Could not convert %s to a sympy expression", expr) + raise SyntaxError("Could not convert %s to a sympy expression" % expr) + elif isinstance(expr, sympy.Expr): + self._expr = expr + class Parameter: def __init__(self, name: str, symbol: str, value: float) -> None: diff --git a/src/nqrduck_spectrometer/pulsesequence.py b/src/nqrduck_spectrometer/pulsesequence.py index f35a05d..882130e 100644 --- a/src/nqrduck_spectrometer/pulsesequence.py +++ b/src/nqrduck_spectrometer/pulsesequence.py @@ -102,3 +102,16 @@ class PulseSequence: obj.events.append(cls.Event.load_event(event_data, pulse_parameter_options)) return obj + + class Variable: + """ A variable is a parameter that can be used within a pulsesequence as a placeholder. + For example the event duration a Variable with name a can be set. This variable can then be set to a list of different values. + On execution of the pulse sequence the event duration will be set to the first value in the list. + Then the pulse sequence will be executed with the second value of the list. This is repeated until the pulse sequence has + been executed with all values in the list.""" + pass + + class VariableGroup: + """ Variables can be grouped together. + If we have groups a and b the pulse sequence will be executed for all combinations of variables in a and b.""" + pass \ No newline at end of file From 05e8812204c1589bfca6dd41be19decc93b2c1e8 Mon Sep 17 00:00:00 2001 From: jupfi Date: Sat, 29 Jul 2023 16:56:16 +0200 Subject: [PATCH 2/4] Added check for valid event duration. --- src/nqrduck_spectrometer/pulseparameters.py | 2 ++ src/nqrduck_spectrometer/pulsesequence.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index 92a5ba6..b48829a 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -60,6 +60,7 @@ class Function: mpl_widget.canvas.ax.plot(xdf, ydf) mpl_widget.canvas.ax.set_xlabel("Frequency in Hz") mpl_widget.canvas.ax.set_ylabel("Magnitude") + mpl_widget.canvas.ax.grid(True) return mpl_widget def time_domain_plot(self, pulse_length: float) -> MplWidget: @@ -68,6 +69,7 @@ class Function: mpl_widget.canvas.ax.plot(td, self.evaluate(pulse_length)) mpl_widget.canvas.ax.set_xlabel("Time in s") mpl_widget.canvas.ax.set_ylabel("Magnitude") + mpl_widget.canvas.ax.grid(True) return mpl_widget def get_pulse_amplitude(self, pulse_length: float) -> np.array: diff --git a/src/nqrduck_spectrometer/pulsesequence.py b/src/nqrduck_spectrometer/pulsesequence.py index 882130e..9c60f00 100644 --- a/src/nqrduck_spectrometer/pulsesequence.py +++ b/src/nqrduck_spectrometer/pulsesequence.py @@ -62,6 +62,23 @@ class PulseSequence: ) return obj + + @property + def duration(self): + return self._duration + + @duration.setter + def duration(self, duration : float): + # Duration needs to be a positive number + try: + duration = float(duration) + except ValueError: + raise ValueError("Duration needs to be a number") + if duration < 0: + raise ValueError("Duration needs to be a positive number") + + self._duration = duration + def to_json(self): """Returns a dict with all the data in the pulse sequence From b36b8a7269acc8c49d9a18a389acad1076503e95 Mon Sep 17 00:00:00 2001 From: jupfi Date: Sat, 29 Jul 2023 17:12:01 +0200 Subject: [PATCH 3/4] Switchted to decimals for event duration. --- src/nqrduck_spectrometer/pulsesequence.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nqrduck_spectrometer/pulsesequence.py b/src/nqrduck_spectrometer/pulsesequence.py index 9c60f00..c84372a 100644 --- a/src/nqrduck_spectrometer/pulsesequence.py +++ b/src/nqrduck_spectrometer/pulsesequence.py @@ -1,5 +1,6 @@ import logging from collections import OrderedDict +from nqrduck.helpers.unitconverter import UnitConverter from nqrduck_spectrometer.pulseparameters import Option logger = logging.getLogger(__name__) @@ -18,7 +19,7 @@ class PulseSequence: class Event: """An event is a part of a pulse sequence. It has a name and a duration and different parameters that have to be set.""" - def __init__(self, name: str, duration: float) -> None: + def __init__(self, name: str, duration: str) -> None: self.parameters = OrderedDict() self.name = name self.duration = duration @@ -26,7 +27,7 @@ class PulseSequence: def add_parameter(self, parameter) -> None: self.parameters.append(parameter) - def on_duration_changed(self, duration: float) -> None: + def on_duration_changed(self, duration: str) -> None: logger.debug("Duration of event %s changed to %s", self.name, duration) self.duration = duration @@ -68,10 +69,10 @@ class PulseSequence: return self._duration @duration.setter - def duration(self, duration : float): + def duration(self, duration : str): # Duration needs to be a positive number try: - duration = float(duration) + duration = UnitConverter.to_decimal(duration) except ValueError: raise ValueError("Duration needs to be a number") if duration < 0: From f5cc56d8a7a1a245d43acd7cbfe0bf6a6c72b62a Mon Sep 17 00:00:00 2001 From: jupfi Date: Sat, 29 Jul 2023 18:07:46 +0200 Subject: [PATCH 4/4] Changed to decimals. --- src/nqrduck_spectrometer/pulseparameters.py | 56 ++++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/nqrduck_spectrometer/pulseparameters.py b/src/nqrduck_spectrometer/pulseparameters.py index b48829a..d6fdad7 100644 --- a/src/nqrduck_spectrometer/pulseparameters.py +++ b/src/nqrduck_spectrometer/pulseparameters.py @@ -1,6 +1,7 @@ import logging import numpy as np import sympy +from decimal import Decimal from pathlib import Path from PyQt6.QtGui import QPixmap from nqrduck.contrib.mplwidget import MplWidget @@ -14,25 +15,25 @@ class Function: name: str parameters: list expression: str | sympy.Expr - resolution: float + resolution: Decimal start_x: float end_x: float def __init__(self, expr) -> None: self.parameters = [] self.expr = expr - self.resolution = 1/30.72e6 + self.resolution = Decimal(1/30.72e6) self.start_x = -1 self.end_x = 1 - def get_time_points(self, pulse_length: float) -> np.ndarray: + def get_time_points(self, pulse_length: Decimal) -> np.ndarray: """Returns the time domain points for the function with the given pulse length.""" # Get the time domain points n = int(pulse_length / self.resolution) - t = np.linspace(0, pulse_length, n) + t = np.linspace(0, float(pulse_length), n) return t - def evaluate(self, pulse_length: float) -> np.ndarray: + def evaluate(self, pulse_length: Decimal) -> np.ndarray: """Evaluates the function for the given pulse length.""" n = int(pulse_length / self.resolution) t = np.linspace(self.start_x, self.end_x, n) @@ -52,7 +53,7 @@ class Function: return f(t) - def frequency_domain_plot(self, pulse_length: float) -> MplWidget: + def frequency_domain_plot(self, pulse_length: Decimal) -> MplWidget: mpl_widget = MplWidget() td = self.get_time_points(pulse_length) yd = self.evaluate(pulse_length) @@ -63,7 +64,7 @@ class Function: mpl_widget.canvas.ax.grid(True) return mpl_widget - def time_domain_plot(self, pulse_length: float) -> MplWidget: + def time_domain_plot(self, pulse_length: Decimal) -> MplWidget: mpl_widget = MplWidget() td = self.get_time_points(pulse_length) mpl_widget.canvas.ax.plot(td, self.evaluate(pulse_length)) @@ -72,7 +73,7 @@ class Function: mpl_widget.canvas.ax.grid(True) return mpl_widget - def get_pulse_amplitude(self, pulse_length: float) -> np.array: + def get_pulse_amplitude(self, pulse_length: Decimal) -> np.array: """Returns the pulse amplitude in the time domain.""" return self.evaluate(pulse_length) @@ -124,6 +125,45 @@ class Function: elif isinstance(expr, sympy.Expr): self._expr = expr + @property + def resolution(self): + """ The resolution of the function in seconds.""" + return self._resolution + + @resolution.setter + def resolution(self, resolution): + try: + self._resolution = Decimal(resolution) + except: + logger.error("Could not convert %s to a decimal", resolution) + raise SyntaxError("Could not convert %s to a decimal" % resolution) + + @property + def start_x(self): + """ The x value where the evalution of the function starts.""" + return self._start_x + + @start_x.setter + def start_x(self, start_x): + try: + self._start_x = float(start_x) + except: + logger.error("Could not convert %s to a float", start_x) + raise SyntaxError("Could not convert %s to a float" % start_x) + + @property + def end_x(self): + """ The x value where the evalution of the function ends.""" + return self._end_x + + @end_x.setter + def end_x(self, end_x): + try: + self._end_x = float(end_x) + except: + logger.error("Could not convert %s to a float", end_x) + raise SyntaxError("Could not convert %s to a float" % end_x) + class Parameter: def __init__(self, name: str, symbol: str, value: float) -> None: