Coverage for pygeodesy/elliptic.py: 96%

482 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-05-06 16:50 -0400

1 

2# -*- coding: utf-8 -*- 

3 

4u'''I{Karney}'s elliptic functions and integrals. 

5 

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}. 

10 

11Python method names follow the C++ member functions, I{except}: 

12 

13 - member functions I{without arguments} are mapped to Python properties 

14 prefixed with C{"c"}, for example C{E()} is property C{cE}, 

15 

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)}, 

19 

20 - other Python method names conventionally start with a lower-case 

21 letter or an underscore if private. 

22 

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}. 

26 

27Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2023) 

28and licensed under the MIT/X11 License. For more information, see the 

29U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation. 

30 

31B{Elliptic integrals and functions.} 

32 

33This provides the elliptic functions and integrals needed for 

34C{Ellipsoid}, C{GeodesicExact}, and C{TransverseMercatorExact}. Two 

35categories of function are provided: 

36 

37 - functions to compute U{symmetric elliptic integrals 

38 <https://DLMF.NIST.gov/19.16.i>} 

39 

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>}. 

43 

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".) 

49 

50In geodesic applications, it is convenient to separate the incomplete 

51integrals into secular and periodic components, e.g. 

52 

53I{C{E(phi, k) = (2 E(k) / pi) [ phi + delta E(phi, k) ]}} 

54 

55where I{C{delta E(phi, k)}} is an odd periodic function with 

56period I{C{pi}}. 

57 

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>}. 

64 

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). 

69 

70The notation follows U{NIST Digital Library of Mathematical Functions 

71<https://DLMF.NIST.gov>} chapters U{19<https://DLMF.NIST.gov/19>} and 

72U{22<https://DLMF.NIST.gov/22>}. 

73''' 

74# make sure int/int division yields float quotient, see .basics 

75from __future__ import division as _; del _ # PYCHOK semicolon 

76 

77from pygeodesy.basics import copysign0, map2, neg, neg_ 

78from pygeodesy.constants import EPS, INF, NAN, PI, PI_2, PI_4, \ 

79 _EPStol as _TolJAC, _0_0, _1_64th, \ 

80 _0_25, _0_5, _1_0, _2_0, _N_2_0, \ 

81 _3_0, _4_0, _6_0, _8_0, _180_0, \ 

82 _360_0, _over 

83# from pygeodesy.errors import _ValueError # from .fmath 

84from pygeodesy.fmath import fdot, hypot1, zqrt, _ValueError 

85from pygeodesy.fsums import Fsum, _sum, _ALL_LAZY 

86from pygeodesy.interns import NN, _delta_, _DOT_, _dunder_nameof, _f_, \ 

87 _invalid_, _invokation_, _negative_, _SPACE_ 

88from pygeodesy.karney import _K_2_0, _norm180, _signBit, _sincos2 

89# from pygeodesy.lazily import _ALL_LAZY # from .fsums 

90from pygeodesy.named import _Named, _NamedTuple, Fmt, unstr 

91from pygeodesy.props import _allPropertiesOf_n, Property_RO, _update_all 

92# from pygeodesy.streprs import Fmt, unstr # from .named 

93from pygeodesy.units import Scalar, Scalar_ 

94# from pygeodesy.utily import sincos2 as _sincos2 # from .karney 

95 

96from math import asinh, atan, atan2, ceil, cosh, fabs, floor, \ 

97 radians, sin, sqrt, tanh 

98 

99__all__ = _ALL_LAZY.elliptic 

100__version__ = '24.04.14' 

101 

102_TolRD = zqrt(EPS * 0.002) 

103_TolRF = zqrt(EPS * 0.030) 

104_TolRG0 = _TolJAC * 2.7 

105_TRIPS = 21 # Max depth, 7 might be sufficient 

106 

107 

108class _Cs(object): 

109 '''(INTERAL) Complete integrals cache. 

110 ''' 

111 def __init__(self, **kwds): 

112 self.__dict__ = kwds 

113 

114 

115class _Dsum(list): 

116 '''(INTERNAL) Deferred C{Fsum}. 

117 ''' 

118 def __call__(self, s): 

119 try: # Fsum *= s 

120 return Fsum(*self).fmul(s) 

121 except ValueError: # Fsum(NAN) exception 

122 return _sum(self) * s 

123 

124 def __iadd__(self, x): 

125 list.append(self, x) 

126 return self 

127 

128 

129class Elliptic(_Named): 

130 '''Elliptic integrals and functions. 

131 

132 @see: I{Karney}'s U{Detailed Description<https://GeographicLib.SourceForge.io/ 

133 C++/doc/classGeographicLib_1_1EllipticFunction.html#details>}. 

134 ''' 

135# _alpha2 = 0 

136# _alphap2 = 0 

137# _eps = EPS 

138# _k2 = 0 

139# _kp2 = 0 

140 

141 def __init__(self, k2=0, alpha2=0, kp2=None, alphap2=None, name=NN): 

142 '''Constructor, specifying the C{modulus} and C{parameter}. 

143 

144 @kwarg name: Optional name (C{str}). 

145 

146 @see: Method L{Elliptic.reset} for further details. 

147 

148 @note: If only elliptic integrals of the first and second kinds 

149 are needed, use C{B{alpha2}=0}, the default value. In 

150 that case, we have C{Π(φ, 0, k) = F(φ, k), G(φ, 0, k) = 

151 E(φ, k)} and C{H(φ, 0, k) = F(φ, k) - D(φ, k)}. 

152 ''' 

153 self.reset(k2=k2, alpha2=alpha2, kp2=kp2, alphap2=alphap2) 

154 

155 if name: 

156 self.name = name 

157 

158 @Property_RO 

159 def alpha2(self): 

160 '''Get α^2, the square of the parameter (C{float}). 

161 ''' 

162 return self._alpha2 

163 

164 @Property_RO 

165 def alphap2(self): 

166 '''Get α'^2, the square of the complementary parameter (C{float}). 

167 ''' 

168 return self._alphap2 

169 

170 @Property_RO 

171 def cD(self): 

172 '''Get Jahnke's complete integral C{D(k)} (C{float}), 

173 U{defined<https://DLMF.NIST.gov/19.2.E6>}. 

174 ''' 

175 return self._cDEKEeps.cD 

176 

177 @Property_RO 

178 def _cDEKEeps(self): 

179 '''(INTERNAL) Get the complete integrals D, E, K and KE plus C{eps}. 

180 ''' 

181 k2, kp2 = self.k2, self.kp2 

