Files
gonum/floats.go

368 lines
9.2 KiB
Go

// Copyright 2013 The Gonum Authors. All rights reserved.
// Use of this code is governed by a BSD-style
// license that can be found in the LICENSE file
package floats
import (
"errors"
"math"
)
// Add returns the element-wise sum of all the slices with the
// results stored in the first slice.
// For computational efficiency, it is assumed that all of
// the variadic arguments have the same length. If this is
// in doubt, EqLen can be used.
func Add(dst []float64, slices ...[]float64) []float64 {
if len(slices) == 0 {
return nil
}
if len(dst) != len(slices[0]) {
panic("floats: length of destination does not match length of the slices")
}
for _, slice := range slices {
for j, val := range slice {
dst[j] += val
}
}
return dst
}
// AddConst adds the value c to all of the values in s
func AddConst(c float64, s []float64) {
for i := range s {
s[i] += c
}
}
// ApplyFunc applies a function f (math.Exp, math.Sin, etc.) to every element
// of the slice s
func Apply(f func(float64) float64, s []float64) {
for i, val := range s {
s[i] = f(val)
}
}
// Count applies the function f to every element of s and returns the number
// of times the function returned true
func Count(f func(float64) bool, s []float64) int {
var n int
for _, val := range s {
if f(val) {
n++
}
}
return n
}
// Cumprod finds the cumulative product of the first i elements in
// s and puts them in place into the ith element of the
// destination. Panic will occur if lengths of do not match
func CumProd(dst, s []float64) []float64 {
if len(dst) != len(s) {
panic("floats: length of destination does not match length of the source")
}
dst[0] = s[0]
for i := 1; i < len(s); i++ {
dst[i] = dst[i-1] * s[i]
}
return dst
}
// Cumsum finds the cumulative sum of the first i elements in
// s and puts them in place into the ith element of the
// destination. Panic will occur if lengths of arguments do not match
func CumSum(dst, s []float64) []float64 {
if len(dst) != len(s) {
panic("floats: length of destination does not match length of the source")
}
dst[0] = s[0]
for i := 1; i < len(s); i++ {
dst[i] = dst[i-1] + s[i]
}
return dst
}
// Dot computes the dot product of s1 and s2, i.e.
// sum_{i = 1}^N s1[n]*s2[n]
// Panic will occur if lengths of arguments do not match
func Dot(s1, s2 []float64) float64 {
if len(s1) != len(s2) {
panic("floats: lengths of the slices do not match")
}
var sum float64
for i, val := range s1 {
sum += val * s2[i]
}
return sum
}
// Eq returns false if the slices have different lengths
// or if |s1[i] - s2[i]| > tol for any i.
func Eq(s1, s2 []float64, tol float64) bool {
if len(s1) != len(s2) {
return false
}
for i, val := range s1 {
if math.Abs(s2[i]-val) > tol {
return false
}
}
return true
}
// Eqlen returns true if all of the slices have equal length,
// and false otherwise. Returns true if there are no input slices
func EqLen(slices ...[]float64) bool {
// This length check is needed: http://play.golang.org/p/sdty6YiLhM
if len(slices) == 0 {
return true
}
l := len(slices[0])
for i := 1; i < len(slices); i++ {
if len(slices[i]) != l {
return false
}
}
return true
}
// Find applies f to every element of s and returns the first
// k elements for which the f returns true, or all such elements
// if k < 0.
// Find will reslice inds to have 0 length, and will append
// found indices to inds.
// If k > 0 and there are fewer than k elements in s satisfying f,
// all of the found elements will be returned along with an error
func Find(inds []int, f func(float64) bool, s []float64, k int) ([]int, error) {
// inds is also returned to allow for calling with nil
// Reslice inds to have zero length
inds = inds[:0]
// If zero elements requested, can just return
if k == 0 {
return inds, nil
}
// If k < 0, return all of the found indices
if k < 0 {
for i, val := range s {
if f(val) {
inds = append(inds, i)
}
}
return inds, nil
}
// Otherwise, find the first k elements
nFound := 0
for i, val := range s {
if f(val) {
inds = append(inds, i)
nFound++
if nFound == k {
return inds, nil
}
}
}
// Finished iterating over the loop, which means k elements were not found
return inds, errors.New("floats: insufficient elements found")
}
// LogSpan returns a set of n equally spaced points in log space between,
// l and u where N is equal to len(dst). The first element of the
// resulting dst will be l and the final element of dst will be u.
// Panics if len(dst) < 2
// Note that this call will return NaNs if either l or u are negative, and
// will return all zeros if l or u is zero.
func LogSpan(dst []float64, l, u float64) []float64 {
Span(dst, math.Log(l), math.Log(u))
Apply(math.Exp, dst)
return dst
}
// Logsumexp returns the log of the sum of the exponentials of the values in s
func LogSumExp(s []float64) (lse float64) {
// Want to do this in a numerically stable way which avoids
// overflow and underflow
// First, find the maximum value in the slice.
maxval, _ := Max(s)
if math.IsInf(maxval, 0) {
// If it's infinity either way, the logsumexp will be infinity as well
// returning now avoids NaNs
return maxval
}
// Compute the sumexp part
for _, val := range s {
lse += math.Exp(val - maxval)
}
// Take the log and add back on the constant taken out
lse = math.Log(lse) + maxval
return lse
}
// Max returns the maximum value in the slice and the location of
// the maximum value. If the input slice is empty, the code will panic
func Max(s []float64) (max float64, ind int) {
max = s[0]
ind = 0
for i, val := range s {
if val > max {
max = val
ind = i
}
}
return max, ind
}
// Min returns the minimum value in the slice and the index of
// the minimum value. If the input slice is empty, zero is returned
// as the minimum value and -1 is returned as the index.
func Min(s []float64) (min float64, ind int) {
min = s[0]
ind = 0
for i, val := range s {
if val < min {
min = val
ind = i
}
}
return min, ind
}
// Nearest returns the index of the element in s
// whose value is nearest to v. If several such
// elements exist, the lowest index is returned
func Nearest(s []float64, v float64) (ind int) {
dist := math.Abs(v - s[0])
ind = 0
for i, val := range s {
newDist := math.Abs(v - val)
if newDist < dist {
dist = newDist
ind = i
}
}
return
}
// NearestInSpan return the index of a hypothetical vector created
// by Span with length n and bounds l and u whose value is closest
// to v. Assumes u > l. If the value is greater than u or less than
// l, the function will panic.
func NearestWithinSpan(n int, l, u float64, v float64) int {
if v < l || v > u {
panic("floats: value outside span bounds")
}
// Can't guarantee anything about exactly halfway between
// because of floating point weirdness
return int((float64(n)-1)/(u-l)*(v-l) + 0.5)
}
// Norm returns the L norm of the slice S, defined as
// (sum_{i=1}^N s[i]^N)^{1/N}
// Special cases:
// L = math.Inf(1) gives the maximum value
// Does not correctly compute the zero norm (use Count)
func Norm(s []float64, L float64) (norm float64) {
// Should this complain if L is not positive?
// Should this be done in log space for better numerical stability?
// would be more cost
// maybe only if L is high?
if L == 2 {
for _, val := range s {
norm += val * val
}
return math.Pow(norm, 0.5)
}
if L == 1 {
for _, val := range s {
norm += math.Abs(val)
}
return norm
}
if math.IsInf(L, 1) {
norm, _ = Max(s)
return norm
}
for _, val := range s {
norm += math.Pow(math.Abs(val), L)
}
return math.Pow(norm, 1/L)
}
// Prod returns the product of the elements of the slice
// Returns 1 if len(s) = 0
func Prod(s []float64) (prod float64) {
prod = 1
for _, val := range s {
prod *= val
}
return prod
}
// Scale multiplies every element in s by c
func Scale(c float64, s []float64) {
for i := range s {
s[i] *= c
}
}
// Span returns a set of N equally spaced points between l and u, where N
// is equal to the length of the destination. The first element of the destination
// is l, the final element of the destination is u.
// Panics if len(dst) < 2
func Span(dst []float64, l, u float64) []float64 {
n := len(dst)
if n < 2 {
panic("floats: destination must have length >1")
}
step := (u - l) / float64(n-1)
for i := range dst {
dst[i] = l + step*float64(i)
}
return dst
}
// Sub subtracts, element-wise, the first argument from the second. Assumes
// the lengths of s and t match (can be tested with EqLen)
func Sub(s, t []float64) {
if len(s) != len(t) {
panic("floats: length of the slices do not match")
}
for i, val := range t {
s[i] -= val
}
}
// SubTo subtracts, element-wise, the first argument from the second and
// stores the result in dest. Panics if the lengths of s and t do not match
func SubTo(dst, s, t []float64) []float64 {
if len(s) != len(t) {
panic("floats: length of subtractor and subtractee do not match")
}
if len(dst) != len(s) {
if dst == nil {
dst = make([]float64, len(s))
} else {
panic("floats: length of destination does not match length of subtractor")
}
}
for i, val := range t {
dst[i] = s[i] - val
}
return dst
}
// Sum returns the sum of the elements of the slice
func Sum(s []float64) (sum float64) {
for _, val := range s {
sum += val
}
return
}