Coverage for pygeodesy/constants.py: 98%

207 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-09-09 13:03 -0400

1 

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

3 

4u'''Single-instance C{float} and C{int} constants across C{pygeodesy} modules and related 

5functions L{pygeodesy.float_}, L{pygeodesy.float0_}, L{pygeodesy.isclose}, L{pygeodesy.isfinite}, 

6L{pygeodesy.isinf}, L{pygeodesy.isint0}, L{pygeodesy.isnan}, L{pygeodesy.isnear0}, 

7L{pygeodesy.isnear1}, L{pygeodesy.isnear90}, L{pygeodesy.isneg0}, L{pygeodesy.isninf}, 

8L{pygeodesy.isnon0} and L{pygeodesy.remainder}. 

9''' 

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

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

12 

13from pygeodesy.basics import _copysign, isbool, iscomplex, isint, signBit 

14from pygeodesy.errors import _xError, _xError2, _xkwds_get1, _xkwds_item2 

15# from pygeodesy.fsums import _isFsum_2Tuple # _MODS 

16from pygeodesy.internals import _0_0, _100_0, typename 

17from pygeodesy.interns import _DMAIN_, _INF_, _NAN_ 

18from pygeodesy.lazily import _ALL_MODS as _MODS, _ALL_LAZY 

19# from pygeodesy.streprs import Fmt # from .unitsBase 

20from pygeodesy.unitsBase import Float, Int, Radius, Fmt 

21 

22from math import fabs, isinf, isnan, pi as _pi, sqrt 

23try: 

24 from math import inf as _inf, nan as _nan # PYCHOK Python 3+ 

25except ImportError: # Python 2- 

26 _inf, _nan = float(_INF_), float(_NAN_) 

27 

28__all__ = _ALL_LAZY.constants 

29__version__ = '25.09.09' 

30 

31 

32def _copysign_0_0(y): 

33 '''(INTERNAL) copysign(0.0, y), only C{float}. 

34 ''' 

35 return _N_0_0 if signBit(y) else _0_0 

36 

37 

38def _copysign_1_0(y): 

39 '''(INTERNAL) copysign(1.0, y), only C{float}. 

40 ''' 

41 return _N_1_0 if signBit(y) else _1_0 

42 

43 

44def _copysignINF(y): 

45 '''(INTERNAL) copysign(INF, y), only C{float}. 

46 ''' 

47 return NINF if signBit(y) else INF 

48 

49 

50def _flipsign(x, y): 

51 '''(INTERNAL) Negate C{x} for negative C{y}. 

52 ''' 

53 return (-x) if signBit(y) else x 

54 

55 

56def _Float(**name_arg): 

57 '''(INTERNAL) New named, cached C{Float}. 

58 ''' 

59 n, arg = _xkwds_item2(name_arg) 

60 return Float(_float(arg), name=n) 

61 

62 

63def _Radius(**name_arg): 

64 '''(INTERNAL) New named, cached C{Radius}. 

65 ''' 

66 n, arg = _xkwds_item2(name_arg) 

67 return Radius(_float(arg), name=n) 

68 

69 

70def float_(*fs, **sets): # sets=False 

71 '''Get scalars as C{float} or I{intern}'ed C{float}. 

72 

73 @arg fs: One more values (C{scalar}), all positional. 

74 @kwarg sets: Use C{B{sets}=True} to C{intern} each 

75 B{C{fs}}, otherwise don't C{intern}. 

76 

77 @return: A single C{float} if only one B{C{fs}} is 

78 given, otherwise a tuple of C{float}s. 

79 

80 @raise TypeError: Some B{C{fs}} is not C{scalar}. 

81 ''' 

82 fl = [] 

83 _a = fl.append 

84 _f = _floats.setdefault if _xkwds_get1(sets, sets=False) else \ 

85 _floats.get 

86 try: 

87 for i, f in enumerate(fs): 

88 f = float(f) 

89 _a(_f(f, f)) 

90 except Exception as x: 

91 E, t = _xError2(x) 

92 fs_i = Fmt.SQUARE(fs=i) 

