convolver 2.0.0 → 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: '08959da24c94d02889f0067f18f7233578b4b14ec4e58538b144efeef1cbe25f'
4
- data.tar.gz: c39df2ebf01a04f93fa62e49bd8a4542e78244868d822d7f47a00cc78b9aaa7e
3
+ metadata.gz: 0461fd5af32c0104f8214df035709fea95c2b50f21f63d1b88c7999cadcafd29
4
+ data.tar.gz: 770518e0498a79c213c3be551f7749d6e20139a9fd327357f1d3608c0cbb72fd
5
5
  SHA512:
6
- metadata.gz: 73d425b7851b21dd93cb135ca7ad88aa75f760b41a53ed8a2b72b2a9110e183062c1643af9563f81d59248d50d3f49a531e2bbac6d0c5cdcef6383717e31f73a
7
- data.tar.gz: 89ddc9f26950329b9ce2ce3917e5f90057c3c625f2210eb39fcc527cbe7b3f418f14f387665a3be3c1ea44d50beac101907c038c6ef9998998e30c21d61966d2
6
+ metadata.gz: 40b871a7ad8883742b27ff4b05ae830f38cbdd7f09f3fc6ef85fd26166bbc9613fe3fc7b3d73bd47f6ce45642dfa3e4b884bc69f9b88bc69028a043d5da5e4de
7
+ data.tar.gz: 506e52bd6f023d6fb169589e1763fe2a3668baf5158a04e545dd8bbc4de151e4b03ece49a51dd9b6d1b0f71666a662d9a148a269ecd96cd73c9ce6d0a2c0fa2b
data/CHANGELOG.md CHANGED
@@ -5,7 +5,88 @@ 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.
38
+
39
+ ## [3.0.1] - 2026-10-04
40
+
41
+ ### Changed
42
+
43
+ - Require `numo-pocketfft >= 0.7.0` and allow `numo-narray-alt < 0.12`,
44
+ enabling the NArray 0.11 series.
45
+
46
+ ### Fixed
47
+
48
+ - Read the logical values of contiguous signal and kernel slices in direct
49
+ convolution and correlation, including unaligned SSE kernels and scalar views.
50
+ - Retain a representable exact FFT shape when an optional fast candidate exceeds
51
+ the native element limit.
52
+ - Reject unrepresentable FFT buffers and native integer arithmetic before
53
+ allocation, including preparation, inverse staging and work buffers. Automatic
54
+ selection retains a safe direct path when FFT exceeds its limits.
55
+
56
+ ## [3.0.0] - 2026-08-22
57
+
58
+ ### Added
59
+
60
+ - Added the `correlate`, `correlate_basic`, and `correlate_fft` calculation
61
+ methods for cross-correlation, preserving the numerical operation exposed by
62
+ the version 2 `convolve*` methods.
63
+ - Added `predict_correlate_basic_time` and `predict_correlate_fft_time` as the
64
+ corresponding estimator family.
65
+ - Added distinct native correlation and convolution primitives whose hot loops
66
+ perform the required traversal directly without materializing a reversed
67
+ kernel.
68
+
69
+ ### Changed
70
+
71
+ - **Breaking:** Changed `convolve`, `convolve_basic`, and `convolve_fft` to
72
+ calculate mathematical discrete convolution. Version 2 users who need
73
+ unchanged results must rename these calls to their `correlate*` counterparts.
74
+ - **Breaking:** Changed the `predict_convolve_*_time` methods to estimate the
75
+ new mathematical convolution implementations.
76
+ - Defined operation-specific `:same` alignment. Correlation uses
77
+ `signal[i + j - anchor]`; convolution uses
78
+ `signal[i + anchor - j]`. This places the extra boundary sample for an even
79
+ kernel on opposite sides.
80
+ - Reworked linear PocketFFT calculations around real transforms and checked
81
+ exact/even or 2/3/5-smooth transform shapes. Circular calculations use real
82
+ transforms when an axis is even and retain complex transforms for all-odd
83
+ shapes.
84
+
85
+ ### Removed
86
+
87
+ - **Breaking:** Removed the deprecated `convolve_fftw3` compatibility method.
88
+ Use `correlate_fft` to preserve version 2 results or `convolve_fft` for
89
+ mathematical convolution.
9
90
 
