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
pysisyphus/exceptions.py
ADDED
pysisyphus/filtertrj.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from pysisyphus.helpers import geoms_from_trj
|
|
8
|
+
from pysisyphus.xyzloader import write_geoms_to_trj
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse_args(args):
|
|
12
|
+
parser = argparse.ArgumentParser()
|
|
13
|
+
|
|
14
|
+
parser.add_argument("trj")
|
|
15
|
+
parser.add_argument("filter")
|
|
16
|
+
parser.add_argument("--first", type=int, metavar="N", default=None,
|
|
17
|
+
help="Only consider first N geometries in _trj.xyz."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
return parser.parse_args(args)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_filter(raw_filter):
|
|
24
|
+
split = raw_filter.strip().split()
|
|
25
|
+
mobjs = [re.match("(?P<not_present>!?)\((?P<atoms>[a-zA-Z-]+)\)", s) for s in split]
|
|
26
|
+
filters = list()
|
|
27
|
+
for m in mobjs:
|
|
28
|
+
internal = m["atoms"]
|
|
29
|
+
is_present = not bool(m["not_present"])
|
|
30
|
+
internal = tuple(sorted(internal.upper().split("-")))
|
|
31
|
+
filters.append((is_present, internal))
|
|
32
|
+
return filters
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_unique_internals(geom):
|
|
36
|
+
attrs = ("bond_atom_indices", "bend_atom_indices", "dihedral_atom_indices")
|
|
37
|
+
atoms_arr = np.array(geom.atoms)
|
|
38
|
+
unique_internals = list()
|
|
39
|
+
for attr in attrs:
|
|
40
|
+
indices = getattr(geom.internal, attr)
|
|
41
|
+
unique = set([tuple(sorted(atoms_arr[inds])) for inds in indices])
|
|
42
|
+
unique_internals.extend(unique)
|
|
43
|
+
return unique_internals
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run():
|
|
47
|
+
args = parse_args(sys.argv[1:])
|
|
48
|
+
geoms = geoms_from_trj(args.trj, first=args.first, coord_type="redund",)
|
|
49
|
+
filters = parse_filter(args.filter)
|
|
50
|
+
print("Filters", filters)
|
|
51
|
+
|
|
52
|
+
valid_geoms = list()
|
|
53
|
+
invalid_geoms = list()
|
|
54
|
+
for i, geom in enumerate(geoms):
|
|
55
|
+
internals = get_unique_internals(geom)
|
|
56
|
+
valid = all([is_present == (internal in internals)
|
|
57
|
+
for is_present, internal in filters]
|
|
58
|
+
)
|
|
59
|
+
if not valid:
|
|
60
|
+
print(f"geom {i+1} is not valid.")
|
|
61
|
+
invalid_geoms.append(geom)
|
|
62
|
+
else:
|
|
63
|
+
valid_geoms.append(geom)
|
|
64
|
+
print()
|
|
65
|
+
print(f"Found {len(valid_geoms)} valid geometries.")
|
|
66
|
+
print(f"Found {len(invalid_geoms)} invalid geometries.")
|
|
67
|
+
|
|
68
|
+
write_geoms_to_trj(valid_geoms, "filtered_valid_trj.xyz")
|
|
69
|
+
write_geoms_to_trj(invalid_geoms, "filtered_invalid_trj.xyz")
|
pysisyphus/helpers.py
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
import getpass
|
|
3
|
+
import itertools as it
|
|
4
|
+
import logging
|
|
5
|
+
from math import log
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import scipy as sp
|
|
14
|
+
from scipy.optimize import linear_sum_assignment
|
|
15
|
+
from scipy.spatial.distance import cdist
|
|
16
|
+
|
|
17
|
+
from pysisyphus.config import p_DEFAULT, T_DEFAULT, LIB_DIR
|
|
18
|
+
from pysisyphus.constants import ANG2BOHR, AU2KJPERMOL
|
|
19
|
+
from pysisyphus.Geometry import Geometry
|
|
20
|
+
from pysisyphus.helpers_pure import (
|
|
21
|
+
eigval_to_wavenumber,
|
|
22
|
+
report_isotopes,
|
|
23
|
+
highlight_text,
|
|
24
|
+
rms,
|
|
25
|
+
)
|
|
26
|
+
from pysisyphus.io import (
|
|
27
|
+
geom_from_cjson,
|
|
28
|
+
geom_from_crd,
|
|
29
|
+
geom_from_hessian,
|
|
30
|
+
geom_from_mol2,
|
|
31
|
+
geom_from_pdb,
|
|
32
|
+
geom_from_qcschema,
|
|
33
|
+
save_hessian as save_h5_hessian,
|
|
34
|
+
geom_from_zmat_fn,
|
|
35
|
+
geoms_from_inline_xyz,
|
|
36
|
+
)
|
|
37
|
+
from pysisyphus.thermo import (
|
|
38
|
+
can_thermoanalysis,
|
|
39
|
+
print_thermoanalysis,
|
|
40
|
+
)
|
|
41
|
+
from pysisyphus.xyzloader import parse_xyz_file, parse_trj_file, make_trj_str
|
|
42
|
+
|
|
43
|
+
import torch
|
|
44
|
+
|
|
45
|
+
def geom_from_xyz_file(xyz_fn, coord_type="cart", **coord_kwargs):
|
|
46
|
+
kwargs = {
|
|
47
|
+
"coord_type": coord_type,
|
|
48
|
+
}
|
|
49
|
+
kwargs.update(coord_kwargs)
|
|
50
|
+
xyz_fn = str(xyz_fn)
|
|
51
|
+
atoms, coords, comment = parse_xyz_file(xyz_fn, with_comment=True)
|
|
52
|
+
# Normally, xyz files are in Angstrom. If "IN_BOHR" is present
|
|
53
|
+
# in the comment, we interpret this file as in bohr.
|
|
54
|
+
if "IN_BOHR" not in comment.split():
|
|
55
|
+
coords *= ANG2BOHR
|
|
56
|
+
geom = Geometry(
|
|
57
|
+
atoms,
|
|
58
|
+
coords.flatten(),
|
|
59
|
+
comment=comment,
|
|
60
|
+
**kwargs,
|
|
61
|
+
)
|
|
62
|
+
return geom
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def geoms_from_trj(trj_fn, first=None, coord_type="cart", **coord_kwargs):
|
|
66
|
+
trj_fn = str(trj_fn)
|
|
67
|
+
kwargs = {
|
|
68
|
+
"coord_type": coord_type,
|
|
69
|
+
}
|
|
70
|
+
kwargs.update(coord_kwargs)
|
|
71
|
+
atoms_coords_comments = parse_trj_file(trj_fn, with_comments=True)[:first]
|
|
72
|
+
geoms = [
|
|
73
|
+
Geometry(atoms, coords.flatten() * ANG2BOHR, comment=comment, **kwargs)
|
|
74
|
+
for atoms, coords, comment in atoms_coords_comments
|
|
75
|
+
]
|
|
76
|
+
return geoms
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def geom_loader(fn, coord_type="cart", iterable=False, **coord_kwargs):
|
|
80
|
+
"""After introducing the pubchem functionality I don't like this
|
|
81
|
+
function anymore :) Too complicated."""
|
|
82
|
+
fn = str(fn)
|
|
83
|
+
org_fn = fn
|
|
84
|
+
|
|
85
|
+
split_ = re.split(r"\[(-?\d+)\]$", fn)
|
|
86
|
+
fn = split_.pop(0)
|
|
87
|
+
if split_:
|
|
88
|
+
index = int(split_.pop(0))
|
|
89
|
+
else:
|
|
90
|
+
index = None
|
|
91
|
+
ext = "" if "\n" in fn else Path(fn).suffix
|
|
92
|
+
|
|
93
|
+
funcs = {
|
|
94
|
+
".cjson": geom_from_cjson,
|
|
95
|
+
".crd": geom_from_crd,
|
|
96
|
+
".h5": geom_from_hessian,
|
|
97
|
+
".mol2": geom_from_mol2,
|
|
98
|
+
".pdb": geom_from_pdb,
|
|
99
|
+
".json": geom_from_qcschema,
|
|
100
|
+
"_trj.xyz": geoms_from_trj,
|
|
101
|
+
".xyz": geom_from_xyz_file,
|
|
102
|
+
".zmat": geom_from_zmat_fn,
|
|
103
|
+
"": geoms_from_inline_xyz,
|
|
104
|
+
}
|
|
105
|
+
assert ext in funcs, f"Unknown filetype for '{fn}'!"
|
|
106
|
+
func = funcs[ext]
|
|
107
|
+
|
|
108
|
+
if fn.startswith("lib:"):
|
|
109
|
+
fn = str(LIB_DIR / fn[4:])
|
|
110
|
+
|
|
111
|
+
kwargs = {
|
|
112
|
+
"coord_type": coord_type,
|
|
113
|
+
}
|
|
114
|
+
kwargs.update(coord_kwargs)
|
|
115
|
+
geom = func(fn, **kwargs)
|
|
116
|
+
|
|
117
|
+
if index is not None:
|
|
118
|
+
geom = geom[index]
|
|
119
|
+
|
|
120
|
+
if iterable and (ext in ("_trj.xyz", "")) and index is None:
|
|
121
|
+
geom = tuple(geom)
|
|
122
|
+
elif not iterable and ext == "" and len(geom) == 1:
|
|
123
|
+
geom = geom[0]
|
|
124
|
+
elif iterable:
|
|
125
|
+
geom = (geom,)
|
|
126
|
+
|
|
127
|
+
return geom
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _svd_align_core(coords_to_align, reference_coords):
|
|
131
|
+
"""Compute the optimal rotation matrix aligning *coords_to_align* onto
|
|
132
|
+
*reference_coords* using SVD. Both arrays must be (N, 3) and already
|
|
133
|
+
centroid-subtracted.
|
|
134
|
+
|
|
135
|
+
Returns ``(rot_mat, rotated_coords)`` where ``rot_mat`` is a (3, 3)
|
|
136
|
+
rotation matrix (reflection-free) and ``rotated_coords`` is
|
|
137
|
+
``coords_to_align.dot(rot_mat)``.
|
|
138
|
+
"""
|
|
139
|
+
# http://nghiaho.com/?page_id=671#comment-559906
|
|
140
|
+
tmp_mat = coords_to_align.T.dot(reference_coords)
|
|
141
|
+
U, W, Vt = np.linalg.svd(tmp_mat)
|
|
142
|
+
rot_mat = U.dot(Vt)
|
|
143
|
+
# Avoid reflections
|
|
144
|
+
if np.linalg.det(rot_mat) < 0:
|
|
145
|
+
U[:, -1] *= -1
|
|
146
|
+
rot_mat = U.dot(Vt)
|
|
147
|
+
rotated_coords = coords_to_align.dot(rot_mat)
|
|
148
|
+
return rot_mat, rotated_coords
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def align_geoms(geoms):
|
|
152
|
+
# http://nghiaho.com/?page_id=671#comment-559906
|
|
153
|
+
first_geom = geoms[0]
|
|
154
|
+
coords3d = first_geom.coords3d
|
|
155
|
+
centroid = coords3d.mean(axis=0)
|
|
156
|
+
last_centered = coords3d - centroid
|
|
157
|
+
first_geom.coords3d = last_centered
|
|
158
|
+
atoms_per_image = len(first_geom.atoms)
|
|
159
|
+
|
|
160
|
+
# Don't rotate the first image, so just add identity matrices
|
|
161
|
+
# for every atom.
|
|
162
|
+
rot_mats = [np.eye(3)] * atoms_per_image
|
|
163
|
+
for i, geom in enumerate(geoms[1:], 1):
|
|
164
|
+
coords3d = geom.coords3d
|
|
165
|
+
centroid = coords3d.mean(axis=0)
|
|
166
|
+
centered = coords3d - centroid
|
|
167
|
+
rot_mat, rotated3d = _svd_align_core(centered, last_centered)
|
|
168
|
+
geom.coords3d = rotated3d
|
|
169
|
+
last_centered = rotated3d
|
|
170
|
+
rot_mats.extend([rot_mat] * atoms_per_image)
|
|
171
|
+
return rot_mats
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def procrustes(geometry, align_factor=1.0):
|
|
175
|
+
# http://nghiaho.com/?page_id=671#comment-559906
|
|
176
|
+
image0 = geometry.images[0]
|
|
177
|
+
coords3d = image0.coords3d
|
|
178
|
+
centroid = coords3d.mean(axis=0)
|
|
179
|
+
last_centered = coords3d - centroid
|
|
180
|
+
geometry.set_coords_at(0, last_centered.flatten())
|
|
181
|
+
atoms_per_image = len(image0.atoms)
|
|
182
|
+
|
|
183
|
+
# Don't rotate the first image, so just add identity matrices
|
|
184
|
+
# for every atom.
|
|
185
|
+
rot_mats = [np.eye(3)] * atoms_per_image
|
|
186
|
+
for i, image in enumerate(geometry.images[1:], 1):
|
|
187
|
+
coords3d = image.coords3d
|
|
188
|
+
centroid = coords3d.mean(axis=0)
|
|
189
|
+
centered = coords3d - centroid
|
|
190
|
+
rot_mat, _ = _svd_align_core(centered, last_centered)
|
|
191
|
+
# do a partial alignment if requested
|
|
192
|
+
if not (0.0 <= align_factor <= 1.0):
|
|
193
|
+
raise ValueError("align_factor must be between 0 and 1")
|
|
194
|
+
# mix the rotation matrix with the identity matrix
|
|
195
|
+
# align_factor=1 for full alignment (default); align_factor=0 for no alignment
|
|
196
|
+
rot_mat = align_factor * rot_mat + (1 - align_factor) * np.eye(3)
|
|
197
|
+
rotated3d = centered.dot(rot_mat)
|
|
198
|
+
geometry.set_coords_at(i, rotated3d.flatten())
|
|
199
|
+
last_centered = rotated3d
|
|
200
|
+
rot_mats.extend([rot_mat] * atoms_per_image)
|
|
201
|
+
return rot_mats
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def align_coords(coords_list):
|
|
205
|
+
coords_list = np.array(coords_list)
|
|
206
|
+
coord_num = len(coords_list)
|
|
207
|
+
aligned_coords = np.empty_like(coords_list).reshape(coord_num, -1, 3)
|
|
208
|
+
|
|
209
|
+
coords0 = coords_list[0]
|
|
210
|
+
coords0_3d = coords0.reshape(-1, 3)
|
|
211
|
+
centroid = coords0_3d.mean(axis=0)
|
|
212
|
+
prev_centered = coords0_3d - centroid
|
|
213
|
+
aligned_coords[0] = prev_centered
|
|
214
|
+
|
|
215
|
+
for i, coords in enumerate(coords_list[1:], 1):
|
|
216
|
+
coords3d = coords.reshape(-1, 3)
|
|
217
|
+
centroid = coords3d.mean(axis=0)
|
|
218
|
+
centered = coords3d - centroid
|
|
219
|
+
rot_mat, rotated3d = _svd_align_core(centered, prev_centered)
|
|
220
|
+
aligned_coords[i] = rotated3d
|
|
221
|
+
prev_centered = rotated3d
|
|
222
|
+
aligned_coords.reshape(coord_num, -1)
|
|
223
|
+
return aligned_coords
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def fit_rigid(
|
|
227
|
+
geometry, vectors=None, vector_lists=None, hessian=None, align_factor=1.0
|
|
228
|
+
):
|
|
229
|
+
if vectors is None:
|
|
230
|
+
vectors = ()
|
|
231
|
+
if vector_lists is None:
|
|
232
|
+
vector_lists = ()
|
|
233
|
+
rotated_vector_lists = list()
|
|
234
|
+
rotated_hessian = None
|
|
235
|
+
|
|
236
|
+
rot_mats = procrustes(geometry, align_factor=align_factor)
|
|
237
|
+
G = sp.linalg.block_diag(*rot_mats)
|
|
238
|
+
rotated_vectors = [vec.dot(G) for vec in vectors]
|
|
239
|
+
for vl in vector_lists:
|
|
240
|
+
rvl = [vec.dot(G) for vec in vl]
|
|
241
|
+
rotated_vector_lists.append(rvl)
|
|
242
|
+
|
|
243
|
+
if hessian is not None:
|
|
244
|
+
# rotated_hessian = G.dot(hessian).dot(G.T)
|
|
245
|
+
# rotated_hessian = G.T.dot(hessian).dot(G)
|
|
246
|
+
rotated_hessian = G * hessian * G.T
|
|
247
|
+
return rotated_vectors, rotated_vector_lists, rotated_hessian
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def slugify_worker(dask_worker):
|
|
251
|
+
slug = re.sub("tcp://", "host_", dask_worker)
|
|
252
|
+
slug = re.sub(r"\.", "_", slug)
|
|
253
|
+
slug = re.sub(":", "-", slug)
|
|
254
|
+
return slug
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def match_geoms(ref_geom, geom_to_match, hydrogen=False):
|
|
258
|
+
"""
|
|
259
|
+
See
|
|
260
|
+
[1] 10.1021/ci400534h
|
|
261
|
+
[2] 10.1021/acs.jcim.6b00516
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
logging.warning(
|
|
265
|
+
"helpers.match_geoms is deprecated!"
|
|
266
|
+
"Use stocastic.align.match_geom_atoms instead!"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
assert len(ref_geom.atoms) == len(geom_to_match.atoms), "Atom numbers don't match!"
|
|
270
|
+
|
|
271
|
+
ref_coords, _ = ref_geom.coords_by_type
|
|
272
|
+
coords_to_match, inds_to_match = geom_to_match.coords_by_type
|
|
273
|
+
atoms = ref_coords.keys()
|
|
274
|
+
for atom in atoms:
|
|
275
|
+
# Only match hydrogens if explicitly requested
|
|
276
|
+
if atom == "H" and not hydrogen:
|
|
277
|
+
continue
|
|
278
|
+
print("atom", atom)
|
|
279
|
+
ref_coords_for_atom = ref_coords[atom]
|
|
280
|
+
coords_to_match_for_atom = coords_to_match[atom]
|
|
281
|
+
# Pairwise distances between two collections
|
|
282
|
+
# Atoms of ref_geom are along the rows, atoms of geom_to_match
|
|
283
|
+
# along the columns.
|
|
284
|
+
cd = cdist(ref_coords_for_atom, coords_to_match_for_atom)
|
|
285
|
+
print(cd)
|
|
286
|
+
# Hungarian method, row_inds are returned already sorted.
|
|
287
|
+
row_inds, col_inds = linear_sum_assignment(cd)
|
|
288
|
+
print("col_inds", col_inds)
|
|
289
|
+
old_inds = inds_to_match[atom]
|
|
290
|
+
new_inds = old_inds[col_inds]
|
|
291
|
+
print("old_inds", old_inds)
|
|
292
|
+
print("new_inds", new_inds)
|
|
293
|
+
new_coords_for_atom = coords_to_match_for_atom[new_inds]
|
|
294
|
+
# print(ref_coords_for_atom)
|
|
295
|
+
# print(new_coords_for_atom)
|
|
296
|
+
# print(ref_coords_for_atom-new_coords_for_atom)
|
|
297
|
+
# Update coordinates
|
|
298
|
+
print("old coords")
|
|
299
|
+
c3d = geom_to_match.coords3d
|
|
300
|
+
print(c3d)
|
|
301
|
+
# Modify the coordinates directly
|
|
302
|
+
c3d[old_inds] = new_coords_for_atom
|
|
303
|
+
# coords_to_match[atom] = coords_to_match_for_atom[new_inds]
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def check_for_end_sign(check_user=True, cwd=".", add_signs=None):
|
|
307
|
+
if add_signs is None:
|
|
308
|
+
add_signs = tuple()
|
|
309
|
+
elif type(add_signs) == str:
|
|
310
|
+
add_signs = (add_signs,)
|
|
311
|
+
else:
|
|
312
|
+
add_signs = tuple(add_signs)
|
|
313
|
+
signs = (
|
|
314
|
+
"stop",
|
|
315
|
+
"converged",
|
|
316
|
+
"exit",
|
|
317
|
+
) + add_signs
|
|
318
|
+
sign_found = False
|
|
319
|
+
cur_user = getpass.getuser()
|
|
320
|
+
cwd = Path(cwd)
|
|
321
|
+
|
|
322
|
+
def sign_owner(path):
|
|
323
|
+
if not check_user:
|
|
324
|
+
return True
|
|
325
|
+
else:
|
|
326
|
+
return path.owner() == cur_user
|
|
327
|
+
|
|
328
|
+
for sign in signs:
|
|
329
|
+
sign_path = cwd / sign
|
|
330
|
+
if sign_path.exists() and sign_owner(sign_path):
|
|
331
|
+
print(f"Found sign '{sign}'. Ending run.")
|
|
332
|
+
os.remove(sign_path)
|
|
333
|
+
sign_found = sign
|
|
334
|
+
|
|
335
|
+
if sign == "exit":
|
|
336
|
+
sys.exit()
|
|
337
|
+
return sign_found
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def index_array_from_overlaps(overlaps, axis=1):
|
|
341
|
+
"""It is assumed that the overlaps between two points with indices
|
|
342
|
+
i and j with (j > i) are computed and that i changes along the first
|
|
343
|
+
axis (axis=0) and j changes along the second axis (axis=1).
|
|
344
|
+
|
|
345
|
+
So the first row of the overlap matrix (overlaps[0]) should contain
|
|
346
|
+
the overlaps between state 0 at index i and all states at index j.
|
|
347
|
+
|
|
348
|
+
argmax along axis 1 returns the indices of the most overlapping states
|
|
349
|
+
at index j with the states at index i, given by the item index in the
|
|
350
|
+
indices array. E.g.:
|
|
351
|
+
[0 1 3 2] indicates a root flip in a system with four states when
|
|
352
|
+
going from index i to index j. Root 2 at i became root 3 at j and
|
|
353
|
+
vice versa.
|
|
354
|
+
"""
|
|
355
|
+
# indices = np.argmax(overlaps**2, axis=1)
|
|
356
|
+
indices = np.argmax(np.abs(overlaps), axis=1)
|
|
357
|
+
return indices
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def np_print(func, precision=2, suppress=True, linewidth=120):
|
|
361
|
+
def wrapped(*args, **kwargs):
|
|
362
|
+
org_print_dict = dict(np.get_printoptions())
|
|
363
|
+
np.set_printoptions(suppress=suppress, precision=precision, linewidth=linewidth)
|
|
364
|
+
result = func(*args, **kwargs)
|
|
365
|
+
np.set_printoptions(**org_print_dict)
|
|
366
|
+
return result
|
|
367
|
+
|
|
368
|
+
return wrapped
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def confirm_input(message):
|
|
372
|
+
full_message = message + " (yes/no)\n"
|
|
373
|
+
inp = input(full_message)
|
|
374
|
+
return inp == "yes"
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def get_geom_getter(ref_geom, calc_setter):
|
|
378
|
+
def geom_from_coords(coords):
|
|
379
|
+
new_geom = ref_geom.copy()
|
|
380
|
+
new_geom.coords = coords
|
|
381
|
+
new_geom.set_calculator(calc_setter())
|
|
382
|
+
return new_geom
|
|
383
|
+
|
|
384
|
+
return geom_from_coords
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def get_coords_diffs(coords, align=False, normalize=True):
|
|
388
|
+
if align:
|
|
389
|
+
coords = align_coords(coords)
|
|
390
|
+
cds = [
|
|
391
|
+
0,
|
|
392
|
+
]
|
|
393
|
+
for i in range(len(coords) - 1):
|
|
394
|
+
diff = np.linalg.norm(coords[i + 1] - coords[i])
|
|
395
|
+
cds.append(diff)
|
|
396
|
+
cds = np.cumsum(cds)
|
|
397
|
+
if normalize:
|
|
398
|
+
cds /= cds.max()
|
|
399
|
+
return cds
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def pick_image_inds(cart_coords, images: int):
|
|
403
|
+
"""Pick approx. evenly distributed images from given Cartesian coordinates."""
|
|
404
|
+
cds = get_coords_diffs(cart_coords, align=True)
|
|
405
|
+
target_cds = iter(np.linspace(0, 1, num=images))
|
|
406
|
+
target_cd = next(target_cds)
|
|
407
|
+
image_inds = list()
|
|
408
|
+
for i, cd in enumerate(cds):
|
|
409
|
+
if len(image_inds) == (images - 1):
|
|
410
|
+
break
|
|
411
|
+
if cd >= target_cd:
|
|
412
|
+
image_inds.append(i)
|
|
413
|
+
target_cd = next(target_cds)
|
|
414
|
+
|
|
415
|
+
image_inds.append(len(cart_coords) - 1)
|
|
416
|
+
return image_inds
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def shake_coords(coords, scale=0.1, seed=None):
|
|
420
|
+
if seed:
|
|
421
|
+
np.random.seed(seed)
|
|
422
|
+
offset = np.random.normal(scale=scale, size=coords.size)
|
|
423
|
+
return coords + offset
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def norm_max_rms(arr):
|
|
427
|
+
norm = np.linalg.norm(arr)
|
|
428
|
+
max_ = np.max(np.abs(arr))
|
|
429
|
+
rms_ = rms(arr)
|
|
430
|
+
return norm, max_, rms_
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def complete_fragments(atoms, fragments):
|
|
434
|
+
all_inds = set(range(len(atoms)))
|
|
435
|
+
frag_inds = set(it.chain(*fragments))
|
|
436
|
+
rest_inds = all_inds - frag_inds
|
|
437
|
+
|
|
438
|
+
assert len(frag_inds) + len(rest_inds) == len(atoms)
|
|
439
|
+
assert frag_inds & rest_inds == set()
|
|
440
|
+
|
|
441
|
+
if rest_inds:
|
|
442
|
+
fragments.append(tuple(rest_inds))
|
|
443
|
+
fragments = tuple([tuple(frag) for frag in fragments])
|
|
444
|
+
|
|
445
|
+
return fragments
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
FinalHessianResult = namedtuple(
|
|
449
|
+
"FinalHessianResult",
|
|
450
|
+
"neg_eigvals eigvals nus imag_fns thermo",
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def do_final_hessian(
|
|
455
|
+
geom,
|
|
456
|
+
save_hessian=True,
|
|
457
|
+
write_imag_modes=False,
|
|
458
|
+
is_ts=False,
|
|
459
|
+
prefix="",
|
|
460
|
+
T=T_DEFAULT,
|
|
461
|
+
p=p_DEFAULT,
|
|
462
|
+
ev_thresh=-1e-6,
|
|
463
|
+
print_thermo=False,
|
|
464
|
+
out_dir=None,
|
|
465
|
+
):
|
|
466
|
+
if out_dir is None:
|
|
467
|
+
out_dir = "."
|
|
468
|
+
out_dir = Path(out_dir)
|
|
469
|
+
|
|
470
|
+
print(highlight_text("Hessian at final geometry", level=1))
|
|
471
|
+
print()
|
|
472
|
+
|
|
473
|
+
report_isotopes(geom, "the_frequencies")
|
|
474
|
+
|
|
475
|
+
start = time.time()
|
|
476
|
+
print("... started Hessian calculation")
|
|
477
|
+
hessian = geom.cart_hessian
|
|
478
|
+
dur = time.time() - start
|
|
479
|
+
print(f"... calculation took {dur/60:.2f} min")
|
|
480
|
+
print("... mass-weighing cartesian hessian")
|
|
481
|
+
mw_hessian = geom.mass_weigh_hessian(hessian)
|
|
482
|
+
print("... doing Eckart-projection")
|
|
483
|
+
proj_hessian = geom.eckart_projection(mw_hessian)
|
|
484
|
+
if isinstance(proj_hessian, torch.Tensor):
|
|
485
|
+
eigvals, _ = torch.linalg.eigh(proj_hessian)
|
|
486
|
+
eigvals = eigvals.cpu().numpy()
|
|
487
|
+
else:
|
|
488
|
+
eigvals, _ = np.linalg.eigh(proj_hessian)
|
|
489
|
+
|
|
490
|
+
neg_inds = eigvals < ev_thresh
|
|
491
|
+
neg_eigvals = eigvals[neg_inds]
|
|
492
|
+
neg_num = sum(neg_inds)
|
|
493
|
+
eigval_str = array2string(eigvals[:10], precision=4)
|
|
494
|
+
print()
|
|
495
|
+
print("First 10 eigenvalues", eigval_str)
|
|
496
|
+
if neg_num > 0:
|
|
497
|
+
wavenumbers = eigval_to_wavenumber(neg_eigvals)
|
|
498
|
+
wavenum_str = array2string(wavenumbers, precision=2)
|
|
499
|
+
print("Imaginary frequencies:", wavenum_str, "cm⁻¹")
|
|
500
|
+
|
|
501
|
+
if prefix:
|
|
502
|
+
prefix = f"{prefix}_"
|
|
503
|
+
|
|
504
|
+
# Dump HDF Hessian
|
|
505
|
+
if save_hessian:
|
|
506
|
+
final_h5_hessian_fn = prefix + "final_hessian.h5"
|
|
507
|
+
save_h5_hessian(out_dir / final_h5_hessian_fn, geom)
|
|
508
|
+
print(f"Wrote Hessian data HD5 file '{final_h5_hessian_fn}'.")
|
|
509
|
+
|
|
510
|
+
imag_fns = list()
|
|
511
|
+
if write_imag_modes:
|
|
512
|
+
imag_modes = imag_modes_from_geom(geom)
|
|
513
|
+
for i, imag_mode in enumerate(imag_modes):
|
|
514
|
+
trj_fn = out_dir / (prefix + f"imaginary_mode_{i:03d}_trj.xyz")
|
|
515
|
+
imag_fns.append(trj_fn)
|
|
516
|
+
with open(trj_fn, "w") as handle:
|
|
517
|
+
handle.write(imag_mode.trj_str)
|
|
518
|
+
print(
|
|
519
|
+
f"Wrote imaginary mode with ṽ={imag_mode.nu: >10.2f} cm⁻¹ to '{trj_fn}'"
|
|
520
|
+
)
|
|
521
|
+
print()
|
|
522
|
+
|
|
523
|
+
thermo = None
|
|
524
|
+
if can_thermoanalysis:
|
|
525
|
+
thermo = geom.get_thermoanalysis(geom, T=T, p=p)
|
|
526
|
+
if print_thermo:
|
|
527
|
+
print_thermoanalysis(thermo, geom=geom, is_ts=is_ts)
|
|
528
|
+
|
|
529
|
+
res = FinalHessianResult(
|
|
530
|
+
neg_eigvals=neg_eigvals,
|
|
531
|
+
eigvals=eigvals,
|
|
532
|
+
nus=eigval_to_wavenumber(eigvals),
|
|
533
|
+
imag_fns=imag_fns,
|
|
534
|
+
thermo=thermo,
|
|
535
|
+
)
|
|
536
|
+
return res
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def print_barrier(ref_energy, comp_energy, ref_str, comp_str):
|
|
540
|
+
barrier = (ref_energy - comp_energy) * AU2KJPERMOL
|
|
541
|
+
print(f"Barrier between {ref_str} and {comp_str}: {barrier:.1f} kJ mol⁻¹")
|
|
542
|
+
return barrier
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def get_tangent_trj_str(atoms, coords, tangent, comment=None, points=10, displ=None):
|
|
546
|
+
if displ is None:
|
|
547
|
+
# Linear equation. Will give displ~3 for 30 atoms and
|
|
548
|
+
# displ ~ 1 for 3 atoms.
|
|
549
|
+
# displ = 2/27 * len(atoms) + 0.78
|
|
550
|
+
|
|
551
|
+
# Logarithmic function f(x) = a*log(x) + b
|
|
552
|
+
# f(3) = ~1 and (f30) = ~2 with a = 0.43429 and b = 0.52288
|
|
553
|
+
# I guess this works better, because only some atoms move, even in bigger
|
|
554
|
+
# systems and the log function converges against a certain value, whereas
|
|
555
|
+
# the linear function just keeps growing.
|
|
556
|
+
displ = 0.43429 * log(len(atoms)) + 0.52288
|
|
557
|
+
step_sizes = np.linspace(-displ, displ, 2 * points + 1)
|
|
558
|
+
steps = step_sizes[:, None] * tangent
|
|
559
|
+
trj_coords = coords[None, :] + steps
|
|
560
|
+
trj_coords = trj_coords.reshape(step_sizes.size, -1, 3) / ANG2BOHR
|
|
561
|
+
|
|
562
|
+
comments = None
|
|
563
|
+
if comment:
|
|
564
|
+
comments = [comment] * step_sizes.size
|
|
565
|
+
trj_str = make_trj_str(atoms, trj_coords, comments=comments)
|
|
566
|
+
|
|
567
|
+
return trj_str
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def imag_modes_from_geom(geom, freq_thresh=-10, points=10, displ=None):
|
|
571
|
+
NormalMode = namedtuple("NormalMode", "nu mode trj_str")
|
|
572
|
+
|
|
573
|
+
# We don't want to do start any calculation here, so we directly access
|
|
574
|
+
# the attribute underlying the geom.hessian property.
|
|
575
|
+
hessian = geom._hessian
|
|
576
|
+
nus, _, _, cart_displs = geom.get_normal_modes(hessian)
|
|
577
|
+
below_thresh = nus < freq_thresh
|
|
578
|
+
|
|
579
|
+
imag_modes = list()
|
|
580
|
+
for nu, eigvec in zip(nus[below_thresh], cart_displs[:, below_thresh].T):
|
|
581
|
+
eigvec = eigvec.cpu().numpy() if isinstance(eigvec, torch.Tensor) else eigvec
|
|
582
|
+
comment = f"{nu:.2f} cm^-1"
|
|
583
|
+
trj_str = get_tangent_trj_str(
|
|
584
|
+
geom.atoms,
|
|
585
|
+
geom.cart_coords,
|
|
586
|
+
eigvec,
|
|
587
|
+
comment=comment,
|
|
588
|
+
points=points,
|
|
589
|
+
displ=displ,
|
|
590
|
+
)
|
|
591
|
+
imag_modes.append(
|
|
592
|
+
NormalMode(
|
|
593
|
+
nu=nu,
|
|
594
|
+
mode=eigvec,
|
|
595
|
+
trj_str=trj_str,
|
|
596
|
+
)
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
return imag_modes
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def simple_peaks(data):
|
|
603
|
+
peaks = list()
|
|
604
|
+
for i, cur in enumerate(data[1:-1], 1):
|
|
605
|
+
prev_ = data[i - 1]
|
|
606
|
+
next_ = data[i + 1]
|
|
607
|
+
if prev_ < cur > next_:
|
|
608
|
+
peaks.append(i)
|
|
609
|
+
peaks = np.array(peaks, dtype=int)
|
|
610
|
+
vals = np.array(data)[peaks]
|
|
611
|
+
return peaks, vals
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def array2string(arr, precision=None, suppress_small=None):
|
|
615
|
+
"""Similar to np.array2string, but able to handle torch tensors.
|
|
616
|
+
Only implement used parameters in pysisyphus."""
|
|
617
|
+
if isinstance(arr, torch.Tensor):
|
|
618
|
+
arr = arr.cpu().numpy()
|
|
619
|
+
return np.array2string(
|
|
620
|
+
arr,
|
|
621
|
+
precision=precision,
|
|
622
|
+
suppress_small=suppress_small,
|
|
623
|
+
)
|