93 raise E(fs_i, f, txt=t) 

94 return fl[0] if len(fl) == 1 else tuple(fl) 

95 

96 

97def _float(f): # in .datums, .ellipsoids, ... 

98 '''(INTERNAL) Cache initial C{float}s. 

99 ''' 

100 f = float(f) 

101 return _floats.setdefault(f, f) # PYCHOK del _floats 

102 

103 

104def float0_(*xs): 

105 '''Yield C{B{x}s} as a non-NEG0 C{float}. 

106 ''' 

107 for x in xs: 

108 yield float(x) if x else _0_0 

109 

110 

111def _float0(f): # in .auxilats.auxily, .resections, .vector3dBase, ... 

112 '''(INTERNAL) Return C{float(B{f})} or C{INT0}. 

113 ''' 

114 if f: 

115 f = float(f) 

116 f = _floats.get(f, f) 

117 elif f is not INT0: 

118 f = float(f) or _0_0 # force None, NN error 

119 return f 

120 

121 

122def _floatuple(*fs): 

123 '''(INTERNAL) Cache a tuple of C{float}s. 

124 ''' 

125 return tuple(map(_float, fs)) 

126 

127 

128try: 

129 from math import log2 as _log2 

130except ImportError: # Python 3.3- 

131 from math import log as _log 

132 

133 def _log2(x): # in .rhumb.aux_, .auxilats.auxLat 

134 return _log(x, 2) 

135 

136 

137def _naninf(p, *xs): 

138 '''(INTERNAL) Return C{NAN}, C{NINF} or C{INF}. 

139 ''' 

140 for x in xs: 

141 p *= x # fmath.fprod(xs) 

142 return _copysignINF(p) if isfinite(p) else p 

143 

144 

145def _over(p, q): 

146 '''(INTERNAL) Return C{B{p} / B{q}} without C{ZeroDivisionError} exceptions. 

147 ''' 

148 try: 

149 return (p / q) # if p else _copysign_0_0(q) 

150 except ZeroDivisionError: 

151 return (_copysignINF(p) if isfinite(p) else NAN) if p else p 

152 

153 

154def _over_1(p, q): 

155 '''(INTERNAL) Return C{B{p} / B{q}} with exact C{1.0} and without C{ZeroDivisionError} exceptions. 

156 ''' 

157 if fabs(p) != fabs(q): 

158 r = _over(p, q) 

159 else: 

160 r = _flipsign(p, q) 

161 if p: 

162 r = _copysign_1_0(r) 

163 return r 

164 

165 

166def _1_over(x): 

167 '''(INTERNAL) Return reciprocal C{1 / B{x}} avoiding C{ZeroDivisionError} exceptions. 

168 ''' 

169 try: 

170 return _1_0 / float(x) 

171 except ZeroDivisionError: 

172 return NINF if isneg0(x) else INF 

173 

174 

175_floats = {} # PYCHOK floats cache, in .__main__ 

176# _float = float # PYCHOK expected 

177# del _floats # XXX zap floats cache never 

178 

179_0_0 = _float( _0_0) # PYCHOK expected 

180_0_0_1T = _0_0, # PYCHOK 1-tuple 

181_0_0_9T = _0_0_1T * 9 # PYCHOK 9-tuple 

182_0_0001 = _float( 0.0001) # PYCHOK expected 

183_0_001 = _float( 0.001) # PYCHOK expected 

184_0_01 = _float( 0.01) # PYCHOK expected 

185_0_1 = _float( 0.1) # PYCHOK expected 

186_0_125 = _float( 0.125) # PYCHOK expected 

187_0_25 = _float( 0.25) # PYCHOK expected 

188_0_5 = _float( 0.5) # PYCHOK expected 

189_1_0 = _float( 1) # PYCHOK expected 

190_1_0_1T = _1_0, # PYCHOK 1-tuple 

191_1_5 = _float( 1.5) # PYCHOK expected 

192_1_75 = _float( 1.75) # PYCHOK expected 

193_2_0 = _float( 2) # PYCHOK expected 

194_3_0 = _float( 3) # PYCHOK expected 

