pygeodesy 25.5.25__py2.py3-none-any.whl → 25.7.25__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
@@ -7,8 +7,8 @@ C{fused-multiply-add}, polynomials, roots, etc.
7
7
  # make sure int/int division yields float quotient, see .basics
8
8
  from __future__ import division as _; del _ # noqa: E702 ;
9
9
 
10
- from pygeodesy.basics import _copysign, copysign0, isbool, isint, isscalar, \
11
- len2, map1, _xiterable, typename
10
+ from pygeodesy.basics import _copysign, copysign0, isbool, isint, isodd, \
11
+ isscalar, len2, map1, _xiterable, typename
12
12
  from pygeodesy.constants import EPS0, EPS02, EPS1, NAN, PI, PI_2, PI_4, \
13
13
  _0_0, _0_125, _1_6th, _0_25, _1_3rd, _0_5, _1_0, \
14
14
  _1_5, _copysign_0_0, isfinite, remainder
@@ -25,7 +25,7 @@ from math import fabs, sqrt # pow
25
25
  import operator as _operator # in .datums, .trf, .utm
26
26
 
27
27
  __all__ = _ALL_LAZY.fmath
28
- __version__ = '25.05.12'
28
+ __version__ = '25.06.03'
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
@@ -66,7 +66,7 @@ class Fdot(Fsum):
66
66
  self._facc_dot(n, a, b, **kwds)
67
67
 
68
68
 
69
- class Fdot_(Fdot):
69
+ class Fdot_(Fdot): # in .elliptic
70
70
  '''Precision dot product.
71
71
  '''
72
72
  def __init__(self, *xys, **start_name_f2product_nonfinites_RESIDUAL):
@@ -78,6 +78,8 @@ class Fdot_(Fdot):
78
78
 
79
79
  @see: Class L{Fdot<Fdot.__init__>} for further details.
80
80
  '''
81
+ if isodd(len(xys)):
82
+ raise LenError(Fdot_, xys=len(xys))
81
83
  Fdot.__init__(self, xys[0::2], *xys[1::2], **start_name_f2product_nonfinites_RESIDUAL)
82
84
 
83
85
 
pygeodesy/frechet.py CHANGED
@@ -101,7 +101,7 @@ from collections import defaultdict as _defaultdict
101
101
  # from math import radians # from .points
102
102
 
103
103
  __all__ = _ALL_LAZY.frechet
104
- __version__ = '25.05.21'
104
+ __version__ = '25.05.26'
105
105
 
106
106
  _formy = _MODS.into(formy=__name__)
107
107
 
@@ -153,7 +153,7 @@ class Frechet(_Named):
153
153
  @raise FrechetError: Insufficient number of B{C{point1s}} or an invalid
154
154
  B{C{point1}}, B{C{fraction}} or B{C{units}}.
155
155
  '''
156
- name, kwds = _name2__(**name__kwds) # name__=self.__class__
156
+ name, kwds = _name2__(**name__kwds) # name__=type(self)
157
157
  if name:
158
158
  self.name = name
159
159
 
pygeodesy/fsums.py CHANGED
@@ -39,15 +39,13 @@ results may differ from Python's C{math.fsum} results.
39
39
  # make sure int/int division yields float quotient, see .basics
40
40
  from __future__ import division as _; del _ # noqa: E702 ;
41
41
 
42
- from pygeodesy.basics import _gcd, isbool, iscomplex, isint, isscalar, \
42
+ from pygeodesy.basics import _gcd, isbool, iscomplex, isint, isodd, isscalar, \
43
43
  _signOf, itemsorted, signOf, _xiterable
