move ESF_RIXS.py from git@git.psi.ch:epics_ioc_modules/ESF_RIXS.git

This commit is contained in:
2024-08-28 08:51:14 +02:00
parent d10fd38d0b
commit bbbe0694f6
4 changed files with 1401 additions and 0 deletions

1033
RIXSconfig/ESF_RIXS.py Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
import numpy as np
class Config:
#_lutProbes={
# '<key>':{
# 'R':<radius of the grating mirror>
# 'a0':<lines/mm>,'a1':<lines/mm^2>,'a2':<lines/mm^3>,'a3':<lines/mm^4> # polynome for line density of the mirror
# 'eqs'{ #<equation solving parameters>
# 'k': <deflection order (default=1)>,
# 'lut': [# lookup table for starting guess values at a given energy for equation solver
# ( <energy (eV)>, <r1(mm)>, <r2(mm)>, <alpha(rad)>),
# ( ... ),
# ],
# },
# },
# '<key>':{
# ...
# }
#}
_lutProbes={
'g80_1':{ # grating-key { radius of the grating mirror, polynome for line density of the mirror
'R':65144.054, 'a0':1160.759,'a1':0.3409,'a2':-3.74E-5,'a3':1.98E-7,
'e':(300,1600), #optional energy range min max
'esp':{'k': 1, # equation solving parameters { deflection order
'lut': [ # lookup table for starting guess values at a given energy for equation solver
(300, 1193, 4127, np.deg2rad(88)), # energy(eV), r1(mm), r2(mm), alpha(rad),
],},},
'g80_2':{
'R':65144.054, 'a0':1160.759,'a1':0.3409,'a2':-3.74E-5,'a3':1.98E-7,
'esp':{'k': 2,
'lut': [
(350, 1045, 4557, np.deg2rad(88)),
(500, 1398, 3763, np.deg2rad(88)),
],},},
'g80_3':{
'R':65144.054, 'a0':1160.759,'a1':0.3409,'a2':-3.74E-5,'a3':1.98E-7,
'esp':{'k': 3,
'lut': [
(400, 988, 4778, np.deg2rad(88)),
(800, 1156, 4218, np.deg2rad(88)),
],},},
'gtst_1':{
'R':65144.054, 'a0':1360.759,'a1':0.3409,'a2':-3.74E-5,'a3':1.98E-7,
'e':(1500,1900), #optional energy range min max
'esp':{'k': 1,
'lut': [
(300, 1133, 4468, np.deg2rad(88)),
],},},
}
_spectrometer={
'vis':{'ofs':(500, 500),},
'probes':{ # upstream 2x6
'80meV' :{'probe':'g80_1','txy':(0., 0.),}, # txy='sample motor positions for probe'
'80meV_k=2' :{'probe':'g80_2','txy':(0., 1.),}, # ofs= offsets for various motors
'80meV_k=3' :{'probe':'g80_3','txy':(0., 2.),},
'gratingTest1':{'probe':'gtst_1','txy':(0., 3.),},
},
}

View File

