Files
obitools4/pkg/obiprimer/taxa.go
T
Eric Coissac 02c8ded683 Implement primer pair finding and evaluation logic
This change introduces the core functionality for identifying, validating, and statistically evaluating primer pairs within the corpus. It includes new data structures for primer parameters, amplicon statistics, and sequence handling, along with methods for assigning taxa to sequences. Comprehensive tests are added to ensure the accuracy of the pair evaluation against expected outputs.
2026-11-03 22:37:03 +11:00

70 lines
1.9 KiB
Go

package obiprimer
import (
"fmt"
"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] }
// AssignTaxa determines the taxon of every sequence of the corpus at rank.
func (c *Corpus) AssignTaxa(taxonomy *obitax.Taxonomy, rank string) (*Taxa, error) {
if taxonomy == nil {
return nil, fmt.Errorf("obiprimer: a taxonomy is required to compute taxon statistics")
}
ranks := taxonomy.RankList()
known := false
for _, r := range ranks {
if r == rank {
known = true
break
}
}
if !known {
return nil, fmt.Errorf("obiprimer: unknown taxonomic rank %q", rank)
}
t := &Taxa{Rank: rank, Of: make([]int32, len(c.Seqs))}
index := map[*obitax.TaxNode]int32{}
exSeen := map[int32]struct{}{}
coSeen := map[int32]struct{}{}
for i, seq := range c.Seqs {
t.Of[i] = -1
taxon := seq.Taxon(taxonomy).TaxonAtRank(rank)
if taxon == nil {
continue
}
id, ok := index[taxon.Node]
if !ok {
id = int32(len(t.nodes))
index[taxon.Node] = id
t.nodes = append(t.nodes, taxon)
}
t.Of[i] = id
if c.Roles[i] == Example {
exSeen[id] = struct{}{}
} else {
coSeen[id] = struct{}{}
}
}
t.ExampleTaxa, t.CounterExamples = len(exSeen), len(coSeen)
return t, nil
}