auom 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.circle.yml +4 -0
- data/.rubocop.yml +7 -0
- data/.travis.yml +2 -9
- data/Changelog.md +4 -0
- data/Gemfile +2 -2
- data/Gemfile.devtools +39 -32
- data/auom.gemspec +3 -4
- data/circle.yml +4 -0
- data/config/flay.yml +1 -1
- data/config/{site.reek → reek.yml} +12 -11
- data/config/rubocop.yml +89 -0
- data/lib/auom.rb +7 -325
- data/lib/auom/algebra.rb +13 -10
- data/lib/auom/equalization.rb +6 -2
- data/lib/auom/inspection.rb +22 -57
- data/lib/auom/relational.rb +4 -2
- data/lib/auom/unit.rb +324 -0
- data/spec/shared/incompatible_operation_behavior.rb +3 -1
- data/spec/shared/operation_behavior.rb +3 -2
- data/spec/shared/sunits_shared.rb +3 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/auom/algebra/add_spec.rb +8 -6
- data/spec/unit/auom/algebra/divide_spec.rb +26 -15
- data/spec/unit/auom/algebra/multiply_spec.rb +25 -15
- data/spec/unit/auom/algebra/substract_spec.rb +8 -6
- data/spec/unit/auom/equalization/equality_operator_spec.rb +5 -3
- data/spec/unit/auom/inspection/class_methods/prettify_unit_part_spec.rb +6 -4
- data/spec/unit/auom/inspection/inspect_spec.rb +15 -13
- data/spec/unit/auom/relational/greater_than_or_equal_to_predicate_spec.rb +3 -1
- data/spec/unit/auom/relational/greater_than_predicate_spec.rb +3 -1
- data/spec/unit/auom/relational/less_than_or_equal_to_predicate_spec.rb +3 -1
- data/spec/unit/auom/relational/less_than_predicate_spec.rb +3 -1
- data/spec/unit/auom/unit/assert_same_unit_spec.rb +5 -3
- data/spec/unit/auom/unit/class_methods/convert_spec.rb +8 -6
- data/spec/unit/auom/unit/class_methods/lookup_spec.rb +6 -4
- data/spec/unit/auom/unit/class_methods/new_spec.rb +22 -21
- data/spec/unit/auom/unit/class_methods/try_convert_spec.rb +6 -4
- data/spec/unit/auom/unit/class_methods/units_spec.rb +3 -1
- data/spec/unit/auom/unit/denominators_spec.rb +5 -3
- data/spec/unit/auom/unit/numerators_spec.rb +5 -3
- data/spec/unit/auom/unit/same_unit_predicate_spec.rb +5 -3
- data/spec/unit/auom/unit/scalar_spec.rb +4 -2
- data/spec/unit/auom/unit/unit +0 -0
- data/spec/unit/auom/unit/unit_spec.rb +5 -3
- data/spec/unit/auom/unit/unitless_predicate_spec.rb +9 -1
- metadata +13 -28
data/lib/auom/algebra.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
module AUOM
|
2
4
|
# The AUOM algebra
|
3
5
|
module Algebra
|
@@ -8,7 +10,7 @@ module AUOM
|
|
8
10
|
# @return [Unit]
|
9
11
|
#
|
10
12
|
# @example
|
11
|
-
#
|
13
|
+
#
|
12
14
|
# # unitless
|
13
15
|
# Unit.new(1) + Unit.new(2) # => <Unit @scalar=3>
|
14
16
|
#
|
@@ -24,7 +26,7 @@ module AUOM
|
|
24
26
|
klass = self.class
|
25
27
|
operand = klass.convert(operand)
|
26
28
|
assert_same_unit(operand)
|
27
|
-
klass.new(operand.scalar + scalar, numerators
|
29
|
+
klass.new(operand.scalar + scalar, numerators, denominators)
|
28
30
|
end
|
29
31
|
|
30
32
|
alias_method :+, :add
|
@@ -36,7 +38,7 @@ module AUOM
|
|
36
38
|
# @return [Unit]
|
37
39
|
#
|
38
40
|
# @example
|
39
|
-
#
|
41
|
+
#
|
40
42
|
# # unitless
|
41
43
|
# Unit.new(2) - Unit.new(1) # => <Unit @scalar=1>
|
42
44
|
#
|
@@ -49,7 +51,7 @@ module AUOM
|
|
49
51
|
# @api public
|
50
52
|
#
|
51
53
|
def substract(operand)
|
52
|
-
add(
|
54
|
+
add(operand * -1)
|
53
55
|
end
|
54
56
|
|
55
57
|
alias_method :-, :substract
|
@@ -61,7 +63,7 @@ module AUOM
|
|
61
63
|
# @return [Unit]
|
62
64
|
#
|
63
65
|
# @example
|
64
|
-
#
|
66
|
+
#
|
65
67
|
# # unitless
|
66
68
|
# Unit.new(2) * Unit.new(1) # => <Unit @scalar=2>
|
67
69
|
#
|
@@ -93,7 +95,7 @@ module AUOM
|
|
93
95
|
# @return [Unit]
|
94
96
|
#
|
95
97
|
# @example
|
96
|
-
#
|
98
|
+
#
|
97
99
|
# # unitless
|
98
100
|
# Unit.new(2) / Unit.new(1) # => <Unit @scalar=2>
|
99
101
|
#
|
@@ -111,11 +113,12 @@ module AUOM
|
|
111
113
|
|
112
114
|
self * klass.new(
|
113
115
|
1 / operand.scalar,
|
114
|
-
operand.denominators
|
115
|
-
operand.numerators
|
116
|
+
operand.denominators,
|
117
|
+
operand.numerators
|
116
118
|
)
|
117
119
|
end
|
118
120
|
|
119
121
|
alias_method :/, :divide
|
120
|
-
|
121
|
-
end
|
122
|
+
|
123
|
+
end # Algebra
|
124
|
+
end # AUOM
|
data/lib/auom/equalization.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
module AUOM
|
2
4
|
# Equalization for auom units
|
3
5
|
module Equalization
|
6
|
+
|
4
7
|
# Check for equivalent value and try to convert
|
5
8
|
#
|
6
9
|
# @param [Object] other
|
@@ -28,5 +31,6 @@ module AUOM
|
|
28
31
|
def ==(other)
|
29
32
|
eql?(self.class.try_convert(other))
|
30
33
|
end
|
31
|
-
|
32
|
-
end
|
34
|
+
|
35
|
+
end # Equalization
|
36
|
+
end # AUOM
|
data/lib/auom/inspection.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
module AUOM
|
2
4
|
# Inspection module for auom units
|
3
5
|
module Inspection
|
@@ -8,7 +10,7 @@ module AUOM
|
|
8
10
|
# @api private
|
9
11
|
#
|
10
12
|
def inspect
|
11
|
-
sprintf('<%s @scalar=%s%s>', self.class
|
13
|
+
sprintf('<%s @scalar=%s%s>', self.class, pretty_scalar, pretty_unit)
|
12
14
|
end
|
13
15
|
|
14
16
|
private
|
@@ -20,43 +22,26 @@ module AUOM
|
|
20
22
|
# @api private
|
21
23
|
#
|
22
24
|
def pretty_scalar
|
23
|
-
|
24
|
-
|
25
|
+
if reminder?
|
26
|
+
sprintf('~%0.4f', scalar)
|
25
27
|
else
|
26
|
-
|
28
|
+
scalar.to_i
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
|
-
# Return float value
|
31
|
-
#
|
32
|
-
# @return [Float]
|
33
|
-
#
|
34
|
-
# @api private
|
35
|
-
#
|
36
|
-
def float_value
|
37
|
-
scalar.to_f
|
38
|
-
end
|
39
|
-
|
40
|
-
# Return integer value
|
41
|
-
#
|
42
|
-
# @return [Fixnum]
|
43
|
-
#
|
44
|
-
# @api private
|
45
|
-
#
|
46
|
-
def integer_value
|
47
|
-
scalar.to_i
|
48
|
-
end
|
49
|
-
|
50
32
|
# Return prettyfied unit part
|
51
33
|
#
|
52
34
|
# @return [String]
|
35
|
+
# if there is a prettifiable unit part
|
36
|
+
#
|
37
|
+
# @return [nil]
|
38
|
+
# otherwise
|
53
39
|
#
|
54
40
|
# @api private
|
55
41
|
#
|
56
42
|
def pretty_unit
|
57
|
-
return
|
43
|
+
return if unitless?
|
58
44
|
|
59
|
-
klass = self.class
|
60
45
|
numerator = Inspection.prettify_unit_part(@numerators)
|
61
46
|
denominator = Inspection.prettify_unit_part(@denominators)
|
62
47
|
|
@@ -65,42 +50,21 @@ module AUOM
|
|
65
50
|
return " #{numerator}"
|
66
51
|
end
|
67
52
|
|
68
|
-
|
69
53
|
sprintf(' %s/%s', numerator, denominator)
|
70
54
|
end
|
71
55
|
|
72
|
-
#
|
56
|
+
# Test if scalar has and reminder in decimal representation
|
73
57
|
#
|
74
58
|
# @return [true]
|
75
|
-
# if
|
59
|
+
# if there is a reminder
|
76
60
|
#
|
77
61
|
# @return [false]
|
78
|
-
#
|
79
|
-
#
|
80
|
-
# @api private
|
81
|
-
#
|
82
|
-
def fractions?
|
83
|
-
!(scalar_numerator % scalar_denominator).zero?
|
84
|
-
end
|
85
|
-
|
86
|
-
# Return scalar numerator
|
87
|
-
#
|
88
|
-
# @return [Fixnum]
|
62
|
+
# otherwise
|
89
63
|
#
|
90
64
|
# @api private
|
91
65
|
#
|
92
|
-
def
|
93
|
-
scalar.
|
94
|
-
end
|
95
|
-
|
96
|
-
# Return scalar denominator
|
97
|
-
#
|
98
|
-
# @return [Fixnum]
|
99
|
-
#
|
100
|
-
# @api private
|
101
|
-
#
|
102
|
-
def scalar_denominator
|
103
|
-
scalar.denominator
|
66
|
+
def reminder?
|
67
|
+
!(scalar % scalar.denominator).zero?
|
104
68
|
end
|
105
69
|
|
106
70
|
# Return prettified units
|
@@ -112,7 +76,7 @@ module AUOM
|
|
112
76
|
# @api private
|
113
77
|
#
|
114
78
|
def self.prettify_unit_part(base)
|
115
|
-
counts(base).map { |unit,length| length > 1 ? "#{unit}^#{length}" : unit }.join('*')
|
79
|
+
counts(base).map { |unit, length| length > 1 ? "#{unit}^#{length}" : unit }.join('*')
|
116
80
|
end
|
117
81
|
|
118
82
|
# Return unit counts
|
@@ -124,8 +88,8 @@ module AUOM
|
|
124
88
|
# @api private
|
125
89
|
#
|
126
90
|
def self.counts(base)
|
127
|
-
counts = base.each_with_object(Hash.new(0)) { |unit,hash| hash[unit] += 1 }
|
128
|
-
counts.sort do |left,right|
|
91
|
+
counts = base.each_with_object(Hash.new(0)) { |unit, hash| hash[unit] += 1 }
|
92
|
+
counts.sort do |left, right|
|
129
93
|
result = right.last <=> left.last
|
130
94
|
if result == 0
|
131
95
|
left.first <=> right.first
|
@@ -136,5 +100,6 @@ module AUOM
|
|
136
100
|
end
|
137
101
|
|
138
102
|
private_class_method :counts
|
139
|
-
|
140
|
-
end
|
103
|
+
|
104
|
+
end # Inspection
|
105
|
+
end # AUOM
|
data/lib/auom/relational.rb
CHANGED
data/lib/auom/unit.rb
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module AUOM
|
4
|
+
# A scalar with units
|
5
|
+
class Unit
|
6
|
+
include Equalizer.new(:scalar, :numerators, :denominators)
|
7
|
+
include Algebra
|
8
|
+
include Equalization
|
9
|
+
include Inspection
|
10
|
+
include Relational
|
11
|
+
|
12
|
+
# Return scalar
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
#
|
16
|
+
# include AUOM
|
17
|
+
# m = Unit.new(1, :meter)
|
18
|
+
# m.scalar # => Rational(1, 1)
|
19
|
+
#
|
20
|
+
# @return [Rational]
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
#
|
24
|
+
attr_reader :scalar
|
25
|
+
|
26
|
+
# Return numerators
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
#
|
30
|
+
# include AUOM
|
31
|
+
# m = Unit.new(1, :meter)
|
32
|
+
# m.numerators # => [:meter]
|
33
|
+
#
|
34
|
+
# @return [Rational]
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
#
|
38
|
+
attr_reader :numerators
|
39
|
+
|
40
|
+
# Return denominators
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
#
|
44
|
+
# include AUOM
|
45
|
+
# m = Unit.new(1, :meter)
|
46
|
+
# m.denoninators # => []
|
47
|
+
#
|
48
|
+
# @return [Rational]
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
#
|
52
|
+
attr_reader :denominators
|
53
|
+
|
54
|
+
# Return unit descriptor
|
55
|
+
#
|
56
|
+
# @return [Array]
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
#
|
62
|
+
# u = Unit.new(1, [:meter, :meter], :euro)
|
63
|
+
# u.unit # => [[:meter, :meter], [:euro]]
|
64
|
+
#
|
65
|
+
attr_reader :unit
|
66
|
+
|
67
|
+
# These constants can easily be changed
|
68
|
+
# by an application specific subclass that overrides
|
69
|
+
# AUOM::Unit.units with an own hash!
|
70
|
+
UNITS = {
|
71
|
+
item: [1, :item],
|
72
|
+
liter: [1, :liter],
|
73
|
+
pack: [1, :pack],
|
74
|
+
can: [1, :can],
|
75
|
+
kilogramm: [1, :kilogramm],
|
76
|
+
euro: [1, :euro],
|
77
|
+
meter: [1, :meter],
|
78
|
+
kilometer: [1000, :meter]
|
79
|
+
}.freeze
|
80
|
+
|
81
|
+
# Return buildin units symbols
|
82
|
+
#
|
83
|
+
# @return [Hash]
|
84
|
+
#
|
85
|
+
# @api private
|
86
|
+
#
|
87
|
+
def self.units
|
88
|
+
UNITS
|
89
|
+
end
|
90
|
+
|
91
|
+
# Check for unitless unit
|
92
|
+
#
|
93
|
+
# @return [true]
|
94
|
+
# return true if unit is unitless
|
95
|
+
#
|
96
|
+
# @return [false]
|
97
|
+
# return false if unit is NOT unitless
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
#
|
101
|
+
# Unit.new(1).unitless? # => true
|
102
|
+
# Unit.new(1, :meter).unitless ? # => false
|
103
|
+
#
|
104
|
+
# @api public
|
105
|
+
#
|
106
|
+
def unitless?
|
107
|
+
numerators.empty? and denominators.empty?
|
108
|
+
end
|
109
|
+
|
110
|
+
# Test if units are the same
|
111
|
+
#
|
112
|
+
# @param [Unit] other
|
113
|
+
#
|
114
|
+
# @return [true]
|
115
|
+
# if units are the same
|
116
|
+
#
|
117
|
+
# @return [false]
|
118
|
+
# otehrwise
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
#
|
122
|
+
# a = Unit.new(1)
|
123
|
+
# b = Unit.new(1, :euro)
|
124
|
+
# c = Unit.new(2, :euro)
|
125
|
+
#
|
126
|
+
# a.same_unit?(b) # => false
|
127
|
+
# b.same_unit?(c) # => true
|
128
|
+
#
|
129
|
+
# @api public
|
130
|
+
#
|
131
|
+
def same_unit?(other)
|
132
|
+
other.unit.eql?(unit)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Instancitate a new unit
|
136
|
+
#
|
137
|
+
# @param [Rational] scalar
|
138
|
+
# @param [Enumerable] numerators
|
139
|
+
# @param [Enumerable] denominators
|
140
|
+
#
|
141
|
+
# @return [Unit]
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
#
|
145
|
+
# # A unitless unit
|
146
|
+
# u = Unit.new(1)
|
147
|
+
# u.unitless? # => true
|
148
|
+
# u.scalar # => Rational(1, 1)
|
149
|
+
#
|
150
|
+
# # A unitless unit from string
|
151
|
+
# u = Unit.new('1.5')
|
152
|
+
# u.unitless? # => true
|
153
|
+
# u.scalar # => Rational(3, 2)
|
154
|
+
#
|
155
|
+
# # A simple unit
|
156
|
+
# u = Unit.new(1, :meter)
|
157
|
+
# u.unitless? # => false
|
158
|
+
# u.numerators # => [:meter]
|
159
|
+
# u.scalar # => Rational(1, 1)
|
160
|
+
#
|
161
|
+
# # A complex unit
|
162
|
+
# u = Unit.new(Rational(1, 3), :euro, :meter)
|
163
|
+
# u.fractions? # => true
|
164
|
+
# u.sclar # => Rational(1, 3)
|
165
|
+
# u.inspect # => <AUOM::Unit @scalar=~0.3333 euro/meter>
|
166
|
+
# u.unit # => [[:euro], [:meter]]
|
167
|
+
#
|
168
|
+
# @api public
|
169
|
+
#
|
170
|
+
# TODO: Move defaults coercions etc to .build method
|
171
|
+
#
|
172
|
+
def self.new(scalar, numerators = nil, denominators = nil)
|
173
|
+
scalar = rational(scalar)
|
174
|
+
|
175
|
+
scalar, numerators = resolve([*numerators], scalar, :*)
|
176
|
+
scalar, denominators = resolve([*denominators], scalar, :/)
|
177
|
+
|
178
|
+
# sorting on #to_s as Symbol#<=> is not present on 1.8.7
|
179
|
+
super(scalar, *[numerators, denominators].map { |base| base.sort_by(&:to_s) }).freeze
|
180
|
+
end
|
181
|
+
|
182
|
+
# Assert units are the same
|
183
|
+
#
|
184
|
+
# @param [Unit] other
|
185
|
+
#
|
186
|
+
# @return [self]
|
187
|
+
#
|
188
|
+
# @api private
|
189
|
+
#
|
190
|
+
def assert_same_unit(other)
|
191
|
+
unless same_unit?(other)
|
192
|
+
raise ArgumentError, 'Incompatible units'
|
193
|
+
end
|
194
|
+
|
195
|
+
self
|
196
|
+
end
|
197
|
+
|
198
|
+
# Return converted operand or raise error
|
199
|
+
#
|
200
|
+
# @param [Object] operand
|
201
|
+
#
|
202
|
+
# @return [Unit]
|
203
|
+
#
|
204
|
+
# @raise [ArgumentError]
|
205
|
+
# raises argument error in case operand cannot be converted
|
206
|
+
#
|
207
|
+
# @api private
|
208
|
+
#
|
209
|
+
def self.convert(operand)
|
210
|
+
converted = try_convert(operand)
|
211
|
+
unless converted
|
212
|
+
raise ArgumentError, "Cannot convert #{operand.inspect} to #{self}"
|
213
|
+
end
|
214
|
+
converted
|
215
|
+
end
|
216
|
+
|
217
|
+
# Return converted operand or nil
|
218
|
+
#
|
219
|
+
# @param [Object] operand
|
220
|
+
#
|
221
|
+
# @return [Unit]
|
222
|
+
# return unit in case operand can be converted
|
223
|
+
#
|
224
|
+
# @return [nil]
|
225
|
+
# return nil in case operand can NOT be converted
|
226
|
+
#
|
227
|
+
# @api private
|
228
|
+
#
|
229
|
+
def self.try_convert(operand)
|
230
|
+
case operand
|
231
|
+
when self
|
232
|
+
operand
|
233
|
+
when Fixnum, Rational
|
234
|
+
new(operand)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
# Initialize unit
|
241
|
+
#
|
242
|
+
# @param [Rational] scalar
|
243
|
+
# @param [Enumerable] numerators
|
244
|
+
# @param [Enumerable] denominators
|
245
|
+
#
|
246
|
+
# @api private
|
247
|
+
#
|
248
|
+
def initialize(scalar, numerators, denominators)
|
249
|
+
@scalar = scalar
|
250
|
+
|
251
|
+
[numerators, denominators].permutation do |left, right|
|
252
|
+
left.delete_if { |item| right.delete_at(right.index(item) || right.length) }
|
253
|
+
end
|
254
|
+
|
255
|
+
@numerators = numerators.freeze
|
256
|
+
@denominators = denominators.freeze
|
257
|
+
|
258
|
+
@unit = [@numerators, @denominators].freeze
|
259
|
+
@scalar.freeze
|
260
|
+
end
|
261
|
+
|
262
|
+
# Return rational converted from value
|
263
|
+
#
|
264
|
+
# @param [Object] value
|
265
|
+
#
|
266
|
+
# @return [Rationa]
|
267
|
+
#
|
268
|
+
# @raise [ArgumentError]
|
269
|
+
# raises argument error when cannot be converted to a rational
|
270
|
+
#
|
271
|
+
# @api private
|
272
|
+
#
|
273
|
+
def self.rational(value)
|
274
|
+
case value
|
275
|
+
when Rational
|
276
|
+
value
|
277
|
+
when Fixnum
|
278
|
+
Rational(value)
|
279
|
+
else
|
280
|
+
raise ArgumentError, "#{value.inspect} cannot be converted to rational"
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
private_class_method :rational
|
285
|
+
|
286
|
+
# Resolve unit component
|
287
|
+
#
|
288
|
+
# @param [Enumerable] components
|
289
|
+
# @param [Symbol] operation
|
290
|
+
#
|
291
|
+
# @return [Array]
|
292
|
+
#
|
293
|
+
# @api private
|
294
|
+
#
|
295
|
+
def self.resolve(components, scalar, operation)
|
296
|
+
resolved = components.map do |component|
|
297
|
+
scale, component = lookup(component)
|
298
|
+
scalar = scalar.public_send(operation, scale)
|
299
|
+
component
|
300
|
+
end
|
301
|
+
[scalar, resolved]
|
302
|
+
end
|
303
|
+
|
304
|
+
private_class_method :resolve
|
305
|
+
|
306
|
+
# Return unit information
|
307
|
+
#
|
308
|
+
# @param [Symbol] value
|
309
|
+
# the unit to search for
|
310
|
+
#
|
311
|
+
# @return [Array]
|
312
|
+
#
|
313
|
+
# @api private
|
314
|
+
#
|
315
|
+
def self.lookup(value)
|
316
|
+
units.fetch(value) do
|
317
|
+
raise ArgumentError, "Unknown unit #{value.inspect}"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
private_class_method :lookup
|
322
|
+
|
323
|
+
end # Unit
|
324
|
+
end # AUOM
|