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,1811 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # [1] https://doi.org/10.1063/1.450106
4
+ # Efficient recursive computation of molecular integrals over Cartesian
5
+ # Gaussian functions
6
+ # Obara, Saika, 1986
7
+ # [2] https://doi.org/10.1002/9781119019572
8
+ # Molecular Electronic-Structure Theory
9
+ # Helgaker, Jørgensen, Olsen
10
+ # [3] https://doi.org/10.1021/acs.jctc.7b00788
11
+ # LIBRETA: Computerized Optimization and Code Synthesis for
12
+ # Electron Repulsion Integral Evaluation
13
+ # Jun Zhang
14
+ # [4] https://doi.org/10.1039/B413539C
15
+ # Efficient evaluation of three-center two-electron integrals over Gaussian functions
16
+ # Ahlrichs, 2004
17
+ # [5] EVALUATING MANY-ELECTRON MOLECULAR INTEGRALS FOR QUANTUM CHEMISTRY
18
+ # James Christopher Womack, PhD Thesis
19
+ # [6] https://doi.org/10.1063/1.4983393
20
+ # Efficient evaluation of three-center Coulomb integrals
21
+ # Samu, Kállay, 2017
22
+ # [7] https://arxiv.org/pdf/2210.03192.pdf
23
+ # Memory-Efficient Recursive Evaluation of 3-Center Gaussian Integrals
24
+ # Asadchev, Valeev, 2022
25
+
26
+
27
+ import argparse
28
+ from datetime import datetime
29
+ import functools
30
+ import itertools as it
31
+ import os
32
+ from pathlib import Path
33
+ import random
34
+ import string
35
+ import sys
36
+ import textwrap
37
+ import time
38
+
39
+ from jinja2 import Template
40
+ import numpy as np
41
+ from sympy import (
42
+ Array,
43
+ cse,
44
+ exp,
45
+ Expr,
46
+ flatten,
47
+ Function,
48
+ IndexedBase,
49
+ Matrix,
50
+ permutedims,
51
+ pi,
52
+ sqrt,
53
+ Symbol,
54
+ symbols,
55
+ tensorcontraction as tc,
56
+ tensorproduct as tp,
57
+ )
58
+
59
+
60
+ from sympy.codegen.ast import Assignment
61
+ from sympy.printing.numpy import NumPyPrinter
62
+ from sympy.printing.c import C99CodePrinter
63
+
64
+ # from pysisyphus.wavefunction.cart2sph import cart2sph_coeffs
65
+
66
+ try:
67
+ from pysisyphus.config import L_MAX, L_AUX_MAX
68
+ except ModuleNotFoundError:
69
+ L_MAX = 4
70
+ L_AUX_MAX = 5
71
+
72
+
73
+ L_MAP = {
74
+ 0: "s",
75
+ 1: "p",
76
+ 2: "d",
77
+ 3: "f",
78
+ 4: "g",
79
+ 5: "h",
80
+ }
81
+
82
+ KEYS = (
83
+ "cgto",
84
+ "ovlp",
85
+ "dpm",
86
+ "dqpm",
87
+ "qpm",
88
+ "kin",
89
+ "coul",
90
+ "3c2e",
91
+ "3c2e_sph",
92
+ )
93
+ ONE_THRESH = 1e-14
94
+
95
+
96
+ def make_py_func(repls, reduced, args=None, name=None, doc_str="", return_array=True):
97
+ if args is None:
98
+ args = list()
99
+ # Generate random name, if no name was supplied
100
+ if name is None:
101
+ name = "func_" + "".join(
102
+ [random.choice(string.ascii_letters) for i in range(8)]
103
+ )
104
+
105
+ # This allows using the 'boys' function without producing an error
106
+ print_settings = {
107
+ "allow_unknown_functions": True,
108
+ }
109
+ print_func = NumPyPrinter(print_settings).doprint
110
+ assignments = [Assignment(lhs, rhs) for lhs, rhs in repls]
111
+ py_lines = [print_func(as_) for as_ in assignments]
112
+ return_val = print_func(reduced)
113
+ try:
114
+ n_return_vals = len(reduced)
115
+ except TypeError:
116
+ n_return_vals = 1
117
+
118
+ tpl = Template(
119
+ """
120
+ def {{ name }}({{ args }}):
121
+ {% if doc_str %}
122
+ \"\"\"{{ doc_str }}\"\"\"
123
+ {% endif %}
124
+
125
+ {% for line in py_lines %}
126
+ {{ line }}
127
+ {% endfor %}
128
+
129
+ {% if return_array %}
130
+ # {{ n_return_vals }} item(s)
131
+ return numpy.array({{ return_val }})
132
+ {% else %}
133
+ return {{ return_val }}
134
+ {% endif %}
135
+ """,
136
+ trim_blocks=True,
137
+ lstrip_blocks=True,
138
+ )
139
+
140
+ rendered = textwrap.dedent(
141
+ tpl.render(
142
+ name=name,
143
+ args=args,
144
+ py_lines=py_lines,
145
+ return_val=return_val,
146
+ n_return_vals=n_return_vals,
147
+ return_array=return_array,
148
+ doc_str=doc_str,
149
+ )
150
+ ).strip()
151
+ return rendered
152
+
153
+
154
+ def make_c_func(repls, reduced, args=None, name=None, doc_str=""):
155
+ if args is None:
156
+ args = list()
157
+ # Generate random name, if no name was supplied
158
+ if name is None:
159
+ name = "func_" + "".join(
160
+ [random.choice(string.ascii_letters) for i in range(8)]
161
+ )
162
+ arg_strs = list()
163
+ for arg in args:
164
+ if arg.islower():
165
+ arg_str = f"double {arg}"
166
+ elif arg.isupper():
167
+ arg_str = f"double {arg}[3]"
168
+ else:
169
+ raise Exception
170
+ arg_strs.append(arg_str)
171
+ args_str = ", ".join(arg_strs)
172
+
173
+ # This allows using the 'boys' function without producing an error
174
+ print_settings = {
175
+ "allow_unknown_functions": True,
176
+ # Without disabling contract some expressions will raise ValueError.
177
+ "contract": False,
178
+ }
179
+ print_func = C99CodePrinter(print_settings).doprint
180
+ assignments = [Assignment(lhs, rhs) for lhs, rhs in repls]
181
+ repl_lines = [print_func(as_) for as_ in assignments]
182
+ res_lines = [print_func(red) for red in reduced]
183
+ res_len = len(reduced)
184
+
185
+ signature = f"double * {name}({args_str})"
186
+
187
+ tpl = Template(
188
+ """
189
+ {{ signature }} {
190
+ {% if doc_str %}
191
+ /* {{ doc_str }} */
192
+ {% endif %}
193
+
194
+ static double {{ res_name }}[{{ res_len}}];
195
+
196
+ {% for line in repl_lines %}
197
+ const double {{ line }}
198
+ {% endfor %}
199
+
200
+ {% for rhs in res_lines %}
201
+ {{ res_name }}[{{ loop.index0}}] = {{ rhs }};
202
+ {% endfor %}
203
+
204
+ return {{ res_name }};
205
+ }
206
+ """,
207
+ trim_blocks=True,
208
+ lstrip_blocks=True,
209
+ )
210
+
211
+ rendered = textwrap.dedent(
212
+ tpl.render(
213
+ signature=signature,
214
+ res_name="results",
215
+ res_len=res_len,
216
+ repl_lines=repl_lines,
217
+ res_lines=res_lines, # c_lines=c_lines,
218
+ reduced=reduced,
219
+ doc_str=doc_str,
220
+ args_str=args_str,
221
+ )
222
+ ).strip()
223
+ return rendered, signature
224
+
225
+
226
+ def canonical_order(L):
227
+ inds = list()
228
+ for i in range(L + 1):
229
+ l = L - i
230
+ for n in range(i + 1):
231
+ m = i - n
232
+ inds.append((l, m, n))
233
+ return inds
234
+
235
+
236
+ def shell_iter(Ls):
237
+ """Iterator over cartesian product of L values in Ls."""
238
+ return it.product(*[canonical_order(L) for L in Ls])
239
+
240
+
241
+ class CartGTO3d(Function):
242
+ """3D Cartesian Gaussian function; not normalized."""
243
+
244
+ @classmethod
245
+ @functools.cache
246
+ def eval(cls, i, j, k, a, Xa, Ya, Za):
247
+ Xa2 = Xa**2
248
+ Ya2 = Ya**2
249
+ Za2 = Za**2
250
+ return (Xa**i) * (Ya**j) * (Za**k) * exp(-a * (Xa2 + Ya2 + Za2))
251
+
252
+
253
+ class CartGTOShell(Function):
254
+ @classmethod
255
+ def eval(cls, La_tot, a, Xa, Ya, Za):
256
+ exprs = [CartGTO3d(*La, a, Xa, Ya, Za) for La, in shell_iter((La_tot,))]
257
+ # print(CartGTO3d.eval.cache_info())
258
+ return exprs
259
+
260
+
261
+ class TwoCenter1d(Expr):
262
+ def __new__(self, a, A, b, B, C=None):
263
+ self.a = a
264
+ self.A = A
265
+ self.b = b
266
+ self.B = B
267
+ self.C = C
268
+ return super().__new__(self, a, A, b, B, C)
269
+
270
+ @property
271
+ def p(self):
272
+ """Total exponent p."""
273
+ return self.a + self.b
274
+
275
+ @property
276
+ def mu(self):
277
+ """Reduced exponent mu."""
278
+ return self.a * self.b / self.p
279
+
280
+ @property
281
+ def AB(self):
282
+ """Relative coordinate/Gaussian separation X_AB."""
283
+ return self.A - self.B
284
+
285
+ @property
286
+ def P(self):
287
+ """Center-of-charge coordinate."""
288
+ return (self.a * self.A + self.b * self.B) / self.p
289
+
290
+ @property
291
+ def PA(self):
292
+ """Relative coordinate/Gaussian separation X_PA."""
293
+ return self.P - self.A
294
+
295
+ @property
296
+ def PB(self):
297
+ """Relative coordinate/Gaussian separation X_PB."""
298
+ return self.P - self.B
299
+
300
+ @property
301
+ def PC(self):
302
+ """Relative coordinate/Gaussian separation X_PC."""
303
+ return self.P - self.C
304
+
305
+ @property
306
+ def K(self):
307
+ return exp(-self.mu * self.AB**2)
308
+
309
+
310
+ class Multipole1d(TwoCenter1d):
311
+ """1d multipole-moment integral of order 'e', between primitive 1d Gaussians
312
+ Ga = G_i(a, r, A) and Gb = G_j(b, r, B) with Cartesian quantum number i and j,
313
+ exponents a and b, centered at A (B). The origin of the multipole expansion is
314
+ at C.
315
+ """
316
+
317
+ @functools.cache
318
+ def eval(self, i, j, e):
319
+ ang_moms = (i, j, e)
320
+ if any([_ < 0 for _ in ang_moms]):
321
+ return 0
322
+
323
+ recur = self.eval
324
+
325
+ def vrr(i, j, e, X):
326
+ return X * recur(i, j, e) + 1 / (2 * self.p) * (
327
+ i * recur(i - 1, j, e) + j * recur(i, j - 1, e) + e * recur(i, j, e - 1)
328
+ )
329
+
330
+ # Base case
331
+ if all([_ == 0 for _ in ang_moms]):
332
+ return sqrt(pi / self.p) * self.K
333
+ # Decrement i
334
+ elif i.is_positive:
335
+ return vrr(i - 1, j, e, self.PA)
336
+ # Decrement j
337
+ elif j.is_positive:
338
+ return vrr(i, j - 1, e, self.PB)
339
+ elif e.is_positive:
340
+ return vrr(i, j, e - 1, self.PC)
341
+
342
+
343
+ class Multipole3d(Function):
344
+ @classmethod
345
+ def eval(cls, La, Lb, a, b, A, B, Le, C):
346
+ x, y, z = [
347
+ Multipole1d(a, A[i], b, B[i], C[i]).eval(La[i], Lb[i], Le[i])
348
+ for i in range(3)
349
+ ]
350
+ return x * y * z
351
+
352
+
353
+ class Multipole3dShell(Function):
354
+ @classmethod
355
+ def eval(cls, La_tot, Lb_tot, a, b, A, B, Le_tot=0, C=(0.0, 0.0, 0.0)):
356
+ exprs = [
357
+ Multipole3d(La, Lb, a, b, A, B, Le, C)
358
+ for Le, La, Lb in shell_iter((Le_tot, La_tot, Lb_tot))
359
+ ]
360
+ # print(Multipole1d.eval.cache_info())
361
+ return exprs
362
+
363
+
364
+ class DiagQuadrupole3dShell(Function):
365
+ @classmethod
366
+ def eval(cls, La_tot, Lb_tot, a, b, A, B, C=(0.0, 0.0, 0.0)):
367
+ exprs = list()
368
+ for Le in ((2, 0, 0), (0, 2, 0), (0, 0, 2)):
369
+ for La, Lb in shell_iter((La_tot, Lb_tot)):
370
+ exprs.append(Multipole3d(La, Lb, a, b, A, B, Le, C))
371
+ # print(DiagQuadrupole3dShell.eval.cache_info())
372
+ return exprs
373
+
374
+
375
+ class Overlap1d(TwoCenter1d):
376
+ def eval(self, i, j):
377
+ return Multipole1d(self.a, self.A, self.b, self.B).eval(i, j, 0)
378
+
379
+
380
+ class Overlap3dShell(Function):
381
+ """Whole shell of 3d overlap integrals for a given L pair
382
+ over primitive gaussians."""
383
+
384
+ @classmethod
385
+ def eval(cls, La_tot, Lb_tot, a, b, A, B):
386
+ exprs = Multipole3dShell(La_tot, Lb_tot, a, b, A, B)
387
+ # print(Multipole1d.eval.cache_info())
388
+ return exprs
389
+
390
+
391
+ class Kinetic1d(TwoCenter1d):
392
+ @functools.cache
393
+ def eval(self, i, j):
394
+ if i < 0 or j < 0:
395
+ return 0
396
+
397
+ recur = self.eval
398
+
399
+ def recur_rel(i, j, X):
400
+ return X * recur(i, j) + 1 / (2 * self.p) * (
401
+ i * recur(i - 1, j) + j * recur(i, j - 1)
402
+ )
403
+
404
+ def recur_ovlp(i, j):
405
+ return Overlap1d(self.a, self.A, self.b, self.B).eval(i, j)
406
+
407
+ # Base case
408
+ if i == 0 and j == 0:
409
+ return (
410
+ self.a - 2 * self.a**2 * (self.PA**2 + 1 / (2 * self.p))
411
+ ) * recur_ovlp(i, j)
412
+ # Decrement i
413
+ elif i > 0:
414
+ # Eq. (9.3.41)
415
+ return recur_rel(i - 1, j, self.PA) + self.b / self.p * (
416
+ 2 * self.a * recur_ovlp(i, j) - i * recur_ovlp(i - 2, j)
417
+ )
418
+ # Decrement j
419
+ elif j > 0:
420
+ # Eq. (9.3.41)
421
+ return recur_rel(i, j - 1, self.PB) + self.a / self.p * (
422
+ 2 * self.b * recur_ovlp(i, j) - j * recur_ovlp(i, j - 2)
423
+ )
424
+
425
+
426
+ class Kinetic3d(Function):
427
+ @classmethod
428
+ def eval(cls, La, Lb, a, b, A, B):
429
+ Tx, Ty, Tz = [Kinetic1d(a, A[i], b, B[i]).eval(La[i], Lb[i]) for i in range(3)]
430
+ Sx, Sy, Sz = [Overlap1d(a, A[i], b, B[i]).eval(La[i], Lb[i]) for i in range(3)]
431
+ return Tx * Sy * Sz + Sx * Ty * Sz + Sx * Sy * Tz
432
+
433
+
434
+ class Kinetic3dShell(Function):
435
+ @classmethod
436
+ def eval(cls, La_tot, Lb_tot, a, b, A, B):
437
+ exprs = [
438
+ Kinetic3d(La, Lb, a, b, A, B) for La, Lb in shell_iter((La_tot, Lb_tot))
439
+ ]
440
+ return exprs
441
+
442
+
443
+ # Placeholder for the Boys-function. The actual Boys-function will be
444
+ # imported in the generated python module.
445
+ boys = Function("boys")
446
+
447
+
448
+ class Coulomb(TwoCenter1d):
449
+ """Nucleus at C."""
450
+
451
+ @functools.cache
452
+ def eval(self, i, k, m, j, l, n, N):
453
+ ang_moms = (i, k, m, j, l, n)
454
+ if any([am < 0 for am in ang_moms]):
455
+ return 0
456
+
457
+ def recur(N, *inds):
458
+ """Simple wrapper to pass all required arguments."""
459
+ return self.eval(*inds, N) # , a, b, A, B, C)
460
+
461
+ def decr(to_decr, decr_ind):
462
+ one = np.zeros(3, dtype=int)
463
+ one[decr_ind] = 1
464
+
465
+ def Ni(inds):
466
+ return inds[decr_ind]
467
+
468
+ bra = np.array((i, k, m), dtype=int)
469
+ ket = np.array((j, l, n), dtype=int)
470
+
471
+ if to_decr == "bra":
472
+ X = self.PA
473
+ bra[decr_ind] -= 1
474
+ else:
475
+ X = self.PB
476
+ ket[decr_ind] -= 1
477
+
478
+ bra_decr = bra - one
479
+ ket_decr = ket - one
480
+
481
+ return (
482
+ X[decr_ind] * recur(N, *bra, *ket)
483
+ - self.PC[decr_ind] * recur(N + 1, *bra, *ket)
484
+ + 1
485
+ / (2 * self.p)
486
+ * Ni(bra)
487
+ * (recur(N, *bra_decr, *ket) - recur(N + 1, *bra_decr, *ket))
488
+ + 1
489
+ / (2 * self.p)
490
+ * Ni(ket)
491
+ * (recur(N, *bra, *ket_decr) - recur(N + 1, *bra, *ket_decr))
492
+ )
493
+
494
+ # Base case
495
+ if all([am == 0 for am in ang_moms]):
496
+ K = exp(-self.mu * self.AB.dot(self.AB))
497
+ return 2 * pi / self.p * K * boys(N, self.p * self.PC.dot(self.PC))
498
+ elif i > 0:
499
+ return decr("bra", 0)
500
+ elif j > 0:
501
+ return decr("ket", 0)
502
+ elif k > 0:
503
+ return decr("bra", 1)
504
+ elif l > 0:
505
+ return decr("ket", 1)
506
+ elif m > 0:
507
+ return decr("bra", 2)
508
+ elif n > 0:
509
+ return decr("ket", 2)
510
+
511
+
512
+ class CoulombShell(Function):
513
+ @classmethod
514
+ def eval(cls, La_tot, Lb_tot, a, b, A, B, C=(0.0, 0.0, 0.0)):
515
+ exprs = [
516
+ # Coulomb(*La, *Lb, 0, a, b, A, B, C)
517
+ Coulomb(a, A, b, B, C).eval(*La, *Lb, 0)
518
+ for La, Lb in shell_iter((La_tot, Lb_tot))
519
+ ]
520
+ # print(Coulomb.eval.cache_info())
521
+ return exprs
522
+
523
+
524
+ class TwoCenterTwoElectron(Function):
525
+ @classmethod
526
+ @functools.cache
527
+ def eval(cls, ia, ja, ka, ib, jb, kb, N, a, b, A, B):
528
+ ang_moms = np.array((ia, ja, ka, ib, jb, kb), dtype=int)
529
+ ang_moms2d = ang_moms.reshape(-1, 3)
530
+ if any([am < 0 for am in ang_moms]):
531
+ return 0
532
+
533
+ p = a + b
534
+ P = (a * A + b * B) / p
535
+ mu = (a * b) / p
536
+
537
+ def recur(N, *inds):
538
+ return cls(*inds, N, a, b, A, B)
539
+
540
+ def vrr(bra_or_ket, cart_ind):
541
+ assert bra_or_ket in ("bra", "ket")
542
+
543
+ if bra_or_ket == "bra":
544
+ ind1 = 0
545
+ ind2 = 1
546
+ else:
547
+ ind1 = 1
548
+ ind2 = 0
549
+ exps = (a, b)
550
+ X = (P - A, P - B)[ind1][cart_ind]
551
+
552
+ l1 = ang_moms2d[ind1, cart_ind] - 1
553
+ exp1 = exps[ind1]
554
+ decr1 = ang_moms2d.copy()
555
+ decr1[ind1, cart_ind] -= 1
556
+ decr11 = decr1.copy()
557
+ decr11[ind1, cart_ind] -= 1
558
+
559
+ l2 = ang_moms2d[ind2, cart_ind]
560
+ exp2 = exps[ind2]
561
+ decr12 = ang_moms2d.copy()
562
+ decr12[ind1, cart_ind] -= 1
563
+ decr12[ind2, cart_ind] -= 1
564
+
565
+ decr1 = decr1.flatten()
566
+ decr11 = decr11.flatten()
567
+ decr12 = decr12.flatten()
568
+
569
+ return (
570
+ X * recur(N + 1, *decr1)
571
+ + l1
572
+ / (2 * exp1)
573
+ * (recur(N, *decr11) - exp2 / p * recur(N + 1, *decr11))
574
+ + l2 / (2 * p) * recur(N + 1, *decr12)
575
+ )
576
+
577
+ # vrr_bra = functools.partial(vrr, bra_or_ket="bra")
578
+ vrr_bra = functools.partial(vrr, "bra")
579
+ vrr_ket = functools.partial(vrr, "ket")
580
+
581
+ # Base case
582
+ if (ang_moms == 0).all():
583
+ AB = A - B
584
+ return 2 * pi**2.5 / sqrt(p) / (a * b) * boys(N, mu * AB.dot(AB))
585
+ elif ia > 0:
586
+ return vrr_bra(0)
587
+ elif ja > 0:
588
+ return vrr_bra(1)
589
+ elif ka > 0:
590
+ return vrr_bra(2)
591
+ elif ib > 0:
592
+ return vrr_ket(0)
593
+ elif jb > 0:
594
+ return vrr_ket(1)
595
+ elif kb > 0:
596
+ return vrr_ket(2)
597
+
598
+
599
+ class TwoCenterTwoElectronShell(Function):
600
+ @classmethod
601
+ def eval(cls, La_tot, Lb_tot, a, b, A, B):
602
+ exprs = [
603
+ TwoCenterTwoElectron(*La, *Lb, 0, a, b, A, B)
604
+ for La, Lb in shell_iter((La_tot, Lb_tot))
605
+ ]
606
+ # print(TwoCenterTwoElectron.eval.cache_info())
607
+ return exprs
608
+
609
+
610
+ class ThreeCenterTwoElectronBase(Function):
611
+ """
612
+ https://pubs.rsc.org/en/content/articlelanding/2004/CP/b413539c
613
+
614
+ There is an error in the base case (00|0). One must divide by
615
+ sqrt(eta + gamma), not multiply.
616
+ """
617
+
618
+ @classmethod
619
+ @functools.cache
620
+ def eval(cls, ia, ja, ka, ib, jb, kb, ic, jc, kc, N, a, b, c, A, B, C):
621
+ ang_moms = np.array((ia, ja, ka, ib, jb, kb, ic, jc, kc), dtype=int)
622
+ ang_moms2d = ang_moms.reshape(-1, 3)
623
+ if any([am < 0 for am in ang_moms]):
624
+ return 0
625
+
626
+ p = a + b
627
+ P = (a * A + b * B) / p
628
+ mu = (a * b) / p
629
+ X_PC = P - C
630
+
631
+ rho = p * c / (p + c)
632
+
633
+ def recur(N, *inds):
634
+ """Simple wrapper to pass all required arguments.
635
+
636
+ Here we don't use ThreeCenterTwoElectronBase, but the derived classes,
637
+ that provide the 'aux_vrr' attribute, to distinguish between the different
638
+ vertical recursion relations."""
639
+ return cls(*inds, N, a, b, c, A, B, C)
640
+
641
+ def recur_hrr(cart_ind):
642
+ """Horizontal recursion relation to transfer angular momentum in bra.
643
+
644
+ (a, b+1_i|c) = (a + 1_i,b|c) + X_AB (ab|c)
645
+ """
646
+ assert N == 0
647
+ incr_ang_moms = ang_moms2d.copy()
648
+ incr_ang_moms[0, cart_ind] += 1 # Increase in left orbital
649
+ incr_ang_moms[1, cart_ind] -= 1 # Decrease in right orbital
650
+
651
+ decr_ang_moms = ang_moms2d.copy()
652
+ decr_ang_moms[1, cart_ind] -= 1 # Decrease in right orbital
653
+
654
+ incr_ang_moms = incr_ang_moms.flatten()
655
+ decr_ang_moms = decr_ang_moms.flatten()
656
+
657
+ AB_dir = (A - B)[cart_ind]
658
+
659
+ return recur(N, *incr_ang_moms) + AB_dir * recur(N, *decr_ang_moms)
660
+
661
+ def recur_vrr(cart_ind):
662
+ assert (ib, jb, kb) == (0, 0, 0)
663
+ assert (ic, jc, kc) == (0, 0, 0)
664
+
665
+ decr_a = ang_moms2d.copy()
666
+ decr_a[0, cart_ind] -= 1 # Decrease in bra left orbital
667
+ decr_aa = decr_a.copy()
668
+ decr_aa[0, cart_ind] -= 1 # Decrease in bra left orbital again
669
+
670
+ PA_dir = (P - A)[cart_ind]
671
+ PC_dir = (P - C)[cart_ind]
672
+ ai = (ia, ja, ka)[cart_ind] - 1
673
+ _2p = 2 * p
674
+
675
+ decr_a = decr_a.flatten()
676
+ decr_aa = decr_aa.flatten()
677
+
678
+ return (
679
+ PA_dir * recur(N, *decr_a)
680
+ - rho / p * PC_dir * recur(N + 1, *decr_a)
681
+ + ai / _2p * (recur(N, *decr_aa) - rho / p * recur(N + 1, *decr_aa))
682
+ )
683
+
684
+ def recur_vrr_aux(cart_ind):
685
+ decr_c = ang_moms2d.copy()
686
+ decr_c[2, cart_ind] -= 1
687
+
688
+ decr_cc = decr_c.copy()
689
+ decr_cc[2, cart_ind] -= 1
690
+
691
+ decr_ac = decr_c.copy()
692
+ decr_ac[0, cart_ind] -= 1
693
+
694
+ decr_bc = decr_c.copy()
695
+ decr_bc[1, cart_ind] -= 1
696
+
697
+ PC_dir = (P - C)[cart_ind]
698
+ la = (ia, ja, ka)[cart_ind]
699
+ lb = (ib, jb, kb)[cart_ind]
700
+ lc = (ic, jc, kc)[cart_ind] - 1
701
+
702
+ decr_c = decr_c.flatten()
703
+ decr_cc = decr_cc.flatten()
704
+ decr_ac = decr_ac.flatten()
705
+ decr_bc = decr_bc.flatten()
706
+
707
+ return (
708
+ p / (p + c) * PC_dir * recur(N + 1, *decr_c)
709
+ + lc
710
+ / (2 * c)
711
+ * (recur(N, *decr_cc) - p / (p + c) * recur(N + 1, *decr_cc))
712
+ + la / (2 * (p + c)) * recur(N + 1, *decr_ac)
713
+ + lb / (2 * (p + c)) * recur(N + 1, *decr_bc)
714
+ )
715
+
716
+ def recur_vrr_aux_sph(cart_ind):
717
+ assert (ib, jb, kb) == (0, 0, 0)
718
+ decr_c = ang_moms2d.copy()
719
+ decr_c[2, cart_ind] -= 1
720
+ decr_ac = decr_c.copy()
721
+ decr_ac[0, cart_ind] -= 1
722
+ La = (ia, ja, ka)[cart_ind]
723
+ PC_dir = (P - C)[cart_ind]
724
+ return (
725
+ rho
726
+ / c
727
+ * (
728
+ PC_dir * recur(N + 1, *decr_c.flatten())
729
+ + La / (2 * p) * recur(N + 1, *decr_ac.flatten())
730
+ )
731
+ )
732
+
733
+ recur_vrr_aux_funcs = {
734
+ "cart": recur_vrr_aux,
735
+ "sph": recur_vrr_aux_sph,
736
+ }
737
+ recur_vrr_aux_func = recur_vrr_aux_funcs[cls.aux_vrr]
738
+
739
+ # Base case
740
+ if (ang_moms == 0).all():
741
+ X_AB = A - B
742
+ r2_PC = X_PC.dot(X_PC)
743
+ r2_AB = X_AB.dot(X_AB)
744
+ chi = rho * r2_PC
745
+ K = exp(-mu * r2_AB)
746
+ return 2 * pi**2.5 / sqrt(p + c) / (p * c) * K * boys(N, chi)
747
+ elif ib > 0:
748
+ return recur_hrr(0)
749
+ elif jb > 0:
750
+ return recur_hrr(1)
751
+ elif kb > 0:
752
+ return recur_hrr(2)
753
+ elif ic > 0:
754
+ return recur_vrr_aux_func(0)
755
+ elif jc > 0:
756
+ return recur_vrr_aux_func(1)
757
+ elif kc > 0:
758
+ return recur_vrr_aux_func(2)
759
+ elif ia > 0:
760
+ return recur_vrr(0)
761
+ elif ja > 0:
762
+ return recur_vrr(1)
763
+ elif ka > 0:
764
+ return recur_vrr(2)
765
+
766
+
767
+ class ThreeCenterTwoElectron(ThreeCenterTwoElectronBase):
768
+ aux_vrr = "cart"
769
+
770
+
771
+ class ThreeCenterTwoElectronSph(ThreeCenterTwoElectronBase):
772
+ aux_vrr = "sph"
773
+
774
+
775
+ class ThreeCenterTwoElectronShell(Function):
776
+ @classmethod
777
+ def eval(cls, La_tot, Lb_tot, Lc_tot, a, b, c, A, B, C):
778
+ exprs = [
779
+ ThreeCenterTwoElectron(*La, *Lb, *Lc, 0, a, b, c, A, B, C)
780
+ for La, Lb, Lc in shell_iter((La_tot, Lb_tot, Lc_tot))
781
+ ]
782
+ # print(ThreeCenterTwoElectron.eval.cache_info())
783
+ return exprs
784
+
785
+
786
+ class ThreeCenterTwoElectronSphShell(Function):
787
+ @classmethod
788
+ def eval(cls, La_tot, Lb_tot, Lc_tot, a, b, c, A, B, C):
789
+ exprs = [
790
+ ThreeCenterTwoElectronSph(*La, *Lb, *Lc, 0, a, b, c, A, B, C)
791
+ for La, Lb, Lc in shell_iter((La_tot, Lb_tot, Lc_tot))
792
+ ]
793
+ # print(ThreeCenterTwoElectron.eval.cache_info())
794
+ return exprs
795
+
796
+
797
+ class ERI(Function):
798
+ """Variables named according to libreta paper [3]."""
799
+
800
+ @classmethod
801
+ def eval(
802
+ cls, ia, ja, ka, ib, jb, kb, ic, jc, kc, id_, jd, kd, N, a, b, c, d, A, B, C, D
803
+ ):
804
+ ang_moms = np.array(
805
+ (ia, ja, ka, ib, jb, kb, ic, jc, kc, id_, jd, kd), dtype=int
806
+ )
807
+ ang_moms2d = ang_moms.reshape(-1, 3)
808
+
809
+ if any([am < 0 for am in ang_moms]):
810
+ return 0
811
+
812
+ AB = A - B
813
+ xi = a + b
814
+ P = (a * A + b * B) / xi # Eq. (5)
815
+
816
+ CD = C - D
817
+ zeta = c + d
818
+ Q = (c * C + d * D) / zeta # Eq. (6)
819
+
820
+ theta = xi * zeta / (xi + zeta) # Eq. (7)
821
+
822
+ def recur(N, *ang_moms):
823
+ """Simple wrapper to pass all required arguments."""
824
+ return ERI(*ang_moms, N, a, b, c, d, A, B, C, D)
825
+
826
+ def recur_hrr(bra_or_ket, cart_ind):
827
+ """Horizontal recursion relation to transfer angmoms in bra or ket.
828
+
829
+ (a, b+1_x|cd) = (a + 1_x,b|cd) + X_AB (ab|cd)
830
+ (ab|c, d + 1_x) = (ab|c + 1_x, d) + X_CD (ab|cd)
831
+ """
832
+ if bra_or_ket == "bra":
833
+ XYZ = AB
834
+ incr_ind = 0
835
+ else:
836
+ XYZ = CD
837
+ incr_ind = 2
838
+
839
+ decr_ind = incr_ind + 1
840
+ incr_ang_moms = ang_moms2d.copy()
841
+ incr_ang_moms[incr_ind, cart_ind] += 1 # Increase in left orbital
842
+ incr_ang_moms[decr_ind, cart_ind] -= 1 # Decrease in right orbital
843
+ incr_ang_moms = incr_ang_moms.flatten()
844
+ decr_ang_moms = ang_moms2d.copy()
845
+ decr_ang_moms[decr_ind, cart_ind] -= 1
846
+ decr_ang_moms = decr_ang_moms.flatten()
847
+
848
+ return recur(N, *incr_ang_moms) + XYZ[cart_ind] * recur(N, *decr_ang_moms)
849
+
850
+ def recur_vrr(bra_or_ket, cart_ind):
851
+ assert (ib, jb, kb) == (0, 0, 0)
852
+ assert (id_, jd, kd) == (0, 0, 0)
853
+
854
+ if bra_or_ket == "bra":
855
+ XYZ = P - A
856
+ decr_ind = 0
857
+ decr_also_ind = 2
858
+ exp1, exp2 = zeta, xi
859
+ am1 = (ia, ja, ka)[cart_ind] - 1
860
+ am2 = (ic, jc, kc)[cart_ind]
861
+ sign = -1
862
+ else:
863
+ XYZ = Q - C
864
+ decr_ind = 2
865
+ decr_also_ind = 0
866
+ exp1, exp2 = xi, zeta
867
+ am1 = (ic, jc, kc)[cart_ind] - 1
868
+ am2 = (ia, ja, ka)[cart_ind]
869
+ sign = 1
870
+
871
+ decr_ang_moms = ang_moms2d.copy()
872
+ decr_ang_moms[decr_ind, cart_ind] -= 1
873
+ decr_ang_moms = decr_ang_moms.flatten()
874
+ decr2_ang_moms = ang_moms2d.copy()
875
+ decr2_ang_moms[decr_ind, cart_ind] -= 2 # Further decrease angular momentum
876
+ decr2_ang_moms = decr2_ang_moms.flatten()
877
+ decr_also_ang_moms = ang_moms2d.copy()
878
+ decr_also_ang_moms[decr_also_ind, cart_ind] -= 1
879
+ decr_also_ang_moms = decr_also_ang_moms.flatten()
880
+
881
+ denom = xi + zeta
882
+ quot = exp1 / denom
883
+ PQ = P - Q
884
+
885
+ return (
886
+ XYZ[cart_ind] * recur(N, *decr_ang_moms)
887
+ + sign * quot * PQ[cart_ind] * recur(N + 1, *decr_ang_moms)
888
+ + am1
889
+ / (2 * exp2)
890
+ * (recur(N, *decr2_ang_moms) - quot * recur(N + 1, *decr2_ang_moms))
891
+ + am2 / (2 * denom) * recur(N + 1, *decr_also_ang_moms)
892
+ )
893
+
894
+ # Base case
895
+ if all([am == 0 for am in ang_moms]):
896
+ RAB2 = AB.dot(AB)
897
+ RCD2 = CD.dot(CD)
898
+ Kab = exp(-(a * b) / xi * RAB2)
899
+ Kcd = exp(-(c * d) / zeta * RCD2)
900
+
901
+ PQ = P - Q
902
+ RPQ2 = PQ.dot(PQ)
903
+
904
+ return (
905
+ 2
906
+ * pi ** (5 / 2)
907
+ / (xi * zeta + sqrt(xi + zeta))
908
+ * Kab
909
+ * Kcd
910
+ * boys(N, theta * RPQ2)
911
+ ) # Eq. (8) [ss|ss]^N
912
+ #
913
+ # Horizontal recursion relation to reduce angular momentum.
914
+ #
915
+ # Bra, basis func. with exp. b, centered at B with Lb_tot = (ib + jb + kb)
916
+ elif ib > 0:
917
+ return recur_hrr("bra", 0)
918
+ elif jb > 0:
919
+ return recur_hrr("bra", 1)
920
+ elif kb > 0:
921
+ return recur_hrr("bra", 2)
922
+ # Ket, basis func. with exp. d, centered at D with Ld_tot = (id_ + jd + kd)
923
+ elif id_ > 0:
924
+ return recur_hrr("ket", 0)
925
+ elif jd > 0:
926
+ return recur_hrr("ket", 1)
927
+ elif kd > 0:
928
+ return recur_hrr("ket", 2)
929
+ #
930
+ # Vertical recursion relation to reduce angular momentum.
931
+ #
932
+ # Bra, basis func. with exp. a, centered at A with La_tot = (ia + ja + ka)
933
+ elif ia > 0:
934
+ return recur_vrr("bra", 0)
935
+ elif ja > 0:
936
+ return recur_vrr("bra", 1)
937
+ elif ka > 0:
938
+ return recur_vrr("bra", 2)
939
+ # Ket, basis func. with exp. c, centered at C with Lc_tot = (ic + jc + kc)
940
+ elif ic > 0:
941
+ return recur_vrr("ket", 0)
942
+ elif jc > 0:
943
+ return recur_vrr("ket", 1)
944
+ elif kc > 0:
945
+ return recur_vrr("ket", 1)
946
+
947
+
948
+ class ERIShell(Function):
949
+ @classmethod
950
+ def eval(cls, La_tot, Lb_tot, Lc_tot, Ld_tot, a, b, c, d, A, B, C, D):
951
+ exprs = [
952
+ ERI(*La, *Lb, *Lc, *Ld, 0, a, b, c, d, A, B, C, D)
953
+ for La, Lb, Lc, Ld in shell_iter((La_tot, Lb_tot, Lc_tot, Ld_tot))
954
+ ]
955
+ # print(ERI.eval.cache_info())
956
+ return exprs
957
+
958
+
959
+ class SpinOrbit(Function):
960
+ """1-electron spin-orbit interaction integral for Cartesian direction μ."""
961
+
962
+ @classmethod
963
+ @functools.cache
964
+ def eval(cls, i, k, m, j, l, n, N, mu, a, b, A, B, C):
965
+ ang_moms = (i, k, m, j, l, n)
966
+ if any([am < 0 for am in ang_moms]):
967
+ return 0
968
+
969
+ p = a + b
970
+ P = (a * A + b * B) / p
971
+ X_PC = P - C
972
+
973
+ def coulomb(N, *inds):
974
+ return Coulomb(*inds, N, a, b, A, B, C)
975
+
976
+ def recur(N, *inds):
977
+ """Simple wrapper to pass all required arguments."""
978
+ return SpinOrbit(*inds, N, mu, a, b, A, B, C)
979
+
980
+ def decr(to_decr, decr_ind):
981
+ one = np.zeros(3, dtype=int)
982
+ one[decr_ind] = 1
983
+
984
+ def Ni(inds):
985
+ return inds[decr_ind]
986
+
987
+ bra = np.array((i, k, m), dtype=int)
988
+ ket = np.array((j, l, n), dtype=int)
989
+
990
+ if to_decr == "bra":
991
+ X = P - A
992
+ bra[decr_ind] -= 1
993
+ else:
994
+ X = P - B
995
+ ket[decr_ind] -= 1
996
+
997
+ bra_decr = bra - one
998
+ ket_decr = ket - one
999
+
1000
+ expr = (
1001
+ # The initial part of this recursion is the same as for the 1-electron
1002
+ # Coulomb integrals.
1003
+ X[decr_ind] * recur(N, *bra, *ket)
1004
+ - X_PC[decr_ind] * recur(N + 1, *bra, *ket)
1005
+ + 1
1006
+ / (2 * p)
1007
+ * Ni(bra)
1008
+ * (recur(N, *bra_decr, *ket) - recur(N + 1, *bra_decr, *ket))
1009
+ + 1
1010
+ / (2 * p)
1011
+ * Ni(ket)
1012
+ * (recur(N, *bra, *ket_decr) - recur(N + 1, *bra, *ket_decr))
1013
+ # From here on, the recursion differs.
1014
+ )
1015
+
1016
+ if to_decr == "bra":
1017
+ X = B - C
1018
+ exponent = b
1019
+ else:
1020
+ X = A - C
1021
+ exponent = a
1022
+ # Second to line in Eq. (A34) in [1]
1023
+ expr += 2 * exponent * Matrix(one).cross(X)[mu] * coulomb(N + 1, *bra, *ket)
1024
+ # Last line in Eq. (A34) in [1]
1025
+ for k_ in range(
1026
+ 3
1027
+ ): # Use k_ instead of k, as this is already an angular momentum
1028
+ one_k = np.zeros(3, dtype=int)
1029
+ one_k[k_] = 1
1030
+ one_ind = np.cross(one, one_k)[mu]
1031
+ if to_decr == "bra":
1032
+ expr += ket[k_] * one_ind * coulomb(N + 1, *bra, *(ket - one_k))
1033
+ else:
1034
+ expr += bra[k_] * one_ind * coulomb(N + 1, *(bra - one_k), *ket)
1035
+ return expr
1036
+
1037
+ # Base case
1038
+ if all([am == 0 for am in ang_moms]):
1039
+ AC = A - C
1040
+ BC = B - C
1041
+ ACxBC = AC.cross(BC)
1042
+ return 4 * p * ACxBC[m] * coulomb(N + 1, 0, 0, 0, 0, 0, 0)
1043
+ elif i > 0:
1044
+ return decr("bra", 0)
1045
+ elif j > 0:
1046
+ return decr("ket", 0)
1047
+ elif k > 0:
1048
+ return decr("bra", 1)
1049
+ elif l > 0:
1050
+ return decr("ket", 1)
1051
+ elif m > 0:
1052
+ return decr("bra", 2)
1053
+ elif n > 0:
1054
+ return decr("ket", 2)
1055
+
1056
+
1057
+ class SpinOrbitShell(Function):
1058
+ @classmethod
1059
+ def eval(cls, La_tot, Lb_tot, a, b, A, B, C=(0.0, 0.0, 0.0)):
1060
+ exprs = [
1061
+ SpinOrbit(*La, *Lb, 0, mu, a, b, A, B, C)
1062
+ for (La, Lb), mu in it.product(shell_iter((La_tot, Lb_tot)), range(3))
1063
+ ]
1064
+ # print(SpinOrbit.eval.cache_info())
1065
+ return exprs
1066
+
1067
+
1068
+ def get_center(i):
1069
+ symbs = [Symbol(str(i) + ind, real=True) for ind in ("x", "y", "z")]
1070
+ return Matrix([*symbs]).T # Return column vector
1071
+
1072
+
1073
+ def get_centers(i):
1074
+ symbs = [Symbol(str(i) + ind, real=True) for ind in ("x", "y", "z")]
1075
+ return Matrix([*symbs]).T # Return column vector
1076
+
1077
+
1078
+ def get_map(i, center_i):
1079
+ array = IndexedBase(i, shape=3)
1080
+ array_map = dict(zip(center_i, array))
1081
+ return array, array_map
1082
+
1083
+
1084
+ def cart2spherical(L_tots, exprs):
1085
+ assert len(L_tots) > 0
1086
+
1087
+ # Coefficient matrices for Cartesian-to-spherical conversion
1088
+ coeffs = [Array(CART2SPH[L]) for L in L_tots]
1089
+ cart_shape = [(l + 1) * (l + 2) // 2 for l in L_tots]
1090
+ cart = Array(exprs).reshape(*cart_shape)
1091
+
1092
+ sph = tc(tp(coeffs[0], cart), (1, 2))
1093
+ if len(L_tots) == 2:
1094
+ sph = tc(tp(sph, coeffs[1].transpose()), (1, 2))
1095
+ elif len(L_tots) == 3:
1096
+ _, Cb, Cc = coeffs
1097
+ sph = tc(tp(permutedims(sph, (0, 2, 1)), Cb.transpose()), (2, 3))
1098
+ sph = tc(tp(permutedims(sph, (0, 2, 1)), Cc.transpose()), (2, 3))
1099
+ else:
1100
+ raise Exception(
1101
+ "Cartesian -> spherical transformation for 4-center integrals "
1102
+ "is not implemented!"
1103
+ )
1104
+
1105
+ # Cartesian-to-spherical transformation introduces quite a number of
1106
+ # multiplications by 1.0, which are uneccessary. Here, we try to drop
1107
+ # some of them by replacing numbers very close to +1.0 with 1.
1108
+ sph = sph.replace(lambda n: n.is_Number and (abs(n - 1) <= ONE_THRESH), lambda n: 1)
1109
+ # TODO: maybe something along the lines
1110
+ # sph = map(lambda expr: expr.evalf(), flatten(sph))
1111
+ # is faster?
1112
+ return flatten(sph)
1113
+
1114
+
1115
+ def gen_integral_exprs(
1116
+ int_func,
1117
+ L_maxs,
1118
+ kind,
1119
+ maps=None,
1120
+ sph=False,
1121
+ ):
1122
+ if maps is None:
1123
+ maps = list()
1124
+
1125
+ ranges = [range(L + 1) for L in L_maxs]
1126
+
1127
+ for L_tots in it.product(*ranges):
1128
+ time_str = time.strftime("%H:%M:%S")
1129
+ start = datetime.now()
1130
+ print(f"{time_str} - Generating {L_tots} {kind}")
1131
+ sys.stdout.flush()
1132
+ # Generate actual list of expressions.
1133
+ exprs = int_func(*L_tots)
1134
+ print("\t... generated expressions")
1135
+ sys.stdout.flush()
1136
+ # if sph:
1137
+ # exprs = cart2spherical(L_tots, exprs)
1138
+ # print("\t... did Cartesian -> Spherical conversion")
1139
+ # sys.stdout.flush()
1140
+
1141
+ # Common subexpression elimination
1142
+ repls, reduced = cse(list(exprs), order="none")
1143
+ print("\t... did common subexpression elimination")
1144
+
1145
+ # Replacement expressions, used to form the reduced expressions.
1146
+ for i, (lhs, rhs) in enumerate(repls):
1147
+ rhs = rhs.evalf()
1148
+ # Replace occurences of Ax, Ay, Az, ... with A[0], A[1], A[2], ...
1149
+ rhs = functools.reduce(lambda rhs, map_: rhs.xreplace(map_), maps, rhs)
1150
+ repls[i] = (lhs, rhs)
1151
+
1152
+ # Reduced expression, i.e., the final integrals/expressions.
1153
+ for i, red in enumerate(reduced):
1154
+ red = red.evalf()
1155
+ reduced[i] = functools.reduce(
1156
+ lambda red, map_: red.xreplace(map_), maps, red
1157
+ )
1158
+ # Carry out Cartesian-to-spherical transformation, if requested.
1159
+ if sph:
1160
+ reduced = cart2spherical(L_tots, reduced)
1161
+ print("\t... did Cartesian -> Spherical conversion")
1162
+ sys.stdout.flush()
1163
+
1164
+ dur = datetime.now() - start
1165
+ print(f"\t... finished in {str(dur)} h")
1166
+ sys.stdout.flush()
1167
+ yield (repls, reduced), L_tots
1168
+
1169
+
1170
+ def render_py_funcs(exprs_Ls, args, base_name, doc_func, add_imports=None, comment=""):
1171
+ if add_imports is None:
1172
+ add_imports = ()
1173
+ add_imports_str = "\n".join(add_imports)
1174
+ if add_imports:
1175
+ add_imports_str += "\n\n"
1176
+
1177
+ args = ", ".join((map(str, args)))
1178
+
1179
+ py_funcs = list()
1180
+ for (repls, reduced), L_tots in exprs_Ls:
1181
+ doc_str = doc_func(L_tots)
1182
+ doc_str += "\n\nGenerated code; DO NOT modify by hand!"
1183
+ name = base_name + "_" + "".join(str(l) for l in L_tots)
1184
+ print(f"Rendering '{name}' ... ", end="")
1185
+ start = time.time()
1186
+ py_func = make_py_func(repls, reduced, args=args, name=name, doc_str=doc_str)
1187
+ dur = time.time() - start
1188
+ print(f"finished in {dur: >8.2f} s")
1189
+ py_funcs.append(py_func)
1190
+ py_funcs_joined = "\n\n".join(py_funcs)
1191
+
1192
+ if comment != "":
1193
+ comment = f'"""\n{comment}\n"""\n\n'
1194
+
1195
+ np_tpl = Template(
1196
+ "import numpy\n\n{{ add_imports }}{{ comment }}{{ py_funcs }}",
1197
+ trim_blocks=True,
1198
+ lstrip_blocks=True,
1199
+ )
1200
+ np_rendered = np_tpl.render(
1201
+ comment=comment, add_imports=add_imports_str, py_funcs=py_funcs_joined
1202
+ )
1203
+ np_rendered = textwrap.dedent(np_rendered)
1204
+ try:
1205
+ from black import format_str, FileMode
1206
+
1207
+ np_rendered = format_str(np_rendered, mode=FileMode(line_length=90))
1208
+ except ModuleNotFoundError:
1209
+ print("Skipped formatting with black, as it is not installed!")
1210
+
1211
+ return np_rendered
1212
+
1213
+
1214
+ def render_c_funcs(exprs_Ls, args, base_name, doc_func, add_imports=None, comment=""):
1215
+ if add_imports is not None:
1216
+ raise Exception("Implement me!")
1217
+
1218
+ arg_strs = [str(arg) for arg in args]
1219
+
1220
+ funcs = list()
1221
+ signatures = list()
1222
+ for (repls, reduced), L_tots in exprs_Ls:
1223
+ doc_str = doc_func(L_tots)
1224
+ doc_str += "\n\n\t\tGenerated code; DO NOT modify by hand!"
1225
+ doc_str = textwrap.dedent(doc_str)
1226
+ name = base_name + "_" + "".join(str(l) for l in L_tots)
1227
+ func, signature = make_c_func(
1228
+ repls, reduced, args=arg_strs, name=name, doc_str=doc_str
1229
+ )
1230
+ funcs.append(func)
1231
+ signatures.append(signature)
1232
+ funcs_joined = "\n\n".join(funcs)
1233
+
1234
+ if comment != "":
1235
+ comment = f"/*{comment}*/"
1236
+
1237
+ # Render C files
1238
+ c_tpl = Template(
1239
+ "#include <math.h>\n\n{{ comment }}\n\n{{ funcs }}",
1240
+ trim_blocks=True,
1241
+ lstrip_blocks=True,
1242
+ )
1243
+ c_rendered = c_tpl.render(comment=comment, funcs=funcs_joined)
1244
+ c_rendered = textwrap.dedent(c_rendered)
1245
+ # Render simple header file
1246
+ h_rendered = "\n".join([f"{sig};" for sig in signatures])
1247
+ return c_rendered, h_rendered
1248
+
1249
+
1250
+ def write_file(out_dir, name, rendered):
1251
+ out_name = out_dir / name
1252
+ with open(out_name, "w") as handle:
1253
+ handle.write(rendered)
1254
+ print(f"Wrote '{out_name}'.")
1255
+
1256
+
1257
+ def write_render(
1258
+ ints_Ls,
1259
+ args,
1260
+ name,
1261
+ doc_func,
1262
+ out_dir,
1263
+ comment="",
1264
+ py_kwargs=None,
1265
+ c=True,
1266
+ c_kwargs=None,
1267
+ ):
1268
+ if py_kwargs is None:
1269
+ py_kwargs = {}
1270
+ if c_kwargs is None:
1271
+ c_kwargs = {}
1272
+ ints_Ls = list(ints_Ls)
1273
+ # Python
1274
+ py_rendered = render_py_funcs(
1275
+ ints_Ls, args, name, doc_func, comment=comment, **py_kwargs
1276
+ )
1277
+ write_file(out_dir, f"{name}.py", py_rendered)
1278
+ # C
1279
+ if c:
1280
+ c_rendered, h_rendered = render_c_funcs(
1281
+ ints_Ls,
1282
+ args,
1283
+ name,
1284
+ doc_func,
1285
+ comment=comment,
1286
+ **c_kwargs,
1287
+ )
1288
+ write_file(out_dir, f"{name}.c", c_rendered)
1289
+ write_file(out_dir, f"{name}.h", h_rendered)
1290
+
1291
+
1292
+ def parse_args(args):
1293
+ parser = argparse.ArgumentParser()
1294
+
1295
+ parser.add_argument(
1296
+ "--lmax",
1297
+ default=L_MAX,
1298
+ type=int,
1299
+ help="Generate 1e-integrals up to this maximum angular momentum.",
1300
+ )
1301
+ parser.add_argument(
1302
+ "--lauxmax",
1303
+ default=L_AUX_MAX,
1304
+ type=int,
1305
+ help="Maximum angular moment for integrals using auxiliary functions.",
1306
+ )
1307
+ parser.add_argument(
1308
+ "--write",
1309
+ action="store_true",
1310
+ help="Write out generated integrals to the current directory, potentially overwriting the present modules.",
1311
+ )
1312
+ parser.add_argument(
1313
+ "--out-dir",
1314
+ default="devel_ints",
1315
+ help="Directory, where the generated integrals are written.",
1316
+ )
1317
+ keys_str = f"({', '.join(KEYS)})"
1318
+ parser.add_argument(
1319
+ "--keys",
1320
+ nargs="+",
1321
+ help=f"Generate only certain expressions. Possible keys are: {keys_str}. "
1322
+ "If not given, all expressions are generated.",
1323
+ )
1324
+
1325
+ return parser.parse_args()
1326
+
1327
+
1328
+ def run():
1329
+ args = parse_args(sys.argv[1:])
1330
+
1331
+ l_max = args.lmax
1332
+ l_aux_max = args.lauxmax
1333
+ out_dir = Path(args.out_dir if not args.write else ".")
1334
+ keys = args.keys
1335
+ if keys is None:
1336
+ keys = list()
1337
+ try:
1338
+ os.mkdir(out_dir)
1339
+ except FileExistsError:
1340
+ pass
1341
+
1342
+ try:
1343
+ global CART2SPH
1344
+ CART2SPH = cart2sph_coeffs(max(l_max, l_aux_max), zero_small=True)
1345
+ except NameError:
1346
+ print("cart2sph_coeffs import is deactivated or pysisyphus is not installed.")
1347
+
1348
+ # Cartesian basis function centers A and B.
1349
+ center_A = get_center("A")
1350
+ center_B = get_center("B")
1351
+ center_C = get_center("C")
1352
+ center_D = get_center("D")
1353
+ # center_R = get_center("R")
1354
+ Xa, Ya, Za = symbols("Xa Ya Za")
1355
+
1356
+ # Orbital exponents a, b, c, d.
1357
+ a, b, c, d = symbols("a b c d", real=True)
1358
+
1359
+ # These maps will be used to convert {Ax, Ay, ...} to array quantities
1360
+ # in the generated code. This way an iterable/np.ndarray can be used as
1361
+ # function argument instead of (Ax, Ay, Az, Bx, By, Bz).
1362
+ A, A_map = get_map("A", center_A)
1363
+ B, B_map = get_map("B", center_B)
1364
+ C, C_map = get_map("C", center_C)
1365
+ D, D_map = get_map("D", center_D)
1366
+
1367
+ boys_import = ("from pysisyphus.wavefunction.ints.boys import boys",)
1368
+
1369
+ #################
1370
+ # Cartesian GTO #
1371
+ #################
1372
+
1373
+ def cart_gto():
1374
+ def cart_gto_doc_func(L_tot):
1375
+ (La_tot,) = L_tot
1376
+ shell_a = L_MAP[La_tot]
1377
+ return (
1378
+ f"3D Cartesian {shell_a}-Gaussian shell.\n\n"
1379
+ "Exponent a, centered at A, evaluated at (Xa, Ya, Za) + A."
1380
+ )
1381
+
1382
+ # This code can evaluate multiple points at a time
1383
+ cart_gto_Ls = gen_integral_exprs(
1384
+ lambda La_tot: CartGTOShell(La_tot, a, Xa, Ya, Za),
1385
+ (l_max,),
1386
+ "cart_gto",
1387
+ )
1388
+ cart_gto_rendered = render_py_funcs(
1389
+ cart_gto_Ls, (a, Xa, Ya, Za), "cart_gto3d", cart_gto_doc_func
1390
+ )
1391
+ write_file(out_dir, "gto3d.py", cart_gto_rendered)
1392
+ print()
1393
+
1394
+ #####################
1395
+ # Overlap integrals #
1396
+ #####################
1397
+
1398
+ def overlap():
1399
+ def ovlp_doc_func(L_tots):
1400
+ La_tot, Lb_tot = L_tots
1401
+ shell_a = L_MAP[La_tot]
1402
+ shell_b = L_MAP[Lb_tot]
1403
+ return f"Cartesian 3D ({shell_a}{shell_b}) overlap integral."
1404
+
1405
+ ovlp_ints_Ls = gen_integral_exprs(
1406
+ lambda La_tot, Lb_tot: Overlap3dShell(
1407
+ La_tot, Lb_tot, a, b, center_A, center_B
1408
+ ),
1409
+ (l_max, l_max),
1410
+ "overlap",
1411
+ (A_map, B_map),
1412
+ )
1413
+ write_render(
1414
+ ovlp_ints_Ls, (a, A, b, B), "ovlp3d", ovlp_doc_func, out_dir, c=True
1415
+ )
1416
+ print()
1417
+
1418
+ ###########################
1419
+ # Dipole moment integrals #
1420
+ ###########################
1421
+
1422
+ def dipole():
1423
+ def dipole_doc_func(L_tots):
1424
+ La_tot, Lb_tot = L_tots
1425
+ shell_a = L_MAP[La_tot]
1426
+ shell_b = L_MAP[Lb_tot]
1427
+ return (
1428
+ f"Cartesian 3D ({shell_a}{shell_b}) dipole moment integrals.\n"
1429
+ "The origin is at C."
1430
+ )
1431
+
1432
+ dipole_comment = """
1433
+ Dipole integrals are given in the order:
1434
+ for bf_a in basis_functions_a:
1435
+ for bf_b in basis_functions_b:
1436
+ for cart_dir in (x, y, z):
1437
+ dipole_integrals(bf_a, bf_b, cart_dir)
1438
+
1439
+ So for <s_a|μ|s_b> it will be:
1440
+
1441
+ <s_a|x|s_b>
1442
+ <s_a|y|s_b>
1443
+ <s_a|z|s_b>
1444
+ """
1445
+
1446
+ dipole_ints_Ls = gen_integral_exprs(
1447
+ lambda La_tot, Lb_tot: Multipole3dShell(
1448
+ La_tot, Lb_tot, a, b, center_A, center_B, 1, center_C
1449
+ ),
1450
+ (l_max, l_max),
1451
+ "dipole moment",
1452
+ (A_map, B_map, C_map),
1453
+ )
1454
+ write_render(
1455
+ dipole_ints_Ls,
1456
+ (a, A, b, B, C),
1457
+ "dipole3d",
1458
+ dipole_doc_func,
1459
+ out_dir,
1460
+ comment=dipole_comment,
1461
+ c=True,
1462
+ )
1463
+ print()
1464
+
1465
+ ###########################################
1466
+ # Diagonal of quadrupole moment integrals #
1467
+ ###########################################
1468
+
1469
+ def diag_quadrupole():
1470
+ def diag_quadrupole_doc_func(L_tots):
1471
+ La_tot, Lb_tot = L_tots
1472
+ shell_a = L_MAP[La_tot]
1473
+ shell_b = L_MAP[Lb_tot]
1474
+ return (
1475
+ f"Cartesian 3D ({shell_a}{shell_b}) quadrupole moment integrals\n"
1476
+ "for operators x², y² and z². The origin is at C."
1477
+ )
1478
+
1479
+ diag_quadrupole_comment = """
1480
+ Diagonal of the quadrupole moment matrix with operators x², y², z².
1481
+
1482
+ for rr in (xx, yy, zz):
1483
+ for bf_a in basis_functions_a:
1484
+ for bf_b in basis_functions_b:
1485
+ quadrupole_integrals(bf_a, bf_b, rr)
1486
+ """
1487
+
1488
+ diag_quadrupole_ints_Ls = gen_integral_exprs(
1489
+ lambda La_tot, Lb_tot: DiagQuadrupole3dShell(
1490
+ La_tot, Lb_tot, a, b, center_A, center_B, center_C
1491
+ ),
1492
+ (l_max, l_max),
1493
+ "diag quadrupole moment",
1494
+ (A_map, B_map, C_map),
1495
+ )
1496
+ write_render(
1497
+ diag_quadrupole_ints_Ls,
1498
+ (a, A, b, B, C),
1499
+ "diag_quadrupole3d",
1500
+ diag_quadrupole_doc_func,
1501
+ out_dir,
1502
+ comment=diag_quadrupole_comment,
1503
+ c=True,
1504
+ )
1505
+ print()
1506
+
1507
+ ###############################
1508
+ # Quadrupole moment integrals #
1509
+ ###############################
1510
+
1511
+ def quadrupole():
1512
+ def quadrupole_doc_func(L_tots):
1513
+ La_tot, Lb_tot = L_tots
1514
+ shell_a = L_MAP[La_tot]
1515
+ shell_b = L_MAP[Lb_tot]
1516
+ return (
1517
+ f"Cartesian 3D ({shell_a}{shell_b}) quadrupole moment integrals.\n"
1518
+ "The origin is at C."
1519
+ )
1520
+
1521
+ quadrupole_comment = """
1522
+ Quadrupole integrals contain the upper triangular part of the symmetric
1523
+ 3x3 quadrupole matrix.
1524
+
1525
+ / xx xy xz \\
1526
+ | yy yz |
1527
+ \ zz /
1528
+ """
1529
+
1530
+ quadrupole_ints_Ls = gen_integral_exprs(
1531
+ lambda La_tot, Lb_tot: Multipole3dShell(
1532
+ La_tot, Lb_tot, a, b, center_A, center_B, 2, center_C
1533
+ ),
1534
+ (l_max, l_max),
1535
+ "quadrupole moment",
1536
+ (A_map, B_map, C_map),
1537
+ )
1538
+
1539
+ write_render(
1540
+ quadrupole_ints_Ls,
1541
+ (a, A, b, B, C),
1542
+ "quadrupole3d",
1543
+ quadrupole_doc_func,
1544
+ out_dir,
1545
+ comment=quadrupole_comment,
1546
+ c=True,
1547
+ )
1548
+ print()
1549
+
1550
+ ############################
1551
+ # Kinetic energy integrals #
1552
+ ############################
1553
+
1554
+ def kinetic():
1555
+ def kinetic_doc_func(L_tots):
1556
+ La_tot, Lb_tot = L_tots
1557
+ shell_a = L_MAP[La_tot]
1558
+ shell_b = L_MAP[Lb_tot]
1559
+ return f"Cartesian 3D ({shell_a}{shell_b}) kinetic energy integral."
1560
+
1561
+ kinetic_ints_Ls = gen_integral_exprs(
1562
+ lambda La_tot, Lb_tot: Kinetic3dShell(
1563
+ La_tot, Lb_tot, a, b, center_A, center_B
1564
+ ),
1565
+ (l_max, l_max),
1566
+ "kinetic",
1567
+ (A_map, B_map),
1568
+ )
1569
+ write_render(
1570
+ kinetic_ints_Ls,
1571
+ (a, A, b, B),
1572
+ "kinetic3d",
1573
+ kinetic_doc_func,
1574
+ out_dir,
1575
+ c=True,
1576
+ )
1577
+ print()
1578
+
1579
+ #########################
1580
+ # 1el Coulomb Integrals #
1581
+ #########################
1582
+
1583
+ def coulomb():
1584
+ def coulomb_doc_func(L_tots):
1585
+ La_tot, Lb_tot = L_tots
1586
+ shell_a = L_MAP[La_tot]
1587
+ shell_b = L_MAP[Lb_tot]
1588
+ return f"Cartesian ({shell_a}{shell_b}) 1-electron Coulomb integral."
1589
+
1590
+ coulomb_ints_Ls = gen_integral_exprs(
1591
+ lambda La_tot, Lb_tot: CoulombShell(
1592
+ La_tot, Lb_tot, a, b, center_A, center_B, center_C
1593
+ ),
1594
+ (l_max, l_max),
1595
+ "coulomb3d",
1596
+ (A_map, B_map, C_map),
1597
+ )
1598
+ coulomb_rendered = render_py_funcs(
1599
+ coulomb_ints_Ls,
1600
+ (a, A, b, B, C),
1601
+ "coulomb3d",
1602
+ coulomb_doc_func,
1603
+ add_imports=boys_import,
1604
+ )
1605
+ # TODO: handle add_args and Boys function in C
1606
+ write_file(out_dir, "coulomb3d.py", coulomb_rendered)
1607
+ print()
1608
+
1609
+ ###############################################
1610
+ # Two-center two-electron repulsion integrals #
1611
+ ###############################################
1612
+
1613
+ def _2center2electron():
1614
+ def _2center2el_doc_func(L_tots):
1615
+ La_tot, Lb_tot = L_tots
1616
+ shell_a = L_MAP[La_tot]
1617
+ shell_b = L_MAP[Lb_tot]
1618
+ return (
1619
+ f"Cartesian ({shell_a}|{shell_b}) "
1620
+ "two-center two-electron repulsion integral."
1621
+ )
1622
+
1623
+ _2center2el_ints_Ls = gen_integral_exprs(
1624
+ lambda La_tot, Lb_tot: TwoCenterTwoElectronShell(
1625
+ La_tot,
1626
+ Lb_tot,
1627
+ a,
1628
+ b,
1629
+ center_A,
1630
+ center_B,
1631
+ ),
1632
+ (l_aux_max, l_aux_max),
1633
+ "_2center2el3d",
1634
+ (A_map, B_map),
1635
+ )
1636
+ write_render(
1637
+ _2center2el_ints_Ls,
1638
+ (a, A, b, B),
1639
+ "_2center2el3d",
1640
+ _2center2el_doc_func,
1641
+ out_dir,
1642
+ c=False,
1643
+ py_kwargs={"add_imports": boys_import},
1644
+ )
1645
+ print()
1646
+
1647
+ #################################################
1648
+ # Three-center two-electron repulsion integrals #
1649
+ #################################################
1650
+
1651
+ def _3center2electron():
1652
+ def _3center2el_doc_func(L_tots):
1653
+ La_tot, Lb_tot, Lc_tot = L_tots
1654
+ shell_a = L_MAP[La_tot]
1655
+ shell_b = L_MAP[Lb_tot]
1656
+ shell_c = L_MAP[Lc_tot]
1657
+ return (
1658
+ f"Cartesian ({shell_a}{shell_b}|{shell_c}) "
1659
+ "three-center two-electron repulsion integral."
1660
+ )
1661
+
1662
+ _3center2el_ints_Ls = gen_integral_exprs(
1663
+ lambda La_tot, Lb_tot, Lc_tot: ThreeCenterTwoElectronShell(
1664
+ La_tot, Lb_tot, Lc_tot, a, b, c, center_A, center_B, center_C
1665
+ ),
1666
+ (l_max, l_max, l_aux_max),
1667
+ "_3center2el3d",
1668
+ (A_map, B_map, C_map),
1669
+ )
1670
+ write_render(
1671
+ _3center2el_ints_Ls,
1672
+ (a, A, b, B, c, C),
1673
+ "_3center2el3d",
1674
+ _3center2el_doc_func,
1675
+ out_dir,
1676
+ c=False,
1677
+ py_kwargs={"add_imports": boys_import},
1678
+ )
1679
+ print()
1680
+
1681
+ def _3center2electron_sph():
1682
+ def _3center2el_doc_func(L_tots):
1683
+ La_tot, Lb_tot, Lc_tot = L_tots
1684
+ shell_a = L_MAP[La_tot]
1685
+ shell_b = L_MAP[Lb_tot]
1686
+ shell_c = L_MAP[Lc_tot]
1687
+ return (
1688
+ f"Cartesian ({shell_a}{shell_b}|{shell_c}) "
1689
+ "three-center two-electron repulsion integral for conversion to "
1690
+ "spherical harmonics.\nUses Ahlrichs' vertical recursion relation, "
1691
+ "that leaves out some terms, that cancel\nwhen convertig to "
1692
+ "spherical harmonics."
1693
+ )
1694
+
1695
+ _3center2el_ints_Ls = gen_integral_exprs(
1696
+ lambda La_tot, Lb_tot, Lc_tot: ThreeCenterTwoElectronSphShell(
1697
+ La_tot, Lb_tot, Lc_tot, a, b, c, center_A, center_B, center_C
1698
+ ),
1699
+ (l_max, l_max, l_aux_max),
1700
+ "_3center2el3d_sph",
1701
+ (A_map, B_map, C_map),
1702
+ sph=False,
1703
+ )
1704
+ write_render(
1705
+ _3center2el_ints_Ls,
1706
+ (a, A, b, B, c, C),
1707
+ "_3center2el3d_sph",
1708
+ _3center2el_doc_func,
1709
+ out_dir,
1710
+ c=False,
1711
+ py_kwargs={"add_imports": boys_import},
1712
+ )
1713
+ print()
1714
+
1715
+ ####################################
1716
+ # Spin-orbit interaction integrals #
1717
+ ####################################
1718
+
1719
+ # def so1el_doc_func(L_tots):
1720
+ # La_tot, Lb_tot = L_tots
1721
+ # shell_a = L_MAP[La_tot]
1722
+ # shell_b = L_MAP[Lb_tot]
1723
+ # return f"Cartesian ({shell_a}{shell_b}) 1-electron spin-orbit-interaction integral."
1724
+
1725
+ # I think this is still faulty!
1726
+ # so1el_ints_Ls = gen_integral_exprs(
1727
+ # lambda La_tot, Lb_tot: SpinOrbitShell(
1728
+ # La_tot, Lb_tot, a, b, center_A, center_B, center_C
1729
+ # ),
1730
+ # (l_max, l_max),
1731
+ # "so1el",
1732
+ # (A_map, B_map, C_map),
1733
+ # )
1734
+
1735
+ # so1el_rendered = render_py_funcs(
1736
+ # so1el_ints_Ls,
1737
+ # (a, A, b, B, C),
1738
+ # "so1el",
1739
+ # so1el_doc_func,
1740
+ # add_imports=boys_import,
1741
+ # )
1742
+ # write_file(out_dir, "so1el.py", so1el_rendered)
1743
+ # print()
1744
+
1745
+ #########################
1746
+ # 2el Coulomb Integrals #
1747
+ #########################
1748
+
1749
+ # def eri_doc_func(L_tots):
1750
+ # La_tot, Lb_tot, Lc_tot, Ld_tot = L_tots
1751
+ # shell_a = L_MAP[La_tot]
1752
+ # shell_b = L_MAP[Lb_tot]
1753
+ # shell_c = L_MAP[Lc_tot]
1754
+ # shell_d = L_MAP[Ld_tot]
1755
+ # return (
1756
+ # f"Cartesian [{shell_a}{shell_b}|{shell_c}{shell_d}] "
1757
+ # "2-electron electron repulsion integral."
1758
+ # )
1759
+
1760
+ # I think this is still faulty!
1761
+ # eri_ints_Ls = gen_integral_exprs(
1762
+ # lambda La_tot, Lb_tot, Lc_tot, Ld_tot: ERIShell(
1763
+ # La_tot,
1764
+ # Lb_tot,
1765
+ # Lc_tot,
1766
+ # Ld_tot,
1767
+ # a,
1768
+ # b,
1769
+ # c,
1770
+ # d,
1771
+ # center_A,
1772
+ # center_B,
1773
+ # center_C,
1774
+ # center_D,
1775
+ # ),
1776
+ # # (l_max, l_max, l_max, l_max),
1777
+ # (2, 2, 2, 2), # Stop at [dd|dd] for now
1778
+ # "eri",
1779
+ # (A_map, B_map, C_map, D_map),
1780
+ # )
1781
+ # eri_rendered = render_py_funcs(
1782
+ # eri_ints_Ls,
1783
+ # (a, A, b, B, c, C, d, D),
1784
+ # "eri",
1785
+ # eri_doc_func,
1786
+ # add_imports=boys_import,
1787
+ # )
1788
+ # write_file(out_dir, "eri.py", eri_rendered)
1789
+ # print()
1790
+
1791
+ funcs = {
1792
+ "cgto": cart_gto, # Cartesian Gaussian-type-orbital for density evaluation
1793
+ "ovlp": overlap, # Overlap integrals
1794
+ "dpm": dipole, # Linear moment (dipole) integrals
1795
+ "dqpm": diag_quadrupole, # Diagonal part of the quadrupole tensor
1796
+ "qpm": quadrupole, # Quadratic moment (quadrupole) integrals
1797
+ "kin": kinetic, # Kinetic energy integrals
1798
+ "coul": coulomb, # 1-electron Coulomb integrals
1799
+ # Integrals for density fitting
1800
+ "2c2e": _2center2electron, # 2-center-2-electron integrals for DF
1801
+ "3c2e": _3center2electron, # 3-center-2-electron integrals for density fitting
1802
+ "3c2e_sph": _3center2electron_sph, # 3c2el for spherical transformation for DF
1803
+ }
1804
+ if len(keys) == 0:
1805
+ keys = funcs.keys()
1806
+ for key in keys:
1807
+ funcs[key]()
1808
+
1809
+
1810
+ if __name__ == "__main__":
1811
+ run()