github.com/gopherd/gonum@v0.0.4/floats/floats.go (about) 1 // Copyright ©2013 The Gonum Authors. All rights reserved. 2 // Use of this code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package floats 6 7 import ( 8 "errors" 9 "math" 10 "sort" 11 12 "github.com/gopherd/gonum/floats/scalar" 13 "github.com/gopherd/gonum/internal/asm/f64" 14 ) 15 16 const ( 17 zeroLength = "floats: zero length slice" 18 shortSpan = "floats: slice length less than 2" 19 badLength = "floats: slice lengths do not match" 20 badDstLength = "floats: destination slice length does not match input" 21 ) 22 23 // Add adds, element-wise, the elements of s and dst, and stores the result in dst. 24 // It panics if the argument lengths do not match. 25 func Add(dst, s []float64) { 26 if len(dst) != len(s) { 27 panic(badDstLength) 28 } 29 f64.AxpyUnitaryTo(dst, 1, s, dst) 30 } 31 32 // AddTo adds, element-wise, the elements of s and t and 33 // stores the result in dst. 34 // It panics if the argument lengths do not match. 35 func AddTo(dst, s, t []float64) []float64 { 36 if len(s) != len(t) { 37 panic(badLength) 38 } 39 if len(dst) != len(s) { 40 panic(badDstLength) 41 } 42 f64.AxpyUnitaryTo(dst, 1, s, t) 43 return dst 44 } 45 46 // AddConst adds the scalar c to all of the values in dst. 47 func AddConst(c float64, dst []float64) { 48 f64.AddConst(c, dst) 49 } 50 51 // AddScaled performs dst = dst + alpha * s. 52 // It panics if the slice argument lengths do not match. 53 func AddScaled(dst []float64, alpha float64, s []float64) { 54 if len(dst) != len(s) { 55 panic(badLength) 56 } 57 f64.AxpyUnitaryTo(dst, alpha, s, dst) 58 } 59 60 // AddScaledTo performs dst = y + alpha * s, where alpha is a scalar, 61 // and dst, y and s are all slices. 62 // It panics if the slice argument lengths do not match. 63 // 64 // At the return of the function, dst[i] = y[i] + alpha * s[i] 65 func AddScaledTo(dst, y []float64, alpha float64, s []float64) []float64 { 66 if len(s) != len(y) { 67 panic(badLength) 68 } 69 if len(dst) != len(y) { 70 panic(badDstLength) 71 } 72 f64.AxpyUnitaryTo(dst, alpha, s, y) 73 return dst 74 } 75 76 // argsort is a helper that implements sort.Interface, as used by 77 // Argsort and ArgsortStable. 78 type argsort struct { 79 s []float64 80 inds []int 81 } 82 83 func (a argsort) Len() int { 84 return len(a.s) 85 } 86 87 func (a argsort) Less(i, j int) bool { 88 return a.s[i] < a.s[j] 89 } 90 91 func (a argsort) Swap(i, j int) { 92 a.s[i], a.s[j] = a.s[j], a.s[i] 93 a.inds[i], a.inds[j] = a.inds[j], a.inds[i] 94 } 95 96 // Argsort sorts the elements of dst while tracking their original order. 97 // At the conclusion of Argsort, dst will contain the original elements of dst 98 // but sorted in increasing order, and inds will contain the original position 99 // of the elements in the slice such that dst[i] = origDst[inds[i]]. 100 // It panics if the argument lengths do not match. 101 func Argsort(dst []float64, inds []int) { 102 if len(dst) != len(inds) { 103 panic(badDstLength) 104 } 105 for i := range dst { 106 inds[i] = i 107 } 108 109 a := argsort{s: dst, inds: inds} 110 sort.Sort(a) 111 } 112 113 // ArgsortStable sorts the elements of dst while tracking their original order and 114 // keeping the original order of equal elements. At the conclusion of ArgsortStable, 115 // dst will contain the original elements of dst but sorted in increasing order, 116 // and inds will contain the original position of the elements in the slice such 117 // that dst[i] = origDst[inds[i]]. 118 // It panics if the argument lengths do not match. 119 func ArgsortStable(dst []float64, inds []int) { 120 if len(dst) != len(inds) { 121 panic(badDstLength) 122 } 123 for i := range dst { 124 inds[i] = i 125 } 126 127 a := argsort{s: dst, inds: inds} 128 sort.Stable(a) 129 } 130 131 // Count applies the function f to every element of s and returns the number 132 // of times the function returned true. 133 func Count(f func(float64) bool, s []float64) int { 134 var n int 135 for _, val := range s { 136 if f(val) { 137 n++ 138 } 139 } 140 return n 141 } 142 143 // CumProd finds the cumulative product of the first i elements in 144 // s and puts them in place into the ith element of the 145 // destination dst. 146 // It panics if the argument lengths do not match. 147 // 148 // At the return of the function, dst[i] = s[i] * s[i-1] * s[i-2] * ... 149 func CumProd(dst, s []float64) []float64 { 150 if len(dst) != len(s) { 151 panic(badDstLength) 152 } 153 if len(dst) == 0 { 154 return dst 155 } 156 return f64.CumProd(dst, s) 157 } 158 159 // CumSum finds the cumulative sum of the first i elements in 160 // s and puts them in place into the ith element of the 161 // destination dst. 162 // It panics if the argument lengths do not match. 163 // 164 // At the return of the function, dst[i] = s[i] + s[i-1] + s[i-2] + ... 165 func CumSum(dst, s []float64) []float64 { 166 if len(dst) != len(s) { 167 panic(badDstLength) 168 } 169 if len(dst) == 0 { 170 return dst 171 } 172 return f64.CumSum(dst, s) 173 } 174 175 // Distance computes the L-norm of s - t. See Norm for special cases. 176 // It panics if the slice argument lengths do not match. 177 func Distance(s, t []float64, L float64) float64 { 178 if len(s) != len(t) { 179 panic(badLength) 180 } 181 if len(s) == 0 { 182 return 0 183 } 184 if L == 2 { 185 return f64.L2DistanceUnitary(s, t) 186 } 187 var norm float64 188 if L == 1 { 189 for i, v := range s { 190 norm += math.Abs(t[i] - v) 191 } 192 return norm 193 } 194 if math.IsInf(L, 1) { 195 for i, v := range s { 196 absDiff := math.Abs(t[i] - v) 197 if absDiff > norm { 198 norm = absDiff 199 } 200 } 201 return norm 202 } 203 for i, v := range s { 204 norm += math.Pow(math.Abs(t[i]-v), L) 205 } 206 return math.Pow(norm, 1/L) 207 } 208 209 // Div performs element-wise division dst / s 210 // and stores the value in dst. 211 // It panics if the argument lengths do not match. 212 func Div(dst, s []float64) { 213 if len(dst) != len(s) { 214 panic(badLength) 215 } 216 f64.Div(dst, s) 217 } 218 219 // DivTo performs element-wise division s / t 220 // and stores the value in dst. 221 // It panics if the argument lengths do not match. 222 func DivTo(dst, s, t []float64) []float64 { 223 if len(s) != len(t) { 224 panic(badLength) 225 } 226 if len(dst) != len(s) { 227 panic(badDstLength) 228 } 229 return f64.DivTo(dst, s, t) 230 } 231 232 // Dot computes the dot product of s1 and s2, i.e. 233 // sum_{i = 1}^N s1[i]*s2[i]. 234 // It panics if the argument lengths do not match. 235 func Dot(s1, s2 []float64) float64 { 236 if len(s1) != len(s2) { 237 panic(badLength) 238 } 239 return f64.DotUnitary(s1, s2) 240 } 241 242 // Equal returns true when the slices have equal lengths and 243 // all elements are numerically identical. 244 func Equal(s1, s2 []float64) bool { 245 if len(s1) != len(s2) { 246 return false 247 } 248 for i, val := range s1 { 249 if s2[i] != val { 250 return false 251 } 252 } 253 return true 254 } 255 256 // EqualApprox returns true when the slices have equal lengths and 257 // all element pairs have an absolute tolerance less than tol or a 258 // relative tolerance less than tol. 259 func EqualApprox(s1, s2 []float64, tol float64) bool { 260 if len(s1) != len(s2) { 261 return false 262 } 263 for i, a := range s1 { 264 if !scalar.EqualWithinAbsOrRel(a, s2[i], tol, tol) { 265 return false 266 } 267 } 268 return true 269 } 270 271 // EqualFunc returns true when the slices have the same lengths 272 // and the function returns true for all element pairs. 273 func EqualFunc(s1, s2 []float64, f func(float64, float64) bool) bool { 274 if len(s1) != len(s2) { 275 return false 276 } 277 for i, val := range s1 { 278 if !f(val, s2[i]) { 279 return false 280 } 281 } 282 return true 283 } 284 285 // EqualLengths returns true when all of the slices have equal length, 286 // and false otherwise. It also returns true when there are no input slices. 287 func EqualLengths(slices ...[]float64) bool { 288 // This length check is needed: http://play.golang.org/p/sdty6YiLhM 289 if len(slices) == 0 { 290 return true 291 } 292 l := len(slices[0]) 293 for i := 1; i < len(slices); i++ { 294 if len(slices[i]) != l { 295 return false 296 } 297 } 298 return true 299 } 300 301 // Find applies f to every element of s and returns the indices of the first 302 // k elements for which the f returns true, or all such elements 303 // if k < 0. 304 // Find will reslice inds to have 0 length, and will append 305 // found indices to inds. 306 // If k > 0 and there are fewer than k elements in s satisfying f, 307 // all of the found elements will be returned along with an error. 308 // At the return of the function, the input inds will be in an undetermined state. 309 func Find(inds []int, f func(float64) bool, s []float64, k int) ([]int, error) { 310 // inds is also returned to allow for calling with nil. 311 312 // Reslice inds to have zero length. 313 inds = inds[:0] 314 315 // If zero elements requested, can just return. 316 if k == 0 { 317 return inds, nil 318 } 319 320 // If k < 0, return all of the found indices. 321 if k < 0 { 322 for i, val := range s { 323 if f(val) { 324 inds = append(inds, i) 325 } 326 } 327 return inds, nil 328 } 329 330 // Otherwise, find the first k elements. 331 nFound := 0 332 for i, val := range s { 333 if f(val) { 334 inds = append(inds, i) 335 nFound++ 336 if nFound == k { 337 return inds, nil 338 } 339 } 340 } 341 // Finished iterating over the loop, which means k elements were not found. 342 return inds, errors.New("floats: insufficient elements found") 343 } 344 345 // HasNaN returns true when the slice s has any values that are NaN and false 346 // otherwise. 347 func HasNaN(s []float64) bool { 348 for _, v := range s { 349 if math.IsNaN(v) { 350 return true 351 } 352 } 353 return false 354 } 355 356 // LogSpan returns a set of n equally spaced points in log space between, 357 // l and u where N is equal to len(dst). The first element of the 358 // resulting dst will be l and the final element of dst will be u. 359 // It panics if the length of dst is less than 2. 360 // Note that this call will return NaNs if either l or u are negative, and 361 // will return all zeros if l or u is zero. 362 // Also returns the mutated slice dst, so that it can be used in range, like: 363 // 364 // for i, x := range LogSpan(dst, l, u) { ... } 365 func LogSpan(dst []float64, l, u float64) []float64 { 366 Span(dst, math.Log(l), math.Log(u)) 367 for i := range dst { 368 dst[i] = math.Exp(dst[i]) 369 } 370 return dst 371 } 372 373 // LogSumExp returns the log of the sum of the exponentials of the values in s. 374 // Panics if s is an empty slice. 375 func LogSumExp(s []float64) float64 { 376 // Want to do this in a numerically stable way which avoids 377 // overflow and underflow 378 // First, find the maximum value in the slice. 379 maxval := Max(s) 380 if math.IsInf(maxval, 0) { 381 // If it's infinity either way, the logsumexp will be infinity as well 382 // returning now avoids NaNs 383 return maxval 384 } 385 var lse float64 386 // Compute the sumexp part 387 for _, val := range s { 388 lse += math.Exp(val - maxval) 389 } 390 // Take the log and add back on the constant taken out 391 return math.Log(lse) + maxval 392 } 393 394 // Max returns the maximum value in the input slice. If the slice is empty, Max will panic. 395 func Max(s []float64) float64 { 396 return s[MaxIdx(s)] 397 } 398 399 // MaxIdx returns the index of the maximum value in the input slice. If several 400 // entries have the maximum value, the first such index is returned. 401 // It panics if s is zero length. 402 func MaxIdx(s []float64) int { 403 if len(s) == 0 { 404 panic(zeroLength) 405 } 406 max := math.NaN() 407 var ind int 408 for i, v := range s { 409 if math.IsNaN(v) { 410 continue 411 } 412 if v > max || math.IsNaN(max) { 413 max = v 414 ind = i 415 } 416 } 417 return ind 418 } 419 420 // Min returns the minimum value in the input slice. 421 // It panics if s is zero length. 422 func Min(s []float64) float64 { 423 return s[MinIdx(s)] 424 } 425 426 // MinIdx returns the index of the minimum value in the input slice. If several 427 // entries have the minimum value, the first such index is returned. 428 // It panics if s is zero length. 429 func MinIdx(s []float64) int { 430 if len(s) == 0 { 431 panic(zeroLength) 432 } 433 min := math.NaN() 434 var ind int 435 for i, v := range s { 436 if math.IsNaN(v) { 437 continue 438 } 439 if v < min || math.IsNaN(min) { 440 min = v 441 ind = i 442 } 443 } 444 return ind 445 } 446 447 // Mul performs element-wise multiplication between dst 448 // and s and stores the value in dst. 449 // It panics if the argument lengths do not match. 450 func Mul(dst, s []float64) { 451 if len(dst) != len(s) { 452 panic(badLength) 453 } 454 for i, val := range s { 455 dst[i] *= val 456 } 457 } 458 459 // MulTo performs element-wise multiplication between s 460 // and t and stores the value in dst. 461 // It panics if the argument lengths do not match. 462 func MulTo(dst, s, t []float64) []float64 { 463 if len(s) != len(t) { 464 panic(badLength) 465 } 466 if len(dst) != len(s) { 467 panic(badDstLength) 468 } 469 for i, val := range t { 470 dst[i] = val * s[i] 471 } 472 return dst 473 } 474 475 // NearestIdx returns the index of the element in s 476 // whose value is nearest to v. If several such 477 // elements exist, the lowest index is returned. 478 // It panics if s is zero length. 479 func NearestIdx(s []float64, v float64) int { 480 if len(s) == 0 { 481 panic(zeroLength) 482 } 483 switch { 484 case math.IsNaN(v): 485 return 0 486 case math.IsInf(v, 1): 487 return MaxIdx(s) 488 case math.IsInf(v, -1): 489 return MinIdx(s) 490 } 491 var ind int 492 dist := math.NaN() 493 for i, val := range s { 494 newDist := math.Abs(v - val) 495 // A NaN distance will not be closer. 496 if math.IsNaN(newDist) { 497 continue 498 } 499 if newDist < dist || math.IsNaN(dist) { 500 dist = newDist 501 ind = i 502 } 503 } 504 return ind 505 } 506 507 // NearestIdxForSpan return the index of a hypothetical vector created 508 // by Span with length n and bounds l and u whose value is closest 509 // to v. That is, NearestIdxForSpan(n, l, u, v) is equivalent to 510 // Nearest(Span(make([]float64, n),l,u),v) without an allocation. 511 // It panics if n is less than two. 512 func NearestIdxForSpan(n int, l, u float64, v float64) int { 513 if n < 2 { 514 panic(shortSpan) 515 } 516 if math.IsNaN(v) { 517 return 0 518 } 519 520 // Special cases for Inf and NaN. 521 switch { 522 case math.IsNaN(l) && !math.IsNaN(u): 523 return n - 1 524 case math.IsNaN(u): 525 return 0 526 case math.IsInf(l, 0) && math.IsInf(u, 0): 527 if l == u { 528 return 0 529 } 530 if n%2 == 1 { 531 if !math.IsInf(v, 0) { 532 return n / 2 533 } 534 if math.Copysign(1, v) == math.Copysign(1, l) { 535 return 0 536 } 537 return n/2 + 1 538 } 539 if math.Copysign(1, v) == math.Copysign(1, l) { 540 return 0 541 } 542 return n / 2 543 case math.IsInf(l, 0): 544 if v == l { 545 return 0 546 } 547 return n - 1 548 case math.IsInf(u, 0): 549 if v == u { 550 return n - 1 551 } 552 return 0 553 case math.IsInf(v, -1): 554 if l <= u { 555 return 0 556 } 557 return n - 1 558 case math.IsInf(v, 1): 559 if u <= l { 560 return 0 561 } 562 return n - 1 563 } 564 565 // Special cases for v outside (l, u) and (u, l). 566 switch { 567 case l < u: 568 if v <= l { 569 return 0 570 } 571 if v >= u { 572 return n - 1 573 } 574 case l > u: 575 if v >= l { 576 return 0 577 } 578 if v <= u { 579 return n - 1 580 } 581 default: 582 return 0 583 } 584 585 // Can't guarantee anything about exactly halfway between 586 // because of floating point weirdness. 587 return int((float64(n)-1)/(u-l)*(v-l) + 0.5) 588 } 589 590 // Norm returns the L norm of the slice S, defined as 591 // (sum_{i=1}^N s[i]^L)^{1/L} 592 // Special cases: 593 // L = math.Inf(1) gives the maximum absolute value. 594 // Does not correctly compute the zero norm (use Count). 595 func Norm(s []float64, L float64) float64 { 596 // Should this complain if L is not positive? 597 // Should this be done in log space for better numerical stability? 598 // would be more cost 599 // maybe only if L is high? 600 if len(s) == 0 { 601 return 0 602 } 603 if L == 2 { 604 return f64.L2NormUnitary(s) 605 } 606 var norm float64 607 if L == 1 { 608 for _, val := range s { 609 norm += math.Abs(val) 610 } 611 return norm 612 } 613 if math.IsInf(L, 1) { 614 for _, val := range s { 615 norm = math.Max(norm, math.Abs(val)) 616 } 617 return norm 618 } 619 for _, val := range s { 620 norm += math.Pow(math.Abs(val), L) 621 } 622 return math.Pow(norm, 1/L) 623 } 624 625 // Prod returns the product of the elements of the slice. 626 // Returns 1 if len(s) = 0. 627 func Prod(s []float64) float64 { 628 prod := 1.0 629 for _, val := range s { 630 prod *= val 631 } 632 return prod 633 } 634 635 // Reverse reverses the order of elements in the slice. 636 func Reverse(s []float64) { 637 for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { 638 s[i], s[j] = s[j], s[i] 639 } 640 } 641 642 // Same returns true when the input slices have the same length and all 643 // elements have the same value with NaN treated as the same. 644 func Same(s, t []float64) bool { 645 if len(s) != len(t) { 646 return false 647 } 648 for i, v := range s { 649 w := t[i] 650 if v != w && !(math.IsNaN(v) && math.IsNaN(w)) { 651 return false 652 } 653 } 654 return true 655 } 656 657 // Scale multiplies every element in dst by the scalar c. 658 func Scale(c float64, dst []float64) { 659 if len(dst) > 0 { 660 f64.ScalUnitary(c, dst) 661 } 662 } 663 664 // ScaleTo multiplies the elements in s by c and stores the result in dst. 665 // It panics if the slice argument lengths do not match. 666 func ScaleTo(dst []float64, c float64, s []float64) []float64 { 667 if len(dst) != len(s) { 668 panic(badDstLength) 669 } 670 if len(dst) > 0 { 671 f64.ScalUnitaryTo(dst, c, s) 672 } 673 return dst 674 } 675 676 // Span returns a set of N equally spaced points between l and u, where N 677 // is equal to the length of the destination. The first element of the destination 678 // is l, the final element of the destination is u. 679 // It panics if the length of dst is less than 2. 680 // 681 // Span also returns the mutated slice dst, so that it can be used in range expressions, 682 // like: 683 // 684 // for i, x := range Span(dst, l, u) { ... } 685 func Span(dst []float64, l, u float64) []float64 { 686 n := len(dst) 687 if n < 2 { 688 panic(shortSpan) 689 } 690 691 // Special cases for Inf and NaN. 692 switch { 693 case math.IsNaN(l): 694 for i := range dst[:len(dst)-1] { 695 dst[i] = math.NaN() 696 } 697 dst[len(dst)-1] = u 698 return dst 699 case math.IsNaN(u): 700 for i := range dst[1:] { 701 dst[i+1] = math.NaN() 702 } 703 dst[0] = l 704 return dst 705 case math.IsInf(l, 0) && math.IsInf(u, 0): 706 for i := range dst[:len(dst)/2] { 707 dst[i] = l 708 dst[len(dst)-i-1] = u 709 } 710 if len(dst)%2 == 1 { 711 if l != u { 712 dst[len(dst)/2] = 0 713 } else { 714 dst[len(dst)/2] = l 715 } 716 } 717 return dst 718 case math.IsInf(l, 0): 719 for i := range dst[:len(dst)-1] { 720 dst[i] = l 721 } 722 dst[len(dst)-1] = u 723 return dst 724 case math.IsInf(u, 0): 725 for i := range dst[1:] { 726 dst[i+1] = u 727 } 728 dst[0] = l 729 return dst 730 } 731 732 step := (u - l) / float64(n-1) 733 for i := range dst { 734 dst[i] = l + step*float64(i) 735 } 736 return dst 737 } 738 739 // Sub subtracts, element-wise, the elements of s from dst. 740 // It panics if the argument lengths do not match. 741 func Sub(dst, s []float64) { 742 if len(dst) != len(s) { 743 panic(badLength) 744 } 745 f64.AxpyUnitaryTo(dst, -1, s, dst) 746 } 747 748 // SubTo subtracts, element-wise, the elements of t from s and 749 // stores the result in dst. 750 // It panics if the argument lengths do not match. 751 func SubTo(dst, s, t []float64) []float64 { 752 if len(s) != len(t) { 753 panic(badLength) 754 } 755 if len(dst) != len(s) { 756 panic(badDstLength) 757 } 758 f64.AxpyUnitaryTo(dst, -1, t, s) 759 return dst 760 } 761 762 // Sum returns the sum of the elements of the slice. 763 func Sum(s []float64) float64 { 764 return f64.Sum(s) 765 } 766 767 // Within returns the first index i where s[i] <= v < s[i+1]. Within panics if: 768 // - len(s) < 2 769 // - s is not sorted 770 func Within(s []float64, v float64) int { 771 if len(s) < 2 { 772 panic(shortSpan) 773 } 774 if !sort.Float64sAreSorted(s) { 775 panic("floats: input slice not sorted") 776 } 777 if v < s[0] || v >= s[len(s)-1] || math.IsNaN(v) { 778 return -1 779 } 780 for i, f := range s[1:] { 781 if v < f { 782 return i 783 } 784 } 785 return -1 786 } 787 788 // SumCompensated returns the sum of the elements of the slice calculated with greater 789 // accuracy than Sum at the expense of additional computation. 790 func SumCompensated(s []float64) float64 { 791 // SumCompensated uses an improved version of Kahan's compensated 792 // summation algorithm proposed by Neumaier. 793 // See https://en.wikipedia.org/wiki/Kahan_summation_algorithm for details. 794 var sum, c float64 795 for _, x := range s { 796 // This type conversion is here to prevent a sufficiently smart compiler 797 // from optimising away these operations. 798 t := float64(sum + x) 799 if math.Abs(sum) >= math.Abs(x) { 800 c += (sum - t) + x 801 } else { 802 c += (x - t) + sum 803 } 804 sum = t 805 } 806 return sum + c 807 }