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_rot_temperatures_from_atoms(atoms, geometry=None, degree_tol=5.): """Calculate the rotational temperatures from ase.Atoms object Parameters ---------- atoms : `ase.Atoms`_ object Atoms object geometry : str, optional Geometry of molecule. If not specified, it will be guessed from Atoms object. degree_tol : float, optional Degree tolerance in degrees. Default is 5 degrees Returns ------- rot_temperatures : list of float Rotational temperatures .. _`ase.Atoms`: https://wiki.fysik.dtu.dk/ase/ase/atoms.html#ase.Atoms """ if geometry is None: geometry = get_geometry_from_atoms(atoms=atoms, degree_tol=degree_tol) rot_temperatures = [] for moment in atoms.get_moments_of_inertia(): if np.isclose(0., moment): continue moment_SI = moment*c.convert_unit(initial='amu', final='kg') \ * c.convert_unit(initial='A2', final='m2') rot_temperatures.append(c.inertia_to_temp(moment_SI)) if geometry == 'monatomic': # Expecting all modes to be 0 if not np.isclose(np.sum(rot_temperatures), 0.): err_msg = ('Geometry expected to be monatomic but contains ' 'non-zero rotational temperatures.') raise ValueError(err_msg) return [0.] elif geometry == 'linear': # Expecting one mode to be 0 and the other modes to be identical if not np.isclose(rot_temperatures[0], rot_temperatures[1]): warn_msg = ('Expected rot_temperatures for linear specie, {}, to ' 'be similar. Values found were: {}' ''.format(atoms, rot_temperatures)) warn(warn_msg) return [min(rot_temperatures)] elif geometry == 'nonlinear': # Expecting 3 modes. May or may not be equal return rot_temperatures else: err_msg = 'Geometry, {}, not supported.'.format(geometry) raise ValueError(err_msg)
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 setUp(self): slope = 0.5 intercept = 10. # kcal/mol del_E = -1. # kcal/mol del_E_eV = del_E * c.convert_unit(initial='kcal/mol', final='eV/molecule') E_surf = -2. # kcal/mol E_surf_eV = E_surf * c.convert_unit(initial='kcal/mol', final='eV/molecule') E_gas = -3. # kcal/mol E_gas_eV = E_gas * c.convert_unit(initial='kcal/mol', final='eV/molecule') species = { 'A(g)_shomate': Shomate(name='A(g)_shomate', T_low=100., T_high=500., a=np.zeros(8)), 'A(g)_statmech': StatMech(), '*': StatMech(), 'A*': StatMech(U=del_E_eV, H=del_E_eV, **presets['constant']), 'surf': StatMech(U=E_surf_eV, H=E_surf_eV, **presets['constant']), 'gas': StatMech(U=E_gas_eV, H=E_gas_eV, **presets['constant']) } reaction_shomate = Reaction.from_string('A(g)_shomate + * = A*', species) reaction_statmech = Reaction.from_string('A(g)_statmech + * = A*', species) self.lsr_const = LSR(slope=slope, intercept=intercept, reaction=del_E, surf_species=E_surf, gas_species=E_gas) self.lsr_shomate = LSR(slope=slope, intercept=intercept, reaction=reaction_shomate, surf_species=species['surf'], gas_species=species['gas']) self.lsr_statmech = LSR(slope=slope, intercept=intercept, reaction=reaction_statmech, surf_species=species['surf'], gas_species=species['gas'])
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 to_CTI(self, act_energy_unit=None, units=None, delimiter='_'): """Writes the object in Cantera's CTI format. Parameters ---------- act_energy_unit : str, optional Unit to use for energy. Default is 'cal/mol' units : :class:`~pmutt.omkm.units.Units` object If specified, `energy_unit` is overwritten. Default is None. Returns ------- cti_str : str Surface reaction string in CTI format """ if units is not None: act_energy_unit = units.act_energy # synthesis_reactions = self._get_reactions_CTI(direction='synthesis') # cleavage_reactions = self._get_reactions_CTI(direction='cleavage') synthesis_reactions = _get_range_CTI(objs=self.synthesis_reactions, parent_obj=self, delimiter=delimiter) cleavage_reactions = _get_range_CTI(objs=self.cleavage_reactions, parent_obj=self, delimiter=delimiter) intercept = c.convert_unit(self.intercept, 'kcal/mol', act_energy_unit) cti_str = ('bep(id="{}",\n' ' slope={},\n' ' intercept={},\n' ' direction="{}",\n' ' cleavage_reactions={},\n' ' synthesis_reactions={})\n' ''.format(self.name, self.slope, intercept, self.direction, cleavage_reactions, synthesis_reactions)) return cti_str
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_E_act(self, units, rev=False, **kwargs): """Calculate Arrhenius activation energy using BEP relationship Parameters ---------- units : str Units as string. See :func:`~pmutt.constants.R` for accepted units but omit the '/K' (e.g. J/mol). rev : bool, optional Reverse direction. If True, uses products as initial state instead of reactants. Default is False kwargs : keyword arguments Parameters required to calculate the descriptor Returns ------- E_act : float Dimensionless activation energy """ if 'rev_delta' in self.descriptor: # If the descriptor is for the reverse reaction, the slope has to # be modified if rev: E_act = self.slope * self._descriptor_fn( **kwargs) + self.intercept else: E_act = (self.slope-1.)*self._descriptor_fn(**kwargs) \ + self.intercept else: if rev: E_act = (self.slope-1.)*self._descriptor_fn(**kwargs) \ + self.intercept else: E_act = self.slope * self._descriptor_fn( **kwargs) + self.intercept return E_act * c.convert_unit(initial='kcal/mol', final=units)
def to_cti(self, energy_unit='kcal', quantity_unit='mol', units=None): """Writes the lateral interaction in CTI format Parameters ---------- energy_unit : str, optional Energy unit for slopes. Default is 'kcal' quantity_unit : str, optional Quantity unit for slopes. Default is 'mol' units : :class:`~pmutt.cantera.units.Units` object If specified, ``energy_unit`` and ``quantity_unit`` are overwritten. Default is None. Returns ------- lat_inter_str : str Lateral interaction in CTI format """ if units is not None: energy_unit = units.energy quantity_unit = units.quantity final = '{}/{}'.format(energy_unit, quantity_unit) slopes = [c.convert_unit(slope, initial='kcal/mol', final=final) \ for slope in self.slopes] lat_inter_str = ('lateral_interaction("{} {}",\n' ' coverage_thresholds={},\n' ' strengths={},\n' ' id="{}")' ''.format(self.name_i, self.name_j, self.intervals, slopes, self.name)) return lat_inter_str
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_Pc(self): """Calculates the critical pressure Returns ------- Pc : float Critical pressure in bar """ return self.a / 27. / self.b**2 * c.convert_unit(initial='Pa', final='bar')
def read_molecular_mass(filename, units='g/mol'): """Reads the molecular mass from the Gaussian log file. Parameters ---------- filename : str Log file units : str, optional Units for molecular mass. Default is 'g/mol' Returns ------- molecular_mass : float Molecular mass in ``units``. Default is 'g/mol' """ if units == 'amu': units = 'amu/molecule' mass_unit, amount_unit = units.split('/') molecular_mass = float(read_pattern(filename=filename, pattern='Molecular mass:(.*)', group=0, return_immediately=True).split()[0]) \ *c.convert_unit(initial='amu', final=mass_unit) \ /c.convert_unit(initial='molecule', final=amount_unit) return molecular_mass
def _float_to_specie(self, val): """Converts a float to a :class:`~pmutt.statmech.StatMech` object Parameters ---------- val : float Value (in kcal/mol) Returns ------- obj : :class:`~pmutt.statmech.StatMech` object :class:`~pmutt.statmech.StatMech` object that gives the val when `get_E` is called """ val = val*c.convert_unit(initial='kcal/mol', final='eV/molecule') return StatMech(U=val, H=val, F=val, G=val, **presets['constant'])
def get_V(self, T, P): """Calculates the molar volume of an ideal gas at T and P :math:`V_m=\\frac{RT}{P}` Parameters ---------- T : float Temperature in K P : float Pressure in bar Returns ------- V : float Molar volume in m3 """ return T*c.R('J/mol/K')/(P*c.convert_unit(initial='bar', final='Pa'))
def from_critical(cls, Tc, Pc): """Creates the van der Waals object from critical temperature and pressure Parameters ---------- Tc : float Critical temperature in K Pc : float Critical pressure in bar Returns ------- vanDerWaalsEOS : vanDerWaalsEOS object """ Pc_SI = Pc * c.convert_unit(initial='bar', final='Pa') a = 27. / 64. * (c.R('J/mol/K') * Tc)**2 / Pc_SI b = c.R('J/mol/K') * Tc / 8. / Pc_SI return cls(a=a, b=b)
def read_zpe(filename, units='eV/molecule'): """Reads the zero-point energy from the Gaussian log file. Parameters ---------- filename : str Log file units : str, optional Units to return energy. Default is 'eV/molecule' Returns ------- zero_point_energy : float Zero point energy in ``units``. Default units are 'eV/molecule' """ return float(read_pattern(filename=filename, pattern='Zero-point correction=(.*?)\(', group=0, return_immediately=True)) \ *c.convert_unit(initial='Ha/molecule', final=units)
def get_P(self, T=c.T0('K'), V=c.V0('m3'), n=1.): """Calculates the pressure of a van der Waals 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 """ Vm = V / n return (c.R('J/mol/K')*T/(Vm - self.b) - self.a*(1./Vm)**2) \ * c.convert_unit(initial='Pa', final='bar')
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 to_omkm_yaml(self, act_energy_unit=None, units=None): """Writes the object in Cantera's YAML format. Parameters ---------- act_energy_unit : str, optional Unit to use for activation energy. Default is 'cal/mol' units : :class:`~pmutt.omkm.units.Units` object If specified, `act_energy_unit` is overwritten. Default is None. Returns ------- yaml_dict : dict Dictionary compatible with Cantera's YAML format """ if units is not None: act_energy_unit = units.act_energy yaml_dict = {} yaml_dict['id'] = self.name yaml_dict['slope'] = self.slope # Assign intercept intercept = c.convert_unit(self.intercept, 'kcal/mol', act_energy_unit) intercept_param = _Param('intercept', intercept, '_act_energy') _assign_yaml_val(intercept_param, yaml_dict, units) yaml_dict['direction'] = self.direction if self.synthesis_reactions is not None \ and len(self.synthesis_reactions) > 0: synthesis_reactions = _get_omkm_range(objs=self.synthesis_reactions, parent_obj=self, format='list') yaml_dict['synthesis-reactions'] = synthesis_reactions if self.cleavage_reactions is not None \ and len(self.cleavage_reactions) > 0: cleavage_reactions = _get_omkm_range(objs=self.cleavage_reactions, parent_obj=self, format='list') yaml_dict['cleavage-reactions'] = cleavage_reactions return yaml_dict
def read_frequencies(filename, units='1/cm'): """Reads the frequencies from the Gaussian log file. Parameters ---------- filename : str Log file units : str, optional Units to return frequencies. Default is '1/cm' Returns ------- frequencies : list of float Frequencies in ``units``. Default is '1/cm' """ final = units.split('/')[-1] freq_patterns = read_pattern(filename=filename, pattern='Frequencies -- (.*)', group=0, return_immediately=False) return [float(freq)/c.convert_unit(initial='cm', final=final) \ for freq in freq_patterns]
def to_CTI(self, energy_unit='kcal/mol', units=None): """Writes the lateral interaction in CTI format Parameters ---------- energy_unit : str, optional Energy unit for slopes. Default is 'kcal/mol' units : :class:`~pmutt.cantera.units.Units` object If specified, energy_unit` are overwritten. Default is None. Returns ------- lat_inter_str : str Lateral interaction in CTI format """ if units is not None: energy_unit = units.energy lat_inter_str = 'lateral_interaction("{} {}", {}, {}, id="{}")'.format( self.name_i, self.name_j, c.convert_unit(num=np.array(self.slopes), initial='kcal/mol', final=energy_unit), self.intervals, self.name) return lat_inter_str
def read_electronic_and_zpe(filename, units='eV/molecule'): """Reads the electronic energy and zero-point energy from the Gaussian log file. Parameters ---------- filename : str Log file units : str, optional Units to return energy. Default is 'eV/molecule' Returns ------- electronic_and_zero_point_energy : float Electronic and zero point energy in ``units``. Default is 'eV/molecule' """ return float(read_pattern(filename=filename, pattern='Sum of electronic and zero-point ' 'Energies=(.*)', group=0, return_immediately=True)) \ *c.convert_unit(initial='Ha/molecule', final=units)
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 _get_R_adj(units, elements=None): """Get adjustment to mass when converting from mol to g Parameters ---------- units : str Units as string. Units are delimited by '/' elements : dict, optional Composition of the species. Default is None. Keys of dictionary are elements, values are stoichiometric values in a formula unit. e.g. CH3OH can be represented as: {'C': 1, 'H': 4, 'O': 1,}. Returns ------- R_adj : float Adjustment to the mass. If no mass units are found, returns R in appropriate units. """ mass_unit = _get_mass_unit(units) # If no mass unit is found, return R in appropriate units if mass_unit is None: return c.R(units) # If elements were not provided, throw error if elements is None: err_msg = ('To calculate thermodynamic quantities on per mass basis, ' 'the species object must have a dictionary assigned to ' 'elements.') raise AttributeError(err_msg) mol_weight = get_molecular_weight(elements) # g/mol mol_units = units.replace('/{}'.format(mass_unit), '/mol') R_adj = c.R(mol_units) / c.convert_unit( num=mol_weight, initial='g', final=mass_unit) return R_adj
def get_E_act(self, units, reaction, rev=False, **kwargs): """Calculate Arrhenius activation energy using BEP relationship Parameters ---------- units : str Units as string. See :func:`~pmutt.constants.R` for accepted units but omit the '/K' (e.g. J/mol). reaction : :class:`~pmutt.reaction.Reaction` object Reaction related to BEP. rev : bool, optional Reverse direction. If True, uses products as initial state instead of reactants. Default is False kwargs : keyword arguments Parameters required to calculate the descriptor Returns ------- E_act : float Dimensionless activation energy """ adj_slope = self._get_adjusted_slope(rev=rev) descriptor_val = self._get_descriptor_val(reaction=reaction, **kwargs) E_act = adj_slope * descriptor_val + self.intercept return E_act * c.convert_unit(initial='kcal/mol', final=units)
def test_energy_to_wavenumber(self): E_J = c.convert_unit(0.1, initial='eV', final='J') self.assertAlmostEqual(c.energy_to_wavenumber(E_J), self.ans.at['test_energy_to_wavenumber', 0])
def test_convert_unit(self): # Test all combinations for temperature conversion self.assertAlmostEqual( c.convert_unit(c.T0('K'), initial='K', final='C'), self.ans.at['test_convert_unit', 1]) self.assertAlmostEqual( c.convert_unit(c.T0('K'), initial='K', final='F'), self.ans.at['test_convert_unit', 2]) self.assertAlmostEqual( c.convert_unit(c.T0('K'), initial='K', final='R'), self.ans.at['test_convert_unit', 3]) self.assertAlmostEqual( c.convert_unit(c.T0('C'), initial='C', final='K'), self.ans.at['test_convert_unit', 0]) self.assertAlmostEqual( c.convert_unit(c.T0('C'), initial='C', final='F'), self.ans.at['test_convert_unit', 2]) self.assertAlmostEqual( c.convert_unit(c.T0('C'), initial='C', final='R'), self.ans.at['test_convert_unit', 3]) self.assertAlmostEqual( c.convert_unit(c.T0('F'), initial='F', final='K'), self.ans.at['test_convert_unit', 0]) self.assertAlmostEqual( c.convert_unit(c.T0('F'), initial='F', final='C'), self.ans.at['test_convert_unit', 1]) self.assertAlmostEqual( c.convert_unit(c.T0('F'), initial='F', final='R'), self.ans.at['test_convert_unit', 3]) self.assertAlmostEqual( c.convert_unit(c.T0('R'), initial='R', final='K'), self.ans.at['test_convert_unit', 0]) self.assertAlmostEqual( c.convert_unit(c.T0('R'), initial='R', final='C'), self.ans.at['test_convert_unit', 1]) self.assertAlmostEqual( c.convert_unit(c.T0('R'), initial='R', final='F'), self.ans.at['test_convert_unit', 2]) # Test a unit conversion with multiple-based units self.assertAlmostEqual(c.convert_unit(initial='m', final='cm'), self.ans.at['test_convert_unit', 4]) # Test if error raised when units in different set with self.assertRaises(ValueError): c.convert_unit(initial='cm', final='J') # Test if error raised when unaccepted unit inputted with self.assertRaises(ValueError): c.convert_unit(initial='arbitrary unit', final='J') with self.assertRaises(ValueError): c.convert_unit(initial='cm', final='arbitrary unit')
# ## Constants # pmutt has a wide variety of constants to increase readability of the code. See [Constants page][0] in the documentation for supported units. # # [0]: https://vlachosgroup.github.io/pmutt/constants.html#constants # In[1]: from pmutt import constants as c print('Some constants') print('R (J/mol/K) = {}'.format(c.R('J/mol/K'))) print("Avogadro's number = {}\n".format(c.Na)) print('Unit conversions') print('5 kJ/mol --> {} eV/molecule'.format(c.convert_unit(num=5., initial='kJ/mol', final='eV/molecule'))) print('Frequency of 1000 Hz --> Wavenumber of {} 1/cm\n'.format(c.freq_to_wavenumber(1000.))) print('See expected inputs, supported units of different constants') help(c.R) help(c.convert_unit) # ## StatMech Objects # Molecules show translational, vibrational, rotational, electronic, and nuclear modes. # # <img src="images/statmech_modes.jpg" width=800> # # The [``StatMech``][0] object allows us to specify translational, vibrational, rotational, electronic and nuclear modes independently, which gives flexibility in what behavior you would like. # # [0]: https://vlachosgroup.github.io/pmutt/statmech.html#pmutt.statmech.StatMech
from pmutt import constants as c h1 = c.h('eV s', bar=True) print('h = {} eV s'.format(h1)) # <a id='section_3_2'></a> # ## 3.2. Convert between units # Below, we convert 12 atm of pressure to psi. # In[2]: from pmutt import constants as c P_atm = 12. # atm P_psi = c.convert_unit(num=P_atm, initial='atm', final='psi') print('{} atm = {} psi'.format(P_atm, P_psi)) # <a id='section_3_3'></a> # ## 3.3. Convert between equivalent quantities # Below, we convert 1000 wavenumbers (cm-1) to frequency. # In[3]: from pmutt import constants as c wave_num = 1000. # cm-1 freq = c.wavenumber_to_freq(wave_num) # Hz
def to_CTI(self, max_line_len=80, mass_unit='g', length_unit='cm', units=None): """Writes the object in Cantera's CTI format. Parameters ---------- max_line_len : int, optional Maximum number of characters in the line. Default is 80. mass_unit : str, optional Mass unit for `density`. Default is 'g' length_unit : str, optional Length unit for `density`. Default is 'cm' units : :class:`~pmutt.cantera.units.Units` object, optional If specified, `mass_unit` and `length_unit` are overwritten. Default is None. Returns ------- CTI_str : str Object represented as a CTI string. """ if units is not None: length_unit = units.length mass_unit = units.mass species_names = [species.name for species in self.species] volume_unit = '{}3'.format(length_unit) density = self.density*c.convert_unit(initial='g', final=mass_unit)\ /c.convert_unit(initial='cm3', final=volume_unit) # Add required fields cti_str = ('stoichiometric_solid(name={},\n' ' elements={},\n' ' species={},\n' ' density={},\n'.format( obj_to_CTI(self.name, line_len=max_line_len - 26, max_line_len=max_line_len - 27), obj_to_CTI(self.elements, line_len=max_line_len - 30, max_line_len=max_line_len), obj_to_CTI(species_names, line_len=max_line_len - 29, max_line_len=max_line_len), density)) # Add optional fields optional_fields = ('transport', 'options', 'note', 'initial_state') for field in optional_fields: val = getattr(self, field) # Skip empty fields if val is None: continue cti_str += ' {}={},\n'.format( field, obj_to_CTI(val, line_len=max_line_len - len(field) - 22, max_line_len=max_line_len)) # Terminate the string cti_str = '{})\n'.format(cti_str[:-2]) return cti_str