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,681 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ import h5py
5
+ import numpy as np
6
+
7
+ from pysisyphus.calculators.Calculator import Calculator
8
+ from pysisyphus.helpers import get_tangent_trj_str
9
+ from pysisyphus.helpers_pure import rms
10
+ from pysisyphus.intcoords.helpers import get_weighted_bond_mode
11
+ from pysisyphus.linalg import perp_comp, make_unit_vec
12
+ from pysisyphus.optimizers.closures import small_lbfgs_closure
13
+ from pysisyphus.optimizers.restrict_step import get_scale_max
14
+
15
+
16
+ class RotationConverged(Exception):
17
+ pass
18
+
19
+
20
+ class Gaussian:
21
+ def __init__(self, height, center, std, N):
22
+ self.center = np.array(center)
23
+ self.height = float(height)
24
+ self.std = float(std)
25
+ self.N = np.array(N)
26
+
27
+ self.s0 = self.center.dot(self.N)
28
+
29
+ def energy(self, R, height=None):
30
+ if height is None:
31
+ height = self.height
32
+
33
+ return height * np.exp(-((R.dot(self.N) - self.s0) ** 2) / (2 * self.std**2))
34
+
35
+ def forces(self, R, height=None):
36
+ if height is None:
37
+ height = self.height
38
+ s_diff = R.dot(self.N) - self.s0
39
+
40
+ return (
41
+ height
42
+ * np.exp(-(s_diff**2) / (2 * self.std**2))
43
+ * s_diff
44
+ / self.std**2
45
+ * self.N
46
+ )
47
+
48
+ def __str__(self):
49
+ return (
50
+ f"Gaussian(height={self.height:.4f}, center={self.center}, "
51
+ f"s0={self.s0:.4f}, std={self.std:.4f}"
52
+ )
53
+
54
+
55
+ # [1] https://aip.scitation.org/doi/abs/10.1063/1.480097
56
+ # Original Dimer paper
57
+ # Henkelmann, 1999
58
+ # [2] https://doi.org/10.1063/1.1809574
59
+ # Comparison of TS search methods
60
+ # Olsen, 2004
61
+ # [3] https://doi.org/10.1063/1.2104507
62
+ # Comparison of Dimer and P-RFO
63
+ # Heyden, 2005
64
+ # [4] https://aip.scitation.org/doi/abs/10.1063/1.2815812
65
+ # Superlinear Dimer method
66
+ # Kästner, 2008
67
+
68
+
69
+ class Dimer(Calculator):
70
+ def __init__(
71
+ self,
72
+ calculator,
73
+ *args,
74
+ N_raw=None,
75
+ length=0.0189,
76
+ rotation_max_cycles=15,
77
+ rotation_method="fourier",
78
+ rotation_thresh=1e-4,
79
+ rotation_tol=1,
80
+ rotation_max_element=0.001,
81
+ rotation_interpolate=True,
82
+ rotation_disable=False,
83
+ rotation_disable_pos_curv=True,
84
+ rotation_remove_trans=True,
85
+ trans_force_f_perp=True,
86
+ bonds=None,
87
+ N_hessian=None,
88
+ bias_rotation=False,
89
+ bias_translation=False,
90
+ bias_gaussian_dot=0.1,
91
+ seed=None,
92
+ write_orientations=True,
93
+ forward_hessian=True,
94
+ **kwargs,
95
+ ):
96
+ super().__init__(*args, **kwargs)
97
+
98
+ self.logger = logging.getLogger("dimer")
99
+
100
+ self.calculator = calculator
101
+ self.length = float(length)
102
+
103
+ # Rotation parameters
104
+ self.rotation_max_cycles = int(rotation_max_cycles)
105
+
106
+ rotation_methods = {
107
+ "direct": self.direct_rotation,
108
+ "fourier": self.fourier_rotation,
109
+ }
110
+ try:
111
+ self.rotation_method = rotation_methods[rotation_method]
112
+ except KeyError as err:
113
+ print(
114
+ f"Invalid rotation_method={rotation_method}! Valid types are: "
115
+ f"{tuple(self.rotation_methods.keys())}"
116
+ )
117
+ raise err
118
+ self.rotation_thresh = float(rotation_thresh)
119
+ self.rotation_tol = np.deg2rad(rotation_tol)
120
+ self.rotation_max_element = float(rotation_max_element)
121
+ self.rotation_interpolate = bool(rotation_interpolate)
122
+ self.rotation_disable = bool(rotation_disable)
123
+ self.rotation_disable_pos_curv = bool(rotation_disable_pos_curv)
124
+ self.rotation_remove_trans = bool(rotation_remove_trans)
125
+ self.trans_force_f_perp = trans_force_f_perp
126
+ self.forward_hessian = forward_hessian
127
+
128
+ # Regarding generation of initial orientation
129
+ self.bonds = bonds
130
+ self.N_hessian = N_hessian
131
+ # Bias
132
+ self.bias_rotation = bool(bias_rotation)
133
+ self.bias_rotation_a = 0.0
134
+ self.bias_translation = bool(bias_translation)
135
+ self.bias_gaussian_dot = float(bias_gaussian_dot)
136
+
137
+ self.write_orientations = write_orientations
138
+
139
+ restrict_steps = {
140
+ "direct": get_scale_max(self.rotation_max_element),
141
+ "fourier": None,
142
+ }
143
+ self.restrict_step = restrict_steps[rotation_method]
144
+
145
+ self._N = None
146
+ self._coords0 = None
147
+ self._energy0 = None
148
+ self._f0 = None
149
+ self._f1 = None
150
+
151
+ self.force_evals = 0
152
+ self.gaussians = list()
153
+
154
+ # Set dimer direction if given
155
+ self.N_raw = N_raw
156
+ if self.N_raw is not None:
157
+ msg = "Setting initial orientation from given 'N_raw'"
158
+ if isinstance(self.N_raw, str) and Path(self.N_raw).exists():
159
+ fn = self.N_raw
160
+ self.N_raw = np.loadtxt(fn)
161
+ msg = f"Read initial orientation from file '{fn}'"
162
+ N_raw = self.N_raw.copy()
163
+ self.N = N_raw
164
+ self.log(msg)
165
+ self.N_init = None
166
+
167
+ if seed is None:
168
+ # 2**32 - 1
169
+ seed = np.random.randint(4294967295)
170
+ np.random.seed(seed)
171
+ msg = f"Using seed {seed} to initialize the random number generator.\n"
172
+ print(msg)
173
+ self.log(msg)
174
+
175
+ @property
176
+ def N(self):
177
+ return self._N
178
+
179
+ @N.setter
180
+ def N(self, N_new):
181
+ N_new = np.array(N_new, dtype=float).flatten()
182
+ if self.rotation_remove_trans:
183
+ N_new = self.remove_translation(N_new)
184
+ N_new /= np.linalg.norm(N_new)
185
+ self._N = N_new
186
+ self._f1 = None
187
+
188
+ @property
189
+ def coords0(self):
190
+ return self._coords0
191
+
192
+ @coords0.setter
193
+ def coords0(self, coords0_new):
194
+ self._coords0 = coords0_new
195
+ self._energy0 = None
196
+ self._f0 = None
197
+ self._f1 = None
198
+
199
+ @property
200
+ def coords1(self):
201
+ return self.coords0 + self.length * self.N
202
+
203
+ @coords1.setter
204
+ def coords1(self, coords1_new):
205
+ N_new = coords1_new - self.coords0
206
+ self.N = N_new
207
+ self._f1 = None
208
+
209
+ @property
210
+ def energy0(self):
211
+ if self._energy0 is None:
212
+ results = self.calculator.get_energy(self.atoms, self.coords0)["energy"]
213
+ self._energy0 = results["energy"]
214
+ return self._energy0
215
+
216
+ @property
217
+ def f0(self):
218
+ if self._f0 is None:
219
+ results = self.calculator.get_forces(self.atoms, self.coords0)
220
+ self.force_evals += 1
221
+ self._f0 = results["forces"]
222
+ self._energy0 = results["energy"]
223
+ return self._f0
224
+
225
+ @property
226
+ def f1(self):
227
+ if self._f1 is None:
228
+ results = self.calculator.get_forces(self.atoms, self.coords1)
229
+ self.force_evals += 1
230
+ self._f1 = results["forces"]
231
+ return self._f1
232
+
233
+ @f1.setter
234
+ def f1(self, f1_new):
235
+ self._f1 = f1_new
236
+
237
+ @property
238
+ def f2(self):
239
+ """Never calculated explicitly, but estimated from f0 and f1."""
240
+ return 2 * self.f0 - self.f1
241
+
242
+ @property
243
+ def can_bias_f1(self):
244
+ return (
245
+ self.bias_rotation
246
+ and (self.N_init is not None)
247
+ and (self.bias_rotation_a is not None)
248
+ and (self.bias_rotation_a > 0.0)
249
+ )
250
+
251
+ @property
252
+ def should_bias_f1(self):
253
+ """May lead to calculation of f0 and/or f1 if present!"""
254
+ return self.can_bias_f1 and self.C > 0.0
255
+
256
+ @property
257
+ def can_bias_f0(self):
258
+ return self.bias_translation and (self.N is not None)
259
+
260
+ @property
261
+ def should_bias_f0(self):
262
+ """May lead to calculation of f0 and/or f1 if present!"""
263
+ return self.can_bias_f0 and self.C > 0.0
264
+
265
+ @property
266
+ def f1_bias(self):
267
+ # Apply bias force to f1 if desired. Dont apply bias if N_init is not (yet)
268
+ # set. When N_raw was converged to a reasonable N_init we can add
269
+ # the bias.
270
+ assert self.bias_rotation_a >= 0.0, "This should not be negative!"
271
+
272
+ fN = self.bias_rotation_a * self.length * self.N.dot(self.N_init) * self.N_init
273
+
274
+ return fN
275
+
276
+ @property
277
+ def rot_force(self):
278
+ f1_perp = perp_comp(self.f1, self.N)
279
+ f2_perp = perp_comp(self.f2, self.N)
280
+ f_perp = f1_perp - f2_perp
281
+
282
+ # Don't bias rotational force if curvature is already negative
283
+ if self.should_bias_f1:
284
+ f1_bias_perp = perp_comp(self.f1_bias, self.N)
285
+ f_perp += f1_bias_perp
286
+
287
+ return f_perp
288
+
289
+ def curvature(self, f1, f2, N):
290
+ """Curvature of the mode represented by the dimer."""
291
+ return (f2 - f1).dot(N) / (2 * self.length)
292
+
293
+ @property
294
+ def C(self):
295
+ """Shortcut for the curvature."""
296
+ return self.curvature(self.f1, self.f2, self.N)
297
+
298
+ def get_gaussian_energies(self, coords, sum_=True):
299
+ energies = [gauss.energy(coords) for gauss in self.gaussians]
300
+ if sum_:
301
+ energies = sum(energies)
302
+ return energies
303
+
304
+ def get_gaussian_forces(self, coords, sum_=True):
305
+ forces = [gauss.forces(coords) for gauss in self.gaussians]
306
+ if sum_:
307
+ forces = np.sum(forces, axis=0)
308
+ return forces
309
+
310
+ def add_gaussian(
311
+ self, atoms, center, N, height=0.1, std=0.0529, max_cycles=50, dot_ref=None
312
+ ):
313
+ # Create new gaussian object with default height that will be
314
+ # refined later.
315
+ gaussian = Gaussian(height=height, center=center, std=std, N=N)
316
+
317
+ if dot_ref is None:
318
+ dot_ref = self.bias_gaussian_dot
319
+
320
+ # Calculate real forces at inflection point of new gaussian
321
+ infl_coords = center + gaussian.std * N
322
+ infl_results = self.calculator.get_forces(atoms, infl_coords)
323
+ self.force_evals += 1
324
+ infl_forces = infl_results["forces"]
325
+
326
+ assert (
327
+ infl_forces.dot(N) < 0
328
+ ), "We probably overstepped the TS. See Section 2.3 in the paper."
329
+
330
+ forces = self.get_gaussian_forces(infl_coords) + infl_forces
331
+
332
+ def get_dot(height):
333
+ """Dot product of forces for a given height and orientation N."""
334
+ tmp_forces = forces.copy()
335
+ tmp_forces += gaussian.forces(infl_coords, height=height)
336
+ dot = tmp_forces.dot(N)
337
+ return dot
338
+
339
+ def can_break(dot):
340
+ """Convergence indicator."""
341
+ return abs(dot - dot_ref) <= 1e-3
342
+
343
+ def bisect(
344
+ min_,
345
+ max_,
346
+ ):
347
+ for i in range(max_cycles):
348
+ if abs(min_ - max_) <= 1e-10:
349
+ raise Exception("min_ and max_ became too similar!")
350
+
351
+ # Determie value at half of the internval
352
+ height = min_ + (max_ - min_) / 2
353
+ dot = get_dot(height)
354
+
355
+ if can_break(dot):
356
+ break
357
+
358
+ if dot > dot_ref:
359
+ max_ = height
360
+ elif dot < dot_ref:
361
+ min_ = height
362
+ return height
363
+
364
+ # Determine appropriate height
365
+ grow = 2
366
+ min_height = 0
367
+ assert get_dot(0) < dot_ref
368
+ for i in range(max_cycles):
369
+ dot = get_dot(height)
370
+
371
+ if can_break(dot):
372
+ break
373
+
374
+ if 0 < dot < dot_ref:
375
+ min_height = height
376
+ height *= grow
377
+ elif dot < dot_ref:
378
+ height *= grow
379
+ else:
380
+ height = bisect(min_height, height)
381
+ break
382
+ dot = get_dot(height)
383
+ gaussian.height = height
384
+ self.gaussians.append(gaussian)
385
+
386
+ self.log(f"Added gaussian with height={height:.6f}")
387
+ self.log(f"There are now {len(self.gaussians)} gaussians.")
388
+
389
+ return gaussian
390
+
391
+ def get_N_raw_from_hessian(self, h5_fn, root=0):
392
+ with h5py.File(h5_fn, "r") as handle:
393
+ hessian = handle["hessian"][:]
394
+ w, v = np.linalg.eigh(hessian)
395
+ assert w[root] < -1e-3
396
+ N_raw = v[:, root]
397
+ return N_raw
398
+
399
+ def set_N_raw(self, coords):
400
+ self.log("No initial orientation given. Generating one.")
401
+ if self.bonds is not None:
402
+ N_raw = get_weighted_bond_mode(self.bonds, coords.reshape(-1, 3))
403
+ msg = "weighted bond mode"
404
+ elif self.N_hessian is not None:
405
+ N_raw = self.get_N_raw_from_hessian(self.N_hessian)
406
+ msg = "first imaginary mode of HDF5 Hessian"
407
+ else:
408
+ msg = "random guess"
409
+ N_raw = np.random.rand(coords.size)
410
+ self.log(f"Obtained initial orientation from {msg}.")
411
+ # Make N_raw translationally invariant and normalize
412
+ self.N = N_raw
413
+ # Now we keep the normalized dimer orientation
414
+ self.N_raw = self.N
415
+
416
+ def remove_translation(self, displacement):
417
+ # Average vector components over cartesian directions (x,y,z)
418
+ # Sum over each direction should equal zero if translationally invariant
419
+ average = displacement.reshape(-1, 3).mean(axis=0)
420
+
421
+ if max(abs(average)) > 1e-8:
422
+ self.log(
423
+ f"N-vector not translationally invariant. Removing average before normalization."
424
+ )
425
+ else:
426
+ return displacement
427
+ # Subtract the average component along each direction to make sum zero
428
+ invariant_displacement = (
429
+ displacement.reshape(-1, 3) - average[None, :]
430
+ ).flatten()
431
+ return invariant_displacement
432
+
433
+ def rotate_coords1(self, rad, theta):
434
+ """Rotate dimer and produce new coords1."""
435
+ return self.coords0 + (self.N * np.cos(rad) + theta * np.sin(rad)) * self.length
436
+
437
+ def direct_rotation(self, optimizer, prev_step):
438
+ rot_step = optimizer(self.rot_force, prev_step)
439
+ rot_step = self.restrict_step(rot_step)
440
+ # Strictly speaking rot_step should be constrained to conserve the desired
441
+ # dimer length (coords1 - coords0)*2. This step is unconstrained.
442
+ # Later on we calculate the actual step between the old coords1 and the new
443
+ # coords1 that have been reconstrained.
444
+ self.coords1 = self.coords1 + rot_step
445
+
446
+ def fourier_rotation(self, optimizer, prev_step):
447
+ theta_dir = optimizer(self.rot_force, prev_step)
448
+ # Remove component that is parallel to N
449
+ theta_dir = theta_dir - theta_dir.dot(self.N) * self.N
450
+ theta = theta_dir / np.linalg.norm(theta_dir)
451
+
452
+ # Get rotated endpoint geometries. The rotation takes place in a plane
453
+ # spanned by N and theta. Theta is a unit vector perpendicular to N that
454
+ # can be formed from the perpendicular components of the forces at the
455
+ # endpoints.
456
+
457
+ C = self.C
458
+ # Derivative of the curvature, Eq. (29) in [2]
459
+ # (f2 - f1) or -(f1 - f2)
460
+ dC = 2 * (self.f0 - self.f1).dot(theta) / self.length
461
+ rad_trial = -0.5 * np.arctan2(dC, 2 * abs(C))
462
+ # self.log(f"rad_trial={rad_trial:.2f}")
463
+ if np.abs(rad_trial) < self.rotation_tol:
464
+ self.log(f"rad_trial={rad_trial:.2f} below threshold. Breaking.")
465
+ raise RotationConverged
466
+
467
+ # Trial rotation for finite difference calculation of rotational force
468
+ # and rotational curvature.
469
+ coords1_trial = self.rotate_coords1(rad_trial, theta)
470
+ f1_trial = self.calculator.get_forces(self.atoms, coords1_trial)["forces"]
471
+ self.force_evals += 1
472
+ f2_trial = 2 * self.f0 - f1_trial
473
+ N_trial = make_unit_vec(coords1_trial, self.coords0)
474
+ C_trial = self.curvature(f1_trial, f2_trial, N_trial)
475
+
476
+ b1 = 0.5 * dC
477
+ a1 = (C - C_trial + b1 * np.sin(2 * rad_trial)) / (1 - np.cos(2 * rad_trial))
478
+ a0 = 2 * (C - a1)
479
+
480
+ rad_min = 0.5 * np.arctan(b1 / a1)
481
+
482
+ # self.log(f"rad_min={rad_min:.2f}")
483
+ def get_C(theta_rad):
484
+ return a0 / 2 + a1 * np.cos(2 * theta_rad) + b1 * np.sin(2 * theta_rad)
485
+
486
+ C_min = get_C(rad_min) # lgtm [py/multiple-definition]
487
+ if C_min > C:
488
+ rad_min += np.deg2rad(90)
489
+ # C_min_new = get_C(rad_min)
490
+ # self.log("Predicted theta_min lead us to a curvature maximum "
491
+ # f"(C(theta)={C_min:.6f}). Adding pi/2 to theta_min. "
492
+ # f"(C(theta+pi/2)={C_min_new:.6f})"
493
+ # )
494
+
495
+ # TODO: handle cases where the curvature is still positive, but
496
+ # the angle is small, so the rotation is skipped.
497
+ # Don't do rotation for small angles
498
+ if np.abs(rad_min) < self.rotation_tol:
499
+ # self.log(f"rad_min={rad_min:.2f} below threshold. Breaking.")
500
+ raise RotationConverged
501
+
502
+ f1 = None
503
+ # Interpolate force at coords1_rot; see Eq. (12) in [4]
504
+ if self.rotation_interpolate:
505
+ f1 = (
506
+ np.sin(rad_trial - rad_min) / np.sin(rad_trial) * self.f1
507
+ + np.sin(rad_min) / np.sin(rad_trial) * f1_trial
508
+ + (1 - np.cos(rad_min) - np.sin(rad_min) * np.tan(rad_trial / 2))
509
+ * self.f0
510
+ )
511
+
512
+ self.coords1 = self.rotate_coords1(rad_min, theta)
513
+ self.f1 = f1
514
+
515
+ def do_dimer_rotations(self, rotation_thresh=None):
516
+ self.log("Doing dimer rotations")
517
+ if rotation_thresh is None:
518
+ rotation_thresh = self.rotation_thresh
519
+ self.log(f"\tThreshold norm(rot_force)={rotation_thresh:.6f}")
520
+
521
+ lbfgs = small_lbfgs_closure(gamma_mult=True)
522
+ try:
523
+ N_first = self.N
524
+ prev_step = None
525
+ for i in range(self.rotation_max_cycles): # lgtm [py/redundant-else]
526
+ N_cur = self.N
527
+ rot_force = self.rot_force
528
+ rms_rot_force = rms(rot_force)
529
+ if self.should_bias_f1:
530
+ C_real = self.C
531
+ C_bias = -self.bias_rotation_a * (self.N.dot(self.N_init)) ** 2
532
+ C = C_real + C_bias
533
+ C_str = f"C={C: .6f}, C_real={C_real: .6f}, C_bias={C_bias: .6f}"
534
+ else:
535
+ C_str = f"C={self.C: .6f}"
536
+ self.log(f"\t{i:02d}: rms(rot_force)={rms_rot_force:.6f} {C_str}")
537
+ if rms_rot_force <= rotation_thresh:
538
+ self.log("\trms(rot_force) is below threshold!")
539
+ raise RotationConverged
540
+ coords1_old = self.coords1
541
+ self.rotation_method(lbfgs, prev_step)
542
+ actual_step = self.coords1 - coords1_old
543
+ prev_step = actual_step
544
+ rot_deg = np.rad2deg(np.arccos(N_cur.dot(self.N)))
545
+ self.log(f"\t\tRotated by {rot_deg:.1f}°")
546
+ else:
547
+ msg = (
548
+ "\tDimer rotation did not converge in "
549
+ f"{self.rotation_max_cycles}"
550
+ )
551
+ except RotationConverged:
552
+ msg = f"\tDimer rotation converged in {i+1} cycle(s)."
553
+ self.log(msg)
554
+ # self.log("\tN after rotation:\n\t" + str(self.N))
555
+ self.log()
556
+ # Restrict to interval [-1,1] where arccos is defined
557
+ rot_deg = np.rad2deg(np.arccos(max(min(N_first.dot(self.N), 1.0), -1.0)))
558
+ self.log(
559
+ f"\tRotated by {rot_deg:.1f}° w.r.t. the orientation " "before rotation(s)."
560
+ )
561
+
562
+ def update_orientation(self, coords):
563
+ # Generate random guess for the dimer orientation if not yet set
564
+ if self.N is None:
565
+ self.set_N_raw(coords)
566
+
567
+ # Refine N_raw to N_init if not yet done
568
+ if self.bias_rotation and self.N_init is None and self.C > 0.0:
569
+ # Run initial sweep with a much softer convergence threshold
570
+ self.log("Initial sweep to refine N_raw to N_init.")
571
+ self.do_dimer_rotations(10 * self.rotation_thresh)
572
+ if self.C > 0:
573
+ self.N_init = self.N
574
+ rot_rad = np.arccos(self.N_raw.dot(self.N_init))
575
+ rot_deg = np.rad2deg(rot_rad)
576
+ self.log(f"N_raw:\n\t{self.N_raw}")
577
+ self.log(f"Rotated N_raw by {rot_deg:.1f}° to N_init")
578
+ self.log(f"N_init:\n\t{self.N_init}")
579
+ C = self.C
580
+ self.log(f"Curvature after intial sweep is C={C:.6f}")
581
+ self.log("Determining proper scaling factor for bias potential.")
582
+ # Determine proper scaling factor for the quadratic bias potential
583
+ # from the current curvature.
584
+ scale_fact = 1.5
585
+ self.bias_rotation_a = scale_fact * self.C
586
+ self.log(f"Using a={scale_fact}*C={self.bias_rotation_a:.6f}")
587
+ else:
588
+ self.log(
589
+ f"Curvature after initial sweep C={self.C:.6f} is "
590
+ "already negative. Not setting N_init and bias_rotation_a!"
591
+ )
592
+
593
+ self.do_dimer_rotations()
594
+
595
+ def get_forces(self, atoms, coords):
596
+ self.atoms = atoms
597
+ self.coords0 = coords
598
+
599
+ try:
600
+ N_backup = self.N.copy()
601
+ except AttributeError:
602
+ N_backup = None
603
+ if not self.rotation_disable:
604
+ self.update_orientation(coords)
605
+ if (N_backup is not None) and self.rotation_disable_pos_curv and self.C > 0:
606
+ self.log(
607
+ "Rotation did not yield a negative curvature. "
608
+ "Restoring previous unrotated N."
609
+ )
610
+ self.N = N_backup
611
+ # Now we (have an updated self.N and) can do the force projections
612
+ N = self.N
613
+ # self.log(f"Orientation N:\n\t{N}")
614
+ # Save orientation N in human-readable format, aka _trj.xyz
615
+ # file in Angstrom.
616
+ if self.write_orientations:
617
+ trj_str = get_tangent_trj_str(atoms, coords, N)
618
+ trj_fn = self.make_fn("N_trj.xyz")
619
+ with open(trj_fn, "w") as handle:
620
+ handle.write(trj_str)
621
+ self.log(f"Wrote current orientation animation to '{trj_fn}'")
622
+ # Always save orientation in Bohr
623
+ N_fn = self.make_fn("N")
624
+ np.savetxt(N_fn, N)
625
+
626
+ energy = self.energy0
627
+ self.log(f"\tenergy={self.energy0:.8f} au")
628
+
629
+ f0 = self.f0
630
+
631
+ if self.should_bias_f0:
632
+ self.log("Biasing translation forces")
633
+ self.log(f"There are currently {len(self.gaussians)} gaussians present.")
634
+ bias_energy = self.get_gaussian_energies(coords)
635
+ energy += bias_energy
636
+ bias_forces = self.get_gaussian_forces(coords, sum_=False)
637
+ try:
638
+ bias_norms = np.linalg.norm(bias_forces, axis=1)
639
+ bias_norms_str = np.array2string(bias_norms, precision=4)
640
+ self.log(f"\tnorm(bias_forces)={bias_norms_str}")
641
+ except np.AxisError:
642
+ self.log("Skipping calculation of norm(bias_forces)")
643
+ f0 += np.sum(bias_forces, axis=0)
644
+
645
+ norm_f0 = np.linalg.norm(f0)
646
+ self.log(f"\tnorm(forces)={norm_f0:.6f}")
647
+
648
+ f_parallel = f0.dot(N) * N
649
+ norm_parallel = np.linalg.norm(f_parallel)
650
+ self.log(f"\tnorm(forces_parallel)={norm_parallel:.6f}")
651
+ # self.log(f"\tforce_parallel:\n\t{f_parallel}")
652
+
653
+ f_perp = f0 - f_parallel
654
+ norm_perp = np.linalg.norm(f_perp)
655
+ self.log(f"\tnorm(forces_perp)={norm_perp:.6f}")
656
+ # self.log(f"\tforce_perp:\n\t{f_perp}")
657
+
658
+ # Only return perpendicular component when curvature is negative
659
+ f_tran = -f_parallel
660
+
661
+ curv_str = "positive" if self.C > 0 else "negative"
662
+
663
+ force_str = "reversed parallel component of"
664
+ if (self.C < 0) or self.trans_force_f_perp:
665
+ f_tran += f_perp
666
+ force_str = "full"
667
+ self.log(f"Curvature is {curv_str}. Returning {force_str} f_tran.")
668
+ # self.log(f"\tf_tran:\n\t{f_tran}")
669
+ self.log()
670
+
671
+ self.calc_counter += 1
672
+
673
+ results = {"energy": energy, "forces": f_tran}
674
+
675
+ return results
676
+
677
+ def get_hessian(self, atoms, coords):
678
+ if not self.forward_hessian:
679
+ raise Exception("Actual Hessian method not forwarded by Dimer calculator!")
680
+ results = self.calculator.get_hessian(atoms, coords)
681
+ return results
@@ -0,0 +1,20 @@
1
+ from pysisyphus.calculators.Calculator import Calculator
2
+
3
+
4
+ class Dummy(Calculator):
5
+ def raise_exception(self):
6
+ raise Exception(
7
+ "Dummy calculator does not implement any calculation capabilities."
8
+ )
9
+
10
+ def run_calculation(self, *args, **kwargs):
11
+ self.raise_exception()
12
+
13
+ def get_energy(self, *args, **kwargs):
14
+ self.raise_exception()
15
+
16
+ def get_forces(self, *args, **kwargs):
17
+ self.raise_exception()
18
+
19
+ def get_hessian(self, *args, **kwargs):
20
+ self.raise_exception()