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,508 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.interpolate import splprep, splev
|
|
3
|
+
|
|
4
|
+
from pysisyphus.constants import AU2KCALPERMOL
|
|
5
|
+
from pysisyphus.intcoords.exceptions import (
|
|
6
|
+
DifferentCoordLengthsException,
|
|
7
|
+
DifferentPrimitivesException,
|
|
8
|
+
RebuiltInternalsException,
|
|
9
|
+
)
|
|
10
|
+
from pysisyphus.cos.ChainOfStates import ChainOfStates
|
|
11
|
+
from pysisyphus.cos.GrowingChainOfStates import GrowingChainOfStates
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [1] https://aip.scitation.org/doi/abs/10.1063/1.1691018
|
|
15
|
+
# Peters, 2004
|
|
16
|
+
# [2] https://aip.scitation.org/doi/abs/10.1063/1.4804162
|
|
17
|
+
# Zimmerman, 2013
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GrowingString(GrowingChainOfStates):
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
images,
|
|
24
|
+
calc_getter,
|
|
25
|
+
perp_thresh=0.05,
|
|
26
|
+
param="equi",
|
|
27
|
+
reparam_every=2,
|
|
28
|
+
reparam_every_full=3,
|
|
29
|
+
reparam_tol=None,
|
|
30
|
+
reparam_check="rms",
|
|
31
|
+
max_micro_cycles=5,
|
|
32
|
+
reset_dlc=True,
|
|
33
|
+
climb=False,
|
|
34
|
+
**kwargs,
|
|
35
|
+
):
|
|
36
|
+
assert len(images) >= 2, "Need at least 2 images for GrowingString."
|
|
37
|
+
if len(images) > 2:
|
|
38
|
+
images = [images[0], images[-1]]
|
|
39
|
+
print("More than 2 images given. Will only use first and last image!")
|
|
40
|
+
if climb:
|
|
41
|
+
climb = "one"
|
|
42
|
+
super().__init__(images, calc_getter, climb=climb, **kwargs)
|
|
43
|
+
|
|
44
|
+
self.perp_thresh = perp_thresh
|
|
45
|
+
self.param = param
|
|
46
|
+
self.reparam_every = int(reparam_every)
|
|
47
|
+
self.reparam_every_full = int(reparam_every_full)
|
|
48
|
+
assert (
|
|
49
|
+
self.reparam_every >= 1 and self.reparam_every_full >= 1
|
|
50
|
+
), "reparam_every and reparam_every_full must be positive integers!"
|
|
51
|
+
if reparam_tol is not None:
|
|
52
|
+
self.reparam_tol = float(reparam_tol)
|
|
53
|
+
assert self.reparam_tol > 0
|
|
54
|
+
else:
|
|
55
|
+
self.reparam_tol = 1 / (self.max_nodes + 2) / 2
|
|
56
|
+
self.log(f"Using reparametrization tolerance of {self.reparam_tol:.4f}")
|
|
57
|
+
self.reparam_check = reparam_check
|
|
58
|
+
assert self.reparam_check in ("norm", "rms")
|
|
59
|
+
self.max_micro_cycles = int(max_micro_cycles)
|
|
60
|
+
self.reset_dlc = bool(reset_dlc)
|
|
61
|
+
|
|
62
|
+
left_img, right_img = self.images
|
|
63
|
+
|
|
64
|
+
self.left_string = [
|
|
65
|
+
left_img,
|
|
66
|
+
]
|
|
67
|
+
self.right_string = [
|
|
68
|
+
right_img,
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
# The desired spacing of the nodes in the final string on the
|
|
72
|
+
# normalized arclength.
|
|
73
|
+
self.sk = 1 / (self.max_nodes + 1)
|
|
74
|
+
|
|
75
|
+
self.reparam_in = reparam_every
|
|
76
|
+
self._tangents = None
|
|
77
|
+
self.tangent_list = list()
|
|
78
|
+
self.perp_forces_list = list()
|
|
79
|
+
self.coords_list = list()
|
|
80
|
+
|
|
81
|
+
left_frontier = self.get_new_image(self.lf_ind)
|
|
82
|
+
self.left_string.append(left_frontier)
|
|
83
|
+
right_frontier = self.get_new_image(self.rf_ind)
|
|
84
|
+
self.right_string.append(right_frontier)
|
|
85
|
+
self.new_image_inds = list()
|
|
86
|
+
|
|
87
|
+
def get_cur_param_density(self, kind=None):
|
|
88
|
+
diffs = [
|
|
89
|
+
image - self.images[max(i - 1, 0)] for i, image in enumerate(self.images)
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
norms = np.linalg.norm(diffs, axis=1)
|
|
93
|
+
param_density = np.cumsum(norms)
|
|
94
|
+
self.log(f"Current string length={param_density[-1]:.6f}")
|
|
95
|
+
|
|
96
|
+
# Energy weighted parametrization density
|
|
97
|
+
if kind == "energy":
|
|
98
|
+
prev_energies = np.array(self.all_energies[-1])
|
|
99
|
+
|
|
100
|
+
if len(prev_energies) != len(self.images):
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
mean_energies = (prev_energies[1:] + prev_energies[:-1]) / 2
|
|
104
|
+
weights = mean_energies - prev_energies.min()
|
|
105
|
+
# This damps everything a bit.
|
|
106
|
+
weights = np.sqrt(weights)
|
|
107
|
+
param_density = [
|
|
108
|
+
0,
|
|
109
|
+
]
|
|
110
|
+
for weight, diff in zip(weights, norms[1:]):
|
|
111
|
+
assert weight > 0.0
|
|
112
|
+
param_density.append(param_density[-1] + weight * diff)
|
|
113
|
+
|
|
114
|
+
param_density = np.array(param_density)
|
|
115
|
+
param_density /= param_density[-1]
|
|
116
|
+
|
|
117
|
+
return param_density
|
|
118
|
+
|
|
119
|
+
def reset_geometries(self, ref_geometry):
|
|
120
|
+
ref_typed_prims = ref_geometry.internal.typed_prims
|
|
121
|
+
self.log(
|
|
122
|
+
f"Resetting image primitives. Got {len(ref_typed_prims)} typed primitives."
|
|
123
|
+
)
|
|
124
|
+
# Do multiple cycles as it may happen, that not all coordinates are valid
|
|
125
|
+
# at every node.
|
|
126
|
+
for i in range(3):
|
|
127
|
+
self.log(f"\tMicro cycle {i:d}")
|
|
128
|
+
intersect = set(self.images[0].internal.typed_prims)
|
|
129
|
+
for j, image in enumerate(self.images):
|
|
130
|
+
image.reset_coords(ref_typed_prims)
|
|
131
|
+
new_typed_prims = set(image.internal.typed_prims)
|
|
132
|
+
self.log(
|
|
133
|
+
f"\tImage {j:02d} now has {len(new_typed_prims)} typed primitives."
|
|
134
|
+
)
|
|
135
|
+
intersect = intersect & new_typed_prims
|
|
136
|
+
|
|
137
|
+
if intersect == set(ref_typed_prims):
|
|
138
|
+
ref_geometry.reset_coords(intersect)
|
|
139
|
+
break
|
|
140
|
+
ref_typed_prims = list(intersect)
|
|
141
|
+
else:
|
|
142
|
+
raise Exception("Too many reset cycles!")
|
|
143
|
+
|
|
144
|
+
def set_coords(self, image, coords):
|
|
145
|
+
try:
|
|
146
|
+
image.coords = coords
|
|
147
|
+
except RebuiltInternalsException:
|
|
148
|
+
print("Rebuilt internal coordinates!")
|
|
149
|
+
self.reset_geometries(image)
|
|
150
|
+
|
|
151
|
+
def get_new_image(self, ref_index):
|
|
152
|
+
"""Get new image by taking a step from self.images[ref_index] towards
|
|
153
|
+
the center of the string."""
|
|
154
|
+
new_img = self.images[ref_index].copy(
|
|
155
|
+
coord_kwargs={
|
|
156
|
+
"check_bends": True,
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if ref_index <= self.lf_ind:
|
|
161
|
+
tangent_ind = ref_index + 1
|
|
162
|
+
insert_ind = tangent_ind
|
|
163
|
+
else:
|
|
164
|
+
tangent_ind = ref_index - 1
|
|
165
|
+
insert_ind = ref_index
|
|
166
|
+
tangent_img = self.images[tangent_ind]
|
|
167
|
+
|
|
168
|
+
# (new_img - tangent_img) points from tangent_img towards new_img.
|
|
169
|
+
# As we want to derive a new image from new_img, we have to step
|
|
170
|
+
# against this vector, so we have to multiply by -1.
|
|
171
|
+
# Why don't we just use (tangent_img - new_img) to get the right
|
|
172
|
+
# direction? In DLC the resulting distance would then be given in
|
|
173
|
+
# the active set U of tangent_img, but we need it in the active set U
|
|
174
|
+
# of new_img.
|
|
175
|
+
# Formulated the other way around the same expression can be used for
|
|
176
|
+
# all coord types.
|
|
177
|
+
try:
|
|
178
|
+
distance = -(new_img - tangent_img)
|
|
179
|
+
except (DifferentCoordLengthsException, DifferentPrimitivesException):
|
|
180
|
+
self.reset_geometries(new_img)
|
|
181
|
+
distance = -(new_img - tangent_img)
|
|
182
|
+
|
|
183
|
+
# The desired step(_length) for the new image be can be easily determined
|
|
184
|
+
# from a simple rule of proportion by relating the actual distance between
|
|
185
|
+
# two images to their parametrization density difference on the normalized
|
|
186
|
+
# arclength and the desired spacing given by self.sk.
|
|
187
|
+
#
|
|
188
|
+
# Δparam_density / distance = self.sk / step
|
|
189
|
+
# step = self.sk / Δparam_density * distance
|
|
190
|
+
cpd = self.get_cur_param_density()
|
|
191
|
+
# As we always want to step in the direction of 'distance' we just take
|
|
192
|
+
# the absolute value of the difference, as we are not interested in the
|
|
193
|
+
# sign.
|
|
194
|
+
param_dens_diff = abs(cpd[ref_index] - cpd[tangent_ind])
|
|
195
|
+
step_length = self.sk / param_dens_diff
|
|
196
|
+
step = step_length * distance
|
|
197
|
+
|
|
198
|
+
new_coords = new_img.coords + step
|
|
199
|
+
self.set_coords(new_img, new_coords)
|
|
200
|
+
|
|
201
|
+
new_img.set_calculator(self.calc_getter())
|
|
202
|
+
ref_calc = self.images[ref_index].calculator
|
|
203
|
+
try:
|
|
204
|
+
chkfiles = ref_calc.get_chkfiles()
|
|
205
|
+
new_img.calculator.set_chkfiles(chkfiles)
|
|
206
|
+
self.log(
|
|
207
|
+
"Set checkfiles from calculator of node "
|
|
208
|
+
f"{ref_index:02d} on calculator of new node."
|
|
209
|
+
)
|
|
210
|
+
except AttributeError:
|
|
211
|
+
self.log("Calculator doesn't support 'get/set_chkfiles()'")
|
|
212
|
+
self.images.insert(insert_ind, new_img)
|
|
213
|
+
self.log(f"Created new image; inserted it before index {insert_ind}.")
|
|
214
|
+
return new_img
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def left_size(self):
|
|
218
|
+
return len(self.left_string)
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def right_size(self):
|
|
222
|
+
return len(self.right_string)
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def string_size(self):
|
|
226
|
+
return self.left_size + self.right_size
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def fully_grown(self):
|
|
230
|
+
"""Returns wether the string is fully grown. Don't count the first
|
|
231
|
+
and last node."""
|
|
232
|
+
return not ((self.string_size - 2) < self.max_nodes)
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def nodes_missing(self):
|
|
236
|
+
"""Returns the number of nodes to be grown."""
|
|
237
|
+
return (self.max_nodes + 2) - self.string_size
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def lf_ind(self):
|
|
241
|
+
"""Index of the left frontier node in self.images."""
|
|
242
|
+
return len(self.left_string) - 1
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def rf_ind(self):
|
|
246
|
+
"""Index of the right frontier node in self.images."""
|
|
247
|
+
return self.lf_ind + 1
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def full_string_image_inds(self):
|
|
251
|
+
left_inds = np.arange(self.left_size)
|
|
252
|
+
right_inds = np.arange(self.max_nodes + 2)[-self.right_size :]
|
|
253
|
+
image_inds = np.concatenate((left_inds, right_inds))
|
|
254
|
+
return image_inds
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def image_inds(self):
|
|
258
|
+
return self.full_string_image_inds
|
|
259
|
+
|
|
260
|
+
def spline(self, tangents=False):
|
|
261
|
+
if (not tangents) and (self.param == "energy") and self.fully_grown:
|
|
262
|
+
u = self.get_cur_param_density(kind="energy")
|
|
263
|
+
else:
|
|
264
|
+
u = self.get_cur_param_density()
|
|
265
|
+
reshaped = self.coords.reshape(-1, self.coords_length)
|
|
266
|
+
# To use splprep we have to transpose the coords.
|
|
267
|
+
transp_coords = reshaped.transpose()
|
|
268
|
+
# Spline in batches as scipy can't handle > 11 rows at once
|
|
269
|
+
tcks, us = zip(
|
|
270
|
+
*[
|
|
271
|
+
splprep(transp_coords[i : i + 9], s=0, k=3, u=u)
|
|
272
|
+
for i in range(0, len(transp_coords), 9)
|
|
273
|
+
]
|
|
274
|
+
)
|
|
275
|
+
return tcks, us
|
|
276
|
+
|
|
277
|
+
def reparam_cart(self, desired_param_density):
|
|
278
|
+
tcks, us = self.spline()
|
|
279
|
+
# Reparametrize mesh
|
|
280
|
+
new_points = np.vstack([splev(desired_param_density, tck) for tck in tcks])
|
|
281
|
+
# Flatten along first dimension.
|
|
282
|
+
new_points = new_points.reshape(-1, len(self.images)).T
|
|
283
|
+
# With a climbing image we ignore the just splined coordinates for the CI
|
|
284
|
+
# and restore its original coordinates.
|
|
285
|
+
for index in self.get_climbing_indices():
|
|
286
|
+
new_points[index] = self.images[index].coords
|
|
287
|
+
self.log(f"Skipped reparametrization of climbing image with index {index}")
|
|
288
|
+
self.coords = new_points.flatten()
|
|
289
|
+
# In contrast to self.reparam_dlc() we don't check if the reparametrization
|
|
290
|
+
# succeeded because it can't fail ;)
|
|
291
|
+
|
|
292
|
+
def reparam_dlc(self, desired_param_density, thresh=1e-3):
|
|
293
|
+
climbing_indices = self.get_climbing_indices()
|
|
294
|
+
# Reparametrization will take place along the tangent between two
|
|
295
|
+
# images. The index of the tangent image depends on wether the image
|
|
296
|
+
# is above or below the desired param_density on the normalized arc.
|
|
297
|
+
#
|
|
298
|
+
# The reparametrization is done in micro cycles, until it is converged.
|
|
299
|
+
cur_param_density = self.get_cur_param_density()
|
|
300
|
+
self.log(f"Density before reparametrization: {cur_param_density}")
|
|
301
|
+
for i, reparam_image in enumerate(self.images[1:-1], 1):
|
|
302
|
+
if i in climbing_indices:
|
|
303
|
+
self.log(f"Skipped reparametrization of climbing image with index {i}")
|
|
304
|
+
continue
|
|
305
|
+
self.log(f"Reparametrizing node {i}")
|
|
306
|
+
for j in range(self.max_micro_cycles):
|
|
307
|
+
diff = (desired_param_density - cur_param_density)[i]
|
|
308
|
+
self.log(f"\t{j}: Δ={diff: .6f}")
|
|
309
|
+
# Do at least one pass
|
|
310
|
+
if (j > 0) and (abs(diff) < thresh):
|
|
311
|
+
break
|
|
312
|
+
# Negative sign: image is too far right and has to be shifted left.
|
|
313
|
+
# Positive sign: image is too far left and has to be shifted right.
|
|
314
|
+
sign = int(np.sign(diff))
|
|
315
|
+
# Index of the tangent image. reparam_image will be shifted along
|
|
316
|
+
# this direction to achieve the desired parametirzation density.
|
|
317
|
+
tangent_ind = i + sign
|
|
318
|
+
tangent_image = self.images[tangent_ind]
|
|
319
|
+
rl = "right" if sign > 0 else "left"
|
|
320
|
+
self.log(f"\t... shifting {rl} towards image {tangent_ind}")
|
|
321
|
+
distance = -(reparam_image - tangent_image)
|
|
322
|
+
|
|
323
|
+
param_dens_diff = abs(
|
|
324
|
+
cur_param_density[tangent_ind] - cur_param_density[i]
|
|
325
|
+
)
|
|
326
|
+
step_length = abs(diff) / param_dens_diff
|
|
327
|
+
step = step_length * distance
|
|
328
|
+
reparam_coords = reparam_image.coords + step
|
|
329
|
+
self.set_coords(reparam_image, reparam_coords)
|
|
330
|
+
cur_param_density = self.get_cur_param_density()
|
|
331
|
+
else:
|
|
332
|
+
self.log(
|
|
333
|
+
f"Reparametrization of node {i} did not converge after "
|
|
334
|
+
f"{self.max_micro_cycles} cycles. Breaking!"
|
|
335
|
+
)
|
|
336
|
+
break
|
|
337
|
+
|
|
338
|
+
cpd_str = np.array2string(cur_param_density, precision=4)
|
|
339
|
+
self.log(f"Param density after reparametrization: {cpd_str}")
|
|
340
|
+
|
|
341
|
+
# This check is disabled at it is not really applicable. While we reparametrize
|
|
342
|
+
# the images the string size may vary wildly, at least in the beginning. Lets
|
|
343
|
+
# say after reparametrization the distance vector between image 0 and 1 is of
|
|
344
|
+
# magnitude 1 and the overall string length is 10. Then image 1 is at 0.1 w.r.t.
|
|
345
|
+
# the parametrization density. If we reparametrize the remaining images the over-
|
|
346
|
+
# all string size may be 8, and now image 1 suddenly sits at 1/8 = 0.125, which
|
|
347
|
+
# may be already above the allowed threshold.
|
|
348
|
+
# Over time the string size will equilibrate and the desired parametrization
|
|
349
|
+
# density will actually be realized.
|
|
350
|
+
# try:
|
|
351
|
+
# # Dont check climbing images
|
|
352
|
+
# np.testing.assert_allclose(
|
|
353
|
+
# np.delete(cur_param_density, climbing_indices),
|
|
354
|
+
# np.delete(desired_param_density, climbing_indices),
|
|
355
|
+
# atol=self.reparam_tol
|
|
356
|
+
# )
|
|
357
|
+
# except AssertionError as err:
|
|
358
|
+
# trj_str = self.as_xyz()
|
|
359
|
+
# fn = "failed_reparametrization_trj.xyz"
|
|
360
|
+
# with open(fn, "w") as handle:
|
|
361
|
+
# handle.write(trj_str)
|
|
362
|
+
# print(f"Wrote coordinates of failed reparametrization to '{fn}'")
|
|
363
|
+
# raise err
|
|
364
|
+
|
|
365
|
+
# Regenerate active set after reparametrization
|
|
366
|
+
if self.reset_dlc and not self.fully_grown:
|
|
367
|
+
[image.internal.set_active_set() for image in self.moving_images]
|
|
368
|
+
self.log(f"Created new DLCs for {len(self.images)} string images.")
|
|
369
|
+
elif self.reset_dlc:
|
|
370
|
+
self.log("Skipping creation of new DLCs, as string is already fully grown.")
|
|
371
|
+
|
|
372
|
+
def get_tangent(self, i):
|
|
373
|
+
# Simple tangent, pointing at each other, for the frontier images.
|
|
374
|
+
if not self.fully_grown and i in (self.lf_ind, self.rf_ind):
|
|
375
|
+
next_ind = i + 1 if (i <= self.lf_ind) else i - 1
|
|
376
|
+
tangent = self.images[next_ind] - self.images[i]
|
|
377
|
+
tangent /= np.linalg.norm(tangent)
|
|
378
|
+
else:
|
|
379
|
+
tangent = super().get_tangent(i, kind="upwinding")
|
|
380
|
+
|
|
381
|
+
return tangent
|
|
382
|
+
|
|
383
|
+
@ChainOfStates.forces.getter
|
|
384
|
+
def forces(self):
|
|
385
|
+
if self._forces is None:
|
|
386
|
+
self.calculate_forces()
|
|
387
|
+
indices = range(len(self.images))
|
|
388
|
+
# In constrast to NEB calculations we only use the perpendicular component
|
|
389
|
+
# of the force, without any spring forces. A desired image distribution is
|
|
390
|
+
# achieved via periodic reparametrization.
|
|
391
|
+
perp_forces = np.array([self.get_perpendicular_forces(i) for i in indices])
|
|
392
|
+
self.perp_forces_list.append(perp_forces.copy().flatten())
|
|
393
|
+
# Add climbing forces
|
|
394
|
+
total_forces = self.set_climbing_forces(perp_forces)
|
|
395
|
+
self._forces = total_forces.flatten()
|
|
396
|
+
return self._forces
|
|
397
|
+
|
|
398
|
+
def reparametrize(self):
|
|
399
|
+
reparametrized = False
|
|
400
|
+
# If this counter reaches 0 reparametrization will occur.
|
|
401
|
+
self.reparam_in -= 1
|
|
402
|
+
|
|
403
|
+
self.new_image_inds = list()
|
|
404
|
+
# Check if new images can be added for incomplete strings.
|
|
405
|
+
if not self.fully_grown:
|
|
406
|
+
perp_forces = self.perp_forces_list[-1].reshape(len(self.images), -1)
|
|
407
|
+
# Calculate norm and rms of the perpendicular force for every
|
|
408
|
+
# node/image on the string.
|
|
409
|
+
to_check = {
|
|
410
|
+
"norm": np.linalg.norm(perp_forces, axis=1),
|
|
411
|
+
"rms": np.sqrt(np.mean(perp_forces**2, axis=1)),
|
|
412
|
+
}
|
|
413
|
+
self.log(
|
|
414
|
+
f"Checking frontier node convergence, threshold={self.perp_thresh:.6f}"
|
|
415
|
+
)
|
|
416
|
+
# We can add a new node if the norm/rms of the perpendicular force is below
|
|
417
|
+
# the threshold.
|
|
418
|
+
def converged(i):
|
|
419
|
+
cur_val = to_check[self.reparam_check][i]
|
|
420
|
+
is_converged = cur_val <= self.perp_thresh
|
|
421
|
+
conv_str = ", converged" if is_converged else ""
|
|
422
|
+
self.log(
|
|
423
|
+
f"\tnode {i:02d}: {self.reparam_check}(perp_forces)={cur_val:.6f}"
|
|
424
|
+
f"{conv_str}"
|
|
425
|
+
)
|
|
426
|
+
return is_converged
|
|
427
|
+
|
|
428
|
+
# New images are added with the same coordinates as the frontier image.
|
|
429
|
+
# We force reparametrization by setting self.reparam_in to 0 to get sane
|
|
430
|
+
# coordinates for the new image(s).
|
|
431
|
+
if converged(self.lf_ind):
|
|
432
|
+
# Insert at the end of the left string, just before the
|
|
433
|
+
# right frontier node.
|
|
434
|
+
new_left_frontier = self.get_new_image(self.lf_ind)
|
|
435
|
+
self.new_image_inds.append(self.left_size)
|
|
436
|
+
self.left_string.append(new_left_frontier)
|
|
437
|
+
self.log("Added new left frontier node.")
|
|
438
|
+
self.reparam_in = 0
|
|
439
|
+
# If an image was just grown in the left substring the string may now
|
|
440
|
+
# be fully grown, so we reavluate 'self.fully_grown' here.
|
|
441
|
+
if (not self.fully_grown) and converged(self.rf_ind):
|
|
442
|
+
# Insert at the end of the right string, just before the
|
|
443
|
+
# current right frontier node.
|
|
444
|
+
new_right_frontier = self.get_new_image(self.rf_ind)
|
|
445
|
+
self.new_image_inds.append(self.left_size)
|
|
446
|
+
self.right_string.append(new_right_frontier)
|
|
447
|
+
self.log("Added new right frontier node.")
|
|
448
|
+
self.reparam_in = 0
|
|
449
|
+
self.log(f"New image indices: {self.new_image_inds}")
|
|
450
|
+
|
|
451
|
+
self.log(
|
|
452
|
+
f"Current string size is {self.left_size}+{self.right_size}="
|
|
453
|
+
f"{self.string_size}. There are still {self.nodes_missing} "
|
|
454
|
+
"nodes to be grown."
|
|
455
|
+
if not self.fully_grown
|
|
456
|
+
else "String is fully grown."
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
if self.reparam_in > 0:
|
|
460
|
+
self.log(
|
|
461
|
+
"Skipping reparametrization. Next reparametrization in "
|
|
462
|
+
f"{self.reparam_in} cycles."
|
|
463
|
+
)
|
|
464
|
+
else:
|
|
465
|
+
# Prepare image reparametrization
|
|
466
|
+
desired_param_density = self.sk * self.full_string_image_inds
|
|
467
|
+
pd_str = np.array2string(desired_param_density, precision=4)
|
|
468
|
+
self.log(f"Desired param density: {pd_str}")
|
|
469
|
+
|
|
470
|
+
# Reparametrize images.
|
|
471
|
+
if self.coord_type == "cart":
|
|
472
|
+
self.reparam_cart(desired_param_density)
|
|
473
|
+
elif self.coord_type == "dlc":
|
|
474
|
+
self.reparam_dlc(desired_param_density, thresh=self.reparam_tol)
|
|
475
|
+
else:
|
|
476
|
+
raise Exception("How did you get here?")
|
|
477
|
+
|
|
478
|
+
self.reparam_in = (
|
|
479
|
+
self.reparam_every_full if self.fully_grown else self.reparam_every
|
|
480
|
+
)
|
|
481
|
+
reparametrized = True
|
|
482
|
+
# Writing is deactivated, as this does not respect an out_dir or
|
|
483
|
+
# something similar.
|
|
484
|
+
# with open("reparametrized_trj.xyz", "w") as handle:
|
|
485
|
+
# handle.write(self.as_xyz())
|
|
486
|
+
|
|
487
|
+
return reparametrized
|
|
488
|
+
|
|
489
|
+
def get_additional_print(self):
|
|
490
|
+
size_str = f"{self.left_size}+{self.right_size}"
|
|
491
|
+
if self.fully_grown:
|
|
492
|
+
size_str = " Full"
|
|
493
|
+
size_info = f"String={size_str: >5s}"
|
|
494
|
+
energies = np.array(self.all_energies[-1])
|
|
495
|
+
barrier = (energies.max() - energies[0]) * AU2KCALPERMOL
|
|
496
|
+
barrier_info = f"(E_hei-E_0)={barrier:6.1f} kcal/mol"
|
|
497
|
+
hei_ind = energies.argmax()
|
|
498
|
+
hei_norm = np.linalg.norm(self.all_true_forces[-1][hei_ind])
|
|
499
|
+
hei_info = f"norm(forces_true,hei)={hei_norm:.6f} E_h/a_0"
|
|
500
|
+
hei_str = f"HEI={hei_ind+1:02d}/{energies.size:02d}"
|
|
501
|
+
|
|
502
|
+
strs = (
|
|
503
|
+
size_info,
|
|
504
|
+
hei_str,
|
|
505
|
+
barrier_info,
|
|
506
|
+
hei_info,
|
|
507
|
+
)
|
|
508
|
+
return "\t" + " ".join(strs)
|
pysisyphus/cos/NEB.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from pysisyphus.cos.ChainOfStates import ChainOfStates
|
|
6
|
+
from pysisyphus.cos.stiffness import get_stiff_stress
|
|
7
|
+
|
|
8
|
+
# [1] http://aip.scitation.org/doi/pdf/10.1063/1.1323224
|
|
9
|
+
# 10.1063/1.1323224
|
|
10
|
+
# [2] http://onlinelibrary.wiley.com/doi/10.1002/jcc.20780/pdf
|
|
11
|
+
# 10.1002/jcc.20780
|
|
12
|
+
# [3] https://aip.scitation.org/doi/10.1063/1.2841941
|
|
13
|
+
# Sheppard, 2008
|
|
14
|
+
# [4] https://aip.scitation.org/doi/pdf/10.1063/1.1636455
|
|
15
|
+
# Trygubenko, 2004
|
|
16
|
+
# [5] Nudged Elastic Band Method for Finding Minimum Energy Paths of Transitions
|
|
17
|
+
# Hannes Jónsson , Greg Mills , Karsten W. Jacobsen
|
|
18
|
+
# https://github.com/cstein/neb/blob/master/neb/neb.py
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NEB(ChainOfStates):
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
images,
|
|
25
|
+
variable_springs=False,
|
|
26
|
+
k_max=0.3,
|
|
27
|
+
k_min=0.1,
|
|
28
|
+
perp_spring_forces=None,
|
|
29
|
+
bandwidth: Optional[float] = None,
|
|
30
|
+
**kwargs,
|
|
31
|
+
):
|
|
32
|
+
super(NEB, self).__init__(images, **kwargs)
|
|
33
|
+
|
|
34
|
+
assert k_max >= k_min, "k_max must be bigger or equal to k_min!"
|
|
35
|
+
self.variable_springs = variable_springs
|
|
36
|
+
self.k_max = k_max
|
|
37
|
+
self.k_min = k_min
|
|
38
|
+
self.perp_spring_forces = perp_spring_forces
|
|
39
|
+
self.bandwidth = bandwidth
|
|
40
|
+
|
|
41
|
+
self.delta_k = self.k_max - self.k_min
|
|
42
|
+
self.k = list()
|
|
43
|
+
|
|
44
|
+
def update_springs(self):
|
|
45
|
+
# Check if there are enough springs
|
|
46
|
+
if len(self.k) != len(self.images) - 1:
|
|
47
|
+
self.k = np.full(len(self.images) - 1, self.k_min)
|
|
48
|
+
if self.variable_springs:
|
|
49
|
+
self.set_variable_springs()
|
|
50
|
+
|
|
51
|
+
def set_variable_springs(self):
|
|
52
|
+
shifted_energies = self.energy - self.energy.min()
|
|
53
|
+
energy_max = max(shifted_energies)
|
|
54
|
+
energy_ref = 0.85 * energy_max
|
|
55
|
+
for i in range(len(self.k)):
|
|
56
|
+
# The ith spring connects images i-1 and i.
|
|
57
|
+
e_i = i + 1
|
|
58
|
+
ith_energy = max(shifted_energies[e_i], shifted_energies[e_i - 1])
|
|
59
|
+
if ith_energy < energy_ref:
|
|
60
|
+
self.k[i] = self.k_min
|
|
61
|
+
else:
|
|
62
|
+
self.k[i] = self.k_max - self.delta_k * (energy_max - ith_energy) / (
|
|
63
|
+
energy_max - energy_ref
|
|
64
|
+
)
|
|
65
|
+
self.log("updated springs: " + self.fmt_k())
|
|
66
|
+
|
|
67
|
+
def fmt_k(self):
|
|
68
|
+
return ", ".join([str(f"{k:.03f}") for k in self.k])
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def parallel_forces(self):
|
|
72
|
+
indices = range(len(self.images))
|
|
73
|
+
par_forces = [self.get_parallel_forces(i) for i in indices]
|
|
74
|
+
return np.array(par_forces).flatten()
|
|
75
|
+
|
|
76
|
+
def get_spring_forces(self, i):
|
|
77
|
+
if i not in self.moving_indices:
|
|
78
|
+
return self.zero_vec.copy()
|
|
79
|
+
|
|
80
|
+
if (i == 0) or (i == len(self.images) - 1):
|
|
81
|
+
# We can't use the last image index because there is one
|
|
82
|
+
# spring less than there are images.
|
|
83
|
+
spring_index = min(i, len(self.images) - 2)
|
|
84
|
+
return self.k[spring_index] * self.get_tangent(i)
|
|
85
|
+
|
|
86
|
+
prev_coords = self.images[i - 1].coords
|
|
87
|
+
ith_coords = self.images[i].coords
|
|
88
|
+
next_coords = self.images[i + 1].coords
|
|
89
|
+
spring_forces = self.k[i] * (next_coords - ith_coords) - (
|
|
90
|
+
ith_coords - prev_coords
|
|
91
|
+
)
|
|
92
|
+
return spring_forces
|
|
93
|
+
|
|
94
|
+
def get_quenched_dneb_forces(self, i):
|
|
95
|
+
"""See [3], Sec. VI and [4] Sec. D."""
|
|
96
|
+
if not self.perp_spring_forces or (i not in self.moving_indices):
|
|
97
|
+
return self.zero_vec.copy()
|
|
98
|
+
forces = self.images[i].forces
|
|
99
|
+
tangent = self.get_tangent(i)
|
|
100
|
+
perp_forces = forces - forces.dot(tangent) * tangent
|
|
101
|
+
spring_forces = self.get_spring_forces(i)
|
|
102
|
+
tangent = self.get_tangent(i)
|
|
103
|
+
perp_spring_forces = spring_forces - spring_forces.dot(tangent) * tangent
|
|
104
|
+
dneb_forces = (
|
|
105
|
+
perp_spring_forces - perp_spring_forces.dot(perp_forces) * perp_forces
|
|
106
|
+
)
|
|
107
|
+
perp_norm = np.linalg.norm(perp_forces)
|
|
108
|
+
perp_spring_norm = np.linalg.norm(perp_spring_forces)
|
|
109
|
+
|
|
110
|
+
# Switching function to quench the dneb forces
|
|
111
|
+
# Eq. (15) in [3]
|
|
112
|
+
#
|
|
113
|
+
# If norm(perp_force) >> norm(perp_spring_forces): dneb_factor ~ 1
|
|
114
|
+
# If norm(perp_force) << norm(perp_spring_forces): dneb_factor ~ 0
|
|
115
|
+
#
|
|
116
|
+
# If the perpendicular spring force is much bigger than the
|
|
117
|
+
# perpendicular force the DNEB forces is nearly fully quenched.
|
|
118
|
+
dneb_factor = 2 / np.pi * np.arctan2(perp_norm**2, perp_spring_norm**2)
|
|
119
|
+
dneb_forces_quenched = dneb_factor * dneb_forces
|
|
120
|
+
|
|
121
|
+
# An alternative switchting function is given in [5], Eq. (10)
|
|
122
|
+
# f(phi) = 1/2 * (1 + cos(pi*cos(theta)))
|
|
123
|
+
# f -> 0 for a straight path (theta -> 0°)
|
|
124
|
+
# f -> 1 for a perpendicular path (theta -> 90°)
|
|
125
|
+
# cos(theta) = (R_(i+1) - R_i) * (R_i - R_(i-1)) / (norm of numerator)
|
|
126
|
+
|
|
127
|
+
return dneb_forces_quenched
|
|
128
|
+
|
|
129
|
+
def get_parallel_forces(self, i):
|
|
130
|
+
if i not in self.moving_indices:
|
|
131
|
+
return self.zero_vec.copy()
|
|
132
|
+
|
|
133
|
+
if (i == 0) or (i == len(self.images) - 1):
|
|
134
|
+
# We can't use the last image index because there is one
|
|
135
|
+
# spring less than there are images.
|
|
136
|
+
spring_index = min(i, len(self.images) - 2)
|
|
137
|
+
return self.k[spring_index] * self.get_tangent(i)
|
|
138
|
+
|
|
139
|
+
prev_coords = self.images[i - 1].coords
|
|
140
|
+
ith_coords = self.images[i].coords
|
|
141
|
+
next_coords = self.images[i + 1].coords
|
|
142
|
+
return (
|
|
143
|
+
self.k[i]
|
|
144
|
+
* (
|
|
145
|
+
np.linalg.norm(next_coords - ith_coords)
|
|
146
|
+
- np.linalg.norm(ith_coords - prev_coords)
|
|
147
|
+
)
|
|
148
|
+
* self.get_tangent(i)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# See https://stackoverflow.com/a/15786149
|
|
152
|
+
# This way we can reuse the parents setter.
|
|
153
|
+
@ChainOfStates.forces.getter
|
|
154
|
+
def forces(self):
|
|
155
|
+
if self._forces is not None:
|
|
156
|
+
return self._forces
|
|
157
|
+
|
|
158
|
+
org_results = self.calculate_forces()
|
|
159
|
+
self.update_springs()
|
|
160
|
+
indices = range(len(self.images))
|
|
161
|
+
total_forces = np.array(
|
|
162
|
+
[
|
|
163
|
+
self.get_parallel_forces(i)
|
|
164
|
+
+ self.get_perpendicular_forces(i)
|
|
165
|
+
+ self.get_quenched_dneb_forces(i)
|
|
166
|
+
for i in indices
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
total_forces = self.set_climbing_forces(total_forces)
|
|
170
|
+
if self.bandwidth is not None:
|
|
171
|
+
# kappa = self.k
|
|
172
|
+
# breakpoint()
|
|
173
|
+
stiff_stress = get_stiff_stress(
|
|
174
|
+
bandwidth=self.bandwidth,
|
|
175
|
+
kappa=self.k,
|
|
176
|
+
image_coords=self.image_coords,
|
|
177
|
+
tangents=self.get_tangents(),
|
|
178
|
+
)
|
|
179
|
+
total_forces = total_forces + stiff_stress
|
|
180
|
+
total_forces[self.org_forces_indices] = org_results["forces"][
|
|
181
|
+
self.org_forces_indices
|
|
182
|
+
]
|
|
183
|
+
if self.org_forces_indices:
|
|
184
|
+
self.log(
|
|
185
|
+
f"Returning unrpojected original forces for image(s): {self.org_forces_indices}."
|
|
186
|
+
)
|
|
187
|
+
self._forces = total_forces.flatten()
|
|
188
|
+
|
|
189
|
+
return self._forces
|