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
pysisyphus/plot.py ADDED
@@ -0,0 +1,1031 @@
1
+ import argparse
2
+ from pathlib import Path
3
+ import sys
4
+ import textwrap
5
+
6
+ import h5py
7
+ import matplotlib
8
+ from matplotlib.animation import FuncAnimation
9
+ from matplotlib.patches import Rectangle
10
+ import matplotlib.image as mpimg
11
+ import matplotlib.pyplot as plt
12
+ import numpy as np
13
+ from scipy.interpolate import splrep, splev
14
+
15
+ from pysisyphus.constants import AU2KJPERMOL, AU2KCALPERMOL, AU2EV
16
+ from pysisyphus.config import OUT_DIR_DEFAULT
17
+ from pysisyphus.dynamics import Gaussian
18
+ from pysisyphus.io import parse_xyz
19
+ from pysisyphus.peakdetect import peakdetect
20
+ from pysisyphus.wrapper.jmol import render_cdd_cube
21
+
22
+
23
+ CDD_PNG_FNS = "cdd_png_fns"
24
+ """
25
+ Default to kJ mol⁻¹. DE_LABEL and get_en_conv will be overwritten when
26
+ run() is executed. Both are still defined here, to make the functions
27
+ from plot.py importable.
28
+ """
29
+ DE_LABEL = r"$\Delta$E / kJ mol⁻¹"
30
+
31
+
32
+ def get_en_conv():
33
+ return AU2KJPERMOL, "kJ mol⁻¹"
34
+
35
+
36
+ def get_force_unit(coord_type):
37
+ force_unit = "$E_h$ Bohr⁻¹"
38
+ if coord_type != "cart":
39
+ force_unit += " (rad)⁻¹"
40
+ return force_unit
41
+
42
+
43
+ def spline_plot_cycles(cart_coords, energies):
44
+ num_cycles = energies.shape[1]
45
+
46
+ fig, ax = plt.subplots()
47
+ colors = matplotlib.cm.Greys(np.linspace(0.2, 1, num=num_cycles))
48
+ # for cycle, color in zip(energies, colors):
49
+ for i, (cycle, color) in enumerate(zip(energies, colors)):
50
+ ax.plot(cycle, "o-", color=color)
51
+ ax.set_title("COS image energies")
52
+
53
+ kwargs = {
54
+ "ls": ":",
55
+ "color": "darkgrey",
56
+ }
57
+ # Try to spline the last cycle to get an estimate for the spliend HEI
58
+ try:
59
+ last_cycle = energies[-1]
60
+ num_images = last_cycle.size
61
+ spl = splrep(np.arange(num_images), last_cycle)
62
+ # Calculate interpolated values
63
+ x2 = np.linspace(0, num_images, 100)
64
+ y2 = splev(x2, spl)
65
+ # Only consider maxima
66
+ peak_inds, _ = peakdetect(y2, lookahead=2)
67
+ if not peak_inds:
68
+ ax.plot(x2, y2)
69
+ else:
70
+ peak_inds = np.array(peak_inds)[:, 0].astype(int)
71
+ peak_xs = x2[peak_inds]
72
+ peak_ys = y2[peak_inds]
73
+ ax.plot(x2, y2, peak_xs, peak_ys, "x")
74
+ for px, py in zip(peak_xs, peak_ys):
75
+ ax.axhline(y=py, **kwargs)
76
+ line = matplotlib.lines.Line2D([px, px], [0, py], **kwargs)
77
+ ax.add_line(line)
78
+ except TypeError:
79
+ print("Not enough images for splining!")
80
+
81
+ # Always draw a line at the minimum y=0
82
+ ax.axhline(y=0, **kwargs)
83
+ ax.set_xlabel("Image")
84
+ _, en_unit = get_en_conv()
85
+ ax.set_ylabel(DE_LABEL)
86
+
87
+ return fig, ax
88
+
89
+
90
+ def plot_cycle(cart_coords, energies):
91
+ # Plot last_cycle
92
+ fig, ax = plt.subplots()
93
+ last_energies = energies[-1].copy()
94
+ xs = np.arange(len(last_energies))
95
+ ax.plot(xs, last_energies, "o-")
96
+ ax.set_xlabel("Image")
97
+ ax.set_ylabel(DE_LABEL)
98
+ ax.set_title(f"COS image energies, (last) cycle {len(energies)-1}")
99
+
100
+ first_image_en = last_energies[0]
101
+ last_image_en = last_energies[-1]
102
+ max_en_ind = np.nanargmax(last_energies)
103
+ max_en = last_energies[max_en_ind]
104
+ nans = np.isnan(last_energies).sum()
105
+ if nans:
106
+ print(
107
+ f"The COS seems to be not fully grown (yet), {nans} energies are missing."
108
+ )
109
+ print(
110
+ "Barrier heights using actual energies (not splined) from "
111
+ f"cycle {energies.shape[0]-1}."
112
+ )
113
+ print(f"\tHighest energy image (HEI) at index {max_en_ind} (0-based)")
114
+
115
+ first_barr = max_en - first_image_en
116
+ _, en_unit = get_en_conv()
117
+ print(f"\tBarrier between first image and HEI: {first_barr:.1f} {en_unit}")
118
+ last_barr = max_en - last_image_en
119
+ print(f"\tBarrier between last image and HEI: {last_barr:.1f} {en_unit}")
120
+
121
+ return fig, ax
122
+
123
+
124
+ def anim_cos(cart_coords, energies):
125
+ num_cycles = cart_coords.shape[0]
126
+
127
+ # Also do an animation
128
+ min_ = np.nanmin(energies)
129
+ max_ = np.nanmax(energies)
130
+
131
+ coord_diffs = np.linalg.norm(cart_coords - cart_coords[0][0], axis=2)
132
+ fig, ax = plt.subplots()
133
+
134
+ # Initial energies
135
+ lines = ax.plot(coord_diffs[0], energies[0], "o-")
136
+ y_max = max_ - min_
137
+ ax.set_ylim(0, y_max)
138
+ ax.set_xlabel("Coordinate differences / Bohr")
139
+ ax.set_ylabel(DE_LABEL)
140
+
141
+ def update_func(i):
142
+ fig.suptitle("Cycle {}".format(i))
143
+ lines[0].set_xdata(coord_diffs[i])
144
+ lines[0].set_ydata(energies[i])
145
+
146
+ def animate():
147
+ animation = FuncAnimation(
148
+ fig,
149
+ update_func,
150
+ frames=num_cycles,
151
+ interval=250,
152
+ )
153
+ return animation
154
+
155
+ anim = animate()
156
+ return anim, fig, ax
157
+
158
+
159
+ def load_h5(h5_fn, h5_group, datasets=None, attrs=None):
160
+ if datasets is None:
161
+ datasets = list()
162
+
163
+ if attrs is None:
164
+ attrs = list()
165
+
166
+ with h5py.File(h5_fn, "r") as handle:
167
+ group = handle[h5_group]
168
+
169
+ atoms = group.attrs["atoms"]
170
+ cur_cycle = group.attrs["cur_cycle"]
171
+ coord_size = group.attrs["coord_size"]
172
+ num_cycles = cur_cycle + 1
173
+
174
+ image_nums = group["image_nums"][:num_cycles].astype(int)
175
+ image_inds = group["image_inds"][:num_cycles].astype(int)
176
+
177
+ _datasets = dict()
178
+ for ds in datasets:
179
+ try:
180
+ _datasets[ds] = group[ds][:num_cycles]
181
+ except KeyError:
182
+ print(f"Could not load dataset '{ds}' from HDF5 file.")
183
+
184
+ _attrs = dict()
185
+ for a in attrs:
186
+ try:
187
+ _attrs[a] = group.attrs[a]
188
+ except KeyError:
189
+ print(f"Could not load attribute '{a}' from HDF5 file.")
190
+
191
+ en_conv, _ = get_en_conv()
192
+ if "energies" in _datasets:
193
+ ens = _datasets["energies"]
194
+ ens -= ens.min()
195
+ ens *= en_conv
196
+
197
+ try:
198
+ # We can't use coord_size because coord_type may be != cart and then
199
+ # coord_size gives the number of internals.
200
+ cart_shape = (num_cycles, -1, 3 * len(atoms))
201
+ _datasets["cart_coords"] = _datasets["cart_coords"].reshape(cart_shape)
202
+ except KeyError:
203
+ pass
204
+
205
+ try:
206
+ # Here we can use coord_size because forces will always be in the same
207
+ # coordinate system as the actual coordinates.
208
+ _datasets["forces"] = _datasets["forces"].reshape((num_cycles, -1, coord_size))
209
+ except KeyError:
210
+ pass
211
+
212
+ def sort_by_image(arr):
213
+ by_image = np.full_like(arr, np.nan)
214
+ for cyc, (img_ind, img_num) in enumerate(zip(image_inds, image_nums)):
215
+ img_ind = img_ind[:img_num]
216
+ by_image[cyc, img_ind] = arr[cyc, :img_num]
217
+ return by_image
218
+
219
+ for k, v in _datasets.items():
220
+ _datasets[k] = sort_by_image(v)
221
+
222
+ # Also copy requested attributes into dictionary
223
+ _datasets.update(_attrs)
224
+
225
+ return _datasets
226
+
227
+
228
+ def plot_cos_energies(h5_fn="optimization.h5", h5_group="opt"):
229
+ results = load_h5(
230
+ h5_fn, h5_group, datasets=("cart_coords", "energies"), attrs=("is_cos",)
231
+ )
232
+ cart_coords = results["cart_coords"]
233
+ energies = results["energies"]
234
+
235
+ assert results["is_cos"]
236
+
237
+ # Splined last cycle and plot of all cycles
238
+ fig_, ax_ = spline_plot_cycles(
239
+ cart_coords, energies
240
+ ) # lgtm [py/unused-local-variable]
241
+ # Plot last cycle
242
+ fig_last, ax_last = plot_cycle(
243
+ cart_coords, energies
244
+ ) # lgtm [py/unused-local-variable]
245
+ # Plot animation
246
+ anim, fig_anim, ax_anim = anim_cos(
247
+ cart_coords, energies
248
+ ) # lgtm [py/unused-local-variable]
249
+
250
+ plt.show()
251
+
252
+
253
+ def plot_cos_forces(h5_fn="optimization.h5", h5_group="opt", last=15):
254
+ results = load_h5(
255
+ h5_fn,
256
+ h5_group,
257
+ datasets=("energies", "forces",),
258
+ attrs=("is_cos", "coord_type", "max_force_thresh", "rms_force_thresh"),
259
+ )
260
+ cycles = len(results["energies"])
261
+ last_cycles = np.arange(cycles)[-last:]
262
+ energies = results["energies"][-last:]
263
+ forces = results["forces"][-last:]
264
+ coord_type = results["coord_type"]
265
+
266
+ assert results["is_cos"]
267
+
268
+ last_axis = forces.ndim - 1
269
+ max_ = np.nanmax(np.abs(forces), axis=last_axis)
270
+ rms = np.sqrt(np.mean(forces ** 2, axis=last_axis))
271
+ hei_indices = energies.argmax(axis=1)
272
+ force_unit = get_force_unit(coord_type)
273
+
274
+ fmt = ".6f"
275
+ print("HEI forces in E_h / a0")
276
+ for i, hei_index in enumerate(hei_indices):
277
+ cycle = last_cycles[i]
278
+ hei_max = max_[i, hei_index]
279
+ hei_rms = rms[i, hei_index]
280
+ print(f"\tCycle {cycle:03d}: max(forces)={hei_max:{fmt}}, rms(forces)={hei_rms:{fmt}}")
281
+
282
+ fig, (ax0, ax1) = plt.subplots(sharex=True, nrows=2)
283
+
284
+ def plot(ax, data, title):
285
+ num = data.shape[0]
286
+ alphas = np.linspace(0.125, 1, num=num)
287
+ colors = matplotlib.cm.Greys(np.linspace(0, 1, num=num))
288
+ colors[-1] = (1., 0., 0., 1.) # use red for latest cycle
289
+ for row, color, alpha in zip(data, colors, alphas):
290
+ ax.plot(row, "o-", color=color, alpha=alpha)
291
+ ax.set_ylabel(force_unit)
292
+ ax.set_yscale("log")
293
+ if title:
294
+ ax.set_title(title)
295
+
296
+ plot(ax0, max_, "max(perp. forces)")
297
+ plot(ax1, rms, "rms(perp. forces)")
298
+ try:
299
+ ax0.axhline(results["max_force_thresh"], ls="--", c="k", label="Conv. thresh.")
300
+ ax0.legend()
301
+ ax1.axhline(results["rms_force_thresh"], ls="--", c="k", label="Conv. thresh.")
302
+ ax1.legend()
303
+ except KeyError:
304
+ print("Could not find max/rms entries for force threshold on HDF5 file!")
305
+ ax1.set_xlabel("Image")
306
+
307
+ plt.tight_layout()
308
+ plt.show()
309
+
310
+
311
+ def plot_all_energies(h5):
312
+ with h5py.File(h5) as handle:
313
+ energies = handle["all_energies"][:]
314
+ roots = handle["roots"][:]
315
+ flips = handle["root_flips"][:]
316
+ ovlp_type = handle.attrs["ovlp_type"]
317
+ ovlp_with = handle.attrs["ovlp_with"]
318
+ print(f"Overlap type: '{ovlp_type}', overlaps with: '{ovlp_with}'.")
319
+ print(f"Found a total of {len(roots)} steps.")
320
+ print(f"{flips} root flips occured.")
321
+
322
+ energies -= energies.min()
323
+ energies *= AU2EV
324
+
325
+ # Don't plot steps where flips occured
326
+ # energies = np.concatenate((energies[0][None,:], energies[1:,:][~flips]), axis=0)
327
+ energies_ = list()
328
+ roots_ = list()
329
+ steps = list()
330
+ for i, root_flip in enumerate(flips[:-1]):
331
+ if root_flip:
332
+ print(f"Root flip occured between {i} and {i+1}.")
333
+ continue
334
+ print(f"Using step {i}")
335
+ energies_.append(energies[i])
336
+ roots_.append(roots[i])
337
+ steps.append(i)
338
+ # Don't append last step if a root flip occured there.
339
+ if not flips[-1]:
340
+ energies_.append(energies[-1])
341
+ roots_.append(roots[-1])
342
+ steps.append(i + 1)
343
+ else:
344
+ print("Root flip occured in the last step. Not showing the last step.")
345
+
346
+ energies = np.array(energies_)
347
+ roots = np.array(roots_)
348
+
349
+ fig, ax = plt.subplots()
350
+ for i, state in enumerate(energies.T):
351
+ ax.plot(steps, state, "o-", label=f"State {i:03d}")
352
+ ax.legend(loc="lower center", ncol=3)
353
+ ax.set_title(f"'{ovlp_type}' overlaps with '{ovlp_with}'")
354
+ ax.set_xlabel("Cycle")
355
+ ax.set_ylabel(r"$\Delta$E / eV")
356
+ root_ens = [s[r] for s, r in zip(energies, roots)]
357
+ ax.plot(steps, root_ens, "--k")
358
+ plt.tight_layout()
359
+ plt.show()
360
+
361
+
362
+ def plot_md(h5_group="run"):
363
+ with h5py.File("md.h5", "r") as handle:
364
+ group = handle[h5_group]
365
+
366
+ steps = group["step"][:]
367
+ ens = group["energy_tot"][:]
368
+ ens_conserved = group["energy_conserved"][:]
369
+ Ts = group["T"][:]
370
+ T_avgs = group["T_avg"][:]
371
+ # coords = group["cart_coords"][:]
372
+ # velocities = group["velocity"][:]
373
+
374
+ dt = group.attrs["dt"]
375
+ T_target = group.attrs["T_target"]
376
+
377
+ _, (ax0, ax1, ax2) = plt.subplots(nrows=3, sharex=True)
378
+ dts = steps * dt
379
+ en_conv, en_unit = get_en_conv()
380
+ ens *= en_conv
381
+ mean = ens.mean()
382
+ ens -= mean
383
+ ax0.plot(dts, ens)
384
+ ax0.axhline(0, ls="--", c="k")
385
+ ax0.set_ylabel(r"$E - \overline{E}$ / " + en_unit)
386
+ ax0.set_title("Energy")
387
+
388
+ ens_conserved *= en_conv
389
+ mean_conserved = ens_conserved.mean()
390
+ ens_conserved -= mean_conserved
391
+ ax1.plot(dts, ens_conserved)
392
+ ax1.axhline(0, ls="--", c="k")
393
+ ax1.set_ylabel(r"$E_\\mathrm{cons.} - \overline{E}_\\mathrm{cons.}$ / {en_unit}")
394
+ ax1.set_title("Conserved quantity")
395
+
396
+ ax2.plot(dts, Ts, label="Current")
397
+ ax2.plot(dts, T_avgs, ls="--", c="orange", label="Average")
398
+ ax2.axhline(T_target, ls="--", c="k", label="Target")
399
+ ax2.legend()
400
+ ax2.set_title(f"mean(T) = {Ts.mean():.2f} K")
401
+ ax2.set_xlabel(r"$\Delta t$ / fs")
402
+ ax2.set_ylabel("T / K")
403
+
404
+ plt.tight_layout()
405
+ plt.show()
406
+
407
+
408
+ def plot_gau(gau_fns, num=50):
409
+ print("Assuming constant Gaussian s & w!")
410
+
411
+ assert (
412
+ 0 < len(gau_fns) < 3
413
+ ), "Currently, only plotting of 1 or 2 collective variables is possible!"
414
+
415
+ gaussians = list()
416
+ centers = list()
417
+ grids = list()
418
+ for gau_fn in gau_fns:
419
+ gau_data = np.loadtxt(gau_fn)
420
+ gau_centers = gau_data[:, 3]
421
+ _, s, w, _ = gau_data[0]
422
+ gau = Gaussian(w=w, s=s)
423
+ print(f"Successfully loaded '{gau_fn}' with w={w:.6f}, s={s:.6f}")
424
+ gaussians.append(gau)
425
+ min_ = gau_centers.min()
426
+ max_ = gau_centers.max()
427
+ diff = abs(max_ - min_)
428
+ centers.append(gau_centers)
429
+ grid = np.linspace(min_, max_, num=num)
430
+ grids.append(grid)
431
+ print(f"\tmin={min_:.4f}, max={max_:.4f}, Δ={diff:.4f}")
432
+
433
+ def eval_gaussians(coords):
434
+ value = 0.0
435
+ for i, gau in enumerate(gaussians):
436
+ value += gau.value(coords=coords[i], x0=centers[i])
437
+ return value
438
+
439
+ fig, ax = plt.subplots()
440
+
441
+ en_conv, en_unit = get_en_conv()
442
+ if len(gau_fns) == 1:
443
+ grid = grids[0]
444
+ ens = -np.array([eval_gaussians(x) for x in grid[:, None]]) * en_conv
445
+ ens -= ens.min()
446
+ ax.plot(grid, ens)
447
+ ax.set_xlabel(f"CV0, {gau_fns[0]}")
448
+ ax.set_ylabel(rf"$\Delta F$ / {en_unit}")
449
+ elif len(gau_fns) == 2:
450
+ grid0, grid1 = grids
451
+ X, Y = np.meshgrid(grid0, grid1)
452
+ xy_flat = np.stack((X.flatten(), Y.flatten()), axis=1)
453
+ ens = (
454
+ -np.array([eval_gaussians(xy) for xy in xy_flat]).reshape(num, num)
455
+ * en_conv
456
+ )
457
+ ens -= ens.min()
458
+ levels = np.linspace(ens.min(), 0.75 * ens.max(), num=15)
459
+ # contour = ax.contour(X, Y, ens, levels=levels)
460
+ _ = ax.contourf(X, Y, ens, levels=levels)
461
+ # plt.clabel(contour, inline=True, fmt="%1.1f", fontsize=10, colors="white", levels=levels)
462
+ ax.set_xlabel(f"CV0, {gau_fns[0]}")
463
+ ax.set_ylabel(f"CV1, {gau_fns[1]}")
464
+ plt.show()
465
+
466
+
467
+ def plot_overlaps(h5, thresh=0.1):
468
+ with h5py.File(h5, "r") as handle:
469
+ overlaps = handle["overlap_matrices"][:]
470
+ roots = handle["roots"][:]
471
+ calculated_roots = handle["calculated_roots"][:]
472
+ ref_cycles = handle["ref_cycles"][:]
473
+ ref_roots = handle["ref_roots"][:]
474
+ try:
475
+ ovlp_type = handle.attrs["ovlp_type"]
476
+ ovlp_with = handle.attrs["ovlp_with"]
477
+ # The old way is handled below. Newer pysis versions store ovlp_type/ovlp_with
478
+ # in attrs.
479
+ except KeyError:
480
+ ovlp_type = handle["ovlp_type"][()].decode()
481
+ ovlp_with = handle["ovlp_with"][()].decode()
482
+ try:
483
+ cdd_img_fns = handle["cdd_imgs"][:]
484
+ except KeyError:
485
+ print(f"Couldn't find image data in '{h5}'.")
486
+ try:
487
+ with open(CDD_PNG_FNS) as handle:
488
+ cdd_img_fns = handle.read().split()
489
+ print(f"Found image data in '{CDD_PNG_FNS}'")
490
+ except FileNotFoundError:
491
+ cdd_img_fns = None
492
+ cdd_imgs = None
493
+ if cdd_img_fns is not None:
494
+ try:
495
+ cdd_imgs = [mpimg.imread(fn) for fn in cdd_img_fns]
496
+ except FileNotFoundError:
497
+ png_paths = [Path(fn.decode()).name for fn in cdd_img_fns]
498
+ cdd_imgs = [mpimg.imread(fn) for fn in png_paths]
499
+ print(f"Found rendered {len(cdd_imgs)} CDD images.")
500
+
501
+ overlaps[np.abs(overlaps) < thresh] = np.nan
502
+ print(f"Overlap type: {ovlp_type}")
503
+ print(f"Overlap with: {ovlp_with}")
504
+ print(f"Found {len(overlaps)} overlap matrices.")
505
+ print(f"Roots: {roots}")
506
+ print(f"Reference cycles: {ref_cycles}")
507
+ print(f"Reference roots: {ref_roots}")
508
+ print()
509
+ print("Key-bindings:")
510
+ print("i: switch between current and first cycle.")
511
+ print("e: switch between current and last cycle.")
512
+
513
+ fig, ax = plt.subplots()
514
+
515
+ n_states = overlaps[0].shape[0]
516
+
517
+ def draw(i):
518
+ fig.clf()
519
+ if cdd_imgs is not None:
520
+ ax = fig.add_subplot(121)
521
+ ax1 = fig.add_subplot(122)
522
+ else:
523
+ ax = fig.add_subplot(111)
524
+ ax1 = None
525
+ o = np.abs(overlaps[i])
526
+ ax.imshow(o, vmin=0, vmax=1)
527
+ ax.grid(color="#CCCCCC", linestyle="--", linewidth=1)
528
+ ax.set_xticks(np.arange(n_states, dtype=np.int))
529
+ ax.set_yticks(np.arange(n_states, dtype=np.int))
530
+ # set_ylim is needed, otherwise set_yticks drastically shrinks the plot
531
+ ax.set_ylim(n_states - 0.5, -0.5)
532
+ ax.set_xlabel("new roots")
533
+ ax.set_ylabel("reference roots")
534
+ for (l, k), value in np.ndenumerate(o):
535
+ if np.isnan(value):
536
+ continue
537
+ value_str = f"{abs(value):.2f}"
538
+ ax.text(k, l, value_str, ha="center", va="center")
539
+ j, k = ref_cycles[i], i + 1
540
+ ref_root = ref_roots[i]
541
+ ref_ind = ref_root - 1
542
+ if ovlp_type == "wf":
543
+ ref_ind += 1
544
+ old_root = calculated_roots[i + 1]
545
+ new_root = roots[i + 1]
546
+ ref_overlaps = o[ref_ind]
547
+ argmax = np.nanargmax(ref_overlaps)
548
+ xy = (argmax - 0.5, ref_ind - 0.5)
549
+ highlight = Rectangle(xy, 1, 1, fill=False, color="red", lw="4")
550
+ ax.add_artist(highlight)
551
+ if ax1:
552
+ ax1.imshow(cdd_imgs[i])
553
+ fig.suptitle(
554
+ f"overlap {i:03d}\n"
555
+ f"{ovlp_type} overlap between {j:03d} and {k:03d}\n"
556
+ f"old root: {old_root}, new root: {new_root}"
557
+ )
558
+ fig.canvas.draw()
559
+
560
+ draw(0)
561
+
562
+ i = 0
563
+ i_backup = i
564
+ i_last = len(overlaps) - 1
565
+
566
+ def press(event):
567
+ nonlocal i
568
+ nonlocal i_backup
569
+ if event.key == "left":
570
+ i = max(0, i - 1)
571
+ elif event.key == "right":
572
+ i = min(i_last, i + 1)
573
+ # Switch between current and first cycle
574
+ elif event.key == "i":
575
+ if i == 0:
576
+ # Restore previous cycle
577
+ i = i_backup
578
+ else:
579
+ # Save current i and jump to the first cycle/image
580
+ i_backup = i
581
+ i = 0
582
+ # Switch between current and last cycle
583
+ elif event.key == "e":
584
+ if i == i_last:
585
+ # Restore previous cycle
586
+ i = i_backup
587
+ else:
588
+ # Save current i and jump to the first cycle/image
589
+ i_backup = i
590
+ i = i_last
591
+ else:
592
+ return
593
+ draw(i)
594
+
595
+ fig.canvas.mpl_connect("key_press_event", press)
596
+
597
+ plt.tight_layout()
598
+ plt.show()
599
+
600
+
601
+ def render_cdds(h5):
602
+ with h5py.File(h5) as handle:
603
+ cdd_cubes = handle["cdd_cubes"][:].astype(str)
604
+ orient = handle["orient"][()].decode()
605
+ cdd_cubes = [Path(cub) for cub in cdd_cubes]
606
+ print(f"Found {len(cdd_cubes)} CDD cube filenames in {h5}")
607
+ # Check if cubes exist
608
+ non_existant_cubes = [cub for cub in cdd_cubes if not cub.exists()]
609
+ existing_cubes = [str(cub) for cub in set(cdd_cubes) - set(non_existant_cubes)]
610
+ if any(non_existant_cubes):
611
+ print("Couldn't find cubes:")
612
+ print("\n".join(["\t" + str(cub) for cub in non_existant_cubes]))
613
+ print("Dropping full path and looking only for cube names.")
614
+ cub_names = [cub.name for cub in non_existant_cubes]
615
+ _ = [cub for cub in cub_names if Path(cub).exists()]
616
+ existing_cubes = existing_cubes + _
617
+ cdd_cubes = existing_cubes
618
+
619
+ # Create list of all final PNG filenames
620
+ png_fns = [Path(cube).with_suffix(".png") for cube in cdd_cubes]
621
+ # Check which cubes are already rendered
622
+ png_stems = [png.stem for png in png_fns if png.exists()]
623
+ print(f"{len(png_stems)} cubes seem already rendered.")
624
+
625
+ # Only render cubes that are not yet rendered
626
+ cdd_cubes = [cube for cube in cdd_cubes if Path(cube).stem not in png_stems]
627
+ print(f"Rendering {len(cdd_cubes)} CDD cubes.")
628
+
629
+ for i, cube in enumerate(cdd_cubes):
630
+ print(f"Rendering cube {i+1:03d}/{len(cdd_cubes):03d}")
631
+ _ = render_cdd_cube(cube, orient=orient)
632
+ joined = "\n".join([str(fn) for fn in png_fns])
633
+ with open(CDD_PNG_FNS, "w") as handle:
634
+ handle.write(joined)
635
+ print("Rendered PNGs:")
636
+ print(joined)
637
+ print(f"Wrote list of rendered PNGs to '{CDD_PNG_FNS}'")
638
+
639
+
640
+ def plot_afir(h5_fn="afir.h5", h5_group="afir"):
641
+
642
+ h5_fns = (h5_fn, Path(OUT_DIR_DEFAULT) / h5_fn)
643
+ for h5_fn in h5_fns:
644
+ print(f"Trying to open '{h5_fn}' ... ", end="")
645
+ try:
646
+ with h5py.File(h5_fn, "r") as handle:
647
+ group = handle[h5_group]
648
+ cycles = group.attrs["cur_cycle"] + 1
649
+ afir_ens = group["energy"][:cycles]
650
+ true_ens = group["true_energy"][:cycles]
651
+ afir_forces = group["forces"][:cycles]
652
+ true_forces = group["true_forces"][:cycles]
653
+ print("done.")
654
+ break
655
+ except FileNotFoundError:
656
+ print("file not found.")
657
+ continue
658
+
659
+
660
+ en_conv, en_unit = get_en_conv()
661
+ afir_ens *= en_conv
662
+ afir_ens -= afir_ens.min()
663
+ true_ens *= en_conv
664
+ true_ens -= true_ens.min()
665
+ afir_forces = np.linalg.norm(afir_forces, axis=1)
666
+ true_forces = np.linalg.norm(true_forces, axis=1)
667
+
668
+ fig, (en_ax, forces_ax) = plt.subplots(nrows=2, sharex=True)
669
+
670
+ style1 = "r--"
671
+ style2 = "g--"
672
+ style3 = "bo-"
673
+
674
+ l1 = en_ax.plot(afir_ens, style1, label="AFIR")
675
+ l2 = en_ax.plot(true_ens, style2, label="True")
676
+ en_ax2 = en_ax.twinx()
677
+ l3 = en_ax2.plot(true_ens + afir_ens, style3, label="Sum")
678
+ en_ax2.tick_params(axis="y", labelcolor="blue")
679
+
680
+ lines = l1 + l2 + l3
681
+ labels = [l.get_label() for l in lines]
682
+ en_ax.legend(lines, labels, loc=0)
683
+
684
+ en_ax.set_title("Energies")
685
+ en_ax.set_ylabel(DE_LABEL)
686
+
687
+ forces_ax.set_title("||Forces||")
688
+ l1 = forces_ax.plot(afir_forces, style1, label="AFIR")
689
+ l2 = forces_ax.plot(true_forces, style2, label="True")
690
+
691
+ forces_ax2 = forces_ax.twinx()
692
+ l3 = forces_ax2.plot(true_forces + afir_forces, style3, label="Sum")
693
+ forces_ax2.tick_params(axis="y", labelcolor="blue")
694
+
695
+ lines = l1 + l2 + l3
696
+ labels = [l.get_label() for l in lines]
697
+ forces_ax.legend(lines, labels, loc=0)
698
+ forces_ax.set_xlabel("Cycle")
699
+ forces_ax.set_ylabel("$E_h$ Bohr⁻¹")
700
+
701
+ peak_inds, _ = peakdetect(true_ens, lookahead=2)
702
+ if peak_inds:
703
+ print("Peaks:")
704
+ print(f"\tCycle: Energy / {en_unit}")
705
+ print()
706
+ for at, energy in peak_inds:
707
+ print(f"\t{at}: {energy:.2f}")
708
+
709
+ try:
710
+ peak_xs, peak_ys = zip(*peak_inds)
711
+ highest = np.argmax(peak_ys)
712
+
713
+ en_ax.scatter(peak_xs, peak_ys, s=100, marker="X", c="k", zorder=10)
714
+ en_ax.scatter(
715
+ peak_xs[highest], peak_ys[highest], s=150, marker="X", c="k", zorder=10
716
+ )
717
+ en_ax.axvline(peak_xs[highest], c="k", ls="--")
718
+ forces_ax.axvline(peak_xs[highest], c="k", ls="--")
719
+ except ValueError as err:
720
+ print("Peak-detection failed!")
721
+
722
+ # fig.legend(loc="upper right")
723
+ plt.tight_layout()
724
+ plt.show()
725
+
726
+
727
+ def parse_args(args):
728
+ parser = argparse.ArgumentParser()
729
+ parser.add_argument("--h5_fn", default="overlap_data.h5")
730
+ parser.add_argument("--h5_group", default="opt", help="HDF5 group to plot.")
731
+ parser.add_argument("--orient", default="")
732
+ parser.add_argument(
733
+ "--kcal", action="store_true", help="Use kcal mol⁻¹ instead of kJ mol⁻¹."
734
+ )
735
+
736
+ group = parser.add_mutually_exclusive_group(required=True)
737
+ group.add_argument(
738
+ "--cosforces",
739
+ "--cf",
740
+ action="store_true",
741
+ help="Plot image forces along a COS.",
742
+ )
743
+ group.add_argument(
744
+ "--cosens", "--ce", action="store_true", help="Plot COS energies."
745
+ )
746
+ group.add_argument(
747
+ "--all_energies",
748
+ "-a",
749
+ action="store_true",
750
+ help="Plot ground and excited state energies from 'overlap_data.h5'.",
751
+ )
752
+ group.add_argument(
753
+ "--afir",
754
+ action="store_true",
755
+ help="Plot AFIR and true -energies and -forces from an AFIR calculation.",
756
+ )
757
+ group.add_argument("--opt", action="store_true", help="Plot optimization progress.")
758
+ group.add_argument("--irc", action="store_true", help="Plot IRC progress.")
759
+ group.add_argument("--overlaps", "-o", action="store_true")
760
+ group.add_argument("--render_cdds", action="store_true")
761
+ group.add_argument("--h5_list", default=None, help="List groups in HDF5 file.")
762
+ group.add_argument("--md", action="store_true", help="Plot MD.")
763
+ group.add_argument("--gau", nargs="*")
764
+ group.add_argument("--scan", action="store_true")
765
+ group.add_argument(
766
+ "--trj", help="Plot energy values from the comments of a " "_trj.xyz file."
767
+ )
768
+
769
+ return parser.parse_args(args)
770
+
771
+
772
+ def plot_opt(h5_fn="optimization.h5", h5_group="opt"):
773
+ with h5py.File(h5_fn, "r") as handle:
774
+ try:
775
+ group = handle[h5_group]
776
+ except KeyError:
777
+ groups = list(handle.keys())
778
+ groups_str = "\t" + "\n\t".join(groups)
779
+ print(
780
+ f"Could not find group '{h5_group}'!\nAvailable groups are:\n{groups_str}\n"
781
+ f"Use '--h5_group [group]' to plot a different group."
782
+ )
783
+ if groups:
784
+ group = handle[groups[0]]
785
+ print(f"Using first group '{group}'.")
786
+ else:
787
+ return
788
+
789
+ cur_cycle = group.attrs["cur_cycle"]
790
+ is_cos = group.attrs["is_cos"]
791
+ is_converged = group.attrs["is_converged"]
792
+ coord_type = group.attrs["coord_type"]
793
+
794
+ ens = group["energies"][:cur_cycle]
795
+ max_forces = group["max_forces"][:cur_cycle]
796
+ rms_forces = group["rms_forces"][:cur_cycle]
797
+ max_force_thresh = group.attrs["max_force_thresh"]
798
+ rms_force_thresh = group.attrs["rms_force_thresh"]
799
+
800
+ en_conv, en_unit = get_en_conv()
801
+ ens -= ens.min()
802
+ ens *= en_conv
803
+ if is_cos:
804
+ text = textwrap.wrap(
805
+ "COS optimization detected. Plotting total energy of all images "
806
+ "in every cycle. Results from optimizing growing COS methods can "
807
+ "be plotted but the plots are not really useful as the varying "
808
+ "number of images is not considered.",
809
+ width=80,
810
+ )
811
+ print("\n".join(text))
812
+ ens = ens.sum(axis=1)
813
+ force_unit = get_force_unit(coord_type)
814
+
815
+ ax_kwargs = {
816
+ "marker": "o",
817
+ }
818
+
819
+ fig, (ax0, ax1, ax2) = plt.subplots(nrows=3, sharex=True)
820
+
821
+ ax0.plot(ens, **ax_kwargs)
822
+ ax0.set_ylabel(DE_LABEL)
823
+
824
+ ax1.plot(max_forces, **ax_kwargs)
825
+ ax1.axhline(max_force_thresh, c="k", ls="--", label="Threshold")
826
+ ax1.set_yscale("log")
827
+ ax1.set_title("max(forces)")
828
+ ax1.set_ylabel(force_unit)
829
+ ax1.legend()
830
+
831
+ ax2.plot(rms_forces, **ax_kwargs)
832
+ ax2.axhline(rms_force_thresh, c="k", ls="--", label="Threshold")
833
+ ax2.set_yscale("log")
834
+ ax2.set_title("rms(forces)")
835
+ ax2.set_xlabel("Cycle")
836
+ ax2.set_ylabel(force_unit)
837
+ ax2.legend()
838
+
839
+ title = f"{h5_fn}/{h5_group}, converged={is_converged}"
840
+ fig.suptitle(title, y=0.999)
841
+
842
+ plt.tight_layout()
843
+ plt.show()
844
+
845
+
846
+ def plot_irc():
847
+ cwd = Path(".")
848
+ h5s = cwd.glob("*irc_data.h5")
849
+ for h5 in h5s:
850
+ type_ = h5.name.split("_")[0]
851
+ title = f"{type_.capitalize()} IRC data"
852
+ _ = plot_irc_h5(h5, title)
853
+ plt.show()
854
+
855
+
856
+ def plot_irc_h5(h5, title=None):
857
+ print(f"Reading IRC data {h5}")
858
+ with h5py.File(h5, "r") as handle:
859
+ mw_coords = handle["mw_coords"][:]
860
+ energies = handle["energies"][:]
861
+ gradients = handle["gradients"][:]
862
+ rms_grad_thresh = handle["rms_grad_thresh"][()]
863
+
864
+ try:
865
+ ts_index = handle["ts_index"][()]
866
+ except KeyError:
867
+ ts_index = None
868
+
869
+ sizes = [dataset.shape[0] for dataset in (mw_coords, energies, gradients)]
870
+ size0 = sizes[0]
871
+ assert all([size == size0 for size in sizes])
872
+ print(f"\tFound {size0} IRC points")
873
+
874
+ en_conv, en_unit = get_en_conv()
875
+ energies -= energies[0]
876
+ energies *= en_conv
877
+
878
+ cds = np.linalg.norm(mw_coords - mw_coords[0], axis=1)
879
+ rms_grads = np.sqrt(np.mean(gradients ** 2, axis=1))
880
+ max_grads = np.abs(gradients).max(axis=1)
881
+
882
+ fig, (ax0, ax1, ax2) = plt.subplots(nrows=3, sharex=True)
883
+
884
+ plt_kwargs = {
885
+ "linestyle": "-",
886
+ "marker": "o",
887
+ }
888
+
889
+ ax0.plot(cds, energies, **plt_kwargs)
890
+ ax0.set_title("energy change")
891
+ ax0.set_ylabel(DE_LABEL)
892
+
893
+ ax1.plot(cds, rms_grads, **plt_kwargs)
894
+ ax1.axhline(rms_grad_thresh, linestyle="--", color="k")
895
+ ax1.set_title("rms(gradient)")
896
+ ax1.set_ylabel("$E_h$ Bohr⁻¹")
897
+
898
+ ax2.plot(cds, max_grads, **plt_kwargs)
899
+ ax2.set_title("max(gradient)")
900
+ ax2.set_xlabel("IRC / amu$^{\\frac{1}{2}}$ Bohr")
901
+ ax2.set_ylabel("$E_h$ Bohr⁻¹")
902
+
903
+ if ts_index:
904
+ x = cds[ts_index]
905
+ for ax, arr in ((ax0, energies), (ax1, rms_grads), (ax2, max_grads)):
906
+ xy = (x, arr[ts_index])
907
+ ax.annotate("TS", xy, fontsize=12, fontweight="bold")
908
+
909
+ if title:
910
+ fig.suptitle(title)
911
+ else:
912
+ fig.tight_layout()
913
+
914
+ return fig, (ax0, ax1, ax2)
915
+
916
+
917
+ def plot_scan(h5_fn="scan.h5"):
918
+ with h5py.File(h5_fn, "r") as handle:
919
+ groups = list()
920
+ energies = list()
921
+ for k in handle.keys():
922
+ group = handle[k]
923
+ groups.append(k)
924
+ energies.append(group["energies"][:])
925
+ print(f"Found {len(groups)} groups in '{h5_fn}'.")
926
+
927
+ en_conv, _ = get_en_conv
928
+ for group, ens in zip(groups, energies):
929
+ ens -= ens.min()
930
+ ens *= en_conv
931
+ fig, ax = plt.subplots()
932
+ ax.plot(ens, "o-")
933
+ ax.set_xlabel("Scan point")
934
+ ax.set_ylabel(DE_LABEL)
935
+ ax.set_title(group)
936
+ plt.show()
937
+
938
+
939
+ def plot_trj_energies(trj):
940
+ """Parse comments of .xyz/_trj.xyz as energies and plot."""
941
+ atoms_coords, comments = parse_xyz(trj, with_comment=True)
942
+ try:
943
+ energies = np.array(comments, dtype=float)
944
+ except ValueError as err:
945
+ print("Could not convert comments to energies!\n")
946
+ raise err
947
+ en_conv, en_unit = get_en_conv()
948
+ energies -= energies.min()
949
+ energies *= en_conv
950
+ fig, ax = plt.subplots()
951
+ ax.plot(energies)
952
+ highlights = [0, energies.argmax(), energies.size - 1]
953
+ highlight_ens = energies[highlights]
954
+ ax.scatter(highlights, highlight_ens, s=50)
955
+ ax.set_xlabel("Step")
956
+ ax.set_ylabel(DE_LABEL)
957
+ ax.set_title(trj)
958
+ plt.show()
959
+
960
+
961
+ def list_h5_groups(h5_fn):
962
+ with h5py.File(h5_fn, "r") as handle:
963
+ groups = list(handle.keys())
964
+
965
+ print(f"Found {len(groups)} groups in '{h5_fn}'\n")
966
+ for i, grp in enumerate(groups):
967
+ print(f"\t{i:02d}: {grp}")
968
+
969
+ if groups:
970
+ print("\nAvailable groups can be selected by '--h5_group [name]'.")
971
+
972
+
973
+ def run():
974
+ args = parse_args(sys.argv[1:])
975
+
976
+ h5_fn = Path(args.h5_fn)
977
+
978
+ global get_en_conv
979
+
980
+ def get_en_conv():
981
+ if args.kcal:
982
+ conv, label = AU2KCALPERMOL, "kcal mol⁻¹"
983
+ else:
984
+ conv, label = AU2KJPERMOL, "kJ mol⁻¹"
985
+ return conv, label
986
+
987
+ global DE_LABEL
988
+ DE_LABEL = rf"$\Delta$E / {get_en_conv()[1]}"
989
+
990
+ if args.all_energies or args.overlaps:
991
+ if not h5_fn.exists():
992
+ if (tmp_fn := Path(OUT_DIR_DEFAULT) / h5_fn).exists():
993
+ h5_fn = tmp_fn
994
+ print(f"Loading overlap data from '{h5_fn}'.")
995
+
996
+ # Optimization
997
+ if args.h5_list:
998
+ list_h5_groups(args.h5_list)
999
+ if args.opt:
1000
+ plot_opt(h5_group=args.h5_group)
1001
+ # COS specific
1002
+ elif args.cosens:
1003
+ plot_cos_energies(h5_group=args.h5_group)
1004
+ elif args.cosforces:
1005
+ plot_cos_forces(h5_group=args.h5_group)
1006
+ # AFIR
1007
+ elif args.afir:
1008
+ plot_afir()
1009
+ # IRC related
1010
+ elif args.irc:
1011
+ plot_irc()
1012
+ # Overlap calculator related
1013
+ elif args.all_energies:
1014
+ plot_all_energies(h5=h5_fn)
1015
+ elif args.overlaps:
1016
+ plot_overlaps(h5=h5_fn)
1017
+ # MD related
1018
+ elif args.md:
1019
+ plot_md()
1020
+ elif args.gau:
1021
+ plot_gau(args.gau)
1022
+ elif args.scan:
1023
+ plot_scan()
1024
+ elif args.render_cdds:
1025
+ render_cdds(h5=h5_fn)
1026
+ elif args.trj:
1027
+ plot_trj_energies(args.trj)
1028
+
1029
+
1030
+ if __name__ == "__main__":
1031
+ run()