Coverage for pygeodesy/triaxials/bases.py: 92%

1

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

3

4 u'''(INTERNAL) Base classes for I{ordered} triaxial ellipsoid classes L{Conformal}, L{Conformal3},

5 L{Triaxial}, L{Triaxial3} and I{unordered} L{Triaxial_}.

6

7 Transcoded to pure Python from I{Karney}'s GeographicLib 2.7 C++ classes U{Ellipsoid3<https://

8 GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Ellipsoid3.html>},

9 U{Cartesian3<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Cartesian3.html>} and

10 U{Conformal3<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Conformal3.html>}.

11

12 GeographicLib 2.5.2 C++ class U{JacobiConformal<https://GeographicLib.SourceForge.io/C++/doc/

13 classGeographicLib_1_1JacobiConformal.html#details>}.

14

15 Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2024, 2025) and licensed under the MIT/X11 License.

16 For more information, see the U{GeographicLib 2.5.2 and 2.7<https://GeographicLib.SourceForge.io/>} documentation.

17

18 Enum-like C{Lat-/Longitude Kinds (LLK)}, see I{Karney}'s U{coord<https://GeographicLib.SourceForge.io/

19 C++/doc/classGeographicLib_1_1Triaxial_1_1Cartesian3.html>}:

20

21@var LLK.CONFORMAL: Jacobi conformal X and Y projection

22@var LLK.ELLIPSOIDAL: Ellipsoidal lat-, longitude and heading C{bet}, C{omg}, C{alp} (L{Ang})

23@var LLK.GEOCENTRIC: Geocentric lat-, longitude and heading C{phi}", C{lam}" and C{zet} (L{Ang})

24@var LLK.GEOCENTRIC_X: Geocentric with pole along major X axis

25@var LLK.GEODETIC: Geodetic lat-, longitude and heading C{phi}, C{lam} and C{zet} (L{Ang})

26@var LLK.GEODETIC_X: Geodetic with pole along major X axis

27@var LLK.GEODETIC_LON0: Geodetic lat-, longitude I{- lon0} and heading C{phi}, C{lam} and C{zet} (L{Ang})

28@var LLK.GEOGRAPHIC = LLK.GEODETIC

29@var LLK.PARAMETRIC: Parametric lat-, longitude and heading C{phi}', C{lam}' and C{zet} (L{Ang})

30@var LLK.PARAMETRIC_X: Parametric with pole along major X axis

31@var LLK.PLANETODETIC = LLK.GEODETIC

32@var LLK.PLANETOCENTRIC = LLK.GEOCENTRIC

33'''

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

35 from __future__ import division as _; del _ # noqa: E702 ;

36

37# from pygeodesy.angles import Ang # _MODS

38# from pygeodesy.basics import map1 # from .namedTuples

39 from pygeodesy.constants import EPS, EPS0, EPS02, EPS4, INT0, NAN, PI_3, PI2, PI4, \

40 _EPS2e4, _isfinite, float0_, _1_over, _0_0, _1_0, \

41 _N_1_0, _3_0, _4_0 # PYCHOK used!

42# from pygeodesy.ellipses import Ellipse, _isFlat # _MODS

43# from pygeodesy.ellipsoids import Ellipsoid, _EWGS84 # _MODS

44# from pygeodesy.elliptic import Elliptic # _MODS

45# from pygeodesy.errors import _ValueError, _xkwds # from .utily

46 from pygeodesy.fmath import cbrt, fmean_, hypot, norm2, sqrt0, fabs, sqrt

47 from pygeodesy.fsums import _Fsumf_, fsumf_

48# from pygeodesy.internals import typename # _MODS

49 from pygeodesy.interns import _a_, _b_, _c_, _inside_, _not_, _NOTEQUAL_, _null_, \

50 _outside_, _scale_, _SPACE_, _spherical_, _x_, _y_, _z_

51 from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _FOR_DOCS

52 from pygeodesy.named import _NamedEnum, _NamedEnumItem, _NamedTuple, _Pass

53# from pygeodesy.named import _lazyNamedEnumItem as _lazy # _MODS

54 from pygeodesy.namedTuples import Ellipse5Tuple, Vector4Tuple, map1

55 from pygeodesy.props import Property_RO, property_doc_, property_RO, \

56 deprecated_method, deprecated_property_RO

57# from pygeodesy.streprs import Fmt # _MODS

58 from pygeodesy.units import Degrees, Easting, Float, Height, Height_, _Lat0, \

59 Meter, Meter2, Meter3, Northing, Radius_, Scalar

60 from pygeodesy.utily import asin1, km2m, m2km, _ValueError, _xkwds

61 from pygeodesy.vector3d import _otherV3d, Vector3d

62

63# from math import fabs, sqrt # from .fmath

64

65 __all__ = _ALL_LAZY.triaxials_bases

66 __version__ = '26.03.12'

67

68 _bet_ = 'bet' # PYCHOK shared

69 _llk_ = 'llk' # PYCHOK shared

70 _KTpFlat = 1.5849625007

71 _MAXIT = 33 # 20 # PYCHOK shared

72 _not_ordered_ = _not_('ordered')

73 _omg_ = 'omg' # PYCHOK shared

74

75

76 class Conformal5Tuple(_NamedTuple): # see .Forward4Tuple

77 '''5-Tuple C{(x, y, z, scale, llk)} with the easting C{x} and

78 northing C{y} projection, C{scale} or C{NAN} I{but with}

79 C{z=INT0} I{and kind} C{llk=LLK.CONFORMAL} I{always}.

80 '''

81 _Names_ = (_x_, _y_, _z_, _scale_, _llk_)

82 _Units_ = ( Easting, Northing, _Pass, Scalar, _Pass)

83

84 def __new__(cls, x, y, z=INT0, scale=NAN, llk=None, **kwds): # **iteration_name

85 args = x, y, (z or INT0), scale, (llk or LLK.CONFORMAL)

86 return _NamedTuple.__new__(cls, args, **kwds)

87

88

89 class _LLK(str):

