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
mlmm/bond_changes.py ADDED
@@ -0,0 +1,231 @@
1
+ # bond_changes.py
2
+
3
+ """
4
+ bond_changes — Bond-change detection and reporting utilities
5
+ ====================================================================
6
+
7
+ Usage (API)
8
+ -----
9
+ from <package>.bond_changes import compare_structures, summarize_changes
10
+
11
+ Examples::
12
+ >>> result = compare_structures(geom_reactant, geom_product, device="cpu")
13
+ >>> print(summarize_changes(geom_product, result))
14
+ Bond formed (1)
15
+ C1-O2 1.50 Å --> 1.36 Å
16
+
17
+ Description
18
+ -----
19
+ This module compares two molecular geometries with identical atom types and ordering and reports covalent bonds that are formed
20
+ or broken between the structures. Bond perception uses element-specific covalent radii and configurable tolerances; distances are computed with PyTorch on CPU or CUDA.
21
+
22
+ Algorithm (core logic):
23
+ - Inputs: `geom1`, `geom2` with attributes `atoms: Iterable[str]` and `coords3d: (N, 3) float` (in Bohr in pysisyphus). Atoms must match exactly (`assert geom1.atoms == geom2.atoms`).
24
+ - Per-element radii: from `pysisyphus.elem_data.COVALENT_RADII`.
25
+ - Threshold per pair (i, j): `T_cov = bond_factor * (r_i + r_j)`; conservative margin `eps = margin_fraction * T_cov`.
26
+ - Bond adjacency in a geometry: `A = (D <= T_cov - eps)` evaluated only for `i < j` (upper triangle).
27
+ - Change gating: only pairs with `|D2 - D1| >= delta_fraction * T_cov` are considered.
28
+ - Classification:
29
+ - Formed: `(~A1) & A2 & need_change`
30
+ - Broken: `A1 & (~A2) & need_change`
31
+ - Distances: pairwise matrices `D1`, `D2` via `torch.cdist`.
32
+
33
+ Public API:
34
+ - `compare_structures(geom1, geom2, device='cuda', bond_factor=1.20, margin_fraction=0.05, delta_fraction=0.05) -> BondChangeResult`
35
+ Detects formed and broken covalent bonds. Returns sets of zero-based index pairs and (by default) the full distance matrices (`numpy.ndarray`) in the same units as the inputs (Bohr in pysisyphus).
36
+ - `summarize_changes(geom, result, one_based: bool = True) -> str`
37
+ Builds a human-readable report:
38
+ - Sections: “Bond formed” and “Bond broken” (with counts).
39
+ - Lines formatted as `ElemI-ElemJ` with atom indices (1-based by default, e.g., `C1-O2`).
40
+ - If `result.distances_1/2` are present, prints bond lengths as `D1 Å --> D2 Å`, converting from Bohr using `pysisyphus.constants.BOHR2ANG`.
41
+ - Helper utilities (internal):
42
+ - `_resolve_device(device: str) -> torch.device`: chooses the requested device; falls back to CPU with a warning if unavailable.
43
+ - `_element_arrays(atoms) -> (elems, cov_radii)`: normalizes element symbols and looks up covalent radii.
44
+ - `_upper_pairs_from_mask(mask) -> Set[Tuple[int, int]]`: returns index pairs where `mask` is True (assumes an upper-triangular mask).
45
+ - `_bond_str(i, j, elems, one_based=True) -> str`: formats `ElemI-ElemJ` labels.
46
+ - Data container:
47
+ - `BondChangeResult`:
48
+ - `formed_covalent: Set[Tuple[int, int]]` — zero-based pairs for bonds formed.
49
+ - `broken_covalent: Set[Tuple[int, int]]` — zero-based pairs for bonds broken.
50
+ - `distances_1: Optional[np.ndarray]`, `distances_2: Optional[np.ndarray]` — square distance matrices (shape N×N).
51
+
52
+ Outputs (& Directory Layout)
53
+ -----
54
+ - No files or directories are created.
55
+ - `compare_structures` returns a `BondChangeResult` as described above.
56
+ - `summarize_changes` returns a multi-line string; typical headings:
57
+ - `Bond formed (k):` followed by `ElemI-ElemJ : D1 Å --> D2 Å` (if distances available).
58
+ - `Bond broken (m):` followed by lines in the same format.
59
+ - If a set is empty, the section reads `None`.
60
+
61
+ Notes:
62
+ -----
63
+ - Units: In pysisyphus, `coords3d` are Bohr; the summary converts to Å with `BOHR2ANG`. If your inputs use different units, adjust accordingly.
64
+ - Atom identity & ordering must be identical between structures; otherwise the comparison is invalid.
65
+ - Only unique pairs with `i < j` are considered (upper triangle); indices in results are zero-based.
66
+ - The three tolerances control sensitivity:
67
+ - `bond_factor` (default 1.20): global scaling of the covalent radii sum.
68
+ - `margin_fraction` (default 0.05): conservative shrinkage of the bond cutoff to avoid borderline matches.
69
+ - `delta_fraction` (default 0.05): minimum relative distance change required to count a bond event.
70
+ - Device selection: pass `'cpu'`, `'cuda'`, or `'cuda:0'` etc. If the requested device is not available or invalid, the code falls back to CPU and issues a `RuntimeWarning`. Computations use `float64` for stability and run under `torch.no_grad()`.
71
+ - The method detects binary bond formation/breakage; it does not estimate bond orders, angles, or multi-center bonding, and it ignores periodic boundary conditions.
72
+ - Numerical caveats: near-threshold pairs may toggle with small geometry noise; tune tolerances to your system size and sampling noise.
73
+ """
74
+
75
+ from __future__ import annotations
76
+ from dataclasses import dataclass
77
+ from typing import Iterable, Tuple, Set, List, Optional
78
+
79
+ import warnings
80
+ import torch
81
+ import numpy as np
82
+
83
+ from pysisyphus.elem_data import (
84
+ COVALENT_RADII as CR,
85
+ )
86
+ from pysisyphus.constants import BOHR2ANG # Convert Bohr distances to Ångström for reporting
87
+
88
+ Pair = Tuple[int, int]
89
+
90
+
91
+ @dataclass
92
+ class BondChangeResult:
93
+ formed_covalent: Set[Pair]
94
+ broken_covalent: Set[Pair]
95
+ distances_1: Optional[np.ndarray] = None
96
+ distances_2: Optional[np.ndarray] = None
97
+
98
+
99
+ def _upper_pairs_from_mask(mask: torch.Tensor) -> Set[Pair]:
100
+ idx = torch.nonzero(mask, as_tuple=False).detach().cpu().numpy()
101
+ return set(map(tuple, idx))
102
+
103
+
104
+ def _element_arrays(atoms: Iterable[str]) -> Tuple[List[str], np.ndarray]:
105
+ elems = [a.capitalize() for a in atoms]
106
+ cov = np.array([CR[a.lower()] for a in elems], dtype=float)
107
+ return elems, cov
108
+
109
+
110
+ def _resolve_device(device: str) -> torch.device:
111
+ dev_str = (device or "cpu").lower()
112
+ if dev_str.startswith("cuda"):
113
+ if torch.cuda.is_available():
114
+ try:
115
+ _ = torch.device(dev_str)
116
+ return torch.device(dev_str)
117
+ except Exception:
118
+ warnings.warn(
119
+ f"Requested device '{device}' is not available. Falling back to CPU.",
120
+ RuntimeWarning,
121
+ )
122
+ return torch.device("cpu")
123
+ else:
124
+ warnings.warn(
125
+ "CUDA is not available. Falling back to CPU.",
126
+ RuntimeWarning,
127
+ )
128
+ return torch.device("cpu")
129
+ try:
130
+ return torch.device(dev_str)
131
+ except Exception:
132
+ warnings.warn(
133
+ f"Requested device '{device}' is not recognized. Falling back to CPU.",
134
+ RuntimeWarning,
135
+ )
136
+ return torch.device("cpu")
137
+
138
+
139
+ @torch.no_grad()
140
+ def compare_structures(
141
+ geom1,
142
+ geom2,
143
+ device: str = "cuda",
144
+ bond_factor: float = 1.20,
145
+ margin_fraction: float = 0.05,
146
+ delta_fraction: float = 0.05,
147
+ ) -> BondChangeResult:
148
+
149
+ assert geom1.atoms == geom2.atoms, "Atom types and ordering must be identical."
150
+ N = len(geom1.atoms)
151
+
152
+ elems, cov_np = _element_arrays(geom1.atoms)
153
+ dev = _resolve_device(device)
154
+
155
+ dtype = torch.float64
156
+ R1 = torch.as_tensor(geom1.coords3d, dtype=dtype, device=dev)
157
+ R2 = torch.as_tensor(geom2.coords3d, dtype=dtype, device=dev)
158
+ cov = torch.as_tensor(cov_np, dtype=dtype, device=dev)
159
+
160
+ T_cov = bond_factor * (cov[:, None] + cov[None, :])
161
+ eps_cov = margin_fraction * T_cov
162
+
163
+ D1 = torch.cdist(R1, R1)
164
+ D2 = torch.cdist(R2, R2)
165
+
166
+ up = torch.triu(torch.ones((N, N), dtype=torch.bool, device=dev), diagonal=1)
167
+
168
+ A1 = (D1 <= (T_cov - eps_cov)) & up
169
+ A2 = (D2 <= (T_cov - eps_cov)) & up
170
+
171
+ dD = D2 - D1
172
+ need_change = (dD.abs() >= (delta_fraction * T_cov)) & up
173
+
174
+ formed_cov_mask = (~A1) & A2 & need_change
175
+ broken_cov_mask = A1 & (~A2) & need_change
176
+
177
+ formed_covalent = _upper_pairs_from_mask(formed_cov_mask)
178
+ broken_covalent = _upper_pairs_from_mask(broken_cov_mask)
179
+
180
+ return BondChangeResult(
181
+ formed_covalent=formed_covalent,
182
+ broken_covalent=broken_covalent,
183
+ distances_1=D1.detach().cpu().numpy(),
184
+ distances_2=D2.detach().cpu().numpy(),
185
+ )
186
+
187
+
188
+ def _bond_str(i: int, j: int, elems: List[str], one_based: bool = True) -> str:
189
+ ii = i + 1 if one_based else i
190
+ jj = j + 1 if one_based else j
191
+ return f"{elems[i]}{ii}-{elems[j]}{jj}"
192
+
193
+
194
+ def summarize_changes(geom, result: BondChangeResult, one_based: bool = True) -> str:
195
+ """
196
+ List bond formations and dissociations and report bond-length changes in Å.
197
+ """
198
+ elems = [a.capitalize() for a in geom.atoms]
199
+ lines: List[str] = []
200
+
201
+ # Use distance matrices (Bohr) converted to Å when available
202
+ D1 = result.distances_1
203
+ D2 = result.distances_2
204
+ have_lengths = (
205
+ isinstance(D1, np.ndarray)
206
+ and isinstance(D2, np.ndarray)
207
+ and D1.shape == D2.shape
208
+ )
209
+
210
+ def _len_str(i: int, j: int) -> str:
211
+ if not have_lengths:
212
+ return ""
213
+ # ``coords3d`` is given in Bohr; convert to Å
214
+ d1 = float(D1[i, j]) * BOHR2ANG
215
+ d2 = float(D2[i, j]) * BOHR2ANG
216
+ return f" : {d1:.3f} Å --> {d2:.3f} Å"
217
+
218
+ def pairs_to_lines(title: str, pairs: Set[Pair]):
219
+ if not pairs:
220
+ lines.append(f"{title}: None")
221
+ return
222
+ lines.append(f"{title} ({len(pairs)}):")
223
+ for i, j in sorted(pairs):
224
+ lines.append(f" - {_bond_str(i, j, elems, one_based)}{_len_str(i, j)}")
225
+
226
+ pairs_to_lines("Bond formed", result.formed_covalent)
227
+ pairs_to_lines("Bond broken", result.broken_covalent)
228
+
229
+ return "\n".join(lines)
230
+
231
+
mlmm/bool_compat.py ADDED
@@ -0,0 +1,223 @@
1
+ """CLI bool-argument normalization helpers.
2
+
3
+ This module keeps backward compatibility for legacy value-style boolean flags:
4
+ `--flag True/False`.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+
10
+ _BOOL_TRUE_LITERALS = {"1", "true", "t", "yes", "y", "on"}
11
+ _BOOL_FALSE_LITERALS = {"0", "false", "f", "no", "n", "off"}
12
+
13
+
14
+ def _parse_bool_literal(raw: str) -> bool | None:
15
+ token = raw.strip().lower()
16
+ if token in _BOOL_TRUE_LITERALS:
17
+ return True
18
+ if token in _BOOL_FALSE_LITERALS:
19
+ return False
20
+ return None
21
+
22
+
23
+ def _toggle_negative_name(
24
+ command: str,
25
+ positive_name: str,
26
+ toggle_negative_aliases: dict[str, dict[str, str]],
27
+ ) -> str:
28
+ aliases = toggle_negative_aliases.get(command)
29
+ if aliases and positive_name in aliases:
30
+ return aliases[positive_name]
31
+ return f"--no-{positive_name[2:]}"
32
+
33
+
34
+ def _read_bool_literal(
35
+ args: list[str], index: int, sep: str, inline_value: str
36
+ ) -> tuple[bool | None, int, bool]:
37
+ """Return (parsed_bool, next_index, explicit_literal_used)."""
38
+ if sep:
39
+ return _parse_bool_literal(inline_value), index + 1, True
40
+ if index + 1 < len(args):
41
+ parsed_next = _parse_bool_literal(args[index + 1])
42
+ if parsed_next is not None:
43
+ return parsed_next, index + 2, True
44
+ return None, index + 1, False
45
+
46
+
47
+ def _build_negative_to_positive_toggle_map(
48
+ toggle_options: frozenset[str],
49
+ toggle_negative_aliases: dict[str, dict[str, str]],
50
+ command: str,
51
+ ) -> dict[str, str]:
52
+ negative_to_positive: dict[str, str] = {}
53
+ for positive_name in toggle_options:
54
+ if not positive_name.startswith("--"):
55
+ continue
56
+ canonical_negative = _toggle_negative_name(
57
+ command, positive_name, toggle_negative_aliases
58
+ )
59
+ negative_to_positive[canonical_negative] = positive_name
60
+ synthetic_no_name = f"--no-{positive_name[2:]}"
61
+ negative_to_positive.setdefault(synthetic_no_name, positive_name)
62
+ return negative_to_positive
63
+
64
+
65
+ def _build_negative_to_positive_single_flag_map(
66
+ single_flag_options: frozenset[str],
67
+ ) -> dict[str, str]:
68
+ negative_to_positive: dict[str, str] = {}
69
+ for positive_name in single_flag_options:
70
+ if not positive_name.startswith("--"):
71
+ continue
72
+ negative_to_positive[f"--no-{positive_name[2:]}"] = positive_name
73
+ return negative_to_positive
74
+
75
+
76
+ def normalize_bool_argv(
77
+ args: list[str],
78
+ bool_value_options_by_command: dict[str, frozenset[str]],
79
+ bool_toggle_options_by_command: dict[str, frozenset[str]],
80
+ toggle_negative_aliases: dict[str, dict[str, str]],
81
+ bool_single_flag_options_by_command: dict[str, frozenset[str]] | None = None,
82
+ ) -> tuple[list[str], bool]:
83
+ """Normalize CLI argv booleans and return (normalized_args, legacy_syntax_used)."""
84
+ if not args:
85
+ return args, False
86
+
87
+ command = args[0]
88
+ bool_value_options = bool_value_options_by_command.get(command, frozenset())
89
+ bool_toggle_options = bool_toggle_options_by_command.get(command, frozenset())
90
+ bool_single_flag_options = (
91
+ bool_single_flag_options_by_command or {}
92
+ ).get(command, frozenset())
93
+
94
+ if not bool_value_options and not bool_toggle_options and not bool_single_flag_options:
95
+ return args, False
96
+
97
+ toggle_negative_to_positive = _build_negative_to_positive_toggle_map(
98
+ bool_toggle_options, toggle_negative_aliases, command
99
+ )
100
+ single_negative_to_positive = _build_negative_to_positive_single_flag_map(
101
+ bool_single_flag_options
102
+ )
103
+
104
+ normalized: list[str] = [command]
105
+ legacy_used = False
106
+ i = 1
107
+ while i < len(args):
108
+ token = args[i]
109
+
110
+ if token == "--":
111
+ normalized.extend(args[i:])
112
+ break
113
+
114
+ if not token.startswith("--"):
115
+ normalized.append(token)
116
+ i += 1
117
+ continue
118
+
119
+ name, sep, inline_value = token.partition("=")
120
+
121
+ if name in toggle_negative_to_positive:
122
+ positive_name = toggle_negative_to_positive[name]
123
+ canonical_negative = _toggle_negative_name(
124
+ command, positive_name, toggle_negative_aliases
125
+ )
126
+ parsed, next_i, explicit_literal = _read_bool_literal(
127
+ args, i, sep, inline_value
128
+ )
129
+ if explicit_literal:
130
+ if parsed is None:
131
+ normalized.append(token)
132
+ else:
133
+ legacy_used = True
134
+ normalized.append(canonical_negative if parsed else positive_name)
135
+ i = next_i
136
+ continue
137
+
138
+ normalized.append(canonical_negative)
139
+ i += 1
140
+ continue
141
+
142
+ if name in single_negative_to_positive:
143
+ positive_name = single_negative_to_positive[name]
144
+ parsed, next_i, explicit_literal = _read_bool_literal(
145
+ args, i, sep, inline_value
146
+ )
147
+ if explicit_literal:
148
+ if parsed is None:
149
+ normalized.append(token)
150
+ else:
151
+ legacy_used = True
152
+ if not parsed:
153
+ normalized.append(positive_name)
154
+ i = next_i
155
+ continue
156
+
157
+ i += 1
158
+ continue
159
+
160
+ if name.startswith("--no-"):
161
+ positive_name = "--" + name[5:]
162
+ if sep == "" and positive_name in bool_value_options:
163
+ normalized.extend([positive_name, "False"])
164
+ i += 1
165
+ continue
166
+ normalized.append(token)
167
+ i += 1
168
+ continue
169
+
170
+ if name in bool_value_options:
171
+ parsed, next_i, explicit_literal = _read_bool_literal(args, i, sep, inline_value)
172
+ if explicit_literal:
173
+ if parsed is not None:
174
+ legacy_used = True
175
+ normalized.extend([name, "True" if parsed else "False"])
176
+ else:
177
+ normalized.append(token)
178
+ i = next_i
179
+ continue
180
+
181
+ normalized.extend([name, "True"])
182
+ i += 1
183
+ continue
184
+
185
+ if name in bool_toggle_options:
186
+ parsed, next_i, explicit_literal = _read_bool_literal(args, i, sep, inline_value)
187
+ if explicit_literal:
188
+ if parsed is not None:
189
+ legacy_used = True
190
+ normalized.append(
191
+ name if parsed else _toggle_negative_name(
192
+ command, name, toggle_negative_aliases
193
+ )
194
+ )
195
+ else:
196
+ normalized.append(token)
197
+ i = next_i
198
+ continue
199
+
200
+ normalized.append(name)
201
+ i += 1
202
+ continue
203
+
204
+ if name in bool_single_flag_options:
205
+ parsed, next_i, explicit_literal = _read_bool_literal(args, i, sep, inline_value)
206
+ if explicit_literal:
207
+ if parsed is not None:
208
+ legacy_used = True
209
+ if parsed:
210
+ normalized.append(name)
211
+ else:
212
+ normalized.append(token)
213
+ i = next_i
214
+ continue
215
+
216
+ normalized.append(name)
217
+ i += 1
218
+ continue
219
+
220
+ normalized.append(token)
221
+ i += 1
222
+
223
+ return normalized, legacy_used