abmptools 2.2.0__tar.gz → 2.3.0__tar.gz

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 (356) hide show
  1. {abmptools-2.2.0/abmptools.egg-info → abmptools-2.3.0}/PKG-INFO +6 -3
  2. {abmptools-2.2.0 → abmptools-2.3.0}/README.md +5 -2
  3. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/analyzer.py +166 -12
  4. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/cli.py +17 -2
  5. abmptools-2.3.0/abmptools/hbond/diagram.py +150 -0
  6. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udfcharge/__init__.py +4 -0
  7. abmptools-2.3.0/abmptools/udfcharge/__main__.py +117 -0
  8. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udfcharge/core.py +186 -5
  9. {abmptools-2.2.0 → abmptools-2.3.0/abmptools.egg-info}/PKG-INFO +6 -3
  10. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools.egg-info/SOURCES.txt +1 -0
  11. {abmptools-2.2.0 → abmptools-2.3.0}/pyproject.toml +1 -1
  12. abmptools-2.2.0/abmptools/udfcharge/__main__.py +0 -63
  13. {abmptools-2.2.0 → abmptools-2.3.0}/LICENSE +0 -0
  14. {abmptools-2.2.0 → abmptools-2.3.0}/NOTICE +0 -0
  15. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/__init__.py +0 -0
  16. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/abinit_io.py +0 -0
  17. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/addsolvfrag.py +0 -0
  18. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/ajf2config.py +0 -0
  19. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/ajfserial.py +0 -0
  20. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/__init__.py +0 -0
  21. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/__main__.py +0 -0
  22. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/builder.py +0 -0
  23. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/cli.py +0 -0
  24. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/density.py +0 -0
  25. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/mdp_protocol.py +0 -0
  26. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/models.py +0 -0
  27. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/molecule_prep.py +0 -0
  28. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/ndx_writer.py +0 -0
  29. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/packing.py +0 -0
  30. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/parameterizer.py +0 -0
  31. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/pubchem.py +0 -0
  32. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/system_model_adapter.py +0 -0
  33. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/amorphous/trajectory_ingest.py +0 -0
  34. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/anlfmo.py +0 -0
  35. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/__init__.py +0 -0
  36. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/__init__.py +0 -0
  37. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/__main__.py +0 -0
  38. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/aij_assign.py +0 -0
  39. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/aij_io.py +0 -0
  40. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/calc_sett_io.py +0 -0
  41. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/dpm_writer.py +0 -0
  42. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/models.py +0 -0
  43. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/monomer_io.py +0 -0
  44. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/notebook_ui.py +0 -0
  45. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/orchestrator.py +0 -0
  46. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/udf_writer.py +0 -0
  47. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/dpd/udf_writer_udfm.py +0 -0
  48. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/__init__.py +0 -0
  49. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/__main__.py +0 -0
  50. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/_subprocess.py +0 -0
  51. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/builder.py +0 -0
  52. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/cli.py +0 -0
  53. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/forcefield_check.py +0 -0
  54. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/insane_runner.py +0 -0
  55. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/mdp_templates.py +0 -0
  56. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/models.py +0 -0
  57. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/pmf.py +0 -0
  58. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/pulling.py +0 -0
  59. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/system_packer.py +0 -0
  60. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/topology_composer.py +0 -0
  61. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/membrane/umbrella.py +0 -0
  62. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/__init__.py +0 -0
  63. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/__main__.py +0 -0
  64. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/_subprocess.py +0 -0
  65. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/builder.py +0 -0
  66. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/cli.py +0 -0
  67. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/forcefield_check.py +0 -0
  68. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/martinize_runner.py +0 -0
  69. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/mdp_templates.py +0 -0
  70. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/models.py +0 -0
  71. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/peptide_atomistic.py +0 -0
  72. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/system_packer.py +0 -0
  73. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/top_writer.py +0 -0
  74. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cg/peptide/water_box.py +0 -0
  75. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/convertcpf.py +0 -0
  76. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/core/__init__.py +0 -0
  77. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/core/system_model.py +0 -0
  78. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cpf2ifielist.py +0 -0
  79. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/cpfmanager.py +0 -0
  80. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/__init__.py +0 -0
  81. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/__main__.py +0 -0
  82. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/_subprocess.py +0 -0
  83. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/atom_distance.py +0 -0
  84. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/builder.py +0 -0
  85. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/cif_engine_ase.py +0 -0
  86. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/cif_engine_legacy.py +0 -0
  87. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/cli.py +0 -0
  88. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/forcefield_check.py +0 -0
  89. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/job_templates.py +0 -0
  90. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/legacy/__init__.py +0 -0
  91. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/models.py +0 -0
  92. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/crystal/postproc.py +0 -0
  93. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/f90/bin/readifiepiedalib.so +0 -0
  94. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/__init__.py +0 -0
  95. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/__main__.py +0 -0
  96. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/_subprocess.py +0 -0
  97. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/analysis/__init__.py +0 -0
  98. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/analysis/aggregate_transition.py +0 -0
  99. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/analysis/contact_map.py +0 -0
  100. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/analysis/hbond.py +0 -0
  101. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/analysis/plots.py +0 -0
  102. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/analysis/sasa.py +0 -0
  103. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/analysis/secondary_structure.py +0 -0
  104. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/builder.py +0 -0
  105. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/cli.py +0 -0
  106. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/mdp_templates.py +0 -0
  107. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/models.py +0 -0
  108. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/ndx.py +0 -0
  109. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/packer.py +0 -0
  110. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/peptide_atomistic.py +0 -0
  111. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/peptide_atomistic_openff.py +0 -0
  112. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/small_molecule.py +0 -0
  113. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/small_molecule_openff.py +0 -0
  114. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/topology.py +0 -0
  115. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/topology_openff.py +0 -0
  116. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/formulation/umbrella_release.py +0 -0
  117. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/__init__.py +0 -0
  118. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/__main__.py +0 -0
  119. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/auto_split.py +0 -0
  120. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/__init__.py +0 -0
  121. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/__main__.py +0 -0
  122. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/cap_attach.py +0 -0
  123. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/chain_splitter.py +0 -0
  124. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/dpdgen_exporter.py +0 -0
  125. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/exporter.py +0 -0
  126. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/fcews_export.py +0 -0
  127. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/models.py +0 -0
  128. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/notebook_ui.py +0 -0
  129. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/orchestrator.py +0 -0
  130. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cg_segmenter/ring_detector.py +0 -0
  131. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/cut_apply.py +0 -0
  132. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/expand_to_system.py +0 -0
  133. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/grouping.py +0 -0
  134. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/headless_io.py +0 -0
  135. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/models.py +0 -0
  136. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/notebook_ui.py +0 -0
  137. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/pdb_loader.py +0 -0
  138. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/fragmenter/polymer.py +0 -0
  139. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/generate_difie.py +0 -0
  140. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/generateajf.py +0 -0
  141. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/__init__.py +0 -0
  142. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/__init__.py +0 -0
  143. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/__main__.py +0 -0
  144. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/_subprocess.py +0 -0
  145. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/analysis.py +0 -0
  146. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/builder.py +0 -0
  147. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/cli.py +0 -0
  148. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/forcefield_check.py +0 -0
  149. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/grest_runner.py +0 -0
  150. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/inp_writer.py +0 -0
  151. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/models.py +0 -0
  152. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/replica_temperatures.py +0 -0
  153. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/rest_selection.py +0 -0
  154. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/grest/system_builder.py +0 -0
  155. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/__init__.py +0 -0
  156. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/__main__.py +0 -0
  157. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/_subprocess.py +0 -0
  158. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/analysis.py +0 -0
  159. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/builder.py +0 -0
  160. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/cli.py +0 -0
  161. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/forcefield_check.py +0 -0
  162. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/gbsa_runner.py +0 -0
  163. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/inp_writer.py +0 -0
  164. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/ligand_parameterize.py +0 -0
  165. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/models.py +0 -0
  166. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/pdb_splitter.py +0 -0
  167. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/genesis/mmgbsa/system_builder.py +0 -0
  168. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/geomopt/__init__.py +0 -0
  169. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/geomopt/mace_optimizer.py +0 -0
  170. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/geomopt/openff_openmm_minimizer.py +0 -0
  171. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/geomopt/pyscf_optimizer.py +0 -0
  172. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/getcharge.py +0 -0
  173. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/getifiepieda.py +0 -0
  174. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/__init__.py +0 -0
  175. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/__main__.py +0 -0
  176. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/cli.py +0 -0
  177. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/default_template.udf +0 -0
  178. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/default_template_cognac101.udf +0 -0
  179. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/exporter.py +0 -0
  180. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/gro_adapter.py +0 -0
  181. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/gro_parser.py +0 -0
  182. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/mdp_parser.py +0 -0
  183. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/top_adapter.py +0 -0
  184. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/top_exporter.py +0 -0
  185. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/top_model.py +0 -0
  186. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/top_parser.py +0 -0
  187. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/trajectory_io.py +0 -0
  188. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/gro2udf/udf_writer.py +0 -0
  189. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/__init__.py +0 -0
  190. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/__main__.py +0 -0
  191. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/bdf_reader.py +0 -0
  192. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/classifier.py +0 -0
  193. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/colorizer.py +0 -0
  194. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/distance_dist.py +0 -0
  195. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/func_tags.py +0 -0
  196. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/functional_groups.py +0 -0
  197. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/hbond_detector.py +0 -0
  198. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/lifetime.py +0 -0
  199. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/notebook_ui.py +0 -0
  200. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/hbond/pair_type_stats.py +0 -0
  201. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/log2config.py +0 -0
  202. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/log2cpf.py +0 -0
  203. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/logmanager.py +0 -0
  204. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/__init__.py +0 -0
  205. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/bilayer.py +0 -0
  206. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/builder.py +0 -0
  207. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/lipid_info.py +0 -0
  208. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/mdp_us_protocol.py +0 -0
  209. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/models.py +0 -0
  210. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/parameterize_amber.py +0 -0
  211. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/parameterize_charmm.py +0 -0
  212. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/pmf.py +0 -0
  213. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/pulling.py +0 -0
  214. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/topology_sanity.py +0 -0
  215. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/membrane/umbrella.py +0 -0
  216. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/mol_io.py +0 -0
  217. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/molcalc.py +0 -0
  218. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/pdb2fmo.py +0 -0
  219. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/pdb_io.py +0 -0
  220. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/pdbmodify.py +0 -0
  221. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/readcif.py +0 -0
  222. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/setfmo.py +0 -0
  223. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/trajectory/__init__.py +0 -0
  224. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/trajectory/__main__.py +0 -0
  225. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/trajectory/cli.py +0 -0
  226. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/trajectory/postprocess.py +0 -0
  227. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2fmo.py +0 -0
  228. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/__init__.py +0 -0
  229. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/__main__.py +0 -0
  230. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/cli.py +0 -0
  231. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/exporter.py +0 -0
  232. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/gromacs/__init__.py +0 -0
  233. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/gromacs/writers/__init__.py +0 -0
  234. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/gromacs/writers/_validator.py +0 -0
  235. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/gromacs/writers/gro_writer.py +0 -0
  236. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/gromacs/writers/itp_writer.py +0 -0
  237. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/gromacs/writers/mdp_writer.py +0 -0
  238. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/gromacs/writers/top_writer.py +0 -0
  239. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf2gro/udf_adapter.py +0 -0
  240. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udf_io.py +0 -0
  241. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udfcreate.py +0 -0
  242. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udfcreate_v2.py +0 -0
  243. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools/udfrm_io.py +0 -0
  244. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools.egg-info/dependency_links.txt +0 -0
  245. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools.egg-info/entry_points.txt +0 -0
  246. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools.egg-info/requires.txt +0 -0
  247. {abmptools-2.2.0 → abmptools-2.3.0}/abmptools.egg-info/top_level.txt +0 -0
  248. {abmptools-2.2.0 → abmptools-2.3.0}/setup.cfg +0 -0
  249. {abmptools-2.2.0 → abmptools-2.3.0}/setup.py +0 -0
  250. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_abinit_io.py +0 -0
  251. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_amorphous_models.py +0 -0
  252. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_anlfmo.py +0 -0
  253. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_builder_integration.py +0 -0
  254. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_builder_mocked.py +0 -0
  255. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_builder_mocked.py +0 -0
  256. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_cli.py +0 -0
  257. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_forcefield_check.py +0 -0
  258. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_insane_runner.py +0 -0
  259. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_integration.py +0 -0
  260. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_mdp.py +0 -0
  261. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_models.py +0 -0
  262. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_packer.py +0 -0
  263. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_pmf.py +0 -0
  264. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_pulling.py +0 -0
  265. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_topology_composer.py +0 -0
  266. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_membrane_umbrella.py +0 -0
  267. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_atomistic.py +0 -0
  268. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_builder_mocked.py +0 -0
  269. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_cli.py +0 -0
  270. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_forcefield_check.py +0 -0
  271. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_integration.py +0 -0
  272. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_martinize_runner.py +0 -0
  273. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_mdp.py +0 -0
  274. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_models.py +0 -0
  275. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_packer.py +0 -0
  276. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_subprocess.py +0 -0
  277. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_topwriter.py +0 -0
  278. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cg_peptide_water_box.py +0 -0
  279. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_charge_method.py +0 -0
  280. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cli_scripts.py +0 -0
  281. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_cpfmanager.py +0 -0
  282. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_crystal_atom_distance.py +0 -0
  283. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_crystal_builder.py +0 -0
  284. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_crystal_cif_ase.py +0 -0
  285. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_crystal_cif_legacy.py +0 -0
  286. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_crystal_cli.py +0 -0
  287. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_crystal_job_templates.py +0 -0
  288. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_crystal_models.py +0 -0
  289. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_crystal_numeric_regression.py +0 -0
  290. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_crystal_regression.py +0 -0
  291. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_density.py +0 -0
  292. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_analysis.py +0 -0
  293. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_builder.py +0 -0
  294. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_mdp.py +0 -0
  295. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_models.py +0 -0
  296. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_ndx.py +0 -0
  297. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_packer.py +0 -0
  298. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_peptide_atomistic.py +0 -0
  299. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_small_molecule.py +0 -0
  300. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_topology.py +0 -0
  301. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_formulation_umbrella_release.py +0 -0
  302. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_getifiepieda.py +0 -0
  303. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_grest_analysis_mocked.py +0 -0
  304. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_grest_builder_mocked.py +0 -0
  305. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_grest_cli.py +0 -0
  306. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_grest_inp_writer.py +0 -0
  307. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_grest_integration.py +0 -0
  308. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_grest_models.py +0 -0
  309. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_grest_replica_temperatures.py +0 -0
  310. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_grest_rest_selection.py +0 -0
  311. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_grest_system_builder.py +0 -0
  312. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_gro_adapter.py +0 -0
  313. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_gro_parser.py +0 -0
  314. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_gro_writer.py +0 -0
  315. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_interchange_adapter.py +0 -0
  316. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_logmanager.py +0 -0
  317. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mdp_parser.py +0 -0
  318. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mdp_protocol.py +0 -0
  319. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mdp_writer.py +0 -0
  320. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_membrane_charmm_translate.py +0 -0
  321. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_membrane_mixed_lipid.py +0 -0
  322. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_membrane_topology_sanity.py +0 -0
  323. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mmgbsa_analysis.py +0 -0
  324. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mmgbsa_builder_mocked.py +0 -0
  325. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mmgbsa_cli.py +0 -0
  326. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mmgbsa_forcefield_check.py +0 -0
  327. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mmgbsa_inp_writer.py +0 -0
  328. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mmgbsa_integration.py +0 -0
  329. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mmgbsa_models.py +0 -0
  330. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mmgbsa_pdb_splitter.py +0 -0
  331. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mmgbsa_system_builder.py +0 -0
  332. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_mol_io.py +0 -0
  333. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_molcalc.py +0 -0
  334. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_ndx_writer.py +0 -0
  335. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_parameterizer.py +0 -0
  336. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_pdb_io.py +0 -0
  337. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_pubchem.py +0 -0
  338. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_pyscf_optimizer.py +0 -0
  339. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_pyscf_parsers.py +0 -0
  340. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_read_ifie_hf_fallback.py +0 -0
  341. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_readcif.py +0 -0
  342. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_regression.py +0 -0
  343. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_setfmo.py +0 -0
  344. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_system_model.py +0 -0
  345. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_system_model_extensions.py +0 -0
  346. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_top_exporter_frames_override.py +0 -0
  347. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_top_model.py +0 -0
  348. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_top_parser.py +0 -0
  349. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_top_writer.py +0 -0
  350. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_trajectory_ingest.py +0 -0
  351. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_trajectory_postprocess.py +0 -0
  352. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_udf_io.py +0 -0
  353. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_udfcreate.py +0 -0
  354. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_udfcreate_v2.py +0 -0
  355. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_udfcreate_v2_byte_equivalence.py +0 -0
  356. {abmptools-2.2.0 → abmptools-2.3.0}/tests/test_udfrm_io.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abmptools
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Pre/post-processing and analysis toolkit for ABINIT-MP Fragment Molecular Orbital calculations
5
5
  Author-email: Koji Okuwaki <koujioku81@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -121,7 +121,7 @@ A Python toolkit for pre-processing, post-processing, and analysis of Fragment M
