convolver 0.4.0 → 1.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.
data/lib/convolver.rb CHANGED
@@ -1,100 +1,109 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'narray'
3
+ require 'numo/narray/alt'
4
+ require 'numo/pocketfft'
4
5
  require 'convolver/convolver'
5
6
  require 'convolver/version'
6
- require 'fftw3'
7
7
 
8
- # Convolution operations for NArray values.
8
+ # Valid cross-correlation operations for Numo::NArray values.
9
9
  module Convolver
10
- # Chooses and calls likely fastest method from #convolve_basic and #convolve_fftw3.
11
- # The two parameters must have the same rank. The output has same rank, its size in each
12
- # dimension d is given by
13
- # signal.shape[d] - kernel.shape[d] + 1
14
- # If you always perform convolutions of the same size, you may be better off benchmarking your
15
- # own code using either #convolve_basic or #convolve_fftw3, and have your code use the fastest.
16
- # @param [NArray] signal must be same size or larger than kernel in each dimension
17
- # @param [NArray] kernel must be same size or smaller than signal in each dimension
18
- # @return [NArray] result of convolving signal with kernel
19
- def self.convolve(signal, kernel)
20
- # For small signals or kernels, just go straight to basic
21
- return convolve_basic(signal, kernel) if signal.size < 1000 || kernel.size < 100
22
-
23
- # If predicted time is less than a millisecond, just do a basic convolve
24
- basic_time_predicted = predict_convolve_basic_time(signal, kernel)
25
- return convolve_basic(signal, kernel) if basic_time_predicted < 0.1
26
-
27
- # Factor of two to allow for large uncertainty in predictions for FFTW3
28
- fft_time_predicted = predict_convolve_fft_time(signal, kernel)
29
- return convolve_fftw3(signal, kernel) if fft_time_predicted < 2 * basic_time_predicted
30
-
31
- convolve_basic(signal, kernel)
32
- end
10
+ # Maximum number of dimensions supported by the direct native implementation.
11
+ MAX_RANK = 16
33
12
 
34
- # Uses FFTW3 library to calculate convolution of an array of floats representing a signal,
35
- # with a second array representing a kernel. The two parameters must have the same rank.
36
- # The output has same rank, its size in each dimension d is given by
37
- # signal.shape[d] - kernel.shape[d] + 1
38
- # @param [NArray] signal must be same size or larger than kernel in each dimension
39
- # @param [NArray] kernel must be same size or smaller than signal in each dimension
40
- # @return [NArray] result of convolving signal with kernel
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)
45
-
46
- (FFTW3.ifft(cfreqs).real * (1.0 / mod_a.size))[*ranges]
47
- end
13
+ class << self
14
+ # Chooses the likely fastest implementation for a valid cross-correlation.
15
+ #
16
+ # The inputs must have the same rank, and the kernel must not be larger than
17
+ # the signal in any dimension. The result shape is:
18
+ #
19
+ # signal.shape.zip(kernel.shape).map { |signal_size, kernel_size| signal_size - kernel_size + 1 }
20
+ #
21
+ # @param signal [Numo::NArray] input values
22
+ # @param kernel [Numo::NArray] correlation kernel
23
+ # @return [Numo::SFloat] valid cross-correlation result
24
+ # @raise [ArgumentError] if the inputs have incompatible ranks or shapes
25
+ def convolve(signal, kernel)
26
+ validate_inputs!(signal, kernel)
27
+ return convolve_basic(signal, kernel) if signal.size < 1000 || kernel.size < 100
48
28
 
49
- # A rough estimate of time that #convolve_fftw3 will take, based on complexity
50
- # of its operations, and some rough benchmarking. A value of 1.0 corresponds to results
51
- # varying between 1 and 12 milliseconds on the test computer.
52
- # @param [NArray] signal must be same size or larger than kernel in each dimension
53
- # @param [NArray] kernel must be same size or smaller than signal in each dimension
54
- # @return [Float] rough estimate of time for convolution compared to baseline
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) }
57
- end
29
+ basic_time_predicted = predict_convolve_basic_time(signal, kernel)
30
+ return convolve_basic(signal, kernel) if basic_time_predicted < 0.1
58
31
 
