pygeodesy 25.12.12__py2.py3-none-any.whl → 26.1.16__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.
pygeodesy/fmath.py CHANGED
@@ -10,8 +10,8 @@ from __future__ import division as _; del _ # noqa: E702 ;
10
10
  from pygeodesy.basics import _copysign, copysign0, isbool, isint, isodd, \
11
11
  isscalar, len2, map1, _xiterable, typename
12
12
  from pygeodesy.constants import EPS0, EPS02, EPS1, NAN, PI, PI_2, PI_4, \
13
- _0_0, _0_125, _0_25, _0_5, _1_0, _1_5, \
14
- _copysign_0_0, isfinite, remainder
13
+ _0_0, _0_125, _0_25, _1_3rd, _0_5, _2_3rd, \
14
+ _1_0, _1_5, _copysign_0_0, isfinite, remainder
15
15
  from pygeodesy.errors import _IsnotError, LenError, _TypeError, _ValueError, \
16
16
  _xError, _xkwds, _xkwds_pop2, _xsError
17
17
  from pygeodesy.fsums import _2float, Fsum, fsum, _isFsum_2Tuple, Fmt, unstr
@@ -22,16 +22,14 @@ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
22
22
  from pygeodesy.units import Int_, _isHeight, _isRadius
23
23
 
24
24
  from math import fabs, sqrt # pow
25
- import operator as _operator # in .datums, .trf, .utm
25
+ import operator as _operator # in .datums, .elliptic, .trf, .utm
26
26
 
27
27
  __all__ = _ALL_LAZY.fmath
28
- __version__ = '25.12.02'
28
+ __version__ = '26.01.06'
29
29
 
30
30
  # sqrt(2) - 1 <https://WikiPedia.org/wiki/Square_root_of_2>
31
31
  _0_4142 = 0.41421356237309504880 # ~ 3_730_904_090_310_553 / 9_007_199_254_740_992
32
- _1_3rd = _1_0 / 3
33
- _1_6th = _1_0 / 6
34
- _2_3rd = _1_3rd * 2
32
+ _1_6th = 1 / 6
35
33
  _h_lt_b_ = 'abs(h) < abs(b)'
36
34
 
37
35
 
@@ -1049,7 +1047,7 @@ def sqrt3(x):
1049
1047
 
1050
1048
 
1051
1049
  def sqrt_a(h, b):
1052
- '''Compute C{I{a}} side of a right-angled triangle from
1050
+ '''Compute the C{I{a}} side of a right-angled triangle from
1053
1051
  C{sqrt(B{h}**2 - B{b}**2)}.
1054
1052
 
1055
1053
  @arg h: Hypotenuse or outer annulus radius (C{scalar}).
pygeodesy/formy.py CHANGED
@@ -11,7 +11,7 @@ from pygeodesy.basics import _copysign, _isin # _args_kwds_count2
11
11
  from pygeodesy.constants import EPS, EPS0, EPS1, PI, PI2, PI3, PI_2, R_M, \
12
12
  _0_0s, float0_, isnon0, remainder, _umod_PI2, \
13
13
  _0_0, _0_125, _0_25, _0_5, _1_0, _2_0, _4_0, \
14
- _90_0, _180_0, _360_0, MANT_DIG as _DIG53
14
+ _90_0, _180_0, _360_0
15
15
  from pygeodesy.datums import Datum, Ellipsoid, _ellipsoidal_datum, \
16
16
  _mean_radius, _spherical_datum, _WGS84, _EWGS84
17
17
  # from pygeodesy.ellipsoids import Ellipsoid, _EWGS84 # from .datums
@@ -23,7 +23,8 @@ from pygeodesy.fsums import fsumf_, Fmt, unstr
23
23
  # from pygeodesy.internals import typename # from .named
24
24
  from pygeodesy.interns import _delta_, _distant_, _inside_, _SPACE_, _too_
25
25
  from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
26
- from pygeodesy.named import _name__, _name2__, _NamedTuple, _xnamed, typename
26
+ from pygeodesy.named import _name__, _name2__, _NamedTuple, _xnamed, \
27
+ typename
27
28
  from pygeodesy.namedTuples import Bearing2Tuple, Distance4Tuple, LatLon2Tuple, \
28
29
  Intersection3Tuple, PhiLam2Tuple
29
30
  # from pygeodesy.streprs import Fmt, unstr # from .fsums
@@ -42,11 +43,10 @@ from contextlib import contextmanager
42
43
  from math import atan, cos, degrees, fabs, radians, sin, sqrt # pow
43
44
 
44
45
  __all__ = _ALL_LAZY.formy
45
- __version__ = '25.12.12'
46
+ __version__ = '26.01.06'
46
47
 
