mirror of
https://github.com/metabarcoding/obitools4.git
synced 2026-11-10 01:24:41 +00:00
Feature: Implement constrained k-mer generation and clique search
This commit introduces core functionality for oligonucleotide family tagging, constrained k-mer generation, and clique finding. It also includes updates to the Go language version and refactors the logic for k-mer dissimilarity metrics.
This commit is contained in:
1 parent
f76e7f03c6
commit
3087c70daa
17 files changed
+1791
-13
No files matched your search
@@ -0,0 +1,123 @@
|
||||
package obikmer
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// IterConstrainedKmers must enumerate exactly the k-mers accepted by Accepts.
|
||||
func TestIterConstrainedKmersMatchesAccepts(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(10))
|
||||
patterns := []string{"", "nnnnnn", "yrnnnn", "cnnnnn", "annnnt"}
|
||||
|
||||
for trial := 0; trial < 60; trial++ {
|
||||
k := 6
|
||||
c := NoKmerConstraints()
|
||||
for b := range 4 {
|
||||
if rng.Intn(3) == 0 {
|
||||
c.MaxCount[b] = rng.Intn(4)
|
||||
}
|
||||
}
|
||||
if rng.Intn(2) == 0 {
|
||||
c.MaxGC = rng.Intn(5)
|
||||
}
|
||||
if rng.Intn(2) == 0 {
|
||||
c.MaxRun = 1 + rng.Intn(3)
|
||||
}
|
||||
if rng.Intn(2) == 0 {
|
||||
c.MinRun = 2 + rng.Intn(3)
|
||||
}
|
||||
if p := patterns[rng.Intn(len(patterns))]; p != "" {
|
||||
c.Accepted, _ = ParseIUPACMasks(p)
|
||||
}
|
||||
if p := patterns[rng.Intn(len(patterns))]; p != "" && rng.Intn(2) == 0 {
|
||||
c.Rejected, _ = ParseIUPACMasks(p)
|
||||
}
|
||||
|
||||
var want []uint64
|
||||
for km := uint64(0); km < 1<<(2*uint(k)); km++ {
|
||||
if c.Accepts(km, k) {
|
||||
want = append(want, km)
|
||||
}
|
||||
}
|
||||
got := collectConstrained(k, c)
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("trial %d %+v: got %d k-mers, want %d", trial, c, len(got), len(want))
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("trial %d %+v: mismatch at %d: %d != %d", trial, c, i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstraintsGCAndRuns(t *testing.T) {
|
||||
enc := func(s string) uint64 { return EncodeKmer([]byte(s), len(s)) }
|
||||
|
||||
c := NoKmerConstraints()
|
||||
c.MaxGC = 2
|
||||
if !c.Accepts(enc("ACGTAA"), 6) || c.Accepts(enc("ACGCAA"), 6) {
|
||||
t.Fatal("MaxGC not enforced")
|
||||
}
|
||||
|
||||
c = NoKmerConstraints()
|
||||
c.MinRun = 3
|
||||
if !c.Accepts(enc("ACCCGT"), 6) || c.Accepts(enc("ACCGGT"), 6) {
|
||||
t.Fatal("MinRun not enforced")
|
||||
}
|
||||
|
||||
c = NoKmerConstraints()
|
||||
c.Rejected, _ = ParseIUPACMasks("cnnnnn") // no C in first position
|
||||
if c.Accepts(enc("CAAAAA"), 6) || !c.Accepts(enc("AAAAAA"), 6) {
|
||||
t.Fatal("Rejected not enforced")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestrictAlphabetAndPatterns(t *testing.T) {
|
||||
c := NoKmerConstraints()
|
||||
if err := c.RestrictAlphabet("ACT"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := len(collectConstrained(4, c)); got != 81 {
|
||||
t.Fatalf("expected 3^4 = 81 k-mers, got %d", got)
|
||||
}
|
||||
c = NoKmerConstraints()
|
||||
if err := c.RestrictAlphabet("H"); err != nil { // H = A, C or T
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.MaxCount != [4]int{-1, -1, 0, -1} {
|
||||
t.Fatalf("unexpected counts %v", c.MaxCount)
|
||||
}
|
||||
if err := c.RestrictAlphabet("AX"); err == nil {
|
||||
t.Fatal("invalid IUPAC code must be refused")
|
||||
}
|
||||
if _, err := ParseIUPACMasks("acgz"); err == nil {
|
||||
t.Fatal("invalid IUPAC code must be refused")
|
||||
}
|
||||
// wrong pattern length
|
||||
c = NoKmerConstraints()
|
||||
c.Accepted, _ = ParseIUPACMasks("nnn")
|
||||
if got := collectConstrained(4, c); len(got) != 0 {
|
||||
t.Fatalf("pattern of wrong length must give nothing, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHammingBound(t *testing.T) {
|
||||
cases := []struct {
|
||||
q, k, d int
|
||||
want float64
|
||||
}{
|
||||
{4, 7, 3, 16384.0 / 22},
|
||||
{4, 8, 4, 16384.0 / 22}, // d even: reduces to (7, 3)
|
||||
{4, 9, 5, 262144.0 / 352},
|
||||
{3, 9, 3, 19683.0 / 19},
|
||||
{4, 5, 1, 1024},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := HammingBound(c.q, c.k, c.d); math.Abs(got-c.want) > 1e-6 {
|
||||
t.Errorf("HammingBound(%d,%d,%d) = %v, want %v", c.q, c.k, c.d, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user