morphological_metrics 1.2.0 → 1.3.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.
data/lib/mm/metric.rb CHANGED
@@ -1,191 +1,180 @@
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
-
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__), '..', 'shortcut
data/lib/mm/pairs.rb CHANGED
@@ -1,11 +1,12 @@
1
- module MM
2
- class Pairs
3
- def linear vector
4
- vector.each_cons(2).to_a
5
- end
6
- def combinatorial vector
7
- vector.combination(2).to_a
8
- end
9
- end
10
- end
11
-
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,148 +1,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
- # Works very similarly to the Prime::prime_division method, except that
36
- # factors in the numerator are positive, and factors in the denominator are
37
- # negative.
38
- def factors
39
- n_factors = ::Prime.prime_division(@numerator)
40
- d_factors = ::Prime.prime_division(@denominator).map {|d| d[1] *= -1; d}
41
- n_factors.concat(d_factors).sort_by {|x| x[0]}
42
- end
43
-
44
- def abs
45
- if self < MM::Ratio.new(0, 1)
46
- self * MM::Ratio.new(-1,1)
47
- else
48
- self
49
- end
50
- end
51
-
52
- def <=> other
53
- # Ensure that the comparison makes sense
54
- return nil unless other.respond_to? :-
55
-
56
- case
57
- when (self - other).to_f > 0
58
- return 1
59
- when (self - other).to_f < 0
60
- return -1
61
- end
62
- return 0
63
- end
64
-
65
- def eql? other
66
- other.is_a?(MM::Ratio) && (self == other)
67
- end
68
-
69
- def hash
70
- [@numerator, @denominator, MM::Ratio].hash
71
- end
72
-
73
- def cents
74
- Math.log2(self.to_f) * 1200.0
75
- end
76
-
77
- def self.from_s r
78
- if r.respond_to? :split
79
- if r =~ /\s/
80
- r.split(/\s/).inject([]) {|memo, ratio|
81
- memo << self.from_s(ratio)
82
- }
83
- else
84
- string_to_ratio r
85
- end
86
- else
87
- r.map {|s| self.from_s s}
88
- end
89
- end
90
-
91
- def self.string_to_ratio string
92
- m = string.match(/(\d+)\/(\d+)/)
93
- MM::Ratio.new(m[1].to_i, m[2].to_i)
94
- end
95
-
96
- # Loads a sequence of MM::Ratios from a YAML file.
97
- def self.from_yaml yaml_string
98
- YAML.load(yaml_string).map {|r| MM::Ratio.from_s r}
99
- end
100
-
101
- def to_f
102
- @numerator.to_f / @denominator
103
- end
104
-
105
- def to_s
106
- "#{@numerator}/#{@denominator}"
107
- end
108
-
109
- def reciprocal
110
- MM::Ratio.new(@denominator, @numerator)
111
- end
112
-
113
- def prime_limit
114
- self.map { |r|
115
- r.prime_division.map { |s|
116
- s.first
117
- }.max
118
- }.compact.max
119
- end
120
-
121
- def each
122
- if block_given?
123
- [@numerator, @denominator].each do |r|
124
- yield r
125
- end
126
- else
127
- [@numerator, @denominator].each
128
- end
129
- end
130
-
131
- def self.to_vector point
132
- point.each_cons(2).map {|r| r[0] / r[1]}
133
- end
134
-
135
- def self.from_vector vector
136
- vector.inject([MM::Ratio.new(1,1)]) {|m, r| m << (m.last / r)}
137
- end
138
-
139
- def self.change_interval point, index, interval
140
- vector = MM::Ratio.to_vector(point)
141
- if interval == :reciprocal
142
- interval = vector[index].reciprocal
143
- end
144
- vector[index] = interval
145
- MM::Ratio.from_vector(vector)
146
- end
147
- end
148
-
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
+ # Works very similarly to the Prime::prime_division method, except that
36
+ # factors in the numerator are positive, and factors in the denominator are
37
+ # negative.
38
+ def factors
39
+ n_factors = ::Prime.prime_division(@numerator)
40
+ d_factors = ::Prime.prime_division(@denominator).map {|d| d[1] *= -1; d}
41
+ n_factors.concat(d_factors).sort_by {|x| x[0]}
42
+ end
43
+
44
+ def abs
45
+ if self < MM::Ratio.new(0, 1)
46
+ self * MM::Ratio.new(-1,1)
47
+ else
48
+ self
49
+ end
50
+ end
51
+
52
+ def <=> other
53
+ # Ensure that the comparison makes sense
54
+ return nil unless other.respond_to? :-
55
+
56
+ case
57
+ when (self - other).to_f > 0
58
+ return 1
59
+ when (self - other).to_f < 0
60
+ return -1
61
+ end
62
+ return 0
63
+ end
64
+
65
+ def eql? other
66
+ other.is_a?(MM::Ratio) && (self == other)
67
+ end
68
+
69
+ def hash
70
+ [@numerator, @denominator, MM::Ratio].hash
71
+ end
72
+
73
+ def cents
74
+ Math.log2(self.to_f) * 1200.0
75
+ end
76
+
77
+ def self.from_s r
78
+ if r.respond_to? :split
79
+ if r =~ /\s/
80
+ r.split(/\s/).inject([]) {|memo, ratio|
81
+ memo << self.from_s(ratio)
82
+ }
83
+ else
84
+ string_to_ratio r
85
+ end
86
+ else
87
+ r.map {|s| self.from_s s}
88
+ end
89
+ end
90
+
91
+ def self.string_to_ratio string
92
+ m = string.match(/(\d+)\/(\d+)/)
93
+ MM::Ratio.new(m[1].to_i, m[2].to_i)
94
+ end
95
+
96
+ # Loads a sequence of MM::Ratios from a YAML file.
97
+ def self.from_yaml yaml_string
98
+ YAML.load(yaml_string).map {|r| MM::Ratio.from_s r}
99
+ end
100
+
101
+ def to_f
102
+ @numerator.to_f / @denominator
103
+ end
104
+
105
+ def to_s
106
+ "#{@numerator}/#{@denominator}"
107
+ end
108
+
109
+ def reciprocal
110
+ MM::Ratio.new(@denominator, @numerator)
111
+ end
112
+
113
+ def prime_limit
114
+ self.map { |r|
115
+ r.prime_division.map { |s|
116
+ s.first
117
+ }.max
118
+ }.compact.max
119
+ end
120
+
121
+ def each
122
+ if block_given?
123
+ [@numerator, @denominator].each do |r|
124
+ yield r
125
+ end
126
+ else
127
+ [@numerator, @denominator].each
128
+ end
129
+ end
130
+
131
+ def self.to_vector point
132
+ point.each_cons(2).map {|r| r[0] / r[1]}
133
+ end
134
+
135
+ def self.from_vector vector
136
+ vector.inject([MM::Ratio.new(1,1)]) {|m, r| m << (m.last / r)}
137
+ end
138
+
139
+ def self.change_interval point, index, interval
140
+ vector = MM::Ratio.to_vector(point)
141
+ if interval == :reciprocal
142
+ interval = vector[index].reciprocal
143
+ end
144
+ vector[index] = interval
145
+