fftw3-ruby 0.1.0

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.
data/lib/fftw3/plan.rb ADDED
@@ -0,0 +1,513 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFTW3
4
+ class Plan
5
+ attr_reader :ptr, :precision
6
+
7
+ def initialize(ptr, precision:, input_buffer: nil, output_buffer: nil, **_metadata)
8
+ raise FFTW3::Error::PlanError, "FFTW returned NULL plan" if ptr.null?
9
+
10
+ @ptr = ptr
11
+ @precision = precision
12
+ @input_buffer = input_buffer
13
+ @output_buffer = output_buffer
14
+ @destroyed_flag = [false]
15
+
16
+ destructor = ffi_module.method(:"#{prefix}destroy_plan")
17
+ ObjectSpace.define_finalizer(self, self.class.release(ptr, destructor, @destroyed_flag))
18
+ end
19
+
20
+ def execute
21
+ ffi_module.send(:"#{prefix}execute", @ptr)
22
+ end
23
+
24
+ def execute_dft(input, output)
25
+ ffi_module.send(:"#{prefix}execute_dft", @ptr, input.pointer, output.pointer)
26
+ end
27
+
28
+ def execute_dft_r2c(input, output)
29
+ ffi_module.send(:"#{prefix}execute_dft_r2c", @ptr, input.pointer, output.pointer)
30
+ end
31
+
32
+ def execute_dft_c2r(input, output)
33
+ ffi_module.send(:"#{prefix}execute_dft_c2r", @ptr, input.pointer, output.pointer)
34
+ end
35
+
36
+ def execute_r2r(input, output)
37
+ ffi_module.send(:"#{prefix}execute_r2r", @ptr, input.pointer, output.pointer)
38
+ end
39
+
40
+ def execute_split_dft(ri, ii, ro, io)
41
+ ffi_module.send(:"#{prefix}execute_split_dft", @ptr, ri.pointer, ii.pointer, ro.pointer, io.pointer)
42
+ end
43
+
44
+ def execute_split_dft_r2c(input, ro, io)
45
+ ffi_module.send(:"#{prefix}execute_split_dft_r2c", @ptr, input.pointer, ro.pointer, io.pointer)
46
+ end
47
+
48
+ def execute_split_dft_c2r(ri, ii, output)
49
+ ffi_module.send(:"#{prefix}execute_split_dft_c2r", @ptr, ri.pointer, ii.pointer, output.pointer)
50
+ end
51
+
52
+ def to_s
53
+ description_ptr = ffi_module.send(:"#{prefix}sprint_plan", @ptr)
54
+ return "" if description_ptr.null?
55
+
56
+ description = description_ptr.read_string
57
+ ffi_module.send(:"#{prefix}free", description_ptr)
58
+ description
59
+ end
60
+
61
+ alias description to_s
62
+
63
+ def print(io = $stdout)
64
+ io.write(to_s)
65
+ io
66
+ end
67
+
68
+ def flops
69
+ add = ::FFI::MemoryPointer.new(:double)
70
+ mul = ::FFI::MemoryPointer.new(:double)
71
+ fma = ::FFI::MemoryPointer.new(:double)
72
+ ffi_module.send(:"#{prefix}flops", @ptr, add, mul, fma)
73
+ { add: add.read_double, mul: mul.read_double, fma: fma.read_double }
74
+ end
75
+
76
+ def cost
77
+ ffi_module.send(:"#{prefix}cost", @ptr)
78
+ end
79
+
80
+ def estimate_cost
81
+ ffi_module.send(:"#{prefix}estimate_cost", @ptr)
82
+ end
83
+
84
+ def destroy!
85
+ return if @destroyed_flag[0]
86
+
87
+ ffi_module.send(:"#{prefix}destroy_plan", @ptr)
88
+ @destroyed_flag[0] = true
89
+ end
90
+
91
+ class << self
92
+ # ---- Basic Complex DFT ----
93
+ def dft_1d(n, input, output, direction:, flags: [:estimate], precision: :double)
94
+ ffi = FFTW3::FFI.module_for(precision)
95
+ pfx = FFTW3::FFI.prefix_for(precision)
96
+ ptr = ffi.send(:"#{pfx}plan_dft_1d", n, input.pointer, output.pointer,
97
+ resolve_direction(direction), resolve_flags(flags))
98
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
99
+ end
100
+
101
+ def dft_2d(n0, n1, input, output, direction:, flags: [:estimate], precision: :double)
102
+ ffi = FFTW3::FFI.module_for(precision)
103
+ pfx = FFTW3::FFI.prefix_for(precision)
104
+ ptr = ffi.send(:"#{pfx}plan_dft_2d", n0, n1, input.pointer, output.pointer,
105
+ resolve_direction(direction), resolve_flags(flags))
106
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
107
+ end
108
+
109
+ def dft_3d(n0, n1, n2, input, output, direction:, flags: [:estimate], precision: :double)
110
+ ffi = FFTW3::FFI.module_for(precision)
111
+ pfx = FFTW3::FFI.prefix_for(precision)
112
+ ptr = ffi.send(:"#{pfx}plan_dft_3d", n0, n1, n2, input.pointer, output.pointer,
113
+ resolve_direction(direction), resolve_flags(flags))
114
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
115
+ end
116
+
117
+ def dft(dims, input, output, direction:, flags: [:estimate], precision: :double)
118
+ ffi = FFTW3::FFI.module_for(precision)
119
+ pfx = FFTW3::FFI.prefix_for(precision)
120
+ n_ptr = ::FFI::MemoryPointer.new(:int, dims.length)
121
+ n_ptr.put_array_of_int(0, dims)
122
+ ptr = ffi.send(:"#{pfx}plan_dft", dims.length, n_ptr, input.pointer, output.pointer,
123
+ resolve_direction(direction), resolve_flags(flags))
124
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
125
+ end
126
+
127
+ # ---- Basic R2C ----
128
+ def dft_r2c_1d(n, input, output, flags: [:estimate], precision: :double)
129
+ ffi = FFTW3::FFI.module_for(precision)
130
+ pfx = FFTW3::FFI.prefix_for(precision)
131
+ ptr = ffi.send(:"#{pfx}plan_dft_r2c_1d", n, input.pointer, output.pointer, resolve_flags(flags))
132
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
133
+ end
134
+
135
+ def dft_r2c_2d(n0, n1, input, output, flags: [:estimate], precision: :double)
136
+ ffi = FFTW3::FFI.module_for(precision)
137
+ pfx = FFTW3::FFI.prefix_for(precision)
138
+ ptr = ffi.send(:"#{pfx}plan_dft_r2c_2d", n0, n1, input.pointer, output.pointer, resolve_flags(flags))
139
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
140
+ end
141
+
142
+ def dft_r2c_3d(n0, n1, n2, input, output, flags: [:estimate], precision: :double)
143
+ ffi = FFTW3::FFI.module_for(precision)
144
+ pfx = FFTW3::FFI.prefix_for(precision)
145
+ ptr = ffi.send(:"#{pfx}plan_dft_r2c_3d", n0, n1, n2, input.pointer, output.pointer, resolve_flags(flags))
146
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
147
+ end
148
+
149
+ def dft_r2c(dims, input, output, flags: [:estimate], precision: :double)
150
+ ffi = FFTW3::FFI.module_for(precision)
151
+ pfx = FFTW3::FFI.prefix_for(precision)
152
+ n_ptr = ::FFI::MemoryPointer.new(:int, dims.length)
153
+ n_ptr.put_array_of_int(0, dims)
154
+ ptr = ffi.send(:"#{pfx}plan_dft_r2c", dims.length, n_ptr, input.pointer, output.pointer,
155
+ resolve_flags(flags))
156
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
157
+ end
158
+
159
+ # ---- Basic C2R ----
160
+ def dft_c2r_1d(n, input, output, flags: [:estimate], precision: :double)
161
+ ffi = FFTW3::FFI.module_for(precision)
162
+ pfx = FFTW3::FFI.prefix_for(precision)
163
+ ptr = ffi.send(:"#{pfx}plan_dft_c2r_1d", n, input.pointer, output.pointer, resolve_flags(flags))
164
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
165
+ end
166
+
167
+ def dft_c2r_2d(n0, n1, input, output, flags: [:estimate], precision: :double)
168
+ ffi = FFTW3::FFI.module_for(precision)
169
+ pfx = FFTW3::FFI.prefix_for(precision)
170
+ ptr = ffi.send(:"#{pfx}plan_dft_c2r_2d", n0, n1, input.pointer, output.pointer, resolve_flags(flags))
171
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
172
+ end
173
+
174
+ def dft_c2r_3d(n0, n1, n2, input, output, flags: [:estimate], precision: :double)
175
+ ffi = FFTW3::FFI.module_for(precision)
176
+ pfx = FFTW3::FFI.prefix_for(precision)
177
+ ptr = ffi.send(:"#{pfx}plan_dft_c2r_3d", n0, n1, n2, input.pointer, output.pointer, resolve_flags(flags))
178
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
179
+ end
180
+
181
+ def dft_c2r(dims, input, output, flags: [:estimate], precision: :double)
182
+ ffi = FFTW3::FFI.module_for(precision)
183
+ pfx = FFTW3::FFI.prefix_for(precision)
184
+ n_ptr = ::FFI::MemoryPointer.new(:int, dims.length)
185
+ n_ptr.put_array_of_int(0, dims)
186
+ ptr = ffi.send(:"#{pfx}plan_dft_c2r", dims.length, n_ptr, input.pointer, output.pointer,
187
+ resolve_flags(flags))
188
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
189
+ end
190
+
191
+ # ---- Basic R2R ----
192
+ def r2r_1d(n, input, output, kind:, flags: [:estimate], precision: :double)
193
+ ffi = FFTW3::FFI.module_for(precision)
194
+ pfx = FFTW3::FFI.prefix_for(precision)
195
+ ptr = ffi.send(:"#{pfx}plan_r2r_1d", n, input.pointer, output.pointer, kind, resolve_flags(flags))
196
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
197
+ end
198
+
199
+ def r2r_2d(n0, n1, input, output, kind0:, kind1:, flags: [:estimate], precision: :double)
200
+ ffi = FFTW3::FFI.module_for(precision)
201
+ pfx = FFTW3::FFI.prefix_for(precision)
202
+ ptr = ffi.send(:"#{pfx}plan_r2r_2d", n0, n1, input.pointer, output.pointer, kind0, kind1,
203
+ resolve_flags(flags))
204
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
205
+ end
206
+
207
+ def r2r_3d(n0, n1, n2, input, output, kind0:, kind1:, kind2:, flags: [:estimate], precision: :double)
208
+ ffi = FFTW3::FFI.module_for(precision)
209
+ pfx = FFTW3::FFI.prefix_for(precision)
210
+ ptr = ffi.send(:"#{pfx}plan_r2r_3d", n0, n1, n2, input.pointer, output.pointer,
211
+ kind0, kind1, kind2, resolve_flags(flags))
212
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
213
+ end
214
+
215
+ def r2r(dims, input, output, kinds:, flags: [:estimate], precision: :double)
216
+ ffi = FFTW3::FFI.module_for(precision)
217
+ pfx = FFTW3::FFI.prefix_for(precision)
218
+ n_ptr = ::FFI::MemoryPointer.new(:int, dims.length)
219
+ n_ptr.put_array_of_int(0, dims)
220
+ kind_ptr = ::FFI::MemoryPointer.new(:int, kinds.length)
221
+ kind_ptr.put_array_of_int(0, kinds)
222
+ ptr = ffi.send(:"#{pfx}plan_r2r", dims.length, n_ptr, input.pointer, output.pointer,
223
+ kind_ptr, resolve_flags(flags))
224
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
225
+ end
226
+
227
+ # ---- Advanced (many) ----
228
+ def many_dft(rank:, n:, howmany:, input:, inembed:, istride:, idist:,
229
+ output:, onembed:, ostride:, odist:, direction:, flags: [:estimate], precision: :double)
230
+ ffi = FFTW3::FFI.module_for(precision)
231
+ pfx = FFTW3::FFI.prefix_for(precision)
232
+ n_ptr = ::FFI::MemoryPointer.new(:int, n.length)
233
+ n_ptr.put_array_of_int(0, n)
234
+ inembed_ptr = array_to_int_ptr(inembed)
235
+ onembed_ptr = array_to_int_ptr(onembed)
236
+ ptr = ffi.send(:"#{pfx}plan_many_dft", rank, n_ptr, howmany,
237
+ input.pointer, inembed_ptr, istride, idist,
238
+ output.pointer, onembed_ptr, ostride, odist,
239
+ resolve_direction(direction), resolve_flags(flags))
240
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
241
+ end
242
+
243
+ def many_dft_r2c(rank:, n:, howmany:, input:, inembed:, istride:, idist:,
244
+ output:, onembed:, ostride:, odist:, flags: [:estimate], precision: :double)
245
+ ffi = FFTW3::FFI.module_for(precision)
246
+ pfx = FFTW3::FFI.prefix_for(precision)
247
+ n_ptr = ::FFI::MemoryPointer.new(:int, n.length)
248
+ n_ptr.put_array_of_int(0, n)
249
+ inembed_ptr = array_to_int_ptr(inembed)
250
+ onembed_ptr = array_to_int_ptr(onembed)
251
+ ptr = ffi.send(:"#{pfx}plan_many_dft_r2c", rank, n_ptr, howmany,
252
+ input.pointer, inembed_ptr, istride, idist,
253
+ output.pointer, onembed_ptr, ostride, odist,
254
+ resolve_flags(flags))
255
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
256
+ end
257
+
258
+ def many_dft_c2r(rank:, n:, howmany:, input:, inembed:, istride:, idist:,
259
+ output:, onembed:, ostride:, odist:, flags: [:estimate], precision: :double)
260
+ ffi = FFTW3::FFI.module_for(precision)
261
+ pfx = FFTW3::FFI.prefix_for(precision)
262
+ n_ptr = ::FFI::MemoryPointer.new(:int, n.length)
263
+ n_ptr.put_array_of_int(0, n)
264
+ inembed_ptr = array_to_int_ptr(inembed)
265
+ onembed_ptr = array_to_int_ptr(onembed)
266
+ ptr = ffi.send(:"#{pfx}plan_many_dft_c2r", rank, n_ptr, howmany,
267
+ input.pointer, inembed_ptr, istride, idist,
268
+ output.pointer, onembed_ptr, ostride, odist,
269
+ resolve_flags(flags))
270
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
271
+ end
272
+
273
+ def many_r2r(rank:, n:, howmany:, input:, inembed:, istride:, idist:,
274
+ output:, onembed:, ostride:, odist:, kinds:, flags: [:estimate], precision: :double)
275
+ ffi = FFTW3::FFI.module_for(precision)
276
+ pfx = FFTW3::FFI.prefix_for(precision)
277
+ n_ptr = ::FFI::MemoryPointer.new(:int, n.length)
278
+ n_ptr.put_array_of_int(0, n)
279
+ inembed_ptr = array_to_int_ptr(inembed)
280
+ onembed_ptr = array_to_int_ptr(onembed)
281
+ kind_ptr = ::FFI::MemoryPointer.new(:int, kinds.length)
282
+ kind_ptr.put_array_of_int(0, kinds)
283
+ ptr = ffi.send(:"#{pfx}plan_many_r2r", rank, n_ptr, howmany,
284
+ input.pointer, inembed_ptr, istride, idist,
285
+ output.pointer, onembed_ptr, ostride, odist,
286
+ kind_ptr, resolve_flags(flags))
287
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
288
+ end
289
+
290
+ # ---- Guru ----
291
+ def guru_dft(dims, howmany_dims, input, output, sign:, flags: [:estimate], precision: :double)
292
+ ffi = FFTW3::FFI.module_for(precision)
293
+ pfx = FFTW3::FFI.prefix_for(precision)
294
+ dims_ptr = IODim.to_ffi(dims)
295
+ howmany_ptr = IODim.to_ffi(howmany_dims)
296
+ ptr = ffi.send(:"#{pfx}plan_guru_dft", dims.length, dims_ptr,
297
+ howmany_dims.length, howmany_ptr,
298
+ input.pointer, output.pointer,
299
+ resolve_direction(sign), resolve_flags(flags))
300
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
301
+ end
302
+
303
+ def guru_split_dft(dims, howmany_dims, ri, ii, ro, io, flags: [:estimate], precision: :double)
304
+ ffi = FFTW3::FFI.module_for(precision)
305
+ pfx = FFTW3::FFI.prefix_for(precision)
306
+ dims_ptr = IODim.to_ffi(dims)
307
+ howmany_ptr = IODim.to_ffi(howmany_dims)
308
+ ptr = ffi.send(:"#{pfx}plan_guru_split_dft", dims.length, dims_ptr,
309
+ howmany_dims.length, howmany_ptr,
310
+ ri.pointer, ii.pointer, ro.pointer, io.pointer,
311
+ resolve_flags(flags))
312
+ new(ptr, precision: precision, input_buffer: [ri, ii], output_buffer: [ro, io])
313
+ end
314
+
315
+ def guru_dft_r2c(dims, howmany_dims, input, output, flags: [:estimate], precision: :double)
316
+ ffi = FFTW3::FFI.module_for(precision)
317
+ pfx = FFTW3::FFI.prefix_for(precision)
318
+ dims_ptr = IODim.to_ffi(dims)
319
+ howmany_ptr = IODim.to_ffi(howmany_dims)
320
+ ptr = ffi.send(:"#{pfx}plan_guru_dft_r2c", dims.length, dims_ptr,
321
+ howmany_dims.length, howmany_ptr,
322
+ input.pointer, output.pointer,
323
+ resolve_flags(flags))
324
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
325
+ end
326
+
327
+ def guru_split_dft_r2c(dims, howmany_dims, input, ro, io, flags: [:estimate], precision: :double)
328
+ ffi = FFTW3::FFI.module_for(precision)
329
+ pfx = FFTW3::FFI.prefix_for(precision)
330
+ dims_ptr = IODim.to_ffi(dims)
331
+ howmany_ptr = IODim.to_ffi(howmany_dims)
332
+ ptr = ffi.send(:"#{pfx}plan_guru_split_dft_r2c", dims.length, dims_ptr,
333
+ howmany_dims.length, howmany_ptr,
334
+ input.pointer, ro.pointer, io.pointer,
335
+ resolve_flags(flags))
336
+ new(ptr, precision: precision, input_buffer: input, output_buffer: [ro, io])
337
+ end
338
+
339
+ def guru_dft_c2r(dims, howmany_dims, input, output, flags: [:estimate], precision: :double)
340
+ ffi = FFTW3::FFI.module_for(precision)
341
+ pfx = FFTW3::FFI.prefix_for(precision)
342
+ dims_ptr = IODim.to_ffi(dims)
343
+ howmany_ptr = IODim.to_ffi(howmany_dims)
344
+ ptr = ffi.send(:"#{pfx}plan_guru_dft_c2r", dims.length, dims_ptr,
345
+ howmany_dims.length, howmany_ptr,
346
+ input.pointer, output.pointer,
347
+ resolve_flags(flags))
348
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
349
+ end
350
+
351
+ def guru_split_dft_c2r(dims, howmany_dims, ri, ii, output, flags: [:estimate], precision: :double)
352
+ ffi = FFTW3::FFI.module_for(precision)
353
+ pfx = FFTW3::FFI.prefix_for(precision)
354
+ dims_ptr = IODim.to_ffi(dims)
355
+ howmany_ptr = IODim.to_ffi(howmany_dims)
356
+ ptr = ffi.send(:"#{pfx}plan_guru_split_dft_c2r", dims.length, dims_ptr,
357
+ howmany_dims.length, howmany_ptr,
358
+ ri.pointer, ii.pointer, output.pointer,
359
+ resolve_flags(flags))
360
+ new(ptr, precision: precision, input_buffer: [ri, ii], output_buffer: output)
361
+ end
362
+
363
+ def guru_r2r(dims, howmany_dims, input, output, kinds:, flags: [:estimate], precision: :double)
364
+ ffi = FFTW3::FFI.module_for(precision)
365
+ pfx = FFTW3::FFI.prefix_for(precision)
366
+ dims_ptr = IODim.to_ffi(dims)
367
+ howmany_ptr = IODim.to_ffi(howmany_dims)
368
+ kind_ptr = ::FFI::MemoryPointer.new(:int, kinds.length)
369
+ kind_ptr.put_array_of_int(0, kinds)
370
+ ptr = ffi.send(:"#{pfx}plan_guru_r2r", dims.length, dims_ptr,
371
+ howmany_dims.length, howmany_ptr,
372
+ input.pointer, output.pointer,
373
+ kind_ptr, resolve_flags(flags))
374
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
375
+ end
376
+
377
+ # ---- Guru64 ----
378
+ def guru64_dft(dims, howmany_dims, input, output, sign:, flags: [:estimate], precision: :double)
379
+ ffi = FFTW3::FFI.module_for(precision)
380
+ pfx = FFTW3::FFI.prefix_for(precision)
381
+ dims_ptr = IODim64.to_ffi(dims)
382
+ howmany_ptr = IODim64.to_ffi(howmany_dims)
383
+ ptr = ffi.send(:"#{pfx}plan_guru64_dft", dims.length, dims_ptr,
384
+ howmany_dims.length, howmany_ptr,
385
+ input.pointer, output.pointer,
386
+ resolve_direction(sign), resolve_flags(flags))
387
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
388
+ end
389
+
390
+ def guru64_split_dft(dims, howmany_dims, ri, ii, ro, io, flags: [:estimate], precision: :double)
391
+ ffi = FFTW3::FFI.module_for(precision)
392
+ pfx = FFTW3::FFI.prefix_for(precision)
393
+ dims_ptr = IODim64.to_ffi(dims)
394
+ howmany_ptr = IODim64.to_ffi(howmany_dims)
395
+ ptr = ffi.send(:"#{pfx}plan_guru64_split_dft", dims.length, dims_ptr,
396
+ howmany_dims.length, howmany_ptr,
397
+ ri.pointer, ii.pointer, ro.pointer, io.pointer,
398
+ resolve_flags(flags))
399
+ new(ptr, precision: precision, input_buffer: [ri, ii], output_buffer: [ro, io])
400
+ end
401
+
402
+ def guru64_dft_r2c(dims, howmany_dims, input, output, flags: [:estimate], precision: :double)
403
+ ffi = FFTW3::FFI.module_for(precision)
404
+ pfx = FFTW3::FFI.prefix_for(precision)
405
+ dims_ptr = IODim64.to_ffi(dims)
406
+ howmany_ptr = IODim64.to_ffi(howmany_dims)
407
+ ptr = ffi.send(:"#{pfx}plan_guru64_dft_r2c", dims.length, dims_ptr,
408
+ howmany_dims.length, howmany_ptr,
409
+ input.pointer, output.pointer,
410
+ resolve_flags(flags))
411
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
412
+ end
413
+
414
+ def guru64_split_dft_r2c(dims, howmany_dims, input, ro, io, flags: [:estimate], precision: :double)
415
+ ffi = FFTW3::FFI.module_for(precision)
416
+ pfx = FFTW3::FFI.prefix_for(precision)
417
+ dims_ptr = IODim64.to_ffi(dims)
418
+ howmany_ptr = IODim64.to_ffi(howmany_dims)
419
+ ptr = ffi.send(:"#{pfx}plan_guru64_split_dft_r2c", dims.length, dims_ptr,
420
+ howmany_dims.length, howmany_ptr,
421
+ input.pointer, ro.pointer, io.pointer,
422
+ resolve_flags(flags))
423
+ new(ptr, precision: precision, input_buffer: input, output_buffer: [ro, io])
424
+ end
425
+
426
+ def guru64_dft_c2r(dims, howmany_dims, input, output, flags: [:estimate], precision: :double)
427
+ ffi = FFTW3::FFI.module_for(precision)
428
+ pfx = FFTW3::FFI.prefix_for(precision)
429
+ dims_ptr = IODim64.to_ffi(dims)
430
+ howmany_ptr = IODim64.to_ffi(howmany_dims)
431
+ ptr = ffi.send(:"#{pfx}plan_guru64_dft_c2r", dims.length, dims_ptr,
432
+ howmany_dims.length, howmany_ptr,
433
+ input.pointer, output.pointer,
434
+ resolve_flags(flags))
435
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
436
+ end
437
+
438
+ def guru64_split_dft_c2r(dims, howmany_dims, ri, ii, output, flags: [:estimate], precision: :double)
439
+ ffi = FFTW3::FFI.module_for(precision)
440
+ pfx = FFTW3::FFI.prefix_for(precision)
441
+ dims_ptr = IODim64.to_ffi(dims)
442
+ howmany_ptr = IODim64.to_ffi(howmany_dims)
443
+ ptr = ffi.send(:"#{pfx}plan_guru64_split_dft_c2r", dims.length, dims_ptr,
444
+ howmany_dims.length, howmany_ptr,
445
+ ri.pointer, ii.pointer, output.pointer,
446
+ resolve_flags(flags))
447
+ new(ptr, precision: precision, input_buffer: [ri, ii], output_buffer: output)
448
+ end
449
+
450
+ def guru64_r2r(dims, howmany_dims, input, output, kinds:, flags: [:estimate], precision: :double)
451
+ ffi = FFTW3::FFI.module_for(precision)
452
+ pfx = FFTW3::FFI.prefix_for(precision)
453
+ dims_ptr = IODim64.to_ffi(dims)
454
+ howmany_ptr = IODim64.to_ffi(howmany_dims)
455
+ kind_ptr = ::FFI::MemoryPointer.new(:int, kinds.length)
456
+ kind_ptr.put_array_of_int(0, kinds)
457
+ ptr = ffi.send(:"#{pfx}plan_guru64_r2r", dims.length, dims_ptr,
458
+ howmany_dims.length, howmany_ptr,
459
+ input.pointer, output.pointer,
460
+ kind_ptr, resolve_flags(flags))
461
+ new(ptr, precision: precision, input_buffer: input, output_buffer: output)
462
+ end
463
+
464
+ private
465
+
466
+ def resolve_flags(flags)
467
+ flags.reduce(0) { |mask, f|
468
+ case f
469
+ when Symbol then mask | FFTW3.const_get(f.to_s.upcase)
470
+ when Integer then mask | f
471
+ else raise FFTW3::Error::InvalidArgument, "Invalid flag: #{f}"
472
+ end
473
+ }
474
+ end
475
+
476
+ def resolve_direction(direction)
477
+ case direction
478
+ when :forward then FFTW3::FORWARD
479
+ when :backward then FFTW3::BACKWARD
480
+ when Integer then direction
481
+ else raise FFTW3::Error::InvalidArgument, "Invalid direction: #{direction}"
482
+ end
483
+ end
484
+
485
+ def array_to_int_ptr(arr)
486
+ return nil if arr.nil?
487
+
488
+ ptr = ::FFI::MemoryPointer.new(:int, arr.length)
489
+ ptr.put_array_of_int(0, arr)
490
+ ptr
491
+ end
492
+ end
493
+
494
+ private
495
+
496
+ def ffi_module
497
+ FFTW3::FFI.module_for(@precision)
498
+ end
499
+
500
+ def prefix
501
+ FFTW3::FFI.prefix_for(@precision)
502
+ end
503
+
504
+ def self.release(ptr, destructor, destroyed_flag)
505
+ proc {
506
+ unless destroyed_flag[0] || ptr.null?
507
+ destructor.call(ptr)
508
+ destroyed_flag[0] = true
509
+ end
510
+ }
511
+ end
512
+ end
513
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFTW3
4
+ module Threads
5
+ @initialized = {}
6
+ @thread_modules = {}
7
+ @callback_state = {}
8
+
9
+ def self.init(precision: :double)
10
+ return if @initialized[precision]
11
+
12
+ pfx = FFTW3::FFI.prefix_for(precision)
13
+ result = thread_module_for(precision).send(:"#{pfx}init_threads")
14
+ raise FFTW3::Error::PlanError, "Failed to init FFTW threads" if result == 0
15
+
16
+ @initialized[precision] = true
17
+ end
18
+
19
+ def self.plan_with_nthreads(nthreads, precision: :double)
20
+ init(precision: precision) unless @initialized[precision]
21
+ pfx = FFTW3::FFI.prefix_for(precision)
22
+ thread_module_for(precision).send(:"#{pfx}plan_with_nthreads", nthreads)
23
+ end
24
+
25
+ def self.cleanup(precision: :double)
26
+ return unless @initialized[precision]
27
+
28
+ pfx = FFTW3::FFI.prefix_for(precision)
29
+ thread_module_for(precision).send(:"#{pfx}cleanup_threads")
30
+ @initialized[precision] = false
31
+ end
32
+
33
+ def self.planner_nthreads(precision: :double)
34
+ init(precision: precision) unless @initialized[precision]
35
+ return nil unless thread_module_for(precision).planner_nthreads_supported?
36
+
37
+ pfx = FFTW3::FFI.prefix_for(precision)
38
+ thread_module_for(precision).send(:"#{pfx}planner_nthreads")
39
+ end
40
+
41
+ def self.make_planner_thread_safe(precision: :double)
42
+ init(precision: precision) unless @initialized[precision]
43
+ return nil unless thread_module_for(precision).make_planner_thread_safe_supported?
44
+
45
+ pfx = FFTW3::FFI.prefix_for(precision)
46
+ thread_module_for(precision).send(:"#{pfx}make_planner_thread_safe")
47
+ end
48
+
49
+ def self.set_callback_supported?(precision: :double)
50
+ thread_module_for(precision).threads_set_callback_supported?
51
+ end
52
+
53
+ def self.set_callback(callback = nil, data: nil, precision: :double, &block)
54
+ callback ||= block
55
+ validate_callback!(callback)
56
+
57
+ thread_mod = thread_module_for(precision)
58
+ return nil unless thread_mod.threads_set_callback_supported?
59
+
60
+ pfx = FFTW3::FFI.prefix_for(precision)
61
+ function = build_parallel_loop_function(callback)
62
+ data_ptr = normalize_data_pointer(data)
63
+
64
+ thread_mod.send(:"#{pfx}threads_set_callback", function, data_ptr)
65
+ @callback_state[precision] = { callback: callback, function: function, data: data }
66
+ true
67
+ end
68
+
69
+ class << self
70
+ private
71
+
72
+ def thread_module_for(precision)
73
+ @thread_modules[precision] ||= build_thread_module(precision)
74
+ end
75
+
76
+ def build_thread_module(precision)
77
+ pfx = FFTW3::FFI.prefix_for(precision)
78
+ threads_lib = FFTW3::FFI::LibraryLoader.find_threads(precision)
79
+
80
+ Module.new do
81
+ extend ::FFI::Library
82
+ ffi_lib threads_lib
83
+
84
+ attach_function :"#{pfx}init_threads", [], :int
85
+ attach_function :"#{pfx}plan_with_nthreads", [:int], :void
86
+ attach_function :"#{pfx}cleanup_threads", [], :void
87
+
88
+ begin
89
+ attach_function :"#{pfx}planner_nthreads", [], :int
90
+ @planner_nthreads_supported = true
91
+ rescue ::FFI::NotFoundError
92
+ @planner_nthreads_supported = false
93
+ end
94
+
95
+ begin
96
+ attach_function :"#{pfx}make_planner_thread_safe", [], :void
97
+ @make_planner_thread_safe_supported = true
98
+ rescue ::FFI::NotFoundError
99
+ @make_planner_thread_safe_supported = false
100
+ end
101
+
102
+ begin
103
+ attach_function :"#{pfx}threads_set_callback", [:pointer, :pointer], :void
104
+ @threads_set_callback_supported = true
105
+ rescue ::FFI::NotFoundError
106
+ @threads_set_callback_supported = false
107
+ end
108
+
109
+ def self.planner_nthreads_supported?
110
+ @planner_nthreads_supported
111
+ end
112
+
113
+ def self.make_planner_thread_safe_supported?
114
+ @make_planner_thread_safe_supported
115
+ end
116
+
117
+ def self.threads_set_callback_supported?
118
+ @threads_set_callback_supported
119
+ end
120
+ end
121
+ end
122
+
123
+ def validate_callback!(callback)
124
+ return if callback.nil? || callback.respond_to?(:call)
125
+
126
+ raise FFTW3::Error::InvalidArgument, "callback must respond to #call"
127
+ end
128
+
129
+ def build_parallel_loop_function(callback)
130
+ return nil if callback.nil?
131
+
132
+ ::FFI::Function.new(:void, %i[pointer pointer size_t int pointer]) do |work_ptr, jobdata, elsize, njobs, data_ptr|
133
+ work = ::FFI::Function.new(:pointer, [:pointer], work_ptr)
134
+ callback.call(work, jobdata, elsize, njobs, data_ptr)
135
+ nil
136
+ end
137
+ end
138
+
139
+ def normalize_data_pointer(data)
140
+ case data
141
+ when nil
142
+ nil
143
+ when ::FFI::Pointer
144
+ data
145
+ else
146
+ return data.to_ptr if data.respond_to?(:to_ptr)
147
+
148
+ raise FFTW3::Error::InvalidArgument,
149
+ "data must be an FFI::Pointer or respond to #to_ptr"
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFTW3
4
+ module Utils
5
+ def self.cleanup(precision: :double)
6
+ ffi = FFTW3::FFI.module_for(precision)
7
+ pfx = FFTW3::FFI.prefix_for(precision)
8
+ ffi.send(:"#{pfx}cleanup")
9
+ end
10
+
11
+ def self.set_timelimit(seconds, precision: :double)
12
+ ffi = FFTW3::FFI.module_for(precision)
13
+ pfx = FFTW3::FFI.prefix_for(precision)
14
+ ffi.send(:"#{pfx}set_timelimit", seconds.to_f)
15
+ end
16
+
17
+ def self.alignment_of(pointer, precision: :double)
18
+ ffi = FFTW3::FFI.module_for(precision)
19
+ pfx = FFTW3::FFI.prefix_for(precision)
20
+ ffi.send(:"#{pfx}alignment_of", pointer)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FFTW3
4
+ VERSION = "0.1.0"
5
+ end