def _fit_HoRT(T_ref, HoRT_ref, a, units): """Fit a[5] coefficient in a_low and a_high attributes given the dimensionless enthalpy Parameters ---------- T_ref : float Reference temperature in K HoRT_ref : float Reference dimensionless enthalpy T_mid : float Temperature to fit the offset units : str Units corresponding to Shomate polynomial. Units should be supported by :class:`~pmutt.constants.R`. Returns ------- a : (8,) `numpy.ndarray`_ Lower coefficients of Shomate polynomial .. _`numpy.ndarray`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html """ a[5] = (HoRT_ref \ - get_shomate_HoRT(T=np.array([T_ref]), a=a, units=units)) \ *c.R(units)*T_ref/c.prefixes['k'] a[7] = - get_shomate_HoRT(T=np.array([c.T0('K')]), a=a, units=units) \ *c.R(units)*c.T0('K')/c.prefixes['k'] return a
def test_T0(self): # Test T0 for all units self.assertAlmostEqual(c.T0('K'), self.ans.at['test_T0', 0]) self.assertAlmostEqual(c.T0('C'), self.ans.at['test_T0', 1]) self.assertAlmostEqual(c.T0('R'), self.ans.at['test_T0', 2]) self.assertAlmostEqual(c.T0('F'), self.ans.at['test_T0', 3]) # Test T0 raises an error when an unsupported unit is passed with self.assertRaises(ValueError): c.T0('arbitrary unit')
def test_get_FoRT(self): self.assertAlmostEqual(self.lsr_const.get_UoRT(), 4.5 / c.R('kcal/mol/K') / c.T0('K'), places=2) self.assertAlmostEqual(self.lsr_shomate.get_UoRT(), 4.5 / c.R('kcal/mol/K') / c.T0('K'), places=2) self.assertAlmostEqual(self.lsr_statmech.get_UoRT(), 4.5 / c.R('kcal/mol/K') / c.T0('K'), places=2)
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 get_Vm(self, T=c.T0('K'), P=c.P0('bar'), gas_phase=True): """Calculates the molar volume of a van der Waals gas Parameters ---------- T : float, optional Temperature in K. Default is standard temperature P : float, optional Pressure in bar. Default is standard pressure gas_phase : bool, optional Relevant if system is in vapor-liquid equilibrium. If True, return the larger volume (gas phase). If False, returns the smaller volume (liquid phase). Returns ------- Vm : float Volume in m3 """ P_SI = P * c.convert_unit(initial='bar', final='Pa') Vm = np.roots([ P_SI, -(P_SI * self.b + c.R('J/mol/K') * T), self.a, -self.a * self.b ]) real_Vm = np.real([Vm_i for Vm_i in Vm if np.isreal(Vm_i)]) if gas_phase: return np.max(real_Vm) else: return np.min(real_Vm)
def get_H(self, units, T=c.T0('K'), **kwargs): """Calculate the enthalpy Parameters ---------- units : str Units as string. See :func:`~pmutt.constants.R` for accepted units but omit the '/K' (e.g. J/mol). T : float, optional Temperature in K. Default is 298.15 K kwargs : keyword arguments Parameters needed by ``get_HoRT`` Returns ------- H : float Enthalpy in appropriate units """ units = '{}/K'.format(units) try: elements = self.elements except AttributeError: elements = None R_adj = _get_R_adj(units=units, elements=elements) HoRT_kwargs = kwargs.copy() HoRT_kwargs['T'] = T return _force_pass_arguments(self.get_HoRT, **HoRT_kwargs) * T * R_adj
def get_UoRT(self, T=c.T0('K'), **kwargs): """Calculates the dimensionless internal energy using the LSR relationship Parameters ---------- T : float, optional Temperature in K. Default is 298.15 K kwargs : keyword arguments Parameters to calculate reference binding energy, surface energy and gas-phase energy Returns ------- UoRT : float Dimensionless internal energy """ kwargs['T'] = T kwargs['units'] = 'kcal/mol' # Calculate reference binding energy try: deltaE_ref = self.reaction.get_delta_E(**kwargs) except AttributeError: deltaE_ref = self.reaction.get_delta_H(**kwargs) # Calculate surface energy try: E_surf = self.surf_specie.get_E(**kwargs) except AttributeError: E_surf = self.surf_specie.get_H(**kwargs) # Calculate gas-phase energy try: E_gas = self.gas_specie.get_E(**kwargs) except AttributeError: E_gas = self.gas_specie.get_H(**kwargs) return (self.slope*deltaE_ref + self.intercept + E_surf + E_gas) \ /c.R('kcal/mol/K')/T
def get_SoR(self, T=c.T0('K'), entropy_state='reactants', **kwargs): """Calculates the dimensionless entropy using reactants or products entropy. The BEP relationship has no entropic contribution Parameters ---------- T : float, optional Temperature in K. Default is 298.15 entropy_state : str or None, optional State to use to estimate entropy. Supported arguments: - 'reactants' (default) - 'products' - None (Entropy contribution is 0. Useful if misc_models have been specified for entropy) kwargs : keyword arguments Parameters required to calculate the descriptor Returns ------- SoR : float Dimensionless entropy """ if entropy_state is None: SoR = 0. else: SoR = self.reaction.get_SoR_state(state=entropy_state, T=T, **kwargs) return SoR
def get_GoRT(self, T=c.T0('K'), entropy_state='reactants', **kwargs): """Calculates the dimensionless Gibbs energy using BEP relationship and reactants Gibbs energy. The BEP relationship has no entropic contribution Parameters ---------- T : float, optional Temperature in K. Default is 298.15 entropy_state : str or None, optional State to use to estimate entropy. Supported arguments: - 'reactants' (default) - 'products' - None (Entropy contribution is 0. Useful if misc_models have been specified for entropy) kwargs : keyword arguments Parameters required to calculate the descriptor Returns ------- GoRT : float Dimensionless Gibbs energy """ return self.get_HoRT(T=T, **kwargs) \ - self.get_SoR(T=T, entropy_state=entropy_state, **kwargs)
def yaml_construct(cls, params, context): if 'T_ref' in params: T_ref = params['T_ref'] else: #pull room temp from pmutt's constants, append 'K' because eval_qty needs units #T_ref is now of type Quantity, a tuple T_ref = eval_qty(str(c.T0(units=K)) + ' K') if 'ND_H_ref' in params: ND_H_ref = params['ND_H_ref'] else: ND_H_ref = params['H_ref'] / (R * T_ref) if 'ND_S_ref' in params: ND_S_ref = params['ND_S_ref'] else: ND_S_ref = params['S_ref'] / R if 'ND_Cp_data' in params: T_data, ND_Cp_data = list(zip(*params['ND_Cp_data'])) Ts = np.array([T.in_units('K') for T in T_data]) ND_Cps = np.array(ND_Cp_data) else: T_data, Cp_data = list(zip(*params['Cp_data'])) Ts = np.array([T.in_units('K') for T in T_data]) ND_Cps = np.array([Cp for Cp in Cp_data]) / R range = params.get('range') if range is not None: range = range[0].in_units('K'), range[1].in_units('K') else: range = Ts.min(), Ts.max() return cls(ND_H_ref, ND_S_ref, Ts, ND_Cps, T_ref.in_units('K'), range)
def setUp(self): unittest.TestCase.setUp(self) # Testing Ideal Gas Model CO2 = molecule('CO2') CO2_pmutt_parameters = { 'name': 'CO2', 'elements': { 'C': 1, 'O': 2 }, 'trans_model': trans.FreeTrans, 'n_degrees': 3, 'molecular_weight': get_molecular_weight('CO2'), 'rot_model': rot.RigidRotor, 'rot_temperatures': rot.get_rot_temperatures_from_atoms(CO2, geometry='linear'), 'geometry': 'linear', 'symmetrynumber': 2, 'elec_model': elec.GroundStateElec, 'potentialenergy': -22.994202, 'spin': 0., 'vib_model': vib.HarmonicVib, 'vib_wavenumbers': [3360., 954., 954., 1890.], } CO2_ase_parameters = { 'atoms': CO2, 'potentialenergy': -22.994202, 'vib_energies': [ c.wavenumber_to_energy(x) * c.convert_unit(initial='J', final='eV') for x in CO2_pmutt_parameters['vib_wavenumbers'] ], 'geometry': 'linear', 'symmetrynumber': 2, 'spin': 0. } self.CO2_pmutt = StatMech(**CO2_pmutt_parameters) self.CO2_ASE = IdealGasThermo(**CO2_ase_parameters) self.T0 = c.T0('K') # K self.P0 = c.P0('Pa') self.V0 = c.V0('m3') self.mw = get_molecular_weight({'C': 1, 'O': 2})
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_UoRT(self, T=c.T0('K'), **kwargs): """Calculates the dimensionless internal energy using the Extended LSR relationship Parameters ---------- T : float, optional Temperature in K. Default is 298.15 K kwargs : keyword arguments Parameters to calculate reference binding energy, surface energy and gas-phase energy Returns ------- UoRT : float Dimensionless internal energy """ # Check if number of surface species, gas species, and reactions are # consistent n_reactions = len(self.reactions) n_surf = len(self.surf_species) n_gas = len(self.gas_species) n_slopes = len(self.slopes) if n_reactions != n_surf \ or n_reactions != n_gas \ or n_reactions != n_slopes: warn_msg = ( 'Extended LSR can an inconsistent number of slopes {} ' 'reactions ({}), gas species ({}), and surface species ' '({}). Some contributions may be ignored.' ''.format(n_slopes, n_reactions, n_surf, n_gas)) warn(warn_msg) kwargs['T'] = T kwargs['units'] = 'kcal/mol' UoRT = 0. for slope, reaction, surf_species, gas_species in zip( self.slopes, self.reactions, self.surf_species, self.gas_species): try: deltaE_ref = reaction.get_delta_E(**kwargs) except AttributeError: deltaE_ref = reaction.get_delta_H(**kwargs) # Calculate surface energy try: E_surf = surf_species.get_E(**kwargs) except AttributeError: E_surf = surf_species.get_H(**kwargs) # Calculate gas-phase energy try: E_gas = gas_species.get_E(**kwargs) except AttributeError: E_gas = gas_species.get_H(**kwargs) UoRT += (slope * deltaE_ref + E_surf + E_gas) / c.R('kcal/mol/K') / T return UoRT + self.intercept / c.R('kcal/mol/K') / T
def __init__(self, offset=None, references=None, descriptor='elements', T_ref=c.T0('K')): self.offset = offset self.references = references self.descriptor = descriptor self.T_ref = T_ref # If offset not specified but references is specified if self.offset is None and self.references is not None: self.fit_HoRT_offset()
def from_statmech(cls, name, statmech_model, T_low, T_high, references=None, elements=None, **kwargs): """Calculates the Shomate polynomial using statistical mechanic models Parameters ---------- name : str Name of the species statmech_model : `pmutt.statmech.StatMech` object or class Statistical Mechanics model to generate data T_low : float Lower limit temerature in K T_high : float Higher limit temperature in K references : `pmutt.empirical.references.References` object Reference to adjust enthalpy **kwargs : keyword arguments Used to initalize ``statmech_model`` or ``EmpiricalBase`` attributes to be stored. Returns ------- shomate : Shomate object Shomate object with polynomial terms fitted to data. """ # Initialize the StatMech object if inspect.isclass(statmech_model): statmech_model = statmech_model(name=name, references=references, elements=elements, **kwargs) # Generate heat capacity data T = np.linspace(T_low, T_high) CpoR = np.array([statmech_model.get_CpoR(T=T_i) for T_i in T]) T_ref = c.T0('K') # Generate enthalpy and entropy data HoRT_ref = statmech_model.get_HoRT(T=T_ref, use_references=True) SoR_ref = statmech_model.get_SoR(T=T_ref, use_references=True) return cls.from_data(name=name, T=T, CpoR=CpoR, T_ref=T_ref, HoRT_ref=HoRT_ref, SoR_ref=SoR_ref, statmech_model=statmech_model, elements=elements, references=references, **kwargs)
def test_get_EoRT_act(self): exp_sm_EoRT = self.H2O_TS_sm.get_HoRT(T=c.T0('K')) \ - self.H2_sm.get_HoRT(T=c.T0('K')) \ - self.O2_sm.get_HoRT(T=c.T0('K'))*0.5 exp_sm_EoRT_rev = self.H2O_TS_sm.get_HoRT(T=c.T0('K')) \ - self.H2O_sm.get_HoRT(T=c.T0('K')) self.assertAlmostEqual(self.rxn_sm.get_EoRT_act(T=c.T0('K')), exp_sm_EoRT) self.assertAlmostEqual(self.rxn_sm.get_EoRT_act(T=c.T0('K'), rev=True), exp_sm_EoRT_rev)
def get_HoRT(self, T=c.T0('K')): """Calculate the dimensionless enthalpy Parameters ---------- T : float, optional Temperature in K. Default is 298.15 K Returns ------- HoRT : float Dimensionless enthalpy """ return self.H / c.R('eV/K') / T
def get_GoRT(self, T=c.T0('K')): """Calculate the dimensionless Gibbs energy Parameters ---------- T : float, optional Temperature in K. Default is 298.15 K Returns ------- GoRT : float Dimensionless Gibbs energy """ return self.G / c.R('eV/K') / T
def test_get_E_act(self): units = 'J/mol' exp_sm_E = self.H2O_TS_sm.get_H(T=c.T0('K'), units=units) \ - self.H2_sm.get_H(T=c.T0('K'), units=units) \ - self.O2_sm.get_H(T=c.T0('K'), units=units)*0.5 exp_sm_E_rev = self.H2O_TS_sm.get_H(T=c.T0('K'), units=units) \ - self.H2O_sm.get_H(T=c.T0('K'), units=units) self.assertAlmostEqual(self.rxn_sm.get_E_act(T=c.T0('K'), units=units), exp_sm_E) self.assertAlmostEqual( self.rxn_sm.get_E_act(T=c.T0('K'), rev=True, units=units), exp_sm_E_rev)
def get_GoRT(self, x=0., T=c.T0('K')): """Calculates the excess Gibbs energy Parameters ---------- x : float, optional Coverage (in ML) of species j. Default is 0 T : float, optional Temperature in K. Default is 298.15 K Returns ------- GoRT : float Dimensionless excess Gibbs energy """ return self.get_HoRT(x=x, T=T) - self.get_SoR()
def _fit_HoRT(T_ref, HoRT_ref, a): """Fit a[5] coefficient in a_low and a_high attributes given the dimensionless enthalpy Parameters ---------- T_ref : float Reference temperature in K HoRT_ref : float Reference dimensionless enthalpy T_mid : float Temperature to fit the offset Returns ------- a : (8,) `numpy.ndarray`_ Lower coefficients of Shomate polynomial .. _`numpy.ndarray`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html """ a[5] = (HoRT_ref - get_shomate_HoRT(T=np.array([T_ref]), a=a)) \ * c.R('kJ/mol/K')*T_ref a[7] = -get_shomate_HoRT(T=np.array([c.T0('K')]), a=a) \ * c.R('kJ/mol/K')*c.T0('K') return a
def get_H(self, units, T=c.T0('K'), raise_error=True, raise_warning=True, verbose=False, use_references=True, **kwargs): """Calculate the enthalpy Parameters ---------- units : str Units as string. See :func:`~pmutt.constants.R` for accepted units but omit the '/K' (e.g. J/mol). T : float, optional Temperature in K. Default is 298.15 K verbose : bool, optional If False, returns the enthalpy. If True, returns contribution of each mode. raise_error : bool, optional If True, raises an error if any of the modes do not have the quantity of interest. Default is True raise_warning : bool, optional Only relevant if raise_error is False. Raises a warning if any of the modes do not have the quantity of interest. Default is True use_references : bool, optional If True, adds contribution from references. Default is True kwargs : key-word arguments Parameters passed to each mode Returns ------- H : float or (N+5,) `numpy.ndarray`_ Enthalpy. N represents the number of misc models. If verbose is True, contribution to each mode are as follows: [trans, vib, rot, elec, nucl, misc_models (if any)] .. _`numpy.ndarray`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html """ units = '{}/K'.format(units) R_adj = _get_R_adj(units=units, elements=self.elements) return self.get_HoRT(verbose=verbose, raise_error=raise_error, raise_warning=raise_warning, T=T, use_references=use_references, **kwargs) * T * R_adj
def test_get_q_state(self): exp_q_react = self.H2_sm.get_q(T=c.T0('K')) \ * self.O2_sm.get_q(T=c.T0('K'))**0.5 exp_q_prod = self.H2O_sm.get_q(T=c.T0('K')) exp_q_TS = self.H2O_TS_sm.get_q(T=c.T0('K')) self.assertAlmostEqual( self.rxn_sm.get_q_state(state='reactants', T=c.T0('K')), exp_q_react) self.assertAlmostEqual( self.rxn_sm.get_q_state(state='products', T=c.T0('K')), exp_q_prod) self.assertAlmostEqual( self.rxn_sm.get_q_state(state='transition state', T=c.T0('K')), exp_q_TS)
def get_n(self, V=c.V0('m3'), P=c.P0('bar'), T=c.T0('K')): """Calculates the moles of an ideal gas Parameters ---------- V : float, optional Volume in m3. Default is standard volume P : float, optional Pressure in bar. Default is standard pressure T : float, optional Temperature in K. Default is standard temperature Returns ------- n : float Number of moles in mol """ return P * V / c.R('m3 bar/mol/K') / T
def get_P(self, T=c.T0('K'), V=c.V0('m3'), n=1.): """Calculates the pressure of an ideal gas Parameters ---------- T : float, optional Temperature in K. Default is standard temperature V : float, optional Volume in m3. Default is standard volume n : float, optional Number of moles (in mol). Default is 1 mol Returns ------- P : float Pressure in bar """ return n * c.R('m3 bar/mol/K') * T / V
def get_HoRT(self, T=c.T0('K'), **kwargs): """Calculates the dimensionless enthalpy using the LSR relationship Parameters ---------- T : float, optional Temperature in K. Default is 298.15 K kwargs : keyword arguments Parameters to calculate reference binding energy, surface energy and gas-phase energy Returns ------- HoRT : float Dimensionless enthalpy """ return self.get_UoRT(T=T, **kwargs)
def get_V(self, T=c.T0('K'), P=c.P0('bar'), n=1.): """Calculates the volume of an ideal gas Parameters ---------- T : float, optional Temperature in K. Default is standard temperature P : float, optional Pressure in bar. Default is standard pressure n : float, optional Number of moles (in mol). Default is 1 mol Returns ------- V : float Volume in m3 """ return n * c.R('m3 bar/mol/K') * T / P
def get_EoRT(self, T=c.T0('K'), include_ZPE=False, raise_error=True, raise_warning=True, **kwargs): """Dimensionless electronic energy Parameters ---------- T : float, optional Temperature in K. If the electronic mode is :class:`~pmutt.statmech.elec.GroundStateElec`, then the output is insensitive to this input. Default is 298.15 K include_ZPE : bool, optional If True, includes the zero point energy. Default is False raise_error : bool, optional If True, raises an error if any of the modes do not have the quantity of interest. Default is True raise_warning : bool, optional Only relevant if raise_error is False. Raises a warning if any of the modes do not have the quantity of interest. Default is True kwargs : key-word arguments Parameters passed to electronic mode Returns ------- EoRT : float Dimensionless electronic energy """ kwargs['T'] = T EoRT = _get_mode_quantity(mode=self.elec_model, method_name='get_UoRT', raise_error=raise_error, raise_warning=raise_warning, default_value=0., **kwargs) if include_ZPE: EoRT += _get_mode_quantity(mode=self.vib_model, method_name='get_ZPE', raise_error=raise_error, raise_warning=raise_warning, default_value=0., **kwargs) / c.R('eV/K') / T return EoRT
def get_UoRT(self, x=0., T=c.T0('K')): """Calculates the excess internal energy Parameters ---------- x : float, optional Coverage (in ML) of species j. Default is 0 T : float, optional Temperature in K. Default is 298.15 K Returns ------- UoRT : float Dimensionless internal energy """ i = np.argmax(x < np.array(self.intervals)) - 1 UoRT = (self.slopes[i] * x + self._intercepts[i]) / (c.R('kcal/mol/K') * T) return UoRT
def get_HoRT(self, T=c.T0('K'), **kwargs): """Calculates the dimensionless enthalpy using BEP relationship and reactants or products enthalpy Parameters ---------- T : float, optional Temperature in K. Default is 298.15 kwargs : keyword arguments Parameters required to calculate the descriptor Returns ------- HoRT : float Dimensionless enthalpy """ HoRT_reactants = self.reaction.get_HoRT_state(state='reactants', T=T, **kwargs) return self.get_EoRT_act(rev=False, T=T, **kwargs) + HoRT_reactants