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,113 @@
|
||||
package obikmer_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obigraph"
|
||||
"git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obikmer"
|
||||
)
|
||||
|
||||
func allKmers(k int) []uint64 {
|
||||
kmers := make([]uint64, 0, 1<<(2*uint(k)))
|
||||
for km := range obikmer.IterConstrainedKmers(k, obikmer.NoKmerConstraints()) {
|
||||
kmers = append(kmers, km)
|
||||
}
|
||||
return kmers
|
||||
}
|
||||
|
||||
func TestDissimilarityFilterMatchesDistances(t *testing.T) {
|
||||
const k = 7
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
kmers := make([]uint64, 300)
|
||||
for i := range kmers {
|
||||
kmers[i] = rng.Uint64() & (1<<(2*k) - 1)
|
||||
}
|
||||
candidates := make([]int32, 0, len(kmers))
|
||||
for i := 1; i < len(kmers); i++ {
|
||||
candidates = append(candidates, int32(i))
|
||||
}
|
||||
|
||||
for _, metric := range []obikmer.KmerMetric{obikmer.HammingMetric, obikmer.LCSMetric} {
|
||||
for _, dmin := range []int{1, 3, 5} {
|
||||
filter := obikmer.NewKmerDissimilarityFilter(kmers, k, metric, dmin)
|
||||
got := filter(0, candidates, nil)
|
||||
|
||||
var want []int32
|
||||
for _, c := range candidates {
|
||||
var d int
|
||||
if metric == obikmer.HammingMetric {
|
||||
d = obikmer.HammingDistance(kmers[0], kmers[c], k)
|
||||
} else {
|
||||
d = obikmer.KmerLCS(kmers[0], kmers[c], k).Distance()
|
||||
}
|
||||
if d >= dmin {
|
||||
want = append(want, c)
|
||||
}
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("%v d>=%d: got %d vertices, want %d", metric, dmin, len(got), len(want))
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("%v d>=%d: mismatch at %d", metric, dmin, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A family of 5-mers pairwise at Hamming distance >= 3, built with FindClique.
|
||||
func TestFindClique_HammingFamily(t *testing.T) {
|
||||
const k, dmin, target = 5, 3, 32
|
||||
kmers := allKmers(k)
|
||||
filter := obikmer.NewKmerDissimilarityFilter(kmers, k, obikmer.HammingMetric, dmin)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
r := obigraph.FindClique(ctx, len(kmers), filter, target, obigraph.CliqueOptions{Workers: 2})
|
||||
if !r.Reached {
|
||||
t.Fatalf("family of %d not found (best %d, exhausted %v)", target, len(r.Clique), r.Exhausted)
|
||||
}
|
||||
for i, a := range r.Clique {
|
||||
for _, b := range r.Clique[i+1:] {
|
||||
if obikmer.HammingDistance(kmers[a], kmers[b], k) < dmin {
|
||||
t.Fatalf("%d and %d are too close", kmers[a], kmers[b])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindClique_LCSFamily(t *testing.T) {
|
||||
const k, dmin, target = 6, 3, 20
|
||||
kmers := allKmers(k)
|
||||
filter := obikmer.NewKmerDissimilarityFilter(kmers, k, obikmer.LCSMetric, dmin)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
r := obigraph.FindClique(ctx, len(kmers), filter, target, obigraph.CliqueOptions{Workers: 2})
|
||||
if !r.Reached {
|
||||
t.Fatalf("family of %d not found (best %d, exhausted %v)", target, len(r.Clique), r.Exhausted)
|
||||
}
|
||||
for i, a := range r.Clique {
|
||||
for _, b := range r.Clique[i+1:] {
|
||||
if obikmer.KmerLCS(kmers[a], kmers[b], k).Distance() < dmin {
|
||||
t.Fatalf("%d and %d are too close", kmers[a], kmers[b])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKmerMetric(t *testing.T) {
|
||||
if m, err := obikmer.ParseKmerMetric("LCS"); err != nil || m != obikmer.LCSMetric {
|
||||
t.Fatal("lcs not parsed")
|
||||
}
|
||||
if m, err := obikmer.ParseKmerMetric("hamming"); err != nil || m != obikmer.HammingMetric {
|
||||
t.Fatal("hamming not parsed")
|
||||
}
|
||||
if _, err := obikmer.ParseKmerMetric("levenshtein"); err == nil {
|
||||
t.Fatal("unknown metric must be refused")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user