59
- # A rough estimate of time that #convolve will take, based on complexity
60
- # of its operations, and some rough benchmarking. A value of 1.0 corresponds to results
61
- # varying bewteen 2 and 8 milliseconds on the test computer.
62
- # @param [NArray] signal must be same size or larger than kernel in each dimension
63
- # @param [NArray] kernel must be same size or smaller than signal in each dimension
64
- # @return [Float] rough estimate of time for convolution compared to baseline
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))
68
- end
32
+ fft_time_predicted = predict_convolve_fft_time(signal, kernel)
33
+ return convolve_fft(signal, kernel) if fft_time_predicted < 2 * basic_time_predicted
69
34
 
70
- def self.shape_to_size(shape)
71
- shape.inject(1) { |t, x| t * x }
72
- end
35
+ convolve_basic(signal, kernel)
36
+ end
73
37
 
74
- def self.result_shape(signal_shape, kernel_shape)
75
- result_shape = []
76
- signal_shape.each_with_index do |signal_size, i|
77
- kernel_size = kernel_shape[i]
78
- result_shape[i] = signal_size - kernel_size + 1
38
+ # Uses PocketFFT to calculate a valid cross-correlation.
39
+ #
40
+ # @param signal [Numo::NArray] input values
41
+ # @param kernel [Numo::NArray] correlation kernel
42
+ # @return [Numo::SFloat] valid cross-correlation result
43
+ # @raise [ArgumentError] if the inputs have incompatible ranks or shapes
44
+ def convolve_fft(signal, kernel)
45
+ validate_inputs!(signal, kernel)
46
+ ranges = kernel.shape.zip(signal.shape).map { |kernel_size, signal_size| (kernel_size - 1)...signal_size }
47
+ full_convolution = Numo::Pocketfft.fftconvolve(signal, kernel.reverse)
48
+
49
+ Numo::SFloat.cast(full_convolution[*ranges])
79
50
  end
80
- result_shape
81
- end
82
51
 
83
- def self.fft_offsets(signal_shape, kernel_shape)
84
- signal_shape.zip(kernel_shape).map { |sizes| fft_offset(*sizes) }.transpose
85
- end
52
+ # Compatibility alias for the former FFTW3-backed implementation.
53
+ #
54
+ # @deprecated Use {.convolve_fft}; Convolver no longer uses FFTW3.
55
+ # @return [Numo::SFloat] valid cross-correlation result
56
+ def convolve_fftw3(signal, kernel)
57
+ warn 'Convolver.convolve_fftw3 is deprecated; use .convolve_fft instead', uplevel: 1
58
+ convolve_fft(signal, kernel)
59
+ end
86
60
 
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
61
+ # Estimates the relative cost of {.convolve_fft}.
62
+ #
63
+ # @param signal [Numo::NArray] input values
64
+ # @param kernel [Numo::NArray] correlation kernel
65
+ # @return [Float] machine-specific relative cost estimate
66
+ def predict_convolve_fft_time(signal, kernel)
67
+ validate_inputs!(signal, kernel)
68
+ output_size = result_shape(signal.shape, kernel.shape).inject(:*)
69
+ 16 * 4.55e-08 * output_size * Math.log(output_size)
70
+ end
94
71
 
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)]
72
+ # Estimates the relative cost of {.convolve_basic}.
73
+ #
74
+ # @param signal [Numo::NArray] input values
75
+ # @param kernel [Numo::NArray] correlation kernel
76
+ # @return [Float] machine-specific relative cost estimate
77
+ def predict_convolve_basic_time(signal, kernel)
78
+ validate_inputs!(signal, kernel)
79
+ outputs = result_shape(signal.shape, kernel.shape).inject(:*)
80
+ 4.54e-12 * (outputs * signal.size * kernel.size)
81
+ end
82
+
83
+ private
84
+
85
+ def result_shape(signal_shape, kernel_shape)
86
+ signal_shape.zip(kernel_shape).map { |signal_size, kernel_size| signal_size - kernel_size + 1 }
87
+ end
88
+
89
+ def validate_inputs!(signal, kernel)
90
+ validate_types!(signal, kernel)
91
+ validate_shapes!(signal, kernel)
92
+ end
93
+
94
+ def validate_types!(signal, kernel)
95
+ unless signal.is_a?(Numo::NArray) && kernel.is_a?(Numo::NArray)
96
+ raise ArgumentError, 'signal and kernel must be Numo::NArray values'
97
+ end
98
+ raise ArgumentError, 'signal and kernel must not be empty' if signal.empty? || kernel.empty?
99
+ end
100
+
101
+ def validate_shapes!(signal, kernel)
102
+ raise ArgumentError, 'signal and kernel must have equal rank' unless signal.ndim == kernel.ndim
103
+ raise ArgumentError, "maximum supported rank is #{MAX_RANK}" if signal.ndim > MAX_RANK
104
+ return if signal.shape.zip(kernel.shape).all? { |signal_size, kernel_size| signal_size >= kernel_size }
105
+
106
+ raise ArgumentError, 'kernel must not be larger than signal in any dimension'
107
+ end
99
108
  end
