convolver 3.0.1 → 4.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e56b4c0d87d92abb3f8c79d3d11b8d41c8dcbb6d7055dce615eef07a363b0cc2
4
- data.tar.gz: 23c852764b162f082fa380e6932a8c5ad0c400c6d766579d7a087d32cd875825
3
+ metadata.gz: 0461fd5af32c0104f8214df035709fea95c2b50f21f63d1b88c7999cadcafd29
4
+ data.tar.gz: 770518e0498a79c213c3be551f7749d6e20139a9fd327357f1d3608c0cbb72fd
5
5
  SHA512:
6
- metadata.gz: 7eda50980613f1d03659465daa5b3ece647485ab38355664808497f966f232893de2133649c0e8cd9262606fde99ad3305552db27a5e719901aee6c095b2ac3d
7
- data.tar.gz: da94b103db3c1af9f735787584a757ed9683a12556879495f041a88a6f7eb178ef214d871f64e8a18aa498b090c9cec748c4c7d11c534a687099e9d2a3c15ff2
6
+ metadata.gz: 40b871a7ad8883742b27ff4b05ae830f38cbdd7f09f3fc6ef85fd26166bbc9613fe3fc7b3d73bd47f6ce45642dfa3e4b884bc69f9b88bc69028a043d5da5e4de
7
+ data.tar.gz: 506e52bd6f023d6fb169589e1763fe2a3668baf5158a04e545dd8bbc4de151e4b03ece49a51dd9b6d1b0f71666a662d9a148a269ecd96cd73c9ce6d0a2c0fa2b
data/CHANGELOG.md CHANGED
@@ -5,7 +5,36 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [4.0.0] - 2026-10-06
9
+
10
+ ### Added
11
+
12
+ - Optional `dtype: Numo::SFloat` or `dtype: Numo::DFloat` on all calculation
13
+ and estimator methods, with matching result types and genuine native double
14
+ arithmetic. Omitted or nil dtype selects precision from both operands.
15
+
16
+ ### Changed
17
+
18
+ - **Breaking, v4.0.0:** Automatically promote SFloat and 8/16-bit integer
19
+ inputs to SFloat, and DFloat and 32/64-bit integer inputs to DFloat; the
20
+ wider floating candidate wins independently of operand order.
21
+ - **Breaking:** Reject Bit, RObject, complex arrays and custom NArray subclasses,
22
+ including with an explicit floating dtype. Integer results are not supported.
23
+ - Reuse a single operation plan during automatic dispatch and account for
24
+ selected dtype and input conversion in cost estimates and buffer checks.
25
+ - Bound FFT shape search in native code and skip FFT planning when a cheap
26
+ cost bound already favors direct execution.
27
+ - Calibrate direct/FFT selection for ranks one through three on arm64 macOS
28
+ and x86_64/aarch64 Linux, including dtype, fixed setup, continuous kernel
29
+ costs, boundary indexing and periodic folding. Retain the existing heuristic
30
+ on other platforms and higher ranks.
31
+
32
+ ### Fixed
33
+
34
+ - Cast both inputs and fill to the resolved dtype before boundary preparation,
35
+ periodic folding and arithmetic, consistently across direct and FFT paths.
36
+ `dtype: Numo::SFloat` selects single-precision inputs/results, but does not
37
+ guarantee bit-for-bit version 3 results. FFT work remains double precision.
9
38
 
10
39
  ## [3.0.1] - 2026-10-04
11
40
 
@@ -133,7 +162,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
133
162
  - Updated the native extension to use the maintained Numo C API and removed the
134
163
  legacy untyped-data compatibility code.
135
164
 
136
- [Unreleased]: https://github.com/neilslater/convolver/compare/v3.0.0...HEAD
165
+ [Unreleased]: https://github.com/neilslater/convolver/compare/v3.0.1...HEAD
137
166
  [3.0.0]: https://github.com/neilslater/convolver/compare/v2.0.0...v3.0.0
138
167
  [2.0.0]: https://github.com/neilslater/convolver/compare/v1.0.1...v2.0.0
139
168
  [1.0.1]: https://github.com/neilslater/convolver/compare/v1.0.0...v1.0.1
data/README.md CHANGED
@@ -53,13 +53,12 @@ convolve(signal, kernel)[n] = sum_j signal[n - j] * kernel[j]
53
53
 
