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

76 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-01-06 12:20 -0500

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 _ # PYCHOK semicolon 

13 

14from pygeodesy.constants import _floats 

15from pygeodesy.errors import _AssertionError, _MODS 

16from pygeodesy.interns import NN, MISSING, _COMMA_, _duplicate_, _NL_, \ 

17 _QUOTE3_, _SLASH_, _ELLIPSIS4_ # PYCHOK used! 

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

19from pygeodesy.named import ADict, Property_RO 

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

21 

22__all__ = () 

23__version__ = '24.09.04' 

24 

25 

26class _Rcoeffs(ADict): 

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

28 ''' 

29 def __init__(self, ALorder, coeffs): 

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

31 ''' 

32 try: 

33 if not isinstance(coeffs, dict): 

34 raise _RdictError(coeffs=type(coeffs)) 

35 n = 0 

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

37 if not isinstance(d, _Rdict): 

38 raise _RdictError(k=k, d=type(d)) 

39 n += d.n 

40 

41 ADict.__init__(self, coeffs) 

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

43 except Exception as x: 

44 raise _RdictError(ALorder=ALorder, cause=x) 

45 

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

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

48 b = n = u = z = 0 

49 _zB = _MODS.internals._sizeof 

50 for R in self._Rtuples(): 

51 _, _, rs = R.k_n_rs 

52 b += _zB(rs) 

53 t = R._tuple 

54 z += _zB(t) # Float 

55 # assert R.Rdict is None 

56 n += len(t) 

57 u += sum(1 for f in t if f in _floats) 

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

59 

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

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

62 yield str(n), v 

63 

64 def _Rtuples(self): # PYCHOK no cover 

65 for d in self.values(): 

66 if isinstance(d, _Rdict): 

67 # yield from d.values() 

68 for R in d.values(): 

69 yield R 

70 

71 def _validate(self, aL, lenAux): 

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

73 a, n = self.ALorder, self.n # PYCHOK Adict! 

74# for R in self._Rtuples(): 

75# assert isinstance(R, _Rtuple) 

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

77 raise _RdictError(aL=aL, ALorder=a, lenAux=lenAux, n=n) 

78 return self 

79 

80 

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

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

83 ''' 

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

85 

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

87 '''New C{_Rdict}. 

88 ''' 

89 if not Rtuples: 

90 raise _RdictError(Rtuples=MISSING) 

91 

92 for R in Rtuples: 

93 if not isinstance(R, _Rtuple): 

94 raise _RdictError(R, R=type(R)) 

95 k, n, _ = R.k_n_rs 

96 if k in self: 

97 raise _RdictError(_duplicate_, k=k) 

98 R.Rdict = self 

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

100 self.n += n 

101 if self.n != nt: 

102 raise _RdictError(n=n, nt=nt) 

103 

104 def _floats(self, rs): 

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

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

107 return (NN if x else p), q 

108 

109 _get = _floats.get 

110 for r in NN(*rs.split()).split(_COMMA_): 

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

112 if p: 

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

114 if not isinstance(f, float): 

115 raise _RdictError(rs, f=f, p=p, q=q, r=r) 

116 yield _get(f, f) # from .constants? 

117 else: 

118 raise _RdictError(rs, r=r) 

119 

120 def _floatuple(self, Rtuple): 

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

122 k, n, rs = Rtuple.k_n_rs 

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

124 for f in m) # ... yield f 

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

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

127 if len(t) != n: 

128 raise _RdictError(*rs, len=len(t), n=n) 

129 self[k] = t 

130 return t 

131 

132 

133class _RdictError(_AssertionError): 

134 '''(INTERNAL) For C{_Rdict} issues. 

135 ''' 

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

137 if rs: 

138 if len(rs) > 1: 

139 t = _NL_(NN, *rs) 

140 t = NN(_QUOTE3_, t, _QUOTE3_) 

141 else: # single rs 

142 t = repr(rs[0]) 

143 kwds_cause.update(txt=t) 

144 _AssertionError.__init__(self, **kwds_cause) 

145 

146 

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

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

149 ''' 

150 Rdict = None 

151 k_n_rs = None, 0, () 

152 

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

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

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

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

157 ''' 

158 try: 

159 if not rs: 

160 raise _RdictError(rs=MISSING) 

161 for t in rs: 

162 if not isinstance(t, str): 

163 raise _RdictError(rs=type(t)) 

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

165 raise _RdictError(n=type(n)) 

166 self.k_n_rs = k, n, rs 

167 except Exception as x: 

168 raise _RdictError(*rs, k=k, n=n, cause=x) 

169 

170 def __getitem__(self, i): 

171 return self._tuple[i] 

172 

173 def __iter__(self): 

174 return iter(self._tuple) 

175 

176 def __len__(self): 

177 return len(self._tuple) 

178 

179 @Property_RO 

180 def _tuple(self): 

181 # build the C{tuple} once, replace C{_Rdict} 

182 # item at C{key} with the C{tuple} and fill 

183 # this C{_Rlist} with the C{tuple} values 

184 # for the initial __getitem__ retrieval[s] 

185 try: 

186 k, n, rs = self.k_n_rs 

187 t = self.Rdict._floatuple(self) 

188 self[:] = t # MUST copy into self! 

189 except Exception as x: 

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

191 rs = rs[0], _ELLIPSIS4_ 

192 raise _RdictError(k=k, n=n, rs=rs, cause=x) 

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

194 return t 

195 

196 def append(self, arg): 

197 raise _RdictError(append=arg) 

198 

199 def extend(self, arg): 

200 raise _RdictError(extend=arg) 

201 

202# **) MIT License 

203# 

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

205# 

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

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

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

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

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

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

212# 

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

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

215# 

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

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

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

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

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

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

222# OTHER DEALINGS IN THE SOFTWARE.