convolver 2.0.0 → 4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +84 -2
- data/README.md +150 -51
- data/ext/convolver/convolve_raw.c +51 -163
- data/ext/convolver/convolve_raw.h +5 -16
- data/ext/convolver/convolver.c +90 -24
- data/ext/convolver/correlate_raw.c +57 -0
- data/ext/convolver/correlate_raw.h +14 -0
- data/ext/convolver/extconf.rb +1 -0
- data/ext/convolver/fft_shape.c +116 -0
- data/ext/convolver/fft_shape.h +8 -0
- data/ext/convolver/raw_config.h +11 -0
- data/ext/convolver/raw_helpers.h +71 -0
- data/ext/convolver/raw_loop.h +93 -0
- data/lib/convolver/buffer_limits.rb +39 -0
- data/lib/convolver/buffer_preparation.rb +56 -0
- data/lib/convolver/circular_fft_operation.rb +84 -0
- data/lib/convolver/cost_profiles.rb +44 -0
- data/lib/convolver/fft_buffer_plan.rb +39 -0
- data/lib/convolver/fft_cost.rb +52 -0
- data/lib/convolver/fft_estimator.rb +64 -0
- data/lib/convolver/fft_limits.rb +64 -0
- data/lib/convolver/linear_fft_operation.rb +52 -0
- data/lib/convolver/operation_dtype.rb +42 -0
- data/lib/convolver/operation_execution.rb +118 -0
- data/lib/convolver/operation_options.rb +13 -10
- data/lib/convolver/operation_plan.rb +34 -6
- data/lib/convolver/operation_shapes.rb +42 -9
- data/lib/convolver/preparation_cost.rb +46 -0
- data/lib/convolver/real_fft_shape.rb +76 -0
- data/lib/convolver/signal_extension.rb +3 -2
- data/lib/convolver/version.rb +1 -1
- data/lib/convolver.rb +101 -114
- metadata +28 -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,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Convolver
|
|
4
|
+
# Shared estimates for input copies and Ruby boundary/folding work.
|
|
5
|
+
# @private
|
|
6
|
+
class PreparationCost
|
|
7
|
+
CONVERSION_COST = 7.5e-10
|
|
8
|
+
EXTENSION_COST = 1.0e-9
|
|
9
|
+
INDEX_COST = { nearest: 1.45e-7, reflect: 1.33e-7, mirror: 1.54e-7, wrap: 1.05e-7 }.freeze
|
|
10
|
+
FOLD_COST = { 1 => 3.3e-7, 2 => 3.7e-7, 3 => 5.5e-7 }.freeze
|
|
11
|
+
|
|
12
|
+
def initialize(plan)
|
|
13
|
+
@plan = plan
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def conversion
|
|
17
|
+
CONVERSION_COST * plan.conversion_size
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def extension
|
|
21
|
+
return 0.0 if plan.valid?
|
|
22
|
+
|
|
23
|
+
(EXTENSION_COST * plan.extended_size * (plan.dtype::ELEMENT_BYTE_SIZE / 4)) + indices
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def indices
|
|
27
|
+
return 0.0 if plan.valid? || plan.boundary == :constant
|
|
28
|
+
|
|
29
|
+
costs = plan.cost_profile ? plan.cost_profile.fetch(:indices) : INDEX_COST
|
|
30
|
+
costs.fetch(plan.boundary) * plan.extended_shape.sum
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def folding(kernel_size)
|
|
34
|
+
rank = plan.result_shape.length
|
|
35
|
+
return plan.cost_profile.fetch(:folding).fetch(rank - 1) * kernel_size if plan.cost_profile
|
|
36
|
+
|
|
37
|
+
FOLD_COST.fetch(rank, FOLD_COST.values.last) * kernel_size
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :plan
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private_constant :PreparationCost
|
|
46
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
Convolver.send(:fft_fast_shape, exact_shape, size_max)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def even_final_axis(shape)
|
|
46
|
+
result = shape.dup
|
|
47
|
+
result[-1] = checked_add(result[-1], 1) if result[-1].odd?
|
|
48
|
+
result.freeze
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def transform_cost(shape)
|
|
52
|
+
Convolver.send(:fft_shape_cost, shape)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def representable?(shape)
|
|
56
|
+
shape.reduce(1) do |product, size|
|
|
57
|
+
return false if !product.zero? && size > size_max / product
|
|
58
|
+
|
|
59
|
+
product * size
|
|
60
|
+
end
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def checked_add(left, right)
|
|
65
|
+
return left + right if right <= size_max - left
|
|
66
|
+
|
|
67
|
+
raise_overflow
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def raise_overflow
|
|
71
|
+
raise(limits ? FftUnavailable : RangeError, 'FFT shape exceeds native implementation limit')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private_constant :RealFftShape
|
|
76
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Convolver
|
|
4
4
|
# Extends a signal according to one operation's boundary policy.
|
|
5
|
+
# @private
|
|
5
6
|
class SignalExtension
|
|
6
7
|
def initialize(shapes, boundary:, fill_value:)
|
|
7
8
|
@shapes = shapes
|
|
@@ -10,7 +11,7 @@ module Convolver
|
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def call(signal)
|
|
13
|
-
source =
|
|
14
|
+
source = signal
|
|
14
15
|
return constant_extension(source) if boundary == :constant
|
|
15
16
|
|
|
16
17
|
source[*extension_indices]
|
|
@@ -21,7 +22,7 @@ module Convolver
|
|
|
21
22
|
attr_reader :shapes, :boundary, :fill_value
|
|
22
23
|
|
|
23
24
|
def constant_extension(source)
|
|
24
|
-
extended =
|
|
25
|
+
extended = source.class.new(*shapes.extended_shape).fill(fill_value)
|
|
25
26
|
ranges = source.shape.zip(shapes.padding_before).map do |signal_size, before|
|
|
26
27
|
before...(before + signal_size)
|
|
27
28
|
end
|
data/lib/convolver/version.rb
CHANGED
data/lib/convolver.rb
CHANGED
|
@@ -5,153 +5,140 @@ 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.
|
|
21
|
+
#
|
|
22
|
+
# Results use the dtype selected from both inputs, unless dtype overrides it.
|
|
23
|
+
# Inputs must be nonempty arrays of equal rank, with at most {MAX_RANK}
|
|
24
|
+
# dimensions. Scalars multiply directly.
|
|
25
|
+
# In valid mode the kernel must fit inside the signal in every dimension.
|
|
26
|
+
# Full mode permits only constant boundaries; nonconstant boundaries and
|
|
27
|
+
# nonzero origins are supported only in same mode.
|
|
17
28
|
#
|
|
18
|
-
# @param signal [Numo::NArray] input
|
|
19
|
-
# @param kernel [Numo::NArray]
|
|
20
|
-
# @param mode [:valid, :same, :full]
|
|
21
|
-
# @param boundary [:constant, :nearest, :reflect, :mirror, :wrap]
|
|
22
|
-
#
|
|
23
|
-
# @param
|
|
24
|
-
#
|
|
25
|
-
# @
|
|
29
|
+
# @param signal [Numo::NArray] input signal
|
|
30
|
+
# @param kernel [Numo::NArray] convolution kernel
|
|
31
|
+
# @param mode [:valid, :same, :full] output extent (default: :valid)
|
|
32
|
+
# @param boundary [:constant, :nearest, :reflect, :mirror, :wrap]
|
|
33
|
+
# signal extension policy (default: :constant)
|
|
34
|
+
# @param fill_value [Numeric] constant boundary value (default: 0);
|
|
35
|
+
# valid mode requires zero, and other boundaries reject this option
|
|
36
|
+
# @param origin [Integer, Array<Integer>] offset from the kernel midpoint,
|
|
37
|
+
# shared across axes or one per axis (default: 0)
|
|
38
|
+
# @param dtype [Class, nil] Numo::SFloat or Numo::DFloat; nil promotes both inputs.
|
|
39
|
+
# Integer types of 8/16 bits select SFloat; 32/64 bits select DFloat.
|
|
40
|
+
# Only these concrete integer and floating classes are supported.
|
|
41
|
+
# @return [Numo::SFloat, Numo::DFloat] mathematical convolution result
|
|
42
|
+
# @raise [ArgumentError] if inputs or options are invalid
|
|
43
|
+
# @raise [RangeError] if planned dimensions, buffers or FFT arithmetic exceed native limits
|
|
26
44
|
def convolve(signal, kernel, mode: :valid, boundary: :constant,
|
|
27
|
-
fill_value: UNSPECIFIED_FILL, origin: 0)
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
45
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
46
|
+
execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).automatic
|
|
47
|
+
end
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
# Chooses the likely fastest cross-correlation implementation.
|
|
50
|
+
# Uses the same input and option rules as {.convolve}, without reversing
|
|
51
|
+
# the kernel's orientation.
|
|
52
|
+
# @param (see convolve)
|
|
53
|
+
# @return [Numo::SFloat, Numo::DFloat] cross-correlation result
|
|
54
|
+
# @raise (see convolve)
|
|
55
|
+
def correlate(signal, kernel, mode: :valid, boundary: :constant,
|
|
56
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
57
|
+
execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).automatic
|
|
40
58
|
end
|
|
41
59
|
|
|
42
|
-
# Uses the direct native
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# @
|
|
60
|
+
# Uses the direct native mathematical convolution implementation.
|
|
61
|
+
# @param (see convolve)
|
|
62
|
+
# @return [Numo::SFloat, Numo::DFloat] mathematical convolution result
|
|
63
|
+
# @raise (see convolve)
|
|
46
64
|
def convolve_basic(signal, kernel, mode: :valid, boundary: :constant,
|
|
47
|
-
fill_value: UNSPECIFIED_FILL, origin: 0)
|
|
48
|
-
|
|
49
|
-
convolve_basic_valid(plan.extend_signal(signal), kernel)
|
|
65
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
66
|
+
execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).basic
|
|
50
67
|
end
|
|
51
68
|
|
|
52
|
-
# Uses
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
plan = operation_plan(signal, kernel, mode:, boundary:, fill_value:, origin:)
|
|
61
|
-
return convolve_fft_wrap(signal, kernel, plan) if plan.wrap?
|
|
69
|
+
# Uses the direct native cross-correlation implementation.
|
|
70
|
+
# @param (see convolve)
|
|
71
|
+
# @return [Numo::SFloat, Numo::DFloat] cross-correlation result
|
|
72
|
+
# @raise (see convolve)
|
|
73
|
+
def correlate_basic(signal, kernel, mode: :valid, boundary: :constant,
|
|
74
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
75
|
+
execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).basic
|
|
76
|
+
end
|
|
62
77
|
|
|
63
|
-
|
|
78
|
+
# Uses PocketFFT to calculate mathematical convolution.
|
|
79
|
+
# @param (see convolve)
|
|
80
|
+
# @return [Numo::SFloat, Numo::DFloat] mathematical convolution result
|
|
81
|
+
# @raise (see convolve)
|
|
82
|
+
def convolve_fft(signal, kernel, mode: :valid, boundary: :constant,
|
|
83
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
84
|
+
execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).fft
|
|
64
85
|
end
|
|
65
86
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# @
|
|
69
|
-
# @
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
options = operation_options(mode, boundary, fill_value, origin)
|
|
74
|
-
convolve_fft(signal, kernel, **options)
|
|
87
|
+
# Uses PocketFFT to calculate cross-correlation.
|
|
88
|
+
# @param (see convolve)
|
|
89
|
+
# @return [Numo::SFloat, Numo::DFloat] cross-correlation result
|
|
90
|
+
# @raise (see convolve)
|
|
91
|
+
def correlate_fft(signal, kernel, mode: :valid, boundary: :constant,
|
|
92
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
93
|
+
execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).fft
|
|
75
94
|
end
|
|
76
95
|
|
|
77
|
-
# Estimates the relative cost of {.convolve_fft}
|
|
78
|
-
#
|
|
96
|
+
# Estimates the relative cost of {.convolve_fft}.
|
|
97
|
+
# This is a heuristic for algorithm selection, not a timing measurement.
|
|
98
|
+
# @param (see convolve)
|
|
79
99
|
# @return [Float] machine-specific relative cost estimate
|
|
100
|
+
# @raise (see convolve)
|
|
80
101
|
def predict_convolve_fft_time(signal, kernel, mode: :valid, boundary: :constant,
|
|
81
|
-
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))
|
|
102
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
103
|
+
execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).fft_time
|
|
86
104
|
end
|
|
87
105
|
|
|
88
|
-
# Estimates the relative cost of {.
|
|
89
|
-
#
|
|
106
|
+
# Estimates the relative cost of {.correlate_fft}.
|
|
107
|
+
# This is a heuristic for algorithm selection, not a timing measurement.
|
|
108
|
+
# @param (see convolve)
|
|
90
109
|
# @return [Float] machine-specific relative cost estimate
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
operations += plan.extended_size unless plan.valid?
|
|
96
|
-
4.54e-12 * operations
|
|
110
|
+
# @raise (see convolve)
|
|
111
|
+
def predict_correlate_fft_time(signal, kernel, mode: :valid, boundary: :constant,
|
|
112
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
113
|
+
execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).fft_time
|
|
97
114
|
end
|
|
98
115
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def operation_options(mode, boundary, fill_value, origin)
|
|
108
|
-
options = { mode:, boundary:, origin: }
|
|
109
|
-
options[:fill_value] = fill_value unless fill_value.equal?(UNSPECIFIED_FILL)
|
|
110
|
-
options
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def convolve_fft_valid(signal, kernel)
|
|
114
|
-
ranges = kernel.shape.zip(signal.shape).map do |kernel_size, signal_size|
|
|
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)
|
|
116
|
+
# Estimates the relative cost of {.convolve_basic}.
|
|
117
|
+
# This is a heuristic for algorithm selection, not a timing measurement.
|
|
118
|
+
# @param (see convolve)
|
|
119
|
+
# @return [Float] machine-specific relative cost estimate
|
|
120
|
+
# @raise (see convolve)
|
|
121
|
+
def predict_convolve_basic_time(signal, kernel, mode: :valid, boundary: :constant,
|
|
122
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
123
|
+
execution(:convolution, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).basic_time
|
|
128
124
|
end
|
|
129
125
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
126
|
+
# Estimates the relative cost of {.correlate_basic}.
|
|
127
|
+
# This is a heuristic for algorithm selection, not a timing measurement.
|
|
128
|
+
# @param (see convolve)
|
|
129
|
+
# @return [Float] machine-specific relative cost estimate
|
|
130
|
+
# @raise (see convolve)
|
|
131
|
+
def predict_correlate_basic_time(signal, kernel, mode: :valid, boundary: :constant,
|
|
132
|
+
fill_value: UNSPECIFIED_FILL, origin: 0, dtype: nil)
|
|
133
|
+
execution(:correlation, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:).basic_time
|
|
137
134
|
end
|
|
138
135
|
|
|
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
|
|
136
|
+
private
|
|
149
137
|
|
|
150
|
-
|
|
151
|
-
return signal.size + kernel.size if plan.wrap?
|
|
152
|
-
return 0 if plan.valid?
|
|
138
|
+
private :convolve_basic_valid, :correlate_basic_valid, :fft_fast_shape, :fft_shape_cost
|
|
153
139
|
|
|
154
|
-
|
|
140
|
+
def execution(operation, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:)
|
|
141
|
+
OperationExecution.new(operation, signal, kernel, mode:, boundary:, fill_value:, origin:, dtype:)
|
|
155
142
|
end
|
|
156
143
|
end
|
|
157
144
|
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: 4.0.0
|
|
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,31 @@ 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
|
|
70
|
+
- ext/convolver/fft_shape.c
|
|
71
|
+
- ext/convolver/fft_shape.h
|
|
72
|
+
- ext/convolver/raw_config.h
|
|
73
|
+
- ext/convolver/raw_helpers.h
|
|
74
|
+
- ext/convolver/raw_loop.h
|
|
68
75
|
- lib/convolver.rb
|
|
76
|
+
- lib/convolver/buffer_limits.rb
|
|
77
|
+
- lib/convolver/buffer_preparation.rb
|
|
78
|
+
- lib/convolver/circular_fft_operation.rb
|
|
79
|
+
- lib/convolver/cost_profiles.rb
|
|
80
|
+
- lib/convolver/fft_buffer_plan.rb
|
|
81
|
+
- lib/convolver/fft_cost.rb
|
|
82
|
+
- lib/convolver/fft_estimator.rb
|
|
83
|
+
- lib/convolver/fft_limits.rb
|
|
84
|
+
- lib/convolver/linear_fft_operation.rb
|
|
85
|
+
- lib/convolver/operation_dtype.rb
|
|
86
|
+
- lib/convolver/operation_execution.rb
|
|
69
87
|
- lib/convolver/operation_options.rb
|
|
70
88
|
- lib/convolver/operation_plan.rb
|
|
71
89
|
- lib/convolver/operation_shapes.rb
|
|
90
|
+
- lib/convolver/preparation_cost.rb
|
|
91
|
+
- lib/convolver/real_fft_shape.rb
|
|
72
92
|
- lib/convolver/signal_extension.rb
|
|
73
93
|
- lib/convolver/version.rb
|
|
74
94
|
homepage: https://github.com/neilslater/convolver
|
|
@@ -93,5 +113,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
93
113
|
requirements: []
|
|
94
114
|
rubygems_version: 4.0.16
|
|
95
115
|
specification_version: 4
|
|
96
|
-
summary: Fast cross-correlation for Numo::NArray
|
|
116
|
+
summary: Fast convolution and cross-correlation for Numo::NArray
|
|
97
117
|
test_files: []
|