From 5662a733459ea8ecf65e0fac4caaea03ee1ae6a4 Mon Sep 17 00:00:00 2001 From: Markus Koller Date: Mon, 20 Feb 2023 16:51:30 +0100 Subject: [PATCH] Introduced adaptable rotation rates --- src/user_interface.py | 120 +++++++++++++++++++++++++++++------------- 1 file changed, 83 insertions(+), 37 deletions(-) diff --git a/src/user_interface.py b/src/user_interface.py index 387336b..8caa5bd 100644 --- a/src/user_interface.py +++ b/src/user_interface.py @@ -14,10 +14,13 @@ import matplotlib.pyplot as plt # import general packages: import numpy as np +from scipy.optimize import fsolve import os import os.path from datetime import datetime from math import pi +import warnings +warnings.filterwarnings("error") # import other project files: import src.globals as g @@ -411,9 +414,11 @@ class ExecuteCSVMode(Frame): # Generate CSV sequence variables self.rot_vector_vars = [DoubleVar(value=1), DoubleVar(value=0), DoubleVar(value=0)] self.rot_center_vars = [DoubleVar(value=0), DoubleVar(value=0), DoubleVar(value=0)] - self.rot_mag_vars = StringVar(value=100) - self.rot_rate_vars = StringVar(value=1) - self.time_step_vars = StringVar(value=1) + self.rot_mag_vars = DoubleVar(value=100) + self.rot_time_step_vars = DoubleVar(value=1) + self.rot_cycle_number_vars = IntVar(value=1) + self.rot_rate_vars = [DoubleVar(value=1), DoubleVar(value=0), DoubleVar(value=0), + DoubleVar(value=0), DoubleVar(value=0), DoubleVar(value=0)] # --- UI ELEMENTS --- row_counter = 0 # keep track of which grid row we are in @@ -510,7 +515,8 @@ class ExecuteCSVMode(Frame): pady=5, padx=5) self.generate_load_csv_button.grid(row=0, column=1, padx=5, pady=5) row_counter += 1 - info_text = Label(self, text="Generate data of a circular, counter clockwise motion around an arbitrary axis and strength.", + info_text = Label(self, + text="Generate data of a circular, counter clockwise motion around an arbitrary axis and strength.", padx=100, pady=3) info_text.grid(row=row_counter, column=col_counter, padx=0, sticky=W) row_counter += 1 @@ -549,19 +555,35 @@ class ExecuteCSVMode(Frame): rot_mag_unit = Label(input_csv_gen_frame, text="\u03BCT") rot_mag_unit.grid(row=row_counter, column=4, padx=5, pady=5, sticky="nw") row_counter += 1 - rot_rate_label = Label(input_csv_gen_frame, text="Rotation rate: ") - rot_rate_label.grid(row=row_counter, column=0, padx=5, pady=5, sticky="nw") - rot_rate_data = Entry(input_csv_gen_frame, textvariable=self.rot_rate_vars, width=15) - rot_rate_data.grid(row=row_counter, column=1, padx=5, pady=5, sticky="nw") - rot_rate_unit = Label(input_csv_gen_frame, text="\u00b0/s") - rot_rate_unit.grid(row=row_counter, column=4, padx=5, pady=5, sticky="s") - row_counter += 1 time_step_label = Label(input_csv_gen_frame, text="Time step: ") time_step_label.grid(row=row_counter, column=0, padx=5, pady=5, sticky="nw") - time_step_data = Entry(input_csv_gen_frame, textvariable=self.time_step_vars, width=15) + time_step_data = Entry(input_csv_gen_frame, textvariable=self.rot_time_step_vars, width=15) time_step_data.grid(row=row_counter, column=1, padx=5, pady=5, sticky="nw") time_step_unit = Label(input_csv_gen_frame, text="s") - time_step_unit.grid(row=row_counter, column=4, padx=5, pady=5, sticky="s") + time_step_unit.grid(row=row_counter, column=4, padx=5, pady=5, sticky="nw") + row_counter += 1 + time_step_label = Label(input_csv_gen_frame, text="Cycle number: ") + time_step_label.grid(row=row_counter, column=0, padx=5, pady=5, sticky="nw") + time_step_data = Entry(input_csv_gen_frame, textvariable=self.rot_cycle_number_vars, width=15) + time_step_data.grid(row=row_counter, column=1, padx=5, pady=5, sticky="nw") + time_step_unit = Label(input_csv_gen_frame, text="-") + time_step_unit.grid(row=row_counter, column=4, padx=5, pady=5, sticky="nw") + row_counter += 1 + rot_rate_label = Label(input_csv_gen_frame, text="Rotation rate: ") + rot_rate_label.grid(row=row_counter, column=0, padx=5, pady=5, sticky="nw") + for i in range(3): + for j in range(2): + rot_rate_data = Entry(input_csv_gen_frame, textvariable=self.rot_rate_vars[i + 3 * j], width=15) + rot_rate_data.grid(row=row_counter + j, column=1 + i, padx=5, pady=5, sticky="nw") + rot_rate_unit = Label(input_csv_gen_frame, text="\u00b0/s, \u00b0/s^2, \u00b0/s^3") + rot_rate_unit.grid(row=row_counter, column=4, padx=5, pady=5, sticky="nw") + row_counter += 1 + rot_rate_unit = Label(input_csv_gen_frame, text="\u00b0/s, 1/s, -") + rot_rate_unit.grid(row=row_counter, column=4, padx=5, pady=5, sticky="nw") + row_counter += 1 + + rot_rate_label = Label(input_csv_gen_frame, text="r(t) = r_0 + r_1*t + r_2*t^2 + r3 * sin( r_4*t + r_5 )") + rot_rate_label.grid(row=row_counter, column=1, columnspan=4, padx=5, pady=5, sticky="nw") def page_switch(self): # function that is called when switching to this window # every class in the UI needs this, even if it doesn't do anything @@ -709,12 +731,14 @@ class ExecuteCSVMode(Frame): # Initialize and get vectors rot_vector = [0, 0, 0] rot_center = [0, 0, 0] + r = [0, 0, 0, 0, 0, 0] for i in range(len(rot_vector)): rot_vector[i] = float(self.rot_vector_vars[i].get()) # [-] rot_center[i] = float(self.rot_center_vars[i].get()) # [uT] rot_mag = float(self.rot_mag_vars.get()) # [uT] - rot_rate = float(self.rot_rate_vars.get()) # [deg/s] - rot_time_step = float(self.time_step_vars.get()) # [s] + for i in range(len(r)): + r[i] = float(self.rot_rate_vars[i].get()) # [deg/s, deg/s^2, deg/s^3, deg/s, 1/s, -] + rot_time_step = float(self.rot_time_step_vars.get()) # [s] # Normalize rotation vector if np.linalg.norm(rot_vector) == 0: ui_print("Error: Rotation axis cannot be singular.") @@ -737,22 +761,11 @@ class ExecuteCSVMode(Frame): rot_mag = rot_mag_default self.rot_mag_vars.set("{:.3f}".format(rot_mag)) # Checking center point - if np.sqrt(rot_center[0]**2+rot_center[1]**2+rot_center[2]**2) > rot_center_geom_mean_max: + if np.sqrt(rot_center[0] ** 2 + rot_center[1] ** 2 + rot_center[2] ** 2) > rot_center_geom_mean_max: ui_print("Warning: Rotation center excessive. Setting to zero automatically.") rot_center = rot_center_default for i in range(len(rot_center)): self.rot_center_vars[i].set("{:.3f}".format(rot_center[i])) - # Checking rotation rate - if rot_rate < 0: - ui_print("Warning: Rotation rate cannot be negative. Changing sign automatically.") - rot_rate = -rot_rate - elif rot_mag == 0: - ui_print("Warning: Rotation rate cannot be zero. Setting default automatically.") - rot_rate = rot_rate_default - elif rot_rate > rot_rate_max: - ui_print("Warning: Rotation rate too large. Setting default automatically.") - rot_rate = rot_rate_default - self.rot_rate_vars.set("{:.3f}".format(rot_rate)) # Check time step if rot_time_step < 0: ui_print("Warning: Time step cannot be negative. Changing sign automatically.") @@ -763,16 +776,48 @@ class ExecuteCSVMode(Frame): elif rot_time_step < rot_time_step_min: ui_print("Warning: Time step too small. Setting default automatically.") rot_time_step = rot_time_step_default - self.time_step_vars.set("{:.3f}".format(rot_time_step)) + self.rot_time_step_vars.set("{:.3f}".format(rot_time_step)) + # Check cycle number + rot_cycle_number = self.rot_cycle_number_vars.get() + if rot_cycle_number < 1: + ui_print("Warning: cycle number cannot be smaller than one!") + rot_cycle_number = 1 + elif isinstance(rot_cycle_number, float): + if not rot_cycle_number.is_integer(): + ui_print("Warning: cycle number must be an integer. Rounding to nearest integer") + rot_cycle_number = round(rot_cycle_number) + self.rot_cycle_number_vars.set("{:d}".format(rot_cycle_number)) + # Variable rotation rate + # Solve integral of r(t) = r_0 + r_1 * t + r_2 * t ^ 2 + r3 * sin(r_4 * t + r_5) from 0 to tau + func = lambda tau2: r[0] + r[1] * tau2 + r[2] * tau2 ** 2 + r[3] * (np.sin(r[4] * tau2 + r[5])) + func_integral = lambda tau: 360 - (r[0] * tau + r[1] / 2 * tau ** 2 + r[2] / 3 * tau ** 3 + - r[3] * (np.cos(r[4] * tau + r[5]) + np.cos(r[5])) + + r[3] * r[4] * (np.sin(r[4] * tau + r[5]) - np.sin(r[5]))) + tau_solution = -1 + i = 0 # iterator and guess variable [s] + while tau_solution < 0 or abs(func_integral(tau_solution)) > 0.5: + tau_initial_guess = i + try: + tau_solution = fsolve(func_integral, tau_initial_guess) + except RuntimeWarning as w: + ui_print("Calculating the cycle time yielded: ", w) + i = i + 1 + if i == 1000: + ui_print( + "Warning: the user inputs for a rotation rate lead to no solution. Rate inputs reset to default") + r = [1, 0, 0, 0, 0, 0, 0] + for j in range(6): self.rot_rate_vars[j].set("{:.3f}".format(r[j])) + ui_print( + "The cycle_time is tau = {:3f} with f(tau) = {:3f}".format(tau_solution[0], func_integral(tau_solution)[0])) + # Calculate timing + cycle_time = tau_solution + arr_len = int(np.floor(cycle_time / rot_time_step)) * rot_cycle_number # Find perpendicular vectors if v[1] == 0 and v[2] == 0: a = np.cross(v, [0, 1, 0]) else: a = np.cross(v, [1, 0, 0]) b = np.cross(v, a) - # Calculate timing - cycle_time = (360 / rot_rate) - arr_len = int(np.floor(cycle_time / rot_time_step)) # Initialize vectors th = 0 # [rad] t = np.zeros(arr_len) # [s] @@ -781,11 +826,12 @@ class ExecuteCSVMode(Frame): z = np.zeros(arr_len) # [T] # Calculate vectors for i in range(arr_len): - th = th + rot_rate * np.pi / 180 * rot_time_step t[i] = i * rot_time_step - x[i] = (rot_center[0] + rot_mag * np.cos(th) * a[0] + rot_mag * np.sin(th) * b[0])*1e-6 - y[i] = (rot_center[1] + rot_mag * np.cos(th) * a[1] + rot_mag * np.sin(th) * b[1])*1e-6 - z[i] = (rot_center[2] + rot_mag * np.cos(th) * a[2] + rot_mag * np.sin(th) * b[2])*1e-6 + rot_rate = func(t[i]) + th = th + rot_rate * np.pi / 180 * rot_time_step + x[i] = (rot_center[0] + rot_mag * np.cos(th) * a[0] + rot_mag * np.sin(th) * b[0]) * 1e-6 + y[i] = (rot_center[1] + rot_mag * np.cos(th) * a[1] + rot_mag * np.sin(th) * b[1]) * 1e-6 + z[i] = (rot_center[2] + rot_mag * np.cos(th) * a[2] + rot_mag * np.sin(th) * b[2]) * 1e-6 # Return vectors return t, x, y, z @@ -1618,7 +1664,7 @@ class CalibrateMagnetometerSimple(Frame): try: calibration_points = self.calibration_points_var.get() calibration_interval = self.calibration_interval_var.get() - compensated_field = self.self.compensated_field_var .get() + compensated_field = self.self.compensated_field_var.get() calibration_mag_field = self.mag_field_magnitude_var.get() * 1e-6 # converted to micro Tesla if calibration_mag_field <= 0 or calibration_mag_field > g.MAG_MAG_FIELD: raise MagFieldOutOfBounds @@ -2159,7 +2205,7 @@ class CalibrateMagnetometerComplete(Frame): calibration_interval = self.calibration_interval_var.get() calibration_mag_field = self.mag_field_magnitude_var.get() * 1e-6 # converted to micro Tesla calibration_oversampling = self.calibration_oversampling_var.get() - compensated_field = self.compensated_field_var.get() + compensated_field = self.compensated_field_var.get() if calibration_mag_field <= 0 or calibration_mag_field > g.MAG_MAG_FIELD: raise MagFieldOutOfBounds self.calibration_thread = MagnetometerCalibrationComplete(self.view_mpi_queue,