195_4_0 = _float( 4) # PYCHOK expected 

196_5_0 = _float( 5) # PYCHOK expected 

197_6_0 = _float( 6) # PYCHOK expected 

198_8_0 = _float( 8) # PYCHOK expected 

199_9_0 = _float( 9) # PYCHOK expected 

200_10_0 = _float( 10) # PYCHOK expected 

201_16_0 = _float( 16) # PYCHOK expected 

202_32_0 = _float( 32) # PYCHOK expected 

203_45_0 = _float( 45) # PYCHOK expected 

204_60_0 = _float( 60) # PYCHOK expected 

205_64_0 = _float( 64) # PYCHOK expected 

206_90_0 = _float( 90) # PYCHOK expected 

207_100_0 = _float(_100_0) # PYCHOK expected 

208_180_0 = _float( 180) # PYCHOK expected 

209_270_0 = _float( 270) # PYCHOK expected 

210_360_0 = _float( 360) # PYCHOK expected 

211_720_0 = _float( 720) # PYCHOK expected 

212_1000_0 = _float(1000) # PYCHOK expected 

213_3600_0 = _float(3600) # PYCHOK expected 

214 

215_N_0_0 = float( '-0.0') # PYCHOK NOT _float! 

216_N_0_5 = _float( -_0_5) # PYCHOK expected 

217_N_1_0 = _float( -_1_0) # PYCHOK expected 

218_N_2_0 = _float( -_2_0) # PYCHOK expected 

219_N_90_0 = _float( -_90_0) # PYCHOK expected 

220_N_180_0 = _float(-_180_0) # PYCHOK expected 

221 

222_M_KM = _1000_0 # meter per Kilo meter, see .utily 

223_M_NM = _float(1852.0) # meter per Nautical Mile, exactly 

224_M_SM = _float(1609.344) # meter per Statute Mile 

225 

226try: 

227 from sys import float_info as _f_i 

228 # @see: <https://NumPy.org/doc/stable/reference/generated/numpy.finfo.html> 

229 DIG = Int( DIG =_f_i.dig) # PYCHOK system's float decimal digits 

230 EPS = _Float(EPS =_f_i.epsilon) # PYCHOK system's EPSilon 

231 MANT_DIG = Int( MANT_DIG=_f_i.mant_dig) # PYCHOK system's float mantissa bits 

232 MAX = _Float(MAX =_f_i.max) # PYCHOK system's MAX float 1.7976931348623157e+308 

233 MAX_EXP = Int( MAX_EXP =_f_i.max_exp) # PYTHON system's max base 2 exponent 

234 MIN = _Float(MIN =_f_i.min) # PYCHOK system's MIN float 2.2250738585072014e-308 

235 MIN_EXP = Int( MIN_EXP =_f_i.min_exp) # PYTHON system's min base 2 exponent 

236# RADIX = Int( RADIX =_f_i.radix) # PYTHON system's float base 

237 del _f_i 

238except ImportError: # PYCHOK no cover 

239 DIG = Int( DIG =15) # PYCHOK system's 64-bit float decimal digits 

240 EPS = _Float(EPS =2.220446049250313e-16) # PYCHOK EPSilon 2**-52, M{EPS +/- 1 != 1} 

241 MANT_DIG = Int( MANT_DIG=53) # PYCHOK float mantissa bits ≈ 53 (C{int}) 

242 MAX = _Float(MAX =pow(_2_0, 1023) * (_2_0 - EPS)) # PYCHOK ≈ 10**308 

243 MAX_EXP = Int( MAX_ESP =_log2(MAX)) # 308 base 10 

244 MIN = _Float(MIN =pow(_2_0, -1021)) # PYCHOK ≈ 10**-308 

245 MIN_EXP = Int(MIN_EXP =_log2(MIN)) # -307 base 10 

246# RADIX = Int(Radix =2) # base 

247 

248EPS0 = _Float( EPS0 = EPS**2) # PYCHOK near-/non-zero comparison 4.930381e-32, or EPS or EPS_2 

249EPS02 = _Float( EPS02 = EPS**4) # PYCHOK near-zero-squared comparison 2.430865e-63 

