pygeodesy 24.8.4__py2.py3-none-any.whl → 24.8.24__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {PyGeodesy-24.8.4.dist-info → PyGeodesy-24.8.24.dist-info}/METADATA +16 -15
  2. {PyGeodesy-24.8.4.dist-info → PyGeodesy-24.8.24.dist-info}/RECORD +50 -50
  3. {PyGeodesy-24.8.4.dist-info → PyGeodesy-24.8.24.dist-info}/WHEEL +1 -1
  4. pygeodesy/__init__.py +23 -23
  5. pygeodesy/auxilats/auxDST.py +2 -2
  6. pygeodesy/basics.py +3 -3
  7. pygeodesy/cartesianBase.py +5 -5
  8. pygeodesy/constants.py +11 -11
  9. pygeodesy/ellipsoidalBase.py +18 -15
  10. pygeodesy/ellipsoidalExact.py +2 -2
  11. pygeodesy/ellipsoidalGeodSolve.py +2 -2
  12. pygeodesy/ellipsoidalKarney.py +2 -2
  13. pygeodesy/ellipsoidalNvector.py +2 -2
  14. pygeodesy/ellipsoidalVincenty.py +7 -6
  15. pygeodesy/epsg.py +3 -3
  16. pygeodesy/fmath.py +2 -1
  17. pygeodesy/fsums.py +4 -4
  18. pygeodesy/gars.py +9 -8
  19. pygeodesy/geodesicx/gx.py +3 -3
  20. pygeodesy/geodesicx/gxarea.py +3 -3
  21. pygeodesy/geodsolve.py +3 -3
  22. pygeodesy/geohash.py +18 -11
  23. pygeodesy/geoids.py +293 -315
  24. pygeodesy/heights.py +150 -158
  25. pygeodesy/internals.py +21 -1
  26. pygeodesy/interns.py +2 -3
  27. pygeodesy/latlonBase.py +13 -7
  28. pygeodesy/lazily.py +6 -6
  29. pygeodesy/ltp.py +5 -6
  30. pygeodesy/ltpTuples.py +7 -1
  31. pygeodesy/named.py +5 -4
  32. pygeodesy/namedTuples.py +14 -1
  33. pygeodesy/osgr.py +7 -7
  34. pygeodesy/points.py +2 -2
  35. pygeodesy/resections.py +7 -7
  36. pygeodesy/rhumb/solve.py +3 -3
  37. pygeodesy/simplify.py +10 -10
  38. pygeodesy/sphericalBase.py +3 -3
  39. pygeodesy/sphericalTrigonometry.py +2 -2
  40. pygeodesy/streprs.py +3 -3
  41. pygeodesy/triaxials.py +207 -201
  42. pygeodesy/units.py +3 -3
  43. pygeodesy/unitsBase.py +4 -4
  44. pygeodesy/utmupsBase.py +3 -3
  45. pygeodesy/vector2d.py +158 -51
  46. pygeodesy/vector3d.py +13 -52
  47. pygeodesy/vector3dBase.py +81 -63
  48. pygeodesy/webmercator.py +3 -3
  49. pygeodesy/wgrs.py +20 -22
  50. {PyGeodesy-24.8.4.dist-info → PyGeodesy-24.8.24.dist-info}/top_level.txt +0 -0
pygeodesy/triaxials.py CHANGED
@@ -30,10 +30,10 @@ see the U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
30
30
  # make sure int/int division yields float quotient, see .basics
31
31
  from __future__ import division as _; del _ # PYCHOK semicolon
32
32
 
33
- from pygeodesy.basics import isLatLon, isscalar, _zip, _ValueError
33
+ from pygeodesy.basics import isLatLon, isscalar, _ValueError
34
34
  from pygeodesy.constants import EPS, EPS0, EPS02, EPS4, INT0, PI2, PI_3, PI4, \
35
- _EPS2e4, float0_, isfinite, isnear1, _0_0, _0_5, \
36
- _1_0, _N_1_0, _N_2_0, _4_0 # PYCHOK used!
35
+ _EPS2e4, float0_, isfinite, isnear1, _over, \
36
+ _0_0, _0_5, _1_0, _N_1_0, _4_0 # PYCHOK used!
37
37
  from pygeodesy.datums import Datum, _spherical_datum, _WGS84, Ellipsoid, _EWGS84, Fmt
38
38
  # from pygeodesy.ellipsoids import Ellipsoid, _EWGS84 # from .datums
39
39
  # from pygeodesy.elliptic import Elliptic # _MODS
@@ -59,11 +59,11 @@ from pygeodesy.vector3d import _otherV3d, Vector3d, _ALL_LAZY, _MODS
59
59
  from math import atan2, fabs, sqrt
60
60
 
61
61
  __all__ = _ALL_LAZY.triaxials
62
- __version__ = '24.07.29'
62
+ __version__ = '24.08.18'
63
63
 
64
64
  _not_ordered_ = _not_('ordered')
65
65
  _omega_ = 'omega'
66
- _TRIPS = 269 # 48-55, Eberly 1074?
66
+ _TRIPS = 359 # Eberly 1074?
67
67
 
68
68
 
69
69
  class _NamedTupleToX(_NamedTupleTo): # in .testNamedTuples
@@ -99,7 +99,7 @@ class BetaOmega2Tuple(_NamedTupleToX):
99
99
  def toDegrees(self, name=NN, **toDMS_kwds):
100
100
  '''Convert this L{BetaOmega2Tuple} to L{Degrees} or C{toDMS}.
101
101
 
102
- @kwarg name: Optional name (C{str}), overriding this name.
102
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
103
103
 
104
104
  @return: L{BetaOmega2Tuple}C{(beta, omega)} with C{beta} and
105
105
  C{omega} both in L{Degrees} or as L{toDMS} strings
@@ -111,7 +111,7 @@ class BetaOmega2Tuple(_NamedTupleToX):
111
111
  def toRadians(self, **name):
112
112
  '''Convert this L{BetaOmega2Tuple} to L{Radians}.
113
113
 
114
- @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.
114
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
115
115
 
116
116
  @return: L{BetaOmega2Tuple}C{(beta, omega)} with C{beta} and C{omega}
117
117
  both in L{Radians}.
@@ -132,7 +132,7 @@ class BetaOmega3Tuple(_NamedTupleToX):
132
132
  def toDegrees(self, name=NN, **toDMS_kwds):
