pyNFFT3 1.1.0__py3-none-win_amd64.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 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()