morphological_metrics 1.3.0 → 1.3.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 +5 -5
- data/.autotest +30 -30
- data/.minitest.rb +3 -3
- data/.travis.yml +10 -10
- data/Gemfile +0 -0
- data/Gemfile.lock +0 -0
- data/History.txt +6 -6
- data/Manifest.txt +27 -27
- data/README.rdoc +66 -65
- data/ROADMAP.txt +63 -63
- data/Rakefile +33 -32
- data/bin/mm +3 -3
- data/lib/mm.rb +10 -10
- data/lib/mm/deltas.rb +34 -35
- data/lib/mm/metric.rb +191 -180
- data/lib/mm/pairs.rb +12 -12
- data/lib/mm/ratio.rb +156 -145
- data/lib/mm/scaling.rb +30 -30
- data/lib/mm/search.rb +81 -81
- data/lib/shortcuts.yml +49 -49
- data/test/helpers.rb +22 -19
- data/test/mm/test_metric.rb +168 -160
- data/test/mm/test_mm.rb +6 -5
- data/test/mm/test_pairs.rb +27 -24
- data/test/mm/test_ratio.rb +139 -130
- data/test/mm/test_search.rb +89 -83
- metadata +8 -16
- data/.gemtest +0 -0
data/lib/mm/pairs.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
module MM
|
2
|
-
class Pairs
|
3
|
-
def linear vector
|
4
|
-
vector.each_cons(2).to_a
|
5
|
-
end
|
6
|
-
|
7
|
-
def combinatorial vector
|
8
|
-
vector.combination(2).to_a
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
1
|
+
module MM
|
2
|
+
class Pairs
|
3
|
+
def linear vector
|
4
|
+
vector.each_cons(2).to_a
|
5
|
+
end
|
6
|
+
|
7
|
+
def combinatorial vector
|
8
|
+
vector.combination(2).to_a
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
data/lib/mm/ratio.rb
CHANGED
@@ -1,145 +1,156 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
require 'prime'
|
3
|
-
|
4
|
-
module MM; end
|
5
|
-
|
6
|
-
class MM::Ratio
|
7
|
-
include Comparable
|
8
|
-
include Enumerable
|
9
|
-
|
10
|
-
def initialize n, d
|
11
|
-
gcd = n.gcd d
|
12
|
-
@numerator = n / gcd
|
13
|
-
@denominator = d / gcd
|
14
|
-
end
|
15
|
-
|
16
|
-
attr_accessor :numerator, :denominator
|
17
|
-
|
18
|
-
def * other
|
19
|
-
MM::Ratio.new(self.numerator * other.numerator, self.denominator * other.denominator)
|
20
|
-
end
|
21
|
-
|
22
|
-
def / other
|
23
|
-
self * other.reciprocal
|
24
|
-
end
|
25
|
-
|
26
|
-
def + other
|
27
|
-
MM::Ratio.new(self.numerator*other.denominator + other.numerator*self.denominator,
|
28
|
-
self.denominator*other.denominator)
|
29
|
-
end
|
30
|
-
|
31
|
-
def - other
|
32
|
-
self + (other * MM::Ratio.new(-1,1))
|
33
|
-
end
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
return
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
def
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
def
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
r
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
def
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
def
|
110
|
-
|
111
|
-
end
|
112
|
-
|
113
|
-
def
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
120
|
-
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
end
|
138
|
-
|
139
|
-
def self.
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
vector[
|
145
|
-
|
1
|
+
require 'yaml'
|
2
|
+
require 'prime'
|
3
|
+
|
4
|
+
module MM; end
|
5
|
+
|
6
|
+
class MM::Ratio
|
7
|
+
include Comparable
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize n, d
|
11
|
+
gcd = n.gcd d
|
12
|
+
@numerator = n / gcd
|
13
|
+
@denominator = d / gcd
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :numerator, :denominator
|
17
|
+
|
18
|
+
def * other
|
19
|
+
MM::Ratio.new(self.numerator * other.numerator, self.denominator * other.denominator)
|
20
|
+
end
|
21
|
+
|
22
|
+
def / other
|
23
|
+
self * other.reciprocal
|
24
|
+
end
|
25
|
+
|
26
|
+
def + other
|
27
|
+
MM::Ratio.new(self.numerator*other.denominator + other.numerator*self.denominator,
|
28
|
+
self.denominator*other.denominator)
|
29
|
+
end
|
30
|
+
|
31
|
+
def - other
|
32
|
+
self + (other * MM::Ratio.new(-1,1))
|
33
|
+
end
|
34
|
+
|
35
|
+
def [] i
|
36
|
+
if i == 0
|
37
|
+
self.numerator
|
38
|
+
elsif i == 1
|
39
|
+
self.denominator
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Works very similarly to the Prime::prime_division method, except that
|
44
|
+
# factors in the numerator are positive, and factors in the denominator are
|
45
|
+
# negative.
|
46
|
+
def factors
|
47
|
+
n_factors = ::Prime.prime_division(@numerator)
|
48
|
+
d_factors = ::Prime.prime_division(@denominator).map {|d| d[1] *= -1; d}
|
49
|
+
n_factors.concat(d_factors).sort_by {|x| x[0]}
|
50
|
+
end
|
51
|
+
|
52
|
+
def abs
|
53
|
+
if self < MM::Ratio.new(0, 1)
|
54
|
+
self * MM::Ratio.new(-1,1)
|
55
|
+
else
|
56
|
+
self
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def <=> other
|
61
|
+
# Ensure that the comparison makes sense
|
62
|
+
return nil unless other.respond_to? :-
|
63
|
+
|
64
|
+
case
|
65
|
+
when (self - other).to_f > 0
|
66
|
+
return 1
|
67
|
+
when (self - other).to_f < 0
|
68
|
+
return -1
|
69
|
+
end
|
70
|
+
return 0
|
71
|
+
end
|
72
|
+
|
73
|
+
def eql? other
|
74
|
+
other.is_a?(MM::Ratio) && (self == other)
|
75
|
+
end
|
76
|
+
|
77
|
+
def hash
|
78
|
+
[@numerator, @denominator, MM::Ratio].hash
|
79
|
+
end
|
80
|
+
|
81
|
+
def cents
|
82
|
+
Math.log2(self.to_f) * 1200.0
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.from_s r
|
86
|
+
if r.respond_to? :split
|
87
|
+
if r =~ /\s/
|
88
|
+
r.split(/\s/).inject([]) {|memo, ratio|
|
89
|
+
memo << self.from_s(ratio)
|
90
|
+
}
|
91
|
+
else
|
92
|
+
string_to_ratio r
|
93
|
+
end
|
94
|
+
else
|
95
|
+
r.map {|s| self.from_s s}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.string_to_ratio string
|
100
|
+
m = string.match(/(\d+)\/(\d+)/)
|
101
|
+
MM::Ratio.new(m[1].to_i, m[2].to_i)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Loads a sequence of MM::Ratios from a YAML file.
|
105
|
+
def self.from_yaml yaml_string
|
106
|
+
YAML.load(yaml_string).map {|r| MM::Ratio.from_s r}
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_f
|
110
|
+
@numerator.to_f / @denominator
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_s
|
114
|
+
"#{@numerator}/#{@denominator}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def reciprocal
|
118
|
+
MM::Ratio.new(@denominator, @numerator)
|
119
|
+
end
|
120
|
+
|
121
|
+
def prime_limit
|
122
|
+
self.map { |r|
|
123
|
+
r.prime_division.map { |s|
|
124
|
+
s.first
|
125
|
+
}.max
|
126
|
+
}.compact.max
|
127
|
+
end
|
128
|
+
|
129
|
+
def each
|
130
|
+
if block_given?
|
131
|
+
[@numerator, @denominator].each do |r|
|
132
|
+
yield r
|
133
|
+
end
|
134
|
+
else
|
135
|
+
[@numerator, @denominator].each
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.to_vector point
|
140
|
+
point.each_cons(2).map {|r| r[0] / r[1]}
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.from_vector vector
|
144
|
+
vector.inject([MM::Ratio.new(1,1)]) {|m, r| m << (m.last / r)}
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.change_interval point, index, interval
|
148
|
+
vector = MM::Ratio.to_vector(point)
|
149
|
+
if interval == :reciprocal
|
150
|
+
interval = vector[index].reciprocal
|
151
|
+
end
|
152
|
+
vector[index] = interval
|
153
|
+
MM::Ratio.from_vector(vector)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
data/lib/mm/scaling.rb
CHANGED
@@ -1,30 +1,30 @@
|
|
1
|
-
module MM
|
2
|
-
module Scaling
|
3
|
-
# All of these functions require a sequence of distance pair evaluations of
|
4
|
-
# the given metric. They all output scaled versions.
|
5
|
-
|
6
|
-
# Scale to the max across both vector
|
7
|
-
def self.none pairs
|
8
|
-
pairs
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.absolute pairs
|
12
|
-
max = (pairs.map(&:max)).max
|
13
|
-
pairs.map {|x| x.map {|y| y.to_f / max}}
|
14
|
-
end
|
15
|
-
|
16
|
-
# Scale each vector to its own max
|
17
|
-
def self.relative pairs
|
18
|
-
maxes = pairs.map(&:max)
|
19
|
-
pairs.zip(maxes).map {|pair, max| pair.map {|x| x.to_f / max}}
|
20
|
-
end
|
21
|
-
|
22
|
-
# Note: a bit hacky. But anything starting with "get_" should be considered
|
23
|
-
# a meta-scaling method. This method returns a Proc that has a particular
|
24
|
-
# scaling value hard-coded into it, for re-use and re-use.
|
25
|
-
def self.get_global max
|
26
|
-
->(pairs) {pairs.map {|x| x.map {|y| y.to_f / max}}}
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
1
|
+
module MM
|
2
|
+
module Scaling
|
3
|
+
# All of these functions require a sequence of distance pair evaluations of
|
4
|
+
# the given metric. They all output scaled versions.
|
5
|
+
|
6
|
+
# Scale to the max across both vector
|
7
|
+
def self.none pairs
|
8
|
+
pairs
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.absolute pairs
|
12
|
+
max = (pairs.map(&:max)).max
|
13
|
+
pairs.map {|x| x.map {|y| y.to_f / max}}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Scale each vector to its own max
|
17
|
+
def self.relative pairs
|
18
|
+
maxes = pairs.map(&:max)
|
19
|
+
pairs.zip(maxes).map {|pair, max| pair.map {|x| x.to_f / max}}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Note: a bit hacky. But anything starting with "get_" should be considered
|
23
|
+
# a meta-scaling method. This method returns a Proc that has a particular
|
24
|
+
# scaling value hard-coded into it, for re-use and re-use.
|
25
|
+
def self.get_global max
|
26
|
+
->(pairs) {pairs.map {|x| x.map {|y| y.to_f / max}}}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
data/lib/mm/search.rb
CHANGED
@@ -1,81 +1,81 @@
|
|
1
|
-
module MM; end
|
2
|
-
|
3
|
-
# All you need to do is add an adjacent_points_function and cost_function
|
4
|
-
class MM::Search
|
5
|
-
attr_accessor :candidates, :delta, :starting_point, :path, :banned, :iterations
|
6
|
-
attr_writer :cost_function, :adjacent_points_function
|
7
|
-
|
8
|
-
def initialize starting_point, delta = 0.001
|
9
|
-
@starting_point = starting_point
|
10
|
-
@delta = delta
|
11
|
-
@current_point = @starting_point
|
12
|
-
@path = []
|
13
|
-
@banned = []
|
14
|
-
@iterations = 0
|
15
|
-
end
|
16
|
-
|
17
|
-
# Finds a vector beginning from the starting point
|
18
|
-
def find
|
19
|
-
find_from_point @starting_point
|
20
|
-
end
|
21
|
-
|
22
|
-
def find_from_point point
|
23
|
-
@iterations += 1
|
24
|
-
# The adjacent points are all sorted
|
25
|
-
# raise StopIteration if cost_function(point) > current_cost
|
26
|
-
add_to_path point
|
27
|
-
sorted_adjacent_points = get_sorted_adjacent_points
|
28
|
-
# If we've made it, return it.
|
29
|
-
if made_it?
|
30
|
-
@current_point
|
31
|
-
else
|
32
|
-
begin
|
33
|
-
find_from_point sorted_adjacent_points.next
|
34
|
-
rescue StopIteration => er
|
35
|
-
# When the list of adjacent points runs out, backtrack
|
36
|
-
backtrack
|
37
|
-
retry unless @current_point.nil?
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def add_to_path point
|
43
|
-
@current_point = point
|
44
|
-
@path << point
|
45
|
-
end
|
46
|
-
|
47
|
-
def backtrack
|
48
|
-
@banned << @path.pop
|
49
|
-
puts "Path: #{@path.size}, Banned: #{@banned.size}" if ENV["DEBUG_RB"] && ENV["DEBUG_RB"].to_i > 1
|
50
|
-
@current_point = @path.last
|
51
|
-
end
|
52
|
-
|
53
|
-
def calculate_cost candidates
|
54
|
-
candidates.map {|x| cost_function x}
|
55
|
-
end
|
56
|
-
|
57
|
-
def made_it?
|
58
|
-
current_cost < @delta
|
59
|
-
end
|
60
|
-
|
61
|
-
def cost_function *args
|
62
|
-
@cost_function.call(*args)
|
63
|
-
end
|
64
|
-
|
65
|
-
def current_cost
|
66
|
-
cost_function @current_point
|
67
|
-
end
|
68
|
-
|
69
|
-
def get_adjacent_points *args
|
70
|
-
@adjacent_points_function.call(@current_point, *args)
|
71
|
-
end
|
72
|
-
|
73
|
-
def get_sorted_adjacent_points *args
|
74
|
-
get_adjacent_points(*args)
|
75
|
-
.reject {|c| @path.include? c}
|
76
|
-
.reject {|c| @banned.include? c}
|
77
|
-
.sort_by {|x| cost_function x}
|
78
|
-
.to_enum
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
1
|
+
module MM; end
|
2
|
+
|
3
|
+
# All you need to do is add an adjacent_points_function and cost_function
|
4
|
+
class MM::Search
|
5
|
+
attr_accessor :candidates, :delta, :starting_point, :path, :banned, :iterations
|
6
|
+
attr_writer :cost_function, :adjacent_points_function
|
7
|
+
|
8
|
+
def initialize starting_point, delta = 0.001
|
9
|
+
@starting_point = starting_point
|
10
|
+
@delta = delta
|
11
|
+
@current_point = @starting_point
|
12
|
+
@path = []
|
13
|
+
@banned = []
|
14
|
+
@iterations = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
# Finds a vector beginning from the starting point
|
18
|
+
def find
|
19
|
+
find_from_point @starting_point
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_from_point point
|
23
|
+
@iterations += 1
|
24
|
+
# The adjacent points are all sorted
|
25
|
+
# raise StopIteration if cost_function(point) > current_cost
|
26
|
+
add_to_path point
|
27
|
+
sorted_adjacent_points = get_sorted_adjacent_points
|
28
|
+
# If we've made it, return it.
|
29
|
+
if made_it?
|
30
|
+
@current_point
|
31
|
+
else
|
32
|
+
begin
|
33
|
+
find_from_point sorted_adjacent_points.next
|
34
|
+
rescue StopIteration => er
|
35
|
+
# When the list of adjacent points runs out, backtrack
|
36
|
+
backtrack
|
37
|
+
retry unless @current_point.nil?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_to_path point
|
43
|
+
@current_point = point
|
44
|
+
@path << point
|
45
|
+
end
|
46
|
+
|
47
|
+
def backtrack
|
48
|
+
@banned << @path.pop
|
49
|
+
puts "Path: #{@path.size}, Banned: #{@banned.size}" if ENV["DEBUG_RB"] && ENV["DEBUG_RB"].to_i > 1
|
50
|
+
@current_point = @path.last
|
51
|
+
end
|
52
|
+
|
53
|
+
def calculate_cost candidates
|
54
|
+
candidates.map {|x| cost_function x}
|
55
|
+
end
|
56
|
+
|
57
|
+
def made_it?
|
58
|
+
current_cost < @delta
|
59
|
+
end
|
60
|
+
|
61
|
+
def cost_function *args
|
62
|
+
@cost_function.call(*args)
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_cost
|
66
|
+
cost_function @current_point
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_adjacent_points *args
|
70
|
+
@adjacent_points_function.call(@current_point, *args)
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_sorted_adjacent_points *args
|
74
|
+
get_adjacent_points(*args)
|
75
|
+
.reject {|c| @path.include? c}
|
76
|
+
.reject {|c| @banned.include? c}
|
77
|
+
.sort_by {|x| cost_function x}
|
78
|
+
.to_enum
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|