133
133
  '''Convert this L{BetaOmega3Tuple} to L{Degrees} or C{toDMS}.
134
134
 
135
- @kwarg name: Optional name (C{str}), overriding this name.
135
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
136
136
 
137
137
  @return: L{BetaOmega3Tuple}C{(beta, omega, height)} with
138
138
  C{beta} and C{omega} both in L{Degrees} or as
@@ -169,7 +169,7 @@ class Jacobi2Tuple(_NamedTupleToX):
169
169
  def toDegrees(self, name=NN, **toDMS_kwds):
170
170
  '''Convert this L{Jacobi2Tuple} to L{Degrees} or C{toDMS}.
171
171
 
172
- @kwarg name: Optional name (C{str}), overriding this name.
172
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
173
173
 
174
174
  @return: L{Jacobi2Tuple}C{(x, y)} with C{x} and C{y} both
175
175
  in L{Degrees} or as L{toDMS} strings provided some
@@ -180,7 +180,7 @@ class Jacobi2Tuple(_NamedTupleToX):
180
180
  def toRadians(self, **name):
181
181
  '''Convert this L{Jacobi2Tuple} to L{Radians}.
182
182
 
183
- @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.
183
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
184
184
 
185
185
  @return: L{Jacobi2Tuple}C{(x, y)} with C{x} and C{y} both in L{Radians}.
186
186
  '''
@@ -244,7 +244,7 @@ class Triaxial_(_NamedEnumItem):
244
244
 
245
245
  @Property_RO
246
246
  def a(self):
247
- '''Get the (largest) C{x} semi-axis (C{meter}, conventionally).
247
+ '''Get the C{largest, x} semi-axis (C{meter}, conventionally).
248
248
  '''
249
249
  a, _, _ = self._abc3
250
250
  return a
@@ -300,7 +300,7 @@ class Triaxial_(_NamedEnumItem):
300
300
 
301
301
  @Property_RO
302
302
  def b(self):
303
- '''Get the (middle) C{y} semi-axis (C{meter}, same units as B{C{a}}).
303
+ '''Get the C{middle, y} semi-axis (C{meter}, same units as B{C{a}}).
304
304
  '''
305
305
  _, b, _ = self._abc3
306
306
  return b
@@ -314,7 +314,7 @@ class Triaxial_(_NamedEnumItem):
314
314
 
315
315
  @Property_RO
316
316
  def c(self):
317
- '''Get the (smallest) C{z} semi-axis (C{meter}, same units as B{C{a}}).
317
+ '''Get the C{smallest, z} semi-axis (C{meter}, same units as B{C{a}}).
318
318
  '''
319
319
  _, _, c = self._abc3
320
320
  return c
@@ -382,8 +382,9 @@ class Triaxial_(_NamedEnumItem):
382
382
  @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
383
383
  otherwise.
384
384
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
385
- @kwarg normal: If C{True} the projection is the I{normal, plumb} to the surface of,
386
- otherwise the C{radial} line to the center of this triaxial (C{bool}).
385
+ @kwarg normal: If C{True}, the projection is the I{perpendicular, plumb} to the
386
+ triaxial's surface, otherwise the C{radial} line to the center of
387
+ this triaxial (C{bool}).
387
388
  @kwarg eps: Tolerance for root finding and validation (C{scalar}), use a negative
388
389
  value to skip validation.
389
390
  @kwarg name: Optional C{B{name}="heigh4"} (C{str}).
@@ -395,8 +396,8 @@ class Triaxial_(_NamedEnumItem):
395
396
  @raise TriaxialError: Non-cartesian B{C{xyz}}, invalid B{C{eps}}, no convergence in
396
397
  root finding or validation failed.
397
398
 
398
- @see: Method L{Ellipsoid.height4} and I{Eberly}'s U{Distance from a Point to ...
399
- <https://www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>}.
399
+ @see: Methods L{Triaxial.normal3d} and L{Ellipsoid.height4} and I{Eberly}'s U{Distance from a
400
+ Point to ...<https://www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>}.
400
401
  '''
401
402
  v, r = _otherV3d_(x_xyz, y, z), self.isSpherical
402
403
 
@@ -405,10 +406,10 @@ class Triaxial_(_NamedEnumItem):
405
406
  x = y = z = _0_0
406
407
  h -= min(self._abc3) # nearest
407
408
  elif r: # .isSpherical
408
- x, y, z = v.times(r / h).xyz
409
+ x, y, z = v.times(r / h).xyz3
409
410
  h -= r
410
411
  else:
411
- x, y, z = v.xyz
412
+ x, y, z = v.xyz3
412
413
  try:
413
414
  if normal: # plumb to surface
414
415
  x, y, z, h, i = _plumbTo5(x, y, z, self, eps=eps)
@@ -418,7 +419,7 @@ class Triaxial_(_NamedEnumItem):
418
419
  except Exception as e:
419
420
  raise TriaxialError(x=x, y=y, z=z, cause=e)
420
421
  if h > 0 and self.sideOf(v, eps=EPS0) < 0:
421
- h = -h # below the surface
422
+ h = -h # inside
422
423
  n = _name__(name, name__=self.height4)
423
424
  return Vector4Tuple(x, y, z, h, iteration=i, name=n)
424
425
 
@@ -439,46 +440,43 @@ class Triaxial_(_NamedEnumItem):
439
440
  def _norm2(self, s, c, *a):
440
441
  '''(INTERNAL) Normalize C{s} and C{c} iff not already.
441
442
  '''
442
- if fabs(_hypot21(s, c)) > EPS02:
443
+ if fabs(_hypot2_1(s, c)) > EPS02:
443
444
  s, c = norm2(s, c)
444
445
  if a:
445
446
  s, c = norm2(s * self.b, c * a[0])
446
447
  return float0_(s, c)
447
448
 
448
449
  def normal3d(self, x_xyz, y=None, z=None, length=_1_0):
449
- '''Get a 3-D vector at a cartesian on and perpendicular to this triaxial's surface.
450
+ '''Get a 3-D vector at a cartesian I{on and perpendicular to} this triaxial's surface.
450
451
 
451
452
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
452
453
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
453
454
  @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar}, ignored
454
455
  otherwise.
