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,260 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from functools import partial
|
|
3
|
+
from typing import Literal, Optional
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from scipy.sparse.linalg import spsolve
|
|
7
|
+
|
|
8
|
+
from pysisyphus.calculators import Dimer
|
|
9
|
+
from pysisyphus.cos.GrowingNT import GrowingNT
|
|
10
|
+
from pysisyphus.Geometry import Geometry
|
|
11
|
+
from pysisyphus.line_searches import *
|
|
12
|
+
from pysisyphus.optimizers.closures import bfgs_multiply
|
|
13
|
+
from pysisyphus.optimizers.Optimizer import Optimizer
|
|
14
|
+
from pysisyphus.optimizers.precon import precon_getter, PreconKind
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
LineSearch = Literal["armijo", "armijo_fg", "strong_wolfe", "hz", None, False]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PreconLBFGS(Optimizer):
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
geometry: Geometry,
|
|
24
|
+
alpha_init: float = 1.0,
|
|
25
|
+
history: int = 7,
|
|
26
|
+
precon: bool = True,
|
|
27
|
+
precon_update: int = 1,
|
|
28
|
+
precon_getter_update: Optional[int] = None,
|
|
29
|
+
precon_kind: PreconKind = "full",
|
|
30
|
+
max_step_element: Optional[float] = None,
|
|
31
|
+
line_search: LineSearch = "armijo",
|
|
32
|
+
c_stab: Optional[float] = None,
|
|
33
|
+
**kwargs,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Preconditioned limited-memory BFGS optimizer.
|
|
36
|
+
|
|
37
|
+
See pysisyphus.optimizers.precon for related references.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
geometry
|
|
42
|
+
Geometry to be optimized.
|
|
43
|
+
alpha_init
|
|
44
|
+
Initial scaling factor for the first trial step in the excplicit line search.
|
|
45
|
+
history
|
|
46
|
+
History size. Keep last 'history' steps and gradient differences.
|
|
47
|
+
precon
|
|
48
|
+
Wheter to use preconditioning or not.
|
|
49
|
+
precon_update
|
|
50
|
+
Recalculate preconditioner P in every n-th cycle with the same topology.
|
|
51
|
+
precon_getter_update
|
|
52
|
+
Recalculate topology for preconditioner P in every n-th cycle. It is usually
|
|
53
|
+
sufficient to only determine the topology once at the beginning.
|
|
54
|
+
precon_kind
|
|
55
|
+
What types of primitive internal coordinates to consider in the preconditioner.
|
|
56
|
+
max_step_element
|
|
57
|
+
Maximum component of the absolute step vector when no line search is carried out.
|
|
58
|
+
line_search
|
|
59
|
+
Whether to use explicit line searches and if so, which kind of line search.
|
|
60
|
+
c_stab
|
|
61
|
+
Regularization constant c in (H + cI)⁻¹ in atomic units.
|
|
62
|
+
|
|
63
|
+
Other Parameters
|
|
64
|
+
----------------
|
|
65
|
+
**kwargs
|
|
66
|
+
Keyword arguments passed to the Optimizer baseclass.
|
|
67
|
+
"""
|
|
68
|
+
if precon:
|
|
69
|
+
assert geometry.coord_type in (
|
|
70
|
+
"cart",
|
|
71
|
+
"cartesian",
|
|
72
|
+
), "Preconditioning makes only sense with 'coord_type: cart|cartesian'"
|
|
73
|
+
# Set before calling the superclass constructor so we can use the value
|
|
74
|
+
# in _set_opt_restart_info.
|
|
75
|
+
self.history = history
|
|
76
|
+
|
|
77
|
+
super().__init__(geometry, **kwargs)
|
|
78
|
+
|
|
79
|
+
self.alpha_init = alpha_init
|
|
80
|
+
self.precon = precon
|
|
81
|
+
# Disable preconditioning for 1 atom species, e.g., analytical potentials
|
|
82
|
+
if self.precon and (self.geometry.cart_coords.size == 3):
|
|
83
|
+
self.precon = None
|
|
84
|
+
self.precon_update = precon_update
|
|
85
|
+
self.precon_getter_update = precon_getter_update
|
|
86
|
+
self.precon_kind = precon_kind
|
|
87
|
+
|
|
88
|
+
self.P = None
|
|
89
|
+
try:
|
|
90
|
+
is_dimer = isinstance(self.geometry.calculator, Dimer)
|
|
91
|
+
# COS objectes may not have a calculator
|
|
92
|
+
except AttributeError:
|
|
93
|
+
is_dimer = False
|
|
94
|
+
go_uphill = is_dimer or isinstance(self.geometry, GrowingNT)
|
|
95
|
+
|
|
96
|
+
if c_stab is None:
|
|
97
|
+
self.log("No c_stab specified.")
|
|
98
|
+
if go_uphill:
|
|
99
|
+
c_stab = 0.103 # 1 eV/Ų
|
|
100
|
+
self.log(
|
|
101
|
+
"Found climbing calculation. Using higher regularization "
|
|
102
|
+
f"c_stab={c_stab:.4f} au/bohr**2."
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
c_stab = 0.0103 # 0.1 eV/Ų
|
|
106
|
+
|
|
107
|
+
self.c_stab = float(c_stab)
|
|
108
|
+
self.log(f"Using c_stab={self.c_stab:.6f}")
|
|
109
|
+
|
|
110
|
+
if max_step_element is None and go_uphill:
|
|
111
|
+
max_step_element = 0.25
|
|
112
|
+
self.log(
|
|
113
|
+
f"Found climbing calculation. Using max_step_element={max_step_element:.2f}"
|
|
114
|
+
)
|
|
115
|
+
elif max_step_element is None and line_search in (None, False):
|
|
116
|
+
max_step_element = 0.2
|
|
117
|
+
self.max_step_element = max_step_element
|
|
118
|
+
|
|
119
|
+
# Disable linesearch if max_step_element is set
|
|
120
|
+
if self.max_step_element is not None:
|
|
121
|
+
self.log(
|
|
122
|
+
f"max_step_element={max_step_element:.6f} given. Setting "
|
|
123
|
+
"line_search to 'None'."
|
|
124
|
+
)
|
|
125
|
+
line_search = None
|
|
126
|
+
|
|
127
|
+
self.line_search = line_search
|
|
128
|
+
ls_cls = {
|
|
129
|
+
"armijo": Backtracking,
|
|
130
|
+
"armijo_fg": partial(Backtracking, use_grad=True),
|
|
131
|
+
"strong_wolfe": StrongWolfe,
|
|
132
|
+
"hz": HagerZhang,
|
|
133
|
+
None: None,
|
|
134
|
+
False: None,
|
|
135
|
+
}
|
|
136
|
+
self.line_search_cls = ls_cls[self.line_search]
|
|
137
|
+
|
|
138
|
+
if not self.restarted:
|
|
139
|
+
self.grad_diffs = deque(maxlen=self.history)
|
|
140
|
+
self.steps_ = deque(maxlen=self.history)
|
|
141
|
+
self.f_evals = 0
|
|
142
|
+
self.df_evals = 0
|
|
143
|
+
|
|
144
|
+
def get_precon_getter(self):
|
|
145
|
+
return precon_getter(
|
|
146
|
+
self.geometry, c_stab=self.c_stab, kind=self.precon_kind, logger=self.logger
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def prepare_opt(self):
|
|
150
|
+
if self.precon:
|
|
151
|
+
self.precon_getter = self.get_precon_getter()
|
|
152
|
+
|
|
153
|
+
def _get_opt_restart_info(self):
|
|
154
|
+
opt_restart_info = {
|
|
155
|
+
"c_stab": self.c_stab,
|
|
156
|
+
"grad_diffs": np.array(self.grad_diffs).tolist(),
|
|
157
|
+
"steps_": np.array(self.steps_).tolist(),
|
|
158
|
+
"precon_kind": self.precon_kind,
|
|
159
|
+
"f_evals": self.f_evals,
|
|
160
|
+
"df_evals": self.df_evals,
|
|
161
|
+
}
|
|
162
|
+
return opt_restart_info
|
|
163
|
+
|
|
164
|
+
def _set_opt_restart_info(self, opt_restart_info):
|
|
165
|
+
self.grad_diffs = deque(
|
|
166
|
+
[np.array(gd) for gd in opt_restart_info["grad_diffs"]], maxlen=self.history
|
|
167
|
+
)
|
|
168
|
+
self.steps_ = deque(
|
|
169
|
+
[np.array(cd) for cd in opt_restart_info["steps_"]], maxlen=self.history
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
self.c_stab = opt_restart_info["c_stab"]
|
|
173
|
+
self.precon_kind = opt_restart_info["precon_kind"]
|
|
174
|
+
self.precon_getter = self.get_precon_getter()
|
|
175
|
+
self.f_evals = opt_restart_info["f_evals"]
|
|
176
|
+
self.df_evals = opt_restart_info["df_evals"]
|
|
177
|
+
|
|
178
|
+
def scale_max_element(self, step, max_step_element):
|
|
179
|
+
max_element = np.abs(step).max()
|
|
180
|
+
if max_element > max_step_element:
|
|
181
|
+
step *= max_step_element / max_element
|
|
182
|
+
return step
|
|
183
|
+
|
|
184
|
+
def optimize(self):
|
|
185
|
+
forces = self.geometry.forces
|
|
186
|
+
energy = self.geometry.energy
|
|
187
|
+
|
|
188
|
+
self.forces.append(forces)
|
|
189
|
+
self.energies.append(energy)
|
|
190
|
+
|
|
191
|
+
norm = np.linalg.norm(forces)
|
|
192
|
+
if not self.is_cos:
|
|
193
|
+
fmt = " >12.6f"
|
|
194
|
+
self.log(f" Current energy={energy:{fmt}} au")
|
|
195
|
+
if self.cur_cycle > 0:
|
|
196
|
+
prev_energy = self.energies[-2]
|
|
197
|
+
self.log(f"Previous energy={prev_energy:{fmt}} au")
|
|
198
|
+
self.log(f" ΔE={energy-prev_energy:{fmt}} au")
|
|
199
|
+
self.log(f"norm(forces)={norm:.6f}")
|
|
200
|
+
|
|
201
|
+
# Check for preconditioner getter update
|
|
202
|
+
if (
|
|
203
|
+
self.precon_getter_update
|
|
204
|
+
and self.cur_cycle > 0
|
|
205
|
+
and self.cur_cycle % self.precon_getter_update == 0
|
|
206
|
+
):
|
|
207
|
+
self.precon_getter = self.get_precon_getter()
|
|
208
|
+
self.log("Calculated new preconditioner getter")
|
|
209
|
+
|
|
210
|
+
# If requested, construct preconditioner
|
|
211
|
+
if self.precon and (self.cur_cycle % self.precon_update == 0):
|
|
212
|
+
self.P = self.precon_getter(self.geometry.coords)
|
|
213
|
+
self.log("Calculated new preconditioner P")
|
|
214
|
+
|
|
215
|
+
# Determine step direction
|
|
216
|
+
|
|
217
|
+
# Preconditioned LBFGS in later cycles
|
|
218
|
+
if self.cur_cycle > 0:
|
|
219
|
+
self.grad_diffs.append(-forces - -self.forces[-2])
|
|
220
|
+
self.steps_.append(self.steps[-1])
|
|
221
|
+
step = bfgs_multiply(self.steps_, self.grad_diffs, forces, P=self.P)
|
|
222
|
+
# Preconditioned steepest descent in cycle 0
|
|
223
|
+
elif self.precon:
|
|
224
|
+
step = spsolve(self.P, forces)
|
|
225
|
+
# Steepest descent w/o preconditioner in cycle 0
|
|
226
|
+
else:
|
|
227
|
+
step = forces
|
|
228
|
+
|
|
229
|
+
step_norm = np.linalg.norm(step)
|
|
230
|
+
self.log(f"norm(unscaled step)={step_norm:.6f} au")
|
|
231
|
+
|
|
232
|
+
# Determine step length
|
|
233
|
+
if self.line_search_cls is not None:
|
|
234
|
+
kwargs = {
|
|
235
|
+
"geometry": self.geometry,
|
|
236
|
+
"p": step,
|
|
237
|
+
"f0": energy,
|
|
238
|
+
"g0": -forces,
|
|
239
|
+
"alpha_init": self.alpha_init,
|
|
240
|
+
}
|
|
241
|
+
line_search = self.line_search_cls(**kwargs)
|
|
242
|
+
line_search_result = line_search.run()
|
|
243
|
+
try:
|
|
244
|
+
alpha = line_search_result.alpha
|
|
245
|
+
step *= alpha
|
|
246
|
+
f_evals = line_search_result.f_evals
|
|
247
|
+
df_evals = line_search_result.df_evals
|
|
248
|
+
self.log(
|
|
249
|
+
f"Evaluated {f_evals} energies and {df_evals} gradients in line search."
|
|
250
|
+
)
|
|
251
|
+
self.f_evals += f_evals
|
|
252
|
+
self.df_evals += df_evals
|
|
253
|
+
except TypeError:
|
|
254
|
+
self.log("Line search did not converge!")
|
|
255
|
+
step = self.scale_max_element(step, self.max_step_element)
|
|
256
|
+
else:
|
|
257
|
+
step = self.scale_max_element(step, self.max_step_element)
|
|
258
|
+
step_norm = np.linalg.norm(step)
|
|
259
|
+
self.log(f"norm(step)={step_norm:.6f} au")
|
|
260
|
+
return step
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.optimizers.Optimizer import Optimizer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class QuickMin(Optimizer):
|
|
7
|
+
def __init__(self, geometry, dt=0.35, **kwargs):
|
|
8
|
+
super(QuickMin, self).__init__(geometry, **kwargs)
|
|
9
|
+
|
|
10
|
+
self.dt = dt
|
|
11
|
+
|
|
12
|
+
def _get_opt_restart_info(self):
|
|
13
|
+
opt_restart_info = {
|
|
14
|
+
"velocity": self.velocities[-1].tolist(),
|
|
15
|
+
}
|
|
16
|
+
return opt_restart_info
|
|
17
|
+
|
|
18
|
+
def _set_opt_restart_info(self, opt_restart_info):
|
|
19
|
+
velocity = np.array(opt_restart_info["velocity"], dtype=float)
|
|
20
|
+
self.velocities = [
|
|
21
|
+
velocity,
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
def prepare_opt(self):
|
|
25
|
+
self.velocities = [
|
|
26
|
+
np.zeros_like(self.geometry.coords),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
def reset(self):
|
|
30
|
+
if self.coords[-1].size != self.coords[-2].size:
|
|
31
|
+
self.log(
|
|
32
|
+
"Number of coordinates changed. Adapting velocity vector "
|
|
33
|
+
"to new coordinate number."
|
|
34
|
+
)
|
|
35
|
+
self.prepare_opt()
|
|
36
|
+
self.log("Resetted optimizer")
|
|
37
|
+
|
|
38
|
+
def optimize(self):
|
|
39
|
+
if self.align and self.is_cos:
|
|
40
|
+
(self.velocities[-1],), _, _ = self.fit_rigid(
|
|
41
|
+
vectors=(self.velocities[-1],)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
prev_velocities = self.velocities[-1]
|
|
45
|
+
cur_forces = self.geometry.forces
|
|
46
|
+
self.forces.append(cur_forces)
|
|
47
|
+
self.energies.append(self.geometry.energy)
|
|
48
|
+
|
|
49
|
+
norm = np.linalg.norm(cur_forces)
|
|
50
|
+
if not self.is_cos:
|
|
51
|
+
self.log(f"Current energy={self.energies[-1]:.6f}")
|
|
52
|
+
self.log(f"norm(forces)={norm:.6f}")
|
|
53
|
+
|
|
54
|
+
if self.cur_cycle == 0:
|
|
55
|
+
tmp_velocities = np.zeros_like(prev_velocities)
|
|
56
|
+
else:
|
|
57
|
+
overlap = prev_velocities.dot(cur_forces)
|
|
58
|
+
self.log(f"Overlap of previous and current forces: {overlap:.6f}")
|
|
59
|
+
if overlap > 0:
|
|
60
|
+
tmp_velocities = overlap * cur_forces / cur_forces.dot(cur_forces)
|
|
61
|
+
else:
|
|
62
|
+
tmp_velocities = np.zeros_like(prev_velocities)
|
|
63
|
+
self.log("Zeroed velocities")
|
|
64
|
+
|
|
65
|
+
accelerations = cur_forces / self.geometry.masses_rep
|
|
66
|
+
cur_velocities = tmp_velocities + self.dt * accelerations
|
|
67
|
+
steps = cur_velocities * self.dt + 1 / 2 * accelerations * self.dt**2
|
|
68
|
+
steps = self.scale_by_max_step(steps)
|
|
69
|
+
self.velocities.append(cur_velocities)
|
|
70
|
+
velo_norm = np.linalg.norm(cur_velocities)
|
|
71
|
+
acc_norm = np.linalg.norm(accelerations)
|
|
72
|
+
self.log(f"norm(v) = {velo_norm:.4f}, norm(a) = {acc_norm:.4f}")
|
|
73
|
+
|
|
74
|
+
return steps
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# [1] http://aip.scitation.org/doi/10.1063/1.1515483 Optimization review
|
|
2
|
+
# [2] https://doi.org/10.1063/1.450914 Trust region method
|
|
3
|
+
# [3] 10.1007/978-0-387-40065-5 Numerical optimization
|
|
4
|
+
# [4] 10.1007/s00214-016-1847-3 Explorations of some refinements
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from pysisyphus.Geometry import Geometry
|
|
10
|
+
from pysisyphus.helpers_pure import rms
|
|
11
|
+
from pysisyphus.optimizers.HessianOptimizer import HessianOptimizer
|
|
12
|
+
from pysisyphus.optimizers.poly_fit import poly_line_search
|
|
13
|
+
from pysisyphus.optimizers.gdiis import gdiis, gediis
|
|
14
|
+
|
|
15
|
+
import torch
|
|
16
|
+
|
|
17
|
+
class RFOptimizer(HessianOptimizer):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
geometry: Geometry,
|
|
21
|
+
line_search: bool = True,
|
|
22
|
+
gediis: bool = False,
|
|
23
|
+
gdiis: bool = True,
|
|
24
|
+
gdiis_thresh: float = 2.5e-3,
|
|
25
|
+
gediis_thresh: float = 1e-2,
|
|
26
|
+
gdiis_test_direction: bool = True,
|
|
27
|
+
max_micro_cycles: int = 25,
|
|
28
|
+
adapt_step_func: bool = False,
|
|
29
|
+
**kwargs,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Rational function Optimizer.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
geometry
|
|
37
|
+
Geometry to be optimized.
|
|
38
|
+
line_search
|
|
39
|
+
Whether to carry out implicit line searches.
|
|
40
|
+
gediis
|
|
41
|
+
Whether to enable GEDIIS.
|
|
42
|
+
gdiis
|
|
43
|
+
Whether to enable GDIIS.
|
|
44
|
+
gdiis_thresh
|
|
45
|
+
Threshold for rms(forces) to enable GDIIS.
|
|
46
|
+
gediis_thresh
|
|
47
|
+
Threshold for rms(step) to enable GEDIIS.
|
|
48
|
+
gdiis_test_direction
|
|
49
|
+
Whether to the overlap of the RFO step and the GDIIS step.
|
|
50
|
+
max_micro_cycles
|
|
51
|
+
Number of restricted-step microcycles. Disabled by default.
|
|
52
|
+
adapt_step_func
|
|
53
|
+
Whether to switch between shifted Newton and RFO-steps.
|
|
54
|
+
|
|
55
|
+
Other Parameters
|
|
56
|
+
----------------
|
|
57
|
+
**kwargs
|
|
58
|
+
Keyword arguments passed to the Optimizer/HessianOptimizer baseclass.
|
|
59
|
+
"""
|
|
60
|
+
super().__init__(geometry, max_micro_cycles=max_micro_cycles, **kwargs)
|
|
61
|
+
|
|
62
|
+
self.line_search = line_search
|
|
63
|
+
self.gediis = gediis
|
|
64
|
+
self.gdiis = gdiis
|
|
65
|
+
self.gdiis_thresh = gdiis_thresh # Will be compared to rms(step)
|
|
66
|
+
self.gediis_thresh = gediis_thresh # Will be compared to rms(forces)
|
|
67
|
+
self.gdiis_test_direction = gdiis_test_direction
|
|
68
|
+
self.adapt_step_func = adapt_step_func
|
|
69
|
+
|
|
70
|
+
self.successful_gediis = 0
|
|
71
|
+
self.successful_gdiis = 0
|
|
72
|
+
self.successful_line_search = 0
|
|
73
|
+
|
|
74
|
+
def optimize(self):
|
|
75
|
+
energy, gradient, H, big_eigvals, big_eigvecs, resetted = self.housekeeping()
|
|
76
|
+
step_func, pred_func = self.get_step_func(big_eigvals, gradient)
|
|
77
|
+
|
|
78
|
+
forces_act = self.active_list(self.forces)
|
|
79
|
+
coords_act = self.active_list(self.coords)
|
|
80
|
+
steps_act = self.active_list(self.steps)
|
|
81
|
+
|
|
82
|
+
ref_gradient = gradient.copy() if isinstance(gradient, np.ndarray) else gradient.clone()
|
|
83
|
+
# Reference step, used for judging the proposed GDIIS step
|
|
84
|
+
ref_step = step_func(big_eigvals, big_eigvecs, gradient) # heavy-compute
|
|
85
|
+
|
|
86
|
+
# Right everything is in place to check for convergence. If all values are below
|
|
87
|
+
# the thresholds, there is no need to do additional inter/extrapolations.
|
|
88
|
+
if self.check_convergence(ref_step)[0]: # Drop conv_info
|
|
89
|
+
self.log("Convergence achieved! Skipping inter/extrapolation.")
|
|
90
|
+
return ref_step
|
|
91
|
+
|
|
92
|
+
# Try to interpolate an intermediate geometry, either from GDIIS or line search.
|
|
93
|
+
#
|
|
94
|
+
# Set some defaults
|
|
95
|
+
ip_gradient = None
|
|
96
|
+
ip_step = None
|
|
97
|
+
diis_result = None
|
|
98
|
+
|
|
99
|
+
# Check if we can do GDIIS or GEDIIS. If we (can) do a line search is decided
|
|
100
|
+
# after trying GDIIS.
|
|
101
|
+
rms_forces = rms(gradient)
|
|
102
|
+
rms_step = rms(ref_step)
|
|
103
|
+
can_diis = (rms_step <= self.gdiis_thresh) and (not resetted)
|
|
104
|
+
can_gediis = (rms_forces <= self.gediis_thresh) and (not resetted)
|
|
105
|
+
|
|
106
|
+
# GDIIS / GEDIIS, prefer GDIIS over GEDIIS
|
|
107
|
+
if self.gdiis and can_diis:
|
|
108
|
+
# Gradients as error vectors
|
|
109
|
+
if isinstance(ref_step, torch.Tensor):
|
|
110
|
+
err_vecs = -torch.from_numpy(np.array(forces_act)).to(ref_step.dtype).to(ref_step.device)
|
|
111
|
+
else:
|
|
112
|
+
err_vecs = -np.array(forces_act)
|
|
113
|
+
diis_result = gdiis(
|
|
114
|
+
err_vecs,
|
|
115
|
+
coords_act,
|
|
116
|
+
forces_act,
|
|
117
|
+
ref_step,
|
|
118
|
+
test_direction=self.gdiis_test_direction,
|
|
119
|
+
)
|
|
120
|
+
self.successful_gdiis += 1 if diis_result else 0
|
|
121
|
+
# Don't try GEDIIS if GDIIS failed. If GEDIIS should be tried after GDIIS failed
|
|
122
|
+
# comment the line below and uncomment the line following it.
|
|
123
|
+
elif self.gediis and can_gediis:
|
|
124
|
+
# if self.gediis and can_gediis and (diis_result == None):
|
|
125
|
+
diis_result = gediis(coords_act, self.energies, forces_act, hessian=H)
|
|
126
|
+
self.successful_gediis += 1 if diis_result else 0
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
ip_coords = diis_result.coords
|
|
130
|
+
if isinstance(ip_coords, torch.Tensor):
|
|
131
|
+
ip_step = ip_coords - torch.from_numpy(coords_act[-1]).to(ip_coords.device, ip_coords.dtype)
|
|
132
|
+
else:
|
|
133
|
+
ip_step = ip_coords - coords_act[-1]
|
|
134
|
+
ip_gradient = -diis_result.forces
|
|
135
|
+
# When diis_result is None
|
|
136
|
+
except AttributeError:
|
|
137
|
+
self.log("GDIIS didn't succeed.")
|
|
138
|
+
|
|
139
|
+
# Try line search if GDIIS failed or not requested
|
|
140
|
+
if self.line_search and (diis_result is None) and (not resetted):
|
|
141
|
+
ip_energy, ip_gradient, ip_step = poly_line_search(
|
|
142
|
+
energy,
|
|
143
|
+
self.energies[-2],
|
|
144
|
+
gradient,
|
|
145
|
+
-forces_act[-2],
|
|
146
|
+
steps_act[-1],
|
|
147
|
+
cubic_max_x=-1,
|
|
148
|
+
quartic_max_x=2,
|
|
149
|
+
logger=self.logger,
|
|
150
|
+
)
|
|
151
|
+
self.successful_line_search += 1 if ip_gradient is not None else 0
|
|
152
|
+
|
|
153
|
+
# Use the interpolated gradient for the RFO step if interpolation succeeded
|
|
154
|
+
if (ip_gradient is not None) and (ip_step is not None):
|
|
155
|
+
gradient = ip_gradient
|
|
156
|
+
step = step_func(big_eigvals, big_eigvecs, gradient) # heavy-compute
|
|
157
|
+
# Form full step with interpolation offset.
|
|
158
|
+
if isinstance(ip_step, torch.Tensor):
|
|
159
|
+
ip_step = ip_step.detach().cpu().numpy()
|
|
160
|
+
step = step + ip_step
|
|
161
|
+
# Keep the original gradient when the interpolation failed; reuse ref_step.
|
|
162
|
+
else:
|
|
163
|
+
step = ref_step
|
|
164
|
+
|
|
165
|
+
# Use the original, actually calculated, gradient
|
|
166
|
+
prediction = pred_func(ref_gradient, H, step)
|
|
167
|
+
self.predicted_energy_changes.append(prediction)
|
|
168
|
+
|
|
169
|
+
step = self.full_from_active(step)
|
|
170
|
+
if isinstance(step, torch.Tensor):
|
|
171
|
+
step = step.cpu().numpy()
|
|
172
|
+
return step
|
|
173
|
+
|
|
174
|
+
def postprocess_opt(self):
|
|
175
|
+
msg = (
|
|
176
|
+
f"Successful invocations:\n"
|
|
177
|
+
f"\t GEDIIS: {self.successful_gediis}\n"
|
|
178
|
+
f"\t GDIIS: {self.successful_gdiis}\n"
|
|
179
|
+
f"\tLine Search: {self.successful_line_search}\n"
|
|
180
|
+
)
|
|
181
|
+
self.log(msg)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from math import sqrt
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.optimize import root_scalar
|
|
5
|
+
|
|
6
|
+
from pysisyphus.optimizers.HessianOptimizer import HessianOptimizer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RSA(HessianOptimizer):
|
|
10
|
+
"""The Importance of Step Control in Optimization Methods, del Campo, 2009."""
|
|
11
|
+
|
|
12
|
+
def optimize(self):
|
|
13
|
+
energy, gradient, H, big_eigvals, big_eigvecs, resetted = self.housekeeping()
|
|
14
|
+
|
|
15
|
+
assert big_eigvals.argmin() == 0
|
|
16
|
+
min_eigval = big_eigvals[0]
|
|
17
|
+
pos_definite = min_eigval > 0.0
|
|
18
|
+
gradient_trans = big_eigvecs.T.dot(gradient)
|
|
19
|
+
# This will be also be True when we come close to a minimizer,
|
|
20
|
+
# but then the Hessian will also be positive definite and a
|
|
21
|
+
# simple Newton step will be used.
|
|
22
|
+
hard_case = abs(gradient_trans[0]) <= 1e-6
|
|
23
|
+
self.log(f"Smallest eigenvalue: {min_eigval:.6f}")
|
|
24
|
+
self.log(f"Positive definite Hessian: {pos_definite}")
|
|
25
|
+
self.log(f"Hard case: {hard_case}")
|
|
26
|
+
|
|
27
|
+
def get_step(lambda_):
|
|
28
|
+
return -gradient_trans / (big_eigvals + lambda_)
|
|
29
|
+
|
|
30
|
+
# Unshifted Newton step
|
|
31
|
+
newton_step = get_step(0.0)
|
|
32
|
+
newton_norm = np.linalg.norm(newton_step)
|
|
33
|
+
|
|
34
|
+
# def on_trust_radius(step, thresh=1e-3):
|
|
35
|
+
# return abs(self.trust_radius - np.linalg.norm(step)) <= thresh
|
|
36
|
+
|
|
37
|
+
def on_trust_radius_lin(step):
|
|
38
|
+
return 1 / self.trust_radius - 1 / np.linalg.norm(step)
|
|
39
|
+
|
|
40
|
+
def finalize_step(lambda_):
|
|
41
|
+
step = get_step(lambda_)
|
|
42
|
+
step = big_eigvecs.dot(step)
|
|
43
|
+
predicted_change = step.dot(gradient) + 0.5 * step.dot(H).dot(step)
|
|
44
|
+
self.predicted_energy_changes.append(predicted_change)
|
|
45
|
+
return step
|
|
46
|
+
|
|
47
|
+
# Simplest case. Positive definite Hessian and predicted step is
|
|
48
|
+
# already in trust radius.
|
|
49
|
+
if pos_definite and newton_norm <= self.trust_radius:
|
|
50
|
+
lambda_ = 0.0
|
|
51
|
+
self.log("Using unshifted Newton step.")
|
|
52
|
+
return finalize_step(lambda_)
|
|
53
|
+
|
|
54
|
+
# If the Hessian is not positive definite or if the step is too
|
|
55
|
+
# long we have to determine the shift parameter lambda.
|
|
56
|
+
rs_kwargs = {
|
|
57
|
+
"f": lambda lambda_: on_trust_radius_lin(get_step(lambda_)),
|
|
58
|
+
"xtol": 1e-3,
|
|
59
|
+
# Would otherwise be chosen automatically, but we set it
|
|
60
|
+
# here explicitly for verbosity.
|
|
61
|
+
"method": "brentq",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def root_search(bracket):
|
|
65
|
+
rs_kwargs.update(
|
|
66
|
+
{
|
|
67
|
+
"bracket": bracket,
|
|
68
|
+
"x0": bracket[0] + 1e-3,
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
res = root_scalar(**rs_kwargs)
|
|
72
|
+
return res
|
|
73
|
+
|
|
74
|
+
BRACKET_END = 1e10
|
|
75
|
+
if not hard_case:
|
|
76
|
+
bracket_start = 0.0 if pos_definite else -min_eigval - 1e-3
|
|
77
|
+
bracket = (bracket_start, BRACKET_END)
|
|
78
|
+
res = root_search(bracket)
|
|
79
|
+
assert res.converged
|
|
80
|
+
return finalize_step(res.root)
|
|
81
|
+
|
|
82
|
+
# Hard case.
|
|
83
|
+
# First we try the bracket (-b1, ∞)
|
|
84
|
+
bracket = (-min_eigval, BRACKET_END)
|
|
85
|
+
res = root_search(bracket)
|
|
86
|
+
if res.converged:
|
|
87
|
+
return finalize_step(res.root)
|
|
88
|
+
|
|
89
|
+
# Now we would try the bracket (-b2, -b1). The resulting step should have
|
|
90
|
+
# a suitable length, but the (shifted) Hessian would have an incorrect
|
|
91
|
+
# eigenvalue spectrum (not positive definite). To solve this we use a
|
|
92
|
+
# different formula to calculate the step.
|
|
93
|
+
without_first = gradient_trans[1:] / (big_eigvals[1:] - min_eigval)
|
|
94
|
+
tau = sqrt(self.trust_radius ** 2 - (without_first ** 2).sum())
|
|
95
|
+
step_trans = [tau] + -without_first.tolist()
|
|
96
|
+
step = big_eigvecs.dot(step_trans)
|
|
97
|
+
predicted_change = step.dot(gradient) + 0.5 * step.dot(H).dot(step)
|
|
98
|
+
self.predicted_energy_changes.append(predicted_change)
|
|
99
|
+
return step
|