Coverage for pygeodesy/auxilats/_CX_Rs.py: 96%

84 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-05-29 12:40 -0400

1 

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

3 

4u'''(INTERNAL) Classes C{_Rcoeffs}, C{_Rdict} and C{_Rtuple} to store the deferred 

5Python versions of coefficients from I{Karney}'s C++ class U{AuxLatitude 

6<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1AuxLatitude.html>}. 

7 

8Copyright (C) Charles Karney (2022-2024) Karney@Alum.MIT.edu> and licensed under the 

9MIT/X11 License. For more information, see <https://GeographicLib.SourceForge.io>. 

10''' 

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

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

13 

14# from pygeodesy.basics import _splituple # _MODS 

15from pygeodesy.constants import _floats as _constants_floats 

16from pygeodesy.errors import _AssertionError, _MODS 

17# from pygeodesy.internals import _sizeof # _MODS 

18from pygeodesy.interns import NN, MISSING, _duplicate_, _NL_, \ 

19 _QUOTE3_, _SLASH_, _ELLIPSIS4_ # PYCHOK used! 

20# from pygeodesy.lazily import _ALL_MODS as _MODS # from .errors 

21from pygeodesy.named import ADict, Property_RO 

22# from pygeodesy.props import Property_RO # from .named 

23 

24__all__ = () 

25__version__ = '25.05.12' 

26 

27 

28class _Rcoeffs(ADict): 

29 '''(INTERNAL) With string-ified C{keys}. 

30 ''' 

31 def __init__(self, ALorder, coeffs): 

32 '''New C{_Rcoeffs} from a C{coeffs} dict. 

33 ''' 

34 try: 

35 if not isinstance(coeffs, dict): 

36 raise _RsError(coeffs=type(coeffs)) 

37 n = 0 

38 for k, d in coeffs.items(): 

39 if not isinstance(k, _Rkey): 

40 raise _RsError(k=type(k)) 

41 if not isinstance(d, _Rdict): 

42 raise _RsError(k=k, d=type(d)) 

43 n += d.n 

44 

45 ADict.__init__(self, coeffs) 

46 self.set_(ALorder=ALorder, n=n) # in .validate 

47 except Exception as x: 

48 raise _RsError(ALorder=ALorder, cause=x) 

49 

50 def bnuz4(self): # in .auxilats.__main__ # PYCHOK no cover 

51 # get C{(strB, number, unique, floatB)} rationals 

52 b = n = u = z = 0 

53 _zB = _MODS.internals._sizeof 

54 for R in self._Rtuples(): 

55 _, _, rs = R.k_n_rs 

56 b += _zB(rs) 

57 t = R._tuple 

58 z += _zB(t) # Float 

59 # assert R.Rdict is None 

60 n += len(t) 

61 u += sum(1 for f in t if f in _constants_floats) 

62 return b, n, (n - u), z 

63 

64 def items(self): # string-ify keys # PYCHOK no cover 

65 for n, v in ADict.items(self): 

66 yield str(n), v 

67 

68 def _Rtuples(self): # PYCHOK no cover 

69 for d in self.values(): 

70 if isinstance(d, _Rdict): 

71 # yield from d.values() 

72 for R in d.values(): 

73 yield R 

74 

75 def _validate(self, aL, lenAux): 

76 # in .auxily.Aux._CXcoeffs(al, Aux.len(aL)) 

77 a, n = self.ALorder, self.n # PYCHOK ADict! 

78# for R in self._Rtuples(): 

79# assert isinstance(R, _Rtuple) 

80 if aL != a or lenAux != n: 

81 raise _RsError(aL=aL, ALorder=a, lenAux=lenAux, n=n) 

82 return self 

83 

84 

85class _Rdict(dict): # in ._CX_#, .auxLat, .rhumb.aux_ 

86 '''(INTERNAL) Dict of C{_Rtuple}s. 

87 ''' 

88 n = 0 # sum(R.k_n_k[1] for R in Rtuples) 

89 

90 def __init__(self, nt, *Rtuples): 

91 '''New C{_Rdict}. 

92 ''' 

93 if not Rtuples: 

94 raise _RsError(Rtuples=MISSING) 

95 

96 for R in Rtuples: 

97 if not isinstance(R, _Rtuple): 

98 raise _RsError(R, R=type(R)) 

99 k, n, _ = R.k_n_rs 

100 if k in self: 

101 raise _RsError(_duplicate_, k=k) 

102 R.Rdict = self 

103 self[k] = R # overwritten in self._floatuple 

104 self.n += n 

105 if self.n != nt: 

106 raise _RsError(n=n, nt=nt) 

107 

108 def _floats(self, rs): 

109 # yield floats from a string of comma-separated rationals 

110 def _p_q(p=NN, q=1, *x): 

111 return (NN if x else p), q 