90 '''(INTERNAL) Lat-/Longitude Kind.

91 '''

92 def __init__(self, llk): # aka C++ alt

93 self._X = bool(llk.endswith('_X'))

94 str.__init__(llk)

95

96

97 class LLK(object):

98 '''Enum-like C{Lat-/Longitude Kinds (LLK)}, see U{coord<https://GeographicLib.

99 SourceForge.io/C++/doc/classGeographicLib_1_1Triaxial_1_1Cartesian3.html>}.

100 '''

101 CONFORMAL = _LLK('CONFORMAL')

102

103 ELLIPSOIDAL = _LLK('ELLIPSOIDAL') # bet, omg, alp

104 GEOCENTRIC = _LLK('GEOCENTRIC') # phi2p, lam2p, zet

105 GEOCENTRIC_X = _LLK('GEOCENTRIC_X')

106 GEODETIC = _LLK('GEODETIC') # phi, lam, zet

107 GEODETIC_LON0 = _LLK('GEODETIC_LON0')

108 GEODETIC_X = _LLK('GEODETIC_X')

109 GEOGRAPHIC = GEODETIC

110 PARAMETRIC = _LLK('PARAMETRIC') # phi1p, lam1p, zet

111 PARAMETRIC_X = _LLK('PARAMETRIC_X')

112 PLANETODETIC = GEODETIC

113 PLANETOCENTRIC = GEOCENTRIC

114

115 _CENTRICS = (GEOCENTRIC, GEOCENTRIC_X, PLANETOCENTRIC)

116 _DETICS = (GEODETIC, GEODETIC_X, GEODETIC_LON0, GEOGRAPHIC, PLANETODETIC)

117 _METRICS = (PARAMETRIC, PARAMETRIC_X)

118 _NOIDAL = (None, ELLIPSOIDAL)

119# _XCLUDE = (CONFORMAL, GEOGRAPHIC, PLANETOCENTRIC, PLANETODETIC)

120

121 def __getitem__(self, name):

122 llk = self.get(name, None)

123 if llk is None:

124 t = _MODS.internals.typename(self)

125 t = _MODS.streprs.Fmt.SQUARE(t, name)

126 raise _ValueError(t, name)

127 return llk

128

129 def get(self, name, dflt=None):

130 '''Get an C{LLK} by C{name}.

131 '''

132 llk = getattr(self, name, None)

133 return llk if isinstance(llk, _LLK) else dflt

134

135 def items(self):

136 '''Yield all C{LLK (name, value)} pairs.

137 '''

138 for n, llk in LLK.__class__.__dict__.items():

139 if isinstance(llk, _LLK):

140 yield n, llk

141

142 def keys(self):

143 '''Yield all C{LLK} names.

144 '''

145 for n, _ in self.items():

146 yield n

147

148 def values(self):

149 '''Yield all C{LLK} values.

150 '''

151 for _, llk in self.items():

152 yield llk

153

154 if not _FOR_DOCS: # PYCHOK force epydoc

155 LLK = LLK() # singleton

156 del _FOR_DOCS

157

158

159 def _HeightINT0(h):

160 return h if h is INT0 else Height(h=h)

161

162

163 class _UnOrderedTriaxialBase(_NamedEnumItem):

164 '''(INTERNAL) Base class for all I{unordered} triaxial classes.

165 '''

166 _ijk = _kji = None

167 _unordered = True

168

169 def __init__(self, a_triaxial, b=None, c=None, **name):

170 '''New I{unordered} C{Triaxial_}.

171

172 @arg a_triaxial: Large, C{X} semi-axis (C{scalar}, conventionally in

173 C{meter}) or an other L{Triaxial}, L{Triaxial_} or

174 L{TriaxialB} instance.

175 @kwarg b: Middle, C{Y} semi-axis (C{meter}, same units as B{C{a}}),

176 required if C{B{a_triaxial} is scalar}, ignored otherwise.

177 @kwarg c: Small, C{Z} semi-axis (C{meter}, like B{C{b}}).

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

179

180 @raise TriaxialError: Invalid semi-axis or -axes.

181 '''

182 try:

183 try:

184 a = a_triaxial

185 t = a._abc3

186 name = _xkwds(name, name=a.name)

187 except AttributeError:

188 t = Radius_(a=a), Radius_(b=b), Radius_(c=c)

189 except (TypeError, ValueError) as x:

190 raise TriaxialError(a=a, b=b, c=c, cause=x)

191 if name:

192 self.name = name

193

194 a, b, c = self._abc3 = t

195 if self._unordered: # == not isinstance(self, Triaxial)

196 s, _, t = sorted(t)

197 if not (_isfinite(t) and _isfinite(s) and s > 0):

198 raise TriaxialError(a=a, b=b, c=c) # txt=_invalid_

199 elif not (_isfinite(a) and a >= b >= c > 0): # see TriaxialB

200 raise TriaxialError(a=a, b=b, c=c, txt=_not_ordered_)

201 elif not (a > c and self._a2c2 > 0 and self.e2ac > 0):

202 raise TriaxialError(a=a, c=c, e2ac=self.e2ac, txt=_spherical_)

203

204 def __repr__(self):

205 '''Default C{repr(self)}.

206 '''

207 return self.toRepr(terse=0)

208

209# def __str__(self): # in _NamedEnumItem

210# return self.toStr()

211

212 @Property_RO

213 def a(self):

214 '''Get the C{largest, x} semi-axis (C{meter}, conventionally).

215 '''

216 a, _, _ = self._abc3

217 return a

218

219 @Property_RO

220 def a2(self):

221 '''Get C{a**2}.

222 '''

223 return self.a**2

224

225 @Property_RO

226 def _a2b2(self):

227 '''(INTERNAL) Get C{a**2 - b**2} == E_sub_e**2.

228 '''

229 a, b, _ = self._abc3

230 d = a - b

231 return (d * (a + b)) if d else _0_0

232

233 @Property_RO

234 def _a2_b2(self):

