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:Charles@Karney.com>} (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

78 from pygeodesy.constants import EPS, _EPStol as _TolJAC, INF, PI, PI_2, PI_4, \

79 _0_0, _0_125, _0_25, _0_5, _1_0, _1_64th, _2_0, \

80 _N_2_0, _3_0, _4_0, _6_0, _8_0, _180_0, _360_0

81 from pygeodesy.errors import _ValueError, _xkwds_pop

82 from pygeodesy.fmath import fdot, Fsum, hypot1

83# from pygeodesy.fsums import Fsum # from .fmath

84 from pygeodesy.interns import NN, _DOT_, _f_, _SPACE_

85 from pygeodesy.karney import _ALL_LAZY, _signBit

86# from pygeodesy.lazily import _ALL_LAZY # from .karney

87 from pygeodesy.named import _Named, _NamedTuple, _NotImplemented

88 from pygeodesy.props import _allPropertiesOf_n, Property_RO, property_RO, \

89 _update_all

90 from pygeodesy.streprs import Fmt, unstr

91 from pygeodesy.units import Scalar, Scalar_

92 from pygeodesy.utily import sincos2, sincos2d

93

94 from math import asinh, atan, atan2, ceil, cosh, fabs, floor, sin, sqrt, tanh

95

96__all__ = _ALL_LAZY.elliptic

97__version__ = '23.05.15'

98

99_delta_ = 'delta'

100_invokation_ = 'invokation'

101_TolRD = pow(EPS * 0.002, _0_125) # 8th root: quadquadratic?, octic?, ocrt?

102_TolRF = pow(EPS * 0.030, _0_125) # 4th root: biquadratic, quartic, qurt?

103_TolRG0 = _TolJAC * 2.7

104_TRIPS = 31 # Max depth, 7 might be sufficient

105

106

107 class _Complete(object):

108 '''(INTERAL) Hold complete integrals.

109 '''

110 def __init__(self, **kwds):

111 self.__dict__ = kwds

112

113

114 class _Deferred(list):

115 '''(INTERNAL) Collector for L{_Deferred_Fsum}.

116 '''

117 def __init__(self, *xs):

118 if xs:

119 list.__init__(self, xs)

120

121 def __add__(self, other): # PYCHOK no cover

122 return _NotImplemented(self, other)

123

124 def __iadd__(self, x): # overide C{list} += C{list}

125 # assert isscalar(x)

126 self.append(float(x))

127 return self

128

129 def __imul__(self, other): # PYCHOK no cover

130 return _NotImplemented(self, other)

131

132 def __isub__(self, x): # PYCHOK no cover

133 # assert isscalar(x)

134 self.append(-float(x))

135 return self

136

137 def __mul__(self, other): # PYCHOK no cover

138 return _NotImplemented(self, other)

139

140 def __radd__(self, other): # PYCHOK no cover

141 return _NotImplemented(self, other)

142

143 def __rsub__(self, other): # PYCHOK no cover

144 return _NotImplemented(self, other)

145

146 def __sub__(self, other): # PYCHOK no cover

147 return _NotImplemented(self, other)

148

149 @property_RO

150 def Fsum(self):

151 # get a L{_Deferred_Fsum} instance, pre-named

152 return _Deferred_Fsum()._facc(self) # known C{float}s

153

154

155 class _Deferred_Fsum(Fsum):

156 '''(INTERNAL) Deferred L{Fsum}.

157 '''

158 name = NN # pre-named, overridden below

159

160 def _update(self, **other): # PYCHOK don't ...

161 # ... waste time zapping non-existing Property/_ROs

162 if other or len(self.__dict__) > 2:

163 Fsum._update(self, **other)

164

165_Deferred_Fsum.name = _Deferred_Fsum.__name__ # PYCHOK once

166

167

168 class Elliptic(_Named):

169 '''Elliptic integrals and functions.

170

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

172 html/classGeographicLib_1_1EllipticFunction.html#details>}.

173 '''

174 _alpha2 = 0

175 _alphap2 = 0

176 _eps = EPS

177 _k2 = 0

178 _kp2 = 0

179

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

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

182

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

184

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

186

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

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

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

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

191 '''

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

193

194 if name:

195 self.name = name

196

197 @Property_RO

198 def alpha2(self):

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

200 '''

201 return self._alpha2

202

203 @Property_RO

204 def alphap2(self):

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

206 '''

207 return self._alphap2

208

209 @Property_RO

210 def cD(self):

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

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

213 '''

214 return self._reset_cDcEcKcKE_eps.cD

215

216 @Property_RO

217 def cE(self):

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

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

220 '''

221 return self._reset_cDcEcKcKE_eps.cE

222

223 @Property_RO

224 def cG(self):

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

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

227 '''

228 return self._reset_cGcHcPi.cG

229

230 @Property_RO

231 def cH(self):

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

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

234 '''

235 return self._reset_cGcHcPi.cH

236

237 @Property_RO

238 def cK(self):

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

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

241 '''

242 return self._reset_cDcEcKcKE_eps.cK

243

244 @Property_RO

245 def cKE(self):

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

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

248 '''

249 return self._reset_cDcEcKcKE_eps.cKE

250

251 @Property_RO

252 def cPi(self):

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

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

255 '''

256 return self._reset_cGcHcPi.cPi

257

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

259 '''The periodic Jahnke's incomplete elliptic integral.

260

261 @arg sn: sin(φ).

262 @arg cn: cos(φ).

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

264

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

266

267 @raise EllipticError: Invalid invokation or no convergence.

268 '''

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

270

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

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

273

274 @arg sn: sin(φ).

275 @arg cn: cos(φ).

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

277

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

279

280 @raise EllipticError: Invalid invokation or no convergence.

281 '''

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

283

284 def deltaEinv(self, stau, ctau):

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

286

287 @arg stau: sin(τ)

288 @arg ctau: cos(τ)

289

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

291

292 @raise EllipticError: No convergence.

293 '''

294 # Function is periodic with period pi

295 t = atan2(-stau, -ctau) if _signBit(ctau) else atan2(stau, ctau)

296 return self.fEinv(t * self.cE / PI_2) - t

297

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

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

300

301 @arg sn: sin(φ).

302 @arg cn: cos(φ).

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

304

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

306

307 @raise EllipticError: Invalid invokation or no convergence.

308 '''

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

310

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

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

313

314 @arg sn: sin(φ).

315 @arg cn: cos(φ).

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

317

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

319

320 @raise EllipticError: Invalid invokation or no convergence.

321 '''

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

323

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

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

326

327 @arg sn: sin(φ).

328 @arg cn: cos(φ).

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

330

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

332

333 @raise EllipticError: Invalid invokation or no convergence.

334 '''

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

336

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

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

339

340 @arg sn: sin(φ).

341 @arg cn: cos(φ).

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

343

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

345 (C{float}).

346

347 @raise EllipticError: Invalid invokation or no convergence.

348 '''

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

350

351 def _deltaX(self, sn, cn, dn, cX, fX):

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

353 '''

354 if cn is None or dn is None:

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

356 raise _invokationError(n, sn, cn, dn)

357

358 if _signBit(cn):

359 cn, sn = -cn, -sn

360 return fX(sn, cn, dn) * PI_2 / cX - atan2(sn, cn)

361

362 @Property_RO

363 def eps(self):

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

365 '''

366 return self._reset_cDcEcKcKE_eps.eps

367

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

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

370 Jacobi elliptic functions.

371

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

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

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

375

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

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

378

379 @raise EllipticError: Invalid invokation or no convergence.

380 '''

381 def _fD(sn, cn, dn):

382 r = fabs(sn)**3

383 if r:

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

385 return r

386

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

388 self.deltaD, _fD)

389

390 def fDelta(self, sn, cn):

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

392

393 @arg sn: sin(φ).

394 @arg cn: cos(φ).

395

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

397 '''

398 k2 = self.k2

399 s = (_1_0 - k2 * sn**2) if k2 < 0 else (self.kp2

400 + ((k2 * cn**2) if k2 > 0 else _0_0))

401 return sqrt(s) if s else _0_0

402

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

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

405 Jacobi elliptic functions.

406

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

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

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

410

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

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

413

414 @raise EllipticError: Invalid invokation or no convergence.

415 '''

416 def _fE(sn, cn, dn):

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

418 '''

419 if sn:

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

421 kp2, k2 = self.kp2, self.k2

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

423 ei = _RF3(self, cn2, dn2, _1_0)

424 if k2:

425 ei -= _RD(self, cn2, dn2, _1_0, _3over(k2, sn2))

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

427 ei = k2 * fabs(cn) / dn

428 if kp2:

429 ei += (_RD( self, cn2, _1_0, dn2, _3over(k2, sn2)) +

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

431 else: # <https://DLMF.NIST.gov/19.25.E11>

432 ei = dn / fabs(cn)

433 ei -= _RD(self, dn2, _1_0, cn2, _3over(kp2, sn2))

434 ei *= fabs(sn)

435 else: # PYCHOK no cover

436 ei = _0_0

437 return ei

438

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

440 self.deltaE, _fE)

441

442 def fEd(self, deg):

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

444 the argument given in degrees.

445

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

447

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

449

450 @raise EllipticError: No convergence.

451 '''

452 if fabs(deg) < _180_0:

453 e = _0_0

454 else: # PYCHOK no cover

455 e = ceil(deg / _360_0 - _0_5)

456 deg -= e * _360_0

457 e *= self.cE * _4_0

458 sn, cn = sincos2d(deg)

459 return self.fE(sn, cn, self.fDelta(sn, cn)) + e

460

461 def fEinv(self, x):

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

463

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

465

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

467 (C{float}).

468

469 @raise EllipticError: No convergence.

470 '''

471 E2 = self.cE * _2_0

472 n = floor(x / E2 + _0_5)

473 y = x - E2 * n # y now in [-ec, ec)

474 # linear approximation

475 phi = PI * y / E2 # phi in [-pi/2, pi/2)

476 Phi = Fsum(phi)

477 # first order correction

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

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

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

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

482 fE = self.fE

483 _sncndnPhi = self._sncndnPhi

484 _Phi2 = Phi.fsum2_

485 self._iteration = 0 # aggregate

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

487 sn, cn, dn = _sncndnPhi(phi)

488 phi, e = _Phi2((y - fE(sn, cn, dn)) / dn)

489 if fabs(e) < _TolJAC:

490 self._iteration += i

491 break

492 else: # PYCHOK no cover

493 raise _no_convergenceError(e, _TolJAC, self.fEinv, x)

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

495

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

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

498 Jacobi elliptic functions.

499

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

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

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

503

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

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

506

507 @raise EllipticError: Invalid invokation or no convergence.

508 '''

509 def _fF(sn, cn, dn):

510 r = fabs(sn)

511 if r:

512 r *= _RF3(self, cn**2, dn**2, _1_0)

513 return r

514

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

516 self.deltaF, _fF)

517

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

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

520 Jacobi elliptic functions.

521

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

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

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

525

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

527

528 @raise EllipticError: Invalid invokation or no convergence.

529

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

531 geodesic in terms of this combination of elliptic

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

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

534 riIOAAAAQAAJ&pg=PA181>}.

535

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

537 GeographicLib.SourceForge.io/html/geodesic.html#geodellip>}

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

539 '''

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

541 self.cG, self.deltaG)