112 

113 _get = _constants_floats.get 

114 for r in _MODS.basics._splituple(rs): 

115 p, q = _p_q(*r.split(_SLASH_)) 

116 if p: 

117 f = int(p) / int(q) # fractions.Fraction? 

118 if not isinstance(f, float): 

119 raise _RsError(rs, f=f, p=p, q=q, r=r) 

120 yield _get(f, f) # from .constants 

121 else: 

122 raise _RsError(rs, r=r) 

123 

124 def _floatuple(self, Rtuple): 

125 # return a tuple of floats from an C{_Rtuple} 

126 k, n, rs = Rtuple.k_n_rs 

127 t = tuple(f for m in map(self._floats, rs) 

128 for f in m) # == yield f 

129 # @see: <https://StackOverflow.com/questions/10632839/> 

130 # and <https://realPython.com/python-flatten-list/> 

131 if len(t) != n: 

132 raise _RsError(*rs, len=len(t), n=n) 

133 self[k] = t # replace _Rtuple with tuple instance 

134 return t 

135 

136 

137class _Rkey(int): 

138 '''(INTERNAL) For C{_Rcoeffs}, C{_Rdict} and C{_Rtuple} keys. 

139 ''' 

140 def __new__(cls, k): 

141 if not isinstance(k, int): 

142 raise _RsError(k=type(k)) 

143 if not 0 <= k <= 8: # 0.._MODS.auxilats.auxily.Aux.N + 2 

144 raise _RsError(k=k) 

145 return int.__new__(cls, k) 

146 

147 

148class _RsError(_AssertionError): 

149 '''(INTERNAL) For C{_Rcoeffs}, C{_Rdict or} C{_Rtuple} issues. 

150 ''' 

151 def __init__(self, *rs, **kwds_cause): # PYCHOK no cover 

152 if rs: 

153 if len(rs) > 1: 

154 t = _NL_(NN, *rs) 

155 t = NN(_QUOTE3_, t, _QUOTE3_) 

156 else: # single rs 

157 t = repr(rs[0]) 

158 kwds_cause.update(txt=t) 

159 _AssertionError.__init__(self, **kwds_cause) 

160 

161 

162class _Rtuple(list): # MUST be list, NOT tuple! 

163 '''(INTERNAL) I{Pseudo-tuple} of float rationals used in C{_Rdict}s. 

164 ''' 

165 Rdict = None # set by _Rdict.__init__ 

166 k_n_rs = None, 0, () 

167 

168 def __init__(self, k, n, *rs): 

169 '''New C{_Rtuple} with key C{k}, number of floats C{n} and with 

170 each C{rs} a C{str} of comma-separated rationals C{"p/q, ..."} 

171 where C{p} and C{q} are C{int} digits only. 

172 ''' 

173 try: 

174 if not isinstance(k, _Rkey): 

175 raise _RsError(k=type(k)) 

176 if not (isinstance(n, int) and n > 0): 

177 raise _RsError(n=type(n)) 

178 if not rs: 

179 raise _RsError(rs=MISSING) 

180 for t in rs: 

181 if not isinstance(t, str): 

182 raise _RsError(rs=type(t)) 

183 self.k_n_rs = k, n, rs 

184 except Exception as x: 

185 raise _RsError(*rs, k=k, n=n, cause=x) 

186 

187 def __getitem__(self, i): 

188 return self._tuple[i] 

189 

190 if _MODS.sys_version_info2 < (3, 0): 

191 def __getslice__(self, *i_j): # PYCHOK 3 args 

192 return self._tuple[slice(*i_j)] 

193 

194 def __iter__(self): 

195 return iter(self._tuple) 

196 

197 def __len__(self): 

198 return len(self._tuple) 

199 

200 @Property_RO 

201 def _tuple(self): 

202 # build the C{tuple} I{once} and replace 

203 # C{_Rdict[key]} item with the C{tuple} 

204 try: 

205 k, n, rs = self.k_n_rs # for except ... 

206 t = self.Rdict._floatuple(self) 

207# self[:] = t # MUST copy into self? 

208 except Exception as x: 

209 if len(rs) > 1 and _QUOTE3_ in str(x): 

210 rs = rs[0], _ELLIPSIS4_ 

211 raise _RsError(k=k, n=n, rs=rs, cause=x) 

212 del self.Rdict, self.k_n_rs # trash refs 

213 return t 

214 

215 def append(self, arg): 

216 raise _RsError(append=arg) 

217 

218 def extend(self, arg): 

219 raise _RsError(extend=arg) 

220 

221# **) MIT License 

222# 

223# Copyright (C) 2024-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

224# 

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

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

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

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

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

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

231# 

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

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

234# 

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

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

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

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

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

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

241# OTHER DEALINGS IN THE SOFTWARE.