auom 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.circle.yml +4 -0
  2. data/.rubocop.yml +7 -0
  3. data/.travis.yml +2 -9
  4. data/Changelog.md +4 -0
  5. data/Gemfile +2 -2
  6. data/Gemfile.devtools +39 -32
  7. data/auom.gemspec +3 -4
  8. data/circle.yml +4 -0
  9. data/config/flay.yml +1 -1
  10. data/config/{site.reek → reek.yml} +12 -11
  11. data/config/rubocop.yml +89 -0
  12. data/lib/auom.rb +7 -325
  13. data/lib/auom/algebra.rb +13 -10
  14. data/lib/auom/equalization.rb +6 -2
  15. data/lib/auom/inspection.rb +22 -57
  16. data/lib/auom/relational.rb +4 -2
  17. data/lib/auom/unit.rb +324 -0
  18. data/spec/shared/incompatible_operation_behavior.rb +3 -1
  19. data/spec/shared/operation_behavior.rb +3 -2
  20. data/spec/shared/sunits_shared.rb +3 -1
  21. data/spec/spec_helper.rb +2 -0
  22. data/spec/unit/auom/algebra/add_spec.rb +8 -6
  23. data/spec/unit/auom/algebra/divide_spec.rb +26 -15
  24. data/spec/unit/auom/algebra/multiply_spec.rb +25 -15
  25. data/spec/unit/auom/algebra/substract_spec.rb +8 -6
  26. data/spec/unit/auom/equalization/equality_operator_spec.rb +5 -3
  27. data/spec/unit/auom/inspection/class_methods/prettify_unit_part_spec.rb +6 -4
  28. data/spec/unit/auom/inspection/inspect_spec.rb +15 -13
  29. data/spec/unit/auom/relational/greater_than_or_equal_to_predicate_spec.rb +3 -1
  30. data/spec/unit/auom/relational/greater_than_predicate_spec.rb +3 -1
  31. data/spec/unit/auom/relational/less_than_or_equal_to_predicate_spec.rb +3 -1
  32. data/spec/unit/auom/relational/less_than_predicate_spec.rb +3 -1
  33. data/spec/unit/auom/unit/assert_same_unit_spec.rb +5 -3
  34. data/spec/unit/auom/unit/class_methods/convert_spec.rb +8 -6
  35. data/spec/unit/auom/unit/class_methods/lookup_spec.rb +6 -4
  36. data/spec/unit/auom/unit/class_methods/new_spec.rb +22 -21
  37. data/spec/unit/auom/unit/class_methods/try_convert_spec.rb +6 -4
  38. data/spec/unit/auom/unit/class_methods/units_spec.rb +3 -1
  39. data/spec/unit/auom/unit/denominators_spec.rb +5 -3
  40. data/spec/unit/auom/unit/numerators_spec.rb +5 -3
  41. data/spec/unit/auom/unit/same_unit_predicate_spec.rb +5 -3
  42. data/spec/unit/auom/unit/scalar_spec.rb +4 -2
  43. data/spec/unit/auom/unit/unit +0 -0
  44. data/spec/unit/auom/unit/unit_spec.rb +5 -3
  45. data/spec/unit/auom/unit/unitless_predicate_spec.rb +9 -1
  46. metadata +13 -28
@@ -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.dup, denominators.dup)
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(self.class.convert(operand) * -1)
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.dup,
115
- operand.numerators.dup
116
+ operand.denominators,
117
+ operand.numerators
116
118
  )
117
119
  end
118
120
 
119
121
  alias_method :/, :divide
120
- end
121
- end
122
+
123
+ end # Algebra
124
+ end # AUOM
@@ -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
- end
32
- end
34
+
35
+ end # Equalization
36
+ end # AUOM
@@ -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.name, pretty_scalar, pretty_unit)
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
- unless fractions?
24
- integer_value
25
+ if reminder?
26
+ sprintf('~%0.4f', scalar)
25
27
  else
26
- '~%0.4f' % float_value
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 '' if unitless?
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
- # Check if scalar has fractions in decimal representation
56
+ # Test if scalar has and reminder in decimal representation
73
57
  #
74
58
  # @return [true]
75
- # if scalar has fractions in decimal representation
59
+ # if there is a reminder
76
60
  #
77
61
  # @return [false]
78
- # if scalar NOT has fractions in decimal representation
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 scalar_numerator
93
- scalar.numerator
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
- end
140
- end
103
+
104
+ end # Inspection
105
+ end # AUOM
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  module AUOM
2
4
  # Mixin to add relational operators
3
5
  module Relational
@@ -86,5 +88,5 @@ module AUOM
86
88
  scalar.public_send(operation, other.scalar)
87
89
  end
88
90
 
89
- end
90
- end
91
+ end # Relational
92
+ end # AUOM
@@ -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