182 if k2: 

183 if kp2: 

184 try: 

185 self._iteration = 0 

186 # D(k) = (K(k) - E(k))/k2, Carlson eq.4.3 

187 # <https://DLMF.NIST.gov/19.25.E1> 

188 D = _RD(self, _0_0, kp2, _1_0, _3_0) 

189 cD = float(D) 

190 # Complete elliptic integral E(k), Carlson eq. 4.2 

191 # <https://DLMF.NIST.gov/19.25.E1> 

192 cE = _rG2(self, kp2, _1_0, PI_=PI_2) 

193 # Complete elliptic integral K(k), Carlson eq. 4.1 

194 # <https://DLMF.NIST.gov/19.25.E1> 

195 cK = _rF2(self, kp2, _1_0) 

196 cKE = float(D.fmul(k2)) 

197 eps = k2 / (sqrt(kp2) + _1_0)**2 

198 

199 except Exception as e: 

200 raise _ellipticError(self.reset, k2=k2, kp2=kp2, cause=e) 

201 else: 

202 cD = cK = cKE = INF 

203 cE = _1_0 

204 eps = k2 

205 else: 

206 cD = PI_4 

207 cE = cK = PI_2 

208 cKE = _0_0 # k2 * cD 

209 eps = EPS 

210 

211 return _Cs(cD=cD, cE=cE, cK=cK, cKE=cKE, eps=eps) 

212 

213 @Property_RO 

214 def cE(self): 

215 '''Get the complete integral of the second kind C{E(k)} 

216 (C{float}), U{defined<https://DLMF.NIST.gov/19.2.E5>}. 

217 ''' 

218 return self._cDEKEeps.cE 

219 

220 @Property_RO 

221 def cG(self): 

222 '''Get Legendre's complete geodesic longitude integral 

223 C{G(α^2, k)} (C{float}). 

224 ''' 

225 return self._cGHPi.cG 

226 

227 @Property_RO 

228 def _cGHPi(self): 

229 '''(INTERNAL) Get the complete integrals G, H and Pi. 

230 ''' 

231 alpha2, alphap2, kp2 = self.alpha2, self.alphap2, self.kp2 

232 try: 

233 self._iteration = 0 

234 if alpha2: 

235 if alphap2: 

236 if kp2: # <https://DLMF.NIST.gov/19.25.E2> 

237 cK = self.cK 

238 Rj = _RJ(self, _0_0, kp2, _1_0, alphap2, _3_0) 

239 cG = float(Rj * (alpha2 - self.k2) + cK) # G(alpha2, k) 

240 cH = -float(Rj * alphap2 - cK) # H(alpha2, k) 

241 cPi = float(Rj * alpha2 + cK) # Pi(alpha2, k) 

242 else: 

243 cG = cH = _rC(self, _1_0, alphap2) 

244 cPi = INF # XXX or NAN? 

245 else: 

246 cG = cH = cPi = INF # XXX or NAN? 

247 else: 

248 cG, cPi = self.cE, self.cK 

249 # H = K - D but this involves large cancellations if k2 is near 1. 

250 # So write (for alpha2 = 0) 

251 # H = int(cos(phi)**2 / sqrt(1-k2 * sin(phi)**2), phi, 0, pi/2) 

252 # = 1 / sqrt(1-k2) * int(sin(phi)**2 / sqrt(1-k2/kp2 * sin(phi)**2,...) 

253 # = 1 / kp * D(i * k/kp) 

254 # and use D(k) = RD(0, kp2, 1) / 3, so 

255 # H = 1/kp * RD(0, 1/kp2, 1) / 3 

256 # = kp2 * RD(0, 1, kp2) / 3 

257 # using <https://DLMF.NIST.gov/19.20.E18>. Equivalently 

258 # RF(x, 1) - RD(0, x, 1) / 3 = x * RD(0, 1, x) / 3 for x > 0 

259 # For k2 = 1 and alpha2 = 0, we have 

260 # H = int(cos(phi),...) = 1 

261 cH = float(_RD(self, _0_0, _1_0, kp2, _3_0 / kp2)) if kp2 else _1_0 

262 

263 except Exception as e: 

264 raise _ellipticError(self.reset, kp2=kp2, alpha2 =alpha2, 

265 alphap2=alphap2, cause=e) 

266 return _Cs(cG=cG, cH=cH, cPi=cPi) 

267 

268 @Property_RO 

269 def cH(self): 

270 '''Get Cayley's complete geodesic longitude difference integral 

271 C{H(α^2, k)} (C{float}). 

272 ''' 

273 return self._cGHPi.cH 

274 

275 @Property_RO 

276 def cK(self): 

277 '''Get the complete integral of the first kind C{K(k)} 

278 (C{float}), U{defined<https://DLMF.NIST.gov/19.2.E4>}. 

279 ''' 

280 return self._cDEKEeps.cK 

281 

282 @Property_RO 

283 def cKE(self): 

284 '''Get the difference between the complete integrals of the 

285 first and second kinds, C{K(k) − E(k)} (C{float}). 

286 ''' 

287 return self._cDEKEeps.cKE 

288 

289 @Property_RO 

290 def cPi(self): 

291 '''Get the complete integral of the third kind C{Pi(α^2, k)} 

292 (C{float}), U{defined<https://DLMF.NIST.gov/19.2.E7>}. 

293 ''' 

294 return self._cGHPi.cPi 

295 

296 def deltaD(self, sn, cn, dn): 

297 '''Jahnke's periodic incomplete elliptic integral. 

298 

299 @arg sn: sin(φ). 

300 @arg cn: cos(φ). 

301 @arg dn: sqrt(1 − k2 * sin(2φ)). 

302 

303 @return: Periodic function π D(φ, k) / (2 D(k)) - φ (C{float}). 

304 

305 @raise EllipticError: Invalid invokation or no convergence. 

306 ''' 

307 return _deltaX(sn, cn, dn, self.cD, self.fD) 

308 

309 def deltaE(self, sn, cn, dn): 

310 '''The periodic incomplete integral of the second kind. 

311 

312 @arg sn: sin(φ). 

313 @arg cn: cos(φ). 

314 @arg dn: sqrt(1 − k2 * sin(2φ)). 

315 

316 @return: Periodic function π E(φ, k) / (2 E(k)) - φ (C{float}). 

317 

318 @raise EllipticError: Invalid invokation or no convergence. 

319 ''' 

320 return _deltaX(sn, cn, dn, self.cE, self.fE) 

321 

