molforge 0.3.0__tar.gz → 0.4.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 (215) hide show
  1. {molforge-0.3.0 → molforge-0.4.0}/CHANGELOG.md +365 -0
  2. {molforge-0.3.0 → molforge-0.4.0}/PKG-INFO +9 -2
  3. {molforge-0.3.0 → molforge-0.4.0}/README.md +4 -1
  4. {molforge-0.3.0 → molforge-0.4.0}/pyproject.toml +6 -1
  5. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/__init__.py +1 -1
  6. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/core/__init__.py +4 -0
  7. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/core/metadata_keys.py +27 -0
  8. molforge-0.4.0/src/molforge/core/provenance.py +350 -0
  9. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/__init__.py +19 -0
  10. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/mmcif.py +103 -15
  11. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/pdb_alphafold.py +10 -0
  12. molforge-0.4.0/src/molforge/io/trajectory.py +399 -0
  13. molforge-0.4.0/src/molforge/prep/__init__.py +62 -0
  14. molforge-0.4.0/src/molforge/prep/_deps.py +58 -0
  15. molforge-0.4.0/src/molforge/prep/_provenance.py +65 -0
  16. molforge-0.4.0/src/molforge/prep/clean.py +157 -0
  17. molforge-0.4.0/src/molforge/prep/fix.py +256 -0
  18. molforge-0.4.0/src/molforge/prep/pipeline.py +120 -0
  19. molforge-0.4.0/src/molforge/prep/protonate.py +123 -0
  20. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/docking/diffdock.py +39 -4
  21. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/docking/vina.py +49 -1
  22. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/folding/alphafold.py +18 -0
  23. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/folding/boltz.py +13 -0
  24. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/folding/esmfold.py +18 -0
  25. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/folding/rosettafold.py +12 -0
  26. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/generative/proteinmpnn.py +40 -6
  27. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/generative/rfdiffusion.py +24 -0
  28. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/md/gromacs.py +48 -3
  29. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/md/openmm.py +53 -3
  30. molforge-0.4.0/tests/unit/core/test_provenance.py +339 -0
  31. molforge-0.4.0/tests/unit/io/test_mmcif.py +767 -0
  32. molforge-0.4.0/tests/unit/io/test_trajectory.py +251 -0
  33. molforge-0.4.0/tests/unit/prep/test_clean.py +124 -0
  34. molforge-0.4.0/tests/unit/prep/test_fix.py +161 -0
  35. molforge-0.4.0/tests/unit/prep/test_pipeline.py +123 -0
  36. molforge-0.4.0/tests/unit/prep/test_protonate.py +121 -0
  37. molforge-0.4.0/tests/unit/wrappers/__init__.py +0 -0
  38. molforge-0.4.0/tests/unit/wrappers/test_provenance_adoption.py +692 -0
  39. molforge-0.3.0/tests/unit/io/test_mmcif.py +0 -171
  40. {molforge-0.3.0 → molforge-0.4.0}/.gitignore +0 -0
  41. {molforge-0.3.0 → molforge-0.4.0}/LICENSE +0 -0
  42. {molforge-0.3.0 → molforge-0.4.0}/data/README.md +0 -0
  43. {molforge-0.3.0 → molforge-0.4.0}/notebooks/README.md +0 -0
  44. {molforge-0.3.0 → molforge-0.4.0}/plugins/README.md +0 -0
  45. {molforge-0.3.0 → molforge-0.4.0}/plugins/example_plugin/README.md +0 -0
  46. {molforge-0.3.0 → molforge-0.4.0}/plugins/example_plugin/pyproject.toml +0 -0
  47. {molforge-0.3.0 → molforge-0.4.0}/requirements/README.md +0 -0
  48. {molforge-0.3.0 → molforge-0.4.0}/scripts/README.md +0 -0
  49. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/core/atom.py +0 -0
  50. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/core/atom_array.py +0 -0
  51. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/core/chain.py +0 -0
  52. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/core/constants.py +0 -0
  53. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/core/protein.py +0 -0
  54. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/core/residue.py +0 -0
  55. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/docking/__init__.py +0 -0
  56. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ensembles/__init__.py +0 -0
  57. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ensembles/clustering.py +0 -0
  58. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ensembles/consensus.py +0 -0
  59. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ensembles/density.py +0 -0
  60. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ensembles/geometry.py +0 -0
  61. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ensembles/weighting.py +0 -0
  62. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/generative.py +0 -0
  63. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/dispatch.py +0 -0
  64. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/fasta.py +0 -0
  65. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/mol2.py +0 -0
  66. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/pdb.py +0 -0
  67. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/pdbqt.py +0 -0
  68. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/pqr.py +0 -0
  69. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/io/sdf.py +0 -0
  70. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/md/__init__.py +0 -0
  71. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/metrics/__init__.py +0 -0
  72. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/metrics/dockq.py +0 -0
  73. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/metrics/gdt.py +0 -0
  74. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/metrics/lddt.py +0 -0
  75. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/metrics/tm.py +0 -0
  76. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ml/__init__.py +0 -0
  77. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ml/embeddings.py +0 -0
  78. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ml/graph.py +0 -0
  79. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ml/sequence_features.py +0 -0
  80. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/ml/structure_features.py +0 -0
  81. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/plugins/__init__.py +0 -0
  82. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/plugins/registry.py +0 -0
  83. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/py.typed +0 -0
  84. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/sequence/__init__.py +0 -0
  85. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/sequence/alignment.py +0 -0
  86. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/sequence/composition.py +0 -0
  87. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/sequence/matrices.py +0 -0
  88. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/sequence/mutations.py +0 -0
  89. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/structure/__init__.py +0 -0
  90. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/structure/contacts.py +0 -0
  91. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/structure/dihedrals.py +0 -0
  92. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/structure/dssp.py +0 -0
  93. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/structure/geometry.py +0 -0
  94. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/structure/rmsd.py +0 -0
  95. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/structure/sasa.py +0 -0
  96. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/structure/superposition.py +0 -0
  97. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/validation/__init__.py +0 -0
  98. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/validation/criteria.py +0 -0
  99. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/validation/orchestration.py +0 -0
  100. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/validation/verdict.py +0 -0
  101. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/__init__.py +0 -0
  102. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/docking/__init__.py +0 -0
  103. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/docking/_base.py +0 -0
  104. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/docking/prep.py +0 -0
  105. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/folding/__init__.py +0 -0
  106. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/folding/_base.py +0 -0
  107. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/generative/__init__.py +0 -0
  108. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/md/__init__.py +0 -0
  109. {molforge-0.3.0 → molforge-0.4.0}/src/molforge/wrappers/md/_base.py +0 -0
  110. {molforge-0.3.0 → molforge-0.4.0}/tests/__init__.py +0 -0
  111. {molforge-0.3.0 → molforge-0.4.0}/tests/benchmarks/__init__.py +0 -0
  112. {molforge-0.3.0 → molforge-0.4.0}/tests/benchmarks/conftest.py +0 -0
  113. {molforge-0.3.0 → molforge-0.4.0}/tests/benchmarks/test_perf.py +0 -0
  114. {molforge-0.3.0 → molforge-0.4.0}/tests/conftest.py +0 -0
  115. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/cif/dipeptide.cif +0 -0
  116. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/fasta/.gitkeep +0 -0
  117. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/fasta/multiline_with_digits.fasta +0 -0
  118. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/fasta/simple.fasta +0 -0
  119. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/.gitkeep +0 -0
  120. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/ala_tripeptide_heavy.pdb +0 -0
  121. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/alphafold_mock.pdb +0 -0
  122. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/dipeptide.pdb +0 -0
  123. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/helix.pdb +0 -0
  124. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/mini_beta_sheet.pdb +0 -0
  125. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/mini_complex_bad.pdb +0 -0
  126. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/mini_complex_good.pdb +0 -0
  127. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/mini_complex_native.pdb +0 -0
  128. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/mini_ensemble.pdb +0 -0
  129. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/mini_mixed.pdb +0 -0
  130. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/mini_with_ligand.pdb +0 -0
  131. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/multi_model.pdb +0 -0
  132. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/real_small_protein.pdb +0 -0
  133. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/real_with_altloc_sidechains.pdb +0 -0
  134. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/real_with_ligand_realistic.pdb +0 -0
  135. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/tripeptide.pdb +0 -0
  136. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/with_altloc.pdb +0 -0
  137. {molforge-0.3.0 → molforge-0.4.0}/tests/fixtures/pdb/with_insertion_code.pdb +0 -0
  138. {molforge-0.3.0 → molforge-0.4.0}/tests/integration/__init__.py +0 -0
  139. {molforge-0.3.0 → molforge-0.4.0}/tests/integration/test_fixtures.py +0 -0
  140. {molforge-0.3.0 → molforge-0.4.0}/tests/integration/test_real_fixtures.py +0 -0
  141. {molforge-0.3.0 → molforge-0.4.0}/tests/integration/test_smoke.py +0 -0
  142. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/__init__.py +0 -0
  143. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/core/__init__.py +0 -0
  144. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/core/test_atom_array.py +0 -0
  145. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/core/test_constants.py +0 -0
  146. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/core/test_core_smoke.py +0 -0
  147. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/core/test_hierarchy.py +0 -0
  148. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/core/test_metadata_keys.py +0 -0
  149. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/docking/__init__.py +0 -0
  150. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/docking/test_docking_smoke.py +0 -0
  151. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ensembles/__init__.py +0 -0
  152. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ensembles/conftest.py +0 -0
  153. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ensembles/test_clustering.py +0 -0
  154. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ensembles/test_consensus.py +0 -0
  155. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ensembles/test_density.py +0 -0
  156. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ensembles/test_geometry.py +0 -0
  157. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ensembles/test_smoke.py +0 -0
  158. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ensembles/test_weighting.py +0 -0
  159. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/__init__.py +0 -0
  160. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/test_alphafold.py +0 -0
  161. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/test_dispatch.py +0 -0
  162. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/test_fasta.py +0 -0
  163. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/test_io_smoke.py +0 -0
  164. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/test_mol2.py +0 -0
  165. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/test_pdb.py +0 -0
  166. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/test_pdbqt.py +0 -0
  167. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/test_pqr.py +0 -0
  168. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/io/test_sdf.py +0 -0
  169. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/md/__init__.py +0 -0
  170. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/metrics/__init__.py +0 -0
  171. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/metrics/test_dockq.py +0 -0
  172. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/metrics/test_gdt.py +0 -0
  173. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/metrics/test_lddt.py +0 -0
  174. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/metrics/test_tm.py +0 -0
  175. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ml/__init__.py +0 -0
  176. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ml/test_embeddings.py +0 -0
  177. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ml/test_graph.py +0 -0
  178. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ml/test_sequence_features.py +0 -0
  179. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/ml/test_structure_features.py +0 -0
  180. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/plugins/__init__.py +0 -0
  181. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/plugins/test_plugins_smoke.py +0 -0
  182. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/plugins/test_registry.py +0 -0
  183. {molforge-0.3.0/tests/unit/sequence → molforge-0.4.0/tests/unit/prep}/__init__.py +0 -0
  184. {molforge-0.3.0/tests/unit/structure → molforge-0.4.0/tests/unit/sequence}/__init__.py +0 -0
  185. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/sequence/test_alignment.py +0 -0
  186. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/sequence/test_composition.py +0 -0
  187. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/sequence/test_matrices.py +0 -0
  188. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/sequence/test_mutations.py +0 -0
  189. {molforge-0.3.0/tests/unit/wrappers → molforge-0.4.0/tests/unit/structure}/__init__.py +0 -0
  190. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/structure/test_contacts.py +0 -0
  191. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/structure/test_dihedrals.py +0 -0
  192. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/structure/test_dssp.py +0 -0
  193. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/structure/test_geometry.py +0 -0
  194. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/structure/test_rmsd.py +0 -0
  195. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/structure/test_sasa.py +0 -0
  196. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/structure/test_structure_smoke.py +0 -0
  197. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/structure/test_superposition.py +0 -0
  198. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/test_typing.py +0 -0
  199. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/validation/test_criteria.py +0 -0
  200. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/validation/test_orchestration.py +0 -0
  201. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/validation/test_verdict.py +0 -0
  202. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_alphafold.py +0 -0
  203. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_boltz.py +0 -0
  204. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_diffdock.py +0 -0
  205. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_docking_base.py +0 -0
  206. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_esmfold.py +0 -0
  207. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_folding_base.py +0 -0
  208. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_gromacs.py +0 -0
  209. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_md_base.py +0 -0
  210. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_openmm.py +0 -0
  211. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_prep.py +0 -0
  212. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_proteinmpnn.py +0 -0
  213. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_rfdiffusion.py +0 -0
  214. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_rosettafold.py +0 -0
  215. {molforge-0.3.0 → molforge-0.4.0}/tests/unit/wrappers/test_vina.py +0 -0
