pyNFFT3 1.1.0__py3-none-win_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyNFFT3/FSFT.py +347 -0
- pyNFFT3/NFCT.py +343 -0
- pyNFFT3/NFFT.py +354 -0
- pyNFFT3/NFMT.py +511 -0
- pyNFFT3/NFSFT.py +348 -0
- pyNFFT3/NFST.py +341 -0
- pyNFFT3/__init__.py +219 -0
- pyNFFT3/fastsum.py +348 -0
- pyNFFT3/flags.py +395 -0
- pyNFFT3/lib/ARM64/libfastsumjulia.dll +0 -0
- pyNFFT3/lib/ARM64/libnfctjulia.dll +0 -0
- pyNFFT3/lib/ARM64/libnfftjulia.dll +0 -0
- pyNFFT3/lib/ARM64/libnfsftjulia.dll +0 -0
- pyNFFT3/lib/ARM64/libnfstjulia.dll +0 -0
- pynfft3-1.1.0.dist-info/METADATA +56 -0
- pynfft3-1.1.0.dist-info/RECORD +18 -0
- pynfft3-1.1.0.dist-info/WHEEL +4 -0
- pynfft3-1.1.0.dist-info/licenses/LICENSE +674 -0
pyNFFT3/FSFT.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from . import _nfsftlib, fsft_plan
|
|
6
|
+
from .flags import *
|
|
7
|
+
|
|
8
|
+
# Set arugment and return types for functions
|
|
9
|
+
_nfsftlib.jnfsft_init.argtypes = [
|
|
10
|
+
ctypes.POINTER(fsft_plan),
|
|
11
|
+
ctypes.c_int32,
|
|
12
|
+
ctypes.c_int32,
|
|
13
|
+
ctypes.c_uint32,
|
|
14
|
+
ctypes.c_uint32,
|
|
15
|
+
ctypes.c_int32,
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
_nfsftlib.jnfsft_alloc.restype = ctypes.POINTER(fsft_plan)
|
|
19
|
+
_nfsftlib.jnfsft_finalize.argtypes = (ctypes.POINTER(fsft_plan),)
|
|
20
|
+
|
|
21
|
+
_nfsftlib.jnfsft_set_x.argtypes = [
|
|
22
|
+
ctypes.POINTER(fsft_plan),
|
|
23
|
+
np.ctypeslib.ndpointer(np.float64, flags="C"),
|
|
24
|
+
]
|
|
25
|
+
_nfsftlib.jnfsft_set_x.restype = ctypes.POINTER(ctypes.c_double)
|
|
26
|
+
_nfsftlib.jnfsft_set_f.argtypes = [
|
|
27
|
+
ctypes.POINTER(fsft_plan),
|
|
28
|
+
np.ctypeslib.ndpointer(np.complex128, flags="C"),
|
|
29
|
+
]
|
|
30
|
+
_nfsftlib.jnfsft_set_f.restype = ctypes.POINTER(ctypes.c_double)
|
|
31
|
+
_nfsftlib.jnfsft_set_fhat.argtypes = [
|
|
32
|
+
ctypes.POINTER(fsft_plan),
|
|
33
|
+
np.ctypeslib.ndpointer(np.complex128, flags="C"),
|
|
34
|
+
]
|
|
35
|
+
_nfsftlib.jnfsft_set_fhat.restype = ctypes.POINTER(ctypes.c_double)
|
|
36
|
+
|
|
37
|
+
_nfsftlib.jnfsft_trafo.argtypes = [ctypes.POINTER(fsft_plan)]
|
|
38
|
+
_nfsftlib.jnfsft_trafo.restype = ctypes.POINTER(ctypes.c_double)
|
|
39
|
+
_nfsftlib.jnfsft_adjoint.argtypes = [ctypes.POINTER(fsft_plan)]
|
|
40
|
+
_nfsftlib.jnfsft_adjoint.restype = ctypes.POINTER(ctypes.c_double)
|
|
41
|
+
_nfsftlib.jnfsft_trafo_direct.argtypes = [ctypes.POINTER(fsft_plan)]
|
|
42
|
+
_nfsftlib.jnfsft_trafo_direct.restype = ctypes.POINTER(ctypes.c_double)
|
|
43
|
+
_nfsftlib.jnfsft_adjoint_direct.argtypes = [ctypes.POINTER(fsft_plan)]
|
|
44
|
+
_nfsftlib.jnfsft_adjoint_direct.restype = ctypes.POINTER(ctypes.c_double)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FSFT:
|
|
48
|
+
"""
|
|
49
|
+
Class to perform fast spherical Fourier transforms (FSFT).
|
|
50
|
+
Just **N** and **M** are required for initializing a plan.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
N: int,
|
|
56
|
+
M: int,
|
|
57
|
+
flags: ctypes.c_uint32 = nfsft_default | NFSFT_EQUISPACED,
|
|
58
|
+
nfft_flags: ctypes.c_uint32 = nfsft_nfft_default,
|
|
59
|
+
nfft_cutoff: int = nfsft_default_nfft_cut_off,
|
|
60
|
+
):
|
|
61
|
+
self.plan = None
|
|
62
|
+
self.N = N # bandwidth
|
|
63
|
+
self.flags = flags
|
|
64
|
+
self.nfft_flags = nfft_flags
|
|
65
|
+
self.nfft_cutoff = nfft_cutoff
|
|
66
|
+
self.init_done = False # bool for plan init
|
|
67
|
+
self.finalized = False # bool for finalizer
|
|
68
|
+
# self.x = None # nodes, will be set later
|
|
69
|
+
self._f = None # function coefficients
|
|
70
|
+
self._fhat = None # spherical Fourier coefficients
|
|
71
|
+
self._fhat_corrupted_by_fast_trafo = False
|
|
72
|
+
|
|
73
|
+
if N <= 0:
|
|
74
|
+
raise ValueError(f"Invalid N: {N}. Argument must be a positive integer")
|
|
75
|
+
|
|
76
|
+
if M <= 0:
|
|
77
|
+
raise ValueError(f"Invalid M: {M}. Argument must be a positive integer")
|
|
78
|
+
|
|
79
|
+
if (self.flags & NFSFT_EQUISPACED) == 0:
|
|
80
|
+
raise ValueError("Must use NFSFT_EQUISPACED flag. Use NFSFT instead")
|
|
81
|
+
|
|
82
|
+
# The underlying C library ignores the M passed to its init function
|
|
83
|
+
# for equispaced plans and always allocates/uses the fixed grid size
|
|
84
|
+
# (2*N+2)*(N+2) instead (see nfsft_init_guru() in
|
|
85
|
+
# kernel/nfsft/nfsft.c). Mirror that here so the Python-side f/fhat
|
|
86
|
+
# buffer bookkeeping (which used to trust the caller-supplied M)
|
|
87
|
+
# matches the C library's real buffer size - using a smaller M here
|
|
88
|
+
# used to silently read/write past the actual buffer, leaving most of
|
|
89
|
+
# it uninitialized and producing NaN from fsft_adjoint().
|
|
90
|
+
self.M = (2 * N + 2) * (N + 2) # number of equispaced grid nodes
|
|
91
|
+
|
|
92
|
+
def __del__(self):
|
|
93
|
+
self.finalize_plan()
|
|
94
|
+
|
|
95
|
+
def fsft_finalize_plan(self):
|
|
96
|
+
"""
|
|
97
|
+
Finalizes an FSFT plan.
|
|
98
|
+
This function does not have to be called by the user.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
if not self.init_done:
|
|
102
|
+
raise ValueError("FSFT not initialized.")
|
|
103
|
+
|
|
104
|
+
if not self.finalized:
|
|
105
|
+
self.finalized = True
|
|
106
|
+
_nfsftlib.jnfsft_finalize(self.plan)
|
|
107
|
+
|
|
108
|
+
def finalize_plan(self):
|
|
109
|
+
"""
|
|
110
|
+
Alternate call for **fsft_finalize_plan()**
|
|
111
|
+
"""
|
|
112
|
+
return self.fsft_finalize_plan()
|
|
113
|
+
|
|
114
|
+
def fsft_init(self):
|
|
115
|
+
"""
|
|
116
|
+
Initializes the FSFT plan in C.
|
|
117
|
+
This function does not have to be called by the user.
|
|
118
|
+
"""
|
|
119
|
+
# Call init for memory allocation
|
|
120
|
+
ptr = _nfsftlib.jnfsft_alloc()
|
|
121
|
+
|
|
122
|
+
# Set the pointer
|
|
123
|
+
self.plan = ctypes.cast(ptr, ctypes.POINTER(fsft_plan))
|
|
124
|
+
|
|
125
|
+
# Initialize values
|
|
126
|
+
_nfsftlib.jnfsft_init(
|
|
127
|
+
self.plan,
|
|
128
|
+
ctypes.c_int(self.N),
|
|
129
|
+
ctypes.c_int(self.M),
|
|
130
|
+
self.flags,
|
|
131
|
+
self.nfft_flags,
|
|
132
|
+
ctypes.c_int(self.nfft_cutoff),
|
|
133
|
+
)
|
|
134
|
+
self.init_done = True
|
|
135
|
+
|
|
136
|
+
# nfsft_trafo_direct()/nfsft_adjoint_direct() (shared with NFSFT)
|
|
137
|
+
# unconditionally read plan->x for node positions, including when
|
|
138
|
+
# called internally by trafo()/adjoint() for N < NFSFT_BREAK_EVEN -
|
|
139
|
+
# but FSFT has no x setter of its own (the whole point of
|
|
140
|
+
# NFSFT_EQUISPACED is that the grid is implicit in N), and
|
|
141
|
+
# empirically plan->x is left uninitialized rather than auto-filled
|
|
142
|
+
# with the equispaced grid as kernel/nfsft/nfsft.c's
|
|
143
|
+
# nfsft_init_guru() comments suggest it should be - this produced
|
|
144
|
+
# NaN/garbage results that varied by process/heap state (see the
|
|
145
|
+
# FSFT_test.py fix this was found in). Set the equispaced grid
|
|
146
|
+
# ourselves via jnfsft_set_x, mirroring that same C code:
|
|
147
|
+
# x[2*(i*(N+2)+j)] = (i-N-1)/(2N+2) for i in 0..2N+1
|
|
148
|
+
# x[2*(i*(N+2)+j)+1] = j/(2N+2) for j in 0..N+1
|
|
149
|
+
# jnfsft_set_x additionally expects its input in "raw angle" units
|
|
150
|
+
# (it divides by 2*pi itself, with a wraparound for the first
|
|
151
|
+
# component) rather than plan->x's own normalized units directly, so
|
|
152
|
+
# the values below are first scaled back up by 2*pi to compensate.
|
|
153
|
+
i = np.arange(2 * self.N + 2, dtype=np.float64)
|
|
154
|
+
j = np.arange(self.N + 2, dtype=np.float64)
|
|
155
|
+
I, J = np.meshgrid(i, j, indexing="ij") # I, J both shape (2N+2, N+2)
|
|
156
|
+
phi = (I - self.N - 1) / (2.0 * self.N + 2)
|
|
157
|
+
theta = J / (2.0 * self.N + 2)
|
|
158
|
+
grid = np.empty(self.M * 2, dtype=np.float64)
|
|
159
|
+
grid[0::2] = (phi * 2.0 * np.pi).ravel()
|
|
160
|
+
grid[1::2] = (theta * 2.0 * np.pi).ravel()
|
|
161
|
+
_nfsftlib.jnfsft_set_x(self.plan, grid)
|
|
162
|
+
|
|
163
|
+
def init(self):
|
|
164
|
+
"""
|
|
165
|
+
Alternate call for **fsft_init()**
|
|
166
|
+
"""
|
|
167
|
+
return self.fsft_init()
|
|
168
|
+
|
|
169
|
+
# @property
|
|
170
|
+
# def x(self) -> np.ndarray:
|
|
171
|
+
# return self._X
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def f(self) -> np.ndarray:
|
|
175
|
+
return self._f
|
|
176
|
+
|
|
177
|
+
@f.setter
|
|
178
|
+
def f(self, value: np.ndarray):
|
|
179
|
+
if value is not None:
|
|
180
|
+
if not self.init_done:
|
|
181
|
+
self.fsft_init()
|
|
182
|
+
if self.finalized:
|
|
183
|
+
raise RuntimeError("Plan already finalized")
|
|
184
|
+
if not (
|
|
185
|
+
isinstance(value, np.ndarray)
|
|
186
|
+
and value.dtype == np.complex128
|
|
187
|
+
and value.flags["C"]
|
|
188
|
+
and value.size == self.M
|
|
189
|
+
):
|
|
190
|
+
raise RuntimeError(
|
|
191
|
+
f"f has to be C-continuous, numpy complex128 array of size M ({self.M})"
|
|
192
|
+
)
|
|
193
|
+
self._f = np.ctypeslib.as_array(
|
|
194
|
+
_nfsftlib.jnfsft_set_f(self.plan, value), shape=(self.M * 2,)
|
|
195
|
+
).view(np.complex128)
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def fhat(self) -> np.ndarray:
|
|
199
|
+
return self._fhat
|
|
200
|
+
|
|
201
|
+
@fhat.setter
|
|
202
|
+
def fhat(self, value: np.ndarray):
|
|
203
|
+
if value is not None:
|
|
204
|
+
N_total = (2 * self.N + 2) ** 2
|
|
205
|
+
if not self.init_done:
|
|
206
|
+
self.fsft_init()
|
|
207
|
+
if self.finalized:
|
|
208
|
+
raise RuntimeError("Plan already finalized")
|
|
209
|
+
if not (
|
|
210
|
+
isinstance(value, np.ndarray)
|
|
211
|
+
and value.dtype == np.complex128
|
|
212
|
+
and value.flags["C"]
|
|
213
|
+
):
|
|
214
|
+
raise RuntimeError(
|
|
215
|
+
"fhat has to be C-continuous, numpy complex128 array"
|
|
216
|
+
)
|
|
217
|
+
if value.shape != (N_total,):
|
|
218
|
+
raise RuntimeError(f"fhat must be of size N_total ({N_total})")
|
|
219
|
+
self._fhat = np.ctypeslib.as_array(
|
|
220
|
+
_nfsftlib.jnfsft_set_fhat(self.plan, value), shape=(N_total * 2,)
|
|
221
|
+
).view(np.complex128)
|
|
222
|
+
self._fhat_corrupted_by_fast_trafo = False
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def num_threads(self) -> int:
|
|
226
|
+
return _nfsftlib.nfft_get_num_threads()
|
|
227
|
+
|
|
228
|
+
def fsft_index(self, k: int, n: int) -> int:
|
|
229
|
+
"""
|
|
230
|
+
Calculates the index for f_hat calculations.
|
|
231
|
+
This function does not have to be called by the user.
|
|
232
|
+
"""
|
|
233
|
+
return (2 * self.N + 2) * (self.N - n + 1) + (self.N + k + 1)
|
|
234
|
+
|
|
235
|
+
def fsft_trafo(self):
|
|
236
|
+
"""
|
|
237
|
+
Computes the FSFT using the fast approximate transform for the provided nodes in **x** and coefficients in **fhat**.
|
|
238
|
+
"""
|
|
239
|
+
# Prevent bad stuff from happening
|
|
240
|
+
if self.finalized:
|
|
241
|
+
raise RuntimeError("FSFT already finalized")
|
|
242
|
+
|
|
243
|
+
if not hasattr(self, "_fhat"):
|
|
244
|
+
raise ValueError("fhat has not been set.")
|
|
245
|
+
self._f = (
|
|
246
|
+
np.ctypeslib.as_array(
|
|
247
|
+
_nfsftlib.jnfsft_trafo(self.plan), shape=(self.M * 2,)
|
|
248
|
+
)
|
|
249
|
+
.view(np.complex128)
|
|
250
|
+
.copy()
|
|
251
|
+
)
|
|
252
|
+
# Same underlying nfsft_trafo() as NFSFT: without NFSFT_PRESERVE_F_HAT
|
|
253
|
+
# it overwrites fhat in place with internal FPT/FFT scratch data.
|
|
254
|
+
if not (self.flags & NFSFT_PRESERVE_F_HAT):
|
|
255
|
+
self._fhat_corrupted_by_fast_trafo = True
|
|
256
|
+
|
|
257
|
+
def trafo(self):
|
|
258
|
+
"""
|
|
259
|
+
Alternative call for **fsft_trafo()**
|
|
260
|
+
"""
|
|
261
|
+
return self.fsft_trafo()
|
|
262
|
+
|
|
263
|
+
def fsft_trafo_direct(self):
|
|
264
|
+
"""
|
|
265
|
+
Computes the NDSFT using direct transformation for the provided nodes in **x** and coefficients in **fhat**.
|
|
266
|
+
"""
|
|
267
|
+
# Prevent bad stuff from happening
|
|
268
|
+
if self.finalized:
|
|
269
|
+
raise RuntimeError("FSFT already finalized")
|
|
270
|
+
|
|
271
|
+
if getattr(self, "_fhat_corrupted_by_fast_trafo", False):
|
|
272
|
+
raise RuntimeError(
|
|
273
|
+
"fsft_trafo_direct() cannot be trusted on this plan: "
|
|
274
|
+
"fsft_trafo() already overwrote fhat in place with internal "
|
|
275
|
+
"scratch data (expected behaviour of the underlying library "
|
|
276
|
+
"without the NFSFT_PRESERVE_F_HAT flag - see simpleTest/"
|
|
277
|
+
"example_nfsft.py). Set fhat again, pass NFSFT_PRESERVE_F_HAT "
|
|
278
|
+
"explicitly, or create a new FSFT plan."
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if self._fhat is None:
|
|
282
|
+
raise ValueError("fhat has not been set.")
|
|
283
|
+
self._f = (
|
|
284
|
+
np.ctypeslib.as_array(
|
|
285
|
+
_nfsftlib.jnfsft_trafo_direct(self.plan), shape=(self.M * 2,)
|
|
286
|
+
)
|
|
287
|
+
.view(np.complex128)
|
|
288
|
+
.copy()
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def trafo_direct(self):
|
|
292
|
+
"""
|
|
293
|
+
Alternative call for **fsft_trafo_direct()**
|
|
294
|
+
"""
|
|
295
|
+
return self.fsft_trafo_direct()
|
|
296
|
+
|
|
297
|
+
def fsft_adjoint(self):
|
|
298
|
+
"""
|
|
299
|
+
Computes the adjoint FSFT using the fast approximate transform for the provided nodes in **x** and coefficients in **f**.
|
|
300
|
+
"""
|
|
301
|
+
N_total = (2 * self.N + 2) ** 2
|
|
302
|
+
# Prevent bad stuff from happening
|
|
303
|
+
if self.finalized:
|
|
304
|
+
raise RuntimeError("FSFT already finalized")
|
|
305
|
+
|
|
306
|
+
if not hasattr(self, "_f"):
|
|
307
|
+
raise ValueError("f has not been set.")
|
|
308
|
+
self._fhat = (
|
|
309
|
+
np.ctypeslib.as_array(
|
|
310
|
+
_nfsftlib.jnfsft_adjoint(self.plan), shape=(N_total * 2,)
|
|
311
|
+
)
|
|
312
|
+
.view(np.complex128)
|
|
313
|
+
.copy()
|
|
314
|
+
)
|
|
315
|
+
self._fhat_corrupted_by_fast_trafo = False
|
|
316
|
+
|
|
317
|
+
def adjoint(self):
|
|
318
|
+
"""
|
|
319
|
+
Alternative call for **fsft_adjoint()**
|
|
320
|
+
"""
|
|
321
|
+
return self.fsft_adjoint()
|
|
322
|
+
|
|
323
|
+
def fsft_adjoint_direct(self):
|
|
324
|
+
"""
|
|
325
|
+
Computes the adjoint NDFST using direct transformation for the provided nodes in **x** and coefficients in **f**.
|
|
326
|
+
"""
|
|
327
|
+
N_total = (2 * self.N + 2) ** 2
|
|
328
|
+
# Prevent bad stuff from happening
|
|
329
|
+
if self.finalized:
|
|
330
|
+
raise RuntimeError("FSFT already finalized")
|
|
331
|
+
|
|
332
|
+
if not hasattr(self, "_f"):
|
|
333
|
+
raise ValueError("f has not been set.")
|
|
334
|
+
self._fhat = (
|
|
335
|
+
np.ctypeslib.as_array(
|
|
336
|
+
_nfsftlib.jnfsft_adjoint_direct(self.plan), shape=(N_total * 2,)
|
|
337
|
+
)
|
|
338
|
+
.view(np.complex128)
|
|
339
|
+
.copy()
|
|
340
|
+
)
|
|
341
|
+
self._fhat_corrupted_by_fast_trafo = False
|
|
342
|
+
|
|
343
|
+
def adjoint_direct(self):
|
|
344
|
+
"""
|
|
345
|
+
Alternative call for **fsft_adjoint_direct()**
|
|
346
|
+
"""
|
|
347
|
+
return self.fsft_adjoint_direct()
|
pyNFFT3/NFCT.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from . import _nfctlib, nfct_plan
|
|
6
|
+
from .flags import *
|
|
7
|
+
|
|
8
|
+
# Set arugment and return types for functions
|
|
9
|
+
_nfctlib.jnfct_init.argtypes = [
|
|
10
|
+
ctypes.POINTER(nfct_plan),
|
|
11
|
+
ctypes.c_int32,
|
|
12
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
13
|
+
ctypes.c_int32,
|
|
14
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
15
|
+
ctypes.c_int32,
|
|
16
|
+
ctypes.c_uint32,
|
|
17
|
+
ctypes.c_uint32,
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
_nfctlib.jnfct_alloc.restype = ctypes.POINTER(nfct_plan)
|
|
21
|
+
_nfctlib.jnfct_finalize.argtypes = (ctypes.POINTER(nfct_plan),)
|
|
22
|
+
|
|
23
|
+
_nfctlib.jnfct_set_x.argtypes = [
|
|
24
|
+
ctypes.POINTER(nfct_plan),
|
|
25
|
+
np.ctypeslib.ndpointer(np.float64, flags="C"),
|
|
26
|
+
]
|
|
27
|
+
_nfctlib.jnfct_set_x.restype = ctypes.POINTER(ctypes.c_double)
|
|
28
|
+
_nfctlib.jnfct_set_f.argtypes = [
|
|
29
|
+
ctypes.POINTER(nfct_plan),
|
|
30
|
+
np.ctypeslib.ndpointer(np.float64, ndim=1, flags="C"),
|
|
31
|
+
]
|
|
32
|
+
_nfctlib.jnfct_set_f.restype = ctypes.POINTER(ctypes.c_double)
|
|
33
|
+
_nfctlib.jnfct_set_fhat.argtypes = [
|
|
34
|
+
ctypes.POINTER(nfct_plan),
|
|
35
|
+
np.ctypeslib.ndpointer(np.float64, ndim=1, flags="C"),
|
|
36
|
+
]
|
|
37
|
+
_nfctlib.jnfct_set_fhat.restype = ctypes.POINTER(ctypes.c_double)
|
|
38
|
+
|
|
39
|
+
_nfctlib.jnfct_trafo.argtypes = [ctypes.POINTER(nfct_plan)]
|
|
40
|
+
_nfctlib.jnfct_trafo.restype = ctypes.POINTER(ctypes.c_double)
|
|
41
|
+
_nfctlib.jnfct_adjoint.argtypes = [ctypes.POINTER(nfct_plan)]
|
|
42
|
+
_nfctlib.jnfct_adjoint.restype = ctypes.POINTER(ctypes.c_double)
|
|
43
|
+
_nfctlib.jnfct_trafo_direct.argtypes = [ctypes.POINTER(nfct_plan)]
|
|
44
|
+
_nfctlib.jnfct_trafo_direct.restype = ctypes.POINTER(ctypes.c_double)
|
|
45
|
+
_nfctlib.jnfct_adjoint_direct.argtypes = [ctypes.POINTER(nfct_plan)]
|
|
46
|
+
_nfctlib.jnfct_adjoint_direct.restype = ctypes.POINTER(ctypes.c_double)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class NFCT:
|
|
50
|
+
"""
|
|
51
|
+
Class to perform non-equispaced fast cosine transforms (NFCT)
|
|
52
|
+
with a dimension of **D**.
|
|
53
|
+
Just **N** and **M** are required for initializing a plan.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
N: np.ndarray,
|
|
59
|
+
M: int,
|
|
60
|
+
n: np.ndarray = None,
|
|
61
|
+
m: int = default_window_cut_off,
|
|
62
|
+
f1: ctypes.c_uint32 = None,
|
|
63
|
+
f2: ctypes.c_uint32 = f2_default,
|
|
64
|
+
):
|
|
65
|
+
self.plan = None
|
|
66
|
+
self.N = N # bandwidth tuple
|
|
67
|
+
self.M = M # number of nodes
|
|
68
|
+
self.n = n # oversampling per dimension
|
|
69
|
+
self.m = m # window size
|
|
70
|
+
self.D = len(N) # dimensions
|
|
71
|
+
|
|
72
|
+
if any(x <= 0 for x in N):
|
|
73
|
+
raise ValueError(f"Invalid N: {N}. Argument must be a positive integer")
|
|
74
|
+
|
|
75
|
+
if sum(x % 2 for x in N) != 0:
|
|
76
|
+
raise ValueError(f"Invalid N: {N}. Argument must be an even integer")
|
|
77
|
+
|
|
78
|
+
if M <= 0:
|
|
79
|
+
raise ValueError(f"Invalid M: {M}. Argument must be a positive integer")
|
|
80
|
+
|
|
81
|
+
if n is None:
|
|
82
|
+
self.n = (2 ** (np.ceil(np.log(self.N) / np.log(2)) + 1)).astype("int32")
|
|
83
|
+
|
|
84
|
+
if any(x <= 0 for x in self.n):
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"Invalid n: {self.n}. Argument must be a positive integer"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if any(x <= y for x, y in zip(self.n, N)):
|
|
90
|
+
raise ValueError(f"Invalid n: {self.n}. Argument must fulfil n_i > N_i")
|
|
91
|
+
|
|
92
|
+
if sum(x % 2 for x in self.n) != 0:
|
|
93
|
+
raise ValueError(f"Invalid n: {self.n}. Argument must be an even integer")
|
|
94
|
+
|
|
95
|
+
if m <= 0:
|
|
96
|
+
raise ValueError(f"Invalid m: {m}. Argument must be a positive integer")
|
|
97
|
+
|
|
98
|
+
if f1 is None:
|
|
99
|
+
self.f1 = f1_default if self.D > 1 else f1_default_1d
|
|
100
|
+
else:
|
|
101
|
+
self.f1 = f1
|
|
102
|
+
|
|
103
|
+
self.f2 = f2 # FFTW flags
|
|
104
|
+
self.init_done = False # bool for plan init
|
|
105
|
+
self.finalized = False # bool for finalizer
|
|
106
|
+
self._X = None # nodes, will be set later
|
|
107
|
+
self._f = None # function values
|
|
108
|
+
self._fhat = None # Fourier coefficients
|
|
109
|
+
|
|
110
|
+
def __del__(self):
|
|
111
|
+
self.finalize_plan()
|
|
112
|
+
|
|
113
|
+
def nfct_finalize_plan(self):
|
|
114
|
+
"""
|
|
115
|
+
Finalizes an NFCT plan.
|
|
116
|
+
This function does not have to be called by the user.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
if not self.init_done:
|
|
120
|
+
raise ValueError("NFST not initialized.")
|
|
121
|
+
|
|
122
|
+
if not self.finalized:
|
|
123
|
+
_nfctlib.jnfct_finalize(self.plan)
|
|
124
|
+
self.finalized = True
|
|
125
|
+
|
|
126
|
+
def finalize_plan(self):
|
|
127
|
+
"""
|
|
128
|
+
Alternative call for **nfct_finalize_plan()**
|
|
129
|
+
"""
|
|
130
|
+
return self.nfct_finalize_plan()
|
|
131
|
+
|
|
132
|
+
def nfct_init(self):
|
|
133
|
+
"""
|
|
134
|
+
Initializes the NFCT plan in C.
|
|
135
|
+
This function does not have to be called by the user.
|
|
136
|
+
"""
|
|
137
|
+
# Convert N and n to numpy arrays for passing them to C
|
|
138
|
+
Nv = np.array(self.N, dtype=np.int32)
|
|
139
|
+
n = np.array(self.n, dtype=np.int32)
|
|
140
|
+
|
|
141
|
+
# Call init for memory allocation
|
|
142
|
+
ptr = _nfctlib.jnfct_alloc()
|
|
143
|
+
|
|
144
|
+
# Set the pointer
|
|
145
|
+
self.plan = ctypes.cast(ptr, ctypes.POINTER(nfct_plan))
|
|
146
|
+
|
|
147
|
+
# Initialize values
|
|
148
|
+
_nfctlib.jnfct_init(
|
|
149
|
+
self.plan,
|
|
150
|
+
ctypes.c_int(self.D),
|
|
151
|
+
ctypes.cast(Nv.ctypes.data, ctypes.POINTER(ctypes.c_int)),
|
|
152
|
+
ctypes.c_int(self.M),
|
|
153
|
+
ctypes.cast(n.ctypes.data, ctypes.POINTER(ctypes.c_int)),
|
|
154
|
+
ctypes.c_int(self.m),
|
|
155
|
+
self.f1,
|
|
156
|
+
self.f2,
|
|
157
|
+
)
|
|
158
|
+
self.init_done = True
|
|
159
|
+
|
|
160
|
+
def init(self):
|
|
161
|
+
"""
|
|
162
|
+
Alternative call for **nfct_init()**
|
|
163
|
+
"""
|
|
164
|
+
return self.nfct_init()
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def x(self) -> np.ndarray:
|
|
168
|
+
return self._X
|
|
169
|
+
|
|
170
|
+
@x.setter
|
|
171
|
+
def x(self, value: np.ndarray):
|
|
172
|
+
if value is not None:
|
|
173
|
+
if not self.init_done:
|
|
174
|
+
self.nfct_init()
|
|
175
|
+
if self.finalized:
|
|
176
|
+
raise RuntimeError("Plan already finalized")
|
|
177
|
+
if not (
|
|
178
|
+
isinstance(value, np.ndarray)
|
|
179
|
+
and value.dtype == np.float64
|
|
180
|
+
and value.flags["C"]
|
|
181
|
+
):
|
|
182
|
+
raise RuntimeError("x has to be C-continuous, numpy float64 array")
|
|
183
|
+
if self.D == 1:
|
|
184
|
+
shape = self.M
|
|
185
|
+
else:
|
|
186
|
+
shape = (self.M, self.D)
|
|
187
|
+
self._X = np.ctypeslib.as_array(
|
|
188
|
+
_nfctlib.jnfct_set_x(self.plan, value), shape=(self.M * self.D,)
|
|
189
|
+
).reshape(shape)
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def f(self) -> np.ndarray:
|
|
193
|
+
return self._f
|
|
194
|
+
|
|
195
|
+
@f.setter
|
|
196
|
+
def f(self, value: np.ndarray):
|
|
197
|
+
if value is not None:
|
|
198
|
+
if not self.init_done:
|
|
199
|
+
self.nfct_init()
|
|
200
|
+
if self.finalized:
|
|
201
|
+
raise RuntimeError("Plan already finalized")
|
|
202
|
+
if not (
|
|
203
|
+
isinstance(value, np.ndarray)
|
|
204
|
+
and value.dtype == np.float64
|
|
205
|
+
and value.flags["C"]
|
|
206
|
+
):
|
|
207
|
+
raise RuntimeError("f has to be C-continuous, numpy float64 array")
|
|
208
|
+
self._f = np.ctypeslib.as_array(
|
|
209
|
+
_nfctlib.jnfct_set_f(self.plan, value), shape=(self.M,)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def fhat(self) -> np.ndarray:
|
|
214
|
+
return self._fhat
|
|
215
|
+
|
|
216
|
+
@fhat.setter
|
|
217
|
+
def fhat(self, value: np.ndarray):
|
|
218
|
+
if value is not None:
|
|
219
|
+
Ns = np.prod(self.N)
|
|
220
|
+
if not self.init_done:
|
|
221
|
+
self.nfct_init()
|
|
222
|
+
if self.finalized:
|
|
223
|
+
raise RuntimeError("Plan already finalized")
|
|
224
|
+
if not (isinstance(value, np.ndarray) and value.dtype == np.float64):
|
|
225
|
+
raise RuntimeError("fhat has to be a numpy float64 array")
|
|
226
|
+
if not value.flags["C"]:
|
|
227
|
+
raise RuntimeError("fhat has to be C-continuous")
|
|
228
|
+
if value.size != Ns:
|
|
229
|
+
raise ValueError(f"fhat has to be an array of size {Ns}")
|
|
230
|
+
self._fhat = np.ctypeslib.as_array(
|
|
231
|
+
_nfctlib.jnfct_set_fhat(self.plan, value), shape=(Ns,)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def num_threads(self) -> int:
|
|
236
|
+
return _nfctlib.nfft_get_num_threads()
|
|
237
|
+
|
|
238
|
+
def nfct_trafo(self):
|
|
239
|
+
"""
|
|
240
|
+
Computes the NDCT via the fast NFCT algorithm for the provided nodes in **x** and coefficients in **fhat**.
|
|
241
|
+
"""
|
|
242
|
+
# Prevent bad stuff from happening
|
|
243
|
+
if self.finalized:
|
|
244
|
+
raise RuntimeError("NFCT already finalized")
|
|
245
|
+
|
|
246
|
+
if not hasattr(self, "_fhat"):
|
|
247
|
+
raise ValueError("fhat has not been set.")
|
|
248
|
+
|
|
249
|
+
if not hasattr(self, "_X"):
|
|
250
|
+
raise ValueError("x has not been set.")
|
|
251
|
+
self._f = np.ctypeslib.as_array(
|
|
252
|
+
_nfctlib.jnfct_trafo(self.plan), shape=(self.M,)
|
|
253
|
+
).copy()
|
|
254
|
+
|
|
255
|
+
def trafo(self):
|
|
256
|
+
"""
|
|
257
|
+
Alternative call for **nfct_trafo()**
|
|
258
|
+
"""
|
|
259
|
+
return self.nfct_trafo()
|
|
260
|
+
|
|
261
|
+
def nfct_trafo_direct(self):
|
|
262
|
+
"""
|
|
263
|
+
Computes the NDCT via naive matrix-vector multiplication for the provided nodes in **x** and coefficients in **fhat**.
|
|
264
|
+
"""
|
|
265
|
+
# Prevent bad stuff from happening
|
|
266
|
+
if self.finalized:
|
|
267
|
+
raise RuntimeError("NFCT already finalized")
|
|
268
|
+
|
|
269
|
+
if self._fhat is None:
|
|
270
|
+
raise ValueError("fhat has not been set.")
|
|
271
|
+
|
|
272
|
+
if self._X is None:
|
|
273
|
+
raise ValueError("x has not been set.")
|
|
274
|
+
self._f = np.ctypeslib.as_array(
|
|
275
|
+
_nfctlib.jnfct_trafo_direct(self.plan), shape=(self.M,)
|
|
276
|
+
).copy()
|
|
277
|
+
|
|
278
|
+
def trafo_direct(self):
|
|
279
|
+
"""
|
|
280
|
+
Alternative call for **nfct_trafo_direct()**
|
|
281
|
+
"""
|
|
282
|
+
return self.nfct_trafo_direct()
|
|
283
|
+
|
|
284
|
+
def nfct_transposed(self):
|
|
285
|
+
"""
|
|
286
|
+
Computes the transposed NDCT via the fast transposed NFCT algorithm for the provided nodes in **x** and coefficients in **f**.
|
|
287
|
+
"""
|
|
288
|
+
Ns = np.prod(self.N)
|
|
289
|
+
|
|
290
|
+
# Prevent bad stuff from happening
|
|
291
|
+
if self.finalized:
|
|
292
|
+
raise RuntimeError("NFCT already finalized")
|
|
293
|
+
|
|
294
|
+
if self._f is None:
|
|
295
|
+
raise ValueError("f has not been set.")
|
|
296
|
+
|
|
297
|
+
if self._X is None:
|
|
298
|
+
raise ValueError("x has not been set.")
|
|
299
|
+
self._fhat = np.ctypeslib.as_array(
|
|
300
|
+
_nfctlib.jnfct_adjoint(self.plan), shape=(Ns,)
|
|
301
|
+
).copy()
|
|
302
|
+
|
|
303
|
+
def nfct_transposed_direct(self):
|
|
304
|
+
"""
|
|
305
|
+
Computes the transposed NDCT via naive matrix-vector multiplication for provided nodes for the provided nodes in **x** and coefficients in **f**.
|
|
306
|
+
"""
|
|
307
|
+
Ns = np.prod(self.N)
|
|
308
|
+
# Prevent bad stuff from happening
|
|
309
|
+
if self.finalized:
|
|
310
|
+
raise RuntimeError("NFCT already finalized")
|
|
311
|
+
|
|
312
|
+
if self._f is None:
|
|
313
|
+
raise ValueError("f has not been set.")
|
|
314
|
+
|
|
315
|
+
if self._X is None:
|
|
316
|
+
raise ValueError("x has not been set.")
|
|
317
|
+
self._fhat = np.ctypeslib.as_array(
|
|
318
|
+
_nfctlib.jnfct_adjoint_direct(self.plan), shape=(Ns,)
|
|
319
|
+
).copy()
|
|
320
|
+
|
|
321
|
+
def nfct_adjoint(self):
|
|
322
|
+
"""
|
|
323
|
+
Alternative call for **nfct_transposed()**
|
|
324
|
+
"""
|
|
325
|
+
return self.nfct_transposed()
|
|
326
|
+
|
|
327
|
+
def nfct_adjoint_direct(self):
|
|
328
|
+
"""
|
|
329
|
+
Alternative call for **nfct_transposed_direct()**
|
|
330
|
+
"""
|
|
331
|
+
return self.nfct_transposed_direct()
|
|
332
|
+
|
|
333
|
+
def adjoint(self):
|
|
334
|
+
"""
|
|
335
|
+
Alternative call for **nfct_adjoint()**
|
|
336
|
+
"""
|
|
337
|
+
return self.nfct_adjoint()
|
|
338
|
+
|
|
339
|
+
def adjoint_direct(self):
|
|
340
|
+
"""
|
|
341
|
+
Alternative call for **nfct_adjoint_direct()**
|
|
342
|
+
"""
|
|
343
|
+
return self.nfct_adjoint_direct()
|