54
54
  The formulas apply component-wise to multidimensional arrays. Correlation
55
55
  conventionally conjugates the second operand, although Convolver's current
56
- single-precision real input contract makes conjugation invisible.
56
+ real input contract makes conjugation invisible.
57
57
 
58
58
  With no keywords, the signal and kernel must have the same rank, the kernel
59
59
  must be no larger than the signal in any dimension, and only positions with
60
- complete overlap are returned. Inputs are converted to single-precision floats
61
- internally and results are returned as `Numo::SFloat`. Supplying
62
- `Numo::SFloat` inputs avoids conversion in the direct implementation.
60
+ complete overlap are returned. Both inputs determine the floating result dtype,
61
+ or you can select it explicitly with `dtype:`.
63
62
 
64
63
  The automatic, direct, and FFT implementations are available for both
65
64
  operations:
@@ -83,6 +82,41 @@ Convolver.convolve(signal, kernel,
83
82
  origin: 0)
84
83
  ```
85
84
 
85
+ ### Input and result precision
86
+
87
+ `dtype:` accepts `Numo::SFloat`, `Numo::DFloat`, or `nil` (automatic, the
88
+ default). Automatic selection maps each input type using this table, then
89
+ chooses DFloat if either input maps to DFloat, otherwise SFloat:
90
+
91
+ | Input type | Automatic floating type |
92
+ | --- | --- |
93
+ | `SFloat`, `Int8`, `UInt8`, `Int16`, `UInt16` | `Numo::SFloat` |
94
+ | `DFloat`, `Int32`, `UInt32`, `Int64`, `UInt64` | `Numo::DFloat` |
95
+
96
+ The input type names above are classes in `Numo`. Both operands must be instances
97
+ of these concrete classes. Bit, RObject, complex arrays, custom subclasses and
98
+ ordinary Ruby arrays are rejected, even with an explicit `dtype:`. Integer
99
+ results and complex arithmetic are not supported.
100
+
101
+ ```ruby
102
+ signal = Numo::SFloat[1, 2, 3]
103
+ kernel = Numo::DFloat[0.25, 0.75]
104
+ Convolver.convolve(signal, kernel) # DFloat result
105
+ Convolver.convolve(signal, kernel, dtype: Numo::SFloat) # SFloat result
106
+ ```
107
+
108
+ Both inputs are cast to the selected dtype before extension, folding or
109
+ arithmetic. Inputs are never mutated. `fill_value` is cast to this same dtype
110
+ and never promotes the result. Ordinary floating conversion applies, including
111
+ rounding, overflow to infinity and underflow; NaN and infinity are not rejected.
112
+ Non-finite propagation and rounding can differ between algorithms.
113
+
114
+ Direct products and accumulation use the selected precision. PocketFFT uses
115
+ double-precision working buffers for either result dtype. The returned class
116
+ is independent of algorithm selection. Integer inputs become approximate
117
+ floating computations: DFloat represents every 32-bit integer, but not every
118
+ 64-bit integer, and neither dtype guarantees exact integer convolution.
119
+
86
120
  ### Output modes
87
121
 
88
122
  `mode:` controls the returned extent independently in each dimension:
@@ -153,6 +187,14 @@ Convolver.predict_correlate_basic_time(signal, kernel, mode: :same, boundary: :n
153
187
  Convolver.predict_correlate_fft_time(signal, kernel, mode: :same, boundary: :wrap)
154
188
  ```
155
189
 
190
+ Estimates are machine-dependent heuristics, including input conversion, boundary
191
+ preparation and periodic kernel folding. Automatic selection avoids FFT planning
192
+ when a cheap lower bound already favors direct calculation. Ranks one through
193
+ three use calibrated profiles on arm64 macOS and x86_64/aarch64 Linux; other
194
+ platforms and higher ranks retain the general heuristic. Algorithm choices
195
+ can change as estimates improve; benchmark the explicit methods for workloads
196
+ where the choice matters.
197
+
156
198
  Calculations and estimators reject unrepresentable buffer sizes with `RangeError`
157
199
  before allocation. FFT planning also checks conservative PocketFFT integer and
158
200
  work-buffer limits, including full complex staging for real inverse transforms.
