convolver 1.0.1 → 3.0.1

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.
@@ -0,0 +1,167 @@
1
+ // ext/convolver/correlate_raw.c
2
+
3
+ #include "correlate_raw.h"
4
+
5
+ static inline size_t checked_add( size_t left, size_t right, const char *description ) {
6
+ if ( right > SIZE_MAX - left ) {
7
+ rb_raise( rb_eRangeError, "%s exceeds native implementation limit", description );
8
+ }
9
+ return left + right;
10
+ }
11
+
12
+ static inline size_t checked_multiply( size_t left, size_t right, const char *description ) {
13
+ if ( left != 0 && right > SIZE_MAX / left ) {
14
+ rb_raise( rb_eRangeError, "%s exceeds native implementation limit", description );
15
+ }
16
+ return left * right;
17
+ }
18
+
19
+ static inline size_t size_from_shape( int rank, const size_t *shape, const char *description ) {
20
+ size_t size = 1;
21
+ int i;
22
+ for ( i = 0; i < rank; i++ ) {
23
+ size = checked_multiply( size, shape[i], description );
24
+ }
25
+ return size;
26
+ }
27
+
28
+ // Sets reverse indices
29
+ static inline void corner_reset( int rank, const size_t *shape, size_t *rev_indices ) {
30
+ int i;
31
+ for ( i = 0; i < rank; i++ ) { rev_indices[i] = shape[i] - 1; }
32
+ }
33
+
34
+ // Counts indices down, returns number of ranks that reset
35
+ static inline int corner_dec( int rank, const size_t *shape, size_t *rev_indices ) {
36
+ int i = 0;
37
+ (void) rank;
38
+ while ( ! rev_indices[i]-- ) {
39
+ rev_indices[i] = shape[i] - 1;
40
+ i++;
41
+ }
42
+ return i;
43
+ }
44
+
45
+ // Generates co-increment steps by rank boundaries crossed, for the outer position as inner position is incremented by 1
46
+ static inline void calc_co_increment(
47
+ int rank, const size_t *outer_shape, const size_t *inner_shape, size_t *co_increment ) {
48
+ size_t factor = 1;
49
+ int i;
50
+ co_increment[0] = 1; // co-increment is always 1 in lowest rank
51
+ for ( i = 0; i < rank; i++ ) {
52
+ size_t skipped = checked_multiply( factor, outer_shape[i] - inner_shape[i], "array offset" );
53
+ co_increment[i+1] = checked_add( co_increment[i], skipped, "array offset" );
54
+ factor = checked_multiply( factor, outer_shape[i], "array size" );
55
+ }
56
+ }
57
+
58
+ static inline size_t maximum_offset(
59
+ int rank, const size_t *outer_shape, const size_t *inner_shape, const char *description ) {
60
+ size_t factor = 1;
61
+ size_t offset = 0;
62
+ int i;
63
+ for ( i = 0; i < rank; i++ ) {
64
+ size_t dimension_offset = checked_multiply( factor, inner_shape[i] - 1, description );
65
+ offset = checked_add( offset, dimension_offset, description );
66
+ factor = checked_multiply( factor, outer_shape[i], "array size" );
67
+ }
68
+ return offset;
69
+ }
70
+
71
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
72
+ //
73
+ // Correlate
74
+ //
75
+
76
+ void correlate_raw(
77
+ int in_rank, const size_t *in_shape, const float *in_ptr,
78
+ int kernel_rank, const size_t *kernel_shape, const float *kernel_ptr,
79
+ int out_rank, const size_t *out_shape, float *out_ptr ) {
80
+ size_t i, j, input_size, kernel_size, kernel_aligned, out_size, offset;
81
+ size_t maximum_input_offset;
82
+ size_t out_co_incr[LARGEST_RANK + 1], kernel_co_incr[LARGEST_RANK + 1];
83
+ size_t ker_q[LARGEST_RANK], out_q[LARGEST_RANK];
84
+ size_t *kernel_co_incr_cache;
85
+ VALUE cache_storage = 0;
86
+
87
+ kernel_size = size_from_shape( kernel_rank, kernel_shape, "kernel size" );
88
+ kernel_aligned = kernel_size - kernel_size % 4;
89
+ out_size = size_from_shape( out_rank, out_shape, "output size" );
90
+ input_size = size_from_shape( in_rank, in_shape, "input size" );
91
+
92
+ calc_co_increment( in_rank, in_shape, out_shape, out_co_incr );
93
+ calc_co_increment( in_rank, in_shape, kernel_shape, kernel_co_incr );
94
+ maximum_input_offset = checked_add(
95
+ maximum_offset( in_rank, in_shape, out_shape, "input offset" ),
96
+ maximum_offset( in_rank, in_shape, kernel_shape, "input offset" ),
97
+ "input offset"
98
+ );
99
+ if ( maximum_input_offset >= input_size ) {
100
+ rb_raise( rb_eRangeError, "input offset exceeds native implementation limit" );
101
+ }
102
+
103
+ kernel_co_incr_cache = RB_ALLOCV_N( size_t, cache_storage, kernel_size );
104
+ kernel_co_incr_cache[0] = 0;
105
+
106
+ corner_reset( kernel_rank, kernel_shape, ker_q );
107
+ for ( i = 1; i < kernel_size; i++ ) {
108
+ kernel_co_incr_cache[i] = checked_add(
109
+ kernel_co_incr_cache[i-1],
110
+ kernel_co_incr[ corner_dec( kernel_rank, kernel_shape, ker_q ) ],
111
+ "kernel offset"
112
+ );
113
+ }
114
+
115
+ offset = 0;
116
+ corner_reset( out_rank, out_shape, out_q );
117
+
118
+ // Main correlation loop
119
+ for ( i = 0; i < out_size; i++ ) {
120
+ #if CONVOLVER_USE_SSE
121
+ __m128 simd_x, simd_y, simd_t;
122
+ float v[4];
123
+ simd_t = _mm_setzero_ps();
124
+ #endif
125
+ float t = 0.0;
126
+
127
+ #if CONVOLVER_USE_SSE
128
+ // Contiguous kernel views need not start on a 16-byte boundary.
129
+ for ( j = 0; j < kernel_aligned; j +=4 ) {
130
+ simd_x = _mm_loadu_ps( kernel_ptr + j );
131
+ // Yes the backwards alignment is correct
132
+ simd_y = _mm_set_ps( in_ptr[ offset + kernel_co_incr_cache[j+3] ], in_ptr[ offset + kernel_co_incr_cache[j+2] ],
133
+ in_ptr[ offset + kernel_co_incr_cache[j+1] ], in_ptr[ offset + kernel_co_incr_cache[j] ] );
134
+ simd_x = _mm_mul_ps( simd_x, simd_y );
135
+ simd_t = _mm_add_ps( simd_x, simd_t );
136
+ }
137
+ _mm_store_ps( v, simd_t );
138
+ #else
139
+ // SSE is unavailable on architectures such as Apple Silicon.
140
+ // Use the same calculation without x86-specific intrinsics.
141
+ for ( j = 0; j < kernel_aligned; j++ ) {
142
+ t += in_ptr[ offset + kernel_co_incr_cache[j] ] * kernel_ptr[j];
143
+ }
144
+ #endif
145
+
146
+ // Complete any remaining 1,2 or 3 items one at a time
147
+ for ( j = kernel_aligned; j < kernel_size; j++ ) {
148
+ t += in_ptr[ offset + kernel_co_incr_cache[j] ] * kernel_ptr[ j ];
149
+ }
150
+
151
+ #if CONVOLVER_USE_SSE
152
+ out_ptr[i] = v[0] + v[1] + v[2] + v[3] + t;
153
+ #else
154
+ out_ptr[i] = t;
155
+ #endif
156
+
157
+ if ( i + 1 < out_size ) {
158
+ offset = checked_add(
159
+ offset,
160
+ out_co_incr[ corner_dec( out_rank, out_shape, out_q ) ],
161
+ "input offset"
162
+ );
163
+ }
164
+ }
165
+
166
+ RB_ALLOCV_END( cache_storage );
167
+ }
@@ -0,0 +1,28 @@
1
+ // ext/convolver/correlate_raw.h
2
+
3
+ ////////////////////////////////////////////////////////////////////////////////////////////////
4
+ //
5
+ // Declarations of narray helper functions
6
+ //
7
+
8
+ #ifndef CORRELATE_RAW_H
9
+ #define CORRELATE_RAW_H
10
+
11
+ #ifndef CONVOLVER_RAW_CONFIG
12
+ #define CONVOLVER_RAW_CONFIG
13
+ #include <ruby.h>
14
+ #if defined(__SSE__)
15
+ #include <xmmintrin.h>
16
+ #define CONVOLVER_USE_SSE 1
17
+ #else
18
+ #define CONVOLVER_USE_SSE 0
19
+ #endif
20
+ #define LARGEST_RANK 16
21
+ #endif
22
+
23
+ void correlate_raw(
24
+ int in_rank, const size_t *in_shape, const float *in_ptr,
25
+ int kernel_rank, const size_t *kernel_shape, const float *kernel_ptr,
26
+ int out_rank, const size_t *out_shape, float *out_ptr );
27
+
28
+ #endif
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Convolver
4
+ # Checks individual allocations against native size and tagged-stride limits.
5
+ # @private
6
+ class BufferLimits
7
+ SIZE_MAX = (1 << ([0].pack('J').bytesize * 8)) - 1
8
+ # Numo tags signed strides with a left shift: floor(SSIZE_MAX / 2).
9
+ BYTE_MAX = SIZE_MAX / 4
10
+ INT_MAX = (1 << (([0].pack('i').bytesize * 8) - 1)) - 1
11
+ INDEX_BYTES = [0].pack('J').bytesize
12
+
13
+ attr_reader :size_max, :byte_max, :int_max
14
+
15
+ def initialize(size_max: SIZE_MAX, byte_max: BYTE_MAX, int_max: INT_MAX)
16
+ @size_max = size_max
17
+ @byte_max = byte_max
18
+ @int_max = int_max
19
+ end
20
+
21
+ def buffer!(count, width, description, error: RangeError)
22
+ return if count <= byte_max / width
23
+
24
+ raise error, "#{description} exceeds native byte/stride limit"
25
+ end
26
+
27
+ def product!(shape, description, error: RangeError)
28
+ shape.reduce(1) do |product, size|
29
+ raise error, "#{description} exceeds native element limit" if size > size_max / product
30
+
31
+ product * size
32
+ end
33
+ end
34
+
35
+ NATIVE = new.freeze
36
+ end
37
+
38
+ private_constant :BufferLimits
39
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'convolver/fft_limits'
4
+
5
+ module Convolver
6
+ # Preflights casts, extension, indexing and direct-path cache allocations.
7
+ # @private
8
+ class BufferPreparation
9
+ def initialize(plan, signal_shape, kernel_shape, limits: BufferLimits::NATIVE)
10
+ @plan = plan
11
+ @signal_size = limits.product!(signal_shape, 'signal size')
12
+ @kernel_size = limits.product!(kernel_shape, 'kernel size')
13
+ @limits = limits
14
+ @scalar = signal_shape.empty?
15
+ end
16
+
17
+ def basic!
18
+ common!
19
+ extension!
20
+ limits.buffer!(kernel_size, BufferLimits::INDEX_BYTES, 'direct kernel offset cache')
21
+ end
22
+
23
+ def fft!
24
+ common!
25
+ return if @scalar
26
+ return extension! unless plan.wrap?
27
+
28
+ limits.buffer!(signal_size, 8, 'circular signal preparation', error: FftUnavailable)
29
+ limits.buffer!(kernel_size, 8, 'circular kernel preparation', error: FftUnavailable)
30
+ limits.buffer!(kernel_size, BufferLimits::INDEX_BYTES, 'circular kernel index buffer', error: FftUnavailable)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :plan, :signal_size, :kernel_size, :limits
36
+
37
+ def common!
38
+ limits.buffer!(plan.result_size, 4, 'result buffer')
39
+ limits.buffer!(signal_size, 4, 'signal conversion buffer')
40
+ limits.buffer!(kernel_size, 4, 'kernel conversion buffer')
41
+ end
42
+
43
+ def extension!
44
+ return if plan.valid?
45
+
46
+ limits.buffer!(plan.extended_size, 4, 'extended signal buffer')
47
+ return if plan.boundary == :constant
48
+
49
+ plan.extended_shape.each do |length|
50
+ limits.buffer!(length, BufferLimits::INDEX_BYTES, 'boundary index buffer')
51
+ end
52
+ end
53
+ end
54
+
55
+ private_constant :BufferPreparation
56
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Convolver
4
+ # Calculates same-sized periodic operations through PocketFFT transforms.
5
+ # @private
6
+ class CircularFftOperation
7
+ def initialize(operation, signal, kernel, plan)
8
+ @real_axis = plan.fft_buffers.real_axis
9
+ @operation = operation
10
+ @signal = Numo::DFloat.cast(signal)
11
+ @kernel = fold_kernel(Numo::DFloat.cast(kernel), signal.shape, plan.anchors)
12
+ end
13
+
14
+ def call
15
+ return complex_product unless real_axis
16
+
17
+ real_product(real_axis)
18
+ end
19
+
20
+ def self.real_axis(shape)
21
+ return nil if shape.empty?
22
+
23
+ final_axis = shape.length - 1
24
+ return final_axis if shape[final_axis].even?
25
+
26
+ shape.each_index.select { |axis| shape[axis].even? }.max_by { |axis| shape[axis] }
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :operation, :signal, :kernel, :real_axis
32
+
33
+ def real_product(real_axis)
34
+ final_axis = signal.ndim - 1
35
+ prepared_signal = prepare_real_operand(signal, real_axis, final_axis)
36
+ prepared_kernel = prepare_real_operand(kernel, real_axis, final_axis)
37
+ result = real_spectrum_product(prepared_signal, prepared_kernel)
38
+ result = result.swapaxes(real_axis, final_axis) unless real_axis == final_axis
39
+ Numo::SFloat.cast(result)
40
+ end
41
+
42
+ def prepare_real_operand(value, real_axis, final_axis)
43
+ real_axis == final_axis ? value : value.swapaxes(real_axis, final_axis)
44
+ end
45
+
46
+ def real_spectrum_product(signal_value, kernel_value)
47
+ signal_spectrum = Numo::Pocketfft.rfftn(signal_value)
48
+ kernel_spectrum = Numo::Pocketfft.rfftn(kernel_value)
49
+ kernel_spectrum.inplace.conj if operation == :correlation
50
+ Numo::Pocketfft.irfftn(signal_spectrum.inplace * kernel_spectrum)
51
+ end
52
+
53
+ def complex_product
54
+ signal_spectrum = Numo::Pocketfft.fftn(signal)
55
+ kernel_spectrum = Numo::Pocketfft.fftn(kernel)
56
+ kernel_spectrum.inplace.conj if operation == :correlation
57
+ result = Numo::Pocketfft.ifftn(signal_spectrum.inplace * kernel_spectrum).real
58
+ Numo::SFloat.cast(result)
59
+ end
60
+
61
+ def fold_kernel(source, signal_shape, anchors)
62
+ folded = Numo::DFloat.zeros(*signal_shape)
63
+ source.flatten.each_with_index do |value, flat_index|
64
+ target = folded_target(flat_index, source.shape, signal_shape, anchors)
65
+ folded[*target] = folded[*target] + value
66
+ end
67
+ folded
68
+ end
69
+
70
+ def folded_target(flat_index, kernel_shape, signal_shape, anchors)
71
+ remainder = flat_index
72
+ Array.new(kernel_shape.length).tap do |target|
73
+ (kernel_shape.length - 1).downto(0) do |axis|
74
+ coordinate = remainder % kernel_shape[axis]
75
+ remainder /= kernel_shape[axis]
76
+ target[axis] = (coordinate - anchors[axis]) % signal_shape[axis]
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ private_constant :CircularFftOperation
83
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'convolver/fft_limits'
4
+ require 'convolver/buffer_preparation'
5
+
6
+ module Convolver
7
+ # Shares allocation-free FFT dimensions between execution and estimation.
8
+ # @private
9
+ class FftBufferPlan
10
+ attr_reader :transform_shape, :real_axis, :transform_size, :spectrum_size
11
+
12
+ def initialize(plan, signal_shape, kernel_shape, limits: BufferLimits::NATIVE)
13
+ BufferPreparation.new(plan, signal_shape, kernel_shape, limits:).fft!
14
+ @real_axis = plan.wrap? ? CircularFftOperation.real_axis(signal_shape) : signal_shape.length - 1
15
+ @transform_shape = select_shape(plan, signal_shape, kernel_shape, FftLimits.new(limits))
16
+ @transform_size = transform_shape.reduce(1, :*)
17
+ @spectrum_size = spectrum_elements
18
+ end
19
+
20
+ private
21
+
22
+ def select_shape(plan, signal_shape, kernel_shape, limits)
23
+ return [].freeze if signal_shape.empty?
24
+ return plan.linear_fft_shape(kernel_shape, limits:) unless plan.wrap?
25
+
26
+ limits.validate!(signal_shape, real_axis:)
27
+ signal_shape.dup.freeze
28
+ end
29
+
30
+ def spectrum_elements
31
+ return transform_size if transform_shape.empty? || real_axis.nil?
32
+
33
+ length = transform_shape[real_axis]
34
+ transform_size / length * ((length / 2) + 1)
35
+ end
36
+ end
37
+
38
+ private_constant :FftBufferPlan
39
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Convolver
4
+ # Estimates the selected real or complex PocketFFT implementation cost.
5
+ # @private
6
+ class FftEstimator
7
+ REAL_FFT_COST = { 1 => 1.0e-9, 2 => 9.0e-10, 3 => 1.45e-9 }.freeze
8
+ COMPLEX_FFT_COST = { 1 => 2.4e-9, 2 => 2.4e-9, 3 => 2.8e-9 }.freeze
9
+ FIXED_COST = 1.5e-4
10
+ PREPARATION_COST = 1.0e-9
11
+ AXIS_MOVE_COST = 1.6e-9
12
+ CORRELATION_COST = 2.0e-10
13
+
14
+ def initialize(operation, signal, kernel, plan)
15
+ @operation = operation
16
+ @signal = signal
17
+ @kernel = kernel
18
+ @plan = plan
19
+ @buffers = plan.fft_buffers
20
+ end
21
+
22
+ def call
23
+ transform_size, spectrum_size, coefficient, moved_size = dimensions
24
+ transform_cost = coefficient * transform_size * Math.log([transform_size, 2].max)
25
+ preparation_cost = PREPARATION_COST * preparation_size(transform_size)
26
+ axis_move_cost = AXIS_MOVE_COST * moved_size
27
+ correlation_cost = operation == :correlation ? CORRELATION_COST * spectrum_size : 0.0
28
+ FIXED_COST + transform_cost + preparation_cost + axis_move_cost + correlation_cost
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :operation, :signal, :kernel, :plan, :buffers
34
+
35
+ def dimensions
36
+ return linear_dimensions unless plan.wrap?
37
+
38
+ circular_dimensions
39
+ end
40
+
41
+ def circular_dimensions
42
+ real_axis = buffers.real_axis
43
+ [buffers.transform_size, buffers.spectrum_size, transform_coefficient(real_axis), moved_size(real_axis)]
44
+ end
45
+
46
+ def linear_dimensions
47
+ [buffers.transform_size, buffers.spectrum_size,
48
+ cost_for(REAL_FFT_COST), 0]
49
+ end
50
+
51
+ def preparation_size(transform_size)
52
+ extension_size = plan.valid? || plan.wrap? ? 0 : plan.extended_size
53
+ working_size = plan.wrap? ? signal.size + kernel.size : 2 * transform_size
54
+ extension_size + working_size
55
+ end
56
+
57
+ def cost_for(costs)
58
+ costs.fetch(signal.ndim, costs.values.last)
59
+ end
60
+
61
+ def transform_coefficient(real_axis)
62
+ cost_for(real_axis ? REAL_FFT_COST : COMPLEX_FFT_COST)
63
+ end
64
+
65
+ def moved_size(real_axis)
66
+ real_axis && real_axis != signal.ndim - 1 ? 3 * signal.size : 0
67
+ end
68
+ end
69
+
70
+ private_constant :FftEstimator
71
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'convolver/buffer_limits'
4
+
5
+ # Internal allocation and arithmetic checks for the PocketFFT paths.
6
+ module Convolver
7
+ # Only FFT representability failures permit automatic direct fallback.
8
+ # @private
9
+ class FftUnavailable < RangeError; end
10
+
11
+ # Bounds PocketFFT buffers and signed arithmetic without constructing plans.
12
+ # @private
13
+ class FftLimits
14
+ def initialize(buffers = BufferLimits::NATIVE)
15
+ @buffers = buffers
16
+ end
17
+
18
+ def validate!(shape, real_axis:)
19
+ total = buffers.product!(shape, 'FFT size', error: FftUnavailable)
20
+ validate_buffers!(shape, total, real_axis)
21
+ # The wrapper casts total to int before dividing it by the axis length.
22
+ integer!(total, buffers.int_max, 'FFT total size')
23
+ # Real forward computes int z_step = (z / 2 + 1) * 2.
24
+ integer!(shape[real_axis], buffers.int_max - 2, 'FFT real stride') if real_axis
25
+ validate_axes!(shape)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :buffers
31
+
32
+ def validate_axes!(shape)
33
+ # FFTPACK twiddle counts are <= 2n complex / 3n real. Bluestein m < 4n,
34
+ # so each variable allocation is <= 128n bytes, including inner twiddles.
35
+ # The 16n int bound also protects signed intermediates at the inner m.
36
+ shape.each do |length|
37
+ integer!(length, buffers.int_max / 16, 'FFT axis arithmetic')
38
+ buffer!(length, 128, 'FFT axis work buffer')
39
+ end
40
+ end
41
+
42
+ def validate_buffers!(shape, total, real_axis)
43
+ buffer!(total, 8, 'FFT spatial buffer')
44
+ return buffer!(total, 16, 'FFT complex buffer') unless real_axis
45
+
46
+ spectrum = total / shape[real_axis] * ((shape[real_axis] / 2) + 1)
47
+ buffer!(spectrum, 16, 'FFT spectrum buffer')
48
+ buffer!(total, 16, 'FFT inverse staging buffer')
49
+ buffer!(total, 8, 'FFT inverse output buffer')
50
+ end
51
+
52
+ def buffer!(count, width, description)
53
+ buffers.buffer!(count, width, description, error: FftUnavailable)
54
+ end
55
+
56
+ def integer!(value, maximum, description)
57
+ return if value <= maximum
58
+
59
+ raise FftUnavailable, "#{description} exceeds native integer limit"
60
+ end
61
+ end
62
+
63
+ private_constant :FftUnavailable, :FftLimits
64
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Convolver
4
+ # Calculates a valid linear operation through real PocketFFT transforms.
5
+ # @private
6
+ class LinearFftOperation
7
+ def initialize(operation, signal, kernel, plan)
8
+ @operation = operation
9
+ @signal = signal
10
+ @kernel = kernel
11
+ @transform_shape = plan.fft_buffers.transform_shape
12
+ end
13
+
14
+ def call
15
+ signal_padded = zero_padded(signal)
16
+ kernel_padded = zero_padded(kernel)
17
+ full_result = real_spectrum_product(signal_padded, kernel_padded)
18
+ Numo::SFloat.cast(full_result[*valid_ranges])
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :operation, :signal, :kernel, :transform_shape
24
+
25
+ def zero_padded(value)
26
+ Numo::DFloat.zeros(*transform_shape).tap do |padded|
27
+ padded[*value.shape.map { |size| 0...size }] = value
28
+ end
29
+ end
30
+
31
+ def real_spectrum_product(signal_value, kernel_value)
32
+ signal_spectrum = Numo::Pocketfft.rfftn(signal_value)
33
+ kernel_spectrum = Numo::Pocketfft.rfftn(kernel_value)
34
+ kernel_spectrum.inplace.conj if operation == :correlation
35
+ Numo::Pocketfft.irfftn(signal_spectrum.inplace * kernel_spectrum)
36
+ end
37
+
38
+ def valid_ranges
39
+ signal.shape.zip(kernel.shape).map do |signal_size, kernel_size|
40
+ if operation == :correlation
41
+ 0...(signal_size - kernel_size + 1)
42
+ else
43
+ (kernel_size - 1)...signal_size
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ private_constant :LinearFftOperation
50
+ end