wip: digital twin
CI for debye_bec / test (push) Failing after 1m0s
CI for debye_bec / test (pull_request) Failing after 1m3s

This commit is contained in:
x01da
2026-05-06 14:53:06 +02:00
parent acc5e320cf
commit b0a7d6905c
2 changed files with 74 additions and 9 deletions
@@ -146,7 +146,10 @@ def calc_positions(cfg):
f = (p*q)/(p+q) # focal length
# Bender radius
radius = 2 * q / np.sin(cfg['fm_pitch']) # ideal bending radius
if cfg['fm_qy'] is None:
radius = 2 * q / np.sin(cfg['fm_pitch']) # ideal bending radius for focused beam
else:
radius = 2 * cfg['fm_qy'] / np.sin(cfg['fm_pitch']) # ideal bending radius for unfocused beam
pos['fm_bnd_radius'] = {'value': radius * 1e-6} # Convert to km
# Pitch
@@ -88,10 +88,14 @@ class DigitalTwin(BECWidget, QWidget):
self.input.mo1_mode.activated_connect(self.calc_assistant)
self.input.mo1_xtal.activated_connect(self.calc_assistant)
self.input.fm_stripe.activated_connect(self.calc_assistant)
self.input.fm_focus.activated_connect(self.calc_assistant)
self.input.fm_pitch.value_changed_connect(self.calc_assistant)
self.input.fm_focx.value_changed_connect(self.calc_assistant)
self.input.fm_focy.value_changed_connect(self.calc_assistant)
self.input.smpl.value_changed_connect(self.calc_assistant)
self.bragg_angle = 0
self.qy = 0
# Initialize all values
self.calc_assistant(identifier='init')
@@ -111,6 +115,7 @@ class DigitalTwin(BECWidget, QWidget):
self.calc_mo1_bragg_angle()
self.calc_cm_crit_pitch()
self.calc_cm_reflectivity()
self.update_fm_mode()
self.calc_fm_reflectivity()
self.calc_cm_fm_harm_suppr()
self.calc_fm_ideal_pitch()
@@ -134,6 +139,13 @@ class DigitalTwin(BECWidget, QWidget):
case 'mo1_xtal':
self.calc_mo1_bragg_angle()
self.calc_mo1_energy_resolution()
case 'fm_focus':
self.update_fm_mode()
self.calc_fm_ideal_pitch()
case 'fm_focx':
self.calc_fm_ideal_pitch()
case 'fm_focy':
self.calc_fm_ideal_pitch()
case 'fm_stripe':
self.calc_fm_reflectivity()
self.calc_cm_fm_harm_suppr()
@@ -144,6 +156,24 @@ 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()
@@ -226,6 +256,18 @@ class DigitalTwin(BECWidget, QWidget):
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()
if fm_focus in 'Manual':
fm_pitch = self.input.fm_pitch.value()
fm_qy = None
elif fm_focus in 'Focused':
fm_pitch = self.input.fm_pitch_ideal.value()
fm_qy = None
else: # Focused
fm_pitch = self.input.fm_pitch_ideal.value()
fm_qy = self.qy
config = { # Config in SI units!
'energy' : self.input.energy.value(),
'h_acc' : self.input.sldi_hacc.value() * 1e-3,
@@ -236,9 +278,10 @@ class DigitalTwin(BECWidget, QWidget):
'mo1_mode' : self.input.mo1_mode.currentText(),
'mo1_xtal' : self.input.mo1_xtal.currentText(),
'mo1_bragg' : self.bragg_angle,
'fm_pitch' : -self.input.fm_pitch.value() * 1e-3,
'fm_pitch' : -fm_pitch * 1e-3,
'fm_stripe' : self.input.fm_stripe.currentText(),
'fm_trx' : None,
'fm_qy' : fm_qy,
'fm_gain_height' : 1,
'smpl' : self.input.smpl.value(),
}
@@ -364,15 +407,28 @@ class DigitalTwin(BECWidget, QWidget):
@SafeSlot()
def update_mo1_mode(self):
if self.input.mo1_mode.currentText() in 'Monochromatic':
self.input.mo1_xtal.setDisabled(False)
self.input.mo1_xtal.setVisible(True)
self.input.mo1_bragg_angle.setVisible(True)
self.input.mo1_eres.setVisible(True)
else:
self.input.mo1_xtal.setDisabled(True)
self.input.mo1_xtal.setVisible(False)
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
f = (p * q) / (p + q) # focal length
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
@@ -423,16 +479,16 @@ class InputPanel(QWidget):
# Collimating mirror
self.cm_stripe = ComboBox('cm_stripe', 'Stripe', ['Si', 'Rh', 'Pt'])
self.cm_pitch_critical = NumberIndicator('Critical Pitch', 'mrad', decimals=3)
self.cm_pitch = InputNumberField('cm_pitch', 'Pitch [mrad]', init=-2.391, decimals=3, single_step=0.01, ll=-4.6, hl=-1.2)
self.cm_pitch_critical = NumberIndicator('Critical Pitch', 'mrad', decimals=3)
self.cm_refl = NumberIndicator('Reflectivity at x eV', '%', decimals=0)
self.cm_refl_harm = NumberIndicator('Reflectivity at x eV', '%', decimals=0)
self.cm_ass_group = Group(
'Collimating Mirror',
[
self.cm_stripe,
self.cm_pitch_critical,
self.cm_pitch,
self.cm_pitch_critical,
self.cm_refl,
self.cm_refl_harm,
]
@@ -455,16 +511,22 @@ class InputPanel(QWidget):
# Focusing Mirror
self.fm_stripe = ComboBox('fm_stripe', 'Stripe', ['Rh (toroid)', 'Rh (flat)', 'Pt (toroid)', 'Pt (flat)'])
self.fm_pitch_ideal = NumberIndicator('Ideal Incidence Angle', 'mrad', decimals=3)
self.fm_focus = ComboBox('fm_focus', 'Focus Type', ['Manual', 'Focused', 'Defocused'])
self.fm_pitch = InputNumberField('fm_pitch', 'Incidence Angle [mrad]', init=-2.391, decimals=3, single_step=0.01, ll=-10, hl=2)
self.fm_focx = InputNumberField('fm_focx', 'Beam Size Horizontal [mm]', init=1, decimals=1, single_step=0.1, ll=0, hl=30)
self.fm_focy = InputNumberField('fm_focy', 'Beam Size Vertical [mm]', init=1, decimals=1, single_step=0.1, ll=0, hl=10)
self.fm_pitch_ideal = NumberIndicator('Incidence Angle for focused beam', 'mrad', decimals=3)
self.fm_refl = NumberIndicator('Reflectivity at x eV', '%', decimals=0)
self.fm_refl_harm = NumberIndicator('Reflectivity at x eV', '%', decimals=0)
self.fm_ass_group = Group(
'Focusing Mirror',
[
self.fm_stripe,
self.fm_pitch_ideal,
self.fm_focus,
self.fm_pitch,
self.fm_focx,
self.fm_focy,
self.fm_pitch_ideal,
self.fm_refl,
self.fm_refl_harm,
]