pygeodesy 24.4.18__py2.py3-none-any.whl → 24.4.24__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyGeodesy
3
- Version: 24.4.18
3
+ Version: 24.4.24
4
4
  Summary: Pure Python geodesy tools
5
5
  Home-page: https://GitHub.com/mrJean1/PyGeodesy
6
6
  Author: Jean M. Brouwers
@@ -158,7 +158,7 @@ and McCabe_ using Python 2.7.18 and with Flake8_ using Python 3.11.5, both in 64
158
158
 
159
159
  For a summary of all *Karney*-based functionality in ``pygeodesy``, see module karney_.
160
160
 
161
- *Last updated: April 18, 2024.*
161
+ *Last updated: April 24, 2024.*
162
162
 
163
163
  License
164
164
  =======
@@ -1,5 +1,5 @@
1
1
  pygeodesy/LICENSE,sha256=YfgAiyxOwY6P9Kkb1_5XN81nueTLrpb3Ffkv3EuPgFU,1144
2
- pygeodesy/__init__.py,sha256=Wx1Zo8hUxl6CV1eZZvLbrQSQ6VSB8mHBTmPqxcG0Mpc,40824
2
+ pygeodesy/__init__.py,sha256=td1SZErPJBK8oIAHrj4sVvK-n3PjnJbckJQjWhyNHf0,40824
3
3
  pygeodesy/__main__.py,sha256=qMFG3caY8ZXWu6uGiemzyT4OqTFZnsFtlxcGCAgkVJw,4637
4
4
  pygeodesy/albers.py,sha256=g2AVlpV8JO2AYFCthoIbRC2h1OJqjb9P3hpwF0C3TI8,30994
5
5
  pygeodesy/azimuthal.py,sha256=tc4JxgLi-0jzU08m4Bvi-t-kzHXYPeGuzL3j_tyVFUA,50125
@@ -7,7 +7,7 @@ pygeodesy/basics.py,sha256=_BMYLzGKA6OIS3qv25HfHy7MIh0kAbmkzyScb59clUs,28160
7
7
  pygeodesy/booleans.py,sha256=HZbwoL-S7Ww9d4C2D1BVqXfmcuqqVpEVSU9_S0uyUyo,74204
8
8
  pygeodesy/cartesianBase.py,sha256=I3q29mRdBB3NCDmPoJsJ0QOFfLzkdMWc8X9zG4IwJyA,47264
9
9
  pygeodesy/clipy.py,sha256=VU3ynQ1IZ0v5hJlicqD48oW0imRgiL5_ZzRPrIjpfPw,27683
10
- pygeodesy/constants.py,sha256=ypxYsWB6tNAVgrD9QVpukJAPscaLgHtPLqPuot1gUfU,19174
10
+ pygeodesy/constants.py,sha256=RxO8dMe_3AEfmZyKaRIy_44QnOTdEpzgYwimN0_w9Qs,19113
11
11
  pygeodesy/css.py,sha256=sKXsahUiyruDcUk-tGjA6mxq-xzwBoBKxKo9_b2uBmY,25394
12
12
  pygeodesy/datums.py,sha256=gJZPgV4bELZvZ8Sj2zE3MBysVtsLxqsN8zm0xjOKvpo,33851
13
13
  pygeodesy/dms.py,sha256=op3MU-59CoJQRdybnu21aVM9wtocd_-XFNAZFqmozSo,44439
@@ -25,11 +25,11 @@ pygeodesy/elliptic.py,sha256=XRVpmpdm3hbztM4P-qhWKQizbgsXDUxWujgmOrIpgiQ,42428
25
25
  pygeodesy/epsg.py,sha256=ldHoLWqJWR4FUiBVnDTtrI_e7TNjjtr9OkxDlri1g5E,8165
26
26
  pygeodesy/errors.py,sha256=mUaacHJKcSCowlcbzamaBMDqjI2dqsTM5O4ozIITtAk,27233
27
27
  pygeodesy/etm.py,sha256=joEhU2lw9ehRv101nRpZvXq8hOYAEzdcYFpif9_obfk,44585
28
- pygeodesy/fmath.py,sha256=9jjm_QxrQKNtOUxgNk02j-UVMFVjC1cDPaEqXznKgzk,30716
28
+ pygeodesy/fmath.py,sha256=cRisVRkuarEwu4pKOCWmBa-98lV2PP9oJVkkODQLc2E,32480
29
29
  pygeodesy/formy.py,sha256=Rces4Q5ecED0WVfuI9mQzeyHE54LDowYfI6faBHpyeA,74536
30
30
  pygeodesy/frechet.py,sha256=qgee-ISBtT7Ov3rJkcd_t-WeXTbeNoMSQuMa0j3MyQc,33512
31
31
  pygeodesy/fstats.py,sha256=r8O2aIknHuoHsW8gZUIY42zpHZy7syHvCB7uCVtPpkc,25559
32
- pygeodesy/fsums.py,sha256=QjV9B-YGbhFALMPEQMKT8-1j7JFDWnabakAdZDvFsSs,72501
32
+ pygeodesy/fsums.py,sha256=Am8CRdltomItdCpdBUDyc1xP53KmrCXXs9rBTa19hs4,73370
33
33
  pygeodesy/gars.py,sha256=fCiWBJ4kOJxPfNOxadX-OzBGDXj7C9g02NuGHiZa_88,11342
34
34
  pygeodesy/geodesicw.py,sha256=5yjJ2rLekSsjT7e-Km6v592ZcFlA41flQP_E42jU9Sw,26901
35
35
  pygeodesy/geodsolve.py,sha256=3GLI2_4gglzTpZwKchNzAv-XhWFBzWRKjcWsjCC3dvI,21900
@@ -42,7 +42,7 @@ pygeodesy/iters.py,sha256=CfU365eE9F4oWZbBx0qTEvfodMhpa8rGOepLIv67xF8,20181
42
42
  pygeodesy/karney.py,sha256=lYdsSHis8xQJVS1V0F3cphx6Dhgd77xTlMOdVR8MN8I,34994
43
43
  pygeodesy/ktm.py,sha256=sCvbLvJItavlruilAyjeZ0sOZx2yJumzGe_UiIVbGi4,27315
44
44
  pygeodesy/latlonBase.py,sha256=LimCMj91J3Q4D_rTu7VN7c63cLL_KzxXagFQfedobmc,77730
45
- pygeodesy/lazily.py,sha256=4ZxBVj1Yu4Db-dw_e6oxXx7EtDSrgdGBV-ypUefMPGY,48633
45
+ pygeodesy/lazily.py,sha256=YeOQQ2Tu0LTD-AMDABc0bemeLRKTteTEJXJDl3_7vZU,48645
46
46
  pygeodesy/lcc.py,sha256=yNpmAdOwv3HNh2ZLopF5QtvDokeQYCmnxSc8UFUPHO4,25737
47
47
  pygeodesy/ltp.py,sha256=8se8dYG7LG__Ru0FoK939DJBF9GCQGeJik_Un1Im8Xw,48609
48
48
  pygeodesy/ltpTuples.py,sha256=iKzvDfv33HfmEevjNISUS4c9KXIkiISEkVlQopyKgfY,55769
