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
mlmm/irc.py
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
# mlmm/irc.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
ML/MM IRC calculation using the EulerPC predictor-corrector integrator.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
mlmm irc -i ts.pdb --parm real.parm7 --model-pdb ml_region.pdb -q 0
|
|
8
|
+
|
|
9
|
+
For detailed documentation, see: docs/irc.md
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, Optional, List
|
|
16
|
+
|
|
17
|
+
import gc
|
|
18
|
+
import logging
|
|
19
|
+
import sys
|
|
20
|
+
import textwrap
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
import click
|
|
25
|
+
import time
|
|
26
|
+
import torch
|
|
27
|
+
|
|
28
|
+
from pysisyphus.helpers import geom_loader
|
|
29
|
+
from pysisyphus.irc.EulerPC import EulerPC
|
|
30
|
+
from .mlmm_calc import mlmm
|
|
31
|
+
from .freq import _torch_device, _calc_full_hessian_torch, _align_three_layer_hessian_targets
|
|
32
|
+
from .defaults import (
|
|
33
|
+
GEOM_KW_DEFAULT,
|
|
34
|
+
MLMM_CALC_KW as _UMA_CALC_KW,
|
|
35
|
+
IRC_KW,
|
|
36
|
+
)
|
|
37
|
+
from .utils import (
|
|
38
|
+
apply_ref_pdb_override,
|
|
39
|
+
apply_layer_freeze_constraints,
|
|
40
|
+
convert_xyz_to_pdb,
|
|
41
|
+
set_convert_file_enabled,
|
|
42
|
+
is_convert_file_enabled,
|
|
43
|
+
convert_xyz_like_outputs,
|
|
44
|
+
load_yaml_dict,
|
|
45
|
+
deep_update,
|
|
46
|
+
apply_yaml_overrides,
|
|
47
|
+
pretty_block,
|
|
48
|
+
strip_inherited_keys,
|
|
49
|
+
filter_calc_for_echo,
|
|
50
|
+
format_freeze_atoms_for_echo,
|
|
51
|
+
format_elapsed,
|
|
52
|
+
merge_freeze_atom_indices,
|
|
53
|
+
prepare_input_structure,
|
|
54
|
+
resolve_charge_spin_or_raise,
|
|
55
|
+
parse_indices_string,
|
|
56
|
+
build_model_pdb_from_bfactors,
|
|
57
|
+
build_model_pdb_from_indices,
|
|
58
|
+
yaml_section_has_key,
|
|
59
|
+
)
|
|
60
|
+
from .cli_utils import resolve_yaml_sources, load_merged_yaml_cfg, make_is_param_explicit
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# --------------------------
|
|
64
|
+
# Default configuration
|
|
65
|
+
# --------------------------
|
|
66
|
+
|
|
67
|
+
CALC_KW_DEFAULT: Dict[str, Any] = dict(_UMA_CALC_KW)
|
|
68
|
+
|
|
69
|
+
IRC_KW_DEFAULT: Dict[str, Any] = {
|
|
70
|
+
**IRC_KW,
|
|
71
|
+
"dump_fn": "irc_data.h5",
|
|
72
|
+
"dump_every": 5,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _echo_convert_trj_to_pdb_if_exists(trj_path: Path, ref_pdb: Path, out_path: Path) -> None:
|
|
77
|
+
if not is_convert_file_enabled():
|
|
78
|
+
return
|
|
79
|
+
if trj_path.exists():
|
|
80
|
+
try:
|
|
81
|
+
convert_xyz_to_pdb(trj_path, ref_pdb, out_path)
|
|
82
|
+
click.echo(f"[convert] Wrote '{out_path}'.")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.debug("Failed to convert %s to PDB", trj_path.name, exc_info=True)
|
|
85
|
+
click.echo(f"[convert] WARNING: Failed to convert '{trj_path.name}' to PDB: {e}", err=True)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# --------------------------
|
|
89
|
+
# CLI
|
|
90
|
+
# --------------------------
|
|
91
|
+
|
|
92
|
+
@click.command(
|
|
93
|
+
help="Run an IRC calculation with EulerPC. Only the documented CLI options are accepted; all other settings come from YAML.",
|
|
94
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
95
|
+
)
|
|
96
|
+
@click.option(
|
|
97
|
+
"-i", "--input",
|
|
98
|
+
"input_path",
|
|
99
|
+
type=click.Path(path_type=Path, exists=True, dir_okay=False),
|
|
100
|
+
required=True,
|
|
101
|
+
help="Input structure file (.pdb, .xyz, _trj.xyz, etc.).",
|
|
102
|
+
)
|
|
103
|
+
@click.option(
|
|
104
|
+
"--parm",
|
|
105
|
+
"real_parm7",
|
|
106
|
+
type=click.Path(path_type=Path, exists=True, dir_okay=False),
|
|
107
|
+
required=False,
|
|
108
|
+
help="Amber parm7 topology for the whole enzyme (MM region). "
|
|
109
|
+
"If omitted, must be provided in YAML as calc.real_parm7.",
|
|
110
|
+
)
|
|
111
|
+
@click.option(
|
|
112
|
+
"--model-pdb",
|
|
113
|
+
"model_pdb",
|
|
114
|
+
type=click.Path(path_type=Path, exists=True, dir_okay=False),
|
|
115
|
+
required=False,
|
|
116
|
+
help="PDB defining atoms belonging to the ML region. Optional when --detect-layer is enabled.",
|
|
117
|
+
)
|
|
118
|
+
@click.option(
|
|
119
|
+
"--model-indices",
|
|
120
|
+
"model_indices_str",
|
|
121
|
+
type=str,
|
|
122
|
+
default=None,
|
|
123
|
+
show_default=False,
|
|
124
|
+
help="Comma-separated atom indices for the ML region (ranges allowed like 1-5). "
|
|
125
|
+
"Used when --model-pdb is omitted.",
|
|
126
|
+
)
|
|
127
|
+
@click.option(
|
|
128
|
+
"--model-indices-one-based/--model-indices-zero-based",
|
|
129
|
+
"model_indices_one_based",
|
|
130
|
+
default=True,
|
|
131
|
+
show_default=True,
|
|
132
|
+
help="Interpret --model-indices as 1-based (default) or 0-based.",
|
|
133
|
+
)
|
|
134
|
+
@click.option(
|
|
135
|
+
"--detect-layer/--no-detect-layer",
|
|
136
|
+
"detect_layer",
|
|
137
|
+
default=True,
|
|
138
|
+
show_default=True,
|
|
139
|
+
help="Detect ML/MM layers from input PDB B-factors (B=0/10/20). "
|
|
140
|
+
"If disabled, you must provide --model-pdb or --model-indices.",
|
|
141
|
+
)
|
|
142
|
+
@click.option("-q", "--charge", type=int, required=False,
|
|
143
|
+
help="Total charge; overrides calc.charge from YAML. Required unless --ligand-charge is provided.")
|
|
144
|
+
@click.option("-l", "--ligand-charge", type=str, default=None, show_default=False,
|
|
145
|
+
help="Total charge or per-resname mapping (e.g., GPP:-3,SAM:1) used to derive "
|
|
146
|
+
"charge when -q is omitted (requires PDB input or --ref-pdb).")
|
|
147
|
+
@click.option(
|
|
148
|
+
"-m",
|
|
149
|
+
"--multiplicity",
|
|
150
|
+
"spin",
|
|
151
|
+
type=int,
|
|
152
|
+
default=None,
|
|
153
|
+
show_default=False,
|
|
154
|
+
help="Spin multiplicity (2S+1); overrides calc.spin from YAML.",
|
|
155
|
+
)
|
|
156
|
+
@click.option(
|
|
157
|
+
"--max-cycles", type=int, default=None, help="Maximum number of IRC steps; overrides irc.max_cycles from YAML."
|
|
158
|
+
)
|
|
159
|
+
@click.option("--step-size", type=float, default=None, help="Step length in mass-weighted coordinates; overrides irc.step_length from YAML.")
|
|
160
|
+
@click.option("--root", type=int, default=None, help="Imaginary mode index used for the initial displacement; overrides irc.root from YAML.")
|
|
161
|
+
@click.option(
|
|
162
|
+
"--forward/--no-forward",
|
|
163
|
+
"forward",
|
|
164
|
+
default=None,
|
|
165
|
+
help="Run the forward IRC; overrides irc.forward from YAML.",
|
|
166
|
+
)
|
|
167
|
+
@click.option(
|
|
168
|
+
"--backward/--no-backward",
|
|
169
|
+
"backward",
|
|
170
|
+
default=None,
|
|
171
|
+
help="Run the backward IRC; overrides irc.backward from YAML.",
|
|
172
|
+
)
|
|
173
|
+
@click.option("-o", "--out-dir", type=str, default="./result_irc/", show_default=True, help="Output directory; overrides irc.out_dir from YAML.")
|
|
174
|
+
@click.option(
|
|
175
|
+
"--hessian-calc-mode",
|
|
176
|
+
type=click.Choice(["Analytical", "FiniteDifference"], case_sensitive=False),
|
|
177
|
+
default=None,
|
|
178
|
+
help="How the ML backend builds the Hessian (Analytical or FiniteDifference); overrides calc.hessian_calc_mode from YAML. Default: 'FiniteDifference'. Use 'Analytical' when VRAM is sufficient.",
|
|
179
|
+
)
|
|
180
|
+
@click.option(
|
|
181
|
+
"--config",
|
|
182
|
+
"config_yaml",
|
|
183
|
+
type=click.Path(path_type=Path, exists=True, dir_okay=False),
|
|
184
|
+
default=None,
|
|
185
|
+
help="Base YAML configuration file applied before explicit CLI options.",
|
|
186
|
+
)
|
|
187
|
+
@click.option(
|
|
188
|
+
"--show-config/--no-show-config",
|
|
189
|
+
"show_config",
|
|
190
|
+
default=False,
|
|
191
|
+
show_default=True,
|
|
192
|
+
help="Print resolved configuration and continue execution.",
|
|
193
|
+
)
|
|
194
|
+
@click.option(
|
|
195
|
+
"--dry-run/--no-dry-run",
|
|
196
|
+
"dry_run",
|
|
197
|
+
default=False,
|
|
198
|
+
show_default=True,
|
|
199
|
+
help="Validate options and print the execution plan without running IRC.",
|
|
200
|
+
)
|
|
201
|
+
@click.option(
|
|
202
|
+
"--ref-pdb",
|
|
203
|
+
type=click.Path(path_type=Path, exists=True, dir_okay=False),
|
|
204
|
+
default=None,
|
|
205
|
+
help="Reference PDB topology to use when --input is XYZ (keeps XYZ coordinates).",
|
|
206
|
+
)
|
|
207
|
+
@click.option(
|
|
208
|
+
"--convert-files/--no-convert-files",
|
|
209
|
+
"convert_files",
|
|
210
|
+
default=True,
|
|
211
|
+
show_default=True,
|
|
212
|
+
help="Convert XYZ/TRJ outputs into PDB companions based on the input format.",
|
|
213
|
+
)
|
|
214
|
+
@click.option(
|
|
215
|
+
"-b", "--backend",
|
|
216
|
+
type=click.Choice(["uma", "orb", "mace", "aimnet2"], case_sensitive=False),
|
|
217
|
+
default=None,
|
|
218
|
+
show_default=False,
|
|
219
|
+
help="ML backend for the ONIOM high-level region (default: uma).",
|
|
220
|
+
)
|
|
221
|
+
@click.option(
|
|
222
|
+
"--embedcharge/--no-embedcharge",
|
|
223
|
+
"embedcharge",
|
|
224
|
+
default=False,
|
|
225
|
+
show_default=True,
|
|
226
|
+
help="Enable xTB point-charge embedding correction for MM→ML environmental effects.",
|
|
227
|
+
)
|
|
228
|
+
@click.option(
|
|
229
|
+
"--embedcharge-cutoff",
|
|
230
|
+
"embedcharge_cutoff",
|
|
231
|
+
type=float,
|
|
232
|
+
default=None,
|
|
233
|
+
show_default=False,
|
|
234
|
+
help="Distance cutoff (Å) from ML region for MM point charges in xTB embedding. "
|
|
235
|
+
"Default: 12.0 Å when --embedcharge is enabled.",
|
|
236
|
+
)
|
|
237
|
+
@click.pass_context
|
|
238
|
+
def cli(
|
|
239
|
+
ctx: click.Context,
|
|
240
|
+
input_path: Path,
|
|
241
|
+
real_parm7: Optional[Path],
|
|
242
|
+
model_pdb: Optional[Path],
|
|
243
|
+
model_indices_str: Optional[str],
|
|
244
|
+
model_indices_one_based: bool,
|
|
245
|
+
detect_layer: bool,
|
|
246
|
+
charge: Optional[int],
|
|
247
|
+
ligand_charge: Optional[str],
|
|
248
|
+
spin: Optional[int],
|
|
249
|
+
max_cycles: Optional[int],
|
|
250
|
+
step_size: Optional[float],
|
|
251
|
+
root: Optional[int],
|
|
252
|
+
forward: Optional[bool],
|
|
253
|
+
backward: Optional[bool],
|
|
254
|
+
out_dir: str,
|
|
255
|
+
hessian_calc_mode: Optional[str],
|
|
256
|
+
config_yaml: Optional[Path],
|
|
257
|
+
show_config: bool,
|
|
258
|
+
dry_run: bool,
|
|
259
|
+
ref_pdb: Optional[Path],
|
|
260
|
+
convert_files: bool,
|
|
261
|
+
backend: Optional[str],
|
|
262
|
+
embedcharge: bool,
|
|
263
|
+
embedcharge_cutoff: Optional[float],
|
|
264
|
+
) -> None:
|
|
265
|
+
set_convert_file_enabled(convert_files)
|
|
266
|
+
_is_param_explicit = make_is_param_explicit(ctx)
|
|
267
|
+
|
|
268
|
+
config_yaml, override_yaml, used_legacy_yaml = resolve_yaml_sources(
|
|
269
|
+
config_yaml=config_yaml,
|
|
270
|
+
override_yaml=None,
|
|
271
|
+
args_yaml_legacy=None,
|
|
272
|
+
)
|
|
273
|
+
merged_yaml_cfg, _, _ = load_merged_yaml_cfg(
|
|
274
|
+
config_yaml=config_yaml,
|
|
275
|
+
override_yaml=None,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
prepared_input = prepare_input_structure(input_path)
|
|
279
|
+
try:
|
|
280
|
+
apply_ref_pdb_override(prepared_input, ref_pdb)
|
|
281
|
+
except click.BadParameter as e:
|
|
282
|
+
click.echo(f"ERROR: {e}", err=True)
|
|
283
|
+
prepared_input.cleanup()
|
|
284
|
+
sys.exit(1)
|
|
285
|
+
geom_input_path = prepared_input.geom_path
|
|
286
|
+
source_path = prepared_input.source_path
|
|
287
|
+
charge, spin = resolve_charge_spin_or_raise(
|
|
288
|
+
prepared_input, charge, spin,
|
|
289
|
+
ligand_charge=ligand_charge, prefix="[irc]",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
model_indices: Optional[List[int]] = None
|
|
293
|
+
if model_indices_str:
|
|
294
|
+
try:
|
|
295
|
+
model_indices = parse_indices_string(model_indices_str, one_based=model_indices_one_based)
|
|
296
|
+
except click.BadParameter as e:
|
|
297
|
+
click.echo(f"ERROR: {e}", err=True)
|
|
298
|
+
prepared_input.cleanup()
|
|
299
|
+
sys.exit(1)
|
|
300
|
+
try:
|
|
301
|
+
time_start = time.perf_counter()
|
|
302
|
+
|
|
303
|
+
# --------------------------
|
|
304
|
+
# 1) Assemble configuration: defaults < config < CLI(explicit) < override
|
|
305
|
+
# --------------------------
|
|
306
|
+
config_layer_cfg = load_yaml_dict(config_yaml)
|
|
307
|
+
override_layer_cfg = load_yaml_dict(override_yaml)
|
|
308
|
+
|
|
309
|
+
geom_cfg: Dict[str, Any] = dict(GEOM_KW_DEFAULT)
|
|
310
|
+
calc_cfg: Dict[str, Any] = dict(CALC_KW_DEFAULT)
|
|
311
|
+
irc_cfg: Dict[str, Any] = dict(IRC_KW_DEFAULT)
|
|
312
|
+
# Keep command-level default for detect-layer unless YAML/explicit CLI overrides it.
|
|
313
|
+
calc_cfg["use_bfactor_layers"] = bool(detect_layer)
|
|
314
|
+
|
|
315
|
+
apply_yaml_overrides(
|
|
316
|
+
config_layer_cfg,
|
|
317
|
+
[
|
|
318
|
+
(geom_cfg, (("geom",),)),
|
|
319
|
+
(calc_cfg, (("calc",), ("mlmm",))),
|
|
320
|
+
(irc_cfg, (("irc",),)),
|
|
321
|
+
],
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# CLI explicit overrides (after config YAML, before override YAML)
|
|
325
|
+
if backend is not None:
|
|
326
|
+
calc_cfg["backend"] = str(backend).lower()
|
|
327
|
+
if _is_param_explicit("embedcharge"):
|
|
328
|
+
calc_cfg["embedcharge"] = bool(embedcharge)
|
|
329
|
+
if _is_param_explicit("embedcharge_cutoff"):
|
|
330
|
+
calc_cfg["embedcharge_cutoff"] = embedcharge_cutoff
|
|
331
|
+
|
|
332
|
+
if _is_param_explicit("hessian_calc_mode") and hessian_calc_mode is not None:
|
|
333
|
+
calc_cfg["hessian_calc_mode"] = str(hessian_calc_mode)
|
|
334
|
+
if _is_param_explicit("max_cycles") and max_cycles is not None:
|
|
335
|
+
irc_cfg["max_cycles"] = int(max_cycles)
|
|
336
|
+
if _is_param_explicit("step_size") and step_size is not None:
|
|
337
|
+
irc_cfg["step_length"] = float(step_size)
|
|
338
|
+
if _is_param_explicit("root") and root is not None:
|
|
339
|
+
irc_cfg["root"] = int(root)
|
|
340
|
+
if _is_param_explicit("forward") and forward is not None:
|
|
341
|
+
irc_cfg["forward"] = bool(forward)
|
|
342
|
+
if _is_param_explicit("backward") and backward is not None:
|
|
343
|
+
irc_cfg["backward"] = bool(backward)
|
|
344
|
+
if _is_param_explicit("out_dir"):
|
|
345
|
+
irc_cfg["out_dir"] = str(out_dir)
|
|
346
|
+
if _is_param_explicit("detect_layer"):
|
|
347
|
+
calc_cfg["use_bfactor_layers"] = bool(detect_layer)
|
|
348
|
+
|
|
349
|
+
model_charge_value = calc_cfg.get("model_charge", charge)
|
|
350
|
+
if model_charge_value is None:
|
|
351
|
+
model_charge_value = charge
|
|
352
|
+
calc_cfg["model_charge"] = int(model_charge_value)
|
|
353
|
+
if _is_param_explicit("charge"):
|
|
354
|
+
calc_cfg["model_charge"] = int(charge)
|
|
355
|
+
|
|
356
|
+
model_mult_value = calc_cfg.get("model_mult", spin)
|
|
357
|
+
if model_mult_value is None:
|
|
358
|
+
model_mult_value = spin
|
|
359
|
+
calc_cfg["model_mult"] = int(model_mult_value)
|
|
360
|
+
if _is_param_explicit("spin"):
|
|
361
|
+
calc_cfg["model_mult"] = int(spin)
|
|
362
|
+
|
|
363
|
+
calc_cfg["input_pdb"] = str(source_path)
|
|
364
|
+
if real_parm7 is not None:
|
|
365
|
+
calc_cfg["real_parm7"] = str(real_parm7)
|
|
366
|
+
if model_pdb is not None:
|
|
367
|
+
calc_cfg["model_pdb"] = str(model_pdb)
|
|
368
|
+
|
|
369
|
+
apply_yaml_overrides(
|
|
370
|
+
override_layer_cfg,
|
|
371
|
+
[
|
|
372
|
+
(geom_cfg, (("geom",),)),
|
|
373
|
+
(calc_cfg, (("calc",), ("mlmm",))),
|
|
374
|
+
(irc_cfg, (("irc",),)),
|
|
375
|
+
],
|
|
376
|
+
)
|
|
377
|
+
calc_paths = (("calc",), ("mlmm",))
|
|
378
|
+
partial_explicit = (
|
|
379
|
+
yaml_section_has_key(config_layer_cfg, calc_paths, "return_partial_hessian")
|
|
380
|
+
or yaml_section_has_key(override_layer_cfg, calc_paths, "return_partial_hessian")
|
|
381
|
+
)
|
|
382
|
+
if not partial_explicit:
|
|
383
|
+
calc_cfg["return_partial_hessian"] = True
|
|
384
|
+
|
|
385
|
+
# Normalize any existing freeze list from YAML before wiring it to UMA
|
|
386
|
+
merge_freeze_atom_indices(geom_cfg)
|
|
387
|
+
calc_cfg["freeze_atoms"] = list(geom_cfg.get("freeze_atoms", []))
|
|
388
|
+
if not calc_cfg.get("real_parm7"):
|
|
389
|
+
raise click.BadParameter("Missing --parm (or calc.real_parm7 in YAML).")
|
|
390
|
+
|
|
391
|
+
out_dir_path = Path(irc_cfg["out_dir"]).resolve()
|
|
392
|
+
layer_source_pdb = source_path
|
|
393
|
+
detect_layer_enabled = bool(calc_cfg.get("use_bfactor_layers", True))
|
|
394
|
+
model_pdb_cfg = calc_cfg.get("model_pdb")
|
|
395
|
+
|
|
396
|
+
if show_config:
|
|
397
|
+
click.echo(
|
|
398
|
+
pretty_block(
|
|
399
|
+
"yaml_layers",
|
|
400
|
+
{
|
|
401
|
+
"config": None if config_yaml is None else str(config_yaml),
|
|
402
|
+
"override_yaml": None if override_yaml is None else str(override_yaml),
|
|
403
|
+
"merged_keys": sorted(merged_yaml_cfg.keys()),
|
|
404
|
+
},
|
|
405
|
+
)
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
if dry_run:
|
|
409
|
+
model_region_source = "bfactor"
|
|
410
|
+
if not detect_layer_enabled:
|
|
411
|
+
if model_pdb_cfg is not None:
|
|
412
|
+
model_region_source = "model_pdb"
|
|
413
|
+
elif model_indices:
|
|
414
|
+
model_region_source = "model_indices"
|
|
415
|
+
else:
|
|
416
|
+
raise click.BadParameter("Provide --model-pdb or --model-indices when --no-detect-layer.")
|
|
417
|
+
if detect_layer_enabled and layer_source_pdb.suffix.lower() != ".pdb":
|
|
418
|
+
raise click.BadParameter("--detect-layer requires a PDB input (or --ref-pdb).")
|
|
419
|
+
if (
|
|
420
|
+
not detect_layer_enabled
|
|
421
|
+
and model_pdb_cfg is None
|
|
422
|
+
and model_indices
|
|
423
|
+
and layer_source_pdb.suffix.lower() != ".pdb"
|
|
424
|
+
):
|
|
425
|
+
raise click.BadParameter("--model-indices requires a PDB input (or --ref-pdb).")
|
|
426
|
+
click.echo(
|
|
427
|
+
pretty_block(
|
|
428
|
+
"dry_run_plan",
|
|
429
|
+
{
|
|
430
|
+
"input_geometry": str(geom_input_path),
|
|
431
|
+
"output_dir": str(out_dir_path),
|
|
432
|
+
"detect_layer": bool(detect_layer_enabled),
|
|
433
|
+
"model_region_source": model_region_source,
|
|
434
|
+
"model_indices_count": 0 if not model_indices else len(model_indices),
|
|
435
|
+
"will_run_irc": True,
|
|
436
|
+
"will_write_trajectories": True,
|
|
437
|
+
"backend": calc_cfg.get("backend", "uma"),
|
|
438
|
+
"embedcharge": bool(calc_cfg.get("embedcharge", False)),
|
|
439
|
+
},
|
|
440
|
+
)
|
|
441
|
+
)
|
|
442
|
+
click.echo("[dry-run] Validation complete. IRC execution was skipped.")
|
|
443
|
+
return
|
|
444
|
+
|
|
445
|
+
out_dir_path.mkdir(parents=True, exist_ok=True)
|
|
446
|
+
|
|
447
|
+
if detect_layer_enabled and layer_source_pdb.suffix.lower() != ".pdb":
|
|
448
|
+
raise click.BadParameter("--detect-layer requires a PDB input (or --ref-pdb).")
|
|
449
|
+
|
|
450
|
+
model_pdb_path: Optional[Path] = None
|
|
451
|
+
layer_info: Optional[Dict[str, List[int]]] = None
|
|
452
|
+
|
|
453
|
+
if detect_layer_enabled:
|
|
454
|
+
try:
|
|
455
|
+
model_pdb_path, layer_info = build_model_pdb_from_bfactors(layer_source_pdb, out_dir_path)
|
|
456
|
+
calc_cfg["use_bfactor_layers"] = True
|
|
457
|
+
click.echo(
|
|
458
|
+
f"[layer] Detected B-factor layers: ML={len(layer_info.get('ml_indices', []))}, "
|
|
459
|
+
f"MovableMM={len(layer_info.get('movable_mm_indices', []))}, "
|
|
460
|
+
f"FrozenMM={len(layer_info.get('frozen_indices', []))}"
|
|
461
|
+
)
|
|
462
|
+
except Exception as e:
|
|
463
|
+
if model_pdb_cfg is None and not model_indices:
|
|
464
|
+
raise click.BadParameter(str(e))
|
|
465
|
+
click.echo(f"[layer] WARNING: {e} Falling back to explicit ML region.", err=True)
|
|
466
|
+
detect_layer_enabled = False
|
|
467
|
+
|
|
468
|
+
if not detect_layer_enabled:
|
|
469
|
+
if model_pdb_cfg is None and not model_indices:
|
|
470
|
+
raise click.BadParameter("Provide --model-pdb or --model-indices when --no-detect-layer.")
|
|
471
|
+
if model_pdb_cfg is not None:
|
|
472
|
+
model_pdb_path = Path(model_pdb_cfg)
|
|
473
|
+
else:
|
|
474
|
+
if layer_source_pdb.suffix.lower() != ".pdb":
|
|
475
|
+
raise click.BadParameter("--model-indices requires a PDB input (or --ref-pdb).")
|
|
476
|
+
try:
|
|
477
|
+
model_pdb_path = build_model_pdb_from_indices(layer_source_pdb, out_dir_path, model_indices or [])
|
|
478
|
+
except Exception as e:
|
|
479
|
+
raise click.BadParameter(str(e))
|
|
480
|
+
calc_cfg["use_bfactor_layers"] = False
|
|
481
|
+
|
|
482
|
+
if model_pdb_path is None:
|
|
483
|
+
raise click.BadParameter("Failed to resolve model PDB for the ML region.")
|
|
484
|
+
|
|
485
|
+
calc_cfg["model_pdb"] = str(model_pdb_path)
|
|
486
|
+
_ = apply_layer_freeze_constraints(
|
|
487
|
+
geom_cfg,
|
|
488
|
+
calc_cfg,
|
|
489
|
+
layer_info,
|
|
490
|
+
echo_fn=click.echo,
|
|
491
|
+
)
|
|
492
|
+
_align_three_layer_hessian_targets(calc_cfg, echo_fn=click.echo)
|
|
493
|
+
|
|
494
|
+
# Pretty-print configuration (expand freeze_atoms for readability)
|
|
495
|
+
click.echo(pretty_block("geom", format_freeze_atoms_for_echo(geom_cfg, key="freeze_atoms")))
|
|
496
|
+
echo_calc = format_freeze_atoms_for_echo(filter_calc_for_echo(calc_cfg), key="freeze_atoms")
|
|
497
|
+
click.echo(pretty_block("calc", echo_calc))
|
|
498
|
+
echo_irc = strip_inherited_keys({**irc_cfg, "out_dir": str(out_dir_path)}, IRC_KW_DEFAULT, mode="same")
|
|
499
|
+
click.echo(pretty_block("irc", echo_irc))
|
|
500
|
+
|
|
501
|
+
# --------------------------
|
|
502
|
+
# 2) Load geometry and configure UMA calculator
|
|
503
|
+
# --------------------------
|
|
504
|
+
coord_type = geom_cfg.get("coord_type", "cart")
|
|
505
|
+
coord_kwargs = dict(geom_cfg)
|
|
506
|
+
coord_kwargs.pop("coord_type", None)
|
|
507
|
+
|
|
508
|
+
geometry = geom_loader(geom_input_path, coord_type=coord_type, **coord_kwargs)
|
|
509
|
+
|
|
510
|
+
# Create mlmm calculator
|
|
511
|
+
calc = mlmm(**calc_cfg)
|
|
512
|
+
geometry.set_calculator(calc)
|
|
513
|
+
|
|
514
|
+
# Seed the initial Hessian — reuse cached TS Hessian when available.
|
|
515
|
+
from .hessian_cache import load as _hess_load, store as _hess_store
|
|
516
|
+
hess_device = _torch_device(calc_cfg.get("ml_device", "auto"))
|
|
517
|
+
cached = _hess_load("ts")
|
|
518
|
+
if cached is not None:
|
|
519
|
+
click.echo("[irc] Reusing cached TS Hessian from tsopt.")
|
|
520
|
+
active_dofs = cached.get("active_dofs")
|
|
521
|
+
h_raw = cached["hessian"]
|
|
522
|
+
if isinstance(h_raw, torch.Tensor):
|
|
523
|
+
h_init = h_raw.to(device=hess_device)
|
|
524
|
+
else:
|
|
525
|
+
h_init = torch.as_tensor(h_raw, dtype=torch.float64, device=hess_device)
|
|
526
|
+
if active_dofs is not None:
|
|
527
|
+
geometry.within_partial_hessian = {
|
|
528
|
+
"active_n_dof": len(active_dofs),
|
|
529
|
+
"full_n_dof": geometry.cart_coords.size,
|
|
530
|
+
"active_dofs": active_dofs,
|
|
531
|
+
"active_atoms": sorted(set(d // 3 for d in active_dofs)),
|
|
532
|
+
}
|
|
533
|
+
else:
|
|
534
|
+
click.echo("[irc] Seeding initial Hessian via shared freq backend.")
|
|
535
|
+
h_init, _ = _calc_full_hessian_torch(
|
|
536
|
+
geometry,
|
|
537
|
+
calc_cfg,
|
|
538
|
+
hess_device,
|
|
539
|
+
refresh_geom_meta=True,
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
geometry.cart_hessian = h_init
|
|
543
|
+
click.echo(f"[irc] Initial Hessian seeded (shape={h_init.shape[0]}x{h_init.shape[1]}).")
|
|
544
|
+
del h_init
|
|
545
|
+
|
|
546
|
+
# --------------------------
|
|
547
|
+
# 3) Construct and run EulerPC
|
|
548
|
+
# --------------------------
|
|
549
|
+
# EulerPC.__init__ forwards **kwargs directly to IRC.__init__
|
|
550
|
+
eulerpc = EulerPC(geometry, **irc_cfg)
|
|
551
|
+
|
|
552
|
+
click.echo("\n=== IRC (EulerPC) started ===\n")
|
|
553
|
+
eulerpc.run()
|
|
554
|
+
click.echo("\n=== IRC (EulerPC) finished ===\n")
|
|
555
|
+
|
|
556
|
+
# Cache IRC endpoint Hessians (Bofill-updated mw → Cartesian)
|
|
557
|
+
def _unmw_and_store(mw_H, key):
|
|
558
|
+
"""Un-mass-weight active-DOF Hessian on device, store partial on CPU."""
|
|
559
|
+
import numpy as np
|
|
560
|
+
act = eulerpc._act_dofs
|
|
561
|
+
m_sqrt = geometry.masses_rep ** 0.5
|
|
562
|
+
ms_act = m_sqrt[act]
|
|
563
|
+
if isinstance(mw_H, torch.Tensor):
|
|
564
|
+
ms_t = torch.as_tensor(ms_act, dtype=mw_H.dtype, device=mw_H.device)
|
|
565
|
+
H_cart_act = ms_t.unsqueeze(1) * mw_H * ms_t.unsqueeze(0)
|
|
566
|
+
H_cart_act_np = H_cart_act.detach().cpu().numpy()
|
|
567
|
+
else:
|
|
568
|
+
H_cart_act_np = np.diag(ms_act) @ mw_H @ np.diag(ms_act)
|
|
569
|
+
_hess_store(key, H_cart_act_np, active_dofs=list(act))
|
|
570
|
+
|
|
571
|
+
if getattr(eulerpc, "forward_mw_hessian", None) is not None:
|
|
572
|
+
_unmw_and_store(eulerpc.forward_mw_hessian, "irc_left")
|
|
573
|
+
click.echo("[irc] Cached forward endpoint Hessian as 'irc_left'.")
|
|
574
|
+
if getattr(eulerpc, "mw_hessian", None) is not None:
|
|
575
|
+
_unmw_and_store(eulerpc.mw_hessian, "irc_right")
|
|
576
|
+
click.echo("[irc] Cached backward endpoint Hessian as 'irc_right'.")
|
|
577
|
+
|
|
578
|
+
# --------------------------
|
|
579
|
+
# 4) Convert trajectories to PDB when the input was PDB (or --ref-pdb provided)
|
|
580
|
+
# --------------------------
|
|
581
|
+
if source_path.suffix.lower() == ".pdb":
|
|
582
|
+
ref_pdb_path = source_path.resolve()
|
|
583
|
+
|
|
584
|
+
# Whole IRC trajectory
|
|
585
|
+
_echo_convert_trj_to_pdb_if_exists(
|
|
586
|
+
out_dir_path / f"{irc_cfg.get('prefix','')}{'finished_irc_trj.xyz'}",
|
|
587
|
+
ref_pdb_path,
|
|
588
|
+
out_dir_path / f"{irc_cfg.get('prefix','')}{'finished_irc.pdb'}",
|
|
589
|
+
)
|
|
590
|
+
# Forward/backward trajectories
|
|
591
|
+
_echo_convert_trj_to_pdb_if_exists(
|
|
592
|
+
out_dir_path / f"{irc_cfg.get('prefix','')}{'forward_irc_trj.xyz'}",
|
|
593
|
+
ref_pdb_path,
|
|
594
|
+
out_dir_path / f"{irc_cfg.get('prefix','')}{'forward_irc.pdb'}",
|
|
595
|
+
)
|
|
596
|
+
_echo_convert_trj_to_pdb_if_exists(
|
|
597
|
+
out_dir_path / f"{irc_cfg.get('prefix','')}{'backward_irc_trj.xyz'}",
|
|
598
|
+
ref_pdb_path,
|
|
599
|
+
out_dir_path / f"{irc_cfg.get('prefix','')}{'backward_irc.pdb'}",
|
|
600
|
+
)
|
|
601
|
+
# Single-frame endpoint PDBs (forward_last, backward_last)
|
|
602
|
+
prefix = irc_cfg.get("prefix", "")
|
|
603
|
+
for tag in ("forward_last", "backward_last"):
|
|
604
|
+
endpoint_xyz = out_dir_path / f"{prefix}{tag}.xyz"
|
|
605
|
+
endpoint_pdb = out_dir_path / f"{prefix}{tag}.pdb"
|
|
606
|
+
if endpoint_xyz.exists() and not endpoint_pdb.exists():
|
|
607
|
+
try:
|
|
608
|
+
convert_xyz_to_pdb(endpoint_xyz, ref_pdb_path, endpoint_pdb)
|
|
609
|
+
click.echo(f"[convert] Wrote '{endpoint_pdb}'.")
|
|
610
|
+
except Exception as e:
|
|
611
|
+
logger.debug("Failed to convert %s to PDB", endpoint_xyz.name, exc_info=True)
|
|
612
|
+
click.echo(f"[convert] WARNING: Failed to convert '{tag}.xyz' to PDB: {e}", err=True)
|
|
613
|
+
|
|
614
|
+
# summary.md and key_* outputs are disabled.
|
|
615
|
+
click.echo(format_elapsed("[time] Elapsed Time for IRC", time_start))
|
|
616
|
+
|
|
617
|
+
except KeyboardInterrupt:
|
|
618
|
+
click.echo("\nInterrupted by user.", err=True)
|
|
619
|
+
sys.exit(130)
|
|
620
|
+
except click.BadParameter as e:
|
|
621
|
+
click.echo(f"ERROR: {e}", err=True)
|
|
622
|
+
sys.exit(1)
|
|
623
|
+
except Exception as e:
|
|
624
|
+
tb = textwrap.indent("".join(__import__("traceback").format_exception(type(e), e, e.__traceback__)), " ")
|
|
625
|
+
click.echo("Unhandled exception during IRC:\n" + tb, err=True)
|
|
626
|
+
sys.exit(1)
|
|
627
|
+
finally:
|
|
628
|
+
prepared_input.cleanup()
|
|
629
|
+
# Release GPU memory (model + Hessian) so subsequent stages don't OOM
|
|
630
|
+
calc = eulerpc = geometry = None
|
|
631
|
+
gc.collect() # break cyclic refs inside torch.nn.Module
|
|
632
|
+
if torch.cuda.is_available():
|
|
633
|
+
torch.cuda.empty_cache()
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
# Script entry point
|
|
637
|
+
if __name__ == "__main__":
|
|
638
|
+
cli()
|