44
- from pygeodesy.constants import INF, INT0, MANT_DIG, NEG0, NINF, _0_0, \
45
- _1_0, _N_1_0, _isfinite, _pos_self, \
46
- Float, Int
47
- from pygeodesy.errors import _AssertionError, _OverflowError, _TypeError, \
48
- _ValueError, _xError, _xError2, _xkwds, \
49
- _xkwds_get, _xkwds_get1, _xkwds_not, \
50
- _xkwds_pop, _xsError
44
+ from pygeodesy.constants import INF, INT0, MANT_DIG, NEG0, NINF, _0_0, _1_0, \
45
+ _N_1_0, _isfinite, _pos_self, Float, Int
46
+ from pygeodesy.errors import _AssertionError, _OverflowError, LenError, _TypeError, \
47
+ _ValueError, _xError, _xError2, _xkwds, _xkwds_get, \
48
+ _xkwds_get1, _xkwds_not, _xkwds_pop, _xsError
51
49
  from pygeodesy.internals import _enquote, _envPYGEODESY, _passarg, typename # _sizeof
52
50
  from pygeodesy.interns import NN, _arg_, _COMMASPACE_, _DMAIN_, _DOT_, _from_, \
53
51
  _not_finite_, _SPACE_, _std_, _UNDER_
@@ -64,7 +62,7 @@ from math import fabs, isinf, isnan, \
64
62
  ceil as _ceil, floor as _floor # PYCHOK used! .ltp
65
63
 
66
64
  __all__ = _ALL_LAZY.fsums
67
- __version__ = '25.05.12'
65
+ __version__ = '25.06.03'
68
66
 