235 '''(INTERNAL) Get C{(a / b)**2}.

236 '''

237 a, b, _ = self._abc3

238 return (a / b)**2 if a != b else _1_0

239

240 @Property_RO

241 def abc3(self): # in geed3solve._a12d

242 '''Get the semi-axes as 3-tuple C{(a, b, c)}.

243 '''

244 return self._abc3

245

246 @Property_RO

247 def _a2b2c23(self): # in .triaxials.triaxial3

248 '''(INTERNAL) Get 3-tuple C{(a**2, b**2, c**2)}.

249 '''

250 return self.a2, self.b2, self.c2

251

252 @Property_RO

253 def _a2c2(self):

254 '''(INTERNAL) Get C{a**2 - c**2} == E_sub_x**2.

255 '''

256 a, _, c = self._abc3

257 d = a - c

258 return (d * (a + c)) if d else _0_0

259

260 @Property_RO

261 def area(self):

262 '''Get the surface area (C{meter} I{squared}).

263 '''

264 return self.areaKT(_KTpFlat) if self.isFlat else self.areaRG

265

266 def areaKT(self, *p):

267 '''I{Approximate} the surface area using U{Knud Thomson's

268 <https://WikiPedia.org/wiki/Ellipsoid#Approximate_formula>}

269 formula (C{meter} I{squared}).

270

271 @arg p: Exponent (C{scalar} > 0), 1.6075 for near-spherical

272 or 1.5849625007 for "near-flat" triaxials.

273 '''

274 a, b, c = self._abc3

275 _p = pow

276 p = p[0] if p else (_KTpFlat if self.isFlat else 1.6075)

277 a = _p(fmean_(_p(a * b, p), _p(a * c, p), _p(b * c, p)), _1_over(p))

278 return Meter2(areaKT=a * PI4)

279

280 @deprecated_method

281 def area_p(self, p=1.6075):

282 '''DEPRECATED on 2026-02-15, use method L{areaKT<Triaxial_.areaKT>}.'''

283 return Meter2(area_p=self.areaKT(p))

284

285 @Property_RO

286 def areaRG(self):

287 '''Get the surface area using Carlson's U{symmetric RG

288 <https://WikiPedia.org/wiki/Ellipsoid#Surface_Area>}

289 form (C{meter} I{squared}), see also C{Elliptic.fRG}

290 '''

291 t = sorted(self._a2b2c23) # all non-zero

292 r = _MODS.elliptic._rG3(*map(_1_over, t))

293 return Meter2(areaRG=self.volume * r * _3_0)

294

295 @Property_RO

296 def b(self):

297 '''Get the C{middle, y} semi-axis (C{meter}, same units as B{C{a}}).

298 '''

299 _, b, _ = self._abc3

300 return b

301

302 @Property_RO

303 def b2(self):

304 '''Get C{b**2}.

305 '''

306 return self.b**2

307

308 @Property_RO

309 def _b2_a2(self):

310 '''(INTERNAL) Get C{(b / a)**2}.

311 '''

312 a, b, _ = self._abc3

313 return (b / a)**2 if a != b else _1_0

314

315 @Property_RO

316 def _b2c2(self):

317 '''(INTERNAL) Get C{b**2 - c**2} == E_sub_y**2.

318 '''

319 _, b, c = self._abc3

320 d = b - c

321 return (d * (b + c)) if d else _0_0

322

323 @Property_RO

324 def c(self):

325 '''Get the C{smallest, z} semi-axis (C{meter}, same units as B{C{a}}).

326 '''

327 _, _, c = self._abc3

328 return c

329

330 @Property_RO

331 def c2(self):

332 '''Get C{c**2}.

333 '''

334 return self.c**2

335

336 @Property_RO

337 def _c2_a2(self):

338 '''(INTERNAL) Get C{(c / a)**2}.

339 '''

340 a, _, c = self._abc3

341 return (c / a)**2 if a != c else _1_0

342

343 @Property_RO

344 def _c2_b2(self):

345 '''(INTERNAL) Get C{(c / b)**2}.

346 '''

347 _, b, c = self._abc3

348 return (c / b)**2 if b != c else _1_0

349

350 @Property_RO

351 def e2ab(self):

352 '''Get the C{ab} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (b/a)**2}.

353 '''

354 return Float(e2ab=(_1_0 - self._b2_a2) or _0_0)

355

356# _1e2ab = _b2_a2 # == C{1 - e2ab} == C{(b/a)**2}

357

358 @Property_RO

359 def e2ac(self):

360 '''Get the C{ac} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/a)**2}.

361 '''

362 return Float(e2ac=(_1_0 - self._c2_a2) or _0_0)

363

364# _1e2ac = _c2_a2 # == C{1 - e2ac} == C{(c/a)**2}

365

366 @Property_RO

367 def e2bc(self):

368 '''Get the C{bc} ellipse' I{(1st) eccentricity squared} (C{scalar}), M{1 - (c/b)**2}.

369 '''

370 return Float(e2bc=(_1_0 - self._c2_b2) or _0_0)

371

372# _1e2bc = _c2_b2 # == C{1 - e2bc} == C{(c/b)**2}

373

374 def ellipse5(self, lat):

375 '''Get the equatorial or a parallel I{ellipse of lattitude}.

376

377 @arg lat: Geodetic latitude (C{degrees90}, C{str} or C{Ang}).

378

379 @return: An L{Ellipse5Tuple}C{(a, b, height, lat, beta)} with C{a},

380 C{b} and C{height} measured along this trixial's semi-axis

381 C{a}, C{b} and C{c}, respectively.

382

383 @see: Method L{Ellipsoid.circle4<pygeodesy.Ellipsoid.circle4>} for

384 further details.

385 '''

386 a, b, c = self._abc3

387 lat = _Lat0(lat)

388 if lat and c > 0:

389 E = _MODS.ellipsoids.Ellipsoid

390 if a > b:

391 r, z, lat, B = E(a, b=c).circle4(lat)

392 b *= r / a

393 a = r

394 elif b > a:

