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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +399 -0
- data/Rakefile +8 -0
- data/lib/fftw3/aligned_memory.rb +144 -0
- data/lib/fftw3/constants.rb +47 -0
- data/lib/fftw3/error.rb +9 -0
- data/lib/fftw3/ffi/binding_template.rb +163 -0
- data/lib/fftw3/ffi/double.rb +15 -0
- data/lib/fftw3/ffi/float.rb +15 -0
- data/lib/fftw3/ffi/library_loader.rb +90 -0
- data/lib/fftw3/ffi/long_double.rb +15 -0
- data/lib/fftw3/ffi/quad.rb +15 -0
- data/lib/fftw3/ffi/types.rb +21 -0
- data/lib/fftw3/iodim.rb +29 -0
- data/lib/fftw3/plan.rb +513 -0
- data/lib/fftw3/threads.rb +154 -0
- data/lib/fftw3/utils.rb +23 -0
- data/lib/fftw3/version.rb +5 -0
- data/lib/fftw3/wisdom.rb +93 -0
- data/lib/fftw3.rb +58 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 50798c6924b8398dfcd52f1dfe1ee43531fa523b3e15ae4d72ab06c07d90121d
|
|
4
|
+
data.tar.gz: a7d75f44a6066629ea627b166137e1043871abb65479687685777d18ab3e58bc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 98ecc76de7b79206228b602070453d7b8db76c523e55ab633a74028c403f079ab79ec54d1128c8e71b236c15cb09a0233b91d8afcdd790dfb1b2a7d8373bf66f
|
|
7
|
+
data.tar.gz: 8526e3eeec9352cccb66149fd57156903c510e6bbd7e372320c711d16e40de99bb81a17fc199cb8afae8103ed654e311a5f06628fe4101fb6c6a1e8e56363f89
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# FFTW3-Ruby
|
|
2
|
+
|
|
3
|
+
Ruby FFI bindings for [FFTW3](http://www.fftw.org/) (Fastest Fourier Transform in the West).
|
|
4
|
+
|
|
5
|
+
The gem exposes the FFTW planning and execution APIs for complex DFT, real-to-complex and complex-to-real transforms, real-to-real transforms, the advanced "many" interface, guru and guru64 planners, wisdom import/export, thread control, and runtime metadata.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Ruby 3.1+
|
|
10
|
+
- FFTW 3.3.x shared libraries installed on the system
|
|
11
|
+
|
|
12
|
+
### Installing FFTW3
|
|
13
|
+
|
|
14
|
+
Ubuntu / Debian:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
sudo apt-get install libfftw3-dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
macOS (Homebrew):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
brew install fftw
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Add the gem to your Gemfile:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
gem "fftw3-ruby"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then install dependencies:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bundle install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Precision Support
|
|
41
|
+
|
|
42
|
+
Runtime precision support depends on which FFTW libraries are installed.
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
require "fftw3"
|
|
46
|
+
|
|
47
|
+
FFTW3.available_precisions
|
|
48
|
+
FFTW3.double_available?
|
|
49
|
+
FFTW3.float_available?
|
|
50
|
+
FFTW3.long_double_available?
|
|
51
|
+
FFTW3.quad_available?
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Notes:
|
|
55
|
+
|
|
56
|
+
- Plan creation is available for any precision whose FFTW library can be loaded.
|
|
57
|
+
- `FFTW3::AlignedMemory#read` and `#write` currently support `:double`, `:float`, and `:long_double`.
|
|
58
|
+
- `:quad` library availability can be detected with `FFTW3.quad_available?`, but high-level buffer read/write helpers are not currently provided for quad buffers.
|
|
59
|
+
|
|
60
|
+
## Core Types
|
|
61
|
+
|
|
62
|
+
### `FFTW3::AlignedMemory`
|
|
63
|
+
|
|
64
|
+
Aligned buffers allocated with FFTW's allocator.
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
require "fftw3"
|
|
68
|
+
|
|
69
|
+
input = FFTW3::AlignedMemory.new(8, type: :complex, precision: :double)
|
|
70
|
+
input.write(Array.new(8) { |i| Complex(i, -i) })
|
|
71
|
+
|
|
72
|
+
input.pointer
|
|
73
|
+
input.read
|
|
74
|
+
input.free!
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Options:
|
|
78
|
+
|
|
79
|
+
- `type:` accepts `:complex` or `:real`
|
|
80
|
+
- `precision:` accepts `:double`, `:float`, `:long_double`, or `:quad`
|
|
81
|
+
|
|
82
|
+
### `FFTW3::Plan`
|
|
83
|
+
|
|
84
|
+
Factory methods return `FFTW3::Plan` instances. A plan can be executed in-place with the buffers it was created with, or with compatible alternate buffers via the new-array execute helpers.
|
|
85
|
+
|
|
86
|
+
### `FFTW3::Threads`
|
|
87
|
+
|
|
88
|
+
Wrapper around FFTW thread initialization, planner thread count, optional planner locking, and the runtime thread callback hook.
|
|
89
|
+
|
|
90
|
+
### `FFTW3::Wisdom`
|
|
91
|
+
|
|
92
|
+
Import and export FFTW wisdom through strings, files, and IO callbacks.
|
|
93
|
+
|
|
94
|
+
### `FFTW3::Utils`
|
|
95
|
+
|
|
96
|
+
Small wrappers for FFTW global utility functions such as `cleanup`, `set_timelimit`, and `alignment_of`.
|
|
97
|
+
|
|
98
|
+
## Quick Start
|
|
99
|
+
|
|
100
|
+
### Basic 1D Complex DFT
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
require "fftw3"
|
|
104
|
+
|
|
105
|
+
n = 8
|
|
106
|
+
input = FFTW3::AlignedMemory.new(n, type: :complex, precision: :double)
|
|
107
|
+
output = FFTW3::AlignedMemory.new(n, type: :complex, precision: :double)
|
|
108
|
+
|
|
109
|
+
data = (0...n).map { |i| Complex(Math.sin(2 * Math::PI * i / n), 0.0) }
|
|
110
|
+
input.write(data)
|
|
111
|
+
|
|
112
|
+
plan = FFTW3::Plan.dft_1d(
|
|
113
|
+
n,
|
|
114
|
+
input,
|
|
115
|
+
output,
|
|
116
|
+
direction: :forward,
|
|
117
|
+
flags: [:estimate]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
plan.execute
|
|
121
|
+
result = output.read
|
|
122
|
+
|
|
123
|
+
puts result.map { |c| "(%.4f, %.4f)" % [c.real, c.imaginary] }
|
|
124
|
+
|
|
125
|
+
plan.destroy!
|
|
126
|
+
input.free!
|
|
127
|
+
output.free!
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Real-to-Complex
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
require "fftw3"
|
|
134
|
+
|
|
135
|
+
n = 16
|
|
136
|
+
input = FFTW3::AlignedMemory.new(n, type: :real)
|
|
137
|
+
output = FFTW3::AlignedMemory.new((n / 2) + 1, type: :complex)
|
|
138
|
+
|
|
139
|
+
input.write(n.times.map { |i| Math.sin(2 * Math::PI * i / n) })
|
|
140
|
+
|
|
141
|
+
plan = FFTW3::Plan.dft_r2c_1d(n, input, output)
|
|
142
|
+
plan.execute
|
|
143
|
+
|
|
144
|
+
result = output.read
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### New-array Execute
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
require "fftw3"
|
|
151
|
+
|
|
152
|
+
n = 8
|
|
153
|
+
input_a = FFTW3::AlignedMemory.new(n, type: :complex)
|
|
154
|
+
output_a = FFTW3::AlignedMemory.new(n, type: :complex)
|
|
155
|
+
input_b = FFTW3::AlignedMemory.new(n, type: :complex)
|
|
156
|
+
output_b = FFTW3::AlignedMemory.new(n, type: :complex)
|
|
157
|
+
|
|
158
|
+
input_a.write(Array.new(n) { |i| Complex(i, 0.0) })
|
|
159
|
+
input_b.write(Array.new(n) { |i| Complex(0.0, i) })
|
|
160
|
+
|
|
161
|
+
plan = FFTW3::Plan.dft_1d(n, input_a, output_a, direction: :forward)
|
|
162
|
+
|
|
163
|
+
# Reuse the same plan with different buffers of the same shape.
|
|
164
|
+
plan.execute_dft(input_b, output_b)
|
|
165
|
+
|
|
166
|
+
plan.destroy!
|
|
167
|
+
input_a.free!
|
|
168
|
+
output_a.free!
|
|
169
|
+
input_b.free!
|
|
170
|
+
output_b.free!
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Threading
|
|
174
|
+
|
|
175
|
+
### Planner Thread Count
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
require "fftw3"
|
|
179
|
+
|
|
180
|
+
n = 1024
|
|
181
|
+
input = FFTW3::AlignedMemory.new(n, type: :complex)
|
|
182
|
+
output = FFTW3::AlignedMemory.new(n, type: :complex)
|
|
183
|
+
|
|
184
|
+
FFTW3::Threads.init
|
|
185
|
+
FFTW3::Threads.plan_with_nthreads(4)
|
|
186
|
+
|
|
187
|
+
plan = FFTW3::Plan.dft_1d(n, input, output, direction: :forward, flags: [:measure])
|
|
188
|
+
plan.execute
|
|
189
|
+
|
|
190
|
+
FFTW3::Threads.planner_nthreads
|
|
191
|
+
plan.destroy!
|
|
192
|
+
input.free!
|
|
193
|
+
output.free!
|
|
194
|
+
FFTW3::Threads.cleanup
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Planner Locking
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
require "fftw3"
|
|
201
|
+
|
|
202
|
+
FFTW3::Threads.init
|
|
203
|
+
FFTW3::Threads.make_planner_thread_safe
|
|
204
|
+
FFTW3::Threads.cleanup
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`make_planner_thread_safe` returns `nil` when the linked FFTW build does not expose that function.
|
|
208
|
+
|
|
209
|
+
### Custom Thread Backend Callback
|
|
210
|
+
|
|
211
|
+
`threads_set_callback` is optional in FFTW. Check support before using it, and set it before creating plans.
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
require "fftw3"
|
|
215
|
+
|
|
216
|
+
if FFTW3::Threads.set_callback_supported?
|
|
217
|
+
marker = FFI::MemoryPointer.new(:int)
|
|
218
|
+
marker.write_int(7)
|
|
219
|
+
|
|
220
|
+
FFTW3::Threads.set_callback(data: marker) do |work, jobdata, elsize, njobs, data_ptr|
|
|
221
|
+
njobs.times do |index|
|
|
222
|
+
work.call(jobdata + (elsize * index))
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Reset to the default FFTW backend.
|
|
227
|
+
FFTW3::Threads.set_callback(nil)
|
|
228
|
+
end
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Wisdom
|
|
232
|
+
|
|
233
|
+
### String and File APIs
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
require "fftw3"
|
|
237
|
+
|
|
238
|
+
wisdom = FFTW3::Wisdom.export_to_string
|
|
239
|
+
FFTW3::Wisdom.import_from_string(wisdom)
|
|
240
|
+
|
|
241
|
+
FFTW3::Wisdom.export_to_file("wisdom.fftw")
|
|
242
|
+
FFTW3::Wisdom.import_from_file("wisdom.fftw")
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### IO Callback APIs
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
require "fftw3"
|
|
249
|
+
require "stringio"
|
|
250
|
+
|
|
251
|
+
io = StringIO.new
|
|
252
|
+
FFTW3::Wisdom.export_to_io(io)
|
|
253
|
+
FFTW3::Wisdom.import_from_io(StringIO.new(io.string))
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### System Wisdom
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
require "fftw3"
|
|
260
|
+
|
|
261
|
+
FFTW3::Wisdom.import_system
|
|
262
|
+
FFTW3::Wisdom.forget!
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Runtime Metadata and Utilities
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
require "fftw3"
|
|
269
|
+
|
|
270
|
+
input = FFTW3::AlignedMemory.new(8, type: :complex)
|
|
271
|
+
|
|
272
|
+
FFTW3.version
|
|
273
|
+
FFTW3.compiler
|
|
274
|
+
FFTW3.codelet_optimizations
|
|
275
|
+
|
|
276
|
+
FFTW3::Utils.set_timelimit(0.25)
|
|
277
|
+
FFTW3::Utils.alignment_of(input.pointer)
|
|
278
|
+
FFTW3::Utils.cleanup
|
|
279
|
+
input.free!
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## API Reference
|
|
283
|
+
|
|
284
|
+
### Top-level `FFTW3`
|
|
285
|
+
|
|
286
|
+
| Method | Description |
|
|
287
|
+
|--------|-------------|
|
|
288
|
+
| `available_precisions` | Returns the precisions whose FFTW libraries can be loaded |
|
|
289
|
+
| `double_available?` / `float_available?` / `long_double_available?` / `quad_available?` | Per-precision availability checks |
|
|
290
|
+
| `version` | Linked FFTW version string |
|
|
291
|
+
| `compiler` | Linked FFTW compiler string |
|
|
292
|
+
| `codelet_optimizations` | Linked FFTW codelet optimization string |
|
|
293
|
+
|
|
294
|
+
### `FFTW3::Plan` Factory Methods
|
|
295
|
+
|
|
296
|
+
| Method group | Methods |
|
|
297
|
+
|-------------|---------|
|
|
298
|
+
| Complex DFT | `dft_1d`, `dft_2d`, `dft_3d`, `dft` |
|
|
299
|
+
| Real-to-complex | `dft_r2c_1d`, `dft_r2c_2d`, `dft_r2c_3d`, `dft_r2c` |
|
|
300
|
+
| Complex-to-real | `dft_c2r_1d`, `dft_c2r_2d`, `dft_c2r_3d`, `dft_c2r` |
|
|
301
|
+
| Real-to-real | `r2r_1d`, `r2r_2d`, `r2r_3d`, `r2r` |
|
|
302
|
+
| Advanced / batched | `many_dft`, `many_dft_r2c`, `many_dft_c2r`, `many_r2r` |
|
|
303
|
+
| Guru | `guru_dft`, `guru_split_dft`, `guru_dft_r2c`, `guru_split_dft_r2c`, `guru_dft_c2r`, `guru_split_dft_c2r`, `guru_r2r` |
|
|
304
|
+
| Guru64 | `guru64_dft`, `guru64_split_dft`, `guru64_dft_r2c`, `guru64_split_dft_r2c`, `guru64_dft_c2r`, `guru64_split_dft_c2r`, `guru64_r2r` |
|
|
305
|
+
|
|
306
|
+
Notes:
|
|
307
|
+
|
|
308
|
+
- `dft_*` and `many_dft` use `direction:`.
|
|
309
|
+
- `guru_dft` and `guru64_dft` use `sign:`.
|
|
310
|
+
- Guru and guru64 dimension arrays use hashes shaped like `{ n:, is:, os: }`.
|
|
311
|
+
|
|
312
|
+
### `FFTW3::Plan` Instance Methods
|
|
313
|
+
|
|
314
|
+
| Method | Description |
|
|
315
|
+
|--------|-------------|
|
|
316
|
+
| `execute` | Execute with the buffers used to create the plan |
|
|
317
|
+
| `execute_dft` | New-array execute for complex DFT |
|
|
318
|
+
| `execute_dft_r2c` | New-array execute for real-to-complex DFT |
|
|
319
|
+
| `execute_dft_c2r` | New-array execute for complex-to-real DFT |
|
|
320
|
+
| `execute_r2r` | New-array execute for real-to-real transforms |
|
|
321
|
+
| `execute_split_dft` | New-array execute for split-complex guru DFT |
|
|
322
|
+
| `execute_split_dft_r2c` | New-array execute for split real-to-complex guru DFT |
|
|
323
|
+
| `execute_split_dft_c2r` | New-array execute for split complex-to-real guru DFT |
|
|
324
|
+
| `flops` | Returns `{ add:, mul:, fma: }` |
|
|
325
|
+
| `cost` | FFTW plan cost |
|
|
326
|
+
| `estimate_cost` | FFTW estimated plan cost |
|
|
327
|
+
| `to_s` / `description` | String representation of the plan |
|
|
328
|
+
| `print(io = $stdout)` | Write the plan description to an IO object |
|
|
329
|
+
| `destroy!` | Destroy the underlying FFTW plan |
|
|
330
|
+
|
|
331
|
+
### `FFTW3::Threads`
|
|
332
|
+
|
|
333
|
+
| Method | Description |
|
|
334
|
+
|--------|-------------|
|
|
335
|
+
| `init` | Initialize FFTW thread support |
|
|
336
|
+
| `plan_with_nthreads(n)` | Set planner and execution thread count |
|
|
337
|
+
| `cleanup` | Release FFTW thread state |
|
|
338
|
+
| `planner_nthreads` | Read configured thread count when supported |
|
|
339
|
+
| `make_planner_thread_safe` | Enable FFTW's optional planner lock when supported |
|
|
340
|
+
| `set_callback_supported?` | Check whether `threads_set_callback` is available |
|
|
341
|
+
| `set_callback(callback = nil, data: nil, &block)` | Install or clear FFTW's runtime threading callback |
|
|
342
|
+
|
|
343
|
+
### `FFTW3::Wisdom`
|
|
344
|
+
|
|
345
|
+
| Method | Description |
|
|
346
|
+
|--------|-------------|
|
|
347
|
+
| `export_to_string` / `import_from_string` | Wisdom through Ruby strings |
|
|
348
|
+
| `export_to_file` / `import_from_file` | Wisdom through filesystem paths |
|
|
349
|
+
| `export_to_io` / `import_from_io` | Wisdom through Ruby IO callbacks |
|
|
350
|
+
| `import_system` | Best-effort import from the system wisdom store |
|
|
351
|
+
| `forget!` | Clear loaded wisdom |
|
|
352
|
+
|
|
353
|
+
### `FFTW3::Utils`
|
|
354
|
+
|
|
355
|
+
| Method | Description |
|
|
356
|
+
|--------|-------------|
|
|
357
|
+
| `cleanup` | Call FFTW global cleanup |
|
|
358
|
+
| `set_timelimit(seconds)` | Set the FFTW planner time limit |
|
|
359
|
+
| `alignment_of(pointer)` | Query FFTW's alignment classification for a pointer |
|
|
360
|
+
|
|
361
|
+
### Constants
|
|
362
|
+
|
|
363
|
+
Planner flags:
|
|
364
|
+
|
|
365
|
+
- `FFTW3::ESTIMATE`
|
|
366
|
+
- `FFTW3::MEASURE`
|
|
367
|
+
- `FFTW3::PATIENT`
|
|
368
|
+
- `FFTW3::EXHAUSTIVE`
|
|
369
|
+
- `FFTW3::PRESERVE_INPUT`
|
|
370
|
+
- `FFTW3::DESTROY_INPUT`
|
|
371
|
+
- `FFTW3::WISDOM_ONLY`
|
|
372
|
+
- Additional exported flags such as `FFTW3::ESTIMATE_PATIENT`, `FFTW3::ALLOW_PRUNING`, and `FFTW3::NO_SIMD`
|
|
373
|
+
|
|
374
|
+
Direction constants:
|
|
375
|
+
|
|
376
|
+
- `FFTW3::FORWARD`
|
|
377
|
+
- `FFTW3::BACKWARD`
|
|
378
|
+
- `:forward` and `:backward` are also accepted where direction symbols are supported
|
|
379
|
+
|
|
380
|
+
R2R kinds:
|
|
381
|
+
|
|
382
|
+
- `FFTW3::R2HC`
|
|
383
|
+
- `FFTW3::HC2R`
|
|
384
|
+
- `FFTW3::DHT`
|
|
385
|
+
- `FFTW3::REDFT00`, `REDFT01`, `REDFT10`, `REDFT11`
|
|
386
|
+
- `FFTW3::RODFT00`, `RODFT01`, `RODFT10`, `RODFT11`
|
|
387
|
+
|
|
388
|
+
## Development
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
bundle install
|
|
392
|
+
bundle exec rake spec
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## License
|
|
396
|
+
|
|
397
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
398
|
+
|
|
399
|
+
FFTW3 itself is licensed under GPLv2+. Using this gem requires an installed FFTW3 library.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bigdecimal"
|
|
4
|
+
|
|
5
|
+
module FFTW3
|
|
6
|
+
class AlignedMemory
|
|
7
|
+
attr_reader :pointer, :size, :precision, :type
|
|
8
|
+
|
|
9
|
+
REAL_SIZES = {
|
|
10
|
+
double: 8, float: 4, long_double: 16, quad: 16
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
COMPLEX_SIZES = REAL_SIZES.transform_values { |v| v * 2 }.freeze
|
|
14
|
+
|
|
15
|
+
def initialize(count, type: :complex, precision: :double)
|
|
16
|
+
@precision = precision
|
|
17
|
+
@type = type
|
|
18
|
+
@count = count
|
|
19
|
+
|
|
20
|
+
sizes = (type == :complex) ? COMPLEX_SIZES : REAL_SIZES
|
|
21
|
+
@element_size = sizes.fetch(precision)
|
|
22
|
+
@size = count * @element_size
|
|
23
|
+
|
|
24
|
+
ffi = FFTW3::FFI.module_for(precision)
|
|
25
|
+
pfx = FFTW3::FFI.prefix_for(precision)
|
|
26
|
+
|
|
27
|
+
@pointer = case type
|
|
28
|
+
when :complex
|
|
29
|
+
ffi.send(:"#{pfx}alloc_complex", count)
|
|
30
|
+
when :real
|
|
31
|
+
ffi.send(:"#{pfx}alloc_real", count)
|
|
32
|
+
else
|
|
33
|
+
ffi.send(:"#{pfx}malloc", @size)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
raise FFTW3::Error::PlanError, "fftw_malloc returned NULL" if @pointer.null?
|
|
37
|
+
|
|
38
|
+
@freed_flag = [false]
|
|
39
|
+
@free_func = ffi.method(:"#{pfx}free")
|
|
40
|
+
ObjectSpace.define_finalizer(self, self.class.release(@pointer, @free_func, @freed_flag))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def write(data)
|
|
44
|
+
case @type
|
|
45
|
+
when :real then write_real(data)
|
|
46
|
+
when :complex then write_complex(data)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def read
|
|
51
|
+
case @type
|
|
52
|
+
when :real then read_real
|
|
53
|
+
when :complex then read_complex
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def free!
|
|
58
|
+
return if @freed_flag[0]
|
|
59
|
+
|
|
60
|
+
@free_func.call(@pointer)
|
|
61
|
+
@freed_flag[0] = true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def write_real(data)
|
|
67
|
+
write_numeric_values(data)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def read_real
|
|
71
|
+
read_numeric_values(@count)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def write_complex(data)
|
|
75
|
+
flat = data.flat_map { |c|
|
|
76
|
+
c.is_a?(Complex) ? [c.real, c.imaginary] : c
|
|
77
|
+
}
|
|
78
|
+
write_numeric_values(flat)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def read_complex
|
|
82
|
+
flat = read_numeric_values(@count * 2)
|
|
83
|
+
flat.each_slice(2).map { |re, im| Complex(re, im) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def write_numeric_values(data)
|
|
87
|
+
case @precision
|
|
88
|
+
when :double
|
|
89
|
+
@pointer.put_array_of_double(0, data)
|
|
90
|
+
when :float
|
|
91
|
+
@pointer.put_array_of_float(0, data)
|
|
92
|
+
when :long_double
|
|
93
|
+
write_generic_values(data, type: :long_double)
|
|
94
|
+
else
|
|
95
|
+
raise_unsupported_precision!(:write)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def read_numeric_values(count)
|
|
100
|
+
case @precision
|
|
101
|
+
when :double
|
|
102
|
+
@pointer.get_array_of_double(0, count)
|
|
103
|
+
when :float
|
|
104
|
+
@pointer.get_array_of_float(0, count)
|
|
105
|
+
when :long_double
|
|
106
|
+
read_generic_values(count, type: :long_double)
|
|
107
|
+
else
|
|
108
|
+
raise_unsupported_precision!(:read)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def write_generic_values(data, type:)
|
|
113
|
+
value_size = ::FFI.type_size(type)
|
|
114
|
+
data.each_with_index do |value, index|
|
|
115
|
+
@pointer.put(type, index * value_size, value)
|
|
116
|
+
end
|
|
117
|
+
rescue TypeError
|
|
118
|
+
raise_unsupported_precision!(:write)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def read_generic_values(count, type:)
|
|
122
|
+
value_size = ::FFI.type_size(type)
|
|
123
|
+
Array.new(count) do |index|
|
|
124
|
+
@pointer.get(type, index * value_size)
|
|
125
|
+
end
|
|
126
|
+
rescue LoadError, TypeError
|
|
127
|
+
raise_unsupported_precision!(:read)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def raise_unsupported_precision!(action)
|
|
131
|
+
raise FFTW3::Error::InvalidArgument,
|
|
132
|
+
"FFI does not support #{action}ing #{@precision} buffers on this platform"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.release(ptr, free_func, freed_flag)
|
|
136
|
+
proc {
|
|
137
|
+
unless freed_flag[0] || ptr.null?
|
|
138
|
+
free_func.call(ptr)
|
|
139
|
+
freed_flag[0] = true
|
|
140
|
+
end
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FFTW3
|
|
4
|
+
NO_TIMELIMIT = -1.0
|
|
5
|
+
|
|
6
|
+
# Planner flags
|
|
7
|
+
MEASURE = 0
|
|
8
|
+
DESTROY_INPUT = 1 << 0
|
|
9
|
+
UNALIGNED = 1 << 1
|
|
10
|
+
CONSERVE_MEMORY = 1 << 2
|
|
11
|
+
EXHAUSTIVE = 1 << 3
|
|
12
|
+
PRESERVE_INPUT = 1 << 4
|
|
13
|
+
PATIENT = 1 << 5
|
|
14
|
+
ESTIMATE = 1 << 6
|
|
15
|
+
ESTIMATE_PATIENT = 1 << 7
|
|
16
|
+
BELIEVE_PCOST = 1 << 8
|
|
17
|
+
NO_DFT_R2HC = 1 << 9
|
|
18
|
+
NO_NONTHREADED = 1 << 10
|
|
19
|
+
NO_BUFFERING = 1 << 11
|
|
20
|
+
NO_INDIRECT_OP = 1 << 12
|
|
21
|
+
ALLOW_LARGE_GENERIC = 1 << 13
|
|
22
|
+
NO_RANK_SPLITS = 1 << 14
|
|
23
|
+
NO_VRANK_SPLITS = 1 << 15
|
|
24
|
+
NO_VRECURSE = 1 << 16
|
|
25
|
+
NO_SIMD = 1 << 17
|
|
26
|
+
NO_SLOW = 1 << 18
|
|
27
|
+
NO_FIXED_RADIX_LARGE_N = 1 << 19
|
|
28
|
+
ALLOW_PRUNING = 1 << 20
|
|
29
|
+
WISDOM_ONLY = 1 << 21
|
|
30
|
+
|
|
31
|
+
# Direction
|
|
32
|
+
FORWARD = -1
|
|
33
|
+
BACKWARD = 1
|
|
34
|
+
|
|
35
|
+
# R2R kinds
|
|
36
|
+
R2HC = 0
|
|
37
|
+
HC2R = 1
|
|
38
|
+
DHT = 2
|
|
39
|
+
REDFT00 = 3
|
|
40
|
+
REDFT01 = 4
|
|
41
|
+
REDFT10 = 5
|
|
42
|
+
REDFT11 = 6
|
|
43
|
+
RODFT00 = 7
|
|
44
|
+
RODFT01 = 8
|
|
45
|
+
RODFT10 = 9
|
|
46
|
+
RODFT11 = 10
|
|
47
|
+
end
|