322 def deltaEinv(self, stau, ctau): 

323 '''The periodic inverse of the incomplete integral of the second kind. 

324 

325 @arg stau: sin(τ) 

326 @arg ctau: cos(τ) 

327 

328 @return: Periodic function E^−1(τ (2 E(k)/π), k) - τ (C{float}). 

329 

330 @raise EllipticError: No convergence. 

331 ''' 

332 try: 

333 if _signBit(ctau): # pi periodic 

334 stau, ctau = neg_(stau, ctau) 

335 t = atan2(stau, ctau) 

336 return self._Einv(t * self.cE / PI_2) - t 

337 

338 except Exception as e: 

339 raise _ellipticError(self.deltaEinv, stau, ctau, cause=e) 

340 

341 def deltaF(self, sn, cn, dn): 

342 '''The periodic incomplete integral of the first kind. 

343 

344 @arg sn: sin(φ). 

345 @arg cn: cos(φ). 

346 @arg dn: sqrt(1 − k2 * sin(2φ)). 

347 

348 @return: Periodic function π F(φ, k) / (2 K(k)) - φ (C{float}). 

349 

350 @raise EllipticError: Invalid invokation or no convergence. 

351 ''' 

352 return _deltaX(sn, cn, dn, self.cK, self.fF) 

353 

354 def deltaG(self, sn, cn, dn): 

355 '''Legendre's periodic geodesic longitude integral. 

356 

357 @arg sn: sin(φ). 

358 @arg cn: cos(φ). 

359 @arg dn: sqrt(1 − k2 * sin(2φ)). 

360 

361 @return: Periodic function π G(φ, k) / (2 G(k)) - φ (C{float}). 

362 

363 @raise EllipticError: Invalid invokation or no convergence. 

364 ''' 

365 return _deltaX(sn, cn, dn, self.cG, self.fG) 

366 

367 def deltaH(self, sn, cn, dn): 

368 '''Cayley's periodic geodesic longitude difference integral. 

369 

370 @arg sn: sin(φ). 

371 @arg cn: cos(φ). 

372 @arg dn: sqrt(1 − k2 * sin(2φ)). 

373 

374 @return: Periodic function π H(φ, k) / (2 H(k)) - φ (C{float}). 

375 

376 @raise EllipticError: Invalid invokation or no convergence. 

377 ''' 

378 return _deltaX(sn, cn, dn, self.cH, self.fH) 

379 

380 def deltaPi(self, sn, cn, dn): 

381 '''The periodic incomplete integral of the third kind. 

382 

383 @arg sn: sin(φ). 

384 @arg cn: cos(φ). 

385 @arg dn: sqrt(1 − k2 * sin(2φ)). 

386 

387 @return: Periodic function π Π(φ, α2, k) / (2 Π(α2, k)) - φ 

388 (C{float}). 

389 

390 @raise EllipticError: Invalid invokation or no convergence. 

391 ''' 

392 return _deltaX(sn, cn, dn, self.cPi, self.fPi) 

393 

394 def _Einv(self, x): 

395 '''(INTERNAL) Helper for C{.deltaEinv} and C{.fEinv}. 

396 ''' 

397 E2 = self.cE * _2_0 

398 n = floor(x / E2 + _0_5) 

399 r = x - E2 * n # r in [-cE, cE) 

400 # linear approximation 

401 phi = PI * r / E2 # phi in [-PI_2, PI_2) 

402 Phi = Fsum(phi) 

403 # first order correction 

404 phi = Phi.fsum_(self.eps * sin(phi * _2_0) / _N_2_0) 

405 # For kp2 close to zero use asin(r / cE) or J. P. Boyd, 

406 # Applied Math. and Computation 218, 7005-7013 (2012) 

407 # <https://DOI.org/10.1016/j.amc.2011.12.021> 

408 _Phi2, self._iteration = Phi.fsum2f_, 0 # aggregate 

409 for i in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC 

410 sn, cn, dn = self._sncndn3(phi) 

411 if dn: 

412 sn = self.fE(sn, cn, dn) 

413 phi, d = _Phi2((r - sn) / dn) 

414 else: # PYCHOK no cover 

415 d = _0_0 # XXX continue? 

416 if fabs(d) < _TolJAC: # 3-4 trips 

417 _iterations(self, i) 

418 break 

419 else: # PYCHOK no cover 

420 raise _convergenceError(d, _TolJAC) 

421 return Phi.fsum_(n * PI) if n else phi 

422 

423 @Property_RO 

424 def eps(self): 

425 '''Get epsilon (C{float}). 

426 ''' 

427 return self._cDEKEeps.eps 

428 

429 def fD(self, phi_or_sn, cn=None, dn=None): 

430 '''Jahnke's incomplete elliptic integral in terms of 

431 Jacobi elliptic functions. 

432 

433 @arg phi_or_sn: φ or sin(φ). 

434 @kwarg cn: C{None} or cos(φ). 

435 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)). 

436 

437 @return: D(φ, k) as though φ ∈ (−π, π] (C{float}), 

438 U{defined<https://DLMF.NIST.gov/19.2.E4>}. 

439 

440 @raise EllipticError: Invalid invokation or no convergence. 

441 ''' 

442 def _fD(sn, cn, dn): 

443 r = fabs(sn)**3 

444 if r: 

445 r = float(_RD(self, cn**2, dn**2, _1_0, _3_0 / r)) 

446 return r 

447 

448 return self._fXf(phi_or_sn, cn, dn, self.cD, 

449 self.deltaD, _fD) 

450 

451 def fDelta(self, sn, cn): 

452 '''The C{Delta} amplitude function. 

453 

454 @arg sn: sin(φ). 

455 @arg cn: cos(φ). 

456 

457 @return: sqrt(1 − k2 * sin(2φ)) (C{float}). 

458 ''' 

459 try: 

460 k2 = self.k2 

461 s = (self.kp2 + cn**2 * k2) if k2 > 0 else ( 

462 (_1_0 - sn**2 * k2) if k2 < 0 else self.kp2) 

463 return sqrt(s) if s else _0_0 

464 

465 except Exception as e: 

466 raise _ellipticError(self.fDelta, sn, cn, k2=k2, cause=e) 

467 

468 def fE(self, phi_or_sn, cn=None, dn=None): 

469 '''The incomplete integral of the second kind in terms of 

470 Jacobi elliptic functions. 

471 

472 @arg phi_or_sn: φ or sin(φ). 

473 @kwarg cn: C{None} or cos(φ). 

474 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)). 

475 

476 @return: E(φ, k) as though φ ∈ (−π, π] (C{float}), 

477 U{defined<https://DLMF.NIST.gov/19.2.E5>}. 

478 

479 @raise EllipticError: Invalid invokation or no convergence. 

480 ''' 

