__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