convolver 1.0.1 → 3.0.1

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
@@ -5,105 +5,136 @@ require 'numo/pocketfft'
5
5
  require 'convolver/convolver'
6
6
  require 'convolver/version'
7
7
 
8
- # Valid cross-correlation operations for Numo::NArray values.
8
+ # Mathematical convolution and cross-correlation for Numo::NArray values.
9
9
  module Convolver
10
- # Maximum number of dimensions supported by the direct native implementation.
10
+ # Maximum number of dimensions supported by the implementations.
11
11
  MAX_RANK = 16
12
12
 
13
+ require 'convolver/operation_plan'
14
+ require 'convolver/linear_fft_operation'
15
+ require 'convolver/circular_fft_operation'
16
+ require 'convolver/fft_estimator'
17
+ require 'convolver/operation_execution'
18
+
13
19
  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:
20
+ # Chooses the likely fastest mathematical convolution implementation.
18
21
  #
19
- # signal.shape.zip(kernel.shape).map { |signal_size, kernel_size| signal_size - kernel_size + 1 }
22
+ # Results use single precision. Inputs must be nonempty arrays of equal
23
+ # rank, with at most {MAX_RANK} dimensions. Scalars multiply directly.
24
+ # In valid mode the kernel must fit inside the signal in every dimension.
25
+ # Full mode permits only constant boundaries; nonconstant boundaries and
26
+ # nonzero origins are supported only in same mode.
20
27
  #
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
28
+ # @param signal [Numo::NArray] input signal
29
+ # @param kernel [Numo::NArray] convolution kernel
30
+ # @param mode [:valid, :same, :full] output extent (default: :valid)
31
+ # @param boundary [:constant, :nearest, :reflect, :mirror, :wrap]
32
+ # signal extension policy (default: :constant)
33
+ # @param fill_value [Numeric] constant boundary value (default: 0);
34
+ # valid mode requires zero, and other boundaries reject this option
35
+ # @param origin [Integer, Array<Integer>] offset from the kernel midpoint,
36
+ # shared across axes or one per axis (default: 0)
37
+ # @return [Numo::SFloat] mathematical convolution result
38
+ # @raise [ArgumentError] if inputs or options are invalid
39
+ # @raise [RangeError] if planned dimensions, buffers or FFT arithmetic exceed native limits
40
+ def convolve(signal, kernel, mode: :valid, boundary: :constant,
41
+ fill_value: UNSPECIFIED_FILL, origin: 0)
42
+ execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:).automatic(self)
43
+ end
28
44
 
29
- basic_time_predicted = predict_convolve_basic_time(signal, kernel)
30
- return convolve_basic(signal, kernel) if basic_time_predicted < 0.1
45
+ # Chooses the likely fastest cross-correlation implementation.
46
+ # Uses the same input and option rules as {.convolve}, without reversing
47
+ # the kernel's orientation.
48
+ # @param (see convolve)
49
+ # @return [Numo::SFloat] cross-correlation result
50
+ # @raise (see convolve)
51
+ def correlate(signal, kernel, mode: :valid, boundary: :constant,
52
+ fill_value: UNSPECIFIED_FILL, origin: 0)
53
+ execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:).automatic(self)
54
+ end
31
55
 
32
- fft_time_predicted = predict_convolve_fft_time(signal, kernel)
33
- return convolve_fft(signal, kernel) if fft_time_predicted < 2 * basic_time_predicted
56
+ # Uses the direct native mathematical convolution implementation.
57
+ # @param (see convolve)
58
+ # @return [Numo::SFloat] mathematical convolution result
59
+ # @raise (see convolve)
60
+ def convolve_basic(signal, kernel, mode: :valid, boundary: :constant,
61
+ fill_value: UNSPECIFIED_FILL, origin: 0)
62
+ execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:).basic
63
+ end
34
64
 
35
- convolve_basic(signal, kernel)
65
+ # Uses the direct native cross-correlation implementation.
66
+ # @param (see convolve)
67
+ # @return [Numo::SFloat] cross-correlation result
68
+ # @raise (see convolve)
69
+ def correlate_basic(signal, kernel, mode: :valid, boundary: :constant,
70
+ fill_value: UNSPECIFIED_FILL, origin: 0)
71
+ execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:).basic
36
72
  end
37
73
 
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])
74
+ # Uses PocketFFT to calculate mathematical convolution.
75
+ # @param (see convolve)
76
+ # @return [Numo::SFloat] mathematical convolution result
77
+ # @raise (see convolve)
78
+ def convolve_fft(signal, kernel, mode: :valid, boundary: :constant,
79
+ fill_value: UNSPECIFIED_FILL, origin: 0)
80
+ execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:).fft
50
81
  end
51
82
 
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)
83
+ # Uses PocketFFT to calculate cross-correlation.
84
+ # @param (see convolve)
85
+ # @return [Numo::SFloat] cross-correlation result
86
+ # @raise (see convolve)
87
+ def correlate_fft(signal, kernel, mode: :valid, boundary: :constant,
88
+ fill_value: UNSPECIFIED_FILL, origin: 0)
89
+ execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:).fft
59
90
  end
60
91
 
61
92
  # Estimates the relative cost of {.convolve_fft}.
62
- #
63
- # @param signal [Numo::NArray] input values
64
- # @param kernel [Numo::NArray] correlation kernel
93
+ # This is a heuristic for algorithm selection, not a timing measurement.
94
+ # @param (see convolve)
65
95
  # @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)