100
109
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: convolver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Slater
@@ -10,34 +10,47 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: fftw3
13
+ name: numo-narray-alt
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '0.3'
18
+ version: 0.9.9
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '0.11'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
- version: '0.3'
28
+ version: 0.9.9
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '0.11'
26
32
  - !ruby/object:Gem::Dependency
27
- name: narray
33
+ name: numo-pocketfft
28
34
  requirement: !ruby/object:Gem::Requirement
29
35
  requirements:
30
36
  - - ">="
31
37
  - !ruby/object:Gem::Version
32
- version: 0.6.0.8
38
+ version: '0.6'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.8'
33
42
  type: :runtime
34
43
  prerelease: false
35
44
  version_requirements: !ruby/object:Gem::Requirement
36
45
  requirements:
37
46
  - - ">="
38
47
  - !ruby/object:Gem::Version
39
- version: 0.6.0.8
40
- description: Convolution for NArray
48
+ version: '0.6'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '0.8'
52
+ description: Fast valid cross-correlation for multidimensional Numo::NArray values,
53
+ with native and FFT implementations.
41
54
  email:
42
55
  - slobo777@gmail.com
43
56
  executables: []
@@ -45,31 +58,21 @@ extensions:
45
58
  - ext/convolver/extconf.rb
46
59
  extra_rdoc_files: []
47
60
  files:
48
- - ".github/workflows/ci.yml"
49
- - ".gitignore"
50
- - ".rubocop.yml"
51
- - Gemfile
61
+ - CHANGELOG.md
52
62
  - LICENSE.txt
53
63
  - README.md
54
- - Rakefile
55
- - convolver.gemspec
56
64
  - ext/convolver/convolve_raw.c
57
65
  - ext/convolver/convolve_raw.h
58
66
  - ext/convolver/convolver.c
59
67
  - ext/convolver/extconf.rb
60
- - ext/convolver/narray_shared.c
61
- - ext/convolver/narray_shared.h
62
68
  - lib/convolver.rb
63
69
  - lib/convolver/version.rb
64
- - spec/convolver_basic_spec.rb
65
- - spec/convolver_fftw3_spec.rb
66
- - spec/convolver_spec.rb
67
- - spec/helpers.rb
68
- homepage: http://github.com/neilslater/convolver
70
+ homepage: https://github.com/neilslater/convolver
69
71
  licenses:
70
72
  - MIT
71
73
  metadata:
72
74
  rubygems_mfa_required: 'true'
75
+ source_code_uri: https://github.com/neilslater/convolver
73
76
  rdoc_options: []
74
77
  require_paths:
75
78
  - lib
@@ -84,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
87
  - !ruby/object:Gem::Version
85
88
  version: '0'
86
89
  requirements: []
87
- rubygems_version: 4.0.1
90
+ rubygems_version: 4.0.16
88
91
  specification_version: 4
89
- summary: Convolution for NArray
92
+ summary: Fast cross-correlation for Numo::NArray
90
93
  test_files: []
