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(from_='eV', to='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(from_='A2', to='m2') *\ c.convert_unit(from_='amu', to='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(from_='g', to='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_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(from_='g', to='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(from_='bar', to='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_convert_unit(self): self.assertEqual(c.convert_unit(num=0., from_='C', to='K'), 273.15) self.assertEqual(c.convert_unit(from_='m', to='cm'), 100.) with self.assertRaises(ValueError): c.convert_unit(from_='cm', to='J') with self.assertRaises(ValueError): c.convert_unit(from_='arbitrary unit', to='J') with self.assertRaises(ValueError): c.convert_unit(from_='cm', to='arbitrary unit')
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(from_='Pa', to='bar')
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 """ 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(from_='amu', to='kg') \ * c.convert_unit(from_='A2', to='m2') rot_temperatures.append(c.inertia_to_temp(moment_SI)) if geometry == 'monatomic': # Expecting all modes to be 0 assert np.isclose(np.sum(rot_temperatures), 0.) 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('Expected rot_temperatures for linear specie, {}, to be ' 'similar. Values found were:{}'.format( atoms, rot_temperatures)) return [max(rot_temperatures)] elif geometry == 'nonlinear': # Expecting 3 modes. May or may not be equal return rot_temperatures else: raise ValueError('Geometry, {}, not supported.'.format(geometry))
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(from_='bar', to='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(from_='bar', to='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 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(from_='bar', to='Pa') + self.a/Vm**2) \ * (Vm - self.b)/c.R('J/mol/K')
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(from_='Pa', to='bar')
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(from_='g', to='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.)
# ## 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., from_='kJ/mol', to='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
CpoR = Cp/c.R('J/mol/K') # Enthalpy of Formation for CH3OH (in kJ/mol) from NIST T_ref = c.T0('K') H_ref = -205. HoRT_ref = H_ref/c.R('kJ/mol/K')/T_ref # Standard molar entropy (in J/mol/K) from Wikipedia, # https://en.wikipedia.org/wiki/Methanol_(data_page) S_ref = 239.9 SoR_ref = S_ref/c.R('J/mol/K') # Units to plot the figure Cp_units = 'J/mol/K' H_units = 'kJ/mol' S_units = 'J/mol/K' G_units = 'kJ/mol' # Input the experimental data and fitting to a NASA polynomial CH3OH_nasa = Nasa.from_data(name='CH3OH', T=T, CpoR=CpoR, T_ref=T_ref, HoRT_ref=HoRT_ref, SoR_ref=SoR_ref) # Compare the Nasa polynomial to the input data fig, axes = CH3OH_nasa.plot_empirical(Cp_units=Cp_units, H_units=H_units, S_units=S_units, G_units=G_units) axes[0].plot(T, Cp, 'ko') axes[1].plot(T_ref, H_ref, 'ko') axes[2].plot(T_ref, S_ref, 'ko') axes[3].plot(T_ref, H_ref - T_ref * S_ref * c.convert_unit(from_='J', to='kJ'), 'ko') plt.show()
print('S expt: {} J/mol/K'.format(S_ref)) print('S shomate: {} J/mol/K'.format(S_shomate)) # ### Compare using a plot # In[5]: fig, axes = CH3OH_shomate.plot_empirical(Cp_units='J/mol/K', H_units='kJ/mol', S_units='J/mol/K', G_units='kJ/mol') # Add experimental data to plot axes[0].plot(T, Cp, 'ko') axes[1].plot(T_ref, H_ref, 'ko') axes[2].plot(T_ref, S_ref, 'ko') axes[3].plot(T_ref, H_ref - T_ref * S_ref * c.convert_unit(from_='J', to='kJ'), 'ko') # Add legend to plot axes[0].legend(['Shomate', 'Expt']) axes[1].legend(['Shomate', 'Expt']) axes[2].legend(['Shomate', 'Expt']) axes[3].legend(['Shomate', 'Expt']) # Adjust figure size so it's viewable fig.set_size_inches((10, 8)) # Note that in this code's section, we showcase the ``pMuTT.constants.convert_unit`` function. Similarly to the ``c.R`` and ``c.T0`` functions shown above, this helps to increase code readability. # # ## Save the Shomate polynomial as a JSON file # If we would like to save our Shomate object, we can save it in JSON format with the help of the ``pMuTT.io_.json.pMuTTEncoder``.