wip: digital twin
This commit is contained in:
@@ -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
|
||||
@@ -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_())
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user