Coverage for pygeodesy/auxilats/auxDST.py: 97%

98 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-01-10 16:55 -0500

1 

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

3 

4u'''Discrete Sine Transforms (AuxDST) in Python, transcoded from I{Karney}'s C++ class 

5U{DST<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1DST.html>} 

6in I{GeographicLib version 2.2+}. 

7 

8Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2022-2024) and licensed 

9under the MIT/X11 License. For more information, see the U{GeographicLib 

10<https://GeographicLib.SourceForge.io>} documentation. 

11 

12@note: Class L{AuxDST} requires U{numpy<https://PyPI.org/project/numpy>} to be 

13 installed, version 1.16 or newer. 

14''' 

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

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

17 

18from pygeodesy.auxilats.auxily import _Dm 

19from pygeodesy.basics import isodd, neg, _reverange, _xnumpy 

20from pygeodesy.constants import PI_2, PI_4, isfinite, _0_0, _0_5, _naninf 

21from pygeodesy.fsums import Fsum as _Fsum 

22from pygeodesy.karney import _2cos2x, _ALL_DOCS 

23# from pygeodesy.lazily import _ALL_DOCS # from .karney 

24from pygeodesy.props import property_RO, property_ROver 

25 

26__all__ = () 

27__version__ = '24.08.13' 

28 

29 

30class AuxDST(object): 

31 '''Discrete Sine Transforms (DST) for I{Auxiliary} latitudes. 

32 

33 @see: I{Karney}'s C++ class U{DST 

34 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1DST.html>}. 

35 ''' 

36 _N = 0 

37 

38 def __init__(self, N): 

39 '''New L{AuxDST} instance. 

40 

41 @arg N: Size, number of points (C{int}). 

42 ''' 

43 if N > 0: 

44 self._N = int(N) 

45 # kissfft(N, False) # size, inverse 

46 

47 @staticmethod 

48 def evaluate(sinx, cosx, F, *N): 

49 '''Evaluate the Fourier sum given the sine and cosine of the angle, 

50 using precision I{Clenshaw} summation. 

51 

52 @arg sinx: The sin(I{sigma}) (C{float}). 

53 @arg cosx: The cos(I{sigma}) (C{float}). 

54 @arg F: The Fourier coefficients (C{float}[]). 

55 @arg N: Optional, (smaller) number of terms to evaluate (C{int}). 

56 

57 @return: Precison I{Clenshaw} sum (C{float}). 

58 

59 @see: Methods C{AuxDST.integral} and C{AuxDST.integral2}. 

60 ''' 

61 a = -_2cos2x(cosx, sinx) 

62 if isfinite(a): 

63 Y0, Y1 = _Fsum(), _Fsum() 

64 n = _len_N(F, *N) 

65 Fn = list(F[:n]) 

66 _F = Fn.pop 

67 if isodd(n): 

68 Y0 -= _F() 

69 while Fn: # Y0, Y1 negated 

70 Y1 -= Y0 * a + _F() 

71 Y0 -= Y1 * a + _F() 

72 r = float(_Dm(-Y0, Y1, sinx)) 

73 else: 

74 r = _naninf(-a) 

75 return r 

76 

77 @property_ROver 

78 def _fft_numpy(self): 

79 '''(INTERNAL) Get the C{numpy.fft} module, I{once}. 

80 ''' 

81 return _xnumpy(AuxDST, 1, 16).fft # overwrite property_ROver 

82 

83 def _fft_real(self, data): 

84 '''(INTERNAL) NumPy's I{kissfft}-like C{transform_real} function, 

85 taking C{float}[:N] B{C{data}} and returning C{complex}[:N*2]. 

86 ''' 

87 # <https://GitHub.com/mborgerding/kissfft/blob/master/test/testkiss.py> 

88 return self._fft_numpy.rfftn(data) 

89 

90 def _ffts(self, data, cIV): 