@@ -7,6 +7,371 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] 2026-06-25
11
+
12
+ ### Added
13
+
14
+ - **Provenance adoption pass 2 — MD wrappers and prep functions.**
15
+ Completes the wrapper-adoption work. Where
16
+ pass 1 demonstrated cross-wrapper chaining (ESMFold → Vina),
17
+ pass 2 exercises chaining **within** a single wrapper's
18
+ multi-step pipeline and across the composable prep functions —
19
+ the harder case and the one that proves the design composes.
20
+
21
+ Adopted (2 wrappers + 5 functions):
22
+
23
+ - **`molforge.wrappers.md.OpenMM`** — `prepare` / `minimize` /
24
+ `run` each attach a `Provenance` to the output's metadata. Each
25
+ step's parent is the previous step's `Provenance`, so a full
26
+ pipeline leaves the final `Trajectory` with a 3-deep chain
27
+ that reads as `["OpenMM.prepare", "OpenMM.minimize", "OpenMM.run"]`
28
+ oldest-first. When the input `Protein` has its own `Provenance`
29
+ (e.g. it came from ESMFold), the chain extends back through it
30
+ — a sequence-to-trajectory workflow ends with a 4-deep chain
31
+ that traces all the way to the sequence.
32
+
33
+ - **`molforge.wrappers.md.GROMACS`** — same three-step pattern
34
+ as OpenMM, with the engine strings `"GROMACS.prepare"`,
35
+ `"GROMACS.minimize"`, `"GROMACS.run"`. The minimize step
36
+ appends to `simulation.metadata` (since minimize returns the
37
+ same Simulation in-place, not a new one), preserving the
38
+ chain across the mutation.
39
+
40
+ - **`molforge.prep.{remove_heterogens, fix_missing_atoms,
41
+ add_caps, add_hydrogens, prepare_for_md}`** — each prep
42
+ function chains a `Provenance` step onto the output's
43
+ metadata. `prepare_for_md` (which composes the other four in
44
+ sequence) leaves the result with a 4-deep chain naturally:
45
+ `["molforge.prep.remove_heterogens",
46
+ "molforge.prep.fix_missing_atoms",
47
+ "molforge.prep.add_caps",
48
+ "molforge.prep.add_hydrogens"]`. No special handling needed
49
+ in the composite — the inner functions chain themselves.
50
+
51
+ Two helpers, both private:
52
+
53
+ - **`molforge.wrappers.md.{openmm,gromacs}._parent_provenance(meta)`**
54
+ — extracts a `Provenance | None` from a free-form metadata
55
+ dict, narrowing the type. Per-wrapper rather than shared so
56
+ each MD wrapper stays self-contained.
57
+
58
+ - **`molforge.prep._provenance.chain_prep_provenance(output, *,
59
+ engine, parameters, input_protein)`** — the DRY-up for the
60
+ five prep functions. One place to evolve the prep-side
61
+ provenance shape later (e.g. when we want to also stamp the
62
+ PDBFixer / OpenMM versions used).
63
+
64
+ Backwards compatibility: every existing ad-hoc metadata key
65
+ (`metadata["engine"]`, `metadata["run_dir"]`, `metadata["emtol"]`,
66
+ etc.) is preserved unchanged. The new `metadata[PROVENANCE]` is
67
+ additive.
68
+
69
+ Tests (`tests/unit/wrappers/test_provenance_adoption.py`, 8 new
70
+ in addition to the 11 from pass 1):
71
+
72
+ - `TestOpenMMProvenanceChain` (2 tests) drives a full prepare →
73
+ minimize → run pipeline against a real OpenMM install
74
+ (skipped if openmm missing). The headline scenario
75
+ `["ESMFold", "OpenMM.prepare", "OpenMM.run"]` is exercised
76
+ end-to-end.
77
+
78
+ - `TestGROMACSProvenanceWiring` (2 tests) — GROMACS needs the
79
+ `gmx` binary which CI usually lacks. The tests inspect the
80
+ module source to assert the three step engine strings appear
81
+ and that `_parent_provenance(...)` is used — a regression net
82
+ that catches future code that bypasses the helper without
83
+ needing the real engine to run.
84
+
85
+ - `TestPrepProvenanceChain` (4 tests) — exercises each prep
86
+ function individually, then a two-function chain, then the
87
+ full `prepare_for_md` 4-step chain, then the "ESMFold + 4
88
+ prep steps" 5-deep chain (the most realistic scenario).
89
+ Skipped if openmm + pdbfixer aren't both installed.
90
+
91
+ With pass 2 complete, the headline scenario from the roadmap
92
+ ("20 designs from ProteinMPNN, docked with Vina, refined with
93
+ OpenMM") is now fully traceable: every output object's
94
+ `metadata[PROVENANCE].chain()` reads as the producer pipeline,
95
+ with sufficient detail in each step's `parameters` to
96
+ reconstruct the exact call. The remaining provenance work is
97
+ optional polish: persistence to a sidecar format, hash-keyed
98
+ caching (the next roadmap item), and richer engine-version
99
+ introspection.
100
+
101
+ - **Provenance adoption across folding, docking, and generative
102
+ wrappers (pass 1).** Builds on the `Provenance` surface added in
103
+ `9fafbba`. Every wrapper in scope now attaches a `Provenance` to
104
+ the output's `metadata[PROVENANCE]` alongside its existing ad-hoc
105
+ keys, so the "20 designs from ProteinMPNN, docked with Vina"
106
+ scenario is now traceable end-to-end.
107
+
108
+ Wrappers adopted:
109
+ - **Folding**: ESMFold, AlphaFold, Boltz, RoseTTAFold. Each
110
+ records its `__init__` config (model name, device, msa mode,
111
+ recycles, etc.) in `parameters` and the input sequence in
112
+ `inputs`. Folding has no upstream wrapper, so `parent` is
113
+ `None`.
114
+ - **Docking**: Vina, DiffDock. Each `DockingResult.metadata`
115
+ gets a `Provenance` covering the run (box, exhaustiveness,
116
+ seed, etc. in `parameters`; receptor + ligand refs in
117
+ `inputs`); when the receptor was a `Protein` with its own
118
+ `Provenance`, that becomes the `parent` so a Vina pose
119
+ docked against an ESMFold prediction chains back to the
120
+ sequence. Per-pose `Pose.metadata` keeps existing per-pose
121
+ keys (`confidence`, `source_file`) — poses aren't
122
+ independently produced, so they share the result-level
123
+ provenance rather than each carrying their own.
124
+ - **Generative**: RFdiffusion, ProteinMPNN. Each returned
125
+ design (a `Protein` for RFdiffusion, a `DesignedSequence`
126
+ for ProteinMPNN) gets its own `Provenance` — all designs
127
+ from one call share the same Provenance object (frozen +
128
+ immutable, so by-reference sharing is safe). `design_index`
129
+ stays as a separate metadata key, not part of `parameters`,
130
+ since it identifies *which* design rather than the engine
131
+ config.
132
+ - **`molforge.io.load_alphafold`**: the loader helper. The
133
+ engine name reflects that this is the loader, not the
134
+ AlphaFold run itself (`engine="load_alphafold"`); the file
135
+ path goes in `inputs["path"]`.
136
+
137
+ Existing ad-hoc metadata keys are preserved across every change
138
+ — `metadata["engine"]`, `metadata["source_sequence"]`,
139
+ `metadata["source_args"]`, etc. continue to work for 1.x
140
+ backwards compatibility. The new `metadata[PROVENANCE]` is
141
+ *additive*, not a replacement, until 2.x.
142
+
143
+ Two wrapper-side surface changes worth noting:
144
+ - `Vina._parse_poses_pdbqt` and `DiffDock._parse_outputs`
145
+ gained optional `provenance_parameters` /
146
+ `provenance_inputs` / `provenance_parent` kwargs (and
147
+ DiffDock additionally `receptor_ref` / `ligand_ref`). The
148
+ kwargs are optional so legacy tests calling the parsers
149
+ directly without those refs still work — they just don't
150
+ get a Provenance attached. The main `dock()` entry points
151
+ always pass them.
152
+ - The `Vina` module gained a `_provenance_ref` helper for
153
+ converting a `Protein | str | PathLike` to a JSON-native
154
+ string identifier.
155
+
156
+ Tests (`tests/unit/wrappers/test_provenance_adoption.py`, 11
157
+ new): one assertion per adopted wrapper plus a parent-chaining
158
+ integration test that exercises the headline scenario
159
+ (`ESMFold -> Vina` chain). Each adoption test holds the wrapper
160
+ to a uniform contract: `engine` matches the documented name,
161
+ `parameters` contains every promised key, `inputs` contains the
162
+ expected input identifier(s). The chaining test confirms
163
+ `result.metadata[PROVENANCE].chain()` reads as
164
+ `["ESMFold", "Vina"]` oldest-first.
165
+
166
+ - **First-class provenance tracking: `molforge.core.Provenance`.** A
167
+ raw PDB and a folded AlphaFold prediction look identical at the
168
+ AtomArray level; the only difference is *how the structure was
169
+ produced*. Pre-existing engine wrappers already wrote some of this
170
+ information into `metadata` ad-hoc — ESMFold sets
171
+ `metadata["engine"] = "ESMFold"`, RFdiffusion sets
172
+ `metadata["source_args"]` — but the keys are scattered, the shapes
173
+ disagree across engines, and there's no concept of a *parent*
174
+ output, so a chain of operations (fold -> dock -> MD) is not
175
+ traceable. This commit canonicalises the shape.
176
+
177
+ The new `Provenance` dataclass (frozen, JSON-round-trippable) has:
178
+ - **`engine`** — producer name; an engine ("ESMFold", "Vina")
179
+ or a molforge function path ("molforge.prep.prepare_for_md").
180
+ - **`engine_version`** — engine's own version string.
181
+ - **`molforge_version`** — auto-filled from `molforge.__version__`.
182
+ - **`timestamp`** — ISO-8601 UTC, auto-filled.
183
+ - **`parameters`** — engine-specific arguments (must be
184
+ JSON-native; validated eagerly at construction so a wrapper
185
+ can't smuggle in a `Path` or NumPy array that crashes much
186
+ later at serialisation).
187
+ - **`inputs`** — identifiers of the input data (e.g.
188
+ `{"sequence": "MKTVRQ..."}`, `{"receptor": "/path/to.pdb"}`).
189
+ - **`parent`** — the provenance of the input this step
190
+ *consumed*. Recursively a `Provenance` or `None`. This is what
191
+ makes the system compositional: walking the parent chain
192
+ reconstructs the whole history.
193
+
194
+ Construction goes through `Provenance.from_engine(engine=...,
195
+ parameters=..., inputs=..., parent=...)` which auto-fills the two
196
+ version fields and the timestamp so wrappers don't have to think
197
+ about them. The dataclass is frozen — mutating an attached
198
+ provenance would corrupt the audit trail; use `.replace(**changes)`
199
+ to derive an amended copy.
200
+
201
+ Traversal helpers: `walk()` yields self then ancestors newest-first;
202
+ `chain()` returns the same list oldest-first (suitable for printing
203
+ as a left-to-right pipeline); `depth` is the step count.
204
+
205
+ Serialisation: `to_dict()` / `from_dict()` give a stable plain-dict
206
+ shape with parents nested recursively; `to_json()` / `from_json()`
207
+ are JSON convenience wrappers. The on-disk shape is part of the
208
+ stability commitment.
209
+
210
+ The new `metadata_keys.PROVENANCE = "provenance"` constant is the
211
+ documented key; `ProteinMetadata` TypedDict declares it. The
212
+ intended use is `protein.metadata[mk.PROVENANCE] = prov`.
213
+ **Wrappers are NOT updated in this commit** — that's deliberately
214
+ separate work. The existing ad-hoc `metadata["engine"]` keys
215
+ continue to work for the 1.x series; engines opt into the
216
+ `Provenance` system gradually.
217
+
218
+ *NOT persisted through PDB / mmCIF writers* — those preserve only
219
+ the six documented IO header keys (per the mmCIF audit in
220
+ `c3a012e`). Provenance is an in-memory concept; users wanting
221
+ persistence serialise via `to_json` to a sidecar file. This is a
222
+ documented limitation, not a bug. A future "molforge bundle"
223
+ format could carry provenance alongside structure data; out of
224
+ scope here.
225
+
226
+ 33 new tests in `tests/unit/core/test_provenance.py` covering
227
+ construction (minimal, autofill, defensive copies, parent),
228
+ strict JSON-input validation, immutability (FrozenInstanceError +
229
+ `.replace`), traversal (walk / chain / depth), serialisation
230
+ (dict shape, JSON round-trip, missing-engine error, forward-
231
+ compatible deserialisation of older shapes), equality, and the
232
+ metadata-key integration. Module coverage 96.8%; the residual
233
+ two lines are a defensive `_molforge_version` exception fallback.
234
+
235
+ - **mmCIF writer round-trip audit and fidelity fixes.** A systematic
236
+ audit of `write_cif_string` against every PDB fixture in the repo
237
+ surfaced five concrete fidelity bugs in the pre-audit writer; all
238
+ five are now fixed.
239
+ 1. **`model_id == 0` was clobbered to 1.** The old writer used
240
+ `int(model_id) or 1`, which turned every single-model PDB's
241
+ `model_id=0` (the `read_pdb` convention for files without
242
+ MODEL records) into 1 on write. The reader's matching
243
+ `or 1` default reinforced the change — every PDB → CIF
244
+ round-trip silently lost the convention. Writer now emits
245
+ the value verbatim; reader's default flipped from 1 to 0.
246
+ *Affected every PDB fixture (19/19) before the fix.*
247
+ 2. **Partial / non-integer charges were truncated to int.** The
248
+ old writer emitted `f"{int(charge):d}"`, turning typical
249
+ PDBQT / PQR partial charges like `-0.297` into `0` and
250
+ `-1.5` into `-1`. Writer now emits `f"{charge:.4f}"` (4
251
+ decimal places preserves enough precision for typical
252
+ force-field partial charges); zero still emits the `?`
253
+ sentinel so "no charge information" round-trips cleanly.
254
+ 3. **`metadata['classification']` and `metadata['deposition_date']`
255
+ not emitted at all.** Both PDB HEADER fields were captured
256
+ by `read_pdb` and dropped by `write_cif`. Writer now emits
257
+ `_struct_keywords.text` for classification and
258
+ `_pdbx_database_status.recvd_initial_deposition_date` for
259
+ the deposition date; reader picks them up symmetrically.
260
+ 4. **`_entry.id` and `data_<id>` block name could disagree.**
261
+ The old writer used `protein.name` for the block name but
262
+ `metadata[pdb_id]` for `_entry.id`. When those differed, the
263
+ reader's `_entry.id` won and silently rewrote both fields on
264
+ round-trip (e.g. dipeptide.pdb's `name='dipeptide'` became
265
+ `'TEST'` because the HEADER's PDB id was `TEST`). The fix
266
+ uses one chosen identifier — preferring `metadata[pdb_id]`,
267
+ falling back to `protein.name`, then `"molforge"` — for both.
268
+ Identifiers with embedded whitespace (which `read_pdb`
269
+ tolerates from malformed HEADER lines) are now quoted in
270
+ `_entry.id` even though the block name has to substitute
271
+ underscores, so `pdb_id` whitespace survives round-trip.
272
+ When no `pdb_id` exists at all, the writer emits the
273
+ `_entry.id .` mmCIF sentinel and the reader knows to leave
274
+ `metadata[pdb_id]` absent rather than manufacturing one from
275
+ the block name.
276
+ 5. **`serial == 0` was clobbered to `i+1`.** Latent twin of #1;
277
+ no fixture triggered it but the bug was there. Same fix
278
+ pattern: only synthesize a default when `serial <= 0`.
279
+ 38 new tests in `tests/unit/io/test_mmcif.py` codify each fix as
280
+ a regression guard. The `TestFixtureSweep` parametrized test
281
+ iterates every PDB fixture in the repo and asserts that
282
+ coordinates, residue/chain/atom-name fields, residue_id, model_id,
283
+ serial, insertion_code, altloc, record_type, entity_type, and the
284
+ six tracked metadata keys all survive a PDB → CIF → in-memory
285
+ round-trip. Three classes — `TestEntryIdAndBlockNameConsistency`,
286
+ `TestAltlocRoundTrip`, and `TestFixtureSweep` — explicitly
287
+ document the two structural mmCIF limitations: (a) `Protein.name`
288
+ is recovered from `metadata[pdb_id]` on round-trip because mmCIF
289
+ carries only one identifier slot, and (b) altloc round-trip
290
+ requires the caller to pass `altloc="all"` (the default strategy
291
+ collapses to highest-occupancy and drops the label). Module
292
+ coverage at 82.9%; the residual misses are defensive error
293
+ branches in the tokenizer and reader.
294
+ - **Trajectory I/O: `read_trajectory`, `iter_trajectory`,
295
+ `write_trajectory`.** `Trajectory` was previously in-memory only —
296
+ any real MD trajectory bigger than RAM had no way into molforge.
297
+ This commit adds binary-trajectory I/O wrapping mdtraj, exposed
298
+ from `molforge.io`. Supported formats are everything mdtraj
299
+ handles: `.xtc` (GROMACS lossy, the common case), `.trr` (GROMACS
300
+ lossless), `.dcd` (CHARMM / NAMD / OpenMM), `.nc` / `.netcdf`
301
+ (AMBER), `.h5` / `.h5md` (HDF5-based), and multi-MODEL PDB. Three
302
+ functions: `read_trajectory(path, topology=..., stride=1,
303
+ atom_indices=None)` loads a whole file into a
304
+ `molforge.md.Trajectory` (use when it fits in memory);
305
+ `iter_trajectory(path, topology=..., chunk_size=100, stride=1,
306
+ atom_indices=None)` yields chunks of frames as `Trajectory` objects
307
+ with bounded memory; `write_trajectory(trajectory, path)` writes
308
+ out, format inferred from the extension. Coordinates are converted
309
+ nm ↔ Å automatically (molforge convention is Å throughout; mdtraj's
310
+ is nm), times in picoseconds either side. The `topology` argument
311
+ is required for binary formats that don't embed it (XTC, TRR, DCD,
312
+ NetCDF); it accepts either a `molforge.core.Protein` (reused
313
+ directly — no PDB round-trip when the caller passes a Protein in)
314
+ or a path to a PDB. PDB-format trajectories can pass
315
+ `topology=None`. The `atom_indices` parameter slices both the
316
+ coordinate array AND the returned topology, so callers analyzing
317
+ a subset (e.g. backbone atoms only) get a self-consistent
318
+ Trajectory rather than coords-and-topology mismatched. Lazy mdtraj
319
+ import: importing `molforge.io` does not import mdtraj; only the
320
+ trajectory functions do, and they raise
321
+ `MDEngineNotInstalledError` with install instructions when mdtraj
322
+ is absent. NOT wired into `load()` / `save()`: trajectories return
323
+ a different type than the dispatcher's `Protein` / `list[Protein]`
324
+ contract, and the topology argument is something the dispatcher
325
+ has no way to supply. Kept as dedicated entry points. 21 new tests
326
+ in `tests/unit/io/test_trajectory.py` covering reading from PDB,
327
+ the Å unit conversion, Protein-topology reuse identity, path-string
328
+ topology, no-topology PDB, atom-indices subset (including the
329
+ metadata-preserving Protein-slice path), stride, time-array
330
+ carry-through, the "source: mdtraj" metadata marker, the streaming
331
+ chunk count (3+3+3+1 from 10 frames), chunk validation, the
332
+ stride-plus-chunking compose case, the DCD / XTC / PDB write paths
333
+ (XTC round-trip with the documented 0.001-nm precision), and the
334
+ missing-mdtraj error path; mdtraj-using tests are
335
+ `pytest.importorskip` gated. The new module is at high coverage
336
+ (the few uncovered branches are unreachable error-handling
337
+ fallbacks). `pdbfixer.*` was added to mypy's missing-stubs override
338
+ list when `molforge.prep` shipped; no new mypy override is needed
339
+ here since mdtraj is already there.
340
+ - **New subpackage: `molforge.prep` for MD-system preparation.** A
341
+ raw PDB from AlphaFold, RoseTTAFold, the RCSB, or a docking engine
342
+ almost always needs the same clean-up before MD: drop
343
+ crystallographic clutter (waters, buffer salts, sometimes the
344
+ ligand), rebuild missing heavy atoms, cap free termini with ACE /
345
+ NME, and add explicit hydrogens at the right pH. The new
346
+ `molforge.prep` subpackage exposes one composable function per
347
+ step plus a convenience pipeline:
348
+ - `remove_heterogens(protein)` — pure-Python residue-name filter.
349
+ By default drops waters, ions, ligands, and everything outside
350
+ the 20 canonical amino acids + standard nucleotides. `keep_water`
351
+ / `keep_ions` / `keep_ligands` toggles plus an explicit `keep`
352
+ allow-list for cofactors. Recognises multiple water aliases
353
+ (HOH, WAT, H2O, SOL, TIP*) and the common monatomic ions.
354
+ - `fix_missing_atoms(protein)` — wraps PDBFixer's rotamer-library
355
+ rebuild for incomplete side chains. `fix_missing_residues=False`
356
+ by default (de-novo loop modelling is risky);
357
+ `replace_nonstandard=True` by default (MSE → MET, etc.).
358
+ - `add_caps(protein)` — wraps PDBFixer to add ACE / NME caps at
359
+ free termini of every protein chain. Multi-chain aware;
360
+ non-protein chains (ligands, DNA) skipped. Either cap can be
361
+ disabled with an empty string.
362
+ - `add_hydrogens(protein, pH=7.4)` — wraps OpenMM
363
+ `Modeller.addHydrogens` for pH-aware protonation. Idempotent on
364
+ already-protonated input. The `force_field` kwarg accepts both
365
+ registered aliases (`"amber14"`, `"charmm36"`) and bare XML
366
+ filenames.
367
+ - `prepare_for_md(protein)` — convenience entry point that chains
368
+ the four steps with sensible defaults for an
369
+ AlphaFold-PDB-to-OpenMM workflow. Each step's options are
370
+ forwarded; individual steps can be turned off
371
+ (`add_caps_to_termini=False`, `add_explicit_hydrogens=False`).
372
+
373
+ ## [v0.3.0] 2026-06-22
374
+
10
375
  ### Added
11
376
  - **`molforge.io.read_pqr` / `write_pqr` are implemented.** PQR
12
377
  (PDB2PQR / APBS) was a committed import path but a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: molforge
3
- Version: 0.3.0
3
+ Version: 0.4.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/
@@ -50,6 +50,7 @@ Requires-Dist: biopython>=1.83; extra == 'all'
50
50
  Requires-Dist: biotite>=0.40; extra == 'all'
51
51
  Requires-Dist: mdtraj>=1.10; extra == 'all'
52
52
  Requires-Dist: openmm>=8.1; (platform_system != 'Windows') and extra == 'all'
53
+ Requires-Dist: pdbfixer>=1.9; (platform_system != 'Windows') and extra == 'all'
53
54
  Requires-Dist: rdkit>=2023.9; extra == 'all'
54
55
  Requires-Dist: scipy>=1.11; extra == 'all'
55
56
  Requires-Dist: torch>=2.1; extra == 'all'
@@ -83,6 +84,9 @@ Requires-Dist: openmm>=8.1; (platform_system != 'Windows') and extra == 'md'
83
84
  Provides-Extra: ml
84
85
  Requires-Dist: torch>=2.1; extra == 'ml'
85
86
  Requires-Dist: transformers>=4.40; extra == 'ml'
87
+ Provides-Extra: prep
88
+ Requires-Dist: openmm>=8.1; (platform_system != 'Windows') and extra == 'prep'
89
+ Requires-Dist: pdbfixer>=1.9; (platform_system != 'Windows') and extra == 'prep'
86
90
  Provides-Extra: sequence
87
91
  Requires-Dist: biopython>=1.83; extra == 'sequence'
88
92
  Provides-Extra: structure
@@ -146,6 +150,9 @@ pip install "molforge[ml]"
146
150
  # with MD support (openmm, mdtraj)
147
151
  pip install "molforge[md]"
148
152
 
153
+ # with structure prep for MD (pdbfixer + openmm)
154
+ pip install "molforge[prep]"
155
+
149
156
  # with docking (rdkit for small molecules)
150
157
  pip install "molforge[docking]"
151
158
 
@@ -221,7 +228,7 @@ molforge/
221
228
  │ ├── plugins/ # Plugin registry and entry-point discovery
222
229
  │ ├── metrics/ # TM-score, lDDT, GDT-TS, docking metrics
223
230
  │ └── wrappers/ # Thin interfaces to external engines
224
- │ ├── folding/ # AlphaFold, ESMFold, Boltz, Rosetta
231
+ │ ├── folding/ # AlphaFold, ESMFold, Boltz, RoseTTAFold
225
232
  │ ├── docking/ # AutoDock Vina, DiffDock
226
233
  │ └── md/ # OpenMM, GROMACS
227
234
  ├── tests/ # pytest suite (909 passing + skips)
@@ -53,6 +53,9 @@ pip install "molforge[ml]"
53
53
  # with MD support (openmm, mdtraj)
54
54
  pip install "molforge[md]"
55
55
 
56
+ # with structure prep for MD (pdbfixer + openmm)
57
+ pip install "molforge[prep]"
58
+
56
59
  # with docking (rdkit for small molecules)
57
60
  pip install "molforge[docking]"
58
61
 
@@ -128,7 +131,7 @@ molforge/
128
131
  │ ├── plugins/ # Plugin registry and entry-point discovery
129
132
  │ ├── metrics/ # TM-score, lDDT, GDT-TS, docking metrics
130
133
  │ └── wrappers/ # Thin interfaces to external engines
131
- │ ├── folding/ # AlphaFold, ESMFold, Boltz, Rosetta
134
+ │ ├── folding/ # AlphaFold, ESMFold, Boltz, RoseTTAFold
132
135
  │ ├── docking/ # AutoDock Vina, DiffDock
133
136
  │ └── md/ # OpenMM, GROMACS
134
137
  ├── tests/ # pytest suite (909 passing + skips)
@@ -58,6 +58,10 @@ md = [
58
58
  "mdtraj>=1.10",
59
59
  "openmm>=8.1; platform_system != 'Windows'",
60
60
  ]
61
+ prep = [
62
+ "openmm>=8.1; platform_system != 'Windows'",
63
+ "pdbfixer>=1.9; platform_system != 'Windows'",
64
+ ]
61
65
  docking = [
62
66
  "rdkit>=2023.9",
63
67
  ]
@@ -70,7 +74,7 @@ io = [
70
74
  "biotite>=0.40",
71
75
  ]
72
76
  all = [
73
- "molforge[structure,sequence,md,docking,ml,io]",
77
+ "molforge[structure,sequence,md,prep,docking,ml,io]",
74
78
  ]
75
79
  dev = [
76
80
  "pytest>=7.4",
@@ -229,6 +233,7 @@ module = [
229
233
  "mdtraj.*",
230
234
  "meeko.*",
231
235
  "openmm.*",
236
+ "pdbfixer.*",
232
237
  "rdkit.*",
233
238
  "torch.*",
234
239
  "transformers.*",
@@ -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.3.0"
16
+ __version__ = "0.4.0"
17
17
  __all__ = ["__version__"]
@@ -37,6 +37,7 @@ from molforge.core.constants import (
37
37
  )
38
38
  from molforge.core.metadata_keys import ProteinMetadata
39
39
  from molforge.core.protein import Protein
40
+ from molforge.core.provenance import Provenance
40
41
  from molforge.core.residue import Residue
41
42
 
42
43
  __all__ = [ # noqa: RUF022 — grouped by concept, not alphabetical
@@ -56,6 +57,9 @@ __all__ = [ # noqa: RUF022 — grouped by concept, not alphabetical
56
57
  # Metadata vocabulary (string constants live in
57
58
  # molforge.core.metadata_keys; the TypedDict is re-exported here)
58
59
  "ProteinMetadata",
60
+ # Provenance — first-class "what produced this output" record;
61
+ # stored under metadata[PROVENANCE] (see metadata_keys.PROVENANCE).
62
+ "Provenance",
59
63
  # Constants & helpers
60
64
  "THREE_TO_ONE",
61
65
  "ONE_TO_THREE",
@@ -64,6 +64,23 @@ EXPERIMENTAL_METHOD = "experimental_method"
64
64
  RESOLUTION = "resolution"
65
65
  """Resolution in Angstrom (float). Absent for non-diffraction structures."""
66
66
 
67
+ # ----------------------------------------------------------------------
68
+ # Provenance
69
+ # ----------------------------------------------------------------------
70
+
71
+ PROVENANCE = "provenance"
72
+ """First-class provenance record (:class:`molforge.core.Provenance`).
73
+
74
+ The canonical key for "what produced this output." Carries engine
75
+ name, version, parameters, inputs, and a recursive parent pointer to
76
+ the upstream step's provenance. See :mod:`molforge.core.provenance`
77
+ for construction helpers and the data shape.
78
+
79
+ This is the documented replacement for the older ad-hoc
80
+ ``metadata["engine"]`` / ``metadata["source_args"]`` keys. Both
81
+ continue to work for backwards compatibility; new code should write a
82
+ :class:`Provenance` to this key instead."""
83
+
67
84
  # ----------------------------------------------------------------------
68
85
  # Uniform folding-engine keys (every folding wrapper sets these)
69
86
  # ----------------------------------------------------------------------
@@ -166,6 +183,13 @@ class ProteinMetadata(TypedDict, total=False):
166
183
  experimental_method: str
167
184
  resolution: float
168
185
 
186
+ # Provenance — first-class record of what produced this output.
187
+ # The value type is molforge.core.Provenance; declared here as
188
+ # ``Any`` to avoid a runtime circular import (the dataclass lives
189
+ # in molforge.core.provenance which imports nothing from this
190
+ # module).
191
+ provenance: Any
192
+
169
193
  # Uniform folding-engine keys.
170
194
  engine: str
171
195
  source_sequence: str
@@ -203,6 +227,7 @@ DOCUMENTED_KEYS: frozenset[str] = frozenset(
203
227
  DEPOSITION_DATE,
204
228
  EXPERIMENTAL_METHOD,
205
229
  RESOLUTION,
230
+ PROVENANCE,
206
231
  ENGINE,
207
232
  SOURCE_SEQUENCE,
208
233
  CONFIDENCE_PER_RESIDUE,
@@ -240,6 +265,8 @@ __all__ = [
240
265
  "DEPOSITION_DATE",
241
266
  "EXPERIMENTAL_METHOD",
242
267
  "RESOLUTION",
268
+ # Provenance
269
+ "PROVENANCE",
243
270
  # Uniform folding-engine keys
244
271
  "ENGINE",
245
272
  "SOURCE_SEQUENCE",