@@ -1,40 +0,0 @@
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/.gitignore DELETED
@@ -1,19 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- lib/convolver/*.bundle
5
- .config
6
- .yardoc
7
- Gemfile.lock
8
- InstalledFiles
9
- _yardoc
10
- coverage
11
- doc/
12
- lib/bundler/man
13
- pkg
14
- rdoc
15
- spec/reports
16
- test/tmp
17
- test/version_tmp
18
- tmp
19
- benchmarks
data/.rubocop.yml DELETED
@@ -1,16 +0,0 @@
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 DELETED
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in convolver.gemspec
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/Rakefile DELETED
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
- require 'rake/extensiontask'
6
-
7
- desc 'Convolver unit tests'
8
- RSpec::Core::RakeTask.new(:test) do |t|
9
- t.pattern = 'spec/*_spec.rb'
10
- t.verbose = true
11
- end
12
-
13
- gemspec = Gem::Specification.load('convolver.gemspec')
14
- Rake::ExtensionTask.new do |ext|
15
- ext.name = 'convolver'
16
- ext.source_pattern = '*.{c,h}'
17
- ext.ext_dir = 'ext/convolver'
18
- ext.lib_dir = 'lib/convolver'
19
- ext.gem_spec = gemspec
20
- end
21
-
22
- task default: %i[compile test]
data/convolver.gemspec DELETED
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'English'
4
- lib = File.expand_path('lib', __dir__)
5
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
- require 'convolver/version'
7
-
8
- Gem::Specification.new do |spec|
9
- spec.name = 'convolver'
10
- spec.version = Convolver::VERSION
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
-
19
- spec.add_dependency 'fftw3', '>= 0.3'
20
- spec.add_dependency 'narray', '>= 0.6.0.8'
21
-
22
- spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
23
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
- spec.extensions = spec.files.grep(%r{/extconf\.rb$})
25
- spec.require_paths = ['lib']
26
- spec.metadata['rubygems_mfa_required'] = 'true'
27
- end
@@ -1,61 +0,0 @@
1
- // ext/convolver/narray_shared.c
2
-
3
- #include "narray_shared.h"
4
-
5
- // This is copied from na_array.c, with safety checks and temp vars removed
6
- int na_quick_idxs_to_pos( int rank, int *shape, int *idxs ) {
7
- int i, pos = 0;
8
- for ( i = rank - 1; i >= 0; i-- ) {
9
- pos = pos * shape[i] + idxs[i];
10
- }
11
- return pos;
12
- }
13
-
14
- // This is inverse of above
15
- void na_quick_pos_to_idxs( int rank, int *shape, int pos, int *idxs ) {
16
- int i;
17
- for ( i = 0; i < rank; i++ ) {
18
- idxs[ i ] = pos % shape[i];
19
- pos /= shape[i];
20
- }
21
- return;
22
- }
23
-
24
- // This is copied from na_array.c, with safety checks and temp vars removed
25
- inline int na_inline_idxs_to_pos( int rank, int *shape, int *idxs ) {
26
- int i, pos = 0;
27
- for ( i = rank - 1; i >= 0; i-- ) {
28
- pos = pos * shape[i] + idxs[i];
29
- }
30
- return pos;
31
- }
32
-
33
- // This is inverse of above
34
- inline void na_inline_pos_to_idxs( int rank, int *shape, int pos, int *idxs ) {
35
- int i;
36
- for ( i = 0; i < rank; i++ ) {
37
- idxs[ i ] = pos % shape[i];
38
- pos /= shape[i];
39
- }
40
- return;
41
- }
42
-
43
- // used to place kernel data into array for FFTW3 processing
44
- void fit_backwards_raw( int rank, int *dst_shape, float *dst, int *src_shape, float *src, int *shift_shape ) {
45
- int i, j, size, x;
46
- int k_idx[16], dst_idx[16];
47
-
48
- size = 1;
49
- for ( j = 0; j < rank; j++ ) { size *= src_shape[j]; }
50
-
51
- for ( i = 0; i < size; i++ ) {
52
- na_inline_pos_to_idxs( rank, src_shape, i, k_idx );
53
- for ( j = 0; j < rank; j++ ) {
54
- x = src_shape[j] - shift_shape[j] - k_idx[j] - 1;
55
- if ( x < 0 ) x = x + dst_shape[j];
56
- dst_idx[j] = x;
57
- }
58
- dst[ na_inline_idxs_to_pos( rank, dst_shape, dst_idx ) ] = src[i];
59
- }
60
- return;
61
- }
@@ -1,22 +0,0 @@
1
- // ext/convolver/narray_shared.h
2
-
3
- ////////////////////////////////////////////////////////////////////////////////////////////////
4
- //
5
- // Declarations of narray helper functions
6
- //
7
-
8
- #ifndef CONVOLVER_NARRAY_SHARED_H
9
- #define CONVOLVER_NARRAY_SHARED_H
10
-
11
- #include <ruby.h>
12
- #include "narray.h"
13
-
14
- // This is copied from na_array.c, with safety checks and temp vars removed
15
- int na_quick_idxs_to_pos( int rank, int *shape, int *idxs );
16
-
17
- // This is inverse of above
18
- void na_quick_pos_to_idxs( int rank, int *shape, int pos, int *idxs );
19
-
20
- void fit_backwards_raw( int rank, int *dst_shape, float *dst, int *src_shape, float *src, int *shift_shape );
21
-
22
- #endif
@@ -1,89 +0,0 @@
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