@@ -109,7 +109,7 @@ pygeodesy/rhumb/aux_.py,sha256=W4HkgoHAQz_aWaysfsmOA5010nCGvBfUs2Q-bymnHYU,16660
109
109
  pygeodesy/rhumb/bases.py,sha256=kzU_Dgt4FNPMgTg5rqbw-HiNpflDPKpmq9jhPbQmR4U,53851
110
110
  pygeodesy/rhumb/ekx.py,sha256=lF3tZ-ZY9fPJV8y1kgHW-7EOZCyb3gJr-kR-jj5Fbf8,23871
111
111
  pygeodesy/rhumb/solve.py,sha256=NZfwx7xv5UriQs7A0c7ZhoxxVUeAT15UwXK_jtwEXQw,27802
112
- PyGeodesy-24.4.18.dist-info/METADATA,sha256=kwVbSazBaM7cCqqiNZ0-SQbbnG7-Y3vLtmAh_yGMK3s,19366
113
- PyGeodesy-24.4.18.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
114
- PyGeodesy-24.4.18.dist-info/top_level.txt,sha256=cEQPatCXzKZqrivpULC5V5fuy9_V_bAwaP_gUGid7pQ,10
115
- PyGeodesy-24.4.18.dist-info/RECORD,,
112
+ PyGeodesy-24.4.24.dist-info/METADATA,sha256=IGu3-2Wu7HfzR8MYGeOU_oWysKdkDfMkndw45lFqWvY,19366
113
+ PyGeodesy-24.4.24.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
114
+ PyGeodesy-24.4.24.dist-info/top_level.txt,sha256=cEQPatCXzKZqrivpULC5V5fuy9_V_bAwaP_gUGid7pQ,10
115
+ PyGeodesy-24.4.24.dist-info/RECORD,,
pygeodesy/__init__.py CHANGED
@@ -586,7 +586,7 @@ else:
586
586
  _init__all__ = False
587
587
 
588
588
  from pygeodesy.interns import _DOT_, _version2 # PYCHOK import
589
- __version__ = '24.04.18'
589
+ __version__ = '24.04.24'
590
590
  # see setup.py for similar logic
591
591
  version = _DOT_(*_version2(__version__, n=3))
592
592
 
pygeodesy/constants.py CHANGED
@@ -23,7 +23,7 @@ except ImportError: # Python 2-
23
23
  _inf, _nan = float(_INF_), float(_NAN_)
24
24
 
25
25
  __all__ = _ALL_LAZY.constants
26
- __version__ = '24.04.04'
26
+ __version__ = '24.04.23'
27
27
 
28
28
 
29
29
  def _copysign_0_0(y):
@@ -247,7 +247,6 @@ _1_16th = _Float(_1_16th =_1_0 / _16_0) # PYCHOK in .ellipsoids, .karney
247
247
  _1_64th = _Float(_1_64th =_1_0 / 64) # PYCHOK in .elliptic, pow(2.0, -6)
248
248
  _1_3rd = _Float(_1_3rd =_1_0 / _3_0) # PYCHOK in .fmath
249
249
  _1_6th = _Float(_1_6th =_1_0 / _6_0) # PYCHOK in .fmath
250
- _2_3rd = _Float(_2_3rd =_2_0 / _3_0) # PYCHOK in .fmath
251
250
 
252
251
  _K0_UTM = _Float(_K0_UTM = 0.9996) # PYCHOK in .etm, .ktm, .utm, UTM scale at central meridian
253
252
  # sqrt(2) <https://WikiPedia.org/wiki/Square_root_of_2>
pygeodesy/fmath.py CHANGED
@@ -6,17 +6,14 @@ u'''Utilities using precision floating point summation.
6
6
  # make sure int/int division yields float quotient, see .basics
7
7
  from __future__ import division as _; del _ # PYCHOK semicolon
8
8
 
9
- from pygeodesy.basics import _copysign, copysign0, isint, len2
9
+ from pygeodesy.basics import _copysign, copysign0, isbool, isint, isscalar, len2
10
10
  from pygeodesy.constants import EPS0, EPS02, EPS1, NAN, PI, PI_2, PI_4, \
11
- _0_0, _0_125, _0_25, _0_5, _1_0, _1_3rd, \
12
- _1_5, _1_6th, _2_0, _2_3rd, _3_0, \
13
- _copysign_0_0, _isfinite, _over, remainder
11
+ _0_0, _0_125, _1_6th, _0_25, _1_3rd, _0_5, _1_0, \
12
+ _1_5, _copysign_0_0, _isfinite, _over, remainder
14
13
  from pygeodesy.errors import _IsnotError, LenError, _TypeError, _ValueError, \
15
14
  _xError, _xkwds_get, _xkwds_pop2
16
- from pygeodesy.fsums import _2float, Fsum, _fsum, fsum, fsum1_, _1primed, \
17
- Fmt, unstr
18
- from pygeodesy.interns import MISSING, _few_, _h_, _invokation_, _negative_, \
19
- _not_scalar_, _SPACE_, _too_
15
+ from pygeodesy.fsums import _2float, Fsum, fsum, fsum1_, _1primed, Fmt, unstr
16
+ from pygeodesy.interns import MISSING, _few_, _negative_, _not_scalar_, _too_
20
17
  from pygeodesy.lazily import _ALL_LAZY, _sys_version_info2
21
18
  # from pygeodesy.streprs import Fmt, unstr # from .fsums
22
19
  from pygeodesy.units import Int_, _isHeight, _isRadius, Float_ # PYCHOK for .heights
@@ -25,21 +22,14 @@ from math import fabs, sqrt # pow
25
22
  import operator as _operator # in .datums, .trf, .utm
26
23
 
27
24
  __all__ = _ALL_LAZY.fmath
28
- __version__ = '24.04.17'
25
+ __version__ = '24.04.24'
29
26
 
30
27
  # sqrt(2) <https://WikiPedia.org/wiki/Square_root_of_2>
31
28
  _0_4142 = 0.41421356237309504880 # ... sqrt(2) - 1
29
+ _2_3rd = _1_3rd * 2
32
30
  _h_lt_b_ = 'abs(h) < abs(b)'
33
31
 
34
32
 
35
- def _Fsum__init__(inst, raiser=MISSING, **name_RESIDUAL):
36
- '''(INTERNAL) Init an C{Fsum} instance.
37
- '''
38
- Fsum.__init__(inst, **name_RESIDUAL) # PYCHOK self
39
- inst._fset_ps(_0_0)
40
- return {} if raiser is MISSING else dict(raiser=raiser)
41
-
42
-
43
33
  class Fdot(Fsum):
44
34
  '''Precision dot product.
45
35
  '''
@@ -104,7 +94,7 @@ class Fhorner(Fsum):
104
94
 
105
95
 
106
96
  class Fhypot(Fsum):
107
- '''Precision summation and hypotenuse, default C{power=2}.
97
+ '''Precision summation and hypotenuse, default C{root=2}.
108
98
  '''
109
99
  def __init__(self, *xs, **root_name_RESIDUAL_raiser):
110
100
  '''New L{Fhypot} hypotenuse of (the I{root} of) several components.
@@ -189,7 +179,7 @@ class Froot(Fsum):
189
179
  try:
190
180
  raiser = _Fsum__init__(self, **name_RESIDUAL_raiser)
191
181
  if xs:
192
- self. fadd(xs)
182
+ self.fadd(xs)
193
183
  self._fset(self.root(root, **raiser))
194
184
  except Exception as X:
195
185
  raise self._ErrorXs(X, xs, root=root)
@@ -203,7 +193,7 @@ class Fcbrt(Froot):
203
193
 
204
194
  @see: Class L{Froot} for further details.
205
195
  '''