@@ -160,6 +202,15 @@ Automatic selection uses direct calculation when FFT exceeds its limits and the
160
202
  direct path remains valid. These checks do not reserve memory; ordinary allocator
161
203
  exhaustion still raises `NoMemoryError`.
162
204
 
205
+ ### Migrating from version 3
206
+
207
+ Version 4 changes default result types and values: DFloat and 32/64-bit integer
208
+ inputs now select DFloat. Pass `dtype: Numo::SFloat` to request single-precision
209
+ inputs and results. This retains the version 3 result type, but does not promise
210
+ identical numerical results: version 4 consistently casts both inputs before
211
+ processing, whereas version 3's direct and FFT paths could round differently.
212
+ The input-class restrictions listed above also apply with a dtype override.
213
+
163
214
  ### Migrating from version 2
164
215
 
165
216
  Version 2's `convolve*` methods calculated cross-correlation. Version 3 corrects
@@ -1,171 +1,57 @@
1
- // ext/convolver/convolve_raw.c
2
-
3
1
  #include "convolve_raw.h"
2
+ #include "raw_helpers.h"
4
3
 
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
- // Convolve
74
- //
75
-
76
- void convolve_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 convolution 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(
133
- in_ptr[ offset + kernel_co_incr_cache[kernel_size - 1 - (j+3)] ],
134
- in_ptr[ offset + kernel_co_incr_cache[kernel_size - 1 - (j+2)] ],
135
- in_ptr[ offset + kernel_co_incr_cache[kernel_size - 1 - (j+1)] ],
136
- in_ptr[ offset + kernel_co_incr_cache[kernel_size - 1 - j] ]
137
- );
138
- simd_x = _mm_mul_ps( simd_x, simd_y );
139
- simd_t = _mm_add_ps( simd_x, simd_t );
140
- }
141
- _mm_store_ps( v, simd_t );
4
+ #define RAW_INDEX(j) (kernel_size - 1 - (j))
5
+ #if defined(__SSE__)
6
+ #define RAW_SIMD 1
142
7
  #else
143
- // SSE is unavailable on architectures such as Apple Silicon.
144
- // Use the same calculation without x86-specific intrinsics.
145
- for ( j = 0; j < kernel_aligned; j++ ) {
146
- t += in_ptr[ offset + kernel_co_incr_cache[kernel_size - 1 - j] ] * kernel_ptr[j];
147
- }
8
+ #define RAW_SIMD 0
148
9
  #endif
149
-
150
- // Complete any remaining 1,2 or 3 items one at a time
151
- for ( j = kernel_aligned; j < kernel_size; j++ ) {
152
- t += in_ptr[ offset + kernel_co_incr_cache[kernel_size - 1 - j] ] * kernel_ptr[ j ];
153
- }
154
-
155
- #if CONVOLVER_USE_SSE
156
- out_ptr[i] = v[0] + v[1] + v[2] + v[3] + t;
10
+ #define RAW_NAME convolve_raw
11
+ #define RAW_TYPE float
12
+ #define RAW_LANES 4
13
+ #define RAW_VECTOR __m128
14
+ #define RAW_ZERO _mm_setzero_ps
15
+ #define RAW_LOAD _mm_loadu_ps
16
+ #define RAW_STORE _mm_storeu_ps
17
+ #define RAW_ADD _mm_add_ps
18
+ #define RAW_MUL _mm_mul_ps
19
+ #include "raw_loop.h"
20
+ #undef RAW_NAME
21
+ #undef RAW_TYPE
22
+ #undef RAW_LANES
23
+ #undef RAW_VECTOR
24
+ #undef RAW_ZERO
25
+ #undef RAW_LOAD
26
+ #undef RAW_STORE
27
+ #undef RAW_ADD
28
+ #undef RAW_MUL
29
+ #undef RAW_SIMD
30
+
31
+ #if defined(__SSE2__)
32
+ #define RAW_SIMD 1
157
33
  #else
158
- out_ptr[i] = t;
34
+ #define RAW_SIMD 0
159
35
  #endif
