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.

Files changed (88) hide show
  1. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/CHANGELOG.md +32 -0
  2. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/PKG-INFO +1 -1
  3. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/SOURCES.md +23 -1
  4. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/__init__.py +1 -1
  5. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/density.py +71 -2
  6. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/pyproject.toml +1 -1
  7. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/.gitignore +0 -0
  8. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/BENCHMARKS.md +0 -0
  9. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/CLAUDE.md +0 -0
  10. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/LICENSE +0 -0
  11. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/README.md +0 -0
  12. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/REPERTOIRE_EMBEDDING.md +0 -0
  13. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/REPERTOIRE_LESSONS.md +0 -0
  14. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/SQRT_D_MIGRATION.md +0 -0
  15. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/THEORY.md +0 -0
  16. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/assets/mirpy_dark.png +0 -0
  17. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/assets/mirpy_dark.svg +0 -0
  18. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/assets/mirpy_light.png +0 -0
  19. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/assets/mirpy_light.svg +0 -0
  20. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/Makefile +0 -0
  21. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/api.rst +0 -0
  22. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/conf.py +0 -0
  23. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/index.rst +0 -0
  24. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/requirements.txt +0 -0
  25. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/docs/usage.rst +0 -0
  26. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/environment.yml +0 -0
  27. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/aliases.py +0 -0
  28. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/alleles.py +0 -0
  29. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/bench/__init__.py +0 -0
  30. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/bench/metrics.py +0 -0
  31. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/bench/theory.py +0 -0
  32. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/bench/vdjdb.py +0 -0
  33. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/distances/__init__.py +0 -0
  34. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/distances/germline.py +0 -0
  35. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/distances/junction.py +0 -0
  36. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/__init__.py +0 -0
  37. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/pca.py +0 -0
  38. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/presets.py +0 -0
  39. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/prototypes.py +0 -0
  40. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/embedding/tcremp.py +0 -0
  41. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/explain.py +0 -0
  42. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/__init__.py +0 -0
  43. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/bundle.py +0 -0
  44. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/codec.py +0 -0
  45. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/decoder.py +0 -0
  46. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/encoder.py +0 -0
  47. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/set_encoder.py +0 -0
  48. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/tokenize.py +0 -0
  49. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/ml/train.py +0 -0
  50. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/py.typed +0 -0
  51. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/repertoire.py +0 -0
  52. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/__init__.py +0 -0
  53. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/README.md +0 -0
  54. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/__init__.py +0 -0
  55. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/build_gene_library.py +0 -0
  56. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/build_region_annotations.py +0 -0
  57. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/imgt_gene_library.txt +0 -0
  58. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/olga_gene_library.txt +0 -0
  59. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/gene_library/region_annotations.txt +0 -0
  60. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/build_germline_dist.py +0 -0
  61. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_IGH.npz +0 -0
  62. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_IGK.npz +0 -0
  63. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_IGL.npz +0 -0
  64. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_TRA.npz +0 -0
  65. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_TRB.npz +0 -0
  66. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_TRD.npz +0 -0
  67. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/human_TRG.npz +0 -0
  68. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_IGH.npz +0 -0
  69. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_IGK.npz +0 -0
  70. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_IGL.npz +0 -0
  71. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_TRA.npz +0 -0
  72. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_TRB.npz +0 -0
  73. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_TRD.npz +0 -0
  74. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/germline_dist/mouse_TRG.npz +0 -0
  75. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/.gitkeep +0 -0
  76. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/generate_prototypes.py +0 -0
  77. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_IGH.tsv +0 -0
  78. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_IGK.tsv +0 -0
  79. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_IGL.tsv +0 -0
  80. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_TRA.tsv +0 -0
  81. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_TRB.tsv +0 -0
  82. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_TRD.tsv +0 -0
  83. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/human_TRG.tsv +0 -0
  84. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/manifest.json +0 -0
  85. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/mouse_TRA.tsv +0 -0
  86. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/mir/resources/prototypes/mouse_TRB.tsv +0 -0
  87. {mirpy_lib-3.2.0 → mirpy_lib-3.3.0}/requirements.txt +0 -0
  88. {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.2.0
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` | OLGA-generated human TRB | Synthetic repertoire sample |
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`.
@@ -15,7 +15,7 @@ from __future__ import annotations
15
15
 
16
16
  import os
17
17
 
18
- __version__ = "3.1.1"
18
+ __version__ = "3.3.0"
19
19
 
20
20
  __all__ = ["__version__", "get_resource_path", "TCREmp", "PairedTCREmp"]
21
21
 
@@ -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). Lets whole repertoires be embedded
180
- without a full-matrix PCA; ``None`` fits on everything.
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:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mirpy-lib"
7
- version = "3.2.0"
7
+ version = "3.3.0"
8
8
  authors = [
9
9
  { name="ISALGO lab", email="mikhail.shugay@gmail.com"}
10
10
  ]
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