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