121
121
 
122
122
  - **udf2gro**: Convert OCTA UDF files to GROMACS format (`.gro`, `.top`, `.mdp`, `.itp`)
123
123
  - **gro2udf**: Convert GROMACS files to OCTA UDF format (supports `--from-top` mode)
124
- - **udfcharge**: Transfer per-atom partial charges from a single-molecule UDF to every same-named molecule in a bulk UDF
124
+ - **udfcharge**: Transfer per-atom partial charges from a single-molecule UDF to bulk molecules (`transfer`), or restore a neutralized UDF's charges to a target integer formal charge (`restore`)
125
125
 
126
126
  ### Geometry Optimization (`geomopt`)
127
127
 
@@ -314,7 +314,10 @@ python -m abmptools.udf2gro.cli -i system.udf -o output
314
314
  python -m abmptools.gro2udf.cli -i system.gro -t system.top -o output.udf
315
315
 
316
316
  # Transfer charges from a single-molecule UDF to all same-named molecules in a bulk UDF
317
- python -m abmptools.udfcharge --template mol.udf --bulk bulk.udf --out bulk_charged.udf
317
+ python -m abmptools.udfcharge transfer --template mol.udf --bulk bulk.udf --out bulk_charged.udf
318
+
319
+ # Restore a neutralized UDF's charges to a target integer formal charge
320
+ python -m abmptools.udfcharge restore --udf mol.udf --formal-charge 12 --out mol_q+12.udf
318
321
 
