shellde 0.2.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 (89) hide show
  1. shellde-0.2.0/PKG-INFO +285 -0
  2. shellde-0.2.0/README.md +254 -0
  3. shellde-0.2.0/pyproject.toml +51 -0
  4. shellde-0.2.0/setup.cfg +4 -0
  5. shellde-0.2.0/src/shellde/__init__.py +36 -0
  6. shellde-0.2.0/src/shellde/acquisition.py +135 -0
  7. shellde-0.2.0/src/shellde/advisor.py +158 -0
  8. shellde-0.2.0/src/shellde/bench/__init__.py +17 -0
  9. shellde-0.2.0/src/shellde/bench/falsification.py +95 -0
  10. shellde-0.2.0/src/shellde/bench/harness.py +134 -0
  11. shellde-0.2.0/src/shellde/bench/stats.py +46 -0
  12. shellde-0.2.0/src/shellde/campaign.py +257 -0
  13. shellde-0.2.0/src/shellde/candidates.py +341 -0
  14. shellde-0.2.0/src/shellde/cli.py +1517 -0
  15. shellde-0.2.0/src/shellde/colab.py +257 -0
  16. shellde-0.2.0/src/shellde/conformal.py +160 -0
  17. shellde-0.2.0/src/shellde/consensus.py +52 -0
  18. shellde-0.2.0/src/shellde/design_space.py +141 -0
  19. shellde-0.2.0/src/shellde/embeddings/__init__.py +6 -0
  20. shellde-0.2.0/src/shellde/embeddings/esm2.py +76 -0
  21. shellde-0.2.0/src/shellde/embeddings/esmc.py +94 -0
  22. shellde-0.2.0/src/shellde/embeddings/provider.py +105 -0
  23. shellde-0.2.0/src/shellde/features/__init__.py +22 -0
  24. shellde-0.2.0/src/shellde/features/base.py +49 -0
  25. shellde-0.2.0/src/shellde/features/defaults.py +11 -0
  26. shellde-0.2.0/src/shellde/features/embedding.py +80 -0
  27. shellde-0.2.0/src/shellde/features/inverse_folding.py +66 -0
  28. shellde-0.2.0/src/shellde/features/matrix.py +50 -0
  29. shellde-0.2.0/src/shellde/features/naturalness.py +65 -0
  30. shellde-0.2.0/src/shellde/features/onehot.py +55 -0
  31. shellde-0.2.0/src/shellde/features/pairwise.py +64 -0
  32. shellde-0.2.0/src/shellde/funclib.py +331 -0
  33. shellde-0.2.0/src/shellde/gating.py +181 -0
  34. shellde-0.2.0/src/shellde/holo.py +175 -0
  35. shellde-0.2.0/src/shellde/hotspots.py +108 -0
  36. shellde-0.2.0/src/shellde/loop.py +161 -0
  37. shellde-0.2.0/src/shellde/msa.py +186 -0
  38. shellde-0.2.0/src/shellde/naturalness.py +142 -0
  39. shellde-0.2.0/src/shellde/oracle.py +94 -0
  40. shellde-0.2.0/src/shellde/plm.py +145 -0
  41. shellde-0.2.0/src/shellde/prereg.py +41 -0
  42. shellde-0.2.0/src/shellde/protocols.py +87 -0
  43. shellde-0.2.0/src/shellde/rank.py +103 -0
  44. shellde-0.2.0/src/shellde/report.py +132 -0
  45. shellde-0.2.0/src/shellde/selector.py +63 -0
  46. shellde-0.2.0/src/shellde/sitefinder.py +465 -0
  47. shellde-0.2.0/src/shellde/structure.py +356 -0
  48. shellde-0.2.0/src/shellde/surrogate.py +323 -0
  49. shellde-0.2.0/src/shellde/types.py +66 -0
  50. shellde-0.2.0/src/shellde/zero_shot.py +160 -0
  51. shellde-0.2.0/src/shellde.egg-info/PKG-INFO +285 -0
  52. shellde-0.2.0/src/shellde.egg-info/SOURCES.txt +87 -0
  53. shellde-0.2.0/src/shellde.egg-info/dependency_links.txt +1 -0
  54. shellde-0.2.0/src/shellde.egg-info/entry_points.txt +2 -0
  55. shellde-0.2.0/src/shellde.egg-info/requires.txt +17 -0
  56. shellde-0.2.0/src/shellde.egg-info/top_level.txt +1 -0
  57. shellde-0.2.0/tests/test_acquisition.py +77 -0
  58. shellde-0.2.0/tests/test_advisor.py +184 -0
  59. shellde-0.2.0/tests/test_anchor.py +58 -0
  60. shellde-0.2.0/tests/test_bench.py +51 -0
  61. shellde-0.2.0/tests/test_campaign.py +215 -0
  62. shellde-0.2.0/tests/test_candidates.py +83 -0
  63. shellde-0.2.0/tests/test_cli.py +326 -0
  64. shellde-0.2.0/tests/test_colab.py +133 -0
  65. shellde-0.2.0/tests/test_conformal.py +199 -0
  66. shellde-0.2.0/tests/test_consensus.py +33 -0
  67. shellde-0.2.0/tests/test_defaults.py +21 -0
  68. shellde-0.2.0/tests/test_design_space.py +56 -0
  69. shellde-0.2.0/tests/test_embeddings.py +149 -0
  70. shellde-0.2.0/tests/test_encode_fast.py +82 -0
  71. shellde-0.2.0/tests/test_falsification.py +58 -0
  72. shellde-0.2.0/tests/test_features.py +54 -0
  73. shellde-0.2.0/tests/test_funclib.py +285 -0
  74. shellde-0.2.0/tests/test_gating.py +161 -0
  75. shellde-0.2.0/tests/test_heldout.py +37 -0
  76. shellde-0.2.0/tests/test_holo.py +143 -0
  77. shellde-0.2.0/tests/test_hotspots.py +59 -0
  78. shellde-0.2.0/tests/test_msa.py +54 -0
  79. shellde-0.2.0/tests/test_naturalness.py +139 -0
  80. shellde-0.2.0/tests/test_oracle_loop.py +57 -0
  81. shellde-0.2.0/tests/test_pairwise.py +50 -0
  82. shellde-0.2.0/tests/test_plm.py +176 -0
  83. shellde-0.2.0/tests/test_prereg.py +20 -0
  84. shellde-0.2.0/tests/test_rank_report.py +120 -0
  85. shellde-0.2.0/tests/test_selector.py +43 -0
  86. shellde-0.2.0/tests/test_sitefinder.py +145 -0
  87. shellde-0.2.0/tests/test_structure.py +245 -0
  88. shellde-0.2.0/tests/test_surrogate.py +172 -0
  89. shellde-0.2.0/tests/test_zero_shot.py +100 -0
