Source code for toolbox_scs.misc.laser_utils

__all__ = [
    'degToRelPower',
    'positionToDelay',
    'delayToPosition',
    'fluenceCalibration',
    'align_ol_to_fel_pId'
]

import numpy as np
import matplotlib.pyplot as plt


[docs]def positionToDelay(pos, origin=0, invert=True, reflections=1): ''' converts a motor position in mm into optical delay in picosecond Inputs: pos: array-like delay stage motor position origin: motor position of time zero in mm invert: bool, inverts the sign of delay if True reflections: number of bounces in the delay stage Output: delay in picosecond ''' c_ = 299792458 * 1e-9 # speed of light in mm/ps x = -1 if invert else 1 return 2*reflections*(pos-origin)*x/c_
[docs]def delayToPosition(delay, origin=0, invert=True, reflections=1): ''' converts an optical delay in picosecond into a motor position in mm Inputs: delay: array-like delay in ps origin: motor position of time zero in mm invert: bool, inverts the sign of delay if True reflections: number of bounces in the delay stage Output: delay in picosecond ''' c_ = 299792458 * 1e-9 # speed of light in mm/ps x = -1 if invert else 1 return origin + 0.5 * x * delay * c_ / reflections
[docs]def degToRelPower(x, theta0=0): ''' converts a half-wave plate position in degrees into relative power between 0 and 1. Inputs: x: array-like positions of half-wave plate, in degrees theta0: position for which relative power is zero Output: array-like relative power ''' return np.sin(2*(x-theta0)*np.pi/180)**2
[docs]def fluenceCalibration(hwp, power_mW, npulses, w0x, w0y=None, train_rep_rate=10, fit_order=1, plot=True, xlabel='HWP [%]'): """ Given a measurement of relative powers or half wave plate angles and averaged powers in mW, this routine calculates the corresponding fluence and fits a polynomial to the data. Parameters ---------- hwp: array-like (N) angle or relative power from the half wave plate power_mW: array-like (N) measured power in mW by powermeter npulses: int number of pulses per train during power measurement w0x: float radius at 1/e^2 in x-axis in meter w0y: float, optional radius at 1/e^2 in y-axis in meter. If None, w0y=w0x is assumed. train_rep_rate: float repetition rate of the FEL, by default equals to 10 Hz. fit_order: int order of the polynomial fit plot: bool Plot the results if True xlabel: str xlabel for the plot Output ------ F: ndarray (N) fluence in mJ/cm^2 fit_F: ndarray coefficients of the fluence polynomial fit E: ndarray (N) pulse energy in microJ fit_E: ndarray coefficients of the fluence polynomial fit """ power = np.array(power_mW) hwp = np.array(hwp) E = power/(train_rep_rate*npulses)*1e-3 # pulse energy in J if w0y is None: w0y = w0x F = 2*E/(np.pi*w0x*w0y) # fluence in J/m^2 fit_E = np.polyfit(hwp, E*1e6, fit_order) fit_F = np.polyfit(hwp, F*1e-1, fit_order) x = np.linspace(hwp.min(), hwp.max(), 100) if plot: fig, ax = plt.subplots(figsize=(6, 4)) ax.set_title(f'w0x = {w0x*1e6:.0f} $\mu$m, w0y = {w0y*1e6:.0f} $\mu$m') ax.plot(hwp, F*1e-1, 'o', label='data') fit_label = 'F = ' for i in range(len(fit_F)-1, 1, -1): fit_label += f'{fit_F[i]:.2g}x$^{i}$ + ' if i % 2 == 0: fit_label += '\n' fit_label += f'{fit_F[-2]:.2g}x + {fit_F[-1]:.2g}' ax.plot(x, np.poly1d(fit_F)(x), label=fit_label) ax.set_ylabel('Fluence [mJ/cm$^2$]') ax.set_xlabel(xlabel) ax.legend() ax.grid() def eTf(x): return 1e-7*2*x/(np.pi*w0x*w0y) def fTe(x): return 1e7*x*np.pi*w0x*w0y/2 ax2 = ax.secondary_yaxis('right', functions=(fTe, eTf)) ax2.set_ylabel(r'Pulse energy [$\mu$J]') return F*1e-1, fit_F, E*1e6, fit_E
[docs]def align_ol_to_fel_pId(ds, ol_dim='ol_pId', fel_dim='sa3_pId', offset=0, fill_value=np.nan): ''' Aligns the optical laser (OL) pulse Ids to the FEL pulse Ids. The new OL coordinates are calculated as ds[ol_dim] + ds[fel_dim][0] + offset. The ol_dim is then removed, and if the number of OL and FEL pulses are different, the missing values are replaced by fill_value (NaN by default). Parameters ---------- ds: xarray.Dataset Dataset containing both OL and FEL dimensions ol_dim: str name of the OL dimension fel_dim: str name of the FEL dimension offset: int offset added to the OL pulse Ids. fill_value: (scalar or dict-like, optional) Value to use for newly missing values. If a dict-like, maps variable names to fill values. Use a data array’s name to refer to its values. Output ------ ds: xarray.Dataset The newly aligned dataset ''' fel_vars = [v for v in ds if fel_dim in ds[v].dims] ol_vars = [v for v in ds if ol_dim in ds[v].dims] + [ol_dim] if len(set.intersection(set(fel_vars), set(ol_vars))) > 0: raise ValueError('Variables share ol and fel dimensions: no alignment' ' possible.') ds_fel = ds.drop(ol_vars) ds_ol = ds[ol_vars] ds_ol = ds_ol.assign_coords({ol_dim: ds[ol_dim] + ds[fel_dim][0].values + offset}) ds_ol = ds_ol.rename({ol_dim: fel_dim}) ds = ds_fel.merge(ds_ol, join='outer', fill_value=fill_value) return ds