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,228 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from pysisyphus.Geometry import Geometry
|
|
6
|
+
from pysisyphus.intcoords.exceptions import NeedNewInternalsException
|
|
7
|
+
from pysisyphus.optimizers.closures import bfgs_multiply, get_update_mu_reg
|
|
8
|
+
from pysisyphus.optimizers.hessian_updates import double_damp
|
|
9
|
+
from pysisyphus.optimizers.Optimizer import Optimizer
|
|
10
|
+
from pysisyphus.optimizers.restrict_step import scale_by_max_step
|
|
11
|
+
from pysisyphus.optimizers.poly_fit import poly_line_search
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LBFGS(Optimizer):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
geometry: Geometry,
|
|
18
|
+
keep_last: int = 7,
|
|
19
|
+
beta: float = 1,
|
|
20
|
+
max_step: float = 0.2,
|
|
21
|
+
double_damp: bool = True,
|
|
22
|
+
gamma_mult: bool = False,
|
|
23
|
+
line_search: bool = False,
|
|
24
|
+
mu_reg: Optional[float] = None,
|
|
25
|
+
max_mu_reg_adaptions: int = 10,
|
|
26
|
+
control_step: bool = True,
|
|
27
|
+
**kwargs,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Limited-memory BFGS optimizer.
|
|
30
|
+
|
|
31
|
+
See [1] Nocedal, Wright - Numerical Optimization, 2006 for a general
|
|
32
|
+
discussion of LBFGS. See pysisyphus.optimizers.hessian_updates for
|
|
33
|
+
the references related to double damping and pysisyphus.optimizers.closures
|
|
34
|
+
for references related to regularized LBFGS.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
geometry
|
|
39
|
+
Geometry to be optimized.
|
|
40
|
+
keep_last
|
|
41
|
+
History size. Keep last 'keep_last' steps and gradient differences.
|
|
42
|
+
beta
|
|
43
|
+
Force constant β in -(H + βI)⁻¹g.
|
|
44
|
+
max_step
|
|
45
|
+
Upper limit for the absolute component of the step vector in whatever
|
|
46
|
+
unit the optimization is carried out.
|
|
47
|
+
double_damp
|
|
48
|
+
Use double damping procedure to modify steps s and gradient differences y
|
|
49
|
+
to ensure sy > 0.
|
|
50
|
+
gamma_mult
|
|
51
|
+
Estimate β from previous cycle. Eq. (7.20) in [1]. See 'beta' argument.
|
|
52
|
+
line_search
|
|
53
|
+
Enable implicit linesearches.
|
|
54
|
+
mu_reg
|
|
55
|
+
Initial guess for regularization constant in regularized LBFGS.
|
|
56
|
+
max_mu_reg_adaptions
|
|
57
|
+
Maximum number of trial steps in regularized LBFGS.
|
|
58
|
+
control_step
|
|
59
|
+
Wheter to scale down the proposed step its biggest absolute component
|
|
60
|
+
is equal to or below 'max_step'
|
|
61
|
+
|
|
62
|
+
Other Parameters
|
|
63
|
+
----------------
|
|
64
|
+
**kwargs
|
|
65
|
+
Keyword arguments passed to the Optimizer baseclass.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
self.coord_diffs = list()
|
|
69
|
+
self.grad_diffs = list()
|
|
70
|
+
super().__init__(geometry, max_step=max_step, **kwargs)
|
|
71
|
+
|
|
72
|
+
self.beta = beta
|
|
73
|
+
self.keep_last = int(keep_last)
|
|
74
|
+
self.double_damp = double_damp
|
|
75
|
+
self.gamma_mult = gamma_mult
|
|
76
|
+
self.mu_reg = mu_reg
|
|
77
|
+
self.max_mu_reg_adaptions = max_mu_reg_adaptions
|
|
78
|
+
self.line_search = (not self.is_cos) and line_search
|
|
79
|
+
self.control_step = control_step
|
|
80
|
+
|
|
81
|
+
self.tot_adapt_mu_cycles = 0
|
|
82
|
+
if self.mu_reg:
|
|
83
|
+
self.mu_reg_0 = self.mu_reg # Backup value
|
|
84
|
+
self.update_mu_reg = get_update_mu_reg(logger=self.logger)
|
|
85
|
+
self.control_step = False
|
|
86
|
+
self.double_damp = False
|
|
87
|
+
self.line_search = False
|
|
88
|
+
self.log(
|
|
89
|
+
f"Regularized-L-BFGS (μ_reg={self.mu_reg:.6f}) requested.\n"
|
|
90
|
+
f"Disabling double damping, step control and line search."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def reset(self):
|
|
94
|
+
self.coord_diffs = list()
|
|
95
|
+
self.grad_diffs = list()
|
|
96
|
+
|
|
97
|
+
def _get_opt_restart_info(self):
|
|
98
|
+
opt_restart_info = {
|
|
99
|
+
"coord_diffs": np.array(self.coord_diffs).tolist(),
|
|
100
|
+
"grad_diffs": np.array(self.grad_diffs).tolist(),
|
|
101
|
+
"double_damp": self.double_damp,
|
|
102
|
+
"gamma_mult": self.gamma_mult,
|
|
103
|
+
"keep_last": self.keep_last,
|
|
104
|
+
}
|
|
105
|
+
return opt_restart_info
|
|
106
|
+
|
|
107
|
+
def _set_opt_restart_info(self, opt_restart_info):
|
|
108
|
+
self.coord_diffs = [np.array(cd) for cd in opt_restart_info["coord_diffs"]]
|
|
109
|
+
self.grad_diffs = [np.array(gd) for gd in opt_restart_info["grad_diffs"]]
|
|
110
|
+
for attr in ("double_damp", "gamma_mult", "keep_last"):
|
|
111
|
+
setattr(self, attr, opt_restart_info[attr])
|
|
112
|
+
|
|
113
|
+
def get_lbfgs_step(self, forces):
|
|
114
|
+
return bfgs_multiply(
|
|
115
|
+
self.coord_diffs,
|
|
116
|
+
self.grad_diffs,
|
|
117
|
+
forces,
|
|
118
|
+
beta=self.beta,
|
|
119
|
+
gamma_mult=self.gamma_mult,
|
|
120
|
+
mu_reg=self.mu_reg,
|
|
121
|
+
logger=self.logger,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def optimize(self):
|
|
125
|
+
if self.is_cos and self.align:
|
|
126
|
+
rot_vecs, rot_vec_lists, _ = self.fit_rigid(
|
|
127
|
+
vector_lists=(
|
|
128
|
+
self.steps,
|
|
129
|
+
self.forces,
|
|
130
|
+
self.coord_diffs,
|
|
131
|
+
self.grad_diffs,
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
rot_steps, rot_forces, rot_coord_diffs, rot_grad_diffs = rot_vec_lists
|
|
135
|
+
self.steps = rot_steps
|
|
136
|
+
self.forces = rot_forces
|
|
137
|
+
self.coord_diffs = rot_coord_diffs
|
|
138
|
+
self.grad_diffs = rot_grad_diffs
|
|
139
|
+
|
|
140
|
+
forces = self.geometry.forces
|
|
141
|
+
self.forces.append(forces)
|
|
142
|
+
energy = self.geometry.energy
|
|
143
|
+
self.energies.append(energy)
|
|
144
|
+
norm = np.linalg.norm(forces)
|
|
145
|
+
if not self.is_cos:
|
|
146
|
+
self.log(f" Energy={energy: >24.6f} au")
|
|
147
|
+
self.log(f"norm(forces)={norm: >24.6f} au / bohr (rad)")
|
|
148
|
+
|
|
149
|
+
if self.cur_cycle > 0 and (self.forces[-2].size == forces.size):
|
|
150
|
+
y = self.forces[-2] - forces
|
|
151
|
+
s = self.steps[-1]
|
|
152
|
+
if self.double_damp:
|
|
153
|
+
s, y = double_damp(
|
|
154
|
+
s, y, s_list=self.coord_diffs, y_list=self.grad_diffs
|
|
155
|
+
)
|
|
156
|
+
self.grad_diffs.append(y)
|
|
157
|
+
self.coord_diffs.append(s)
|
|
158
|
+
|
|
159
|
+
# Drop superfluous oldest vectors
|
|
160
|
+
self.coord_diffs = self.coord_diffs[-self.keep_last :]
|
|
161
|
+
self.grad_diffs = self.grad_diffs[-self.keep_last :]
|
|
162
|
+
|
|
163
|
+
###############
|
|
164
|
+
# Line search #
|
|
165
|
+
###############
|
|
166
|
+
|
|
167
|
+
ip_gradient, ip_step = None, None
|
|
168
|
+
if self.line_search and (self.cur_cycle > 0):
|
|
169
|
+
ip_energy, ip_gradient, ip_step = poly_line_search(
|
|
170
|
+
energy, self.energies[-2], -forces, -self.forces[-2], self.steps[-1]
|
|
171
|
+
)
|
|
172
|
+
# Use the interpolated gradient for the step if interpolation succeeded
|
|
173
|
+
if (ip_gradient is not None) and (ip_step is not None):
|
|
174
|
+
forces = -ip_gradient
|
|
175
|
+
self.log("Interpolation succeeded")
|
|
176
|
+
# Keep the original gradient when the interpolation failed, but use
|
|
177
|
+
# zero (interpolated) step.
|
|
178
|
+
else:
|
|
179
|
+
ip_step = np.zeros_like(forces)
|
|
180
|
+
|
|
181
|
+
step = self.get_lbfgs_step(forces)
|
|
182
|
+
|
|
183
|
+
# Skip adapation mu_reg in first cycle, because in this implementation
|
|
184
|
+
# the first step will be steepest descent, which is independent of
|
|
185
|
+
# self.mu_reg, so this loop would never break.
|
|
186
|
+
adapt_mu_cycles = 0
|
|
187
|
+
while self.mu_reg and (self.cur_cycle > 0):
|
|
188
|
+
self.log(
|
|
189
|
+
f"Adapt μ_reg={self.mu_reg:.6f}, norm(step)={np.linalg.norm(step):.6f}"
|
|
190
|
+
)
|
|
191
|
+
if adapt_mu_cycles == self.max_mu_reg_adaptions:
|
|
192
|
+
raise Exception("Adapation of mu_reg failed! Breaking!")
|
|
193
|
+
try:
|
|
194
|
+
trial_energy = self.geometry.get_energy_at(self.geometry.coords + step)
|
|
195
|
+
self.mu_reg, recompute_step = self.update_mu_reg(
|
|
196
|
+
self.mu_reg, energy, trial_energy, -forces, step
|
|
197
|
+
)
|
|
198
|
+
except NeedNewInternalsException:
|
|
199
|
+
self.log("Internal coordinate breakdown in linesearch!")
|
|
200
|
+
# Nothing further is done here, as the coordinates will probably also
|
|
201
|
+
# breakdown when taking the step, which is then handled.
|
|
202
|
+
recompute_step = False
|
|
203
|
+
|
|
204
|
+
# Leave loop if step was accepted
|
|
205
|
+
if not recompute_step:
|
|
206
|
+
self.log(f"Next μ_reg={self.mu_reg:.6f}")
|
|
207
|
+
break
|
|
208
|
+
# Otherwise, recompute using updated μ_reg
|
|
209
|
+
step = self.get_lbfgs_step(forces)
|
|
210
|
+
adapt_mu_cycles += 1
|
|
211
|
+
|
|
212
|
+
if self.mu_reg:
|
|
213
|
+
self.tot_adapt_mu_cycles += adapt_mu_cycles + 1
|
|
214
|
+
|
|
215
|
+
# Form full step. If we did not interpolate or interpolation failed,
|
|
216
|
+
# ip_step will be zero.
|
|
217
|
+
step = step + ip_step
|
|
218
|
+
|
|
219
|
+
# Only try to scale down first step in regularized L-BFGS
|
|
220
|
+
if (self.mu_reg and self.cur_cycle == 0) or self.control_step:
|
|
221
|
+
step = scale_by_max_step(step, self.max_step)
|
|
222
|
+
|
|
223
|
+
return step
|
|
224
|
+
|
|
225
|
+
def postprocess_opt(self):
|
|
226
|
+
if self.mu_reg:
|
|
227
|
+
msg = f"\nNumber of μ updates: {self.tot_adapt_mu_cycles}"
|
|
228
|
+
self.log(msg)
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# [1] https://doi.org/10.1002/jcc.10156
|
|
2
|
+
# Geometry optimization with QM/MM, ONIOM, and other combined methods.
|
|
3
|
+
# I. Microiterations and constraints
|
|
4
|
+
# Vreven, Morokuma, Farkas, Schlegel, Frisch, 2003
|
|
5
|
+
# [2] https://doi.org/10.1039/A909486E
|
|
6
|
+
# Linear scaling geometry optimisation and transition state search
|
|
7
|
+
# in hybrid delocalised internal coordinates
|
|
8
|
+
# Billeter, Turner, Thiel, 2000
|
|
9
|
+
import logging
|
|
10
|
+
from pprint import pprint
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from pysisyphus.calculators import IPIServer, ONIOM
|
|
15
|
+
from pysisyphus.Geometry import Geometry
|
|
16
|
+
from pysisyphus.intcoords.exceptions import RebuiltInternalsException
|
|
17
|
+
from pysisyphus.helpers_pure import full_expand, highlight_text, recursive_update
|
|
18
|
+
from pysisyphus.optimizers.Optimizer import Optimizer
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_geom_kwargs(layer_ind, layer_mask):
|
|
22
|
+
coord_type = "tric" if layer_ind == 0 else "cartesian"
|
|
23
|
+
all_indices = np.arange(layer_mask.size)
|
|
24
|
+
freeze_atoms = all_indices[layer_mask]
|
|
25
|
+
geom_kwargs = {
|
|
26
|
+
"type": coord_type,
|
|
27
|
+
"freeze_atoms": freeze_atoms,
|
|
28
|
+
"coord_kwargs": {
|
|
29
|
+
"freeze_atoms_exclude": layer_ind == 0,
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
return geom_kwargs
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_opt_kwargs(opt_key, layer_ind, thresh):
|
|
36
|
+
# Some defaults tailored to LayerOpt
|
|
37
|
+
opt_defaults = {
|
|
38
|
+
"lbfgs": {
|
|
39
|
+
"mu_reg": 0.1,
|
|
40
|
+
},
|
|
41
|
+
"plbfgs": {
|
|
42
|
+
"precon_kind": "full_fast",
|
|
43
|
+
"precon_update": 50,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
if layer_ind == 0:
|
|
47
|
+
opt_kwargs = {
|
|
48
|
+
"type": "rfo",
|
|
49
|
+
"thresh": "never",
|
|
50
|
+
# "prefix": "model",
|
|
51
|
+
# "dump": True,
|
|
52
|
+
# "h5_group_name": "model_opt",
|
|
53
|
+
}
|
|
54
|
+
else:
|
|
55
|
+
if opt_key is None:
|
|
56
|
+
opt_key = "lbfgs"
|
|
57
|
+
|
|
58
|
+
opt_kwargs = {
|
|
59
|
+
"type": opt_key,
|
|
60
|
+
"max_cycles": 1_500,
|
|
61
|
+
"thresh": thresh,
|
|
62
|
+
"overachieve_factor": 3,
|
|
63
|
+
# "prefix": f"layer_{layer_ind:02d}",
|
|
64
|
+
# "dump": True,
|
|
65
|
+
}
|
|
66
|
+
try:
|
|
67
|
+
opt_kwargs.update(opt_defaults[opt_key])
|
|
68
|
+
except KeyError:
|
|
69
|
+
pass
|
|
70
|
+
return opt_kwargs
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
logger = logging.getLogger("optimizer")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Layers:
|
|
77
|
+
def __init__(self, geometry, opt_thresh, layers=None):
|
|
78
|
+
self.geometry = geometry
|
|
79
|
+
self.layers = layers
|
|
80
|
+
self.opt_thresh = opt_thresh
|
|
81
|
+
|
|
82
|
+
print(highlight_text("Layers", level=1))
|
|
83
|
+
pprint(self.layers, compact=True)
|
|
84
|
+
print("")
|
|
85
|
+
|
|
86
|
+
atoms = geometry.atoms
|
|
87
|
+
all_indices = np.arange(len(geometry.atoms))
|
|
88
|
+
# Boolean array; every item corresponds to an atom in the total system.
|
|
89
|
+
freeze_mask = np.full_like(all_indices, True, dtype=bool)
|
|
90
|
+
|
|
91
|
+
self.indices = list()
|
|
92
|
+
self.geom_getters = list()
|
|
93
|
+
self.opt_getters = list()
|
|
94
|
+
|
|
95
|
+
# We import 'get_opt_cls' in the constructor, as it relies on dicts, that
|
|
96
|
+
# contain references to this class (LayerOpt). Trying to import 'get_opt_cls'
|
|
97
|
+
# at the top of the module will result in an circular import.
|
|
98
|
+
from pysisyphus.optimizers.cls_map import get_opt_cls
|
|
99
|
+
|
|
100
|
+
indices_below = set()
|
|
101
|
+
# Iterate in reverse order from smallest (lowest) layer to biggest (highest) layer.
|
|
102
|
+
# to setup geom_getter and opt_getter.
|
|
103
|
+
for i, layer in enumerate(layers[::-1]):
|
|
104
|
+
try:
|
|
105
|
+
indices = full_expand(layer["indices"])
|
|
106
|
+
# Allow missing 'indices' key. Then it is assumed that this layer contains the
|
|
107
|
+
# whole system.
|
|
108
|
+
except KeyError:
|
|
109
|
+
assert (
|
|
110
|
+
i != 0
|
|
111
|
+
), "Found whole system in highest level layer. I don't like that!"
|
|
112
|
+
indices = all_indices
|
|
113
|
+
# Drop indices from layers below ...
|
|
114
|
+
indices = sorted(set(indices) - indices_below)
|
|
115
|
+
# ... and update 'indices_below' for the next layer
|
|
116
|
+
indices_below.update(set(indices))
|
|
117
|
+
self.indices.append(indices)
|
|
118
|
+
# Set up mask that indicates which atoms to freeze in the current layer (
|
|
119
|
+
# all atoms that are not in the current layer.)
|
|
120
|
+
layer_mask = freeze_mask.copy()
|
|
121
|
+
layer_mask[indices] = False
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
try:
|
|
125
|
+
calc_kwargs = {
|
|
126
|
+
"address": layer["address"],
|
|
127
|
+
}
|
|
128
|
+
except KeyError:
|
|
129
|
+
_calc_kwargs = layer["calc"]
|
|
130
|
+
calc_kwargs = recursive_update({}, _calc_kwargs)
|
|
131
|
+
# The popped calc key is currently unused as using an IPIServer is
|
|
132
|
+
# mandatory.
|
|
133
|
+
_ = calc_kwargs.pop("type", None)
|
|
134
|
+
calc = IPIServer(**calc_kwargs)
|
|
135
|
+
# When calculating layer 0 we have access to the true energy of the system.
|
|
136
|
+
# So we assign the calculator of layer0 to the actual geometry containing
|
|
137
|
+
# the whole system.
|
|
138
|
+
if i == 0:
|
|
139
|
+
geometry.set_calculator(calc)
|
|
140
|
+
# If no address is given, we assume that pysisyphus' ONIOM calculator
|
|
141
|
+
# is used.
|
|
142
|
+
except KeyError as err:
|
|
143
|
+
try:
|
|
144
|
+
calc = layer["layer_calc"]
|
|
145
|
+
except KeyError:
|
|
146
|
+
print(
|
|
147
|
+
"Currently, a socket address for an IPI-protol client is mandatory!"
|
|
148
|
+
)
|
|
149
|
+
raise err
|
|
150
|
+
|
|
151
|
+
####################
|
|
152
|
+
# Geometry setup #
|
|
153
|
+
####################
|
|
154
|
+
|
|
155
|
+
_geom_kwargs = layer.get("geom", dict())
|
|
156
|
+
_geom_kwargs = recursive_update({}, _geom_kwargs)
|
|
157
|
+
geom_kwargs = get_geom_kwargs(i, layer_mask=layer_mask)
|
|
158
|
+
try:
|
|
159
|
+
geom_kwargs.update(_geom_kwargs)
|
|
160
|
+
# Allow empty "geom:" block
|
|
161
|
+
except TypeError:
|
|
162
|
+
pass
|
|
163
|
+
coord_type = geom_kwargs.pop("type")
|
|
164
|
+
|
|
165
|
+
# Geometry
|
|
166
|
+
def get_geom_getter(persistent_geom=None):
|
|
167
|
+
# Rebind the variables here, otherwise the wrong geom_kwargs
|
|
168
|
+
# and calc will be used, as they are redefined in the next loop cycle.
|
|
169
|
+
layer_geom_kwargs = geom_kwargs.copy()
|
|
170
|
+
layer_calc = calc
|
|
171
|
+
|
|
172
|
+
def get_geom(coords3d):
|
|
173
|
+
if persistent_geom is not None:
|
|
174
|
+
geom = persistent_geom
|
|
175
|
+
geom.coords3d = coords3d
|
|
176
|
+
else:
|
|
177
|
+
geom = Geometry(
|
|
178
|
+
atoms,
|
|
179
|
+
coords3d.copy(),
|
|
180
|
+
coord_type=coord_type,
|
|
181
|
+
**layer_geom_kwargs,
|
|
182
|
+
)
|
|
183
|
+
geom.set_calculator(layer_calc)
|
|
184
|
+
return geom
|
|
185
|
+
|
|
186
|
+
return get_geom
|
|
187
|
+
|
|
188
|
+
get_geom = get_geom_getter()
|
|
189
|
+
# Use a persistent Geometry for the layer 0. Overwrite the function
|
|
190
|
+
# above with a definition that always returns the same geometry.
|
|
191
|
+
if i == 0:
|
|
192
|
+
geom0 = get_geom(geometry.coords3d)
|
|
193
|
+
get_geom = get_geom_getter(geom0)
|
|
194
|
+
|
|
195
|
+
self.geom_getters.append(get_geom)
|
|
196
|
+
|
|
197
|
+
#####################
|
|
198
|
+
# Optimizer setup #
|
|
199
|
+
#####################
|
|
200
|
+
|
|
201
|
+
_opt_kwargs = layer.get("opt", dict())
|
|
202
|
+
_opt_kwargs = recursive_update({}, _opt_kwargs)
|
|
203
|
+
opt_key = _opt_kwargs.get("type", None)
|
|
204
|
+
opt_kwargs = get_opt_kwargs(opt_key, i, thresh=self.opt_thresh)
|
|
205
|
+
try:
|
|
206
|
+
opt_kwargs.update(_opt_kwargs)
|
|
207
|
+
# Allow empty "opt:" block
|
|
208
|
+
except TypeError:
|
|
209
|
+
pass
|
|
210
|
+
opt_key = opt_kwargs.pop("type")
|
|
211
|
+
opt_cls = get_opt_cls(opt_key)
|
|
212
|
+
|
|
213
|
+
def get_opt_getter():
|
|
214
|
+
layer_opt_kwargs = opt_kwargs
|
|
215
|
+
layer_opt_cls = opt_cls
|
|
216
|
+
|
|
217
|
+
def get_opt(geom, **kwargs):
|
|
218
|
+
kwargs_ = layer_opt_kwargs.copy()
|
|
219
|
+
kwargs_.update(kwargs)
|
|
220
|
+
opt = layer_opt_cls(geom, **kwargs_)
|
|
221
|
+
return opt
|
|
222
|
+
|
|
223
|
+
return get_opt
|
|
224
|
+
|
|
225
|
+
get_opt = get_opt_getter()
|
|
226
|
+
# Persistent optimizer for most expensive layer.
|
|
227
|
+
if i == 0:
|
|
228
|
+
model_opt = get_opt(geom0)
|
|
229
|
+
|
|
230
|
+
def get_opt(geom, **kwargs):
|
|
231
|
+
return model_opt
|
|
232
|
+
|
|
233
|
+
self.opt_getters.append(get_opt)
|
|
234
|
+
|
|
235
|
+
# Most expensive layer will come last.
|
|
236
|
+
self.indices = self.indices[::-1]
|
|
237
|
+
self.geom_getters = self.geom_getters[::-1]
|
|
238
|
+
self.opt_getters = self.opt_getters[::-1]
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def from_oniom_calculator(cls, geometry, oniom_calc=None, layers=None, **kwargs):
|
|
242
|
+
calc = geometry.calculator
|
|
243
|
+
if calc is None:
|
|
244
|
+
calc = oniom_calc
|
|
245
|
+
|
|
246
|
+
if layers is not None:
|
|
247
|
+
assert len(layers) == len(calc.layers), (
|
|
248
|
+
f"ONIOM calculator has {len(calc.layers)} layers, but only "
|
|
249
|
+
f"{len(layers)} layer were defined in 'layers:'!"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
layers = list(layers)
|
|
253
|
+
for i, layer in enumerate(layers):
|
|
254
|
+
if layer is None:
|
|
255
|
+
layers[i] = dict()
|
|
256
|
+
elif isinstance(layer, dict):
|
|
257
|
+
pass
|
|
258
|
+
else:
|
|
259
|
+
raise Exception("Layer definition must be empty or dict-like!")
|
|
260
|
+
else:
|
|
261
|
+
layers = [dict() for _ in calc.layers]
|
|
262
|
+
|
|
263
|
+
for i, layer in enumerate(calc.layers):
|
|
264
|
+
assert len(layer) == 1, "Multicenter-ONIOM is not yet supported!"
|
|
265
|
+
model = layer[0]
|
|
266
|
+
link_hosts = [link.parent_ind for link in model.links]
|
|
267
|
+
indices = model.atom_inds + link_hosts
|
|
268
|
+
layer_calc = calc.get_layer_calc(i)
|
|
269
|
+
layer = {
|
|
270
|
+
"layer_calc": layer_calc,
|
|
271
|
+
"indices": indices,
|
|
272
|
+
}
|
|
273
|
+
layers[i].update(layer)
|
|
274
|
+
return Layers(geometry, layers=layers, **kwargs)
|
|
275
|
+
|
|
276
|
+
def __len__(self):
|
|
277
|
+
return len(self.layers)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class LayerOpt(Optimizer):
|
|
281
|
+
def __init__(
|
|
282
|
+
self,
|
|
283
|
+
geometry: Geometry,
|
|
284
|
+
layers: dict = None,
|
|
285
|
+
**kwargs,
|
|
286
|
+
) -> None:
|
|
287
|
+
super().__init__(geometry, **kwargs)
|
|
288
|
+
assert geometry.coord_type in ("cart", "cartesian")
|
|
289
|
+
|
|
290
|
+
layers_kwargs = {
|
|
291
|
+
"geometry": self.geometry,
|
|
292
|
+
"opt_thresh": self.thresh,
|
|
293
|
+
"layers": layers,
|
|
294
|
+
}
|
|
295
|
+
# Construct layers from ONIOM calculator
|
|
296
|
+
if isinstance(self.geometry.calculator, ONIOM):
|
|
297
|
+
layers = Layers.from_oniom_calculator(**layers_kwargs)
|
|
298
|
+
else:
|
|
299
|
+
layers = Layers(**layers_kwargs)
|
|
300
|
+
self.layers = layers
|
|
301
|
+
|
|
302
|
+
self.micro_cycles = list()
|
|
303
|
+
self.micro_cycles_converged = list()
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def layer_num(self) -> int:
|
|
307
|
+
return len(self.layers)
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def model_opt(self):
|
|
311
|
+
"""Return the persistent optimizer belonging to the model system.
|
|
312
|
+
We don't have to supply any coordinates to the optimizer of the
|
|
313
|
+
most expensive layer, as it is persistent, as well as the associated geometry."""
|
|
314
|
+
return self.layers.opt_getters[-1](None)
|
|
315
|
+
|
|
316
|
+
def optimize(self) -> None:
|
|
317
|
+
coords3d_org = self.geometry.coords3d.copy()
|
|
318
|
+
coords3d_cur = coords3d_org.copy()
|
|
319
|
+
cur_micro_cycles = list()
|
|
320
|
+
cur_micro_cycles_converged = list()
|
|
321
|
+
for i, (indices, get_geom, get_opt) in enumerate(
|
|
322
|
+
zip(self.layers.indices, self.layers.geom_getters, self.layers.opt_getters)
|
|
323
|
+
):
|
|
324
|
+
print(highlight_text(f"Layer {i}", level=1))
|
|
325
|
+
is_last_layer = i == self.layer_num - 1
|
|
326
|
+
geom = get_geom(coords3d_cur)
|
|
327
|
+
prefix = f"mc{self.cur_cycle:03d}"
|
|
328
|
+
opt = get_opt(geom, prefix=prefix, h5_group_name=f"{prefix}_opt")
|
|
329
|
+
if is_last_layer:
|
|
330
|
+
if self.cur_cycle == 0:
|
|
331
|
+
opt.prepare_opt()
|
|
332
|
+
break
|
|
333
|
+
opt.run()
|
|
334
|
+
cur_micro_cycles.append(opt.cur_cycle + 1)
|
|
335
|
+
cur_micro_cycles_converged.append(opt.is_converged)
|
|
336
|
+
coords3d_cur[indices] = geom.coords3d[indices]
|
|
337
|
+
self.micro_cycles.append(cur_micro_cycles)
|
|
338
|
+
self.micro_cycles_converged.append(cur_micro_cycles_converged)
|
|
339
|
+
|
|
340
|
+
####################
|
|
341
|
+
# Relax last layer #
|
|
342
|
+
####################
|
|
343
|
+
|
|
344
|
+
# 'geom' and 'indices' for the last layer were defined in the for-loop
|
|
345
|
+
# above, before breaking from the loop.
|
|
346
|
+
#
|
|
347
|
+
# Calculate forces and energy of the last layer. This have to be the "true"
|
|
348
|
+
# ONIOM forces of the system, containing all contributions. That's why we
|
|
349
|
+
# also save them as the true forces in the optimizer.
|
|
350
|
+
cart_forces = geom.cart_forces
|
|
351
|
+
energy = geom.energy
|
|
352
|
+
self.energies.append(energy)
|
|
353
|
+
self.forces.append(cart_forces.copy())
|
|
354
|
+
|
|
355
|
+
# Also store relevant quantities in the optimizer of the last layer, so
|
|
356
|
+
# things like Hessian updates are possible. These quantities are usually
|
|
357
|
+
# stored in the big optimization-loop in Optimizer.run(). As run() is
|
|
358
|
+
# never called for the last optimizer we have to store them manually.
|
|
359
|
+
opt.coords.append(geom.coords.copy())
|
|
360
|
+
opt.cart_coords.append(geom.cart_coords.copy())
|
|
361
|
+
|
|
362
|
+
# Calculate one step
|
|
363
|
+
opt.cur_cycle = self.cur_cycle
|
|
364
|
+
int_step = opt.optimize()
|
|
365
|
+
opt.steps.append(int_step)
|
|
366
|
+
try:
|
|
367
|
+
geom.coords = geom.coords + int_step
|
|
368
|
+
except RebuiltInternalsException:
|
|
369
|
+
geom.reset_coords()
|
|
370
|
+
opt.reset()
|
|
371
|
+
coords3d_cur[indices] = geom.coords3d[indices]
|
|
372
|
+
|
|
373
|
+
full_step = coords3d_cur - coords3d_org
|
|
374
|
+
return full_step.flatten()
|
|
375
|
+
|
|
376
|
+
def check_convergence(self, *args, **kwargs):
|
|
377
|
+
"""Check if we must use the model optimizer to signal convergence."""
|
|
378
|
+
opt = self.model_opt
|
|
379
|
+
if opt.thresh != "never":
|
|
380
|
+
return opt.check_convergence()
|
|
381
|
+
else:
|
|
382
|
+
return super().check_convergence(*args, **kwargs)
|
|
383
|
+
|
|
384
|
+
def print_opt_progress(self, *args, **kwargs):
|
|
385
|
+
"""Pick the correct method to report opt_progress.
|
|
386
|
+
|
|
387
|
+
When the model optimizer decides convergence we also report optimization
|
|
388
|
+
progress using its data and not the data from LayerOpt, where the total
|
|
389
|
+
ONIOM gradient is stored."""
|
|
390
|
+
opt = self.model_opt
|
|
391
|
+
if opt.thresh != "never":
|
|
392
|
+
func = opt.print_opt_progress
|
|
393
|
+
else:
|
|
394
|
+
func = super().print_opt_progress
|
|
395
|
+
func(*args, **kwargs)
|
|
396
|
+
|
|
397
|
+
def postprocess_opt(self) -> None:
|
|
398
|
+
coord_types = list()
|
|
399
|
+
for layer in self.layers.layers:
|
|
400
|
+
try:
|
|
401
|
+
coord_type = layer["geom"]["type"]
|
|
402
|
+
coord_types.append(coord_type)
|
|
403
|
+
except KeyError:
|
|
404
|
+
pass
|
|
405
|
+
micro_sum = np.array(self.micro_cycles).sum()
|
|
406
|
+
print("\nMicrocycles:")
|
|
407
|
+
print("\t", end="")
|
|
408
|
+
pprint(self.micro_cycles)
|
|
409
|
+
print(f"\t@@@ Σ {micro_sum},", ", ".join(coord_types))
|
|
410
|
+
print(f"\t@@@ Macrocycles: {self.cur_cycle+1}, converged? {self.is_converged}")
|
|
411
|
+
print("\t@@@")
|