319
322
  # Build an amorphous mixture from SMILES (50 ketoprofen molecules, density 0.8 g/cm^3)
320
323
  python -m abmptools.amorphous --smiles "OC(=O)C(C)c1cccc(C(=O)c2ccccc2)c1" \
@@ -37,7 +37,7 @@ A Python toolkit for pre-processing, post-processing, and analysis of Fragment M
37
37
 
38
38
  - **udf2gro**: Convert OCTA UDF files to GROMACS format (`.gro`, `.top`, `.mdp`, `.itp`)
39
39
  - **gro2udf**: Convert GROMACS files to OCTA UDF format (supports `--from-top` mode)
40
- - **udfcharge**: Transfer per-atom partial charges from a single-molecule UDF to every same-named molecule in a bulk UDF
40
+ - **udfcharge**: Transfer per-atom partial charges from a single-molecule UDF to bulk molecules (`transfer`), or restore a neutralized UDF's charges to a target integer formal charge (`restore`)
41
41
 
42
42
  ### Geometry Optimization (`geomopt`)
43
43
 
@@ -230,7 +230,10 @@ python -m abmptools.udf2gro.cli -i system.udf -o output
230
230
  python -m abmptools.gro2udf.cli -i system.gro -t system.top -o output.udf
231
231
 
