From ad065f68532432a9f104adaefac42194ce4b8355 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Tue, 3 Nov 2026 14:41:30 +1100 Subject: [PATCH] Introduce new sequence matching logic and corpus handling This commit introduces the core matching functionality, including parallel corpus loading, the `Corpus` data structure, and implementations for `matchNaive` and `matchApat`. It also defines new matching rules, variant handling, and comprehensive unit and integration tests to validate the new logic and parameter handling. --- OBIPRIMERS_NOTES.md | 51 + pkg/obiprimer/corpus.go | 108 ++ pkg/obiprimer/match.go | 156 +++ pkg/obiprimer/match_apat.go | 87 ++ pkg/obiprimer/match_naive.go | 78 ++ pkg/obiprimer/match_test.go | 285 ++++ .../testdata/ecoprimers_Cyprinidae.words.tsv | 1208 +++++++++++++++++ .../testdata/ecoprimers_Teleostei.words.tsv | 255 ++++ pkg/obiprimer/words.go | 72 +- pkg/obiprimer/words_test.go | 24 +- 10 files changed, 2265 insertions(+), 59 deletions(-) create mode 100644 pkg/obiprimer/corpus.go create mode 100644 pkg/obiprimer/match.go create mode 100644 pkg/obiprimer/match_apat.go create mode 100644 pkg/obiprimer/match_naive.go create mode 100644 pkg/obiprimer/match_test.go create mode 100644 pkg/obiprimer/testdata/ecoprimers_Cyprinidae.words.tsv create mode 100644 pkg/obiprimer/testdata/ecoprimers_Teleostei.words.tsv diff --git a/OBIPRIMERS_NOTES.md b/OBIPRIMERS_NOTES.md index 77e1862..e335cc3 100644 --- a/OBIPRIMERS_NOTES.md +++ b/OBIPRIMERS_NOTES.md @@ -250,3 +250,54 @@ suffisait. **Corrigé** (accord de l'utilisateur) : `globalLockerCounter` est un Suite : étape 3 (recherche approchée des mots stricts, <= e substitutions, deux brins, dans exemples et contre-exemples), puis paires et indices. + +## 11. Mode A, étape 3 : recherche approchée (2026-11-03) + +Code : `pkg/obiprimer/corpus.go` (`Corpus`, `LoadCorpus` : exemples + contre-exemples en mémoire, ordre +d'entrée conservé, ignorés comptés seulement, comme ecoPrimers), `match.go` (types `Match`, +`WordMatches`, `MatchParams`, harnais parallèle `matchCorpus` indépendant du nombre de workers), +`match_naive.go` (oracle en Go pur), `match_apat.go` (variante `obiapat`). `FindStrictWords` travaille +maintenant sur un `Corpus`. + +**Règle d'appariement** (celle d'ecoPrimers, figée dans `MatchParams`) : substitutions seules ; toute base +hors A, C, G, T (U lu comme T) dans la séquence compte comme une erreur ; toutes les fenêtres à <= e +erreurs sont rapportées, chevauchantes comprises ; fenêtres à cheval sur la jonction si circulaire. +**`obiapat` ne suit pas cette règle de lui-même** : il lit les codes IUPAC dans la séquence (un N +correspond à toutes les bases). La variante `obiapat` remplace donc les symboles ambigus par `z` (rien +ne lui correspond), sur une copie, seulement pour les séquences qui en contiennent. + +Vérification de justesse : +- oracle validé à la main sur 12 cas (brins, N, IUPAC, U, jonction, chevauchement, séquence trop courte) ; +- `obiapat` == oracle position par position et nombre d'erreurs compris, 6 configurations (k = 12, 18, + 25 ; e = 0 à 4 ; linéaire/circulaire ; séquences avec n, r, y, k, m ; mots plantés sur les deux brins + avec 0 à 3 substitutions et à la jonction) ; +- **bout en bout** : pour chaque mot strict, nombre de séquences exemples touchées == celui que + ecoPrimers a journalisé (`e = 3`) : **1 207 mots Cyprinidae et 255 mots Teleostei, 0 différence**. + Les nombres d'ecoPrimers sont dans `testdata/ecoprimers_*.words.tsv` ; test opt-in + `TestMatchApatAgainstEcoPrimers` (`OBIPRIMERS_TUTORIAL`, `OBIPRIMERS_WORKERS=n` pour fixer les cœurs). + (Cyprinidae : 1 207 des 1 208 mots d'ecoPrimers, le mot en trop de la section 10 est exclu.) + +Mesures, `e = 3`, génomes circulaires, M3 Max : + +| Jeu | Mots x séquences | Matches | 16 workers | 1 worker | Mémoire ajoutée | +|---|---|---|---|---|---| +| Cyprinidae | 1 207 x 325 (5,4 Mb) | 416 386 | 3,8 s | non mesuré | 53 Mo | +| Teleostei | 255 x 3 909 (65 Mb) | 999 407 | 10,7 s | **113 s** (293 Mb/s par passe mot-brin) | 151 Mo | + +ecoPrimers complet (non décomposé par étape) : 134 s et 350 s, 1,9 et 2,1 Go. + +Profil monothread (Teleostei, `go tool pprof`) : 74 % dans `runtime.cgocall` (le balayage C), 11 % en +surcoût de transition cgo (`pthread_cond_signal`), tri/GC marginaux. Il y a 2 millions d'appels cgo +(255 mots x 2 brins x 3 909 séquences). + +Variantes du protocole : +1. `obiapat` mot par mot, parallélisé par séquence : **retenue comme référence de production** pour l'instant. +2. Arrêt anticipé sous le quorum de 90 % : **écartée, mesuré inutile** (Teleostei : 255/255 mots + conservés ; Cyprinidae : 1 201/1 208). Le parallélisme par séquence est déjà dans la variante 1. +3. Bit-parallèle multi-mots en Go (SIMD) : **non écrite**. Cible : les ~74 % de balayage C. +4. Index de voisinage / Aho-Corasick : écartée pour e >= 2 par le calcul (graines de 4 à 5 bases, + ~250 000 candidats par graine et par mot sur 65 Mb) ; `obicorazick` reste pertinent pour e = 0. + Non mesurée. + +Décision en suspens : écrire la variante 3 maintenant, ou d'abord finir le pipeline (paires et indices) +et profiler l'ensemble pour savoir si l'appariement domine encore. diff --git a/pkg/obiprimer/corpus.go b/pkg/obiprimer/corpus.go new file mode 100644 index 0000000..0796dd2 --- /dev/null +++ b/pkg/obiprimer/corpus.go @@ -0,0 +1,108 @@ +package obiprimer + +import ( + "cmp" + "fmt" + "slices" + "sync" + + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obidefault" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiiter" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq" +) + +// Counts gives the number of sequences by role. +type Counts struct { + Examples int + CounterExamples int + Ignored int +} + +// Corpus is the set of sequences used by the primer search: the examples and +// the counterexamples, in input order. Sequences with the Ignored role are not +// kept (as in ecoPrimers), only counted. +// +// The primer search needs several passes over the sequences (words are +// searched approximately in every one of them), so the corpus lives in memory. +type Corpus struct { + Seqs []*obiseq.BioSequence + Roles []Role // Roles[i] is Example or CounterExample + Ignored int // number of sequences dropped by the selection +} + +// Counts returns the number of sequences by role. +func (c *Corpus) Counts() Counts { + n := Counts{Ignored: c.Ignored} + for _, r := range c.Roles { + if r == Example { + n.Examples++ + } else { + n.CounterExamples++ + } + } + return n +} + +type corpusEntry struct { + batch, index int + seq *obiseq.BioSequence + role Role +} + +// LoadCorpus reads every sequence of seqs, once, and keeps those selected as +// examples or counterexamples. The order of the input is preserved whatever +// the number of reader goroutines. +func LoadCorpus(seqs obiiter.IBioSequence, sel Selection) (*Corpus, error) { + var mu sync.Mutex + var entries []corpusEntry + ignored := 0 + + consumer := func(it obiiter.IBioSequence, wg *sync.WaitGroup) { + defer wg.Done() + var local []corpusEntry + localIgnored := 0 + for it.Next() { + batch := it.Get() + for i, seq := range batch.Slice() { + if role := sel.Role(seq); role == Ignored { + localIgnored++ + } else { + local = append(local, corpusEntry{batch.Order(), i, seq, role}) + } + } + } + mu.Lock() + entries = append(entries, local...) + ignored += localIgnored + mu.Unlock() + } + + var wg sync.WaitGroup + for i := 1; i < obidefault.ParallelWorkers(); i++ { + wg.Add(1) + go consumer(seqs.Split(), &wg) + } + wg.Add(1) + go consumer(seqs, &wg) + wg.Wait() + + slices.SortFunc(entries, func(a, b corpusEntry) int { + if c := cmp.Compare(a.batch, b.batch); c != 0 { + return c + } + return cmp.Compare(a.index, b.index) + }) + + c := &Corpus{ + Seqs: make([]*obiseq.BioSequence, len(entries)), + Roles: make([]Role, len(entries)), + Ignored: ignored, + } + for i, e := range entries { + c.Seqs[i], c.Roles[i] = e.seq, e.role + } + if c.Counts().Examples == 0 { + return nil, fmt.Errorf("obiprimer: no example sequence selected") + } + return c, nil +} diff --git a/pkg/obiprimer/match.go b/pkg/obiprimer/match.go new file mode 100644 index 0000000..9a14f72 --- /dev/null +++ b/pkg/obiprimer/match.go @@ -0,0 +1,156 @@ +package obiprimer + +import ( + "cmp" + "fmt" + "slices" + "sync" + + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obidefault" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obikmer" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq" +) + +// Match is one approximate occurrence of a word in a corpus sequence. +// +// Pos is the 0-based start of the window on the sequence as given (forward +// coordinates). When Reverse is false the window reads like the word; when it +// is true the window reads like the reverse complement of the word, i.e. the +// word occurs on the other strand. Errors is the number of substitutions. +type Match struct { + Seq uint32 // index in the Corpus + Pos uint32 + Errors uint8 + Reverse bool +} + +// WordMatches gathers every occurrence of one canonical word, sorted by +// (Seq, Pos, Reverse). +type WordMatches struct { + Word uint64 + Matches []Match +} + +// MatchParams parameterizes the approximate search. +// +// Rules, those of ecoPrimers: only substitutions are allowed; a base other +// than A, C, G, T (or U, read as T) in the sequence never matches, it counts as +// one error; every window with at most MaxErrors errors is reported, windows +// overlapping each other included; on circular sequences windows may span the +// junction. +type MatchParams struct { + K int + MaxErrors int + Circular bool +} + +func (p MatchParams) validate() error { + if p.K < 2 || p.K > 31 { + return fmt.Errorf("obiprimer: word size must be between 2 and 31, got %d", p.K) + } + if p.MaxErrors < 0 || p.MaxErrors >= p.K { + return fmt.Errorf("obiprimer: error count must be in [0,%d[, got %d", p.K, p.MaxErrors) + } + return nil +} + +// hit is a Match before its sequence index is known to be final. +type hit struct { + word int + m Match +} + +// scanFunc finds every occurrence of every word, on both strands, in one +// sequence and reports them through emit. +type scanFunc func(seq *obiseq.BioSequence, emit func(word, pos, errors int, reverse bool)) + +// scannerMaker returns an independent scanFunc; each worker gets its own so +// that scanners can keep private buffers. +type scannerMaker func() scanFunc + +// wordStrings returns, for each canonical word, the word and its reverse +// complement as lowercase strings. +func wordStrings(words []uint64, k int) (fwd, rev []string) { + fwd = make([]string, len(words)) + rev = make([]string, len(words)) + for i, w := range words { + fwd[i] = string(obikmer.DecodeKmer(w, k, nil)) + rev[i] = string(obikmer.DecodeKmer(obikmer.ReverseComplement(w, k), k, nil)) + } + return +} + +// matchCorpus runs the scanners over every sequence of the corpus in parallel +// and assembles the per-word results. The output does not depend on the number +// of workers. +func matchCorpus(c *Corpus, words []uint64, mk scannerMaker) []WordMatches { + perSeq := make([][]hit, len(c.Seqs)) + + jobs := make(chan int) + var wg sync.WaitGroup + for range obidefault.ParallelWorkers() { + wg.Add(1) + go func() { + defer wg.Done() + scan := mk() + for i := range jobs { + var hits []hit + scan(c.Seqs[i], func(word, pos, errors int, reverse bool) { + hits = append(hits, hit{word, Match{uint32(i), uint32(pos), uint8(errors), reverse}}) + }) + perSeq[i] = hits + } + }() + } + for i := range c.Seqs { + jobs <- i + } + close(jobs) + wg.Wait() + + res := make([]WordMatches, len(words)) + for i, w := range words { + res[i].Word = w + } + for _, hits := range perSeq { + for _, h := range hits { + res[h.word].Matches = append(res[h.word].Matches, h.m) + } + } + for i := range res { + slices.SortFunc(res[i].Matches, func(a, b Match) int { + if c := cmp.Compare(a.Seq, b.Seq); c != 0 { + return c + } + if c := cmp.Compare(a.Pos, b.Pos); c != 0 { + return c + } + if a.Reverse != b.Reverse { + if !a.Reverse { + return -1 + } + return 1 + } + return 0 + }) + } + return res +} + +// SequencesMatched returns, for the matches of one word, the number of +// distinct example and counterexample sequences it occurs in. +func (w WordMatches) SequencesMatched(c *Corpus) (examples, counterExamples int) { + last := ^uint32(0) + for _, m := range w.Matches { + if m.Seq == last { + continue + } + last = m.Seq + if c.Roles[m.Seq] == Example { + examples++ + } else { + counterExamples++ + } + } + return +} diff --git a/pkg/obiprimer/match_apat.go b/pkg/obiprimer/match_apat.go new file mode 100644 index 0000000..d96e1dc --- /dev/null +++ b/pkg/obiprimer/match_apat.go @@ -0,0 +1,87 @@ +package obiprimer + +import ( + "fmt" + + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiapat" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq" +) + +// apatSafe returns seq when it only holds a, c, g, t; otherwise a copy where u +// becomes t and every other symbol becomes z. obiapat reads IUPAC codes in the +// sequence (an n matches every base); z is the symbol it matches with nothing, +// which gives back the ecoPrimers rule "an ambiguous base is an error". +func apatSafe(seq *obiseq.BioSequence) *obiseq.BioSequence { + raw := seq.Sequence() + clean := true + for _, b := range raw { + if b != 'a' && b != 'c' && b != 'g' && b != 't' { + clean = false + break + } + } + if clean { + return seq + } + out := make([]byte, len(raw)) + for i, b := range raw { + switch b { + case 'a', 'c', 'g', 't': + out[i] = b + case 'A', 'C', 'G', 'T': + out[i] = b + 'a' - 'A' + case 'u', 'U': + out[i] = 't' + default: + out[i] = 'z' + } + } + return obiseq.NewBioSequence(seq.Id(), out, "") +} + +// matchApat searches the words with obiapat, the bit-parallel Baeza-Yates / +// Manber matcher of OBITools (the engine of ecoPrimers and obipcr). +func matchApat(c *Corpus, words []uint64, p MatchParams) ([]WordMatches, error) { + if err := p.validate(); err != nil { + return nil, err + } + fwd, rev := wordStrings(words, p.K) + pats := make([][2]obiapat.ApatPattern, len(words)) + for i := range words { + var err error + if pats[i][0], err = obiapat.MakeApatPattern(fwd[i], p.MaxErrors, false); err != nil { + return nil, fmt.Errorf("obiprimer: pattern %s: %w", fwd[i], err) + } + if pats[i][1], err = obiapat.MakeApatPattern(rev[i], p.MaxErrors, false); err != nil { + return nil, fmt.Errorf("obiprimer: pattern %s: %w", rev[i], err) + } + } + + mk := func() scanFunc { + var recycle []obiapat.ApatSequence + return func(seq *obiseq.BioSequence, emit func(word, pos, errors int, reverse bool)) { + n := seq.Len() + if n < p.K { + return + } + aseq, err := obiapat.MakeApatSequence(apatSafe(seq), p.Circular, recycle...) + if err != nil { + panic(fmt.Sprintf("obiprimer: cannot encode sequence %s: %v", seq.Id(), err)) + } + recycle = append(recycle[:0], aseq) + + for w := range pats { + for strand := range 2 { + for _, m := range pats[w][strand].FindAllIndex(aseq, 0, -1) { + // On circular sequences obiapat also scans a copy of the + // start: those hits repeat the ones at start-n. + if m[0] >= 0 && m[0] < n { + emit(w, m[0], m[2], strand == 1) + } + } + } + } + } + } + return matchCorpus(c, words, mk), nil +} diff --git a/pkg/obiprimer/match_naive.go b/pkg/obiprimer/match_naive.go new file mode 100644 index 0000000..54e9567 --- /dev/null +++ b/pkg/obiprimer/match_naive.go @@ -0,0 +1,78 @@ +package obiprimer + +import "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq" + +// baseCode maps a nucleotide to 0..3 (A,C,G,T; U is T) and everything else to 4, +// which never equals a word base. +var baseCode = func() (t [256]byte) { + for i := range t { + t[i] = 4 + } + for i, c := range "ACGT" { + t[c], t[c+'a'-'A'] = byte(i), byte(i) + } + t['U'], t['u'] = 3, 3 + return +}() + +func codesOf(s string) []byte { + out := make([]byte, len(s)) + for i := range out { + out[i] = baseCode[s[i]] + } + return out +} + +// matchNaive is the reference implementation: every window of every sequence +// is compared with every word, base by base. It is slow and exists to define +// the exact semantics (see MatchParams) that faster implementations must +// reproduce. +func matchNaive(c *Corpus, words []uint64, p MatchParams) ([]WordMatches, error) { + if err := p.validate(); err != nil { + return nil, err + } + fwd, rev := wordStrings(words, p.K) + fc, rc := make([][]byte, len(words)), make([][]byte, len(words)) + for i := range words { + fc[i], rc[i] = codesOf(fwd[i]), codesOf(rev[i]) + } + + mk := func() scanFunc { + var codes []byte + return func(seq *obiseq.BioSequence, emit func(word, pos, errors int, reverse bool)) { + raw := seq.Sequence() + n := len(raw) + if n < p.K { + return + } + codes = codes[:0] + for _, b := range raw { + codes = append(codes, baseCode[b]) + } + windows := n - p.K + 1 + if p.Circular { + codes = append(codes, codes[:p.K-1]...) + windows = n + } + for pos := range windows { + win := codes[pos : pos+p.K] + for w := range words { + for strand, pat := range [2][]byte{fc[w], rc[w]} { + errs := 0 + for j, b := range pat { + if win[j] != b { + if errs++; errs > p.MaxErrors { + break + } + } + } + if errs <= p.MaxErrors { + emit(w, pos, errs, strand == 1) + } + } + } + } + } + } + return matchCorpus(c, words, mk), nil +} diff --git a/pkg/obiprimer/match_test.go b/pkg/obiprimer/match_test.go new file mode 100644 index 0000000..65ccd82 --- /dev/null +++ b/pkg/obiprimer/match_test.go @@ -0,0 +1,285 @@ +package obiprimer + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "path/filepath" + "reflect" + "runtime" + "strconv" + "strings" + "testing" + "time" + + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obidefault" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiformats" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obikmer" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq" +) + +func corpusOf(roles []Role, seqs ...string) *Corpus { + c := &Corpus{Roles: roles} + for i, s := range seqs { + c.Seqs = append(c.Seqs, obiseq.NewBioSequence(fmt.Sprintf("s%d", i), []byte(s), "")) + } + return c +} + +func canon(word string) uint64 { return obikmer.EncodeCanonicalKmer([]byte(word), len(word)) } + +// The reference implementation is checked by hand first: everything else is +// compared with it. +func TestNaiveMatchSemantics(t *testing.T) { + const word = "AACCGGTA" // not a palindrome; reverse complement TACCGGTT + cases := []struct { + name string + seq string + circular bool + e int + want []Match + }{ + {"exact forward", "ggaaccggtacc", false, 0, []Match{{0, 2, 0, false}}}, + {"exact reverse strand", "ggtaccggttcc", false, 0, []Match{{0, 2, 0, true}}}, + {"one error is not found at e=0", "ggaaccgctacc", false, 0, nil}, + {"one error found at e=1 with its count", "ggaaccgctacc", false, 1, []Match{{0, 2, 1, false}}}, + {"an N is an error", "ggaaccgntacc", false, 1, []Match{{0, 2, 1, false}}}, + {"an N is never a match at e=0", "ggaaccgntacc", false, 0, nil}, + {"an IUPAC code is an error, not a wildcard", "ggaaccgrtacc", false, 0, nil}, + {"U reads as T", "ggaaccgguacc", false, 0, []Match{{0, 2, 0, false}}}, + {"junction ignored on a linear sequence", "ccggtagggaa", false, 0, nil}, + {"junction found on a circular sequence", "ccggtagggaa", true, 0, []Match{{0, 9, 0, false}}}, + {"overlapping windows are all reported", "aaccggtaaccggta", false, 0, []Match{{0, 0, 0, false}, {0, 7, 0, false}}}, + {"too short", "aaccgg", true, 3, nil}, + } + for _, tc := range cases { + c := corpusOf([]Role{Example}, tc.seq) + got, err := matchNaive(c, []uint64{canon(word)}, MatchParams{K: len(word), MaxErrors: tc.e, Circular: tc.circular}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got[0].Matches, tc.want) { + t.Errorf("%s: got %v, want %v", tc.name, got[0].Matches, tc.want) + } + } +} + +func TestMatchParamsValidation(t *testing.T) { + c := corpusOf([]Role{Example}, "acgtacgtacgtacgt") + for _, p := range []MatchParams{{K: 1}, {K: 32}, {K: 8, MaxErrors: -1}, {K: 8, MaxErrors: 8}} { + if _, err := matchNaive(c, nil, p); err == nil { + t.Errorf("%+v must be rejected", p) + } + if _, err := matchApat(c, nil, p); err == nil { + t.Errorf("%+v must be rejected by obiapat variant", p) + } + } +} + +func randSeq(rng *rand.Rand, n int, alphabet string) []byte { + b := make([]byte, n) + for i := range b { + b[i] = alphabet[rng.Intn(len(alphabet))] + } + return b +} + +func mutate(rng *rand.Rand, s []byte, n int) []byte { + out := append([]byte(nil), s...) + for _, i := range rng.Perm(len(out))[:n] { + out[i] = "acgt"[rng.Intn(4)] + } + return out +} + +// randomCorpus builds sequences with words planted on both strands, with 0..3 +// substitutions, across the circular junction, and with ambiguity codes. +func randomCorpus(rng *rand.Rand, words []string, nseq int) *Corpus { + c := &Corpus{} + for i := 0; i < nseq; i++ { + alphabet := "acgt" + if i%3 == 0 { + alphabet = "acgtacgtacgtacgtacgtnnrykm" + } + s := randSeq(rng, 30+rng.Intn(500), alphabet) + for range 1 + rng.Intn(4) { + w := []byte(words[rng.Intn(len(words))]) + if rng.Intn(2) == 0 { + w = []byte(revcomp(string(w))) + } + w = mutate(rng, []byte(string(w)), rng.Intn(4)) + for j := range w { + w[j] += 'a' - 'A' + } + at := rng.Intn(len(s) - len(w) + 1) + if rng.Intn(5) == 0 { // across the junction + s = append(s[:0:0], append(append([]byte(nil), w[len(w)/2:]...), append(s[:len(s)-len(w)/2], w[:len(w)/2]...)...)...) + continue + } + copy(s[at:], w) + } + c.Seqs = append(c.Seqs, obiseq.NewBioSequence(fmt.Sprintf("r%d", i), s, "")) + c.Roles = append(c.Roles, Example) + } + return c +} + +// Every implementation must return exactly what the reference returns. +func TestMatchersAgreeWithReference(t *testing.T) { + for _, tc := range []struct { + k, e int + circular bool + }{{18, 0, false}, {18, 1, true}, {18, 3, false}, {18, 3, true}, {12, 2, true}, {25, 4, false}} { + t.Run(fmt.Sprintf("k%d-e%d-circ%v", tc.k, tc.e, tc.circular), func(t *testing.T) { + rng := rand.New(rand.NewSource(int64(tc.k*100 + tc.e))) + var wordStr []string + var words []uint64 + for range 7 { + w := string(randSeq(rng, tc.k, "ACGT")) + wordStr = append(wordStr, w) + words = append(words, canon(w)) + } + c := randomCorpus(rng, wordStr, 40) + p := MatchParams{K: tc.k, MaxErrors: tc.e, Circular: tc.circular} + + want, err := matchNaive(c, words, p) + if err != nil { + t.Fatal(err) + } + total := 0 + for _, w := range want { + total += len(w.Matches) + } + if total < 10 { + t.Fatalf("test data too poor: only %d reference matches", total) + } + + got, err := matchApat(c, words, p) + if err != nil { + t.Fatal(err) + } + for i := range want { + if !reflect.DeepEqual(got[i].Matches, want[i].Matches) { + t.Fatalf("word %d: obiapat gives %v\nreference gives %v", i, got[i].Matches, want[i].Matches) + } + } + }) + } +} + +func TestSequencesMatched(t *testing.T) { + c := corpusOf([]Role{Example, Example, CounterExample, CounterExample}, "a", "a", "a", "a") + w := WordMatches{Matches: []Match{{0, 1, 0, false}, {0, 9, 0, true}, {1, 3, 0, false}, {3, 0, 0, false}}} + if e, x := w.SequencesMatched(c); e != 2 || x != 1 { + t.Errorf("got %d examples, %d counterexamples", e, x) + } +} + +func readEcoCounts(t *testing.T, path string) map[string]int { + t.Helper() + f, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + defer f.Close() + counts := map[string]int{} + sc := bufio.NewScanner(f) + for sc.Scan() { + var w string + var in, out int + if _, err := fmt.Sscanf(sc.Text(), "%s\t%d\t%d", &w, &in, &out); err != nil { + t.Fatal(err) + } + counts[w] = in + } + return counts +} + +// Opt-in comparison with ecoPrimers on the tutorial data (same variable as the +// strict word test). For every strict word, ecoPrimers logs the number of +// example sequences in which the fuzzy search (-e 3, both strands, circular) +// finds it; testdata/ecoprimers_*.words.tsv holds those numbers. +func TestMatchApatAgainstEcoPrimers(t *testing.T) { + dir := os.Getenv("OBIPRIMERS_TUTORIAL") + if dir == "" { + t.Skip("OBIPRIMERS_TUTORIAL not set") + } + if v := os.Getenv("OBIPRIMERS_WORKERS"); v != "" { // e.g. 1, to time a single core + n, err := strconv.Atoi(v) + if err != nil { + t.Fatal(err) + } + obidefault.SetMaxCPU(n) + } + taxonomy, err := obiformats.LoadTaxonomy(filepath.Join(dir, "ncbitaxo_20250211.tgz"), false, false) + if err != nil { + t.Fatal(err) + } + + for _, tc := range []struct{ name, taxid string }{{"Cyprinidae", "7953"}, {"Teleostei", "32443"}} { + t.Run(tc.name, func(t *testing.T) { + restrict, err := BelongingTo(taxonomy, []string{tc.taxid}) + if err != nil { + t.Fatal(err) + } + seqs, err := obiformats.ReadSequencesFromFile(filepath.Join(dir, "mito.vert.fasta.gz")) + if err != nil { + t.Fatal(err) + } + corpus, err := LoadCorpus(seqs, Selection{Restrict: restrict}) + if err != nil { + t.Fatal(err) + } + sw, err := FindStrictWords(corpus, WordSearch{PrimerLength: 18, Quorum: 0.7, Circular: true, TmpDir: t.TempDir()}) + if err != nil { + t.Fatal(err) + } + words := make([]uint64, len(sw.Words)) + for i, w := range sw.Words { + words[i] = w.Kmer + } + + var before, after runtime.MemStats + runtime.GC() + runtime.ReadMemStats(&before) + start := time.Now() + res, err := matchApat(corpus, words, MatchParams{K: 18, MaxErrors: 3, Circular: true}) + elapsed := time.Since(start) + runtime.ReadMemStats(&after) + if err != nil { + t.Fatal(err) + } + nm := 0 + for _, r := range res { + nm += len(r.Matches) + } + bases := 0 + for _, s := range corpus.Seqs { + bases += s.Len() + } + t.Logf("obiapat: %d words x %d sequences (%.1f Mb) e=3 in %s (%.0f Mb/s per word-strand pass); %d matches; heap +%.0f MB, allocated %.0f MB", + len(words), len(corpus.Seqs), float64(bases)/1e6, elapsed.Round(time.Millisecond), + float64(bases)*float64(len(words))*2/1e6/elapsed.Seconds(), nm, + float64(after.HeapAlloc-before.HeapAlloc)/1e6, float64(after.TotalAlloc-before.TotalAlloc)/1e6) + + eco := readEcoCounts(t, filepath.Join("testdata", "ecoprimers_"+tc.name+".words.tsv")) + compared, differ := 0, 0 + for i, r := range res { + w := strings.ToUpper(string(obikmer.DecodeKmer(words[i], 18, nil))) + want, ok := eco[w] + if !ok { + t.Errorf("word %s is not in the ecoPrimers log", w) + continue + } + got, _ := r.SequencesMatched(corpus) + compared++ + if got != want { + differ++ + t.Errorf("word %s: %d example sequences, ecoPrimers %d", w, got, want) + } + } + t.Logf("%d words compared with ecoPrimers, %d differ", compared, differ) + }) + } +} diff --git a/pkg/obiprimer/testdata/ecoprimers_Cyprinidae.words.tsv b/pkg/obiprimer/testdata/ecoprimers_Cyprinidae.words.tsv new file mode 100644 index 0000000..49f6354 --- /dev/null +++ b/pkg/obiprimer/testdata/ecoprimers_Cyprinidae.words.tsv @@ -0,0 +1,1208 @@ +AAAAACATCGCCTCCTGC 324 0 +AAAAACTCTTGGTGCAAA 324 0 +AAAACATCGCCTCCTGCA 324 0 +AAAACCCAAAGGACCTGA 324 0 +AAAACTCGTGCCAGCCAC 325 0 +AAAACTCTTGGTGCAAAT 324 0 +AAAAGACAAGTGATTGCG 324 0 +AAAAGAGTGGGAAGAGCT 324 0 +AAAAGCATCGGTCTTGTA 324 0 +AAAAGGGCTTAGGCCTTT 325 0 +AAAATGAGTAACAAGAAG 325 0 +AAAATGTAGCCCATTTCT 325 0 +AAACAAACTGGGATTAGA 325 0 +AAACACAAGCCTCGCCTG 320 0 +AAACAGAAAGCGTTAAAG 325 0 +AAACAGGCGAGGCTTGTG 323 0 +AAACATCGCCTCCTGCAA 324 0 +AAACCCAAAGGACCTGAC 324 0 +AAACCCCAAGGAAAGATA 324 0 +AAACCCCCCTACCCCCCT 320 0 +AAACCTCTGTCTTCGGGG 313 0 +AAACGAACCCTTAATAGC 323 0 +AAACGAGGGCTTAACTGT 325 0 +AAACTAAAGGTCTCTTTG 325 0 +AAACTCGTGCCAGCCACC 325 0 +AAACTCTTAGTTAACAGC 325 0 +AAACTCTTGGTGCAAATC 324 0 +AAACTGACCTGGATTGCT 325 0 +AAACTGGGATTAGATACC 325 0 +AAAGACAAGTGATTGCGC 324 0 +AAAGAGACCTTTAGTTTG 325 0 +AAAGAGTGGGAAGAGCTC 323 0 +AAAGATTATGAGCCTCAT 300 0 +AAAGCATAGCACTGAAGA 324 0 +AAAGCATCGGTCTTGTAA 325 0 +AAAGCGTTAAAGCTCAGA 325 0 +AAAGCTGAAAGAGAAATG 325 0 +AAAGGAAAGACTAAAAGA 321 0 +AAAGGACCTGACGGTGTC 324 0 +AAAGGCATGGTCCCGACC 323 0 +AAAGGGAGGAATTGAACC 325 0 +AAAGGGCTTAGGCCTTTC 325 0 +AAAGGGGCTGTACCCCCT 325 0 +AAAGGGTCTTCTCGTCTT 325 0 +AAAGGTAGCGCAATCACT 324 0 +AAAGTAGATGGATGCTCG 324 0 +AAAGTCCTACGTGATCTG 325 0 +AAATAAGGACATACTGGG 311 0 +AAATAGGAACCAGATGCC 322 0 +AAATCATGATGCAAAAGG 325 0 +AAATGAGTAACAAGAAGG 325 0 +AAATGGGCTACATTTTCT 325 0 +AAATGTAGCCCATTTCTT 325 0 +AAATTGCGAAAGGCCTAA 320 0 +AACAAACGAACCCTTAAT 323 0 +AACAAACTGGGATTAGAT 325 0 +AACAACGGTGGTTCTTCA 325 0 +AACACAAGCCTCGCCTGT 320 0 +AACACGGTAAGTGTACCG 324 0 +AACAGAAAGCGTTAAAGC 325 0 +AACAGCGCAATCCTCTCC 325 0 +AACAGGCGAGGCTTGTGT 320 0 +AACAGGCTCCTCTAGGGG 325 0 +AACATCGAGGTCGTAAAC 325 0 +AACATCGCCTCCTGCAAC 324 0 +AACCAAAAACTCTTGGTG 325 0 +AACCAAGTTACCCTAGGG 325 0 +AACCACCCTTTACGCCGT 324 0 +AACCACCGTTGTTATTCA 316 0 +AACCAGATGCCAGTAATA 324 0 +AACCAGCTATCACCAGGT 325 0 +AACCCAAAGGACCTGACG 324 0 +AACCCCAAGGAAAGATAA 324 0 +AACCCCCCTACCCCCCTA 320 0 +AACCCGGCTCTGAGACGC 324 0 +AACCCGTCTCTGTGGCAA 325 0 +AACCCTTAATAGCGGCTG 325 0 +AACCGAGACCAATGACTT 320 0 +AACCGCGGTGGCTGGCAC 325 0 +AACCGTCATGTTCGGGGT 324 0 +AACCTCCGATCTTCGGAT 321 0 +AACCTCTGTCTTCGGGGC 314 0 +AACCTGGTGATAGCTGGT 325 0 +AACGAACCAAGTTACCCT 325 0 +AACGAACCCTTAATAGCG 323 0 +AACGAGAAAGGGAGGAAT 324 0 +AACGAGGGCTTAACTGTC 325 0 +AACGATTAAAGTCCTACG 320 0 +AACGGCCGCGGTATTTTG 323 0 +AACGGCGTAAAGGGTGGT 324 0 +AACGTCAGGTCGAGGTGT 325 0 +AACTAAAGGTCTCTTTGC 325 0 +AACTAAGAGTTTGTAGGA 325 0 +AACTATTACTGGCATCTG 322 0 +AACTCAGATCACGTAGGA 325 0 +AACTCGGCAAACACAAGC 322 0 +AACTCGTGCCAGCCACCG 325 0 +AACTCTTAGTTAACAGCT 325 0 +AACTCTTGGTGCAAATCC 324 0 +AACTGACCTGGATTGCTC 325 0 +AACTGGGATTAGATACCC 325 0 +AACTTACACATGCAAGTC 325 0 +AACTTGGTTCGTTGATCG 325 0 +AAGAACCACCGTTGTTAT 321 0 +AAGACAAGTGATTGCGCT 324 0 +AAGACCCTTTGGAGCTTA 325 0 +AAGACCTCTGATTTCGGC 324 0 +AAGACTGTGTGATTGGAA 322 0 +AAGAGACCTTTAGTTTGA 325 0 +AAGAGTTTGTAGGATCGA 325 0 +AAGAGTTTTTGGTTCCTA 323 0 +AAGATTATGAGCCTCATC 317 0 +AAGATTTTCACTTCTACT 322 0 +AAGCAAAGAGACCTTTAG 325 0 +AAGCATAGCACTGAAGAT 324 0 +AAGCATCGGTCTTGTAAT 325 0 +AAGCATCTCACTTACACC 323 0 +AAGCCACAGAACATCTGA 321 0 +AAGCCAGCCACATAACCA 322 0 +AAGCCATAAGTGAAAACT 325 0 +AAGCCTCGCCTGTTTACC 325 0 +AAGCGTTAAAGCTCAGAC 325 0 +AAGCTCCAAAGGGTCTTC 325 0 +AAGCTGAAAGAGAAATGA 325 0 +AAGCTGACGACGGCGGTA 325 0 +AAGCTTTCGGGCCCATAC 325 0 +AAGGAAAGACTAAAAGAA 323 0 +AAGGAACTCGGCAAACAC 323 0 +AAGGAAGGAACTCGGCAA 324 0 +AAGGACATACTGGGGTGG 312 0 +AAGGACCACTTTGATAGA 325 0 +AAGGACCTGACGGTGTCT 325 0 +AAGGAGGATTTAGTAGTA 325 0 +AAGGCATGGTCCCGACCT 323 0 +AAGGCCTAAGCCCTTTTA 324 0 +AAGGCCTCGATCCTACAA 325 0 +AAGGGAAAGCTGAAAGAG 325 0 +AAGGGAATTCAGCAGTGA 325 0 +AAGGGAGGAATTGAACCC 325 0 +AAGGGCTTAGGCCTTTCG 325 0 +AAGGGGAGGCAAGTCGTA 325 0 +AAGGGGCTGTACCCCCTT 325 0 +AAGGGTCTTCTCGTCTTG 325 0 +AAGGGTTCGTTTGTTCAA 324 0 +AAGGTAGCGCAATCACTT 324 0 +AAGGTCGGGACCATGCCT 322 0 +AAGTAGAAGTGAAAATCT 323 0 +AAGTCCTACGTGATCTGA 325 0 +AAGTCGTAACACGGTAAG 325 0 +AAGTGACAGACCTACCGA 322 0 +AAGTGACAGAGTGGTTAT 322 0 +AAGTGACTTCCAATCACA 320 0 +AAGTGCACCTTCCGGTAC 323 0 +AAGTGTACCGGAAGGTGC 324 0 +AAGTTACCCTAGGGATAA 325 0 +AAGTTTTCACTTATGGCT 325 0 +AATAACAACGGTGGTTCT 319 0 +AATACAAGACGAGAAGAC 325 0 +AATACCGCGGCCGTTGAA 323 0 +AATAGCGGCTGCACCATT 324 0 +AATAGGAACCAGATGCCA 322 0 +AATAGGCTGTCTCGGGGT 322 0 +AATCACTTGTCTTTTAAA 324 0 +AATCATGATGCAAAAGGT 325 0 +AATCCAGGTCAGTTTCTA 325 0 +AATCCGAAGATCGGAGGT 321 0 +AATCCTCTCCCAGAGTCC 325 0 +AATCGTTGAACAAACGAA 325 0 +AATCTTTGGCTTAAAAGG 323 0 +AATGAGTAACAAGAAGGC 325 0 +AATGGCCCACCAAGCACA 325 0 +AATGGGCTACATTTTCTA 325 0 +AATTCCCTTGGGGGTGTG 325 0 +AATTCCTCCCTTTCTCGT 324 0 +AATTGCGAAAGGCCTAAG 322 0 +AATTGTGGCATTTCACTA 317 0 +AATTTACCATGCTCTGCC 324 0 +ACAAACGAACCCTTAATA 322 0 +ACAAACTCTTAGTTAACA 325 0 +ACAAACTGGGATTAGATA 325 0 +ACAAAGGCATGGTCCCGA 322 0 +ACAACGGTGGTTCTTCAA 325 0 +ACAAGACCGATGCTTTTA 324 0 +ACAAGACGAGAAGACCCT 325 0 +ACAAGCCTCGCCTGTTTA 325 0 +ACAAGGGGAGGCAAGTCG 321 0 +ACAAGTGACTTCCAATCA 320 0 +ACAAGTGATTGCGCTACC 324 0 +ACACACCGCCCGTCACTC 325 0 +ACACATGCAAGTCTCCGC 324 0 +ACACCCCCAAGGGAATTC 324 0 +ACACCGAGAAGACATCCA 324 0 +ACACCGCCCGTCACTCTC 325 0 +ACACCGTCAGGTCCTTTG 324 0 +ACACGGTAAGTGTACCGG 324 0 +ACACTTACCGTGTTACGA 325 0 +ACAGAAAGCGTTAAAGCT 325 0 +ACAGACCTACCGAACCTG 323 0 +ACAGAGACGGGTTGGCCC 325 0 +ACAGAGTGGTTATGTGGC 319 0 +ACAGATAGAAACTGACCT 323 0 +ACAGCCCCTTTAACAAAG 324 0 +ACAGCGCAATCCTCTCCC 325 0 +ACAGGCTCCTCTAGGGGG 325 0 +ACAGGGTAAGCTGACGAC 325 0 +ACATAACCACTCTGTCAC 320 0 +ACATACTGGGGTGGCAGA 320 0 +ACATCCTAATGGTGCAGC 325 0 +ACATCGAGGTCGTAAACC 325 0 +ACCAAAAACATCGCCTCC 324 0 +ACCAAAAACTCTTGGTGC 325 0 +ACCAAAAGCCTTCAAAGC 321 0 +ACCAAGACTGTGTGATTG 322 0 +ACCAAGAGTTTTTGGTTC 325 0 +ACCAAGCACATGCCTATC 323 0 +ACCAAGTTACCCTAGGGA 325 0 +ACCAATCTTTGGCTTAAA 321 0 +ACCAATGACTTGAAGAAC 325 0 +ACCACCGTTGTTATTCAA 316 0 +ACCACTTTGATAGAGTGA 325 0 +ACCAGATGCCAGTAATAG 322 0 +ACCAGGTTCGGTAGGTCT 323 0 +ACCATGCTCTGCCACCCC 324 0 +ACCATTAGGATGTCCTGA 325 0 +ACCCAAAGGACCTGACGG 324 0 +ACCCCAAGGAAAGATAAT 324 0 +ACCCCCAAGGGAATTCAG 324 0 +ACCCCCCTACCCCCCTAC 298 0 +ACCCGGCTCTGAGACGCG 324 0 +ACCCGTCTCTGTGGCAAA 325 0 +ACCCTACCTGTGGCAATC 324 0 +ACCCTAGGGATAACAGCG 325 0 +ACCCTTAATAGCGGCTGC 325 0 +ACCCTTTGGAGCTTAAGG 325 0 +ACCGAACCTGGTGATAGC 325 0 +ACCGAGAAGACATCCATG 323 0 +ACCGAGACCAATGACTTG 320 0 +ACCGCAAGGGAAAGCTGA 324 0 +ACCGCCCGTCACTCTCCC 325 0 +ACCGCCGTCGTCAGCTTA 325 0 +ACCGCGGTGGCTGGCACG 325 0 +ACCGCGGTTAGACGAGAG 324 0 +ACCGGAAGGTGCACTTGG 322 0 +ACCGGAGCAATCCAGGTC 324 0 +ACCGTCAGGTCCTTTGGG 324 0 +ACCGTCATGTTCGGGGTA 324 0 +ACCGTGCAAAGGTAGCGC 324 0 +ACCGTTGTTATTCAACTA 317 0 +ACCTACCGAACCTGGTGA 325 0 +ACCTCGATGTTGGATCAG 325 0 +ACCTCTGATTTCGGCTCA 324 0 +ACCTCTGTCTTCGGGGCT 314 0 +ACCTGACGGTGTCTCAGA 325 0 +ACCTGGATTGCTCCGGTC 324 0 +ACCTGTATGAATGGCTAA 325 0 +ACCTTAAGCTCCAAAGGG 325 0 +ACCTTCCGGTACACTTAC 324 0 +ACCTTCGGTTGGGGCGAC 325 0 +ACCTTTAGTTTGAAACCC 324 0 +ACCTTTGCACGGTCAAAA 324 0 +ACGAACCAAGTTACCCTA 325 0 +ACGAACCCTTAATAGCGG 323 0 +ACGACCTCGATGTTGGAT 325 0 +ACGACGGCGGTATATAGG 324 0 +ACGACTTGCCTCCCCTTG 323 0 +ACGAGAAGACCCTTTGGA 325 0 +ACGAGGAGCGGGCATCAG 316 0 +ACGAGGGCTTAACTGTCT 325 0 +ACGAGTTTTACCGGCCCT 324 0 +ACGATTAAAGTCCTACGT 321 0 +ACGCGTACACACCGCCCG 325 0 +ACGCGTCTCAGAGCCGGG 323 0 +ACGGCCGCGGTATTTTGA 323 0 +ACGGCGGTATATAGGCTG 324 0 +ACGGGCGGTGTGTACGCG 325 0 +ACGGTAAGTGTACCGGAA 324 0 +ACGGTCAAAATACCGCGG 323 0 +ACGGTGTCTCAGACCCCC 323 0 +ACGTCAGGTCGAGGTGTA 325 0 +ACGTGATCTGAGTTCAGA 325 0 +ACTAAAGGTCTCTTTGCT 325 0 +ACTAAGAGTTTGTAGGAT 325 0 +ACTACTAAATCCTCCTTC 324 0 +ACTAGAAAGATTATGAGC 284 0 +ACTATTACTGGCATCTGG 322 0 +ACTATTCCGGCTCAGGCA 323 0 +ACTCAAGAGATCAAAACT 320 0 +ACTCAGATCACGTAGGAC 325 0 +ACTCGGCAAACACAAGCC 322 0 +ACTCGTGCCAGCCACCGC 325 0 +ACTCTATCAAAGTGGTCC 325 0 +ACTCTGGGAGAGGATTGC 325 0 +ACTCTTAGTTAACAGCTA 325 0 +ACTCTTGGTGCAAATCCA 324 0 +ACTCTTTTGCCACAGAGA 325 0 +ACTGAAGATGCTAAGATG 325 0 +ACTGACCTGGATTGCTCC 324 0 +ACTGAGTCAAGTTTTCAC 325 0 +ACTGCTGAATTCCCTTGG 325 0 +ACTGGCATCTGGTTCCTA 322 0 +ACTGGCTAAATCATGATG 325 0 +ACTGGGATTAGATACCCC 325 0 +ACTGGGCAGGCTGGACCT 323 0 +ACTGGGGTGGCAGAGCAT 323 0 +ACTTACACATGCAAGTCT 325 0 +ACTTACACCGAGAAGACA 323 0 +ACTTACCGTGTTACGACT 325 0 +ACTTGCATGTGTAAGTTG 325 0 +ACTTGGATTTGCACCAAG 324 0 +ACTTGGTTCGTTGATCGG 325 0 +ACTTTAATCGTTGAACAA 325 0 +ACTTTTCCTAGTACGAAA 323 0 +AGAAACAAACTGGGATTA 325 0 +AGAAACTGACCTGGATTG 325 0 +AGAAAGATTATGAGCCTC 271 0 +AGAAAGCGTTAAAGCTCA 325 0 +AGAAAGGGAGGAATTGAA 325 0 +AGAACAGGCTCCTCTAGG 325 0 +AGAAGACCCTTTGGAGCT 325 0 +AGAAGATTTTCACTTCTA 323 0 +AGAAGTGAAAATCTTCTA 323 0 +AGAAGTGACAGACCTACC 321 0 +AGAATCACAATCTAATAT 321 0 +AGAATTCTGGCTTTGGGA 321 0 +AGACAAGTGATTGCGCTA 324 0 +AGACCAATGACTTGAAGA 325 0 +AGACCCTTTGGAGCTTAA 325 0 +AGACCGGAGCAATCCAGG 324 0 +AGACCTCTGATTTCGGCT 324 0 +AGACCTTTAGTTTGAAAC 324 0 +AGACGAGAAGACCCTTTG 325 0 +AGACGCGTACACACCGCC 324 0 +AGACTGTGTGATTGGAAG 318 0 +AGAGACCTTTAGTTTGAA 325 0 +AGAGCATGGTAAATTGCG 307 0 +AGAGGAGCCTGTTCTAGA 325 0 +AGAGGATTGCGCTGTTAT 325 0 +AGAGGATTTGAACCTCTG 325 0 +AGAGTCCATATCGACGAG 324 0 +AGAGTGACGGGCGGTGTG 325 0 +AGAGTGGTTATGTGGCTG 321 0 +AGAGTTTGTAGGATCGAG 325 0 +AGAGTTTTGATCTCTTGA 316 0 +AGAGTTTTTGGTTCCTAA 323 0 +AGATACCCCACTATGCTC 325 0 +AGATAGAAACTGACCTGG 323 0 +AGATCAATTTCACTGACT 325 0 +AGATCACGTAGGACTTTA 325 0 +AGATGCCAGTAATAGTTC 322 0 +AGATTATGAGCCTCATCA 317 0 +AGATTGTGATTCTAAAGA 322 0 +AGCACTGAAGATGCTAAG 324 0 +AGCATAGCACTGAAGATG 324 0 +AGCATAGTGGGGTATCTA 325 0 +AGCATCGGTCTTGTAATC 325 0 +AGCATCTCACTTACACCG 323 0 +AGCATCTTCAGTGCTATG 324 0 +AGCATGGTAAATTGCGAA 306 0 +AGCCACACCCCCAAGGGA 324 0 +AGCCACAGAACATCTGAC 321 0 +AGCCACATAACCACTCTG 321 0 +AGCCACCGCGGTTAGACG 325 0 +AGCCAGCCACATAACCAC 322 0 +AGCCCCTTTAACAAAGGA 324 0 +AGCCGCTATTAAGGGTTC 324 0 +AGCCTATATACCGCCGTC 324 0 +AGCCTCGCCTGTTTACCA 325 0 +AGCCTGTTCTAGAACCGA 325 0 +AGCGCAATCCTCTCCCAG 325 0 +AGCGCTTAGCTGTTAACT 325 0 +AGCGGCTGCACCATTAGG 324 0 +AGCGGGCATCAGGCACAA 323 0 +AGCGTAGGGGGGTAGGGG 305 0 +AGCGTTAAAGCTCAGACA 325 0 +AGCTACCCCGAGACAGCC 324 0 +AGCTAGCTGTTTGGCTCA 324 0 +AGCTATCACCAGGTTCGG 325 0 +AGCTGAAAGAGAAATGAA 325 0 +AGCTGACGACGGCGGTAT 325 0 +AGCTTAAAACCCAAAGGA 325 0 +AGCTTTCCCTTGCGGTAC 324 0 +AGCTTTCGGGCCCATACC 323 0 +AGCTTTGAAGGCTTTTGG 315 0 +AGGAAAGACTAAAAGAAA 322 0 +AGGAACCAAAAACTCTTG 323 0 +AGGAACCAGATGCCAGTA 322 0 +AGGAACTCGGCAAACACA 323 0 +AGGAAGGAACTCGGCAAA 324 0 +AGGAATTGAACCCCCATA 325 0 +AGGACATACTGGGGTGGC 311 0 +AGGACATCCTAATGGTGC 325 0 +AGGACCACTTTGATAGAG 325 0 +AGGACCTGACGGTGTCTC 325 0 +AGGACTTTAATCGTTGAA 320 0 +AGGAGCCTGTTCTAGAAC 325 0 +AGGAGCGGGCATCAGGCA 321 0 +AGGAGGATAAAGATCATA 325 0 +AGGAGGATTTAGTAGTAA 325 0 +AGGAGGCGATGTTTTTGG 324 0 +AGGAGGTCCAGCCTGCCC 324 0 +AGGATAAAGATCATAATA 324 0 +AGGATCGAGGCCTTCCCA 325 0 +AGGATGTCCTGATCCAAC 325 0 +AGGATTGCGCTGTTATCC 325 0 +AGGCAACCAGCTATCACC 325 0 +AGGCATGTGCTTGGTGGG 323 0 +AGGCCTCGATCCTACAAA 325 0 +AGGCCTTTCGCAATTTAC 316 0 +AGGCGAGGCTTGTGTTTG 320 0 +AGGCGATGTTTTTGGTAA 325 0 +AGGCGGGAAAAGCCCCGG 314 0 +AGGCGGTGGGTTGTAGCC 323 0 +AGGCTCATAATCTTTCTA 274 0 +AGGCTGGACCTCCTATAC 320 0 +AGGCTGTCTCGGGGTAGC 324 0 +AGGCTTGTGTTTGCCGAG 320 0 +AGGGAAAGCTGAAAGAGA 325 0 +AGGGAATTCAGCAGTGAT 325 0 +AGGGAGGAATTGAACCCC 325 0 +AGGGATAACAGCGCAATC 325 0 +AGGGCCAACCCGTCTCTG 325 0 +AGGGCTTAACTGTCTCCC 325 0 +AGGGCTTAGGCCTTTCGC 325 0 +AGGGGAGGCAAGTCGTAA 325 0 +AGGGGTTAAAATCCCCTC 325 0 +AGGGTAAGCTGACGACGG 325 0 +AGGGTTCGTTTGTTCAAC 324 0 +AGGTAGCGCAATCACTTG 324 0 +AGGTCCTTTGGGTTTTAA 325 0 +AGGTCGAGGTGTAGCGCA 325 0 +AGGTCTGTCACTTCTACC 323 0 +AGGTGAGCTACCCCGAGA 325 0 +AGGTTCGGTAGGTCTGTC 322 0 +AGTACAAGTGACTTCCAA 325 0 +AGTACCGCAAGGGAAAGC 324 0 +AGTACGAAAGGATCGGAA 325 0 +AGTCAAGTTTTCACTTAT 325 0 +AGTCATTGGTCTCGGTTA 320 0 +AGTCCTACGTGATCTGAG 325 0 +AGTGAAAACTTGACTCAG 325 0 +AGTGACAGACCTACCGAA 323 0 +AGTGACAGAGTGGTTATG 322 0 +AGTGACGGGCGGTGTGTA 325 0 +AGTGACTTCCAATCACAC 316 0 +AGTGCACCTTCCGGTACA 323 0 +AGTGGGCCTAAAAGCAGC 324 0 +AGTGGGGTATCTAATCCC 325 0 +AGTGGTTATGTGGCTGGC 321 0 +AGTGTACCGGAAGGTGCA 323 0 +AGTTACCCTAGGGATAAC 325 0 +AGTTCAGACCGGAGCAAT 325 0 +AGTTGAATAACAACGGTG 317 0 +AGTTTCTATCTGTAACGC 323 0 +AGTTTGAAACCCCGAAAC 324 0 +AGTTTGTAGGATCGAGGC 325 0 +AGTTTTTGGTTCCTAAGA 325 0 +ATACAAGACGAGAAGACC 325 0 +ATACCCCACTATGCTCAG 325 0 +ATACCCCGAACATGACGG 324 0 +ATACCGCGGCCGTTGAAC 323 0 +ATACTAGAAAGATTATGA 271 0 +ATACTGGGGTGGCAGAGC 323 0 +ATAGAAACTGACCTGGAT 324 0 +ATAGCACTGAAGATGCTA 325 0 +ATAGCGGCTGCACCATTA 324 0 +ATAGCTGGTTGCCTAAGA 324 0 +ATAGGAACCAGATGCCAG 322 0 +ATAGGAGGTCCAGCCTGC 324 0 +ATAGGCATGTGCTTGGTG 322 0 +ATAGGCTGTCTCGGGGTA 322 0 +ATAGTGGGGTATCTAATC 325 0 +ATATACCGCCGTCGTCAG 325 0 +ATATATATATATATATAA 315 0 +ATATATATATATATATAT 307 0 +ATATGGACTCTGGGAGAG 325 0 +ATATGGGGGTTCAATTCC 325 0 +ATCAACGAACCAAGTTAC 325 0 +ATCACCAGGTTCGGTAGG 325 0 +ATCACCCGTTATAGAAGA 323 0 +ATCACGTAGGACTTTAAT 325 0 +ATCACTTGTCTTTTAAAT 324 0 +ATCAGGACATCCTAATGG 325 0 +ATCATGATGCAAAAGGTA 325 0 +ATCATGATTTAGCCAGTA 325 0 +ATCCCTAGGGTAACTTGG 325 0 +ATCCTAATGGTGCAGCCG 325 0 +ATCCTCTCCCAGAGTCCA 325 0 +ATCCTTTCGTACTAGGAA 324 0 +ATCGGTCTTGTAATCCGA 325 0 +ATCGGTTCTAGAACAGGC 325 0 +ATCGTTGAACAAACGAAC 325 0 +ATCTCACTTACACCGAGA 323 0 +ATCTGAGTTCAGACCGGA 325 0 +ATCTGGTTCCTATTTCAG 321 0 +ATCTTAGCATCTTCAGTG 325 0 +ATCTTCGGATTACAAGAC 323 0 +ATCTTTCCTTGGGGTTTA 323 0 +ATCTTTGGCTTAAAAGGC 323 0 +ATGAGTAACAAGAAGGCC 325 0 +ATGATCTTTATCCTCCTG 325 0 +ATGCCCGCTCCTCGTCCC 306 0 +ATGCGCTACACCTCGACC 325 0 +ATGCTAAAATGAGTAACA 325 0 +ATGCTGGTTTCAAGCCAG 323 0 +ATGGACTCTGGGAGAGGA 325 0 +ATGGATGTCTTCTCGGTG 324 0 +ATGGCAAGCCTACGAAAA 324 0 +ATGGCCCACCAAGCACAT 325 0 +ATGGGGGTTCAATTCCTC 325 0 +ATGGTAAATTGCGAAAGG 304 0 +ATGGTGCAGCCGCTATTA 324 0 +ATGTCCTGATCCAACATC 325 0 +ATGTCTTCTCGGTGTAAG 322 0 +ATGTGGCTGGCTTGAAAC 323 0 +ATGTTCGGGGTATGGGCC 325 0 +ATGTTGGATCAGGACATC 325 0 +ATGTTTTTGGTAAACAGG 324 0 +ATTAAGCCATAAGTGAAA 323 0 +ATTACTGGCATCTGGTTC 323 0 +ATTAGATACCCCACTATG 325 0 +ATTAGATTGTGATTCTAA 321 0 +ATTAGGATGTCCTGATCC 325 0 +ATTATGATCTTTATCCTC 325 0 +ATTCCCTTCTTTCTAGGA 323 0 +ATTCCCTTGGGGGTGTGG 325 0 +ATTCCGGCTCAGGCACCA 323 0 +ATTGCGAAAGGCCTAAGC 323 0 +ATTGCGCTACCTTTGCAC 324 0 +ATTGCGCTGTTATCCCTA 325 0 +ATTGGAAGTCACTTGTAC 325 0 +ATTTACCATGCTCTGCCA 325 0 +ATTTATTTGTTTTCATAA 325 0 +ATTTCTCTTTCAGCTTTC 325 0 +ATTTGGTGCCTGAGCCGG 324 0 +ATTTTGACCGTGCAAAGG 324 0 +CAAAAACATCGCCTCCTG 324 0 +CAAAAACTCTTGGTGCAA 325 0 +CAAAAGAGTGGGAAGAGC 323 0 +CAAACCCCCCTACCCCCC 320 0 +CAAACGAACCCTTAATAG 323 0 +CAAACTCTTAGTTAACAG 325 0 +CAAACTGGGATTAGATAC 325 0 +CAAAGGCATGGTCCCGAC 321 0 +CAAAGGTAGCGCAATCAC 324 0 +CAACATCGAGGTCGTAAA 325 0 +CAACCAGCTATCACCAGG 325 0 +CAACCCGTCTCTGTGGCA 325 0 +CAACGAACCAAGTTACCC 325 0 +CAACGATTAAAGTCCTAC 320 0 +CAAGACTGTGTGATTGGA 322 0 +CAAGCAAAGAGACCTTTA 324 0 +CAAGCCAGCCACATAACC 322 0 +CAAGCCTCGCCTGTTTAC 325 0 +CAAGGGAAAGCTGAAAGA 323 0 +CAAGGGAATTCAGCAGTG 325 0 +CAAGTCGTAACACGGTAA 325 0 +CAAGTGACTTCCAATCAC 320 0 +CAAGTGCACCTTCCGGTA 323 0 +CAAGTTACCCTAGGGATA 325 0 +CAAGTTTTCACTTATGGC 325 0 +CAATCACTTGTCTTTTAA 324 0 +CAATCCTCTCCCAGAGTC 325 0 +CAATCTTTGGCTTAAAAG 324 0 +CAATGACTTGAAGAACCA 325 0 +CAATTCCTCCCTTTCTCG 324 0 +CAATTTACCATGCTCTGC 323 0 +CACAAAGGCATGGTCCCG 324 0 +CACAGAACATCTGACCAA 320 0 +CACAGAGACGGGTTGGCC 325 0 +CACAGGGTAAGCTGACGA 325 0 +CACATAACCACTCTGTCA 320 0 +CACATGCAAGTCTCCGCA 324 0 +CACCAATCTTTGGCTTAA 321 0 +CACCAGGTTCGGTAGGTC 323 0 +CACCATTAGGATGTCCTG 325 0 +CACCCCCAAGGGAATTCA 324 0 +CACCGCCCGTCACTCTCC 325 0 +CACCGCGGTTAGACGAGA 325 0 +CACCGTCAGGTCCTTTGG 324 0 +CACCTTCCGGTACACTTA 324 0 +CACGAGTTTTACCGGCCC 324 0 +CACGGTAAGTGTACCGGA 324 0 +CACGGTCAAAATACCGCG 323 0 +CACGTAGGACTTTAATCG 320 0 +CACTCTATCAAAGTGGTC 325 0 +CACTCTTTTGCCACAGAG 325 0 +CACTGGGCAGGCTGGACC 323 0 +CACTTACACCGAGAAGAC 323 0 +CACTTACCGTGTTACGAC 325 0 +CAGAAAGCGTTAAAGCTC 325 0 +CAGACCGGAGCAATCCAG 324 0 +CAGACCTACCGAACCTGG 323 0 +CAGAGCATGGTAAATTGC 310 0 +CAGAGTCCATATCGACGA 324 0 +CAGATAGAAACTGACCTG 323 0 +CAGCCACCGCGGTTAGAC 325 0 +CAGCCCCTTTAACAAAGG 324 0 +CAGCGAGCATCCATCTAC 324 0 +CAGCGCAATCCTCTCCCA 325 0 +CAGCTATCACCAGGTTCG 325 0 +CAGCTTACCCTGTGAAGG 325 0 +CAGCTTTCCCTTGCGGTA 324 0 +CAGCTTTCGTGGGGTCAG 323 0 +CAGGCTGGACCTCCTATA 324 0 +CAGGGTAAGCTGACGACG 325 0 +CAGGTCCTTTGGGTTTTA 325 0 +CAGGTCGAGGTGTAGCGC 325 0 +CAGGTGAGCTACCCCGAG 325 0 +CAGTTAAGCCCTCGTTTA 325 0 +CAGTTTCTATCTGTAACG 323 0 +CATAAGTGAAAACTTGAC 325 0 +CATACCCCGAACATGACG 324 0 +CATACTGGGGTGGCAGAG 323 0 +CATATATATATATATATA 309 0 +CATCACCCGTTATAGAAG 323 0 +CATCCTAATGGTGCAGCC 325 0 +CATCGAGGTCGTAAACCC 325 0 +CATCGGTCTTGTAATCCG 325 0 +CATCTCACTTACACCGAG 324 0 +CATCTGGTTCCTATTTCA 321 0 +CATGATGCAAAAGGTACA 325 0 +CATGCGCTACACCTCGAC 325 0 +CATGCTCTGCCACCCCAG 325 0 +CATGGTAAATTGCGAAAG 306 0 +CATGTGCTTGGTGGGCCA 324 0 +CATGTTCGGGGTATGGGC 325 0 +CATTAGGATGTCCTGATC 325 0 +CATTATCTTTCCTTGGGG 324 0 +CCAAAAACTCTTGGTGCA 325 0 +CCAAAGCCAGAATTCTAA 319 0 +CCAAAGGGTCTTCTCGTC 325 0 +CCAACATCGAGGTCGTAA 325 0 +CCAACCCGTCTCTGTGGC 325 0 +CCAAGACTGTGTGATTGG 322 0 +CCAAGAGTTTTTGGTTCC 323 0 +CCAAGCACATGCCTATCA 323 0 +CCAAGGAAAGATAATGAA 325 0 +CCAATCTTTGGCTTAAAA 324 0 +CCAATGACTTGAAGAACC 325 0 +CCACAGAACATCTGACCA 321 0 +CCACAGAGACGGGTTGGC 325 0 +CCACATAACCACTCTGTC 319 0 +CCACCAAGCACATGCCTA 323 0 +CCACCGCGGTTAGACGAG 325 0 +CCACCGTTGTTATTCAAC 317 0 +CCACTCTTTTGCCACAGA 325 0 +CCAGAGGTTCAAATCCTC 323 0 +CCAGAGTCCATATCGACG 324 0 +CCAGCCACATAACCACTC 321 0 +CCAGCCACCGCGGTTAGA 325 0 +CCAGCCTATATACCGCCG 323 0 +CCAGCGAGCATCCATCTA 324 0 +CCAGCTATCACCAGGTTC 325 0 +CCAGGTGAGCTACCCCGA 325 0 +CCATAAGTGAAAACTTGA 325 0 +CCATACCCCGAACATGAC 325 0 +CCATGCTCTGCCACCCCA 325 0 +CCCAAAGCCAGAATTCTA 320 0 +CCCAAGGAAAGATAATGA 325 0 +CCCAAGGGAATTCAGCAG 325 0 +CCCACTCTTTTGCCACAG 324 0 +CCCAGAGTCCATATCGAC 324 0 +CCCAGCCTATATACCGCC 323 0 +CCCATACCCCGAACATGA 325 0 +CCCCAACCGAAGGTAAAA 320 0 +CCCCAAGGGAATTCAGCA 325 0 +CCCCCAAGGGAATTCAGC 325 0 +CCCCCCTACCCCCCTACG 295 0 +CCCCCTACCCCCCTACGC 314 0 +CCCCGAACATGACGGTTA 324 0 +CCCCGAGACAGCCTATTA 322 0 +CCCCTCAGTTCCTAGAAA 319 0 +CCCGAACATGACGGTTAA 322 0 +CCCGCTCCTCGTCCCCGG 305 0 +CCCGGGGACGAGGAGCGG 305 0 +CCCGTCTCTGTGGCAAAA 325 0 +CCCTACCTGTGGCAATCA 324 0 +CCCTAGAGGAGCCTGTTC 325 0 +CCCTAGGGATAACAGCGC 325 0 +CCCTCCTCCGCCAATGAA 311 0 +CCCTGAGCCAAACAGCTA 324 0 +CCCTTAATAGCGGCTGCA 324 0 +CCGAACATGACGGTTAAA 323 0 +CCGAAGATCGGAGGTTAA 322 0 +CCGAGAAGACATCCATGC 324 0 +CCGAGACCAATGACTTGA 320 0 +CCGATCCTTTCGTACTAG 323 0 +CCGATCTTCGGATTACAA 321 0 +CCGCAAGGGAAAGCTGAA 324 0 +CCGCCAGGGTACTACGAG 325 0 +CCGCCCGTCACTCTCCCC 325 0 +CCGCCGTCGTCAGCTTAC 325 0 +CCGCGGTGGCTGGCACGA 325 0 +CCGCGGTTAGACGAGAGG 325 0 +CCGGAAGGTGCACTTGGA 323 0 +CCGGAGCAATCCAGGTCA 324 0 +CCGGCTCTGAGACGCGTA 324 0 +CCGGGTAGAAGTGACAGA 323 0 +CCGGTAAAACTCGTGCCA 324 0 +CCGGTCTGAACTCAGATC 325 0 +CCGTCTCTGTGGCAAAAG 325 0 +CCGTGCAAAGGTAGCGCA 324 0 +CCGTGGTCGCCCCAACCG 325 0 +CCGTTGTTATTCAACTAC 316 0 +CCTACAAACTCTTAGTTA 325 0 +CCTACCTGTGGCAATCAC 324 0 +CCTACGTGATCTGAGTTC 325 0 +CCTAGGGATAACAGCGCA 325 0 +CCTAGGGTAACTTGGTTC 325 0 +CCTAGTACGAAAGGATCG 323 0 +CCTCCGATCTTCGGATTA 320 0 +CCTCCGTGGTCGCCCCAA 325 0 +CCTCGATCCTACAAACTC 325 0 +CCTCGATGTTGGATCAGG 325 0 +CCTCGCCTGTTTACCAAA 325 0 +CCTCTCCCAGAGTCCATA 325 0 +CCTCTGATTTCGGCTCAG 324 0 +CCTCTGTCTTCGGGGCTA 312 0 +CCTGAAATAGGAACCAGA 321 0 +CCTGAGCCAAACAGCTAG 324 0 +CCTGATGCCCGCTCCTCG 321 0 +CCTGTATGAATGGCTAAA 325 0 +CCTGTTCTAGAACCGATA 325 0 +CCTTAACCACCCTTTACG 324 0 +CCTTAATAGCGGCTGCAC 324 0 +CCTTCCGGTACACTTACC 324 0 +CCTTCGGTTGGGGCGACC 325 0 +CCTTGGGGTTTAACCAAG 323 0 +CCTTTAGTTTGAAACCCC 324 0 +CCTTTCGTACTAGGAAAA 324 0 +CCTTTGGAGCTTAAGGTA 325 0 +CGAACCAAGTTACCCTAG 325 0 +CGAACCCTTAATAGCGGC 323 0 +CGAAGATCGGAGGTTAAA 322 0 +CGACCTCGATGTTGGATC 325 0 +CGACGGCGGTATATAGGC 324 0 +CGAGAAGACATCCATGCA 323 0 +CGAGAAGACCCTTTGGAG 325 0 +CGAGACCAATGACTTGAA 320 0 +CGAGGCTTGTGTTTGCCG 320 0 +CGAGGGCTTAACTGTCTC 325 0 +CGATATGGACTCTGGGAG 324 0 +CGATCCTACAAACTCTTA 325 0 +CGATCTTCGGATTACAAG 321 0 +CGATGTTGGATCAGGACA 325 0 +CGATGTTTTTGGTAAACA 324 0 +CGCAAGGGAAAGCTGAAA 324 0 +CGCAATCCTCTCCCAGAG 325 0 +CGCCAGGGTACTACGAGC 325 0 +CGCCCCAACCGAAGGTAA 325 0 +CGCCGTCGTCAGCTTACC 325 0 +CGCCTGTTTACCAAAAAC 325 0 +CGCGGTGGCTGGCACGAG 325 0 +CGCGGTTAGACGAGAGGC 322 0 +CGCTACACCTCGACCTGA 325 0 +CGCTACCTTTGCACGGTC 324 0 +CGCTTAGCTGTTAACTAA 325 0 +CGGAGACTTGCATGTGTA 324 0 +CGGAGCAATCCAGGTCAG 324 0 +CGGCCGCGGTATTTTGAC 323 0 +CGGCGTAAAGGGTGGTTA 324 0 +CGGCTCAGGCACCAAATA 324 0 +CGGCTCTGAGACGCGTAC 324 0 +CGGGAAAAGCCCCGGCAG 321 0 +CGGGCCCATACCCCGAAC 323 0 +CGGGGACGAGGAGCGGGC 304 0 +CGGGGCTACAACCCACCG 322 0 +CGGGGTATGGGCCCGAAA 323 0 +CGGGGTTTCAAACTAAAG 325 0 +CGGGTAGAAGTGACAGAC 322 0 +CGGTAAAACTCGTGCCAG 324 0 +CGGTAAGTGTACCGGAAG 324 0 +CGGTAGGTCTGTCACTTC 321 0 +CGGTATTTTGACCGTGCA 324 0 +CGGTCAAAATACCGCGGC 323 0 +CGGTCTGAACTCAGATCA 325 0 +CGGTCTTGTAATCCGAAG 325 0 +CGGTGTGTACGCGTCTCA 324 0 +CGGTTAGACGAGAGGCCC 322 0 +CGGTTCTAGAACAGGCTC 325 0 +CGTAACACGGTAAGTGTA 325 0 +CGTACACACCGCCCGTCA 325 0 +CGTCAGCTTACCCTGTGA 325 0 +CGTCAGGTCGAGGTGTAG 325 0 +CGTCTCAGAGCCGGGTTC 324 0 +CGTCTCTGTGGCAAAAGA 325 0 +CGTGATCTGAGTTCAGAC 325 0 +CGTGCAAAGGTAGCGCAA 324 0 +CGTGGTCGCCCCAACCGA 325 0 +CGTTAAAGCTCAGACAGA 325 0 +CGTTGAACAAACGAACCC 325 0 +CGTTGTTATTCAACTACA 317 0 +CGTTTAGCCATTCATACA 325 0 +CGTTTGTTCAACGATTAA 325 0 +CTAAAATGAGTAACAAGA 325 0 +CTAAATCATGATGCAAAA 325 0 +CTAAATCCTCCTTCAAGC 324 0 +CTAACCGCGGTGGCTGGC 325 0 +CTAAGAGTTTGTAGGATC 325 0 +CTAATCCCAGTTTGTTTC 325 0 +CTAATGGTGCAGCCGCTA 324 0 +CTACAAACTCTTAGTTAA 325 0 +CTACAACCCACCGCCTAA 320 0 +CTACCCCGAGACAGCCTA 323 0 +CTACCGAACCTGGTGATA 325 0 +CTACCTTTGCACGGTCAA 324 0 +CTACGTGATCTGAGTTCA 325 0 +CTACTAAATCCTCCTTCA 324 0 +CTAGAAAGATTATGAGCC 275 0 +CTAGAACAGGCTCCTCTA 325 0 +CTAGAGGAGCCTGTTCTA 325 0 +CTAGGGATAACAGCGCAA 325 0 +CTAGTGGGCCTAAAAGCA 324 0 +CTATATACCGCCGTCGTC 324 0 +CTATCACCAGGTTCGGTA 325 0 +CTATTCCGGCTCAGGCAC 324 0 +CTCAAGAGATCAAAACTC 320 0 +CTCACTTACACCGAGAAG 323 0 +CTCAGAGCCGGGTTCAAA 324 0 +CTCATAATCTTTCTAGTA 275 0 +CTCCCAAAGCCAGAATTC 320 0 +CTCCGATCTTCGGATTAC 321 0 +CTCCGGGTAGAAGTGACA 323 0 +CTCCGGTCTGAACTCAGA 325 0 +CTCCGTGGTCGCCCCAAC 325 0 +CTCGATGTTGGATCAGGA 325 0 +CTCGCCTGTTTACCAAAA 325 0 +CTCTGAGACGCGTACACA 324 0 +CTCTGATTTCGGCTCAGA 324 0 +CTCTGTCTTCGGGGCTAC 312 0 +CTCTTAGTTAACAGCTAA 325 0 +CTCTTCCCACTCTTTTGC 323 0 +CTCTTGGTGCAAATCCAA 324 0 +CTCTTTTGCCACAGAGAC 325 0 +CTGAACTCAGATCACGTA 325 0 +CTGAGACACCGTCAGGTC 325 0 +CTGAGACGCGTACACACC 324 0 +CTGAGCCAAACAGCTAGC 324 0 +CTGAGCTTTAACGCTTTC 325 0 +CTGAGTTCAGACCGGAGC 325 0 +CTGCACCATTAGGATGTC 325 0 +CTGCCACCCCAGTATGTC 316 0 +CTGCTTTTAGGCCCACTA 324 0 +CTGGCTAAATCATGATGC 325 0 +CTGGCTCCCAAAGCCAGA 307 0 +CTGGGATTAGATACCCCA 325 0 +CTGGGCAGGCTGGACCTC 324 0 +CTGGTGATAGCTGGTTGC 325 0 +CTGGTTTCAAGCCAGCCA 322 0 +CTGTATGAATGGCTAAAC 325 0 +CTGTCACTTCTACCCGGA 323 0 +CTGTCTCGGGGTAGCTCA 324 0 +CTGTCTGAGCTTTAACGC 325 0 +CTGTCTTCGGGGCTACAA 315 0 +CTGTGTGATTGGAAGTCA 319 0 +CTGTTATCCCTAGGGTAA 325 0 +CTGTTCTAGAACCGATAA 325 0 +CTGTTCTAGTGGGCCTAA 317 0 +CTGTTTACCAAAAACATC 324 0 +CTTAAAACCCAAAGGACC 325 0 +CTTAACCACCCTTTACGC 324 0 +CTTAAGCTCCAAAGGGTC 325 0 +CTTAATAGCGGCTGCACC 324 0 +CTTACACATGCAAGTCTC 325 0 +CTTAGCTGTTAACTAAGA 325 0 +CTTAGGAACCAAAAACTC 323 0 +CTTAGGCAACCAGCTATC 324 0 +CTTAGTTAACAGCTAAGC 325 0 +CTTCAAGTCATTGGTCTC 325 0 +CTTCACAGGGTAAGCTGA 325 0 +CTTCCCACTCTTTTGCCA 324 0 +CTTCGGGGCTACAACCCA 319 0 +CTTCGGTTGGGGCGACCA 325 0 +CTTGGATTTGCACCAAGA 324 0 +CTTGGGGTTTAACCAAGA 323 0 +CTTGTAGTTGAATAACAA 318 0 +CTTGTGTTTGCCGAGTTC 323 0 +CTTGTTACTCATTTTAGC 325 0 +CTTTAATCGTTGAACAAA 325 0 +CTTTAGAATCACAATCTA 322 0 +CTTTCAGCTTTCCCTTGC 323 0 +CTTTCCTTGGGGTTTAAC 323 0 +CTTTCGGGCCCATACCCC 323 0 +CTTTCGTACTAGGAAAAG 323 0 +CTTTGCACGGTCAAAATA 324 0 +CTTTGGAGCTTAAGGTAC 325 0 +CTTTGGCTTAAAAGGCCA 324 0 +CTTTTAGGCCCACTAGAA 323 0 +CTTTTCTTTTAGTCTTTC 323 0 +CTTTTGCATCATGATTTA 325 0 +GAAAATGTAGCCCATTTC 325 0 +GAAACTGACCTGGATTGC 325 0 +GAAAGATTATGAGCCTCA 283 0 +GAAAGGGAGGAATTGAAC 325 0 +GAAATAGGAACCAGATGC 322 0 +GAACAAACGAACCCTTAA 324 0 +GAACCACCGTTGTTATTC 317 0 +GAAGAACCACCGTTGTTA 324 0 +GAAGATTTTCACTTCTAC 322 0 +GAAGCTAAATATTGGACA 296 0 +GAAGGAACTCGGCAAACA 323 0 +GAAGGCCTCGATCCTACA 325 0 +GAATAGAAGTTCAGCCTC 324 0 +GAATTCAGCAGTGATAAA 325 0 +GACAAGTGATTGCGCTAC 324 0 +GACCAATGACTTGAAGAA 325 0 +GACCTCGATGTTGGATCA 325 0 +GACCTCTGATTTCGGCTC 324 0 +GACCTGTATGAATGGCTA 325 0 +GACCTTTAGTTTGAAACC 324 0 +GACGAGGAGCGGGCATCA 312 0 +GACGCGTACACACCGCCC 325 0 +GACGGGCGGTGTGTACGC 325 0 +GACGGTGTCTCAGACCCC 323 0 +GACTTTAATCGTTGAACA 320 0 +GAGAAAGGGAGGAATTGA 325 0 +GAGAAGACATCCATGCAA 324 0 +GAGAAGACCCTTTGGAGC 325 0 +GAGACCTGTATGAATGGC 325 0 +GAGACCTTTAGTTTGAAA 324 0 +GAGACGCGTACACACCGC 324 0 +GAGAGGATTGCGCTGTTA 325 0 +GAGCATGGTAAATTGCGA 306 0 +GAGCGCTTAGCTGTTAAC 325 0 +GAGCGGGCATCAGGCACA 322 0 +GAGCTACCCCGAGACAGC 324 0 +GAGGAGCCTGTTCTAGAA 325 0 +GAGGAGCGGGCATCAGGC 321 0 +GAGGATTGCGCTGTTATC 325 0 +GAGGATTTAGTAGTAAAA 325 0 +GAGGCGATGTTTTTGGTA 325 0 +GAGGCTTGTGTTTGCCGA 320 0 +GAGGGCTTAACTGTCTCC 325 0 +GAGTTCAGACCGGAGCAA 325 0 +GATACCCCACTATGCTCA 325 0 +GATAGAAACTGACCTGGA 323 0 +GATATGGACTCTGGGAGA 325 0 +GATCAACGAACCAAGTTA 325 0 +GATCACGTAGGACTTTAA 325 0 +GATCCTTTCGTACTAGGA 323 0 +GATCTTCGGATTACAAGA 321 0 +GATGCCAGTAATAGTTCA 322 0 +GATGCCCGCTCCTCGTCC 308 0 +GATGTCTTCTCGGTGTAA 323 0 +GATTAAAGTCCTACGTGA 320 0 +GATTGCGCTACCTTTGCA 324 0 +GATTGCTCCGGTCTGAAC 325 0 +GATTGGAAGTCACTTGTA 325 0 +GATTTTCACTTCTACTTA 324 0 +GCAAACACAAGCCTCGCC 320 0 +GCAAAGGTAGCGCAATCA 324 0 +GCAATCACTTGTCTTTTA 324 0 +GCACAAAGGCATGGTCCC 324 0 +GCACAAGTGTAAGCCAAA 324 0 +GCACGAGTTTTACCGGCC 324 0 +GCACGGTCAAAATACCGC 324 0 +GCACTGAAGATGCTAAGA 325 0 +GCATAGCACTGAAGATGC 324 0 +GCATAGTGGGGTATCTAA 325 0 +GCATCACCCGTTATAGAA 323 0 +GCATCGGTCTTGTAATCC 325 0 +GCATCTCACTTACACCGA 324 0 +GCATGGTAAATTGCGAAA 307 0 +GCATGTGCTTGGTGGGCC 324 0 +GCCACACCCCCAAGGGAA 324 0 +GCCACAGAACATCTGACC 321 0 +GCCACCGCGGTTAGACGA 325 0 +GCCAGGGTACTACGAGCA 325 0 +GCCCACCAAGCACATGCC 323 0 +GCCCCAACCGAAGGTAAA 325 0 +GCCGGGGCTTTTCCCGCC 310 0 +GCCGGTAAAACTCGTGCC 324 0 +GCCGTCGTCAGCTTACCC 325 0 +GCCTCGCCTGTTTACCAA 325 0 +GCCTGTTTACCAAAAACA 325 0 +GCCTTTCGCAATTTACCA 303 0 +GCGAGGCTTGTGTTTGCC 320 0 +GCGATGTTTTTGGTAAAC 324 0 +GCGCAATCCTCTCCCAGA 325 0 +GCGCTTAGCTGTTAACTA 325 0 +GCGGCTGCACCATTAGGA 324 0 +GCGGGAAAAGCCCCGGCA 319 0 +GCGGTGGGTTGTAGCCCC 322 0 +GCGGTTAGACGAGAGGCC 322 0 +GCTAAATCATGATGCAAA 325 0 +GCTACAACCCACCGCCTA 322 0 +GCTACACCTCGACCTGAC 325 0 +GCTACCTTTGCACGGTCA 324 0 +GCTCTGAGACGCGTACAC 324 0 +GCTGAAAGAGAAATGAAA 325 0 +GCTGACGACGGCGGTATA 325 0 +GCTGAGCATAGTGGGGTA 325 0 +GCTGGCACGAGTTTTACC 324 0 +GCTGGCTTGAAACCAGCA 323 0 +GCTGGTTTCAAGCCAGCC 323 0 +GCTGTTATCCCTAGGGTA 325 0 +GCTTAAAACCCAAAGGAC 325 0 +GCTTTCGGGCCCATACCC 323 0 +GCTTTTAGGCCCACTAGA 323 0 +GGAAAGACTAAAAGAAAA 323 0 +GGAAAGCTGAAAGAGAAA 325 0 +GGAACCAGATGCCAGTAA 322 0 +GGAACTCGGCAAACACAA 323 0 +GGAAGGAACTCGGCAAAC 324 0 +GGAAGGCCTCGATCCTAC 325 0 +GGAATTCAGCAGTGATAA 325 0 +GGACATACTGGGGTGGCA 312 0 +GGACATCCTAATGGTGCA 325 0 +GGACCATGCCTTTGTGCA 323 0 +GGACCTGACGGTGTCTCA 325 0 +GGACTTTAATCGTTGAAC 320 0 +GGAGACTTGCATGTGTAA 325 0 +GGAGCCTGTTCTAGAACC 325 0 +GGAGCGGGCATCAGGCAC 321 0 +GGAGCTTAAGGTACAAAA 325 0 +GGAGGAATTGAACCCCCA 325 0 +GGAGGATAAAGATCATAA 325 0 +GGAGGATTTAGTAGTAAA 325 0 +GGAGGTCCAGCCTGCCCA 324 0 +GGATGTCCTGATCCAACA 325 0 +GGATGTCTTCTCGGTGTA 323 0 +GGATTAGATACCCCACTA 325 0 +GGATTGCGCTGTTATCCC 325 0 +GGATTGCTCCGGTCTGAA 324 0 +GGCAAAAGAGTGGGAAGA 323 0 +GGCAACCAGCTATCACCA 325 0 +GGCAAGCCTACGAAAAAC 324 0 +GGCAGGCTGGACCTCCTA 324 0 +GGCATGGTCCCGACCTTA 321 0 +GGCCGCGGTATTTTGACC 323 0 +GGCCTCGATCCTACAAAC 325 0 +GGCCTTTCGCAATTTACC 310 0 +GGCCTTTTAAGCCAAAGA 324 0 +GGCGATGTTTTTGGTAAA 324 0 +GGCGGTGGGTTGTAGCCC 323 0 +GGCGTAAAGGGTGGTTAA 324 0 +GGCTAAATCATGATGCAA 325 0 +GGCTCAGGCACCAAATAC 324 0 +GGCTCTGAGACGCGTACA 324 0 +GGCTGCTTTTAGGCCCAC 324 0 +GGCTGGCACGAGTTTTAC 325 0 +GGCTTAGGCCTTTCGCAA 325 0 +GGGAAAGCTGAAAGAGAA 325 0 +GGGAAGGCCTCGATCCTA 325 0 +GGGAATTCAGCAGTGATA 325 0 +GGGAGGAATTGAACCCCC 325 0 +GGGAGGCAAGTCGTAACA 325 0 +GGGCCCATACCCCGAACA 325 0 +GGGCGACCACGGAGGAAA 323 0 +GGGCTTAGGCCTTTCGCA 325 0 +GGGGACGAGGAGCGGGCA 305 0 +GGGGAGGCAAGTCGTAAC 325 0 +GGGGCGACCACGGAGGAA 324 0 +GGGGGTACAGCCCCTTTA 325 0 +GGGGTACAGCCCCTTTAA 325 0 +GGGGTTAAAATCCCCTCA 325 0 +GGGTACAGCCCCTTTAAC 325 0 +GGGTAGAAGTGACAGACC 322 0 +GGGTCTGAGACACCGTCA 324 0 +GGGTCTTCTCGTCTTGTA 325 0 +GGGTGGCAGAGCATGGTA 325 0 +GGGTTATCGGTTCTAGAA 325 0 +GGGTTGTAGCCCCGAAGA 319 0 +GGTAACTTGGTTCGTTGA 325 0 +GGTACACTTACCGTGTTA 324 0 +GGTACAGCCCCTTTAACA 325 0 +GGTATTTTGACCGTGCAA 324 0 +GGTCAGTTTCTATCTGTA 323 0 +GGTCTGAACTCAGATCAC 325 0 +GGTCTTAGGAACCAAAAA 325 0 +GGTCTTGTAATCCGAAGA 325 0 +GGTGAGCTACCCCGAGAC 325 0 +GGTGCCTGAGCCGGAATA 324 0 +GGTGGCAGAGCATGGTAA 325 0 +GGTGGGTTGTAGCCCCGA 323 0 +GGTTAAACCCCAAGGAAA 323 0 +GGTTATCGGTTCTAGAAC 325 0 +GGTTCGGTAGGTCTGTCA 322 0 +GGTTCGTTTGTTCAACGA 325 0 +GGTTCTTGTAGTTGAATA 319 0 +GGTTGGGGCGACCACGGA 325 0 +GGTTGTAGCCCCGAAGAC 319 0 +GGTTTCAAGCCAGCCACA 324 0 +GGTTTCGGGGTTTCAAAC 324 0 +GTAACACGGTAAGTGTAC 325 0 +GTACACACCGCCCGTCAC 325 0 +GTACAGCCCCTTTAACAA 325 0 +GTACCTTTTGCATCATGA 325 0 +GTACGAAAGGATCGGAAA 325 0 +GTAGAAGTGACAGACCTA 323 0 +GTAGGTCTGTCACTTCTA 321 0 +GTAGTACCCTGGCGGACA 325 0 +GTATGAATGGCTAAACGA 325 0 +GTATTTTGACCGTGCAAA 324 0 +GTCACTGGGCAGGCTGGA 323 0 +GTCAGCTTACCCTGTGAA 325 0 +GTCAGTTTCTATCTGTAA 323 0 +GTCATTGGTCTCGGTTAA 318 0 +GTCCAGCCTGCCCAGTGA 323 0 +GTCCTGATCCAACATCGA 325 0 +GTCTCAGAGCCGGGTTCA 324 0 +GTCTTAGGAACCAAAAAC 325 0 +GTCTTGGTTAAACCCCAA 323 0 +GTGACAGACCTACCGAAC 323 0 +GTGACTTCCAATCACACA 317 0 +GTGAGCTACCCCGAGACA 325 0 +GTGATAGCTGGTTGCCTA 325 0 +GTGCACCTTCCGGTACAC 323 0 +GTGCTTCCTTTACACCAC 323 0 +GTGCTTGGTGGGCCATTA 325 0 +GTGGCAAAAGAGTGGGAA 324 0 +GTGGCAGAGCATGGTAAA 324 0 +GTGGCTGGCTTGAAACCA 323 0 +GTGGGGTATCTAATCCCA 325 0 +GTGGGTTGTAGCCCCGAA 322 0 +GTGGTCGCCCCAACCGAA 325 0 +GTGTGTACGCGTCTCAGA 324 0 +GTTAACTAAGAGTTTGTA 325 0 +GTTACAGATAGAAACTGA 323 0 +GTTACCCTAGGGATAACA 325 0 +GTTACTCATTTTAGCATA 325 0 +GTTATCGGTTCTAGAACA 325 0 +GTTATGTGGCTGGCTTGA 322 0 +GTTATTCAACTACAAGAA 321 0 +GTTCCTAGAAAGAAGGGA 317 0 +GTTCTAGTGGGCCTAAAA 319 0 +GTTCTTGTAGTTGAATAA 320 0 +GTTGTAGCCCCGAAGACA 315 0 +GTTGTTATTCAACTACAA 318 0 +GTTTGTTCAACGATTAAA 325 0 +GTTTTCACTTATGGCTTA 323 0 +TAAAACTCGTGCCAGCCA 325 0 +TAAAATGAGTAACAAGAA 325 0 +TAAATCCTCCTTCAAGCA 323 0 +TAAATTGCGAAAGGCCTA 317 0 +TAACAGCTAAGCGCTCAA 325 0 +TAACCGCGGTGGCTGGCA 325 0 +TAACCTCCGATCTTCGGA 321 0 +TAACGAGAAAGGGAGGAA 324 0 +TAAGCCAAAGATTGGTGA 322 0 +TAAGGGTTCGTTTGTTCA 324 0 +TAATCGTTGAACAAACGA 325 0 +TACAGCCCCTTTAACAAA 325 0 +TACCTTCGGTTGGGGCGA 325 0 +TACCTTTGCACGGTCAAA 324 0 +TACGAAAGGATCGGAAAA 325 0 +TACGACCTCGATGTTGGA 325 0 +TACTGGGGTGGCAGAGCA 323 0 +TAGAATCACAATCTAATA 320 0 +TAGCACTGAAGATGCTAA 325 0 +TAGCTGGTTGCCTAAGAA 324 0 +TAGGACTTTAATCGTTGA 320 0 +TAGGATGTCCTGATCCAA 325 0 +TAGTACAAGTGACTTCCA 324 0 +TAGTACGAAAGGATCGGA 324 0 +TAGTTTGAAACCCCGAAA 324 0 +TATATACCGCCGTCGTCA 324 0 +TATATATATATATATATA 312 0 +TATGAAAACAAATAAATA 325 0 +TATGCTGGTTTCAAGCCA 324 0 +TATGTGGCTGGCTTGAAA 323 0 +TATTTAAAAGACAAGTGA 323 0 +TCACTTACACCGAGAAGA 323 0 +TCAGACCGGAGCAATCCA 324 0 +TCATTGGCGGAGGAGGGA 306 0 +TCATTGGTCTCGGTTAAA 322 0 +TCCCACTCTTTTGCCACA 324 0 +TCCCAGAGTCCATATCGA 324 0 +TCCGATCTTCGGATTACA 321 0 +TCCGGCTCAGGCACCAAA 323 0 +TCCTCCGTGGTCGCCCCA 324 0 +TCCTTGGGGTTTAACCAA 323 0 +TCCTTTCGTACTAGGAAA 324 0 +TCGCCTGTTTACCAAAAA 325 0 +TCGGGCCCATACCCCGAA 323 0 +TCGGGGTATGGGCCCGAA 322 0 +TCGGGGTTTCAAACTAAA 325 0 +TCGGTCTTGTAATCCGAA 324 0 +TCTCACTTACACCGAGAA 323 0 +TCTCAGAGCCGGGTTCAA 324 0 +TCTGATTTCGGCTCAGAA 324 0 +TCTGTCTTCGGGGCTACA 313 0 +TCTTGTAGTTGAATAACA 321 0 +TCTTTCCTTGGGGTTTAA 323 0 +TGAGCGCTTAGCTGTTAA 325 0 +TGAGTTCAGACCGGAGCA 325 0 +TGATAGCTGGTTGCCTAA 324 0 +TGCATCATGATTTAGCCA 325 0 +TGCTAAAATGAGTAACAA 325 0 +TGGAGCTTAAGGTACAAA 325 0 +TGGATCAGGACATCCTAA 325 0 +TGGCAAGCCTACGAAAAA 324 0 +TGGCTGCTTTTAGGCCCA 325 0 +TGGTCTTAGGAACCAAAA 325 0 +TGGTGCAGCCGCTATTAA 324 0 +TGGTTAAACCCCAAGGAA 323 0 +TGGTTTCGGGGTTTCAAA 324 0 +TGTACCTTAAGCTCCAAA 325 0 +TTAAGCCATAAGTGAAAA 323 0 +TTAGATTGTGATTCTAAA 321 0 +TTAGTTTGAAACCCCGAA 325 0 +TTATGTGGCTGGCTTGAA 322 0 +TTCCCTTCTTTCTAGGAA 323 0 +TTCCGGCTCAGGCACCAA 323 0 +TTGGAGCTTAAGGTACAA 325 0 +TTGGCCTTTTAAGCCAAA 324 0 +TTGGTCTTAGGAACCAAA 325 0 +TTTATGAAAACAAATAAA 325 0 diff --git a/pkg/obiprimer/testdata/ecoprimers_Teleostei.words.tsv b/pkg/obiprimer/testdata/ecoprimers_Teleostei.words.tsv new file mode 100644 index 0000000..89319e3 --- /dev/null +++ b/pkg/obiprimer/testdata/ecoprimers_Teleostei.words.tsv @@ -0,0 +1,255 @@ +AAAACTCGTGCCAGCCAC 3891 0 +AAACCCAAAGGACTTGGC 3890 0 +AAACGAACCCTTAATAGC 3875 0 +AAACTCGTGCCAGCCACC 3894 0 +AAACTGGGATTAGATACC 3896 0 +AAAGTCCTACGTGATCTG 3901 0 +AAATCATGATGCAAAAGG 3894 0 +AACAAACGAACCCTTAAT 3888 0 +AACATCGAGGTCGTAAAC 3893 0 +AACATGGTAAGTGTACCG 3894 0 +AACCAAAAACTCTTGGTG 3854 0 +AACCCAAAGGACTTGGCG 3888 0 +AACCCTTAATAGCGGCTG 3858 0 +AACCGCGGTGGCTGGCAC 3896 0 +AACGAACCCTTAATAGCG 3860 0 +AACGATTAAAGTCCTACG 3700 0 +AACGTCAGGTCGAGGTGT 3894 0 +AACTCAGATCACGTAGGA 3899 0 +AACTCGTGCCAGCCACCG 3893 0 +AACTGGGATTAGATACCC 3896 0 +AAGAGTTTTTGGTTCCTA 3848 0 +AAGCCTCGCCTGTTTACC 3876 0 +AAGGGTTCGTTTGTTCAA 3889 0 +AAGGTAGCGCAATCACTT 3889 0 +AAGTCCTACGTGATCTGA 3900 0 +AAGTCGTAACATGGTAAG 3893 0 +AAGTGCACCTTCCGGTAC 3888 0 +AAGTGTACCGGAAGGTGC 3889 0 +AATAGCGGCTGCACCATT 3839 0 +AATCACTTGTCTTTTAAA 3856 0 +AATCATGATGCAAAAGGT 3893 0 +AATCCAGGTCAGTTTCTA 3903 0 +AATCGTTGAACAAACGAA 3876 0 +AATGTAGCCCATTTCTTC 3871 0 +ACAAACGAACCCTTAATA 3885 0 +ACAAACTGGGATTAGATA 3893 0 +ACAAGTGATTGCGCTACC 3889 0 +ACACACCGCCCGTCACTC 3891 0 +ACACCGCCCGTCACTCTC 3890 0 +ACACTTACCATGTTACGA 3893 0 +ACATCCTAATGGTGCAGC 3858 0 +ACATCGAGGTCGTAAACC 3894 0 +ACATGGTAAGTGTACCGG 3894 0 +ACCAAAAACTCTTGGTGC 3875 0 +ACCAAGAGTTTTTGGTTC 3857 0 +ACCATGTTACGACTTGCC 3891 0 +ACCATTAGGATGTCCTGA 3891 0 +ACCCAAAGGACTTGGCGG 3886 0 +ACCCGTCTCTGTGGCAAA 3892 0 +ACCCTAGGGATAACAGCG 3895 0 +ACCCTTAATAGCGGCTGC 3861 0 +ACCGCCCGTCACTCTCCC 3893 0 +ACCGCGGTGGCTGGCACG 3895 0 +ACCGGAAGGTGCACTTGG 3887 0 +ACCTCGATGTTGGATCAG 3891 0 +ACCTTCCGGTACACTTAC 3893 0 +ACGAACCCTTAATAGCGG 3859 0 +ACGACCTCGATGTTGGAT 3889 0 +ACGATTAAAGTCCTACGT 3701 0 +ACGTCAGGTCGAGGTGTA 3894 0 +ACGTGATCTGAGTTCAGA 3901 0 +ACTCAGATCACGTAGGAC 3899 0 +ACTCGTGCCAGCCACCGC 3894 0 +ACTCTTTTGCCACAGAGA 3867 0 +ACTGGGATTAGATACCCC 3896 0 +ACTTACCATGTTACGACT 3894 0 +ACTTTAATCGTTGAACAA 3876 0 +AGAACAGGCTCCTCTAGG 3893 0 +AGACAAGTGATTGCGCTA 3883 0 +AGAGGAGCCTGTTCTAGA 3876 0 +AGAGTGACGGGCGGTGTG 3890 0 +AGAGTTTTTGGTTCCTAA 3851 0 +AGATAGAAACTGACCTGG 3900 0 +AGATCACGTAGGACTTTA 3901 0 +AGCCGCTATTAAGGGTTC 3856 0 +AGCCTCGCCTGTTTACCA 3889 0 +AGCCTGTTCTAGAACCGA 3871 0 +AGCGGCTGCACCATTAGG 3846 0 +AGGAACCAAAAACTCTTG 3842 0 +AGGACATCCTAATGGTGC 3866 0 +AGGACTTTAATCGTTGAA 3688 0 +AGGAGCCTGTTCTAGAAC 3874 0 +AGGATGTCCTGATCCAAC 3897 0 +AGGCAAGTCGTAACATGG 3868 0 +AGGCGATGTTTTTGGTAA 3894 0 +AGGGATAACAGCGCAATC 3896 0 +AGGGGAGGCAAGTCGTAA 3890 0 +AGGGGGTTTACGACCTCG 3894 0 +AGGGTTCGTTTGTTCAAC 3888 0 +AGGTAGCGCAATCACTTG 3888 0 +AGTCCTACGTGATCTGAG 3900 0 +AGTGGGGTATCTAATCCC 3896 0 +AGTTACCCTAGGGATAAC 3895 0 +AGTTTTTGGTTCCTAAGA 3848 0 +ATAGAAACTGACCTGGAT 3901 0 +ATAGCGGCTGCACCATTA 3837 0 +ATAGTGGGGTATCTAATC 3895 0 +ATCACGTAGGACTTTAAT 3880 0 +ATCACTTGTCTTTTAAAT 3856 0 +ATCAGGACATCCTAATGG 3892 0 +ATCATGATGCAAAAGGTA 3894 0 +ATCCTAATGGTGCAGCCG 3852 0 +ATCGAGGTCGTAAACCCC 3894 0 +ATCGGTTCTAGAACAGGC 3862 0 +ATCGTTGAACAAACGAAC 3877 0 +ATCTGAGTTCAGACCGGA 3902 0 +ATGATGCAAAAGGTACGA 3891 0 +ATGGTAAGTGTACCGGAA 3895 0 +ATGGTGCAGCCGCTATTA 3840 0 +ATGTAGCCCATTTCTTCC 3871 0 +ATGTCCTGATCCAACATC 3892 0 +ATGTTACGACTTGCCTCC 3864 0 +ATGTTGGATCAGGACATC 3893 0 +ATGTTTTTGGTAAACAGG 3887 0 +ATTAGATACCCCACTATG 3896 0 +ATTAGGATGTCCTGATCC 3893 0 +ATTGCGCTGTTATCCCTA 3896 0 +CAAAAACTCTTGGTGCAA 3886 0 +CAAACGAACCCTTAATAG 3882 0 +CAAACTGGGATTAGATAC 3895 0 +CAACATCGAGGTCGTAAA 3894 0 +CAACGATTAAAGTCCTAC 3691 0 +CAAGTCGTAACATGGTAA 3893 0 +CAAGTGCACCTTCCGGTA 3888 0 +CACCATTAGGATGTCCTG 3890 0 +CACCGCCCGTCACTCTCC 3888 0 +CACCTTCCGGTACACTTA 3891 0 +CACGTAGGACTTTAATCG 3702 0 +CACTCTTTTGCCACAGAG 3856 0 +CACTTACCATGTTACGAC 3894 0 +CATCCTAATGGTGCAGCC 3852 0 +CATCGAGGTCGTAAACCC 3894 0 +CATGATGCAAAAGGTACG 3890 0 +CATGGTAAGTGTACCGGA 3894 0 +CATGTTACGACTTGCCTC 3866 0 +CATTAGGATGTCCTGATC 3893 0 +CCAAAAACTCTTGGTGCA 3883 0 +CCAACATCGAGGTCGTAA 3894 0 +CCAAGAGTTTTTGGTTCC 3844 0 +CCACTCTTTTGCCACAGA 3879 0 +CCCACTCTTTTGCCACAG 3879 0 +CCCGTCTCTGTGGCAAAA 3894 0 +CCCTAGGGATAACAGCGC 3898 0 +CCCTTAATAGCGGCTGCA 3853 0 +CCGCCCGTCACTCTCCCC 3890 0 +CCGCGGTGGCTGGCACGA 3895 0 +CCGGTAAAACTCGTGCCA 3847 0 +CCGGTCTGAACTCAGATC 3904 0 +CCGTCTCTGTGGCAAAAG 3894 0 +CCTACGTGATCTGAGTTC 3899 0 +CCTAGGGATAACAGCGCA 3896 0 +CCTCGATGTTGGATCAGG 3892 0 +CCTCGCCTGTTTACCAAA 3892 0 +CCTGTTCTAGAACCGATA 3862 0 +CCTTAATAGCGGCTGCAC 3850 0 +CCTTCCGGTACACTTACC 3893 0 +CGAACCCTTAATAGCGGC 3855 0 +CGACCTCGATGTTGGATC 3890 0 +CGACGGCGGTATATAGGC 3890 0 +CGATGTTGGATCAGGACA 3892 0 +CGATGTTTTTGGTAAACA 3886 0 +CGCCTGTTTACCAAAAAC 3889 0 +CGCGGTGGCTGGCACGAG 3894 0 +CGCTTAGCTGTTAACTAA 3864 0 +CGGTAAAACTCGTGCCAG 3856 0 +CGGTCTGAACTCAGATCA 3904 0 +CGGTTCTAGAACAGGCTC 3873 0 +CGTAACATGGTAAGTGTA 3892 0 +CGTCAGGTCGAGGTGTAG 3893 0 +CGTCTCTGTGGCAAAAGA 3891 0 +CGTGATCTGAGTTCAGAC 3902 0 +CGTTGAACAAACGAACCC 3887 0 +CGTTTGTTCAACGATTAA 3877 0 +CTAAATCATGATGCAAAA 3896 0 +CTAATGGTGCAGCCGCTA 3840 0 +CTACGTGATCTGAGTTCA 3899 0 +CTAGAACAGGCTCCTCTA 3877 0 +CTAGAGGAGCCTGTTCTA 3891 0 +CTAGGGATAACAGCGCAA 3897 0 +CTCCGGTCTGAACTCAGA 3903 0 +CTCGATGTTGGATCAGGA 3892 0 +CTCGCCTGTTTACCAAAA 3892 0 +CTCGTACCTTTTGCATCA 3889 0 +CTCTTTTGCCACAGAGAC 3869 0 +CTGAACTCAGATCACGTA 3900 0 +CTGCACCATTAGGATGTC 3861 0 +CTGGGATTAGATACCCCA 3895 0 +CTGTTATCCCTAGGGTAA 3897 0 +CTGTTTACCAAAAACATC 3886 0 +CTTAATAGCGGCTGCACC 3848 0 +CTTAGCTGTTAACTAAGA 3878 0 +CTTAGGAACCAAAAACTC 3847 0 +CTTAGTTAACAGCTAAGC 3875 0 +CTTCCGGTACACTTACCA 3891 0 +CTTGGATTTGCACCAAGA 3899 0 +CTTTAATCGTTGAACAAA 3876 0 +CTTTTGCATCATGATTTA 3896 0 +GAACAAACGAACCCTTAA 3890 0 +GAAGGAACTCGGCAAACA 3889 0 +GACAAGTGATTGCGCTAC 3889 0 +GACCTCGATGTTGGATCA 3890 0 +GACTTTAATCGTTGAACA 3686 0 +GAGGAGCCTGTTCTAGAA 3876 0 +GAGGCGATGTTTTTGGTA 3893 0 +GATAGAAACTGACCTGGA 3900 0 +GATCACGTAGGACTTTAA 3900 0 +GATTAAAGTCCTACGTGA 3704 0 +GCAAGTCGTAACATGGTA 3891 0 +GCATAGTGGGGTATCTAA 3896 0 +GCCGGTAAAACTCGTGCC 3827 0 +GCCTCGCCTGTTTACCAA 3890 0 +GCCTGTTTACCAAAAACA 3889 0 +GCGATGTTTTTGGTAAAC 3888 0 +GCGCTTAGCTGTTAACTA 3899 0 +GCGGCTGCACCATTAGGA 3850 0 +GCTAAATCATGATGCAAA 3893 0 +GCTACACCTCGACCTGAC 3893 0 +GCTGGCACGAGTTTTACC 3881 0 +GCTGTTATCCCTAGGGTA 3896 0 +GGACATCCTAATGGTGCA 3865 0 +GGACTTTAATCGTTGAAC 3687 0 +GGAGCCTGTTCTAGAACC 3874 0 +GGATGTCCTGATCCAACA 3895 0 +GGATTAGATACCCCACTA 3896 0 +GGATTGCGCTGTTATCCC 3895 0 +GGCGATGTTTTTGGTAAA 3890 0 +GGCTGGCACGAGTTTTAC 3880 0 +GGGAAGAAATGGGCTACA 3872 0 +GGGAGGCAAGTCGTAACA 3891 0 +GGGGAGGCAAGTCGTAAC 3890 0 +GGGGGTTTACGACCTCGA 3894 0 +GGTACACTTACCATGTTA 3893 0 +GGTCTGAACTCAGATCAC 3902 0 +GGTCTTAGGAACCAAAAA 3862 0 +GGTTCGTTTGTTCAACGA 3877 0 +GTAACATGGTAAGTGTAC 3893 0 +GTACCTTTTGCATCATGA 3892 0 +GTCCTGATCCAACATCGA 3892 0 +GTCTTAGGAACCAAAAAC 3853 0 +GTGGGGTATCTAATCCCA 3895 0 +GTTACCCTAGGGATAACA 3896 0 +GTTTGTTCAACGATTAAA 3877 0 +TAAAACTCGTGCCAGCCA 3887 0 +TAACCGCGGTGGCTGGCA 3895 0 +TAAGGGTTCGTTTGTTCA 3889 0 +TAATCGTTGAACAAACGA 3876 0 +TACGACCTCGATGTTGGA 3892 0 +TAGGACTTTAATCGTTGA 3689 0 +TAGGATGTCCTGATCCAA 3897 0 +TCCCACTCTTTTGCCACA 3866 0 +TCGCCTGTTTACCAAAAA 3891 0 +TGGATCAGGACATCCTAA 3894 0 +TGGTCTTAGGAACCAAAA 3862 0 +TGGTGCAGCCGCTATTAA 3847 0 +TTGGTCTTAGGAACCAAA 3862 0 diff --git a/pkg/obiprimer/words.go b/pkg/obiprimer/words.go index c154e4d..d492039 100644 --- a/pkg/obiprimer/words.go +++ b/pkg/obiprimer/words.go @@ -4,10 +4,8 @@ import ( "fmt" "math" "sync" - "sync/atomic" "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obidefault" - "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiiter" "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obikmer" ) @@ -20,13 +18,6 @@ type WordSearch struct { TmpDir string // parent of the temporary directory ("" = system default) } -// Counts gives the number of sequences by role. -type Counts struct { - Examples int - CounterExamples int - Ignored int -} - // StrictWords is the result of the strict word search: the canonical words of // PrimerLength bases present, without any error, in at least MinSequences // example sequences. @@ -44,12 +35,12 @@ func MinSequences(examples int, quorum float64) int { return int(math.Floor(float64(examples)*quorum + 1e-9)) } -// FindStrictWords reads every sequence of seqs once, indexes the example -// sequences, and returns the words reaching the quorum. Sequences no longer -// than the word size are counted as examples but cannot contain any word, as -// in ecoPrimers. Words are canonical (the smaller of the word and its reverse -// complement), so both strands are searched. -func FindStrictWords(seqs obiiter.IBioSequence, sel Selection, cfg WordSearch) (*StrictWords, error) { +// FindStrictWords indexes the example sequences of the corpus and returns the +// words reaching the quorum. Sequences no longer than the word size are +// counted as examples but cannot contain any word, as in ecoPrimers. Words are +// canonical (the smaller of the word and its reverse complement), so both +// strands are searched. +func FindStrictWords(c *Corpus, cfg WordSearch) (*StrictWords, error) { if cfg.PrimerLength < 2 || cfg.PrimerLength > 31 { return nil, fmt.Errorf("obiprimer: primer length must be between 2 and 31, got %d", cfg.PrimerLength) } @@ -66,50 +57,27 @@ func FindStrictWords(seqs obiiter.IBioSequence, sel Selection, cfg WordSearch) ( } defer counter.Close() - var nextID atomic.Uint32 - var examples, counters, ignored atomic.Int64 - - consumer := func(it obiiter.IBioSequence, wg *sync.WaitGroup) { - defer wg.Done() - for it.Next() { - for _, seq := range it.Get().Slice() { - switch sel.Role(seq) { - case Example: - examples.Add(1) - if seq.Len() > cfg.PrimerLength { - counter.AddSequence(nextID.Add(1)-1, seq.Sequence(), cfg.Circular) - } - case CounterExample: - counters.Add(1) - default: - ignored.Add(1) + jobs := make(chan int) + var wg sync.WaitGroup + for range obidefault.ParallelWorkers() { + wg.Add(1) + go func() { + defer wg.Done() + for i := range jobs { + if seq := c.Seqs[i]; c.Roles[i] == Example && seq.Len() > cfg.PrimerLength { + counter.AddSequence(uint32(i), seq.Sequence(), cfg.Circular) } } - } + }() } - - var wg sync.WaitGroup - for i := 1; i < obidefault.ParallelWorkers(); i++ { - wg.Add(1) - go consumer(seqs.Split(), &wg) + for i := range c.Seqs { + jobs <- i } - wg.Add(1) - go consumer(seqs, &wg) + close(jobs) wg.Wait() - res := &StrictWords{ - PrimerLength: cfg.PrimerLength, - Counts: Counts{ - Examples: int(examples.Load()), - CounterExamples: int(counters.Load()), - Ignored: int(ignored.Load()), - }, - } + res := &StrictWords{PrimerLength: cfg.PrimerLength, Counts: c.Counts()} res.MinSequences = MinSequences(res.Counts.Examples, cfg.Quorum) - if res.Counts.Examples == 0 { - return nil, fmt.Errorf("obiprimer: no example sequence selected") - } - res.Words, err = counter.CountAtLeast(max(res.MinSequences, 1)) if err != nil { return nil, err diff --git a/pkg/obiprimer/words_test.go b/pkg/obiprimer/words_test.go index 1fbdb0d..8abaa18 100644 --- a/pkg/obiprimer/words_test.go +++ b/pkg/obiprimer/words_test.go @@ -107,8 +107,11 @@ func TestFindStrictWords(t *testing.T) { add("zz1", randDNA(rng, 50)+motif) sel := Selection{Restrict: idPrefix("ex").Or(idPrefix("cx")), Ignore: idPrefix("cx"), Except: idPrefix("cx")} - res, err := FindStrictWords(obiiter.IBatchOver("t", data, 3), sel, - WordSearch{PrimerLength: k, Quorum: 0.7, Partitions: 8, TmpDir: t.TempDir()}) + corpus, err := LoadCorpus(obiiter.IBatchOver("t", data, 3), sel) + if err != nil { + t.Fatal(err) + } + res, err := FindStrictWords(corpus, WordSearch{PrimerLength: k, Quorum: 0.7, Partitions: 8, TmpDir: t.TempDir()}) if err != nil { t.Fatal(err) } @@ -151,14 +154,17 @@ func TestFindStrictWordsParameters(t *testing.T) { it := func() obiiter.IBioSequence { return obiiter.IBatchOver("t", obiseq.BioSequenceSlice{obiseq.NewBioSequence("a", []byte("acgtacgtacgtacgtacgtacgt"), "")}, 1) } - if _, err := FindStrictWords(it(), Selection{}, WordSearch{PrimerLength: 1, Quorum: 0.7}); err == nil { + corpus, err := LoadCorpus(it(), Selection{}) + if err != nil { + t.Fatal(err) + } + if _, err := FindStrictWords(corpus, WordSearch{PrimerLength: 1, Quorum: 0.7}); err == nil { t.Error("primer length 1 must fail") } - if _, err := FindStrictWords(it(), Selection{}, WordSearch{PrimerLength: 18, Quorum: 0}); err == nil { + if _, err := FindStrictWords(corpus, WordSearch{PrimerLength: 18, Quorum: 0}); err == nil { t.Error("quorum 0 must fail") } - if _, err := FindStrictWords(it(), Selection{Restrict: idPrefix("zz")}, - WordSearch{PrimerLength: 18, Quorum: 0.7}); err == nil { + if _, err := LoadCorpus(it(), Selection{Restrict: idPrefix("zz")}); err == nil { t.Error("no example must fail") } } @@ -206,7 +212,11 @@ func TestStrictWordsAgainstEcoPrimers(t *testing.T) { if err != nil { t.Fatal(err) } - res, err := FindStrictWords(seqs, Selection{Restrict: restrict}, + corpus, err := LoadCorpus(seqs, Selection{Restrict: restrict}) + if err != nil { + t.Fatal(err) + } + res, err := FindStrictWords(corpus, WordSearch{PrimerLength: 18, Quorum: 0.7, Circular: true, TmpDir: t.TempDir()}) if err != nil { t.Fatal(err)