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,962 @@
|
|
|
1
|
+
# [1] http://link.aip.org/link/doi/10.1063/1.1515483
|
|
2
|
+
# The efficient optimization of molecular geometries using redundant internal
|
|
3
|
+
# coordinates
|
|
4
|
+
# Bakken, Helgaker, 2002
|
|
5
|
+
|
|
6
|
+
from collections import namedtuple
|
|
7
|
+
import itertools as it
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from scipy.spatial.distance import pdist, squareform
|
|
12
|
+
from sklearn.cluster import KMeans
|
|
13
|
+
|
|
14
|
+
from pysisyphus.constants import BOHR2ANG
|
|
15
|
+
from pysisyphus.config import BEND_MIN_DEG, DIHED_MAX_DEG
|
|
16
|
+
from pysisyphus.helpers_pure import log, sort_by_central, merge_sets
|
|
17
|
+
from pysisyphus.elem_data import VDW_RADII, COVALENT_RADII as CR
|
|
18
|
+
from pysisyphus.intcoords import Stretch, Bend, LinearBend, Torsion
|
|
19
|
+
from pysisyphus.intcoords.setup_fast import find_bonds as find_bonds_fast
|
|
20
|
+
from pysisyphus.intcoords.PrimTypes import PrimTypes, PrimMap, Rotations
|
|
21
|
+
from pysisyphus.intcoords.valid import bend_valid, dihedral_valid
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
BOND_FACTOR = 1.3
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_pair_covalent_radii(atoms):
|
|
28
|
+
atoms = [a.lower() for a in atoms]
|
|
29
|
+
cov_radii = np.array([CR[a.lower()] for a in atoms])
|
|
30
|
+
pair_cov_radii = np.array([r1 + r2 for r1, r2 in it.combinations(cov_radii, 2)])
|
|
31
|
+
return pair_cov_radii
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_bond_mat(geom, bond_factor=BOND_FACTOR):
|
|
35
|
+
cdm = pdist(geom.coords3d)
|
|
36
|
+
pair_cov_radii = get_pair_covalent_radii(geom.atoms)
|
|
37
|
+
bond_mat = squareform(cdm <= (pair_cov_radii * bond_factor))
|
|
38
|
+
return bond_mat
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_bond_sets(
|
|
42
|
+
atoms, coords3d, bond_factor=BOND_FACTOR, return_cdm=False, return_cbm=False
|
|
43
|
+
):
|
|
44
|
+
"""I'm sorry, but this function does not return sets, but an int ndarray."""
|
|
45
|
+
cdm = pdist(coords3d)
|
|
46
|
+
# Generate indices corresponding to the atom pairs in the
|
|
47
|
+
# condensed distance matrix cdm.
|
|
48
|
+
atom_inds = list(it.combinations(range(len(coords3d)), 2))
|
|
49
|
+
atom_inds = np.array(atom_inds, dtype=int)
|
|
50
|
+
scaled_cr_sums = bond_factor * get_pair_covalent_radii(atoms)
|
|
51
|
+
# condensed bond matrix
|
|
52
|
+
cbm = cdm <= scaled_cr_sums
|
|
53
|
+
bond_inds = atom_inds[cbm]
|
|
54
|
+
if not return_cbm and not return_cdm:
|
|
55
|
+
return bond_inds
|
|
56
|
+
add_returns = tuple(
|
|
57
|
+
[mat for flag, mat in ((return_cdm, cdm), (return_cbm, cbm)) if flag]
|
|
58
|
+
)
|
|
59
|
+
return (bond_inds,) + add_returns
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_fragments(atoms, coords, bond_inds=None, bond_factor=BOND_FACTOR):
|
|
63
|
+
"""This misses unconnected single atoms!"""
|
|
64
|
+
coords3d = coords.reshape(-1, 3)
|
|
65
|
+
if bond_inds is None:
|
|
66
|
+
# Bond indices without interfragment bonds and/or hydrogen bonds
|
|
67
|
+
bond_inds = get_bond_sets(atoms, coords3d, bond_factor=bond_factor)
|
|
68
|
+
|
|
69
|
+
bond_ind_sets = [frozenset(bi) for bi in bond_inds]
|
|
70
|
+
fragments = merge_sets(bond_ind_sets)
|
|
71
|
+
|
|
72
|
+
return fragments
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def connect_fragments(
|
|
76
|
+
cdm, fragments, max_aux=3.78, aux_factor=BOND_FACTOR, logger=None
|
|
77
|
+
):
|
|
78
|
+
"""Determine the smallest interfragment bond for a list
|
|
79
|
+
of fragments and a condensed distance matrix. For more than a few fragments
|
|
80
|
+
this function performs poorly, as each fragment is connected to all reamaining
|
|
81
|
+
fragments, leading to an explosion of bonds, bends and dihedrals."""
|
|
82
|
+
if len(fragments) > 1:
|
|
83
|
+
log(
|
|
84
|
+
logger,
|
|
85
|
+
f"Detected {len(fragments)} fragments. Generating interfragment bonds.",
|
|
86
|
+
)
|
|
87
|
+
dist_mat = squareform(cdm)
|
|
88
|
+
interfrag_inds = list()
|
|
89
|
+
aux_interfrag_inds = list()
|
|
90
|
+
for frag1, frag2 in it.combinations(fragments, 2):
|
|
91
|
+
log(logger, f"\tConnecting {len(frag1)} atom and {len(frag2)} atom fragment")
|
|
92
|
+
inds = [(i1, i2) for i1, i2 in it.product(frag1, frag2)]
|
|
93
|
+
distances = np.array([dist_mat[ind] for ind in inds])
|
|
94
|
+
|
|
95
|
+
# Determine minimum distance bond
|
|
96
|
+
min_ind = distances.argmin()
|
|
97
|
+
min_dist = distances[min_ind]
|
|
98
|
+
interfrag_bond = tuple(inds[min_ind])
|
|
99
|
+
interfrag_inds.append(interfrag_bond)
|
|
100
|
+
log(logger, f"\tMinimum distance bond: {interfrag_bond}, {min_dist:.4f} au")
|
|
101
|
+
|
|
102
|
+
# Determine auxiliary interfragment bonds that are either below max_aux
|
|
103
|
+
# (default 2 Å, ≈ 3.78 au), or less than aux_factor (default 1.3) times the
|
|
104
|
+
# minimum interfragment distance.
|
|
105
|
+
below_max_aux = [
|
|
106
|
+
ind for ind in inds if (dist_mat[ind] < max_aux) and (ind != interfrag_bond)
|
|
107
|
+
]
|
|
108
|
+
if below_max_aux:
|
|
109
|
+
log(
|
|
110
|
+
logger,
|
|
111
|
+
f"\tAux. interfrag bonds below {max_aux*BOHR2ANG:.2f} Å:\n"
|
|
112
|
+
+ "\n".join(
|
|
113
|
+
[f"\t\t{ind}: {dist_mat[ind]:.4f} au" for ind in below_max_aux]
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
scaled_min_dist = aux_factor * min_dist
|
|
117
|
+
above_min_dist = [
|
|
118
|
+
ind
|
|
119
|
+
for ind in inds
|
|
120
|
+
if (dist_mat[ind] < scaled_min_dist)
|
|
121
|
+
and (ind != interfrag_bond)
|
|
122
|
+
and (ind not in below_max_aux)
|
|
123
|
+
]
|
|
124
|
+
if above_min_dist:
|
|
125
|
+
log(
|
|
126
|
+
logger,
|
|
127
|
+
f"\tAux. interfrag bonds below {aux_factor:.2f} * min_dist:\n"
|
|
128
|
+
+ "\n".join(
|
|
129
|
+
[f"\t\t{ind}: {dist_mat[ind]:.4f} au" for ind in above_min_dist]
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
aux_interfrag_inds.extend(below_max_aux)
|
|
133
|
+
aux_interfrag_inds.extend(above_min_dist)
|
|
134
|
+
# Or as Philipp proposed: two loops over the fragments and only
|
|
135
|
+
# generate interfragment distances. So we get a full matrix with
|
|
136
|
+
# the original indices but only the required distances.
|
|
137
|
+
return interfrag_inds, aux_interfrag_inds
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def connect_fragments_kmeans(
|
|
141
|
+
cdm,
|
|
142
|
+
fragments,
|
|
143
|
+
atoms,
|
|
144
|
+
aux_below_thresh=3.7807, # 2 Å
|
|
145
|
+
aux_add_dist=2.8356, # 1.5 Å
|
|
146
|
+
aux_keep=5,
|
|
147
|
+
aux_no_hh=True,
|
|
148
|
+
min_dist_thresh=5.0,
|
|
149
|
+
min_scale=1.2,
|
|
150
|
+
logger=None,
|
|
151
|
+
):
|
|
152
|
+
"""Generate (auxiliary) interfragment bonds.
|
|
153
|
+
|
|
154
|
+
In the a first step, minimum distance interfragment bonds (IFBs) are determined between
|
|
155
|
+
all possible fragment pairs. Similarly, possible auxiliary IFBs are determined.
|
|
156
|
+
Candidates for auxiliary IFBs are:
|
|
157
|
+
IFB <= aux_below_thresh, default 2 Å
|
|
158
|
+
IFB <= (minimum distance IFB + aux_add_dist), default 1.5 Å
|
|
159
|
+
By default, only the first aux_keep (default = 5) auxiliary IFBs are kept.
|
|
160
|
+
|
|
161
|
+
Connecting all fragments can lead to bonds between very distant atoms. If more than
|
|
162
|
+
two fragments are present we cluster the minimum distance IFB distances using KMeans,
|
|
163
|
+
to determine a reasonable length for valid IFBs. We start out with two clusters and
|
|
164
|
+
increase the number of cluster until the center of one cluster is around the scaled
|
|
165
|
+
global minimum distance between the fragments. The center of this cluster is then used
|
|
166
|
+
as a cutoff vor valid IFBs.
|
|
167
|
+
|
|
168
|
+
After pruning all possible IFBs we can determine the fragment pairs, that are actually
|
|
169
|
+
connected. This information is then used to also prune possible interfragment bonds.
|
|
170
|
+
Only auxiliary IFBs between fragments that are actually connected via IFBs are kept.
|
|
171
|
+
"""
|
|
172
|
+
# atoms = [atom.lower() for atom in atoms]
|
|
173
|
+
if len(fragments) > 1:
|
|
174
|
+
log(
|
|
175
|
+
logger,
|
|
176
|
+
f"Detected {len(fragments)} fragments. Generating interfragment bonds.",
|
|
177
|
+
)
|
|
178
|
+
dist_mat = squareform(cdm)
|
|
179
|
+
|
|
180
|
+
frag_pairs = list()
|
|
181
|
+
interfrag_inds = list()
|
|
182
|
+
interfrag_dists = list()
|
|
183
|
+
aux_dict = dict()
|
|
184
|
+
for i, j in it.combinations(range(len(fragments)), 2):
|
|
185
|
+
frag1 = fragments[i]
|
|
186
|
+
frag2 = fragments[j]
|
|
187
|
+
log(logger, f"\tConnecting {len(frag1)}-atom and {len(frag2)}-atom fragments.")
|
|
188
|
+
# Pairs of possible interfragment bonds
|
|
189
|
+
inds = np.array([(i1, i2) for i1, i2 in it.product(frag1, frag2)], dtype=int)
|
|
190
|
+
distances = np.array([dist_mat[k, l] for k, l in inds])
|
|
191
|
+
|
|
192
|
+
frag_pairs.append((i, j))
|
|
193
|
+
# Determine minimum distance bond
|
|
194
|
+
min_ind = distances.argmin()
|
|
195
|
+
min_dist = distances[min_ind]
|
|
196
|
+
interfrag_inds.append(inds[min_ind])
|
|
197
|
+
interfrag_dists.append(min_dist)
|
|
198
|
+
"""
|
|
199
|
+
But also consider other bonds with reasonable lengths as auxiliary interfrag bonds.
|
|
200
|
+
Contrary to [1] we don't use a scaled minimum interfragment distances (MID),
|
|
201
|
+
but just add a fixed value to the MID. Scaling a possibly big MID would
|
|
202
|
+
probably include too many coordinates.
|
|
203
|
+
"""
|
|
204
|
+
# if aux_no_hh:
|
|
205
|
+
# is_hh = np.array(
|
|
206
|
+
# [(atoms[k] == "h") and (atoms[k] == atoms[l]) for k, l in inds]
|
|
207
|
+
# )
|
|
208
|
+
# else:
|
|
209
|
+
# is_hh = np.zeros(len(inds))
|
|
210
|
+
|
|
211
|
+
aux_mask = np.logical_or(
|
|
212
|
+
distances <= aux_below_thresh,
|
|
213
|
+
distances <= (min_dist + aux_add_dist),
|
|
214
|
+
)
|
|
215
|
+
# aux_mask = distances <= 1.1 * min_dist
|
|
216
|
+
# aux_mask = np.logical_and(aux_mask, ~is_hh)
|
|
217
|
+
# Don't include interfragment bond
|
|
218
|
+
aux_mask[min_ind] = False
|
|
219
|
+
aux_inds = inds[aux_mask]
|
|
220
|
+
|
|
221
|
+
if aux_keep >= 0:
|
|
222
|
+
aux_dists = distances[aux_mask]
|
|
223
|
+
aux_keep_inds = aux_dists.argsort()[:aux_keep]
|
|
224
|
+
aux_inds = aux_inds[aux_keep_inds]
|
|
225
|
+
|
|
226
|
+
aux_dict[(i, j)] = aux_inds
|
|
227
|
+
frag_pairs = np.array(frag_pairs, dtype=int)
|
|
228
|
+
|
|
229
|
+
"""
|
|
230
|
+
The code below tries to determine a reasonable distance, to define
|
|
231
|
+
interfragment bonds. We don't want to use all interfragment bonds defined
|
|
232
|
+
above, as this would connect all fragments to each other, resulting in an
|
|
233
|
+
explosion of the number of internal coordinates.
|
|
234
|
+
|
|
235
|
+
Here we try to cluster the interfragment distances, until one cluster center
|
|
236
|
+
comes close to the scaled minimum interfragment distance. We then use this
|
|
237
|
+
distance to filter all interfragment bonds.
|
|
238
|
+
"""
|
|
239
|
+
if len(fragments) > 2:
|
|
240
|
+
dists = np.reshape(interfrag_dists, (-1, 1))
|
|
241
|
+
min_dist = dists.min()
|
|
242
|
+
|
|
243
|
+
for n_clusters in range(2, 10):
|
|
244
|
+
kmeans = KMeans(n_clusters=n_clusters)
|
|
245
|
+
_ = kmeans.fit_predict(dists)
|
|
246
|
+
min_center = kmeans.cluster_centers_.min()
|
|
247
|
+
|
|
248
|
+
if min_center <= (min_scale * min_dist):
|
|
249
|
+
break
|
|
250
|
+
else:
|
|
251
|
+
raise Exception("Not handled!")
|
|
252
|
+
|
|
253
|
+
interfrag_inds = np.array(interfrag_inds)
|
|
254
|
+
mask = dists <= min_scale * min_center
|
|
255
|
+
# We also use interfragment bonds that still have a reasonable length.
|
|
256
|
+
if (min_dist_thresh is not None) and (min_dist_thresh > 0.0):
|
|
257
|
+
mask |= dists <= min_dist_thresh
|
|
258
|
+
mask = mask.flatten()
|
|
259
|
+
interfrag_inds = interfrag_inds[mask]
|
|
260
|
+
conn_frags_mask = mask
|
|
261
|
+
else:
|
|
262
|
+
conn_frags_mask = np.ones(len(frag_pairs), dtype=bool)
|
|
263
|
+
|
|
264
|
+
# Only keep auxiliary interfragment bonds between actually connected fragments
|
|
265
|
+
actually_connected_frags = frag_pairs[conn_frags_mask]
|
|
266
|
+
aux_interfrag_inds = list(
|
|
267
|
+
it.chain(*[aux_dict[(i, j)] for i, j in actually_connected_frags])
|
|
268
|
+
)
|
|
269
|
+
aux_interfrag_inds = np.array(aux_interfrag_inds, dtype=int)
|
|
270
|
+
return interfrag_inds, aux_interfrag_inds
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def connect_fragments_ahlrichs(
|
|
274
|
+
cdm,
|
|
275
|
+
fragments,
|
|
276
|
+
atoms,
|
|
277
|
+
min_dist_scale=1.1,
|
|
278
|
+
scale=1.2,
|
|
279
|
+
avoid_h=False,
|
|
280
|
+
logger=None,
|
|
281
|
+
):
|
|
282
|
+
atoms = [atom.lower() for atom in atoms]
|
|
283
|
+
if len(fragments) > 1:
|
|
284
|
+
log(
|
|
285
|
+
logger,
|
|
286
|
+
f"Detected {len(fragments)} fragments. Generating interfragment bonds.",
|
|
287
|
+
)
|
|
288
|
+
dist_mat = squareform(cdm)
|
|
289
|
+
|
|
290
|
+
frag_pairs = list()
|
|
291
|
+
interfrag_inds = list()
|
|
292
|
+
max_dist = 3.0 / BOHR2ANG
|
|
293
|
+
all_fragment_inds = [i for i, _ in enumerate(fragments)]
|
|
294
|
+
# Leave out the first fragment, so we can also handle only 1 fragment here.
|
|
295
|
+
unconnected_fragment_inds = all_fragment_inds.copy()[1:]
|
|
296
|
+
h_inds = set([i for i, atom in enumerate(atoms) if atom.lower() == "h"])
|
|
297
|
+
while True:
|
|
298
|
+
for i, j in it.product(all_fragment_inds, unconnected_fragment_inds):
|
|
299
|
+
# Don't try to connect a fragment to itself and don't try to connect
|
|
300
|
+
# fragments two times, e.g., (0 and 2) as well as (2 and 0).
|
|
301
|
+
if i >= j:
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
frag1 = fragments[i]
|
|
305
|
+
frag2 = fragments[j]
|
|
306
|
+
log(
|
|
307
|
+
logger,
|
|
308
|
+
f"\tConnecting {len(frag1)}-atom and {len(frag2)}-atom fragments.",
|
|
309
|
+
)
|
|
310
|
+
# Pairs of possible interfragment bonds
|
|
311
|
+
inds = np.array(
|
|
312
|
+
[(i1, i2) for i1, i2 in it.product(frag1, frag2)], dtype=int
|
|
313
|
+
)
|
|
314
|
+
distances = np.array([dist_mat[k, l] for k, l in inds])
|
|
315
|
+
|
|
316
|
+
frag_pairs.append((i, j))
|
|
317
|
+
# Determine minimum distance bond
|
|
318
|
+
min_ind = distances.argmin()
|
|
319
|
+
min_dist = distances[min_ind]
|
|
320
|
+
|
|
321
|
+
if avoid_h:
|
|
322
|
+
sort_inds = np.argsort(distances, kind="stable")
|
|
323
|
+
# Try to avoid interfragment bonds involving hydrogen.
|
|
324
|
+
for (k, dist) in zip(sort_inds, distances[sort_inds]):
|
|
325
|
+
if set(inds[k]) & h_inds:
|
|
326
|
+
continue
|
|
327
|
+
if dist >= 1.5 * min_dist:
|
|
328
|
+
break
|
|
329
|
+
min_ind = k
|
|
330
|
+
min_dist = distances[k]
|
|
331
|
+
break
|
|
332
|
+
|
|
333
|
+
if min_dist <= max_dist:
|
|
334
|
+
# Scaling of bigger 'min_dist' values by a fixed factor can lead
|
|
335
|
+
# to definition of many additional bonds. We restrict the offset
|
|
336
|
+
# to at most 1 Å.
|
|
337
|
+
offset = min((min_dist_scale - 1.0) * min_dist, 1.0 / BOHR2ANG)
|
|
338
|
+
mask = distances <= (min_dist + offset)
|
|
339
|
+
interfrag_inds.extend(inds[mask])
|
|
340
|
+
# Indicate that the just connected fragments don't have to be
|
|
341
|
+
# connected anymore. In the current cycle of the while loop additional
|
|
342
|
+
# bonds to the just connected fragments can still be defined.
|
|
343
|
+
unconnected_fragment_inds = [
|
|
344
|
+
k for k in unconnected_fragment_inds if k not in (i, j)
|
|
345
|
+
]
|
|
346
|
+
# Leave the outer while loop when all fragments are connected
|
|
347
|
+
if len(unconnected_fragment_inds) == 0:
|
|
348
|
+
break
|
|
349
|
+
# If there are still unconnected fragments present we allow longer
|
|
350
|
+
# interfragment bonds.
|
|
351
|
+
max_dist *= scale
|
|
352
|
+
|
|
353
|
+
interfrag_inds = np.array(interfrag_inds, dtype=int)
|
|
354
|
+
return interfrag_inds, list()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def get_hydrogen_bond_inds(atoms, coords3d, bond_inds, logger=None):
|
|
358
|
+
tmp_sets = [frozenset(bi) for bi in bond_inds]
|
|
359
|
+
hydrogen_inds = [i for i, a in enumerate(atoms) if a.lower() == "h"]
|
|
360
|
+
x_inds = [i for i, a in enumerate(atoms) if a.lower() in "n o f p s cl".split()]
|
|
361
|
+
hydrogen_bond_inds = list()
|
|
362
|
+
for h_ind, x_ind in it.product(hydrogen_inds, x_inds):
|
|
363
|
+
as_set = set((h_ind, x_ind))
|
|
364
|
+
if as_set not in tmp_sets:
|
|
365
|
+
continue
|
|
366
|
+
# Check if distance of H to another electronegative atom Y is
|
|
367
|
+
# greater than (1.1 * sum of their covalent radii) but smaller than
|
|
368
|
+
# (0.9 * sum of their van der Waals radii). If the
|
|
369
|
+
# angle X-H-Y is greater than 90° a hydrogen bond is asigned.
|
|
370
|
+
y_inds = set(x_inds) - set((x_ind,))
|
|
371
|
+
for y_ind in y_inds:
|
|
372
|
+
y_atom = atoms[y_ind].lower()
|
|
373
|
+
cov_rad_sum = CR["h"] + CR[y_atom]
|
|
374
|
+
distance = Stretch._calculate(coords3d, (h_ind, y_ind))
|
|
375
|
+
vdw_rad_sum = VDW_RADII["h"] + VDW_RADII[y_atom]
|
|
376
|
+
angle = Bend._calculate(coords3d, (x_ind, h_ind, y_ind))
|
|
377
|
+
if (1.1 * cov_rad_sum < distance < 0.9 * vdw_rad_sum) and (
|
|
378
|
+
angle > np.pi / 2
|
|
379
|
+
):
|
|
380
|
+
hydrogen_bond_inds.append((h_ind, y_ind))
|
|
381
|
+
log(
|
|
382
|
+
logger,
|
|
383
|
+
f"Detected hydrogen bond between atoms {h_ind} "
|
|
384
|
+
f"({atoms[h_ind]}) and {y_ind} ({atoms[y_ind]})",
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
return hydrogen_bond_inds
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def get_hydrogen_bond_inds_v2(atoms, coords3d, bond_inds, logger=None):
|
|
391
|
+
def to_set(iterable):
|
|
392
|
+
return {frozenset(_) for _ in iterable}
|
|
393
|
+
|
|
394
|
+
atoms_lower = [atom.lower() for atom in atoms]
|
|
395
|
+
# Determine Hydrogen indices
|
|
396
|
+
org_h_inds = {i for i, atom in enumerate(atoms_lower) if atom == "h"}
|
|
397
|
+
|
|
398
|
+
org_bond_sets = to_set(bond_inds)
|
|
399
|
+
# Determine Hydrogen bonding partners in original bond set
|
|
400
|
+
org_h_partners = dict()
|
|
401
|
+
for org_bond in org_bond_sets:
|
|
402
|
+
if h_set := org_bond & org_h_inds:
|
|
403
|
+
(x_ind,) = org_bond - h_set
|
|
404
|
+
(h_ind,) = h_set
|
|
405
|
+
org_h_partners.setdefault(h_ind, list()).append(x_ind)
|
|
406
|
+
|
|
407
|
+
hx = {"h", "n", "o", "f", "p", "s", "cl"}
|
|
408
|
+
# Determine indices of potential hydrogen bond acceptors and hydrogen to
|
|
409
|
+
# carry out a search for bonds with a bigger bond factor.
|
|
410
|
+
hx_inds = [i for i, atom in enumerate(atoms) if atom.lower() in hx]
|
|
411
|
+
hx_atoms = [atoms[i] for i in hx_inds]
|
|
412
|
+
hx_map = {j: i for j, i in enumerate(hx_inds)}
|
|
413
|
+
# See pysisyphus.elemdata.HBOND_FACTORS
|
|
414
|
+
hx_coords3d = coords3d[hx_inds]
|
|
415
|
+
# Search bonds with KDTree
|
|
416
|
+
h_bonds = find_bonds_fast(hx_atoms, hx_coords3d, bond_factor=2.3)
|
|
417
|
+
h_bonds = to_set(h_bonds)
|
|
418
|
+
# Map back to original indices. Until now the indices were only in the basis
|
|
419
|
+
# of the reduced number of atoms.
|
|
420
|
+
h_bonds = {frozenset((hx_map[i], hx_map[j])) for i, j in h_bonds}
|
|
421
|
+
# Drop h_bonds that were already defined as "normal" bonds
|
|
422
|
+
h_bonds = h_bonds - org_bond_sets
|
|
423
|
+
|
|
424
|
+
hydrogen_bond_inds = list()
|
|
425
|
+
# h_bonds can still contain XX and HH bonds
|
|
426
|
+
for h_bond in h_bonds:
|
|
427
|
+
hi = h_bond & org_h_inds
|
|
428
|
+
# Skip XX and HH. We are only interest in 'h_bond' with one hydrogen.
|
|
429
|
+
if len(hi) in (0, 2):
|
|
430
|
+
continue
|
|
431
|
+
(h_ind,) = hi
|
|
432
|
+
(y_partner,) = h_bond - hi
|
|
433
|
+
v = coords3d[y_partner] - coords3d[h_ind]
|
|
434
|
+
for x_partner in org_h_partners[h_ind]:
|
|
435
|
+
u = coords3d[x_partner] - coords3d[h_ind]
|
|
436
|
+
# > 90°
|
|
437
|
+
if u.dot(v) < 0.0:
|
|
438
|
+
hydrogen_bond_inds.append((h_ind, y_partner))
|
|
439
|
+
return hydrogen_bond_inds
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def get_bend_inds(coords3d, bond_inds, min_deg, max_deg, logger=None):
|
|
443
|
+
bond_sets = {frozenset(bi) for bi in bond_inds}
|
|
444
|
+
|
|
445
|
+
bend_inds = list()
|
|
446
|
+
for bond_set1, bond_set2 in it.combinations(bond_sets, 2):
|
|
447
|
+
union = bond_set1 | bond_set2
|
|
448
|
+
if len(union) == 3:
|
|
449
|
+
indices, _ = sort_by_central(bond_set1, bond_set2)
|
|
450
|
+
if not bend_valid(coords3d, indices, min_deg, max_deg):
|
|
451
|
+
log(logger, f"Bend {indices} is not valid!")
|
|
452
|
+
continue
|
|
453
|
+
bend_inds.append(indices)
|
|
454
|
+
|
|
455
|
+
return bend_inds
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def get_linear_bend_inds(coords3d, cbm, bends, min_deg=175, max_bonds=4, logger=None):
|
|
459
|
+
linear_bends = list()
|
|
460
|
+
complements = list()
|
|
461
|
+
|
|
462
|
+
if min_deg is None:
|
|
463
|
+
return linear_bends, complements
|
|
464
|
+
|
|
465
|
+
bm = squareform(cbm)
|
|
466
|
+
for bend in bends:
|
|
467
|
+
deg = np.rad2deg(Bend._calculate(coords3d, bend))
|
|
468
|
+
bonds = sum(bm[bend[1]])
|
|
469
|
+
if (deg >= min_deg) and (bonds <= max_bonds):
|
|
470
|
+
log(
|
|
471
|
+
logger,
|
|
472
|
+
f"Bend {bend}={deg:.1f}° is (close to) linear. "
|
|
473
|
+
"Creating linear bend & complement.",
|
|
474
|
+
)
|
|
475
|
+
linear_bends.append(bend)
|
|
476
|
+
complements.append(bend)
|
|
477
|
+
return linear_bends, complements
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def get_dihedral_inds(coords3d, bond_inds, bend_inds, max_deg, logger=None):
|
|
481
|
+
max_rad = np.deg2rad(max_deg)
|
|
482
|
+
bond_dict = dict()
|
|
483
|
+
for from_, to_ in bond_inds:
|
|
484
|
+
bond_dict.setdefault(from_, list()).append(to_)
|
|
485
|
+
bond_dict.setdefault(to_, list()).append(from_)
|
|
486
|
+
proper_dihedral_inds = list()
|
|
487
|
+
improper_candidates = list()
|
|
488
|
+
improper_dihedral_inds = list()
|
|
489
|
+
|
|
490
|
+
def log_dihed_skip(inds):
|
|
491
|
+
log(
|
|
492
|
+
logger,
|
|
493
|
+
f"Skipping generation of dihedral {inds} "
|
|
494
|
+
"as some of the the atoms are (close too) linear.",
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
def set_dihedral_index(dihedral_ind, proper=True):
|
|
498
|
+
dihed = tuple(dihedral_ind)
|
|
499
|
+
check_in = proper_dihedral_inds if proper else improper_dihedral_inds
|
|
500
|
+
# Check if this dihedral is already present
|
|
501
|
+
if (dihed in check_in) or (dihed[::-1] in check_in):
|
|
502
|
+
return
|
|
503
|
+
# Assure that the angles are below 175° (3.054326 rad)
|
|
504
|
+
if not dihedral_valid(coords3d, dihedral_ind, deg_thresh=max_deg):
|
|
505
|
+
log_dihed_skip(dihedral_ind)
|
|
506
|
+
return
|
|
507
|
+
if proper:
|
|
508
|
+
proper_dihedral_inds.append(dihed)
|
|
509
|
+
else:
|
|
510
|
+
improper_dihedral_inds.append(dihed)
|
|
511
|
+
|
|
512
|
+
for bond, bend in it.product(bond_inds, bend_inds):
|
|
513
|
+
central = bend[1]
|
|
514
|
+
bend_set = set(bend)
|
|
515
|
+
bond_set = set(bond)
|
|
516
|
+
# Check if the two sets share one common atom. If not continue.
|
|
517
|
+
intersect = bend_set & bond_set
|
|
518
|
+
if len(intersect) != 1:
|
|
519
|
+
continue
|
|
520
|
+
|
|
521
|
+
# TODO: check collinearity of bond and bend.
|
|
522
|
+
|
|
523
|
+
# When the common atom between bond and bend is a terminal, and not a central atom
|
|
524
|
+
# in the bend we create a proper dihedral. Improper dihedrals are only created
|
|
525
|
+
# when no proper dihedrals have been found.
|
|
526
|
+
if central not in bond_set:
|
|
527
|
+
# The new terminal atom in the dihedral is the one, that doesn' intersect.
|
|
528
|
+
terminal = tuple(bond_set - intersect)[0]
|
|
529
|
+
intersecting_atom = tuple(intersect)[0]
|
|
530
|
+
bend_terminal = tuple(bend_set - {central} - intersect)[0]
|
|
531
|
+
|
|
532
|
+
bend_rad = Bend._calculate(coords3d, bend)
|
|
533
|
+
# Bend atoms are nearly collinear. Check if we can skip the central bend atom
|
|
534
|
+
# and use an atom that is conneced to the terminal atom of the bend or bond.
|
|
535
|
+
if bend_rad >= max_rad:
|
|
536
|
+
bend_terminal_bonds = set(bond_dict[bend_terminal]) - bend_set
|
|
537
|
+
bond_terminal_bonds = set(bond_dict[terminal]) - bond_set
|
|
538
|
+
set_dihedrals = [
|
|
539
|
+
(terminal, intersecting_atom, bend_terminal, betb)
|
|
540
|
+
for betb in bend_terminal_bonds
|
|
541
|
+
] + [
|
|
542
|
+
(bend_terminal, intersecting_atom, terminal, botb)
|
|
543
|
+
for botb in bond_terminal_bonds
|
|
544
|
+
]
|
|
545
|
+
# Hardcoded for now ... look ahead to next shell of atoms
|
|
546
|
+
if not any(
|
|
547
|
+
[
|
|
548
|
+
dihedral_valid(coords3d, inds, deg_thresh=max_deg)
|
|
549
|
+
for inds in set_dihedrals
|
|
550
|
+
]
|
|
551
|
+
):
|
|
552
|
+
set_dihedrals = []
|
|
553
|
+
for betb in bend_terminal_bonds:
|
|
554
|
+
bend_terminal_bonds_v2 = (
|
|
555
|
+
set(bond_dict[betb]) - bend_set - bond_set
|
|
556
|
+
)
|
|
557
|
+
set_dihedrals = [
|
|
558
|
+
(terminal, intersecting_atom, betb, betb_v2)
|
|
559
|
+
for betb_v2 in bend_terminal_bonds_v2
|
|
560
|
+
]
|
|
561
|
+
for botb in bond_terminal_bonds:
|
|
562
|
+
bond_terminal_bonds_v2 = (
|
|
563
|
+
set(bond_dict[botb]) - bend_set - bond_set
|
|
564
|
+
)
|
|
565
|
+
set_dihedrals = [
|
|
566
|
+
(bend_terminal, intersecting_atom, botb, botb_v2)
|
|
567
|
+
for botb_v2 in bond_terminal_bonds_v2
|
|
568
|
+
]
|
|
569
|
+
elif intersecting_atom == bend[0]:
|
|
570
|
+
set_dihedrals = [[terminal] + list(bend)]
|
|
571
|
+
else:
|
|
572
|
+
set_dihedrals = [list(bend) + [terminal]]
|
|
573
|
+
[set_dihedral_index(dihed) for dihed in set_dihedrals]
|
|
574
|
+
# If the common atom is the central atom we try to form an out
|
|
575
|
+
# of plane bend / improper torsion. They may be created later on.
|
|
576
|
+
else:
|
|
577
|
+
fourth_atom = list(bond_set - intersect)
|
|
578
|
+
dihedral_ind = list(bend) + fourth_atom
|
|
579
|
+
# This way dihedrals may be generated that contain linear
|
|
580
|
+
# atoms and these would be undefinied. So we check for this.
|
|
581
|
+
if dihedral_valid(coords3d, dihedral_ind, deg_thresh=max_deg):
|
|
582
|
+
improper_candidates.append(dihedral_ind)
|
|
583
|
+
else:
|
|
584
|
+
log_dihed_skip(dihedral_ind)
|
|
585
|
+
|
|
586
|
+
# Now try to create the remaining improper dihedrals.
|
|
587
|
+
if (len(coords3d) >= 4) and (len(proper_dihedral_inds) == 0):
|
|
588
|
+
log(
|
|
589
|
+
logger,
|
|
590
|
+
"Could not define any proper dihedrals! Generating improper dihedrals!",
|
|
591
|
+
)
|
|
592
|
+
for improp in improper_candidates:
|
|
593
|
+
set_dihedral_index(improp, proper=False)
|
|
594
|
+
log(
|
|
595
|
+
logger,
|
|
596
|
+
"Permutational symmetry not considerd in generation of "
|
|
597
|
+
"improper dihedrals.",
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
return proper_dihedral_inds, improper_dihedral_inds
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def sort_by_prim_type(to_sort=None):
|
|
604
|
+
if to_sort is None:
|
|
605
|
+
to_sort = list()
|
|
606
|
+
|
|
607
|
+
by_prim_type = [[], [], []]
|
|
608
|
+
for item in to_sort:
|
|
609
|
+
len_ = len(item)
|
|
610
|
+
# len -> index
|
|
611
|
+
# 2 -> 0 (bond)
|
|
612
|
+
# 3 -> 1 (bend)
|
|
613
|
+
# 4 -> 2 (torsion)
|
|
614
|
+
by_prim_type[len_ - 2].append(tuple(item))
|
|
615
|
+
return by_prim_type
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
CoordInfo = namedtuple(
|
|
619
|
+
"CoordInfo",
|
|
620
|
+
"bonds hydrogen_bonds interfrag_bonds aux_interfrag_bonds "
|
|
621
|
+
"bends linear_bends linear_bend_complements "
|
|
622
|
+
# "dihedrals typed_prims fragments cdm cbm".split(),
|
|
623
|
+
"proper_dihedrals improper_dihedrals "
|
|
624
|
+
"translation_inds rotation_inds cartesian_inds "
|
|
625
|
+
"typed_prims fragments".split(),
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def setup_redundant(
|
|
630
|
+
atoms,
|
|
631
|
+
coords3d,
|
|
632
|
+
factor=BOND_FACTOR,
|
|
633
|
+
define_prims=None,
|
|
634
|
+
min_deg=BEND_MIN_DEG,
|
|
635
|
+
dihed_max_deg=DIHED_MAX_DEG,
|
|
636
|
+
lb_min_deg=None,
|
|
637
|
+
lb_max_bonds=4,
|
|
638
|
+
min_weight=None,
|
|
639
|
+
tric=False,
|
|
640
|
+
hybrid=False,
|
|
641
|
+
interfrag_hbonds=True,
|
|
642
|
+
hbond_angles=False,
|
|
643
|
+
freeze_atoms=None,
|
|
644
|
+
define_for=None,
|
|
645
|
+
internals_with_frozen=False,
|
|
646
|
+
rm_for_frag: Optional[set] = None,
|
|
647
|
+
logger=None,
|
|
648
|
+
):
|
|
649
|
+
if define_prims is None:
|
|
650
|
+
define_prims = list()
|
|
651
|
+
if freeze_atoms is None:
|
|
652
|
+
freeze_atoms = list()
|
|
653
|
+
if define_for is None:
|
|
654
|
+
define_for = list()
|
|
655
|
+
if rm_for_frag is None:
|
|
656
|
+
rm_for_frag = set()
|
|
657
|
+
|
|
658
|
+
log(
|
|
659
|
+
logger,
|
|
660
|
+
f"Detecting primitive internals for {len(atoms)} atoms.\n"
|
|
661
|
+
f"Excluding {len(freeze_atoms)} frozen atoms from the internal coordinate setup.",
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
# Mask array. By default all atomes are used to generate internal coordinates.
|
|
665
|
+
use_atoms = np.ones_like(atoms, dtype=bool)
|
|
666
|
+
# Only use atoms in 'define_for' to generate internal coordinates
|
|
667
|
+
if define_for:
|
|
668
|
+
use_atoms[:] = False # Disable/mask all others
|
|
669
|
+
use_atoms[define_for] = True
|
|
670
|
+
# If not explicitly enabled, don't form internal coordinates containing frozen atoms.
|
|
671
|
+
# With 'internals_with_frozen', the bonds will be filtered for bonds, containing
|
|
672
|
+
# at most one frozen atom.
|
|
673
|
+
elif not internals_with_frozen:
|
|
674
|
+
use_atoms[freeze_atoms] = False
|
|
675
|
+
freeze_atom_set = set(freeze_atoms)
|
|
676
|
+
atoms = [atom for mobile, atom in zip(use_atoms, atoms) if mobile]
|
|
677
|
+
coords3d = coords3d[use_atoms]
|
|
678
|
+
|
|
679
|
+
# Maps (different) indices of mobile atoms back to their original indices
|
|
680
|
+
freeze_map = {
|
|
681
|
+
sub_ind: org_ind for sub_ind, org_ind in enumerate(np.where(use_atoms)[0])
|
|
682
|
+
}
|
|
683
|
+
mobile_org_inds = set(freeze_map.values())
|
|
684
|
+
|
|
685
|
+
def keep_coord(prim_cls, prim_inds):
|
|
686
|
+
return (
|
|
687
|
+
True
|
|
688
|
+
if (min_weight is None)
|
|
689
|
+
else (prim_cls._weight(atoms, coords3d, prim_inds, 0.12) >= min_weight)
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
def keep_coords(prims, prim_cls):
|
|
693
|
+
return [prim for prim in prims if keep_coord(prim_cls, prim)]
|
|
694
|
+
|
|
695
|
+
# Bonds
|
|
696
|
+
bonds, cdm, cbm = get_bond_sets(
|
|
697
|
+
atoms,
|
|
698
|
+
coords3d,
|
|
699
|
+
bond_factor=factor,
|
|
700
|
+
return_cdm=True,
|
|
701
|
+
return_cbm=True,
|
|
702
|
+
)
|
|
703
|
+
if internals_with_frozen:
|
|
704
|
+
bonds = [bond for bond in bonds if len(set(bond) & freeze_atom_set) <= 1]
|
|
705
|
+
bonds = [tuple(bond) for bond in bonds]
|
|
706
|
+
bonds = keep_coords(bonds, Stretch)
|
|
707
|
+
bonds = [bond for bond in bonds if rm_for_frag.isdisjoint(set(bond))]
|
|
708
|
+
|
|
709
|
+
# Fragments
|
|
710
|
+
fragments = merge_sets(bonds) + [
|
|
711
|
+
frozenset((rmed_atom,)) for rmed_atom in rm_for_frag
|
|
712
|
+
]
|
|
713
|
+
# Check for unbonded single atoms and create fragments for them.
|
|
714
|
+
bonded_set = set(tuple(np.ravel(bonds)))
|
|
715
|
+
unbonded_set = set(range(len(atoms))) - bonded_set - freeze_atom_set
|
|
716
|
+
log(
|
|
717
|
+
logger,
|
|
718
|
+
f"Merging bonded atoms yielded {len(fragments)} fragment(s) and "
|
|
719
|
+
f"{len(unbonded_set)} atoms.",
|
|
720
|
+
)
|
|
721
|
+
fragments.extend([frozenset((atom,)) for atom in unbonded_set])
|
|
722
|
+
|
|
723
|
+
interfrag_bonds = list()
|
|
724
|
+
aux_interfrag_bonds = list()
|
|
725
|
+
translation_inds = list()
|
|
726
|
+
rotation_inds = list()
|
|
727
|
+
# With translational & rotational internal coordinates (TRIC) we don't need
|
|
728
|
+
# interfragment coordinates.
|
|
729
|
+
if tric:
|
|
730
|
+
translation_inds = [list(fragment) for fragment in fragments]
|
|
731
|
+
# Exclude rotational coordinates for atomic species (1 atom)
|
|
732
|
+
rotation_inds = [list(fragment) for fragment in fragments if len(fragment) > 1]
|
|
733
|
+
# Without TRIC we have to somehow connect all fragments.
|
|
734
|
+
else:
|
|
735
|
+
# interfrag_bonds, aux_interfrag_bonds = connect_fragments_kmeans(
|
|
736
|
+
interfrag_bonds, aux_interfrag_bonds = connect_fragments_ahlrichs(
|
|
737
|
+
cdm, fragments, atoms, logger=logger
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
# Hydrogen bonds
|
|
741
|
+
assert interfrag_hbonds, "Disabling interfrag_hbonds is not yet supported!"
|
|
742
|
+
hydrogen_bonds = get_hydrogen_bond_inds(atoms, coords3d, bonds, logger=logger)
|
|
743
|
+
hydrogen_set = [frozenset(bond) for bond in hydrogen_bonds]
|
|
744
|
+
|
|
745
|
+
def remove_h_bonds(bond_list):
|
|
746
|
+
return [bond for bond in bond_list if set(bond) not in hydrogen_set]
|
|
747
|
+
|
|
748
|
+
# Remove newly obtained hydrogen bonds from other lists
|
|
749
|
+
interfrag_bonds = remove_h_bonds(interfrag_bonds)
|
|
750
|
+
aux_interfrag_bonds = remove_h_bonds(aux_interfrag_bonds)
|
|
751
|
+
bonds = remove_h_bonds(bonds)
|
|
752
|
+
aux_bonds = list() # Not defined by default
|
|
753
|
+
# Don't use auxilary interfragment bonds for bend detection
|
|
754
|
+
bonds_for_bends = [
|
|
755
|
+
bonds,
|
|
756
|
+
]
|
|
757
|
+
# If we use regular redundant internals (not TRIC) we define interfragment
|
|
758
|
+
# bends.
|
|
759
|
+
if not tric:
|
|
760
|
+
bonds_for_bends += [interfrag_bonds]
|
|
761
|
+
if hbond_angles:
|
|
762
|
+
bonds_for_bends += [hydrogen_bonds]
|
|
763
|
+
bonds_for_bends = set([frozenset(bond) for bond in it.chain(*bonds_for_bends)])
|
|
764
|
+
|
|
765
|
+
# Bends
|
|
766
|
+
bends = get_bend_inds(
|
|
767
|
+
coords3d,
|
|
768
|
+
bonds_for_bends,
|
|
769
|
+
min_deg=min_deg,
|
|
770
|
+
# All bends will be checked, for being (close to) linear and will be removed from
|
|
771
|
+
# bend_inds, if needed. Thats why we keep 180° here.
|
|
772
|
+
max_deg=180.0,
|
|
773
|
+
logger=logger,
|
|
774
|
+
)
|
|
775
|
+
bends = keep_coords(bends, Bend)
|
|
776
|
+
|
|
777
|
+
# Linear Bends and orthogonal complements
|
|
778
|
+
linear_bends, linear_bend_complements = get_linear_bend_inds(
|
|
779
|
+
coords3d,
|
|
780
|
+
cbm,
|
|
781
|
+
bends,
|
|
782
|
+
min_deg=lb_min_deg,
|
|
783
|
+
max_bonds=lb_max_bonds,
|
|
784
|
+
logger=logger,
|
|
785
|
+
)
|
|
786
|
+
# Remove linear bends from bends
|
|
787
|
+
bends = [bend for bend in bends if bend not in linear_bends]
|
|
788
|
+
linear_bends = keep_coords(linear_bends, LinearBend)
|
|
789
|
+
linear_bend_complements = keep_coords(linear_bend_complements, LinearBend)
|
|
790
|
+
|
|
791
|
+
# Dihedrals
|
|
792
|
+
bends_for_dihedrals = bends + linear_bends
|
|
793
|
+
proper_dihedrals, improper_dihedrals = get_dihedral_inds(
|
|
794
|
+
coords3d,
|
|
795
|
+
bonds_for_bends,
|
|
796
|
+
bends_for_dihedrals,
|
|
797
|
+
max_deg=dihed_max_deg,
|
|
798
|
+
logger=logger,
|
|
799
|
+
)
|
|
800
|
+
proper_dihedrals = keep_coords(proper_dihedrals, Torsion)
|
|
801
|
+
improper_dihedrals = keep_coords(improper_dihedrals, Torsion)
|
|
802
|
+
# Improper dihedrals are disabled for now in TRIC
|
|
803
|
+
if tric:
|
|
804
|
+
improper_dihedrals = []
|
|
805
|
+
|
|
806
|
+
cartesian_inds = []
|
|
807
|
+
if hybrid:
|
|
808
|
+
cartesian_inds = [i for i, _ in enumerate(atoms)]
|
|
809
|
+
|
|
810
|
+
"""
|
|
811
|
+
When additional primitives are given in 'define_prims', we want to append
|
|
812
|
+
them to the correct lists, that may contain already some primitive internals
|
|
813
|
+
detected by our algorithms. Here we define a map between the PrimTypes and the
|
|
814
|
+
present lists.
|
|
815
|
+
"""
|
|
816
|
+
defined_cartesians = list()
|
|
817
|
+
defined_translations = list()
|
|
818
|
+
defined_rotations = list()
|
|
819
|
+
|
|
820
|
+
define_map = {
|
|
821
|
+
PrimTypes.BOND: "bonds",
|
|
822
|
+
PrimTypes.AUX_BOND: "aux_bonds",
|
|
823
|
+
PrimTypes.HYDROGEN_BOND: "hydrogen_bonds",
|
|
824
|
+
PrimTypes.INTERFRAG_BOND: "interfrag_bonds",
|
|
825
|
+
PrimTypes.AUX_INTERFRAG_BOND: "aux_interfrag_bonds",
|
|
826
|
+
PrimTypes.BEND: "bends",
|
|
827
|
+
PrimTypes.LINEAR_BEND: "linear_bends",
|
|
828
|
+
PrimTypes.LINEAR_BEND_COMPLEMENT: "linear_bend_complements",
|
|
829
|
+
PrimTypes.PROPER_DIHEDRAL: "proper_dihedrals",
|
|
830
|
+
PrimTypes.IMPROPER_DIHEDRAL: "improper_dihedrals",
|
|
831
|
+
}
|
|
832
|
+
unmapped_typed_prims = list()
|
|
833
|
+
for type_, *indices in define_prims:
|
|
834
|
+
try:
|
|
835
|
+
key = define_map[type_]
|
|
836
|
+
except KeyError:
|
|
837
|
+
try:
|
|
838
|
+
key = define_map[PrimTypes(type_)]
|
|
839
|
+
except KeyError:
|
|
840
|
+
"""
|
|
841
|
+
With the current approach, some primitives in 'define_prims' can't
|
|
842
|
+
be mapped to their respective lists, e.g., given CARTESIAN_X/Y/Z.
|
|
843
|
+
Currently, every item in 'cartesian_inds' is expanded to three
|
|
844
|
+
Cartesians (X/Y/Z). When only the X-component is to be defined, adding
|
|
845
|
+
only the atom index to the list would result in all three components
|
|
846
|
+
to be generated.
|
|
847
|
+
|
|
848
|
+
So instead of adding them to their respective lists we keep them in
|
|
849
|
+
'unmapped_typed_prims' and use them later as is.
|
|
850
|
+
"""
|
|
851
|
+
unmapped_typed_prims.append((type_, *indices))
|
|
852
|
+
continue
|
|
853
|
+
locals()[key].append(tuple(indices))
|
|
854
|
+
|
|
855
|
+
def make_tp(prim_type, *indices):
|
|
856
|
+
"""Map possibly modified indices to their original indices.
|
|
857
|
+
|
|
858
|
+
With frozen atoms, the indices used to set up internal coordinates do
|
|
859
|
+
not correspond to the actual indices. Here we map them back.
|
|
860
|
+
"""
|
|
861
|
+
try:
|
|
862
|
+
org_indices = [freeze_map[ind] for ind in indices]
|
|
863
|
+
"""The given 'indices' may not be present in freeze_map. This can happen,
|
|
864
|
+
when coordinates are rebuilt, frozen atoms are excluded from using them
|
|
865
|
+
in the coordinate definition and a previous set of coordinates ALREADY
|
|
866
|
+
using the original indices is supplied."""
|
|
867
|
+
except KeyError as error:
|
|
868
|
+
"""In such a case we check if the given coordinates are already fully
|
|
869
|
+
defined in terms of original indices. If so, we use them as is."""
|
|
870
|
+
if set(indices) < mobile_org_inds:
|
|
871
|
+
org_indices = indices
|
|
872
|
+
else:
|
|
873
|
+
raise error
|
|
874
|
+
return (prim_type, *org_indices)
|
|
875
|
+
|
|
876
|
+
# Shortcut for PrimTypes Enum
|
|
877
|
+
pt = PrimTypes
|
|
878
|
+
# Create actual typed prims with the desired indices
|
|
879
|
+
typed_prims = (
|
|
880
|
+
# Bonds, two indices
|
|
881
|
+
[make_tp(pt.BOND, *bond) for bond in bonds]
|
|
882
|
+
+ [make_tp(pt.AUX_BOND, *abond) for abond in aux_bonds]
|
|
883
|
+
+ [make_tp(pt.HYDROGEN_BOND, *hbond) for hbond in hydrogen_bonds]
|
|
884
|
+
+ [make_tp(pt.INTERFRAG_BOND, *ifbond) for ifbond in interfrag_bonds]
|
|
885
|
+
+ [make_tp(pt.AUX_INTERFRAG_BOND, *aifbond) for aifbond in aux_interfrag_bonds]
|
|
886
|
+
# Bends, three indices
|
|
887
|
+
+ [make_tp(pt.BEND, *bend) for bend in bends]
|
|
888
|
+
+ [make_tp(pt.LINEAR_BEND, *lbend) for lbend in linear_bends]
|
|
889
|
+
+ [
|
|
890
|
+
make_tp(pt.LINEAR_BEND_COMPLEMENT, *lbendc)
|
|
891
|
+
for lbendc in linear_bend_complements
|
|
892
|
+
]
|
|
893
|
+
# Dihedral, four indices
|
|
894
|
+
+ [make_tp(pt.PROPER_DIHEDRAL, *pdihedral) for pdihedral in proper_dihedrals]
|
|
895
|
+
+ [
|
|
896
|
+
make_tp(pt.IMPROPER_DIHEDRAL, *idihedral)
|
|
897
|
+
for idihedral in improper_dihedrals
|
|
898
|
+
]
|
|
899
|
+
+ [make_tp(pt.CARTESIAN_X, cind) for cind in cartesian_inds]
|
|
900
|
+
+ [make_tp(pt.CARTESIAN_Y, cind) for cind in cartesian_inds]
|
|
901
|
+
+ [make_tp(pt.CARTESIAN_Z, cind) for cind in cartesian_inds]
|
|
902
|
+
+ [make_tp(pt.CARTESIAN_Z, cind) for cind in cartesian_inds]
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
# Translational and rotational coordinates result in 3 different coordinates each
|
|
906
|
+
for frag in translation_inds:
|
|
907
|
+
typed_prims += [
|
|
908
|
+
make_tp(pt.TRANSLATION_X, *frag),
|
|
909
|
+
make_tp(pt.TRANSLATION_Y, *frag),
|
|
910
|
+
make_tp(pt.TRANSLATION_Z, *frag),
|
|
911
|
+
]
|
|
912
|
+
for frag in rotation_inds:
|
|
913
|
+
typed_prims += [
|
|
914
|
+
make_tp(pt.ROTATION_A, *frag),
|
|
915
|
+
make_tp(pt.ROTATION_B, *frag),
|
|
916
|
+
make_tp(pt.ROTATION_C, *frag),
|
|
917
|
+
]
|
|
918
|
+
typed_prims += unmapped_typed_prims
|
|
919
|
+
# Drop duplicated typed_prims
|
|
920
|
+
typed_prims = tuple(dict.fromkeys(typed_prims))
|
|
921
|
+
|
|
922
|
+
coord_info = CoordInfo(
|
|
923
|
+
bonds=bonds,
|
|
924
|
+
hydrogen_bonds=hydrogen_bonds,
|
|
925
|
+
interfrag_bonds=interfrag_bonds,
|
|
926
|
+
aux_interfrag_bonds=aux_interfrag_bonds,
|
|
927
|
+
bends=bends,
|
|
928
|
+
linear_bends=linear_bends,
|
|
929
|
+
linear_bend_complements=linear_bend_complements,
|
|
930
|
+
proper_dihedrals=proper_dihedrals,
|
|
931
|
+
improper_dihedrals=improper_dihedrals,
|
|
932
|
+
translation_inds=translation_inds,
|
|
933
|
+
rotation_inds=rotation_inds,
|
|
934
|
+
cartesian_inds=cartesian_inds,
|
|
935
|
+
typed_prims=typed_prims,
|
|
936
|
+
fragments=fragments,
|
|
937
|
+
)
|
|
938
|
+
return coord_info
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def setup_redundant_from_geom(geom, *args, **kwargs):
|
|
942
|
+
return setup_redundant(geom.atoms, geom.coords3d, *args, **kwargs)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def get_primitives(coords3d, typed_prims, logger=None):
|
|
946
|
+
primitives = list()
|
|
947
|
+
for type_, *indices in typed_prims:
|
|
948
|
+
cls = PrimMap[type_]
|
|
949
|
+
cls_kwargs = {"indices": indices}
|
|
950
|
+
if type_ in Rotations:
|
|
951
|
+
cls_kwargs["ref_coords3d"] = coords3d
|
|
952
|
+
primitives.append(cls(**cls_kwargs))
|
|
953
|
+
|
|
954
|
+
msg = (
|
|
955
|
+
"Defined primitives\n"
|
|
956
|
+
+ "\n".join(
|
|
957
|
+
[f"\t{i:03d}: {str(p.indices): >14}" for i, p in enumerate(primitives)]
|
|
958
|
+
)
|
|
959
|
+
+ "\n"
|
|
960
|
+
)
|
|
961
|
+
log(logger, msg)
|
|
962
|
+
return primitives
|