455
456
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
456
- @kwarg length: Optional length and in-/outward direction (C{scalar}).
457
+ @kwarg length: Optional, signed length and in-/outward direction (C{scalar}).
457
458
 
458
- @return: A C{Vector3d(x_, y_, z_)} normalized to B{C{length}}, pointing
459
- in- or outward for neg- respectively positive B{C{length}}.
459
+ @return: A C{Vector3d(x_, y_, z_)} normalized to B{C{length}}, pointing in- or
460
+ outward for neg- respectively positive B{C{length}}.
460
461
 
461
462
  @raise TriaxialError: Zero length cartesian or vector.
462
463
 
463
- @note: Cartesian location C{(B{x}, B{y}, B{z})} must be on this triaxial's
464
- surface, use method L{Triaxial.sideOf} to validate.
464
+ @note: Cartesian C{(B{x}, B{y}, B{z})} I{must be on} this triaxial's surface, use
465
+ method L{Triaxial.sideOf} to validate.
466
+
467
+ @see: Methods L{Triaxial.height4} and L{Triaxial.sideOf}.
465
468
  '''
466
469
  # n = 2 * (x / a2, y / b2, z / c2)
467
470
  # == 2 * (x, y * a2 / b2, z * a2 / c2) / a2 # iff ordered
468
471
  # == 2 * (x, y / _1e2ab, z / _1e2ac) / a2
469
472
  # == unit(x, y / _1e2ab, z / _1e2ac).times(length)
470
- n = self._normal3d.times_(*_otherV3d_(x_xyz, y, z).xyz)
471
- if n.length < EPS0:
473
+ x, y, z = _otherV3d_(x_xyz, y, z).xyz3
474
+ n = Vector3d(x, y / self._1e2ab,
475
+ z / self._1e2ac, name__=self.normal3d)
476
+ u = n.length
477
+ if u < EPS0:
472
478
  raise TriaxialError(x=x_xyz, y=y, z=z, txt=_null_)
473
- return n.times(length / n.length)
474
-
475
- @Property_RO
476
- def _normal3d(self):
477
- '''(INTERNAL) Get M{Vector3d((d/a)**2, (d/b)**2, (d/c)**2)}, M{d = max(a, b, c)}.
478
- '''
479
- d = max(self._abc3)
480
- t = tuple(((d / x)**2 if x != d else _1_0) for x in self._abc3)
481
- return Vector3d(*t, name__=self.normal3d)
479
+ return n.times(length / u)
482
480
 
483
481
  def _order3(self, *abc, **reverse): # reverse=False
484
482
  '''(INTERNAL) Un-/Order C{a}, C{b} and C{c}.
@@ -495,7 +493,7 @@ class Triaxial_(_NamedEnumItem):
495
493
  @return: Vector3d(x, y, z) un-/ordered.
496
494
  '''
497
495
  ijk = self._order_ijk(**reverse)
498
- return v.classof(*_getitems(v.xyz, *ijk)) if ijk else v
496
+ return v.classof(*_getitems(v.xyz3, *ijk)) if ijk else v
499
497
 
500
498
  @Property_RO
501
499
  def _ordered4(self):
@@ -549,33 +547,34 @@ class Triaxial_(_NamedEnumItem):
549
547
  a = _rphi(a, b, sb, cb)
550
548
  if a != c:
551
549
  c = _rphi(a, c, sa, ca)
552
- z, r = c * sa, c * ca
553
- x, y = r * cb, r * sb
554
- return x, y, z
550
+ t = c * ca
551
+ return (t * cb), (t * sb), (c * sa)
555
552
 
556
553
  def sideOf(self, x_xyz, y=None, z=None, eps=EPS4):
557
- '''Is a cartesian above, below or on the surface of this triaxial?
554
+ '''Is a cartesian on, above or below the surface of this triaxial?
558
555
 
559
556
  @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian},
560
557
  L{Ecef9Tuple}, L{Vector3d}, L{Vector3Tuple} or L{Vector4Tuple}).
561
558
  @kwarg y: Y component (C{scalar}), required if B{C{x_xyz}} if C{scalar},
562
559
  ignored otherwise.
563
560
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
564
- @kwarg eps: Near-surface tolerance (C{scalar}, distance I{squared}).
561
+ @kwarg eps: On-surface tolerance (C{scalar}, distance I{squared}).
565
562
 
566
563
  @return: C{INT0} if C{(B{x}, B{y}, B{z})} is near this triaxial's surface
567
- within tolerance B{C{eps}}, otherwise a signed, radial, normalized
568
- distance I{squared} (C{float}), negative or positive for in-
569
- respectively outside this triaxial.
564
+ within tolerance B{C{eps}}, otherwise the signed, radial distance
565
+ I{squared} (C{float}), negative for in- or positive for outside
566
+ this triaxial.
570
567
 
571
568
  @see: Methods L{Triaxial.height4} and L{Triaxial.normal3d}.
572
569
  '''
573
- return _sideOf(_otherV3d_(x_xyz, y, z).xyz, self._abc3, eps=eps)
570
+ v = _otherV3d_(x_xyz, y, z)
571
+ s = fsumf_(_N_1_0, *map(_over02, v.xyz3, self._abc3))
572
+ return INT0 if fabs(s) < eps else s
574
573
 
575
574
  def toEllipsoid(self, **name):
576
575
  '''Convert this triaxial to an L{Ellipsoid}, provided 2 axes match.
577
576
 
578
- @kwarg name: Optional, overriding C{B{name}=NN} (C{str})=.
577
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
579
578
 
580
579
  @return: An L{Ellipsoid} with north along this C{Z} axis if C{a == b},
581
580
  this C{Y} axis if C{a == c} or this C{X} axis if C{b == c}.
@@ -598,8 +597,8 @@ class Triaxial_(_NamedEnumItem):
598
597
  '''Return this C{Triaxial} as a string.
599
598
 
600
599
  @kwarg prec: Precision, number of decimal digits (0..9).
601
- @kwarg name: Optional, overriding C{B{name}=NN} (C{str})
602
- or C{None} to exclude this triaxial's name.
600
+ @kwarg name: Optional name (C{str}), to override or C{None}
601
+ to exclude this triaxial's name.
603
602
 
604
603
  @return: This C{Triaxial}'s attributes (C{str}).
