auom 0.0.6 → 0.1.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/.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
|