47
48
  _RADIANS2 = radians(_1_0)**2 # degree to radians-squared
48
49
  _ratio_ = 'ratio'
49
- _TOL53 = sqrt(pow(_0_5, _DIG53)) # elliperim
50
50
  _xline_ = 'xline'
51
51
 
52
52
 
@@ -368,107 +368,6 @@ def _dS(fun_, radius, wrap, *lls, **adjust):
368
368
  return r * radius
369
369
 
370
370
 
371
- def elliperim(a, b):
372
- '''Compute the perimeter of an ellipse with semi-axes C{a} and C{b} using U{SciPy's
373
- ellipe<https://www.JohnDCook.com/perimeter_ellipse.html>} function or the U{AGM
374
- <https://PaulBourke.net/geometry/ellipsecirc>} (Arithmetic Geometric Mean) method.
375
-
376
- @return: The perimeter (C{scalar}, same units as C{a} and C{b}).
377
- '''
378
- if a < b:
379
- a, b = b, a
380
- if 0 < b < a:
381
- try:
382
- from scipy.special import ellipe
383
- a *= float(ellipe(_1_0 - (b / a)**2)) * _4_0
384
- except (AttributeError, ImportError):
385
- # relative accuracy is about _TOL53**2
386
- if (b * _DIG53) > (a * _TOL53):
387
- c = a + b
388
- d = a - b
389
- m, s = -1, [c**2]
390
- _s = s.append
391
- while d > (b * _TOL53) and len(s) < 32: # 4..5 trips
392
- b = sqrt(a * b)
393
- a = c * _0_5
394
- c = a + b
395
- d = a - b
396
- m *= 2
397
- _s(m * d**2)
398
- a = fsumf_(*s) * PI / c
399
- else: # near flat
400
- a *= _4_0
401
- elif b < 0:
402
- raise _ValueError(unstr(elliperim, a, b))
403
- else: # circle or flat
404
- a *= PI2 if b else _4_0
405
- return a
406
-
407
-
408
- # def elliperimR2(a, b):
409
- # '''Compute the perimeter of an ellipse with semi-axes C{a} and C{b} using
410
- # Ramanujan's U{2nd approximation<https://PaulBourke.net/geometry/ellipsecirc>}.
411
- # '''
412
- # if a < 0 or b < 0:
413
- # raise ValueError(unstr(elliperimR2, a, b))
414
- # p = a + b
415
- # if p:
416
- # t = ((a - b) / p)**2 * _3_0
417
- # t = t / (_10_0 + sqrt(_4_0 - t)) + _1_0
418
- # p *= t * PI
419
- # return p
420
-
421
-
422
- # def elliperim4arc3(a, b):
423
- # '''Compute the perimeter of an ellipse with semi-axes C{a} and C{b} using
424
- # the U{4 arc approximation<https://PaulBourke.net/geometry/ellipsecirc>}.
425
- #
426
- # @return: 3-Tuple C{(p, ra, rb)} with perimeter C{p}, arc radius C{ra}
427
- # at the major and arc radius C{rb} at the minor semi-axes.
428
- # '''
429
- # _r = a < b
430
- # if _r:
431
- # a, b = b, a
432
- # if 0 < b < a:
433
- # h = hypot(a, b)
434
- # L = (h - b) * _0_5
435
- # p = atan2(b, a)
436
- # s, c = sincos2(p)
437
- # ra = L / c
438
- # rb = (h - L) / s
439
- # p = rb * p + ra * (PI_2 - p)
440
- # elif b < 0:
441
- # raise ValueError(unstr(elliperim4arc3, a, b))
442
- # elif b == a:
443
- # ra = rb = a
444
- # p = a * PI_2
445
- # else: # b == 0
446
- # ra, rb = _0_0, a
447
- # p = a
448
- # p *= _4_0
449
- # if _r:
450
- # ra, rb = rb, ra
451
- # return p, ra, rb
452
-
453
-
454
- # def elliperimGKS(a, b):
455
- # '''Compute the perimeter of an ellipse with semi-axes C{a} and C{b} using the U{Gauss-Kummer
456
- # Series<https://www.JohnDCook.com/blog/2023/05/28/approximate-ellipse-perimeter/>}.
457
- # '''
458
- # if a < b:
459
- # a, b = b, a
460
- # if b < 0:
461
- # raise ValueError(unstr(elliperimGKS, a, b))
462
- # if b:
463
- # p = a + b
464
- # h = (a - b) / p
465
- # h *= h
466
- # p *= (1 + h * (1 / 4 + h * (1 / 64 + h * (1 / 256 + h * (25 / 16384 + h * (49 / 65536)))))) * PI
467
- # else:
468
- # p = _4_0 * a
469
- # return p
470
-
471
-
472
371
  def _ellipsoidal(earth, where):
