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/bond_changes.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# bond_changes.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
bond_changes — Bond-change detection and reporting utilities
|
|
5
|
+
====================================================================
|
|
6
|
+
|
|
7
|
+
Usage (API)
|
|
8
|
+
-----
|
|
9
|
+
from <package>.bond_changes import compare_structures, summarize_changes
|
|
10
|
+
|
|
11
|
+
Examples::
|
|
12
|
+
>>> result = compare_structures(geom_reactant, geom_product, device="cpu")
|
|
13
|
+
>>> print(summarize_changes(geom_product, result))
|
|
14
|
+
Bond formed (1)
|
|
15
|
+
C1-O2 1.50 Å --> 1.36 Å
|
|
16
|
+
|
|
17
|
+
Description
|
|
18
|
+
-----
|
|
19
|
+
This module compares two molecular geometries with identical atom types and ordering and reports covalent bonds that are formed
|
|
20
|
+
or broken between the structures. Bond perception uses element-specific covalent radii and configurable tolerances; distances are computed with PyTorch on CPU or CUDA.
|
|
21
|
+
|
|
22
|
+
Algorithm (core logic):
|
|
23
|
+
- Inputs: `geom1`, `geom2` with attributes `atoms: Iterable[str]` and `coords3d: (N, 3) float` (in Bohr in pysisyphus). Atoms must match exactly (`assert geom1.atoms == geom2.atoms`).
|
|
24
|
+
- Per-element radii: from `pysisyphus.elem_data.COVALENT_RADII`.
|
|
25
|
+
- Threshold per pair (i, j): `T_cov = bond_factor * (r_i + r_j)`; conservative margin `eps = margin_fraction * T_cov`.
|
|
26
|
+
- Bond adjacency in a geometry: `A = (D <= T_cov - eps)` evaluated only for `i < j` (upper triangle).
|
|
27
|
+
- Change gating: only pairs with `|D2 - D1| >= delta_fraction * T_cov` are considered.
|
|
28
|
+
- Classification:
|
|
29
|
+
- Formed: `(~A1) & A2 & need_change`
|
|
30
|
+
- Broken: `A1 & (~A2) & need_change`
|
|
31
|
+
- Distances: pairwise matrices `D1`, `D2` via `torch.cdist`.
|
|
32
|
+
|
|
33
|
+
Public API:
|
|
34
|
+
- `compare_structures(geom1, geom2, device='cuda', bond_factor=1.20, margin_fraction=0.05, delta_fraction=0.05) -> BondChangeResult`
|
|
35
|
+
Detects formed and broken covalent bonds. Returns sets of zero-based index pairs and (by default) the full distance matrices (`numpy.ndarray`) in the same units as the inputs (Bohr in pysisyphus).
|
|
36
|
+
- `summarize_changes(geom, result, one_based: bool = True) -> str`
|
|
37
|
+
Builds a human-readable report:
|
|
38
|
+
- Sections: “Bond formed” and “Bond broken” (with counts).
|
|
39
|
+
- Lines formatted as `ElemI-ElemJ` with atom indices (1-based by default, e.g., `C1-O2`).
|
|
40
|
+
- If `result.distances_1/2` are present, prints bond lengths as `D1 Å --> D2 Å`, converting from Bohr using `pysisyphus.constants.BOHR2ANG`.
|
|
41
|
+
- Helper utilities (internal):
|
|
42
|
+
- `_resolve_device(device: str) -> torch.device`: chooses the requested device; falls back to CPU with a warning if unavailable.
|
|
43
|
+
- `_element_arrays(atoms) -> (elems, cov_radii)`: normalizes element symbols and looks up covalent radii.
|
|
44
|
+
- `_upper_pairs_from_mask(mask) -> Set[Tuple[int, int]]`: returns index pairs where `mask` is True (assumes an upper-triangular mask).
|
|
45
|
+
- `_bond_str(i, j, elems, one_based=True) -> str`: formats `ElemI-ElemJ` labels.
|
|
46
|
+
- Data container:
|
|
47
|
+
- `BondChangeResult`:
|
|
48
|
+
- `formed_covalent: Set[Tuple[int, int]]` — zero-based pairs for bonds formed.
|
|
49
|
+
- `broken_covalent: Set[Tuple[int, int]]` — zero-based pairs for bonds broken.
|
|
50
|
+
- `distances_1: Optional[np.ndarray]`, `distances_2: Optional[np.ndarray]` — square distance matrices (shape N×N).
|
|
51
|
+
|
|
52
|
+
Outputs (& Directory Layout)
|
|
53
|
+
-----
|
|
54
|
+
- No files or directories are created.
|
|
55
|
+
- `compare_structures` returns a `BondChangeResult` as described above.
|
|
56
|
+
- `summarize_changes` returns a multi-line string; typical headings:
|
|
57
|
+
- `Bond formed (k):` followed by `ElemI-ElemJ : D1 Å --> D2 Å` (if distances available).
|
|
58
|
+
- `Bond broken (m):` followed by lines in the same format.
|
|
59
|
+
- If a set is empty, the section reads `None`.
|
|
60
|
+
|
|
61
|
+
Notes:
|
|
62
|
+
-----
|
|
63
|
+
- Units: In pysisyphus, `coords3d` are Bohr; the summary converts to Å with `BOHR2ANG`. If your inputs use different units, adjust accordingly.
|
|
64
|
+
- Atom identity & ordering must be identical between structures; otherwise the comparison is invalid.
|
|
65
|
+
- Only unique pairs with `i < j` are considered (upper triangle); indices in results are zero-based.
|
|
66
|
+
- The three tolerances control sensitivity:
|
|
67
|
+
- `bond_factor` (default 1.20): global scaling of the covalent radii sum.
|
|
68
|
+
- `margin_fraction` (default 0.05): conservative shrinkage of the bond cutoff to avoid borderline matches.
|
|
69
|
+
- `delta_fraction` (default 0.05): minimum relative distance change required to count a bond event.
|
|
70
|
+
- Device selection: pass `'cpu'`, `'cuda'`, or `'cuda:0'` etc. If the requested device is not available or invalid, the code falls back to CPU and issues a `RuntimeWarning`. Computations use `float64` for stability and run under `torch.no_grad()`.
|
|
71
|
+
- The method detects binary bond formation/breakage; it does not estimate bond orders, angles, or multi-center bonding, and it ignores periodic boundary conditions.
|
|
72
|
+
- Numerical caveats: near-threshold pairs may toggle with small geometry noise; tune tolerances to your system size and sampling noise.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
from __future__ import annotations
|
|
76
|
+
from dataclasses import dataclass
|
|
77
|
+
from typing import Iterable, Tuple, Set, List, Optional
|
|
78
|
+
|
|
79
|
+
import warnings
|
|
80
|
+
import torch
|
|
81
|
+
import numpy as np
|
|
82
|
+
|
|
83
|
+
from pysisyphus.elem_data import (
|
|
84
|
+
COVALENT_RADII as CR,
|
|
85
|
+
)
|
|
86
|
+
from pysisyphus.constants import BOHR2ANG # Convert Bohr distances to Ångström for reporting
|
|
87
|
+
|
|
88
|
+
Pair = Tuple[int, int]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class BondChangeResult:
|
|
93
|
+
formed_covalent: Set[Pair]
|
|
94
|
+
broken_covalent: Set[Pair]
|
|
95
|
+
distances_1: Optional[np.ndarray] = None
|
|
96
|
+
distances_2: Optional[np.ndarray] = None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _upper_pairs_from_mask(mask: torch.Tensor) -> Set[Pair]:
|
|
100
|
+
idx = torch.nonzero(mask, as_tuple=False).detach().cpu().numpy()
|
|
101
|
+
return set(map(tuple, idx))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _element_arrays(atoms: Iterable[str]) -> Tuple[List[str], np.ndarray]:
|
|
105
|
+
elems = [a.capitalize() for a in atoms]
|
|
106
|
+
cov = np.array([CR[a.lower()] for a in elems], dtype=float)
|
|
107
|
+
return elems, cov
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _resolve_device(device: str) -> torch.device:
|
|
111
|
+
dev_str = (device or "cpu").lower()
|
|
112
|
+
if dev_str.startswith("cuda"):
|
|
113
|
+
if torch.cuda.is_available():
|
|
114
|
+
try:
|
|
115
|
+
_ = torch.device(dev_str)
|
|
116
|
+
return torch.device(dev_str)
|
|
117
|
+
except Exception:
|
|
118
|
+
warnings.warn(
|
|
119
|
+
f"Requested device '{device}' is not available. Falling back to CPU.",
|
|
120
|
+
RuntimeWarning,
|
|
121
|
+
)
|
|
122
|
+
return torch.device("cpu")
|
|
123
|
+
else:
|
|
124
|
+
warnings.warn(
|
|
125
|
+
"CUDA is not available. Falling back to CPU.",
|
|
126
|
+
RuntimeWarning,
|
|
127
|
+
)
|
|
128
|
+
return torch.device("cpu")
|
|
129
|
+
try:
|
|
130
|
+
return torch.device(dev_str)
|
|
131
|
+
except Exception:
|
|
132
|
+
warnings.warn(
|
|
133
|
+
f"Requested device '{device}' is not recognized. Falling back to CPU.",
|
|
134
|
+
RuntimeWarning,
|
|
135
|
+
)
|
|
136
|
+
return torch.device("cpu")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@torch.no_grad()
|
|
140
|
+
def compare_structures(
|
|
141
|
+
geom1,
|
|
142
|
+
geom2,
|
|
143
|
+
device: str = "cuda",
|
|
144
|
+
bond_factor: float = 1.20,
|
|
145
|
+
margin_fraction: float = 0.05,
|
|
146
|
+
delta_fraction: float = 0.05,
|
|
147
|
+
) -> BondChangeResult:
|
|
148
|
+
|
|
149
|
+
assert geom1.atoms == geom2.atoms, "Atom types and ordering must be identical."
|
|
150
|
+
N = len(geom1.atoms)
|
|
151
|
+
|
|
152
|
+
elems, cov_np = _element_arrays(geom1.atoms)
|
|
153
|
+
dev = _resolve_device(device)
|
|
154
|
+
|
|
155
|
+
dtype = torch.float64
|
|
156
|
+
R1 = torch.as_tensor(geom1.coords3d, dtype=dtype, device=dev)
|
|
157
|
+
R2 = torch.as_tensor(geom2.coords3d, dtype=dtype, device=dev)
|
|
158
|
+
cov = torch.as_tensor(cov_np, dtype=dtype, device=dev)
|
|
159
|
+
|
|
160
|
+
T_cov = bond_factor * (cov[:, None] + cov[None, :])
|
|
161
|
+
eps_cov = margin_fraction * T_cov
|
|
162
|
+
|
|
163
|
+
D1 = torch.cdist(R1, R1)
|
|
164
|
+
D2 = torch.cdist(R2, R2)
|
|
165
|
+
|
|
166
|
+
up = torch.triu(torch.ones((N, N), dtype=torch.bool, device=dev), diagonal=1)
|
|
167
|
+
|
|
168
|
+
A1 = (D1 <= (T_cov - eps_cov)) & up
|
|
169
|
+
A2 = (D2 <= (T_cov - eps_cov)) & up
|
|
170
|
+
|
|
171
|
+
dD = D2 - D1
|
|
172
|
+
need_change = (dD.abs() >= (delta_fraction * T_cov)) & up
|
|
173
|
+
|
|
174
|
+
formed_cov_mask = (~A1) & A2 & need_change
|
|
175
|
+
broken_cov_mask = A1 & (~A2) & need_change
|
|
176
|
+
|
|
177
|
+
formed_covalent = _upper_pairs_from_mask(formed_cov_mask)
|
|
178
|
+
broken_covalent = _upper_pairs_from_mask(broken_cov_mask)
|
|
179
|
+
|
|
180
|
+
return BondChangeResult(
|
|
181
|
+
formed_covalent=formed_covalent,
|
|
182
|
+
broken_covalent=broken_covalent,
|
|
183
|
+
distances_1=D1.detach().cpu().numpy(),
|
|
184
|
+
distances_2=D2.detach().cpu().numpy(),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _bond_str(i: int, j: int, elems: List[str], one_based: bool = True) -> str:
|
|
189
|
+
ii = i + 1 if one_based else i
|
|
190
|
+
jj = j + 1 if one_based else j
|
|
191
|
+
return f"{elems[i]}{ii}-{elems[j]}{jj}"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def summarize_changes(geom, result: BondChangeResult, one_based: bool = True) -> str:
|
|
195
|
+
"""
|
|
196
|
+
List bond formations and dissociations and report bond-length changes in Å.
|
|
197
|
+
"""
|
|
198
|
+
elems = [a.capitalize() for a in geom.atoms]
|
|
199
|
+
lines: List[str] = []
|
|
200
|
+
|
|
201
|
+
# Use distance matrices (Bohr) converted to Å when available
|
|
202
|
+
D1 = result.distances_1
|
|
203
|
+
D2 = result.distances_2
|
|
204
|
+
have_lengths = (
|
|
205
|
+
isinstance(D1, np.ndarray)
|
|
206
|
+
and isinstance(D2, np.ndarray)
|
|
207
|
+
and D1.shape == D2.shape
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def _len_str(i: int, j: int) -> str:
|
|
211
|
+
if not have_lengths:
|
|
212
|
+
return ""
|
|
213
|
+
# ``coords3d`` is given in Bohr; convert to Å
|
|
214
|
+
d1 = float(D1[i, j]) * BOHR2ANG
|
|
215
|
+
d2 = float(D2[i, j]) * BOHR2ANG
|
|
216
|
+
return f" : {d1:.3f} Å --> {d2:.3f} Å"
|
|
217
|
+
|
|
218
|
+
def pairs_to_lines(title: str, pairs: Set[Pair]):
|
|
219
|
+
if not pairs:
|
|
220
|
+
lines.append(f"{title}: None")
|
|
221
|
+
return
|
|
222
|
+
lines.append(f"{title} ({len(pairs)}):")
|
|
223
|
+
for i, j in sorted(pairs):
|
|
224
|
+
lines.append(f" - {_bond_str(i, j, elems, one_based)}{_len_str(i, j)}")
|
|
225
|
+
|
|
226
|
+
pairs_to_lines("Bond formed", result.formed_covalent)
|
|
227
|
+
pairs_to_lines("Bond broken", result.broken_covalent)
|
|
228
|
+
|
|
229
|
+
return "\n".join(lines)
|
|
230
|
+
|
|
231
|
+
|
mlmm/bool_compat.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""CLI bool-argument normalization helpers.
|
|
2
|
+
|
|
3
|
+
This module keeps backward compatibility for legacy value-style boolean flags:
|
|
4
|
+
`--flag True/False`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_BOOL_TRUE_LITERALS = {"1", "true", "t", "yes", "y", "on"}
|
|
11
|
+
_BOOL_FALSE_LITERALS = {"0", "false", "f", "no", "n", "off"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _parse_bool_literal(raw: str) -> bool | None:
|
|
15
|
+
token = raw.strip().lower()
|
|
16
|
+
if token in _BOOL_TRUE_LITERALS:
|
|
17
|
+
return True
|
|
18
|
+
if token in _BOOL_FALSE_LITERALS:
|
|
19
|
+
return False
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _toggle_negative_name(
|
|
24
|
+
command: str,
|
|
25
|
+
positive_name: str,
|
|
26
|
+
toggle_negative_aliases: dict[str, dict[str, str]],
|
|
27
|
+
) -> str:
|
|
28
|
+
aliases = toggle_negative_aliases.get(command)
|
|
29
|
+
if aliases and positive_name in aliases:
|
|
30
|
+
return aliases[positive_name]
|
|
31
|
+
return f"--no-{positive_name[2:]}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _read_bool_literal(
|
|
35
|
+
args: list[str], index: int, sep: str, inline_value: str
|
|
36
|
+
) -> tuple[bool | None, int, bool]:
|
|
37
|
+
"""Return (parsed_bool, next_index, explicit_literal_used)."""
|
|
38
|
+
if sep:
|
|
39
|
+
return _parse_bool_literal(inline_value), index + 1, True
|
|
40
|
+
if index + 1 < len(args):
|
|
41
|
+
parsed_next = _parse_bool_literal(args[index + 1])
|
|
42
|
+
if parsed_next is not None:
|
|
43
|
+
return parsed_next, index + 2, True
|
|
44
|
+
return None, index + 1, False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _build_negative_to_positive_toggle_map(
|
|
48
|
+
toggle_options: frozenset[str],
|
|
49
|
+
toggle_negative_aliases: dict[str, dict[str, str]],
|
|
50
|
+
command: str,
|
|
51
|
+
) -> dict[str, str]:
|
|
52
|
+
negative_to_positive: dict[str, str] = {}
|
|
53
|
+
for positive_name in toggle_options:
|
|
54
|
+
if not positive_name.startswith("--"):
|
|
55
|
+
continue
|
|
56
|
+
canonical_negative = _toggle_negative_name(
|
|
57
|
+
command, positive_name, toggle_negative_aliases
|
|
58
|
+
)
|
|
59
|
+
negative_to_positive[canonical_negative] = positive_name
|
|
60
|
+
synthetic_no_name = f"--no-{positive_name[2:]}"
|
|
61
|
+
negative_to_positive.setdefault(synthetic_no_name, positive_name)
|
|
62
|
+
return negative_to_positive
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _build_negative_to_positive_single_flag_map(
|
|
66
|
+
single_flag_options: frozenset[str],
|
|
67
|
+
) -> dict[str, str]:
|
|
68
|
+
negative_to_positive: dict[str, str] = {}
|
|
69
|
+
for positive_name in single_flag_options:
|
|
70
|
+
if not positive_name.startswith("--"):
|
|
71
|
+
continue
|
|
72
|
+
negative_to_positive[f"--no-{positive_name[2:]}"] = positive_name
|
|
73
|
+
return negative_to_positive
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def normalize_bool_argv(
|
|
77
|
+
args: list[str],
|
|
78
|
+
bool_value_options_by_command: dict[str, frozenset[str]],
|
|
79
|
+
bool_toggle_options_by_command: dict[str, frozenset[str]],
|
|
80
|
+
toggle_negative_aliases: dict[str, dict[str, str]],
|
|
81
|
+
bool_single_flag_options_by_command: dict[str, frozenset[str]] | None = None,
|
|
82
|
+
) -> tuple[list[str], bool]:
|
|
83
|
+
"""Normalize CLI argv booleans and return (normalized_args, legacy_syntax_used)."""
|
|
84
|
+
if not args:
|
|
85
|
+
return args, False
|
|
86
|
+
|
|
87
|
+
command = args[0]
|
|
88
|
+
bool_value_options = bool_value_options_by_command.get(command, frozenset())
|
|
89
|
+
bool_toggle_options = bool_toggle_options_by_command.get(command, frozenset())
|
|
90
|
+
bool_single_flag_options = (
|
|
91
|
+
bool_single_flag_options_by_command or {}
|
|
92
|
+
).get(command, frozenset())
|
|
93
|
+
|
|
94
|
+
if not bool_value_options and not bool_toggle_options and not bool_single_flag_options:
|
|
95
|
+
return args, False
|
|
96
|
+
|
|
97
|
+
toggle_negative_to_positive = _build_negative_to_positive_toggle_map(
|
|
98
|
+
bool_toggle_options, toggle_negative_aliases, command
|
|
99
|
+
)
|
|
100
|
+
single_negative_to_positive = _build_negative_to_positive_single_flag_map(
|
|
101
|
+
bool_single_flag_options
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
normalized: list[str] = [command]
|
|
105
|
+
legacy_used = False
|
|
106
|
+
i = 1
|
|
107
|
+
while i < len(args):
|
|
108
|
+
token = args[i]
|
|
109
|
+
|
|
110
|
+
if token == "--":
|
|
111
|
+
normalized.extend(args[i:])
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
if not token.startswith("--"):
|
|
115
|
+
normalized.append(token)
|
|
116
|
+
i += 1
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
name, sep, inline_value = token.partition("=")
|
|
120
|
+
|
|
121
|
+
if name in toggle_negative_to_positive:
|
|
122
|
+
positive_name = toggle_negative_to_positive[name]
|
|
123
|
+
canonical_negative = _toggle_negative_name(
|
|
124
|
+
command, positive_name, toggle_negative_aliases
|
|
125
|
+
)
|
|
126
|
+
parsed, next_i, explicit_literal = _read_bool_literal(
|
|
127
|
+
args, i, sep, inline_value
|
|
128
|
+
)
|
|
129
|
+
if explicit_literal:
|
|
130
|
+
if parsed is None:
|
|
131
|
+
normalized.append(token)
|
|
132
|
+
else:
|
|
133
|
+
legacy_used = True
|
|
134
|
+
normalized.append(canonical_negative if parsed else positive_name)
|
|
135
|
+
i = next_i
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
normalized.append(canonical_negative)
|
|
139
|
+
i += 1
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
if name in single_negative_to_positive:
|
|
143
|
+
positive_name = single_negative_to_positive[name]
|
|
144
|
+
parsed, next_i, explicit_literal = _read_bool_literal(
|
|
145
|
+
args, i, sep, inline_value
|
|
146
|
+
)
|
|
147
|
+
if explicit_literal:
|
|
148
|
+
if parsed is None:
|
|
149
|
+
normalized.append(token)
|
|
150
|
+
else:
|
|
151
|
+
legacy_used = True
|
|
152
|
+
if not parsed:
|
|
153
|
+
normalized.append(positive_name)
|
|
154
|
+
i = next_i
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
i += 1
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
if name.startswith("--no-"):
|
|
161
|
+
positive_name = "--" + name[5:]
|
|
162
|
+
if sep == "" and positive_name in bool_value_options:
|
|
163
|
+
normalized.extend([positive_name, "False"])
|
|
164
|
+
i += 1
|
|
165
|
+
continue
|
|
166
|
+
normalized.append(token)
|
|
167
|
+
i += 1
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
if name in bool_value_options:
|
|
171
|
+
parsed, next_i, explicit_literal = _read_bool_literal(args, i, sep, inline_value)
|
|
172
|
+
if explicit_literal:
|
|
173
|
+
if parsed is not None:
|
|
174
|
+
legacy_used = True
|
|
175
|
+
normalized.extend([name, "True" if parsed else "False"])
|
|
176
|
+
else:
|
|
177
|
+
normalized.append(token)
|
|
178
|
+
i = next_i
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
normalized.extend([name, "True"])
|
|
182
|
+
i += 1
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
if name in bool_toggle_options:
|
|
186
|
+
parsed, next_i, explicit_literal = _read_bool_literal(args, i, sep, inline_value)
|
|
187
|
+
if explicit_literal:
|
|
188
|
+
if parsed is not None:
|
|
189
|
+
legacy_used = True
|
|
190
|
+
normalized.append(
|
|
191
|
+
name if parsed else _toggle_negative_name(
|
|
192
|
+
command, name, toggle_negative_aliases
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
normalized.append(token)
|
|
197
|
+
i = next_i
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
normalized.append(name)
|
|
201
|
+
i += 1
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
if name in bool_single_flag_options:
|
|
205
|
+
parsed, next_i, explicit_literal = _read_bool_literal(args, i, sep, inline_value)
|
|
206
|
+
if explicit_literal:
|
|
207
|
+
if parsed is not None:
|
|
208
|
+
legacy_used = True
|
|
209
|
+
if parsed:
|
|
210
|
+
normalized.append(name)
|
|
211
|
+
else:
|
|
212
|
+
normalized.append(token)
|
|
213
|
+
i = next_i
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
normalized.append(name)
|
|
217
|
+
i += 1
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
normalized.append(token)
|
|
221
|
+
i += 1
|
|
222
|
+
|
|
223
|
+
return normalized, legacy_used
|