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,965 @@
1
+ from math import sqrt
2
+ import os
3
+ from pathlib import Path
4
+ import re
5
+ import shutil
6
+ import subprocess
7
+ import warnings
8
+
9
+ from jinja2 import Template
10
+ import numpy as np
11
+ import pyparsing as pp
12
+
13
+ from pysisyphus.calculators.cosmo_data import COSMO_RADII
14
+ from pysisyphus.calculators.OverlapCalculator import OverlapCalculator
15
+ from pysisyphus.calculators.parser import (
16
+ parse_turbo_gradient,
17
+ parse_turbo_ccre0_ascii,
18
+ parse_turbo_mos,
19
+ # parse_turbo_exstates,
20
+ parse_turbo_exstates_re as parse_turbo_exstates,
21
+ )
22
+ from pysisyphus.helpers_pure import file_or_str, get_random_path
23
+
24
+
25
+ def index_strs_for_atoms(atoms):
26
+ indices = dict()
27
+ for i, atom in enumerate(atoms, 1):
28
+ indices.setdefault(atom.lower(), list()).append(i)
29
+
30
+ pairs = dict()
31
+ for key, values in indices.items():
32
+ pairs[key] = list()
33
+ i_end = len(values) - 1
34
+ start = None
35
+ for i, v in enumerate(values):
36
+ if start is None:
37
+ start = v
38
+ if (i == i_end) or (v + 1 != values[i + 1]):
39
+ end = None if (start == v) else v
40
+ pairs[key].append((start, end))
41
+ start = None
42
+
43
+ atom_strs = dict()
44
+ for key, prs in pairs.items():
45
+ tmp = list()
46
+ for start, end in prs:
47
+ if end is None:
48
+ itm = str(start)
49
+ else:
50
+ itm = f"{start}-{end}"
51
+ tmp.append(itm)
52
+ atom_str = f"{key} " + ",".join(tmp)
53
+ atom_strs[key] = atom_str
54
+
55
+ return atom_strs
56
+
57
+
58
+ def get_cosmo_data_groups(atoms, epsilon, rsolv=None, dcosmo_rs=None):
59
+ cosmo_dgs = {
60
+ "cosmo": {
61
+ "epsilon": epsilon,
62
+ },
63
+ "cosmo_out": "file=out.ccf",
64
+ "cosmo_data": "file=cosmo_transfer.tmp",
65
+ }
66
+ if rsolv is not None:
67
+ cosmo_dgs["cosmo"]["rsolv"] = rsolv
68
+ # Transform atom list into TURBOMOLE-style compressed indices
69
+ # H, H, H, O, O, H, O
70
+ # is transformed to
71
+ # h 1-3, 6
72
+ # o 4-5, 7
73
+ # etc.
74
+ index_strs = index_strs_for_atoms(atoms)
75
+ cosmo_atom_strs = list()
76
+ cosmo_atoms = dict()
77
+ # This does not yet work; so we stick with the default radii.
78
+ # for key, index_str in index_strs.items():
79
+ # radius = COSMO_RADII[key].radius
80
+ # cosmo_atoms[index_str] = f"\nradius={radius:.6f}"
81
+ # cosmo_dgs["cosmo_atoms"] = cosmo_atoms
82
+
83
+ if dcosmo_rs:
84
+ cosmo_dgs["dcosmo_rs"] = f"file={dcosmo_rs}"
85
+ return cosmo_dgs
86
+
87
+
88
+ DATA_GROUP_TPL = Template(
89
+ """
90
+ {% for dg in data_groups %}
91
+ ${{ dg }}
92
+ {% for key, value in data_groups[dg].items() %}
93
+ {{ key }}{% if value %} ={{ value }}{% endif %}
94
+
95
+ {% endfor %}
96
+ {% endfor %}
97
+ """,
98
+ trim_blocks=True,
99
+ lstrip_blocks=True,
100
+ )
101
+ SIMPLE_INPUT_TPL = Template(
102
+ """
103
+ $atoms
104
+ basis={{ basis }}
105
+ $coord file=coord
106
+ $grad file=gradient
107
+ $symmetry c1
108
+ $eht charge={{ charge }} unpaired={{ unpaired }}
109
+ {{ data_groups }}
110
+ $end
111
+ """
112
+ )
113
+
114
+
115
+ def render_data_groups(raw_data_groups):
116
+ data_groups = dict()
117
+ for dg, kws in raw_data_groups.items():
118
+ # datagroup w/o keywords
119
+ if kws is None:
120
+ data_groups[dg] = dict()
121
+ # datagroup w/ one keyword on same line. Here we just merge the name and the keyword,
122
+ # so they appear on the same line.
123
+ elif type(kws) == str:
124
+ data_groups[f"{dg} {kws}"] = dict()
125
+ # Otherwise a dict is expected
126
+ elif type(kws) == dict:
127
+ data_groups[dg] = kws
128
+ else:
129
+ raise Exception(f"Can't handle input '{dg}': '{kws}'!")
130
+ return DATA_GROUP_TPL.render(data_groups=data_groups)
131
+
132
+
133
+ def control_from_simple_input(simple_inp, charge, mult, cosmo_kwargs=None):
134
+ """Create control file from 'simple input'.
135
+
136
+ See examples/opt/26_turbomole_simple_input/ for an example."""
137
+ unpaired = mult - 1
138
+ simple_inp = simple_inp.copy()
139
+ basis = simple_inp.pop("basis")
140
+ # Add memory statement, if not already present
141
+ if "maxcor" not in simple_inp.keys():
142
+ simple_inp["maxcor"] = "500 MiB per_core"
143
+
144
+ data_groups = render_data_groups(simple_inp)
145
+
146
+ rendered = SIMPLE_INPUT_TPL.render(
147
+ basis=basis,
148
+ charge=charge,
149
+ unpaired=unpaired,
150
+ data_groups=data_groups,
151
+ )
152
+ return rendered
153
+
154
+
155
+ class Turbomole(OverlapCalculator):
156
+ conf_key = "turbomole"
157
+ _set_plans = (
158
+ "out",
159
+ "control",
160
+ "alpha",
161
+ "beta",
162
+ "mos",
163
+ ("ciss_a", "td_vec_fn"),
164
+ ("sing_a", "td_vec_fn"),
165
+ "ccres",
166
+ "exstates",
167
+ "mwfn_wf",
168
+ )
169
+
170
+ def __init__(
171
+ self,
172
+ control_path=None,
173
+ simple_input=None,
174
+ root=None,
175
+ double_mol_path=None,
176
+ cosmo_kwargs=None,
177
+ **kwargs,
178
+ ):
179
+ super().__init__(**kwargs)
180
+
181
+ assert (control_path is not None) or (
182
+ simple_input is not None
183
+ ), "Please either provide a prepared 'control_path' or 'simple_input'!"
184
+
185
+ # Handle simple input
186
+ if simple_input:
187
+ control_path = (self.out_dir / get_random_path("control_path")).absolute()
188
+ self.log(
189
+ "Set 'control_path' to '{control_path}'. Creating 'control' from simple input in it."
190
+ )
191
+ try:
192
+ control_path.mkdir()
193
+ except FileExistsError:
194
+ # Clean directory if already exists
195
+ for fn in control_path.iterdir():
196
+ fn.unlink()
197
+ control_str = control_from_simple_input(
198
+ simple_input, charge=self.charge, mult=self.mult
199
+ )
200
+ with open(control_path / "control", "w") as handle:
201
+ handle.write(control_str)
202
+ # End of simple input handling
203
+
204
+ # Set provided control_path or use the one generated for simple_input
205
+ self.control_path = Path(control_path).absolute()
206
+
207
+ self.root = root
208
+ self.double_mol_path = double_mol_path
209
+ if self.double_mol_path:
210
+ self.double_mol_path = Path(self.double_mol_path)
211
+ # Check if the overlap matrix will be printed and assert
212
+ # that no SCF iterations are done.
213
+ if self.double_mol_path:
214
+ with open(self.double_mol_path / "control") as handle:
215
+ text = handle.read()
216
+ assert re.search(r"\$intsdebug\s*sao", text) and re.search(
217
+ r"\$scfiterlimit\s*0", text
218
+ ), ("Please set " "$intsdebug sao and $scfiterlimit 0 !")
219
+ self.cosmo_kwargs = cosmo_kwargs
220
+ if self.cosmo_kwargs:
221
+ assert (
222
+ "epsilon" in self.cosmo_kwargs
223
+ ), "If 'cosmo_kwargs' is given 'epsilon' must be specified!"
224
+
225
+ self.to_keep = (
226
+ "control",
227
+ "mos",
228
+ "alpha",
229
+ "beta",
230
+ "out",
231
+ "ciss_a",
232
+ "ucis_a",
233
+ "gradient",
234
+ "sing_a",
235
+ "__ccre*",
236
+ "exstates",
237
+ "coord",
238
+ "mwfn_wf:wavefunction.molden",
239
+ "input.xyz",
240
+ "pc_gradients",
241
+ "nprhessian",
242
+ )
243
+
244
+ self.parser_funcs = {
245
+ "energy": self.parse_energy,
246
+ "force": self.parse_force,
247
+ "hessian": self.parse_hessian,
248
+ "double_mol": self.parse_double_mol,
249
+ "noparse": lambda path: None,
250
+ }
251
+
252
+ # Turbomole uses the 'control' file implicitly
253
+ self.inp_fn = ""
254
+ self.out_fn = "turbomole.out"
255
+ # MO coefficient files
256
+ self.mos = None
257
+ self.alpha = None
258
+ self.beta = None
259
+
260
+ # Prepare base_cmd
261
+ with open(self.control_path / "control") as handle:
262
+ text = handle.read()
263
+ scf_cmd = "dscf"
264
+ second_cmd = "grad"
265
+ # Check for RI
266
+ if ("$rij" in text) or ("$rik" in text):
267
+ scf_cmd = "ridft"
268
+ second_cmd = "rdgrad"
269
+ self.log("Found RI calculation.")
270
+
271
+ self.uhf = ("$uhf" in text) or (self.mult > 1)
272
+
273
+ # It seems as they changed whats written in the control file in version 7.7
274
+ try:
275
+ self.set_occ_and_mo_nums(text)
276
+ except TypeError:
277
+ print(
278
+ "Parsing of occupied and virtual MO numbers failed! Disabling "
279
+ "excited state tracking!"
280
+ )
281
+ self.track = False
282
+
283
+ assert not (("$exopt" in text) and ("$ricc2" in text)), (
284
+ "Found $exopt and $ricc2 in the control file! $exopt is used "
285
+ "for TD-DFT/TDA gradients whereas $ricc2 with 'geoopt ...' "
286
+ "leads to ricc2 gradients. Please delete one of the keywords!"
287
+ )
288
+
289
+ self.td = False
290
+ self.td_vec_fn = None
291
+ self.ricc2 = False
292
+ self.ricc2_opt = False
293
+ # Check for excited state calculation
294
+ if "$exopt" in text:
295
+ exopt_re = r"\$exopt\s*(\d+)"
296
+ self.root = int(re.search(exopt_re, text)[1])
297
+ second_cmd = "egrad"
298
+ self.prepare_td(text)
299
+ self.td = True
300
+ elif "$soes" in text:
301
+ second_cmd = "escf"
302
+ self.td = True
303
+ self.prepare_td(text)
304
+ elif ("$ricc2" in text) and ("$excitations" in text):
305
+ self.ricc2 = True
306
+ self.ricc2_opt = "geoopt" in text
307
+ second_cmd = "ricc2"
308
+ self.prepare_td(text)
309
+ self.root = self.get_ricc2_root(text)
310
+ try:
311
+ frozen_mos = int(re.search(r"implicit core=\s*(\d+)", text)[1])
312
+ except TypeError:
313
+ frozen_mos = 0
314
+ self.frozen_mos = frozen_mos
315
+ self.log(f"Found {self.frozen_mos} frozen orbitals.")
316
+ if self.track:
317
+ assert self.td or self.ricc2, (
318
+ "track=True can only be used "
319
+ "in connection with excited state calculations."
320
+ )
321
+ # Right now this can't handle a root flip from some excited state
322
+ # to the ground state ... Then we would need grad/rdgrad again,
323
+ # instead of egrad.
324
+ self.scf_cmd = scf_cmd
325
+ self.second_cmd = second_cmd
326
+
327
+ # Setup several cmds, depending on the calc type
328
+ def get_cmd(cmd):
329
+ return ";".join((self.scf_cmd, cmd))
330
+
331
+ if self.td:
332
+ self.energy_cmd = get_cmd("escf")
333
+ self.forces_cmd = get_cmd("egrad")
334
+ self.hessian_cmd = "not_yet_implemented"
335
+ elif self.ricc2:
336
+ ricc2_cmd = get_cmd("ricc2")
337
+ self.energy_cmd = ricc2_cmd
338
+ self.forces_cmd = ricc2_cmd
339
+ self.hessian_cmd = "not_yet_implemented"
340
+ else:
341
+ self.energy_cmd = self.scf_cmd
342
+ self.forces_cmd = get_cmd(second_cmd)
343
+ self.hessian_cmd = get_cmd("aoforce")
344
+ self.log("Prepared commands:")
345
+ self.log("\tEnergy cmd: " + self.energy_cmd)
346
+ self.log("\tForces cmd: " + self.forces_cmd)
347
+ self.log("\tHessian cmd: " + self.hessian_cmd)
348
+
349
+ if self.td or self.ricc2 and (self.root is None):
350
+ self.root = 1
351
+ warnings.warn(
352
+ "No root set! Either include '$exopt' for TDA/TDDFT or "
353
+ "'geoopt' for ricc2 in the control or supply a value for 'root'! "
354
+ f"Continuing with root={self.root}."
355
+ )
356
+
357
+ def set_occ_and_mo_nums(self, text):
358
+ # Determine number of basis functions
359
+ nbf_re = r"nbf\(AO\)=(\d+)"
360
+ nbf = int(re.search(nbf_re, text)[1])
361
+
362
+ self.occ_mos = None
363
+ self.virt_mos = None
364
+
365
+ # Determine number of occupied orbitals
366
+ if not self.uhf:
367
+ # Sometimes Turbomole does NOT use a range, but only a single integer,
368
+ # e.g., for hydrogen (H_2).
369
+ occ_re = r"closed shells\s+(\w)\s*(\d+)-?(\d*)"
370
+ occ_mobj = re.search(occ_re, text)
371
+ occ_mos = occ_mobj[3]
372
+ if occ_mos == "":
373
+ occ_mos = occ_mobj[2]
374
+ self.occ_mos = int(occ_mos)
375
+
376
+ self.log(f"Found {self.occ_mos} occupied MOs.")
377
+ # Number of spherical basis functions. May be different from CAO
378
+ # Determine number of virtual orbitals
379
+ self.virt_mos = nbf - self.occ_mos
380
+ else:
381
+ alpha_re = r"alpha shells\s+(\w)\s*\d+-(\d+)"
382
+ alpha_mos = int(re.search(alpha_re, text)[2])
383
+ self.log(f"Found {alpha_mos} occupied alpha MOs.")
384
+
385
+ beta_re = r"beta shells\s+(\w)\s*\d+-(\d+)"
386
+ beta_mos = int(re.search(beta_re, text)[2])
387
+ self.log(f"Found {beta_mos} occupied beta MOs.")
388
+
389
+ def get_ricc2_root(self, text):
390
+ regex = r"geoopt.+?state=\((.+?)\)"
391
+ mobj = re.search(regex, text)
392
+ if not mobj:
393
+ root = None
394
+ elif mobj[1] == "x":
395
+ root = 0
396
+ else:
397
+ assert mobj[1].startswith("a "), "symmetry isn't supported!"
398
+ root = int(mobj[1][-1])
399
+ return root
400
+
401
+ def prepare_td(self, text):
402
+ self.log("Preparing for excited state (gradient) calculations")
403
+ self.td_vec_fn = None
404
+ self.ci_coeffs = None
405
+ self.mo_inds = None
406
+
407
+ def prepare_point_charges(self, point_charges):
408
+ """$point_charges
409
+ <x> <y> <z> <q>
410
+ """
411
+ lines = [f"{x:.12} {y:.12f} {z:.12f} {q:.12f}" for x, y, z, q in point_charges]
412
+
413
+ return "$point_charges\n\t" + "\n".join(lines)
414
+
415
+ def prepare_input(self, atoms, coords, calc_type, point_charges=None):
416
+ """To rectify this we have to construct the basecmd
417
+ dynamically and construct it ad hoc. We could set a RI flag
418
+ in the beginning and select the correct scf binary here from
419
+ it. Then we select the following binary on demand, e.g. aoforce
420
+ or rdgrad or egrad etc."""
421
+
422
+ valid_calc_types = ("energy", "force", "double_mol", "noparse", "hessian")
423
+ if calc_type not in valid_calc_types:
424
+ raise Exception(
425
+ f"Invalid calc_type '{calc_type}'! Supported "
426
+ f"calc_types are '{valid_calc_types}'."
427
+ )
428
+
429
+ path = self.prepare_path(use_in_run=True)
430
+ if calc_type == "double_mol":
431
+ copy_from = self.double_mol_path
432
+ else:
433
+ copy_from = self.control_path
434
+ # Copy everything from the reference control_dir into this path
435
+ # Use self.control_path for all calculations except the double
436
+ # molecule calculation.
437
+ """Maybe we shouldn't copy everything because it may give convergence
438
+ problems? Right now we use the initial MO guess generated in the
439
+ reference path for all images along the path."""
440
+ for glob in copy_from.glob("./*"):
441
+ shutil.copy(glob, path)
442
+ xyz_str = self.prepare_xyz_string(atoms, coords)
443
+ with open(path / "input.xyz", "w") as handle:
444
+ handle.write(xyz_str)
445
+ # Write coordinates
446
+ coord_str = self.prepare_turbo_coords(atoms, coords)
447
+ coord_fn = path / "coord"
448
+ with open(coord_fn, "w") as handle:
449
+ handle.write(coord_str)
450
+ # Copy MO coefficients from previous cycle with this calculator
451
+ # if present.
452
+ if self.mos:
453
+ shutil.copy(self.mos, path / "mos")
454
+ self.log(f"Using {self.mos} as MO guess.")
455
+ elif self.alpha and self.beta:
456
+ shutil.copy(self.alpha, path / "alpha")
457
+ shutil.copy(self.beta, path / "beta")
458
+ self.log(f"Using {self.alpha} and {self.beta} as MO guesses.")
459
+ if self.td_vec_fn:
460
+ # The suffix contains the true name with a leading
461
+ # dot, that we drop.
462
+ td_vec_fn = self.td_vec_fn.suffix[1:]
463
+ shutil.copy(self.td_vec_fn, path / td_vec_fn)
464
+ self.log(f"Using '{self.td_vec_fn}' as escf guess.")
465
+
466
+ # Set memory
467
+ self.sub_control(r"\$maxcor.+", f"$maxcor {self.mem} MiB per_core")
468
+
469
+ if self.cosmo_kwargs is not None:
470
+ cosmo_data_groups = get_cosmo_data_groups(**self.cosmo_kwargs, atoms=atoms)
471
+ cosmo_rendered = render_data_groups(cosmo_data_groups)
472
+ self.sub_control(r"\$end", cosmo_rendered + "\n$end")
473
+
474
+ root_log_msg = f"with current root information: {self.root}"
475
+ if self.root and self.td:
476
+ repl = f"$exopt {self.root}"
477
+ self.sub_control(r"\$exopt\s*(\d+)", f"$exopt {self.root}", root_log_msg)
478
+ self.log(f"Using '{repl}'")
479
+
480
+ if self.root and self.ricc2:
481
+ repl = f"state=(a {self.root})"
482
+ self.sub_control(
483
+ r"state=\(a\s+(?P<state>\d+)\)", f"state=(a {self.root})", root_log_msg
484
+ )
485
+ self.log(f"Using '{repl}' for geoopt.")
486
+
487
+ if point_charges is not None:
488
+ charge_num = len(point_charges)
489
+ pc_str = self.prepare_point_charges(point_charges)
490
+ self.sub_control(
491
+ r"\$end", pc_str + "\n$end", f"appended {charge_num} point charges"
492
+ )
493
+ # Activate calculation of gradients on point charges
494
+ self.sub_control(r"\$drvopt", "$drvopt\npoint charges\n")
495
+ # Write point charge gradients to file
496
+ self.sub_control(
497
+ r"\$end", "$point_charge_gradients file=pc_gradients\n$end"
498
+ )
499
+
500
+ if calc_type == "hessian":
501
+ self.append_control("$noproj\n$nprhessian file=nprhessian")
502
+
503
+ def sub_control(self, pattern, repl, log_msg="", **kwargs):
504
+ path = self.path_already_prepared
505
+ assert path
506
+ self.log(f"Updating control file in '{path}' {log_msg}")
507
+ control_path = path / "control"
508
+ with open(control_path) as handle:
509
+ text = handle.read()
510
+ text = re.sub(pattern, repl, text, **kwargs)
511
+ with open(control_path, "w") as handle:
512
+ handle.write(text)
513
+
514
+ def append_control(self, to_append, log_msg="", **kwargs):
515
+ self.sub_control(r"\$end", f"{to_append}\n$end", log_msg, **kwargs)
516
+
517
+ def get_pal_env(self):
518
+ env_copy = os.environ.copy()
519
+ env_copy["PARA_ARCH"] = "SMP"
520
+ env_copy["PARNODES"] = str(self.pal)
521
+ env_copy["SMPCPUS"] = str(self.pal)
522
+
523
+ return env_copy
524
+
525
+ def store_and_track(self, results, func, atoms, coords, **prepare_kwargs):
526
+ if self.track:
527
+ prev_run_path = self.last_run_path
528
+ self.store_overlap_data(atoms, coords)
529
+ # Redo the calculation with the updated root
530
+ if self.track_root():
531
+ self.calc_counter += 1
532
+ results = func(atoms, coords, **prepare_kwargs)
533
+ self.last_run_path = prev_run_path
534
+ try:
535
+ shutil.rmtree(self.last_run_path)
536
+ except FileNotFoundError:
537
+ self.log(f"'{self.last_run_path}' has already been deleted!")
538
+ results["all_energies"] = self.parse_all_energies()
539
+ return results
540
+
541
+ def get_energy(self, atoms, coords, **prepare_kwargs):
542
+ self.prepare_input(atoms, coords, "energy", **prepare_kwargs)
543
+ kwargs = {
544
+ "calc": "energy",
545
+ "shell": True,
546
+ "hold": self.track,
547
+ "env": self.get_pal_env(),
548
+ "cmd": self.energy_cmd,
549
+ }
550
+ results = self.run(None, **kwargs)
551
+ results = self.store_and_track(
552
+ results, self.get_energy, atoms, coords, **prepare_kwargs
553
+ )
554
+ return results
555
+
556
+ def get_forces(self, atoms, coords, cmd=None, **prepare_kwargs):
557
+ self.prepare_input(atoms, coords, "force", **prepare_kwargs)
558
+
559
+ if cmd is None:
560
+ cmd = self.forces_cmd
561
+
562
+ kwargs = {
563
+ "calc": "force",
564
+ "shell": True, # To allow chained commands like 'ridft; rdgrad'
565
+ "hold": self.track, # Keep the files for WFOverlap
566
+ "env": self.get_pal_env(),
567
+ "cmd": cmd,
568
+ }
569
+ # Use inp=None because we don't use any dedicated input besides
570
+ # the previously prepared control file and the current coords.
571
+ results = self.run(None, **kwargs)
572
+ results = self.store_and_track(
573
+ results, self.get_forces, atoms, coords, **prepare_kwargs
574
+ )
575
+ return results
576
+
577
+ def get_hessian(self, atoms, coords, **prepare_kwargs):
578
+ if self.td or self.ricc2:
579
+ raise Exception("ricc2 or TD-DFT/TDA hessian not yet supported!")
580
+
581
+ self.prepare_input(atoms, coords, "hessian", **prepare_kwargs)
582
+ kwargs = {
583
+ "calc": "hessian",
584
+ "shell": True, # To allow chained commands like 'ridft; rdgrad'
585
+ "hold": self.track,
586
+ "env": self.get_pal_env(),
587
+ "cmd": self.hessian_cmd,
588
+ }
589
+ results = self.run(None, **kwargs)
590
+ results = self.store_and_track(
591
+ results, self.get_hessian, atoms, coords, **prepare_kwargs
592
+ )
593
+ return results
594
+
595
+ def run_calculation(self, atoms, coords, **prepare_kwargs):
596
+ return self.get_energy(atoms, coords, **prepare_kwargs)
597
+
598
+ def run_double_mol_calculation(self, atoms, coords1, coords2):
599
+ if not self.double_mol_path:
600
+ self.log(
601
+ "Skipping double molecule calculations as double mol "
602
+ "path is not specified.!"
603
+ )
604
+ return None
605
+ self.log("Running double molecule calculation")
606
+ double_atoms = atoms + atoms
607
+ double_coords = np.hstack((coords1, coords2))
608
+ self.prepare_input(double_atoms, double_coords, "double_mol")
609
+ kwargs = {
610
+ "calc": "double_mol",
611
+ "shell": True,
612
+ "keep": False,
613
+ "hold": True,
614
+ "cmd": self.scf_cmd,
615
+ "env": self.get_pal_env(),
616
+ }
617
+ results = self.run(None, **kwargs)
618
+ return results
619
+
620
+ def parse_double_mol(self, path):
621
+ """Parse a double molecule overlap matrix from Turbomole output
622
+ to be used with WFOWrapper."""
623
+ with open(path / self.out_fn) as handle:
624
+ text = handle.read()
625
+ regex = r"OVERLAP\(SAO\)\s+-+([\d\.E\-\s*\+]+)\s+-+"
626
+ ovlp_str = re.search(regex, text)[1]
627
+ ovlp = np.array(ovlp_str.strip().split(), dtype=np.float64)
628
+ mo_num = self.occ_mos + self.virt_mos
629
+ double_mo_num = 2 * mo_num
630
+ full_ovlp = np.zeros((double_mo_num, double_mo_num))
631
+ full_ovlp[np.tril_indices(double_mo_num)] = ovlp
632
+ double_mol_S = full_ovlp[mo_num:, :mo_num]
633
+ return double_mol_S
634
+
635
+ def parse_mos(self):
636
+ pass
637
+
638
+ def parse_energy(self, path):
639
+ with open(path / self.out_fn) as handle:
640
+ text = handle.read()
641
+ en_regex = re.compile(r"Total energy\s*:?\s*=?\s*([\d\-\.]+)", re.IGNORECASE)
642
+ tot_ens = en_regex.findall(text)
643
+
644
+ if self.td:
645
+ # Drop ground state energy that is repeated
646
+ tot_en = tot_ens[1:][self.root]
647
+ elif self.ricc2 and self.ricc2_opt:
648
+ results = parse_turbo_gradient(path)
649
+ tot_en = results["energy"]
650
+ elif self.ricc2 and not self.ricc2_opt:
651
+ raise Exception("Implement me!")
652
+ else:
653
+ tot_en = tot_ens[0]
654
+
655
+ tot_en = float(tot_en)
656
+ return {
657
+ "energy": tot_en,
658
+ }
659
+
660
+ @staticmethod
661
+ @file_or_str(".sing_a", ".ciss_a") # , exact=True)
662
+ def parse_tddft_tden(text):
663
+ eigval_re = re.compile(r"(\d+)\s+eigenvalue\s+=\s+([\d\.\-D\+]+)")
664
+ eigvals = eigval_re.findall(text)
665
+ state_inds, exc_ens = zip(*eigvals)
666
+ exc_ens = [exc_en.replace("D", "E") for exc_en in exc_ens]
667
+ return np.array(exc_ens, dtype=float)
668
+
669
+ def parse_all_energies(self):
670
+ # Parse eigenvectors from escf/egrad calculation
671
+ gs_energy = self.parse_gs_energy()
672
+ if self.second_cmd in ("escf", "egrad"):
673
+ exc_energies = Turbomole.parse_tddft_tden(self.td_vec_fn)
674
+ # Parse eigenvectors from ricc2 calculation
675
+ elif self.second_cmd == "ricc2":
676
+ with open(self.exstates) as handle:
677
+ exstates_text = handle.read()
678
+ exc_energies_by_model = parse_turbo_exstates(exstates_text)
679
+ # Drop CCS and take energies from whatever model was used
680
+ exc_energies = [
681
+ (model, exc_ens)
682
+ for model, exc_ens in exc_energies_by_model.items()
683
+ if model != "CCS"
684
+ ]
685
+ assert len(exc_energies) == 1
686
+ _, exc_energies = exc_energies[0]
687
+
688
+ else:
689
+ exc_energies = np.array(list())
690
+
691
+ if exc_energies.size == 0:
692
+ all_energies = np.array((gs_energy,))
693
+ else:
694
+ all_energies = np.full(len(exc_energies) + 1, gs_energy)
695
+ all_energies[1:] += exc_energies
696
+
697
+ return all_energies
698
+
699
+ def parse_ci_coeffs(self):
700
+ if self.second_cmd in ("escf", "egrad"):
701
+ with open(self.td_vec_fn) as handle:
702
+ text = handle.read()
703
+ ci_coeffs = self.parse_td_vectors(text)
704
+ ci_coeffs = [cc["vector"] for cc in ci_coeffs]
705
+ # Parse eigenvectors from ricc2 calculation
706
+ elif self.second_cmd == "ricc2":
707
+ ci_coeffs = [self.parse_cc2_vectors(ccre) for ccre in self.ccres]
708
+
709
+ ci_coeffs = np.array(ci_coeffs)
710
+ states = ci_coeffs.shape[0]
711
+ tden_size = self.occ_mos * self.virt_mos
712
+ if ci_coeffs.shape[1] == (2 * tden_size):
713
+ self.log("TDDFT calculation with X and Y vectors present. ")
714
+ XpY = ci_coeffs[:, :tden_size]
715
+ XmY = ci_coeffs[:, tden_size:]
716
+ X = (XpY + XmY) / 2.0
717
+ Y = XpY - X
718
+ else:
719
+ X = ci_coeffs
720
+ Y = np.zeros_like(X)
721
+ tden_shape = (states, self.occ_mos, self.virt_mos)
722
+ X = X.reshape(tden_shape)
723
+ Y = Y.reshape(tden_shape)
724
+
725
+ return X, Y
726
+
727
+ def parse_force(self, path):
728
+ results = parse_turbo_gradient(path)
729
+ return results
730
+
731
+ def parse_hessian(self, path, fn=None):
732
+ if fn is None:
733
+ fn = path / "nprhessian"
734
+
735
+ with open(fn) as handle:
736
+ text = handle.read()
737
+
738
+ split = text.strip().split()
739
+ assert split[0] == "$nprhessian"
740
+ assert split[-1] == "$end"
741
+
742
+ def is_float(str_):
743
+ return "." in str_
744
+
745
+ hess_items = [item for item in split if is_float(item)]
746
+ coord_num = int(sqrt(len(hess_items)))
747
+ assert coord_num**2 == len(hess_items)
748
+ hessian = np.array(hess_items, dtype=float).reshape(-1, coord_num)
749
+
750
+ energy = self.parse_energy(path)["energy"]
751
+
752
+ results = {
753
+ "energy": energy,
754
+ "hessian": hessian,
755
+ }
756
+ return results
757
+
758
+ def parse_td_vectors(self, text):
759
+ """For TDA calculations only the X vector is present in the
760
+ ciss_a/etc. file. In TDDFT calculations there are twise as
761
+ much items compared with TDA. The first half corresponds to
762
+ (X+Y) and the second half to (X-Y). X can be calculated as
763
+ X = ((X+Y)+(X-Y))/2. Y is then given as Y = (X+Y)-X. The
764
+ normalization can then by checked as
765
+ np.concatenate((X, Y)).dot(np.concatenate((X, -Y)))
766
+ and should be 1."""
767
+
768
+ def to_float(s, loc, toks):
769
+ match = toks[0].replace("D", "E")
770
+ return float(match)
771
+
772
+ float_ = pp.Word(pp.nums + ".-D+").setParseAction(to_float)
773
+ integer = pp.Word(pp.nums).setParseAction(lambda t: int(t[0]))
774
+ float_chrs = pp.nums + "D.+-"
775
+ float_20 = pp.Word(float_chrs, exact=20).setParseAction(to_float)
776
+
777
+ title = pp.Literal("$title")
778
+ symmetry = pp.Literal("$symmetry") + pp.Word(pp.alphanums).setResultsName(
779
+ "symmetry"
780
+ )
781
+ tensor_dim = pp.Literal("$tensor space dimension") + integer.setResultsName(
782
+ "tensor_dim"
783
+ )
784
+ scfinstab = pp.Literal("$scfinstab") + pp.Word(pp.alphanums).setResultsName(
785
+ "scfinstab"
786
+ )
787
+ subspace_dim = pp.Literal(
788
+ "$current subspace dimension"
789
+ ) + integer.setResultsName("subspace_dim")
790
+ converged = pp.Literal("$current iteration converged")
791
+ eigenpairs = pp.Literal("$eigenpairs")
792
+ eigenpair = pp.Group(
793
+ integer.setResultsName("state")
794
+ + pp.Literal("eigenvalue =")
795
+ + float_.setResultsName("eigenvalue")
796
+ + pp.Group(pp.OneOrMore(float_20)).setResultsName("vector")
797
+ )
798
+ end = pp.Literal("$end")
799
+
800
+ parser = (
801
+ title
802
+ + symmetry
803
+ + tensor_dim
804
+ + scfinstab
805
+ + subspace_dim
806
+ + converged
807
+ + eigenpairs
808
+ + pp.OneOrMore(eigenpair).setResultsName("eigenpairs")
809
+ + end
810
+ )
811
+ result = parser.parseString(text)
812
+ states = result["subspace_dim"]
813
+ eigenpairs = result["eigenpairs"]
814
+ eigenpair_list = [eigenpairs[i].asDict() for i in range(states)]
815
+ return eigenpair_list
816
+
817
+ def parse_cc2_vectors(self, ccre):
818
+ with open(ccre) as handle:
819
+ text = handle.read()
820
+ coeffs = parse_turbo_ccre0_ascii(text)
821
+ coeffs = coeffs.reshape(-1, self.virt_mos)
822
+
823
+ eigenpairs_full = np.zeros((self.occ_mos, self.virt_mos))
824
+ eigenpairs_full[self.frozen_mos :, :] = coeffs
825
+ """
826
+ from_inds, to_inds = np.where(np.abs(eigenpairs_full) > 0.1)
827
+ for i, (from_, to_) in enumerate(zip(from_inds, to_inds)):
828
+ sq = eigenpairs_full[from_, to_]**2
829
+ print(f"{from_+1:02d} -> {to_+self.occ_mos+1:02d}: {sq:.2%}")
830
+ print()
831
+ """
832
+
833
+ return eigenpairs_full
834
+
835
+ def parse_gs_energy(self):
836
+ """Several places are possible:
837
+ $subenergy from control file
838
+ total energy from turbomole.out
839
+ Final MP2 energy from turbomole.out with ADC(2)
840
+ Final CC2 energy from turbomole.out with CC(2)
841
+ """
842
+ float_re = r"([\d\-\.E]+)"
843
+ regexs = [
844
+ # CC2 ground state energy
845
+ ("out", r"Final CC2 energy\s*:\s*" + float_re, 0),
846
+ # ADC(2) ground state energy
847
+ ("out", r"Final MP2 energy\s*:\s*" + float_re, 0),
848
+ ("control", r"\$subenergy.*$\s*" + float_re, re.MULTILINE),
849
+ # DSCF ground state energy
850
+ ("out", r"total energy\s*=\s*" + float_re, 0),
851
+ # From egrad when a rootflip occured. Then only the excited
852
+ # state calculation will be redone and the ground state calculation
853
+ # won't be present in the out-file.
854
+ ("out", r"Ground state\s*?Total energy:\s+" + float_re, re.MULTILINE),
855
+ ]
856
+ for file_attr, regex, flag in regexs:
857
+ regex_ = re.compile(regex, flags=flag)
858
+ with open(getattr(self, file_attr)) as handle:
859
+ text = handle.read()
860
+ mobj = regex_.search(text)
861
+ try:
862
+ gs_energy = float(mobj[1])
863
+ self.log(
864
+ f"Parsed ground state energy from '{file_attr}' using "
865
+ f"regex '{regex[:11]}'."
866
+ )
867
+ return gs_energy
868
+ except TypeError:
869
+ continue
870
+ raise Exception("Couldn't parse ground state energy!")
871
+
872
+ def prepare_overlap_data(self, path):
873
+ all_energies = self.parse_all_energies()
874
+ X, Y = self.parse_ci_coeffs()
875
+ self.log(f"Reading MO coefficients from '{self.mos}'.")
876
+ with open(self.mos) as handle:
877
+ text = handle.read()
878
+ C = parse_turbo_mos(text)
879
+ self.log(f"Reading electronic energies from '{self.out}'.")
880
+ return C, X, Y, all_energies
881
+
882
+ def run_after(self, path):
883
+ # Convert binary CCRE0 files to ASCII for easier parsing
884
+ for ccre in path.glob("CCRE0-*"):
885
+ cmd = f"ricctools -dump {ccre.name}".split()
886
+ result = subprocess.Popen(
887
+ cmd, cwd=path, stdout=subprocess.PIPE, stderr=subprocess.PIPE
888
+ )
889
+ result.wait()
890
+ # ricctools seem to crash sometimes, even though the respective ASCII
891
+ # ccre-files are generated.
892
+ subprocess.run(
893
+ "actual -r".split(),
894
+ cwd=path,
895
+ stdout=subprocess.PIPE,
896
+ stderr=subprocess.PIPE,
897
+ )
898
+
899
+ if self.td:
900
+ self.make_molden(path)
901
+ # With ricc2 we probably have a frozen core that we have to disable
902
+ # temporarily before creating the molden file. Afterwards we restore
903
+ # the original control file with the frozen core.
904
+ elif self.ricc2:
905
+ # Backup original control file
906
+ ctrl_backup = path / "control.backup"
907
+ shutil.copy(path / "control", ctrl_backup)
908
+ # We have to remove line with implicit core in the control file
909
+ with open(path / "control") as handle:
910
+ text = handle.read()
911
+ lines = text.split("\n")
912
+ lines = [l for l in lines if "implicit core" not in l]
913
+ with open(path / "control", "w") as handle:
914
+ handle.write("\n".join(lines))
915
+ self.make_molden(path)
916
+ # Restore control backup
917
+ shutil.copy(ctrl_backup, path / "control")
918
+
919
+ @staticmethod
920
+ def make_molden(path):
921
+ cmd = "tm2molden norm".split()
922
+ fn = "wavefunction.molden"
923
+ stdin = f"""{fn}
924
+
925
+ """
926
+ res = subprocess.Popen(
927
+ cmd,
928
+ cwd=path,
929
+ universal_newlines=True,
930
+ stdin=subprocess.PIPE,
931
+ stdout=subprocess.PIPE,
932
+ stderr=subprocess.PIPE,
933
+ )
934
+ stdout, stderr = res.communicate(stdin)
935
+ res.terminate()
936
+
937
+ def get_chkfiles(self):
938
+ if self.uhf:
939
+ chkfiles = {
940
+ "alpha": self.alpha,
941
+ "beta": self.beta,
942
+ }
943
+ else:
944
+ chkfiles = {
945
+ "mos": self.mos,
946
+ }
947
+ return chkfiles
948
+
949
+ def set_chkfiles(self, chkfiles):
950
+ try:
951
+ if self.uhf:
952
+ alpha = chkfiles["alpha"]
953
+ beta = chkfiles["beta"]
954
+ self.alpha = alpha
955
+ self.beta = beta
956
+ self.log(f"Set chkfile '{alpha}' and '{beta}' as alpha and beta.")
957
+ else:
958
+ mos = chkfiles["mos"]
959
+ self.mos = mos
960
+ self.log(f"Set chkfile '{mos}' as mos.")
961
+ except KeyError:
962
+ self.log("Found no chkfile information in chkfiles!")
963
+
964
+ def __str__(self):
965
+ return "Turbomole calculator"