mlmm-toolkit 0.2.2.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hessian_ff/__init__.py +50 -0
- hessian_ff/analytical_hessian.py +609 -0
- hessian_ff/constants.py +46 -0
- hessian_ff/forcefield.py +339 -0
- hessian_ff/loaders.py +608 -0
- hessian_ff/native/Makefile +8 -0
- hessian_ff/native/__init__.py +28 -0
- hessian_ff/native/analytical_hessian.py +88 -0
- hessian_ff/native/analytical_hessian_ext.cpp +258 -0
- hessian_ff/native/bonded.py +82 -0
- hessian_ff/native/bonded_ext.cpp +640 -0
- hessian_ff/native/loader.py +349 -0
- hessian_ff/native/nonbonded.py +118 -0
- hessian_ff/native/nonbonded_ext.cpp +1150 -0
- hessian_ff/prmtop_parmed.py +23 -0
- hessian_ff/system.py +107 -0
- hessian_ff/terms/__init__.py +14 -0
- hessian_ff/terms/angle.py +73 -0
- hessian_ff/terms/bond.py +44 -0
- hessian_ff/terms/cmap.py +406 -0
- hessian_ff/terms/dihedral.py +141 -0
- hessian_ff/terms/nonbonded.py +209 -0
- hessian_ff/tests/__init__.py +0 -0
- hessian_ff/tests/conftest.py +75 -0
- hessian_ff/tests/data/small/complex.parm7 +1346 -0
- hessian_ff/tests/data/small/complex.pdb +125 -0
- hessian_ff/tests/data/small/complex.rst7 +63 -0
- hessian_ff/tests/test_coords_input.py +44 -0
- hessian_ff/tests/test_energy_force.py +49 -0
- hessian_ff/tests/test_hessian.py +137 -0
- hessian_ff/tests/test_smoke.py +18 -0
- hessian_ff/tests/test_validation.py +40 -0
- hessian_ff/workflows.py +889 -0
- mlmm/__init__.py +36 -0
- mlmm/__main__.py +7 -0
- mlmm/_version.py +34 -0
- mlmm/add_elem_info.py +374 -0
- mlmm/advanced_help.py +91 -0
- mlmm/align_freeze_atoms.py +601 -0
- mlmm/all.py +3535 -0
- mlmm/bond_changes.py +231 -0
- mlmm/bool_compat.py +223 -0
- mlmm/cli.py +574 -0
- mlmm/cli_utils.py +166 -0
- mlmm/default_group.py +337 -0
- mlmm/defaults.py +467 -0
- mlmm/define_layer.py +526 -0
- mlmm/dft.py +1041 -0
- mlmm/energy_diagram.py +253 -0
- mlmm/extract.py +2213 -0
- mlmm/fix_altloc.py +464 -0
- mlmm/freq.py +1406 -0
- mlmm/harmonic_constraints.py +140 -0
- mlmm/hessian_cache.py +44 -0
- mlmm/hessian_calc.py +174 -0
- mlmm/irc.py +638 -0
- mlmm/mlmm_calc.py +2262 -0
- mlmm/mm_parm.py +945 -0
- mlmm/oniom_export.py +1983 -0
- mlmm/oniom_import.py +457 -0
- mlmm/opt.py +1742 -0
- mlmm/path_opt.py +1353 -0
- mlmm/path_search.py +2299 -0
- mlmm/preflight.py +88 -0
- mlmm/py.typed +1 -0
- mlmm/pysis_runner.py +45 -0
- mlmm/scan.py +1047 -0
- mlmm/scan2d.py +1226 -0
- mlmm/scan3d.py +1265 -0
- mlmm/scan_common.py +184 -0
- mlmm/summary_log.py +736 -0
- mlmm/trj2fig.py +448 -0
- mlmm/tsopt.py +2871 -0
- mlmm/utils.py +2309 -0
- mlmm/xtb_embedcharge_correction.py +475 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/METADATA +1159 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/RECORD +372 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/WHEEL +5 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/entry_points.txt +2 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/licenses/LICENSE +674 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/top_level.txt +4 -0
- pysisyphus/Geometry.py +1667 -0
- pysisyphus/LICENSE +674 -0
- pysisyphus/TableFormatter.py +63 -0
- pysisyphus/TablePrinter.py +74 -0
- pysisyphus/__init__.py +12 -0
- pysisyphus/calculators/AFIR.py +452 -0
- pysisyphus/calculators/AnaPot.py +20 -0
- pysisyphus/calculators/AnaPot2.py +48 -0
- pysisyphus/calculators/AnaPot3.py +12 -0
- pysisyphus/calculators/AnaPot4.py +20 -0
- pysisyphus/calculators/AnaPotBase.py +337 -0
- pysisyphus/calculators/AnaPotCBM.py +25 -0
- pysisyphus/calculators/AtomAtomTransTorque.py +154 -0
- pysisyphus/calculators/CFOUR.py +250 -0
- pysisyphus/calculators/Calculator.py +844 -0
- pysisyphus/calculators/CerjanMiller.py +24 -0
- pysisyphus/calculators/Composite.py +123 -0
- pysisyphus/calculators/ConicalIntersection.py +171 -0
- pysisyphus/calculators/DFTBp.py +430 -0
- pysisyphus/calculators/DFTD3.py +66 -0
- pysisyphus/calculators/DFTD4.py +84 -0
- pysisyphus/calculators/Dalton.py +61 -0
- pysisyphus/calculators/Dimer.py +681 -0
- pysisyphus/calculators/Dummy.py +20 -0
- pysisyphus/calculators/EGO.py +76 -0
- pysisyphus/calculators/EnergyMin.py +224 -0
- pysisyphus/calculators/ExternalPotential.py +264 -0
- pysisyphus/calculators/FakeASE.py +35 -0
- pysisyphus/calculators/FourWellAnaPot.py +28 -0
- pysisyphus/calculators/FreeEndNEBPot.py +39 -0
- pysisyphus/calculators/Gaussian09.py +18 -0
- pysisyphus/calculators/Gaussian16.py +726 -0
- pysisyphus/calculators/HardSphere.py +159 -0
- pysisyphus/calculators/IDPPCalculator.py +49 -0
- pysisyphus/calculators/IPIClient.py +133 -0
- pysisyphus/calculators/IPIServer.py +234 -0
- pysisyphus/calculators/LEPSBase.py +24 -0
- pysisyphus/calculators/LEPSExpr.py +139 -0
- pysisyphus/calculators/LennardJones.py +80 -0
- pysisyphus/calculators/MOPAC.py +219 -0
- pysisyphus/calculators/MullerBrownSympyPot.py +51 -0
- pysisyphus/calculators/MultiCalc.py +85 -0
- pysisyphus/calculators/NFK.py +45 -0
- pysisyphus/calculators/OBabel.py +87 -0
- pysisyphus/calculators/ONIOMv2.py +1129 -0
- pysisyphus/calculators/ORCA.py +893 -0
- pysisyphus/calculators/ORCA5.py +6 -0
- pysisyphus/calculators/OpenMM.py +88 -0
- pysisyphus/calculators/OpenMolcas.py +281 -0
- pysisyphus/calculators/OverlapCalculator.py +908 -0
- pysisyphus/calculators/Psi4.py +218 -0
- pysisyphus/calculators/PyPsi4.py +37 -0
- pysisyphus/calculators/PySCF.py +341 -0
- pysisyphus/calculators/PyXTB.py +73 -0
- pysisyphus/calculators/QCEngine.py +106 -0
- pysisyphus/calculators/Rastrigin.py +22 -0
- pysisyphus/calculators/Remote.py +76 -0
- pysisyphus/calculators/Rosenbrock.py +15 -0
- pysisyphus/calculators/SocketCalc.py +97 -0
- pysisyphus/calculators/TIP3P.py +111 -0
- pysisyphus/calculators/TransTorque.py +161 -0
- pysisyphus/calculators/Turbomole.py +965 -0
- pysisyphus/calculators/VRIPot.py +37 -0
- pysisyphus/calculators/WFOWrapper.py +333 -0
- pysisyphus/calculators/WFOWrapper2.py +341 -0
- pysisyphus/calculators/XTB.py +418 -0
- pysisyphus/calculators/__init__.py +81 -0
- pysisyphus/calculators/cosmo_data.py +139 -0
- pysisyphus/calculators/parser.py +150 -0
- pysisyphus/color.py +19 -0
- pysisyphus/config.py +133 -0
- pysisyphus/constants.py +65 -0
- pysisyphus/cos/AdaptiveNEB.py +230 -0
- pysisyphus/cos/ChainOfStates.py +725 -0
- pysisyphus/cos/FreeEndNEB.py +25 -0
- pysisyphus/cos/FreezingString.py +103 -0
- pysisyphus/cos/GrowingChainOfStates.py +71 -0
- pysisyphus/cos/GrowingNT.py +309 -0
- pysisyphus/cos/GrowingString.py +508 -0
- pysisyphus/cos/NEB.py +189 -0
- pysisyphus/cos/SimpleZTS.py +64 -0
- pysisyphus/cos/__init__.py +22 -0
- pysisyphus/cos/stiffness.py +199 -0
- pysisyphus/drivers/__init__.py +17 -0
- pysisyphus/drivers/afir.py +855 -0
- pysisyphus/drivers/barriers.py +271 -0
- pysisyphus/drivers/birkholz.py +138 -0
- pysisyphus/drivers/cluster.py +318 -0
- pysisyphus/drivers/diabatization.py +133 -0
- pysisyphus/drivers/merge.py +368 -0
- pysisyphus/drivers/merge_mol2.py +322 -0
- pysisyphus/drivers/opt.py +375 -0
- pysisyphus/drivers/perf.py +91 -0
- pysisyphus/drivers/pka.py +52 -0
- pysisyphus/drivers/precon_pos_rot.py +669 -0
- pysisyphus/drivers/rates.py +480 -0
- pysisyphus/drivers/replace.py +219 -0
- pysisyphus/drivers/scan.py +212 -0
- pysisyphus/drivers/spectrum.py +166 -0
- pysisyphus/drivers/thermo.py +31 -0
- pysisyphus/dynamics/Gaussian.py +103 -0
- pysisyphus/dynamics/__init__.py +20 -0
- pysisyphus/dynamics/colvars.py +136 -0
- pysisyphus/dynamics/driver.py +297 -0
- pysisyphus/dynamics/helpers.py +256 -0
- pysisyphus/dynamics/lincs.py +105 -0
- pysisyphus/dynamics/mdp.py +364 -0
- pysisyphus/dynamics/rattle.py +121 -0
- pysisyphus/dynamics/thermostats.py +128 -0
- pysisyphus/dynamics/wigner.py +266 -0
- pysisyphus/elem_data.py +3473 -0
- pysisyphus/exceptions.py +2 -0
- pysisyphus/filtertrj.py +69 -0
- pysisyphus/helpers.py +623 -0
- pysisyphus/helpers_pure.py +649 -0
- pysisyphus/init_logging.py +50 -0
- pysisyphus/intcoords/Bend.py +69 -0
- pysisyphus/intcoords/Bend2.py +25 -0
- pysisyphus/intcoords/BondedFragment.py +32 -0
- pysisyphus/intcoords/Cartesian.py +41 -0
- pysisyphus/intcoords/CartesianCoords.py +140 -0
- pysisyphus/intcoords/Coords.py +56 -0
- pysisyphus/intcoords/DLC.py +197 -0
- pysisyphus/intcoords/DistanceFunction.py +34 -0
- pysisyphus/intcoords/DummyImproper.py +70 -0
- pysisyphus/intcoords/DummyTorsion.py +72 -0
- pysisyphus/intcoords/LinearBend.py +105 -0
- pysisyphus/intcoords/LinearDisplacement.py +80 -0
- pysisyphus/intcoords/OutOfPlane.py +59 -0
- pysisyphus/intcoords/PrimTypes.py +286 -0
- pysisyphus/intcoords/Primitive.py +137 -0
- pysisyphus/intcoords/RedundantCoords.py +659 -0
- pysisyphus/intcoords/RobustTorsion.py +59 -0
- pysisyphus/intcoords/Rotation.py +147 -0
- pysisyphus/intcoords/Stretch.py +31 -0
- pysisyphus/intcoords/Torsion.py +101 -0
- pysisyphus/intcoords/Torsion2.py +25 -0
- pysisyphus/intcoords/Translation.py +45 -0
- pysisyphus/intcoords/__init__.py +61 -0
- pysisyphus/intcoords/augment_bonds.py +126 -0
- pysisyphus/intcoords/derivatives.py +10512 -0
- pysisyphus/intcoords/eval.py +80 -0
- pysisyphus/intcoords/exceptions.py +37 -0
- pysisyphus/intcoords/findiffs.py +48 -0
- pysisyphus/intcoords/generate_derivatives.py +414 -0
- pysisyphus/intcoords/helpers.py +235 -0
- pysisyphus/intcoords/logging_conf.py +10 -0
- pysisyphus/intcoords/mp_derivatives.py +10836 -0
- pysisyphus/intcoords/setup.py +962 -0
- pysisyphus/intcoords/setup_fast.py +176 -0
- pysisyphus/intcoords/update.py +272 -0
- pysisyphus/intcoords/valid.py +89 -0
- pysisyphus/interpolate/Geodesic.py +93 -0
- pysisyphus/interpolate/IDPP.py +55 -0
- pysisyphus/interpolate/Interpolator.py +116 -0
- pysisyphus/interpolate/LST.py +70 -0
- pysisyphus/interpolate/Redund.py +152 -0
- pysisyphus/interpolate/__init__.py +9 -0
- pysisyphus/interpolate/helpers.py +34 -0
- pysisyphus/io/__init__.py +22 -0
- pysisyphus/io/aomix.py +178 -0
- pysisyphus/io/cjson.py +24 -0
- pysisyphus/io/crd.py +101 -0
- pysisyphus/io/cube.py +220 -0
- pysisyphus/io/fchk.py +184 -0
- pysisyphus/io/hdf5.py +49 -0
- pysisyphus/io/hessian.py +72 -0
- pysisyphus/io/mol2.py +146 -0
- pysisyphus/io/molden.py +293 -0
- pysisyphus/io/orca.py +189 -0
- pysisyphus/io/pdb.py +269 -0
- pysisyphus/io/psf.py +79 -0
- pysisyphus/io/pubchem.py +31 -0
- pysisyphus/io/qcschema.py +34 -0
- pysisyphus/io/sdf.py +29 -0
- pysisyphus/io/xyz.py +61 -0
- pysisyphus/io/zmat.py +175 -0
- pysisyphus/irc/DWI.py +108 -0
- pysisyphus/irc/DampedVelocityVerlet.py +134 -0
- pysisyphus/irc/Euler.py +22 -0
- pysisyphus/irc/EulerPC.py +345 -0
- pysisyphus/irc/GonzalezSchlegel.py +187 -0
- pysisyphus/irc/IMKMod.py +164 -0
- pysisyphus/irc/IRC.py +878 -0
- pysisyphus/irc/IRCDummy.py +10 -0
- pysisyphus/irc/Instanton.py +307 -0
- pysisyphus/irc/LQA.py +53 -0
- pysisyphus/irc/ModeKill.py +136 -0
- pysisyphus/irc/ParamPlot.py +53 -0
- pysisyphus/irc/RK4.py +36 -0
- pysisyphus/irc/__init__.py +31 -0
- pysisyphus/irc/initial_displ.py +219 -0
- pysisyphus/linalg.py +411 -0
- pysisyphus/line_searches/Backtracking.py +88 -0
- pysisyphus/line_searches/HagerZhang.py +184 -0
- pysisyphus/line_searches/LineSearch.py +232 -0
- pysisyphus/line_searches/StrongWolfe.py +108 -0
- pysisyphus/line_searches/__init__.py +9 -0
- pysisyphus/line_searches/interpol.py +15 -0
- pysisyphus/modefollow/NormalMode.py +40 -0
- pysisyphus/modefollow/__init__.py +10 -0
- pysisyphus/modefollow/davidson.py +199 -0
- pysisyphus/modefollow/lanczos.py +95 -0
- pysisyphus/optimizers/BFGS.py +99 -0
- pysisyphus/optimizers/BacktrackingOptimizer.py +113 -0
- pysisyphus/optimizers/ConjugateGradient.py +98 -0
- pysisyphus/optimizers/CubicNewton.py +75 -0
- pysisyphus/optimizers/FIRE.py +113 -0
- pysisyphus/optimizers/HessianOptimizer.py +1176 -0
- pysisyphus/optimizers/LBFGS.py +228 -0
- pysisyphus/optimizers/LayerOpt.py +411 -0
- pysisyphus/optimizers/MicroOptimizer.py +169 -0
- pysisyphus/optimizers/NCOptimizer.py +90 -0
- pysisyphus/optimizers/Optimizer.py +1084 -0
- pysisyphus/optimizers/PreconLBFGS.py +260 -0
- pysisyphus/optimizers/PreconSteepestDescent.py +7 -0
- pysisyphus/optimizers/QuickMin.py +74 -0
- pysisyphus/optimizers/RFOptimizer.py +181 -0
- pysisyphus/optimizers/RSA.py +99 -0
- pysisyphus/optimizers/StabilizedQNMethod.py +248 -0
- pysisyphus/optimizers/SteepestDescent.py +23 -0
- pysisyphus/optimizers/StringOptimizer.py +173 -0
- pysisyphus/optimizers/__init__.py +41 -0
- pysisyphus/optimizers/closures.py +301 -0
- pysisyphus/optimizers/cls_map.py +58 -0
- pysisyphus/optimizers/exceptions.py +6 -0
- pysisyphus/optimizers/gdiis.py +280 -0
- pysisyphus/optimizers/guess_hessians.py +311 -0
- pysisyphus/optimizers/hessian_updates.py +355 -0
- pysisyphus/optimizers/poly_fit.py +285 -0
- pysisyphus/optimizers/precon.py +153 -0
- pysisyphus/optimizers/restrict_step.py +24 -0
- pysisyphus/pack.py +172 -0
- pysisyphus/peakdetect.py +948 -0
- pysisyphus/plot.py +1031 -0
- pysisyphus/run.py +2106 -0
- pysisyphus/socket_helper.py +74 -0
- pysisyphus/stocastic/FragmentKick.py +132 -0
- pysisyphus/stocastic/Kick.py +81 -0
- pysisyphus/stocastic/Pipeline.py +303 -0
- pysisyphus/stocastic/__init__.py +21 -0
- pysisyphus/stocastic/align.py +127 -0
- pysisyphus/testing.py +96 -0
- pysisyphus/thermo.py +156 -0
- pysisyphus/trj.py +824 -0
- pysisyphus/tsoptimizers/RSIRFOptimizer.py +56 -0
- pysisyphus/tsoptimizers/RSPRFOptimizer.py +182 -0
- pysisyphus/tsoptimizers/TRIM.py +59 -0
- pysisyphus/tsoptimizers/TSHessianOptimizer.py +463 -0
- pysisyphus/tsoptimizers/__init__.py +23 -0
- pysisyphus/wavefunction/Basis.py +239 -0
- pysisyphus/wavefunction/DIIS.py +76 -0
- pysisyphus/wavefunction/__init__.py +25 -0
- pysisyphus/wavefunction/build_ext.py +42 -0
- pysisyphus/wavefunction/cart2sph.py +190 -0
- pysisyphus/wavefunction/diabatization.py +304 -0
- pysisyphus/wavefunction/excited_states.py +435 -0
- pysisyphus/wavefunction/gen_ints.py +1811 -0
- pysisyphus/wavefunction/helpers.py +104 -0
- pysisyphus/wavefunction/ints/__init__.py +0 -0
- pysisyphus/wavefunction/ints/boys.py +193 -0
- pysisyphus/wavefunction/ints/boys_table_N_64_xasym_27.1_step_0.01.npy +0 -0
- pysisyphus/wavefunction/ints/cart_gto3d.py +176 -0
- pysisyphus/wavefunction/ints/coulomb3d.py +25928 -0
- pysisyphus/wavefunction/ints/diag_quadrupole3d.py +10036 -0
- pysisyphus/wavefunction/ints/dipole3d.py +8762 -0
- pysisyphus/wavefunction/ints/int2c2e3d.py +7198 -0
- pysisyphus/wavefunction/ints/int3c2e3d_sph.py +65040 -0
- pysisyphus/wavefunction/ints/kinetic3d.py +8240 -0
- pysisyphus/wavefunction/ints/ovlp3d.py +3777 -0
- pysisyphus/wavefunction/ints/quadrupole3d.py +15054 -0
- pysisyphus/wavefunction/ints/self_ovlp3d.py +198 -0
- pysisyphus/wavefunction/localization.py +458 -0
- pysisyphus/wavefunction/multipole.py +159 -0
- pysisyphus/wavefunction/normalization.py +36 -0
- pysisyphus/wavefunction/pop_analysis.py +134 -0
- pysisyphus/wavefunction/shells.py +1171 -0
- pysisyphus/wavefunction/wavefunction.py +504 -0
- pysisyphus/wrapper/__init__.py +11 -0
- pysisyphus/wrapper/exceptions.py +2 -0
- pysisyphus/wrapper/jmol.py +120 -0
- pysisyphus/wrapper/mwfn.py +169 -0
- pysisyphus/wrapper/packmol.py +71 -0
- pysisyphus/xyzloader.py +168 -0
- pysisyphus/yaml_mods.py +45 -0
- thermoanalysis/LICENSE +674 -0
- thermoanalysis/QCData.py +244 -0
- thermoanalysis/__init__.py +0 -0
- thermoanalysis/config.py +3 -0
- thermoanalysis/constants.py +20 -0
- thermoanalysis/thermo.py +1011 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from openbabel import pybel
|
|
9
|
+
|
|
10
|
+
HAS_OPENBABEL = True
|
|
11
|
+
except ModuleNotFoundError:
|
|
12
|
+
HAS_OPENBABEL = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from pysisyphus.calculators import (
|
|
16
|
+
Composite,
|
|
17
|
+
HardSphere,
|
|
18
|
+
PWHardSphere,
|
|
19
|
+
TransTorque,
|
|
20
|
+
)
|
|
21
|
+
from pysisyphus.calculators.OBabel import OBabel
|
|
22
|
+
from pysisyphus.drivers.precon_pos_rot import (
|
|
23
|
+
center_fragments,
|
|
24
|
+
form_A,
|
|
25
|
+
get_steps_to_active_atom_mean,
|
|
26
|
+
get_which_frag,
|
|
27
|
+
rotate_inplace,
|
|
28
|
+
SteepestDescent,
|
|
29
|
+
)
|
|
30
|
+
from pysisyphus.Geometry import Geometry
|
|
31
|
+
from pysisyphus.helpers import align_coords, geom_loader
|
|
32
|
+
from pysisyphus.io import geom_to_crd_str
|
|
33
|
+
from pysisyphus.linalg import get_rot_mat_for_coords
|
|
34
|
+
from pysisyphus.optimizers.LBFGS import LBFGS
|
|
35
|
+
from pysisyphus.xyzloader import coords_to_trj
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def merge_geoms(geom1, geom2, geom1_del=None, geom2_del=None, make_bonds=None):
|
|
39
|
+
"""Merge geom1 and geom2 while keeping the original coordinates.
|
|
40
|
+
|
|
41
|
+
Supports deleting certain atoms.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
if geom1_del is None:
|
|
45
|
+
geom1_del = list()
|
|
46
|
+
if geom2_del is None:
|
|
47
|
+
geom2_del = list()
|
|
48
|
+
|
|
49
|
+
geom1_del = np.array(geom1_del)
|
|
50
|
+
geom2_del = np.array(geom2_del)
|
|
51
|
+
# Update indices of atoms to be deleted in geom2 by the number of atoms in geom1,
|
|
52
|
+
# as geom1 and geom2 will be merged.
|
|
53
|
+
# If we want to delete atom 0 and atom 1 in geom2 and geom1 comprises 10 atoms,
|
|
54
|
+
# then the updated indices in geom2 will be 0 + 10 = 10 and 1 + 10 = 11 in the
|
|
55
|
+
# merged geometry.
|
|
56
|
+
atom_num1 = len(geom1.atoms)
|
|
57
|
+
geom2_del += atom_num1
|
|
58
|
+
|
|
59
|
+
ndel1 = len(geom1_del)
|
|
60
|
+
|
|
61
|
+
if make_bonds is not None:
|
|
62
|
+
make_bonds = np.array(make_bonds, dtype=int)
|
|
63
|
+
geom1_bond_inds, geom2_bond_inds = make_bonds.T
|
|
64
|
+
geom2_bond_inds += atom_num1
|
|
65
|
+
# Correct original bond indices given in make_bonds
|
|
66
|
+
#
|
|
67
|
+
# Determe how many atoms to be deleted lie below the atom(s) that are
|
|
68
|
+
# used to form new bonds.
|
|
69
|
+
corr1 = (geom1_del < geom1_bond_inds[:, None]).sum(axis=1)
|
|
70
|
+
geom1_bond_inds -= corr1
|
|
71
|
+
# Correct bond indices in geom2 by the number of deleted atoms in geom1.
|
|
72
|
+
geom2_bond_inds -= ndel1
|
|
73
|
+
corr2 = (geom2_del < geom2_bond_inds[:, None]).sum(axis=1)
|
|
74
|
+
geom2_bond_inds -= corr2
|
|
75
|
+
make_bonds_cor = np.stack((geom1_bond_inds, geom2_bond_inds), axis=1)
|
|
76
|
+
else:
|
|
77
|
+
make_bonds_cor = None
|
|
78
|
+
|
|
79
|
+
union = geom1 + geom2
|
|
80
|
+
union = union.del_atoms(list(geom1_del) + list(geom2_del))
|
|
81
|
+
|
|
82
|
+
# Set appropriate fragments
|
|
83
|
+
atom_num1 -= len(geom1_del)
|
|
84
|
+
frag1 = np.arange(atom_num1)
|
|
85
|
+
lig_atoms = geom2.atoms
|
|
86
|
+
frag2 = np.arange(atom_num1, atom_num1 + len(lig_atoms) - len(geom2_del))
|
|
87
|
+
union.fragments = {"geom1": frag1.tolist(), "geom2": frag2.tolist()}
|
|
88
|
+
|
|
89
|
+
return union, make_bonds_cor
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def hardsphere_merge(geom1, geom2):
|
|
93
|
+
union = geom1 + geom2
|
|
94
|
+
|
|
95
|
+
atom_num = len(geom1.atoms)
|
|
96
|
+
# Set up lists containing the atom indices for the two fragments
|
|
97
|
+
frag_lists = [
|
|
98
|
+
[i for i, _ in enumerate(geom1.atoms)],
|
|
99
|
+
[atom_num + i for i, _ in enumerate(geom2.atoms)],
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
def get_hs(kappa=1.0):
|
|
103
|
+
return HardSphere(
|
|
104
|
+
union,
|
|
105
|
+
frag_lists,
|
|
106
|
+
permutations=True,
|
|
107
|
+
kappa=kappa,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
union.set_calculator(get_hs(1.0))
|
|
111
|
+
opt_kwargs = {
|
|
112
|
+
"max_cycles": 1000,
|
|
113
|
+
"max_step": 0.5,
|
|
114
|
+
"rms_force": 0.0005,
|
|
115
|
+
}
|
|
116
|
+
opt = SteepestDescent(union, **opt_kwargs)
|
|
117
|
+
opt.run()
|
|
118
|
+
return union
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def prepare_merge(geom1, bond_diff, geom2=None, del1=None, del2=None, dump=False):
|
|
122
|
+
bond_diff = np.array(bond_diff, dtype=int)
|
|
123
|
+
if del1 is not None:
|
|
124
|
+
geom1 = geom1.del_atoms(del1)
|
|
125
|
+
|
|
126
|
+
if geom2:
|
|
127
|
+
if del2 is not None:
|
|
128
|
+
geom2 = geom2.del_atoms(del2)
|
|
129
|
+
union = geom1 + geom2
|
|
130
|
+
atom_num = len(geom1.atoms)
|
|
131
|
+
# Set up lists containing the atom indices for the two fragments
|
|
132
|
+
frag_lists = [
|
|
133
|
+
[i for i, _ in enumerate(geom1.atoms)],
|
|
134
|
+
[atom_num + i for i, _ in enumerate(geom2.atoms)],
|
|
135
|
+
]
|
|
136
|
+
fragments = {"geom1": frag_lists[0], "geom2": frag_lists[1]}
|
|
137
|
+
union.fragments = fragments
|
|
138
|
+
else:
|
|
139
|
+
union = geom1
|
|
140
|
+
frag_lists = [union.fragments["geom1"], union.fragments["geom2"]]
|
|
141
|
+
|
|
142
|
+
backup = list()
|
|
143
|
+
|
|
144
|
+
def keep(comment):
|
|
145
|
+
backup.append((union.cart_coords.copy(), comment))
|
|
146
|
+
|
|
147
|
+
keep("Initial union")
|
|
148
|
+
|
|
149
|
+
which_frag = get_which_frag(frag_lists)
|
|
150
|
+
AR = form_A(frag_lists, which_frag, bond_diff)
|
|
151
|
+
|
|
152
|
+
# Center fragments at their geometric average
|
|
153
|
+
center_fragments(frag_lists, union)
|
|
154
|
+
keep("Centered fragments")
|
|
155
|
+
|
|
156
|
+
# Translate centroid of active atoms to origin
|
|
157
|
+
alphas = get_steps_to_active_atom_mean(frag_lists, frag_lists, AR, union.coords3d)
|
|
158
|
+
for frag, alpha in zip(frag_lists, alphas):
|
|
159
|
+
union.coords3d[frag] += alpha
|
|
160
|
+
keep("Shifted centroids to active atoms")
|
|
161
|
+
|
|
162
|
+
def get_hs(kappa=1.0, **kwargs):
|
|
163
|
+
return HardSphere(
|
|
164
|
+
union,
|
|
165
|
+
frag_lists,
|
|
166
|
+
permutations=True,
|
|
167
|
+
kappa=kappa,
|
|
168
|
+
**kwargs,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
union.set_calculator(get_hs(1.0))
|
|
172
|
+
opt_kwargs = {
|
|
173
|
+
"max_cycles": 1000,
|
|
174
|
+
"max_step": 0.5,
|
|
175
|
+
"rms_force": 0.0005,
|
|
176
|
+
}
|
|
177
|
+
opt = SteepestDescent(union, **opt_kwargs)
|
|
178
|
+
opt.run()
|
|
179
|
+
keep("Initial hardsphere optimization")
|
|
180
|
+
|
|
181
|
+
# Corresponds to A.3 S_3 initial orientation of molecules in Habershon paper
|
|
182
|
+
rotate_inplace(frag_lists, union, bond_diff)
|
|
183
|
+
keep("Rotated after inital hardsphere optimization")
|
|
184
|
+
|
|
185
|
+
sub_frags = [bond_diff[:, 0].tolist(), bond_diff[:, 1].tolist()]
|
|
186
|
+
keys_calcs = {
|
|
187
|
+
"hs": get_hs(100.0, radii_offset=3.0),
|
|
188
|
+
"tt": TransTorque(frag_lists, frag_lists, AR, AR, kappa=2, do_trans=True),
|
|
189
|
+
"pwhs": PWHardSphere(union, frag_lists, sub_frags=sub_frags, kappa=10.0),
|
|
190
|
+
}
|
|
191
|
+
comp = Composite("hs + tt + pwhs", keys_calcs, remove_translation=True)
|
|
192
|
+
union.set_calculator(comp)
|
|
193
|
+
|
|
194
|
+
max_cycles = 15_000
|
|
195
|
+
max_dist = 50
|
|
196
|
+
max_step = max_dist / max_cycles
|
|
197
|
+
opt_kwargs = {
|
|
198
|
+
"max_cycles": max_cycles,
|
|
199
|
+
"max_step": max_step,
|
|
200
|
+
"rms_force": 0.1,
|
|
201
|
+
# "dump": True,
|
|
202
|
+
}
|
|
203
|
+
opt = SteepestDescent(union, **opt_kwargs)
|
|
204
|
+
opt.run()
|
|
205
|
+
keep("Hardsphere + TransTorque optimization")
|
|
206
|
+
|
|
207
|
+
if dump:
|
|
208
|
+
kept_coords, kept_comments = zip(*backup)
|
|
209
|
+
align_coords(kept_coords)
|
|
210
|
+
fn = "merge_dump_trj.xyz"
|
|
211
|
+
coords_to_trj(fn, union.atoms, kept_coords, comments=kept_comments)
|
|
212
|
+
|
|
213
|
+
return union
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def merge_opt(union, bond_diff, ff="mmff94"):
|
|
217
|
+
"""Fragment merging along given bond by forcefield optimization."""
|
|
218
|
+
|
|
219
|
+
geom1 = union.get_fragments("geom1")
|
|
220
|
+
freeze = list(range(len(geom1.atoms)))
|
|
221
|
+
|
|
222
|
+
# Create pybel.Molecule/OBMol to set the missing bonds
|
|
223
|
+
mol = pybel.readstring("xyz", union.as_xyz())
|
|
224
|
+
obmol = mol.OBMol
|
|
225
|
+
for from_, to_ in bond_diff:
|
|
226
|
+
obmol.AddBond(int(from_ + 1), int(to_ + 1), 1)
|
|
227
|
+
|
|
228
|
+
# Use modified pybel.Molecule
|
|
229
|
+
calc = OBabel(mol=mol, ff=ff)
|
|
230
|
+
funion = union.copy()
|
|
231
|
+
# Only releax second fragment
|
|
232
|
+
funion.freeze_atoms = freeze
|
|
233
|
+
funion.set_calculator(calc)
|
|
234
|
+
|
|
235
|
+
opt = LBFGS(funion, max_cycles=1000, max_step=0.5, dump=False, print_every=25)
|
|
236
|
+
opt.run()
|
|
237
|
+
|
|
238
|
+
return funion
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def align_on_subset(geom1, union, del1=None):
|
|
242
|
+
"""Align 'union' onto subset of 'geom1'"""
|
|
243
|
+
|
|
244
|
+
# Delete some coordinates (atoms) in geom1, that are not present in union
|
|
245
|
+
coords3d_1 = geom1.coords3d.copy()
|
|
246
|
+
atoms1 = geom1.atoms
|
|
247
|
+
if del1 is not None:
|
|
248
|
+
atoms1 = [atom for i, atom in enumerate(atoms1) if i not in del1]
|
|
249
|
+
coords3d_1 = np.delete(coords3d_1, del1, axis=0)
|
|
250
|
+
atoms1 = tuple(atoms1)
|
|
251
|
+
num1 = coords3d_1.shape[0]
|
|
252
|
+
# Restrict length of union to length of coords3d_1
|
|
253
|
+
coords3d_2 = union.coords3d.copy()
|
|
254
|
+
coords3d_2_subset = coords3d_2[:num1]
|
|
255
|
+
atoms2 = union.atoms
|
|
256
|
+
assert atoms1 == tuple(atoms2[:num1])
|
|
257
|
+
|
|
258
|
+
*_, rot_mat = get_rot_mat_for_coords(coords3d_1, coords3d_2_subset)
|
|
259
|
+
|
|
260
|
+
# Align merged system
|
|
261
|
+
coords3d_2_aligned = (coords3d_2 - coords3d_2.mean(axis=0)[None, :]).dot(rot_mat)
|
|
262
|
+
|
|
263
|
+
# Translate aligned system so that centroids of subsets match
|
|
264
|
+
centroid_1 = coords3d_1.mean(axis=0)
|
|
265
|
+
centroid_2 = coords3d_2_aligned[:num1].mean(axis=0)
|
|
266
|
+
coords3d_2_aligned += centroid_1 - centroid_2
|
|
267
|
+
|
|
268
|
+
aligned = Geometry(atoms2, coords3d_2_aligned)
|
|
269
|
+
subset = Geometry(atoms2[:num1], coords3d_2_aligned[:num1])
|
|
270
|
+
rest = Geometry(atoms2[num1:], coords3d_2_aligned[num1:])
|
|
271
|
+
return aligned, subset, rest
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def merge_with_frozen_geom(
|
|
275
|
+
frozen_geom, lig_geom, make_bonds, frozen_del, lig_del, ff="mmff94"
|
|
276
|
+
):
|
|
277
|
+
union, make_bonds_cor = merge_geoms(
|
|
278
|
+
frozen_geom, lig_geom, frozen_del, lig_del, make_bonds
|
|
279
|
+
)
|
|
280
|
+
atoms = union.atoms
|
|
281
|
+
print("Docking to form bonds:")
|
|
282
|
+
for i, (from_, to_) in enumerate(make_bonds_cor):
|
|
283
|
+
from_atom = atoms[from_]
|
|
284
|
+
to_atom = atoms[to_]
|
|
285
|
+
print(f"\t{i:02d} {from_atom}{from_}-{to_atom}{to_}")
|
|
286
|
+
|
|
287
|
+
union = prepare_merge(union, make_bonds_cor, dump=True)
|
|
288
|
+
if HAS_OPENBABEL:
|
|
289
|
+
opt_union = merge_opt(union, make_bonds_cor, ff=ff)
|
|
290
|
+
else:
|
|
291
|
+
warnings.warn(f"Could not import openbabel. Skipping '{ff}' optimization.")
|
|
292
|
+
opt_union = union
|
|
293
|
+
# aligned: whole system
|
|
294
|
+
# subset: frozen_geom - deleted atoms
|
|
295
|
+
# rest: aligned ligand
|
|
296
|
+
aligned, subset, rest = align_on_subset(frozen_geom, opt_union, frozen_del)
|
|
297
|
+
return aligned, subset, rest
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def parse_args(args):
|
|
301
|
+
parser = argparse.ArgumentParser()
|
|
302
|
+
|
|
303
|
+
parser.add_argument("frozen_fn", help="Filename of the frozen geometry.")
|
|
304
|
+
parser.add_argument("lig_fn", help="Filename of the ligand to be merged.")
|
|
305
|
+
parser.add_argument(
|
|
306
|
+
"--frozen-del",
|
|
307
|
+
nargs="+",
|
|
308
|
+
type=int,
|
|
309
|
+
help="0-based atom indices to be deleted from frozen_fn.",
|
|
310
|
+
)
|
|
311
|
+
parser.add_argument(
|
|
312
|
+
"--lig-del",
|
|
313
|
+
nargs="+",
|
|
314
|
+
type=int,
|
|
315
|
+
help="0-based atom indices to be deleted from lig_fn.",
|
|
316
|
+
)
|
|
317
|
+
parser.add_argument(
|
|
318
|
+
"--make-bonds",
|
|
319
|
+
nargs="+",
|
|
320
|
+
type=int,
|
|
321
|
+
help="0-based indices of atom pairs (frozen, lig), forming bonds "
|
|
322
|
+
"in the merged geometry. lig indices should start at 0.",
|
|
323
|
+
)
|
|
324
|
+
parser.add_argument("--res", default="LIG")
|
|
325
|
+
parser.add_argument("--resno", type=int)
|
|
326
|
+
|
|
327
|
+
return parser.parse_args(args)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def run_merge():
|
|
331
|
+
args = parse_args(sys.argv[1:])
|
|
332
|
+
|
|
333
|
+
frozen_fn = args.frozen_fn
|
|
334
|
+
lig_fn = args.lig_fn
|
|
335
|
+
protein_pdb = frozen_fn
|
|
336
|
+
lig_pdb = lig_fn
|
|
337
|
+
|
|
338
|
+
frozen_geom = geom_loader(protein_pdb)
|
|
339
|
+
lig_geom = geom_loader(lig_pdb)
|
|
340
|
+
|
|
341
|
+
prot_del = args.frozen_del
|
|
342
|
+
lig_del = args.lig_del
|
|
343
|
+
make_bonds = args.make_bonds
|
|
344
|
+
make_bonds = np.array(make_bonds, dtype=int).reshape(-1, 2)
|
|
345
|
+
aligned, subset, rest = merge_with_frozen_geom(
|
|
346
|
+
frozen_geom, lig_geom, make_bonds, prot_del, lig_del
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
trj_fn = "merged_trj.xyz"
|
|
350
|
+
trj = "\n".join([geom.as_xyz() for geom in (aligned, rest)])
|
|
351
|
+
with open(trj_fn, "w") as handle:
|
|
352
|
+
handle.write(trj)
|
|
353
|
+
print(f"Dumped geometries to '{trj_fn}'.")
|
|
354
|
+
|
|
355
|
+
res = args.res
|
|
356
|
+
resno = args.resno
|
|
357
|
+
if (res is not None) and (resno is not None):
|
|
358
|
+
crd_str = geom_to_crd_str(
|
|
359
|
+
rest, res=res, resno=resno, ref_atoms=lig_geom.atoms, del_atoms=lig_del
|
|
360
|
+
)
|
|
361
|
+
crd_fn = "lig_aligned.crd"
|
|
362
|
+
with open(crd_fn, "w") as handle:
|
|
363
|
+
handle.write(crd_str)
|
|
364
|
+
print(f"Dumped ligand coordinates to to '{crd_fn}'.")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
if __name__ == "__main__":
|
|
368
|
+
run_merge()
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import copy
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Optional
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from pysisyphus.constants import BOHR2ANG
|
|
10
|
+
from pysisyphus.drivers.merge import merge_with_frozen_geom
|
|
11
|
+
from pysisyphus.helpers import geom_loader
|
|
12
|
+
from pysisyphus.io.mol2 import parse_mol2, dict_to_mol2_string
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def delete_atoms_bonds_inplace(
|
|
16
|
+
as_dict: dict, inds: list[int], atom_offset: int = 0, bond_offset: int = 0
|
|
17
|
+
) -> dict[int, int]:
|
|
18
|
+
"""Update atom_ids and bonds in mol2-dict inplace.
|
|
19
|
+
|
|
20
|
+
Parameter
|
|
21
|
+
---------
|
|
22
|
+
as_dict
|
|
23
|
+
mol2-dict as returned from pysisyphus.io.mol2.parse_mol2.
|
|
24
|
+
inds
|
|
25
|
+
List of positive integer atom_ids to be deleted.
|
|
26
|
+
atom_offset
|
|
27
|
+
Integer >= 0. atom_ids will be shifted by this number.
|
|
28
|
+
bond_offset
|
|
29
|
+
Integer >= 0. bond_ids will be shifted by this number.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
atom_map
|
|
34
|
+
Dictionary w/ origin atom_ids as keys and updated atom_ids as values.
|
|
35
|
+
"""
|
|
36
|
+
to_del = set(inds)
|
|
37
|
+
|
|
38
|
+
# Atoms
|
|
39
|
+
axs = as_dict["atoms_xyzs"]
|
|
40
|
+
axs_mod = list()
|
|
41
|
+
atoms_del = 0
|
|
42
|
+
atom_map = {}
|
|
43
|
+
for ax in axs:
|
|
44
|
+
if ax["atom_id"] in to_del:
|
|
45
|
+
warnings.warn("Charges were not updated after deleting atoms!")
|
|
46
|
+
print(f"Deleted atom {ax['atom_name']} with atom_id {ax['atom_id']}")
|
|
47
|
+
atoms_del += 1
|
|
48
|
+
continue
|
|
49
|
+
atom_id = ax["atom_id"]
|
|
50
|
+
mod_atom_id = atom_id - atoms_del + atom_offset
|
|
51
|
+
# Store updated atom_id in dict with original atom_id as key,
|
|
52
|
+
# so we can later acces them to update the bond atom_ids.
|
|
53
|
+
atom_map[atom_id] = mod_atom_id
|
|
54
|
+
ax["atom_id"] = mod_atom_id
|
|
55
|
+
axs_mod.append(ax)
|
|
56
|
+
as_dict["atoms_xyzs"] = axs_mod
|
|
57
|
+
|
|
58
|
+
# Bonds
|
|
59
|
+
bonds_mod = list()
|
|
60
|
+
bonds_del = 0
|
|
61
|
+
for bond in as_dict["bond"]:
|
|
62
|
+
bond_inds = set((bond["origin_atom_id"], bond["target_atom_id"]))
|
|
63
|
+
if bond_inds & to_del:
|
|
64
|
+
print(f"Deleted bond with bond_id {bond['bond_id']}")
|
|
65
|
+
bonds_del += 1
|
|
66
|
+
continue
|
|
67
|
+
bond["bond_id"] -= bonds_del
|
|
68
|
+
bond["bond_id"] += bond_offset
|
|
69
|
+
bond["origin_atom_id"] = atom_map[bond["origin_atom_id"]]
|
|
70
|
+
bond["target_atom_id"] = atom_map[bond["target_atom_id"]]
|
|
71
|
+
bonds_mod.append(bond)
|
|
72
|
+
as_dict["bond"] = bonds_mod
|
|
73
|
+
return atom_map
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def update_coords_inplace(as_dict: dict, coords3d: np.ndarray):
|
|
77
|
+
"""Update xyz coordinates in mol2 dict inplace."""
|
|
78
|
+
axs = as_dict["atoms_xyzs"]
|
|
79
|
+
assert len(axs) == len(coords3d)
|
|
80
|
+
|
|
81
|
+
for ax, nc3d in zip(axs, coords3d):
|
|
82
|
+
ax["xyz"] = (nc3d * BOHR2ANG).tolist()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def merge_mol2_dicts(as_dict1: dict, as_dict2: dict) -> dict:
|
|
86
|
+
"""Merge two mol2 dict. Atoms, bonds and associated numbers will be updated.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
as_dict1
|
|
91
|
+
Mol2 dict.
|
|
92
|
+
as_dict2
|
|
93
|
+
Mo2 dict.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
merged
|
|
98
|
+
Merged mol2 dict. Most entries will be copied from as_dict1 but atoms,
|
|
99
|
+
bonds and associated counts will be updated with entries from as_dict2.
|
|
100
|
+
"""
|
|
101
|
+
# Atoms
|
|
102
|
+
atoms_xyzs = as_dict1["atoms_xyzs"] + as_dict2["atoms_xyzs"]
|
|
103
|
+
|
|
104
|
+
# Bonds
|
|
105
|
+
bond = as_dict1["bond"] + as_dict2["bond"]
|
|
106
|
+
|
|
107
|
+
merged = copy.deepcopy(as_dict1)
|
|
108
|
+
merged.update(
|
|
109
|
+
{
|
|
110
|
+
"atoms_xyzs": atoms_xyzs,
|
|
111
|
+
"num_atoms": len(atoms_xyzs),
|
|
112
|
+
"bond": bond,
|
|
113
|
+
"num_bonds": len(bond),
|
|
114
|
+
"mol_name": "merged",
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
return merged
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_substs(as_dict: dict) -> tuple[set[tuple[int, str]], int]:
|
|
121
|
+
keys = set()
|
|
122
|
+
for ax in as_dict["atoms_xyzs"]:
|
|
123
|
+
key = (ax["subst_id"], ax["subst_name"])
|
|
124
|
+
keys.add(key)
|
|
125
|
+
subst_ids, _ = zip(*keys)
|
|
126
|
+
max_subst_id = max(subst_ids)
|
|
127
|
+
return keys, max_subst_id
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def update_subst_ids_inplace(as_dict: dict, offset: int):
|
|
131
|
+
for ax in as_dict["atoms_xyzs"]:
|
|
132
|
+
ax["subst_id"] += offset
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def merge_mol2(
|
|
136
|
+
fn1: str,
|
|
137
|
+
fn2: str,
|
|
138
|
+
del1: Optional[list[int]] = None,
|
|
139
|
+
del2: Optional[list[int]] = None,
|
|
140
|
+
bonds: Optional[list[int]] = None,
|
|
141
|
+
new_coords: Optional[np.ndarray] = None,
|
|
142
|
+
) -> dict:
|
|
143
|
+
if del1 is None:
|
|
144
|
+
del1 = list()
|
|
145
|
+
if del2 is None:
|
|
146
|
+
del2 = list()
|
|
147
|
+
if bonds is None:
|
|
148
|
+
bonds = list()
|
|
149
|
+
bonds = np.array(bonds, dtype=int).reshape(-1, 2)
|
|
150
|
+
|
|
151
|
+
dict1 = parse_mol2(fn1).as_dict()
|
|
152
|
+
# dict1 = m1.as_dict()
|
|
153
|
+
dict2 = parse_mol2(fn2).as_dict()
|
|
154
|
+
# dict2 = m2.as_dict()
|
|
155
|
+
|
|
156
|
+
# Delete atoms and assoicated bonds
|
|
157
|
+
print(f"Deleting atoms/bonds from '{fn1}'")
|
|
158
|
+
atom_map1 = delete_atoms_bonds_inplace(dict1, del1)
|
|
159
|
+
print()
|
|
160
|
+
natoms1 = len(dict1["atoms_xyzs"])
|
|
161
|
+
nbonds1 = len(dict1["bond"])
|
|
162
|
+
|
|
163
|
+
print(f"Deleting atoms/bonds from '{fn2}'")
|
|
164
|
+
atom_map2 = delete_atoms_bonds_inplace(
|
|
165
|
+
dict2, del2, atom_offset=natoms1, bond_offset=nbonds1
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Determine highest subst_id in dict1, to shift subst_ids in dict2
|
|
169
|
+
_, max_subst_id1 = get_substs(dict1)
|
|
170
|
+
print(f"Highest subst_id in '{fn1}' is {max_subst_id1}.")
|
|
171
|
+
update_subst_ids_inplace(dict2, max_subst_id1)
|
|
172
|
+
|
|
173
|
+
if new_coords is not None:
|
|
174
|
+
coords1 = new_coords[:natoms1]
|
|
175
|
+
coords2 = new_coords[natoms1:]
|
|
176
|
+
assert len(coords1) + len(coords2) == len(new_coords)
|
|
177
|
+
update_coords_inplace(dict1, coords1)
|
|
178
|
+
update_coords_inplace(dict2, coords2)
|
|
179
|
+
print("Updated coordinates")
|
|
180
|
+
|
|
181
|
+
# Add new bond(s) to dict2
|
|
182
|
+
bond2 = dict2["bond"]
|
|
183
|
+
nbonds2 = len(bond2)
|
|
184
|
+
for from_, to_ in bonds:
|
|
185
|
+
# Use updated atom_ids for the bond target/origin
|
|
186
|
+
origin = atom_map1[from_]
|
|
187
|
+
target = atom_map2[to_]
|
|
188
|
+
nbonds2 += 1
|
|
189
|
+
bond_type = 1
|
|
190
|
+
new_bond = {
|
|
191
|
+
"bond_id": nbonds1 + nbonds2,
|
|
192
|
+
"bond_type": bond_type,
|
|
193
|
+
"origin_atom_id": origin,
|
|
194
|
+
"target_atom_id": target,
|
|
195
|
+
}
|
|
196
|
+
print(f"Added new bond '{new_bond}'")
|
|
197
|
+
warnings.warn(f"Set bond_type to '{bond_type}'.")
|
|
198
|
+
bond2.append(new_bond)
|
|
199
|
+
|
|
200
|
+
merged = merge_mol2_dicts(dict1, dict2)
|
|
201
|
+
return merged
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def merge_mol2_geoms(
|
|
205
|
+
fn1: str,
|
|
206
|
+
fn2: str,
|
|
207
|
+
bonds: list[list[int]],
|
|
208
|
+
del1: Optional[list[int]] = None,
|
|
209
|
+
del2: Optional[list[int]] = None,
|
|
210
|
+
out_fn: Optional[str] = None,
|
|
211
|
+
) -> str:
|
|
212
|
+
"""Merge geometries in mol2 files w/ atom deletion and bond formation.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
fn1
|
|
217
|
+
Filename of first mol2 file.
|
|
218
|
+
fn2
|
|
219
|
+
Filename of second mol2 file.
|
|
220
|
+
bonds
|
|
221
|
+
List of list of two integers. Each integer pair comprises an atom
|
|
222
|
+
of fn1 and an atom of fn2. Original indices/atom_ids must be used,
|
|
223
|
+
regardless of any atom deletion.
|
|
224
|
+
del1
|
|
225
|
+
Optional list of integers of atoms to be deleted. As for 'bonds',
|
|
226
|
+
atom_ids as appearing in the mol2 file must used.
|
|
227
|
+
del2
|
|
228
|
+
Same as 'del1' but deletes atoms in 'fn2'.
|
|
229
|
+
out_fn
|
|
230
|
+
Optional str. If given the resulting mol2-file will be written to
|
|
231
|
+
this filename.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
rendered
|
|
236
|
+
Merged mol2 w/ deleted atoms.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
if del1 is None:
|
|
240
|
+
del1 = []
|
|
241
|
+
if del2 is None:
|
|
242
|
+
del2 = []
|
|
243
|
+
|
|
244
|
+
del1 = list(sorted(del1))
|
|
245
|
+
del2 = list(sorted(del2))
|
|
246
|
+
# Make 0-based indices from 1-based mol2 indices
|
|
247
|
+
del10 = [d - 1 for d in del1]
|
|
248
|
+
del20 = [d - 1 for d in del2]
|
|
249
|
+
bonds0 = np.array(bonds, dtype=int).reshape(-1, 2) - 1
|
|
250
|
+
|
|
251
|
+
geom1 = geom_loader(fn1)
|
|
252
|
+
geom2 = geom_loader(fn2)
|
|
253
|
+
# Get new coordinates by merging both fragments.
|
|
254
|
+
# Function takes 0-based indices
|
|
255
|
+
new_geom, *_ = merge_with_frozen_geom(geom1, geom2, bonds0, del10, del20)
|
|
256
|
+
new_coords3d = new_geom.coords3d
|
|
257
|
+
|
|
258
|
+
# Merge mol2 files w/ updated coordinates
|
|
259
|
+
# Takes 1-based indices, bad?!
|
|
260
|
+
merged = merge_mol2(fn1, fn2, del1, del2, bonds, new_coords3d)
|
|
261
|
+
|
|
262
|
+
# Render mol2 string from merged data
|
|
263
|
+
rendered = dict_to_mol2_string(merged)
|
|
264
|
+
|
|
265
|
+
# Dump merged mol2 dict to file
|
|
266
|
+
if out_fn is not None:
|
|
267
|
+
with open(out_fn, "w") as handle:
|
|
268
|
+
handle.write(rendered)
|
|
269
|
+
print(f"Dumped merged geometry to '{out_fn}'.")
|
|
270
|
+
return rendered
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def parse_args(args):
|
|
274
|
+
parser = argparse.ArgumentParser(
|
|
275
|
+
description="Merge two mol2 files w/ atom deletion & bond formation."
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
parser.add_argument("fn1", help="Name of frozen mol2 file.")
|
|
279
|
+
parser.add_argument("fn2", help="Name of mobile mol2 file.")
|
|
280
|
+
parser.add_argument(
|
|
281
|
+
"--bonds",
|
|
282
|
+
nargs="+",
|
|
283
|
+
type=int,
|
|
284
|
+
help="1-based atom id pairs (id1, id2), between which bonds are formed."
|
|
285
|
+
"The original atom ids must be used, regardless of any atom deletions.",
|
|
286
|
+
)
|
|
287
|
+
parser.add_argument(
|
|
288
|
+
"--del1",
|
|
289
|
+
nargs="+",
|
|
290
|
+
type=int,
|
|
291
|
+
help="1-based atom ids to be deleted from fn1.",
|
|
292
|
+
)
|
|
293
|
+
parser.add_argument(
|
|
294
|
+
"--del2",
|
|
295
|
+
nargs="+",
|
|
296
|
+
type=int,
|
|
297
|
+
help="1-based atom ids to be deleted from fn2.",
|
|
298
|
+
)
|
|
299
|
+
parser.add_argument(
|
|
300
|
+
"--out", default="merged.mol2", help="Name of the final mol2 file."
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return parser.parse_args(args)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def run() -> str:
|
|
307
|
+
"""CLI frontend for merge_mol2_geoms()."""
|
|
308
|
+
|
|
309
|
+
args = parse_args(sys.argv[1:])
|
|
310
|
+
|
|
311
|
+
fn1 = args.fn1
|
|
312
|
+
fn2 = args.fn2
|
|
313
|
+
bonds = args.bonds
|
|
314
|
+
del1 = args.del1
|
|
315
|
+
del2 = args.del2
|
|
316
|
+
out = args.out
|
|
317
|
+
|
|
318
|
+
return merge_mol2_geoms(fn1, fn2, bonds, del1, del2, out)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
run()
|