if-split 0.1.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 (67) hide show
  1. if_split-0.3.0/CHANGELOG.md +88 -0
  2. {if_split-0.1.0 → if_split-0.3.0}/CLAUDE.md +14 -6
  3. if_split-0.3.0/PKG-INFO +546 -0
  4. {if_split-0.1.0 → if_split-0.3.0}/PLAN.md +44 -14
  5. if_split-0.3.0/README.md +527 -0
  6. {if_split-0.1.0 → if_split-0.3.0}/config/default.yaml +31 -5
  7. if_split-0.3.0/config/masterclass.yaml +51 -0
  8. if_split-0.3.0/examples/IF-Split-2026.07.14/README.md +118 -0
  9. if_split-0.3.0/examples/IF-Split-2026.07.14/STATS.txt +28 -0
  10. {if_split-0.1.0/examples/IF-Split-2026.05.31 → if_split-0.3.0/examples/IF-Split-2026.07.14}/config.yaml +8 -4
  11. if_split-0.3.0/examples/IF-Split-2026.07.14/manifest.json +167 -0
  12. {if_split-0.1.0/examples/IF-Split-2026.05.31 → if_split-0.3.0/examples/IF-Split-2026.07.14}/test/metal_test.json +193 -157
  13. if_split-0.1.0/examples/IF-Split-2026.05.31/test/nucleotide_test.json → if_split-0.3.0/examples/IF-Split-2026.07.14/test/nucleic_acid_test.json +9 -2
  14. {if_split-0.1.0/examples/IF-Split-2026.05.31 → if_split-0.3.0/examples/IF-Split-2026.07.14}/test/small_molecule_test.json +3118 -292
  15. {if_split-0.1.0/examples/IF-Split-2026.05.31 → if_split-0.3.0/examples/IF-Split-2026.07.14}/test.json +247 -84
  16. {if_split-0.1.0/examples/IF-Split-2026.05.31 → if_split-0.3.0/examples/IF-Split-2026.07.14}/train.json +1550 -289
  17. {if_split-0.1.0/examples/IF-Split-2026.05.31 → if_split-0.3.0/examples/IF-Split-2026.07.14}/val.json +222 -117
  18. {if_split-0.1.0 → if_split-0.3.0}/pyproject.toml +1 -1
  19. if_split-0.3.0/scripts/audit_nico_histag.py +206 -0
  20. if_split-0.3.0/scripts/consume_split.py +157 -0
  21. if_split-0.3.0/scripts/eval_metal_tiering.py +122 -0
  22. if_split-0.3.0/scripts/eval_sm_tiering.py +73 -0
  23. if_split-0.3.0/scripts/eval_structural_clustering.py +52 -0
  24. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/__init__.py +1 -1
  25. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/cli.py +252 -52
  26. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/cluster.py +46 -1
  27. if_split-0.3.0/src/ifsplit/config.py +312 -0
  28. if_split-0.3.0/src/ifsplit/dataset.py +200 -0
  29. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/enumerate.py +22 -0
  30. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/hydrate.py +17 -2
  31. if_split-0.3.0/src/ifsplit/ligands.py +477 -0
  32. if_split-0.3.0/src/ifsplit/manifest.py +713 -0
  33. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/parse.py +62 -5
  34. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/rcsb.py +24 -2
  35. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/schema.py +104 -2
  36. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/split.py +116 -6
  37. if_split-0.3.0/tests/test_config.py +197 -0
  38. {if_split-0.1.0 → if_split-0.3.0}/tests/test_download.py +45 -0
  39. if_split-0.3.0/tests/test_enumerate_lock.py +298 -0
  40. if_split-0.3.0/tests/test_ligands.py +647 -0
  41. {if_split-0.1.0 → if_split-0.3.0}/tests/test_loader.py +60 -0
  42. {if_split-0.1.0 → if_split-0.3.0}/tests/test_pipeline.py +277 -0
  43. {if_split-0.1.0 → if_split-0.3.0}/tests/test_schema.py +101 -0
  44. {if_split-0.1.0 → if_split-0.3.0}/uv.lock +1 -1
  45. if_split-0.1.0/PKG-INFO +0 -312
  46. if_split-0.1.0/README.md +0 -293
  47. if_split-0.1.0/examples/IF-Split-2026.05.31/README.md +0 -80
  48. if_split-0.1.0/examples/IF-Split-2026.05.31/STATS.txt +0 -20
  49. if_split-0.1.0/examples/IF-Split-2026.05.31/manifest.json +0 -129
  50. if_split-0.1.0/src/ifsplit/config.py +0 -146
  51. if_split-0.1.0/src/ifsplit/dataset.py +0 -112
  52. if_split-0.1.0/src/ifsplit/ligands.py +0 -267
  53. if_split-0.1.0/src/ifsplit/manifest.py +0 -417
  54. if_split-0.1.0/tests/test_config.py +0 -106
  55. if_split-0.1.0/tests/test_enumerate_lock.py +0 -101
  56. if_split-0.1.0/tests/test_ligands.py +0 -247
  57. {if_split-0.1.0 → if_split-0.3.0}/.github/workflows/ci.yml +0 -0
  58. {if_split-0.1.0 → if_split-0.3.0}/.github/workflows/publish.yml +0 -0
  59. {if_split-0.1.0 → if_split-0.3.0}/.gitignore +0 -0
  60. {if_split-0.1.0 → if_split-0.3.0}/.python-version +0 -0
  61. {if_split-0.1.0 → if_split-0.3.0}/LICENSE +0 -0
  62. {if_split-0.1.0 → if_split-0.3.0}/data/cache/.gitkeep +0 -0
  63. {if_split-0.1.0 → if_split-0.3.0}/data/out/.gitkeep +0 -0
  64. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/__main__.py +0 -0
  65. {if_split-0.1.0 → if_split-0.3.0}/src/ifsplit/download.py +0 -0
  66. {if_split-0.1.0 → if_split-0.3.0}/tests/conftest.py +0 -0
  67. {if_split-0.1.0 → if_split-0.3.0}/tests/test_integration.py +0 -0
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ All notable changes to IF-Split are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims to
5
+ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ The **split is always computed from metadata + sequences only** — `build` never
8
+ downloads structure coordinates. That invariant holds across every release below.
9
+
10
+ ## [0.3.0] — 2026-07-14
11
+
12
+ A large release: fold-honest splitting, split-output certification, a two-corpus
13
+ training model, a metadata-only curation overhaul, and offline re-derivability.
14
+
15
+ ### Added
16
+
17
+ - **Fold-level structural leakage control** (opt-in `structural_clustering`:
18
+ `off` | `cath` | `ecod` | `scop2`). Same-fold protein chains are union-merged into
19
+ one leakage-safe component in addition to shared sequence clusters, so a fold cannot
20
+ straddle train/test — using RCSB's precomputed CATH/ECOD/SCOP2 classifications
21
+ (metadata only, no coordinates).
22
+ - **Balance-aware split strategy** (`split_strategy: balanced`). Caps dominant folds
23
+ to train and fills val/test to their *entry* targets from the fold tail, restoring
24
+ ~80/10/10 by entries with thousands of held-out folds. `config/masterclass.yaml`
25
+ ships the fold-honest recipe (`scop2` + `balanced`).
26
+ - **Split-output certification.** The `@2` `dataset.lock` records `split_sha256` (a
27
+ hash of the entry→split partition); `verify` re-derives Stages 3–6 and certifies the
28
+ split *output* reproduced, not just the Stage-1 candidate set.
29
+ - **Two training corpora from one split**: all kept structures as design *backbones*,
30
+ plus a functional-ligand *conditioning-target* corpus (`targets.jsonl`, one row per
31
+ ligand keyed to entry + split + class + tier). `SplitView` exposes both views.
32
+ - **Offline `resplit`** (`if-split resplit --candidates candidates.jsonl --config X`):
33
+ re-derives Stages 3–7 from a cached snapshot with no RCSB — ablate curation /
34
+ clustering / split settings, or tighten a filter, in seconds instead of
35
+ re-enumerating the PDB. The lock records `source` (`build` | `resplit`).
36
+ - **Offline `verify`** (`verify LOCK --candidates candidates.jsonl`): integrity-check
37
+ a distributed dataset with no network; a corrupt candidates file is reported as an
38
+ integrity failure. A `resplit` lock is steered to offline verification.
39
+ - **Per-method resolution caps** (`resolution_max_A_by_method`) and a **cryo-EM
40
+ map-fit floor** (`min_em_backbone_inclusion`, wiring in the previously-unused
41
+ `em_backbone_inclusion` metric). Resolution is now re-derived in Stage 3, so the cut
42
+ is auditable from `candidates.jsonl` and tightenable offline.
43
+ - **Opt-in sequence-usability floor** (`min_modeled_residues`) and an always-on drop of
44
+ empty / all-`X` (poly-UNK) protein chains, which carry no learnable label.
45
+ - RCSB **metal-binding annotations** (GO/InterPro/Pfam) captured to rescue native
46
+ metalloenzymes; `if-split spec` to emit a portable, self-identifying split spec.
47
+
48
+ ### Changed / curation
49
+
50
+ - **Metal tiering**: heavy-atom / lanthanide **phasing derivatives** (Hg/Au/Pt/Pb/Tl/…)
51
+ demoted to `ambiguous` (reported, recoverable) rather than counted as functional
52
+ metal sites; inorganic **Fe-S / metal-oxo / FeMo clusters** (SF4/FES, the OEC) now
53
+ classed `metal`; native Ni/Co (and heavy/lanthanide) sites rescued via annotation,
54
+ affinity, or subject-of-investigation. The lone-Ni/Co His-tag figure was corrected
55
+ (~96% → ~82%).
56
+ - **Glycans** (RCSB CCD `type` = *saccharide*) with no measured affinity are tiered
57
+ `glycan` (decorative / detergent), recoverable via an opt-in tier — not counted as
58
+ small-molecule conditioning targets.
59
+ - **Small molecules**: a measured binding affinity now overrides the additive
60
+ blacklist, so a blacklisted comp that is the real measured ligand stays functional.
61
+ - **Nucleic acids**: `is_nucleic` now recognizes the `NA-hybrid` polymer type; the
62
+ ligand class was renamed `nucleotide` → `nucleic_acid`.
63
+ - The size cap keeps `< 6000` residues correctly (`> max_total_residues`, not `>=`).
64
+ - Adding a resolution-less method (NMR/SAXS) now warns instead of silently returning
65
+ zero entries.
66
+
67
+ ### Fixed
68
+
69
+ - `verify` warns (rather than fails) on a version-only lock mismatch.
70
+ - `fetch` reads split id-lists from the manifest directory, not the current directory.
71
+ - `identity_threshold` is validated against RCSB's precomputed cluster levels
72
+ (30/50/70/90/95/100) so an unsupported level can't silently disable clustering.
73
+ - A bound halide is tiered a counterion, not a functional small molecule.
74
+
75
+ ## [0.2.0] — 2026
76
+
77
+ - Recover non-covalently bound cofactors (FAD/NAD/FMN/NADP, inhibitors) via RCSB's
78
+ `is_subject_of_investigation` flag.
79
+ - Harden Ni/Co metal curation against His-tags absent from the deposited sequence.
80
+ - Shareable split spec (`if-split spec`) and a self-identifying config header.
81
+ - Rename the ligand class `nucleotide` → `nucleic_acid`; PyPI/CI badges + install docs.
82
+
83
+ ## [0.1.0] — 2026
84
+
85
+ - Initial release: a reproducible, date-pinned, ligand-aware train/val/test splitter
86
+ for the PDB. Enumerate → filter → tier ligands → cluster (union-find, leakage-safe)
87
+ → deterministic split → manifest + lock, all from RCSB Search + Data API metadata
88
+ (no coordinates). Optional `fetch` downloads structures for a built split.
@@ -10,7 +10,8 @@ usage. This file is the orientation for working in the repo.
10
10
  **The split is computed from metadata + sequences only — `build` never downloads
