def dz2dDc(self,z1,z2): r""" Comoving distance between `z1` and `z2`. Two calls to :func:`Dcz` are made and the results subtracted. `z1` < `z2` produces positive comoving distance. .. math:: D_C(z1,z2) = d_{\rm H_0} \int_{z1}^{z2} \frac{dz'}{E(z')} """ if utils.isiterable(z1) and utils.isiterable(z2): if not len(z1) == len(z2): raise utils.InputError, "len(z1) /= len(z2)" Dc1 = self.Dcz(z1) Dc2 = self.Dcz(z2) Dc = Dc2 - Dc1 return Dc
def dz2dDc(self, z1, z2): r""" Comoving distance between `z1` and `z2`. Two calls to :func:`Dcz` are made and the results subtracted. `z1` < `z2` produces positive comoving distance. .. math:: D_C(z1,z2) = d_{\rm H_0} \int_{z1}^{z2} \frac{dz'}{E(z')} """ if utils.isiterable(z1) and utils.isiterable(z2): if not len(z1) == len(z2): raise utils.InputError, "len(z1) /= len(z2)" Dc1 = self.Dcz(z1) Dc2 = self.Dcz(z2) Dc = Dc2 - Dc1 return Dc
def return_Eth( self, Z, N ): """ Returns threshold ionization energy for ions defined by `Z` and `N`. Args: `Z` (int): atomic number (number of protons) `N` (int): electron number (number of electrons) Returns: `Eth` (float): ionization energy """ # Make sure input is OK #-------------------------------------------------------- if utils.isiterable( Z ) or not isinstance(Z,int): raise utils.InputError, '\n Z must be an integer scalar' if utils.isiterable( N ) or not isinstance(N,int): raise utils.InputError, '\n N must be an integer scalar' if Z < 1 or Z > 26: raise utils.InputError, '\n We must have 1 <= Z <= 26' if N < 1 or N > Z: raise utils.InputError, '\n We must have 1 <= N <= Z' # calculate #-------------------------------------------------------- c1 = self.Z == Z c2 = self.N == N indx = np.where( c1 & c2 ) indx = indx[0][0] Eth = self.Eth[indx] Eth.units = 'eV' return Eth
def Dcz(self,z): r""" Comoving distance between z=0 and z, Dc(z) .. math:: D_C(z) = d_{\rm H_0} \int_{0}^{z} \frac{dz'}{E(z')} """ if utils.isiterable( z ): Dc = np.array( [quad( self.iEz, 0.0, zz )[0] for zz in z] ) Dc = Dc * self.dH0 else: Dc = quad( self.iEz, 0.0, z )[0] * self.dH0 Dc.units = self.cu.Mpc/self.cu.h return Dc
def Dcz(self, z): r""" Comoving distance between z=0 and z, Dc(z) .. math:: D_C(z) = d_{\rm H_0} \int_{0}^{z} \frac{dz'}{E(z')} """ if utils.isiterable(z): Dc = np.array([quad(self.iEz, 0.0, zz)[0] for zz in z]) Dc = Dc * self.dH0 else: Dc = quad(self.iEz, 0.0, z)[0] * self.dH0 Dc.units = self.cu.Mpc / self.cu.h return Dc
def D1z(self, z): r""" Linear growth function D1(z) .. math:: D_1(z) \propto H(z) \int_z^\infty \frac{1+z'}{H(z')^3} dz' The normalization is such that D1z(0) = 1 """ if utils.isiterable(z): int_u = [quad(self._D1z_integrand, zz, np.inf)[0] for zz in z] else: int_u = quad(self._D1z_integrand, z, np.inf)[0] D1 = self.Hz(z) / self.H0 * int_u * self._D1z_Norm return D1
def D1a(self, a): r""" Linear growth function D1(a). .. math:: D_1(a) \propto H(a) \int_0^a \frac{da'}{a'^3 H(a')^3} The normalization is such that D1a(1) = 1 """ if utils.isiterable(a): integral = [quad( self._D1a_integrand, 0.0, aa )[0] for aa in a] else: integral = quad( self._D1a_integrand, 0.0, a )[0] D1 = self.Ha(a) / self.H0 * integral * self._D1a_Norm return D1
def D1z( self, z ): r""" Linear growth function D1(z) .. math:: D_1(z) \propto H(z) \int_z^\infty \frac{1+z'}{H(z')^3} dz' The normalization is such that D1z(0) = 1 """ if utils.isiterable(z): int_u = [quad( self._D1z_integrand, zz, np.inf )[0] for zz in z] else: int_u = quad( self._D1z_integrand, z, np.inf )[0] D1 = self.Hz(z) / self.H0 * int_u * self._D1z_Norm return D1
def D1a(self, a): r""" Linear growth function D1(a). .. math:: D_1(a) \propto H(a) \int_0^a \frac{da'}{a'^3 H(a')^3} The normalization is such that D1a(1) = 1 """ if utils.isiterable(a): integral = [quad(self._D1a_integrand, 0.0, aa)[0] for aa in a] else: integral = quad(self._D1a_integrand, 0.0, a)[0] D1 = self.Ha(a) / self.H0 * integral * self._D1a_Norm return D1
def ta(self, a): r""" Time since a=0 or age of the Universe, t(a). .. math:: t(a) = \int_0^a \frac{da'}{a' H(a')} """ # quad works but takes a long time. #------------------------------------------------------------------- # if utils.isiterable( a ): # t = np.array( [quad( self._dt_da, 0.0, aa )[0] for aa in a] ) # else: # t = quad( self._dt_da, 0.0, a )[0] # t = t * self.cu.s # print 't = ', t.rescale("Myr/hh") # trap rule includes OmegaR and is fast and accurate to 6 decimals #------------------------------------------------------------------- N = 5000 a_min = 1.0e-10 if utils.isiterable(a): t = np.zeros(a.size) * self.cu.s for ii, aa in enumerate(a): xx = np.linspace(a_min, aa, N) yy = 1.0 / (xx * self.Ha(xx)) t[ii] = utils.trap(xx, yy) else: xx = np.linspace(a_min, a, N) yy = 1.0 / (xx * self.Ha(xx)) t = utils.trap(xx, yy) t.units = 'Myr/hh' # this is analytic but does not include OmegaR #------------------------------------------------------------------- # pre = 2.0 / ( 3.0 * np.sqrt( self.OmegaL ) ) # aeq = (self.OmegaM/self.OmegaL)**(1./3.) # arg = (a/aeq)**(3./2.) + np.sqrt(1 + (a/aeq)**3) # t = pre * np.log(arg) * self.tH0 return t
def ta( self, a): r""" Time since a=0 or age of the Universe, t(a). .. math:: t(a) = \int_0^a \frac{da'}{a' H(a')} """ # quad works but takes a long time. #------------------------------------------------------------------- # if utils.isiterable( a ): # t = np.array( [quad( self._dt_da, 0.0, aa )[0] for aa in a] ) # else: # t = quad( self._dt_da, 0.0, a )[0] # t = t * self.cu.s # print 't = ', t.rescale("Myr/hh") # trap rule includes OmegaR and is fast and accurate to 6 decimals #------------------------------------------------------------------- N = 5000 a_min = 1.0e-10 if utils.isiterable( a ): t = np.zeros( a.size ) * self.cu.s for ii,aa in enumerate(a): xx = np.linspace( a_min, aa, N ) yy = 1.0 / ( xx * self.Ha(xx) ) t[ii] = utils.trap( xx, yy ) else: xx = np.linspace( a_min, a, N ) yy = 1.0 / ( xx * self.Ha(xx) ) t = utils.trap( xx, yy ) t.units = 'Myr/hh' # this is analytic but does not include OmegaR #------------------------------------------------------------------- # pre = 2.0 / ( 3.0 * np.sqrt( self.OmegaL ) ) # aeq = (self.OmegaM/self.OmegaL)**(1./3.) # arg = (a/aeq)**(3./2.) + np.sqrt(1 + (a/aeq)**3) # t = pre * np.log(arg) * self.tH0 return t
def analytic_soltn_xH1(self, nH, y, k): """ Analytic equilibrium solution for the neutral fraction xH1 = nH1/nH Args: `nH` (array): H number density `y` (array): electrons from elements heavier than hydrogen, ne = nH * (xH2 + y) `k` (rates object): must contain the attributes ciH1, reH2, and H1i, see :class:`~rabacus.atomic.chemistry.ChemistryRates` Returns: `xH1` (array): neutral hydrogen fraction """ RR = (k.ciH1 + k.reH2) * nH QQ = -(k.H1i + k.reH2 * nH + RR * (1 + y)) PP = k.reH2 * nH * (1.0 + y) dd = QQ * QQ - 4.0 * RR * PP if utils.isiterable(dd): if np.any(dd < 0.0): indx = np.where(dd < 0.0) dd[indx] = dd[indx] * 0.0 else: if dd < 0.0: dd = dd * 0.0 # QQ is always negative in this case #q = -0.5 * (QQ + np.sign(QQ) * np.sqrt(QQ*QQ - 4.0*RR*PP)) q = -0.5 * (QQ - np.sqrt(dd)) xH1 = PP / q return xH1
def analytic_soltn_xH1(self, nH, y, k): """ Analytic equilibrium solution for the neutral fraction xH1 = nH1/nH Args: `nH` (array): H number density `y` (array): electrons from elements heavier than hydrogen, ne = nH * (xH2 + y) `k` (rates object): must contain the attributes ciH1, reH2, and H1i, see :class:`~rabacus.atomic.chemistry.ChemistryRates` Returns: `xH1` (array): neutral hydrogen fraction """ RR = (k.ciH1 + k.reH2) * nH QQ = -( k.H1i + k.reH2 * nH + RR * (1+y) ) PP = k.reH2 * nH * (1.0 + y) dd = QQ*QQ - 4.0*RR*PP if utils.isiterable( dd ): if np.any( dd < 0.0 ): indx = np.where( dd < 0.0 ) dd[indx] = dd[indx] * 0.0 else: if dd < 0.0: dd = dd * 0.0 # QQ is always negative in this case #q = -0.5 * (QQ + np.sign(QQ) * np.sqrt(QQ*QQ - 4.0*RR*PP)) q = -0.5 * ( QQ - np.sqrt(dd) ) xH1 = PP / q return xH1
def _clean_input(self, T, fcA_H2, fcA_He2, fcA_He3, H1i, He1i, He2i): # check temperature #------------------------------------------ if not hasattr(T, 'shape'): raise utils.InputError, '\n T must be an array with units. \n' if T.shape == (): self.T = np.ones(1) * T else: self.T = T.copy() if not hasattr(self.T, 'units'): raise utils.NeedUnitsError, '\n T must have units \n' else: self.T.units = 'K' # check fcA #------------------------------------------ if utils.isiterable(fcA_H2): assert fcA_H2.shape == self.T.shape self.fcA_H2 = fcA_H2.copy() else: self.fcA_H2 = np.ones(self.T.shape) * fcA_H2 if utils.isiterable(fcA_He2): assert fcA_He2.shape == self.T.shape self.fcA_He2 = fcA_He2.copy() else: self.fcA_He2 = np.ones(self.T.shape) * fcA_He2 if utils.isiterable(fcA_He3): assert fcA_He3.shape == self.T.shape self.fcA_He3 = fcA_He3.copy() else: self.fcA_He3 = np.ones(self.T.shape) * fcA_He3 # check HI photoionization rate #------------------------------------------ if H1i == None: self.H1i = np.zeros(self.T.shape) / self.U.s else: if H1i.shape == (): self.H1i = np.ones(1) * H1i else: self.H1i = H1i.copy() if not hasattr(self.H1i, 'units'): raise utils.NeedUnitsError, '\n H1i must have units \n' else: self.H1i.units = '1/s' if self.H1i.shape != self.T.shape: raise utils.InputError, '\n T and H1i must have same shape \n' # check HeI photoionization rate #------------------------------------------ if He1i == None: self.He1i = np.zeros(1) / self.U.s else: if He1i.shape == (): self.He1i = np.ones(1) * He1i else: self.He1i = He1i.copy() if not hasattr(self.He1i, 'units'): raise utils.NeedUnitsError, '\n He1i must have units \n' else: self.He1i.units = '1/s' if self.He1i.shape != self.T.shape: raise utils.InputError, '\n T and He1i must have same shape \n' # check HeII photoionization rate #------------------------------------------ if He2i == None: self.He2i = np.zeros(1) / self.U.s else: if He2i.shape == (): self.He2i = np.ones(1) * He2i else: self.He2i = He2i.copy() if not hasattr(self.He2i, 'units'): raise utils.NeedUnitsError, '\n He2i must have units \n' else: self.He2i.units = '1/s' if self.He2i.shape != self.T.shape: raise utils.InputError, '\n T and He2i must have same shape \n'
def _clean_input( self, T, fcA_H2, fcA_He2, fcA_He3, H1i, He1i, He2i ): # check temperature #------------------------------------------ if not hasattr( T, 'shape' ): raise utils.InputError, '\n T must be an array with units. \n' if T.shape == (): self.T = np.ones(1) * T else: self.T = T.copy() if not hasattr(self.T,'units'): raise utils.NeedUnitsError, '\n T must have units \n' else: self.T.units = 'K' # check fcA #------------------------------------------ if utils.isiterable( fcA_H2 ): assert fcA_H2.shape == self.T.shape self.fcA_H2 = fcA_H2.copy() else: self.fcA_H2 = np.ones(self.T.shape) * fcA_H2 if utils.isiterable( fcA_He2 ): assert fcA_He2.shape == self.T.shape self.fcA_He2 = fcA_He2.copy() else: self.fcA_He2 = np.ones(self.T.shape) * fcA_He2 if utils.isiterable( fcA_He3 ): assert fcA_He3.shape == self.T.shape self.fcA_He3 = fcA_He3.copy() else: self.fcA_He3 = np.ones(self.T.shape) * fcA_He3 # check HI photoionization rate #------------------------------------------ if H1i == None: self.H1i = np.zeros(self.T.shape) / self.U.s else: if H1i.shape == (): self.H1i = np.ones(1) * H1i else: self.H1i = H1i.copy() if not hasattr(self.H1i,'units'): raise utils.NeedUnitsError, '\n H1i must have units \n' else: self.H1i.units = '1/s' if self.H1i.shape != self.T.shape: raise utils.InputError, '\n T and H1i must have same shape \n' # check HeI photoionization rate #------------------------------------------ if He1i == None: self.He1i = np.zeros(1) / self.U.s else: if He1i.shape == (): self.He1i = np.ones(1) * He1i else: self.He1i = He1i.copy() if not hasattr(self.He1i,'units'): raise utils.NeedUnitsError, '\n He1i must have units \n' else: self.He1i.units = '1/s' if self.He1i.shape != self.T.shape: raise utils.InputError, '\n T and He1i must have same shape \n' # check HeII photoionization rate #------------------------------------------ if He2i == None: self.He2i = np.zeros(1) / self.U.s else: if He2i.shape == (): self.He2i = np.ones(1) * He2i else: self.He2i = He2i.copy() if not hasattr(self.He2i,'units'): raise utils.NeedUnitsError, '\n He2i must have units \n' else: self.He2i.units = '1/s' if self.He2i.shape != self.T.shape: raise utils.InputError, '\n T and He2i must have same shape \n'
def return_fit( self, Z, N, E ): """ Returns a photo-ionization cross-section for an ion defined by `Z` and `N` at energies `E`. Args: `Z` (int): atomic number (number of protons) `N` (int): electron number (number of electrons) `E` (array): calculate cross-section at these energies Returns: `sigma` (array): photoionization cross-sections """ # Make sure input is OK #-------------------------------------------------------- if hasattr(E,'units'): E.units = 'eV' else: raise utils.NeedUnitsError, '\n Input variable E must have units \n' if utils.isiterable( Z ) or not isinstance(Z,int): raise utils.InputError, '\n Z must be an integer scalar' if utils.isiterable( N ) or not isinstance(N,int): raise utils.InputError, '\n N must be an integer scalar' if Z < 1 or Z > 26: raise utils.InputError, '\n We must have 1 <= Z <= 26' if N < 1 or N > Z: raise utils.InputError, '\n We must have 1 <= N <= Z' # calculate fit #-------------------------------------------------------- c1 = self.Z == Z c2 = self.N == N indx = np.where( c1 & c2 ) indx = indx[0][0] Z = self.Z[indx] N = self.N[indx] Eth = self.Eth[indx] Emax = self.Emax[indx] E0 = self.E0[indx] sigma0 = self.sigma0[indx] ya = self.ya[indx] P = self.P[indx] yw = self.yw[indx] y0 = self.y0[indx] y1 = self.y1[indx] x = E / E0 - y0 y = np.sqrt( x*x + y1*y1 ) t1 = (x-1)*(x-1) + yw*yw t2 = y**(0.5*P - 5.5) t3 = (1+np.sqrt(y/ya))**(-P) F = t1 * t2 * t3 sigma = sigma0 * F # zero cross-section below threshold #-------------------------------------------------------- if utils.isiterable( E ): indx = np.where( E < Eth ) if indx[0].size > 0: sigma[indx] = 0.0 * self.U.cm**2 else: if E < Eth: sigma = 0.0 * self.U.cm**2 return sigma
def _clean_input( self, T, fcA_H2, fcA_He2, fcA_He3, H1h, He1h, He2h ): # check temperature #------------------------------------------ if not hasattr( T, 'shape' ): raise utils.InputError, '\n T must be an array with units. \n' if T.shape == (): self.T = np.ones(1) * T else: self.T = T.copy() if not hasattr(self.T,'units'): raise utils.NeedUnitsError, '\n T must have units \n' else: self.T.units = 'K' # check fcA #------------------------------------------ if utils.isiterable( fcA_H2 ): assert fcA_H2.shape == self.T.shape self.fcA_H2 = fcA_H2.copy() else: self.fcA_H2 = np.ones(self.T.shape) * fcA_H2 if utils.isiterable( fcA_He2 ): assert fcA_He2.shape == self.T.shape self.fcA_He2 = fcA_He2.copy() else: self.fcA_He2 = np.ones(self.T.shape) * fcA_He2 if utils.isiterable( fcA_He3 ): assert fcA_He3.shape == self.T.shape self.fcA_He3 = fcA_He3.copy() else: self.fcA_He3 = np.ones(self.T.shape) * fcA_He3 # check HI photo-heating rate #------------------------------------------ if H1h == None: self.H1h = np.zeros(1) * self.u.erg / self.u.s else: if H1h.shape == (): self.H1h = np.ones(1) * H1h else: self.H1h = H1h.copy() if not hasattr(self.H1h,'units'): raise utils.NeedUnitsError, '\n H1h must have units \n' else: self.H1h.units = 'erg/s' if self.H1h.shape != self.T.shape: raise utils.InputError, '\n T and H1h must have same shape \n' # check HeI photo-heating rate #------------------------------------------ if He1h == None: self.He1h = np.zeros(1) * self.u.erg / self.u.s else: if He1h.shape == (): self.He1h = np.ones(1) * He1h else: self.He1h = He1h.copy() if not hasattr(self.He1h,'units'): raise utils.NeedUnitsError, '\n He1h must have units \n' else: self.He1h.units = 'erg/s' if self.He1h.shape != self.T.shape: raise utils.InputError, '\n T and He1h must have same shape \n' # check HeII photo-heating rate #------------------------------------------ if He2h == None: self.He2h = np.zeros(1) * self.u.erg / self.u.s else: if He2h.shape == (): self.He2h = np.ones(1) * He2h else: self.He2h = He2h.copy() if not hasattr(self.He2h,'units'): raise utils.NeedUnitsError, '\n He2h must have units \n' else: self.He2h.units = 'erg/s' if self.He2h.shape != self.T.shape: raise utils.InputError, '\n T and He2h must have same shape \n'