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/__init__.py ADDED
@@ -0,0 +1,36 @@
1
+ # mlmm/__init__.py
2
+
3
+ try:
4
+ from mlmm._version import __version__, __version_tuple__
5
+ except ImportError:
6
+ __version__ = "0.0.0.dev0"
7
+ __version_tuple__ = (0, 0, 0, "dev0")
8
+
9
+ __all__ = [
10
+ "__version__",
11
+ "__version_tuple__",
12
+ "MLMMCore",
13
+ "MLMMASECalculator",
14
+ "mlmm",
15
+ "mlmm_ase",
16
+ "mlmm_mm_only",
17
+ ]
18
+
19
+ _LAZY_IMPORTS = {
20
+ "MLMMCore": "mlmm.mlmm_calc",
21
+ "MLMMASECalculator": "mlmm.mlmm_calc",
22
+ "mlmm": "mlmm.mlmm_calc",
23
+ "mlmm_ase": "mlmm.mlmm_calc",
24
+ "mlmm_mm_only": "mlmm.mlmm_calc",
25
+ }
26
+
27
+
28
+ def __getattr__(name: str):
29
+ if name in _LAZY_IMPORTS:
30
+ import importlib
31
+
32
+ module = importlib.import_module(_LAZY_IMPORTS[name])
33
+ value = getattr(module, name)
34
+ globals()[name] = value
35
+ return value
36
+ raise AttributeError(f"module 'mlmm' has no attribute {name!r}")
mlmm/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Module entrypoint for `python -m mlmm`."""
2
+
3
+ from .cli import cli
4
+
5
+
6
+ if __name__ == "__main__":
7
+ cli()
mlmm/_version.py ADDED
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.2.2.dev0'
32
+ __version_tuple__ = version_tuple = (0, 2, 2, 'dev0')
33
+
34
+ __commit_id__ = commit_id = None
mlmm/add_elem_info.py ADDED
@@ -0,0 +1,374 @@
1
+ # mlmm/add_elem_info.py
2
+
3
+ """
4
+ Add/repair PDB element symbols (columns 77-78) using Biopython inference.
5
+
6
+ Example:
7
+ mlmm add-elem-info -i input.pdb -o fixed.pdb
8
+
9
+ For detailed documentation, see: docs/add_elem_info.md
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import collections
16
+ import os
17
+ import re
18
+ import sys
19
+ from pathlib import Path
20
+ from typing import Optional, Set
21
+
22
+ import click
23
+ from Bio.PDB import PDBParser, PDBIO
24
+
25
+ # Reuse residue/ion dictionaries from extract.py to keep definitions in sync
26
+ from .extract import AMINO_ACIDS, ION, WATER_RES
27
+
28
+ # -----------------------------
29
+ # Element symbols (IUPAC, 1–118)
30
+ # -----------------------------
31
+ ELEMENTS: Set[str] = {
32
+ "H","He","Li","Be","B","C","N","O","F","Ne","Na","Mg","Al","Si","P","S","Cl","Ar",
33
+ "K","Ca","Sc","Ti","V","Cr","Mn","Fe","Co","Ni","Cu","Zn","Ga","Ge","As","Se","Br","Kr",
34
+ "Rb","Sr","Y","Zr","Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn","Sb","Te","I","Xe",
35
+ "Cs","Ba","La","Ce","Pr","Nd","Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb","Lu",
36
+ "Hf","Ta","W","Re","Os","Ir","Pt","Au","Hg","Tl","Pb","Bi","Po","At","Rn","Fr","Ra",
37
+ "Ac","Th","Pa","U","Np","Pu","Am","Cm","Bk","Cf","Es","Fm","Md","No","Lr","Rf","Db",
38
+ "Sg","Bh","Hs","Mt","Ds","Rg","Cn","Fl","Lv","Ts","Og"
39
+ }
40
+
41
+ # Common residue classes
42
+ PROTEIN_RES = set(AMINO_ACIDS.keys())
43
+ NUCLEIC_RES = {
44
+ # DNA/RNA (minimum set)
45
+ "DA","DT","DG","DC","DI",
46
+ "A","U","G","C","I",
47
+ }
48
+
49
+ # -----------------------------
50
+ # Helper: normalize strings to element symbols
51
+ # -----------------------------
52
+ _re_letters = re.compile(r"[A-Za-z]+")
53
+
54
+ def _normalize_symbol(s: str) -> Optional[str]:
55
+ """Remove non-letters; prefer a 2-letter match, then 1-letter, against known elements.
56
+ Returns the correctly cased symbol if matched.
57
+ Treat deuterium 'D' as hydrogen 'H' (PDB often uses D interchangeably with H).
58
+ """
59
+ if not s:
60
+ return None
61
+ m = _re_letters.findall(s)
62
+ if not m:
63
+ return None
64
+ letters = "".join(m)
65
+ if len(letters) >= 2:
66
+ cand2 = (letters[:2][0].upper() + letters[:2][1].lower())
67
+ if cand2 in ELEMENTS:
68
+ return cand2
69
+ cand1 = letters[0].upper()
70
+ if cand1 in ELEMENTS:
71
+ return cand1
72
+ # Deuterium -> Hydrogen fallback
73
+ if letters[0].upper() == "D":
74
+ return "H"
75
+ return None
76
+
77
+ def _symbol_from_resname(resname: str) -> Optional[str]:
78
+ """
79
+ Extract an element symbol from an ion residue name (e.g., CA, FE2, Cl-, YB2, IOD).
80
+ """
81
+ res = resname.strip()
82
+ sym = _normalize_symbol(res)
83
+ if sym is None and res.upper().startswith("IOD"):
84
+ sym = "I"
85
+ return sym
86
+
87
+ # -----------------------------
88
+ # Element inference (use residue to disambiguate)
89
+ # -----------------------------
90
+ def guess_element(atom_name: str, resname: str, is_het: bool) -> Optional[str]:
91
+ """
92
+ Infer the element from atom name + residue name.
93
+ Priority:
94
+ 1) Ion residues: prefer the residue name (NH4 / H3O+ handled per-atom as H/N/O)
95
+ 2) Polymers (protein/nucleic acid) and water: follow convention (H/C/N/O/S/P/Se)
96
+ - e.g., CA = Carbon (Cα), HG = Hydrogen, etc.
97
+ 3) Other ligands: use atom-name prefix; prioritize Carbon for C* (except CL) and P for P*
98
+ 4) Fallback to 2-letter then 1-letter normalization; return None if still ambiguous
99
+ """
100
+ name_u = atom_name.strip().upper()
101
+ res_u = resname.strip().upper()
102
+
103
+ # 1) Ion residues — strongly prefer the residue-derived element
104
+ if res_u in {k.upper() for k in ION.keys()}:
105
+ # Polyatomic ions (NH4, H3O+, …): decide per atom name (treat D* as H)
106
+ if name_u.startswith(("H", "D")):
107
+ return "H"
108
+ if name_u.startswith("N"):
109
+ return "N"
110
+ if name_u.startswith("O"):
111
+ return "O"
112
+ # Monatomic metals/halogens: from residue name
113
+ sym = _symbol_from_resname(res_u)
114
+ if sym:
115
+ return sym
116
+ # If residue is atypical, allow atom-name halogens (CL/BR/I/F)
117
+ if name_u.startswith("CL"):
118
+ return "Cl"
119
+ if name_u.startswith("BR"):
120
+ return "Br"
121
+ if name_u.startswith("I"):
122
+ return "I"
123
+ if name_u.startswith("F"):
124
+ return "F"
125
+
126
+ # 2) Polymers (protein/nucleic) / water
127
+ is_protein = res_u in PROTEIN_RES
128
+ is_nucl = res_u in NUCLEIC_RES
129
+ is_water = res_u in WATER_RES
130
+ if is_protein or is_nucl or is_water:
131
+ # Water: only O and H (treat D* as H)
132
+ if is_water:
133
+ if name_u.startswith(("H", "D")):
134
+ return "H"
135
+ return "O"
136
+
137
+ # Hydrogen (including D*)
138
+ if name_u.startswith(("H", "D")):
139
+ return "H"
140
+
141
+ # Selenium (e.g., selenomethionine/selenocysteine)
142
+ if name_u.startswith("SE"):
143
+ return "Se"
144
+
145
+ # P, N, O, S map directly by first letter
146
+ if name_u.startswith("P"):
147
+ return "P"
148
+ if name_u.startswith("N"):
149
+ return "N"
150
+ if name_u.startswith("O"):
151
+ return "O"
152
+ if name_u.startswith("S"):
153
+ return "S"
154
+
155
+ # Carbon for Cα/sidechain labels (CA, CB, CG, CD, CE, CZ, CH*, etc.)
156
+ if name_u.startswith("C"):
157
+ return "C"
158
+
159
+ # Rare halogens in polymers: final fallback to normalization
160
+ sym = _normalize_symbol(name_u)
161
+ if sym:
162
+ return sym
163
+
164
+ # 3) Non-polymers (ligands / cofactors)
165
+ # Hydrogen (including D*) for ligands/cofactors
166
+ if name_u.startswith(("H", "D")):
167
+ return "H"
168
+ # Carbon/Phosphorus-like labels (C*, P*) -> C/P (exclude CL)
169
+ if name_u.startswith("C") and not name_u.startswith("CL"):
170
+ return "C"
171
+ if name_u.startswith("P"):
172
+ return "P"
173
+
174
+ # Metals and halogens often appear as the atom name (FE, ZN, MG, HG, CL, BR, I, F ...)
175
+ sym = _normalize_symbol(name_u)
176
+ if sym:
177
+ return sym
178
+
179
+ # 4) Unresolved
180
+ return None
181
+
182
+ # -----------------------------
183
+ # Detect whether the input originally had element fields,
184
+ # keyed by atom serial number (columns 7–11)
185
+ # -----------------------------
186
+ def scan_existing_elements_by_serial(pdb_path: str) -> Set[int]:
187
+ """
188
+ Scan the raw PDB lines and return the serial numbers of ATOM/HETATM records whose
189
+ element field (columns 77–78) was non-empty in the original file.
190
+ This avoids Biopython side effects and reflects the true presence/absence in the input.
191
+ """
192
+ serials_with_elem: Set[int] = set()
193
+ try:
194
+ with open(pdb_path, "r", encoding="utf-8", errors="ignore") as fh:
195
+ for line in fh:
196
+ if not (line.startswith("ATOM") or line.startswith("HETATM")):
197
+ continue
198
+ if len(line) < 78:
199
+ # No element field present
200
+ continue
201
+ serial_str = line[6:11].strip()
202
+ elem_raw = line[76:78].strip()
203
+ if not serial_str:
204
+ continue
205
+ try:
206
+ serial = int(serial_str)
207
+ except ValueError:
208
+ continue
209
+ # If non-empty, consider that the original file had an element entry
210
+ # (keep isotopic labels like D as-is)
211
+ if elem_raw:
212
+ serials_with_elem.add(serial)
213
+ except Exception:
214
+ # If the file can't be read, return empty (treat all as unset)
215
+ pass
216
+ return serials_with_elem
217
+
218
+ def _get_atom_serial(atom) -> Optional[int]:
219
+ """
220
+ Safely obtain the serial number from a Biopython Atom, handling version differences.
221
+ """
222
+ sn = getattr(atom, "serial_number", None)
223
+ if sn is None and hasattr(atom, "get_serial_number"):
224
+ try:
225
+ sn = atom.get_serial_number()
226
+ except Exception:
227
+ sn = None
228
+ return sn
229
+
230
+ # -----------------------------
231
+ # Main processing
232
+ # -----------------------------
233
+ def assign_elements(in_pdb: str, out_pdb: Optional[str], overwrite: bool = False) -> None:
234
+ # Scan the input file for the original presence of element fields
235
+ existing_by_serial = scan_existing_elements_by_serial(in_pdb)
236
+
237
+ parser = PDBParser(QUIET=True)
238
+ structure_id = os.path.splitext(os.path.basename(in_pdb))[0]
239
+ structure = parser.get_structure(structure_id, in_pdb)
240
+
241
+ total = 0
242
+ assigned_new = 0 # newly set for atoms that lacked an element field
243
+ overwritten = 0 # element existed originally but was re-inferred due to --overwrite
244
+ kept_existing = 0 # element existed originally and was preserved (no --overwrite)
245
+ unknown = [] # could not infer (left unchanged)
246
+
247
+ by_element = collections.Counter()
248
+
249
+ for model in structure:
250
+ for chain in model:
251
+ for residue in chain:
252
+ hetflag = residue.id[0].strip() # '' (empty) = standard; 'W' = water; 'H_' = HETATM
253
+ is_het = (hetflag != "")
254
+ resname = residue.get_resname()
255
+ for atom in residue:
256
+ total += 1
257
+ name = atom.get_name()
258
+
259
+ serial = _get_atom_serial(atom)
260
+ had_element_in_input = (serial in existing_by_serial) if serial is not None else False
261
+
262
+ if had_element_in_input and not overwrite:
263
+ kept_existing += 1
264
+ continue # Respect existing element: do not modify without --overwrite
265
+
266
+ sym = guess_element(name, resname, is_het)
267
+ if sym is None:
268
+ unknown.append((model.id, chain.id, residue.id, resname, name, serial))
269
+ # If inference failed: keep the previous value (if any), otherwise leave unset
270
+ continue
271
+
272
+ # Biopython uses atom.element to populate columns 77–78 on output
273
+ prev = getattr(atom, "element", None)
274
+ atom.element = sym
275
+ by_element[sym] += 1
276
+ if had_element_in_input:
277
+ if prev != sym:
278
+ overwritten += 1
279
+ else:
280
+ assigned_new += 1
281
+
282
+ io = PDBIO()
283
+ io.set_structure(structure)
284
+ out_path = out_pdb if out_pdb else in_pdb # overwrite input if not specified
285
+ io.save(out_path)
286
+
287
+ # Summary
288
+ click.echo(f"[add-elem-info] Wrote: {out_path}")
289
+ click.echo(f" total atoms : {total}")
290
+ click.echo(f" newly assigned : {assigned_new}")
291
+ click.echo(f" kept existing (no overwrite): {kept_existing}")
292
+ click.echo(f" overwritten (--overwrite) : {overwritten}")
293
+ if by_element:
294
+ top = ", ".join(f"{k}:{v}" for k, v in by_element.most_common())
295
+ click.echo(f" assignment breakdown : {top}")
296
+ if unknown:
297
+ click.echo(f"[add-elem-info] WARNING: Could not confidently assign {len(unknown)} atoms; left unchanged.")
298
+ for (mid, chid, resid, resn, aname, serial) in unknown[:50]:
299
+ if isinstance(resid, tuple):
300
+ resseq = resid[1]
301
+ icode = resid[2].strip()
302
+ else:
303
+ resseq, icode = "?", ""
304
+ s_str = f" serial {serial}" if serial is not None else ""
305
+ click.echo(f" model {mid} chain {chid} {resn} {resseq}{icode} : {aname}{s_str}")
306
+ if len(unknown) > 50:
307
+ click.echo(" ... (truncated) ...")
308
+
309
+ def main():
310
+ ap = argparse.ArgumentParser(
311
+ description="Add/repair element columns (77–78) in a PDB using Biopython."
312
+ )
313
+ ap.add_argument("pdb", help="input PDB filepath")
314
+ ap.add_argument("-o", "--out", help="output PDB filepath (omit to overwrite input)")
315
+ ap.add_argument(
316
+ "--overwrite",
317
+ action="store_true",
318
+ help="Re-infer and overwrite element fields even if present (by default, existing values are preserved).",
319
+ )
320
+ args = ap.parse_args()
321
+
322
+ if not os.path.isfile(args.pdb):
323
+ click.echo(f"[add-elem-info] ERROR: Input not found: {args.pdb}", err=True)
324
+ sys.exit(1)
325
+
326
+ try:
327
+ assign_elements(args.pdb, args.out, overwrite=args.overwrite)
328
+ except Exception as e:
329
+ click.echo(f"[add-elem-info] ERROR: Failed: {e}", err=True)
330
+ sys.exit(2)
331
+
332
+ # -----------------------------
333
+ # Click subcommand (mlmm add-elem-info)
334
+ # -----------------------------
335
+ @click.command(
336
+ help="Add/repair element columns (77–78) in a PDB using Biopython.",
337
+ context_settings={"help_option_names": ["-h", "--help"]},
338
+ )
339
+ @click.option(
340
+ "-i", "--input",
341
+ "in_pdb",
342
+ type=click.Path(path_type=Path, exists=True, dir_okay=False),
343
+ required=True,
344
+ help="Input PDB filepath",
345
+ )
346
+ @click.option(
347
+ "-o", "--out",
348
+ "out_pdb",
349
+ type=click.Path(path_type=Path, dir_okay=False),
350
+ default=None,
351
+ help="Output PDB filepath (omit to overwrite input)",
352
+ )
353
+ @click.option(
354
+ "--overwrite/--no-overwrite",
355
+ "overwrite",
356
+ default=False,
357
+ show_default=True,
358
+ help="Re-infer and overwrite element fields even if present (by default, existing values are preserved).",
359
+ )
360
+ def cli(in_pdb: Path, out_pdb: Optional[Path], overwrite: bool) -> None:
361
+ """
362
+ Click wrapper to run via the `mlmm add-elem-info` subcommand.
363
+ """
364
+ try:
365
+ assign_elements(str(in_pdb), (str(out_pdb) if out_pdb else None), overwrite=overwrite)
366
+ except SystemExit as e:
367
+ # Match argparse-like behavior: propagate SystemExit as-is
368
+ raise e
369
+ except Exception as e:
370
+ click.echo(f"[ERR] Failed: {e}", err=True)
371
+ sys.exit(2)
372
+
373
+ if __name__ == "__main__":
374
+ main()
mlmm/advanced_help.py ADDED
@@ -0,0 +1,91 @@
1
+ """Helpers for progressive `--help` / `--help-advanced` behavior."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+
8
+ def _show_advanced_subcommand_help(
9
+ ctx: click.Context, _param: click.Parameter, value: bool
10
+ ) -> None:
11
+ """Print subcommand help with advanced options and exit."""
12
+ if not value or ctx.resilient_parsing:
13
+ return
14
+
15
+ if getattr(ctx.command, "_advanced_passthrough_help", False):
16
+ try:
17
+ ctx.command.main(args=["--help"], standalone_mode=False)
18
+ except SystemExit as exc:
19
+ code = getattr(exc, "code", 1)
20
+ if code not in (None, 0):
21
+ raise
22
+ ctx.exit()
23
+
24
+ hidden = getattr(ctx.command, "_advanced_hidden_options", ())
25
+ restored: list[click.Option] = []
26
+ for opt in hidden:
27
+ if opt.hidden:
28
+ opt.hidden = False
29
+ restored.append(opt)
30
+ try:
31
+ click.echo(ctx.command.get_help(ctx))
32
+ finally:
33
+ for opt in restored:
34
+ opt.hidden = True
35
+ ctx.exit()
36
+
37
+
38
+ def _ensure_help_advanced_option(command: click.Command) -> click.Command:
39
+ """Attach --help-advanced to lazily loaded subcommands when absent."""
40
+ passthrough_help = (
41
+ command.context_settings.get("help_option_names") == []
42
+ if isinstance(command.context_settings, dict)
43
+ else False
44
+ )
45
+ setattr(command, "_advanced_passthrough_help", passthrough_help)
46
+
47
+ if any(
48
+ isinstance(param, click.Option) and "--help-advanced" in param.opts
49
+ for param in command.params
50
+ ):
51
+ return command
52
+
53
+ option = click.Option(
54
+ ["--help-advanced"],
55
+ is_flag=True,
56
+ is_eager=True,
57
+ expose_value=False,
58
+ callback=_show_advanced_subcommand_help,
59
+ help="Show all options (including advanced settings) and exit.",
60
+ )
61
+ command.params.insert(0, option)
62
+ return command
63
+
64
+
65
+ def _configure_subcommand_help_visibility(
66
+ command_name: str,
67
+ command: click.Command,
68
+ primary_options_by_subcommand: dict[str, frozenset[str]],
69
+ ) -> click.Command:
70
+ """Hide advanced options from default --help for selected subcommands."""
71
+ if hasattr(command, "_advanced_hidden_options"):
72
+ return command
73
+
74
+ primary_options = primary_options_by_subcommand.get(command_name)
75
+ if not primary_options:
76
+ return command
77
+
78
+ hidden_options: list[click.Option] = []
79
+ for param in command.params:
80
+ if not isinstance(param, click.Option):
81
+ continue
82
+ names = set(param.opts + param.secondary_opts)
83
+ if names & primary_options:
84
+ continue
85
+ if param.hidden:
86
+ continue
87
+ param.hidden = True
88
+ hidden_options.append(param)
89
+
90
+ setattr(command, "_advanced_hidden_options", tuple(hidden_options))
91
+ return command