molforge 0.2.0__tar.gz → 0.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 (205) hide show
  1. {molforge-0.2.0 → molforge-0.3.0}/CHANGELOG.md +398 -159
  2. {molforge-0.2.0 → molforge-0.3.0}/PKG-INFO +4 -3
  3. {molforge-0.2.0 → molforge-0.3.0}/README.md +2 -1
  4. {molforge-0.2.0 → molforge-0.3.0}/pyproject.toml +1 -1
  5. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/__init__.py +1 -1
  6. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/io/__init__.py +16 -5
  7. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/io/dispatch.py +16 -6
  8. molforge-0.3.0/src/molforge/io/mol2.py +361 -0
  9. molforge-0.3.0/src/molforge/io/pdbqt.py +265 -0
  10. molforge-0.3.0/src/molforge/io/pqr.py +232 -0
  11. molforge-0.3.0/src/molforge/io/sdf.py +309 -0
  12. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/docking/__init__.py +1 -1
  13. molforge-0.3.0/src/molforge/wrappers/docking/diffdock.py +372 -0
  14. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/docking/vina.py +11 -8
  15. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/folding/__init__.py +0 -3
  16. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/folding/_base.py +2 -2
  17. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/md/__init__.py +3 -2
  18. molforge-0.3.0/src/molforge/wrappers/md/gromacs.py +671 -0
  19. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/md/openmm.py +52 -6
  20. molforge-0.3.0/tests/fixtures/pdb/ala_tripeptide_heavy.pdb +26 -0
  21. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/io/test_dispatch.py +30 -5
  22. molforge-0.3.0/tests/unit/io/test_mol2.py +353 -0
  23. molforge-0.3.0/tests/unit/io/test_pdbqt.py +244 -0
  24. molforge-0.3.0/tests/unit/io/test_pqr.py +205 -0
  25. molforge-0.3.0/tests/unit/io/test_sdf.py +256 -0
  26. molforge-0.3.0/tests/unit/wrappers/test_diffdock.py +308 -0
  27. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_docking_base.py +0 -30
  28. molforge-0.3.0/tests/unit/wrappers/test_gromacs.py +588 -0
  29. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_md_base.py +0 -45
  30. molforge-0.3.0/tests/unit/wrappers/test_openmm.py +235 -0
  31. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_rfdiffusion.py +146 -0
  32. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_rosettafold.py +0 -26
  33. molforge-0.2.0/src/molforge/io/mol2.py +0 -33
  34. molforge-0.2.0/src/molforge/io/pdbqt.py +0 -33
  35. molforge-0.2.0/src/molforge/io/pqr.py +0 -33
  36. molforge-0.2.0/src/molforge/io/sdf.py +0 -33
  37. molforge-0.2.0/src/molforge/wrappers/docking/diffdock.py +0 -54
  38. molforge-0.2.0/src/molforge/wrappers/folding/rosetta.py +0 -55
  39. molforge-0.2.0/src/molforge/wrappers/md/gromacs.py +0 -78
  40. molforge-0.2.0/tests/unit/wrappers/test_openmm.py +0 -157
  41. {molforge-0.2.0 → molforge-0.3.0}/.gitignore +0 -0
  42. {molforge-0.2.0 → molforge-0.3.0}/LICENSE +0 -0
  43. {molforge-0.2.0 → molforge-0.3.0}/data/README.md +0 -0
  44. {molforge-0.2.0 → molforge-0.3.0}/notebooks/README.md +0 -0
  45. {molforge-0.2.0 → molforge-0.3.0}/plugins/README.md +0 -0
  46. {molforge-0.2.0 → molforge-0.3.0}/plugins/example_plugin/README.md +0 -0
  47. {molforge-0.2.0 → molforge-0.3.0}/plugins/example_plugin/pyproject.toml +0 -0
  48. {molforge-0.2.0 → molforge-0.3.0}/requirements/README.md +0 -0
  49. {molforge-0.2.0 → molforge-0.3.0}/scripts/README.md +0 -0
  50. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/core/__init__.py +0 -0
  51. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/core/atom.py +0 -0
  52. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/core/atom_array.py +0 -0
  53. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/core/chain.py +0 -0
  54. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/core/constants.py +0 -0
  55. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/core/metadata_keys.py +0 -0
  56. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/core/protein.py +0 -0
  57. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/core/residue.py +0 -0
  58. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/docking/__init__.py +0 -0
  59. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ensembles/__init__.py +0 -0
  60. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ensembles/clustering.py +0 -0
  61. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ensembles/consensus.py +0 -0
  62. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ensembles/density.py +0 -0
  63. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ensembles/geometry.py +0 -0
  64. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ensembles/weighting.py +0 -0
  65. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/generative.py +0 -0
  66. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/io/fasta.py +0 -0
  67. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/io/mmcif.py +0 -0
  68. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/io/pdb.py +0 -0
  69. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/io/pdb_alphafold.py +0 -0
  70. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/md/__init__.py +0 -0
  71. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/metrics/__init__.py +0 -0
  72. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/metrics/dockq.py +0 -0
  73. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/metrics/gdt.py +0 -0
  74. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/metrics/lddt.py +0 -0
  75. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/metrics/tm.py +0 -0
  76. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ml/__init__.py +0 -0
  77. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ml/embeddings.py +0 -0
  78. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ml/graph.py +0 -0
  79. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ml/sequence_features.py +0 -0
  80. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/ml/structure_features.py +0 -0
  81. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/plugins/__init__.py +0 -0
  82. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/plugins/registry.py +0 -0
  83. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/py.typed +0 -0
  84. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/sequence/__init__.py +0 -0
  85. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/sequence/alignment.py +0 -0
  86. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/sequence/composition.py +0 -0
  87. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/sequence/matrices.py +0 -0
  88. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/sequence/mutations.py +0 -0
  89. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/structure/__init__.py +0 -0
  90. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/structure/contacts.py +0 -0
  91. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/structure/dihedrals.py +0 -0
  92. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/structure/dssp.py +0 -0
  93. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/structure/geometry.py +0 -0
  94. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/structure/rmsd.py +0 -0
  95. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/structure/sasa.py +0 -0
  96. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/structure/superposition.py +0 -0
  97. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/validation/__init__.py +0 -0
  98. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/validation/criteria.py +0 -0
  99. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/validation/orchestration.py +0 -0
  100. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/validation/verdict.py +0 -0
  101. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/__init__.py +0 -0
  102. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/docking/_base.py +0 -0
  103. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/docking/prep.py +0 -0
  104. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/folding/alphafold.py +0 -0
  105. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/folding/boltz.py +0 -0
  106. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/folding/esmfold.py +0 -0
  107. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/folding/rosettafold.py +0 -0
  108. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/generative/__init__.py +0 -0
  109. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/generative/proteinmpnn.py +0 -0
  110. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/generative/rfdiffusion.py +0 -0
  111. {molforge-0.2.0 → molforge-0.3.0}/src/molforge/wrappers/md/_base.py +0 -0
  112. {molforge-0.2.0 → molforge-0.3.0}/tests/__init__.py +0 -0
  113. {molforge-0.2.0 → molforge-0.3.0}/tests/benchmarks/__init__.py +0 -0
  114. {molforge-0.2.0 → molforge-0.3.0}/tests/benchmarks/conftest.py +0 -0
  115. {molforge-0.2.0 → molforge-0.3.0}/tests/benchmarks/test_perf.py +0 -0
  116. {molforge-0.2.0 → molforge-0.3.0}/tests/conftest.py +0 -0
  117. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/cif/dipeptide.cif +0 -0
  118. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/fasta/.gitkeep +0 -0
  119. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/fasta/multiline_with_digits.fasta +0 -0
  120. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/fasta/simple.fasta +0 -0
  121. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/.gitkeep +0 -0
  122. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/alphafold_mock.pdb +0 -0
  123. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/dipeptide.pdb +0 -0
  124. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/helix.pdb +0 -0
  125. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/mini_beta_sheet.pdb +0 -0
  126. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/mini_complex_bad.pdb +0 -0
  127. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/mini_complex_good.pdb +0 -0
  128. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/mini_complex_native.pdb +0 -0
  129. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/mini_ensemble.pdb +0 -0
  130. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/mini_mixed.pdb +0 -0
  131. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/mini_with_ligand.pdb +0 -0
  132. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/multi_model.pdb +0 -0
  133. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/real_small_protein.pdb +0 -0
  134. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/real_with_altloc_sidechains.pdb +0 -0
  135. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/real_with_ligand_realistic.pdb +0 -0
  136. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/tripeptide.pdb +0 -0
  137. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/with_altloc.pdb +0 -0
  138. {molforge-0.2.0 → molforge-0.3.0}/tests/fixtures/pdb/with_insertion_code.pdb +0 -0
  139. {molforge-0.2.0 → molforge-0.3.0}/tests/integration/__init__.py +0 -0
  140. {molforge-0.2.0 → molforge-0.3.0}/tests/integration/test_fixtures.py +0 -0
  141. {molforge-0.2.0 → molforge-0.3.0}/tests/integration/test_real_fixtures.py +0 -0
  142. {molforge-0.2.0 → molforge-0.3.0}/tests/integration/test_smoke.py +0 -0
  143. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/__init__.py +0 -0
  144. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/core/__init__.py +0 -0
  145. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/core/test_atom_array.py +0 -0
  146. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/core/test_constants.py +0 -0
  147. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/core/test_core_smoke.py +0 -0
  148. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/core/test_hierarchy.py +0 -0
  149. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/core/test_metadata_keys.py +0 -0
  150. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/docking/__init__.py +0 -0
  151. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/docking/test_docking_smoke.py +0 -0
  152. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ensembles/__init__.py +0 -0
  153. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ensembles/conftest.py +0 -0
  154. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ensembles/test_clustering.py +0 -0
  155. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ensembles/test_consensus.py +0 -0
  156. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ensembles/test_density.py +0 -0
  157. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ensembles/test_geometry.py +0 -0
  158. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ensembles/test_smoke.py +0 -0
  159. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ensembles/test_weighting.py +0 -0
  160. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/io/__init__.py +0 -0
  161. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/io/test_alphafold.py +0 -0
  162. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/io/test_fasta.py +0 -0
  163. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/io/test_io_smoke.py +0 -0
  164. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/io/test_mmcif.py +0 -0
  165. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/io/test_pdb.py +0 -0
  166. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/md/__init__.py +0 -0
  167. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/metrics/__init__.py +0 -0
  168. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/metrics/test_dockq.py +0 -0
  169. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/metrics/test_gdt.py +0 -0
  170. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/metrics/test_lddt.py +0 -0
  171. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/metrics/test_tm.py +0 -0
  172. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ml/__init__.py +0 -0
  173. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ml/test_embeddings.py +0 -0
  174. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ml/test_graph.py +0 -0
  175. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ml/test_sequence_features.py +0 -0
  176. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/ml/test_structure_features.py +0 -0
  177. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/plugins/__init__.py +0 -0
  178. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/plugins/test_plugins_smoke.py +0 -0
  179. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/plugins/test_registry.py +0 -0
  180. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/sequence/__init__.py +0 -0
  181. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/sequence/test_alignment.py +0 -0
  182. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/sequence/test_composition.py +0 -0
  183. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/sequence/test_matrices.py +0 -0
  184. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/sequence/test_mutations.py +0 -0
  185. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/structure/__init__.py +0 -0
  186. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/structure/test_contacts.py +0 -0
  187. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/structure/test_dihedrals.py +0 -0
  188. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/structure/test_dssp.py +0 -0
  189. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/structure/test_geometry.py +0 -0
  190. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/structure/test_rmsd.py +0 -0
  191. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/structure/test_sasa.py +0 -0
  192. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/structure/test_structure_smoke.py +0 -0
  193. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/structure/test_superposition.py +0 -0
  194. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/test_typing.py +0 -0
  195. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/validation/test_criteria.py +0 -0
  196. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/validation/test_orchestration.py +0 -0
  197. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/validation/test_verdict.py +0 -0
  198. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/__init__.py +0 -0
  199. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_alphafold.py +0 -0
  200. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_boltz.py +0 -0
  201. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_esmfold.py +0 -0
  202. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_folding_base.py +0 -0
  203. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_prep.py +0 -0
  204. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_proteinmpnn.py +0 -0
  205. {molforge-0.2.0 → molforge-0.3.0}/tests/unit/wrappers/test_vina.py +0 -0