96
+ # @raise (see convolve)
97
+ def predict_convolve_fft_time(signal, kernel, mode: :valid, boundary: :constant,
98
+ fill_value: UNSPECIFIED_FILL, origin: 0)
99
+ execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:).fft_time
70
100
  end
71
101
 
72
- # Estimates the relative cost of {.convolve_basic}.
73
- #
74
- # @param signal [Numo::NArray] input values
75
- # @param kernel [Numo::NArray] correlation kernel
102
+ # Estimates the relative cost of {.correlate_fft}.
103
+ # This is a heuristic for algorithm selection, not a timing measurement.
104
+ # @param (see convolve)
76
105
  # @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)
106
+ # @raise (see convolve)
107
+ def predict_correlate_fft_time(signal, kernel, mode: :valid, boundary: :constant,
108
+ fill_value: UNSPECIFIED_FILL, origin: 0)
109
+ execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:).fft_time
81
110
  end
82
111
 
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 }
112
+ # Estimates the relative cost of {.convolve_basic}.
113
+ # This is a heuristic for algorithm selection, not a timing measurement.
114
+ # @param (see convolve)
115
+ # @return [Float] machine-specific relative cost estimate
116
+ # @raise (see convolve)
117
+ def predict_convolve_basic_time(signal, kernel, mode: :valid, boundary: :constant,
118
+ fill_value: UNSPECIFIED_FILL, origin: 0)
119
+ execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:).basic_time
87
120
  end
88
121
 
89
- def validate_inputs!(signal, kernel)
90
- validate_types!(signal, kernel)
91
- validate_shapes!(signal, kernel)
122
+ # Estimates the relative cost of {.correlate_basic}.
123
+ # This is a heuristic for algorithm selection, not a timing measurement.
124
+ # @param (see convolve)
125
+ # @return [Float] machine-specific relative cost estimate
126
+ # @raise (see convolve)
127
+ def predict_correlate_basic_time(signal, kernel, mode: :valid, boundary: :constant,
128
+ fill_value: UNSPECIFIED_FILL, origin: 0)
129
+ execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:).basic_time
92
130
  end
93
131
 
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
132
+ private
100
133
 
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 }
134
+ private :convolve_basic_valid, :correlate_basic_valid
105
135
 
106
- raise ArgumentError, 'kernel must not be larger than signal in any dimension'
136
+ def execution(operation, signal, kernel, mode:, boundary:, fill_value:, origin:)
137
+ OperationExecution.new(operation, signal, kernel, mode:, boundary:, fill_value:, origin:)
107
138
  end
108
139
  end
109
140
  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: 1.0.1
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Slater
@@ -18,7 +18,7 @@ dependencies:
18
18
  version: 0.9.9
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
- version: '0.11'
21
+ version: '0.12'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -28,14 +28,14 @@ dependencies:
28
28
  version: 0.9.9
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
- version: '0.11'
31
+ version: '0.12'
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: numo-pocketfft
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: '0.6'
38
+ version: 0.7.0
39
39
  - - "<"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0.8'
@@ -45,12 +45,12 @@ dependencies:
45
45
  requirements:
46
46
  - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: '0.6'
48
+ version: 0.7.0
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
51
  version: '0.8'
52
- description: Fast valid cross-correlation for multidimensional Numo::NArray values,
53
- with native and FFT implementations.
52
+ description: Fast mathematical convolution and cross-correlation for multidimensional
53
+ Numo::NArray values, with configurable output and boundary modes.
54
54
  email:
55
55
  - slobo777@gmail.com
56
56
  executables: []
@@ -64,8 +64,23 @@ files:
64
64
  - ext/convolver/convolve_raw.c
65
65
  - ext/convolver/convolve_raw.h
66
66
  - ext/convolver/convolver.c
67
+ - ext/convolver/correlate_raw.c
68
+ - ext/convolver/correlate_raw.h
67
69
  - ext/convolver/extconf.rb
68
70
  - lib/convolver.rb
71
+ - lib/convolver/buffer_limits.rb
72
+ - lib/convolver/buffer_preparation.rb
73
+ - lib/convolver/circular_fft_operation.rb
74
+ - lib/convolver/fft_buffer_plan.rb
75
+ - lib/convolver/fft_estimator.rb
76
+ - lib/convolver/fft_limits.rb
77
+ - lib/convolver/linear_fft_operation.rb
78
+ - lib/convolver/operation_execution.rb
79
+ - lib/convolver/operation_options.rb
80
+ - lib/convolver/operation_plan.rb
81
+ - lib/convolver/operation_shapes.rb
82
+ - lib/convolver/real_fft_shape.rb
83
+ - lib/convolver/signal_extension.rb
69
84
  - lib/convolver/version.rb
70
85
  homepage: https://github.com/neilslater/convolver
71
86
  licenses:
@@ -80,7 +95,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
80
95
  requirements:
81
96
  - - ">="
82
97
  - !ruby/object:Gem::Version
83
- version: '3.2'
98
+ version: '3.3'
84
99
  required_rubygems_version: !ruby/object:Gem::Requirement
85
100
  requirements:
86
101
  - - ">="
@@ -89,5 +104,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
104
  requirements: []
90
105
  rubygems_version: 4.0.16
91
106
  specification_version: 4
92
- summary: Fast cross-correlation for Numo::NArray
107
+ summary: Fast convolution and cross-correlation for Numo::NArray
93
108
  test_files: []