func (m *mapper) mapNext(query sequence.Sequence, openA, openB []*Mapping, aligner seeds.Aligner) (newA, newB, matched []*Mapping) {
	var extended []*Mapping
	if query.Len() < m.edgeSize*4 {
		newA = m.performMapping(query.SubSequence(m.edgeSize, query.Len()-m.edgeSize),aligner)
		newA = removeDominated(newA, newA, query.Len())
		updateQuery(newA, query)
		openA, newA, extended = m.matchPairs(openA, newA)
		if extended != nil {
			openA = append(newA, extended...)  
		} else {
			openA = append(openA, newA...)  
		}
		newA, newB, matched = m.matchPairs(openA, openB)
		if matched == nil {
			return newA, newB, matched  
		}
		return newA[:0], newB[:0], matched  
	}
	newA = m.performMapping(query.SubSequence(m.edgeSize, m.edgeSize*2),aligner)
	newA = removeDominated(newA, newA, query.Len())
	updateQuery(newA, query)
	openA, newA, extended = m.matchPairs(openA, newA)
	openA = append(openA, newA...)
	if extended != nil {
		openA = append(openA, extended...)
	}
	newB = m.performMapping(query.SubSequence(query.Len()-m.edgeSize*2, query.Len()-m.edgeSize),aligner)
	newB = removeDominated(newB, newB, query.Len())
	updateQuery(newB, query)
	openB, newB, extended = m.matchPairs(newB, openB)
	openB = append(openB, newB...)
	if extended != nil {
		openB = append(openB, extended...)
	}
	newA, newB, matched = m.matchPairs(openA, openB)
	if matched == nil {
		if query.Len() > m.edgeSize*5 {
			openA = m.performMapping(query.SubSequence(m.edgeSize*2, m.edgeSize*3),aligner)
			openA = removeDominated(openA, openA, query.Len())
			updateQuery(openA, query)
			openA, newA, extended = m.matchPairs(newA, openA)
			if extended != nil {
				openA = append(openA, extended...)
			}
			openA = append(openA, newA...)  
		}
		if query.Len() > m.edgeSize*6 {  
			openB = m.performMapping(query.SubSequence(query.Len()-m.edgeSize*3, query.Len()-m.edgeSize*2),aligner)
			openB = removeDominated(openB, openB, query.Len())
			updateQuery(openB, query)
			openB, newB, extended = m.matchPairs(openB, newB)
			if extended != nil {
				openB = append(openB, extended...)
			}
			openB = append(openB, newB...)
		} else {
			openB = newB
		}
		if query.Len() > m.edgeSize*5 {
			newA, newB, matched = m.matchPairs(openA, openB)
		}
	}
	return newA, newB, matched
}
count A/B are how many of the open list are at an edge (starting at index 0)
Take further edgeSize steps in, testing for matching pairs and removing redundant hits
package mapping
import (
	"fmt"
	"github.com/jteutenberg/downpore/seeds"
	"github.com/jteutenberg/downpore/sequence"
	"github.com/jteutenberg/downpore/util"
	"sort"
)
type Mapping struct {
	Query       sequence.Sequence
	Start       int
	End         int
	QueryOffset int
	QueryInset  int
	RC          bool
	match       *seeds.SeedMatch
	ids         int  
}
type Mapper interface {
	Map(sequence.Sequence, seeds.Aligner) []*Mapping
	MapWorker(<-chan sequence.Sequence, chan<- []*Mapping, chan<- bool)
	AsString(*Mapping) string
}
type mapper struct {
	index     *seeds.SeedIndex
	reference sequence.Sequence
	edgeSize  int  
	circular  bool
}
type mappingsByPos []*Mapping
type mappingsByQPos []*Mapping
type mappingsByIds []*Mapping
func (m mappingsByPos) Len() int {
	return len(m)
}
func (m mappingsByPos) Less(i, j int) bool {
	return m[i].Start < m[j].Start
}
func (m mappingsByPos) Swap(i, j int) {
	m[i], m[j] = m[j], m[i]
}
func (m mappingsByQPos) Len() int {
	return len(m)
}
func (m mappingsByQPos) Less(i, j int) bool {
	return m[i].QueryOffset < m[j].QueryOffset
}
func (m mappingsByQPos) Swap(i, j int) {
	m[i], m[j] = m[j], m[i]
}
func (m mappingsByIds) Len() int {
	return len(m)
}
func (m mappingsByIds) Less(i, j int) bool {
	return m[i].ids > m[j].ids
}
func (m mappingsByIds) Swap(i, j int) {
	m[i], m[j] = m[j], m[i]
}
func NewMapper(reference sequence.Sequence, circular bool, k uint, kmerValues []float64, seedRate int, edgeSize int, chunkSize int, numWorkers int) Mapper {
	m := mapper{index: seeds.NewSeedIndex(k), reference: reference, edgeSize: edgeSize, circular: circular}
	m.index.AddSingleSeeds(reference, seedRate, kmerValues)
	chunks := make(chan sequence.Sequence, numWorkers*2)
	seedSeqs := make(chan *seeds.SeedSequence, numWorkers*2)
	done := make(chan bool, numWorkers)
	for i := 0; i < numWorkers; i++ {
		go seeds.AddSequenceWorker(chunks, m.index, seedSeqs, done)
	}
	ind := 0
	go func() {
		for j := 0; j < 10; j++ {
			start := j * chunkSize
			step := chunkSize*10 - edgeSize
			for i := start; i < reference.Len()-chunkSize/2; i += step {
				end := i + chunkSize
				if i >= reference.Len() {
					end = reference.Len()
				}
				chunks <- reference.SubSequence(i, end)
			}
		}
		if circular {
			chunks <- reference.SubSequence(reference.Len()-edgeSize, reference.Len()).Append(0, reference.SubSequence(0, edgeSize), nil)
		}
		close(chunks)
		for i := 0; i < numWorkers; i++ {
			<-done
		}
		close(seedSeqs)
	}()
	for seq := range seedSeqs {
		seq.SetID(ind)
		m.index.AddSequence(seq)
		ind++
	}
	m.index.IndexSequences(numWorkers)
	return &m
}
func (m *mapper) AsString(mapping *Mapping) string {
	rc := "+"
	if mapping.RC {
		rc = "-"
	}
	mappedLength := mapping.End - mapping.Start
	if m.circular && mappedLength < 0 {
		mappedLength = m.reference.Len() - mapping.Start + mapping.End
	}
	return fmt.Sprintf("%s\t%d\t%d\t%d\t%s\t%s\t%d\t%d\t%d\t%d\t%d\t255", mapping.Query.GetName(), mapping.Query.Len(), mapping.QueryOffset, mapping.Query.Len()-mapping.QueryInset, rc, m.reference.GetName(), m.reference.Len(), mapping.Start, mapping.End, mapping.ids, mappedLength)
}
func updateQuery(ms []*Mapping, q sequence.Sequence) {
	for _, m := range ms {
		m.Query = q
	}
}
func (m *mapper) isConsistent(left, right *Mapping) bool {
	if left.RC != right.RC {
		return false
	}
	expectedDistance := right.QueryOffset - left.Query.Len() + left.QueryInset
	var distance int
	if !left.RC {
		distance = right.Start - left.End  
	} else {
		distance = left.Start - right.End  
	}
	if m.circular && distance < -50 {
		distance += m.reference.Len()
	}
	if distance < 50 && expectedDistance < 50 && distance > -50 {
		return true
	}
	if distance < 500 {
		return (expectedDistance < (distance*3)/2 && expectedDistance > (distance*2)/3)
	}
	if distance > 5000 {
		return (expectedDistance < (distance*10)/9 && expectedDistance > (distance*9)/10)
	}
	ratio := float64(distance-500) / 4500.0
	ratio = 3.0/2.0 + ratio*(10.0/9.0-3.0/2.0)
	return distance < int(float64(expectedDistance)*ratio) && distance > int(float64(expectedDistance)/ratio)
}
func (m *mapper) mapEnds(query sequence.Sequence, aligner seeds.Aligner) (openA, openB, matching []*Mapping) {
	openA = m.performMapping(query.SubSequence(0, m.edgeSize),aligner)
	openB = m.performMapping(query.SubSequence(query.Len()-m.edgeSize, query.Len()),aligner)
	openA = removeDominated(openA, openA, query.Len())
	openB = removeDominated(openB, openB, query.Len())
	updateQuery(openA, query)
	updateQuery(openB, query)
	return m.matchPairs(openA, openB)
}
func (m *mapper) matchPairs(openA, openB []*Mapping) (remainingA, remainingB, matched []*Mapping) {
	for i := len(openA) - 1; i >= 0; i-- {
		ra := openA[i]
		for j := len(openB) - 1; j >= 0; j-- {
			rb := openB[j]
			if m.isConsistent(ra, rb) {
				qOffset := ra.QueryOffset
				qInset := rb.QueryInset
				if ra.RC {
					ra, rb = rb, ra
				}
				combined := &Mapping{Start: ra.Start, End: rb.End, Query: ra.Query, QueryOffset: qOffset, QueryInset: qInset, RC: ra.RC, ids: ra.ids + rb.ids}
				if matched == nil {
					matched = make([]*Mapping, 0, i+1)
				}
				matched = append(matched, combined)
				if ra.RC {  
					ra, rb = rb, ra
				}
				openA[i] = openA[len(openA)-1]
				openA = openA[:len(openA)-1]
				openB[j] = openB[len(openB)-1]
				openB = openB[:len(openB)-1]
				break  
			}
		}
	}
	return openA, openB, matched
}
func (m *mapper) findSplitPoint(query sequence.Sequence, openA, openB []*Mapping, left, right int, aligner seeds.Aligner) {
	for right-left >= m.edgeSize {
		start := (right + left - m.edgeSize) / 2
		end := start + m.edgeSize
		mid := m.performMapping(query.SubSequence(start, end),aligner)
		newLeft := left
		newRight := right
		afterA := 0  
		afterB := 0
		for _, mm := range mid {
			mm.Query = query
			for _, ma := range openA {
				if m.isConsistent(ma, mm) {
					ma.QueryInset = mm.QueryInset
					ma.ids += mm.ids
					if ma.RC {
						ma.Start = mm.Start
					} else {
						ma.End = mm.End
					}
					midMatched := query.Len() - mm.QueryInset - mm.QueryOffset
					if midMatched > afterA {
						afterA = midMatched
					}
					if query.Len()-mm.QueryInset > newLeft {
						newLeft = query.Len() - mm.QueryInset
					}
					break
				}
			}
			if afterA < (m.edgeSize*2)/3 {
				for _, mb := range openB {
					if m.isConsistent(mm, mb) {
						mb.QueryOffset = mm.QueryOffset
						mb.ids += mm.ids
						if mb.RC {
							mb.End = mm.End
						} else {
							mb.Start = mm.Start
						}
						midMatched := query.Len() - mm.QueryInset - mm.QueryOffset
						if midMatched > afterB {
							afterB = midMatched
						}
						if mm.QueryOffset < newRight {
							newRight = mm.QueryOffset
						}
						break
					}
				}
			}
		}
		if afterA > 0 && afterB > 0 {
			empty := make([]*Mapping, 0)
			if newLeft-left > m.edgeSize*2 {  
				m.findSplitPoint(query, openA, empty, newLeft-m.edgeSize*2, newLeft-m.edgeSize, aligner)
			}
			if right-newRight > m.edgeSize*2 {
				m.findSplitPoint(query, empty, openB, newRight+m.edgeSize, newRight+m.edgeSize*2, aligner)
			}
			return  
		}
		if afterA == 0 && afterB == 0 {
			empty := make([]*Mapping, 0)
			if len(openA) > 0 {
				m.findSplitPoint(query, openA, empty, left, start, aligner)
			}
			if len(openB) > 0 {
				m.findSplitPoint(query, empty, openB, end, right, aligner)
			}
			return
		}
		left = newLeft
		right = newRight
	}
}
func (m *mapper) GetRepeats(start, end int, aligner seeds.Aligner) (starts, ends []int) {
	query := m.reference.SubSequence(start, end)
	hits := m.performMapping(query,aligner)
	hits = removeDominated(hits, hits, query.Len())
	starts = make([]int, len(hits))
	ends = make([]int, len(hits))
	for i, mapping := range hits {
		starts[i] = mapping.Start
		ends[i] = mapping.End
	}
	return starts, ends
}