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