481 def _fE(sn, cn, dn): 

482 '''(INTERNAL) Core of C{.fE}. 

483 ''' 

484 if sn: 

485 sn2, cn2, dn2 = sn**2, cn**2, dn**2 

486 kp2, k2 = self.kp2, self.k2 

487 if k2 <= 0: # Carlson, eq. 4.6, <https://DLMF.NIST.gov/19.25.E9> 

488 Ei = _RF3(self, cn2, dn2, _1_0) 

489 if k2: 

490 Ei -= _RD(self, cn2, dn2, _1_0, _3over(k2, sn2)) 

491 elif kp2 >= 0: # k2 > 0, <https://DLMF.NIST.gov/19.25.E10> 

492 Ei = _over(k2 * fabs(cn), dn) # float 

493 if kp2: 

494 Ei += (_RD( self, cn2, _1_0, dn2, _3over(k2, sn2)) + 

495 _RF3(self, cn2, dn2, _1_0)) * kp2 

496 else: # kp2 < 0, <https://DLMF.NIST.gov/19.25.E11> 

497 Ei = _over(dn, fabs(cn)) 

498 Ei -= _RD(self, dn2, _1_0, cn2, _3over(kp2, sn2)) 

499 Ei *= fabs(sn) 

500 ei = float(Ei) 

501 else: # PYCHOK no cover 

502 ei = _0_0 

503 return ei 

504 

505 return self._fXf(phi_or_sn, cn, dn, self.cE, 

506 self.deltaE, _fE) 

507 

508 def fEd(self, deg): 

509 '''The incomplete integral of the second kind with 

510 the argument given in C{degrees}. 

511 

512 @arg deg: Angle (C{degrees}). 

513 

514 @return: E(π B{C{deg}} / 180, k) (C{float}). 

515 

516 @raise EllipticError: No convergence. 

517 ''' 

518 if _K_2_0: 

519 e = round((deg - _norm180(deg)) / _360_0) 

520 elif fabs(deg) < _180_0: 

521 e = _0_0 

522 else: 

523 e = ceil(deg / _360_0 - _0_5) 

524 deg -= e * _360_0 

525 return self.fE(radians(deg)) + e * self.cE * _4_0 

526 

527 def fEinv(self, x): 

528 '''The inverse of the incomplete integral of the second kind. 

529 

530 @arg x: Argument (C{float}). 

531 

532 @return: φ = 1 / E(B{C{x}}, k), such that E(φ, k) = B{C{x}} 

533 (C{float}). 

534 

535 @raise EllipticError: No convergence. 

536 ''' 

537 try: 

538 return self._Einv(x) 

539 except Exception as e: 

540 raise _ellipticError(self.fEinv, x, cause=e) 

541 

542 def fF(self, phi_or_sn, cn=None, dn=None): 

543 '''The incomplete integral of the first kind in terms of 

544 Jacobi elliptic functions. 

545 

546 @arg phi_or_sn: φ or sin(φ). 

547 @kwarg cn: C{None} or cos(φ). 

548 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)). 

549 

550 @return: F(φ, k) as though φ ∈ (−π, π] (C{float}), 

551 U{defined<https://DLMF.NIST.gov/19.2.E4>}. 

552 

553 @raise EllipticError: Invalid invokation or no convergence. 

554 ''' 

555 def _fF(sn, cn, dn): 

556 r = fabs(sn) 

557 if r: 

558 r = float(_RF3(self, cn**2, dn**2, _1_0).fmul(r)) 

559 return r 

560 

561 return self._fXf(phi_or_sn, cn, dn, self.cK, 

562 self.deltaF, _fF) 

563 

564 def fG(self, phi_or_sn, cn=None, dn=None): 

565 '''Legendre's geodesic longitude integral in terms of 

566 Jacobi elliptic functions. 

567 

568 @arg phi_or_sn: φ or sin(φ). 

569 @kwarg cn: C{None} or cos(φ). 

570 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)). 

571 

572 @return: G(φ, k) as though φ ∈ (−π, π] (C{float}). 

573 

574 @raise EllipticError: Invalid invokation or no convergence. 

575 

576 @note: Legendre expresses the longitude of a point on the 

577 geodesic in terms of this combination of elliptic 

578 integrals in U{Exercices de Calcul Intégral, Vol 1 

579 (1811), p 181<https://Books.Google.com/books?id= 

580 riIOAAAAQAAJ&pg=PA181>}. 

581 

582 @see: U{Geodesics in terms of elliptic integrals<https:// 

583 GeographicLib.SourceForge.io/C++/doc/geodesic.html#geodellip>} 

584 for the expression for the longitude in terms of this function. 

585 ''' 

586 return self._fXa(phi_or_sn, cn, dn, self.alpha2 - self.k2, 

587 self.cG, self.deltaG) 

588 

589 def fH(self, phi_or_sn, cn=None, dn=None): 

590 '''Cayley's geodesic longitude difference integral in terms of 

591 Jacobi elliptic functions. 

592 

593 @arg phi_or_sn: φ or sin(φ). 

594 @kwarg cn: C{None} or cos(φ). 

595 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)). 

596 

597 @return: H(φ, k) as though φ ∈ (−π, π] (C{float}). 

598 

599 @raise EllipticError: Invalid invokation or no convergence. 

600 

601 @note: Cayley expresses the longitude difference of a point 

602 on the geodesic in terms of this combination of 

603 elliptic integrals in U{Phil. Mag. B{40} (1870), p 333 

604 <https://Books.Google.com/books?id=Zk0wAAAAIAAJ&pg=PA333>}. 

605 

606 @see: U{Geodesics in terms of elliptic integrals<https:// 

607 GeographicLib.SourceForge.io/C++/doc/geodesic.html#geodellip>} 

608 for the expression for the longitude in terms of this function. 

609 ''' 

610 return self._fXa(phi_or_sn, cn, dn, -self.alphap2, 

611 self.cH, self.deltaH) 

612 

613 def fPi(self, phi_or_sn, cn=None, dn=None): 

614 '''The incomplete integral of the third kind in terms of 

615 Jacobi elliptic functions. 

616 

617 @arg phi_or_sn: φ or sin(φ). 

618 @kwarg cn: C{None} or cos(φ). 

619 @kwarg dn: C{None} or sqrt(1 − k2 * sin(2φ)). 

620 

621 @return: Π(φ, α2, k) as though φ ∈ (−π, π] (C{float}). 

622 

623 @raise EllipticError: Invalid invokation or no convergence. 

624 ''' 

