mirror of
https://github.com/gonum/gonum.git
synced 2025-10-08 16:40:06 +08:00
368 lines
9.2 KiB
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
|
|
}
|