mirror of
https://github.com/metabarcoding/obitools4.git
synced 2026-03-25 13:30:52 +00:00
Refactor FrequencyFilter to use KmerSetGroup
Refactor FrequencyFilter to inherit from KmerSetGroup for better code organization and maintainability. This change replaces the direct bitmap management with a group-based approach, simplifying the implementation and improving readability.
This commit is contained in:
@@ -4,34 +4,21 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq"
|
"git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq"
|
||||||
"github.com/RoaringBitmap/roaring/roaring64"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FrequencyFilter filtre les k-mers par fréquence minimale
|
// FrequencyFilter filtre les k-mers par fréquence minimale
|
||||||
// Utilise v bitmaps où index[i] contient les k-mers vus au moins i+1 fois
|
// Spécialisation de KmerSetGroup où index[i] contient les k-mers vus au moins i+1 fois
|
||||||
type FrequencyFilter struct {
|
type FrequencyFilter struct {
|
||||||
K int
|
*KmerSetGroup // Groupe de KmerSet (un par niveau de fréquence)
|
||||||
MinFreq int // v - fréquence minimale requise
|
MinFreq int // v - fréquence minimale requise
|
||||||
index []*roaring64.Bitmap // index[i] = k-mers vus ≥(i+1) fois
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFrequencyFilter crée un nouveau filtre par fréquence
|
// NewFrequencyFilter crée un nouveau filtre par fréquence
|
||||||
// minFreq: nombre minimum d'occurrences requises (v)
|
// minFreq: nombre minimum d'occurrences requises (v)
|
||||||
func NewFrequencyFilter(k, minFreq int) *FrequencyFilter {
|
func NewFrequencyFilter(k, minFreq int) *FrequencyFilter {
|
||||||
if minFreq < 1 {
|
|
||||||
panic("minFreq must be >= 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Créer v bitmaps
|
|
||||||
bitmaps := make([]*roaring64.Bitmap, minFreq)
|
|
||||||
for i := range bitmaps {
|
|
||||||
bitmaps[i] = roaring64.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &FrequencyFilter{
|
return &FrequencyFilter{
|
||||||
K: k,
|
KmerSetGroup: NewKmerSetGroup(k, minFreq),
|
||||||
MinFreq: minFreq,
|
MinFreq: minFreq,
|
||||||
index: bitmaps,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,30 +35,30 @@ func (ff *FrequencyFilter) AddSequence(seq *obiseq.BioSequence) {
|
|||||||
func (ff *FrequencyFilter) addKmer(kmer uint64) {
|
func (ff *FrequencyFilter) addKmer(kmer uint64) {
|
||||||
// Trouver le niveau actuel du k-mer
|
// Trouver le niveau actuel du k-mer
|
||||||
c := 0
|
c := 0
|
||||||
for c < ff.MinFreq && ff.index[c].Contains(kmer) {
|
for c < ff.MinFreq && ff.Get(c).Contains(kmer) {
|
||||||
c++
|
c++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ajouter au niveau suivant (si pas encore au maximum)
|
// Ajouter au niveau suivant (si pas encore au maximum)
|
||||||
if c < ff.MinFreq {
|
if c < ff.MinFreq {
|
||||||
ff.index[c].Add(kmer)
|
ff.Get(c).Add(kmer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFilteredSet retourne un KmerSet des k-mers avec fréquence ≥ minFreq
|
// GetFilteredSet retourne un KmerSet des k-mers avec fréquence ≥ minFreq
|
||||||
func (ff *FrequencyFilter) GetFilteredSet() *KmerSet {
|
func (ff *FrequencyFilter) GetFilteredSet() *KmerSet {
|
||||||
// Les k-mers filtrés sont dans le dernier niveau
|
// Les k-mers filtrés sont dans le dernier niveau
|
||||||
return NewKmerSetFromBitmap(ff.K, ff.index[ff.MinFreq-1].Clone())
|
return ff.Get(ff.MinFreq - 1).Clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKmersAtLevel retourne un KmerSet des k-mers vus au moins (level+1) fois
|
// GetKmersAtLevel retourne un KmerSet des k-mers vus au moins (level+1) fois
|
||||||
// level doit être dans [0, minFreq-1]
|
// level doit être dans [0, minFreq-1]
|
||||||
func (ff *FrequencyFilter) GetKmersAtLevel(level int) *KmerSet {
|
func (ff *FrequencyFilter) GetKmersAtLevel(level int) *KmerSet {
|
||||||
if level < 0 || level >= ff.MinFreq {
|
ks := ff.Get(level)
|
||||||
|
if ks == nil {
|
||||||
return NewKmerSet(ff.K)
|
return NewKmerSet(ff.K)
|
||||||
}
|
}
|
||||||
|
return ks.Clone()
|
||||||
return NewKmerSetFromBitmap(ff.K, ff.index[level].Clone())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats retourne des statistiques sur les niveaux de fréquence
|
// Stats retourne des statistiques sur les niveaux de fréquence
|
||||||
@@ -82,8 +69,9 @@ func (ff *FrequencyFilter) Stats() FrequencyFilterStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < ff.MinFreq; i++ {
|
for i := 0; i < ff.MinFreq; i++ {
|
||||||
card := ff.index[i].GetCardinality()
|
ks := ff.Get(i)
|
||||||
sizeBytes := ff.index[i].GetSizeInBytes()
|
card := ks.Len()
|
||||||
|
sizeBytes := ks.MemoryUsage()
|
||||||
|
|
||||||
stats.Levels[i] = LevelStats{
|
stats.Levels[i] = LevelStats{
|
||||||
Level: i + 1, // Niveau 1 = freq ≥ 1
|
Level: i + 1, // Niveau 1 = freq ≥ 1
|
||||||
@@ -134,10 +122,9 @@ Level breakdown:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear libère la mémoire de tous les niveaux
|
// Clear libère la mémoire de tous les niveaux
|
||||||
|
// (héritée de KmerSetGroup mais redéfinie pour clarté)
|
||||||
func (ff *FrequencyFilter) Clear() {
|
func (ff *FrequencyFilter) Clear() {
|
||||||
for _, bitmap := range ff.index {
|
ff.KmerSetGroup.Clear()
|
||||||
bitmap.Clear()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
// ==================================
|
||||||
@@ -175,7 +162,7 @@ func (ff *FrequencyFilter) Load(path string) error {
|
|||||||
// Contains vérifie si un k-mer a atteint la fréquence minimale
|
// Contains vérifie si un k-mer a atteint la fréquence minimale
|
||||||
func (ff *FrequencyFilter) Contains(kmer uint64) bool {
|
func (ff *FrequencyFilter) Contains(kmer uint64) bool {
|
||||||
canonical := NormalizeKmer(kmer, ff.K)
|
canonical := NormalizeKmer(kmer, ff.K)
|
||||||
return ff.index[ff.MinFreq-1].Contains(canonical)
|
return ff.Get(ff.MinFreq - 1).Contains(canonical)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFrequency retourne la fréquence approximative d'un k-mer
|
// GetFrequency retourne la fréquence approximative d'un k-mer
|
||||||
@@ -185,7 +172,7 @@ func (ff *FrequencyFilter) GetFrequency(kmer uint64) int {
|
|||||||
|
|
||||||
freq := 0
|
freq := 0
|
||||||
for i := 0; i < ff.MinFreq; i++ {
|
for i := 0; i < ff.MinFreq; i++ {
|
||||||
if ff.index[i].Contains(canonical) {
|
if ff.Get(i).Contains(canonical) {
|
||||||
freq = i + 1
|
freq = i + 1
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -199,27 +186,15 @@ func (ff *FrequencyFilter) GetFrequency(kmer uint64) int {
|
|||||||
// Sans argument: retourne le nombre de k-mers avec freq ≥ minFreq (dernier niveau)
|
// Sans argument: retourne le nombre de k-mers avec freq ≥ minFreq (dernier niveau)
|
||||||
// Avec argument level: retourne le nombre de k-mers avec freq ≥ (level+1)
|
// Avec argument level: retourne le nombre de k-mers avec freq ≥ (level+1)
|
||||||
// Exemple: Len() pour les k-mers filtrés, Len(2) pour freq ≥ 3
|
// Exemple: Len() pour les k-mers filtrés, Len(2) pour freq ≥ 3
|
||||||
|
// (héritée de KmerSetGroup mais redéfinie pour la documentation)
|
||||||
func (ff *FrequencyFilter) Len(level ...int) uint64 {
|
func (ff *FrequencyFilter) Len(level ...int) uint64 {
|
||||||
if len(level) == 0 {
|
return ff.KmerSetGroup.Len(level...)
|
||||||
// Sans argument: dernier niveau (k-mers filtrés)
|
|
||||||
return ff.index[ff.MinFreq-1].GetCardinality()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avec argument: niveau spécifique
|
|
||||||
lvl := level[0]
|
|
||||||
if lvl < 0 || lvl >= ff.MinFreq {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return ff.index[lvl].GetCardinality()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryUsage retourne l'utilisation mémoire en bytes
|
// MemoryUsage retourne l'utilisation mémoire en bytes
|
||||||
|
// (héritée de KmerSetGroup mais redéfinie pour clarté)
|
||||||
func (ff *FrequencyFilter) MemoryUsage() uint64 {
|
func (ff *FrequencyFilter) MemoryUsage() uint64 {
|
||||||
total := uint64(0)
|
return ff.KmerSetGroup.MemoryUsage()
|
||||||
for _, bitmap := range ff.index {
|
|
||||||
total += bitmap.GetSizeInBytes()
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
// ==================================
|
||||||
@@ -228,7 +203,7 @@ func (ff *FrequencyFilter) MemoryUsage() uint64 {
|
|||||||
|
|
||||||
// CompareWithSimpleMap compare la mémoire avec une simple map
|
// CompareWithSimpleMap compare la mémoire avec une simple map
|
||||||
func (ff *FrequencyFilter) CompareWithSimpleMap() string {
|
func (ff *FrequencyFilter) CompareWithSimpleMap() string {
|
||||||
totalKmers := ff.index[0].GetCardinality()
|
totalKmers := ff.Get(0).Len()
|
||||||
|
|
||||||
simpleMapBytes := totalKmers * 24 // ~24 bytes par entrée
|
simpleMapBytes := totalKmers * 24 // ~24 bytes par entrée
|
||||||
roaringBytes := ff.MemoryUsage()
|
roaringBytes := ff.MemoryUsage()
|
||||||
|
|||||||
195
pkg/obikmer/kmer_set_group.go
Normal file
195
pkg/obikmer/kmer_set_group.go
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
package obikmer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KmerSetGroup représente un vecteur de KmerSet
|
||||||
|
// Utilisé pour gérer plusieurs ensembles de k-mers (par exemple, par niveau de fréquence)
|
||||||
|
type KmerSetGroup struct {
|
||||||
|
K int // Taille des k-mers
|
||||||
|
sets []*KmerSet // Vecteur de KmerSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKmerSetGroup crée un nouveau groupe de n KmerSets
|
||||||
|
func NewKmerSetGroup(k int, n int) *KmerSetGroup {
|
||||||
|
if n < 1 {
|
||||||
|
panic("KmerSetGroup size must be >= 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
sets := make([]*KmerSet, n)
|
||||||
|
for i := range sets {
|
||||||
|
sets[i] = NewKmerSet(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &KmerSetGroup{
|
||||||
|
K: k,
|
||||||
|
sets: sets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size retourne le nombre de KmerSet dans le groupe
|
||||||
|
func (ksg *KmerSetGroup) Size() int {
|
||||||
|
return len(ksg.sets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retourne le KmerSet à l'index donné
|
||||||
|
// Retourne nil si l'index est invalide
|
||||||
|
func (ksg *KmerSetGroup) Get(index int) *KmerSet {
|
||||||
|
if index < 0 || index >= len(ksg.sets) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ksg.sets[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set remplace le KmerSet à l'index donné
|
||||||
|
// Panique si l'index est invalide ou si le k ne correspond pas
|
||||||
|
func (ksg *KmerSetGroup) Set(index int, ks *KmerSet) {
|
||||||
|
if index < 0 || index >= len(ksg.sets) {
|
||||||
|
panic(fmt.Sprintf("Index out of bounds: %d (size: %d)", index, len(ksg.sets)))
|
||||||
|
}
|
||||||
|
if ks.K != ksg.K {
|
||||||
|
panic(fmt.Sprintf("KmerSet k mismatch: expected %d, got %d", ksg.K, ks.K))
|
||||||
|
}
|
||||||
|
ksg.sets[index] = ks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len retourne le nombre de k-mers dans un KmerSet spécifique
|
||||||
|
// Sans argument: retourne le nombre de k-mers dans le dernier KmerSet
|
||||||
|
// Avec argument index: retourne le nombre de k-mers dans le KmerSet à cet index
|
||||||
|
func (ksg *KmerSetGroup) Len(index ...int) uint64 {
|
||||||
|
if len(index) == 0 {
|
||||||
|
// Sans argument: dernier KmerSet
|
||||||
|
return ksg.sets[len(ksg.sets)-1].Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avec argument: KmerSet spécifique
|
||||||
|
idx := index[0]
|
||||||
|
if idx < 0 || idx >= len(ksg.sets) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return ksg.sets[idx].Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryUsage retourne l'utilisation mémoire totale en bytes
|
||||||
|
func (ksg *KmerSetGroup) MemoryUsage() uint64 {
|
||||||
|
total := uint64(0)
|
||||||
|
for _, ks := range ksg.sets {
|
||||||
|
total += ks.MemoryUsage()
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear vide tous les KmerSet du groupe
|
||||||
|
func (ksg *KmerSetGroup) Clear() {
|
||||||
|
for _, ks := range ksg.sets {
|
||||||
|
ks.Clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone crée une copie complète du groupe
|
||||||
|
func (ksg *KmerSetGroup) Clone() *KmerSetGroup {
|
||||||
|
clonedSets := make([]*KmerSet, len(ksg.sets))
|
||||||
|
for i, ks := range ksg.sets {
|
||||||
|
clonedSets[i] = ks.Clone()
|
||||||
|
}
|
||||||
|
return &KmerSetGroup{
|
||||||
|
K: ksg.K,
|
||||||
|
sets: clonedSets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSequence ajoute tous les k-mers d'une séquence à un KmerSet spécifique
|
||||||
|
func (ksg *KmerSetGroup) AddSequence(seq *obiseq.BioSequence, index int) {
|
||||||
|
if index < 0 || index >= len(ksg.sets) {
|
||||||
|
panic(fmt.Sprintf("Index out of bounds: %d (size: %d)", index, len(ksg.sets)))
|
||||||
|
}
|
||||||
|
ksg.sets[index].AddSequence(seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSequences ajoute tous les k-mers de plusieurs séquences à un KmerSet spécifique
|
||||||
|
func (ksg *KmerSetGroup) AddSequences(sequences *obiseq.BioSequenceSlice, index int) {
|
||||||
|
if index < 0 || index >= len(ksg.sets) {
|
||||||
|
panic(fmt.Sprintf("Index out of bounds: %d (size: %d)", index, len(ksg.sets)))
|
||||||
|
}
|
||||||
|
ksg.sets[index].AddSequences(sequences)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union retourne l'union de tous les KmerSet du groupe
|
||||||
|
func (ksg *KmerSetGroup) Union() *KmerSet {
|
||||||
|
if len(ksg.sets) == 0 {
|
||||||
|
return NewKmerSet(ksg.K)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ksg.sets[0].Clone()
|
||||||
|
for i := 1; i < len(ksg.sets); i++ {
|
||||||
|
result = result.Union(ksg.sets[i])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersect retourne l'intersection de tous les KmerSet du groupe
|
||||||
|
func (ksg *KmerSetGroup) Intersect() *KmerSet {
|
||||||
|
if len(ksg.sets) == 0 {
|
||||||
|
return NewKmerSet(ksg.K)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ksg.sets[0].Clone()
|
||||||
|
for i := 1; i < len(ksg.sets); i++ {
|
||||||
|
result = result.Intersect(ksg.sets[i])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats retourne des statistiques pour chaque KmerSet du groupe
|
||||||
|
type KmerSetGroupStats struct {
|
||||||
|
K int
|
||||||
|
Size int // Nombre de KmerSet
|
||||||
|
TotalBytes uint64 // Mémoire totale utilisée
|
||||||
|
Sets []KmerSetStats // Stats de chaque KmerSet
|
||||||
|
}
|
||||||
|
|
||||||
|
type KmerSetStats struct {
|
||||||
|
Index int // Index du KmerSet dans le groupe
|
||||||
|
Len uint64 // Nombre de k-mers
|
||||||
|
SizeBytes uint64 // Taille en bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ksg *KmerSetGroup) Stats() KmerSetGroupStats {
|
||||||
|
stats := KmerSetGroupStats{
|
||||||
|
K: ksg.K,
|
||||||
|
Size: len(ksg.sets),
|
||||||
|
Sets: make([]KmerSetStats, len(ksg.sets)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ks := range ksg.sets {
|
||||||
|
sizeBytes := ks.MemoryUsage()
|
||||||
|
stats.Sets[i] = KmerSetStats{
|
||||||
|
Index: i,
|
||||||
|
Len: ks.Len(),
|
||||||
|
SizeBytes: sizeBytes,
|
||||||
|
}
|
||||||
|
stats.TotalBytes += sizeBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ksgs KmerSetGroupStats) String() string {
|
||||||
|
result := fmt.Sprintf(`KmerSetGroup Statistics (k=%d, size=%d):
|
||||||
|
Total memory: %.2f MB
|
||||||
|
|
||||||
|
Set breakdown:
|
||||||
|
`, ksgs.K, ksgs.Size, float64(ksgs.TotalBytes)/1024/1024)
|
||||||
|
|
||||||
|
for _, set := range ksgs.Sets {
|
||||||
|
result += fmt.Sprintf(" Set[%d]: %d k-mers (%.2f MB)\n",
|
||||||
|
set.Index,
|
||||||
|
set.Len,
|
||||||
|
float64(set.SizeBytes)/1024/1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user