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)
}
maps the (potentially noisy) ends
if any map end-to-end then these are merged and all others discarded
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)
}