395 r, z, lat, B = E(b, b=c).circle4(lat)

396 a *= r / b

397 b = r

398 else: # a == b

399 r, z, lat, B = E(a, b=c).circle4(lat)

400 a = b = r

401 else: # equatorial or "flat"

402 z = lat = B = _0_0

403 return Ellipse5Tuple(a, b, z, lat, B)

404

405 def hartzell4(self, pov, los=False, **name):

406 '''Compute the intersection of this triaxial's surface with a Line-Of-Sight

407 from a Point-Of-View in space.

408

409 @see: Function L{hartzell4<triaxials.triaxial5.hartzell4>} for further details.

410 '''

411 return _MODS.triaxials.hartzell4(pov, los=los, tri_biax=self, **name)

412

413 def height4(self, x_xyz, y=None, z=None, normal=True, eps=EPS, **name):

414 '''Compute the projection on and the height above or below this triaxial's surface.

415

416 @see: Function L{height4<triaxials.triaxial5.height4>} for further details.

417 '''

418 return _MODS.triaxials.height4(x_xyz, y=y, z=z, tri_biax=self, normal=normal, eps=eps, **name)

419

420 @Property_RO

421 def isFlat(self):

422 '''Is this triaxial "flat", too pro-/oblate (C{bool})?

423 '''

424 _f = _MODS.ellipses._isFlat

425 c, b, a = sorted(self._abc3)

426 return _f(a, c) or _f(b, c) or _f(a, b)

427

428 @Property_RO

429 def isOblate(self):

430 '''Is this triaxial oblate (C{bool})?

431 '''

432 return not (self.isProlate or self.isSpherical)

433

434 @Property_RO

435 def isOrdered(self):

436 '''Is this triaxial I{ordered} and I{not spherical} (C{bool})?

437 '''

438 a, b, c = self._abc3

439 return bool(a >= b > c) # b > c!

440

441 @Property_RO

442 def isProlate(self):

443 '''Is this triaxial prolate (C{bool})?

444 '''

445 a, b, c = self._abc3

446 return a < b or b < c or a < c

447

448 @Property_RO

449 def isSpherical(self):

450 '''Is this triaxial I{spherical} (C{Radius} or INT0)?

451 '''

452 a, b, c = self._abc3

453 return a if a == b == c else INT0

454

455 def _norm2(self, s, c, *a):

456 '''(INTERNAL) Normalize C{s} and C{c} iff not already.

457 '''

458 if fabs(_hypot2_1(s, c)) > EPS02:

459 s, c = norm2(s, c)

460 if a:

461 s, c = norm2(s * self.b, c * a[0])

462 return float0_(s, c)

463

464 def normal3d(self, x_xyz, y=None, z=None, length=_1_0):

465 '''Get a 3-D vector I{on and perpendicular to} this triaxial's surface.

466

467 @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},

468 L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).

469 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored

470 otherwise.

471 @kwarg z: Z component (C{scalar}), like B{C{y}}.

472 @kwarg length: Optional, signed length in out-/inward direction (C{scalar}).

473

474 @return: A C{Vector3d(x_, y_, z_)} normalized to B{C{length}}, pointing out-

475 or inward for postive respectively negative B{C{length}}.

476

477 @raise TriaxialError: Zero length cartesian or vector.

478

479 @note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface,

480 use method L{Triaxial.sideOf} to validate.

481

482 @see: Methods L{Triaxial.height4} and L{Triaxial.sideOf}.

483 '''

484 # n = 2 * (x / a2, y / b2, z / c2)

485 # == 2 * (x, y * a2 / b2, z * a2 / c2) / a2 # iff ordered

486 # == 2 * (x, y / _b2_a2, z / _c2_a2) / a2

487 # == unit(x, y / _b2_a2, z / _c2_a2).times(length)

488 x, y, z = _otherV3d_(x_xyz, y, z).xyz3

489 n = Vector3d(x, y / self._b2_a2,

490 z / self._c2_a2, name__=self.normal3d)

491 u = n.length

492 if u < EPS0:

493 raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)

494 return n.times(length / u)

495

496 def normal4(self, x_xyz, y=None, z=None, height=0, normal=True):

497 '''Compute a cartesian at a B{C{height}} above or below this triaxial's surface.

498

499 @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},

500 L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).

501 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored

502 otherwise.

503 @kwarg z: Z component (C{scalar}), like B{C{y}}.

504 @kwarg normal: If C{True}, the B{C{height}} is I{perpendicular, plumb} to the

505 triaxial's surface, otherwise C{radially} to the center of this

506 triaxial (C{bool}).

507

508 @return: L{Vector4Tuple}C{(x, y, z, h)} with the cartesian coordinates C{x},

509 C{y} and C{z} and C{h} the I{signed, normal distance} to the triaxial's

510 surface in C{meter}, conventionally. Positive C{h} indicates, the

511 cartesian is outside the triaxial, negative C{h} means inside.

512

513 @raise TriaxialError: Zero length cartesian or vector.

514

515 @note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface,

516 use method L{Triaxial.sideOf} to validate.

517

518 @see: Methods L{Triaxial.normal3d} and L{Triaxial.height4}.

519 '''

520 v, h = _otherV3d_(x_xyz, y, z), Height_(height, low=None)

521 if h:

522 if v.length < EPS0:

523 raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)

524 if normal:

525 n = self.normal3d(v, length=h)

526 h = n.length

527 n += v

528 else:

529 h = h / v.length # /= chokes PyChecker

530 n = v.times(h + _1_0)

531 else:

532 n = v

533 return Vector4Tuple(n.x, n.y, n.z, h, name__=self.normal4)

534

535 def _order3(self, *abc, **reverse): # reverse=False

536 '''(INTERNAL) Un-/Order C{a}, C{b} and C{c}.

537

538 @return: 3-Tuple C{(a, b, c)} ordered by or un-ordered

539 (reverse-ordered) C{ijk} if C{B{reverse}=True}.

540 '''

541 ijk = self._order_ijk(**reverse)

