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,844 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import Enum
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import platform
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import tempfile
|
|
10
|
+
from typing import Callable, Optional
|
|
11
|
+
|
|
12
|
+
from natsort import natsorted
|
|
13
|
+
|
|
14
|
+
from pysisyphus import logger
|
|
15
|
+
from pysisyphus import helpers_pure
|
|
16
|
+
from pysisyphus.config import get_cmd, OUT_DIR_DEFAULT
|
|
17
|
+
from pysisyphus.constants import BOHR2ANG
|
|
18
|
+
from pysisyphus.helpers import geom_loader
|
|
19
|
+
from pysisyphus.linalg import finite_difference_hessian
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
KeepKind = Enum("KeepKind", ["ALL", "LATEST", "NONE"])
|
|
23
|
+
HessKind = Enum("HessKind", ["ORG", "NUMERICAL"])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class SetPlan:
|
|
28
|
+
key: str
|
|
29
|
+
name: Optional[str] = None
|
|
30
|
+
condition: Callable = lambda obj: True
|
|
31
|
+
# success: Optional[Callable] = None
|
|
32
|
+
fail: Optional[Callable] = None
|
|
33
|
+
|
|
34
|
+
def __post_init__(self):
|
|
35
|
+
if self.name is None:
|
|
36
|
+
self.name = self.key
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Calculator:
|
|
40
|
+
|
|
41
|
+
conf_key = None
|
|
42
|
+
_set_plans = []
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
calc_number=0,
|
|
47
|
+
charge=0,
|
|
48
|
+
mult=1,
|
|
49
|
+
base_name="calculator",
|
|
50
|
+
pal=1,
|
|
51
|
+
mem=1000,
|
|
52
|
+
keep_kind="all",
|
|
53
|
+
check_mem=True,
|
|
54
|
+
retry_calc=0,
|
|
55
|
+
last_calc_cycle=None,
|
|
56
|
+
clean_after=True,
|
|
57
|
+
out_dir=OUT_DIR_DEFAULT,
|
|
58
|
+
force_num_hess=False,
|
|
59
|
+
num_hess_kwargs=None,
|
|
60
|
+
):
|
|
61
|
+
"""Base-class of all calculators.
|
|
62
|
+
|
|
63
|
+
Meant to be extended.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
calc_number : int, default=0
|
|
68
|
+
Identifier of the Calculator. Used in distinguishing it from
|
|
69
|
+
other Calculators, e.g. in ChainOfStates calculations. Also
|
|
70
|
+
used in the creation of filenames.
|
|
71
|
+
charge : int, default=0
|
|
72
|
+
Molecular charge.
|
|
73
|
+
mult : int, default=1
|
|
74
|
+
Molecular multiplicity (1 = singlet, 2 = doublet, ...)
|
|
75
|
+
base_name : str, default=calculator
|
|
76
|
+
Generated filenames will start with this string.
|
|
77
|
+
pal : int, default=1
|
|
78
|
+
Positive integer that gives the number of physical cores to
|
|
79
|
+
use on 1 node.
|
|
80
|
+
mem : int, default=1000
|
|
81
|
+
Mememory per core in MB. The total amount of memory is given as
|
|
82
|
+
mem*pal.
|
|
83
|
+
check_mem : bool, default=True
|
|
84
|
+
Whether to adjust the requested memory if too much is requested.
|
|
85
|
+
retry_calc : int, default=0
|
|
86
|
+
Number of additional retries when calculation failed.
|
|
87
|
+
last_calc_cycle : int
|
|
88
|
+
Internal variable used in restarts.
|
|
89
|
+
clean_after : bool
|
|
90
|
+
Delete temporary directory were calculations were executed
|
|
91
|
+
after a calculation.
|
|
92
|
+
out_dir : str
|
|
93
|
+
Path that is prepended to generated filenames.
|
|
94
|
+
force_hess_kwargs : bool, default False
|
|
95
|
+
Force numerical Hessians.
|
|
96
|
+
num_hess_kwargs : dict
|
|
97
|
+
Keyword arguments for finite difference Hessian calculation.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
self.logger = logging.getLogger("calculator")
|
|
101
|
+
|
|
102
|
+
self.calc_number = calc_number
|
|
103
|
+
self.charge = int(charge)
|
|
104
|
+
self.mult = int(mult)
|
|
105
|
+
self.base_name = base_name
|
|
106
|
+
self.pal = int(pal)
|
|
107
|
+
assert self.pal > 0, "pal must be a non-negative integer!"
|
|
108
|
+
if check_mem:
|
|
109
|
+
mem = helpers_pure.check_mem(int(mem), pal, logger=self.logger)
|
|
110
|
+
self.mem = mem
|
|
111
|
+
self.keep_kind = KeepKind[keep_kind.upper()]
|
|
112
|
+
# Disasble retries if check_termination method is not implemented
|
|
113
|
+
self.retry_calc = int(retry_calc) if hasattr(self, "check_termination") else 0
|
|
114
|
+
assert self.retry_calc >= 0
|
|
115
|
+
try:
|
|
116
|
+
self.out_dir = Path(out_dir).resolve()
|
|
117
|
+
except TypeError:
|
|
118
|
+
self.out_dir = Path(OUT_DIR_DEFAULT).resolve()
|
|
119
|
+
self.out_dir.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
if num_hess_kwargs is None:
|
|
121
|
+
num_hess_kwargs = dict()
|
|
122
|
+
self.num_hess_kwargs = num_hess_kwargs
|
|
123
|
+
|
|
124
|
+
# Extensions of the files to keep after running a calculation.
|
|
125
|
+
# Usually overridden in derived classes.
|
|
126
|
+
self.to_keep = ()
|
|
127
|
+
# How many calculations were already run
|
|
128
|
+
self.calc_counter = 0
|
|
129
|
+
# Handle restarts
|
|
130
|
+
if last_calc_cycle:
|
|
131
|
+
self.calc_counter = int(last_calc_cycle) + 1
|
|
132
|
+
self.reattach(int(last_calc_cycle))
|
|
133
|
+
self.log(f"Set {self.calc_counter} for this calculation")
|
|
134
|
+
self.clean_after = clean_after
|
|
135
|
+
|
|
136
|
+
self.inp_fn = "calc.inp"
|
|
137
|
+
self.out_fn = "calc.out"
|
|
138
|
+
# When this is set the run() method will use this path
|
|
139
|
+
# instead of creating a new one.
|
|
140
|
+
# Currently this is only used with the Turbomole calculator.
|
|
141
|
+
self.path_already_prepared = None
|
|
142
|
+
self.last_run_path = None
|
|
143
|
+
self.backup_dir = None
|
|
144
|
+
self.kept_history = dict()
|
|
145
|
+
self.build_set_plans()
|
|
146
|
+
|
|
147
|
+
# Backup original get_hessian method
|
|
148
|
+
self._org_get_hessian = self.get_hessian
|
|
149
|
+
self.hessian_kind = HessKind["ORG"]
|
|
150
|
+
if force_num_hess:
|
|
151
|
+
self.force_num_hessian()
|
|
152
|
+
|
|
153
|
+
def get_cmd(self, key="cmd"):
|
|
154
|
+
assert self.conf_key, "'conf_key'-attribute is missing for this calculator!"
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
return get_cmd(section=self.conf_key, key=key, use_defaults=True)
|
|
158
|
+
except KeyError:
|
|
159
|
+
logger.debug(f"Failed to load key '{key}' from section '{self.conf_key}'!")
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def geom_from_fn(cls, fn, **kwargs):
|
|
163
|
+
geom = geom_loader(fn)
|
|
164
|
+
calc = cls(**kwargs)
|
|
165
|
+
geom.set_calculator(calc)
|
|
166
|
+
return geom
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def name(self):
|
|
170
|
+
return f"{self.base_name}_{self.calc_number:03d}"
|
|
171
|
+
|
|
172
|
+
def log(self, message=""):
|
|
173
|
+
"""Write a log message.
|
|
174
|
+
|
|
175
|
+
Wraps the logger variable.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
message : str
|
|
180
|
+
Message to be logged.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
self.logger.debug(f"{self.name}, cycle {self.calc_counter:03d}: {message}")
|
|
184
|
+
|
|
185
|
+
def get_energy(self, atoms, coords, **prepare_kwargs):
|
|
186
|
+
"""Meant to be extended."""
|
|
187
|
+
raise Exception("Not implemented!")
|
|
188
|
+
|
|
189
|
+
def get_forces(self, atoms, coords, **prepare_kwargs):
|
|
190
|
+
"""Meant to be extended."""
|
|
191
|
+
raise Exception("Not implemented!")
|
|
192
|
+
|
|
193
|
+
def get_hessian(self, atoms, coords, **prepare_kwargs):
|
|
194
|
+
"""Get Hessian matrix. Fall back to numerical Hessian, if not overriden.
|
|
195
|
+
|
|
196
|
+
Preferrably, this method should provide an analytical Hessian."""
|
|
197
|
+
self.log(f"Calculator {self} has no native Hessian method.")
|
|
198
|
+
return self.get_num_hessian(atoms, coords, **prepare_kwargs)
|
|
199
|
+
|
|
200
|
+
def get_num_hessian(self, atoms, coords, **prepare_kwargs):
|
|
201
|
+
self.log("Calculating numerical Hessian.")
|
|
202
|
+
results = self.get_energy(atoms, coords, **prepare_kwargs)
|
|
203
|
+
|
|
204
|
+
def grad_func(coords):
|
|
205
|
+
results = self.get_forces(atoms, coords, **prepare_kwargs)
|
|
206
|
+
gradient = -results["forces"]
|
|
207
|
+
return gradient
|
|
208
|
+
|
|
209
|
+
def callback(i, j):
|
|
210
|
+
self.log(f"Displacement {j} of coordinate {i}")
|
|
211
|
+
|
|
212
|
+
_num_hess_kwargs = {
|
|
213
|
+
"step_size": 0.005,
|
|
214
|
+
# Central difference by default
|
|
215
|
+
"acc": 2,
|
|
216
|
+
}
|
|
217
|
+
_num_hess_kwargs.update(self.num_hess_kwargs)
|
|
218
|
+
|
|
219
|
+
fd_hessian = finite_difference_hessian(
|
|
220
|
+
coords,
|
|
221
|
+
grad_func,
|
|
222
|
+
callback=callback,
|
|
223
|
+
**_num_hess_kwargs,
|
|
224
|
+
)
|
|
225
|
+
results["hessian"] = fd_hessian
|
|
226
|
+
return results
|
|
227
|
+
|
|
228
|
+
def force_num_hessian(self):
|
|
229
|
+
"""Always calculate numerical Hessians."""
|
|
230
|
+
self.log("Doing numerical Hessians from now on.")
|
|
231
|
+
self.get_hessian = self.get_num_hessian
|
|
232
|
+
self.hessian_kind = HessKind["NUMERICAL"]
|
|
233
|
+
|
|
234
|
+
def restore_org_hessian(self):
|
|
235
|
+
"""Restore original 'get_hessian' method, which may also fallback to numerical
|
|
236
|
+
Hessians, if not implemented."""
|
|
237
|
+
self.log("Restored original 'get_hessian' method.")
|
|
238
|
+
self.get_hessian = self._org_get_hessian
|
|
239
|
+
self.hessian_kind = HessKind["ORG"]
|
|
240
|
+
|
|
241
|
+
def make_fn(self, name, counter=None, return_str=False):
|
|
242
|
+
"""Make a full filename.
|
|
243
|
+
|
|
244
|
+
Return a full filename including the calculator name and the
|
|
245
|
+
current counter given a suffix.
|
|
246
|
+
|
|
247
|
+
Parameters
|
|
248
|
+
----------
|
|
249
|
+
name: str
|
|
250
|
+
Suffix of the filename.
|
|
251
|
+
counter : int, optional
|
|
252
|
+
If not given use the current calc_counter.
|
|
253
|
+
return_str : int, optional
|
|
254
|
+
Return a string instead of a Path when True.
|
|
255
|
+
|
|
256
|
+
Returns
|
|
257
|
+
-------
|
|
258
|
+
fn : str
|
|
259
|
+
Filename.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
if counter is None:
|
|
263
|
+
counter = self.calc_counter
|
|
264
|
+
fn = self.out_dir / f"{self.name}.{counter:03d}.{name}"
|
|
265
|
+
if return_str:
|
|
266
|
+
fn = str(fn)
|
|
267
|
+
return fn
|
|
268
|
+
|
|
269
|
+
def prepare_path(self, use_in_run=False):
|
|
270
|
+
"""Get a temporary directory handle.
|
|
271
|
+
|
|
272
|
+
Create a temporary directory that can later be used in a calculation.
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
use_in_run : bool, option
|
|
277
|
+
Sets the internal variable ``self.path_already_prepared`` that
|
|
278
|
+
is later read by ``self.run()``. No new temporary directory will
|
|
279
|
+
be created in ``self.run()``.
|
|
280
|
+
|
|
281
|
+
Returns
|
|
282
|
+
-------
|
|
283
|
+
path: Path
|
|
284
|
+
Prepared directory.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
prefix = f"{self.name}_{self.calc_counter:03d}_"
|
|
288
|
+
path = Path(tempfile.mkdtemp(prefix=prefix))
|
|
289
|
+
if use_in_run:
|
|
290
|
+
self.path_already_prepared = path
|
|
291
|
+
return path
|
|
292
|
+
|
|
293
|
+
def prepare(self, inp):
|
|
294
|
+
"""Prepare a temporary directory and write input.
|
|
295
|
+
|
|
296
|
+
Similar to prepare_path, but the input is also written into
|
|
297
|
+
the prepared directory.
|
|
298
|
+
|
|
299
|
+
Paramters
|
|
300
|
+
---------
|
|
301
|
+
inp : str
|
|
302
|
+
Input to be written into the file ``self.inp_fn`` in
|
|
303
|
+
the prepared directory.
|
|
304
|
+
|
|
305
|
+
Returns
|
|
306
|
+
-------
|
|
307
|
+
path: Path
|
|
308
|
+
Prepared directory.
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
if not self.path_already_prepared:
|
|
312
|
+
path = self.prepare_path()
|
|
313
|
+
else:
|
|
314
|
+
path = self.path_already_prepared
|
|
315
|
+
|
|
316
|
+
# Calculators like Turbomole got no input.
|
|
317
|
+
if inp:
|
|
318
|
+
inp_path = path / self.inp_fn
|
|
319
|
+
with open(inp_path, "w") as handle:
|
|
320
|
+
handle.write(inp)
|
|
321
|
+
|
|
322
|
+
return path
|
|
323
|
+
|
|
324
|
+
def prepare_input(self, atoms, coords, calc_type):
|
|
325
|
+
"""Meant to be extended."""
|
|
326
|
+
raise Exception("Not implemented!")
|
|
327
|
+
|
|
328
|
+
def print_out_fn(self, path):
|
|
329
|
+
"""Print calculation output.
|
|
330
|
+
|
|
331
|
+
Prints the output of a calculator after a calculation.
|
|
332
|
+
|
|
333
|
+
Parameters
|
|
334
|
+
----------
|
|
335
|
+
path : Path
|
|
336
|
+
Temporary directory of the calculation.
|
|
337
|
+
"""
|
|
338
|
+
with open(path / self.out_fn) as handle:
|
|
339
|
+
text = handle.read()
|
|
340
|
+
print(text)
|
|
341
|
+
|
|
342
|
+
def prepare_turbo_coords(self, atoms, coords):
|
|
343
|
+
"""Get a Turbomole coords string.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
atoms : iterable
|
|
348
|
+
Atom descriptors (element symbols).
|
|
349
|
+
coords: np.array, 1d
|
|
350
|
+
1D-array holding coordinates in Bohr.
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
coords: str
|
|
355
|
+
String holding coordinates in Turbomole coords format.
|
|
356
|
+
"""
|
|
357
|
+
fmt = "{:<20.014f}"
|
|
358
|
+
coord_str = "$coord\n"
|
|
359
|
+
for atom, coord in zip(atoms, coords.reshape(-1, 3)):
|
|
360
|
+
coord_line = (fmt + fmt + fmt).format(*coord) + atom.lower() + "\n"
|
|
361
|
+
coord_str += coord_line
|
|
362
|
+
coord_str += "$end"
|
|
363
|
+
return coord_str
|
|
364
|
+
|
|
365
|
+
def prepare_coords(self, atoms, coords):
|
|
366
|
+
"""Get 3d coords in Angstrom.
|
|
367
|
+
|
|
368
|
+
Reshape internal 1d coords to 3d and convert to Angstrom.
|
|
369
|
+
|
|
370
|
+
Parameters
|
|
371
|
+
----------
|
|
372
|
+
atoms : iterable
|
|
373
|
+
Atom descriptors (element symbols).
|
|
374
|
+
coords: np.array, 1d
|
|
375
|
+
1D-array holding coordinates in Bohr.
|
|
376
|
+
|
|
377
|
+
Returns
|
|
378
|
+
-------
|
|
379
|
+
coords: np.array, 3d
|
|
380
|
+
3D-array holding coordinates in Angstrom.
|
|
381
|
+
"""
|
|
382
|
+
coords = coords.reshape(-1, 3) * BOHR2ANG
|
|
383
|
+
coords = "\n".join(
|
|
384
|
+
[
|
|
385
|
+
"{} {:10.08f} {:10.08f} {:10.08f}".format(a, *c)
|
|
386
|
+
for a, c in zip(atoms, coords)
|
|
387
|
+
]
|
|
388
|
+
)
|
|
389
|
+
return coords
|
|
390
|
+
|
|
391
|
+
def prepare_xyz_string(self, atoms, coords):
|
|
392
|
+
"""Returns a xyz string in Angstrom.
|
|
393
|
+
|
|
394
|
+
Parameters
|
|
395
|
+
----------
|
|
396
|
+
atoms : iterable
|
|
397
|
+
Atom descriptors (element symbols).
|
|
398
|
+
coords: np.array, 1d
|
|
399
|
+
1D-array holding coordinates in Bohr.
|
|
400
|
+
|
|
401
|
+
Returns
|
|
402
|
+
-------
|
|
403
|
+
xyz_str: string
|
|
404
|
+
Coordinates in .xyz format.
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
return f"{len(atoms)}\n\n{self.prepare_coords(atoms, coords)}"
|
|
408
|
+
|
|
409
|
+
def run(
|
|
410
|
+
self,
|
|
411
|
+
inp,
|
|
412
|
+
calc,
|
|
413
|
+
add_args=None,
|
|
414
|
+
env=None,
|
|
415
|
+
shell=False,
|
|
416
|
+
hold=False,
|
|
417
|
+
keep=True,
|
|
418
|
+
cmd=None,
|
|
419
|
+
inc_counter=True,
|
|
420
|
+
run_after=True,
|
|
421
|
+
parser_kwargs=None,
|
|
422
|
+
symlink=True,
|
|
423
|
+
):
|
|
424
|
+
"""Run a calculation.
|
|
425
|
+
|
|
426
|
+
The bread-and-butter method to actually run an external quantum
|
|
427
|
+
chemistry code.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
inp : str
|
|
432
|
+
Input for the external program that is written to the temp-dir.
|
|
433
|
+
calc : str, hashable
|
|
434
|
+
Key (and more or less type of calculation) to select the right
|
|
435
|
+
parsing function from ``self.parser_funcs``.
|
|
436
|
+
add_args : iterable, optional
|
|
437
|
+
Additional arguments that will be appended to the program call.
|
|
438
|
+
env : Environment, optional
|
|
439
|
+
A potentially modified environment for the subprocess call.
|
|
440
|
+
shell : bool, optional
|
|
441
|
+
Use a shell to execute the program call. Need for Turbomole were
|
|
442
|
+
we chain program calls like dscf; escf.
|
|
443
|
+
hold : bool, optional
|
|
444
|
+
Wether to remove the temporary directory after the calculation.
|
|
445
|
+
keep : bool, optional
|
|
446
|
+
Wether to backup files as specified in ``self.to_keep()``. Usually
|
|
447
|
+
you want this.
|
|
448
|
+
cmd : str or iterable, optional
|
|
449
|
+
Overwrites ``self.base_cmd``.
|
|
450
|
+
inc_counter : bool, optional
|
|
451
|
+
Wether to increment the counter after a calculation.
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
results : dict
|
|
456
|
+
Dictionary holding all applicable results of the calculations
|
|
457
|
+
like the energy, a forces vector and/or excited state energies
|
|
458
|
+
from TDDFT.
|
|
459
|
+
"""
|
|
460
|
+
|
|
461
|
+
self.backup_dir = None
|
|
462
|
+
path = self.prepare(inp)
|
|
463
|
+
self.log(f"Running in {path} on {platform.node()}")
|
|
464
|
+
if cmd is None:
|
|
465
|
+
cmd = self.base_cmd
|
|
466
|
+
|
|
467
|
+
if isinstance(cmd, str):
|
|
468
|
+
cmd = [cmd]
|
|
469
|
+
|
|
470
|
+
act_cmd = cmd[0]
|
|
471
|
+
cmd_availble = shutil.which(act_cmd)
|
|
472
|
+
if (not cmd_availble) and (not shell):
|
|
473
|
+
print(
|
|
474
|
+
f"Command '{act_cmd}' could not be found on $PATH! "
|
|
475
|
+
"Maybe you forgot to make it available?"
|
|
476
|
+
)
|
|
477
|
+
return
|
|
478
|
+
|
|
479
|
+
args = cmd + [self.inp_fn]
|
|
480
|
+
if add_args:
|
|
481
|
+
args.extend(add_args)
|
|
482
|
+
if not env:
|
|
483
|
+
env = os.environ.copy()
|
|
484
|
+
tmp_out_fn = path / self.out_fn
|
|
485
|
+
with open(tmp_out_fn, "w") as handle:
|
|
486
|
+
if symlink:
|
|
487
|
+
# We can't use resolve here as a previous symlink may already
|
|
488
|
+
# exist. Calling resolve would translate this to the original
|
|
489
|
+
# out file in some tempdir (that is already deleted ...).
|
|
490
|
+
# sym_fn = Path("cur_out").resolve()
|
|
491
|
+
sym_fn = self.out_dir / "cur_out"
|
|
492
|
+
try:
|
|
493
|
+
os.remove(sym_fn)
|
|
494
|
+
except FileNotFoundError:
|
|
495
|
+
pass
|
|
496
|
+
|
|
497
|
+
try:
|
|
498
|
+
os.symlink(tmp_out_fn, sym_fn)
|
|
499
|
+
self.log(f"Created symlink in '{sym_fn}'")
|
|
500
|
+
# This may happen if we use a dask scheduler
|
|
501
|
+
except FileExistsError:
|
|
502
|
+
self.log("Symlink already exists. Skipping generation.")
|
|
503
|
+
|
|
504
|
+
# Do at least one cycle. When retries are disabled retry_calc == 0
|
|
505
|
+
# and range(0+1) will result in one cycle
|
|
506
|
+
added_retry_args = False
|
|
507
|
+
for retry in range(self.retry_calc + 1):
|
|
508
|
+
result = subprocess.Popen(
|
|
509
|
+
args,
|
|
510
|
+
cwd=path,
|
|
511
|
+
stdout=handle,
|
|
512
|
+
stderr=subprocess.PIPE,
|
|
513
|
+
env=env,
|
|
514
|
+
shell=shell,
|
|
515
|
+
)
|
|
516
|
+
result.communicate()
|
|
517
|
+
if hasattr(self, 'check_termination'):
|
|
518
|
+
normal_termination = False
|
|
519
|
+
try:
|
|
520
|
+
# Calling check_termination may result in an exception and
|
|
521
|
+
# normal_termination will stay at False
|
|
522
|
+
normal_termination = self.check_termination(tmp_out_fn)
|
|
523
|
+
# The out file may not be present
|
|
524
|
+
except FileNotFoundError:
|
|
525
|
+
self.log(
|
|
526
|
+
f"Could not find out-file {str(tmp_out_fn)} for termination status check!"
|
|
527
|
+
)
|
|
528
|
+
else:
|
|
529
|
+
normal_termination = True
|
|
530
|
+
|
|
531
|
+
if normal_termination:
|
|
532
|
+
break
|
|
533
|
+
else:
|
|
534
|
+
print("Abnormal termination! Retrying calculation.")
|
|
535
|
+
shutil.copy(tmp_out_fn, str(tmp_out_fn) + f".fail_{retry:02d}")
|
|
536
|
+
if hasattr(self, 'clean_tmp'):
|
|
537
|
+
self.clean_tmp(path)
|
|
538
|
+
else:
|
|
539
|
+
self.log("'self.clean_tmp()' not implemented!")
|
|
540
|
+
# Clear tmp_out_fn
|
|
541
|
+
handle.seek(0)
|
|
542
|
+
handle.truncate()
|
|
543
|
+
self.log("Detected abnormal termination! Retrying calculation.")
|
|
544
|
+
|
|
545
|
+
if not added_retry_args:
|
|
546
|
+
if hasattr(self, 'get_retry_args'):
|
|
547
|
+
args += self.get_retry_args()
|
|
548
|
+
added_retry_args = True
|
|
549
|
+
else:
|
|
550
|
+
self.log("'self.get_retry_args()' not implemented!")
|
|
551
|
+
|
|
552
|
+
# Parse results for desired quantities
|
|
553
|
+
try:
|
|
554
|
+
if run_after:
|
|
555
|
+
self.run_after(path)
|
|
556
|
+
parser_kwargs = {} if parser_kwargs is None else parser_kwargs
|
|
557
|
+
results = self.parser_funcs[calc](path, **parser_kwargs)
|
|
558
|
+
if keep:
|
|
559
|
+
self.keep(path)
|
|
560
|
+
|
|
561
|
+
except Exception as err:
|
|
562
|
+
print("Crashed input:")
|
|
563
|
+
print(inp)
|
|
564
|
+
backup_dir = Path(os.getcwd()) / f"crashed_{self.name}"
|
|
565
|
+
self.backup_dir = backup_dir
|
|
566
|
+
if backup_dir.exists():
|
|
567
|
+
shutil.rmtree(backup_dir)
|
|
568
|
+
shutil.copytree(path, backup_dir)
|
|
569
|
+
print(
|
|
570
|
+
f"Copied contents of\n\t'{path}'\nto\n\t'{backup_dir}'.\n"
|
|
571
|
+
"Consider checking the log files there.\n"
|
|
572
|
+
)
|
|
573
|
+
raise err
|
|
574
|
+
finally:
|
|
575
|
+
if (not hold) and self.clean_after:
|
|
576
|
+
self.clean(path)
|
|
577
|
+
if inc_counter:
|
|
578
|
+
self.calc_counter += 1
|
|
579
|
+
|
|
580
|
+
self.path_already_prepared = None
|
|
581
|
+
self.last_run_path = path
|
|
582
|
+
return results
|
|
583
|
+
|
|
584
|
+
def run_after(self, path):
|
|
585
|
+
"""Meant to be extended.
|
|
586
|
+
|
|
587
|
+
This method is called after a calculation was done, but before
|
|
588
|
+
entering ``self.keep()`` and ``self.clean()``. Can be used to call
|
|
589
|
+
tools like formchk or ricctools.
|
|
590
|
+
"""
|
|
591
|
+
|
|
592
|
+
def popen(self, cmd, cwd=None):
|
|
593
|
+
if isinstance(cmd, str):
|
|
594
|
+
cmd = cmd.split()
|
|
595
|
+
proc = subprocess.Popen(
|
|
596
|
+
cmd,
|
|
597
|
+
cwd=cwd,
|
|
598
|
+
universal_newlines=True,
|
|
599
|
+
stdout=subprocess.PIPE,
|
|
600
|
+
stderr=subprocess.PIPE,
|
|
601
|
+
)
|
|
602
|
+
proc.wait()
|
|
603
|
+
return proc
|
|
604
|
+
|
|
605
|
+
def prepare_pattern(self, raw_pat):
|
|
606
|
+
"""Prepare globs.
|
|
607
|
+
|
|
608
|
+
Transforms an entry of ``self.to_keep`` into a glob and a key
|
|
609
|
+
suitable for the use in ``self.keep()``.
|
|
610
|
+
|
|
611
|
+
Parameters
|
|
612
|
+
----------
|
|
613
|
+
raw_pat : str
|
|
614
|
+
Entry of ``self.to_keep``
|
|
615
|
+
|
|
616
|
+
Returns
|
|
617
|
+
-------
|
|
618
|
+
pattern : str
|
|
619
|
+
Glob that can be used in Path.glob()
|
|
620
|
+
multi : bool
|
|
621
|
+
Flag if glob may match multiple files.
|
|
622
|
+
key : str
|
|
623
|
+
A key to be used in the ``kept_fns`` dict.
|
|
624
|
+
"""
|
|
625
|
+
key_given = None
|
|
626
|
+
if ":" in raw_pat:
|
|
627
|
+
key_given, raw_pat = raw_pat.split(":")
|
|
628
|
+
# Indicates if multiple files are expected
|
|
629
|
+
multi = "*" in raw_pat
|
|
630
|
+
# Drop '*' as it just indicates if we expect multiple matches
|
|
631
|
+
raw_pat = raw_pat.replace("*", "")
|
|
632
|
+
# Interpret it as prefix and drop the two underscores
|
|
633
|
+
if raw_pat.startswith("__"):
|
|
634
|
+
pattern = f"{raw_pat[2:]}*"
|
|
635
|
+
pattern_key = f"{raw_pat[2:]}s"
|
|
636
|
+
# Use raw_pat as suffix
|
|
637
|
+
else:
|
|
638
|
+
pattern = f"*{raw_pat}"
|
|
639
|
+
pattern_key = f"{raw_pat}"
|
|
640
|
+
if key_given:
|
|
641
|
+
pattern_key = key_given
|
|
642
|
+
pattern_key = pattern_key.lower()
|
|
643
|
+
return pattern, multi, pattern_key
|
|
644
|
+
|
|
645
|
+
def build_set_plans(self, _set_plans=None):
|
|
646
|
+
if _set_plans is None:
|
|
647
|
+
_set_plans = self._set_plans
|
|
648
|
+
|
|
649
|
+
set_plans = list()
|
|
650
|
+
for sp in _set_plans:
|
|
651
|
+
if type(sp) == SetPlan:
|
|
652
|
+
set_plans.append(sp)
|
|
653
|
+
continue
|
|
654
|
+
if type(sp) == dict:
|
|
655
|
+
set_plans.append(SetPlan(**sp))
|
|
656
|
+
continue
|
|
657
|
+
if type(sp) == str:
|
|
658
|
+
sp = (sp,)
|
|
659
|
+
set_plans.append(SetPlan(*sp))
|
|
660
|
+
self.set_plans = set_plans
|
|
661
|
+
|
|
662
|
+
def apply_keep_kind(self):
|
|
663
|
+
assert self.keep_kind in KeepKind
|
|
664
|
+
|
|
665
|
+
# Nothing to do when set to ALL
|
|
666
|
+
delete_calc_counter = list()
|
|
667
|
+
if self.keep_kind == KeepKind["LATEST"]:
|
|
668
|
+
delete_calc_counter.extend(
|
|
669
|
+
[
|
|
670
|
+
self.calc_counter - 1,
|
|
671
|
+
]
|
|
672
|
+
if self.calc_counter > 0
|
|
673
|
+
else []
|
|
674
|
+
)
|
|
675
|
+
elif self.keep_kind == KeepKind["NONE"]:
|
|
676
|
+
delete_calc_counter.extend(list(self.kept_history.keys()))
|
|
677
|
+
|
|
678
|
+
for cycle in delete_calc_counter:
|
|
679
|
+
self.log(f"Deleting kept files from cycle {cycle}.")
|
|
680
|
+
for k, f in self.kept_history[cycle].items():
|
|
681
|
+
if type(f) is list:
|
|
682
|
+
[os.remove(f_) for f_ in f]
|
|
683
|
+
else:
|
|
684
|
+
os.remove(f)
|
|
685
|
+
del self.kept_history[cycle]
|
|
686
|
+
|
|
687
|
+
def keep(self, path):
|
|
688
|
+
"""Backup calculation results.
|
|
689
|
+
|
|
690
|
+
Parameters
|
|
691
|
+
----------
|
|
692
|
+
path : Path
|
|
693
|
+
Temporary directory of the calculation.
|
|
694
|
+
|
|
695
|
+
Returns
|
|
696
|
+
-------
|
|
697
|
+
kept_fns : dict
|
|
698
|
+
Dictonary holding the filenames that were backed up. The keys
|
|
699
|
+
correspond to the type of file.
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
kept_fns = dict()
|
|
703
|
+
for raw_pattern in self.to_keep:
|
|
704
|
+
pattern, multi, key = self.prepare_pattern(raw_pattern)
|
|
705
|
+
globbed = natsorted(path.glob(pattern))
|
|
706
|
+
if not multi:
|
|
707
|
+
assert len(globbed) <= 1, (
|
|
708
|
+
f"Expected at most one file "
|
|
709
|
+
f"matching {pattern} in {path}. Found {len(globbed)} "
|
|
710
|
+
f"files instead ({', '.join([g.name for g in globbed])})!"
|
|
711
|
+
)
|
|
712
|
+
else:
|
|
713
|
+
# I wonder if this has to be a list? Probably to support 'multi', when the pattern
|
|
714
|
+
# contains a '*' character.
|
|
715
|
+
kept_fns[key] = list()
|
|
716
|
+
for tmp_fn in globbed:
|
|
717
|
+
base = tmp_fn.name
|
|
718
|
+
new_fn = self.make_fn(base)
|
|
719
|
+
shutil.copy(tmp_fn, new_fn)
|
|
720
|
+
if multi:
|
|
721
|
+
kept_fns[key].append(new_fn)
|
|
722
|
+
else:
|
|
723
|
+
kept_fns[key] = new_fn
|
|
724
|
+
self.kept_history[self.calc_counter] = kept_fns
|
|
725
|
+
self.apply_keep_kind()
|
|
726
|
+
try:
|
|
727
|
+
kept_fns = self.kept_history[self.calc_counter]
|
|
728
|
+
except KeyError:
|
|
729
|
+
kept_fns = dict()
|
|
730
|
+
self.apply_set_plans(kept_fns)
|
|
731
|
+
return kept_fns
|
|
732
|
+
|
|
733
|
+
def apply_set_plans(self, kept_fns, set_plans=None):
|
|
734
|
+
if set_plans is None:
|
|
735
|
+
set_plans = self.set_plans
|
|
736
|
+
|
|
737
|
+
for sp in set_plans:
|
|
738
|
+
if not sp.condition(self):
|
|
739
|
+
continue
|
|
740
|
+
try:
|
|
741
|
+
setattr(self, sp.name, kept_fns[sp.key])
|
|
742
|
+
except KeyError:
|
|
743
|
+
if sp.fail is not None:
|
|
744
|
+
self.log(sp.fail(sp.key))
|
|
745
|
+
|
|
746
|
+
def clean(self, path):
|
|
747
|
+
"""Delete the temporary directory.
|
|
748
|
+
|
|
749
|
+
Parameters
|
|
750
|
+
----------
|
|
751
|
+
path : Path
|
|
752
|
+
Directory to delete.
|
|
753
|
+
"""
|
|
754
|
+
shutil.rmtree(path)
|
|
755
|
+
self.log(f"Cleaned {path}")
|
|
756
|
+
|
|
757
|
+
def get_restart_info(self):
|
|
758
|
+
"""Return a dict containing chkfiles.
|
|
759
|
+
|
|
760
|
+
Returns
|
|
761
|
+
-------
|
|
762
|
+
restart_info : dict
|
|
763
|
+
Dictionary holding the calculator state. Used for restoring calculaters
|
|
764
|
+
in restarted calculations.
|
|
765
|
+
"""
|
|
766
|
+
try:
|
|
767
|
+
# Convert possible Paths to str
|
|
768
|
+
chkfiles = {k: str(v) for k, v in self.get_chkfiles().items()}
|
|
769
|
+
except AttributeError:
|
|
770
|
+
chkfiles = dict()
|
|
771
|
+
|
|
772
|
+
restart_info = {
|
|
773
|
+
"base_name": self.base_name,
|
|
774
|
+
"calc_number": self.calc_number,
|
|
775
|
+
"calc_counter": self.calc_counter,
|
|
776
|
+
"chkfiles": chkfiles,
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return restart_info
|
|
780
|
+
|
|
781
|
+
def verify_chkfiles(self, chkfiles):
|
|
782
|
+
"""Checks if given chkfiles exist and return them as Paths
|
|
783
|
+
|
|
784
|
+
Parameters
|
|
785
|
+
----------
|
|
786
|
+
chkfiles : dict
|
|
787
|
+
Dictionary holding the chkfiles. The keys correspond to the attribute
|
|
788
|
+
names, the values are strs holding the (potentially full) filename (path).
|
|
789
|
+
|
|
790
|
+
Returns
|
|
791
|
+
-------
|
|
792
|
+
paths : dict
|
|
793
|
+
Dictionary of Paths.
|
|
794
|
+
"""
|
|
795
|
+
paths = {}
|
|
796
|
+
for key, chkfile in chkfiles.items():
|
|
797
|
+
chkfile = Path(chkfile)
|
|
798
|
+
# If the chkfile exists at the given path we use it as it is.
|
|
799
|
+
if not chkfile.exists():
|
|
800
|
+
self.log(
|
|
801
|
+
f"Given chkfile '{chkfile}' could not be found! Dropping "
|
|
802
|
+
"absolute part and trying only its name."
|
|
803
|
+
)
|
|
804
|
+
# Check if relative path exists. This may happen if the calculation
|
|
805
|
+
# has been moved to a different folder.
|
|
806
|
+
name = Path(chkfile.name)
|
|
807
|
+
if name.exists():
|
|
808
|
+
chkfile = name
|
|
809
|
+
else:
|
|
810
|
+
self.log(f"'{name}' could not be found! Skipping this chkfile.")
|
|
811
|
+
continue
|
|
812
|
+
paths[key] = chkfile
|
|
813
|
+
return paths
|
|
814
|
+
|
|
815
|
+
def set_restart_info(self, restart_info):
|
|
816
|
+
"""Sets restart information (chkfiles etc.) on the calculator.
|
|
817
|
+
|
|
818
|
+
Parameters
|
|
819
|
+
-------
|
|
820
|
+
restart_info : dict
|
|
821
|
+
Dictionary holding the calculator state. Used for restoring calculaters
|
|
822
|
+
in restarted calculations.
|
|
823
|
+
"""
|
|
824
|
+
try:
|
|
825
|
+
chkfiles = self.verify_chkfiles(restart_info.pop("chkfiles"))
|
|
826
|
+
self.set_chkfiles(chkfiles)
|
|
827
|
+
except KeyError:
|
|
828
|
+
self.log("No chkfiles preset in restart_info")
|
|
829
|
+
except AttributeError:
|
|
830
|
+
self.log(
|
|
831
|
+
"Found chkfiles on restart_info, but 'set_chkfiles' is not "
|
|
832
|
+
"implemented for Calculator."
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
self.log("Setting restart_info")
|
|
836
|
+
for key, value in restart_info.items():
|
|
837
|
+
setattr(self, key, value)
|
|
838
|
+
self.log(f"\t{key}: {value}")
|
|
839
|
+
|
|
840
|
+
def print_capabilities(self):
|
|
841
|
+
print(
|
|
842
|
+
f" Can retry?: {hasattr(self, 'check_termination')}\n"
|
|
843
|
+
f"Can track ES??: {hasattr(self, 'prepare_overlap_data')}\n"
|
|
844
|
+
)
|