542

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

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

545 Jacobi elliptic functions.

546

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

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

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

550

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

552

553 @raise EllipticError: Invalid invokation or no convergence.

554

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

556 on the geodesic in terms of this combination of

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

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

559

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

561 GeographicLib.SourceForge.io/html/geodesic.html#geodellip>}

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

563 '''

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

565 self.cH, self.deltaH)

566

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

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

569 Jacobi elliptic functions.

570

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

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

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

574

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

576

577 @raise EllipticError: Invalid invokation or no convergence.

578 '''

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

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

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

582 self.cPi, self.deltaPi)

583

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

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

586 '''

587 def _fX(sn, cn, dn):

588 if sn:

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

590 r = _RF3(self, cn2, dn2, _1_0)

591 if aX:

592 z = cn2 + sn2 * self.alphap2

593 r += _RJ(self, cn2, dn2, _1_0, z, _3over(aX, sn2))

594 r *= fabs(sn)

595 else: # PYCHOK no cover

596 r = _0_0

597 return r

598

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

600

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

602 '''(INTERNAL) Helper for C{f.D}, C{.fE}, C{.fF} and C{._fXa}.

603 '''

604 self._iteration = 0 # aggregate

605 phi = sn = phi_or_sn

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

607 sn, cn, dn = self._sncndnPhi(phi)

608 if fabs(phi) >= PI: # PYCHOK no cover

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

610 # fall through

611 elif cn is None or dn is None:

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

613 raise _invokationError(n, sn, cn, dn)

614

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

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

617 elif cn > 0:

618 xi = fX(sn, cn, dn)

619 else:

620 xi = cX

621 return copysign0(xi, sn)

622

623 @Property_RO

624 def k2(self):

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

626 '''