542 return _getitems(abc, *ijk) if ijk else abc

543

544 def _order3d(self, v, **reverse): # reverse=False

545 '''(INTERNAL) Un-/Order a C{Vector3d}.

546

547 @return: Vector3d(x, y, z) un-/ordered.

548 '''

549 ijk = self._order_ijk(**reverse)

550 return v.classof(*_getitems(v.xyz3, *ijk)) if ijk else v

551

552 @Property_RO

553 def _ordered4(self):

554 '''(INTERNAL) Helper for C{_hartzell3} and C{_plumbTo5}.

555 '''

556 def _order2(reverse, a, b, c):

557 '''(INTERNAL) Un-Order C{a}, C{b} and C{c}.

558

559 @return: 2-Tuple C{((a, b, c), ijk)} with C{a} >= C{b} >= C{c}

560 and C{ijk} a 3-tuple with the initial indices.

561 '''

562 i, j, k = range(3)

563 if a < b:

564 a, b, i, j = b, a, j, i

565 if a < c:

566 a, c, i, k = c, a, k, i

567 if b < c:

568 b, c, j, k = c, b, k, j

569 # reverse (k, j, i) since (a, b, c) is reversed-sorted

570 ijk = (k, j, i) if reverse else (None if i < j < k else (i, j, k))

571 return (a, b, c), ijk

572

573 abc, T = self._abc3, self

574 if not self.isOrdered:

575 abc, ijk = _order2(False, *abc)

576 if ijk:

577 _, kji = _order2(True, *ijk)

578 T = _UnOrderedTriaxialBase(*abc)

579 T._ijk, T._kji = ijk, kji

580 return abc + (T,)

581

582 def _order_ijk(self, reverse=False):

583 '''(INTERNAL) Get the un-/order indices.

584 '''

585 return self._kji if reverse else self._ijk

586

587 @deprecated_property_RO

588 def perimeter4ab(self):

589 '''DEPRECATED on 2026.02.09, use property L{Ellipse<pygeodesy.Ellipse>}C{(a, b).perimeter2k_}.'''

590 a, b, _ = self._abc3

591 return Meter(perimeter4ab=_MODS.ellipses.Ellipse(a, b).perimeter2k_)

592

593 @deprecated_property_RO

594 def perimeter4ac(self):

595 '''DEPRECATED on 2026.02.09, use property L{Ellipse<pygeodesy.Ellipse>}C{(a, c).perimeter2k_}.'''

596 a, _, c = self._abc3

597 return Meter(perimeter4ac=_MODS.ellipses.Ellipse(a, c).perimeter2k_)

598

599 @deprecated_property_RO

600 def perimeter4bc(self):

601 '''DEPRECATED on 2026.02.09, use property L{Ellipse<pygeodesy.Ellipse>}C{(b, c).perimeter2k_}.'''

602 _, b, c = self._abc3

603 return Meter(perimeter4bc=_MODS.ellipses.Ellipse(b, c).perimeter2k_)

604

605 @Property_RO

606 def R2(self):

607 '''Get the I{authalic} earth radius (C{meter}), M{sqrt(area / PI4)}.

608 '''

609 r = self.isSpherical

610 return Meter(R2=r if r else sqrt(self.area / PI4)) # Radius

611

612 Rauthalic = R2

613

614 @Property_RO

615 def R3(self):

616 '''Get the I{volumetric} earth radius (C{meter}), M{(a * b * c)**(1/3)}.

617 '''

618 a, b, c = self._abc3

619 return Meter(R3=a if a == b == c else cbrt(a * b * c)) # Radius

620

621 Rvolumetric = R3

622

623 def _radialTo3(self, sbeta, cbeta, somega, comega):

624 '''(INTERNAL) I{Unordered} helper for C{.height4}.

625 '''

626 def _rphi(a, b, sphi, cphi):

627 # <https://WikiPedia.org/wiki/Ellipse#Polar_form_relative_to_focus>

628 # polar form: radius(phi) = a * b / hypot(a * sphi, b * cphi)

629 return (b / hypot(sphi, b / a * cphi)) if a > b else (

630 (a / hypot(cphi, a / b * sphi)) if a < b else a)

631

632 sa, ca = self._norm2(sbeta, cbeta)

633 sb, cb = self._norm2(somega, comega)

634

635 a, b, c = self._abc3

636 if a != b:

637 a = _rphi(a, b, sb, cb)

638 if a != c:

639 c = _rphi(a, c, sa, ca)

640 t = c * ca

641 return (t * cb), (t * sb), (c * sa)

642

643 def sideOf(self, x_xyz, y=None, z=None, eps=EPS4):

644 '''Is a cartesian on, above or below the surface of this triaxial?

645

646 @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},

647 L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).

648 @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} is C{scalar},

649 ignored otherwise.

650 @kwarg z: Z component (C{scalar}), like B{C{y}}.

651 @kwarg eps: On-surface tolerance (C{scalar}, distance I{squared}).

652

653 @return: C{INT0} if C{(B{x}, B{y}, B{z})} is near this triaxial's surface

654 within tolerance B{C{eps}}, otherwise the signed, radial distance

655 I{squared} (C{float}), nega-/positive for in- respectively outside

656 this triaxial.

657

658 @see: Methods L{Triaxial.height4} and L{Triaxial.normal3d}.

659 '''

660 v = _otherV3d_(x_xyz, y, z)

661 s = fsumf_(_N_1_0, *map(_over02, v.xyz3, self._abc3))

662 return INT0 if fabs(s) < eps else s

663

664 def _sideOn(self, v, eps=_EPS2e4):

665 s = self.sideOf(v.xyz, eps=eps)

666 if s: # PYCHOK no cover

667 t = _SPACE_((_inside_ if s < 0 else _outside_), self.toRepr())

668 raise TriaxialError(eps=eps, sideOf=s, x=v.x, y=v.y, z=v.z, txt=t)

669 return s

670

671 def toEllipsoid(self, **name):

