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,228 @@
1
+ from typing import Optional
2
+
3
+ import numpy as np
4
+
5
+ from pysisyphus.Geometry import Geometry
6
+ from pysisyphus.intcoords.exceptions import NeedNewInternalsException
7
+ from pysisyphus.optimizers.closures import bfgs_multiply, get_update_mu_reg
8
+ from pysisyphus.optimizers.hessian_updates import double_damp
9
+ from pysisyphus.optimizers.Optimizer import Optimizer
10
+ from pysisyphus.optimizers.restrict_step import scale_by_max_step
11
+ from pysisyphus.optimizers.poly_fit import poly_line_search
12
+
13
+
14
+ class LBFGS(Optimizer):
15
+ def __init__(
16
+ self,
17
+ geometry: Geometry,
18
+ keep_last: int = 7,
19
+ beta: float = 1,
20
+ max_step: float = 0.2,
21
+ double_damp: bool = True,
22
+ gamma_mult: bool = False,
23
+ line_search: bool = False,
24
+ mu_reg: Optional[float] = None,
25
+ max_mu_reg_adaptions: int = 10,
26
+ control_step: bool = True,
27
+ **kwargs,
28
+ ) -> None:
29
+ """Limited-memory BFGS optimizer.
30
+
31
+ See [1] Nocedal, Wright - Numerical Optimization, 2006 for a general
32
+ discussion of LBFGS. See pysisyphus.optimizers.hessian_updates for
33
+ the references related to double damping and pysisyphus.optimizers.closures
34
+ for references related to regularized LBFGS.
35
+
36
+ Parameters
37
+ ----------
38
+ geometry
39
+ Geometry to be optimized.
40
+ keep_last
41
+ History size. Keep last 'keep_last' steps and gradient differences.
42
+ beta
43
+ Force constant β in -(H + βI)⁻¹g.
44
+ max_step
45
+ Upper limit for the absolute component of the step vector in whatever
46
+ unit the optimization is carried out.
47
+ double_damp
48
+ Use double damping procedure to modify steps s and gradient differences y
49
+ to ensure sy > 0.
50
+ gamma_mult
51
+ Estimate β from previous cycle. Eq. (7.20) in [1]. See 'beta' argument.
52
+ line_search
53
+ Enable implicit linesearches.
54
+ mu_reg
55
+ Initial guess for regularization constant in regularized LBFGS.
56
+ max_mu_reg_adaptions
57
+ Maximum number of trial steps in regularized LBFGS.
58
+ control_step
59
+ Wheter to scale down the proposed step its biggest absolute component
60
+ is equal to or below 'max_step'
61
+
62
+ Other Parameters
63
+ ----------------
64
+ **kwargs
65
+ Keyword arguments passed to the Optimizer baseclass.
66
+ """
67
+
68
+ self.coord_diffs = list()
69
+ self.grad_diffs = list()
70
+ super().__init__(geometry, max_step=max_step, **kwargs)
71
+
72
+ self.beta = beta
73
+ self.keep_last = int(keep_last)
74
+ self.double_damp = double_damp
75
+ self.gamma_mult = gamma_mult
76
+ self.mu_reg = mu_reg
77
+ self.max_mu_reg_adaptions = max_mu_reg_adaptions
78
+ self.line_search = (not self.is_cos) and line_search
79
+ self.control_step = control_step
80
+
81
+ self.tot_adapt_mu_cycles = 0
82
+ if self.mu_reg:
83
+ self.mu_reg_0 = self.mu_reg # Backup value
84
+ self.update_mu_reg = get_update_mu_reg(logger=self.logger)
85
+ self.control_step = False
86
+ self.double_damp = False
87
+ self.line_search = False
88
+ self.log(
89
+ f"Regularized-L-BFGS (μ_reg={self.mu_reg:.6f}) requested.\n"
90
+ f"Disabling double damping, step control and line search."
91
+ )
92
+
93
+ def reset(self):
94
+ self.coord_diffs = list()
95
+ self.grad_diffs = list()
96
+
97
+ def _get_opt_restart_info(self):
98
+ opt_restart_info = {
99
+ "coord_diffs": np.array(self.coord_diffs).tolist(),
100
+ "grad_diffs": np.array(self.grad_diffs).tolist(),
101
+ "double_damp": self.double_damp,
102
+ "gamma_mult": self.gamma_mult,
103
+ "keep_last": self.keep_last,
104
+ }
105
+ return opt_restart_info
106
+
107
+ def _set_opt_restart_info(self, opt_restart_info):
108
+ self.coord_diffs = [np.array(cd) for cd in opt_restart_info["coord_diffs"]]
109
+ self.grad_diffs = [np.array(gd) for gd in opt_restart_info["grad_diffs"]]
110
+ for attr in ("double_damp", "gamma_mult", "keep_last"):
111
+ setattr(self, attr, opt_restart_info[attr])
112
+
113
+ def get_lbfgs_step(self, forces):
114
+ return bfgs_multiply(
115
+ self.coord_diffs,
116
+ self.grad_diffs,
117
+ forces,
118
+ beta=self.beta,
119
+ gamma_mult=self.gamma_mult,
120
+ mu_reg=self.mu_reg,
121
+ logger=self.logger,
122
+ )
123
+
124
+ def optimize(self):
125
+ if self.is_cos and self.align:
126
+ rot_vecs, rot_vec_lists, _ = self.fit_rigid(
127
+ vector_lists=(
128
+ self.steps,
129
+ self.forces,
130
+ self.coord_diffs,
131
+ self.grad_diffs,
132
+ ),
133
+ )
134
+ rot_steps, rot_forces, rot_coord_diffs, rot_grad_diffs = rot_vec_lists
135
+ self.steps = rot_steps
136
+ self.forces = rot_forces
137
+ self.coord_diffs = rot_coord_diffs
138
+ self.grad_diffs = rot_grad_diffs
139
+
140
+ forces = self.geometry.forces
141
+ self.forces.append(forces)
142
+ energy = self.geometry.energy
143
+ self.energies.append(energy)
144
+ norm = np.linalg.norm(forces)
145
+ if not self.is_cos:
146
+ self.log(f" Energy={energy: >24.6f} au")
147
+ self.log(f"norm(forces)={norm: >24.6f} au / bohr (rad)")
148
+
149
+ if self.cur_cycle > 0 and (self.forces[-2].size == forces.size):
150
+ y = self.forces[-2] - forces
151
+ s = self.steps[-1]
152
+ if self.double_damp:
153
+ s, y = double_damp(
154
+ s, y, s_list=self.coord_diffs, y_list=self.grad_diffs
155
+ )
156
+ self.grad_diffs.append(y)
157
+ self.coord_diffs.append(s)
158
+
159
+ # Drop superfluous oldest vectors
160
+ self.coord_diffs = self.coord_diffs[-self.keep_last :]
161
+ self.grad_diffs = self.grad_diffs[-self.keep_last :]
162
+
163
+ ###############
164
+ # Line search #
165
+ ###############
166
+
167
+ ip_gradient, ip_step = None, None
168
+ if self.line_search and (self.cur_cycle > 0):
169
+ ip_energy, ip_gradient, ip_step = poly_line_search(
170
+ energy, self.energies[-2], -forces, -self.forces[-2], self.steps[-1]
171
+ )
172
+ # Use the interpolated gradient for the step if interpolation succeeded
173
+ if (ip_gradient is not None) and (ip_step is not None):
174
+ forces = -ip_gradient
175
+ self.log("Interpolation succeeded")
176
+ # Keep the original gradient when the interpolation failed, but use
177
+ # zero (interpolated) step.
178
+ else:
179
+ ip_step = np.zeros_like(forces)
180
+
181
+ step = self.get_lbfgs_step(forces)
182
+
183
+ # Skip adapation mu_reg in first cycle, because in this implementation
184
+ # the first step will be steepest descent, which is independent of
185
+ # self.mu_reg, so this loop would never break.
186
+ adapt_mu_cycles = 0
187
+ while self.mu_reg and (self.cur_cycle > 0):
188
+ self.log(
189
+ f"Adapt μ_reg={self.mu_reg:.6f}, norm(step)={np.linalg.norm(step):.6f}"
190
+ )
191
+ if adapt_mu_cycles == self.max_mu_reg_adaptions:
192
+ raise Exception("Adapation of mu_reg failed! Breaking!")
193
+ try:
194
+ trial_energy = self.geometry.get_energy_at(self.geometry.coords + step)
195
+ self.mu_reg, recompute_step = self.update_mu_reg(
196
+ self.mu_reg, energy, trial_energy, -forces, step
197
+ )
198
+ except NeedNewInternalsException:
199
+ self.log("Internal coordinate breakdown in linesearch!")
200
+ # Nothing further is done here, as the coordinates will probably also
201
+ # breakdown when taking the step, which is then handled.
202
+ recompute_step = False
203
+
204
+ # Leave loop if step was accepted
205
+ if not recompute_step:
206
+ self.log(f"Next μ_reg={self.mu_reg:.6f}")
207
+ break
208
+ # Otherwise, recompute using updated μ_reg
209
+ step = self.get_lbfgs_step(forces)
210
+ adapt_mu_cycles += 1
211
+
212
+ if self.mu_reg:
213
+ self.tot_adapt_mu_cycles += adapt_mu_cycles + 1
214
+
215
+ # Form full step. If we did not interpolate or interpolation failed,
216
+ # ip_step will be zero.
217
+ step = step + ip_step
218
+
219
+ # Only try to scale down first step in regularized L-BFGS
220
+ if (self.mu_reg and self.cur_cycle == 0) or self.control_step:
221
+ step = scale_by_max_step(step, self.max_step)
222
+
223
+ return step
224
+
225
+ def postprocess_opt(self):
226
+ if self.mu_reg:
227
+ msg = f"\nNumber of μ updates: {self.tot_adapt_mu_cycles}"
228
+ self.log(msg)
@@ -0,0 +1,411 @@
1
+ # [1] https://doi.org/10.1002/jcc.10156
2
+ # Geometry optimization with QM/MM, ONIOM, and other combined methods.
3
+ # I. Microiterations and constraints
4
+ # Vreven, Morokuma, Farkas, Schlegel, Frisch, 2003
5
+ # [2] https://doi.org/10.1039/A909486E
6
+ # Linear scaling geometry optimisation and transition state search
7
+ # in hybrid delocalised internal coordinates
8
+ # Billeter, Turner, Thiel, 2000
9
+ import logging
10
+ from pprint import pprint
11
+
12
+ import numpy as np
13
+
14
+ from pysisyphus.calculators import IPIServer, ONIOM
15
+ from pysisyphus.Geometry import Geometry
16
+ from pysisyphus.intcoords.exceptions import RebuiltInternalsException
17
+ from pysisyphus.helpers_pure import full_expand, highlight_text, recursive_update
18
+ from pysisyphus.optimizers.Optimizer import Optimizer
19
+
20
+
21
+ def get_geom_kwargs(layer_ind, layer_mask):
22
+ coord_type = "tric" if layer_ind == 0 else "cartesian"
23
+ all_indices = np.arange(layer_mask.size)
24
+ freeze_atoms = all_indices[layer_mask]
25
+ geom_kwargs = {
26
+ "type": coord_type,
27
+ "freeze_atoms": freeze_atoms,
28
+ "coord_kwargs": {
29
+ "freeze_atoms_exclude": layer_ind == 0,
30
+ },
31
+ }
32
+ return geom_kwargs
33
+
34
+
35
+ def get_opt_kwargs(opt_key, layer_ind, thresh):
36
+ # Some defaults tailored to LayerOpt
37
+ opt_defaults = {
38
+ "lbfgs": {
39
+ "mu_reg": 0.1,
40
+ },
41
+ "plbfgs": {
42
+ "precon_kind": "full_fast",
43
+ "precon_update": 50,
44
+ },
45
+ }
46
+ if layer_ind == 0:
47
+ opt_kwargs = {
48
+ "type": "rfo",
49
+ "thresh": "never",
50
+ # "prefix": "model",
51
+ # "dump": True,
52
+ # "h5_group_name": "model_opt",
53
+ }
54
+ else:
55
+ if opt_key is None:
56
+ opt_key = "lbfgs"
57
+
58
+ opt_kwargs = {
59
+ "type": opt_key,
60
+ "max_cycles": 1_500,
61
+ "thresh": thresh,
62
+ "overachieve_factor": 3,
63
+ # "prefix": f"layer_{layer_ind:02d}",
64
+ # "dump": True,
65
+ }
66
+ try:
67
+ opt_kwargs.update(opt_defaults[opt_key])
68
+ except KeyError:
69
+ pass
70
+ return opt_kwargs
71
+
72
+
73
+ logger = logging.getLogger("optimizer")
74
+
75
+
76
+ class Layers:
77
+ def __init__(self, geometry, opt_thresh, layers=None):
78
+ self.geometry = geometry
79
+ self.layers = layers
80
+ self.opt_thresh = opt_thresh
81
+
82
+ print(highlight_text("Layers", level=1))
83
+ pprint(self.layers, compact=True)
84
+ print("")
85
+
86
+ atoms = geometry.atoms
87
+ all_indices = np.arange(len(geometry.atoms))
88
+ # Boolean array; every item corresponds to an atom in the total system.
89
+ freeze_mask = np.full_like(all_indices, True, dtype=bool)
90
+
91
+ self.indices = list()
92
+ self.geom_getters = list()
93
+ self.opt_getters = list()
94
+
95
+ # We import 'get_opt_cls' in the constructor, as it relies on dicts, that
96
+ # contain references to this class (LayerOpt). Trying to import 'get_opt_cls'
97
+ # at the top of the module will result in an circular import.
98
+ from pysisyphus.optimizers.cls_map import get_opt_cls
99
+
100
+ indices_below = set()
101
+ # Iterate in reverse order from smallest (lowest) layer to biggest (highest) layer.
102
+ # to setup geom_getter and opt_getter.
103
+ for i, layer in enumerate(layers[::-1]):
104
+ try:
105
+ indices = full_expand(layer["indices"])
106
+ # Allow missing 'indices' key. Then it is assumed that this layer contains the
107
+ # whole system.
108
+ except KeyError:
109
+ assert (
110
+ i != 0
111
+ ), "Found whole system in highest level layer. I don't like that!"
112
+ indices = all_indices
113
+ # Drop indices from layers below ...
114
+ indices = sorted(set(indices) - indices_below)
115
+ # ... and update 'indices_below' for the next layer
116
+ indices_below.update(set(indices))
117
+ self.indices.append(indices)
118
+ # Set up mask that indicates which atoms to freeze in the current layer (
119
+ # all atoms that are not in the current layer.)
120
+ layer_mask = freeze_mask.copy()
121
+ layer_mask[indices] = False
122
+
123
+ try:
124
+ try:
125
+ calc_kwargs = {
126
+ "address": layer["address"],
127
+ }
128
+ except KeyError:
129
+ _calc_kwargs = layer["calc"]
130
+ calc_kwargs = recursive_update({}, _calc_kwargs)
131
+ # The popped calc key is currently unused as using an IPIServer is
132
+ # mandatory.
133
+ _ = calc_kwargs.pop("type", None)
134
+ calc = IPIServer(**calc_kwargs)
135
+ # When calculating layer 0 we have access to the true energy of the system.
136
+ # So we assign the calculator of layer0 to the actual geometry containing
137
+ # the whole system.
138
+ if i == 0:
139
+ geometry.set_calculator(calc)
140
+ # If no address is given, we assume that pysisyphus' ONIOM calculator
141
+ # is used.
142
+ except KeyError as err:
143
+ try:
144
+ calc = layer["layer_calc"]
145
+ except KeyError:
146
+ print(
147
+ "Currently, a socket address for an IPI-protol client is mandatory!"
148
+ )
149
+ raise err
150
+
151
+ ####################
152
+ # Geometry setup #
153
+ ####################
154
+
155
+ _geom_kwargs = layer.get("geom", dict())
156
+ _geom_kwargs = recursive_update({}, _geom_kwargs)
157
+ geom_kwargs = get_geom_kwargs(i, layer_mask=layer_mask)
158
+ try:
159
+ geom_kwargs.update(_geom_kwargs)
160
+ # Allow empty "geom:" block
161
+ except TypeError:
162
+ pass
163
+ coord_type = geom_kwargs.pop("type")
164
+
165
+ # Geometry
166
+ def get_geom_getter(persistent_geom=None):
167
+ # Rebind the variables here, otherwise the wrong geom_kwargs
168
+ # and calc will be used, as they are redefined in the next loop cycle.
169
+ layer_geom_kwargs = geom_kwargs.copy()
170
+ layer_calc = calc
171
+
172
+ def get_geom(coords3d):
173
+ if persistent_geom is not None:
174
+ geom = persistent_geom
175
+ geom.coords3d = coords3d
176
+ else:
177
+ geom = Geometry(
178
+ atoms,
179
+ coords3d.copy(),
180
+ coord_type=coord_type,
181
+ **layer_geom_kwargs,
182
+ )
183
+ geom.set_calculator(layer_calc)
184
+ return geom
185
+
186
+ return get_geom
187
+
188
+ get_geom = get_geom_getter()
189
+ # Use a persistent Geometry for the layer 0. Overwrite the function
190
+ # above with a definition that always returns the same geometry.
191
+ if i == 0:
192
+ geom0 = get_geom(geometry.coords3d)
193
+ get_geom = get_geom_getter(geom0)
194
+
195
+ self.geom_getters.append(get_geom)
196
+
197
+ #####################
198
+ # Optimizer setup #
199
+ #####################
200
+
201
+ _opt_kwargs = layer.get("opt", dict())
202
+ _opt_kwargs = recursive_update({}, _opt_kwargs)
203
+ opt_key = _opt_kwargs.get("type", None)
204
+ opt_kwargs = get_opt_kwargs(opt_key, i, thresh=self.opt_thresh)
205
+ try:
206
+ opt_kwargs.update(_opt_kwargs)
207
+ # Allow empty "opt:" block
208
+ except TypeError:
209
+ pass
210
+ opt_key = opt_kwargs.pop("type")
211
+ opt_cls = get_opt_cls(opt_key)
212
+
213
+ def get_opt_getter():
214
+ layer_opt_kwargs = opt_kwargs
215
+ layer_opt_cls = opt_cls
216
+
217
+ def get_opt(geom, **kwargs):
218
+ kwargs_ = layer_opt_kwargs.copy()
219
+ kwargs_.update(kwargs)
220
+ opt = layer_opt_cls(geom, **kwargs_)
221
+ return opt
222
+
223
+ return get_opt
224
+
225
+ get_opt = get_opt_getter()
226
+ # Persistent optimizer for most expensive layer.
227
+ if i == 0:
228
+ model_opt = get_opt(geom0)
229
+
230
+ def get_opt(geom, **kwargs):
231
+ return model_opt
232
+
233
+ self.opt_getters.append(get_opt)
234
+
235
+ # Most expensive layer will come last.
236
+ self.indices = self.indices[::-1]
237
+ self.geom_getters = self.geom_getters[::-1]
238
+ self.opt_getters = self.opt_getters[::-1]
239
+
240
+ @classmethod
241
+ def from_oniom_calculator(cls, geometry, oniom_calc=None, layers=None, **kwargs):
242
+ calc = geometry.calculator
243
+ if calc is None:
244
+ calc = oniom_calc
245
+
246
+ if layers is not None:
247
+ assert len(layers) == len(calc.layers), (
248
+ f"ONIOM calculator has {len(calc.layers)} layers, but only "
249
+ f"{len(layers)} layer were defined in 'layers:'!"
250
+ )
251
+
252
+ layers = list(layers)
253
+ for i, layer in enumerate(layers):
254
+ if layer is None:
255
+ layers[i] = dict()
256
+ elif isinstance(layer, dict):
257
+ pass
258
+ else:
259
+ raise Exception("Layer definition must be empty or dict-like!")
260
+ else:
261
+ layers = [dict() for _ in calc.layers]
262
+
263
+ for i, layer in enumerate(calc.layers):
264
+ assert len(layer) == 1, "Multicenter-ONIOM is not yet supported!"
265
+ model = layer[0]
266
+ link_hosts = [link.parent_ind for link in model.links]
267
+ indices = model.atom_inds + link_hosts
268
+ layer_calc = calc.get_layer_calc(i)
269
+ layer = {
270
+ "layer_calc": layer_calc,
271
+ "indices": indices,
272
+ }
273
+ layers[i].update(layer)
274
+ return Layers(geometry, layers=layers, **kwargs)
275
+
276
+ def __len__(self):
277
+ return len(self.layers)
278
+
279
+
280
+ class LayerOpt(Optimizer):
281
+ def __init__(
282
+ self,
283
+ geometry: Geometry,
284
+ layers: dict = None,
285
+ **kwargs,
286
+ ) -> None:
287
+ super().__init__(geometry, **kwargs)
288
+ assert geometry.coord_type in ("cart", "cartesian")
289
+
290
+ layers_kwargs = {
291
+ "geometry": self.geometry,
292
+ "opt_thresh": self.thresh,
293
+ "layers": layers,
294
+ }
295
+ # Construct layers from ONIOM calculator
296
+ if isinstance(self.geometry.calculator, ONIOM):
297
+ layers = Layers.from_oniom_calculator(**layers_kwargs)
298
+ else:
299
+ layers = Layers(**layers_kwargs)
300
+ self.layers = layers
301
+
302
+ self.micro_cycles = list()
303
+ self.micro_cycles_converged = list()
304
+
305
+ @property
306
+ def layer_num(self) -> int:
307
+ return len(self.layers)
308
+
309
+ @property
310
+ def model_opt(self):
311
+ """Return the persistent optimizer belonging to the model system.
312
+ We don't have to supply any coordinates to the optimizer of the
313
+ most expensive layer, as it is persistent, as well as the associated geometry."""
314
+ return self.layers.opt_getters[-1](None)
315
+
316
+ def optimize(self) -> None:
317
+ coords3d_org = self.geometry.coords3d.copy()
318
+ coords3d_cur = coords3d_org.copy()
319
+ cur_micro_cycles = list()
320
+ cur_micro_cycles_converged = list()
321
+ for i, (indices, get_geom, get_opt) in enumerate(
322
+ zip(self.layers.indices, self.layers.geom_getters, self.layers.opt_getters)
323
+ ):
324
+ print(highlight_text(f"Layer {i}", level=1))
325
+ is_last_layer = i == self.layer_num - 1
326
+ geom = get_geom(coords3d_cur)
327
+ prefix = f"mc{self.cur_cycle:03d}"
328
+ opt = get_opt(geom, prefix=prefix, h5_group_name=f"{prefix}_opt")
329
+ if is_last_layer:
330
+ if self.cur_cycle == 0:
331
+ opt.prepare_opt()
332
+ break
333
+ opt.run()
334
+ cur_micro_cycles.append(opt.cur_cycle + 1)
335
+ cur_micro_cycles_converged.append(opt.is_converged)
336
+ coords3d_cur[indices] = geom.coords3d[indices]
337
+ self.micro_cycles.append(cur_micro_cycles)
338
+ self.micro_cycles_converged.append(cur_micro_cycles_converged)
339
+
340
+ ####################
341
+ # Relax last layer #
342
+ ####################
343
+
344
+ # 'geom' and 'indices' for the last layer were defined in the for-loop
345
+ # above, before breaking from the loop.
346
+ #
347
+ # Calculate forces and energy of the last layer. This have to be the "true"
348
+ # ONIOM forces of the system, containing all contributions. That's why we
349
+ # also save them as the true forces in the optimizer.
350
+ cart_forces = geom.cart_forces
351
+ energy = geom.energy
352
+ self.energies.append(energy)
353
+ self.forces.append(cart_forces.copy())
354
+
355
+ # Also store relevant quantities in the optimizer of the last layer, so
356
+ # things like Hessian updates are possible. These quantities are usually
357
+ # stored in the big optimization-loop in Optimizer.run(). As run() is
358
+ # never called for the last optimizer we have to store them manually.
359
+ opt.coords.append(geom.coords.copy())
360
+ opt.cart_coords.append(geom.cart_coords.copy())
361
+
362
+ # Calculate one step
363
+ opt.cur_cycle = self.cur_cycle
364
+ int_step = opt.optimize()
365
+ opt.steps.append(int_step)
366
+ try:
367
+ geom.coords = geom.coords + int_step
368
+ except RebuiltInternalsException:
369
+ geom.reset_coords()
370
+ opt.reset()
371
+ coords3d_cur[indices] = geom.coords3d[indices]
372
+
373
+ full_step = coords3d_cur - coords3d_org
374
+ return full_step.flatten()
375
+
376
+ def check_convergence(self, *args, **kwargs):
377
+ """Check if we must use the model optimizer to signal convergence."""
378
+ opt = self.model_opt
379
+ if opt.thresh != "never":
380
+ return opt.check_convergence()
381
+ else:
382
+ return super().check_convergence(*args, **kwargs)
383
+
384
+ def print_opt_progress(self, *args, **kwargs):
385
+ """Pick the correct method to report opt_progress.
386
+
387
+ When the model optimizer decides convergence we also report optimization
388
+ progress using its data and not the data from LayerOpt, where the total
389
+ ONIOM gradient is stored."""
390
+ opt = self.model_opt
391
+ if opt.thresh != "never":
392
+ func = opt.print_opt_progress
393
+ else:
394
+ func = super().print_opt_progress
395
+ func(*args, **kwargs)
396
+
397
+ def postprocess_opt(self) -> None:
398
+ coord_types = list()
399
+ for layer in self.layers.layers:
400
+ try:
401
+ coord_type = layer["geom"]["type"]
402
+ coord_types.append(coord_type)
403
+ except KeyError:
404
+ pass
405
+ micro_sum = np.array(self.micro_cycles).sum()
406
+ print("\nMicrocycles:")
407
+ print("\t", end="")
408
+ pprint(self.micro_cycles)
409
+ print(f"\t@@@ Σ {micro_sum},", ", ".join(coord_types))
410
+ print(f"\t@@@ Macrocycles: {self.cur_cycle+1}, converged? {self.is_converged}")
411
+ print("\t@@@")