627 return self._k2

628

629 @Property_RO

630 def kp2(self):

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

632 '''

633 return self._kp2

634

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

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

637

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

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

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

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

642

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

644 or B{C{alphap2}}.

645

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

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

648 that these conditions are met to enable accuracy to be

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

650 '''

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

652

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

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

655

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

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

658 Error=EllipticError)

659

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

661 # K E D

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

663 # k = 1: inf 1 inf

664 # Pi G H

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

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

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

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

669 #

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

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

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

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

674 # Pi(alpha2, 1) = inf

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

676

677 @Property_RO

678 def _reset_cDcEcKcKE_eps(self):

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

680 '''

681 k2 = self.k2

682 if k2:

683 kp2 = self.kp2

684 if kp2:

685 self._iteration = 0

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

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

688 cD = _RD(self, _0_0, kp2, _1_0, _3_0)

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

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

691 cE = _RG2(self, kp2, _1_0)

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

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

694 cK = _RF2(self, kp2, _1_0)

695 cKE = k2 * cD

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

697 else: # PYCHOK no cover

698 cD = cK = cKE = INF

699 cE = _1_0

700 eps = k2

701 else: # PYCHOK no cover

702 cD = PI_4

703 cE = cK = PI_2

704 cKE = _0_0 # k2 * cD

705 eps = EPS

706

707 return _Complete(cD=cD, cE=cE, cK=cK, cKE=cKE, eps=eps)

708

709 @Property_RO

710 def _reset_cGcHcPi(self):

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

712 '''

