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,480 @@
|
|
|
1
|
+
# [1] https://doi.org/10.1002/ange.201914943
|
|
2
|
+
# Heavy-Atom Tunneling in Organic Reactions
|
|
3
|
+
# Castro, Karney, 2020
|
|
4
|
+
# [2] https://doi.org/10.1002/wcms.1165
|
|
5
|
+
# Theory and simulation of atom tunneling in chemical reactions
|
|
6
|
+
# Kästner, 2014
|
|
7
|
+
# [3] https://doi.org/10.1002/qua.25686
|
|
8
|
+
# Eyringpy
|
|
9
|
+
# Dzib, Merino et al, 2018
|
|
10
|
+
# [4] https://pubs.acs.org/doi/pdf/10.1021/j100809a040
|
|
11
|
+
# TUNNELLING CORRECTIONS FOR UNSYMMETRICAL ECKART POTENTIAL ENERGY BARRIERS
|
|
12
|
+
# Johnston, Heicklen, 1961
|
|
13
|
+
# [5] https://dx.doi.org/10.6028%2Fjres.086.014
|
|
14
|
+
# A Method of Calculating Tunneling Corrections For Eckart Potential Barriers
|
|
15
|
+
# Brown, 1981
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from math import sin
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import jinja2
|
|
22
|
+
import numpy as np
|
|
23
|
+
import numpy.typing as npt
|
|
24
|
+
import scipy.integrate as integrate
|
|
25
|
+
|
|
26
|
+
from pysisyphus.constants import AU2KJPERMOL, AU2SEC, C, KB, KBAU, PLANCK, PLANCKAU
|
|
27
|
+
from pysisyphus.io import geom_from_hessian
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ReactionRates:
|
|
32
|
+
from_: str
|
|
33
|
+
barrier: float # in E_h
|
|
34
|
+
barrier_si: float # in kJ mol⁻¹
|
|
35
|
+
temperature: float # in K
|
|
36
|
+
imag_wavenumber: float # in cm⁻¹
|
|
37
|
+
imag_frequency: float # in s⁻¹
|
|
38
|
+
rate_eyring: float # in s⁻¹
|
|
39
|
+
kappa_eyring: float
|
|
40
|
+
rate_wigner: Optional[float] = None # in s⁻¹
|
|
41
|
+
kappa_wigner: Optional[float] = None
|
|
42
|
+
rate_bell: Optional[float] = None # in s⁻¹
|
|
43
|
+
kappa_bell: Optional[float] = None
|
|
44
|
+
rate_eckart: Optional[float] = None # in s⁻¹
|
|
45
|
+
kappa_eckart: Optional[float] = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
RX_RATES_TPL = """
|
|
49
|
+
From: {{ "{: >18s}".format(rxr.from_) }} to TS
|
|
50
|
+
Barrier: {{ "{: >18.6f}".format(rxr.barrier) }} E_h ( {{ rxr.barrier_si|f2 }} kJ mol⁻¹)
|
|
51
|
+
Temperature: {{ "{: >18.2f}".format(rxr.temperature) }} K
|
|
52
|
+
Transition vector:
|
|
53
|
+
Wavenumber: {{ "{: >18.2f}".format(rxr.imag_wavenumber) }} cm⁻¹
|
|
54
|
+
Frequency: {{ "{: >18.6e}".format(rxr.imag_frequency) }} s⁻¹
|
|
55
|
+
|
|
56
|
+
Type kappa rate / s⁻¹ rate / h⁻¹ comment
|
|
57
|
+
------- ------------ -------------- -------------- ------------------
|
|
58
|
+
{{ rate_line("Eyring", rxr.kappa_eyring, rxr.rate_eyring) }}
|
|
59
|
+
{%- if rxr.rate_wigner %}
|
|
60
|
+
{{ rate_line("Wigner", rxr.kappa_wigner, rxr.rate_wigner, "1d tunnel corr.") }}
|
|
61
|
+
{%- endif %}
|
|
62
|
+
{%- if rxr.rate_bell %}
|
|
63
|
+
{{ rate_line("Bell", rxr.kappa_bell, rxr.rate_bell, "1d tunnel corr.") }}
|
|
64
|
+
{%- endif %}
|
|
65
|
+
{%- if rxr.rate_eckart %}
|
|
66
|
+
{{ rate_line("Eckart", rxr.kappa_eckart, rxr.rate_eckart, "1d tunnel corr.") }}
|
|
67
|
+
{%- endif %}
|
|
68
|
+
------- ------------ -------------- -------------- ------------------
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def render_rx_rates(rx_rates: ReactionRates) -> str:
|
|
73
|
+
def rate_line(name, kappa, rate, comment=""):
|
|
74
|
+
rate_h = rate * 3600
|
|
75
|
+
return f"{name: <12s} {kappa: >8.4f} {rate: >12.8e} {rate_h: >12.8e} {comment: >18s}"
|
|
76
|
+
|
|
77
|
+
env = jinja2.Environment()
|
|
78
|
+
env.filters["f2"] = lambda _: f"{_:.2f}"
|
|
79
|
+
env.filters["f4"] = lambda _: f"{_:.4f}"
|
|
80
|
+
env.filters["f6"] = lambda _: f"{_:.6f}"
|
|
81
|
+
env.filters["e8"] = lambda _: f"{_: >16.8e}"
|
|
82
|
+
|
|
83
|
+
tpl = env.from_string(RX_RATES_TPL)
|
|
84
|
+
|
|
85
|
+
rendered = tpl.render(rxr=rx_rates, rate_line=rate_line)
|
|
86
|
+
return rendered
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def eyring_rate(
|
|
90
|
+
barrier_height: float,
|
|
91
|
+
temperature: npt.ArrayLike,
|
|
92
|
+
trans_coeff: float = 1.0,
|
|
93
|
+
) -> npt.NDArray[np.float64]:
|
|
94
|
+
"""Rate constant in 1/s from the Eyring equation.
|
|
95
|
+
|
|
96
|
+
See https://pubs.acs.org/doi/10.1021/acs.organomet.8b00456
|
|
97
|
+
"Reaction Rates and Barriers" on p. 3234 and eq. (8).
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
barrier_height
|
|
102
|
+
Barrier height (energy, enthalpy, gibbs energy, ...) in Hartree.
|
|
103
|
+
temperature
|
|
104
|
+
Temperature in Kelvin.
|
|
105
|
+
trans_coeff
|
|
106
|
+
Unitless transmission coefficient, e.g., obtained from Wigner or Eckart
|
|
107
|
+
correction.
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
rate
|
|
112
|
+
Eyring reaction rate in 1/s.
|
|
113
|
+
"""
|
|
114
|
+
temperature = np.array(temperature, dtype=float)
|
|
115
|
+
prefactor = trans_coeff * KB * temperature / PLANCK
|
|
116
|
+
rate = prefactor * np.exp(-barrier_height / (KBAU * temperature))
|
|
117
|
+
return rate
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def harmonic_tst_rate(
|
|
121
|
+
barrier_height: float,
|
|
122
|
+
temperature: float,
|
|
123
|
+
rs_part_func: float,
|
|
124
|
+
ts_part_func: float,
|
|
125
|
+
trans_coeff: float = 1.0,
|
|
126
|
+
) -> float:
|
|
127
|
+
"""Rate constant in 1/s from harmonic TST.
|
|
128
|
+
|
|
129
|
+
See http://dx.doi.org/10.18419/opus-9841, chapter 5. Contrary to
|
|
130
|
+
the Eyring rate this function does only takes a scalar temperature as the
|
|
131
|
+
partition functions are also functions of the temperature and would
|
|
132
|
+
have to be recalculated for different temperatures.
|
|
133
|
+
|
|
134
|
+
A possible extension would be to also support multiple rs/ts partition functions,
|
|
135
|
+
one for each temperature.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
barrier_height
|
|
140
|
+
Barrier height (energy, enthalpy, gibbs energy, ...) in Hartree.
|
|
141
|
+
rs_part_func
|
|
142
|
+
Partition function of the reactant state.
|
|
143
|
+
ts_part_func
|
|
144
|
+
Partition function of the transition state.
|
|
145
|
+
temperature
|
|
146
|
+
Temperature in Kelvin.
|
|
147
|
+
trans_coeff
|
|
148
|
+
Unitless transmission coefficient, e.g., obtained from Wigner or Eckart
|
|
149
|
+
correction.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
rate
|
|
154
|
+
HTST reaction rate in 1/s.
|
|
155
|
+
"""
|
|
156
|
+
rate_eyring = eyring_rate(barrier_height, temperature, trans_coeff)
|
|
157
|
+
rate = ts_part_func / rs_part_func * rate_eyring
|
|
158
|
+
return rate
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def wigner_corr(
|
|
162
|
+
temperature: float,
|
|
163
|
+
imag_frequency: float,
|
|
164
|
+
) -> float:
|
|
165
|
+
"""Tunneling correction according to Wigner.
|
|
166
|
+
|
|
167
|
+
See https://doi.org/10.1002/qua.25686 eq. (12) and
|
|
168
|
+
https://doi.org/10.1007/978-3-642-59033-7_9 for the original publication.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
temperature
|
|
173
|
+
Temperature in Kelvin.
|
|
174
|
+
imag_frequency
|
|
175
|
+
Imaginary frequency in 1/s.
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
kappa
|
|
180
|
+
Unitless tunneling correction according to Wigner.
|
|
181
|
+
"""
|
|
182
|
+
kappa = 1 + 1 / 24 * (PLANCK * abs(imag_frequency) / (KB * temperature)) ** 2
|
|
183
|
+
return kappa
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def bell_corr(
|
|
187
|
+
temperature: float,
|
|
188
|
+
imag_frequency: float,
|
|
189
|
+
) -> float:
|
|
190
|
+
"""Tunneling correction according to Bell.
|
|
191
|
+
|
|
192
|
+
See https://onlinelibrary.wiley.com/doi/10.1002/anie.201708489 eq. (1) and
|
|
193
|
+
eq. (2).
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
temperature
|
|
198
|
+
Temperature in Kelvin.
|
|
199
|
+
imag_frequency
|
|
200
|
+
Imaginary frequency in 1/s.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
kappa
|
|
205
|
+
Unitless tunneling correction according to Bell. Negative kappas are
|
|
206
|
+
meaningless.
|
|
207
|
+
"""
|
|
208
|
+
u = PLANCK * abs(imag_frequency) / (KB * temperature)
|
|
209
|
+
kappa = (u / 2) / sin(u / 2)
|
|
210
|
+
return kappa
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def eckart_corr(
|
|
214
|
+
fw_barrier_height: float,
|
|
215
|
+
bw_barrier_height: float,
|
|
216
|
+
temperature: float,
|
|
217
|
+
imag_frequency: float,
|
|
218
|
+
) -> float:
|
|
219
|
+
"""Tunneling correction according to Eckart.
|
|
220
|
+
|
|
221
|
+
See [3], [4] and [5]. The correction should be independent of the order
|
|
222
|
+
fw_barrier_height/bw_barrier_height.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
fw_barrier_height
|
|
227
|
+
Barrier height in forward direction in Hartree.
|
|
228
|
+
bw_barrier_height
|
|
229
|
+
Barrier height in backward direction in Hartree.
|
|
230
|
+
temperature
|
|
231
|
+
Temperature in Kelvin.
|
|
232
|
+
imag_frequency
|
|
233
|
+
Frequency in 1/s of the imaginary mode at the TS.
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
kappa
|
|
238
|
+
Unitless tunneling correction according to Eckart.
|
|
239
|
+
"""
|
|
240
|
+
kBT = KBAU * temperature # Hartree
|
|
241
|
+
two_pi = 2 * np.pi
|
|
242
|
+
|
|
243
|
+
imag_frequency = abs(imag_frequency) * AU2SEC # Convert from 1/s to 1/au
|
|
244
|
+
hnu = PLANCKAU * imag_frequency
|
|
245
|
+
u = hnu / kBT # unitless
|
|
246
|
+
v1 = fw_barrier_height / kBT
|
|
247
|
+
v2 = bw_barrier_height / kBT
|
|
248
|
+
alpha1, alpha2 = [
|
|
249
|
+
two_pi * barrier / hnu for barrier in (fw_barrier_height, bw_barrier_height)
|
|
250
|
+
]
|
|
251
|
+
quot = 1 / (1 / np.sqrt(alpha1) + 1 / np.sqrt(alpha2))
|
|
252
|
+
d = 1 / two_pi * np.sqrt(np.abs(4 * alpha1 * alpha2 - np.pi ** 2))
|
|
253
|
+
cosh_d = np.cosh(two_pi * np.abs(d))
|
|
254
|
+
|
|
255
|
+
def eps(E):
|
|
256
|
+
return (E - fw_barrier_height) / kBT
|
|
257
|
+
|
|
258
|
+
def ai(E, vi):
|
|
259
|
+
epsilon = eps(E)
|
|
260
|
+
return np.sqrt(2 * (epsilon + vi) / (np.pi * u)) * quot
|
|
261
|
+
|
|
262
|
+
def _a1(E):
|
|
263
|
+
return ai(E, vi=v1)
|
|
264
|
+
|
|
265
|
+
def _a2(E):
|
|
266
|
+
return ai(E, vi=v2)
|
|
267
|
+
|
|
268
|
+
def eckart_probability(E):
|
|
269
|
+
a1 = _a1(E)
|
|
270
|
+
a2 = _a2(E)
|
|
271
|
+
plus = np.cosh(two_pi * (a1 + a2))
|
|
272
|
+
minus = np.cosh(two_pi * (a1 - a2))
|
|
273
|
+
return (plus - minus) / (plus + cosh_d)
|
|
274
|
+
|
|
275
|
+
def eckart_func(E):
|
|
276
|
+
epsilon = eps(E)
|
|
277
|
+
P = eckart_probability(E)
|
|
278
|
+
return P * np.exp(-epsilon)
|
|
279
|
+
|
|
280
|
+
# Integration limits
|
|
281
|
+
E_low = max(0, fw_barrier_height - bw_barrier_height)
|
|
282
|
+
E_max = 1 # 1 Hartree max
|
|
283
|
+
kappa, _ = integrate.quad(eckart_func, E_low, E_max, limit=250)
|
|
284
|
+
# Make kappa unitless again by dividing by kBT, as we integrated over the energy.
|
|
285
|
+
kappa /= kBT
|
|
286
|
+
return kappa
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def tunl(alph1: float, alph2: float, U: float, strict: bool = False):
|
|
290
|
+
"""Eckart correction factor for rate constants according to Brown.
|
|
291
|
+
|
|
292
|
+
Python adaption of the TUNL subroutine in 4. Appendix of [5].
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
alph1
|
|
297
|
+
Unitless barrier height descriptor. 2π V1 / (h nu*); see (2) in [5].
|
|
298
|
+
alph2
|
|
299
|
+
Unitless barrier heigth descriptor. 2π V2 / (h nu*); see (2) in [5].
|
|
300
|
+
u
|
|
301
|
+
Unitless curvature descriptor. h nu* / kT; see (2) in [5].
|
|
302
|
+
strict
|
|
303
|
+
If enabled, arguments are bound checked. Will raise AssertionError if
|
|
304
|
+
they are out of bonds. TUNL was found to yield accurate results when
|
|
305
|
+
the arguments are within bounds.
|
|
306
|
+
|
|
307
|
+
Returns
|
|
308
|
+
-------
|
|
309
|
+
G
|
|
310
|
+
Unitless tunneling correction according to Eckart.
|
|
311
|
+
"""
|
|
312
|
+
if strict:
|
|
313
|
+
alphs = np.array((alph1, alph2))
|
|
314
|
+
assert (0.5 <= alphs).all() and (alphs <= 20).all() and 0 < U <= 16
|
|
315
|
+
if (alphs >= 8).all():
|
|
316
|
+
assert U <= 12
|
|
317
|
+
if (alphs >= 16).all():
|
|
318
|
+
assert U <= 10
|
|
319
|
+
|
|
320
|
+
# Quadrature arguments
|
|
321
|
+
x = np.array((-0.9324695, -0.6612094, -0.2386192, 0.2386192, 0.6612094, 0.9324695))
|
|
322
|
+
w = np.array((0.1713245, 0.3607616, 0.4679139, 0.4679139, 0.3607616, 0.1713245))
|
|
323
|
+
|
|
324
|
+
pi = np.pi
|
|
325
|
+
upi2 = U / (2 * pi)
|
|
326
|
+
|
|
327
|
+
C = 0.125 * pi * U * (1 / np.sqrt(alph1) + 1.0 / np.sqrt(alph2)) ** 2
|
|
328
|
+
v1 = upi2 * alph1
|
|
329
|
+
v2 = upi2 * alph2
|
|
330
|
+
D = 4 * alph1 * alph2 - pi ** 2
|
|
331
|
+
DF = np.cosh(np.sqrt(D)) if D >= 0 else np.cos(np.sqrt(-D))
|
|
332
|
+
|
|
333
|
+
ez = -v1 if (v2 >= v1) else -v2
|
|
334
|
+
eb = min(
|
|
335
|
+
C * (np.log(2.0 * (1.0 + DF) / 0.014) / (2 * pi)) ** 2 - (v1 + v2) / 2, 3.2
|
|
336
|
+
)
|
|
337
|
+
em = (eb - ez) / 2
|
|
338
|
+
ep = (eb + ez) / 2
|
|
339
|
+
|
|
340
|
+
# Original loop
|
|
341
|
+
E = em * x + ep
|
|
342
|
+
A1 = pi * np.sqrt((E + v1) / C)
|
|
343
|
+
A2 = pi * np.sqrt((E + v2) / C)
|
|
344
|
+
fm = np.cosh(A1 - A2)
|
|
345
|
+
fp = np.cosh(A1 + A2)
|
|
346
|
+
G = (w * np.exp(-E) * (fp - fm) / (fp + DF)).sum()
|
|
347
|
+
# Outside the loop
|
|
348
|
+
G = em * G + np.exp(-eb)
|
|
349
|
+
return G
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def eckart_corr_brown(
|
|
353
|
+
fw_barrier_height: float,
|
|
354
|
+
bw_barrier_height: float,
|
|
355
|
+
temperature: float,
|
|
356
|
+
imag_frequency: float,
|
|
357
|
+
) -> float:
|
|
358
|
+
"""Tunneling correction according to Eckart.
|
|
359
|
+
|
|
360
|
+
Wrapper for the TUNL subroutine as given in the appendix of [5].
|
|
361
|
+
|
|
362
|
+
Parameters
|
|
363
|
+
----------
|
|
364
|
+
fw_barrier_height
|
|
365
|
+
Barrier height in forward direction in Hartree.
|
|
366
|
+
bw_barrier_height
|
|
367
|
+
Barrier height in backward direction in Hartree.
|
|
368
|
+
temperature
|
|
369
|
+
Temperature in Kelvin.
|
|
370
|
+
imag_frequency
|
|
371
|
+
Frequency in 1/s of the imaginary mode at the TS.
|
|
372
|
+
|
|
373
|
+
Returns
|
|
374
|
+
-------
|
|
375
|
+
kappa
|
|
376
|
+
Unitless tunneling correction according to Eckart.
|
|
377
|
+
"""
|
|
378
|
+
kBT = KBAU * temperature
|
|
379
|
+
hnu = PLANCKAU * imag_frequency * AU2SEC
|
|
380
|
+
u = hnu / kBT
|
|
381
|
+
|
|
382
|
+
def alpha(barr):
|
|
383
|
+
return 2 * np.pi * barr / hnu
|
|
384
|
+
|
|
385
|
+
alpha1 = alpha(fw_barrier_height)
|
|
386
|
+
alpha2 = alpha(bw_barrier_height)
|
|
387
|
+
kappa = tunl(alpha1, alpha2, u)
|
|
388
|
+
return kappa
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def get_rates(temperature, reactant_thermos, ts_thermo, product_thermos=None):
|
|
392
|
+
G_TS = ts_thermo.G
|
|
393
|
+
imag_wavenumber = ts_thermo.org_wavenumbers[0]
|
|
394
|
+
assert imag_wavenumber < 0.0
|
|
395
|
+
imag_frequency = (
|
|
396
|
+
imag_wavenumber * C * 100
|
|
397
|
+
) # Convert from wavenumbers (1/cm) to (1/s)
|
|
398
|
+
kappa_wigner = wigner_corr(temperature, imag_frequency)
|
|
399
|
+
kappa_bell = bell_corr(temperature, imag_frequency)
|
|
400
|
+
|
|
401
|
+
Gs_reactant = [thermo.G for thermo in reactant_thermos]
|
|
402
|
+
G_reactant = sum(Gs_reactant)
|
|
403
|
+
fw_barrier_height = G_TS - G_reactant
|
|
404
|
+
fw_rate_eyring = eyring_rate(fw_barrier_height, temperature)
|
|
405
|
+
|
|
406
|
+
if product_thermos:
|
|
407
|
+
Gs_product = [thermo.G for thermo in product_thermos]
|
|
408
|
+
G_product = sum(Gs_product)
|
|
409
|
+
bw_barrier_height = G_TS - G_product
|
|
410
|
+
bw_rate_eyring = eyring_rate(bw_barrier_height, temperature)
|
|
411
|
+
kappa_eckart = eckart_corr(
|
|
412
|
+
fw_barrier_height, bw_barrier_height, temperature, imag_frequency
|
|
413
|
+
)
|
|
414
|
+
else:
|
|
415
|
+
kappa_eckart = None
|
|
416
|
+
|
|
417
|
+
def make_rx_rates(from_, barrier, rate_eyring, kappa_eyring=1.0):
|
|
418
|
+
kwargs = {}
|
|
419
|
+
if kappa_wigner and not np.isnan(kappa_wigner):
|
|
420
|
+
kwargs.update(
|
|
421
|
+
{
|
|
422
|
+
"kappa_wigner": kappa_wigner,
|
|
423
|
+
"rate_wigner": kappa_wigner * rate_eyring,
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
if kappa_bell and kappa_bell > 0.0:
|
|
427
|
+
kwargs.update(
|
|
428
|
+
{
|
|
429
|
+
"kappa_bell": kappa_bell,
|
|
430
|
+
"rate_bell": kappa_bell * rate_eyring,
|
|
431
|
+
}
|
|
432
|
+
)
|
|
433
|
+
if kappa_eckart and not np.isnan(kappa_eckart):
|
|
434
|
+
kwargs.update(
|
|
435
|
+
{
|
|
436
|
+
"kappa_eckart": kappa_eckart,
|
|
437
|
+
"rate_eckart": kappa_eckart * rate_eyring,
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
rx_rates = ReactionRates(
|
|
441
|
+
from_=from_,
|
|
442
|
+
barrier=barrier,
|
|
443
|
+
barrier_si=barrier * AU2KJPERMOL,
|
|
444
|
+
temperature=temperature,
|
|
445
|
+
imag_wavenumber=imag_wavenumber,
|
|
446
|
+
imag_frequency=imag_frequency,
|
|
447
|
+
rate_eyring=rate_eyring,
|
|
448
|
+
kappa_eyring=kappa_eyring,
|
|
449
|
+
**kwargs,
|
|
450
|
+
)
|
|
451
|
+
return rx_rates
|
|
452
|
+
|
|
453
|
+
reactant_rx_rates = make_rx_rates("Reactant(s)", fw_barrier_height, fw_rate_eyring)
|
|
454
|
+
rx_rates = [
|
|
455
|
+
reactant_rx_rates,
|
|
456
|
+
]
|
|
457
|
+
if product_thermos:
|
|
458
|
+
product_rx_rates = make_rx_rates(
|
|
459
|
+
"Product(s)", bw_barrier_height, bw_rate_eyring
|
|
460
|
+
)
|
|
461
|
+
rx_rates.append(product_rx_rates)
|
|
462
|
+
return rx_rates
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def get_rates_for_geoms(temperature, reactant_geoms, ts_geom, product_geoms):
|
|
466
|
+
def get_thermos(geoms):
|
|
467
|
+
return [geom.get_thermoanalysis(T=temperature) for geom in geoms]
|
|
468
|
+
|
|
469
|
+
reactant_thermos = get_thermos(reactant_geoms)
|
|
470
|
+
ts_thermo = get_thermos((ts_geom,))[0]
|
|
471
|
+
product_thermos = get_thermos(product_geoms)
|
|
472
|
+
return get_rates(temperature, reactant_thermos, ts_thermo, product_thermos)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def get_rates_for_hessians(temperature, reactant_h5s, ts_h5, product_h5s):
|
|
476
|
+
reactant_geoms = [geom_from_hessian(h5) for h5 in reactant_h5s]
|
|
477
|
+
product_geoms = [geom_from_hessian(h5) for h5 in product_h5s]
|
|
478
|
+
ts_geom = geom_from_hessian(ts_h5)
|
|
479
|
+
rates = get_rates_for_geoms(temperature, reactant_geoms, ts_geom, product_geoms)
|
|
480
|
+
return rates
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from pysisyphus import logger
|
|
5
|
+
from pysisyphus.calculators import XTB
|
|
6
|
+
from pysisyphus.calculators.OBabel import OBabel
|
|
7
|
+
from pysisyphus.config import LIB_DIR
|
|
8
|
+
from pysisyphus.Geometry import Geometry
|
|
9
|
+
from pysisyphus.helpers import geom_loader
|
|
10
|
+
from pysisyphus.helpers_pure import chunks
|
|
11
|
+
from pysisyphus.intcoords.setup import get_bond_sets
|
|
12
|
+
from pysisyphus.linalg import get_rot_mat_for_coords
|
|
13
|
+
from pysisyphus.optimizers.LBFGS import LBFGS
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_bond_subgeom(geom, ind, invert=False):
|
|
17
|
+
bond_inds = get_bond_sets(geom.atoms, geom.coords3d)
|
|
18
|
+
# Determine bonds that are connected to atom 'ind' in 'geom'
|
|
19
|
+
conn_bond_inds = [bond for bond in bond_inds if ind in bond]
|
|
20
|
+
assert len(conn_bond_inds) == 1
|
|
21
|
+
ind0, ind1 = conn_bond_inds[0]
|
|
22
|
+
# Index of atom to which 'ind' is connected
|
|
23
|
+
anchor_ind = ind0 if (ind == ind1) else ind1
|
|
24
|
+
# conn_bond_ind = (ind0, ind1)
|
|
25
|
+
conn_bond_ind = (ind, anchor_ind) if invert else (anchor_ind, ind)
|
|
26
|
+
return geom.get_subgeom(conn_bond_ind), anchor_ind
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def run_opt(geom, freeze_inds, ff="uff", use_xtb=False, charge=0, mult=1):
|
|
30
|
+
def get_xtb_calc():
|
|
31
|
+
return XTB(charge=charge, mult=mult, pal=2)
|
|
32
|
+
|
|
33
|
+
if not use_xtb:
|
|
34
|
+
try:
|
|
35
|
+
from openbabel import pybel
|
|
36
|
+
|
|
37
|
+
# Create pybel.Molecule/OBMol to set the missing bonds
|
|
38
|
+
mol = pybel.readstring("xyz", geom.as_xyz())
|
|
39
|
+
# Use modified pybel.Molecule
|
|
40
|
+
calc = OBabel(mol=mol, ff=ff)
|
|
41
|
+
except ImportError:
|
|
42
|
+
logger.warning("Could not import openbabel. Trying fallback to GFN2-xTB.")
|
|
43
|
+
calc = get_xtb_calc()
|
|
44
|
+
else:
|
|
45
|
+
calc = get_xtb_calc()
|
|
46
|
+
|
|
47
|
+
frozen_geom = geom.copy()
|
|
48
|
+
# Only some atoms
|
|
49
|
+
frozen_geom.freeze_atoms = freeze_inds
|
|
50
|
+
frozen_geom.set_calculator(calc)
|
|
51
|
+
|
|
52
|
+
opt = LBFGS(frozen_geom, max_cycles=1000, max_step=0.5, dump=False)
|
|
53
|
+
opt.run()
|
|
54
|
+
|
|
55
|
+
return frozen_geom
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def replace_atom(
|
|
59
|
+
geom,
|
|
60
|
+
ind,
|
|
61
|
+
repl_geom,
|
|
62
|
+
repl_ind,
|
|
63
|
+
return_full=True,
|
|
64
|
+
opt=False,
|
|
65
|
+
use_xtb=False,
|
|
66
|
+
charge=0,
|
|
67
|
+
mult=1,
|
|
68
|
+
):
|
|
69
|
+
"""Replace atom with fragment."""
|
|
70
|
+
|
|
71
|
+
geom = geom.copy(coord_type="cart")
|
|
72
|
+
if len(repl_geom.atoms) == 1:
|
|
73
|
+
atoms = list(geom.atoms)
|
|
74
|
+
atoms[ind] = repl_geom.atoms[0]
|
|
75
|
+
coords3d = geom.coords3d
|
|
76
|
+
return Geometry(atoms, coords3d)
|
|
77
|
+
|
|
78
|
+
repl_geom = repl_geom.copy(coord_type="cart")
|
|
79
|
+
|
|
80
|
+
# Determine bond-subgeometries, which will be used to calculate a rotation
|
|
81
|
+
# matrix.
|
|
82
|
+
geom_sub, _ = get_bond_subgeom(geom, ind)
|
|
83
|
+
# Call with invert=True, as we want the bonds to point in opposite directions.
|
|
84
|
+
# Without invert=True, 'get_bond_subgeom' will always return with the atom order
|
|
85
|
+
# (ind, anchor_ind). This would lead to overlapping fragments.
|
|
86
|
+
repl_sub, repl_anchor = get_bond_subgeom(repl_geom, repl_ind, invert=True)
|
|
87
|
+
|
|
88
|
+
# Determine step that translates 'repl_geom' so that the atom 'ind' in 'geom'
|
|
89
|
+
# and 'anchor_ind' in 'repl_geom' coincide.
|
|
90
|
+
# step = geom.coords3d[ind] - repl_geom.coords3d[repl_anchor]
|
|
91
|
+
# repl_sub.coords3d += step[None, :]
|
|
92
|
+
|
|
93
|
+
# Determine rotation matrix that aligns 'repl_geom' on on 'geom_bond'
|
|
94
|
+
*_, rot_mat = get_rot_mat_for_coords(geom_sub.coords3d, repl_sub.coords3d)
|
|
95
|
+
# Rotate whole 'repl_geom' with 'rot_mat'
|
|
96
|
+
repl_c3d = repl_geom.coords3d
|
|
97
|
+
repl_centroid = repl_c3d.mean(axis=0)
|
|
98
|
+
repl_c3d_rot = (repl_c3d - repl_centroid[None, :]).dot(rot_mat) + repl_centroid[
|
|
99
|
+
None, :
|
|
100
|
+
]
|
|
101
|
+
repl_geom.coords = repl_c3d_rot
|
|
102
|
+
# Translate repl_geom so that repl_anchor and ind coincide
|
|
103
|
+
step = geom.coords3d[ind] - repl_c3d_rot[repl_anchor]
|
|
104
|
+
repl_c3d_rot += step[None, :]
|
|
105
|
+
repl_geom.coords = repl_c3d_rot
|
|
106
|
+
|
|
107
|
+
# Create geometries without atoms that will be deleted/replaces
|
|
108
|
+
geom_ = geom.get_subgeom_without([ind])
|
|
109
|
+
repl_geom_ = repl_geom.get_subgeom_without([repl_ind])
|
|
110
|
+
# Union
|
|
111
|
+
union = geom_ + repl_geom_
|
|
112
|
+
if opt:
|
|
113
|
+
freeze_inds = [ind for ind, _ in enumerate(geom_.atoms)]
|
|
114
|
+
union = run_opt(union, freeze_inds, use_xtb=use_xtb, charge=charge, mult=mult)
|
|
115
|
+
# Update coords in 'repl_geom' with optimized coordinates
|
|
116
|
+
repl_geom_.coords3d = union.coords3d[len(freeze_inds) :]
|
|
117
|
+
|
|
118
|
+
# This is useful when multiple replacements are to be done. If multiple
|
|
119
|
+
# replacements would be done sequentially on the same geometry, while
|
|
120
|
+
# returning the union, would require taking into account altered atom ordering/
|
|
121
|
+
# numbering. By disabling return_full we can only return the replacement fragment,
|
|
122
|
+
# that can be added to the original geometry, after all replacement fragments
|
|
123
|
+
# have been calculated.
|
|
124
|
+
if return_full:
|
|
125
|
+
return_geom = union
|
|
126
|
+
else:
|
|
127
|
+
return_geom = repl_geom_
|
|
128
|
+
return return_geom
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Located in ../geom_library/replacements/
|
|
132
|
+
REPLACEMENTS = {
|
|
133
|
+
"Ph": ("benzene.xyz", 8),
|
|
134
|
+
"OMe": ("methanol.xyz", 4),
|
|
135
|
+
"OEt": ("ethanol.xyz", 8),
|
|
136
|
+
"OH": ("water.xyz", 1),
|
|
137
|
+
"F": ("hf.xyz", 1),
|
|
138
|
+
"Cl": ("hcl.xyz", 1),
|
|
139
|
+
"Br": ("hbr.xyz", 1),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def normalize_replacements(replacements):
|
|
144
|
+
replacements = list(replacements)
|
|
145
|
+
for i, repl in enumerate(replacements):
|
|
146
|
+
if isinstance(repl[1], str):
|
|
147
|
+
ind, repl_key = repl
|
|
148
|
+
repl_fn, repl_ind = REPLACEMENTS[repl_key]
|
|
149
|
+
repl_geom = geom_loader(LIB_DIR / "replacements" / repl_fn)
|
|
150
|
+
replacements[i] = (ind, repl_ind, repl_geom)
|
|
151
|
+
elif len(repl) == 3:
|
|
152
|
+
continue
|
|
153
|
+
else:
|
|
154
|
+
raise Exception("Invalid replacements!")
|
|
155
|
+
return replacements
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def replace_atoms(geom, replacements, opt=False, use_xtb=False, charge=0, mult=1):
|
|
159
|
+
replacements = normalize_replacements(replacements)
|
|
160
|
+
repl_frags = list()
|
|
161
|
+
del_inds = list()
|
|
162
|
+
for ind, repl_ind, repl_geom in replacements:
|
|
163
|
+
repl_frag = replace_atom(geom, ind, repl_geom, repl_ind, False, opt=opt, use_xtb=use_xtb)
|
|
164
|
+
repl_frags.append(repl_frag)
|
|
165
|
+
del_inds.append(ind)
|
|
166
|
+
org_atom_num = len(geom.atoms)
|
|
167
|
+
union = geom.get_subgeom_without(del_inds)
|
|
168
|
+
for frag in repl_frags:
|
|
169
|
+
union += frag
|
|
170
|
+
|
|
171
|
+
if opt:
|
|
172
|
+
freeze_inds = [ind for ind in range(org_atom_num - len(del_inds))]
|
|
173
|
+
union = run_opt(union, freeze_inds, use_xtb=use_xtb)
|
|
174
|
+
|
|
175
|
+
return union
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def parse_args(args):
|
|
179
|
+
parser = argparse.ArgumentParser()
|
|
180
|
+
|
|
181
|
+
parser.add_argument("geom_fn")
|
|
182
|
+
parser.add_argument("replacements", nargs="+")
|
|
183
|
+
parser.add_argument("--opt", action="store_true")
|
|
184
|
+
parser.add_argument("--jmol", action="store_true")
|
|
185
|
+
parser.add_argument("--out_fn", type=str, default="union.xyz")
|
|
186
|
+
parser.add_argument("--charge", type=int, default=0)
|
|
187
|
+
parser.add_argument("--mult", type=int, default=1)
|
|
188
|
+
parser.add_argument("--xtb", action="store_true")
|
|
189
|
+
|
|
190
|
+
return parser.parse_args(args)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def run():
|
|
194
|
+
args = parse_args(sys.argv[1:])
|
|
195
|
+
geom_fn = args.geom_fn
|
|
196
|
+
replacements_ = args.replacements
|
|
197
|
+
opt = args.opt
|
|
198
|
+
jmol = args.jmol
|
|
199
|
+
out_fn = args.out_fn
|
|
200
|
+
charge = args.charge
|
|
201
|
+
mult = args.mult
|
|
202
|
+
xtb = args.xtb
|
|
203
|
+
|
|
204
|
+
# Activate opt when xtb is given
|
|
205
|
+
opt = opt or xtb
|
|
206
|
+
|
|
207
|
+
assert len(replacements_) % 2 == 0
|
|
208
|
+
replacements = [(int(ind), key) for ind, key in chunks(replacements_, 2)]
|
|
209
|
+
|
|
210
|
+
geom = geom_loader(geom_fn)
|
|
211
|
+
union = replace_atoms(geom, replacements, opt=opt, charge=charge, mult=mult, use_xtb=xtb)
|
|
212
|
+
print(union.as_xyz())
|
|
213
|
+
|
|
214
|
+
if union:
|
|
215
|
+
with open(out_fn, "w") as handle:
|
|
216
|
+
handle.write(union.as_xyz())
|
|
217
|
+
print(f"Saved new geometry to '{out_fn}'.")
|
|
218
|
+
if jmol:
|
|
219
|
+
union.jmol()
|