github.com/biogo/biogo@v1.0.4/align/pals/filter/merge.go (about)

     1  // Copyright ©2011-2013 The bíogo Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package filter
     6  
     7  import (
     8  	"github.com/biogo/biogo/alphabet"
     9  	"github.com/biogo/biogo/index/kmerindex"
    10  	"github.com/biogo/biogo/seq/linear"
    11  
    12  	"sort"
    13  )
    14  
    15  const (
    16  	diagonalPadding = 2
    17  )
    18  
    19  // A Merger aggregates and clips an ordered set of trapezoids.
    20  type Merger struct {
    21  	target, query              *linear.Seq
    22  	filterParams               *Params
    23  	maxIGap                    int
    24  	leftPadding, bottomPadding int
    25  	binWidth                   int
    26  	selfComparison             bool
    27  	freeTraps, trapList        *trapezoid
    28  	trapOrder, tail            *trapezoid
    29  	eoTerm                     *trapezoid
    30  	trapCount                  int
    31  	valueToCode                alphabet.Index
    32  }
    33  
    34  // Create a new Merger using the provided kmerindex, query sequence, filter parameters and maximum inter-segment gap length.
    35  // If selfCompare is true only the upper diagonal of the comparison matrix is examined.
    36  func NewMerger(ki *kmerindex.Index, query *linear.Seq, filterParams *Params, maxIGap int, selfCompare bool) *Merger {
    37  	tubeWidth := filterParams.TubeOffset + filterParams.MaxError
    38  	binWidth := tubeWidth - 1
    39  	leftPadding := diagonalPadding + binWidth
    40  
    41  	eoTerm := &trapezoid{Trapezoid: Trapezoid{
    42  		Left:   query.Len() + 1 + leftPadding,
    43  		Right:  query.Len() + 1,
    44  		Bottom: -1,
    45  		Top:    query.Len() + 1,
    46  	}}
    47  
    48  	return &Merger{
    49  		target:         ki.Seq(),
    50  		filterParams:   filterParams,
    51  		maxIGap:        maxIGap,
    52  		query:          query,
    53  		selfComparison: selfCompare,
    54  		bottomPadding:  ki.K() + 2,
    55  		leftPadding:    leftPadding,
    56  		binWidth:       binWidth,
    57  		eoTerm:         eoTerm,
    58  		trapOrder:      eoTerm,
    59  		valueToCode:    ki.Seq().Alpha.LetterIndex(),
    60  	}
    61  }
    62  
    63  // Merge a filter hit into the collection.
    64  func (m *Merger) MergeFilterHit(h *Hit) {
    65  	Left := -h.Diagonal
    66  	if m.selfComparison && Left <= m.filterParams.MaxError {
    67  		return
    68  	}
    69  	Top := h.To
    70  	Bottom := h.From
    71  
    72  	var temp, free *trapezoid
    73  	for base := m.trapOrder; ; base = temp {
    74  		temp = base.next
    75  		switch {
    76  		case Bottom-m.bottomPadding > base.Top:
    77  			if free == nil {
    78  				m.trapOrder = temp
    79  			} else {
    80  				free.join(temp)
    81  			}
    82  			m.trapList = base.join(m.trapList)
    83  			m.trapCount++
    84  		case Left-diagonalPadding > base.Right:
    85  			free = base
    86  		case Left+m.leftPadding >= base.Left:
    87  			if Left+m.binWidth > base.Right {
    88  				base.Right = Left + m.binWidth
    89  			}
    90  			if Left < base.Left {
    91  				base.Left = Left
    92  			}
    93  			if Top > base.Top {
    94  				base.Top = Top
    95  			}
    96  
    97  			if free != nil && free.Right+diagonalPadding >= base.Left {
    98  				free.Right = base.Right
    99  				if free.Bottom > base.Bottom {
   100  					free.Bottom = base.Bottom
   101  				}
   102  				if free.Top < base.Top {
   103  					free.Top = base.Top
   104  				}
   105  
   106  				free.join(temp)
   107  				m.freeTraps = base.join(m.freeTraps)
   108  			} else if temp != nil && temp.Left-diagonalPadding <= base.Right {
   109  				base.Right = temp.Right
   110  				if base.Bottom > temp.Bottom {
   111  					base.Bottom = temp.Bottom
   112  				}
   113  				if base.Top < temp.Top {
   114  					base.Top = temp.Top
   115  				}
   116  				base.join(temp.next)
   117  				m.freeTraps = temp.join(m.freeTraps)
   118  				temp = base.next
   119  			}
   120  
   121  			return
   122  		default:
   123  			if m.freeTraps == nil {
   124  				m.freeTraps = &trapezoid{}
   125  			}
   126  			if free == nil {
   127  				m.trapOrder = m.freeTraps
   128  			} else {
   129  				free.join(m.freeTraps)
   130  			}
   131  
   132  			free, m.freeTraps = m.freeTraps.decapitate()
   133  			free.join(base)
   134  
   135  			free.Top = Top
   136  			free.Bottom = Bottom
   137  			free.Left = Left
   138  			free.Right = Left + m.binWidth
   139  
   140  			return
   141  		}
   142  	}
   143  }
   144  
   145  func (m *Merger) clipVertical() {
   146  	for base := m.trapList; base != nil; base = base.next {
   147  		lagPosition := base.Bottom - m.maxIGap + 1
   148  		if lagPosition < 0 {
   149  			lagPosition = 0
   150  		}
   151  		lastPosition := base.Top + m.maxIGap
   152  		if lastPosition > m.query.Len() {
   153  			lastPosition = m.query.Len()
   154  		}
   155  
   156  		var pos int
   157  		for pos = lagPosition; pos < lastPosition; pos++ {
   158  			if m.valueToCode[m.query.Seq[pos]] >= 0 {
   159  				if pos-lagPosition >= m.maxIGap {
   160  					if lagPosition-base.Bottom > 0 {
   161  						if m.freeTraps == nil {
   162  							m.freeTraps = &trapezoid{}
   163  						}
   164  
   165  						m.freeTraps = m.freeTraps.prependFrontTo(base)
   166  
   167  						base.Top = lagPosition
   168  						base = base.next
   169  						base.Bottom = pos
   170  						m.trapCount++
   171  					} else {
   172  						base.Bottom = pos
   173  					}
   174  				}
   175  				lagPosition = pos + 1
   176  			}
   177  		}
   178  		if pos-lagPosition >= m.maxIGap {
   179  			base.Top = lagPosition
   180  		}
   181  	}
   182  }
   183  
   184  func (m *Merger) clipTrapezoids() {
   185  	for base := m.trapList; base != nil; base = base.next {
   186  		if base.Top-base.Bottom < m.bottomPadding-2 {
   187  			continue
   188  		}
   189  
   190  		aBottom := base.Bottom - base.Right
   191  		aTop := base.Top - base.Left
   192  
   193  		lagPosition := aBottom - m.maxIGap + 1
   194  		if lagPosition < 0 {
   195  			lagPosition = 0
   196  		}
   197  		lastPosition := aTop + m.maxIGap
   198  		if lastPosition > m.target.Len() {
   199  			lastPosition = m.target.Len()
   200  		}
   201  
   202  		lagClip := aBottom
   203  		var pos int
   204  		for pos = lagPosition; pos < lastPosition; pos++ {
   205  			if m.valueToCode[m.target.Seq[pos]] >= 0 {
   206  				if pos-lagPosition >= m.maxIGap {
   207  					if lagPosition > lagClip {
   208  						if m.freeTraps == nil {
   209  							m.freeTraps = &trapezoid{}
   210  						}
   211  
   212  						m.freeTraps = m.freeTraps.prependFrontTo(base)
   213  
   214  						base.clip(lagPosition, lagClip)
   215  
   216  						base = base.next
   217  						m.trapCount++
   218  					}
   219  					lagClip = pos
   220  				}
   221  				lagPosition = pos + 1
   222  			}
   223  		}
   224  
   225  		if pos-lagPosition < m.maxIGap {
   226  			lagPosition = aTop
   227  		}
   228  
   229  		base.clip(lagPosition, lagClip)
   230  
   231  		m.tail = base
   232  	}
   233  }
   234  
   235  // Finalise the merged collection and return a sorted slice of Trapezoids.
   236  func (m *Merger) FinaliseMerge() Trapezoids {
   237  	var next *trapezoid
   238  	for base := m.trapOrder; base != m.eoTerm; base = next {
   239  		next = base.next
   240  		m.trapList = base.join(m.trapList)
   241  		m.trapCount++
   242  	}
   243  
   244  	m.clipVertical()
   245  	m.clipTrapezoids()
   246  
   247  	if m.tail != nil {
   248  		m.freeTraps = m.tail.join(m.freeTraps)
   249  	}
   250  
   251  	traps := make(Trapezoids, m.trapCount)
   252  	for i, z := 0, m.trapList; i < m.trapCount; i++ {
   253  		traps[i] = z.Trapezoid
   254  		z, z.next = z.next, nil
   255  	}
   256  
   257  	sort.Sort(traps)
   258  
   259  	return traps
   260  }