Module xenodiffusionscope.ElectronDrift
Expand source code
import numpy as np
import warnings
from tqdm import tqdm
from .TPC import TPC
class ElectronDrift:
'''
Processes and variables related to teh drift of electrons through the LXe TPC.
Needs the input of a TPC object and an int n_electrons
'''
def __init__(self, tpc, xelamp,drift_delta_t):
self.tpc = tpc
self.drift_velocity = tpc.drift_velocity
self.drift_delta_t = drift_delta_t # us
self.sigma_trans = np.sqrt(2*tpc.diffusion_trans*self.drift_delta_t)
self.sigma_long = np.sqrt(2*tpc.diffusion_long*self.drift_delta_t)
self.lamp = xelamp
#harcoded values to go to a config file
self.e_lifetime = 2000 #us
self.se_gain = 28.57 #pe/e- from Xurich ii
self.extract_efficiency = 0.99 # Electron extraction efficiency
warnings.formatwarning = TPC.simple_warning
# def check_boundaries(self):
# '''
# !NOT IN USE!
# Check if all the electrons are within the boundaries.
# If outside r>r_max -> turn to nan
# If at z=0 (gate) -> put into X/Y/Z_gas
# '''
# mask_at_gate = Z >= 0
# t_gas[mask_at_gate] = self.t
# R = get_r(X,Y)
# mask_outside_bound = R > self.TPC.radius
# X[mask_outside_bound] = np.nan
# Y[mask_outside_bound] = np.nan
# Z[mask_outside_bound] = np.nan
def drift_lamp_pulse_slice(self, t0_pulse_slice, tf_pulse_slice):
'''
Drift electrons from a given integration period of the lamp pulse.
'''
n_electrons = self.lamp.emitted_electrons_in_interval(t0_pulse_slice, tf_pulse_slice)
x0,y0,z0 = self.lamp.init_positions(n_electrons)
x,y,z = x0.copy(),y0.copy(),z0.copy()
self.t_running = 0
warnings.warn('Starting drifting process with dt=%.2f us. Light-speed!'%self.drift_delta_t)
half_warning = False
while np.any(z<self.tpc.length):
#there is a catch here that I'm not recording the x,y
#when they cross z=tpc.length but only when ALL cross z=tpc.length
# TO BE FIXED
x,y,z = self.drift_step(x,y,z)
if np.any(z>self.tpc.length/2) and half_warning==False:
warnings.warn('An electron reached half-way there in %d us.' %self.t_running)
half_warning = True
warnings.warn('All electrons have drifted to the gate after %d us.' %self.t_running)
return x,y,z
def drift_full_pulse(self, lamp_end_time = 6):
'''Drift all the full initial pulse of the lamp as a single slice.
'''
delta_t_lamp = self.lamp.delta_t_lamp
times_lamp = np.arange(0,8, self.lamp.delta_t_lamp)
positions_lamp_slices = dict() # bulky but practicle
for t_lamp_index in tqdm(range(len(times_lamp) - 1)):
x,y,z = self.drift_lamp_pulse_slice(times_lamp[t_lamp_index],
times_lamp[t_lamp_index + 1])
positions_lamp_slices['slice_%d'%t_lamp_index] = dict(start = times_lamp[t_lamp_index],
end = times_lamp[t_lamp_index + 1],
delta_t_lamp = delta_t_lamp,
x = x,
y = y,
z = z)
return positions_lamp_slices
def drift_step(self, x,y,z):
'''
Make an increment on the electron array position following diffusion.
Currently the arrival times are not recorded.
'''
_n_electrons = len(x)
delta_x = np.random.normal(0, self.sigma_trans, _n_electrons)
delta_y = np.random.normal(0, self.sigma_trans, _n_electrons)
delta_z = (np.random.normal(0, self.sigma_long, _n_electrons) +
self.drift_velocity * self.drift_delta_t)
x = x + delta_x
y = y + delta_y
z = z + delta_z
self.t_running += self.drift_delta_t
return x,y,z
def apply_corrections(self, x,y,z):
'''
Apply all the reductions needed.
'''
x,y,z = self.apply_elifetime(x,y,z)
x,y,z = self.extract_electrons(x,y,z)
return x,y,z
def extract_electrons(self,x,y,z):
'''
Apply extraction efficiency.
'''
n_electrons = len(x)
n_unlucky = round(n_electrons * (1-self.extract_efficiency))
unlucky_electrons_index = np.random.choice(n_electrons,size = n_unlucky, replace=False)
x = np.delete(x, unlucky_electrons_index)
y = np.delete(y, unlucky_electrons_index)
z = np.delete(z, unlucky_electrons_index)
# #turn to nan the unlucky electrons
# x[unlucky_electrons_index] = np.nan
# y[unlucky_electrons_index] = np.nan
# z[unlucky_electrons_index] = np.nan
return x,y,z
def apply_elifetime(self,x,y,z):
'''
Reduce the ammount of electrons due to the non-infinite e-lifetime.
To finish: x,y,z_old are the final arrays of electrons.
'''
n_electrons = len(x)
drift_time = self.tpc.length / self.drift_velocity # should be electron dependant in the future
n_unlucky = round(n_electrons *(1-np.exp(-drift_time/self.e_lifetime)))
unlucky_electrons_index = np.random.choice(n_electrons,size = n_unlucky, replace=False)
x = np.delete(x, unlucky_electrons_index)
y = np.delete(y, unlucky_electrons_index)
z = np.delete(z, unlucky_electrons_index)
# #turn to nan the unlucky electrons
# x[unlucky_electrons_index] = np.nan
# y[unlucky_electrons_index] = np.nan
# z[unlucky_electrons_index] = np.nan
return x,y,z
def convert_electron_to_photons(self,n_electrons):
'''
This will be a very fancy method to infer the number of either photons or
pe or whatever. For now it uses the SE gain value of either Xurich II.
Please make a PR.
'''
n_pe = n_electrons * self.se_gain
return n_pe
Classes
class ElectronDrift (tpc, xelamp, drift_delta_t)
-
Processes and variables related to teh drift of electrons through the LXe TPC. Needs the input of a TPC object and an int n_electrons
Expand source code
class ElectronDrift: ''' Processes and variables related to teh drift of electrons through the LXe TPC. Needs the input of a TPC object and an int n_electrons ''' def __init__(self, tpc, xelamp,drift_delta_t): self.tpc = tpc self.drift_velocity = tpc.drift_velocity self.drift_delta_t = drift_delta_t # us self.sigma_trans = np.sqrt(2*tpc.diffusion_trans*self.drift_delta_t) self.sigma_long = np.sqrt(2*tpc.diffusion_long*self.drift_delta_t) self.lamp = xelamp #harcoded values to go to a config file self.e_lifetime = 2000 #us self.se_gain = 28.57 #pe/e- from Xurich ii self.extract_efficiency = 0.99 # Electron extraction efficiency warnings.formatwarning = TPC.simple_warning # def check_boundaries(self): # ''' # !NOT IN USE! # Check if all the electrons are within the boundaries. # If outside r>r_max -> turn to nan # If at z=0 (gate) -> put into X/Y/Z_gas # ''' # mask_at_gate = Z >= 0 # t_gas[mask_at_gate] = self.t # R = get_r(X,Y) # mask_outside_bound = R > self.TPC.radius # X[mask_outside_bound] = np.nan # Y[mask_outside_bound] = np.nan # Z[mask_outside_bound] = np.nan def drift_lamp_pulse_slice(self, t0_pulse_slice, tf_pulse_slice): ''' Drift electrons from a given integration period of the lamp pulse. ''' n_electrons = self.lamp.emitted_electrons_in_interval(t0_pulse_slice, tf_pulse_slice) x0,y0,z0 = self.lamp.init_positions(n_electrons) x,y,z = x0.copy(),y0.copy(),z0.copy() self.t_running = 0 warnings.warn('Starting drifting process with dt=%.2f us. Light-speed!'%self.drift_delta_t) half_warning = False while np.any(z<self.tpc.length): #there is a catch here that I'm not recording the x,y #when they cross z=tpc.length but only when ALL cross z=tpc.length # TO BE FIXED x,y,z = self.drift_step(x,y,z) if np.any(z>self.tpc.length/2) and half_warning==False: warnings.warn('An electron reached half-way there in %d us.' %self.t_running) half_warning = True warnings.warn('All electrons have drifted to the gate after %d us.' %self.t_running) return x,y,z def drift_full_pulse(self, lamp_end_time = 6): '''Drift all the full initial pulse of the lamp as a single slice. ''' delta_t_lamp = self.lamp.delta_t_lamp times_lamp = np.arange(0,8, self.lamp.delta_t_lamp) positions_lamp_slices = dict() # bulky but practicle for t_lamp_index in tqdm(range(len(times_lamp) - 1)): x,y,z = self.drift_lamp_pulse_slice(times_lamp[t_lamp_index], times_lamp[t_lamp_index + 1]) positions_lamp_slices['slice_%d'%t_lamp_index] = dict(start = times_lamp[t_lamp_index], end = times_lamp[t_lamp_index + 1], delta_t_lamp = delta_t_lamp, x = x, y = y, z = z) return positions_lamp_slices def drift_step(self, x,y,z): ''' Make an increment on the electron array position following diffusion. Currently the arrival times are not recorded. ''' _n_electrons = len(x) delta_x = np.random.normal(0, self.sigma_trans, _n_electrons) delta_y = np.random.normal(0, self.sigma_trans, _n_electrons) delta_z = (np.random.normal(0, self.sigma_long, _n_electrons) + self.drift_velocity * self.drift_delta_t) x = x + delta_x y = y + delta_y z = z + delta_z self.t_running += self.drift_delta_t return x,y,z def apply_corrections(self, x,y,z): ''' Apply all the reductions needed. ''' x,y,z = self.apply_elifetime(x,y,z) x,y,z = self.extract_electrons(x,y,z) return x,y,z def extract_electrons(self,x,y,z): ''' Apply extraction efficiency. ''' n_electrons = len(x) n_unlucky = round(n_electrons * (1-self.extract_efficiency)) unlucky_electrons_index = np.random.choice(n_electrons,size = n_unlucky, replace=False) x = np.delete(x, unlucky_electrons_index) y = np.delete(y, unlucky_electrons_index) z = np.delete(z, unlucky_electrons_index) # #turn to nan the unlucky electrons # x[unlucky_electrons_index] = np.nan # y[unlucky_electrons_index] = np.nan # z[unlucky_electrons_index] = np.nan return x,y,z def apply_elifetime(self,x,y,z): ''' Reduce the ammount of electrons due to the non-infinite e-lifetime. To finish: x,y,z_old are the final arrays of electrons. ''' n_electrons = len(x) drift_time = self.tpc.length / self.drift_velocity # should be electron dependant in the future n_unlucky = round(n_electrons *(1-np.exp(-drift_time/self.e_lifetime))) unlucky_electrons_index = np.random.choice(n_electrons,size = n_unlucky, replace=False) x = np.delete(x, unlucky_electrons_index) y = np.delete(y, unlucky_electrons_index) z = np.delete(z, unlucky_electrons_index) # #turn to nan the unlucky electrons # x[unlucky_electrons_index] = np.nan # y[unlucky_electrons_index] = np.nan # z[unlucky_electrons_index] = np.nan return x,y,z def convert_electron_to_photons(self,n_electrons): ''' This will be a very fancy method to infer the number of either photons or pe or whatever. For now it uses the SE gain value of either Xurich II. Please make a PR. ''' n_pe = n_electrons * self.se_gain return n_pe
Methods
def apply_corrections(self, x, y, z)
-
Apply all the reductions needed.
Expand source code
def apply_corrections(self, x,y,z): ''' Apply all the reductions needed. ''' x,y,z = self.apply_elifetime(x,y,z) x,y,z = self.extract_electrons(x,y,z) return x,y,z
def apply_elifetime(self, x, y, z)
-
Reduce the ammount of electrons due to the non-infinite e-lifetime. To finish: x,y,z_old are the final arrays of electrons.
Expand source code
def apply_elifetime(self,x,y,z): ''' Reduce the ammount of electrons due to the non-infinite e-lifetime. To finish: x,y,z_old are the final arrays of electrons. ''' n_electrons = len(x) drift_time = self.tpc.length / self.drift_velocity # should be electron dependant in the future n_unlucky = round(n_electrons *(1-np.exp(-drift_time/self.e_lifetime))) unlucky_electrons_index = np.random.choice(n_electrons,size = n_unlucky, replace=False) x = np.delete(x, unlucky_electrons_index) y = np.delete(y, unlucky_electrons_index) z = np.delete(z, unlucky_electrons_index) # #turn to nan the unlucky electrons # x[unlucky_electrons_index] = np.nan # y[unlucky_electrons_index] = np.nan # z[unlucky_electrons_index] = np.nan return x,y,z
def convert_electron_to_photons(self, n_electrons)
-
This will be a very fancy method to infer the number of either photons or pe or whatever. For now it uses the SE gain value of either Xurich II. Please make a PR.
Expand source code
def convert_electron_to_photons(self,n_electrons): ''' This will be a very fancy method to infer the number of either photons or pe or whatever. For now it uses the SE gain value of either Xurich II. Please make a PR. ''' n_pe = n_electrons * self.se_gain return n_pe
def drift_full_pulse(self, lamp_end_time=6)
-
Drift all the full initial pulse of the lamp as a single slice.
Expand source code
def drift_full_pulse(self, lamp_end_time = 6): '''Drift all the full initial pulse of the lamp as a single slice. ''' delta_t_lamp = self.lamp.delta_t_lamp times_lamp = np.arange(0,8, self.lamp.delta_t_lamp) positions_lamp_slices = dict() # bulky but practicle for t_lamp_index in tqdm(range(len(times_lamp) - 1)): x,y,z = self.drift_lamp_pulse_slice(times_lamp[t_lamp_index], times_lamp[t_lamp_index + 1]) positions_lamp_slices['slice_%d'%t_lamp_index] = dict(start = times_lamp[t_lamp_index], end = times_lamp[t_lamp_index + 1], delta_t_lamp = delta_t_lamp, x = x, y = y, z = z) return positions_lamp_slices
def drift_lamp_pulse_slice(self, t0_pulse_slice, tf_pulse_slice)
-
Drift electrons from a given integration period of the lamp pulse.
Expand source code
def drift_lamp_pulse_slice(self, t0_pulse_slice, tf_pulse_slice): ''' Drift electrons from a given integration period of the lamp pulse. ''' n_electrons = self.lamp.emitted_electrons_in_interval(t0_pulse_slice, tf_pulse_slice) x0,y0,z0 = self.lamp.init_positions(n_electrons) x,y,z = x0.copy(),y0.copy(),z0.copy() self.t_running = 0 warnings.warn('Starting drifting process with dt=%.2f us. Light-speed!'%self.drift_delta_t) half_warning = False while np.any(z<self.tpc.length): #there is a catch here that I'm not recording the x,y #when they cross z=tpc.length but only when ALL cross z=tpc.length # TO BE FIXED x,y,z = self.drift_step(x,y,z) if np.any(z>self.tpc.length/2) and half_warning==False: warnings.warn('An electron reached half-way there in %d us.' %self.t_running) half_warning = True warnings.warn('All electrons have drifted to the gate after %d us.' %self.t_running) return x,y,z
def drift_step(self, x, y, z)
-
Make an increment on the electron array position following diffusion. Currently the arrival times are not recorded.
Expand source code
def drift_step(self, x,y,z): ''' Make an increment on the electron array position following diffusion. Currently the arrival times are not recorded. ''' _n_electrons = len(x) delta_x = np.random.normal(0, self.sigma_trans, _n_electrons) delta_y = np.random.normal(0, self.sigma_trans, _n_electrons) delta_z = (np.random.normal(0, self.sigma_long, _n_electrons) + self.drift_velocity * self.drift_delta_t) x = x + delta_x y = y + delta_y z = z + delta_z self.t_running += self.drift_delta_t return x,y,z
def extract_electrons(self, x, y, z)
-
Apply extraction efficiency.
Expand source code
def extract_electrons(self,x,y,z): ''' Apply extraction efficiency. ''' n_electrons = len(x) n_unlucky = round(n_electrons * (1-self.extract_efficiency)) unlucky_electrons_index = np.random.choice(n_electrons,size = n_unlucky, replace=False) x = np.delete(x, unlucky_electrons_index) y = np.delete(y, unlucky_electrons_index) z = np.delete(z, unlucky_electrons_index) # #turn to nan the unlucky electrons # x[unlucky_electrons_index] = np.nan # y[unlucky_electrons_index] = np.nan # z[unlucky_electrons_index] = np.nan return x,y,z