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,475 @@
1
+ """xTB point-charge embedding correction for ONIOM ML/MM.
2
+
3
+ The correction term accounts for the electrostatic interaction between
4
+ the ML (QM) region and the MM point charges:
5
+
6
+ dE = E_xTB(QM + MM_charges) - E_xTB(QM_only)
7
+ dF_Q = F_Q(embed) - F_Q(no-embed)
8
+ dF_M = F_M(embed) - 0
9
+
10
+ where Q are QM/ML atoms and M are external MM point charges.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import concurrent.futures
16
+ import logging
17
+ import os
18
+ import re
19
+ import shlex
20
+ import shutil
21
+ import subprocess
22
+ import tempfile
23
+
24
+ import numpy as np
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ # ---- Constants ----
30
+ BOHR_PER_ANG = 1.8897261254578281
31
+ EV_PER_HARTREE = 27.211386245988
32
+ ANG_PER_BOHR = 0.529177210903
33
+ FORCE_EV_ANG_PER_HA_BOHR = EV_PER_HARTREE / ANG_PER_BOHR
34
+
35
+ _TOTAL_ENERGY_RE = re.compile(
36
+ r"TOTAL ENERGY\s+([-+]?(?:\d+\.\d*|\d*\.\d+|\d+)(?:[DdEe][-+]?\d+)?)"
37
+ )
38
+
39
+
40
+ class XTBEmbedError(RuntimeError):
41
+ """Raised for xTB embedding correction failures."""
42
+
43
+
44
+ def _parse_float_token(text):
45
+ return float(str(text).replace("D", "E").replace("d", "e"))
46
+
47
+
48
+ def _resolve_ncores(explicit_ncores=None, fallback=4):
49
+ candidates = []
50
+ if explicit_ncores is not None:
51
+ candidates.append(explicit_ncores)
52
+ candidates.extend([
53
+ os.getenv("OMP_NUM_THREADS"),
54
+ os.getenv("SLURM_CPUS_PER_TASK"),
55
+ ])
56
+ for item in candidates:
57
+ try:
58
+ val = int(item)
59
+ if val > 0:
60
+ return val
61
+ except Exception:
62
+ continue
63
+ return max(1, int(fallback))
64
+
65
+
66
+ def _make_run_dir(xtb_workdir):
67
+ base = None
68
+ mode = str(xtb_workdir or "tmp").strip()
69
+ if mode and mode.lower() != "tmp":
70
+ base = os.path.abspath(mode)
71
+ os.makedirs(base, exist_ok=True)
72
+ return tempfile.mkdtemp(prefix="xtb-embed-", dir=base)
73
+
74
+
75
+ def _cleanup_run_dir(path, keep_files):
76
+ if keep_files:
77
+ return
78
+ try:
79
+ shutil.rmtree(path)
80
+ except Exception:
81
+ logger.debug("Failed to clean up xTB run directory %s", path, exc_info=True)
82
+
83
+
84
+ def _write_xyz(path, symbols, coords_ang):
85
+ coords = np.asarray(coords_ang, dtype=np.float64).reshape(-1, 3)
86
+ if len(symbols) != coords.shape[0]:
87
+ raise XTBEmbedError(
88
+ f"XYZ write failed: symbol count {len(symbols)} != coord count {coords.shape[0]}"
89
+ )
90
+ with open(path, "w") as fh:
91
+ fh.write(f"{coords.shape[0]}\n")
92
+ fh.write("generated by mlmm embedcharge correction\n")
93
+ for sym, xyz in zip(symbols, coords):
94
+ fh.write(f"{sym:<2s} {xyz[0]: .12f} {xyz[1]: .12f} {xyz[2]: .12f}\n")
95
+
96
+
97
+ def _write_pcharge(path, mm_coords_ang, mm_charges):
98
+ coords = np.asarray(mm_coords_ang, dtype=np.float64).reshape(-1, 3)
99
+ charges = np.asarray(mm_charges, dtype=np.float64).reshape(-1)
100
+ if coords.shape[0] != charges.shape[0]:
101
+ raise XTBEmbedError(
102
+ f"Point-charge write failed: coord count {coords.shape[0]} != charge count {charges.shape[0]}"
103
+ )
104
+ coords_bohr = coords * BOHR_PER_ANG
105
+ with open(path, "w") as fh:
106
+ fh.write(f"{coords_bohr.shape[0]}\n")
107
+ for q, xyz in zip(charges, coords_bohr):
108
+ fh.write(f"{q: .12f} {xyz[0]: .12f} {xyz[1]: .12f} {xyz[2]: .12f}\n")
109
+
110
+
111
+ def _build_xtb_cmd(xtb_cmd, xyz_filename, charge, multiplicity, xtb_acc, mode):
112
+ tokens = shlex.split(str(xtb_cmd or "xtb"))
113
+ if not tokens:
114
+ raise XTBEmbedError("Empty xtb_cmd value.")
115
+ tokens.extend([str(xyz_filename), "--acc", str(float(xtb_acc)), "--norestart"])
116
+ m = str(mode).strip().lower()
117
+ if m == "grad":
118
+ tokens.append("--grad")
119
+ elif m == "sp":
120
+ pass
121
+ else:
122
+ raise XTBEmbedError(f"Unknown xTB mode '{mode}'")
123
+ if charge is not None:
124
+ tokens.extend(["--chrg", str(int(charge))])
125
+ if multiplicity is not None:
126
+ tokens.extend(["--uhf", str(max(0, int(multiplicity) - 1))])
127
+ return tokens
128
+
129
+
130
+ def _run_xtb(run_dir, xyz_filename, charge, multiplicity, xtb_cmd, xtb_acc, mode, ncores):
131
+ cmd = _build_xtb_cmd(xtb_cmd, xyz_filename, charge, multiplicity, xtb_acc, mode)
132
+ env = os.environ.copy()
133
+ env["OMP_NUM_THREADS"] = str(_resolve_ncores(ncores))
134
+
135
+ try:
136
+ proc = subprocess.run(
137
+ cmd, cwd=run_dir, env=env,
138
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
139
+ text=True, check=False,
140
+ )
141
+ except FileNotFoundError as exc:
142
+ raise XTBEmbedError(f"xTB command not found: '{xtb_cmd}'.") from exc
143
+ except Exception as exc:
144
+ raise XTBEmbedError(f"Failed to run xTB: {exc}")
145
+
146
+ if proc.returncode != 0:
147
+ out_tail = "\n".join((proc.stdout or "").splitlines()[-20:])
148
+ err_tail = "\n".join((proc.stderr or "").splitlines()[-20:])
149
+ raise XTBEmbedError(
150
+ f"xTB failed (embedcharge mode={mode}, rc={proc.returncode}).\n"
151
+ f"CMD: {' '.join(cmd)}\nSTDOUT:\n{out_tail}\nSTDERR:\n{err_tail}"
152
+ )
153
+ return proc.stdout or ""
154
+
155
+
156
+ def _parse_energy_from_stdout(stdout_text):
157
+ matches = _TOTAL_ENERGY_RE.findall(str(stdout_text or ""))
158
+ if not matches:
159
+ return None
160
+ return _parse_float_token(matches[-1])
161
+
162
+
163
+ def _parse_engrad(path, natoms):
164
+ with open(path) as fh:
165
+ lines = fh.readlines()
166
+
167
+ n = len(lines)
168
+ energy_ha = None
169
+ grad_vals = []
170
+
171
+ i = 0
172
+ while i < n:
173
+ if "the current total energy in eh" in lines[i].strip().lower():
174
+ i += 1
175
+ while i < n:
176
+ text = lines[i].strip()
177
+ if text and not text.startswith("#"):
178
+ energy_ha = _parse_float_token(text.split()[0])
179
+ break
180
+ i += 1
181
+ break
182
+ i += 1
183
+
184
+ grad_start = None
185
+ for idx, line in enumerate(lines):
186
+ if "the current gradient in eh/bohr" in line.strip().lower():
187
+ grad_start = idx + 1
188
+ break
189
+
190
+ if grad_start is not None:
191
+ j = grad_start
192
+ while j < n and len(grad_vals) < 3 * int(natoms):
193
+ text = lines[j].strip()
194
+ if text and not text.startswith("#"):
195
+ grad_vals.append(_parse_float_token(text.split()[0]))
196
+ j += 1
197
+
198
+ if energy_ha is None:
199
+ raise XTBEmbedError(f"Could not parse energy from engrad file: {path}")
200
+ if len(grad_vals) != 3 * int(natoms):
201
+ raise XTBEmbedError(
202
+ f"Gradient parse failed in {path}: expected {3 * int(natoms)}, got {len(grad_vals)}"
203
+ )
204
+
205
+ grad = np.asarray(grad_vals, dtype=np.float64).reshape(int(natoms), 3)
206
+ return float(energy_ha), grad
207
+
208
+
209
+ def _parse_pcgrad(path, ncharges):
210
+ if int(ncharges) <= 0:
211
+ return np.zeros((0, 3), dtype=np.float64)
212
+ if not os.path.isfile(path):
213
+ raise XTBEmbedError(f"xTB point-charge gradient file not found: {path}")
214
+
215
+ rows = []
216
+ with open(path) as fh:
217
+ for line in fh:
218
+ text = line.strip()
219
+ if not text:
220
+ continue
221
+ parts = text.split()
222
+ if len(parts) < 3:
223
+ raise XTBEmbedError(f"Malformed pcgrad row in {path}: '{text}'")
224
+ rows.append([_parse_float_token(p) for p in parts[:3]])
225
+
226
+ if len(rows) != int(ncharges):
227
+ raise XTBEmbedError(
228
+ f"pcgrad row count mismatch: expected {int(ncharges)}, got {len(rows)} in {path}"
229
+ )
230
+ return np.asarray(rows, dtype=np.float64).reshape(int(ncharges), 3)
231
+
232
+
233
+ def _convert_units(energy_ha=None, gradient_ha_bohr=None):
234
+ """Convert xTB atomic units to eV / eV/Å."""
235
+ energy_ev = None
236
+ forces_ev_ang = None
237
+ if energy_ha is not None:
238
+ energy_ev = float(energy_ha) * EV_PER_HARTREE
239
+ if gradient_ha_bohr is not None:
240
+ grad = np.asarray(gradient_ha_bohr, dtype=np.float64).reshape(-1, 3)
241
+ forces_ev_ang = -grad * FORCE_EV_ANG_PER_HA_BOHR
242
+ return energy_ev, forces_ev_ang
243
+
244
+
245
+ def _run_xtb_state(
246
+ symbols, coords_q_ang, charge, multiplicity, mode,
247
+ with_embedding, mm_coords_ang, mm_charges,
248
+ xtb_cmd, xtb_acc, xtb_workdir, xtb_keep_files, ncores,
249
+ ):
250
+ """Run xTB with or without point-charge embedding."""
251
+ run_dir = _make_run_dir(xtb_workdir)
252
+ xyz_name = "xtb_input.xyz"
253
+ nm = int(np.asarray(mm_charges, dtype=np.float64).reshape(-1).size)
254
+ try:
255
+ _write_xyz(os.path.join(run_dir, xyz_name), symbols, coords_q_ang)
256
+ if with_embedding and nm > 0:
257
+ _write_pcharge(
258
+ os.path.join(run_dir, "pcharge"),
259
+ mm_coords_ang=mm_coords_ang,
260
+ mm_charges=mm_charges,
261
+ )
262
+
263
+ stdout = _run_xtb(
264
+ run_dir=run_dir, xyz_filename=xyz_name,
265
+ charge=charge, multiplicity=multiplicity,
266
+ xtb_cmd=xtb_cmd, xtb_acc=xtb_acc,
267
+ mode=mode, ncores=ncores,
268
+ )
269
+
270
+ if str(mode).strip().lower() == "sp":
271
+ energy_ha = _parse_energy_from_stdout(stdout)
272
+ if energy_ha is None:
273
+ raise XTBEmbedError("Could not parse xTB energy from stdout.")
274
+ energy_ev, _ = _convert_units(energy_ha=energy_ha)
275
+ return float(energy_ev), None, None
276
+
277
+ engrad_path = os.path.join(
278
+ run_dir, f"{os.path.splitext(xyz_name)[0]}.engrad"
279
+ )
280
+ if not os.path.isfile(engrad_path):
281
+ raise XTBEmbedError(f"xTB engrad file not found: {engrad_path}")
282
+ energy_ha, grad_q_ha_bohr = _parse_engrad(engrad_path, len(symbols))
283
+ energy_ev, forces_q_ev_ang = _convert_units(
284
+ energy_ha=energy_ha, gradient_ha_bohr=grad_q_ha_bohr,
285
+ )
286
+
287
+ forces_m_ev_ang = np.zeros((nm, 3), dtype=np.float64)
288
+ if with_embedding and nm > 0:
289
+ pcgrad_ha_bohr = _parse_pcgrad(os.path.join(run_dir, "pcgrad"), nm)
290
+ _, forces_m_ev_ang = _convert_units(gradient_ha_bohr=pcgrad_ha_bohr)
291
+
292
+ return (
293
+ float(energy_ev),
294
+ np.asarray(forces_q_ev_ang, dtype=np.float64).reshape(len(symbols), 3),
295
+ np.asarray(forces_m_ev_ang, dtype=np.float64).reshape(nm, 3),
296
+ )
297
+ finally:
298
+ _cleanup_run_dir(run_dir, xtb_keep_files)
299
+
300
+
301
+ def _delta_embed_minus_vac(
302
+ symbols, coords_q_ang, mm_coords_ang, mm_charges,
303
+ charge, multiplicity, need_forces,
304
+ xtb_cmd, xtb_acc, xtb_workdir, xtb_keep_files, ncores,
305
+ ):
306
+ """Compute dE = E(embed) - E(vac), dF = F(embed) - F(vac)."""
307
+ nm = int(np.asarray(mm_charges, dtype=np.float64).reshape(-1).size)
308
+ if nm <= 0:
309
+ zq = np.zeros((len(symbols), 3), dtype=np.float64) if need_forces else None
310
+ zm = np.zeros((0, 3), dtype=np.float64) if need_forces else None
311
+ return 0.0, zq, zm
312
+
313
+ mode = "grad" if need_forces else "sp"
314
+ common_kw = dict(
315
+ symbols=symbols, coords_q_ang=coords_q_ang,
316
+ charge=charge, multiplicity=multiplicity, mode=mode,
317
+ xtb_cmd=xtb_cmd, xtb_acc=xtb_acc,
318
+ xtb_workdir=xtb_workdir, xtb_keep_files=xtb_keep_files, ncores=ncores,
319
+ )
320
+
321
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:
322
+ fut_embed = pool.submit(
323
+ _run_xtb_state, with_embedding=True,
324
+ mm_coords_ang=mm_coords_ang, mm_charges=mm_charges, **common_kw,
325
+ )
326
+ fut_vac = pool.submit(
327
+ _run_xtb_state, with_embedding=False,
328
+ mm_coords_ang=np.zeros((0, 3), dtype=np.float64),
329
+ mm_charges=np.zeros((0,), dtype=np.float64), **common_kw,
330
+ )
331
+ embed_e, embed_fq, embed_fm = fut_embed.result()
332
+ vac_e, vac_fq, _ = fut_vac.result()
333
+
334
+ de_ev = float(embed_e - vac_e)
335
+ if not need_forces:
336
+ return de_ev, None, None
337
+
338
+ df_q = (
339
+ np.asarray(embed_fq, dtype=np.float64).reshape(len(symbols), 3)
340
+ - np.asarray(vac_fq, dtype=np.float64).reshape(len(symbols), 3)
341
+ )
342
+ df_m = np.asarray(embed_fm, dtype=np.float64).reshape(nm, 3)
343
+ return de_ev, df_q, df_m
344
+
345
+
346
+ def _assemble_full_force(df_q, df_m):
347
+ fq = np.asarray(df_q, dtype=np.float64).reshape(-1, 3)
348
+ fm = np.asarray(df_m, dtype=np.float64).reshape(-1, 3)
349
+ if fm.shape[0] == 0:
350
+ return fq
351
+ return np.vstack([fq, fm])
352
+
353
+
354
+ def _numerical_hessian_from_forces(
355
+ eval_full_forces, coords_q_ang, mm_coords_ang, step_ang, force0_ev_ang=None,
356
+ ):
357
+ """Central-difference numerical Hessian from embedding force deltas."""
358
+ q0 = np.asarray(coords_q_ang, dtype=np.float64).reshape(-1, 3)
359
+ m0 = np.asarray(mm_coords_ang, dtype=np.float64).reshape(-1, 3)
360
+ nq = q0.shape[0]
361
+ nm = m0.shape[0]
362
+ nall = nq + nm
363
+ dof = nall * 3
364
+ step = float(step_ang)
365
+
366
+ if force0_ev_ang is None:
367
+ f0 = np.asarray(eval_full_forces(q0, m0), dtype=np.float64).reshape(nall, 3)
368
+ else:
369
+ f0 = np.asarray(force0_ev_ang, dtype=np.float64).reshape(nall, 3)
370
+
371
+ hess = np.zeros((dof, dof), dtype=np.float64)
372
+ nqdof = 3 * nq
373
+ for k in range(dof):
374
+ qp = q0.copy()
375
+ qm = q0.copy()
376
+ mp = m0.copy()
377
+ mm_m = m0.copy()
378
+
379
+ if k < nqdof:
380
+ a, c = k // 3, k % 3
381
+ qp[a, c] += step
382
+ qm[a, c] -= step
383
+ else:
384
+ kk = k - nqdof
385
+ a, c = kk // 3, kk % 3
386
+ mp[a, c] += step
387
+ mm_m[a, c] -= step
388
+
389
+ f_plus = np.asarray(eval_full_forces(qp, mp), dtype=np.float64).reshape(-1)
390
+ f_minus = np.asarray(eval_full_forces(qm, mm_m), dtype=np.float64).reshape(-1)
391
+ hess[:, k] = (-f_plus + f_minus) / (2.0 * step)
392
+
393
+ hess = 0.5 * (hess + hess.T)
394
+ return f0, hess
395
+
396
+
397
+ def delta_embedcharge_minus_noembed(
398
+ symbols,
399
+ coords_q_ang,
400
+ mm_coords_ang,
401
+ mm_charges,
402
+ charge,
403
+ multiplicity,
404
+ need_forces,
405
+ need_hessian,
406
+ xtb_cmd="xtb",
407
+ xtb_acc=0.2,
408
+ xtb_workdir="tmp",
409
+ xtb_keep_files=False,
410
+ ncores=4,
411
+ hessian_step=1.0e-3,
412
+ ):
413
+ """Return xTB point-charge embedding correction in eV / eV/Š/ eV/Ų units.
414
+
415
+ Returns
416
+ -------
417
+ tuple
418
+ ``(dE_ev, dF_full_ev_ang, dH_full_ev_ang2)``, where full index order is
419
+ concatenated as ``[QM atoms, MM point charges]``.
420
+ """
421
+ q_symbols = list(symbols or [])
422
+ q_coords = np.asarray(coords_q_ang, dtype=np.float64).reshape(-1, 3)
423
+ m_coords = np.asarray(mm_coords_ang, dtype=np.float64).reshape(-1, 3)
424
+ m_charges = np.asarray(mm_charges, dtype=np.float64).reshape(-1)
425
+
426
+ nq = q_coords.shape[0]
427
+ nm = m_coords.shape[0]
428
+ nall = nq + nm
429
+ if nm <= 0:
430
+ zero_force = np.zeros((nall, 3), dtype=np.float64) if (need_forces or need_hessian) else None
431
+ zero_hess = np.zeros((3 * nall, 3 * nall), dtype=np.float64) if need_hessian else None
432
+ return 0.0, zero_force, zero_hess
433
+
434
+ run_ncores = _resolve_ncores(ncores)
435
+ if need_hessian:
436
+ run_ncores = 1
437
+
438
+ req_force = bool(need_forces) or bool(need_hessian)
439
+ de_ev, df_q, df_m = _delta_embed_minus_vac(
440
+ symbols=q_symbols, coords_q_ang=q_coords,
441
+ mm_coords_ang=m_coords, mm_charges=m_charges,
442
+ charge=charge, multiplicity=multiplicity,
443
+ need_forces=req_force,
444
+ xtb_cmd=xtb_cmd, xtb_acc=xtb_acc,
445
+ xtb_workdir=xtb_workdir, xtb_keep_files=xtb_keep_files,
446
+ ncores=run_ncores,
447
+ )
448
+
449
+ df_full = None
450
+ if req_force:
451
+ df_full = _assemble_full_force(df_q, df_m)
452
+
453
+ dh_full = None
454
+ if need_hessian:
455
+ step = float(hessian_step)
456
+
457
+ def _eval_full_forces(cur_q, cur_m):
458
+ _de, _fq, _fm = _delta_embed_minus_vac(
459
+ symbols=q_symbols, coords_q_ang=cur_q,
460
+ mm_coords_ang=cur_m, mm_charges=m_charges,
461
+ charge=charge, multiplicity=multiplicity,
462
+ need_forces=True,
463
+ xtb_cmd=xtb_cmd, xtb_acc=xtb_acc,
464
+ xtb_workdir=xtb_workdir, xtb_keep_files=xtb_keep_files,
465
+ ncores=run_ncores,
466
+ )
467
+ return _assemble_full_force(_fq, _fm)
468
+
469
+ _f0, dh_full = _numerical_hessian_from_forces(
470
+ eval_full_forces=_eval_full_forces,
471
+ coords_q_ang=q_coords, mm_coords_ang=m_coords,
472
+ step_ang=step, force0_ev_ang=df_full,
473
+ )
474
+
475
+ return float(de_ev), df_full, dh_full