69
67
  from pygeodesy.interns import (
70
68
  _PLUS_ as _add_op_, # in .auxilats.auxAngle
@@ -121,7 +119,9 @@ try: # MCCABE 26
121
119
  f = x * y
122
120
  yield f
123
121
  if _isfinite(f):
124
- yield _fma(x, y, -f)
122
+ f = _fma(x, y, -f)
123
+ if f:
124
+ yield f
125
125
  for z in zs:
126
126
  yield z
127
127
 
@@ -162,7 +162,7 @@ except ImportError: # PYCHOK DSPACE! Python 3.12-
162
162
  def _fmaX(r, *a_b_c): # PYCHOK no cover
163
163
  # handle non-finite fma result as Python 3.13+ C-function U{math_fma_impl
164
164
  # <https://GitHub.com/python/cpython/blob/main/Modules/mathmodule.c#L2305>}:
165
- # raise a ValueError for a NAN result from non-NAN C{a_b_c}s otherwise an
165
+ # raise a ValueError for a NAN result from non-NAN C{a_b_c}s, otherwise an
166
166
  # OverflowError for a non-finite, non-NAN result from all finite C{a_b_c}s.
167
167
  if isnan(r):
168
168
  def _x(x):
@@ -560,7 +560,6 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
560
560
  self._fset(other, op=_fset_op_, **up)
561
561
  return self
562
562
 
563
-
564
563
  def __ceil__(self): # PYCHOK not special in Python 2-
565
564
  '''Return this instance' C{math.ceil} as C{int} or C{float}.
566
565
 
@@ -1137,7 +1136,7 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
1137
1136
  return E(u, txt=t, cause=X)
1138
1137
 
1139
1138
  def _facc(self, xs, up=True, **_X_x_origin):
1140
- '''(INTERNAL) Accumulate more C{scalar}s or L{Fsum}s.
1139
+ '''(INTERNAL) Accumulate more C{scalar}s, L{Fsum}s pr L{Fsum2Tuple}s.
1141
1140
  '''
1142
1141
  if xs:
1143
1142
  kwds = self._isfine
@@ -1472,6 +1471,29 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
1472
1471
  f = self._nonfiniteX(X, op, f, **nonfinites)
1473
1472
  return self._fset(f)
1474
1473
 
1474
+ def fma_(self, *xys, **nonfinites):
1475
+ '''Fused-multiply-accumulate C{for i in range(0, len(xys), B{2}):
1476
+ self = }L{fma<pygeodesy.fmath.fma>}C{(xys[i], xys[i+1], self)}.
1477
+
1478
+ @arg xys: Pairwise multiplicand, multiplier (each C{scalar},
1479
+ an L{Fsum} or L{Fsum2Tuple}), all positional.
1480
+ @kwarg nonfinites: Use C{B{nonfinites}=True} or C{False}, to
1481
+ override L{nonfinites<Fsum.nonfinites>} and
1482
+ L{nonfiniterrors} default (C{bool}).
1483
+
1484
+ @note: Equivalent to L{fdot_<pygeodesy.fmath.fdot_>}C{(*xys,
1485
+ start=self)}.
1486
+ '''
1487
+ if xys:
1488
+ n = len(xys)
1489
+ if n < 2 or isodd(n):
1490
+ raise LenError(self.fma_, xys=n)
1491
+ f, _fmath_fma = self, _MODS.fmath.fma
1492
+ for x, y in zip(xys[0::2], xys[1::2]):
1493
+ f = _fmath_fma(x, y, f, **nonfinites)
1494
+ self._fset(f)
1495
+ return self
1496
+
1475
1497
  fmul = __imul__
1476
1498
 
1477
1499
  def _fmul(self, other, op):
@@ -1581,8 +1603,8 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase, .fstats
1581
1603
  L{Fsum}, overriding the L{f2product} default.
1582
1604
 
1583
1605
  @arg two: If omitted, leave the override unchanged, if C{True},
1584
- turn I{TwoProduct} on, if C{False} off, if C{None}e
1585
- remove th override (C{bool} or C{None}).
1606
+ turn I{TwoProduct} on, if C{False} off, or if C{None}
1607
+ remove the override (C{bool} or C{None}).
1586
1608
 
1587
1609
  @return: The previous setting (C{bool} or C{None} if not set).
1588
1610
 
@@ -2682,11 +2704,11 @@ try:
2682
2704
  del _fsum # nope, remove _fsum ...
2683
2705
  raise ImportError() # ... use _fsum below
2684
2706
 
2685
- _sum = _fsum # in .elliptic
2707
+ _sum = _fsum
2686
2708
  except ImportError:
2687
- _sum = sum # in .elliptic
2709
+ _sum = sum
2688
2710
 
2689
- def _fsum(xs):
2711
+ def _fsum(xs): # in .elliptic
2690
2712
  '''(INTERNAL) Precision summation, Python 2.5-.
2691
2713
  '''
2692
2714
  F = Fsum(name=_fsum.name, f2product=False, nonfinites=True)
pygeodesy/geodesici.py CHANGED
@@ -29,8 +29,8 @@ from pygeodesy.basics import _copy, _enumereverse, map1, \
29
29
  _xinstanceof, _xor, typename
30
30
  from pygeodesy.constants import EPS, INF, INT0, PI, PI2, PI_4, \
31
31
  _0_0, _0_5, _1_0, _1_5, _2_0, _3_0, \
32
- _45_0, _64_0, _90_0, isfinite, \
33
- _EPSjam # PYCHOK used!
32
+ _45_0, _64_0, _90_0, isfinite
33
+ from pygeodesy.constants import _EPSjam # PYCHOK used!
34
34
  from pygeodesy.ellipsoids import _EWGS84, Fmt, unstr
35
35
  from pygeodesy.errors import GeodesicError, IntersectionError, _an, \
36
36
  _xgeodesics, _xkwds_get, _xkwds_kwds, \
@@ -57,7 +57,7 @@ from pygeodesy.utily import atan2, sincos2, fabs, radians
57
57
  # from math import ceil as _ceil, fabs, radians # .fsums, .utily
58
58
 
59
59
  __all__ = _ALL_LAZY.geodesici
60
- __version__ = '25.05.12'
60
+ __version__ = '25.06.02'
61
61
 
62
62
  _0t = 0, # int
63
63
  _1_1t = -1, +1
pygeodesy/geodesicw.py CHANGED
@@ -39,7 +39,7 @@ from contextlib import contextmanager
39
39
  # from math import fabs # from .utily
40
40
 
41
41
  __all__ = _ALL_LAZY.geodesicw
42
- __version__ = '25.04.14'
42
+ __version__ = '25.05.28'
43
43
 
44
44
  _plumb_ = 'plumb'
45
45
  _TRIPS = 65
@@ -96,6 +96,28 @@ class _gWrapped(_kWrapped):
96
96
  if name:
97
97
  self._name, _ = _name2__(name, _or_nameof=E)
98
98
 
99
+ def Area(self, polyline=False, **name): # like GeodesicExact.Area
100
+ '''Return a C{PolygonArea} instance with method C{Compute} extended.
101
+ '''
102
+ _AreaBase = _wrapped._PolygonArea # in .karney._kwrapped
103
+
104
+ class _PolygonArea(_AreaBase):
105
+ # def __init__(self, *earth_polyline):
106
+ # _PolygonArea.__init__(self, *earth_polyline)
107
+
108
+ def Compute(self, reverse=False, sign=True, polar=False):
109
+ '''Use C{B{polar}=True} to adjust the area, see function
110
+ L{areaOf<pygeodesy.geodesicx.gxarea.areaOf>}.
111
+ '''
112
+ n, p, a = _AreaBase.Compute(self, reverse=reverse, sign=sign)
113
+ if polar: # see .geodesicx.gxarea.GeodesicAreaExact._reduced
114
+ a += _copysign(self.area0 * _0_5 * n, a)
115
+ return n, p, a
116
+
117
+ A = _PolygonArea(self, polyline)
118
+ A.name = _name2__(name, _or_nameof=self)
119
+ return A
120
+
99
121
  def ArcDirect(self, lat1, lon1, azi1, a12, outmask=Caps.STANDARD): # PYCHOK no cover
100
122
  '''Return the C{_Geodesic.ArcDirect} result as L{GDict}.
101
123
  '''
@@ -108,8 +130,6 @@ class _gWrapped(_kWrapped):
108
130
  '''
109
131
  return self._GenDirectLine(lat1, lon1, azi1, True, a12, caps, **name)
110
132
 
111
- Area = _Geodesic.Polygon # like GeodesicExact.Area
112
-
113
133
  @property_RO
114
134
  def datum(self):
115
135
  '''Get this geodesic's datum (C{Datum}).
@@ -257,9 +277,8 @@ class _gWrapped(_kWrapped):
257
277
  '''
258
278
  return self._name
259
279
 
260
- # Polygon = _Geodesic.Polygon
261
-
262
- WGS84 = None # _EWGS84.geodesicw recusion
280
+ Polygon = Area
281
+ WGS84 = None # _EWGS84.geodesicw recusion
263
282
 
264
283
  # Geodesic.ArcDirect.__doc__ = _Geodesic.ArcDirect.__doc__
265
284
  # Geodesic.Direct.__doc__ = _Geodesic.Direct.__doc__
@@ -461,7 +480,7 @@ class Geodesic(_gWrapped): # overwritten by 1st instance
461
480
  @kwarg name: Optional C{B{name}=NN} (C{str}).
462
481
  '''
463
482
  g = _wrapped.Geodesic(a_ellipsoid, f=f, **name)
464
- _MODS.geodesicw.Geodesic = g.__class__ # overwrite class
483
+ _MODS.geodesicw.Geodesic = type(g) # overwrite class
465
484
  return g
466
485
 
467
486
 
@@ -485,7 +504,7 @@ class GeodesicLine(_gWrapped): # overwritten by 1st instance
485
504
  @kwarg name: Optional C{B{name}=NN} (C{str}).
486
505
  '''
487
506
  gl = _wrapped.GeodesicLine(geodesic, lat1, lon1, azi1, caps=caps, **name)
488
- _MODS.geodesicw.GeodesicLine = gl.__class__ # overwrite class
507
+ _MODS.geodesicw.GeodesicLine = type(gl) # overwrite class
489
508
  return gl
490
509
 
491
510
 
@@ -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.05.12'
26
+ __version__ = '25.06.04'
27
27
 
28
28
  # **) MIT License
29
29
  #
@@ -5,7 +5,7 @@ u'''Print L{geodesicx} version, etc. using C{python -m pygeodesy.geodesicx}.
5
5
  '''
6
6
 
7
7
  __all__ = ()
8
- __version__ = '25.04.14'
8
+ __version__ = '25.06.01'
9
9
 
10
10
 
11
11
  def _main(**C4order): # PYCHOK no cover
@@ -47,7 +47,7 @@ from sys import argv # .internals._isPyChOK
47
47
  _main(C4order=int(argv[1])) if len(argv) == 2 and argv[1].isdigit() else _main()
48
48
 
49
49
  # % python3.13 -m pygeodesy.geodesicx
50
- # pygeodesy.geodesicx 25.04.14: C4order=30, C4n=5425, C4u=5107, C4u_n=94.1%, C4x=465, C4t=tuple, C4z=166008, geographiclib 2.0 (pygeodesy 25.4.24 Python 3.13.3 64bit arm64 macOS 15.4)
50
+ # pygeodesy.geodesicx 25.06.01: C4order=30, C4n=5425, C4u=5107, C4u_n=94.1%, C4x=465, C4t=tuple, C4z=166008, geographiclib 2.0 (pygeodesy 25.5.28 Python 3.13.3 64bit arm64 macOS 15.5)
51
51
 
52
52
  # % python3.13 -m pygeodesy.geodesicx 30
53
53
  # pygeodesy.geodesicx 24.09.06: C4order=30, C4n=5425, C4u=5107, C4u_n=94.1%, C4x=465, C4t=tuple, C4z=166008 (pygeodesy 24.9.6 Python 3.13.0rc1 64bit arm64 macOS 14.6.1)
pygeodesy/geodesicx/gx.py CHANGED
@@ -44,11 +44,11 @@ from pygeodesy.constants import EPS, EPS0, EPS02, MANT_DIG, NAN, PI, _EPSqrt, \
44
44
  from pygeodesy.datums import _earth_datum, _WGS84, _EWGS84
45
45
  # from pygeodesy.ellipsoids import _EWGS84 # from .datums
46
46
  from pygeodesy.errors import GeodesicError, _xkwds_pop2
47
- from pygeodesy.fmath import hypot as _hypot, Fmt
47
+ from pygeodesy.fmath import fdot_, hypot, hypot2, Fmt
48
48
  from pygeodesy.fsums import fsumf_, fsum1f_
49
49
  from pygeodesy.geodesicx.gxbases import _cosSeries, _GeodesicBase, \
50
50
  _sincos12, _sin1cos2, _sinf1cos2d, \
51
- _TINY, _xnC4
51
+ _TINY, _toNAN, _xnC4
52
52
  from pygeodesy.geodesicx.gxline import _GeodesicLineExact, _update_glXs
53
53
  # from pygeodesy.internals import typename # from .basics
54
54
  from pygeodesy.interns import NN, _DOT_, _UNDER_
@@ -65,7 +65,7 @@ from pygeodesy.utily import atan2, atan2d as _atan2d_reverse, _unrollon, \
65
65
  from math import copysign, cos, degrees, fabs, radians, sqrt
66
66
 
67
67
  __all__ = ()
68
- __version__ = '25.05.12'
68
+ __version__ = '25.06.01'
69
69
 
70
70
  _MAXIT1 = 20
71
71
  _MAXIT2 = 10 + _MAXIT1 + MANT_DIG # MANT_DIG == C++ digits
@@ -96,15 +96,12 @@ def _eTOL2(f):
96
96
  # (bet1 + bet2) / 2, the relative error in the azimuth
97
97
  # consistency check is sig12^2 * abs(f) * min(1, 1-f/2) / 2.
98
98
  # (Error measured for 1/100 < b/a < 100 and abs(f) >= 1/1000.
99
-
100
- # For a given f and sig12, the max error occurs for lines
101
- # near the pole. If the old rule for computing dnm = (dn1
102
- # + dn2)/2 is used, then the error increases by a factor of
103
- # 2.) Setting this equal to epsilon gives sig12 = etol2.
104
-
105
- # Here 0.1 is a safety factor (error decreased by 100) and
106
- # max(0.001, abs(f)) stops etol2 getting too large in the
107
- # nearly spherical case.
99
+ # For a given f and sig12, the max error occurs for lines
100
+ # near the pole. If the old rule for computing dnm = (dn1 +
101
+ # dn2)/2 is used, then the error increases by a factor of 2.)
102
+ # Setting this equal to epsilon gives sig12 = eTOL2. Here,
103
+ # safety factor 0.1 (error decreased by 100) and max(0.001, abs(f))
104
+ # stops eTOL2 getting too large in the nearly spherical case.
108
105
  t = min(_1_0, _1_0 - f * _0_5) * max(_0_001, fabs(f)) * _0_5
109
106
  return _TOL3 / (sqrt(t) if t > EPS02 else EPS0)
110
107
 
@@ -216,11 +213,10 @@ class GeodesicExact(_GeodesicBase):
216
213
  return GeodesicLineExact(self, lat1, lon1, azi1, caps=caps, **name)._GenSet(self._debug, a12=a12)
217
214
 
218
215
  def Area(self, polyline=False, **name):
219
- '''Set up a L{GeodesicAreaExact} to compute area and
220
- perimeter of a polygon.
216
+ '''Set up a L{GeodesicAreaExact} to compute area and perimeter of a polygon.
221
217
 
222
- @kwarg polyline: If C{True}, compute the perimeter only, otherwise
223
- the perimeter and area (C{bool}).
218
+ @kwarg polyline: If C{True}, compute the perimeter only, otherwise the
219
+ perimeter and area (C{bool}).
224
220
  @kwarg name: Optional C{B{name}=NN} (C{str}).
225
221
 
226
222
  @return: A L{GeodesicAreaExact} instance.
@@ -429,17 +425,21 @@ class GeodesicExact(_GeodesicBase):
429
425
  def _eF_reset_cHe2_f1(self, x, y):
430
426
  '''(INTERNAL) Reset elliptic function and return M{cH * e2 / f1 * ...}.
431
427
  '''
432
- self._eF_reset_k2(x)
433
- return y * self._eF.cH * self._e2_f1
428
+ if isnan(x):
429
+ y = NAN
430
+ else:
431
+ self._eF_reset_k2(x)
432
+ y *= self._eF.cH * self._e2_f1
433
+ return y
434
434
 
435
435
  def _eF_reset_k2(self, x):
436
436
  '''(INTERNAL) Reset elliptic function and return C{k2}.
437
437
  '''
438
438
  ep2 = self.ep2
439
- k2 = x**2 * ep2 # see .gxline._GeodesicLineExact._eF
440
- self._eF.reset(k2=-k2, alpha2=-ep2) # kp2, alphap2 defaults
439
+ x *= x * ep2 # see .gxline._GeodesicLineExact._eF
440
+ self._eF.reset(k2=-x, alpha2=-ep2) # kp2, alphap2 defaults
441
441
  _update_glXs(self) # zap cached/memoized _GeodesicLineExact attrs
442
- return k2
442
+ return x
443
443
 
444
444
  @Property_RO
445
445
  def ellipsoid(self):
@@ -499,6 +499,7 @@ class GeodesicExact(_GeodesicBase):
499
499
  if self._debug: # PYCHOK no cover
500
500
  outmask |= Cs._DEBUG_INVERSE & self._debug
501
501
  outmask &= Cs._OUT_MASK # incl. _SALP_CALPs_ and _DEBUG_
502
+ toNAN = _toNAN(outmask, lat1, lon1, lat2, lon2)
502
503
  # compute longitude difference carefully (with _diff182):
503
504
  # result is in [-180, +180] but -180 is only for west-going
504
505
  # geodesics, +180 is for east-going and meridional geodesics
@@ -700,7 +701,9 @@ class GeodesicExact(_GeodesicBase):
700
701
  eFk2=eF.k2, eFa2=eF.alpha2)
701
702
  p.update(r) # r overrides p
702
703
  r = p.toGDict()
703
- return self._iter2tion(r, **p)
704
+
705
+ r = self._iter2tion(r, **p)
706
+ return r._toNAN(outmask) if toNAN else r
704
707
 
705
708
  def _GenDirect(self, lat1, lon1, azi1, arcmode, s12_a12, outmask=Caps.STANDARD):
706
709
  '''(INTERNAL) The general I{Inverse} geodesic calculation.
@@ -805,8 +808,7 @@ class GeodesicExact(_GeodesicBase):
805
808
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
806
809
  Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
807
810
  '''
808
- Cs = Caps
809
- r = self._GDictInverse(lat1, lon1, lat2, lon2, caps | Cs._SALP_CALPs_)
811
+ r = self._GDictInverse(lat1, lon1, lat2, lon2, caps | Caps._SALP_CALPs_)
810
812
  return GeodesicLineExact(self, lat1, lon1, None, caps=caps, _s_calp1=(r.salp1, r.calp1),
811
813
  **name)._GenSet(self._debug, **r)
812
814
 
@@ -839,12 +841,12 @@ class GeodesicExact(_GeodesicBase):
839
841
  # (tan(bet1/2) + tan(bet2/2)) /
840
842
  # (tan(bet1/2) * tan(bet2/2) + 1))
841
843
  # with tan(x/2) = sin(x) / (1 + cos(x))
842
- dbet1 = p.cbet1 + _1_0
843
- dbet2 = p.cbet2 + _1_0
844
- domg12 = comg12 + _1_0
845
- salp12 = (p.sbet1 * dbet2 + dbet1 * p.sbet2) * somg12
846
- calp12 = (p.sbet1 * p.sbet2 + dbet1 * dbet2) * domg12
847
- alp12 = atan2(salp12, calp12) * _2_0
844
+ dbet1 = p.cbet1 + _1_0
845
+ dbet2 = p.cbet2 + _1_0
846
+ domg12 = comg12 + _1_0
847
+ salp12 = fdot_(p.sbet1, dbet2, dbet1, p.sbet2) * somg12
848
+ calp12 = fdot_(p.sbet1, p.sbet2, dbet1, dbet2) * domg12
849
+ alp12 = atan2(salp12, calp12) * _2_0
848
850
  else:
849
851
  # alp12 = alp2 - alp1, used in atan2, no need to normalize
850
852
  salp12, calp12 = _sincos12(salp1, calp1, salp2, calp2)
@@ -870,7 +872,7 @@ class GeodesicExact(_GeodesicBase):
870
872
  and C{p.set_sigs} updated for Newton, C{sig12=None}.
871
873
  '''
872
874
  sig12 = None # use Newton
873
- salp1 = calp1 = salp2 = calp2 = dnm = NAN
875
+ salp1 = calp1 = salp2 = calp2 = NAN
874
876
 
875
877
  # bet12 = bet2 - bet1 in [0, PI)
876
878
  sbet12, cbet12 = _sincos12(p.sbet1, p.cbet1, p.sbet2, p.cbet2)
@@ -878,12 +880,15 @@ class GeodesicExact(_GeodesicBase):
878
880
  if shortline:
879
881
  # sin((bet1 + bet2)/2)^2 = (sbet1 + sbet2)^2 / (
880
882
  # (cbet1 + cbet2)^2 + (sbet1 + sbet2)^2)
881
- t = (p.sbet1 + p.sbet2)**2
882
- s = t / ((p.cbet1 + p.cbet2)**2 + t)
883
- dnm = sqrt(_1_0 + self.ep2 * s)
883
+ t = p.sbet1 + p.sbet2
884
+ if t:
885
+ t *= t / hypot2(t, p.cbet1 + p.cbet2)
886
+ dnm = sqrt(self.ep2 * t + _1_0)
887
+ else:
888
+ dnm = _1_0
884
889
  somg12, comg12 = _sincos2(lam12 / (self.f1 * dnm))
885
890
  else:
886
- somg12, comg12 = p.slam12, p.clam12
891
+ somg12, comg12, dnm = p.slam12, p.clam12, NAN
887
892
 
888
893
  # bet12a = bet2 + bet1 in (-PI, 0], note -sbet1
889
894
  sbet12a, cbet12a = _sincos12(-p.sbet1, p.cbet1, p.sbet2, p.cbet2)
@@ -894,8 +899,8 @@ class GeodesicExact(_GeodesicBase):
894
899
  salp1 = p.cbet2 * somg12
895
900
  calp1 = (sbet12a - t) if comg12 < 0 else (sbet12 + t)
896
901
 
897
- ssig12 = _hypot(salp1, calp1)
898
- csig12 = p.sbet1 * p.sbet2 + p.cbet1 * p.cbet2 * comg12
902
+ ssig12 = hypot(salp1, calp1)
903
+ csig12 = fdot_(p.sbet1, p.sbet2, p.cbet1, p.cbet2 * comg12)
899
904
 
900
905
  if shortline and ssig12 < self._eTOL2: # really short lines
901
906
  t = c if comg12 < 0 else s
@@ -970,8 +975,8 @@ class GeodesicExact(_GeodesicBase):
970
975
  #
971
976
  # omg12 is near PI, estimate work with omg12a = PI - omg12
972
977
  k = _Astroid(x, y)
973
- sca *= (y * (k + _1_0) / k) if f < 0 else \
974
- (x * k / (k + _1_0))
978
+ k1 = _1_0 + k
979
+ sca *= (y * k1 / k) if f < 0 else (x * k / k1)
975
980
  s, c = _sincos2(-sca) # omg12a
976
981
  # update spherical estimate of alp1 using omg12 instead of lam12
977
982
  salp1 = p.cbet2 * s
@@ -1082,16 +1087,15 @@ class GeodesicExact(_GeodesicBase):
1082
1087
  # Missing a factor of self.b. Add parens around
1083
1088
  # (csig1 * ssig2) and (ssig1 * csig2) to ensure
1084
1089
  # accurate cancellation for coincident points.
1085
- m12b = fsum1f_(p.dn2 * (p.csig1 * p.ssig2),
1086
- -p.dn1 * (p.ssig1 * p.csig2),
1087
- J12 * (p.csig1 * p.csig2))
1090
+ m12b = fdot_(p.dn2, (p.csig1 * p.ssig2),
1091
+ -p.dn1, (p.ssig1 * p.csig2),
1092
+ J12, (p.csig1 * p.csig2))
1088
1093
  if (outmask & Cs.GEODESICSCALE):
1089
- M12 = M21 = p.ssig1 * p.ssig2 + \
1090
- p.csig1 * p.csig2
1094
+ M12 = M21 = fdot_(p.ssig1, p.ssig2, p.csig1, p.csig2)
1091
1095
  t = (p.cbet1 - p.cbet2) * self.ep2 * \
1092
1096
  (p.cbet1 + p.cbet2) / (p.dn1 + p.dn2)
1093
- M12 += (p.ssig2 * t + p.csig2 * J12) * p.ssig1 / p.dn1
1094
- M21 -= (p.ssig1 * t + p.csig1 * J12) * p.ssig2 / p.dn2
1097
+ M12 += fdot_(p.ssig2, t, p.csig2, J12) * p.ssig1 / p.dn1
1098
+ M21 -= fdot_(p.ssig1, t, p.csig1, J12) * p.ssig2 / p.dn2
1095
1099
 
1096
1100
  return s12b, m12b, m0, M12, M21
1097
1101
 
@@ -1223,7 +1227,8 @@ class GeodesicExact(_GeodesicBase):
1223
1227
  tripb = fsum1f_(calp1a, -calp1, _abs(salp1a - salp1)) < TOLb or \
1224
1228
  fsum1f_(calp1b, -calp1, _abs(salp1b - salp1)) < TOLb
1225
1229
  else:
1226
- raise GeodesicError(Fmt.no_convergence(v, TOLv), txt=repr(self)) # self.toRepr()
1230
+ v = Fmt.no_convergence(v, TOLv)
1231
+ raise GeodesicError(v, txt=repr(self)) # self.toRepr()
1227
1232
 
1228
1233
  p.set_(iter=i, trip=tripb) # like .geodsolve._GDictInvoke: iter NOT iteration!
1229
1234
  return sig12, salp1, calp1, salp2, calp2, domg12
@@ -1310,7 +1315,7 @@ def _Astroid(x, y):
1310
1315
  u = _cbrt(S * _2_0) # == T3 + _copysign(abs(S), T3)
1311
1316
  else:
1312
1317
  u = _0_0
1313
- v = _hypot(u, y) # sqrt(u**2 + q)
1318
+ v = hypot(u, y) # sqrt(u**2 + q)
1314
1319
  # avoid loss of accuracy when u < 0
1315
1320
  u = (q / (v - u)) if u < 0 else (v + u)
1316
1321
  w = (u - q) / (v + v) # positive?