625 if dn is None and cn is not None: # and isscalar(phi_or_sn) 

626 dn = self.fDelta(phi_or_sn, cn) # in .triaxial 

627 return self._fXa(phi_or_sn, cn, dn, self.alpha2, 

628 self.cPi, self.deltaPi) 

629 

630 def _fXa(self, phi_or_sn, cn, dn, aX, cX, deltaX): 

631 '''(INTERNAL) Helper for C{.fG}, C{.fH} and C{.fPi}. 

632 ''' 

633 def _fX(sn, cn, dn): 

634 if sn: 

635 cn2, dn2 = cn**2, dn**2 

636 R = _RF3(self, cn2, dn2, _1_0) 

637 if aX: 

638 sn2 = sn**2 

639 p = sn2 * self.alphap2 + cn2 

640 R += _RJ(self, cn2, dn2, _1_0, p, _3over(aX, sn2)) 

641 R *= fabs(sn) 

642 r = float(R) 

643 else: # PYCHOK no cover 

644 r = _0_0 

645 return r 

646 

647 return self._fXf(phi_or_sn, cn, dn, cX, deltaX, _fX) 

648 

649 def _fXf(self, phi_or_sn, cn, dn, cX, deltaX, fX): 

650 '''(INTERNAL) Helper for C{.fD}, C{.fE}, C{.fF} and C{._fXa}. 

651 ''' 

652 self._iteration = 0 # aggregate 

653 phi = sn = phi_or_sn 

654 if cn is dn is None: # fX(phi) call 

655 sn, cn, dn = self._sncndn3(phi) 

656 if fabs(phi) >= PI: 

657 return (deltaX(sn, cn, dn) + phi) * cX / PI_2 

658 # fall through 

659 elif cn is None or dn is None: 

660 n = NN(_f_, deltaX.__name__[5:]) 

661 raise _ellipticError(n, sn, cn, dn) 

662 

663 if _signBit(cn): # enforce usual trig-like symmetries 

664 xi = cX * _2_0 - fX(sn, cn, dn) 

665 else: 

666 xi = fX(sn, cn, dn) if cn > 0 else cX 

667 return copysign0(xi, sn) 

668 

669 @Property_RO 

670 def k2(self): 

671 '''Get k^2, the square of the modulus (C{float}). 

672 ''' 

673 return self._k2 

674 

675 @Property_RO 

676 def kp2(self): 

677 '''Get k'^2, the square of the complementary modulus (C{float}). 

678 ''' 

679 return self._kp2 

680 

681 def reset(self, k2=0, alpha2=0, kp2=None, alphap2=None): # MCCABE 13 

682 '''Reset the modulus, parameter and the complementaries. 

683 

684 @kwarg k2: Modulus squared (C{float}, NINF <= k^2 <= 1). 

685 @kwarg alpha2: Parameter squared (C{float}, NINF <= α^2 <= 1). 

686 @kwarg kp2: Complementary modulus squared (C{float}, k'^2 >= 0). 

687 @kwarg alphap2: Complementary parameter squared (C{float}, α'^2 >= 0). 

688 

689 @raise EllipticError: Invalid B{C{k2}}, B{C{alpha2}}, B{C{kp2}} 

690 or B{C{alphap2}}. 

691 

692 @note: The arguments must satisfy C{B{k2} + B{kp2} = 1} and 

693 C{B{alpha2} + B{alphap2} = 1}. No checking is done 

694 that these conditions are met to enable accuracy to be 

695 maintained, e.g., when C{k} is very close to unity. 

696 ''' 

697 if self.__dict__: 

698 _update_all(self, _Named.iteration._uname, Base=Property_RO) 

699 

700 self._k2 = Scalar_(k2=k2, Error=EllipticError, low=None, high=_1_0) 

701 self._kp2 = Scalar_(kp2=((_1_0 - k2) if kp2 is None else kp2), Error=EllipticError) 

702 

703 self._alpha2 = Scalar_(alpha2=alpha2, Error=EllipticError, low=None, high=_1_0) 

704 self._alphap2 = Scalar_(alphap2=((_1_0 - alpha2) if alphap2 is None else alphap2), 

705 Error=EllipticError) 

706 

707 # Values of complete elliptic integrals for k = 0,1 and alpha = 0,1 

708 # K E D 

709 # k = 0: pi/2 pi/2 pi/4 

710 # k = 1: inf 1 inf 

711 # Pi G H 

712 # k = 0, alpha = 0: pi/2 pi/2 pi/4 

713 # k = 1, alpha = 0: inf 1 1 

714 # k = 0, alpha = 1: inf inf pi/2 

715 # k = 1, alpha = 1: inf inf inf 

716 # 

717 # G(0, k) = Pi(0, k) = H(1, k) = E(k) 

718 # H(0, k) = K(k) - D(k) 

719 # Pi(alpha2, 0) = G(alpha2, 0) = pi / (2 * sqrt(1 - alpha2)) 

720 # H( alpha2, 0) = pi / (2 * (sqrt(1 - alpha2) + 1)) 

721 # Pi(alpha2, 1) = inf 

722 # G( alpha2, 1) = H(alpha2, 1) = RC(1, alphap2) 

723 

724 def sncndn(self, x): 

725 '''The Jacobi elliptic function. 

726 

727 @arg x: The argument (C{float}). 

728 

729 @return: An L{Elliptic3Tuple}C{(sn, cn, dn)} with 

730 C{*n(B{x}, k)}. 

731 

732 @raise EllipticError: No convergence. 

733 ''' 

734 self._iteration = 0 # reset 

735 try: # Bulirsch's sncndn routine, p 89. 

736 if self.kp2: 

737 c, d, cd, mn = self._sncndn4 

738 dn = _1_0 

739 sn, cn = _sincos2(x * cd) 

740 if sn: 

741 a = cn / sn 

742 c *= a 

743 for m, n in reversed(mn): 

744 a *= c 

745 c *= dn 

746 dn = (n + a) / (m + a) 

747 a = c / m 

748 a = _1_0 / hypot1(c) 

749 sn = neg(a) if _signBit(sn) else a 

750 cn = c * sn 

751 if d and _signBit(self.kp2): 

752 cn, dn = dn, cn 

753 sn = sn / d # /= chokes PyChecker 

754 else: 

