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,199 @@
1
+ # [1] https://aip.scitation.org/doi/pdf/10.1063/1.1523908
2
+ # Neugebauer, Reiher 2002
3
+ # [2] https://reiher.ethz.ch/software/akira.html
4
+
5
+
6
+ from collections import namedtuple
7
+ import sys
8
+
9
+ import numpy as np
10
+
11
+ from pysisyphus.helpers_pure import eigval_to_wavenumber
12
+ from pysisyphus.Geometry import get_trans_rot_projector
13
+ from pysisyphus.modefollow.NormalMode import NormalMode
14
+ from pysisyphus.TablePrinter import TablePrinter
15
+
16
+
17
+ DavidsonResult = namedtuple(
18
+ "DavidsonResult",
19
+ "cur_cycle converged final_modes qs nus mode_inds res_rms",
20
+ )
21
+
22
+
23
+ def forces_fin_diff(forces_getter, coords, b, step_size):
24
+ plus = forces_getter(coords + b)
25
+ minus = forces_getter(coords - b)
26
+ fd = (minus - plus) / (2 * step_size)
27
+ return fd
28
+
29
+
30
+ def block_davidson(
31
+ cart_coords,
32
+ masses,
33
+ forces_getter,
34
+ guess_modes,
35
+ lowest=None,
36
+ trial_step_size=0.01,
37
+ hessian_precon=None,
38
+ max_cycles=25,
39
+ res_rms_thresh=1e-4,
40
+ start_precon=5,
41
+ remove_trans_rot=True,
42
+ print_level=1,
43
+ ):
44
+ num = len(guess_modes)
45
+ B_full = np.zeros((len(guess_modes[0]), num * max_cycles))
46
+ S_full = np.zeros_like(B_full)
47
+ I = np.eye(cart_coords.size)
48
+ masses_rep = np.repeat(masses, 3)
49
+ msqrt = np.sqrt(masses_rep)
50
+
51
+ # Projector to remove translation and rotation
52
+ P = get_trans_rot_projector(cart_coords, masses, full=True)
53
+ guess_modes = [
54
+ NormalMode(P.dot(mode.l_mw) / msqrt, masses_rep) for mode in guess_modes
55
+ ]
56
+
57
+ col_fmts = "int int int float_short float str".split()
58
+ header = ("#", "subspace size", "mode", "ṽ / cm⁻¹", "rms(r)", "Conv")
59
+ fmts_update = {"float_short": "{: >11.2f}"}
60
+ table = TablePrinter(header, col_fmts, width=11, fmts_update=fmts_update)
61
+ if print_level == 1:
62
+ table.print_header()
63
+
64
+ b_prev = np.array([mode.l_mw for mode in guess_modes]).T
65
+ for i in range(max_cycles):
66
+ # Add new basis vectors to B matrix
67
+ b = np.array([mode.l_mw for mode in guess_modes]).T
68
+ from_ = i * num
69
+ to_ = (i + 1) * num
70
+ B_full[:, from_:to_] = b
71
+
72
+ # Estimate action of Hessian by finite differences.
73
+ for j in range(num):
74
+ mode = guess_modes[j]
75
+ # Get a step size in mass-weighted coordinates that results
76
+ # in the desired 'trial_step_size' in not-mass-weighted coordinates.
77
+ mw_step_size = mode.mw_norm_for_norm(trial_step_size)
78
+ # Actual step in non-mass-weighted coordinates
79
+ step = trial_step_size * mode.l
80
+ S_full[:, from_ + j] = (
81
+ # Convert to mass-weighted coordinates
82
+ forces_fin_diff(forces_getter, cart_coords, step, mw_step_size)
83
+ / msqrt
84
+ )
85
+
86
+ # Views on columns that are actually set
87
+ B = B_full[:, :to_]
88
+ S = S_full[:, :to_]
89
+
90
+ # Calculate and symmetrize approximate hessian
91
+ Hm = B.T.dot(S)
92
+ Hm = (Hm + Hm.T) / 2
93
+ # Diagonalize small Hessian
94
+ w, v = np.linalg.eigh(Hm)
95
+
96
+ # Approximations to exact eigenvectors in current cycle
97
+ approx_modes = (v * B[:, :, None]).sum(axis=1).T
98
+ # Calculate overlaps between previous root and the new approximate
99
+ # normal modes for root following.
100
+ if lowest is None:
101
+ # 2D overlap array. approx_modes in row, b_prev in columns.
102
+ overlaps = np.einsum("ij,jk->ik", approx_modes, b_prev)
103
+ mode_inds = np.abs(overlaps).argmax(axis=0)
104
+ else:
105
+ mode_inds = np.arange(lowest)
106
+ b_prev = approx_modes[mode_inds].T
107
+
108
+ # Eq. (7) in [1]
109
+ residues = (v * (S[:, :, None] - w * B[:, :, None])).sum(axis=1)
110
+
111
+ # Determine preconditioner matrix
112
+ #
113
+ # Use supplied matrix
114
+ if hessian_precon is not None:
115
+ precon_mat = hessian_precon
116
+ # Reconstruct Hessian, but only start after some cycles
117
+ elif i >= start_precon:
118
+ precon_mat = B.dot(Hm).dot(B.T)
119
+ # No preconditioning if no matrix was supplied or we are in an early cycle.
120
+ else:
121
+ precon_mat = None
122
+
123
+ # Construct new basis vector from residuum of selected mode
124
+ b = np.zeros_like(b_prev)
125
+ for j, mode_ind in enumerate(mode_inds):
126
+ r = residues[:, mode_ind]
127
+ if precon_mat is not None:
128
+ # Construct actual preconditioner X
129
+ X = np.linalg.pinv(precon_mat - w[mode_ind] * I, rcond=1e-8)
130
+ b[:, j] = X.dot(r)
131
+ else:
132
+ b[:, j] = r
133
+
134
+ # Project out translation and rotation from new mode guess
135
+ if remove_trans_rot:
136
+ b = P.dot(b)
137
+ # Orthogonalize new vectors against preset vectors
138
+ b = np.linalg.qr(np.concatenate((B, b), axis=1))[0][:, -num:]
139
+
140
+ # New NormalMode from non-mass-weighted displacements
141
+ guess_modes = [NormalMode(b_ / msqrt, masses_rep) for b_ in b.T]
142
+
143
+ # Calculate wavenumbers
144
+ nus = eigval_to_wavenumber(w)
145
+
146
+ # Check convergence criteria
147
+ max_res = np.abs(residues).max(axis=0)
148
+ res_rms = np.sqrt(np.mean(residues ** 2, axis=0))
149
+
150
+ converged = res_rms < res_rms_thresh
151
+ # Print progress if requested
152
+ if print_level == 2:
153
+ print(f"Cycle {i:02d}")
154
+ print("\t # | ṽ / cm⁻¹| rms(r) | max(|r|) ")
155
+ print("\t------------------------------------------")
156
+ for j, (nu, rms, mr) in enumerate(zip(nus, res_rms, max_res)):
157
+ sel_str = "*" if (j in mode_inds) else " "
158
+ conv_str = "✓" if converged[j] else ""
159
+ print(
160
+ f"\t{j:02d}{sel_str} | {nu:> 10.2f} | {rms:.8f} | {mr:.8f} {conv_str}"
161
+ )
162
+ print()
163
+ elif print_level == 1:
164
+ for j in mode_inds:
165
+ conv_str = "✓" if converged[j] else "✗"
166
+ table.print_row((i, B.shape[1], j, nus[j], res_rms[j], conv_str))
167
+
168
+ # Convergence is signalled using only the roots we are actually interested in
169
+ modes_converged = all(converged[mode_inds])
170
+ if modes_converged:
171
+ if print_level > 0:
172
+ print(f"\tDavidson procedure converged in {i+1} cycles!")
173
+ if lowest is not None:
174
+ nus_str = np.array2string(nus[mode_inds], precision=2)
175
+ print(f"\tLowest {lowest} wavenumbers: {nus_str} cm⁻¹")
176
+ neg_nus = sum(nus[mode_inds] < 0)
177
+ type_ = "minimum" if (neg_nus == 0) else f"saddle point of index {neg_nus}"
178
+ print(f"\tThis geometry seems to be a {type_} on the PES.")
179
+ break
180
+ sys.stdout.flush()
181
+
182
+ result = DavidsonResult(
183
+ cur_cycle=i,
184
+ converged=modes_converged,
185
+ final_modes=guess_modes,
186
+ qs=approx_modes,
187
+ nus=nus,
188
+ mode_inds=mode_inds,
189
+ res_rms=res_rms,
190
+ )
191
+
192
+ return result
193
+
194
+
195
+ def geom_davidson(geom, *args, **kwargs):
196
+ def forces_getter(cart_coords):
197
+ return geom.get_energy_and_cart_forces_at(cart_coords)["forces"]
198
+
199
+ return block_davidson(geom.cart_coords, geom.masses, forces_getter, *args, **kwargs)
@@ -0,0 +1,95 @@
1
+ import numpy as np
2
+
3
+
4
+ # [1] https://doi.org/10.1063/1.1809574
5
+
6
+
7
+ def lanczos(coords, grad_getter, dx=5e-3, dl=1e-2, guess=None, max_cycles=25,
8
+ reortho=True, logger=None):
9
+ """Lanczos method to determine smallest eigenvalue & -vector.
10
+
11
+ See [1] for description of algorithm.
12
+ """
13
+
14
+ def log(msg):
15
+ if logger is not None:
16
+ logger.debug(msg)
17
+
18
+ log("Lanczos Algorithm")
19
+ r_prev = guess
20
+ if r_prev is None:
21
+ r_prev = np.random.rand(coords.size)
22
+ beta_prev = np.linalg.norm(r_prev)
23
+ q_prev = np.zeros_like(r_prev)
24
+
25
+ alphas = list()
26
+ betas = list()
27
+ w_mins = list()
28
+ Q = np.zeros((coords.size, max_cycles))
29
+ qs = list()
30
+ # Gradient at current coordinates; does not change.
31
+ grad_l = grad_getter(coords)
32
+ for i in range(max_cycles):
33
+ # Normalize q
34
+ q = r_prev / beta_prev
35
+ Q[:,i] = q
36
+ qs.append(q.copy())
37
+ # Approximate action of Hessian on q (u = Hq) by finite differences.
38
+ #
39
+ # Gradient at perturbed coordinates
40
+ grad_k = grad_getter(coords + dx*q)
41
+ u = (grad_k - grad_l) / dx
42
+ # Residue
43
+ r = u - beta_prev*q_prev
44
+ alpha = q.dot(r)
45
+ r -= alpha*q
46
+ # Reorthogonalization of r against the present Lanczos vectors in Q
47
+ if reortho:
48
+ r -= Q.dot(Q.T.dot(r))
49
+ beta = np.linalg.norm(r)
50
+
51
+ alphas.append(alpha)
52
+ betas.append(beta)
53
+ size = len(alphas)
54
+ # Construct tri-diagonal matrix T
55
+ T = np.zeros((size, size))
56
+ diag_inds = np.diag_indices(size)
57
+ T[diag_inds] = alphas
58
+ if len(alphas) > 1:
59
+ for j, b in enumerate(betas[:-1], 1):
60
+ k = j-1
61
+ T[j,k] = b
62
+ T[k,j] = b
63
+
64
+ # Values for next cycle
65
+ beta_prev = beta
66
+ q_prev = q
67
+ r_prev = r
68
+ # Diagonalize T
69
+ w, v = np.linalg.eigh(T)
70
+ w_min = w[0]
71
+ log(f"Cycle {i: >3d}: w_min={w_min: .6f}")
72
+
73
+ # Check eigenvalue convergence
74
+ if (i > 0) and (abs((w_min - w_mins[-1])/w_mins[-1]) < dl):
75
+ log("Converged")
76
+ break
77
+
78
+ w_mins.append(w_min)
79
+ v_min = v[:,0]
80
+
81
+ # Form eigenvector from linear combination of Lanczos vectors
82
+ eigenvector = (v_min[:,None] * qs).sum(axis=0)
83
+ eigenvector /= np.linalg.norm(eigenvector)
84
+ return w_min, eigenvector
85
+
86
+
87
+ def geom_lanczos(geom, *args, **kwargs):
88
+ """Wraps Lanczos algorithm for use with Geometry objects."""
89
+ coords = geom.coords.copy()
90
+
91
+ def grad_getter(coords):
92
+ results = geom.get_energy_and_forces_at(coords)
93
+ return -results["forces"]
94
+
95
+ return lanczos(coords, grad_getter, *args, **kwargs)
@@ -0,0 +1,99 @@
1
+ import numpy as np
2
+
3
+ from pysisyphus.optimizers.Optimizer import Optimizer
4
+ from pysisyphus.optimizers.hessian_updates import double_damp
5
+ from pysisyphus.optimizers.restrict_step import scale_by_max_step
6
+
7
+
8
+ # [1] Nocedal, Wright - Numerical Optimization, 2006
9
+ # [2] http://dx.doi.org/10.1016/j.jcp.2013.08.044
10
+ # Badreddine, 2013
11
+ # [3] https://arxiv.org/abs/2006.08877
12
+ # Goldfarb, 2020
13
+
14
+
15
+ class BFGS(Optimizer):
16
+
17
+ def __init__(self, geometry, *args, update="bfgs", **kwargs):
18
+ super().__init__(geometry, *args, **kwargs)
19
+
20
+ assert self.align == False, \
21
+ "align=True does not work with this optimizer! Consider using LBFGS."
22
+
23
+ self.update = update
24
+
25
+ update_funcs = {
26
+ "bfgs": self.bfgs_update,
27
+ "damped": self.damped_bfgs_update,
28
+ "double": self.double_damped_bfgs_update,
29
+ }
30
+ self.update_func = update_funcs[self.update]
31
+
32
+ def prepare_opt(self):
33
+ # Inverse Hessian
34
+ self.H = self.eye
35
+
36
+ @property
37
+ def eye(self):
38
+ size = self.geometry.coords.size
39
+ if not hasattr(self, '_eye_cache') or self._eye_cache.shape[0] != size:
40
+ self._eye_cache = np.eye(size)
41
+ return self._eye_cache
42
+
43
+ def bfgs_update(self, s, y):
44
+ rho = 1 / s.dot(y)
45
+ V = self.eye - rho*np.outer(s, y)
46
+ self.H = V.dot(self.H).dot(V.T) + rho*np.outer(s, s)
47
+
48
+ def double_damped_bfgs_update(self, s, y, mu_1=0.2, mu_2=0.2):
49
+ """Double damped BFGS update of inverse Hessian.
50
+
51
+ See [3]. Potentially updates s and y."""
52
+
53
+ # Call using the inverse Hessian 'H'
54
+ s, y = double_damp(s, y, H=self.H, mu_1=mu_1, mu_2=mu_2,
55
+ logger=self.logger)
56
+ self.log(f"s·y={s.dot(y):.6f} (damped)")
57
+ self.bfgs_update(s, y)
58
+
59
+ def damped_bfgs_update(self, s, y, mu_1=0.2):
60
+ """Damped BFGS update of inverse Hessian.
61
+
62
+ Potentially updates s.
63
+ See Section 3.2 of [2], Eq. (30) - (33). There is a typo ;)
64
+ It should be
65
+ H_{k+1} = V_k H_k V_k^T + ...
66
+ instead of
67
+ H_{k+1} = V_k^T H_k V_k + ...
68
+ """
69
+ self.double_damped_bfgs_update(s, y, mu_2=None)
70
+
71
+ def optimize(self):
72
+ forces = self.geometry.forces
73
+ energy = self.geometry.energy
74
+ self.forces.append(forces)
75
+ self.energies.append(energy)
76
+
77
+ if self.cur_cycle > 0:
78
+ # Gradient difference
79
+ y = self.forces[-2] - forces
80
+ # Coordinate difference / step
81
+ s = self.steps[-1]
82
+ # Curvature condition
83
+ sy = s.dot(y)
84
+ self.log(f"s·y={sy:.6f} (undamped)")
85
+ # Hessian update
86
+ self.update_func(s, y)
87
+
88
+ # Results in simple SD step in the first cycle
89
+ step = self.H.dot(forces)
90
+ self.log(f"Calcualted {self.update} step")
91
+
92
+ # Step restriction
93
+ unscaled_norm = np.linalg.norm(step)
94
+ step = scale_by_max_step(step, self.max_step)
95
+ scaled_norm = np.linalg.norm(step)
96
+ self.log(f"Unscaled norm(step)={unscaled_norm:.4f}")
97
+ self.log(f" Scaled norm(step)={scaled_norm:.4f}")
98
+
99
+ return step
@@ -0,0 +1,113 @@
1
+ import numpy as np
2
+
3
+ from pysisyphus.optimizers.Optimizer import Optimizer
4
+
5
+ class BacktrackingOptimizer(Optimizer):
6
+
7
+ def __init__(self, geometry, alpha, bt_force=5,
8
+ dont_skip_after=2, bt_max_scale=4,
9
+ bt_disable=False, **kwargs):
10
+ # Setting some default values
11
+ self.alpha = alpha
12
+ assert(self.alpha > 0), "Alpha must be positive!"
13
+ self.bt_force = bt_force
14
+ self.dont_skip_after = dont_skip_after
15
+ self.bt_max_scale = bt_max_scale
16
+ self.bt_disable = bt_disable
17
+ assert(self.dont_skip_after >= 1)
18
+ self.cycles_since_backtrack = self.bt_force
19
+ self.scale_factor = 0.5
20
+
21
+ super(BacktrackingOptimizer, self).__init__(geometry, **kwargs)
22
+
23
+ self.alpha0 = self.alpha
24
+ self.alpha_max = self.bt_max_scale * self.alpha0
25
+
26
+ # Keep the skipping history to avoid infinite skipping, e.g. always
27
+ # return skip = False if we already skipped in the last n iterations.
28
+ self.skip_log = list()
29
+
30
+ def _get_opt_restart_info(self):
31
+ opt_restart_info = {
32
+ "alpha": self.alpha,
33
+ "cycles_since_backtrack": self.cycles_since_backtrack,
34
+ }
35
+ return opt_restart_info
36
+
37
+ def _set_opt_restart_info(self, opt_restart_info):
38
+ self.alpha = opt_restart_info["alpha"]
39
+ self.cycles_since_backtrack = opt_restart_info["cycles_since_backtrack"]
40
+
41
+ def reset(self):
42
+ if self.alpha > self.alpha0:
43
+ self.alpha = self.alpha0
44
+ self.log(f"Resetting! Current alpha is {self.alpha}. Lowering "
45
+ f"it to {self.alpha0}.")
46
+
47
+ def backtrack(self, cur_forces, prev_forces, reset_hessian=None):
48
+ """Accelerated backtracking line search."""
49
+ if self.bt_disable:
50
+ return False
51
+
52
+ epsilon = 1e-3
53
+
54
+ rms = lambda f: np.sqrt(np.mean(np.square(f)))
55
+ cur_rms_force = rms(cur_forces)
56
+ prev_rms_force = rms(prev_forces)
57
+
58
+ rms_diff = (
59
+ (cur_rms_force - prev_rms_force) /
60
+ np.abs(cur_rms_force + prev_rms_force)
61
+ )
62
+
63
+ # Skip tells us if we overshot
64
+ skip = False
65
+
66
+ # When the optimiziation is converging cur_forces will
67
+ # be smaller than prev_forces, so rms_diff will be negative
68
+ # and hence smaller than epsilon, which is a positive number.
69
+
70
+ # We went uphill, slow alpha
71
+ self.log(f"Backtracking: rms_diff = {rms_diff:.03f}")
72
+ if rms_diff > epsilon:
73
+ self.log(f"Scaling alpha with {self.scale_factor:.03f}")
74
+ # self.alpha = max(self.alpha0*.5, self.alpha*self.scale_factor)
75
+ self.alpha *= self.scale_factor
76
+ skip = True
77
+ self.cycles_since_backtrack = self.bt_force
78
+ # We continnue going downhill, rms_diff is smaller than epsilon
79
+ else:
80
+ self.cycles_since_backtrack -= 1
81
+ # Check if we didn't accelerate in the previous cycles
82
+ if self.cycles_since_backtrack < 0:
83
+ self.cycles_since_backtrack = self.bt_force
84
+ if self.alpha < self.alpha0:
85
+ # Reset alpha
86
+ self.alpha = self.alpha0
87
+ skip = True
88
+ self.log(f"Reset alpha to alpha0 = {self.alpha0:.4f}")
89
+ else:
90
+ # Accelerate alpha
91
+ self.alpha /= self.scale_factor
92
+ self.log(f"Scaled alpha to {self.alpha:.4f}")
93
+
94
+ # Avoid huge alphas
95
+ if self.alpha > self.alpha_max:
96
+ self.alpha = self.alpha_max
97
+ self.log("Didn't accelerate as alpha would become too large. "
98
+ f"keeping it at {self.alpha}.")
99
+
100
+ # Don't skip if we already skipped the previous iterations to
101
+ # avoid infinite skipping.
102
+ if ((len(self.skip_log) >= self.dont_skip_after)
103
+ and all(self.skip_log[-self.dont_skip_after:])):
104
+ self.log(f"already skipped last {self.dont_skip_after} "
105
+ "iterations don't skip now.")
106
+ skip = False
107
+ if self.alpha > self.alpha0:
108
+ self.alpha = self.alpha0
109
+ self.log("Resetted alpha to alpha0.")
110
+ self.skip_log.append(skip)
111
+ self.log(f"alpha = {self.alpha:.4f}, skip = {skip}")
112
+
113
+ return skip
@@ -0,0 +1,98 @@
1
+ import numpy as np
2
+
3
+ from pysisyphus.optimizers.BacktrackingOptimizer import BacktrackingOptimizer
4
+
5
+ # http://ikuz.eu/2015/04/15/the-concept-of-conjugate-gradient-descent-in-python/
6
+
7
+
8
+ class ConjugateGradient(BacktrackingOptimizer):
9
+ def __init__(self, geometry, alpha=0.1, formula="FR", dont_skip=True, **kwargs):
10
+ super().__init__(geometry, alpha=alpha, **kwargs)
11
+
12
+ self.formula = formula
13
+ self.dont_skip = dont_skip
14
+
15
+ def prepare_opt(self):
16
+ if self.is_cos and self.align:
17
+ self.procrustes()
18
+ # Calculate initial forces before the first iteration
19
+ self.coords.append(self.geometry.coords)
20
+ self.forces.append(self.geometry.forces)
21
+
22
+ def reset(self):
23
+ super().reset()
24
+
25
+ # Check if the number of coordinates changed
26
+ if self.forces[-1].size != self.geometry.coords.size:
27
+ new_forces = self.geometry.forces
28
+ self.forces.append(new_forces)
29
+ self.resetted = True
30
+
31
+ def get_beta(self, cur_forces, prev_forces):
32
+ # Fletcher-Reeves formula
33
+ if self.formula == "FR":
34
+ beta = cur_forces.dot(cur_forces) / prev_forces.dot(prev_forces)
35
+ # Polak-Ribiere
36
+ elif self.formula == "PR":
37
+ beta = -cur_forces.dot(prev_forces - cur_forces) / prev_forces.dot(
38
+ prev_forces
39
+ )
40
+ beta_old = cur_forces.dot(cur_forces - prev_forces) / prev_forces.dot(
41
+ prev_forces
42
+ )
43
+ self.log(f"beta_old={beta_old:.4f}, beta={beta:.4f}")
44
+ if beta < 0:
45
+ self.log(f"beta = {beta:.04f} < 0, resetting to 0")
46
+ # beta = 0 basically restarts CG, as no previous step
47
+ # information is mixed into the current step.
48
+ beta = 0
49
+ return beta
50
+
51
+ def optimize(self):
52
+ cur_forces = self.forces[-1]
53
+
54
+ if not self.resetted and self.cur_cycle > 0:
55
+ prev_forces = self.forces[-2]
56
+ beta = self.get_beta(cur_forces, prev_forces)
57
+ self.log(f"beta = {beta:.06f}")
58
+ if np.isinf(beta):
59
+ beta = 1.0
60
+ step = cur_forces + beta * self.steps[-1]
61
+ else:
62
+ # Start with steepest descent in the first iteration
63
+ step = cur_forces
64
+ self.resetted = False
65
+
66
+ step = self.alpha * step
67
+ step = self.scale_by_max_step(step)
68
+
69
+ cur_coords = self.geometry.coords
70
+ new_coords = cur_coords + step
71
+ self.geometry.coords = new_coords
72
+
73
+ new_forces = self.geometry.forces
74
+ new_energy = self.geometry.energy
75
+
76
+ skip = self.backtrack(new_forces, cur_forces)
77
+ # Imho backtracking gives bad results here, so only use it if
78
+ # explicitly requested (self.dont_skip == False).
79
+ if (not self.dont_skip) and skip:
80
+ self.geometry.coords = cur_coords
81
+ return None
82
+
83
+ if self.align and self.is_cos:
84
+ (new_forces, cur_forces, step), _, _ = self.fit_rigid(
85
+ vectors=(new_forces, cur_forces, step),
86
+ )
87
+ # Minus step???
88
+ new_coords = self.geometry.coords - step
89
+ self.geometry.coords = new_coords
90
+ # Set the calculated properties on the rotated geometries
91
+ self.geometry.energy = new_energy
92
+ self.geometry.forces = new_forces
93
+
94
+ self.forces[-1] = cur_forces
95
+ self.forces.append(new_forces)
96
+ self.energies.append(new_energy)
97
+
98
+ return step
@@ -0,0 +1,75 @@
1
+ # [1] https://arxiv.org/abs/2112.02089
2
+ # Regularized Newton Method with Global O(1/k²) Convergence
3
+ # Konstantin Mishchenko
4
+
5
+ from math import sqrt
6
+
7
+ import numpy as np
8
+
9
+ from pysisyphus.optimizers.HessianOptimizer import HessianOptimizer
10
+ from pysisyphus.optimizers.exceptions import OptimizationError
11
+
12
+
13
+ class CubicNewton(HessianOptimizer):
14
+ def __init__(self, geometry, **kwargs):
15
+ # Force-disable trust radius update, as this optimizers uses a line search
16
+ kwargs["trust_update"] = False
17
+ super().__init__(geometry, **kwargs)
18
+
19
+ self.line_search_cycles = 0
20
+
21
+ def optimize(self):
22
+ energy, gradient, hessian, *_ = self.housekeeping()
23
+
24
+ if self.cur_cycle == 0:
25
+ # Initial Lipschitz constant estimate; line 2 in algorithm 2 in [1]
26
+ trial_step_length = 0.1
27
+ trial_step = trial_step_length * (-gradient / np.linalg.norm(gradient))
28
+ trial_coords = self.geometry.coords + trial_step
29
+ trial_results = self.geometry.get_energy_and_forces_at(trial_coords)
30
+ trial_gradient = -trial_results["forces"]
31
+ H = (
32
+ np.linalg.norm(trial_gradient - gradient - hessian.dot(trial_step))
33
+ / np.linalg.norm(trial_step) ** 2
34
+ )
35
+ else:
36
+ H = self.H_prev / 4
37
+ self.log(f"Lipschitz constant in cycle {self.cur_cycle}, H={H:.4f}")
38
+
39
+ for i in range(self.max_micro_cycles):
40
+ self.line_search_cycles += 1
41
+ H *= 2
42
+ self.log(f"Adaptive Newton line search, cycle {i} using H={H:.4f}")
43
+ lambda_ = sqrt(H * np.linalg.norm(gradient))
44
+ # Instead of solving the linear system we could also use the
45
+ # eigenvectors/-values from housekeeping(). Currently, they are
46
+ # gathered in '*_' and not used.
47
+ trial_step = np.linalg.solve(
48
+ hessian + lambda_ * np.eye(gradient.size), -gradient
49
+ )
50
+ trial_step_norm = np.linalg.norm(trial_step)
51
+ trial_coords = self.geometry.coords + trial_step
52
+
53
+ trial_results = self.geometry.get_energy_and_forces_at(trial_coords)
54
+ trial_gradient = -trial_results["forces"]
55
+ trial_energy = trial_results["energy"]
56
+
57
+ trial_gradient_small_enough = (
58
+ np.linalg.norm(trial_gradient) <= 2 * lambda_ * trial_step_norm
59
+ )
60
+ sufficient_energy_lowering = (
61
+ trial_energy <= energy - 2 / 3 * lambda_ * trial_step_norm ** 2
62
+ )
63
+
64
+ if trial_gradient_small_enough and sufficient_energy_lowering:
65
+ step = trial_step
66
+ break
67
+ else:
68
+ raise OptimizationError("Adaptive Newton line search failed!")
69
+
70
+ self.H_prev = H
71
+
72
+ return step
73
+
74
+ def postprocess_opt(self):
75
+ self.log(f"Line search cycles: {self.line_search_cycles}")