206
- Froot.__init__(self, _3_0, *xs, **name_RESIDUAL_raiser)
196
+ Froot.__init__(self, 3, *xs, **name_RESIDUAL_raiser)
207
197
 
208
198
 
209
199
  class Fsqrt(Froot):
@@ -214,15 +204,24 @@ class Fsqrt(Froot):
214
204
 
215
205
  @see: Class L{Froot} for further details.
216
206
  '''
217
- Froot.__init__(self, _2_0, *xs, **name_RESIDUAL_raiser)
207
+ Froot.__init__(self, 2, *xs, **name_RESIDUAL_raiser)
208
+
209
+
210
+ def _Fsum__init__(inst, raiser=MISSING, **name_RESIDUAL):
211
+ '''(INTERNAL) Init an C{F...} instance above.
212
+ '''
213
+ Fsum.__init__(inst, **name_RESIDUAL) # PYCHOK self
214
+ inst._fset_ps(_0_0)
215
+ return {} if raiser is MISSING else dict(raiser=raiser)
218
216
 
219
217
 
220
218
  def bqrt(x):
221
- '''Return the 4-th, I{bi-quadratic} or I{quartic} root, M{x**(1 / 4)}.
219
+ '''Return the 4-th, I{bi-quadratic} or I{quartic} root, M{x**(1 / 4)},
220
+ preserving C{type(B{x})}.
222
221
 
223
- @arg x: Value (C{scalar}).
222
+ @arg x: Value (C{scalar} or L{Fsum} instance).
224
223
 
225
- @return: I{Quartic} root (C{float}).
224
+ @return: I{Quartic} root (C{float} or L{Fsum}).
226
225
 
227
226
  @raise ValueError: Negative B{C{x}}.
228
227
 
@@ -232,70 +231,76 @@ def bqrt(x):
232
231
 
233
232
 
234
233
  try:
235
- from math import cbrt # Python 3.11+
236
-
237
- def cbrt2(x):
238
- '''Compute the cube root I{squared} M{x**(2/3)}.
239
- '''
240
- return cbrt(x)**2 # cbrt(-0.0*2) == -0.0
234
+ from math import cbrt as _cbrt # Python 3.11+
241
235
 
242
236
  except ImportError: # Python 3.10-
243
237
 
244
- def cbrt(x):
245
- '''Compute the cube root M{x**(1/3)}.
246
-
247
- @arg x: Value (C{scalar}).
248
-
249
- @return: Cubic root (C{float}).
250
-
251
- @see: Functions L{cbrt2} and L{sqrt3}.
238
+ def _cbrt(x):
239
+ '''(INTERNAL) Compute the I{signed}, cube root M{x**(1/3)}.
252
240
  '''
253
241
  # <https://archive.lib.MSU.edu/crcmath/math/math/r/r021.htm>
254
242
  # simpler and more accurate than Ken Turkowski's CubeRoot, see
255
243
  # <https://People.FreeBSD.org/~lstewart/references/apple_tr_kt32_cuberoot.pdf>
256
- return _copysign(pow(fabs(x), _1_3rd), x) # cbrt(-0.0) == -0.0
244
+ return _copysign(pow(fabs(x), _1_3rd), x) # to avoid complex
257
245
 
258
- def cbrt2(x): # PYCHOK attr
259
- '''Compute the cube root I{squared} M{x**(2/3)}.
260
246
 
261
- @arg x: Value (C{scalar}).
247
+ def cbrt(x):
248
+ '''Compute the cube root M{x**(1/3)}, preserving C{type(B{x})}.
262
249
 
263
- @return: Cube root I{squared} (C{float}).
250
+ @arg x: Value (C{scalar} or L{Fsum} instance).
264
251
 
265
- @see: Functions L{cbrt} and L{sqrt3}.
266
- '''
267
- return pow(fabs(x), _2_3rd) # XXX pow(fabs(x), _1_3rd)**2
252
+ @return: Cubic root (C{float} or L{Fsum}).
253
+
254
+ @see: Functions L{cbrt2} and L{sqrt3}.
255
+ '''
256
+ if isinstance(x, Fsum):
257
+ r = (-(-x).pow(_1_3rd)) if x < 0 else x.pow(_1_3rd)
258
+ else:
259
+ r = _cbrt(x)
260
+ return r # cbrt(-0.0) == -0.0
261
+
262
+
263
+ def cbrt2(x): # PYCHOK attr
264
+ '''Compute the cube root I{squared} M{x**(2/3)}, preserving C{type(B{x})}.
265
+
266
+ @arg x: Value (C{scalar} or L{Fsum} instance).
267
+
268
+ @return: Cube root I{squared} (C{float} or L{Fsum}).
269
+
270
+ @see: Functions L{cbrt} and L{sqrt3}.
271
+ '''
272
+ return abs(x).pow(_2_3rd) if isinstance(x, Fsum) else _cbrt(x**2)
268
273
 
269
274
 
270
275
  def euclid(x, y):
271
276
  '''I{Appoximate} the norm M{sqrt(x**2 + y**2)} by
272
277
  M{max(abs(x), abs(y)) + min(abs(x), abs(y)) * 0.4142...}.
273
278
 
274
- @arg x: X component (C{scalar}).
275
- @arg y: Y component (C{scalar}).
279
+ @arg x: X component (C{scalar} or L{Fsum} instance).
280
+ @arg y: Y component (C{scalar} or L{Fsum} instance).
276
281
 
277
- @return: Appoximate norm (C{float}).
282
+ @return: Appoximate norm (C{float} or L{Fsum}).
278
283
 
279
284
  @see: Function L{euclid_}.
280
285
  '''
281
- x, y = fabs(x), fabs(y)
286
+ x, y = abs(x), abs(y) # NOT fabs!
282
287
  if x < y:
283
288
  x, y = y, x
284
289
  return x + y * _0_4142 # XXX * _0_5 before 20.10.02
285
290
 
286
291
 
287
292
  def euclid_(*xs):
288
- '''I{Appoximate} the norm M{sqrt(sum(x**2 for x in xs))}
289
- by cascaded L{euclid}.
293
+ '''I{Appoximate} the norm M{sqrt(sum(x**2 for x in xs))} by
294
+ cascaded L{euclid}.
290
295
 
291
- @arg xs: X arguments, positional (C{scalar}s).
296
+ @arg xs: X arguments, positional (C{scalar}s or L{Fsum} instances).
292
297
 
293
- @return: Appoximate norm (C{float}).
298
+ @return: Appoximate norm (C{float} or L{Fsum}).
294
299
 
295
300
  @see: Function L{euclid}.
296
301
  '''
297
302
  e = _0_0
298
- for x in sorted(map(fabs, xs)): # XXX not reverse=True
303
+ for x in sorted(map(abs, xs)): # NOT fabs, reverse=True!
299
304
  # e = euclid(x, e)
300
305
  if e < x:
301
306
  e, x = x, e
@@ -388,8 +393,8 @@ def fatan2(y, x):
388
393
  def favg(v1, v2, f=_0_5):
389
394
  '''Return the average of two values.