232
232
  # Transfer charges from a single-molecule UDF to all same-named molecules in a bulk UDF
233
- python -m abmptools.udfcharge --template mol.udf --bulk bulk.udf --out bulk_charged.udf
233
+ python -m abmptools.udfcharge transfer --template mol.udf --bulk bulk.udf --out bulk_charged.udf
234
+
235
+ # Restore a neutralized UDF's charges to a target integer formal charge
236
+ python -m abmptools.udfcharge restore --udf mol.udf --formal-charge 12 --out mol_q+12.udf
234
237
 
235
238
  # Build an amorphous mixture from SMILES (50 ketoprofen molecules, density 0.8 g/cm^3)
236
239
  python -m abmptools.amorphous --smiles "OC(=O)C(C)c1cccc(C(=O)c2ccccc2)c1" \
@@ -140,6 +140,12 @@ class AnalyzerConfig:
140
140
  distance_d_max: float = 3.6 # Å, upper edge (Luzar-Chandler cutoff 3.5 + slack)
141
141
  distance_bin_width: float = 0.05 # Å
142
142
  angle_bin_width: float = 5.0 # deg, used for the 2-D (d, θ) heatmap
143
+ # Per-molecule 2D structure diagram marking the detected H-bond sites
144
+ # (red = donor, cyan = acceptor). Requires RDKit; skipped with a warning if
145
+ # unavailable or if bond-order perception fails. diagram_charge is the net
146
+ # molecular charge passed to DetermineBondOrders (0 = neutral).
147
+ do_diagram: bool = True
148
+ diagram_charge: int = 0
143
149
 
144
150
  def get_criteria(self) -> HBondCriteria:
145
151
  if self.criteria_mode == "luzar-chandler":
@@ -302,6 +308,36 @@ class Analyzer:
302
308
  meta_by_type["ether_O"] = [(s.mol_index, s.acceptor_a) for s in sites]
303
309
  return sites_by_type, meta_by_type
304
310
 
311
+ def _auto_groups(self) -> Tuple[List[str], List[str]]:
312
+ """Auto-detect donor/acceptor group types present in the system.
313
+
314
+ Used by ``classify_mode='auto'``. Returns ``(donor_groups,
315
+ acceptor_groups)`` listing only the functional-group types actually
316
+ detected (carboxyl / amide / secondary-amide N-H / amine N-H /
317
+ hydroxyl). ``ether_O`` is NOT auto-listed because there is no reliable
318
+ ether detector yet (GAFF2 ``os``-tag only); pass ``--acceptor-groups
319
+ ether_O`` explicitly if needed.
320
+ """
321
+ if self.traj is None:
322
+ self.load()
323
+ from .functional_groups import detect_hydroxyls
324
+ donors: List[str] = []
325
+ acceptors: List[str] = []
326
+ if self.carboxyls:
327
+ donors.append("carboxyl")
328
+ acceptors.append("carboxyl_O")
329
+ if any(g.from_amide for g in self.amine_donors):
330
+ donors.append("amide_donor")
331
+ if any(not g.from_amide for g in self.amine_donors):
332
+ donors.append("amine_donor")
333
+ hydroxyls = detect_hydroxyls(self.traj.molecules, mapping=self.mapping)
334
+ if hydroxyls:
335
+ donors.append("hydroxyl")
336
+ acceptors.append("hydroxyl_O")
337
+ if self.amides:
338
+ acceptors.append("amide_O")
339
+ return donors, acceptors
340
+
305
341
  def run(self) -> List[FrameResult]:
306
342
  if self.traj is None:
307
343
  self.load()
@@ -309,12 +345,28 @@ class Analyzer:
309
345
  end = c.record_end if c.record_end >= 0 else self.traj.n_records
310
346
  criteria = c.get_criteria()
311
347
  mode = (c.classify_mode or "imc").lower()
312
- if mode not in {"imc", "generic"}:
348
+ if mode not in {"imc", "generic", "auto"}:
313
349
  raise ValueError(
314
350
  f"Unknown classify_mode={c.classify_mode!r}; "
315
- "must be 'imc' or 'generic'"
351
+ "must be 'imc', 'generic', or 'auto'"
316
352
  )
317
353
 
354
+ if mode == "auto":
355
+ # Detect which functional-group donor/acceptor types are present and
356
+ # run them through the generic pair-stats path. Explicit
357
+ # --donor-groups / --acceptor-groups still override the auto picks.
358
+ auto_donors, auto_acceptors = self._auto_groups()
359
+ if c.donor_groups is None:
360
+ c.donor_groups = auto_donors
361
+ if c.acceptor_groups is None:
362
+ c.acceptor_groups = auto_acceptors
363
+ if c.verbose:
364
+ print(f" [auto] donor groups : {c.donor_groups or '(none detected)'}")
365
+ print(f" [auto] acceptor groups: {c.acceptor_groups or '(none detected)'}")
366
+ # Resolve to generic so run()/write_outputs use the generic path.
367
+ c.classify_mode = "generic"
368
+ mode = "generic"
369
+
318
370
  donor_groups = c.donor_groups or (
319
371
  ["carboxyl"] if mode == "imc" else []
320
372
  )