@@ -0,0 +1,169 @@
#!/usr/bin/env python
# *-----------------------------------------------------------------------*
# | |
# | Copyright (c) 2022 by Paul Scherrer Institute (http://www.psi.ch) |
# | |
# | Author Thierry Zamofing (thierry.zamofing@psi.ch) |
# *-----------------------------------------------------------------------*
'''
coordinate systems, optical center, xray axis, pixel sizes etc.
ppm= pixel per mm
update_pix2pos
modes:
0x01: update_pix2pos
0x02: update_optical_center
'''
import logging
_log=logging.getLogger(__name__)
import numpy as np
from scipy.optimize import fsolve
from ESF_RIXSconfig import Config
class VLSgrating: #Gratings with Variable Line Density
def __init__(self):
pass
def setup(self,key):
self._param=Config._lutProbes[key]
# ---------- eugenio calculation functions ----------
def solve_focus_equ(self, eV):
esp=self._param['esp']
p0=esp['lut'][0][1:] # fsolve starting values for [r1, r2, alpha]
esp['En']=eV # Update the dictionary with the energy at which I want to calculate the positions
esp['Lam_nm']=1239.842/eV # Convert energy to wavelength
pn=fsolve(self.equations, p0, maxfev=100000000)
#es['r1'], es['r2'], es['alpha']=pn
return pn
def equations(self, pn): # system of 3 equations for 3 parameters
return (self.Eq1(*pn), self.Eq2(*pn), self.Eq4(*pn))
def Eq1(self, r1, r2, alpha):
p=self._param;esp=p['esp']
a1, R, a0, k, Lam_nm=p['a1'], p['R'], p['a0'], esp['k'], esp['Lam_nm']
beta=self.beta(r1, r2, alpha)
return np.cos(alpha)**2/r1+np.cos(beta)**2/r2-\
(np.cos(alpha)+np.cos(beta))/R-a1*k*Lam_nm/1e6
def Eq2(self, r1, r2, alpha):
p=self._param;esp=p['esp']
a0, a1, a2, R, k, Lam_nm=p['a0'], p['a1'], p['a2'], p['R'], esp['k'], esp['Lam_nm']
beta=self.beta(r1, r2, alpha)
return np.sin(alpha)/r1*(np.cos(alpha)**2/r1-np.cos(alpha)/R)-\
np.sin(beta)/r2*(
np.cos(beta)**2/r2-np.cos(beta)/R)+2*a2*k*Lam_nm/1e6/3
def Eq4(self, r1, r2, alpha):
p=self._param;esp=p['esp']
a3, R, k, Lam_nm, a0=p['a3'], p['R'], esp['k'], esp['Lam_nm'], p['a0']
beta=self.beta(r1, r2, alpha)
return 4*np.sin(alpha)**2/r1**2*(np.cos(alpha)**2/r1-np.cos(alpha)/R)\
-1/r1*(np.cos(alpha)**2/r1-np.cos(alpha)/R)**2+1/R**2*(1/r1-np.cos(alpha)/R)\
+4*np.sin(beta)**2/r2**2*(
np.cos(beta)**2/r2-np.cos(beta)/R)\
-1/r2*(np.cos(beta)**2/r2-np.cos(beta)/R)**2\
+1/R**2*(1/r2-np.cos(beta)/R)-2*a3*k*Lam_nm/1e6
def beta(self, r1, r2, alpha): # calculate beta as a function of alpha, energy, and diffraction order (k)
p=self._param;esp=p['esp']
a0, k, Lam_nm=p['a0'], esp['k'], esp['Lam_nm']
return np.arcsin(np.sin(alpha)-a0*k*Lam_nm/1e6)
def gamma(self, r1, r2, alpha): # compute detector lean angle in respect of the r2 arm
p=self._param;esp=p['esp']
R, a0, a1=p['R'], p['a0'], p['a1']
beta=self.beta(r1, r2, alpha)
return np.arctan(np.cos(beta)/(2*np.sin(beta)-r2*(np.tan(beta)/R+a1/a0)))
def energy2geometry(self, energy,probe):
# mode raytrace or interpolate lut
# prec precision requitements (precision iteration)
# returns R1,R2,aa,bb,cc
pn=self.solve_focus_equ(energy)
r1, r2, alpha=pn
beta=self.beta(*pn)
gamma=self.gamma(*pn)
geo={'r1':r1,'r2':r2,'aa':np.rad2deg(alpha), 'bb':np.rad2deg(beta), 'cc':np.rad2deg(gamma)}
return geo
if __name__=="__main__":
import argparse
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ')
#(h, t)=os.path.split(sys.argv[0]);cmd='\n '+(t if len(h)>20 else sys.argv[0])+' '
#exampleCmd=('', '-m0xf -v0')
epilog=__doc__ #+'\nExamples:'+''.join(map(lambda s:cmd+s, exampleCmd))+'\n'
parser=argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-m", "--mode", type=lambda x: int(x,0), help="mode (see bitmasks) default=0x%(default)x", default=0xff)
args=parser.parse_args()
_log.info('Arguments:{}'.format(args.__dict__))
np.set_printoptions(suppress=True)
np.set_printoptions(linewidth=196)
if args.mode&0x01:
vlsg=VLSgrating()
print(f'{"grating":>7s} | {"energy(eV)":>10s} | {"r1(mm)":>8s} | {"r2(mm)":>8s} | {"aa(°)":>5s} | {"bb(°)":>5s} | {"cc(°)":>5s}')
for gr,eV in (
('g80_1',500),
('g80_2',500),
('g80_3',500),
('gtst_1',500),
):
vlsg.setup(gr)
geo=vlsg.energy2geometry(eV)
print('{gr:>7s} | {eV:10.4g} | {r1:8.2f} | {r2:8.2f} | {aa:5.2f} | {bb:5.2f} | {cc:5.2f}'.format(gr=gr,eV=eV,**geo))
#print(g, vlsg.energy2geometry(e))
vlsg.setup('g80_1')
Es=np.arange(250, 1550, 50)
print('')
print(f'{"energy(eV)":>10s} | {"r1(mm)":>8s} | {"r2(mm)":>8s} | {"aa(°)":>5s} | {"bb(°)":>5s} | {"cc(°)":>5s}')
for eV in Es:
geo=vlsg.energy2geometry(eV)
print('{eV:10.4g} | {r1:8.2f} | {r2:8.2f} | {aa:5.2f} | {bb:5.2f} | {cc:5.2f}'.format(eV=eV,**geo))
#pn=vlsg.solve_focus_equ(eV)
#r1, r2, alpha=pn
#beta=vlsg.beta(*pn)
#gamma=vlsg.gamma(*pn)
#print(f'{eV:10.4g} | {r1:8.2f} | {r2:8.2f} | {np.rad2deg(alpha):5.2f} | {np.rad2deg(beta):5.2f} | {np.rad2deg(gamma):5.2f}')
#vlsg.setup('80meV')
#[[ 250. 1137.32196015 4269.702359 88.0898184 83.55888668 36.27529716]
# [ 300. 1193.65841126 4127.21279026 88.10217759 84.07300885 31.7396222 ]
# [ 350. 1247.36978781 4010.83457789 88.10391338 84.46686259 28.55391769]
# [ 400. 1299.07861098 3914.08632128 88.0986373 84.77926418 26.23765458]
# [ 450. 1349.21078537 3832.65392519 88.08846973 85.03343956 24.51153466]
# [ 500. 1398.0721341 3763.52172859 88.0747408 85.24424965 23.20495827]
# [ 550. 1445.89064959 3704.49942068 88.05832818 85.42170416 22.20901965]
# [ 600. 1492.84122971 3653.94623698 88.03983478 85.57282519 21.45157952]
# [ 650. 1539.06120728 3610.60137571 88.01968853 85.70270455 20.88341654]
# [ 700. 1584.66050296 3573.4750186 87.9982015 85.8151371 20.47018758]
# [ 750. 1629.72855701 3541.77572852 87.9756065 85.91301734 20.18757719]
# [ 800. 1674.33907781 3514.86055917 87.95208076 85.99859726 20.01827307]
# [ 850. 1718.5535488 3492.19987783 87.92776146 86.07365932 19.95002982]
# [ 900. 1762.42379391 3473.35198496 87.90275638 86.13963571 19.97440536]
# [ 950. 1805.9938836 3457.94443997 87.87715139 86.19769259 20.08592919]
# [1000. 1849.30170875 3445.66007701 87.85101563 86.24879078 20.28155755]
# [1050. 1892.37950222 3436.22635374 87.82440571 86.29373053 20.5603321 ]
# [1100. 1935.25594973 3429.40718162 87.79736786 86.33318446 20.92317777]
# [1150. 1977.95569753 3424.99649949 87.76994068 86.36772314 21.37282654]
# [1200. 2020.5004814 3422.81321829 87.74215652 86.39783427 21.91383066]
# [1250. 2062.90951189 3422.6972001 87.71404265 86.42393782 22.55267071]
# [1300. 2105.19981239 3424.50599743 87.6856223 86.44639783 23.29795403]
# [1350. 2147.38653997 3428.11221941 87.6569154 86.46553179 24.16071358]
# [1400. 2189.48325951 3433.40137902 87.62793911 86.4816182 25.15482162]
# [1450. 2231.50212122 3440.27009676 87.59870838 86.49490264 26.29753817]
# [1500. 2273.45406713 3448.62463224 87.56923627 86.50560273 27.61022401]]

