func KsStat(vector govector.Vector, conf AnomalyzerConf) float64 {
	reference, active, err := extractWindows(vector, conf.referenceSize, conf.ActiveSize, conf.ActiveSize)
	if err != nil {
		return NA
	}
	n1 := len(reference)
	n2 := len(active)
	if n1%n2 != 0 {
		return NA
	}
	activeEcdf := active.Ecdf()
	refEcdf := reference.Ecdf()
	min := math.Min(reference.Min(), active.Min())
	max := math.Max(reference.Max(), active.Max())
	interpolated := interpolate(min, max, n1+n2)
	activeDist := interpolated.Apply(activeEcdf)
	refDist := interpolated.Apply(refEcdf)
	d := 0.0
	for i := 0; i < n1+n2; i++ {
		d = math.Max(d, math.Abs(activeDist[i]-refDist[i]))
	}
	return d
}
Calculate a Kolmogorov-Smirnov test statistic.
package anomalyzer
import (
	"fmt"
	"github.com/drewlanenga/govector"
	"math"
)
type Algorithm func(govector.Vector, AnomalyzerConf) float64
var (
	Algorithms = map[string]Algorithm{
		"magnitude": MagnitudeTest,
		"diff":      DiffTest,
		"highrank":  RankTest,
		"lowrank":   ReverseRankTest,
		"cdf":       CDFTest,
		"fence":     FenceTest,
		"ks":        BootstrapKsTest,
	}
)
func identity(anything interface{}) interface{} {
	return anything
}
func cap(x, min, max float64) float64 {
	return math.Max(math.Min(x, max), min)
}
func constant(x float64) float64 {
	return 0.2
}
func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}
func min(x, y int) int {
	if y < x {
		return y
	}
	return x
}
func extractWindows(vector govector.Vector, refSize, activeSize, minRefSize int) (govector.Vector, govector.Vector, error) {
	n := len(vector)
	activeSize = min(activeSize, n)
	refSize = min(refSize, n-activeSize)
	if refSize < minRefSize {
		return nil, nil, fmt.Errorf("Reference size must be at least as big as active size")
	}
	return vector[n-activeSize-refSize : n-activeSize], vector[n-activeSize:], nil
}
func FenceTest(vector govector.Vector, conf AnomalyzerConf) float64 {
	_, active, _ := extractWindows(vector, conf.referenceSize, conf.ActiveSize, -1)
	x := active.Mean()
	distance := 0.0
	if conf.LowerBound == NA {
		distance = x / conf.UpperBound
	} else {
		bound := (conf.UpperBound - conf.LowerBound) / 2
		mid := conf.LowerBound + bound
		distance = (math.Abs(x - mid)) / bound
	}
	return weightExp(cap(distance, 0, 1), 10)
}
func weightExp(x, base float64) float64 {
	return (math.Pow(base, x) - 1) / (math.Pow(base, 1) - 1)
}
func DiffTest(vector govector.Vector, conf AnomalyzerConf) float64 {
	ranks := vector.RelDiff().Apply(math.Abs).Rank()
	_, active, err := extractWindows(ranks, conf.referenceSize-1, conf.ActiveSize, conf.ActiveSize)
	if err != nil {
		return NA
	}
	activeSum := active.Sum()
	i := 0
	significant := 0
	for i < conf.PermCount {
		permRanks := vector.Shuffle().RelDiff().Apply(math.Abs).Rank()
		_, permActive, _ := extractWindows(permRanks, conf.referenceSize-1, conf.ActiveSize, conf.ActiveSize)
		if permActive.Sum() < activeSum {
			significant++
		}
		i++
	}
	return float64(significant) / float64(conf.PermCount)
}
func RankTest(vector govector.Vector, conf AnomalyzerConf) float64 {
	return rankTest(vector, conf, lessThan)
}
func ReverseRankTest(vector govector.Vector, conf AnomalyzerConf) float64 {
	return rankTest(vector, conf, greaterThan)
}
type compare func(x, y float64) bool
func greaterThan(x, y float64) bool {
	if x > y {
		return true
	}
	return false
}
func lessThan(x, y float64) bool {
	if x < y {
		return true
	}
	return false
}
func rankTest(vector govector.Vector, conf AnomalyzerConf, comparison compare) float64 {
	ranks := vector.Rank()
	_, active, err := extractWindows(ranks, conf.referenceSize, conf.ActiveSize, conf.ActiveSize)
	if err != nil {
		return NA
	}
	activeSum := active.Sum()
	i := 0
	significant := 0
	for i < conf.PermCount {
		permRanks := vector.Shuffle().Rank()
		_, permActive, _ := extractWindows(permRanks, conf.referenceSize, conf.ActiveSize, conf.ActiveSize)
		permSum := permActive.Sum()
		if comparison(permSum, activeSum) {
			significant++
		}
		i++
	}
	return float64(significant) / float64(conf.PermCount)
}
func CDFTest(vector govector.Vector, conf AnomalyzerConf) float64 {
	diffs := vector.Diff().Apply(math.Abs)
	reference, active, err := extractWindows(diffs, conf.referenceSize-1, conf.ActiveSize, conf.ActiveSize)
	if err != nil {
		return NA
	}
	refEcdf := reference.Ecdf()
	activeDiff := active.Mean() - reference.Mean()
	percentile := refEcdf(activeDiff)
	return (2 * math.Abs(0.5-percentile))
}
func MagnitudeTest(vector govector.Vector, conf AnomalyzerConf) float64 {
	reference, active, err := extractWindows(vector, conf.referenceSize, conf.ActiveSize, 1)
	if err != nil {
		return NA
	}
	activeMean := active.Mean()
	refMean := reference.Mean()
	if refMean == 0 {
		if activeMean == 0 {
			return 0
		} else {
			return 1
		}
	}
	pdiff := math.Abs(activeMean-refMean) / refMean
	return pdiff
}