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
thermoanalysis/thermo.py
ADDED
|
@@ -0,0 +1,1011 @@
|
|
|
1
|
+
# [1] http://gaussian.com/thermo/
|
|
2
|
+
# [2] https://doi.org/10.1002/chem.201200497
|
|
3
|
+
# [3] https://doi.org/10.1021/acs.organomet.8b00456
|
|
4
|
+
# [4] https://cccbdb.nist.gov/thermo.asp
|
|
5
|
+
# [5] https://pubs.acs.org/doi/10.1021/acs.jctc.0c01306
|
|
6
|
+
# Single-Point Hessian Calculations
|
|
7
|
+
# Spicher, Grimme, 2021
|
|
8
|
+
# [6] https://doi.org/10.1063/5.0061187
|
|
9
|
+
# Calculation of improved enthalpy and entropy of vaporization by a modified
|
|
10
|
+
# partition function in quantum cluster equilibrium theory
|
|
11
|
+
# Ingenmey, Grimme, 2021
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from collections import namedtuple
|
|
15
|
+
from typing import Literal, Optional, Tuple
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from numpy.typing import ArrayLike, NDArray
|
|
19
|
+
|
|
20
|
+
from thermoanalysis.constants import (
|
|
21
|
+
C,
|
|
22
|
+
KB,
|
|
23
|
+
KBAU,
|
|
24
|
+
NA,
|
|
25
|
+
R,
|
|
26
|
+
PLANCK,
|
|
27
|
+
J2AU,
|
|
28
|
+
J2CAL,
|
|
29
|
+
AMU2KG,
|
|
30
|
+
)
|
|
31
|
+
from thermoanalysis.config import p_DEFAULT, ROTOR_CUT_DEFAULT
|
|
32
|
+
from thermoanalysis.QCData import QCData
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
TRANS_ENTROPY_KINDS = Literal["sackur", "sackur_simple"]
|
|
36
|
+
VIB_KINDS = Literal["qrrho", "rrho"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
ThermoResults = namedtuple(
|
|
40
|
+
"ThermoResults",
|
|
41
|
+
(
|
|
42
|
+
"T kBT M p org_wavenumbers wavenumbers "
|
|
43
|
+
"scale_factor zpe_scale_factor invert_imag cutoff kind "
|
|
44
|
+
"atom_num linear point_group sym_num "
|
|
45
|
+
"Q_el Q_trans Q_rot Q_vib Q_vib_V0 Q_tot Q_tot_V0 "
|
|
46
|
+
"U_el U_trans U_rot U_vib U_therm U_tot ZPE H "
|
|
47
|
+
"S_trans S_rot S_vib S_el S_tot "
|
|
48
|
+
"c_el c_trans c_rot c_vib c_tot "
|
|
49
|
+
"TS_trans TS_rot TS_vib TS_el TS_tot G dG"
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def chai_head_gordon_weights(
|
|
55
|
+
frequencies: NDArray[np.float64], cutoff: float, alpha: int = 4
|
|
56
|
+
) -> NDArray[np.float64]:
|
|
57
|
+
"""Chai-Head-Gordon damping function.
|
|
58
|
+
|
|
59
|
+
Used for interpolating between harmonic oscillator and hindered rotor
|
|
60
|
+
approximations. See eq. (8) in [2], or Eq. (10) in [7].
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
frequencies
|
|
65
|
+
Vibrational frequencies in 1/s.
|
|
66
|
+
cutoff
|
|
67
|
+
Wavenumber cutoff in cm⁻¹. Vibrations below this threshold will mostly
|
|
68
|
+
be treated as hindered rotors.
|
|
69
|
+
alpha
|
|
70
|
+
Exponent alpha in the damping function.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
weights
|
|
75
|
+
Weights of the damping function.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
wavenumbers = (frequencies / C) / 100 # in cm⁻¹
|
|
79
|
+
weights = 1 / (1 + (cutoff / wavenumbers) ** alpha)
|
|
80
|
+
return weights
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
##############
|
|
84
|
+
# ELECTRONIC #
|
|
85
|
+
##############
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def electronic_part_func(
|
|
89
|
+
multiplicity: int, electronic_energies: ArrayLike, temperature: float
|
|
90
|
+
) -> float:
|
|
91
|
+
"""Electronic partition function.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
multiplicity
|
|
96
|
+
Multiplicity of the molecule.
|
|
97
|
+
electronic_energies
|
|
98
|
+
Electronic energy/energies in Hartree.
|
|
99
|
+
temperature : float
|
|
100
|
+
Absolute temperature in Kelvin.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
Q_el
|
|
105
|
+
Electronic partition function.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
electronic_energies = np.atleast_1d(electronic_energies)
|
|
109
|
+
energy_diffs = electronic_energies - electronic_energies.min()
|
|
110
|
+
g = multiplicity
|
|
111
|
+
q_el = g * (np.exp(-energy_diffs / (KBAU * temperature))).sum()
|
|
112
|
+
return q_el
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def electronic_entropy(multiplicity: int) -> float:
|
|
116
|
+
"""Electronic entropy.
|
|
117
|
+
|
|
118
|
+
Currently, only one electronic state is considered. See [1] for reference.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
multiplicity
|
|
123
|
+
Multiplicity of the molecule.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
S_el
|
|
128
|
+
Electronic entropy in Hartree / particle.
|
|
129
|
+
"""
|
|
130
|
+
S_el = KBAU * np.log(multiplicity)
|
|
131
|
+
return S_el
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
#######################
|
|
135
|
+
# TRANSLATIONAL TERMS #
|
|
136
|
+
#######################
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def translational_part_func(
|
|
140
|
+
molecular_mass: float, temperature: float, pressure: float
|
|
141
|
+
) -> float:
|
|
142
|
+
"""Translational partition function Q_trans.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
molecular_mass
|
|
147
|
+
Molecular mass in atomic mass units (amu).
|
|
148
|
+
temperature
|
|
149
|
+
Absolute temperature in Kelvin.
|
|
150
|
+
pressure
|
|
151
|
+
Pressure in Pascal.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
q_trans
|
|
156
|
+
Translational partition function.
|
|
157
|
+
"""
|
|
158
|
+
# Volume of an atom of an ideal gas
|
|
159
|
+
volume = KB * temperature / pressure
|
|
160
|
+
molecular_mass_kg = molecular_mass * AMU2KG
|
|
161
|
+
q_trans = (
|
|
162
|
+
(2 * np.pi * molecular_mass_kg * KB * temperature / PLANCK**2) ** 1.5
|
|
163
|
+
) * volume
|
|
164
|
+
return q_trans
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def translational_energy(temperature: float) -> float:
|
|
168
|
+
"""Kinectic energy of an ideal gas.
|
|
169
|
+
|
|
170
|
+
See [1] for reference.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
temperature
|
|
175
|
+
Absolute temperature in Kelvin.
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
U_trans
|
|
180
|
+
Kinetic energy in Hartree / particle.
|
|
181
|
+
"""
|
|
182
|
+
U_trans = 3 / 2 * KBAU * temperature
|
|
183
|
+
return U_trans
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def sackur_tetrode(molecular_mass: float, temperature: float, pressure: float) -> float:
|
|
187
|
+
"""Translational entropy of an ideal gas.
|
|
188
|
+
|
|
189
|
+
See [1] for reference.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
molecular_mass
|
|
194
|
+
Molecular mass in atomic mass units (amu).
|
|
195
|
+
temperature
|
|
196
|
+
Absolute temperature in Kelvin.
|
|
197
|
+
pressure
|
|
198
|
+
Pressure in Pascal.
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
S_trans
|
|
203
|
+
Translational entropy in Hartree / (particle * K).
|
|
204
|
+
"""
|
|
205
|
+
# Just using 1e5 instead of a "true" atmosphere of 1.01325e5 seems to
|
|
206
|
+
# agree better with the results Gaussian and ORCA produce.
|
|
207
|
+
q_trans = (
|
|
208
|
+
(2 * np.pi * molecular_mass * AMU2KG * KB * temperature / PLANCK**2) ** (3 / 2)
|
|
209
|
+
* KB
|
|
210
|
+
* temperature
|
|
211
|
+
/ pressure
|
|
212
|
+
)
|
|
213
|
+
S_trans = KBAU * (np.log(q_trans) + 1 + 3 / 2)
|
|
214
|
+
return S_trans
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def sackur_tetrode_simplified(molecular_mass: float, temperature: float) -> float:
|
|
218
|
+
"""Translational entropy of a monoatomic ideal gas.
|
|
219
|
+
|
|
220
|
+
See [3] for reference.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
molecular_mass
|
|
225
|
+
Molecular mass in atomic mass units (amu).
|
|
226
|
+
temperature
|
|
227
|
+
Absolute temperature in Kelvin.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
S_trans
|
|
232
|
+
Translational entropy in J/(mol*K).
|
|
233
|
+
"""
|
|
234
|
+
S_trans = (
|
|
235
|
+
3 / 2 * R * np.log(molecular_mass) + 5 / 2 * R * np.log(temperature) - 2.315
|
|
236
|
+
)
|
|
237
|
+
return S_trans
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def translational_entropy(
|
|
241
|
+
molecular_mass: float,
|
|
242
|
+
temperature: float,
|
|
243
|
+
pressure: float,
|
|
244
|
+
kind: TRANS_ENTROPY_KINDS = "sackur",
|
|
245
|
+
) -> float:
|
|
246
|
+
"""Wrapper for translational entropy calculation.
|
|
247
|
+
|
|
248
|
+
Parameters
|
|
249
|
+
----------
|
|
250
|
+
molecular_mass
|
|
251
|
+
Molecular mass in atomic mass units (amu).
|
|
252
|
+
temperature
|
|
253
|
+
Absolute temperature in Kelvin.
|
|
254
|
+
kind
|
|
255
|
+
Type of calculation method.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
S_trans
|
|
260
|
+
Translational entropy.
|
|
261
|
+
"""
|
|
262
|
+
funcs = {
|
|
263
|
+
"sackur": lambda M, T: sackur_tetrode(
|
|
264
|
+
molecular_mass=M, temperature=T, pressure=pressure
|
|
265
|
+
),
|
|
266
|
+
"sackur_simple": sackur_tetrode_simplified,
|
|
267
|
+
}
|
|
268
|
+
return funcs[kind](molecular_mass, temperature)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def translational_heat_capacity() -> float:
|
|
272
|
+
"""Constant volume heat capacity.
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
c_trans
|
|
277
|
+
Constant volume heat capacity in J / (K mol).
|
|
278
|
+
"""
|
|
279
|
+
c_trans = 1.5 * R
|
|
280
|
+
return c_trans
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
####################
|
|
284
|
+
# ROTATIONAL TERMS #
|
|
285
|
+
####################
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def rotational_part_func(
|
|
289
|
+
temperature: float,
|
|
290
|
+
rot_temperatures: NDArray[np.float64],
|
|
291
|
+
symmetry_number: int,
|
|
292
|
+
is_atom: bool,
|
|
293
|
+
is_linear: bool,
|
|
294
|
+
) -> float:
|
|
295
|
+
"""Rotational partition function Q_rot.
|
|
296
|
+
|
|
297
|
+
See [1] for reference.
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
temperature
|
|
302
|
+
Absolute temperature in Kelvin.
|
|
303
|
+
rot_temperatures
|
|
304
|
+
Rotational temperatures in Kelvin.
|
|
305
|
+
symmetry_number
|
|
306
|
+
Symmetry number.
|
|
307
|
+
is_atom
|
|
308
|
+
Wether the molcule is an atom.
|
|
309
|
+
is_linear
|
|
310
|
+
Wether the molecule is linear.
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
Q_rot
|
|
315
|
+
Rotational partition function.
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
if is_atom:
|
|
319
|
+
q_rot = 1
|
|
320
|
+
elif is_linear:
|
|
321
|
+
# First rot_temperature will be infinite, and the last two components
|
|
322
|
+
# will be identical. Only use the last one.
|
|
323
|
+
q_rot = temperature / (rot_temperatures[-1] * symmetry_number)
|
|
324
|
+
else:
|
|
325
|
+
# Polyamtomic, non-linear case
|
|
326
|
+
q_rot = (
|
|
327
|
+
np.pi ** (1 / 2)
|
|
328
|
+
/ symmetry_number
|
|
329
|
+
* (temperature ** (3 / 2) / np.prod(rot_temperatures) ** (1 / 2))
|
|
330
|
+
)
|
|
331
|
+
return q_rot
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def rotational_energy(temperature: float, is_linear: bool, is_atom: bool) -> float:
|
|
335
|
+
"""Rotational energy.
|
|
336
|
+
|
|
337
|
+
See [1] for reference.
|
|
338
|
+
|
|
339
|
+
Parameters
|
|
340
|
+
----------
|
|
341
|
+
temperature
|
|
342
|
+
Absolute temperature in Kelvin.
|
|
343
|
+
is_linear
|
|
344
|
+
Wether the molecule is linear.
|
|
345
|
+
is_atom
|
|
346
|
+
Wether the molcule is an atom.
|
|
347
|
+
|
|
348
|
+
Returns
|
|
349
|
+
-------
|
|
350
|
+
U_rot
|
|
351
|
+
Rotational energy in Hartree / particle.
|
|
352
|
+
"""
|
|
353
|
+
if is_atom:
|
|
354
|
+
factor = 0.0
|
|
355
|
+
elif is_linear:
|
|
356
|
+
factor = 1.0
|
|
357
|
+
else:
|
|
358
|
+
factor = 1.5
|
|
359
|
+
|
|
360
|
+
U_rot = factor * KBAU * temperature
|
|
361
|
+
return U_rot
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def rotational_entropy(
|
|
365
|
+
temperature: float,
|
|
366
|
+
rot_temperatures: NDArray[np.float64],
|
|
367
|
+
symmetry_number: int,
|
|
368
|
+
is_atom: bool,
|
|
369
|
+
is_linear: bool,
|
|
370
|
+
) -> float:
|
|
371
|
+
"""Rotational entropy.
|
|
372
|
+
|
|
373
|
+
See [1] for reference.
|
|
374
|
+
|
|
375
|
+
Parameters
|
|
376
|
+
----------
|
|
377
|
+
temperature
|
|
378
|
+
Absolute temperature in Kelvin.
|
|
379
|
+
rot_temperatures
|
|
380
|
+
Rotational temperatures in Kelvin.
|
|
381
|
+
symmetry_number
|
|
382
|
+
Symmetry number.
|
|
383
|
+
is_atom
|
|
384
|
+
Wether the molcule is an atom.
|
|
385
|
+
is_linear
|
|
386
|
+
Wether the molecule is linear.
|
|
387
|
+
|
|
388
|
+
Returns
|
|
389
|
+
-------
|
|
390
|
+
S_rot
|
|
391
|
+
Rotational entropy in Hartree /(particle * K).
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
q_rot = rotational_part_func(
|
|
395
|
+
temperature, rot_temperatures, symmetry_number, is_atom, is_linear=is_linear
|
|
396
|
+
)
|
|
397
|
+
if is_atom:
|
|
398
|
+
plus = 0.0
|
|
399
|
+
elif is_linear:
|
|
400
|
+
plus = 1.0
|
|
401
|
+
# Polyamtomic, non-linear case
|
|
402
|
+
else:
|
|
403
|
+
plus = 1.5
|
|
404
|
+
S_rot = KBAU * (np.log(q_rot) + plus)
|
|
405
|
+
return S_rot
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def rotational_heat_capacity(is_atom: bool, is_linear: bool) -> float:
|
|
409
|
+
"""Rotational heat capacity.
|
|
410
|
+
|
|
411
|
+
Parameters
|
|
412
|
+
----------
|
|
413
|
+
is_atom
|
|
414
|
+
Wether the molcule is an atom.
|
|
415
|
+
is_linear
|
|
416
|
+
Wether the molecule is linear.
|
|
417
|
+
|
|
418
|
+
Returns
|
|
419
|
+
-------
|
|
420
|
+
c_rot
|
|
421
|
+
Rotational contributions to the heat capacity.
|
|
422
|
+
"""
|
|
423
|
+
if is_atom:
|
|
424
|
+
c_rot = 0
|
|
425
|
+
elif is_linear:
|
|
426
|
+
c_rot = R
|
|
427
|
+
else:
|
|
428
|
+
c_rot = 1.5 * R
|
|
429
|
+
return c_rot
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
#####################
|
|
433
|
+
# VIBRATIONAL TERMS #
|
|
434
|
+
#####################
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def zero_point_energy(frequencies: NDArray[np.float64]) -> float:
|
|
438
|
+
"""Vibrational zero point energies.
|
|
439
|
+
|
|
440
|
+
See [1] for reference.
|
|
441
|
+
|
|
442
|
+
Parameters
|
|
443
|
+
----------
|
|
444
|
+
frequencies
|
|
445
|
+
Vibrational frequencies in 1/s.
|
|
446
|
+
|
|
447
|
+
Returns
|
|
448
|
+
-------
|
|
449
|
+
ZPE
|
|
450
|
+
Vibrational ZPE for the given frequencies in Hartree / particle.
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
ZPE = J2AU * (PLANCK * frequencies / 2).sum()
|
|
454
|
+
return ZPE
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def vibrational_part_funcs(
|
|
458
|
+
temperature: float, frequencies: NDArray[np.float64]
|
|
459
|
+
) -> Tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
460
|
+
"""Vibrational partition functions for harmonic oscillators.
|
|
461
|
+
|
|
462
|
+
Parameters
|
|
463
|
+
----------
|
|
464
|
+
temperature
|
|
465
|
+
Absolute temperature in Kelvin.
|
|
466
|
+
frequencies
|
|
467
|
+
Vibrational frequencies in 1/s.
|
|
468
|
+
|
|
469
|
+
Returns
|
|
470
|
+
-------
|
|
471
|
+
q_vibs : ArrayLike
|
|
472
|
+
Vibrational partition functions at the bottom of the well.
|
|
473
|
+
q_vibs_V0 : ArrayLike
|
|
474
|
+
Vibrational partition functions with first vibrational energy level
|
|
475
|
+
as reference.
|
|
476
|
+
"""
|
|
477
|
+
frequencies = np.array(frequencies, dtype=float)
|
|
478
|
+
quot = -PLANCK * frequencies / (KB * temperature)
|
|
479
|
+
denom = 1 / (1 - np.exp(quot))
|
|
480
|
+
quot_half = quot / 2
|
|
481
|
+
# Bottom of the well as reference
|
|
482
|
+
q_vibs = np.exp(quot_half) * denom
|
|
483
|
+
# First vibrational energy level as reference
|
|
484
|
+
q_vibs_V0 = denom
|
|
485
|
+
return q_vibs, q_vibs_V0
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def qrrho_vibrational_part_func(
|
|
489
|
+
temperature: float,
|
|
490
|
+
frequencies: NDArray[np.float64],
|
|
491
|
+
I_mean: Optional[float],
|
|
492
|
+
cutoff: float,
|
|
493
|
+
alpha: int = 4,
|
|
494
|
+
) -> Tuple[float, float]:
|
|
495
|
+
"""QRRHO vibrational partition function.
|
|
496
|
+
|
|
497
|
+
As given in Eq. (7) in [6]. Mix between hindererd rotor
|
|
498
|
+
and harmonic oscillator partition function.
|
|
499
|
+
|
|
500
|
+
Parameters
|
|
501
|
+
----------
|
|
502
|
+
temperature
|
|
503
|
+
Absolute temperature in Kelvin.
|
|
504
|
+
frequencies
|
|
505
|
+
Vibrational frequencies in 1/s.
|
|
506
|
+
I_mean
|
|
507
|
+
Average moment of inertia of the molecule in Angstrom² * amu.
|
|
508
|
+
cutoff
|
|
509
|
+
Wavenumber cutoff in cm⁻¹. Vibrations below this threshold will mostly
|
|
510
|
+
be treated as hindered rotors.
|
|
511
|
+
alpha
|
|
512
|
+
Exponent alpha in the damping function (Eq. (8) in [2], or Eq. (10) in [7])
|
|
513
|
+
|
|
514
|
+
Returns
|
|
515
|
+
-------
|
|
516
|
+
q_qrrho
|
|
517
|
+
QRRHO Vibrational partition function, unitless.
|
|
518
|
+
q_qrrho_V0
|
|
519
|
+
QRRHO Vibrational partition function, evaluated at first vibrational energy level.
|
|
520
|
+
"""
|
|
521
|
+
|
|
522
|
+
wavenumbers = (frequencies / C) / 100 # in cm⁻¹
|
|
523
|
+
q_vibs, q_vibs_V0 = vibrational_part_funcs(temperature, frequencies)
|
|
524
|
+
|
|
525
|
+
if cutoff > 0.0 and I_mean:
|
|
526
|
+
# Normal mode moments of inertia
|
|
527
|
+
wavenumbers_m = 100 * wavenumbers # in m⁻¹
|
|
528
|
+
mu = PLANCK / (
|
|
529
|
+
2 * np.pi * 4 * np.pi * wavenumbers_m
|
|
530
|
+
) # Eq. (9) in [6], in kg m³ s⁻¹
|
|
531
|
+
# Convert average moment of inertia from Ų AMU to SI units (m² kg)
|
|
532
|
+
I_mean_SI = I_mean * 1e-20 * AMU2KG
|
|
533
|
+
mu_ = mu * I_mean_SI / (mu + I_mean_SI)
|
|
534
|
+
q_hr = np.sqrt(
|
|
535
|
+
2 * mu_ / (np.pi * (PLANCK / (2 * np.pi)) ** 2 / (temperature * KB))
|
|
536
|
+
)
|
|
537
|
+
# Without cutoff this partition function reduces to the partition function
|
|
538
|
+
# of the harmonic oscillator.
|
|
539
|
+
else:
|
|
540
|
+
q_hr = np.ones_like(q_vibs)
|
|
541
|
+
|
|
542
|
+
weights = chai_head_gordon_weights(frequencies, cutoff, alpha)
|
|
543
|
+
|
|
544
|
+
def prod(q_vibs):
|
|
545
|
+
# Work in log-domain to avoid overflow in large mode products.
|
|
546
|
+
tiny = np.finfo(float).tiny
|
|
547
|
+
max_log = np.log(np.finfo(float).max)
|
|
548
|
+
min_log = np.log(tiny)
|
|
549
|
+
log_q = np.sum(
|
|
550
|
+
weights * np.log(np.clip(q_vibs, tiny, None))
|
|
551
|
+
+ (1 - weights) * np.log(np.clip(q_hr, tiny, None))
|
|
552
|
+
)
|
|
553
|
+
if log_q >= max_log:
|
|
554
|
+
return float("inf")
|
|
555
|
+
if log_q <= min_log:
|
|
556
|
+
return 0.0
|
|
557
|
+
return float(np.exp(log_q))
|
|
558
|
+
|
|
559
|
+
q_qrrho = prod(q_vibs)
|
|
560
|
+
q_qrrho_V0 = prod(q_vibs_V0)
|
|
561
|
+
return q_qrrho, q_qrrho_V0
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def vibrational_part_func(
|
|
565
|
+
temperature: float, frequencies: NDArray[np.float64]
|
|
566
|
+
) -> Tuple[float, float]:
|
|
567
|
+
"""Vibrational partition function.
|
|
568
|
+
|
|
569
|
+
Parameters
|
|
570
|
+
----------
|
|
571
|
+
temperature
|
|
572
|
+
Absolute temperature in Kelvin.
|
|
573
|
+
frequencies
|
|
574
|
+
Vibrational frequencies in 1/s.
|
|
575
|
+
|
|
576
|
+
Returns
|
|
577
|
+
-------
|
|
578
|
+
q_qrrho
|
|
579
|
+
QRRHO Vibrational partition function, unitless.
|
|
580
|
+
q_qrrho_V0
|
|
581
|
+
QRRHO Vibrational partition function, evaluated at first vibrational energy level,
|
|
582
|
+
unitless.
|
|
583
|
+
"""
|
|
584
|
+
return qrrho_vibrational_part_func(
|
|
585
|
+
temperature, frequencies, I_mean=None, cutoff=0.0
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def vibrational_energy(temperature: float, frequencies: NDArray[np.float64]) -> float:
|
|
590
|
+
"""Vibrational energy.
|
|
591
|
+
|
|
592
|
+
See [1] for reference.
|
|
593
|
+
|
|
594
|
+
Parameters
|
|
595
|
+
----------
|
|
596
|
+
temperature
|
|
597
|
+
Absolute temperature in Kelvin.
|
|
598
|
+
frequencies
|
|
599
|
+
Vibrational frequencies in 1/s.
|
|
600
|
+
|
|
601
|
+
Returns
|
|
602
|
+
-------
|
|
603
|
+
U_vib
|
|
604
|
+
Vibrational energy in Hartree / particle.
|
|
605
|
+
"""
|
|
606
|
+
vib_temperatures = PLANCK * frequencies / KB
|
|
607
|
+
U_vib = KBAU * np.sum(
|
|
608
|
+
vib_temperatures * (1 / 2 + 1 / (np.exp(vib_temperatures / temperature) - 1))
|
|
609
|
+
)
|
|
610
|
+
return U_vib
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def harmonic_vibrational_entropies(
|
|
614
|
+
temperature: float, frequencies: NDArray[np.float64]
|
|
615
|
+
) -> NDArray[np.float64]:
|
|
616
|
+
"""Vibrational entropy of a harmonic oscillator.
|
|
617
|
+
|
|
618
|
+
See [1] and [2] for reference. Eq. (3) in the Grimme paper
|
|
619
|
+
is lacking a T in the denominator of the first term. It is given as
|
|
620
|
+
h*w/(k(e^(hw/kt) -1)) but it must be h*w(kT(e^(hw/kT)-1)) instead.
|
|
621
|
+
Here the calculation is done as presented in [1].
|
|
622
|
+
|
|
623
|
+
Parameters
|
|
624
|
+
----------
|
|
625
|
+
temperature : float
|
|
626
|
+
Absolute temperature in Kelvin.
|
|
627
|
+
frequencies : array-like
|
|
628
|
+
Vibrational frequencies in 1/s.
|
|
629
|
+
|
|
630
|
+
Returns
|
|
631
|
+
-------
|
|
632
|
+
S_vib : array-like
|
|
633
|
+
Array containing vibrational entropies in Hartree / (particle * K).
|
|
634
|
+
"""
|
|
635
|
+
|
|
636
|
+
# Correct formula from the Grimme paper [3].
|
|
637
|
+
# hnu = PLANCK * frequencies
|
|
638
|
+
# hnu_kt = hnu / (KB * temperature)
|
|
639
|
+
# S_vib = KB * (hnu / (KB*(np.exp(hnu_kt) - 1)*temperature)
|
|
640
|
+
# - np.log(1 - np.exp(-hnu_kt))
|
|
641
|
+
# ).sum()
|
|
642
|
+
|
|
643
|
+
# As given in [1].
|
|
644
|
+
vib_temps = frequencies * PLANCK / KB
|
|
645
|
+
S_vibs = KBAU * (
|
|
646
|
+
(vib_temps / temperature) / (np.exp(vib_temps / temperature) - 1)
|
|
647
|
+
- np.log(1 - np.exp(-vib_temps / temperature))
|
|
648
|
+
)
|
|
649
|
+
return S_vibs
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def free_rotor_entropies(
|
|
653
|
+
temperature: float, frequencies: NDArray[np.float64], B_av: float = 1e-44
|
|
654
|
+
) -> NDArray[np.float64]:
|
|
655
|
+
"""Entropy of a free rotor.
|
|
656
|
+
|
|
657
|
+
See [2] for reference.
|
|
658
|
+
|
|
659
|
+
Parameters
|
|
660
|
+
----------
|
|
661
|
+
temperature
|
|
662
|
+
Absolute temperature in Kelvin.
|
|
663
|
+
frequencies
|
|
664
|
+
Vibrational frequencies in 1/s.
|
|
665
|
+
B_av
|
|
666
|
+
Limiting value for effective moment of inertia in kg*m².
|
|
667
|
+
|
|
668
|
+
Returns
|
|
669
|
+
-------
|
|
670
|
+
S_free_rots
|
|
671
|
+
Array containing free-rotor entropies in Hartree / (particle * K).
|
|
672
|
+
"""
|
|
673
|
+
inertia_moments = PLANCK / (8 * np.pi**2 * frequencies)
|
|
674
|
+
eff_inertia_moments = (inertia_moments * B_av) / (inertia_moments + B_av)
|
|
675
|
+
S_free_rots = KBAU * (
|
|
676
|
+
1 / 2
|
|
677
|
+
+ np.log(
|
|
678
|
+
(8 * np.pi**3 * eff_inertia_moments * KB * temperature / PLANCK**2)
|
|
679
|
+
** (1 / 2)
|
|
680
|
+
)
|
|
681
|
+
)
|
|
682
|
+
return S_free_rots
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def vibrational_entropies(
|
|
686
|
+
temperature: float, frequencies: NDArray[np.float64], cutoff: float, alpha: int = 4
|
|
687
|
+
) -> NDArray[np.float64]:
|
|
688
|
+
"""Weighted vibrational entropy.
|
|
689
|
+
|
|
690
|
+
As given in Eq. (7) of [2].
|
|
691
|
+
|
|
692
|
+
Parameters
|
|
693
|
+
----------
|
|
694
|
+
temperature
|
|
695
|
+
Absolute temperature in Kelvin.
|
|
696
|
+
frequencies
|
|
697
|
+
Vibrational frequencies in 1/s.
|
|
698
|
+
cutoff
|
|
699
|
+
Wavenumber cutoff in cm⁻¹. Vibrations below this threshold will mostly
|
|
700
|
+
be described by free-rotor entropies.
|
|
701
|
+
alpha
|
|
702
|
+
Exponent alpha in the damping function (Eq. (8) in [2]).
|
|
703
|
+
|
|
704
|
+
Returns
|
|
705
|
+
-------
|
|
706
|
+
S_vibs
|
|
707
|
+
Array containing vibrational entropies in Hartree / (particle * K).
|
|
708
|
+
"""
|
|
709
|
+
weights = chai_head_gordon_weights(frequencies, cutoff, alpha)
|
|
710
|
+
S_harmonic = harmonic_vibrational_entropies(temperature, frequencies)
|
|
711
|
+
S_quasi_harmonic = free_rotor_entropies(temperature, frequencies)
|
|
712
|
+
S_vibs = weights * S_harmonic + (1 - weights) * S_quasi_harmonic
|
|
713
|
+
return S_vibs
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def vibrational_entropy(
|
|
717
|
+
temperature: float, frequencies: NDArray[np.float64], cutoff: float, alpha: int = 4
|
|
718
|
+
) -> float:
|
|
719
|
+
"""Vibrational entropy.
|
|
720
|
+
|
|
721
|
+
Wrapper function. As given in Eq. (7) of [2].
|
|
722
|
+
|
|
723
|
+
Parameters
|
|
724
|
+
----------
|
|
725
|
+
temperature
|
|
726
|
+
Absolute temperature in Kelvin.
|
|
727
|
+
frequencies
|
|
728
|
+
Vibrational frequencies in 1/s.
|
|
729
|
+
cutoff
|
|
730
|
+
Wavenumber cutoff in cm⁻¹. Vibrations below this threshold will mostly
|
|
731
|
+
be described by free-rotor entropies.
|
|
732
|
+
alpha
|
|
733
|
+
Exponent alpha in the damping function (Eq. (8) in [2]).
|
|
734
|
+
|
|
735
|
+
Returns
|
|
736
|
+
-------
|
|
737
|
+
S_vib
|
|
738
|
+
Vibrational entropy in Hartree / (particle * K).
|
|
739
|
+
"""
|
|
740
|
+
return vibrational_entropies(temperature, frequencies, cutoff, alpha).sum()
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def vibrational_heat_capacity(
|
|
744
|
+
temperature: float, frequencies: NDArray[np.float64]
|
|
745
|
+
) -> float:
|
|
746
|
+
"""
|
|
747
|
+
Parameters
|
|
748
|
+
----------
|
|
749
|
+
temperature
|
|
750
|
+
Absolute temperature in Kelvin.
|
|
751
|
+
frequencies
|
|
752
|
+
Vibrational frequencies in 1/s.
|
|
753
|
+
|
|
754
|
+
Returns
|
|
755
|
+
-------
|
|
756
|
+
c_vib
|
|
757
|
+
Vibrational contributions to the heat capacity in J / (K mol).
|
|
758
|
+
"""
|
|
759
|
+
quot = PLANCK * frequencies / (KB * temperature)
|
|
760
|
+
|
|
761
|
+
c_vib = R * (np.exp(quot) * (quot / (np.exp(quot) - 1)) ** 2).sum()
|
|
762
|
+
return c_vib
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def thermochemistry(
|
|
766
|
+
qc: QCData,
|
|
767
|
+
temperature: float,
|
|
768
|
+
pressure: float = p_DEFAULT,
|
|
769
|
+
kind: VIB_KINDS = "qrrho",
|
|
770
|
+
rotor_cutoff: float = ROTOR_CUT_DEFAULT,
|
|
771
|
+
scale_factor: float = 1.0,
|
|
772
|
+
zpe_scale_factor: float = 1.0,
|
|
773
|
+
invert_imags: float = 0.0,
|
|
774
|
+
cutoff: float = 0.0,
|
|
775
|
+
) -> ThermoResults:
|
|
776
|
+
assert kind in "qrrho rrho".split()
|
|
777
|
+
assert invert_imags <= 0.0
|
|
778
|
+
assert cutoff >= 0.0
|
|
779
|
+
|
|
780
|
+
T = temperature
|
|
781
|
+
pressure = pressure
|
|
782
|
+
|
|
783
|
+
org_wavenumbers = qc.wavenumbers.copy()
|
|
784
|
+
wavenumbers = org_wavenumbers.copy()
|
|
785
|
+
# If given a full set of 3N normal mode wavenumbers, exclude N wavenumbers
|
|
786
|
+
# with the smallest absolute wavenumbers.
|
|
787
|
+
if wavenumbers.size == 3 * qc.atom_num:
|
|
788
|
+
abs_wavenumbers = np.abs(wavenumbers)
|
|
789
|
+
inds = np.argsort(abs_wavenumbers)
|
|
790
|
+
drop_first = 6 - int(qc.is_linear)
|
|
791
|
+
# Drop first N wavenumbers with smallest absolute values
|
|
792
|
+
keep_mask = np.ones_like(wavenumbers, dtype=bool)
|
|
793
|
+
keep_mask[inds[:drop_first]] = False
|
|
794
|
+
wavenumbers = wavenumbers[keep_mask]
|
|
795
|
+
|
|
796
|
+
"""
|
|
797
|
+
Remaining imaginary frequencies with small absolute values can be inverted.
|
|
798
|
+
Afterwards, all frequencies are scaled by the given factor. Finally, positive
|
|
799
|
+
frequencies below a given cutoff are set to this cutoff, e.g. 50 cm⁻¹.
|
|
800
|
+
|
|
801
|
+
[5] suggests inverting imaginary frequencies above -20 cm⁻¹.
|
|
802
|
+
"""
|
|
803
|
+
if invert_imags < 0.0:
|
|
804
|
+
to_invert = np.logical_and(invert_imags <= wavenumbers, wavenumbers <= 0.0)
|
|
805
|
+
if to_invert.sum() > 0:
|
|
806
|
+
print(f"Inverted {to_invert.sum()} imaginary frequencies.")
|
|
807
|
+
wavenumbers[to_invert] *= -1
|
|
808
|
+
|
|
809
|
+
wavenumbers *= scale_factor
|
|
810
|
+
|
|
811
|
+
if cutoff > 0.0:
|
|
812
|
+
to_cut = np.logical_and(wavenumbers < cutoff, wavenumbers > 0.0)
|
|
813
|
+
wavenumbers[to_cut] = cutoff
|
|
814
|
+
|
|
815
|
+
wavenumbers = wavenumbers[wavenumbers > 0.0]
|
|
816
|
+
vib_frequencies = C * wavenumbers * 100
|
|
817
|
+
|
|
818
|
+
# Partition functions
|
|
819
|
+
Q_el = electronic_part_func(qc.mult, qc.scf_energy, temperature=T)
|
|
820
|
+
Q_trans = translational_part_func(qc.M, temperature=T, pressure=pressure)
|
|
821
|
+
Q_rot = rotational_part_func(
|
|
822
|
+
temperature=T,
|
|
823
|
+
rot_temperatures=qc.rot_temperatures,
|
|
824
|
+
symmetry_number=qc.symmetry_number,
|
|
825
|
+
is_atom=qc.is_atom,
|
|
826
|
+
is_linear=qc.is_linear,
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
# Zero point energy
|
|
830
|
+
zpe_org = zpe_scale_factor * zero_point_energy(vib_frequencies)
|
|
831
|
+
zpe = zpe_scale_factor * zpe_org
|
|
832
|
+
|
|
833
|
+
# Internal energies
|
|
834
|
+
U_el = qc.scf_energy
|
|
835
|
+
U_trans = translational_energy(T)
|
|
836
|
+
U_rot = rotational_energy(T, qc.is_linear, qc.is_atom)
|
|
837
|
+
# U_vib already includes ZPE
|
|
838
|
+
U_vib = vibrational_energy(T, vib_frequencies)
|
|
839
|
+
# Replace ZPE in U_vib w/ scaled ZPE
|
|
840
|
+
U_vib = U_vib - zpe_org + zpe
|
|
841
|
+
U_therm = U_rot + U_vib + U_trans
|
|
842
|
+
U_tot = U_el + U_therm
|
|
843
|
+
|
|
844
|
+
H = U_tot + KBAU * T
|
|
845
|
+
|
|
846
|
+
# Entropies
|
|
847
|
+
S_el = electronic_entropy(qc.mult)
|
|
848
|
+
S_rot = rotational_entropy(
|
|
849
|
+
T,
|
|
850
|
+
qc.rot_temperatures,
|
|
851
|
+
qc.symmetry_number,
|
|
852
|
+
is_atom=qc.is_atom,
|
|
853
|
+
is_linear=qc.is_linear,
|
|
854
|
+
)
|
|
855
|
+
S_trans = translational_entropy(qc.M, T, pressure=pressure)
|
|
856
|
+
|
|
857
|
+
# Heat capacities
|
|
858
|
+
c_el = 0.0 # always zero
|
|
859
|
+
c_trans = translational_heat_capacity()
|
|
860
|
+
c_rot = rotational_heat_capacity(is_atom=qc.is_atom, is_linear=qc.is_linear)
|
|
861
|
+
c_vib = vibrational_heat_capacity(T, vib_frequencies)
|
|
862
|
+
c_tot = c_el + c_trans + c_rot + c_vib
|
|
863
|
+
|
|
864
|
+
if kind == "rrho":
|
|
865
|
+
S_hvibs = harmonic_vibrational_entropies(T, vib_frequencies)
|
|
866
|
+
S_vib = S_hvibs.sum()
|
|
867
|
+
# Harmonic oscillator partition function
|
|
868
|
+
Q_vib, Q_vib_V0 = vibrational_part_func(
|
|
869
|
+
frequencies=vib_frequencies, temperature=T
|
|
870
|
+
)
|
|
871
|
+
elif kind == "qrrho":
|
|
872
|
+
S_vib = vibrational_entropy(T, vib_frequencies, cutoff=rotor_cutoff)
|
|
873
|
+
I_mean = qc.average_moment_of_inertia
|
|
874
|
+
Q_vib, Q_vib_V0 = qrrho_vibrational_part_func(
|
|
875
|
+
T, vib_frequencies, I_mean, cutoff=rotor_cutoff
|
|
876
|
+
)
|
|
877
|
+
else:
|
|
878
|
+
raise Exception("You should never get here!")
|
|
879
|
+
|
|
880
|
+
Q_tot = Q_el * Q_trans * Q_rot * Q_vib
|
|
881
|
+
Q_tot_V0 = Q_el * Q_trans * Q_rot * Q_vib_V0
|
|
882
|
+
|
|
883
|
+
S_tot = S_el + S_trans + S_rot + S_vib
|
|
884
|
+
G = H - T * S_tot
|
|
885
|
+
dG = G - U_el
|
|
886
|
+
|
|
887
|
+
thermo = ThermoResults(
|
|
888
|
+
T=temperature,
|
|
889
|
+
kBT=KBAU * temperature,
|
|
890
|
+
M=qc.M,
|
|
891
|
+
p=pressure,
|
|
892
|
+
point_group=qc.point_group,
|
|
893
|
+
sym_num=qc.symmetry_number,
|
|
894
|
+
scale_factor=scale_factor,
|
|
895
|
+
zpe_scale_factor=zpe_scale_factor,
|
|
896
|
+
invert_imag=invert_imags,
|
|
897
|
+
cutoff=cutoff,
|
|
898
|
+
kind=kind,
|
|
899
|
+
org_wavenumbers=org_wavenumbers,
|
|
900
|
+
wavenumbers=wavenumbers,
|
|
901
|
+
atom_num=qc.atom_num,
|
|
902
|
+
linear=qc.is_linear,
|
|
903
|
+
Q_el=Q_el,
|
|
904
|
+
Q_trans=Q_trans,
|
|
905
|
+
Q_rot=Q_rot,
|
|
906
|
+
Q_vib=Q_vib,
|
|
907
|
+
Q_vib_V0=Q_vib_V0,
|
|
908
|
+
Q_tot=Q_tot,
|
|
909
|
+
Q_tot_V0=Q_tot_V0,
|
|
910
|
+
U_el=U_el,
|
|
911
|
+
U_trans=U_trans,
|
|
912
|
+
U_rot=U_rot,
|
|
913
|
+
U_vib=U_vib,
|
|
914
|
+
U_therm=U_therm,
|
|
915
|
+
U_tot=U_tot,
|
|
916
|
+
ZPE=zpe,
|
|
917
|
+
H=H,
|
|
918
|
+
S_trans=S_trans,
|
|
919
|
+
S_rot=S_rot,
|
|
920
|
+
S_vib=S_vib,
|
|
921
|
+
S_el=S_el,
|
|
922
|
+
S_tot=S_tot,
|
|
923
|
+
c_el=c_el,
|
|
924
|
+
c_trans=c_trans,
|
|
925
|
+
c_rot=c_rot,
|
|
926
|
+
c_vib=c_vib,
|
|
927
|
+
c_tot=c_tot,
|
|
928
|
+
TS_trans=T * S_trans,
|
|
929
|
+
TS_rot=T * S_rot,
|
|
930
|
+
TS_vib=T * S_vib,
|
|
931
|
+
TS_el=T * S_el,
|
|
932
|
+
TS_tot=T * S_tot,
|
|
933
|
+
G=G,
|
|
934
|
+
dG=dG,
|
|
935
|
+
)
|
|
936
|
+
return thermo
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def print_thermo_results(thermo_results: ThermoResults):
|
|
940
|
+
au2CalMol = 1 / J2AU * NA * J2CAL
|
|
941
|
+
|
|
942
|
+
def toCalMol(E):
|
|
943
|
+
return f"{E*au2CalMol: >14.2f} cal/mol"
|
|
944
|
+
|
|
945
|
+
def StoCalKMol(S):
|
|
946
|
+
return f"{S*au2CalMol: >12.2f} cal/(K mol)"
|
|
947
|
+
|
|
948
|
+
def CtoCalKMol(c):
|
|
949
|
+
return f"{c*J2CAL: >12.4f} cal/(K mol)"
|
|
950
|
+
|
|
951
|
+
def part_func(Q):
|
|
952
|
+
return f"{Q: >12.8e}"
|
|
953
|
+
|
|
954
|
+
def print_line(key, fmt_func, num):
|
|
955
|
+
print(f"{key: >18s} = {fmt_func(num)}")
|
|
956
|
+
|
|
957
|
+
def pQ(key, num):
|
|
958
|
+
print_line(f"Q_{key}", part_func, num)
|
|
959
|
+
|
|
960
|
+
def pU(key, num):
|
|
961
|
+
print_line(f"U_{key}", toCalMol, num)
|
|
962
|
+
|
|
963
|
+
def pS(key, num):
|
|
964
|
+
print_line(f"S_{key}", StoCalKMol, num)
|
|
965
|
+
|
|
966
|
+
def pC(key, num):
|
|
967
|
+
print_line(f"C_{key}", CtoCalKMol, num)
|
|
968
|
+
|
|
969
|
+
tr = thermo_results
|
|
970
|
+
T = tr.T
|
|
971
|
+
print(
|
|
972
|
+
f"Thermochemistry @ {T:.2f} K and {tr.p:.6e} Pa, '{tr.kind.upper()}' analysis.\n"
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
print("Partition functions:")
|
|
976
|
+
pQ("el", tr.Q_el)
|
|
977
|
+
pQ("trans", tr.Q_trans)
|
|
978
|
+
pQ("rot", tr.Q_rot)
|
|
979
|
+
pQ("vib BOT", tr.Q_vib)
|
|
980
|
+
pQ("vib V0", tr.Q_vib_V0)
|
|
981
|
+
print()
|
|
982
|
+
|
|
983
|
+
print("Zero-point energy")
|
|
984
|
+
print_line("ZPE", toCalMol, tr.ZPE)
|
|
985
|
+
print()
|
|
986
|
+
|
|
987
|
+
print("Internal energy")
|
|
988
|
+
pU("el", tr.U_el)
|
|
989
|
+
pU("trans", tr.U_trans)
|
|
990
|
+
pU("rot", tr.U_rot)
|
|
991
|
+
pU("vib (incl. ZPE)", tr.U_vib)
|
|
992
|
+
pU("therm", tr.U_therm)
|
|
993
|
+
print("U_tot = U_el + U_trans + U_rot + U_vib")
|
|
994
|
+
pU("tot", tr.U_tot)
|
|
995
|
+
print()
|
|
996
|
+
|
|
997
|
+
print("Entropy contributions:")
|
|
998
|
+
pS("el", tr.S_el)
|
|
999
|
+
pS("trans", tr.S_trans)
|
|
1000
|
+
pS("rot", tr.S_rot)
|
|
1001
|
+
pS("vib", tr.S_vib)
|
|
1002
|
+
print("S_tot = S_el + S_trans + S_rot + S_vib")
|
|
1003
|
+
pS("tot", tr.S_tot)
|
|
1004
|
+
print()
|
|
1005
|
+
|
|
1006
|
+
print("Heat capacities:")
|
|
1007
|
+
pC("el", tr.c_el)
|
|
1008
|
+
pC("trans", tr.c_trans)
|
|
1009
|
+
pC("rot", tr.c_rot)
|
|
1010
|
+
pC("vib", tr.c_vib)
|
|
1011
|
+
pC("tot", tr.c_tot)
|