import numpy as np
from integration_tools import lewenstein
# from slowenstein import slowenstein as lewenstein
# from parallellewenstein import parallel_lewenstein as lewenstein
[docs]
class config:
"""
This is the class that stores all of the information on laser and target properties, as well as calculation parameters. By default has parallelization enabled,
however, if you want to use a single core version which only requires numpy, set config.parallel to False.
"""
parallel = True
pass
[docs]
def generate_t(config):
"""
A function to generate the time axis in SAU, using the number of cycles
"""
return 2*np.pi*np.arange(-config.calculation_cycles/2,config.calculation_cycles/2,1/config.ppcycle)
[docs]
def sau_convert(value,quantity,target,config):
"""
Converts between standard SI units and atomic units, currently supports:
e - Electric field u - Energy s - Length a - Area vol - Volume v - velocity
Args:
value (float): The quantity to convert.
quantity: The physical parameter that is being converted, e.g. e for electric field.
target (string): 'si' to convert to standard units and 'sau' to convert to scaled atomic units.
config (class): The config containing at least the wavelength
Returns:
float: Value converted to the corresponding unit system.
"""
c = 299792458;
hbar = 1.054571726e-34
eq = 1.602176565e-19 # electron charge
a0 = 5.2917721092e-11 # Bohr radius
Ry = 13.60569253*eq # Rydberg unit of energy
# scaled atomic unit quantities expressed in SI units
t_unit_SI = (config.wavelength*1e-3) / c / (2*np.pi);
omega_unit_SI = 1/t_unit_SI
U_unit_SI = hbar * omega_unit_SI
q_unit_SI = eq
s_unit_SI = a0 * np.sqrt(2*Ry/U_unit_SI)
E_unit_SI = U_unit_SI / q_unit_SI / s_unit_SI
factors = {'e':E_unit_SI,'u':U_unit_SI,'s':s_unit_SI,'a':s_unit_SI**2,'vol':s_unit_SI**3,'t':t_unit_SI,'v':s_unit_SI/t_unit_SI}
target = target.lower()
quantity = quantity.lower()
if target == 'si':
return value*factors[quantity]
elif target == 'sau':
return value/factors[quantity]
else:
raise ValueError("Invalid quantity or target")
[docs]
def generate_pulse(config):
'''
Generates the driving pulse, forces the user to set a pulse type,
currently supports pulse types:
Constant - A constant envelope
Gaussian - Gaussian beam with no cutoff
Super Gaussian - Gaussian with a faster decline
Cos Squared - Cos squared envelope
Args:
config (class):
- Calculation_cycles
- Points per cycle
- Pulse duration
- Pulse shape
Returns:
driving_field (array): The electric field amplitude over time, same size as t
'''
t = generate_t(config)
pult = sau_convert(config.pulse_duration*1e-15, 't', 'sau', config)
pulse_list = ['constant','gaussian','super_gaussian','cos_sqr','sin_sqr','sin_6']
if not hasattr(config,'pulse_shape') or (config.pulse_shape.lower() not in pulse_list):
raise ValueError('You must specify a pulse shape from the following: {}'.format(pulse_list))
if config.pulse_shape == 'constant':
envelope = 1
elif config.pulse_shape.lower() == 'gaussian':
tau = pult / 2 / np.sqrt(np.log(np.sqrt(2)))
envelope = np.exp(-(t / tau) ** 2)
elif config.pulse_shape.lower() == 'super-gaussian':
tau = pult / 2 / np.sqrt(np.sqrt(np.log(np.sqrt(2))))
envelope = np.exp(-(t / tau) ** 4)
elif config.pulse_shape.lower() == 'cos_sqr':
tau = pult / 2 / np.arccos(1 / np.sqrt(np.sqrt(2)))
envelope = np.cos(t / tau) ** 2
envelope[t / tau <= -np.pi / 2] = 0
envelope[t / tau >= np.pi / 2] = 0
elif config.pulse_shape.lower() == 'sin_sqr':
tau = pult / 2 / np.arccos(1 / np.sqrt(np.sqrt(2)))
envelope = 1-np.cos(np.pi/2 +0.5*t / tau) ** 6
envelope[t / tau <= -np.pi ] = 0
envelope[t / tau >= np.pi ] = 0
elif config.pulse_shape.lower() == 'sin_6':
t = t - t[0] + 0.0001
tau = pult / 2 / np.arccos(1 / np.sqrt(np.sqrt(2)))
envelope = 1-np.sin(np.pi/2 +0.5*t / tau) ** 6
# envelope[np.pi/2 +0.5*t / tau <= 0 ] = 0
# envelope[np.pi/2 +0.5*t / tau >= 2*np.pi ] = 0
else:
raise ValueError('Wrong type of carrier, use one of the following: {}'.format(pulse_list))
if not hasattr(config, 'carrier') or config.carrier.lower() == 'cos':
carrier = np.cos
elif config.carrier.lower() == 'exp':
carrier = lambda x: np.exp(1j * x)
else:
raise ValueError("Invalid carrier: must be 'cos' or 'exp'")
# print(envelope)
amplitude = envelope*carrier(t)
# Setup frequency axis
# domega = 2 * np.pi / (t[1] - t[0]) / len(t)
# temp = np.arange(len(t))
# temp[temp >= np.ceil(len(temp) / 2)] -= len(temp)
# omega = temp * domega
# Fourier transform
# coefficients = np.conj(np.fft.fft(np.conj(amplitude), axis=1))
E0_SI = np.sqrt(2*config.peak_intensity*10000/299792458/8.854187817e-12)
driving_field = amplitude*sau_convert(E0_SI, 'E', 'SAU', config)
return np.squeeze(driving_field)
def get_omega_axis(t, config):
dt = t[1] - t[0]
domega = 2*np.pi/dt/len(t)
temp = np.arange(0,len(t))
temp[int(len(temp)/2)-1:] = temp[int(len(temp)/2)-1:] - len(temp)
omega = temp*domega
return omega
[docs]
def dipole_response(points,driving_field,config,t=np.array([])):
if t.size == 0:
t = generate_t(config)
pi = np.pi
'''
To avoid integration artifacts, a soft integration window is applied through
the weights which multiply each integrand later, how it works is the weights
are equal to 1 [0 -> tau_window_pts] and then drop off as cos^2
The input field for this function must be fourier transformed, alongside the corresponding frequency axis
'''
if not hasattr(config, 'tau_window_length'):
config.tau_window_length = 1
print('Tau window length was not defined, setting to 1.0 as default')
if not hasattr(config, 'tau_dropoff_pts'):
config.tau_dropoff_pts = 0.2
print('Tau dropoff length was not defined, setting to 0.2 as default')
tau_window_pts = int(config.ppcycle*config.tau_window_length) # The number of cycles to integrate over (can be less than one)
tau_dropoff_pts = int(config.tau_dropoff_pts*tau_window_pts) # The range of the soft window
tau_window_pts -= tau_dropoff_pts
weights = np.ones((1, tau_dropoff_pts + tau_window_pts))[0]
if tau_dropoff_pts > 1:
dropoff_factor = pi/2 / (tau_dropoff_pts-1)
else: # avoid division by zero
dropoff_factor = 0.5
if tau_dropoff_pts < 2:
print('The value for tau_interval_length is too small, setting points integrated over to 2')
dropoff = np.cos(dropoff_factor*np.arange(0,max(tau_dropoff_pts,2)))**2
weights[tau_window_pts:] = weights[tau_window_pts:] * dropoff
config.weights = weights
Ip = sau_convert(config.ionization_potential*1.602176565e-19, 'u', 'sau', config)
config.Ip = Ip
config.alpha = 2*Ip
wstart = t.size - 5*config.ppcycle
t_window = np.cos(0.5 * np.arange(t.size-wstart)) ** 2
wind = np.sin((np.pi*t)/t[-1])**2
omega = get_omega_axis(t,config)
final = np.array([])
if config.parallel == True:
from parallellewenstein import parallel_lewenstein as lewenstein
elif config.parallel == False:
from integration_tools import lewenstein
# TODO: Implement a generator that uses numpy arrays for 3D spaces in the future, the current implementation is quite archaic
'''
https://stackoverflow.com/questions/44854593/any-object-that-exists-with-memory-usage-like-a-generator-but-can-return-a-numpy
'''
for point in points:
xi,yi,zi = point
d_t = lewenstein(t,driving_field,config)#*t_window
np.save('/home/alex/Desktop/Python/SNAIL/Benflattop/responsestore/response.npy',d_t)
# np.save('/home/alex/Desktop/Python/SNAIL/src/stored_data/single.npy',d_t)
np.save('/home/alex/Desktop/Python/SNAIL/src/stored_data/time.npy',t)
d_t = d_t*wind
d_omega = np.conj(np.fft.fft(d_t))
d_omega = d_omega*np.exp(-1j*omega*t[0])*(t[1]-t[0])
omega = omega[:d_omega.size]
final = np.append(final,d_omega)
return [omega,final]