390
395
 
391
- @arg v1: One value (C{scalar}).
392
- @arg v2: Other value (C{scalar}).
396
+ @arg v1: One value (C{scalar} or L{Fsum} instance).
397
+ @arg v2: Other value (C{scalar} or L{Fsum} instance).
393
398
  @kwarg f: Optional fraction (C{float}).
394
399
 
395
400
  @return: M{v1 + f * (v2 - v1)} (C{float}).
@@ -606,6 +611,39 @@ except ImportError:
606
611
  return freduce(_operator.mul, xs, start)
607
612
 
608
613
 
614
+ def frandoms(n, seeded=None):
615
+ '''Generate C{n} (long) lists of random C{floats}.
616
+
617
+ @arg n: Number of lists to generate (C{int}, non-negative).
618
+ @kwarg seeded: If C{scalar}, use C{random.seed(B{seeded})} or
619
+ if C{True}, seed using today's C{year-day}.
620
+
621
+ @see: U{Hettinger<https://GitHub.com/ActiveState/code/tree/master/recipes/
622
+ Python/393090_Binary_floating_point_summatiaccurate_full/recipe-393090.py>}.
623
+ '''
624
+ from random import gauss, random, seed, shuffle
625
+
626
+ if seeded is None:
627
+ pass
628
+ elif seeded and isbool(seeded):
629
+ from time import localtime
630
+ seed(localtime().tm_yday)
631
+ elif isscalar(seeded):
632
+ seed(seeded)
633
+
634
+ c = (7, 1e100, -7, -1e100, -9e-20, 8e-20) * 7
635
+ for _ in range(n):
636
+ s = 0
637
+ t = list(c)
638
+ _a = t.append
639
+ for _ in range(n * 8):
640
+ v = gauss(0, random())**7 - s
641
+ _a(v)
642
+ s += v
643
+ shuffle(t)
644
+ yield t
645
+
646
+
609
647
  def frange(start, number, step=1):
610
648
  '''Generate a range of C{float}s.
611
649
 
@@ -715,8 +753,8 @@ if _sys_version_info2 < (3, 8): # PYCHOK no cover
715
753
  computed as M{hypot_(*((c1 - c2) for c1, c2 in zip(p1, p2)))},
716
754
  provided I{p1} and I{p2} have the same, non-zero length I{n}.
717
755
  '''
718
- h, x2 = _h_x2(xs, hypot_)
719
- return (h * sqrt(x2)) if x2 else _0_0
756
+ _, R = _h_xs2(xs, True, hypot_)
757
+ return float(R)
720
758
 
721
759
  elif _sys_version_info2 < (3, 10):
722
760
  # In Python 3.8 and 3.9 C{math.hypot} is inaccurate, see
@@ -732,13 +770,8 @@ elif _sys_version_info2 < (3, 10):
732
770
 
733
771
  @return: C{sqrt(B{x}**2 + B{y}**2)} (C{float}).
734
772
  '''
735
- if x:
736
- h = sqrt(x**2 + y**2) if y else fabs(x)
737
- elif y:
738
- h = fabs(y)
739
- else:
740
- h = _0_0
741
- return h
773
+ return (float(Fhypot(x, y, raiser=False)) if y else
774
+ fabs(x)) if x else fabs(y)
742
775
 
743
776
  from math import hypot as hypot_ # PYCHOK in Python 3.8 and 3.9
744
777
  else:
@@ -746,29 +779,32 @@ else:
746
779
  hypot_ = hypot
747
780
 
748
781
 
749
- def _h_x2(xs, which):
782
+ def _h_xs2(xs, pot_, which):
750
783
  '''(INTERNAL) Helper for L{hypot_} and L{hypot2_}.
751
784
  '''
752
785
  n, xs = len2(xs)
753
786
  if n > 0:
754
- h = float(max(map(fabs, xs)))
787
+ h = float(max(map(abs, xs))) # NOT fabs!
755
788
  if h < EPS0:
756
- x2 = _0_0
789
+ R = _0_0
757
790
  elif n > 1:
758
- _h = (_1_0 / h) if h != _1_0 else _1_0
759
- x2 = _fsum(_1primed((x * _h)**2 for x in xs))
791
+ if pot_:
792
+ if h != _1_0:
793
+ xs = ((x / h) for x in xs)
794
+ R = Fhypot(*xs, raiser=False) * h
795
+ else:
796
+ R = Fpowers(2, *xs)
760
797
  else:
761
- x2 = _1_0
762
- return h, x2
798
+ R = h if pot_ else (h**2)
799
+ return h, R
763
800
 
764
- t = Fmt.PAREN(which.__name__, xs)
765
- raise _ValueError(t, txt=_too_(_few_))
801
+ raise _ValueError(unstr(which, xs), txt=_too_(_few_))
766
802
 
767
803
 
768
804
  def hypot1(x):
769
805
  '''Compute the norm M{sqrt(1 + x**2)}.
770
806
 
771
- @arg x: Argument (C{scalar}).
807
+ @arg x: Argument (C{scalar} or L{Fsum} instance).
772
808
 
773
809
  @return: Norm (C{float}).
774
810
  '''
@@ -778,12 +814,12 @@ def hypot1(x):
778
814
  def hypot2(x, y):
779
815
  '''Compute the I{squared} norm M{x**2 + y**2}.
780
816
 
781
- @arg x: X argument (C{scalar}).
782
- @arg y: Y argument (C{scalar}).
817
+ @arg x: X argument (C{scalar} or L{Fsum} instance).
818
+ @arg y: Y argument (C{scalar} or L{Fsum} instance).
783
819
 
784
- @return: C{B{x}**2 + B{y}**2} (C{float}).
820
+ @return: C{B{x}**2 + B{y}**2} (C{float} or L{Fsum}).
785
821
  '''
786
- if fabs(x) < fabs(y):
822
+ if abs(x) < abs(y): # NOT fabs!
787
823
  x, y = y, x
788
824
  if x:
789
825
  h2 = x**2
@@ -795,9 +831,10 @@ def hypot2(x, y):
795
831
 
796
832
 
797
833
  def hypot2_(*xs):
798
- '''Compute the I{squared} norm C{sum(x**2 for x in B{xs})}.
834
+ '''Compute the I{squared} norm C{fsum(x**2 for x in B{xs})}.
799
835
 
800
- @arg xs: X arguments (C{scalar}s), all positional.
836
+ @arg xs: X arguments (C{scalar}s or L{Fsum} instances),
837
+ all positional.
801
838
 
802
839
  @return: Squared norm (C{float}).
803
840
 
@@ -807,8 +844,8 @@ def hypot2_(*xs):
807
844
 
808
845
  @see: Function L{hypot_}.
809
846
  '''
810
- h, x2 = _h_x2(xs, hypot2_)
811
- return (h**2 * x2) if x2 else _0_0
847
+ _, R = _h_xs2(xs, False, hypot2_)
848
+ return float(R)
812
849
 
813
850
 
814
851
  def _map_mul(a, b, where):
@@ -860,12 +897,13 @@ def norm_(*xs):
860
897
  or zero norm.
