From 6da7e665b342e8fcd7d419af8c44c45c59739683 Mon Sep 17 00:00:00 2001 From: x01da Date: Wed, 6 May 2026 16:12:25 +0200 Subject: [PATCH] wip: digital twin --- ...lculate_positions.py => calc_positions.py} | 0 ...calculate_sideview.py => calc_sideview.py} | 0 .../widgets/digital_twin/calc_varia.py | 173 ++++++++++ .../widgets/digital_twin/digital_twin.py | 307 +++++++----------- .../bec_widgets/widgets/x01da_parameters.py | 2 +- 5 files changed, 285 insertions(+), 197 deletions(-) rename debye_bec/bec_widgets/widgets/digital_twin/{calculate_positions.py => calc_positions.py} (100%) rename debye_bec/bec_widgets/widgets/digital_twin/{calculate_sideview.py => calc_sideview.py} (100%) create mode 100644 debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calculate_positions.py b/debye_bec/bec_widgets/widgets/digital_twin/calc_positions.py similarity index 100% rename from debye_bec/bec_widgets/widgets/digital_twin/calculate_positions.py rename to debye_bec/bec_widgets/widgets/digital_twin/calc_positions.py diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calculate_sideview.py b/debye_bec/bec_widgets/widgets/digital_twin/calc_sideview.py similarity index 100% rename from debye_bec/bec_widgets/widgets/digital_twin/calculate_sideview.py rename to debye_bec/bec_widgets/widgets/digital_twin/calc_sideview.py diff --git a/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py b/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py new file mode 100644 index 0000000..55bd5e1 --- /dev/null +++ b/debye_bec/bec_widgets/widgets/digital_twin/calc_varia.py @@ -0,0 +1,173 @@ +import sys +import re +import numpy as np +from scipy.interpolate import UnivariateSpline +from xrt.backends.raycing.physconsts import CHeVcm, AVOGADRO +from bec_lib import bec_logger + +# pylint: disable=E0611 +from qtpy.QtWidgets import ( + QWidget, + QVBoxLayout, + QHBoxLayout, + QApplication, + QLayout, +) +# pylint: disable=E0611 +from qtpy.QtCore import ( + Qt, + QTimer, +) +from qtpy.QtGui import ( + QColor, + QBrush, +) +import pyqtgraph as pg + +from bec_widgets.utils.bec_widget import BECWidget +from bec_widgets.utils.error_popups import SafeSlot + +from debye_bec.bec_widgets.widgets.qt_widgets import ( + InputNumberField, + ComboBox, + Group, + NumberIndicator, + Mover, +) +from debye_bec.bec_widgets.widgets.digital_twin.calc_positions import calc_positions +from debye_bec.bec_widgets.widgets.digital_twin.calc_sideview import calc_sideview +from debye_bec.bec_widgets.widgets.digital_twin.calc_surfaces import calc_surfaces + +import debye_bec.bec_widgets.widgets.x01da_parameters as bl + +logger = bec_logger.logger + +def sldi_gap_to_acc(sldi_gapx, sldi_gapy): + d1 = bl.feSlits.center1[1] + h_acc = np.tan(sldi_gapx / (2 * d1)) + v_acc = np.tan(sldi_gapy / (2 * d1)) + return h_acc, v_acc + +def cm_trx_to_stripe(cm_trx): + cm_stripe = None + for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]): + if low <= cm_trx <= high: + cm_stripe = name + return cm_stripe + +def fm_trx_to_stripe(fm_trx): + fm_stripe = None + for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]): + if low <= fm_trx <= high: + fm_stripe = name + ' (flat)' + for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]): + if low <= fm_trx <= high: + fm_stripe = name + ' (toroid)' + return fm_stripe + +def mo1_energy_resolution(xtal, energy): + index = bl.mo1.xtal.index(xtal) + crystal = bl.mo1.material1[index] + + dtheta = np.linspace(-30, 90, 601) + theta = crystal.get_Bragg_angle(energy) + dtheta * 1e-6 + refl = np.abs(crystal.get_amplitude(energy, np.sin(theta))[0])**2 # single crystal + + refl2 = refl**2 # DCM with parallel crystals + + # FWHM of the DCM curve + spline = UnivariateSpline(dtheta, refl2 - refl2.max()/2, s=0) + r1, r2 = spline.roots() + fwhm_rad = (r2 - r1) * 1e-6 # µrad → rad + + # Energy resolution + theta_B = crystal.get_Bragg_angle(energy) + dE_over_E = fwhm_rad / np.tan(theta_B) + dE = dE_over_E * energy + + # logger.info(f"DCM FWHM : {r2-r1:.2f} µrad") + # logger.info(f"ΔE/E : {dE_over_E:.2e}") + # logger.info(f"ΔE : {dE:.3f} eV at {E} eV") + + return dE + +def cm_reflectivity(cm_stripe, cm_pitch, energy): + index = bl.cm.surface.index(cm_stripe) + rs, rp = bl.cm.material[index].get_amplitude( + energy, + np.sin(cm_pitch) + )[0:2] + refl = abs(rs)**2 + return refl + +def fm_reflectivity(fm_stripe, fm_pitch, energy): + if fm_stripe in ('Rh (toroid)', 'Pt (toroid)'): + surface = bl.fm.surfaceToroid + material = bl.fm.materialToroid + stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip() + index = surface.index(stripe) + else: + surface = bl.fm.surfaceFlat + material = bl.fm.materialFlat + stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip() + index = surface.index(stripe) + rs, rp = material[index].get_amplitude( + energy, + np.sin(fm_pitch) + )[0:2] + refl = abs(rs)**2 + return refl + +def mo1_bragg_angle(mo_mode, d_spacing, energy, cm_pitch): + H = 6.62606957E-34 + E = 1.602176634E-19 + C = 299792458 + wl = C * H / (E * energy) + val = wl / (2 * d_spacing * 1e-10) + bragg_angle = 0 + if val > -1 and val < 1: + bragg_angle = np.asin(val) + if mo_mode in 'Monochromatic': + # Add 2x CM pitch to the bragg angle + bragg_angle_cor = ((2 * cm_pitch) + bragg_angle) + elif mo_mode in 'Pinkbeam': + # Align xtal surfaces parallel to beam + bragg_angle_cor = (2 * cm_pitch) + return bragg_angle, bragg_angle_cor + +def fm_ideal_pitch(fm_focus, fm_stripe, smpl, sldi_hacc=None, sldi_vacc=None, fm_focx=None, fm_focy=None): + p = bl.fm.center[1] # posFM + q = smpl - bl.fm.center[1] # dist posFM to posEX + if fm_focus in 'Defocused': + a = 2 * np.tan(sldi_hacc) * bl.fm.center[1] # Beam width at focusing mirror + b = 2 * np.tan(sldi_vacc) * bl.cm.center[1] # Beam height at focusing mirror (collimated beam) + x = fm_focx + y = fm_focy + qx = q + x * p / a + qy = q + y * p / b + f = (p * qx) / (p + qx) # focal length + else: # Calculate for focused beam on sample in "manual" and "focused" mode + qy = None + f = (p * q) / (p + q) # focal length + pitch = 0 + if 'Rh' in fm_stripe: + pitch = np.arcsin(bl.fm.r[0]/(2*f))# ideal pitch for FM + if 'Pt' in fm_stripe: + pitch = np.arcsin(bl.fm.r[1]/(2*f)) # ideal pitch for FM + return pitch, qy + +def cm_critical_angle(cm_stripe, energy): + if cm_stripe in 'Si': + stripe = bl.stripeSi + elif cm_stripe in 'Pt': + stripe = bl.stripePt + elif cm_stripe in 'Rh': + stripe = bl.stripeRh + else: + raise Exception(f'Stripe {stripe} not found in beamline parameters!') + w = CHeVcm/100/energy # convert energy [eV] to wavelength [m] + # Calculate critical angle for mirror + f1 = stripe.elements[0].Z + np.real(stripe.elements[0].get_f1f2(energy)) + numberDensity = stripe.rho*1e3*AVOGADRO/(stripe.elements[0].mass/1e3) + criticalAngle = np.sqrt(numberDensity*2.8179e-15*w**2*f1/np.pi) + return criticalAngle diff --git a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py index 0c739d1..cabaa96 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py @@ -38,9 +38,20 @@ from debye_bec.bec_widgets.widgets.qt_widgets import ( NumberIndicator, Mover, ) -from debye_bec.bec_widgets.widgets.digital_twin.calculate_positions import calc_positions -from debye_bec.bec_widgets.widgets.digital_twin.calculate_sideview import calc_sideview +from debye_bec.bec_widgets.widgets.digital_twin.calc_positions import calc_positions +from debye_bec.bec_widgets.widgets.digital_twin.calc_sideview import calc_sideview from debye_bec.bec_widgets.widgets.digital_twin.calc_surfaces import calc_surfaces +from debye_bec.bec_widgets.widgets.digital_twin.calc_varia import ( + sldi_gap_to_acc, + cm_trx_to_stripe, + fm_trx_to_stripe, + mo1_energy_resolution, + cm_reflectivity, + fm_reflectivity, + mo1_bragg_angle, + fm_ideal_pitch, + cm_critical_angle, +) import debye_bec.bec_widgets.widgets.x01da_parameters as bl @@ -104,9 +115,12 @@ class DigitalTwin(BECWidget, QWidget): self._timer = QTimer(self) self._timer.setInterval(1000) self._timer.timeout.connect(self.calc_reality) - # TODO: Check if I need to stop the timer if the widget is closed? self._timer.start() + def apply_theme(self, theme): + self.sideview_plot.apply_theme(theme) + self.surface_plots.apply_theme(theme) + @SafeSlot() def calc_assistant(self, *args, **kwargs): identifier = kwargs['identifier'] @@ -156,105 +170,6 @@ class DigitalTwin(BECWidget, QWidget): self.calc_assistant_sideview() self.calc_assistant_surfaces() - def update_fm_mode(self): - fm_focus = self.input.fm_focus.currentText() - if fm_focus in 'Manual': - self.input.fm_pitch.setVisible(True) - self.input.fm_pitch_ideal.setVisible(True) - self.input.fm_focx.setVisible(False) - self.input.fm_focy.setVisible(False) - elif fm_focus in 'Focused': - self.input.fm_pitch.setVisible(False) - self.input.fm_pitch_ideal.setVisible(True) - self.input.fm_focx.setVisible(False) - self.input.fm_focy.setVisible(False) - else: # Defocused - self.input.fm_pitch.setVisible(False) - self.input.fm_pitch_ideal.setVisible(True) - self.input.fm_focx.setVisible(True) - self.input.fm_focy.setVisible(True) - - @SafeSlot() - def calc_reality(self): - config = self.get_reality_config() - beam = calc_sideview(config) - data = {'x': beam['Z'], 'y': beam['Y']} - self.sideview_plot.update_curves('reality', data) - surfaces = calc_surfaces(config) - self.surface_plots.update_surfaces(scene='reality', data=surfaces) - - def calc_mo1_energy_resolution(self, *args, **kwargs): - xtal = self.input.mo1_xtal.currentText().translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters - index = bl.mo1.xtal.index(xtal) - crystal = bl.mo1.material1[index] - E = self.input.energy.value() - - dtheta = np.linspace(-30, 90, 601) - theta = crystal.get_Bragg_angle(E) + dtheta * 1e-6 - refl = np.abs(crystal.get_amplitude(E, np.sin(theta))[0])**2 # single crystal - - refl2 = refl**2 # DCM with parallel crystals - - # FWHM of the DCM curve - spline = UnivariateSpline(dtheta, refl2 - refl2.max()/2, s=0) - r1, r2 = spline.roots() - fwhm_rad = (r2 - r1) * 1e-6 # µrad → rad - - # Energy resolution - theta_B = crystal.get_Bragg_angle(E) - dE_over_E = fwhm_rad / np.tan(theta_B) - dE = dE_over_E * E - - # logger.info(f"DCM FWHM : {r2-r1:.2f} µrad") - # logger.info(f"ΔE/E : {dE_over_E:.2e}") - # logger.info(f"ΔE : {dE:.3f} eV at {E} eV") - - self.input.mo1_eres.setValue(dE) - - def calc_cm_reflectivity(self): - index = bl.cm.surface.index(self.input.cm_stripe.currentText()) - rs, rp = bl.cm.material[index].get_amplitude( - self.input.energy.value(), - np.sin(-self.input.cm_pitch.value() * 1e-3) - )[0:2] - self.input.cm_refl.setValue(100 * abs(rs)**2) - self.input.cm_refl.setLabel(f"Reflectivity at \n{self.input.energy.value():.0f} eV") - rs, rp = bl.cm.material[index].get_amplitude( - 2 * self.input.energy.value(), - np.sin(-self.input.cm_pitch.value() * 1e-3) - )[0:2] - self.input.cm_refl_harm.setValue(100 * abs(rs)**2) - self.input.cm_refl_harm.setLabel(f"Reflectivity at \n{3 * self.input.energy.value():.0f} eV") - - def calc_fm_reflectivity(self): - if self.input.fm_stripe.currentText() in ('Rh (toroid)', 'Pt (toroid)'): - surface = bl.fm.surfaceToroid - material = bl.fm.materialToroid - stripe = re.sub(r'\s*\(.*?\)', '', self.input.fm_stripe.currentText()).strip() - index = surface.index(stripe) - else: - surface = bl.fm.surfaceFlat - material = bl.fm.materialFlat - stripe = re.sub(r'\s*\(.*?\)', '', self.input.fm_stripe.currentText()).strip() - index = surface.index(stripe) - rs, rp = material[index].get_amplitude( - self.input.energy.value(), - np.sin(-self.input.fm_pitch.value() * 1e-3) - )[0:2] - self.input.fm_refl.setValue(100 * abs(rs)**2) - self.input.fm_refl.setLabel(f"Reflectivity at \n{self.input.energy.value():.0f} eV") - rs, rp = material[index].get_amplitude( - 2 * self.input.energy.value(), - np.sin(-self.input.fm_pitch.value() * 1e-3) - )[0:2] - self.input.fm_refl_harm.setValue(100 * abs(rs)**2) - self.input.fm_refl_harm.setLabel(f"Reflectivity at \n{3 * self.input.energy.value():.0f} eV") - - def calc_cm_fm_harm_suppr(self): - harm_suppr = (self.input.cm_refl.value() * self.input.fm_refl.value()) / (self.input.cm_refl_harm.value() * self.input.fm_refl_harm.value()) - self.input.cm_fm_harm_suppr.setValue(harm_suppr) - self.input.cm_fm_harm_suppr.setLabel(f"Total Suppression Factor at {3 * self.input.energy.value():.0f} eV") - def get_assistant_config(self): fm_focus = self.input.fm_focus.currentText() @@ -296,23 +211,12 @@ class DigitalTwin(BECWidget, QWidget): mo1_bragg = self.dev.mo1_bragg.read() sldi_gapx = self.dev.sldi_gapx.read()['sldi_gapx']['value'] sldi_gapy = self.dev.sldi_gapy.read()['sldi_gapy']['value'] - d1 = bl.feSlits.center1[1] - h_acc = np.tan(sldi_gapx / (2 * d1)) - v_acc = np.tan(sldi_gapy / (2 * d1)) + h_acc, v_acc = sldi_gap_to_acc(sldi_gapx, sldi_gapy) cm_trx = -self.dev.cm_trx.read()['cm_trx']['value'] - cm_stripe = None - for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]): - if low <= cm_trx <= high: - cm_stripe = name + cm_stripe = cm_trx_to_stripe(cm_trx) cm_pitch = -self.dev.cm_rotx.read()['cm_rotx']['value'] * 1e-3 fm_trx = -self.dev.fm_trx.read()['fm_trx']['value'] - fm_stripe = None - for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]): - if low <= fm_trx <= high: - fm_stripe = name + ' (flat)' - for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]): - if low <= fm_trx <= high: - fm_stripe = name + ' (toroid)' + fm_stripe = fm_trx_to_stripe(fm_trx) fm_pitch = -self.dev.fm_rotx.read()['fm_rotx']['value'] * 1e-3 fm_pitch_real = 2 * cm_pitch - fm_pitch config = { # Config in SI units! @@ -334,21 +238,75 @@ class DigitalTwin(BECWidget, QWidget): # logger.info(f'Config created: {config}') return config - @SafeSlot() + def update_fm_mode(self): + fm_focus = self.input.fm_focus.currentText() + if fm_focus in 'Manual': + self.input.fm_pitch.setVisible(True) + self.input.fm_pitch_ideal.setVisible(True) + self.input.fm_focx.setVisible(False) + self.input.fm_focy.setVisible(False) + elif fm_focus in 'Focused': + self.input.fm_pitch.setVisible(False) + self.input.fm_pitch_ideal.setVisible(True) + self.input.fm_focx.setVisible(False) + self.input.fm_focy.setVisible(False) + else: # Defocused + self.input.fm_pitch.setVisible(False) + self.input.fm_pitch_ideal.setVisible(True) + self.input.fm_focx.setVisible(True) + self.input.fm_focy.setVisible(True) + + def calc_reality(self): + config = self.get_reality_config() + beam = calc_sideview(config) + data = {'x': beam['Z'], 'y': beam['Y']} # TODO: Refactor sideview calculator to match data format + self.sideview_plot.update_curves('reality', data) + surfaces = calc_surfaces(config) + self.surface_plots.update_surfaces(scene='reality', data=surfaces) + + def calc_mo1_energy_resolution(self): + xtal = self.input.mo1_xtal.currentText().translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters + energy = self.input.energy.value() + self.input.mo1_eres.setValue(mo1_energy_resolution(xtal, energy)) + + def calc_cm_reflectivity(self): + cm_stripe = self.input.cm_stripe.currentText() + cm_pitch = -self.input.cm_pitch.value() * 1e-3 + energy = self.input.energy.value() + self.input.cm_refl.setValue(100 * cm_reflectivity(cm_stripe, cm_pitch, energy)) + self.input.cm_refl.setLabel(f"Reflectivity at \n{energy:.0f} eV") + self.input.cm_refl_harm.setValue(100 * cm_reflectivity(cm_stripe, cm_pitch, 3*energy)) + self.input.cm_refl_harm.setLabel(f"Reflectivity at \n{3*energy:.0f} eV") + + def calc_fm_reflectivity(self): + fm_stripe = self.input.fm_stripe.currentText() + fm_focus = self.input.fm_focus.currentText() + if fm_focus in 'Manual': + fm_pitch = -self.input.fm_pitch.value() * 1e-3 + else: + fm_pitch = -self.input.fm_pitch_ideal.value() * 1e-3 + energy = self.input.energy.value() + self.input.fm_refl.setValue(100 * fm_reflectivity(fm_stripe, fm_pitch, energy)) + self.input.fm_refl.setLabel(f"Reflectivity at \n{energy:.0f} eV") + self.input.fm_refl_harm.setValue(100 * fm_reflectivity(fm_stripe, fm_pitch, 3*energy)) + self.input.fm_refl_harm.setLabel(f"Reflectivity at \n{3*energy:.0f} eV") + + def calc_cm_fm_harm_suppr(self): + harm_suppr = (self.input.cm_refl.value() * self.input.fm_refl.value()) / (self.input.cm_refl_harm.value() * self.input.fm_refl_harm.value()) + self.input.cm_fm_harm_suppr.setValue(harm_suppr) + self.input.cm_fm_harm_suppr.setLabel(f"Total Suppression Factor at {3 * self.input.energy.value():.0f} eV") + def calc_assistant_sideview(self): beam = calc_sideview(self.get_assistant_config()) data = {'x': beam['Z'], 'y': beam['Y']} self.sideview_plot.update_curves('assistant', data) - @SafeSlot() def calc_assistant_surfaces(self): surfaces = calc_surfaces(self.get_assistant_config()) self.surface_plots.update_surfaces(scene='assistant', data=surfaces) - @SafeSlot() def calc_positions(self): out = calc_positions(self.get_assistant_config()) - self.positions.sldi_gapx.setValue(out['sldi_gapx']['value']) self.positions.sldi_gapy.setValue(out['sldi_gapy']['value']) self.positions.cm_trx.setValue(out['cm_trx']['value']) @@ -370,7 +328,6 @@ class DigitalTwin(BECWidget, QWidget): self.positions.ot_rotx.setValue(out['ot_rotx']['value']) self.positions.ot_es1_trz.setValue(out['ot_es1_trz']['value']) - @SafeSlot() def calc_mo1_bragg_angle(self): """ Calculates bragg angle in rad @@ -382,29 +339,13 @@ class DigitalTwin(BECWidget, QWidget): d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si311.get() else: raise Exception(f'Invalid xtal selection: {xtal}') - - H = 6.62606957E-34 - E = 1.602176634E-19 - C = 299792458 - - wl = C * H / (E * self.input.energy.value()) - val = wl / (2 * d_spacing * 1e-10) - self.bragg_angle = 0 - if val > -1 and val < 1: - self.bragg_angle = np.asin(val) - cm_pitch = -self.dev.cm_rotx.read()['cm_rotx']['value'] * 1e-3 - if self.input.mo1_mode.currentText() in 'Monochromatic': - # Add 2x CM pitch to the bragg angle - bragg_angle_cor = ((2 * cm_pitch) + self.bragg_angle) / np.pi * 180 - elif self.input.mo1_mode.currentText() in 'Pinkbeam': - # Align xtal surfaces parallel to beam - bragg_angle_cor = (2 * cm_pitch) / np.pi * 180 + mo1_mode = self.input.mo1_mode.currentText() + energy = self.input.energy.value() + theta, theta_cor = mo1_bragg_angle(mo1_mode, d_spacing, energy, cm_pitch) + self.bragg_angle = theta + self.input.mo1_bragg_angle.setValue(theta_cor / np.pi * 180) - self.input.mo1_bragg_angle.setValue(bragg_angle_cor) - # self.calc_positions() - - @SafeSlot() def update_mo1_mode(self): if self.input.mo1_mode.currentText() in 'Monochromatic': self.input.mo1_xtal.setVisible(True) @@ -415,45 +356,22 @@ class DigitalTwin(BECWidget, QWidget): self.input.mo1_bragg_angle.setVisible(False) self.input.mo1_eres.setVisible(False) - @SafeSlot() - def calc_fm_ideal_pitch(self): - p = bl.fm.center[1] # posFM - q = self.input.smpl.value() - bl.fm.center[1] # dist posFM to posEX - if self.input.fm_focus.currentText() in 'Defocused': - a = 2 * np.tan(self.input.sldi_hacc.value() * 1e-3) * bl.fm.center[1] # Beam width at focusing mirror - b = 2 * np.tan(self.input.sldi_vacc.value() * 1e-3) * bl.cm.center[1] # Beam height at focusing mirror (collimated beam) - x = self.input.fm_focx.value() - y = self.input.fm_focy.value() - qx = q + x * p / a - self.qy = q + y * p / b - f = (p * qx) / (p + qx) # focal length - else: # Calculate for focused beam on sample in "manual" and "focused" mode - f = (p * q) / (p + q) # focal length - pitch = 0 - if 'Rh' in self.input.fm_stripe.currentText(): - pitch = np.arcsin(bl.fm.r[0]/(2*f))# ideal pitch for FM - if 'Pt' in self.input.fm_stripe.currentText(): - pitch = np.arcsin(bl.fm.r[1]/(2*f)) # ideal pitch for FM - self.input.fm_pitch_ideal.setValue(-pitch * 1e3) + def calc_fm_ideal_pitch(self): # TODO: What happens if the flats are selected? + fm_focus = self.input.fm_focus.currentText() + fm_stripe = self.input.fm_stripe.currentText() + smpl = self.input.smpl.value() + sldi_hacc = self.input.sldi_hacc.value() * 1e-3 + sldi_vacc = self.input.sldi_vacc.value() * 1e-3 + fm_focx = self.input.fm_focx.value() + fm_focy = self.input.fm_focy.value() + fm_pitch, qy = fm_ideal_pitch(fm_focus, fm_stripe, smpl, sldi_hacc, sldi_vacc, fm_focx, fm_focy) + self.qy = qy + self.input.fm_pitch_ideal.setValue(-fm_pitch * 1e3) - @SafeSlot() def calc_cm_crit_pitch(self): - stripe = self.input.cm_stripe.currentText() - # Config Mirror - if stripe in 'Si': - stripe = bl.stripeSi - elif stripe in 'Pt': - stripe = bl.stripePt - elif stripe in 'Rh': - stripe = bl.stripeRh - else: - raise Exception(f'Stripe {stripe} not found in beamline parameters!') - w = CHeVcm/100/self.input.energy.value() # convert energy [eV] to wavelength [m] - # Calculate critical angle for mirror - f1 = stripe.elements[0].Z + np.real(stripe.elements[0].get_f1f2(self.input.energy.value())) - numberDensity = stripe.rho*1e3*AVOGADRO/(stripe.elements[0].mass/1e3) - criticalAngle = np.sqrt(numberDensity*2.8179e-15*w**2*f1/np.pi) - self.input.cm_pitch_critical.setValue(-criticalAngle * 1e3) + cm_stripe = self.input.cm_stripe.currentText() + energy = self.input.energy.value() + self.input.cm_pitch_critical.setValue(-cm_critical_angle(cm_stripe, energy) * 1e3) class InputPanel(QWidget): """Right-side control panel: input field, indicator, send, recording.""" @@ -720,11 +638,11 @@ class MoverPanel(QWidget): self._layout .addWidget(self.mover_group) self._layout .addStretch() -class SurfacePlots(BECWidget, QWidget): +class SurfacePlots(QWidget): """Plot widget with two curves and legend.""" - def __init__(self, parent=None, *arg, **kwargs): - super().__init__(parent=parent, theme_update=True, *arg, **kwargs) + def __init__(self, parent=None): + super().__init__(parent=parent) self._layout = QHBoxLayout(self) self.surfaces = { @@ -776,7 +694,7 @@ class SurfacePlots(BECWidget, QWidget): # Create surfaces for idx, scene in enumerate(self.surfaces): - for name, device in self.surfaces[scene].items(): + for name, _ in self.surfaces[scene].items(): if scene in 'assistant': brush = QBrush(QColor(*self.colors[idx], 255), Qt.DiagCrossPattern) pen = pg.mkPen(QColor(*self.colors[idx], 255), width=1, style=Qt.DashLine) @@ -830,7 +748,7 @@ class SurfacePlots(BECWidget, QWidget): self.text_color = (0, 0, 0) for idx, scene in enumerate(self.surfaces): - for name, device in self.surfaces[scene].items(): + for name, _ in self.surfaces[scene].items(): if scene in 'assistant': brush = QBrush(QColor(*self.colors[idx], 255), Qt.DiagCrossPattern) pen = pg.mkPen(QColor(*self.colors[idx], 255), width=1, style=Qt.DashLine) @@ -860,7 +778,7 @@ class SurfacePlots(BECWidget, QWidget): rect.setBrush(pg.QtGui.QBrush(pg.QtGui.QColor(*self.color_impenetrable))) # pylint: disable=E1101 rect.setPen(pg.mkPen(color=self.color_impenetrable, width=2)) widget.addItem(rect) - text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5)) # TODO: CHange color according to theme + text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5)) widget.addItem(text) text.setPos((hx+lx)/2, (hy+ly)/2) text.setZValue(10) @@ -868,17 +786,17 @@ class SurfacePlots(BECWidget, QWidget): self.texts.append(text) def plot_mono_surface(widget, xtal, xtalWidth, xtalOffsetX, xtalLength): - for sf, w, offx, len in zip(xtal, xtalWidth, xtalOffsetX, xtalLength): + for sf, w, offx, length in zip(xtal, xtalWidth, xtalOffsetX, xtalLength): rect = pg.QtWidgets.QGraphicsRectItem( # pylint: disable=E1101 offx - w/2, - -len/2, + -length/2, w, - len, + length, ) rect.setBrush(pg.QtGui.QBrush(pg.QtGui.QColor(*self.color_impenetrable))) # pylint: disable=E1101 rect.setPen(pg.mkPen(color=self.color_impenetrable, width=2)) widget.addItem(rect) - text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5)) # TODO: CHange color according to theme + text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5)) widget.addItem(text) text.setPos(offx, 0) text.setZValue(10) @@ -897,7 +815,7 @@ class SurfacePlots(BECWidget, QWidget): plot_mirror_stripe(plot['widget'], bl.fm.surfaceToroid, bl.fm.limOptXToroid, bl.fm.limOptYToroid) else: raise Exception(f'Plot {name} not found!') - for name, plot in self.plots.items(): + for name, plot in self.plots.items(): plot['widget'].disableAutoRange() def update_surfaces(self, scene, data): @@ -908,11 +826,11 @@ class SurfacePlots(BECWidget, QWidget): y = np.array(device['y'] + [device['y'][0]]) if len(device['y']) != 0 else np.array([]) plot.setData(x=x, y=y) -class SideviewPlot(BECWidget, QWidget): +class SideviewPlot(QWidget): """Plot widget with two curves and legend.""" - def __init__(self, parent=None, *arg, **kwargs): - super().__init__(parent=parent, theme_update=True, *arg, **kwargs) + def __init__(self, parent=None): + super().__init__(parent=parent) self._layout = QVBoxLayout(self) # self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore @@ -1045,7 +963,6 @@ class SideviewPlot(BECWidget, QWidget): if __name__ == "__main__": - from qtpy.QtWidgets import QApplication from bec_widgets.utils.bec_dispatcher import BECDispatcher from bec_widgets.utils.colors import apply_theme @@ -1053,7 +970,5 @@ if __name__ == "__main__": apply_theme("light") dispatcher = BECDispatcher(gui_id="digital_twin") win = DigitalTwin() - - # win.resize(1000, 800) win.show() sys.exit(app.exec_()) diff --git a/debye_bec/bec_widgets/widgets/x01da_parameters.py b/debye_bec/bec_widgets/widgets/x01da_parameters.py index cb5db48..8a97e5a 100644 --- a/debye_bec/bec_widgets/widgets/x01da_parameters.py +++ b/debye_bec/bec_widgets/widgets/x01da_parameters.py @@ -139,7 +139,7 @@ cm = collimatingMirror( material=(stripeSi, stripePt, stripeRh), limPhysX=(-34, 34), limPhysY=(-600, 600), - limOptX=((-27, -3.5, 15), (-11, 6.5, 25)), + limOptX=((-21, -7, 14), (-11, 11, 23)), limOptY=((-500, -500, -500), (500, 500, 500)), R=[3e6, 15e6], pitch=[-5.0e-3, -0.0e-3],