1

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

3

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

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

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

7

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

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

10'''

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

12 from __future__ import division as _; del _ # PYCHOK semicolon

13

14 from pygeodesy.constants import _floats

15 from pygeodesy.errors import _AssertionError, _MODS

16 from 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

19 from 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

26 class _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

81 class _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

133 class _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

147 class _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-2024 -- 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.