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.
Files changed (372) hide show
  1. hessian_ff/__init__.py +50 -0
  2. hessian_ff/analytical_hessian.py +609 -0
  3. hessian_ff/constants.py +46 -0
  4. hessian_ff/forcefield.py +339 -0
  5. hessian_ff/loaders.py +608 -0
  6. hessian_ff/native/Makefile +8 -0
  7. hessian_ff/native/__init__.py +28 -0
  8. hessian_ff/native/analytical_hessian.py +88 -0
  9. hessian_ff/native/analytical_hessian_ext.cpp +258 -0
  10. hessian_ff/native/bonded.py +82 -0
  11. hessian_ff/native/bonded_ext.cpp +640 -0
  12. hessian_ff/native/loader.py +349 -0
  13. hessian_ff/native/nonbonded.py +118 -0
  14. hessian_ff/native/nonbonded_ext.cpp +1150 -0
  15. hessian_ff/prmtop_parmed.py +23 -0
  16. hessian_ff/system.py +107 -0
  17. hessian_ff/terms/__init__.py +14 -0
  18. hessian_ff/terms/angle.py +73 -0
  19. hessian_ff/terms/bond.py +44 -0
  20. hessian_ff/terms/cmap.py +406 -0
  21. hessian_ff/terms/dihedral.py +141 -0
  22. hessian_ff/terms/nonbonded.py +209 -0
  23. hessian_ff/tests/__init__.py +0 -0
  24. hessian_ff/tests/conftest.py +75 -0
  25. hessian_ff/tests/data/small/complex.parm7 +1346 -0
  26. hessian_ff/tests/data/small/complex.pdb +125 -0
  27. hessian_ff/tests/data/small/complex.rst7 +63 -0
  28. hessian_ff/tests/test_coords_input.py +44 -0
  29. hessian_ff/tests/test_energy_force.py +49 -0
  30. hessian_ff/tests/test_hessian.py +137 -0
  31. hessian_ff/tests/test_smoke.py +18 -0
  32. hessian_ff/tests/test_validation.py +40 -0
  33. hessian_ff/workflows.py +889 -0
  34. mlmm/__init__.py +36 -0
  35. mlmm/__main__.py +7 -0
  36. mlmm/_version.py +34 -0
  37. mlmm/add_elem_info.py +374 -0
  38. mlmm/advanced_help.py +91 -0
  39. mlmm/align_freeze_atoms.py +601 -0
  40. mlmm/all.py +3535 -0
  41. mlmm/bond_changes.py +231 -0
  42. mlmm/bool_compat.py +223 -0
  43. mlmm/cli.py +574 -0
  44. mlmm/cli_utils.py +166 -0
  45. mlmm/default_group.py +337 -0
  46. mlmm/defaults.py +467 -0
  47. mlmm/define_layer.py +526 -0
  48. mlmm/dft.py +1041 -0
  49. mlmm/energy_diagram.py +253 -0
  50. mlmm/extract.py +2213 -0
  51. mlmm/fix_altloc.py +464 -0
  52. mlmm/freq.py +1406 -0
  53. mlmm/harmonic_constraints.py +140 -0
  54. mlmm/hessian_cache.py +44 -0
  55. mlmm/hessian_calc.py +174 -0
  56. mlmm/irc.py +638 -0
  57. mlmm/mlmm_calc.py +2262 -0
  58. mlmm/mm_parm.py +945 -0
  59. mlmm/oniom_export.py +1983 -0
  60. mlmm/oniom_import.py +457 -0
  61. mlmm/opt.py +1742 -0
  62. mlmm/path_opt.py +1353 -0
  63. mlmm/path_search.py +2299 -0
  64. mlmm/preflight.py +88 -0
  65. mlmm/py.typed +1 -0
  66. mlmm/pysis_runner.py +45 -0
  67. mlmm/scan.py +1047 -0
  68. mlmm/scan2d.py +1226 -0
  69. mlmm/scan3d.py +1265 -0
  70. mlmm/scan_common.py +184 -0
  71. mlmm/summary_log.py +736 -0
  72. mlmm/trj2fig.py +448 -0
  73. mlmm/tsopt.py +2871 -0
  74. mlmm/utils.py +2309 -0
  75. mlmm/xtb_embedcharge_correction.py +475 -0
  76. mlmm_toolkit-0.2.2.dev0.dist-info/METADATA +1159 -0
  77. mlmm_toolkit-0.2.2.dev0.dist-info/RECORD +372 -0
  78. mlmm_toolkit-0.2.2.dev0.dist-info/WHEEL +5 -0
  79. mlmm_toolkit-0.2.2.dev0.dist-info/entry_points.txt +2 -0
  80. mlmm_toolkit-0.2.2.dev0.dist-info/licenses/LICENSE +674 -0
  81. mlmm_toolkit-0.2.2.dev0.dist-info/top_level.txt +4 -0
  82. pysisyphus/Geometry.py +1667 -0
  83. pysisyphus/LICENSE +674 -0
  84. pysisyphus/TableFormatter.py +63 -0
  85. pysisyphus/TablePrinter.py +74 -0
  86. pysisyphus/__init__.py +12 -0
  87. pysisyphus/calculators/AFIR.py +452 -0
  88. pysisyphus/calculators/AnaPot.py +20 -0
  89. pysisyphus/calculators/AnaPot2.py +48 -0
  90. pysisyphus/calculators/AnaPot3.py +12 -0
  91. pysisyphus/calculators/AnaPot4.py +20 -0
  92. pysisyphus/calculators/AnaPotBase.py +337 -0
  93. pysisyphus/calculators/AnaPotCBM.py +25 -0
  94. pysisyphus/calculators/AtomAtomTransTorque.py +154 -0
  95. pysisyphus/calculators/CFOUR.py +250 -0
  96. pysisyphus/calculators/Calculator.py +844 -0
  97. pysisyphus/calculators/CerjanMiller.py +24 -0
  98. pysisyphus/calculators/Composite.py +123 -0
  99. pysisyphus/calculators/ConicalIntersection.py +171 -0
  100. pysisyphus/calculators/DFTBp.py +430 -0
  101. pysisyphus/calculators/DFTD3.py +66 -0
  102. pysisyphus/calculators/DFTD4.py +84 -0
  103. pysisyphus/calculators/Dalton.py +61 -0
  104. pysisyphus/calculators/Dimer.py +681 -0
  105. pysisyphus/calculators/Dummy.py +20 -0
  106. pysisyphus/calculators/EGO.py +76 -0
  107. pysisyphus/calculators/EnergyMin.py +224 -0
  108. pysisyphus/calculators/ExternalPotential.py +264 -0
  109. pysisyphus/calculators/FakeASE.py +35 -0
  110. pysisyphus/calculators/FourWellAnaPot.py +28 -0
  111. pysisyphus/calculators/FreeEndNEBPot.py +39 -0
  112. pysisyphus/calculators/Gaussian09.py +18 -0
  113. pysisyphus/calculators/Gaussian16.py +726 -0
  114. pysisyphus/calculators/HardSphere.py +159 -0
  115. pysisyphus/calculators/IDPPCalculator.py +49 -0
  116. pysisyphus/calculators/IPIClient.py +133 -0
  117. pysisyphus/calculators/IPIServer.py +234 -0
  118. pysisyphus/calculators/LEPSBase.py +24 -0
  119. pysisyphus/calculators/LEPSExpr.py +139 -0
  120. pysisyphus/calculators/LennardJones.py +80 -0
  121. pysisyphus/calculators/MOPAC.py +219 -0
  122. pysisyphus/calculators/MullerBrownSympyPot.py +51 -0
  123. pysisyphus/calculators/MultiCalc.py +85 -0
  124. pysisyphus/calculators/NFK.py +45 -0
  125. pysisyphus/calculators/OBabel.py +87 -0
  126. pysisyphus/calculators/ONIOMv2.py +1129 -0
  127. pysisyphus/calculators/ORCA.py +893 -0
  128. pysisyphus/calculators/ORCA5.py +6 -0
  129. pysisyphus/calculators/OpenMM.py +88 -0
  130. pysisyphus/calculators/OpenMolcas.py +281 -0
  131. pysisyphus/calculators/OverlapCalculator.py +908 -0
  132. pysisyphus/calculators/Psi4.py +218 -0
  133. pysisyphus/calculators/PyPsi4.py +37 -0
  134. pysisyphus/calculators/PySCF.py +341 -0
  135. pysisyphus/calculators/PyXTB.py +73 -0
  136. pysisyphus/calculators/QCEngine.py +106 -0
  137. pysisyphus/calculators/Rastrigin.py +22 -0
  138. pysisyphus/calculators/Remote.py +76 -0
  139. pysisyphus/calculators/Rosenbrock.py +15 -0
  140. pysisyphus/calculators/SocketCalc.py +97 -0
  141. pysisyphus/calculators/TIP3P.py +111 -0
  142. pysisyphus/calculators/TransTorque.py +161 -0
  143. pysisyphus/calculators/Turbomole.py +965 -0
  144. pysisyphus/calculators/VRIPot.py +37 -0
  145. pysisyphus/calculators/WFOWrapper.py +333 -0
  146. pysisyphus/calculators/WFOWrapper2.py +341 -0
  147. pysisyphus/calculators/XTB.py +418 -0
  148. pysisyphus/calculators/__init__.py +81 -0
  149. pysisyphus/calculators/cosmo_data.py +139 -0
  150. pysisyphus/calculators/parser.py +150 -0
  151. pysisyphus/color.py +19 -0
  152. pysisyphus/config.py +133 -0
  153. pysisyphus/constants.py +65 -0
  154. pysisyphus/cos/AdaptiveNEB.py +230 -0
  155. pysisyphus/cos/ChainOfStates.py +725 -0
  156. pysisyphus/cos/FreeEndNEB.py +25 -0
  157. pysisyphus/cos/FreezingString.py +103 -0
  158. pysisyphus/cos/GrowingChainOfStates.py +71 -0
  159. pysisyphus/cos/GrowingNT.py +309 -0
  160. pysisyphus/cos/GrowingString.py +508 -0
  161. pysisyphus/cos/NEB.py +189 -0
  162. pysisyphus/cos/SimpleZTS.py +64 -0
  163. pysisyphus/cos/__init__.py +22 -0
  164. pysisyphus/cos/stiffness.py +199 -0
  165. pysisyphus/drivers/__init__.py +17 -0
  166. pysisyphus/drivers/afir.py +855 -0
  167. pysisyphus/drivers/barriers.py +271 -0
  168. pysisyphus/drivers/birkholz.py +138 -0
  169. pysisyphus/drivers/cluster.py +318 -0
  170. pysisyphus/drivers/diabatization.py +133 -0
  171. pysisyphus/drivers/merge.py +368 -0
  172. pysisyphus/drivers/merge_mol2.py +322 -0
  173. pysisyphus/drivers/opt.py +375 -0
  174. pysisyphus/drivers/perf.py +91 -0
  175. pysisyphus/drivers/pka.py +52 -0
  176. pysisyphus/drivers/precon_pos_rot.py +669 -0
  177. pysisyphus/drivers/rates.py +480 -0
  178. pysisyphus/drivers/replace.py +219 -0
  179. pysisyphus/drivers/scan.py +212 -0
  180. pysisyphus/drivers/spectrum.py +166 -0
  181. pysisyphus/drivers/thermo.py +31 -0
  182. pysisyphus/dynamics/Gaussian.py +103 -0
  183. pysisyphus/dynamics/__init__.py +20 -0
  184. pysisyphus/dynamics/colvars.py +136 -0
  185. pysisyphus/dynamics/driver.py +297 -0
  186. pysisyphus/dynamics/helpers.py +256 -0
  187. pysisyphus/dynamics/lincs.py +105 -0
  188. pysisyphus/dynamics/mdp.py +364 -0
  189. pysisyphus/dynamics/rattle.py +121 -0
  190. pysisyphus/dynamics/thermostats.py +128 -0
  191. pysisyphus/dynamics/wigner.py +266 -0
  192. pysisyphus/elem_data.py +3473 -0
  193. pysisyphus/exceptions.py +2 -0
  194. pysisyphus/filtertrj.py +69 -0
  195. pysisyphus/helpers.py +623 -0
  196. pysisyphus/helpers_pure.py +649 -0
  197. pysisyphus/init_logging.py +50 -0
  198. pysisyphus/intcoords/Bend.py +69 -0
  199. pysisyphus/intcoords/Bend2.py +25 -0
  200. pysisyphus/intcoords/BondedFragment.py +32 -0
  201. pysisyphus/intcoords/Cartesian.py +41 -0
  202. pysisyphus/intcoords/CartesianCoords.py +140 -0
  203. pysisyphus/intcoords/Coords.py +56 -0
  204. pysisyphus/intcoords/DLC.py +197 -0
  205. pysisyphus/intcoords/DistanceFunction.py +34 -0
  206. pysisyphus/intcoords/DummyImproper.py +70 -0
  207. pysisyphus/intcoords/DummyTorsion.py +72 -0
  208. pysisyphus/intcoords/LinearBend.py +105 -0
  209. pysisyphus/intcoords/LinearDisplacement.py +80 -0
  210. pysisyphus/intcoords/OutOfPlane.py +59 -0
  211. pysisyphus/intcoords/PrimTypes.py +286 -0
  212. pysisyphus/intcoords/Primitive.py +137 -0
  213. pysisyphus/intcoords/RedundantCoords.py +659 -0
  214. pysisyphus/intcoords/RobustTorsion.py +59 -0
  215. pysisyphus/intcoords/Rotation.py +147 -0
  216. pysisyphus/intcoords/Stretch.py +31 -0
  217. pysisyphus/intcoords/Torsion.py +101 -0
  218. pysisyphus/intcoords/Torsion2.py +25 -0
  219. pysisyphus/intcoords/Translation.py +45 -0
  220. pysisyphus/intcoords/__init__.py +61 -0
  221. pysisyphus/intcoords/augment_bonds.py +126 -0
  222. pysisyphus/intcoords/derivatives.py +10512 -0
  223. pysisyphus/intcoords/eval.py +80 -0
  224. pysisyphus/intcoords/exceptions.py +37 -0
  225. pysisyphus/intcoords/findiffs.py +48 -0
  226. pysisyphus/intcoords/generate_derivatives.py +414 -0
  227. pysisyphus/intcoords/helpers.py +235 -0
  228. pysisyphus/intcoords/logging_conf.py +10 -0
  229. pysisyphus/intcoords/mp_derivatives.py +10836 -0
  230. pysisyphus/intcoords/setup.py +962 -0
  231. pysisyphus/intcoords/setup_fast.py +176 -0
  232. pysisyphus/intcoords/update.py +272 -0
  233. pysisyphus/intcoords/valid.py +89 -0
  234. pysisyphus/interpolate/Geodesic.py +93 -0
  235. pysisyphus/interpolate/IDPP.py +55 -0
  236. pysisyphus/interpolate/Interpolator.py +116 -0
  237. pysisyphus/interpolate/LST.py +70 -0
  238. pysisyphus/interpolate/Redund.py +152 -0
  239. pysisyphus/interpolate/__init__.py +9 -0
  240. pysisyphus/interpolate/helpers.py +34 -0
  241. pysisyphus/io/__init__.py +22 -0
  242. pysisyphus/io/aomix.py +178 -0
  243. pysisyphus/io/cjson.py +24 -0
  244. pysisyphus/io/crd.py +101 -0
  245. pysisyphus/io/cube.py +220 -0
  246. pysisyphus/io/fchk.py +184 -0
  247. pysisyphus/io/hdf5.py +49 -0
  248. pysisyphus/io/hessian.py +72 -0
  249. pysisyphus/io/mol2.py +146 -0
  250. pysisyphus/io/molden.py +293 -0
  251. pysisyphus/io/orca.py +189 -0
  252. pysisyphus/io/pdb.py +269 -0
  253. pysisyphus/io/psf.py +79 -0
  254. pysisyphus/io/pubchem.py +31 -0
  255. pysisyphus/io/qcschema.py +34 -0
  256. pysisyphus/io/sdf.py +29 -0
  257. pysisyphus/io/xyz.py +61 -0
  258. pysisyphus/io/zmat.py +175 -0
  259. pysisyphus/irc/DWI.py +108 -0
  260. pysisyphus/irc/DampedVelocityVerlet.py +134 -0
  261. pysisyphus/irc/Euler.py +22 -0
  262. pysisyphus/irc/EulerPC.py +345 -0
  263. pysisyphus/irc/GonzalezSchlegel.py +187 -0
  264. pysisyphus/irc/IMKMod.py +164 -0
  265. pysisyphus/irc/IRC.py +878 -0
  266. pysisyphus/irc/IRCDummy.py +10 -0
  267. pysisyphus/irc/Instanton.py +307 -0
  268. pysisyphus/irc/LQA.py +53 -0
  269. pysisyphus/irc/ModeKill.py +136 -0
  270. pysisyphus/irc/ParamPlot.py +53 -0
  271. pysisyphus/irc/RK4.py +36 -0
  272. pysisyphus/irc/__init__.py +31 -0
  273. pysisyphus/irc/initial_displ.py +219 -0
  274. pysisyphus/linalg.py +411 -0
  275. pysisyphus/line_searches/Backtracking.py +88 -0
  276. pysisyphus/line_searches/HagerZhang.py +184 -0
  277. pysisyphus/line_searches/LineSearch.py +232 -0
  278. pysisyphus/line_searches/StrongWolfe.py +108 -0
  279. pysisyphus/line_searches/__init__.py +9 -0
  280. pysisyphus/line_searches/interpol.py +15 -0
  281. pysisyphus/modefollow/NormalMode.py +40 -0
  282. pysisyphus/modefollow/__init__.py +10 -0
  283. pysisyphus/modefollow/davidson.py +199 -0
  284. pysisyphus/modefollow/lanczos.py +95 -0
  285. pysisyphus/optimizers/BFGS.py +99 -0
  286. pysisyphus/optimizers/BacktrackingOptimizer.py +113 -0
  287. pysisyphus/optimizers/ConjugateGradient.py +98 -0
  288. pysisyphus/optimizers/CubicNewton.py +75 -0
  289. pysisyphus/optimizers/FIRE.py +113 -0
  290. pysisyphus/optimizers/HessianOptimizer.py +1176 -0
  291. pysisyphus/optimizers/LBFGS.py +228 -0
  292. pysisyphus/optimizers/LayerOpt.py +411 -0
  293. pysisyphus/optimizers/MicroOptimizer.py +169 -0
  294. pysisyphus/optimizers/NCOptimizer.py +90 -0
  295. pysisyphus/optimizers/Optimizer.py +1084 -0
  296. pysisyphus/optimizers/PreconLBFGS.py +260 -0
  297. pysisyphus/optimizers/PreconSteepestDescent.py +7 -0
  298. pysisyphus/optimizers/QuickMin.py +74 -0
  299. pysisyphus/optimizers/RFOptimizer.py +181 -0
  300. pysisyphus/optimizers/RSA.py +99 -0
  301. pysisyphus/optimizers/StabilizedQNMethod.py +248 -0
  302. pysisyphus/optimizers/SteepestDescent.py +23 -0
  303. pysisyphus/optimizers/StringOptimizer.py +173 -0
  304. pysisyphus/optimizers/__init__.py +41 -0
  305. pysisyphus/optimizers/closures.py +301 -0
  306. pysisyphus/optimizers/cls_map.py +58 -0
  307. pysisyphus/optimizers/exceptions.py +6 -0
  308. pysisyphus/optimizers/gdiis.py +280 -0
  309. pysisyphus/optimizers/guess_hessians.py +311 -0
  310. pysisyphus/optimizers/hessian_updates.py +355 -0
  311. pysisyphus/optimizers/poly_fit.py +285 -0
  312. pysisyphus/optimizers/precon.py +153 -0
  313. pysisyphus/optimizers/restrict_step.py +24 -0
  314. pysisyphus/pack.py +172 -0
  315. pysisyphus/peakdetect.py +948 -0
  316. pysisyphus/plot.py +1031 -0
  317. pysisyphus/run.py +2106 -0
  318. pysisyphus/socket_helper.py +74 -0
  319. pysisyphus/stocastic/FragmentKick.py +132 -0
  320. pysisyphus/stocastic/Kick.py +81 -0
  321. pysisyphus/stocastic/Pipeline.py +303 -0
  322. pysisyphus/stocastic/__init__.py +21 -0
  323. pysisyphus/stocastic/align.py +127 -0
  324. pysisyphus/testing.py +96 -0
  325. pysisyphus/thermo.py +156 -0
  326. pysisyphus/trj.py +824 -0
  327. pysisyphus/tsoptimizers/RSIRFOptimizer.py +56 -0
  328. pysisyphus/tsoptimizers/RSPRFOptimizer.py +182 -0
  329. pysisyphus/tsoptimizers/TRIM.py +59 -0
  330. pysisyphus/tsoptimizers/TSHessianOptimizer.py +463 -0
  331. pysisyphus/tsoptimizers/__init__.py +23 -0
  332. pysisyphus/wavefunction/Basis.py +239 -0
  333. pysisyphus/wavefunction/DIIS.py +76 -0
  334. pysisyphus/wavefunction/__init__.py +25 -0
  335. pysisyphus/wavefunction/build_ext.py +42 -0
  336. pysisyphus/wavefunction/cart2sph.py +190 -0
  337. pysisyphus/wavefunction/diabatization.py +304 -0
  338. pysisyphus/wavefunction/excited_states.py +435 -0
  339. pysisyphus/wavefunction/gen_ints.py +1811 -0
  340. pysisyphus/wavefunction/helpers.py +104 -0
  341. pysisyphus/wavefunction/ints/__init__.py +0 -0
  342. pysisyphus/wavefunction/ints/boys.py +193 -0
  343. pysisyphus/wavefunction/ints/boys_table_N_64_xasym_27.1_step_0.01.npy +0 -0
  344. pysisyphus/wavefunction/ints/cart_gto3d.py +176 -0
  345. pysisyphus/wavefunction/ints/coulomb3d.py +25928 -0
  346. pysisyphus/wavefunction/ints/diag_quadrupole3d.py +10036 -0
  347. pysisyphus/wavefunction/ints/dipole3d.py +8762 -0
  348. pysisyphus/wavefunction/ints/int2c2e3d.py +7198 -0
  349. pysisyphus/wavefunction/ints/int3c2e3d_sph.py +65040 -0
  350. pysisyphus/wavefunction/ints/kinetic3d.py +8240 -0
  351. pysisyphus/wavefunction/ints/ovlp3d.py +3777 -0
  352. pysisyphus/wavefunction/ints/quadrupole3d.py +15054 -0
  353. pysisyphus/wavefunction/ints/self_ovlp3d.py +198 -0
  354. pysisyphus/wavefunction/localization.py +458 -0
  355. pysisyphus/wavefunction/multipole.py +159 -0
  356. pysisyphus/wavefunction/normalization.py +36 -0
  357. pysisyphus/wavefunction/pop_analysis.py +134 -0
  358. pysisyphus/wavefunction/shells.py +1171 -0
  359. pysisyphus/wavefunction/wavefunction.py +504 -0
  360. pysisyphus/wrapper/__init__.py +11 -0
  361. pysisyphus/wrapper/exceptions.py +2 -0
  362. pysisyphus/wrapper/jmol.py +120 -0
  363. pysisyphus/wrapper/mwfn.py +169 -0
  364. pysisyphus/wrapper/packmol.py +71 -0
  365. pysisyphus/xyzloader.py +168 -0
  366. pysisyphus/yaml_mods.py +45 -0
  367. thermoanalysis/LICENSE +674 -0
  368. thermoanalysis/QCData.py +244 -0
  369. thermoanalysis/__init__.py +0 -0
  370. thermoanalysis/config.py +3 -0
  371. thermoanalysis/constants.py +20 -0
  372. 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.")