mirpy-lib 3.2.0__tar.gz → 3.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.
Potentially problematic release.
This version of mirpy-lib might be problematic. Click here for more details.
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/CHANGELOG.md +32 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/PKG-INFO +1 -1
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/SOURCES.md +23 -1
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/__init__.py +1 -1
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/density.py +71 -2
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/pyproject.toml +1 -1
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/.gitignore +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/BENCHMARKS.md +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/CLAUDE.md +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/LICENSE +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/README.md +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/REPERTOIRE_EMBEDDING.md +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/REPERTOIRE_LESSONS.md +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/SQRT_D_MIGRATION.md +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/THEORY.md +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/assets/mirpy_dark.png +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/assets/mirpy_dark.svg +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/assets/mirpy_light.png +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/assets/mirpy_light.svg +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/Makefile +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/api.rst +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/conf.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/index.rst +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/requirements.txt +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/usage.rst +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/environment.yml +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/aliases.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/alleles.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/bench/__init__.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/bench/metrics.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/bench/theory.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/bench/vdjdb.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/distances/__init__.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/distances/germline.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/distances/junction.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/__init__.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/pca.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/presets.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/prototypes.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/tcremp.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/explain.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/__init__.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/bundle.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/codec.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/decoder.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/encoder.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/set_encoder.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/tokenize.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/train.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/py.typed +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/repertoire.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/__init__.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/README.md +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/__init__.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/build_gene_library.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/build_region_annotations.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/imgt_gene_library.txt +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/olga_gene_library.txt +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/region_annotations.txt +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/build_germline_dist.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_IGH.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_IGK.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_IGL.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_TRA.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_TRB.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_TRD.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_TRG.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_IGH.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_IGK.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_IGL.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_TRA.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_TRB.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_TRD.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_TRG.npz +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/.gitkeep +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/generate_prototypes.py +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_IGH.tsv +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_IGK.tsv +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_IGL.tsv +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_TRA.tsv +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_TRB.tsv +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_TRD.tsv +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_TRG.tsv +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/manifest.json +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/mouse_TRA.tsv +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/mouse_TRB.tsv +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/requirements.txt +0 -0
- {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/setup.sh +0 -0
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
All notable changes to `mirpy-lib` (import `mir`). This project follows semantic versioning; the v3 line is a
|
|
4
4
|
greenfield ML/embedding rewrite (the classical v1.x/v2 toolkit is frozen on branch `legacy-v2`).
|
|
5
5
|
|
|
6
|
+
## 3.3.0 — 2026-07-17
|
|
7
|
+
|
|
8
|
+
Minor: one new public parameter, nothing removed or changed.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`fit_density_space(chunk_size=)`** — embed and project in batches so the full raw matrix is never
|
|
13
|
+
resident. Peak memory becomes `max(pca_fit_cap, chunk_size) × n_features` instead of
|
|
14
|
+
`len(df) × n_features`: measured **10.60 GB → 1.81 GB** at 450k pooled clonotypes, and flat in `N`
|
|
15
|
+
(vs linear), at no wall-clock cost. This is what makes whole-cohort density arms runnable on a
|
|
16
|
+
laptop — the 4.2M-clonotype pooled arm is ~51 GB raw and ~102 GB once `scaler.transform` upcasts to
|
|
17
|
+
float64. Chunking is bit-exact at the embedding level (`_embed` of a slice == the slice of
|
|
18
|
+
`_embed`); the projected coordinates agree to float noise (~1e-7 relative), since BLAS summation
|
|
19
|
+
order depends on batch shape.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `fit_density_space`'s `pca_fit_cap` docstring claimed it "lets whole repertoires be embedded without
|
|
24
|
+
a full-matrix PCA". It caps the **fit**, not the memory — both raw matrices were already
|
|
25
|
+
materialized before the PCA was fitted. Documented, and `chunk_size=` is the actual remedy.
|
|
26
|
+
- **`mir.__version__` was stale** — it read `3.1.1` on the published 3.2.0, because the release bump
|
|
27
|
+
moved `pyproject.toml` but not `mir/__init__.py`, and `publish.yml` only validates *pyproject ==
|
|
28
|
+
tag*. Both now read 3.3.0. (`__version__` is still hand-maintained; deriving it from
|
|
29
|
+
`importlib.metadata` would retire this failure mode for good.)
|
|
30
|
+
- `tests/assets/olga_humanTRB_1000.txt.gz` was a slice of the alphabetically sorted VDJdb TRB dump,
|
|
31
|
+
not OLGA output as its name and `SOURCES.md` claimed — so it was **not** an antigen-naive null (12%
|
|
32
|
+
of rows had a Hamming-1 neighbour vs 0.2% for real OLGA). Regenerated from `olga-generate_sequences`;
|
|
33
|
+
provenance and a byte-reproducible regenerate command recorded in `SOURCES.md`. No test was
|
|
34
|
+
invalidated (they use it only as a generic TRB junction pool), but external calibrations that treated
|
|
35
|
+
it as a synthetic negative control were comparing VDJdb against itself. Not shipped (tests are
|
|
36
|
+
excluded from the sdist); listed here because it invalidates results, not code.
|
|
37
|
+
|
|
6
38
|
## 3.2.0 — 2026-07-17
|
|
7
39
|
|
|
8
40
|
Minor: one new public module, nothing removed or changed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mirpy-lib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: ML-oriented embeddings for immune receptor repertoires (TCR/BCR)
|
|
5
5
|
Project-URL: Homepage, https://github.com/antigenomics/mirpy
|
|
6
6
|
Project-URL: Repository, https://github.com/antigenomics/mirpy
|
|
@@ -17,7 +17,29 @@ Every dataset used by mirpy v3, its origin, and how to regenerate it.
|
|
|
17
17
|
|---|---|---|---|
|
|
18
18
|
| VDJdb slim dump | `tests/assets/vdjdb.slim.txt.gz` | VDJdb (Goncharov et al., *Nat Methods* 2022) export, dotted-column slim format | Antigen labels for the Table S1 benchmark; a specific release — F1 differs from the paper's 2023 summer release |
|
|
19
19
|
| Epitope-specific TRB sets | `tests/assets/{gilgfvftl,llwngpmav}_*.gz` | VDJdb subsets | Single-antigen fixtures |
|
|
20
|
-
| OLGA TRB sample | `tests/assets/olga_humanTRB_1000.txt.gz` |
|
|
20
|
+
| OLGA TRB sample | `tests/assets/olga_humanTRB_1000.txt.gz` | **Computed**, not experimental: 1 000 human TRB rearrangements drawn from the OLGA generative model (Sethna et al., *Bioinformatics* 2019), seed 42. Cols (no header): `junction_nt, junction_aa, v_call, j_call`; kept in **generation order — do not sort** (see note below) | `conda run -n mirpy --no-capture-output olga-generate_sequences --humanTRB -n 1000 -o /tmp/olga.tsv --seed 42` then `awk -F'\t' -v OFS='\t' '{print $1,$2,$3"*01",$4"*01"}' /tmp/olga.tsv \| gzip -n > tests/assets/olga_humanTRB_1000.txt.gz` (the `*01` suffix is appended because OLGA emits gene-level calls but `mir` expects allele-level, matching the bundled prototypes) |
|
|
21
|
+
|
|
22
|
+
> **Provenance correction (2026-07-17).** Until this date `olga_humanTRB_1000.txt.gz` was **not**
|
|
23
|
+
> OLGA output despite its name and this row's claim. It was a `head`-slice of the *alphabetically
|
|
24
|
+
> sorted* VDJdb TRB dump: all 969 unique junctions matched `vdjdb.slim.txt.gz` TRB `cdr3` exactly
|
|
25
|
+
> (100%, vs **0.96%** for genuine OLGA), the `junction_nt` column was a placeholder run of `N`, and
|
|
26
|
+
> the first row (`ATSIRFTDTQYF`) lacked the Cys anchor OLGA always emits. Consequences of the old
|
|
27
|
+
> file, for anyone re-reading results predating this fix:
|
|
28
|
+
> 1. **Not an antigen-naive null.** 12% of its rows had a Hamming-1 neighbour within the file vs
|
|
29
|
+
> **0.2%** for real OLGA (166 vs 4 pairs per 1 000) — VDJdb is *selected for* the antigen-driven
|
|
30
|
+
> convergence such a background is supposed to lack. Any chance-rate / null calibration against it
|
|
31
|
+
> compared VDJdb to itself and is meaningless.
|
|
32
|
+
> 2. **Sorted ⇒ slices are not exchangeable.** Because it was alphabetically sorted, the
|
|
33
|
+
> `_load_olga(n)` / `_load_olga(n, offset=400)` obs-vs-background slice pattern in
|
|
34
|
+
> `tests/test_density.py` drew two systematically different regions of sequence space (the
|
|
35
|
+
> background block was 58% `CAIS`-prefixed and shared only 20 of 54 V genes with obs). Genuine
|
|
36
|
+
> OLGA output is in generation order, which is why the regenerate command above must **not** sort.
|
|
37
|
+
>
|
|
38
|
+
> The `tests/` suite was **not** invalidated: all 58 tests pass identically on both files (they use
|
|
39
|
+
> the asset only as a generic pool of TRB junctions, never as a null), and the Hamming-1 gate in
|
|
40
|
+
> `test_continuous_matches_discrete_hamming1` scores rho 0.727 on the old file vs 0.809 on real OLGA.
|
|
41
|
+
> The damage was confined to external calibrations that treated the file as a synthetic negative
|
|
42
|
+
> control.
|
|
21
43
|
|
|
22
44
|
Bundled model / Pgen data used at runtime lives in **vdjtools** (`vdjtools.model` bundled
|
|
23
45
|
parquet marginals for 7 loci × {olga, learned}) — see the vdjtools `SOURCES.md`.
|
|
@@ -99,6 +99,24 @@ def _embed(model, df: pl.DataFrame, space: str) -> np.ndarray:
|
|
|
99
99
|
return _slice(model.embed(df), space).astype(np.float32, copy=False)
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
def _embed_transform_chunked(model, df: pl.DataFrame, space: str, scaler, pca,
|
|
103
|
+
chunk_size: int) -> np.ndarray:
|
|
104
|
+
"""Embed *df* and project it into ``scaler``+``pca``, ``chunk_size`` rows at a time.
|
|
105
|
+
|
|
106
|
+
Only ``chunk_size × n_features`` of raw embedding is ever resident: each chunk is embedded,
|
|
107
|
+
standardized, projected down to ``n_components``, and dropped. Peak memory is set by the chunk,
|
|
108
|
+
not by ``len(df)`` — which is what makes whole-cohort clouds tractable (a 4.2M-clonotype arm is
|
|
109
|
+
~51 GB raw, and ~102 GB again once ``scaler.transform`` upcasts it to float64).
|
|
110
|
+
"""
|
|
111
|
+
out = np.empty((df.height, pca.n_components_), dtype=np.float64)
|
|
112
|
+
for start in range(0, df.height, chunk_size):
|
|
113
|
+
stop = min(start + chunk_size, df.height)
|
|
114
|
+
raw = _embed(model, df.slice(start, stop - start), space)
|
|
115
|
+
out[start:stop] = pca.transform(scaler.transform(raw))
|
|
116
|
+
del raw
|
|
117
|
+
return out
|
|
118
|
+
|
|
119
|
+
|
|
102
120
|
@dataclass
|
|
103
121
|
class DensitySpace:
|
|
104
122
|
"""A single fitted TCREMP → PCA coordinate system for density comparison.
|
|
@@ -158,6 +176,7 @@ def fit_density_space(
|
|
|
158
176
|
space: str = "full",
|
|
159
177
|
seed: int = 0,
|
|
160
178
|
pca_fit_cap: int | None = None,
|
|
179
|
+
chunk_size: int | None = None,
|
|
161
180
|
) -> tuple[DensitySpace, np.ndarray, np.ndarray]:
|
|
162
181
|
"""Embed observed + background frames into one shared PCA coordinate system.
|
|
163
182
|
|
|
@@ -176,13 +195,27 @@ def fit_density_space(
|
|
|
176
195
|
third of the memory at whole-repertoire scale).
|
|
177
196
|
seed: RNG / PCA solver seed.
|
|
178
197
|
pca_fit_cap: Fit the ``StandardScaler`` + PCA on at most this many randomly-sampled
|
|
179
|
-
pooled rows (then transform *all* rows).
|
|
180
|
-
|
|
198
|
+
pooled rows (then transform *all* rows). ``None`` fits on everything. NB on its own
|
|
199
|
+
this caps the **fit**, not the memory: without ``chunk_size`` the full raw matrix for
|
|
200
|
+
both frames is materialized before the PCA is fitted at all.
|
|
201
|
+
chunk_size: Embed and project in batches of this many rows, so the full raw matrix is
|
|
202
|
+
never resident (peak ≈ ``chunk_size × n_features`` instead of ``len(df) × n_features``).
|
|
203
|
+
Required for whole-cohort clouds: 4.2M clonotypes × 1000 prototypes is ~51 GB raw and
|
|
204
|
+
~102 GB after ``scaler.transform`` upcasts to float64, versus ~2.4 GB at
|
|
205
|
+
``chunk_size=200_000``. When set and ``pca_fit_cap`` is ``None``, the fit is capped at
|
|
206
|
+
200k pooled rows — fitting on everything would defeat the purpose. ``None`` (default)
|
|
207
|
+
keeps the original single-shot path exactly.
|
|
181
208
|
|
|
182
209
|
Returns:
|
|
183
210
|
``(density_space, obs_emb, bg_emb)`` — the fitted :class:`DensitySpace` and the
|
|
184
211
|
two reduced ``float`` arrays, row-aligned to ``obs_df`` / ``bg_df``.
|
|
185
212
|
"""
|
|
213
|
+
if chunk_size is not None:
|
|
214
|
+
return _fit_density_space_chunked(
|
|
215
|
+
model, obs_df, bg_df, n_components=n_components, space=space, seed=seed,
|
|
216
|
+
pca_fit_cap=pca_fit_cap if pca_fit_cap is not None else 200_000,
|
|
217
|
+
chunk_size=chunk_size,
|
|
218
|
+
)
|
|
186
219
|
obs = _embed(model, obs_df, space)
|
|
187
220
|
bg = _embed(model, bg_df, space)
|
|
188
221
|
n_total, n_feat = obs.shape[0] + bg.shape[0], obs.shape[1]
|
|
@@ -203,6 +236,42 @@ def fit_density_space(
|
|
|
203
236
|
return ds, pca.transform(scaler.transform(obs)), pca.transform(scaler.transform(bg))
|
|
204
237
|
|
|
205
238
|
|
|
239
|
+
def _fit_density_space_chunked(
|
|
240
|
+
model, obs_df: pl.DataFrame, bg_df: pl.DataFrame, *, n_components: int, space: str,
|
|
241
|
+
seed: int, pca_fit_cap: int, chunk_size: int,
|
|
242
|
+
) -> tuple[DensitySpace, np.ndarray, np.ndarray]:
|
|
243
|
+
"""``fit_density_space`` without ever holding a full raw matrix.
|
|
244
|
+
|
|
245
|
+
Same coordinate system as the single-shot path — one scaler + one PCA fitted on a pooled
|
|
246
|
+
sample, applied to both frames — but the fit sample is drawn as *frame rows* and embedded on
|
|
247
|
+
its own, and each frame is then embedded/projected in chunks. Memory is set by
|
|
248
|
+
``max(pca_fit_cap, chunk_size)``, not by the cohort.
|
|
249
|
+
"""
|
|
250
|
+
rng = np.random.default_rng(seed)
|
|
251
|
+
n_o, n_b = obs_df.height, bg_df.height
|
|
252
|
+
n_total = n_o + n_b
|
|
253
|
+
|
|
254
|
+
if n_total > pca_fit_cap:
|
|
255
|
+
take_o = min(n_o, pca_fit_cap * n_o // n_total)
|
|
256
|
+
take_b = min(n_b, pca_fit_cap - take_o)
|
|
257
|
+
fit_rows = np.vstack([
|
|
258
|
+
_embed(model, obs_df[rng.choice(n_o, take_o, replace=False)], space),
|
|
259
|
+
_embed(model, bg_df[rng.choice(n_b, take_b, replace=False)], space),
|
|
260
|
+
])
|
|
261
|
+
else:
|
|
262
|
+
fit_rows = np.vstack([_embed(model, obs_df, space), _embed(model, bg_df, space)])
|
|
263
|
+
|
|
264
|
+
scaler = StandardScaler().fit(fit_rows)
|
|
265
|
+
k = min(n_components, fit_rows.shape[0], fit_rows.shape[1])
|
|
266
|
+
pca = PCA(n_components=k, random_state=seed).fit(scaler.transform(fit_rows))
|
|
267
|
+
del fit_rows
|
|
268
|
+
|
|
269
|
+
ds = DensitySpace(model=model, space=space, scaler=scaler, pca=pca)
|
|
270
|
+
return (ds,
|
|
271
|
+
_embed_transform_chunked(model, obs_df, space, scaler, pca, chunk_size),
|
|
272
|
+
_embed_transform_chunked(model, bg_df, space, scaler, pca, chunk_size))
|
|
273
|
+
|
|
274
|
+
|
|
206
275
|
def _mutate1(seq: str, rng) -> str:
|
|
207
276
|
"""Apply one interior amino-acid substitution to a *different* residue."""
|
|
208
277
|
if len(seq) <= 2:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|