11
11
  structure coordinates.** Everything needed (resolution, method, release date,
12
12
  residue counts, per-entity sequences, ligand chem-comp + bound-component signals,
13
- RCSB cluster membership) comes from the RCSB Search + Data APIs. Coordinates
13
+ RCSB sequence-cluster membership, and CATH/ECOD/SCOP2 structural classifications)
14
+ comes from the RCSB Search + Data APIs. Coordinates
14
15
  (mmCIF) are large and only needed downstream, so `fetch` (Stage 2) is optional.
15
16
  Keep it that way: do not add coordinate access to the build path.
16
17
 
@@ -36,6 +37,8 @@ wsl -d ubuntu bash -lc 'cd ~/projects/IF-Split && export PATH="$HOME/.local/bin:
36
37
  uv run ruff check . # lint (must pass)
37
38
  uv run ruff format . # format
38
39
  uv run if-split build --limit 50 --out /tmp/ifs # dev build (small, live RCSB)
40
+ uv run if-split build --config config/masterclass.yaml --out /tmp/mc # fold-honest split (scop2 + balanced)
41
+ uv run if-split resplit --candidates data/out/candidates.jsonl --config X.yaml --out /tmp/rs # re-derive Stages 3-7 offline (no RCSB)
39
42
  ```
40
43
 
41
44
  - `uv sync` sets up the env; `uv sync --extra mlops` adds pyarrow for `fetch`'s
@@ -47,17 +50,22 @@ uv run if-split build --limit 50 --out /tmp/ifs # dev build (small, live RCSB)
47
50
 
48
51
  `enumerate.py`+`rcsb.py` (Stage 1, Search+Data API → candidates.jsonl) →
49
52
  `parse.py` (3, metadata filters) → `ligands.py` (4, confidence tiering) →
50
- `cluster.py` (5, union-find components) `split.py` (6, deterministic hash) →
53
+ `cluster.py` (5, union-find components: sequence + optional fold-level structural
54
+ clustering) → `split.py` (6, split assignment: `hash` | `balanced`) →
51
55
  `manifest.py` (7, lock + manifest + registry, verify/stats) → `dataset.py` (8,
52
- loader). `download.py`+`hydrate.py` are the optional Stage 2 `fetch`.
56
+ loader). `download.py`+`hydrate.py` are the optional Stage 2 `fetch`. `cli.py`'s
57
+ `resplit` re-runs Stages 3-7 from a cached `candidates.jsonl` (no Stage 1) via the
58
+ shared `_run_pipeline`; `verify --candidates` does the same for offline checking.
53
59
 
54
60
  Invariants that must not regress:
55
61
  - **Determinism:** same config → byte-identical `manifest.json` (no wall-clock
56
62
  fields). `test_manifest_is_deterministic` guards this.
57
63
  - **No cross-split leakage:** sequence clusters joined by a shared multi-chain
58
- entry are union-find–merged into one component; a component maps to exactly one
59
- split, so overlap is impossible by construction. `check_no_leakage` is a real
60
- invariant (not a tautology) keep it that way.
64
+ entry (and, with `structural_clustering` on, by a shared fold superfamily) are
65
+ union-find–merged into one component; a component maps to exactly one split, so
66
+ overlap is impossible by construction. This holds for both split strategies
67
+ (`hash` and `balanced`, which only chooses *which* split a whole component lands
68
+ in). `check_no_leakage` is a real invariant (not a tautology) — keep it that way.
61
69
  - **Growth stability:** a cluster/component's split is `hash(salt + canonical_key)`
62
70
  into cumulative fractions, keyed on the global-min member id (not RCSB's volatile
63
71
  integer id). A larger snapshot only *adds* components; `splits.registry.json`