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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e886e4cc4f0c47a5c0e40209a20ac4b90f9c5fa349c76e9b04889fb8c010fa70
4
- data.tar.gz: b656b786753b87ab68d89985262bedbbd3ad0929527a2953acde7af56cde643b
3
+ metadata.gz: e56b4c0d87d92abb3f8c79d3d11b8d41c8dcbb6d7055dce615eef07a363b0cc2
4
+ data.tar.gz: 23c852764b162f082fa380e6932a8c5ad0c400c6d766579d7a087d32cd875825
5
5
  SHA512:
6
- metadata.gz: 187f65c4daf5c25007348d0e3f21e44dda4bccb629f0dab067ac8193aac8977d22a441b79249d35ad4b7c2b17e43e3339db7045c0f83882009556bae6b446425
7
- data.tar.gz: 4605ecab87c0027c02c30c82e78298766b311a4e359b178fec8b47427a5c6bfc57a049193173fb38fba3877874e679cf1d96ac41700a5cf090a31c5fa43c9635
6
+ metadata.gz: 7eda50980613f1d03659465daa5b3ece647485ab38355664808497f966f232893de2133649c0e8cd9262606fde99ad3305552db27a5e719901aee6c095b2ac3d
7
+ data.tar.gz: da94b103db3c1af9f735787584a757ed9683a12556879495f041a88a6f7eb178ef214d871f64e8a18aa498b090c9cec748c4c7d11c534a687099e9d2a3c15ff2
data/CHANGELOG.md CHANGED
@@ -5,6 +5,85 @@ 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]
9
+
10
+ ## [3.0.1] - 2026-10-04
11
+
12
+ ### Changed
13
+
14
+ - Require `numo-pocketfft >= 0.7.0` and allow `numo-narray-alt < 0.12`,
15
+ enabling the NArray 0.11 series.
16
+
17
+ ### Fixed
18
+
19
+ - Read the logical values of contiguous signal and kernel slices in direct
20
+ convolution and correlation, including unaligned SSE kernels and scalar views.
21
+ - Retain a representable exact FFT shape when an optional fast candidate exceeds
22
+ the native element limit.
23
+ - Reject unrepresentable FFT buffers and native integer arithmetic before
24
+ allocation, including preparation, inverse staging and work buffers. Automatic
25
+ selection retains a safe direct path when FFT exceeds its limits.
26
+
27
+ ## [3.0.0] - 2026-08-22
28
+
29
+ ### Added
30
+
31
+ - Added the `correlate`, `correlate_basic`, and `correlate_fft` calculation
32
+ methods for cross-correlation, preserving the numerical operation exposed by
33
+ the version 2 `convolve*` methods.
34
+ - Added `predict_correlate_basic_time` and `predict_correlate_fft_time` as the
35
+ corresponding estimator family.
36
+ - Added distinct native correlation and convolution primitives whose hot loops
37
+ perform the required traversal directly without materializing a reversed
38
+ kernel.
39
+
40
+ ### Changed
41
+
42
+ - **Breaking:** Changed `convolve`, `convolve_basic`, and `convolve_fft` to
43
+ calculate mathematical discrete convolution. Version 2 users who need
44
+ unchanged results must rename these calls to their `correlate*` counterparts.
45
+ - **Breaking:** Changed the `predict_convolve_*_time` methods to estimate the
46
+ new mathematical convolution implementations.
47
+ - Defined operation-specific `:same` alignment. Correlation uses
48
+ `signal[i + j - anchor]`; convolution uses
49
+ `signal[i + anchor - j]`. This places the extra boundary sample for an even
50
+ kernel on opposite sides.
51
+ - Reworked linear PocketFFT calculations around real transforms and checked
52
+ exact/even or 2/3/5-smooth transform shapes. Circular calculations use real
53
+ transforms when an axis is even and retain complex transforms for all-odd
54
+ shapes.
55
+
56
+ ### Removed
57
+
58
+ - **Breaking:** Removed the deprecated `convolve_fftw3` compatibility method.
59
+ Use `correlate_fft` to preserve version 2 results or `convolve_fft` for
60
+ mathematical convolution.
61
+
62
+ ## [2.0.0] - 2026-08-20
63
+
64
+ ### Added
65
+
66
+ - Added `:valid`, `:same`, and `:full` output modes to every calculation and
67
+ estimator method.
68
+ - Added constant, nearest, reflect, mirror, and wrap signal boundary extensions,
69
+ including nonzero constant fill and kernels larger than the signal where the
70
+ output mode permits them.
71
+ - Added scalar and per-axis kernel origins with an explicit even-kernel
72
+ alignment convention.
73
+ - Added an origin-aware circular PocketFFT correlation path for periodic
74
+ same-sized output.
75
+
76
+ ### Changed
77
+
78
+ - **Breaking:** Raised the minimum supported Ruby version to 3.3.
79
+ - Adopted `ncs_rubocop_conf` 0.1.0 as the versioned RuboCop policy, added
80
+ exception auditing, and began tracking the development dependency lockfile.
81
+ - Made algorithm selection and both public time estimators account for output
82
+ shape, boundary extension, kernel folding, and FFT working shape.
83
+ - Moved the native valid-only calculation behind a shared Ruby orchestration
84
+ layer so direct and FFT implementations use identical option validation and
85
+ boundary semantics.
86
+
8
87
  ## [1.0.1] - 2026-07-29
