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,908 @@
|
|
|
1
|
+
# [1] https://pubs.acs.org/doi/pdf/10.1021/acs.jctc.5b01148
|
|
2
|
+
# Plasser, 2016
|
|
3
|
+
# [2] https://doi.org/10.1002/jcc.25800
|
|
4
|
+
# Garcia, Campetella, 2019
|
|
5
|
+
|
|
6
|
+
from collections import namedtuple
|
|
7
|
+
from pathlib import Path, PosixPath
|
|
8
|
+
import shutil
|
|
9
|
+
import tempfile
|
|
10
|
+
|
|
11
|
+
import h5py
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from pysisyphus import logger
|
|
15
|
+
from pysisyphus.calculators.Calculator import Calculator
|
|
16
|
+
|
|
17
|
+
# from pysisyphus.calculators.WFOWrapper import WFOWrapper
|
|
18
|
+
from pysisyphus.config import get_cmd
|
|
19
|
+
from pysisyphus.helpers_pure import describe
|
|
20
|
+
from pysisyphus.io.hdf5 import get_h5_group
|
|
21
|
+
from pysisyphus.wrapper.mwfn import make_cdd, get_mwfn_exc_str
|
|
22
|
+
from pysisyphus.wrapper.jmol import render_cdd_cube as render_cdd_cube_jmol
|
|
23
|
+
from pysisyphus.wavefunction.excited_states import (
|
|
24
|
+
# nto_overlaps,
|
|
25
|
+
norm_ci_coeffs,
|
|
26
|
+
# nto_org_overlaps,
|
|
27
|
+
tden_overlaps,
|
|
28
|
+
top_differences,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
NTOs = namedtuple("NTOs", "ntos lambdas")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_data_model(
|
|
36
|
+
# exc_state_num, occ_mo_num, virt_mo_num, ovlp_type, atoms, max_cycles
|
|
37
|
+
exc_state_num,
|
|
38
|
+
occ_a,
|
|
39
|
+
virt_a,
|
|
40
|
+
occ_b,
|
|
41
|
+
virt_b,
|
|
42
|
+
ovlp_type,
|
|
43
|
+
atoms,
|
|
44
|
+
max_cycles,
|
|
45
|
+
):
|
|
46
|
+
state_num = exc_state_num + 1 # including GS
|
|
47
|
+
_1d = (max_cycles,)
|
|
48
|
+
ovlp_state_num = state_num if ovlp_type == "wfow" else exc_state_num
|
|
49
|
+
|
|
50
|
+
assert (occ_a + virt_a) == (occ_b + virt_b)
|
|
51
|
+
nbf = occ_a + virt_a
|
|
52
|
+
data_model = {
|
|
53
|
+
"Ca": (max_cycles, nbf, nbf),
|
|
54
|
+
"Cb": (max_cycles, nbf, nbf),
|
|
55
|
+
"Xa": (max_cycles, exc_state_num, occ_a, virt_a),
|
|
56
|
+
"Ya": (max_cycles, exc_state_num, occ_a, virt_a),
|
|
57
|
+
"Xb": (max_cycles, exc_state_num, occ_b, virt_b),
|
|
58
|
+
"Yb": (max_cycles, exc_state_num, occ_b, virt_b),
|
|
59
|
+
"coords": (max_cycles, len(atoms) * 3),
|
|
60
|
+
"all_energies": (
|
|
61
|
+
max_cycles,
|
|
62
|
+
state_num,
|
|
63
|
+
),
|
|
64
|
+
"calculated_roots": _1d,
|
|
65
|
+
"roots": _1d,
|
|
66
|
+
"root_flips": _1d,
|
|
67
|
+
"row_inds": _1d,
|
|
68
|
+
"ref_cycles": _1d,
|
|
69
|
+
"ref_roots": _1d,
|
|
70
|
+
"overlap_matrices": (max_cycles, ovlp_state_num, ovlp_state_num),
|
|
71
|
+
"cdd_cubes": _1d,
|
|
72
|
+
"cdd_imgs": _1d,
|
|
73
|
+
}
|
|
74
|
+
return data_model
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class OverlapCalculator(Calculator):
|
|
78
|
+
OVLP_TYPE_VERBOSE = {
|
|
79
|
+
"wf": "wavefunction overlap",
|
|
80
|
+
"tden": "transition density matrix overlap",
|
|
81
|
+
"nto": "natural transition orbital overlap",
|
|
82
|
+
# As described in 10.1002/jcc.25800
|
|
83
|
+
"nto_org": "original natural transition orbital overlap",
|
|
84
|
+
"top": "transition orbital pair overlap",
|
|
85
|
+
}
|
|
86
|
+
VALID_KEYS = [
|
|
87
|
+
k for k in OVLP_TYPE_VERBOSE.keys()
|
|
88
|
+
] # lgtm [py/non-iterable-in-for-loop]
|
|
89
|
+
VALID_CDDS = (None, "calc", "render")
|
|
90
|
+
VALID_XY = ("X", "X+Y", "X-Y")
|
|
91
|
+
H5_MAP = {
|
|
92
|
+
"Ca": "Ca_list",
|
|
93
|
+
"Cb": "Cb_list",
|
|
94
|
+
"Xa": "Xa_list",
|
|
95
|
+
"Ya": "Ya_list",
|
|
96
|
+
"Xb": "Xb_list",
|
|
97
|
+
"Yb": "Yb_list",
|
|
98
|
+
"coords": "coords_list",
|
|
99
|
+
"all_energies": "all_energies_list",
|
|
100
|
+
"roots": "roots_list",
|
|
101
|
+
"ref_roots": "reference_roots",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
*args,
|
|
107
|
+
track=False,
|
|
108
|
+
ovlp_type="tden",
|
|
109
|
+
double_mol=False,
|
|
110
|
+
ovlp_with="previous",
|
|
111
|
+
# XY="X+Y",
|
|
112
|
+
adapt_args=(0.5, 0.3, 0.6),
|
|
113
|
+
# use_ntos=4,
|
|
114
|
+
# pr_nto=False,
|
|
115
|
+
# nto_thresh=0.3,
|
|
116
|
+
cdds=None,
|
|
117
|
+
orient="",
|
|
118
|
+
dump_fn="overlap_data.h5",
|
|
119
|
+
h5_dump=False,
|
|
120
|
+
# ncore=0,
|
|
121
|
+
conf_thresh=1e-3,
|
|
122
|
+
# dyn_roots=0,
|
|
123
|
+
mos_ref="cur",
|
|
124
|
+
mos_renorm=True,
|
|
125
|
+
**kwargs,
|
|
126
|
+
):
|
|
127
|
+
super().__init__(*args, **kwargs)
|
|
128
|
+
|
|
129
|
+
self.track = track
|
|
130
|
+
self.ovlp_type = ovlp_type
|
|
131
|
+
assert (
|
|
132
|
+
self.ovlp_type in self.OVLP_TYPE_VERBOSE.keys()
|
|
133
|
+
), f"Valid overlap types are {self.VALID_KEYS}"
|
|
134
|
+
self.double_mol = double_mol
|
|
135
|
+
assert ovlp_with in ("previous", "first", "adapt")
|
|
136
|
+
self.ovlp_with = ovlp_with
|
|
137
|
+
assert (self.ovlp_type, self.ovlp_with) != (
|
|
138
|
+
"top",
|
|
139
|
+
"adapat",
|
|
140
|
+
), "ovlp_type: top and ovlp_with: adapat are not yet compatible"
|
|
141
|
+
# self.XY = XY
|
|
142
|
+
# assert self.XY in self.VALID_XY
|
|
143
|
+
self.adapt_args = np.abs(adapt_args, dtype=float)
|
|
144
|
+
self.adpt_thresh, self.adpt_min, self.adpt_max = self.adapt_args
|
|
145
|
+
# self.use_ntos = use_ntos
|
|
146
|
+
# self.pr_nto = pr_nto
|
|
147
|
+
# self.nto_thresh = nto_thresh
|
|
148
|
+
self.cdds = cdds
|
|
149
|
+
# When calculation/rendering of charge density differences (CDDs) is
|
|
150
|
+
# requested check fore the required programs (Multiwfn/Jmol). If they're
|
|
151
|
+
# not available, we fallback to a more sensible command and print a warning.
|
|
152
|
+
msg = (
|
|
153
|
+
"'cdds: {0}' requested, but {1} was not found! "
|
|
154
|
+
"Falling back to 'cdds: {2}'!\nConsider defining the {1} "
|
|
155
|
+
"command in '.pysisyphusrc'."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
jmol_cmd = get_cmd("jmol")
|
|
159
|
+
mwfn_cmd = get_cmd("mwfn")
|
|
160
|
+
if (self.cdds == "render") and not jmol_cmd:
|
|
161
|
+
logger.debug(msg.format(self.cdds, "Jmol", "calc"))
|
|
162
|
+
self.cdds = "calc"
|
|
163
|
+
if (self.cdds in ("calc", "render")) and not mwfn_cmd:
|
|
164
|
+
logger.debug(msg.format(self.cdds, "Multiwfn", None))
|
|
165
|
+
self.cdds = None
|
|
166
|
+
self.log(f"cdds: {self.cdds}, jmol={jmol_cmd}, mwfn={mwfn_cmd}")
|
|
167
|
+
assert self.cdds in self.VALID_CDDS
|
|
168
|
+
self.orient = orient
|
|
169
|
+
self.dump_fn = self.out_dir / dump_fn
|
|
170
|
+
self.h5_dump = h5_dump
|
|
171
|
+
# self.ncore = int(ncore)
|
|
172
|
+
self.conf_thresh = float(conf_thresh)
|
|
173
|
+
"""
|
|
174
|
+
self.dyn_roots = int(dyn_roots)
|
|
175
|
+
if self.dyn_roots != 0:
|
|
176
|
+
self.dyn_roots = 0
|
|
177
|
+
self.log("dyn_roots = 0 is hardcoded right now")
|
|
178
|
+
"""
|
|
179
|
+
self.mos_ref = mos_ref
|
|
180
|
+
assert self.mos_ref in ("cur", "ref")
|
|
181
|
+
self.mos_renorm = bool(mos_renorm)
|
|
182
|
+
|
|
183
|
+
# assert self.ncore >= 0, "ncore must be a >= 0!"
|
|
184
|
+
|
|
185
|
+
# MO-coefficients; MOs are expected to be in rows.
|
|
186
|
+
self.Ca_list = list()
|
|
187
|
+
self.Cb_list = list()
|
|
188
|
+
# Transition density matrices.
|
|
189
|
+
self.Xa_list = list()
|
|
190
|
+
self.Ya_list = list()
|
|
191
|
+
self.Xb_list = list()
|
|
192
|
+
self.Yb_list = list()
|
|
193
|
+
|
|
194
|
+
self.wfow = None
|
|
195
|
+
self.nto_list = list()
|
|
196
|
+
self.coords_list = list()
|
|
197
|
+
# This list will hold the root indices at the beginning of the cycle
|
|
198
|
+
# before any overlap calculation.
|
|
199
|
+
self.calculated_roots = list()
|
|
200
|
+
# This list will hold the (potentially) updated root after an overlap
|
|
201
|
+
# calculation and it may differ from the value stored in
|
|
202
|
+
# self.calculated_roots.
|
|
203
|
+
self.roots_list = list()
|
|
204
|
+
# Roots at the reference states that are used for comparison
|
|
205
|
+
self.reference_roots = list()
|
|
206
|
+
self.cdd_cubes = list()
|
|
207
|
+
self.cdd_imgs = list()
|
|
208
|
+
self.all_energies_list = list()
|
|
209
|
+
# Why did is there already False in the list? Probably related
|
|
210
|
+
# to plotting...
|
|
211
|
+
self.root_flips = [
|
|
212
|
+
False,
|
|
213
|
+
]
|
|
214
|
+
self.first_root = None
|
|
215
|
+
self.overlap_matrices = list()
|
|
216
|
+
self.row_inds = list()
|
|
217
|
+
# The first overlap calculation can be done in cycle 1, and then
|
|
218
|
+
# we compare cycle 1 to cycle 0, regardless of the ovlp_with.
|
|
219
|
+
self.ref_cycle = 0
|
|
220
|
+
self.ref_cycles = list()
|
|
221
|
+
self.atoms = None
|
|
222
|
+
self.root = None
|
|
223
|
+
|
|
224
|
+
if track:
|
|
225
|
+
self.log(
|
|
226
|
+
"Tracking excited states with "
|
|
227
|
+
f"{self.OVLP_TYPE_VERBOSE[ovlp_type]}s "
|
|
228
|
+
f"between the current and the {self.ovlp_with} geometry."
|
|
229
|
+
)
|
|
230
|
+
if self.ovlp_with == "adapt":
|
|
231
|
+
self.log(f"Adapt args: {self.adapt_args}")
|
|
232
|
+
|
|
233
|
+
self.h5_fn = self.out_dir / "ovlp_data.h5"
|
|
234
|
+
self.h5_group_name = self.name
|
|
235
|
+
# We can't initialize the HDF5 group as we don't know the shape of
|
|
236
|
+
# atoms/coords yet. So we wait until after the first calculation.
|
|
237
|
+
self.h5_cycles = 50
|
|
238
|
+
|
|
239
|
+
self._data_model = None
|
|
240
|
+
self._h5_initialized = False
|
|
241
|
+
|
|
242
|
+
def get_h5_group(self):
|
|
243
|
+
if not self._h5_initialized:
|
|
244
|
+
reset = True
|
|
245
|
+
self._h5_initialized = True
|
|
246
|
+
else:
|
|
247
|
+
reset = False
|
|
248
|
+
# h5_group = get_h5_group(
|
|
249
|
+
# self.h5_fn, self.h5_group_name, self.data_model, reset=reset
|
|
250
|
+
# )
|
|
251
|
+
# return h5_group
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def data_model(self):
|
|
255
|
+
if self._data_model is None:
|
|
256
|
+
max_cycles = self.h5_cycles
|
|
257
|
+
# exc_state_num, occ_mo_num, virt_mo_num = self.ci_coeff_list[0].shape
|
|
258
|
+
exc_state_num, occ_a, virt_a = self.Xa_list[0].shape
|
|
259
|
+
exc_state_num, occ_b, virt_b = self.Xb_list[0].shape
|
|
260
|
+
self._data_model = get_data_model(
|
|
261
|
+
exc_state_num,
|
|
262
|
+
occ_a,
|
|
263
|
+
virt_a,
|
|
264
|
+
occ_b,
|
|
265
|
+
virt_b,
|
|
266
|
+
self.ovlp_type,
|
|
267
|
+
self.atoms,
|
|
268
|
+
max_cycles,
|
|
269
|
+
)
|
|
270
|
+
return self._data_model
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def stored_calculations(self):
|
|
274
|
+
assert (
|
|
275
|
+
len(self.Xa_list)
|
|
276
|
+
== len(self.Ya_list)
|
|
277
|
+
== len(self.Xb_list)
|
|
278
|
+
== len(self.Yb_list)
|
|
279
|
+
)
|
|
280
|
+
return len(self.Xa_list)
|
|
281
|
+
|
|
282
|
+
def get_ci_coeffs_for(self, ind):
|
|
283
|
+
return [
|
|
284
|
+
lst[ind] for lst in (self.Xa_list, self.Ya_list, self.Xb_list, self.Yb_list)
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
def prepare_overlap_data(self, path):
|
|
288
|
+
"""This method has to implement the calculator specific parsing of
|
|
289
|
+
MO-coefficients, CI-coefficients and energies.
|
|
290
|
+
Should return a filename pointing to TURBOMOLE
|
|
291
|
+
like mos, a MO coefficient array and a CI coefficient array."""
|
|
292
|
+
raise Exception("Implement me!")
|
|
293
|
+
|
|
294
|
+
def store_overlap_data(self, atoms, coords, path=None, overlap_data=None):
|
|
295
|
+
if self.atoms is None:
|
|
296
|
+
self.atoms = atoms
|
|
297
|
+
|
|
298
|
+
if overlap_data is None:
|
|
299
|
+
overlap_data = self.prepare_overlap_data(path)
|
|
300
|
+
|
|
301
|
+
if len(overlap_data) == 4:
|
|
302
|
+
Ca, Xa, Ya, all_ens = overlap_data
|
|
303
|
+
# Use same data for beta part
|
|
304
|
+
Xb = Xa.copy()
|
|
305
|
+
Yb = Ya.copy()
|
|
306
|
+
Cb = Ca.copy()
|
|
307
|
+
elif len(overlap_data) == 7:
|
|
308
|
+
Ca, Xa, Ya, Cb, Xb, Yb, all_ens = overlap_data
|
|
309
|
+
else:
|
|
310
|
+
raise Exception("Expecting either 4 or 7 items in overlap_data!")
|
|
311
|
+
|
|
312
|
+
assert Ca.ndim == Cb.ndim == 2
|
|
313
|
+
assert all([mat.ndim == 3 for mat in (Xa, Ya, Xb, Yb)])
|
|
314
|
+
|
|
315
|
+
Xa_, Ya_, Xb_, Yb_ = norm_ci_coeffs(Xa, Ya, Xb, Yb)
|
|
316
|
+
self.Ca_list.append(Ca.copy())
|
|
317
|
+
self.Cb_list.append(Cb.copy())
|
|
318
|
+
self.Xa_list.append(Xa_)
|
|
319
|
+
self.Ya_list.append(Ya_)
|
|
320
|
+
self.Xb_list.append(Xb_)
|
|
321
|
+
self.Yb_list.append(Yb_)
|
|
322
|
+
|
|
323
|
+
# TODO: handle self.XY
|
|
324
|
+
"""
|
|
325
|
+
if self.XY == "X":
|
|
326
|
+
ci_coeffs = X
|
|
327
|
+
elif self.XY == "X+Y":
|
|
328
|
+
ci_coeffs = X + Y
|
|
329
|
+
elif self.XY == "X-Y":
|
|
330
|
+
ci_coeffs = X - Y
|
|
331
|
+
else:
|
|
332
|
+
raise Exception(
|
|
333
|
+
f"Invalid 'XY' value. Allowed values are: '{self.VALID_XY}'!"
|
|
334
|
+
)
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
# Don't create the wf-object when we use a different ovlp method.
|
|
338
|
+
# TODO: handle overlap calculator
|
|
339
|
+
# if (self.ovlp_type == "wf") and (self.wfow is None):
|
|
340
|
+
# self.set_wfow(ci_coeffs)
|
|
341
|
+
|
|
342
|
+
if self.first_root is None:
|
|
343
|
+
self.first_root = self.root
|
|
344
|
+
self.log(f"Set first root to {self.first_root}.")
|
|
345
|
+
|
|
346
|
+
self.coords_list.append(coords)
|
|
347
|
+
self.calculated_roots.append(self.root)
|
|
348
|
+
# We can't calculate any overlaps in the first cycle, so we can't
|
|
349
|
+
# compute a new root value. So we store the same value as for
|
|
350
|
+
# calculated_roots.
|
|
351
|
+
if self.stored_calculations < 2:
|
|
352
|
+
self.roots_list.append(self.root)
|
|
353
|
+
self.all_energies_list.append(all_ens)
|
|
354
|
+
|
|
355
|
+
# Also store NTOs if requested
|
|
356
|
+
# TODO: handle this differently
|
|
357
|
+
# if self.ovlp_type in ("nto", "nto_org"):
|
|
358
|
+
# self.set_ntos(mo_coeffs, ci_coeffs)
|
|
359
|
+
|
|
360
|
+
def get_indices(self, indices=None):
|
|
361
|
+
"""
|
|
362
|
+
A new root is determined by selecting the overlap matrix row
|
|
363
|
+
corresponding to the reference root and checking for the root
|
|
364
|
+
with the highest overlap (at the current geometry).
|
|
365
|
+
|
|
366
|
+
The overlap matrix is usually formed by a double loop like:
|
|
367
|
+
|
|
368
|
+
overlap_matrix = np.empty((ref_states, cur_states))
|
|
369
|
+
for i, ref_state in enumerate(ref_states):
|
|
370
|
+
for j, cur_state in enumerate(cur_states):
|
|
371
|
+
overlap_matrix[i, j] = make_overlap(ref_state, cur_state)
|
|
372
|
+
|
|
373
|
+
So the reference states run along the rows. Thats why the ref_state index
|
|
374
|
+
comes first in the 'indices' tuple.
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
if indices is None:
|
|
378
|
+
# By default we compare a reference cycle with the current (last)
|
|
379
|
+
# cycle, so the second index is -1.
|
|
380
|
+
ref, cur = self.ref_cycle, -1
|
|
381
|
+
else:
|
|
382
|
+
assert len(indices) == 2
|
|
383
|
+
ref, cur = [int(i) for i in indices]
|
|
384
|
+
return (ref, cur)
|
|
385
|
+
|
|
386
|
+
@staticmethod
|
|
387
|
+
def get_mo_norms(C, S_AO):
|
|
388
|
+
return np.diag(C.T @ S_AO @ C)
|
|
389
|
+
|
|
390
|
+
@staticmethod
|
|
391
|
+
def renorm_mos(C, S_AO):
|
|
392
|
+
norms = OverlapCalculator.get_mo_norms(C, S_AO)
|
|
393
|
+
sqrts = np.sqrt(norms)
|
|
394
|
+
return C / sqrts[None, :]
|
|
395
|
+
|
|
396
|
+
def get_ref_mos(self, C_ref, C_cur):
|
|
397
|
+
return {
|
|
398
|
+
"ref": C_ref,
|
|
399
|
+
"cur": C_cur,
|
|
400
|
+
}[self.mos_ref]
|
|
401
|
+
|
|
402
|
+
def get_orbital_matrices(self, indices=None, S_AO=None):
|
|
403
|
+
"""Return MO coefficents and AO overlaps for the given indices.
|
|
404
|
+
|
|
405
|
+
If not provided, a AO overlap matrix is constructed from one of
|
|
406
|
+
the MO coefficient matrices (controlled by self.mos_ref). Also,
|
|
407
|
+
if requested one of the two MO coefficient matrices is re-normalized.
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
ref, cur = self.get_indices(indices)
|
|
411
|
+
Ca_ref = self.Ca_list[ref].copy()
|
|
412
|
+
Ca_cur = self.Ca_list[cur].copy()
|
|
413
|
+
Cb_ref = self.Cb_list[ref].copy()
|
|
414
|
+
Cb_cur = self.Cb_list[cur].copy()
|
|
415
|
+
|
|
416
|
+
# Reconstruct from alpha MO coefficients
|
|
417
|
+
if reconstruct_S_AO := (S_AO is None):
|
|
418
|
+
C_S_AO = Ca_cur if (self.mos_ref == "cur") else Ca_ref
|
|
419
|
+
self.log(f"Reconstructed S_AO from '{self.mos_ref}' MO coefficients.")
|
|
420
|
+
S_AO = self.get_sao_from_mo_coeffs(C_S_AO)
|
|
421
|
+
self.log(f"max(abs(S_AO))={np.abs(S_AO).max():.6f}")
|
|
422
|
+
|
|
423
|
+
Cs = [[Ca_ref, Cb_ref], [Ca_cur, Cb_cur]]
|
|
424
|
+
# Only renormalize if requested and we reconstructed the AO overlap matrix.
|
|
425
|
+
if self.mos_renorm and reconstruct_S_AO:
|
|
426
|
+
# If S_AO was reconstructed from "cur" MOs, then "ref" MOs won't be
|
|
427
|
+
# normalized anymore and vice versa.
|
|
428
|
+
renorm_ind = 0 if (self.mos_ref == "cur") else 1
|
|
429
|
+
Cs[renorm_ind] = [self.renorm_mos(C, S_AO) for C in Cs[renorm_ind]]
|
|
430
|
+
self.log(f"Renormalized '{('ref', 'cur')[renorm_ind]}' MO coefficients.")
|
|
431
|
+
elif self.mos_renorm and (not reconstruct_S_AO):
|
|
432
|
+
self.log("Skipped MO re-normalization as 'S_AO' was provided.")
|
|
433
|
+
|
|
434
|
+
norms_0a = self.get_mo_norms(Cs[0][0], S_AO)
|
|
435
|
+
norms_0b = self.get_mo_norms(Cs[0][1], S_AO)
|
|
436
|
+
norms_1a = self.get_mo_norms(Cs[1][0], S_AO)
|
|
437
|
+
norms_1b = self.get_mo_norms(Cs[1][1], S_AO)
|
|
438
|
+
self.log(f"norm(MOs_0a): {describe(norms_0a)}")
|
|
439
|
+
self.log(f"norm(MOs_0b): {describe(norms_0b)}")
|
|
440
|
+
self.log(f"norm(MOs_1a): {describe(norms_1a)}")
|
|
441
|
+
self.log(f"norm(MOs_1b): {describe(norms_1b)}")
|
|
442
|
+
return *Cs[0], *Cs[1], S_AO
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def get_sao_from_mo_coeffs(C):
|
|
446
|
+
"""Recover AO overlaps from given MO coefficients.
|
|
447
|
+
|
|
448
|
+
For MOs in the columns of mo_coeffs:
|
|
449
|
+
|
|
450
|
+
S_AO = C⁻¹^T C⁻¹
|
|
451
|
+
S_AO C = C⁻¹^T
|
|
452
|
+
(S_AO C)^T = C⁻¹
|
|
453
|
+
C^T S_AO^T = C⁻¹
|
|
454
|
+
C^T S_AO C = I
|
|
455
|
+
"""
|
|
456
|
+
C_inv = np.linalg.pinv(C, rcond=1e-8)
|
|
457
|
+
S_AO = C_inv.T @ C_inv
|
|
458
|
+
return S_AO
|
|
459
|
+
|
|
460
|
+
"""
|
|
461
|
+
def get_wf_overlaps(self, indices=None, S_AO=None):
|
|
462
|
+
old, new = self.get_indices(indices)
|
|
463
|
+
old_cycle = (self.mo_coeff_list[old], self.ci_coeff_list[old])
|
|
464
|
+
new_cycle = (self.mo_coeff_list[new], self.ci_coeff_list[new])
|
|
465
|
+
return self.wfow.wf_overlap(old_cycle, new_cycle, S_AO)
|
|
466
|
+
|
|
467
|
+
def wf_overlaps(self, mo_coeffs1, ci_coeffs1, mo_coeffs2, ci_coeffs2, S_AO=None):
|
|
468
|
+
cycle1 = (mo_coeffs1, ci_coeffs1)
|
|
469
|
+
cycle2 = (mo_coeffs2, ci_coeffs2)
|
|
470
|
+
overlaps = self.wfow.wf_overlap(cycle1, cycle2, S_AO=S_AO)
|
|
471
|
+
return overlaps
|
|
472
|
+
|
|
473
|
+
def wf_overlap_with_calculator(self, calc, S_AO=None):
|
|
474
|
+
mo_coeffs1 = self.mo_coeff_list[-1]
|
|
475
|
+
ci_coeffs1 = self.ci_coeff_list[-1]
|
|
476
|
+
mo_coeffs2 = calc.mo_coeff_list[-1]
|
|
477
|
+
ci_coeffs2 = calc.ci_coeff_list[-1]
|
|
478
|
+
overlaps = self.wf_overlaps(
|
|
479
|
+
mo_coeffs1, ci_coeffs1, mo_coeffs2, ci_coeffs2, S_AO=S_AO
|
|
480
|
+
)
|
|
481
|
+
return overlaps
|
|
482
|
+
"""
|
|
483
|
+
|
|
484
|
+
def get_tden_overlaps(self, indices=None, S_AO=None):
|
|
485
|
+
Ca_ref, Cb_ref, Ca_cur, Cb_cur, S_AO = self.get_orbital_matrices(indices, S_AO)
|
|
486
|
+
|
|
487
|
+
ref, cur = self.get_indices(indices)
|
|
488
|
+
# Reference step
|
|
489
|
+
Xa_ref, Ya_ref, Xb_ref, Yb_ref = self.get_ci_coeffs_for(ref)
|
|
490
|
+
# Current step
|
|
491
|
+
Xa_cur, Ya_cur, Xb_cur, Yb_cur = self.get_ci_coeffs_for(cur)
|
|
492
|
+
|
|
493
|
+
# TODO: remove 2 when beta part is also considered!
|
|
494
|
+
overlaps = 2 * tden_overlaps(Ca_ref, Xa_ref, Ca_cur, Xa_cur, S_AO)
|
|
495
|
+
return overlaps
|
|
496
|
+
|
|
497
|
+
"""
|
|
498
|
+
def calculate_state_ntos(self, state_ci_coeffs, mos):
|
|
499
|
+
# TODO: don't renorm; respect self.XY choice
|
|
500
|
+
normed = state_ci_coeffs / np.linalg.norm(state_ci_coeffs)
|
|
501
|
+
# u, s, vh = np.linalg.svd(state_ci_coeffs)
|
|
502
|
+
u, s, vh = np.linalg.svd(normed)
|
|
503
|
+
lambdas = s ** 2
|
|
504
|
+
self.log("Normalized transition density vector to 1.")
|
|
505
|
+
self.log(f"Sum(lambdas)={np.sum(lambdas):.4f}")
|
|
506
|
+
lambdas_str = np.array2string(lambdas[:3], precision=4, suppress_small=True)
|
|
507
|
+
self.log(f"First three lambdas: {lambdas_str}")
|
|
508
|
+
|
|
509
|
+
occ_mo_num = state_ci_coeffs.shape[0]
|
|
510
|
+
occ_mos = mos[:occ_mo_num]
|
|
511
|
+
vir_mos = mos[occ_mo_num:]
|
|
512
|
+
occ_ntos = occ_mos.T.dot(u)
|
|
513
|
+
vir_ntos = vir_mos.T.dot(vh)
|
|
514
|
+
return occ_ntos, vir_ntos, lambdas
|
|
515
|
+
|
|
516
|
+
def get_nto_overlaps(self, indices=None, S_AO=None, org=False):
|
|
517
|
+
ref, cur = self.get_indices(indices)
|
|
518
|
+
|
|
519
|
+
if S_AO is None:
|
|
520
|
+
S_AO = self.get_sao_from_mo_coeffs_and_dump(
|
|
521
|
+
self.get_ref_mos(self.mo_coeff_list[ref], self.mo_coeff_list[cur])
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
ntos_1 = self.nto_list[ref]
|
|
525
|
+
ntos_2 = self.nto_list[cur]
|
|
526
|
+
if org:
|
|
527
|
+
overlaps = nto_org_overlaps(
|
|
528
|
+
ntos_1, ntos_2, S_AO, nto_thresh=self.nto_thresh
|
|
529
|
+
)
|
|
530
|
+
else:
|
|
531
|
+
overlaps = nto_overlaps(ntos_1, ntos_2, S_AO)
|
|
532
|
+
return overlaps
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
"""
|
|
536
|
+
def set_ntos(self, mo_coeffs, ci_coeffs):
|
|
537
|
+
roots = ci_coeffs.shape[0]
|
|
538
|
+
ntos_for_cycle = list()
|
|
539
|
+
for root in range(roots):
|
|
540
|
+
sn_ci_coeffs = ci_coeffs[root]
|
|
541
|
+
self.log(f"Calculating NTOs for root {root+1}")
|
|
542
|
+
occ_ntos, vir_ntos, lambdas = self.calculate_state_ntos(
|
|
543
|
+
sn_ci_coeffs,
|
|
544
|
+
mo_coeffs,
|
|
545
|
+
)
|
|
546
|
+
pr_nto = lambdas.sum() ** 2 / (lambdas ** 2).sum()
|
|
547
|
+
if self.pr_nto:
|
|
548
|
+
use_ntos = int(np.round(pr_nto))
|
|
549
|
+
self.log(f"PR_NTO={pr_nto:.2f}")
|
|
550
|
+
else:
|
|
551
|
+
use_ntos = self.use_ntos
|
|
552
|
+
self.log(f"Using {use_ntos} NTOS")
|
|
553
|
+
ovlp_occ_ntos = occ_ntos.T[:use_ntos]
|
|
554
|
+
ovlp_vir_ntos = vir_ntos.T[:use_ntos]
|
|
555
|
+
ovlp_lambdas = lambdas[:use_ntos]
|
|
556
|
+
ovlp_lambdas = np.concatenate((ovlp_lambdas, ovlp_lambdas))
|
|
557
|
+
ovlp_ntos = np.concatenate((ovlp_occ_ntos, ovlp_vir_ntos), axis=0)
|
|
558
|
+
ntos = NTOs(ntos=ovlp_ntos, lambdas=ovlp_lambdas)
|
|
559
|
+
ntos_for_cycle.append(ntos)
|
|
560
|
+
self.nto_list.append(ntos_for_cycle)
|
|
561
|
+
"""
|
|
562
|
+
|
|
563
|
+
def get_top_differences(self, indices=None, S_AO=None):
|
|
564
|
+
"""Transition orbital projection."""
|
|
565
|
+
Ca_ref, Cb_ref, Ca_cur, Cb_cur, S_AO = self.get_orbital_matrices(indices, S_AO)
|
|
566
|
+
S_MO_a = Ca_ref.T @ S_AO @ Ca_cur
|
|
567
|
+
S_MO_b = Cb_ref.T @ S_AO @ Cb_cur
|
|
568
|
+
|
|
569
|
+
ref, cur = self.get_indices(indices)
|
|
570
|
+
# Reference step
|
|
571
|
+
Xa_ref, Ya_ref, Xb_ref, Yb_ref = self.get_ci_coeffs_for(ref)
|
|
572
|
+
# Current step
|
|
573
|
+
Xa_cur, Ya_cur, Xb_cur, Yb_cur = self.get_ci_coeffs_for(cur)
|
|
574
|
+
|
|
575
|
+
alpha_diffs = top_differences(Xa_ref, Ya_ref, Xa_cur, Ya_cur, S_MO_a)
|
|
576
|
+
beta_diffs = top_differences(Xb_ref, Yb_ref, Xb_cur, Yb_cur, S_MO_b)
|
|
577
|
+
diffs = alpha_diffs + beta_diffs
|
|
578
|
+
return diffs
|
|
579
|
+
|
|
580
|
+
def dump_overlap_data(self):
|
|
581
|
+
if self.h5_dump:
|
|
582
|
+
# h5_group = self.get_h5_group()
|
|
583
|
+
|
|
584
|
+
# h5_group.attrs["ovlp_type"] = self.ovlp_type
|
|
585
|
+
# h5_group.attrs["ovlp_with"] = self.ovlp_with
|
|
586
|
+
# h5_group.attrs["orient"] = self.orient
|
|
587
|
+
# h5_group.attrs["atoms"] = np.bytes_(self.atoms)
|
|
588
|
+
|
|
589
|
+
# for key, shape in self.data_model.items():
|
|
590
|
+
# try:
|
|
591
|
+
# mapped_key = self.H5_MAP[key]
|
|
592
|
+
# except KeyError:
|
|
593
|
+
# mapped_key = key
|
|
594
|
+
# value = getattr(self, mapped_key)
|
|
595
|
+
# # Skip this value if the underlying list is empty
|
|
596
|
+
# if not value:
|
|
597
|
+
# continue
|
|
598
|
+
# cur_cycle = self.calc_counter
|
|
599
|
+
# cur_value = value[-1]
|
|
600
|
+
# # the CDD strings are currently not yet handled properly
|
|
601
|
+
# if type(cur_value) == PosixPath:
|
|
602
|
+
# cur_value = str(cur_value)
|
|
603
|
+
# continue
|
|
604
|
+
# if len(shape) > 1:
|
|
605
|
+
# h5_group[key][cur_cycle, : len(cur_value)] = cur_value
|
|
606
|
+
# else:
|
|
607
|
+
# h5_group[key][cur_cycle] = cur_value
|
|
608
|
+
pass
|
|
609
|
+
|
|
610
|
+
data_dict = {
|
|
611
|
+
"Ca": np.array(self.Ca_list, dtype=float),
|
|
612
|
+
"Cb": np.array(self.Cb_list, dtype=float),
|
|
613
|
+
"Xa": np.array(self.Xa_list, dtype=float),
|
|
614
|
+
"Ya": np.array(self.Ya_list, dtype=float),
|
|
615
|
+
"Xb": np.array(self.Xb_list, dtype=float),
|
|
616
|
+
"Yb": np.array(self.Yb_list, dtype=float),
|
|
617
|
+
"coords": np.array(self.coords_list, dtype=float),
|
|
618
|
+
"all_energies": np.array(self.all_energies_list, dtype=float),
|
|
619
|
+
}
|
|
620
|
+
if self.root:
|
|
621
|
+
root_dict = {
|
|
622
|
+
"calculated_roots": np.array(self.calculated_roots, dtype=int),
|
|
623
|
+
"roots": np.array(self.roots_list, dtype=int),
|
|
624
|
+
"root_flips": np.array(self.root_flips, dtype=bool),
|
|
625
|
+
"overlap_matrices": np.array(self.overlap_matrices, dtype=float),
|
|
626
|
+
"row_inds": np.array(self.row_inds, dtype=int),
|
|
627
|
+
"ref_cycles": np.array(self.ref_cycles, dtype=int),
|
|
628
|
+
"ref_roots": np.array(self.reference_roots, dtype=int),
|
|
629
|
+
}
|
|
630
|
+
data_dict.update(root_dict)
|
|
631
|
+
|
|
632
|
+
if self.cdd_cubes:
|
|
633
|
+
data_dict["cdd_cubes"] = np.array(self.cdd_cubes, dtype="S")
|
|
634
|
+
if self.cdd_imgs:
|
|
635
|
+
data_dict["cdd_imgs"] = np.array(self.cdd_imgs, dtype="S")
|
|
636
|
+
|
|
637
|
+
# with h5py.File(self.dump_fn, "w") as handle:
|
|
638
|
+
# for key, val in data_dict.items():
|
|
639
|
+
# if key in ("Ca", "Cb", "Xa", "Ya", "Xb", "Yb"):
|
|
640
|
+
# add_kwargs = {
|
|
641
|
+
# "compression": "gzip",
|
|
642
|
+
# "compression_opts": 9,
|
|
643
|
+
# }
|
|
644
|
+
# else:
|
|
645
|
+
# add_kwargs = {}
|
|
646
|
+
# handle.create_dataset(name=key, dtype=val.dtype, data=val, **add_kwargs)
|
|
647
|
+
# handle.attrs["ovlp_type"] = self.ovlp_type
|
|
648
|
+
# handle.attrs["ovlp_with"] = self.ovlp_with
|
|
649
|
+
# handle.attrs["orient"] = self.orient
|
|
650
|
+
# handle.attrs["atoms"] = np.array(self.atoms, "S1")
|
|
651
|
+
|
|
652
|
+
@staticmethod
|
|
653
|
+
def from_overlap_data(h5_fn, set_wfow=False):
|
|
654
|
+
calc = OverlapCalculator(track=True)
|
|
655
|
+
|
|
656
|
+
root_info = False
|
|
657
|
+
with h5py.File(h5_fn) as handle:
|
|
658
|
+
try:
|
|
659
|
+
ovlp_with = handle["ovlp_with"][()].decode()
|
|
660
|
+
ovlp_type = handle["ovlp_type"][()].decode()
|
|
661
|
+
except KeyError:
|
|
662
|
+
ovlp_with = handle.attrs["ovlp_with"]
|
|
663
|
+
ovlp_type = handle.attrs["ovlp_type"]
|
|
664
|
+
all_energies = handle["all_energies"][:]
|
|
665
|
+
try:
|
|
666
|
+
ref_roots = handle["ref_roots"][:]
|
|
667
|
+
roots = handle["roots"][:]
|
|
668
|
+
calculated_roots = handle["calculated_roots"][:]
|
|
669
|
+
root_info = True
|
|
670
|
+
except KeyError:
|
|
671
|
+
print(f"Couldn't find root information in '{h5_fn}'.")
|
|
672
|
+
# TODO: set using H5 map?
|
|
673
|
+
calc.Ca_list = handle["Ca"][:]
|
|
674
|
+
calc.Cb_list = handle["Cb"][:]
|
|
675
|
+
calc.Xa_list = handle["Xa"][:]
|
|
676
|
+
calc.Ya_list = handle["Ya"][:]
|
|
677
|
+
calc.Xb_list = handle["Xb"][:]
|
|
678
|
+
calc.Yb_list = handle["Yb"][:]
|
|
679
|
+
|
|
680
|
+
calc.ovlp_type = ovlp_type
|
|
681
|
+
calc.ovlp_with = ovlp_with
|
|
682
|
+
calc.all_energies_list = list(all_energies)
|
|
683
|
+
if root_info:
|
|
684
|
+
calc.roots_list = list(roots)
|
|
685
|
+
calc.calculated_roots = list(calculated_roots)
|
|
686
|
+
try:
|
|
687
|
+
calc.first_root = ref_roots[0]
|
|
688
|
+
calc.root = calc.first_root
|
|
689
|
+
except IndexError:
|
|
690
|
+
calc.root = roots[0]
|
|
691
|
+
|
|
692
|
+
# TODO: set wfow
|
|
693
|
+
# if (ovlp_type == "wf") or set_wfow:
|
|
694
|
+
# calc.set_wfow(ci_coeffs[0])
|
|
695
|
+
|
|
696
|
+
return calc
|
|
697
|
+
|
|
698
|
+
"""
|
|
699
|
+
def set_wfow(self, ci_coeffs):
|
|
700
|
+
occ_mo_num, virt_mo_num = ci_coeffs[0].shape
|
|
701
|
+
try:
|
|
702
|
+
wfow_mem = self.pal * self.mem
|
|
703
|
+
except AttributeError:
|
|
704
|
+
wfow_mem = 8000
|
|
705
|
+
self.wfow = WFOWrapper(
|
|
706
|
+
occ_mo_num,
|
|
707
|
+
virt_mo_num,
|
|
708
|
+
calc_number=self.calc_number,
|
|
709
|
+
wfow_mem=wfow_mem,
|
|
710
|
+
ncore=self.ncore,
|
|
711
|
+
conf_thresh=self.conf_thresh,
|
|
712
|
+
)
|
|
713
|
+
"""
|
|
714
|
+
|
|
715
|
+
def track_root(self, ovlp_type=None):
|
|
716
|
+
"""Check if a root flip occured occured compared to the previous cycle
|
|
717
|
+
by calculating the overlap matrix wrt. a reference cycle."""
|
|
718
|
+
|
|
719
|
+
if ovlp_type is None:
|
|
720
|
+
ovlp_type = self.ovlp_type
|
|
721
|
+
|
|
722
|
+
# Nothing to compare to if only one calculation was done yet.
|
|
723
|
+
# Nonetheless, dump the first cycle to HDF5.
|
|
724
|
+
if self.stored_calculations < 2:
|
|
725
|
+
self.dump_overlap_data()
|
|
726
|
+
self.log(
|
|
727
|
+
"Skipping overlap calculation in the first cycle "
|
|
728
|
+
"as there is nothing to compare to."
|
|
729
|
+
)
|
|
730
|
+
return False
|
|
731
|
+
|
|
732
|
+
S_AO = None
|
|
733
|
+
# We can only run a double molecule calculation if it is
|
|
734
|
+
# implemented for the specific calculator.
|
|
735
|
+
if self.double_mol and hasattr(self, "run_double_mol_calculation"):
|
|
736
|
+
old, new = self.get_indices()
|
|
737
|
+
two_coords = self.coords_list[old], self.coords_list[new]
|
|
738
|
+
S_AO = self.run_double_mol_calculation(self.atoms, *two_coords)
|
|
739
|
+
elif (self.double_mol is False) and (self.ovlp_type == "wf"):
|
|
740
|
+
# TODO: respect ref_mos?!
|
|
741
|
+
S_AO = self.get_sao_from_mo_coeffs(self.Ca_list[-1])
|
|
742
|
+
self.log("Created S_AO to avoid its creation in WFOverlap.")
|
|
743
|
+
|
|
744
|
+
if ovlp_type == "wf":
|
|
745
|
+
raise Exception("wf-overlaps are not yet implemented!")
|
|
746
|
+
overlap_mats = self.get_wf_overlaps(S_AO=S_AO)
|
|
747
|
+
overlaps = np.abs(overlap_mats[2])
|
|
748
|
+
# overlaps = overlaps**2
|
|
749
|
+
elif ovlp_type == "tden":
|
|
750
|
+
overlaps = self.get_tden_overlaps(S_AO=S_AO)
|
|
751
|
+
elif ovlp_type == "nto":
|
|
752
|
+
raise Exception("nto-overlaps are not yet implemented!")
|
|
753
|
+
overlaps = self.get_nto_overlaps(S_AO=S_AO)
|
|
754
|
+
elif ovlp_type == "nto_org":
|
|
755
|
+
raise Exception("nto_org-overlaps are not yet implemented!")
|
|
756
|
+
overlaps = self.get_nto_overlaps(S_AO=S_AO, org=True)
|
|
757
|
+
elif ovlp_type == "top":
|
|
758
|
+
top_rs = self.get_top_differences(S_AO=S_AO)
|
|
759
|
+
overlaps = 1 - top_rs
|
|
760
|
+
else:
|
|
761
|
+
raise Exception(
|
|
762
|
+
"Invalid overlap type key! Use one of " + ", ".join(self.VALID_KEYS)
|
|
763
|
+
)
|
|
764
|
+
self.overlap_matrices.append(overlaps)
|
|
765
|
+
overlaps = np.abs(overlaps)
|
|
766
|
+
|
|
767
|
+
# In the end we are looking for a root flip compared to the
|
|
768
|
+
# previous cycle.
|
|
769
|
+
# This is done by comparing the excited states at the current cycle
|
|
770
|
+
# to some older cycle (the first, the previous, or some cycle
|
|
771
|
+
# in between), by determining the highest overlap in a given row
|
|
772
|
+
# of the overlap matrix.
|
|
773
|
+
ref_root = self.roots_list[self.ref_cycle]
|
|
774
|
+
self.reference_roots.append(ref_root)
|
|
775
|
+
# Row index in the overlap matrix. Depends on the root of the reference
|
|
776
|
+
# cycle and corresponds to the old root.
|
|
777
|
+
row_ind = ref_root - 1
|
|
778
|
+
# With WFOverlaps the ground state is also present and the overlap
|
|
779
|
+
# matrix has shape (N+1, N+1) instead of (N, N), with N being the
|
|
780
|
+
# number of excited states.
|
|
781
|
+
if self.ovlp_type == "wf":
|
|
782
|
+
row_ind += 1
|
|
783
|
+
self.row_inds.append(row_ind)
|
|
784
|
+
self.ref_cycles.append(self.ref_cycle)
|
|
785
|
+
self.log(
|
|
786
|
+
f"Reference is cycle {self.ref_cycle}, root {ref_root}. "
|
|
787
|
+
f"Analyzing row {row_ind} of the overlap matrix."
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
ref_root_row = overlaps[row_ind]
|
|
791
|
+
new_root = ref_root_row.argmax()
|
|
792
|
+
max_overlap = ref_root_row[new_root]
|
|
793
|
+
if self.ovlp_type == "wf":
|
|
794
|
+
new_root -= 1
|
|
795
|
+
prev_root = self.root
|
|
796
|
+
self.log(f"Root at previous cycle is {prev_root}.")
|
|
797
|
+
self.root = new_root + 1
|
|
798
|
+
ref_root_row_str = ", ".join(
|
|
799
|
+
[f"{i}: {ov:.2%}" for i, ov in enumerate(ref_root_row)]
|
|
800
|
+
)
|
|
801
|
+
self.log(f"Overlaps: {ref_root_row_str}")
|
|
802
|
+
root_flip = self.root != prev_root
|
|
803
|
+
self.log(f"Highest overlap is {max_overlap:.2%}.")
|
|
804
|
+
if not root_flip:
|
|
805
|
+
self.log(f"Keeping current root {self.root}.")
|
|
806
|
+
else:
|
|
807
|
+
self.log(
|
|
808
|
+
f"Root flip! New root is {self.root}. Root at previous "
|
|
809
|
+
f"step was {prev_root}."
|
|
810
|
+
)
|
|
811
|
+
# Look for a new reference state if requested. We want to avoid
|
|
812
|
+
# overlap matrices just after a root flip.
|
|
813
|
+
if self.ovlp_with == "previous":
|
|
814
|
+
self.ref_cycle += 1
|
|
815
|
+
elif (self.ovlp_with == "adapt") and not root_flip:
|
|
816
|
+
self.log("Checking wether the reference cycle has to be adapted.")
|
|
817
|
+
sorted_inds = ref_root_row.argsort()
|
|
818
|
+
sec_highest, highest = ref_root_row[sorted_inds[-2:]]
|
|
819
|
+
ratio = sec_highest / highest
|
|
820
|
+
self.log(
|
|
821
|
+
f"Two highest overlaps: {sec_highest:.2%}, {highest:.2%}, "
|
|
822
|
+
f"ratio={ratio:.4f}"
|
|
823
|
+
)
|
|
824
|
+
above_thresh = highest >= self.adpt_thresh
|
|
825
|
+
self.log(
|
|
826
|
+
f"Highest overlap is above threshold? (>= {self.adpt_thresh:.4f}): "
|
|
827
|
+
f"{above_thresh}"
|
|
828
|
+
)
|
|
829
|
+
valid_ratio = self.adpt_min < ratio < self.adpt_max
|
|
830
|
+
self.log(
|
|
831
|
+
f"Ratio is valid? (between {self.adpt_min:.4f} and "
|
|
832
|
+
f"{self.adpt_max:.4f}): {valid_ratio}"
|
|
833
|
+
)
|
|
834
|
+
"""Only adapt the reference cycle when the overlaps are well
|
|
835
|
+
behaved and the following two conditions are True:
|
|
836
|
+
|
|
837
|
+
1.) The highest overlap is above the threshold.
|
|
838
|
+
|
|
839
|
+
2.) The ratio value of (second highest)/(highest) is valid. A small
|
|
840
|
+
value indicates cleary separated states and we probably
|
|
841
|
+
don't have to update the reference cycle as the overlaps are still
|
|
842
|
+
big enough.
|
|
843
|
+
As the overlaps between two states become more similar the ratio
|
|
844
|
+
approaches 1. This may occur in regions of state crossings and then
|
|
845
|
+
we dont't want to update the reference cycle.
|
|
846
|
+
"""
|
|
847
|
+
if above_thresh and valid_ratio:
|
|
848
|
+
self.ref_cycle = len(self.calculated_roots) - 1
|
|
849
|
+
|
|
850
|
+
if self.ref_cycle != self.ref_cycles[-1]:
|
|
851
|
+
self.log(f"New reference cycle is {self.ref_cycle}.")
|
|
852
|
+
else:
|
|
853
|
+
self.log(f"Keeping old reference cycle {self.ref_cycle}.")
|
|
854
|
+
|
|
855
|
+
self.root_flips.append(root_flip)
|
|
856
|
+
self.roots_list.append(self.root)
|
|
857
|
+
assert len(self.roots_list) == len(self.calculated_roots)
|
|
858
|
+
|
|
859
|
+
if self.cdds:
|
|
860
|
+
try:
|
|
861
|
+
self.calc_cdd_cube(self.root)
|
|
862
|
+
except Exception as err:
|
|
863
|
+
print("CDD calculation by Multiwfn crashed. Disabling it!")
|
|
864
|
+
self.log(err)
|
|
865
|
+
self.cdds = None
|
|
866
|
+
if self.cdds == "render":
|
|
867
|
+
self.render_cdd_cube()
|
|
868
|
+
|
|
869
|
+
self.dump_overlap_data()
|
|
870
|
+
self.log("\n")
|
|
871
|
+
|
|
872
|
+
# True if a root flip occured
|
|
873
|
+
return root_flip
|
|
874
|
+
|
|
875
|
+
def calc_cdd_cube(self, root, cycle=-1):
|
|
876
|
+
# Check if Calculator provides an input file (.fchk/.molden) for Mwfn
|
|
877
|
+
if not hasattr(self, "mwfn_wf"):
|
|
878
|
+
self.log(
|
|
879
|
+
"Calculator does not provide an input file for Multiwfn, "
|
|
880
|
+
"as 'self.mwfn_wf' is not set! Skipping CDD cube generation!"
|
|
881
|
+
)
|
|
882
|
+
if cycle != -1:
|
|
883
|
+
self.log("'cycle' argument to make_cdd_cube is currently ignored!")
|
|
884
|
+
energies = self.all_energies_list[cycle]
|
|
885
|
+
# As of Multiwfn 3.8 it seems that MWFN can't handle plain text input
|
|
886
|
+
# with spin labels, so we only provide the alpha part..
|
|
887
|
+
Xa, Ya, *_ = self.get_ci_coeffs_for(cycle)
|
|
888
|
+
exc_str = get_mwfn_exc_str(energies, Xa=Xa, Ya=Ya)
|
|
889
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
890
|
+
tmp_path = Path(tmp_dir)
|
|
891
|
+
exc_path = tmp_path / "exc_input"
|
|
892
|
+
with open(exc_path, "w") as handle:
|
|
893
|
+
handle.write(exc_str)
|
|
894
|
+
cubes = make_cdd(self.mwfn_wf, root, str(exc_path), tmp_path)
|
|
895
|
+
assert len(cubes) == 1
|
|
896
|
+
cube = cubes[0]
|
|
897
|
+
cube_fn = cubes[0].name
|
|
898
|
+
new_cube_fn = self.make_fn(cube_fn)
|
|
899
|
+
shutil.copy(cube, new_cube_fn)
|
|
900
|
+
self.cdd_cubes.append(new_cube_fn)
|
|
901
|
+
|
|
902
|
+
def render_cdd_cube(self):
|
|
903
|
+
cdd_cube = self.cdd_cubes[-1]
|
|
904
|
+
try:
|
|
905
|
+
cdd_img = render_cdd_cube_jmol(cdd_cube, orient=self.orient)
|
|
906
|
+
self.cdd_imgs.append(cdd_img)
|
|
907
|
+
except:
|
|
908
|
+
self.log("Something went wrong while rendering the CDD cube.")
|