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,844 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ import logging
4
+ import os
5
+ from pathlib import Path
6
+ import platform
7
+ import shutil
8
+ import subprocess
9
+ import tempfile
10
+ from typing import Callable, Optional
11
+
12
+ from natsort import natsorted
13
+
14
+ from pysisyphus import logger
15
+ from pysisyphus import helpers_pure
16
+ from pysisyphus.config import get_cmd, OUT_DIR_DEFAULT
17
+ from pysisyphus.constants import BOHR2ANG
18
+ from pysisyphus.helpers import geom_loader
19
+ from pysisyphus.linalg import finite_difference_hessian
20
+
21
+
22
+ KeepKind = Enum("KeepKind", ["ALL", "LATEST", "NONE"])
23
+ HessKind = Enum("HessKind", ["ORG", "NUMERICAL"])
24
+
25
+
26
+ @dataclass
27
+ class SetPlan:
28
+ key: str
29
+ name: Optional[str] = None
30
+ condition: Callable = lambda obj: True
31
+ # success: Optional[Callable] = None
32
+ fail: Optional[Callable] = None
33
+
34
+ def __post_init__(self):
35
+ if self.name is None:
36
+ self.name = self.key
37
+
38
+
39
+ class Calculator:
40
+
41
+ conf_key = None
42
+ _set_plans = []
43
+
44
+ def __init__(
45
+ self,
46
+ calc_number=0,
47
+ charge=0,
48
+ mult=1,
49
+ base_name="calculator",
50
+ pal=1,
51
+ mem=1000,
52
+ keep_kind="all",
53
+ check_mem=True,
54
+ retry_calc=0,
55
+ last_calc_cycle=None,
56
+ clean_after=True,
57
+ out_dir=OUT_DIR_DEFAULT,
58
+ force_num_hess=False,
59
+ num_hess_kwargs=None,
60
+ ):
61
+ """Base-class of all calculators.
62
+
63
+ Meant to be extended.
64
+
65
+ Parameters
66
+ ----------
67
+ calc_number : int, default=0
68
+ Identifier of the Calculator. Used in distinguishing it from
69
+ other Calculators, e.g. in ChainOfStates calculations. Also
70
+ used in the creation of filenames.
71
+ charge : int, default=0
72
+ Molecular charge.
73
+ mult : int, default=1
74
+ Molecular multiplicity (1 = singlet, 2 = doublet, ...)
75
+ base_name : str, default=calculator
76
+ Generated filenames will start with this string.
77
+ pal : int, default=1
78
+ Positive integer that gives the number of physical cores to
79
+ use on 1 node.
80
+ mem : int, default=1000
81
+ Mememory per core in MB. The total amount of memory is given as
82
+ mem*pal.
83
+ check_mem : bool, default=True
84
+ Whether to adjust the requested memory if too much is requested.
85
+ retry_calc : int, default=0
86
+ Number of additional retries when calculation failed.
87
+ last_calc_cycle : int
88
+ Internal variable used in restarts.
89
+ clean_after : bool
90
+ Delete temporary directory were calculations were executed
91
+ after a calculation.
92
+ out_dir : str
93
+ Path that is prepended to generated filenames.
94
+ force_hess_kwargs : bool, default False
95
+ Force numerical Hessians.
96
+ num_hess_kwargs : dict
97
+ Keyword arguments for finite difference Hessian calculation.
98
+ """
99
+
100
+ self.logger = logging.getLogger("calculator")
101
+
102
+ self.calc_number = calc_number
103
+ self.charge = int(charge)
104
+ self.mult = int(mult)
105
+ self.base_name = base_name
106
+ self.pal = int(pal)
107
+ assert self.pal > 0, "pal must be a non-negative integer!"
108
+ if check_mem:
109
+ mem = helpers_pure.check_mem(int(mem), pal, logger=self.logger)
110
+ self.mem = mem
111
+ self.keep_kind = KeepKind[keep_kind.upper()]
112
+ # Disasble retries if check_termination method is not implemented
113
+ self.retry_calc = int(retry_calc) if hasattr(self, "check_termination") else 0
114
+ assert self.retry_calc >= 0
115
+ try:
116
+ self.out_dir = Path(out_dir).resolve()
117
+ except TypeError:
118
+ self.out_dir = Path(OUT_DIR_DEFAULT).resolve()
119
+ self.out_dir.mkdir(parents=True, exist_ok=True)
120
+ if num_hess_kwargs is None:
121
+ num_hess_kwargs = dict()
122
+ self.num_hess_kwargs = num_hess_kwargs
123
+
124
+ # Extensions of the files to keep after running a calculation.
125
+ # Usually overridden in derived classes.
126
+ self.to_keep = ()
127
+ # How many calculations were already run
128
+ self.calc_counter = 0
129
+ # Handle restarts
130
+ if last_calc_cycle:
131
+ self.calc_counter = int(last_calc_cycle) + 1
132
+ self.reattach(int(last_calc_cycle))
133
+ self.log(f"Set {self.calc_counter} for this calculation")
134
+ self.clean_after = clean_after
135
+
136
+ self.inp_fn = "calc.inp"
137
+ self.out_fn = "calc.out"
138
+ # When this is set the run() method will use this path
139
+ # instead of creating a new one.
140
+ # Currently this is only used with the Turbomole calculator.
141
+ self.path_already_prepared = None
142
+ self.last_run_path = None
143
+ self.backup_dir = None
144
+ self.kept_history = dict()
145
+ self.build_set_plans()
146
+
147
+ # Backup original get_hessian method
148
+ self._org_get_hessian = self.get_hessian
149
+ self.hessian_kind = HessKind["ORG"]
150
+ if force_num_hess:
151
+ self.force_num_hessian()
152
+
153
+ def get_cmd(self, key="cmd"):
154
+ assert self.conf_key, "'conf_key'-attribute is missing for this calculator!"
155
+
156
+ try:
157
+ return get_cmd(section=self.conf_key, key=key, use_defaults=True)
158
+ except KeyError:
159
+ logger.debug(f"Failed to load key '{key}' from section '{self.conf_key}'!")
160
+
161
+ @classmethod
162
+ def geom_from_fn(cls, fn, **kwargs):
163
+ geom = geom_loader(fn)
164
+ calc = cls(**kwargs)
165
+ geom.set_calculator(calc)
166
+ return geom
167
+
168
+ @property
169
+ def name(self):
170
+ return f"{self.base_name}_{self.calc_number:03d}"
171
+
172
+ def log(self, message=""):
173
+ """Write a log message.
174
+
175
+ Wraps the logger variable.
176
+
177
+ Parameters
178
+ ----------
179
+ message : str
180
+ Message to be logged.
181
+ """
182
+
183
+ self.logger.debug(f"{self.name}, cycle {self.calc_counter:03d}: {message}")
184
+
185
+ def get_energy(self, atoms, coords, **prepare_kwargs):
186
+ """Meant to be extended."""
187
+ raise Exception("Not implemented!")
188
+
189
+ def get_forces(self, atoms, coords, **prepare_kwargs):
190
+ """Meant to be extended."""
191
+ raise Exception("Not implemented!")
192
+
193
+ def get_hessian(self, atoms, coords, **prepare_kwargs):
194
+ """Get Hessian matrix. Fall back to numerical Hessian, if not overriden.
195
+
196
+ Preferrably, this method should provide an analytical Hessian."""
197
+ self.log(f"Calculator {self} has no native Hessian method.")
198
+ return self.get_num_hessian(atoms, coords, **prepare_kwargs)
199
+
200
+ def get_num_hessian(self, atoms, coords, **prepare_kwargs):
201
+ self.log("Calculating numerical Hessian.")
202
+ results = self.get_energy(atoms, coords, **prepare_kwargs)
203
+
204
+ def grad_func(coords):
205
+ results = self.get_forces(atoms, coords, **prepare_kwargs)
206
+ gradient = -results["forces"]
207
+ return gradient
208
+
209
+ def callback(i, j):
210
+ self.log(f"Displacement {j} of coordinate {i}")
211
+
212
+ _num_hess_kwargs = {
213
+ "step_size": 0.005,
214
+ # Central difference by default
215
+ "acc": 2,
216
+ }
217
+ _num_hess_kwargs.update(self.num_hess_kwargs)
218
+
219
+ fd_hessian = finite_difference_hessian(
220
+ coords,
221
+ grad_func,
222
+ callback=callback,
223
+ **_num_hess_kwargs,
224
+ )
225
+ results["hessian"] = fd_hessian
226
+ return results
227
+
228
+ def force_num_hessian(self):
229
+ """Always calculate numerical Hessians."""
230
+ self.log("Doing numerical Hessians from now on.")
231
+ self.get_hessian = self.get_num_hessian
232
+ self.hessian_kind = HessKind["NUMERICAL"]
233
+
234
+ def restore_org_hessian(self):
235
+ """Restore original 'get_hessian' method, which may also fallback to numerical
236
+ Hessians, if not implemented."""
237
+ self.log("Restored original 'get_hessian' method.")
238
+ self.get_hessian = self._org_get_hessian
239
+ self.hessian_kind = HessKind["ORG"]
240
+
241
+ def make_fn(self, name, counter=None, return_str=False):
242
+ """Make a full filename.
243
+
244
+ Return a full filename including the calculator name and the
245
+ current counter given a suffix.
246
+
247
+ Parameters
248
+ ----------
249
+ name: str
250
+ Suffix of the filename.
251
+ counter : int, optional
252
+ If not given use the current calc_counter.
253
+ return_str : int, optional
254
+ Return a string instead of a Path when True.
255
+
256
+ Returns
257
+ -------
258
+ fn : str
259
+ Filename.
260
+ """
261
+
262
+ if counter is None:
263
+ counter = self.calc_counter
264
+ fn = self.out_dir / f"{self.name}.{counter:03d}.{name}"
265
+ if return_str:
266
+ fn = str(fn)
267
+ return fn
268
+
269
+ def prepare_path(self, use_in_run=False):
270
+ """Get a temporary directory handle.
271
+
272
+ Create a temporary directory that can later be used in a calculation.
273
+
274
+ Parameters
275
+ ----------
276
+ use_in_run : bool, option
277
+ Sets the internal variable ``self.path_already_prepared`` that
278
+ is later read by ``self.run()``. No new temporary directory will
279
+ be created in ``self.run()``.
280
+
281
+ Returns
282
+ -------
283
+ path: Path
284
+ Prepared directory.
285
+ """
286
+
287
+ prefix = f"{self.name}_{self.calc_counter:03d}_"
288
+ path = Path(tempfile.mkdtemp(prefix=prefix))
289
+ if use_in_run:
290
+ self.path_already_prepared = path
291
+ return path
292
+
293
+ def prepare(self, inp):
294
+ """Prepare a temporary directory and write input.
295
+
296
+ Similar to prepare_path, but the input is also written into
297
+ the prepared directory.
298
+
299
+ Paramters
300
+ ---------
301
+ inp : str
302
+ Input to be written into the file ``self.inp_fn`` in
303
+ the prepared directory.
304
+
305
+ Returns
306
+ -------
307
+ path: Path
308
+ Prepared directory.
309
+ """
310
+
311
+ if not self.path_already_prepared:
312
+ path = self.prepare_path()
313
+ else:
314
+ path = self.path_already_prepared
315
+
316
+ # Calculators like Turbomole got no input.
317
+ if inp:
318
+ inp_path = path / self.inp_fn
319
+ with open(inp_path, "w") as handle:
320
+ handle.write(inp)
321
+
322
+ return path
323
+
324
+ def prepare_input(self, atoms, coords, calc_type):
325
+ """Meant to be extended."""
326
+ raise Exception("Not implemented!")
327
+
328
+ def print_out_fn(self, path):
329
+ """Print calculation output.
330
+
331
+ Prints the output of a calculator after a calculation.
332
+
333
+ Parameters
334
+ ----------
335
+ path : Path
336
+ Temporary directory of the calculation.
337
+ """
338
+ with open(path / self.out_fn) as handle:
339
+ text = handle.read()
340
+ print(text)
341
+
342
+ def prepare_turbo_coords(self, atoms, coords):
343
+ """Get a Turbomole coords string.
344
+
345
+ Parameters
346
+ ----------
347
+ atoms : iterable
348
+ Atom descriptors (element symbols).
349
+ coords: np.array, 1d
350
+ 1D-array holding coordinates in Bohr.
351
+
352
+ Returns
353
+ -------
354
+ coords: str
355
+ String holding coordinates in Turbomole coords format.
356
+ """
357
+ fmt = "{:<20.014f}"
358
+ coord_str = "$coord\n"
359
+ for atom, coord in zip(atoms, coords.reshape(-1, 3)):
360
+ coord_line = (fmt + fmt + fmt).format(*coord) + atom.lower() + "\n"
361
+ coord_str += coord_line
362
+ coord_str += "$end"
363
+ return coord_str
364
+
365
+ def prepare_coords(self, atoms, coords):
366
+ """Get 3d coords in Angstrom.
367
+
368
+ Reshape internal 1d coords to 3d and convert to Angstrom.
369
+
370
+ Parameters
371
+ ----------
372
+ atoms : iterable
373
+ Atom descriptors (element symbols).
374
+ coords: np.array, 1d
375
+ 1D-array holding coordinates in Bohr.
376
+
377
+ Returns
378
+ -------
379
+ coords: np.array, 3d
380
+ 3D-array holding coordinates in Angstrom.
381
+ """
382
+ coords = coords.reshape(-1, 3) * BOHR2ANG
383
+ coords = "\n".join(
384
+ [
385
+ "{} {:10.08f} {:10.08f} {:10.08f}".format(a, *c)
386
+ for a, c in zip(atoms, coords)
387
+ ]
388
+ )
389
+ return coords
390
+
391
+ def prepare_xyz_string(self, atoms, coords):
392
+ """Returns a xyz string in Angstrom.
393
+
394
+ Parameters
395
+ ----------
396
+ atoms : iterable
397
+ Atom descriptors (element symbols).
398
+ coords: np.array, 1d
399
+ 1D-array holding coordinates in Bohr.
400
+
401
+ Returns
402
+ -------
403
+ xyz_str: string
404
+ Coordinates in .xyz format.
405
+ """
406
+
407
+ return f"{len(atoms)}\n\n{self.prepare_coords(atoms, coords)}"
408
+
409
+ def run(
410
+ self,
411
+ inp,
412
+ calc,
413
+ add_args=None,
414
+ env=None,
415
+ shell=False,
416
+ hold=False,
417
+ keep=True,
418
+ cmd=None,
419
+ inc_counter=True,
420
+ run_after=True,
421
+ parser_kwargs=None,
422
+ symlink=True,
423
+ ):
424
+ """Run a calculation.
425
+
426
+ The bread-and-butter method to actually run an external quantum
427
+ chemistry code.
428
+
429
+ Parameters
430
+ ----------
431
+ inp : str
432
+ Input for the external program that is written to the temp-dir.
433
+ calc : str, hashable
434
+ Key (and more or less type of calculation) to select the right
435
+ parsing function from ``self.parser_funcs``.
436
+ add_args : iterable, optional
437
+ Additional arguments that will be appended to the program call.
438
+ env : Environment, optional
439
+ A potentially modified environment for the subprocess call.
440
+ shell : bool, optional
441
+ Use a shell to execute the program call. Need for Turbomole were
442
+ we chain program calls like dscf; escf.
443
+ hold : bool, optional
444
+ Wether to remove the temporary directory after the calculation.
445
+ keep : bool, optional
446
+ Wether to backup files as specified in ``self.to_keep()``. Usually
447
+ you want this.
448
+ cmd : str or iterable, optional
449
+ Overwrites ``self.base_cmd``.
450
+ inc_counter : bool, optional
451
+ Wether to increment the counter after a calculation.
452
+
453
+ Returns
454
+ -------
455
+ results : dict
456
+ Dictionary holding all applicable results of the calculations
457
+ like the energy, a forces vector and/or excited state energies
458
+ from TDDFT.
459
+ """
460
+
461
+ self.backup_dir = None
462
+ path = self.prepare(inp)
463
+ self.log(f"Running in {path} on {platform.node()}")
464
+ if cmd is None:
465
+ cmd = self.base_cmd
466
+
467
+ if isinstance(cmd, str):
468
+ cmd = [cmd]
469
+
470
+ act_cmd = cmd[0]
471
+ cmd_availble = shutil.which(act_cmd)
472
+ if (not cmd_availble) and (not shell):
473
+ print(
474
+ f"Command '{act_cmd}' could not be found on $PATH! "
475
+ "Maybe you forgot to make it available?"
476
+ )
477
+ return
478
+
479
+ args = cmd + [self.inp_fn]
480
+ if add_args:
481
+ args.extend(add_args)
482
+ if not env:
483
+ env = os.environ.copy()
484
+ tmp_out_fn = path / self.out_fn
485
+ with open(tmp_out_fn, "w") as handle:
486
+ if symlink:
487
+ # We can't use resolve here as a previous symlink may already
488
+ # exist. Calling resolve would translate this to the original
489
+ # out file in some tempdir (that is already deleted ...).
490
+ # sym_fn = Path("cur_out").resolve()
491
+ sym_fn = self.out_dir / "cur_out"
492
+ try:
493
+ os.remove(sym_fn)
494
+ except FileNotFoundError:
495
+ pass
496
+
497
+ try:
498
+ os.symlink(tmp_out_fn, sym_fn)
499
+ self.log(f"Created symlink in '{sym_fn}'")
500
+ # This may happen if we use a dask scheduler
501
+ except FileExistsError:
502
+ self.log("Symlink already exists. Skipping generation.")
503
+
504
+ # Do at least one cycle. When retries are disabled retry_calc == 0
505
+ # and range(0+1) will result in one cycle
506
+ added_retry_args = False
507
+ for retry in range(self.retry_calc + 1):
508
+ result = subprocess.Popen(
509
+ args,
510
+ cwd=path,
511
+ stdout=handle,
512
+ stderr=subprocess.PIPE,
513
+ env=env,
514
+ shell=shell,
515
+ )
516
+ result.communicate()
517
+ if hasattr(self, 'check_termination'):
518
+ normal_termination = False
519
+ try:
520
+ # Calling check_termination may result in an exception and
521
+ # normal_termination will stay at False
522
+ normal_termination = self.check_termination(tmp_out_fn)
523
+ # The out file may not be present
524
+ except FileNotFoundError:
525
+ self.log(
526
+ f"Could not find out-file {str(tmp_out_fn)} for termination status check!"
527
+ )
528
+ else:
529
+ normal_termination = True
530
+
531
+ if normal_termination:
532
+ break
533
+ else:
534
+ print("Abnormal termination! Retrying calculation.")
535
+ shutil.copy(tmp_out_fn, str(tmp_out_fn) + f".fail_{retry:02d}")
536
+ if hasattr(self, 'clean_tmp'):
537
+ self.clean_tmp(path)
538
+ else:
539
+ self.log("'self.clean_tmp()' not implemented!")
540
+ # Clear tmp_out_fn
541
+ handle.seek(0)
542
+ handle.truncate()
543
+ self.log("Detected abnormal termination! Retrying calculation.")
544
+
545
+ if not added_retry_args:
546
+ if hasattr(self, 'get_retry_args'):
547
+ args += self.get_retry_args()
548
+ added_retry_args = True
549
+ else:
550
+ self.log("'self.get_retry_args()' not implemented!")
551
+
552
+ # Parse results for desired quantities
553
+ try:
554
+ if run_after:
555
+ self.run_after(path)
556
+ parser_kwargs = {} if parser_kwargs is None else parser_kwargs
557
+ results = self.parser_funcs[calc](path, **parser_kwargs)
558
+ if keep:
559
+ self.keep(path)
560
+
561
+ except Exception as err:
562
+ print("Crashed input:")
563
+ print(inp)
564
+ backup_dir = Path(os.getcwd()) / f"crashed_{self.name}"
565
+ self.backup_dir = backup_dir
566
+ if backup_dir.exists():
567
+ shutil.rmtree(backup_dir)
568
+ shutil.copytree(path, backup_dir)
569
+ print(
570
+ f"Copied contents of\n\t'{path}'\nto\n\t'{backup_dir}'.\n"
571
+ "Consider checking the log files there.\n"
572
+ )
573
+ raise err
574
+ finally:
575
+ if (not hold) and self.clean_after:
576
+ self.clean(path)
577
+ if inc_counter:
578
+ self.calc_counter += 1
579
+
580
+ self.path_already_prepared = None
581
+ self.last_run_path = path
582
+ return results
583
+
584
+ def run_after(self, path):
585
+ """Meant to be extended.
586
+
587
+ This method is called after a calculation was done, but before
588
+ entering ``self.keep()`` and ``self.clean()``. Can be used to call
589
+ tools like formchk or ricctools.
590
+ """
591
+
592
+ def popen(self, cmd, cwd=None):
593
+ if isinstance(cmd, str):
594
+ cmd = cmd.split()
595
+ proc = subprocess.Popen(
596
+ cmd,
597
+ cwd=cwd,
598
+ universal_newlines=True,
599
+ stdout=subprocess.PIPE,
600
+ stderr=subprocess.PIPE,
601
+ )
602
+ proc.wait()
603
+ return proc
604
+
605
+ def prepare_pattern(self, raw_pat):
606
+ """Prepare globs.
607
+
608
+ Transforms an entry of ``self.to_keep`` into a glob and a key
609
+ suitable for the use in ``self.keep()``.
610
+
611
+ Parameters
612
+ ----------
613
+ raw_pat : str
614
+ Entry of ``self.to_keep``
615
+
616
+ Returns
617
+ -------
618
+ pattern : str
619
+ Glob that can be used in Path.glob()
620
+ multi : bool
621
+ Flag if glob may match multiple files.
622
+ key : str
623
+ A key to be used in the ``kept_fns`` dict.
624
+ """
625
+ key_given = None
626
+ if ":" in raw_pat:
627
+ key_given, raw_pat = raw_pat.split(":")
628
+ # Indicates if multiple files are expected
629
+ multi = "*" in raw_pat
630
+ # Drop '*' as it just indicates if we expect multiple matches
631
+ raw_pat = raw_pat.replace("*", "")
632
+ # Interpret it as prefix and drop the two underscores
633
+ if raw_pat.startswith("__"):
634
+ pattern = f"{raw_pat[2:]}*"
635
+ pattern_key = f"{raw_pat[2:]}s"
636
+ # Use raw_pat as suffix
637
+ else:
638
+ pattern = f"*{raw_pat}"
639
+ pattern_key = f"{raw_pat}"
640
+ if key_given:
641
+ pattern_key = key_given
642
+ pattern_key = pattern_key.lower()
643
+ return pattern, multi, pattern_key
644
+
645
+ def build_set_plans(self, _set_plans=None):
646
+ if _set_plans is None:
647
+ _set_plans = self._set_plans
648
+
649
+ set_plans = list()
650
+ for sp in _set_plans:
651
+ if type(sp) == SetPlan:
652
+ set_plans.append(sp)
653
+ continue
654
+ if type(sp) == dict:
655
+ set_plans.append(SetPlan(**sp))
656
+ continue
657
+ if type(sp) == str:
658
+ sp = (sp,)
659
+ set_plans.append(SetPlan(*sp))
660
+ self.set_plans = set_plans
661
+
662
+ def apply_keep_kind(self):
663
+ assert self.keep_kind in KeepKind
664
+
665
+ # Nothing to do when set to ALL
666
+ delete_calc_counter = list()
667
+ if self.keep_kind == KeepKind["LATEST"]:
668
+ delete_calc_counter.extend(
669
+ [
670
+ self.calc_counter - 1,
671
+ ]
672
+ if self.calc_counter > 0
673
+ else []
674
+ )
675
+ elif self.keep_kind == KeepKind["NONE"]:
676
+ delete_calc_counter.extend(list(self.kept_history.keys()))
677
+
678
+ for cycle in delete_calc_counter:
679
+ self.log(f"Deleting kept files from cycle {cycle}.")
680
+ for k, f in self.kept_history[cycle].items():
681
+ if type(f) is list:
682
+ [os.remove(f_) for f_ in f]
683
+ else:
684
+ os.remove(f)
685
+ del self.kept_history[cycle]
686
+
687
+ def keep(self, path):
688
+ """Backup calculation results.
689
+
690
+ Parameters
691
+ ----------
692
+ path : Path
693
+ Temporary directory of the calculation.
694
+
695
+ Returns
696
+ -------
697
+ kept_fns : dict
698
+ Dictonary holding the filenames that were backed up. The keys
699
+ correspond to the type of file.
700
+ """
701
+
702
+ kept_fns = dict()
703
+ for raw_pattern in self.to_keep:
704
+ pattern, multi, key = self.prepare_pattern(raw_pattern)
705
+ globbed = natsorted(path.glob(pattern))
706
+ if not multi:
707
+ assert len(globbed) <= 1, (
708
+ f"Expected at most one file "
709
+ f"matching {pattern} in {path}. Found {len(globbed)} "
710
+ f"files instead ({', '.join([g.name for g in globbed])})!"
711
+ )
712
+ else:
713
+ # I wonder if this has to be a list? Probably to support 'multi', when the pattern
714
+ # contains a '*' character.
715
+ kept_fns[key] = list()
716
+ for tmp_fn in globbed:
717
+ base = tmp_fn.name
718
+ new_fn = self.make_fn(base)
719
+ shutil.copy(tmp_fn, new_fn)
720
+ if multi:
721
+ kept_fns[key].append(new_fn)
722
+ else:
723
+ kept_fns[key] = new_fn
724
+ self.kept_history[self.calc_counter] = kept_fns
725
+ self.apply_keep_kind()
726
+ try:
727
+ kept_fns = self.kept_history[self.calc_counter]
728
+ except KeyError:
729
+ kept_fns = dict()
730
+ self.apply_set_plans(kept_fns)
731
+ return kept_fns
732
+
733
+ def apply_set_plans(self, kept_fns, set_plans=None):
734
+ if set_plans is None:
735
+ set_plans = self.set_plans
736
+
737
+ for sp in set_plans:
738
+ if not sp.condition(self):
739
+ continue
740
+ try:
741
+ setattr(self, sp.name, kept_fns[sp.key])
742
+ except KeyError:
743
+ if sp.fail is not None:
744
+ self.log(sp.fail(sp.key))
745
+
746
+ def clean(self, path):
747
+ """Delete the temporary directory.
748
+
749
+ Parameters
750
+ ----------
751
+ path : Path
752
+ Directory to delete.
753
+ """
754
+ shutil.rmtree(path)
755
+ self.log(f"Cleaned {path}")
756
+
757
+ def get_restart_info(self):
758
+ """Return a dict containing chkfiles.
759
+
760
+ Returns
761
+ -------
762
+ restart_info : dict
763
+ Dictionary holding the calculator state. Used for restoring calculaters
764
+ in restarted calculations.
765
+ """
766
+ try:
767
+ # Convert possible Paths to str
768
+ chkfiles = {k: str(v) for k, v in self.get_chkfiles().items()}
769
+ except AttributeError:
770
+ chkfiles = dict()
771
+
772
+ restart_info = {
773
+ "base_name": self.base_name,
774
+ "calc_number": self.calc_number,
775
+ "calc_counter": self.calc_counter,
776
+ "chkfiles": chkfiles,
777
+ }
778
+
779
+ return restart_info
780
+
781
+ def verify_chkfiles(self, chkfiles):
782
+ """Checks if given chkfiles exist and return them as Paths
783
+
784
+ Parameters
785
+ ----------
786
+ chkfiles : dict
787
+ Dictionary holding the chkfiles. The keys correspond to the attribute
788
+ names, the values are strs holding the (potentially full) filename (path).
789
+
790
+ Returns
791
+ -------
792
+ paths : dict
793
+ Dictionary of Paths.
794
+ """
795
+ paths = {}
796
+ for key, chkfile in chkfiles.items():
797
+ chkfile = Path(chkfile)
798
+ # If the chkfile exists at the given path we use it as it is.
799
+ if not chkfile.exists():
800
+ self.log(
801
+ f"Given chkfile '{chkfile}' could not be found! Dropping "
802
+ "absolute part and trying only its name."
803
+ )
804
+ # Check if relative path exists. This may happen if the calculation
805
+ # has been moved to a different folder.
806
+ name = Path(chkfile.name)
807
+ if name.exists():
808
+ chkfile = name
809
+ else:
810
+ self.log(f"'{name}' could not be found! Skipping this chkfile.")
811
+ continue
812
+ paths[key] = chkfile
813
+ return paths
814
+
815
+ def set_restart_info(self, restart_info):
816
+ """Sets restart information (chkfiles etc.) on the calculator.
817
+
818
+ Parameters
819
+ -------
820
+ restart_info : dict
821
+ Dictionary holding the calculator state. Used for restoring calculaters
822
+ in restarted calculations.
823
+ """
824
+ try:
825
+ chkfiles = self.verify_chkfiles(restart_info.pop("chkfiles"))
826
+ self.set_chkfiles(chkfiles)
827
+ except KeyError:
828
+ self.log("No chkfiles preset in restart_info")
829
+ except AttributeError:
830
+ self.log(
831
+ "Found chkfiles on restart_info, but 'set_chkfiles' is not "
832
+ "implemented for Calculator."
833
+ )
834
+
835
+ self.log("Setting restart_info")
836
+ for key, value in restart_info.items():
837
+ setattr(self, key, value)
838
+ self.log(f"\t{key}: {value}")
839
+
840
+ def print_capabilities(self):
841
+ print(
842
+ f" Can retry?: {hasattr(self, 'check_termination')}\n"
843
+ f"Can track ES??: {hasattr(self, 'prepare_overlap_data')}\n"
844
+ )