250EPS_2 = _Float( EPS_2 = EPS / _2_0) # PYCHOK ≈ 1.110223024625e-16 

251EPS1 = _Float( EPS1 =_1_0 - EPS) # PYCHOK ≈ 0.9999999999999998 

252EPS2 = _Float( EPS2 = EPS * _2_0) # PYCHOK ≈ 4.440892098501e-16 

253EPS4 = _Float( EPS4 = EPS * _4_0) # PYCHOK ≈ 8.881784197001e-16 

254# _1EPS = _Float(_1EPS =_1_0 + EPS) # PYCHOK ≈ 1.0000000000000002 

255_1_EPS = _Float(_1_EPS =_1_0 / EPS) # PYCHOK = 4503599627370496.0 

256# _2_EPS = _Float(_2_EPS =_2_0 / EPS) # PYCHOK = 9007199254740992.0 

257_EPS2e4 = _Float(_EPS2e4= EPS2 * 1.e4) # PYCHOK ≈ 4.440892098501e-12 

258_EPS4e8 = _Float(_EPS4e8= EPS4 * 1.e8) # PYCHOK ≈ 8.881784197001e-08 

259_EPSjam = _Float(_EPSjam= pow(EPS, 0.75)) # PYCHOK = 1.818989403546e-12 

260_EPSmin = _Float(_EPSmin= sqrt(MIN)) # PYCHOK = 1.49166814624e-154 

261_EPSqrt = _Float(_EPSqrt= sqrt(EPS)) # PYCHOK = 1.49011611938e5-08 

262_EPStol = _Float(_EPStol=_EPSqrt * _0_1) # PYCHOK = 1.49011611938e5-09 == sqrt(EPS * _0_01) 

263 

264_89_999 = _Float(_89_999=_90_0 * EPS1) # just below 90.0 

265# <https://Numbers.Computation.Free.FR/Constants/Miscellaneous/digits.html> 

266# _1__90 = _Float(_1__90 =_1_0 / _90_0) # PYCHOK = 0.011_111_111_111_111_111_111_111_111_111_111_111_111_111_111_11111 

267_2__PI = _Float(_2__PI =_2_0 / _pi) # PYCHOK = 0.636_619_772_367_581_343_075_535_053_490_057_448_137_838_582_96182 

268 

269_1_16th = _Float(_1_16th=_1_0 / _16_0) # PYCHOK in .ellipsoids, .karney 

270_1_3rd = _Float(_1_3rd =_1_0 / _3_0) # PYCHOK in .fmath 

271_1_6th = _Float(_1_6th =_1_0 / _6_0) # PYCHOK in .fmath 

272 

273_K0_UTM = _Float(_K0_UTM = 0.9996) # PYCHOK in .etm, .ktm, .utm, UTM scale at central meridian 

274# sqrt(2) <https://WikiPedia.org/wiki/Square_root_of_2> 

275# 1.414213562373095_048_801_688_724_209_698_078_569_671_875_376_948_073_176_679_737_99 

276# _1SQRT2= _Float(_1SQRT2 =sqrt(_2_0) + 1) 

277# 0.707106781186547_524_400_844_362_104_849_039_284_835_937_688_474_036_588_339_868_99 

278_SQRT2_2 = _Float(_SQRT2_2=sqrt(_0_5)) # PYCHOK = 0.707106781186547_5 == sqrt(2) / 2 

279 

280INF = Float(INF =_inf) # PYCHOK INFinity, see function L{isinf}, L{isfinite}, NOT _Float! 

281INT0 = Int( INT0= 0) # PYCHOK unique int(0) instance, see .fsums, useZ=False 

282NAN = Float(NAN =_nan) # PYCHOK Not-A-Number, see function L{isnan}, NOT _Float! 

283NEG0 = Float(NEG0=_N_0_0) # PYCHOK NEGative 0.0, see function L{isneg0}, NOT _Float! 

284NINF = Float(NINF=-INF) # PYCHOK Negative INFinity, NOT _Float! 

285 

