1

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

3

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

5

6 Class L{Elliptic} transcoded from I{Charles Karney}'s C++ class U{EllipticFunction

7<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1EllipticFunction.html>}

8 to pure Python, including symmetric integrals L{Elliptic.fRC}, L{Elliptic.fRD},

9 L{Elliptic.fRF}, L{Elliptic.fRG} and L{Elliptic.fRJ} as C{static methods}.

10

11 Python 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

23 Following is a copy of I{Karney}'s U{EllipticFunction.hpp

24<https://GeographicLib.SourceForge.io/C++/doc/EllipticFunction_8hpp_source.html>}

25 file C{Header}.

26

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

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

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

30

31 B{Elliptic integrals and functions.}

32

33 This provides the elliptic functions and integrals needed for

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

35 categories 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

44 In the latter case, an object is constructed giving the modulus

45 C{k} (and optionally the parameter C{alpha}). The modulus (and

46 parameter) are always passed as squares which allows C{k} to be

47 pure imaginary. (Confusingly, Abramowitz and Stegun call C{m = k**2}

48 the "parameter" and C{n = alpha**2} the "characteristic".)

49

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

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

52

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

54

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

56 period I{C{pi}}.

57

58 The computation of the elliptic integrals uses the algorithms given

59 in 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,

62 13--26 (1995) with the additional optimizations given U{here

63<https://DLMF.NIST.gov/19.36.i>}.

64

65 The computation of the Jacobi elliptic functions uses the algorithm

66 given in U{R. Bulirsch, Numerical Calculation of Elliptic Integrals

67 and Elliptic Functions<https://DOI.org/10.1007/BF01397975>},

68 Numerische Mathematik 7, 78--90 (1965).

69

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

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

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

73'''

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

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

76

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

78 from 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

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

85 from pygeodesy.fsums import Fsum, _sum, _ALL_LAZY

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

87 _invalid_, _invokation_, _negative_, _SPACE_

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

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

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

91 from pygeodesy.props import _allPropertiesOf_n, Property_RO, _update_all

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

93 from pygeodesy.units import Scalar, Scalar_

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

95

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

97 radians, sin, sqrt, tanh

98

99__all__ = _ALL_LAZY.elliptic

100__version__ = '24.03.22'

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

108 class _Cs(object):

109 '''(INTERAL) Complete integrals cache.

110 '''

111 def __init__(self, **kwds):

112 self.__dict__ = kwds

113

114

115 class _D(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

129 class 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.fsum2_, 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

886 class EllipticError(_ValueError):

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

888 '''

889 pass

890

891

892 class 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

899 class _L(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 for x in xs:

956 yield (a0 - x) / am

957

958

959 def _ab2(inst, x, y):

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

961 '''

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

963 if b > a:

964 a, b = b, a

965 yield a, b # initial x0, y0

966 for i in range(_TRIPS):

967 d = fabs(a - b)

968 t = _TolRG0 * a

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

970 _iterations(inst, i)

971 break

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

973 yield a, b # xn, yn

974 else: # PYCHOK no cover

975 raise _convergenceError(d, t)

976

977

978 def _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

985 def _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

1002 def _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

1017 def _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 F = Fsum

1027 e = e1 * 4084080

1028 S *= e

1029 S += F(E2 * -540540, 471240).fmul(E5)

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

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

1032 S += F(E2 * 417690, E22 * -255255, -875160).fmul(E2)

1033 S += 4084080

1034 return S.fdiv((over[0] * e) if over else e) # Fsum

1035

1036

1037 def _iterations(inst, i):

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

1039 '''

1040 if inst and i > 0:

1041 inst._iteration += i

1042

1043

1044 def _3over(a, b):

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

1046 '''

1047 return _over(_3_0, a * b)

1048

1049

1050 def _rC(unused, x, y):

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

1052 '''

1053 d = x - y

1054 if d < 0: # catch NaN

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

1056 d = -d

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

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

1059 d, r = y, _1_0

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

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

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

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

1064 else: # PYCHOK no cover

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

1066 return r / sqrt(d) # float

1067

1068

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

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

1071 '''

1072 L = _L(x, y, z)

1073 S = _D()

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

1075 if s:

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

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

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

1079 xy = x * y

1080 z = (x + y) / _3_0

1081 z2 = z**2

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

1083 xy - _6_0 * z2,

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

1085 (xy - z2) * _3_0 * z2,

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

1087

1088

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

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

1091 '''

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

1093 pass

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

1095

1096

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

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

1099 '''

1100 L = _L(x, y, z)

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

1102 pass

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

1104 z = neg(x + y)

1105 xy = x * y

1106 e2 = xy - z**2

1107 e3 = xy * z

1108 e4 = e2**2

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

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

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

1112 # converted to Horner-like form ...

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

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

1115 S += 240240

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

1117

1118

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

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

1121 '''

1122 m = -1 # neg!

1123 S = _D()

1124 # assert not S

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

1126 if S:

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

1128 m *= 2

1129 else: # initial

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

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

1132

1133

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

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

1136 '''

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

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

1139 if rd: # Carlson, eq 1.7

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

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

1142 return R.fover(_2_0)

1143

1144

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

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

1147 '''

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

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

1150

1151 L = _L(x, y, z, p)

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

1153 S = _D()

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

1155 if s:

1156 d = _xyzp(*s)

1157 if d:

1158 if n:

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

1160 n *= _1_64th # /= chokes PyChecker

1161 else:

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

1163 S += rc / (d * m)

1164 else: # PYCHOK no cover

1165 return NAN

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

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

1168 p2 = p**2

1169 p3 = p2 * p

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

1171 E2p = E2 * p

1172 xyz = x * y * z

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

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

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

1176 xyz * p2, *over) # Fsum

1177

1178# **) MIT License

1179#

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

1181#

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

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

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

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

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

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

1188#

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

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

1191#

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

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

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

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

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

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

1198# OTHER DEALINGS IN THE SOFTWARE.