Files
obitools4/pkg/obialign/fastlcs_bench_test.go
T
Eric Coissac 75bb635a6c Implement Banded LCS calculation with scalar and SIMD support
Introduces the BandedLCS function for efficient LCS scoring, supporting both scalar and SIMD implementations. Adds comprehensive unit tests, benchmarking utilities, and error handling for sequence constraints.
2026-11-03 09:28:46 +11:00

112 lines
2.9 KiB
Go

package obialign
import (
"fmt"
"math/rand"
"testing"
"git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq"
)
// randomDNA returns a random ACGT sequence of length n.
func randomDNA(rng *rand.Rand, n int) []byte {
const nuc = "ACGT"
s := make([]byte, n)
for i := range s {
s[i] = nuc[rng.Intn(4)]
}
return s
}
// mutate returns a copy of s in which each position is, with probability
// rate, substituted, deleted or followed by an inserted base (equiprobable).
func mutate(rng *rand.Rand, s []byte, rate float64) []byte {
const nuc = "ACGT"
out := make([]byte, 0, len(s)+len(s)/10)
for _, c := range s {
if rng.Float64() >= rate {
out = append(out, c)
continue
}
switch rng.Intn(3) {
case 0:
out = append(out, nuc[rng.Intn(4)])
case 1: // deletion
case 2:
out = append(out, c, nuc[rng.Intn(4)])
}
}
return out
}
// benchPairs builds a fixed set of pairs. "similar" pairs differ by about
// 2% of mutated positions, "random" pairs are unrelated sequences.
func benchPairs(n, length int, similar bool) [][2]*obiseq.BioSequence {
rng := rand.New(rand.NewSource(42))
pairs := make([][2]*obiseq.BioSequence, n)
for i := range pairs {
a := randomDNA(rng, length)
var b []byte
if similar {
b = mutate(rng, a, 0.02)
} else {
b = randomDNA(rng, length)
}
pairs[i] = [2]*obiseq.BioSequence{
obiseq.NewBioSequence("a", a, ""),
obiseq.NewBioSequence("b", b, ""),
}
}
return pairs
}
// BenchmarkLegacyLCS measures the historical implementation (the one
// FastLCSScore used before BandedLCS), kept as the "before" reference.
func BenchmarkLegacyLCS(b *testing.B) {
for _, similar := range []bool{true, false} {
kind := "random"
if similar {
kind = "similar"
}
for _, length := range []int{100, 500, 2000} {
pairs := benchPairs(64, length, similar)
for _, maxErr := range []int{-1, 5, length / 20, length / 5} {
name := fmt.Sprintf("%s/len=%d/maxErr=%d", kind, length, maxErr)
b.Run(name, func(b *testing.B) {
var buffer []uint64
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
p := pairs[i%len(pairs)]
FastLCSEGFScoreByte(p[0].Sequence(), p[1].Sequence(), maxErr, false, &buffer)
}
})
}
}
}
}
// BenchmarkBandedLCS runs on the same pairs as BenchmarkLegacyLCS.
func BenchmarkBandedLCS(b *testing.B) {
for _, similar := range []bool{true, false} {
kind := "random"
if similar {
kind = "similar"
}
for _, length := range []int{100, 500, 2000} {
pairs := benchPairs(64, length, similar)
for _, maxErr := range []int{-1, 5, length / 20, length / 5} {
name := fmt.Sprintf("%s/len=%d/maxErr=%d", kind, length, maxErr)
b.Run(name, func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
p := pairs[i%len(pairs)]
BandedLCS(p[0].Sequence(), p[1].Sequence(), maxErr)
}
})
}
}
}
}