286PI = _Float(PI =_pi) # 3.1415_9265_3589_7932_3846_2643_3832_795 

287PI2 = _Float(PI2 =_pi * _2_0) # PYCHOK Two PI, M{PI * 2} aka I{Tau} 

288PI_2 = _Float(PI_2 =_pi / _2_0) # PYCHOK Half PI, M{PI / 2} 

289PI3 = _Float(PI3 =_pi * _3_0) # PYCHOK Three PI, M{PI * 3} 

290PI3_2 = _Float(PI3_2=_pi * _1_5) # PYCHOK PI and a half, M{PI * 3 / 2} 

291PI_3 = _Float(PI_3 =_pi / _3_0) # PYCHOK One third PI, M{PI / 3} 

292PI4 = _Float(PI4 =_pi * _4_0) # PYCHOK Four PI, M{PI * 4} 

293PI_4 = _Float(PI_4 =_pi / _4_0) # PYCHOK Quarter PI, M{PI / 4} 

294PI_6 = _Float(PI_6 =_pi / _6_0) # PYCHOK One sixth PI, M{PI / 6} 

295 

296R_MA = _Radius(R_MA=6378137.0) # PYCHOK equatorial earth radius (C{meter}), WGS84, EPSG:3785 

297R_MB = _Radius(R_MB=6356752.3) # PYCHOK polar earth radius (C{meter}), WGS84, EPSG:3785 

298R_M = _Radius(R_M =6371008.771415) # PYCHOK mean, spherical earth radius (C{meter}) 

299R_KM = _Radius(R_KM=R_M / _M_KM) # PYCHOK mean, spherical earth radius (C{KM}, Kilo meter) 

300R_NM = _Radius(R_NM=R_M / _M_NM) # PYCHOK mean, spherical earth radius (C{NM}, nautical miles) 

301R_SM = _Radius(R_SM=R_M / _M_SM) # PYCHOK mean, spherical earth radius (C{SM}, statute miles) 

302# See <https://www.EdWilliams.org/avform.htm>, <https://www.DTIC.mil/dtic/tr/fulltext/u2/a216843.pdf> 

303# and <https://GitHub.com/NASA/MultiDop/blob/master/src/share/man/man3/geog_lib.3> based on the 

304# International Standard Nautical Mile of 1,852 meter (1' latitude) 

305R_FM = _Radius(R_FM=6371000.0) # PYCHOK former FAI Sphere earth radius (C{meter}) 

306R_GM = _Radius(R_GM=6371230.0) # PYCHOK avg. radius, distance to geoid surface (C{meter}) 

307# <http://Wiki.GIS.com/wiki/index.php/Ellipsoidal_quadratic_mean_radius> 

308R_QM = _Radius(R_QM=6372797.560856) # PYCHOK earth' quadratic mean radius (C{meter}) 

309# Rtri= _Radius(Rtri=6372797.5559594) # PYCHOK Rtriaxial quadratic mean radius (C{meter}), WGS84 

310# Rbi = _Radius(Rbi =6367453.6345163) # PYCHOK Rbiaxial quadratic mean radius (C{meter}), WGS84 

311R_VM = _Radius(R_VM=6366707.0194937) # PYCHOK aViation/naVigation earth radius (C{meter}) 

312# R_AU= Meter( R_AU=149597870700.0) # PYCHOK <https://WikiPedia.org/wiki/Astronomical_unit> 

313 

314_INF_NAN_NINF = INF, NAN, NINF 

315_pos_self = _1_0.__pos__() is _1_0 # PYCHOK in .fsums, .vector3dBase 

316 

317 

318def _0_0s(n): 

319 '''(INTERNAL) Return an C{B{n}-tuple} of C{_0_0} zeros. 

320 ''' 

321 return _0_0_9T[:n] if 0 <= n <= len(_0_0_9T) else (_0_0_1T * n) 

322 

323 

324try: 

325 from math import isclose as _isclose 

326except ImportError: # Python 3.4- 

327 

328 def _isclose(a, b, rel_tol=1e-9, abs_tol=0): 

329 '''Mimick Python 3.5+ C{math.isclose}. 

330 ''' 

