def test_get_A(self): # Testing partition function method exp_sm_q = self.H2O_TS_sm.get_q(T=c.T0('K'), include_ZPE=False) \ / self.H2_sm.get_q(T=c.T0('K'), include_ZPE=False) \ / self.O2_sm.get_q(T=c.T0('K'), include_ZPE=False)**0.5 exp_sm_A = c.kb('J/K') * c.T0('K') / c.h('J s') * exp_sm_q exp_sm_q_rev = self.H2O_TS_sm.get_q(T=c.T0('K'), include_ZPE=False) \ / self.H2O_sm.get_q(T=c.T0('K'), include_ZPE=False) exp_sm_A_rev = c.kb('J/K') * c.T0('K') / c.h('J s') * exp_sm_q_rev np.testing.assert_almost_equal(self.rxn_sm.get_A(T=c.T0('K')), exp_sm_A, decimal=0) np.testing.assert_almost_equal(self.rxn_sm.get_A(T=c.T0('K'), rev=True), exp_sm_A_rev, decimal=0) # Testing entropy method exp_sm_SoR = self.H2O_TS_sm.get_SoR(T=c.T0('K')) \ - self.H2_sm.get_SoR(T=c.T0('K')) \ - self.O2_sm.get_SoR(T=c.T0('K'))*0.5 exp_sm_A = c.kb('J/K') * c.T0('K') / c.h('J s') * np.exp(exp_sm_SoR) exp_sm_SoR_rev = self.H2O_TS_sm.get_SoR(T=c.T0('K')) \ - self.H2O_sm.get_SoR(T=c.T0('K')) exp_sm_A_rev = c.kb('J/K')*c.T0('K')/c.h('J s') \ * np.exp(exp_sm_SoR_rev) np.testing.assert_almost_equal(self.rxn_sm.get_A(T=c.T0('K'), use_q=False), exp_sm_A, decimal=0) np.testing.assert_almost_equal(self.rxn_sm.get_A(T=c.T0('K'), rev=True, use_q=False), exp_sm_A_rev, decimal=0)
def __init__(self, A_st=None, atoms=None, symmetrynumber=None, inertia=None, geometry=None, vib_wavenumbers=None, potentialenergy=None, **kwargs): super().__init__(atoms=atoms, symmetrynumber=symmetrynumber, geometry=geometry, vib_wavenumbers=vib_wavenumbers, potentialenergy=potentialenergy, **kwargs) self.A_st = A_st self.atoms = atoms self.geometry = geometry self.symmetrynumber = symmetrynumber self.inertia = inertia self.etotal = potentialenergy self.vib_energies = c.wavenumber_to_energy(np.array(vib_wavenumbers)) self.theta = np.array(self.vib_energies) / c.kb('eV/K') self.zpe = sum(np.array(self.vib_energies)/2.) *\ c.convert_unit(initial='eV', final='kcal')*c.Na if np.sum(self.vib_energies) != 0: self.q_vib = np.product( np.divide(1, (1 - np.exp(-self.theta / c.T0('K'))))) if self.phase == 'G': if self.inertia is not None: self.I3 = self.inertia else: self.I3 = atoms.get_moments_of_inertia() *\ c.convert_unit(initial='A2', final='m2') *\ c.convert_unit(initial='amu', final='kg') self.T_I = c.h('J s')**2 / (8 * np.pi**2 * c.kb('J/K')) if self.phase == 'G': Irot = np.max(self.I3) if self.geometry == 'nonlinear': self.q_rot = np.sqrt(np.pi*Irot)/self.symmetrynumber *\ (c.T0('K')/self.T_I)**(3./2.) else: self.q_rot = (c.T0('K') * Irot / self.symmetrynumber) / self.T_I else: self.q_rot = 0. if self.A_st is not None: self.MW = mw(self.elements) * c.convert_unit(initial='g', final='kg') / c.Na self.q_trans2D = self.A_st * (2 * np.pi * self.MW * c.kb('J/K') * c.T0('K')) / c.h('J s')**2
def test_kb(self): # Test kb for all units self.assertAlmostEqual(c.kb('J/K'), self.ans.at['test_kb', 0]) self.assertAlmostEqual(c.kb('kJ/K'), self.ans.at['test_kb', 1]) self.assertAlmostEqual(c.kb('eV/K'), self.ans.at['test_kb', 2]) self.assertAlmostEqual(c.kb('cal/K'), self.ans.at['test_kb', 3]) self.assertAlmostEqual(c.kb('kcal/K'), self.ans.at['test_kb', 4]) self.assertAlmostEqual(c.kb('Eh/K'), self.ans.at['test_kb', 5]) self.assertAlmostEqual(c.kb('Ha/K'), self.ans.at['test_kb', 6]) # Test kb raises an error when an unsupported unit is passed with self.assertRaises(KeyError): c.kb('arbitrary unit')
def get_q(self, T, ignore_q_elec=True): """Calculates the partition function :math:`q^{elec}=1 + \\omega_i \\exp\\bigg(-\\frac{E}{RT}\\bigg)` Parameters ---------- T : float Temperature in K ignore_q_elec : bool, optional Ignore contribution of electronic mode to partition function . Often necessary since DFT's value for potentialenergy is very negative causing q_elec to go to infinity. Default is True Returns ------- q_elec : float Electronic partition function """ if ignore_q_elec: return 1. else: if self.D0 is not None: Epsilon = self.D0 / c.kb('eV/K') / T else: Epsilon = self.get_UoRT(T=T) return self._degeneracy * (1 + np.exp(-Epsilon))
def get_SoR(self, T, P=c.P0('bar')): """Calculates the dimensionless entropy :math:`\\frac{S^{trans}}{R}=1+\\frac{n_{degrees}}{2}+\\log\\bigg(\\big( \\frac{2\\pi mk_bT}{h^2})^\\frac{n_{degrees}}{2}\\frac{RT}{PN_a}\\bigg)` Parameters ---------- T : float Temperature in K P : float, optional Pressure (bar) or pressure-like quantity. Default is atmospheric pressure Returns ------- SoR_trans : float Translational dimensionless entropy """ V = self.get_V(T=T, P=P) unit_mass = self.molecular_weight *\ c.convert_unit(initial='g', final='kg')/c.Na return 1. + float(self.n_degrees)/2. \ + np.log((2.*np.pi*unit_mass*c.kb('J/K')*T/c.h('J s')**2) ** (float(self.n_degrees)/2.)*V/c.Na)
def get_A(self, sden_operation='min', include_entropy=True, T=c.T0('K'), units='molec/cm2', **kwargs): """Calculates the preexponential factor in the Cantera format Parameters ---------- sden_operation : str, optional Site density operation to use. Default is 'min' include_entropy : bool, optional If True, includes the entropy of activation. Default is True T : float, optional Temperature in K. Default is 298.15 K units : str or :class:`~pmutt.omkm.units.Units`, optional Units for A. If `Units` class specified, determines the units for A. Default is 'molec/cm2' kwargs : keyword arguments Parameters required to calculate pre-exponential factor """ if self.transition_state is None or not include_entropy: A = c.kb('J/K') / c.h('J s') else: A = super().get_A(T=T, **kwargs) / T # Uses site with highest site density site_dens = [] for reactant, stoich in zip(self.reactants, self.reactants_stoich): # Skip species without a catalyst site try: site_den = reactant.phase.site_density except AttributeError: continue site_dens.extend([site_den] * int(stoich)) # Apply the operation to the site densities if len(site_dens) == 0: err_msg = ('At least one species requires a catalytic site with ' 'site density to calculate A.') raise ValueError(err_msg) eff_site_den = _apply_numpy_operation(quantity=site_dens, operation=sden_operation, verbose=False) # Convert site density to appropriate unit if isinstance(units, Units): quantity_unit = units.quantity area_unit = '{}2'.format(units.length) else: quantity_unit, area_unit = units.split('/') eff_site_den = eff_site_den\ *c.convert_unit(initial='mol', final=quantity_unit)\ /c.convert_unit(initial='cm2', final=area_unit) n_surf = self._get_n_surf() A = A / eff_site_den**(n_surf - 1) return A
def get_ZPE(self): """Calculates the zero point energy :math:`ZPE=\\frac{1}{2}k_b\\sum_i \\Theta_{V,i}` Returns ------- zpe : float Zero point energy in eV """ return 0.5 * c.kb('eV/K') * np.sum(self._valid_vib_temperatures)
def get_ZPE(self): """Calculates the zero point energy :math:`u^0_E=u+\\frac{3}{2}\\Theta_E k_B` Returns ------- zpe : float Zero point energy in eV """ return self.interaction_energy \ + 1.5*self.einstein_temperature*c.kb('eV/K')
def get_UoRT(self, T): """Calculates the imensionless internal energy :math:`\\frac{U^{elec}}{RT}=\\frac{E}{RT}` Parameters ---------- T : float Temperature in K Returns ------- UoRT_elec : float Electronic dimensionless internal energy """ return (self.potentialenergy) / c.kb('eV/K') / T
def _get_SoR_RRHO(self, T, vib_inertia): """Calculates the dimensionless RRHO contribution to entropy Parameters ---------- T : float Temperature in K vib_inertia : float Vibrational inertia in kg m2 Returns ------- SoR_RHHO : float Dimensionless entropy of Rigid Rotor Harmonic Oscillator """ return 0.5 + np.log( (8. * np.pi**3 * vib_inertia * c.kb('J/K') * T / c.h('J s')**2)** 0.5)
def get_UoRT(self, T): """Calculates the dimensionless internal energy :math:`\\frac{U^{vib}}{RT}=\\frac{u^0_E}{k_BT}+3\\frac{\\Theta_E}{T} \\bigg(\\frac{\\exp(-\\frac{\\Theta_E}{T})}{1-\\exp(-\\frac{\\Theta_E} {T})}\\bigg)` Parameters ---------- T : float Temperature in K Returns ------- UoRT_vib : float Vibrational dimensionless internal energy """ theta_E = self.einstein_temperature return self.get_ZPE()/c.kb('eV/K')/T \ + 3.*theta_E/T*np.exp(-theta_E/T)/(1. - np.exp(-theta_E/T))
def get_q(self, T): """Calculates the partition function :math:`q^{vib}=\\exp\\bigg({\\frac{-u}{k_BT}}\\bigg)\\bigg(\\frac{ \\exp(-\\frac{\\Theta_E}{2T})}{1-\\exp(\\frac{-\\Theta_E}{T})}\\bigg)` Parameters ---------- T : float Temperature in K Returns ------- q_vib : float Vibrational partition function """ u = self.interaction_energy theta_E = self.einstein_temperature return np.exp(-u/c.kb('eV/K')/T) \ * (np.exp(-theta_E/2./T)/(1. - np.exp(-theta_E/T)))
def get_UoRT(self, T): """Calculates dimensionless internal energy :math:`\\frac{U^{vib}}{RT} = \\frac{u_D^o}{RT} + 3F\\bigg(\\frac{ \\Theta_D}{T}\\bigg)` :math:`F\\bigg(\\frac{\\Theta_D}{T}\\bigg) = 3\\bigg(\\frac{T}{ \\Theta_D}\\bigg)^3 \\int_0^{\\frac{\\Theta_D}{T}} \\frac{x^3 e^x} {e^x-1} dx` Parameters ---------- T : float Temperature in K Returns ------- UoRT : float Dimensionless internal energy """ return self.get_ZPE()/c.kb('eV/K')/T \ + 3.*self._get_intermediate_fn(T=T, fn=self._F_integrand)
def get_q(self, T): """Calculate the partition function :math:`q^{vib} = \\exp\\bigg(-\\frac{u}{3k_B T} - \\frac{3}{8} \\frac{\\Theta_D}{T} - G\\big(\\frac{\\Theta_D}{T}\\big)\\bigg)` :math:`G\\bigg(\\frac{\\Theta_D}{T}\\bigg) = 3\\bigg(\\frac{T}{ \\Theta_D}\\bigg)^3\\int_0^{\\frac{\\Theta_D}{T}}x^2 \\ln \\bigg(1-e^{-x}\\bigg)dx` Parameters ---------- T : float Temperature in K Returns ------- q : float Partition function """ G = self._get_intermediate_fn(T=T, fn=self._G_integrand) return np.exp(-self.interaction_energy/3./c.kb('eV/K')/T \ -3./8.*self.debye_temperature/T - G)
def get_q(self, T, P=c.P0('bar')): """Calculates the partition function :math:`q_{trans} = \\bigg(\\frac{2\\pi \\sum_{i}^{atoms}m_ikT}{h^2} \\bigg)^\\frac {n_{degrees}} {2}V` Parameters ---------- T : float Temperature in K P : float, optional Pressure (bar) or pressure-like quantity. Default is atmospheric pressure Returns ------- q_trans : float Translational partition function """ V = self.get_V(T=T, P=P) unit_mass = self.molecular_weight *\ c.convert_unit(initial='g', final='kg')/c.Na return V*(2*np.pi*c.kb('J/K')*T*unit_mass/c.h('J s')**2) \ ** (float(self.n_degrees)/2.)
def test_kb(self): self.assertEqual(c.kb('J/K'), 1.38064852e-23) with self.assertRaises(KeyError): c.kb('arbitrary unit')
def test_get_HoRT(self): exp_HoRT = (self.u + 9./8.*self.theta_D*c.kb('eV/K')) \ /c.kb('eV/K')/self.T \ + 3.*self.F self.assertAlmostEqual(self.vib_Ag.get_HoRT(T=self.T), exp_HoRT, 4)
def test_get_ZPE(self): self.assertAlmostEqual(self.vib_Ag.get_ZPE(), self.u + 9. / 8. * self.theta_D * c.kb('eV/K'))
def test_get_q(self): exp_q = np.exp(-self.u/3./c.kb('eV/K')/self.T \ -3./8.*self.theta_D/self.T \ -self.G) self.assertAlmostEqual(self.vib_Ag.get_q(T=self.T), exp_q)