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 +4 -4
- data/CHANGELOG.md +84 -2
- data/README.md +150 -51
- data/ext/convolver/convolve_raw.c +51 -163
- data/ext/convolver/convolve_raw.h +5 -16
- data/ext/convolver/convolver.c +90 -24
- data/ext/convolver/correlate_raw.c +57 -0
- data/ext/convolver/correlate_raw.h +14 -0
- data/ext/convolver/extconf.rb +1 -0
- data/ext/convolver/fft_shape.c +116 -0
- data/ext/convolver/fft_shape.h +8 -0
- data/ext/convolver/raw_config.h +11 -0
- data/ext/convolver/raw_helpers.h +71 -0
- data/ext/convolver/raw_loop.h +93 -0
- data/lib/convolver/buffer_limits.rb +39 -0
- data/lib/convolver/buffer_preparation.rb +56 -0
- data/lib/convolver/circular_fft_operation.rb +84 -0
- data/lib/convolver/cost_profiles.rb +44 -0
- data/lib/convolver/fft_buffer_plan.rb +39 -0
- data/lib/convolver/fft_cost.rb +52 -0
- data/lib/convolver/fft_estimator.rb +64 -0
- data/lib/convolver/fft_limits.rb +64 -0
- data/lib/convolver/linear_fft_operation.rb +52 -0
- data/lib/convolver/operation_dtype.rb +42 -0
- data/lib/convolver/operation_execution.rb +118 -0
- data/lib/convolver/operation_options.rb +13 -10
- data/lib/convolver/operation_plan.rb +34 -6
- data/lib/convolver/operation_shapes.rb +42 -9
- data/lib/convolver/preparation_cost.rb +46 -0
- data/lib/convolver/real_fft_shape.rb +76 -0
- data/lib/convolver/signal_extension.rb +3 -2
- data/lib/convolver/version.rb +1 -1
- data/lib/convolver.rb +101 -114
- metadata +28 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0461fd5af32c0104f8214df035709fea95c2b50f21f63d1b88c7999cadcafd29
|
|
4
|
+
data.tar.gz: 770518e0498a79c213c3be551f7749d6e20139a9fd327357f1d3608c0cbb72fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
## [
|
|
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/
|
|
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
|
[](https://github.com/neilslater/convolver/actions/workflows/ci.yml)
|
|
4
4
|
[](https://badge.fury.io/rb/convolver)
|
|
5
5
|
|
|
6
|
-
Convolver calculates cross-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
|
119
|
-
to every dimension or an array with one integer per dimension. For a
|
|
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
|
-
```
|
|
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.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
|
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
|
|
154
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
#
|
|
154
|
-
|
|
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
|
-
|
|
34
|
+
#define RAW_SIMD 0
|
|
157
35
|
#endif
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|