605
604
  '''
@@ -614,6 +613,12 @@ class Triaxial_(_NamedEnumItem):
614
613
  t += T.volume, T.area
615
614
  return self._instr(area_p=self.area_p(), prec=prec, props=t, **name)
616
615
 
616
+ @Property_RO
617
+ def unOrdered(self):
618
+ '''Is this triaxial I{un-ordered} and I{not spherical} (C{bool})?
619
+ '''
620
+ return not (self.isOrdered or bool(self.isSpherical))
621
+
617
622
  @Property_RO
618
623
  def volume(self):
619
624
  '''Get the volume (C{meter**3}), M{4 / 3 * PI * a * b * c}.
@@ -665,27 +670,16 @@ class Triaxial(Triaxial_):
665
670
  c2 = self._1e2ac # cos(phi)**2 = (c/a)**2
666
671
  s = sqrt(self.e2ac) # sin(phi)**2 = 1 - c2
667
672
  r = asin1(s) # phi = atan2(sqrt(c2), s)
668
- b *= fsum1f_(aE.fE(r) * s, c / a * c / b,
673
+ b *= fsum1f_(aE.fE(r) * s, (c / a) * (c / b),
669
674
  aE.fF(r) * c2 / s)
670
675
  a = Meter2(area=a * b * PI2)
671
676
  else: # a == b > c
672
677
  a = Ellipsoid(a, b=c).areax
673
678
  return a
674
679
 
675
- def _exyz3(self, u):
676
- '''(INTERNAL) Helper for C{.forwardBetOmg}.
677
- '''
678
- if u > 0:
679
- u2 = u**2
680
- x = u * sqrt0(_1_0 + self._a2c2 / u2, Error=TriaxialError)
681
- y = u * sqrt0(_1_0 + self._b2c2 / u2, Error=TriaxialError)
682
- else:
683
- x = y = u = _0_0
684
- return x, y, u
685
-
686
680
  def forwardBetaOmega(self, beta, omega, height=0, **name):
687
- '''Convert I{ellipsoidal} lat- and longitude C{beta}, C{omega}
688
- and height to cartesian.
681
+ '''Convert I{ellipsoidal} lat- C{beta}, longitude C{omega} and C{height}
682
+ to cartesian.
689
683
 
690
684
  @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
691
685
  @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
@@ -695,22 +689,27 @@ class Triaxial(Triaxial_):
695
689
 
696
690
  @return: A L{Vector3Tuple}C{(x, y, z)}.
697
691
 
698
- @see: Method L{Triaxial.reverseBetaOmega} and U{Expressions (23-25)<https://
692
+ @see: Method L{Triaxial.reverseBetaOmega} and U{expressions (23-25)<https://
699
693
  www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
700
694
  '''
701
695
  if height:
702
- h = self._Height(height)
703
- x, y, z = self._exyz3(h + self.c)
696
+ z = self._Height(height) + self.c
697
+ if z > 0:
698
+ z2 = z**2
699
+ x = z * _sqrt0(_1_0 + self._a2c2 / z2)
700
+ y = z * _sqrt0(_1_0 + self._b2c2 / z2)
701
+ else:
702
+ x = y = z = _0_0
704
703
  else:
705
- x, y, z = self._abc3 # == self._exyz3(self.c)
704
+ x, y, z = self._abc3
706
705
  if z: # and x and y:
707
706
  sa, ca = SinCos2(beta)
708
707
  sb, cb = SinCos2(omega)
709
708
 
710
709
  r = self._a2b2_a2c2
711
- x *= cb * sqrt0(ca**2 + r * sa**2, Error=TriaxialError)
712
- y *= ca * sb
713
- z *= sa * sqrt0(_1_0 - r * cb**2, Error=TriaxialError)
710
+ x *= cb * _sqrt0(ca**2 + sa**2 * r)
711
+ y *= ca * sb
712
+ z *= sa * _sqrt0(_1_0 - cb**2 * r)
714
713
  return Vector3Tuple(x, y, z, **name)
715
714
 
716
715
  def forwardBetaOmega_(self, sbeta, cbeta, somega, comega, **name):
@@ -763,7 +762,7 @@ class Triaxial(Triaxial_):
763
762
 
764
763
  @return: A L{Vector3Tuple}C{(x, y, z)}.
765
764
 
766
- @see: Method L{Triaxial.reverseLatLon} and U{Expressions (9-11)<https://
765
+ @see: Method L{Triaxial.reverseLatLon} and U{expressions (9-11)<https://
767
766
  www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
768
767
  '''
769
768
  return self._forwardLatLon3(height, name, *sincos2d_(lat, lon))
@@ -781,7 +780,7 @@ class Triaxial(Triaxial_):
781
780
 
782
781
  @return: A L{Vector3Tuple}C{(x, y, z)}.
783
782
 
784
- @see: Method L{Triaxial.reverseLatLon} and U{Expressions (9-11)<https://
783
+ @see: Method L{Triaxial.reverseLatLon} and U{expressions (9-11)<https://
785
784
  www.Topo.Auth.GR/wp-content/uploads/sites/111/2021/12/09_Panou.pdf>}.
786
785
  '''
787
786
  sa, ca = self._norm2(slat, clat)
@@ -795,7 +794,7 @@ class Triaxial(Triaxial_):
795
794
  h = self._Height(height)
796
795
  # 1 - (1 - (c/a)**2) * sa**2 - (1 - (b/a)**2) * ca**2 * sb**2
797
796
  t = fsumf_(_1_0, -self.e2ac * sa**2, -self.e2ab * ca_x_sb**2)
798
- n = self.a / sqrt0(t, Error=TriaxialError) # prime vertical
797
+ n = self.a / _sqrt0(t) # prime vertical
799
798
  x = (h + n) * ca * cb
800
799
  y = (h + n * self._1e2ab) * ca_x_sb
801
800
  z = (h + n * self._1e2ac) * sa
@@ -834,10 +833,10 @@ class Triaxial(Triaxial_):
834
833
  x2 = _Fsumf_(_1_0, -b2_a2 * sa**2, c2_a2 * ca**2).fover(a2c2_a2)
835
834
  z2 = _Fsumf_(c2_a2, sb**2, b2_a2 * cb**2).fover(a2c2_a2)
836
835
 
837
- x, y, z = self._abc3
838
- x *= cb * sqrt0(x2, Error=TriaxialError)
839
- y *= ca * sb
840
- z *= sa * sqrt0(z2, Error=TriaxialError)
836
+ x, y, z = self._abc3
837
+ x *= cb * _sqrt0(x2)
838
+ y *= ca * sb
839
+ z *= sa * _sqrt0(z2)
841
840
  return x, y, z
842
841
 
843
842
  def reverseBetaOmega(self, x_xyz, y=None, z=None, **name):
@@ -856,7 +855,7 @@ class Triaxial(Triaxial_):
856
855
  units as this triaxial's axes.
857
856
 
858
857
  @see: Methods L{Triaxial.forwardBetaOmega} and L{Triaxial.forwardBetaOmega_}
859
- and U{Expressions (21-22)<https://www.Topo.Auth.GR/wp-content/uploads/
858
+ and U{expressions (21-22)<https://www.Topo.Auth.GR/wp-content/uploads/
860
859
  sites/111/2021/12/09_Panou.pdf>}.
861
860
  '''
862
861
  v = _otherV3d_(x_xyz, y, z)
@@ -873,9 +872,9 @@ class Triaxial(Triaxial_):
873
872
  @kwarg z: Z component (C{scalar}), like B{C{y}}.
874
873
  @arg h: Height above or below this triaxial's surface (C{meter}, same units
875
874
  as the axes).
876
- @kwarg normal: If C{True} the height is C{normal} to the surface, otherwise
875
+ @kwarg normal: If C{True}, the height is C{normal} to the surface, otherwise
877
876
  C{radially} to the center of this triaxial (C{bool}).
878
- @kwarg eps: Tolerance for surface test (C{scalar}).
877
+ @kwarg eps: Tolerance for on-surface test (C{scalar}), see method L{Triaxial.sideOf}.
879
878
  @kwarg name: Optional C{B{name}=NN} (C{str}).
880
879
 
881
880
  @return: A L{Vector3Tuple}C{(x, y, z)}.
@@ -885,7 +884,7 @@ class Triaxial(Triaxial_):
885
884
  @see: Methods L{Triaxial.forwardCartesian} and L{Triaxial.height4}.
886
885
  '''
887
886
  v = _otherV3d_(x_xyz, y, z, **name)
888
- s = _sideOf(v.xyz, self._abc3, eps=eps)
887
+ s = self.sideOf(v.xyz, eps=eps)
889
888
  if s: # PYCHOK no cover
890
889
  t = _SPACE_((_inside_ if s < 0 else _outside_), self.toRepr())
891
890
  raise TriaxialError(eps=eps, sideOf=s, x=v.x, y=v.y, z=v.z, txt=t)
@@ -912,7 +911,7 @@ class Triaxial(Triaxial_):
912
911
  as this triaxial's axes.
913
912
 
914
913
  @see: Methods L{Triaxial.forwardLatLon} and L{Triaxial.forwardLatLon_}
915
- and U{Expressions (4-5)<https://www.Topo.Auth.GR/wp-content/uploads/
914
+ and U{expressions (4-5)<https://www.Topo.Auth.GR/wp-content/uploads/
916
915
  sites/111/2021/12/09_Panou.pdf>}.
917
916
  '''
918
917
  v = _otherV3d_(x_xyz, y, z)
@@ -925,7 +924,7 @@ class Triaxial(Triaxial_):
925
924
  def _reverseLatLon3(self, s, atan2_, v, forward_):
926
925
  '''(INTERNAL) Helper for C{.reverseBetOmg} and C{.reverseLatLon}.
927
926
  '''
928
- x, y, z = s.xyz
927
+ x, y, z = s.xyz3
929
928
  d = hypot( x, y)
930
929
  a = atan2_(z, d)
931
930
  b = atan2_(y, x)
@@ -996,7 +995,7 @@ class JacobiConformal(Triaxial):
996
995
 
997
996
  @arg beta: Ellipsoidal latitude (C{radians} or L{Degrees}).
998
997
  @arg omega: Ellipsoidal longitude (C{radians} or L{Degrees}).
999
- @kwarg name: Optional, overriding C{B{name}="xyR2"} (C{str}).
998
+ @kwarg name: Optional name (C{str}), overriding C{B{name}="xyR2"}.
1000
999
 
1001
1000
  @return: A L{Jacobi2Tuple}C{(x, y)}.
1002
1001
  '''
@@ -1010,7 +1009,7 @@ class JacobiConformal(Triaxial):
1010
1009
  @arg cbeta: Ellipsoidal latitude C{cos(beta)} (C{scalar}).
1011
1010
  @arg somega: Ellipsoidal longitude C{sin(omega)} (C{scalar}).
1012
1011
  @arg comega: Ellipsoidal longitude C{cos(omega)} (C{scalar}).
1013
- @kwarg name: Optional, overriding C{B{name}="xyR2_"} (C{str}).
1012
+ @kwarg name: Optional name (C{str}), overriding C{B{name}="xyR2_"}.
1014
1013
 
1015
1014
  @return: A L{Jacobi2Tuple}C{(x, y)}.
1016
1015
  '''
@@ -1098,7 +1097,7 @@ class JacobiConformalSpherical(JacobiConformal):
1098
1097
 
1099
1098
  @Property_RO
1100
1099
  def ab(self):
1101
- '''Get relative magnitude C{ab} (C{meter}, same units as B{C{a}}).
1100
+ '''Get relative magnitude C{a - b} (C{meter}, same units as B{C{a}}).
1102
1101
  '''
1103
1102
  return self._ab
1104
1103
 
@@ -1117,7 +1116,7 @@ class JacobiConformalSpherical(JacobiConformal):
1117
1116
 
1118
1117
  @Property_RO
1119
1118
  def bc(self):
1120
- '''Get relative magnitude C{bc} (C{meter}, same units as B{C{a}}).
1119
+ '''Get relative magnitude C{b - c} (C{meter}, same units as B{C{a}}).
1121
1120
  '''
1122
1121
  return self._bc
1123
1122
 
@@ -1158,7 +1157,7 @@ Triaxials = Triaxials(Triaxial, Triaxial_) # PYCHOK singleton
1158
1157
  # <https://link.Springer.com/article/10.1007/s00190-022-01650-9>
1159
1158
  _abc84_35 = (_EWGS84.a + 35), (_EWGS84.a - 35), _EWGS84.b
1160
1159
  Triaxials._assert( # a (Km) b (Km) c (Km) planet
1161
- Amalthea = _lazy('Amalthea', 125.0, 73.0, 64), # Jupiter
1160
+ Amalthea = _lazy('Amalthea', 125.0, 73.0, 64.0), # Jupiter
1162
1161
  Ariel = _lazy('Ariel', 581.1, 577.9, 577.7), # Uranus
1163
1162
  Earth = _lazy('Earth', 6378.173435, 6378.1039, 6356.7544),
1164
1163
  Enceladus = _lazy('Enceladus', 256.6, 251.4, 248.3), # Saturn
@@ -1210,9 +1209,9 @@ def _hartzell3(pov, los, Tun): # in .Ellipsoid.hartzell4, .formy.hartzell
1210
1209
  p3 = T._order3d(p3)
1211
1210
  u3 = T._order3d(u3).unit() # unit vector, opposing signs
1212
1211
 
1213
- x2, y2, z2 = p3.x2y2z2 # p3.times_(p3).xyz
1214
- ux, vy, wz = u3.times_(p3).xyz
1215
- u2, v2, w2 = u3.x2y2z2 # u3.times_(u3).xyz
1212
+ x2, y2, z2 = p3.x2y2z2 # p3.times_(p3).xyz3
1213
+ ux, vy, wz = u3.times_(p3).xyz3
1214
+ u2, v2, w2 = u3.x2y2z2 # u3.times_(u3).xyz3
1216
1215
 
1217
1216
  t = (p2 * c2), c2, b2
1218
1217
  m = fdot(t, u2, v2, w2) # a2 factored out
@@ -1229,9 +1228,9 @@ def _hartzell3(pov, los, Tun): # in .Ellipsoid.hartzell4, .formy.hartzell
1229
1228
 
1230
1229
  d = Fdot(t, ux, vy, wz).fadd_(r).fover(m) # -r for antipode, a2 factored out
1231
1230
  if d > 0: # POV inside or LOS outside or missing the triaxial
1232
- s = fsumf_(_N_1_0, x2 / a2, y2 / b2, z2 / c2) # like _sideOf
1231
+ s = fsumf_(_N_1_0, _over(x2, a2), _over(y2, b2), _over(z2, c2)) # like _sideOf
1233
1232
  raise _ValueError(_outside_ if s > 0 else _inside_)
1234
- elif fsum1f_(x2, y2, z2) < d**2: # d past triaxial's center
1233
+ elif fsum1f_(x2, y2, z2, -d*d) < 0: # d past triaxial's center
1235
1234
  raise _ValueError(_too_(_distant_))
1236
1235
 
1237
1236
  v = p3.minus(u3.times(d)) # cartesian type(pov) or Vector3d
@@ -1240,19 +1239,19 @@ def _hartzell3(pov, los, Tun): # in .Ellipsoid.hartzell4, .formy.hartzell
1240
1239
 
1241
1240
 
1242
1241
  def hartzell4(pov, los=False, tri_biax=_WGS84, **name):
1243
- '''Compute the intersection of a tri-/biaxial ellipsoid and a Line-Of-Sight
1244
- from a Point-Of-View outside.
1242
+ '''Compute the intersection of a tri-/biaxial ellipsoid and a Line-Of-Sight from
1243
+ a Point-Of-View outside.
1245
1244
 
1246
1245
  @arg pov: Point-Of-View outside the tri-/biaxial (C{Cartesian}, L{Ecef9Tuple}
1247
1246
  C{LatLon} or L{Vector3d}).
1248
1247
  @kwarg los: Line-Of-Sight, I{direction} to the tri-/biaxial (L{Los}, L{Vector3d}),
1249
- C{True} for the I{normal, plumb} onto the surface or C{False} or
1250
- C{None} to point to the center of the tri-/biaxial.
1248
+ or C{True} for the I{normal, perpendicular, plumb} to the surface of
1249
+ the tri-/biaxial or C{False} or C{None} to point to its center.
1251
1250
  @kwarg tri_biax: A triaxial (L{Triaxial}, L{Triaxial_}, L{JacobiConformal} or
1252
1251
  L{JacobiConformalSpherical}) or biaxial ellipsoid (L{Datum},
1253
- L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple} or C{scalar} radius,
1254
- conventionally in C{meter}).
1255
- @kwarg name: Optional, overriding C{B{name}="hartzell4"} (C{str}).
1252
+ L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}) or spherical earth
1253
+ radius (C{scalar}, conventionally in C{meter}).
1254
+ @kwarg name: Optional name (C{str}), overriding C{B{name}="hartzell4"}.
1256
1255
 
1257
1256
  @return: L{Vector4Tuple}C{(x, y, z, h)} on the tri-/biaxial's surface, with C{h}
1258
1257
  the distance from B{C{pov}} to C{(x, y, z)} I{along the} B{C{los}}, all
@@ -1283,11 +1282,11 @@ def hartzell4(pov, los=False, tri_biax=_WGS84, **name):
1283
1282
  name=_name__(name, name__=hartzell4))
