Coverage for pygeodesy/elliptic.py: 96%
482 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-02 14:35 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-02 14:35 -0400
2# -*- coding: utf-8 -*-
4u'''I{Karney}'s elliptic functions and integrals.
6Class L{Elliptic} transcoded from I{Charles Karney}'s C++ class U{EllipticFunction
7<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1EllipticFunction.html>}
8to pure Python, including symmetric integrals L{Elliptic.fRC}, L{Elliptic.fRD},
9L{Elliptic.fRF}, L{Elliptic.fRG} and L{Elliptic.fRJ} as C{static methods}.
11Python method names follow the C++ member functions, I{except}:
13 - member functions I{without arguments} are mapped to Python properties
14 prefixed with C{"c"}, for example C{E()} is property C{cE},
16 - member functions with 1 or 3 arguments are renamed to Python methods
17 starting with an C{"f"}, example C{E(psi)} to C{fE(psi)} and C{E(sn,
18 cn, dn)} to C{fE(sn, cn, dn)},
20 - other Python method names conventionally start with a lower-case
21 letter or an underscore if private.
23Following is a copy of I{Karney}'s U{EllipticFunction.hpp
24<https://GeographicLib.SourceForge.io/C++/doc/EllipticFunction_8hpp_source.html>}
25file C{Header}.
27Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2023)
28and licensed under the MIT/X11 License. For more information, see the
29U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
31B{Elliptic integrals and functions.}
33This provides the elliptic functions and integrals needed for
34C{Ellipsoid}, C{GeodesicExact}, and C{TransverseMercatorExact}. Two
35categories of function are provided:
37 - functions to compute U{symmetric elliptic integrals
38 <https://DLMF.NIST.gov/19.16.i>}
40 - methods to compute U{Legrendre's elliptic integrals
41 <https://DLMF.NIST.gov/19.2.ii>} and U{Jacobi elliptic
42 functions<https://DLMF.NIST.gov/22.2>}.
44In the latter case, an object is constructed giving the modulus
45C{k} (and optionally the parameter C{alpha}). The modulus (and
46parameter) are always passed as squares which allows C{k} to be
47pure imaginary. (Confusingly, Abramowitz and Stegun call C{m = k**2}
48the "parameter" and C{n = alpha**2} the "characteristic".)
50In geodesic applications, it is convenient to separate the incomplete
51integrals into secular and periodic components, e.g.
53I{C{E(phi, k) = (2 E(k) / pi) [ phi + delta E(phi, k) ]}}
55where I{C{delta E(phi, k)}} is an odd periodic function with
56period I{C{pi}}.
58The computation of the elliptic integrals uses the algorithms given
59in U{B. C. Carlson, Computation of real or complex elliptic integrals
60<https://DOI.org/10.1007/BF02198293>} (also available U{here
61<https://ArXiv.org/pdf/math/9409227.pdf>}), Numerical Algorithms 10,
6213--26 (1995) with the additional optimizations given U{here
63<https://DLMF.NIST.gov/19.36.i>}.
65The computation of the Jacobi elliptic functions uses the algorithm
66given in U{R. Bulirsch, Numerical Calculation of Elliptic Integrals
67and Elliptic Functions<https://DOI.org/10.1007/BF01397975>},
68Numerische Mathematik 7, 78--90 (1965).
70The notation follows U{NIST Digital Library of Mathematical Functions
71<https://DLMF.NIST.gov>} chapters U{19<https://DLMF.NIST.gov/19>} and
72U{22<https://DLMF.NIST.gov/22>}.
73'''
74# make sure int/int division yields float quotient, see .basics
75from __future__ import division as _; del _ # PYCHOK semicolon
77from pygeodesy.basics import copysign0, map2, neg, neg_
78from pygeodesy.constants import EPS, INF, NAN, PI, PI_2, PI_4, \
79 _EPStol as _TolJAC, _0_0, _1_64th, \
80 _0_25, _0_5, _1_0, _2_0, _N_2_0, \
81 _3_0, _4_0, _6_0, _8_0, _180_0, \
82 _360_0, _over
83# from pygeodesy.errors import _ValueError # from .fmath
84from pygeodesy.fmath import fdot, hypot1, zqrt, _ValueError
85from pygeodesy.fsums import Fsum, _sum, _ALL_LAZY
86from pygeodesy.interns import NN, _delta_, _DOT_, _dunder_nameof, _f_, \
87 _invalid_, _invokation_, _negative_, _SPACE_
88from pygeodesy.karney import _K_2_0, _norm180, _signBit, _sincos2
89# from pygeodesy.lazily import _ALL_LAZY # from .fsums
90from pygeodesy.named import _Named, _NamedTuple, Fmt, unstr
91from pygeodesy.props import _allPropertiesOf_n, Property_RO, _update_all
92# from pygeodesy.streprs import Fmt, unstr # from .named
93from pygeodesy.units import Scalar, Scalar_
94# from pygeodesy.utily import sincos2 as _sincos2 # from .karney
96from math import asinh, atan, atan2, ceil, cosh, fabs, floor, \
97 radians, sin, sqrt, tanh
99__all__ = _ALL_LAZY.elliptic
100__version__ = '24.04.14'
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
108class _Cs(object):
109 '''(INTERAL) Complete integrals cache.
110 '''
111 def __init__(self, **kwds):
112 self.__dict__ = kwds
115class _Dsum(list):
116 '''(INTERNAL) Deferred C{Fsum}.
117 '''
118 def __call__(self, s):
119 try: # Fsum *= s
120 return Fsum(*self).fmul(s)
121 except ValueError: # Fsum(NAN) exception
122 return _sum(self) * s
124 def __iadd__(self, x):
125 list.append(self, x)
126 return self
129class Elliptic(_Named):
130 '''Elliptic integrals and functions.
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
141 def __init__(self, k2=0, alpha2=0, kp2=None, alphap2=None, name=NN):
142 '''Constructor, specifying the C{modulus} and C{parameter}.
144 @kwarg name: Optional name (C{str}).
146 @see: Method L{Elliptic.reset} for further details.
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)
155 if name:
156 self.name = name
158 @Property_RO
159 def alpha2(self):
160 '''Get α^2, the square of the parameter (C{float}).
161 '''
162 return self._alpha2
164 @Property_RO
165 def alphap2(self):
166 '''Get α'^2, the square of the complementary parameter (C{float}).
167 '''
168 return self._alphap2
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
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
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
211 return _Cs(cD=cD, cE=cE, cK=cK, cKE=cKE, eps=eps)
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
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
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
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)
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
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
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
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
296 def deltaD(self, sn, cn, dn):
297 '''Jahnke's periodic incomplete elliptic integral.
299 @arg sn: sin(φ).
300 @arg cn: cos(φ).
301 @arg dn: sqrt(1 − k2 * sin(2φ)).
303 @return: Periodic function π D(φ, k) / (2 D(k)) - φ (C{float}).
305 @raise EllipticError: Invalid invokation or no convergence.
306 '''
307 return _deltaX(sn, cn, dn, self.cD, self.fD)
309 def deltaE(self, sn, cn, dn):
310 '''The periodic incomplete integral of the second kind.
312 @arg sn: sin(φ).
313 @arg cn: cos(φ).
314 @arg dn: sqrt(1 − k2 * sin(2φ)).
316 @return: Periodic function π E(φ, k) / (2 E(k)) - φ (C{float}).
318 @raise EllipticError: Invalid invokation or no convergence.
319 '''
320 return _deltaX(sn, cn, dn, self.cE, self.fE)
322 def deltaEinv(self, stau, ctau):
323 '''The periodic inverse of the incomplete integral of the second kind.
325 @arg stau: sin(τ)
326 @arg ctau: cos(τ)
328 @return: Periodic function E^−1(τ (2 E(k)/π), k) - τ (C{float}).
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
338 except Exception as e:
339 raise _ellipticError(self.deltaEinv, stau, ctau, cause=e)
341 def deltaF(self, sn, cn, dn):
342 '''The periodic incomplete integral of the first kind.
344 @arg sn: sin(φ).
345 @arg cn: cos(φ).
346 @arg dn: sqrt(1 − k2 * sin(2φ)).
348 @return: Periodic function π F(φ, k) / (2 K(k)) - φ (C{float}).
350 @raise EllipticError: Invalid invokation or no convergence.
351 '''
352 return _deltaX(sn, cn, dn, self.cK, self.fF)
354 def deltaG(self, sn, cn, dn):
355 '''Legendre's periodic geodesic longitude integral.
357 @arg sn: sin(φ).
358 @arg cn: cos(φ).
359 @arg dn: sqrt(1 − k2 * sin(2φ)).
361 @return: Periodic function π G(φ, k) / (2 G(k)) - φ (C{float}).
363 @raise EllipticError: Invalid invokation or no convergence.
364 '''
365 return _deltaX(sn, cn, dn, self.cG, self.fG)
367 def deltaH(self, sn, cn, dn):
368 '''Cayley's periodic geodesic longitude difference integral.
370 @arg sn: sin(φ).
371 @arg cn: cos(φ).
372 @arg dn: sqrt(1 − k2 * sin(2φ)).
374 @return: Periodic function π H(φ, k) / (2 H(k)) - φ (C{float}).
376 @raise EllipticError: Invalid invokation or no convergence.
377 '''
378 return _deltaX(sn, cn, dn, self.cH, self.fH)
380 def deltaPi(self, sn, cn, dn):
381 '''The periodic incomplete integral of the third kind.
383 @arg sn: sin(φ).
384 @arg cn: cos(φ).
385 @arg dn: sqrt(1 − k2 * sin(2φ)).
387 @return: Periodic function π Π(φ, α2, k) / (2 Π(α2, k)) - φ
388 (C{float}).
390 @raise EllipticError: Invalid invokation or no convergence.
391 '''
392 return _deltaX(sn, cn, dn, self.cPi, self.fPi)
394 def _Einv(self, x):
395 '''(INTERNAL) Helper for C{.deltaEinv} and C{.fEinv}.
396 '''
397 E2 = self.cE * _2_0
398 n = floor(x / E2 + _0_5)
399 r = x - E2 * n # r in [-cE, cE)
400 # linear approximation
401 phi = PI * r / E2 # phi in [-PI_2, PI_2)
402 Phi = Fsum(phi)
403 # first order correction
404 phi = Phi.fsum_(self.eps * sin(phi * _2_0) / _N_2_0)
405 # For kp2 close to zero use asin(r / cE) or J. P. Boyd,
406 # Applied Math. and Computation 218, 7005-7013 (2012)
407 # <https://DOI.org/10.1016/j.amc.2011.12.021>
408 _Phi2, self._iteration = Phi.fsum2f_, 0 # aggregate
409 for i in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC
410 sn, cn, dn = self._sncndn3(phi)
411 if dn:
412 sn = self.fE(sn, cn, dn)
413 phi, d = _Phi2((r - sn) / dn)
414 else: # PYCHOK no cover
415 d = _0_0 # XXX continue?
416 if fabs(d) < _TolJAC: # 3-4 trips
417 _iterations(self, i)
418 break
419 else: # PYCHOK no cover
420 raise _convergenceError(d, _TolJAC)
421 return Phi.fsum_(n * PI) if n else phi
423 @Property_RO
424 def eps(self):
425 '''Get epsilon (C{float}).
426 '''
427 return self._cDEKEeps.eps
429 def fD(self, phi_or_sn, cn=None, dn=None):
430 '''Jahnke's incomplete elliptic integral in terms of
431 Jacobi elliptic functions.
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φ)).
437 @return: D(φ, k) as though φ ∈ (−π, π] (C{float}),
438 U{defined<https://DLMF.NIST.gov/19.2.E4>}.
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
448 return self._fXf(phi_or_sn, cn, dn, self.cD,
449 self.deltaD, _fD)
451 def fDelta(self, sn, cn):
452 '''The C{Delta} amplitude function.
454 @arg sn: sin(φ).
455 @arg cn: cos(φ).
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
465 except Exception as e:
466 raise _ellipticError(self.fDelta, sn, cn, k2=k2, cause=e)
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.
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φ)).
476 @return: E(φ, k) as though φ ∈ (−π, π] (C{float}),
477 U{defined<https://DLMF.NIST.gov/19.2.E5>}.
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
505 return self._fXf(phi_or_sn, cn, dn, self.cE,
506 self.deltaE, _fE)
508 def fEd(self, deg):
509 '''The incomplete integral of the second kind with
510 the argument given in C{degrees}.
512 @arg deg: Angle (C{degrees}).
514 @return: E(π B{C{deg}} / 180, k) (C{float}).
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
527 def fEinv(self, x):
528 '''The inverse of the incomplete integral of the second kind.
530 @arg x: Argument (C{float}).
532 @return: φ = 1 / E(B{C{x}}, k), such that E(φ, k) = B{C{x}}
533 (C{float}).
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)
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.
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φ)).
550 @return: F(φ, k) as though φ ∈ (−π, π] (C{float}),
551 U{defined<https://DLMF.NIST.gov/19.2.E4>}.
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
561 return self._fXf(phi_or_sn, cn, dn, self.cK,
562 self.deltaF, _fF)
564 def fG(self, phi_or_sn, cn=None, dn=None):
565 '''Legendre's geodesic longitude integral in terms of
566 Jacobi elliptic functions.
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φ)).
572 @return: G(φ, k) as though φ ∈ (−π, π] (C{float}).
574 @raise EllipticError: Invalid invokation or no convergence.
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>}.
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)
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.
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φ)).
597 @return: H(φ, k) as though φ ∈ (−π, π] (C{float}).
599 @raise EllipticError: Invalid invokation or no convergence.
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>}.
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)
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.
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φ)).
621 @return: Π(φ, α2, k) as though φ ∈ (−π, π] (C{float}).
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)
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
647 return self._fXf(phi_or_sn, cn, dn, cX, deltaX, _fX)
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)
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)
669 @Property_RO
670 def k2(self):
671 '''Get k^2, the square of the modulus (C{float}).
672 '''
673 return self._k2
675 @Property_RO
676 def kp2(self):
677 '''Get k'^2, the square of the complementary modulus (C{float}).
678 '''
679 return self._kp2
681 def reset(self, k2=0, alpha2=0, kp2=None, alphap2=None): # MCCABE 13
682 '''Reset the modulus, parameter and the complementaries.
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).
689 @raise EllipticError: Invalid B{C{k2}}, B{C{alpha2}}, B{C{kp2}}
690 or B{C{alphap2}}.
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)
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)
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)
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)
724 def sncndn(self, x):
725 '''The Jacobi elliptic function.
727 @arg x: The argument (C{float}).
729 @return: An L{Elliptic3Tuple}C{(sn, cn, dn)} with
730 C{*n(B{x}, k)}.
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)
758 except Exception as e:
759 raise _ellipticError(self.sncndn, x, kp2=self.kp2, cause=e)
761 return Elliptic3Tuple(sn, cn, dn, iteration=self._iteration)
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)
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)
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
797 @staticmethod
798 def fRC(x, y):
799 '''Degenerate symmetric integral of the first kind C{RC(x, y)}.
801 @return: C{RC(x, y)}, equivalent to C{RF(x, y, y)}.
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)
808 @staticmethod
809 def fRD(x, y, z, *over):
810 '''Degenerate symmetric integral of the third kind C{RD(x, y, z)}.
812 @return: C{RD(x, y, z) / over}, equivalent to C{RJ(x, y, z, z)
813 / over} with C{over} typically 3.
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)
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)}.
828 @return: C{RF(x, y, z)} or C{RF(x, y)} for missing or zero B{C{z}}.
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)
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)}.
843 @return: C{RG(x, y, z)} or C{RG(x, y)} for missing or zero B{C{z}}.
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)
858 @staticmethod
859 def fRJ(x, y, z, p):
860 '''Symmetric integral of the third kind C{RJ(x, y, z, p)}.
862 @return: C{RJ(x, y, z, p)}.
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)
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)
883_allPropertiesOf_n(15, Elliptic) # # PYCHOK assert, see Elliptic.reset
886class EllipticError(_ValueError):
887 '''Elliptic function, integral, convergence or other L{Elliptic} issue.
888 '''
889 pass
892class Elliptic3Tuple(_NamedTuple):
893 '''3-Tuple C{(sn, cn, dn)} all C{scalar}.
894 '''
895 _Names_ = ('sn', 'cn', 'dn')
896 _Units_ = ( Scalar, Scalar, Scalar)
899class _List(list):
900 '''(INTERNAL) Helper for C{_RD}, C{_RF3} and C{_RJ}.
901 '''
902 _a0 = None
903# _xyzp = ()
905 def __init__(self, *xyzp): # x, y, z [, p]
906 list.__init__(self, xyzp)
907 self._xyzp = xyzp
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
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
950 def rescale(self, am, *xs):
951 '''Rescale C{x}, C{y}, ...
952 '''
953 # assert am
954 a0 = self._a0
955 _am = _1_0 / am
956 for x in xs:
957 yield (a0 - x) * _am
960def _ab2(inst, x, y):
961 '''(INTERNAL) Yield Carlson 2-tuples C{(xn, yn)}.
962 '''
963 a, b = sqrt(x), sqrt(y)
964 if b > a:
965 b, a = a, b
966 for i in range(_TRIPS):
967 yield a, b # xi, yi
968 d = fabs(a - b)
969 t = _TolRG0 * a
970 if d <= t: # 3-4 trips
971 _iterations(inst, i)
972 break
973 a, b = ((a + b) * _0_5), sqrt(a * b)
974 else: # PYCHOK no cover
975 raise _convergenceError(d, t)
978def _convergenceError(d, tol, **thresh):
979 '''(INTERNAL) Format a no-convergence Error.
980 '''
981 t = Fmt.no_convergence(d, tol, **thresh)
982 return ValueError(t) # txt only
985def _deltaX(sn, cn, dn, cX, fX):
986 '''(INTERNAL) Helper for C{Elliptic.deltaD} thru C{.deltaPi}.
987 '''
988 try:
989 if cn is None or dn is None:
990 raise ValueError(_invalid_)
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)
997 except Exception as e:
998 n = NN(_delta_, fX.__name__[1:])
999 raise _ellipticError(n, sn, cn, dn, cause=e)
1002def _ellipticError(where, *args, **kwds_cause_txt):
1003 '''(INTERNAL) Format an L{EllipticError}.
1004 '''
1005 def _x_t_kwds(cause=None, txt=NN, **kwds):
1006 return cause, txt, kwds
1008 x, t, kwds = _x_t_kwds(**kwds_cause_txt)
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)
1017def _Horner(S, e1, E2, E3, E4, E5, *over):
1018 '''(INTERNAL) Horner form for C{_RD} and C{_RJ} below.
1019 '''
1020 E22 = E2**2
1021 # Polynomial is <https://DLMF.NIST.gov/19.36.E2>
1022 # (1 - 3*E2/14 + E3/6 + 9*E2**2/88 - 3*E4/22 - 9*E2*E3/52
1023 # + 3*E5/26 - E2**3/16 + 3*E3**2/40 + 3*E2*E4/20
1024 # + 45*E2**2*E3/272 - 9*(E3*E4+E2*E5)/68)
1025 # converted to Horner-like form ...
1026 e = e1 * 4084080
1027 S *= e
1028 S += Fsum(E2 * -540540, 471240).fmul(E5)
1029 S += Fsum(E2 * 612612, E3 * -540540, -556920).fmul(E4)
1030 S += Fsum(E2 * -706860, E22 * 675675, E3 * 306306, 680680).fmul(E3)
1031 S += Fsum(E2 * 417690, E22 * -255255, -875160).fmul(E2)
1032 S += 4084080
1033 if over:
1034 e *= over[0]
1035 return S.fdiv(e) # Fsum
1038def _iterations(inst, i):
1039 '''(INTERNAL) Aggregate iterations B{C{i}}.
1040 '''
1041 if inst and i > 0:
1042 inst._iteration += i
1045def _3over(a, b):
1046 '''(INTERNAL) Return C{3 / (a * b)}.
1047 '''
1048 return _over(_3_0, a * b)
1051def _rC(unused, x, y):
1052 '''(INTERNAL) Defined only for C{y != 0} and C{x >= 0}.
1053 '''
1054 d = x - y
1055 if d < 0: # catch NaN
1056 # <https://DLMF.NIST.gov/19.2.E18>
1057 d = -d
1058 r = atan(sqrt(d / x)) if x > 0 else PI_2
1059 elif d == 0: # XXX d < EPS0? or EPS02 or _EPSmin
1060 d, r = y, _1_0
1061 elif y > 0: # <https://DLMF.NIST.gov/19.2.E19>
1062 r = asinh(sqrt(d / y)) # atanh(sqrt((x - y) / x))
1063 elif y < 0: # <https://DLMF.NIST.gov/19.2.E20>
1064 r = asinh(sqrt(-x / y)) # atanh(sqrt(x / (x - y)))
1065 else: # PYCHOK no cover
1066 raise _ellipticError(Elliptic.fRC, x, y)
1067 return r / sqrt(d) # float
1070def _RD(inst, x, y, z, *over):
1071 '''(INTERNAL) Carlson, eqs 2.28 - 2.34.
1072 '''
1073 L = _List(x, y, z)
1074 S = _Dsum()
1075 for a, m, r, s in L.amrs4(inst, True, _TolRF):
1076 if s:
1077 S += _over(_3_0, (r + z) * s[2] * m)
1078 z = L[2] # s[2] = sqrt(z)
1079 x, y = L.rescale(-a * m, x, y)
1080 xy = x * y
1081 z = (x + y) / _3_0
1082 z2 = z**2
1083 return _Horner(S(_1_0), sqrt(a) * a * m,
1084 xy - _6_0 * z2,
1085 (xy * _3_0 - _8_0 * z2) * z,
1086 (xy - z2) * _3_0 * z2,
1087 xy * z2 * z, *over) # Fsum
1090def _rF2(inst, x, y): # 2-arg version, z=0
1091 '''(INTERNAL) Carlson, eqs 2.36 - 2.38.
1092 '''
1093 for a, b in _ab2(inst, x, y): # PYCHOK yield
1094 pass
1095 return _over(PI, a + b) # float
1098def _RF3(inst, x, y, z): # 3-arg version
1099 '''(INTERNAL) Carlson, eqs 2.2 - 2.7.
1100 '''
1101 L = _List(x, y, z)
1102 for a, m, _, _ in L.amrs4(inst, False, _TolRF):
1103 pass
1104 x, y = L.rescale(a * m, x, y)
1105 z = neg(x + y)
1106 xy = x * y
1107 e2 = xy - z**2
1108 e3 = xy * z
1109 e4 = e2**2
1110 # Polynomial is <https://DLMF.NIST.gov/19.36.E1>
1111 # (1 - E2/10 + E3/14 + E2**2/24 - 3*E2*E3/44
1112 # - 5*E2**3/208 + 3*E3**2/104 + E2**2*E3/16)
1113 # converted to Horner-like form ...
1114 S = Fsum(e4 * 15015, e3 * 6930, e2 * -16380, 17160).fmul(e3)
1115 S += Fsum(e4 * -5775, e2 * 10010, -24024).fmul(e2)
1116 S += 240240
1117 return S.fdiv(sqrt(a) * 240240) # Fsum
1120def _rG2(inst, x, y, PI_=PI_4): # 2-args
1121 '''(INTERNAL) Carlson, eqs 2.36 - 2.39.
1122 '''
1123 m = -1 # neg!
1124 S = None
1125 for a, b in _ab2(inst, x, y): # PYCHOK yield
1126 if S is None: # initial
1127 S = _Dsum()
1128 S += (a + b)**2 * _0_5
1129 else:
1130 S += (a - b)**2 * m
1131 m *= 2
1132 return S(PI_).fover(a + b)
1135def _rG3(inst, x, y, z): # 3-arg version
1136 '''(INTERNAL) C{x}, C{y} and C{z} all non-zero, see C{.fRG}.
1137 '''
1138 R = _RF3(inst, x, y, z) * z
1139 rd = (x - z) * (z - y) # - (y - z)
1140 if rd: # Carlson, eq 1.7
1141 R += _RD(inst, x, y, z, _3_0 / rd)
1142 R += sqrt(x * y / z)
1143 return R.fover(_2_0)
1146def _RJ(inst, x, y, z, p, *over):
1147 '''(INTERNAL) Carlson, eqs 2.17 - 2.25.
1148 '''
1149 def _xyzp(x, y, z, p):
1150 return (x + p) * (y + p) * (z + p)
1152 L = _List(x, y, z, p)
1153 n = neg(_xyzp(x, y, z, -p))
1154 S = _Dsum()
1155 for a, m, _, s in L.amrs4(inst, True, _TolRD):
1156 if s:
1157 d = _xyzp(*s)
1158 if d:
1159 if n:
1160 rc = _rC(inst, _1_0, n / d**2 + _1_0)
1161 n *= _1_64th # /= chokes PyChecker
1162 else:
1163 rc = _1_0 # == _rC(None, _1_0, _1_0)
1164 S += rc / (d * m)
1165 else: # PYCHOK no cover
1166 return NAN
1167 x, y, z = L.rescale(a * m, x, y, z)
1168 p = Fsum(x, y, z).fover(_N_2_0)
1169 p2 = p**2
1170 p3 = p2 * p
1171 E2 = Fsum(x * y, x * z, y * z, -p2 * _3_0)
1172 E2p = E2 * p
1173 xyz = x * y * z
1174 return _Horner(S(_6_0), sqrt(a) * a * m, E2,
1175 Fsum(p3 * _4_0, xyz, E2p * _2_0),
1176 Fsum(p3 * _3_0, E2p, xyz * _2_0).fmul(p),
1177 xyz * p2, *over) # Fsum
1179# **) MIT License
1180#
1181# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1182#
1183# Permission is hereby granted, free of charge, to any person obtaining a
1184# copy of this software and associated documentation files (the "Software"),
1185# to deal in the Software without restriction, including without limitation
1186# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1187# and/or sell copies of the Software, and to permit persons to whom the
1188# Software is furnished to do so, subject to the following conditions:
1189#
1190# The above copyright notice and this permission notice shall be included
1191# in all copies or substantial portions of the Software.
1192#
1193# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1194# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1195# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1196# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1197# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1198# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1199# OTHER DEALINGS IN THE SOFTWARE.