Files
obitools4/pkg/obiprimer/taxa.go
T
Eric Coissac 8cc02aa9c1 Refactor sequence processing for parallel loading and memory management
This commit introduces significant changes to the sequence processing pipeline, implementing parallel loading, k-mer budget control, explicit memory management, and refined logic for pair evaluation and resolution.
2026-11-03 22:53:54 +11:00

84 lines
2.5 KiB
Go

package obiprimer
import (
"fmt"
"slices"
"git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obitax"
)
// Taxa gives every corpus sequence its taxon at one rank (the species by
// default in ecoPrimers), as a dense index, and the number of distinct taxa
// among the examples and among the counterexamples.
//
// A sequence whose taxon has no ancestor at that rank (or no taxon at all) has
// the index -1; it is never counted as a taxon.
type Taxa struct {
Rank string
Of []int32 // Of[i] is the taxon index of Corpus.Seqs[i], or -1
ExampleTaxa int
CounterExamples int // distinct taxa among the counterexamples
nodes []*obitax.Taxon
}
// Name returns the taxon with the given index, for reporting.
func (t *Taxa) Name(i int32) *obitax.Taxon { return t.nodes[i] }
// checkRank reports an error if the taxonomy has no such rank.
func checkRank(taxonomy *obitax.Taxonomy, rank string) error {
if taxonomy == nil {
return fmt.Errorf("obiprimer: a taxonomy is required to compute taxon statistics")
}
if !slices.Contains(taxonomy.RankList(), rank) {
return fmt.Errorf("obiprimer: unknown taxonomic rank %q", rank)
}
return nil
}
// AssignTaxa determines the taxon of every sequence of the corpus at rank. The
// sequences must not be packed (see LoadPackedCorpus).
func (c *Corpus) AssignTaxa(taxonomy *obitax.Taxonomy, rank string) (*Taxa, error) {
if err := checkRank(taxonomy, rank); err != nil {
return nil, err
}
taxons := make([]*obitax.Taxon, len(c.Seqs))
for i, seq := range c.Seqs {
taxons[i] = seq.Taxon(taxonomy).TaxonAtRank(rank)
}
return buildTaxa(rank, taxons, c.Roles)
}
// buildTaxa gives the sequences, whose taxon at rank is taxons[i] (nil when
// there is none), their dense taxon index.
func buildTaxa(rank string, taxons []*obitax.Taxon, roles []Role) (*Taxa, error) {
t := &Taxa{Rank: rank, Of: make([]int32, len(taxons))}
index := map[*obitax.TaxNode]int32{}
exSeen := map[int32]struct{}{}
coSeen := map[int32]struct{}{}
for i, taxon := range taxons {
t.Of[i] = -1
if taxon == nil {
continue
}
id, ok := index[taxon.Node]
if !ok {
if len(t.nodes) >= maxTaxa {
return nil, fmt.Errorf("obiprimer: more than %d taxa at rank %q", maxTaxa, rank)
}
id = int32(len(t.nodes))
index[taxon.Node] = id
t.nodes = append(t.nodes, taxon)
}
t.Of[i] = id
switch roles[i] {
case Example:
exSeen[id] = struct{}{}
case CounterExample:
coSeen[id] = struct{}{}
}
}
t.ExampleTaxa, t.CounterExamples = len(exSeen), len(coSeen)
return t, nil
}