View File

@@ -0,0 +1,138 @@
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QPainter, QColor
import PyQt5.QtGui as QtGui
import PyQt5.QtCore as QtCore
import numpy as np
class WndGirderRIXS(QWidget):
_lutColor={0:(255,0,0),1:(0,255,0),2:(255,255,0)}
def __init__(self):
super().__init__()
self._param={'ctr':(200, 300), # location of big chamber
'rCmb':80, # radius of chamber
'rTrg':10, # radius of target
'szSeal': (20,50),
'szArm': (80,350),
'aaSeal':70, # RIXS sliding angle
'aaArm':70, # RIXS arm angle
'airPads':0, # air pads (off=0 on=1 undef=2)
'defComp':0, # deformation compensation (off=0 on=1 undef=2)
}
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 850, 800)
self.setWindowTitle('RIXS girder')
#label = QLabel('Python', self)
#label.move(50,50)
#b1 = QPushButton("Button1",self)
#b1.move(400,150)
self._wdGrpDraw=w=QtGui.QGroupBox("Drawing",self)
w.move(10,10)
l=QtGui.QVBoxLayout(w)
sld={}
for key,rng,tk in (('aaSeal',(0,180),5),('aaArm',(0,180),5),('airPads',(0,2),1),('defComp',(0,2),1),):
sl=QtGui.QSlider(QtCore.Qt.Horizontal,objectName=key)
sl.setFixedWidth(200);sl.setMinimum(rng[0]);sl.setMaximum(rng[1])
sl.setValue(self._param[key])
sl.setTickPosition(QtGui.QSlider.TicksBelow);sl.setTickInterval(tk)
l.addWidget(sl)
sl.valueChanged.connect(lambda val,key=key: self.sldChanged(key,val))
sld[key]=sl
self.show()
def sldChanged(self,key,val,*args,**kwargs):
p=self._param
#print(key,val)
if key=='aaArm':
p['aaSeal']=v=p['aaSeal']-p['aaArm']+val
wSl=self._wdGrpDraw.findChild(QtGui.QSlider, 'aaSeal')
wSl.blockSignals(True)
wSl.setValue(int(v))
wSl.blockSignals(False)
sl=QtGui.QSlider(QtCore.Qt.Horizontal,objectName=key)
p[key]=val
self.update()
def paintEvent(self, e):
p=self._param
ctr=p['ctr']
aaSeal=p['aaSeal']
aaArm=p['aaArm']
rCmb=p['rCmb']
rTrg=p['rTrg']
xa,ya=p['szArm']
xs,ys=p['szSeal']
ap=p['airPads']
dc=p['defComp']
qp = QPainter()
qp.begin(self)
qp.translate(ctr[0],ctr[1])
tf0=qp.transform()
qp.setPen(QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine))
qp.setBrush(QColor(155, 155, 155, 128))
br1=qp.brush()
qp.drawEllipse(-rCmb, -rCmb, 2*rCmb, 2*rCmb) # big chamber
qp.drawEllipse(-rTrg, -rTrg, 2*rTrg, 2*rTrg) # target
qp.drawLine(0,-rCmb-50,0,-rTrg) #beam
qp.rotate(-aaSeal)
qp.translate(0,rCmb)
tf1=qp.transform()
xs2=xs//2
xa2=xa//2
ya2=ya//2
d=int(abs(aaSeal-aaArm)*20)
r=min(155+d,255)
gb=max(155-d,0)
qp.setBrush(QColor(r, gb, gb, 255))
qp.drawRect(-xs2, 0, xs, ys) # seal bellow
qp.setBrush(br1)
qp.setTransform(tf0)
qp.rotate(-aaArm)
qp.translate(0,rCmb+ys)
tf2=qp.transform()
qp.drawRect(-xa2, 0, xa, ya) # girder
#qp.drawEllipse(-ya2, 100+150, 20, 20) #pusher left
#qp.drawEllipse( ya2-r, 100+150, 20, 20) #pusher right
#air pad
r,g,b=WndGirderRIXS._lutColor[ap]
rd=13
r3=int(rd*np.sqrt(3))
qp.setBrush(QColor(r, g, b, 255))
qp.drawEllipse(-xa2-rd, ya-4*rd, 2*rd, 2*rd) #left
qp.drawEllipse(-xa2-rd+r3,ya-3*rd, 2*rd, 2*rd) #left
qp.drawEllipse(-xa2-rd+r3,ya-5*rd, 2*rd, 2*rd) #left
qp.drawEllipse( xa2-rd, ya-4*rd, 2*rd, 2*rd) #right
qp.drawEllipse( xa2-rd-r3,ya-3*rd, 2*rd, 2*rd) #right
qp.drawEllipse( xa2-rd-r3,ya-5*rd, 2*rd, 2*rd) #right
qp.drawEllipse( -rd, 3*rd, 2*rd, 2*rd) #grating
qp.drawEllipse( 0, 3*rd-r3, 2*rd, 2*rd) #grating
qp.drawEllipse( -2*rd, 3*rd-r3, 2*rd, 2*rd) #grating
qp.setBrush(br1)
#deformation compensation
r,g,b=WndGirderRIXS._lutColor[dc]
rd=14
qp.setBrush(QColor(r, g, b, 255))
qp.drawEllipse(-xa2, ya2, 2*rd, 2*rd) #air pad left
qp.drawEllipse( xa2-2*rd, ya2, 2*rd, 2*rd) #air pad right
#qp.drawEllipse( -rd, rd, 2*rd, 2*rd) #air pad grating
qp.setBrush(br1)
qp.end()