1284
1283
 
1285
1284
 
1286
- def _hypot21(x, y, z=0):
1287
- '''(INTERNAL) Compute M{x**2 + y**2 + z**2 - 1} with C{max(fabs(x),
1288
- fabs(y), fabs(z))} rarely greater than 1.0.
1285
+ def _hypot2_1(x, y, z=0):
1286
+ '''(INTERNAL) Compute M{x**2 + y**2 + z**2 - 1} with C{max(fabs(x), fabs(y),
1287
+ fabs(z))} rarely greater than 1.0.
1289
1288
  '''
1290
- return fsumf_(_1_0, x**2, y**2, (z**2 if z else _0_0), _N_2_0)
1289
+ return fsumf_(_N_1_0, x*x, y*y, z*z)
1291
1290
 
1292
1291
 
1293
1292
  def _otherV3d_(x_xyz, y, z, **name):
@@ -1297,6 +1296,18 @@ def _otherV3d_(x_xyz, y, z, **name):
1297
1296
  _otherV3d(x_xyz=x_xyz, **name)
1298
1297
 
1299
1298
 
1299
+ def _over0(p, q):
1300
+ '''(INTERNAL) Return C{p / q} or C{0}.
1301
+ '''
1302
+ return (p / q) if q > fabs(p) else _0_0
1303
+
1304
+
1305
+ def _over02(p, q):
1306
+ '''(INTERNAL) Return C{(p / q)**2} or C{0}.
1307
+ '''
1308
+ return (p / q)**2 if p and q else _0_0
1309
+
1310
+
1300
1311
  def _plumbTo4(x, y, a, b, eps=EPS):
1301
1312
  '''(INTERNAL) Nearest point on and distance to a 2-D ellipse, I{unordered}.
