mirror of
https://github.com/metabarcoding/obitools4.git
synced 2026-03-25 13:30:52 +00:00
This commit introduces new functions for encoding and decoding k-mers, including support for normalized k-mers. It also updates the frequency filter and k-mer set implementations to use the new encoding functions, providing zero-allocation encoding for better performance. The commit hash has been updated to reflect the latest changes.
170 lines
4.9 KiB
Go
170 lines
4.9 KiB
Go
package obikmer
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq"
|
|
"github.com/RoaringBitmap/roaring/roaring64"
|
|
)
|
|
|
|
// KmerSet encapsule un ensemble de k-mers stockés dans un Roaring Bitmap
|
|
// Fournit des méthodes utilitaires pour manipuler des ensembles de k-mers
|
|
type KmerSet struct {
|
|
id string // Identifiant unique du KmerSet
|
|
k int // Taille des k-mers (immutable)
|
|
bitmap *roaring64.Bitmap // Bitmap contenant les k-mers
|
|
Metadata map[string]interface{} // Métadonnées utilisateur (clé=valeur atomique)
|
|
}
|
|
|
|
// NewKmerSet crée un nouveau KmerSet vide
|
|
func NewKmerSet(k int) *KmerSet {
|
|
return &KmerSet{
|
|
k: k,
|
|
bitmap: roaring64.New(),
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
}
|
|
|
|
// NewKmerSetFromBitmap crée un KmerSet à partir d'un bitmap existant
|
|
func NewKmerSetFromBitmap(k int, bitmap *roaring64.Bitmap) *KmerSet {
|
|
return &KmerSet{
|
|
k: k,
|
|
bitmap: bitmap,
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
}
|
|
|
|
// K retourne la taille des k-mers (immutable)
|
|
func (ks *KmerSet) K() int {
|
|
return ks.k
|
|
}
|
|
|
|
// AddKmerCode ajoute un k-mer encodé à l'ensemble
|
|
func (ks *KmerSet) AddKmerCode(kmer uint64) {
|
|
ks.bitmap.Add(kmer)
|
|
}
|
|
|
|
// AddNormalizedKmerCode ajoute un k-mer encodé normalisé à l'ensemble
|
|
func (ks *KmerSet) AddNormalizedKmerCode(kmer uint64) {
|
|
canonical := NormalizeKmer(kmer, ks.k)
|
|
ks.bitmap.Add(canonical)
|
|
}
|
|
|
|
// AddKmer ajoute un k-mer à l'ensemble en encodant la séquence
|
|
// La séquence doit avoir exactement k nucléotides
|
|
// Zero-allocation: encode directement sans créer de slice intermédiaire
|
|
func (ks *KmerSet) AddKmer(seq []byte) {
|
|
kmer := EncodeKmer(seq, ks.k)
|
|
ks.bitmap.Add(kmer)
|
|
}
|
|
|
|
// AddNormalizedKmer ajoute un k-mer normalisé à l'ensemble en encodant la séquence
|
|
// La séquence doit avoir exactement k nucléotides
|
|
// Zero-allocation: encode directement en forme canonique sans créer de slice intermédiaire
|
|
func (ks *KmerSet) AddNormalizedKmer(seq []byte) {
|
|
canonical := EncodeNormalizedKmer(seq, ks.k)
|
|
ks.bitmap.Add(canonical)
|
|
}
|
|
|
|
// AddSequence ajoute tous les k-mers d'une séquence à l'ensemble
|
|
// Utilise un itérateur pour éviter l'allocation d'un vecteur intermédiaire
|
|
func (ks *KmerSet) AddSequence(seq *obiseq.BioSequence) {
|
|
rawSeq := seq.Sequence()
|
|
for canonical := range IterNormalizedKmers(rawSeq, ks.k) {
|
|
ks.bitmap.Add(canonical)
|
|
}
|
|
}
|
|
|
|
// AddSequences ajoute tous les k-mers de plusieurs séquences en batch
|
|
func (ks *KmerSet) AddSequences(sequences *obiseq.BioSequenceSlice) {
|
|
for _, seq := range *sequences {
|
|
ks.AddSequence(seq)
|
|
}
|
|
}
|
|
|
|
// Contains vérifie si un k-mer est dans l'ensemble
|
|
func (ks *KmerSet) Contains(kmer uint64) bool {
|
|
return ks.bitmap.Contains(kmer)
|
|
}
|
|
|
|
// Len retourne le nombre de k-mers dans l'ensemble
|
|
func (ks *KmerSet) Len() uint64 {
|
|
return ks.bitmap.GetCardinality()
|
|
}
|
|
|
|
// MemoryUsage retourne l'utilisation mémoire en bytes
|
|
func (ks *KmerSet) MemoryUsage() uint64 {
|
|
return ks.bitmap.GetSizeInBytes()
|
|
}
|
|
|
|
// Clear vide l'ensemble
|
|
func (ks *KmerSet) Clear() {
|
|
ks.bitmap.Clear()
|
|
}
|
|
|
|
// Copy crée une copie de l'ensemble (cohérent avec BioSequence.Copy)
|
|
func (ks *KmerSet) Copy() *KmerSet {
|
|
// Copier les métadonnées
|
|
metadata := make(map[string]interface{}, len(ks.Metadata))
|
|
for k, v := range ks.Metadata {
|
|
metadata[k] = v
|
|
}
|
|
|
|
return &KmerSet{
|
|
id: ks.id,
|
|
k: ks.k,
|
|
bitmap: ks.bitmap.Clone(),
|
|
Metadata: metadata,
|
|
}
|
|
}
|
|
|
|
// Id retourne l'identifiant du KmerSet (cohérent avec BioSequence.Id)
|
|
func (ks *KmerSet) Id() string {
|
|
return ks.id
|
|
}
|
|
|
|
// SetId définit l'identifiant du KmerSet (cohérent avec BioSequence.SetId)
|
|
func (ks *KmerSet) SetId(id string) {
|
|
ks.id = id
|
|
}
|
|
|
|
// Union retourne l'union de cet ensemble avec un autre
|
|
func (ks *KmerSet) Union(other *KmerSet) *KmerSet {
|
|
if ks.k != other.k {
|
|
panic(fmt.Sprintf("Cannot union KmerSets with different k values: %d vs %d", ks.k, other.k))
|
|
}
|
|
result := ks.bitmap.Clone()
|
|
result.Or(other.bitmap)
|
|
return NewKmerSetFromBitmap(ks.k, result)
|
|
}
|
|
|
|
// Intersect retourne l'intersection de cet ensemble avec un autre
|
|
func (ks *KmerSet) Intersect(other *KmerSet) *KmerSet {
|
|
if ks.k != other.k {
|
|
panic(fmt.Sprintf("Cannot intersect KmerSets with different k values: %d vs %d", ks.k, other.k))
|
|
}
|
|
result := ks.bitmap.Clone()
|
|
result.And(other.bitmap)
|
|
return NewKmerSetFromBitmap(ks.k, result)
|
|
}
|
|
|
|
// Difference retourne la différence de cet ensemble avec un autre (this - other)
|
|
func (ks *KmerSet) Difference(other *KmerSet) *KmerSet {
|
|
if ks.k != other.k {
|
|
panic(fmt.Sprintf("Cannot subtract KmerSets with different k values: %d vs %d", ks.k, other.k))
|
|
}
|
|
result := ks.bitmap.Clone()
|
|
result.AndNot(other.bitmap)
|
|
return NewKmerSetFromBitmap(ks.k, result)
|
|
}
|
|
|
|
// Iterator retourne un itérateur sur tous les k-mers de l'ensemble
|
|
func (ks *KmerSet) Iterator() roaring64.IntIterable64 {
|
|
return ks.bitmap.Iterator()
|
|
}
|
|
|
|
// Bitmap retourne le bitmap sous-jacent (pour compatibilité)
|
|
func (ks *KmerSet) Bitmap() *roaring64.Bitmap {
|
|
return ks.bitmap
|
|
}
|