convolver 2.0.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -1
- data/README.md +99 -51
- data/ext/convolver/convolve_raw.c +11 -9
- data/ext/convolver/convolve_raw.h +3 -0
- data/ext/convolver/convolver.c +68 -24
- data/ext/convolver/correlate_raw.c +167 -0
- data/ext/convolver/correlate_raw.h +28 -0
- data/lib/convolver/buffer_limits.rb +39 -0
- data/lib/convolver/buffer_preparation.rb +56 -0
- data/lib/convolver/circular_fft_operation.rb +83 -0
- data/lib/convolver/fft_buffer_plan.rb +39 -0
- data/lib/convolver/fft_estimator.rb +71 -0
- data/lib/convolver/fft_limits.rb +64 -0
- data/lib/convolver/linear_fft_operation.rb +50 -0
- data/lib/convolver/operation_execution.rb +107 -0
- data/lib/convolver/operation_options.rb +11 -7
- data/lib/convolver/operation_plan.rb +22 -7
- data/lib/convolver/operation_shapes.rb +42 -9
- data/lib/convolver/real_fft_shape.rb +135 -0
- data/lib/convolver/signal_extension.rb +1 -0
- data/lib/convolver/version.rb +1 -1
- data/lib/convolver.rb +91 -108
- metadata +19 -8
|
@@ -1,33 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'convolver/real_fft_shape'
|
|
4
|
+
require 'convolver/fft_limits'
|
|
5
|
+
|
|
3
6
|
module Convolver
|
|
4
7
|
# Calculates and validates output, extension, and FFT dimensions.
|
|
8
|
+
# @private
|
|
5
9
|
class OperationShapes
|
|
6
10
|
SIZE_MAX = (1 << ([0].pack('J').bytesize * 8)) - 1
|
|
7
11
|
|
|
8
12
|
attr_reader :padding_before, :padding_after, :result_shape,
|
|
9
13
|
:extended_shape, :result_size, :extended_size
|
|
10
14
|
|
|
11
|
-
def initialize(signal_shape, kernel_shape, mode:, anchors:)
|
|
15
|
+
def initialize(signal_shape, kernel_shape, operation:, mode:, anchors:)
|
|
16
|
+
@operation = operation
|
|
12
17
|
@mode = mode
|
|
13
18
|
calculate_shapes(signal_shape, kernel_shape, anchors)
|
|
14
19
|
validate_sizes!
|
|
15
20
|
end
|
|
16
21
|
|
|
17
|
-
def linear_fft_shape(kernel_shape)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
)
|
|
22
|
+
def linear_fft_shape(kernel_shape, limits: FftLimits.new)
|
|
23
|
+
return [].freeze if kernel_shape.empty?
|
|
24
|
+
|
|
25
|
+
RealFftShape.new(linear_result_shape(kernel_shape), size_max: SIZE_MAX, limits:).call
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def linear_fft_size(kernel_shape)
|
|
25
29
|
checked_product(linear_fft_shape(kernel_shape), 'FFT size')
|
|
26
30
|
end
|
|
27
31
|
|
|
32
|
+
def linear_spectrum_size(kernel_shape)
|
|
33
|
+
shape = linear_fft_shape(kernel_shape)
|
|
34
|
+
return 1 if shape.empty?
|
|
35
|
+
|
|
36
|
+
spectrum_shape = shape.dup
|
|
37
|
+
spectrum_shape[-1] = (spectrum_shape[-1] / 2) + 1
|
|
38
|
+
checked_product(spectrum_shape, 'FFT spectrum size')
|
|
39
|
+
end
|
|
40
|
+
|
|
28
41
|
private
|
|
29
42
|
|
|
30
|
-
attr_reader :mode
|
|
43
|
+
attr_reader :operation, :mode
|
|
31
44
|
|
|
32
45
|
def calculate_shapes(signal_shape, kernel_shape, anchors)
|
|
33
46
|
@padding_before, @padding_after, @result_shape = shapes_for(signal_shape, kernel_shape, anchors)
|
|
@@ -53,8 +66,15 @@ module Convolver
|
|
|
53
66
|
end
|
|
54
67
|
|
|
55
68
|
def same_shapes(signal_shape, kernel_shape, anchors)
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
correlation_before = anchors.dup
|
|
70
|
+
correlation_after = kernel_shape.zip(anchors).map { |kernel_size, anchor| kernel_size - 1 - anchor }
|
|
71
|
+
before, after = if operation == :correlation
|
|
72
|
+
[correlation_before, correlation_after]
|
|
73
|
+
else
|
|
74
|
+
[correlation_after, correlation_before]
|
|
75
|
+
end
|
|
76
|
+
before.freeze
|
|
77
|
+
after.freeze
|
|
58
78
|
[before, after, signal_shape.dup]
|
|
59
79
|
end
|
|
60
80
|
|
|
@@ -90,6 +110,19 @@ module Convolver
|
|
|
90
110
|
product * size
|
|
91
111
|
end
|
|
92
112
|
end
|
|
113
|
+
|
|
114
|
+
def linear_result_shape(kernel_shape)
|
|
115
|
+
shape = extended_shape.zip(kernel_shape).map do |signal_size, kernel_size|
|
|
116
|
+
checked_add(signal_size, kernel_size - 1, 'FFT shape')
|
|
117
|
+
end
|
|
118
|
+
checked_shape(shape, 'FFT shape')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def checked_add(left, right, description)
|
|
122
|
+
return left + right if right <= SIZE_MAX - left
|
|
123
|
+
|
|
124
|
+
raise RangeError, "#{description} exceeds native implementation limit"
|
|
125
|
+
end
|
|
93
126
|
end
|
|
94
127
|
|
|
95
128
|
private_constant :OperationShapes
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'convolver/fft_limits'
|
|
4
|
+
|
|
5
|
+
module Convolver
|
|
6
|
+
# Selects a safe real-transform shape from exact/even and fast candidates.
|
|
7
|
+
# @private
|
|
8
|
+
class RealFftShape
|
|
9
|
+
def initialize(minimum_shape, size_max:, limits: nil)
|
|
10
|
+
@minimum_shape = minimum_shape
|
|
11
|
+
@size_max = size_max
|
|
12
|
+
@limits = limits
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
exact_shape = even_final_axis(minimum_shape)
|
|
17
|
+
candidates = [exact_shape, fast_shape(exact_shape)].compact.select { |shape| admissible?(shape) }
|
|
18
|
+
(candidates.min_by { |shape| transform_cost(shape) } || raise_rejection).freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :minimum_shape, :size_max, :limits
|
|
24
|
+
|
|
25
|
+
def admissible?(shape)
|
|
26
|
+
return false unless representable?(shape)
|
|
27
|
+
|
|
28
|
+
limits&.validate!(shape, real_axis: shape.length - 1)
|
|
29
|
+
true
|
|
30
|
+
rescue FftUnavailable => e
|
|
31
|
+
@rejection ||= e
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def raise_rejection
|
|
36
|
+
raise @rejection if @rejection
|
|
37
|
+
|
|
38
|
+
raise_overflow
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def fast_shape(exact_shape)
|
|
42
|
+
fast_shape = exact_shape.map.with_index do |size, axis|
|
|
43
|
+
next_fast_size(size, even: axis == exact_shape.length - 1)
|
|
44
|
+
end
|
|
45
|
+
fast_shape if fast_shape.all?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def even_final_axis(shape)
|
|
49
|
+
result = shape.dup
|
|
50
|
+
result[-1] = checked_add(result[-1], 1) if result[-1].odd?
|
|
51
|
+
result.freeze
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def next_fast_size(target, even:)
|
|
55
|
+
best = nil
|
|
56
|
+
each_power(5) do |power_of_five|
|
|
57
|
+
each_power(3, power_of_five) do |power_of_three|
|
|
58
|
+
candidate = doubled_candidate(power_of_three, target, even)
|
|
59
|
+
best = candidate if candidate && (!best || candidate < best)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
best
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def each_power(factor, initial = 1)
|
|
66
|
+
value = initial
|
|
67
|
+
loop do
|
|
68
|
+
yield value
|
|
69
|
+
break if value > size_max / factor
|
|
70
|
+
|
|
71
|
+
value *= factor
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def doubled_candidate(initial, target, even)
|
|
76
|
+
candidate = initial
|
|
77
|
+
while candidate < target || (even && candidate.odd?)
|
|
78
|
+
return nil if candidate > size_max / 2
|
|
79
|
+
|
|
80
|
+
candidate *= 2
|
|
81
|
+
end
|
|
82
|
+
candidate
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def transform_cost(shape)
|
|
86
|
+
shape.reduce(:*) * shape.sum { |size| Math.log2(size) + large_factor_penalty(size) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def large_factor_penalty(size)
|
|
90
|
+
prime_factors(size).sum do |factor|
|
|
91
|
+
factor > 7 ? 1.5 * (Math.log2(factor) - 3) : 0.0
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def prime_factors(value)
|
|
96
|
+
factors = []
|
|
97
|
+
divisor = 2
|
|
98
|
+
while divisor * divisor <= value
|
|
99
|
+
value = extract_factor(value, divisor, factors)
|
|
100
|
+
divisor += divisor == 2 ? 1 : 2
|
|
101
|
+
end
|
|
102
|
+
factors << value if value > 1
|
|
103
|
+
factors
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def extract_factor(value, divisor, factors)
|
|
107
|
+
while (value % divisor).zero?
|
|
108
|
+
factors << divisor
|
|
109
|
+
value /= divisor
|
|
110
|
+
end
|
|
111
|
+
value
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def representable?(shape)
|
|
115
|
+
shape.reduce(1) do |product, size|
|
|
116
|
+
return false if !product.zero? && size > size_max / product
|
|
117
|
+
|
|
118
|
+
product * size
|
|
119
|
+
end
|
|
120
|
+
true
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def checked_add(left, right)
|
|
124
|
+
return left + right if right <= size_max - left
|
|
125
|
+
|
|
126
|
+
raise_overflow
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def raise_overflow
|
|
130
|
+
raise(limits ? FftUnavailable : RangeError, 'FFT shape exceeds native implementation limit')
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private_constant :RealFftShape
|
|
135
|
+
end
|
data/lib/convolver/version.rb
CHANGED
data/lib/convolver.rb
CHANGED
|
@@ -5,153 +5,136 @@ require 'numo/pocketfft'
|
|
|
5
5
|
require 'convolver/convolver'
|
|
6
6
|
require 'convolver/version'
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# Mathematical convolution and cross-correlation for Numo::NArray values.
|
|
9
9
|
module Convolver
|
|
10
10
|
# Maximum number of dimensions supported by the implementations.
|
|
11
11
|
MAX_RANK = 16
|
|
12
12
|
|
|
13
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'
|
|
14
18
|
|
|
15
19
|
class << self
|
|
16
|
-
# Chooses the likely fastest
|
|
20
|
+
# Chooses the likely fastest mathematical convolution implementation.
|
|
17
21
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# @
|
|
25
|
-
# @
|
|
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.
|
|
27
|
+
#
|
|
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
|
|
26
40
|
def convolve(signal, kernel, mode: :valid, boundary: :constant,
|
|
27
41
|
fill_value: UNSPECIFIED_FILL, origin: 0)
|
|
28
|
-
|
|
29
|
-
options = operation_options(mode, boundary, fill_value, origin)
|
|
30
|
-
|
|
31
|
-
return convolve_basic(signal, kernel, **options) if plan.extended_size < 1000 || kernel.size < 100
|
|
32
|
-
|
|
33
|
-
basic_time_predicted = predict_convolve_basic_time(signal, kernel, **options)
|
|
34
|
-
return convolve_basic(signal, kernel, **options) if basic_time_predicted < 0.1
|
|
35
|
-
|
|
36
|
-
fft_time_predicted = predict_convolve_fft_time(signal, kernel, **options)
|
|
37
|
-
return convolve_fft(signal, kernel, **options) if fft_time_predicted < 2 * basic_time_predicted
|
|
38
|
-
|
|
39
|
-
convolve_basic(signal, kernel, **options)
|
|
42
|
+
execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:).automatic(self)
|
|
40
43
|
end
|
|
41
44
|
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
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)
|
|
45
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
|
|
55
|
+
|
|
56
|
+
# Uses the direct native mathematical convolution implementation.
|
|
57
|
+
# @param (see convolve)
|
|
58
|
+
# @return [Numo::SFloat] mathematical convolution result
|
|
59
|
+
# @raise (see convolve)
|
|
46
60
|
def convolve_basic(signal, kernel, mode: :valid, boundary: :constant,
|
|
47
61
|
fill_value: UNSPECIFIED_FILL, origin: 0)
|
|
48
|
-
|
|
49
|
-
convolve_basic_valid(plan.extend_signal(signal), kernel)
|
|
62
|
+
execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:).basic
|
|
50
63
|
end
|
|
51
64
|
|
|
52
|
-
# Uses
|
|
53
|
-
#
|
|
54
|
-
# Periodic same-sized results use a circular transform. Other combinations
|
|
55
|
-
# use the shared extension plan and PocketFFT's linear convolution.
|
|
56
|
-
#
|
|
65
|
+
# Uses the direct native cross-correlation implementation.
|
|
66
|
+
# @param (see convolve)
|
|
57
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
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Uses PocketFFT to calculate mathematical convolution.
|
|
75
|
+
# @param (see convolve)
|
|
76
|
+
# @return [Numo::SFloat] mathematical convolution result
|
|
77
|
+
# @raise (see convolve)
|
|
58
78
|
def convolve_fft(signal, kernel, mode: :valid, boundary: :constant,
|
|
59
79
|
fill_value: UNSPECIFIED_FILL, origin: 0)
|
|
60
|
-
|
|
61
|
-
return convolve_fft_wrap(signal, kernel, plan) if plan.wrap?
|
|
62
|
-
|
|
63
|
-
convolve_fft_valid(plan.extend_signal(signal), kernel)
|
|
80
|
+
execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:).fft
|
|
64
81
|
end
|
|
65
82
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# @deprecated Use {.convolve_fft}; Convolver no longer uses FFTW3.
|
|
83
|
+
# Uses PocketFFT to calculate cross-correlation.
|
|
84
|
+
# @param (see convolve)
|
|
69
85
|
# @return [Numo::SFloat] cross-correlation result
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
convolve_fft(signal, kernel, **options)
|
|
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
|
|
75
90
|
end
|
|
76
91
|
|
|
77
|
-
# Estimates the relative cost of {.convolve_fft}
|
|
78
|
-
#
|
|
92
|
+
# Estimates the relative cost of {.convolve_fft}.
|
|
93
|
+
# This is a heuristic for algorithm selection, not a timing measurement.
|
|
94
|
+
# @param (see convolve)
|
|
79
95
|
# @return [Float] machine-specific relative cost estimate
|
|
96
|
+
# @raise (see convolve)
|
|
80
97
|
def predict_convolve_fft_time(signal, kernel, mode: :valid, boundary: :constant,
|
|
81
98
|
fill_value: UNSPECIFIED_FILL, origin: 0)
|
|
82
|
-
|
|
83
|
-
transform_size = plan.wrap? ? signal.size : plan.linear_fft_size(kernel.shape)
|
|
84
|
-
transform_cost = 16 * 4.55e-08 * transform_size * Math.log(transform_size)
|
|
85
|
-
transform_cost + (4.55e-08 * fft_preparation_size(plan, signal, kernel))
|
|
99
|
+
execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:).fft_time
|
|
86
100
|
end
|
|
87
101
|
|
|
88
|
-
# Estimates the relative cost of {.
|
|
89
|
-
#
|
|
102
|
+
# Estimates the relative cost of {.correlate_fft}.
|
|
103
|
+
# This is a heuristic for algorithm selection, not a timing measurement.
|
|
104
|
+
# @param (see convolve)
|
|
90
105
|
# @return [Float] machine-specific relative cost estimate
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
operations += plan.extended_size unless plan.valid?
|
|
96
|
-
4.54e-12 * operations
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
private
|
|
100
|
-
|
|
101
|
-
private :convolve_basic_valid
|
|
102
|
-
|
|
103
|
-
def operation_plan(signal, kernel, mode:, boundary:, fill_value:, origin:)
|
|
104
|
-
OperationPlan.new(signal, kernel, mode:, boundary:, fill_value:, origin:)
|
|
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
|
|
105
110
|
end
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
(kernel_size - 1)...signal_size
|
|
116
|
-
end
|
|
117
|
-
full_convolution = Numo::Pocketfft.fftconvolve(signal, kernel.reverse)
|
|
118
|
-
|
|
119
|
-
Numo::SFloat.cast(full_convolution[*ranges])
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def convolve_fft_wrap(signal, kernel, plan)
|
|
123
|
-
signal_value = Numo::DFloat.cast(signal)
|
|
124
|
-
folded_kernel = fold_kernel(Numo::DFloat.cast(kernel), signal.shape, plan.anchors)
|
|
125
|
-
spectrum = Numo::Pocketfft.fftn(signal_value) * Numo::Pocketfft.fftn(folded_kernel).conj
|
|
126
|
-
|
|
127
|
-
Numo::SFloat.cast(Numo::Pocketfft.ifftn(spectrum).real)
|
|
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
|
|
128
120
|
end
|
|
129
121
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
137
130
|
end
|
|
138
131
|
|
|
139
|
-
|
|
140
|
-
remainder = flat_index
|
|
141
|
-
Array.new(kernel_shape.length).tap do |target|
|
|
142
|
-
(kernel_shape.length - 1).downto(0) do |axis|
|
|
143
|
-
coordinate = remainder % kernel_shape[axis]
|
|
144
|
-
remainder /= kernel_shape[axis]
|
|
145
|
-
target[axis] = (coordinate - anchors[axis]) % signal_shape[axis]
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
132
|
+
private
|
|
149
133
|
|
|
150
|
-
|
|
151
|
-
return signal.size + kernel.size if plan.wrap?
|
|
152
|
-
return 0 if plan.valid?
|
|
134
|
+
private :convolve_basic_valid, :correlate_basic_valid
|
|
153
135
|
|
|
154
|
-
|
|
136
|
+
def execution(operation, signal, kernel, mode:, boundary:, fill_value:, origin:)
|
|
137
|
+
OperationExecution.new(operation, signal, kernel, mode:, boundary:, fill_value:, origin:)
|
|
155
138
|
end
|
|
156
139
|
end
|
|
157
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:
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
48
|
+
version: 0.7.0
|
|
49
49
|
- - "<"
|
|
50
50
|
- !ruby/object:Gem::Version
|
|
51
51
|
version: '0.8'
|
|
52
|
-
description: Fast cross-correlation for multidimensional
|
|
53
|
-
configurable output and boundary modes.
|
|
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,11 +64,22 @@ 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
|
|
69
79
|
- lib/convolver/operation_options.rb
|
|
70
80
|
- lib/convolver/operation_plan.rb
|
|
71
81
|
- lib/convolver/operation_shapes.rb
|
|
82
|
+
- lib/convolver/real_fft_shape.rb
|
|
72
83
|
- lib/convolver/signal_extension.rb
|
|
73
84
|
- lib/convolver/version.rb
|
|
74
85
|
homepage: https://github.com/neilslater/convolver
|
|
@@ -93,5 +104,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
93
104
|
requirements: []
|
|
94
105
|
rubygems_version: 4.0.16
|
|
95
106
|
specification_version: 4
|
|
96
|
-
summary: Fast cross-correlation for Numo::NArray
|
|
107
|
+
summary: Fast convolution and cross-correlation for Numo::NArray
|
|
97
108
|
test_files: []
|