1302
1313
 
@@ -1311,46 +1322,48 @@ def _plumbTo4(x, y, a, b, eps=EPS):
1311
1322
  if not (b > 0 and isfinite(a)):
1312
1323
  raise _ValueError(a=a, b=b)
1313
1324
 
1314
- i, _a = None, fabs
1325
+ i = None
1315
1326
  if y:
1316
1327
  if x:
1317
- u = _a(x / a)
1318
- v = _a(y / b)
1319
- g = _hypot21(u, v)
1320
- if _a(g) < EPS02: # on the ellipse
1321
- a, b, d = x, y, _0_0
1322
- else:
1328
+ u = fabs(x / a)
1329
+ w = fabs(y / b)
1330
+ g = _hypot2_1(u, w)
1331
+ if fabs(g) > EPS02:
1323
1332
  r = (b / a)**2
1324
- t, i = _rootXd(_1_0 / r, 0, u, 0, v, g, eps)
1325
- a = x / (t * r + _1_0)
1326
- b = y / (t + _1_0)
1327
- d = hypot(x - a, y - b)
1333
+ t, i = _rootNd(_1_0 / r, 0, u, 0, w, g) # eps
1334
+ a = _over(x, t * r + _1_0)
1335
+ b = _over(y, t + _1_0)
1336
+ d = hypot(x - a, y - b)
1337
+ else: # on the ellipse
1338
+ a, b, d = x, y, _0_0
1328
1339
  else: # x == 0
