mirror of
https://github.com/metabarcoding/obitools4.git
synced 2026-11-09 17:24:41 +00:00
Introduces parallel processing for clustering, refines scoring mechanisms, and expands test coverage for the alignment functionality. This change introduces parallel processing capabilities within the clustering logic, updates the core similarity engine with new data structures and scoring methods, and implements a comprehensive test suite for the alignment tool. It also adds new command-line options for configuring alignment parameters and reference lengths.
104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
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. With
|
|
// no minimum there is nothing to reject, and the pass would be wasted.
|
|
if minLCS > 0 && 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
|
|
}
|