473
372
  '''(INTERNAL) Helper for distances.
474
373
  '''
@@ -1028,7 +927,8 @@ def hartzell(pov, los=False, earth=_WGS84, **name_LatLon_and_kwds):
1028
927
  n, kwds = _name2__(name_LatLon_and_kwds, name__=hartzell)
1029
928
  try:
1030
929
  D = _spherical_datum(earth, name__=hartzell)
1031
- r, h, i = _MODS.triaxials.triaxial5._hartzell3(pov, los, D.ellipsoid._triaxial)
930
+ m = _MODS._triaxials_triaxial5
931
+ r, h, i = m._hartzell3(pov, los, D.ellipsoid._triaxial)
1032
932
 
1033
933
  C = _MODS.cartesianBase.CartesianBase
1034
934
  if kwds:
pygeodesy/fsums.py CHANGED
@@ -5,16 +5,16 @@ u'''Class L{Fsum} for precision floating point summation similar to
5
5
  Python's C{math.fsum}, but enhanced with I{precision running} summation
6
6
  plus optionally, accurate I{TwoProduct} multiplication.
7
7
 
8
- Accurate multiplication is based on the C{math.fma} function from
9
- Python 3.13 and newer or an equivalent C{fma} implementation for
10
- Python 3.12 and older. To enable accurate multiplication, set env
11
- variable C{PYGEODESY_FSUM_F2PRODUCT} to C{"std"} or any non-empty
12
- string or invoke function C{pygeodesy.f2product(True)} or set. With
13
- C{"std"} the C{fma} implemention follows the C{math.fma} function,
14
- otherwise the C{PyGeodesy 24.09.09} release.
8
+ Accurate multiplication is based on the C{math.fma} function from Python
9
+ 3.13 and newer or an equivalent C{fma} implementation for Python 3.12 and
10
+ older. Set env variable C{PYGEODESY_FSUM_F2PRODUCT} to C{"std"} or any
11
+ non-empty string or invoke function C{pygeodesy.f2product(True)} to enable
12
+ accurate multiplication. With C{"std"} the C{fma} implemention follows
13
+ the C{math.fma} function, otherwise the implementation of the C{PyGeodesy
14
+ 24.09.09} release.
15
15
 
16
16
  Generally, an L{Fsum} instance is considered a C{float} plus a small or
17
- zero C{residue} aka C{residual} value, see property L{Fsum.residual}.
17
+ zero C{residue} aka C{residual}, see property L{Fsum.residual}.
18
18
 
19
19
  Set env variable C{PYGEODESY_FSUM_RESIDUAL} to a C{float} string greater
20
20
  than C{"0.0"} as the threshold to throw a L{ResidualError} for a division,
@@ -28,7 +28,7 @@ L{Fsum.fint2} and L{Fsum.is_integer}. Also, L{Fsum} methods L{Fsum.pow},
28
28
  L{Fsum.__ipow__}, L{Fsum.__pow__} and L{Fsum.__rpow__} return a (very long)
29
29
  C{int} if invoked with optional argument C{mod} set to C{None}. The
30
30
  C{residual} of an C{integer} L{Fsum} is between C{-1.0} and C{+1.0} and
31
- will be C{INT0} if that is considered to be I{exact}.
31
+ will be C{INT0} if that L{Fsum} is an I{exact float} or I{exact integer}.
32
32
 
33
33
  Set env variable C{PYGEODESY_FSUM_NONFINITES} to C{"std"} or use function
34
34
  C{pygeodesy.nonfiniterrors(False)} to allow I{non-finite} C{float}s like
@@ -62,7 +62,7 @@ from math import fabs, isinf, isnan, \
62
62
  ceil as _ceil, floor as _floor # PYCHOK used! .ltp
63
63
 
64
64
  __all__ = _ALL_LAZY.fsums
65
- __version__ = '25.06.03'
65
+ __version__ = '26.01.16'
66
66
 
67
67
  from pygeodesy.interns import (
68
68
  _PLUS_ as _add_op_, # in .auxilats.auxAngle
@@ -321,8 +321,8 @@ def nonfiniterrors(raiser=None):
321
321
  '''
322
322
  d = Fsum._isfine
323
323
  if raiser is not None:
