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/bin/mm
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
abort "you need to write me"
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
abort "you need to write me"
|
data/lib/mm.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
module MM
|
2
|
-
VERSION = "1.
|
3
|
-
end
|
4
|
-
|
5
|
-
require 'mm/deltas'
|
6
|
-
require 'mm/metric'
|
7
|
-
require 'mm/pairs'
|
8
|
-
require 'mm/ratio'
|
9
|
-
require 'mm/scaling'
|
10
|
-
require 'mm/
|
1
|
+
module MM
|
2
|
+
VERSION = "1.3.1"
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'mm/deltas'
|
6
|
+
require 'mm/metric'
|
7
|
+
require 'mm/pairs'
|
8
|
+
require 'mm/ratio'
|
9
|
+
require 'mm/scaling'
|
10
|
+
require 'mm/search'
|
data/lib/mm/deltas.rb
CHANGED
@@ -1,35 +1,34 @@
|
|
1
|
-
module MM
|
2
|
-
module Deltas
|
3
|
-
def self.abs n
|
4
|
-
(n[0] - n[1]).abs
|
5
|
-
end
|
6
|
-
|
7
|
-
def self.diff n
|
8
|
-
n[0] - n[1]
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.mean n
|
12
|
-
n.inject(0.0, :+) / n.size
|
13
|
-
end
|
14
|
-
|
15
|
-
# Have to scale by 0.5 in order to normalize to a max distance of 1.0
|
16
|
-
def self.direction n
|
17
|
-
(n[0] <=> n[1]) * 0.5
|
18
|
-
end
|
19
|
-
|
20
|
-
# Accepts a tuple where the quotient responds to #numerator and #denominator
|
21
|
-
def self.tenney n
|
22
|
-
->(r) { Math.log2(r.numerator * r.denominator) }.call(n[0] / n[1])
|
23
|
-
end
|
24
|
-
|
25
|
-
# Accepts a tuple of anything that Math.log2 can handle
|
26
|
-
def self.log_ratio n
|
27
|
-
Math.log2((n[0] / n[1]).to_f).abs
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.ratio n
|
31
|
-
n[0] / n[1]
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
1
|
+
module MM
|
2
|
+
module Deltas
|
3
|
+
def self.abs n
|
4
|
+
(n[0] - n[1]).abs
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.diff n
|
8
|
+
n[0] - n[1]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.mean n
|
12
|
+
n.inject(0.0, :+) / n.size
|
13
|
+
end
|
14
|
+
|
15
|
+
# Have to scale by 0.5 in order to normalize to a max distance of 1.0
|
16
|
+
def self.direction n
|
17
|
+
(n[0] <=> n[1]) * 0.5
|
18
|
+
end
|
19
|
+
|
20
|
+
# Accepts a tuple where the quotient responds to #numerator and #denominator
|
21
|
+
def self.tenney n
|
22
|
+
->(r) { Math.log2(r.numerator * r.denominator) }.call(n[0] / n[1])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Accepts a tuple of anything that Math.log2 can handle
|
26
|
+
def self.log_ratio n
|
27
|
+
Math.log2((n[0] / n[1]).to_f).abs
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.ratio n
|
31
|
+
n[0] / n[1]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/mm/metric.rb
CHANGED
@@ -1,180 +1,191 @@
|
|
1
|
-
require 'psych'
|
2
|
-
|
3
|
-
module MM
|
4
|
-
class Metric
|
5
|
-
# Constructor for the Metric object.
|
6
|
-
#
|
7
|
-
# ordered - [Boolean]
|
8
|
-
# Controls whether metric is ordered
|
9
|
-
# pair - [Symbol, #call]
|
10
|
-
# Method of +MM::Deltas+, or where +Object#call+ returns an +Array+ of
|
11
|
-
# pairs.
|
12
|
-
# scale - [Symbol, #call]
|
13
|
-
# Method of +MM::Scaling+, or where +Object#call+ returns a scaled diff
|
14
|
-
# +Array+
|
15
|
-
# intra_delta - [Symbol, #call]
|
16
|
-
# Method of +MM::Deltas+, or where +Object#call+ returns +Array+ of
|
17
|
-
# differences between pairs of elements
|
18
|
-
# inter_delta - [Symbol, #call]
|
19
|
-
# Method of +MM::Deltas+, or where +Object#call+ returns +Array+ of
|
20
|
-
# differences between the diffs of the two input morphologies
|
21
|
-
#
|
22
|
-
def initialize(ordered: true, pair: nil, scale: nil, intra_delta: nil, inter_delta: nil)
|
23
|
-
@ordered = ordered
|
24
|
-
self.pair = pair
|
25
|
-
self.scale = scale
|
26
|
-
self.intra_delta = intra_delta
|
27
|
-
self.inter_delta = inter_delta
|
28
|
-
end
|
29
|
-
|
30
|
-
attr_accessor :ordered
|
31
|
-
|
32
|
-
# Public: Gets the distance between two vectors, according to the Metric
|
33
|
-
# object. Since +Metric+ can be duck-typed to work with +intra_delta+ and
|
34
|
-
# +inter_delta+, it should be possible to nest +Metric+ objects.
|
35
|
-
#
|
36
|
-
# v1 - The vector to call on.
|
37
|
-
# v2 - The vector to compare against.
|
38
|
-
#
|
39
|
-
# Returns a [Float] distance between the two vectors.
|
40
|
-
def call v1, v2
|
41
|
-
# "Readable" method provided for the parenthetically inclined
|
42
|
-
# inter_delta(scale(intra_delta(get_pairs(v1, v2))))
|
43
|
-
inter_delta scale intra_delta get_pairs v1, v2
|
44
|
-
end
|
45
|
-
|
46
|
-
# Public: Setter method for pair.
|
47
|
-
#
|
48
|
-
# pair - [Symbol, #call]
|
49
|
-
# Method of +MM::Deltas+, or where +Object#call+ returns an +Array+ of
|
50
|
-
# pairs.
|
51
|
-
#
|
52
|
-
# Returns a [Proc] pair.
|
53
|
-
def pair= pair
|
54
|
-
protected_use_method(MM::Pairs.new, :@pair, pair)
|
55
|
-
end
|
56
|
-
|
57
|
-
# Public: Setter method for scale.
|
58
|
-
#
|
59
|
-
# scale - Either a Proc that can process scaling, or a Symbol to look up in
|
60
|
-
# MM::Scaling.
|
61
|
-
#
|
62
|
-
# Returns scale.
|
63
|
-
def scale= scale
|
64
|
-
protected_use_method(MM::Scaling, :@scale, scale)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Public: Setter method for intra_delta.
|
68
|
-
#
|
69
|
-
# intra_delta - Either a Proc that can process the intra_delta, or a Symbol
|
70
|
-
# to look up in MM::Deltas.
|
71
|
-
#
|
72
|
-
# Returns intra_delta.
|
73
|
-
def intra_delta= intra_delta
|
74
|
-
protected_use_method(MM::Deltas, :@intra_delta, intra_delta)
|
75
|
-
end
|
76
|
-
|
77
|
-
# Public: Setter method for inter_delta.
|
78
|
-
#
|
79
|
-
# inter_delta - Either a Proc that can process as an inter_delta, or a
|
80
|
-
# Symbol where +MM::Deltas.respond_to? Symbol == true+
|
81
|
-
#
|
82
|
-
# Returns itself. Sets the instance variable @inter_delta.
|
83
|
-
def inter_delta= inter_delta
|
84
|
-
protected_use_method(MM::Deltas, :@inter_delta, inter_delta)
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
# Private: Calls the get_pairs Proc on each of v1 and v2. In lp's
|
90
|
-
# terminology, the get_pairs Proc should return either adjacent pairs
|
91
|
-
# of each of the two vectors (for linear metrics) or all possible pair
|
92
|
-
# combinations (for combinatorial metrics). For more, see Polansky 1992.
|
93
|
-
#
|
94
|
-
# v1 - the metric to use as a base
|
95
|
-
# v2 - the metric to compare to
|
96
|
-
#
|
97
|
-
# Returns an Array of Arrays of pairs.
|
98
|
-
def get_pairs v1, v2
|
99
|
-
[v1, v2].map {|x| @pair.call(x)}
|
100
|
-
end
|
101
|
-
|
102
|
-
# Private: Applies the delta to each pair of elements in a collection
|
103
|
-
# where each pair is [elem1, elem2]
|
104
|
-
#
|
105
|
-
# vp - vector pairs
|
106
|
-
#
|
107
|
-
# Returns the vector_deltas, which is the difference between each pair of
|
108
|
-
# elements in a given vector.
|
109
|
-
def intra_delta vp
|
110
|
-
vp.map {|x| x.map {|n| @intra_delta.call(n)}}
|
111
|
-
end
|
112
|
-
|
113
|
-
# Private: Calls the scaling Proc. It's a Method, so if you want to subclass it when
|
114
|
-
# subclassing Metric (in order to do something fast and crazy) you totally can.
|
115
|
-
#
|
116
|
-
# pairs - A sequence of pairs.
|
117
|
-
#
|
118
|
-
# Returns the output of the scaling Proc, ideally a sequence of pairs.
|
119
|
-
def scale pairs
|
120
|
-
@scale.call pairs
|
121
|
-
end
|
122
|
-
|
123
|
-
# Private: Accepts a series of vectors, either a sequence of pairs or two full
|
124
|
-
# collections, and reduces them to a single vector. Does not do any scaling.
|
125
|
-
#
|
126
|
-
# diffs - [Enumerable] Series of vectors, either a sequence of pairs or two full
|
127
|
-
# collections.
|
128
|
-
#
|
129
|
-
# Returns a single vector of the diffs between the two.
|
130
|
-
def inter_delta diffs
|
131
|
-
if @ordered
|
132
|
-
# Ordered Metrics take the mean of differences
|
133
|
-
Deltas.mean(diffs[0].zip(diffs[1]).map {|x| @inter_delta.call x})
|
134
|
-
else
|
135
|
-
# Unordered Metrics take the difference of means
|
136
|
-
Deltas.abs(diffs.map {|x| @inter_delta.call x})
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Private: Performs final averaging on the output of inter_delta. Can be overwritten
|
141
|
-
# when subclassed, in case you want to use a different method of averaging
|
142
|
-
# (sum of squares, etc.)
|
143
|
-
#
|
144
|
-
# diffs - The vector of the differences between the two vector deltas. Essentially
|
145
|
-
# the output of inter_delta. Should respond to #reduce.
|
146
|
-
#
|
147
|
-
# Returns distance [Numeric] The distance calculated by the diff
|
148
|
-
def post_scale diffs
|
149
|
-
diffs.reduce(0, :+).to_f / diffs.size
|
150
|
-
end
|
151
|
-
|
152
|
-
# Private: Assigns the Method named sym, if mod responds to it, to the
|
153
|
-
# instance variable var. Otherwise, assumes that the sym is actually a Proc
|
154
|
-
# and just tries to use it straight.
|
155
|
-
#
|
156
|
-
# mod - Object to see whether it has a method.
|
157
|
-
# var - instance variable to assign to.
|
158
|
-
# sym - Symbol to lookup in mod's exposed methods.
|
159
|
-
#
|
160
|
-
# Returns +sym+.
|
161
|
-
def protected_use_method mod, var, sym
|
162
|
-
if sym.is_a?(Symbol) && mod.respond_to?(sym)
|
163
|
-
self.instance_variable_set(var, mod.method(sym))
|
164
|
-
else
|
165
|
-
self.instance_variable_set(var, sym)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
### CONVENIENCE CREATION METHODS ###
|
170
|
-
# All of the following methods are created using the YAML definition file.
|
171
|
-
# See shortcuts.yml for the full definition.
|
172
|
-
# ::olm
|
173
|
-
# ::ocm
|
174
|
-
# ::ulm
|
175
|
-
# ::ucm
|
176
|
-
# ::old
|
177
|
-
# ::ocd
|
178
|
-
# ::uld
|
179
|
-
# ::ucd
|
180
|
-
METHOD_SHORTCUTS = Psych.load(File.read(File.join(File.dirname(__FILE__), '..', '
|
1
|
+
require 'psych'
|
2
|
+
|
3
|
+
module MM
|
4
|
+
class Metric
|
5
|
+
# Constructor for the Metric object.
|
6
|
+
#
|
7
|
+
# ordered - [Boolean]
|
8
|
+
# Controls whether metric is ordered
|
9
|
+
# pair - [Symbol, #call]
|
10
|
+
# Method of +MM::Deltas+, or where +Object#call+ returns an +Array+ of
|
11
|
+
# pairs.
|
12
|
+
# scale - [Symbol, #call]
|
13
|
+
# Method of +MM::Scaling+, or where +Object#call+ returns a scaled diff
|
14
|
+
# +Array+
|
15
|
+
# intra_delta - [Symbol, #call]
|
16
|
+
# Method of +MM::Deltas+, or where +Object#call+ returns +Array+ of
|
17
|
+
# differences between pairs of elements
|
18
|
+
# inter_delta - [Symbol, #call]
|
19
|
+
# Method of +MM::Deltas+, or where +Object#call+ returns +Array+ of
|
20
|
+
# differences between the diffs of the two input morphologies
|
21
|
+
#
|
22
|
+
def initialize(ordered: true, pair: nil, scale: nil, intra_delta: nil, inter_delta: nil)
|
23
|
+
@ordered = ordered
|
24
|
+
self.pair = pair
|
25
|
+
self.scale = scale
|
26
|
+
self.intra_delta = intra_delta
|
27
|
+
self.inter_delta = inter_delta
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :ordered
|
31
|
+
|
32
|
+
# Public: Gets the distance between two vectors, according to the Metric
|
33
|
+
# object. Since +Metric+ can be duck-typed to work with +intra_delta+ and
|
34
|
+
# +inter_delta+, it should be possible to nest +Metric+ objects.
|
35
|
+
#
|
36
|
+
# v1 - The vector to call on.
|
37
|
+
# v2 - The vector to compare against.
|
38
|
+
#
|
39
|
+
# Returns a [Float] distance between the two vectors.
|
40
|
+
def call v1, v2
|
41
|
+
# "Readable" method provided for the parenthetically inclined
|
42
|
+
# inter_delta(scale(intra_delta(get_pairs(v1, v2))))
|
43
|
+
inter_delta scale intra_delta get_pairs v1, v2
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: Setter method for pair.
|
47
|
+
#
|
48
|
+
# pair - [Symbol, #call]
|
49
|
+
# Method of +MM::Deltas+, or where +Object#call+ returns an +Array+ of
|
50
|
+
# pairs.
|
51
|
+
#
|
52
|
+
# Returns a [Proc] pair.
|
53
|
+
def pair= pair
|
54
|
+
protected_use_method(MM::Pairs.new, :@pair, pair)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Setter method for scale.
|
58
|
+
#
|
59
|
+
# scale - Either a Proc that can process scaling, or a Symbol to look up in
|
60
|
+
# MM::Scaling.
|
61
|
+
#
|
62
|
+
# Returns scale.
|
63
|
+
def scale= scale
|
64
|
+
protected_use_method(MM::Scaling, :@scale, scale)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Setter method for intra_delta.
|
68
|
+
#
|
69
|
+
# intra_delta - Either a Proc that can process the intra_delta, or a Symbol
|
70
|
+
# to look up in MM::Deltas.
|
71
|
+
#
|
72
|
+
# Returns intra_delta.
|
73
|
+
def intra_delta= intra_delta
|
74
|
+
protected_use_method(MM::Deltas, :@intra_delta, intra_delta)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public: Setter method for inter_delta.
|
78
|
+
#
|
79
|
+
# inter_delta - Either a Proc that can process as an inter_delta, or a
|
80
|
+
# Symbol where +MM::Deltas.respond_to? Symbol == true+
|
81
|
+
#
|
82
|
+
# Returns itself. Sets the instance variable @inter_delta.
|
83
|
+
def inter_delta= inter_delta
|
84
|
+
protected_use_method(MM::Deltas, :@inter_delta, inter_delta)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Private: Calls the get_pairs Proc on each of v1 and v2. In lp's
|
90
|
+
# terminology, the get_pairs Proc should return either adjacent pairs
|
91
|
+
# of each of the two vectors (for linear metrics) or all possible pair
|
92
|
+
# combinations (for combinatorial metrics). For more, see Polansky 1992.
|
93
|
+
#
|
94
|
+
# v1 - the metric to use as a base
|
95
|
+
# v2 - the metric to compare to
|
96
|
+
#
|
97
|
+
# Returns an Array of Arrays of pairs.
|
98
|
+
def get_pairs v1, v2
|
99
|
+
[v1, v2].map {|x| @pair.call(x)}
|
100
|
+
end
|
101
|
+
|
102
|
+
# Private: Applies the delta to each pair of elements in a collection
|
103
|
+
# where each pair is [elem1, elem2]
|
104
|
+
#
|
105
|
+
# vp - vector pairs
|
106
|
+
#
|
107
|
+
# Returns the vector_deltas, which is the difference between each pair of
|
108
|
+
# elements in a given vector.
|
109
|
+
def intra_delta vp
|
110
|
+
vp.map {|x| x.map {|n| @intra_delta.call(n)}}
|
111
|
+
end
|
112
|
+
|
113
|
+
# Private: Calls the scaling Proc. It's a Method, so if you want to subclass it when
|
114
|
+
# subclassing Metric (in order to do something fast and crazy) you totally can.
|
115
|
+
#
|
116
|
+
# pairs - A sequence of pairs.
|
117
|
+
#
|
118
|
+
# Returns the output of the scaling Proc, ideally a sequence of pairs.
|
119
|
+
def scale pairs
|
120
|
+
@scale.call pairs
|
121
|
+
end
|
122
|
+
|
123
|
+
# Private: Accepts a series of vectors, either a sequence of pairs or two full
|
124
|
+
# collections, and reduces them to a single vector. Does not do any scaling.
|
125
|
+
#
|
126
|
+
# diffs - [Enumerable] Series of vectors, either a sequence of pairs or two full
|
127
|
+
# collections.
|
128
|
+
#
|
129
|
+
# Returns a single vector of the diffs between the two.
|
130
|
+
def inter_delta diffs
|
131
|
+
if @ordered
|
132
|
+
# Ordered Metrics take the mean of differences
|
133
|
+
Deltas.mean(diffs[0].zip(diffs[1]).map {|x| @inter_delta.call x})
|
134
|
+
else
|
135
|
+
# Unordered Metrics take the difference of means
|
136
|
+
Deltas.abs(diffs.map {|x| @inter_delta.call x})
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Private: Performs final averaging on the output of inter_delta. Can be overwritten
|
141
|
+
# when subclassed, in case you want to use a different method of averaging
|
142
|
+
# (sum of squares, etc.)
|
143
|
+
#
|
144
|
+
# diffs - The vector of the differences between the two vector deltas. Essentially
|
145
|
+
# the output of inter_delta. Should respond to #reduce.
|
146
|
+
#
|
147
|
+
# Returns distance [Numeric] The distance calculated by the diff
|
148
|
+
def post_scale diffs
|
149
|
+
diffs.reduce(0, :+).to_f / diffs.size
|
150
|
+
end
|
151
|
+
|
152
|
+
# Private: Assigns the Method named sym, if mod responds to it, to the
|
153
|
+
# instance variable var. Otherwise, assumes that the sym is actually a Proc
|
154
|
+
# and just tries to use it straight.
|
155
|
+
#
|
156
|
+
# mod - Object to see whether it has a method.
|
157
|
+
# var - instance variable to assign to.
|
158
|
+
# sym - Symbol to lookup in mod's exposed methods.
|
159
|
+
#
|
160
|
+
# Returns +sym+.
|
161
|
+
def protected_use_method mod, var, sym
|
162
|
+
if sym.is_a?(Symbol) && mod.respond_to?(sym)
|
163
|
+
self.instance_variable_set(var, mod.method(sym))
|
164
|
+
else
|
165
|
+
self.instance_variable_set(var, sym)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
### CONVENIENCE CREATION METHODS ###
|
170
|
+
# All of the following methods are created using the YAML definition file.
|
171
|
+
# See shortcuts.yml for the full definition.
|
172
|
+
# ::olm
|
173
|
+
# ::ocm
|
174
|
+
# ::ulm
|
175
|
+
# ::ucm
|
176
|
+
# ::old
|
177
|
+
# ::ocd
|
178
|
+
# ::uld
|
179
|
+
# ::ucd
|
180
|
+
METHOD_SHORTCUTS = Psych.load(File.read(File.join(File.dirname(__FILE__), '..', 'shortcuts.yml')))
|
181
|
+
|
182
|
+
class << self
|
183
|
+
METHOD_SHORTCUTS.each do |k, v|
|
184
|
+
define_method k do |other = {}|
|
185
|
+
new((v.merge other))
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|