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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: aa84808d5f3c0be7b1128732c98ffecbd71d1673
4
- data.tar.gz: 91a4d77ad8feb496408251f688f2eeab0124c8a0
2
+ SHA256:
3
+ metadata.gz: 9c83dcadafabcbb8a971258601e61697a35d73afc28e7341bbe9d6f5fee887e3
4
+ data.tar.gz: dfc6018d6b2ae5a9ea7570036874f036961e9925c18e41116b481b4604f0cc56
5
5
  SHA512:
6
- metadata.gz: 45559b58c01d30623707001ba3af4e442695d5108e6757f93a716d6683055ed7f07b95559bb010c42c9f7bb621c64895b01f82431301b8645a030303c40c33a5
7
- data.tar.gz: 51db7895f0e8c07b4a4e47d20c6b242c6e4691ad838109afeb450aef8fad496c70bb1ff15759afe38276f329a7efec0b8123ea4e8e7334ad554ed07b786c0b3c
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
@@ -0,0 +1,16 @@
1
+ plugins:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+
5
+ AllCops:
6
+ NewCops: enable
7
+
8
+ Style/GlobalVars:
9
+ Exclude:
10
+ - ext/convolver/extconf.rb
11
+
12
+ RSpec/ExampleLength:
13
+ Max: 30
14
+
15
+ RSpec/MultipleExpectations:
16
+ Max: 7
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
  [![Gem Version](https://badge.fury.io/rb/convolver.png)](http://badge.fury.io/rb/convolver)
4
- [![Build Status](https://travis-ci.org/neilslater/convolver.png?branch=master)](http://travis-ci.org/neilslater/convolver)
5
- [![Dependency Status](https://gemnasium.com/neilslater/convolver.png)](https://gemnasium.com/neilslater/convolver)
4
+ [![Build Status](https://api.travis-ci.com/neilslater/convolver.png?branch=master)](https://travis-ci.com/github/neilslater/convolver)
5
+ [![Coverage Status](https://coveralls.io/repos/neilslater/convolver/badge.png?branch=master)](https://coveralls.io/r/neilslater/convolver?branch=master)
6
+ [![Code Climate](https://codeclimate.com/github/neilslater/convolver.png)](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
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
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 "Convolver unit tests"
7
+ desc 'Convolver unit tests'
6
8
  RSpec::Core::RakeTask.new(:test) do |t|
7
- t.pattern = "spec/*_spec.rb"
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 = "*.{c,h}"
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 :default => [:compile, :test]
22
+ task default: %i[compile test]
data/convolver.gemspec CHANGED
@@ -1,31 +1,27 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
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 = "convolver"
9
+ spec.name = 'convolver'
8
10
  spec.version = Convolver::VERSION
9
- spec.authors = ["Neil Slater"]
10
- spec.email = ["slobo777@gmail.com"]
11
- spec.description = %q{Convolution for NArray}
12
- spec.summary = %q{Convolution for NArray}
13
- spec.homepage = "http://github.com/neilslater/convolver"
14
- spec.license = "MIT"
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.add_development_dependency "yard", ">= 0.8.7.2"
20
- spec.add_development_dependency "bundler", ">= 1.3"
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 = ["lib"]
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 );
@@ -9,7 +9,12 @@
9
9
  #define CONVOLVE_RAW_H
10
10
 
11
11
  #include <ruby.h>
12
+ #if defined(__SSE__)
12
13
  #include <xmmintrin.h>
14
+ #define CONVOLVER_USE_SSE 1
15
+ #else
16
+ #define CONVOLVER_USE_SSE 0
17
+ #endif
13
18
  #include "narray_shared.h"
14
19
 
15
20
  #define LARGEST_RANK 16
@@ -3,7 +3,6 @@
3
3
  #include <ruby.h>
4
4
  #include "narray.h"
5
5
  #include <stdio.h>
6
- #include <xmmintrin.h>
7
6
 
8
7
  #include "narray_shared.h"
9
8
  #include "convolve_raw.h"
@@ -1,27 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # ext/convolver/extconf.rb
2
- require "mkmf"
3
- require "rubygems"
4
+ require 'mkmf'
5
+ require 'rubygems'
4
6
 
5
7
  # Following code stolen shamelessly from fftw3 gem:
6
- narray_dir = File.dirname(Gem.find_files("narray.h").first) rescue $sitearchdir
7
- dir_config('narray', narray_dir, narray_dir)
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
- if ( ! ( have_header("narray.h") && have_header("narray_config.h") ) ) then
10
- print <<-EOS
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
- EOS
18
- exit(-1)
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
- have_library("narray") || raise("ERROR: narray library is not found")
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( 'convolver/convolver' )
37
+ create_makefile('convolver/convolver')
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Convolver
2
- VERSION = "0.3.1"
4
+ VERSION = '0.4.0'
3
5
  end
data/lib/convolver.rb CHANGED
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'narray'
2
- require "convolver/convolver"
3
- require "convolver/version"
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 signal, kernel
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( signal, kernel )
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
- if predict_convolve_fft_time( signal, kernel ) < 2 * basic_time_predicted
30
- return convolve_fftw3( signal, kernel )
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( signal, kernel )
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 signal, kernel
44
- combined_shape, shift_by, ranges = fft_offsets( signal.shape, kernel.shape )
45
-
46
- mod_a = NArray.sfloat(*combined_shape)
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
- mod_b = NArray.sfloat(*combined_shape)
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 signal, kernel
67
- 16 * 4.55e-08 * combined_shape(signal.shape,kernel.shape).inject(1) { |t,x| t * x * Math.log(x) }
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 signal, kernel
77
- outputs = shape_to_size( result_shape( signal.shape, kernel.shape ) )
78
- 4.54e-12 * (outputs * shape_to_size( signal.shape ) * shape_to_size( kernel.shape ))
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.combined_shape signal_shape, kernel_shape
88
- combined_shape = [ ]
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 signal_shape, kernel_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 signal_shape, kernel_shape
106
- combined_shape = []
107
- shift_by = []
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
- combined_shape[i] = signal_size + kernel_size - 1
113
- output_size = signal_size - kernel_size + 1
114
- output_offset = kernel_size - 1
115
- shift_by[i] = kernel_size / 2
116
- ranges[i] = (output_offset...(output_offset + output_size))
117
- end
118
- [ combined_shape, shift_by, ranges ]
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