160
-
161
- if ( i + 1 < out_size ) {
162
- offset = checked_add(
163
- offset,
164
- out_co_incr[ corner_dec( out_rank, out_shape, out_q ) ],
165
- "input offset"
166
- );
167
- }
168
- }
169
-
170
- RB_ALLOCV_END( cache_storage );
171
- }
36
+ #define RAW_NAME convolve_raw_double
37
+ #define RAW_TYPE double
38
+ #define RAW_LANES 2
39
+ #define RAW_VECTOR __m128d
40
+ #define RAW_ZERO _mm_setzero_pd
41
+ #define RAW_LOAD _mm_loadu_pd
42
+ #define RAW_STORE _mm_storeu_pd
43
+ #define RAW_ADD _mm_add_pd
44
+ #define RAW_MUL _mm_mul_pd
45
+ #include "raw_loop.h"
46
+ #undef RAW_NAME
47
+ #undef RAW_TYPE
48
+ #undef RAW_LANES
49
+ #undef RAW_VECTOR
50
+ #undef RAW_ZERO
51
+ #undef RAW_LOAD
52
+ #undef RAW_STORE
53
+ #undef RAW_ADD
54
+ #undef RAW_MUL
55
+ #undef RAW_SIMD
56
+
57
+ #undef RAW_INDEX
@@ -1,28 +1,14 @@
1
- // ext/convolver/convolve_raw.h
2
-
3
- ////////////////////////////////////////////////////////////////////////////////////////////////
4
- //
5
- // Declarations of narray helper functions
6
- //
7
-
8
1
  #ifndef CONVOLVE_RAW_H
9
2
  #define CONVOLVE_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
3
+ #include "raw_config.h"
22
4
 
23
5
  void convolve_raw(
24
6
  int in_rank, const size_t *in_shape, const float *in_ptr,
25
7
  int kernel_rank, const size_t *kernel_shape, const float *kernel_ptr,
26
8
  int out_rank, const size_t *out_shape, float *out_ptr );
27
9
 
10
+ void convolve_raw_double(
11
+ int in_rank, const size_t *in_shape, const double *in_ptr,
12
+ int kernel_rank, const size_t *kernel_shape, const double *kernel_ptr,
13
+ int out_rank, const size_t *out_shape, double *out_ptr );
28
14
  #endif
@@ -4,18 +4,18 @@
4
4
 
5
5
  #include "correlate_raw.h"
6
6
  #include "convolve_raw.h"
7
+ #include "fft_shape.h"
7
8
 
8
9
  static VALUE mConvolver;
9
10
 
10
11
  /* Contiguous views share their backing storage but start at a byte offset. */