9
88
 
10
89
  ### Fixed
@@ -54,5 +133,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
54
133
  - Updated the native extension to use the maintained Numo C API and removed the
55
134
  legacy untyped-data compatibility code.
56
135
 
136
+ [Unreleased]: https://github.com/neilslater/convolver/compare/v3.0.0...HEAD
137
+ [3.0.0]: https://github.com/neilslater/convolver/compare/v2.0.0...v3.0.0
138
+ [2.0.0]: https://github.com/neilslater/convolver/compare/v1.0.1...v2.0.0
57
139
  [1.0.1]: https://github.com/neilslater/convolver/compare/v1.0.0...v1.0.1
58
140
  [1.0.0]: https://github.com/neilslater/convolver/compare/v0.3.2...v1.0.0
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Convolver
2
2
 
3
+ [![CI](https://github.com/neilslater/convolver/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/neilslater/convolver/actions/workflows/ci.yml)
3
4
  [![Gem Version](https://badge.fury.io/rb/convolver.svg)](https://badge.fury.io/rb/convolver)
4
5
 
5
- Convolver calculates valid cross-correlations between multidimensional
6
- [`Numo::NArray`](https://github.com/yoshoku/numo-narray-alt) values. It chooses
7
- between a direct native implementation for smaller inputs and a
8
- [`Numo::Pocketfft`](https://github.com/yoshoku/numo-pocketfft)-based
6
+ Convolver calculates mathematical convolution and cross-correlation between
7
+ multidimensional
8
+ [`Numo::NArray`](https://github.com/yoshoku/numo-narray-alt) values. Both
9
+ operations support configurable output extents and signal boundary extensions.
10
+ Convolver chooses between a direct native implementation for smaller inputs
11
+ and a [`Numo::Pocketfft`](https://github.com/yoshoku/numo-pocketfft)-based
9
12
  implementation for larger inputs.
10
13
 
11
- Version 1.0 replaces the unmaintained `narray` and `fftw3` gems with
12
- `numo-narray-alt` and `numo-pocketfft`.
13
-
14
14
  ## Installation
15
15
 
16
16
  Add Convolver to your application's Gemfile:
@@ -19,22 +19,16 @@ Add Convolver to your application's Gemfile:
19
19
  gem 'convolver'
20
20
  ```
21
21
 
22
- Then run:
23
-
24
- ```sh
25
- bundle install
26
- ```
27
-
28
- Alternatively, install it directly:
29
-
30
- ```sh
31
- gem install convolver
32
- ```
33
-
34
- No external FFT library is required; PocketFFT is bundled by its Ruby gem.
22
+ Then run `bundle install`, or install the gem directly with
23
+ `gem install convolver`. No external FFT library is required; PocketFFT is
24
+ bundled by its Ruby gem.
35
25
 
36
26
  ## Usage
37
27
 
28
+ `convolve` calculates mathematical discrete convolution. `correlate`
29
+ calculates cross-correlation, following the public naming convention used by
30
+ NumPy and SciPy:
31
+
38
32
  ```ruby
39
33
  require 'convolver'
40
34
 
@@ -43,46 +37,169 @@ kernel = Numo::SFloat[1.3, -0.5]
43
37
 
44
38
  Convolver.convolve(signal, kernel)
45
39
  # => Numo::SFloat#shape=[2]
40
+ # [0.37, 0.45]
41
+
42
+ Convolver.correlate(signal, kernel)
43
+ # => Numo::SFloat#shape=[2]
46
44
  # [0.19, 0.27]
47
45
  ```
48
46
 
49
- The signal and kernel must have the same rank, and the kernel must be no larger
50
- than the signal in any dimension. Convolver returns only positions at which the
51
- kernel overlaps the signal completely. The result size in each dimension is:
47
+ For real one-dimensional inputs, the two valid operations are:
52
48
 
53
- ```ruby
54
- signal_size - kernel_size + 1
49
+ ```text
50
+ correlate(signal, kernel)[p] = sum_j signal[p + j] * conjugate(kernel[j])
51
+ convolve(signal, kernel)[n] = sum_j signal[n - j] * kernel[j]
55
52
  ```
56
53
 
57
- Inputs are converted to single-precision floats internally, and results are
58
- returned as `Numo::SFloat`. Supplying `Numo::SFloat` inputs avoids conversion in
59
- the direct implementation.
54
+ The formulas apply component-wise to multidimensional arrays. Correlation
55
+ conventionally conjugates the second operand, although Convolver's current
56
+ single-precision real input contract makes conjugation invisible.
57
+
58
+ With no keywords, the signal and kernel must have the same rank, the kernel
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
63
 
61
- `Convolver.convolve` normally chooses the implementation automatically. The
62
- implementations can also be called directly for application-specific
63
- benchmarking:
64
+ The automatic, direct, and FFT implementations are available for both
65
+ operations:
64
66
 
65
67
  ```ruby
68
+ Convolver.convolve(signal, kernel)
66
69
  Convolver.convolve_basic(signal, kernel)
67
70
  Convolver.convolve_fft(signal, kernel)
71
+
72
+ Convolver.correlate(signal, kernel)
73
+ Convolver.correlate_basic(signal, kernel)
74
+ Convolver.correlate_fft(signal, kernel)
75
+ ```
76
+
77
+ Every calculation method accepts the same options:
78
+
79
+ ```ruby
80
+ Convolver.convolve(signal, kernel,
81
+ mode: :same,
82
+ boundary: :reflect,
83
+ origin: 0)
68
84
  ```
69
85
 
70
- `Convolver.convolve_fftw3` remains as a deprecated alias for `convolve_fft` to
71
- ease migration from Convolver 0.x.
86
+ ### Output modes
87
+
88
+ `mode:` controls the returned extent independently in each dimension:
89
+
90
+ | Mode | Meaning | Result size |
91
+ | --- | --- | --- |
92
+ | `:valid` | Kernel overlaps the stored signal completely | `S - K + 1` |
93
+ | `:same` | One result aligned with each stored signal position | `S` |
94
+ | `:full` | Every kernel position with any stored-signal overlap | `S + K - 1` |
95
+
96
+ `:valid` is the default and accepts only the default `boundary: :constant`,
97
+ `fill_value: 0.0`, and `origin: 0`. `:full` supports constant extension only.
98
+ `:same` supports every boundary described below. Kernels larger than the signal
99
+ are supported by `:same` and `:full`, but not by `:valid`. `mode:` and
100
+ `boundary:` accept only the symbols listed here.
101
+
102
+ ### Boundary extension
103
+
104
+ For a one-dimensional signal `a b c d`, `boundary:` selects values outside the
105
+ stored signal:
106
+
107
+ | Boundary | Extended sequence |
108
+ | --- | --- |
109
+ | `:constant` | `k k k k | a b c d | k k k k` |
110
+ | `:nearest` | `a a a a | a b c d | d d d d` |
111
+ | `:reflect` | `d c b a | a b c d | d c b a` |
112
+ | `:mirror` | `d c b | a b c d | c b a` |
113
+ | `:wrap` | `a b c d | a b c d | a b c d` |
114
+
115
+ `fill_value:` sets `k` for `:constant` and defaults to zero. It must not be
116
+ passed with another boundary. `:reflect` repeats the edge sample; `:mirror`
117
+ does not. All boundary modes work across every dimension, including
118
+ length-one axes and extensions wider than the stored signal.
119
+
120
+ ### Kernel origin and alignment
121
+
122
+ `origin:` shifts the stored-kernel anchor for `:same`. It accepts one integer
123
+ applied to every dimension or an array with one integer per dimension. For a
124
+ kernel dimension of length `K`:
125
+
126
+ ```text
127
+ anchor = floor(K / 2) + origin
128
+
129
+ correlation[i] = sum_j extended_signal[i + j - anchor] * kernel[j]
130
+ convolution[i] = sum_j extended_signal[i + anchor - j] * kernel[j]
131
+ ```
132
+
133
+ The anchor must remain within the kernel. Correlation pads `anchor` samples
134
+ before the signal and `K - 1 - anchor` after it; convolution swaps those
135
+ widths. Odd centered kernels therefore align alike. For an even centered
136
+ kernel, correlation puts the extra sample before the signal and convolution
137
+ puts it after the signal. Positive origins move correlation toward lower signal
138
+ indices and convolution toward higher signal indices.
139
+
140
+ Nonzero origins are supported only for `:same`; `:valid` and `:full` require
141
+ zero.
142
+
143
+ ### Estimators
144
+
145
+ The estimator methods accept and validate the same options as their calculation
146
+ families:
147
+
148
+ ```ruby
149
+ Convolver.predict_convolve_basic_time(signal, kernel, mode: :same, boundary: :nearest)
150
+ Convolver.predict_convolve_fft_time(signal, kernel, mode: :same, boundary: :wrap)
151
+
152
+ Convolver.predict_correlate_basic_time(signal, kernel, mode: :same, boundary: :nearest)
153
+ Convolver.predict_correlate_fft_time(signal, kernel, mode: :same, boundary: :wrap)
154
+ ```
155
+
156
+ Calculations and estimators reject unrepresentable buffer sizes with `RangeError`
157
+ before allocation. FFT planning also checks conservative PocketFFT integer and
158
+ work-buffer limits, including full complex staging for real inverse transforms.
159
+ Automatic selection uses direct calculation when FFT exceeds its limits and the
160
+ direct path remains valid. These checks do not reserve memory; ordinary allocator
161
+ exhaustion still raises `NoMemoryError`.
162
+
163
+ ### Migrating from version 2
164
+
165
+ Version 2's `convolve*` methods calculated cross-correlation. Version 3 corrects
166
+ the terminology and changes every `convolve*` method to mathematical
167
+ convolution. Asymmetric kernels make the result change visible.
168
+
169
+ To preserve version 2 results, rename the complete method family:
170
+
171
+ | Version 2 | Version 3 equivalent |
172
+ | --- | --- |
173
+ | `convolve` | `correlate` |
174
+ | `convolve_basic` | `correlate_basic` |
175
+ | `convolve_fft` | `correlate_fft` |
176
+ | `predict_convolve_basic_time` | `predict_correlate_basic_time` |
177
+ | `predict_convolve_fft_time` | `predict_correlate_fft_time` |
178
+ | `convolve_fftw3` | `correlate_fft` |
179
+
180
+ `convolve_fftw3` has been removed. No `cross_correlate` aliases are provided;
181
+ the standard `correlate` name denotes cross-correlation explicitly documented
182
+ above.
72
183
 
73
184
  ## Contributing
74
185
 
75
- Install the development dependencies, then run the main suite:
186
+ Install the development dependencies, then run the complete local gate:
76
187
 
77
188
  ```sh
78
189
  bundle install
79
190
  bundle exec rake
80
191
  bundle exec rubocop
192
+ bundle exec ncs-rubocop-conf-audit
81
193
  bundle exec rake c:lint
194
+ bundle exec bundle-audit check --update
195
+ bundle exec rake docs:check
82
196
  ```
83
197
 
84
- The Ruby specs exercise both the Ruby API and native extension. Additional
85
- native-code checks are available:
198
+ The Ruby specs exercise both the Ruby API and native extension and enforce 95%
199
+ line and branch coverage for the Ruby library. Dependency auditing refreshes
200
+ the advisory database and requires network access. The documentation check
201
+ rejects YARD warnings and undocumented public API objects; private implementation
202
+ classes are marked `@private`. Additional native-code checks are available:
86
203
 
87
204
  ```sh
88
205
  bundle exec rake c:coverage # Requires GCC and gcovr
@@ -72,8 +72,6 @@ static inline size_t maximum_offset(
72
72
  //
73
73
  // Convolve
74
74
  //
75
- // Benchmark: 640x480 image, 8x8 kernel, 1000 iterations. 12.3 seconds.
76
- //
77
75
 
78
76
  void convolve_raw(
79
77
  int in_rank, const size_t *in_shape, const float *in_ptr,
@@ -117,7 +115,7 @@ void convolve_raw(
117
115
  offset = 0;
118
116
  corner_reset( out_rank, out_shape, out_q );
119
117
 
120
- // Main convolve loop
118
+ // Main convolution loop
121
119
  for ( i = 0; i < out_size; i++ ) {
122
120
  #if CONVOLVER_USE_SSE
123
121
  __m128 simd_x, simd_y, simd_t;
@@ -127,12 +125,16 @@ void convolve_raw(
127
125
  float t = 0.0;
128
126
 
129
127
  #if CONVOLVER_USE_SSE
130
- // Use SIMD for all the aligned values in groups of 4
128
+ // Contiguous kernel views need not start on a 16-byte boundary.
131
129
  for ( j = 0; j < kernel_aligned; j +=4 ) {
132
- simd_x = _mm_load_ps( kernel_ptr + j );
130
+ simd_x = _mm_loadu_ps( kernel_ptr + j );
133
131
  // Yes the backwards alignment is correct
134
- simd_y = _mm_set_ps( in_ptr[ offset + kernel_co_incr_cache[j+3] ], in_ptr[ offset + kernel_co_incr_cache[j+2] ],
135
- in_ptr[ offset + kernel_co_incr_cache[j+1] ], in_ptr[ offset + kernel_co_incr_cache[j] ] );
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
+ );
136
138
  simd_x = _mm_mul_ps( simd_x, simd_y );
137
139
  simd_t = _mm_add_ps( simd_x, simd_t );
138
140
  }
@@ -141,13 +143,13 @@ void convolve_raw(
141
143
  // SSE is unavailable on architectures such as Apple Silicon.
142
144
  // Use the same calculation without x86-specific intrinsics.
143
145
  for ( j = 0; j < kernel_aligned; j++ ) {
144
- t += in_ptr[ offset + kernel_co_incr_cache[j] ] * kernel_ptr[j];
146
+ t += in_ptr[ offset + kernel_co_incr_cache[kernel_size - 1 - j] ] * kernel_ptr[j];
145
147
  }
146
148
  #endif
147
149
 
148
150
  // Complete any remaining 1,2 or 3 items one at a time
149
151
  for ( j = kernel_aligned; j < kernel_size; j++ ) {
150
- t += in_ptr[ offset + kernel_co_incr_cache[j] ] * kernel_ptr[ j ];
152
+ t += in_ptr[ offset + kernel_co_incr_cache[kernel_size - 1 - j] ] * kernel_ptr[ j ];
151
153
  }
152
154
 
153
155
  #if CONVOLVER_USE_SSE
@@ -8,6 +8,8 @@
8
8
  #ifndef CONVOLVE_RAW_H
9
9
  #define CONVOLVE_RAW_H
10
10
 
11
+ #ifndef CONVOLVER_RAW_CONFIG
12
+ #define CONVOLVER_RAW_CONFIG
11
13
  #include <ruby.h>
12
14
  #if defined(__SSE__)
13
15
  #include <xmmintrin.h>
@@ -16,6 +18,7 @@
16
18
  #define CONVOLVER_USE_SSE 0
17
19
  #endif
18
20
  #define LARGEST_RANK 16
21
+ #endif
19
22
 
20
23
  void convolve_raw(
21
24
  int in_rank, const size_t *in_shape, const float *in_ptr,
@@ -2,10 +2,33 @@
2
2
  #include <numo/narray.h>
3
3
  #include <numo/intern.h>
4
4
 
5
+ #include "correlate_raw.h"
5
6
  #include "convolve_raw.h"
6
7
 
7
8
  static VALUE mConvolver;
8
9
 
10
+ /* 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));
13
+ }
14
+
15
+ static VALUE contiguous_sfloat(VALUE value) {
16
+ narray_t *narray;
17
+
18
+ value = rb_funcall(numo_cSFloat, rb_intern("cast"), 1, value);
19
+ GetNArray(value, narray);
20
+ /* A scalar has no strides; Numo's contiguous check requires an axis. */
21
+ if (narray->ndim > 0 && !RTEST(na_check_contiguous(value))) {
22
+ value = na_copy(value);
23
+ }
24
+ return value;
25
+ }
26
+
27
+ enum convolver_operation {
28
+ CONVOLVER_CORRELATION,
29
+ CONVOLVER_CONVOLUTION
30
+ };
31
+
9
32
  static void copy_shape(int rank, const size_t *source, size_t *target) {
10
33
  int i;
11
34
 
@@ -14,15 +37,7 @@ static void copy_shape(int rank, const size_t *source, size_t *target) {
14
37
  }
15
38
  }
16
39
 
17
- /*
18
- * Calculates a valid cross-correlation using the direct native implementation.
19
- *
20
- * @overload convolve_basic(signal, kernel)
21
- * @param signal [Numo::NArray] input values
22
- * @param kernel [Numo::NArray] correlation kernel
23
- * @return [Numo::SFloat] valid cross-correlation result
24
- */
25
- static VALUE convolver_convolve_basic(VALUE self, VALUE signal, VALUE kernel) {
40
+ static VALUE convolver_basic_valid(VALUE signal, VALUE kernel, enum convolver_operation operation) {
26
41
  volatile VALUE signal_value;
27
42
  volatile VALUE kernel_value;
28
43
  volatile VALUE result_value;
@@ -35,20 +50,12 @@ static VALUE convolver_convolve_basic(VALUE self, VALUE signal, VALUE kernel) {
35
50
  size_t result_shape[LARGEST_RANK];
36
51
  size_t numo_result_shape[LARGEST_RANK];
37
52
 
38
- (void)self;
39
-
40
53
  if (!rb_obj_is_kind_of(signal, numo_cNArray) || !rb_obj_is_kind_of(kernel, numo_cNArray)) {
41
54
  rb_raise(rb_eArgError, "signal and kernel must be Numo::NArray values");
42
55
  }
43
56
 
44
- signal_value = rb_funcall(numo_cSFloat, rb_intern("cast"), 1, signal);
45
- kernel_value = rb_funcall(numo_cSFloat, rb_intern("cast"), 1, kernel);
46
- if (!RTEST(na_check_contiguous(signal_value))) {
47
- signal_value = na_copy(signal_value);
48
- }
49
- if (!RTEST(na_check_contiguous(kernel_value))) {
50
- kernel_value = na_copy(kernel_value);
51
- }
57
+ signal_value = contiguous_sfloat(signal);
58
+ kernel_value = contiguous_sfloat(kernel);
52
59
  GetNArray(signal_value, signal_narray);
53
60
  GetNArray(kernel_value, kernel_narray);
54
61
 
@@ -76,16 +83,53 @@ static VALUE convolver_convolve_basic(VALUE self, VALUE signal, VALUE kernel) {
76
83
  }
77
84
 
78
85
  result_value = nary_new(numo_cSFloat, rank, numo_result_shape);
79
- convolve_raw(
80
- rank, signal_shape, (float *)na_get_pointer_for_read(signal_value),
81
- rank, kernel_shape, (float *)na_get_pointer_for_read(kernel_value),
82
- rank, result_shape, (float *)na_get_pointer_for_write(result_value)
83
- );
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
+ );
92
+ } 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
+ );
98
+ }
84
99
 
85
100
  return result_value;
86
101
  }
87
102
 
103
+ /*
104
+ * Calculates a valid cross-correlation using the direct native implementation.
105
+ *
106
+ * @private
107
+ * @overload correlate_basic_valid(signal, kernel)
108
+ * @param signal [Numo::NArray] input values
109
+ * @param kernel [Numo::NArray] correlation kernel
110
+ * @return [Numo::SFloat] valid cross-correlation result
111
+ */
112
+ static VALUE convolver_correlate_basic_valid(VALUE self, VALUE signal, VALUE kernel) {
113
+ (void)self;
114
+ return convolver_basic_valid(signal, kernel, CONVOLVER_CORRELATION);
115
+ }
116
+
117
+ /*
118
+ * Calculates a valid mathematical convolution using the direct native implementation.
119
+ *
120
+ * @private
121
+ * @overload convolve_basic_valid(signal, kernel)
122
+ * @param signal [Numo::NArray] input values
123
+ * @param kernel [Numo::NArray] convolution kernel
124
+ * @return [Numo::SFloat] valid mathematical convolution result
125
+ */
126
+ static VALUE convolver_convolve_basic_valid(VALUE self, VALUE signal, VALUE kernel) {
127
+ (void)self;
128
+ return convolver_basic_valid(signal, kernel, CONVOLVER_CONVOLUTION);
129
+ }
130
+
88
131
  void Init_convolver(void) {
89
132
  mConvolver = rb_define_module("Convolver");
90
- rb_define_singleton_method(mConvolver, "convolve_basic", convolver_convolve_basic, 2);
133
+ rb_define_singleton_method(mConvolver, "correlate_basic_valid", convolver_correlate_basic_valid, 2);
134
+ rb_define_singleton_method(mConvolver, "convolve_basic_valid", convolver_convolve_basic_valid, 2);
91
135
  }