713 self._iteration = 0

714 alpha2 = self.alpha2

715 if alpha2:

716 alphap2 = self.alphap2

717 if alphap2:

718 kp2 = self.kp2

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

720 rj = _RJ(self, _0_0, kp2, _1_0, alphap2, _3_0)

721 cPi = cH = cG = self.cK

722 cG += (alpha2 - self.k2) * rj # G(alpha2, k)

723 cH -= alphap2 * rj # H(alpha2, k)

724 cPi += alpha2 * rj # Pi(alpha2, k)

725 else: # PYCHOK no cover

726 cG = cH = _RC(self, _1_0, alphap2)

727 cPi = INF # XXX or NAN?

728 else: # PYCHOK no cover

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

730 else:

731 cG, cPi, kp2 = self.cE, self.cK, self.kp2

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

733 # So write (for alpha2 = 0)

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

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

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

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

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

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

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

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

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

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

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

745

746 return _Complete(cG=cG, cH=cH, cPi=cPi)

747

748 def sncndn(self, x):

749 '''The Jacobi elliptic function.

750

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

752

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

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

755

756 @raise EllipticError: No convergence.

757 '''

758 self._iteration = 0 # reset

759 # Bulirsch's sncndn routine, p 89.

760 if self.kp2:

761 c, d, cd, mn_ = self._sncndnBulirsch4

