Coverage for pygeodesy/auxilats/_CX_Rs.py: 99%
76 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-10 16:55 -0500
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-10 16:55 -0500
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
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
22__all__ = ()
23__version__ = '24.09.04'
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
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)
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
60 def items(self): # string-ify keys # PYCHOK no cover
61 for n, v in ADict.items(self):
62 yield str(n), v
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
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
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)
86 def __init__(self, nt, *Rtuples):
87 '''New C{_Rdict}.
88 '''
89 if not Rtuples:
90 raise _RdictError(Rtuples=MISSING)
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)
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
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)
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
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)
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, ()
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)
170 def __getitem__(self, i):
171 return self._tuple[i]
173 def __iter__(self):
174 return iter(self._tuple)
176 def __len__(self):
177 return len(self._tuple)
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
196 def append(self, arg):
197 raise _RdictError(append=arg)
199 def extend(self, arg):
200 raise _RdictError(extend=arg)
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.