convolver 0.3.2 → 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: 4c3bcf13a230b62ed29260b84490f720af3408cc
4
- data.tar.gz: 3d00b5f4ece4078525b5d57fbe536d3dfcc6020e
2
+ SHA256:
3
+ metadata.gz: 9c83dcadafabcbb8a971258601e61697a35d73afc28e7341bbe9d6f5fee887e3
4
+ data.tar.gz: dfc6018d6b2ae5a9ea7570036874f036961e9925c18e41116b481b4604f0cc56
5
5
  SHA512:
6
- metadata.gz: 113c51e9233dabd6d94490b1906e31787f0fc9e7826bacf8b999c2c63e2722381baf417c616519735456f286c781e2597547b9851e77addd8267ec87c38947c2
7
- data.tar.gz: 2495231401448636a915562cf6ad09ba4351094a4af290b0d0a84ee5a7a313e57e1e2d8e07b70746b7ee60b1780ba6ed9e4e8c8c19518fa785d4c01e589b8258
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,9 +1,8 @@
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)
4
+ [![Build Status](https://api.travis-ci.com/neilslater/convolver.png?branch=master)](https://travis-ci.com/github/neilslater/convolver)
5
5
  [![Coverage Status](https://coveralls.io/repos/neilslater/convolver/badge.png?branch=master)](https://coveralls.io/r/neilslater/convolver?branch=master)
6
- [![Inline docs](http://inch-ci.org/github/neilslater/convolver.png?branch=master)](http://inch-ci.org/github/neilslater/convolver)
7
6
  [![Code Climate](https://codeclimate.com/github/neilslater/convolver.png)](https://codeclimate.com/github/neilslater/convolver)
8
7
 
9
8
  Calculates discrete convolution between two multi-dimensional arrays of floats.
@@ -16,6 +15,27 @@ See http://en.wikipedia.org/wiki/Convolution
16
15
  Before you install *convolver*, you should install the FFTW3 library on your system.
17
16
  See http://www.fftw.org/ for details.
18
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
+
19
39
  ### Installing the gem
20
40
 
21
41
  Add this line to your application's Gemfile:
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 "rake", ">= 1.9.1"
23
- spec.add_development_dependency "rake-compiler", ">= 0.8.3"
24
- spec.add_development_dependency "coveralls", ">= 0.6.7"
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,31 +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
8
+ narray_dir = begin
9
+ File.dirname(Gem.find_files('narray.h').first)
10
+ rescue StandardError
11
+ $sitearchdir
12
+ end
7
13
  if /cygwin|mingw/ =~ RUBY_PLATFORM
8
14
  dir_config('narray', narray_dir, "#{narray_dir}/src")
9
15
  else
10
16
  dir_config('narray', narray_dir, narray_dir)
11
17
  end
12
18
 
13
- if ( ! ( have_header("narray.h") && have_header("narray_config.h") ) ) then
14
- print <<-EOS
19
+ unless have_header('narray.h') && have_header('narray_config.h')
20
+ print <<-ERROR_MESSAGE
15
21
  ** configure error **
16
22
  Header narray.h or narray_config.h is not found. If you have these files in
17
23
  /narraydir/include, try the following:
18
24
 
19
25
  % ruby extconf.rb --with-narray-include=/narraydir/include
20
26
 
21
- EOS
22
- exit(-1)
27
+ ERROR_MESSAGE
28
+ exit(-1)
23
29
  end
24
30
 
25
31
  # This also stolen from fftw3 gem (and not confirmed for Windows platforms - please let me know if it works!)
26
32
  if /cygwin|mingw/ =~ RUBY_PLATFORM
27
- have_library("narray") || raise("ERROR: narray library is not found")
33
+ have_library('narray') || raise('ERROR: narray library is not found')
28
34
  end
29
35
 
30
36
  $CFLAGS << ' -O3 -funroll-loops'
31
- 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.2"
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,25 +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
- fft_time_predicted = predict_convolve_fft_time( signal, kernel )
30
- if fft_time_predicted < 2 * basic_time_predicted
31
- return convolve_fftw3( signal, kernel )
32
- 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
33
30
 
34
- convolve_basic( signal, kernel )
31
+ convolve_basic(signal, kernel)
35
32
  end
36
33
 
37
34
  # Uses FFTW3 library to calculate convolution of an array of floats representing a signal,
@@ -41,21 +38,12 @@ module Convolver
41
38
  # @param [NArray] signal must be same size or larger than kernel in each dimension
42
39
  # @param [NArray] kernel must be same size or smaller than signal in each dimension
43
40
  # @return [NArray] result of convolving signal with kernel
44
- def self.convolve_fftw3 signal, kernel
45
- combined_shape, shift_by, ranges = fft_offsets( signal.shape, kernel.shape )
46
-
47
- mod_a = NArray.sfloat(*combined_shape)
48
- mod_a[*shift_by] = signal
49
-
50
- mod_b = NArray.sfloat(*combined_shape)
51
-
52
- Convolver.fit_kernel_backwards( mod_b, kernel )
53
-
54
- afreqs = FFTW3.fft(mod_a)
55
- bfreqs = FFTW3.fft(mod_b)
56
- cfreqs = afreqs * bfreqs
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)
57
45
 
58
- (FFTW3.ifft( cfreqs ).real * (1.0/mod_a.size))[*ranges]
46
+ (FFTW3.ifft(cfreqs).real * (1.0 / mod_a.size))[*ranges]
59
47
  end
60
48
 
61
49
  # A rough estimate of time that #convolve_fftw3 will take, based on complexity
@@ -64,8 +52,8 @@ module Convolver
64
52
  # @param [NArray] signal must be same size or larger than kernel in each dimension
65
53
  # @param [NArray] kernel must be same size or smaller than signal in each dimension
66
54
  # @return [Float] rough estimate of time for convolution compared to baseline
67
- def self.predict_convolve_fft_time signal, kernel
68
- 16 * 4.55e-08 * result_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) }
69
57
  end
70
58
 
71
59
  # A rough estimate of time that #convolve will take, based on complexity
@@ -74,19 +62,17 @@ module Convolver
74
62
  # @param [NArray] signal must be same size or larger than kernel in each dimension
75
63
  # @param [NArray] kernel must be same size or smaller than signal in each dimension
76
64
  # @return [Float] rough estimate of time for convolution compared to baseline
77
- def self.predict_convolve_basic_time signal, kernel
78
- outputs = shape_to_size( result_shape( signal.shape, kernel.shape ) )
79
- 4.54e-12 * (outputs * shape_to_size( signal.shape ) * shape_to_size( kernel.shape ))
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))
80
68
  end
81
69
 
82
- private
83
-
84
- def self.shape_to_size shape
85
- shape.inject(1) { |t,x| t * x }
70
+ def self.shape_to_size(shape)
71
+ shape.inject(1) { |t, x| t * x }
86
72
  end
87
73
 
88
- def self.result_shape signal_shape, kernel_shape
89
- result_shape = [ ]
74
+ def self.result_shape(signal_shape, kernel_shape)
75
+ result_shape = []
90
76
  signal_shape.each_with_index do |signal_size, i|
91
77
  kernel_size = kernel_shape[i]
92
78
  result_shape[i] = signal_size - kernel_size + 1
@@ -94,19 +80,21 @@ module Convolver
94
80
  result_shape
95
81
  end
96
82
 
97
- def self.fft_offsets signal_shape, kernel_shape
98
- combined_shape = []
99
- shift_by = []
100
- ranges = []
101
- signal_shape.each_with_index do |signal_size, i|
102
- 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
103
86
 
104
- combined_shape[i] = signal_size + kernel_size - 1
105
- output_size = signal_size - kernel_size + 1
106
- output_offset = kernel_size - 1
107
- shift_by[i] = kernel_size / 2
108
- ranges[i] = (output_offset...(output_offset + output_size))
109
- end
110
- [ 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)]
111
99
  end
112
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