331 t, d = abs_tol, fabs(a - b) 

332 if d > t: 

333 r = max(fabs(a), fabs(b)) * rel_tol 

334 t = max(r, t) 

335 return d <= t 

336 

337 

338def isclose(a, b, rel_tol=1e-12, abs_tol=EPS0): 

339 '''Like C{math.isclose}, but with defaults such 

340 that C{isclose(0, EPS0)} is C{True} by default. 

341 ''' 

342 return _isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol) 

343 

344 

345try: 

346 from cmath import isfinite as _iscfinite 

347except ImportError: # Python 3.1- 

348 

349 def _iscfinite(x): # PYCHOK not self? 

350 '''Mimick Python 3.2+ C{cmath.isfinite}. 

351 ''' 

352 return _isfinite(x.real) and _isfinite(x.imag) 

353 

354try: 

355 from math import isfinite as _isfinite # in .ellipsoids, ... 

356except ImportError: # Python 3.1- 

357 

358 def _isfinite(x): # PYCHOK not self? 

359 '''Mimick Python 3.2+ C{math.isfinite}. 

360 ''' 

361 return not (isinf(x) or isnan(x)) 

362 

363 

364def isfinite(obj): 

365 '''Check a finite C{scalar}, C{complex}, ... value. 

366 

367 @arg obj: Value (C{scalar}, C{complex}, an L{Fsum} or 

368 L{Fsum2Tuple}). 

369 

370 @return: C{False} if B{C{obj}} is C{INF}, C{NINF} or 

371 C{NAN}, C{True} otherwise. 

372 

373 @raise TypeError: Non-scalar and non-complex B{C{obj}}. 

374 ''' 

375 try: 

376 return (obj not in _INF_NAN_NINF) and _isfinite(obj) 

377 except Exception as x: 

378 if iscomplex(obj): # _isfinite(complex) thows TypeError 

379 return _iscfinite(obj) 

380 if _MODS.fsums._isFsum_2Tuple(obj): # OverflowError 

381 return obj.is_finite() 

382 raise _xError(x, Fmt.PAREN(typename(isfinite), obj)) 

383 

384 

385def isint0(obj, both=False): 

386 '''Check for L{INT0} or C{int(0)} value. 

387 

388 @arg obj: The object (any C{type}). 

389 @kwarg both: If C{true}, also check C{float(0)} (C{bool}). 

390 

391 @return: C{True} if B{C{obj}} is L{INT0}, C{int(0)} or 

392 C{float(0)}, C{False} otherwise. 

393 ''' 

394 return (obj is INT0 or obj is int(0) or 

395 bool(both and (not obj) and isint(obj, both=True))) and \ 

396 not isbool(obj) 

397 

398 

399def isnear0(x, eps0=EPS0): 

400 '''Is B{C{x}} near I{zero} within a tolerance? 

401 

402 @arg x: Value (C{scalar}). 

403 @kwarg eps0: Near-I{zero} tolerance (C{EPS0}). 

404 

405 @return: C{True} if C{abs(B{x}) < B{eps0}}, 

406 C{False} otherwise. 

407 

408 @see: Function L{isnon0}. 

409 ''' 

410 return bool(eps0 > x > -eps0) 

411 

412 

413def isnear1(x, eps1=EPS0): 

414 '''Is B{C{x}} near I{one} within a tolerance? 

415 

416 @arg x: Value (C{scalar}). 

417 @kwarg eps1: Near-I{one} tolerance (C{EPS0}). 

418 

419 @return: C{isnear0(B{x} - 1, eps0=B{eps1})}. 

420 

421 @see: Function L{isnear0}. 

422 ''' 

423 return bool(eps1 > (x - _1_0) > -eps1) 

424 

425 

426def isnear90(x, eps90=EPS0): 

427 '''Is B{C{x}} near I{90} within a tolerance? 

428 

429 @arg x: Value (C{scalar}). 

430 @kwarg eps90: Near-I{90} tolerance (C{EPS0}). 

431 

432 @return: C{isnear0(B{x} - 90, eps0=eps90)}. 

433 

434 @see: Function L{isnear0}. 

435 ''' 

