Coverage for pygeodesy/elliptic.py: 97%

527 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-09-09 13:03 -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-2024) 

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) or optionally the C{Jacobi 

69amplitude} in method L{Elliptic.sncndn<pygeodesy.Elliptic.sncndn>}. 

70 

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 ; 

77 

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 

98 

99from math import asin, asinh, atan, ceil, cosh, fabs, floor, radians, \ 

100 sin, sinh, sqrt, tan, tanh # tan as _tan 

101 

102__all__ = _ALL_LAZY.elliptic 

103__version__ = '25.09.04' 

104 

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 

109 

110 

111class _Cs(_Enum): 

112 '''(INTERAL) Complete Integrals cache. 

113 ''' 

114 pass 

115 

116 

117class Elliptic(_Named): 

118 '''Elliptic integrals and functions. 

119 

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 

128 

129 def __init__(self, k2=0, alpha2=0, kp2=None, alphap2=None, **name): 

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

131 

132 @kwarg name: Optional C{B{name}=NN} (C{str}). 

133 

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

135 

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) 

144 

145 @Property_RO 

146 def alpha2(self): 

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

148 ''' 

149 return self._alpha2 

150 

151 @Property_RO 

152 def alphap2(self): 

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

154 ''' 

155 return self._alphap2 

156 

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

182 

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 

189 

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 

211 

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 

223 

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

225 

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 

232 

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 

239 

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 

275 

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) 

280 

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 

287 

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 

294 

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 

301 

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 

308 

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

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

311 

312 @arg sn: sin(φ). 

313 @arg cn: cos(φ). 

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

315 

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

317 

318 @raise EllipticError: Invalid invokation or no convergence. 

319 ''' 

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

321 

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

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

324 

325 @arg sn: sin(φ). 

326 @arg cn: cos(φ). 

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

328 

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

330 

331 @raise EllipticError: Invalid invokation or no convergence. 

332 ''' 

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

334 

335 def deltaEinv(self, stau, ctau): 

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

337 

338 @arg stau: sin(τ) 

339 @arg ctau: cos(τ) 

340 

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

342 

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 

350 

351 except Exception as X: 

352 raise _ellipticError(self.deltaEinv, stau, ctau, cause=X) 

353 

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

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

356 

357 @arg sn: sin(φ). 

358 @arg cn: cos(φ). 

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

360 

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

362 

363 @raise EllipticError: Invalid invokation or no convergence. 

364 ''' 

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

366 

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

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

369 

370 @arg sn: sin(φ). 

371 @arg cn: cos(φ). 

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

373 

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

375 

376 @raise EllipticError: Invalid invokation or no convergence. 

377 ''' 

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

379 

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

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

382 

383 @arg sn: sin(φ). 

384 @arg cn: cos(φ). 

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

386 

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

388 

389 @raise EllipticError: Invalid invokation or no convergence. 

390 ''' 

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

392 

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

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

395 

396 @arg sn: sin(φ). 

397 @arg cn: cos(φ). 

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

399 

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

401 (C{float}). 

402 

403 @raise EllipticError: Invalid invokation or no convergence. 

404 ''' 

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

406 

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 

436 

437 @Property_RO 

438 def eps(self): 

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

440 ''' 

441 return self._cDEKEeps.eps 

442 

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

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

445 Jacobi elliptic functions. 

446 

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

450 

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

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

453 

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 

461 

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

463 self.deltaD, _fD) 

464 

465 def fDelta(self, sn, cn): 

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

467 

468 @arg sn: sin(φ). 

469 @arg cn: cos(φ). 

470 

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 

478 

479 except Exception as X: 

480 raise _ellipticError(self.fDelta, sn, cn, k2=k2, cause=X) 

481 

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. 

485 

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

489 

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

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

492 

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) 

517 

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

519 self.deltaE, _fE, 

520 k2_0=self.k2==0) 

521 

522 def fEd(self, deg): 

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

524 the argument given in C{degrees}. 

525 

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

527 

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

529 

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 

541 

542 def fEinv(self, x): 

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

544 

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

546 

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

548 (C{float}). 

549 

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) 

556 

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. 

560 

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

564 

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

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

567 

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 

575 

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) 

579 

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

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

582 Jacobi elliptic functions. 

583 

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

587 

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

589 

590 @raise EllipticError: Invalid invokation or no convergence. 