861
898
  '''
862
899
  try:
900
+ i = x = h = None
863
901
  h = hypot_(*xs)
864
902
  _h = (_1_0 / h) if h else _0_0
865
903
  for i, x in enumerate(xs):
866
904
  yield x * _h
867
- except Exception as e:
868
- raise _xError(e, Fmt.SQUARE(xs=i), x, _h_, h)
905
+ except Exception as X:
906
+ raise _xError(X, Fmt.SQUARE(xs=i), x, h=h)
869
907
 
870
908
 
871
909
  def _powers(x, n):
@@ -880,34 +918,40 @@ def _powers(x, n):
880
918
  def _root(x, p, where):
881
919
  '''(INTERNAL) Raise C{x} to power C{0 < p < 1}.
882
920
  '''
883
- if x < 0:
884
- t = _SPACE_(_invokation_, where.__name__)
885
- raise _ValueError(unstr(t, x), txt=_negative_)
886
- return pow(x, p) if x else _0_0
921
+ try:
922
+ if x > 0:
923
+ return pow(x, p)
924
+ elif x < 0:
925
+ raise ValueError(_negative_)
926
+ except Exception as X:
927
+ raise _xError(X, unstr(where, x))
928
+ return _0_0
887
929
 
888
930
 
889
931
  def sqrt0(x, Error=None):
890
- '''Return the square root iff C{B{x} >} L{EPS02}.
932
+ '''Return the square root C{sqrt(B{x})} iff C{B{x} > }L{EPS02},
933
+ preserving C{type(B{x})}.
891
934
 
892
- @arg x: Value (C{scalar}).
935
+ @arg x: Value (C{scalar} or L{Fsum} instance).
893
936
  @kwarg Error: Error to raise for negative B{C{x}}.
894
937
 
895
- @return: Square root (C{float}) or C{0.0}.
938
+ @return: Square root (C{float} or L{Fsum}) or C{0.0}.
896
939
 
897
940
  @note: Any C{B{x} < }L{EPS02} I{including} C{B{x} < 0}
898
941
  returns C{0.0}.
899
942
  '''
900
943
  if Error and x < 0:
901
- raise Error(Fmt.PAREN(sqrt=x))
902
- return sqrt(x) if x > EPS02 else (_0_0 if x < EPS02 else EPS0)
944
+ raise Error(unstr(sqrt0, x))
945
+ return _root(x, _0_5, sqrt0) if x > EPS02 else (_0_0 if x < EPS02 else EPS0)
903
946
 
904
947
 
905
948
  def sqrt3(x):
906
- '''Return the square root, I{cubed} M{sqrt(x)**3} or M{sqrt(x**3)}.
949
+ '''Return the square root, I{cubed} M{sqrt(x)**3} or M{sqrt(x**3)},
950
+ preserving C{type(B{x})}.
907
951
 
908
- @arg x: Value (C{scalar}).
952
+ @arg x: Value (C{scalar} or L{Fsum} instance).
909
953
 
910
- @return: Square root I{cubed} (C{float}).
954
+ @return: Square root I{cubed} (C{float} or L{Fsum}).
911
955
 
912
956
  @raise ValueError: Negative B{C{x}}.
913
957
 
@@ -956,11 +1000,12 @@ def sqrt_a(h, b):
956
1000
 
957
1001
 
958
1002
  def zcrt(x):
959
- '''Return the 6-th, I{zenzi-cubic} root, M{x**(1 / 6)}.
1003
+ '''Return the 6-th, I{zenzi-cubic} root, M{x**(1 / 6)},
1004
+ preserving C{type(B{x})}.
960
1005
 
961
- @arg x: Value (C{scalar}).
1006
+ @arg x: Value (C{scalar} or L{Fsum} instance).
962
1007
 
963
- @return: I{Zenzi-cubic} root (C{float}).
1008
+ @return: I{Zenzi-cubic} root (C{float} or L{Fsum}).
964
1009
 
965
1010
  @see: Functions L{bqrt} and L{zqrt}.
966
1011
 
@@ -970,11 +1015,12 @@ def zcrt(x):
970
1015
 
971
1016
 
972
1017
  def zqrt(x):
973
- '''Return the 8-th, I{zenzi-quartic} or I{squared-quartic} root, M{x**(1 / 8)}.
1018
+ '''Return the 8-th, I{zenzi-quartic} or I{squared-quartic} root,
1019
+ M{x**(1 / 8)}, preserving C{type(B{x})}.
974
1020
 
975
- @arg x: Value (C{scalar}).
1021
+ @arg x: Value (C{scalar} or L{Fsum} instance).
976
1022
 
977
- @return: I{Zenzi-quartic} root (C{float}).
1023
+ @return: I{Zenzi-quartic} root (C{float} or L{Fsum}).
978
1024
 
979
1025
  @see: Functions L{bqrt} and L{zcrt}.
980
1026
 
pygeodesy/fsums.py CHANGED
@@ -29,8 +29,9 @@ from pygeodesy.basics import iscomplex, isint, isscalar, itemsorted, \
29
29
  signOf, _signOf
30
30
  from pygeodesy.constants import INT0, _isfinite, NEG0, _pos_self, \
31
31
  _0_0, _1_0, _N_1_0, Float, Int
32
- from pygeodesy.errors import _OverflowError, _TypeError, _ValueError, _xError, \
33
- _xError2, _xkwds_get, _ZeroDivisionError
32
+ from pygeodesy.errors import _AssertionError, _OverflowError, _TypeError, \
33
+ _ValueError, _xError, _xError2, _xkwds_get, \
34
+ _ZeroDivisionError
34
35
  from pygeodesy.interns import NN, _arg_, _COMMASPACE_, _DASH_, _DOT_, _EQUAL_, \
35
36
  _exceeds_, _from_, _iadd_op_, _LANGLE_, _negative_, \
36
37
  _NOTEQUAL_, _not_finite_, _PERCENT_, _PLUS_, _R_, \
@@ -45,7 +46,7 @@ from pygeodesy.props import _allPropertiesOf_n, deprecated_property_RO, \
45
46
  from math import ceil as _ceil, fabs, floor as _floor # PYCHOK used! .ltp
46
47
 
47
48
  __all__ = _ALL_LAZY.fsums
48
- __version__ = '24.04.18'
49
+ __version__ = '24.04.24'
49
50
 
50
51
  _add_op_ = _PLUS_ # in .auxilats.auxAngle
51
52
  _eq_op_ = _EQUAL_ * 2 # _DEQUAL_
@@ -145,6 +146,12 @@ def _2halfeven(s, r, p):
145
146
  return s
146
147
 
147
148
 
149
+ def _1_over(x, op=_truediv_op_, **raiser):
150
+ '''(INTERNAL) Return C{Fsum(1) /= B{x}}.
151
+ '''
152
+ return _Psum_(_1_0)._ftruediv(x, op, **raiser)
153
+
154
+
148
155
  def _1primed(xs): # in .fmath
149
156
  '''(INTERNAL) 1-Primed summation of iterable C{xs}
150
157
  items, all I{known} to be C{finite float}.
@@ -156,9 +163,11 @@ def _1primed(xs): # in .fmath
156
163
 
157
164
 
158
165
  def _2ps(s, r):
159
- '''(INTERNAL) Return a C{s} and C{r} pair, I{ps-ordered}.
166
+ '''(INTERNAL) Return an C{s} and C{r} pair, I{ps-ordered}.
160
167
  '''
