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,307 @@
|
|
|
1
|
+
# [1] https://doi.org/10.1021/acs.jctc.8b00068
|
|
2
|
+
# Dual-Level Approach to Instanton Theory
|
|
3
|
+
# Meisner, Kästner 2018
|
|
4
|
+
# [2] https://doi.org/10.1021/ct100658y
|
|
5
|
+
# Locating Instantons in Many Degrees of Freedom
|
|
6
|
+
# Rommel, Goumans, Kästner, 2011
|
|
7
|
+
# [3] https://aip.scitation.org/doi/full/10.1063/1.4932362
|
|
8
|
+
# Ring-polymer instanton theory of electron transofer in the
|
|
9
|
+
# nonadiabatic limit
|
|
10
|
+
# Richardson, 2015
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import scipy as sp
|
|
15
|
+
|
|
16
|
+
from pysisyphus import logger as pysis_logger
|
|
17
|
+
from pysisyphus.Geometry import Geometry
|
|
18
|
+
from pysisyphus.helpers import align_coords, get_coords_diffs
|
|
19
|
+
from pysisyphus.helpers_pure import eigval_to_wavenumber
|
|
20
|
+
|
|
21
|
+
import torch
|
|
22
|
+
|
|
23
|
+
def T_crossover_from_eigval(eigval):
|
|
24
|
+
nu = eigval_to_wavenumber(eigval) # in cm⁻¹
|
|
25
|
+
nu_m = 100 * abs(nu) # in m⁻¹
|
|
26
|
+
freq = nu_m * sp.constants.speed_of_light
|
|
27
|
+
# In papers the crossover temperature is usually defined with the angular
|
|
28
|
+
# frequency nu*2*pi. When using the "normal" frequency we don't have to
|
|
29
|
+
# divide by 2*pi. Both approaches obviously yield the same T_c.
|
|
30
|
+
# ang_freq = freq * 2 * np.pi
|
|
31
|
+
# T_c_ = sp.constants.hbar * ang_freq / (sp.constants.Boltzmann * 2 * np.pi)
|
|
32
|
+
T_c = sp.constants.hbar * freq / (sp.constants.Boltzmann)
|
|
33
|
+
return T_c
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def T_crossover_from_ts(ts_geom):
|
|
37
|
+
mw_hessian = ts_geom.mw_hessian
|
|
38
|
+
proj_hessian = ts_geom.eckart_projection(mw_hessian, full=True)
|
|
39
|
+
|
|
40
|
+
if isinstance(proj_hessian, torch.Tensor):
|
|
41
|
+
eigvals, eigvecs = torch.linalg.eigh(proj_hessian)
|
|
42
|
+
eigvals = eigvals.to(torch.double).cpu().numpy()
|
|
43
|
+
else:
|
|
44
|
+
eigvals, eigvecs = np.linalg.eigh(proj_hessian)
|
|
45
|
+
|
|
46
|
+
T_c = T_crossover_from_eigval(eigvals[0])
|
|
47
|
+
return T_c
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
logger = pysis_logger.getChild("instanton")
|
|
51
|
+
logger.setLevel(logging.DEBUG)
|
|
52
|
+
file_handler = logging.FileHandler("instanton.log", mode="w", delay=True)
|
|
53
|
+
logger.addHandler(file_handler)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def log_progress(val, key, i):
|
|
57
|
+
logger.debug(f"Calculated {key} at image {i}")
|
|
58
|
+
return val
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Instanton:
|
|
62
|
+
def __init__(self, images, calc_getter, T):
|
|
63
|
+
self.images = images
|
|
64
|
+
self.calc_getter = calc_getter
|
|
65
|
+
for image in self.images:
|
|
66
|
+
image.set_calculator(calc_getter())
|
|
67
|
+
self.T = T
|
|
68
|
+
|
|
69
|
+
# Pre-calculate action prefactors for the given temperature
|
|
70
|
+
beta = 1 / (sp.constants.Boltzmann * self.T) # Joule
|
|
71
|
+
beta_hbar = beta * sp.constants.hbar # seconds
|
|
72
|
+
beta_hbar_fs = beta_hbar * 1e15 # fs
|
|
73
|
+
self.beta_hbar = beta_hbar_fs
|
|
74
|
+
self.P_over_beta_hbar = self.P / self.beta_hbar
|
|
75
|
+
self.P_bh = self.P_over_beta_hbar
|
|
76
|
+
"""The Instanton is periodic: the first image is connected to the
|
|
77
|
+
last image.
|
|
78
|
+
At k=0 the index k-1 will be -1, which points to the last image.
|
|
79
|
+
|
|
80
|
+
Below we pre-calculate some indices (assuming N images).
|
|
81
|
+
unshifted indices ks: k = {0, 1, .. , N-1}
|
|
82
|
+
shifted indices ksm1: k-1 = {-1, 0, 1, .. , N-2}
|
|
83
|
+
shifted indices ksp1: k+1 = {1, 2, .. , N-1, 0}
|
|
84
|
+
"""
|
|
85
|
+
self.ks = np.arange(self.P)
|
|
86
|
+
self.ksm1 = self.ks - 1
|
|
87
|
+
self.ksp1 = self.ks + 1
|
|
88
|
+
self.ksp1[-1] = 0 # k+1 for the last image points to the first image
|
|
89
|
+
|
|
90
|
+
self.coord_type = "mwcartesian"
|
|
91
|
+
self.internal = None
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def P(self):
|
|
95
|
+
return len(self.images)
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def from_ts(cls, ts_geom, P, dr=0.4, delta_T=25, cart_hessian=None, **kwargs):
|
|
99
|
+
assert ts_geom.coord_type == "mwcartesian"
|
|
100
|
+
atoms = ts_geom.atoms
|
|
101
|
+
ts_coords = ts_geom.coords
|
|
102
|
+
|
|
103
|
+
if cart_hessian is None:
|
|
104
|
+
mw_hessian = ts_geom.mw_hessian
|
|
105
|
+
else:
|
|
106
|
+
mw_hessian = ts_geom.mass_weigh_hessian(cart_hessian)
|
|
107
|
+
|
|
108
|
+
proj_hessian = ts_geom.eckart_projection(mw_hessian, full=True)
|
|
109
|
+
|
|
110
|
+
if isinstance(proj_hessian, torch.Tensor):
|
|
111
|
+
eigvals, eigvecs = torch.linalg.eigh(proj_hessian)
|
|
112
|
+
eigvals = eigvals.to(torch.double).cpu().numpy()
|
|
113
|
+
eigvecs = eigvecs.to(torch.double).cpu().numpy()
|
|
114
|
+
else:
|
|
115
|
+
eigvals, eigvecs = np.linalg.eigh(proj_hessian)
|
|
116
|
+
|
|
117
|
+
# Use crossover temperature with a little offset (delta_T) if no T is given.
|
|
118
|
+
try:
|
|
119
|
+
kwargs["T"]
|
|
120
|
+
except KeyError:
|
|
121
|
+
T_c = T_crossover_from_eigval(eigvals[0])
|
|
122
|
+
kwargs["T"] = T_c - delta_T
|
|
123
|
+
imag_mode = eigvecs[:, 0]
|
|
124
|
+
cosines = np.cos((np.arange(P) + 1 - 0.5) / P * np.pi)
|
|
125
|
+
image_mw_coords = ts_coords + dr * cosines[:, None] * imag_mode
|
|
126
|
+
image_coords = image_mw_coords / np.sqrt(ts_geom.masses_rep)
|
|
127
|
+
if not ts_geom.is_analytical_2d:
|
|
128
|
+
align_coords(image_coords)
|
|
129
|
+
images = [
|
|
130
|
+
Geometry(atoms, coords, coord_type="mwcartesian") for coords in image_coords
|
|
131
|
+
]
|
|
132
|
+
instanton = Instanton(images, **kwargs)
|
|
133
|
+
return instanton
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def from_instanton(cls, other, **kwargs):
|
|
137
|
+
images = other.images
|
|
138
|
+
instanton = Instanton(images, **kwargs)
|
|
139
|
+
return instanton
|
|
140
|
+
|
|
141
|
+
def as_xyz(self):
|
|
142
|
+
return "\n".join([geom.as_xyz() for geom in self.images])
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def coords(self):
|
|
146
|
+
return np.ravel([image.coords for image in self.images])
|
|
147
|
+
|
|
148
|
+
@coords.setter
|
|
149
|
+
def coords(self, coords):
|
|
150
|
+
coords = coords.reshape(len(self.images), -1)
|
|
151
|
+
for img_coords, image in zip(coords, self.images):
|
|
152
|
+
image.coords = img_coords
|
|
153
|
+
|
|
154
|
+
def action(self):
|
|
155
|
+
"""Action in au / fs, Hartree per femtosecond."""
|
|
156
|
+
all_coords = np.array([image.coords for image in self.images])
|
|
157
|
+
diffs = all_coords[self.ks] - all_coords[self.ksm1]
|
|
158
|
+
dists = np.linalg.norm(diffs, axis=1)
|
|
159
|
+
S_0 = self.P_bh * (dists**2).sum()
|
|
160
|
+
|
|
161
|
+
energies = [image.energy for image in self.images]
|
|
162
|
+
S_pot = 1 / self.P_bh * sum(energies)
|
|
163
|
+
S_E = S_0 / 2 + S_pot
|
|
164
|
+
results = {
|
|
165
|
+
"action": S_E,
|
|
166
|
+
}
|
|
167
|
+
return results
|
|
168
|
+
|
|
169
|
+
def action_gradient(self):
|
|
170
|
+
"""
|
|
171
|
+
kin_grad corresponds to the gradient of S_0 in (Eq. 1 in [1], or
|
|
172
|
+
first term in Eq. 6 in [2].) It boils down to the derivative of a sum
|
|
173
|
+
of vector norms
|
|
174
|
+
|
|
175
|
+
d sum_k (||y_k - y_(k-1)||_2)²
|
|
176
|
+
---
|
|
177
|
+
d y_k
|
|
178
|
+
|
|
179
|
+
The derivative of a norm of a vector difference is quite simple, but
|
|
180
|
+
care has to be taken to recognize, that y_k appears two times in the sum.
|
|
181
|
+
It appears in the first summand for k and in the second summand for k+1.
|
|
182
|
+
|
|
183
|
+
sum_k (||y_k - y_(k-1)||_2)²
|
|
184
|
+
1. term 2. term
|
|
185
|
+
= (||y_k - y_(k-1)||_2)² + (||y_(k+1) - y_k||_2)² + ... and so on
|
|
186
|
+
|
|
187
|
+
The derivative of the first term is
|
|
188
|
+
|
|
189
|
+
2 * (y_k - y_(k-1))
|
|
190
|
+
|
|
191
|
+
and the derivative of the second term is
|
|
192
|
+
|
|
193
|
+
-2 * (y_(k+1) - y_k))
|
|
194
|
+
|
|
195
|
+
which is equal to
|
|
196
|
+
|
|
197
|
+
2 * (y_k - y_(k+1)) .
|
|
198
|
+
|
|
199
|
+
To summarize:
|
|
200
|
+
|
|
201
|
+
d sum_k(||y_k - y_(k-1)||_2)²
|
|
202
|
+
---
|
|
203
|
+
d y_k
|
|
204
|
+
|
|
205
|
+
= 2 * (2 * y_k - y_(k-1) - y_(k+1)) .
|
|
206
|
+
"""
|
|
207
|
+
image_coords = np.array([image.coords for image in self.images])
|
|
208
|
+
kin_grad = (
|
|
209
|
+
2
|
|
210
|
+
* (
|
|
211
|
+
2 * image_coords # y_k
|
|
212
|
+
- image_coords[self.ksm1] # y_(k-1)
|
|
213
|
+
- image_coords[self.ksp1] # y_(k+1)
|
|
214
|
+
).flatten()
|
|
215
|
+
)
|
|
216
|
+
kin_grad *= self.P_bh
|
|
217
|
+
pot_grad = np.array(
|
|
218
|
+
[
|
|
219
|
+
log_progress(image.gradient, "gradient", i)
|
|
220
|
+
for i, image in enumerate(self.images)
|
|
221
|
+
]
|
|
222
|
+
).flatten()
|
|
223
|
+
pot_grad /= self.P_bh
|
|
224
|
+
gradient = kin_grad / 2 + pot_grad
|
|
225
|
+
# gradient = pot_grad
|
|
226
|
+
# gradient = kin_grad
|
|
227
|
+
# gradient = kin_grad / 2
|
|
228
|
+
results = {
|
|
229
|
+
"gradient": gradient,
|
|
230
|
+
}
|
|
231
|
+
results.update(self.action())
|
|
232
|
+
return results
|
|
233
|
+
|
|
234
|
+
def action_hessian(self):
|
|
235
|
+
image_hessians = [
|
|
236
|
+
log_progress(image.hessian, "hessian", i)
|
|
237
|
+
for i, image in enumerate(self.images)
|
|
238
|
+
]
|
|
239
|
+
pot_hess = sp.linalg.block_diag(*image_hessians)
|
|
240
|
+
pot_hess /= self.P_bh
|
|
241
|
+
coord_num = pot_hess.shape[0]
|
|
242
|
+
zeroes = np.zeros((coord_num, coord_num))
|
|
243
|
+
image_coord_num = self.images[0].coords.size
|
|
244
|
+
inds = np.arange(coord_num).reshape(-1, image_coord_num)
|
|
245
|
+
ks = inds[self.ks].flatten()
|
|
246
|
+
ksm1 = inds[self.ksm1].flatten()
|
|
247
|
+
ksp1 = inds[self.ksp1].flatten()
|
|
248
|
+
km = zeroes.copy()
|
|
249
|
+
km[ks, ksm1] = 1.0
|
|
250
|
+
kp = zeroes.copy()
|
|
251
|
+
kp[ks, ksp1] = 1.0
|
|
252
|
+
kin_hess = 4 * np.eye(coord_num) - 2 * km - 2 * kp # y_k # y_k-1 # y_k+1
|
|
253
|
+
kin_hess *= self.P_bh
|
|
254
|
+
hessian = kin_hess / 2 + pot_hess
|
|
255
|
+
# hessian = pot_hess
|
|
256
|
+
# hessian = kin_hess / 2
|
|
257
|
+
results = {
|
|
258
|
+
"hessian": hessian,
|
|
259
|
+
}
|
|
260
|
+
results.update(self.action())
|
|
261
|
+
return results
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def energy(self):
|
|
265
|
+
return self.action()["action"]
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def gradient(self):
|
|
269
|
+
return self.action_gradient()["gradient"]
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def forces(self):
|
|
273
|
+
return -self.gradient
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def hessian(self):
|
|
277
|
+
return self.action_hessian()["hessian"]
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def cart_hessian(self):
|
|
281
|
+
return sp.linalg.block_diag(*[image.cart_hessian for image in self.images])
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def cart_coords(self):
|
|
285
|
+
return np.ravel([image.cart_coords for image in self.images])
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def cart_forces(self):
|
|
289
|
+
return np.ravel([image.cart_forces for image in self.images])
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def cart_hessian(self):
|
|
293
|
+
return sp.linalg.block_diag(*[image.cart_forces for image in self.images])
|
|
294
|
+
|
|
295
|
+
def is_analytical_2d(self):
|
|
296
|
+
return self.images[0].is_analytical_2d
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def path_length(self):
|
|
300
|
+
image_coords3d = [image.coords3d for image in self.images]
|
|
301
|
+
coord_diffs = get_coords_diffs(image_coords3d, align=False, normalize=False)
|
|
302
|
+
length = coord_diffs.sum()
|
|
303
|
+
return length
|
|
304
|
+
|
|
305
|
+
def get_additional_print(self):
|
|
306
|
+
length = self.path_length
|
|
307
|
+
return f"\t\tInstanton length={length:.2f} √a̅m̅u̅·au"
|
pysisyphus/irc/LQA.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# [1] https://aip.scitation.org/doi/pdf/10.1063/1.459634?class=pdf
|
|
2
|
+
# Page, 1990, Eq. 19 is missing a **2 after g'_0,i
|
|
3
|
+
# [2] https://aip.scitation.org/doi/10.1063/1.1724823
|
|
4
|
+
# Hratchian, 2004
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from pysisyphus.optimizers.hessian_updates import bfgs_update
|
|
9
|
+
from pysisyphus.irc.IRC import IRC
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LQA(IRC):
|
|
13
|
+
|
|
14
|
+
def __init__(self, geometry, N_euler=5000, **kwargs):
|
|
15
|
+
super().__init__(geometry, **kwargs)
|
|
16
|
+
|
|
17
|
+
self.N_euler = N_euler
|
|
18
|
+
|
|
19
|
+
def step(self):
|
|
20
|
+
mw_gradient = self.mw_gradient
|
|
21
|
+
|
|
22
|
+
if len(self.irc_mw_gradients) > 1:
|
|
23
|
+
dg = self.irc_mw_gradients[-1] - self.irc_mw_gradients[-2]
|
|
24
|
+
dx = self.irc_mw_coords[-1] - self.irc_mw_coords[-2]
|
|
25
|
+
dH, _ = bfgs_update(self.mw_hessian, dx, dg)
|
|
26
|
+
self.mw_hessian += dH
|
|
27
|
+
|
|
28
|
+
eigenvalues, eigenvectors = np.linalg.eigh(self.mw_hessian)
|
|
29
|
+
# Drop small eigenvalues and corresponding eigenvectors
|
|
30
|
+
small_vals = np.abs(eigenvalues) < 1e-8
|
|
31
|
+
eigenvalues = eigenvalues[~small_vals]
|
|
32
|
+
eigenvectors = eigenvectors[:,~small_vals]
|
|
33
|
+
|
|
34
|
+
# t step for numerical integration
|
|
35
|
+
dt = 1 / self.N_euler * self.step_length / np.linalg.norm(mw_gradient)
|
|
36
|
+
|
|
37
|
+
# Transform gradient to eigensystem of the hessian
|
|
38
|
+
mw_gradient_trans = eigenvectors.T @ mw_gradient
|
|
39
|
+
|
|
40
|
+
t = dt
|
|
41
|
+
cur_length = 0
|
|
42
|
+
for i in range(self.N_euler):
|
|
43
|
+
dsdt = np.sqrt(np.sum(mw_gradient_trans**2 * np.exp(-2*eigenvalues*t)))
|
|
44
|
+
cur_length += dsdt * dt
|
|
45
|
+
if cur_length > self.step_length:
|
|
46
|
+
break
|
|
47
|
+
t += dt
|
|
48
|
+
alphas = (np.exp(-eigenvalues*t) - 1) / eigenvalues
|
|
49
|
+
A = eigenvectors @ np.diag(alphas) @ eigenvectors.T
|
|
50
|
+
step = A @ mw_gradient
|
|
51
|
+
|
|
52
|
+
mw_coords = self.mw_coords.copy()
|
|
53
|
+
self.mw_coords = mw_coords + step
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.helpers import do_final_hessian
|
|
4
|
+
from pysisyphus.helpers_pure import eigval_to_wavenumber
|
|
5
|
+
from pysisyphus.irc.IRC import IRC
|
|
6
|
+
from pysisyphus.optimizers.hessian_updates import bofill_update
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ModeKill(IRC):
|
|
10
|
+
|
|
11
|
+
def __init__(self, geometry, kill_inds, nu_thresh=-5.,
|
|
12
|
+
do_hess=True, hessian_update=True, **kwargs):
|
|
13
|
+
# Use tight convergence criteria so the IRC/ModeKill doesn't
|
|
14
|
+
# ent prematurely.
|
|
15
|
+
super().__init__(geometry, downhill=True, rms_grad_thresh=1e-6,
|
|
16
|
+
hessian_init="calc", **kwargs)
|
|
17
|
+
|
|
18
|
+
assert self.geometry.coord_type == "cart"
|
|
19
|
+
|
|
20
|
+
self.kill_inds = np.array(kill_inds, dtype=int)
|
|
21
|
+
self.nu_thresh = float(nu_thresh)
|
|
22
|
+
self.do_hess = do_hess
|
|
23
|
+
self.hessian_update = hessian_update
|
|
24
|
+
|
|
25
|
+
self.fmt = {"float": lambda f: f"{f:.4f}",}
|
|
26
|
+
self.ovlp_thresh = .3
|
|
27
|
+
self.indices = np.arange(self.geometry.coords.size)
|
|
28
|
+
self.neg_nus = list()
|
|
29
|
+
|
|
30
|
+
def prepare(self, *args, **kwargs):
|
|
31
|
+
super().prepare(*args, **kwargs)
|
|
32
|
+
|
|
33
|
+
self.update_mw_down_step()
|
|
34
|
+
|
|
35
|
+
def update_mw_down_step(self):
|
|
36
|
+
w, v = np.linalg.eigh(self.mw_hessian)
|
|
37
|
+
|
|
38
|
+
self.kill_modes = v[:,self.kill_inds]
|
|
39
|
+
nus = eigval_to_wavenumber(w)
|
|
40
|
+
assert all(nus[self.kill_inds] < self.nu_thresh), \
|
|
41
|
+
"ModeKill is intended for removal of imaginary frequencies " \
|
|
42
|
+
f"below {self.nu_thresh} cm⁻¹! The specified indices " \
|
|
43
|
+
f"{self.kill_inds} contain modes with positive frequencies " \
|
|
44
|
+
f"({nus[self.kill_inds]} cm⁻¹). Please choose different kill_inds!"
|
|
45
|
+
|
|
46
|
+
"""After diagonalization of the mass-weighted hessian the signs
|
|
47
|
+
of the eigenvectors are arbitrary.
|
|
48
|
+
We determine the correct sign of the eigenvector(s) from its
|
|
49
|
+
overlap(s) with the gradient.
|
|
50
|
+
To decrease the overall energy we have to step against the
|
|
51
|
+
gradient, so the overlap of gradient and the respective eigen-
|
|
52
|
+
vector(s) must be negative.
|
|
53
|
+
If an overlap is positive we flip the sign of the corresponding
|
|
54
|
+
eigenvector.
|
|
55
|
+
"""
|
|
56
|
+
mw_grad = self.mw_gradient
|
|
57
|
+
mw_grad_normed = mw_grad / np.linalg.norm(mw_grad)
|
|
58
|
+
overlaps = np.einsum("ij,i->j", self.kill_modes, mw_grad_normed)
|
|
59
|
+
self.log("Overlaps between gradient and eigenvectors:")
|
|
60
|
+
self.log(overlaps)
|
|
61
|
+
flip = overlaps > 0
|
|
62
|
+
self.log("Eigenvector signs to be flipped:")
|
|
63
|
+
self.log(str(flip))
|
|
64
|
+
self.kill_modes[:,flip] *= -1
|
|
65
|
+
# Create the step as the sum of the downhill steps along the modes
|
|
66
|
+
# to remove.
|
|
67
|
+
self.mw_down_step = (self.step_length*self.kill_modes).sum(axis=1)
|
|
68
|
+
|
|
69
|
+
def step(self):
|
|
70
|
+
if self.hessian_update and (self.cur_cycle > 0):
|
|
71
|
+
# Hessian update with mass-weighted values
|
|
72
|
+
dx = self.irc_mw_coords[-1] - self.irc_mw_coords[-2]
|
|
73
|
+
dg = (self.irc_mw_gradients[-1] - self.irc_mw_gradients[-2])
|
|
74
|
+
d_mw_H, key = bofill_update(self.mw_hessian, dx, dg)
|
|
75
|
+
self.mw_hessian += d_mw_H
|
|
76
|
+
|
|
77
|
+
# norm(dx) is probably self.step_length ;)
|
|
78
|
+
norm_dx = np.linalg.norm(dx)
|
|
79
|
+
norm_dg = np.linalg.norm(dg)
|
|
80
|
+
self.log(f"Did {key} hessian update: norm(dx)={norm_dx:.4e}, "
|
|
81
|
+
f"norm(dg)={norm_dg:.4e}."
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
# Recalculate exact hessian
|
|
85
|
+
self.mw_hessian = self.geometry.mw_hessian
|
|
86
|
+
self.log("Recalculated exact hessian.")
|
|
87
|
+
|
|
88
|
+
w, v = np.linalg.eigh(self.mw_hessian)
|
|
89
|
+
# Overlaps between current normal modes and the modes we want to
|
|
90
|
+
# remove.
|
|
91
|
+
overlaps = np.abs(np.einsum("ij,ik->jk", self.kill_modes, v))
|
|
92
|
+
self.log(f"Overlaps between original modes and current modes:")
|
|
93
|
+
# overlaps contains one row per mode to remove
|
|
94
|
+
for i, ovlps in enumerate(overlaps):
|
|
95
|
+
above_thresh = ovlps > self.ovlp_thresh
|
|
96
|
+
ovlp_str = " ".join(
|
|
97
|
+
[f"{i:02d}: {o:.4f}" for i, o in zip(self.indices[above_thresh],
|
|
98
|
+
ovlps[above_thresh])
|
|
99
|
+
]
|
|
100
|
+
)
|
|
101
|
+
self.log(f"Org. mode {i:02d}:\n\t\t\t{ovlp_str}")
|
|
102
|
+
|
|
103
|
+
nus = eigval_to_wavenumber(w)
|
|
104
|
+
neg_inds = nus <= self.nu_thresh
|
|
105
|
+
neg_nus = nus[neg_inds]
|
|
106
|
+
self.neg_nus.append(neg_nus)
|
|
107
|
+
self.log(f"Wavenumbers of imaginary modes (<= {self.nu_thresh} cm⁻¹):")
|
|
108
|
+
self.log(f"{neg_nus} cm⁻¹")
|
|
109
|
+
|
|
110
|
+
# Check if any eigenvalues became positive. If so remove them and update
|
|
111
|
+
# the step. If no mode to remove is left we are finished and can signal
|
|
112
|
+
# convergence.
|
|
113
|
+
argmax = overlaps.argmax(axis=1)
|
|
114
|
+
eigvals = w[argmax]
|
|
115
|
+
pos_eigvals = eigvals > 0
|
|
116
|
+
if any(pos_eigvals):
|
|
117
|
+
# Only keep negative eigenvalues.
|
|
118
|
+
flipped = self.kill_inds[pos_eigvals]
|
|
119
|
+
self.kill_inds = self.kill_inds[~pos_eigvals]
|
|
120
|
+
self.update_mw_down_step()
|
|
121
|
+
self.log(f"Eigenvalue(s) of mode(s) {flipped} became positive!")
|
|
122
|
+
self.converged = len(self.kill_inds) == 0
|
|
123
|
+
|
|
124
|
+
if not self.converged:
|
|
125
|
+
self.mw_coords += self.mw_down_step
|
|
126
|
+
|
|
127
|
+
def postprocess(self):
|
|
128
|
+
super().postprocess()
|
|
129
|
+
|
|
130
|
+
if self.do_hess:
|
|
131
|
+
print()
|
|
132
|
+
do_final_hessian(self.geometry)
|
|
133
|
+
|
|
134
|
+
def get_additional_print(self):
|
|
135
|
+
neg_nus = np.array2string(self.neg_nus[-1], precision=2)
|
|
136
|
+
return f"\timag. ῦ: {neg_nus} cm⁻¹"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ParamPlot:
|
|
6
|
+
|
|
7
|
+
def __init__(self, coords_list, param1_inds, param2_inds):
|
|
8
|
+
self.coords_list = coords_list
|
|
9
|
+
self.coords_list_3d = [c.reshape(-1, 3) for c in coords_list]
|
|
10
|
+
self.p1inds = param1_inds
|
|
11
|
+
self.p2inds = param2_inds
|
|
12
|
+
|
|
13
|
+
self.p1 = [self.get_param(c, param1_inds) for c in self.coords_list_3d]
|
|
14
|
+
self.p2 = [self.get_param(c, param2_inds) for c in self.coords_list_3d]
|
|
15
|
+
|
|
16
|
+
self.fig, self.ax = plt.subplots()
|
|
17
|
+
|
|
18
|
+
def calc_bond(self, coords, ind1, ind2):
|
|
19
|
+
return np.linalg.norm(coords[ind1] - coords[ind2])
|
|
20
|
+
|
|
21
|
+
def calc_angle(self, coords, ind1, ind2, ind3):
|
|
22
|
+
vec1 = coords[ind1] - coords[ind2]
|
|
23
|
+
vec2 = coords[ind3] - coords[ind2]
|
|
24
|
+
vec1n = np.linalg.norm(vec1)
|
|
25
|
+
vec2n = np.linalg.norm(vec2)
|
|
26
|
+
dotp = np.dot(vec1, vec2)
|
|
27
|
+
radians = np.arccos(dotp / (vec1n * vec2n))
|
|
28
|
+
return radians * 180 / np.pi
|
|
29
|
+
|
|
30
|
+
def get_param(self, coords, param_inds):
|
|
31
|
+
if len(param_inds) == 2:
|
|
32
|
+
return self.calc_bond(coords, *param_inds)
|
|
33
|
+
elif len(param_inds) == 3:
|
|
34
|
+
return self.calc_angle(coords, *param_inds)
|
|
35
|
+
else:
|
|
36
|
+
raise Exception
|
|
37
|
+
|
|
38
|
+
def plot(self):
|
|
39
|
+
self.ax.plot(self.p1, self.p2, "ro", ls="--")
|
|
40
|
+
self.ax.set_xlabel(str(self.p1inds))
|
|
41
|
+
self.ax.set_ylabel(str(self.p2inds))
|
|
42
|
+
|
|
43
|
+
def show(self):
|
|
44
|
+
plt.tight_layout()
|
|
45
|
+
plt.show()
|
|
46
|
+
|
|
47
|
+
def save(self, path, prefix):
|
|
48
|
+
out_fn = str(path / (prefix + ".pdf"))
|
|
49
|
+
self.fig.tight_layout()
|
|
50
|
+
self.fig.savefig(out_fn)
|
|
51
|
+
|
|
52
|
+
def save_coords(self, path, prefix):
|
|
53
|
+
np.savetxt(path / (prefix + ".coords"), self.coords_list)
|
pysisyphus/irc/RK4.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.irc.IRC import IRC
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# [1] https://aip.scitation.org/doi/pdf/10.1063/1.462674?class=pdf
|
|
7
|
+
# [2] https://www.sciencedirect.com/science/article/pii/S0009261406015673
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RK4(IRC):
|
|
11
|
+
|
|
12
|
+
def get_k(self, mw_coords):
|
|
13
|
+
self.mw_coords = mw_coords
|
|
14
|
+
grad = self.mw_gradient
|
|
15
|
+
direction = -grad / np.linalg.norm(grad)
|
|
16
|
+
k = self.step_length * direction
|
|
17
|
+
return k
|
|
18
|
+
|
|
19
|
+
def step(self):
|
|
20
|
+
# Eq. (25 - 29) in [1]
|
|
21
|
+
mw_coords_1 = self.mw_coords.copy()
|
|
22
|
+
k1 = self.get_k(mw_coords_1)
|
|
23
|
+
|
|
24
|
+
mw_coords_2 = mw_coords_1 + 0.5*k1
|
|
25
|
+
k2 = self.get_k(mw_coords_2)
|
|
26
|
+
|
|
27
|
+
mw_coords_3 = mw_coords_1 + 0.5*k2
|
|
28
|
+
k3 = self.get_k(mw_coords_3)
|
|
29
|
+
|
|
30
|
+
mw_coords_4 = mw_coords_1 + k3
|
|
31
|
+
k4 = self.get_k(mw_coords_4)
|
|
32
|
+
|
|
33
|
+
step = (k1 + 2*k2 + 2*k3 + k4) / 6
|
|
34
|
+
step_norm = np.linalg.norm(step)
|
|
35
|
+
self.log(f"norm(step)={step_norm:.6f}")
|
|
36
|
+
self.mw_coords = mw_coords_1 + step
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"DampedVelocityVerlet",
|
|
5
|
+
"Euler",
|
|
6
|
+
"EulerPC",
|
|
7
|
+
"LQA",
|
|
8
|
+
"GonzalezSchlegel",
|
|
9
|
+
"IMKMod",
|
|
10
|
+
"ModeKill",
|
|
11
|
+
"RK4",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
from pysisyphus.irc.DampedVelocityVerlet import DampedVelocityVerlet
|
|
15
|
+
from pysisyphus.irc.Euler import Euler
|
|
16
|
+
from pysisyphus.irc.EulerPC import EulerPC
|
|
17
|
+
from pysisyphus.irc.GonzalezSchlegel import GonzalezSchlegel
|
|
18
|
+
from pysisyphus.irc.IMKMod import IMKMod
|
|
19
|
+
from pysisyphus.irc.LQA import LQA
|
|
20
|
+
from pysisyphus.irc.ModeKill import ModeKill
|
|
21
|
+
from pysisyphus.irc.RK4 import RK4
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("irc")
|
|
24
|
+
logger.setLevel(logging.DEBUG)
|
|
25
|
+
# delay = True prevents creation of empty logfiles
|
|
26
|
+
handler = logging.FileHandler("irc.log", mode="w", delay=True)
|
|
27
|
+
# fmt_str = "%(levelname)s - %(message)s"
|
|
28
|
+
fmt_str = "%(message)s"
|
|
29
|
+
formatter = logging.Formatter(fmt_str)
|
|
30
|
+
handler.setFormatter(formatter)
|
|
31
|
+
logger.addHandler(handler)
|