591 

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

597 

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) 

604 

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. 

608 

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

612 

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

614 

615 @raise EllipticError: Invalid invokation or no convergence. 

616 

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

621 

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) 

628 

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. 

632 

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

636 

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

638 

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) 

645 

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) 

661 

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

663 

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) 

681 

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) 

687 

688 def _jam(self, x): 

689 '''Jacobi amplitude function. 

690 

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 

707 

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

732 

733 @Property_RO 

734 def k2(self): 

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

736 ''' 

737 return self._k2 

738 

739 @Property_RO 

740 def kp2(self): 

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

742 ''' 

743 return self._kp2 

744 

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

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

747 

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

752 

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

754 or B{C{alphap2}}. 

755 

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) 

763 

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 

769 

770 def _S(**kwds): 

771 return Scalar_(Error=EllipticError, **kwds) 

772 

773 self._k2 = _S(k2 = k2, low=None, high=_1_0) 

774 self._kp2 = _S(kp2=_1p2(kp2, k2)) # low=_0_0 

775 

776 self._alpha2 = _S(alpha2 = alpha2, low=None, high=_1_0) 

777 self._alphap2 = _S(alphap2=_1p2(alphap2, alpha2)) # low=_0_0 

778 

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) 

795 

796 self._iteration = 0 

797 

798 def sncndn(self, x, jam=False): 

799 '''The Jacobi amplitude and elliptic function. 

800 

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

802 @kwarg jam: If C{True}, use the Jacobi amplitude otherwise 

803 Bulirsch' function (C{bool}). 

804 

805 @return: An L{Elliptic3Tuple}C{(sn, cn, dn)} with C{*n(B{x}, k)}. 

806 

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

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 

835 else: 

836 sn = tanh(x) # accurate for large abs(x) 

837 cn = dn = _1_0 / cosh(x) 

838 

839 except Exception as X: 

840 raise _ellipticError(self.sncndn, x, kp2=self.kp2, cause=X) 

841 

842 return Elliptic3Tuple(sn, cn, dn, iteration=self._iteration - i) 

843 

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) 

849 

850 @staticmethod 

851 def fRC(x, y): 

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

853 

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

855 

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) 

860 

861 @staticmethod 

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

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

864 

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

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

867 

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) 

875 

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

880 

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

882 

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) 

890 

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

895 

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

897 

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) 

910 

911 @staticmethod 

912 def fRJ(x, y, z, p): # *over 

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

914 

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

916 

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) 

924 

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) 

934 

935_allPropertiesOf_n(16, Elliptic) # # PYCHOK assert, see Elliptic.reset 

936 

937 

938class EllipticError(_ValueError): 

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

940 ''' 

941 pass 

942 

943 

944class Elliptic3Tuple(_NamedTuple): 

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

946 ''' 

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

948 _Units_ = ( Scalar, Scalar, Scalar) 

949 

950 

951class _List(list): 

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

953 ''' 

954 _a0 = None 

955 

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

957 list.__init__(self, xyzp) 

958 

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 

968 

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 

994 

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 

1003 

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 

1009 

1010 

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) 

1019 

1020 

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()! 

1030 

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 

1038 

1039 def __float__(self): 

1040 return _fsum(self) # nonfinites=True 

1041 

1042 

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 

1061 

1062 

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 

1068 

1069 

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

1076 

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) 

1081 

1082 except Exception as X: 

1083 n = NN(_delta_, typename(fX)[1:]) 

1084 raise _ellipticError(n, sn, cn, dn, cause=X) 

1085 

1086 

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 

1092 

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

1094 

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) 

1100 

1101 

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 

1121 

1122 

1123def _3over(a, b): 

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

1125 ''' 

1126 return _over(_3_0, a * b) 

1127 

1128 

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) 

1149 

1150 

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 

1169 

1170 

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 

1177 

1178 

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 

1199 

1200 

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 

1213 

1214 

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 

1224 

1225 

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) 

1231 

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 

1258 

1259 

1260class _RJfma(object): 

1261 '''(INTERNAL) Carlson, "fma"able. 

1262 ''' 

1263 def __init__(self, *args): 

1264 self._Rj = _RJ(*args) 

1265 

1266 def ma(self, b, c): 

1267 r = fma(self._Rj, b, c) 

1268 return float(r) 

1269 

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.