mirror of
https://github.com/metabarcoding/obitools4.git
synced 2026-11-09 17:24:41 +00:00
Implement core clustering logic and scoring features
This update introduces the core clustering algorithm, including parallel execution, dynamic block sizing, and SIMD-accelerated scoring methods. It also adds robust sequence preparation, k-mer analysis, and comprehensive test data fixtures for validation.
This commit is contained in:
1 parent
75bb635a6c
commit
49225030ca
32 files changed
+3404
-119
No files matched your search
@@ -0,0 +1,102 @@
|
||||
package obialign
|
||||
|
||||
// BandedLCSBuffer is a working memory that can be reused from one alignment to
|
||||
// the next to avoid any allocation. It must not be shared between goroutines.
|
||||
type BandedLCSBuffer struct {
|
||||
ws bandedLCSWorkspace
|
||||
}
|
||||
|
||||
// AtLeast is BandedLCSAtLeast on the buffer.
|
||||
func (b *BandedLCSBuffer) AtLeast(seqA, seqB []byte, minLCS int) (lcs, alilen int, err error) {
|
||||
return atLeast(seqA, seqB, minLCS, &b.ws)
|
||||
}
|
||||
|
||||
func atLeast(seqA, seqB []byte, minLCS int, ws *bandedLCSWorkspace) (lcs, alilen int, err error) {
|
||||
if len(seqA) > MaxBandedLCSLength || len(seqB) > MaxBandedLCSLength {
|
||||
return -1, -1, ErrSequenceTooLong
|
||||
}
|
||||
if len(seqA) < len(seqB) {
|
||||
seqA, seqB = seqB, seqA
|
||||
}
|
||||
lcs, alilen = bandedLCSAtLeast(seqA, seqB, minLCS, ws, haveSIMD)
|
||||
return lcs, alilen, nil
|
||||
}
|
||||
|
||||
// BandedLCSAtLeast computes the LCS score of two nucleotide sequences and the
|
||||
// length of the shortest alignment achieving it, as BandedLCS does, but with
|
||||
// the bound expressed on the score: the result is the exact optimum when its
|
||||
// score is at least minLCS, and (-1, -1) otherwise.
|
||||
//
|
||||
// The DP only visits the diagonals an alignment of score minLCS can use (one
|
||||
// whose path makes at most lB-minLCS gaps on one side and lA-minLCS on the
|
||||
// other), so the cost decreases as minLCS grows. This is exact: every alignment
|
||||
// of score >= minLCS lies in that band, hence so does the optimum when it
|
||||
// reaches minLCS, and when the best path of the band is below minLCS so is
|
||||
// the global optimum.
|
||||
func BandedLCSAtLeast(seqA, seqB []byte, minLCS int) (lcs, alilen int, err error) {
|
||||
ws := bandedLCSPool.Get().(*bandedLCSWorkspace)
|
||||
lcs, alilen, err = atLeast(seqA, seqB, minLCS, ws)
|
||||
bandedLCSPool.Put(ws)
|
||||
return lcs, alilen, err
|
||||
}
|
||||
|
||||
// bandedLCSAtLeast expects len(a) >= len(b), both within MaxBandedLCSLength.
|
||||
func bandedLCSAtLeast(a, b []byte, minLCS int, ws *bandedLCSWorkspace, useSIMD bool) (int, int) {
|
||||
lA, lB := len(a), len(b)
|
||||
minLCS = max(minLCS, 0)
|
||||
if minLCS > lB {
|
||||
return -1, -1
|
||||
}
|
||||
if lB == 0 {
|
||||
return 0, lA
|
||||
}
|
||||
simdOK := useSIMD && allRegularIUPAC(a) && allRegularIUPAC(b)
|
||||
kmin, kmax := -(lB - minLCS), lA-minLCS
|
||||
|
||||
if !simdOK {
|
||||
lcs, alilen := ws.align(a, b, kmin, kmax, false)
|
||||
if lcs < minLCS {
|
||||
return -1, -1
|
||||
}
|
||||
return lcs, alilen
|
||||
}
|
||||
|
||||
// The score alone is much cheaper than the score with the length of the
|
||||
// alignment: the latter is only computed when the pair reaches minLCS.
|
||||
if lcsScoreSIMD(a, b, kmin, kmax, minLCS, ws) < minLCS {
|
||||
return -1, -1
|
||||
}
|
||||
encodeBases(ws, a, b)
|
||||
return ws.align(a, b, kmin, kmax, true)
|
||||
}
|
||||
|
||||
// ScoreAtLeast computes the LCS score of two nucleotide sequences when it is
|
||||
// at least minLCS, and returns -1 otherwise. It is much cheaper than AtLeast,
|
||||
// which also computes the length of the shortest alignment.
|
||||
func (b *BandedLCSBuffer) ScoreAtLeast(seqA, seqB []byte, minLCS int) (int, error) {
|
||||
if len(seqA) > MaxBandedLCSLength || len(seqB) > MaxBandedLCSLength {
|
||||
return -1, ErrSequenceTooLong
|
||||
}
|
||||
if len(seqA) < len(seqB) {
|
||||
seqA, seqB = seqB, seqA
|
||||
}
|
||||
lA, lB := len(seqA), len(seqB)
|
||||
minLCS = max(minLCS, 0)
|
||||
if minLCS > lB {
|
||||
return -1, nil
|
||||
}
|
||||
if lB == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
kmin, kmax := -(lB - minLCS), lA-minLCS
|
||||
var lcs int
|
||||
if haveSIMD && allRegularIUPAC(seqA) && allRegularIUPAC(seqB) {
|
||||
lcs = lcsScoreSIMD(seqA, seqB, kmin, kmax, minLCS, &b.ws)
|
||||
} else {
|
||||
lcs, _ = b.ws.align(seqA, seqB, kmin, kmax, false)
|
||||
}
|
||||
if lcs < minLCS {
|
||||
return -1, nil
|
||||
}
|
||||
return lcs, nil
|
||||
}
|
||||
Reference in new issue
Block a user