672 '''Convert this triaxial to a I{biaxial} L{Ellipsoid}, provided 2 axes match.

673

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

675

676 @return: An L{Ellipsoid} with north along this C{Z} axis if C{a == b},

677 this C{Y} axis if C{a == c} or this C{X} axis if C{b == c}.

678

679 @raise TriaxialError: This C{a != b}, C{b != c} and C{c != a}.

680

681 @see: Method L{Ellipsoid.toTriaxial}.

682 '''

683 a, b, c = self._abc3

684 if a == b:

685 b = c # N = c-Z

686 elif b == c: # N = a-X

687 a, b = b, a

688 elif a != c: # N = b-Y

689 t = _SPACE_(_a_, _NOTEQUAL_, _b_, _NOTEQUAL_, _c_)

690 raise TriaxialError(a=a, b=b, c=c, txt=t)

691 return _MODS.ellipsoids.Ellipsoid(a, b=b, name=self._name__(name))

692

693 toBiaxial = toEllipsoid

694

695 def toStr(self, prec=9, terse=-3, **name): # PYCHOK signature

696 '''Return this C{Triaxial} as a string.

697

698 @kwarg prec: Precision, number of decimal digits (0..9).

699 @kwarg terse: Limit the number of items (C{int}, 3..11),

700 use C{B{terse}=0} or C{=None} for all.

701 @kwarg name: Optional name (C{str}), to override or C{None}

702 to exclude this triaxial's name.

703

704 @return: This C{Triaxial}'s attributes (C{str}).

705 '''

706 T = _UnOrderedTriaxialBase

707 m = _MODS.triaxials

708 C = m.Triaxial3B

709 if isinstance(self, C):

710 t = T.b, C.e2, C.k2, C.kp2

711 else:

712 t = T.a, # props

713 C = m.ConformalSphere

714 t += (C.ab, C.bc) if isinstance(self, C) else (T.b, T.c)

715 C = _Triaxial3Base

716 t += (C.k2, C.kp2) if isinstance(self, C) else \

717 (T.e2ab, T.e2bc, T.e2ac)

718 for C in (m.Conformal, m.Conformal3):

719 if isinstance(self, C):

720 t += C.xyQ2,

721 break

722 t += T.volume, T.area, T.R2

723 if terse:

724 t = t[:terse]

725 return self._instr(prec=prec, props=t, **name)

726

727 @Property_RO

728 def unOrdered(self):

729 '''Is this triaxial I{un-ordered} and I{not spherical} (C{bool})?

730 '''

731 return not (self.isOrdered or bool(self.isSpherical))

732

733 @Property_RO

734 def volume(self):

735 '''Get the volume (C{meter**3}), M{4 / 3 * PI * a * b * c}.

736 '''

737 a, b, c = self._abc3

738 return Meter3(volume=a * b * c * PI_3 * _4_0)

739

740

741 class _OrderedTriaxialBase(_UnOrderedTriaxialBase):

742 '''(INTERNAL) Base class for all I{ordered} triaxial classes.

743 '''

744 _unordered = False

745

746 def __init__(self, a_triaxial, b=None, c=None, **name):

747 '''New I{ordered} L{Triaxial}, L{Triaxial3}, L{Conformal} or L{Conformal3}.

748

749 @arg a_triaxial: Largest semi-axis (C{scalar}, conventionally in C{meter})

750 or an other L{Triaxial} or L{Triaxial_} instance.

751 @kwarg b: Middle semi-axis (C{meter}, same units as B{C{a}}), required

752 if C{B{a_triaxial} is scalar}, ignored otherwise.

753 @kwarg c: Smallest semi-axis (C{meter}, like B{C{b}}).

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

755

756 @note: The semi-axes must be ordered as C{B{a} >= B{b} >= B{c} > 0} and

757 must be ellipsoidal, C{B{a} > B{c}}.

758

759 @raise TriaxialError: Semi-axes unordered, spherical or invalid.

760 '''

761 _UnOrderedTriaxialBase.__init__(self, a_triaxial, b=b, c=c, **name)

762

763 @Property_RO

764 def _a2b2_a2c2(self):

765 '''@see: Methods C{.forwardBetaOmega} and property C{._k2_kp2E}.

766 '''

767 s = self._a2c2

768 if s:

769 s = self._a2b2 / s

770 return s or _0_0

771

772 @Property_RO

773 def area(self):

774 '''Get the surface area (C{meter} I{squared}).

775

776 @see: U{Surface area<https://WikiPedia.org/wiki/Ellipsoid#Surface_area>}.

777 '''

778 a = self._areax

779 if a is None:

780 a = _UnOrderedTriaxialBase(self).area # or self.area21k

781 return a

782

783 @Property_RO

784 def area21k(self):

785 '''Get the surface area using incomplete elliptic integrals of the

786 2nd and 1st kind (C{meter} I{squared}), see also C{Elliptic.fE}

787 respectively C{Elliptic.fF}.

788 '''

789 a = self._areax

790 if a is None:

791 k2, kp2 = t = self._k2_kp2E

792 if self.e2ac < EPS or min(t) < EPS or max(t) > _1_0:

793 a = self.areaKT() # "flat" or near-spherical

794 else:

795 aE = _MODS.elliptic.Elliptic(k2=kp2, kp2=k2) # swapped!

796 s = sqrt(self.e2ac) # == sin(phi)

797 t = self._c2_a2 / s # == cos(phi)**2 / sin(phi)

798 r = asin1(s) # phi

799 a, b, c = self._abc3

800 t = (aE.fE(r) * s + aE.fF(r) * t) * a * b

801 a = Meter2(area21k=(c**2 + t) * PI2)

802 return a

803

804 @Property_RO

805 def _areax(self):

806 '''(INTERNAL) Get the area as ellipsoidal or C{None}.

807 '''

808 a, b, c = self._abc3

809 return None if a != b else \

810 _MODS.ellipsoids.Ellipsoid(a, b=c).areax

811

812 @Property_RO

813 def _k2_kp2E(self):