755 sn = tanh(x) 

756 cn = dn = _1_0 / cosh(x) 

757 

758 except Exception as e: 

759 raise _ellipticError(self.sncndn, x, kp2=self.kp2, cause=e) 

760 

761 return Elliptic3Tuple(sn, cn, dn, iteration=self._iteration) 

762 

763 def _sncndn3(self, phi): 

764 '''(INTERNAL) Helper for C{.fEinv} and C{._fXf}. 

765 ''' 

766 sn, cn = _sincos2(phi) 

767 return sn, cn, self.fDelta(sn, cn) 

768 

769 @Property_RO 

770 def _sncndn4(self): 

771 '''(INTERNAL) Get Bulirsch' 4-tuple C{(c, d, cd, mn)}. 

772 ''' 

773 # Bulirsch's sncndn routine, p 89. 

774 d, mc = 0, self.kp2 

775 if _signBit(mc): 

776 d = _1_0 - mc 

777 mc = neg(mc / d) 

778 d = sqrt(d) 

779 

780 mn, a = [], _1_0 

781 for i in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC 

782 mc = sqrt(mc) 

783 mn.append((a, mc)) 

784 c = (a + mc) * _0_5 

785 r = fabs(mc - a) 

786 t = _TolJAC * a 

787 if r <= t: # 6 trips, quadratic 

788 _iterations(self, i) 

789 break 

790 mc *= a 

791 a = c 

792 else: # PYCHOK no cover 

793 raise _convergenceError(r, t) 

794 cd = (c * d) if d else c 

795 return c, d, cd, mn 

796 

797 @staticmethod 

798 def fRC(x, y): 

799 '''Degenerate symmetric integral of the first kind C{RC(x, y)}. 

800 

801 @return: C{RC(x, y)}, equivalent to C{RF(x, y, y)}. 

802 

803 @see: U{C{RC} definition<https://DLMF.NIST.gov/19.2.E17>} and 

804 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}. 

805 ''' 

806 return _rC(None, x, y) 

807 

808 @staticmethod 

809 def fRD(x, y, z, *over): 

810 '''Degenerate symmetric integral of the third kind C{RD(x, y, z)}. 

811 

812 @return: C{RD(x, y, z) / over}, equivalent to C{RJ(x, y, z, z) 

813 / over} with C{over} typically 3. 

814 

815 @see: U{C{RD} definition<https://DLMF.NIST.gov/19.16.E5>} and 

816 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}. 

817 ''' 

818 try: 

819 return float(_RD(None, x, y, z, *over)) 

820 except Exception as e: 

821 raise _ellipticError(Elliptic.fRD, x, y, z, *over, cause=e) 

822 

823 @staticmethod 

824 def fRF(x, y, z=0): 

825 '''Symmetric or complete symmetric integral of the first kind 

826 C{RF(x, y, z)} respectively C{RF(x, y)}. 

827 

828 @return: C{RF(x, y, z)} or C{RF(x, y)} for missing or zero B{C{z}}. 

829 

830 @see: U{C{RF} definition<https://DLMF.NIST.gov/19.16.E1>} and 

831 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}. 

832 ''' 

833 try: 

834 return float(_RF3(None, x, y, z)) if z else _rF2(None, x, y) 

835 except Exception as e: 

836 raise _ellipticError(Elliptic.fRF, x, y, z, cause=e) 

837 

838 @staticmethod 

839 def fRG(x, y, z=0): 

840 '''Symmetric or complete symmetric integral of the second kind 

841 C{RG(x, y, z)} respectively C{RG(x, y)}. 

842 

843 @return: C{RG(x, y, z)} or C{RG(x, y)} for missing or zero B{C{z}}. 

844 

845 @see: U{C{RG} definition<https://DLMF.NIST.gov/19.16.E3>}, 

846 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>} and 

847 U{RG<https://GeographicLib.SourceForge.io/C++/doc/ 

848 EllipticFunction_8cpp_source.html#l00096>} version 2.3. 

849 ''' 

850 try: 

851 return _rG2(None, x, y) if z == 0 else ( 

852 _rG2(None, z, x) if y == 0 else ( 

853 _rG2(None, y, z) if x == 0 else _rG3(None, x, y, z))) 

854 except Exception as e: 

855 t = _negative_ if min(x, y, z) < 0 else NN 

856 raise _ellipticError(Elliptic.fRG, x, y, z, cause=e, txt=t) 

857 

858 @staticmethod 

859 def fRJ(x, y, z, p): 

860 '''Symmetric integral of the third kind C{RJ(x, y, z, p)}. 

861 

862 @return: C{RJ(x, y, z, p)}. 

863 

864 @see: U{C{RJ} definition<https://DLMF.NIST.gov/19.16.E2>} and 

865 U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}. 

866 ''' 

867 try: 

868 return float(_RJ(None, x, y, z, p)) 

869 except Exception as e: 

870 raise _ellipticError(Elliptic.fRJ, x, y, z, p, cause=e) 

871 

872 @staticmethod 

873 def _RFRD(x, y, z, m): 

874 # in .auxilats.AuxDLat.DE, .auxilats.AuxLat.Rectifying 

875 try: # float(RF(x, y, z) - RD(x, y, z, 3 / m)) 

876 R = _RF3(None, x, y, z) 

877 if m: 

878 R -= _RD(None, x, y, z, _3_0 / m) 

879 except Exception as e: 

880 raise _ellipticError(Elliptic._RFRD, x, y, z, m, cause=e) 

881 return float(R) 

882 

883_allPropertiesOf_n(15, Elliptic) # # PYCHOK assert, see Elliptic.reset 

884 

885 

886class EllipticError(_ValueError): 

887 '''Elliptic function, integral, convergence or other L{Elliptic} issue. 

888 ''' 

889 pass 

890 

891 

892class Elliptic3Tuple(_NamedTuple): 

893 '''3-Tuple C{(sn, cn, dn)} all C{scalar}. 

894 ''' 

895 _Names_ = ('sn', 'cn', 'dn') 

896 _Units_ = ( Scalar, Scalar, Scalar) 

897 

898 

899class _List(list): 

900 '''(INTERNAL) Helper for C{_RD}, C{_RF3} and C{_RJ}. 

901 ''' 

902 _a0 = None 

903# _xyzp = () 

904 

905 def __init__(self, *xyzp): # x, y, z [, p] 

906 list.__init__(self, xyzp) 

907 self._xyzp = xyzp 

908 

909 def a0(self, n): 

910 '''Compute the initial C{a}. 

911 ''' 

