prsoxr_script_gen.pyΒΆ
Example script generator:
"""
Runfile to generate a motor-position script to be read by ALS beamline 11.0.1.2.
"""
#Initializations
import numpy as np
import pandas as pd
import math
"""
Setup Parameters
"""
save_dir = 'my/path/to/save/location' # Location to save script
save_name = 'fancy_name' # Scipt name
#Store sample names and energies you want to run
sample_list = {} #Do not change this line
sample_list['Samp_A'] = [270.0, 284.5, 287.3, 288.7]
sample_list['Samp_B'] = [270.0, 284.3, 288.7]
sample_list['Samp_C'] = [250.0, 283.5, 284.8, 285.3]
#External energy calibration
EnergyOffset = 0 # [eV]
# Inputs to run sample plate
SampleOrder = ['Samp_A', 'Samp_B', 'Samp_C'] # Order that you want to run samples.
# Motor positions
XPosition = [36.52, 23.36, 0] # 'Sample X' motor position
XOffset = [0.15, 0.15, 0.15] # Offset to translate beam on sample after each energy.
YPosition = [-3.2150, -1.9750, 0] # 'Sample Y' motor position
ZPosition = [-0.27, -0.36, 0] # 'Sample Z' motor position
ZFlipPosition = [0.21, 0.15, 0] # 'Sample Z' motor position when 'Sample Theta' is flipped 180deg
ReverseHolder = [0, 0, 0] #Set to True (1) for a sample that is on the reverse side of the holder
ZDirectBeam = [-2, -2, -1] #'Sample Z' motor positions to access the direct beam
ThetaOffset = [0, -0.55, 0] #'Sample Theta' offset if measuring multiple samples
##
SampleThickness = [250, 250, 250] #Approximate film thickness [A], used to determine dq
LowAngleDensity = [15, 15, 15] #Approximate number of points per fringe at low angles
HighAngleDensity = [9, 9, 9] #Approximate number of points per fringe at high angles
AngleCrossover = [10, 10, 10] #Angle [deg] to cross from low -> high density
##
OverlapPoints = [3, 3, 3] #Number of overlap points upon changing motor. Used for stitching
CheckUncertainty = [3, 3, 3] #Number of points for assessing error at each change in motor conditions. Used for error reduction
HOSBuffer = [2, 2, 2] #Number of points to add upon changing HOS to account for motor movement.
I0Points = [10, 10, 10] #Number of I0 measureents taken at the start of the scan. Used to calculate direct beam uncertainty
#Initialize compilation lists:
VariableMotors = []
MotorPositions = {}
"""
COPY THIS BLOCK OF CODE FOR EACH SAMPLE
"""
#########################################
##### Sample 0 #####
#########################################
DEG = [] # Angles to change settings
HOS = [] # HOS positions
HES = [] # HES positions
EXP = [] # Exposures
#########################################
#########################################
##### Energy 1 #####
#########################################
DEG.append([1, 6, 10, 15, 20, 25, 30, 40, 75])
HOS.append([12, 11, 10, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5])
HES.append([150, 150, 150, 150, 150, 150, 150, 150, 150])
EXP.append([0.001, 0.001, 0.001, 0.001, 0.1, 0.5, 0.5, 0.5, 0.5])
#########################################
##### Energy 2 #####
#########################################
DEG.append([1, 4, 6, 10, 12, 15, 20, 25, 30, 40, 75])
HOS.append([12, 11.5, 12, 10, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5])
HES.append([1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500])
EXP.append([0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.1, 0.1, 0.1, 0.1, 0.1])
#########################################
##### Energy 3 #####
#########################################
DEG.append([1, 4, 6, 10, 12, 15, 20, 25, 30, 40, 75])
HOS.append([12, 11.5, 12, 10, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5])
HES.append([1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500])
EXP.append([0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.1, 0.1, 0.1, 0.1, 0.1])
#########################################
##### Energy 4 #####
#########################################
DEG.append([1, 4, 6, 10, 12, 15, 20, 25, 30, 40, 75])
HOS.append([12, 11.5, 12, 10, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5])
HES.append([1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500])
EXP.append([0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.1, 0.1, 0.1, 0.1, 0.1])
#Consolodate information
motors = zip(DEG, HOS, HES, EXP)
temp_list = []
columns = ['Angle', 'HOS', 'HES', 'EXPOSURE']
for deg, hos, hes, exp in motors:
temp_list.append(pd.DataFrame({'Angle':deg, 'HOS':hos, 'HES':hes, 'EXPOSURE':exp}))
VariableMotors.append(temp_list)
"""
STOP COPYING HERE
"""
#Add samples here ~~
"""
DON'T ADD SAMPLES BEYOND THIS POINT
"""
#Setting up meta-data
MotorPositions['XPosition'] = Xposition
MotorPositions['YPosition'] = Xposition
MotorPositions['ZPosition'] = Xposition
MotorPositions['XOffset'] = XOffset
MotorPositions['ZFlipPosition'] = ZFlipPosition
MotorPositions['ZDirectBeam'] = ZDirectBeam
MotorPositions['ThetaOffset'] = ThetaOffset
MotorPositions['ReverseHolder'] = ReverseHolder
MotorPositions['SampleThickness'] = SampleThickness
MotorPositions['LowAngleDensity'] = LowAngleDensity
MotorPositions['HighAngleDensity'] = HighAngleDensity
MotorPositions['AngleCrossover'] = AngleCrossover
MotorPositions['OverlapPoints'] = OverlapPoints
MotorPositions['CheckUncertainty'] = CheckUncertainty
MotorPositions['HOSBuffer'] = HOSBuffer
MotorPositions['I0Points'] = I0Points
df_motor = pd.DataFrame(MotorPositions)
#Function that will generate beamline inputs
def AngleRunGeneration(MotorPositions, VariableMotors, Energy):
#Constants ## https://www.nist.gov/si-redefinition
SOL = 299792458 #m/s
PLANCK_JOULE = 6.6267015e-34 #Joule s
ELEMCHARGE = 1.602176634e-19 #coulombs
PLANCK = PLANCK_JOULE / ELEMCHARGE #eV s
meterToAng = 10**(10)
##Initialization of needed components
Wavelength = SOL * PLANCK * meterToAng / Energy
AngleNumber = VariableMotors['Angle'].nunique()
XPosition = MotorPositions['XPosition']
YPosition = MotorPositions['YPosition']
ZPosition = MotorPositions['ZPosition']
Z180Position = MotorPositions['ZFlipPosition']
Zdelta = ZPosition - Z180Position
ThetaOffset = MotorPositions['ThetaOffset']
SampleThickness = MotorPositions['SampleThickness']
LowAngleDensity = MotorPositions['LowAngleDensity']
HighAngleDensity = MotorPositions['HighAngleDensity']
AngleCrossover = MotorPositions['AngleCrossover']
OverlapPoints = int(MotorPositions['OverlapPoints'])
for i in range(AngleNumber-1):
if i ==0: # starts the list
##Calculate the start and stop location for Q
AngleStart = VariableMotors['Angle'].iloc[i] # All of the relevant values are in terms of angles, but Q is calculated as a check
AngleStop = VariableMotors['Angle'].iloc[i+1]
QStart = 4*math.pi*math.sin(AngleStart*math.pi/180)/Wavelength
QStop = 4*math.pi*math.sin(AngleStop*math.pi/180)/Wavelength
if AngleStop <= AngleCrossover:
AngleDensity=LowAngleDensity
else:
AngleDensity=HighAngleDensity
#Setup dq in terms of an approximate fringe size (L = 2*PI/Thickness)
#Break it up based on the desired point density per fringe
dq=2*math.pi/(SampleThickness*AngleDensity)
QPoints = math.ceil((QStop-QStart)/dq) #Number of points to run is going to depend on fringe size
QList = np.linspace(QStart,QStop,QPoints).tolist() #Initialize the QList based on initial configuration
SampleTheta = np.linspace(AngleStart,AngleStop,QPoints) ##Begin generating list of 'Sample Theta' locations to take data
CCDTheta = SampleTheta*2 #Make corresponding CCDTheta positions
SampleTheta = SampleTheta+ThetaOffset #If running multiple samples in a row this will offset the sample theta based on alignment ##CCD THETA SHOULD NOT BE CHANGED
#Check what side the sample is on. If on the bottom, sample theta starts @ -180
#if MotorPositions['ReverseHolder'] == 1:
# SampleTheta=SampleTheta-180 # for samples on the backside of the holder, need to check and see if this is correct
SampleX=[XPosition]*len(QList)
BeamLineEnergy=[Energy]*len(QList)
SampleY = YPosition+Zdelta/2+Zdelta/2*np.sin(SampleTheta*math.pi/180) #Adjust 'Sample Y' based on the relative axis of rotation
SampleZ = ZPosition+Zdelta/2*(np.cos(SampleTheta*math.pi/180)-1) #Adjust 'Sample Z' based on the relative axis of rotation
#Convert numpy arrays into lists for Pandas generation
SampleTheta=SampleTheta.tolist()
SampleY=SampleY.tolist()
SampleZ=SampleZ.tolist()
CCDTheta=CCDTheta.tolist()
#Generate HOS / HES / Exposure lists for updating flux
HOSList=[VariableMotors['HOS'].iloc[i]]*len(QList)
HESList=[VariableMotors['HES'].iloc[i]]*len(QList)
ExposureList=[VariableMotors['EXPOSURE'].iloc[i]]*len(QList)
#Adding points to assess the error in beam intensity given new HOS / HES / Exposure conditions
for d in range(int(MotorPositions['I0Points'])):
QList.insert(0,0)
#ThetaInsert = 0 if MotorPositions['ReverseHolder']==0 else -180
SampleTheta.insert(0,0)
CCDTheta.insert(0,0)
SampleX.insert(0,SampleX[d])
SampleY.insert(0,YPosition)
SampleZ.insert(0,MotorPositions['ZDirectBeam'])
HOSList.insert(0,HOSList[d])
HESList.insert(0, HESList[d])
ExposureList.insert(0,ExposureList[d])
BeamLineEnergy.insert(0,BeamLineEnergy[d])
else: # for all of the ranges after the first set of samples
##Section is identical to the above
AngleStart=VariableMotors['Angle'].iloc[i]
AngleStop=VariableMotors['Angle'].iloc[i+1]
QStart=4*math.pi*math.sin(AngleStart*math.pi/180)/Wavelength
QStop=4*math.pi*math.sin(AngleStop*math.pi/180)/Wavelength
if AngleStop <= AngleCrossover:
AngleDensity=LowAngleDensity
else:
AngleDensity=HighAngleDensity
dq=2*math.pi/(SampleThickness*AngleDensity)
QPoints=math.ceil((QStop-QStart)/dq)
QListAddition=np.linspace(QStart,QStop,QPoints).tolist()
SampleThetaAddition=np.linspace(AngleStart,AngleStop,QPoints).tolist()
##Calculate the points that are used to stitch datasets
#p+2 selects the appropriate number of points to repeat without doubling at the start of the angle range.
#Compensate the number of points by reducing OverlapPoints down by 1 (Nominally at 4)
for p in range(OverlapPoints):
QListAddition.insert(0,QList[-1*(p+2)]) #Add to Qlist
SampleThetaAddition.insert(0,SampleTheta[-1*(p+2)]-ThetaOffset) #Add to Sample Theta List ###QUICK CHANGE! REMOVE SAMPLE OFFSET TO ADDITION
SampleThetaAdditionArray=np.asarray(SampleThetaAddition) #Convert back to numpy array
CCDThetaAddition=SampleThetaAdditionArray*2 #Calculate the CCD theta POsitions
CCDThetaAddition=CCDThetaAddition.tolist() #Convert to list
SampleThetaAdditionArray=SampleThetaAdditionArray+ThetaOffset #Account for theta offset
SampleThetaAddition = SampleThetaAdditionArray.tolist()
#Check what side the sample is on. If on the bottom, sample theta starts @ -180
#if MotorPositions['ReverseHolder']==1:
# SampleThetaAdditionArray=SampleThetaAdditionArray-180
SampleXAddition=[XPosition]*len(QListAddition)
BeamLineEnergyAddition=[Energy]*len(QListAddition)
SampleYAddition=YPosition+Zdelta/2+Zdelta/2*np.sin(SampleThetaAdditionArray*math.pi/180)
SampleZAddition=ZPosition+Zdelta/2*(np.cos(SampleThetaAdditionArray*math.pi/180)-1)
SampleYAddition=SampleYAddition.tolist()
SampleZAddition=SampleZAddition.tolist()
#Generate HOS / HES / Exposure lists for updating flux
HOSListAddition=[VariableMotors['HOS'].iloc[i]]*len(QListAddition)
HESListAddition = [VariableMotors['HES'].iloc[i]]*len(QListAddition)
ExposureListAddition=[VariableMotors['EXPOSURE'].iloc[i]]*len(QListAddition)
#Check to see if any of the variable motors have moved to add buffer points
if VariableMotors['HOS'].iloc[i] != VariableMotors['HOS'].iloc[i-1] or VariableMotors['HES'].iloc[i] != VariableMotors['HES'].iloc[i-1] or VariableMotors['EXPOSURE'].iloc[i] != VariableMotors['EXPOSURE'].iloc[i-1]:
#If a change is made, buffer the change with points to judge new counting statistics error and a few points to buffer the motor movements.
#Motor movements buffer is to make sure motors have fully moved before continuing data collection / may require post process changes
#Adding points to assess the error in beam intensity given new HOS / HES / Exposure conditions
for d in range(int(MotorPositions['CheckUncertainty'])):
QListAddition.insert(0,QListAddition[d])
SampleThetaAddition.insert(0,SampleThetaAddition[d])
CCDThetaAddition.insert(0,CCDThetaAddition[d])
SampleXAddition.insert(0,SampleXAddition[d])
SampleYAddition.insert(0,SampleYAddition[d])
SampleZAddition.insert(0,SampleZAddition[d])
HOSListAddition.insert(0,HOSListAddition[d])
HESListAddition.insert(0,HESListAddition[d])
ExposureListAddition.insert(0,ExposureListAddition[d])
BeamLineEnergyAddition.insert(0,BeamLineEnergyAddition[d])
#Adding dummy points to beginning of to account for HOS movement
for d in range(int(MotorPositions['HOSBuffer'])):
QListAddition.insert(0,QListAddition[d])
SampleThetaAddition.insert(0,SampleThetaAddition[d])
CCDThetaAddition.insert(0,CCDThetaAddition[d])
SampleXAddition.insert(0,SampleXAddition[d])
SampleYAddition.insert(0,SampleYAddition[d])
SampleZAddition.insert(0,SampleZAddition[d])
HOSListAddition.insert(0,HOSListAddition[d])
HESListAddition.insert(0,HESListAddition[d])
ExposureListAddition.insert(0,ExposureListAddition[d])
BeamLineEnergyAddition.insert(0,BeamLineEnergyAddition[d])
QList.extend(QListAddition)
HOSList.extend(HOSListAddition)
HESList.extend(HESListAddition)
ExposureList.extend(ExposureListAddition)
SampleTheta.extend(SampleThetaAddition)
CCDTheta.extend(CCDThetaAddition)
SampleX.extend(SampleXAddition)
SampleY.extend(SampleYAddition)
SampleZ.extend(SampleZAddition)
BeamLineEnergy.extend(BeamLineEnergyAddition)
#Check what side the sample is on. If on the bottom, sample theta starts @ -180
if MotorPositions['ReverseHolder'] == 1:
SampleTheta=[theta-180 for theta in SampleTheta] # for samples on the backside of the holder, need to check and see if this is correct
return (SampleX, SampleY, SampleZ, SampleTheta, CCDTheta, HOSList, HESList, BeamLineEnergy, ExposureList, QList)
##Generate the compiled dataframe for each sample
AdjustableMotors = ['Sample X', 'Sample Y', 'Sample Z',
'Sample Theta', 'CCD Theta', 'Higher Order Suppressor',
'Horizontal Exit Slit Size', 'Beamline Energy', 'Exposure'
]
RunFile = pd.DataFrame(columns = AdjustableMotors)
#NumSamples = len(VariableMotors) #The number of variable motor scans corresponding to each sample location
#NumEnergies = len(Energy) #The number of energies for each sample
for i, Samp in enumerate(VariableMotors):
en_list = sample_list[SampleOrder[i]]
for j, loc in enumerate(Samp):
sampdf = pd.DataFrame()
samp_energy = np.round(float(en_list[j] + EnergyOffset),2)
MotorPos_En = AngleRunGeneration(df_motor.iloc[i], loc, samp_energy)
sampdf['Sample X'] = np.round(np.round(MotorPos_En[0], 4) + XOffset[i]*j , 4)
sampdf['Sample Y'] = np.round(MotorPos_En[1], 4)
sampdf['Sample Z'] = np.round(MotorPos_En[2], 4)
sampdf['Sample Theta'] = np.round(MotorPos_En[3], 4)
sampdf['CCD Theta'] = np.round(MotorPos_En[4], 4)
sampdf['Higher Order Suppressor'] = MotorPos_En[5]
sampdf['Horizontal Exit Slit Size'] = MotorPos_En[6]
sampdf['Beamline Energy'] = MotorPos_En[7]
sampdf['Exposure'] = MotorPos_En[8]
RunFile = RunFile.append(sampdf, ignore_index=True)
#Cleanup
runfile_name = save_dir + save_name
HeaderFile = pd.DataFrame({key : pd.Series(val) for key, val in sample_list.items()})
RunFile.to_csv((runfile_name + '.txt'), index=False, sep='\t')
HeaderFile.to_csv((runfile_name+'_HEADER.txt'), index=False, sep='\t')
###Cleanup the output -- Definitly better ways to do this....
with open((runfile_name + '.txt'), 'r') as f: #Loads the file into memory
lines = f.readlines()
lines[0] = lines[0].replace('\tExposure' , '') #Remove the 'Exposure' header
lines[-1] = lines[-1].replace('\n', '') #Remove the last carriage return
with open((runfile_name + '.txt'), "w") as f: #Writes it back in
f.writelines(lines)
del lines #Remove it from memory (it can be large)