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,219 @@
1
+ # [1] https://pubs.acs.org/doi/10.1021/j100338a027
2
+ # Koeski, Gordon, 1989
3
+ # [2] https://aip.scitation.org/doi/abs/10.1063/1.459634
4
+ # Page, Doubleday, McIver, 1990
5
+
6
+ from collections import namedtuple
7
+
8
+ import h5py
9
+ import numpy as np
10
+ from scipy.optimize import bisect
11
+
12
+ from pysisyphus.constants import AU2KJPERMOL
13
+
14
+
15
+ def get_curv_vec(H, Gv, v0, w0):
16
+ v0 = v0[:, None]
17
+ I = np.eye(v0.size)
18
+ first = np.linalg.pinv(2 * w0 * I - H, rcond=1e-8)
19
+ second = Gv.dot(v0) - v0.T.dot(Gv).dot(v0) * v0
20
+ v1 = first.dot(second)
21
+ return v1.flatten()
22
+
23
+
24
+ def taylor_closure(H, Gv, v0, v1, w0):
25
+ """Taylor expansion of energy to 3rd order.
26
+
27
+ dx(ds) = ds*v0 + ds**2*v1 / 2
28
+ dE(ds) = dx^T H dx / 2 + dx^T [Gv] dx / 6
29
+
30
+ H = Hessian
31
+ Gv = 3rd derivative of energy along v0
32
+ v0 = Transition vector
33
+ v1 = Curvature vector
34
+ w0 = Eigenvalue belonging to v0
35
+ """
36
+
37
+ # Precontract some values that will be reused
38
+ v0v1 = v0.dot(v1)
39
+ v1Hv1 = v1.dot(H).dot(v1)
40
+ v0Gvv0 = v0.dot(Gv).dot(v0)
41
+ v0Gvv1 = v0.dot(Gv).dot(v1)
42
+ v1Gvv1 = v1.dot(Gv).dot(v1)
43
+
44
+ def dE(ds):
45
+ ds2 = ds ** 2
46
+ ds3 = ds2 * ds
47
+ ds4 = ds2 * ds2
48
+
49
+ # Δx^T H Δx / 2
50
+ quad = (w0 * ds2 + ds3 * w0 ** 2 * v0v1 + (ds4 * v1Hv1) / 4) / 2
51
+ # Δx^T [Gv] Δx / 6
52
+ cubic = (ds2 * v0Gvv0 + ds3 * v0Gvv1 + ds4 * v1Gvv1 / 4) / 6
53
+ return quad + cubic
54
+
55
+ return dE
56
+
57
+
58
+ ThirdDerivResult = namedtuple(
59
+ "ThirdDerivResult",
60
+ "coords_plus energy_plus H_plus coords_minus energy_minus H_minus G_vec vec ds",
61
+ )
62
+
63
+
64
+ def third_deriv_fd(geom, vec, ds=0.001):
65
+ """Third derivative of the energy in direction 'vec'."""
66
+
67
+ def get_H(geom, coords):
68
+ results = geom.get_energy_and_cart_hessian_at(coords)
69
+ energy = results["energy"]
70
+ H = results["hessian"]
71
+ H_mw = geom.mass_weigh_hessian(H)
72
+ # Only project for multi-atomic geometries.
73
+ if geom.coords.size > 3:
74
+ H_mw = geom.eckart_projection(H_mw)
75
+ return H_mw, H, energy
76
+
77
+ delta = ds * vec
78
+ plus = geom.coords + delta
79
+ minus = geom.coords - delta
80
+ H_mw_plus, H_plus, energy_plus = get_H(geom, plus)
81
+ H_mw_minus, H_minus, energy_minus = get_H(geom, minus)
82
+ G_vec = (H_mw_plus - H_mw_minus) / (2 * ds)
83
+
84
+ third_deriv_res = ThirdDerivResult(
85
+ coords_plus=plus,
86
+ energy_plus=energy_plus,
87
+ H_plus=H_plus,
88
+ coords_minus=minus,
89
+ energy_minus=energy_minus,
90
+ H_minus=H_minus,
91
+ G_vec=G_vec,
92
+ vec=vec,
93
+ ds=ds,
94
+ )
95
+ return G_vec, third_deriv_res
96
+
97
+
98
+ def cubic_displ(H, v0, w0, Gv, dE):
99
+ """
100
+ According to Eq. (26) in [2] v1 does not depend on the sign of v0.
101
+ v1 = (F0 - 2v0^T F0 v0 I)⁻¹ x ([G0v0] - v0^T [G0v0] v0 I) v0
102
+ The first term is obviously independent of v0's sign. Using v0' = -v0 the
103
+ second term becomes
104
+ ([G0v0'] - v0'^T [G0v0'] v0' I) v0'
105
+ (-[G0v0] - v0^T [G0v0'] v0 I) v0'
106
+ (-[G0v0] + v0^T [G0v0] v0 I) v0'
107
+ -(-[G0v0] + v0^T [G0v0] v0 I) v0
108
+ ([G0v0] - v0^T [G0v0] v0 I) v0
109
+ Strictly speaking the contraction [G0v0] should depend on the sign of v0.
110
+ In the current implementation, the direction of v0 is not taken into account,
111
+ but
112
+ get_curv_vec(H, Gv, v0, w0) == get_curv_vec(H, -Gv, -v0, w0) .
113
+ But somehow the Taylor expansion gives bogus results when called with -Gv and -v0...
114
+ """
115
+
116
+ assert dE < 0.0, f"Supplied dE={dE:.6f} is positive but it must be negative!"
117
+ assert w0 < 0.0, f"Expected first eigenvalue to be negative but it is w0={w0:.6e}!"
118
+
119
+ v1 = get_curv_vec(H, Gv, v0, w0)
120
+ E_taylor = taylor_closure(H, Gv, v0, v1, w0)
121
+
122
+ def func(ds):
123
+ return E_taylor(ds) - dE
124
+
125
+ def prepare_bisect(x0, theta=1.25, max_cycles=20):
126
+ """Determine (lower) upper bound for scipy.optimize.bisect."""
127
+ assert theta > 1.0
128
+
129
+ ds = x0
130
+ dE_min = func(0.0)
131
+ dE_prev = dE_min
132
+ ds_min = ds
133
+ # Grow until we find an upper (lower) bound of the interval
134
+ for _ in range(max_cycles):
135
+ dE = func(ds)
136
+ # print(
137
+ # f"ds={ds:.4f} dE={dE*AU2KJPERMOL:.3f} dE_min={dE_min*AU2KJPERMOL:.3f}"
138
+ # )
139
+
140
+ if dE <= 0.0:
141
+ break
142
+ # Keep best guess
143
+ elif dE <= dE_min:
144
+ dE_min = dE
145
+ ds_min = ds
146
+ # Return (yet) best guess when function value grows again
147
+ elif dE >= dE_prev:
148
+ ds = ds_min
149
+ break
150
+
151
+ dE_prev = dE
152
+ ds *= theta # Grow ds
153
+ return ds
154
+
155
+ def bisect_(ds0):
156
+ try:
157
+ ds_, rr = bisect(func, a=0.0, b=ds0, full_output=True)
158
+ # Will be raised when f(a) and f(b) have the same sign.
159
+ except ValueError:
160
+ ds_ = ds0
161
+ return ds_
162
+
163
+ plus_bound = prepare_bisect(0.1)
164
+ ds_plus = bisect_(plus_bound)
165
+
166
+ minus_bound = prepare_bisect(-0.1)
167
+ ds_minus = bisect_(minus_bound)
168
+
169
+ # import matplotlib.pyplot as plt
170
+ # dss = np.linspace(-1, 1, num=51)
171
+ # # dss = np.linspace(-6, 6, num=200)
172
+ # E0 = E_taylor(0.0)
173
+ # Es = E_taylor(dss) - E0
174
+ # Es *= AU2KJPERMOL
175
+ # Emp = (np.array((E_taylor(ds_minus), E_taylor(ds_plus))) - E0) * AU2KJPERMOL
176
+ # _, ax = plt.subplots()
177
+ # ax.plot(dss, Es, "o-")
178
+ # ax.axvline(0.0, c="k", ls="--")
179
+ # ax.axhline(dE*AU2KJPERMOL, c="k", ls=":")
180
+ # ax.scatter((ds_minus, ds_plus), Emp, s=75, marker="x", c="r", zorder=3)
181
+ # ax.set_xlabel("ds")
182
+ # ax.set_ylabel("dE / kJ mol⁻¹")
183
+ # plt.show()
184
+
185
+ def step(ds):
186
+ return ds * v0 + ds ** 2 * v1 / 2
187
+
188
+ step_plus = step(ds_plus)
189
+ step_minus = step(ds_minus)
190
+ return step_plus, step_minus
191
+
192
+
193
+ def cubic_displ_for_h5(h5_fn="third_deriv.h5", dE=-5e-4):
194
+ with h5py.File(h5_fn, "r") as handle:
195
+ coords3d = handle["coords3d"][:]
196
+ if coords3d.size > 3:
197
+ H = handle["H_proj"][:]
198
+ else:
199
+ H = handle["H_mw"][:]
200
+ Gv = handle["G_vec"][:]
201
+
202
+ w, v = np.linalg.eigh(H)
203
+ w0 = w[0]
204
+ v0 = v[:, 0]
205
+ return cubic_displ(H, v0, w0, Gv, dE)
206
+
207
+
208
+ def cubic_displ_for_geom(geom, dE=-5e-4):
209
+ H = geom.mw_hessian
210
+ # Only project for multi-atomic geometries.
211
+ if geom.coords.size > 3:
212
+ H = geom.eckart_projection(H)
213
+ w, v = np.linalg.eigh(H)
214
+ # Transition vector (imaginary mode) and corresponding eigenvalue
215
+ v0 = v[:, 0]
216
+ w0 = w[0]
217
+ Gv, third_deriv_res = third_deriv_fd(geom, v0)
218
+ step_plus, step_minus = cubic_displ(H, v0, w0, Gv, dE=dE)
219
+ return step_plus, step_minus, third_deriv_res
pysisyphus/linalg.py ADDED
@@ -0,0 +1,411 @@
1
+ from math import cos, sin, sqrt
2
+ from typing import Callable, Literal, Optional, Tuple
3
+
4
+ import numpy as np
5
+ from numpy.typing import NDArray
6
+ from scipy.spatial.transform import Rotation
7
+ from scipy.linalg.lapack import dpstrf
8
+
9
+
10
+ def gram_schmidt(vecs, thresh=1e-8):
11
+ def proj(v1, v2):
12
+ return v1.dot(v2) / v1.dot(v1)
13
+
14
+ ortho = [
15
+ vecs[0],
16
+ ]
17
+ for v1 in vecs[1:]:
18
+ tmp = v1.copy()
19
+ for v2 in ortho:
20
+ tmp -= proj(v2, v1) * v2
21
+ norm = np.linalg.norm(tmp)
22
+ # Don't append linear dependent vectors, as their norm will be
23
+ # near zero. Renormalizing them to unity would lead to numerical
24
+ # garbage and to erronous results later on, when we orthgonalize
25
+ # against this 'arbitrary' vector.
26
+ if norm <= thresh:
27
+ continue
28
+ ortho.append(tmp / norm)
29
+ return np.array(ortho)
30
+
31
+
32
+ def orthogonalize_against(mat, vecs, max_cycles=5, thresh=1e-10):
33
+ """Orthogonalize rows of 'mat' against rows in 'vecs'
34
+
35
+ Returns a (modified) copy of mat.
36
+ """
37
+
38
+ omat = mat.copy()
39
+ for _ in range(max_cycles):
40
+ max_overlap = 0.0
41
+ for row in omat:
42
+ for ovec in vecs:
43
+ row -= ovec.dot(row) * ovec
44
+
45
+ # Remaining overlap
46
+ overlap = np.sum(np.dot(vecs, row))
47
+ max_overlap = max(overlap, max_overlap)
48
+
49
+ if max_overlap < thresh:
50
+ break
51
+ else:
52
+ raise Exception(f"Orthogonalization did not succeed in {max_cycles} cycles!")
53
+
54
+ return omat
55
+
56
+
57
+ def perp_comp(vec, along):
58
+ """Return the perpendicular component of vec along along."""
59
+ return vec - vec.dot(along) * along
60
+
61
+
62
+ def make_unit_vec(vec1, vec2):
63
+ """Return unit vector pointing from vec2 to vec1."""
64
+ diff = vec1 - vec2
65
+ return diff / np.linalg.norm(diff)
66
+
67
+
68
+ def svd_inv(array, thresh, hermitian=False):
69
+ U, S, Vt = np.linalg.svd(array, hermitian=hermitian)
70
+ keep = S > thresh
71
+ S_inv = np.zeros_like(S)
72
+ S_inv[keep] = 1 / S[keep]
73
+ return Vt.T.dot(np.diag(S_inv)).dot(U.T)
74
+
75
+
76
+ def get_rot_mat(abc=None):
77
+ # Euler angles
78
+ if abc is None:
79
+ abc = np.random.rand(3) * np.pi * 2
80
+ a, b, c = abc
81
+ R = np.array(
82
+ (
83
+ (
84
+ cos(a) * cos(b) * cos(c) - sin(a) * sin(c),
85
+ -cos(a) * cos(b) * sin(c) - sin(a) * cos(c),
86
+ cos(a) * sin(b),
87
+ ),
88
+ (
89
+ sin(a) * cos(b) * cos(c) + cos(a) * sin(c),
90
+ -sin(a) * cos(b) * sin(c) + cos(a) * cos(c),
91
+ sin(a) * sin(b),
92
+ ),
93
+ (-sin(b) * cos(c), sin(b) * sin(c), cos(b)),
94
+ )
95
+ )
96
+ return R
97
+
98
+
99
+ def get_rot_mat_for_coords(coords3d_1, coords3d_2):
100
+ coords3d_1 = coords3d_1.copy()
101
+ coords3d_2 = coords3d_2.copy()
102
+ centroid_1 = coords3d_1.mean(axis=0)
103
+ centroid_2 = coords3d_2.mean(axis=0)
104
+ coords3d_1 -= centroid_1[None, :]
105
+ coords3d_2 -= centroid_2[None, :]
106
+
107
+ tmp = coords3d_2.T.dot(coords3d_1)
108
+ U, W, Vt = np.linalg.svd(tmp)
109
+ rot_mat = U.dot(Vt)
110
+ if np.linalg.det(rot_mat) < 0:
111
+ U[:, -1] *= -1
112
+ rot_mat = U.dot(Vt)
113
+ return coords3d_1, coords3d_2, rot_mat
114
+
115
+
116
+ def eigvec_grad(w, v, ind, mat_grad):
117
+ """Gradient of 'ind'-th eigenvector.
118
+
119
+ dv_i / dx_i = (w_i*I - mat)⁻¹ dmat/dx_i v_i
120
+ """
121
+ eigval = w[ind]
122
+ eigvec = v[:, ind]
123
+
124
+ w_diff = eigval - w
125
+ w_inv = np.divide(
126
+ 1.0, w_diff, out=np.zeros_like(w_diff).astype(float), where=w_diff != 0.0
127
+ )
128
+ assert np.isfinite(w_inv).all()
129
+ pinv = v.dot(np.diag(w_inv)).dot(v.T)
130
+ pinv.dot(mat_grad)
131
+
132
+ wh = pinv.dot(mat_grad).dot(eigvec)
133
+ return wh
134
+
135
+
136
+ def cross3(a, b):
137
+ """10x as fast as np.cross for two 1d arrays of size 3."""
138
+ return np.array(
139
+ (
140
+ a[1] * b[2] - a[2] * b[1],
141
+ a[2] * b[0] - a[0] * b[2],
142
+ a[0] * b[1] - a[1] * b[0],
143
+ )
144
+ )
145
+
146
+
147
+ def norm3(a):
148
+ """5x as fas as np.linalg.norm for a 1d array of size 3."""
149
+ return sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2])
150
+
151
+
152
+ def finite_difference_hessian(
153
+ coords: NDArray[float],
154
+ grad_func: Callable[[NDArray[float]], NDArray[float]],
155
+ step_size: float = 1e-2,
156
+ acc: Literal[2, 4] = 2,
157
+ callback: Optional[Callable] = None,
158
+ ) -> NDArray[float]:
159
+ """Numerical Hessian from central finite gradient differences.
160
+
161
+ See central differences in
162
+ https://en.wikipedia.org/wiki/Finite_difference_coefficient
163
+ for the different accuracies.
164
+ """
165
+ if callback is None:
166
+
167
+ def callback(*args):
168
+ pass
169
+
170
+ accuracies = {
171
+ 2: ((-0.5, -1), (0.5, 1)), # 2 calculations
172
+ 4: ((1 / 12, -2), (-2 / 3, -1), (2 / 3, 1), (-1 / 12, 2)), # 4 calculations
173
+ }
174
+ accs_avail = list(accuracies.keys())
175
+ assert acc in accs_avail
176
+
177
+ size = coords.size
178
+ fd_hessian = np.zeros((size, size))
179
+ zero_step = np.zeros(size)
180
+
181
+ coeffs = accuracies[acc]
182
+ for i, _ in enumerate(coords):
183
+ step = zero_step.copy()
184
+ step[i] = step_size
185
+
186
+ def get_grad(factor, displ, j):
187
+ displ_coords = coords + step * displ
188
+ callback(i, j)
189
+ grad = grad_func(displ_coords)
190
+ return factor * grad
191
+
192
+ grads = [get_grad(factor, displ, j) for j, (factor, displ) in enumerate(coeffs)]
193
+ fd = np.sum(grads, axis=0) / step_size
194
+ fd_hessian[i] = fd
195
+
196
+ # Symmetrize
197
+ fd_hessian = (fd_hessian + fd_hessian.T) / 2
198
+ return fd_hessian
199
+
200
+
201
+ def rot_quaternion(coords3d, ref_coords3d):
202
+ # Translate to origin by removing centroid
203
+ c3d = coords3d - coords3d.mean(axis=0)
204
+ ref_c3d = ref_coords3d - ref_coords3d.mean(axis=0)
205
+
206
+ # Setup correlation matrix
207
+ R = c3d.T.dot(ref_c3d)
208
+
209
+ # Setup F matrix, Eq. (6) in [1]
210
+ F = np.zeros((4, 4))
211
+ R11, R12, R13, R21, R22, R23, R31, R32, R33 = R.flatten()
212
+ # Fill only upper triangular part.
213
+ F[0, 0] = R11 + R22 + R33
214
+ F[0, 1] = R23 - R32
215
+ F[0, 2] = R31 - R13
216
+ F[0, 3] = R12 - R21
217
+ #
218
+ F[1, 1] = R11 - R22 - R33
219
+ F[1, 2] = R12 + R21
220
+ F[1, 3] = R13 + R31
221
+ #
222
+ F[2, 2] = -R11 + R22 - R33
223
+ F[2, 3] = R23 + R32
224
+ #
225
+ F[3, 3] = -R11 - R22 + R33
226
+
227
+ # Eigenvalues, eigenvectors of upper triangular part.
228
+ w, v_ = np.linalg.eigh(F, UPLO="U")
229
+
230
+ # Quaternion corresponds to biggest (last) eigenvalue.
231
+ # np.linalg.eigh already returns sorted eigenvalues.
232
+ return w, v_, c3d, ref_c3d
233
+
234
+
235
+ def quaternion_to_rot_mat(q):
236
+ q0, q1, q2, q3 = q
237
+ q_ = q0**2 - (q1**2 + q2**2 + q3**2)
238
+ R = np.zeros((3, 3))
239
+ R[0, 0] = q_ + 2 * q1**2
240
+ R[0, 1] = 2 * (q1 * q2 - q0 * q3)
241
+ R[0, 2] = 2 * (q1 * q3 - q0 * q2)
242
+ R[1, 0] = 2 * (q1 * q2 + q0 * q3)
243
+ R[1, 1] = q_ + 2 * q2**2
244
+ R[1, 2] = 2 * (q2 * q3 - q0 * q1)
245
+ R[2, 0] = 2 * (q1 * q3 - q0 * q2)
246
+ R[2, 1] = 2 * (q2 * q3 + q0 * q1)
247
+ R[2, 2] = q_ + 2 * q3**2
248
+ return R
249
+
250
+
251
+ def rmsd_grad(
252
+ coords3d: NDArray[float], ref_coords3d: NDArray[float], offset: float = 1e-9
253
+ ) -> Tuple[float, NDArray[float]]:
254
+ """RMSD and gradient between two sets of coordinates from quaternions.
255
+
256
+ The gradient is given w.r.t. the coordinates of 'coords3d'.
257
+
258
+ Python adaption of
259
+ ls_rmsd.f90
260
+ from the xtb repository of the Grimme group, which in turn implements
261
+ [1] https://doi.org/10.1002/jcc.20110
262
+ .
263
+
264
+ Parameters
265
+ ----------
266
+ coords3d
267
+ Coordinate array of shape (N, 3) with N denoting the number of atoms.
268
+ ref_coords3d
269
+ Reference coordinates.
270
+ offset
271
+ Small floating-point number that is added to the RMSD, to avoid division
272
+ by zero.
273
+
274
+ Returns
275
+ -------
276
+ rmsd
277
+ RMSD value.
278
+ rmsd_grad
279
+ Gradient of the RMSD value w.r.t. to 'coords3d'.
280
+ """
281
+ assert coords3d.shape == ref_coords3d.shape
282
+
283
+ w, v_, c3d, ref_c3d = rot_quaternion(coords3d, ref_coords3d)
284
+ quat = v_[:, -1]
285
+ eigval = w[-1]
286
+
287
+ atom_num = coords3d.shape[0]
288
+ x_norm = np.linalg.norm(c3d) ** 2
289
+ y_norm = np.linalg.norm(ref_c3d) ** 2
290
+ rmsd = np.sqrt(max(0.0, ((x_norm + y_norm) - 2.0 * eigval)) / (atom_num)) + offset
291
+
292
+ # scipy expects the quaternion
293
+ # a + b*i + c*j + d*k
294
+ # as (i, j, k, a), that is the scalar component must come last.
295
+ # Currently we have (a, i, j, k), so we have to rearrange before passing the
296
+ # quaternion to scipy.
297
+ rot = Rotation.from_quat((*quat[1:], quat[0]))
298
+ U = rot.as_matrix()
299
+
300
+ grad = (c3d - ref_c3d @ U) / (rmsd * atom_num)
301
+ return rmsd, grad
302
+
303
+
304
+ def pivoted_cholesky(A: NDArray, tol: float = -1.0):
305
+ """Cholesky factorization a real symmetric positive semidefinite matrix.
306
+ Cholesky factorization is carried out with full pivoting.
307
+
308
+ Adapated from PySCF.
309
+
310
+ P.T * A * P = L * L.T
311
+
312
+ Parameters
313
+ ----------
314
+ A
315
+ Matrix to be factorized.
316
+ tol
317
+ User defined tolerance, as outlined in the LAPACK docs.
318
+
319
+ Returns
320
+ -------
321
+ L
322
+ Lower or upper triangular matrix.
323
+ piv
324
+ Pivot vectors, starting at 0.
325
+ rank
326
+ Rank of the factoirzed matrix.
327
+ """
328
+
329
+ N = A.shape[0]
330
+ L, piv, rank, info = dpstrf(A, tol=tol, lower=True)
331
+ piv -= 1 # LAPACK returns 1-based indices
332
+ assert info >= 0
333
+ L[np.triu_indices(N, k=1)] = 0
334
+ L[:, rank:] = 0
335
+ return L, piv, rank
336
+
337
+
338
+ def pivoted_cholesky2(A: NDArray[float], tol: Optional[float] = None, m_max: int = 0):
339
+ """https://doi.org/10.1016/j.apnum.2011.10.001"""
340
+
341
+ R = np.zeros_like(A)
342
+ m = 0
343
+ n = len(A)
344
+ # Decompose to max rank.
345
+ if tol is None:
346
+ tol = 0.0
347
+ assert tol >= 0.0, f"{tol=} must be >= 0.0!"
348
+ if m_max == 0:
349
+ m_max = n
350
+ assert 0 <= m_max <= n, f"{m_max=} must fullfil (0 <= m_max <= {n=})!"
351
+ d = np.diag(A).copy() # Diagonal
352
+ error = np.linalg.norm(A, ord="nuc")
353
+ piv = np.arange(n, dtype=int)
354
+ while True:
355
+ # Stop when error is sufficiently small or we are at max rank.
356
+ # while (error > tol) or (m_max and m < m_max):
357
+ if (error <= tol) or (m_max and (m == m_max)):
358
+ break
359
+ i = m + d[piv[m:]].argmax()
360
+ piv[m], piv[i] = piv[i], piv[m]
361
+ R[m, piv[m]] = np.sqrt(d[piv[m]])
362
+ for i in range(m + 1, n):
363
+ j = np.arange(m)
364
+ sum_ = (R[j, piv[m]] * R[j, piv[i]]).sum()
365
+ R[m, piv[i]] = (A[piv[m], piv[i]] - sum_) / R[m, piv[m]]
366
+ d[piv[i]] -= R[m, piv[i]] ** 2
367
+ error = d[m + 1 :].sum()
368
+ m += 1
369
+ # R.T @ R == A
370
+ return R, piv, m
371
+
372
+
373
+ def matrix_power(mat, p, thresh=1e-12, strict=True):
374
+ w, v = np.linalg.eigh(mat)
375
+ assert (not strict) or (
376
+ w > 0.0
377
+ ).all(), "matrix_power must be called with a (semi)-positive-definite matrix!"
378
+ mask = np.abs(w) > thresh
379
+ w_pow = w[mask] ** p
380
+ v_mask = v[:, mask]
381
+ return v_mask @ np.diag(w_pow) @ v_mask.T
382
+
383
+
384
+ def sym_mat_from_triu(arr, data):
385
+ nrows, ncols = arr.shape
386
+ assert nrows == ncols
387
+ triu = np.triu_indices(nrows)
388
+ tril1 = np.tril_indices(nrows, k=-1)
389
+ arr[triu] = data
390
+ arr[tril1] = arr.T[tril1]
391
+
392
+
393
+ def sym_mat_from_tril(arr, data):
394
+ nrows, ncols = arr.shape
395
+ assert nrows == ncols
396
+ tril = np.tril_indices(nrows)
397
+ arr[tril] = data
398
+ triu1 = np.triu_indices(nrows, k=1)
399
+ arr[triu1] = arr.T[triu1]
400
+
401
+
402
+ def multi_component_sym_mat(arr, dim):
403
+ target_shape = (dim, dim)
404
+ shape = arr.shape
405
+ sym = np.zeros((*target_shape, *shape[1:]))
406
+ triu = np.triu_indices(dim)
407
+ triu1 = np.triu_indices(dim, k=1)
408
+ tril1 = np.tril_indices(dim, k=-1)
409
+ sym[triu] = arr
410
+ sym[tril1] = sym[triu1]
411
+ return sym.reshape(*target_shape, *shape[1:])
@@ -0,0 +1,88 @@
1
+ from pysisyphus.line_searches.LineSearch import (
2
+ LineSearch,
3
+ LineSearchNotConverged,
4
+ )
5
+
6
+ from pysisyphus.line_searches.interpol import interpol_alpha_quad, interpol_alpha_cubic
7
+ from pysisyphus.optimizers.poly_fit import cubic_fit, quartic_fit
8
+
9
+
10
+ class Backtracking(LineSearch):
11
+ def __init__(self, *args, rho_lo=5e-2, rho_hi=0.9, use_grad=False, **kwargs):
12
+ """Backtracking line search enforcing Armijo conditions.
13
+
14
+ Uses only energy evaluations.
15
+
16
+ See [1], Chapter 3, Line Search methods, Section 3.1 p. 31 and
17
+ Section 3.5 p. 56."""
18
+
19
+ kwargs["cond"] = "armijo"
20
+ super().__init__(*args, **kwargs)
21
+
22
+ self.rho_lo = float(rho_lo)
23
+ self.rho_hi = float(rho_hi)
24
+ self.use_grad = use_grad
25
+
26
+ def alpha_new_from_phi(self, cycle, phi0, dphi0, alpha, alpha_prev):
27
+ phi_i = self.get_phi_dphi("f", alpha)
28
+ self.log(f"\tCycle {cycle:02d}: alpha={alpha:.6f}, ϕ={phi_i:.6f} au")
29
+
30
+ if cycle == 0:
31
+ # Quadratic interpolation
32
+ alpha_new = interpol_alpha_quad(phi0, dphi0, phi_i, alpha)
33
+ type_ = "quadratic"
34
+ else:
35
+ # Cubic interpolation
36
+ phi_prev = self.get_phi_dphi("f", alpha_prev)
37
+ alpha_new = interpol_alpha_cubic(
38
+ phi0, dphi0, phi_prev, phi_i, alpha_prev, alpha
39
+ )
40
+ type_ = "cubic"
41
+ return alpha_new, type_
42
+
43
+ def alpha_new_from_phi_dphi(self, cycle, phi0, dphi0, alpha):
44
+ phi_i, dphi_i = self.get_phi_dphi("fg", alpha)
45
+ self.log(f"\tCycle {cycle:02d}: α={alpha:.6f}, ϕ={phi_i:.6f} au")
46
+
47
+ # First we try a constrained quartic polynomial
48
+ res = quartic_fit(phi0, phi_i, dphi0, dphi_i)
49
+ type_ = "quartic"
50
+ # If the quartic poly failed, we continue with a cubic polynomial
51
+ if res is None:
52
+ res = cubic_fit(phi0, phi_i, dphi0, dphi_i)
53
+ type_ = "cubic"
54
+ # If the cubic poly failed we resort to bisection. ohoh
55
+ if res is None:
56
+ alpha_new = 0.5 * alpha
57
+ type_ = "bisection"
58
+ else:
59
+ alpha_new = res.x * alpha
60
+ return alpha_new, type_
61
+
62
+ def run_line_search(self):
63
+ phi0, dphi0 = self.get_phi_dphi("fg", 0)
64
+
65
+ alpha_prev = None
66
+ alpha = self.alpha_init
67
+ for i in range(self.max_cycles):
68
+ if self.use_grad:
69
+ alpha_new, type_ = self.alpha_new_from_phi_dphi(i, phi0, dphi0, alpha)
70
+ else:
71
+ alpha_new, type_ = self.alpha_new_from_phi(i, phi0, dphi0, alpha, alpha_prev)
72
+ self.log(f"\tNew α from {type_} interpolation: {alpha_new:.6f}")
73
+
74
+ lower_bound = alpha * self.rho_lo
75
+ upper_bound = alpha * self.rho_hi
76
+ if alpha_new < lower_bound:
77
+ self.log("\tNew α is too small!")
78
+ if alpha_new > upper_bound:
79
+ self.log("\tNew α is too big!")
80
+
81
+ # Assert that alpha doesn't change too much compared to the previous alpha
82
+ alpha_new = min(alpha_new, upper_bound)
83
+ alpha_new = max(alpha_new, lower_bound)
84
+ alpha_prev = alpha
85
+ alpha = alpha_new
86
+ self.log(f"\tNext α: {alpha:.6f}\n")
87
+
88
+ raise LineSearchNotConverged