@@ -328,6 +380,9 @@ class Analyzer:
328
380
  acceptor_sites_by_type, acceptor_meta_by_type = (
329
381
  self._build_acceptor_sites_by_type(acceptor_groups)
330
382
  )
383
+ # Stash detected sites so write_outputs() can render the 2D diagram.
384
+ self.donor_sites_by_type = donor_sites_by_type
385
+ self.acceptor_sites_by_type = acceptor_sites_by_type
331
386
 
332
387
  # IMC mode keeps backward-compat names for hb_cc/hb_ca path
333
388
  carb_donors = donor_sites_by_type.get("carboxyl", [])
@@ -654,17 +709,36 @@ class Analyzer:
654
709
  import matplotlib.pyplot as plt
655
710
  recs = [fr.record for fr in self.frame_results]
656
711
  fig, ax = plt.subplots(figsize=(7, 4))
657
- ax.plot(recs, [fr.n_dual_mols for fr in self.frame_results],
658
- "o-", color="red", label="dual COOH-COOH mols")
659
- ax.plot(recs, [fr.n_chain_mols for fr in self.frame_results],
660
- "D-", color="magenta", label="chain COOH-COOH mols")
661
- ax.plot(recs, [fr.n_single_mols for fr in self.frame_results],
662
- "s-", color="blue", label="single COOH-amide mols")
663
- ax.plot(recs, [fr.n_free_mols for fr in self.frame_results],
664
- "^-", color="gray", label="free mols")
712
+ if mode == "imc":
713
+ ax.plot(recs, [fr.n_dual_mols for fr in self.frame_results],
714
+ "o-", color="red", label="dual COOH-COOH mols")
715
+ ax.plot(recs, [fr.n_chain_mols for fr in self.frame_results],
716
+ "D-", color="magenta", label="chain COOH-COOH mols")
717
+ ax.plot(recs, [fr.n_single_mols for fr in self.frame_results],
718
+ "s-", color="blue", label="single COOH-amide mols")
719
+ ax.plot(recs, [fr.n_free_mols for fr in self.frame_results],
720
+ "^-", color="gray", label="free mols")
721
+ ax.set_ylabel("number of molecules")
722
+ ax.set_title(f"H-bond classification ({c.criteria_mode})")
723
+ else:
724
+ # generic / auto: one line per donor→acceptor pair type
725
+ pair_types = sorted(
726
+ {pt for fr in self.frame_results
727
+ for pt in fr.hbonds_by_pair_type}
728
+ )
729
+ for pt in pair_types:
730
+ counts = [len(fr.hbonds_by_pair_type.get(pt, []))
731
+ for fr in self.frame_results]
732
+ ax.plot(recs, counts, "o-", markersize=3,
733
+ label=f"{pt[0]}→{pt[1]}")
734
+ if not pair_types:
735
+ ax.plot(recs, [0] * len(recs), "o-", color="gray",
736
+ label="(no H-bonds detected)")
737
+ ax.set_ylabel("number of H-bonds")
738
+ ax.set_title(
739
+ f"H-bond count per pair type ({c.criteria_mode})"
740
+ )
665
741
  ax.set_xlabel("record")
666
- ax.set_ylabel("number of molecules")
667
- ax.set_title(f"H-bond classification ({c.criteria_mode})")
668
742
  ax.legend()
669
743
  ax.grid(True, alpha=0.3)
670
744
  fig.tight_layout()
@@ -682,6 +756,86 @@ class Analyzer:
682
756
  if c.compute_lifetime and len(self.frame_results) >= 2:
683
757
  out_paths.update(self._write_lifetime_outputs())
684
758
 
759
+ # Per-molecule 2D structure diagram of the detected H-bond sites
760
+ if c.do_diagram:
761
+ out_paths.update(self._write_diagram_outputs())
762
+
763
+ return out_paths
764
+
765
+ def _write_diagram_outputs(self) -> Dict[str, str]:
766
+ """Render a 2D structure diagram per molecular species, marking the
767
+ detected donor (red) / acceptor (cyan) atoms. Requires RDKit; skipped
768
+ with a warning if unavailable or if a molecule cannot be perceived."""
769
+ from . import diagram as _dg
770
+ c = self.config
771
+ out_paths: Dict[str, str] = {}
772
+
773
+ donor_by_type = getattr(self, "donor_sites_by_type", {}) or {}
774
+ acceptor_by_type = getattr(self, "acceptor_sites_by_type", {}) or {}
775
+ donors_per_mol: Dict[int, set] = {}
776
+ acceptors_per_mol: Dict[int, set] = {}
777
+ donor_types_per_mol: Dict[int, set] = {}
778
+ acceptor_types_per_mol: Dict[int, set] = {}
779
+ for dtype, sites in donor_by_type.items():
780
+ for s in sites:
781
+ donors_per_mol.setdefault(s.mol_index, set()).add(s.d_local)
782
+ donor_types_per_mol.setdefault(s.mol_index, set()).add(dtype)
783
+ for atype, sites in acceptor_by_type.items():
784
+ for s in sites:
785
+ acceptors_per_mol.setdefault(s.mol_index, set()).add(s.a_local)
786
+ acceptor_types_per_mol.setdefault(s.mol_index, set()).add(atype)
787
+
788
+ if not donors_per_mol and not acceptors_per_mol:
789
+ if c.verbose:
790
+ print(" (diagram skipped: no donor/acceptor atoms detected)")
791
+ return {}
792
+
793
+ rec0 = c.record_start
794
+ try:
795
+ frame = self.traj.get_frame(rec0)
796
+ except Exception:
797
+ frame = self.traj.get_frame(0)
798
+
799
+ # Group molecules into unique species by element signature (robust to
800
+ # per-atom-unique force-field type names).
801
+ species: Dict[tuple, int] = {}
802
+ for mi, topo in enumerate(self.traj.molecules):
803
+ sig = tuple(_dg.element_from_name(a.atom_name) for a in topo.atoms)
804
+ species.setdefault(sig, mi)
805
+ multi = len(species) > 1
806
+ n_done = 0
807
+ for sig, mi in species.items():
808
+ d_atoms = sorted(donors_per_mol.get(mi, set()))
809
+ a_atoms = sorted(acceptors_per_mol.get(mi, set()))
810
+ if not d_atoms and not a_atoms:
811
+ continue
812
+ topo = self.traj.molecules[mi]
813
+ coords = frame.positions[mi]
814
+ mol, reason = _dg.build_mol_from_topology(
815
+ topo, coords, charge=c.diagram_charge,
816
+ )
817
+ if mol is None:
818
+ print(f"Warning: diagram skipped for {topo.mol_name}: {reason}")
819
+ continue
820
+ dnames = ", ".join(sorted(donor_types_per_mol.get(mi, set()))) or "—"
821
+ anames = ", ".join(sorted(acceptor_types_per_mol.get(mi, set()))) or "—"
822
+ legend = (f"{topo.mol_name} red = donor ({dnames}) "
823
+ f"cyan = acceptor ({anames})")
824
+ suffix = f"_diagram_{topo.mol_name}" if multi else "_diagram"
825
+ png = f"{c.out_prefix}{suffix}.png"
826
+ svg = f"{c.out_prefix}{suffix}.svg"
827
+ try:
828
+ _dg.draw_hbond_diagram(mol, d_atoms, a_atoms, png, svg,
829
+ legend=legend)
830
+ except Exception as e: # noqa: BLE001
831
+ print(f"Warning: diagram render failed for {topo.mol_name}: {e}")
832
+ continue
833
+ key = f"diagram_{topo.mol_name}" if multi else "diagram"
834
+ out_paths[key] = png
835
+ out_paths[key + "_svg"] = svg
836
+ n_done += 1
837
+ if c.verbose and n_done:
838
+ print(f" diagram: {n_done} molecular species rendered")
685
839
  return out_paths