161
- return (s, r) if fabs(s) < fabs(r) else (r, s)
168
+ if fabs(s) < fabs(r):
169
+ s, r = r, s
170
+ return (r, s) if r else (s,) # PYCHOK types
162
171
 
163
172
 
164
173
  def _psum(ps): # PYCHOK used!
@@ -192,13 +201,10 @@ def _Psum(ps, **name):
192
201
  return F
193
202
 
194
203
 
195
- def _Psum_1(p=_1_0, **name):
196
- '''(INTERNAL) Return an C{Fsum} from a single partial C{p}.
204
+ def _Psum_(*ps, **name):
205
+ '''(INTERNAL) Return an C{Fsum} from 1 or 2 known scalar(s) C{ps}.
197
206
  '''
198
- F = Fsum(**name) if name else Fsum()
199
- F._ps[:] = p,
200
- F._n = 1 # len(F._ps)
201
- return F
207
+ return _Psum(ps, **name)
202
208
 
203
209
 
204
210
  def _2scalar(other, _raiser=None, **mod):
@@ -267,9 +273,9 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
267
273
  @note: Handling of exceptions and C{inf}, C{INF}, C{nan} and C{NAN} differs from
268
274
  Python's C{math.fsum}.
269
275
 
270
- @see: U{Hettinger<https://GitHub.com/ActiveState/code/blob/master/recipes/Python/
271
- 393090_Binary_floating_point_summatiaccurate_full/recipe-393090.py>}, U{Kahan
272
- <https://WikiPedia.org/wiki/Kahan_summation_algorithm>}, U{Klein
276
+ @see: U{Hettinger<https://GitHub.com/ActiveState/code/tree/master/recipes/Python/
277
+ 393090_Binary_floating_point_summatiaccurate_full/recipe-393090.py>},
278
+ U{Kahan<https://WikiPedia.org/wiki/Kahan_summation_algorithm>}, U{Klein
273
279
  <https://Link.Springer.com/article/10.1007/s00607-005-0139-x>}, Python 2.6+
274
280
  file I{Modules/mathmodule.c} and the issue log U{Full precision summation
275
281
  <https://Bugs.Python.org/issue2819>}.
@@ -336,8 +342,8 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
336
342
  '''
337
343
  return self.ceil
338
344
 
339
- def __cmp__(self, other): # Python 2-
340
- '''Compare this with an other instance or C{scalar}.
345
+ def __cmp__(self, other): # PYCHOK no cover
346
+ '''Compare this with an other instance or C{scalar}, Python 2-.
341
347
 
342
348
  @return: -1, 0 or +1 (C{int}).
343
349
 
@@ -686,8 +692,8 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
686
692
  @arg ndigits: Optional number of digits (C{int}).
687
693
  '''
688
694
  # <https://docs.Python.org/3.12/reference/datamodel.html?#object.__round__>
689
- return _Psum_1(round(float(self), *ndigits), # can be C{int}
690
- name=self.__round__.__name__)
695
+ return _Psum_(round(float(self), *ndigits), # can be C{int}
696
+ name=self.__round__.__name__)
691
697
 
692
698
  def __rpow__(self, other, *mod):
693
699
  '''Return C{B{other}**B{self}} as an L{Fsum}.
@@ -771,6 +777,13 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
771
777
  d = 1
772
778
  return n, d
773
779
 
780
+ @property_RO
781
+ def as_iscalar(self):
782
+ '''Get this instance I{as-is} (L{Fsum}) or C{scalar} iff scalar.
783
+ '''
784
+ s, r = self._fprs2
785
+ return self if r else s
786
+
774
787
  @property_RO
775
788
  def ceil(self):
776
789
  '''Get this instance' C{ceil} value (C{int} in Python 3+,
@@ -854,7 +867,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
854
867
  def _Error(self, op, other, Error, **txt_cause):
855
868
  '''(INTERNAL) Format an B{C{Error}} for C{{self} B{op} B{other}}.
856
869
  '''
857
- return Error(_SPACE_(self.toStr(), op, other), **txt_cause)
870
+ return Error(_SPACE_(self.as_iscalar, op, other), **txt_cause)
858
871
 
859
872
  def _ErrorX(self, X, op, other, *mod):
860
873
  '''(INTERNAL) Format the caught exception C{X}.
@@ -875,9 +888,9 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
875
888
  '''(INTERNAL) Accumulate more C{scalars} or L{Fsum}s.
876
889
  '''
877
890
  if xs:
878
- _x = _2floats(xs, **origin_X_x) # PYCHOK yield
879
- ps = self._ps
880
- ps[:] = self._ps_acc(list(ps), _x, up=up)
891
+ _xs = _2floats(xs, **origin_X_x) # PYCHOK yield
892
+ ps = self._ps
893
+ ps[:] = self._ps_acc(list(ps), _xs, up=up)
881
894
  return self
882
895
 
883
896
  def _facc_1(self, xs, **up):
@@ -907,7 +920,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
907
920
  r = 0
908
921
  if isinstance(p, Fsum):
909
922
  s, r = p._fprs2
910
- if r:
923
+ if r == 0:
911
924
  return _Pow4(s)
912
925
  m = Fsum._pow
913
926
  elif isint(p, both=True) and int(p) >= 0:
@@ -922,7 +935,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
922
935
  if p: # and xs:
923
936
  _pow = Fsum._pow_2_3
924
937
  _Fs = Fsum
925
- _Ps = _Psum_1()._fset_ps_
938
+ _Ps = _Psum_ # ()._fset_ps_
926
939
  op = which.__name__
927
940
 
928
941
  def _X(X):
@@ -1052,24 +1065,28 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1052
1065
  raise ValueError(_not_finite_) if op is None else \
1053
1066
  self._ValueError(op, other, txt=_not_finite_)
1054
1067
 
1055
- def fint(self, raiser=True, **name):
1068
+ def fint(self, raiser=True, name=NN, **RESIDUAL):
1056
1069
  '''Return this instance' current running sum as C{integer}.
1057
1070
 
1058
1071
  @kwarg raiser: Use C{B{raiser}=False} to ignore L{ResidualError}s
1059
1072
  (C{bool}), see also method L{RESIDUAL}.
1060
1073
  @kwarg name: Optional name (C{str}), overriding C{"fint"}.
1074
+ @kwarg RESIDUAL: Optional threshold, overriding the current
1075
+ L{RESIDUAL<Fsum.RESIDUAL>} (C{scalar}).
1061
1076
 
1062
- @return: The C{integer} (L{Fsum}).
1077
+ @return: The C{integer} sum (L{Fsum}) if this instance
1078
+ C{is_integer} and the residual is zero or
1079
+ insignificant or if C{B{raiser}=False}.
1063
1080
 
1064
1081
  @raise ResidualError: Non-zero I{integer} residual.
1065
1082
 
1066
1083
  @see: Methods L{Fsum.int_float} and L{Fsum.is_integer}.
1067
1084
  '''
1068
1085
  i, r = self._fint2
1069
- if r and raiser:
1086
+ if r and raiser and self._raiser2sum(r, i, **RESIDUAL):
1070
1087
  t = _stresidual(_integer_, r)
1071
1088
  raise ResidualError(_integer_, i, txt=t)
1072
- f = self._copy_2(self.fint, **name)
1089
+ f = self._copy_2(self.fint, name=name)
1073
1090
  return f._fset(i)
