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,199 @@
|
|
|
1
|
+
# [1] https://aip.scitation.org/doi/pdf/10.1063/1.1523908
|
|
2
|
+
# Neugebauer, Reiher 2002
|
|
3
|
+
# [2] https://reiher.ethz.ch/software/akira.html
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from collections import namedtuple
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from pysisyphus.helpers_pure import eigval_to_wavenumber
|
|
12
|
+
from pysisyphus.Geometry import get_trans_rot_projector
|
|
13
|
+
from pysisyphus.modefollow.NormalMode import NormalMode
|
|
14
|
+
from pysisyphus.TablePrinter import TablePrinter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
DavidsonResult = namedtuple(
|
|
18
|
+
"DavidsonResult",
|
|
19
|
+
"cur_cycle converged final_modes qs nus mode_inds res_rms",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def forces_fin_diff(forces_getter, coords, b, step_size):
|
|
24
|
+
plus = forces_getter(coords + b)
|
|
25
|
+
minus = forces_getter(coords - b)
|
|
26
|
+
fd = (minus - plus) / (2 * step_size)
|
|
27
|
+
return fd
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def block_davidson(
|
|
31
|
+
cart_coords,
|
|
32
|
+
masses,
|
|
33
|
+
forces_getter,
|
|
34
|
+
guess_modes,
|
|
35
|
+
lowest=None,
|
|
36
|
+
trial_step_size=0.01,
|
|
37
|
+
hessian_precon=None,
|
|
38
|
+
max_cycles=25,
|
|
39
|
+
res_rms_thresh=1e-4,
|
|
40
|
+
start_precon=5,
|
|
41
|
+
remove_trans_rot=True,
|
|
42
|
+
print_level=1,
|
|
43
|
+
):
|
|
44
|
+
num = len(guess_modes)
|
|
45
|
+
B_full = np.zeros((len(guess_modes[0]), num * max_cycles))
|
|
46
|
+
S_full = np.zeros_like(B_full)
|
|
47
|
+
I = np.eye(cart_coords.size)
|
|
48
|
+
masses_rep = np.repeat(masses, 3)
|
|
49
|
+
msqrt = np.sqrt(masses_rep)
|
|
50
|
+
|
|
51
|
+
# Projector to remove translation and rotation
|
|
52
|
+
P = get_trans_rot_projector(cart_coords, masses, full=True)
|
|
53
|
+
guess_modes = [
|
|
54
|
+
NormalMode(P.dot(mode.l_mw) / msqrt, masses_rep) for mode in guess_modes
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
col_fmts = "int int int float_short float str".split()
|
|
58
|
+
header = ("#", "subspace size", "mode", "ṽ / cm⁻¹", "rms(r)", "Conv")
|
|
59
|
+
fmts_update = {"float_short": "{: >11.2f}"}
|
|
60
|
+
table = TablePrinter(header, col_fmts, width=11, fmts_update=fmts_update)
|
|
61
|
+
if print_level == 1:
|
|
62
|
+
table.print_header()
|
|
63
|
+
|
|
64
|
+
b_prev = np.array([mode.l_mw for mode in guess_modes]).T
|
|
65
|
+
for i in range(max_cycles):
|
|
66
|
+
# Add new basis vectors to B matrix
|
|
67
|
+
b = np.array([mode.l_mw for mode in guess_modes]).T
|
|
68
|
+
from_ = i * num
|
|
69
|
+
to_ = (i + 1) * num
|
|
70
|
+
B_full[:, from_:to_] = b
|
|
71
|
+
|
|
72
|
+
# Estimate action of Hessian by finite differences.
|
|
73
|
+
for j in range(num):
|
|
74
|
+
mode = guess_modes[j]
|
|
75
|
+
# Get a step size in mass-weighted coordinates that results
|
|
76
|
+
# in the desired 'trial_step_size' in not-mass-weighted coordinates.
|
|
77
|
+
mw_step_size = mode.mw_norm_for_norm(trial_step_size)
|
|
78
|
+
# Actual step in non-mass-weighted coordinates
|
|
79
|
+
step = trial_step_size * mode.l
|
|
80
|
+
S_full[:, from_ + j] = (
|
|
81
|
+
# Convert to mass-weighted coordinates
|
|
82
|
+
forces_fin_diff(forces_getter, cart_coords, step, mw_step_size)
|
|
83
|
+
/ msqrt
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Views on columns that are actually set
|
|
87
|
+
B = B_full[:, :to_]
|
|
88
|
+
S = S_full[:, :to_]
|
|
89
|
+
|
|
90
|
+
# Calculate and symmetrize approximate hessian
|
|
91
|
+
Hm = B.T.dot(S)
|
|
92
|
+
Hm = (Hm + Hm.T) / 2
|
|
93
|
+
# Diagonalize small Hessian
|
|
94
|
+
w, v = np.linalg.eigh(Hm)
|
|
95
|
+
|
|
96
|
+
# Approximations to exact eigenvectors in current cycle
|
|
97
|
+
approx_modes = (v * B[:, :, None]).sum(axis=1).T
|
|
98
|
+
# Calculate overlaps between previous root and the new approximate
|
|
99
|
+
# normal modes for root following.
|
|
100
|
+
if lowest is None:
|
|
101
|
+
# 2D overlap array. approx_modes in row, b_prev in columns.
|
|
102
|
+
overlaps = np.einsum("ij,jk->ik", approx_modes, b_prev)
|
|
103
|
+
mode_inds = np.abs(overlaps).argmax(axis=0)
|
|
104
|
+
else:
|
|
105
|
+
mode_inds = np.arange(lowest)
|
|
106
|
+
b_prev = approx_modes[mode_inds].T
|
|
107
|
+
|
|
108
|
+
# Eq. (7) in [1]
|
|
109
|
+
residues = (v * (S[:, :, None] - w * B[:, :, None])).sum(axis=1)
|
|
110
|
+
|
|
111
|
+
# Determine preconditioner matrix
|
|
112
|
+
#
|
|
113
|
+
# Use supplied matrix
|
|
114
|
+
if hessian_precon is not None:
|
|
115
|
+
precon_mat = hessian_precon
|
|
116
|
+
# Reconstruct Hessian, but only start after some cycles
|
|
117
|
+
elif i >= start_precon:
|
|
118
|
+
precon_mat = B.dot(Hm).dot(B.T)
|
|
119
|
+
# No preconditioning if no matrix was supplied or we are in an early cycle.
|
|
120
|
+
else:
|
|
121
|
+
precon_mat = None
|
|
122
|
+
|
|
123
|
+
# Construct new basis vector from residuum of selected mode
|
|
124
|
+
b = np.zeros_like(b_prev)
|
|
125
|
+
for j, mode_ind in enumerate(mode_inds):
|
|
126
|
+
r = residues[:, mode_ind]
|
|
127
|
+
if precon_mat is not None:
|
|
128
|
+
# Construct actual preconditioner X
|
|
129
|
+
X = np.linalg.pinv(precon_mat - w[mode_ind] * I, rcond=1e-8)
|
|
130
|
+
b[:, j] = X.dot(r)
|
|
131
|
+
else:
|
|
132
|
+
b[:, j] = r
|
|
133
|
+
|
|
134
|
+
# Project out translation and rotation from new mode guess
|
|
135
|
+
if remove_trans_rot:
|
|
136
|
+
b = P.dot(b)
|
|
137
|
+
# Orthogonalize new vectors against preset vectors
|
|
138
|
+
b = np.linalg.qr(np.concatenate((B, b), axis=1))[0][:, -num:]
|
|
139
|
+
|
|
140
|
+
# New NormalMode from non-mass-weighted displacements
|
|
141
|
+
guess_modes = [NormalMode(b_ / msqrt, masses_rep) for b_ in b.T]
|
|
142
|
+
|
|
143
|
+
# Calculate wavenumbers
|
|
144
|
+
nus = eigval_to_wavenumber(w)
|
|
145
|
+
|
|
146
|
+
# Check convergence criteria
|
|
147
|
+
max_res = np.abs(residues).max(axis=0)
|
|
148
|
+
res_rms = np.sqrt(np.mean(residues ** 2, axis=0))
|
|
149
|
+
|
|
150
|
+
converged = res_rms < res_rms_thresh
|
|
151
|
+
# Print progress if requested
|
|
152
|
+
if print_level == 2:
|
|
153
|
+
print(f"Cycle {i:02d}")
|
|
154
|
+
print("\t # | ṽ / cm⁻¹| rms(r) | max(|r|) ")
|
|
155
|
+
print("\t------------------------------------------")
|
|
156
|
+
for j, (nu, rms, mr) in enumerate(zip(nus, res_rms, max_res)):
|
|
157
|
+
sel_str = "*" if (j in mode_inds) else " "
|
|
158
|
+
conv_str = "✓" if converged[j] else ""
|
|
159
|
+
print(
|
|
160
|
+
f"\t{j:02d}{sel_str} | {nu:> 10.2f} | {rms:.8f} | {mr:.8f} {conv_str}"
|
|
161
|
+
)
|
|
162
|
+
print()
|
|
163
|
+
elif print_level == 1:
|
|
164
|
+
for j in mode_inds:
|
|
165
|
+
conv_str = "✓" if converged[j] else "✗"
|
|
166
|
+
table.print_row((i, B.shape[1], j, nus[j], res_rms[j], conv_str))
|
|
167
|
+
|
|
168
|
+
# Convergence is signalled using only the roots we are actually interested in
|
|
169
|
+
modes_converged = all(converged[mode_inds])
|
|
170
|
+
if modes_converged:
|
|
171
|
+
if print_level > 0:
|
|
172
|
+
print(f"\tDavidson procedure converged in {i+1} cycles!")
|
|
173
|
+
if lowest is not None:
|
|
174
|
+
nus_str = np.array2string(nus[mode_inds], precision=2)
|
|
175
|
+
print(f"\tLowest {lowest} wavenumbers: {nus_str} cm⁻¹")
|
|
176
|
+
neg_nus = sum(nus[mode_inds] < 0)
|
|
177
|
+
type_ = "minimum" if (neg_nus == 0) else f"saddle point of index {neg_nus}"
|
|
178
|
+
print(f"\tThis geometry seems to be a {type_} on the PES.")
|
|
179
|
+
break
|
|
180
|
+
sys.stdout.flush()
|
|
181
|
+
|
|
182
|
+
result = DavidsonResult(
|
|
183
|
+
cur_cycle=i,
|
|
184
|
+
converged=modes_converged,
|
|
185
|
+
final_modes=guess_modes,
|
|
186
|
+
qs=approx_modes,
|
|
187
|
+
nus=nus,
|
|
188
|
+
mode_inds=mode_inds,
|
|
189
|
+
res_rms=res_rms,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def geom_davidson(geom, *args, **kwargs):
|
|
196
|
+
def forces_getter(cart_coords):
|
|
197
|
+
return geom.get_energy_and_cart_forces_at(cart_coords)["forces"]
|
|
198
|
+
|
|
199
|
+
return block_davidson(geom.cart_coords, geom.masses, forces_getter, *args, **kwargs)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# [1] https://doi.org/10.1063/1.1809574
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def lanczos(coords, grad_getter, dx=5e-3, dl=1e-2, guess=None, max_cycles=25,
|
|
8
|
+
reortho=True, logger=None):
|
|
9
|
+
"""Lanczos method to determine smallest eigenvalue & -vector.
|
|
10
|
+
|
|
11
|
+
See [1] for description of algorithm.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def log(msg):
|
|
15
|
+
if logger is not None:
|
|
16
|
+
logger.debug(msg)
|
|
17
|
+
|
|
18
|
+
log("Lanczos Algorithm")
|
|
19
|
+
r_prev = guess
|
|
20
|
+
if r_prev is None:
|
|
21
|
+
r_prev = np.random.rand(coords.size)
|
|
22
|
+
beta_prev = np.linalg.norm(r_prev)
|
|
23
|
+
q_prev = np.zeros_like(r_prev)
|
|
24
|
+
|
|
25
|
+
alphas = list()
|
|
26
|
+
betas = list()
|
|
27
|
+
w_mins = list()
|
|
28
|
+
Q = np.zeros((coords.size, max_cycles))
|
|
29
|
+
qs = list()
|
|
30
|
+
# Gradient at current coordinates; does not change.
|
|
31
|
+
grad_l = grad_getter(coords)
|
|
32
|
+
for i in range(max_cycles):
|
|
33
|
+
# Normalize q
|
|
34
|
+
q = r_prev / beta_prev
|
|
35
|
+
Q[:,i] = q
|
|
36
|
+
qs.append(q.copy())
|
|
37
|
+
# Approximate action of Hessian on q (u = Hq) by finite differences.
|
|
38
|
+
#
|
|
39
|
+
# Gradient at perturbed coordinates
|
|
40
|
+
grad_k = grad_getter(coords + dx*q)
|
|
41
|
+
u = (grad_k - grad_l) / dx
|
|
42
|
+
# Residue
|
|
43
|
+
r = u - beta_prev*q_prev
|
|
44
|
+
alpha = q.dot(r)
|
|
45
|
+
r -= alpha*q
|
|
46
|
+
# Reorthogonalization of r against the present Lanczos vectors in Q
|
|
47
|
+
if reortho:
|
|
48
|
+
r -= Q.dot(Q.T.dot(r))
|
|
49
|
+
beta = np.linalg.norm(r)
|
|
50
|
+
|
|
51
|
+
alphas.append(alpha)
|
|
52
|
+
betas.append(beta)
|
|
53
|
+
size = len(alphas)
|
|
54
|
+
# Construct tri-diagonal matrix T
|
|
55
|
+
T = np.zeros((size, size))
|
|
56
|
+
diag_inds = np.diag_indices(size)
|
|
57
|
+
T[diag_inds] = alphas
|
|
58
|
+
if len(alphas) > 1:
|
|
59
|
+
for j, b in enumerate(betas[:-1], 1):
|
|
60
|
+
k = j-1
|
|
61
|
+
T[j,k] = b
|
|
62
|
+
T[k,j] = b
|
|
63
|
+
|
|
64
|
+
# Values for next cycle
|
|
65
|
+
beta_prev = beta
|
|
66
|
+
q_prev = q
|
|
67
|
+
r_prev = r
|
|
68
|
+
# Diagonalize T
|
|
69
|
+
w, v = np.linalg.eigh(T)
|
|
70
|
+
w_min = w[0]
|
|
71
|
+
log(f"Cycle {i: >3d}: w_min={w_min: .6f}")
|
|
72
|
+
|
|
73
|
+
# Check eigenvalue convergence
|
|
74
|
+
if (i > 0) and (abs((w_min - w_mins[-1])/w_mins[-1]) < dl):
|
|
75
|
+
log("Converged")
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
w_mins.append(w_min)
|
|
79
|
+
v_min = v[:,0]
|
|
80
|
+
|
|
81
|
+
# Form eigenvector from linear combination of Lanczos vectors
|
|
82
|
+
eigenvector = (v_min[:,None] * qs).sum(axis=0)
|
|
83
|
+
eigenvector /= np.linalg.norm(eigenvector)
|
|
84
|
+
return w_min, eigenvector
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def geom_lanczos(geom, *args, **kwargs):
|
|
88
|
+
"""Wraps Lanczos algorithm for use with Geometry objects."""
|
|
89
|
+
coords = geom.coords.copy()
|
|
90
|
+
|
|
91
|
+
def grad_getter(coords):
|
|
92
|
+
results = geom.get_energy_and_forces_at(coords)
|
|
93
|
+
return -results["forces"]
|
|
94
|
+
|
|
95
|
+
return lanczos(coords, grad_getter, *args, **kwargs)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.optimizers.Optimizer import Optimizer
|
|
4
|
+
from pysisyphus.optimizers.hessian_updates import double_damp
|
|
5
|
+
from pysisyphus.optimizers.restrict_step import scale_by_max_step
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# [1] Nocedal, Wright - Numerical Optimization, 2006
|
|
9
|
+
# [2] http://dx.doi.org/10.1016/j.jcp.2013.08.044
|
|
10
|
+
# Badreddine, 2013
|
|
11
|
+
# [3] https://arxiv.org/abs/2006.08877
|
|
12
|
+
# Goldfarb, 2020
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BFGS(Optimizer):
|
|
16
|
+
|
|
17
|
+
def __init__(self, geometry, *args, update="bfgs", **kwargs):
|
|
18
|
+
super().__init__(geometry, *args, **kwargs)
|
|
19
|
+
|
|
20
|
+
assert self.align == False, \
|
|
21
|
+
"align=True does not work with this optimizer! Consider using LBFGS."
|
|
22
|
+
|
|
23
|
+
self.update = update
|
|
24
|
+
|
|
25
|
+
update_funcs = {
|
|
26
|
+
"bfgs": self.bfgs_update,
|
|
27
|
+
"damped": self.damped_bfgs_update,
|
|
28
|
+
"double": self.double_damped_bfgs_update,
|
|
29
|
+
}
|
|
30
|
+
self.update_func = update_funcs[self.update]
|
|
31
|
+
|
|
32
|
+
def prepare_opt(self):
|
|
33
|
+
# Inverse Hessian
|
|
34
|
+
self.H = self.eye
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def eye(self):
|
|
38
|
+
size = self.geometry.coords.size
|
|
39
|
+
if not hasattr(self, '_eye_cache') or self._eye_cache.shape[0] != size:
|
|
40
|
+
self._eye_cache = np.eye(size)
|
|
41
|
+
return self._eye_cache
|
|
42
|
+
|
|
43
|
+
def bfgs_update(self, s, y):
|
|
44
|
+
rho = 1 / s.dot(y)
|
|
45
|
+
V = self.eye - rho*np.outer(s, y)
|
|
46
|
+
self.H = V.dot(self.H).dot(V.T) + rho*np.outer(s, s)
|
|
47
|
+
|
|
48
|
+
def double_damped_bfgs_update(self, s, y, mu_1=0.2, mu_2=0.2):
|
|
49
|
+
"""Double damped BFGS update of inverse Hessian.
|
|
50
|
+
|
|
51
|
+
See [3]. Potentially updates s and y."""
|
|
52
|
+
|
|
53
|
+
# Call using the inverse Hessian 'H'
|
|
54
|
+
s, y = double_damp(s, y, H=self.H, mu_1=mu_1, mu_2=mu_2,
|
|
55
|
+
logger=self.logger)
|
|
56
|
+
self.log(f"s·y={s.dot(y):.6f} (damped)")
|
|
57
|
+
self.bfgs_update(s, y)
|
|
58
|
+
|
|
59
|
+
def damped_bfgs_update(self, s, y, mu_1=0.2):
|
|
60
|
+
"""Damped BFGS update of inverse Hessian.
|
|
61
|
+
|
|
62
|
+
Potentially updates s.
|
|
63
|
+
See Section 3.2 of [2], Eq. (30) - (33). There is a typo ;)
|
|
64
|
+
It should be
|
|
65
|
+
H_{k+1} = V_k H_k V_k^T + ...
|
|
66
|
+
instead of
|
|
67
|
+
H_{k+1} = V_k^T H_k V_k + ...
|
|
68
|
+
"""
|
|
69
|
+
self.double_damped_bfgs_update(s, y, mu_2=None)
|
|
70
|
+
|
|
71
|
+
def optimize(self):
|
|
72
|
+
forces = self.geometry.forces
|
|
73
|
+
energy = self.geometry.energy
|
|
74
|
+
self.forces.append(forces)
|
|
75
|
+
self.energies.append(energy)
|
|
76
|
+
|
|
77
|
+
if self.cur_cycle > 0:
|
|
78
|
+
# Gradient difference
|
|
79
|
+
y = self.forces[-2] - forces
|
|
80
|
+
# Coordinate difference / step
|
|
81
|
+
s = self.steps[-1]
|
|
82
|
+
# Curvature condition
|
|
83
|
+
sy = s.dot(y)
|
|
84
|
+
self.log(f"s·y={sy:.6f} (undamped)")
|
|
85
|
+
# Hessian update
|
|
86
|
+
self.update_func(s, y)
|
|
87
|
+
|
|
88
|
+
# Results in simple SD step in the first cycle
|
|
89
|
+
step = self.H.dot(forces)
|
|
90
|
+
self.log(f"Calcualted {self.update} step")
|
|
91
|
+
|
|
92
|
+
# Step restriction
|
|
93
|
+
unscaled_norm = np.linalg.norm(step)
|
|
94
|
+
step = scale_by_max_step(step, self.max_step)
|
|
95
|
+
scaled_norm = np.linalg.norm(step)
|
|
96
|
+
self.log(f"Unscaled norm(step)={unscaled_norm:.4f}")
|
|
97
|
+
self.log(f" Scaled norm(step)={scaled_norm:.4f}")
|
|
98
|
+
|
|
99
|
+
return step
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.optimizers.Optimizer import Optimizer
|
|
4
|
+
|
|
5
|
+
class BacktrackingOptimizer(Optimizer):
|
|
6
|
+
|
|
7
|
+
def __init__(self, geometry, alpha, bt_force=5,
|
|
8
|
+
dont_skip_after=2, bt_max_scale=4,
|
|
9
|
+
bt_disable=False, **kwargs):
|
|
10
|
+
# Setting some default values
|
|
11
|
+
self.alpha = alpha
|
|
12
|
+
assert(self.alpha > 0), "Alpha must be positive!"
|
|
13
|
+
self.bt_force = bt_force
|
|
14
|
+
self.dont_skip_after = dont_skip_after
|
|
15
|
+
self.bt_max_scale = bt_max_scale
|
|
16
|
+
self.bt_disable = bt_disable
|
|
17
|
+
assert(self.dont_skip_after >= 1)
|
|
18
|
+
self.cycles_since_backtrack = self.bt_force
|
|
19
|
+
self.scale_factor = 0.5
|
|
20
|
+
|
|
21
|
+
super(BacktrackingOptimizer, self).__init__(geometry, **kwargs)
|
|
22
|
+
|
|
23
|
+
self.alpha0 = self.alpha
|
|
24
|
+
self.alpha_max = self.bt_max_scale * self.alpha0
|
|
25
|
+
|
|
26
|
+
# Keep the skipping history to avoid infinite skipping, e.g. always
|
|
27
|
+
# return skip = False if we already skipped in the last n iterations.
|
|
28
|
+
self.skip_log = list()
|
|
29
|
+
|
|
30
|
+
def _get_opt_restart_info(self):
|
|
31
|
+
opt_restart_info = {
|
|
32
|
+
"alpha": self.alpha,
|
|
33
|
+
"cycles_since_backtrack": self.cycles_since_backtrack,
|
|
34
|
+
}
|
|
35
|
+
return opt_restart_info
|
|
36
|
+
|
|
37
|
+
def _set_opt_restart_info(self, opt_restart_info):
|
|
38
|
+
self.alpha = opt_restart_info["alpha"]
|
|
39
|
+
self.cycles_since_backtrack = opt_restart_info["cycles_since_backtrack"]
|
|
40
|
+
|
|
41
|
+
def reset(self):
|
|
42
|
+
if self.alpha > self.alpha0:
|
|
43
|
+
self.alpha = self.alpha0
|
|
44
|
+
self.log(f"Resetting! Current alpha is {self.alpha}. Lowering "
|
|
45
|
+
f"it to {self.alpha0}.")
|
|
46
|
+
|
|
47
|
+
def backtrack(self, cur_forces, prev_forces, reset_hessian=None):
|
|
48
|
+
"""Accelerated backtracking line search."""
|
|
49
|
+
if self.bt_disable:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
epsilon = 1e-3
|
|
53
|
+
|
|
54
|
+
rms = lambda f: np.sqrt(np.mean(np.square(f)))
|
|
55
|
+
cur_rms_force = rms(cur_forces)
|
|
56
|
+
prev_rms_force = rms(prev_forces)
|
|
57
|
+
|
|
58
|
+
rms_diff = (
|
|
59
|
+
(cur_rms_force - prev_rms_force) /
|
|
60
|
+
np.abs(cur_rms_force + prev_rms_force)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Skip tells us if we overshot
|
|
64
|
+
skip = False
|
|
65
|
+
|
|
66
|
+
# When the optimiziation is converging cur_forces will
|
|
67
|
+
# be smaller than prev_forces, so rms_diff will be negative
|
|
68
|
+
# and hence smaller than epsilon, which is a positive number.
|
|
69
|
+
|
|
70
|
+
# We went uphill, slow alpha
|
|
71
|
+
self.log(f"Backtracking: rms_diff = {rms_diff:.03f}")
|
|
72
|
+
if rms_diff > epsilon:
|
|
73
|
+
self.log(f"Scaling alpha with {self.scale_factor:.03f}")
|
|
74
|
+
# self.alpha = max(self.alpha0*.5, self.alpha*self.scale_factor)
|
|
75
|
+
self.alpha *= self.scale_factor
|
|
76
|
+
skip = True
|
|
77
|
+
self.cycles_since_backtrack = self.bt_force
|
|
78
|
+
# We continnue going downhill, rms_diff is smaller than epsilon
|
|
79
|
+
else:
|
|
80
|
+
self.cycles_since_backtrack -= 1
|
|
81
|
+
# Check if we didn't accelerate in the previous cycles
|
|
82
|
+
if self.cycles_since_backtrack < 0:
|
|
83
|
+
self.cycles_since_backtrack = self.bt_force
|
|
84
|
+
if self.alpha < self.alpha0:
|
|
85
|
+
# Reset alpha
|
|
86
|
+
self.alpha = self.alpha0
|
|
87
|
+
skip = True
|
|
88
|
+
self.log(f"Reset alpha to alpha0 = {self.alpha0:.4f}")
|
|
89
|
+
else:
|
|
90
|
+
# Accelerate alpha
|
|
91
|
+
self.alpha /= self.scale_factor
|
|
92
|
+
self.log(f"Scaled alpha to {self.alpha:.4f}")
|
|
93
|
+
|
|
94
|
+
# Avoid huge alphas
|
|
95
|
+
if self.alpha > self.alpha_max:
|
|
96
|
+
self.alpha = self.alpha_max
|
|
97
|
+
self.log("Didn't accelerate as alpha would become too large. "
|
|
98
|
+
f"keeping it at {self.alpha}.")
|
|
99
|
+
|
|
100
|
+
# Don't skip if we already skipped the previous iterations to
|
|
101
|
+
# avoid infinite skipping.
|
|
102
|
+
if ((len(self.skip_log) >= self.dont_skip_after)
|
|
103
|
+
and all(self.skip_log[-self.dont_skip_after:])):
|
|
104
|
+
self.log(f"already skipped last {self.dont_skip_after} "
|
|
105
|
+
"iterations don't skip now.")
|
|
106
|
+
skip = False
|
|
107
|
+
if self.alpha > self.alpha0:
|
|
108
|
+
self.alpha = self.alpha0
|
|
109
|
+
self.log("Resetted alpha to alpha0.")
|
|
110
|
+
self.skip_log.append(skip)
|
|
111
|
+
self.log(f"alpha = {self.alpha:.4f}, skip = {skip}")
|
|
112
|
+
|
|
113
|
+
return skip
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.optimizers.BacktrackingOptimizer import BacktrackingOptimizer
|
|
4
|
+
|
|
5
|
+
# http://ikuz.eu/2015/04/15/the-concept-of-conjugate-gradient-descent-in-python/
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConjugateGradient(BacktrackingOptimizer):
|
|
9
|
+
def __init__(self, geometry, alpha=0.1, formula="FR", dont_skip=True, **kwargs):
|
|
10
|
+
super().__init__(geometry, alpha=alpha, **kwargs)
|
|
11
|
+
|
|
12
|
+
self.formula = formula
|
|
13
|
+
self.dont_skip = dont_skip
|
|
14
|
+
|
|
15
|
+
def prepare_opt(self):
|
|
16
|
+
if self.is_cos and self.align:
|
|
17
|
+
self.procrustes()
|
|
18
|
+
# Calculate initial forces before the first iteration
|
|
19
|
+
self.coords.append(self.geometry.coords)
|
|
20
|
+
self.forces.append(self.geometry.forces)
|
|
21
|
+
|
|
22
|
+
def reset(self):
|
|
23
|
+
super().reset()
|
|
24
|
+
|
|
25
|
+
# Check if the number of coordinates changed
|
|
26
|
+
if self.forces[-1].size != self.geometry.coords.size:
|
|
27
|
+
new_forces = self.geometry.forces
|
|
28
|
+
self.forces.append(new_forces)
|
|
29
|
+
self.resetted = True
|
|
30
|
+
|
|
31
|
+
def get_beta(self, cur_forces, prev_forces):
|
|
32
|
+
# Fletcher-Reeves formula
|
|
33
|
+
if self.formula == "FR":
|
|
34
|
+
beta = cur_forces.dot(cur_forces) / prev_forces.dot(prev_forces)
|
|
35
|
+
# Polak-Ribiere
|
|
36
|
+
elif self.formula == "PR":
|
|
37
|
+
beta = -cur_forces.dot(prev_forces - cur_forces) / prev_forces.dot(
|
|
38
|
+
prev_forces
|
|
39
|
+
)
|
|
40
|
+
beta_old = cur_forces.dot(cur_forces - prev_forces) / prev_forces.dot(
|
|
41
|
+
prev_forces
|
|
42
|
+
)
|
|
43
|
+
self.log(f"beta_old={beta_old:.4f}, beta={beta:.4f}")
|
|
44
|
+
if beta < 0:
|
|
45
|
+
self.log(f"beta = {beta:.04f} < 0, resetting to 0")
|
|
46
|
+
# beta = 0 basically restarts CG, as no previous step
|
|
47
|
+
# information is mixed into the current step.
|
|
48
|
+
beta = 0
|
|
49
|
+
return beta
|
|
50
|
+
|
|
51
|
+
def optimize(self):
|
|
52
|
+
cur_forces = self.forces[-1]
|
|
53
|
+
|
|
54
|
+
if not self.resetted and self.cur_cycle > 0:
|
|
55
|
+
prev_forces = self.forces[-2]
|
|
56
|
+
beta = self.get_beta(cur_forces, prev_forces)
|
|
57
|
+
self.log(f"beta = {beta:.06f}")
|
|
58
|
+
if np.isinf(beta):
|
|
59
|
+
beta = 1.0
|
|
60
|
+
step = cur_forces + beta * self.steps[-1]
|
|
61
|
+
else:
|
|
62
|
+
# Start with steepest descent in the first iteration
|
|
63
|
+
step = cur_forces
|
|
64
|
+
self.resetted = False
|
|
65
|
+
|
|
66
|
+
step = self.alpha * step
|
|
67
|
+
step = self.scale_by_max_step(step)
|
|
68
|
+
|
|
69
|
+
cur_coords = self.geometry.coords
|
|
70
|
+
new_coords = cur_coords + step
|
|
71
|
+
self.geometry.coords = new_coords
|
|
72
|
+
|
|
73
|
+
new_forces = self.geometry.forces
|
|
74
|
+
new_energy = self.geometry.energy
|
|
75
|
+
|
|
76
|
+
skip = self.backtrack(new_forces, cur_forces)
|
|
77
|
+
# Imho backtracking gives bad results here, so only use it if
|
|
78
|
+
# explicitly requested (self.dont_skip == False).
|
|
79
|
+
if (not self.dont_skip) and skip:
|
|
80
|
+
self.geometry.coords = cur_coords
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
if self.align and self.is_cos:
|
|
84
|
+
(new_forces, cur_forces, step), _, _ = self.fit_rigid(
|
|
85
|
+
vectors=(new_forces, cur_forces, step),
|
|
86
|
+
)
|
|
87
|
+
# Minus step???
|
|
88
|
+
new_coords = self.geometry.coords - step
|
|
89
|
+
self.geometry.coords = new_coords
|
|
90
|
+
# Set the calculated properties on the rotated geometries
|
|
91
|
+
self.geometry.energy = new_energy
|
|
92
|
+
self.geometry.forces = new_forces
|
|
93
|
+
|
|
94
|
+
self.forces[-1] = cur_forces
|
|
95
|
+
self.forces.append(new_forces)
|
|
96
|
+
self.energies.append(new_energy)
|
|
97
|
+
|
|
98
|
+
return step
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# [1] https://arxiv.org/abs/2112.02089
|
|
2
|
+
# Regularized Newton Method with Global O(1/k²) Convergence
|
|
3
|
+
# Konstantin Mishchenko
|
|
4
|
+
|
|
5
|
+
from math import sqrt
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from pysisyphus.optimizers.HessianOptimizer import HessianOptimizer
|
|
10
|
+
from pysisyphus.optimizers.exceptions import OptimizationError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CubicNewton(HessianOptimizer):
|
|
14
|
+
def __init__(self, geometry, **kwargs):
|
|
15
|
+
# Force-disable trust radius update, as this optimizers uses a line search
|
|
16
|
+
kwargs["trust_update"] = False
|
|
17
|
+
super().__init__(geometry, **kwargs)
|
|
18
|
+
|
|
19
|
+
self.line_search_cycles = 0
|
|
20
|
+
|
|
21
|
+
def optimize(self):
|
|
22
|
+
energy, gradient, hessian, *_ = self.housekeeping()
|
|
23
|
+
|
|
24
|
+
if self.cur_cycle == 0:
|
|
25
|
+
# Initial Lipschitz constant estimate; line 2 in algorithm 2 in [1]
|
|
26
|
+
trial_step_length = 0.1
|
|
27
|
+
trial_step = trial_step_length * (-gradient / np.linalg.norm(gradient))
|
|
28
|
+
trial_coords = self.geometry.coords + trial_step
|
|
29
|
+
trial_results = self.geometry.get_energy_and_forces_at(trial_coords)
|
|
30
|
+
trial_gradient = -trial_results["forces"]
|
|
31
|
+
H = (
|
|
32
|
+
np.linalg.norm(trial_gradient - gradient - hessian.dot(trial_step))
|
|
33
|
+
/ np.linalg.norm(trial_step) ** 2
|
|
34
|
+
)
|
|
35
|
+
else:
|
|
36
|
+
H = self.H_prev / 4
|
|
37
|
+
self.log(f"Lipschitz constant in cycle {self.cur_cycle}, H={H:.4f}")
|
|
38
|
+
|
|
39
|
+
for i in range(self.max_micro_cycles):
|
|
40
|
+
self.line_search_cycles += 1
|
|
41
|
+
H *= 2
|
|
42
|
+
self.log(f"Adaptive Newton line search, cycle {i} using H={H:.4f}")
|
|
43
|
+
lambda_ = sqrt(H * np.linalg.norm(gradient))
|
|
44
|
+
# Instead of solving the linear system we could also use the
|
|
45
|
+
# eigenvectors/-values from housekeeping(). Currently, they are
|
|
46
|
+
# gathered in '*_' and not used.
|
|
47
|
+
trial_step = np.linalg.solve(
|
|
48
|
+
hessian + lambda_ * np.eye(gradient.size), -gradient
|
|
49
|
+
)
|
|
50
|
+
trial_step_norm = np.linalg.norm(trial_step)
|
|
51
|
+
trial_coords = self.geometry.coords + trial_step
|
|
52
|
+
|
|
53
|
+
trial_results = self.geometry.get_energy_and_forces_at(trial_coords)
|
|
54
|
+
trial_gradient = -trial_results["forces"]
|
|
55
|
+
trial_energy = trial_results["energy"]
|
|
56
|
+
|
|
57
|
+
trial_gradient_small_enough = (
|
|
58
|
+
np.linalg.norm(trial_gradient) <= 2 * lambda_ * trial_step_norm
|
|
59
|
+
)
|
|
60
|
+
sufficient_energy_lowering = (
|
|
61
|
+
trial_energy <= energy - 2 / 3 * lambda_ * trial_step_norm ** 2
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if trial_gradient_small_enough and sufficient_energy_lowering:
|
|
65
|
+
step = trial_step
|
|
66
|
+
break
|
|
67
|
+
else:
|
|
68
|
+
raise OptimizationError("Adaptive Newton line search failed!")
|
|
69
|
+
|
|
70
|
+
self.H_prev = H
|
|
71
|
+
|
|
72
|
+
return step
|
|
73
|
+
|
|
74
|
+
def postprocess_opt(self):
|
|
75
|
+
self.log(f"Line search cycles: {self.line_search_cycles}")
|