91 '''(INTERNAL) Compute the DST-III or DST-IV FFTransforms. 

92 

93 @arg data: Elements DST-III[0:N+1] or DST-IV[0:N] (C{float}[]) 

94 with DST_III[0] = 0. 

95 @arg cIV: If C{True}, DST-IV, otherwise DST-III. 

96 

97 @return: FFTransforms (C{float}[0:N]). 

98 ''' 

99 T, N = (), self.N 

100 if N > 0: 

101 N2 = N * 2 

102 d = tuple(data) 

103 # assert len(d) == N + (0 if cIV else 1) 

104 

105 if cIV: # DST-IV 

106 from cmath import exp as _cexp 

107 

108 def _cF(c, j, r=-PI_4 / N): 

109 return c * _cexp(complex(0, r * j)) 

110 

111 i = 0 

112 else: # DST-III 

113 i = 1 

114 # assert d[0] == _0_0 

115 

116 def _cF(c, *unused): # PYCHOK redef 

117 return c 

118 

119 d += tuple(reversed(d[i:N])) # i == len(d) - N 

120 d += tuple(map(neg, d[:N2])) 

121 c = self._fft_real(d) # complex[0:N*2] 

122 n2 = float(-N2) 

123 T = tuple(_cF(c[j], j).imag / n2 for j in range(1, N2, 2)) 

124 return T 

125 

126 def _ffts2(self, data, F): 

127 '''(INTERNAL) Doubled FFTransforms. 

128 

129 @arg data: Grid centers (C{float}[N]). 

130 @arg F: The transforms (C{float}[N]) 

131 

132 @return: Doubled FFTransforms (C{float}[N*2]). 

133 ''' 

134 __2 = _0_5 # N = self._N 

135 # copy DST-IV order N transform to D[0:N] 

136 D = self._ffts(data, True) 

137 # assert len(D) == N and len(F) >= N 

138 # (DST-IV order N - DST-III order N) / 2 

139 M = tuple((d - f) * __2 for d, f in zip(D, F)) # strict=False 

140 # (DST-IV order N + DST-III order N) / 2 

141 P = tuple((d + f) * __2 for d, f in zip(D, F)) # strict=False 

142 # assert len(M) == len(P) == self._N 

143 return P + tuple(reversed(M)) 

144 

145 @staticmethod 

146 def integral(sinx, cosx, F, *N): 

147 '''Evaluate the integral of Fourier sum given the sine and 

148 cosine of the angle, using precision I{Clenshaw} summation. 

149 

150 @arg sinx: The sin(I{sigma}) (C{float}). 

151 @arg cosx: The cos(I{sigma}) (C{float}). 

152 @arg F: The Fourier coefficients (C{float}[]). 

153 @arg N: Optional, C{len(B{F})} or a (smaller) number of 

154 terms to evaluate (C{int}). 

155 

156 @return: Precison I{Clenshaw} intergral (C{float}). 

157 

158 @see: Methods C{AuxDST.evaluate} and C{AuxDST.integral2}. 

159 ''' 

160 a = _2cos2x(cosx - sinx, cosx + sinx) 

161 if isfinite(a): 

162 Y0, Y1 = _Fsum(), _Fsum() 

163 for r in _reverscaled(F, *N): 

164 Y1 -= Y0 * a + r 

165 Y1, Y0 = Y0, -Y1 

166 r = float(_Dm(Y1, Y0, cosx)) 

167 else: 

168 r = _naninf(a) 

169 return r 

170 

171 @staticmethod 

172 def integral2(sinx, cosx, siny, cosy, F, *N): # PYCHOK no cover 

173 '''Compute the definite integral of Fourier sum given the 

174 sine and cosine of the angles at the end points, using 

175 precision I{Clenshaw} summation. 

176 

177 @arg sinx: The sin(I{sigma1}) (C{float}). 

178 @arg cosx: The cos(I{sigma1}) (C{float}). 

179 @arg siny: The sin(I{sigma2}) (C{float}). 

180 @arg cosy: The cos(I{sigma2}) (C{float}). 

181 @arg F: The Fourier coefficients (C{float}[]). 

182 @arg N: Optional, C{len(B{F})} or a (smaller) number of 

183 terms to evaluate (C{int}). 

184 

185 @return: Precison I{Clenshaw} integral (C{float}). 

186 

187 @see: Methods C{AuxDST.evaluate} and C{AuxDST.integral}. 

188 ''' 

