######################################################################
################## Transient Absorption agent ########################
######################################################################
# Version 0.1
# (c) Larry Lueer, Vincent M. Le Corre, i-MEET 2021-2023
# Import libraries
import sys
import os,itertools
from scipy import interpolate, constants
from copy import deepcopy
import matplotlib.pyplot as plt
# Import boar
from boar.dynamic_utils.pump import *
from boar.dynamic_utils.rate_eq import *
from boar.agents.Agent import Agent
from boar.core.funcs import callable_name, get_unique_X, get_unique_X_and_xaxis_values
## Physics constants
q = constants.value(u'elementary charge')
kb = constants.value(u'Boltzmann constant in eV/K')
[docs]
class TrMC_agent(Agent):
""" Agent to run Transient Phololuminescence simulation based on rate equations to be used with BOAR MultiObjectiveOptimizer
Parameters
----------
trMC_model : function, optional
trMC model to be used, by default Bimolecular_Trapping_equation
To see the available models, check the rate_eq.py file in the dynamic_utils folder
pump_model : function, optional
pump model to be used, by default square_pump
To see the available models, check the pump.py file in the dynamic_utils folder
pump_params : dict, optional
dictionary of pump parameters, by default {'P':0.0039, 'wvl':850, 'fpu':10000, 'A':0.3*0.3*1e-4, 'alpha':1e-5*1e-2, 'pulse_width':0.2*(1/10000), 't0':0, 'background':0}
including:
P : float
total CW power of pulse in W
wvl : float
excitation wavelength in nm
fpu : float
pump frequency in Hz
A : float
effective pump area in m^-2
alpha : float
penetration depth in m
pulse_width : float
width of the pump pulse in seconds
t0 : float, optional
time shift of the pump pulse, by default 0
background : float, optional
background volume density of generated photons, by default 0
"""
def __init__(self,trMC_model = Bimolecular_Trapping_equation,pump_model = initial_carrier_density, pump_params = {'fpu':1000, 'background':0},flux_density_model = get_flux_density) -> None:
super().__init__()
self.flux_density_model = flux_density_model
self.trMC_model = trMC_model
self.pump_model = pump_model
self.pump_params= pump_params
# check model function name even when it is called inside a partial function
model_name = callable_name(self.trMC_model)
# Reset default values when checking for model, to avoid errors
if model_name == 'Bimolecular_Trapping_equation':
self.trMC_default_val = {'kdirect' : 1e-18, 'ktrap': 1e5, 'QE':0.9, 'I_MC': 1e-17} # kwargs for the trMC model by defaults (tpulse=None, equilibrate=True,eq_limit=1e-2)
elif model_name == 'Bimolecular_Trapping_Detrapping_equation':
self.trMC_default_val = {'kdirect' : 26e-17, 'ktrap': 1.2e-15, 'kdetrap': 80e-17,'Bulk_tr':6e18,'p_0':65e18, 'QE':0.9, 'I_MC': 1e-17} # kwargs for the trMC model by defaults (tpulse=None, equilibrate=True,eq_limit=1e-2) ktrap = 1.2e-15
else:
self.trMC_default_val = {}
self.model_name = model_name
# if self.model_name == 'Bimolecular_Trapping_equation':
# self.trMC_default_val = {'kdirect' : 1e-18, 'ktrap': 1e5, 'QE':0.9, 'I_MC': 1e-17} # kwargs for the trMC model by defaults (tpulse=None, equilibrate=True,eq_limit=1e-2)
# elif self.trMC_model == Bimolecular_Trapping_Detrapping_equation:
# self.trMC_default_val = {'kdirect' : 26e-17, 'ktrap': 1.2e-15, 'kdetrap': 80e-17,'Bulk_tr':6e18,'p_0':65e18, 'QE':0.9, 'I_MC': 1e-17} # kwargs for the trMC model by defaults (tpulse=None, equilibrate=True,eq_limit=1e-2) ktrap = 1.2e-15
[docs]
def trMC(self,X,params,X_dimensions=[],take_log=False):
""" Run the trMC simulations for a given list of parameters
Parameters
----------
X : np.array
Array of fixed parameters (like time, light intensities, etc.))
params : list
list of Fitparam objects
X_dimensions : list, optional
name of the fixed parameters in X, by default []
Returns
-------
np.array
Array of containing the simulation results
"""
# check if X is np.array
if not isinstance(X,np.ndarray):
X = np.array(X)
QE, I_MC,p_0,r_mu,mun_0,mup_0,Gfrac = None,None,None,None,None,None,None
y = []
X_unique, X_dimensions_uni,ts = get_unique_X_and_xaxis_values(X,'t',X_dimensions) # get unique X values and their dimensions
pnames = [p.name for p in params] # get parameter names
flux_args, pump_args, pump_params, trMC_params, trMC_default_val, trMC_args_names = self.init_args_flux_pump_models() # initialize arguments for the flux density and pump models
for idx, uni in enumerate(X_unique):
t = ts[idx]
# update the pump,flux_density and trMC arguments from a fixed parameter
for key in X_dimensions_uni:
val = uni[X_dimensions_uni.index(key)]
if key in pnames:
raise ValueError(f'Parameter {key} is optimized, it should not be in the fixed parameters')
if key in flux_args.keys() and self.pump_model != initial_carrier_density:
flux_args[key] = uni[X_dimensions_uni.index(key)]
if key in pump_args.keys():
pump_args[key] = uni[X_dimensions_uni.index(key)]
if key in pnames:
pump_params[key] = params[pnames.index(key)].value
warnings.warn(f'Typically Pump parameter {key} should not be optimized, it is set to {pump_params[key]}')
if key == 'QE':
QE = uni[X_dimensions_uni.index(key)]
if key == 'I_MC':
I_MC = uni[X_dimensions_uni.index(key)]
if key == 'p_0':
p_0 = uni[X_dimensions_uni.index(key)]
if key == 'r_mu':
r_mu = uni[X_dimensions_uni.index(key)]
if key == 'mun_0':
mun_0 = uni[X_dimensions_uni.index(key)]
if key == 'mup_0':
mup_0 = uni[X_dimensions_uni.index(key)]
if key == 'Gfrac':
Gfrac = uni[X_dimensions_uni.index(key)]
if key == 'N0':
pump_args[key] = uni[X_dimensions_uni.index(key)]
else:
pass
# update the trMC arguments from the params list
for p in params:
if p.name in pump_args.keys():
pump_args[p.name] = p.val
if p.name in flux_args.keys():
flux_args[p.name] = p.val
if p.name in trMC_args_names:
trMC_params[p.name] = p.val
if p.name == 'QE':
QE = p.val
if p.name == 'I_MC':
I_MC = p.val
if p.name == 'p_0':
p_0 = p.val
if p.name == 'r_mu':
r_mu = p.val
if p.name == 'mun_0':
mun_0 = p.val
if p.name == 'mup_0':
mup_0 = p.val
if p.name == 'Gfrac':
Gfrac = p.val
if p.name == 'N0':
pump_args[p.name] = p.val
# check if the trMC arguments are set
if QE is None:
QE = trMC_default_val['QE']
warnings.warn(f'QE is not set, it is set to {QE} %')
if I_MC is None:
I_MC = trMC_default_val['I_MC']
warnings.warn(f'I_MC is not set, it is set to {I_MC} ')
if p_0 is None and self.model_name == 'Bimolecular_Trapping_Detrapping_equation':
p_0 = trMC_default_val['p_0']
warnings.warn(f'p_0 is not set, it is set to {p_0} cm-3')
if mun_0 is not None and mup_0 is not None: # if both are set use them else use r_mu
r_mu = mun_0/mup_0
if r_mu is None:
raise ValueError('r_mu is not set, it is required for the trMC model, please either provide a value for r_mu or for mun_0 AND mup_0')
if Gfrac is None :
Gfrac = 1
pump_args['Gfrac'] = Gfrac
# warnings.warn(f'Gfrac is not set, it is set to {Gfrac} ')
else:
pump_args['Gfrac'] = Gfrac
# calculate the flux density
if self.pump_model != initial_carrier_density:
flux,density = self.flux_density_model(**flux_args)
# calculate the pump
pump_args['P'] = density # update the pump power with the density
trMC_params['t']= t
tmax = 0.99999*1/pump_args['fpu'] # maximum time for the pump
# check if the time is too long to use arange, we need to do this to ensure that the pulse_width is not too small compared to the timestep
order_of_magnitude = np.floor(np.log10(pump_args['pulse_width']))
if tmax > 100*10**order_of_magnitude:
# use logspace if the time is too long
num_steps = (np.floor(np.log10(tmax))+1-order_of_magnitude) * 10 # at least ten steps per order of magnitude
tpulse = np.logspace(order_of_magnitude-1,np.log10(tmax),int(num_steps)) # time vector for the pump
#add 0 to the beginning of the time vector
tpulse = np.insert(tpulse,0,0)
else: # use arange if the time is short enough can use linear steps
tpulse = np.arange(0,tmax,10**order_of_magnitude)
trMC_params['tpulse'] = tpulse
trMC_params['Gpulse'] = self.pump_model(tpulse,**pump_args) * QE# * Gfrac
signal = self.trMC_model(**trMC_params)
else:
trMC_params['t']= t
tmax = 0.99999*1/pump_args['fpu'] # maximum time for the pump
tpulse = np.linspace(0,tmax,5000) # time vector for the pump
trMC_params['tpulse'] = tpulse
trMC_params['Gpulse'] = self.pump_model(tpulse,**pump_args) * QE #* Gfrac
if self.model_name == 'Bimolecular_Trapping_equation': # initial electron density
trMC_params['ninit'] = [pump_args['N0']*Gfrac]
elif self.model_name == 'Bimolecular_Trapping_Detrapping_equation':
trMC_params['ninit'] = [pump_args['N0']*Gfrac,0,pump_args['N0']*Gfrac] # initial electron, hole and exciton density
signal = self.trMC_model(**trMC_params)
if self.model_name == 'Bimolecular_Trapping_equation':
signal = I_MC * (r_mu*signal + signal) # sigma = q * I_trMC * (mun_0/mup_0 * n + p), here I_trMC = cst * mup_0
elif self.model_name == 'Bimolecular_Trapping_Detrapping_equation':
n, nt , p = signal# electron density
signal = I_MC * (r_mu*n + p) # sigma = q * I_trMC * (mun_0/mup_0 * n + p) , here I_trMC = cst * mup_0
y = y + list(signal) # small signal limit !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
y = np.array(y)
if take_log:
y = np.log10(abs(y))
return y
[docs]
def init_args_flux_pump_models(self):
""" Initialize the arguments for the flux density model, pump model and trMC model
For more information, check the pump.py file in the dynamic_utils folder
"""
# check model function name even when it is called inside a partial function
model_name = callable_name(self.trMC_model)
# Reset default values when checking for model, to avoid errors
if model_name == 'Bimolecular_Trapping_equation':
self.trMC_default_val = {'kdirect' : 1e-18, 'ktrap': 1e5, 'QE':0.9, 'I_MC': 1e-17} # kwargs for the trMC model by defaults (tpulse=None, equilibrate=True,eq_limit=1e-2)
elif model_name == 'Bimolecular_Trapping_Detrapping_equation':
self.trMC_default_val = {'kdirect' : 26e-17, 'ktrap': 1.2e-15, 'kdetrap': 80e-17,'Bulk_tr':6e18,'p_0':65e18, 'QE':0.9, 'I_MC': 1e-17} # kwargs for the trMC model by defaults (tpulse=None, equilibrate=True,eq_limit=1e-2) ktrap = 1.2e-15
else:
self.trMC_default_val = {}
# Initialize the dictionary with the default values and make deep copy
trMC_default_val = deepcopy(self.trMC_default_val)
pump_params = deepcopy(self.pump_params)
# get arguments for get_flux_density
if self.pump_model != initial_carrier_density: # if the pump model is not the initial carrier density, then the flux density model is used
if self.flux_density_model == get_flux_density:
# P,wvl,fpu,A,alpha
flux_args = {'wvl':self.pump_params['wvl'], 'fpu':self.pump_params['fpu'], 'A':self.pump_params['A'], 'alpha':self.pump_params['alpha'],'P':self.pump_params['P']}
else:
flux_args = {}
# get arguments for pump_model
# P is ommited because it is calculated in get_flux_density
if self.pump_model == square_pump:
pump_args = {'fpu':self.pump_params['fpu'],'pulse_width':self.pump_params['pulse_width'], 't0':self.pump_params['t0'], 'background':self.pump_params['background']}
elif self.pump_model == gaussian_pump:
pump_args = {'fpu':self.pump_params['fpu'],'pulse_width':self.pump_params['pulse_width'], 't0':self.pump_params['t0'], 'background':self.pump_params['background']}
elif self.pump_model == pump_from_file:
pump_args = {'filename':self.pump_params['filename'], 'P':self.pump_params['P'], 'background':self.pump_params['background'], 'sep':self.pump_params['sep']}
elif self.pump_model == initial_carrier_density:
pump_args = {'fpu':self.pump_params['fpu'],'background':self.pump_params['background']}
if 'N0' in self.pump_params.keys():
pump_args['N0'] = self.pump_params['N0']
else:
raise ValueError(f'pump model {self.pump_model} not implemented')
# get argument for trMC_model
if self.model_name == 'Bimolecular_Trapping_equation':
trMC_args_names = ['ktrap','kdirect'] # name of the physical parameters in the Bimolecular_Trapping_equation
for i in trMC_args_names:
if i not in self.trMC_default_val.keys():
trMC_default_val[i] = None
trMC_params = {'ktrap':self.trMC_default_val['ktrap'],'kdirect':self.trMC_default_val['kdirect']} # default values of the physical parameters in the Bimolecular_Trapping_equation
elif self.model_name == 'Bimolecular_Trapping_Detrapping_equation':
trMC_args_names = ['ktrap', 'kdirect', 'kdetrap', 'Bulk_tr', 'p_0']
for i in trMC_args_names:
if i not in self.trMC_default_val.keys():
trMC_default_val[i] = None
trMC_params = {'ktrap':self.trMC_default_val['ktrap'],'kdirect':self.trMC_default_val['kdirect'], 'kdetrap':self.trMC_default_val['kdetrap'], 'Bulk_tr':self.trMC_default_val['Bulk_tr'], 'p_0':self.trMC_default_val['p_0']}
else:
raise ValueError(f'trMC model {self.model_name} not implemented')
return flux_args, pump_args, pump_params, trMC_params, trMC_default_val, trMC_args_names