1074
1091
 
1075
1092
  def fint2(self, **name):
@@ -1165,7 +1182,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1165
1182
  i, _ = self._fint2 # assert _ == 0
1166
1183
  x = _2scalar(other) # C{int}, C{float} or other
1167
1184
  f = self._pow_2_3(i, x, other, op, **raiser) if isscalar(x) else \
1168
- _Psum_1(i)._pow( x, other, op, **raiser) # x is Fsum
1185
+ _Psum_(i)._pow( x, other, op, **raiser) # x is Fsum
1169
1186
  else: # mod[0] is None, power(self, other)
1170
1187
  f = self._pow(other, other, op, **raiser)
1171
1188
  else: # pow(self, other)
@@ -1250,7 +1267,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1250
1267
  # Property's _fset zaps the value just set by the @setter
1251
1268
  self.__dict__.update(_fint2=t, _fprs=s, _fprs2=Fsum2Tuple(s, INT0))
1252
1269
  else: # PYCHOK no cover
1253
- raise self._TypeError(_fset_op_, other) # AssertionError
1270
+ raise self._Error(_fset_op_, other, _AssertionError)
1254
1271
  return self
1255
1272
 
1256
1273
  def _fset_ps(self, other, n=0): # in .fmath
@@ -1264,12 +1281,12 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1264
1281
  self._n = n or 1
1265
1282
  return self
1266
1283
 
1267
- def _fset_ps_(self, *xs):
1268
- '''(INTERNAL) Set partials to all known scalar C{xs}.
1269
- '''
1270
- self._ps[:] = xs
1271
- self.n = len(xs)
1272
- return self
1284
+ # def _fset_ps_(self, *xs):
1285
+ # '''(INTERNAL) Set partials to all known scalar C{xs}.
1286
+ # '''
1287
+ # self._ps[:] = xs
1288
+ # self.n = len(xs)
1289
+ # return self
1273
1290
 
1274
1291
  def fsub(self, xs=()):
1275
1292
  '''Subtract an iterable of C{scalar} or L{Fsum} instances from
@@ -1436,24 +1453,26 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1436
1453
  '''
1437
1454
  return _0_0
1438
1455
 
1439
- def int_float(self, raiser=False):
1456
+ def int_float(self, raiser=False, **RESIDUAL):
1440
1457
  '''Return this instance' current running sum as C{int} or C{float}.
1441
1458
 
1442
1459
  @kwarg raiser: If C{True} throw a L{ResidualError} if the
1443
- residual is non-zero (C{bool}).
1460
+ residual exceeds the C{RESIDUAL} (C{bool}).
1461
+ @kwarg RESIDUAL: Optional threshold, overriding the current
1462
+ L{RESIDUAL<Fsum.RESIDUAL>} (C{scalar}).
1444
1463
 
1445
1464
  @return: This C{integer} sum if this instance C{is_integer},
1446
- otherwise return the C{float} sum if the residual
1447
- is zero or if C{B{raiser}=False}.
1465
+ otherwise return the C{float} sum if the residual is
1466
+ zero or insignificant or if C{B{raiser}=False}.
1448
1467
 
1449
1468
  @raise ResidualError: Non-zero residual and C{B{raiser}=True}.
1450
1469
 
1451
- @see: Methods L{Fsum.fint} and L{Fsum.fint2}.
1470
+ @see: Methods L{Fsum.fint} and L{Fsum.fint2} and property L{Fsum.as_iscalar}.
1452
1471
  '''
1453
1472
  s, r = self._fint2
1454
1473
  if r:
1455
1474
  s, r = self._fprs2
1456
- if r and raiser: # PYCHOK no cover
1475
+ if r and raiser and self._raiser2sum(r, s, **RESIDUAL): # PYCHOK no cover
1457
1476
  t = _stresidual(_non_zero_, r)
1458
1477
  raise ResidualError(int_float=s, txt=t)
1459
1478
  s = float(s) # redundant
@@ -1467,7 +1486,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1467
1486
  def is_integer(self):
1468
1487
  '''Is this instance' running sum C{integer}? (C{bool}).
1469
1488
 
1470
- @see: Methods L{Fsum.fint} and L{Fsum.fint2}.
1489
+ @see: Methods L{Fsum.fint}, L{Fsum.fint2} and L{Fsum.is_scalar}.
1471
1490
  '''
1472
1491
  _, r = self._fint2
1473
1492
  return False if r else True
@@ -1486,12 +1505,20 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1486
1505
  f = Fsum._math_fsum
1487
1506
  return 2 if _psum is f else bool(f)
1488
1507
 
1508
+ def is_scalar(self):
1509
+ '''Is this instance' running sum C{scalar}? (C{bool}).
1510
+
1511
+ @see: Method L{Fsum.is_integer} and property L{Fsum.as_iscalar}.
1512
+ '''
1513
+ s, r = t = self._fprs2
1514
+ return False if r and _2sum(s, r) != t else True
1515
+
1489
1516
  def _mul_Fsum(self, other, op=_mul_op_): # in .fmath.Fhorner
1490
1517
  '''(INTERNAL) Return C{B{self} * Fsum B{other}} as L{Fsum} or C{0}.
1491
1518
  '''
1492
1519
  # assert isinstance(other, Fsum)
1493
1520
  if self._ps and other._ps:
1494
- f = self._ps_mul(op, *other._ps) # NO ._2scalar
1521
+ f = self._ps_mul(op, *other._ps) # NO .as_iscalar
1495
1522
  else:
1496
1523
  f = _0_0
1497
1524
  return f
@@ -1503,7 +1530,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1503
1530
  if self._ps and self._finite(factor, op):
1504
1531
  f = self if factor == _1_0 else (
1505
1532
  self._neg if factor == _N_1_0 else
1506
- self._ps_mul(op, factor)._2scalar)
1533
+ self._ps_mul(op, factor).as_iscalar)
1507
1534
  else:
1508
1535
  f = _0_0
1509
1536
  return f
@@ -1594,7 +1621,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1594
1621
  _mul_Fsum = Fsum._mul_Fsum
1595
1622
  if x > 4:
1596
1623
  p = self
1597
- f = self if (x & 1) else _Psum_1()
1624
+ f = self if (x & 1) else _Psum_(_1_0)
1598
1625
  m = x >> 1 # // 2
1599
1626
  while m:
1600
1627
  p = _mul_Fsum(p, p, op) # p **= 2
@@ -1605,7 +1632,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1605
1632
  f = _mul_Fsum(self, self, op)
1606
1633
  if x > 2: # self**3 or 4
1607
1634
  p = self if x < 4 else f
1608
- f = _mul_Fsum(f, p, op)._2scalar
1635
+ f = _mul_Fsum(f, p, op).as_iscalar
1609
1636
  else: # self**1 or self**0 == 1 or _1_0
1610
1637
  f = self._pow_0_1(x, other)
1611
1638
  elif ps: # self._ps[0]**x
@@ -1630,19 +1657,23 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1630
1657
  # assert x < 0 # < -1
1631
1658
  s, r = f._fprs2 if isinstance(f, Fsum) else (f, 0)
1632
1659
  if r:
1633
- return _Psum_1()._ftruediv(f, op, **raiser)
1660
+ return _1_over(f, op, **raiser) # PYCHOK 2 args
1634
1661
  # use **= -1 for the CPython float_pow