189 # 2 * cos(y - x) * cos(y + x) -> 2 * cos(2 * x) 

190 c = _2cos2x(cosy * cosx, siny * sinx) 

191 # -2 * sin(y - x) * sin(y + x) -> 0 

192 s = -_2cos2x(siny * cosx, cosy * sinx) 

193 if isfinite(c) and isfinite(s): 

194 Y0, Y1 = _Fsum(), _Fsum() 

195 Z0, Z1 = _Fsum(), _Fsum() 

196 for r in _reverscaled(F, *N): 

197 Y1 -= Y0 * c + Z0 * s + r 

198 Z1 -= Y0 * s + Z0 * c 

199 Y1, Y0 = Y0, -Y1 

200 Z1, Z0 = Z0, -Z1 

201 r = float(_Dm(Y1, Y0, cosy - cosx) + 

202 _Dm(Z1, Z0, cosy + cosx)) 

203 else: 

204 r = _naninf(c, s) 

205 return r 

206 

207 @property_RO 

208 def N(self): 

209 '''Get this DST's size, number of points (C{int}). 

210 ''' 

211 return self._N 

212 

213 def refine(self, f, F, *sentinel): 

214 '''Refine the Fourier series by doubling the sampled points. 

215 

216 @arg f: Single-argument callable (C{B{f}(sigma)}). 

217 @arg F: Initial Fourier series coefficients (C{float}[:N]). 

218 @arg sentinel: Optional coefficient(s) to append (C{float}(s)). 

219 

220 @return: Fourier series coefficients (C{float}[:N*2]). 

221 

222 @note: Any initial C{B{F}[N:]} sentinel coefficients are ignored. 

223 ''' 

224 def _data(_f, N): # [:N] 

225 if N > 0: 

226 r = PI_4 / N 

227 for j in range(1, N*2, 2): 

228 yield _f(r * j) 

229 

230 # F = F[:self.N] handled by zip strict=False in ._ffts2 above 

231 return self._ffts2(_data(f, self.N), F) + sentinel 

232 

233 def reset(self, N): 

234 '''Reset this DST. 

235 

236 @arg N: Size, number of points (C{int}). 

237 

238 @return: The new size (C{int}, non-negative). 

239 ''' 

240 self._N = N = max(0, N) 

241 # kissfft.assign(N*2, False) # "reset" size, inverse 

242 return N 

243 

244 def transform(self, f): 

245 '''Determine C{[N + 1]} terms in the Fourier series. 

246 

247 @arg f: Single-argument callable (C{B{f}(sigma)}). 

248 

249 @return: Fourier series coefficients (C{float}[:N+1], 

250 leading 0). 

251 ''' 

252 def _data(_f, N): # [:N + 1] 

253 yield _0_0 # data[0] = 0 

254 if N > 0: 

255 r = PI_2 / N 

256 for i in range(1, N + 1): 

257 yield _f(r * i) 

258 

259 return self._ffts(_data(f, self.N), False) 

260 

261 

262def _len_N(F, *N): 

263 # Adjusted C{len(B{F})}. 

264 return min(len(F), *N) if N else len(F) 

265 

266 

267def _reverscaled(F, *N): 

268 # Yield F[:N], reversed and scaled 

269 for n in _reverange(_len_N(F, *N)): 

270 yield F[n] / float(n * 2 + 1) 

271 

272 

273__all__ += _ALL_DOCS(AuxDST) 

274 

275# **) MIT License 

276# 

277# Copyright (C) 2023-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

278# 

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

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

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

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

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

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

285# 

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

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

288# 

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

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

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

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

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

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

295# OTHER DEALINGS IN THE SOFTWARE.