814 '''(INTERNAL) Get elliptic C{k2} and C{kp2} for C{._xE}, C{._yE} and C{.areaE}.

815 '''

816 # k2 = a2b2 / a2c2 * c2_b2

817 # kp2 = b2c2 / a2c2 * a2_b2

818 # b2 = b**2

819 # xE = Elliptic(k2, -a2b2 / b2, kp2, a2_b2)

820 # yE = Elliptic(kp2, b2c2 / b2, k2, c2_b2)

821 # aE = Elliptic(kp2, 0, k2, 1)

822 k2 = (self._c2_b2 * self._a2b2_a2c2) or _0_0

823 kp2 = (self._a2_b2 * self._b2c2 / self._a2c2) if k2 else _1_0

824 return k2, kp2

825

826 def _radialTo3(self, sbeta, cbeta, somega, comega):

827 '''(INTERNAL) Convert I{ellipsoidal} lat- C{beta} and longitude

828 C{omega} to a cartesian I{on this triaxial's surface}, also

829 I{ordered} helper for C{.height4 with normal=False}.

830 '''

831 sa, ca = self._norm2(sbeta, cbeta)

832 sb, cb = self._norm2(somega, comega)

833

834 b2_a2 = self._b2_a2 # == (b/a)**2

835 c2_a2 = -self._c2_a2 # == -(c/a)**2

836 a2c2_a2 = self. e2ac # (a**2 - c**2) / a**2 == 1 - (c/a)**2

837

838 x2 = _Fsumf_(_1_0, -b2_a2 * sa**2, c2_a2 * ca**2).fover(a2c2_a2)

839 z2 = _Fsumf_(c2_a2, sb**2, b2_a2 * cb**2).fover(a2c2_a2)

840

841 x, y, z = self._abc3

842 x *= cb * _sqrt0(x2)

843 y *= ca * sb

844 z *= sa * _sqrt0(z2)

845 return x, y, z

846

847

848 class _Triaxial3Base(_OrderedTriaxialBase):

849 '''(INTERNAL) Base class for I{unordered} triaxialC{3} classes.

850 '''

851 _e2_k2_kp2 = None

852 _Lon0 = None

853

854 @Property_RO

855 def e2(self):

856 '''Get the I{squared eccentricity} (C{scalar}), M{(a**2 - c**2) / b**2}.

857 '''

858 if self._e2_k2_kp2:

859 e2, _, _ = self._e2_k2_kp2

860 else:

861 e2 = self._a2c2 / self.b2

862 return Float(e2=e2)

863

864 def _init_abc3_e2_k2_kp2(self, b, e2, k2, kp2, **name):

865 '''(INTERNAL) C{Triaxial3B.__init__}.

866 '''

867 if name:

868 self.name = name

869 s = k2 + kp2

870 if s > 0 and s != _1_0:

871 k2 = k2 / s # /= chokes PyChecker

872 kp2 = kp2 / s

873 if min(e2, k2, kp2) < 0 or not s > 0:

874 raise TriaxialError(e2=e2, k2=k2, kp2=kp2)

875 if e2:

876 a = Radius_(a=_sqrt0(_1_0 + e2 * kp2) * b) if kp2 else b

877 c = Radius_(c=_sqrt0(_1_0 - e2 * k2) * b) if k2 else b

878 else: # spherical

879 a = c = b

880 if not (_isfinite(b) and a >= b >= c > 0):

881 raise TriaxialError(b=b, a=a, c=c, e2=e2,

882 k2=k2, kp2=kp2, txt=_not_ordered_)

883 self._abc3 = a, b, c

884 self._e2_k2_kp2 = e2, k2, kp2

885

886 @property_RO

887 def isBiaxial(self):

888 '''Is this triaxial I{biaxial} (C{bool}), C{a} == C{b} or C{b} == C{c}?

889 '''

890 return self.isOblate or self.isProlate

891

892 @property_RO

893 def isOblate(self):

894 '''Is this triaxial I{oblate} (C{bool}), C{a} == C{b}?

895 '''

896 return bool(self.kp2 == 0)

897

898 @property_RO

899 def isProlate(self):

900 '''Is this triaxial I{prolate} (C{bool}), C{b} == C{c}?

901 '''

902 return bool(self.k2 == 0)

903

904 @Property_RO

905 def _k_kp(self):

906 '''(INTERNAL) Get the oblate C{k} and prolate C{kp} parameters.

907 '''

908 return map1(_sqrt0, *self._k2_kp2)

909

910 @Property_RO

911 def k2(self):

912 '''(INTERNAL) Get the oblate C{k2} parameter I{squared}.

913 '''

914 k2, _ = self._k2_kp2

915 return k2

916

917 @Property_RO

918 def _k2_kp2(self):

919 '''(INTERNAL) Get the oblate C{k2} and prolate C{kp2} parameters I{squared}.

920 '''

921 if self._e2_k2_kp2:

922 _, k2, kp2 = self._e2_k2_kp2

923 else:

924 s = self._a2c2

925 k2 = (self._b2c2 / s) if s else _1_0

926 kp2 = (self._a2b2 / s) if s else _0_0

927 return k2, kp2

928

929 @Property_RO

930 def kp2(self):

931 '''(INTERNAL) Get the prolate C{kp2} parameter I{squared}.

932 '''

933 _, kp2 = self._k2_kp2

934 return kp2

935

936 @Property_RO

937 def _lcc23(self):

938 return self._a2c2, self._b2c2, _0_0

939

940 @property_doc_(" longitude of the I{earth}'s major semi-axis C{a}, (L{Ang}), Karney's C{Triaxial_Earth_lon0}.")

941 def Lon0(self):

942 if self._Lon0 is None:

943 WGS84_3 = self.name.startswith('WGS84_3')

944 self.Lon0 = -(1493 / 100) if WGS84_3 else 0

945 return self._Lon0

946

947 @Lon0.setter # PYCHOK setter!

948 def Lon0(self, lon0):

949 m = _MODS.angles