762 dn = _1_0

763 sn, cn = sincos2(x * cd)

764 if sn:

765 a = cn / sn

766 c *= a

767 for m, n in mn_:

768 a *= c

769 c *= dn

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

771 a = c / m

772 sn = copysign0(_1_0 / hypot1(c), sn) # _signBit(sn)

773 cn = c * sn

774 if d and _signBit(self.kp2): # PYCHOK no cover

775 cn, dn = dn, cn

776 sn = sn / d # /= chokes PyChecker

777 else:

778 sn = tanh(x)

779 cn = dn = _1_0 / cosh(x)

780

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

782

783 @Property_RO

784 def _sncndnBulirsch4(self):

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

786 '''

787 # Bulirsch's sncndn routine, p 89.

788 d, mc = 0, self.kp2

789 if _signBit(mc): # PYCHOK no cover

790 d = _1_0 - mc

791 mc = neg(mc / d)

792 d = sqrt(d)

793

794 mn, a = [], _1_0

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

796 # This converges quadratically, max 6 trips

797 mc = sqrt(mc)

798 mn.append((a, mc))

799 c = (a + mc) * _0_5

800 t = _TolJAC * a

801 if fabs(a - mc) <= t:

802 self._iteration += i # accumulate

803 break

804 mc *= a

805 a = c

806 else: # PYCHOK no cover

807 raise _no_convergenceError(a - mc, t, None, kp=self.kp, kp2=self.kp2)

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

809 return c, d, cd, tuple(reversed(mn)) # mn reversed!

810

811 def _sncndnPhi(self, phi):

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

813 '''

814 sn, cn = sincos2(phi)

815 return Elliptic3Tuple(sn, cn, self.fDelta(sn, cn))

816

817 @staticmethod

818 def fRC(x, y):

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

820

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

822

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

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

825 '''

826 return _RC(None, x, y)

827

828 @staticmethod

829 def fRD(x, y, z):

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

831

832 @return: C{RD(x, y, z)}, equivalent to C{RJ(x, y, z, z)}.

833

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

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

836 '''

837 return _RD(None, x, y, z)

838

839 @staticmethod

840 def fRF(x, y, *z):

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

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

843

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

845

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

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

848 '''

849 return _RF3(None, x, y, *z) if z and z[0] else _RF2(None, x, y)

850

851 @staticmethod

852 def fRG(x, y, *z):

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

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

855

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

857

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

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

860 '''

861 return _RG3(None, x, y, *z) if z and z[0] else (

862 _RG2(None, x, y) * _0_5)

863

864 @staticmethod

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

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

867

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

869

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

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

872 '''

873 return _RJ(None, x, y, z, p)

874

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

876

877

878 class EllipticError(_ValueError):

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

880 '''

881 pass

882

883

884 class Elliptic3Tuple(_NamedTuple):

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

886 '''

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

888 _Units_ = ( Scalar, Scalar, Scalar)

889

890

891 class _Lxyz(list):

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

893 '''

894 _a = None

895 _a0 = None

896

897 def __init__(self, *xyz_): # x, y, z [, p]

898 list.__init__(self, xyz_)

899

900 def a0(self, n):

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

902 '''

903 t = tuple(self)

904 m = n - len(t)

905 if m > 0:

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

907 self._a0 = self._a = Fsum(*t).fover(n)

908 return self._a0

909

910 def asr3(self, a):

911 '''Compute next C{a}, C{sqrt(xyz_)} and C{fdot(sqrt(xyz))}.

