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,480 @@
1
+ # [1] https://doi.org/10.1002/ange.201914943
2
+ # Heavy-Atom Tunneling in Organic Reactions
3
+ # Castro, Karney, 2020
4
+ # [2] https://doi.org/10.1002/wcms.1165
5
+ # Theory and simulation of atom tunneling in chemical reactions
6
+ # Kästner, 2014
7
+ # [3] https://doi.org/10.1002/qua.25686
8
+ # Eyringpy
9
+ # Dzib, Merino et al, 2018
10
+ # [4] https://pubs.acs.org/doi/pdf/10.1021/j100809a040
11
+ # TUNNELLING CORRECTIONS FOR UNSYMMETRICAL ECKART POTENTIAL ENERGY BARRIERS
12
+ # Johnston, Heicklen, 1961
13
+ # [5] https://dx.doi.org/10.6028%2Fjres.086.014
14
+ # A Method of Calculating Tunneling Corrections For Eckart Potential Barriers
15
+ # Brown, 1981
16
+
17
+ from dataclasses import dataclass
18
+ from math import sin
19
+ from typing import Optional
20
+
21
+ import jinja2
22
+ import numpy as np
23
+ import numpy.typing as npt
24
+ import scipy.integrate as integrate
25
+
26
+ from pysisyphus.constants import AU2KJPERMOL, AU2SEC, C, KB, KBAU, PLANCK, PLANCKAU
27
+ from pysisyphus.io import geom_from_hessian
28
+
29
+
30
+ @dataclass
31
+ class ReactionRates:
32
+ from_: str
33
+ barrier: float # in E_h
34
+ barrier_si: float # in kJ mol⁻¹
35
+ temperature: float # in K
36
+ imag_wavenumber: float # in cm⁻¹
37
+ imag_frequency: float # in s⁻¹
38
+ rate_eyring: float # in s⁻¹
39
+ kappa_eyring: float
40
+ rate_wigner: Optional[float] = None # in s⁻¹
41
+ kappa_wigner: Optional[float] = None
42
+ rate_bell: Optional[float] = None # in s⁻¹
43
+ kappa_bell: Optional[float] = None
44
+ rate_eckart: Optional[float] = None # in s⁻¹
45
+ kappa_eckart: Optional[float] = None
46
+
47
+
48
+ RX_RATES_TPL = """
49
+ From: {{ "{: >18s}".format(rxr.from_) }} to TS
50
+ Barrier: {{ "{: >18.6f}".format(rxr.barrier) }} E_h ( {{ rxr.barrier_si|f2 }} kJ mol⁻¹)
51
+ Temperature: {{ "{: >18.2f}".format(rxr.temperature) }} K
52
+ Transition vector:
53
+ Wavenumber: {{ "{: >18.2f}".format(rxr.imag_wavenumber) }} cm⁻¹
54
+ Frequency: {{ "{: >18.6e}".format(rxr.imag_frequency) }} s⁻¹
55
+
56
+ Type kappa rate / s⁻¹ rate / h⁻¹ comment
57
+ ------- ------------ -------------- -------------- ------------------
58
+ {{ rate_line("Eyring", rxr.kappa_eyring, rxr.rate_eyring) }}
59
+ {%- if rxr.rate_wigner %}
60
+ {{ rate_line("Wigner", rxr.kappa_wigner, rxr.rate_wigner, "1d tunnel corr.") }}
61
+ {%- endif %}
62
+ {%- if rxr.rate_bell %}
63
+ {{ rate_line("Bell", rxr.kappa_bell, rxr.rate_bell, "1d tunnel corr.") }}
64
+ {%- endif %}
65
+ {%- if rxr.rate_eckart %}
66
+ {{ rate_line("Eckart", rxr.kappa_eckart, rxr.rate_eckart, "1d tunnel corr.") }}
67
+ {%- endif %}
68
+ ------- ------------ -------------- -------------- ------------------
69
+ """
70
+
71
+
72
+ def render_rx_rates(rx_rates: ReactionRates) -> str:
73
+ def rate_line(name, kappa, rate, comment=""):
74
+ rate_h = rate * 3600
75
+ return f"{name: <12s} {kappa: >8.4f} {rate: >12.8e} {rate_h: >12.8e} {comment: >18s}"
76
+
77
+ env = jinja2.Environment()
78
+ env.filters["f2"] = lambda _: f"{_:.2f}"
79
+ env.filters["f4"] = lambda _: f"{_:.4f}"
80
+ env.filters["f6"] = lambda _: f"{_:.6f}"
81
+ env.filters["e8"] = lambda _: f"{_: >16.8e}"
82
+
83
+ tpl = env.from_string(RX_RATES_TPL)
84
+
85
+ rendered = tpl.render(rxr=rx_rates, rate_line=rate_line)
86
+ return rendered
87
+
88
+
89
+ def eyring_rate(
90
+ barrier_height: float,
91
+ temperature: npt.ArrayLike,
92
+ trans_coeff: float = 1.0,
93
+ ) -> npt.NDArray[np.float64]:
94
+ """Rate constant in 1/s from the Eyring equation.
95
+
96
+ See https://pubs.acs.org/doi/10.1021/acs.organomet.8b00456
97
+ "Reaction Rates and Barriers" on p. 3234 and eq. (8).
98
+
99
+ Parameters
100
+ ----------
101
+ barrier_height
102
+ Barrier height (energy, enthalpy, gibbs energy, ...) in Hartree.
103
+ temperature
104
+ Temperature in Kelvin.
105
+ trans_coeff
106
+ Unitless transmission coefficient, e.g., obtained from Wigner or Eckart
107
+ correction.
108
+
109
+ Returns
110
+ -------
111
+ rate
112
+ Eyring reaction rate in 1/s.
113
+ """
114
+ temperature = np.array(temperature, dtype=float)
115
+ prefactor = trans_coeff * KB * temperature / PLANCK
116
+ rate = prefactor * np.exp(-barrier_height / (KBAU * temperature))
117
+ return rate
118
+
119
+
120
+ def harmonic_tst_rate(
121
+ barrier_height: float,
122
+ temperature: float,
123
+ rs_part_func: float,
124
+ ts_part_func: float,
125
+ trans_coeff: float = 1.0,
126
+ ) -> float:
127
+ """Rate constant in 1/s from harmonic TST.
128
+
129
+ See http://dx.doi.org/10.18419/opus-9841, chapter 5. Contrary to
130
+ the Eyring rate this function does only takes a scalar temperature as the
131
+ partition functions are also functions of the temperature and would
132
+ have to be recalculated for different temperatures.
133
+
134
+ A possible extension would be to also support multiple rs/ts partition functions,
135
+ one for each temperature.
136
+
137
+ Parameters
138
+ ----------
139
+ barrier_height
140
+ Barrier height (energy, enthalpy, gibbs energy, ...) in Hartree.
141
+ rs_part_func
142
+ Partition function of the reactant state.
143
+ ts_part_func
144
+ Partition function of the transition state.
145
+ temperature
146
+ Temperature in Kelvin.
147
+ trans_coeff
148
+ Unitless transmission coefficient, e.g., obtained from Wigner or Eckart
149
+ correction.
150
+
151
+ Returns
152
+ -------
153
+ rate
154
+ HTST reaction rate in 1/s.
155
+ """
156
+ rate_eyring = eyring_rate(barrier_height, temperature, trans_coeff)
157
+ rate = ts_part_func / rs_part_func * rate_eyring
158
+ return rate
159
+
160
+
161
+ def wigner_corr(
162
+ temperature: float,
163
+ imag_frequency: float,
164
+ ) -> float:
165
+ """Tunneling correction according to Wigner.
166
+
167
+ See https://doi.org/10.1002/qua.25686 eq. (12) and
168
+ https://doi.org/10.1007/978-3-642-59033-7_9 for the original publication.
169
+
170
+ Parameters
171
+ ----------
172
+ temperature
173
+ Temperature in Kelvin.
174
+ imag_frequency
175
+ Imaginary frequency in 1/s.
176
+
177
+ Returns
178
+ -------
179
+ kappa
180
+ Unitless tunneling correction according to Wigner.
181
+ """
182
+ kappa = 1 + 1 / 24 * (PLANCK * abs(imag_frequency) / (KB * temperature)) ** 2
183
+ return kappa
184
+
185
+
186
+ def bell_corr(
187
+ temperature: float,
188
+ imag_frequency: float,
189
+ ) -> float:
190
+ """Tunneling correction according to Bell.
191
+
192
+ See https://onlinelibrary.wiley.com/doi/10.1002/anie.201708489 eq. (1) and
193
+ eq. (2).
194
+
195
+ Parameters
196
+ ----------
197
+ temperature
198
+ Temperature in Kelvin.
199
+ imag_frequency
200
+ Imaginary frequency in 1/s.
201
+
202
+ Returns
203
+ -------
204
+ kappa
205
+ Unitless tunneling correction according to Bell. Negative kappas are
206
+ meaningless.
207
+ """
208
+ u = PLANCK * abs(imag_frequency) / (KB * temperature)
209
+ kappa = (u / 2) / sin(u / 2)
210
+ return kappa
211
+
212
+
213
+ def eckart_corr(
214
+ fw_barrier_height: float,
215
+ bw_barrier_height: float,
216
+ temperature: float,
217
+ imag_frequency: float,
218
+ ) -> float:
219
+ """Tunneling correction according to Eckart.
220
+
221
+ See [3], [4] and [5]. The correction should be independent of the order
222
+ fw_barrier_height/bw_barrier_height.
223
+
224
+ Parameters
225
+ ----------
226
+ fw_barrier_height
227
+ Barrier height in forward direction in Hartree.
228
+ bw_barrier_height
229
+ Barrier height in backward direction in Hartree.
230
+ temperature
231
+ Temperature in Kelvin.
232
+ imag_frequency
233
+ Frequency in 1/s of the imaginary mode at the TS.
234
+
235
+ Returns
236
+ -------
237
+ kappa
238
+ Unitless tunneling correction according to Eckart.
239
+ """
240
+ kBT = KBAU * temperature # Hartree
241
+ two_pi = 2 * np.pi
242
+
243
+ imag_frequency = abs(imag_frequency) * AU2SEC # Convert from 1/s to 1/au
244
+ hnu = PLANCKAU * imag_frequency
245
+ u = hnu / kBT # unitless
246
+ v1 = fw_barrier_height / kBT
247
+ v2 = bw_barrier_height / kBT
248
+ alpha1, alpha2 = [
249
+ two_pi * barrier / hnu for barrier in (fw_barrier_height, bw_barrier_height)
250
+ ]
251
+ quot = 1 / (1 / np.sqrt(alpha1) + 1 / np.sqrt(alpha2))
252
+ d = 1 / two_pi * np.sqrt(np.abs(4 * alpha1 * alpha2 - np.pi ** 2))
253
+ cosh_d = np.cosh(two_pi * np.abs(d))
254
+
255
+ def eps(E):
256
+ return (E - fw_barrier_height) / kBT
257
+
258
+ def ai(E, vi):
259
+ epsilon = eps(E)
260
+ return np.sqrt(2 * (epsilon + vi) / (np.pi * u)) * quot
261
+
262
+ def _a1(E):
263
+ return ai(E, vi=v1)
264
+
265
+ def _a2(E):
266
+ return ai(E, vi=v2)
267
+
268
+ def eckart_probability(E):
269
+ a1 = _a1(E)
270
+ a2 = _a2(E)
271
+ plus = np.cosh(two_pi * (a1 + a2))
272
+ minus = np.cosh(two_pi * (a1 - a2))
273
+ return (plus - minus) / (plus + cosh_d)
274
+
275
+ def eckart_func(E):
276
+ epsilon = eps(E)
277
+ P = eckart_probability(E)
278
+ return P * np.exp(-epsilon)
279
+
280
+ # Integration limits
281
+ E_low = max(0, fw_barrier_height - bw_barrier_height)
282
+ E_max = 1 # 1 Hartree max
283
+ kappa, _ = integrate.quad(eckart_func, E_low, E_max, limit=250)
284
+ # Make kappa unitless again by dividing by kBT, as we integrated over the energy.
285
+ kappa /= kBT
286
+ return kappa
287
+
288
+
289
+ def tunl(alph1: float, alph2: float, U: float, strict: bool = False):
290
+ """Eckart correction factor for rate constants according to Brown.
291
+
292
+ Python adaption of the TUNL subroutine in 4. Appendix of [5].
293
+
294
+ Parameters
295
+ ----------
296
+ alph1
297
+ Unitless barrier height descriptor. 2π V1 / (h nu*); see (2) in [5].
298
+ alph2
299
+ Unitless barrier heigth descriptor. 2π V2 / (h nu*); see (2) in [5].
300
+ u
301
+ Unitless curvature descriptor. h nu* / kT; see (2) in [5].
302
+ strict
303
+ If enabled, arguments are bound checked. Will raise AssertionError if
304
+ they are out of bonds. TUNL was found to yield accurate results when
305
+ the arguments are within bounds.
306
+
307
+ Returns
308
+ -------
309
+ G
310
+ Unitless tunneling correction according to Eckart.
311
+ """
312
+ if strict:
313
+ alphs = np.array((alph1, alph2))
314
+ assert (0.5 <= alphs).all() and (alphs <= 20).all() and 0 < U <= 16
315
+ if (alphs >= 8).all():
316
+ assert U <= 12
317
+ if (alphs >= 16).all():
318
+ assert U <= 10
319
+
320
+ # Quadrature arguments
321
+ x = np.array((-0.9324695, -0.6612094, -0.2386192, 0.2386192, 0.6612094, 0.9324695))
322
+ w = np.array((0.1713245, 0.3607616, 0.4679139, 0.4679139, 0.3607616, 0.1713245))
323
+
324
+ pi = np.pi
325
+ upi2 = U / (2 * pi)
326
+
327
+ C = 0.125 * pi * U * (1 / np.sqrt(alph1) + 1.0 / np.sqrt(alph2)) ** 2
328
+ v1 = upi2 * alph1
329
+ v2 = upi2 * alph2
330
+ D = 4 * alph1 * alph2 - pi ** 2
331
+ DF = np.cosh(np.sqrt(D)) if D >= 0 else np.cos(np.sqrt(-D))
332
+
333
+ ez = -v1 if (v2 >= v1) else -v2
334
+ eb = min(
335
+ C * (np.log(2.0 * (1.0 + DF) / 0.014) / (2 * pi)) ** 2 - (v1 + v2) / 2, 3.2
336
+ )
337
+ em = (eb - ez) / 2
338
+ ep = (eb + ez) / 2
339
+
340
+ # Original loop
341
+ E = em * x + ep
342
+ A1 = pi * np.sqrt((E + v1) / C)
343
+ A2 = pi * np.sqrt((E + v2) / C)
344
+ fm = np.cosh(A1 - A2)
345
+ fp = np.cosh(A1 + A2)
346
+ G = (w * np.exp(-E) * (fp - fm) / (fp + DF)).sum()
347
+ # Outside the loop
348
+ G = em * G + np.exp(-eb)
349
+ return G
350
+
351
+
352
+ def eckart_corr_brown(
353
+ fw_barrier_height: float,
354
+ bw_barrier_height: float,
355
+ temperature: float,
356
+ imag_frequency: float,
357
+ ) -> float:
358
+ """Tunneling correction according to Eckart.
359
+
360
+ Wrapper for the TUNL subroutine as given in the appendix of [5].
361
+
362
+ Parameters
363
+ ----------
364
+ fw_barrier_height
365
+ Barrier height in forward direction in Hartree.
366
+ bw_barrier_height
367
+ Barrier height in backward direction in Hartree.
368
+ temperature
369
+ Temperature in Kelvin.
370
+ imag_frequency
371
+ Frequency in 1/s of the imaginary mode at the TS.
372
+
373
+ Returns
374
+ -------
375
+ kappa
376
+ Unitless tunneling correction according to Eckart.
377
+ """
378
+ kBT = KBAU * temperature
379
+ hnu = PLANCKAU * imag_frequency * AU2SEC
380
+ u = hnu / kBT
381
+
382
+ def alpha(barr):
383
+ return 2 * np.pi * barr / hnu
384
+
385
+ alpha1 = alpha(fw_barrier_height)
386
+ alpha2 = alpha(bw_barrier_height)
387
+ kappa = tunl(alpha1, alpha2, u)
388
+ return kappa
389
+
390
+
391
+ def get_rates(temperature, reactant_thermos, ts_thermo, product_thermos=None):
392
+ G_TS = ts_thermo.G
393
+ imag_wavenumber = ts_thermo.org_wavenumbers[0]
394
+ assert imag_wavenumber < 0.0
395
+ imag_frequency = (
396
+ imag_wavenumber * C * 100
397
+ ) # Convert from wavenumbers (1/cm) to (1/s)
398
+ kappa_wigner = wigner_corr(temperature, imag_frequency)
399
+ kappa_bell = bell_corr(temperature, imag_frequency)
400
+
401
+ Gs_reactant = [thermo.G for thermo in reactant_thermos]
402
+ G_reactant = sum(Gs_reactant)
403
+ fw_barrier_height = G_TS - G_reactant
404
+ fw_rate_eyring = eyring_rate(fw_barrier_height, temperature)
405
+
406
+ if product_thermos:
407
+ Gs_product = [thermo.G for thermo in product_thermos]
408
+ G_product = sum(Gs_product)
409
+ bw_barrier_height = G_TS - G_product
410
+ bw_rate_eyring = eyring_rate(bw_barrier_height, temperature)
411
+ kappa_eckart = eckart_corr(
412
+ fw_barrier_height, bw_barrier_height, temperature, imag_frequency
413
+ )
414
+ else:
415
+ kappa_eckart = None
416
+
417
+ def make_rx_rates(from_, barrier, rate_eyring, kappa_eyring=1.0):
418
+ kwargs = {}
419
+ if kappa_wigner and not np.isnan(kappa_wigner):
420
+ kwargs.update(
421
+ {
422
+ "kappa_wigner": kappa_wigner,
423
+ "rate_wigner": kappa_wigner * rate_eyring,
424
+ }
425
+ )
426
+ if kappa_bell and kappa_bell > 0.0:
427
+ kwargs.update(
428
+ {
429
+ "kappa_bell": kappa_bell,
430
+ "rate_bell": kappa_bell * rate_eyring,
431
+ }
432
+ )
433
+ if kappa_eckart and not np.isnan(kappa_eckart):
434
+ kwargs.update(
435
+ {
436
+ "kappa_eckart": kappa_eckart,
437
+ "rate_eckart": kappa_eckart * rate_eyring,
438
+ }
439
+ )
440
+ rx_rates = ReactionRates(
441
+ from_=from_,
442
+ barrier=barrier,
443
+ barrier_si=barrier * AU2KJPERMOL,
444
+ temperature=temperature,
445
+ imag_wavenumber=imag_wavenumber,
446
+ imag_frequency=imag_frequency,
447
+ rate_eyring=rate_eyring,
448
+ kappa_eyring=kappa_eyring,
449
+ **kwargs,
450
+ )
451
+ return rx_rates
452
+
453
+ reactant_rx_rates = make_rx_rates("Reactant(s)", fw_barrier_height, fw_rate_eyring)
454
+ rx_rates = [
455
+ reactant_rx_rates,
456
+ ]
457
+ if product_thermos:
458
+ product_rx_rates = make_rx_rates(
459
+ "Product(s)", bw_barrier_height, bw_rate_eyring
460
+ )
461
+ rx_rates.append(product_rx_rates)
462
+ return rx_rates
463
+
464
+
465
+ def get_rates_for_geoms(temperature, reactant_geoms, ts_geom, product_geoms):
466
+ def get_thermos(geoms):
467
+ return [geom.get_thermoanalysis(T=temperature) for geom in geoms]
468
+
469
+ reactant_thermos = get_thermos(reactant_geoms)
470
+ ts_thermo = get_thermos((ts_geom,))[0]
471
+ product_thermos = get_thermos(product_geoms)
472
+ return get_rates(temperature, reactant_thermos, ts_thermo, product_thermos)
473
+
474
+
475
+ def get_rates_for_hessians(temperature, reactant_h5s, ts_h5, product_h5s):
476
+ reactant_geoms = [geom_from_hessian(h5) for h5 in reactant_h5s]
477
+ product_geoms = [geom_from_hessian(h5) for h5 in product_h5s]
478
+ ts_geom = geom_from_hessian(ts_h5)
479
+ rates = get_rates_for_geoms(temperature, reactant_geoms, ts_geom, product_geoms)
480
+ return rates
@@ -0,0 +1,219 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from pysisyphus import logger
5
+ from pysisyphus.calculators import XTB
6
+ from pysisyphus.calculators.OBabel import OBabel
7
+ from pysisyphus.config import LIB_DIR
8
+ from pysisyphus.Geometry import Geometry
9
+ from pysisyphus.helpers import geom_loader
10
+ from pysisyphus.helpers_pure import chunks
11
+ from pysisyphus.intcoords.setup import get_bond_sets
12
+ from pysisyphus.linalg import get_rot_mat_for_coords
13
+ from pysisyphus.optimizers.LBFGS import LBFGS
14
+
15
+
16
+ def get_bond_subgeom(geom, ind, invert=False):
17
+ bond_inds = get_bond_sets(geom.atoms, geom.coords3d)
18
+ # Determine bonds that are connected to atom 'ind' in 'geom'
19
+ conn_bond_inds = [bond for bond in bond_inds if ind in bond]
20
+ assert len(conn_bond_inds) == 1
21
+ ind0, ind1 = conn_bond_inds[0]
22
+ # Index of atom to which 'ind' is connected
23
+ anchor_ind = ind0 if (ind == ind1) else ind1
24
+ # conn_bond_ind = (ind0, ind1)
25
+ conn_bond_ind = (ind, anchor_ind) if invert else (anchor_ind, ind)
26
+ return geom.get_subgeom(conn_bond_ind), anchor_ind
27
+
28
+
29
+ def run_opt(geom, freeze_inds, ff="uff", use_xtb=False, charge=0, mult=1):
30
+ def get_xtb_calc():
31
+ return XTB(charge=charge, mult=mult, pal=2)
32
+
33
+ if not use_xtb:
34
+ try:
35
+ from openbabel import pybel
36
+
37
+ # Create pybel.Molecule/OBMol to set the missing bonds
38
+ mol = pybel.readstring("xyz", geom.as_xyz())
39
+ # Use modified pybel.Molecule
40
+ calc = OBabel(mol=mol, ff=ff)
41
+ except ImportError:
42
+ logger.warning("Could not import openbabel. Trying fallback to GFN2-xTB.")
43
+ calc = get_xtb_calc()
44
+ else:
45
+ calc = get_xtb_calc()
46
+
47
+ frozen_geom = geom.copy()
48
+ # Only some atoms
49
+ frozen_geom.freeze_atoms = freeze_inds
50
+ frozen_geom.set_calculator(calc)
51
+
52
+ opt = LBFGS(frozen_geom, max_cycles=1000, max_step=0.5, dump=False)
53
+ opt.run()
54
+
55
+ return frozen_geom
56
+
57
+
58
+ def replace_atom(
59
+ geom,
60
+ ind,
61
+ repl_geom,
62
+ repl_ind,
63
+ return_full=True,
64
+ opt=False,
65
+ use_xtb=False,
66
+ charge=0,
67
+ mult=1,
68
+ ):
69
+ """Replace atom with fragment."""
70
+
71
+ geom = geom.copy(coord_type="cart")
72
+ if len(repl_geom.atoms) == 1:
73
+ atoms = list(geom.atoms)
74
+ atoms[ind] = repl_geom.atoms[0]
75
+ coords3d = geom.coords3d
76
+ return Geometry(atoms, coords3d)
77
+
78
+ repl_geom = repl_geom.copy(coord_type="cart")
79
+
80
+ # Determine bond-subgeometries, which will be used to calculate a rotation
81
+ # matrix.
82
+ geom_sub, _ = get_bond_subgeom(geom, ind)
83
+ # Call with invert=True, as we want the bonds to point in opposite directions.
84
+ # Without invert=True, 'get_bond_subgeom' will always return with the atom order
85
+ # (ind, anchor_ind). This would lead to overlapping fragments.
86
+ repl_sub, repl_anchor = get_bond_subgeom(repl_geom, repl_ind, invert=True)
87
+
88
+ # Determine step that translates 'repl_geom' so that the atom 'ind' in 'geom'
89
+ # and 'anchor_ind' in 'repl_geom' coincide.
90
+ # step = geom.coords3d[ind] - repl_geom.coords3d[repl_anchor]
91
+ # repl_sub.coords3d += step[None, :]
92
+
93
+ # Determine rotation matrix that aligns 'repl_geom' on on 'geom_bond'
94
+ *_, rot_mat = get_rot_mat_for_coords(geom_sub.coords3d, repl_sub.coords3d)
95
+ # Rotate whole 'repl_geom' with 'rot_mat'
96
+ repl_c3d = repl_geom.coords3d
97
+ repl_centroid = repl_c3d.mean(axis=0)
98
+ repl_c3d_rot = (repl_c3d - repl_centroid[None, :]).dot(rot_mat) + repl_centroid[
99
+ None, :
100
+ ]
101
+ repl_geom.coords = repl_c3d_rot
102
+ # Translate repl_geom so that repl_anchor and ind coincide
103
+ step = geom.coords3d[ind] - repl_c3d_rot[repl_anchor]
104
+ repl_c3d_rot += step[None, :]
105
+ repl_geom.coords = repl_c3d_rot
106
+
107
+ # Create geometries without atoms that will be deleted/replaces
108
+ geom_ = geom.get_subgeom_without([ind])
109
+ repl_geom_ = repl_geom.get_subgeom_without([repl_ind])
110
+ # Union
111
+ union = geom_ + repl_geom_
112
+ if opt:
113
+ freeze_inds = [ind for ind, _ in enumerate(geom_.atoms)]
114
+ union = run_opt(union, freeze_inds, use_xtb=use_xtb, charge=charge, mult=mult)
115
+ # Update coords in 'repl_geom' with optimized coordinates
116
+ repl_geom_.coords3d = union.coords3d[len(freeze_inds) :]
117
+
118
+ # This is useful when multiple replacements are to be done. If multiple
119
+ # replacements would be done sequentially on the same geometry, while
120
+ # returning the union, would require taking into account altered atom ordering/
121
+ # numbering. By disabling return_full we can only return the replacement fragment,
122
+ # that can be added to the original geometry, after all replacement fragments
123
+ # have been calculated.
124
+ if return_full:
125
+ return_geom = union
126
+ else:
127
+ return_geom = repl_geom_
128
+ return return_geom
129
+
130
+
131
+ # Located in ../geom_library/replacements/
132
+ REPLACEMENTS = {
133
+ "Ph": ("benzene.xyz", 8),
134
+ "OMe": ("methanol.xyz", 4),
135
+ "OEt": ("ethanol.xyz", 8),
136
+ "OH": ("water.xyz", 1),
137
+ "F": ("hf.xyz", 1),
138
+ "Cl": ("hcl.xyz", 1),
139
+ "Br": ("hbr.xyz", 1),
140
+ }
141
+
142
+
143
+ def normalize_replacements(replacements):
144
+ replacements = list(replacements)
145
+ for i, repl in enumerate(replacements):
146
+ if isinstance(repl[1], str):
147
+ ind, repl_key = repl
148
+ repl_fn, repl_ind = REPLACEMENTS[repl_key]
149
+ repl_geom = geom_loader(LIB_DIR / "replacements" / repl_fn)
150
+ replacements[i] = (ind, repl_ind, repl_geom)
151
+ elif len(repl) == 3:
152
+ continue
153
+ else:
154
+ raise Exception("Invalid replacements!")
155
+ return replacements
156
+
157
+
158
+ def replace_atoms(geom, replacements, opt=False, use_xtb=False, charge=0, mult=1):
159
+ replacements = normalize_replacements(replacements)
160
+ repl_frags = list()
161
+ del_inds = list()
162
+ for ind, repl_ind, repl_geom in replacements:
163
+ repl_frag = replace_atom(geom, ind, repl_geom, repl_ind, False, opt=opt, use_xtb=use_xtb)
164
+ repl_frags.append(repl_frag)
165
+ del_inds.append(ind)
166
+ org_atom_num = len(geom.atoms)
167
+ union = geom.get_subgeom_without(del_inds)
168
+ for frag in repl_frags:
169
+ union += frag
170
+
171
+ if opt:
172
+ freeze_inds = [ind for ind in range(org_atom_num - len(del_inds))]
173
+ union = run_opt(union, freeze_inds, use_xtb=use_xtb)
174
+
175
+ return union
176
+
177
+
178
+ def parse_args(args):
179
+ parser = argparse.ArgumentParser()
180
+
181
+ parser.add_argument("geom_fn")
182
+ parser.add_argument("replacements", nargs="+")
183
+ parser.add_argument("--opt", action="store_true")
184
+ parser.add_argument("--jmol", action="store_true")
185
+ parser.add_argument("--out_fn", type=str, default="union.xyz")
186
+ parser.add_argument("--charge", type=int, default=0)
187
+ parser.add_argument("--mult", type=int, default=1)
188
+ parser.add_argument("--xtb", action="store_true")
189
+
190
+ return parser.parse_args(args)
191
+
192
+
193
+ def run():
194
+ args = parse_args(sys.argv[1:])
195
+ geom_fn = args.geom_fn
196
+ replacements_ = args.replacements
197
+ opt = args.opt
198
+ jmol = args.jmol
199
+ out_fn = args.out_fn
200
+ charge = args.charge
201
+ mult = args.mult
202
+ xtb = args.xtb
203
+
204
+ # Activate opt when xtb is given
205
+ opt = opt or xtb
206
+
207
+ assert len(replacements_) % 2 == 0
208
+ replacements = [(int(ind), key) for ind, key in chunks(replacements_, 2)]
209
+
210
+ geom = geom_loader(geom_fn)
211
+ union = replace_atoms(geom, replacements, opt=opt, charge=charge, mult=mult, use_xtb=xtb)
212
+ print(union.as_xyz())
213
+
214
+ if union:
215
+ with open(out_fn, "w") as handle:
216
+ handle.write(union.as_xyz())
217
+ print(f"Saved new geometry to '{out_fn}'.")
218
+ if jmol:
219
+ union.jmol()