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_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 test_P0(self): # Test P0 for all units self.assertAlmostEqual(c.P0('bar'), self.ans.at['test_P0', 0]) self.assertAlmostEqual(c.P0('atm'), self.ans.at['test_P0', 1]) self.assertAlmostEqual(c.P0('Pa'), self.ans.at['test_P0', 2]) self.assertAlmostEqual(c.P0('kPa'), self.ans.at['test_P0', 3]) self.assertAlmostEqual(c.P0('MPa'), self.ans.at['test_P0', 4]) self.assertAlmostEqual(c.P0('psi'), self.ans.at['test_P0', 5]) self.assertAlmostEqual(c.P0('mmHg'), self.ans.at['test_P0', 6]) self.assertAlmostEqual(c.P0('torr'), self.ans.at['test_P0', 7]) # Test P0 raises an error when an unsupported unit is passed with self.assertRaises(ValueError): c.P0('arbitrary unit')
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_SoR(self, P=c.P0('bar')): """Calculates dimesionless entropy :math:`\\frac{S}{R} = -\\ln\\bigg(\\frac{P}{P_0}\\bigg)` Parameters ---------- P : float or `numpy.ndarray`_, optional Pressure in bar. Default is P0 (1 bar) Returns ------- SoR : float or `numpy.ndarray`_ Dimensionless adjustment to entropy .. _`numpy.ndarray`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html """ return -np.log(P)
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_T(self, V=c.V0('m3'), P=c.P0('bar'), n=1.): """Calculates the temperature 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 n : float, optional Number of moles (in mol). Default is 1 mol Returns ------- T : float Temperature in K """ return P * V / c.R('m3 bar/mol/K') / n
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_GoRT(self, T, P=c.P0('bar')): """Calculates the dimensionless Gibbs energy :math:`\\frac{G^{trans}}{RT}=\\frac{H^{trans}}{RT}-\\frac{S^{trans}}{R}` Parameters ---------- T : float Temperature in K P : float, optional Pressure (bar) or pressure-like quantity. Default is atmospheric pressure Returns ------- GoR_trans : float Translational dimensionless Gibbs energy """ return self.get_HoRT() - self.get_SoR(T=T, P=P)
def get_T(self, V=c.V0('m3'), P=c.P0('bar'), n=1.): """Calculates the temperature of a van der Waals gas Parameters ---------- V : float, optional Volume in m3. Default is standard volume P : float, optional Pressure in bar. Default is standard pressure n : float, optional Number of moles (in mol). Default is 1 mol Returns ------- T : float Temperature in K """ Vm = V / n return (P*c.convert_unit(initial='bar', final='Pa') + self.a/Vm**2) \ * (Vm - self.b)/c.R('J/mol/K')
def get_n(self, V=c.V0('m3'), P=c.P0('bar'), T=c.T0('K'), gas_phase=True): """Calculates the moles of a van der Waals 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 gas_phase : bool, optional Relevant if system is in vapor-liquid equilibrium. If True, return the smaller moles (gas phase). If False, returns the larger moles (liquid phase). Returns ------- n : float Number of moles in mol """ return V / self.get_Vm(T=T, P=P, gas_phase=gas_phase)
def get_V(self, T=c.T0('K'), P=c.P0('bar'), n=1., gas_phase=True): """Calculates the 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 n : float, optional Number of moles (in mol). Default is 1 mol 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 ------- V : float Volume in m3 """ return self.get_Vm(T=T, P=P, gas_phase=gas_phase) * n
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 to_CTI(self, T=c.T0('K'), P=c.P0('bar'), quantity_unit='molec', length_unit='cm', act_energy_unit='cal/mol', units=None): """Writes the object in Cantera's CTI format. Parameters ---------- T : float, optional Temperature in K. Default is 298.15 K P : float, optional Pressure in bar. Default is 1 bar quantity_unit : str, optional Quantity unit to calculate A. Default is 'molec' length_unit : str, optional Length unit to calculate A. Default is 'cm' act_energy_unit : str, optional Unit to use for activation energy. Default is 'cal/mol' units : :class:`~pmutt.omkm.units.Units` object If specified, `quantity_unit`, `length_unit`, and `act_energy_unit` are overwritten. Default is None. Returns ------- cti_str : str Surface reaction string in CTI format """ if units is not None: quantity_unit = units.quantity length_unit = units.length act_energy_unit = units.act_energy reaction_str = self.to_string(stoich_space=True, species_delimiter=' + ', reaction_delimiter=' <=> ',\ include_TS=False) # Determine the reaction IDs try: id = self.id except AttributeError: id_str = '' else: if id is None: id_str = '' else: id_str = ',\n id="{}"'.format(self.id) if self.is_adsorption: G_act = self.get_G_act(units=act_energy_unit, T=T, P=P) cti_str = ('surface_reaction("{}",\n' ' stick({: .5e}, {}, {: .5e}){})' ''.format(reaction_str, self.sticking_coeff, self.beta, G_act, id_str)) else: A_units = '{}/{}2'.format(quantity_unit, length_unit) cti_str = ('surface_reaction("{}",\n' ' [{: .5e}, {}, {: .5e}]{})'.format( reaction_str, self.get_A(T=T, P=P, include_entropy=False, units=A_units), self.beta, self.get_G_act(units=act_energy_unit, T=T, P=P), id_str)) return cti_str
def test_P0(self): self.assertEqual(c.P0('bar'), 1.) with self.assertRaises(ValueError): c.P0('arbitrary unit')
def to_omkm_yaml(self, T=c.T0('K'), P=c.P0('bar'), quantity_unit='molec', length_unit='cm', act_energy_unit='cal/mol', ads_act_method='get_H_act', units=None): """Writes the object in Cantera's YAML format. Parameters ---------- T : float, optional Temperature in K. Default is 298.15 K P : float, optional Pressure in bar. Default is 1 bar quantity_unit : str, optional Quantity unit to calculate A. Default is 'molec' length_unit : str, optional Length unit to calculate A. Default is 'cm' act_energy_unit : str, optional Unit to use for activation energy. Default is 'cal/mol' ads_act_method : str, optional Activation method to use for adsorption reactions. Accepted options include 'get_H_act' and 'get_G_act'. Default is 'get_H_act'. units : :class:`~pmutt.omkm.units.Units` object If specified, `quantity_unit`, `length_unit`, and `act_energy_unit` are overwritten. Default is None. Returns ------- yaml_dict : dict Dictionary compatible with Cantera's YAML format """ if units is not None: quantity_unit = units.quantity length_unit = units.length act_energy_unit = units.act_energy yaml_dict = {} # Assign reaction name yaml_dict['equation'] = self.to_string(stoich_space=True, species_delimiter=' + ', reaction_delimiter=' <=> ',\ include_TS=False) if self.is_adsorption: rate_constant_name = 'sticking-coefficient' # Pre-exponential A_param = _Param('A', self.sticking_coeff, None) # Activation energy act_method = getattr(self, ads_act_method) act_val = act_method(units=act_energy_unit, T=T, P=P) # Sticking-species for species in self.reactants: # Look for gas phase species if isinstance(species.phase, IdealGas) \ or species.phase.lower() == 'g' \ or species.phase.lower() == 'gas': yaml_dict['sticking-species'] = species.name break else: err_msg = ('Could not find gas reactant in reaction, {}. ' 'One species must have its phase attribute set to ' '"g" or "gas".' ''.format(str(self))) raise ValueError(err_msg) # Motz-Wise correction yaml_dict['Motz-Wise'] = self.use_motz_wise else: rate_constant_name = 'rate-constant' # Pre-exponential A_units = '{}/{}2'.format(quantity_unit, length_unit) A = float(self.get_A(T=T, P=P, include_entropy=False, units=A_units)) # TODO Check units for A. Should have time dependence. A_param = _Param('A', A, None) # Activation energy act_val = self.get_G_act(units=act_energy_unit, T=T, P=P) # Assign activation energy, beta and pre-exponential factor rate_constant_dict = {} _assign_yaml_val(A_param, rate_constant_dict, units) rate_constant_dict['b'] = self.beta act_param = _Param('Ea', act_val, '_act_energy') _assign_yaml_val(act_param, rate_constant_dict, units) # Add kinetic parameters to overall dictionary yaml_dict[rate_constant_name] = rate_constant_dict # Assign reaction ID try: yaml_dict['id'] = self.id except AttributeError: pass return yaml_dict