Coverage for pygeodesy/elliptic.py: 97%
527 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-09-09 13:03 -0400
« prev ^ index » next coverage.py v7.6.1, created at 2025-09-09 13:03 -0400
2# -*- coding: utf-8 -*-
4u'''I{Karney}'s elliptic functions and integrals.
6Class L{Elliptic} transcoded from I{Charles Karney}'s C++ class U{EllipticFunction
7<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1EllipticFunction.html>}
8to pure Python, including symmetric integrals L{Elliptic.fRC}, L{Elliptic.fRD},
9L{Elliptic.fRF}, L{Elliptic.fRG} and L{Elliptic.fRJ} as C{static methods}.
11Python method names follow the C++ member functions, I{except}:
13 - member functions I{without arguments} are mapped to Python properties
14 prefixed with C{"c"}, for example C{E()} is property C{cE},
16 - member functions with 1 or 3 arguments are renamed to Python methods
17 starting with an C{"f"}, example C{E(psi)} to C{fE(psi)} and C{E(sn,
18 cn, dn)} to C{fE(sn, cn, dn)},
20 - other Python method names conventionally start with a lower-case
21 letter or an underscore if private.
23Following is a copy of I{Karney}'s U{EllipticFunction.hpp
24<https://GeographicLib.SourceForge.io/C++/doc/EllipticFunction_8hpp_source.html>}
25file C{Header}.
27Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2024)
28and licensed under the MIT/X11 License. For more information, see the
29U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
31B{Elliptic integrals and functions.}
33This provides the elliptic functions and integrals needed for
34C{Ellipsoid}, C{GeodesicExact}, and C{TransverseMercatorExact}. Two
35categories of function are provided:
37 - functions to compute U{symmetric elliptic integrals
38 <https://DLMF.NIST.gov/19.16.i>}
40 - methods to compute U{Legrendre's elliptic integrals
41 <https://DLMF.NIST.gov/19.2.ii>} and U{Jacobi elliptic
42 functions<https://DLMF.NIST.gov/22.2>}.
44In the latter case, an object is constructed giving the modulus
45C{k} (and optionally the parameter C{alpha}). The modulus (and
46parameter) are always passed as squares which allows C{k} to be
47pure imaginary. (Confusingly, Abramowitz and Stegun call C{m = k**2}
48the "parameter" and C{n = alpha**2} the "characteristic".)
50In geodesic applications, it is convenient to separate the incomplete
51integrals into secular and periodic components, e.g.
53I{C{E(phi, k) = (2 E(k) / pi) [ phi + delta E(phi, k) ]}}
55where I{C{delta E(phi, k)}} is an odd periodic function with
56period I{C{pi}}.
58The computation of the elliptic integrals uses the algorithms given
59in U{B. C. Carlson, Computation of real or complex elliptic integrals
60<https://DOI.org/10.1007/BF02198293>} (also available U{here
61<https://ArXiv.org/pdf/math/9409227.pdf>}), Numerical Algorithms 10,
6213--26 (1995) with the additional optimizations given U{here
63<https://DLMF.NIST.gov/19.36.i>}.
65The computation of the Jacobi elliptic functions uses the algorithm
66given in U{R. Bulirsch, Numerical Calculation of Elliptic Integrals
67and Elliptic Functions<https://DOI.org/10.1007/BF01397975>},
68Numerische Mathematik 7, 78--90 (1965) or optionally the C{Jacobi
69amplitude} in method L{Elliptic.sncndn<pygeodesy.Elliptic.sncndn>}.
71The notation follows U{NIST Digital Library of Mathematical Functions
72<https://DLMF.NIST.gov>} chapters U{19<https://DLMF.NIST.gov/19>} and
73U{22<https://DLMF.NIST.gov/22>}.
74'''
75# make sure int/int division yields float quotient, see .basics
76from __future__ import division as _; del _ # noqa: E702 ;
78from pygeodesy.basics import copysign0, map2, neg, neg_
79from 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
84from pygeodesy.constants import _EPSjam as _TolJAM # PYCHOK used!
85# from pygeodesy.errors import _ValueError # from .fsums
86from pygeodesy.fmath import favg, Fdot_, fma, hypot1, zqrt
87from pygeodesy.fsums import Fsum, _fsum, _ValueError
88from pygeodesy.internals import _Enum, typename
89from pygeodesy.interns import NN, _delta_, _DOT_, _f_, _invalid_, \
90 _invokation_, _negative_, _SPACE_
91from pygeodesy.karney import _K_2_0, _norm180, _signBit, _sincos2
92# from pygeodesy.lazily import _ALL_LAZY # from .named
93from pygeodesy.named import _Named, _NamedTuple, _ALL_LAZY, Fmt, unstr
94from pygeodesy.props import _allPropertiesOf_n, Property_RO, _update_all
95# from pygeodesy.streprs import Fmt, unstr # from .named
96from pygeodesy.units import Scalar, Scalar_
97from pygeodesy.utily import atan2 # sincos2 as _sincos2
99from math import asin, asinh, atan, ceil, cosh, fabs, floor, radians, \
100 sin, sinh, sqrt, tan, tanh # tan as _tan
102__all__ = _ALL_LAZY.elliptic
103__version__ = '25.09.04'
105_TolRD = zqrt(EPS * 0.002)
106_TolRF = zqrt(EPS * 0.030)
107_TolRG0 = _TolJAC * 2.7
108_TRIPS = 28 # Max depth, 6-18 might be sufficient
111class _Cs(_Enum):
112 '''(INTERAL) Complete Integrals cache.
113 '''
114 pass
117class Elliptic(_Named):
118 '''Elliptic integrals and functions.
120 @see: I{Karney}'s U{Detailed Description<https://GeographicLib.SourceForge.io/
121 C++/doc/classGeographicLib_1_1EllipticFunction.html#details>}.
122 '''
123# _alpha2 = 0
124# _alphap2 = 0
125# _eps = EPS
126# _k2 = 0
127# _kp2 = 0
129 def __init__(self, k2=0, alpha2=0, kp2=None, alphap2=None, **name):
130 '''Constructor, specifying the C{modulus} and C{parameter}.
132 @kwarg name: Optional C{B{name}=NN} (C{str}).
134 @see: Method L{Elliptic.reset} for further details.
136 @note: If only elliptic integrals of the first and second kinds
137 are needed, use C{B{alpha2}=0}, the default value. In
138 that case, we have C{Π(φ, 0, k) = F(φ, k), G(φ, 0, k) =
139 E(φ, k)} and C{H(φ, 0, k) = F(φ, k) - D(φ, k)}.
140 '''
141 if name:
142 self.name = name
143 self._reset(k2, alpha2, kp2, alphap2)
145 @Property_RO
146 def alpha2(self):
147 '''Get α^2, the square of the parameter (C{float}).
148 '''
149 return self._alpha2
151 @Property_RO
152 def alphap2(self):
153 '''Get α'^2, the square of the complementary parameter (C{float}).
154 '''
155 return self._alphap2
157 @Property_RO
158 def _b4(self):
159 '''(INTERNAL) Get Bulirsch' 4-tuple C{(c, d, cd, mn)}.
160 '''
161 d, b = 0, self.kp2 # note, kp2 >= 0 always here
162 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))
170 c = favg(a, b)
171 r = fabs(a - b)
172 if r <= (a * _TolJAC): # 6 trips, quadratic
173 self._iteration += i
174 break
175 t = a
176 b *= a
177 a = c
178 else: # PYCHOK no cover
179 raise _convergenceError(r / t, _TolJAC)
180 cd = (c * d) if d else c
181 return c, d, cd, tuple(reversed(ab))
183 @Property_RO
184 def cD(self):
185 '''Get Jahnke's complete integral C{D(k)} (C{float}),
186 U{defined<https://DLMF.NIST.gov/19.2.E6>}.
187 '''
188 return self._cDEKEeps.cD
190 @Property_RO
191 def _cDEKEeps(self):
192 '''(INTERNAL) Get the complete integrals D, E, K, KE and C{eps}.
193 '''
194 k2, kp2 = self.k2, self.kp2
195 if k2:
196 if kp2:
197 try:
198 # self._iteration = 0
199 # D(k) = (K(k) - E(k))/k2, Carlson eq.4.3
200 # <https://DLMF.NIST.gov/19.25.E1>
201 D = _RD(_0_0, kp2, _1_0, _3_0, self)
202 cD = float(D)
203 # Complete elliptic integral E(k), Carlson eq. 4.2
204 # <https://DLMF.NIST.gov/19.25.E1>
205 cE = _rG2(kp2, _1_0, self, PI_=PI_2)
206 # Complete elliptic integral K(k), Carlson eq. 4.1
207 # <https://DLMF.NIST.gov/19.25.E1>
208 cK = _rF2(kp2, _1_0, self)
209 cKE = float(D.fmul(k2))
210 eps = k2 / (sqrt(kp2) + _1_0)**2
212 except Exception as X:
213 raise _ellipticError(self._cDEKEeps, k2=k2, kp2=kp2, cause=X)
214 else:
215 cD = cK = cKE = INF
216 cE = _1_0
217 eps = k2
218 else:
219 cD = PI_4
220 cE = cK = PI_2
221 cKE = _0_0 # k2 * cD
222 eps = EPS
224 return _Cs(cD=cD, cE=cE, cK=cK, cKE=cKE, eps=eps)
226 @Property_RO
227 def cE(self):
228 '''Get the complete integral of the second kind C{E(k)}
229 (C{float}), U{defined<https://DLMF.NIST.gov/19.2.E5>}.
230 '''
231 return self._cDEKEeps.cE
233 @Property_RO
234 def cG(self):
235 '''Get Legendre's complete geodesic longitude integral
236 C{G(α^2, k)} (C{float}).
237 '''
238 return self._cGHPi.cG
240 @Property_RO
241 def _cGHPi(self):
242 '''(INTERNAL) Get the complete integrals G, H and Pi.
243 '''
244 alpha2, alphap2, kp2 = self.alpha2, self.alphap2, self.kp2
245 try:
246 # self._iteration = 0
247 if alpha2:
248 if alphap2:
249 if kp2: # <https://DLMF.NIST.gov/19.25.E2>
250 cK = self.cK
251 Rj = _RJfma(_0_0, kp2, _1_0, alphap2, _3_0, self)
252 cG = Rj.ma(alpha2 - self.k2, cK) # G(alpha2, k)
253 cH = -Rj.ma(alphap2, -cK) # H(alpha2, k)
254 cPi = Rj.ma(alpha2, cK) # Pi(alpha2, k)
255 else:
256 cG = cH = _rC(_1_0, alphap2)
257 cPi = INF # XXX or NAN?
258 else:
259 cG = cH = cPi = INF # XXX or NAN?
260 else:
261 cG, cPi = self.cE, self.cK
262 # H = K - D but this involves large cancellations if k2 is near 1.
263 # So write (for alpha2 = 0)
264 # H = int(cos(phi)**2 / sqrt(1-k2 * sin(phi)**2), phi, 0, pi/2)
265 # = 1 / sqrt(1-k2) * int(sin(phi)**2 / sqrt(1-k2/kp2 * sin(phi)**2,...)
266 # = 1 / kp * D(i * k/kp)
267 # and use D(k) = RD(0, kp2, 1) / 3, so
268 # H = 1/kp * RD(0, 1/kp2, 1) / 3
269 # = kp2 * RD(0, 1, kp2) / 3
270 # using <https://DLMF.NIST.gov/19.20.E18>. Equivalently
271 # RF(x, 1) - RD(0, x, 1) / 3 = x * RD(0, 1, x) / 3 for x > 0
272 # For k2 = 1 and alpha2 = 0, we have
273 # H = int(cos(phi),...) = 1
274 cH = float(_RD(_0_0, _1_0, kp2, _3_0 / kp2, self)) if kp2 else _1_0
276 except Exception as X:
277 raise _ellipticError(self._cGHPi, kp2=kp2, alpha2 =alpha2,
278 alphap2=alphap2, cause=X)
279 return _Cs(cG=cG, cH=cH, cPi=cPi)
281 @Property_RO
282 def cH(self):
283 '''Get Cayley's complete geodesic longitude difference integral
284 C{H(α^2, k)} (C{float}).
285 '''
286 return self._cGHPi.cH
288 @Property_RO
289 def cK(self):
290 '''Get the complete integral of the first kind C{K(k)}
291 (C{float}), U{defined<https://DLMF.NIST.gov/19.2.E4>}.
292 '''
293 return self._cDEKEeps.cK
295 @Property_RO
296 def cKE(self):
297 '''Get the difference between the complete integrals of the
298 first and second kinds, C{K(k) − E(k)} (C{float}).
299 '''
300 return self._cDEKEeps.cKE
302 @Property_RO
303 def cPi(self):
304 '''Get the complete integral of the third kind C{Pi(α^2, k)}
305 (C{float}), U{defined<https://DLMF.NIST.gov/19.2.E7>}.
306 '''
307 return self._cGHPi.cPi
309 def deltaD(self, sn, cn, dn):
310 '''Jahnke's periodic incomplete elliptic integral.
312 @arg sn: sin(φ).
313 @arg cn: cos(φ).
314 @arg dn: sqrt(1 − k2 * sin(2φ)).
316 @return: Periodic function π D(φ, k) / (2 D(k)) - φ (C{float}).
318 @raise EllipticError: Invalid invokation or no convergence.
319 '''
320 return _deltaX(sn, cn, dn, self.cD, self.fD)
322 def deltaE(self, sn, cn, dn):
323 '''The periodic incomplete integral of the second kind.
325 @arg sn: sin(φ).
326 @arg cn: cos(φ).
327 @arg dn: sqrt(1 − k2 * sin(2φ)).
329 @return: Periodic function π E(φ, k) / (2 E(k)) - φ (C{float}).
331 @raise EllipticError: Invalid invokation or no convergence.
332 '''
333 return _deltaX(sn, cn, dn, self.cE, self.fE)
335 def deltaEinv(self, stau, ctau):
336 '''The periodic inverse of the incomplete integral of the second kind.
338 @arg stau: sin(τ)
339 @arg ctau: cos(τ)
341 @return: Periodic function E^−1(τ (2 E(k)/π), k) - τ (C{float}).
343 @raise EllipticError: No convergence.
344 '''
345 try:
346 if _signBit(ctau): # pi periodic
347 stau, ctau = neg_(stau, ctau)
348 t = atan2(stau, ctau)
349 return self._Einv(t * self.cE / PI_2) - t
351 except Exception as X:
352 raise _ellipticError(self.deltaEinv, stau, ctau, cause=X)
354 def deltaF(self, sn, cn, dn):
355 '''The periodic incomplete integral of the first kind.
357 @arg sn: sin(φ).
358 @arg cn: cos(φ).
359 @arg dn: sqrt(1 − k2 * sin(2φ)).
361 @return: Periodic function π F(φ, k) / (2 K(k)) - φ (C{float}).
363 @raise EllipticError: Invalid invokation or no convergence.
364 '''
365 return _deltaX(sn, cn, dn, self.cK, self.fF)
367 def deltaG(self, sn, cn, dn):
368 '''Legendre's periodic geodesic longitude integral.
370 @arg sn: sin(φ).
371 @arg cn: cos(φ).
372 @arg dn: sqrt(1 − k2 * sin(2φ)).
374 @return: Periodic function π G(φ, k) / (2 G(k)) - φ (C{float}).
376 @raise EllipticError: Invalid invokation or no convergence.
377 '''
378 return _deltaX(sn, cn, dn, self.cG, self.fG)
380 def deltaH(self, sn, cn, dn):
381 '''Cayley's periodic geodesic longitude difference integral.
383 @arg sn: sin(φ).
384 @arg cn: cos(φ).
385 @arg dn: sqrt(1 − k2 * sin(2φ)).
387 @return: Periodic function π H(φ, k) / (2 H(k)) - φ (C{float}).
389 @raise EllipticError: Invalid invokation or no convergence.
390 '''
391 return _deltaX(sn, cn, dn, self.cH, self.fH)
393 def deltaPi(self, sn, cn, dn):
394 '''The periodic incomplete integral of the third kind.
396 @arg sn: sin(φ).
397 @arg cn: cos(φ).
398 @arg dn: sqrt(1 − k2 * sin(2φ)).
400 @return: Periodic function π Π(φ, α2, k) / (2 Π(α2, k)) - φ
401 (C{float}).
403 @raise EllipticError: Invalid invokation or no convergence.
404 '''
405 return _deltaX(sn, cn, dn, self.cPi, self.fPi)
407 def _Einv(self, x):
408 '''(INTERNAL) Helper for C{.deltaEinv} and C{.fEinv}.
409 '''
410 E2 = self.cE * _2_0
411 n = floor(x / E2 + _0_5)
412 r = x - E2 * n # r in [-cE, cE)
413 # linear approximation
414 phi = PI * r / E2 # phi in [-PI_2, PI_2)
415 Phi = Fsum(phi)
416 # first order correction
417 phi = Phi.fsum_(self.eps * sin(phi * _2_0) / _N_2_0)
418 # self._iteration = 0
419 # For kp2 close to zero use asin(r / cE) or J. P. Boyd,
420 # Applied Math. and Computation 218, 7005-7013 (2012)
421 # <https://DOI.org/10.1016/j.amc.2011.12.021>
422 _Phi2 = Phi.fsum2f_ # aggregate
423 for i in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC
424 sn, cn, dn = self._sncndn3(phi)
425 if dn:
426 sn = self.fE(sn, cn, dn)
427 phi, d = _Phi2((r - sn) / dn)
428 else: # PYCHOK no cover
429 d = _0_0 # XXX or continue?
430 if fabs(d) < _TolJAC: # 3-4 trips
431 self._iteration += i
432 break
433 else: # PYCHOK no cover
434 raise _convergenceError(d, _TolJAC)
435 return Phi.fsum_(n * PI) if n else phi
437 @Property_RO
438 def eps(self):
439 '''Get epsilon (C{float}).
440 '''
441 return self._cDEKEeps.eps
443 def fD(self, phi_or_sn, cn=None, dn=None):
444 '''Jahnke's incomplete elliptic integral in terms of
445 Jacobi elliptic functions.
447 @arg phi_or_sn: φ or sin(φ).
448 @kwarg cn: C{None} or cos(φ).
449 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)).
451 @return: D(φ, k) as though φ ∈ (−π, π] (C{float}),
452 U{defined<https://DLMF.NIST.gov/19.2.E4>}.
454 @raise EllipticError: Invalid invokation or no convergence.
455 '''
456 def _fD(sn, cn, dn):
457 r = fabs(sn)**3
458 if r:
459 r = float(_RD(cn**2, dn**2, _1_0, _3_0 / r, self))
460 return r
462 return self._fXf(phi_or_sn, cn, dn, self.cD,
463 self.deltaD, _fD)
465 def fDelta(self, sn, cn):
466 '''The C{Delta} amplitude function.
468 @arg sn: sin(φ).
469 @arg cn: cos(φ).
471 @return: sqrt(1 − k2 * sin(2φ)) (C{float}).
472 '''
473 try:
474 k2 = self.k2
475 s = (self.kp2 + cn**2 * k2) if k2 > 0 else (
476 (_1_0 - sn**2 * k2) if k2 < 0 else self.kp2)
477 return sqrt(s) if s else _0_0
479 except Exception as X:
480 raise _ellipticError(self.fDelta, sn, cn, k2=k2, cause=X)
482 def fE(self, phi_or_sn, cn=None, dn=None):
483 '''The incomplete integral of the second kind in terms of
484 Jacobi elliptic functions.
486 @arg phi_or_sn: φ or sin(φ).
487 @kwarg cn: C{None} or cos(φ).
488 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)).
490 @return: E(φ, k) as though φ ∈ (−π, π] (C{float}),
491 U{defined<https://DLMF.NIST.gov/19.2.E5>}.
493 @raise EllipticError: Invalid invokation or no convergence.
494 '''
495 def _fE(sn, cn, dn):
496 '''(INTERNAL) Core of C{.fE}.
497 '''
498 if sn:
499 cn2, dn2 = cn**2, dn**2
500 kp2, k2 = self.kp2, self.k2
501 if k2 <= 0: # Carlson, eq. 4.6, <https://DLMF.NIST.gov/19.25.E9>
502 Ei = _RF3(cn2, dn2, _1_0, self)
503 if k2:
504 Ei -= _RD(cn2, dn2, _1_0, _3over(k2, sn**2), self)
505 elif kp2 >= 0: # k2 > 0, <https://DLMF.NIST.gov/19.25.E10>
506 Ei = _over(k2 * fabs(cn), dn) # float
507 if kp2:
508 Ei += (_RD( cn2, _1_0, dn2, _3over(k2, sn**2), self) +
509 _RF3(cn2, dn2, _1_0, self)) * kp2
510 else: # PYCHOK no cover
511 Ei = _over(dn, fabs(cn)) # <https://DLMF.NIST.gov/19.25.E11>
512 Ei -= _RD(dn2, _1_0, cn2, _3over(kp2, sn**2), self)
513 Ei *= fabs(sn)
514 else: # PYCHOK no cover
515 Ei = _0_0
516 return float(Ei)
518 return self._fXf(phi_or_sn, cn, dn, self.cE,
519 self.deltaE, _fE,
520 k2_0=self.k2==0)
522 def fEd(self, deg):
523 '''The incomplete integral of the second kind with
524 the argument given in C{degrees}.
526 @arg deg: Angle (C{degrees}).
528 @return: E(π B{C{deg}} / 180, k) (C{float}).
530 @raise EllipticError: No convergence.
531 '''
532 if _K_2_0:
533 e = round((deg - _norm180(deg)) / _360_0)
534 elif fabs(deg) < _180_0:
535 e = _0_0
536 else:
537 e = ceil(deg / _360_0 - _0_5)
538 deg -= e * _360_0
539 e *= self.cE * _4_0
540 return self.fE(radians(deg)) + e
542 def fEinv(self, x):
543 '''The inverse of the incomplete integral of the second kind.
545 @arg x: Argument (C{float}).
547 @return: φ = 1 / E(B{C{x}}, k), such that E(φ, k) = B{C{x}}
548 (C{float}).
550 @raise EllipticError: No convergence.
551 '''
552 try:
553 return self._Einv(x)
554 except Exception as X:
555 raise _ellipticError(self.fEinv, x, cause=X)
557 def fF(self, phi_or_sn, cn=None, dn=None):
558 '''The incomplete integral of the first kind in terms of
559 Jacobi elliptic functions.
561 @arg phi_or_sn: φ or sin(φ).
562 @kwarg cn: C{None} or cos(φ).
563 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)).
565 @return: F(φ, k) as though φ ∈ (−π, π] (C{float}),
566 U{defined<https://DLMF.NIST.gov/19.2.E4>}.
568 @raise EllipticError: Invalid invokation or no convergence.
569 '''
570 def _fF(sn, cn, dn):
571 r = fabs(sn)
572 if r:
573 r = float(_RF3(cn**2, dn**2, _1_0, self).fmul(r))
574 return r
576 return self._fXf(phi_or_sn, cn, dn, self.cK,
577 self.deltaF, _fF,
578 k2_0=self.k2==0, kp2_0=self.kp2==0)
580 def fG(self, phi_or_sn, cn=None, dn=None):
581 '''Legendre's geodesic longitude integral in terms of
582 Jacobi elliptic functions.
584 @arg phi_or_sn: φ or sin(φ).
585 @kwarg cn: C{None} or cos(φ).
586 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)).
588 @return: G(φ, k) as though φ ∈ (−π, π] (C{float}).
590 @raise EllipticError: Invalid invokation or no convergence.
592 @note: Legendre expresses the longitude of a point on the
593 geodesic in terms of this combination of elliptic
594 integrals in U{Exercices de Calcul Intégral, Vol 1
595 (1811), p 181<https://Books.Google.com/books?id=
596 riIOAAAAQAAJ&pg=PA181>}.
598 @see: U{Geodesics in terms of elliptic integrals<https://
599 GeographicLib.SourceForge.io/C++/doc/geodesic.html#geodellip>}
600 for the expression for the longitude in terms of this function.
601 '''
602 return self._fXa(phi_or_sn, cn, dn, self.alpha2 - self.k2,
603 self.cG, self.deltaG)
605 def fH(self, phi_or_sn, cn=None, dn=None):
606 '''Cayley's geodesic longitude difference integral in terms of
607 Jacobi elliptic functions.
609 @arg phi_or_sn: φ or sin(φ).
610 @kwarg cn: C{None} or cos(φ).
611 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)).
613 @return: H(φ, k) as though φ ∈ (−π, π] (C{float}).
615 @raise EllipticError: Invalid invokation or no convergence.
617 @note: Cayley expresses the longitude difference of a point
618 on the geodesic in terms of this combination of
619 elliptic integrals in U{Phil. Mag. B{40} (1870), p 333
620 <https://Books.Google.com/books?id=Zk0wAAAAIAAJ&pg=PA333>}.
622 @see: U{Geodesics in terms of elliptic integrals<https://
623 GeographicLib.SourceForge.io/C++/doc/geodesic.html#geodellip>}
624 for the expression for the longitude in terms of this function.
625 '''
626 return self._fXa(phi_or_sn, cn, dn, -self.alphap2,
627 self.cH, self.deltaH)
629 def fPi(self, phi_or_sn, cn=None, dn=None):
630 '''The incomplete integral of the third kind in terms of
631 Jacobi elliptic functions.
633 @arg phi_or_sn: φ or sin(φ).
634 @kwarg cn: C{None} or cos(φ).
635 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)).
637 @return: Π(φ, α2, k) as though φ ∈ (−π, π] (C{float}).
639 @raise EllipticError: Invalid invokation or no convergence.
640 '''
641 if dn is None and cn is not None: # and isscalar(phi_or_sn)
642 dn = self.fDelta(phi_or_sn, cn) # in .triaxial
643 return self._fXa(phi_or_sn, cn, dn, self.alpha2,
644 self.cPi, self.deltaPi)
646 def _fXa(self, phi_or_sn, cn, dn, aX, cX, deltaX):
647 '''(INTERNAL) Helper for C{.fG}, C{.fH} and C{.fPi}.
648 '''
649 def _fX(sn, cn, dn):
650 if sn:
651 cn2, dn2 = cn**2, dn**2
652 R = _RF3(cn2, dn2, _1_0, self)
653 if aX:
654 sn2 = sn**2
655 p = sn2 * self.alphap2 + cn2
656 R += _RJ(cn2, dn2, _1_0, p, _3over(aX, sn2), self)
657 R *= fabs(sn)
658 else: # PYCHOK no cover
659 R = _0_0
660 return float(R)
662 return self._fXf(phi_or_sn, cn, dn, cX, deltaX, _fX)
664 def _fXf(self, phi_or_sn, cn, dn, cX, deltaX, fX, k2_0=False, kp2_0=False):
665 '''(INTERNAL) Helper for C{.fD}, C{.fE}, C{.fF} and C{._fXa}.
666 '''
667 # self._iteration = 0 # aggregate
668 phi = sn = phi_or_sn
669 if cn is dn is None: # fX(phi) call
670 if k2_0: # C++ version 2.4
671 return phi
672 elif kp2_0:
673 return asinh(tan(phi))
674 sn, cn, dn = self._sncndn3(phi)
675 if fabs(phi) >= PI:
676 return (deltaX(sn, cn, dn) + phi) * cX / PI_2
677 # fall through
678 elif cn is None or dn is None:
679 n = NN(_f_, typename(deltaX)[5:])
680 raise _ellipticError(n, sn, cn, dn)
682 if _signBit(cn): # enforce usual trig-like symmetries
683 xi = cX * _2_0 - fX(sn, cn, dn)
684 else:
685 xi = fX(sn, cn, dn) if cn > 0 else cX
686 return copysign0(xi, sn)
688 def _jam(self, x):
689 '''Jacobi amplitude function.
691 @return: C{phi} (C{float}).
692 '''
693 # implements DLMF Sec 22.20(ii), see also U{Sala
694 # (1989)<https://doi.org/10.1137/0520100>}, Sec 5
695 if self.k2:
696 if self.kp2:
697 r, ac = self._jamac2
698 x *= r # phi
699 for a, c in ac:
700 p = x
701 x = favg(asin(c * sin(x) / a), x)
702 if self.k2 < 0: # Sala Eq. 5.8
703 x = p - x
704 else: # PYCHOK no cover
705 x = atan(sinh(x)) # gd(x)
706 return x
708 @Property_RO
709 def _jamac2(self):
710 '''(INTERNAL) Get Jacobi amplitude 2-tuple C{(r, ac)}.
711 '''
712 a = r = _1_0
713 b, c = self.kp2, self.k2
714 # assert b and c
715 if c < 0: # Sala Eq. 5.8
716 r = sqrt(b)
717 b = _1_0 / b
718# c *= b # unused
719 ac = [] # [(a, sqrt(c))] unused
720 for i in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC
721 b = sqrt(a * b)
722 c = favg(a, -b)
723 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
727 break
728 else: # PYCHOK no cover
729 raise _convergenceError(c / a, _TolJAM)
730 r *= a * pow(2, i) # 2**len(ac)
731 return r, tuple(reversed(ac))
733 @Property_RO
734 def k2(self):
735 '''Get k^2, the square of the modulus (C{float}).
736 '''
737 return self._k2
739 @Property_RO
740 def kp2(self):
741 '''Get k'^2, the square of the complementary modulus (C{float}).
742 '''
743 return self._kp2
745 def reset(self, k2=0, alpha2=0, kp2=None, alphap2=None): # MCCABE 13
746 '''Reset the modulus, parameter and the complementaries.
748 @kwarg k2: Modulus squared (C{float}, NINF <= k^2 <= 1).
749 @kwarg alpha2: Parameter squared (C{float}, NINF <= α^2 <= 1).
750 @kwarg kp2: Complementary modulus squared (C{float}, k'^2 >= 0).
751 @kwarg alphap2: Complementary parameter squared (C{float}, α'^2 >= 0).
753 @raise EllipticError: Invalid B{C{k2}}, B{C{alpha2}}, B{C{kp2}}
754 or B{C{alphap2}}.
756 @note: The arguments must satisfy C{B{k2} + B{kp2} = 1} and
757 C{B{alpha2} + B{alphap2} = 1}. No checking is done
758 that these conditions are met to enable accuracy to be
759 maintained, e.g., when C{k} is very close to unity.
760 '''
761 _update_all(self, Base=Property_RO, needed=4)
762 self._reset(k2, alpha2, kp2, alphap2)
764 def _reset(self, k2, alpha2, kp2, alphap2):
765 '''(INITERNAL) Reset this elliptic.
766 '''
767 def _1p2(kp2, k2):
768 return (_1_0 - k2) if kp2 is None else kp2
770 def _S(**kwds):
771 return Scalar_(Error=EllipticError, **kwds)
773 self._k2 = _S(k2 = k2, low=None, high=_1_0)
774 self._kp2 = _S(kp2=_1p2(kp2, k2)) # low=_0_0
776 self._alpha2 = _S(alpha2 = alpha2, low=None, high=_1_0)
777 self._alphap2 = _S(alphap2=_1p2(alphap2, alpha2)) # low=_0_0
779 # Values of complete elliptic integrals for k = 0,1 and alpha = 0,1
780 # K E D
781 # k = 0: pi/2 pi/2 pi/4
782 # k = 1: inf 1 inf
783 # Pi G H
784 # k = 0, alpha = 0: pi/2 pi/2 pi/4
785 # k = 1, alpha = 0: inf 1 1
786 # k = 0, alpha = 1: inf inf pi/2
787 # k = 1, alpha = 1: inf inf inf
788 #
789 # G(0, k) = Pi(0, k) = H(1, k) = E(k)
790 # H(0, k) = K(k) - D(k)
791 # Pi(alpha2, 0) = G(alpha2, 0) = pi / (2 * sqrt(1 - alpha2))
792 # H( alpha2, 0) = pi / (2 * (sqrt(1 - alpha2) + 1))
793 # Pi(alpha2, 1) = inf
794 # G( alpha2, 1) = H(alpha2, 1) = RC(1, alphap2)
796 self._iteration = 0
798 def sncndn(self, x, jam=False):
799 '''The Jacobi amplitude and elliptic function.
801 @arg x: The argument (C{float}).
802 @kwarg jam: If C{True}, use the Jacobi amplitude otherwise
803 Bulirsch' function (C{bool}).
805 @return: An L{Elliptic3Tuple}C{(sn, cn, dn)} with C{*n(B{x}, k)}.
807 @raise EllipticError: No convergence.
808 '''
809 i = self._iteration
810 try:
811 if self.kp2:
812 if jam: # Jacobi amplitude, C++ v 2.4
813 sn, cn, dn = self._sncndn3(self._jam(x))
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
835 else:
836 sn = tanh(x) # accurate for large abs(x)
837 cn = dn = _1_0 / cosh(x)
839 except Exception as X:
840 raise _ellipticError(self.sncndn, x, kp2=self.kp2, cause=X)
842 return Elliptic3Tuple(sn, cn, dn, iteration=self._iteration - i)
844 def _sncndn3(self, phi):
845 '''(INTERNAL) Helper for C{.fEinv}, C{._fXf} and C{.sncndn}.
846 '''
847 sn, cn = _sincos2(phi)
848 return sn, cn, self.fDelta(sn, cn)
850 @staticmethod
851 def fRC(x, y):
852 '''Degenerate symmetric integral of the first kind C{RC(x, y)}.
854 @return: C{RC(x, y)}, equivalent to C{RF(x, y, y)}.
856 @see: U{C{RC} definition<https://DLMF.NIST.gov/19.2.E17>} and
857 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}.
858 '''
859 return _rC(x, y)
861 @staticmethod
862 def fRD(x, y, z, *over):
863 '''Degenerate symmetric integral of the third kind C{RD(x, y, z)}.
865 @return: C{RD(x, y, z) / over}, equivalent to C{RJ(x, y, z, z)
866 / over} with C{over} typically 3.
868 @see: U{C{RD} definition<https://DLMF.NIST.gov/19.16.E5>} and
869 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}.
870 '''
871 try:
872 return float(_RD(x, y, z, *over))
873 except Exception as X:
874 raise _ellipticError(Elliptic.fRD, x, y, z, *over, cause=X)
876 @staticmethod
877 def fRF(x, y, z=0):
878 '''Symmetric or complete symmetric integral of the first kind
879 C{RF(x, y, z)} respectively C{RF(x, y)}.
881 @return: C{RF(x, y, z)} or C{RF(x, y)} for missing or zero B{C{z}}.
883 @see: U{C{RF} definition<https://DLMF.NIST.gov/19.16.E1>} and
884 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}.
885 '''
886 try:
887 return float(_RF3(x, y, z)) if z else _rF2(x, y)
888 except Exception as X:
889 raise _ellipticError(Elliptic.fRF, x, y, z, cause=X)
891 @staticmethod
892 def fRG(x, y, z=0):
893 '''Symmetric or complete symmetric integral of the second kind
894 C{RG(x, y, z)} respectively C{RG(x, y)}.
896 @return: C{RG(x, y, z)} or C{RG(x, y)} for missing or zero B{C{z}}.
898 @see: U{C{RG} definition<https://DLMF.NIST.gov/19.16.E3>},
899 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>} and
900 U{RG<https://GeographicLib.SourceForge.io/C++/doc/
901 EllipticFunction_8cpp_source.html#l00096>} version 2.3.
902 '''
903 try:
904 return _rG2(x, y) if z == 0 else (
905 _rG2(z, x) if y == 0 else (
906 _rG2(y, z) if x == 0 else _rG3(x, y, z)))
907 except Exception as X:
908 t = _negative_ if min(x, y, z) < 0 else NN
909 raise _ellipticError(Elliptic.fRG, x, y, z, cause=X, txt=t)
911 @staticmethod
912 def fRJ(x, y, z, p): # *over
913 '''Symmetric integral of the third kind C{RJ(x, y, z, p)}.
915 @return: C{RJ(x, y, z, p)}.
917 @see: U{C{RJ} definition<https://DLMF.NIST.gov/19.16.E2>} and
918 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}.
919 '''
920 try:
921 return float(_RJ(x, y, z, p))
922 except Exception as X:
923 raise _ellipticError(Elliptic.fRJ, x, y, z, p, cause=X)
925 @staticmethod
926 def _RFRD(x, y, z, m): # in .auxilats.AuxDLat.DE, -.AuxLat.Rectifying
927 try: # float(RF(x, y, z) - RD(x, y, z, 3 / m))
928 R = _RF3(x, y, z)
929 if m:
930 R -= _RD(x, y, z, _3_0 / m)
931 except Exception as X:
932 raise _ellipticError(Elliptic._RFRD, x, y, z, m, cause=X)
933 return float(R)
935_allPropertiesOf_n(16, Elliptic) # # PYCHOK assert, see Elliptic.reset
938class EllipticError(_ValueError):
939 '''Elliptic function, integral, convergence or other L{Elliptic} issue.
940 '''
941 pass
944class Elliptic3Tuple(_NamedTuple):
945 '''3-Tuple C{(sn, cn, dn)} all C{scalar}.
946 '''
947 _Names_ = ('sn', 'cn', 'dn')
948 _Units_ = ( Scalar, Scalar, Scalar)
951class _List(list):
952 '''(INTERNAL) Helper for C{_RD}, C{_RF3} and C{_RJ}.
953 '''
954 _a0 = None
956 def __init__(self, *xyzp): # x, y, z [, p]
957 list.__init__(self, xyzp)
959 def a0(self, n):
960 '''Compute the initial C{a}.
961 '''
962 a = list(self)
963 m = n - len(a)
964 if m > 0:
965 a[-1] *= m + 1
966 self._a0 = a0 = _fsum(a) / n
967 return a0
969 def amrs4(self, y, Tol, inst=None):
970 '''Yield Carlson 4-tuples C{(An, mul, lam, s)} plus sentinel, with
971 C{lam = fdot(sqrt(x), ... (z))} and C{s = (sqrt(x), ... (p))}.
972 '''
973 L = self
974 a = L.a0(5 if y else 3)
975 t = L.threshold(Tol)
976 m = 1
977 for i in range(_TRIPS):
978 d = fabs(a * m)
979 if d > t: # 3-6 trips
980 break
981 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])
983 a = Q(a) # An = sum(An, *Q)) / 4
984 L[:] = map(Q, L) # x = sum(x, *Q) / 4, ...
985 if y: # yield only if used
986 r = float(Q) # lam = sum(Q)
987 yield a, m, r, s # L[2] is next z
988 m *= 4
989 else: # PYCHOK no cover
990 raise _convergenceError(d, t, thresh=True)
991 yield a, m, None, () # sentinel: same a, next m, no r and s
992 if inst:
993 inst._iteration += i
995 def rescale(self, am, *xs):
996 '''Rescale C{x}, C{y}, ...
997 '''
998 # assert am
999 a0 = self._a0
1000 _am = _1_0 / am
1001 for x in xs:
1002 yield (a0 - x) * _am
1004 def threshold(self, Tol):
1005 '''Get the convergence C{threshold}.
1006 '''
1007 a0 = self._a0
1008 return max(fabs(x - a0) for x in self) / Tol
1011# class _Qot3(Fsum):
1012# '''(INTERNAL) "Quarter" 3-dot product.
1013# '''
1014# def __init__(self, x, y, z, *unused): # PYCHOK signature
1015# Fsum.__init__(self, x * y, y * z, z * x)
1016#
1017# def __call__(self, a): # PYCHOK signature
1018# return (self + a).fover(_4_0)
1021class _Qot3(list):
1022 '''(INTERNAL) "Quarter" 3-dot product.
1023 '''
1024 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()!
1031 def __call__(self, a):
1032 try:
1033 self[0] = a
1034 q = float(self) * _0_25
1035 finally:
1036 self[0] = 0
1037 return q
1039 def __float__(self):
1040 return _fsum(self) # nonfinites=True
1043def _ab3(x, y, inst=None):
1044 '''(INTERNAL) Yield Carlson 3-tuples C{(xn, yn, i)}.
1045 '''
1046 a, b = sqrt(x), sqrt(y)
1047 if b > a:
1048 b, a = a, b
1049 for i in range(_TRIPS): # see
1050 yield a, b, i # xi, yi, i
1051 d = fabs(a - b)
1052 if d <= (a * _TolRG0): # 3-4 trips
1053 break
1054 t = a
1055 a = favg(t, b)
1056 b = sqrt(t * b)
1057 else: # PYCHOK no cover
1058 raise _convergenceError(d / t, _TolRG0)
1059 if inst:
1060 inst._iteration += i
1063def _convergenceError(d, tol, **thresh):
1064 '''(INTERNAL) Format a no-convergence Error.
1065 '''
1066 t = Fmt.no_convergence(d, tol, **thresh)
1067 return ValueError(t) # txt only
1070def _deltaX(sn, cn, dn, cX, fX):
1071 '''(INTERNAL) Helper for C{Elliptic.deltaD} thru C{.deltaPi}.
1072 '''
1073 try:
1074 if cn is None or dn is None:
1075 raise ValueError(_invalid_)
1077 if _signBit(cn):
1078 sn, cn = neg_(sn, cn)
1079 r = fX(sn, cn, dn) * PI_2 / cX
1080 return r - atan2(sn, cn)
1082 except Exception as X:
1083 n = NN(_delta_, typename(fX)[1:])
1084 raise _ellipticError(n, sn, cn, dn, cause=X)
1087def _ellipticError(where, *args, **kwds_cause_txt):
1088 '''(INTERNAL) Format an L{EllipticError}.
1089 '''
1090 def _x_t_kwds(cause=None, txt=NN, **kwds):
1091 return cause, txt, kwds
1093 x, t, kwds = _x_t_kwds(**kwds_cause_txt)
1095 n = typename(where, where)
1096 n = _DOT_(typename(Elliptic), n)
1097 n = _SPACE_(_invokation_, n)
1098 u = unstr(n, *args, **kwds)
1099 return EllipticError(u, cause=x, txt=t)
1102def _Horner(S, e1, E2, E3, E4, E5, over):
1103 '''(INTERNAL) Horner-like form for C{_RD} and C{_RJ} below.
1104 '''
1105 E22 = E2**2
1106 # Polynomial is <https://DLMF.NIST.gov/19.36.E2>
1107 # (1 - 3*E2/14 + E3/6 + 9*E2**2/88 - 3*E4/22 - 9*E2*E3/52
1108 # + 3*E5/26 - E2**3/16 + 3*E3**2/40 + 3*E2*E4/20
1109 # + 45*E2**2*E3/272 - 9*(E3*E4+E2*E5)/68)
1110 # 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
1123def _3over(a, b):
1124 '''(INTERNAL) Return C{3 / (a * b)}.
1125 '''
1126 return _over(_3_0, a * b)
1129def _rC(x, y):
1130 '''(INTERNAL) Defined only for C{y != 0} and C{x >= 0}.
1131 '''
1132 if x < y: # catch NaN
1133 # <https://DLMF.NIST.gov/19.2.E18>
1134 d = y - x
1135 r = atan(sqrt(d / x)) if x > 0 else PI_2
1136 elif x == y: # XXX d < EPS0? or EPS02 or _EPSmin
1137 d, r = y, _1_0
1138 elif y > 0: # <https://DLMF.NIST.gov/19.2.E19>
1139 d = x - y
1140 r = asinh(sqrt(d / y)) # atanh(sqrt((x - y) / x))
1141 elif y < 0: # <https://DLMF.NIST.gov/19.2.E20>
1142 d = x - y
1143 r = asinh(sqrt(-x / y)) # atanh(sqrt(x / (x - y)))
1144 else: # PYCHOK no cover
1145 d = 0 # y == 0
1146 if d > 0 and x >= 0:
1147 return r / sqrt(d) # float
1148 raise _ellipticError(Elliptic.fRC, x, y)
1151def _RD(x, y, z, over=_1_0, inst=None):
1152 '''(INTERNAL) Carlson, eqs 2.28 - 2.34.
1153 '''
1154 L = _List(x, y, z)
1155 S = Fsum()
1156 for a, m, r, s in L.amrs4(True, _TolRF, inst):
1157 if s:
1158 S += _over(_3_0, (r + z) * s[2] * m)
1159 z = L[2] # s[2] = sqrt(z)
1160 x, y = L.rescale(-a * m, x, y)
1161 xy = x * y
1162 z = (x + y) / _3_0
1163 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
1171def _rF2(x, y, inst=None): # 2-arg version, z=0
1172 '''(INTERNAL) Carlson, eqs 2.36 - 2.38.
1173 '''
1174 for a, b, _ in _ab3(x, y, inst): # PYCHOK yield
1175 pass
1176 return _over(PI, a + b) # float
1179def _RF3(x, y, z, inst=None): # 3-arg version
1180 '''(INTERNAL) Carlson, eqs 2.2 - 2.7.
1181 '''
1182 L = _List(x, y, z)
1183 for a, m, _, _ in L.amrs4(False, _TolRF, inst):
1184 pass
1185 x, y = L.rescale(a * m, x, y)
1186 z = neg(x + y)
1187 xy = x * y
1188 e2 = xy - z**2
1189 e3 = xy * z
1190 e4 = e2**2
1191 # Polynomial is <https://DLMF.NIST.gov/19.36.E1>
1192 # (1 - E2/10 + E3/14 + E2**2/24 - 3*E2*E3/44
1193 # - 5*E2**3/208 + 3*E3**2/104 + E2**2*E3/16)
1194 # 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
1201def _rG2(x, y, inst=None, PI_=PI_4): # 2-args
1202 '''(INTERNAL) Carlson, eqs 2.36 - 2.39.
1203 '''
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
1215def _rG3(x, y, z): # 3-arg version
1216 '''(INTERNAL) C{x}, C{y} and C{z} all non-zero, see C{.fRG}.
1217 '''
1218 R = _RF3(x, y, z) * z
1219 rd = (x - z) * (z - y) # - (y - z)
1220 if rd: # Carlson, eq 1.7
1221 R += _RD(x, y, z, _3_0 / rd)
1222 R += sqrt(x * y / z)
1223 return R.fover(_2_0) # float
1226def _RJ(x, y, z, p, over=_1_0, inst=None):
1227 '''(INTERNAL) Carlson, eqs 2.17 - 2.25.
1228 '''
1229 def _xyzp(x, y, z, p):
1230 return (x + p) * (y + p) * (z + p)
1232 L = _List(x, y, z, p)
1233 n = neg(_xyzp(x, y, z, -p))
1234 S = Fsum()
1235 for a, m, _, s in L.amrs4(True, _TolRD, inst):
1236 if s:
1237 d = _xyzp(*s)
1238 if d:
1239 if n:
1240 r = _rC(_1_0, (n / d**2 + _1_0))
1241 n = n / _64_0 # /= chokes PyChecker
1242 else:
1243 r = _1_0 # == _rC(_1_0, _1_0)
1244 S += r / (d * m)
1245 else: # PYCHOK no cover
1246 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
1260class _RJfma(object):
1261 '''(INTERNAL) Carlson, "fma"able.
1262 '''
1263 def __init__(self, *args):
1264 self._Rj = _RJ(*args)
1266 def ma(self, b, c):
1267 r = fma(self._Rj, b, c)
1268 return float(r)
1270# **) MIT License
1271#
1272# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
1273#
1274# Permission is hereby granted, free of charge, to any person obtaining a
1275# copy of this software and associated documentation files (the "Software"),
1276# to deal in the Software without restriction, including without limitation
1277# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1278# and/or sell copies of the Software, and to permit persons to whom the
1279# Software is furnished to do so, subject to the following conditions:
1280#
1281# The above copyright notice and this permission notice shall be included
1282# in all copies or substantial portions of the Software.
1283#
1284# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1285# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1286# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1287# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1288# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1289# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1290# OTHER DEALINGS IN THE SOFTWARE.