# -*- coding: utf-8 -*- """ Created on Fri Jul 15 16:47:35 2022 @author: frede """ import numpy as np import radiation_fields as rf ''' This programme computes the photoelectric heating rate and efficiency by PAHs This program works with the script "radiation_fields.py" which calculates the scale factor of the radiation field G0 Example of execution of the program for the star of 10 solar radius HD200775, for a distance to the star at 20pc, for a gas at 750K, an electron density at 2.4 cm-3 and a size of PAHs à 54 C atoms : HeatingGas(filename='HD200775_RF.txt', star_radius=10, t_gas=750, n_e=1.6, n_c=54, parsec=20) If i consider the Interstellar Radiation Field with an approach described by Habing (1968), for a gas at 50K and a fraction of cosmic carbon locked in PAHs at 5%: HeatingGas('habing1968.txt', 1, t_gas=100, n_e=1.6, n_c=54, parsec=1, fc_pah=0.05, ISRF=True) While considering ISRF, the value of the parameters parsec and star_radius are no longer important ''' ''' Constants ''' h = 6.62607015e-34 #Planck constant in J s c = 299792458 #Light speed in m s-1 eps_0 = 8.85418782e-12 #epsilon_0, vacuum permitivity in F (Farad) m-1 z_0 = 0 #the charge state of the neutral molecule z_1 = 1 #the charge state of the ionized 1 molecule one_in_4_pi_eps_0 = 1/( 4*np.pi*(eps_0/1e9) ) ''' Conversions ''' ev = 1.602176634e-19 # 1 ev = 1.60218e-19 J and value of electron charge erg = 1e-7 # 1 erg = 1e-7 J ev_to_erg = ev/erg #1 eV in erg mb = 1e-18 #1Mb = 1e-18cm2, Mb for Megabarn (unit used to express the cross sectional area of nuclei) ''' Saving parameters ''' dust_heating_rate = np.zeros([3]) ionization_rate = np.zeros([2]) recombination_rate = np.zeros([2]) gas_heating_rate = np.zeros([2]) intrinsic_efficiency = np.zeros([2]) class HeatingGas: """ ---------- Returns total_gas_heating, heating_efficiency, g_0 , gamma , t_gas, n_e , n_c total_gas_heating : float, gas heating rate in erg s-1 H-1 heating_efficiency : float, gas heating efficiency g_0 : float, scaling factor of the radiation field gamma : float, ( g_0 * sqrt(t_gas) )/n_e ionization parameter t_gas : float, gas temperature n_e : float, electron density in cm-3 n_c : float, number of carbon atoms in PAHs (size of the PAH) ------- """ def __init__(self, filename, star_radius, t_gas, n_e, n_c, parsec, fc_pah=0.1, ISRF=False): """ ---------- filename : str, name of the file containing wavelength in nm and intensity in erg cm-2 s-1 sr-1 nm-1 of a star or of the interstellar medium star_radius : float, star radius, in unit of solar radius n_e : float, electron density in cm-3 (n_e = n_h * 1.6e-4, n_h : hydrogen density) parsec : float, distance in parsec from the star fc_pah : float, fraction of cosmic carbon locked in PAHs (default: 0.1) ISRF : bool, Interstellar Radiation Field; if true, then the filename is a file for ISRF if false, the radiation field of a star is studied (default: False) ------- """ ''' input parameters ''' self.filename = filename self.t_gas = t_gas self.n_e = n_e self.n_c = n_c self.parsec = parsec self.star_radius = star_radius self.fc_pah = fc_pah self.ISRF = ISRF ''' parameters to be observed ''' self.g_0 = None self.distance = None self.wavelength = None self.wavelength_intensity = None self.energy_intensity = None self.energy = None self.energy_range = None self.energy_negative_charged = None self.energy_neutral = None self.energy_charged = None self.energy_double_charged = None self.pah_cross_a = None self.pah_cross_n = None self.pah_cross_c = None self.pah_cross_dc = None self.ip_negative_charged = None self.ip_neutral = None self.ip_charged = None self.detachment_yield = None self.yield_of_first_photoionization = None self.yield_of_second_photoionization = None self.heating_efficiency = None self.total_gas_heating = None self.frac_anion = None self.frac_neutral = None self.frac_charged = None self.frac_double_charged = None def parameters(self): ''' others parameters to be returned ''' self.g_0, self.distance, self.wavelength, self.wavelength_intensity, self.energy_intensity, self.energy, self.ISRF, RF_list = rf.radiation_field(self.filename, self.star_radius, self.parsec, self.ISRF, RF_list=False) self.gamma = ( self.g_0 * np.sqrt(self.t_gas) ) / self.n_e #ionization parameter #in cm3 K^1/2 ''' Ionization Potential (IP) estimation ''' a = (self.n_c/468)**(1/3) #molecule diameter, in nm self.ip_neutral = 3.9 + one_in_4_pi_eps_0 * ( ( z_0 + (1/2) ) * (ev**2/a) + ( z_0 + 2 ) * (ev**2/a) *(0.03/a) ) * (1/ev) #IP to ionize the molecule : neutral to charged, in ev self.ip_charged = 3.9 + one_in_4_pi_eps_0 * ( ( z_1 + (1/2) ) * (ev**2/a) + ( z_1 + 2 ) * (ev**2/a) *(0.03/a) ) * (1/ev) #IP to ionize the charged molecule : charged 1 to charged 2, in ev self.ip_negative_charged = 6 #IP to detache the electron from a PAH anion, in ev '''==========================|building of the cross section|=======================''' ''' derives a mean photoabsorption cross section of the molecule considered, in 3 size ranges''' ''' small size ''' self.energy_negative_charged,cross_anion = np.loadtxt('./anions/coronene_anion.txt',unpack=True) #C24 self.energy_negative_charged,crossa_1_case1 = np.loadtxt('./anions/ovalene_anion.txt',unpack=True) #C32 self.energy_neutral,crossn_1_case1 = np.loadtxt('./neutrals/ovalene_neutral.txt',unpack=True) #C32 self.energy_charged,crossc_1_case1 = np.loadtxt('./cations/ovalene_cation.txt',unpack=True) #C32 self.energy_double_charged,crossdc_1_case1 = np.loadtxt('./dications/ovalene_dication.txt',unpack=True) #C32 self.energy_negative_charged,crossa_2_case1 = np.loadtxt('./anions/tetrabenzocoronene_anion.txt',unpack=True) #C36 self.energy_neutral,crossn_2_case1 = np.loadtxt('./neutrals/tetrabenzocoronene_neutral.txt',unpack=True) #C36 self.energy_charged,crossc_2_case1 = np.loadtxt('./cations/tetrabenzocoronene_cation.txt',unpack=True) #C36 self.energy_double_charged,crossdc_2_case1 = np.loadtxt('./dications/tetrabenzocoronene_dication.txt',unpack=True) #C36 self.energy_negative_charged,crossa_3_case1 = np.loadtxt('./anions/circumbiphenyl_anion.txt',unpack=True) #C38 self.energy_neutral,crossn_3_case1 = np.loadtxt('./neutrals/circumbiphenyl_neutral.txt',unpack=True) #C38 self.energy_charged,crossc_3_case1 = np.loadtxt('./cations/circumbiphenyl_cation.txt',unpack=True) #C38 self.energy_double_charged,crossdc_3_case1 = np.loadtxt('./dications/circumbiphenyl_dication.txt',unpack=True) #C38 ''' medium size ''' self.energy_negative_charged,crossa_1_case2 = np.loadtxt('./anions/circumanthracene_anion.txt',unpack=True) #C40 self.energy_neutral,crossn_1_case2 = np.loadtxt('./neutrals/circumanthracene_neutral.txt',unpack=True) #C40 self.energy_charged,crossc_1_case2 = np.loadtxt('./cations/circumanthracene_cation.txt',unpack=True) #C40 self.energy_double_charged,crossdc_1_case2 = np.loadtxt('./dications/circumanthracene_dication.txt',unpack=True) #C40 self.energy_negative_charged,crossa_2_case2 = np.loadtxt('./anions/circumpyrene_anion.txt',unpack=True) #C42 self.energy_neutral,crossn_2_case2 = np.loadtxt('./neutrals/circumpyrene_neutral.txt',unpack=True) #C42 self.energy_charged,crossc_2_case2 = np.loadtxt('./cations/circumpyrene_cation.txt',unpack=True) #C42 self.energy_double_charged,crossdc_2_case2 = np.loadtxt('./dications/circumpyrene_dication.txt',unpack=True) #C42 self.energy_negative_charged,crossa_3_case2 = np.loadtxt('./anions/hexabenzocoronene_anion.txt',unpack=True) #C42 self.energy_neutral,crossn_3_case2 = np.loadtxt('./neutrals/hexabenzocoronene_neutral.txt',unpack=True) #C42 self.energy_charged,crossc_3_case2 = np.loadtxt('./cations/hexabenzocoronene_cation.txt',unpack=True) #C42 self.energy_double_charged,crossdc_3_case2 = np.loadtxt('./dications/hexabenzocoronene_dication.txt',unpack=True) #C42 ''' large size ''' self.energy_negative_charged,crossa_1_case3 = np.loadtxt('./anions/dicoronylene_anion.txt',unpack=True) #C48 self.energy_neutral,crossn_1_case3 = np.loadtxt('./neutrals/dicoronylene_neutral.txt',unpack=True) #C48 self.energy_charged,crossc_1_case3 = np.loadtxt('./cations/dicoronylene_cation.txt',unpack=True) #C48 self.energy_double_charged,crossdc_1_case3 = np.loadtxt('./dications/dicoronylene_dication.txt',unpack=True) #C48 self.energy_negative_charged,crossa_2_case3 = np.loadtxt('./anions/circumcoronene_anion.txt',unpack=True) #C54 self.energy_neutral,crossn_2_case3 = np.loadtxt('./neutrals/circumcoronene_neutral.txt',unpack=True) #C54 self.energy_charged,crossc_2_case3 = np.loadtxt('./cations/circumcoronene_cation.txt',unpack=True) #C54 self.energy_double_charged,crossdc_2_case3 = np.loadtxt('./dications/circumcoronene_dication.txt',unpack=True) #C54 self.energy_negative_charged,crossa_3_case3 = np.loadtxt('./anions/circumovalene_anion.txt',unpack=True) #C66 self.energy_neutral,crossn_3_case3 = np.loadtxt('./neutrals/circumovalene_neutral.txt',unpack=True) #C66 self.energy_charged,crossc_3_case3 = np.loadtxt('./cations/circumovalene_cation.txt',unpack=True) #C66 self.energy_double_charged,crossdc_3_case3 = np.loadtxt('./dications/circumovalene_dication.txt',unpack=True) #C66 #for each cross section for each state of the molecule, we have an associated energy self.energy_range = np.where(self.energy_neutral<13.6)[0] self.pah_cross_a = ( ( (cross_anion/24) +(crossa_1_case1/32)+(crossa_2_case1/36) +\ (crossa_3_case1/38)+(crossa_1_case2/40)+(crossa_2_case2/42) +\ (crossa_3_case2/42)+(crossa_1_case3/48)+(crossa_2_case3/54) +\ (crossa_3_case3/66) )/10 ) * self.n_c self.pah_cross_n = ( ( (crossn_1_case1/32)+(crossn_2_case1/36)+(crossn_3_case1/38) +\ (crossn_1_case2/40)+(crossn_2_case2/42)+(crossn_3_case2/42) +\ (crossn_1_case3/48)+(crossn_2_case3/54)+(crossn_3_case3/66) )/9 ) * self.n_c self.pah_cross_c = ( ( (crossc_1_case1/32)+(crossc_2_case1/36)+(crossc_3_case1/38) +\ (crossc_1_case2/40)+(crossc_2_case2/42)+(crossc_3_case2/42) +\ (crossc_1_case3/48)+(crossc_2_case3/54)+(crossc_3_case3/66) )/9 ) * self.n_c self.pah_cross_dc = ( ((crossdc_1_case1/32)+(crossdc_2_case1/36)+(crossdc_3_case1/38) +\ (crossdc_1_case2/40)+(crossdc_2_case2/42)+(crossdc_3_case2/42) +\ (crossdc_1_case3/48)+(crossdc_2_case3/54)+(crossdc_3_case3/66) )/9 ) * self.n_c #cross_n is the average cross section of a pah of all types of size ''' Ranges imposed ''' self.energy_negative_charged = self.energy_negative_charged[self.energy_range] self.energy_neutral = self.energy_neutral[self.energy_range] self.energy_charged = self.energy_charged[self.energy_range] self.energy_double_charged = self.energy_double_charged[self.energy_range] self.pah_cross_a = self.pah_cross_a[self.energy_range] self.pah_cross_n = self.pah_cross_n[self.energy_range] self.pah_cross_c = self.pah_cross_c[self.energy_range] self.pah_cross_dc = self.pah_cross_dc[self.energy_range] ''' yield from anion to neutral ''' part = np.where(self.energy_negative_charged<=13.6)[0] self.detachment_yield = np.full(len(part), 1) #the anion being unstable, any energy is enough to detach the electron ''' yield from neutral to the first photoionization ''' first_part = np.where(self.energy_neutral=self.ip_neutral) & (self.energy_neutral<(self.ip_neutral+9.2) ) )[0] third_part = np.where((self.energy_neutral>self.ip_neutral+9.2))[0] y_1 = np.zeros([len(first_part)]) y_2 = ( self.energy_neutral[second_part]-self.ip_neutral )/9.2 y_3 = np.full(len(third_part), 1) self.yield_of_first_photoionization = np.concatenate( (y_1,y_2,y_3) ) ''' yield from the first photoionization to the second photoionization ''' alpha = 0.3 #teepness coefficient, see Wenzel et al. 2020 if (self.n_c >= 32) & (self.n_c < 50): beta = 0.59 + 8.1e-3 * self.n_c if self.n_c >= 50: beta = 1 first_part = np.where( self.energy_charged=self.ip_charged ) & ( self.energy_charged<11.3 ) )[0] third_part = np.where( ( self.energy_charged>=11.3 ) & ( self.energy_charged<12.9 ) )[0] fourth_part = np.where( ( self.energy_charged>=12.9 ) & ( self.energy_charged<13.6 ) )[0] y_1 = np.zeros([len(first_part)]) y_2 = ( alpha/(11.3-self.ip_charged) ) * (self.energy_charged[second_part]-self.ip_charged) y_3 = np.full(len(third_part), alpha) y_4 = ( (beta-alpha)/2.1 ) * (self.energy_charged[fourth_part]-12.9) + alpha self.yield_of_second_photoionization = np.concatenate( (y_1,y_2,y_3,y_4) ) ''' adaptating the cross section from 0 to 13.6eV''' pah_crossa = np.interp(self.energy,self.energy_negative_charged,self.pah_cross_a)*mb #in cm2/Carbon (from Mb/C to cm2/C) pah_crossn = np.interp(self.energy,self.energy_neutral,self.pah_cross_n)*mb #in cm2/Carbon (from Mb/C to cm2/C) pah_crossc = np.interp(self.energy,self.energy_charged,self.pah_cross_c)*mb #in cm2/Carbon (from Mb/C to cm2/C) pah_crossdc = np.interp(self.energy,self.energy_double_charged,self.pah_cross_dc)*mb #in cm2/Carbon (from Mb/C to cm2/C) yield_a = np.interp(self.energy,self.energy_negative_charged,self.detachment_yield) yield_n = np.interp(self.energy,self.energy_neutral,self.yield_of_first_photoionization) yield_c = np.interp(self.energy,self.energy_charged,self.yield_of_second_photoionization) #interpolation '''===================== dust and gas heating calculation ===================''' energy_range_power_absorbed = np.where(self.energy<=13.6)[0] energy_range_anion = np.where(np.logical_and(self.energy >= self.ip_negative_charged, self.energy <= 13.6))[0] energy_range_neutral = np.where(np.logical_and(self.energy >= self.ip_neutral, self.energy <= 13.6))[0] energy_range_charged = np.where(np.logical_and(self.energy >= self.ip_charged, self.energy <= 13.6))[0] ''' photoabsorption of the neutrals, cations and dications molecules ''' photo_absorption_a = self.energy_intensity*pah_crossa*2*np.pi #erg s-1 eV-1 /!\ the 2*np.pi is the solid angle considered => the RF comes from the star only photo_absorption_n = self.energy_intensity*pah_crossn*2*np.pi #erg s-1 eV-1 photo_absorption_c = self.energy_intensity*pah_crossc*2*np.pi #erg s-1 eV-1 photo_absorption_dc = self.energy_intensity*pah_crossdc*2*np.pi #erg s-1 eV-1 ''' power density absorbed for ionization ''' ionization_absorption_a = yield_a*photo_absorption_a #erg s-1 eV-1 ionization_absorption_n = yield_n*photo_absorption_n #erg s-1 eV-1 ionization_absorption_c = yield_c*photo_absorption_c #erg s-1 eV-1 ''' number of ionizations ''' number_ionization_absorption_a = ionization_absorption_a/(self.energy*ev_to_erg) number_ionization_absorption_n = ionization_absorption_n/(self.energy*ev_to_erg) number_ionization_absorption_c = ionization_absorption_c/(self.energy*ev_to_erg) #number of ionizations per s per eV for a charge state ''' detachment rate ''' kdet = np.trapz(number_ionization_absorption_a[energy_range_anion], (self.energy-self.ip_negative_charged)[energy_range_anion]) #in s-1 ''' photoemission rate ''' kpe_neutral = np.trapz(number_ionization_absorption_n[energy_range_neutral], (self.energy-self.ip_neutral)[energy_range_neutral]) kpe_charged = np.trapz(number_ionization_absorption_c[energy_range_charged], (self.energy-self.ip_charged)[energy_range_charged]) #in s-1 ''' attachment rate ''' #for coronene katt = self.n_e * 2.74e-9 * (self.t_gas/300)**(0.11) * np.exp(-(-1.12)/self.t_gas) #in s-1 ''' recombination rate ''' phi = ( 1.85 * 1e5 )/( self.t_gas*np.sqrt(self.n_c) ) #dimensionless krec_neutral = self.n_e * 1.28e-10 * self.n_c * np.sqrt(self.t_gas) * ( 1 + phi ) krec_charged = self.n_e * 1.28e-10 * self.n_c * np.sqrt(self.t_gas) * ( 1 + phi * (1 + z_1) ) #in s-1 ''' population fraction computation ''' #anions self.frac_anion = 1/(1 + (kdet/katt) + (kdet*kpe_neutral)/(katt*krec_neutral) + (kdet*kpe_neutral*kpe_charged)/(katt*krec_neutral*krec_charged)) #neutrals self.frac_neutral = 1/(1 + (katt/kdet) + (kpe_neutral/krec_neutral) + (kpe_neutral*kpe_charged)/(krec_neutral*krec_charged)) #cations self.frac_charged = 1/(1 + (krec_neutral/kpe_neutral) + (krec_neutral*katt)/(kdet*kpe_neutral) + (kpe_charged/krec_charged)) #dications self.frac_double_charged = 1/(1 + (krec_charged/kpe_charged) + (krec_neutral*krec_charged)/(kpe_neutral*kpe_charged) + (katt*krec_neutral*krec_charged)/(kdet*kpe_neutral*kpe_charged) ) ''' selection of the partition coefficient ''' partition_coeff = 0.46 #pah parameter, 0.46 + or - 0.06 ''' spectrum of the gas heating per charge state ''' anion_heating_rate_spectrum = 1 * (self.energy-self.ip_negative_charged) *\ number_ionization_absorption_a * ev_to_erg #erg s-1 eV-1 neutral_heating_rate_spectrum = partition_coeff * (self.energy-self.ip_neutral) *\ number_ionization_absorption_n * ev_to_erg #erg s-1 eV-1 charged_heating_rate_spectrum = partition_coeff * (self.energy-self.ip_charged) *\ number_ionization_absorption_c * ev_to_erg #erg s-1 eV-1 #partition_coeff * (E-IP) is the kinetic energy of the photoelectron following absorption of a UV photon of energy E ''' gas heating rate per charge state ''' anion_gas_heating_rate = np.trapz(anion_heating_rate_spectrum[energy_range_anion],\ (self.energy-self.ip_negative_charged)[energy_range_anion] ) #erg s-1 molecule-1 neutral_gas_heating_rate = np.trapz(neutral_heating_rate_spectrum[energy_range_neutral],\ (self.energy-self.ip_neutral)[energy_range_neutral] ) #erg s-1 molecule-1 charged_gas_heating_rate = np.trapz(charged_heating_rate_spectrum[energy_range_charged],\ (self.energy-self.ip_charged)[energy_range_charged] ) #erg s-1 molecule-1 #powers injected in the gas by photoelectrons ejected from #anionic neutral and cationic PAHs '''======================== heating efficiencies ========================''' ''' total power injected into the gas via the photoelectrons from pahs ''' total_injected_power = self.frac_anion * anion_gas_heating_rate +\ self.frac_neutral * neutral_gas_heating_rate +\ self.frac_charged * charged_gas_heating_rate #erg s-1 /(molecule of size n_c) ''' heating rate of the molecule itself ''' heating_pah_a = np.trapz(photo_absorption_a[energy_range_power_absorbed],\ self.energy[energy_range_power_absorbed]) #erg s-1 heating_pah_n = np.trapz(photo_absorption_n[energy_range_power_absorbed],\ self.energy[energy_range_power_absorbed]) #erg s-1 heating_pah_c = np.trapz(photo_absorption_c[energy_range_power_absorbed],\ self.energy[energy_range_power_absorbed]) #erg s-1 heating_pah_dc = np.trapz(photo_absorption_dc[energy_range_power_absorbed],\ self.energy[energy_range_power_absorbed]) #erg s-1 #power absorbed by each PAH charge state ''' total power of the radiation absorbed by pahs ''' total_absorbed_radiation_power = self.frac_anion * heating_pah_a +\ self.frac_neutral * heating_pah_n +\ self.frac_charged * heating_pah_c +\ self.frac_double_charged * heating_pah_dc #erg s-1 /(molecule of size n_c) ''' heating efficiency ''' self.heating_efficiency = total_injected_power/total_absorbed_radiation_power ''' gas heating ''' self.total_gas_heating = total_injected_power * (self.fc_pah/self.n_c) * 2.7e-4 #2.7e-4 : elemental abundance of C relative to H (Tielens 2021) #total gas heating in erg s-1 H-1 return self.total_gas_heating, self.heating_efficiency, self.g_0 , self.gamma, self.t_gas, self.n_e, self.n_c