686
840
 
687
841
  def _write_distance_outputs(self) -> Dict[str, str]:
@@ -62,9 +62,11 @@ def main(argv=None) -> int:
62
62
  help="Base molecule name for renamed groups (default: IMC)"
63
63
  )
64
64
  parser.add_argument(
65
- "--classify-mode", choices=["imc", "generic"], default="imc",
65
+ "--classify-mode", choices=["imc", "generic", "auto"], default="imc",
66
66
  help="imc: COOH-centric 4-species (dual/chain/single/free, IMC default). "
67
- "generic: donor-type x acceptor-type pair stats (PVA/peptide/etc.)"
67
+ "generic: donor-type x acceptor-type pair stats (PVA/peptide/etc.). "
68
+ "auto: detect functional groups present and run generic with them "
69
+ "(no need to pass --donor-groups/--acceptor-groups; ether_O excluded)"
68
70
  )
69
71
  parser.add_argument(
70
72
  "--no-element-fallback", action="store_true",
@@ -148,6 +150,17 @@ def main(argv=None) -> int:
148
150
  "--no-distance-plots", action="store_true",
149
151
  help="Skip d(D...A) distance distribution + (d, ∠) 2-D heatmap"
150
152
  )
153
+ parser.add_argument(
154
+ "--no-diagram", action="store_true",
155
+ help="Skip the per-molecule 2D structure diagram of the detected "
156
+ "H-bond sites (red=donor/cyan=acceptor). Requires RDKit; the "
157
+ "diagram is auto-generated by default when RDKit is available."
158
+ )
159
+ parser.add_argument(
160
+ "--diagram-charge", type=int, default=0,
161
+ help="Net molecular charge for diagram bond-order perception "
162
+ "(DetermineBondOrders), default 0 = neutral"
163
+ )
151
164
  parser.add_argument(
152
165
  "--distance-d-min", type=float, default=2.0,
153
166
  help="d(D...A) histogram lower edge [Å] (default 2.0)"
@@ -213,6 +226,8 @@ def main(argv=None) -> int:
213
226
  distance_d_max=args.distance_d_max,
214
227
  distance_bin_width=args.distance_bin_width,
215
228
  angle_bin_width=args.angle_bin_width,
229
+ do_diagram=not args.no_diagram,
230
+ diagram_charge=args.diagram_charge,
216
231
  )
217
232
 
218
233
  analyzer = Analyzer(config)
