def as_srw(self, gap="min", energy=None, harmonic=1, **kwargs): use_srw = kwargs.get("use_srw", False) if energy is not None: pars = self.find_harmonic_and_gap(energy, sort_harmonics=True, use_srw=use_srw)[0] gap = pars["gap"] harmonic = pars["harmonic"] if isinstance(gap, str) and gap == "min": gap = self.min_gap import srwlib ebeam = beam.srw_ebeam(self.ebeam) harmB = srwlib.SRWLMagFldH() # magnetic field harmonic harmB.n = harmonic # harmonic number harmB.h_or_v = "v" # magnetic field plane: horzontal ('h') or vertical ('v') harmB.B = self.field(gap=gap) # magnetic field amplitude [T] und = srwlib.SRWLMagFldU([harmB]) und.per = self.period / 1e3 # period length [m] und.nPer = self.N # number of periods (will be rounded to integer) magFldCnt = srwlib.SRWLMagFldC( [und], srwlib.array("d", [0]), srwlib.array("d", [0]), srwlib.array("d", [0]), ) # Container of all magnetic field elements return ds(ebeam=ebeam, und=und, field=magFldCnt)
def as_gsm(self, gap="min", energy=None, harmonic=1, distance=None, **kwargs): use_srw = kwargs.get("use_srw", False) if energy is not None: pars = self.find_harmonic_and_gap(energy, sort_harmonics=True, use_srw=use_srw)[0] gap = pars["gap"] harmonic = pars["harmonic"] b = self.photon_beam_characteristics(gap=gap, harmonic=harmonic, **kwargs) gsmh = GSM_Numeric(rms_size=b.sh, rms_cl=b.gsm_sclh, wavelen=b.wavelength * 1e-10) gsmv = GSM_Numeric(rms_size=b.sv, rms_cl=b.gsm_sclv, wavelen=b.wavelength * 1e-10) if distance is not None: gsmh = gsmh.propagate(distance) gsmv = gsmv.propagate(distance) return ds(h=gsmh, v=gsmv)
def propagate(b0, aperture=20e-6, as_numpy=True): # optics is at 85, from source zb = free_space(85) # equivalent FL = 2.13 F = lens(2.13) a = gaussian_aperture(aperture) δ = sympy.Symbol("δ", real=True) za = free_space(2.13 + δ) b1 = b0.apply(zb) div = b0.divergence.evalf() * 1e6 print(f"Divergence @ source: {div:.2f} (μrad)") b2 = b1.apply(a) b3 = b2.apply(F) s1 = b1.rms_size.evalf() * 1e6 s2 = b2.rms_size.evalf() * 1e6 print(f"RMS size before aperture: {s1:.2f} (μm)") print(f"RMS size after aperture: {s2:.2f} (μm)") b4 = b3.apply(za) if as_numpy: size = b4.rms_size.evalf() size = sympy.lambdify(size.free_symbols, size) cl = b4.rms_cl.evalf() cl = sympy.lambdify(cl.free_symbols, cl) return ds(size=size, cl=cl)
def e_beam(lattice="EBS"): if isinstance(lattice, str): lattice = lattice.casefold() if not lattice in _e_lattices: raise KeyError( f"Can't find {lattice} in database; available lattices are (not case sensitive): {str(list(_e_lattices.keys()))}" ) e = ds(_e_lattices[lattice].copy()) else: e = ds(lattice.copy()) lattice = "source" e.emitth = e.sh * e.divh e.emittv = e.sv * e.divv e.betah = e.sh / e.divh e.betav = e.sv / e.divv e.name = lattice return e
def find_best_set_for_focal_length(self, energy=8, focal_length=10, accuracy_needed=0.1, verbose=False, beam_fwhm=None): """ find lensset that has a certain focal_length (within accuracy_needed) and transmission if beam_fwhm is provided (and sort_by_transmission is """ fl = self._focal_lengths(energy) delta_fl = np.abs(fl - focal_length) if np.isnan(focal_length): idx_best = 0 idx_good = np.zeros_like(fl, dtype=bool) idx_good[0] = True elif fl[np.isfinite(fl)].max() < focal_length * 5: idx_best = 0 idx_good = np.ones_like(fl, dtype=bool) else: idx_best = np.argmin(delta_fl) idx_good = delta_fl < accuracy_needed if idx_good.sum() == 0: if verbose: print( f"Could not find good set within required accuracy ({accuracy_needed:.3f}); will try with factor 2 bigger" ) return self.find_best_set_for_focal_length( energy=energy, focal_length=focal_length, accuracy_needed=2 * accuracy_needed, verbose=verbose) good_lensets = self.all_sets[idx_good] transmission = [ g.transmission_central_ray(energy) for g in good_lensets ] if beam_fwhm is not None: transmission_gauss_beam = [ g.transmission_gaussian_beam(energy, gauss_beam_fwhm=beam_fwhm) for g in good_lensets ] idx_best = np.argmax(transmission_gauss_beam) else: idx_best = np.argmin(delta_fl[idx_good]) transmission_gauss_beam = None # deep copying best_lens_set in case it is modified as return value ret = ds( in_out=self.all_confs[idx_good][idx_best], focal_length=self.all_sets[idx_good][idx_best].focal_length( energy), best_lens_set=copy.deepcopy(self.all_sets[idx_good][idx_best]), all_delta_fl=delta_fl, good_lensets=self.all_sets[idx_good], transmission_central_ray=transmission[idx_best], ) return ret
def ebs_minibeta(beta_h=6.8, beta_v=2.7): eps_h = 130e-12 eps_v = 10e-12 return ds(sh=np.sqrt(eps_h * beta_h), divh=np.sqrt(eps_h / beta_h), sv=np.sqrt(eps_v * beta_v), divv=np.sqrt(eps_v / beta_v), ebeam_energy=6, sr_cur=0.2, rms_energy_spread=0.00094, name="minibeta")
def find_mirror( E, mirrors=id18_mirrors, reflectivity=0.8, angles=np.linspace(2.5e-3, 4.5e-3, 201), reduction_factor=0.95, max_3E_reflectivity=1e-5, verbose=True, ): """ mirrors should be ordered from lighter to heavier element """ for mirror in mirrors: r = np.empty_like(angles) r3 = np.empty_like(angles) for i, a in enumerate(angles): r[i] = mirror.reflectivity2(E, a) r3[i] = mirror.reflectivity2(E * 3, a) idx1 = r > reflectivity idx3 = r3 < max_3E_reflectivity if (idx1 & idx3).sum() > 0: idx = np.argwhere(idx1 & idx3).ravel()[-1] # take biggest angle return ds( surface=mirror.material_name, angle=angles[idx], two_bounces_reflectivity=r[idx], two_bounces_reflectivity_3E=r3[idx], ) if verbose: print( f"Could not find any mirror with reflectivity@E>{reflectivity:.2f} and reflectivity@3E < {max_3E_reflectivity:.3e}" ) print( f"For {mirrors[-1].material_name}, max of reflectivity@E {r.max():.3e} and min reflectivity@3E {r3.min():.3e}" ) print(f"will try with reflectivity = {reflectivity*reduction_factor:.2f}") if reflectivity < 0.01: # start new search with higher max_3E_reflectivity return find_mirror( E, mirrors=mirrors, reflectivity=1, angles=angles, max_3E_reflectivity=max_3E_reflectivity*10, verbose=verbose, ) else: return find_mirror( E, mirrors=mirrors, reflectivity=reflectivity * reduction_factor, angles=angles, max_3E_reflectivity=max_3E_reflectivity, verbose=verbose, )
def beamsize_mono_vibrations(focus_dist=200, mono_dist=40, lens_dist=60, rms_vibration=0.1e-6, source_rms_size=5e-6, source_rms_div=5e-6, lens_rms_aperture=300e-6, z=None): """ based on ttp://dx.doi.org/10.1107/S16005775160111881 Parameters ---------- mono_dist : float distance from mono to source """ # eq 10 zi = focus_dist - lens_dist if z is None: z = np.linspace(zi - 10, zi + 10, 1001) zo = lens_dist zm = mono_dist beam_size = zi / zo * np.sqrt(source_rms_size**2 + (2 * zm * rms_vibration)**2) beam_size_no_vibration = zi / zo * source_rms_size A = lens_rms_aperture B = lens_dist * source_rms_div # r = zi / zo r2 = (zi / zo)**2 position_focus_from_lens = zi - zi * (2 * rms_vibration * zm)**2 * (1 / A**2 * r2 + 1 / B**2 * (r2 + r - zi / zm)) position_focus = position_focus_from_lens + lens_dist # eq 4 c_z = z / zo - (zo - zm) * (z - zi) / (zm * zi * (1 + B**2 / A**2)) s_0_z_squared = source_rms_size**2 * r2 + (z / zi - 1)**2 / (1 / A**2 + 1 / B**2) s_z_squared = s_0_z_squared + (2 * zm * rms_vibration * c_z)**2 / (1 + (2 * rms_vibration * (zo - zm))**2 / (A**2 + B**2)) s_z = np.sqrt(s_z_squared) ret = ds( rms_beamsize_at_dist=beam_size, fwhm_beamsize_at_dist=beam_size * 2.35, fractional_change_of_beamsize=(beam_size - beam_size_no_vibration) / beam_size_no_vibration, focus_position=position_focus, fraction_change_of_focus_position=(position_focus - focus_dist) / focus_dist, z=z + lens_dist, rms_beamsize=s_z) return ret
def f(z, z0=30, F=30): if isinstance(z, (float, int)): z = np.asarray([z]) res = dict() for bname in beams.keys(): res[bname] = dict() for name in attrs.keys(): res[bname][name] = dict() y = np.zeros_like(z) idx = z < z0 y[idx] = funcs[bname][name]["before"](z[idx]) * 1e6 y[~idx] = funcs[bname][name]["after"](z0, F, z[~idx] - z0) * 1e6 res[bname][name] = y return ds(res)
def __init__(self, photon_beam=None, sh=None, sv=None, divh=None, divv=None, wavelength=None): if photon_beam is None: photon_beam = srbeam.Photon_Beam() if sh is None: sh = photon_beam.sh if sv is None: sv = photon_beam.sv if divh is None: divh = photon_beam.divh if divv is None: divv = photon_beam.divv emitth = sh * divh emittv = sv * divv if wavelength is None: wavelength = photon_beam.wavelength if wavelength > 1e-6: wavelength = wavelength * 1e-10 beam = ds( sh=sh, sv=sv, divh=divh, divv=divv, emitth=emitth, emittv=emittv, wavelength=wavelength, ) self.beam = beam k = 2 * np.pi / wavelength # source coherence length ξ_{Sx,y} self.sclh = 2 * sh / np.sqrt(4 * k**2 * emitth**2 - 1) # eq 33 self.sclv = 2 * sv / np.sqrt(4 * k**2 * emittv**2 - 1) # eq 33 self.qh = self.sclh / sh self.qv = self.sclv / sv self.cofh = self.qh / np.sqrt(4 + self.qh**2) self.cofv = self.qv / np.sqrt(4 + self.qv**2) # cldiv = coherence length divergence self.cldivh = 1 / (2 * k * sh) * np.sqrt(4 + self.qh**2) self.cldivv = 1 / (2 * k * sv) * np.sqrt(4 + self.qv**2)
def rms_size_at_dist(self, D=100): sh = _sqrt_squares(self.sh, self.divh * D) sv = _sqrt_squares(self.sv, self.divv * D) divh = self.divh divv = self.divv return ds(sh=sh, sv=sv, divh=divh, divv=divv)
def _general_scan(motors=None,positions=None,acquire=None,fname=None,force=False): """ general porpouse scanning macro Parameters ---------- motors : list|tuple each item should be a motor/stage positions : 2D arrayable 2D array scan_point,num_mot acquire : function a function that must be provided, it must return a dictionary with items to pack as output the keys starting with _ will only be included one (and not in all scan points) example: def read(): time.sleep(1); return dict(val=3,_x=np.arange(10)) like data structure """ if fname is None: print("Must give fname") return # motors must be a list if not isinstance(motors,Iterable): motors = [motors,] # positions bust be 2D-like array positions = np.asarray( positions ) if positions.ndim == 1: positions = positions[:,np.newaxis] info = ds() info.num_motors = len(motors) info.motors = [m.mne for m in motors] info.motors_paramters = ds() for m in motors: info.motors_paramters[m.mne] = m.get_info_str() info.positions = np.squeeze(positions) info.npoints_per_axis = _get_npoints(positions) info.time_start = now() # ensure we are using pathlib fname = pathlib.Path(fname) # check if file exists if fname.exists() and not force: print("File %s exists, returning"%fname) return else: fname.parent.mkdir(exist_ok=True,parents=True) print("Will save in",str(fname)) data_buffer = [] # can't use enumerate because of tqdm missing support (at least for 4.11) for iscan in tqdm.trange(len(positions)): acquire_positions = positions[iscan] tosave = ds(info=info) # first axis is scan points tosave.positions = np.squeeze(positions[:iscan+1]) tosave.npoints_per_axis = _get_npoints(positions[:iscan+1]) # ask to move motors to positions for motor,position in zip(motors,acquire_positions): motor.move(position) # wait until they arrive for motor in motors: motor.wait() _data = acquire() data_buffer.append( _data ) for key in _data: if key[0] == "_": continue tosave[key] = np.asarray( [data[key] for data in data_buffer] ) tosave[key] = np.squeeze( tosave[key] ) keys_tosave_once = list(_data.keys()) keys_tosave_once = [key for key in keys_tosave_once if key[0] == "_"] for key in keys_tosave_once: tosave[key.strip("_")] = _data[key] tosave.info.time_last_save = now() tosave.save(str(fname)) return tosave
def propagate( beam=id18h, optics=[[40, "x1", "coll"], [150, "x1", "focus@200"]], z=np.arange(0, 230, 0.5), use_transfocator=True, transfocator=transfocator, fixed_f=None, fname=None, force=False, ): """ beam is a GSM or a GSM_Numeric beam optics = [ [ pos1, aperture1, coll|flat|focus@dist|sizeUM@dist,None ], [ pos2, aperture2, coll|flat|focus@dist|sizeUM@dism,None ], [ .................................... ], ] sizeUM@dist means that FL to get as close as possible to UM um at a certain distance will be calculated (and used) if optics element starts with 'crl_', a CRL lens set will be used. The use of lenses can be 'forced' using use_transfocator=True transfocator is either an istance of crl.Transfocator or a dictionary of transfocator instances (key is the distance from source) fixed_f is to take into account constrains in one direction (e.g. h) imposed by having fixed focal length or lenses in v direction It should be a dictionary of focal length or CRL lenset (key is distance) aperture can be an: - absolute value, "x1.2" is 1.2 times the FWHM CL, coherence length, None """ print( "WARNING: hard apertures are implemented as shown in Vartanyans 2013 JSR paper" ) print(" They are an approximations (valid in the far field?)") if not force and fname is not None and os.path.isfile(fname): data = datastorage.read(fname) return data info = ds() energy = 12.398 / (beam.wavelen * 1e10) positions = [0] apertures = [None] focal_lengths = [None] desired_focal_lengths = [None] lenses = [None] beams_before_aperture_before_optics = [beam] beams_after_aperture_before_optics = [beam] beams_after_aperture_after_optics = [beam] log = ["source"] for i, o in enumerate(optics, start=1): _log = [] _log.append(f"Working on optics element {i}") _pos, _aperture, _element = o if isinstance(_element, (int, float)): fl = _element _element = "" # to make if statements below happy _log.append(f"Explicitly asked to use {fl:.3f} focal_length") if _element is not None and _element.startswith("crl_"): _use_transfocator = True _element = _element[:4] _log.append( "Will use CRL for this element because element name starts with crl_" ) else: _use_transfocator = False _use_transfocator = _use_transfocator or use_transfocator positions.append(_pos) dpos = _pos - positions[i - 1] # babo = before aperture, before optics babo = beams_after_aperture_after_optics[-1].propagate(dpos) ### WORK ON APERTURE ### # aabo = after aperture, before optics if _aperture is None: aabo = babo apertures.append(None) _log.append("no aperture for this element") else: if isinstance(_aperture, str): # "x1.2" aperture_as_fraction_of_cl = float(_aperture[1:]) _aperture = aperture_as_fraction_of_cl * babo.rms_cl * 2.35 _log.append( f"aperture defined as {aperture_as_fraction_of_cl:.2f} of FWHM CL {babo.rms_cl * 2.35:.2e} m" ) _log.append(f"aperture of {_aperture:.2e} m") apertures.append(_aperture) _aperture = hard_aperture(_aperture) aabo = babo.apply(_aperture) ### WORK ON FOCUSING OPTICS ### # aaao = after aperture, after optics if _element is None: aaao = aabo focal_lengths.append(None) desired_focal_lengths.append(None) lenses.append(None) _log.append(f"No focusing optics for this element") else: if fixed_f is not None and _pos in fixed_f: c = fixed_f[_pos] _log.append("Adding constrain from other direction:" + str(c)) if isinstance(c, (float, int)): c = lens(c) aabo = aabo.apply(c) if _element[:4].lower() == "coll": fl = aabo.radius _log.append( f"Asked for collimating, will try to use focal length = radius of curvature {fl:.3e} m" ) if _element[:5].lower() == "focus": where_to_focus = float(_element.split("@")[1]) dist_to_focus = where_to_focus - _pos _log.append( f"Asked for focusing at {where_to_focus:.3f} m from source (meaning {dist_to_focus:.3f} m from optical element)" ) fl = find_fl(aabo, dist_to_focus) _log.append(f"Found the FL needed: {fl:.3f} m") if _element[:4].lower() == "size": size, where = _element[4:].split("@") size = float(size) * 1e-6 dist = float(where) - _pos _log.append( f"Asked for imaging beam to a size of {size:.3e} m at a distance of {float(where):.3f} m (meaning {float(dist):.3f} m from optical element)" ) fl = find_fl_to_get_size(aabo, dist, size) _log.append(f"Found the FL needed: {fl:.3f} m") desired_focal_lengths.append(fl) if _use_transfocator: _log.append( f"Using transfocator, finding best combination for FL {fl:.3f} m" ) if not isinstance(transfocator, Transfocator): _transfocator = transfocator[_pos] else: _transfocator = transfocator ret_transf = _transfocator.find_best_set_for_focal_length( energy=energy, focal_length=fl, accuracy_needed=min(fl / 1000, 0.1), beam_fwhm=None, ) fl = ret_transf.focal_length _log.append( f"Using transfocator, found set with FL of {fl:.2f} m") _log.append( f"Using transfocator, using set {str(ret_transf.best_lens_set)}" ) fl_obj = ret_transf.best_lens_set _log.append(fl_obj) lenses.append(fl_obj) else: lenses.append(fl) fl_obj = lens(fl) focal_lengths.append(fl) if fl == np.inf: aaao = aabo else: aaao = aabo.apply(fl_obj) beams_before_aperture_before_optics.append(babo) beams_after_aperture_before_optics.append(aabo) beams_after_aperture_after_optics.append(aaao) log.append(_log) print("\n".join([l for l in _log if isinstance(l, str)])) positions = np.asarray(positions) info = ds( log=log, inputs=optics, optics_positions=positions, apertures=apertures, focal_lengths=focal_lengths, desired_focal_lengths=desired_focal_lengths, beams_before_aperture_before_optics=beams_before_aperture_before_optics, beams_after_aperture_before_optics=beams_after_aperture_before_optics, beams_after_aperture_after_optics=beams_after_aperture_after_optics, lenses=lenses) size = np.zeros_like(z) cl = np.zeros_like(z) gdc = np.zeros_like(z) radius = np.zeros_like(z) for i, zi in enumerate(z): print(f"calculating {i}/{len(z)}", end="\r") # calc which beam to use div = np.floor_divide(positions, zi) temp_idx = np.ravel(np.argwhere(div == 0)) if len(temp_idx) == 0: idx = 0 else: idx = temp_idx[-1] beam = beams_after_aperture_after_optics[idx] dpos = zi - positions[idx] b = beam.propagate(dpos) size[i] = b.rms_size * 2.35 * 1e6 cl[i] = b.rms_cl * 2.35 * 1e6 gdc[i] = b.global_degree_of_coherence radius[i] = b.radius divergence = np.gradient(size, z) ret = ds( z=z, divergence=divergence, fwhm_size=size, fwhm_cl=cl, global_degree_of_coherence=gdc, radius=radius, info=info, ) if fname is not None: ret.save(fname) return ret
import numpy as np import datastorage from datastorage import DataStorage as ds ## create storage ## # empty data = ds() # from a dict data = ds(dict(key1='value1')) # with keywords arguments data = ds(key1=3) ## adding stuff ... ## # as if it is a dict data['key2'] = 1234 # this is even nicer ... ;) data.key3 = 34 # addidng another key (by default it will converted to a DataStorage instance if possible) data.key4 = dict(key4_1=3, key4_2="ciao") print("is key4 a datastorage ?", isinstance(data.key4, ds)) # can also handle lists/tuples ... data.key5 = [1, 2, 3]
def calc_focusing_GSM( self, gsm=DEFAULT_GSM, source_distance=None, slit_opening=4e-3, verbose=True, ): """ Based on Singer&Vartanyans JSR 2014 """ if source_distance is None: gsm_at_lens = gsm else: gsm_at_lens = gsm.propagate(source_distance) if isinstance(gsm_at_lens, GSM): gsm_at_lens.evalf() input_rms_beam = float(gsm_at_lens.rms_size) input_R = float(gsm_at_lens.radius) input_cl = float(gsm_at_lens.rms_cl) energy = wavelength_to_energy(gsm_at_lens.wavelen * 1e10) k = 2 * np.pi / gsm_at_lens.wavelen # calculate lens opening (called Ω in paper) gauss_slit_opening = slit_opening / 4.55 aperture = min(self.gaussian_aperture(), gauss_slit_opening) abs_opening = self.absorption_opening(energy) lens_effective_aperture = _calc_sum_square_inverse( [aperture, abs_opening]) fl = self.focal_length(energy) # tilde_ are values at lens tilde_Sigma = _calc_sum_square_inverse( [input_rms_beam, lens_effective_aperture]) tilde_R = _calc_sum_inverse([input_R, -fl]) tilde_cl = input_cl # Coherence length is not modified by the lens # gdc = global_degree_of_coherence tilde_gdc = 1 / np.sqrt(1 + (2 * tilde_Sigma / tilde_cl)**2) Z_L = 2 * k * tilde_Sigma**2 * tilde_gdc # distance_at which it will focus (1/a+1/b=1/f) focus_distance = -tilde_R / (1 + (tilde_R / Z_L)**2) focus_rms_size = tilde_Sigma / np.sqrt(1 + (Z_L / tilde_R)**2) focus_cl = tilde_cl / np.sqrt(1 + (Z_L / tilde_R)**2) rayleigh_range = 4 * k * focus_rms_size**2 * tilde_gdc t = self.transmission_central_ray(energy) if isinstance(gsm_at_lens, GSM): beam_at_focus = GSM( wavelen=gsm_at_lens.wavelen, rms_size=focus_rms_size, rms_cl=focus_cl, auto_apply_evalf=gsm_at_lens.auto_apply_evalf, ) else: beam_at_focus = GSM_Numeric( wavelen=gsm_at_lens.wavelen, rms_size=focus_rms_size, rms_cl=focus_cl, ) gsm_at_lens = beam_at_focus.propagate(-focus_distance) res = ds(focal_length=fl, focus_distance=focus_distance, fwhm_unfocused=input_rms_beam * 2.35, fwhm_at_waist=focus_rms_size * 2.35, cl_fwhm_at_waist=focus_cl * 2.35, rayleigh_range=rayleigh_range, energy=energy, lens_set=self.lens_set, transmission_central_ray=t, gsm_at_focus=beam_at_focus, gsm_at_lens=gsm_at_lens) if verbose: print("FWHM @ lens : %.3e" % (input_rms_beam * 2.35)) print("RMS @ lens : %.3e" % input_rms_beam) print("RMS CL @ lens : %.3e" % input_cl) print("GDC @ lens : %.3e" % float(gsm_at_lens.global_degree_of_coherence)) print("--------------------------------") print("focus distance : %.3e" % focus_distance) print("focal length : %.3e" % fl) print("Lens set abs opening : %.3e" % abs_opening) print("Lens set opening : %.3e" % lens_effective_aperture) print("beam FWHM after lens : %.3e" % (tilde_Sigma * 2.35)) print("--------------------------------") print("FWHM @ waist : %.3e" % (focus_rms_size * 2.35)) print("RMS @ waist : %.3e" % focus_rms_size) print("RMS CL @ waist : %.3e" % focus_cl) print("GDC @ waist : %.3e" % tilde_gdc) print("rayleigh_range : %.3e" % rayleigh_range) return res
def srw_photon_flux_density( self, gap="min", energy=None, dist=30, h=[-0.6, 0.6], nh=13, v=[-0.5, 0.5], nv=11, e=[1, 30], ne=200, harmonic="auto", abs_filter=None, **kwargs, ): """ Calculate intensity spectral density (ph/sec) Parameters ---------- dist : float [m] distance from source h : (min,max) or value [mm] start/end of horizontal slit. If a single value the range -value/2,value/2 is used nh: int number of point in horizontal direction v : (min,max) or value [mm] start/end of vertical slit. If a single value the range -value/2,value/2 is used nv: int number of point in vertical direction e : [min,max] or value [keV] start/end of energy spectral_photon_flux. ne: int number of point in energy spectral_photon_flux. If e is single value, forced to 1 harmonic : int|(min,max)|"auto" harmonic to consider, if auto it is autodetected based on energy range if int, only that harmonic is used abs_filter : None or object with calc_transmission(energy_kev) method """ import srwlib import srwlpy if energy is not None: pars = self.find_harmonic_and_gap(energy, sort_harmonics=True, use_srw=True)[0] gap = pars["gap"] # harmonic will be determined below if isinstance(gap, str) and gap == "min": gap = self.min_gap if isinstance(v, (float, int)): v = [-v / 2, v / 2] if isinstance(h, (float, int)): h = [-h / 2, h / 2] data = self.as_srw(gap=gap) if harmonic == "auto": harmonic_m = int(max(np.floor(e[0] / self.fundamental(gap=gap)), 1)) harmonic_M = int(max(np.ceil(e[1] / self.fundamental(gap=gap)), 1)) elif isinstance(harmonic, int): harmonic_m = harmonic harmonic_M = harmonic else: harmonic_m, harmonic_M = harmonic if isinstance(e, (float, int)): if e == 0: e = self.photon_energy(gap=gap, harmonic=harmonic[0]) e = [e, e] ne = 1 elif isinstance(e, (tuple, list)) and e[0] < 0: e0 = self.photon_energy(gap=gap, harmonic=harmonic_m) e = [e0 + e[0], e0 + e[1]] calc_flux = nh == 1 and nv == 1 arPrecF = [0] * 5 # for spectral flux vs photon energy arPrecF[0] = harmonic_m # initial UR harmonic to take into account arPrecF[1] = harmonic_M # final UR harmonic to take into account arPrecF[2] = 1.5 # longitudinal integration precision parameter arPrecF[3] = 1.5 # azimuthal integration precision parameter if calc_flux: arPrecF[4] = 1 # calculate flux (1) or flux per unit surface (2) else: arPrecF[4] = 2 # calculate flux (1) or flux per unit surface (2) stk = srwlib.SRWLStokes() # for spectral_photon_flux shape = (ne, nh, nv) stk.allocate( *shape ) # numbers of points vs horizontal and vertical positions (photon energy is not taken into account) stk.mesh.zStart = dist # longitudinal position [m] at which power density has to be calculated stk.mesh.xStart = h[0] / 1e3 # initial horizontal position [m] stk.mesh.xFin = h[1] / 1e3 # final horizontal position [m] stk.mesh.yStart = v[0] / 1e3 # initial vertical position [m] stk.mesh.yFin = v[1] / 1e3 # final vertical position [m] stk.mesh.eStart = e[0] * 1e3 stk.mesh.eFin = e[1] * 1e3 srwlpy.CalcStokesUR(stk, data.ebeam, data.und, arPrecF) e = np.linspace(e[0], e[1], ne) dh = h[1] - h[0] dv = v[1] - v[0] h = np.linspace(h[0], h[1], nh) v = np.linspace(v[0], v[1], nv) if calc_flux: spectral_photon_flux = np.asarray(stk.arS[:ne]) spectral_photon_flux_density = ( spectral_photon_flux[:, np.newaxis, np.newaxis] / dh / dv) else: spectral_photon_flux_density = np.reshape(stk.arS[:ne * nh * nv], (nv, nh, ne)) spectral_photon_flux_density = np.moveaxis( spectral_photon_flux_density, 2, 0) spectral_photon_flux = _integrate2d(h, v, spectral_photon_flux_density) if abs_filter is not None: t = abs_filter.calc_transmission(e) spectral_photon_flux_density *= t[:, np.newaxis, np.newaxis] spectral_photon_flux *= t abs_info = "absorption filter : " + str(abs_filter) else: abs_info = "no absorption filter" data = ds( energy=e, h=h, v=v, spectral_photon_flux_density=spectral_photon_flux_density, spectral_photon_flux=spectral_photon_flux, undulator_info=str(self), info=f"harmonic = {str(harmonic)}, " + abs_info, ebeam=self.ebeam, ) data = _photon_flux_density_helper(data) # calculates more things ... return data
def srw_power_density( self, gap="min", dist=30, energy=None, h=[-3, 3], nh=151, v=[-2, 2], nv=101, **kwargs, ): """ Calculate power density (W/mm^2) Parameters ---------- dist : float [m] distance from source h : [min,max] or gap [mm] start/end of horizontal slit. If a single value the range -gap/2,gap/2 is used nh: int number of point in horizontal direction v : [min,max] or gap [mm] start/end of vertical slit. If a single value the range -gap/2,gap/2 is used nv: int number of point in vertical direction """ if energy is not None: pars = self.find_harmonic_and_gap(energy, sort_harmonics=True, use_srw=True)[0] gap = pars["gap"] harmonic = pars["harmonic"] if isinstance(gap, str) and gap == "min": gap = self.min_gap import srwlib import srwlpy if isinstance(v, (float, int)): v = [-v / 2, v / 2] if isinstance(h, (float, int)): h = [-h / 2, h / 2] data = self.as_srw(gap=gap) arPrecP = [0] * 5 # for power density arPrecP[0] = 1.5 # precision factor arPrecP[ 1] = 2 # power density computation method (1- "near field", 2- "far field") arPrecP[2] = ( -self.length / 2 ) # initial longitudinal position (effective if arPrecP[2] < arPrecP[3]) arPrecP[3] = ( self.length / 2 ) # final longitudinal position (effective if arPrecP[2] < arPrecP[3]) arPrecP[ 4] = 20000 # number of points for (intermediate) trajectory calculation stkP = srwlib.SRWLStokes() # for power density shape = (1, nh, nv) stkP.allocate( *shape ) # numbers of points vs horizontal and vertical positions (photon energy is not taken into account) stkP.mesh.zStart = dist # longitudinal position [m] at which power density has to be calculated stkP.mesh.xStart = h[0] / 1e3 # initial horizontal position [m] stkP.mesh.xFin = h[1] / 1e3 # final horizontal position [m] stkP.mesh.yStart = v[0] / 1e3 # initial vertical position [m] stkP.mesh.yFin = v[1] / 1e3 # final vertical position [m] # stkP.mesh.eStart = # stkP.mesh.eFin = 8000 srwlpy.CalcPowDenSR(stkP, data["ebeam"], 0, data["field"], arPrecP) pd = np.asarray(stkP.arS[:nh * nv]).reshape((nv, nh)) h = np.linspace(h[0], h[1], nh) v = np.linspace(v[0], v[1], nv) power_total = _integrate2d(h, v, pd) units_power_density = "W/mm2" units_power_total = "W" units_h = "mm" units_v = "mm" return ds( h=h, v=v, power_density=pd, power_density_max=pd.max(), power_total=power_total, units_power_density=units_power_density, units_power_density_max=units_power_density, units_power_total=units_power_total, units_h=units_h, units_v=units_v, )
def xrt_photon_flux_density( self, gap="min", energy=None, dist=30, h=[-0.6, 0.6], nh=13, v=[-0.5, 0.5], nv=11, e=[1, 30], ne=200, harmonic="auto", abs_filter=None, **kwargs, ): """ Calculate intensity spectral density (ph/sec) Parameters ---------- dist : float [m] distance from source h : (min,max) or value [mm] start/end of horizontal slit. If a single value the range -value/2,value/2 is used nh: int number of point in horizontal direction v : (min,max) or value [mm] start/end of vertical slit. If a single value the range -value/2,value/2 is used nv: int number of point in vertical direction e : [min,max] or value [keV] start/end of energy spectral_photon_flux. if emin is neg it is interpreted as around harmonic[0] if e is float and == 0, the extact harmonic is used. ne: int number of point in energy spectral_photon_flux. If e is single value, forced to 1 harmonic : int|list|None harmonic to consider, if auto it is autodetected based on energy range if int, only that harmonic is used """ use_srw = kwargs.get("use_srw", False) if energy is not None: pars = self.find_harmonic_and_gap(energy, sort_harmonics=True, use_srw=use_srw)[0] gap = pars["gap"] harmonic = pars["harmonic"] if isinstance(e, (int, float)) and e != 0: e = [e, e] if harmonic == "auto": harmonic_m = int(max(np.floor(e[0] / self.fundamental(gap=gap)), 1)) harmonic_M = int(max(np.ceil(e[1] / self.fundamental(gap=gap)), 1)) harmonic = range(harmonic_m, harmonic_M + 1) if isinstance(harmonic, int): harmonic = (harmonic, ) else: pass if isinstance(v, (float, int)): v = [-v / 2, v / 2] if isinstance(h, (float, int)): h = [-h / 2, h / 2] if isinstance(e, (float, int)): if e == 0: e = self.photon_energy(gap=gap, harmonic=harmonic[0]) e = [e, e] ne = 1 elif isinstance(e, (tuple, list)) and e[0] < 0: e0 = self.photon_energy(gap=gap, harmonic=harmonic[0]) print("Working around", e0) e = [e0 + e[0], e0 + e[1]] if nh == 1: nh = 2 if nv == 1: nv = 2 dh = h[1] - h[0] dv = v[1] - v[0] h = np.linspace(h[0], h[1], nh) v = np.linspace(v[0], v[1], nv) e = np.linspace(e[0], e[1], ne) theta = h * 1e-3 / dist psi = v * 1e-3 / dist dtheta = dh * 1e-3 / dist dpsi = dv * 1e-3 / dist u = self.as_xrt(gap=gap) spectral_photon_flux_density, *_ = u.intensities_on_mesh( energy=e * 1e3, theta=theta, psi=psi, harmonic=harmonic) # sum over harmonics spectral_photon_flux_density = spectral_photon_flux_density.sum(-1) # convert from flux per unit solid angle into flux per mm^2 spectral_photon_flux_density *= (dtheta * dpsi) / (dh * dv) spectral_photon_flux_density = np.swapaxes( spectral_photon_flux_density, 1, 2) if abs_filter is not None: t = abs_filter.calc_transmission(e) spectral_photon_flux_density *= t[:, np.newaxis, np.newaxis] abs_info = "absorption filter : " + str(abs_filter) else: abs_info = "no absorption filter" # integrate2d works only if len(axis) > 1 if nh == 1 or nv == 1: spectral_photon_flux = spectral_photon_flux_density * (dh * dv) else: spectral_photon_flux = _integrate2d(h, v, spectral_photon_flux_density) data = ds( energy=e, h=h, v=v, spectral_photon_flux_density=spectral_photon_flux_density, spectral_photon_flux=spectral_photon_flux, undulator_info=str(self), info=f"harmonic = {str(harmonic)}, " + abs_info, ebeam=self.ebeam, ) data = _photon_flux_density_helper(data) # calculates more things ... return data
def get_ebeam(self): return ds(self.ebeam.copy())
def rms_cl_at_dist(self, dist=100): sh, dh = self.gsm_sclh, self.gsm_cldivh sv, dv = self.gsm_sclv, self.gsm_cldivv clh, clv = _sqrt_squares(sh, dh * dist), _sqrt_squares(sv, dv * dist) return ds(clh=clh, clv=clv, cldivh=dh, cldivv=dv)
def calc_focusing( self, energy, distance=4, source_distance=150, fwhm_beam=500e-6, slit_opening=4e-3, verbose=True, ): # calculate effective beamsize input_rms_beam = fwhm_beam / 2.35 gauss_slit_opening = slit_opening / 4.55 # see single&vartanyans JSR 2014 aperture = min(self.gaussian_aperture(), gauss_slit_opening) absorption_ap = self.absorption_opening(energy) lens_effective_aperture = _calc_sum_square_inverse( [aperture, absorption_ap]) rms_beam = _calc_sum_square_inverse( [input_rms_beam, lens_effective_aperture]) fl = self.focal_length(energy) # distance_at which it will focus (1/a+1/b=1/f) focus_distance = source_distance * fl / (source_distance - fl) lam = energy_to_wavelength(energy) * 1e-10 w_unfocused = rms_beam * 2 # assuming gaussian beam divergence = # = w_unfocused/focus_distance we can obtain waist = lam / np.pi * focus_distance / w_unfocused waist_fwhm = waist * 2.35 / 2.0 rayleigh_range = np.pi * waist**2 / lam size = waist * np.sqrt(1.0 + (distance - focus_distance)**2.0 / rayleigh_range**2) fwhm_at_dist = size / 2 * 2.35 t = self.transmission_central_ray(energy) res = ds( focal_length=fl, focus_distance=focus_distance, distance=distance, fwhm_at_dist=fwhm_at_dist, fwhm_unfocused=fwhm_beam, fwhm_at_waist=waist_fwhm, rayleigh_range=rayleigh_range, energy=energy, lens_set=self.lens_set, transmission_central_ray=t, ) if verbose: print("beam FWHM @ lens : %.3e" % (input_rms_beam * 2.35)) print("Lens set opening : %.3e" % lens_effective_aperture) print("beam FWHM after lens : %.3e" % (rms_beam * 2.35)) print("------------------------") print("focus distance : %.3e" % focus_distance) print("focal length : %.3e" % fl) print("------------------------") print("waist : %.3e" % waist) print("waist FWHM : %.3e" % waist_fwhm) print("rayleigh_range : %.3e" % rayleigh_range) print("------------------------") print("size @ dist : %.3e" % size) print("size FWHM @ dist : %.3e" % fwhm_at_dist) return res
_e_lattices = ds( esrf_highb=ds( sh=387.80e-6, divh=10.31e-6, sv=5.43e-6, divv=1.84e-6, ebeam_energy=6.04, sr_cur=0.2, rms_energy_spread=0.001, ), ebs=ebs_minibeta(beta_h=6.8, beta_v=2.8), super_source=ds( sh=3e-6, divh=1e-6, sv=1.0e-6, divv=1e-6, ebeam_energy=6, sr_cur=0.2, rms_energy_spread=0.00094, ), jsr2019=ds( sh=4.47e-6, divh=2.24e-6, sv=4.47e-6, divv=2.24e-6, ebeam_energy=6, sr_cur=0.1, rms_energy_spread=0.001, ), jsr2019es0=ds( sh=4.47e-6, divh=2.24e-6, sv=4.47e-6, divv=2.24e-6, ebeam_energy=6, sr_cur=0.1, rms_energy_spread=0.000, ), )