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,176 @@
|
|
|
1
|
+
import itertools as it
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from sklearn.neighbors import KDTree
|
|
6
|
+
|
|
7
|
+
from pysisyphus.elem_data import COVALENT_RADII as CR
|
|
8
|
+
from pysisyphus.helpers_pure import log, timed
|
|
9
|
+
from pysisyphus.intcoords.valid import bend_valid, dihedral_valid
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("internal_coords")
|
|
13
|
+
|
|
14
|
+
BOND_FACTOR = 1.3
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_max_bond_dists(atoms, bond_factor, covalent_radii=None):
|
|
18
|
+
if covalent_radii is None:
|
|
19
|
+
cr = CR
|
|
20
|
+
else:
|
|
21
|
+
cr = {atom: covrad for atom, covrad in zip(atoms, covalent_radii)}
|
|
22
|
+
|
|
23
|
+
unique_atoms = set(atoms)
|
|
24
|
+
atom_pairs = it.combinations_with_replacement(unique_atoms, 2)
|
|
25
|
+
max_bond_dists = {
|
|
26
|
+
frozenset((from_, to_)): bond_factor * (cr[from_] + cr[to_])
|
|
27
|
+
for from_, to_ in atom_pairs
|
|
28
|
+
}
|
|
29
|
+
return max_bond_dists
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def find_bonds(
|
|
33
|
+
atoms, coords3d, covalent_radii=None, bond_factor=BOND_FACTOR, min_dist=0.1
|
|
34
|
+
):
|
|
35
|
+
atoms = [atom.lower() for atom in atoms]
|
|
36
|
+
c3d = coords3d.reshape(-1, 3)
|
|
37
|
+
if covalent_radii is None:
|
|
38
|
+
covalent_radii = [CR[atom] for atom in atoms]
|
|
39
|
+
cr = np.array(covalent_radii)
|
|
40
|
+
|
|
41
|
+
max_bond_dists = get_max_bond_dists(atoms, bond_factor, covalent_radii=cr)
|
|
42
|
+
radii = bond_factor * (cr.copy() + max(cr))
|
|
43
|
+
kdt = KDTree(c3d)
|
|
44
|
+
res, dists = kdt.query_radius(c3d, radii, return_distance=True)
|
|
45
|
+
bonds_ = list()
|
|
46
|
+
for i, (atom, bonds, dists_) in enumerate(zip(atoms, res, dists)):
|
|
47
|
+
fsets = [frozenset((atom, atoms[a])) for a in bonds]
|
|
48
|
+
ref_dists = np.array([max_bond_dists[fset] for fset in fsets])
|
|
49
|
+
keep = np.logical_and(dists_ <= ref_dists, min_dist <= dists_)
|
|
50
|
+
for to_ in bonds[keep]:
|
|
51
|
+
bonds_.append(frozenset((i, to_)))
|
|
52
|
+
bonds = np.array([tuple((from_, to_)) for from_, to_ in set(bonds_)])
|
|
53
|
+
return bonds
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def find_bonds_for_geom(geom, bond_factor=BOND_FACTOR):
|
|
57
|
+
return find_bonds(geom.atoms, geom.coords3d, geom.covalent_radii)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_bond_vec_getter(
|
|
61
|
+
atoms, covalent_radii, bonds_for_inds, no_bonds_with=None, bond_factor=BOND_FACTOR
|
|
62
|
+
):
|
|
63
|
+
max_bond_dists = get_max_bond_dists(
|
|
64
|
+
atoms, bond_factor, covalent_radii=covalent_radii
|
|
65
|
+
)
|
|
66
|
+
max_bond_dists_for_inds = [
|
|
67
|
+
np.array([max_bond_dists[frozenset((atoms[ind], atom_))] for atom_ in atoms])
|
|
68
|
+
for ind in bonds_for_inds
|
|
69
|
+
]
|
|
70
|
+
if no_bonds_with is None:
|
|
71
|
+
# List of empty lists
|
|
72
|
+
no_bonds_with = [[] * len(bonds_for_inds)]
|
|
73
|
+
|
|
74
|
+
# Set it to a negative value, so the calculated distance, which is always positive
|
|
75
|
+
# can't be smaller than this value.
|
|
76
|
+
for mbd, nbw in zip(max_bond_dists_for_inds, no_bonds_with):
|
|
77
|
+
mbd[nbw] = -1
|
|
78
|
+
|
|
79
|
+
all_inds = np.arange(len(atoms))
|
|
80
|
+
|
|
81
|
+
def get_bond_vecs(coords, return_bonded_inds=False):
|
|
82
|
+
coords3d = coords.reshape(-1, 3)
|
|
83
|
+
all_bond_vecs = list()
|
|
84
|
+
all_bonded_inds = list()
|
|
85
|
+
for ind, max_dists in zip(bonds_for_inds, max_bond_dists_for_inds):
|
|
86
|
+
distance_vecs = coords3d - coords3d[ind]
|
|
87
|
+
distances = np.linalg.norm(distance_vecs, axis=1)
|
|
88
|
+
# Set 0.0 distance of atom with itself to a high value to not form
|
|
89
|
+
# and ind-ind bond.
|
|
90
|
+
distances[ind] = 10_000
|
|
91
|
+
bond_mask = distances <= max_dists
|
|
92
|
+
all_bond_vecs.append(distance_vecs[bond_mask])
|
|
93
|
+
all_bonded_inds.append(all_inds[bond_mask])
|
|
94
|
+
|
|
95
|
+
if return_bonded_inds:
|
|
96
|
+
return all_bond_vecs, all_bonded_inds
|
|
97
|
+
else:
|
|
98
|
+
return all_bond_vecs
|
|
99
|
+
|
|
100
|
+
return get_bond_vecs
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_bend_candidates(bonds):
|
|
104
|
+
"""Also yields duplicates [a, b, c] and [c, b, a]."""
|
|
105
|
+
bond_dict = {}
|
|
106
|
+
bonds = [tuple(bond) for bond in bonds]
|
|
107
|
+
# Construct a dictionary holding neighbours for a given atom.
|
|
108
|
+
for from_, to_ in bonds:
|
|
109
|
+
bond_dict.setdefault(from_, list()).append(to_)
|
|
110
|
+
bond_dict.setdefault(to_, list()).append(from_)
|
|
111
|
+
|
|
112
|
+
for bond in bonds:
|
|
113
|
+
from_, to_ = bond
|
|
114
|
+
# Look up neighbours of from_ and to_ in the dictionary
|
|
115
|
+
from_neighs = set(bond_dict[from_]) - set((to_,))
|
|
116
|
+
to_neighs = set(bond_dict[to_]) - set((from_,))
|
|
117
|
+
bend_candidates = [(neigh,) + bond for neigh in from_neighs] + [
|
|
118
|
+
bond + (neigh,) for neigh in to_neighs
|
|
119
|
+
]
|
|
120
|
+
yield from bend_candidates
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def find_bends(coords3d, bonds, min_deg, max_deg, logger=None):
|
|
124
|
+
bend_set = set()
|
|
125
|
+
for indices in get_bend_candidates(bonds):
|
|
126
|
+
if (
|
|
127
|
+
(not bend_valid(coords3d, indices, min_deg, max_deg))
|
|
128
|
+
or (indices in bend_set)
|
|
129
|
+
or (indices[::-1] in bend_set)
|
|
130
|
+
):
|
|
131
|
+
continue
|
|
132
|
+
bend_set.add(indices)
|
|
133
|
+
return [list(bend) for bend in bend_set]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def find_dihedrals(coords3d, bonds, bends, max_deg, logger=None):
|
|
137
|
+
bond_dict = {}
|
|
138
|
+
bonds = [tuple(bond) for bond in bonds]
|
|
139
|
+
for from_, to_ in bonds:
|
|
140
|
+
bond_dict.setdefault(from_, list()).append(to_)
|
|
141
|
+
bond_dict.setdefault(to_, list()).append(from_)
|
|
142
|
+
|
|
143
|
+
dihedral_set = set()
|
|
144
|
+
for bend in bends:
|
|
145
|
+
bend = tuple(bend)
|
|
146
|
+
from_, central, to_ = bend
|
|
147
|
+
from_neighs = set(bond_dict[from_]) - set((to_, central))
|
|
148
|
+
to_neighs = set(bond_dict[to_]) - set((from_, central))
|
|
149
|
+
dihedral_candidates = [(neigh,) + bend for neigh in from_neighs] + [
|
|
150
|
+
bend + (neigh,) for neigh in to_neighs
|
|
151
|
+
]
|
|
152
|
+
for indices in dihedral_candidates:
|
|
153
|
+
if not dihedral_valid(coords3d, indices, deg_thresh=max_deg):
|
|
154
|
+
continue
|
|
155
|
+
dihedral_set.add(indices)
|
|
156
|
+
return [list(dihedral) for dihedral in dihedral_set]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def find_bonds_bends(geom, bond_factor=BOND_FACTOR, min_deg=15, max_deg=175):
|
|
160
|
+
log(logger, "Starting detection of bonds and bends.")
|
|
161
|
+
bonds = find_bonds_for_geom(geom, bond_factor=bond_factor)
|
|
162
|
+
log(logger, f"Found {len(bonds)} bonds.")
|
|
163
|
+
bends = find_bends(geom.coords3d, bonds, min_deg=min_deg, max_deg=max_deg)
|
|
164
|
+
log(logger, f"Found {len(bends)} bends.")
|
|
165
|
+
return bonds, bends
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@timed(logger)
|
|
169
|
+
def find_bonds_bends_dihedrals(geom, bond_factor=BOND_FACTOR, min_deg=15, max_deg=175):
|
|
170
|
+
log(logger, f"Detecting bonds, bends and dihedrals for {len(geom.atoms)} atoms.")
|
|
171
|
+
bonds, bends = find_bonds_bends(
|
|
172
|
+
geom, bond_factor=bond_factor, min_deg=min_deg, max_deg=max_deg
|
|
173
|
+
)
|
|
174
|
+
proper_dihedrals = find_dihedrals(geom.coords3d, bonds, bends, max_deg)
|
|
175
|
+
log(logger, f"Found {len(proper_dihedrals)} proper dihedrals.")
|
|
176
|
+
return bonds, bends, proper_dihedrals
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.config import BEND_MIN_DEG, LB_MIN_DEG
|
|
4
|
+
from pysisyphus.helpers_pure import log
|
|
5
|
+
from pysisyphus.intcoords.eval import eval_primitives
|
|
6
|
+
from pysisyphus.intcoords.exceptions import NeedNewInternalsException
|
|
7
|
+
from pysisyphus.intcoords.valid import bend_valid, dihedral_valid
|
|
8
|
+
from pysisyphus.intcoords.PrimTypes import Bends, Dihedrals, Rotations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def correct_dihedrals(new_dihedrals, old_dihedrals):
|
|
12
|
+
"""Dihedrals are periodic. Going from -179° to 179° is not a step of 358°,
|
|
13
|
+
but a step of 2°. By considering the actual distance of the dihedrals from
|
|
14
|
+
π the correct step can be calculated.
|
|
15
|
+
|
|
16
|
+
dihedral step length = abs(abs(new_dihedral) - π) + abs(abs(old_dihedral) - π)
|
|
17
|
+
|
|
18
|
+
or put differently
|
|
19
|
+
|
|
20
|
+
dihedral step length = abs(abs(new_dihedral - old_dihedral) - 2*π)
|
|
21
|
+
|
|
22
|
+
The sign is left to be determined. Going from -179° to 179° (roughly π - -π = 2π)
|
|
23
|
+
is a counter clockwise rotation and the dihedral has to decrease below -π. Going
|
|
24
|
+
from 179° to -179° (roughly -π - π = -2π) is a clockwise rotation and the dihedral
|
|
25
|
+
increases abvove π. So the correct sign corresponds to the negative sign of the
|
|
26
|
+
original difference.
|
|
27
|
+
|
|
28
|
+
original difference 2π -> dihedral must decrease -> sign = -1
|
|
29
|
+
original difference -2π -> dihedral must increase -> sign = +1
|
|
30
|
+
|
|
31
|
+
Overall, the old dihedral is modified by the actual step length with the correct
|
|
32
|
+
sign."""
|
|
33
|
+
new_dihedrals = np.atleast_1d(new_dihedrals)
|
|
34
|
+
old_dihedrals = np.atleast_1d(old_dihedrals)
|
|
35
|
+
dihedrals_step = new_dihedrals - old_dihedrals
|
|
36
|
+
shifted_by_2pi = np.abs(np.abs(dihedrals_step) - 2 * np.pi) < np.pi / 2
|
|
37
|
+
corrected_dihedrals = new_dihedrals.copy()
|
|
38
|
+
corrected_dihedrals[shifted_by_2pi] -= (
|
|
39
|
+
2 * np.pi * np.sign(dihedrals_step[shifted_by_2pi])
|
|
40
|
+
)
|
|
41
|
+
return corrected_dihedrals
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def update_internals(
|
|
45
|
+
new_coords3d,
|
|
46
|
+
old_internals,
|
|
47
|
+
primitives,
|
|
48
|
+
dihedral_inds,
|
|
49
|
+
rotation_inds,
|
|
50
|
+
bend_inds,
|
|
51
|
+
check_dihedrals=False,
|
|
52
|
+
check_bends=False,
|
|
53
|
+
bend_min_deg=BEND_MIN_DEG,
|
|
54
|
+
bend_max_deg=LB_MIN_DEG,
|
|
55
|
+
rotation_thresh=0.9,
|
|
56
|
+
logger=None,
|
|
57
|
+
):
|
|
58
|
+
prim_internals = eval_primitives(new_coords3d, primitives)
|
|
59
|
+
new_internals = np.array([prim_int.val for prim_int in prim_internals])
|
|
60
|
+
internal_diffs = new_internals - old_internals
|
|
61
|
+
|
|
62
|
+
new_rotations = new_internals[rotation_inds]
|
|
63
|
+
# Check for approaching singularity as discussed in the geomeTRIC paper. The
|
|
64
|
+
# original code seems to check this only for linear molecules and instead
|
|
65
|
+
# does some +2π/-2π magic, similar to how dihedrals differences are handled.
|
|
66
|
+
if (np.abs(new_rotations / np.pi) >= rotation_thresh).any():
|
|
67
|
+
raise NeedNewInternalsException(new_coords3d)
|
|
68
|
+
|
|
69
|
+
dihedrals = [prim_internals[i] for i in dihedral_inds]
|
|
70
|
+
|
|
71
|
+
dihedral_diffs = internal_diffs[dihedral_inds]
|
|
72
|
+
|
|
73
|
+
# Find differences that are shifted by 2*pi
|
|
74
|
+
shifted_by_2pi = np.abs(np.abs(dihedral_diffs) - 2 * np.pi) < np.pi / 2
|
|
75
|
+
new_dihedrals = np.array([dihed.val for dihed in dihedrals])
|
|
76
|
+
if any(shifted_by_2pi):
|
|
77
|
+
new_dihedrals[shifted_by_2pi] -= (
|
|
78
|
+
2 * np.pi * np.sign(dihedral_diffs[shifted_by_2pi])
|
|
79
|
+
)
|
|
80
|
+
# Update values
|
|
81
|
+
for dihed, new_val in zip(dihedrals, new_dihedrals):
|
|
82
|
+
dihed.val = new_val
|
|
83
|
+
|
|
84
|
+
invalid_inds = list()
|
|
85
|
+
# See if dihedrals became invalid (collinear atoms)
|
|
86
|
+
if check_dihedrals:
|
|
87
|
+
are_valid = [dihedral_valid(new_coords3d, prim.inds) for prim in dihedrals]
|
|
88
|
+
try:
|
|
89
|
+
first_dihedral = dihedral_inds[0]
|
|
90
|
+
except IndexError:
|
|
91
|
+
first_dihedral = 0
|
|
92
|
+
invalid_inds = [
|
|
93
|
+
i + first_dihedral for i, is_valid in enumerate(are_valid) if not is_valid
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
if check_bends and len(bend_inds) > 0:
|
|
97
|
+
bends = [prim_internals[i] for i in bend_inds]
|
|
98
|
+
are_valid = [
|
|
99
|
+
bend_valid(new_coords3d, prim.inds, bend_min_deg, bend_max_deg)
|
|
100
|
+
for prim in bends
|
|
101
|
+
]
|
|
102
|
+
first_bend = bend_inds[0]
|
|
103
|
+
invalid_inds = [
|
|
104
|
+
i + first_bend for i, is_valid in enumerate(are_valid) if not is_valid
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
if len(invalid_inds) > 0:
|
|
108
|
+
invalid_prims = [primitives[i] for i in invalid_inds]
|
|
109
|
+
invalid_msg = ", ".join([str(tp) for tp in invalid_prims])
|
|
110
|
+
log(logger, "Internal coordinate(s) became invalid! Need new internal coordinates!")
|
|
111
|
+
log(logger, f"Invalid primitives: {invalid_msg}")
|
|
112
|
+
raise NeedNewInternalsException(
|
|
113
|
+
new_coords3d, invalid_inds=invalid_inds, invalid_prims=invalid_prims
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return prim_internals
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def inds_from_prim_types(typed_prims, prim_types):
|
|
120
|
+
inds = [
|
|
121
|
+
i for i, (prim_type, *inds) in enumerate(typed_prims) if prim_type in prim_types
|
|
122
|
+
]
|
|
123
|
+
return inds
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def transform_int_step(
|
|
127
|
+
int_step,
|
|
128
|
+
old_cart_coords,
|
|
129
|
+
cur_internals,
|
|
130
|
+
Bt_inv_prim,
|
|
131
|
+
primitives,
|
|
132
|
+
typed_prims=None,
|
|
133
|
+
dihedral_inds=None,
|
|
134
|
+
rotation_inds=None,
|
|
135
|
+
bend_inds=None,
|
|
136
|
+
check_dihedrals=False,
|
|
137
|
+
check_bends=False,
|
|
138
|
+
bend_min_deg=BEND_MIN_DEG,
|
|
139
|
+
bend_max_deg=LB_MIN_DEG,
|
|
140
|
+
freeze_atoms=None,
|
|
141
|
+
constrained_inds=None,
|
|
142
|
+
update_constraints=False,
|
|
143
|
+
cart_rms_thresh=1e-6,
|
|
144
|
+
Bt_inv_prim_getter=None,
|
|
145
|
+
max_cycles=25,
|
|
146
|
+
logger=None,
|
|
147
|
+
):
|
|
148
|
+
"""Transformation is done in primitive internals, so int_step must be given
|
|
149
|
+
in primitive internals and not in DLC!"""
|
|
150
|
+
|
|
151
|
+
# If an iterable of typed prims is given we can derive bend/dihedral/rotation
|
|
152
|
+
# indices from them.
|
|
153
|
+
if typed_prims is not None:
|
|
154
|
+
if dihedral_inds is None:
|
|
155
|
+
dihedral_inds = inds_from_prim_types(typed_prims, Dihedrals)
|
|
156
|
+
|
|
157
|
+
if bend_inds is None:
|
|
158
|
+
bend_inds = inds_from_prim_types(typed_prims, Bends)
|
|
159
|
+
|
|
160
|
+
if rotation_inds is None:
|
|
161
|
+
rotation_inds = inds_from_prim_types(typed_prims, Rotations)
|
|
162
|
+
|
|
163
|
+
if freeze_atoms is None:
|
|
164
|
+
freeze_atoms = list()
|
|
165
|
+
|
|
166
|
+
if constrained_inds is None:
|
|
167
|
+
constrained_inds = list()
|
|
168
|
+
|
|
169
|
+
freeze_atoms = np.array(freeze_atoms, dtype=int)
|
|
170
|
+
new_cart_coords = old_cart_coords.copy()
|
|
171
|
+
remaining_int_step = int_step
|
|
172
|
+
target_internals = cur_internals + int_step
|
|
173
|
+
# When we want to update the constraints we use the target primitive internals,
|
|
174
|
+
if update_constraints:
|
|
175
|
+
constrained_vals = target_internals[constrained_inds]
|
|
176
|
+
# otherwise we try to stay with the original constrained values.
|
|
177
|
+
else:
|
|
178
|
+
constrained_vals = cur_internals[constrained_inds]
|
|
179
|
+
|
|
180
|
+
def backtransform(remaining_int_step, Bt_inv_prim):
|
|
181
|
+
"""Separate function so it can be a called after the main loop
|
|
182
|
+
finished to re-enforce the constraints."""
|
|
183
|
+
nonlocal new_cart_coords
|
|
184
|
+
|
|
185
|
+
cart_step = Bt_inv_prim.T.dot(remaining_int_step)
|
|
186
|
+
# Remove step from frozen atoms.
|
|
187
|
+
cart_step.reshape(-1, 3)[freeze_atoms] = 0.0
|
|
188
|
+
cart_rms = np.sqrt(np.mean(cart_step**2))
|
|
189
|
+
# Update cartesian coordinates
|
|
190
|
+
new_cart_coords += cart_step
|
|
191
|
+
# Determine new internal coordinates
|
|
192
|
+
new_prim_ints = update_internals(
|
|
193
|
+
new_cart_coords.reshape(-1, 3),
|
|
194
|
+
old_internals,
|
|
195
|
+
primitives,
|
|
196
|
+
dihedral_inds,
|
|
197
|
+
rotation_inds,
|
|
198
|
+
bend_inds,
|
|
199
|
+
check_dihedrals=check_dihedrals,
|
|
200
|
+
check_bends=check_bends,
|
|
201
|
+
bend_min_deg=bend_min_deg,
|
|
202
|
+
bend_max_deg=bend_max_deg,
|
|
203
|
+
logger=logger,
|
|
204
|
+
)
|
|
205
|
+
new_internals = [prim.val for prim in new_prim_ints]
|
|
206
|
+
return new_prim_ints, new_internals, cart_rms
|
|
207
|
+
|
|
208
|
+
last_rms = 9999
|
|
209
|
+
old_internals = cur_internals
|
|
210
|
+
backtransform_failed = True
|
|
211
|
+
for i in range(max_cycles):
|
|
212
|
+
if Bt_inv_prim_getter is not None:
|
|
213
|
+
Bt_inv_prim = Bt_inv_prim_getter(new_cart_coords)
|
|
214
|
+
log(logger, f"Recalculated (B^T)^+ in microcycle {i}")
|
|
215
|
+
|
|
216
|
+
new_prim_ints, new_internals, cart_rms = backtransform(
|
|
217
|
+
remaining_int_step, Bt_inv_prim
|
|
218
|
+
)
|
|
219
|
+
remaining_int_step = target_internals - new_internals
|
|
220
|
+
internal_rms = np.sqrt(np.mean(remaining_int_step**2))
|
|
221
|
+
log(
|
|
222
|
+
logger,
|
|
223
|
+
f"Cycle {i}: rms(Δcart)={cart_rms:1.4e}, rms(Δint.) = {internal_rms:1.5e}",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# This assumes the first cart_rms won't be > 9999 ;)
|
|
227
|
+
if cart_rms < last_rms:
|
|
228
|
+
# Store results of the conversion cycle for laster use, if
|
|
229
|
+
# the internal-cartesian-transformation goes bad.
|
|
230
|
+
best_cycle = (new_cart_coords.copy(), new_internals.copy())
|
|
231
|
+
best_cycle_ind = i
|
|
232
|
+
elif i != 0:
|
|
233
|
+
# If the conversion somehow fails we fallback to the best previous step.
|
|
234
|
+
log(logger, f"Backconversion failed! Falling back to step {best_cycle_ind}")
|
|
235
|
+
new_cart_coords, new_internals = best_cycle
|
|
236
|
+
break
|
|
237
|
+
else:
|
|
238
|
+
raise Exception(
|
|
239
|
+
"Internal-cartesian back-transformation already "
|
|
240
|
+
"failed in the first step. Aborting!"
|
|
241
|
+
)
|
|
242
|
+
old_internals = new_internals
|
|
243
|
+
|
|
244
|
+
last_rms = cart_rms
|
|
245
|
+
if cart_rms < cart_rms_thresh:
|
|
246
|
+
log(
|
|
247
|
+
logger,
|
|
248
|
+
f"Internal->Cartesian transformation converged in {i+1} cycle(s)!",
|
|
249
|
+
)
|
|
250
|
+
backtransform_failed = False
|
|
251
|
+
break
|
|
252
|
+
|
|
253
|
+
if len(constrained_inds) > 0:
|
|
254
|
+
for j in range(max_cycles):
|
|
255
|
+
cur_constrained_vals = np.array(new_internals)[constrained_inds]
|
|
256
|
+
diff = constrained_vals - cur_constrained_vals
|
|
257
|
+
if any(np.abs(diff) <= 1e-5):
|
|
258
|
+
break
|
|
259
|
+
remaining_int_step = np.zeros_like(remaining_int_step)
|
|
260
|
+
remaining_int_step[constrained_inds] = diff
|
|
261
|
+
new_prim_ints, new_internals, _ = backtransform(
|
|
262
|
+
remaining_int_step, Bt_inv_prim
|
|
263
|
+
)
|
|
264
|
+
if j > 0:
|
|
265
|
+
log(logger, f"Re-enforced constraints in {j} additional cycle(s).")
|
|
266
|
+
|
|
267
|
+
log(logger, "")
|
|
268
|
+
|
|
269
|
+
# Return the difference between the new cartesian coordinates that yield
|
|
270
|
+
# the desired internal coordinates and the old cartesian coordinates.
|
|
271
|
+
cart_step = new_cart_coords - old_cart_coords
|
|
272
|
+
return new_prim_ints, cart_step, backtransform_failed
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.helpers_pure import log
|
|
4
|
+
from pysisyphus.intcoords.PrimTypes import PrimTypes
|
|
5
|
+
from pysisyphus.intcoords import Bend
|
|
6
|
+
from pysisyphus.linalg import norm3
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def bend_valid(coords3d, indices, min_deg, max_deg):
|
|
10
|
+
val = Bend._calculate(coords3d, indices)
|
|
11
|
+
deg = np.rad2deg(val)
|
|
12
|
+
return min_deg <= deg <= max_deg
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def are_collinear(vec1, vec2, deg_thresh=179.5):
|
|
16
|
+
thresh = np.cos(np.deg2rad(deg_thresh))
|
|
17
|
+
return abs(vec1.dot(vec2)) >= abs(thresh)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def dihedral_valid(coords3d, inds, deg_thresh=177.5):
|
|
21
|
+
if len(set(inds)) != 4:
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
m, o, p, n = inds
|
|
25
|
+
u_dash = coords3d[m] - coords3d[o]
|
|
26
|
+
v_dash = coords3d[n] - coords3d[p]
|
|
27
|
+
w_dash = coords3d[p] - coords3d[o]
|
|
28
|
+
u_norm = norm3(u_dash)
|
|
29
|
+
v_norm = norm3(v_dash)
|
|
30
|
+
w_norm = norm3(w_dash)
|
|
31
|
+
u = u_dash / u_norm
|
|
32
|
+
v = v_dash / v_norm
|
|
33
|
+
w = w_dash / w_norm
|
|
34
|
+
|
|
35
|
+
valid = not (
|
|
36
|
+
are_collinear(u, w, deg_thresh=deg_thresh)
|
|
37
|
+
or are_collinear(v, w, deg_thresh=deg_thresh)
|
|
38
|
+
)
|
|
39
|
+
return valid
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def dihedrals_are_valid(coords3d, dihedral_inds, logger=None):
|
|
43
|
+
valid = [dihedral_valid(coords3d, inds) for inds in dihedral_inds]
|
|
44
|
+
invalid = [dihedral_ind for dihedral_ind, v in zip(dihedral_inds, valid) if not v]
|
|
45
|
+
if invalid:
|
|
46
|
+
log(logger, f"Invalid dihedrals: {invalid}")
|
|
47
|
+
all_valid = all(valid)
|
|
48
|
+
return all_valid
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def check_typed_prims(
|
|
52
|
+
coords3d,
|
|
53
|
+
typed_prims,
|
|
54
|
+
bend_min_deg,
|
|
55
|
+
dihed_max_deg,
|
|
56
|
+
lb_min_deg,
|
|
57
|
+
logger=None,
|
|
58
|
+
check_bends=True,
|
|
59
|
+
):
|
|
60
|
+
if check_bends:
|
|
61
|
+
bend_func = lambda indices: bend_valid(
|
|
62
|
+
coords3d, indices, min_deg=bend_min_deg, max_deg=lb_min_deg
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
bend_func = lambda indices: True
|
|
66
|
+
funcs = {
|
|
67
|
+
PrimTypes.BEND: bend_func,
|
|
68
|
+
PrimTypes.PROPER_DIHEDRAL: lambda indices: dihedral_valid(
|
|
69
|
+
coords3d,
|
|
70
|
+
indices,
|
|
71
|
+
deg_thresh=dihed_max_deg,
|
|
72
|
+
),
|
|
73
|
+
PrimTypes.IMPROPER_DIHEDRAL: lambda indices: dihedral_valid(
|
|
74
|
+
coords3d,
|
|
75
|
+
indices,
|
|
76
|
+
deg_thresh=dihed_max_deg,
|
|
77
|
+
),
|
|
78
|
+
}
|
|
79
|
+
valid_typed_prims = list()
|
|
80
|
+
for i, (type_, *indices) in enumerate(typed_prims):
|
|
81
|
+
try:
|
|
82
|
+
valid = funcs[type_](indices)
|
|
83
|
+
except KeyError:
|
|
84
|
+
valid = True
|
|
85
|
+
if valid:
|
|
86
|
+
valid_typed_prims.append((type_, *indices))
|
|
87
|
+
else:
|
|
88
|
+
log(logger, f"Primitive {(type_, *indices)} is invalid!")
|
|
89
|
+
return valid_typed_prims
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# [1] https://doi.org/10.1063/1.5090303
|
|
2
|
+
# Geodesic interpolation for reaction pathways
|
|
3
|
+
# Zhu, Thompson, Martinez, 2019
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from pysisyphus.Geometry import Geometry
|
|
8
|
+
from pysisyphus.interpolate.Interpolator import Interpolator
|
|
9
|
+
from pysisyphus.constants import BOHR2ANG
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from geodesic_interpolate.interpolation import redistribute
|
|
13
|
+
from geodesic_interpolate.geodesic import Geodesic as Geo
|
|
14
|
+
|
|
15
|
+
can_geodesic = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
can_geodesic = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Geodesic(Interpolator):
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
*args,
|
|
24
|
+
align: bool = False,
|
|
25
|
+
tol: float = 2e-3,
|
|
26
|
+
scaling: float = 1.7,
|
|
27
|
+
dist_cutoff: float = 3.0,
|
|
28
|
+
friction: float = 1e-2,
|
|
29
|
+
maxiter: int = 15,
|
|
30
|
+
microiter: int = 20,
|
|
31
|
+
**kwargs,
|
|
32
|
+
):
|
|
33
|
+
"""Geodesic Interpolation for Reaction Pathways.
|
|
34
|
+
|
|
35
|
+
Requires the 'geodesic-interpolate' package found at
|
|
36
|
+
|
|
37
|
+
https://github.com/virtualzx-nad/geodesic-interpolate.git
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
align : bool, optional
|
|
42
|
+
Whether to align geometries, by default False
|
|
43
|
+
tol : float, optional
|
|
44
|
+
Convergence tolerance, by default 2e-3
|
|
45
|
+
scaling : float, optional
|
|
46
|
+
Exponential parameter for morse potential, by default 1.7
|
|
47
|
+
dist_cutoff : float, optional
|
|
48
|
+
Cut-off value for the distance between a pair of atoms
|
|
49
|
+
to be included in the coordinate system, by default 3.0
|
|
50
|
+
friction : float, optional
|
|
51
|
+
Size of friction term used to prevent very large
|
|
52
|
+
change of geometry, by default 1e-2
|
|
53
|
+
maxiter : int, optional
|
|
54
|
+
Maximum number of minimization iterations, by default 15
|
|
55
|
+
microiter : int, optional
|
|
56
|
+
Maximum number of micro iterations for
|
|
57
|
+
sweeping algorithm, by default 20
|
|
58
|
+
"""
|
|
59
|
+
super().__init__(*args, align=align, **kwargs)
|
|
60
|
+
self.tol = tol
|
|
61
|
+
self.scaling = scaling
|
|
62
|
+
self.dist_cutoff = dist_cutoff
|
|
63
|
+
self.friction = friction
|
|
64
|
+
self.maxiter = maxiter
|
|
65
|
+
self.microiter = microiter
|
|
66
|
+
|
|
67
|
+
def interpolate(self, initial_geom, final_geom, **kwargs):
|
|
68
|
+
if not can_geodesic:
|
|
69
|
+
raise ModuleNotFoundError(
|
|
70
|
+
"Geodesic interpolation requires the geodesic_interpolate package."
|
|
71
|
+
)
|
|
72
|
+
coords3d = np.array((initial_geom.coords3d, final_geom.coords3d))
|
|
73
|
+
coords3d *= BOHR2ANG
|
|
74
|
+
|
|
75
|
+
nimages = 2 + self.between
|
|
76
|
+
raw = redistribute(self.atoms, coords3d, nimages, tol=self.tol * 5)
|
|
77
|
+
|
|
78
|
+
smoother = Geo(
|
|
79
|
+
self.atoms,
|
|
80
|
+
raw,
|
|
81
|
+
self.scaling,
|
|
82
|
+
threshold=self.dist_cutoff,
|
|
83
|
+
friction=self.friction,
|
|
84
|
+
)
|
|
85
|
+
if len(self.atoms) > 35:
|
|
86
|
+
path = smoother.sweep(
|
|
87
|
+
tol=self.tol, max_iter=self.maxiter, micro_iter=self.microiter
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
path = smoother.smooth(tol=self.tol, max_iter=self.maxiter)
|
|
91
|
+
path /= BOHR2ANG
|
|
92
|
+
interpolated_geoms = [Geometry(self.atoms, coords) for coords in path]
|
|
93
|
+
return interpolated_geoms[1:-1]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from scipy.spatial.distance import pdist
|
|
2
|
+
|
|
3
|
+
# from pysisyphus.constants import BOHR2ANG, ANG2BOHR
|
|
4
|
+
from pysisyphus.calculators.IDPPCalculator import IDPPCalculator
|
|
5
|
+
from pysisyphus.constants import BOHR2ANG, ANG2BOHR
|
|
6
|
+
from pysisyphus.cos.NEB import NEB
|
|
7
|
+
from pysisyphus.helpers import align_geoms
|
|
8
|
+
from pysisyphus.optimizers.FIRE import FIRE
|
|
9
|
+
from pysisyphus.interpolate.Interpolator import Interpolator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# [1] http://aip.scitation.org/doi/full/10.1063/1.4878664
|
|
13
|
+
# See https://gitlab.com/ase/ase/blob/master/ase/neb.py
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IDPP(Interpolator):
|
|
17
|
+
|
|
18
|
+
def interpolate(self, initial_geom, final_geom, **kwargs):
|
|
19
|
+
# Do an initial linear interpolation to generate all geometries/images
|
|
20
|
+
# that will be refined later by IDPP interpolation.
|
|
21
|
+
linear_interpol = super().interpolate(initial_geom, final_geom)
|
|
22
|
+
idpp_geoms = [initial_geom] + linear_interpol + [final_geom]
|
|
23
|
+
align_geoms(idpp_geoms)
|
|
24
|
+
|
|
25
|
+
# Interestingly IDPP calculations work much better when done
|
|
26
|
+
# in Angstroem instead of in Bohr.
|
|
27
|
+
for geom in idpp_geoms:
|
|
28
|
+
geom.coords *= BOHR2ANG
|
|
29
|
+
|
|
30
|
+
# We want to interpolate between these two condensed distance matrices
|
|
31
|
+
initial_pd = pdist(initial_geom.coords3d)
|
|
32
|
+
final_pd = pdist(final_geom.coords3d)
|
|
33
|
+
steps = 1 + self.between
|
|
34
|
+
pd_diff = (final_pd - initial_pd) / steps
|
|
35
|
+
|
|
36
|
+
for i, geom in enumerate(idpp_geoms):
|
|
37
|
+
geom.set_calculator(IDPPCalculator(initial_pd + i * pd_diff))
|
|
38
|
+
|
|
39
|
+
neb = NEB(idpp_geoms)
|
|
40
|
+
opt_kwargs = {
|
|
41
|
+
"max_cycles": 1000,
|
|
42
|
+
"rms_force": 1e-2,
|
|
43
|
+
"align": False,
|
|
44
|
+
"check_coord_diffs": False,
|
|
45
|
+
}
|
|
46
|
+
opt = FIRE(neb, **opt_kwargs)
|
|
47
|
+
opt.run()
|
|
48
|
+
|
|
49
|
+
for geom in idpp_geoms:
|
|
50
|
+
# Delete IDPP calculator, energies and forces
|
|
51
|
+
geom.clear()
|
|
52
|
+
geom.coords *= ANG2BOHR
|
|
53
|
+
|
|
54
|
+
interpolated_geoms = idpp_geoms[1:-1]
|
|
55
|
+
return interpolated_geoms
|