@@ -0,0 +1,150 @@
1
+ """Per-molecule 2D structure diagrams marking the detected H-bond sites.
2
+
3
+ Builds an RDKit molecule from the COGNAC UDF topology (element from
4
+ ``Atom_Name`` + connectivity + a single frame's 3D coordinates) and renders a
5
+ 2D depiction with the **detected** donor / acceptor atoms highlighted
6
+ (red = donor, cyan = acceptor). One diagram is produced per unique molecular
7
+ species.
8
+
9
+ COGNAC UDFs (and OpenFF-interchange-exported ones in particular) usually carry
10
+ **no bond orders** (every ``order`` is 1.0), so aromaticity and C=O double
11
+ bonds are recovered from the 3D geometry via
12
+ ``rdkit.Chem.rdDetermineBonds.DetermineBondOrders`` (assumes a neutral, closed
13
+ shell molecule by default — override with ``charge``).
14
+
15
+ RDKit is an optional dependency: if it is not importable, or the molecule
16
+ cannot be perceived (e.g. an ion with the wrong ``charge``), diagram generation
17
+ is skipped with a warning rather than aborting the analysis.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from typing import Dict, List, Optional, Sequence, Tuple
22
+
23
+ # Two-letter elements first so "Cl"/"Br" are not truncated to "C"/"B".
24
+ _TWO_LETTER = ("Cl", "Br", "Si", "Na", "Mg", "Ca", "Fe", "Zn", "Li", "Se")
25
+ _ONE_LETTER = set("HCNOFPSKIBW")
26
+
27
+ DONOR_COLOR = (0.92, 0.30, 0.30) # red
28
+ ACCEPTOR_COLOR = (0.20, 0.75, 0.90) # cyan
29
+
30
+
31
+ def element_from_name(name: str) -> str:
32
+ """Best-effort element symbol from a UDF ``Atom_Name``.
33
+
34
+ Handles clean symbols (``"Cl"``, ``"O"``), element+index (``"C10"``,
35
+ ``"HO"`` → ``H``), and is case-insensitive on the trailing letter so
36
+ ``"CL"`` and ``"cl"`` both resolve to ``Cl``.
37
+ """
38
+ s = "".join(ch for ch in (name or "") if ch.isalpha())
39
+ if not s:
40
+ return ""
41
+ cap = s[0].upper() + s[1:2].lower()
42
+ for two in _TWO_LETTER:
43
+ if cap.startswith(two):
44
+ return two
45
+ if s[0].upper() in _ONE_LETTER:
46
+ return s[0].upper()
47
+ return s[0].upper()
48
+
49
+
50
+ def build_mol_from_topology(topology, coords, charge: int = 0):
51
+ """Build an RDKit ``Mol`` from a ``MoleculeTopology`` + 3D coordinates.
52
+
53
+ Returns ``(mol, None)`` on success or ``(None, reason)`` on failure.
54
+ ``coords`` is an ``(n_atoms, 3)`` array-like (Å). Atom order is preserved,
55
+ so caller-supplied atom indices map 1:1 onto the returned molecule.
56
+ """
57
+ try:
58
+ from rdkit import Chem
59
+ from rdkit.Chem import rdDetermineBonds
60
+ from rdkit.Geometry import Point3D
61
+ except ImportError:
62
+ return None, "rdkit not installed"
63
+
64
+ atoms = topology.atoms
65
+ if coords is None or len(coords) != len(atoms):
66
+ return None, "coordinate/atom count mismatch"
67
+
68
+ rw = Chem.RWMol()
69
+ for a in atoms:
70
+ sym = element_from_name(a.atom_name)
71
+ if not sym:
72
+ return None, f"could not resolve element for {a.atom_name!r}"
73
+ rw.AddAtom(Chem.Atom(sym))
74
+ for b in topology.bonds:
75
+ rw.AddBond(int(b.atom1), int(b.atom2), Chem.BondType.SINGLE)
76
+ mol = rw.GetMol()
77
+
78
+ conf = Chem.Conformer(mol.GetNumAtoms())
79
+ for i in range(mol.GetNumAtoms()):
80
+ x, y, z = coords[i]
81
+ conf.SetAtomPosition(i, Point3D(float(x), float(y), float(z)))
82
+ mol.AddConformer(conf, assignId=True)
83
+
84
+ try:
85
+ # Perceive bond orders / aromaticity from geometry (bonds already set).
86
+ rdDetermineBonds.DetermineBondOrders(mol, charge=int(charge))
87
+ Chem.SanitizeMol(mol)
88
+ except Exception as e: # noqa: BLE001 - any perception failure → skip
89
+ return None, f"bond-order perception failed: {type(e).__name__}: {e}"
90
+ return mol, None
91
+
92
+
93
+ def draw_hbond_diagram(
94
+ mol,
95
+ donor_atoms: Sequence[int],
96
+ acceptor_atoms: Sequence[int],
97
+ png_path: str,
98
+ svg_path: Optional[str] = None,
99
+ legend: str = "",
100
+ donor_note: str = "donor",
101
+ acceptor_note: str = "acceptor",
102
+ ) -> List[str]:
103
+ """Render ``mol`` 2D with donor (red) / acceptor (cyan) atoms highlighted.
104
+
105
+ ``donor_atoms`` / ``acceptor_atoms`` are atom indices into ``mol`` (the
106
+ heavy donor atom, e.g. amide N, and the acceptor atom, e.g. carbonyl O).
107
+ Non-polar H are removed for clarity; the highlighted heavy atoms are tracked
108
+ across H removal via an atom property. Returns the list of written paths.
109
+ """
110
+ from rdkit import Chem
111
+ from rdkit.Chem import AllChem
112
+ from rdkit.Chem.Draw import rdMolDraw2D
113
+
114
+ work = Chem.Mol(mol)
115
+ for i in donor_atoms:
116
+ work.GetAtomWithIdx(int(i)).SetProp("_hb_role", "donor")
117
+ for i in acceptor_atoms:
118
+ work.GetAtomWithIdx(int(i)).SetProp("_hb_role", "acceptor")
119
+
120
+ heavy = Chem.RemoveHs(work)
121
+ AllChem.Compute2DCoords(heavy) # 2D depiction, not a projection of the 3D pose
122
+
123
+ hi_atoms: List[int] = []
124
+ atom_cols: Dict[int, Tuple[float, float, float]] = {}
125
+ for a in heavy.GetAtoms():
126
+ if not a.HasProp("_hb_role"):
127
+ continue
128
+ role = a.GetProp("_hb_role")
129
+ hi_atoms.append(a.GetIdx())
130
+ atom_cols[a.GetIdx()] = DONOR_COLOR if role == "donor" else ACCEPTOR_COLOR
131
+ a.SetProp("atomNote", donor_note if role == "donor" else acceptor_note)
132
+
133
+ written: List[str] = []
134
+ targets = [("png", png_path)] + ([("svg", svg_path)] if svg_path else [])
135
+ for kind, path in targets:
136
+ drawer = (rdMolDraw2D.MolDraw2DCairo if kind == "png"
137
+ else rdMolDraw2D.MolDraw2DSVG)(1100, 820)
138
+ op = drawer.drawOptions()
139
+ op.legendFontSize = 20
140
+ op.bondLineWidth = 2
141
+ rdMolDraw2D.PrepareAndDrawMolecule(
142
+ drawer, heavy, legend=legend,
143
+ highlightAtoms=hi_atoms, highlightAtomColors=atom_cols,
144
+ )
145
+ drawer.FinishDrawing()
146
+ data = drawer.GetDrawingText()
147
+ with open(path, "wb" if kind == "png" else "w") as f:
148
+ f.write(data)
149
+ written.append(path)
150
+ return written
@@ -14,8 +14,10 @@ from .core import (
14
14
  POINT_CHARGE,
15
15
  AssignResult,
16
16
  MoleculeChargeTemplate,
17
+ RestoreResult,
17
18
  assign_charges_to_bulk,
18
19
  read_molecule_charges,
20
+ restore_formal_charge,
19
21
  )
20
22
 
21
23
  __all__ = [
@@ -23,6 +25,8 @@ __all__ = [
23
25
  "POINT_CHARGE",
24
26
  "AssignResult",
25
27
  "MoleculeChargeTemplate",
28
+ "RestoreResult",
26
29
  "assign_charges_to_bulk",
27
30
  "read_molecule_charges",
31
+ "restore_formal_charge",
28
32
  ]
@@ -0,0 +1,117 @@
1
+ """CLI: OCTA/COGNAC UDF の per-atom 電荷操作。
2
+
3
+ # 単分子 UDF の電荷をバルク UDF の同名分子へ転写
4
+ python -m abmptools.udfcharge transfer --template mol.udf --bulk bulk.udf \
5
+ [--out bulk_charged.udf] [--mol-name NAME] [--mol-index I] \
6
+ [--no-verify-types] [--non-strict]
7
+
8
+ # 中和 (Σq≈0) された 1 分子 UDF の電荷を指定形式電荷に復元
9
+ python -m abmptools.udfcharge restore --udf mol.udf --formal-charge 12 \
10
+ [--out mol_q+12.udf] [--mol-index I] [--mol-name NAME]
11
+
12
+ 後方互換: サブコマンド省略時は ``transfer`` として解釈する。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import logging
19
+ import sys
20
+
21
+ from .core import (
22
+ assign_charges_to_bulk,
23
+ read_molecule_charges,
24
+ restore_formal_charge,
25
+ )
26
+
27
+ _SUBCOMMANDS = {"transfer", "restore"}
28
+
29
+
30
+ def _run_transfer(args) -> int:
31
+ tmpl = read_molecule_charges(
32
+ args.template, mol_name=args.mol_name, mol_index=args.mol_index,
33
+ )
34
+ res = assign_charges_to_bulk(
35
+ args.bulk, tmpl, args.out,
36
+ verify_atom_types=not args.no_verify_types,
37
+ strict=not args.non_strict,
38
+ )
39
+ print(f"template : {tmpl.mol_name} (n_atoms={tmpl.n_atoms}, "
40
+ f"net_charge={tmpl.net_charge:+.4f})")
41
+ print(f"assigned : {res.n_molecules_assigned}/{res.n_molecules_total} molecules")
42
+ if res.skipped_indices:
43
+ print(f"skipped : {res.skipped_indices}")
44
+ print(f"output : {res.out_path}")
45
+ return 0
46
+
47
+
48
+ def _run_restore(args) -> int:
49
+ res = restore_formal_charge(
50
+ args.udf, args.formal_charge, args.out,
51
+ mol_index=args.mol_index, mol_name=args.mol_name, mode=args.mode,
52
+ )
53
+ detail = (f"λ={res.lam:.8f}" if res.mode == "proportional"
54
+ else f"shift=S/N={res.shift:.8f}")
55
+ print(f"molecule : {res.mol_name} (n_atoms={res.n_atoms})")
56
+ print(f"mode : {res.mode}")
57
+ print(f"input total : {res.input_total:+.6f} (中和済み想定)")
58
+ print(f"formal charge: {res.formal_charge:+d} ({detail})")
59
+ print(f"output total : {res.output_total:+.6f}")
60
+ print(f"output : {res.out_path}")
61
+ return 0
62
+
63
+
64
+ def main(argv=None) -> int:
65
+ argv = list(sys.argv[1:] if argv is None else argv)
66
+ # 後方互換: 旧フラット呼び出し (transfer の --template ...) を許容
67
+ if argv and argv[0] not in _SUBCOMMANDS and not argv[0].startswith("-"):
68
+ pass # 不明な位置引数 → そのまま argparse にエラーさせる
69
+ elif argv and argv[0].startswith("-") and "--template" in argv:
70
+ argv = ["transfer"] + argv
71
+
72
+ p = argparse.ArgumentParser(
73
+ prog="python -m abmptools.udfcharge",
74
+ description="OCTA/COGNAC UDF の per-atom 電荷操作 (転写 / 形式電荷復元)。",
75
+ )
76
+ sub = p.add_subparsers(dest="cmd")
77
+
78
+ pt = sub.add_parser("transfer",
79
+ help="単分子 UDF の電荷をバルク UDF の同名分子へ転写")
80
+ pt.add_argument("--template", required=True, help="電荷を持つ単分子 UDF")
81
+ pt.add_argument("--bulk", required=True, help="割り当て先のバルク UDF")
82
+ pt.add_argument("--out", default=None, help="出力 (省略時 <bulk>_charged.udf)")
83
+ pt.add_argument("--mol-name", default=None)
84
+ pt.add_argument("--mol-index", type=int, default=None)
85
+ pt.add_argument("--no-verify-types", action="store_true")
86
+ pt.add_argument("--non-strict", action="store_true")
87
+ pt.add_argument("-v", "--verbose", action="store_true")
88
+ pt.set_defaults(func=_run_transfer)
89
+
90
+ pr = sub.add_parser("restore",
91
+ help="中和済み 1 分子 UDF の電荷を指定形式電荷に復元")
92
+ pr.add_argument("--udf", required=True, help="中和 (Σq≈0) された 1 分子 UDF")
93
+ pr.add_argument("--formal-charge", type=int, required=True,
94
+ help="目標の形式電荷 (整数)")
95
+ pr.add_argument("--out", default=None, help="出力 (省略時 <udf>_q<±S>.udf)")
96
+ pr.add_argument("--mode", choices=["proportional", "uniform"],
97
+ default="proportional",
98
+ help="中和ルール: proportional (|q| 比例、 既定) / uniform (均等分配)")
99
+ pr.add_argument("--mol-name", default=None)
100
+ pr.add_argument("--mol-index", type=int, default=0)
101
+ pr.add_argument("-v", "--verbose", action="store_true")
102
+ pr.set_defaults(func=_run_restore)
103
+
104
+ args = p.parse_args(argv)
105
+ if not getattr(args, "cmd", None):
106
+ p.print_help()
107
+ return 2
108
+
109
+ logging.basicConfig(
110
+ level=logging.INFO if getattr(args, "verbose", False) else logging.WARNING,
111
+ format="%(levelname)s %(name)s: %(message)s",
112
+ )
113
+ return args.func(args)
114
+
115
+
116
+ if __name__ == "__main__":
117
+ sys.exit(main())