324
- Fsum._isfine = {} if bool(raiser) else Fsum._nonfinites_isfine_kwds[True]
325
- return (False if d is Fsum._nonfinites_isfine_kwds[True] else
324
+ Fsum._isfine = {} if bool(raiser) else _nonfinites_isfine_kwds[True]
325
+ return (False if d is _nonfinites_isfine_kwds[True] else
326
326
  _xkwds_get1(d, _isfine=_isfinite) is _isfinite) if d else True
327
327
 
328
328
 
@@ -370,7 +370,7 @@ def _Psum(ps, **name_f2product_nonfinites_RESIDUAL):
370
370
  return F
371
371
 
372
372
 
373
- def _Psum_(*ps, **name_f2product_nonfinites_RESIDUAL): # in .fmath
373
+ def _Psum_(*ps, **name_f2product_nonfinites_RESIDUAL):
374
374
  '''(INTERNAL) Return an C{Fsum} from I{known scalar} C{ps}.
375
375
  '''
376
376
  return _Psum(ps, **name_f2product_nonfinites_RESIDUAL)
@@ -486,7 +486,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
486
486
 
487
487
  @note: Handling of I{non-finites} as C{inf}, C{INF}, C{NINF}, C{nan} and C{NAN} is
488
488
  determined by function L{nonfiniterrors<fsums.nonfiniterrors>} for the default
489
- and by method L{nonfinites<Fsum.nonfinites>} for individual C{Fsum} instances,
489
+ or by method L{nonfinites<Fsum.nonfinites>} for individual C{Fsum} instances,
490
490
  overruling the default. For backward compatibility, I{non-finites} raise
491
491
  exceptions by default.
492
492
 
@@ -829,7 +829,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
829
829
  return self._cmp_0(other, _lt_op_ + _fset_op_) <= 0
830
830
 
831
831
  def __len__(self):
832
- '''Return the number of values accumulated (C{int}).
832
+ '''Return the number of (non-zero) values accumulated (C{int}).
833
833
  '''
834
834
  return self._n
835
835
 
@@ -1447,7 +1447,13 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
1447
1447
  @arg other2: Addend (C{scalar}, an L{Fsum} or L{Fsum2Tuple}).
1448
1448
  @kwarg nonfinites: Use C{B{nonfinites}=True} or C{False}, to
1449
1449
  override L{nonfinites<Fsum.nonfinites>} and
1450
- L{nonfiniterrors} default (C{bool}).
1450
+ the L{nonfiniterrors} default (C{bool}).
1451
+ '''
1452
+ f = self._fma(other1, other2, **nonfinites)
1453
+ return self._fset(f)
1454
+
1455
+ def _fma(self, other1, other2, **nonfinites): # in .elliptic
1456
+ '''(INTERNAL) Return C{self * B{other1} + B{other2}}.
1451
1457
  '''
1452
1458
  op = typename(self.fma)
1453
1459
  _fs = self._ps_other
@@ -1459,7 +1465,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
1459
1465
  elif _residue(other1) or _residue(other2):
1460
1466
  fs = _2split3s(_fs(op, other1))
1461
1467
  fs = _2products(s, fs, *_fs(op, other2))
1462
- f = _Psum(self._ps_acc([], fs, up=False), name=op)
1468
+ f = Fsum(fs, name=op, **nonfinites)
1463
1469
  else:
1464
1470
  f = _fma(s, other1, other2)
1465
1471
  f = _2finite(f, **self._isfine)
@@ -1469,7 +1475,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
1469
1475
  f = self._mul_reduce(s, other1) # INF, NAN, NINF
1470
1476
  f += sum(_fs(op, other2))
1471
1477
  f = self._nonfiniteX(X, op, f, **nonfinites)
1472
- return self._fset(f)
1478
+ return f
1473
1479
 
1474
1480
  def fma_(self, *xys, **nonfinites):
1475
1481
  '''Fused-multiply-accumulate C{for i in range(0, len(xys), B{2}):
@@ -1479,7 +1485,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
1479
1485
  an L{Fsum} or L{Fsum2Tuple}), all positional.
1480
1486
  @kwarg nonfinites: Use C{B{nonfinites}=True} or C{False}, to
1481
1487
  override L{nonfinites<Fsum.nonfinites>} and
1482
- L{nonfiniterrors} default (C{bool}).
1488
+ the L{nonfiniterrors} default (C{bool}).
1483
1489
 
1484
1490
  @note: Equivalent to L{fdot_<pygeodesy.fmath.fdot_>}C{(*xys,
1485
1491
  start=self)}.
@@ -1615,8 +1621,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
1615
1621
  '''
1616
1622
  if two: # delattrof(self, _f2product=None)
1617
1623
  t = _xkwds_pop(self.__dict__, _f2product=None)
1618
- if two[0] is not None:
1619
- self._f2product = bool(two[0])
1624
+ self._optionals(f2product=two[0])
1620
1625
  else: # getattrof(self, _f2product=None)
1621
1626
  t = _xkwds_get(self.__dict__, _f2product=None)
1622
1627
  return t
@@ -2070,25 +2075,21 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
2070
2075
  @see: Function L{nonfiniterrors<fsums.nonfiniterrors>}.
2071
2076
 
2072
2077
  @note: Use property L{nonfinitesOK<Fsum.nonfinitesOK>} to determine
2073
- whether I{non-finites} are C{OK} for this L{Fsum} and by the
2078
+ whether I{non-finites} are C{OK} for this L{Fsum} or by the
2074
2079
  L{nonfiniterrors} default.
2075
2080
  '''
2076
- _ks = Fsum._nonfinites_isfine_kwds
2077
2081
  if OK: # delattrof(self, _isfine=None)
2078
2082
  k = _xkwds_pop(self.__dict__, _isfine=None)
2079
- if OK[0] is not None:
2080
- self._isfine = _ks[bool(OK[0])]
2083
+ self._optionals(nonfinites=OK[0])
2081
2084
  self._update()
2082
2085
  else: # getattrof(self, _isfine=None)
2083
2086
  k = _xkwds_get(self.__dict__, _isfine=None)
2087
+ _ks = _nonfinites_isfine_kwds
2084
2088
  # dict(map(reversed, _ks.items())).get(k, None)
2085
2089
  # raises a TypeError: unhashable type: 'dict'
2086
2090
  return True if k is _ks[True] else (
2087
2091
  False if k is _ks[False] else None)
2088
2092
 
2089
- _nonfinites_isfine_kwds = {True: dict(_isfine=_isOK),
2090
- False: dict(_isfine=_isfinite)}
2091
-
2092
2093
  @property_RO
2093
2094
  def nonfinitesOK(self):
2094
2095
  '''Are I{non-finites} C{OK} for this L{Fsum} or by default? (C{bool}).
@@ -2111,9 +2112,9 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
2111
2112
  '''(INTERNAL) Re/set options from keyword arguments.
2112
2113
  '''
2113
2114
  if f2product is not None:
2114
- self.f2product(f2product)
2115
+ self._f2product = bool(f2product)
2115
2116
  if nonfinites is not None:
2116
- self.nonfinites(nonfinites)
2117
+ self._isfine = _nonfinites_isfine_kwds[bool(nonfinites)]
2117
2118
  if name_RESIDUAL: # MUST be last
2118
2119
  n, kwds = _name2__(**name_RESIDUAL)
2119
2120
  if kwds:
@@ -2535,6 +2536,8 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
2535
2536
 
2536
2537
  _ROs = _allPropertiesOf_n(3, Fsum, Property_RO) # PYCHOK see Fsum._update
2537
2538
 
2539
+ _nonfinites_isfine_kwds = {True: dict(_isfine=_isOK),
2540
+ False: dict(_isfine=_isfinite)}
2538
2541
  if _NONFINITES == _std_: # PYCHOK no cover
2539
2542
  _ = nonfiniterrors(False)
2540
2543
 
@@ -2685,6 +2688,21 @@ class Fsum2Tuple(_NamedTuple): # in .fstats
2685
2688
  _Fsum_2Tuple_types = Fsum, Fsum2Tuple # PYCHOK lines
2686
2689
 
2687
2690
 
2691
+ class _Ksum(Fsum):
2692
+ '''(INTERNAL) For C{.karney._sum3}, specifically and only.
2693
+ '''
2694
+ _isfine = _nonfinites_isfine_kwds[True]
2695
+
2696
+ def __init__(self, s, t, *xs):
2697
+ ps = [t, s] if t else [s]
2698
+ self._ps = self._ps_acc(ps, xs, up=False)
2699
+
2700
+ @property_RO
2701
+ def _s_t_n3(self):
2702
+ s, t = self._fprs2
2703
+ return s, t, self._n
2704
+
2705
+
2688
2706
  class ResidualError(_ValueError):
2689
2707
  '''Error raised for a division, power or root operation of
2690
2708
  an L{Fsum} instance with a C{residual} I{ratio} exceeding
pygeodesy/geod3solve.py CHANGED
@@ -5,13 +5,14 @@ u'''Wrapper to invoke I{Karney}'s U{Geod3Solve
5
5
  <https://GeographicLib.SourceForge.io/C++/doc/Geod3Solve.1.html>} utility
6
6
  as a C{triaxial} geodesic, but intended I{mainly for testing purposes}.
7
7
 
8
- Set env variable C{PYGEODESY_GEOD3SOLVE} to the (fully qualified) path
9
- of the C{Geod3Solve} executable.
8
+ Set env variable C{PYGEODESY_GEOD3SOLVE} to the (fully qualified) path of
9
+ the C{Geod3Solve} executable or use property L{Geodesic3Solve.Geod3Solve
10
+ <geod3solve._Geodesic3SolveBase.Geod3Solve>}.
10
11
  '''
11
12
 
12
13
  from pygeodesy.angles import Ang, Deg, isAng, hypot
13
14
  from pygeodesy.basics import _xinstanceof # typename
14
- from pygeodesy.constants import _0_0, _0_5, _360_0
15
+ from pygeodesy.constants import _0_0, _0_5, _360_0, _over
15
16
  from pygeodesy.errors import GeodesicError, _xkwds_get
16
17
  # from pygeodesy.fmath import hypot # from .angles
17
18
  # from pygeodesy.geodesicx import GeodesicAreaExact # _MODS
@@ -25,7 +26,7 @@ from pygeodesy.units import Degrees, Meter
25
26
  # from pygeodesy.utily import sincos2d # from .karney
26
27
 
27
28
  __all__ = _ALL_LAZY.geod3solve
28
- __version__ = '25.12.12'
29
+ __version__ = '26.01.04'
29
30
 
30
31
  _Triaxial3_WGS84 = Triaxial3s.WGS84_3r # a=6378172, b=6378102, c=6356752
31
32
 
@@ -39,24 +40,39 @@ class Geodesic3Error(GeodesicError):
39
40
  class Geod3Solve8Tuple(_GTuple):
40
41
  '''8-Tuple C{(bet1, omg1, alp1, bet2, omg2, alp2, s12, a12)} with C{ellipsoidal}
41
42
  latitudes C{bet1} and C{bet2}, C{ellipsoidal} longitudes C{omg1} and C{omg2},
42
- forward azimuths C{alp1} and C{alp2} in bearings from North, distanc C{s12} in
43
- C{meter}, conventionally and I{approximate} arc length {a12} in degrees, see
43
+ forward azimuths C{alp1} and C{alp2} in bearings from North, distance C{s12} in
44
+ C{meter}, conventionally and I{approximate} arc length C{a12} in degrees, see
44
45
  U{Geod3Solve<https://GeographicLib.SourceForge.io/C++/doc/Geod3Solve.1.html>}.
45
46
  '''
46
47
  # from Geod3Solve --help option -f ... bet1 omg1 alp1 bet2 omg2 alp2 s12
47
48
  _Names_ = ('bet1', 'omg1', 'alp1', 'bet2', 'omg2', 'alp2', _s12_, _a12_)
48
49
  _Units_ = ( Deg, Deg, Deg, Deg, Deg, Deg, Meter, Deg)
49
50
 
51
+ # @Property_RO
52
+ # def A12(self):
53
+ # '''Approximate arc C{A12} as C{Deg}.
54
+ # '''
55
+ # t = self
56
+ # d = t.s12 or _0_0
57
+ # if d:
58
+ # a = hypot(Deg(t.bet2 - t.bet1).degrees,
59
+ # Deg(t.omg2 - t.omg1).degrees)
60
+ # d = (-a) if d < 0 else a
61
+ # return Deg(d)
62
+
50
63
 
51
64
  class _Geodesic3SolveBase(_Solve3Base):
52
65
  '''(INTERNAL) Base class for L{Geodesic3Solve} and L{GeodesicLine3Solve}.
53
66
  '''
67
+ _a12x = Geod3Solve8Tuple._Names_.index(_a12_) # last
54
68
  _Error = Geodesic3Error
55
69
  _Names_Direct = _Names_Distance = \
56
- _Names_Inverse = Geod3Solve8Tuple._Names_[:7] # 7 only, always
70
+ _Names_Inverse = Geod3Solve8Tuple._Names_[:_a12x] # 7 only, always
57
71
  _triaxial3 = _Triaxial3_WGS84
58
72
  _Xable_name = _Xables.Geod3Solve.__name__ # typename
59
73
  _Xable_path = _Xables.Geod3Solve()
74
+ # assert _a12x == len(Geod3Solve8Tuple._Names_) - 1
75
+ del _a12x
60
76
 
61
77
  @Property_RO
62
78
  def a(self):
@@ -161,11 +177,11 @@ class Geodesic3Solve(_Geodesic3SolveBase):
161
177
  '''
162
178
  a = r.s12 or _0_0
163
179
  if a:
180
+ t = self.triaxial3
164
181
  z = _toAzi(r.alp1) + _toAzi(r.alp2)
165
182
  s, c = sincos2d(z * _0_5)
166
- t = self.triaxial3
167
- a *= hypot(s / t._ab_elliperim, # azimuth!
168
- c / t._bc_elliperim) * _360_0
183
+ a *= hypot(_over(s, t.perimeter4ab), # azimuth!
184
+ _over(c, t.perimeter4bc)) * _360_0
169
185
  r[_a12_] = a
170
186
  return r
171
187
 
pygeodesy/geodesici.py CHANGED
@@ -7,10 +7,11 @@ Class L{Intersector} is a pure Python version of I{Karney}'s C++ class U{Interse
7
7
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Intersect.html>}.
8
8
 
9
9
  Class L{Intersectool} is a wrapper to invoke I{Karney}'s U{IntersectTool
10
- <https://GeographicLib.SourceForge.io/C++/doc/IntersectTool.1.html>} utility, but intended I{for testing purposes only}.
10
+ <https://GeographicLib.SourceForge.io/C++/doc/IntersectTool.1.html>} utility, mainly intended I{for testing purposes}.
11
11
 
12
- Set env variable C{PYGEODESY_INTERSECTTOOL} to the (fully qualified) path of the C{IntersectTool} executable. For usage
13
- and some examples run C{"env PYGEODESY_INTERSECTTOOL=<IntersectTool-path> python3 -m pygeodesy.geodesici --help"}.
12
+ Set env variable C{PYGEODESY_INTERSECTTOOL} to the (fully qualified) path of the C{IntersectTool} executable or use
13
+ property L{Intersectool.IntersectTool}. For usage and some examples run C{"env PYGEODESY_INTERSECTTOOL=<IntersectTool-path>
14
+ python3 -m pygeodesy.geodesici --help"}.
14
15
 
15
16
  Both L{Intersectool} and L{Intersector} provide methods C{All}, C{Closest}, C{Next} and C{Segment} and produce
16
17
  L{XDict} instances with 4 or more items. Adjacent methods C{All5}, C{Closest5}, C{Next5} and C{Segment} return
@@ -57,7 +58,7 @@ from pygeodesy.utily import atan2, sincos2, fabs, radians
57
58
  # from math import ceil as _ceil, fabs, radians # .fsums, .utily
58
59
 
59
60
  __all__ = _ALL_LAZY.geodesici
60
- __version__ = '25.06.02'
61
+ __version__ = '25.12.31'
61
62
 
62
63
  _0t = 0, # int
63
64
  _1_1t = -1, +1
@@ -23,7 +23,7 @@ from pygeodesy.karney import Caps, GeodesicError
23
23
  from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY
24
24
 
25
25
  __all__ = _ALL_LAZY.geodesicx + _ALL_DOCS(Caps, GeodesicError)
26
- __version__ = '25.09.02'
26
+ __version__ = '25.12.23'
27
27
 
28
28
  # **) MIT License
29
29
  #
@@ -19,19 +19,20 @@ from __future__ import division as _; del _ # noqa: E702 ;
19
19
 
20
20
  from pygeodesy.basics import _copysign, isodd, unsigned0
21
21
  from pygeodesy.constants import NAN, _0_0, _0_5, _720_0
22
+ from pygeodesy.fmath import _fma
22
23
  from pygeodesy.internals import printf, typename
23
24
  # from pygeodesy.interns import _COMMASPACE_ # from .lazily
24
25
  from pygeodesy.karney import Area3Tuple, _diff182, GeodesicError, \
25
- _norm180, _remainder, _sum3
26
+ _norm180, _remainder, _sum2
26
27
  from pygeodesy.lazily import _ALL_DOCS, _COMMASPACE_
27
28
  from pygeodesy.named import ADict, callername, _NamedBase, pairs
28
29
  from pygeodesy.props import Property, Property_RO, property_RO
29
30
  # from pygeodesy.streprs import pairs # from .named
30
31
 
31
- from math import fmod as _fmod
32
+ from math import fabs, fmod as _fmod
32
33
 
33
34
  __all__ = ()
34
- __version__ = '25.09.16'
35
+ __version__ = '25.12.23'
35
36
 
36
37
 
37
38
  class GeodesicAreaExact(_NamedBase):
@@ -427,30 +428,39 @@ class _Accumulator(_NamedBase):
427
428
  @kwarg y: Initial value (C{scalar}).
428
429
  @kwarg name: Optional C{B{name}=NN} (C{str}).
429
430
  '''
430
- if isinstance(y, _Accumulator):
431
- self._s, self._t, self._n = y._s, y._t, 1
432
- elif y:
433
- self._s, self._n = float(y), 1
431
+ self._s_t(*_s_t2(y))
432
+ self._n = 1
434
433
  if name:
435
434
  self.name = name
436
435
 
437
- def Add(self, y):
438
- '''Add a value.
436
+ def Add(self, *ys):
437
+ '''Add one or more scalars or L{_Accumulator}s.
439
438
 
440
439
  @return: Current C{sum}.
441
- '''
442
- self._n += 1
443
- self._s, self._t, _ = _sum3(self._s, self._t, y)
444
- return self._s # current .Sum()
440
+
441
+ @see: C++ U{Accumulator.Add<https://GeographicLib.sourceforge.io/C++/doc/Accumulator_8hpp_source.html>}
442
+ for more details about Karney's and Shewchuk's addition.
443
+ '''
444
+ # _Accumulator().Add(1, 1e20, 2, 100, 5000, -1e20) ... 5103.0
445
+ s, t = self._s, self._t
446
+ for y in ys:
447
+ for y in _s_t2(y):
448
+ if y:
449
+ t, u = _sum2(t, y)
450
+ s, t = _sum2(s, t)
451
+ if s: # accumlate u in t
452
+ t += u
453
+ else: # s == 0 implies t == 0
454
+ s = u
455
+ self._n += 1
456
+ return self._s_t(s, t)
445
457
 
446
458
  def Negate(self):
447
459
  '''Negate sum.
448
460
 
449
461
  @return: Current C{sum}.
450
462
  '''
451
- self._s = s = -self._s
452
- self._t = -self._t
453
- return s # current .Sum()
463
+ return self._s_t(-self._s, -self._t)
454
464
 
455
465
  @property_RO
456
466
  def num(self):
@@ -459,22 +469,27 @@ class _Accumulator(_NamedBase):
459
469
  return self._n
460
470
 
461
471
  def Remainder(self, y):
462
- '''Remainder on division by B{C{y}}.
472
+ '''Remainder of division by B{C{y}}.
463
473
 
464
474
  @return: Remainder of C{sum} / B{C{y}}.
465
475
  '''
466
- self._s = _remainder(self._s, y)
467
- # self._t = _remainder(self._t, y)
468
- self._n = -1
469
- return self.Add(_0_0)
476
+ return self._s_t(_remainder(self._s, y),
477
+ _remainder(self._t, y))
470
478
 
471
479
  def Reset(self, y=0):
472
- '''Set value from argument.
480
+ '''Reset from scalar or L{_Accumulator}.
473
481
  '''
474
- self._n, self._s, self._t = 0, float(y), _0_0
482
+ self._s_t(*_s_t2(y))
483
+ self._n = 0
475
484
 
476
485
  Set = Reset
477
486
 
487
+ def _s_t(self, s, t=0):
488
+ if t and fabs(s) < fabs(t):
489
+ s, t = t, s
490
+ self._s, self._t = s, t
491
+ return s
492
+
478
493
  def Sum(self, y=0):
479
494
  '''Return C{sum + B{y}}.
480
495
 
@@ -488,6 +503,17 @@ class _Accumulator(_NamedBase):
488
503
  s = self
489
504
  return s._s
490
505
 
506
+ def Times(self, y):
507
+ '''Multiply by a scalar.
508
+
509
+ @return: Current C{sum}.
510
+ '''
511
+ s = d = self._s
512
+ s *= y
513
+ d = _fma(y, d, -s)
514
+ t = _fma(y, self._t, d)
515
+ return self._s_t(s, t) # current .Sum()
516
+
491
517
  def toStr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature
492
518
  '''Return this C{_Accumulator} as string.
493
519
 
@@ -502,6 +528,10 @@ class _Accumulator(_NamedBase):
502
528
  return sep.join(pairs(d, prec=prec))
503
529
 
504
530
 
531
+ def _s_t2(y):
532
+ return (y._s, y._t) if isinstance(y, _Accumulator) else (float(y),) # PYCHOK OK
533
+
534
+
505
535
  __all__ += _ALL_DOCS(GeodesicAreaExact, PolygonArea)
506
536
 
507
537
  # **) MIT License
pygeodesy/geodsolve.py CHANGED
@@ -5,8 +5,9 @@ u'''Wrapper to invoke I{Karney}'s U{GeodSolve
5
5
  <https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>} utility
6
6
  as an (exact) geodesic, but intended I{mainly for testing purposes}.
7
7
 
8
- Set env variable C{PYGEODESY_GEODSOLVE} to the (fully qualified) path
9
- of the C{GeodSolve} executable.
8
+ Set env variable C{PYGEODESY_GEODSOLVE} to the (fully qualified) path of
9
+ the C{GeodSolve} executable or use property L{GeodesicSolve.GeodSolve
10
+ <geodsolve._GeodesicSolveBase.GeodSolve>}.
10
11
  '''
11
12
 
12
13
  from pygeodesy.basics import _xinstanceof # typename
@@ -28,7 +29,7 @@ from pygeodesy.solveBase import _SolveGDictBase, _SolveGDictLineBase
28
29
  from pygeodesy.utily import _unrollon, _Wrap, wrap360
29
30
 
30
31
  __all__ = _ALL_LAZY.geodsolve
31
- __version__ = '25.12.06'
32
+ __version__ = '25.12.31'
32
33
 
33
34
 
34
35
  class GeodSolve12Tuple(_GTuple):