1635
1662
  # error if s is zero, and not s = 1 / s
1636
1663
  x = -1
1637
1664
  elif x < 0: # == -1: self**(-1) == 1 / self
1638
1665
  if r:
1639
- return _Psum_1()._ftruediv(self, op, **raiser)
1666
+ return _1_over(self, op, **raiser) # PYCHOK 2 args
1640
1667
  else: # self**1 or self**0
1641
1668
  return self._pow_0_1(x, other) # self, 1 or 1.0
1642
- elif r and self._raiser(r, s, **raiser): # non-zero residual**fractional
1643
- # raise self._ResidualError(op, other, r, fractional_power=x)
1644
- t = _stresidual(_non_zero_, r, fractional_power=x)
1645
- raise self._Error(op, other, ResidualError, txt=t)
1669
+ elif r: # non-zero residual**fractional
1670
+ if s:
1671
+ n, d = self.as_integer_ratio()
1672
+ if abs(n) > abs(d):
1673
+ n, d, x = d, n, (-x)
1674
+ s = n / d
1675
+ else:
1676
+ s = r
1646
1677
  # assert isscalar(s) and isscalar(x)
1647
1678
  return self._pow_2_3(s, x, other, op, **raiser)
1648
1679
 
@@ -1701,11 +1732,22 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1701
1732
  yield -p
1702
1733
  yield _N_1_0
1703
1734
 
1704
- def _raiser(self, r, s, raiser=True):
1735
+ def _raiser(self, r, s, raiser=True, **RESIDUAL):
1705
1736
  '''(INTERNAL) Does ratio C{r / s} exceed the RESIDUAL threshold?
1706
1737
  '''
1707
- self._ratio = t = fabs(r / s) if s else 0 # _0_0
1708
- return raiser and (t > self._RESIDUAL)
1738
+ self._ratio = r = (r / s) if s else s # == 0.
1739
+ if r and raiser:
1740
+ R = self._RESIDUAL
1741
+ if RESIDUAL:
1742
+ R = _xkwds_get(RESIDUAL, RESIDUAL=R)
1743
+ return fabs(r) > R
1744
+ return False
1745
+
1746
+ def _raiser2sum(self, r, s, **raiser_RESIDUAL):
1747
+ '''(INTERNAL) Does ratio C{r / s} exceed the RESIDUAL threshold
1748
+ I{and} is the residual B{C{r}} significant vs sum B{C{s}}?
1749
+ '''
1750
+ return self._raiser(r, s, **raiser_RESIDUAL) and _2sum(s, r) != (s, r)
1709
1751
 
1710
1752
  @property_RO
1711
1753
  def real(self):
@@ -1739,7 +1781,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1739
1781
  C{PYGEODESY_FSUM_RESIDUAL} or if omitted, keep the
1740
1782
  current setting.
1741
1783
 
1742
- @return: The previous C{RESIDUAL} setting (C{float}), default C{0.}.
1784
+ @return: The previous C{RESIDUAL} setting (C{float}), default C{0.0}.
1743
1785
 
1744
1786
  @raise ValueError: Negative B{C{threshold}}.
1745
1787
 
@@ -1777,18 +1819,9 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1777
1819
 
1778
1820
  @see: Method L{Fsum.pow}.
1779
1821
  '''
1780
- _root_ = self.root.__name__
1781
- if isinstance(root, Fsum):
1782
- x = root.__rtruediv__(_1_0, **raiser)
1783
- else:
1784
- try:
1785
- x = _1_0 / _2float(root=root)
1786
- except Exception as X:
1787
- E, t = _xError2(X)
1788
- n = _SPACE_(_1_0, _truediv_op_, _root_)
1789
- raise E(n, root, txt=t, cause=X)
1790
- f = self._copy_2(self.root)
1791
- return f._fpow(x, _root_, **raiser) # == pow(f, x)
1822
+ x = _1_over(root, **raiser)
1823
+ f = self._copy_2(self.root)
1824
+ return f._fpow(x, f.name, **raiser) # == pow(f, x)
1792
1825
 
1793
1826
  def _scalar(self, other, op, **txt):
1794
1827
  '''(INTERNAL) Return scalar C{other}.
@@ -1797,13 +1830,6 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1797
1830
  return other
1798
1831
  raise self._TypeError(op, other, **txt) # _invalid_
1799
1832
 
1800
- @property_RO
1801
- def _2scalar(self):
1802
- '''(INTERNAL) Get this instance as C{scalar} or C{as-is}.
1803
- '''
1804
- s, r = self._fprs2
1805
- return self if r else s
1806
-
1807
1833
  def signOf(self, res=True):
1808
1834
  '''Determine the sign of this instance.
1809
1835
 
@@ -2049,23 +2075,14 @@ if __name__ == '__main__':
2049
2075
 
2050
2076
  def _test(n):
2051
2077
  # copied from Hettinger, see L{Fsum} reference
2052
- from pygeodesy import printf
2053
- from random import gauss, random, shuffle
2078
+ from pygeodesy import frandoms, printf
2054
2079
 
2055
2080
  printf(_fsum.__name__, end=_COMMASPACE_)
2056
2081
  printf(_psum.__name__, end=_COMMASPACE_)
2057
2082
 
2058
2083
  F = Fsum()
2059
2084
  if F.is_math_fsum():
2060
- c = (7, 1e100, -7, -1e100, -9e-20, 8e-20) * 10
2061
- for _ in range(n):
2062
- t = list(c)
2063
- s = 0
2064
- for _ in range(n * 8):
2065
- v = gauss(0, random())**7 - s
2066
- t.append(v)
2067
- s += v
2068
- shuffle(t)
2085
+ for t in frandoms(n, seeded=True):
2069
2086
  assert float(F.fset_(*t)) == _fsum(t)
2070
2087
  printf(_DOT_, end=NN)
2071
2088
  printf(NN)
pygeodesy/lazily.py CHANGED
@@ -260,7 +260,7 @@ _ALL_LAZY = _NamedEnum_RO(_name='_ALL_LAZY',
260
260
  'bqrt', 'cbrt', 'cbrt2', 'euclid', 'euclid_',
261
261
  'facos1', 'fasin1', 'fatan', 'fatan1', 'fatan2', 'favg',
262
262
  'fdot', 'fdot3', 'fmean', 'fmean_', 'fhorner', 'fidw', 'fpolynomial',
263
- 'fpowers', 'fprod', 'frange', 'freduce', 'fremainder',
263
+ 'fpowers', 'fprod', 'frandoms', 'frange', 'freduce', 'fremainder',
264
264
  'hypot', 'hypot_', 'hypot1', 'hypot2', 'hypot2_',
265
265
  'norm2', 'norm_', 'sqrt0', 'sqrt3', 'sqrt_a', 'zcrt', 'zqrt'),
266
266
  formy=_i('Radical2Tuple',
@@ -504,7 +504,7 @@ class _ALL_MODS(object):
504
504
  _ALL_MODS = _ALL_MODS() # PYCHOK singleton
505
505
 
506
506
  __all__ = _ALL_LAZY.lazily
507
- __version__ = '24.04.17'
507
+ __version__ = '24.04.22'
508
508
 
509
509
 
510
510
  def _ALL_OTHER(*objs):