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,726 @@
1
+ from collections import namedtuple
2
+ import io
3
+ from pathlib import Path
4
+ import re
5
+ import shutil
6
+ import subprocess
7
+ import textwrap
8
+
9
+ import numpy as np
10
+ import pyparsing as pp
11
+
12
+ from pysisyphus.calculators.OverlapCalculator import OverlapCalculator
13
+ from pysisyphus.constants import AU2EV, BOHR2ANG
14
+ from pysisyphus.helpers_pure import file_or_str
15
+
16
+
17
+ class Gaussian16(OverlapCalculator):
18
+ conf_key = "gaussian16"
19
+ _set_plans = (
20
+ {"key": "chk", "condition": lambda self: self.keep_chk},
21
+ "fchk",
22
+ ("fchk", "mwfn_wf"),
23
+ "dump_635r",
24
+ )
25
+
26
+ def __init__(
27
+ self,
28
+ route,
29
+ gbs="",
30
+ gen="",
31
+ keep_chk=False,
32
+ stable="",
33
+ fchk=None,
34
+ **kwargs,
35
+ ):
36
+ super().__init__(**kwargs)
37
+
38
+ self.route = route.lower()
39
+ invalid_keywords = ("symmetry", "nosymm", "force", "opt", "freq", "irc")
40
+ invalid_kws_given = [kw for kw in invalid_keywords if kw in self.route]
41
+ assert (
42
+ not invalid_kws_given
43
+ ), f"Invalid keywords given in route: {invalid_kws_given}. Please remove them."
44
+ self.gbs = gbs
45
+ assert "@" not in gbs, "Give only the path to the .gbs file, " "without the @!"
46
+ self.gen = gen
47
+ self.keep_chk = keep_chk
48
+ self.stable = stable
49
+ self.fchk = fchk
50
+
51
+ keywords = {
52
+ kw: option
53
+ for kw, option in [self.parse_keyword(kw) for kw in self.route.split()]
54
+ }
55
+ exc_keyword = [key for key in "td tda cis".split() if key in keywords]
56
+ self.root = None
57
+ self.nstates = None
58
+ if exc_keyword:
59
+ self.exc_key = exc_keyword[0]
60
+ exc_dict = keywords[self.exc_key]
61
+ self.nstates = int(exc_dict["nstates"])
62
+ try:
63
+ self.root = int(exc_dict["root"])
64
+ except KeyError:
65
+ self.root = 1
66
+ self.log("No explicit root was specified! Using root=1 as default!")
67
+ # Collect remaining options if specified
68
+ self.exc_args = {
69
+ k: v for k, v in exc_dict.items() if k not in ("nstates", "root")
70
+ }
71
+ # Delete exc keyword, as we build it later on
72
+ self.route = re.sub(r"((?:td|cis|tda).+?(:?\s|$))", "", self.route)
73
+
74
+ self.to_keep = ("com", "fchk", "log", "dump_635r", "input.xyz")
75
+ if self.keep_chk:
76
+ self.to_keep += (".chk",)
77
+
78
+ self.fn_base = "gaussian16"
79
+ self.inp_fn = f"{self.fn_base}.com"
80
+ self.out_fn = f"{self.fn_base}.log"
81
+ self.chk_fn = f"{self.fn_base}.chk"
82
+ self.dump_base_fn = f"{self.fn_base}_rwfdump"
83
+
84
+ self.gaussian_input = """
85
+ %nproc={pal}
86
+ %mem={mem}MB
87
+ {chk_link0}
88
+ {add_link0}
89
+ #P {calc_type} {route} {exc}
90
+ # Symmetry=None {reuse_data}
91
+
92
+ title
93
+
94
+ {charge} {mult}
95
+ {coords}
96
+
97
+ {gen}{gbs}
98
+
99
+
100
+
101
+ """
102
+ self.gaussian_input = textwrap.dedent(self.gaussian_input)
103
+
104
+ self.parser_funcs = {
105
+ "force": self.parse_force,
106
+ "hessian": self.parse_hessian,
107
+ "stable": self.parse_stable,
108
+ "noparse": lambda path: None,
109
+ "double_mol": self.parse_double_mol,
110
+ }
111
+
112
+ self.base_cmd = self.get_cmd("cmd")
113
+ self.formchk_cmd = self.get_cmd("formchk")
114
+ self.unfchk_cmd = self.get_cmd("unfchk")
115
+ self.rwfdump_cmd = self.get_cmd("rwfdump")
116
+ self.log(
117
+ f"Using commands: g16={self.base_cmd}, formchk={self.formchk_cmd}, "
118
+ f"unfchk={self.unfchk_cmd}, rwfdump={self.rwfdump_cmd}"
119
+ )
120
+
121
+ def make_exc_str(self):
122
+ # Ground state calculation
123
+ if not self.root:
124
+ return ""
125
+ root = f"root={self.root}"
126
+ nstates = f"nstates={self.nstates}"
127
+ pair2str = lambda k, v: f"{k}" + (f"={v}" if v else "")
128
+ arg_str = ",".join([pair2str(k, v) for k, v in self.exc_args.items()])
129
+ exc_str = f"{self.exc_key}=({root},{nstates},{arg_str})"
130
+ return exc_str
131
+
132
+ def reuse_data(self, path):
133
+ # Nothing to reuse if no fchk or chk present
134
+ if (self.fchk is None) and not hasattr(self, "chk"):
135
+ return ""
136
+ new_chk = path / self.chk_fn
137
+ prev_fchk = new_chk.with_suffix(".fchk")
138
+ shutil.copy(self.fchk, prev_fchk)
139
+ cmd = f"{self.unfchk_cmd} {prev_fchk}".split()
140
+ subprocess.run(cmd, stdout=subprocess.PIPE, cwd=path)
141
+ self.log(f"Using MO guess from '{self.fchk}'.")
142
+
143
+ reuse_str = "guess=read"
144
+ # Also try to reuse information of previous TD calculation
145
+ # if self.nstates and hasattr(self, "chk"):
146
+ # DISABLED for now, as gaussian also resorts the states
147
+ # internally, which then messes up our internal numbering
148
+ # of states.
149
+ # shutil.copy(self.chk, new_chk)
150
+ # reuse_str += " td=read"
151
+ # self.log("Using td=read")
152
+
153
+ return reuse_str
154
+
155
+ def make_gbs_str(self):
156
+ if self.gbs:
157
+ return f"@{self.gbs}"
158
+ else:
159
+ return ""
160
+
161
+ def prepare_input(
162
+ self, atoms, coords, calc_type, did_stable=False, point_charges=None
163
+ ):
164
+ path = self.prepare_path(use_in_run=True)
165
+ xyz_str = self.prepare_xyz_string(atoms, coords)
166
+ with open(path / "input.xyz", "w") as handle:
167
+ handle.write(xyz_str)
168
+
169
+ coords = self.prepare_coords(atoms, coords)
170
+ kwargs = {
171
+ "pal": self.pal,
172
+ "mem": self.pal * self.mem,
173
+ "chk_link0": f"%chk={self.chk_fn}",
174
+ "add_link0": "",
175
+ "route": self.route,
176
+ # td/tda/cis(...)
177
+ "exc": self.make_exc_str(),
178
+ # guess=read td=read
179
+ "reuse_data": self.reuse_data(path),
180
+ "calc_type": calc_type,
181
+ "charge": self.charge,
182
+ "mult": self.mult,
183
+ "coords": coords,
184
+ "gbs": self.make_gbs_str(),
185
+ "gen": self.gen,
186
+ }
187
+ if calc_type == "double_mol":
188
+ update = {
189
+ "chk_link0": "",
190
+ "add_link0": "%KJob L302 1",
191
+ "route": self.route + " iop(3/33=1) geom=notest",
192
+ "calc_type": "",
193
+ "reuse_data": "",
194
+ "exc": "",
195
+ }
196
+ kwargs.update(update)
197
+ # stable and td/tda can't be used together, so we omit the
198
+ # td/tda string here
199
+ if "stable" in calc_type.lower():
200
+ kwargs["exc"] = ""
201
+ # after a stability analysis we already got a converged wavefuntion
202
+ # and we dont want to 'optimize' it any further
203
+ if did_stable:
204
+ kwargs["route"] += " scf=maxcycles=0"
205
+ if point_charges is not None:
206
+ kwargs["route"] += " charge"
207
+ point_charges = point_charges.copy()
208
+ point_charges[:, :3] *= BOHR2ANG
209
+ with io.StringIO() as handle:
210
+ np.savetxt(handle, point_charges, fmt="%16.10f")
211
+ pc_str = handle.getvalue()
212
+ # Append point charges to coords
213
+ kwargs["coords"] += "\n\n" + pc_str
214
+ inp = self.gaussian_input.format(**kwargs)
215
+ return inp
216
+
217
+ def make_fchk(self, path):
218
+ cmd = f"{self.formchk_cmd} {self.chk_fn}".split()
219
+ subprocess.run(cmd, stdout=subprocess.PIPE, cwd=path)
220
+ self.log("Created .fchk")
221
+
222
+ def run_rwfdump(self, path, rwf_index, chk_path=None):
223
+ if chk_path is None:
224
+ chk_path = path / self.chk_fn
225
+ dump_fn = path / f"{self.dump_base_fn}_dump_{rwf_index}"
226
+ cmd = f"rwfdump {chk_path} {dump_fn} {rwf_index}".split()
227
+ subprocess.run(cmd)
228
+ self.log(f"Dumped {rwf_index} from .chk.")
229
+ return dump_fn
230
+
231
+ def run_after(self, path):
232
+ chk_path = path / self.chk_fn
233
+ if not chk_path.exists():
234
+ self.log("No .chk file found.")
235
+ return
236
+ # Create the .fchk file so we can keep it and parse it later on.
237
+ self.make_fchk(path)
238
+ if self.track:
239
+ self.run_rwfdump(path, "635r")
240
+ self.nmos, self.roots = self.parse_log(path / self.out_fn)
241
+
242
+ def parse_keyword(self, text):
243
+ word = pp.Word(pp.alphanums + "-" + "/")
244
+
245
+ keyword = word.setResultsName("keyword")
246
+ equals = pp.Literal("=")
247
+ option = pp.Group(
248
+ word
249
+ + pp.Suppress(pp.Optional(equals))
250
+ + pp.Optional(word, default="")
251
+ + pp.Suppress(pp.Optional(","))
252
+ )
253
+ options = (
254
+ pp.Suppress(pp.Optional(equals))
255
+ + pp.Suppress(pp.Optional("("))
256
+ + pp.OneOrMore(option)
257
+ + pp.Suppress(pp.Optional(")"))
258
+ ).setResultsName("options")
259
+
260
+ parser = keyword + pp.Optional(options, default=[])
261
+
262
+ result = parser.parseString(text)
263
+ as_dict = result.asDict()
264
+ kw = as_dict["keyword"]
265
+ opt_dict = {key: value for key, value in as_dict["options"]}
266
+ return kw, opt_dict
267
+
268
+ @staticmethod
269
+ def parse_fchk(fchk_path, keys):
270
+ with open(fchk_path) as handle:
271
+ text = handle.read()
272
+
273
+ def to_float(s, loc, toks):
274
+ return float(toks[0])
275
+
276
+ # Matches -4.10693837E-16 and 1.60184209E-15
277
+ float_ = pp.Word(pp.nums + "+-E.").setParseAction(to_float)
278
+ # Start with Empty so we can progessively build the
279
+ # parser for all keys.
280
+ parser = pp.Empty()
281
+
282
+ def parser_for_key(key):
283
+ return pp.Group(
284
+ pp.Suppress(pp.SkipTo(key))
285
+ + key
286
+ + pp.restOfLine
287
+ + pp.ZeroOrMore(float_)
288
+ )
289
+
290
+ for key in keys:
291
+ parser += parser_for_key(key)
292
+ results = parser.parseString(text)
293
+ results_dict = {}
294
+ for key, res in zip(keys, results):
295
+ # This handles scalar entries like
296
+ # Total Energy [...] R -9.219940072302333E+01
297
+ if len(res) == 2:
298
+ results_dict[key] = float(res[-1].split()[-1])
299
+ # This handles matrices like
300
+ # Cartesian Gradient [...] R N= 9 \
301
+ # [Matrix entries]
302
+ if len(res) > 2:
303
+ results_dict[key] = np.array(res[2:])
304
+ return results_dict
305
+
306
+ def parse_all_energies(self, fchk=None):
307
+ if fchk is None:
308
+ fchk = self.fchk
309
+ with open(fchk) as handle:
310
+ text = handle.read()
311
+
312
+ float_ = r"([\d\-\+Ee\.]+)"
313
+ gs_re = re.compile(r"SCF Energy\s+R\s+" + float_)
314
+ gs_mobj = gs_re.search(text)
315
+ gs_energy = float(gs_mobj[1])
316
+ # cis_re = re.compile(r"CIS Energy\s+R\s+" + float_)
317
+ # cis_mobj = cis_re.search(text)
318
+ # cis_energy = float(cis_mobj[1])
319
+ etrans_re = re.compile(
320
+ r"ETran state values\s+R\s+N=\s+(\d+)([\d\-\.Ee\+\s]+)", re.DOTALL
321
+ )
322
+ etrans_mobj = etrans_re.search(text)
323
+ try:
324
+ etrans = etrans_mobj[2].strip().split()
325
+ etrans = np.array(etrans, dtype=float).reshape(-1, 16)
326
+ exc_energies = etrans[:, 0]
327
+ all_energies = np.zeros(exc_energies.size + 1)
328
+ all_energies[0] = gs_energy
329
+ all_energies[1:] = exc_energies
330
+ except TypeError:
331
+ all_energies = np.array((gs_energy,))
332
+ return all_energies
333
+
334
+ def store_and_track(self, results, func, atoms, coords, **prepare_kwargs):
335
+ if self.track:
336
+ self.store_overlap_data(atoms, coords)
337
+ if self.track_root():
338
+ # Redo the calculation with the updated root
339
+ results = func(atoms, coords, **prepare_kwargs)
340
+ results["all_energies"] = self.parse_all_energies()
341
+ return results
342
+
343
+ def get_energy(self, atoms, coords, **prepare_kwargs):
344
+ results = self.get_forces(atoms, coords, **prepare_kwargs)
345
+ results = self.store_and_track(
346
+ results, self.get_energy, atoms, coords, **prepare_kwargs
347
+ )
348
+ return results
349
+
350
+ def get_forces(self, atoms, coords, **prepare_kwargs):
351
+ did_stable = False
352
+ if self.stable:
353
+ is_stable = self.run_stable(atoms, coords)
354
+ self.log(f"Wavefunction is now stable: {is_stable}")
355
+ did_stable = True
356
+ inp = self.prepare_input(
357
+ atoms, coords, "force", did_stable=did_stable, **prepare_kwargs
358
+ )
359
+ kwargs = {
360
+ "calc": "force",
361
+ }
362
+ results = self.run(inp, **kwargs)
363
+ results = self.store_and_track(
364
+ results, self.get_forces, atoms, coords, **prepare_kwargs
365
+ )
366
+ return results
367
+
368
+ def get_hessian(self, atoms, coords, **prepare_kwargs):
369
+ inp = self.prepare_input(atoms, coords, "freq", **prepare_kwargs)
370
+ kwargs = {
371
+ "calc": "hessian",
372
+ }
373
+ results = self.run(inp, **kwargs)
374
+ results = self.store_and_track(
375
+ results, self.get_hessian, atoms, coords, **prepare_kwargs
376
+ )
377
+ return results
378
+
379
+ def run_stable(self, atoms, coords, **prepare_kwargs):
380
+ inp = self.prepare_input(atoms, coords, self.stable, **prepare_kwargs)
381
+ self.log(f"Running stability analysis with {self.stable}")
382
+ kwargs = {
383
+ "calc": "stable",
384
+ }
385
+ results = self.run(inp, **kwargs)
386
+ return results
387
+
388
+ def parse_stable(self, path):
389
+ log_path = path / self.out_fn
390
+ with open(log_path) as handle:
391
+ text = handle.read()
392
+ instab_re = "wavefunction.*instability.*"
393
+ instab_mobj = re.search(instab_re, text)
394
+ if instab_mobj:
395
+ self.log("Found instability!")
396
+ stable_re = "wavefunction is stable"
397
+ mobj = re.search(stable_re, text)
398
+ is_stable = bool(mobj)
399
+ return is_stable
400
+
401
+ def run_calculation(self, atoms, coords, **prepare_kwargs):
402
+ inp = self.prepare_input(atoms, coords, "", **prepare_kwargs)
403
+ kwargs = {
404
+ "calc": "noparse",
405
+ }
406
+ results = self.run(inp, **kwargs)
407
+ if self.track:
408
+ self.store_overlap_data(atoms, coords)
409
+ self.track_root()
410
+ self.log(
411
+ "This track_root() call is a bit superfluous as the "
412
+ "as the result is ignored :)"
413
+ )
414
+ return results
415
+
416
+ def run_double_mol_calculation(self, atoms, coords1, coords2):
417
+ self.log("Running double molecule calculation")
418
+ double_atoms = atoms + atoms
419
+ double_coords = np.hstack((coords1, coords2))
420
+ inp = self.prepare_input(double_atoms, double_coords, "double_mol")
421
+ kwargs = {
422
+ "calc": "double_mol",
423
+ "keep": False,
424
+ "inc_counter": False,
425
+ "run_after": False,
426
+ }
427
+ double_mol_ovlps = self.run(inp, **kwargs)
428
+ return double_mol_ovlps
429
+
430
+ def parse_tddft(self, path):
431
+ with open(path / self.out_fn) as handle:
432
+ text = handle.read()
433
+ td_re = r"Excited State\s*\d+:\s*[\.\w\?-]+\s*([\d\.-]+?)\s*eV"
434
+ matches = re.findall(td_re, text)
435
+ assert len(matches) == self.nstates
436
+ # Excitation energies in eV
437
+ exc_energies = np.array(matches, dtype=np.float64)
438
+ # Convert to Hartree
439
+ exc_energies /= AU2EV
440
+ return exc_energies
441
+
442
+ @file_or_str(".log", method=True)
443
+ def parse_log(self, text):
444
+ def parse(text, regex, func):
445
+ mobj = re.search(regex, text)
446
+ return func(mobj[1])
447
+
448
+ # Depending on wether we did the calculation with td=read or not
449
+ # roots will be at a different value. Without reading the CI coeffs
450
+ # from the checkpoint Gaussian will calculate four times as much roots
451
+ # as requested in the first iterations of the calculation. This will
452
+ # lead to a much higher number of expected number of CI-coefficients
453
+ # when parsing the 635r dump later on.
454
+ roots_re = r"Root\s+(\d+)"
455
+ roots = np.array(re.findall(roots_re, text), dtype=int).max()
456
+
457
+ # NBasis= 16 NAE= 12 NBE= 12 NFC= 6 NFV= 0
458
+ basis_re = "NBasis=(.+)NAE=(.+)NBE=(.+)NFC=(.+)NFV=(.+)"
459
+ basis_mobj = re.search(basis_re, text)
460
+ basis_funcs, alpha, beta, _, _ = [int(n) for n in basis_mobj.groups()]
461
+ a_occ = alpha
462
+ b_occ = beta
463
+ a_vir = basis_funcs - a_occ
464
+ b_vir = basis_funcs - b_occ
465
+ restricted = alpha == beta
466
+ act_re = "NROrb=(.*)NOA=(.*)NOB=(.*)NVA=(.*)NVB=(.*)"
467
+ act_mobj = re.search(act_re, text)
468
+ _, a_act, b_act, _, _ = [int(n) for n in act_mobj.groups()]
469
+ a_core = a_occ - a_act
470
+ b_core = b_occ - b_act
471
+
472
+ NMOs = namedtuple(
473
+ "NMOs",
474
+ "a_core, a_occ a_act a_vir " "b_core b_occ b_act b_vir " "restricted",
475
+ )
476
+
477
+ nmos = NMOs(
478
+ a_core, a_occ, a_act, a_vir, b_core, b_occ, b_act, b_vir, restricted
479
+ )
480
+ self.log(str(nmos))
481
+ return nmos, roots
482
+
483
+ def parse_635r_dump(self, dump_path, roots, nmos):
484
+ self.log(f"Parsing 635r dump '{dump_path}'")
485
+ with open(dump_path) as handle:
486
+ text = handle.read()
487
+ regex = r"read left to right\):\s*(.+)"
488
+ mobj = re.search(regex, text, re.DOTALL)
489
+ arr_str = mobj[1].replace("D", "E")
490
+ # Drop the first 12 items as they are always 0
491
+ tmp = np.array(arr_str.split()[12:], dtype=np.float64)
492
+
493
+ # the core electrons are frozen in TDDFT/TDA
494
+ expected = (nmos.a_act * nmos.a_vir + nmos.b_act * nmos.b_vir) * roots * 2
495
+ self.log(
496
+ f"Expecting {expected} ci coefficients in {dump_path}. "
497
+ f"There are {tmp.size} items (including eigenvalues)."
498
+ )
499
+ coeffs = tmp[:expected]
500
+ # 1. dim: X+Y, X-Y -> 2
501
+ # 2. dim: roots -> variable, usually higher than Nstates as gaussian
502
+ # calculates more roots in the first iteration of TDDFT
503
+ # 3. dim: alpha, beta -> 2
504
+ # The remainder depends on the number of alpha and beta electrons.
505
+ # The vectors are given for active orbs. x virt. obs that is the
506
+ # core orbitals are neglected.
507
+ if nmos.restricted:
508
+ XpY, XmY = coeffs.reshape(2, roots, 2, -1)
509
+ X = 0.5 * (XpY + XmY)
510
+ # Within a TDA calculation XpY and XmY are the same and
511
+ # Y will only contain zeros.
512
+ Y = XpY - X
513
+ # In the unrestricted case we can't use 2 for the 3. dimension anymore
514
+ # (same number of alpha and beta electrons). The sizes for the respective
515
+ # spins would be (a_act*a_vir) and (b_act*b_vir).
516
+ else:
517
+ raise Exception("Unrestricted not supported yet!")
518
+ # Shapes of X and Y
519
+ # 1. dim: roots (more than initially requested) -> variable
520
+ # 2. dim: alpha, beta -> 2
521
+ # 3. dim: act. x virt. -> variable
522
+
523
+ # xpy_fn = f"{dump_fn}_XpY"
524
+ # np.save(xpy_fn, XpY)
525
+ # xmy_fn = f"{dump_fn}_XmY"
526
+ # np.save(xmy_fn, XmY)
527
+ # x_fn = f"{dump_fn}_X"
528
+ # np.save(x_fn, X)
529
+
530
+ # As we only handle restricted calculations for now the alpha
531
+ # and beta part are identical. So we only keep the alpha part.
532
+ if self.nmos.restricted:
533
+ # Drop beta part and restrict to the requested states
534
+ X = X[: self.nstates, 0, :]
535
+ Y = Y[: self.nstates, 0, :]
536
+
537
+ X_full = np.zeros((self.nstates, nmos.a_occ, nmos.a_vir))
538
+ X_full[:, nmos.a_core :] = X.reshape(-1, nmos.a_act, nmos.a_vir)
539
+ Y_full = np.zeros((self.nstates, nmos.a_occ, nmos.a_vir))
540
+ Y_full[:, nmos.a_core :] = Y.reshape(-1, nmos.a_act, nmos.a_vir)
541
+
542
+ return X_full, Y_full
543
+
544
+ def prepare_overlap_data(self, path):
545
+ # Parse X eigenvector from 635r dump
546
+ X, Y = self.parse_635r_dump(self.dump_635r, self.roots, self.nmos)
547
+
548
+ # From http://gaussian.com/cis/, Examples tab, Normalization:
549
+ #
550
+ # 'For closed shell calculations, the sum of the squares of the
551
+ # expansion coefficients is normalized to total 1/2 (as the beta
552
+ # coefficients are not shown).'
553
+ #
554
+ # Right now we only deal with restricted calculatios, so alpha == beta
555
+ # and we ignore beta. So we are lacking a factor of sqrt(2). Another
556
+ # option would be to normalize all states to 1.
557
+ # ci_coeffs *= 2**0.5
558
+
559
+ # Parse mo coefficients from .fchk file and write a 'fake' turbomole
560
+ # mos file.
561
+ keys = (
562
+ "SCF Energy",
563
+ "Alpha Orbital Energies",
564
+ "Alpha MO coefficients",
565
+ "ETran state values",
566
+ )
567
+ scf_key, mo_energies_key, mo_key, exc_key = keys
568
+ fchk_dict = self.parse_fchk(self.fchk, keys=keys)
569
+ mo_energies = fchk_dict[mo_energies_key]
570
+ C = fchk_dict[mo_key].T
571
+ C = C.reshape(-1, mo_energies.size)
572
+
573
+ gs_energy = fchk_dict[scf_key]
574
+ exc_data = fchk_dict[exc_key].reshape(-1, 16)
575
+ exc_energies = exc_data[:, 0]
576
+ all_energies = np.zeros(len(exc_energies) + 1)
577
+ all_energies[0] = gs_energy
578
+ all_energies[1:] += exc_energies
579
+ return C, X, Y, all_energies
580
+
581
+ def parse_force(self, path):
582
+ results = {}
583
+ keys = ("SCF Energy", "Total Energy", "Cartesian Gradient")
584
+ fchk_path = Path(path) / f"{self.fn_base}.fchk"
585
+ fchk_dict = self.parse_fchk(fchk_path, keys)
586
+ results["energy"] = fchk_dict["SCF Energy"]
587
+ results["forces"] = -fchk_dict["Cartesian Gradient"]
588
+
589
+ if self.nstates:
590
+ # This sets the proper excited state energy in the
591
+ # results dict and also stores all energies.
592
+ exc_energies = self.parse_tddft(path)
593
+ # G16 root input is 1 based, so we substract 1 to get
594
+ # the right index here.
595
+ root_exc_en = exc_energies[self.root - 1]
596
+ gs_energy = fchk_dict["SCF Energy"]
597
+ # Add excitation energy to ground state energy.
598
+ results["energy"] += root_exc_en
599
+ # Create a new array including the ground state energy
600
+ # to save the energies of all states.
601
+ all_ens = np.full(len(exc_energies) + 1, gs_energy)
602
+ all_ens[1:] += exc_energies
603
+ results["all_energies"] = all_ens
604
+
605
+ return results
606
+
607
+ def parse_hessian(self, path):
608
+ keys = (
609
+ "Total Energy",
610
+ "Cartesian Gradient",
611
+ "Cartesian Force Constants",
612
+ )
613
+ fchk_path = Path(path) / f"{self.fn_base}.fchk"
614
+ fchk_dict = self.parse_fchk(fchk_path, keys=keys)
615
+ en_key, grad_key, hess_key = keys
616
+ grad = fchk_dict[grad_key]
617
+ tril_hess = fchk_dict[hess_key]
618
+ tril_inds = np.tril_indices(grad.size)
619
+ full_hessian = np.zeros((grad.size, grad.size))
620
+ full_hessian[tril_inds] = tril_hess
621
+ triu_inds = np.triu_indices(grad.size, k=1)
622
+ full_hessian[triu_inds] = full_hessian.T[triu_inds]
623
+ results = {
624
+ "energy": fchk_dict[en_key],
625
+ "forces": -grad,
626
+ "hessian": full_hessian,
627
+ }
628
+ return results
629
+
630
+ def parse_double_mol(self, path, out_fn=None):
631
+ def repl_double(s, loc, toks):
632
+ return toks[0].replace("D", "E")
633
+
634
+ if out_fn is None:
635
+ out_fn = self.out_fn
636
+
637
+ with open(path / out_fn) as handle:
638
+ text = handle.read()
639
+ # Number of basis functions in the double molecule
640
+ nbas = int(re.search(r"NBasis =\s*(\d+)", text)[1])
641
+ assert nbas % 2 == 0
642
+ # Gaussian prints columns of a triangular matrix including
643
+ # the diagonal
644
+ backup_white = pp.ParserElement.DEFAULT_WHITE_CHARS
645
+ pp.ParserElement.setDefaultWhitespaceChars(" \t")
646
+ int_ = pp.Suppress(pp.Word(pp.nums))
647
+
648
+ float_ = pp.Word(pp.nums + ".D+-").setParseAction(repl_double)
649
+ nl = pp.Suppress(pp.Literal("\n"))
650
+ header = pp.OneOrMore(int_) + nl
651
+ line = int_ + pp.OneOrMore(float_) + nl
652
+
653
+ block = pp.Group(header + pp.OneOrMore(~header + line))
654
+
655
+ parser = pp.Suppress(
656
+ pp.SkipTo("*** Overlap *** \n", include=True)
657
+ ) + pp.OneOrMore(block).setResultsName("blocks")
658
+
659
+ result = parser.parseFile(path / out_fn)
660
+ pp.ParserElement.setDefaultWhitespaceChars(backup_white)
661
+
662
+ # The full double molecule overlap matrix (square)
663
+ full_mat = np.zeros((nbas, nbas))
664
+
665
+ def get_block_inds(block_ind, nbas, cols=5):
666
+ """Returns the indices as required to assign the matrix
667
+ printed by Gaussian. Gaussian prints a lower triangle
668
+ matrix in blocks of five columns each."""
669
+ start_row = block_ind * cols
670
+ start_col = start_row
671
+ inds = list()
672
+ for row in range(start_row, nbas):
673
+ col = start_col
674
+ while (col <= row) and (col < start_row + cols):
675
+ inds.append((row, col))
676
+ col += 1
677
+ return inds
678
+
679
+ for i, block in enumerate(result["blocks"]):
680
+ rows, cols = zip(*get_block_inds(i, nbas))
681
+ full_mat[rows, cols] = block.asList()
682
+
683
+ nbas_single = nbas // 2
684
+ """The whole matrix consists of four blocks:
685
+ Original overlaps of molecule 1
686
+ b1 = full_mat[:nbas_single, :nbas_single]
687
+ Zero, as we only get the lower triangle
688
+ b2 = full_mat[:nbas_single, nbas_single:]
689
+ Double molecule overlaps between basis functions of
690
+ molecule 1 and molecule 2
691
+ b3 = full_mat[nbas_single:, :nbas_single]
692
+ Original overlaps of molecule 2
693
+ b4 = full_mat[nbas_single:, nbas_single:]
694
+ """
695
+ double_mol_ovlp = full_mat[nbas_single:, :nbas_single]
696
+ return double_mol_ovlp
697
+
698
+ def parse_charges(self, path=None):
699
+ if path is None and self.fchk is not None:
700
+ fchk_path = self.fchk
701
+ elif path is not None and path.endswith(".fchk"):
702
+ fchk_path = path
703
+ elif path is not None:
704
+ fchk_path = Path(path) / f"{self.fn_base}.fchk"
705
+
706
+ keys = ("Mulliken Charges",)
707
+ fchk_dict = self.parse_fchk(fchk_path, keys=keys)
708
+ charges = np.array(fchk_dict["Mulliken Charges"])
709
+
710
+ return charges
711
+
712
+ def get_chkfiles(self):
713
+ return {
714
+ "fchk": self.fchk,
715
+ }
716
+
717
+ def set_chkfiles(self, chkfiles):
718
+ try:
719
+ fchk = chkfiles["fchk"]
720
+ self.fchk = fchk
721
+ self.log(f"Set chkfile '{fchk}' as fchk.")
722
+ except KeyError:
723
+ self.log("Found no fchk information in chkfiles!")
724
+
725
+ def __str__(self):
726
+ return "Gaussian16 calculator"