func inferLongestCombinedSpan(gL, gR geneRangeInfo, opts Opts) (fi FusionInfo) {
	fi.G1ID = gL.geneID
	fi.G2ID = gR.geneID
	fi.FusionOrder = true
	prefixLenL := prefixLength(gL.ranges)
	prefixLenR := prefixLength(gR.ranges)
	suffixLenL := suffixLength(gL.ranges)
	suffixLenR := suffixLength(gR.ranges)
	overlap := 0
	maxSpan := 0
	var iL, iR int
	for iL < len(gL.ranges) && iR < len(gR.ranges) {
		if overlap >= opts.MaxHomology {
			break
		}
		rangeL := gL.ranges[iL]
		rangeR := gR.ranges[iR]
		if rangeL.End <= rangeR.End {
			if rangeL.End < rangeR.Start {
				if (rangeL.readType() == R1 && rangeR.readType() == R2) ||
					posSpan(rangeR.Start, rangeL.End)+1 <= opts.MaxGap {
					jointSpan := prefixLenL[iL] + suffixLenR[iR]
					if jointSpan > maxSpan {
						maxSpan = jointSpan
						fi.G1Span = prefixLenL[iL]
						fi.G2Span = suffixLenR[iR]
						fi.JointSpan = maxSpan
						fi.FusionOrder = true
						fi.G1Range = newCrossReadPosRange(gL.ranges[0].Start, rangeL.End)
						fi.G2Range = newCrossReadPosRange(rangeR.Start, gR.ranges[len(gR.ranges)-1].End)
					}
				}
			} else {
				if rangeL.Start <= rangeR.Start {
					jointSpan := posSpan(rangeR.End, rangeL.Start)
					overlap += posSpan(rangeL.End, rangeR.Start)
					if iL > 0 {
						jointSpan += prefixLenL[iL-1]
					}
					if iR < len(gR.ranges)-1 {
						jointSpan += suffixLenR[iR+1]
					}
					if jointSpan > maxSpan {
						maxSpan = jointSpan
						if prefixLenL[iL] >= suffixLenR[iR] {
							fi.G1Span = prefixLenL[iL]
							fi.G2Span = maxSpan - prefixLenL[iL]
							fi.JointSpan = maxSpan
							fi.FusionOrder = true
							fi.G1Range = newCrossReadPosRange(gL.ranges[0].Start, rangeL.End)
							fi.G2Range = newCrossReadPosRange(rangeL.End, gR.ranges[len(gR.ranges)-1].End)
						} else {
							fi.G1Span = maxSpan - suffixLenR[iR]
							fi.G2Span = suffixLenR[iR]
							fi.JointSpan = maxSpan
							fi.FusionOrder = true
							fi.G1Range = newCrossReadPosRange(gL.ranges[0].Start, rangeR.Start)
							fi.G2Range = newCrossReadPosRange(rangeR.Start, gR.ranges[len(gR.ranges)-1].End)
						}
					}
				} else {
					overlap += posSpan(rangeL.End, rangeL.Start)
				}
			}
			iL++
		} else {
			if rangeL.Start > rangeR.End {
				if (rangeL.readType() == R2 && rangeR.readType() == R1) ||
					posSpan(rangeL.Start, rangeR.End)+1 <= opts.MaxGap {
					jointSpan := prefixLenR[iR] + suffixLenL[iL]
					if jointSpan > maxSpan {
						maxSpan = jointSpan
						fi.G1Span = suffixLenL[iL]
						fi.G2Span = prefixLenR[iR]
						fi.JointSpan = maxSpan
						fi.FusionOrder = false
						fi.G1Range = newCrossReadPosRange(rangeL.Start, gL.ranges[len(gL.ranges)-1].End)
						fi.G2Range = newCrossReadPosRange(gR.ranges[0].Start, rangeR.End)
					}
				}
			} else {
				if rangeL.Start >= rangeR.Start {
					jointSpan := posSpan(rangeL.End, rangeR.Start)
					overlap += posSpan(rangeR.End, rangeL.Start)
					if iL < len(gL.ranges)-1 {
						jointSpan += suffixLenL[iL+1]
					}
					if iR > 0 {
						jointSpan += prefixLenR[iR-1]
					}
					if jointSpan > maxSpan {
						maxSpan = jointSpan
						if suffixLenL[iL] >= prefixLenR[iR] {
							fi.G1Span = suffixLenL[iL]
							fi.G2Span = maxSpan - suffixLenL[iL]
							fi.JointSpan = maxSpan
							fi.FusionOrder = false
							fi.G1Range = newCrossReadPosRange(rangeL.Start, gL.ranges[len(gL.ranges)-1].End)
							fi.G2Range = newCrossReadPosRange(gR.ranges[0].Start, rangeL.Start)
						} else {
							fi.G1Span = maxSpan - prefixLenR[iR]
							fi.G2Span = prefixLenR[iR]
							fi.JointSpan = maxSpan
							fi.FusionOrder = false
							fi.G1Range = newCrossReadPosRange(rangeR.End, gL.ranges[len(gL.ranges)-1].End)
							fi.G2Range = newCrossReadPosRange(gR.ranges[0].Start, rangeR.End)
						}
					}
				} else {
					overlap += posSpan(rangeR.End, rangeR.Start)
				}
			}
			iR++
		}
	}
	if len(gL.ranges) > 0 && prefixLenL[len(prefixLenL)-1] > maxSpan {
		maxSpan = prefixLenL[len(prefixLenL)-1]
		fi.G1Span = maxSpan
		fi.G2Span = 0
		fi.JointSpan = maxSpan
		fi.FusionOrder = true
		fi.G1Range = newCrossReadPosRange(gL.ranges[0].Start, gL.ranges[len(gL.ranges)-1].End)
		fi.G2Range = newCrossReadPosRange(0, 0)
	} else if len(gR.ranges) > 0 && prefixLenR[len(prefixLenR)-1] > maxSpan {
		maxSpan = prefixLenR[len(prefixLenR)-1]
		fi.G1Span = maxSpan
		fi.G2Span = 0
		fi.JointSpan = maxSpan
		fi.FusionOrder = true
		fi.G1Range = newCrossReadPosRange(0, 0)
		fi.G2Range = newCrossReadPosRange(gR.ranges[0].Start, gR.ranges[len(gR.ranges)-1].End)
	} else if overlap >= opts.MaxHomology {
		fi = FusionInfo{G1ID: gL.geneID, G2ID: gR.geneID, FusionOrder: true}
	}
	return
}
Algorithm to infer the longest combined span on a fragment by a given gene
pair. More specifically, gL and gR specify the ranges of two
genes under consideration.
The function
returns inferred FusionSpan. If lhs -- rhs span the same order wrt r1 -- r2
FusionSpan.order = true.
package fusion
import (
	"sort"
	"strings"
	"github.com/grailbio/base/log"
)
const ReproduceBug = true
type geneRangeInfo struct {
	geneID GeneID
	r1Span, r2Span int
	ranges []PosRange
}
func (gr *geneRangeInfo) totalSpan() int { return gr.r1Span + gr.r2Span }
type FusionInfo struct {
	G1ID GeneID
	G2ID GeneID
	G1Span int
	G2Span int
	JointSpan int
	FusionOrder bool
	G1Range CrossReadPosRange
	G2Range CrossReadPosRange
}
func (fi FusionInfo) Name(geneDB *GeneDB, opts Opts) string {
	buf := strings.Builder{}
	order := CosmicOrder
	if opts.Denovo {
		order = AlphabeticalOrder
	}
	g1, g2 := SortGenePair(geneDB, fi.G1ID, fi.G2ID, order)
	buf.WriteString(geneDB.GeneInfo(g1).Gene)
	buf.WriteByte('/')
	buf.WriteString(geneDB.GeneInfo(g2).Gene)
	return buf.String()
}
func inferGeneRangeInfo(frag Fragment, geneDB *GeneDB, kmerLength int) []geneRangeInfo {
	geneRanges := map[GeneID][]PosRange{}
	for _, km := range frag.kmers {
		k := km.minKmer()
		giter := geneDB.findByKmer(k)
		for idx := 0; ; idx++ {
			geneID := giter.get(idx)
			if geneID == invalidGeneID {
				break
			}
			end := km.pos + Pos(kmerLength)
			if ranges, ok := geneRanges[geneID]; ok && km.pos <= ranges[len(ranges)-1].End {
				geneRanges[geneID][len(ranges)-1].End = end
			} else {
				geneRanges[geneID] = append(geneRanges[geneID], newPosRange(km.pos, end))
			}
		}
	}
	geneRangeVec := make([]geneRangeInfo, 0, len(geneRanges))
	for geneID, ranges := range geneRanges {
		ri := geneRangeInfo{geneID: geneID}
		for _, rr := range ranges {
			switch rr.readType() {
			case R1:
				ri.r1Span += rr.span()
			case R2:
				ri.r2Span += rr.span()
			default:
				panic(rr)
			}
		}
		ri.ranges = ranges
		geneRangeVec = append(geneRangeVec, ri)
	}
	sort.Slice(geneRangeVec, func(i, j int) bool {
		gri := geneRangeVec[i]
		grj := geneRangeVec[j]
		giSpan := gri.r1Span + gri.r2Span
		gjSpan := grj.r1Span + grj.r2Span
		if giSpan != gjSpan {
			return giSpan > gjSpan
		}
		return gri.geneID < grj.geneID
	})
	return geneRangeVec
}
func inferCandidatePair(
	geneDB *GeneDB,
	fragName string,
	r1SeqLen, r2SeqLen int,
	geneRangeVec []geneRangeInfo,
	opts Opts) []FusionInfo {
	var fusionSpanInfoVec []FusionInfo
	const minReadSpanPerc = 50.0
	const fragSpanPercLarge = 90.0
	const fragSpanPercSmall = 80.0
	r1MinSpan := max(opts.KmerLength, r1SeqLen*minReadSpanPerc/100.0)
	r2MinSpan := max(opts.KmerLength, r2SeqLen*minReadSpanPerc/100.0)
	totalLen := r1SeqLen + r2SeqLen
	fragSpanLarge := totalLen * fragSpanPercLarge / 100
	fragSpanSmall := totalLen * fragSpanPercSmall / 100
	const goodSpanEvidence = 30
	maxSpan := 0
	for gi, gr := range geneRangeVec {
		if gr.totalSpan() > totalLen-opts.KmerLength {
			if !ReproduceBug {
				if len(geneRangeVec) > 1 {
					break
				}
			}
		}
		if gr.r1Span < r1MinSpan && gr.r2Span < r2MinSpan {
			break
		}
		for _, gr2 := range geneRangeVec[gi+1:] {
			minFragSpan := fragSpanLarge
			if gr.totalSpan() >= goodSpanEvidence && gr2.totalSpan() >= goodSpanEvidence {
				minFragSpan = fragSpanSmall
			}
			minFragSpan = max(maxSpan, minFragSpan)
			if gr.totalSpan()+gr2.totalSpan() < minFragSpan {
				break
			}
			if !opts.Denovo && !geneDB.IsFusionPair(gr.geneID, gr2.geneID) && !geneDB.IsFusionPair(gr2.geneID, gr.geneID) {
				break
			}
			fusionSpan := inferLongestCombinedSpan(gr, gr2, opts)
			if fusionSpan.JointSpan-gr.totalSpan() >= opts.MinSpan {
				if !fusionSpan.FusionOrder {
					fusionSpan.G2Span, fusionSpan.G1Span = fusionSpan.G1Span, fusionSpan.G2Span
					fusionSpan.G2Range, fusionSpan.G1Range = fusionSpan.G1Range, fusionSpan.G2Range
				}
			} else if fusionSpan.G2Span*fusionSpan.G1Span != 0 {
				fusionSpan.G1Span = fusionSpan.JointSpan
				fusionSpan.G2Span = 0
			}
			if fusionSpan.JointSpan >= minFragSpan {
				fusionSpanInfoVec = append(fusionSpanInfoVec, fusionSpan)
				if maxSpan > fusionSpan.JointSpan {
					log.Error.Printf("maxspan: %v %v %v", maxSpan, minFragSpan, fusionSpan.JointSpan)
				}
				maxSpan = fusionSpan.JointSpan
			}
		}
	}
	sort.SliceStable(fusionSpanInfoVec, func(i, j int) bool {
		return fusionSpanInfoVec[i].JointSpan > fusionSpanInfoVec[j].JointSpan
	})
	sameFusionSpan := func(f1, f2 FusionInfo) bool {
		return f1.G1Span == f2.G1Span &&
			f1.G2Span == f2.G2Span &&
			f1.JointSpan == f2.JointSpan && (
		(f1.G1Range.Equal(f2.G1Range) && f1.G2Range.Equal(f2.G2Range)) ||
			(f1.G1Range.Equal(f2.G2Range) && f1.G2Range.Equal(f2.G1Range)))
	}
	if len(fusionSpanInfoVec) > 0 {
		for _, fs := range fusionSpanInfoVec {
			if fs.JointSpan < maxSpan {
				break
			}
			if fs.G1Span == 0 || fs.G2Span == 0 {
				return nil
			}
		}
		if len(fusionSpanInfoVec) == 1 {
			return fusionSpanInfoVec
		}
		fi0 := fusionSpanInfoVec[0]
		var i int
		for i = 1; i < len(fusionSpanInfoVec); i++ {
			fi := fusionSpanInfoVec[i]
			if fi.JointSpan < fi0.JointSpan {
				break
			}
			if !sameFusionSpan(fi0, fi) {
				log.Error.Printf("Dropping candidates with equal length for fragment %s", fragName)
				return nil
			}
		}
		return fusionSpanInfoVec[:i]
	}
	return nil
}