1329
1340
  if y < 0:
1330
1341
  b = -b
1331
- a, d = x, _a(y - b)
1342
+ a = x # signed-0
1343
+ d = fabs(y - b)
1332
1344
 
1333
- else: # y == 0
1334
- n = a * x
1335
- d = (a + b) * (a - b)
1336
- if d > _a(n): # PYCHOK no cover
1337
- r = n / d
1345
+ elif x: # y == 0
1346
+ d, r = None, _over0(a * x, (a + b) * (a - b))
1347
+ if r:
1338
1348
  a *= r
1339
1349
  r = _1_0 - r**2
1340
1350
  if r > EPS02:
1341
1351
  b *= sqrt(r)
1342
- d = hypot(x - a, b)
1343
- else:
1344
- b = _0_0
1345
- d = _a(x - a)
1346
- else:
1347
- if x < 0:
1348
- a = -a
1349
- b, d = y, _a(x - a)
1352
+ d = hypot(x - a, y - b)
1353
+ elif x < 0:
1354
+ a = -a
1355
+ if d is None:
1356
+ b = y # signed-0
1357
+ d = fabs(x - a)
1358
+
1359
+ else: # x == y == 0
1360
+ a = x # signed-0
1361
+ d = b
1362
+
1350
1363
  return a, b, d, i
1351
1364
 
1352
1365
 
1353
- def _plumbTo5(x, y, z, Tun, eps=EPS): # MCCABE 19 in .testTriaxials
1366
+ def _plumbTo5(x, y, z, Tun, eps=EPS): # in .testTriaxials
1354
1367
  '''(INTERNAL) Nearest point on and distance to an I{un-/ordered} triaxial.
1355
1368
 
1356
1369
  @see: I{Eberly}'s U{Distance from a Point to ... an Ellipsoid ...<https://
@@ -1370,24 +1383,24 @@ def _plumbTo5(x, y, z, Tun, eps=EPS): # MCCABE 19 in .testTriaxials
1370
1383
  else: # no validation
1371
1384
  val, eps = 0, max(EPS0, -eps)
1372
1385
 
1373
- i, _a = None, fabs
1386
+ i = None
1374
1387
  if z:
1375
1388
  if y:
1376
1389
  if x:
1377
- u = _a(x / a)
1378
- v = _a(y / b)
1379
- w = _a(z / c)
1380
- g = _hypot21(u, v, w)
1381
- if _a(g) < EPS02: # on the ellipsoid
1382
- a, b, c, d = x, y, z, _0_0
1383
- else:
1390
+ u = fabs(x / a)
1391
+ v = fabs(y / b)
1392
+ w = fabs(z / c)
1393
+ g = _hypot2_1(u, v, w)
1394
+ if fabs(g) > EPS02:
1384
1395
  r = T._1e2ac # (c / a)**2
1385
1396
  s = T._1e2bc # (c / b)**2
1386
- t, i = _rootXd(_1_0 / r, _1_0 / s, u, v, w, g, eps)
1387
- a = x / (t * r + _1_0)
1388
- b = y / (t * s + _1_0)
1389
- c = z / (t + _1_0)
1390
- d = hypot_(x - a, y - b, z - c)
1397
+ t, i = _rootNd(_1_0 / r, _1_0 / s, u, v, w, g) # eps
1398
+ a = _over(x, t * r + _1_0)
1399
+ b = _over(y, t * s + _1_0)
1400
+ c = _over(z, t + _1_0)
1401
+ d = hypot_(x - a, y - b, z - c)
1402
+ else: # on the ellipsoid
1403
+ a, b, c, d = x, y, z, _0_0
1391
1404
  else: # x == 0
1392
1405
  a = x # 0
1393
1406
  b, c, d, i = _plumbTo4(y, z, b, c, eps=eps)
@@ -1397,69 +1410,47 @@ def _plumbTo5(x, y, z, Tun, eps=EPS): # MCCABE 19 in .testTriaxials
1397
1410
  else: # x == y == 0
1398
1411
  if z < 0:
1399
1412
  c = -c
1400
- a, b, d = x, y, _a(z - c)
1413
+ a, b, d = x, y, fabs(z - c)
1401
1414
 
1402
1415
  else: # z == 0
1403
- t = True
1404
- d = T._a2c2 # (a + c) * (a - c)
1405
- n = a * x
1406
- if d > _a(n):
1407
- u = n / d
1408
- d = T._b2c2 # (b + c) * (b - c)
1409
- n = b * y
1410
- if d > _a(n):
1411
- v = n / d
1412
- n = _hypot21(u, v)
1413
- if n < 0:
1414
- a *= u
1415
- b *= v
1416
- c *= sqrt(-n)
1417
- d = hypot_(x - a, y - b, c)
1418
- t = False
1419
- if t:
1416
+ u = _over0(a * x, T._a2c2) # (a + c) * (a - c)
1417
+ v = _over0(b * y, T._b2c2) # (b + c) * (b - c)
1418
+ s = _hypot2_1(u, v)
1419
+ if u and v and s < 0:
1420
+ a *= u
1421
+ b *= v
1422
+ c *= sqrt(-s)
1423
+ d = hypot_(x - a, y - b, c)
1424
+ else:
1420
1425
  c = z # signed-0
1421
1426
  a, b, d, i = _plumbTo4(x, y, a, b, eps=eps)
1422
1427
 
1423
- if val > 0: # validate
1424
- e = T.sideOf(a, b, c, eps=val)
1425
- if e: # not near the ellipsoid's surface
1426
- raise _ValueError(a=a, b=b, c=c, d=d,
1427
- sideOf=e, eps=val)
1428
- if d: # angle of delta and normal vector
1429
- m = Vector3d(x, y, z).minus_(a, b, c)
1430
- if m.euclid > val:
1431
- m = m.unit()
1432
- n = T.normal3d(a, b, c)
1433
- e = n.dot(m) # n.negate().dot(m)
1434
- if not isnear1(_a(e), eps1=val):
1435
- raise _ValueError(n=n, m=m,
1436
- dot=e, eps=val)
1428
+ if val > 0:
1429
+ _validate(a, b, c, d, T, x, y, z, val)
1437
1430
  return a, b, c, d, i
1438
1431
 
1439
1432
 
1440
- def _rootXd(r, s, u, v, w, g, eps):
1441
- '''(INTERNAL) Robust 2d- or 3d-root finder: 2d- if C{s == v == 0} else 3d-root.
1433
+ def _rootNd(r, s, u, v, w, g, eps=EPS0):
1434
+ '''(INTERNAL) Robust 2-D or 3-D root finder: 2-D if C{s == v == 0} else 3-D root.
1442
1435
 
1443
- @see: I{Eberly}'s U{Robust Root Finders ...<https://www.GeometricTools.com/
1444
- Documentation/DistancePointEllipseEllipsoid.pdf>}.
1436
+ @see: I{Eberly}'s U{Robust Root Finders ... and Listing 4<https://
1437
+ www.GeometricTools.com/Documentation/DistancePointEllipseEllipsoid.pdf>}.
1445
1438
  '''
