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,219 @@
|
|
|
1
|
+
# [1] https://pubs.acs.org/doi/10.1021/j100338a027
|
|
2
|
+
# Koeski, Gordon, 1989
|
|
3
|
+
# [2] https://aip.scitation.org/doi/abs/10.1063/1.459634
|
|
4
|
+
# Page, Doubleday, McIver, 1990
|
|
5
|
+
|
|
6
|
+
from collections import namedtuple
|
|
7
|
+
|
|
8
|
+
import h5py
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy.optimize import bisect
|
|
11
|
+
|
|
12
|
+
from pysisyphus.constants import AU2KJPERMOL
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_curv_vec(H, Gv, v0, w0):
|
|
16
|
+
v0 = v0[:, None]
|
|
17
|
+
I = np.eye(v0.size)
|
|
18
|
+
first = np.linalg.pinv(2 * w0 * I - H, rcond=1e-8)
|
|
19
|
+
second = Gv.dot(v0) - v0.T.dot(Gv).dot(v0) * v0
|
|
20
|
+
v1 = first.dot(second)
|
|
21
|
+
return v1.flatten()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def taylor_closure(H, Gv, v0, v1, w0):
|
|
25
|
+
"""Taylor expansion of energy to 3rd order.
|
|
26
|
+
|
|
27
|
+
dx(ds) = ds*v0 + ds**2*v1 / 2
|
|
28
|
+
dE(ds) = dx^T H dx / 2 + dx^T [Gv] dx / 6
|
|
29
|
+
|
|
30
|
+
H = Hessian
|
|
31
|
+
Gv = 3rd derivative of energy along v0
|
|
32
|
+
v0 = Transition vector
|
|
33
|
+
v1 = Curvature vector
|
|
34
|
+
w0 = Eigenvalue belonging to v0
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Precontract some values that will be reused
|
|
38
|
+
v0v1 = v0.dot(v1)
|
|
39
|
+
v1Hv1 = v1.dot(H).dot(v1)
|
|
40
|
+
v0Gvv0 = v0.dot(Gv).dot(v0)
|
|
41
|
+
v0Gvv1 = v0.dot(Gv).dot(v1)
|
|
42
|
+
v1Gvv1 = v1.dot(Gv).dot(v1)
|
|
43
|
+
|
|
44
|
+
def dE(ds):
|
|
45
|
+
ds2 = ds ** 2
|
|
46
|
+
ds3 = ds2 * ds
|
|
47
|
+
ds4 = ds2 * ds2
|
|
48
|
+
|
|
49
|
+
# Δx^T H Δx / 2
|
|
50
|
+
quad = (w0 * ds2 + ds3 * w0 ** 2 * v0v1 + (ds4 * v1Hv1) / 4) / 2
|
|
51
|
+
# Δx^T [Gv] Δx / 6
|
|
52
|
+
cubic = (ds2 * v0Gvv0 + ds3 * v0Gvv1 + ds4 * v1Gvv1 / 4) / 6
|
|
53
|
+
return quad + cubic
|
|
54
|
+
|
|
55
|
+
return dE
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
ThirdDerivResult = namedtuple(
|
|
59
|
+
"ThirdDerivResult",
|
|
60
|
+
"coords_plus energy_plus H_plus coords_minus energy_minus H_minus G_vec vec ds",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def third_deriv_fd(geom, vec, ds=0.001):
|
|
65
|
+
"""Third derivative of the energy in direction 'vec'."""
|
|
66
|
+
|
|
67
|
+
def get_H(geom, coords):
|
|
68
|
+
results = geom.get_energy_and_cart_hessian_at(coords)
|
|
69
|
+
energy = results["energy"]
|
|
70
|
+
H = results["hessian"]
|
|
71
|
+
H_mw = geom.mass_weigh_hessian(H)
|
|
72
|
+
# Only project for multi-atomic geometries.
|
|
73
|
+
if geom.coords.size > 3:
|
|
74
|
+
H_mw = geom.eckart_projection(H_mw)
|
|
75
|
+
return H_mw, H, energy
|
|
76
|
+
|
|
77
|
+
delta = ds * vec
|
|
78
|
+
plus = geom.coords + delta
|
|
79
|
+
minus = geom.coords - delta
|
|
80
|
+
H_mw_plus, H_plus, energy_plus = get_H(geom, plus)
|
|
81
|
+
H_mw_minus, H_minus, energy_minus = get_H(geom, minus)
|
|
82
|
+
G_vec = (H_mw_plus - H_mw_minus) / (2 * ds)
|
|
83
|
+
|
|
84
|
+
third_deriv_res = ThirdDerivResult(
|
|
85
|
+
coords_plus=plus,
|
|
86
|
+
energy_plus=energy_plus,
|
|
87
|
+
H_plus=H_plus,
|
|
88
|
+
coords_minus=minus,
|
|
89
|
+
energy_minus=energy_minus,
|
|
90
|
+
H_minus=H_minus,
|
|
91
|
+
G_vec=G_vec,
|
|
92
|
+
vec=vec,
|
|
93
|
+
ds=ds,
|
|
94
|
+
)
|
|
95
|
+
return G_vec, third_deriv_res
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def cubic_displ(H, v0, w0, Gv, dE):
|
|
99
|
+
"""
|
|
100
|
+
According to Eq. (26) in [2] v1 does not depend on the sign of v0.
|
|
101
|
+
v1 = (F0 - 2v0^T F0 v0 I)⁻¹ x ([G0v0] - v0^T [G0v0] v0 I) v0
|
|
102
|
+
The first term is obviously independent of v0's sign. Using v0' = -v0 the
|
|
103
|
+
second term becomes
|
|
104
|
+
([G0v0'] - v0'^T [G0v0'] v0' I) v0'
|
|
105
|
+
(-[G0v0] - v0^T [G0v0'] v0 I) v0'
|
|
106
|
+
(-[G0v0] + v0^T [G0v0] v0 I) v0'
|
|
107
|
+
-(-[G0v0] + v0^T [G0v0] v0 I) v0
|
|
108
|
+
([G0v0] - v0^T [G0v0] v0 I) v0
|
|
109
|
+
Strictly speaking the contraction [G0v0] should depend on the sign of v0.
|
|
110
|
+
In the current implementation, the direction of v0 is not taken into account,
|
|
111
|
+
but
|
|
112
|
+
get_curv_vec(H, Gv, v0, w0) == get_curv_vec(H, -Gv, -v0, w0) .
|
|
113
|
+
But somehow the Taylor expansion gives bogus results when called with -Gv and -v0...
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
assert dE < 0.0, f"Supplied dE={dE:.6f} is positive but it must be negative!"
|
|
117
|
+
assert w0 < 0.0, f"Expected first eigenvalue to be negative but it is w0={w0:.6e}!"
|
|
118
|
+
|
|
119
|
+
v1 = get_curv_vec(H, Gv, v0, w0)
|
|
120
|
+
E_taylor = taylor_closure(H, Gv, v0, v1, w0)
|
|
121
|
+
|
|
122
|
+
def func(ds):
|
|
123
|
+
return E_taylor(ds) - dE
|
|
124
|
+
|
|
125
|
+
def prepare_bisect(x0, theta=1.25, max_cycles=20):
|
|
126
|
+
"""Determine (lower) upper bound for scipy.optimize.bisect."""
|
|
127
|
+
assert theta > 1.0
|
|
128
|
+
|
|
129
|
+
ds = x0
|
|
130
|
+
dE_min = func(0.0)
|
|
131
|
+
dE_prev = dE_min
|
|
132
|
+
ds_min = ds
|
|
133
|
+
# Grow until we find an upper (lower) bound of the interval
|
|
134
|
+
for _ in range(max_cycles):
|
|
135
|
+
dE = func(ds)
|
|
136
|
+
# print(
|
|
137
|
+
# f"ds={ds:.4f} dE={dE*AU2KJPERMOL:.3f} dE_min={dE_min*AU2KJPERMOL:.3f}"
|
|
138
|
+
# )
|
|
139
|
+
|
|
140
|
+
if dE <= 0.0:
|
|
141
|
+
break
|
|
142
|
+
# Keep best guess
|
|
143
|
+
elif dE <= dE_min:
|
|
144
|
+
dE_min = dE
|
|
145
|
+
ds_min = ds
|
|
146
|
+
# Return (yet) best guess when function value grows again
|
|
147
|
+
elif dE >= dE_prev:
|
|
148
|
+
ds = ds_min
|
|
149
|
+
break
|
|
150
|
+
|
|
151
|
+
dE_prev = dE
|
|
152
|
+
ds *= theta # Grow ds
|
|
153
|
+
return ds
|
|
154
|
+
|
|
155
|
+
def bisect_(ds0):
|
|
156
|
+
try:
|
|
157
|
+
ds_, rr = bisect(func, a=0.0, b=ds0, full_output=True)
|
|
158
|
+
# Will be raised when f(a) and f(b) have the same sign.
|
|
159
|
+
except ValueError:
|
|
160
|
+
ds_ = ds0
|
|
161
|
+
return ds_
|
|
162
|
+
|
|
163
|
+
plus_bound = prepare_bisect(0.1)
|
|
164
|
+
ds_plus = bisect_(plus_bound)
|
|
165
|
+
|
|
166
|
+
minus_bound = prepare_bisect(-0.1)
|
|
167
|
+
ds_minus = bisect_(minus_bound)
|
|
168
|
+
|
|
169
|
+
# import matplotlib.pyplot as plt
|
|
170
|
+
# dss = np.linspace(-1, 1, num=51)
|
|
171
|
+
# # dss = np.linspace(-6, 6, num=200)
|
|
172
|
+
# E0 = E_taylor(0.0)
|
|
173
|
+
# Es = E_taylor(dss) - E0
|
|
174
|
+
# Es *= AU2KJPERMOL
|
|
175
|
+
# Emp = (np.array((E_taylor(ds_minus), E_taylor(ds_plus))) - E0) * AU2KJPERMOL
|
|
176
|
+
# _, ax = plt.subplots()
|
|
177
|
+
# ax.plot(dss, Es, "o-")
|
|
178
|
+
# ax.axvline(0.0, c="k", ls="--")
|
|
179
|
+
# ax.axhline(dE*AU2KJPERMOL, c="k", ls=":")
|
|
180
|
+
# ax.scatter((ds_minus, ds_plus), Emp, s=75, marker="x", c="r", zorder=3)
|
|
181
|
+
# ax.set_xlabel("ds")
|
|
182
|
+
# ax.set_ylabel("dE / kJ mol⁻¹")
|
|
183
|
+
# plt.show()
|
|
184
|
+
|
|
185
|
+
def step(ds):
|
|
186
|
+
return ds * v0 + ds ** 2 * v1 / 2
|
|
187
|
+
|
|
188
|
+
step_plus = step(ds_plus)
|
|
189
|
+
step_minus = step(ds_minus)
|
|
190
|
+
return step_plus, step_minus
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def cubic_displ_for_h5(h5_fn="third_deriv.h5", dE=-5e-4):
|
|
194
|
+
with h5py.File(h5_fn, "r") as handle:
|
|
195
|
+
coords3d = handle["coords3d"][:]
|
|
196
|
+
if coords3d.size > 3:
|
|
197
|
+
H = handle["H_proj"][:]
|
|
198
|
+
else:
|
|
199
|
+
H = handle["H_mw"][:]
|
|
200
|
+
Gv = handle["G_vec"][:]
|
|
201
|
+
|
|
202
|
+
w, v = np.linalg.eigh(H)
|
|
203
|
+
w0 = w[0]
|
|
204
|
+
v0 = v[:, 0]
|
|
205
|
+
return cubic_displ(H, v0, w0, Gv, dE)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def cubic_displ_for_geom(geom, dE=-5e-4):
|
|
209
|
+
H = geom.mw_hessian
|
|
210
|
+
# Only project for multi-atomic geometries.
|
|
211
|
+
if geom.coords.size > 3:
|
|
212
|
+
H = geom.eckart_projection(H)
|
|
213
|
+
w, v = np.linalg.eigh(H)
|
|
214
|
+
# Transition vector (imaginary mode) and corresponding eigenvalue
|
|
215
|
+
v0 = v[:, 0]
|
|
216
|
+
w0 = w[0]
|
|
217
|
+
Gv, third_deriv_res = third_deriv_fd(geom, v0)
|
|
218
|
+
step_plus, step_minus = cubic_displ(H, v0, w0, Gv, dE=dE)
|
|
219
|
+
return step_plus, step_minus, third_deriv_res
|
pysisyphus/linalg.py
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
from math import cos, sin, sqrt
|
|
2
|
+
from typing import Callable, Literal, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
from scipy.spatial.transform import Rotation
|
|
7
|
+
from scipy.linalg.lapack import dpstrf
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def gram_schmidt(vecs, thresh=1e-8):
|
|
11
|
+
def proj(v1, v2):
|
|
12
|
+
return v1.dot(v2) / v1.dot(v1)
|
|
13
|
+
|
|
14
|
+
ortho = [
|
|
15
|
+
vecs[0],
|
|
16
|
+
]
|
|
17
|
+
for v1 in vecs[1:]:
|
|
18
|
+
tmp = v1.copy()
|
|
19
|
+
for v2 in ortho:
|
|
20
|
+
tmp -= proj(v2, v1) * v2
|
|
21
|
+
norm = np.linalg.norm(tmp)
|
|
22
|
+
# Don't append linear dependent vectors, as their norm will be
|
|
23
|
+
# near zero. Renormalizing them to unity would lead to numerical
|
|
24
|
+
# garbage and to erronous results later on, when we orthgonalize
|
|
25
|
+
# against this 'arbitrary' vector.
|
|
26
|
+
if norm <= thresh:
|
|
27
|
+
continue
|
|
28
|
+
ortho.append(tmp / norm)
|
|
29
|
+
return np.array(ortho)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def orthogonalize_against(mat, vecs, max_cycles=5, thresh=1e-10):
|
|
33
|
+
"""Orthogonalize rows of 'mat' against rows in 'vecs'
|
|
34
|
+
|
|
35
|
+
Returns a (modified) copy of mat.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
omat = mat.copy()
|
|
39
|
+
for _ in range(max_cycles):
|
|
40
|
+
max_overlap = 0.0
|
|
41
|
+
for row in omat:
|
|
42
|
+
for ovec in vecs:
|
|
43
|
+
row -= ovec.dot(row) * ovec
|
|
44
|
+
|
|
45
|
+
# Remaining overlap
|
|
46
|
+
overlap = np.sum(np.dot(vecs, row))
|
|
47
|
+
max_overlap = max(overlap, max_overlap)
|
|
48
|
+
|
|
49
|
+
if max_overlap < thresh:
|
|
50
|
+
break
|
|
51
|
+
else:
|
|
52
|
+
raise Exception(f"Orthogonalization did not succeed in {max_cycles} cycles!")
|
|
53
|
+
|
|
54
|
+
return omat
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def perp_comp(vec, along):
|
|
58
|
+
"""Return the perpendicular component of vec along along."""
|
|
59
|
+
return vec - vec.dot(along) * along
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def make_unit_vec(vec1, vec2):
|
|
63
|
+
"""Return unit vector pointing from vec2 to vec1."""
|
|
64
|
+
diff = vec1 - vec2
|
|
65
|
+
return diff / np.linalg.norm(diff)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def svd_inv(array, thresh, hermitian=False):
|
|
69
|
+
U, S, Vt = np.linalg.svd(array, hermitian=hermitian)
|
|
70
|
+
keep = S > thresh
|
|
71
|
+
S_inv = np.zeros_like(S)
|
|
72
|
+
S_inv[keep] = 1 / S[keep]
|
|
73
|
+
return Vt.T.dot(np.diag(S_inv)).dot(U.T)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_rot_mat(abc=None):
|
|
77
|
+
# Euler angles
|
|
78
|
+
if abc is None:
|
|
79
|
+
abc = np.random.rand(3) * np.pi * 2
|
|
80
|
+
a, b, c = abc
|
|
81
|
+
R = np.array(
|
|
82
|
+
(
|
|
83
|
+
(
|
|
84
|
+
cos(a) * cos(b) * cos(c) - sin(a) * sin(c),
|
|
85
|
+
-cos(a) * cos(b) * sin(c) - sin(a) * cos(c),
|
|
86
|
+
cos(a) * sin(b),
|
|
87
|
+
),
|
|
88
|
+
(
|
|
89
|
+
sin(a) * cos(b) * cos(c) + cos(a) * sin(c),
|
|
90
|
+
-sin(a) * cos(b) * sin(c) + cos(a) * cos(c),
|
|
91
|
+
sin(a) * sin(b),
|
|
92
|
+
),
|
|
93
|
+
(-sin(b) * cos(c), sin(b) * sin(c), cos(b)),
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
return R
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_rot_mat_for_coords(coords3d_1, coords3d_2):
|
|
100
|
+
coords3d_1 = coords3d_1.copy()
|
|
101
|
+
coords3d_2 = coords3d_2.copy()
|
|
102
|
+
centroid_1 = coords3d_1.mean(axis=0)
|
|
103
|
+
centroid_2 = coords3d_2.mean(axis=0)
|
|
104
|
+
coords3d_1 -= centroid_1[None, :]
|
|
105
|
+
coords3d_2 -= centroid_2[None, :]
|
|
106
|
+
|
|
107
|
+
tmp = coords3d_2.T.dot(coords3d_1)
|
|
108
|
+
U, W, Vt = np.linalg.svd(tmp)
|
|
109
|
+
rot_mat = U.dot(Vt)
|
|
110
|
+
if np.linalg.det(rot_mat) < 0:
|
|
111
|
+
U[:, -1] *= -1
|
|
112
|
+
rot_mat = U.dot(Vt)
|
|
113
|
+
return coords3d_1, coords3d_2, rot_mat
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def eigvec_grad(w, v, ind, mat_grad):
|
|
117
|
+
"""Gradient of 'ind'-th eigenvector.
|
|
118
|
+
|
|
119
|
+
dv_i / dx_i = (w_i*I - mat)⁻¹ dmat/dx_i v_i
|
|
120
|
+
"""
|
|
121
|
+
eigval = w[ind]
|
|
122
|
+
eigvec = v[:, ind]
|
|
123
|
+
|
|
124
|
+
w_diff = eigval - w
|
|
125
|
+
w_inv = np.divide(
|
|
126
|
+
1.0, w_diff, out=np.zeros_like(w_diff).astype(float), where=w_diff != 0.0
|
|
127
|
+
)
|
|
128
|
+
assert np.isfinite(w_inv).all()
|
|
129
|
+
pinv = v.dot(np.diag(w_inv)).dot(v.T)
|
|
130
|
+
pinv.dot(mat_grad)
|
|
131
|
+
|
|
132
|
+
wh = pinv.dot(mat_grad).dot(eigvec)
|
|
133
|
+
return wh
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def cross3(a, b):
|
|
137
|
+
"""10x as fast as np.cross for two 1d arrays of size 3."""
|
|
138
|
+
return np.array(
|
|
139
|
+
(
|
|
140
|
+
a[1] * b[2] - a[2] * b[1],
|
|
141
|
+
a[2] * b[0] - a[0] * b[2],
|
|
142
|
+
a[0] * b[1] - a[1] * b[0],
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def norm3(a):
|
|
148
|
+
"""5x as fas as np.linalg.norm for a 1d array of size 3."""
|
|
149
|
+
return sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2])
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def finite_difference_hessian(
|
|
153
|
+
coords: NDArray[float],
|
|
154
|
+
grad_func: Callable[[NDArray[float]], NDArray[float]],
|
|
155
|
+
step_size: float = 1e-2,
|
|
156
|
+
acc: Literal[2, 4] = 2,
|
|
157
|
+
callback: Optional[Callable] = None,
|
|
158
|
+
) -> NDArray[float]:
|
|
159
|
+
"""Numerical Hessian from central finite gradient differences.
|
|
160
|
+
|
|
161
|
+
See central differences in
|
|
162
|
+
https://en.wikipedia.org/wiki/Finite_difference_coefficient
|
|
163
|
+
for the different accuracies.
|
|
164
|
+
"""
|
|
165
|
+
if callback is None:
|
|
166
|
+
|
|
167
|
+
def callback(*args):
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
accuracies = {
|
|
171
|
+
2: ((-0.5, -1), (0.5, 1)), # 2 calculations
|
|
172
|
+
4: ((1 / 12, -2), (-2 / 3, -1), (2 / 3, 1), (-1 / 12, 2)), # 4 calculations
|
|
173
|
+
}
|
|
174
|
+
accs_avail = list(accuracies.keys())
|
|
175
|
+
assert acc in accs_avail
|
|
176
|
+
|
|
177
|
+
size = coords.size
|
|
178
|
+
fd_hessian = np.zeros((size, size))
|
|
179
|
+
zero_step = np.zeros(size)
|
|
180
|
+
|
|
181
|
+
coeffs = accuracies[acc]
|
|
182
|
+
for i, _ in enumerate(coords):
|
|
183
|
+
step = zero_step.copy()
|
|
184
|
+
step[i] = step_size
|
|
185
|
+
|
|
186
|
+
def get_grad(factor, displ, j):
|
|
187
|
+
displ_coords = coords + step * displ
|
|
188
|
+
callback(i, j)
|
|
189
|
+
grad = grad_func(displ_coords)
|
|
190
|
+
return factor * grad
|
|
191
|
+
|
|
192
|
+
grads = [get_grad(factor, displ, j) for j, (factor, displ) in enumerate(coeffs)]
|
|
193
|
+
fd = np.sum(grads, axis=0) / step_size
|
|
194
|
+
fd_hessian[i] = fd
|
|
195
|
+
|
|
196
|
+
# Symmetrize
|
|
197
|
+
fd_hessian = (fd_hessian + fd_hessian.T) / 2
|
|
198
|
+
return fd_hessian
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def rot_quaternion(coords3d, ref_coords3d):
|
|
202
|
+
# Translate to origin by removing centroid
|
|
203
|
+
c3d = coords3d - coords3d.mean(axis=0)
|
|
204
|
+
ref_c3d = ref_coords3d - ref_coords3d.mean(axis=0)
|
|
205
|
+
|
|
206
|
+
# Setup correlation matrix
|
|
207
|
+
R = c3d.T.dot(ref_c3d)
|
|
208
|
+
|
|
209
|
+
# Setup F matrix, Eq. (6) in [1]
|
|
210
|
+
F = np.zeros((4, 4))
|
|
211
|
+
R11, R12, R13, R21, R22, R23, R31, R32, R33 = R.flatten()
|
|
212
|
+
# Fill only upper triangular part.
|
|
213
|
+
F[0, 0] = R11 + R22 + R33
|
|
214
|
+
F[0, 1] = R23 - R32
|
|
215
|
+
F[0, 2] = R31 - R13
|
|
216
|
+
F[0, 3] = R12 - R21
|
|
217
|
+
#
|
|
218
|
+
F[1, 1] = R11 - R22 - R33
|
|
219
|
+
F[1, 2] = R12 + R21
|
|
220
|
+
F[1, 3] = R13 + R31
|
|
221
|
+
#
|
|
222
|
+
F[2, 2] = -R11 + R22 - R33
|
|
223
|
+
F[2, 3] = R23 + R32
|
|
224
|
+
#
|
|
225
|
+
F[3, 3] = -R11 - R22 + R33
|
|
226
|
+
|
|
227
|
+
# Eigenvalues, eigenvectors of upper triangular part.
|
|
228
|
+
w, v_ = np.linalg.eigh(F, UPLO="U")
|
|
229
|
+
|
|
230
|
+
# Quaternion corresponds to biggest (last) eigenvalue.
|
|
231
|
+
# np.linalg.eigh already returns sorted eigenvalues.
|
|
232
|
+
return w, v_, c3d, ref_c3d
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def quaternion_to_rot_mat(q):
|
|
236
|
+
q0, q1, q2, q3 = q
|
|
237
|
+
q_ = q0**2 - (q1**2 + q2**2 + q3**2)
|
|
238
|
+
R = np.zeros((3, 3))
|
|
239
|
+
R[0, 0] = q_ + 2 * q1**2
|
|
240
|
+
R[0, 1] = 2 * (q1 * q2 - q0 * q3)
|
|
241
|
+
R[0, 2] = 2 * (q1 * q3 - q0 * q2)
|
|
242
|
+
R[1, 0] = 2 * (q1 * q2 + q0 * q3)
|
|
243
|
+
R[1, 1] = q_ + 2 * q2**2
|
|
244
|
+
R[1, 2] = 2 * (q2 * q3 - q0 * q1)
|
|
245
|
+
R[2, 0] = 2 * (q1 * q3 - q0 * q2)
|
|
246
|
+
R[2, 1] = 2 * (q2 * q3 + q0 * q1)
|
|
247
|
+
R[2, 2] = q_ + 2 * q3**2
|
|
248
|
+
return R
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def rmsd_grad(
|
|
252
|
+
coords3d: NDArray[float], ref_coords3d: NDArray[float], offset: float = 1e-9
|
|
253
|
+
) -> Tuple[float, NDArray[float]]:
|
|
254
|
+
"""RMSD and gradient between two sets of coordinates from quaternions.
|
|
255
|
+
|
|
256
|
+
The gradient is given w.r.t. the coordinates of 'coords3d'.
|
|
257
|
+
|
|
258
|
+
Python adaption of
|
|
259
|
+
ls_rmsd.f90
|
|
260
|
+
from the xtb repository of the Grimme group, which in turn implements
|
|
261
|
+
[1] https://doi.org/10.1002/jcc.20110
|
|
262
|
+
.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
coords3d
|
|
267
|
+
Coordinate array of shape (N, 3) with N denoting the number of atoms.
|
|
268
|
+
ref_coords3d
|
|
269
|
+
Reference coordinates.
|
|
270
|
+
offset
|
|
271
|
+
Small floating-point number that is added to the RMSD, to avoid division
|
|
272
|
+
by zero.
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
rmsd
|
|
277
|
+
RMSD value.
|
|
278
|
+
rmsd_grad
|
|
279
|
+
Gradient of the RMSD value w.r.t. to 'coords3d'.
|
|
280
|
+
"""
|
|
281
|
+
assert coords3d.shape == ref_coords3d.shape
|
|
282
|
+
|
|
283
|
+
w, v_, c3d, ref_c3d = rot_quaternion(coords3d, ref_coords3d)
|
|
284
|
+
quat = v_[:, -1]
|
|
285
|
+
eigval = w[-1]
|
|
286
|
+
|
|
287
|
+
atom_num = coords3d.shape[0]
|
|
288
|
+
x_norm = np.linalg.norm(c3d) ** 2
|
|
289
|
+
y_norm = np.linalg.norm(ref_c3d) ** 2
|
|
290
|
+
rmsd = np.sqrt(max(0.0, ((x_norm + y_norm) - 2.0 * eigval)) / (atom_num)) + offset
|
|
291
|
+
|
|
292
|
+
# scipy expects the quaternion
|
|
293
|
+
# a + b*i + c*j + d*k
|
|
294
|
+
# as (i, j, k, a), that is the scalar component must come last.
|
|
295
|
+
# Currently we have (a, i, j, k), so we have to rearrange before passing the
|
|
296
|
+
# quaternion to scipy.
|
|
297
|
+
rot = Rotation.from_quat((*quat[1:], quat[0]))
|
|
298
|
+
U = rot.as_matrix()
|
|
299
|
+
|
|
300
|
+
grad = (c3d - ref_c3d @ U) / (rmsd * atom_num)
|
|
301
|
+
return rmsd, grad
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def pivoted_cholesky(A: NDArray, tol: float = -1.0):
|
|
305
|
+
"""Cholesky factorization a real symmetric positive semidefinite matrix.
|
|
306
|
+
Cholesky factorization is carried out with full pivoting.
|
|
307
|
+
|
|
308
|
+
Adapated from PySCF.
|
|
309
|
+
|
|
310
|
+
P.T * A * P = L * L.T
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
A
|
|
315
|
+
Matrix to be factorized.
|
|
316
|
+
tol
|
|
317
|
+
User defined tolerance, as outlined in the LAPACK docs.
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
L
|
|
322
|
+
Lower or upper triangular matrix.
|
|
323
|
+
piv
|
|
324
|
+
Pivot vectors, starting at 0.
|
|
325
|
+
rank
|
|
326
|
+
Rank of the factoirzed matrix.
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
N = A.shape[0]
|
|
330
|
+
L, piv, rank, info = dpstrf(A, tol=tol, lower=True)
|
|
331
|
+
piv -= 1 # LAPACK returns 1-based indices
|
|
332
|
+
assert info >= 0
|
|
333
|
+
L[np.triu_indices(N, k=1)] = 0
|
|
334
|
+
L[:, rank:] = 0
|
|
335
|
+
return L, piv, rank
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def pivoted_cholesky2(A: NDArray[float], tol: Optional[float] = None, m_max: int = 0):
|
|
339
|
+
"""https://doi.org/10.1016/j.apnum.2011.10.001"""
|
|
340
|
+
|
|
341
|
+
R = np.zeros_like(A)
|
|
342
|
+
m = 0
|
|
343
|
+
n = len(A)
|
|
344
|
+
# Decompose to max rank.
|
|
345
|
+
if tol is None:
|
|
346
|
+
tol = 0.0
|
|
347
|
+
assert tol >= 0.0, f"{tol=} must be >= 0.0!"
|
|
348
|
+
if m_max == 0:
|
|
349
|
+
m_max = n
|
|
350
|
+
assert 0 <= m_max <= n, f"{m_max=} must fullfil (0 <= m_max <= {n=})!"
|
|
351
|
+
d = np.diag(A).copy() # Diagonal
|
|
352
|
+
error = np.linalg.norm(A, ord="nuc")
|
|
353
|
+
piv = np.arange(n, dtype=int)
|
|
354
|
+
while True:
|
|
355
|
+
# Stop when error is sufficiently small or we are at max rank.
|
|
356
|
+
# while (error > tol) or (m_max and m < m_max):
|
|
357
|
+
if (error <= tol) or (m_max and (m == m_max)):
|
|
358
|
+
break
|
|
359
|
+
i = m + d[piv[m:]].argmax()
|
|
360
|
+
piv[m], piv[i] = piv[i], piv[m]
|
|
361
|
+
R[m, piv[m]] = np.sqrt(d[piv[m]])
|
|
362
|
+
for i in range(m + 1, n):
|
|
363
|
+
j = np.arange(m)
|
|
364
|
+
sum_ = (R[j, piv[m]] * R[j, piv[i]]).sum()
|
|
365
|
+
R[m, piv[i]] = (A[piv[m], piv[i]] - sum_) / R[m, piv[m]]
|
|
366
|
+
d[piv[i]] -= R[m, piv[i]] ** 2
|
|
367
|
+
error = d[m + 1 :].sum()
|
|
368
|
+
m += 1
|
|
369
|
+
# R.T @ R == A
|
|
370
|
+
return R, piv, m
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def matrix_power(mat, p, thresh=1e-12, strict=True):
|
|
374
|
+
w, v = np.linalg.eigh(mat)
|
|
375
|
+
assert (not strict) or (
|
|
376
|
+
w > 0.0
|
|
377
|
+
).all(), "matrix_power must be called with a (semi)-positive-definite matrix!"
|
|
378
|
+
mask = np.abs(w) > thresh
|
|
379
|
+
w_pow = w[mask] ** p
|
|
380
|
+
v_mask = v[:, mask]
|
|
381
|
+
return v_mask @ np.diag(w_pow) @ v_mask.T
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def sym_mat_from_triu(arr, data):
|
|
385
|
+
nrows, ncols = arr.shape
|
|
386
|
+
assert nrows == ncols
|
|
387
|
+
triu = np.triu_indices(nrows)
|
|
388
|
+
tril1 = np.tril_indices(nrows, k=-1)
|
|
389
|
+
arr[triu] = data
|
|
390
|
+
arr[tril1] = arr.T[tril1]
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def sym_mat_from_tril(arr, data):
|
|
394
|
+
nrows, ncols = arr.shape
|
|
395
|
+
assert nrows == ncols
|
|
396
|
+
tril = np.tril_indices(nrows)
|
|
397
|
+
arr[tril] = data
|
|
398
|
+
triu1 = np.triu_indices(nrows, k=1)
|
|
399
|
+
arr[triu1] = arr.T[triu1]
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def multi_component_sym_mat(arr, dim):
|
|
403
|
+
target_shape = (dim, dim)
|
|
404
|
+
shape = arr.shape
|
|
405
|
+
sym = np.zeros((*target_shape, *shape[1:]))
|
|
406
|
+
triu = np.triu_indices(dim)
|
|
407
|
+
triu1 = np.triu_indices(dim, k=1)
|
|
408
|
+
tril1 = np.tril_indices(dim, k=-1)
|
|
409
|
+
sym[triu] = arr
|
|
410
|
+
sym[tril1] = sym[triu1]
|
|
411
|
+
return sym.reshape(*target_shape, *shape[1:])
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from pysisyphus.line_searches.LineSearch import (
|
|
2
|
+
LineSearch,
|
|
3
|
+
LineSearchNotConverged,
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
from pysisyphus.line_searches.interpol import interpol_alpha_quad, interpol_alpha_cubic
|
|
7
|
+
from pysisyphus.optimizers.poly_fit import cubic_fit, quartic_fit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Backtracking(LineSearch):
|
|
11
|
+
def __init__(self, *args, rho_lo=5e-2, rho_hi=0.9, use_grad=False, **kwargs):
|
|
12
|
+
"""Backtracking line search enforcing Armijo conditions.
|
|
13
|
+
|
|
14
|
+
Uses only energy evaluations.
|
|
15
|
+
|
|
16
|
+
See [1], Chapter 3, Line Search methods, Section 3.1 p. 31 and
|
|
17
|
+
Section 3.5 p. 56."""
|
|
18
|
+
|
|
19
|
+
kwargs["cond"] = "armijo"
|
|
20
|
+
super().__init__(*args, **kwargs)
|
|
21
|
+
|
|
22
|
+
self.rho_lo = float(rho_lo)
|
|
23
|
+
self.rho_hi = float(rho_hi)
|
|
24
|
+
self.use_grad = use_grad
|
|
25
|
+
|
|
26
|
+
def alpha_new_from_phi(self, cycle, phi0, dphi0, alpha, alpha_prev):
|
|
27
|
+
phi_i = self.get_phi_dphi("f", alpha)
|
|
28
|
+
self.log(f"\tCycle {cycle:02d}: alpha={alpha:.6f}, ϕ={phi_i:.6f} au")
|
|
29
|
+
|
|
30
|
+
if cycle == 0:
|
|
31
|
+
# Quadratic interpolation
|
|
32
|
+
alpha_new = interpol_alpha_quad(phi0, dphi0, phi_i, alpha)
|
|
33
|
+
type_ = "quadratic"
|
|
34
|
+
else:
|
|
35
|
+
# Cubic interpolation
|
|
36
|
+
phi_prev = self.get_phi_dphi("f", alpha_prev)
|
|
37
|
+
alpha_new = interpol_alpha_cubic(
|
|
38
|
+
phi0, dphi0, phi_prev, phi_i, alpha_prev, alpha
|
|
39
|
+
)
|
|
40
|
+
type_ = "cubic"
|
|
41
|
+
return alpha_new, type_
|
|
42
|
+
|
|
43
|
+
def alpha_new_from_phi_dphi(self, cycle, phi0, dphi0, alpha):
|
|
44
|
+
phi_i, dphi_i = self.get_phi_dphi("fg", alpha)
|
|
45
|
+
self.log(f"\tCycle {cycle:02d}: α={alpha:.6f}, ϕ={phi_i:.6f} au")
|
|
46
|
+
|
|
47
|
+
# First we try a constrained quartic polynomial
|
|
48
|
+
res = quartic_fit(phi0, phi_i, dphi0, dphi_i)
|
|
49
|
+
type_ = "quartic"
|
|
50
|
+
# If the quartic poly failed, we continue with a cubic polynomial
|
|
51
|
+
if res is None:
|
|
52
|
+
res = cubic_fit(phi0, phi_i, dphi0, dphi_i)
|
|
53
|
+
type_ = "cubic"
|
|
54
|
+
# If the cubic poly failed we resort to bisection. ohoh
|
|
55
|
+
if res is None:
|
|
56
|
+
alpha_new = 0.5 * alpha
|
|
57
|
+
type_ = "bisection"
|
|
58
|
+
else:
|
|
59
|
+
alpha_new = res.x * alpha
|
|
60
|
+
return alpha_new, type_
|
|
61
|
+
|
|
62
|
+
def run_line_search(self):
|
|
63
|
+
phi0, dphi0 = self.get_phi_dphi("fg", 0)
|
|
64
|
+
|
|
65
|
+
alpha_prev = None
|
|
66
|
+
alpha = self.alpha_init
|
|
67
|
+
for i in range(self.max_cycles):
|
|
68
|
+
if self.use_grad:
|
|
69
|
+
alpha_new, type_ = self.alpha_new_from_phi_dphi(i, phi0, dphi0, alpha)
|
|
70
|
+
else:
|
|
71
|
+
alpha_new, type_ = self.alpha_new_from_phi(i, phi0, dphi0, alpha, alpha_prev)
|
|
72
|
+
self.log(f"\tNew α from {type_} interpolation: {alpha_new:.6f}")
|
|
73
|
+
|
|
74
|
+
lower_bound = alpha * self.rho_lo
|
|
75
|
+
upper_bound = alpha * self.rho_hi
|
|
76
|
+
if alpha_new < lower_bound:
|
|
77
|
+
self.log("\tNew α is too small!")
|
|
78
|
+
if alpha_new > upper_bound:
|
|
79
|
+
self.log("\tNew α is too big!")
|
|
80
|
+
|
|
81
|
+
# Assert that alpha doesn't change too much compared to the previous alpha
|
|
82
|
+
alpha_new = min(alpha_new, upper_bound)
|
|
83
|
+
alpha_new = max(alpha_new, lower_bound)
|
|
84
|
+
alpha_prev = alpha
|
|
85
|
+
alpha = alpha_new
|
|
86
|
+
self.log(f"\tNext α: {alpha:.6f}\n")
|
|
87
|
+
|
|
88
|
+
raise LineSearchNotConverged
|