912 t = tuple(self) 

913 m = n - len(t) 

914 if m > 0: 

915 t += t[-1:] * m 

916 try: 

917 a = Fsum(*t).fover(n) 

918 except ValueError: # Fsum(NAN) exception 

919 a = _sum(t) / n 

920 self._a0 = a 

921 return a 

922 

923 def amrs4(self, inst, y, Tol): 

924 '''Yield Carlson 4-tuples C{(An, mul, lam, s)} plus sentinel, with 

925 C{lam = fdot(sqrt(x), ... (z))} and C{s = (sqrt(x), ... (p))}. 

926 ''' 

927 L = self 

928 a = L.a0(5 if y else 3) 

929 m = 1 

930 t = max(fabs(a - _) for _ in L) / Tol 

931 for i in range(_TRIPS): 

932 d = fabs(a * m) 

933 if d > t: # 3-6 trips 

934 _iterations(inst, i) 

935 break 

936 s = map2(sqrt, L) # sqrt(x), srqt(y), sqrt(z) [, sqrt(p)] 

937 try: 

938 r = fdot(s[:3], s[1], s[2], s[0]) # sqrt(x) * sqrt(y) + ... 

939 except ValueError: # Fsum(NAN) exception 

940 r = _sum(s[i] * s[(i + 1) % 3] for i in range(3)) 

941 L[:] = ((r + _) * _0_25 for _ in L) 

942 a = (r + a) * _0_25 

943 if y: # yield only if used 

944 yield a, m, r, s # L[2] is next z 

945 m *= 4 

946 else: # PYCHOK no cover 

947 raise _convergenceError(d, t, thresh=True) 

948 yield a, m, None, () # sentinel: same a, next m, no r and s 

949 

950 def rescale(self, am, *xs): 

951 '''Rescale C{x}, C{y}, ... 

952 ''' 

953 # assert am 

954 a0 = self._a0 

955 _am = _1_0 / am 

956 for x in xs: 

957 yield (a0 - x) * _am 

958 

959 

960def _ab2(inst, x, y): 

961 '''(INTERNAL) Yield Carlson 2-tuples C{(xn, yn)}. 

962 ''' 

963 a, b = sqrt(x), sqrt(y) 

964 if b > a: 

965 b, a = a, b 

966 for i in range(_TRIPS): 

967 yield a, b # xi, yi 

968 d = fabs(a - b) 

969 t = _TolRG0 * a 

970 if d <= t: # 3-4 trips 

971 _iterations(inst, i) 

972 break 

973 a, b = ((a + b) * _0_5), sqrt(a * b) 

974 else: # PYCHOK no cover 

975 raise _convergenceError(d, t) 

976 

977 

978def _convergenceError(d, tol, **thresh): 

979 '''(INTERNAL) Format a no-convergence Error. 

980 ''' 

981 t = Fmt.no_convergence(d, tol, **thresh) 

982 return ValueError(t) # txt only 

983 

984 

985def _deltaX(sn, cn, dn, cX, fX): 

986 '''(INTERNAL) Helper for C{Elliptic.deltaD} thru C{.deltaPi}. 

987 ''' 

988 try: 

989 if cn is None or dn is None: 

990 raise ValueError(_invalid_) 

991 

992 if _signBit(cn): 

993 sn, cn = neg_(sn, cn) 

994 r = fX(sn, cn, dn) * PI_2 / cX 

995 return r - atan2(sn, cn) 

996 

997 except Exception as e: 

998 n = NN(_delta_, fX.__name__[1:]) 

999 raise _ellipticError(n, sn, cn, dn, cause=e) 

1000 

1001 

1002def _ellipticError(where, *args, **kwds_cause_txt): 

1003 '''(INTERNAL) Format an L{EllipticError}. 

1004 ''' 

1005 def _x_t_kwds(cause=None, txt=NN, **kwds): 

1006 return cause, txt, kwds 

1007 

1008 x, t, kwds = _x_t_kwds(**kwds_cause_txt) 

1009 

1010 n = _dunder_nameof(where, where) 

1011 n = _DOT_(Elliptic.__name__, n) 

1012 n = _SPACE_(_invokation_, n) 

1013 u = unstr(n, *args, **kwds) 

1014 return EllipticError(u, cause=x, txt=t) 

1015 

1016 

1017def _Horner(S, e1, E2, E3, E4, E5, *over): 

1018 '''(INTERNAL) Horner form for C{_RD} and C{_RJ} below. 

1019 ''' 

1020 E22 = E2**2 

1021 # Polynomial is <https://DLMF.NIST.gov/19.36.E2> 

1022 # (1 - 3*E2/14 + E3/6 + 9*E2**2/88 - 3*E4/22 - 9*E2*E3/52 

1023 # + 3*E5/26 - E2**3/16 + 3*E3**2/40 + 3*E2*E4/20 

1024 # + 45*E2**2*E3/272 - 9*(E3*E4+E2*E5)/68) 

1025 # converted to Horner-like form ... 

1026 e = e1 * 4084080 

1027 S *= e 

1028 S += Fsum(E2 * -540540, 471240).fmul(E5) 

1029 S += Fsum(E2 * 612612, E3 * -540540, -556920).fmul(E4) 

1030 S += Fsum(E2 * -706860, E22 * 675675, E3 * 306306, 680680).fmul(E3) 

1031 S += Fsum(E2 * 417690, E22 * -255255, -875160).fmul(E2) 

1032 S += 4084080 

1033 if over: 

1034 e *= over[0] 

1035 return S.fdiv(e) # Fsum 

1036 

1037 

1038def _iterations(inst, i): 

1039 '''(INTERNAL) Aggregate iterations B{C{i}}. 

1040 ''' 

1041 if inst and i > 0: 

1042 inst._iteration += i 

1043 

1044 

1045def _3over(a, b): 

1046 '''(INTERNAL) Return C{3 / (a * b)}. 

1047 ''' 

1048 return _over(_3_0, a * b) 

1049 

1050 

1051def _rC(unused, x, y): 

1052 '''(INTERNAL) Defined only for C{y != 0} and C{x >= 0}. 

1053 ''' 

1054 d = x - y 

1055 if d < 0: # catch NaN 

1056 # <https://DLMF.NIST.gov/19.2.E18> 

1057 d = -d 

1058 r = atan(sqrt(d / x)) if x > 0 else PI_2 

1059 elif d == 0: # XXX d < EPS0? or EPS02 or _EPSmin 

1060 d, r = y, _1_0 