1446
- _1, __2 = _1_0, _0_5
1447
- _a, _h21 = fabs, _hypot21
1448
-
1449
1439
  u *= r
1450
- v *= s # 0 for 2d-root
1451
- t0 = w - _1
1452
- t1 = _0_0 if g < 0 else (hypot_(u, w, v) - _1)
1440
+ v *= s # 0 for 2-D root
1441
+ t0 = w + _N_1_0
1442
+ t1 = _0_0 if g < 0 else (hypot_(u, w, v) + _N_1_0)
1453
1443
  # assert t0 <= t1
1454
- for i in range(1, _TRIPS): # 48-55
1455
- e = _a(t0 - t1)
1456
- if e < eps:
1457
- break
1458
- t = (t0 + t1) * __2
1459
- if t in (t0, t1):
1444
+ _e = -eps
1445
+ for i in range(1, _TRIPS): # 47..65
1446
+ t = (t1 + t0) * _0_5
1447
+ e = t1 - t0
1448
+ if _e < e < eps or t in (t0, t1):
1460
1449
  break
1461
- g = _h21(u / (t + r), w / (t + _1),
1462
- (v / (t + s)) if v else 0)
1450
+ g = fsumf_(_N_1_0, # ~= _hypot2_1
1451
+ _over02(u, t + r),
1452
+ _over02(w, t - _N_1_0),
1453
+ _over02(v, t + s) if v else _0_0)
1463
1454
  if g > 0:
1464
1455
  t0 = t
1465
1456
  elif g < 0:
@@ -1468,17 +1459,32 @@ def _rootXd(r, s, u, v, w, g, eps):
1468
1459
  break
1469
1460
  else: # PYCHOK no cover
1470
1461
  t = Fmt.no_convergence(e, eps)
1471
- raise _ValueError(t, txt__=_rootXd)
1462
+ raise _ValueError(t, txt__=_rootNd)
1472
1463
  return t, i
1473
1464
 
1474
1465
 
1475
- def _sideOf(xyz, abc, eps=EPS):
1476
- '''(INTERNAL) Helper for C{_hartzell3}, M{.sideOf} and M{.reverseCartesian}.
1466
+ def _sqrt0(x):
1467
+ '''(INTERNAL) C{sqrt0} with C{TriaxialError}.
1468
+ '''
1469
+ return sqrt0(x, Error=TriaxialError)
1470
+
1477
1471
 
1478
- @return: M{sum((x / a)**2 for x, a in zip(xyz, abc)) - 1} or C{INT0}.
1472
+ def _validate(a, b, c, d, T, x, y, z, val):
1473
+ '''(INTERNAL) Validate an C{_plumTo5} result.
1479
1474
  '''
1480
- s = fsumf_(_N_1_0, *((x / a)**2 for x, a in _zip(xyz, abc) if a)) # strict=True
1481
- return INT0 if fabs(s) < eps else s
1475
+ e = T.sideOf(a, b, c, eps=val)
1476
+ if e: # not near the ellipsoid's surface
1477
+ raise _ValueError(a=a, b=b, c=c, d=d,
1478
+ sideOf=e, eps=val)
1479
+ if d: # angle of delta and normal vector
1480
+ m = Vector3d(x, y, z).minus_(a, b, c)
1481
+ if m.euclid > val:
1482
+ m = m.unit()
1483
+ n = T.normal3d(a, b, c)
1484
+ e = n.dot(m) # n.negate().dot(m)
1485
+ if not isnear1(fabs(e), eps1=val):
1486
+ raise _ValueError(n=n, m=m,
1487
+ dot=e, eps=val)
1482
1488
 
1483
1489
 
1484
1490
  if __name__ == '__main__':