mlmm-toolkit 0.2.2.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. hessian_ff/__init__.py +50 -0
  2. hessian_ff/analytical_hessian.py +609 -0
  3. hessian_ff/constants.py +46 -0
  4. hessian_ff/forcefield.py +339 -0
  5. hessian_ff/loaders.py +608 -0
  6. hessian_ff/native/Makefile +8 -0
  7. hessian_ff/native/__init__.py +28 -0
  8. hessian_ff/native/analytical_hessian.py +88 -0
  9. hessian_ff/native/analytical_hessian_ext.cpp +258 -0
  10. hessian_ff/native/bonded.py +82 -0
  11. hessian_ff/native/bonded_ext.cpp +640 -0
  12. hessian_ff/native/loader.py +349 -0
  13. hessian_ff/native/nonbonded.py +118 -0
  14. hessian_ff/native/nonbonded_ext.cpp +1150 -0
  15. hessian_ff/prmtop_parmed.py +23 -0
  16. hessian_ff/system.py +107 -0
  17. hessian_ff/terms/__init__.py +14 -0
  18. hessian_ff/terms/angle.py +73 -0
  19. hessian_ff/terms/bond.py +44 -0
  20. hessian_ff/terms/cmap.py +406 -0
  21. hessian_ff/terms/dihedral.py +141 -0
  22. hessian_ff/terms/nonbonded.py +209 -0
  23. hessian_ff/tests/__init__.py +0 -0
  24. hessian_ff/tests/conftest.py +75 -0
  25. hessian_ff/tests/data/small/complex.parm7 +1346 -0
  26. hessian_ff/tests/data/small/complex.pdb +125 -0
  27. hessian_ff/tests/data/small/complex.rst7 +63 -0
  28. hessian_ff/tests/test_coords_input.py +44 -0
  29. hessian_ff/tests/test_energy_force.py +49 -0
  30. hessian_ff/tests/test_hessian.py +137 -0
  31. hessian_ff/tests/test_smoke.py +18 -0
  32. hessian_ff/tests/test_validation.py +40 -0
  33. hessian_ff/workflows.py +889 -0
  34. mlmm/__init__.py +36 -0
  35. mlmm/__main__.py +7 -0
  36. mlmm/_version.py +34 -0
  37. mlmm/add_elem_info.py +374 -0
  38. mlmm/advanced_help.py +91 -0
  39. mlmm/align_freeze_atoms.py +601 -0
  40. mlmm/all.py +3535 -0
  41. mlmm/bond_changes.py +231 -0
  42. mlmm/bool_compat.py +223 -0
  43. mlmm/cli.py +574 -0
  44. mlmm/cli_utils.py +166 -0
  45. mlmm/default_group.py +337 -0
  46. mlmm/defaults.py +467 -0
  47. mlmm/define_layer.py +526 -0
  48. mlmm/dft.py +1041 -0
  49. mlmm/energy_diagram.py +253 -0
  50. mlmm/extract.py +2213 -0
  51. mlmm/fix_altloc.py +464 -0
  52. mlmm/freq.py +1406 -0
  53. mlmm/harmonic_constraints.py +140 -0
  54. mlmm/hessian_cache.py +44 -0
  55. mlmm/hessian_calc.py +174 -0
  56. mlmm/irc.py +638 -0
  57. mlmm/mlmm_calc.py +2262 -0
  58. mlmm/mm_parm.py +945 -0
  59. mlmm/oniom_export.py +1983 -0
  60. mlmm/oniom_import.py +457 -0
  61. mlmm/opt.py +1742 -0
  62. mlmm/path_opt.py +1353 -0
  63. mlmm/path_search.py +2299 -0
  64. mlmm/preflight.py +88 -0
  65. mlmm/py.typed +1 -0
  66. mlmm/pysis_runner.py +45 -0
  67. mlmm/scan.py +1047 -0
  68. mlmm/scan2d.py +1226 -0
  69. mlmm/scan3d.py +1265 -0
  70. mlmm/scan_common.py +184 -0
  71. mlmm/summary_log.py +736 -0
  72. mlmm/trj2fig.py +448 -0
  73. mlmm/tsopt.py +2871 -0
  74. mlmm/utils.py +2309 -0
  75. mlmm/xtb_embedcharge_correction.py +475 -0
  76. mlmm_toolkit-0.2.2.dev0.dist-info/METADATA +1159 -0
  77. mlmm_toolkit-0.2.2.dev0.dist-info/RECORD +372 -0
  78. mlmm_toolkit-0.2.2.dev0.dist-info/WHEEL +5 -0
  79. mlmm_toolkit-0.2.2.dev0.dist-info/entry_points.txt +2 -0
  80. mlmm_toolkit-0.2.2.dev0.dist-info/licenses/LICENSE +674 -0
  81. mlmm_toolkit-0.2.2.dev0.dist-info/top_level.txt +4 -0
  82. pysisyphus/Geometry.py +1667 -0
  83. pysisyphus/LICENSE +674 -0
  84. pysisyphus/TableFormatter.py +63 -0
  85. pysisyphus/TablePrinter.py +74 -0
  86. pysisyphus/__init__.py +12 -0
  87. pysisyphus/calculators/AFIR.py +452 -0
  88. pysisyphus/calculators/AnaPot.py +20 -0
  89. pysisyphus/calculators/AnaPot2.py +48 -0
  90. pysisyphus/calculators/AnaPot3.py +12 -0
  91. pysisyphus/calculators/AnaPot4.py +20 -0
  92. pysisyphus/calculators/AnaPotBase.py +337 -0
  93. pysisyphus/calculators/AnaPotCBM.py +25 -0
  94. pysisyphus/calculators/AtomAtomTransTorque.py +154 -0
  95. pysisyphus/calculators/CFOUR.py +250 -0
  96. pysisyphus/calculators/Calculator.py +844 -0
  97. pysisyphus/calculators/CerjanMiller.py +24 -0
  98. pysisyphus/calculators/Composite.py +123 -0
  99. pysisyphus/calculators/ConicalIntersection.py +171 -0
  100. pysisyphus/calculators/DFTBp.py +430 -0
  101. pysisyphus/calculators/DFTD3.py +66 -0
  102. pysisyphus/calculators/DFTD4.py +84 -0
  103. pysisyphus/calculators/Dalton.py +61 -0
  104. pysisyphus/calculators/Dimer.py +681 -0
  105. pysisyphus/calculators/Dummy.py +20 -0
  106. pysisyphus/calculators/EGO.py +76 -0
  107. pysisyphus/calculators/EnergyMin.py +224 -0
  108. pysisyphus/calculators/ExternalPotential.py +264 -0
  109. pysisyphus/calculators/FakeASE.py +35 -0
  110. pysisyphus/calculators/FourWellAnaPot.py +28 -0
  111. pysisyphus/calculators/FreeEndNEBPot.py +39 -0
  112. pysisyphus/calculators/Gaussian09.py +18 -0
  113. pysisyphus/calculators/Gaussian16.py +726 -0
  114. pysisyphus/calculators/HardSphere.py +159 -0
  115. pysisyphus/calculators/IDPPCalculator.py +49 -0
  116. pysisyphus/calculators/IPIClient.py +133 -0
  117. pysisyphus/calculators/IPIServer.py +234 -0
  118. pysisyphus/calculators/LEPSBase.py +24 -0
  119. pysisyphus/calculators/LEPSExpr.py +139 -0
  120. pysisyphus/calculators/LennardJones.py +80 -0
  121. pysisyphus/calculators/MOPAC.py +219 -0
  122. pysisyphus/calculators/MullerBrownSympyPot.py +51 -0
  123. pysisyphus/calculators/MultiCalc.py +85 -0
  124. pysisyphus/calculators/NFK.py +45 -0
  125. pysisyphus/calculators/OBabel.py +87 -0
  126. pysisyphus/calculators/ONIOMv2.py +1129 -0
  127. pysisyphus/calculators/ORCA.py +893 -0
  128. pysisyphus/calculators/ORCA5.py +6 -0
  129. pysisyphus/calculators/OpenMM.py +88 -0
  130. pysisyphus/calculators/OpenMolcas.py +281 -0
  131. pysisyphus/calculators/OverlapCalculator.py +908 -0
  132. pysisyphus/calculators/Psi4.py +218 -0
  133. pysisyphus/calculators/PyPsi4.py +37 -0
  134. pysisyphus/calculators/PySCF.py +341 -0
  135. pysisyphus/calculators/PyXTB.py +73 -0
  136. pysisyphus/calculators/QCEngine.py +106 -0
  137. pysisyphus/calculators/Rastrigin.py +22 -0
  138. pysisyphus/calculators/Remote.py +76 -0
  139. pysisyphus/calculators/Rosenbrock.py +15 -0
  140. pysisyphus/calculators/SocketCalc.py +97 -0
  141. pysisyphus/calculators/TIP3P.py +111 -0
  142. pysisyphus/calculators/TransTorque.py +161 -0
  143. pysisyphus/calculators/Turbomole.py +965 -0
  144. pysisyphus/calculators/VRIPot.py +37 -0
  145. pysisyphus/calculators/WFOWrapper.py +333 -0
  146. pysisyphus/calculators/WFOWrapper2.py +341 -0
  147. pysisyphus/calculators/XTB.py +418 -0
  148. pysisyphus/calculators/__init__.py +81 -0
  149. pysisyphus/calculators/cosmo_data.py +139 -0
  150. pysisyphus/calculators/parser.py +150 -0
  151. pysisyphus/color.py +19 -0
  152. pysisyphus/config.py +133 -0
  153. pysisyphus/constants.py +65 -0
  154. pysisyphus/cos/AdaptiveNEB.py +230 -0
  155. pysisyphus/cos/ChainOfStates.py +725 -0
  156. pysisyphus/cos/FreeEndNEB.py +25 -0
  157. pysisyphus/cos/FreezingString.py +103 -0
  158. pysisyphus/cos/GrowingChainOfStates.py +71 -0
  159. pysisyphus/cos/GrowingNT.py +309 -0
  160. pysisyphus/cos/GrowingString.py +508 -0
  161. pysisyphus/cos/NEB.py +189 -0
  162. pysisyphus/cos/SimpleZTS.py +64 -0
  163. pysisyphus/cos/__init__.py +22 -0
  164. pysisyphus/cos/stiffness.py +199 -0
  165. pysisyphus/drivers/__init__.py +17 -0
  166. pysisyphus/drivers/afir.py +855 -0
  167. pysisyphus/drivers/barriers.py +271 -0
  168. pysisyphus/drivers/birkholz.py +138 -0
  169. pysisyphus/drivers/cluster.py +318 -0
  170. pysisyphus/drivers/diabatization.py +133 -0
  171. pysisyphus/drivers/merge.py +368 -0
  172. pysisyphus/drivers/merge_mol2.py +322 -0
  173. pysisyphus/drivers/opt.py +375 -0
  174. pysisyphus/drivers/perf.py +91 -0
  175. pysisyphus/drivers/pka.py +52 -0
  176. pysisyphus/drivers/precon_pos_rot.py +669 -0
  177. pysisyphus/drivers/rates.py +480 -0
  178. pysisyphus/drivers/replace.py +219 -0
  179. pysisyphus/drivers/scan.py +212 -0
  180. pysisyphus/drivers/spectrum.py +166 -0
  181. pysisyphus/drivers/thermo.py +31 -0
  182. pysisyphus/dynamics/Gaussian.py +103 -0
  183. pysisyphus/dynamics/__init__.py +20 -0
  184. pysisyphus/dynamics/colvars.py +136 -0
  185. pysisyphus/dynamics/driver.py +297 -0
  186. pysisyphus/dynamics/helpers.py +256 -0
  187. pysisyphus/dynamics/lincs.py +105 -0
  188. pysisyphus/dynamics/mdp.py +364 -0
  189. pysisyphus/dynamics/rattle.py +121 -0
  190. pysisyphus/dynamics/thermostats.py +128 -0
  191. pysisyphus/dynamics/wigner.py +266 -0
  192. pysisyphus/elem_data.py +3473 -0
  193. pysisyphus/exceptions.py +2 -0
  194. pysisyphus/filtertrj.py +69 -0
  195. pysisyphus/helpers.py +623 -0
  196. pysisyphus/helpers_pure.py +649 -0
  197. pysisyphus/init_logging.py +50 -0
  198. pysisyphus/intcoords/Bend.py +69 -0
  199. pysisyphus/intcoords/Bend2.py +25 -0
  200. pysisyphus/intcoords/BondedFragment.py +32 -0
  201. pysisyphus/intcoords/Cartesian.py +41 -0
  202. pysisyphus/intcoords/CartesianCoords.py +140 -0
  203. pysisyphus/intcoords/Coords.py +56 -0
  204. pysisyphus/intcoords/DLC.py +197 -0
  205. pysisyphus/intcoords/DistanceFunction.py +34 -0
  206. pysisyphus/intcoords/DummyImproper.py +70 -0
  207. pysisyphus/intcoords/DummyTorsion.py +72 -0
  208. pysisyphus/intcoords/LinearBend.py +105 -0
  209. pysisyphus/intcoords/LinearDisplacement.py +80 -0
  210. pysisyphus/intcoords/OutOfPlane.py +59 -0
  211. pysisyphus/intcoords/PrimTypes.py +286 -0
  212. pysisyphus/intcoords/Primitive.py +137 -0
  213. pysisyphus/intcoords/RedundantCoords.py +659 -0
  214. pysisyphus/intcoords/RobustTorsion.py +59 -0
  215. pysisyphus/intcoords/Rotation.py +147 -0
  216. pysisyphus/intcoords/Stretch.py +31 -0
  217. pysisyphus/intcoords/Torsion.py +101 -0
  218. pysisyphus/intcoords/Torsion2.py +25 -0
  219. pysisyphus/intcoords/Translation.py +45 -0
  220. pysisyphus/intcoords/__init__.py +61 -0
  221. pysisyphus/intcoords/augment_bonds.py +126 -0
  222. pysisyphus/intcoords/derivatives.py +10512 -0
  223. pysisyphus/intcoords/eval.py +80 -0
  224. pysisyphus/intcoords/exceptions.py +37 -0
  225. pysisyphus/intcoords/findiffs.py +48 -0
  226. pysisyphus/intcoords/generate_derivatives.py +414 -0
  227. pysisyphus/intcoords/helpers.py +235 -0
  228. pysisyphus/intcoords/logging_conf.py +10 -0
  229. pysisyphus/intcoords/mp_derivatives.py +10836 -0
  230. pysisyphus/intcoords/setup.py +962 -0
  231. pysisyphus/intcoords/setup_fast.py +176 -0
  232. pysisyphus/intcoords/update.py +272 -0
  233. pysisyphus/intcoords/valid.py +89 -0
  234. pysisyphus/interpolate/Geodesic.py +93 -0
  235. pysisyphus/interpolate/IDPP.py +55 -0
  236. pysisyphus/interpolate/Interpolator.py +116 -0
  237. pysisyphus/interpolate/LST.py +70 -0
  238. pysisyphus/interpolate/Redund.py +152 -0
  239. pysisyphus/interpolate/__init__.py +9 -0
  240. pysisyphus/interpolate/helpers.py +34 -0
  241. pysisyphus/io/__init__.py +22 -0
  242. pysisyphus/io/aomix.py +178 -0
  243. pysisyphus/io/cjson.py +24 -0
  244. pysisyphus/io/crd.py +101 -0
  245. pysisyphus/io/cube.py +220 -0
  246. pysisyphus/io/fchk.py +184 -0
  247. pysisyphus/io/hdf5.py +49 -0
  248. pysisyphus/io/hessian.py +72 -0
  249. pysisyphus/io/mol2.py +146 -0
  250. pysisyphus/io/molden.py +293 -0
  251. pysisyphus/io/orca.py +189 -0
  252. pysisyphus/io/pdb.py +269 -0
  253. pysisyphus/io/psf.py +79 -0
  254. pysisyphus/io/pubchem.py +31 -0
  255. pysisyphus/io/qcschema.py +34 -0
  256. pysisyphus/io/sdf.py +29 -0
  257. pysisyphus/io/xyz.py +61 -0
  258. pysisyphus/io/zmat.py +175 -0
  259. pysisyphus/irc/DWI.py +108 -0
  260. pysisyphus/irc/DampedVelocityVerlet.py +134 -0
  261. pysisyphus/irc/Euler.py +22 -0
  262. pysisyphus/irc/EulerPC.py +345 -0
  263. pysisyphus/irc/GonzalezSchlegel.py +187 -0
  264. pysisyphus/irc/IMKMod.py +164 -0
  265. pysisyphus/irc/IRC.py +878 -0
  266. pysisyphus/irc/IRCDummy.py +10 -0
  267. pysisyphus/irc/Instanton.py +307 -0
  268. pysisyphus/irc/LQA.py +53 -0
  269. pysisyphus/irc/ModeKill.py +136 -0
  270. pysisyphus/irc/ParamPlot.py +53 -0
  271. pysisyphus/irc/RK4.py +36 -0
  272. pysisyphus/irc/__init__.py +31 -0
  273. pysisyphus/irc/initial_displ.py +219 -0
  274. pysisyphus/linalg.py +411 -0
  275. pysisyphus/line_searches/Backtracking.py +88 -0
  276. pysisyphus/line_searches/HagerZhang.py +184 -0
  277. pysisyphus/line_searches/LineSearch.py +232 -0
  278. pysisyphus/line_searches/StrongWolfe.py +108 -0
  279. pysisyphus/line_searches/__init__.py +9 -0
  280. pysisyphus/line_searches/interpol.py +15 -0
  281. pysisyphus/modefollow/NormalMode.py +40 -0
  282. pysisyphus/modefollow/__init__.py +10 -0
  283. pysisyphus/modefollow/davidson.py +199 -0
  284. pysisyphus/modefollow/lanczos.py +95 -0
  285. pysisyphus/optimizers/BFGS.py +99 -0
  286. pysisyphus/optimizers/BacktrackingOptimizer.py +113 -0
  287. pysisyphus/optimizers/ConjugateGradient.py +98 -0
  288. pysisyphus/optimizers/CubicNewton.py +75 -0
  289. pysisyphus/optimizers/FIRE.py +113 -0
  290. pysisyphus/optimizers/HessianOptimizer.py +1176 -0
  291. pysisyphus/optimizers/LBFGS.py +228 -0
  292. pysisyphus/optimizers/LayerOpt.py +411 -0
  293. pysisyphus/optimizers/MicroOptimizer.py +169 -0
  294. pysisyphus/optimizers/NCOptimizer.py +90 -0
  295. pysisyphus/optimizers/Optimizer.py +1084 -0
  296. pysisyphus/optimizers/PreconLBFGS.py +260 -0
  297. pysisyphus/optimizers/PreconSteepestDescent.py +7 -0
  298. pysisyphus/optimizers/QuickMin.py +74 -0
  299. pysisyphus/optimizers/RFOptimizer.py +181 -0
  300. pysisyphus/optimizers/RSA.py +99 -0
  301. pysisyphus/optimizers/StabilizedQNMethod.py +248 -0
  302. pysisyphus/optimizers/SteepestDescent.py +23 -0
  303. pysisyphus/optimizers/StringOptimizer.py +173 -0
  304. pysisyphus/optimizers/__init__.py +41 -0
  305. pysisyphus/optimizers/closures.py +301 -0
  306. pysisyphus/optimizers/cls_map.py +58 -0
  307. pysisyphus/optimizers/exceptions.py +6 -0
  308. pysisyphus/optimizers/gdiis.py +280 -0
  309. pysisyphus/optimizers/guess_hessians.py +311 -0
  310. pysisyphus/optimizers/hessian_updates.py +355 -0
  311. pysisyphus/optimizers/poly_fit.py +285 -0
  312. pysisyphus/optimizers/precon.py +153 -0
  313. pysisyphus/optimizers/restrict_step.py +24 -0
  314. pysisyphus/pack.py +172 -0
  315. pysisyphus/peakdetect.py +948 -0
  316. pysisyphus/plot.py +1031 -0
  317. pysisyphus/run.py +2106 -0
  318. pysisyphus/socket_helper.py +74 -0
  319. pysisyphus/stocastic/FragmentKick.py +132 -0
  320. pysisyphus/stocastic/Kick.py +81 -0
  321. pysisyphus/stocastic/Pipeline.py +303 -0
  322. pysisyphus/stocastic/__init__.py +21 -0
  323. pysisyphus/stocastic/align.py +127 -0
  324. pysisyphus/testing.py +96 -0
  325. pysisyphus/thermo.py +156 -0
  326. pysisyphus/trj.py +824 -0
  327. pysisyphus/tsoptimizers/RSIRFOptimizer.py +56 -0
  328. pysisyphus/tsoptimizers/RSPRFOptimizer.py +182 -0
  329. pysisyphus/tsoptimizers/TRIM.py +59 -0
  330. pysisyphus/tsoptimizers/TSHessianOptimizer.py +463 -0
  331. pysisyphus/tsoptimizers/__init__.py +23 -0
  332. pysisyphus/wavefunction/Basis.py +239 -0
  333. pysisyphus/wavefunction/DIIS.py +76 -0
  334. pysisyphus/wavefunction/__init__.py +25 -0
  335. pysisyphus/wavefunction/build_ext.py +42 -0
  336. pysisyphus/wavefunction/cart2sph.py +190 -0
  337. pysisyphus/wavefunction/diabatization.py +304 -0
  338. pysisyphus/wavefunction/excited_states.py +435 -0
  339. pysisyphus/wavefunction/gen_ints.py +1811 -0
  340. pysisyphus/wavefunction/helpers.py +104 -0
  341. pysisyphus/wavefunction/ints/__init__.py +0 -0
  342. pysisyphus/wavefunction/ints/boys.py +193 -0
  343. pysisyphus/wavefunction/ints/boys_table_N_64_xasym_27.1_step_0.01.npy +0 -0
  344. pysisyphus/wavefunction/ints/cart_gto3d.py +176 -0
  345. pysisyphus/wavefunction/ints/coulomb3d.py +25928 -0
  346. pysisyphus/wavefunction/ints/diag_quadrupole3d.py +10036 -0
  347. pysisyphus/wavefunction/ints/dipole3d.py +8762 -0
  348. pysisyphus/wavefunction/ints/int2c2e3d.py +7198 -0
  349. pysisyphus/wavefunction/ints/int3c2e3d_sph.py +65040 -0
  350. pysisyphus/wavefunction/ints/kinetic3d.py +8240 -0
  351. pysisyphus/wavefunction/ints/ovlp3d.py +3777 -0
  352. pysisyphus/wavefunction/ints/quadrupole3d.py +15054 -0
  353. pysisyphus/wavefunction/ints/self_ovlp3d.py +198 -0
  354. pysisyphus/wavefunction/localization.py +458 -0
  355. pysisyphus/wavefunction/multipole.py +159 -0
  356. pysisyphus/wavefunction/normalization.py +36 -0
  357. pysisyphus/wavefunction/pop_analysis.py +134 -0
  358. pysisyphus/wavefunction/shells.py +1171 -0
  359. pysisyphus/wavefunction/wavefunction.py +504 -0
  360. pysisyphus/wrapper/__init__.py +11 -0
  361. pysisyphus/wrapper/exceptions.py +2 -0
  362. pysisyphus/wrapper/jmol.py +120 -0
  363. pysisyphus/wrapper/mwfn.py +169 -0
  364. pysisyphus/wrapper/packmol.py +71 -0
  365. pysisyphus/xyzloader.py +168 -0
  366. pysisyphus/yaml_mods.py +45 -0
  367. thermoanalysis/LICENSE +674 -0
  368. thermoanalysis/QCData.py +244 -0
  369. thermoanalysis/__init__.py +0 -0
  370. thermoanalysis/config.py +3 -0
  371. thermoanalysis/constants.py +20 -0
  372. thermoanalysis/thermo.py +1011 -0