912 '''

913 L = self

914 # assert a is L._a

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

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

917 L[:] = [(x + r) * _0_25 for x in L]

918 # assert L is self

919 L._a = a = (a + r) * _0_25

920 return a, s, r

921

922 def rescale(self, am, *xy_):

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

924 '''

925 for x in xy_:

926 yield (self._a0 - x) / am

927

928 def thresh(self, Tol):

929 '''Return the convergence threshold.

930 '''

931 return max(fabs(self._a0 - x) for x in self) / Tol

932

933

934 def _horner(S, e1, E2, E3, E4, E5, *over):

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

936 '''

937 E22 = E2**2

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

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

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

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

942 # converted to Horner-like form ...

943 e = e1 * 4084080

944 S *= e

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

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

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

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

949 S += 4084080

950 return S.fover((e * over[0]) if over else e)

951

952

953 def _invokationError(name, *args): # PYCHOK no cover

954 '''(INTERNAL) Return an L{EllipticError}.

955 '''

956 n = _DOT_(Elliptic.__name__, name)

957 n = _SPACE_(_invokation_, n)

958 return EllipticError(NN(n, repr(args))) # unstr

959

960

961 def _iterations(inst, i):

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

963 '''

964 if inst:

965 inst._iteration += i

966

967

968 def _no_convergenceError(d, tol, where, *args, **kwds_thresh): # PYCHOK no cover

969 '''(INTERNAL) Return an L{EllipticError}.

970 '''

971 n = Elliptic.__name__

972 if where:

973 n = _DOT_(n, where.__name__)

974 if kwds_thresh:

975 q = _xkwds_pop(kwds_thresh, thresh=False)

976 t = unstr(n, *args, **kwds_thresh)

977 else:

978 q = False

979 t = unstr(n, *args)

980 return EllipticError(Fmt.no_convergence(d, tol, thresh=q), txt=t)

981

982

983 def _3over(a, b):

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

985 '''

986 return _3_0 / (a * b)

987

988

989 def _RC(unused, x, y):

990 '''(INTERNAL) Defined only for y != 0 and x >= 0.

991 '''

992 d = x - y

993 if d < 0: # catch _NaN

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

995 d = -d

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

997 elif d == _0_0: # XXX d < EPS0? or EPS02 or _EPSmin

998 d, r = y, _1_0

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

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

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

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

1003 else:

1004 raise _invokationError(Elliptic.fRC.__name__, x, y)

1005 return r / sqrt(d)

1006

1007

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

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

1010 '''

1011 L = _Lxyz(x, y, z)

1012 a = L.a0(5)

1013 q = L.thresh(_TolRF)

1014 S = _Deferred()

1015 am, m = a, 1

1016 for i in range(_TRIPS):

1017 if fabs(am) > q: # max 7 trips

1018 _iterations(inst, i)

1019 break

1020 t = L[2] # z0...n

1021 a, s, r = L.asr3(a)

1022 S += _1_0 / ((t + r) * s[2] * m)

1023 m *= 4

1024 am = a * m

1025 else: # PYCHOK no cover

1026 raise _no_convergenceError(am, q, Elliptic.fRD, x, y, z, *over,

1027 thresh=True)

1028 x, y = L.rescale(-am, x, y)

1029 xy = x * y

1030 z = (x + y) / _3_0

1031 z2 = z**2

1032 S = S.Fsum.fmul(_3_0)

1033 return _horner(S, am * sqrt(a),

1034 xy - _6_0 * z2,

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

1036 (xy - z2) * _3_0 * z2,

1037 xy * z2 * z, *over)

1038

1039

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

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

1042 '''

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

1044 if a < b:

1045 a, b = b, a

1046 for i in range(_TRIPS):

1047 t = _TolRG0 * a

1048 if fabs(a - b) <= t: # max 4 trips

1049 _iterations(inst, i)

1050 return (PI / (a + b))

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

1052 else: # PYCHOK no cover

1053 raise _no_convergenceError(a - b, t, Elliptic.fRF, x, y)

1054

1055

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

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

1058 '''

