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/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# mlmm/__init__.py
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from mlmm._version import __version__, __version_tuple__
|
|
5
|
+
except ImportError:
|
|
6
|
+
__version__ = "0.0.0.dev0"
|
|
7
|
+
__version_tuple__ = (0, 0, 0, "dev0")
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"__version__",
|
|
11
|
+
"__version_tuple__",
|
|
12
|
+
"MLMMCore",
|
|
13
|
+
"MLMMASECalculator",
|
|
14
|
+
"mlmm",
|
|
15
|
+
"mlmm_ase",
|
|
16
|
+
"mlmm_mm_only",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
_LAZY_IMPORTS = {
|
|
20
|
+
"MLMMCore": "mlmm.mlmm_calc",
|
|
21
|
+
"MLMMASECalculator": "mlmm.mlmm_calc",
|
|
22
|
+
"mlmm": "mlmm.mlmm_calc",
|
|
23
|
+
"mlmm_ase": "mlmm.mlmm_calc",
|
|
24
|
+
"mlmm_mm_only": "mlmm.mlmm_calc",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def __getattr__(name: str):
|
|
29
|
+
if name in _LAZY_IMPORTS:
|
|
30
|
+
import importlib
|
|
31
|
+
|
|
32
|
+
module = importlib.import_module(_LAZY_IMPORTS[name])
|
|
33
|
+
value = getattr(module, name)
|
|
34
|
+
globals()[name] = value
|
|
35
|
+
return value
|
|
36
|
+
raise AttributeError(f"module 'mlmm' has no attribute {name!r}")
|
mlmm/__main__.py
ADDED
mlmm/_version.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.2.2.dev0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 2, 'dev0')
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
mlmm/add_elem_info.py
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# mlmm/add_elem_info.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Add/repair PDB element symbols (columns 77-78) using Biopython inference.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
mlmm add-elem-info -i input.pdb -o fixed.pdb
|
|
8
|
+
|
|
9
|
+
For detailed documentation, see: docs/add_elem_info.md
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import collections
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Optional, Set
|
|
21
|
+
|
|
22
|
+
import click
|
|
23
|
+
from Bio.PDB import PDBParser, PDBIO
|
|
24
|
+
|
|
25
|
+
# Reuse residue/ion dictionaries from extract.py to keep definitions in sync
|
|
26
|
+
from .extract import AMINO_ACIDS, ION, WATER_RES
|
|
27
|
+
|
|
28
|
+
# -----------------------------
|
|
29
|
+
# Element symbols (IUPAC, 1–118)
|
|
30
|
+
# -----------------------------
|
|
31
|
+
ELEMENTS: Set[str] = {
|
|
32
|
+
"H","He","Li","Be","B","C","N","O","F","Ne","Na","Mg","Al","Si","P","S","Cl","Ar",
|
|
33
|
+
"K","Ca","Sc","Ti","V","Cr","Mn","Fe","Co","Ni","Cu","Zn","Ga","Ge","As","Se","Br","Kr",
|
|
34
|
+
"Rb","Sr","Y","Zr","Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn","Sb","Te","I","Xe",
|
|
35
|
+
"Cs","Ba","La","Ce","Pr","Nd","Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb","Lu",
|
|
36
|
+
"Hf","Ta","W","Re","Os","Ir","Pt","Au","Hg","Tl","Pb","Bi","Po","At","Rn","Fr","Ra",
|
|
37
|
+
"Ac","Th","Pa","U","Np","Pu","Am","Cm","Bk","Cf","Es","Fm","Md","No","Lr","Rf","Db",
|
|
38
|
+
"Sg","Bh","Hs","Mt","Ds","Rg","Cn","Fl","Lv","Ts","Og"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Common residue classes
|
|
42
|
+
PROTEIN_RES = set(AMINO_ACIDS.keys())
|
|
43
|
+
NUCLEIC_RES = {
|
|
44
|
+
# DNA/RNA (minimum set)
|
|
45
|
+
"DA","DT","DG","DC","DI",
|
|
46
|
+
"A","U","G","C","I",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# -----------------------------
|
|
50
|
+
# Helper: normalize strings to element symbols
|
|
51
|
+
# -----------------------------
|
|
52
|
+
_re_letters = re.compile(r"[A-Za-z]+")
|
|
53
|
+
|
|
54
|
+
def _normalize_symbol(s: str) -> Optional[str]:
|
|
55
|
+
"""Remove non-letters; prefer a 2-letter match, then 1-letter, against known elements.
|
|
56
|
+
Returns the correctly cased symbol if matched.
|
|
57
|
+
Treat deuterium 'D' as hydrogen 'H' (PDB often uses D interchangeably with H).
|
|
58
|
+
"""
|
|
59
|
+
if not s:
|
|
60
|
+
return None
|
|
61
|
+
m = _re_letters.findall(s)
|
|
62
|
+
if not m:
|
|
63
|
+
return None
|
|
64
|
+
letters = "".join(m)
|
|
65
|
+
if len(letters) >= 2:
|
|
66
|
+
cand2 = (letters[:2][0].upper() + letters[:2][1].lower())
|
|
67
|
+
if cand2 in ELEMENTS:
|
|
68
|
+
return cand2
|
|
69
|
+
cand1 = letters[0].upper()
|
|
70
|
+
if cand1 in ELEMENTS:
|
|
71
|
+
return cand1
|
|
72
|
+
# Deuterium -> Hydrogen fallback
|
|
73
|
+
if letters[0].upper() == "D":
|
|
74
|
+
return "H"
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def _symbol_from_resname(resname: str) -> Optional[str]:
|
|
78
|
+
"""
|
|
79
|
+
Extract an element symbol from an ion residue name (e.g., CA, FE2, Cl-, YB2, IOD).
|
|
80
|
+
"""
|
|
81
|
+
res = resname.strip()
|
|
82
|
+
sym = _normalize_symbol(res)
|
|
83
|
+
if sym is None and res.upper().startswith("IOD"):
|
|
84
|
+
sym = "I"
|
|
85
|
+
return sym
|
|
86
|
+
|
|
87
|
+
# -----------------------------
|
|
88
|
+
# Element inference (use residue to disambiguate)
|
|
89
|
+
# -----------------------------
|
|
90
|
+
def guess_element(atom_name: str, resname: str, is_het: bool) -> Optional[str]:
|
|
91
|
+
"""
|
|
92
|
+
Infer the element from atom name + residue name.
|
|
93
|
+
Priority:
|
|
94
|
+
1) Ion residues: prefer the residue name (NH4 / H3O+ handled per-atom as H/N/O)
|
|
95
|
+
2) Polymers (protein/nucleic acid) and water: follow convention (H/C/N/O/S/P/Se)
|
|
96
|
+
- e.g., CA = Carbon (Cα), HG = Hydrogen, etc.
|
|
97
|
+
3) Other ligands: use atom-name prefix; prioritize Carbon for C* (except CL) and P for P*
|
|
98
|
+
4) Fallback to 2-letter then 1-letter normalization; return None if still ambiguous
|
|
99
|
+
"""
|
|
100
|
+
name_u = atom_name.strip().upper()
|
|
101
|
+
res_u = resname.strip().upper()
|
|
102
|
+
|
|
103
|
+
# 1) Ion residues — strongly prefer the residue-derived element
|
|
104
|
+
if res_u in {k.upper() for k in ION.keys()}:
|
|
105
|
+
# Polyatomic ions (NH4, H3O+, …): decide per atom name (treat D* as H)
|
|
106
|
+
if name_u.startswith(("H", "D")):
|
|
107
|
+
return "H"
|
|
108
|
+
if name_u.startswith("N"):
|
|
109
|
+
return "N"
|
|
110
|
+
if name_u.startswith("O"):
|
|
111
|
+
return "O"
|
|
112
|
+
# Monatomic metals/halogens: from residue name
|
|
113
|
+
sym = _symbol_from_resname(res_u)
|
|
114
|
+
if sym:
|
|
115
|
+
return sym
|
|
116
|
+
# If residue is atypical, allow atom-name halogens (CL/BR/I/F)
|
|
117
|
+
if name_u.startswith("CL"):
|
|
118
|
+
return "Cl"
|
|
119
|
+
if name_u.startswith("BR"):
|
|
120
|
+
return "Br"
|
|
121
|
+
if name_u.startswith("I"):
|
|
122
|
+
return "I"
|
|
123
|
+
if name_u.startswith("F"):
|
|
124
|
+
return "F"
|
|
125
|
+
|
|
126
|
+
# 2) Polymers (protein/nucleic) / water
|
|
127
|
+
is_protein = res_u in PROTEIN_RES
|
|
128
|
+
is_nucl = res_u in NUCLEIC_RES
|
|
129
|
+
is_water = res_u in WATER_RES
|
|
130
|
+
if is_protein or is_nucl or is_water:
|
|
131
|
+
# Water: only O and H (treat D* as H)
|
|
132
|
+
if is_water:
|
|
133
|
+
if name_u.startswith(("H", "D")):
|
|
134
|
+
return "H"
|
|
135
|
+
return "O"
|
|
136
|
+
|
|
137
|
+
# Hydrogen (including D*)
|
|
138
|
+
if name_u.startswith(("H", "D")):
|
|
139
|
+
return "H"
|
|
140
|
+
|
|
141
|
+
# Selenium (e.g., selenomethionine/selenocysteine)
|
|
142
|
+
if name_u.startswith("SE"):
|
|
143
|
+
return "Se"
|
|
144
|
+
|
|
145
|
+
# P, N, O, S map directly by first letter
|
|
146
|
+
if name_u.startswith("P"):
|
|
147
|
+
return "P"
|
|
148
|
+
if name_u.startswith("N"):
|
|
149
|
+
return "N"
|
|
150
|
+
if name_u.startswith("O"):
|
|
151
|
+
return "O"
|
|
152
|
+
if name_u.startswith("S"):
|
|
153
|
+
return "S"
|
|
154
|
+
|
|
155
|
+
# Carbon for Cα/sidechain labels (CA, CB, CG, CD, CE, CZ, CH*, etc.)
|
|
156
|
+
if name_u.startswith("C"):
|
|
157
|
+
return "C"
|
|
158
|
+
|
|
159
|
+
# Rare halogens in polymers: final fallback to normalization
|
|
160
|
+
sym = _normalize_symbol(name_u)
|
|
161
|
+
if sym:
|
|
162
|
+
return sym
|
|
163
|
+
|
|
164
|
+
# 3) Non-polymers (ligands / cofactors)
|
|
165
|
+
# Hydrogen (including D*) for ligands/cofactors
|
|
166
|
+
if name_u.startswith(("H", "D")):
|
|
167
|
+
return "H"
|
|
168
|
+
# Carbon/Phosphorus-like labels (C*, P*) -> C/P (exclude CL)
|
|
169
|
+
if name_u.startswith("C") and not name_u.startswith("CL"):
|
|
170
|
+
return "C"
|
|
171
|
+
if name_u.startswith("P"):
|
|
172
|
+
return "P"
|
|
173
|
+
|
|
174
|
+
# Metals and halogens often appear as the atom name (FE, ZN, MG, HG, CL, BR, I, F ...)
|
|
175
|
+
sym = _normalize_symbol(name_u)
|
|
176
|
+
if sym:
|
|
177
|
+
return sym
|
|
178
|
+
|
|
179
|
+
# 4) Unresolved
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
# -----------------------------
|
|
183
|
+
# Detect whether the input originally had element fields,
|
|
184
|
+
# keyed by atom serial number (columns 7–11)
|
|
185
|
+
# -----------------------------
|
|
186
|
+
def scan_existing_elements_by_serial(pdb_path: str) -> Set[int]:
|
|
187
|
+
"""
|
|
188
|
+
Scan the raw PDB lines and return the serial numbers of ATOM/HETATM records whose
|
|
189
|
+
element field (columns 77–78) was non-empty in the original file.
|
|
190
|
+
This avoids Biopython side effects and reflects the true presence/absence in the input.
|
|
191
|
+
"""
|
|
192
|
+
serials_with_elem: Set[int] = set()
|
|
193
|
+
try:
|
|
194
|
+
with open(pdb_path, "r", encoding="utf-8", errors="ignore") as fh:
|
|
195
|
+
for line in fh:
|
|
196
|
+
if not (line.startswith("ATOM") or line.startswith("HETATM")):
|
|
197
|
+
continue
|
|
198
|
+
if len(line) < 78:
|
|
199
|
+
# No element field present
|
|
200
|
+
continue
|
|
201
|
+
serial_str = line[6:11].strip()
|
|
202
|
+
elem_raw = line[76:78].strip()
|
|
203
|
+
if not serial_str:
|
|
204
|
+
continue
|
|
205
|
+
try:
|
|
206
|
+
serial = int(serial_str)
|
|
207
|
+
except ValueError:
|
|
208
|
+
continue
|
|
209
|
+
# If non-empty, consider that the original file had an element entry
|
|
210
|
+
# (keep isotopic labels like D as-is)
|
|
211
|
+
if elem_raw:
|
|
212
|
+
serials_with_elem.add(serial)
|
|
213
|
+
except Exception:
|
|
214
|
+
# If the file can't be read, return empty (treat all as unset)
|
|
215
|
+
pass
|
|
216
|
+
return serials_with_elem
|
|
217
|
+
|
|
218
|
+
def _get_atom_serial(atom) -> Optional[int]:
|
|
219
|
+
"""
|
|
220
|
+
Safely obtain the serial number from a Biopython Atom, handling version differences.
|
|
221
|
+
"""
|
|
222
|
+
sn = getattr(atom, "serial_number", None)
|
|
223
|
+
if sn is None and hasattr(atom, "get_serial_number"):
|
|
224
|
+
try:
|
|
225
|
+
sn = atom.get_serial_number()
|
|
226
|
+
except Exception:
|
|
227
|
+
sn = None
|
|
228
|
+
return sn
|
|
229
|
+
|
|
230
|
+
# -----------------------------
|
|
231
|
+
# Main processing
|
|
232
|
+
# -----------------------------
|
|
233
|
+
def assign_elements(in_pdb: str, out_pdb: Optional[str], overwrite: bool = False) -> None:
|
|
234
|
+
# Scan the input file for the original presence of element fields
|
|
235
|
+
existing_by_serial = scan_existing_elements_by_serial(in_pdb)
|
|
236
|
+
|
|
237
|
+
parser = PDBParser(QUIET=True)
|
|
238
|
+
structure_id = os.path.splitext(os.path.basename(in_pdb))[0]
|
|
239
|
+
structure = parser.get_structure(structure_id, in_pdb)
|
|
240
|
+
|
|
241
|
+
total = 0
|
|
242
|
+
assigned_new = 0 # newly set for atoms that lacked an element field
|
|
243
|
+
overwritten = 0 # element existed originally but was re-inferred due to --overwrite
|
|
244
|
+
kept_existing = 0 # element existed originally and was preserved (no --overwrite)
|
|
245
|
+
unknown = [] # could not infer (left unchanged)
|
|
246
|
+
|
|
247
|
+
by_element = collections.Counter()
|
|
248
|
+
|
|
249
|
+
for model in structure:
|
|
250
|
+
for chain in model:
|
|
251
|
+
for residue in chain:
|
|
252
|
+
hetflag = residue.id[0].strip() # '' (empty) = standard; 'W' = water; 'H_' = HETATM
|
|
253
|
+
is_het = (hetflag != "")
|
|
254
|
+
resname = residue.get_resname()
|
|
255
|
+
for atom in residue:
|
|
256
|
+
total += 1
|
|
257
|
+
name = atom.get_name()
|
|
258
|
+
|
|
259
|
+
serial = _get_atom_serial(atom)
|
|
260
|
+
had_element_in_input = (serial in existing_by_serial) if serial is not None else False
|
|
261
|
+
|
|
262
|
+
if had_element_in_input and not overwrite:
|
|
263
|
+
kept_existing += 1
|
|
264
|
+
continue # Respect existing element: do not modify without --overwrite
|
|
265
|
+
|
|
266
|
+
sym = guess_element(name, resname, is_het)
|
|
267
|
+
if sym is None:
|
|
268
|
+
unknown.append((model.id, chain.id, residue.id, resname, name, serial))
|
|
269
|
+
# If inference failed: keep the previous value (if any), otherwise leave unset
|
|
270
|
+
continue
|
|
271
|
+
|
|
272
|
+
# Biopython uses atom.element to populate columns 77–78 on output
|
|
273
|
+
prev = getattr(atom, "element", None)
|
|
274
|
+
atom.element = sym
|
|
275
|
+
by_element[sym] += 1
|
|
276
|
+
if had_element_in_input:
|
|
277
|
+
if prev != sym:
|
|
278
|
+
overwritten += 1
|
|
279
|
+
else:
|
|
280
|
+
assigned_new += 1
|
|
281
|
+
|
|
282
|
+
io = PDBIO()
|
|
283
|
+
io.set_structure(structure)
|
|
284
|
+
out_path = out_pdb if out_pdb else in_pdb # overwrite input if not specified
|
|
285
|
+
io.save(out_path)
|
|
286
|
+
|
|
287
|
+
# Summary
|
|
288
|
+
click.echo(f"[add-elem-info] Wrote: {out_path}")
|
|
289
|
+
click.echo(f" total atoms : {total}")
|
|
290
|
+
click.echo(f" newly assigned : {assigned_new}")
|
|
291
|
+
click.echo(f" kept existing (no overwrite): {kept_existing}")
|
|
292
|
+
click.echo(f" overwritten (--overwrite) : {overwritten}")
|
|
293
|
+
if by_element:
|
|
294
|
+
top = ", ".join(f"{k}:{v}" for k, v in by_element.most_common())
|
|
295
|
+
click.echo(f" assignment breakdown : {top}")
|
|
296
|
+
if unknown:
|
|
297
|
+
click.echo(f"[add-elem-info] WARNING: Could not confidently assign {len(unknown)} atoms; left unchanged.")
|
|
298
|
+
for (mid, chid, resid, resn, aname, serial) in unknown[:50]:
|
|
299
|
+
if isinstance(resid, tuple):
|
|
300
|
+
resseq = resid[1]
|
|
301
|
+
icode = resid[2].strip()
|
|
302
|
+
else:
|
|
303
|
+
resseq, icode = "?", ""
|
|
304
|
+
s_str = f" serial {serial}" if serial is not None else ""
|
|
305
|
+
click.echo(f" model {mid} chain {chid} {resn} {resseq}{icode} : {aname}{s_str}")
|
|
306
|
+
if len(unknown) > 50:
|
|
307
|
+
click.echo(" ... (truncated) ...")
|
|
308
|
+
|
|
309
|
+
def main():
|
|
310
|
+
ap = argparse.ArgumentParser(
|
|
311
|
+
description="Add/repair element columns (77–78) in a PDB using Biopython."
|
|
312
|
+
)
|
|
313
|
+
ap.add_argument("pdb", help="input PDB filepath")
|
|
314
|
+
ap.add_argument("-o", "--out", help="output PDB filepath (omit to overwrite input)")
|
|
315
|
+
ap.add_argument(
|
|
316
|
+
"--overwrite",
|
|
317
|
+
action="store_true",
|
|
318
|
+
help="Re-infer and overwrite element fields even if present (by default, existing values are preserved).",
|
|
319
|
+
)
|
|
320
|
+
args = ap.parse_args()
|
|
321
|
+
|
|
322
|
+
if not os.path.isfile(args.pdb):
|
|
323
|
+
click.echo(f"[add-elem-info] ERROR: Input not found: {args.pdb}", err=True)
|
|
324
|
+
sys.exit(1)
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
assign_elements(args.pdb, args.out, overwrite=args.overwrite)
|
|
328
|
+
except Exception as e:
|
|
329
|
+
click.echo(f"[add-elem-info] ERROR: Failed: {e}", err=True)
|
|
330
|
+
sys.exit(2)
|
|
331
|
+
|
|
332
|
+
# -----------------------------
|
|
333
|
+
# Click subcommand (mlmm add-elem-info)
|
|
334
|
+
# -----------------------------
|
|
335
|
+
@click.command(
|
|
336
|
+
help="Add/repair element columns (77–78) in a PDB using Biopython.",
|
|
337
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
338
|
+
)
|
|
339
|
+
@click.option(
|
|
340
|
+
"-i", "--input",
|
|
341
|
+
"in_pdb",
|
|
342
|
+
type=click.Path(path_type=Path, exists=True, dir_okay=False),
|
|
343
|
+
required=True,
|
|
344
|
+
help="Input PDB filepath",
|
|
345
|
+
)
|
|
346
|
+
@click.option(
|
|
347
|
+
"-o", "--out",
|
|
348
|
+
"out_pdb",
|
|
349
|
+
type=click.Path(path_type=Path, dir_okay=False),
|
|
350
|
+
default=None,
|
|
351
|
+
help="Output PDB filepath (omit to overwrite input)",
|
|
352
|
+
)
|
|
353
|
+
@click.option(
|
|
354
|
+
"--overwrite/--no-overwrite",
|
|
355
|
+
"overwrite",
|
|
356
|
+
default=False,
|
|
357
|
+
show_default=True,
|
|
358
|
+
help="Re-infer and overwrite element fields even if present (by default, existing values are preserved).",
|
|
359
|
+
)
|
|
360
|
+
def cli(in_pdb: Path, out_pdb: Optional[Path], overwrite: bool) -> None:
|
|
361
|
+
"""
|
|
362
|
+
Click wrapper to run via the `mlmm add-elem-info` subcommand.
|
|
363
|
+
"""
|
|
364
|
+
try:
|
|
365
|
+
assign_elements(str(in_pdb), (str(out_pdb) if out_pdb else None), overwrite=overwrite)
|
|
366
|
+
except SystemExit as e:
|
|
367
|
+
# Match argparse-like behavior: propagate SystemExit as-is
|
|
368
|
+
raise e
|
|
369
|
+
except Exception as e:
|
|
370
|
+
click.echo(f"[ERR] Failed: {e}", err=True)
|
|
371
|
+
sys.exit(2)
|
|
372
|
+
|
|
373
|
+
if __name__ == "__main__":
|
|
374
|
+
main()
|
mlmm/advanced_help.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Helpers for progressive `--help` / `--help-advanced` behavior."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _show_advanced_subcommand_help(
|
|
9
|
+
ctx: click.Context, _param: click.Parameter, value: bool
|
|
10
|
+
) -> None:
|
|
11
|
+
"""Print subcommand help with advanced options and exit."""
|
|
12
|
+
if not value or ctx.resilient_parsing:
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
if getattr(ctx.command, "_advanced_passthrough_help", False):
|
|
16
|
+
try:
|
|
17
|
+
ctx.command.main(args=["--help"], standalone_mode=False)
|
|
18
|
+
except SystemExit as exc:
|
|
19
|
+
code = getattr(exc, "code", 1)
|
|
20
|
+
if code not in (None, 0):
|
|
21
|
+
raise
|
|
22
|
+
ctx.exit()
|
|
23
|
+
|
|
24
|
+
hidden = getattr(ctx.command, "_advanced_hidden_options", ())
|
|
25
|
+
restored: list[click.Option] = []
|
|
26
|
+
for opt in hidden:
|
|
27
|
+
if opt.hidden:
|
|
28
|
+
opt.hidden = False
|
|
29
|
+
restored.append(opt)
|
|
30
|
+
try:
|
|
31
|
+
click.echo(ctx.command.get_help(ctx))
|
|
32
|
+
finally:
|
|
33
|
+
for opt in restored:
|
|
34
|
+
opt.hidden = True
|
|
35
|
+
ctx.exit()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _ensure_help_advanced_option(command: click.Command) -> click.Command:
|
|
39
|
+
"""Attach --help-advanced to lazily loaded subcommands when absent."""
|
|
40
|
+
passthrough_help = (
|
|
41
|
+
command.context_settings.get("help_option_names") == []
|
|
42
|
+
if isinstance(command.context_settings, dict)
|
|
43
|
+
else False
|
|
44
|
+
)
|
|
45
|
+
setattr(command, "_advanced_passthrough_help", passthrough_help)
|
|
46
|
+
|
|
47
|
+
if any(
|
|
48
|
+
isinstance(param, click.Option) and "--help-advanced" in param.opts
|
|
49
|
+
for param in command.params
|
|
50
|
+
):
|
|
51
|
+
return command
|
|
52
|
+
|
|
53
|
+
option = click.Option(
|
|
54
|
+
["--help-advanced"],
|
|
55
|
+
is_flag=True,
|
|
56
|
+
is_eager=True,
|
|
57
|
+
expose_value=False,
|
|
58
|
+
callback=_show_advanced_subcommand_help,
|
|
59
|
+
help="Show all options (including advanced settings) and exit.",
|
|
60
|
+
)
|
|
61
|
+
command.params.insert(0, option)
|
|
62
|
+
return command
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _configure_subcommand_help_visibility(
|
|
66
|
+
command_name: str,
|
|
67
|
+
command: click.Command,
|
|
68
|
+
primary_options_by_subcommand: dict[str, frozenset[str]],
|
|
69
|
+
) -> click.Command:
|
|
70
|
+
"""Hide advanced options from default --help for selected subcommands."""
|
|
71
|
+
if hasattr(command, "_advanced_hidden_options"):
|
|
72
|
+
return command
|
|
73
|
+
|
|
74
|
+
primary_options = primary_options_by_subcommand.get(command_name)
|
|
75
|
+
if not primary_options:
|
|
76
|
+
return command
|
|
77
|
+
|
|
78
|
+
hidden_options: list[click.Option] = []
|
|
79
|
+
for param in command.params:
|
|
80
|
+
if not isinstance(param, click.Option):
|
|
81
|
+
continue
|
|
82
|
+
names = set(param.opts + param.secondary_opts)
|
|
83
|
+
if names & primary_options:
|
|
84
|
+
continue
|
|
85
|
+
if param.hidden:
|
|
86
|
+
continue
|
|
87
|
+
param.hidden = True
|
|
88
|
+
hidden_options.append(param)
|
|
89
|
+
|
|
90
|
+
setattr(command, "_advanced_hidden_options", tuple(hidden_options))
|
|
91
|
+
return command
|