11
- static const float *logical_pointer_for_read(VALUE value) {
12
- return (const float *)(na_get_pointer_for_read(value) + na_get_offset(value));
12
+ static const void *logical_pointer_for_read(VALUE value) {
13
+ return (const void *)(na_get_pointer_for_read(value) + na_get_offset(value));
13
14
  }
14
15
 
15
- static VALUE contiguous_sfloat(VALUE value) {
16
+ static VALUE contiguous_float(VALUE value) {
16
17
  narray_t *narray;
17
18
 
18
- value = rb_funcall(numo_cSFloat, rb_intern("cast"), 1, value);
19
19
  GetNArray(value, narray);
20
20
  /* A scalar has no strides; Numo's contiguous check requires an axis. */
21
21
  if (narray->ndim > 0 && !RTEST(na_check_contiguous(value))) {
@@ -54,8 +54,13 @@ static VALUE convolver_basic_valid(VALUE signal, VALUE kernel, enum convolver_op
54
54
  rb_raise(rb_eArgError, "signal and kernel must be Numo::NArray values");
55
55
  }
56
56
 
57
- signal_value = contiguous_sfloat(signal);
58
- kernel_value = contiguous_sfloat(kernel);
57
+ VALUE dtype = CLASS_OF(signal);
58
+ if ((dtype != numo_cSFloat && dtype != numo_cDFloat) || CLASS_OF(kernel) != dtype) {
59
+ rb_raise(rb_eArgError, "native inputs must have matching SFloat or DFloat dtypes");
60
+ }
61
+
62
+ signal_value = contiguous_float(signal);
63
+ kernel_value = contiguous_float(kernel);
59
64
  GetNArray(signal_value, signal_narray);
60
65
  GetNArray(kernel_value, kernel_narray);
61
66
 
@@ -82,19 +87,35 @@ static VALUE convolver_basic_valid(VALUE signal, VALUE kernel, enum convolver_op
82
87
  numo_result_shape[rank - i - 1] = (size_t)result_shape[i];
83
88
  }
84
89
 
85
- result_value = nary_new(numo_cSFloat, rank, numo_result_shape);
86
- if (operation == CONVOLVER_CORRELATION) {
87
- correlate_raw(
88
- rank, signal_shape, logical_pointer_for_read(signal_value),
89
- rank, kernel_shape, logical_pointer_for_read(kernel_value),
90
- rank, result_shape, (float *)na_get_pointer_for_write(result_value)
91
- );
90
+ result_value = nary_new(dtype, rank, numo_result_shape);
91
+ if (dtype == numo_cSFloat) {
92
+ if (operation == CONVOLVER_CORRELATION) {
93
+ correlate_raw(
94
+ rank, signal_shape, logical_pointer_for_read(signal_value),
95
+ rank, kernel_shape, logical_pointer_for_read(kernel_value),
96
+ rank, result_shape, (float *)na_get_pointer_for_write(result_value)
97
+ );
98
+ } else {
99
+ convolve_raw(
100
+ rank, signal_shape, logical_pointer_for_read(signal_value),
101
+ rank, kernel_shape, logical_pointer_for_read(kernel_value),
102
+ rank, result_shape, (float *)na_get_pointer_for_write(result_value)
103
+ );
104
+ }
92
105
  } else {
93
- convolve_raw(
94
- rank, signal_shape, logical_pointer_for_read(signal_value),
95
- rank, kernel_shape, logical_pointer_for_read(kernel_value),
96
- rank, result_shape, (float *)na_get_pointer_for_write(result_value)
97
- );
106
+ if (operation == CONVOLVER_CORRELATION) {
107
+ correlate_raw_double(
108
+ rank, signal_shape, logical_pointer_for_read(signal_value),
109
+ rank, kernel_shape, logical_pointer_for_read(kernel_value),
110
+ rank, result_shape, (double *)na_get_pointer_for_write(result_value)
111
+ );
112
+ } else {
113
+ convolve_raw_double(
114
+ rank, signal_shape, logical_pointer_for_read(signal_value),
115
+ rank, kernel_shape, logical_pointer_for_read(kernel_value),
116
+ rank, result_shape, (double *)na_get_pointer_for_write(result_value)
117
+ );
118
+ }
98
119
  }
99
120
 
100
121
  return result_value;
@@ -107,7 +128,7 @@ static VALUE convolver_basic_valid(VALUE signal, VALUE kernel, enum convolver_op
107
128
  * @overload correlate_basic_valid(signal, kernel)
108
129
  * @param signal [Numo::NArray] input values
109
130
  * @param kernel [Numo::NArray] correlation kernel
110
- * @return [Numo::SFloat] valid cross-correlation result
131
+ * @return [Numo::SFloat, Numo::DFloat] valid cross-correlation result
111
132
  */
112
133
  static VALUE convolver_correlate_basic_valid(VALUE self, VALUE signal, VALUE kernel) {
113
134
  (void)self;
@@ -121,7 +142,7 @@ static VALUE convolver_correlate_basic_valid(VALUE self, VALUE signal, VALUE ker
121
142
  * @overload convolve_basic_valid(signal, kernel)
122
143
  * @param signal [Numo::NArray] input values
123
144
  * @param kernel [Numo::NArray] convolution kernel
124
- * @return [Numo::SFloat] valid mathematical convolution result
145
+ * @return [Numo::SFloat, Numo::DFloat] valid mathematical convolution result
125
146
  */
126
147
  static VALUE convolver_convolve_basic_valid(VALUE self, VALUE signal, VALUE kernel) {
127
148
  (void)self;
@@ -130,6 +151,7 @@ static VALUE convolver_convolve_basic_valid(VALUE self, VALUE signal, VALUE kern
130
151
 
131
152
  void Init_convolver(void) {
132
153
  mConvolver = rb_define_module("Convolver");
154
+ convolver_init_fft_shape(mConvolver);
133
155
  rb_define_singleton_method(mConvolver, "correlate_basic_valid", convolver_correlate_basic_valid, 2);
134
156
  rb_define_singleton_method(mConvolver, "convolve_basic_valid", convolver_convolve_basic_valid, 2);
135
157
  }