1059 L = _Lxyz(x, y, z)

1060 a = L.a0(3)

1061 q = L.thresh(_TolRF)

1062 am, m = a, 1

1063 for i in range(_TRIPS):

1064 if fabs(am) > q: # max 6 trips

1065 _iterations(inst, i)

1066 break

1067 a, _, _ = L.asr3(a)

1068 m *= 4

1069 am = a * m

1070 else: # PYCHOK no cover

1071 raise _no_convergenceError(am, q, Elliptic.fRF, x, y, z,

1072 thresh=True)

1073 x, y = L.rescale(am, x, y)

1074 z = neg(x + y)

1075 xy = x * y

1076 e2 = xy - z**2

1077 e3 = xy * z

1078 e4 = e2**2

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

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

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

1082 # converted to Horner-like form ...

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

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

1085 S += Fsum(240240)

1086 return S.fover(sqrt(a) * 240240)

1087

1088

1089 def _RG2(inst, x, y): # 2-args and I{doubled}

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

1091 '''

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

1093 if a < b:

1094 a, b = b, a

1095 ab = a - b # fabs(a - b)

1096 S = _Deferred(_0_5 * (a + b)**2)

1097 m = -1

1098 for i in range(_TRIPS): # max 4 trips

1099 t = _TolRG0 * a

1100 if ab <= t:

1101 _iterations(inst, i)

1102 return S.Fsum.fover((a + b) / PI_2)

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

1104 ab = fabs(a - b)

1105 S += ab**2 * m

1106 m *= 2

1107 else: # PYCHOK no cover

1108 raise _no_convergenceError(ab, t, Elliptic.fRG, x, y)

1109

1110

1111 def _RG3(inst, x, y, z): # 3-arg version

1112 '''(INTERNAL) Never called with zero B{C{z}}, see C{.fRG}.

1113 '''

1114# if not z:

1115# y, z = z, y

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

1117 if rd: # Carlson, eq 1.7

1118 rd = _RD(inst, x, y, z, _3_0 * z / rd)

1119 xyz = x * y

1120 if xyz:

1121 xyz = sqrt(xyz / z**3)

1122 return Fsum(_RF3(inst, x, y, z), rd, xyz).fover(_2_0 / z)

1123

1124

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

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

1127 '''

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

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

1130

1131 L = _Lxyz(x, y, z, p)

1132 a = L.a0(5)

1133 q = L.thresh(_TolRD)

1134 S = _Deferred()

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

1136 am, m = a, 1

1137 for i in range(_TRIPS):

1138 if fabs(am) > q: # max 7 trips

1139 _iterations(inst, i)

1140 break

1141 a, s, _ = L.asr3(a)

1142 d = _xyzp(*s)

1143 if n:

1144 rc = _RC(inst, _1_0, n / d**2 + _1_0)

1145 n *= _1_64th # /= chokes PyChecker

1146 else:

1147 rc = _1_0 # == _RC(None, _1_0, _1_0)

1148 S += rc / (d * m)

1149 m *= 4

1150 am = a * m

1151 else: # PYCHOK no cover

1152 raise _no_convergenceError(am, q, Elliptic.fRJ, x, y, z, p,

1153 thresh=True)

1154 x, y, z = L.rescale(am, x, y, z)

1155 xyz = x * y * z

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

1157 p2 = p**2

1158 p3 = p2*p

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

1160 E2p = E2 * p

1161 S = S.Fsum.fmul(_6_0)

1162 return _horner(S, am * sqrt(a), E2,

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

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

1165 xyz * p2, *over)

1166

1167# **) MIT License

1168#

1169# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.

1170#

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

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

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

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

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

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

1177#

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

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

1180#

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

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

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

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

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

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

1187# OTHER DEALINGS IN THE SOFTWARE.