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/elliptic.py CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- u'''I{Karney}'s elliptic functions and integrals.
4
+ u'''I{Karney}'s elliptic integrals and elliptic and ellipse functions.
5
5
 
6
6
  Class L{Elliptic} transcoded from I{Charles Karney}'s C++ class U{EllipticFunction
7
7
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1EllipticFunction.html>}
@@ -76,36 +76,38 @@ U{22<https://DLMF.NIST.gov/22>}.
76
76
  from __future__ import division as _; del _ # noqa: E702 ;
77
77
 
78
78
  from pygeodesy.basics import copysign0, map2, neg, neg_
79
- from pygeodesy.constants import EPS, INF, NAN, PI, PI_2, PI_4, \
80
- _EPStol as _TolJAC, _0_0, _0_25, \
81
- _0_5, _1_0, _2_0, _N_2_0, _3_0, \
82
- _4_0, _6_0, _8_0, _64_0, _180_0, \
83
- _360_0, _over
84
- from pygeodesy.constants import _EPSjam as _TolJAM # PYCHOK used!
79
+ from pygeodesy.constants import EPS, EPS_2, INF, NAN, PI, PI_2, PI_4, PI2, \
80
+ _EPStol as _TolJAC, _0_0, _0_25, _0_5, _1_0, \
81
+ _2_0, _3_0, _4_0, _6_0, _8_0, _64_0, _180_0, \
82
+ _360_0, _flipsign, _over, _1_over
83
+ from pygeodesy.constants import _10_0, _EPSjam as _TolJAM, MANT_DIG as _DIG53 # PYCHOK used!
85
84
  # from pygeodesy.errors import _ValueError # from .fsums
86
- from pygeodesy.fmath import favg, Fdot_, fma, hypot1, zqrt
85
+ from pygeodesy.fmath import favg, Fdot, Fdot_, fhorner, hypot, hypot1, \
86
+ zqrt, _operator
87
87
  from pygeodesy.fsums import Fsum, _fsum, _ValueError
88
88
  from pygeodesy.internals import _Enum, typename
89
- from pygeodesy.interns import NN, _delta_, _DOT_, _f_, _invalid_, \
90
- _invokation_, _negative_, _SPACE_
89
+ from pygeodesy.interns import NN, _delta_, _DOT_, _f_, _invalid_, _invokation_, \
90
+ _negative_, _SPACE_
91
91
  from pygeodesy.karney import _K_2_0, _norm180, _signBit, _sincos2
92
- # from pygeodesy.lazily import _ALL_LAZY # from .named
93
- from pygeodesy.named import _Named, _NamedTuple, _ALL_LAZY, Fmt, unstr
94
- from pygeodesy.props import _allPropertiesOf_n, Property_RO, _update_all
92
+ from pygeodesy.lazily import _ALL_LAZY, _FOR_DOCS
93
+ from pygeodesy.named import callername, _Named, _NamedTuple, Fmt, unstr
94
+ from pygeodesy.props import _allPropertiesOf_n, Property_RO, property_ROnce, \
95
+ _update_all
95
96
  # from pygeodesy.streprs import Fmt, unstr # from .named
96
97
  from pygeodesy.units import Scalar, Scalar_
97
98
  from pygeodesy.utily import atan2 # sincos2 as _sincos2
98
99
 
99
100
  from math import asin, asinh, atan, ceil, cosh, fabs, floor, radians, \
100
101
  sin, sinh, sqrt, tan, tanh # tan as _tan
102
+ # import operator as _operator # from .fmath
101
103
 
102
104
  __all__ = _ALL_LAZY.elliptic
103
- __version__ = '25.10.10'
105
+ __version__ = '26.01.16'
104
106
 
105
107
  _TolRD = zqrt(EPS * 0.002)
106
108
  _TolRF = zqrt(EPS * 0.030)
107
109
  _TolRG0 = _TolJAC * 2.7
108
- _TRIPS = 28 # Max depth, 6-18 might be sufficient
110
+ _MAXIT = 32 # Max depth, 6-18 sufficient
109
111
 
110
112
 
111
113
  class _Cs(_Enum):
@@ -114,6 +116,314 @@ class _Cs(_Enum):
114
116
  pass
115
117
 
116
118
 
119
+ class Elliperim(object):
120
+ '''Singleton with various methods to compute the perimeter of an ellipse.
121
+ '''
122
+ _Ek = None
123
+ _k = _2_0 # 0 < _k < 1
124
+ _TOL53 = sqrt(EPS_2) # sqrt(pow(_0_5, _DIG53))
125
+ _TOL53_53 = _TOL53 / _DIG53 # "flat" b/a tolerance, 1.9e-10
126
+ # assert _DIG53 == 53
127
+
128
+ def AGM(self, a, b, maxit=_DIG53):
129
+ '''Compute the perimeter of an ellipse with semi-axes B{C{a}} and B{C{b}} using the U{AGM
130
+ <https://PaulBourke.net/geometry/ellipsecirc>} (Arithmetic-Geometric Mean) method.
131
+
132
+ @kwarg maxit: Number of iterations (C{int}).
133
+
134
+ @return: Perimeter (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
135
+
136
+ @raise ValueError: Invalid B{C{a}} or B{C{b}} or no convergence for B{C{maxit}} iterations.
137
+ '''
138
+ _, p, a, b = self._pab4(a, b)
139
+ if p is None:
140
+ t = self._TOL53
141
+ m = -1
142
+ c = a + b
143
+ ds = [c**2]
144
+ _d = ds.append
145
+ for _ in range(maxit): # 4..5 trips
146
+ b = sqrt(a * b)
147
+ a = c * _0_5
148
+ c = a + b
149
+ d = a - b
150
+ m *= 2
151
+ _d(d**2 * m)
152
+ if d <= (b * t):
153
+ break
154
+ else: # PYCHOK no cover
155
+ raise _convergenceError(maxit, _over(d, b), t)
156
+ p = _over(_fsum(ds) * PI, c) # nonfinites=True
157
+ return p
158
+
159
+ def arc(self, a, b, deg2, deg1=0):
160
+ '''Compute the length of U{elliptic arc<https://www.JohnDCook.com/blog/2022/11/02/elliptic-arc-length/>}
161
+ C{(B{deg2} - B{deg1})}, both counter-clockwise from semi-axis B{C{a}} to B{C{b}} of the ellipse.
162
+
163
+ @arg deg2: End angle of the elliptic arc (C{degrees}).
164
+ @kwarg deg1: Start angle of the elliptic arc (C{degrees}).
165
+
166
+ @return: Arc length, signed (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
167
+
168
+ @raise ValueError: Invalid B{C{a}} or B{C{b}}.
169
+ '''
170
+ return self.arc_(a, b, radians(deg2), (radians(deg1) if deg1 else _0_0))
171
+
172
+ def arc_(self, a, b, rad2, rad1=0):
173
+ '''Compute the length of U{elliptic arc<https://www.JohnDCook.com/blog/2022/11/02/elliptic-arc-length/>}
174
+ C{(B{rad2} - B{rad1})}, both counter-clockwise from semi-axis B{C{a}} to B{C{b}} of the ellipse.
175
+
176
+ @arg rad2: End angle of the elliptic arc (C{radians}).
177
+ @kwarg rad1: Start angle of the elliptic arc (C{radians}).
178
+
179
+ @return: Arc length, signed (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
180
+
181
+ @raise ValueError: Invalid B{C{a}} or B{C{b}}.
182
+ '''
183
+ r, p, a, b = self._pab4(a, b)
184
+ if p is None:
185
+ _e = self._ellipe or self._ellipE
186
+ k = (b / a)**2
187
+ r = PI_2 if r else _0_0
188
+ p = self._arc(_e, k, rad2 + r)
189
+ r += rad1
190
+ if r:
191
+ p -= self._arc(_e, k, r)
192
+ p *= a
193
+ else:
194
+ p *= (rad2 - rad1) / PI2
195
+ return p
196
+
197
+ def _arc(self, _e, k, r):
198
+ t, r = divmod(r, PI2)
199
+ p = _e(k, r)
200
+ if t: # + t * perimeter
201
+ t *= _e(k) * _4_0
202
+ p += t
203
+ return p
204
+
205
+ def Arc43(self, a, b):
206
+ '''Compute the perimeter (and arcs) of an ellipse with semi-axes B{C{a}} and B{C{b}}
207
+ using the U{4-Arc<https://PaulBourke.net/geometry/ellipsecirc>} approximation.
208
+
209
+ @return: 3-Tuple C{(p, Ra, Rb)} with perimeter C{p}, arc radius C{Ra} at the
210
+ major and arc radius C{Rb} at the minor semi-axis (C{meter}, same
211
+ units as semi-axes B{C{a}} and B{C{b}}.
212
+
213
+ @raise ValueError: Invalid B{C{a}} or B{C{b}}.
214
+ '''
215
+ r, p, a, b = self._pab4(a, b)
216
+ if p is None:
217
+ h = hypot(a, b)
218
+ p = atan2(b, a)
219
+ s, c = _sincos2(p)
220
+ L = (h - (a - b)) * _0_5
221
+ Ra = _over(L, c)
222
+ Rb = _over(h - L, s)
223
+ p = (p * Rb + (PI_2 - p) * Ra) * _4_0
224
+ elif a > b: # flat
225
+ Ra, Rb = _0_0, _1_over(b) # INF
226
+ else: # circle
227
+ Ra, Rb = a, b
228
+ return (p, Rb, Ra) if r else (p, Ra, Rb)
229
+
230
+ # def CR(self, a, b):
231
+ # '''Compute the perimeter of an ellipse with semi-axes B{C{a}} and B{C{b}} using U{Rackauckas'
232
+ # <https://www.ChrisRackauckas.com/assets/Papers/ChrisRackauckas-The_Circumference_of_an_Ellipse.pdf>}
233
+ # approximation, also U{here<https://ExtremeLearning.com.AU/a-formula-for-the-perimeter-of-an-ellipse>}.
234
+ #
235
+ # @return: Perimeter (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
236
+ #
237
+ # @raise ValueError: Invalid B{C{a}} or B{C{b}}.
238
+ # '''
239
+ # _, p, a, b = self._pab4(a, b)
240
+ # if p is None:
241
+ # p = a + b
242
+ # h = ((a - b) / p)**2
243
+ # p *= (fhorner(h, 135168, -85760, -5568, 3867) /
244
+ # fhorner(h, 135168, -119552, 22208, 345)) * PI
245
+ # return p
246
+
247
+ def E2k(self, a, b):
248
+ '''Compute the perimeter of an ellipse with semi-axes B{C{a}} and B{C{b}} using L{E(k)
249
+ <Elliptic.cE>}, the complete C{elliptic} integral of the 2nd kind.
250
+
251
+ @return: Perimeter (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
252
+
253
+ @raise ValueError: Invalid B{C{a}} or B{C{b}}.
254
+ '''
255
+ return self._ellip2k(a, b, self._ellipE)
256
+
257
+ def e2k(self, a, b, E_ab=None):
258
+ '''Compute the perimeter of an ellipse with semi-axes B{C{a}} and B{C{b}} using U{SciPy's
259
+ ellipe<https://www.JohnDCook.com/perimeter_ellipse.html>} function or method
260
+ C{E_ab}, otherwise C{None}.
261
+
262
+ @kwarg E_ab: Alternate C{Elliperim}C{(a, b)} method to use in case C{SciPy's
263
+ ellipe} is not available.
264
+
265
+ @return: Perimeter (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
266
+
267
+ @raise ValueError: Invalid B{C{a}} or B{C{b}}.
268
+ '''
269
+ p = self._ellipe
270
+ if callable(p): # p is not None
271
+ p = self._ellip2k(a, b, p)
272
+ elif callable(E_ab): # and E_ab is not Elliperim.e2k
273
+ p = E_ab(a, b)
274
+ return p
275
+
276
+ def _ellipE(self, k, phi=None):
277
+ '''(INTERNAL) Get the in- C{fE(phi, k)} or complete C{cE(k)} integral of the 2nd kind.
278
+ '''
279
+ if self._k != k: # or self._Ek is None
280
+ self._k = k
281
+ self._Ek = _Elliptic(k)
282
+ return self._Ek.cE if phi is None else self._Ek.fE(phi)
283
+
284
+ @property_ROnce
285
+ def _ellipe(self):
286
+ '''(INTERNAL) Wrap functions C{scipy.special.ellipe} and C{-.ellipeinc}, I{once}.
287
+ '''
288
+ try:
289
+ from scipy.special import ellipe, ellipeinc
290
+
291
+ def _ellipe(k, phi=None):
292
+ m = _1_0 - k
293
+ p = ellipe(m) if phi is None else ellipeinc(phi, m)
294
+ return float(p)
295
+
296
+ except (AttributeError, ImportError):
297
+ _ellipe = None
298
+ return _ellipe # overwrite property_ROnce
299
+
300
+ def _ellip2k(self, a, b, _ellip):
301
+ '''(INTERNAL) Helper for methods C{E2k} and C{e2k}.
302
+ '''
303
+ _, p, a, b = self._pab4(a, b)
304
+ if p is None: # see .ellipsoids.Ellipsoid.L
305
+ k = (b / a)**2
306
+ p = _ellip(k) * a * _4_0
307
+ return p
308
+
309
+ def GK(self, a, b):
310
+ '''Compute the perimeter of an ellipse with semi-axes B{C{a}} and B{C{b}} using the U{Gauss-Kummer
311
+ <https://www.JohnDCook.com/blog/2023/05/28/approximate-ellipse-perimeter>} series, and U{here
312
+ <https://MathWorld.Wolfram.com/Gauss-KummerSeries.html>}, C{B{b / a} > 0.75}.
313
+
314
+ @return: Perimeter (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
315
+
316
+ @raise ValueError: Invalid B{C{a}} or B{C{b}}.
317
+ '''
318
+ p, h = self._ph2(a, b)
319
+ if h:
320
+ p *= fhorner(h**2, *self._GKs) * PI
321
+ return p
322
+
323
+ @property_ROnce
324
+ def _GKs(self):
325
+ '''(INTERNAL) Compute the Gauss-Kummer coefficients, I{once} from U{numerators
326
+ <https://OEIS.org/A056981>} and U{denominators<https://OEIS.org/A056982>}.
327
+ '''
328
+ return (1, 1 / 4, 1 / 64, 1 / 256, 25 / 16384, 49 / 65536,
329
+ 441 / 1048576, 1089 / 4194304) # overwrite property_ROnce
330
+
331
+ def HG(self, a, b, maxit=_DIG53):
332
+ '''Compute the perimeter of an ellipse with semi-axes B{C{a}} and B{C{b}} using the U{HG
333
+ <https://web.Tecnico.ULisboa.PT/~mcasquilho/compute/com/,ellips/PerimeterOfEllipse.pdf>}
334
+ (HyperGeometric Gauss-Kummer) series.
335
+
336
+ @kwarg maxit: Number of iterations (C{int}), sufficient for C{B{b / a} > 0.125}.
337
+
338
+ @return: Perimeter (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
339
+
340
+ @raise ValueError: Invalid B{C{a}} or B{C{b}} or no convergence for B{C{maxit}} iterations.
341
+ '''
342
+ p, h = self._ph2(a, b)
343
+ if h:
344
+ hs = self._HGs(h, max(maxit, _DIG53))
345
+ p *= _fsum(hs) * PI # nonfinites=True
346
+ return p
347
+
348
+ def _HGs(self, h, maxit):
349
+ '''(INTERNAL) Yield the C{HG} terms.
350
+ '''
351
+ s = _1_0
352
+ yield s
353
+ p = t = -1
354
+ for u in range(-1, maxit * 2, 2):
355
+ t *= u / (u + 3) * h
356
+ t2 = t**2
357
+ s += t2
358
+ yield t2
359
+ if s == p: # 44 trips
360
+ break
361
+ p = s
362
+ else: # PYCHOK no cover
363
+ raise _convergenceError(maxit, s, s - t2)
364
+
365
+ # def LS(self, a, b):
366
+ # '''Compute the perimeter of an ellipse with semi-axes B{C{a}} and B{C{b}} using the U{Linderholm-Segal
367
+ # <https://www.JohnDCook.com/blog/2021/03/24/perimeter-of-an-ellipse>} formula, aka C{3/2 norm}.
368
+ #
369
+ # @return: Perimeter (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
370
+ #
371
+ # @raise ValueError: Invalid B{C{a}} or B{C{b}}.
372
+ # '''
373
+ # _, p, a, b = self._pab4(a, b)
374
+ # if p is None:
375
+ # n = pow(a, _1_5) + pow(b, _1_5)
376
+ # p = pow(n * _0_5, _2_3rd) * PI2
377
+ # return p
378
+
379
+ def _pab4(self, a, b):
380
+ r = a < b
381
+ if r:
382
+ a, b = b, a
383
+ if a > b:
384
+ if b > (a * self._TOL53_53):
385
+ p = None
386
+ elif b < 0: # PYCHOK no cover
387
+ t = callername() # underOK=True
388
+ t = _DOT_(typename(self), t)
389
+ t = unstr(t, b, a) if r else \
390
+ unstr(t, a, b)
391
+ raise _ValueError(t)
392
+ else: # "flat"
393
+ p = a * _4_0
394
+ else: # circle
395
+ p = a * PI2
396
+ return r, p, a, b
397
+
398
+ def _ph2(self, a, b):
399
+ _, p, a, b = self._pab4(a, b)
400
+ if p is None:
401
+ p = a + b
402
+ h = (a - b) / p
403
+ else:
404
+ h = None
405
+ return p, h
406
+
407
+ def R2(self, a, b):
408
+ '''Compute the perimeter of an ellipse with semi-axes B{C{a}} and B{C{b}} using U{Ramanujan's
409
+ 2nd<https://PaulBourke.net/geometry/ellipsecirc>} approximation, C{B{b / a} > 0.9}.
410
+
411
+ @return: Perimeter (C{meter}, same units as semi-axes B{C{a}} and B{C{b}}).
412
+
413
+ @raise ValueError: Invalid B{C{a}} or B{C{b}}.
414
+ '''
415
+ p, h = self._ph2(a, b)
416
+ if h:
417
+ h *= _3_0 * h
418
+ h /= sqrt(_4_0 - h) + _10_0 # /= chokes PyChecker?
419
+ p *= (h + _1_0) * PI
420
+ return p
421
+
422
+ if not _FOR_DOCS: # PYCHOK force epydoc
423
+ Elliperim = Elliperim() # singleton
424
+ del _FOR_DOCS
425
+
426
+
117
427
  class Elliptic(_Named):
118
428
  '''Elliptic integrals and functions.
119
429
 
@@ -154,31 +464,55 @@ class Elliptic(_Named):
154
464
  '''
155
465
  return self._alphap2
156
466
 
467
+ def _B3(self, x):
468
+ '''(INTERNAL) Bulirsch' sncndn routine.
469
+ '''
470
+ # Numerische Mathematik 7, 1965, p 89
471
+ # implements DLMF Eqs 22.17.2 - 22.17.4
472
+ c, d, cd, mn = self._B4
473
+ sn, cn = _sincos2(x * cd)
474
+ dn = _1_0
475
+ if sn:
476
+ a = cn / sn
477
+ c *= a
478
+ for m, n in mn:
479
+ a *= c
480
+ c *= dn
481
+ dn = (n + a) / (m + a)
482
+ a = c / m
483
+ a = _1_over(hypot1(c))
484
+ sn = _flipsign(a, sn)
485
+ cn = sn * c
486
+ if d: # and _signBit(self.kp2): # implied
487
+ cn, dn = dn, cn
488
+ sn = sn / d # /= chokes PyChecker
489
+ return sn, cn, dn
490
+
157
491
  @Property_RO
158
- def _b4(self):
492
+ def _B4(self):
159
493
  '''(INTERNAL) Get Bulirsch' 4-tuple C{(c, d, cd, mn)}.
160
494
  '''
161
- d, b = 0, self.kp2 # note, kp2 >= 0 always here
495
+ a = p = _1_0
496
+ b, d = self.kp2, 0 # kp2 >= 0 always here
162
497
  if _signBit(b): # PYCHOK no cover
163
- d = _1_0 - b
164
- b = neg(b / d)
165
- d = sqrt(d)
166
- ab, a = [], _1_0
167
- for i in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC
168
- b = sqrt(b)
169
- ab.append((a, b))
498
+ d = a - b
499
+ b = neg(b / d)
500
+ d = sqrt(d)
501
+ mn = []
502
+ _mn = mn.append
503
+ for i in range(1, _MAXIT): # GEOGRAPHICLIB_PANIC
504
+ b = sqrt(p * b)
505
+ _mn((a, b))
170
506
  c = favg(a, b)
171
507
  r = fabs(a - b)
172
- if r <= (a * _TolJAC): # 6 trips, quadratic
508
+ if r <= (a * _TolJAC): # 4..6 trips, quadratic
173
509
  self._iteration += i
174
510
  break
175
- t = a
176
- b *= a
177
- a = c
511
+ p, a = a, c
178
512
  else: # PYCHOK no cover
179
- raise _convergenceError(r / t, _TolJAC)
513
+ raise _convergenceError(_MAXIT, _over(r, p), _TolJAC)
180
514
  cd = (c * d) if d else c
181
- return c, d, cd, tuple(reversed(ab))
515
+ return c, d, cd, tuple(reversed(mn))
182
516
 
183
517
  @Property_RO
184
518
  def cD(self):
@@ -202,15 +536,15 @@ class Elliptic(_Named):
202
536
  cD = float(D)
203
537
  # Complete elliptic integral E(k), Carlson eq. 4.2
204
538
  # <https://DLMF.NIST.gov/19.25.E1>
205
- cE = _rG2(kp2, _1_0, self, PI_=PI_2)
539
+ cE = self._cE(kp2)
206
540
  # Complete elliptic integral K(k), Carlson eq. 4.1
207
541
  # <https://DLMF.NIST.gov/19.25.E1>
208
- cK = _rF2(kp2, _1_0, self)
542
+ cK = self._cK(kp2)
209
543
  cKE = float(D.fmul(k2))
210
544
  eps = k2 / (sqrt(kp2) + _1_0)**2
211
545
 
212
546
  except Exception as X:
213
- raise _ellipticError(self._cDEKEeps, k2=k2, kp2=kp2, cause=X)
547
+ raise _ellipticError(self, k2=k2, kp2=kp2, cause=X)
214
548
  else:
215
549
  cD = cK = cKE = INF
216
550
  cE = _1_0
@@ -230,6 +564,9 @@ class Elliptic(_Named):
230
564
  '''
231
565
  return self._cDEKEeps.cE
232
566
 
567
+ def _cE(self, kp2): # compl integr 2nd kind
568
+ return _rG2(kp2, _1_0, self, PI_=PI_2)
569
+
233
570
  @Property_RO
234
571
  def cG(self):
235
572
  '''Get Legendre's complete geodesic longitude integral
@@ -274,8 +611,8 @@ class Elliptic(_Named):
274
611
  cH = float(_RD(_0_0, _1_0, kp2, _3_0 / kp2, self)) if kp2 else _1_0
275
612
 
276
613
  except Exception as X:
277
- raise _ellipticError(self._cGHPi, kp2=kp2, alpha2 =alpha2,
278
- alphap2=alphap2, cause=X)
614
+ raise _ellipticError(self, kp2=kp2, alpha2 =alpha2,
615
+ alphap2=alphap2, cause=X)
279
616
  return _Cs(cG=cG, cH=cH, cPi=cPi)
280
617
 
281
618
  @Property_RO
@@ -292,6 +629,9 @@ class Elliptic(_Named):
292
629
  '''
293
630
  return self._cDEKEeps.cK
294
631
 
632
+ def _cK(self, kp2): # compl integr 1st kind
633
+ return _rF2(kp2, _1_0, self)
634
+
295
635
  @Property_RO
296
636
  def cKE(self):
297
637
  '''Get the difference between the complete integrals of the
@@ -414,24 +754,24 @@ class Elliptic(_Named):
414
754
  phi = PI * r / E2 # phi in [-PI_2, PI_2)
415
755
  Phi = Fsum(phi)
416
756
  # first order correction
417
- phi = Phi.fsum_(self.eps * sin(phi * _2_0) / _N_2_0)
757
+ phi = Phi.fsum_(-sin(phi * _2_0) * self.eps * _0_5)
418
758
  # self._iteration = 0
419
759
  # For kp2 close to zero use asin(r / cE) or J. P. Boyd,
420
760
  # Applied Math. and Computation 218, 7005-7013 (2012)
421
761
  # <https://DOI.org/10.1016/j.amc.2011.12.021>
422
762
  _Phi2 = Phi.fsum2f_ # aggregate
423
- for i in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC
763
+ for i in range(1, _MAXIT): # GEOGRAPHICLIB_PANIC
424
764
  sn, cn, dn = self._sncndn3(phi)
425
765
  if dn:
426
766
  sn = self.fE(sn, cn, dn)
427
767
  phi, d = _Phi2((r - sn) / dn)
428
768
  else: # PYCHOK no cover
429
- d = _0_0 # XXX or continue?
769
+ d = _0_0 # XXX continue?
430
770
  if fabs(d) < _TolJAC: # 3-4 trips
431
771
  self._iteration += i
432
772
  break
433
773
  else: # PYCHOK no cover
434
- raise _convergenceError(d, _TolJAC)
774
+ raise _convergenceError(_MAXIT, d, _TolJAC)
435
775
  return Phi.fsum_(n * PI) if n else phi
436
776
 
437
777
  @Property_RO
@@ -698,7 +1038,8 @@ class Elliptic(_Named):
698
1038
  x *= r # phi
699
1039
  for a, c in ac:
700
1040
  p = x
701
- x = favg(asin(c * sin(x) / a), x)
1041
+ a = asin(c * sin(x) / a)
1042
+ x = favg(a, x)
702
1043
  if self.k2 < 0: # Sala Eq. 5.8
703
1044
  x = p - x
704
1045
  else: # PYCHOK no cover
@@ -714,20 +1055,22 @@ class Elliptic(_Named):
714
1055
  # assert b and c
715
1056
  if c < 0: # Sala Eq. 5.8
716
1057
  r = sqrt(b)
717
- b = _1_0 / b
1058
+ b = _1_over(b)
718
1059
  # c *= b # unused
719
- ac = [] # [(a, sqrt(c))] unused
720
- for i in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC
1060
+ ac = [] # [(a, sqrt(c))] unused
1061
+ _ac = ac.append
1062
+ for _ in range(_MAXIT): # GEOGRAPHICLIB_PANIC
721
1063
  b = sqrt(a * b)
722
1064
  c = favg(a, -b)
723
1065
  a = favg(a, b) # == PI_2 / K
724
- ac.append((a, c))
725
- if c <= (a * _TolJAM): # 7-18 trips, quadratic
726
- self._iteration += i
1066
+ _ac((a, c))
1067
+ if c <= (a * _TolJAM): # 7..18 trips, quadratic
727
1068
  break
728
1069
  else: # PYCHOK no cover
729
- raise _convergenceError(c / a, _TolJAM)
730
- r *= a * pow(2, i) # 2**len(ac)
1070
+ raise _convergenceError(_MAXIT, _over(c, a), _TolJAM)
1071
+ i = len(ac)
1072
+ r *= 2**i * a
1073
+ self._iteration += i
731
1074
  return r, tuple(reversed(ac))
732
1075
 
733
1076
  @Property_RO
@@ -811,30 +1154,11 @@ class Elliptic(_Named):
811
1154
  if self.kp2:
812
1155
  if jam: # Jacobi amplitude, C++ v 2.4
813
1156
  sn, cn, dn = self._sncndn3(self._jam(x))
814
-
815
- else: # Bulirsch's sncndn routine, p 89 of
816
- # Numerische Mathematik 7, 78-90 (1965).
817
- # Implements DLMF Eqs 22.17.2 - 22.17.4
818
- c, d, cd, mn = self._b4
819
- sn, cn = _sincos2(x * cd)
820
- dn = _1_0
821
- if sn:
822
- a = cn / sn
823
- c *= a
824
- for m, n in mn:
825
- a *= c
826
- c *= dn
827
- dn = (n + a) / (m + a)
828
- a = c / m
829
- a = _1_0 / hypot1(c)
830
- sn = neg(a) if _signBit(sn) else a
831
- cn = sn * c
832
- if d: # and _signBit(self.kp2): # implied
833
- cn, dn = dn, cn
834
- sn = sn / d # /= chokes PyChecker
1157
+ else:
1158
+ sn, cn, dn = self._B3(x)
835
1159
  else:
836
1160
  sn = tanh(x) # accurate for large abs(x)
837
- cn = dn = _1_0 / cosh(x)
1161
+ cn = dn = _1_over(cosh(x))
838
1162
 
839
1163
  except Exception as X:
840
1164
  raise _ellipticError(self.sncndn, x, kp2=self.kp2, cause=X)
@@ -935,6 +1259,22 @@ class Elliptic(_Named):
935
1259
  _allPropertiesOf_n(16, Elliptic) # PYCHOK assert, see Elliptic.reset
936
1260
 
937
1261
 
1262
+ class _Elliptic(Elliptic):
1263
+ '''(INTERNAL) Low overhead C{Elliptic} for C{Elliperim._ellipE}.
1264
+ '''
1265
+ _alpha2 = _0_0
1266
+ _alphap2 = _1_0
1267
+ cE = _0_0 # overide Property_RO
1268
+ _iteration = 0
1269
+
1270
+ def __init__(self, k): # k == kp2
1271
+ self._k2 = _1_0 - k
1272
+ self._kp2 = k # 0 < k < 1
1273
+ # assert self.kp2 and self.k2
1274
+ self.cE = self._cE(k) # override Property_RO
1275
+ # self.cK = self._cK(k) # ditto
1276
+
1277
+
938
1278
  class EllipticError(_ValueError):
939
1279
  '''Elliptic function, integral, convergence or other L{Elliptic} issue.
940
1280
  '''
@@ -948,6 +1288,24 @@ class Elliptic3Tuple(_NamedTuple):
948
1288
  _Units_ = ( Scalar, Scalar, Scalar)
949
1289
 
950
1290
 
1291
+ class _Hdot(dict):
1292
+ '''(INTERNAL) Caching helper for C{_Horner} and C{_RF3}.
1293
+ '''
1294
+ def __call__(self, F, h, *Xys):
1295
+ k = Xys[1] # unique key
1296
+ ys = self.get(k, None)
1297
+ if ys is None:
1298
+ self[k] = ys = tuple((y / h) for y in Xys[1::2])
1299
+ try:
1300
+ D = Fdot(Xys[0::2], *ys, f2product=True)
1301
+ except (OverflowError, TypeError, ValueError):
1302
+ ts = map(_operator.mul, Xys[0::2], ys)
1303
+ D = Fsum(*ts, nonfinites=True) # _Ksum(0, 0, *ts)
1304
+ return D.fmul(F) # Fsum
1305
+
1306
+ _Hdot = _Hdot() # PYCHOK singleton
1307
+
1308
+
951
1309
  class _List(list):
952
1310
  '''(INTERNAL) Helper for C{_RD}, C{_RF3} and C{_RJ}.
953
1311
  '''
@@ -974,12 +1332,12 @@ class _List(list):
974
1332
  a = L.a0(5 if y else 3)
975
1333
  t = L.threshold(Tol)
976
1334
  m = 1
977
- for i in range(_TRIPS):
1335
+ for i in range(_MAXIT):
978
1336
  d = fabs(a * m)
979
1337
  if d > t: # 3-6 trips
980
1338
  break
981
1339
  s = map2(sqrt, L) # sqrt(x), sqrt(y), sqrt(z) [, sqrt(p)]
982
- Q = _Qot3(*s) # (s[0] * s[1], s[1] * s[2], s[2] * s[0])
1340
+ Q = _Qdot3(*s) # (s[0] * s[1], s[1] * s[2], s[2] * s[0])
983
1341
  a = Q(a) # An = sum(An, *Q)) / 4
984
1342
  L[:] = map(Q, L) # x = sum(x, *Q) / 4, ...
985
1343
  if y: # yield only if used
@@ -987,7 +1345,7 @@ class _List(list):
987
1345
  yield a, m, r, s # L[2] is next z
988
1346
  m *= 4
989
1347
  else: # PYCHOK no cover
990
- raise _convergenceError(d, t, thresh=True)
1348
+ raise _convergenceError(_MAXIT, d, t, thresh=True)
991
1349
  yield a, m, None, () # sentinel: same a, next m, no r and s
992
1350
  if inst:
993
1351
  inst._iteration += i
@@ -997,7 +1355,7 @@ class _List(list):
997
1355
  '''
998
1356
  # assert am
999
1357
  a0 = self._a0
1000
- _am = _1_0 / am
1358
+ _am = _1_over(am)
1001
1359
  for x in xs:
1002
1360
  yield (a0 - x) * _am
1003
1361
 
@@ -1008,7 +1366,7 @@ class _List(list):
1008
1366
  return max(fabs(x - a0) for x in self) / Tol
1009
1367
 
1010
1368
 
1011
- # class _Qot3(Fsum):
1369
+ # class _Qdot3(Fsum):
1012
1370
  # '''(INTERNAL) "Quarter" 3-dot product.
1013
1371
  # '''
1014
1372
  # def __init__(self, x, y, z, *unused): # PYCHOK signature
@@ -1018,15 +1376,12 @@ class _List(list):
1018
1376
  # return (self + a).fover(_4_0)
1019
1377
 
1020
1378
 
1021
- class _Qot3(list):
1379
+ class _Qdot3(list):
1022
1380
  '''(INTERNAL) "Quarter" 3-dot product.
1023
1381
  '''
1024
1382
  def __init__(self, x, y, z, *unused): # PYCHOK signature
1025
- try:
1026
- D = Fdot_(x, y, y, z, z, x, f2product=True)
1027
- except (OverflowError, TypeError, ValueError):
1028
- D = Fsum(x * y, y * z, z * x, nonfinites=True)
1029
- list.__init__(self, (0,) + D.partials) # NOT D.fsum2()!
1383
+ R = _Rdot(x, y, z, _0_0).partials
1384
+ list.__init__(self, (0,) + R) # NOT R.fsum2()!
1030
1385
 
1031
1386
  def __call__(self, a):
1032
1387
  try:
@@ -1040,31 +1395,35 @@ class _Qot3(list):
1040
1395
  return _fsum(self) # nonfinites=True
1041
1396
 
1042
1397
 
1043
- def _ab3(x, y, inst=None):
1044
- '''(INTERNAL) Yield Carlson 3-tuples C{(xn, yn, i)}.
1398
+ def _abm3(x, y, inst=None):
1399
+ '''(INTERNAL) Yield Carlson 3-tuples C{(xn, yn, m)}.
1045
1400
  '''
1046
- a, b = sqrt(x), sqrt(y)
1401
+ a, b = sqrt(x), (sqrt(y) if y != _1_0 else y)
1047
1402
  if b > a:
1048
1403
  b, a = a, b
1049
- for i in range(_TRIPS): # see
1050
- yield a, b, i # xi, yi, i
1404
+ yield a, -b, _0_5 # (x0 + y0)**2 * _0_5
1405
+ m = -1
1406
+ for i in range(_MAXIT):
1051
1407
  d = fabs(a - b)
1052
- if d <= (a * _TolRG0): # 3-4 trips
1408
+ if d <= (a * _TolRG0): # 2..4 trips
1053
1409
  break
1054
- t = a
1055
- a = favg(t, b)
1056
- b = sqrt(t * b)
1410
+ p = a
1411
+ a = favg(p, b)
1412
+ b = sqrt(p * b)
1413
+ yield a, b, m # (xi - yi)**2 * m
1414
+ m *= 2
1057
1415
  else: # PYCHOK no cover
1058
- raise _convergenceError(d / t, _TolRG0)
1416
+ raise _convergenceError(_MAXIT, _over(d, p), _TolRG0)
1059
1417
  if inst:
1060
1418
  inst._iteration += i
1419
+ yield a, b, 0 # sentinel: m = 0
1061
1420
 
1062
1421
 
1063
- def _convergenceError(d, tol, **thresh):
1422
+ def _convergenceError(maxit, d, tol, **thresh):
1064
1423
  '''(INTERNAL) Format a no-convergence Error.
1065
1424
  '''
1066
1425
  t = Fmt.no_convergence(d, tol, **thresh)
1067
- return ValueError(t) # txt only
1426
+ return _ValueError(maxit=maxit, txt=t)
1068
1427
 
1069
1428
 
1070
1429
  def _deltaX(sn, cn, dn, cX, fX):
@@ -1076,7 +1435,7 @@ def _deltaX(sn, cn, dn, cX, fX):
1076
1435
 
1077
1436
  if _signBit(cn):
1078
1437
  sn, cn = neg_(sn, cn)
1079
- r = fX(sn, cn, dn) * PI_2 / cX
1438
+ r = fX(sn, cn, dn) * _over(PI_2, cX)
1080
1439
  return r - atan2(sn, cn)
1081
1440
 
1082
1441
  except Exception as X:
@@ -1084,6 +1443,28 @@ def _deltaX(sn, cn, dn, cX, fX):
1084
1443
  raise _ellipticError(n, sn, cn, dn, cause=X)
1085
1444
 
1086
1445
 
1446
+ def elliperim(a, b, *deg2_1):
1447
+ '''Compute the perimeter or an arc of an ellipse with semi-axes C{a} and C{b} using
1448
+ method L{Elliperim.e2k}, L{Elliperim.E2k} or L{Elliperim.arc}.
1449
+
1450
+ @arg deg2_1: Optional, arc end and start angle (C{degrees}), see method L{Elliperim.arc}.
1451
+
1452
+ @return: The perimeter or arc length (C{scalar}, same units as C{a} and C{b}).
1453
+ '''
1454
+ return Elliperim.arc(a, b, *deg2_1) if deg2_1 else Elliperim.e2k(a, b, Elliperim.E2k)
1455
+
1456
+
1457
+ def elliperim_(a, b, *rad2_1):
1458
+ '''Compute the perimeter or an arc of an ellipse with semi-axes C{a} and C{b} using
1459
+ method L{Elliperim.e2k}, L{Elliperim.E2k} or L{Elliperim.arc_}.
1460
+
1461
+ @arg rad2_1: Optional, arc end and start angle (C{radians}), see method L{Elliperim.arc_}.
1462
+
1463
+ @return: The perimeter or arc length (C{scalar}, same units as C{a} and C{b}).
1464
+ '''
1465
+ return Elliperim.arc_(a, b, *rad2_1) if rad2_1 else Elliperim.e2k(a, b, Elliperim.E2k)
1466
+
1467
+
1087
1468
  def _ellipticError(where, *args, **kwds_cause_txt):
1088
1469
  '''(INTERNAL) Format an L{EllipticError}.
1089
1470
  '''
@@ -1099,7 +1480,7 @@ def _ellipticError(where, *args, **kwds_cause_txt):
1099
1480
  return EllipticError(u, cause=x, txt=t)
1100
1481
 
1101
1482
 
1102
- def _Horner(S, e1, E2, E3, E4, E5, over):
1483
+ def _Hsum(S, e1, E2, E3, E4, E5, over):
1103
1484
  '''(INTERNAL) Horner-like form for C{_RD} and C{_RJ} below.
1104
1485
  '''
1105
1486
  E22 = E2**2
@@ -1108,16 +1489,17 @@ def _Horner(S, e1, E2, E3, E4, E5, over):
1108
1489
  # + 3*E5/26 - E2**3/16 + 3*E3**2/40 + 3*E2*E4/20
1109
1490
  # + 45*E2**2*E3/272 - 9*(E3*E4+E2*E5)/68)
1110
1491
  # converted to Horner-like form ...
1111
- e = e1 * 4084080
1112
- S *= e
1113
- S += Fsum(-E2 * 540540, 471240).fmul(E5)
1114
- S += Fsum( E2 * 612612, -E3 * 540540, -556920).fmul(E4)
1115
- S += Fsum(-E2 * 706860, E22 * 675675, E3 * 306306, 680680).fmul(E3)
1116
- S += Fsum( E2 * 417690, -E22 * 255255, -875160).fmul(E2)
1117
- S += 4084080
1118
- if over != _1_0:
1119
- e *= over
1120
- return S.fdiv(e) # Fsum
1492
+ _1 = _1_0
1493
+ h = 4084080
1494
+ S *= e1
1495
+ S += _Hdot(E5, h, E2, -540540, _1, 471240)
1496
+ S += _Hdot(E4, h, E2, 612612, E3, -540540, _1, -556920)
1497
+ S += _Hdot(E3, h, E2, -706860, E22, 675675, E3, 306306, _1, 680680)
1498
+ S += _Hdot(E2, h, E2, 417690, E22, -255255, _1, -875160)
1499
+ S += _1
1500
+ if over:
1501
+ e1 *= over
1502
+ return S.fdiv(e1) # Fsum
1121
1503
 
1122
1504
 
1123
1505
  def _3over(a, b):
@@ -1148,7 +1530,7 @@ def _rC(x, y):
1148
1530
  raise _ellipticError(Elliptic.fRC, x, y)
1149
1531
 
1150
1532
 
1151
- def _RD(x, y, z, over=_1_0, inst=None):
1533
+ def _RD(x, y, z, over=_0_0, inst=None):
1152
1534
  '''(INTERNAL) Carlson, eqs 2.28 - 2.34.
1153
1535
  '''
1154
1536
  L = _List(x, y, z)
@@ -1157,21 +1539,32 @@ def _RD(x, y, z, over=_1_0, inst=None):
1157
1539
  if s:
1158
1540
  S += _over(_3_0, (r + z) * s[2] * m)
1159
1541
  z = L[2] # s[2] = sqrt(z)
1160
- x, y = L.rescale(-a * m, x, y)
1542
+ m *= a
1543
+ x, y = L.rescale(-m, x, y)
1161
1544
  xy = x * y
1162
1545
  z = (x + y) / _3_0
1163
1546
  z2 = z**2
1164
- return _Horner(S, sqrt(a) * a * m,
1165
- (xy - z2 * _6_0),
1166
- (xy * _3_0 - z2 * _8_0) * z,
1167
- (xy - z2) * z2 * _3_0,
1168
- (xy * z2 * z), over) # Fsum
1547
+ return _Hsum(S, sqrt(a) * m,
1548
+ (xy - z2 * _6_0),
1549
+ (xy * _3_0 - z2 * _8_0) * z,
1550
+ (xy - z2) * z2 * _3_0,
1551
+ (xy * z2 * z), over) # Fsum
1552
+
1553
+
1554
+ def _Rdot(x, y, z, start3):
1555
+ '''(INTERNAL) "Rotated" C{dot}.
1556
+ '''
1557
+ try:
1558
+ R = Fdot_(x, y, y, z, z, x, start3, _3_0, f2product=True)
1559
+ except (OverflowError, TypeError, ValueError):
1560
+ R = Fsum(x * y, y * z, z * x, start3 * _3_0, nonfinites=True)
1561
+ return R
1169
1562
 
1170
1563
 
1171
1564
  def _rF2(x, y, inst=None): # 2-arg version, z=0
1172
1565
  '''(INTERNAL) Carlson, eqs 2.36 - 2.38.
1173
1566
  '''
1174
- for a, b, _ in _ab3(x, y, inst): # PYCHOK yield
1567
+ for a, b, m in _abm3(x, y, inst): # PYCHOK yield
1175
1568
  pass
1176
1569
  return _over(PI, a + b) # float
1177
1570
 
@@ -1192,24 +1585,22 @@ def _RF3(x, y, z, inst=None): # 3-arg version
1192
1585
  # (1 - E2/10 + E3/14 + E2**2/24 - 3*E2*E3/44
1193
1586
  # - 5*E2**3/208 + 3*E3**2/104 + E2**2*E3/16)
1194
1587
  # converted to Horner-like form ...
1195
- S = Fsum(e4 * 15015, e3 * 6930, e2 * -16380, 17160).fmul(e3)
1196
- S += Fsum(e4 * -5775, e2 * 10010, -24024).fmul(e2)
1197
- S += 240240
1198
- return S.fdiv(sqrt(a) * 240240) # Fsum
1588
+ _1 = _1_0
1589
+ h = 240240
1590
+ S = _Hdot(e3, h, e4, 15015, e3, 6930, e2, -16380, _1, 17160)
1591
+ S += _Hdot(e2, h, e4, -5775, e2, 10010, _1, -24024)
1592
+ S += _1
1593
+ return S.fdiv(sqrt(a)) # Fsum
1199
1594
 
1200
1595
 
1201
1596
  def _rG2(x, y, inst=None, PI_=PI_4): # 2-args
1202
1597
  '''(INTERNAL) Carlson, eqs 2.36 - 2.39.
1203
1598
  '''
1204
- m = 1
1205
- S = Fsum()
1206
- for a, b, i in _ab3(x, y, inst): # PYCHOK yield
1207
- if i:
1208
- S -= (a - b)**2 * m
1209
- m *= 2
1210
- else:
1211
- S += (a + b)**2 * _0_5
1212
- return S.fmul(PI_).fover(a + b) # float
1599
+ rs = [] # len 2..7, incl sentinel
1600
+ _r = rs.append
1601
+ for a, b, m in _abm3(x, y, inst): # PYCHOK yield
1602
+ _r((a - b)**2 * m)
1603
+ return _over(_fsum(rs) * PI_, a + b) # nonfinites=True, float
1213
1604
 
1214
1605
 
1215
1606
  def _rG3(x, y, z): # 3-arg version
@@ -1223,7 +1614,7 @@ def _rG3(x, y, z): # 3-arg version
1223
1614
  return R.fover(_2_0) # float
1224
1615
 
1225
1616
 
1226
- def _RJ(x, y, z, p, over=_1_0, inst=None):
1617
+ def _RJ(x, y, z, p, over=_0_0, inst=None):
1227
1618
  '''(INTERNAL) Carlson, eqs 2.17 - 2.25.
1228
1619
  '''
1229
1620
  def _xyzp(x, y, z, p):
@@ -1244,17 +1635,18 @@ def _RJ(x, y, z, p, over=_1_0, inst=None):
1244
1635
  S += r / (d * m)
1245
1636
  else: # PYCHOK no cover
1246
1637
  return NAN
1247
- x, y, z = L.rescale(a * m, x, y, z)
1248
- p = Fsum(x, y, z).fover(_N_2_0)
1249
- p2 = p**2
1250
- p3 = p2 * p
1251
- E2 = Fsum(x * y, x * z, y * z, -p2 * _3_0)
1252
- E2p = E2 * p
1253
- xyz = x * y * z
1254
- return _Horner(S.fmul(_6_0), sqrt(a) * a * m, E2,
1255
- Fsum(p3 * _4_0, xyz, E2p * _2_0),
1256
- Fsum(p3 * _3_0, E2p, xyz * _2_0).fmul(p),
1257
- xyz * p2, over) # Fsum
1638
+ m *= a
1639
+ x, y, z = L.rescale(m, x, y, z)
1640
+ p = neg(Fsum(x, y, z).fover(_2_0))
1641
+ p2 = p**2
1642
+ p3 = p2 * p
1643
+ E2 = _Rdot(x, y, z, -p2)
1644
+ E2p = E2 * p
1645
+ xyz = x * y * z
1646
+ return _Hsum(S.fmul(_6_0), sqrt(a) * m, E2,
1647
+ Fsum(p3 * _4_0, xyz, E2p, E2p),
1648
+ Fsum(p3 * _3_0, E2p, xyz, xyz).fmul(p),
1649
+ p2 * xyz, over) # Fsum
1258
1650
 
1259
1651
 
1260
1652
  class _RJfma(object):
@@ -1264,7 +1656,8 @@ class _RJfma(object):
1264
1656
  self._Rj = _RJ(*args)
1265
1657
 
1266
1658
  def ma(self, b, c):
1267
- r = fma(self._Rj, b, c)
1659
+ r = self._Rj._fma(b, c, nonfinites=True)
1660
+ # assert r is not self._Rj
1268
1661
  return float(r)
1269
1662
 
1270
1663
  # **) MIT License