refine k-mer index architecture documentation and remove obsolete spec
Introduces raw mapping and iteration APIs that bypass membership checks, clarifies variant-specific storage layouts and auto-detection logic, and documents optimized batch access patterns with caller-provided buffers. Removes the outdated obicompactvector_reflexion.md specification to consolidate architectural details into current implementation docs.
This commit is contained in:
@@ -292,14 +292,18 @@ Pass 1 — byte max, SIMD-vectorizable, O(n)
|
|||||||
|
|
||||||
## Matrix types
|
## Matrix types
|
||||||
|
|
||||||
Four matrix types, two encodings × two formats:
|
Both matrix types are enums behind a transparent API — the caller never matches on the variant. `PersistentCompactIntMatrix` has two variants (`Columnar`, `Packed`). `PersistentBitMatrix` has four:
|
||||||
|
|
||||||
| | Columnar format | Packed format |
|
| Variant | Storage | When |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Bit** | `PersistentBitMatrix` (Columnar variant) | `PersistentBitMatrix` (Packed variant) |
|
| `Columnar` | one `.pbiv`/`.pciv` file per column + `meta.json` | build-time default (`*Builder::new`) |
|
||||||
| **Int** | `PersistentCompactIntMatrix` (Columnar variant) | `PersistentCompactIntMatrix` (Packed variant) |
|
| `Packed` | single `matrix.pbmx` mmap file | query-optimised, produced by `pack_bit_matrix`/`pack_compact_int_matrix` |
|
||||||
|
| `Sparse` (bit only) | `sparse_meta.json` + PFIV/Elias-Fano component files, row-major | `pack --sparse`; see [siblings.md](../architecture/siblings.md) for the sparse-vs-dense access-pattern trade-off |
|
||||||
|
| `Implicit` (bit only) | no file at all | mono-genome presence layers — `n_cols` is always reported as `1`, every value is `true` |
|
||||||
|
|
||||||
Both matrix types are enums (`Columnar` / `Packed` / `Implicit` for bit) behind a transparent API. `col_view(c)` returns the appropriate view directly:
|
`PersistentBitMatrix::open(layer_dir)` auto-detects the variant, in order: `matrix.pbmx` → Packed, `presence/meta.json` → Columnar, `presence/sparse_meta.json` → Sparse, `layer_meta.json` (no presence dir at all) → Implicit. `col_view`/`col`/`sub_matrix` panic on `Sparse`/`Implicit` where the operation has no direct-slice equivalent (Sparse is k-mer-major, not column-major; Implicit has no backing storage) — callers needing per-column data on those variants go through `row`/`fill_row`.
|
||||||
|
|
||||||
|
`col_view(c)` returns the appropriate view directly:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// PersistentBitMatrix
|
// PersistentBitMatrix
|
||||||
|
|||||||
@@ -250,6 +250,59 @@ Mode 3 (`PersistentBitMatrix`) has no `push_layer` on `LayeredMap`; callers buil
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Layer\<D\> — raw mapping, iteration, and batch access
|
||||||
|
|
||||||
|
Beyond `query`/`find` (membership-checked), `Layer<D>` exposes lower-level access used by consumers that already know a kmer is in the layer (e.g. cross-partition sibling resolution) or that need to sweep every kmer/slot without paying for a membership check each time.
|
||||||
|
|
||||||
|
### Raw kmer → slot mapping
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn index(&self, kmer: CanonicalKmer) -> usize
|
||||||
|
pub fn index_batch(&self, kmers: &[CanonicalKmer]) -> Vec<usize>
|
||||||
|
```
|
||||||
|
|
||||||
|
Pure MPHF mapping, no evidence/fingerprint check — equivalent to `MphfOnly::index`. Only meaningful when the caller already knows `kmer` belongs to the layer; on an absent kmer the MPHF still returns *some* slot (undefined, not `None`).
|
||||||
|
|
||||||
|
### Kmer iteration
|
||||||
|
|
||||||
|
Four iterators, all built from `unitigs.bin` (physical layout order, **not** correlated with MPHF slot numbers):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn iter_kmers(&self) -> KmerIter<'_>
|
||||||
|
pub fn enumerate_kmers(&self) -> Enumerate<KmerIter<'_>> // (order_index, kmer)
|
||||||
|
pub fn iter_kmers_batch(&self, n: usize) -> KmerBatchIter<'_> // Vec<CanonicalKmer> of size ≤ n
|
||||||
|
pub fn enumerate_kmers_batch(&self, n: usize) -> impl Iterator<Item = (usize, Vec<CanonicalKmer>)> + Send + 'static
|
||||||
|
```
|
||||||
|
|
||||||
|
`KmerIter`/`KmerBatchIter` own a clone of the underlying `Arc<UnitigFileReader>` rather than borrowing `self` — `Send + 'static`, streamed from disk one kmer at a time, never materialised as a whole. Multiple instances can coexist concurrently, each with its own cursor. `enumerate_kmers_batch`'s index is the batch's starting offset in iteration order (a multiple of `n` except for the final, possibly shorter, batch).
|
||||||
|
|
||||||
|
### Batch lookup on payload vectors/views
|
||||||
|
|
||||||
|
`PersistentCompactIntVec`, `PersistentBitVec`, `IntSliceView`, `BitSliceView` all expose:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn get_batch(&self, slots: &[usize]) -> Vec<T>
|
||||||
|
fn fill_batch(&self, slots: &[usize], out: &mut [T])
|
||||||
|
```
|
||||||
|
|
||||||
|
Both sort `slots` internally for sequential mmap access, then reorder results back to the caller's original order. `fill_batch` fills a caller-provided buffer, avoiding the `Vec` allocation.
|
||||||
|
|
||||||
|
### sub_matrix / fill_sub_matrix
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Layer<PersistentCompactIntMatrix>
|
||||||
|
pub fn sub_matrix(&self, slots: &[usize]) -> Vec<Vec<u32>> // column-first
|
||||||
|
pub fn fill_sub_matrix(&self, slots: &[usize], out: &mut [Vec<u32>])
|
||||||
|
|
||||||
|
// Layer<PersistentBitMatrix> (and any D: BinaryMatrix, e.g. PersistentSparseBitMatrix)
|
||||||
|
pub fn sub_matrix(&self, slots: &[usize]) -> Vec<Vec<bool>>
|
||||||
|
pub fn fill_sub_matrix(&self, slots: &[usize], out: &mut [Vec<bool>])
|
||||||
|
```
|
||||||
|
|
||||||
|
Column-first to match the on-disk column-major layout. `fill_sub_matrix` sorts `slots` once, then calls each column's `fill_batch` in turn — no redundant per-column sort. On `PersistentSparseBitMatrix` (k-mer-major, no column method) this degrades to a row-by-row decode; see [siblings.md](../architecture/siblings.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## LayeredStore\<S\> and aggregation traits
|
## LayeredStore\<S\> and aggregation traits
|
||||||
|
|
||||||
`LayeredStore<S>` is a generic aggregation wrapper over `Vec<S>`. It propagates three traits from `obicompactvec::traits` up the hierarchy via blanket impls:
|
`LayeredStore<S>` is a generic aggregation wrapper over `Vec<S>`. It propagates three traits from `obicompactvec::traits` up the hierarchy via blanket impls:
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
# La crate obicompactvector
|
|
||||||
|
|
||||||
Le code actuelle est ce qu'il est. Ce n'est pad la vrérité absolue, c'est un premier effort d'implémentation rien de plus. Ci-dessous je vais décrire les objectif et la structure qui devrait être. LA VERITE A ATTEINDRE.
|
|
||||||
|
|
||||||
La crate fournie des représentations les plus compact possible en mémoire de matrice de comptage ou de présence de k-mer dans des génomes. Chaque colonne représente un génome chaque ligne un kmer. une matrice est une collection de vecteur ou chacun des vecteur est un colonne de la matrice.
|
|
||||||
|
|
||||||
Les matrices comme les colonnes ont vocation à être persistante. Les données sont stockées dans des fichiers binaires. Les données sont mappées en mémoire via `mmap`
|
|
||||||
|
|
||||||
Les structure sont par essence immutables. Il existe des représentations mutables des colonnes qui permettent leur construction. À la fin de leur construction, les colonnes sont fermée ce qui les rends immutable.
|
|
||||||
|
|
||||||
Les matrices peuvent êtres représenté de deux façons:
|
|
||||||
- via un répertoire contenant une collection de fichier colonnes
|
|
||||||
- via un fichier matrix qui est la concatenation de plusieurs fichiers colonnes.
|
|
||||||
|
|
||||||
|
|
||||||
## Les matrices de comptage
|
|
||||||
|
|
||||||
Ce sont des matrice d'entiers positif la plus part du temps de petites valeurs (inferieurs à 255). On assume que toutes les valeurs sont représentables sur un `u32`
|
|
||||||
|
|
||||||
## Les matrices de presence
|
|
||||||
|
|
||||||
Ce sont des matrices de boolean représenté comme des champs de bits
|
|
||||||
|
|
||||||
Il existe une forme implicite des vecteur de présence, qui n'est représenté par aucun fichier pour lequel toutes les valeurs sont vraies
|
|
||||||
|
|
||||||
## représentation légère des colonnes
|
|
||||||
|
|
||||||
Les colonnes qu'elles soient de unitiaire (fichier colonne) ou partie d'un fichier composite matrice peuvent être représenté par un objet léger donnant acces à ces valeurs ainsi qu'à la longeur du vecteurs. Toutes les méthodes de calcules doivent uniquement travailler à partir de ces représentations légère unifiées des colonnes.
|
|
||||||
|
|
||||||
### Représentation légère d'un vecteur de présence
|
|
||||||
|
|
||||||
Le vecteur est représenté par
|
|
||||||
- un champs de bits encodé comme un [u64]
|
|
||||||
- un usize encodant la longeur du champs de bits
|
|
||||||
|
|
||||||
### Représentation légère d'un vecteur de présence
|
|
||||||
|
|
||||||
Le vecteur est représenté par
|
|
||||||
- un vecteur [u8] encodant directement les valeur faibe du vecteur [0,255[
|
|
||||||
La valeur 255 est une valeur sentinelle indiquant que la valeure vraie est >=255
|
|
||||||
et se trouvent dans une structure d'overflow
|
|
||||||
- un iterateur de (usize,u32) listant les valeurs d'overflow coorespondant aux valeurs
|
|
||||||
sentinels (255) du [u8]
|
|
||||||
- un usize encodant la longeur du champs de bits
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
**Nous avons un vrai problème architectural dans la partie phylo, basée sur les sibling.**
|
|
||||||
|
|
||||||
La structure de l'index est en quatre parties, à l'intérieur d'un layer:
|
|
||||||
|
|
||||||
- Un fichier de superkmers qui permet d'itinérer sur les kmers gérées par le layer.
|
|
||||||
- Un MPFH qui permet de convertir un kmer en un numéro de slot compact.
|
|
||||||
- Un tableau d'évidence, qui peut être exact ou probabiliste. Ce qui permet de vérifier lorsque l'on interroge le MPFH avec un kmer, si ce kmer est contenu dans le layer.
|
|
||||||
- Il est donc une **erreur conceptuelle grave** d'utiliser cette évidence pour retrouver un kmer à partir de son numéro de slot. Toute tentative en ce sens est vouée à l’échec sur une évidence non exacte.
|
|
||||||
- Un tableau de présence ou de comptage par génome. Ranger colonne first : une colonne correspond à un génome, et indexé par numéro de slot
|
|
||||||
|
|
||||||
## Concernant le MPFH.
|
|
||||||
|
|
||||||
- Le rôle premier du MPFH est de convertir un kmer en un numéro de slot.
|
|
||||||
- On peut, en rôle secondaire, en combinant le MPFH et le tableau d'évidence, s'en servir pour mettre en place un test d'appartenance au layer pour un kmer.
|
|
||||||
|
|
||||||
L'API du `Layer<D>` expose également des méthodes de mapping brut kmer → slot via le MPHF, sans vérification d'appartenance :
|
|
||||||
|
|
||||||
- `index(&self, kmer: CanonicalKmer) -> usize` — retourne le numéro de slot pour un kmer. C'est un mapping pur, équivalent à `MphfOnly::index`.
|
|
||||||
- `index_batch(&self, kmers: &[CanonicalKmer]) -> Vec<usize>` — retourne un vecteur de slots pour un slice de kmers.
|
|
||||||
|
|
||||||
Ces méthodes sont distinctes de `query()` et `find()` qui incluent la vérification d'appartenance.
|
|
||||||
|
|
||||||
Lorsque l'on itère sur le fichier de superkmer, l'ordre des kmer est déterministe, mais non corrélé avec les numéros de slot correspondants. Il existe donc un numéro d'ordre dans les kmer contenues dans le fichier de superkmer.
|
|
||||||
|
|
||||||
L'API du `Layer<D>` expose maintenant quatre itérateurs sur les kmers du layer :
|
|
||||||
|
|
||||||
- `iter_kmers(&self) -> KmerIter<'_>` — itérateur nommé sur `CanonicalKmer`, construit à partir de `unitigs.bin`. Plusieurs instances peuvent coexister concurremment tant que le layer vit.
|
|
||||||
- `enumerate_kmers(&self) -> Enumerate<KmerIter<'_>>` — variante indexée retournant `(usize, CanonicalKmer)`, où l'index 0 correspond au premier kmer du fichier de superkmers. Construit par composition sur `iter_kmers()` sans duplication de code d'itération.
|
|
||||||
- `iter_kmers_batch(&self, n: usize) -> KmerBatchIter<'_>` — itérateur par batch retournant `Vec<CanonicalKmer>` de taille `n`. Le dernier batch peut être tronqué.
|
|
||||||
- `enumerate_kmers_batch(&self, n: usize) -> Enumerate<KmerBatchIter<'_>>` — variante indexée retournant `(usize, Vec<CanonicalKmer>)`, où l'index correspond au premier kmer du batch. Construit par composition sur `iter_kmers_batch()`.
|
|
||||||
|
|
||||||
L'itération est implémentée via des structs `KmerIter` et `KmerBatchIter` qui encapsulent l'itérateur interne de `UnitigFileReader::iter_indexed_canonical_kmers()`. Les structs sont publics et peuvent être stockés, transmis ou combinés avec d'autres adaptateurs d'itérateur.
|
|
||||||
|
|
||||||
## Performance — itérateurs de batch
|
|
||||||
|
|
||||||
Les itérateurs `iter_kmers_batch` et `enumerate_kmers_batch` allouent un nouveau `Vec` à chaque batch. Pour des tailles de batch importantes ou des chemins critiques, cela peut générer une pression malloc significative.
|
|
||||||
|
|
||||||
**À concevoir** : un système de pool de vecteurs avec réallocation automatique dès qu'un buffer n'est plus référencé, pour réutiliser les allocations entre batches et réduire le nombre d'appels système. Ce pool pourrait être intégré à `KmerBatchIter` ou proposé comme un adaptateur d'itérateur générique.
|
|
||||||
|
|
||||||
## Accès aux matrices de présence/comptage
|
|
||||||
|
|
||||||
Les types de vecteurs persistants exposent maintenant des méthodes de lookup batch optimisées pour l'accès séquentiel au mmap :
|
|
||||||
|
|
||||||
- `PersistentCompactIntVec::get_batch(slots) -> Vec<u32>` et `fill_batch(slots, out)`
|
|
||||||
- `PersistentBitVec::get_batch(slots) -> Vec<bool>` et `fill_batch(slots, out)`
|
|
||||||
- `IntSliceView::get_batch(slots) -> Vec<u32>` et `fill_batch(slots, out)`
|
|
||||||
- `BitSliceView::get_batch(slots) -> Vec<bool>` et `fill_batch(slots, out)`
|
|
||||||
|
|
||||||
Toutes ces méthodes trient les slots en interne pour un accès mmap séquentiel, puis réordonnent les résultats selon l'ordre d'origine. `fill_batch` évite l'allocation en remplissant un buffer fourni par le caller.
|
|
||||||
|
|
||||||
Les matrices persistantes exposent `sub_matrix(slots) -> Vec<Vec<T>>` et `fill_sub_matrix(slots, out)` :
|
|
||||||
|
|
||||||
- `PersistentCompactIntMatrix::sub_matrix(slots)` — retourne `Vec<Vec<u32>>` column-first
|
|
||||||
- `PersistentCompactIntMatrix::fill_sub_matrix(slots, out: &mut [Vec<u32>])` — remplit des buffers fournis, réutilise les allocations existantes
|
|
||||||
- `PersistentBitMatrix::sub_matrix(slots)` — retourne `Vec<Vec<bool>>` column-first
|
|
||||||
- `PersistentBitMatrix::fill_sub_matrix(slots, out: &mut [Vec<bool>])` — remplit des buffers fournis, réutilise les allocations existantes
|
|
||||||
|
|
||||||
Le scan est colonne-first pour respecter la layout mémoire. `fill_sub_matrix` trie les slots une seule fois, puis appelle `fill_batch_sorted` sur chaque colonne pour éviter le tri redondant. Ces méthodes sont également disponibles sur `Layer<PersistentCompactIntMatrix>` et `Layer<PersistentBitMatrix>`.
|
|
||||||
Reference in New Issue
Block a user