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.
- hessian_ff/__init__.py +50 -0
- hessian_ff/analytical_hessian.py +609 -0
- hessian_ff/constants.py +46 -0
- hessian_ff/forcefield.py +339 -0
- hessian_ff/loaders.py +608 -0
- hessian_ff/native/Makefile +8 -0
- hessian_ff/native/__init__.py +28 -0
- hessian_ff/native/analytical_hessian.py +88 -0
- hessian_ff/native/analytical_hessian_ext.cpp +258 -0
- hessian_ff/native/bonded.py +82 -0
- hessian_ff/native/bonded_ext.cpp +640 -0
- hessian_ff/native/loader.py +349 -0
- hessian_ff/native/nonbonded.py +118 -0
- hessian_ff/native/nonbonded_ext.cpp +1150 -0
- hessian_ff/prmtop_parmed.py +23 -0
- hessian_ff/system.py +107 -0
- hessian_ff/terms/__init__.py +14 -0
- hessian_ff/terms/angle.py +73 -0
- hessian_ff/terms/bond.py +44 -0
- hessian_ff/terms/cmap.py +406 -0
- hessian_ff/terms/dihedral.py +141 -0
- hessian_ff/terms/nonbonded.py +209 -0
- hessian_ff/tests/__init__.py +0 -0
- hessian_ff/tests/conftest.py +75 -0
- hessian_ff/tests/data/small/complex.parm7 +1346 -0
- hessian_ff/tests/data/small/complex.pdb +125 -0
- hessian_ff/tests/data/small/complex.rst7 +63 -0
- hessian_ff/tests/test_coords_input.py +44 -0
- hessian_ff/tests/test_energy_force.py +49 -0
- hessian_ff/tests/test_hessian.py +137 -0
- hessian_ff/tests/test_smoke.py +18 -0
- hessian_ff/tests/test_validation.py +40 -0
- hessian_ff/workflows.py +889 -0
- mlmm/__init__.py +36 -0
- mlmm/__main__.py +7 -0
- mlmm/_version.py +34 -0
- mlmm/add_elem_info.py +374 -0
- mlmm/advanced_help.py +91 -0
- mlmm/align_freeze_atoms.py +601 -0
- mlmm/all.py +3535 -0
- mlmm/bond_changes.py +231 -0
- mlmm/bool_compat.py +223 -0
- mlmm/cli.py +574 -0
- mlmm/cli_utils.py +166 -0
- mlmm/default_group.py +337 -0
- mlmm/defaults.py +467 -0
- mlmm/define_layer.py +526 -0
- mlmm/dft.py +1041 -0
- mlmm/energy_diagram.py +253 -0
- mlmm/extract.py +2213 -0
- mlmm/fix_altloc.py +464 -0
- mlmm/freq.py +1406 -0
- mlmm/harmonic_constraints.py +140 -0
- mlmm/hessian_cache.py +44 -0
- mlmm/hessian_calc.py +174 -0
- mlmm/irc.py +638 -0
- mlmm/mlmm_calc.py +2262 -0
- mlmm/mm_parm.py +945 -0
- mlmm/oniom_export.py +1983 -0
- mlmm/oniom_import.py +457 -0
- mlmm/opt.py +1742 -0
- mlmm/path_opt.py +1353 -0
- mlmm/path_search.py +2299 -0
- mlmm/preflight.py +88 -0
- mlmm/py.typed +1 -0
- mlmm/pysis_runner.py +45 -0
- mlmm/scan.py +1047 -0
- mlmm/scan2d.py +1226 -0
- mlmm/scan3d.py +1265 -0
- mlmm/scan_common.py +184 -0
- mlmm/summary_log.py +736 -0
- mlmm/trj2fig.py +448 -0
- mlmm/tsopt.py +2871 -0
- mlmm/utils.py +2309 -0
- mlmm/xtb_embedcharge_correction.py +475 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/METADATA +1159 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/RECORD +372 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/WHEEL +5 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/entry_points.txt +2 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/licenses/LICENSE +674 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/top_level.txt +4 -0
- pysisyphus/Geometry.py +1667 -0
- pysisyphus/LICENSE +674 -0
- pysisyphus/TableFormatter.py +63 -0
- pysisyphus/TablePrinter.py +74 -0
- pysisyphus/__init__.py +12 -0
- pysisyphus/calculators/AFIR.py +452 -0
- pysisyphus/calculators/AnaPot.py +20 -0
- pysisyphus/calculators/AnaPot2.py +48 -0
- pysisyphus/calculators/AnaPot3.py +12 -0
- pysisyphus/calculators/AnaPot4.py +20 -0
- pysisyphus/calculators/AnaPotBase.py +337 -0
- pysisyphus/calculators/AnaPotCBM.py +25 -0
- pysisyphus/calculators/AtomAtomTransTorque.py +154 -0
- pysisyphus/calculators/CFOUR.py +250 -0
- pysisyphus/calculators/Calculator.py +844 -0
- pysisyphus/calculators/CerjanMiller.py +24 -0
- pysisyphus/calculators/Composite.py +123 -0
- pysisyphus/calculators/ConicalIntersection.py +171 -0
- pysisyphus/calculators/DFTBp.py +430 -0
- pysisyphus/calculators/DFTD3.py +66 -0
- pysisyphus/calculators/DFTD4.py +84 -0
- pysisyphus/calculators/Dalton.py +61 -0
- pysisyphus/calculators/Dimer.py +681 -0
- pysisyphus/calculators/Dummy.py +20 -0
- pysisyphus/calculators/EGO.py +76 -0
- pysisyphus/calculators/EnergyMin.py +224 -0
- pysisyphus/calculators/ExternalPotential.py +264 -0
- pysisyphus/calculators/FakeASE.py +35 -0
- pysisyphus/calculators/FourWellAnaPot.py +28 -0
- pysisyphus/calculators/FreeEndNEBPot.py +39 -0
- pysisyphus/calculators/Gaussian09.py +18 -0
- pysisyphus/calculators/Gaussian16.py +726 -0
- pysisyphus/calculators/HardSphere.py +159 -0
- pysisyphus/calculators/IDPPCalculator.py +49 -0
- pysisyphus/calculators/IPIClient.py +133 -0
- pysisyphus/calculators/IPIServer.py +234 -0
- pysisyphus/calculators/LEPSBase.py +24 -0
- pysisyphus/calculators/LEPSExpr.py +139 -0
- pysisyphus/calculators/LennardJones.py +80 -0
- pysisyphus/calculators/MOPAC.py +219 -0
- pysisyphus/calculators/MullerBrownSympyPot.py +51 -0
- pysisyphus/calculators/MultiCalc.py +85 -0
- pysisyphus/calculators/NFK.py +45 -0
- pysisyphus/calculators/OBabel.py +87 -0
- pysisyphus/calculators/ONIOMv2.py +1129 -0
- pysisyphus/calculators/ORCA.py +893 -0
- pysisyphus/calculators/ORCA5.py +6 -0
- pysisyphus/calculators/OpenMM.py +88 -0
- pysisyphus/calculators/OpenMolcas.py +281 -0
- pysisyphus/calculators/OverlapCalculator.py +908 -0
- pysisyphus/calculators/Psi4.py +218 -0
- pysisyphus/calculators/PyPsi4.py +37 -0
- pysisyphus/calculators/PySCF.py +341 -0
- pysisyphus/calculators/PyXTB.py +73 -0
- pysisyphus/calculators/QCEngine.py +106 -0
- pysisyphus/calculators/Rastrigin.py +22 -0
- pysisyphus/calculators/Remote.py +76 -0
- pysisyphus/calculators/Rosenbrock.py +15 -0
- pysisyphus/calculators/SocketCalc.py +97 -0
- pysisyphus/calculators/TIP3P.py +111 -0
- pysisyphus/calculators/TransTorque.py +161 -0
- pysisyphus/calculators/Turbomole.py +965 -0
- pysisyphus/calculators/VRIPot.py +37 -0
- pysisyphus/calculators/WFOWrapper.py +333 -0
- pysisyphus/calculators/WFOWrapper2.py +341 -0
- pysisyphus/calculators/XTB.py +418 -0
- pysisyphus/calculators/__init__.py +81 -0
- pysisyphus/calculators/cosmo_data.py +139 -0
- pysisyphus/calculators/parser.py +150 -0
- pysisyphus/color.py +19 -0
- pysisyphus/config.py +133 -0
- pysisyphus/constants.py +65 -0
- pysisyphus/cos/AdaptiveNEB.py +230 -0
- pysisyphus/cos/ChainOfStates.py +725 -0
- pysisyphus/cos/FreeEndNEB.py +25 -0
- pysisyphus/cos/FreezingString.py +103 -0
- pysisyphus/cos/GrowingChainOfStates.py +71 -0
- pysisyphus/cos/GrowingNT.py +309 -0
- pysisyphus/cos/GrowingString.py +508 -0
- pysisyphus/cos/NEB.py +189 -0
- pysisyphus/cos/SimpleZTS.py +64 -0
- pysisyphus/cos/__init__.py +22 -0
- pysisyphus/cos/stiffness.py +199 -0
- pysisyphus/drivers/__init__.py +17 -0
- pysisyphus/drivers/afir.py +855 -0
- pysisyphus/drivers/barriers.py +271 -0
- pysisyphus/drivers/birkholz.py +138 -0
- pysisyphus/drivers/cluster.py +318 -0
- pysisyphus/drivers/diabatization.py +133 -0
- pysisyphus/drivers/merge.py +368 -0
- pysisyphus/drivers/merge_mol2.py +322 -0
- pysisyphus/drivers/opt.py +375 -0
- pysisyphus/drivers/perf.py +91 -0
- pysisyphus/drivers/pka.py +52 -0
- pysisyphus/drivers/precon_pos_rot.py +669 -0
- pysisyphus/drivers/rates.py +480 -0
- pysisyphus/drivers/replace.py +219 -0
- pysisyphus/drivers/scan.py +212 -0
- pysisyphus/drivers/spectrum.py +166 -0
- pysisyphus/drivers/thermo.py +31 -0
- pysisyphus/dynamics/Gaussian.py +103 -0
- pysisyphus/dynamics/__init__.py +20 -0
- pysisyphus/dynamics/colvars.py +136 -0
- pysisyphus/dynamics/driver.py +297 -0
- pysisyphus/dynamics/helpers.py +256 -0
- pysisyphus/dynamics/lincs.py +105 -0
- pysisyphus/dynamics/mdp.py +364 -0
- pysisyphus/dynamics/rattle.py +121 -0
- pysisyphus/dynamics/thermostats.py +128 -0
- pysisyphus/dynamics/wigner.py +266 -0
- pysisyphus/elem_data.py +3473 -0
- pysisyphus/exceptions.py +2 -0
- pysisyphus/filtertrj.py +69 -0
- pysisyphus/helpers.py +623 -0
- pysisyphus/helpers_pure.py +649 -0
- pysisyphus/init_logging.py +50 -0
- pysisyphus/intcoords/Bend.py +69 -0
- pysisyphus/intcoords/Bend2.py +25 -0
- pysisyphus/intcoords/BondedFragment.py +32 -0
- pysisyphus/intcoords/Cartesian.py +41 -0
- pysisyphus/intcoords/CartesianCoords.py +140 -0
- pysisyphus/intcoords/Coords.py +56 -0
- pysisyphus/intcoords/DLC.py +197 -0
- pysisyphus/intcoords/DistanceFunction.py +34 -0
- pysisyphus/intcoords/DummyImproper.py +70 -0
- pysisyphus/intcoords/DummyTorsion.py +72 -0
- pysisyphus/intcoords/LinearBend.py +105 -0
- pysisyphus/intcoords/LinearDisplacement.py +80 -0
- pysisyphus/intcoords/OutOfPlane.py +59 -0
- pysisyphus/intcoords/PrimTypes.py +286 -0
- pysisyphus/intcoords/Primitive.py +137 -0
- pysisyphus/intcoords/RedundantCoords.py +659 -0
- pysisyphus/intcoords/RobustTorsion.py +59 -0
- pysisyphus/intcoords/Rotation.py +147 -0
- pysisyphus/intcoords/Stretch.py +31 -0
- pysisyphus/intcoords/Torsion.py +101 -0
- pysisyphus/intcoords/Torsion2.py +25 -0
- pysisyphus/intcoords/Translation.py +45 -0
- pysisyphus/intcoords/__init__.py +61 -0
- pysisyphus/intcoords/augment_bonds.py +126 -0
- pysisyphus/intcoords/derivatives.py +10512 -0
- pysisyphus/intcoords/eval.py +80 -0
- pysisyphus/intcoords/exceptions.py +37 -0
- pysisyphus/intcoords/findiffs.py +48 -0
- pysisyphus/intcoords/generate_derivatives.py +414 -0
- pysisyphus/intcoords/helpers.py +235 -0
- pysisyphus/intcoords/logging_conf.py +10 -0
- pysisyphus/intcoords/mp_derivatives.py +10836 -0
- pysisyphus/intcoords/setup.py +962 -0
- pysisyphus/intcoords/setup_fast.py +176 -0
- pysisyphus/intcoords/update.py +272 -0
- pysisyphus/intcoords/valid.py +89 -0
- pysisyphus/interpolate/Geodesic.py +93 -0
- pysisyphus/interpolate/IDPP.py +55 -0
- pysisyphus/interpolate/Interpolator.py +116 -0
- pysisyphus/interpolate/LST.py +70 -0
- pysisyphus/interpolate/Redund.py +152 -0
- pysisyphus/interpolate/__init__.py +9 -0
- pysisyphus/interpolate/helpers.py +34 -0
- pysisyphus/io/__init__.py +22 -0
- pysisyphus/io/aomix.py +178 -0
- pysisyphus/io/cjson.py +24 -0
- pysisyphus/io/crd.py +101 -0
- pysisyphus/io/cube.py +220 -0
- pysisyphus/io/fchk.py +184 -0
- pysisyphus/io/hdf5.py +49 -0
- pysisyphus/io/hessian.py +72 -0
- pysisyphus/io/mol2.py +146 -0
- pysisyphus/io/molden.py +293 -0
- pysisyphus/io/orca.py +189 -0
- pysisyphus/io/pdb.py +269 -0
- pysisyphus/io/psf.py +79 -0
- pysisyphus/io/pubchem.py +31 -0
- pysisyphus/io/qcschema.py +34 -0
- pysisyphus/io/sdf.py +29 -0
- pysisyphus/io/xyz.py +61 -0
- pysisyphus/io/zmat.py +175 -0
- pysisyphus/irc/DWI.py +108 -0
- pysisyphus/irc/DampedVelocityVerlet.py +134 -0
- pysisyphus/irc/Euler.py +22 -0
- pysisyphus/irc/EulerPC.py +345 -0
- pysisyphus/irc/GonzalezSchlegel.py +187 -0
- pysisyphus/irc/IMKMod.py +164 -0
- pysisyphus/irc/IRC.py +878 -0
- pysisyphus/irc/IRCDummy.py +10 -0
- pysisyphus/irc/Instanton.py +307 -0
- pysisyphus/irc/LQA.py +53 -0
- pysisyphus/irc/ModeKill.py +136 -0
- pysisyphus/irc/ParamPlot.py +53 -0
- pysisyphus/irc/RK4.py +36 -0
- pysisyphus/irc/__init__.py +31 -0
- pysisyphus/irc/initial_displ.py +219 -0
- pysisyphus/linalg.py +411 -0
- pysisyphus/line_searches/Backtracking.py +88 -0
- pysisyphus/line_searches/HagerZhang.py +184 -0
- pysisyphus/line_searches/LineSearch.py +232 -0
- pysisyphus/line_searches/StrongWolfe.py +108 -0
- pysisyphus/line_searches/__init__.py +9 -0
- pysisyphus/line_searches/interpol.py +15 -0
- pysisyphus/modefollow/NormalMode.py +40 -0
- pysisyphus/modefollow/__init__.py +10 -0
- pysisyphus/modefollow/davidson.py +199 -0
- pysisyphus/modefollow/lanczos.py +95 -0
- pysisyphus/optimizers/BFGS.py +99 -0
- pysisyphus/optimizers/BacktrackingOptimizer.py +113 -0
- pysisyphus/optimizers/ConjugateGradient.py +98 -0
- pysisyphus/optimizers/CubicNewton.py +75 -0
- pysisyphus/optimizers/FIRE.py +113 -0
- pysisyphus/optimizers/HessianOptimizer.py +1176 -0
- pysisyphus/optimizers/LBFGS.py +228 -0
- pysisyphus/optimizers/LayerOpt.py +411 -0
- pysisyphus/optimizers/MicroOptimizer.py +169 -0
- pysisyphus/optimizers/NCOptimizer.py +90 -0
- pysisyphus/optimizers/Optimizer.py +1084 -0
- pysisyphus/optimizers/PreconLBFGS.py +260 -0
- pysisyphus/optimizers/PreconSteepestDescent.py +7 -0
- pysisyphus/optimizers/QuickMin.py +74 -0
- pysisyphus/optimizers/RFOptimizer.py +181 -0
- pysisyphus/optimizers/RSA.py +99 -0
- pysisyphus/optimizers/StabilizedQNMethod.py +248 -0
- pysisyphus/optimizers/SteepestDescent.py +23 -0
- pysisyphus/optimizers/StringOptimizer.py +173 -0
- pysisyphus/optimizers/__init__.py +41 -0
- pysisyphus/optimizers/closures.py +301 -0
- pysisyphus/optimizers/cls_map.py +58 -0
- pysisyphus/optimizers/exceptions.py +6 -0
- pysisyphus/optimizers/gdiis.py +280 -0
- pysisyphus/optimizers/guess_hessians.py +311 -0
- pysisyphus/optimizers/hessian_updates.py +355 -0
- pysisyphus/optimizers/poly_fit.py +285 -0
- pysisyphus/optimizers/precon.py +153 -0
- pysisyphus/optimizers/restrict_step.py +24 -0
- pysisyphus/pack.py +172 -0
- pysisyphus/peakdetect.py +948 -0
- pysisyphus/plot.py +1031 -0
- pysisyphus/run.py +2106 -0
- pysisyphus/socket_helper.py +74 -0
- pysisyphus/stocastic/FragmentKick.py +132 -0
- pysisyphus/stocastic/Kick.py +81 -0
- pysisyphus/stocastic/Pipeline.py +303 -0
- pysisyphus/stocastic/__init__.py +21 -0
- pysisyphus/stocastic/align.py +127 -0
- pysisyphus/testing.py +96 -0
- pysisyphus/thermo.py +156 -0
- pysisyphus/trj.py +824 -0
- pysisyphus/tsoptimizers/RSIRFOptimizer.py +56 -0
- pysisyphus/tsoptimizers/RSPRFOptimizer.py +182 -0
- pysisyphus/tsoptimizers/TRIM.py +59 -0
- pysisyphus/tsoptimizers/TSHessianOptimizer.py +463 -0
- pysisyphus/tsoptimizers/__init__.py +23 -0
- pysisyphus/wavefunction/Basis.py +239 -0
- pysisyphus/wavefunction/DIIS.py +76 -0
- pysisyphus/wavefunction/__init__.py +25 -0
- pysisyphus/wavefunction/build_ext.py +42 -0
- pysisyphus/wavefunction/cart2sph.py +190 -0
- pysisyphus/wavefunction/diabatization.py +304 -0
- pysisyphus/wavefunction/excited_states.py +435 -0
- pysisyphus/wavefunction/gen_ints.py +1811 -0
- pysisyphus/wavefunction/helpers.py +104 -0
- pysisyphus/wavefunction/ints/__init__.py +0 -0
- pysisyphus/wavefunction/ints/boys.py +193 -0
- pysisyphus/wavefunction/ints/boys_table_N_64_xasym_27.1_step_0.01.npy +0 -0
- pysisyphus/wavefunction/ints/cart_gto3d.py +176 -0
- pysisyphus/wavefunction/ints/coulomb3d.py +25928 -0
- pysisyphus/wavefunction/ints/diag_quadrupole3d.py +10036 -0
- pysisyphus/wavefunction/ints/dipole3d.py +8762 -0
- pysisyphus/wavefunction/ints/int2c2e3d.py +7198 -0
- pysisyphus/wavefunction/ints/int3c2e3d_sph.py +65040 -0
- pysisyphus/wavefunction/ints/kinetic3d.py +8240 -0
- pysisyphus/wavefunction/ints/ovlp3d.py +3777 -0
- pysisyphus/wavefunction/ints/quadrupole3d.py +15054 -0
- pysisyphus/wavefunction/ints/self_ovlp3d.py +198 -0
- pysisyphus/wavefunction/localization.py +458 -0
- pysisyphus/wavefunction/multipole.py +159 -0
- pysisyphus/wavefunction/normalization.py +36 -0
- pysisyphus/wavefunction/pop_analysis.py +134 -0
- pysisyphus/wavefunction/shells.py +1171 -0
- pysisyphus/wavefunction/wavefunction.py +504 -0
- pysisyphus/wrapper/__init__.py +11 -0
- pysisyphus/wrapper/exceptions.py +2 -0
- pysisyphus/wrapper/jmol.py +120 -0
- pysisyphus/wrapper/mwfn.py +169 -0
- pysisyphus/wrapper/packmol.py +71 -0
- pysisyphus/xyzloader.py +168 -0
- pysisyphus/yaml_mods.py +45 -0
- thermoanalysis/LICENSE +674 -0
- thermoanalysis/QCData.py +244 -0
- thermoanalysis/__init__.py +0 -0
- thermoanalysis/config.py +3 -0
- thermoanalysis/constants.py +20 -0
- thermoanalysis/thermo.py +1011 -0
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
import functools
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sys
|
|
8
|
+
import textwrap
|
|
9
|
+
import time
|
|
10
|
+
from typing import Literal, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
from pysisyphus.cos.ChainOfStates import ChainOfStates
|
|
16
|
+
from pysisyphus.Geometry import Geometry
|
|
17
|
+
from pysisyphus.helpers import (
|
|
18
|
+
check_for_end_sign,
|
|
19
|
+
fit_rigid,
|
|
20
|
+
get_coords_diffs,
|
|
21
|
+
procrustes,
|
|
22
|
+
)
|
|
23
|
+
from pysisyphus.helpers_pure import highlight_text
|
|
24
|
+
from pysisyphus.intcoords.exceptions import RebuiltInternalsException
|
|
25
|
+
from pysisyphus.intcoords.helpers import interfragment_distance
|
|
26
|
+
from pysisyphus.io.hdf5 import get_h5_group, resize_h5_group
|
|
27
|
+
from pysisyphus.optimizers.exceptions import ZeroStepLength
|
|
28
|
+
from pysisyphus.TablePrinter import TablePrinter
|
|
29
|
+
|
|
30
|
+
import torch
|
|
31
|
+
|
|
32
|
+
def get_data_model(geometry, is_cos, max_cycles):
|
|
33
|
+
try:
|
|
34
|
+
# Attribute is only present in COS classes
|
|
35
|
+
image_num = geometry.max_image_num
|
|
36
|
+
dummy_geom = geometry.images[0]
|
|
37
|
+
except AttributeError:
|
|
38
|
+
image_num = 1
|
|
39
|
+
dummy_geom = geometry
|
|
40
|
+
|
|
41
|
+
# Define dataset shapes. As pysisyphus offers growing COS methods where
|
|
42
|
+
# the number of images changes along the optimization we have to define
|
|
43
|
+
# the shapes accordingly by considering the maximum number of images.
|
|
44
|
+
_1d = (max_cycles,)
|
|
45
|
+
_2d = (max_cycles, image_num * dummy_geom.coords.size)
|
|
46
|
+
_image_inds = (max_cycles, image_num)
|
|
47
|
+
# Number of cartesian coordinates is probably different from the number
|
|
48
|
+
# of internal coordinates.
|
|
49
|
+
_2d_cart = (max_cycles, image_num * dummy_geom.cart_coords.size)
|
|
50
|
+
# The dimensionality of energies depends on whether a COS is optimized or
|
|
51
|
+
# not. I know this is probably not the best idea...
|
|
52
|
+
_energy = _1d if (not is_cos) else (max_cycles, geometry.max_image_num)
|
|
53
|
+
|
|
54
|
+
data_model = {
|
|
55
|
+
"image_nums": _1d,
|
|
56
|
+
"image_inds": _image_inds,
|
|
57
|
+
"cart_coords": _2d_cart,
|
|
58
|
+
"coords": _2d,
|
|
59
|
+
"energies": _energy,
|
|
60
|
+
"forces": _2d,
|
|
61
|
+
# AFIR related
|
|
62
|
+
"true_energies": _energy,
|
|
63
|
+
"true_forces": _2d_cart,
|
|
64
|
+
"steps": _2d,
|
|
65
|
+
# Convergence related
|
|
66
|
+
"max_forces": _1d,
|
|
67
|
+
"rms_forces": _1d,
|
|
68
|
+
"max_steps": _1d,
|
|
69
|
+
"rms_steps": _1d,
|
|
70
|
+
# Misc
|
|
71
|
+
"cycle_times": _1d,
|
|
72
|
+
"modified_forces": _2d,
|
|
73
|
+
# COS specific
|
|
74
|
+
"tangents": _2d,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return data_model
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
CONV_THRESHS = {
|
|
81
|
+
# max_force, rms_force, max_step, rms_step
|
|
82
|
+
"nwchem_loose": (4.5e-3, 3.0e-3, 5.4e-3, 3.6e-3),
|
|
83
|
+
"gau_loose": (2.5e-3, 1.7e-3, 1.0e-2, 6.7e-3),
|
|
84
|
+
"gau": (4.5e-4, 3.0e-4, 1.8e-3, 1.2e-3),
|
|
85
|
+
"gau_tight": (1.5e-5, 1.0e-5, 6.0e-5, 4.0e-5),
|
|
86
|
+
"gau_vtight": (2.0e-6, 1.0e-6, 6.0e-6, 4.0e-6),
|
|
87
|
+
"baker": (3.0e-4, 2.0e-4, 3.0e-4, 2.0e-4),
|
|
88
|
+
# Dummy thresholds
|
|
89
|
+
"never": (2.0e-6, 1.0e-6, 6.0e-6, 4.0e-6),
|
|
90
|
+
}
|
|
91
|
+
Thresh = Literal["gau_loose", "gau", "gau_tight", "gau_vtight", "baker", "never"]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class ConvInfo:
|
|
96
|
+
cur_cycle: int
|
|
97
|
+
energy_converged: bool
|
|
98
|
+
max_force_converged: bool
|
|
99
|
+
rms_force_converged: bool
|
|
100
|
+
max_step_converged: bool
|
|
101
|
+
rms_step_converged: bool
|
|
102
|
+
desired_eigval_structure: bool
|
|
103
|
+
|
|
104
|
+
def get_convergence(self):
|
|
105
|
+
return (
|
|
106
|
+
self.energy_converged,
|
|
107
|
+
self.max_force_converged,
|
|
108
|
+
self.rms_force_converged,
|
|
109
|
+
self.max_step_converged,
|
|
110
|
+
self.rms_step_converged,
|
|
111
|
+
self.desired_eigval_structure,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def is_converged(self):
|
|
115
|
+
return all(self.get_convergence())
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Optimizer(metaclass=abc.ABCMeta):
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
geometry: Geometry,
|
|
122
|
+
thresh: Thresh = "gau_loose",
|
|
123
|
+
max_step: float = 0.04,
|
|
124
|
+
max_cycles: int = 150,
|
|
125
|
+
min_step_norm: float = 1e-8,
|
|
126
|
+
assert_min_step: bool = True,
|
|
127
|
+
rms_force: Optional[float] = None,
|
|
128
|
+
rms_force_only: bool = False,
|
|
129
|
+
max_force_only: bool = False,
|
|
130
|
+
force_only: bool = False,
|
|
131
|
+
converge_to_geom_rms_thresh: float = 0.05,
|
|
132
|
+
align: bool = False,
|
|
133
|
+
align_factor: float = 1.0,
|
|
134
|
+
dump: bool = False,
|
|
135
|
+
dump_restart: bool = False,
|
|
136
|
+
print_every: int = 1,
|
|
137
|
+
prefix: str = "",
|
|
138
|
+
reparam_thresh: float = 1e-3,
|
|
139
|
+
reparam_check_rms: bool = True,
|
|
140
|
+
reparam_when: Optional[Literal["before", "after"]] = "after",
|
|
141
|
+
overachieve_factor: float = 0.0,
|
|
142
|
+
check_eigval_structure: bool = False,
|
|
143
|
+
restart_info=None,
|
|
144
|
+
check_coord_diffs: bool = True,
|
|
145
|
+
coord_diff_thresh: float = 0.01,
|
|
146
|
+
fragments: Optional[Tuple] = None,
|
|
147
|
+
monitor_frag_dists: int = 0,
|
|
148
|
+
out_dir: str = ".",
|
|
149
|
+
h5_fn: str = "optimization.h5",
|
|
150
|
+
h5_group_name: str = "opt",
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Optimizer baseclass. Meant to be subclassed.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
geometry
|
|
157
|
+
Geometry to be optimized.
|
|
158
|
+
thresh
|
|
159
|
+
Convergence threshold.
|
|
160
|
+
max_step
|
|
161
|
+
Maximum absolute component of the allowed step vector. Utilized in
|
|
162
|
+
optimizers that don't support a trust region or line search.
|
|
163
|
+
max_cycles
|
|
164
|
+
Maximum number of allowed optimization cycles.
|
|
165
|
+
min_step_norm
|
|
166
|
+
Minimum norm of an allowed step. If the step norm drops below
|
|
167
|
+
this value a ZeroStepLength-exception is raised. The unit depends
|
|
168
|
+
on the coordinate system of the supplied geometry.
|
|
169
|
+
assert_min_step
|
|
170
|
+
Flag that controls whether the norm of the proposed step is check
|
|
171
|
+
for being too small.
|
|
172
|
+
rms_force
|
|
173
|
+
Root-mean-square of the force from which user-defined thresholds
|
|
174
|
+
are derived. When 'rms_force' is given 'thresh' is ignored.
|
|
175
|
+
rms_force_only
|
|
176
|
+
When set, convergence is signalled only based on rms(forces).
|
|
177
|
+
max_force_only
|
|
178
|
+
When set, convergence is signalled only based on max(|forces|).
|
|
179
|
+
force_only
|
|
180
|
+
When set, convergence is signalled only based on max(|forces|) and rms(forces).
|
|
181
|
+
converge_to_geom_rms_thresh
|
|
182
|
+
Threshold for the RMSD with another geometry. When the RMSD drops
|
|
183
|
+
below this threshold convergence is signalled. Only used with
|
|
184
|
+
Growing Newton trajectories.
|
|
185
|
+
align
|
|
186
|
+
Flag that controls whether the geometry is aligned in every step
|
|
187
|
+
onto the coordinates of the previous step. Must not be used with
|
|
188
|
+
internal coordinates.
|
|
189
|
+
align_factor
|
|
190
|
+
Factor that controls the strength of the alignment. 1.0 means
|
|
191
|
+
full alignment, 0.0 means no alignment. The factor mixes the
|
|
192
|
+
rotation matrix of the alignment with the identity matrix.
|
|
193
|
+
dump
|
|
194
|
+
Flag to control dumping/writing of optimization progress to the
|
|
195
|
+
filesystem
|
|
196
|
+
dump_restart
|
|
197
|
+
Flag to control whether restart information is dumped to the
|
|
198
|
+
filesystem.
|
|
199
|
+
print_every
|
|
200
|
+
Report optimization progress every nth cycle.
|
|
201
|
+
prefix
|
|
202
|
+
Short string that is prepended to several files created by
|
|
203
|
+
the optimizer. Allows distinguishing several optimizations carried
|
|
204
|
+
out in the same directory.
|
|
205
|
+
reparam_thresh
|
|
206
|
+
Controls the minimal allowed similarity between coordinates
|
|
207
|
+
after two successive reparametrizations. Convergence is signalled
|
|
208
|
+
if the coordinates did not change significantly.
|
|
209
|
+
reparam_check_rms
|
|
210
|
+
Whether to check for (too) similar coordinates after reparametrization.
|
|
211
|
+
reparam_when
|
|
212
|
+
Reparametrize before or after calculating the step. Can also be turned
|
|
213
|
+
off by setting it to None.
|
|
214
|
+
overachieve_factor
|
|
215
|
+
Signal convergence when max(forces) and rms(forces) fall below the
|
|
216
|
+
chosen threshold, divided by this factor. Convergence of max(step) and
|
|
217
|
+
rms(step) is ignored.
|
|
218
|
+
check_eigval_structure
|
|
219
|
+
Check the eigenvalues of the modes we maximize along. Convergence requires
|
|
220
|
+
them to be negative. Useful if TS searches are started from geometries close
|
|
221
|
+
to a minimum.
|
|
222
|
+
restart_info
|
|
223
|
+
Restart information. Undocumented.
|
|
224
|
+
check_coord_diffs
|
|
225
|
+
Whether coordinates of chain-of-sates images are checked for being
|
|
226
|
+
too similar.
|
|
227
|
+
coord_diff_thresh
|
|
228
|
+
Unitless threshold for similary checking of COS image coordinates.
|
|
229
|
+
The first image is assigned 0, the last image is assigned to 1.
|
|
230
|
+
fragments
|
|
231
|
+
Tuple of lists containing atom indices, defining two fragments.
|
|
232
|
+
monitor_frag_dists
|
|
233
|
+
Monitor fragment distances for N cycles. The optimization is terminated
|
|
234
|
+
when the interfragment distances falls below the initial value after N
|
|
235
|
+
cycles.
|
|
236
|
+
out_dir
|
|
237
|
+
String poiting to a directory where optimization progress is
|
|
238
|
+
dumped.
|
|
239
|
+
h5_fn
|
|
240
|
+
Basename of the HDF5 file used for dumping.
|
|
241
|
+
h5_group_name
|
|
242
|
+
Groupname used for dumping of this optimization.
|
|
243
|
+
"""
|
|
244
|
+
assert thresh in CONV_THRESHS.keys()
|
|
245
|
+
|
|
246
|
+
self.geometry = geometry
|
|
247
|
+
self.thresh = thresh
|
|
248
|
+
self.max_step = max_step
|
|
249
|
+
self.min_step_norm = min_step_norm
|
|
250
|
+
self.assert_min_step = assert_min_step
|
|
251
|
+
self.rms_force_only = rms_force_only
|
|
252
|
+
self.max_force_only = max_force_only
|
|
253
|
+
self.force_only = force_only
|
|
254
|
+
self.converge_to_geom_rms_thresh = converge_to_geom_rms_thresh
|
|
255
|
+
self.align = align
|
|
256
|
+
self.align_factor = align_factor
|
|
257
|
+
self.dump = dump
|
|
258
|
+
self.dump_restart = dump_restart
|
|
259
|
+
print_every = int(print_every)
|
|
260
|
+
assert print_every >= 1
|
|
261
|
+
self.print_every = print_every
|
|
262
|
+
self.prefix = f"{prefix}_" if prefix else prefix
|
|
263
|
+
self.reparam_thresh = reparam_thresh
|
|
264
|
+
self.reparam_check_rms = reparam_check_rms
|
|
265
|
+
self.reparam_when = reparam_when
|
|
266
|
+
assert self.reparam_when in ("after", "before", None)
|
|
267
|
+
self.overachieve_factor = float(overachieve_factor)
|
|
268
|
+
self.check_eigval_structure = check_eigval_structure
|
|
269
|
+
self.check_coord_diffs = check_coord_diffs
|
|
270
|
+
self.coord_diff_thresh = float(coord_diff_thresh)
|
|
271
|
+
|
|
272
|
+
self.logger = logging.getLogger("optimizer")
|
|
273
|
+
self.is_cos = issubclass(type(self.geometry), ChainOfStates)
|
|
274
|
+
|
|
275
|
+
# Set up convergence thresholds
|
|
276
|
+
self.convergence = self.make_conv_dict(
|
|
277
|
+
thresh, rms_force, rms_force_only, max_force_only, force_only
|
|
278
|
+
)
|
|
279
|
+
for key, value in self.convergence.items():
|
|
280
|
+
setattr(self, key, value)
|
|
281
|
+
|
|
282
|
+
if self.thresh == "never":
|
|
283
|
+
max_cycles = 1_000_000_000
|
|
284
|
+
self.dump = False
|
|
285
|
+
self.log(
|
|
286
|
+
f"Got threshold {self.thresh}, set 'max_cycles' to {max_cycles} "
|
|
287
|
+
"and disabled dumping!"
|
|
288
|
+
)
|
|
289
|
+
self.conv_dict = {}
|
|
290
|
+
try:
|
|
291
|
+
self.converge_to_geom = self.geometry.converge_to_geom
|
|
292
|
+
except AttributeError:
|
|
293
|
+
# TODO: log that attribute is not present at debug level
|
|
294
|
+
self.converge_to_geom = None
|
|
295
|
+
self.max_cycles = max_cycles
|
|
296
|
+
|
|
297
|
+
self.fragments = fragments
|
|
298
|
+
self.monitor_frag_dists = monitor_frag_dists
|
|
299
|
+
if self.monitor_frag_dists:
|
|
300
|
+
assert (
|
|
301
|
+
len(self.fragments) == 2
|
|
302
|
+
), "Interfragment monitoring requires two fragments!"
|
|
303
|
+
assert all(
|
|
304
|
+
[len(frag) > 0 for frag in self.fragments]
|
|
305
|
+
), "Fragments must not be empty!"
|
|
306
|
+
# Setting some default values
|
|
307
|
+
self.monitor_frag_dists_counter = self.monitor_frag_dists
|
|
308
|
+
self.interfrag_dists = list()
|
|
309
|
+
self.resetted = False
|
|
310
|
+
try:
|
|
311
|
+
out_dir = Path(out_dir)
|
|
312
|
+
except TypeError:
|
|
313
|
+
out_dir = Path(".")
|
|
314
|
+
self.out_dir = out_dir.resolve()
|
|
315
|
+
self.out_dir.mkdir(parents=True, exist_ok=True)
|
|
316
|
+
|
|
317
|
+
if self.is_cos:
|
|
318
|
+
moving_image_num = len(self.geometry.moving_indices)
|
|
319
|
+
print(f"Path with {moving_image_num} moving images.")
|
|
320
|
+
|
|
321
|
+
# Don't use prefix for this fn, as different optimizations
|
|
322
|
+
# can be distinguished according to their group in the HDF5 file.
|
|
323
|
+
self.h5_fn = self.get_path_for_fn(h5_fn, with_prefix=False)
|
|
324
|
+
self.h5_group_name = h5_group_name
|
|
325
|
+
|
|
326
|
+
current_fn = "current_geometries_trj.xyz" if self.is_cos else "current_geometry.xyz"
|
|
327
|
+
self.current_fn = self.get_path_for_fn(current_fn)
|
|
328
|
+
final_fn = "final_geometries_trj.xyz" if self.is_cos else "final_geometry.xyz"
|
|
329
|
+
self.final_fn = self.get_path_for_fn(final_fn)
|
|
330
|
+
self.hei_trj_fn = self.get_path_for_fn("cos_hei_trj.xyz")
|
|
331
|
+
try:
|
|
332
|
+
os.remove(self.hei_trj_fn)
|
|
333
|
+
except FileNotFoundError:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
# Setting some empty lists as default. The actual shape of the respective
|
|
337
|
+
# entries is not considered, which gives us some flexibility.
|
|
338
|
+
self.data_model = get_data_model(self.geometry, self.is_cos, self.max_cycles)
|
|
339
|
+
for la in self.data_model.keys():
|
|
340
|
+
setattr(self, la, list())
|
|
341
|
+
|
|
342
|
+
if self.dump:
|
|
343
|
+
out_trj_fn = self.get_path_for_fn("optimization_trj.xyz")
|
|
344
|
+
self.out_trj_handle = open(out_trj_fn, "w")
|
|
345
|
+
# Call with reset=True to delete remnants of previous calculations, unless
|
|
346
|
+
# the optimizer was restarted. Given a previous optimization with, e.g. 30
|
|
347
|
+
# cycles and a second restarted optimization with 20 cycles the last 10 cycles
|
|
348
|
+
# of the previous optimization would still be present.
|
|
349
|
+
reset = restart_info is None
|
|
350
|
+
# h5_group = get_h5_group(
|
|
351
|
+
# self.h5_fn, self.h5_group_name, self.data_model, reset=reset
|
|
352
|
+
# )
|
|
353
|
+
# h5_group.file.close()
|
|
354
|
+
if self.prefix:
|
|
355
|
+
self.log(f"Created optimizer with prefix {self.prefix}")
|
|
356
|
+
|
|
357
|
+
self.restarted = False
|
|
358
|
+
self.last_cycle = 0
|
|
359
|
+
self.cur_cycle = 0
|
|
360
|
+
if restart_info is not None:
|
|
361
|
+
if isinstance(restart_info, str):
|
|
362
|
+
restart_info = yaml.load(restart_info, Loader=yaml.SafeLoader)
|
|
363
|
+
self.set_restart_info(restart_info)
|
|
364
|
+
self.restarted = True
|
|
365
|
+
|
|
366
|
+
header = "cycle Δ(energy) max(|force|) rms(force) max(|step|) rms(step) s/cycle".split()
|
|
367
|
+
col_fmts = "int float float float float float float_short".split()
|
|
368
|
+
self.table = TablePrinter(header, col_fmts, width=12)
|
|
369
|
+
self.is_converged = False
|
|
370
|
+
|
|
371
|
+
def get_path_for_fn(self, fn, with_prefix=True):
|
|
372
|
+
prefix = self.prefix if with_prefix else ""
|
|
373
|
+
return self.out_dir / (prefix + fn)
|
|
374
|
+
|
|
375
|
+
def make_conv_dict(
|
|
376
|
+
self, key, rms_force=None, rms_force_only=False, max_force_only=False, force_only=False
|
|
377
|
+
):
|
|
378
|
+
if not rms_force:
|
|
379
|
+
threshs = CONV_THRESHS[key]
|
|
380
|
+
else:
|
|
381
|
+
print(
|
|
382
|
+
"Deriving convergence threshold from supplied "
|
|
383
|
+
f"rms_force={rms_force}."
|
|
384
|
+
)
|
|
385
|
+
threshs = (
|
|
386
|
+
1.5 * rms_force,
|
|
387
|
+
rms_force,
|
|
388
|
+
6 * rms_force,
|
|
389
|
+
4 * rms_force,
|
|
390
|
+
)
|
|
391
|
+
keys = keep_keys = [
|
|
392
|
+
"max_force_thresh",
|
|
393
|
+
"rms_force_thresh",
|
|
394
|
+
"max_step_thresh",
|
|
395
|
+
"rms_step_thresh",
|
|
396
|
+
]
|
|
397
|
+
conv_dict = {k: v for k, v in zip(keys, threshs)}
|
|
398
|
+
|
|
399
|
+
# Only used gradient information for COS optimizations
|
|
400
|
+
if self.is_cos:
|
|
401
|
+
keep_keys = ["max_force_thresh", "rms_force_thresh"]
|
|
402
|
+
|
|
403
|
+
if rms_force_only:
|
|
404
|
+
self.log("Checking convergence with rms(forces) only!")
|
|
405
|
+
keep_keys = ["rms_force_thresh"]
|
|
406
|
+
elif max_force_only:
|
|
407
|
+
self.log("Checking convergence with max(forces) only!")
|
|
408
|
+
keep_keys = ["max_force_thresh"]
|
|
409
|
+
elif force_only:
|
|
410
|
+
self.log("Checking convergence with max(forces) and rms(forces) only!")
|
|
411
|
+
keep_keys = ["max_force_thresh", "rms_force_thresh"]
|
|
412
|
+
|
|
413
|
+
# The dictionary should only contain pairs that are needed
|
|
414
|
+
conv_dict = {key: value for key, value in conv_dict.items() if key in keep_keys}
|
|
415
|
+
return conv_dict
|
|
416
|
+
|
|
417
|
+
def report_conv_thresholds(self):
|
|
418
|
+
oaf = self.overachieve_factor
|
|
419
|
+
|
|
420
|
+
# Overachieved
|
|
421
|
+
def oa(val):
|
|
422
|
+
return f", ({val/oaf:.6f})" if oaf > 0.0 else ""
|
|
423
|
+
|
|
424
|
+
internal_coords = self.geometry.coord_type not in (
|
|
425
|
+
"cart",
|
|
426
|
+
"cartesian",
|
|
427
|
+
"mwcartesian",
|
|
428
|
+
)
|
|
429
|
+
fu = "E_h a_0⁻¹" + (" (rad⁻¹)" if internal_coords else "") # forces unit
|
|
430
|
+
su = "a_0" + (" (rad)" if internal_coords else "") # step unit
|
|
431
|
+
|
|
432
|
+
try:
|
|
433
|
+
rms_thresh = f"\tmax(|force|) <= {self.max_force_thresh:.6f}{oa(self.max_force_thresh)} {fu}"
|
|
434
|
+
except AttributeError:
|
|
435
|
+
rms_thresh = None
|
|
436
|
+
try:
|
|
437
|
+
max_thresh = f"\t rms(force) <= {self.rms_force_thresh:.6f}{oa(self.rms_force_thresh)} {fu}"
|
|
438
|
+
except AttributeError:
|
|
439
|
+
max_thresh = None
|
|
440
|
+
threshs = (rms_thresh, max_thresh)
|
|
441
|
+
|
|
442
|
+
if self.rms_force_only:
|
|
443
|
+
use_threshs = (threshs[1],)
|
|
444
|
+
elif self.max_force_only:
|
|
445
|
+
use_threshs = (threshs[0],)
|
|
446
|
+
elif self.force_only:
|
|
447
|
+
use_threshs = threshs
|
|
448
|
+
elif self.is_cos:
|
|
449
|
+
use_threshs = threshs
|
|
450
|
+
else:
|
|
451
|
+
use_threshs = threshs + (
|
|
452
|
+
f"\t max(|step|) <= {self.max_step_thresh:.6f} {su}",
|
|
453
|
+
f"\t rms(step) <= {self.rms_step_thresh:.6f} {su}",
|
|
454
|
+
)
|
|
455
|
+
if self.thresh == "baker":
|
|
456
|
+
use_threshs = use_threshs + ("\t Δ(energy) <= 0.000001 E_h",)
|
|
457
|
+
print(
|
|
458
|
+
"Convergence thresholds"
|
|
459
|
+
+ (", (overachieved when)" if oaf > 0.0 else "")
|
|
460
|
+
+ ":\n"
|
|
461
|
+
+ "\n".join(use_threshs)
|
|
462
|
+
+ f"\n\t'Superscript {self.table.mark}' indicates convergence"
|
|
463
|
+
+ "\n"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
def log(self, message, level=50):
|
|
467
|
+
self.logger.log(level, message)
|
|
468
|
+
|
|
469
|
+
def check_convergence(self, step=None, multiple=1.0, overachieve_factor=None):
|
|
470
|
+
"""Check if the current convergence of the optimization
|
|
471
|
+
is equal to or below the required thresholds, or a multiple
|
|
472
|
+
thereof. The latter may be used in initiating the climbing image.
|
|
473
|
+
"""
|
|
474
|
+
|
|
475
|
+
if step is None:
|
|
476
|
+
step = self.steps[-1]
|
|
477
|
+
if overachieve_factor is None:
|
|
478
|
+
overachieve_factor = self.overachieve_factor
|
|
479
|
+
|
|
480
|
+
if isinstance(step, torch.Tensor):
|
|
481
|
+
step = step.detach().cpu().numpy()
|
|
482
|
+
|
|
483
|
+
# When using a ChainOfStates method we are only interested
|
|
484
|
+
# in optimizing the forces perpendicular to the MEP.
|
|
485
|
+
if self.is_cos:
|
|
486
|
+
forces = self.geometry.perpendicular_forces
|
|
487
|
+
elif len(self.modified_forces) == len(self.forces):
|
|
488
|
+
self.log("Using modified forces to determine convergence!")
|
|
489
|
+
forces = self.modified_forces[-1]
|
|
490
|
+
else:
|
|
491
|
+
forces = self.forces[-1]
|
|
492
|
+
|
|
493
|
+
# The forces of fixed images may be zero and this may distort the RMS
|
|
494
|
+
# values. So we take into account the number of moving images with
|
|
495
|
+
# non-zero forces vectors.
|
|
496
|
+
if self.is_cos:
|
|
497
|
+
non_zero_elements = (
|
|
498
|
+
len(self.geometry.moving_indices) * self.geometry.coords_length
|
|
499
|
+
)
|
|
500
|
+
rms_force = np.sqrt(np.sum(np.square(forces)) / non_zero_elements)
|
|
501
|
+
rms_step = np.sqrt(np.sum(np.square(step)) / non_zero_elements)
|
|
502
|
+
else:
|
|
503
|
+
rms_force = np.sqrt(np.mean(np.square(forces)))
|
|
504
|
+
rms_step = np.sqrt(np.mean(np.square(step)))
|
|
505
|
+
|
|
506
|
+
max_force = np.abs(forces).max()
|
|
507
|
+
max_step = np.abs(step).max()
|
|
508
|
+
|
|
509
|
+
self.max_forces.append(max_force)
|
|
510
|
+
self.rms_forces.append(rms_force)
|
|
511
|
+
self.max_steps.append(max_step)
|
|
512
|
+
self.rms_steps.append(rms_step)
|
|
513
|
+
|
|
514
|
+
# Give geometry a chance to signal convergence, e.g. GrowingNT that
|
|
515
|
+
# is supposed to stop when a TS was passed.
|
|
516
|
+
try:
|
|
517
|
+
geom_converged = self.geometry.check_convergence()
|
|
518
|
+
except AttributeError:
|
|
519
|
+
geom_converged = False
|
|
520
|
+
|
|
521
|
+
converged_to_geom = False
|
|
522
|
+
if self.converge_to_geom is not None:
|
|
523
|
+
rmsd = np.sqrt(
|
|
524
|
+
np.mean((self.converge_to_geom.coords - self.geometry.coords) ** 2)
|
|
525
|
+
)
|
|
526
|
+
converged_to_geom = rmsd < self.converge_to_geom_rms_thresh
|
|
527
|
+
|
|
528
|
+
this_cycle = {
|
|
529
|
+
"max_force_thresh": max_force,
|
|
530
|
+
"rms_force_thresh": rms_force,
|
|
531
|
+
"max_step_thresh": max_step,
|
|
532
|
+
"rms_step_thresh": rms_step,
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
def check(key):
|
|
536
|
+
# Always return True if given key is not checked
|
|
537
|
+
key_is_checked = key in self.convergence
|
|
538
|
+
if key_is_checked:
|
|
539
|
+
result = this_cycle[key] <= getattr(self, key) * multiple
|
|
540
|
+
else:
|
|
541
|
+
result = True
|
|
542
|
+
return result
|
|
543
|
+
|
|
544
|
+
convergence = {
|
|
545
|
+
"energy_converged": True,
|
|
546
|
+
"max_force_converged": check("max_force_thresh"),
|
|
547
|
+
"rms_force_converged": check("rms_force_thresh"),
|
|
548
|
+
"max_step_converged": check("max_step_thresh"),
|
|
549
|
+
"rms_step_converged": check("rms_step_thresh"),
|
|
550
|
+
}
|
|
551
|
+
# For TS optimizations we also try to check the eigenvalue structure of the
|
|
552
|
+
# Hessian. A saddle point of order n must have exatcly only n significant negative
|
|
553
|
+
# eigenvalues. We try to check this below.
|
|
554
|
+
#
|
|
555
|
+
# Currently, this is not totally strict,
|
|
556
|
+
# as only the values in self.ts_mode_eigvals are checked but actually all eigenvalues
|
|
557
|
+
# would have to be checked.
|
|
558
|
+
desired_eigval_structure = True
|
|
559
|
+
if self.check_eigval_structure:
|
|
560
|
+
try:
|
|
561
|
+
desired_eigval_structure = (
|
|
562
|
+
# Acutally all eigenvalues would have to be checked, but
|
|
563
|
+
# currently they are not stored anywhere.
|
|
564
|
+
self.ts_mode_eigvals
|
|
565
|
+
< self.small_eigval_thresh
|
|
566
|
+
).sum() == len(self.roots)
|
|
567
|
+
except AttributeError:
|
|
568
|
+
self.log(
|
|
569
|
+
"Skipping check of eigenvalue structure, as information is unavailable."
|
|
570
|
+
)
|
|
571
|
+
convergence["desired_eigval_structure"] = desired_eigval_structure
|
|
572
|
+
conv_info = ConvInfo(self.cur_cycle, **convergence)
|
|
573
|
+
|
|
574
|
+
# Check if force convergence is overachieved. If the eigenvalue-structure is
|
|
575
|
+
# checked, a wrong structure will prevent overachieved convergence.
|
|
576
|
+
overachieved = False
|
|
577
|
+
if overachieve_factor > 0 and desired_eigval_structure:
|
|
578
|
+
max_thresh = self.convergence.get("max_force_thresh", 0) / overachieve_factor
|
|
579
|
+
rms_thresh = self.convergence.get("rms_force_thresh", 0) / overachieve_factor
|
|
580
|
+
max_ = max_force < max_thresh
|
|
581
|
+
rms_ = rms_force < rms_thresh
|
|
582
|
+
overachieved = max_ and rms_
|
|
583
|
+
if max_:
|
|
584
|
+
self.log("max(force) is overachieved")
|
|
585
|
+
if rms_:
|
|
586
|
+
self.log("rms(force) is overachieved")
|
|
587
|
+
if max_ and rms_:
|
|
588
|
+
self.log("Force convergence overachieved!")
|
|
589
|
+
|
|
590
|
+
converged = all([val for val in convergence.values()])
|
|
591
|
+
not_never = self.thresh != "never"
|
|
592
|
+
|
|
593
|
+
if self.thresh == "baker":
|
|
594
|
+
energy_converged = False
|
|
595
|
+
if len(self.energies) >= 2:
|
|
596
|
+
cur_energy = np.asarray(self.energies[-1])
|
|
597
|
+
prev_energy = np.asarray(self.energies[-2])
|
|
598
|
+
if cur_energy.shape == prev_energy.shape:
|
|
599
|
+
energy_converged = np.all(np.abs(cur_energy - prev_energy) < 1e-6)
|
|
600
|
+
|
|
601
|
+
# Enforce at least one new cycle after (re)start and require energy convergence.
|
|
602
|
+
convergence["energy_converged"] = bool(energy_converged)
|
|
603
|
+
conv_info = ConvInfo(self.cur_cycle, **convergence)
|
|
604
|
+
converged = (self.cur_cycle > self.last_cycle) and all(convergence.values())
|
|
605
|
+
# Keep Baker strict: don't bypass the energy criterion via overachievement.
|
|
606
|
+
overachieved = False
|
|
607
|
+
return (
|
|
608
|
+
any((converged_to_geom, converged, overachieved, geom_converged))
|
|
609
|
+
and not_never,
|
|
610
|
+
conv_info,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
def print_opt_progress(self, conv_info):
|
|
614
|
+
try:
|
|
615
|
+
energy_diff = self.energies[-1] - self.energies[-2]
|
|
616
|
+
# ValueError: maybe raised when the number of images differ in cycles
|
|
617
|
+
# IndexError: raised in first cycle when only one energy is present
|
|
618
|
+
except (ValueError, IndexError):
|
|
619
|
+
energy_diff = float("nan")
|
|
620
|
+
|
|
621
|
+
# Try to sum COS energies
|
|
622
|
+
try:
|
|
623
|
+
energy_diff = sum(energy_diff)
|
|
624
|
+
except TypeError:
|
|
625
|
+
pass
|
|
626
|
+
|
|
627
|
+
if (self.cur_cycle > 1) and (self.cur_cycle % 10 == 0):
|
|
628
|
+
self.table.print_sep()
|
|
629
|
+
|
|
630
|
+
# desired_eigval_structure is also returned, but currently not reported.
|
|
631
|
+
marks = [False, *conv_info.get_convergence()[:-1], False]
|
|
632
|
+
try:
|
|
633
|
+
cycle_time = self.cycle_times[-1]
|
|
634
|
+
except IndexError:
|
|
635
|
+
cycle_time = 0.0
|
|
636
|
+
self.table.print_row(
|
|
637
|
+
(
|
|
638
|
+
self.cur_cycle,
|
|
639
|
+
energy_diff,
|
|
640
|
+
self.max_forces[-1],
|
|
641
|
+
self.rms_forces[-1],
|
|
642
|
+
self.max_steps[-1],
|
|
643
|
+
self.rms_steps[-1],
|
|
644
|
+
cycle_time,
|
|
645
|
+
),
|
|
646
|
+
marks=marks,
|
|
647
|
+
)
|
|
648
|
+
try:
|
|
649
|
+
# Geometries/ChainOfStates objects can also do some printing.
|
|
650
|
+
add_info = self.geometry.get_additional_print()
|
|
651
|
+
if add_info:
|
|
652
|
+
self.table.print(add_info)
|
|
653
|
+
except AttributeError:
|
|
654
|
+
pass
|
|
655
|
+
|
|
656
|
+
def fit_rigid(self, *, vectors=None, vector_lists=None, hessian=None):
|
|
657
|
+
return fit_rigid(
|
|
658
|
+
self.geometry,
|
|
659
|
+
vectors=vectors,
|
|
660
|
+
vector_lists=vector_lists,
|
|
661
|
+
hessian=hessian,
|
|
662
|
+
align_factor=self.align_factor,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
def procrustes(self):
|
|
666
|
+
"""Wrapper for procrustes that passes additional arguments along."""
|
|
667
|
+
procrustes(self.geometry, self.align_factor)
|
|
668
|
+
|
|
669
|
+
def scale_by_max_step(self, steps):
|
|
670
|
+
steps_max = np.abs(steps).max()
|
|
671
|
+
if steps_max > self.max_step:
|
|
672
|
+
steps *= self.max_step / steps_max
|
|
673
|
+
return steps
|
|
674
|
+
|
|
675
|
+
def prepare_opt(self):
|
|
676
|
+
pass
|
|
677
|
+
|
|
678
|
+
def postprocess_opt(self):
|
|
679
|
+
pass
|
|
680
|
+
|
|
681
|
+
@abc.abstractmethod
|
|
682
|
+
def optimize(self):
|
|
683
|
+
pass
|
|
684
|
+
|
|
685
|
+
def write_to_out_dir(self, out_fn, content, mode="w"):
|
|
686
|
+
out_path = self.out_dir / out_fn
|
|
687
|
+
with open(out_path, mode) as handle:
|
|
688
|
+
handle.write(content)
|
|
689
|
+
|
|
690
|
+
def write_image_trjs(self):
|
|
691
|
+
base_name = "image_{:03d}_trj.xyz"
|
|
692
|
+
for i, image in enumerate(self.geometry.images):
|
|
693
|
+
image_fn = base_name.format(i)
|
|
694
|
+
comment = f"cycle {self.cur_cycle}"
|
|
695
|
+
as_xyz = image.as_xyz(comment)
|
|
696
|
+
self.write_to_out_dir(image_fn, as_xyz + "\n", "a")
|
|
697
|
+
|
|
698
|
+
def write_results(self):
|
|
699
|
+
# Save results from the Optimizer to HDF5 file if requested
|
|
700
|
+
# h5_group = get_h5_group(self.h5_fn, self.h5_group_name)
|
|
701
|
+
|
|
702
|
+
# Some attributes never change and are only set in the first cycle
|
|
703
|
+
# if self.cur_cycle == 0:
|
|
704
|
+
# h5_group.attrs["is_cos"] = self.is_cos
|
|
705
|
+
# try:
|
|
706
|
+
# atoms = self.geometry.images[0].atoms
|
|
707
|
+
# coord_size = self.geometry.images[0].coords.size
|
|
708
|
+
# except AttributeError:
|
|
709
|
+
# atoms = self.geometry.atoms
|
|
710
|
+
# coord_size = self.geometry.coords.size
|
|
711
|
+
# h5_group.attrs["atoms"] = np.bytes_(atoms)
|
|
712
|
+
# h5_group.attrs["coord_type"] = self.geometry.coord_type
|
|
713
|
+
# h5_group.attrs["coord_size"] = coord_size
|
|
714
|
+
# h5_group.attrs["overachieve_factor"] = self.overachieve_factor
|
|
715
|
+
# for key in (
|
|
716
|
+
# "max_force_thresh",
|
|
717
|
+
# "rms_force_thresh",
|
|
718
|
+
# "max_step_thresh",
|
|
719
|
+
# "rms_step_thresh",
|
|
720
|
+
# ):
|
|
721
|
+
# try:
|
|
722
|
+
# h5_group.attrs[key] = self.convergence[key]
|
|
723
|
+
# # Step threshold may not be present
|
|
724
|
+
# except KeyError:
|
|
725
|
+
# pass
|
|
726
|
+
|
|
727
|
+
# Update changing attributes
|
|
728
|
+
# h5_group.attrs["cur_cycle"] = self.cur_cycle
|
|
729
|
+
# h5_group.attrs["is_converged"] = self.is_converged
|
|
730
|
+
|
|
731
|
+
# for key, shape in self.data_model.items():
|
|
732
|
+
# value = getattr(self, key)
|
|
733
|
+
# # Don't try to set empty values, e.g. 'tangents' are only present
|
|
734
|
+
# # for COS methods. 'modified_forces' are only present for NCOptimizer.
|
|
735
|
+
# if not value:
|
|
736
|
+
# continue
|
|
737
|
+
# if len(shape) > 1:
|
|
738
|
+
# h5_group[key][self.cur_cycle, : len(value[-1])] = value[-1]
|
|
739
|
+
# else:
|
|
740
|
+
# h5_group[key][self.cur_cycle] = value[-1]
|
|
741
|
+
|
|
742
|
+
# h5_group.file.close()
|
|
743
|
+
pass
|
|
744
|
+
|
|
745
|
+
def write_cycle_to_file(self):
|
|
746
|
+
as_xyz_str = self.geometry.as_xyz()
|
|
747
|
+
|
|
748
|
+
if self.is_cos:
|
|
749
|
+
out_fn = "cycle_{:03d}_trj.xyz".format(self.cur_cycle)
|
|
750
|
+
self.write_to_out_dir(out_fn, as_xyz_str)
|
|
751
|
+
# Also write separate _trj.xyz files for every image in the cos
|
|
752
|
+
self.write_image_trjs()
|
|
753
|
+
|
|
754
|
+
# Dump current HEI
|
|
755
|
+
max_ind = np.argmax(self.energies[-1])
|
|
756
|
+
with open(self.hei_trj_fn, "a") as handle:
|
|
757
|
+
handle.write(self.geometry.images[max_ind].as_xyz() + "\n")
|
|
758
|
+
|
|
759
|
+
else:
|
|
760
|
+
# Append to _trj.xyz file
|
|
761
|
+
self.out_trj_handle.write(as_xyz_str + "\n")
|
|
762
|
+
self.out_trj_handle.flush()
|
|
763
|
+
# Dump to HDF5
|
|
764
|
+
# self.write_results()
|
|
765
|
+
|
|
766
|
+
def final_summary(self):
|
|
767
|
+
# If the optimization was stopped _forces may not be set, so
|
|
768
|
+
# then we force a calculation if it was not already set.
|
|
769
|
+
_ = self.geometry.forces
|
|
770
|
+
cart_forces = self.geometry.cart_forces
|
|
771
|
+
max_cart_forces = np.abs(cart_forces).max()
|
|
772
|
+
rms_cart_forces = np.sqrt(np.mean(cart_forces**2))
|
|
773
|
+
int_str = ""
|
|
774
|
+
if self.geometry.coord_type not in ("cart", "cartesian", "mwcartesian"):
|
|
775
|
+
int_forces = self.geometry.forces
|
|
776
|
+
max_int_forces = np.abs(int_forces).max()
|
|
777
|
+
rms_int_forces = np.sqrt(np.mean(int_forces**2))
|
|
778
|
+
int_str = f"""
|
|
779
|
+
\tmax(forces, internal): {max_int_forces:.6f} hartree/(bohr,rad)
|
|
780
|
+
\trms(forces, internal): {rms_int_forces:.6f} hartree/(bohr,rad)"""
|
|
781
|
+
energy = self.geometry.energy
|
|
782
|
+
final_summary = f"""
|
|
783
|
+
Final summary:{int_str}
|
|
784
|
+
\tmax(forces,cartesian): {max_cart_forces:.6f} hartree/bohr
|
|
785
|
+
\trms(forces,cartesian): {rms_cart_forces:.6f} hartree/bohr
|
|
786
|
+
\tenergy: {energy:.8f} hartree
|
|
787
|
+
"""
|
|
788
|
+
return textwrap.dedent(final_summary.strip())
|
|
789
|
+
|
|
790
|
+
def run(self):
|
|
791
|
+
print("If not specified otherwise, all quantities are given in au.\n")
|
|
792
|
+
|
|
793
|
+
if not self.restarted:
|
|
794
|
+
prep_start_time = time.time()
|
|
795
|
+
self.prepare_opt()
|
|
796
|
+
self.log(f"{self.geometry.coords.size} degrees of freedom.")
|
|
797
|
+
prep_end_time = time.time()
|
|
798
|
+
prep_time = prep_end_time - prep_start_time
|
|
799
|
+
self.report_conv_thresholds()
|
|
800
|
+
print(f"Spent {prep_time:.1f} s preparing the first cycle.")
|
|
801
|
+
|
|
802
|
+
self.table.print_header(with_sep=False)
|
|
803
|
+
self.stopped = False
|
|
804
|
+
# Actual optimization loop
|
|
805
|
+
for self.cur_cycle in range(self.last_cycle, self.max_cycles):
|
|
806
|
+
start_time = time.time()
|
|
807
|
+
self.log(highlight_text(f"Cycle {self.cur_cycle:03d}"))
|
|
808
|
+
|
|
809
|
+
if self.is_cos and self.check_coord_diffs:
|
|
810
|
+
image_coords = [image.cart_coords for image in self.geometry.images]
|
|
811
|
+
align = len(image_coords[0]) > 3
|
|
812
|
+
cds = get_coords_diffs(image_coords, align=align)
|
|
813
|
+
# Differences of coordinate differences ;)
|
|
814
|
+
cds_diffs = np.diff(cds)
|
|
815
|
+
min_ind = cds_diffs.argmin()
|
|
816
|
+
if cds_diffs[min_ind] < self.coord_diff_thresh:
|
|
817
|
+
similar_inds = min_ind, min_ind + 1
|
|
818
|
+
msg = (
|
|
819
|
+
f"Cartesian coordinates of images {similar_inds} are "
|
|
820
|
+
"too similar. Stopping optimization!"
|
|
821
|
+
)
|
|
822
|
+
# I should improve my logging :)
|
|
823
|
+
print(msg)
|
|
824
|
+
self.log(msg)
|
|
825
|
+
sim_fn = "too_similar_trj.xyz"
|
|
826
|
+
with open(sim_fn, "w") as handle:
|
|
827
|
+
handle.write(self.geometry.as_xyz())
|
|
828
|
+
print(f"Dumped latest coordinates to '{sim_fn}'.")
|
|
829
|
+
break
|
|
830
|
+
|
|
831
|
+
# Check if something considerably changed in the optimization,
|
|
832
|
+
# e.g. new images were added/interpolated. Then the optimizer
|
|
833
|
+
# should be reset.
|
|
834
|
+
reset_flag = False
|
|
835
|
+
if self.cur_cycle > 0 and self.is_cos:
|
|
836
|
+
reset_flag = self.geometry.prepare_opt_cycle(
|
|
837
|
+
self.coords[-1], self.energies[-1], self.forces[-1]
|
|
838
|
+
)
|
|
839
|
+
# Reset when number of coordinates changed
|
|
840
|
+
elif self.cur_cycle > 0:
|
|
841
|
+
reset_flag = reset_flag or (
|
|
842
|
+
self.geometry.coords.size != self.coords[-1].size
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
if reset_flag:
|
|
846
|
+
self.reset()
|
|
847
|
+
|
|
848
|
+
# Coordinates may be updated here.
|
|
849
|
+
if self.reparam_when == "before" and hasattr(
|
|
850
|
+
self.geometry, "reparametrize"
|
|
851
|
+
):
|
|
852
|
+
# This call actually returns a bool, but right now we just drop it.
|
|
853
|
+
self.geometry.reparametrize()
|
|
854
|
+
|
|
855
|
+
self.coords.append(self.geometry.coords.copy())
|
|
856
|
+
self.cart_coords.append(self.geometry.cart_coords.copy())
|
|
857
|
+
|
|
858
|
+
# Determine and store number of currenctly actively optimized images
|
|
859
|
+
try:
|
|
860
|
+
image_inds = self.geometry.image_inds
|
|
861
|
+
image_num = len(image_inds)
|
|
862
|
+
except AttributeError:
|
|
863
|
+
image_inds = [
|
|
864
|
+
0,
|
|
865
|
+
]
|
|
866
|
+
image_num = 1
|
|
867
|
+
self.image_inds.append(image_inds)
|
|
868
|
+
self.image_nums.append(image_num)
|
|
869
|
+
|
|
870
|
+
# Here the actual step is obtained from the actual optimizer class.
|
|
871
|
+
step = self.optimize()
|
|
872
|
+
step_norm = np.linalg.norm(step)
|
|
873
|
+
self.log(f"norm(step)={step_norm:.6f} au (rad)")
|
|
874
|
+
for source, target in (
|
|
875
|
+
("true_energy", "true_energies"),
|
|
876
|
+
("true_forces", "true_forces"),
|
|
877
|
+
):
|
|
878
|
+
try:
|
|
879
|
+
if (value := getattr(self.geometry, source)) is not None:
|
|
880
|
+
getattr(self, target).append(value)
|
|
881
|
+
except AttributeError:
|
|
882
|
+
pass
|
|
883
|
+
|
|
884
|
+
if step is None:
|
|
885
|
+
# Remove the previously added coords
|
|
886
|
+
self.coords.pop(-1)
|
|
887
|
+
self.cart_coords.pop(-1)
|
|
888
|
+
continue
|
|
889
|
+
|
|
890
|
+
if self.is_cos:
|
|
891
|
+
self.tangents.append(self.geometry.get_tangents().flatten())
|
|
892
|
+
|
|
893
|
+
self.steps.append(step)
|
|
894
|
+
|
|
895
|
+
# Convergence check
|
|
896
|
+
self.is_converged, conv_info = self.check_convergence()
|
|
897
|
+
|
|
898
|
+
end_time = time.time()
|
|
899
|
+
elapsed_seconds = end_time - start_time
|
|
900
|
+
self.cycle_times.append(elapsed_seconds)
|
|
901
|
+
|
|
902
|
+
if self.dump:
|
|
903
|
+
self.write_cycle_to_file()
|
|
904
|
+
with open(self.current_fn, "w") as handle:
|
|
905
|
+
handle.write(self.geometry.as_xyz())
|
|
906
|
+
|
|
907
|
+
if (
|
|
908
|
+
self.dump
|
|
909
|
+
and self.dump_restart
|
|
910
|
+
and (self.cur_cycle % self.dump_restart) == 0
|
|
911
|
+
):
|
|
912
|
+
self.dump_restart_info()
|
|
913
|
+
|
|
914
|
+
if (self.cur_cycle % self.print_every) == 0 or self.is_converged:
|
|
915
|
+
self.print_opt_progress(conv_info)
|
|
916
|
+
if self.is_converged:
|
|
917
|
+
self.table.print("Converged!")
|
|
918
|
+
break
|
|
919
|
+
# Allow convergence, before checking for too small steps
|
|
920
|
+
elif self.assert_min_step and (step_norm <= self.min_step_norm):
|
|
921
|
+
raise ZeroStepLength
|
|
922
|
+
|
|
923
|
+
# Update coordinates
|
|
924
|
+
new_coords = self.geometry.coords.copy() + step
|
|
925
|
+
try:
|
|
926
|
+
self.geometry.coords = new_coords
|
|
927
|
+
# Use the actual step. It may differ from the proposed step
|
|
928
|
+
# when internal coordinates are used, as the internal-Cartesian
|
|
929
|
+
# transformation is done iteratively.
|
|
930
|
+
self.steps[-1] = self.geometry.coords - self.coords[-1]
|
|
931
|
+
except RebuiltInternalsException as exception:
|
|
932
|
+
print("Rebuilt internal coordinates!")
|
|
933
|
+
rebuilt_fn = self.get_path_for_fn("rebuilt_primitives.xyz")
|
|
934
|
+
with open(rebuilt_fn, "w") as handle:
|
|
935
|
+
handle.write(self.geometry.as_xyz())
|
|
936
|
+
if self.is_cos:
|
|
937
|
+
for image in self.geometry.images:
|
|
938
|
+
image.reset_coords(exception.typed_prims)
|
|
939
|
+
self.reset()
|
|
940
|
+
|
|
941
|
+
if self.dump:
|
|
942
|
+
self.data_model = get_data_model(
|
|
943
|
+
self.geometry, self.is_cos, self.max_cycles
|
|
944
|
+
)
|
|
945
|
+
self.h5_group_name += "_rebuilt"
|
|
946
|
+
# h5_group = get_h5_group(
|
|
947
|
+
# self.h5_fn,
|
|
948
|
+
# self.h5_group_name,
|
|
949
|
+
# self.data_model,
|
|
950
|
+
# reset=True,
|
|
951
|
+
# )
|
|
952
|
+
# h5_group.file.close()
|
|
953
|
+
|
|
954
|
+
# Coordinates may be updated here.
|
|
955
|
+
if (self.reparam_when == "after") and hasattr(
|
|
956
|
+
self.geometry, "reparametrize"
|
|
957
|
+
):
|
|
958
|
+
reparametrized = self.geometry.reparametrize()
|
|
959
|
+
else:
|
|
960
|
+
reparametrized = False
|
|
961
|
+
|
|
962
|
+
cur_coords = self.geometry.coords
|
|
963
|
+
prev_coords = self.coords[-1]
|
|
964
|
+
if (
|
|
965
|
+
self.is_cos
|
|
966
|
+
and self.reparam_check_rms
|
|
967
|
+
and reparametrized
|
|
968
|
+
and (cur_coords.size == prev_coords.size)
|
|
969
|
+
):
|
|
970
|
+
self.log("Did reparametrization")
|
|
971
|
+
|
|
972
|
+
rms = np.sqrt(np.mean((prev_coords - cur_coords) ** 2))
|
|
973
|
+
self.log(f"rms of coordinates after reparametrization={rms:.6f}")
|
|
974
|
+
self.is_converged = rms < self.reparam_thresh
|
|
975
|
+
if self.is_converged:
|
|
976
|
+
self.table.print(
|
|
977
|
+
"Insignificant coordinate change after "
|
|
978
|
+
"reparametrization. Signalling convergence!"
|
|
979
|
+
)
|
|
980
|
+
break
|
|
981
|
+
|
|
982
|
+
# Alternative: calcualte overlap of AFIR force and step. If this
|
|
983
|
+
# overlap is negative the step is taken against the AFIR force.
|
|
984
|
+
if self.monitor_frag_dists_counter > 0:
|
|
985
|
+
interfrag_dist = interfragment_distance(
|
|
986
|
+
*self.fragments, self.geometry.coords3d
|
|
987
|
+
)
|
|
988
|
+
try:
|
|
989
|
+
prev_interfrag_dist = self.interfrag_dists[-1]
|
|
990
|
+
if interfrag_dist > prev_interfrag_dist:
|
|
991
|
+
print("Interfragment distances increased!")
|
|
992
|
+
self.stopped = True # Can't use := in if clause
|
|
993
|
+
break
|
|
994
|
+
except IndexError:
|
|
995
|
+
pass
|
|
996
|
+
self.interfrag_dists.append(interfrag_dist)
|
|
997
|
+
self.monitor_frag_dists_counter -= 1
|
|
998
|
+
|
|
999
|
+
sys.stdout.flush()
|
|
1000
|
+
sign = check_for_end_sign()
|
|
1001
|
+
if sign == "stop":
|
|
1002
|
+
self.stopped = True
|
|
1003
|
+
break
|
|
1004
|
+
elif sign == "converged":
|
|
1005
|
+
self.converged = True
|
|
1006
|
+
self.table.print("Operator indicated convergence!")
|
|
1007
|
+
break
|
|
1008
|
+
|
|
1009
|
+
self.log("")
|
|
1010
|
+
else:
|
|
1011
|
+
self.table.print("Number of cycles exceeded!")
|
|
1012
|
+
|
|
1013
|
+
# Outside loop
|
|
1014
|
+
print()
|
|
1015
|
+
if self.dump:
|
|
1016
|
+
self.out_trj_handle.close()
|
|
1017
|
+
|
|
1018
|
+
if (not self.is_cos) and (not self.stopped):
|
|
1019
|
+
print(self.final_summary())
|
|
1020
|
+
# Remove 'current_geometry.xyz' file
|
|
1021
|
+
try:
|
|
1022
|
+
os.remove(self.current_fn)
|
|
1023
|
+
except FileNotFoundError:
|
|
1024
|
+
self.log(f"Tried to delete '{self.current_fn}'. Couldn't find it.")
|
|
1025
|
+
with open(self.final_fn, "w") as handle:
|
|
1026
|
+
handle.write(self.geometry.as_xyz())
|
|
1027
|
+
self.table.print(
|
|
1028
|
+
f"Wrote final, hopefully optimized, geometry to '{self.final_fn.name}'"
|
|
1029
|
+
)
|
|
1030
|
+
self.postprocess_opt()
|
|
1031
|
+
sys.stdout.flush()
|
|
1032
|
+
|
|
1033
|
+
def _get_opt_restart_info(self):
|
|
1034
|
+
"""To be re-implemented in the derived classes."""
|
|
1035
|
+
return dict()
|
|
1036
|
+
|
|
1037
|
+
def _set_opt_restart_info(self, opt_restart_info):
|
|
1038
|
+
"""To be re-implemented in the derived classes."""
|
|
1039
|
+
return
|
|
1040
|
+
|
|
1041
|
+
def get_restart_info(self):
|
|
1042
|
+
restart_info = {
|
|
1043
|
+
"geom_info": self.geometry.get_restart_info(),
|
|
1044
|
+
"last_cycle": self.cur_cycle,
|
|
1045
|
+
"max_cycles": self.max_cycles,
|
|
1046
|
+
"energies": self.energies,
|
|
1047
|
+
"coords": self.coords,
|
|
1048
|
+
"forces": [forces.tolist() for forces in self.forces],
|
|
1049
|
+
"steps": [step.tolist() for step in self.steps],
|
|
1050
|
+
}
|
|
1051
|
+
restart_info.update(self._get_opt_restart_info())
|
|
1052
|
+
return restart_info
|
|
1053
|
+
|
|
1054
|
+
def set_restart_info(self, restart_info):
|
|
1055
|
+
# Set restart information general to all optimizers
|
|
1056
|
+
self.last_cycle = restart_info["last_cycle"] + 1
|
|
1057
|
+
|
|
1058
|
+
must_resize = self.last_cycle >= self.max_cycles
|
|
1059
|
+
if must_resize:
|
|
1060
|
+
self.max_cycles += restart_info["max_cycles"]
|
|
1061
|
+
# Resize HDF5
|
|
1062
|
+
if self.dump:
|
|
1063
|
+
# h5_group = get_h5_group(self.h5_fn, self.h5_group_name)
|
|
1064
|
+
# resize_h5_group(h5_group, self.max_cycles)
|
|
1065
|
+
# h5_group.file.close()
|
|
1066
|
+
pass
|
|
1067
|
+
|
|
1068
|
+
self.coords = [np.array(coords) for coords in restart_info["coords"]]
|
|
1069
|
+
self.energies = restart_info["energies"]
|
|
1070
|
+
self.forces = [np.array(forces) for forces in restart_info["forces"]]
|
|
1071
|
+
self.steps = [np.array(step) for step in restart_info["steps"]]
|
|
1072
|
+
|
|
1073
|
+
# Set subclass specific information
|
|
1074
|
+
self._set_opt_restart_info(restart_info)
|
|
1075
|
+
|
|
1076
|
+
# Propagate restart information downwards to the geometry
|
|
1077
|
+
self.geometry.set_restart_info(restart_info["geom_info"])
|
|
1078
|
+
|
|
1079
|
+
def dump_restart_info(self):
|
|
1080
|
+
restart_info = self.get_restart_info()
|
|
1081
|
+
|
|
1082
|
+
restart_fn = f"restart_{self.cur_cycle:03d}.yaml"
|
|
1083
|
+
restart_yaml = yaml.dump(restart_info)
|
|
1084
|
+
self.write_to_out_dir(restart_fn, restart_yaml)
|