@@ -0,0 +1,1011 @@
1
+ # [1] http://gaussian.com/thermo/
2
+ # [2] https://doi.org/10.1002/chem.201200497
3
+ # [3] https://doi.org/10.1021/acs.organomet.8b00456
4
+ # [4] https://cccbdb.nist.gov/thermo.asp
5
+ # [5] https://pubs.acs.org/doi/10.1021/acs.jctc.0c01306
6
+ # Single-Point Hessian Calculations
7
+ # Spicher, Grimme, 2021
8
+ # [6] https://doi.org/10.1063/5.0061187
9
+ # Calculation of improved enthalpy and entropy of vaporization by a modified
10
+ # partition function in quantum cluster equilibrium theory
11
+ # Ingenmey, Grimme, 2021
12
+
13
+
14
+ from collections import namedtuple
15
+ from typing import Literal, Optional, Tuple
16
+
17
+ import numpy as np
18
+ from numpy.typing import ArrayLike, NDArray
19
+
20
+ from thermoanalysis.constants import (
21
+ C,
22
+ KB,
23
+ KBAU,
24
+ NA,
25
+ R,
26
+ PLANCK,
27
+ J2AU,
28
+ J2CAL,
29
+ AMU2KG,
30
+ )
31
+ from thermoanalysis.config import p_DEFAULT, ROTOR_CUT_DEFAULT
32
+ from thermoanalysis.QCData import QCData
33
+
34
+
35
+ TRANS_ENTROPY_KINDS = Literal["sackur", "sackur_simple"]
36
+ VIB_KINDS = Literal["qrrho", "rrho"]
37
+
38
+
39
+ ThermoResults = namedtuple(
40
+ "ThermoResults",
41
+ (
42
+ "T kBT M p org_wavenumbers wavenumbers "
43
+ "scale_factor zpe_scale_factor invert_imag cutoff kind "
44
+ "atom_num linear point_group sym_num "
45
+ "Q_el Q_trans Q_rot Q_vib Q_vib_V0 Q_tot Q_tot_V0 "
46
+ "U_el U_trans U_rot U_vib U_therm U_tot ZPE H "
47
+ "S_trans S_rot S_vib S_el S_tot "
48
+ "c_el c_trans c_rot c_vib c_tot "
49
+ "TS_trans TS_rot TS_vib TS_el TS_tot G dG"
50
+ ),
51
+ )
52
+
53
+
54
+ def chai_head_gordon_weights(
55
+ frequencies: NDArray[np.float64], cutoff: float, alpha: int = 4
56
+ ) -> NDArray[np.float64]:
57
+ """Chai-Head-Gordon damping function.
58
+
59
+ Used for interpolating between harmonic oscillator and hindered rotor
60
+ approximations. See eq. (8) in [2], or Eq. (10) in [7].
61
+
62
+ Parameters
63
+ ----------
64
+ frequencies
65
+ Vibrational frequencies in 1/s.
66
+ cutoff
67
+ Wavenumber cutoff in cm⁻¹. Vibrations below this threshold will mostly
68
+ be treated as hindered rotors.
69
+ alpha
70
+ Exponent alpha in the damping function.
71
+
72
+ Returns
73
+ -------
74
+ weights
75
+ Weights of the damping function.
76
+ """
77
+
78
+ wavenumbers = (frequencies / C) / 100 # in cm⁻¹
79
+ weights = 1 / (1 + (cutoff / wavenumbers) ** alpha)
80
+ return weights
81
+
82
+
83
+ ##############
84
+ # ELECTRONIC #
85
+ ##############
86
+
87
+
88
+ def electronic_part_func(
89
+ multiplicity: int, electronic_energies: ArrayLike, temperature: float
90
+ ) -> float:
91
+ """Electronic partition function.
92
+
93
+ Parameters
94
+ ----------
95
+ multiplicity
96
+ Multiplicity of the molecule.
97
+ electronic_energies
98
+ Electronic energy/energies in Hartree.
99
+ temperature : float
100
+ Absolute temperature in Kelvin.
101
+
102
+ Returns
103
+ -------
104
+ Q_el
105
+ Electronic partition function.
106
+ """
107
+
108
+ electronic_energies = np.atleast_1d(electronic_energies)
109
+ energy_diffs = electronic_energies - electronic_energies.min()
110
+ g = multiplicity
111
+ q_el = g * (np.exp(-energy_diffs / (KBAU * temperature))).sum()
112
+ return q_el
113
+
114
+
115
+ def electronic_entropy(multiplicity: int) -> float:
116
+ """Electronic entropy.
117
+
118
+ Currently, only one electronic state is considered. See [1] for reference.
119
+
120
+ Parameters
121
+ ----------
122
+ multiplicity
123
+ Multiplicity of the molecule.
124
+
125
+ Returns
126
+ -------
127
+ S_el
128
+ Electronic entropy in Hartree / particle.
129
+ """
130
+ S_el = KBAU * np.log(multiplicity)
131
+ return S_el
132
+
133
+
134
+ #######################
135
+ # TRANSLATIONAL TERMS #
136
+ #######################
137
+
138
+
139
+ def translational_part_func(
140
+ molecular_mass: float, temperature: float, pressure: float
141
+ ) -> float:
142
+ """Translational partition function Q_trans.
143
+
144
+ Parameters
145
+ ----------
146
+ molecular_mass
147
+ Molecular mass in atomic mass units (amu).
148
+ temperature
149
+ Absolute temperature in Kelvin.
150
+ pressure
151
+ Pressure in Pascal.
152
+
153
+ Returns
154
+ -------
155
+ q_trans
156
+ Translational partition function.
157
+ """
158
+ # Volume of an atom of an ideal gas
159
+ volume = KB * temperature / pressure
160
+ molecular_mass_kg = molecular_mass * AMU2KG
161
+ q_trans = (
162
+ (2 * np.pi * molecular_mass_kg * KB * temperature / PLANCK**2) ** 1.5
163
+ ) * volume
164
+ return q_trans
165
+
166
+
167
+ def translational_energy(temperature: float) -> float:
168
+ """Kinectic energy of an ideal gas.
169
+
170
+ See [1] for reference.
171
+
172
+ Parameters
173
+ ----------
174
+ temperature
175
+ Absolute temperature in Kelvin.
176
+
177
+ Returns
178
+ -------
179
+ U_trans
180
+ Kinetic energy in Hartree / particle.
181
+ """
182
+ U_trans = 3 / 2 * KBAU * temperature
183
+ return U_trans
184
+
185
+
186
+ def sackur_tetrode(molecular_mass: float, temperature: float, pressure: float) -> float:
187
+ """Translational entropy of an ideal gas.
188
+
189
+ See [1] for reference.
190
+
191
+ Parameters
192
+ ----------
193
+ molecular_mass
194
+ Molecular mass in atomic mass units (amu).
195
+ temperature
196
+ Absolute temperature in Kelvin.
197
+ pressure
198
+ Pressure in Pascal.
199
+
200
+ Returns
201
+ -------
202
+ S_trans
203
+ Translational entropy in Hartree / (particle * K).
204
+ """
205
+ # Just using 1e5 instead of a "true" atmosphere of 1.01325e5 seems to
206
+ # agree better with the results Gaussian and ORCA produce.
207
+ q_trans = (
208
+ (2 * np.pi * molecular_mass * AMU2KG * KB * temperature / PLANCK**2) ** (3 / 2)
209
+ * KB
210
+ * temperature
211
+ / pressure
212
+ )
213
+ S_trans = KBAU * (np.log(q_trans) + 1 + 3 / 2)
214
+ return S_trans
215
+
216
+
217
+ def sackur_tetrode_simplified(molecular_mass: float, temperature: float) -> float:
218
+ """Translational entropy of a monoatomic ideal gas.
219
+
220
+ See [3] for reference.
221
+
222
+ Parameters
223
+ ----------
224
+ molecular_mass
225
+ Molecular mass in atomic mass units (amu).
226
+ temperature
227
+ Absolute temperature in Kelvin.
228
+
229
+ Returns
230
+ -------
231
+ S_trans
232
+ Translational entropy in J/(mol*K).
233
+ """
234
+ S_trans = (
235
+ 3 / 2 * R * np.log(molecular_mass) + 5 / 2 * R * np.log(temperature) - 2.315
236
+ )
237
+ return S_trans
238
+
239
+
240
+ def translational_entropy(
241
+ molecular_mass: float,
242
+ temperature: float,
243
+ pressure: float,
244
+ kind: TRANS_ENTROPY_KINDS = "sackur",
245
+ ) -> float:
246
+ """Wrapper for translational entropy calculation.
247
+
248
+ Parameters
249
+ ----------
250
+ molecular_mass
251
+ Molecular mass in atomic mass units (amu).
252
+ temperature
253
+ Absolute temperature in Kelvin.
254
+ kind
255
+ Type of calculation method.
256
+
257
+ Returns
258
+ -------
259
+ S_trans
260
+ Translational entropy.
261
+ """
262
+ funcs = {
263
+ "sackur": lambda M, T: sackur_tetrode(
264
+ molecular_mass=M, temperature=T, pressure=pressure
265
+ ),
266
+ "sackur_simple": sackur_tetrode_simplified,
267
+ }
268
+ return funcs[kind](molecular_mass, temperature)
269
+
270
+
271
+ def translational_heat_capacity() -> float:
272
+ """Constant volume heat capacity.
273
+
274
+ Returns
275
+ -------
276
+ c_trans
277
+ Constant volume heat capacity in J / (K mol).
278
+ """
279
+ c_trans = 1.5 * R
280
+ return c_trans
281
+
282
+
283
+ ####################
284
+ # ROTATIONAL TERMS #
285
+ ####################
286
+
287
+
288
+ def rotational_part_func(
289
+ temperature: float,
290
+ rot_temperatures: NDArray[np.float64],
291
+ symmetry_number: int,
292
+ is_atom: bool,
293
+ is_linear: bool,
294
+ ) -> float:
295
+ """Rotational partition function Q_rot.
296
+
297
+ See [1] for reference.
298
+
299
+ Parameters
300
+ ----------
301
+ temperature
302
+ Absolute temperature in Kelvin.
303
+ rot_temperatures
304
+ Rotational temperatures in Kelvin.
305
+ symmetry_number
306
+ Symmetry number.
307
+ is_atom
308
+ Wether the molcule is an atom.
309
+ is_linear
310
+ Wether the molecule is linear.
311
+
312
+ Returns
313
+ -------
314
+ Q_rot
315
+ Rotational partition function.
316
+ """
317
+
318
+ if is_atom:
319
+ q_rot = 1
320
+ elif is_linear:
321
+ # First rot_temperature will be infinite, and the last two components
322
+ # will be identical. Only use the last one.
323
+ q_rot = temperature / (rot_temperatures[-1] * symmetry_number)
324
+ else:
325
+ # Polyamtomic, non-linear case
326
+ q_rot = (
327
+ np.pi ** (1 / 2)
328
+ / symmetry_number
329
+ * (temperature ** (3 / 2) / np.prod(rot_temperatures) ** (1 / 2))
330
+ )
331
+ return q_rot
332
+
333
+
334
+ def rotational_energy(temperature: float, is_linear: bool, is_atom: bool) -> float:
335
+ """Rotational energy.
336
+
337
+ See [1] for reference.
338
+
339
+ Parameters
340
+ ----------
341
+ temperature
342
+ Absolute temperature in Kelvin.
343
+ is_linear
344
+ Wether the molecule is linear.
345
+ is_atom
346
+ Wether the molcule is an atom.
347
+
348
+ Returns
349
+ -------
350
+ U_rot
351
+ Rotational energy in Hartree / particle.
352
+ """
353
+ if is_atom:
354
+ factor = 0.0
355
+ elif is_linear:
356
+ factor = 1.0
357
+ else:
358
+ factor = 1.5
359
+
360
+ U_rot = factor * KBAU * temperature
361
+ return U_rot
362
+
363
+
364
+ def rotational_entropy(
365
+ temperature: float,
366
+ rot_temperatures: NDArray[np.float64],
367
+ symmetry_number: int,
368
+ is_atom: bool,
369
+ is_linear: bool,
370
+ ) -> float:
371
+ """Rotational entropy.
372
+
373
+ See [1] for reference.
374
+
375
+ Parameters
376
+ ----------
377
+ temperature
378
+ Absolute temperature in Kelvin.
379
+ rot_temperatures
380
+ Rotational temperatures in Kelvin.
381
+ symmetry_number
382
+ Symmetry number.
383
+ is_atom
384
+ Wether the molcule is an atom.
385
+ is_linear
386
+ Wether the molecule is linear.
387
+
388
+ Returns
389
+ -------
390
+ S_rot
391
+ Rotational entropy in Hartree /(particle * K).
392
+ """
393
+
394
+ q_rot = rotational_part_func(
395
+ temperature, rot_temperatures, symmetry_number, is_atom, is_linear=is_linear
396
+ )
397
+ if is_atom:
398
+ plus = 0.0
399
+ elif is_linear:
400
+ plus = 1.0
401
+ # Polyamtomic, non-linear case
402
+ else:
403
+ plus = 1.5
404
+ S_rot = KBAU * (np.log(q_rot) + plus)
405
+ return S_rot
406
+
407
+
408
+ def rotational_heat_capacity(is_atom: bool, is_linear: bool) -> float:
409
+ """Rotational heat capacity.
410
+
411
+ Parameters
412
+ ----------
413
+ is_atom
414
+ Wether the molcule is an atom.
415
+ is_linear
416
+ Wether the molecule is linear.
417
+
418
+ Returns
419
+ -------
420
+ c_rot
421
+ Rotational contributions to the heat capacity.
422
+ """
423
+ if is_atom:
424
+ c_rot = 0
425
+ elif is_linear:
426
+ c_rot = R
427
+ else:
428
+ c_rot = 1.5 * R
429
+ return c_rot
430
+
431
+
432
+ #####################
433
+ # VIBRATIONAL TERMS #
434
+ #####################
435
+
436
+
437
+ def zero_point_energy(frequencies: NDArray[np.float64]) -> float:
438
+ """Vibrational zero point energies.
439
+
440
+ See [1] for reference.
441
+
442
+ Parameters
443
+ ----------
444
+ frequencies
445
+ Vibrational frequencies in 1/s.
446
+
447
+ Returns
448
+ -------
449
+ ZPE
450
+ Vibrational ZPE for the given frequencies in Hartree / particle.
451
+ """
452
+
453
+ ZPE = J2AU * (PLANCK * frequencies / 2).sum()
454
+ return ZPE
455
+
456
+
457
+ def vibrational_part_funcs(
458
+ temperature: float, frequencies: NDArray[np.float64]
459
+ ) -> Tuple[NDArray[np.float64], NDArray[np.float64]]:
460
+ """Vibrational partition functions for harmonic oscillators.
461
+
462
+ Parameters
463
+ ----------
464
+ temperature
465
+ Absolute temperature in Kelvin.
466
+ frequencies
467
+ Vibrational frequencies in 1/s.
468
+
469
+ Returns
470
+ -------
471
+ q_vibs : ArrayLike
472
+ Vibrational partition functions at the bottom of the well.
473
+ q_vibs_V0 : ArrayLike
474
+ Vibrational partition functions with first vibrational energy level
475
+ as reference.
476
+ """
477
+ frequencies = np.array(frequencies, dtype=float)
478
+ quot = -PLANCK * frequencies / (KB * temperature)
479
+ denom = 1 / (1 - np.exp(quot))
480
+ quot_half = quot / 2
481
+ # Bottom of the well as reference
482
+ q_vibs = np.exp(quot_half) * denom
483
+ # First vibrational energy level as reference
484
+ q_vibs_V0 = denom
485
+ return q_vibs, q_vibs_V0
486
+
487
+
488
+ def qrrho_vibrational_part_func(
489
+ temperature: float,
490
+ frequencies: NDArray[np.float64],
491
+ I_mean: Optional[float],
492
+ cutoff: float,
493
+ alpha: int = 4,
494
+ ) -> Tuple[float, float]:
495
+ """QRRHO vibrational partition function.
496
+
497
+ As given in Eq. (7) in [6]. Mix between hindererd rotor
498
+ and harmonic oscillator partition function.
499
+
500
+ Parameters
501
+ ----------
502
+ temperature
503
+ Absolute temperature in Kelvin.
504
+ frequencies
505
+ Vibrational frequencies in 1/s.
506
+ I_mean
507
+ Average moment of inertia of the molecule in Angstrom² * amu.
508
+ cutoff
509
+ Wavenumber cutoff in cm⁻¹. Vibrations below this threshold will mostly
510
+ be treated as hindered rotors.
511
+ alpha
512
+ Exponent alpha in the damping function (Eq. (8) in [2], or Eq. (10) in [7])
513
+
514
+ Returns
515
+ -------
516
+ q_qrrho
517
+ QRRHO Vibrational partition function, unitless.
518
+ q_qrrho_V0
519
+ QRRHO Vibrational partition function, evaluated at first vibrational energy level.
520
+ """
521
+
522
+ wavenumbers = (frequencies / C) / 100 # in cm⁻¹
523
+ q_vibs, q_vibs_V0 = vibrational_part_funcs(temperature, frequencies)
524
+
525
+ if cutoff > 0.0 and I_mean:
526
+ # Normal mode moments of inertia
527
+ wavenumbers_m = 100 * wavenumbers # in m⁻¹
528
+ mu = PLANCK / (
529
+ 2 * np.pi * 4 * np.pi * wavenumbers_m
530
+ ) # Eq. (9) in [6], in kg m³ s⁻¹
531
+ # Convert average moment of inertia from Ų AMU to SI units (m² kg)
532
+ I_mean_SI = I_mean * 1e-20 * AMU2KG
533
+ mu_ = mu * I_mean_SI / (mu + I_mean_SI)
534
+ q_hr = np.sqrt(
535
+ 2 * mu_ / (np.pi * (PLANCK / (2 * np.pi)) ** 2 / (temperature * KB))
536
+ )
537
+ # Without cutoff this partition function reduces to the partition function
538
+ # of the harmonic oscillator.
539
+ else:
540
+ q_hr = np.ones_like(q_vibs)
541
+
542
+ weights = chai_head_gordon_weights(frequencies, cutoff, alpha)
543
+
544
+ def prod(q_vibs):
545
+ # Work in log-domain to avoid overflow in large mode products.
546
+ tiny = np.finfo(float).tiny
547
+ max_log = np.log(np.finfo(float).max)
548
+ min_log = np.log(tiny)
549
+ log_q = np.sum(
550
+ weights * np.log(np.clip(q_vibs, tiny, None))
551
+ + (1 - weights) * np.log(np.clip(q_hr, tiny, None))
552
+ )
553
+ if log_q >= max_log:
554
+ return float("inf")
555
+ if log_q <= min_log:
556
+ return 0.0
557
+ return float(np.exp(log_q))
558
+
559
+ q_qrrho = prod(q_vibs)
560
+ q_qrrho_V0 = prod(q_vibs_V0)
561
+ return q_qrrho, q_qrrho_V0
562
+
563
+
564
+ def vibrational_part_func(
565
+ temperature: float, frequencies: NDArray[np.float64]
566
+ ) -> Tuple[float, float]:
567
+ """Vibrational partition function.
568
+
569
+ Parameters
570
+ ----------
571
+ temperature
572
+ Absolute temperature in Kelvin.
573
+ frequencies
574
+ Vibrational frequencies in 1/s.
575
+
576
+ Returns
577
+ -------
578
+ q_qrrho
579
+ QRRHO Vibrational partition function, unitless.
580
+ q_qrrho_V0
581
+ QRRHO Vibrational partition function, evaluated at first vibrational energy level,
582
+ unitless.
583
+ """
584
+ return qrrho_vibrational_part_func(
585
+ temperature, frequencies, I_mean=None, cutoff=0.0
586
+ )
587
+
588
+
589
+ def vibrational_energy(temperature: float, frequencies: NDArray[np.float64]) -> float:
590
+ """Vibrational energy.
591
+
592
+ See [1] for reference.
593
+
594
+ Parameters
595
+ ----------
596
+ temperature
597
+ Absolute temperature in Kelvin.
598
+ frequencies
599
+ Vibrational frequencies in 1/s.
600
+
601
+ Returns
602
+ -------
603
+ U_vib
604
+ Vibrational energy in Hartree / particle.
605
+ """
606
+ vib_temperatures = PLANCK * frequencies / KB
607
+ U_vib = KBAU * np.sum(
608
+ vib_temperatures * (1 / 2 + 1 / (np.exp(vib_temperatures / temperature) - 1))
609
+ )
610
+ return U_vib
611
+
612
+
613
+ def harmonic_vibrational_entropies(
614
+ temperature: float, frequencies: NDArray[np.float64]
615
+ ) -> NDArray[np.float64]:
616
+ """Vibrational entropy of a harmonic oscillator.
617
+
618
+ See [1] and [2] for reference. Eq. (3) in the Grimme paper
619
+ is lacking a T in the denominator of the first term. It is given as
620
+ h*w/(k(e^(hw/kt) -1)) but it must be h*w(kT(e^(hw/kT)-1)) instead.
621
+ Here the calculation is done as presented in [1].
622
+
623
+ Parameters
624
+ ----------
625
+ temperature : float
626
+ Absolute temperature in Kelvin.
627
+ frequencies : array-like
628
+ Vibrational frequencies in 1/s.
629
+
630
+ Returns
631
+ -------
632
+ S_vib : array-like
633
+ Array containing vibrational entropies in Hartree / (particle * K).
634
+ """
635
+
636
+ # Correct formula from the Grimme paper [3].
637
+ # hnu = PLANCK * frequencies
638
+ # hnu_kt = hnu / (KB * temperature)
639
+ # S_vib = KB * (hnu / (KB*(np.exp(hnu_kt) - 1)*temperature)
640
+ # - np.log(1 - np.exp(-hnu_kt))
641
+ # ).sum()
642
+
643
+ # As given in [1].
644
+ vib_temps = frequencies * PLANCK / KB
645
+ S_vibs = KBAU * (
646
+ (vib_temps / temperature) / (np.exp(vib_temps / temperature) - 1)
647
+ - np.log(1 - np.exp(-vib_temps / temperature))
648
+ )
649
+ return S_vibs
650
+
651
+
652
+ def free_rotor_entropies(
653
+ temperature: float, frequencies: NDArray[np.float64], B_av: float = 1e-44
654
+ ) -> NDArray[np.float64]:
655
+ """Entropy of a free rotor.
656
+
657
+ See [2] for reference.
658
+
659
+ Parameters
660
+ ----------
661
+ temperature
662
+ Absolute temperature in Kelvin.
663
+ frequencies
664
+ Vibrational frequencies in 1/s.
665
+ B_av
666
+ Limiting value for effective moment of inertia in kg*m².
667
+
668
+ Returns
669
+ -------
670
+ S_free_rots
671
+ Array containing free-rotor entropies in Hartree / (particle * K).
672
+ """
673
+ inertia_moments = PLANCK / (8 * np.pi**2 * frequencies)
674
+ eff_inertia_moments = (inertia_moments * B_av) / (inertia_moments + B_av)
675
+ S_free_rots = KBAU * (
676
+ 1 / 2
677
+ + np.log(
678
+ (8 * np.pi**3 * eff_inertia_moments * KB * temperature / PLANCK**2)
679
+ ** (1 / 2)
680
+ )
681
+ )
682
+ return S_free_rots
683
+
684
+
685
+ def vibrational_entropies(
686
+ temperature: float, frequencies: NDArray[np.float64], cutoff: float, alpha: int = 4
687
+ ) -> NDArray[np.float64]:
688
+ """Weighted vibrational entropy.
689
+
690
+ As given in Eq. (7) of [2].
691
+
692
+ Parameters
693
+ ----------
694
+ temperature
695
+ Absolute temperature in Kelvin.
696
+ frequencies
697
+ Vibrational frequencies in 1/s.
698
+ cutoff
699
+ Wavenumber cutoff in cm⁻¹. Vibrations below this threshold will mostly
700
+ be described by free-rotor entropies.
701
+ alpha
702
+ Exponent alpha in the damping function (Eq. (8) in [2]).
703
+
704
+ Returns
705
+ -------
706
+ S_vibs
707
+ Array containing vibrational entropies in Hartree / (particle * K).
708
+ """
709
+ weights = chai_head_gordon_weights(frequencies, cutoff, alpha)
710
+ S_harmonic = harmonic_vibrational_entropies(temperature, frequencies)
711
+ S_quasi_harmonic = free_rotor_entropies(temperature, frequencies)
712
+ S_vibs = weights * S_harmonic + (1 - weights) * S_quasi_harmonic
713
+ return S_vibs
714
+
715
+
716
+ def vibrational_entropy(
717
+ temperature: float, frequencies: NDArray[np.float64], cutoff: float, alpha: int = 4
718
+ ) -> float:
719
+ """Vibrational entropy.
720
+
721
+ Wrapper function. As given in Eq. (7) of [2].
722
+
723
+ Parameters
724
+ ----------
725
+ temperature
726
+ Absolute temperature in Kelvin.
727
+ frequencies
728
+ Vibrational frequencies in 1/s.
729
+ cutoff
730
+ Wavenumber cutoff in cm⁻¹. Vibrations below this threshold will mostly
731
+ be described by free-rotor entropies.
732
+ alpha
733
+ Exponent alpha in the damping function (Eq. (8) in [2]).
734
+
735
+ Returns
736
+ -------
737
+ S_vib
738
+ Vibrational entropy in Hartree / (particle * K).
739
+ """
740
+ return vibrational_entropies(temperature, frequencies, cutoff, alpha).sum()
741
+
742
+
743
+ def vibrational_heat_capacity(
744
+ temperature: float, frequencies: NDArray[np.float64]
745
+ ) -> float:
746
+ """
747
+ Parameters
748
+ ----------
749
+ temperature
750
+ Absolute temperature in Kelvin.
751
+ frequencies
752
+ Vibrational frequencies in 1/s.
753
+
754
+ Returns
755
+ -------
756
+ c_vib
757
+ Vibrational contributions to the heat capacity in J / (K mol).
758
+ """
759
+ quot = PLANCK * frequencies / (KB * temperature)
760
+
761
+ c_vib = R * (np.exp(quot) * (quot / (np.exp(quot) - 1)) ** 2).sum()
762
+ return c_vib
763
+
764
+
765
+ def thermochemistry(
766
+ qc: QCData,
767
+ temperature: float,
768
+ pressure: float = p_DEFAULT,
769
+ kind: VIB_KINDS = "qrrho",
770
+ rotor_cutoff: float = ROTOR_CUT_DEFAULT,
771
+ scale_factor: float = 1.0,
772
+ zpe_scale_factor: float = 1.0,
773
+ invert_imags: float = 0.0,
774
+ cutoff: float = 0.0,
775
+ ) -> ThermoResults:
776
+ assert kind in "qrrho rrho".split()
777
+ assert invert_imags <= 0.0
778
+ assert cutoff >= 0.0
779
+
780
+ T = temperature
781
+ pressure = pressure
782
+
783
+ org_wavenumbers = qc.wavenumbers.copy()
784
+ wavenumbers = org_wavenumbers.copy()
785
+ # If given a full set of 3N normal mode wavenumbers, exclude N wavenumbers
786
+ # with the smallest absolute wavenumbers.
787
+ if wavenumbers.size == 3 * qc.atom_num:
788
+ abs_wavenumbers = np.abs(wavenumbers)
789
+ inds = np.argsort(abs_wavenumbers)
790
+ drop_first = 6 - int(qc.is_linear)
791
+ # Drop first N wavenumbers with smallest absolute values
792
+ keep_mask = np.ones_like(wavenumbers, dtype=bool)
793
+ keep_mask[inds[:drop_first]] = False
794
+ wavenumbers = wavenumbers[keep_mask]
795
+
796
+ """
797
+ Remaining imaginary frequencies with small absolute values can be inverted.
798
+ Afterwards, all frequencies are scaled by the given factor. Finally, positive
799
+ frequencies below a given cutoff are set to this cutoff, e.g. 50 cm⁻¹.
800
+
801
+ [5] suggests inverting imaginary frequencies above -20 cm⁻¹.
802
+ """
803
+ if invert_imags < 0.0:
804
+ to_invert = np.logical_and(invert_imags <= wavenumbers, wavenumbers <= 0.0)
805
+ if to_invert.sum() > 0:
806
+ print(f"Inverted {to_invert.sum()} imaginary frequencies.")
807
+ wavenumbers[to_invert] *= -1
808
+
809
+ wavenumbers *= scale_factor
810
+
811
+ if cutoff > 0.0:
812
+ to_cut = np.logical_and(wavenumbers < cutoff, wavenumbers > 0.0)
813
+ wavenumbers[to_cut] = cutoff
814
+
815
+ wavenumbers = wavenumbers[wavenumbers > 0.0]
816
+ vib_frequencies = C * wavenumbers * 100
817
+
818
+ # Partition functions
819
+ Q_el = electronic_part_func(qc.mult, qc.scf_energy, temperature=T)
820
+ Q_trans = translational_part_func(qc.M, temperature=T, pressure=pressure)
821
+ Q_rot = rotational_part_func(
822
+ temperature=T,
823
+ rot_temperatures=qc.rot_temperatures,
824
+ symmetry_number=qc.symmetry_number,
825
+ is_atom=qc.is_atom,
826
+ is_linear=qc.is_linear,
827
+ )
828
+
829
+ # Zero point energy
830
+ zpe_org = zpe_scale_factor * zero_point_energy(vib_frequencies)
831
+ zpe = zpe_scale_factor * zpe_org
832
+
833
+ # Internal energies
834
+ U_el = qc.scf_energy
835
+ U_trans = translational_energy(T)
836
+ U_rot = rotational_energy(T, qc.is_linear, qc.is_atom)
837
+ # U_vib already includes ZPE
838
+ U_vib = vibrational_energy(T, vib_frequencies)
839
+ # Replace ZPE in U_vib w/ scaled ZPE
840
+ U_vib = U_vib - zpe_org + zpe
841
+ U_therm = U_rot + U_vib + U_trans
842
+ U_tot = U_el + U_therm
843
+
844
+ H = U_tot + KBAU * T
845
+
846
+ # Entropies
847
+ S_el = electronic_entropy(qc.mult)
848
+ S_rot = rotational_entropy(
849
+ T,
850
+ qc.rot_temperatures,
851
+ qc.symmetry_number,
852
+ is_atom=qc.is_atom,
853
+ is_linear=qc.is_linear,
854
+ )
855
+ S_trans = translational_entropy(qc.M, T, pressure=pressure)
856
+
857
+ # Heat capacities
858
+ c_el = 0.0 # always zero
859
+ c_trans = translational_heat_capacity()
860
+ c_rot = rotational_heat_capacity(is_atom=qc.is_atom, is_linear=qc.is_linear)
861
+ c_vib = vibrational_heat_capacity(T, vib_frequencies)
862
+ c_tot = c_el + c_trans + c_rot + c_vib
863
+
864
+ if kind == "rrho":
865
+ S_hvibs = harmonic_vibrational_entropies(T, vib_frequencies)
866
+ S_vib = S_hvibs.sum()
867
+ # Harmonic oscillator partition function
868
+ Q_vib, Q_vib_V0 = vibrational_part_func(
869
+ frequencies=vib_frequencies, temperature=T
870
+ )
871
+ elif kind == "qrrho":
872
+ S_vib = vibrational_entropy(T, vib_frequencies, cutoff=rotor_cutoff)
873
+ I_mean = qc.average_moment_of_inertia
874
+ Q_vib, Q_vib_V0 = qrrho_vibrational_part_func(
875
+ T, vib_frequencies, I_mean, cutoff=rotor_cutoff
876
+ )
877
+ else:
878
+ raise Exception("You should never get here!")
879
+
880
+ Q_tot = Q_el * Q_trans * Q_rot * Q_vib
881
+ Q_tot_V0 = Q_el * Q_trans * Q_rot * Q_vib_V0
882
+
883
+ S_tot = S_el + S_trans + S_rot + S_vib
884
+ G = H - T * S_tot
885
+ dG = G - U_el
886
+
887
+ thermo = ThermoResults(
888
+ T=temperature,
889
+ kBT=KBAU * temperature,
890
+ M=qc.M,
891
+ p=pressure,
892
+ point_group=qc.point_group,
893
+ sym_num=qc.symmetry_number,
894
+ scale_factor=scale_factor,
895
+ zpe_scale_factor=zpe_scale_factor,
896
+ invert_imag=invert_imags,
897
+ cutoff=cutoff,
898
+ kind=kind,
899
+ org_wavenumbers=org_wavenumbers,
900
+ wavenumbers=wavenumbers,
901
+ atom_num=qc.atom_num,
902
+ linear=qc.is_linear,
903
+ Q_el=Q_el,
904
+ Q_trans=Q_trans,
905
+ Q_rot=Q_rot,
906
+ Q_vib=Q_vib,
907
+ Q_vib_V0=Q_vib_V0,
908
+ Q_tot=Q_tot,
909
+ Q_tot_V0=Q_tot_V0,
910
+ U_el=U_el,
911
+ U_trans=U_trans,
912
+ U_rot=U_rot,
913
+ U_vib=U_vib,
914
+ U_therm=U_therm,
915
+ U_tot=U_tot,
916
+ ZPE=zpe,
917
+ H=H,
918
+ S_trans=S_trans,
919
+ S_rot=S_rot,
920
+ S_vib=S_vib,
921
+ S_el=S_el,
922
+ S_tot=S_tot,
923
+ c_el=c_el,
924
+ c_trans=c_trans,
925
+ c_rot=c_rot,
926
+ c_vib=c_vib,
927
+ c_tot=c_tot,
928
+ TS_trans=T * S_trans,
929
+ TS_rot=T * S_rot,
930
+ TS_vib=T * S_vib,
931
+ TS_el=T * S_el,
932
+ TS_tot=T * S_tot,
933
+ G=G,
934
+ dG=dG,
935
+ )
936
+ return thermo
937
+
938
+
939
+ def print_thermo_results(thermo_results: ThermoResults):
940
+ au2CalMol = 1 / J2AU * NA * J2CAL
941
+
942
+ def toCalMol(E):
943
+ return f"{E*au2CalMol: >14.2f} cal/mol"
944
+
945
+ def StoCalKMol(S):
946
+ return f"{S*au2CalMol: >12.2f} cal/(K mol)"
947
+
948
+ def CtoCalKMol(c):
949
+ return f"{c*J2CAL: >12.4f} cal/(K mol)"
950
+
951
+ def part_func(Q):
952
+ return f"{Q: >12.8e}"
953
+
954
+ def print_line(key, fmt_func, num):
955
+ print(f"{key: >18s} = {fmt_func(num)}")
956
+
957
+ def pQ(key, num):
958
+ print_line(f"Q_{key}", part_func, num)
959
+
960
+ def pU(key, num):
961
+ print_line(f"U_{key}", toCalMol, num)
962
+
963
+ def pS(key, num):
964
+ print_line(f"S_{key}", StoCalKMol, num)
965
+
966
+ def pC(key, num):
967
+ print_line(f"C_{key}", CtoCalKMol, num)
968
+
969
+ tr = thermo_results
970
+ T = tr.T
971
+ print(
972
+ f"Thermochemistry @ {T:.2f} K and {tr.p:.6e} Pa, '{tr.kind.upper()}' analysis.\n"
973
+ )
974
+
975
+ print("Partition functions:")
976
+ pQ("el", tr.Q_el)
977
+ pQ("trans", tr.Q_trans)
978
+ pQ("rot", tr.Q_rot)
979
+ pQ("vib BOT", tr.Q_vib)
980
+ pQ("vib V0", tr.Q_vib_V0)
981
+ print()
982
+
983
+ print("Zero-point energy")
984
+ print_line("ZPE", toCalMol, tr.ZPE)
985
+ print()
986
+
987
+ print("Internal energy")
988
+ pU("el", tr.U_el)
989
+ pU("trans", tr.U_trans)
990
+ pU("rot", tr.U_rot)
991
+ pU("vib (incl. ZPE)", tr.U_vib)
992
+ pU("therm", tr.U_therm)
993
+ print("U_tot = U_el + U_trans + U_rot + U_vib")
994
+ pU("tot", tr.U_tot)
995
+ print()
996
+
997
+ print("Entropy contributions:")
998
+ pS("el", tr.S_el)
999
+ pS("trans", tr.S_trans)
1000
+ pS("rot", tr.S_rot)
1001
+ pS("vib", tr.S_vib)
1002
+ print("S_tot = S_el + S_trans + S_rot + S_vib")
1003
+ pS("tot", tr.S_tot)
1004
+ print()
1005
+
1006
+ print("Heat capacities:")
1007
+ pC("el", tr.c_el)
1008
+ pC("trans", tr.c_trans)
1009
+ pC("rot", tr.c_rot)
1010
+ pC("vib", tr.c_vib)
1011
+ pC("tot", tr.c_tot)