1061 elif y > 0: # <https://DLMF.NIST.gov/19.2.E19> 

1062 r = asinh(sqrt(d / y)) # atanh(sqrt((x - y) / x)) 

1063 elif y < 0: # <https://DLMF.NIST.gov/19.2.E20> 

1064 r = asinh(sqrt(-x / y)) # atanh(sqrt(x / (x - y))) 

1065 else: # PYCHOK no cover 

1066 raise _ellipticError(Elliptic.fRC, x, y) 

1067 return r / sqrt(d) # float 

1068 

1069 

1070def _RD(inst, x, y, z, *over): 

1071 '''(INTERNAL) Carlson, eqs 2.28 - 2.34. 

1072 ''' 

1073 L = _List(x, y, z) 

1074 S = _Dsum() 

1075 for a, m, r, s in L.amrs4(inst, True, _TolRF): 

1076 if s: 

1077 S += _over(_3_0, (r + z) * s[2] * m) 

1078 z = L[2] # s[2] = sqrt(z) 

1079 x, y = L.rescale(-a * m, x, y) 

1080 xy = x * y 

1081 z = (x + y) / _3_0 

1082 z2 = z**2 

1083 return _Horner(S(_1_0), sqrt(a) * a * m, 

1084 xy - _6_0 * z2, 

1085 (xy * _3_0 - _8_0 * z2) * z, 

1086 (xy - z2) * _3_0 * z2, 

1087 xy * z2 * z, *over) # Fsum 

1088 

1089 

1090def _rF2(inst, x, y): # 2-arg version, z=0 

1091 '''(INTERNAL) Carlson, eqs 2.36 - 2.38. 

1092 ''' 

1093 for a, b in _ab2(inst, x, y): # PYCHOK yield 

1094 pass 

1095 return _over(PI, a + b) # float 

1096 

1097 

1098def _RF3(inst, x, y, z): # 3-arg version 

1099 '''(INTERNAL) Carlson, eqs 2.2 - 2.7. 

1100 ''' 

1101 L = _List(x, y, z) 

1102 for a, m, _, _ in L.amrs4(inst, False, _TolRF): 

1103 pass 

1104 x, y = L.rescale(a * m, x, y) 

1105 z = neg(x + y) 

1106 xy = x * y 

1107 e2 = xy - z**2 

1108 e3 = xy * z 

1109 e4 = e2**2 

1110 # Polynomial is <https://DLMF.NIST.gov/19.36.E1> 

1111 # (1 - E2/10 + E3/14 + E2**2/24 - 3*E2*E3/44 

1112 # - 5*E2**3/208 + 3*E3**2/104 + E2**2*E3/16) 

1113 # converted to Horner-like form ... 

1114 S = Fsum(e4 * 15015, e3 * 6930, e2 * -16380, 17160).fmul(e3) 

1115 S += Fsum(e4 * -5775, e2 * 10010, -24024).fmul(e2) 

1116 S += 240240 

1117 return S.fdiv(sqrt(a) * 240240) # Fsum 

1118 

1119 

1120def _rG2(inst, x, y, PI_=PI_4): # 2-args 

1121 '''(INTERNAL) Carlson, eqs 2.36 - 2.39. 

1122 ''' 

1123 m = -1 # neg! 

1124 S = None 

1125 for a, b in _ab2(inst, x, y): # PYCHOK yield 

1126 if S is None: # initial 

1127 S = _Dsum() 

1128 S += (a + b)**2 * _0_5 

1129 else: 

1130 S += (a - b)**2 * m 

1131 m *= 2 

1132 return S(PI_).fover(a + b) 

1133 

1134 

1135def _rG3(inst, x, y, z): # 3-arg version 

1136 '''(INTERNAL) C{x}, C{y} and C{z} all non-zero, see C{.fRG}. 

1137 ''' 

1138 R = _RF3(inst, x, y, z) * z 

1139 rd = (x - z) * (z - y) # - (y - z) 

1140 if rd: # Carlson, eq 1.7 

1141 R += _RD(inst, x, y, z, _3_0 / rd) 

1142 R += sqrt(x * y / z) 

1143 return R.fover(_2_0) 

1144 

1145 

1146def _RJ(inst, x, y, z, p, *over): 

1147 '''(INTERNAL) Carlson, eqs 2.17 - 2.25. 

1148 ''' 

1149 def _xyzp(x, y, z, p): 

1150 return (x + p) * (y + p) * (z + p) 

1151 

1152 L = _List(x, y, z, p) 

1153 n = neg(_xyzp(x, y, z, -p)) 

1154 S = _Dsum() 

1155 for a, m, _, s in L.amrs4(inst, True, _TolRD): 

1156 if s: 

1157 d = _xyzp(*s) 

1158 if d: 

1159 if n: 

1160 rc = _rC(inst, _1_0, n / d**2 + _1_0) 

1161 n *= _1_64th # /= chokes PyChecker 

1162 else: 

1163 rc = _1_0 # == _rC(None, _1_0, _1_0) 

1164 S += rc / (d * m) 

1165 else: # PYCHOK no cover 

1166 return NAN 

1167 x, y, z = L.rescale(a * m, x, y, z) 

1168 p = Fsum(x, y, z).fover(_N_2_0) 

1169 p2 = p**2 

1170 p3 = p2 * p 

1171 E2 = Fsum(x * y, x * z, y * z, -p2 * _3_0) 

1172 E2p = E2 * p 

1173 xyz = x * y * z 

1174 return _Horner(S(_6_0), sqrt(a) * a * m, E2, 

1175 Fsum(p3 * _4_0, xyz, E2p * _2_0), 

1176 Fsum(p3 * _3_0, E2p, xyz * _2_0).fmul(p), 

1177 xyz * p2, *over) # Fsum 

1178 

1179# **) MIT License 

1180# 

1181# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved. 

1182# 

1183# Permission is hereby granted, free of charge, to any person obtaining a 

1184# copy of this software and associated documentation files (the "Software"), 

1185# to deal in the Software without restriction, including without limitation 

1186# the rights to use, copy, modify, merge, publish, distribute, sublicense, 

1187# and/or sell copies of the Software, and to permit persons to whom the 

1188# Software is furnished to do so, subject to the following conditions: 

1189# 

1190# The above copyright notice and this permission notice shall be included 

1191# in all copies or substantial portions of the Software. 

1192# 

1193# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 

1194# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

1195# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 

1196# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 

1197# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 

1198# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 

1199# OTHER DEALINGS IN THE SOFTWARE.