Introduce sequence roles and filtering logic

Implement new data structures for corpus management, multi-match filtering, and barcode resolution.
This commit is contained in:
Eric Coissac committed 2026-11-03 22:50:02 +11:00
1 parent d7d63f315c
commit d6dd39f878
22 files changed
+1083 -342

No files matched your search

+35 -20
View File
@@ -64,10 +64,14 @@ func tutorialParams(t *testing.T) SearchParams {
// ecoPrimers -U on the Cyprinidae tutorial case (-e 3 -3 2 -l 30 -L 150 -c -r 7953 -U)
// keeps 1366 pairs. testdata/ecoprimers_Cyprinidae_U.pairs.tsv holds, for each,
// the primers, the amplified example and counterexample sequences, the amplified
// example and counterexample taxa, and the identified taxa.
// example and counterexample taxa, and the identified taxa. The identified taxa
// are not compared (obiprimers defines them differently, see
// TestPairsAgainstEcoPrimers) and the specificity threshold is off, so that
// the pairs ecoPrimers dropped with its own Bs do not hide any of its pairs.
func TestNoMultiMatchAgainstEcoPrimers(t *testing.T) {
corpus, taxa, _ := loadTutorial(t)
p := tutorialParams(t)
p.MinSpecificity = 0
p.NoMultiMatch = true
pairs, info, err := Search(corpus, taxa, p)
if err != nil {
@@ -91,23 +95,28 @@ func TestNoMultiMatchAgainstEcoPrimers(t *testing.T) {
want[p1+"/"+p2] = v
}
if len(pairs) != len(want) {
t.Errorf("%d pairs, ecoPrimers -U reports %d", len(pairs), len(want))
}
bad := 0
ours := map[string]PairStats{}
for _, g := range pairs {
v, ok := want[g.Primer1+"/"+g.Primer2]
ours[g.Primer1+"/"+g.Primer2] = g
}
bad, missing := 0, 0
for key, v := range want {
g, ok := ours[key]
if !ok {
t.Errorf("pair %s/%s is not in the ecoPrimers -U result", g.Primer1, g.Primer2)
if missing++; missing <= 3 {
t.Errorf("ecoPrimers -U pair %s is not found", key)
}
continue
}
got := [5]int{g.Examples, g.CounterExamples, g.ExampleTaxa, g.CounterTaxa, g.Identified}
if got != v {
got := [4]int{g.Examples, g.CounterExamples, g.ExampleTaxa, g.CounterTaxa}
if got != [4]int{v[0], v[1], v[2], v[3]} || g.Identified > v[4] {
if bad++; bad <= 3 {
t.Errorf("pair %s/%s: got %v, ecoPrimers %v", g.Primer1, g.Primer2, got, v)
t.Errorf("pair %s: got %v (identified %d), ecoPrimers %v", key, got, g.Identified, v)
}
}
}
t.Logf("%d pairs of ecoPrimers -U checked, %d missing, %d different; obiprimers has %d pairs (%d more, dropped by ecoPrimers' own Bs)",
len(want), missing, bad, len(pairs), len(pairs)-len(want)+missing)
}
var locRE = regexp.MustCompile(`(\d+)\.\.(\d+)`)
@@ -195,7 +204,9 @@ func TestCounterExamplesAgainstEcoPrimers(t *testing.T) {
if c := corpus.Counts(); c.Examples != 325 || c.CounterExamples != 7497 || taxa.CounterExamples != 7387 {
t.Fatalf("counts %+v, %d counterexample species (ecoPrimers: 325, 7497, 7387)", c, taxa.CounterExamples)
}
pairs, info, err := Search(corpus, taxa, tutorialParams(t))
pc := tutorialParams(t)
pc.MinSpecificity = 0 // ecoPrimers' own Bs filter is not reproduced, see TestPairsAgainstEcoPrimers
pairs, info, err := Search(corpus, taxa, pc)
if err != nil {
t.Fatal(err)
}
@@ -222,22 +233,26 @@ func TestCounterExamplesAgainstEcoPrimers(t *testing.T) {
want[p1+"/"+p2] = r
}
if len(pairs) != len(want) {
t.Errorf("%d pairs, ecoPrimers reports %d", len(pairs), len(want))
}
bad := 0
ours := map[string]PairStats{}
for _, g := range pairs {
w, ok := want[g.Primer1+"/"+g.Primer2]
ours[g.Primer1+"/"+g.Primer2] = g
}
bad, missing := 0, 0
for key, w := range want {
g, ok := ours[key]
if !ok {
t.Errorf("pair %s/%s is not in the ecoPrimers result", g.Primer1, g.Primer2)
if missing++; missing <= 3 {
t.Errorf("ecoPrimers pair %s is not found", key)
}
continue
}
good := string("bG"[b2i(g.Good1)]) + string("bG"[b2i(g.Good2)])
got := [5]int{g.Examples, g.CounterExamples, g.ExampleTaxa, g.CounterTaxa, g.Identified}
if got != w.v || good != w.good {
got := [4]int{g.Examples, g.CounterExamples, g.ExampleTaxa, g.CounterTaxa}
if got != [4]int{w.v[0], w.v[1], w.v[2], w.v[3]} || good != w.good || g.Identified > w.v[4] {
if bad++; bad <= 3 {
t.Errorf("pair %s/%s: got %s %v, ecoPrimers %s %v", g.Primer1, g.Primer2, good, got, w.good, w.v)
t.Errorf("pair %s: got %s %v (identified %d), ecoPrimers %s %v", key, good, got, g.Identified, w.good, w.v)
}
}
}
t.Logf("%d pairs of ecoPrimers checked, %d missing, %d different; obiprimers has %d pairs", len(want), missing, bad, len(pairs))
}