import numpy as np import tomllib from scipy.optimize import root_scalar import xraylib from transfocator_calcs import lookup_diameter, materials_to_deltas, materials_to_linear_attenuatio, calc_lookup_tablen MAT_MACRO = 'MAT' NLENS_MACRO = 'NUMLENS' RADIUS_MACRO = 'RADIUS' LOC_MACRO = 'LOC' THICKERR_MACRO = 'THICKERR' ''' Config variables Beam Properties energy : energy in keV L_und : undulator length in m sigmaH_e : Sigma electron source size in H direction in m sigmaV_e : Sigma electron source size in V direction in m sigmaHp : Sigma electron divergence in H direction in rad sigmaVp_e : Sigma electron divergence in V direction in rad Beamline properties d_StoL1 : Source-to-CRL1 distance, in m d_Stof : Source-to-focus distance, in m CRL properties d_min : Minimum thickness at the apex in m stack_d : Stack thickness in m Slit properties ''' DEFAULT_CONFIG = {'beam':{'energy': 15, 'L_und': 4.7, 'sigmaH_e': 14.8e-6, 'sigmaV_e': 3.7e-6, 'sigmaHp_e': 2.8e-6, 'sigmaVp_e': 1.5e-6}, 'beam_line': {'d_StoL': 51.9, 'd_Stof': 66.2}, 'crl':{'stack_d': 50.0e-3, 'd_min': 3.0e-5}, 'slits':{}} """ pyDevice TO DO: WHAT inputs change the focal size arrays? Energy, what else? WHAT inputs change the search through the arrays? desired focal size, what else? IOC init functions -get lens stack parameters (# of lenses in each stack, radius, location, thickness, thickness error) -- from substitution file but put into PVs? Update with autosave? -get source info -energy from from ID IOC -hor/vert sizes and divergence (also energy dependent) -lens diameter table? What is it doing? -desired focal size is changed --> what needs updating? --> nothing, just need to search focal size array again -multiple flags: is focal size achievable? is it achievable at sample? recalc function -- should probably be same as init function -energy is updated --> what needs updating? -what else could user/staff change? sample position? """ # Beamline input block energy = 15000.0 # Energy in eV energy_keV = energy/1000.0 # Energy in keV wl = 1239.84 / (energy * 10**9) d_StoL1 = 51.9 # Source-to-CRL1 distance, in m d_StoL2 = 62.1 # Source-to-CRL2 distance, in m d_Stof = 66.2 # Source-to-focus distance, in m #slit1_H = 500.0e-6 # H slit size before CRL 1 #slit1_V = 300.0e-6 # V slit size before CRL 1 # CRL input block d_min = 3.0e-5 # Minimum thickness at the apex in m stack_d = 50.0e-3 # Stack thickness in m L1_n = np.array([1, 1, 1, 1, 1, 1, 2, 4, 8, 16]) # CRL1 number of lenses in each stack L1_R = np.array([2.0e-3, 1.0e-3, 5.0e-4, 3.0e-4, 2.0e-4, 1.0e-4, 1.0e-4, 1.0e-4, 1.0e-4, 1.0e-4]) # CRL1 lens radius in each stack L1_mater= np.array(["Be", "Be", "Be", "Be", "Be", "Be", "Be", "Be", "Be", "Be"]) # CRL1 lens material in each stack L1_loc = np.array([4.5, 3.5, 2.5, 1.5, 0.5, -0.5, -1.5, -2.5, -3.5, -4.5])*stack_d # CRL1 lens stack location relative to center stack, positive means upstream L1_HE = np.array([1.0e-6, 1.0e-6, 1.0e-6, 1.0e-6, 1.0e-6, 1.0e-6, 1.4e-6, 2.0e-6, 2.8e-6, 4.0e-6]) # CRL1 lens RMS thickness error # Source size input block L_und = 4.7 # undulator length sigmaH_e = 14.8e-6 # Sigma electron source size in H direction in m sigmaV_e = 3.7e-6 # Sigma electron source size in V direction in m sigmaHp_e = 2.8e-6 # Sigma electron divergence in H direction in rad sigmaVp_e = 1.5e-6 # Sigma electron divergence in V direction in rad sigmaH = (sigmaH_e**2 + wl*L_und/2/np.pi/np.pi)**0.5 sigmaV = (sigmaV_e**2 + wl*L_und/2/np.pi/np.pi)**0.5 sigmaHp = (sigmaHp_e**2 + wl/L_und/2)**0.5 sigmaVp = (sigmaVp_e**2 + wl/L_und/2)**0.5 def absorptionaperture(x, n1mud, sigma, n1mur): numerator = np.exp(-(x**2/(2*sigma**2))) * np.exp(-n1mur*(x**2) - n1mud) denominator = np.exp(-n1mud) return numerator / denominator - 0.5 def find_levels(array, levels, direction='forward'): """ Find the first indices at which the array crosses specified levels and the corresponding crossed values. Parameters: array (numpy.ndarray): An array of numbers. levels (float or numpy.ndarray): A number or an array of levels to find crossings. direction (str, optional): The searching direction. Defaults to 'forward'. Can be either 'forward' or 'backward'. Returns: tuple: A tuple containing two arrays: - An array of first indices at which the array crosses the specified levels. - An array of first crossed values at the corresponding indices. """ # Convert a single level to a numpy array if isinstance(levels, (int, float)): levels = np.array([levels]) indices = [] values = [] # Compute the max and min of the array ignoring NaNs max_val = np.nanmax(array) min_val = np.nanmin(array) for level in levels: # If level is out of bounds if level > max_val or level < min_val: indices.append(-1) values.append(np.nan) continue crossings = [] if direction == 'forward': for i in range(1, len(array)): if np.isnan(array[i - 1]) or np.isnan(array[i]): continue if (array[i - 1] < level <= array[i]) or (array[i - 1] > level >= array[i]): crossings.append(i - 1) break elif direction == 'backward': for i in range(len(array) - 2, -1, -1): if np.isnan(array[i + 1]) or np.isnan(array[i]): continue if (array[i + 1] < level <= array[i]) or (array[i + 1] > level >= array[i]): crossings.append(i) break else: raise ValueError("Invalid direction. It should be either 'forward' or 'backward'.") if len(crossings) > 0: idx = crossings[0] indices.append(idx) values.append(array[idx]) else: # In case no crossing is found within the range indices.append(-1) values.append(np.nan) return np.array(indices), np.array(values) def Single_CRL2D_control(fsize): L1_D = np.zeros(L1_R.size) # CRL1 diameters for each stack for i in range(L1_R.size): L1_D[i] = lookup_diameter(L1_R[i]) L1_delta = materials_to_deltas(L1_mater, energy_keV) # delta values for CRL1 stacks L1_mu = materials_to_linear_attenuation(L1_mater, energy_keV) # mu values for CRL1 stacks L1_Feq = L1_R/(2*L1_n*L1_delta) + L1_loc # CRL1 equivalent f in m for each stack L1_index_n = 2**L1_Feq.size # Total number of combinations for CRL1 L1_invF_list= np.zeros(L1_index_n) # List of equivalent 1/f in m^-1 for CRL1 for i in range(L1_index_n): L1_invF_list[i] = np.sum(index_to_binary_list(i, L1_Feq.size)/L1_Feq) # Sort the L1_invF list (to avoid zigzagging) L1_invF_list_sort_indices = np.argsort(L1_invF_list) L1_invF_list_sorted = L1_invF_list[L1_invF_list_sort_indices] q1_list = 1/(L1_invF_list_sorted - 1/d_StoL1) # focal position of CRL1 for all configurations (sorted) dq1_list = q1_list - (d_Stof - d_StoL1) # Start generating focal size list as a function of CRL1 setting sigma1H = (sigmaH**2 + (sigmaHp*d_StoL1)**2)**0.5 # sigma beam size before CRL1 sigma1V = (sigmaV**2 + (sigmaVp*d_StoL1)**2)**0.5 # sigma beam size before CRL1 L1_n1mud_list = np.zeros(L1_index_n) # List of n1*mu*d_min for all possible CRL1 configurations L1_n1muR_list = np.zeros(L1_index_n) # List of n1*mu/R for all possible CRL1 configurations aperL1H_list = np.zeros(L1_index_n) # absorption H aperture of CRL1 for all configurations aperL1V_list = np.zeros(L1_index_n) # absorption V aperture of CRL1 for all configurations diameter1_list = np.zeros(L1_index_n) # CRL1 diameter for all possible configurations FWHM1H_list = np.zeros(L1_index_n) # H focal size at the focus of CRL1 FWHM1V_list = np.zeros(L1_index_n) # V focal size at the focus of CRL1 Strehl_list = np.zeros(L1_index_n) # Strehl ratio based on lens thickness error for i in range(L1_index_n): # absorption aperture is a function of CRL absorption/physical aperture, incident beam size, and physical slits L1_n1mud_list[i] = np.sum(index_to_binary_list(L1_invF_list_sort_indices[i], L1_Feq.size)*np.array(L1_mu*L1_n*d_min)) L1_n1muR_list[i] = np.sum(index_to_binary_list(L1_invF_list_sort_indices[i], L1_Feq.size)*np.array(L1_mu*L1_n/L1_R)) solution = root_scalar(absorptionaperture, args=(L1_n1mud_list[i], sigma1H, L1_n1muR_list[i]), bracket=[0.0, 2*sigma1H], method='bisect') aperL1H_list[i] = solution.root*2.0 solution = root_scalar(absorptionaperture, args=(L1_n1mud_list[i], sigma1V, L1_n1muR_list[i]), bracket=[0.0, 2*sigma1V], method='bisect') aperL1V_list[i] = solution.root*2.0 mask = (np.array(index_to_binary_list(L1_invF_list_sort_indices[i], L1_Feq.size)) == 1) if np.all(mask == False): diameter1_list[i] = np.inf else: diameter1_list[i] = np.min(L1_D[mask]) aperL1H_list[i] = min(aperL1H_list[i], diameter1_list[i], slit1_H) aperL1V_list[i] = min(aperL1V_list[i], diameter1_list[i], slit1_V) phase_error_tmp = np.linalg.norm(index_to_binary_list(L1_invF_list_sort_indices[i], L1_Feq.size)*np.array(L1_HE*L1_delta)*2*np.pi/wl) Strehl_list[i] = np.exp(-phase_error_tmp**2) # FWHMbeam size at CRL1 focus FWHM1H_list = ((0.88*wl*q1_list/aperL1H_list)**2 + (2.355*sigmaH*q1_list/d_StoL1)**2)**0.5 FWHM1V_list = ((0.88*wl*q1_list/aperL1V_list)**2 + (2.355*sigmaV*q1_list/d_StoL1)**2)**0.5 if flag_HE: FWHM1H_list *= (Strehl_list)**(-0.5) FWHM1V_list *= (Strehl_list)**(-0.5) FWHM_list = (FWHM1H_list*FWHM1V_list)**0.5 indices, values = find_levels(FWHM_list, fsize, direction='backward') index = indices[0] if index == -1: print(f"Cannot achieve the focal size {fsize*1.0e6:.2f} μm") else: print("======== Find size at focus ========================================") print(f"Energy: {energy_keV} keV") print(f"CRL1 configuration index in sorted list is {index}") print(f"CRL1 configuration index is {L1_invF_list_sort_indices[index]} or {index_to_binary_list(L1_invF_list_sort_indices[index], L1_Feq.size)}") print(f"CRL1 f is {1/L1_invF_list_sorted[index]:.2f} m, focus at q1 = {q1_list[index]:.2f} m") print(f"Focal size is {FWHM1H_list[index]*1.0e6:.2f} μm x {FWHM1V_list[index]*1.0e6:.2f} μm at the focal point ({dq1_list[index]*1e3:.1f} mm from sample)") FWHM1H_atsample_list = (FWHM1H_list**2 + (aperL1H_list*dq1_list/q1_list)**2)**0.5 FWHM1V_atsample_list = (FWHM1V_list**2 + (aperL1V_list*dq1_list/q1_list)**2)**0.5 FWHM_atsample_list = (FWHM1H_atsample_list*FWHM1V_atsample_list)**0.5 indices, values = find_levels(FWHM_atsample_list, fsize, direction='forward') index2 = indices[0] if index2 == -1: print(f"Cannot achieve the bame size {fsize*1.0e6:.2f} μm at sample") else: print("======== Find size at sample =======================================") print(f"CRL1 configuration index in sorted list is {index2}") print(f"CRL1 configuration index is {L1_invF_list_sort_indices[index2]} or {index_to_binary_list(L1_invF_list_sort_indices[index2], L1_Feq.size)}") print(f"CRL1 f is {1/L1_invF_list_sorted[index2]:.2f} m, focus at q1 = {q1_list[index2]:.2f} m ({dq1_list[index2]*1e3:.1f} mm from sample)") print(f"Beam size is {FWHM1H_atsample_list[index2]*1.0e6:.2f} μm x {FWHM1V_atsample_list[index2]*1.0e6:.2f} μm at the sample position)") indices, values = find_levels(dq1_list, 0.0, direction='backward') index3 = indices[0] if index == -1: print(f"Cannot find combination to focus close to sample") else: print("======== Find configuration focus close to the sample ==============") print(f"CRL1 configuration index in sorted list is {index3}") print(f"CRL1 configuration index is {L1_invF_list_sort_indices[index3]} or {index_to_binary_list(L1_invF_list_sort_indices[index3], L1_Feq.size)}") print(f"CRL1 f is {1/L1_invF_list_sorted[index3]:.2f} m, focus at q1 = {q1_list[index3]:.2f} m ({dq1_list[index3]*1e3:.1f} mm from sample)") print(f"Beam size is {FWHM1H_atsample_list[index3]*1.0e6:.2f} μm x {FWHM1V_atsample_list[index3]*1.0e6:.2f} μm at the sample position)") return if __name__ == "__main__": flag_HE = True fsize = 50.0e-6 # Desired focal size in m (area average of h and v size) #Single_CRL2D_control(fsize) # Find the best configuration for a single transfocator system ''' Update the following to accommodate XS code ''' class singleTF(): def __init__(self, crl_setup = None, beam_config = DEFAULT_CONFIG['beam'], beamline_config = DEFAULT_CONFIG['beamline'], crl_config = DEFAULT_CONFIG['crl'], slits_config = DEFAULT_CONFIG['slits']): if crl_setup is None: beam = beam_config beamline = beamline_config crl = crl_config slits = slits_config else: with open(crl_setup, "rb") as f: config = tomllib.load(f) beam = config['beam'] beamline = config['beamline'] crl = config['crl'] slits = config['slits'] self.setupSource(beam) self.setupBeamline(beamline) self.setupCRL(crl) self.setupSlits(slits) # Initialize lens variables self.L1_n = np.array([1, 1, 1, 1, 1, 1, 2, 4, 8, 16]) # CRL1 number of lenses in each stack self.L1_R = np.array([2.0e-3, 1.0e-3, 5.0e-4, 3.0e-4, 2.0e-4, 1.0e-4, 1.0e-4, 1.0e-4, 1.0e-4, 1.0e-4]) # CRL1 lens radius in each stack self.L1_mater= np.array(["Be", "Be", "Be", "Be", "Be", "Be", "Be", "Be", "Be", "Be"]) # CRL1 lens material in each stack self.L1_loc = np.array([4.5, 3.5, 2.5, 1.5, 0.5, -0.5, -1.5, -2.5, -3.5, -4.5])*stack_d # CRL1 lens stack location relative to center stack, positive means upstream self.L1_HE = np.array([1.0e-6, 1.0e-6, 1.0e-6, 1.0e-6, 1.0e-6, 1.0e-6, 1.4e-6, 2.0e-6, 2.8e-6, 4.0e-6]) # CRL1 lens RMS thickness error self.Lens_diameter_table = [ (50, 450.0), (100, 632.0), (200, 894.0), (300, 1095.0), (500, 1414.0), (1000, 2000.0), (1500, 2450.0), ] # Convert the lookup table to a dictionary for faster lookup self.Lens_diameter_dict = {int(col1): col2 for col1, col2 in Lens_diameter_table} # Initialize pre-CRL slit size self.slit1_H = 500.0e-6 # H slit size before CRL 1 self.slit1_V = 300.0e-6 # V slit size before CRL 1 self.energy = 0 # gets value from an ao (incoming beam energy) self.focalSize = 0 # get value from an ao (desired focal length) self.lenses = 0 # sets integer (2^12) whose binary representation indicates which lenses are in or out self.num_lense = 12 # Number of lenses in system self.verbosity = True self.lookupTable = [] def setupSource(self, beam_properties): ''' Beam properties can have entries for the following energy : energy in keV L_und : undulator length in m sigmaH_e : Sigma electron source size in H direction in m sigmaV_e : Sigma electron source size in V direction in m sigmaHp : Sigma electron divergence in H direction in rad sigmaVp_e : Sigma electron divergence in V direction in rad ''' self.setEnergy(beam_properites['energy']) self.L_und = beam_properties['L_und'] self.sigmaH_e = beam_properties['sigmaH_e'] self.sigmaV_e = beam_properties['sigmaV_e'] self.sigmaHp_e = beam_properties['sigmaHp_e'] self.sigmaVp_e = beam_properties['sigmaVp_e'] self.sigmaH = (self.sigmaH_e**2 + self.wl*self.L_und/2/np.pi/np.pi)**0.5 self.sigmaV = (self.sigmaV_e**2 + self.wl*self.L_und/2/np.pi/np.pi)**0.5 self.sigmaHp = (self.sigmaHp_e**2 + self.wl/self.L_und/2)**0.5 self.sigmaVp = (self.sigmaVp_e**2 + self.wl/self.L_und/2)**0.5 def setupBeamline(self, beamline_properties): ''' Beamline properties can contain entries for the following d_StoL1 : Source-to-CRL1 distance, in m d_Stof : Source-to-focus distance, in m ''' self.d_StoL = beam_properties['d_StoL'] self.d_Stof = beam_properties['d_Stof'] def setupCRL(self, crl_properties): ''' CRL properties can contiain entries for the following d_min : Minimum thickness at the apex in m stack_d : Stack thickness in m ''' self.d_min = crl_properties['d_min'] self.stack_d = crl_properties['stack_d'] def setupSlits(self, slit_properties): ''' Setting up properties of slits ''' #Default values pass def setLensCount(self, lensCount): self.numLens = def setupLookupTable(self, subs_file, n_lenses, energy = 8.0): ''' lookup table created after IOC startup (after filter materials and thicknesses are set ''' print(80*'#') print('Setting up lens control...') self.num_lenses = n_lenses self.energy = energy #read in substitutions file try: subsFile = open(subs_file,"r") except: raise RuntimeError(f"Substiution file ({subsFile}) not found.") subsFileContent = subsFile.readlines() subsFile.close() macros = subsFileContent[2].replace('{','').replace('}','').replace(',','').split() lens_properties = {key: [] for key in macros} # dictionary of lists for i in range(self.num_filters): try: xx = subsFileContent[3+i].replace('{','').replace('}','').replace(',','').replace('"','').split() lens_properties[macros[0]].append(xx[0]) lens_properties[macros[1]].append(xx[1]) lens_properties[macros[2]].append(xx[2]) lens_properties[macros[3]].append(xx[3]) lens_properties[macros[4]].append(xx[4]) lens_properties[macros[5]].append(xx[5]) lens_properties[macros[6]].append(xx[6]) except: raise RuntimeError(f"Number of lenses ({self.num_lenses}) doesn't match substitution file") self.numlens = [] self.radius = [] self.materials = [] self.lens_loc = [] self.lens_thickerr = [] # get number of lens for each lens from lens properties dictionary-list print('Getting lens materials...') if NLENS_MACRO in macros: self.numlens = lens_properties[NLENS_MACRO] print('Number of lens read in.\n') else: raise RuntimeError(f"Number of lenses macro ({NLENS_MACRO}) not found in substituion file") # get radii for each lens from lens properties dictionary-list print('Getting lens\' radii...') if RAD_MACRO in macros: self.radius = lens_properties[RAD_MACRO] print('Radius of lenses read in.\n') else: raise RuntimeError(f"Radius macro ({RAD_MACRO}) not found in substituion file") # get materials from lens properties dictionary-list print('Getting lens materials...') if MAT_MACRO in macros: self.materials = lens_properties[MAT_MACRO] print('Lens material read in.\n') else: raise RuntimeError(f"Material macro ({MAT_MACRO}) not found in substituion file") # get densities from local definition (for compounds) or from xraylib (for elements) densities = get_densities(self.materials) self.densities = [densities[material] for material in self.materials] # get location of each lens from lens properties dictionary-list print('Getting lens\' locations...') if LOC_MACRO in macros: self.lens_loc = lens_properties[LOC_MACRO]*self.stack_d print('Location of lenses read in.\n') else: raise RuntimeError(f"Location macro ({RAD_MACRO}) not found in substituion file") # get thicknesses errprfrom lens properties dictionary-list print('Getting lens thickness error...') if TERR_MACRO in macros: self.lens_thickerr = [float(i) for i in lens_properties[TERR_MACRO]] print('Lens thickness errors read in.\n') else: raise RuntimeError(f"Thickness errors macro ({TERR_MACRO}) not found in substituion file") print('Constructing lookup table...') self.construct_lookup_table() print('Lookup table calculation complete.\n') print('Filter control setup complete.') print(80*'#') def construct_lookup_table(self): self.lookupTable = calc_lookup_table(self.num_configs, self.radius, self.material, self.energy, self.numlens, self.lens_loc) self.culledTable() def cull_lookup_table(self): ''' Culls the lookup table based on lenses that are locked and or disabled ''' self.culledSize = 2**(self.num_lenses - (self.outMask | self.inMask).bit_count()) if self.verbose: print(f'Operating spaced now at {self.culledSize} configurations') if self.verbose: print(f'Culling table with in mask {self.inMask} and out mask {self.outMask}') self.culledConfigs = np.empty(self.culledSize, dtype=int) self.culledTable = np.empty(self.culledSize) j = 0 for i in range(2**self.num_lenses): if ((i & self.outMask == 0) and (i & self.inMask == self.inMask)): self.culledConfigs[j]=i self.culledTable[j] = self.lookupTable[i] j += 1 self.sort_lookup_table() def sort_lookup_table(self): ''' ''' if self.verbose: print(f'Sorting culled lookup table of length {len(self.culledTable)}') self.sorted_index = np.argsort(self.culledTable) def setInMask(self, inMask): ''' update mask for lenses that are locked in ''' self.inMask = int(inMask) self.cull_lookup_table() if self.verbose: print(f'Converting culled index via in Mask') self.convertCulledIndex() if self.verbose: print(f'Updating filter RBV via in Mask') self.updateLensRBV() if self.verbose: print(f'Setting in mask RBV to {self.inMask}') pydev.iointr('new_inMask', int(self.inMask)) def setOutMask(self, outMask): ''' update mask for lenses that must remain out (either disabled or locked) ''' self.outMask = int(outMask) self.cull_lookup_table() if self.verbose: print(f'Converting culled index via out Mask') self.convertCulledIndex() if self.verbose: print(f'Updating filter RBV via out Mask') self.updateLensRBV() if self.verbose: print(f'Setting out mask RBV to {self.outMask}') pydev.iointr('new_outMask', int(self.outMask)) def convertCulledIndex(self): ''' When available configs change, need to update index so that tweaks continue to work ''' if self.verbose: print('Converting ...') self.culledIndex = (np.where(self.culledConfigs == self.config))[0][0] if self.verbose: print(f'Culled index is {self.culledIndex}') self.culledIndexSorted = self.sorted_index.tolist().index(self.culledIndex) if self.verbose: print(f'Sorted culled index is {self.culledIndexSorted}') def setFocalSizeActual(self): ''' ''' self.focalSize_actual = self.culledTable[self.culledIndex] def find_config(self): ''' User selected focal size, this function finds nearest acheivable focal size from the lookup table ''' # Code to search lookup table for nearest focal size to desired if self.verbose: print(f'Searching for config closest to {self.focalSize}') self.culledIndex = np.argmin(np.abs(self.culledTable - self.focalSize)) if self.verbose: print(f'Config index found at {self.culledIndex}') self.culledIndexSorted = self.sorted_index.tolist().index(self.culledIndex) if self.verbose: print(f'Sorted config index found at {self.culledIndexSorted}') # Update PVs self.setFocalSizeActual() self.updateLensConfigPV() self.updateLensRBV() self.updateFocalSizeRBVs() def updateSlitSize(self, size, slit): ''' Slit size updates are propagated to CRL object from EPICS. The beam size lookup table is then recalculated. ''' if slit = 'hor': self.slit1_H = float(size) # H slit size before CRL 1 elif slit == 'vert': self.slit1_V = float(size) # V slit size before CRL 1 else # Need error handling break self.construct_lookup_table() def setEnergy(self, energy): ''' Sets various forms of energy ''' self.energy = float(energy) self.energy_eV = self.energy*1000.0 # Energy in keV self.wl = 1239.84 / (self.energy_eV * 10**9) #Wavelength in nm(?) def updateE(self, energy): ''' Beam energy updates are propagated to CRL object from EPICS. The beam size lookup table is then recalculated. ''' # Energy variable sent from IOC as a string self.setEnergy(energy) self.construct_lookup_table() # Do I need to find what the current config would produce as far as focal size and location? # self.focalSizeRBV = def updateFsize(self, focalSize): ''' User updates desired focal size. Lookup table is traversed to find nearest to desired. ''' # focalPoint variable sent from IOC as a string self.focalSize = float(focalSize) self.find_config() # def calc_lenses(self): # self.lenses = (self.energy * self.focalPoint) % 4096 # pydev.iointr('new_lens_config', self.lenses) def updateLensConfigPV(self): ''' ''' self.config = self.culledConfigs[self.culledIndex] pydev.iointr('new_lenses', int(self.config)) def updateLensRBV(self): ''' ''' pydev.iointr('new_index', int(self.culledIndexSorted)) def updateFocalSizeRBVs(self): ''' ''' pydev.iointr('new_fSize', self.focalSize_actual) def updateVerbosity(self, verbosity): ''' Turn on minor printing ''' print(f'Verbosity set to {verbosity}') self.verbose = verbosity