Skip to content

Commit 035010a

Browse files
committed
Support long double buffer IO
1 parent 53a8b36 commit 035010a

4 files changed

Lines changed: 123 additions & 18 deletions

File tree

fftw3.gemspec

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
88
spec.authors = ["Yudai Takada"]
99
spec.email = ["t.yudai92@gmail.com"]
1010

11-
spec.summary = "Complete Ruby FFI bindings for FFTW3"
12-
spec.description = "Full-featured Ruby bindings for libfftw3 via FFI. " \
11+
spec.summary = "Comprehensive Ruby FFI bindings for FFTW3"
12+
spec.description = "Comprehensive Ruby bindings for libfftw3 via FFI. " \
1313
"Supports double/float/long_double/quad precision, " \
1414
"Basic/Advanced/Guru/Guru64 interfaces, Wisdom, and threading."
1515
spec.homepage = "https://github.com/ydah/fftw3"
@@ -31,5 +31,6 @@ Gem::Specification.new do |spec|
3131
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
3232
spec.require_paths = ["lib"]
3333

34+
spec.add_dependency "bigdecimal"
3435
spec.add_dependency "ffi", "~> 1.15"
3536
end

lib/fftw3/aligned_memory.rb

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "bigdecimal"
4+
35
module FFTW3
46
class AlignedMemory
57
attr_reader :pointer, :size, :precision, :type
@@ -62,37 +64,74 @@ def free!
6264
private
6365

6466
def write_real(data)
65-
case @precision
66-
when :double then @pointer.put_array_of_double(0, data)
67-
when :float then @pointer.put_array_of_float(0, data)
68-
end
67+
write_numeric_values(data)
6968
end
7069

7170
def read_real
72-
case @precision
73-
when :double then @pointer.get_array_of_double(0, @count)
74-
when :float then @pointer.get_array_of_float(0, @count)
75-
end
71+
read_numeric_values(@count)
7672
end
7773

7874
def write_complex(data)
7975
flat = data.flat_map { |c|
8076
c.is_a?(Complex) ? [c.real, c.imaginary] : c
8177
}
82-
case @precision
83-
when :double then @pointer.put_array_of_double(0, flat)
84-
when :float then @pointer.put_array_of_float(0, flat)
85-
end
78+
write_numeric_values(flat)
8679
end
8780

8881
def read_complex
89-
flat = case @precision
90-
when :double then @pointer.get_array_of_double(0, @count * 2)
91-
when :float then @pointer.get_array_of_float(0, @count * 2)
92-
end
82+
flat = read_numeric_values(@count * 2)
9383
flat.each_slice(2).map { |re, im| Complex(re, im) }
9484
end
9585

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+
96135
def self.release(ptr, free_func, freed_flag)
97136
proc {
98137
unless freed_flag[0] || ptr.null?

spec/fftw3/aligned_memory_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "bigdecimal"
4+
35
RSpec.describe FFTW3::AlignedMemory do
46
describe "real memory" do
57
it "allocates and reads/writes double real data" do
@@ -25,6 +27,19 @@
2527
end
2628
mem.free!
2729
end
30+
31+
it "allocates and reads/writes long double real data", skip: !FFTW3.long_double_available? do
32+
n = 8
33+
mem = FFTW3::AlignedMemory.new(n, type: :real, precision: :long_double)
34+
data = n.times.map { |i| BigDecimal((i.to_f / 3).to_s) }
35+
mem.write(data)
36+
37+
result = mem.read
38+
result.each_with_index do |value, index|
39+
expect(value.to_f).to be_within(1e-15).of(data[index].to_f)
40+
end
41+
mem.free!
42+
end
2843
end
2944

3045
describe "complex memory" do
@@ -52,6 +67,24 @@
5267
end
5368
mem.free!
5469
end
70+
71+
it "allocates and reads/writes long double complex data", skip: !FFTW3.long_double_available? do
72+
n = 4
73+
mem = FFTW3::AlignedMemory.new(n, type: :complex, precision: :long_double)
74+
data = [
75+
Complex(BigDecimal("1.0"), BigDecimal("2.0")),
76+
Complex(BigDecimal("3.0"), BigDecimal("4.0")),
77+
Complex(BigDecimal("5.0"), BigDecimal("6.0")),
78+
Complex(BigDecimal("7.0"), BigDecimal("8.0"))
79+
]
80+
mem.write(data)
81+
82+
result = mem.read
83+
expect(result.map { |value| [value.real.to_s("F"), value.imaginary.to_s("F")] }).to eq(
84+
data.map { |value| [value.real.to_s("F"), value.imaginary.to_s("F")] }
85+
)
86+
mem.free!
87+
end
5588
end
5689

5790
describe "#free!" do
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# frozen_string_literal: true
2+
3+
require "bigdecimal"
4+
5+
RSpec.describe "Long double precision", skip: !FFTW3.long_double_available? do
6+
it "performs DFT roundtrip with long double precision" do
7+
n = 16
8+
input = FFTW3::AlignedMemory.new(n, type: :complex, precision: :long_double)
9+
freq_domain = FFTW3::AlignedMemory.new(n, type: :complex, precision: :long_double)
10+
output = FFTW3::AlignedMemory.new(n, type: :complex, precision: :long_double)
11+
12+
original = n.times.map do |i|
13+
real = BigDecimal(Math.sin(2 * Math::PI * i / n).to_s)
14+
Complex(real, BigDecimal("0"))
15+
end
16+
input.write(original)
17+
18+
forward = FFTW3::Plan.dft_1d(n, input, freq_domain, direction: :forward, precision: :long_double)
19+
forward.execute
20+
forward.destroy!
21+
22+
backward = FFTW3::Plan.dft_1d(n, freq_domain, output, direction: :backward, precision: :long_double)
23+
backward.execute
24+
backward.destroy!
25+
26+
result = output.read
27+
result.each_with_index do |value, index|
28+
expect((value.real / n).to_f).to be_within(1e-12).of(original[index].real.to_f)
29+
expect((value.imaginary / n).to_f).to be_within(1e-12).of(original[index].imaginary.to_f)
30+
end
31+
end
32+
end

0 commit comments

Comments
 (0)