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,726 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
import io
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import textwrap
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pyparsing as pp
|
|
11
|
+
|
|
12
|
+
from pysisyphus.calculators.OverlapCalculator import OverlapCalculator
|
|
13
|
+
from pysisyphus.constants import AU2EV, BOHR2ANG
|
|
14
|
+
from pysisyphus.helpers_pure import file_or_str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Gaussian16(OverlapCalculator):
|
|
18
|
+
conf_key = "gaussian16"
|
|
19
|
+
_set_plans = (
|
|
20
|
+
{"key": "chk", "condition": lambda self: self.keep_chk},
|
|
21
|
+
"fchk",
|
|
22
|
+
("fchk", "mwfn_wf"),
|
|
23
|
+
"dump_635r",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
route,
|
|
29
|
+
gbs="",
|
|
30
|
+
gen="",
|
|
31
|
+
keep_chk=False,
|
|
32
|
+
stable="",
|
|
33
|
+
fchk=None,
|
|
34
|
+
**kwargs,
|
|
35
|
+
):
|
|
36
|
+
super().__init__(**kwargs)
|
|
37
|
+
|
|
38
|
+
self.route = route.lower()
|
|
39
|
+
invalid_keywords = ("symmetry", "nosymm", "force", "opt", "freq", "irc")
|
|
40
|
+
invalid_kws_given = [kw for kw in invalid_keywords if kw in self.route]
|
|
41
|
+
assert (
|
|
42
|
+
not invalid_kws_given
|
|
43
|
+
), f"Invalid keywords given in route: {invalid_kws_given}. Please remove them."
|
|
44
|
+
self.gbs = gbs
|
|
45
|
+
assert "@" not in gbs, "Give only the path to the .gbs file, " "without the @!"
|
|
46
|
+
self.gen = gen
|
|
47
|
+
self.keep_chk = keep_chk
|
|
48
|
+
self.stable = stable
|
|
49
|
+
self.fchk = fchk
|
|
50
|
+
|
|
51
|
+
keywords = {
|
|
52
|
+
kw: option
|
|
53
|
+
for kw, option in [self.parse_keyword(kw) for kw in self.route.split()]
|
|
54
|
+
}
|
|
55
|
+
exc_keyword = [key for key in "td tda cis".split() if key in keywords]
|
|
56
|
+
self.root = None
|
|
57
|
+
self.nstates = None
|
|
58
|
+
if exc_keyword:
|
|
59
|
+
self.exc_key = exc_keyword[0]
|
|
60
|
+
exc_dict = keywords[self.exc_key]
|
|
61
|
+
self.nstates = int(exc_dict["nstates"])
|
|
62
|
+
try:
|
|
63
|
+
self.root = int(exc_dict["root"])
|
|
64
|
+
except KeyError:
|
|
65
|
+
self.root = 1
|
|
66
|
+
self.log("No explicit root was specified! Using root=1 as default!")
|
|
67
|
+
# Collect remaining options if specified
|
|
68
|
+
self.exc_args = {
|
|
69
|
+
k: v for k, v in exc_dict.items() if k not in ("nstates", "root")
|
|
70
|
+
}
|
|
71
|
+
# Delete exc keyword, as we build it later on
|
|
72
|
+
self.route = re.sub(r"((?:td|cis|tda).+?(:?\s|$))", "", self.route)
|
|
73
|
+
|
|
74
|
+
self.to_keep = ("com", "fchk", "log", "dump_635r", "input.xyz")
|
|
75
|
+
if self.keep_chk:
|
|
76
|
+
self.to_keep += (".chk",)
|
|
77
|
+
|
|
78
|
+
self.fn_base = "gaussian16"
|
|
79
|
+
self.inp_fn = f"{self.fn_base}.com"
|
|
80
|
+
self.out_fn = f"{self.fn_base}.log"
|
|
81
|
+
self.chk_fn = f"{self.fn_base}.chk"
|
|
82
|
+
self.dump_base_fn = f"{self.fn_base}_rwfdump"
|
|
83
|
+
|
|
84
|
+
self.gaussian_input = """
|
|
85
|
+
%nproc={pal}
|
|
86
|
+
%mem={mem}MB
|
|
87
|
+
{chk_link0}
|
|
88
|
+
{add_link0}
|
|
89
|
+
#P {calc_type} {route} {exc}
|
|
90
|
+
# Symmetry=None {reuse_data}
|
|
91
|
+
|
|
92
|
+
title
|
|
93
|
+
|
|
94
|
+
{charge} {mult}
|
|
95
|
+
{coords}
|
|
96
|
+
|
|
97
|
+
{gen}{gbs}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
self.gaussian_input = textwrap.dedent(self.gaussian_input)
|
|
103
|
+
|
|
104
|
+
self.parser_funcs = {
|
|
105
|
+
"force": self.parse_force,
|
|
106
|
+
"hessian": self.parse_hessian,
|
|
107
|
+
"stable": self.parse_stable,
|
|
108
|
+
"noparse": lambda path: None,
|
|
109
|
+
"double_mol": self.parse_double_mol,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
self.base_cmd = self.get_cmd("cmd")
|
|
113
|
+
self.formchk_cmd = self.get_cmd("formchk")
|
|
114
|
+
self.unfchk_cmd = self.get_cmd("unfchk")
|
|
115
|
+
self.rwfdump_cmd = self.get_cmd("rwfdump")
|
|
116
|
+
self.log(
|
|
117
|
+
f"Using commands: g16={self.base_cmd}, formchk={self.formchk_cmd}, "
|
|
118
|
+
f"unfchk={self.unfchk_cmd}, rwfdump={self.rwfdump_cmd}"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def make_exc_str(self):
|
|
122
|
+
# Ground state calculation
|
|
123
|
+
if not self.root:
|
|
124
|
+
return ""
|
|
125
|
+
root = f"root={self.root}"
|
|
126
|
+
nstates = f"nstates={self.nstates}"
|
|
127
|
+
pair2str = lambda k, v: f"{k}" + (f"={v}" if v else "")
|
|
128
|
+
arg_str = ",".join([pair2str(k, v) for k, v in self.exc_args.items()])
|
|
129
|
+
exc_str = f"{self.exc_key}=({root},{nstates},{arg_str})"
|
|
130
|
+
return exc_str
|
|
131
|
+
|
|
132
|
+
def reuse_data(self, path):
|
|
133
|
+
# Nothing to reuse if no fchk or chk present
|
|
134
|
+
if (self.fchk is None) and not hasattr(self, "chk"):
|
|
135
|
+
return ""
|
|
136
|
+
new_chk = path / self.chk_fn
|
|
137
|
+
prev_fchk = new_chk.with_suffix(".fchk")
|
|
138
|
+
shutil.copy(self.fchk, prev_fchk)
|
|
139
|
+
cmd = f"{self.unfchk_cmd} {prev_fchk}".split()
|
|
140
|
+
subprocess.run(cmd, stdout=subprocess.PIPE, cwd=path)
|
|
141
|
+
self.log(f"Using MO guess from '{self.fchk}'.")
|
|
142
|
+
|
|
143
|
+
reuse_str = "guess=read"
|
|
144
|
+
# Also try to reuse information of previous TD calculation
|
|
145
|
+
# if self.nstates and hasattr(self, "chk"):
|
|
146
|
+
# DISABLED for now, as gaussian also resorts the states
|
|
147
|
+
# internally, which then messes up our internal numbering
|
|
148
|
+
# of states.
|
|
149
|
+
# shutil.copy(self.chk, new_chk)
|
|
150
|
+
# reuse_str += " td=read"
|
|
151
|
+
# self.log("Using td=read")
|
|
152
|
+
|
|
153
|
+
return reuse_str
|
|
154
|
+
|
|
155
|
+
def make_gbs_str(self):
|
|
156
|
+
if self.gbs:
|
|
157
|
+
return f"@{self.gbs}"
|
|
158
|
+
else:
|
|
159
|
+
return ""
|
|
160
|
+
|
|
161
|
+
def prepare_input(
|
|
162
|
+
self, atoms, coords, calc_type, did_stable=False, point_charges=None
|
|
163
|
+
):
|
|
164
|
+
path = self.prepare_path(use_in_run=True)
|
|
165
|
+
xyz_str = self.prepare_xyz_string(atoms, coords)
|
|
166
|
+
with open(path / "input.xyz", "w") as handle:
|
|
167
|
+
handle.write(xyz_str)
|
|
168
|
+
|
|
169
|
+
coords = self.prepare_coords(atoms, coords)
|
|
170
|
+
kwargs = {
|
|
171
|
+
"pal": self.pal,
|
|
172
|
+
"mem": self.pal * self.mem,
|
|
173
|
+
"chk_link0": f"%chk={self.chk_fn}",
|
|
174
|
+
"add_link0": "",
|
|
175
|
+
"route": self.route,
|
|
176
|
+
# td/tda/cis(...)
|
|
177
|
+
"exc": self.make_exc_str(),
|
|
178
|
+
# guess=read td=read
|
|
179
|
+
"reuse_data": self.reuse_data(path),
|
|
180
|
+
"calc_type": calc_type,
|
|
181
|
+
"charge": self.charge,
|
|
182
|
+
"mult": self.mult,
|
|
183
|
+
"coords": coords,
|
|
184
|
+
"gbs": self.make_gbs_str(),
|
|
185
|
+
"gen": self.gen,
|
|
186
|
+
}
|
|
187
|
+
if calc_type == "double_mol":
|
|
188
|
+
update = {
|
|
189
|
+
"chk_link0": "",
|
|
190
|
+
"add_link0": "%KJob L302 1",
|
|
191
|
+
"route": self.route + " iop(3/33=1) geom=notest",
|
|
192
|
+
"calc_type": "",
|
|
193
|
+
"reuse_data": "",
|
|
194
|
+
"exc": "",
|
|
195
|
+
}
|
|
196
|
+
kwargs.update(update)
|
|
197
|
+
# stable and td/tda can't be used together, so we omit the
|
|
198
|
+
# td/tda string here
|
|
199
|
+
if "stable" in calc_type.lower():
|
|
200
|
+
kwargs["exc"] = ""
|
|
201
|
+
# after a stability analysis we already got a converged wavefuntion
|
|
202
|
+
# and we dont want to 'optimize' it any further
|
|
203
|
+
if did_stable:
|
|
204
|
+
kwargs["route"] += " scf=maxcycles=0"
|
|
205
|
+
if point_charges is not None:
|
|
206
|
+
kwargs["route"] += " charge"
|
|
207
|
+
point_charges = point_charges.copy()
|
|
208
|
+
point_charges[:, :3] *= BOHR2ANG
|
|
209
|
+
with io.StringIO() as handle:
|
|
210
|
+
np.savetxt(handle, point_charges, fmt="%16.10f")
|
|
211
|
+
pc_str = handle.getvalue()
|
|
212
|
+
# Append point charges to coords
|
|
213
|
+
kwargs["coords"] += "\n\n" + pc_str
|
|
214
|
+
inp = self.gaussian_input.format(**kwargs)
|
|
215
|
+
return inp
|
|
216
|
+
|
|
217
|
+
def make_fchk(self, path):
|
|
218
|
+
cmd = f"{self.formchk_cmd} {self.chk_fn}".split()
|
|
219
|
+
subprocess.run(cmd, stdout=subprocess.PIPE, cwd=path)
|
|
220
|
+
self.log("Created .fchk")
|
|
221
|
+
|
|
222
|
+
def run_rwfdump(self, path, rwf_index, chk_path=None):
|
|
223
|
+
if chk_path is None:
|
|
224
|
+
chk_path = path / self.chk_fn
|
|
225
|
+
dump_fn = path / f"{self.dump_base_fn}_dump_{rwf_index}"
|
|
226
|
+
cmd = f"rwfdump {chk_path} {dump_fn} {rwf_index}".split()
|
|
227
|
+
subprocess.run(cmd)
|
|
228
|
+
self.log(f"Dumped {rwf_index} from .chk.")
|
|
229
|
+
return dump_fn
|
|
230
|
+
|
|
231
|
+
def run_after(self, path):
|
|
232
|
+
chk_path = path / self.chk_fn
|
|
233
|
+
if not chk_path.exists():
|
|
234
|
+
self.log("No .chk file found.")
|
|
235
|
+
return
|
|
236
|
+
# Create the .fchk file so we can keep it and parse it later on.
|
|
237
|
+
self.make_fchk(path)
|
|
238
|
+
if self.track:
|
|
239
|
+
self.run_rwfdump(path, "635r")
|
|
240
|
+
self.nmos, self.roots = self.parse_log(path / self.out_fn)
|
|
241
|
+
|
|
242
|
+
def parse_keyword(self, text):
|
|
243
|
+
word = pp.Word(pp.alphanums + "-" + "/")
|
|
244
|
+
|
|
245
|
+
keyword = word.setResultsName("keyword")
|
|
246
|
+
equals = pp.Literal("=")
|
|
247
|
+
option = pp.Group(
|
|
248
|
+
word
|
|
249
|
+
+ pp.Suppress(pp.Optional(equals))
|
|
250
|
+
+ pp.Optional(word, default="")
|
|
251
|
+
+ pp.Suppress(pp.Optional(","))
|
|
252
|
+
)
|
|
253
|
+
options = (
|
|
254
|
+
pp.Suppress(pp.Optional(equals))
|
|
255
|
+
+ pp.Suppress(pp.Optional("("))
|
|
256
|
+
+ pp.OneOrMore(option)
|
|
257
|
+
+ pp.Suppress(pp.Optional(")"))
|
|
258
|
+
).setResultsName("options")
|
|
259
|
+
|
|
260
|
+
parser = keyword + pp.Optional(options, default=[])
|
|
261
|
+
|
|
262
|
+
result = parser.parseString(text)
|
|
263
|
+
as_dict = result.asDict()
|
|
264
|
+
kw = as_dict["keyword"]
|
|
265
|
+
opt_dict = {key: value for key, value in as_dict["options"]}
|
|
266
|
+
return kw, opt_dict
|
|
267
|
+
|
|
268
|
+
@staticmethod
|
|
269
|
+
def parse_fchk(fchk_path, keys):
|
|
270
|
+
with open(fchk_path) as handle:
|
|
271
|
+
text = handle.read()
|
|
272
|
+
|
|
273
|
+
def to_float(s, loc, toks):
|
|
274
|
+
return float(toks[0])
|
|
275
|
+
|
|
276
|
+
# Matches -4.10693837E-16 and 1.60184209E-15
|
|
277
|
+
float_ = pp.Word(pp.nums + "+-E.").setParseAction(to_float)
|
|
278
|
+
# Start with Empty so we can progessively build the
|
|
279
|
+
# parser for all keys.
|
|
280
|
+
parser = pp.Empty()
|
|
281
|
+
|
|
282
|
+
def parser_for_key(key):
|
|
283
|
+
return pp.Group(
|
|
284
|
+
pp.Suppress(pp.SkipTo(key))
|
|
285
|
+
+ key
|
|
286
|
+
+ pp.restOfLine
|
|
287
|
+
+ pp.ZeroOrMore(float_)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
for key in keys:
|
|
291
|
+
parser += parser_for_key(key)
|
|
292
|
+
results = parser.parseString(text)
|
|
293
|
+
results_dict = {}
|
|
294
|
+
for key, res in zip(keys, results):
|
|
295
|
+
# This handles scalar entries like
|
|
296
|
+
# Total Energy [...] R -9.219940072302333E+01
|
|
297
|
+
if len(res) == 2:
|
|
298
|
+
results_dict[key] = float(res[-1].split()[-1])
|
|
299
|
+
# This handles matrices like
|
|
300
|
+
# Cartesian Gradient [...] R N= 9 \
|
|
301
|
+
# [Matrix entries]
|
|
302
|
+
if len(res) > 2:
|
|
303
|
+
results_dict[key] = np.array(res[2:])
|
|
304
|
+
return results_dict
|
|
305
|
+
|
|
306
|
+
def parse_all_energies(self, fchk=None):
|
|
307
|
+
if fchk is None:
|
|
308
|
+
fchk = self.fchk
|
|
309
|
+
with open(fchk) as handle:
|
|
310
|
+
text = handle.read()
|
|
311
|
+
|
|
312
|
+
float_ = r"([\d\-\+Ee\.]+)"
|
|
313
|
+
gs_re = re.compile(r"SCF Energy\s+R\s+" + float_)
|
|
314
|
+
gs_mobj = gs_re.search(text)
|
|
315
|
+
gs_energy = float(gs_mobj[1])
|
|
316
|
+
# cis_re = re.compile(r"CIS Energy\s+R\s+" + float_)
|
|
317
|
+
# cis_mobj = cis_re.search(text)
|
|
318
|
+
# cis_energy = float(cis_mobj[1])
|
|
319
|
+
etrans_re = re.compile(
|
|
320
|
+
r"ETran state values\s+R\s+N=\s+(\d+)([\d\-\.Ee\+\s]+)", re.DOTALL
|
|
321
|
+
)
|
|
322
|
+
etrans_mobj = etrans_re.search(text)
|
|
323
|
+
try:
|
|
324
|
+
etrans = etrans_mobj[2].strip().split()
|
|
325
|
+
etrans = np.array(etrans, dtype=float).reshape(-1, 16)
|
|
326
|
+
exc_energies = etrans[:, 0]
|
|
327
|
+
all_energies = np.zeros(exc_energies.size + 1)
|
|
328
|
+
all_energies[0] = gs_energy
|
|
329
|
+
all_energies[1:] = exc_energies
|
|
330
|
+
except TypeError:
|
|
331
|
+
all_energies = np.array((gs_energy,))
|
|
332
|
+
return all_energies
|
|
333
|
+
|
|
334
|
+
def store_and_track(self, results, func, atoms, coords, **prepare_kwargs):
|
|
335
|
+
if self.track:
|
|
336
|
+
self.store_overlap_data(atoms, coords)
|
|
337
|
+
if self.track_root():
|
|
338
|
+
# Redo the calculation with the updated root
|
|
339
|
+
results = func(atoms, coords, **prepare_kwargs)
|
|
340
|
+
results["all_energies"] = self.parse_all_energies()
|
|
341
|
+
return results
|
|
342
|
+
|
|
343
|
+
def get_energy(self, atoms, coords, **prepare_kwargs):
|
|
344
|
+
results = self.get_forces(atoms, coords, **prepare_kwargs)
|
|
345
|
+
results = self.store_and_track(
|
|
346
|
+
results, self.get_energy, atoms, coords, **prepare_kwargs
|
|
347
|
+
)
|
|
348
|
+
return results
|
|
349
|
+
|
|
350
|
+
def get_forces(self, atoms, coords, **prepare_kwargs):
|
|
351
|
+
did_stable = False
|
|
352
|
+
if self.stable:
|
|
353
|
+
is_stable = self.run_stable(atoms, coords)
|
|
354
|
+
self.log(f"Wavefunction is now stable: {is_stable}")
|
|
355
|
+
did_stable = True
|
|
356
|
+
inp = self.prepare_input(
|
|
357
|
+
atoms, coords, "force", did_stable=did_stable, **prepare_kwargs
|
|
358
|
+
)
|
|
359
|
+
kwargs = {
|
|
360
|
+
"calc": "force",
|
|
361
|
+
}
|
|
362
|
+
results = self.run(inp, **kwargs)
|
|
363
|
+
results = self.store_and_track(
|
|
364
|
+
results, self.get_forces, atoms, coords, **prepare_kwargs
|
|
365
|
+
)
|
|
366
|
+
return results
|
|
367
|
+
|
|
368
|
+
def get_hessian(self, atoms, coords, **prepare_kwargs):
|
|
369
|
+
inp = self.prepare_input(atoms, coords, "freq", **prepare_kwargs)
|
|
370
|
+
kwargs = {
|
|
371
|
+
"calc": "hessian",
|
|
372
|
+
}
|
|
373
|
+
results = self.run(inp, **kwargs)
|
|
374
|
+
results = self.store_and_track(
|
|
375
|
+
results, self.get_hessian, atoms, coords, **prepare_kwargs
|
|
376
|
+
)
|
|
377
|
+
return results
|
|
378
|
+
|
|
379
|
+
def run_stable(self, atoms, coords, **prepare_kwargs):
|
|
380
|
+
inp = self.prepare_input(atoms, coords, self.stable, **prepare_kwargs)
|
|
381
|
+
self.log(f"Running stability analysis with {self.stable}")
|
|
382
|
+
kwargs = {
|
|
383
|
+
"calc": "stable",
|
|
384
|
+
}
|
|
385
|
+
results = self.run(inp, **kwargs)
|
|
386
|
+
return results
|
|
387
|
+
|
|
388
|
+
def parse_stable(self, path):
|
|
389
|
+
log_path = path / self.out_fn
|
|
390
|
+
with open(log_path) as handle:
|
|
391
|
+
text = handle.read()
|
|
392
|
+
instab_re = "wavefunction.*instability.*"
|
|
393
|
+
instab_mobj = re.search(instab_re, text)
|
|
394
|
+
if instab_mobj:
|
|
395
|
+
self.log("Found instability!")
|
|
396
|
+
stable_re = "wavefunction is stable"
|
|
397
|
+
mobj = re.search(stable_re, text)
|
|
398
|
+
is_stable = bool(mobj)
|
|
399
|
+
return is_stable
|
|
400
|
+
|
|
401
|
+
def run_calculation(self, atoms, coords, **prepare_kwargs):
|
|
402
|
+
inp = self.prepare_input(atoms, coords, "", **prepare_kwargs)
|
|
403
|
+
kwargs = {
|
|
404
|
+
"calc": "noparse",
|
|
405
|
+
}
|
|
406
|
+
results = self.run(inp, **kwargs)
|
|
407
|
+
if self.track:
|
|
408
|
+
self.store_overlap_data(atoms, coords)
|
|
409
|
+
self.track_root()
|
|
410
|
+
self.log(
|
|
411
|
+
"This track_root() call is a bit superfluous as the "
|
|
412
|
+
"as the result is ignored :)"
|
|
413
|
+
)
|
|
414
|
+
return results
|
|
415
|
+
|
|
416
|
+
def run_double_mol_calculation(self, atoms, coords1, coords2):
|
|
417
|
+
self.log("Running double molecule calculation")
|
|
418
|
+
double_atoms = atoms + atoms
|
|
419
|
+
double_coords = np.hstack((coords1, coords2))
|
|
420
|
+
inp = self.prepare_input(double_atoms, double_coords, "double_mol")
|
|
421
|
+
kwargs = {
|
|
422
|
+
"calc": "double_mol",
|
|
423
|
+
"keep": False,
|
|
424
|
+
"inc_counter": False,
|
|
425
|
+
"run_after": False,
|
|
426
|
+
}
|
|
427
|
+
double_mol_ovlps = self.run(inp, **kwargs)
|
|
428
|
+
return double_mol_ovlps
|
|
429
|
+
|
|
430
|
+
def parse_tddft(self, path):
|
|
431
|
+
with open(path / self.out_fn) as handle:
|
|
432
|
+
text = handle.read()
|
|
433
|
+
td_re = r"Excited State\s*\d+:\s*[\.\w\?-]+\s*([\d\.-]+?)\s*eV"
|
|
434
|
+
matches = re.findall(td_re, text)
|
|
435
|
+
assert len(matches) == self.nstates
|
|
436
|
+
# Excitation energies in eV
|
|
437
|
+
exc_energies = np.array(matches, dtype=np.float64)
|
|
438
|
+
# Convert to Hartree
|
|
439
|
+
exc_energies /= AU2EV
|
|
440
|
+
return exc_energies
|
|
441
|
+
|
|
442
|
+
@file_or_str(".log", method=True)
|
|
443
|
+
def parse_log(self, text):
|
|
444
|
+
def parse(text, regex, func):
|
|
445
|
+
mobj = re.search(regex, text)
|
|
446
|
+
return func(mobj[1])
|
|
447
|
+
|
|
448
|
+
# Depending on wether we did the calculation with td=read or not
|
|
449
|
+
# roots will be at a different value. Without reading the CI coeffs
|
|
450
|
+
# from the checkpoint Gaussian will calculate four times as much roots
|
|
451
|
+
# as requested in the first iterations of the calculation. This will
|
|
452
|
+
# lead to a much higher number of expected number of CI-coefficients
|
|
453
|
+
# when parsing the 635r dump later on.
|
|
454
|
+
roots_re = r"Root\s+(\d+)"
|
|
455
|
+
roots = np.array(re.findall(roots_re, text), dtype=int).max()
|
|
456
|
+
|
|
457
|
+
# NBasis= 16 NAE= 12 NBE= 12 NFC= 6 NFV= 0
|
|
458
|
+
basis_re = "NBasis=(.+)NAE=(.+)NBE=(.+)NFC=(.+)NFV=(.+)"
|
|
459
|
+
basis_mobj = re.search(basis_re, text)
|
|
460
|
+
basis_funcs, alpha, beta, _, _ = [int(n) for n in basis_mobj.groups()]
|
|
461
|
+
a_occ = alpha
|
|
462
|
+
b_occ = beta
|
|
463
|
+
a_vir = basis_funcs - a_occ
|
|
464
|
+
b_vir = basis_funcs - b_occ
|
|
465
|
+
restricted = alpha == beta
|
|
466
|
+
act_re = "NROrb=(.*)NOA=(.*)NOB=(.*)NVA=(.*)NVB=(.*)"
|
|
467
|
+
act_mobj = re.search(act_re, text)
|
|
468
|
+
_, a_act, b_act, _, _ = [int(n) for n in act_mobj.groups()]
|
|
469
|
+
a_core = a_occ - a_act
|
|
470
|
+
b_core = b_occ - b_act
|
|
471
|
+
|
|
472
|
+
NMOs = namedtuple(
|
|
473
|
+
"NMOs",
|
|
474
|
+
"a_core, a_occ a_act a_vir " "b_core b_occ b_act b_vir " "restricted",
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
nmos = NMOs(
|
|
478
|
+
a_core, a_occ, a_act, a_vir, b_core, b_occ, b_act, b_vir, restricted
|
|
479
|
+
)
|
|
480
|
+
self.log(str(nmos))
|
|
481
|
+
return nmos, roots
|
|
482
|
+
|
|
483
|
+
def parse_635r_dump(self, dump_path, roots, nmos):
|
|
484
|
+
self.log(f"Parsing 635r dump '{dump_path}'")
|
|
485
|
+
with open(dump_path) as handle:
|
|
486
|
+
text = handle.read()
|
|
487
|
+
regex = r"read left to right\):\s*(.+)"
|
|
488
|
+
mobj = re.search(regex, text, re.DOTALL)
|
|
489
|
+
arr_str = mobj[1].replace("D", "E")
|
|
490
|
+
# Drop the first 12 items as they are always 0
|
|
491
|
+
tmp = np.array(arr_str.split()[12:], dtype=np.float64)
|
|
492
|
+
|
|
493
|
+
# the core electrons are frozen in TDDFT/TDA
|
|
494
|
+
expected = (nmos.a_act * nmos.a_vir + nmos.b_act * nmos.b_vir) * roots * 2
|
|
495
|
+
self.log(
|
|
496
|
+
f"Expecting {expected} ci coefficients in {dump_path}. "
|
|
497
|
+
f"There are {tmp.size} items (including eigenvalues)."
|
|
498
|
+
)
|
|
499
|
+
coeffs = tmp[:expected]
|
|
500
|
+
# 1. dim: X+Y, X-Y -> 2
|
|
501
|
+
# 2. dim: roots -> variable, usually higher than Nstates as gaussian
|
|
502
|
+
# calculates more roots in the first iteration of TDDFT
|
|
503
|
+
# 3. dim: alpha, beta -> 2
|
|
504
|
+
# The remainder depends on the number of alpha and beta electrons.
|
|
505
|
+
# The vectors are given for active orbs. x virt. obs that is the
|
|
506
|
+
# core orbitals are neglected.
|
|
507
|
+
if nmos.restricted:
|
|
508
|
+
XpY, XmY = coeffs.reshape(2, roots, 2, -1)
|
|
509
|
+
X = 0.5 * (XpY + XmY)
|
|
510
|
+
# Within a TDA calculation XpY and XmY are the same and
|
|
511
|
+
# Y will only contain zeros.
|
|
512
|
+
Y = XpY - X
|
|
513
|
+
# In the unrestricted case we can't use 2 for the 3. dimension anymore
|
|
514
|
+
# (same number of alpha and beta electrons). The sizes for the respective
|
|
515
|
+
# spins would be (a_act*a_vir) and (b_act*b_vir).
|
|
516
|
+
else:
|
|
517
|
+
raise Exception("Unrestricted not supported yet!")
|
|
518
|
+
# Shapes of X and Y
|
|
519
|
+
# 1. dim: roots (more than initially requested) -> variable
|
|
520
|
+
# 2. dim: alpha, beta -> 2
|
|
521
|
+
# 3. dim: act. x virt. -> variable
|
|
522
|
+
|
|
523
|
+
# xpy_fn = f"{dump_fn}_XpY"
|
|
524
|
+
# np.save(xpy_fn, XpY)
|
|
525
|
+
# xmy_fn = f"{dump_fn}_XmY"
|
|
526
|
+
# np.save(xmy_fn, XmY)
|
|
527
|
+
# x_fn = f"{dump_fn}_X"
|
|
528
|
+
# np.save(x_fn, X)
|
|
529
|
+
|
|
530
|
+
# As we only handle restricted calculations for now the alpha
|
|
531
|
+
# and beta part are identical. So we only keep the alpha part.
|
|
532
|
+
if self.nmos.restricted:
|
|
533
|
+
# Drop beta part and restrict to the requested states
|
|
534
|
+
X = X[: self.nstates, 0, :]
|
|
535
|
+
Y = Y[: self.nstates, 0, :]
|
|
536
|
+
|
|
537
|
+
X_full = np.zeros((self.nstates, nmos.a_occ, nmos.a_vir))
|
|
538
|
+
X_full[:, nmos.a_core :] = X.reshape(-1, nmos.a_act, nmos.a_vir)
|
|
539
|
+
Y_full = np.zeros((self.nstates, nmos.a_occ, nmos.a_vir))
|
|
540
|
+
Y_full[:, nmos.a_core :] = Y.reshape(-1, nmos.a_act, nmos.a_vir)
|
|
541
|
+
|
|
542
|
+
return X_full, Y_full
|
|
543
|
+
|
|
544
|
+
def prepare_overlap_data(self, path):
|
|
545
|
+
# Parse X eigenvector from 635r dump
|
|
546
|
+
X, Y = self.parse_635r_dump(self.dump_635r, self.roots, self.nmos)
|
|
547
|
+
|
|
548
|
+
# From http://gaussian.com/cis/, Examples tab, Normalization:
|
|
549
|
+
#
|
|
550
|
+
# 'For closed shell calculations, the sum of the squares of the
|
|
551
|
+
# expansion coefficients is normalized to total 1/2 (as the beta
|
|
552
|
+
# coefficients are not shown).'
|
|
553
|
+
#
|
|
554
|
+
# Right now we only deal with restricted calculatios, so alpha == beta
|
|
555
|
+
# and we ignore beta. So we are lacking a factor of sqrt(2). Another
|
|
556
|
+
# option would be to normalize all states to 1.
|
|
557
|
+
# ci_coeffs *= 2**0.5
|
|
558
|
+
|
|
559
|
+
# Parse mo coefficients from .fchk file and write a 'fake' turbomole
|
|
560
|
+
# mos file.
|
|
561
|
+
keys = (
|
|
562
|
+
"SCF Energy",
|
|
563
|
+
"Alpha Orbital Energies",
|
|
564
|
+
"Alpha MO coefficients",
|
|
565
|
+
"ETran state values",
|
|
566
|
+
)
|
|
567
|
+
scf_key, mo_energies_key, mo_key, exc_key = keys
|
|
568
|
+
fchk_dict = self.parse_fchk(self.fchk, keys=keys)
|
|
569
|
+
mo_energies = fchk_dict[mo_energies_key]
|
|
570
|
+
C = fchk_dict[mo_key].T
|
|
571
|
+
C = C.reshape(-1, mo_energies.size)
|
|
572
|
+
|
|
573
|
+
gs_energy = fchk_dict[scf_key]
|
|
574
|
+
exc_data = fchk_dict[exc_key].reshape(-1, 16)
|
|
575
|
+
exc_energies = exc_data[:, 0]
|
|
576
|
+
all_energies = np.zeros(len(exc_energies) + 1)
|
|
577
|
+
all_energies[0] = gs_energy
|
|
578
|
+
all_energies[1:] += exc_energies
|
|
579
|
+
return C, X, Y, all_energies
|
|
580
|
+
|
|
581
|
+
def parse_force(self, path):
|
|
582
|
+
results = {}
|
|
583
|
+
keys = ("SCF Energy", "Total Energy", "Cartesian Gradient")
|
|
584
|
+
fchk_path = Path(path) / f"{self.fn_base}.fchk"
|
|
585
|
+
fchk_dict = self.parse_fchk(fchk_path, keys)
|
|
586
|
+
results["energy"] = fchk_dict["SCF Energy"]
|
|
587
|
+
results["forces"] = -fchk_dict["Cartesian Gradient"]
|
|
588
|
+
|
|
589
|
+
if self.nstates:
|
|
590
|
+
# This sets the proper excited state energy in the
|
|
591
|
+
# results dict and also stores all energies.
|
|
592
|
+
exc_energies = self.parse_tddft(path)
|
|
593
|
+
# G16 root input is 1 based, so we substract 1 to get
|
|
594
|
+
# the right index here.
|
|
595
|
+
root_exc_en = exc_energies[self.root - 1]
|
|
596
|
+
gs_energy = fchk_dict["SCF Energy"]
|
|
597
|
+
# Add excitation energy to ground state energy.
|
|
598
|
+
results["energy"] += root_exc_en
|
|
599
|
+
# Create a new array including the ground state energy
|
|
600
|
+
# to save the energies of all states.
|
|
601
|
+
all_ens = np.full(len(exc_energies) + 1, gs_energy)
|
|
602
|
+
all_ens[1:] += exc_energies
|
|
603
|
+
results["all_energies"] = all_ens
|
|
604
|
+
|
|
605
|
+
return results
|
|
606
|
+
|
|
607
|
+
def parse_hessian(self, path):
|
|
608
|
+
keys = (
|
|
609
|
+
"Total Energy",
|
|
610
|
+
"Cartesian Gradient",
|
|
611
|
+
"Cartesian Force Constants",
|
|
612
|
+
)
|
|
613
|
+
fchk_path = Path(path) / f"{self.fn_base}.fchk"
|
|
614
|
+
fchk_dict = self.parse_fchk(fchk_path, keys=keys)
|
|
615
|
+
en_key, grad_key, hess_key = keys
|
|
616
|
+
grad = fchk_dict[grad_key]
|
|
617
|
+
tril_hess = fchk_dict[hess_key]
|
|
618
|
+
tril_inds = np.tril_indices(grad.size)
|
|
619
|
+
full_hessian = np.zeros((grad.size, grad.size))
|
|
620
|
+
full_hessian[tril_inds] = tril_hess
|
|
621
|
+
triu_inds = np.triu_indices(grad.size, k=1)
|
|
622
|
+
full_hessian[triu_inds] = full_hessian.T[triu_inds]
|
|
623
|
+
results = {
|
|
624
|
+
"energy": fchk_dict[en_key],
|
|
625
|
+
"forces": -grad,
|
|
626
|
+
"hessian": full_hessian,
|
|
627
|
+
}
|
|
628
|
+
return results
|
|
629
|
+
|
|
630
|
+
def parse_double_mol(self, path, out_fn=None):
|
|
631
|
+
def repl_double(s, loc, toks):
|
|
632
|
+
return toks[0].replace("D", "E")
|
|
633
|
+
|
|
634
|
+
if out_fn is None:
|
|
635
|
+
out_fn = self.out_fn
|
|
636
|
+
|
|
637
|
+
with open(path / out_fn) as handle:
|
|
638
|
+
text = handle.read()
|
|
639
|
+
# Number of basis functions in the double molecule
|
|
640
|
+
nbas = int(re.search(r"NBasis =\s*(\d+)", text)[1])
|
|
641
|
+
assert nbas % 2 == 0
|
|
642
|
+
# Gaussian prints columns of a triangular matrix including
|
|
643
|
+
# the diagonal
|
|
644
|
+
backup_white = pp.ParserElement.DEFAULT_WHITE_CHARS
|
|
645
|
+
pp.ParserElement.setDefaultWhitespaceChars(" \t")
|
|
646
|
+
int_ = pp.Suppress(pp.Word(pp.nums))
|
|
647
|
+
|
|
648
|
+
float_ = pp.Word(pp.nums + ".D+-").setParseAction(repl_double)
|
|
649
|
+
nl = pp.Suppress(pp.Literal("\n"))
|
|
650
|
+
header = pp.OneOrMore(int_) + nl
|
|
651
|
+
line = int_ + pp.OneOrMore(float_) + nl
|
|
652
|
+
|
|
653
|
+
block = pp.Group(header + pp.OneOrMore(~header + line))
|
|
654
|
+
|
|
655
|
+
parser = pp.Suppress(
|
|
656
|
+
pp.SkipTo("*** Overlap *** \n", include=True)
|
|
657
|
+
) + pp.OneOrMore(block).setResultsName("blocks")
|
|
658
|
+
|
|
659
|
+
result = parser.parseFile(path / out_fn)
|
|
660
|
+
pp.ParserElement.setDefaultWhitespaceChars(backup_white)
|
|
661
|
+
|
|
662
|
+
# The full double molecule overlap matrix (square)
|
|
663
|
+
full_mat = np.zeros((nbas, nbas))
|
|
664
|
+
|
|
665
|
+
def get_block_inds(block_ind, nbas, cols=5):
|
|
666
|
+
"""Returns the indices as required to assign the matrix
|
|
667
|
+
printed by Gaussian. Gaussian prints a lower triangle
|
|
668
|
+
matrix in blocks of five columns each."""
|
|
669
|
+
start_row = block_ind * cols
|
|
670
|
+
start_col = start_row
|
|
671
|
+
inds = list()
|
|
672
|
+
for row in range(start_row, nbas):
|
|
673
|
+
col = start_col
|
|
674
|
+
while (col <= row) and (col < start_row + cols):
|
|
675
|
+
inds.append((row, col))
|
|
676
|
+
col += 1
|
|
677
|
+
return inds
|
|
678
|
+
|
|
679
|
+
for i, block in enumerate(result["blocks"]):
|
|
680
|
+
rows, cols = zip(*get_block_inds(i, nbas))
|
|
681
|
+
full_mat[rows, cols] = block.asList()
|
|
682
|
+
|
|
683
|
+
nbas_single = nbas // 2
|
|
684
|
+
"""The whole matrix consists of four blocks:
|
|
685
|
+
Original overlaps of molecule 1
|
|
686
|
+
b1 = full_mat[:nbas_single, :nbas_single]
|
|
687
|
+
Zero, as we only get the lower triangle
|
|
688
|
+
b2 = full_mat[:nbas_single, nbas_single:]
|
|
689
|
+
Double molecule overlaps between basis functions of
|
|
690
|
+
molecule 1 and molecule 2
|
|
691
|
+
b3 = full_mat[nbas_single:, :nbas_single]
|
|
692
|
+
Original overlaps of molecule 2
|
|
693
|
+
b4 = full_mat[nbas_single:, nbas_single:]
|
|
694
|
+
"""
|
|
695
|
+
double_mol_ovlp = full_mat[nbas_single:, :nbas_single]
|
|
696
|
+
return double_mol_ovlp
|
|
697
|
+
|
|
698
|
+
def parse_charges(self, path=None):
|
|
699
|
+
if path is None and self.fchk is not None:
|
|
700
|
+
fchk_path = self.fchk
|
|
701
|
+
elif path is not None and path.endswith(".fchk"):
|
|
702
|
+
fchk_path = path
|
|
703
|
+
elif path is not None:
|
|
704
|
+
fchk_path = Path(path) / f"{self.fn_base}.fchk"
|
|
705
|
+
|
|
706
|
+
keys = ("Mulliken Charges",)
|
|
707
|
+
fchk_dict = self.parse_fchk(fchk_path, keys=keys)
|
|
708
|
+
charges = np.array(fchk_dict["Mulliken Charges"])
|
|
709
|
+
|
|
710
|
+
return charges
|
|
711
|
+
|
|
712
|
+
def get_chkfiles(self):
|
|
713
|
+
return {
|
|
714
|
+
"fchk": self.fchk,
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
def set_chkfiles(self, chkfiles):
|
|
718
|
+
try:
|
|
719
|
+
fchk = chkfiles["fchk"]
|
|
720
|
+
self.fchk = fchk
|
|
721
|
+
self.log(f"Set chkfile '{fchk}' as fchk.")
|
|
722
|
+
except KeyError:
|
|
723
|
+
self.log("Found no fchk information in chkfiles!")
|
|
724
|
+
|
|
725
|
+
def __str__(self):
|
|
726
|
+
return "Gaussian16 calculator"
|