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/irc/DWI.py ADDED
@@ -0,0 +1,108 @@
1
+ # [1] https://aip.scitation.org/doi/10.1063/1.1724823
2
+ # Hratchian, 2004
3
+ # [2] https://aip.scitation.org/doi/pdf/10.1063/1.475419?class=pdf
4
+ # Thompson, 1998
5
+
6
+ from collections import deque
7
+
8
+ import numpy as np
9
+
10
+ import torch
11
+
12
+ def taylor(energy, gradient, hessian, step):
13
+ """Taylor series expansion of the energy to second order."""
14
+ if isinstance(hessian, torch.Tensor):
15
+ step_t = torch.as_tensor(step, dtype=hessian.dtype, device=hessian.device)
16
+ return energy + step @ gradient + 0.5 * (step_t @ hessian @ step_t).item()
17
+ else:
18
+ return energy + step @ gradient + 0.5 * step @ hessian @ step
19
+
20
+
21
+ def taylor_grad(gradient, hessian, step):
22
+ """Gradient of a Taylor series expansion of the energy to second order."""
23
+ if isinstance(hessian, torch.Tensor):
24
+ step_t = torch.as_tensor(step, dtype=hessian.dtype, device=hessian.device)
25
+ return gradient + (hessian @ step_t).cpu().numpy()
26
+ else:
27
+ return gradient + hessian @ step
28
+
29
+
30
+ class DWI:
31
+
32
+ def __init__(self, n=4, maxlen=2):
33
+ """Distance weighted interpolation."""
34
+
35
+ self.n = int(n)
36
+ assert self.n > 0
37
+ assert (self.n % 2) == 0
38
+ self.maxlen = maxlen
39
+ assert self.maxlen == 2, \
40
+ "Right now only maxlen=2 is supported!"
41
+
42
+ # Using FIFO deques for easy updating of the lists
43
+ self.coords = deque(maxlen=self.maxlen)
44
+ self.energies = deque(maxlen=self.maxlen)
45
+ self.gradients = deque(maxlen=self.maxlen)
46
+ self.hessians = deque(maxlen=self.maxlen)
47
+
48
+ def update(self, coords, energy, gradient, hessian):
49
+ self.coords.append(coords)
50
+ self.energies.append(energy)
51
+ self.gradients.append(gradient)
52
+ self.hessians.append(hessian)
53
+
54
+ assert len(self.coords) == len(self.energies) \
55
+ == len(self.gradients) == len(self.hessians)
56
+
57
+ def interpolate(self, at_coords, gradient=False):
58
+ """See [1] Eq. (25) - (29)"""
59
+ c1, c2 = self.coords
60
+
61
+ dx1 = at_coords - c1
62
+ dx2 = at_coords - c2
63
+
64
+ dx1_norm = np.linalg.norm(dx1)
65
+ dx2_norm = np.linalg.norm(dx2)
66
+ dx1_norm_n = dx1_norm**self.n
67
+ dx2_norm_n = dx2_norm**self.n
68
+
69
+ denom = dx1_norm**self.n + dx2_norm**self.n
70
+ w1 = dx2_norm_n / denom
71
+ w2 = dx1_norm_n / denom
72
+
73
+ e1, e2 = self.energies
74
+ g1, g2 = self.gradients
75
+ h1, h2 = self.hessians
76
+
77
+ t1 = taylor(e1, g1, h1, dx1)
78
+ t2 = taylor(e2, g2, h2, dx2)
79
+
80
+ E_dwi = w1*t1 + w2*t2
81
+
82
+ if not gradient:
83
+ return E_dwi
84
+
85
+ t1_grad = taylor_grad(g1, h1, dx1)
86
+ t2_grad = taylor_grad(g2, h2, dx2)
87
+
88
+ # The gradient of dx2_norm_n w.r.t the coordinates is formulated with
89
+ # **2n instead of **n, so the square root can be easily reduced.
90
+ # sqrt(x)**2n = x**(1/2)**2n = x**n
91
+ #
92
+ # Thats why we do the following calculations with n/2.
93
+ # of n.
94
+ n_2 = self.n // 2
95
+ dx1_norm_n_grad = 2 * n_2 * dx1_norm**(2*n_2-2) * dx1
96
+ dx2_norm_n_grad = 2 * n_2 * dx2_norm**(2*n_2-2) * dx2
97
+ w1_grad = (dx2_norm_n_grad*dx1_norm_n - dx1_norm_n_grad*dx2_norm_n) / denom**2
98
+ w2_grad = -w1_grad
99
+
100
+ # E_dwi = w1(x)*T1(x) + w2(x)*T2(x)
101
+ #
102
+ # dE_DWI / dx = dw1(x)*T1(x) + w1(x)*dT1(x) + dw2(x)*T2(x) + w2*dT2(x)
103
+ grad_dwi = w1_grad*t1 + w1*t1_grad + w2_grad*t2 + w2*t2_grad
104
+
105
+ return E_dwi, grad_dwi
106
+
107
+ def __repr__(self):
108
+ return f"DWI(n={self.n}, maxlen={self.maxlen})"
@@ -0,0 +1,134 @@
1
+ import numpy as np
2
+
3
+ from pysisyphus.constants import BOHR2M, AU2J, AMU2KG
4
+ from pysisyphus.irc.IRC import IRC
5
+ from pysisyphus.TableFormatter import TableFormatter
6
+
7
+
8
+ # [1] https://pubs.acs.org/doi/10.1021/jp012125b
9
+ # Following Reaction Pathways Using a Damped Classical Trajectory Algorithm
10
+ # Hratchian, Schlegel, 2002
11
+
12
+
13
+ class DampedVelocityVerlet(IRC):
14
+
15
+ def __init__(self, geometry, v0=0.04, dt0=0.5, error_tol=0.003,
16
+ max_cycles=150, **kwargs):
17
+ super().__init__(geometry, max_cycles=max_cycles, **kwargs)
18
+
19
+ self.v0 = v0
20
+ self.error_tol = error_tol
21
+ self.dt0 = dt0
22
+
23
+ step_header = "damping dt dt_new error".split()
24
+ step_fmts = [".2f", ".4f", ".4f", ".3E"]
25
+ self.step_formatter = TableFormatter(step_header, step_fmts, 10)
26
+
27
+ def mw_grad_to_acc(self, mw_grad):
28
+ """Takes care of the units for the mass-weighted gradient.
29
+ Converts units of a mass-weighted gradient [Hartree/(Bohr*amu)]
30
+ to units of acceleration [sqrt(amu)*Bohr/fs²]. The 1e30 comes
31
+ from converting second² to femto second²."""
32
+ return mw_grad * AU2J / AMU2KG / BOHR2M**2 / 1e30
33
+
34
+ def prepare(self, direction):
35
+ # In contrast to the paper we don't start from the TS geometry but
36
+ # instead do an initial displacement along the imaginary mode as for
37
+ # the other IRC classes. So there should always be a non-vanishing
38
+ # gradient after the prepare call.
39
+ super().prepare(direction)
40
+
41
+ acceleration = -self.mw_grad_to_acc(self.mw_gradient)
42
+ # init_velocity = acceleration
43
+ init_velocity, _ = self.damp_velocity(acceleration)
44
+
45
+ self.velocities = [init_velocity]
46
+ self.accelerations = [acceleration]
47
+ self.time_steps = [self.dt0]
48
+
49
+ def damp_velocity(self, velocity):
50
+ # Eq. (4) in [1]
51
+ damping_factor = self.v0 / np.linalg.norm(velocity)
52
+ self.log(f"Damping factor={damping_factor:.6f}")
53
+ damped_velocity = velocity * damping_factor
54
+ return damped_velocity, damping_factor
55
+
56
+ def estimate_error(self, new_mw_coords):
57
+ self.log("Error estimation")
58
+ # See Fig. 1 and Eq. (5) in [1]
59
+ cur_time_step = self.time_steps[-1]
60
+ prev_time_step = self.time_steps[-2]
61
+ time_step_sum = prev_time_step + cur_time_step
62
+ self.log(f"\tSum of cur. and prev. timestep: {time_step_sum:.6f} fs")
63
+ # Calculation of x' coordinates
64
+ # irc_mw_coords
65
+ # -1: current coords <- x_i-1 in the paper
66
+ # -2: previous coords <- x_i-2 in the paper
67
+ # velocities/accelerations
68
+ # -1: new velo./acc. for next time step
69
+ # -2: velo./acc. that yielded the proposed coords x_i
70
+ # -3: velo./acc. that yielded previous structure <- v_i-2 and a_i-2 in the paper
71
+ ref_coords = (
72
+ self.irc_mw_coords[-2]
73
+ # TODO: This should probably be -3 but it seems to give inferior results
74
+ + self.velocities[-2]*time_step_sum
75
+ + 0.5*self.accelerations[-2]*time_step_sum**2
76
+ )
77
+ diff = new_mw_coords - ref_coords
78
+ diff /= np.sqrt(self.geometry.masses_rep)
79
+ largest_component = np.max(np.abs(diff))
80
+ norm = np.linalg.norm(diff)
81
+ # Take either the largest component of the difference vector
82
+ # or the norm of the difference vector.
83
+ self.log("\tdiff=Proposed coords - Reference coords")
84
+ self.log(f"\tmax(|diff|)={largest_component:.6f}")
85
+ self.log(f"\ttnorm(diff)={norm:.6f}")
86
+ # estimated_error = max(largest_component, norm)
87
+ estimated_error = max(largest_component, norm)
88
+ self.log(f"\testimated error={estimated_error:.6f}")
89
+ return estimated_error
90
+
91
+ def step(self):
92
+ prev_acceleration = self.accelerations[-1]
93
+ prev_velocity = self.velocities[-1]
94
+ time_step = self.time_steps[-1]
95
+
96
+ # Get new acceleration
97
+ acceleration = -self.mw_grad_to_acc(self.mw_gradient)
98
+
99
+ acc_normed = acceleration/np.linalg.norm(acceleration)
100
+ prev_vel_normed = prev_velocity / np.linalg.norm(prev_velocity)
101
+ ovlp = acc_normed @ prev_vel_normed
102
+ self.log(f"a @ v_i-1={ovlp:.8f}")
103
+ self.accelerations.append(acceleration)
104
+
105
+ # Calculate new coords and velocity
106
+ # Eq. (2) in [1]
107
+ new_mw_coords = (self.mw_coords
108
+ + prev_velocity*time_step
109
+ + 0.5*prev_acceleration*time_step**2
110
+ )
111
+
112
+ # Update velocity
113
+ velocity = prev_velocity + 0.5*(prev_acceleration+acceleration)*time_step
114
+ # Damp velocity
115
+ damped_velocity, damping_factor = self.damp_velocity(velocity)
116
+ self.velocities.append(damped_velocity)
117
+
118
+ if self.cur_cycle == 0:
119
+ # No error estimation in the first step
120
+ estimated_error = self.error_tol
121
+ else:
122
+ estimated_error = self.estimate_error(new_mw_coords)
123
+
124
+ # Get next time step from error estimation
125
+ new_time_step = time_step * (self.error_tol / estimated_error)**(1/3)
126
+ # Constrain time step between 0.0025 fs and 3.0 fs
127
+ new_time_step = min(new_time_step, 3.)
128
+ new_time_step = max(new_time_step, 0.025)
129
+ self.time_steps.append(new_time_step)
130
+ self.log(f"\tCur. time step={time_step:.6f} fs")
131
+ self.log(f"\tNext time step={new_time_step:.6f} fs")
132
+
133
+ # Set new coordinates
134
+ self.mw_coords = new_mw_coords
@@ -0,0 +1,22 @@
1
+ import numpy as np
2
+
3
+ from pysisyphus.irc.IRC import IRC
4
+ from pysisyphus.TableFormatter import TableFormatter
5
+
6
+
7
+ class Euler(IRC):
8
+
9
+ def __init__(self, geometry, step_length=0.01, **kwargs):
10
+ super(Euler, self).__init__(geometry, step_length, **kwargs)
11
+
12
+ step_header = "E/au ΔE(TS)/au |gradient|".split()
13
+ step_fmts = [".6f", ".6f", ".4f"]
14
+ self.step_formatter = TableFormatter(step_header, step_fmts, 10)
15
+
16
+ def step(self):
17
+ grad = self.mw_gradient
18
+ grad_norm = np.linalg.norm(grad)
19
+
20
+ # Step downhill, against the gradient
21
+ step_direction = -grad / grad_norm
22
+ self.mw_coords += self.step_length*step_direction
@@ -0,0 +1,345 @@
1
+ # [1 ] https://aip.scitation.org/doi/pdf/10.1063/1.3514202?class=pdf
2
+ # Original EulerPC
3
+ # Hratchian, Schlegel, 2010
4
+ # [2 ] https://aip.scitation.org/doi/pdf/10.1063/1.1724823?class=pdf
5
+ # Original HPC
6
+ # Hratchian, Schlegel, 2004
7
+ # [3 ] https://pubs.rsc.org/en/content/articlepdf/2017/cp/c7cp03722h
8
+ # EulerPC re-implementation
9
+ # Meisner, Kästner, 2017
10
+ # [3.1] http://www.rsc.org/suppdata/c7/cp/c7cp03722h/c7cp03722h1.pdf
11
+ # Corresponding SI
12
+ # [4 ] https://aip.scitation.org/doi/pdf/10.1063/1.3593456?class=pdf
13
+ # Hratchian, Frisch, 2011
14
+ # [6 ] https://aip.scitation.org/doi/10.1063/1.3593456<Paste>
15
+ # Hratchian, Frisch
16
+ # Further improvements for DWI; not implemented
17
+
18
+ import time
19
+ from collections import deque
20
+
21
+ import numpy as np
22
+
23
+ from pysisyphus.helpers_pure import rms
24
+ from pysisyphus.irc.DWI import DWI
25
+ from pysisyphus.irc.IRC import IRC
26
+ from pysisyphus.optimizers.hessian_updates import bfgs_update, bofill_update
27
+
28
+ import torch
29
+
30
+
31
+ class EulerPC(IRC):
32
+ def __init__(
33
+ self,
34
+ *args,
35
+ hessian_recalc=None,
36
+ hessian_update="bofill",
37
+ hessian_init="calc",
38
+ max_pred_steps=500,
39
+ loose_cycles=3,
40
+ corr_func="mbs",
41
+ **kwargs,
42
+ ):
43
+ super().__init__(*args, hessian_init=hessian_init, **kwargs)
44
+
45
+ self.hessian_recalc = hessian_recalc
46
+ self.hessian_update = {
47
+ "bfgs": bfgs_update,
48
+ "bofill": bofill_update,
49
+ }
50
+ self.hessian_update_func = self.hessian_update[hessian_update]
51
+ self.max_pred_steps = int(max_pred_steps)
52
+ self.loose_cycles = loose_cycles
53
+
54
+ corr_funcs = {
55
+ "mbs": self.corrector_step,
56
+ }
57
+ self.corr_func = corr_funcs[corr_func]
58
+
59
+ self._to_active_vec = lambda v: v[self._act_dofs]
60
+
61
+ def _to_full_vec(v_act, template):
62
+ if isinstance(v_act, torch.Tensor):
63
+ full = template.clone()
64
+ else:
65
+ full = template.copy()
66
+ full[self._act_dofs] = v_act
67
+ return full
68
+
69
+ self._to_full_vec = _to_full_vec
70
+
71
+ def prepare(self, *args, **kwargs):
72
+ super().prepare(*args, **kwargs)
73
+
74
+ # Initialize the distance weighted interpolator with the data
75
+ # from the initial displacement.
76
+ self.dwi = DWI()
77
+ mw_grad_full = self.mw_gradient
78
+ energy = self.energy
79
+
80
+ mw_grad_act = self._to_active_vec(mw_grad_full)
81
+ mw_coords_act = self._to_active_vec(self.mw_coords)
82
+
83
+ if isinstance(self.mw_hessian, torch.Tensor):
84
+ self.dwi.update(mw_coords_act, energy, mw_grad_act, self.mw_hessian.detach().clone())
85
+ else:
86
+ self.dwi.update(mw_coords_act, energy, mw_grad_act, self.mw_hessian.copy())
87
+
88
+ if self.downhill:
89
+ return
90
+
91
+ # Do a first Hessian update with the information between the TS
92
+ # and the initially displaced geometry.
93
+ dx = self._to_active_vec(self.mw_coords - self.ts_mw_coords)
94
+ dg = mw_grad_act - self._to_active_vec(self.ts_mw_gradient)
95
+ dH, key = self.hessian_update_func(self.mw_hessian, dx, dg)
96
+ self.log(f"Did {key} hessian update.")
97
+ self.mw_hessian += dH
98
+
99
+ def get_integration_length_func(self, init_mw_coords):
100
+ """
101
+ Return a closure that computes the un-mass-weighted path length Δs.
102
+ """
103
+ if init_mw_coords.shape[0] == self._m_sqrt.shape[0]:
104
+ m_sqrt_vec = self._m_sqrt # full (3N)
105
+ else:
106
+ m_sqrt_vec = self._m_sqrt[self._act_dofs] # active-only
107
+
108
+ def get_integration_length(cur_mw_coords):
109
+ return np.linalg.norm((cur_mw_coords - init_mw_coords) / m_sqrt_vec)
110
+
111
+ return get_integration_length
112
+
113
+ def step(self):
114
+ ##################
115
+ # PREDICTOR STEP #
116
+ ##################
117
+
118
+ mw_grad_full = self.mw_gradient # 3N-vector, mass-weighted
119
+ energy = self.energy
120
+ mw_grad_act = self._to_active_vec(mw_grad_full) # active slice
121
+
122
+ if self.cur_cycle > 0:
123
+ if self.hessian_recalc and (self.cur_cycle % self.hessian_recalc == 0):
124
+ self.mw_hessian = self.geometry.mw_hessian
125
+ self.geometry.clear()
126
+ self.log("Calculated excact hessian")
127
+ else:
128
+ dx = self._to_active_vec(self.mw_coords - self.irc_mw_coords[-2])
129
+ dg = mw_grad_act - self._to_active_vec(self.irc_mw_gradients[-2])
130
+ dH, key = self.hessian_update_func(self.mw_hessian, dx, dg)
131
+ if isinstance(dH, torch.Tensor):
132
+ self.mw_hessian.add_(dH)
133
+ else:
134
+ self.mw_hessian += dH
135
+ self.log(f"Did {key} hessian update before predictor step.")
136
+ if isinstance(self.mw_hessian, torch.Tensor):
137
+ self.dwi.update(
138
+ self._to_active_vec(self.mw_coords).copy(),
139
+ energy, mw_grad_act,
140
+ self.mw_hessian.detach().clone()
141
+ )
142
+ else:
143
+ self.dwi.update(
144
+ self._to_active_vec(self.mw_coords).copy(),
145
+ energy, mw_grad_act,
146
+ self.mw_hessian.copy()
147
+ )
148
+
149
+ # Create a copy of the inital coordinates for the determination
150
+ # of the actual step size in the predictor Euler integration.
151
+ init_mw_coords = self.mw_coords.copy()
152
+
153
+ get_integration_length = self.get_integration_length_func(init_mw_coords)
154
+
155
+ # Calculate predictor Euler-integration step length. See get_conv_fact
156
+ # method definition for a comment on this.
157
+ conv_fact = self.get_conv_fact(mw_grad_act)
158
+ euler_step_length = self.step_length / (self.max_pred_steps / conv_fact)
159
+
160
+ def taylor_gradient(step_full):
161
+ """Return full-length gradient via 2nd-order Taylor (active Hessian)."""
162
+ step_act = self._to_active_vec(step_full)
163
+ if isinstance(self.mw_hessian, torch.Tensor):
164
+ step_t = torch.tensor(step_act, dtype=self.mw_hessian.dtype,
165
+ device=self.mw_hessian.device)
166
+ hvp_act = (self.mw_hessian @ step_t).cpu().numpy()
167
+ else:
168
+ hvp_act = self.mw_hessian @ step_act
169
+ grad_act = mw_grad_act + hvp_act
170
+ return self._to_full_vec(grad_act, step_full)
171
+
172
+ # These variables will hold the coordinates and gradients along
173
+ # the Euler integration and will be updated frequently.
174
+ euler_mw_coords = self.mw_coords.copy()
175
+ euler_mw_grad = mw_grad_full.copy()
176
+ self.log(
177
+ f"Predictor-Euler-integration with Δs={euler_step_length:.6f} "
178
+ f"for up to {self.max_pred_steps} steps\n # |step| d|step|"
179
+ )
180
+ prev_cur_length = 0.0
181
+ for i in range(self.max_pred_steps):
182
+ # Calculate step length in non-mass-weighted coordinates
183
+ cur_length = get_integration_length(euler_mw_coords)
184
+ if i % 50 == 0:
185
+ diff = cur_length - prev_cur_length
186
+ self.log(f"\t{i:03d}: {cur_length:.4f} Δ={diff:.4f}")
187
+ prev_cur_length = cur_length
188
+
189
+ # Check if we achieved the desired step length.
190
+ if cur_length >= self.step_length:
191
+ self.log(
192
+ "Predictor-Euler integration converged with "
193
+ f"Δs={cur_length:.4f} (desired Δs={self.step_length:.4f}) "
194
+ f"after {i+1} steps!"
195
+ )
196
+ break
197
+ grad_norm = np.linalg.norm(euler_mw_grad)
198
+ if not np.isfinite(grad_norm) or grad_norm == 0.0:
199
+ self.log("Gradient norm is zero/NaN; using transition vector direction.")
200
+ direction = getattr(self, "mw_transition_vector", euler_mw_grad)
201
+ dir_norm = np.linalg.norm(direction)
202
+ if not np.isfinite(dir_norm) or dir_norm == 0.0:
203
+ raise ValueError("Cannot determine IRC step direction (zero/NaN norms).")
204
+ step_ = euler_step_length * -direction / dir_norm
205
+ else:
206
+ step_ = euler_step_length * -euler_mw_grad / grad_norm
207
+ euler_mw_coords += step_
208
+ # Determine actual step by comparing the current and the initial coordinates
209
+ euler_step = euler_mw_coords - init_mw_coords
210
+ euler_mw_grad = taylor_gradient(euler_step)
211
+ else:
212
+ self.log(
213
+ f"Predictor-Euler integration did not converge in {i+1} "
214
+ f"steps. Δs={cur_length:.4f}."
215
+ )
216
+
217
+ # Check if we are already sufficiently converged. If so signal
218
+ # convergence.
219
+ self.mw_coords = euler_mw_coords
220
+
221
+ # Use rms of gradient from taylor expansion for convergence check.
222
+ euler_grad = self.unweight_vec(euler_mw_grad)
223
+ rms_grad = rms(euler_grad)
224
+
225
+ # Or check true gradient? But this would need an additional calculation,
226
+ # so I disabled it for now.
227
+ # rms_grad = rms(self.gradient)
228
+
229
+ if self.cur_cycle < self.loose_cycles:
230
+ self.log(
231
+ f"Current cycle {self.cur_cycle} is still in 'loose' mode.\n"
232
+ "Continuing IRC integration even though predictor integration "
233
+ f"did not succeed.\n{self.loose_cycles - self.cur_cycle - 1} loose "
234
+ "cycles remaining."
235
+ )
236
+ # elif rms_grad <= 5*self.rms_grad_thresh:
237
+ elif rms_grad <= self.rms_grad_thresh:
238
+ self.log("Sufficient convergence achieved on rms(grad)")
239
+ self.converged = True
240
+ return
241
+ self.log("")
242
+
243
+ # Calculate energy and gradient at new predicted geometry. Update the
244
+ # hessian accordingly. These results will be added to the DWI for use
245
+ # in the corrector step.
246
+ self.mw_coords = euler_mw_coords
247
+ self.log("Calculating energy and gradient at predictor step geometry.")
248
+ mw_grad_full = self.mw_gradient
249
+ energy = self.energy
250
+ mw_grad_act = self._to_active_vec(mw_grad_full)
251
+
252
+ # Hessian update
253
+ dx = self._to_active_vec(self.mw_coords - self.irc_mw_coords[-1])
254
+ dg = mw_grad_act - self._to_active_vec(self.irc_mw_gradients[-1])
255
+ dH, key = self.hessian_update_func(self.mw_hessian, dx, dg)
256
+ self.mw_hessian += dH
257
+ self.log(f"Did {key} hessian update after predictor step.\n")
258
+ if isinstance(self.mw_hessian, torch.Tensor):
259
+ self.dwi.update(
260
+ self._to_active_vec(self.mw_coords).copy(),
261
+ energy, mw_grad_act,
262
+ self.mw_hessian.detach().clone()
263
+ )
264
+ else:
265
+ self.dwi.update(
266
+ self._to_active_vec(self.mw_coords).copy(),
267
+ energy, mw_grad_act,
268
+ self.mw_hessian.copy()
269
+ )
270
+
271
+ corrected_mw_coords_act = self.corr_func(self._to_active_vec(init_mw_coords), self.step_length, self.dwi)
272
+ self.mw_coords = self._to_full_vec(corrected_mw_coords_act, self.mw_coords)
273
+ corr_step_length = get_integration_length(self.mw_coords)
274
+ self.log(f"Corrected unweighted step length: {corr_step_length:.6f}")
275
+
276
+ def corrector_step(self, init_mw_coords, step_length, dwi):
277
+ self.log("Corrector step using mBS integration (active dofs)")
278
+
279
+ get_integration_length = self.get_integration_length_func(init_mw_coords)
280
+
281
+ errors = list()
282
+ richardson = dict()
283
+ for k in range(15):
284
+ points = 20 * (2**k)
285
+ corr_step_length = step_length / (points - 1)
286
+ cur_coords = init_mw_coords.copy()
287
+ # Only keep the last 2 coords (needed for oscillation check)
288
+ k_coords = deque(maxlen=2)
289
+ cur_length = 0
290
+
291
+ # Integrate until the desired spacing is reached
292
+ while True:
293
+ k_coords.append(cur_coords.copy())
294
+ if abs(step_length - cur_length) < 0.5 * corr_step_length:
295
+ self.log(
296
+ f"\tk={k:02d} points={points: >4d} "
297
+ f"step_length={corr_step_length:.4f} Δs={cur_length:.4f}"
298
+ )
299
+ break
300
+
301
+ energy, gradient = dwi.interpolate(cur_coords, gradient=True)
302
+ cur_coords += corr_step_length * -gradient / np.linalg.norm(gradient)
303
+ # cur_length += corr_step_length
304
+ cur_length = get_integration_length(cur_coords)
305
+
306
+ # Check for oscillation
307
+ if len(k_coords) >= 2:
308
+ prev_coords = k_coords[-2]
309
+ osc_norm = np.linalg.norm(cur_coords - prev_coords)
310
+ # TODO: Handle this by restarting everything with a smaller stepsize?
311
+ # Check 10.1039/c7cp03722h SI
312
+ if osc_norm <= corr_step_length:
313
+ self.log(
314
+ "\tDetected oscillation in Corrector-Euler "
315
+ f"integration for k={k:02d} and {points} points.\n"
316
+ "\tAborting corrector integration!"
317
+ )
318
+ return prev_coords
319
+ richardson[(k, 0)] = cur_coords
320
+
321
+ # Refine using Richardson extrapolation
322
+ # Set additional values using Richard extrapolation
323
+ for j in range(1, k + 1):
324
+ richardson[(k, j)] = (
325
+ (2**j) * richardson[(k, j - 1)] - richardson[(k - 1, j - 1)]
326
+ ) / (2**j - 1)
327
+ # Can only be done after the second successful integration
328
+ if k > 0:
329
+ # Error estimate according to Numerical Recipes Eq. (17.3.9).
330
+ # We compare the last two entries/columns in the current row.
331
+ # RMS error
332
+ error = np.sqrt(
333
+ np.mean((richardson[(k, k)] - richardson[(k, k - 1)]) ** 2)
334
+ )
335
+ errors.append(error)
336
+ if error <= 1e-5:
337
+ self.log(f"mBS integration converged (error={error:.4e})!")
338
+ break
339
+ else:
340
+ raise Exception("Richardson did not converge!")
341
+
342
+ self.log(
343
+ f"Returning corrected mass-weighted coordinates from richardson[({k},{k})]"
344
+ )
345
+ return richardson[(k, k)]