shellde-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,285 @@
1
+ Metadata-Version: 2.4
2
+ Name: shellde
3
+ Version: 0.2.0
4
+ Summary: Low-N, acquisition-driven protein mutation campaign planner: additive default, opt-in signals, honest abstention
5
+ Author: gyuminlee-repo
6
+ Keywords: directed-evolution,protein-engineering,active-learning,bayesian-optimization,epistasis,mutation-recommendation,uncertainty-calibration,active-site
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: numpy
18
+ Requires-Dist: scipy
19
+ Requires-Dist: scikit-learn
20
+ Requires-Dist: pandas
21
+ Provides-Extra: plm
22
+ Requires-Dist: torch; extra == "plm"
23
+ Requires-Dist: esm; extra == "plm"
24
+ Provides-Extra: plm2
25
+ Requires-Dist: fair-esm; extra == "plm2"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest; extra == "dev"
28
+ Requires-Dist: pyright; extra == "dev"
29
+ Requires-Dist: build; extra == "dev"
30
+ Requires-Dist: twine; extra == "dev"
31
+
32
+ # ShellDE
33
+
34
+ Low-N, acquisition-driven protein mutation recommendation. The spine is one measurement campaign:
35
+
36
+ ```text
37
+ R0 seed to measure -> fit surrogate on all measured data -> acquire next plate -> measure -> repeat
38
+ ```
39
+
40
+ ShellDE is not a magic zero-shot "better protein" predictor. Zero-shot, MSA, inverse-folding,
41
+ structure, and ddG signals are used as priors, constraints, or features. The function-aligned signal
42
+ is still measurement. The tool's job is to spend a small measurement budget better.
43
+
44
+ > **Naming.** `ShellDE` in prose, `shellde` as the PyPI distribution, the import, and the command. Never
45
+ > `Shellde`, `shellDE`, or `SHELLDE`.
46
+ >
47
+ > **Renamed.** This project was `epiquire` through v0.1.46.00 and is `shellde` from v0.2.00.00. The name
48
+ > is the active-site contact SHELL that `resolve-site` computes (and the 2nd-shell ring that
49
+ > `--exclude-catalytic` designs over), plus DE for directed evolution.
50
+ >
51
+ > **PyPI status (checked 2026-08-28).** `shellde` is NOT on PyPI: the PyPI JSON API returns 404 for it.
52
+ > `epiquire` IS on PyPI at 0.1.21, and that wheel still installs the OLD pre-rename package, unchanged,
53
+ > which is why a pinned Colab install naming it keeps working. No deprecation shim exists, in this tree
54
+ > or on PyPI. Publishing `shellde`, and only then turning `epiquire` into a shim that points at it, are
55
+ > PLANNED owner actions tracked in `docs/RENAME_PLAN.md`, not things already done.
56
+
57
+ ## Install
58
+
59
+ `shellde` is not on PyPI yet, so install it from a source checkout of this repository. Run this from
60
+ the repository root, the directory holding `pyproject.toml`:
61
+
62
+ ```bash
63
+ pip install . # core deps: numpy/scipy/scikit-learn/pandas
64
+ pip install ".[plm]" # optional: adds torch + ESM-C (PLM re-rank / naturalness)
65
+ pip install -e . # editable, when working on the code itself
66
+ ```
67
+
68
+ Verified 2026-08-28 in a throwaway venv: `pip install .` builds `shellde-0.2.0` and the installed
69
+ `shellde` console script renders `usage: shellde`.
70
+
71
+ The short PyPI form stays here because it becomes the recommended path once the distribution is
72
+ published. It returns a 404 today, so do not run it yet:
73
+
74
+ ```bash
75
+ pip install shellde # ONLY AFTER shellde is published to PyPI (not yet; 404 today)
76
+ pip install "shellde[plm]" # ONLY AFTER shellde is published to PyPI (not yet; 404 today)
77
+ ```
78
+
79
+ Or run it in your browser with **no install** via the Colab notebook below.
80
+
81
+ ## Easiest entry point: `shellde round`
82
+
83
+ One command per round. It auto-resolves the active-site design region (the `resolve-site` evidence
84
+ ladder: UniProt -> linked PDB cocrystal -> AlphaFold) and then runs the campaign round, so there is
85
+ no position bookkeeping by hand.
86
+
87
+ **Run it in your browser, no install:** open the notebook in Google Colab via its public gist (works
88
+ without repo access): [open in Colab](https://colab.research.google.com/gist/gyuminlee-repo/1a7a0b5dbf3919d652a6ba2dbdb3f49a/epiquire_round_colab.ipynb).
89
+ The notebook is a 3-line bootstrap over a `plate(...)` call: all logic lives in the pip package, so the
90
+ notebook file never needs re-uploading. That link opens the PRE-RENAME notebook. The published gist
91
+ still installs `epiquire` and imports `epiquire.colab.plate`, and that is exactly why it still runs
92
+ today. `notebooks/COLAB_GIST.md` records the resync order, which waits on the
93
+ PyPI publish.
94
+
95
+ Once `shellde` is published and the gist is resynced, any notebook can skip the form and call it
96
+ directly with the three lines below. They do not work yet, because the first one 404s:
97
+
98
+ ```python
99
+ %pip install -q -U shellde
100
+ from shellde.colab import plate
101
+ df = plate("...WT sequence...", uniprot="Q50L36", auto_msa=True) # R0 seed plate, MSA built for you
102
+ ```
103
+
104
+ The core loop is numpy/scikit-learn (seconds); only the optional PLM path is heavy (Colab's free GPU).
105
+
106
+ ```bash
107
+ # R0: no --measured -> the funclib seed plate to MEASURE. The seed needs a tolerance signal;
108
+ # supply --msa/--ddg/--if-logprobs, OR --auto-msa to build the MSA from the WT (ColabFold MMseqs2).
109
+ shellde round WT.fasta --uniprot Q50L36 --auto-msa --plate 95 --outdir outputs/round
110
+
111
+ # Rk: add each round's measured results -> the next AL plate over the full saturation universe
112
+ shellde round WT.fasta --uniprot Q50L36 --measured round0_measured.csv --plate 95 --outdir outputs/round
113
+ ```
114
+
115
+ Point at the design region with whatever you have: `--uniprot ACC` (or `--uniprot auto` to find the accession by
116
+ an EXACT match of your WT sequence against UniProt), `--query`/`--pdb` (auto ladder), a local
117
+ `--holo` structure, or explicit `--positions`. Give more than one and the most-specific wins,
118
+ resolved by a fixed priority `positions > holo > pdb+uniprot > pdb > uniprot/query` -- so you can just
119
+ pass whatever you have. Two special combinations: giving **both** `--pdb` and `--uniprot` designs over
120
+ the PDB contact shell while flagging (QC) the UniProt catalytic core that falls inside it, and adding
121
+ `--exclude-catalytic` then designs only the tunable 2nd-shell ring (shell minus the usually-lethal
122
+ catalytic core). If the ladder cannot map evidence onto your WT it abstains (supply `--positions` or
123
+ `--holo`) rather than guessing. `round` is a thin wrapper: `resolve-site` and `campaign` below remain
124
+ the explicit, fully-configurable commands it calls.
125
+ No cocrystal at all? `--find-holo` (on `round`/`resolve-site`) Foldseek-searches for a ligand-bound
126
+ (holo) structural homolog of your WT and, if a SIGNIFICANT one exists (E-value + coverage gated), uses
127
+ its ligand pose to define the shell; it abstains when only distant homologs are found so a mismatched
128
+ pocket is never silently borrowed. When several evidence types are available (e.g. `--pdb` + `--uniprot`)
129
+ ShellDE prints an evidence-consensus map: each position tagged with which independent lines support it
130
+ and a confidence tier (high = >=2 agree), so you can measure high-confidence positions first.
131
+
132
+ ## The campaign engine (what `round` calls)
133
+
134
+ Use `campaign` when starting an active-site campaign. It combines the two validated pieces:
135
+
136
+ 1. **R0 cold-start seed**: FuncLib-style active-site library construction. This proposes variants to
137
+ measure; it does not predict winners.
138
+ 2. **R1+ active learning**: after measurements arrive, the AL loop searches the full saturation space
139
+ of the design positions, not just the R0 seed.
140
+
141
+ ### R0: generate the seed plate
142
+
143
+ ```bash
144
+ shellde campaign WT.fasta \
145
+ --holo holo_structure.pdb --ligand-resnames LIG \
146
+ --msa alignment.a3m \
147
+ --plate 95 \
148
+ --outdir outputs/campaign
149
+ ```
150
+
151
+ This writes `outputs/campaign/round0_plate.csv` with `variant,mutations,n_mut,seed_score`. Measure
152
+ those variants and save a `variant,fitness` CSV.
153
+
154
+ You can use explicit design positions instead of a holo structure:
155
+
156
+ ```bash
157
+ shellde campaign WT.fasta \
158
+ --positions 183,184,227,228 \
159
+ --msa alignment.a3m \
160
+ --plate 95 \
161
+ --outdir outputs/campaign
162
+ ```
163
+
164
+ ### R1+: propose the next plate from measured data
165
+
166
+ ```bash
167
+ shellde campaign WT.fasta \
168
+ --positions 183,184,227,228 \
169
+ --measured round0_measured.csv \
170
+ --plate 95 \
171
+ --outdir outputs/campaign
172
+ ```
173
+
174
+ With multiple rounds, pass all accumulated CSVs:
175
+
176
+ ```bash
177
+ shellde campaign WT.fasta \
178
+ --positions 183,184,227,228 \
179
+ --measured round0_measured.csv round1_measured.csv \
180
+ --plate 95 \
181
+ --outdir outputs/campaign
182
+ ```
183
+
184
+ On later rounds, add `--plm esmc` to rerank the plate with an ESM-C surrogate refit on YOUR measured
185
+ data (EVOLVEpro-style; opt-in, GPU-recommended). Every Rk round also auto-prints an `advise` line --
186
+ held-out Spearman + a recommendation (`gather_more` / `model_reliable` / `open_epistasis`) -- so you
187
+ know each round whether to trust the surrogate or keep measuring broadly.
188
+
189
+ ### Simulate against a complete landscape
190
+
191
+ For validation or retrospective benchmarks:
192
+
193
+ ```bash
194
+ shellde campaign WT.fasta \
195
+ --simulate data/trpb.csv \
196
+ --positions 183,184,227,228 \
197
+ --msa isps_run/msa_trpb.a3m \
198
+ --strategies funclib singles random \
199
+ --plate 95 --rounds 3 --seeds 20 --jobs 4 \
200
+ --model-class ridge \
201
+ --outdir artifacts/campaign_trpb
202
+ ```
203
+
204
+ `--jobs` parallelizes independent seeds with spawn processes; serial output and parallel output are
205
+ byte-identical by test.
206
+
207
+ ## Other commands
208
+
209
+ - `shellde recommend measured.csv`: rank next candidates from accumulated measurements. This is the
210
+ AL round engine exposed directly. If you pass `--if-logprobs`, ShellDE CV-gates that signal by
211
+ default and uses it only if it improves held-out performance on this protein. Use
212
+ `--no-auto-signals` only as an expert override. `--recombine` also adds recombinations of your
213
+ MEASURED beneficials (variants beating WT) to the candidate pool -- the strategy the real-data
214
+ evidence supports (measure broadly, then recombine confirmed wins).
215
+ - `shellde resolve-site WT.fasta --uniprot ACC` (or `--query`): resolve active-site/binding positions
216
+ from UniProt experimental curation, sequence-aligned onto YOUR WT numbering (handles transit-peptide
217
+ / species offsets), with provenance + confidence per position. If UniProt features do not map it
218
+ falls back to a UniProt-linked PDB cocrystal (active-site shells); `--pdb ID` forces the structure
219
+ path. If neither maps, it fetches the AlphaFold model (predicted, apo). Optional `--apo-pocket`
220
+ (fpocket) guesses pocket positions but is LOW-confidence -- it missed the catalytic site on ispS,
221
+ so it is warned, not default. `--find-holo` instead Foldseek-finds a ligand-bound homolog (gated on
222
+ significance) to borrow a pose from; giving `--pdb` + `--uniprot` prints an evidence-consensus map
223
+ (per-position support + confidence tier). Abstains on positions rather than guessing -- then provide
224
+ `--holo`/`--positions`. Feed resolved positions to `campaign`.
225
+ - `shellde hotspots scan.csv`: rank hotspot positions from a whole-protein single-mutant scan
226
+ (`variant,activity`; CSV or Excel), then hand the top positions to `round --positions` for
227
+ combinatorial AL. Combinatorial means the CANDIDATE POOL spans multi-mutants; the fitted model is
228
+ additive by default (one-hot + bootstrap-ridge ensemble). Explicit pairwise epistasis is an opt-in
229
+ on `recommend`: `--auto-gate-pairwise` opens the block only if cross-validated evidence on your own
230
+ measured data supports it, while `--contacts-pdb` opens a contact-restricted pairwise block
231
+ unconditionally (no CV gate).
232
+ Honest: scan-based position selection is a coarse pre-filter,
233
+ reliable only on additive sites (see `artifacts/hotspots_backtest_evidence.md`).
234
+ - `shellde funclib WT.fasta ...`: build only the active-site R0 measurement library. Useful when you
235
+ want the seed plate without the campaign wrapper.
236
+ - `shellde advise measured.csv`: readiness check on measured data (held-out Spearman + pairwise gate
237
+ -> gather_more / model_reliable / open_epistasis). Also auto-printed at the end of every `round`/`campaign`
238
+ Rk round, so you are told each round whether to trust the surrogate or keep measuring.
239
+ - `shellde bench`: synthetic alpha-spectrum benchmark.
240
+ - `shellde report`: summarize a JSON report.
241
+
242
+ ## What the opt-ins mean
243
+
244
+ Most optional flags are not feature clutter. They are either:
245
+
246
+ - **resources you must provide**: MSA, PDB, inverse-folding table, ddG table, PLM cache; or
247
+ - **expert overrides** for known regimes.
248
+
249
+ The default stays conservative because low-N model selection overfits. When ShellDE can decide from
250
+ this protein's measured data, it should decide itself -- and that is wired per command, not in general.
251
+ On `recommend`: a supplied `--if-logprobs` signal is CV-gated by default, and pairwise epistasis has
252
+ its own data gate (`--auto-gate-pairwise`). Not wired elsewhere: `--contacts-pdb` opens the pairwise
253
+ block with no CV gate, `campaign` and `round` add `--if-logprobs` unconditionally, and `--plm` /
254
+ `--naturalness` are never gated on any command. See `docs/OPTIN_AUDIT.md`.
255
+
256
+ ## Honest scope
257
+
258
+ Evidence so far supports these claims:
259
+
260
+ - active-site FuncLib-style seeds enrich measurable libraries and improve low-budget discovery;
261
+ - the global activity winner can be outside the seed, so AL must search beyond the seed;
262
+ - stability/ddG signals are best used as constraints, while activity improvement is measurement-bound;
263
+ - high-order activity epistasis makes zero-shot winner prediction unreliable.
264
+
265
+ Evidence does **not** support: "ShellDE predicts the best protein from sequence/structure alone." It
266
+ increases the probability and efficiency of finding a better variant per measured plate, conditional on
267
+ the design space and assay.
268
+
269
+ ## When NOT to use ShellDE
270
+
271
+ The focused active-site campaign helps only when the wins plausibly live in a region you can name
272
+ and measure combinatorially. Do NOT reach for it when:
273
+
274
+ - you do not know where beneficial mutations are (no mechanistic/structural prior on the target site);
275
+ - the wins are likely distal/distributed across the protein (use a broad whole-protein single-mutant
276
+ scan instead -- that is a different tool class; the ShellDE additive default cannot rank
277
+ whole-protein singles because one-hot has no cross-position transfer);
278
+ - you cannot measure a focused combinatorial library (no assay throughput at the chosen site).
279
+
280
+ Real-data caution (PtIspS, `artifacts/ispS_wetlab_validation.md`): a campaign run as a broad
281
+ single-mutant scan with the active site barely sampled and the global winner distal is exactly the
282
+ regime ShellDE is NOT for. Pick broad-vs-focused from your biology; ShellDE does not decide it.
283
+
284
+ More detail: `ARCHITECTURE.md`, `artifacts/DECISION_EVIDENCE.md`, and the Obsidian decision log named
285
+ there.
@@ -0,0 +1,254 @@
1
+ # ShellDE
2
+
3
+ Low-N, acquisition-driven protein mutation recommendation. The spine is one measurement campaign:
4
+
5
+ ```text
6
+ R0 seed to measure -> fit surrogate on all measured data -> acquire next plate -> measure -> repeat
7
+ ```
8
+
9
+ ShellDE is not a magic zero-shot "better protein" predictor. Zero-shot, MSA, inverse-folding,
10
+ structure, and ddG signals are used as priors, constraints, or features. The function-aligned signal
11
+ is still measurement. The tool's job is to spend a small measurement budget better.
12
+
13
+ > **Naming.** `ShellDE` in prose, `shellde` as the PyPI distribution, the import, and the command. Never
14
+ > `Shellde`, `shellDE`, or `SHELLDE`.
15
+ >
16
+ > **Renamed.** This project was `epiquire` through v0.1.46.00 and is `shellde` from v0.2.00.00. The name
17
+ > is the active-site contact SHELL that `resolve-site` computes (and the 2nd-shell ring that
18
+ > `--exclude-catalytic` designs over), plus DE for directed evolution.
19
+ >
20
+ > **PyPI status (checked 2026-08-28).** `shellde` is NOT on PyPI: the PyPI JSON API returns 404 for it.
21
+ > `epiquire` IS on PyPI at 0.1.21, and that wheel still installs the OLD pre-rename package, unchanged,
22
+ > which is why a pinned Colab install naming it keeps working. No deprecation shim exists, in this tree
23
+ > or on PyPI. Publishing `shellde`, and only then turning `epiquire` into a shim that points at it, are
24
+ > PLANNED owner actions tracked in `docs/RENAME_PLAN.md`, not things already done.
25
+
26
+ ## Install
27
+
28
+ `shellde` is not on PyPI yet, so install it from a source checkout of this repository. Run this from
29
+ the repository root, the directory holding `pyproject.toml`:
30
+
31
+ ```bash
32
+ pip install . # core deps: numpy/scipy/scikit-learn/pandas
33
+ pip install ".[plm]" # optional: adds torch + ESM-C (PLM re-rank / naturalness)
34
+ pip install -e . # editable, when working on the code itself
35
+ ```
36
+
37
+ Verified 2026-08-28 in a throwaway venv: `pip install .` builds `shellde-0.2.0` and the installed
38
+ `shellde` console script renders `usage: shellde`.
39
+
40
+ The short PyPI form stays here because it becomes the recommended path once the distribution is
41
+ published. It returns a 404 today, so do not run it yet:
42
+
43
+ ```bash
44
+ pip install shellde # ONLY AFTER shellde is published to PyPI (not yet; 404 today)
45
+ pip install "shellde[plm]" # ONLY AFTER shellde is published to PyPI (not yet; 404 today)
46
+ ```
47
+
48
+ Or run it in your browser with **no install** via the Colab notebook below.
49
+
50
+ ## Easiest entry point: `shellde round`
51
+
52
+ One command per round. It auto-resolves the active-site design region (the `resolve-site` evidence
53
+ ladder: UniProt -> linked PDB cocrystal -> AlphaFold) and then runs the campaign round, so there is
54
+ no position bookkeeping by hand.
55
+
56
+ **Run it in your browser, no install:** open the notebook in Google Colab via its public gist (works
57
+ without repo access): [open in Colab](https://colab.research.google.com/gist/gyuminlee-repo/1a7a0b5dbf3919d652a6ba2dbdb3f49a/epiquire_round_colab.ipynb).
58
+ The notebook is a 3-line bootstrap over a `plate(...)` call: all logic lives in the pip package, so the
59
+ notebook file never needs re-uploading. That link opens the PRE-RENAME notebook. The published gist
60
+ still installs `epiquire` and imports `epiquire.colab.plate`, and that is exactly why it still runs
61
+ today. `notebooks/COLAB_GIST.md` records the resync order, which waits on the
62
+ PyPI publish.
63
+
64
+ Once `shellde` is published and the gist is resynced, any notebook can skip the form and call it
65
+ directly with the three lines below. They do not work yet, because the first one 404s:
66
+
67
+ ```python
68
+ %pip install -q -U shellde
69
+ from shellde.colab import plate
70
+ df = plate("...WT sequence...", uniprot="Q50L36", auto_msa=True) # R0 seed plate, MSA built for you
71
+ ```
72
+
73
+ The core loop is numpy/scikit-learn (seconds); only the optional PLM path is heavy (Colab's free GPU).
74
+
75
+ ```bash
76
+ # R0: no --measured -> the funclib seed plate to MEASURE. The seed needs a tolerance signal;
77
+ # supply --msa/--ddg/--if-logprobs, OR --auto-msa to build the MSA from the WT (ColabFold MMseqs2).
78
+ shellde round WT.fasta --uniprot Q50L36 --auto-msa --plate 95 --outdir outputs/round
79
+
80
+ # Rk: add each round's measured results -> the next AL plate over the full saturation universe
81
+ shellde round WT.fasta --uniprot Q50L36 --measured round0_measured.csv --plate 95 --outdir outputs/round
82
+ ```
83
+
84
+ Point at the design region with whatever you have: `--uniprot ACC` (or `--uniprot auto` to find the accession by
85
+ an EXACT match of your WT sequence against UniProt), `--query`/`--pdb` (auto ladder), a local
86
+ `--holo` structure, or explicit `--positions`. Give more than one and the most-specific wins,
87
+ resolved by a fixed priority `positions > holo > pdb+uniprot > pdb > uniprot/query` -- so you can just
88
+ pass whatever you have. Two special combinations: giving **both** `--pdb` and `--uniprot` designs over
89
+ the PDB contact shell while flagging (QC) the UniProt catalytic core that falls inside it, and adding
90
+ `--exclude-catalytic` then designs only the tunable 2nd-shell ring (shell minus the usually-lethal
91
+ catalytic core). If the ladder cannot map evidence onto your WT it abstains (supply `--positions` or
92
+ `--holo`) rather than guessing. `round` is a thin wrapper: `resolve-site` and `campaign` below remain
93
+ the explicit, fully-configurable commands it calls.
94
+ No cocrystal at all? `--find-holo` (on `round`/`resolve-site`) Foldseek-searches for a ligand-bound
95
+ (holo) structural homolog of your WT and, if a SIGNIFICANT one exists (E-value + coverage gated), uses
96
+ its ligand pose to define the shell; it abstains when only distant homologs are found so a mismatched
97
+ pocket is never silently borrowed. When several evidence types are available (e.g. `--pdb` + `--uniprot`)
98
+ ShellDE prints an evidence-consensus map: each position tagged with which independent lines support it
99
+ and a confidence tier (high = >=2 agree), so you can measure high-confidence positions first.
100
+
101
+ ## The campaign engine (what `round` calls)
102
+
103
+ Use `campaign` when starting an active-site campaign. It combines the two validated pieces:
104
+
105
+ 1. **R0 cold-start seed**: FuncLib-style active-site library construction. This proposes variants to
106
+ measure; it does not predict winners.
107
+ 2. **R1+ active learning**: after measurements arrive, the AL loop searches the full saturation space
108
+ of the design positions, not just the R0 seed.
109
+
110
+ ### R0: generate the seed plate
111
+
112
+ ```bash
113
+ shellde campaign WT.fasta \
114
+ --holo holo_structure.pdb --ligand-resnames LIG \
115
+ --msa alignment.a3m \
116
+ --plate 95 \
117
+ --outdir outputs/campaign
118
+ ```
119
+
120
+ This writes `outputs/campaign/round0_plate.csv` with `variant,mutations,n_mut,seed_score`. Measure
121
+ those variants and save a `variant,fitness` CSV.
122
+
123
+ You can use explicit design positions instead of a holo structure:
124
+
125
+ ```bash
126
+ shellde campaign WT.fasta \
127
+ --positions 183,184,227,228 \
128
+ --msa alignment.a3m \
129
+ --plate 95 \
130
+ --outdir outputs/campaign
131
+ ```
132
+
133
+ ### R1+: propose the next plate from measured data
134
+
135
+ ```bash
136
+ shellde campaign WT.fasta \
137
+ --positions 183,184,227,228 \
138
+ --measured round0_measured.csv \
139
+ --plate 95 \
140
+ --outdir outputs/campaign
141
+ ```
142
+
143
+ With multiple rounds, pass all accumulated CSVs:
144
+
145
+ ```bash
146
+ shellde campaign WT.fasta \
147
+ --positions 183,184,227,228 \
148
+ --measured round0_measured.csv round1_measured.csv \
149
+ --plate 95 \
150
+ --outdir outputs/campaign
151
+ ```
152
+
153
+ On later rounds, add `--plm esmc` to rerank the plate with an ESM-C surrogate refit on YOUR measured
154
+ data (EVOLVEpro-style; opt-in, GPU-recommended). Every Rk round also auto-prints an `advise` line --
155
+ held-out Spearman + a recommendation (`gather_more` / `model_reliable` / `open_epistasis`) -- so you
156
+ know each round whether to trust the surrogate or keep measuring broadly.
157
+
158
+ ### Simulate against a complete landscape
159
+
160
+ For validation or retrospective benchmarks:
161
+
162
+ ```bash
163
+ shellde campaign WT.fasta \
164
+ --simulate data/trpb.csv \
165
+ --positions 183,184,227,228 \
166
+ --msa isps_run/msa_trpb.a3m \
167
+ --strategies funclib singles random \
168
+ --plate 95 --rounds 3 --seeds 20 --jobs 4 \
169
+ --model-class ridge \
170
+ --outdir artifacts/campaign_trpb
171
+ ```
172
+
173
+ `--jobs` parallelizes independent seeds with spawn processes; serial output and parallel output are
174
+ byte-identical by test.
175
+
176
+ ## Other commands
177
+
178
+ - `shellde recommend measured.csv`: rank next candidates from accumulated measurements. This is the
179
+ AL round engine exposed directly. If you pass `--if-logprobs`, ShellDE CV-gates that signal by
180
+ default and uses it only if it improves held-out performance on this protein. Use
181
+ `--no-auto-signals` only as an expert override. `--recombine` also adds recombinations of your
182
+ MEASURED beneficials (variants beating WT) to the candidate pool -- the strategy the real-data
183
+ evidence supports (measure broadly, then recombine confirmed wins).
184
+ - `shellde resolve-site WT.fasta --uniprot ACC` (or `--query`): resolve active-site/binding positions
185
+ from UniProt experimental curation, sequence-aligned onto YOUR WT numbering (handles transit-peptide
186
+ / species offsets), with provenance + confidence per position. If UniProt features do not map it
187
+ falls back to a UniProt-linked PDB cocrystal (active-site shells); `--pdb ID` forces the structure
188
+ path. If neither maps, it fetches the AlphaFold model (predicted, apo). Optional `--apo-pocket`
189
+ (fpocket) guesses pocket positions but is LOW-confidence -- it missed the catalytic site on ispS,
190
+ so it is warned, not default. `--find-holo` instead Foldseek-finds a ligand-bound homolog (gated on
191
+ significance) to borrow a pose from; giving `--pdb` + `--uniprot` prints an evidence-consensus map
192
+ (per-position support + confidence tier). Abstains on positions rather than guessing -- then provide
193
+ `--holo`/`--positions`. Feed resolved positions to `campaign`.
194
+ - `shellde hotspots scan.csv`: rank hotspot positions from a whole-protein single-mutant scan
195
+ (`variant,activity`; CSV or Excel), then hand the top positions to `round --positions` for
196
+ combinatorial AL. Combinatorial means the CANDIDATE POOL spans multi-mutants; the fitted model is
197
+ additive by default (one-hot + bootstrap-ridge ensemble). Explicit pairwise epistasis is an opt-in
198
+ on `recommend`: `--auto-gate-pairwise` opens the block only if cross-validated evidence on your own
199
+ measured data supports it, while `--contacts-pdb` opens a contact-restricted pairwise block
200
+ unconditionally (no CV gate).
201
+ Honest: scan-based position selection is a coarse pre-filter,
202
+ reliable only on additive sites (see `artifacts/hotspots_backtest_evidence.md`).
203
+ - `shellde funclib WT.fasta ...`: build only the active-site R0 measurement library. Useful when you
204
+ want the seed plate without the campaign wrapper.
205
+ - `shellde advise measured.csv`: readiness check on measured data (held-out Spearman + pairwise gate
206
+ -> gather_more / model_reliable / open_epistasis). Also auto-printed at the end of every `round`/`campaign`
207
+ Rk round, so you are told each round whether to trust the surrogate or keep measuring.
208
+ - `shellde bench`: synthetic alpha-spectrum benchmark.
209
+ - `shellde report`: summarize a JSON report.
210
+
211
+ ## What the opt-ins mean
212
+
213
+ Most optional flags are not feature clutter. They are either:
214
+
215
+ - **resources you must provide**: MSA, PDB, inverse-folding table, ddG table, PLM cache; or
216
+ - **expert overrides** for known regimes.
217
+
218
+ The default stays conservative because low-N model selection overfits. When ShellDE can decide from
219
+ this protein's measured data, it should decide itself -- and that is wired per command, not in general.
220
+ On `recommend`: a supplied `--if-logprobs` signal is CV-gated by default, and pairwise epistasis has
221
+ its own data gate (`--auto-gate-pairwise`). Not wired elsewhere: `--contacts-pdb` opens the pairwise
222
+ block with no CV gate, `campaign` and `round` add `--if-logprobs` unconditionally, and `--plm` /
223
+ `--naturalness` are never gated on any command. See `docs/OPTIN_AUDIT.md`.
224
+
225
+ ## Honest scope
226
+
227
+ Evidence so far supports these claims:
228
+
229
+ - active-site FuncLib-style seeds enrich measurable libraries and improve low-budget discovery;
230
+ - the global activity winner can be outside the seed, so AL must search beyond the seed;
231
+ - stability/ddG signals are best used as constraints, while activity improvement is measurement-bound;
232
+ - high-order activity epistasis makes zero-shot winner prediction unreliable.
233
+
234
+ Evidence does **not** support: "ShellDE predicts the best protein from sequence/structure alone." It
235
+ increases the probability and efficiency of finding a better variant per measured plate, conditional on
236
+ the design space and assay.
237
+
238
+ ## When NOT to use ShellDE
239
+
240
+ The focused active-site campaign helps only when the wins plausibly live in a region you can name
241
+ and measure combinatorially. Do NOT reach for it when:
242
+
243
+ - you do not know where beneficial mutations are (no mechanistic/structural prior on the target site);
244
+ - the wins are likely distal/distributed across the protein (use a broad whole-protein single-mutant
245
+ scan instead -- that is a different tool class; the ShellDE additive default cannot rank
246
+ whole-protein singles because one-hot has no cross-position transfer);
247
+ - you cannot measure a focused combinatorial library (no assay throughput at the chosen site).
248
+
249
+ Real-data caution (PtIspS, `artifacts/ispS_wetlab_validation.md`): a campaign run as a broad
250
+ single-mutant scan with the active site barely sampled and the global winner distal is exactly the
251
+ regime ShellDE is NOT for. Pick broad-vs-focused from your biology; ShellDE does not decide it.
252
+
253
+ More detail: `ARCHITECTURE.md`, `artifacts/DECISION_EVIDENCE.md`, and the Obsidian decision log named
254
+ there.
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "shellde"
7
+ version = "0.2.0"
8
+ description = "Low-N, acquisition-driven protein mutation campaign planner: additive default, opt-in signals, honest abstention"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{ name = "gyuminlee-repo" }]
12
+ keywords = [
13
+ "directed-evolution",
14
+ "protein-engineering",
15
+ "active-learning",
16
+ "bayesian-optimization",
17
+ "epistasis",
18
+ "mutation-recommendation",
19
+ "uncertainty-calibration",
20
+ "active-site",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Intended Audience :: Science/Research",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
30
+ "Operating System :: OS Independent",
31
+ ]
32
+ dependencies = [
33
+ "numpy",
34
+ "scipy",
35
+ "scikit-learn",
36
+ "pandas",
37
+ ]
38
+
39
+ [project.optional-dependencies]
40
+ plm = ["torch", "esm"] # ESM-C (EvolutionaryScale) + on-demand embeddings
41
+ plm2 = ["fair-esm"] # ESM-2 (Meta) alternative provider
42
+ dev = ["pytest", "pyright", "build", "twine"]
43
+
44
+ [project.scripts]
45
+ shellde = "shellde.cli:main"
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["src"]
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,36 @@
1
+ """ShellDE: low-N, acquisition-driven mutation recommendation for directed evolution.
2
+
3
+ The name states the scope. `shell` is the active-site contact shell that `resolve-site`
4
+ computes, and the second-shell ring that `--exclude-catalytic` designs over. `DE` is
5
+ directed evolution. Casing is fixed and does not vary: the distribution, the import name
6
+ and the CLI command are always lowercase `shellde`, while prose and citations use
7
+ `ShellDE`. Never `Shellde`, `shellDE` or `SHELLDE`.
8
+
9
+ The shipped default is calibrated-additive. On `recommend`, --auto-gate-pairwise and
10
+ --if-logprobs are CV-gated on your own held-out data (--no-auto-signals forces the latter
11
+ on), while --contacts-pdb opens the contact-restricted pairwise block unconditionally.
12
+ `campaign` and `round` offer neither pairwise flag and add --if-logprobs UNCONDITIONALLY,
13
+ with no gate. --plm and --naturalness are never gated on any command."""
14
+ from __future__ import annotations
15
+
16
+ from importlib.metadata import PackageNotFoundError
17
+ from importlib.metadata import version as _pkg_version
18
+
19
+ try:
20
+ __version__ = _pkg_version("shellde")
21
+ except PackageNotFoundError: # not installed (e.g. run from a source tree without install)
22
+ __version__ = "0.0.0+unknown"
23
+
24
+ from shellde.funclib import (
25
+ FuncLibLibrary,
26
+ combine_tolerances,
27
+ design_library,
28
+ tolerance_from_logprob_table,
29
+ )
30
+
31
+ __all__ = [
32
+ "FuncLibLibrary",
33
+ "combine_tolerances",
34
+ "design_library",
35
+ "tolerance_from_logprob_table",
36
+ ]