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,725 @@
|
|
|
1
|
+
from copy import copy
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from distributed import Client
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy.interpolate import interp1d, splprep, splev
|
|
8
|
+
|
|
9
|
+
from pysisyphus.helpers import align_coords, get_coords_diffs
|
|
10
|
+
from pysisyphus.helpers_pure import hash_arr
|
|
11
|
+
from pysisyphus.modefollow import geom_lanczos
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [1] http://dx.doi.org/10.1063/1.1323224
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ChainOfStates:
|
|
18
|
+
logger = logging.getLogger("cos")
|
|
19
|
+
valid_coord_types = ("cart", "cartesian", "dlc")
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
images,
|
|
24
|
+
fix_first=True,
|
|
25
|
+
fix_last=True,
|
|
26
|
+
align_fixed=True,
|
|
27
|
+
climb=False,
|
|
28
|
+
climb_rms=5e-3,
|
|
29
|
+
climb_lanczos=False,
|
|
30
|
+
climb_lanczos_rms=5e-3,
|
|
31
|
+
climb_fixed=True,
|
|
32
|
+
energy_min_mix=False,
|
|
33
|
+
scheduler=None,
|
|
34
|
+
progress=False,
|
|
35
|
+
):
|
|
36
|
+
assert len(images) >= 2, "Need at least 2 images!"
|
|
37
|
+
self.images = list(images)
|
|
38
|
+
self.fix_first = fix_first
|
|
39
|
+
self.fix_last = fix_last
|
|
40
|
+
self.align_fixed = align_fixed
|
|
41
|
+
self.climb = climb
|
|
42
|
+
self.climb_rms = climb_rms
|
|
43
|
+
self.climb_lanczos = climb_lanczos
|
|
44
|
+
self.climb_fixed = climb_fixed
|
|
45
|
+
self.energy_min_mix = energy_min_mix
|
|
46
|
+
# Must not be lower than climb_rms
|
|
47
|
+
self.climb_lanczos_rms = min(self.climb_rms, climb_lanczos_rms)
|
|
48
|
+
self.scheduler = scheduler
|
|
49
|
+
self.progress = progress
|
|
50
|
+
|
|
51
|
+
self._coords = None
|
|
52
|
+
self._forces = None
|
|
53
|
+
self._energy = None
|
|
54
|
+
self.counter = 0
|
|
55
|
+
self.coords_length = self.images[0].coords.size
|
|
56
|
+
self.cart_coords_length = self.images[0].cart_coords.size
|
|
57
|
+
self.zero_vec = np.zeros(self.coords_length)
|
|
58
|
+
|
|
59
|
+
self.coords_list = list()
|
|
60
|
+
self.forces_list = list()
|
|
61
|
+
self.all_energies = list()
|
|
62
|
+
self.all_true_forces = list()
|
|
63
|
+
self.lanczos_tangents = dict()
|
|
64
|
+
self.prev_lanczos_hash = None
|
|
65
|
+
|
|
66
|
+
# Start climbing immediateley with climb_rms == -1
|
|
67
|
+
self.started_climbing = self.climb_rms == -1
|
|
68
|
+
if self.started_climbing:
|
|
69
|
+
self.log("Will start climbing immediately.")
|
|
70
|
+
self.started_climbing_lanczos = False
|
|
71
|
+
self.fixed_climb_indices = None
|
|
72
|
+
# Use original forces for these images
|
|
73
|
+
self.org_forces_indices = list()
|
|
74
|
+
|
|
75
|
+
img0 = self.images[0]
|
|
76
|
+
self.image_atoms = copy(img0.atoms)
|
|
77
|
+
self.coord_type = img0.coord_type
|
|
78
|
+
assert (
|
|
79
|
+
self.coord_type in self.valid_coord_types
|
|
80
|
+
), f"Invalid coord_type! Supported types are: {self.valid_coord_types}"
|
|
81
|
+
assert all(
|
|
82
|
+
[img.coord_type == self.coord_type for img in self.images]
|
|
83
|
+
), "coord_type of images differ!"
|
|
84
|
+
try:
|
|
85
|
+
self.typed_prims = img0.internal.typed_prims
|
|
86
|
+
except AttributeError:
|
|
87
|
+
self.typed_prims = None
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def calculator(self):
|
|
91
|
+
try:
|
|
92
|
+
calc = self.images[0].calculator
|
|
93
|
+
except IndexError:
|
|
94
|
+
calc = None
|
|
95
|
+
return calc
|
|
96
|
+
|
|
97
|
+
def log(self, message):
|
|
98
|
+
self.logger.debug(f"Counter {self.counter+1:03d}, {message}")
|
|
99
|
+
|
|
100
|
+
def get_fixed_indices(self):
|
|
101
|
+
fixed = list()
|
|
102
|
+
if self.fix_first:
|
|
103
|
+
fixed.append(0)
|
|
104
|
+
if self.fix_last:
|
|
105
|
+
fixed.append(len(self.images) - 1)
|
|
106
|
+
return fixed
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def moving_indices(self):
|
|
110
|
+
"""Returns the indices of the images that aren't fixed and can be
|
|
111
|
+
optimized."""
|
|
112
|
+
fixed = self.get_fixed_indices()
|
|
113
|
+
return [i for i in range(len(self.images)) if i not in fixed]
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def last_index(self):
|
|
117
|
+
return len(self.images) - 1
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def moving_images(self):
|
|
121
|
+
return [self.images[i] for i in self.moving_indices]
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def max_image_num(self):
|
|
125
|
+
return len(self.images)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def image_inds(self):
|
|
129
|
+
return list(range(self.max_image_num))
|
|
130
|
+
|
|
131
|
+
def zero_fixed_vector(self, vector):
|
|
132
|
+
fixed = self.get_fixed_indices()
|
|
133
|
+
for i in fixed:
|
|
134
|
+
vector[i] = self.zero_vec
|
|
135
|
+
return vector
|
|
136
|
+
|
|
137
|
+
def clear(self):
|
|
138
|
+
self._energy = None
|
|
139
|
+
self._forces = None
|
|
140
|
+
self._hessian = None
|
|
141
|
+
try:
|
|
142
|
+
self._tangents = None
|
|
143
|
+
except AttributeError:
|
|
144
|
+
# TODO: move this to another logging level?!
|
|
145
|
+
self.log("There are no tangents to reset.")
|
|
146
|
+
|
|
147
|
+
# @property
|
|
148
|
+
# def freeze_atoms(self):
|
|
149
|
+
# image_freeze_atoms = [image.freeze_atoms for image in self.images]
|
|
150
|
+
# lens = [len(fa) for fa in image_freeze_atoms]
|
|
151
|
+
# len0 = lens[0]
|
|
152
|
+
# assert all([len_ == len0 for len_ in lens])
|
|
153
|
+
# return image_freeze_atoms[0]
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def atoms(self):
|
|
157
|
+
atoms_ = self.images[0].atoms
|
|
158
|
+
return len(self.images) * atoms_
|
|
159
|
+
|
|
160
|
+
def set_vector(self, name, vector, clear=False):
|
|
161
|
+
vec_per_image = vector.reshape(-1, self.coords_length)
|
|
162
|
+
assert len(self.images) == len(vec_per_image)
|
|
163
|
+
for i in self.moving_indices:
|
|
164
|
+
setattr(self.images[i], name, vec_per_image[i])
|
|
165
|
+
if clear:
|
|
166
|
+
self.clear()
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def coords(self):
|
|
170
|
+
"""Return a flat 1d array containing the coordinates of all images."""
|
|
171
|
+
all_coords = [image.coords for image in self.images]
|
|
172
|
+
# Note: why does this getter set self._coords? ... I wrote this line 6 years ago.
|
|
173
|
+
self._coords = np.concatenate(all_coords)
|
|
174
|
+
return self._coords
|
|
175
|
+
|
|
176
|
+
@coords.setter
|
|
177
|
+
def coords(self, coords):
|
|
178
|
+
"""Distribute the flat 1d coords array over all images."""
|
|
179
|
+
self.set_vector("coords", coords, clear=True)
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def cart_coords(self):
|
|
183
|
+
"""Return a flat 1d array containing the cartesian coordinates of all
|
|
184
|
+
images."""
|
|
185
|
+
return np.concatenate([image.cart_coords for image in self.images])
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def coords3d(self):
|
|
189
|
+
assert self.images[0].coord_type == "cart"
|
|
190
|
+
return self.coords.reshape(-1, 3)
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def image_coords(self):
|
|
194
|
+
return np.array([image.coords for image in self.images])
|
|
195
|
+
|
|
196
|
+
def set_coords_at(self, i, coords):
|
|
197
|
+
"""Called from helpers.procrustes with cartesian coordinates.
|
|
198
|
+
Then tries to set cartesian coordinate as self.images[i].coords
|
|
199
|
+
which will raise an error when coord_type != "cart".
|
|
200
|
+
"""
|
|
201
|
+
assert self.images[i].coord_type in ("cart", "cartesian"), (
|
|
202
|
+
"ChainOfStates.set_coords_at() has to be reworked to support "
|
|
203
|
+
"internal coordiantes. Try to set 'align: False' in the 'opt' "
|
|
204
|
+
"section of the .yaml input file."
|
|
205
|
+
)
|
|
206
|
+
if i in self.moving_indices:
|
|
207
|
+
self.images[i].coords = coords
|
|
208
|
+
# When dealing with a fixed image don't set coords through the
|
|
209
|
+
# property, which would result in resetting the image's calculated
|
|
210
|
+
# data. Instead, assign coords directly. This only occurs when
|
|
211
|
+
# aligning the fixed images.
|
|
212
|
+
elif self.align_fixed:
|
|
213
|
+
self.images[i]._coords = coords
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def energy(self):
|
|
217
|
+
self._energy = np.array([image.energy for image in self.images])
|
|
218
|
+
return self._energy
|
|
219
|
+
|
|
220
|
+
@energy.setter
|
|
221
|
+
def energy(self, energies):
|
|
222
|
+
"""This is needed for some optimizers like CG and BFGS."""
|
|
223
|
+
assert len(self.images) == len(energies)
|
|
224
|
+
for i in self.moving_indices:
|
|
225
|
+
self.images[i].energy = energies[i]
|
|
226
|
+
|
|
227
|
+
self._energy = energies
|
|
228
|
+
|
|
229
|
+
def par_image_calc(self, image):
|
|
230
|
+
image.calc_energy_and_forces()
|
|
231
|
+
return image
|
|
232
|
+
|
|
233
|
+
def set_images(self, indices, images):
|
|
234
|
+
for ind, image in zip(indices, images):
|
|
235
|
+
self.images[ind] = image
|
|
236
|
+
|
|
237
|
+
def concurrent_force_calcs(self, images_to_calculate, image_indices):
|
|
238
|
+
client = self.get_dask_client()
|
|
239
|
+
self.log(client)
|
|
240
|
+
|
|
241
|
+
# save original pals to restore them later
|
|
242
|
+
orig_pal = images_to_calculate[0].calculator.pal
|
|
243
|
+
|
|
244
|
+
# divide pal of each image by the number of workers available or available images
|
|
245
|
+
# number of workers available
|
|
246
|
+
n_workers = len(client.scheduler_info()["workers"])
|
|
247
|
+
# number of images to calculate
|
|
248
|
+
n_images = len(images_to_calculate)
|
|
249
|
+
# divide pal by the number of workers (or 1 if more workers)
|
|
250
|
+
new_pal = max(1, orig_pal // n_workers)
|
|
251
|
+
|
|
252
|
+
# split images to calculate into batches of n_workers and set pal of each image
|
|
253
|
+
n_batches = n_images // n_workers
|
|
254
|
+
for i in range(0, n_batches):
|
|
255
|
+
for j in range(i * n_workers, (i + 1) * n_workers):
|
|
256
|
+
images_to_calculate[j].calculator.pal = new_pal
|
|
257
|
+
|
|
258
|
+
# distribute the pals among the remaining images
|
|
259
|
+
n_last_batch = n_images % n_workers
|
|
260
|
+
if n_last_batch > 0:
|
|
261
|
+
# divide pal by the remainder
|
|
262
|
+
new_pal = max(1, orig_pal // n_last_batch)
|
|
263
|
+
for i in range(n_batches * n_workers, n_images):
|
|
264
|
+
images_to_calculate[i].calculator.pal = new_pal
|
|
265
|
+
|
|
266
|
+
# map images to workers
|
|
267
|
+
image_futures = client.map(self.par_image_calc, images_to_calculate)
|
|
268
|
+
# set images to the results of the calculations
|
|
269
|
+
self.set_images(image_indices, client.gather(image_futures))
|
|
270
|
+
|
|
271
|
+
# Restore original pals
|
|
272
|
+
for i in range(0, n_images):
|
|
273
|
+
self.images[i].calculator.pal = orig_pal
|
|
274
|
+
|
|
275
|
+
def calculate_forces(self):
|
|
276
|
+
# Determine the number of images for which we have to do calculations.
|
|
277
|
+
# There may also be calculations for fixed images, as they need an
|
|
278
|
+
# energy value. But every fixed image only needs a calculation once.
|
|
279
|
+
images_to_calculate = self.moving_images
|
|
280
|
+
image_indices = self.moving_indices
|
|
281
|
+
if self.fix_first and (self.images[0]._energy is None):
|
|
282
|
+
images_to_calculate = [self.images[0]] + images_to_calculate
|
|
283
|
+
image_indices = [0] + list(image_indices)
|
|
284
|
+
if self.fix_last and (self.images[-1]._energy is None):
|
|
285
|
+
images_to_calculate = images_to_calculate + [self.images[-1]]
|
|
286
|
+
image_indices = list(image_indices) + [-1]
|
|
287
|
+
assert len(images_to_calculate) <= len(self.images)
|
|
288
|
+
|
|
289
|
+
# Parallel calculation with dask
|
|
290
|
+
if self.scheduler:
|
|
291
|
+
self.concurrent_force_calcs(images_to_calculate, image_indices)
|
|
292
|
+
# Serial calculation
|
|
293
|
+
else:
|
|
294
|
+
for image in images_to_calculate:
|
|
295
|
+
image.calc_energy_and_forces()
|
|
296
|
+
# Poor mans progress bar ;)
|
|
297
|
+
if self.progress:
|
|
298
|
+
print(".", end="")
|
|
299
|
+
sys.stdout.flush()
|
|
300
|
+
if self.progress:
|
|
301
|
+
print("\r", end="")
|
|
302
|
+
self.set_zero_forces_for_fixed_images()
|
|
303
|
+
self.counter += 1
|
|
304
|
+
|
|
305
|
+
if self.energy_min_mix:
|
|
306
|
+
# Will be None for calculators that already mix
|
|
307
|
+
all_energies = np.array([image.all_energies for image in self.images])
|
|
308
|
+
energy_diffs = np.diff(all_energies, axis=1).flatten()
|
|
309
|
+
calc_inds = all_energies.argmin(axis=1)
|
|
310
|
+
mix_at = []
|
|
311
|
+
for i, calc_ind in enumerate(calc_inds[:-1]):
|
|
312
|
+
next_ind = calc_inds[i + 1]
|
|
313
|
+
if (
|
|
314
|
+
(calc_ind != next_ind)
|
|
315
|
+
and (i not in self.org_forces_indices)
|
|
316
|
+
and (i + 1 not in self.org_forces_indices)
|
|
317
|
+
):
|
|
318
|
+
min_diff_offset = energy_diffs[[i, i + 1]].argmin()
|
|
319
|
+
mix_at.append(i + min_diff_offset)
|
|
320
|
+
|
|
321
|
+
for ind in mix_at:
|
|
322
|
+
self.images[ind].calculator.mix = True
|
|
323
|
+
# Recalculate correct energy and forces
|
|
324
|
+
print(
|
|
325
|
+
f"Switch after calc_ind={calc_ind} at index {ind}. Recalculating."
|
|
326
|
+
)
|
|
327
|
+
self.images[ind].calc_energy_and_forces()
|
|
328
|
+
self.org_forces_indices.append(ind)
|
|
329
|
+
calc_ind = calc_inds[ind]
|
|
330
|
+
|
|
331
|
+
energies = [image.energy for image in self.images]
|
|
332
|
+
forces = np.array([image.forces for image in self.images])
|
|
333
|
+
self.all_energies.append(energies)
|
|
334
|
+
self.all_true_forces.append(forces)
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
"energies": energies,
|
|
338
|
+
"forces": forces,
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def forces(self):
|
|
343
|
+
self.set_zero_forces_for_fixed_images()
|
|
344
|
+
forces = [image.forces for image in self.images]
|
|
345
|
+
self._forces = np.concatenate(forces)
|
|
346
|
+
self.counter += 1
|
|
347
|
+
return self._forces
|
|
348
|
+
|
|
349
|
+
@forces.setter
|
|
350
|
+
def forces(self, forces):
|
|
351
|
+
self.set_vector("forces", forces)
|
|
352
|
+
|
|
353
|
+
@property
|
|
354
|
+
def perpendicular_forces(self):
|
|
355
|
+
indices = range(len(self.images))
|
|
356
|
+
perp_forces = [self.get_perpendicular_forces(i) for i in indices]
|
|
357
|
+
return np.array(perp_forces).flatten()
|
|
358
|
+
|
|
359
|
+
def get_perpendicular_forces(self, i):
|
|
360
|
+
"""[1] Eq. 12"""
|
|
361
|
+
# Our goal in optimizing a ChainOfStates is minimizing the
|
|
362
|
+
# perpendicular force. Always return zero perpendicular
|
|
363
|
+
# forces for fixed images, so that they don't interfere
|
|
364
|
+
# with the convergence check.
|
|
365
|
+
if i not in self.moving_indices:
|
|
366
|
+
return self.zero_vec
|
|
367
|
+
|
|
368
|
+
forces = self.images[i].forces
|
|
369
|
+
tangent = self.get_tangent(i)
|
|
370
|
+
perp_forces = forces - forces.dot(tangent) * tangent
|
|
371
|
+
return perp_forces
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def gradient(self):
|
|
375
|
+
return -self.forces
|
|
376
|
+
|
|
377
|
+
@gradient.setter
|
|
378
|
+
def gradient(self, gradient):
|
|
379
|
+
self.forces = -gradient
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def masses_rep(self):
|
|
383
|
+
return np.array([image.masses_rep for image in self.images]).flatten()
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def results(self):
|
|
387
|
+
tmp_results = list()
|
|
388
|
+
for image in self.images:
|
|
389
|
+
res = image.results
|
|
390
|
+
res["coords"] = image.coords
|
|
391
|
+
res["cart_coords"] = image.cart_coords
|
|
392
|
+
tmp_results.append(res)
|
|
393
|
+
return tmp_results
|
|
394
|
+
|
|
395
|
+
def set_zero_forces_for_fixed_images(self):
|
|
396
|
+
"""This is always done in cartesian coordinates, independent
|
|
397
|
+
of the actual coord_type of the images as setting forces only
|
|
398
|
+
work with cartesian forces."""
|
|
399
|
+
zero_forces = np.zeros_like(self.images[0].cart_coords)
|
|
400
|
+
if self.fix_first:
|
|
401
|
+
self.images[0].cart_forces = zero_forces
|
|
402
|
+
self.log("Zeroed forces on fixed first image.")
|
|
403
|
+
if self.fix_last:
|
|
404
|
+
self.images[-1].cart_forces = zero_forces
|
|
405
|
+
self.log("Zeroed forces on fixed last image.")
|
|
406
|
+
|
|
407
|
+
def get_tangent(
|
|
408
|
+
self, i, kind="upwinding", lanczos_guess=None, disable_lanczos=False
|
|
409
|
+
):
|
|
410
|
+
"""[1] Equations (8) - (11)"""
|
|
411
|
+
|
|
412
|
+
# Converge to lowest curvature mode at the climbing image.
|
|
413
|
+
# In the current implementation the given kind may be overwritten when
|
|
414
|
+
# Lanczos iterations are enabled and there are climbing images. By
|
|
415
|
+
# setting 'disable_lanczos=True' the provided kind is never overwritten.
|
|
416
|
+
if (
|
|
417
|
+
not disable_lanczos
|
|
418
|
+
and self.started_climbing_lanczos
|
|
419
|
+
# and (i in self.get_climbing_indices())
|
|
420
|
+
and (i == self.get_hei_index())
|
|
421
|
+
):
|
|
422
|
+
kind = "lanczos"
|
|
423
|
+
|
|
424
|
+
tangent_kinds = ("upwinding", "simple", "bisect", "lanczos")
|
|
425
|
+
assert kind in tangent_kinds, "Invalid kind! Valid kinds are: {tangent_kinds}"
|
|
426
|
+
prev_index = max(i - 1, 0)
|
|
427
|
+
next_index = min(i + 1, len(self.images) - 1)
|
|
428
|
+
|
|
429
|
+
prev_image = self.images[prev_index]
|
|
430
|
+
ith_image = self.images[i]
|
|
431
|
+
next_image = self.images[next_index]
|
|
432
|
+
|
|
433
|
+
# If (i == 0) or (i == len(self.images)-1) then one
|
|
434
|
+
# of this tangents is zero.
|
|
435
|
+
tangent_plus = next_image - ith_image
|
|
436
|
+
tangent_minus = ith_image - prev_image
|
|
437
|
+
|
|
438
|
+
# Handle first and last image
|
|
439
|
+
if i == 0:
|
|
440
|
+
return tangent_plus / np.linalg.norm(tangent_plus)
|
|
441
|
+
elif i == (len(self.images) - 1):
|
|
442
|
+
return tangent_minus / np.linalg.norm(tangent_minus)
|
|
443
|
+
|
|
444
|
+
# [1], Eq. (1)
|
|
445
|
+
if kind == "simple":
|
|
446
|
+
tangent = next_image - prev_image
|
|
447
|
+
# [1], Eq. (2)
|
|
448
|
+
elif kind == "bisect":
|
|
449
|
+
first_term = tangent_minus / np.linalg.norm(tangent_minus)
|
|
450
|
+
sec_term = tangent_plus / np.linalg.norm(tangent_plus)
|
|
451
|
+
tangent = first_term + sec_term
|
|
452
|
+
# Upwinding tangent from [1] Eq. (8) and so on
|
|
453
|
+
elif kind == "upwinding":
|
|
454
|
+
prev_energy = prev_image.energy
|
|
455
|
+
ith_energy = ith_image.energy
|
|
456
|
+
next_energy = next_image.energy
|
|
457
|
+
|
|
458
|
+
next_energy_diff = abs(next_energy - ith_energy)
|
|
459
|
+
prev_energy_diff = abs(prev_energy - ith_energy)
|
|
460
|
+
delta_energy_max = max(next_energy_diff, prev_energy_diff)
|
|
461
|
+
delta_energy_min = min(next_energy_diff, prev_energy_diff)
|
|
462
|
+
|
|
463
|
+
# Uphill
|
|
464
|
+
if next_energy > ith_energy > prev_energy:
|
|
465
|
+
tangent = tangent_plus
|
|
466
|
+
# Downhill
|
|
467
|
+
elif next_energy < ith_energy < prev_energy:
|
|
468
|
+
tangent = tangent_minus
|
|
469
|
+
# Minimum or Maximum
|
|
470
|
+
else:
|
|
471
|
+
if next_energy >= prev_energy:
|
|
472
|
+
tangent = (
|
|
473
|
+
tangent_plus * delta_energy_max
|
|
474
|
+
+ tangent_minus * delta_energy_min
|
|
475
|
+
)
|
|
476
|
+
# next_energy < prev_energy
|
|
477
|
+
else:
|
|
478
|
+
tangent = (
|
|
479
|
+
tangent_plus * delta_energy_min
|
|
480
|
+
+ tangent_minus * delta_energy_max
|
|
481
|
+
)
|
|
482
|
+
elif kind == "lanczos":
|
|
483
|
+
# Calculating a lanczos tangent is costly, so we store the
|
|
484
|
+
# tangent in a dictionary. The current coordinates are
|
|
485
|
+
# stringified with precision=4 and then hashed. The tangent
|
|
486
|
+
# is stored/looked up with this hash.
|
|
487
|
+
cur_hash = hash_arr(ith_image.coords, precision=4)
|
|
488
|
+
try:
|
|
489
|
+
tangent = self.lanczos_tangents[cur_hash]
|
|
490
|
+
self.log(
|
|
491
|
+
"Returning previously calculated Lanczos tangent with "
|
|
492
|
+
f"hash={cur_hash}"
|
|
493
|
+
)
|
|
494
|
+
except KeyError:
|
|
495
|
+
# Try to use previous Lanczos tangent
|
|
496
|
+
guess = lanczos_guess
|
|
497
|
+
if (guess is None) and (self.prev_lanczos_hash is not None):
|
|
498
|
+
guess = self.lanczos_tangents[self.prev_lanczos_hash]
|
|
499
|
+
self.log(
|
|
500
|
+
f"Using tangent with hash={self.prev_lanczos_hash} "
|
|
501
|
+
"as initial guess for Lanczos algorithm."
|
|
502
|
+
)
|
|
503
|
+
w_min, tangent = geom_lanczos(
|
|
504
|
+
ith_image, guess=guess, logger=self.logger
|
|
505
|
+
)
|
|
506
|
+
self.lanczos_tangents[cur_hash] = tangent
|
|
507
|
+
# Update hash
|
|
508
|
+
self.prev_lanczos_hash = cur_hash
|
|
509
|
+
|
|
510
|
+
tangent /= np.linalg.norm(tangent)
|
|
511
|
+
return tangent
|
|
512
|
+
|
|
513
|
+
def get_tangents(self):
|
|
514
|
+
return np.array([self.get_tangent(i) for i in range(len(self.images))])
|
|
515
|
+
|
|
516
|
+
def as_xyz(self, comments=None):
|
|
517
|
+
return "\n".join([image.as_xyz() for image in self.images])
|
|
518
|
+
|
|
519
|
+
def get_dask_client(self):
|
|
520
|
+
return Client(self.scheduler)
|
|
521
|
+
|
|
522
|
+
def get_hei_index(self, energies=None):
|
|
523
|
+
"""Return index of highest energy image."""
|
|
524
|
+
if energies is None:
|
|
525
|
+
energies = [image.energy for image in self.images]
|
|
526
|
+
return np.argmax(energies)
|
|
527
|
+
|
|
528
|
+
def prepare_opt_cycle(self, last_coords, last_energies, last_forces):
|
|
529
|
+
"""Implements additional logic in preparation of the next
|
|
530
|
+
optimization cycle.
|
|
531
|
+
|
|
532
|
+
Should be called by the optimizer at the beginning of a new
|
|
533
|
+
optimization cycle. Can be used to implement additional logic
|
|
534
|
+
as needed for AdaptiveNEB etc.
|
|
535
|
+
"""
|
|
536
|
+
self.coords_list.append(last_coords)
|
|
537
|
+
self.forces_list.append(last_forces)
|
|
538
|
+
|
|
539
|
+
# Return False if we don't want to climb or are already
|
|
540
|
+
# climbing.
|
|
541
|
+
already_climbing = self.started_climbing
|
|
542
|
+
if self.climb and not already_climbing:
|
|
543
|
+
self.started_climbing = self.check_for_climbing_start(self.climb_rms)
|
|
544
|
+
if self.started_climbing:
|
|
545
|
+
msg = "Will use climbing image(s) in next cycle."
|
|
546
|
+
self.log(msg)
|
|
547
|
+
print(msg)
|
|
548
|
+
# Determine climbing index/indices if not set, but requested.
|
|
549
|
+
if already_climbing and self.climb_fixed and (self.fixed_climb_indices is None):
|
|
550
|
+
self.fixed_climb_indices = self.get_climbing_indices()
|
|
551
|
+
|
|
552
|
+
already_climbing_lanczos = self.started_climbing_lanczos
|
|
553
|
+
if (
|
|
554
|
+
self.climb_lanczos
|
|
555
|
+
and self.started_climbing
|
|
556
|
+
and not already_climbing_lanczos
|
|
557
|
+
):
|
|
558
|
+
self.started_climbing_lanczos = self.check_for_climbing_start(
|
|
559
|
+
self.climb_lanczos_rms
|
|
560
|
+
)
|
|
561
|
+
if self.started_climbing_lanczos:
|
|
562
|
+
msg = "Will use Lanczos algorithm for HEI tangent in next cycle."
|
|
563
|
+
self.log(msg)
|
|
564
|
+
print(msg)
|
|
565
|
+
|
|
566
|
+
return not already_climbing and self.started_climbing
|
|
567
|
+
|
|
568
|
+
def rms(self, arr):
|
|
569
|
+
"""Root mean square
|
|
570
|
+
|
|
571
|
+
Returns the root mean square of the given array.
|
|
572
|
+
|
|
573
|
+
Parameters
|
|
574
|
+
----------
|
|
575
|
+
arr : iterable of numbers
|
|
576
|
+
|
|
577
|
+
Returns
|
|
578
|
+
-------
|
|
579
|
+
rms : float
|
|
580
|
+
Root mean square of the given array.
|
|
581
|
+
"""
|
|
582
|
+
return np.sqrt(np.mean(np.square(arr)))
|
|
583
|
+
|
|
584
|
+
def check_for_climbing_start(self, ref_rms):
|
|
585
|
+
# Only initiate climbing on a sufficiently converged MEP.
|
|
586
|
+
# This can be determined from a supplied threshold for the
|
|
587
|
+
# RMS force (rms_force) or from a multiple of the
|
|
588
|
+
# RMS force convergence threshold (rms_multiple, default).
|
|
589
|
+
rms_forces = self.rms(self.forces_list[-1])
|
|
590
|
+
# Only start climbing when the COS is fully grown. This
|
|
591
|
+
# attribute may not be defined in all subclasses, so it
|
|
592
|
+
# defaults to True here.
|
|
593
|
+
try:
|
|
594
|
+
fully_grown = self.fully_grown
|
|
595
|
+
except AttributeError:
|
|
596
|
+
fully_grown = True
|
|
597
|
+
start_climbing = (rms_forces <= ref_rms) and fully_grown
|
|
598
|
+
return start_climbing
|
|
599
|
+
|
|
600
|
+
def get_climbing_indices(self):
|
|
601
|
+
# Index of the highest energy image (HEI)
|
|
602
|
+
hei_index = self.get_hei_index()
|
|
603
|
+
|
|
604
|
+
move_inds = self.moving_indices
|
|
605
|
+
# Don't climb if not yet enabled or requested.
|
|
606
|
+
if not (self.climb and self.started_climbing):
|
|
607
|
+
climb_indices = tuple()
|
|
608
|
+
elif self.fixed_climb_indices is not None:
|
|
609
|
+
climb_indices = self.fixed_climb_indices
|
|
610
|
+
_ = "index" if len(climb_indices) == 1 else "indices"
|
|
611
|
+
self.log(f"Returning fixed climbing {_}.")
|
|
612
|
+
# Do one image climbing (C1) neb if explicitly requested or
|
|
613
|
+
# the HEI is the first or last item in moving_indices.
|
|
614
|
+
elif self.climb == "one" or ((hei_index == 1) or (hei_index == move_inds[-1])):
|
|
615
|
+
climb_indices = (hei_index,)
|
|
616
|
+
# We can do two climbing (C2) neb if the highest energy image (HEI)
|
|
617
|
+
# is in moving_indices but not the first or last item in this list.
|
|
618
|
+
# elif self.climb != "one" and hei_index in move_inds[1:-1]:
|
|
619
|
+
elif hei_index in move_inds[1:-1]:
|
|
620
|
+
climb_indices = (hei_index - 1, hei_index + 1)
|
|
621
|
+
# climb_indices = (hei_index,)
|
|
622
|
+
# Don't climb when the HEI is the first or last image of the whole
|
|
623
|
+
# NEB.
|
|
624
|
+
else:
|
|
625
|
+
climb_indices = tuple()
|
|
626
|
+
self.log("Want to climb but can't. HEI is first or last image!")
|
|
627
|
+
# self.log(f"Climbing indices: {climb_indices}")
|
|
628
|
+
return climb_indices
|
|
629
|
+
|
|
630
|
+
def get_climbing_forces(self, ind):
|
|
631
|
+
climbing_image = self.images[ind]
|
|
632
|
+
ci_forces = climbing_image.forces
|
|
633
|
+
tangent = self.get_tangent(ind)
|
|
634
|
+
climbing_forces = ci_forces - 2 * ci_forces.dot(tangent) * tangent
|
|
635
|
+
|
|
636
|
+
return climbing_forces, climbing_image.energy
|
|
637
|
+
|
|
638
|
+
def set_climbing_forces(self, forces):
|
|
639
|
+
# Avoids calling the other methods with their logging output etc.
|
|
640
|
+
if not self.started_climbing:
|
|
641
|
+
return forces
|
|
642
|
+
|
|
643
|
+
for i in self.get_climbing_indices():
|
|
644
|
+
climb_forces, climb_en = self.get_climbing_forces(i)
|
|
645
|
+
forces[i] = climb_forces
|
|
646
|
+
norm = np.linalg.norm(climb_forces)
|
|
647
|
+
self.log(
|
|
648
|
+
f"Climbing with image {i}, E = {climb_en:.6f} au, "
|
|
649
|
+
f"norm(forces)={norm:.6f}"
|
|
650
|
+
)
|
|
651
|
+
return forces
|
|
652
|
+
|
|
653
|
+
def get_splined_hei(self):
|
|
654
|
+
self.log("Splining HEI")
|
|
655
|
+
# Interpolate energies
|
|
656
|
+
cart_coords = align_coords([image.cart_coords for image in self.images])
|
|
657
|
+
coord_diffs = get_coords_diffs(cart_coords)
|
|
658
|
+
self.log(f"\tCoordinate differences: {coord_diffs}")
|
|
659
|
+
energies = np.array(self.energy)
|
|
660
|
+
energies_spline = interp1d(coord_diffs, energies, kind="cubic")
|
|
661
|
+
x_fine = np.linspace(0, 1, 500)
|
|
662
|
+
energies_fine = energies_spline(x_fine)
|
|
663
|
+
# Determine index that yields the highest energy
|
|
664
|
+
hei_ind = energies_fine.argmax()
|
|
665
|
+
hei_x = x_fine[hei_ind]
|
|
666
|
+
self.log(f"Found splined HEI at x={hei_x:.4f}")
|
|
667
|
+
hei_frac_index = hei_x * (len(self.images) - 1)
|
|
668
|
+
hei_energy = energies_fine[hei_ind]
|
|
669
|
+
|
|
670
|
+
reshaped = cart_coords.reshape(-1, self.cart_coords_length)
|
|
671
|
+
# To use splprep we have to transpose the coords.
|
|
672
|
+
transp_coords = reshaped.transpose()
|
|
673
|
+
tcks, us = zip(
|
|
674
|
+
*[
|
|
675
|
+
splprep(transp_coords[i : i + 9], s=0, k=3, u=coord_diffs)
|
|
676
|
+
for i in range(0, len(transp_coords), 9)
|
|
677
|
+
]
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
# Reparametrize mesh
|
|
681
|
+
hei_coords = np.vstack(
|
|
682
|
+
[
|
|
683
|
+
# WTF, Black? This looks horrible.
|
|
684
|
+
splev(
|
|
685
|
+
[
|
|
686
|
+
hei_x,
|
|
687
|
+
],
|
|
688
|
+
tck,
|
|
689
|
+
)
|
|
690
|
+
for tck in tcks
|
|
691
|
+
]
|
|
692
|
+
)
|
|
693
|
+
hei_coords = hei_coords.flatten()
|
|
694
|
+
|
|
695
|
+
# Actually it looks like that splined tangents are really bad approximations
|
|
696
|
+
# to the actual imaginary mode. The Cartesian upwinding tangent is usually
|
|
697
|
+
# much much better. In 'run_tsopt_from_cos' we actually mix two "normal" tangents
|
|
698
|
+
# to obtain the HEI tangent.
|
|
699
|
+
hei_tangent = np.vstack(
|
|
700
|
+
[
|
|
701
|
+
# WTF, Black? This looks horrible.
|
|
702
|
+
splev(
|
|
703
|
+
[
|
|
704
|
+
hei_x,
|
|
705
|
+
],
|
|
706
|
+
tck,
|
|
707
|
+
der=1,
|
|
708
|
+
)
|
|
709
|
+
for tck in tcks
|
|
710
|
+
]
|
|
711
|
+
).T
|
|
712
|
+
hei_tangent = hei_tangent.flatten()
|
|
713
|
+
hei_tangent /= np.linalg.norm(hei_tangent)
|
|
714
|
+
return hei_coords, hei_energy, hei_tangent, hei_frac_index
|
|
715
|
+
|
|
716
|
+
def get_image_calc_counter_sum(self):
|
|
717
|
+
return sum([image.calculator.calc_counter for image in self.images])
|
|
718
|
+
|
|
719
|
+
def describe(self):
|
|
720
|
+
imgs = self.images
|
|
721
|
+
img = imgs[0]
|
|
722
|
+
return f"ChainOfStates, {len(imgs)} images, ({img.sum_formula}, {len(img.atoms)} atoms) per image"
|
|
723
|
+
|
|
724
|
+
def __str__(self):
|
|
725
|
+
return self.__class__.__name__
|