950 d = lon0.degrees if m.isAng(lon0) else Degrees(lon0)

951 n = _Triaxial3Base.Lon0.name

952 self._Lon0 = m.Ang.fromDegrees(d, name=n)

953

954 @Property_RO

955 def _xE(self):

956 '''(INTERNAL) Get the x-elliptic function.

957 '''

958 return self._xyE(self.e2, self.k2, self.kp2)

959

960 def _xyE(self, e2, k2, kp2):

961 '''(INTERNAL) Helper for C{._xE} and C{._yE}.

962 '''

963 if e2:

964 a2 = -kp2 * e2

965 ap2 = _1_0 - a2

966 kp2 *= _1_0 - k2 * e2

967 k2 *= ap2

968 else:

969 a2, ap2 = _0_0, _1_0

970 return _MODS.elliptic.Elliptic(kp2, a2, k2, ap2)

971

972 @Property_RO

973 def _yE(self):

974 '''(INTERNAL) Get the y-elliptic function.

975 '''

976 return self._xyE(-self.e2, self.kp2, self.k2)

977

978

979 class TriaxialError(_ValueError):

980 '''Raised for any triaxial issue.

981 '''

982 pass # ...

983

984

985 class _TriaxialsBase(_NamedEnum):

986 '''(INTERNAL) C{Triaxial*} registry, I{must} be a sub-class

987 to accommodate the L{_LazyNamedEnumItem} properties.

988 '''

989 _assert_kwds = {} # like propertyROnce

990 _Triaxial = None # must be overloaded

991

992 def _Lazy(self, *abc, **name):

993 '''(INTERNAL) Instantiate the C{self._Triaxial}.

994 '''

995 return self._Triaxial(*abc, **name)

996

997 def _assert(self): # PYCHOK signature

998 kwds = _TriaxialsBase._assert_kwds

999 if not kwds:

1000 _lazy = _MODS.named._lazyNamedEnumItem

1001 EWGS84 = _MODS.ellipsoids._EWGS84

1002 abc84_35 = map1(m2km, EWGS84.a + 35, EWGS84.a - 35, EWGS84.b)

1003 # <https://ArxIV.org/pdf/1909.06452.pdf> Table 1 Semi-axes in Km

1004 # <https://www.JPS.NASA.gov/education/images/pdf/ss-moons.pdf>

1005 # <https://link.Springer.com/article/10.1007/s00190-022-01650-9>

1006 # <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Constants.html>

1007 # <https://www.ResearchGate.net/publication/344992491_Fitting_a_triaxial_ellipsoid_to_a_geoid_model>

1008 for n, abc in dict( # a (Km) b (Km) c (Km) planet

1009 Amalthea= (125.0, 73.0, 64.0), # Jupiter

1010 Ariel= (581.1, 577.9, 577.7), # Uranus

1011 Earth= (6378.173435, 6378.1039, 6356.7544),

1012 Enceladus=(256.6, 251.4, 248.3), # Saturn

1013 Europa= (1564.13, 1561.23, 1560.93), # Jupiter

1014 Io= (1829.4, 1819.3, 1815.7), # Jupiter

1015 Mars= (3394.6, 3393.3, 3376.3),

1016 Mimas= (207.4, 196.8, 190.6), # Saturn

1017 Miranda= (240.4, 234.2, 232.9), # Uranus

1018 Moon= (1735.55, 1735.324, 1734.898), # Earth

1019 Tethys= (535.6, 528.2, 525.8), # Saturn

1020 WGS84_3= (6378.17136, 6378.10161, 6356.75184), # C++

1021 WGS84_3r=(6378.172, 6378.102, 6356.752), # C++, rounded

1022# Panou= (6378.17188, 6378.10203, 6356.75224), # et.al. Fitting ...

1023 WGS84_35=abc84_35).items():

1024 kwds[n] = _lazy(n, *map(km2m, abc))

1025 _NamedEnum._assert(self, **kwds)

1026

1027

1028 def _getitems(items, *indices):

1029 '''(INTERNAL) Get the C{items} at the given I{indices}.

1030

1031 @return: C{Type(items[i] for i in indices)} with

1032 C{Type = type(items)}, any C{type} having

1033 the special method C{__getitem__}.

1034 '''

1035 return type(items)(map(items.__getitem__, indices))

1036

1037

1038 def _hypot2_1(x, y, z=0):

1039 '''(INTERNAL) Compute M{x**2 + y**2 + z**2 - 1} with C{max(fabs(x), fabs(y),

1040 fabs(z))} rarely greater than 1.0.

1041 '''

1042 return fsumf_(_N_1_0, x*x, y*y, z*z)

1043

1044

1045 def _otherV3d_(x_xyz, y, z, **name):

1046 '''(INTERNAL) Get a Vector3d from C{x_xyz}, C{y} and C{z}.

1047 '''

1048 return _otherV3d(x_xyz=x_xyz, **name) if y is z is None else \

1049 Vector3d(x_xyz, y, z, **name)

1050

1051

1052 def _over0(p, q):

1053 '''(INTERNAL) Return C{p / q} or C{0}.

1054 '''

1055 return (p / q) if q > fabs(p) else _0_0

1056

1057

1058 def _over02(p, q):

1059 '''(INTERNAL) Return C{(p / q)**2} or C{0}.

1060 '''

1061 return (p / q)**2 if p and q else _0_0

1062

1063

1064 def _sqrt0(x):

1065 '''(INTERNAL) C{sqrt0} with C{TriaxialError}.

1066 '''

1067 return sqrt0(x, Error=TriaxialError)

1068

1069

1070 __all__ += _ALL_DOCS(_OrderedTriaxialBase, _Triaxial3Base, _UnOrderedTriaxialBase)

1071

1072# **) MIT License

1073#

1074# Copyright (C) 2025-2026 -- mrJean1 at Gmail -- All Rights Reserved.

1075#

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

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

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

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

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

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

1082#

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

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

1085#

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

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

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

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

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

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

1092# OTHER DEALINGS IN THE SOFTWARE.