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
pysisyphus/irc/DWI.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# [1] https://aip.scitation.org/doi/10.1063/1.1724823
|
|
2
|
+
# Hratchian, 2004
|
|
3
|
+
# [2] https://aip.scitation.org/doi/pdf/10.1063/1.475419?class=pdf
|
|
4
|
+
# Thompson, 1998
|
|
5
|
+
|
|
6
|
+
from collections import deque
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
|
|
12
|
+
def taylor(energy, gradient, hessian, step):
|
|
13
|
+
"""Taylor series expansion of the energy to second order."""
|
|
14
|
+
if isinstance(hessian, torch.Tensor):
|
|
15
|
+
step_t = torch.as_tensor(step, dtype=hessian.dtype, device=hessian.device)
|
|
16
|
+
return energy + step @ gradient + 0.5 * (step_t @ hessian @ step_t).item()
|
|
17
|
+
else:
|
|
18
|
+
return energy + step @ gradient + 0.5 * step @ hessian @ step
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def taylor_grad(gradient, hessian, step):
|
|
22
|
+
"""Gradient of a Taylor series expansion of the energy to second order."""
|
|
23
|
+
if isinstance(hessian, torch.Tensor):
|
|
24
|
+
step_t = torch.as_tensor(step, dtype=hessian.dtype, device=hessian.device)
|
|
25
|
+
return gradient + (hessian @ step_t).cpu().numpy()
|
|
26
|
+
else:
|
|
27
|
+
return gradient + hessian @ step
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DWI:
|
|
31
|
+
|
|
32
|
+
def __init__(self, n=4, maxlen=2):
|
|
33
|
+
"""Distance weighted interpolation."""
|
|
34
|
+
|
|
35
|
+
self.n = int(n)
|
|
36
|
+
assert self.n > 0
|
|
37
|
+
assert (self.n % 2) == 0
|
|
38
|
+
self.maxlen = maxlen
|
|
39
|
+
assert self.maxlen == 2, \
|
|
40
|
+
"Right now only maxlen=2 is supported!"
|
|
41
|
+
|
|
42
|
+
# Using FIFO deques for easy updating of the lists
|
|
43
|
+
self.coords = deque(maxlen=self.maxlen)
|
|
44
|
+
self.energies = deque(maxlen=self.maxlen)
|
|
45
|
+
self.gradients = deque(maxlen=self.maxlen)
|
|
46
|
+
self.hessians = deque(maxlen=self.maxlen)
|
|
47
|
+
|
|
48
|
+
def update(self, coords, energy, gradient, hessian):
|
|
49
|
+
self.coords.append(coords)
|
|
50
|
+
self.energies.append(energy)
|
|
51
|
+
self.gradients.append(gradient)
|
|
52
|
+
self.hessians.append(hessian)
|
|
53
|
+
|
|
54
|
+
assert len(self.coords) == len(self.energies) \
|
|
55
|
+
== len(self.gradients) == len(self.hessians)
|
|
56
|
+
|
|
57
|
+
def interpolate(self, at_coords, gradient=False):
|
|
58
|
+
"""See [1] Eq. (25) - (29)"""
|
|
59
|
+
c1, c2 = self.coords
|
|
60
|
+
|
|
61
|
+
dx1 = at_coords - c1
|
|
62
|
+
dx2 = at_coords - c2
|
|
63
|
+
|
|
64
|
+
dx1_norm = np.linalg.norm(dx1)
|
|
65
|
+
dx2_norm = np.linalg.norm(dx2)
|
|
66
|
+
dx1_norm_n = dx1_norm**self.n
|
|
67
|
+
dx2_norm_n = dx2_norm**self.n
|
|
68
|
+
|
|
69
|
+
denom = dx1_norm**self.n + dx2_norm**self.n
|
|
70
|
+
w1 = dx2_norm_n / denom
|
|
71
|
+
w2 = dx1_norm_n / denom
|
|
72
|
+
|
|
73
|
+
e1, e2 = self.energies
|
|
74
|
+
g1, g2 = self.gradients
|
|
75
|
+
h1, h2 = self.hessians
|
|
76
|
+
|
|
77
|
+
t1 = taylor(e1, g1, h1, dx1)
|
|
78
|
+
t2 = taylor(e2, g2, h2, dx2)
|
|
79
|
+
|
|
80
|
+
E_dwi = w1*t1 + w2*t2
|
|
81
|
+
|
|
82
|
+
if not gradient:
|
|
83
|
+
return E_dwi
|
|
84
|
+
|
|
85
|
+
t1_grad = taylor_grad(g1, h1, dx1)
|
|
86
|
+
t2_grad = taylor_grad(g2, h2, dx2)
|
|
87
|
+
|
|
88
|
+
# The gradient of dx2_norm_n w.r.t the coordinates is formulated with
|
|
89
|
+
# **2n instead of **n, so the square root can be easily reduced.
|
|
90
|
+
# sqrt(x)**2n = x**(1/2)**2n = x**n
|
|
91
|
+
#
|
|
92
|
+
# Thats why we do the following calculations with n/2.
|
|
93
|
+
# of n.
|
|
94
|
+
n_2 = self.n // 2
|
|
95
|
+
dx1_norm_n_grad = 2 * n_2 * dx1_norm**(2*n_2-2) * dx1
|
|
96
|
+
dx2_norm_n_grad = 2 * n_2 * dx2_norm**(2*n_2-2) * dx2
|
|
97
|
+
w1_grad = (dx2_norm_n_grad*dx1_norm_n - dx1_norm_n_grad*dx2_norm_n) / denom**2
|
|
98
|
+
w2_grad = -w1_grad
|
|
99
|
+
|
|
100
|
+
# E_dwi = w1(x)*T1(x) + w2(x)*T2(x)
|
|
101
|
+
#
|
|
102
|
+
# dE_DWI / dx = dw1(x)*T1(x) + w1(x)*dT1(x) + dw2(x)*T2(x) + w2*dT2(x)
|
|
103
|
+
grad_dwi = w1_grad*t1 + w1*t1_grad + w2_grad*t2 + w2*t2_grad
|
|
104
|
+
|
|
105
|
+
return E_dwi, grad_dwi
|
|
106
|
+
|
|
107
|
+
def __repr__(self):
|
|
108
|
+
return f"DWI(n={self.n}, maxlen={self.maxlen})"
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.constants import BOHR2M, AU2J, AMU2KG
|
|
4
|
+
from pysisyphus.irc.IRC import IRC
|
|
5
|
+
from pysisyphus.TableFormatter import TableFormatter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# [1] https://pubs.acs.org/doi/10.1021/jp012125b
|
|
9
|
+
# Following Reaction Pathways Using a Damped Classical Trajectory Algorithm
|
|
10
|
+
# Hratchian, Schlegel, 2002
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DampedVelocityVerlet(IRC):
|
|
14
|
+
|
|
15
|
+
def __init__(self, geometry, v0=0.04, dt0=0.5, error_tol=0.003,
|
|
16
|
+
max_cycles=150, **kwargs):
|
|
17
|
+
super().__init__(geometry, max_cycles=max_cycles, **kwargs)
|
|
18
|
+
|
|
19
|
+
self.v0 = v0
|
|
20
|
+
self.error_tol = error_tol
|
|
21
|
+
self.dt0 = dt0
|
|
22
|
+
|
|
23
|
+
step_header = "damping dt dt_new error".split()
|
|
24
|
+
step_fmts = [".2f", ".4f", ".4f", ".3E"]
|
|
25
|
+
self.step_formatter = TableFormatter(step_header, step_fmts, 10)
|
|
26
|
+
|
|
27
|
+
def mw_grad_to_acc(self, mw_grad):
|
|
28
|
+
"""Takes care of the units for the mass-weighted gradient.
|
|
29
|
+
Converts units of a mass-weighted gradient [Hartree/(Bohr*amu)]
|
|
30
|
+
to units of acceleration [sqrt(amu)*Bohr/fs²]. The 1e30 comes
|
|
31
|
+
from converting second² to femto second²."""
|
|
32
|
+
return mw_grad * AU2J / AMU2KG / BOHR2M**2 / 1e30
|
|
33
|
+
|
|
34
|
+
def prepare(self, direction):
|
|
35
|
+
# In contrast to the paper we don't start from the TS geometry but
|
|
36
|
+
# instead do an initial displacement along the imaginary mode as for
|
|
37
|
+
# the other IRC classes. So there should always be a non-vanishing
|
|
38
|
+
# gradient after the prepare call.
|
|
39
|
+
super().prepare(direction)
|
|
40
|
+
|
|
41
|
+
acceleration = -self.mw_grad_to_acc(self.mw_gradient)
|
|
42
|
+
# init_velocity = acceleration
|
|
43
|
+
init_velocity, _ = self.damp_velocity(acceleration)
|
|
44
|
+
|
|
45
|
+
self.velocities = [init_velocity]
|
|
46
|
+
self.accelerations = [acceleration]
|
|
47
|
+
self.time_steps = [self.dt0]
|
|
48
|
+
|
|
49
|
+
def damp_velocity(self, velocity):
|
|
50
|
+
# Eq. (4) in [1]
|
|
51
|
+
damping_factor = self.v0 / np.linalg.norm(velocity)
|
|
52
|
+
self.log(f"Damping factor={damping_factor:.6f}")
|
|
53
|
+
damped_velocity = velocity * damping_factor
|
|
54
|
+
return damped_velocity, damping_factor
|
|
55
|
+
|
|
56
|
+
def estimate_error(self, new_mw_coords):
|
|
57
|
+
self.log("Error estimation")
|
|
58
|
+
# See Fig. 1 and Eq. (5) in [1]
|
|
59
|
+
cur_time_step = self.time_steps[-1]
|
|
60
|
+
prev_time_step = self.time_steps[-2]
|
|
61
|
+
time_step_sum = prev_time_step + cur_time_step
|
|
62
|
+
self.log(f"\tSum of cur. and prev. timestep: {time_step_sum:.6f} fs")
|
|
63
|
+
# Calculation of x' coordinates
|
|
64
|
+
# irc_mw_coords
|
|
65
|
+
# -1: current coords <- x_i-1 in the paper
|
|
66
|
+
# -2: previous coords <- x_i-2 in the paper
|
|
67
|
+
# velocities/accelerations
|
|
68
|
+
# -1: new velo./acc. for next time step
|
|
69
|
+
# -2: velo./acc. that yielded the proposed coords x_i
|
|
70
|
+
# -3: velo./acc. that yielded previous structure <- v_i-2 and a_i-2 in the paper
|
|
71
|
+
ref_coords = (
|
|
72
|
+
self.irc_mw_coords[-2]
|
|
73
|
+
# TODO: This should probably be -3 but it seems to give inferior results
|
|
74
|
+
+ self.velocities[-2]*time_step_sum
|
|
75
|
+
+ 0.5*self.accelerations[-2]*time_step_sum**2
|
|
76
|
+
)
|
|
77
|
+
diff = new_mw_coords - ref_coords
|
|
78
|
+
diff /= np.sqrt(self.geometry.masses_rep)
|
|
79
|
+
largest_component = np.max(np.abs(diff))
|
|
80
|
+
norm = np.linalg.norm(diff)
|
|
81
|
+
# Take either the largest component of the difference vector
|
|
82
|
+
# or the norm of the difference vector.
|
|
83
|
+
self.log("\tdiff=Proposed coords - Reference coords")
|
|
84
|
+
self.log(f"\tmax(|diff|)={largest_component:.6f}")
|
|
85
|
+
self.log(f"\ttnorm(diff)={norm:.6f}")
|
|
86
|
+
# estimated_error = max(largest_component, norm)
|
|
87
|
+
estimated_error = max(largest_component, norm)
|
|
88
|
+
self.log(f"\testimated error={estimated_error:.6f}")
|
|
89
|
+
return estimated_error
|
|
90
|
+
|
|
91
|
+
def step(self):
|
|
92
|
+
prev_acceleration = self.accelerations[-1]
|
|
93
|
+
prev_velocity = self.velocities[-1]
|
|
94
|
+
time_step = self.time_steps[-1]
|
|
95
|
+
|
|
96
|
+
# Get new acceleration
|
|
97
|
+
acceleration = -self.mw_grad_to_acc(self.mw_gradient)
|
|
98
|
+
|
|
99
|
+
acc_normed = acceleration/np.linalg.norm(acceleration)
|
|
100
|
+
prev_vel_normed = prev_velocity / np.linalg.norm(prev_velocity)
|
|
101
|
+
ovlp = acc_normed @ prev_vel_normed
|
|
102
|
+
self.log(f"a @ v_i-1={ovlp:.8f}")
|
|
103
|
+
self.accelerations.append(acceleration)
|
|
104
|
+
|
|
105
|
+
# Calculate new coords and velocity
|
|
106
|
+
# Eq. (2) in [1]
|
|
107
|
+
new_mw_coords = (self.mw_coords
|
|
108
|
+
+ prev_velocity*time_step
|
|
109
|
+
+ 0.5*prev_acceleration*time_step**2
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Update velocity
|
|
113
|
+
velocity = prev_velocity + 0.5*(prev_acceleration+acceleration)*time_step
|
|
114
|
+
# Damp velocity
|
|
115
|
+
damped_velocity, damping_factor = self.damp_velocity(velocity)
|
|
116
|
+
self.velocities.append(damped_velocity)
|
|
117
|
+
|
|
118
|
+
if self.cur_cycle == 0:
|
|
119
|
+
# No error estimation in the first step
|
|
120
|
+
estimated_error = self.error_tol
|
|
121
|
+
else:
|
|
122
|
+
estimated_error = self.estimate_error(new_mw_coords)
|
|
123
|
+
|
|
124
|
+
# Get next time step from error estimation
|
|
125
|
+
new_time_step = time_step * (self.error_tol / estimated_error)**(1/3)
|
|
126
|
+
# Constrain time step between 0.0025 fs and 3.0 fs
|
|
127
|
+
new_time_step = min(new_time_step, 3.)
|
|
128
|
+
new_time_step = max(new_time_step, 0.025)
|
|
129
|
+
self.time_steps.append(new_time_step)
|
|
130
|
+
self.log(f"\tCur. time step={time_step:.6f} fs")
|
|
131
|
+
self.log(f"\tNext time step={new_time_step:.6f} fs")
|
|
132
|
+
|
|
133
|
+
# Set new coordinates
|
|
134
|
+
self.mw_coords = new_mw_coords
|
pysisyphus/irc/Euler.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.irc.IRC import IRC
|
|
4
|
+
from pysisyphus.TableFormatter import TableFormatter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Euler(IRC):
|
|
8
|
+
|
|
9
|
+
def __init__(self, geometry, step_length=0.01, **kwargs):
|
|
10
|
+
super(Euler, self).__init__(geometry, step_length, **kwargs)
|
|
11
|
+
|
|
12
|
+
step_header = "E/au ΔE(TS)/au |gradient|".split()
|
|
13
|
+
step_fmts = [".6f", ".6f", ".4f"]
|
|
14
|
+
self.step_formatter = TableFormatter(step_header, step_fmts, 10)
|
|
15
|
+
|
|
16
|
+
def step(self):
|
|
17
|
+
grad = self.mw_gradient
|
|
18
|
+
grad_norm = np.linalg.norm(grad)
|
|
19
|
+
|
|
20
|
+
# Step downhill, against the gradient
|
|
21
|
+
step_direction = -grad / grad_norm
|
|
22
|
+
self.mw_coords += self.step_length*step_direction
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# [1 ] https://aip.scitation.org/doi/pdf/10.1063/1.3514202?class=pdf
|
|
2
|
+
# Original EulerPC
|
|
3
|
+
# Hratchian, Schlegel, 2010
|
|
4
|
+
# [2 ] https://aip.scitation.org/doi/pdf/10.1063/1.1724823?class=pdf
|
|
5
|
+
# Original HPC
|
|
6
|
+
# Hratchian, Schlegel, 2004
|
|
7
|
+
# [3 ] https://pubs.rsc.org/en/content/articlepdf/2017/cp/c7cp03722h
|
|
8
|
+
# EulerPC re-implementation
|
|
9
|
+
# Meisner, Kästner, 2017
|
|
10
|
+
# [3.1] http://www.rsc.org/suppdata/c7/cp/c7cp03722h/c7cp03722h1.pdf
|
|
11
|
+
# Corresponding SI
|
|
12
|
+
# [4 ] https://aip.scitation.org/doi/pdf/10.1063/1.3593456?class=pdf
|
|
13
|
+
# Hratchian, Frisch, 2011
|
|
14
|
+
# [6 ] https://aip.scitation.org/doi/10.1063/1.3593456<Paste>
|
|
15
|
+
# Hratchian, Frisch
|
|
16
|
+
# Further improvements for DWI; not implemented
|
|
17
|
+
|
|
18
|
+
import time
|
|
19
|
+
from collections import deque
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from pysisyphus.helpers_pure import rms
|
|
24
|
+
from pysisyphus.irc.DWI import DWI
|
|
25
|
+
from pysisyphus.irc.IRC import IRC
|
|
26
|
+
from pysisyphus.optimizers.hessian_updates import bfgs_update, bofill_update
|
|
27
|
+
|
|
28
|
+
import torch
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class EulerPC(IRC):
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
*args,
|
|
35
|
+
hessian_recalc=None,
|
|
36
|
+
hessian_update="bofill",
|
|
37
|
+
hessian_init="calc",
|
|
38
|
+
max_pred_steps=500,
|
|
39
|
+
loose_cycles=3,
|
|
40
|
+
corr_func="mbs",
|
|
41
|
+
**kwargs,
|
|
42
|
+
):
|
|
43
|
+
super().__init__(*args, hessian_init=hessian_init, **kwargs)
|
|
44
|
+
|
|
45
|
+
self.hessian_recalc = hessian_recalc
|
|
46
|
+
self.hessian_update = {
|
|
47
|
+
"bfgs": bfgs_update,
|
|
48
|
+
"bofill": bofill_update,
|
|
49
|
+
}
|
|
50
|
+
self.hessian_update_func = self.hessian_update[hessian_update]
|
|
51
|
+
self.max_pred_steps = int(max_pred_steps)
|
|
52
|
+
self.loose_cycles = loose_cycles
|
|
53
|
+
|
|
54
|
+
corr_funcs = {
|
|
55
|
+
"mbs": self.corrector_step,
|
|
56
|
+
}
|
|
57
|
+
self.corr_func = corr_funcs[corr_func]
|
|
58
|
+
|
|
59
|
+
self._to_active_vec = lambda v: v[self._act_dofs]
|
|
60
|
+
|
|
61
|
+
def _to_full_vec(v_act, template):
|
|
62
|
+
if isinstance(v_act, torch.Tensor):
|
|
63
|
+
full = template.clone()
|
|
64
|
+
else:
|
|
65
|
+
full = template.copy()
|
|
66
|
+
full[self._act_dofs] = v_act
|
|
67
|
+
return full
|
|
68
|
+
|
|
69
|
+
self._to_full_vec = _to_full_vec
|
|
70
|
+
|
|
71
|
+
def prepare(self, *args, **kwargs):
|
|
72
|
+
super().prepare(*args, **kwargs)
|
|
73
|
+
|
|
74
|
+
# Initialize the distance weighted interpolator with the data
|
|
75
|
+
# from the initial displacement.
|
|
76
|
+
self.dwi = DWI()
|
|
77
|
+
mw_grad_full = self.mw_gradient
|
|
78
|
+
energy = self.energy
|
|
79
|
+
|
|
80
|
+
mw_grad_act = self._to_active_vec(mw_grad_full)
|
|
81
|
+
mw_coords_act = self._to_active_vec(self.mw_coords)
|
|
82
|
+
|
|
83
|
+
if isinstance(self.mw_hessian, torch.Tensor):
|
|
84
|
+
self.dwi.update(mw_coords_act, energy, mw_grad_act, self.mw_hessian.detach().clone())
|
|
85
|
+
else:
|
|
86
|
+
self.dwi.update(mw_coords_act, energy, mw_grad_act, self.mw_hessian.copy())
|
|
87
|
+
|
|
88
|
+
if self.downhill:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Do a first Hessian update with the information between the TS
|
|
92
|
+
# and the initially displaced geometry.
|
|
93
|
+
dx = self._to_active_vec(self.mw_coords - self.ts_mw_coords)
|
|
94
|
+
dg = mw_grad_act - self._to_active_vec(self.ts_mw_gradient)
|
|
95
|
+
dH, key = self.hessian_update_func(self.mw_hessian, dx, dg)
|
|
96
|
+
self.log(f"Did {key} hessian update.")
|
|
97
|
+
self.mw_hessian += dH
|
|
98
|
+
|
|
99
|
+
def get_integration_length_func(self, init_mw_coords):
|
|
100
|
+
"""
|
|
101
|
+
Return a closure that computes the un-mass-weighted path length Δs.
|
|
102
|
+
"""
|
|
103
|
+
if init_mw_coords.shape[0] == self._m_sqrt.shape[0]:
|
|
104
|
+
m_sqrt_vec = self._m_sqrt # full (3N)
|
|
105
|
+
else:
|
|
106
|
+
m_sqrt_vec = self._m_sqrt[self._act_dofs] # active-only
|
|
107
|
+
|
|
108
|
+
def get_integration_length(cur_mw_coords):
|
|
109
|
+
return np.linalg.norm((cur_mw_coords - init_mw_coords) / m_sqrt_vec)
|
|
110
|
+
|
|
111
|
+
return get_integration_length
|
|
112
|
+
|
|
113
|
+
def step(self):
|
|
114
|
+
##################
|
|
115
|
+
# PREDICTOR STEP #
|
|
116
|
+
##################
|
|
117
|
+
|
|
118
|
+
mw_grad_full = self.mw_gradient # 3N-vector, mass-weighted
|
|
119
|
+
energy = self.energy
|
|
120
|
+
mw_grad_act = self._to_active_vec(mw_grad_full) # active slice
|
|
121
|
+
|
|
122
|
+
if self.cur_cycle > 0:
|
|
123
|
+
if self.hessian_recalc and (self.cur_cycle % self.hessian_recalc == 0):
|
|
124
|
+
self.mw_hessian = self.geometry.mw_hessian
|
|
125
|
+
self.geometry.clear()
|
|
126
|
+
self.log("Calculated excact hessian")
|
|
127
|
+
else:
|
|
128
|
+
dx = self._to_active_vec(self.mw_coords - self.irc_mw_coords[-2])
|
|
129
|
+
dg = mw_grad_act - self._to_active_vec(self.irc_mw_gradients[-2])
|
|
130
|
+
dH, key = self.hessian_update_func(self.mw_hessian, dx, dg)
|
|
131
|
+
if isinstance(dH, torch.Tensor):
|
|
132
|
+
self.mw_hessian.add_(dH)
|
|
133
|
+
else:
|
|
134
|
+
self.mw_hessian += dH
|
|
135
|
+
self.log(f"Did {key} hessian update before predictor step.")
|
|
136
|
+
if isinstance(self.mw_hessian, torch.Tensor):
|
|
137
|
+
self.dwi.update(
|
|
138
|
+
self._to_active_vec(self.mw_coords).copy(),
|
|
139
|
+
energy, mw_grad_act,
|
|
140
|
+
self.mw_hessian.detach().clone()
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
self.dwi.update(
|
|
144
|
+
self._to_active_vec(self.mw_coords).copy(),
|
|
145
|
+
energy, mw_grad_act,
|
|
146
|
+
self.mw_hessian.copy()
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Create a copy of the inital coordinates for the determination
|
|
150
|
+
# of the actual step size in the predictor Euler integration.
|
|
151
|
+
init_mw_coords = self.mw_coords.copy()
|
|
152
|
+
|
|
153
|
+
get_integration_length = self.get_integration_length_func(init_mw_coords)
|
|
154
|
+
|
|
155
|
+
# Calculate predictor Euler-integration step length. See get_conv_fact
|
|
156
|
+
# method definition for a comment on this.
|
|
157
|
+
conv_fact = self.get_conv_fact(mw_grad_act)
|
|
158
|
+
euler_step_length = self.step_length / (self.max_pred_steps / conv_fact)
|
|
159
|
+
|
|
160
|
+
def taylor_gradient(step_full):
|
|
161
|
+
"""Return full-length gradient via 2nd-order Taylor (active Hessian)."""
|
|
162
|
+
step_act = self._to_active_vec(step_full)
|
|
163
|
+
if isinstance(self.mw_hessian, torch.Tensor):
|
|
164
|
+
step_t = torch.tensor(step_act, dtype=self.mw_hessian.dtype,
|
|
165
|
+
device=self.mw_hessian.device)
|
|
166
|
+
hvp_act = (self.mw_hessian @ step_t).cpu().numpy()
|
|
167
|
+
else:
|
|
168
|
+
hvp_act = self.mw_hessian @ step_act
|
|
169
|
+
grad_act = mw_grad_act + hvp_act
|
|
170
|
+
return self._to_full_vec(grad_act, step_full)
|
|
171
|
+
|
|
172
|
+
# These variables will hold the coordinates and gradients along
|
|
173
|
+
# the Euler integration and will be updated frequently.
|
|
174
|
+
euler_mw_coords = self.mw_coords.copy()
|
|
175
|
+
euler_mw_grad = mw_grad_full.copy()
|
|
176
|
+
self.log(
|
|
177
|
+
f"Predictor-Euler-integration with Δs={euler_step_length:.6f} "
|
|
178
|
+
f"for up to {self.max_pred_steps} steps\n # |step| d|step|"
|
|
179
|
+
)
|
|
180
|
+
prev_cur_length = 0.0
|
|
181
|
+
for i in range(self.max_pred_steps):
|
|
182
|
+
# Calculate step length in non-mass-weighted coordinates
|
|
183
|
+
cur_length = get_integration_length(euler_mw_coords)
|
|
184
|
+
if i % 50 == 0:
|
|
185
|
+
diff = cur_length - prev_cur_length
|
|
186
|
+
self.log(f"\t{i:03d}: {cur_length:.4f} Δ={diff:.4f}")
|
|
187
|
+
prev_cur_length = cur_length
|
|
188
|
+
|
|
189
|
+
# Check if we achieved the desired step length.
|
|
190
|
+
if cur_length >= self.step_length:
|
|
191
|
+
self.log(
|
|
192
|
+
"Predictor-Euler integration converged with "
|
|
193
|
+
f"Δs={cur_length:.4f} (desired Δs={self.step_length:.4f}) "
|
|
194
|
+
f"after {i+1} steps!"
|
|
195
|
+
)
|
|
196
|
+
break
|
|
197
|
+
grad_norm = np.linalg.norm(euler_mw_grad)
|
|
198
|
+
if not np.isfinite(grad_norm) or grad_norm == 0.0:
|
|
199
|
+
self.log("Gradient norm is zero/NaN; using transition vector direction.")
|
|
200
|
+
direction = getattr(self, "mw_transition_vector", euler_mw_grad)
|
|
201
|
+
dir_norm = np.linalg.norm(direction)
|
|
202
|
+
if not np.isfinite(dir_norm) or dir_norm == 0.0:
|
|
203
|
+
raise ValueError("Cannot determine IRC step direction (zero/NaN norms).")
|
|
204
|
+
step_ = euler_step_length * -direction / dir_norm
|
|
205
|
+
else:
|
|
206
|
+
step_ = euler_step_length * -euler_mw_grad / grad_norm
|
|
207
|
+
euler_mw_coords += step_
|
|
208
|
+
# Determine actual step by comparing the current and the initial coordinates
|
|
209
|
+
euler_step = euler_mw_coords - init_mw_coords
|
|
210
|
+
euler_mw_grad = taylor_gradient(euler_step)
|
|
211
|
+
else:
|
|
212
|
+
self.log(
|
|
213
|
+
f"Predictor-Euler integration did not converge in {i+1} "
|
|
214
|
+
f"steps. Δs={cur_length:.4f}."
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Check if we are already sufficiently converged. If so signal
|
|
218
|
+
# convergence.
|
|
219
|
+
self.mw_coords = euler_mw_coords
|
|
220
|
+
|
|
221
|
+
# Use rms of gradient from taylor expansion for convergence check.
|
|
222
|
+
euler_grad = self.unweight_vec(euler_mw_grad)
|
|
223
|
+
rms_grad = rms(euler_grad)
|
|
224
|
+
|
|
225
|
+
# Or check true gradient? But this would need an additional calculation,
|
|
226
|
+
# so I disabled it for now.
|
|
227
|
+
# rms_grad = rms(self.gradient)
|
|
228
|
+
|
|
229
|
+
if self.cur_cycle < self.loose_cycles:
|
|
230
|
+
self.log(
|
|
231
|
+
f"Current cycle {self.cur_cycle} is still in 'loose' mode.\n"
|
|
232
|
+
"Continuing IRC integration even though predictor integration "
|
|
233
|
+
f"did not succeed.\n{self.loose_cycles - self.cur_cycle - 1} loose "
|
|
234
|
+
"cycles remaining."
|
|
235
|
+
)
|
|
236
|
+
# elif rms_grad <= 5*self.rms_grad_thresh:
|
|
237
|
+
elif rms_grad <= self.rms_grad_thresh:
|
|
238
|
+
self.log("Sufficient convergence achieved on rms(grad)")
|
|
239
|
+
self.converged = True
|
|
240
|
+
return
|
|
241
|
+
self.log("")
|
|
242
|
+
|
|
243
|
+
# Calculate energy and gradient at new predicted geometry. Update the
|
|
244
|
+
# hessian accordingly. These results will be added to the DWI for use
|
|
245
|
+
# in the corrector step.
|
|
246
|
+
self.mw_coords = euler_mw_coords
|
|
247
|
+
self.log("Calculating energy and gradient at predictor step geometry.")
|
|
248
|
+
mw_grad_full = self.mw_gradient
|
|
249
|
+
energy = self.energy
|
|
250
|
+
mw_grad_act = self._to_active_vec(mw_grad_full)
|
|
251
|
+
|
|
252
|
+
# Hessian update
|
|
253
|
+
dx = self._to_active_vec(self.mw_coords - self.irc_mw_coords[-1])
|
|
254
|
+
dg = mw_grad_act - self._to_active_vec(self.irc_mw_gradients[-1])
|
|
255
|
+
dH, key = self.hessian_update_func(self.mw_hessian, dx, dg)
|
|
256
|
+
self.mw_hessian += dH
|
|
257
|
+
self.log(f"Did {key} hessian update after predictor step.\n")
|
|
258
|
+
if isinstance(self.mw_hessian, torch.Tensor):
|
|
259
|
+
self.dwi.update(
|
|
260
|
+
self._to_active_vec(self.mw_coords).copy(),
|
|
261
|
+
energy, mw_grad_act,
|
|
262
|
+
self.mw_hessian.detach().clone()
|
|
263
|
+
)
|
|
264
|
+
else:
|
|
265
|
+
self.dwi.update(
|
|
266
|
+
self._to_active_vec(self.mw_coords).copy(),
|
|
267
|
+
energy, mw_grad_act,
|
|
268
|
+
self.mw_hessian.copy()
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
corrected_mw_coords_act = self.corr_func(self._to_active_vec(init_mw_coords), self.step_length, self.dwi)
|
|
272
|
+
self.mw_coords = self._to_full_vec(corrected_mw_coords_act, self.mw_coords)
|
|
273
|
+
corr_step_length = get_integration_length(self.mw_coords)
|
|
274
|
+
self.log(f"Corrected unweighted step length: {corr_step_length:.6f}")
|
|
275
|
+
|
|
276
|
+
def corrector_step(self, init_mw_coords, step_length, dwi):
|
|
277
|
+
self.log("Corrector step using mBS integration (active dofs)")
|
|
278
|
+
|
|
279
|
+
get_integration_length = self.get_integration_length_func(init_mw_coords)
|
|
280
|
+
|
|
281
|
+
errors = list()
|
|
282
|
+
richardson = dict()
|
|
283
|
+
for k in range(15):
|
|
284
|
+
points = 20 * (2**k)
|
|
285
|
+
corr_step_length = step_length / (points - 1)
|
|
286
|
+
cur_coords = init_mw_coords.copy()
|
|
287
|
+
# Only keep the last 2 coords (needed for oscillation check)
|
|
288
|
+
k_coords = deque(maxlen=2)
|
|
289
|
+
cur_length = 0
|
|
290
|
+
|
|
291
|
+
# Integrate until the desired spacing is reached
|
|
292
|
+
while True:
|
|
293
|
+
k_coords.append(cur_coords.copy())
|
|
294
|
+
if abs(step_length - cur_length) < 0.5 * corr_step_length:
|
|
295
|
+
self.log(
|
|
296
|
+
f"\tk={k:02d} points={points: >4d} "
|
|
297
|
+
f"step_length={corr_step_length:.4f} Δs={cur_length:.4f}"
|
|
298
|
+
)
|
|
299
|
+
break
|
|
300
|
+
|
|
301
|
+
energy, gradient = dwi.interpolate(cur_coords, gradient=True)
|
|
302
|
+
cur_coords += corr_step_length * -gradient / np.linalg.norm(gradient)
|
|
303
|
+
# cur_length += corr_step_length
|
|
304
|
+
cur_length = get_integration_length(cur_coords)
|
|
305
|
+
|
|
306
|
+
# Check for oscillation
|
|
307
|
+
if len(k_coords) >= 2:
|
|
308
|
+
prev_coords = k_coords[-2]
|
|
309
|
+
osc_norm = np.linalg.norm(cur_coords - prev_coords)
|
|
310
|
+
# TODO: Handle this by restarting everything with a smaller stepsize?
|
|
311
|
+
# Check 10.1039/c7cp03722h SI
|
|
312
|
+
if osc_norm <= corr_step_length:
|
|
313
|
+
self.log(
|
|
314
|
+
"\tDetected oscillation in Corrector-Euler "
|
|
315
|
+
f"integration for k={k:02d} and {points} points.\n"
|
|
316
|
+
"\tAborting corrector integration!"
|
|
317
|
+
)
|
|
318
|
+
return prev_coords
|
|
319
|
+
richardson[(k, 0)] = cur_coords
|
|
320
|
+
|
|
321
|
+
# Refine using Richardson extrapolation
|
|
322
|
+
# Set additional values using Richard extrapolation
|
|
323
|
+
for j in range(1, k + 1):
|
|
324
|
+
richardson[(k, j)] = (
|
|
325
|
+
(2**j) * richardson[(k, j - 1)] - richardson[(k - 1, j - 1)]
|
|
326
|
+
) / (2**j - 1)
|
|
327
|
+
# Can only be done after the second successful integration
|
|
328
|
+
if k > 0:
|
|
329
|
+
# Error estimate according to Numerical Recipes Eq. (17.3.9).
|
|
330
|
+
# We compare the last two entries/columns in the current row.
|
|
331
|
+
# RMS error
|
|
332
|
+
error = np.sqrt(
|
|
333
|
+
np.mean((richardson[(k, k)] - richardson[(k, k - 1)]) ** 2)
|
|
334
|
+
)
|
|
335
|
+
errors.append(error)
|
|
336
|
+
if error <= 1e-5:
|
|
337
|
+
self.log(f"mBS integration converged (error={error:.4e})!")
|
|
338
|
+
break
|
|
339
|
+
else:
|
|
340
|
+
raise Exception("Richardson did not converge!")
|
|
341
|
+
|
|
342
|
+
self.log(
|
|
343
|
+
f"Returning corrected mass-weighted coordinates from richardson[({k},{k})]"
|
|
344
|
+
)
|
|
345
|
+
return richardson[(k, k)]
|