Coverage for pygeodesy/auxilats/_CX_Rs.py: 96%
84 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-04 12:01 -0400
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-04 12:01 -0400
2# -*- coding: utf-8 -*-
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>}.
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
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, _COMMA_, _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
24__all__ = ()
25__version__ = '25.04.11'
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
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)
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
64 def items(self): # string-ify keys # PYCHOK no cover
65 for n, v in ADict.items(self):
66 yield str(n), v
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
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
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)
90 def __init__(self, nt, *Rtuples):
91 '''New C{_Rdict}.
92 '''
93 if not Rtuples:
94 raise _RsError(Rtuples=MISSING)
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)
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
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)
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
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)
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)
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, ()
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)
187 def __getitem__(self, i):
188 return self._tuple[i]
190 if _MODS.sys_version_info2 < (3, 0):
191 def __getslice__(self, *i_j): # PYCHOK 3 args
192 return self._tuple[slice(*i_j)]
194 def __iter__(self):
195 return iter(self._tuple)
197 def __len__(self):
198 return len(self._tuple)
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
215 def append(self, arg):
216 raise _RsError(append=arg)
218 def extend(self, arg):
219 raise _RsError(extend=arg)
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.