1 2# -*- coding: utf-8 -*- 3 4u'''Precision floating point functions, classes and utilities. 5''' 6# make sure int/int division yields float quotient, see .basics 7from __future__ import division 8 9from pygeodesy.basics import copysign0, isfinite, isint, isnear0, \ 10 isscalar, len2, _xcopy 11from pygeodesy.errors import _IsnotError, LenError, _OverflowError, \ 12 _TypeError, _ValueError 13from pygeodesy.interns import EPS0, EPS02, EPS1, MISSING, NN, PI, PI_2, PI_4, \ 14 _finite_, _few_, _h_, _negative_, _not_, _singular_, \ 15 _SPACE_, _too_, _0_0, _1_0, _1_5 as _3_2nd, _2_0, _3_0 16from pygeodesy.lazily import _ALL_LAZY, _sys_version_info2 17from pygeodesy.streprs import Fmt, unstr 18from pygeodesy.units import Int_ 19 20from math import sqrt # pow 21from operator import mul as _mul 22 23__all__ = _ALL_LAZY.fmath 24__version__ = '21.09.14' 25 26# sqrt(2) <https://WikiPedia.org/wiki/Square_root_of_2> 27_0_4142 = 0.414213562373095 # sqrt(_2_0) - _1_0 28_1_3rd = _1_0 / _3_0 29_2_3rd = _2_0 / _3_0 30 31 32def _2even(s, r, p): 33 '''(INTERNAL) Half-even rounding. 34 ''' 35 if (r > 0 and p > 0) or \ 36 (r < 0 and p < 0): # signs match 37 t, p = _2sum(s, p * 2) 38 if not p: 39 s = t 40 return s 41 42 43def _2sum(a, b): # by .testFmath 44 '''(INTERNAL) Precision C{2sum} of M{a + b}. 45 ''' 46 s = a + b 47 if not isfinite(s): 48 raise _OverflowError(unstr(_2sum.__name__, a, b), txt=str(s)) 49 if abs(a) < abs(b): 50 a, b = b, a 51 return s, b - (s - a) 52 53 54class Fsum(object): 55 '''Precision summation similar to standard Python function C{math.fsum}. 56 57 Unlike C{math.fsum}, this class accumulates the values and provides 58 intermediate, precision running sums. Accumulation may continue 59 after intermediate summations. 60 61 @note: Handling of exceptions, C{inf}, C{INF}, C{nan} and C{NAN} 62 values is different from C{math.fsum}. 63 64 @see: U{Hettinger<https://GitHub.com/ActiveState/code/blob/master/recipes/Python/ 65 393090_Binary_floating_point_summatiaccurate_full/recipe-393090.py>}, 66 U{Kahan<https://WikiPedia.org/wiki/Kahan_summation_algorithm>}, 67 U{Klein<https://Link.Springer.com/article/10.1007/s00607-005-0139-x>}, 68 Python 2.6+ file I{Modules/mathmodule.c} and the issue log 69 U{Full precision summation<https://Bugs.Python.org/issue2819>}. 70 ''' 71 _fsum2_ = None 72 _n = 0 73 _ps = [] 74 75 def __init__(self, *starts): 76 '''Initialize a new accumulator with one or more start values. 77 78 @arg starts: No, one or more start values (C{scalar}s). 79 80 @raise OverflowError: Partial C{2sum} overflow. 81 82 @raise TypeError: Non-scalar B{C{starts}} value. 83 84 @raise ValueError: Invalid or non-finite B{C{starts}} value. 85 ''' 86 self._n = 0 87 self._ps = [] 88 if starts: 89 self.fadd(starts) 90 91 def __add__(self, other): 92 '''Sum of this and an other instance or a scalar. 93 94 @arg other: L{Fsum} instance or C{scalar}. 95 96 @return: The sum, a new instance (L{Fsum}). 97 98 @see: Method L{Fsum.__iadd__}. 99 ''' 100 f = self.fcopy() 101 f += other 102 return f # self.fcopy().__iadd__(other) 103 104 def __iadd__(self, other): 105 '''Add a scalar or an other instance to this instance. 106 107 @arg other: L{Fsum} instance or C{scalar}. 108 109 @return: This instance, updated (L{Fsum}). 110 111 @raise TypeError: Invalid B{C{other}} type. 112 113 @see: Method L{Fsum.fadd}. 114 ''' 115 if isscalar(other): 116 self.fadd_(other) 117 elif other is self: 118 self.fmul(2) 119 elif isinstance(other, Fsum): 120 self.fadd(other._ps) 121 else: 122 raise _TypeError(_SPACE_(self, '+=', repr(other))) 123 return self 124 125 def __imul__(self, other): 126 '''Multiply this instance by a scalar or an other instance. 127 128 @arg other: L{Fsum} instance or C{scalar}. 129 130 @return: This instance, updated (L{Fsum}). 131 132 @raise TypeError: Invalid B{C{other}} type. 133 134 @see: Method L{Fsum.fmul}. 135 ''' 136 if isscalar(other): 137 self.fmul(other) 138 elif isinstance(other, Fsum): 139 ps = list(other._ps) # copy 140 if ps: 141 s = self.fcopy() 142 self.fmul(ps.pop()) 143 while ps: # self += s * ps.pop() 144 p = s.fcopy() 145 p.fmul(ps.pop()) 146 self.fadd(p._ps) 147 else: 148 self._ps = [] # zero 149 self._fsum2_ = None 150 else: 151 raise _TypeError(_SPACE_(self, '*=', repr(other))) 152 return self 153 154 def __isub__(self, other): 155 '''Subtract a scalar or an other instance from this instance. 156 157 @arg other: L{Fsum} instance or C{scalar}. 158 159 @return: This instance, updated (L{Fsum}). 160 161 @raise TypeError: Invalid B{C{other}} type. 162 163 @see: Method L{Fsum.fadd}. 164 ''' 165 if isscalar(other): 166 self.fadd_(-other) 167 elif other is self: 168 self._ps = [] # zero 169 self._fsum2_ = None 170 elif isinstance(other, Fsum): 171 self.fadd(-p for p in other._ps) 172 else: 173 raise _TypeError(_SPACE_(self, '-=', repr(other))) 174 return self 175 176 def __len__(self): 177 '''Return the number of accumulated values. 178 ''' 179 return self._n 180 181 def __mul__(self, other): 182 '''Product of this and an other instance or a scalar. 183 184 @arg other: L{Fsum} instance or C{scalar}. 185 186 @return: The product, a new instance (L{Fsum}). 187 188 @see: Method L{Fsum.__imul__}. 189 ''' 190 f = self.fcopy() 191 f *= other 192 return f 193 194 def __str__(self): 195 from pygeodesy.named import classname 196 return Fmt.PAREN(classname(self, prefixed=True), NN) 197 198 def __sub__(self, other): 199 '''Difference of this and an other instance or a scalar. 200 201 @arg other: L{Fsum} instance or C{scalar}. 202 203 @return: The difference, a new instance (L{Fsum}). 204 205 @see: Method L{Fsum.__isub__}. 206 ''' 207 f = self.fcopy() 208 f -= other 209 return f 210 211 def fadd(self, xs): 212 '''Accumulate more values from an iterable. 213 214 @arg xs: Iterable, list, tuple, etc. (C{scalar}s). 215 216 @raise OverflowError: Partial C{2sum} overflow. 217 218 @raise TypeError: Non-scalar B{C{xs}} value. 219 220 @raise ValueError: Invalid or non-finite B{C{xs}} value. 221 ''' 222 if isscalar(xs): # for backward compatibility 223 xs = (xs,) 224 225 ps = self._ps 226 for n, x in enumerate(map(float, xs)): # _iter() 227 if not isfinite(x): 228 n = Fmt.SQUARE(xs=n) 229 raise _ValueError(n, x, txt=_not_(_finite_)) 230 i = 0 231 for p in ps: 232 x, p = _2sum(x, p) 233 if p: 234 ps[i] = p 235 i += 1 236 ps[i:] = [x] 237 # assert self._ps is ps 238 self._n += n + 1 239 self._fsum2_ = None 240 241 def fadd_(self, *xs): 242 '''Accumulate more values from positional arguments. 243 244 @arg xs: Values to add (C{scalar}s), all positional. 245 246 @raise OverflowError: Partial C{2sum} overflow. 247 248 @raise TypeError: Non-scalar B{C{xs}} value. 249 250 @raise ValueError: Invalid or non-finite B{C{xs}} value. 251 ''' 252 self.fadd(xs) 253 254 def fcopy(self, deep=False): 255 '''Copy this instance, C{shallow} or B{C{deep}}. 256 257 @return: The copy, a new instance (L{Fsum}). 258 ''' 259 f = _xcopy(self, deep=deep) 260 # f._fsum2_ = self._fsum2_ 261 # f._n = self._n 262 f._ps = list(self._ps) # separate copy 263 return f 264 265 copy = fcopy 266 267 def fmul(self, factor): 268 '''Multiple the current, partial sum by a factor. 269 270 @arg factor: The multiplier (C{scalar}). 271 272 @raise TypeError: Non-scalar B{C{factor}}. 273 274 @raise ValueError: Invalid or non-finite B{C{factor}}. 275 276 @see: Method L{Fsum.fadd}. 277 ''' 278 if not isfinite(factor): 279 raise _ValueError(factor=factor, txt=_not_(_finite_)) 280 281 f, ps = float(factor), self._ps 282 if ps: # multiply and adjust partial sums 283 ps[:] = [p * f for p in ps] 284 self.fadd_(ps.pop()) 285 self._n -= 1 286 # assert self._ps is ps 287 288 def fsub(self, iterable): 289 '''Accumulate more values from an iterable. 290 291 @arg iterable: Sequence, list, tuple, etc. (C{scalar}s). 292 293 @see: Method L{Fsum.fadd}. 294 ''' 295 if iterable: 296 self.fadd(-s for s in iterable) 297 298 def fsub_(self, *xs): 299 '''Accumulate more values from positional arguments. 300 301 @arg xs: Values to subtract (C{scalar}s), all positional. 302 303 @see: Method L{Fsum.fadd}. 304 ''' 305 self.fsub(xs) 306 307 def fsum(self, iterable=()): 308 '''Accumulate more values from an iterable and sum all. 309 310 @kwarg iterable: Sequence, list, tuple, etc. (C{scalar}s), optional. 311 312 @return: Accurate, running sum (C{float}). 313 314 @raise OverflowError: Partial C{2sum} overflow. 315 316 @raise TypeError: Non-scalar B{C{iterable}} value. 317 318 @raise ValueError: Invalid or non-finite B{C{iterable}} value. 319 320 @note: Accumulation can continue after summation. 321 ''' 322 if iterable: 323 self.fadd(iterable) 324 325 ps = self._ps 326 i = len(ps) - 1 327 if i < 0: 328 s = _0_0 329 else: 330 s = ps[i] 331 while i > 0: 332 i -= 1 333 s, p = _2sum(s, ps[i]) 334 ps[i:] = [s] 335 if p: # sum(ps) became inexact 336 ps.append(p) 337 if i > 0: # half-even round if signs match 338 s = _2even(s, ps[i-1], p) 339 break 340 # assert self._ps is ps 341 self._fsum2_ = s 342 return s 343 344 def fsum_(self, *xs): 345 '''Accumulate more values from positional arguments and sum all. 346 347 @arg xs: Values to add (C{scalar}s), all positional. 348 349 @return: Accurate, running sum (C{float}). 350 351 @see: Method L{Fsum.fsum}. 352 353 @note: Accumulation can continue after summation. 354 ''' 355 return self.fsum(xs) 356 357 def fsum2_(self, *xs): 358 '''Accumulate more values from positional arguments, sum all 359 and provide the sum and delta. 360 361 @arg xs: Values to add (C{scalar}s), all positional. 362 363 @return: 2-Tuple C{(sum, delta)} with the accurate, 364 running C{sum} and the C{delta} with the 365 previous running C{sum}, both (C{float}). 366 367 @see: Method L{Fsum.fsum_}. 368 369 @note: Accumulation can continue after summation. 370 ''' 371 p = self._fsum2_ 372 if p is None: 373 p = self.fsum() 374 s = self.fsum(xs) # if xs else self._fsum2_ 375 return s, s - p 376 377 378class Fdot(Fsum): 379 '''Precision dot product. 380 ''' 381 def __init__(self, a, *b): 382 '''New L{Fdot} precision dot product M{sum(a[i] * b[i] 383 for i=0..len(a))}. 384 385 @arg a: List, sequence, tuple, etc. (C{scalar}s). 386 @arg b: All positional arguments (C{scalar}s). 387 388 @raise OverflowError: Partial C{2sum} overflow. 389 390 @raise LenError: Unequal C{len(B{a})} and C{len(B{b})}. 391 392 @see: Function L{fdot} and method L{Fsum.fadd}. 393 ''' 394 if len(a) != len(b): 395 raise LenError(Fdot, a=len(a), b=len(b)) 396 397 Fsum.__init__(self) 398 self.fadd(map(_mul, a, b)) 399 400 401class Fhorner(Fsum): 402 '''Precision polynomial evaluation using the Horner form. 403 ''' 404 def __init__(self, x, *cs): 405 '''New L{Fhorner} evaluation of the polynomial 406 M{sum(cs[i] * x**i for i=0..len(cs))}. 407 408 @arg x: Polynomial argument (C{scalar}). 409 @arg cs: Polynomial coeffients (C{scalar}[]). 410 411 @raise OverflowError: Partial C{2sum} overflow. 412 413 @raise TypeError: Non-scalar B{C{x}}. 414 415 @raise ValueError: No B{C{cs}} coefficients or B{C{x}} is not finite. 416 417 @see: Function L{fhorner} and methods L{Fsum.fadd} and L{Fsum.fmul}. 418 ''' 419 if not isfinite(x): 420 raise _ValueError(x=x, txt=_not_(_finite_)) 421 if not cs: 422 raise _ValueError(cs=cs, txt=MISSING) 423 424 x, cs = float(x), list(cs) 425 426 Fsum.__init__(self, cs.pop()) 427 while cs: 428 self.fmul(x) 429 c = cs.pop() 430 if c: 431 self.fadd_(c) 432 433 434class Fpolynomial(Fsum): 435 '''Precision polynomial evaluation. 436 ''' 437 def __init__(self, x, *cs): 438 '''New L{Fpolynomial} evaluation of the polynomial 439 M{sum(cs[i] * x**i for i=0..len(cs))}. 440 441 @arg x: Polynomial argument (C{scalar}). 442 @arg cs: Polynomial coeffients (C{scalar}[]). 443 444 @raise OverflowError: Partial C{2sum} overflow. 445 446 @raise TypeError: Non-scalar B{C{x}}. 447 448 @raise ValueError: No B{C{cs}} coefficients or B{C{x}} is not finite. 449 450 @see: Function L{fpolynomial} and method L{Fsum.fadd}. 451 ''' 452 if not isfinite(x): 453 raise _ValueError(x=x, txt=_not_(_finite_)) 454 if not cs: 455 raise _ValueError(cs=cs, txt=MISSING) 456 457 x, cs, xp = float(x), list(cs), _1_0 458 459 Fsum.__init__(self, cs.pop(0)) 460 while cs: 461 xp *= x 462 c = cs.pop(0) 463 if c: 464 self.fadd_(xp * c) 465 466 467def cbrt(x3): 468 '''Compute the cube root M{x3**(1/3)}. 469 470 @arg x3: Value (C{scalar}). 471 472 @return: Cubic root (C{float}). 473 474 @see: Functions L{cbrt2} and L{sqrt3}. 475 ''' 476 # <https://archive.lib.MSU.edu/crcmath/math/math/r/r021.htm> 477 # simpler and more accurate than Ken Turkowski's CubeRoot, see 478 # <https://People.FreeBSD.org/~lstewart/references/apple_tr_kt32_cuberoot.pdf> 479 return copysign0(pow(abs(x3), _1_3rd), x3) 480 481 482def cbrt2(x3): 483 '''Compute the cube root I{squared} M{x3**(2/3)}. 484 485 @arg x3: Value (C{scalar}). 486 487 @return: Cube root I{squared} (C{float}). 488 489 @see: Functions L{cbrt} and L{sqrt3}. 490 ''' 491 return pow(abs(x3), _2_3rd) # XXX pow(abs(x3), _1_3rd)**2 492 493 494def euclid(x, y): 495 '''I{Appoximate} the norm M{sqrt(x**2 + y**2)} by 496 M{max(abs(x), abs(y)) + min(abs(x), abs(y)) * 0.4142...}. 497 498 @arg x: X component (C{scalar}). 499 @arg y: Y component (C{scalar}). 500 501 @return: Appoximate norm (C{float}). 502 503 @see: Function L{euclid_}. 504 ''' 505 x, y = abs(x), abs(y) 506 if y > x: 507 x, y = y, x 508 return x + y * _0_4142 # XXX * _0_5 before 20.10.02 509 510 511def euclid_(*xs): 512 '''I{Appoximate} the norm M{sqrt(sum(x**2 for x in xs))} 513 by cascaded L{euclid}. 514 515 @arg xs: X arguments, positional (C{scalar}[]). 516 517 @return: Appoximate norm (C{float}). 518 519 @see: Function L{euclid}. 520 ''' 521 e = _0_0 522 for x in sorted(map(abs, xs)): # XXX not reverse=True 523 # e = euclid(x, e) 524 if x > e: 525 e, x = x, e 526 if x: 527 e += x * _0_4142 528 return e 529 530 531def facos1(x): 532 '''Fast approximation of L{acos1}C{(B{x})}. 533 534 @see: U{ShaderFastLibs.h<https://GitHub.com/michaldrobot/ 535 ShaderFastLibs/blob/master/ShaderFastMathLib.h>}. 536 ''' 537 a = abs(x) 538 if a < EPS0: 539 r = PI_2 540 elif a < EPS1: 541 r = 1.5707288 - a * (0.2121144 - a * (0.0742610 - a * 0.0187293)) 542 r *= sqrt(_1_0 - a) 543 else: 544 r = _0_0 545 return (PI - r) if x < 0 else r 546 547 548def fasin1(x): # PYCHOK no cover 549 '''Fast approximation of L{asin1}C{(B{x})}. 550 551 @see: L{facos1}. 552 ''' 553 return PI_2 - facos1(x) 554 555 556def fatan(x): 557 '''Fast approximation of C{atan(B{x})}. 558 ''' 559 a = abs(x) 560 if a < _1_0: 561 r = fatan1(a) if a else _0_0 562 elif a > _1_0: 563 r = PI_2 - fatan1(_1_0 / a) # == fatan2(a, _1_0) 564 else: 565 r = PI_4 566 return -r if x < 0 else r 567 568 569def fatan1(x): 570 '''Fast approximation of C{atan(B{x})} for C{0 <= B{x} <= 1}, I{unchecked}. 571 572 @see: U{ShaderFastLibs.h<https://GitHub.com/michaldrobot/ 573 ShaderFastLibs/blob/master/ShaderFastMathLib.h>} and 574 U{Efficient approximations for the arctangent function 575 <http://www-Labs.IRO.UMontreal.CA/~mignotte/IFT2425/Documents/ 576 EfficientApproximationArctgFunction.pdf>}, IEEE Signal 577 Processing Magazine, 111, May 2006. 578 ''' 579 # Eq (9): PI_4 * x - x * (x - 1) * (0.2447 + 0.0663 * x**2) 580 return x * (1.0300982 - x * (0.1784 + 0.0663 * x)) # w/o x**4 581 582 583def fatan2(y, x): 584 '''Fast approximation of C{atan2(B{y}, B{x})}. 585 586 @see: U{fastApproximateAtan(x, y)<https://GitHub.com/CesiumGS/cesium/blob/ 587 master/Source/Shaders/Builtin/Functions/fastApproximateAtan.glsl>} 588 and L{fatan1}. 589 ''' 590 b, a = abs(y), abs(x) 591 if a < b: 592 r = (PI_2 - fatan1(a / b)) if a else PI_2 593 elif b < a: 594 r = fatan1(b / a) if b else _0_0 595 elif a: # == b != 0 596 r = PI_4 597 else: # a == b == 0 598 return _0_0 599 if x < 0: 600 r = PI - r 601 return -r if y < 0 else r 602 603 604def favg(v1, v2, f=0.5): 605 '''Return the average of two values. 606 607 @arg v1: One value (C{scalar}). 608 @arg v2: Other value (C{scalar}). 609 @kwarg f: Optional fraction (C{float}). 610 611 @return: M{v1 + f * (v2 - v1)} (C{float}). 612 ''' 613# @raise ValueError: Fraction out of range. 614# ''' 615# if not 0 <= f <= 1: # XXX restrict fraction? 616# raise _ValueError(fraction=f) 617 return v1 + f * (v2 - v1) # v1 * (1 - f) + v2 * f 618 619 620def fdot(a, *b): 621 '''Return the precision dot product M{sum(a[i] * b[i] for 622 i=0..len(a))}. 623 624 @arg a: List, sequence, tuple, etc. (C{scalar}s). 625 @arg b: All positional arguments (C{scalar}s). 626 627 @return: Dot product (C{float}). 628 629 @raise LenError: Unequal C{len(B{a})} and C{len(B{b})}. 630 631 @see: Class L{Fdot}. 632 ''' 633 if len(a) != len(b): 634 raise LenError(fdot, a=len(a), b=len(b)) 635 636 return fsum(map(_mul, a, b)) 637 638 639def fdot3(a, b, c, start=0): 640 '''Return the precision dot product M{start + 641 sum(a[i] * b[i] * c[i] for i=0..len(a))}. 642 643 @arg a: List, sequence, tuple, etc. (C{scalar}[]). 644 @arg b: List, sequence, tuple, etc. (C{scalar}[]). 645 @arg c: List, sequence, tuple, etc. (C{scalar}[]). 646 @kwarg start: Optional bias (C{scalar}). 647 648 @return: Dot product (C{float}). 649 650 @raise LenError: Unequal C{len(B{a})}, C{len(B{b})} 651 and/or C{len(B{c})}. 652 653 @raise OverflowError: Partial C{2sum} overflow. 654 ''' 655 def _mul3(a, b, c): # map function 656 return a * b * c # PYCHOK returns 657 658 if not len(a) == len(b) == len(c): 659 raise LenError(fdot3, a=len(a), b=len(b), c=len(c)) 660 661 if start: 662 f = Fsum(start) 663 return f.fsum(map(_mul3, a, b, c)) 664 else: 665 return fsum(map(_mul3, a, b, c)) 666 667 668def fhorner(x, *cs): 669 '''Evaluate the polynomial M{sum(cs[i] * x**i for 670 i=0..len(cs))} using the Horner form. 671 672 @arg x: Polynomial argument (C{scalar}). 673 @arg cs: Polynomial coeffients (C{scalar}[]). 674 675 @return: Horner value (C{float}). 676 677 @raise OverflowError: Partial C{2sum} overflow. 678 679 @raise TypeError: Non-scalar B{C{x}}. 680 681 @raise ValueError: No B{C{cs}} coefficients or B{C{x}} is not finite. 682 683 @see: Function L{fpolynomial} and class L{Fhorner}. 684 ''' 685 h = Fhorner(x, *cs) 686 return h.fsum() 687 688 689def fidw(xs, ds, beta=2): 690 '''Interpolate using using U{Inverse Distance Weighting 691 <https://WikiPedia.org/wiki/Inverse_distance_weighting>} (IDW). 692 693 @arg xs: Known values (C{scalar}[]). 694 @arg ds: Non-negative distances (C{scalar}[]). 695 @kwarg beta: Inverse distance power (C{int}, 0, 1, 2, or 3). 696 697 @return: Interpolated value C{x} (C{float}). 698 699 @raise LenError: Unequal or zero C{len(B{ds})} and C{len(B{xs})}. 700 701 @raise ValueError: Invalid B{C{beta}}, negative B{C{ds}} value, 702 weighted B{C{ds}} below L{EPS}. 703 704 @note: Using C{B{beta}=0} returns the mean of B{C{xs}}. 705 ''' 706 n, xs = len2(xs) 707 d, ds = len2(ds) 708 if n != d or n < 1: 709 raise LenError(fidw, xs=n, ds=d) 710 711 d, x = min(zip(ds, xs)) 712 if d > EPS0 and n > 1: 713 b = -Int_(beta=beta, low=0, high=3) 714 if b < 0: 715 ds = tuple(d**b for d in ds) 716 d = fsum(ds) 717 if isnear0(d): 718 n = Fmt.PAREN(fsum='ds') 719 raise _ValueError(n, d, txt=_singular_) 720 x = fdot(xs, *ds) / d 721 else: # b == 0 722 x = fsum(xs) / n # fmean(xs) 723 elif d < 0: 724 n = Fmt.SQUARE(ds=ds.index(d)) 725 raise _ValueError(n, d, txt=_negative_) 726 return x 727 728 729def fmean(xs): 730 '''Compute the accurate mean M{sum(xs[i] for 731 i=0..len(xs)) / len(xs)}. 732 733 @arg xs: Values (C{scalar}s). 734 735 @return: Mean value (C{float}). 736 737 @raise OverflowError: Partial C{2sum} overflow. 738 739 @raise ValueError: No B{C{xs}} values. 740 ''' 741 n, xs = len2(xs) 742 if n > 0: 743 return fsum(xs) / n 744 raise _ValueError(xs=xs) 745 746 747def fmean_(*xs): 748 '''Compute the accurate mean M{sum(xs[i] for 749 i=0..len(xs)) / len(xs)}. 750 751 @arg xs: Values (C{scalar}s). 752 753 @return: Mean value (C{float}). 754 755 @raise OverflowError: Partial C{2sum} overflow. 756 757 @raise ValueError: No B{C{xs}} values. 758 ''' 759 return fmean(xs) 760 761 762def fpolynomial(x, *cs): 763 '''Evaluate the polynomial M{sum(cs[i] * x**i for 764 i=0..len(cs))}. 765 766 @arg x: Polynomial argument (C{scalar}). 767 @arg cs: Polynomial coeffients (C{scalar}[]). 768 769 @return: Polynomial value (C{float}). 770 771 @raise OverflowError: Partial C{2sum} overflow. 772 773 @raise TypeError: Non-scalar B{C{x}}. 774 775 @raise ValueError: No B{C{cs}} coefficients or B{C{x}} is not finite. 776 777 @see: Function L{fhorner} and class L{Fpolynomial}. 778 ''' 779 p = Fpolynomial(x, *cs) 780 return p.fsum() 781 782 783def fpowers(x, n, alts=0): 784 '''Return a series of powers M{[x**i for i=1..n]}. 785 786 @arg x: Value (C{scalar}). 787 @arg n: Highest exponent (C{int}). 788 @kwarg alts: Only alternating powers, starting with 789 this exponent (C{int}). 790 791 @return: Powers of B{C{x}} (C{float}s or C{int}s). 792 793 @raise TypeError: Non-scalar B{C{x}} or B{C{n}} not C{int}. 794 795 @raise ValueError: Non-finite B{C{x}} or non-positive B{C{n}}. 796 ''' 797 if not isfinite(x): 798 raise _ValueError(x=x, txt=_not_(_finite_)) 799 if not isint(n): 800 raise _IsnotError(int.__name__, n=n) 801 elif n < 1: 802 raise _ValueError(n=n) 803 804 xs = [x] 805 for _ in range(1, n): 806 xs.append(xs[-1] * x) 807 808 if alts > 0: # x**2, x**4, ... 809 # xs[alts-1::2] chokes PyChecker 810 xs = xs[slice(alts-1, None, 2)] 811 812 return xs 813 814 815try: 816 from math import prod as fprod # Python 3.8 817except ImportError: 818 819 def fprod(iterable, start=_1_0): 820 '''Iterable product, like C{math.prod} or C{numpy.prod}. 821 822 @arg iterable: Terms to be multiplied (C{scalar}[]). 823 @kwarg start: Initial term, also the value returned 824 for an empty iterable (C{scalar}). 825 826 @return: The product (C{float}). 827 828 @see: U{NumPy.prod<https://docs.SciPy.org/doc/ 829 numpy/reference/generated/numpy.prod.html>}. 830 ''' 831 return freduce(_mul, iterable, start) 832 833 834def frange(start, number, step=1): 835 '''Generate a range of C{float}s. 836 837 @arg start: First value (C{float}). 838 @arg number: The number of C{float}s to generate (C{int}). 839 @kwarg step: Increment value (C{float}). 840 841 @return: A generator (C{float}s). 842 843 @see: U{NumPy.prod<https://docs.SciPy.org/doc/ 844 numpy/reference/generated/numpy.arange.html>}. 845 ''' 846 if not isint(number): 847 raise _IsnotError(int.__name__, number=number) 848 for i in range(number): 849 yield start + i * step 850 851 852try: 853 from functools import reduce as freduce 854except ImportError: 855 try: 856 freduce = reduce # PYCHOK expected 857 except NameError: # Python 3+ 858 859 def freduce(f, iterable, *start): 860 '''For missing C{functools.reduce}. 861 ''' 862 if start: 863 r = v = start[0] 864 else: 865 r, v = 0, MISSING 866 for v in iterable: 867 r = f(r, v) 868 if v is MISSING: 869 raise _TypeError(iterable=(), start=MISSING) 870 return r 871 872 873try: 874 from math import fsum # precision IEEE-754 sum, Python 2.6+ 875 876 # make sure fsum works as expected (XXX check 877 # float.__getformat__('float')[:4] == 'IEEE'?) 878 if fsum((1, 1e101, 1, -1e101)) != 2: 879 del fsum # nope, remove fsum ... 880 raise ImportError # ... use fsum below 881 882except ImportError: 883 884 def fsum(iterable): 885 '''Precision summation similar to standard Python function C{math.fsum}. 886 887 Exception and I{non-finite} handling differs from C{math.fsum}. 888 889 @arg iterable: Values to be added (C{scalar}[]). 890 891 @return: Accurate C{sum} (C{float}). 892 893 @raise OverflowError: Partial C{2sum} overflow. 894 895 @raise TypeError: Non-scalar B{C{iterable}} value. 896 897 @raise ValueError: Invalid or non-finite B{C{iterable}} value. 898 899 @see: Class L{Fsum}. 900 ''' 901 f = Fsum() 902 return f.fsum(iterable) 903 904 905def fsum_(*xs): 906 '''Precision summation of all positional arguments. 907 908 @arg xs: Values to be added (C{scalar}s). 909 910 @return: Accurate L{fsum} (C{float}). 911 912 @raise OverflowError: Partial C{2sum} overflow. 913 914 @raise TypeError: Non-scalar B{C{xs}} value. 915 916 @raise ValueError: Invalid or non-finite B{C{xs}} value. 917 ''' 918 return fsum(map(float, xs)) 919 920 921def fsum1_(*xs): 922 '''Precision summation of a few arguments, primed with C{1.0}. 923 924 @arg xs: Values to be added (C{scalar}s). 925 926 @return: Accurate L{fsum} (C{float}). 927 928 @raise OverflowError: Partial C{2sum} overflow. 929 930 @raise TypeError: Non-scalar B{C{xs}} value. 931 932 @raise ValueError: Invalid or non-finite B{C{xs}} value. 933 ''' 934 def _xs(xs): 935 yield _1_0 936 for x in xs: 937 yield x 938 yield -_1_0 939 940 return fsum(_xs(xs)) 941 942 943if _sys_version_info2 > (3, 9): 944 from math import hypot # OK in Python 3.10+ 945 hypot_ = hypot 946else: 947 # In Python 3.8 and 3.9 C{math.hypot} is inaccurate, see 948 # agdhruv <https://GitHub.com/geopy/geopy/issues/466>, 949 # cffk <https://Bugs.Python.org/issue43088> and module 950 # geomath.py <https://PyPI.org/project/geographiclib/1.52> 951 if _sys_version_info2 < (3, 8): 952 from math import hypot # PYCHOK re-imported? 953 else: 954 def hypot(x, y): 955 '''Compute the norm M{sqrt(x**2 + y**2)}. 956 957 @arg x: Argument (C{scalar}). 958 @arg y: Argument (C{scalar}). 959 960 @return: C{sqrt(B{x}**2 + B{y}**2)} (C{float}). 961 ''' 962 if x: 963 h = sqrt(fsum1_(x**2, y**2)) if y else abs(x) 964 elif y: 965 h = abs(y) 966 else: 967 h = _0_0 968 return h 969 970 def hypot_(*xs): 971 '''Compute the norm M{sqrt(sum(x**2 for x in xs))}. 972 973 @arg xs: X arguments, positional (C{scalar}[]). 974 975 @return: Norm (C{float}). 976 977 @raise OverflowError: Partial C{2sum} overflow. 978 979 @raise ValueError: Invalid or no B{C{xs}} values. 980 981 @see: Similar to Python 3.8+ n-dimension U{math.hypot 982 <https://docs.Python.org/3.8/library/math.html#math.hypot>}, 983 but handling of exceptions, C{nan} and C{infinite} values 984 is different. 985 986 @note: The Python 3.8+ Euclidian distance U{math.dist 987 <https://docs.Python.org/3.8/library/math.html#math.dist>} 988 between 2 I{n}-dimensional points I{p1} and I{p2} can be 989 computed as M{hypot_(*((c1 - c2) for c1, c2 in zip(p1, p2)))}, 990 provided I{p1} and I{p2} have the same, non-zero length I{n}. 991 ''' 992 h, x2 = _h_x2(xs) 993 return (h * sqrt(x2)) if x2 else _0_0 994 995 996def _h_x2(xs): 997 '''(INTERNAL) Helper for L{hypot_} and L{hypot2_}. 998 ''' 999 def _x2s(xs, h): 1000 yield _1_0 1001 for x in xs: 1002 if x: 1003 yield (x / h)**2 1004 yield -_1_0 1005 1006 if xs: 1007 n, xs = len2(xs) 1008 if n > 0: 1009 h = float(max(map(abs, xs))) 1010 x2 = fsum(_x2s(xs, h)) if h else _0_0 1011 return h, x2 1012 1013 raise _ValueError(xs=xs, txt=_too_(_few_)) 1014 1015 1016def hypot1(x): 1017 '''Compute the norm M{sqrt(1 + x**2)}. 1018 1019 @arg x: Argument (C{scalar}). 1020 1021 @return: Norm (C{float}). 1022 ''' 1023 return hypot(_1_0, x) if x else _1_0 1024 1025 1026def hypot2(x, y): 1027 '''Compute the I{squared} norm M{x**2 + y**2}. 1028 1029 @arg x: Argument (C{scalar}). 1030 @arg y: Argument (C{scalar}). 1031 1032 @return: C{B{x}**2 + B{y}**2} (C{float}). 1033 ''' 1034 if x: 1035 x *= x 1036 h2 = fsum1_(x, y**2) if y else x 1037 elif y: 1038 h2 = y**2 1039 else: 1040 h2 = _0_0 1041 return h2 1042 1043 1044def hypot2_(*xs): 1045 '''Compute the I{squared} norm C{sum(x**2 for x in B{xs})}. 1046 1047 @arg xs: X arguments, positional (C{scalar}[]). 1048 1049 @return: Squared norm (C{float}). 1050 1051 @raise OverflowError: Partial C{2sum} overflow. 1052 1053 @raise ValueError: Invalid or no B{C{xs}} value. 1054 1055 @see: Function L{hypot_}. 1056 ''' 1057 h, x2 = _h_x2(xs) 1058 return (h**2 * x2) if x2 else _0_0 1059 1060 1061def norm2(x, y): 1062 '''Normalize a 2-dimensional vector. 1063 1064 @arg x: X component (C{scalar}). 1065 @arg y: Y component (C{scalar}). 1066 1067 @return: 2-Tuple C{(x, y)}, normalized. 1068 1069 @raise ValueError: Invalid B{C{x}} or B{C{y}} 1070 or zero norm. 1071 ''' 1072 h = hypot(x, y) 1073 try: 1074 return x / h, y / h 1075 except (TypeError, ValueError) as X: 1076 raise _ValueError(x=x, y=y, h=h, txt=str(X)) 1077 1078 1079def norm_(*xs): 1080 '''Normalize all n-dimensional vector components. 1081 1082 @arg xs: The component (C{scalar}[]). 1083 1084 @return: Yield each component, normalized. 1085 1086 @raise ValueError: Invalid or insufficent B{C{xs}} 1087 or zero norm. 1088 ''' 1089 h = hypot_(*xs) 1090 try: 1091 for i, x in enumerate(xs): 1092 yield x / h 1093 except (TypeError, ValueError) as X: 1094 raise _ValueError(Fmt.SQUARE(xs=i), x, _h_, h, txt=str(X)) 1095 1096 1097def sqrt0(x2): 1098 '''Compute the square root iff C{B{x2} >} L{EPS02}. 1099 1100 @arg x2: Value (C{scalar}). 1101 1102 @return: Square root (C{float}) or C{0.0}. 1103 1104 @note: Any C{B{x2} <} L{EPS02} I{including} C{B{x2} < 0} 1105 returns C{0.0}. 1106 ''' 1107 return sqrt(x2) if x2 > EPS02 else (_0_0 if x2 < EPS02 else EPS0) 1108 1109 1110def sqrt3(x2): 1111 '''Compute the square root, I{cubed} M{sqrt(x)**3} or M{sqrt(x**3)}. 1112 1113 @arg x2: Value (C{scalar}). 1114 1115 @return: Cubed square root (C{float}). 1116 1117 @raise ValueError: Negative B{C{x2}}. 1118 1119 @see: Functions L{cbrt} and L{cbrt2}. 1120 ''' 1121 if x2 < 0: 1122 raise _ValueError(x2=x2) 1123 return pow(x2, _3_2nd) if x2 else _0_0 1124 1125# **) MIT License 1126# 1127# Copyright (C) 2016-2021 -- mrJean1 at Gmail -- All Rights Reserved. 1128# 1129# Permission is hereby granted, free of charge, to any person obtaining a 1130# copy of this software and associated documentation files (the "Software"), 1131# to deal in the Software without restriction, including without limitation 1132# the rights to use, copy, modify, merge, publish, distribute, sublicense, 1133# and/or sell copies of the Software, and to permit persons to whom the 1134# Software is furnished to do so, subject to the following conditions: 1135# 1136# The above copyright notice and this permission notice shall be included 1137# in all copies or substantial portions of the Software. 1138# 1139# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 1140# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1141# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 1142# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 1143# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 1144# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 1145# OTHER DEALINGS IN THE SOFTWARE. 1146