convolver 0.3.1 → 0.4.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 +5 -5
- data/.github/workflows/ci.yml +40 -0
- data/.rubocop.yml +16 -0
- data/Gemfile +11 -0
- data/README.md +28 -7
- data/Rakefile +8 -6
- data/convolver.gemspec +17 -21
- data/ext/convolver/convolve_raw.c +15 -1
- data/ext/convolver/convolve_raw.h +5 -0
- data/ext/convolver/convolver.c +0 -1
- data/ext/convolver/extconf.rb +20 -10
- data/lib/convolver/version.rb +3 -1
- data/lib/convolver.rb +41 -61
- data/spec/convolver_basic_spec.rb +89 -0
- data/spec/convolver_fftw3_spec.rb +166 -0
- data/spec/convolver_spec.rb +53 -0
- data/spec/helpers.rb +17 -17
- metadata +23 -114
- data/.travis.yml +0 -11
- data/spec/convolve_basic_spec.rb +0 -84
- data/spec/convolve_fftw3_spec.rb +0 -161
- data/spec/convolve_spec.rb +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9c83dcadafabcbb8a971258601e61697a35d73afc28e7341bbe9d6f5fee887e3
|
|
4
|
+
data.tar.gz: dfc6018d6b2ae5a9ea7570036874f036961e9925c18e41116b481b4604f0cc56
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41b62c830ba924ddc25abcf92363fcf474477d806e4040d7c694496ed1eda15d3929e4bfeea2597b7ee372baa28c44991d69c9941cfa7db753070ca8bac7b90c
|
|
7
|
+
data.tar.gz: 0fd4024a1a4eb0c1b0a6059e6f606508e048dc4a6e56d5499e0c5b94d85669ba17831472a0c3167e1361b8b25ec299d665cab3a4bc38162c363c756dddb60ecc
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Ruby ${{ matrix.ruby }}
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
timeout-minutes: 10
|
|
14
|
+
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
ruby:
|
|
19
|
+
- '3.3'
|
|
20
|
+
- '3.4'
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Check out repository
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Install FFTW
|
|
27
|
+
run: sudo apt-get update && sudo apt-get install --yes libfftw3-dev
|
|
28
|
+
|
|
29
|
+
- name: Set up Ruby
|
|
30
|
+
uses: ruby/setup-ruby@v1
|
|
31
|
+
with:
|
|
32
|
+
ruby-version: ${{ matrix.ruby }}
|
|
33
|
+
bundler: latest
|
|
34
|
+
bundler-cache: true
|
|
35
|
+
|
|
36
|
+
- name: Compile extension and run specs
|
|
37
|
+
run: bundle exec rake compile test
|
|
38
|
+
|
|
39
|
+
- name: Run RuboCop
|
|
40
|
+
run: bundle exec rubocop
|
data/.rubocop.yml
ADDED
data/Gemfile
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
source 'https://rubygems.org'
|
|
2
4
|
|
|
3
5
|
# Specify your gem's dependencies in convolver.gemspec
|
|
4
6
|
gemspec
|
|
7
|
+
|
|
8
|
+
gem 'rake', '>= 1.9.1'
|
|
9
|
+
gem 'rake-compiler', '>= 0.8.3'
|
|
10
|
+
gem 'rspec', '>= 2.13.0'
|
|
11
|
+
gem 'rubocop', '~> 1.85'
|
|
12
|
+
gem 'rubocop-rake', '~> 0.7'
|
|
13
|
+
gem 'rubocop-rspec', '~> 3.9'
|
|
14
|
+
gem 'simplecov', '>= 0.22'
|
|
15
|
+
gem 'yard', '>= 0.8.7.2'
|
data/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Convolver
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/rb/convolver)
|
|
4
|
-
[](https://travis-ci.com/github/neilslater/convolver)
|
|
5
|
+
[](https://coveralls.io/r/neilslater/convolver?branch=master)
|
|
6
|
+
[](https://codeclimate.com/github/neilslater/convolver)
|
|
6
7
|
|
|
7
8
|
Calculates discrete convolution between two multi-dimensional arrays of floats.
|
|
8
9
|
See http://en.wikipedia.org/wiki/Convolution
|
|
@@ -14,6 +15,27 @@ See http://en.wikipedia.org/wiki/Convolution
|
|
|
14
15
|
Before you install *convolver*, you should install the FFTW3 library on your system.
|
|
15
16
|
See http://www.fftw.org/ for details.
|
|
16
17
|
|
|
18
|
+
On macOS with Homebrew, the `fftw3` Ruby gem needs to be told where Homebrew
|
|
19
|
+
installed the headers and libraries:
|
|
20
|
+
|
|
21
|
+
brew install fftw
|
|
22
|
+
bundle config set --local build.fftw3 "--with-fftw3-dir=$(brew --prefix fftw)"
|
|
23
|
+
bundle install
|
|
24
|
+
|
|
25
|
+
The local Bundler setting is written to `.bundle/config`, which is intentionally
|
|
26
|
+
not committed because the Homebrew prefix depends on the machine.
|
|
27
|
+
|
|
28
|
+
### Known warning with Ruby 3.4
|
|
29
|
+
|
|
30
|
+
The first use of `NArray` may emit this warning:
|
|
31
|
+
|
|
32
|
+
warning: undefining the allocator of T_DATA class NArray
|
|
33
|
+
|
|
34
|
+
This comes from the legacy native allocation API used by `narray` 0.6.1.2. Ruby
|
|
35
|
+
disables the inherited allocator when the first native NArray object is created.
|
|
36
|
+
The warning does not affect convolver's results; removing it properly requires a
|
|
37
|
+
fix in `narray` or a future migration to a maintained NArray implementation.
|
|
38
|
+
|
|
17
39
|
### Installing the gem
|
|
18
40
|
|
|
19
41
|
Add this line to your application's Gemfile:
|
|
@@ -51,11 +73,6 @@ small convolutions are processed directly by multiplying out all combinations an
|
|
|
51
73
|
and large convolutions are processed using FFTW3 to convert to frequency space where convolution
|
|
52
74
|
is simpler and faster to calculate, then convert back.
|
|
53
75
|
|
|
54
|
-
## Convolutional Neural Nets
|
|
55
|
-
|
|
56
|
-
Code for CNNs in Ruby, based on the convolve_basic method from this gem, is planned for a
|
|
57
|
-
new gem ```co_ne_ne```.
|
|
58
|
-
|
|
59
76
|
## Contributing
|
|
60
77
|
|
|
61
78
|
1. Fork it
|
|
@@ -63,3 +80,7 @@ new gem ```co_ne_ne```.
|
|
|
63
80
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
64
81
|
4. Push to the branch (`git push origin my-new-feature`)
|
|
65
82
|
5. Create new Pull Request
|
|
83
|
+
|
|
84
|
+
## Contributors
|
|
85
|
+
|
|
86
|
+
* [Dima Ermilov](https://github.com/adworse) contributed fix to support compiling under Windows.
|
data/Rakefile
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
3
5
|
require 'rake/extensiontask'
|
|
4
6
|
|
|
5
|
-
desc
|
|
7
|
+
desc 'Convolver unit tests'
|
|
6
8
|
RSpec::Core::RakeTask.new(:test) do |t|
|
|
7
|
-
t.pattern =
|
|
9
|
+
t.pattern = 'spec/*_spec.rb'
|
|
8
10
|
t.verbose = true
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
gemspec = Gem::Specification.load('convolver.gemspec')
|
|
12
14
|
Rake::ExtensionTask.new do |ext|
|
|
13
15
|
ext.name = 'convolver'
|
|
14
|
-
ext.source_pattern =
|
|
16
|
+
ext.source_pattern = '*.{c,h}'
|
|
15
17
|
ext.ext_dir = 'ext/convolver'
|
|
16
18
|
ext.lib_dir = 'lib/convolver'
|
|
17
19
|
ext.gem_spec = gemspec
|
|
18
20
|
end
|
|
19
21
|
|
|
20
|
-
task :
|
|
22
|
+
task default: %i[compile test]
|
data/convolver.gemspec
CHANGED
|
@@ -1,31 +1,27 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
6
|
require 'convolver/version'
|
|
5
7
|
|
|
6
8
|
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name =
|
|
9
|
+
spec.name = 'convolver'
|
|
8
10
|
spec.version = Convolver::VERSION
|
|
9
|
-
spec.authors = [
|
|
10
|
-
spec.email = [
|
|
11
|
-
spec.description =
|
|
12
|
-
spec.summary =
|
|
13
|
-
spec.homepage =
|
|
14
|
-
spec.license =
|
|
15
|
-
|
|
16
|
-
spec.add_dependency "narray", ">= 0.6.0.8"
|
|
17
|
-
spec.add_dependency "fftw3", ">= 0.3"
|
|
11
|
+
spec.authors = ['Neil Slater']
|
|
12
|
+
spec.email = ['slobo777@gmail.com']
|
|
13
|
+
spec.description = 'Convolution for NArray'
|
|
14
|
+
spec.summary = 'Convolution for NArray'
|
|
15
|
+
spec.homepage = 'http://github.com/neilslater/convolver'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
spec.required_ruby_version = '>= 3.2'
|
|
18
18
|
|
|
19
|
-
spec.
|
|
20
|
-
spec.
|
|
21
|
-
spec.add_development_dependency "rspec", ">= 2.13.0"
|
|
22
|
-
spec.add_development_dependency "mocha", ">= 0.14.0"
|
|
23
|
-
spec.add_development_dependency "rake", ">= 1.9.1"
|
|
24
|
-
spec.add_development_dependency "rake-compiler", ">= 0.8.3"
|
|
19
|
+
spec.add_dependency 'fftw3', '>= 0.3'
|
|
20
|
+
spec.add_dependency 'narray', '>= 0.6.0.8'
|
|
25
21
|
|
|
26
|
-
spec.files = `git ls-files`.split(
|
|
22
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
|
27
23
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
28
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
29
24
|
spec.extensions = spec.files.grep(%r{/extconf\.rb$})
|
|
30
|
-
spec.require_paths = [
|
|
25
|
+
spec.require_paths = ['lib']
|
|
26
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
31
27
|
end
|
|
@@ -76,13 +76,16 @@ void convolve_raw(
|
|
|
76
76
|
|
|
77
77
|
// Main convolve loop
|
|
78
78
|
for ( i = 0; i < out_size; i++ ) {
|
|
79
|
+
#if CONVOLVER_USE_SSE
|
|
79
80
|
__m128 simd_x, simd_y, simd_t;
|
|
80
|
-
float t = 0.0;
|
|
81
81
|
float v[4];
|
|
82
82
|
simd_t = _mm_setzero_ps();
|
|
83
|
+
#endif
|
|
84
|
+
float t = 0.0;
|
|
83
85
|
|
|
84
86
|
offset += out_co_incr[ corner_dec( out_rank, out_shape, out_q ) ];
|
|
85
87
|
|
|
88
|
+
#if CONVOLVER_USE_SSE
|
|
86
89
|
// Use SIMD for all the aligned values in groups of 4
|
|
87
90
|
for ( j = 0; j < kernel_aligned; j +=4 ) {
|
|
88
91
|
simd_x = _mm_load_ps( kernel_ptr + j );
|
|
@@ -93,13 +96,24 @@ void convolve_raw(
|
|
|
93
96
|
simd_t = _mm_add_ps( simd_x, simd_t );
|
|
94
97
|
}
|
|
95
98
|
_mm_store_ps( v, simd_t );
|
|
99
|
+
#else
|
|
100
|
+
// SSE is unavailable on architectures such as Apple Silicon.
|
|
101
|
+
// Use the same calculation without x86-specific intrinsics.
|
|
102
|
+
for ( j = 0; j < kernel_aligned; j++ ) {
|
|
103
|
+
t += in_ptr[ offset + kernel_co_incr_cache[j] ] * kernel_ptr[j];
|
|
104
|
+
}
|
|
105
|
+
#endif
|
|
96
106
|
|
|
97
107
|
// Complete any remaining 1,2 or 3 items one at a time
|
|
98
108
|
for ( j = kernel_aligned; j < kernel_size; j++ ) {
|
|
99
109
|
t += in_ptr[ offset + kernel_co_incr_cache[j] ] * kernel_ptr[ j ];
|
|
100
110
|
}
|
|
101
111
|
|
|
112
|
+
#if CONVOLVER_USE_SSE
|
|
102
113
|
out_ptr[i] = v[0] + v[1] + v[2] + v[3] + t;
|
|
114
|
+
#else
|
|
115
|
+
out_ptr[i] = t;
|
|
116
|
+
#endif
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
xfree( kernel_co_incr_cache );
|
data/ext/convolver/convolver.c
CHANGED
data/ext/convolver/extconf.rb
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# ext/convolver/extconf.rb
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
+
require 'mkmf'
|
|
5
|
+
require 'rubygems'
|
|
4
6
|
|
|
5
7
|
# Following code stolen shamelessly from fftw3 gem:
|
|
6
|
-
narray_dir =
|
|
7
|
-
|
|
8
|
+
narray_dir = begin
|
|
9
|
+
File.dirname(Gem.find_files('narray.h').first)
|
|
10
|
+
rescue StandardError
|
|
11
|
+
$sitearchdir
|
|
12
|
+
end
|
|
13
|
+
if /cygwin|mingw/ =~ RUBY_PLATFORM
|
|
14
|
+
dir_config('narray', narray_dir, "#{narray_dir}/src")
|
|
15
|
+
else
|
|
16
|
+
dir_config('narray', narray_dir, narray_dir)
|
|
17
|
+
end
|
|
8
18
|
|
|
9
|
-
|
|
10
|
-
|
|
19
|
+
unless have_header('narray.h') && have_header('narray_config.h')
|
|
20
|
+
print <<-ERROR_MESSAGE
|
|
11
21
|
** configure error **
|
|
12
22
|
Header narray.h or narray_config.h is not found. If you have these files in
|
|
13
23
|
/narraydir/include, try the following:
|
|
14
24
|
|
|
15
25
|
% ruby extconf.rb --with-narray-include=/narraydir/include
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
ERROR_MESSAGE
|
|
28
|
+
exit(-1)
|
|
19
29
|
end
|
|
20
30
|
|
|
21
31
|
# This also stolen from fftw3 gem (and not confirmed for Windows platforms - please let me know if it works!)
|
|
22
32
|
if /cygwin|mingw/ =~ RUBY_PLATFORM
|
|
23
|
-
|
|
33
|
+
have_library('narray') || raise('ERROR: narray library is not found')
|
|
24
34
|
end
|
|
25
35
|
|
|
26
36
|
$CFLAGS << ' -O3 -funroll-loops'
|
|
27
|
-
create_makefile(
|
|
37
|
+
create_makefile('convolver/convolver')
|
data/lib/convolver/version.rb
CHANGED
data/lib/convolver.rb
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'narray'
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
+
require 'convolver/convolver'
|
|
5
|
+
require 'convolver/version'
|
|
4
6
|
require 'fftw3'
|
|
5
7
|
|
|
8
|
+
# Convolution operations for NArray values.
|
|
6
9
|
module Convolver
|
|
7
10
|
# Chooses and calls likely fastest method from #convolve_basic and #convolve_fftw3.
|
|
8
11
|
# The two parameters must have the same rank. The output has same rank, its size in each
|
|
@@ -13,24 +16,19 @@ module Convolver
|
|
|
13
16
|
# @param [NArray] signal must be same size or larger than kernel in each dimension
|
|
14
17
|
# @param [NArray] kernel must be same size or smaller than signal in each dimension
|
|
15
18
|
# @return [NArray] result of convolving signal with kernel
|
|
16
|
-
def self.convolve
|
|
19
|
+
def self.convolve(signal, kernel)
|
|
17
20
|
# For small signals or kernels, just go straight to basic
|
|
18
|
-
if signal.size < 1000 || kernel.size < 100
|
|
19
|
-
return convolve_basic( signal, kernel )
|
|
20
|
-
end
|
|
21
|
+
return convolve_basic(signal, kernel) if signal.size < 1000 || kernel.size < 100
|
|
21
22
|
|
|
22
23
|
# If predicted time is less than a millisecond, just do a basic convolve
|
|
23
|
-
basic_time_predicted = predict_convolve_basic_time(
|
|
24
|
-
if basic_time_predicted < 0.1
|
|
25
|
-
return convolve_basic( signal, kernel )
|
|
26
|
-
end
|
|
24
|
+
basic_time_predicted = predict_convolve_basic_time(signal, kernel)
|
|
25
|
+
return convolve_basic(signal, kernel) if basic_time_predicted < 0.1
|
|
27
26
|
|
|
28
27
|
# Factor of two to allow for large uncertainty in predictions for FFTW3
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
28
|
+
fft_time_predicted = predict_convolve_fft_time(signal, kernel)
|
|
29
|
+
return convolve_fftw3(signal, kernel) if fft_time_predicted < 2 * basic_time_predicted
|
|
32
30
|
|
|
33
|
-
convolve_basic(
|
|
31
|
+
convolve_basic(signal, kernel)
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
# Uses FFTW3 library to calculate convolution of an array of floats representing a signal,
|
|
@@ -40,21 +38,12 @@ module Convolver
|
|
|
40
38
|
# @param [NArray] signal must be same size or larger than kernel in each dimension
|
|
41
39
|
# @param [NArray] kernel must be same size or smaller than signal in each dimension
|
|
42
40
|
# @return [NArray] result of convolving signal with kernel
|
|
43
|
-
def self.convolve_fftw3
|
|
44
|
-
combined_shape, shift_by, ranges = fft_offsets(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
mod_a[*shift_by] = signal
|
|
41
|
+
def self.convolve_fftw3(signal, kernel)
|
|
42
|
+
combined_shape, shift_by, ranges = fft_offsets(signal.shape, kernel.shape)
|
|
43
|
+
mod_a, mod_b = fft_inputs(signal, kernel, combined_shape, shift_by)
|
|
44
|
+
cfreqs = FFTW3.fft(mod_a) * FFTW3.fft(mod_b)
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Convolver.fit_kernel_backwards( mod_b, kernel )
|
|
52
|
-
|
|
53
|
-
afreqs = FFTW3.fft(mod_a)
|
|
54
|
-
bfreqs = FFTW3.fft(mod_b)
|
|
55
|
-
cfreqs = afreqs * bfreqs
|
|
56
|
-
|
|
57
|
-
(FFTW3.ifft( cfreqs ).real * (1.0/mod_a.size))[*ranges]
|
|
46
|
+
(FFTW3.ifft(cfreqs).real * (1.0 / mod_a.size))[*ranges]
|
|
58
47
|
end
|
|
59
48
|
|
|
60
49
|
# A rough estimate of time that #convolve_fftw3 will take, based on complexity
|
|
@@ -63,8 +52,8 @@ module Convolver
|
|
|
63
52
|
# @param [NArray] signal must be same size or larger than kernel in each dimension
|
|
64
53
|
# @param [NArray] kernel must be same size or smaller than signal in each dimension
|
|
65
54
|
# @return [Float] rough estimate of time for convolution compared to baseline
|
|
66
|
-
def self.predict_convolve_fft_time
|
|
67
|
-
16 * 4.55e-08 *
|
|
55
|
+
def self.predict_convolve_fft_time(signal, kernel)
|
|
56
|
+
16 * 4.55e-08 * result_shape(signal.shape, kernel.shape).inject(1) { |t, x| t * x * Math.log(x) }
|
|
68
57
|
end
|
|
69
58
|
|
|
70
59
|
# A rough estimate of time that #convolve will take, based on complexity
|
|
@@ -73,28 +62,17 @@ module Convolver
|
|
|
73
62
|
# @param [NArray] signal must be same size or larger than kernel in each dimension
|
|
74
63
|
# @param [NArray] kernel must be same size or smaller than signal in each dimension
|
|
75
64
|
# @return [Float] rough estimate of time for convolution compared to baseline
|
|
76
|
-
def self.predict_convolve_basic_time
|
|
77
|
-
outputs = shape_to_size(
|
|
78
|
-
4.54e-12 * (outputs * shape_to_size(
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
private
|
|
82
|
-
|
|
83
|
-
def self.shape_to_size shape
|
|
84
|
-
shape.inject(1) { |t,x| t * x }
|
|
65
|
+
def self.predict_convolve_basic_time(signal, kernel)
|
|
66
|
+
outputs = shape_to_size(result_shape(signal.shape, kernel.shape))
|
|
67
|
+
4.54e-12 * (outputs * shape_to_size(signal.shape) * shape_to_size(kernel.shape))
|
|
85
68
|
end
|
|
86
69
|
|
|
87
|
-
def self.
|
|
88
|
-
|
|
89
|
-
signal_shape.each_with_index do |signal_size, i|
|
|
90
|
-
kernel_size = kernel_shape[i]
|
|
91
|
-
combined_shape[i] = signal_size + kernel_size - 1
|
|
92
|
-
end
|
|
93
|
-
combined_shape
|
|
70
|
+
def self.shape_to_size(shape)
|
|
71
|
+
shape.inject(1) { |t, x| t * x }
|
|
94
72
|
end
|
|
95
73
|
|
|
96
|
-
def self.result_shape
|
|
97
|
-
result_shape = [
|
|
74
|
+
def self.result_shape(signal_shape, kernel_shape)
|
|
75
|
+
result_shape = []
|
|
98
76
|
signal_shape.each_with_index do |signal_size, i|
|
|
99
77
|
kernel_size = kernel_shape[i]
|
|
100
78
|
result_shape[i] = signal_size - kernel_size + 1
|
|
@@ -102,19 +80,21 @@ module Convolver
|
|
|
102
80
|
result_shape
|
|
103
81
|
end
|
|
104
82
|
|
|
105
|
-
def self.fft_offsets
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
ranges = []
|
|
109
|
-
signal_shape.each_with_index do |signal_size, i|
|
|
110
|
-
kernel_size = kernel_shape[i]
|
|
83
|
+
def self.fft_offsets(signal_shape, kernel_shape)
|
|
84
|
+
signal_shape.zip(kernel_shape).map { |sizes| fft_offset(*sizes) }.transpose
|
|
85
|
+
end
|
|
111
86
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
87
|
+
def self.fft_inputs(signal, kernel, combined_shape, shift_by)
|
|
88
|
+
signal_input = NArray.sfloat(*combined_shape)
|
|
89
|
+
signal_input[*shift_by] = signal
|
|
90
|
+
kernel_input = NArray.sfloat(*combined_shape)
|
|
91
|
+
fit_kernel_backwards(kernel_input, kernel)
|
|
92
|
+
[signal_input, kernel_input]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.fft_offset(signal_size, kernel_size)
|
|
96
|
+
output_offset = kernel_size - 1
|
|
97
|
+
output_size = signal_size - kernel_size + 1
|
|
98
|
+
[signal_size + kernel_size - 1, kernel_size / 2, output_offset...(output_offset + output_size)]
|
|
119
99
|
end
|
|
120
100
|
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'helpers'
|
|
4
|
+
|
|
5
|
+
describe Convolver do
|
|
6
|
+
describe '#convolve_basic' do
|
|
7
|
+
it 'works like the example in the README' do
|
|
8
|
+
a = NArray[0.3, 0.4, 0.5]
|
|
9
|
+
b = NArray[1.3, -0.5]
|
|
10
|
+
c = described_class.convolve_basic(a, b)
|
|
11
|
+
expect(c).to be_narray_like NArray[0.19, 0.27]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'calculates a 2D convolution' do
|
|
15
|
+
a = NArray[[0.3, 0.4, 0.5], [0.6, 0.8, 0.2], [0.9, 1.0, 0.1]]
|
|
16
|
+
b = NArray[[1.2, -0.5], [0.5, -1.3]]
|
|
17
|
+
c = described_class.convolve_basic(a, b)
|
|
18
|
+
expect(c).to be_narray_like NArray[[-0.58, 0.37], [-0.53, 1.23]]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'calculates a 2D convolution with rectangular arrays' do
|
|
22
|
+
a = NArray[ [0.3, 0.4, 0.5, 0.3, 0.4], [0.6, 0.8, 0.2, 0.8, 0.2],
|
|
23
|
+
[0.9, 1.0, 0.1, 0.9, 1.0], [0.5, 0.9, 0.3, 0.2, 0.8], [0.7, 0.1, 0.3, 0.0, 0.1],
|
|
24
|
+
[0.4, 0.5, 0.6, 0.7, 0.8], [0.5, 0.4, 0.3, 0.2, 0.1] ]
|
|
25
|
+
b = NArray[[1.2, -0.5, 0.2], [1.8, 0.5, -1.3]]
|
|
26
|
+
c = described_class.convolve_basic(a, b)
|
|
27
|
+
expect(c).to be_narray_like NArray[ [1.48, 0.79, 1.03], [2.35, 1.7, -0.79], [1.56, 2.84, -0.53],
|
|
28
|
+
[1.13, 1.3, 0.83], [1.04, 0.26, 0.77], [1.06, 1.05, 1.04] ]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'calculates a 3D convolution' do
|
|
32
|
+
# 5x4x3
|
|
33
|
+
a = NArray[
|
|
34
|
+
[[1.0, 0.6, 1.1, 0.2, 0.9], [1.0, 0.7, 0.8, 1.0, 1.0], [0.2, 0.6, 0.1, 0.2, 0.5],
|
|
35
|
+
[0.5, 0.9, 0.2, 0.1, 0.6]],
|
|
36
|
+
[[0.4, 0.9, 0.4, 0.0, 0.6], [0.2, 1.1, 0.2, 0.4, 0.1], [0.4, 0.2, 0.5, 0.8, 0.7],
|
|
37
|
+
[0.1, 0.9, 0.7, 0.1, 0.3]],
|
|
38
|
+
[[0.8, 0.6, 1.0, 0.1, 0.4], [0.3, 0.8, 0.6, 0.7, 1.1], [0.9, 1.0, 0.3, 0.4, 0.6],
|
|
39
|
+
[0.2, 0.5, 0.4, 0.7, 0.2]]
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# 3x3x3
|
|
43
|
+
b = NArray[
|
|
44
|
+
[[-0.9, 1.2, 0.8], [0.9, 0.1, -0.5], [1.1, 0.1, -1.1]],
|
|
45
|
+
[[-0.2, -1.0, 1.4], [-1.4, 0.0, 1.3], [0.3, 1.0, -0.5]],
|
|
46
|
+
[[0.6, 0.0, 0.7], [-0.7, 1.1, 1.2], [1.3, 0.7, 0.0]]
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# Should be 3x2x1
|
|
50
|
+
c = described_class.convolve_basic(a, b)
|
|
51
|
+
expect(c).to be_narray_like NArray[[[5.51, 3.04, 4.3], [3.04, 6.31, 3.87]]]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'calculates a 4D convolution' do
|
|
55
|
+
# 3x4x5x3
|
|
56
|
+
a = NArray[
|
|
57
|
+
[[[0.5, 0.4, 0.9], [0.1, 0.9, 0.8], [0.4, 0.0, 0.1], [0.8, 0.3, 0.4]],
|
|
58
|
+
[[0.0, 0.4, 0.0], [0.2, 0.3, 0.8], [0.6, 0.3, 0.2], [0.7, 0.4, 0.3]],
|
|
59
|
+
[[0.3, 0.3, 0.1], [0.6, 0.9, 0.4], [0.4, 0.0, 0.1], [0.8, 0.3, 0.4]],
|
|
60
|
+
[[0.0, 0.4, 0.0], [0.2, 0.3, 0.8], [0.6, 0.3, 0.2], [0.7, 0.4, 0.3]],
|
|
61
|
+
[[0.3, 0.3, 0.1], [0.6, 0.9, 0.4], [0.4, 0.0, 0.1], [0.8, 0.3, 0.4]]],
|
|
62
|
+
[[[0.5, 0.4, 0.9], [0.1, 0.9, 0.8], [0.4, 0.0, 0.1], [0.8, 0.3, 0.4]],
|
|
63
|
+
[[0.0, 0.4, 0.0], [0.2, 0.3, 0.8], [0.6, 0.3, 0.2], [0.7, 0.4, 0.3]],
|
|
64
|
+
[[0.3, 0.3, 0.1], [0.6, 0.9, 0.4], [0.4, 0.0, 0.1], [0.8, 0.3, 0.4]],
|
|
65
|
+
[[0.0, 0.4, 0.0], [0.2, 0.3, 0.8], [0.6, 0.3, 0.2], [0.7, 0.4, 0.3]],
|
|
66
|
+
[[0.3, 0.3, 0.1], [0.6, 0.9, 0.4], [0.4, 0.0, 0.1], [0.8, 0.3, 0.4]]],
|
|
67
|
+
[[[0.5, 0.4, 0.9], [0.1, 0.9, 0.8], [0.4, 0.0, 0.1], [0.8, 0.3, 0.4]],
|
|
68
|
+
[[0.0, 0.4, 0.0], [0.2, 0.3, 0.8], [0.6, 0.3, 0.2], [0.7, 0.4, 0.3]],
|
|
69
|
+
[[0.3, 0.3, 0.1], [0.6, 0.9, 0.4], [0.4, 0.0, 0.1], [0.8, 0.3, 0.4]],
|
|
70
|
+
[[0.0, 0.4, 0.0], [0.2, 0.3, 0.8], [0.6, 0.3, 0.2], [0.7, 0.4, 0.3]],
|
|
71
|
+
[[0.3, 0.3, 0.1], [0.6, 0.9, 0.4], [0.4, 0.0, 0.1], [0.8, 0.3, 0.4]]] ]
|
|
72
|
+
|
|
73
|
+
# 2x3x3x2
|
|
74
|
+
b = NArray[ [
|
|
75
|
+
[[1.1, 0.6], [1.2, 0.6], [0.8, 0.1]], [[-0.4, 0.8], [0.5, 0.4], [1.2, 0.2]],
|
|
76
|
+
[[0.8, 0.2], [0.5, 0.0], [1.4, 1.3]]
|
|
77
|
+
],
|
|
78
|
+
[[[1.1, 0.6], [1.2, 0.6], [0.8, 0.1]], [[-0.4, 0.8], [0.5, 0.4], [1.2, 0.2]],
|
|
79
|
+
[[0.8, 0.2], [0.5, 0.0], [1.4, 1.3]]] ]
|
|
80
|
+
|
|
81
|
+
# Should be 2x2x3x2
|
|
82
|
+
c = described_class.convolve_basic(a, b)
|
|
83
|
+
expect(c).to be_narray_like NArray[
|
|
84
|
+
[[[8.5, 8.2], [11.34, 9.68]], [[7.68, 6.56], [11.24, 7.16]], [[9.14, 6.54], [12.44, 9.2]]],
|
|
85
|
+
[[[8.5, 8.2], [11.34, 9.68]], [[7.68, 6.56], [11.24, 7.16]], [[9.14, 6.54], [12.44, 9.2]]]
|
|
86
|
+
]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|