10
91
  ## [2.0.0] - 2026-08-20
11
92
 
@@ -81,7 +162,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
81
162
  - Updated the native extension to use the maintained Numo C API and removed the
82
163
  legacy untyped-data compatibility code.
83
164
 
84
- [Unreleased]: https://github.com/neilslater/convolver/compare/v2.0.0...HEAD
165
+ [Unreleased]: https://github.com/neilslater/convolver/compare/v3.0.1...HEAD
166
+ [3.0.0]: https://github.com/neilslater/convolver/compare/v2.0.0...v3.0.0
85
167
  [2.0.0]: https://github.com/neilslater/convolver/compare/v1.0.1...v2.0.0
86
168
  [1.0.1]: https://github.com/neilslater/convolver/compare/v1.0.0...v1.0.1
87
169
  [1.0.0]: https://github.com/neilslater/convolver/compare/v0.3.2...v1.0.0
data/README.md CHANGED
@@ -3,16 +3,14 @@
3
3
  [![CI](https://github.com/neilslater/convolver/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/neilslater/convolver/actions/workflows/ci.yml)
4
4
  [![Gem Version](https://badge.fury.io/rb/convolver.svg)](https://badge.fury.io/rb/convolver)
5
5
 
6
- Convolver calculates cross-correlations between multidimensional
7
- [`Numo::NArray`](https://github.com/yoshoku/numo-narray-alt) values, with
8
- configurable output extents and signal boundary extensions. It chooses between
9
- a direct native implementation for smaller inputs and a
10
- [`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
11
12
  implementation for larger inputs.
12
13
 
13
- Version 1.0 replaces the unmaintained `narray` and `fftw3` gems with
14
- `numo-narray-alt` and `numo-pocketfft`.
15
-
16
14
  ## Installation
17
15
 
18
16
  Add Convolver to your application's Gemfile:
@@ -21,22 +19,16 @@ Add Convolver to your application's Gemfile:
21
19
  gem 'convolver'
22
20
  ```
23
21
 
24
- Then run:
25
-
26
- ```sh
27
- bundle install
28
- ```
29
-
30
- Alternatively, install it directly:
31
-
32
- ```sh
33
- gem install convolver
34
- ```
35
-
36
- 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.
37
25
 
38
26
  ## Usage
39
27
 
28
+ `convolve` calculates mathematical discrete convolution. `correlate`
29
+ calculates cross-correlation, following the public naming convention used by
30
+ NumPy and SciPy:
31
+
40
32
  ```ruby
41
33
  require 'convolver'
42
34
 
@@ -45,32 +37,43 @@ kernel = Numo::SFloat[1.3, -0.5]
45
37
 
46
38
  Convolver.convolve(signal, kernel)
47
39
  # => Numo::SFloat#shape=[2]
40
+ # [0.37, 0.45]
41
+
42
+ Convolver.correlate(signal, kernel)
43
+ # => Numo::SFloat#shape=[2]
48
44
  # [0.19, 0.27]
49
45
  ```
50
46
 
51
- With no keywords, Convolver preserves its original valid-correlation behavior:
52
- the signal and kernel must have the same rank, the kernel must be no larger
53
- than the signal in any dimension, and only positions with complete overlap are
54
- returned. The result size in each dimension is:
47
+ For real one-dimensional inputs, the two valid operations are:
55
48
 
56
- ```ruby
57
- 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]
58
52
  ```
59
53
 
60
- Inputs are converted to single-precision floats internally, and results are
61
- returned as `Numo::SFloat`. Supplying `Numo::SFloat` inputs avoids conversion in
62
- the direct implementation.
54
+ The formulas apply component-wise to multidimensional arrays. Correlation
55
+ conventionally conjugates the second operand, although Convolver's current
56
+ real input contract makes conjugation invisible.
63
57
 
64
- `Convolver.convolve` normally chooses the implementation automatically. The
65
- implementations can also be called directly for application-specific
66
- benchmarking:
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. Both inputs determine the floating result dtype,
61
+ or you can select it explicitly with `dtype:`.
62
+
63
+ The automatic, direct, and FFT implementations are available for both
64
+ operations:
67
65
 
68
66
  ```ruby
67
+ Convolver.convolve(signal, kernel)
69
68
  Convolver.convolve_basic(signal, kernel)
70
69
  Convolver.convolve_fft(signal, kernel)
70
+
71
+ Convolver.correlate(signal, kernel)
72
+ Convolver.correlate_basic(signal, kernel)
73
+ Convolver.correlate_fft(signal, kernel)
71
74
  ```
72
75
 
73
- Both implementations and the automatic method accept the same options:
76
+ Every calculation method accepts the same options:
74
77
 
75
78
  ```ruby
76
79
  Convolver.convolve(signal, kernel,
@@ -79,6 +82,41 @@ Convolver.convolve(signal, kernel,
79
82
  origin: 0)
80
83
  ```
81
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
+
82
120
  ### Output modes
83
121
 
84
122
  `mode:` controls the returned extent independently in each dimension:
@@ -113,45 +151,106 @@ passed with another boundary. `:reflect` repeats the edge sample; `:mirror`
113
151
  does not. All boundary modes work across every dimension, including
114
152
  length-one axes and extensions wider than the stored signal.
115
153
 
116
- ### Kernel origin
154
+ ### Kernel origin and alignment
117
155
 
118
- `origin:` shifts the kernel anchor for `:same`. It accepts one integer applied
119
- to every dimension or an array with one integer per dimension. For a kernel
120
- dimension of length `K`:
156
+ `origin:` shifts the stored-kernel anchor for `:same`. It accepts one integer
157
+ applied to every dimension or an array with one integer per dimension. For a
158
+ kernel dimension of length `K`:
121
159
 
122
- ```ruby
123
- anchor = (K / 2) + origin
160
+ ```text
161
+ anchor = floor(K / 2) + origin
162
+
163
+ correlation[i] = sum_j extended_signal[i + j - anchor] * kernel[j]
164
+ convolution[i] = sum_j extended_signal[i + anchor - j] * kernel[j]
124
165
  ```
125
166
 
126
- The anchor must remain within the kernel. Positive origins sample farther
127
- toward lower signal indices. With the default origin, an even kernel gives the
128
- extra boundary sample to the lower-index, or left, side: a length-four kernel
129
- uses two samples before and one after the aligned signal position. Nonzero
130
- origins are supported only for `:same`; `:valid` and `:full` require zero.
167
+ The anchor must remain within the kernel. Correlation pads `anchor` samples
168
+ before the signal and `K - 1 - anchor` after it; convolution swaps those
169
+ widths. Odd centered kernels therefore align alike. For an even centered
170
+ kernel, correlation puts the extra sample before the signal and convolution
171
+ puts it after the signal. Positive origins move correlation toward lower signal
172
+ indices and convolution toward higher signal indices.
173
+
174
+ Nonzero origins are supported only for `:same`; `:valid` and `:full` require
175
+ zero.
176
+
177
+ ### Estimators
131
178
 
132
- The estimator methods accept and validate the same options:
179
+ The estimator methods accept and validate the same options as their calculation
180
+ families:
133
181
 
134
182
  ```ruby
135
183
  Convolver.predict_convolve_basic_time(signal, kernel, mode: :same, boundary: :nearest)
136
184
  Convolver.predict_convolve_fft_time(signal, kernel, mode: :same, boundary: :wrap)
185
+
186
+ Convolver.predict_correlate_basic_time(signal, kernel, mode: :same, boundary: :nearest)
187
+ Convolver.predict_correlate_fft_time(signal, kernel, mode: :same, boundary: :wrap)
137
188
  ```
138
189
 
139
- `Convolver.convolve_fftw3` remains as a deprecated alias for `convolve_fft` to
140
- ease migration from Convolver 0.x and forwards all options.
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
+
198
+ Calculations and estimators reject unrepresentable buffer sizes with `RangeError`
199
+ before allocation. FFT planning also checks conservative PocketFFT integer and
200
+ work-buffer limits, including full complex staging for real inverse transforms.
201
+ Automatic selection uses direct calculation when FFT exceeds its limits and the
202
+ direct path remains valid. These checks do not reserve memory; ordinary allocator
203
+ exhaustion still raises `NoMemoryError`.
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
+
214
+ ### Migrating from version 2
215
+
216
+ Version 2's `convolve*` methods calculated cross-correlation. Version 3 corrects
217
+ the terminology and changes every `convolve*` method to mathematical
218
+ convolution. Asymmetric kernels make the result change visible.
219
+
220
+ To preserve version 2 results, rename the complete method family:
221
+
222
+ | Version 2 | Version 3 equivalent |
223
+ | --- | --- |
224
+ | `convolve` | `correlate` |
225
+ | `convolve_basic` | `correlate_basic` |
226
+ | `convolve_fft` | `correlate_fft` |
227
+ | `predict_convolve_basic_time` | `predict_correlate_basic_time` |
228
+ | `predict_convolve_fft_time` | `predict_correlate_fft_time` |
229
+ | `convolve_fftw3` | `correlate_fft` |
230
+
231
+ `convolve_fftw3` has been removed. No `cross_correlate` aliases are provided;
232
+ the standard `correlate` name denotes cross-correlation explicitly documented
233
+ above.
141
234
 
142
235
  ## Contributing
143
236
 
144
- Install the development dependencies, then run the main suite:
237
+ Install the development dependencies, then run the complete local gate:
145
238
 
146
239
  ```sh
147
240
  bundle install
148
241
  bundle exec rake
149
242
  bundle exec rubocop
243
+ bundle exec ncs-rubocop-conf-audit
150
244
  bundle exec rake c:lint
245
+ bundle exec bundle-audit check --update
246
+ bundle exec rake docs:check
151
247
  ```
152
248
 
153
- The Ruby specs exercise both the Ruby API and native extension. Additional
154
- native-code checks are available:
249
+ The Ruby specs exercise both the Ruby API and native extension and enforce 95%
250
+ line and branch coverage for the Ruby library. Dependency auditing refreshes
251
+ the advisory database and requires network access. The documentation check
252
+ rejects YARD warnings and undocumented public API objects; private implementation
253
+ classes are marked `@private`. Additional native-code checks are available:
155
254
 
156
255
  ```sh
157
256
  bundle exec rake c:coverage # Requires GCC and gcovr
@@ -1,169 +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
- // Benchmark: 640x480 image, 8x8 kernel, 1000 iterations. 12.3 seconds.
76
- //
77
-
78
- void convolve_raw(
79
- int in_rank, const size_t *in_shape, const float *in_ptr,
80
- int kernel_rank, const size_t *kernel_shape, const float *kernel_ptr,
81
- int out_rank, const size_t *out_shape, float *out_ptr ) {
82
- size_t i, j, input_size, kernel_size, kernel_aligned, out_size, offset;
83
- size_t maximum_input_offset;
84
- size_t out_co_incr[LARGEST_RANK + 1], kernel_co_incr[LARGEST_RANK + 1];
85
- size_t ker_q[LARGEST_RANK], out_q[LARGEST_RANK];
86
- size_t *kernel_co_incr_cache;
87
- VALUE cache_storage = 0;
88
-
89
- kernel_size = size_from_shape( kernel_rank, kernel_shape, "kernel size" );
90
- kernel_aligned = kernel_size - kernel_size % 4;
91
- out_size = size_from_shape( out_rank, out_shape, "output size" );
92
- input_size = size_from_shape( in_rank, in_shape, "input size" );
93
-
94
- calc_co_increment( in_rank, in_shape, out_shape, out_co_incr );
95
- calc_co_increment( in_rank, in_shape, kernel_shape, kernel_co_incr );
96
- maximum_input_offset = checked_add(
97
- maximum_offset( in_rank, in_shape, out_shape, "input offset" ),
98
- maximum_offset( in_rank, in_shape, kernel_shape, "input offset" ),
99
- "input offset"
100
- );
101
- if ( maximum_input_offset >= input_size ) {
102
- rb_raise( rb_eRangeError, "input offset exceeds native implementation limit" );
103
- }
104
-
105
- kernel_co_incr_cache = RB_ALLOCV_N( size_t, cache_storage, kernel_size );
106
- kernel_co_incr_cache[0] = 0;
107
-
108
- corner_reset( kernel_rank, kernel_shape, ker_q );
109
- for ( i = 1; i < kernel_size; i++ ) {
110
- kernel_co_incr_cache[i] = checked_add(
111
- kernel_co_incr_cache[i-1],
112
- kernel_co_incr[ corner_dec( kernel_rank, kernel_shape, ker_q ) ],
113
- "kernel offset"
114
- );
115
- }
116
-
117
- offset = 0;
118
- corner_reset( out_rank, out_shape, out_q );
119
-
120
- // Main convolve loop
121
- for ( i = 0; i < out_size; i++ ) {
122
- #if CONVOLVER_USE_SSE
123
- __m128 simd_x, simd_y, simd_t;
124
- float v[4];
125
- simd_t = _mm_setzero_ps();
126
- #endif
127
- float t = 0.0;
128
-
129
- #if CONVOLVER_USE_SSE
130
- // Use SIMD for all the aligned values in groups of 4
131
- for ( j = 0; j < kernel_aligned; j +=4 ) {
132
- simd_x = _mm_load_ps( kernel_ptr + j );
133
- // 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] ] );
136
- simd_x = _mm_mul_ps( simd_x, simd_y );
137
- simd_t = _mm_add_ps( simd_x, simd_t );
138
- }
139
- _mm_store_ps( v, simd_t );
4
+ #define RAW_INDEX(j) (kernel_size - 1 - (j))
5
+ #if defined(__SSE__)
6
+ #define RAW_SIMD 1
140
7
  #else
141
- // SSE is unavailable on architectures such as Apple Silicon.
142
- // Use the same calculation without x86-specific intrinsics.
143
- for ( j = 0; j < kernel_aligned; j++ ) {
144
- t += in_ptr[ offset + kernel_co_incr_cache[j] ] * kernel_ptr[j];
145
- }
8
+ #define RAW_SIMD 0
146
9
  #endif
147
-
148
- // Complete any remaining 1,2 or 3 items one at a time
149
- for ( j = kernel_aligned; j < kernel_size; j++ ) {
150
- t += in_ptr[ offset + kernel_co_incr_cache[j] ] * kernel_ptr[ j ];
151
- }
152
-
153
- #if CONVOLVER_USE_SSE
154
- 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
155
33
  #else
156
- out_ptr[i] = t;
34
+ #define RAW_SIMD 0
157
35
  #endif
158
-
159
- if ( i + 1 < out_size ) {
160
- offset = checked_add(
161
- offset,
162
- out_co_incr[ corner_dec( out_rank, out_shape, out_q ) ],
163
- "input offset"
164
- );
165
- }
166
- }
167
-
168
- RB_ALLOCV_END( cache_storage );
169
- }
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,25 +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
- #include <ruby.h>
12
- #if defined(__SSE__)
13
- #include <xmmintrin.h>
14
- #define CONVOLVER_USE_SSE 1
15
- #else
16
- #define CONVOLVER_USE_SSE 0
17
- #endif
18
- #define LARGEST_RANK 16
3
+ #include "raw_config.h"
19
4
 
20
5
  void convolve_raw(
21
6
  int in_rank, const size_t *in_shape, const float *in_ptr,
22
7
  int kernel_rank, const size_t *kernel_shape, const float *kernel_ptr,
23
8
  int out_rank, const size_t *out_shape, float *out_ptr );
24
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 );
25
14
  #endif