436 return bool(eps90 > (x - _90_0) > -eps90) 

437 

438 

439def isneg(x): 

440 '''Check for negative C{x}, including L{NEG0}. 

441 

442 @arg x: Value (C{scalar}). 

443 

444 @return: C{True} if C{B{x} < 0 or NEG0}, 

445 C{False} otherwise. 

446 ''' 

447 return signBit(x) 

448 

449 

450def isneg0(x): 

451 '''Check for L{NEG0}, negative C{0.0}. 

452 

453 @arg x: Value (C{scalar}). 

454 

455 @return: C{True} if B{C{x}} is C{NEG0} or C{-0.0}, 

456 C{False} otherwise. 

457 ''' 

458 return (not x) and _copysign(1, x) < 0 

459# and str(x).startswith(_MINUS_) 

460 

461 

462def isninf(x): 

463 '''Check for L{NINF}, negative C{INF}. 

464 

465 @arg x: Value (C{scalar}). 

466 

467 @return: C{True} if B{C{x}} is C{NINF} or C{-inf}, 

468 C{False} otherwise. 

469 ''' 

470 return x is NINF or (x < 0 and not isfinite(x)) 

471 

472 

473def isnon0(x, eps0=EPS0): 

474 '''Is B{C{x}} non-zero with a tolerance? 

475 

476 @arg x: Value (C{scalar}). 

477 @kwarg eps0: Non-zero tolerance (C{EPS0}). 

478 

479 @return: C{True} if C{abs(B{x}) > B{eps0}}, 

480 C{False} otherwise. 

481 

482 @see: Function L{isnear0}. 

483 ''' 

484 return not bool(eps0 > x > -eps0) # not isnear0 

485 

486 

487def _off90(lat): 

488 '''(INTERNAL) Off 90.0 for .gars and .wgrs. 

489 ''' 

490 return max(min(lat, _89_999), -_89_999) 

491 

492 

493try: 

494 from math import remainder 

495except ImportError: # Python 3.6- 

496 from math import fmod as _fmod 

497 

498 def remainder(x, y): 

499 '''Mimick Python 3.7+ C{math.remainder}. 

500 ''' 

501 if isnan(y): 

502 x = NAN 

503 elif x and not isnan(x): 

504 y = fabs(y) 

505 x = _fmod(x, y) 

506 h = _0_5 * y 

507 if x >= h: 

508 x -= y 

509 elif x < -h: 

510 x += y 

511 return x # keep signed 0.0 

512 

513 

514def _umod_360(deg): 

515 '''(INTERNAL) Non-negative C{deg} modulo 360, basic C{.utily.wrap360}. 

516 ''' 

517 return (deg % _360_0) or _0_0 

518 

519 

520def _umod_PI2(rad): 

521 '''(INTERNAL) Non-negative C{rad} modulo PI2, basic C{.utily.wrapPI2}. 

522 ''' 

523 return (rad % PI2) or _0_0 

524 

525 

526if __name__ == _DMAIN_: 

527 

528 def _main(globalocals): 

529 from pygeodesy import itemsorted, printf 

530 from pygeodesy.interns import _DALL_, _UNDER_ 

531 

532 t = n = v = [] 

533 for n, v in itemsorted(globalocals): 

534 if isinstance(v, (Float, Int, Radius)): 

535 printf('%9s: %r', n, v.toRepr(std=False)) 

536 if v.name != n: 

537 raise AssertionError('%r != %r' % (n, v)) 

538 if v.name is not n: 

539 raise AssertionError('%r is not %r' % (n, v)) 

540 if not n.startswith(_UNDER_): 

541 t.append(n) 

542 t.append(typename(float_)) 

543 printf('%s = %r', _DALL_, tuple(t)) 

544 

545 _main(globals()) # or locals() 

546 

547# **) MIT License 

548# 

549# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

550# 

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

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

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

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

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

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

557# 

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

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

560# 

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

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

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

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

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

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

567# OTHER DEALINGS IN THE SOFTWARE.