Merge branch 'main' of github.com:nqrduck/nqrduck-spectrometer

This commit is contained in:
jupfi 2023-07-30 12:44:23 +02:00
commit 44b8b84823
2 changed files with 99 additions and 14 deletions

View file

@ -1,6 +1,7 @@
import logging import logging
import numpy as np import numpy as np
import sympy import sympy
from decimal import Decimal
from pathlib import Path from pathlib import Path
from PyQt6.QtGui import QPixmap from PyQt6.QtGui import QPixmap
from nqrduck.contrib.mplwidget import MplWidget from nqrduck.contrib.mplwidget import MplWidget
@ -14,34 +15,30 @@ class Function:
name: str name: str
parameters: list parameters: list
expression: str | sympy.Expr expression: str | sympy.Expr
resolution: float resolution: Decimal
start_x: float start_x: float
end_x: float end_x: float
def __init__(self, expr) -> None: def __init__(self, expr) -> None:
self.parameters = [] self.parameters = []
self.expr = expr self.expr = expr
self.resolution = 1/30.72e6 self.resolution = Decimal(1/30.72e6)
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: Decimal) -> 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, float(pulse_length), n)
return t 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.""" """Evaluates the function for the given pulse length."""
n = int(pulse_length / self.resolution) n = int(pulse_length / self.resolution)
t = np.linspace(self.start_x, self.end_x, n) t = np.linspace(self.start_x, self.end_x, n)
x = sympy.symbols("x") 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() found_variables = dict()
# Create a dictionary of the parameters and their values # Create a dictionary of the parameters and their values
for parameter in self.parameters: for parameter in self.parameters:
@ -56,7 +53,7 @@ class Function:
return f(t) return f(t)
def frequency_domain_plot(self, pulse_length: float) -> MplWidget: def frequency_domain_plot(self, pulse_length: Decimal) -> 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)
@ -64,17 +61,19 @@ class Function:
mpl_widget.canvas.ax.plot(xdf, ydf) mpl_widget.canvas.ax.plot(xdf, ydf)
mpl_widget.canvas.ax.set_xlabel("Frequency in Hz") mpl_widget.canvas.ax.set_xlabel("Frequency in Hz")
mpl_widget.canvas.ax.set_ylabel("Magnitude") mpl_widget.canvas.ax.set_ylabel("Magnitude")
mpl_widget.canvas.ax.grid(True)
return mpl_widget return mpl_widget
def time_domain_plot(self, pulse_length: float) -> MplWidget: def time_domain_plot(self, pulse_length: Decimal) -> 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, abs(self.evaluate(pulse_length))) mpl_widget.canvas.ax.plot(td, abs(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")
mpl_widget.canvas.ax.grid(True)
return mpl_widget 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.""" """Returns the pulse amplitude in the time domain."""
return self.evaluate(pulse_length) return self.evaluate(pulse_length)
@ -111,6 +110,61 @@ class Function:
return obj 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
@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: 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

View file

@ -1,5 +1,6 @@
import logging import logging
from collections import OrderedDict from collections import OrderedDict
from nqrduck.helpers.unitconverter import UnitConverter
from nqrduck_spectrometer.pulseparameters import Option from nqrduck_spectrometer.pulseparameters import Option
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -18,7 +19,7 @@ class PulseSequence:
class Event: 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.""" """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.parameters = OrderedDict()
self.name = name self.name = name
self.duration = duration self.duration = duration
@ -26,7 +27,7 @@ class PulseSequence:
def add_parameter(self, parameter) -> None: def add_parameter(self, parameter) -> None:
self.parameters.append(parameter) 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) logger.debug("Duration of event %s changed to %s", self.name, duration)
self.duration = duration self.duration = duration
@ -63,6 +64,23 @@ class PulseSequence:
return obj return obj
@property
def duration(self):
return self._duration
@duration.setter
def duration(self, duration : str):
# Duration needs to be a positive number
try:
duration = UnitConverter.to_decimal(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): 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
@ -102,3 +120,16 @@ class PulseSequence:
obj.events.append(cls.Event.load_event(event_data, pulse_parameter_options)) obj.events.append(cls.Event.load_event(event_data, pulse_parameter_options))
return obj 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