@@ -7,6 +7,249 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+ - **`molforge.io.read_pqr` / `write_pqr` are implemented.** PQR
12
+ (PDB2PQR / APBS) was a committed import path but a
13
+ `NotImplementedError` stub; it now parses and writes PQR files,
14
+ completing the format-I/O backlog (SDF, MOL2, PDBQT, PQR all real).
15
+ PQR is a PDB-like format that appends per-atom partial charge and
16
+ atomic radius as whitespace-separated trailing fields. Unlike PDB
17
+ or PDBQT, PQR is **not** strictly fixed-column past the
18
+ coordinates — different generators (PDB2PQR, AMBER, CHARMM, APBS)
19
+ emit different widths. The reader handles all of them by parsing
20
+ columns 1-54 as fixed (the atom record through coordinates,
21
+ PDB-compatible) and whitespace-splitting the remainder for charge
22
+ and radius. Charges land on `AtomArray.charge`; radii land on
23
+ `protein.metadata["radii"]` as a per-atom list (no native
24
+ `radius` field on `AtomArray` — electrostatics is a small enough
25
+ slice of the surface that this didn't warrant a core-type change).
26
+ The writer is the symmetric operation: it calls `write_pdb_string`,
27
+ truncates each atom line at column 54, then appends
28
+ `charge radius`. When no radii are recorded in metadata, a default
29
+ 1.5 Å is used (a reasonable middle-of-the-road heavy-atom radius
30
+ that lets a charge-only Protein still be written as PQR). 19 new
31
+ tests in `tests/unit/io/test_pqr.py` covering the charge/radius
32
+ extractor (clean tokens, trailing garbage, defaults), reading from
33
+ string and from disk, the dispatcher routes, two variant-width
34
+ tails seen in real-world PDB2PQR / AMBER output, the full
35
+ round-trip (coordinates, charges, radii), and the default-radius
36
+ writer path; `pqr.py` is at 92.0% coverage.
37
+ `test_dispatch.py`: the two stub-format tests (load and save) now
38
+ monkeypatch a synthetic planned format rather than depending on
39
+ any real format being unimplemented — the dispatcher's
40
+ planned-readers fallback machinery still exists for future use, so
41
+ the tests are still meaningful. The `_PLANNED_READERS` dict in
42
+ `dispatch.py` is now empty.
43
+ - **`molforge.io.read_pdbqt` / `write_pdbqt` are implemented.** PDBQT
44
+ (AutoDock / Vina) was a committed import path but a
45
+ `NotImplementedError` stub; it now parses and writes PDBQT files.
46
+ PDBQT is a thin extension of PDB — columns 1-66 are PDB-compatible,
47
+ columns 71-76 hold the per-atom partial charge, and columns 78-79
48
+ hold the AutoDock atom type (`C`, `OA`, `HD`, `NA`, ...). The
49
+ reader reuses `molforge.io.read_pdb_string` for the heavy lifting
50
+ (atom-array construction, altloc handling, entity classification,
51
+ multi-MODEL parsing) and post-processes each atom line to pick up
52
+ the extra columns: charges are written to `AtomArray.charge`,
53
+ AutoDock types land on `protein.metadata["autodock_types"]` as a
54
+ per-atom list. `ROOT` / `BRANCH` / `TORSDOF` rotatable-bond markers
55
+ are read-tolerated (recognised and skipped — `AtomArray` doesn't
56
+ carry bond topology). The writer is the symmetric operation: it
57
+ calls `write_pdb_string`, then rewrites each `ATOM` / `HETATM` line
58
+ to append the charge and AutoDock-type columns; when no AutoDock
59
+ type is recorded in metadata, the element is used as a documented
60
+ best-effort fallback. Round-tripping preserves coordinates,
61
+ charges, and types. The Vina wrapper's pose parser is refactored to
62
+ go through `read_pdbqt_string` rather than its previous "truncate
63
+ every atom line to 66 columns and feed to the PDB reader" hack — so
64
+ per-atom charges now propagate to `Pose.ligand` instead of being
65
+ silently discarded. 23 new tests in `tests/unit/io/test_pdbqt.py`
66
+ covering the column extractors (charge and AutoDock type, including
67
+ the whitespace-split fallback), reading from string and from disk,
68
+ the dispatcher routes, the full round-trip (coordinates, charges,
69
+ types), the element-fallback writer path, multi-MODEL handling
70
+ (Vina pose output), and `ROOT` / `BRANCH` / `TORSDOF` tolerance;
71
+ `pdbqt.py` is at 93.8% coverage. The `test_dispatch.py` stub-format
72
+ tests are updated to use `.pqr` (the only remaining stub format).
73
+ - **`molforge.io.read_mol2` / `write_mol2` are implemented.** MOL2
74
+ (Tripos) was a committed import path but a `NotImplementedError`
75
+ stub; it now parses and writes Tripos MOL2 files. Like the SDF
76
+ reader, `read_mol2` is multi-molecule by default and returns
77
+ `list[Protein]` (the format supports multi-molecule files via
78
+ repeated `@<TRIPOS>MOLECULE` markers, common in docking output
79
+ libraries). The reader populates coordinates, elements (extracted
80
+ from the prefix of the Tripos atom type — `C.ar` → `C`, `N.am` →
81
+ `N`, two-letter `Cl`/`Br` preserved), atom names, per-atom partial
82
+ charges from the atom line's last column, and substructure info
83
+ (residue id / name from the MOL2 `subst_id` / `subst_name`
84
+ columns). Short atom lines (optional trailing columns omitted) and
85
+ non-conforming writers that emit `***` for the subst_id or a
86
+ non-numeric charge are tolerated with silent fallbacks rather
87
+ than crashing the whole molecule. Bond orders, ring information,
88
+ stereochemistry, and the `@<TRIPOS>SUBSTRUCTURE` /
89
+ `@<TRIPOS>CRYSIN` / `@<TRIPOS>UNITY` sections are intentionally
90
+ dropped — those need a chemistry toolkit. The writer emits a
91
+ minimal, spec-conformant MOL2 with an empty `@<TRIPOS>BOND` section
92
+ (some downstream tools error without the tag). The MOLECULE header
93
+ declares the atom count; a mismatch between that and the ATOM
94
+ section raises a clear error. `read_mol2` is wired into
95
+ `molforge.io.load`. 34 new tests in `tests/unit/io/test_mol2.py`
96
+ covering single/multi-molecule reading, Tripos atom-type element
97
+ extraction, two-letter elements, partial charges, optional-column
98
+ fallbacks, blank-line tolerance, every error path, dispatcher
99
+ integration, and the full round-trip; mol2.py is at 94.4% coverage
100
+ (the residual misses are unreachable defensive branches).
101
+ - **`molforge.io.read_sdf` / `write_sdf` are implemented.** SDF was a
102
+ committed import path but a `NotImplementedError` stub; it now
103
+ parses and writes V2000 SDF / MOL files. The reader is multi-
104
+ molecule by default, returning a `list[Protein]` (a single-molecule
105
+ `.mol` file still returns a one-element list, keeping the return
106
+ type uniform across callers). The atom block, title line, and the
107
+ ``> <Name>`` / value property block all round-trip; properties land
108
+ on `Protein.metadata["properties"]`. The implementation uses no
109
+ chemistry toolkit — the V2000 atom block has a fixed positional
110
+ layout that's enough for everything molforge does downstream
111
+ (coordinates, pose ranking, distance calculations). Bond orders,
112
+ aromaticity, and stereochemistry are intentionally dropped; users
113
+ who need them should call RDKit directly. V3000 files are detected
114
+ and raise a clear error pointing at conversion paths. `read_sdf` is
115
+ wired into `molforge.io.load`, so `load("foo.sdf")` works. The
116
+ DiffDock wrapper, which previously parsed SDF inline, now goes
117
+ through `molforge.io.sdf.read_sdf_string` — the inline
118
+ `_ligand_from_sdf` helper is removed (-1 duplicated parser).
119
+ `api-stability.md` is updated; MOL2, PDBQT, and PQR remain
120
+ tentative. 26 new tests in `tests/unit/io/test_sdf.py` covering
121
+ single/multi-molecule reading, the title and property block,
122
+ round-trip writing, dispatcher integration, and every error path;
123
+ the eight DiffDock tests that exercised the inline parser are
124
+ removed (now subsumed by the SDF tests).
125
+ - **`GROMACS` is now a real MD engine.** `GROMACS`
126
+ (`molforge.wrappers.md`) was a coherent stub whose `prepare` /
127
+ `minimize` / `run` all raised `NotImplementedError`; it is now
128
+ fully implemented. [GROMACS](https://www.gromacs.org/) is a
129
+ command-line program (`gmx`), not a Python library, so the wrapper
130
+ drives it as a subprocess. One `prepare` / `minimize` / `run` cycle
131
+ maps onto the standard GROMACS workflow: `prepare` runs
132
+ `pdb2gmx` → `editconf` → (optionally) `solvate`; `minimize` writes
133
+ a steepest-descent `.mdp`, then `grompp` → `mdrun`; `run` writes a
134
+ production `.mdp`, runs `grompp` → `mdrun`, then reads the frames
135
+ back with `trjconv` and per-frame energies with `gmx energy`. All
136
+ state for a simulation lives in one run directory whose path is
137
+ carried on `Simulation.engine_handle` (and mirrored in
138
+ `metadata["run_dir"]`), so `minimize` and `run` continue from
139
+ whatever `prepare` produced. Trajectory frames are read back by
140
+ asking GROMACS itself (`gmx trjconv`) to convert its binary `.xtc`
141
+ to a multi-model PDB, which molforge's own PDB reader then parses —
142
+ the wrapper deliberately takes no dependency on a third-party
143
+ binary-trajectory library. Three small fixed-layout parsers
144
+ (`.gro` coordinates, multi-model PDB, `.xvg` columns) handle the
145
+ GROMACS outputs directly. `gmx` is resolved lazily via
146
+ `shutil.which`, so construction never touches the filesystem; a
147
+ clear `MDEngineNotInstalledError` (pointing at OpenMM) is raised
148
+ when it is absent. A constructor flag covers the water model, box
149
+ margin/type, and a `verbose` pass-through. 36 tests (a new
150
+ `test_gromacs.py`), covering construction and validation, `gmx`
151
+ resolution, the three parsers and their error paths, and the full
152
+ `prepare` / `minimize` / `run` pipeline driven by a mocked
153
+ `subprocess.run` that writes the files each `gmx` step would
154
+ produce; the wrapper module is at 94% coverage (the residual
155
+ misses are defensive `except` branches for corrupt output GROMACS
156
+ would never actually emit). The 6 obsolete `TestGROMACSStub` tests
157
+ are removed.
158
+ - **`DiffDock` is now a real docking engine.** `DiffDock`
159
+ (`molforge.wrappers.docking`) was a coherent stub whose `dock()`
160
+ raised `NotImplementedError`; it is now fully implemented.
161
+ [DiffDock](https://github.com/gcorso/DiffDock) is a
162
+ diffusion-generative model for *blind* protein-ligand docking — it
163
+ needs no search box, sampling poses over the whole receptor and
164
+ ranking them with a learned confidence model. Like the
165
+ `RoseTTAFold` wrapper, DiffDock ships as a research repository
166
+ rather than a pip package, so the wrapper drives it as a
167
+ subprocess: it locates the cloned repo (`$DIFFDOCK_HOME` or an
168
+ explicit `repo_dir`), materializes the receptor to PDB, accepts the
169
+ ligand as a SMILES string or a path to an SDF/MOL2 file, runs
170
+ `python -m inference`, and parses the ranked
171
+ `rank{N}_confidence{C}.sdf` output into a `DockingResult`. DiffDock
172
+ reports a *confidence* (higher = better), the opposite of Vina's
173
+ affinity convention; the wrapper stores the raw value in
174
+ `Pose.metadata["confidence"]` and sets `Pose.score` to its
175
+ negation, so `score` ascending is best-first for every engine. SDF
176
+ poses are parsed by reading the V2000 atom block directly (molforge's
177
+ RDKit-backed SDF reader is still a stub, and the atom block —
178
+ 3D coordinates plus element symbols — needs no chemistry toolkit).
179
+ A constructor flag covers `samples_per_complex`, `inference_steps`,
180
+ and `batch_size`. 30 tests (a new `test_diffdock.py`), covering
181
+ construction and validation, install resolution, SDF and
182
+ confidence-from-filename parsing, and the `_run_cli` subprocess seam
183
+ via a mocked `subprocess.run`; the wrapper module is at 100%
184
+ coverage. The 4 obsolete `TestDiffDockStub` tests are removed.
185
+ - **OpenMM wrapper test coverage raised from 24% to 95%.** The
186
+ OpenMM tests previously gated every real path behind
187
+ `skipif(openmm installed)` — so `prepare` / `minimize` / `run`
188
+ were exercised by *nothing*: when openmm was absent they couldn't
189
+ run, and when present the negative-path tests skipped. The file is
190
+ restructured into a dependency-free half (construction, the
191
+ force-field registry, the missing-dependency errors) and a new
192
+ `TestRealOpenMM` class that runs `prepare` / `minimize` / `run`
193
+ end to end against a real OpenMM install — system building,
194
+ hydrogen addition, the minimizer, the integration loop, trajectory
195
+ assembly, and argument validation. A new chemically complete
196
+ heavy-atom fixture, `tests/fixtures/pdb/ala_tripeptide_heavy.pdb`
197
+ (ALA-ALA-ALA with all standard heavy atoms plus the C-terminal
198
+ OXT), gives the force field something it can template. The
199
+ real-engine tests are deliberately *not* marked `slow` — the
200
+ tripeptide is tiny and a 20-step run is sub-second — so they run
201
+ in the normal suite wherever openmm is installed, and skip cleanly
202
+ (9 skips) where it isn't. A new CI job, `md-openmm`, installs the
203
+ `[md]` extra and runs the MD wrapper tests on every push so
204
+ `TestRealOpenMM` is actually exercised.
205
+ - **RFdiffusion wrapper test coverage raised from 84% to 99%.** The
206
+ `_run_cli` subprocess seam of `wrappers.generative.rfdiffusion` —
207
+ previously untested — now has direct tests via a mocked
208
+ `subprocess.run` (no RFdiffusion or torch needed): command and
209
+ Hydra-arg assembly, the `design_*.pdb` output-parsing path, the
210
+ no-output `RuntimeError`, `CalledProcessError` → `RuntimeError`
211
+ translation, the public `generate()` entry point, and
212
+ `contigs` / `symmetry` pass-through. 5 new tests (16 → 21 in the
213
+ file). Mirrors the ProteinMPNN coverage work from the previous
214
+ cycle.
215
+
216
+ ### Removed
217
+ - **BREAKING `molforge.wrappers.folding.Rosetta` removed.** The `Rosetta`
218
+ name was a placeholder from the `0.0.x` series whose meaning was
219
+ ambiguous — it could read as PyRosetta (the classical
220
+ sequence-design library) or RoseTTAFold (the deep-learning
221
+ model). The real wrapper now lives at `RoseTTAFold`. `Rosetta`
222
+ had been kept this cycle as a `DeprecationWarning`-emitting alias,
223
+ but since it never appeared in a tagged release, carrying it —
224
+ and the day-one deprecation it implies — into the 1.0 stable
225
+ surface added nothing. It is removed outright: import
226
+ `RoseTTAFold` instead. A PyRosetta wrapper, if ever added, would
227
+ be a separate class (`PyRosetta`) in its own module, since
228
+ PyRosetta's surface is far wider than the `FoldingEngine`
229
+ contract. The 3 alias tests are removed (folding-engine count
230
+ unchanged: ESMFold, AlphaFold, Boltz, RoseTTAFold).
231
+
232
+ ### Fixed
233
+ +- **`OpenMM.prepare()` now adds missing hydrogens, so heavy-atom
234
+ structures are usable.** A force field needs explicit hydrogens,
235
+ but `prepare()` called `ForceField.createSystem()` directly on
236
+ whatever atoms the input had. Heavy-atom structures — the normal
237
+ output of every folding and docking engine, and what most PDB
238
+ files on disk contain — therefore failed with a cryptic
239
+ OpenMM "no template found for residue" error, making the wrapper
240
+ effectively unusable on exactly the structures molforge produces.
241
+ `prepare()` now runs `Modeller.addHydrogens()` before building the
242
+ system; the step is idempotent, so an already-protonated structure
243
+ is unaffected. Because adding hydrogens changes the atom count, the
244
+ molforge `Protein` attached to the returned `Simulation` is rebuilt
245
+ from the protonated structure, so its topology and the coordinate
246
+ array agree (previously a heavy-atom topology could be paired with
247
+ a protonated coordinate array). A new `add_hydrogens` constructor
248
+ flag (default `True`) lets callers who have pre-protonated their
249
+ structure opt out.
250
+
251
+ ## [v0.2.0] 2026-05-26
252
+
10
253
  ### Added
11
254
  - **ProteinMPNN wrapper test coverage raised from 69% to 96%.** The
12
255
  two previously-untested seams of `wrappers.generative.proteinmpnn`
@@ -72,148 +315,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
72
315
  still permitted but carry no cross-version stability guarantee. 15
73
316
  new tests, including consistency checks that the parsers only emit
74
317
  documented keys and that the TypedDict matches `DOCUMENTED_KEYS`.
75
-
76
- ### Fixed
77
- - **`load_alphafold` now emits the uniform confidence metadata keys.**
78
- `molforge.io.load_alphafold` previously wrote only AlphaFold-specific
79
- keys (`plddt`, `plddt_per_residue`, `mean_plddt`, `source`), while
80
- the AlphaFold *wrapper* wrote the cross-engine-uniform keys
81
- (`confidence_per_atom`, `confidence_per_residue`, `mean_confidence`,
82
- `engine`). Downstream code reading confidence uniformly across
83
- engines silently missed AlphaFold structures loaded from disk.
84
- `load_alphafold` now populates both sets (uniform keys preferred,
85
- legacy keys retained for backward compatibility); the two carry
86
- identical values. Surfaced by the API audit.
87
- - **`GROMACS` and `DiffDock` are now coherent stubs.** Both are
88
- exported (committed import paths) but unimplemented. Previously
89
- they were *incoherent*: `GROMACS` didn't implement its `MDEngine`
90
- abstract methods at all, so `GROMACS()` failed with a cryptic
91
- "Can't instantiate abstract class" `TypeError` rather than a
92
- meaningful message; both engines' methods raised a bare
93
- `NotImplementedError` with no text. They are now coherent stubs —
94
- instantiable, satisfying their respective engine ABCs
95
- (`MDEngine` / `DockingEngine`), with every method raising
96
- `NotImplementedError` carrying a clear message that points at the
97
- working alternative (`OpenMM` / `Vina`) and the tracking issue.
98
- 10 new tests. Surfaced by the API audit.
99
- - - **Lint drift from a Ruff version bump cleared; CI lint job green
100
- again.** `.pre-commit-config.yaml` pinned `ruff-pre-commit` at
101
- `v0.5.0`, but the `[dev]` extra installs `ruff>=0.5` unpinned, so
102
- CI resolved a much newer Ruff (0.15.x) whose added rules flagged
103
- 33 pre-existing issues — meaning the CI `lint` job was effectively
104
- red. All 33 are now resolved: a genuine dead variable in
105
- `ensembles.clustering` removed, an unused `shutil` import dropped,
106
- five `pytest.raises(match=...)` patterns with unescaped regex
107
- metacharacters made explicit (raw strings / escaped dots), a
108
- `zip()` given an explicit `strict=`, four nested `with` statements
109
- collapsed, a `getattr()` call with a string literal in
110
- `ensembles.weighting` replaced by a `cast`-backed direct attribute
111
- access (dropping a now-misplaced `# noqa`), and a Ruff-version
112
- formatting refresh applied across 24 files (cosmetic line-joining
113
- only). Two intentional-notation cases
114
- are configured rather than rewritten: `allowed-confusables`
115
- permits `×`, `σ`, and `–` in docstrings (matrix dimensions, the
116
- standard deviation, prose dashes), and `RUF022` is per-file-ignored
117
- for the two modules whose `__all__` is deliberately grouped by
118
- category with section comments. The `ruff` and `mypy` pre-commit
119
- pins are bumped to the versions CI resolves, so the two stay in
120
- lock-step and this drift cannot silently recur. No source-behaviour
121
- or test-count change (918 pass + 11 skipped, unchanged).
122
-
123
- ### Changed
124
- - **BREAKING: `cross_validate` now defaults to `on_error="raise"`.**
125
- Previously `cross_validate` defaulted to `on_error="record"` —
126
- exceptions raised by individual validators were silently caught,
127
- recorded in verdict metadata, and the verdict marked
128
- `passed=False`. The problem: a validator that throws on every
129
- design (a misconfigured engine, a missing dependency, a bad
130
- input) produced a full list of `passed=False` verdicts that
131
- *looked* like a real result, hiding the bug. The new default
132
- fails loud. Code that genuinely wants a batch to survive
133
- individual validator failures must now pass `on_error="record"`
134
- explicitly. Flagged and resolved by the API audit.
135
- - **The entire `molforge` package is now `mypy --strict` clean.**
136
- With `wrappers` and `plugins` brought up to strict, all 77 source
137
- modules across every subpackage pass `mypy --strict` with zero
138
- errors. The CI `typecheck` job is correspondingly simplified: the
139
- previous two-step arrangement (a strict gate on the clean
140
- subpackages plus a non-blocking informational full-tree run)
141
- collapses to a single `mypy src` gate that fails the build on any
142
- type error. The `tests/unit/test_typing.py` regression test is
143
- likewise simplified to one whole-package check. 31 errors fixed in
144
- this final tranche: 20 stale `# type: ignore` comments (made
145
- redundant when the optional heavy dependencies were added to the
146
- mypy `ignore_missing_imports` override), four deliberate engine-
147
- method `# type: ignore[override]` annotations (the concrete
148
- engine wrappers refine the permissive `**kwargs` signatures of
149
- their `DockingEngine` / `MDEngine` / `GenerativeEngine` abstract
150
- bases — an intentional, documented refinement that mypy's strict
151
- Liskov check cannot model), `cast`s for the opaque
152
- `Simulation.engine_handle` inside the OpenMM wrapper and for the
153
- unstubbed-dependency return values, and `Vina.dock`'s receptor
154
- narrowing switched from `hasattr` to `isinstance` (a more correct
155
- check that mypy can also narrow on).
156
- - **`molforge.ml` is now `mypy --strict` clean.** The ML subpackage
157
- (sequence/structure featurization, protein-language-model
158
- embeddings) joins the strict gate — eight strict-clean
159
- subpackages in total, 51 source files. Six errors fixed: the four
160
- numpy-widening `no-any-return`s in `embeddings.py` (resolved with
161
- `cast`s), and two real type bugs in `structure_features.py` —
162
- `pair_distances` and `pair_distance_features` declared
163
- `atom_choice: str` but pass it to `distance_map`, which requires
164
- the `Literal["ca","cb","heavy","all"]` the docstrings already
165
- specify, and a coordinate feature array silently upcast to
166
- float64 by a division. The `torch` and `transformers` (and
167
- `colabfold`, `meeko`, `vina`) optional heavy dependencies, which
168
- ship no type stubs, are added to the mypy `ignore_missing_imports`
169
- override alongside the existing `Bio` / `biotite` / `mdtraj` /
170
- `openmm` / `rdkit` entries. CI strict gate and the
171
- `tests/unit/test_typing.py` regression test updated; only
172
- `plugins` and `wrappers` remain outside the gate.
173
- - **Six more subpackages are now `mypy --strict` clean.**
174
- `molforge.io`, `molforge.sequence`, `molforge.structure`,
175
- `molforge.metrics`, `molforge.ensembles`, and
176
- `molforge.validation` now pass `mypy --strict` with zero errors,
177
- joining `molforge.core` — seven strict-clean subpackages in total,
178
- 46 source files. The 12 errors fixed were mostly numpy operations
179
- mypy widens to `Any` (resolved with explicit `cast`s that document
180
- the known array dtype) and two stale `type: ignore` comments; two
181
- were genuine annotation bugs — `_place_hydrogens` in `dssp.py` was
182
- declared to return a single array but actually returns a
183
- `(coords, mask)` tuple, and `_score` in `alignment.py` was
184
- declared `NDArray[np.int_]` but builds an `int32` array (`np.int_`
185
- is `int64` on 64-bit platforms). The CI strict gate now covers all
186
- seven subpackages; the regression test
187
- (`tests/unit/test_typing.py`, moved up from `tests/unit/core/` and
188
- parametrized) checks each one in-suite. The remaining subpackages
189
- (`ml`, `plugins`, `wrappers`) are still tracked by the
190
- non-blocking informational `mypy src` CI step.
191
- - **`molforge.core` is now `mypy --strict` clean, and CI enforces
192
- it.** The `core` subpackage — the data model the rest of the
193
- library is built on — now passes `mypy --strict` with zero
194
- errors (fixed: two missing `NDArray` type arguments in
195
- `AtomArray`, an `Any`-return in `Atom.coord`, and an untyped
196
- `Chain.__iter__` that was suppressed with a `type: ignore`). The
197
- CI `typecheck` job now runs `mypy --strict src/molforge/core/`
198
- as a hard gate, with a separate non-blocking full-tree `mypy src`
199
- step that keeps the remaining (out-of-`core`) type errors visible
200
- while they're worked through. A new `slow`-marked regression test
201
- (`tests/unit/core/test_typing.py`) runs the strict check in-suite
202
- so a `core` type regression is caught locally too.
203
-
204
- ### Documented
205
- - **`Simulation.engine_handle` contract clarified.** The attribute
206
- type (`object | None`) is correct — it really is an opaque,
207
- engine-specific handle — but the contract was under-specified.
208
- The docstring now states explicitly that `engine_handle` is
209
- engine-private (callers must not inspect it or set it), is **not
210
- serialized** (it typically wraps unpicklable C-extension state;
211
- persistence layers must drop it and let the engine wrapper
212
- rebuild it on resume), and carries **no semver guarantee**. For
213
- inspectable per-simulation data, `Simulation.metadata` is the
214
- supported field. No code change. Flagged by the API audit.
215
-
216
- ### Added
217
318
  - **RoseTTAFold All-Atom folding wrapper.** New file
218
319
  `src/molforge/wrappers/folding/rosettafold.py` implements a real
219
320
  wrapper around the Baker lab's RoseTTAFold-All-Atom (Krishna et
@@ -241,25 +342,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
241
342
  prediction matching the rest of the folding wrappers; protein-
242
343
  ligand and covalent-modification co-folding (RFAA's headline
243
344
  capability) need a separate `predict_complex()` surface and
244
- remain planned. 47 new tests (45 passing + 2 correctly skipped:
345
+ remain planned.
346
+ - 47 new tests (45 passing + 2 correctly skipped:
245
347
  one for the torch tensor conversion when torch isn't installed,
246
348
  one @slow end-to-end requiring `$RFAA_HOME`). Total test count:
247
349
  830 → 875 passed + 11 skipped.
248
-
249
- ### Deprecated
250
- - **`molforge.wrappers.folding.Rosetta` is now a deprecated alias
251
- for `RoseTTAFold`.** The original `rosetta.py` placeholder was
252
- ambiguous about whether it referred to PyRosetta (the Baker lab's
253
- classical sequence-design library) or RoseTTAFold (the deep-
254
- learning model). The new real wrapper lives at
255
- `RoseTTAFold` for clarity. `Rosetta` is retained as a thin
256
- subclass that emits `DeprecationWarning` on construction so
257
- existing imports / isinstance checks keep working through the
258
- next minor release. A PyRosetta wrapper, if added, would live in
259
- a separate module (`pyrosetta.py`) since PyRosetta's surface is
260
- much wider than the `FoldingEngine` contract.
261
-
262
- ### Added
263
350
  - **Boltz / Boltz-2 folding wrapper.** Real implementation replacing
264
351
  the `boltz.py` stub. Drives the `boltz predict` CLI via subprocess
265
352
  against a temporary directory and parses the resulting mmCIF +
@@ -275,7 +362,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
275
362
  metadata follows the uniform folding-engine convention
276
363
  (`confidence_per_residue`, `confidence_per_atom`, `mean_confidence`)
277
364
  and additionally surfaces Boltz-specific `ptm`, `iptm`, and
278
- `confidence_score` from the JSON sidecar. 47 new tests (46 passing
365
+ `confidence_score` from the JSON sidecar.
366
+ - 47 new tests (46 passing
279
367
  + 1 correctly skipped @slow end-to-end), structured as a series of
280
368
  testable seams: construction, sequence validation, YAML input
281
369
  construction, command-line assembly, environment setup, output
@@ -477,6 +565,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
477
565
  the empty-entry-points fallthrough.
478
566
 
479
567
  ### Fixed
568
+ - **`load_alphafold` now emits the uniform confidence metadata keys.**
569
+ `molforge.io.load_alphafold` previously wrote only AlphaFold-specific
570
+ keys (`plddt`, `plddt_per_residue`, `mean_plddt`, `source`), while
571
+ the AlphaFold *wrapper* wrote the cross-engine-uniform keys
572
+ (`confidence_per_atom`, `confidence_per_residue`, `mean_confidence`,
573
+ `engine`). Downstream code reading confidence uniformly across
574
+ engines silently missed AlphaFold structures loaded from disk.
575
+ `load_alphafold` now populates both sets (uniform keys preferred,
576
+ legacy keys retained for backward compatibility); the two carry
577
+ identical values. Surfaced by the API audit.
578
+ - **`GROMACS` and `DiffDock` are now coherent stubs.** Both are
579
+ exported (committed import paths) but unimplemented. Previously
580
+ they were *incoherent*: `GROMACS` didn't implement its `MDEngine`
581
+ abstract methods at all, so `GROMACS()` failed with a cryptic
582
+ "Can't instantiate abstract class" `TypeError` rather than a
583
+ meaningful message; both engines' methods raised a bare
584
+ `NotImplementedError` with no text. They are now coherent stubs —
585
+ instantiable, satisfying their respective engine ABCs
586
+ (`MDEngine` / `DockingEngine`), with every method raising
587
+ `NotImplementedError` carrying a clear message that points at the
588
+ working alternative (`OpenMM` / `Vina`) and the tracking issue.
589
+ 10 new tests. Surfaced by the API audit.
590
+ - - **Lint drift from a Ruff version bump cleared; CI lint job green
591
+ again.** `.pre-commit-config.yaml` pinned `ruff-pre-commit` at
592
+ `v0.5.0`, but the `[dev]` extra installs `ruff>=0.5` unpinned, so
593
+ CI resolved a much newer Ruff (0.15.x) whose added rules flagged
594
+ 33 pre-existing issues — meaning the CI `lint` job was effectively
595
+ red. All 33 are now resolved: a genuine dead variable in
596
+ `ensembles.clustering` removed, an unused `shutil` import dropped,
597
+ five `pytest.raises(match=...)` patterns with unescaped regex
598
+ metacharacters made explicit (raw strings / escaped dots), a
599
+ `zip()` given an explicit `strict=`, four nested `with` statements
600
+ collapsed, a `getattr()` call with a string literal in
601
+ `ensembles.weighting` replaced by a `cast`-backed direct attribute
602
+ access (dropping a now-misplaced `# noqa`), and a Ruff-version
603
+ formatting refresh applied across 24 files (cosmetic line-joining
604
+ only). Two intentional-notation cases
605
+ are configured rather than rewritten: `allowed-confusables`
606
+ permits `×`, `σ`, and `–` in docstrings (matrix dimensions, the
607
+ standard deviation, prose dashes), and `RUF022` is per-file-ignored
608
+ for the two modules whose `__all__` is deliberately grouped by
609
+ category with section comments. The `ruff` and `mypy` pre-commit
610
+ pins are bumped to the versions CI resolves, so the two stay in
611
+ lock-step and this drift cannot silently recur. No source-behaviour
612
+ or test-count change (918 pass + 11 skipped, unchanged).
480
613
  - **Docs notebooks no longer use symlinks.** The walkthrough and
481
614
  example notebooks were previously symlinked from `docs/` into the
482
615
  canonical `notebooks/` directory. Symlinks broke two things: (1)
@@ -499,6 +632,112 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
499
632
  the actual public attributes. Discovered while writing ensemble
500
633
  test fixtures.
501
634
 
635
+ ### Changed
636
+ - **BREAKING: `cross_validate` now defaults to `on_error="raise"`.**
637
+ Previously `cross_validate` defaulted to `on_error="record"` —
638
+ exceptions raised by individual validators were silently caught,
639
+ recorded in verdict metadata, and the verdict marked
640
+ `passed=False`. The problem: a validator that throws on every
641
+ design (a misconfigured engine, a missing dependency, a bad
642
+ input) produced a full list of `passed=False` verdicts that
643
+ *looked* like a real result, hiding the bug. The new default
644
+ fails loud. Code that genuinely wants a batch to survive
645
+ individual validator failures must now pass `on_error="record"`
646
+ explicitly. Flagged and resolved by the API audit.
647
+ - **The entire `molforge` package is now `mypy --strict` clean.**
648
+ With `wrappers` and `plugins` brought up to strict, all 77 source
649
+ modules across every subpackage pass `mypy --strict` with zero
650
+ errors. The CI `typecheck` job is correspondingly simplified: the
651
+ previous two-step arrangement (a strict gate on the clean
652
+ subpackages plus a non-blocking informational full-tree run)
653
+ collapses to a single `mypy src` gate that fails the build on any
654
+ type error. The `tests/unit/test_typing.py` regression test is
655
+ likewise simplified to one whole-package check. 31 errors fixed in
656
+ this final tranche: 20 stale `# type: ignore` comments (made
657
+ redundant when the optional heavy dependencies were added to the
658
+ mypy `ignore_missing_imports` override), four deliberate engine-
659
+ method `# type: ignore[override]` annotations (the concrete
660
+ engine wrappers refine the permissive `**kwargs` signatures of
661
+ their `DockingEngine` / `MDEngine` / `GenerativeEngine` abstract
662
+ bases — an intentional, documented refinement that mypy's strict
663
+ Liskov check cannot model), `cast`s for the opaque
664
+ `Simulation.engine_handle` inside the OpenMM wrapper and for the
665
+ unstubbed-dependency return values, and `Vina.dock`'s receptor
666
+ narrowing switched from `hasattr` to `isinstance` (a more correct
667
+ check that mypy can also narrow on).
668
+ - **`molforge.ml` is now `mypy --strict` clean.** The ML subpackage
669
+ (sequence/structure featurization, protein-language-model
670
+ embeddings) joins the strict gate — eight strict-clean
671
+ subpackages in total, 51 source files. Six errors fixed: the four
672
+ numpy-widening `no-any-return`s in `embeddings.py` (resolved with
673
+ `cast`s), and two real type bugs in `structure_features.py` —
674
+ `pair_distances` and `pair_distance_features` declared
675
+ `atom_choice: str` but pass it to `distance_map`, which requires
676
+ the `Literal["ca","cb","heavy","all"]` the docstrings already
677
+ specify, and a coordinate feature array silently upcast to
678
+ float64 by a division. The `torch` and `transformers` (and
679
+ `colabfold`, `meeko`, `vina`) optional heavy dependencies, which
680
+ ship no type stubs, are added to the mypy `ignore_missing_imports`
681
+ override alongside the existing `Bio` / `biotite` / `mdtraj` /
682
+ `openmm` / `rdkit` entries. CI strict gate and the
683
+ `tests/unit/test_typing.py` regression test updated; only
684
+ `plugins` and `wrappers` remain outside the gate.
685
+ - **Six more subpackages are now `mypy --strict` clean.**
686
+ `molforge.io`, `molforge.sequence`, `molforge.structure`,
687
+ `molforge.metrics`, `molforge.ensembles`, and
688
+ `molforge.validation` now pass `mypy --strict` with zero errors,
689
+ joining `molforge.core` — seven strict-clean subpackages in total,
690
+ 46 source files. The 12 errors fixed were mostly numpy operations
691
+ mypy widens to `Any` (resolved with explicit `cast`s that document
692
+ the known array dtype) and two stale `type: ignore` comments; two
693
+ were genuine annotation bugs — `_place_hydrogens` in `dssp.py` was
694
+ declared to return a single array but actually returns a
695
+ `(coords, mask)` tuple, and `_score` in `alignment.py` was
696
+ declared `NDArray[np.int_]` but builds an `int32` array (`np.int_`
697
+ is `int64` on 64-bit platforms). The CI strict gate now covers all
698
+ seven subpackages; the regression test
699
+ (`tests/unit/test_typing.py`, moved up from `tests/unit/core/` and
700
+ parametrized) checks each one in-suite. The remaining subpackages
701
+ (`ml`, `plugins`, `wrappers`) are still tracked by the
702
+ non-blocking informational `mypy src` CI step.
703
+ - **`molforge.core` is now `mypy --strict` clean, and CI enforces
704
+ it.** The `core` subpackage — the data model the rest of the
705
+ library is built on — now passes `mypy --strict` with zero
706
+ errors (fixed: two missing `NDArray` type arguments in
707
+ `AtomArray`, an `Any`-return in `Atom.coord`, and an untyped
708
+ `Chain.__iter__` that was suppressed with a `type: ignore`). The
709
+ CI `typecheck` job now runs `mypy --strict src/molforge/core/`
710
+ as a hard gate, with a separate non-blocking full-tree `mypy src`
711
+ step that keeps the remaining (out-of-`core`) type errors visible
712
+ while they're worked through. A new `slow`-marked regression test
713
+ (`tests/unit/core/test_typing.py`) runs the strict check in-suite
714
+ so a `core` type regression is caught locally too.
715
+
716
+ ### Documented
717
+ - **`Simulation.engine_handle` contract clarified.** The attribute
718
+ type (`object | None`) is correct — it really is an opaque,
719
+ engine-specific handle — but the contract was under-specified.
720
+ The docstring now states explicitly that `engine_handle` is
721
+ engine-private (callers must not inspect it or set it), is **not
722
+ serialized** (it typically wraps unpicklable C-extension state;
723
+ persistence layers must drop it and let the engine wrapper
724
+ rebuild it on resume), and carries **no semver guarantee**. For
725
+ inspectable per-simulation data, `Simulation.metadata` is the
726
+ supported field. No code change. Flagged by the API audit.
727
+
728
+ ### Deprecated
729
+ - **`molforge.wrappers.folding.Rosetta` is now a deprecated alias
730
+ for `RoseTTAFold`.** The original `rosetta.py` placeholder was
731
+ ambiguous about whether it referred to PyRosetta (the Baker lab's
732
+ classical sequence-design library) or RoseTTAFold (the deep-
733
+ learning model). The new real wrapper lives at
734
+ `RoseTTAFold` for clarity. `Rosetta` is retained as a thin
735
+ subclass that emits `DeprecationWarning` on construction so
736
+ existing imports / isinstance checks keep working through the
737
+ next minor release. A PyRosetta wrapper, if added, would live in
738
+ a separate module (`pyrosetta.py`) since PyRosetta's surface is
739
+ much wider than the `FoldingEngine` contract.
740
+
502
741
  ### Removed
503
742
  - **`tests/unit/core/test_core_types.py`.** A pre-existing fossil
504
743
  from before the view-based data-model refactor: it imported from
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: molforge
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A unified Python library for structural bioinformatics, MD, protein engineering, and ML.
5
5
  Project-URL: Homepage, https://github.com/DoctorDean/molforge
6
6
  Project-URL: Documentation, https://doctordean.github.io/molforge/
@@ -43,7 +43,7 @@ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
43
43
  Classifier: Topic :: Scientific/Engineering :: Chemistry
44
44
  Classifier: Typing :: Typed
45
45
  Requires-Python: >=3.10
46
- Requires-Dist: numpy>=1.24
46
+ Requires-Dist: numpy>=2.2.6
47
47
  Requires-Dist: typing-extensions>=4.7
48
48
  Provides-Extra: all
49
49
  Requires-Dist: biopython>=1.83; extra == 'all'
@@ -94,7 +94,8 @@ Description-Content-Type: text/markdown
94
94
  # molforge
95
95
 
96
96
  [![CI](https://github.com/DoctorDean/molforge/actions/workflows/ci.yml/badge.svg)](https://github.com/DoctorDean/molforge/actions/workflows/ci.yml)
97
- [![PyPI version](https://img.shields.io/pypi/v/molforge.svg)](https://pypi.org/project/molforge/)
97
+ [![PyPI version](https://img.shields.io/pypi/v/molforge.svg)](https://pypi.org/project/molforge/)
98
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/molforge?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/molforge)
98
99
  [![Python versions](https://img.shields.io/pypi/pyversions/molforge.svg)](https://pypi.org/project/molforge/)
99
100
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
100
101
  [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
@@ -1,7 +1,8 @@
1
1
  # molforge
2
2
 
3
3
  [![CI](https://github.com/DoctorDean/molforge/actions/workflows/ci.yml/badge.svg)](https://github.com/DoctorDean/molforge/actions/workflows/ci.yml)
4
- [![PyPI version](https://img.shields.io/pypi/v/molforge.svg)](https://pypi.org/project/molforge/)
4
+ [![PyPI version](https://img.shields.io/pypi/v/molforge.svg)](https://pypi.org/project/molforge/)
5
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/molforge?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/molforge)
5
6
  [![Python versions](https://img.shields.io/pypi/pyversions/molforge.svg)](https://pypi.org/project/molforge/)
6
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
8
  [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
@@ -41,7 +41,7 @@ classifiers = [
41
41
  # Minimal runtime dependencies — keep this list small.
42
42
  # Heavy / optional deps go under [project.optional-dependencies].
43
43
  dependencies = [
44
- "numpy>=1.24",
44
+ "numpy>=2.2.6",
45
45
  "typing-extensions>=4.7",
46
46
  ]
47
47
 
@@ -13,5 +13,5 @@ orchestration layer, and no required entry point. Import what you need.
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
- __version__ = "0.2.0"
16
+ __version__ = "0.3.0"
17
17
  __all__ = ["__version__"]