dimensional 0.0.6 → 0.1.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.
@@ -29,10 +29,11 @@ module Dimensional
29
29
  @abbreviation_registry.clear
30
30
  end
31
31
 
32
- attr_reader :name, :abbreviation
32
+ attr_reader :abbreviation, :description
33
33
 
34
- def initialize(name, abbreviation = nil)
34
+ def initialize(name, abbreviation = nil, description = nil)
35
35
  @abbreviation = abbreviation && abbreviation.to_s
36
+ @description = description || name
36
37
  super(name)
37
38
  end
38
39
  end
@@ -1,20 +1,26 @@
1
1
  require 'dimensional/dimension'
2
2
  require 'dimensional/system'
3
3
  require 'set'
4
+ require 'rational'
4
5
 
5
6
  module Dimensional
6
7
  # A standard scale unit for measuring physical quantities. In addition to the Dimension and System attribute
7
8
  # that are well-defined by classes above, the user-defined metric attribute is available to identify units as
8
9
  # belonging to an arbitrary metric like length, draft or property size. Effective use of the metric attribute
9
- # can simplify presentation of Measures and make parsing of user input more accurate.
10
+ # can simplify presentation of Measures and make parsing of user input more accurate.
10
11
  # Reference: http://en.wikipedia.org/wiki/Units_of_measurement
11
12
  class Unit
13
+ extend Enumerable
14
+
12
15
  @store = Set.new
13
16
 
17
+ def self.each(&block)
18
+ @store.each(&block)
19
+ end
20
+
14
21
  def self.register(*args)
15
22
  u = new(*args)
16
- raise "Namespace collision: #{u.dimension}:#{u.system}:#{u.name}" if self[u.dimension, u.system, u.name.to_sym]
17
- raise "Namespace collision: #{u.dimension}:#{u.system}:#{u.abbreviation}" if self[u.dimension, u.system, u.abbreviation.to_sym] if u.abbreviation
23
+ raise "Namespace collision: #{u.inspect}" if @store.include?(u)
18
24
  @store << u
19
25
  u
20
26
  end
@@ -25,7 +31,8 @@ module Dimensional
25
31
  sys = System[sys] unless sys.kind_of?(System)
26
32
  sym = sym.to_sym
27
33
  us = @store.select{|u| u.dimension == dim}.select{|u| u.system == sys}
28
- us.detect{|u| sym == u.name.to_sym || (u.abbreviation && sym == u.abbreviation.to_sym)}
34
+ u = us.detect{|u| sym == u.name.to_sym || (u.abbreviation && sym == u.abbreviation.to_sym)}
35
+ u || (raise ArgumentError, "Can't find unit: #{dim}, #{sys}, #{sym}")
29
36
  end
30
37
 
31
38
  def self.reset!
@@ -34,49 +41,80 @@ module Dimensional
34
41
 
35
42
  attr_reader :name, :abbreviation
36
43
  attr_reader :system, :dimension
37
- attr_reader :reference_factor, :reference_unit
44
+ attr_reader :reference_factor, :reference_units
45
+ attr_reader :detector, :format, :preference
38
46
 
39
47
  def initialize(name, system, dimension, options = {})
40
48
  @name = name.to_s
41
49
  @system = system
42
50
  @dimension = dimension
43
- @reference_factor = options[:reference_factor]
44
- @reference_unit = options[:reference_unit]
51
+ @reference_factor = options[:reference_factor] || 1
52
+ @reference_units = options[:reference_units] || {}
45
53
  @abbreviation = options[:abbreviation]
54
+ @detector = options[:detector] || /\A#{[name, abbreviation].compact.join('|')}\Z/
55
+ @format = options[:format] || dimension.nil? ? "%s %U" : "%s%U"
56
+ @preference = options[:preference] || 0
57
+ validate
58
+ end
59
+
60
+ def validate
61
+ return "Reference factor must be numeric: #{@reference_factor}." unless factor.kind_of?(Numeric)
62
+ return "Reference units must all be units: #{@reference_units}." unless reference_units.all?{|u, exp| u.kind_of?(Dimensional::Unit)}
63
+ return "Reference exponents must all be rationals: #{@reference_units}." unless reference_units.all?{|u, exp| exp.kind_of?(Rational)}
64
+ return "Preference must be numeric: #{@preference}." unless preference.kind_of?(Numeric)
46
65
  end
47
66
 
48
67
  # If no reference was provided during initialization, this unit must itself be a base unit.
49
68
  def base?
50
- !reference_unit
69
+ reference_units.empty?
51
70
  end
52
71
 
53
72
  # Returns the unit or array of units on which this unit's scale is ultimately based.
73
+ # The technique used is to multiply the bases' exponents by our exponent and then consolidate
74
+ # resulting common bases by adding their exponents.
54
75
  def base
55
- return self if base?
56
- @base ||= reference_unit.kind_of?(Enumerable) ? reference_unit.map{|ru| ru.base} : reference_unit.base
76
+ return {self => 1} if base?
77
+ @base ||= reference_units.inject({}) do |summary0, (ru0, exp0)|
78
+ t = ru0.base.inject({}){|summary1, (ru1, exp1)| summary1[ru1] = exp1 * exp0;summary1}
79
+ summary0.merge(t) {|ru, expa, expb| expa + expb}
80
+ end
57
81
  end
58
-
82
+
59
83
  # The conversion factor relative to the base unit.
60
84
  def factor
61
- return 1 if base?
62
- @factor ||= reference_factor * (reference_unit.kind_of?(Enumerable) ? reference_unit.inject(1){|f, ru| f * ru.factor} : reference_unit.factor)
85
+ @factor ||= reference_factor * reference_units.inject(1){|f, (ru, exp)| f * (ru.factor**exp)}
63
86
  end
64
-
87
+
65
88
  # Returns the conversion factor to convert to the other unit
66
89
  def convert(other)
67
90
  raise "Units #{self} and #{other} are not commensurable" unless commensurable?(other)
68
91
  return 1 if self == other
69
92
  self.factor / other.factor
70
93
  end
71
-
94
+
72
95
  def commensurable?(other)
73
96
  dimension == other.dimension
74
97
  end
75
-
98
+
99
+ # Equality is determined by equality of value-ish attributes. Specifically, equal factors relative to the same base.
100
+ def ==(other)
101
+ (other.base == self.base) && other.factor == self.factor
102
+ end
103
+
104
+ # Hashing collisions are desired when we have same identity-defining attributes.
105
+ def eql?(other)
106
+ other.kind_of?(self.class) && other.dimension.eql?(self.dimension) && other.system.eql?(self.system) && other.name.eql?(self.name)
107
+ end
108
+
109
+ # This is pretty lame, but the expected usage means we shouldn't get penalized
110
+ def hash
111
+ [self.class, dimension, system, name].hash
112
+ end
113
+
76
114
  def to_s
77
115
  name rescue super
78
116
  end
79
-
117
+
80
118
  def inspect
81
119
  "#<#{self.class.inspect}: #{dimension.to_s}:#{system.to_s}:#{to_s}>"
82
120
  end
@@ -1,3 +1,3 @@
1
1
  module Dimensional
2
- VERSION = "0.0.6"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -0,0 +1,217 @@
1
+ require 'test/unit'
2
+ require 'dimensional/configuration'
3
+
4
+ class ConfigurationTest < Test::Unit::TestCase
5
+ include Dimensional
6
+
7
+ def setup
8
+ @s0 = System.new('International System', 'SI', "International System (kg, m)")
9
+ @s1 = System.new('United States Customary', 'US', "US Customary (ft, lbs)")
10
+ @dL = Dimension.new('Length')
11
+ @dM = Dimension.new('Mass')
12
+ @um = Unit.new('meter', @s0, @dL)
13
+ @ug = Unit.new('gram', @s0, @dM)
14
+ # System.register('British Admiralty', 'BA')
15
+ # Dimension.register('Area', 'A', {Dimension::L => 2})
16
+ end
17
+
18
+ def test_create_configuration
19
+ assert_instance_of Configuration, c = Configuration.new
20
+ assert c.dimensions.empty?
21
+ assert c.systems.empty?
22
+ assert c.empty?
23
+ end
24
+
25
+ def test_add_dimension
26
+ c = Configuration.new
27
+ assert c.dimensions << @dL
28
+ assert_same @dL, c.dimensions['Length']
29
+ assert_same @dL, c.dimensions['L']
30
+ assert_same @dL, c.dimensions[:L]
31
+ end
32
+
33
+ def test_add_system
34
+ c = Configuration.new
35
+ assert c.systems << @s0
36
+ assert_same @s0, c.systems['SI']
37
+ assert_same @s0, c.systems['International System']
38
+ assert_same @s0, c.systems[:SI]
39
+ end
40
+
41
+ def test_prioritize_systems
42
+ c = Configuration.new
43
+ assert c.systems << @s0
44
+ assert c.systems << @s1
45
+ c.systems.prioritize([:US, :SI])
46
+ assert_equal @s1, c.systems.first
47
+ assert_equal [@s1, @s0], c.systems.to_a
48
+ c.systems.prioritize([:SI, :US])
49
+ assert_equal [@s0, @s1], c.systems.to_a
50
+ end
51
+
52
+ def test_add_unit
53
+ c = Configuration.new
54
+ assert c << @um
55
+ assert c.include?(@um)
56
+ assert c.dimensions.include?(@dL)
57
+ assert c.systems.include?(@s0)
58
+ assert_same @um, c[@dL, @s0, 'meter']
59
+ end
60
+
61
+ def test_scope
62
+ c = Configuration.new
63
+ c << @um
64
+ c << @ug
65
+ assert c.include?(@ug)
66
+ c1 = c.dimension(@dL)
67
+ assert c1.include?(@um)
68
+ assert_same @um, c1[@dL, @s0, 'meter']
69
+ assert !c1.include?(@ug)
70
+ c2 = c1.dimension(@dM)
71
+ assert c2.empty?
72
+ assert !c2.any?
73
+ assert c.include?(@ug)
74
+ end
75
+
76
+ # def test_start_configurator
77
+ # assert Configurator.start
78
+ # assert Configurator.start {true}
79
+ # assert !Configurator.start {false}
80
+ # end
81
+ #
82
+ # def test_start_configurator_with_context_args
83
+ # assert_same Dimension::L, Configurator.start(:dimension => Dimension::L){context.dimension}
84
+ # end
85
+ #
86
+ # def test_change_dimension_context_for_duration_of_block
87
+ # test_context = self
88
+ # Configurator.start do
89
+ # dimension(Dimension::L) do
90
+ # test_context.assert_equal Dimension::L, context.dimension
91
+ # true
92
+ # end
93
+ # test_context.assert_nil context.dimension
94
+ # end
95
+ # end
96
+ #
97
+ # def test_change_system_context_for_duration_of_block
98
+ # test_context = self
99
+ # Configurator.start do
100
+ # system(System::SI) do
101
+ # test_context.assert_equal System::SI, context.system
102
+ # true
103
+ # end
104
+ # test_context.assert_nil context.system
105
+ # end
106
+ # end
107
+ #
108
+ # def test_preserve_context_within_block
109
+ # test_context = self
110
+ # Dimensional::Configurator.start do
111
+ # dimension(:L) do
112
+ # system(:SI) do
113
+ # base('meter') do
114
+ # test_context.assert uc = context.unit
115
+ # derive('centimeter', 'cm', 1e-2)
116
+ # test_context.assert_same uc, context.unit
117
+ # end
118
+ # end
119
+ # end
120
+ # end
121
+ # end
122
+ #
123
+ # def test_build_base_unit
124
+ # Configurator.start(:system => System::SI, :dimension => Dimension::L) do
125
+ # base('meter', 'm', :detector => /\A(meters?|m)\Z/)
126
+ # end
127
+ # assert_instance_of Unit, u = Unit[Dimension::L, System::SI, 'meter']
128
+ # assert_same System::SI, u.system
129
+ # assert_same Dimension::L, u.dimension
130
+ # assert u.base?
131
+ # assert_equal 'm', u.abbreviation
132
+ # assert_instance_of Metric, m = Metric[:L]
133
+ # assert m.preferences(u)[:detector]
134
+ # end
135
+ #
136
+ # def test_build_derived_unit
137
+ # Configurator.start(:system => System::SI, :dimension => Dimension::L) do
138
+ # base('meter', 'm', :detector => /\A(meters?|m)\Z/) do
139
+ # derive('centimeter', 'cm', 1e-2, :detector => /\A(centimeters?|cm)\Z/)
140
+ # end
141
+ # end
142
+ # u0 = Unit[Dimension::L, System::SI, 'meter']
143
+ # assert_instance_of Unit, u = Unit[Dimension::L, System::SI, 'centimeter']
144
+ # assert_same System::SI, u.system
145
+ # assert_same Dimension::L, u.dimension
146
+ # assert_same u0, u.base
147
+ # assert_equal 1E-2, u.factor
148
+ # assert_equal 'cm', u.abbreviation
149
+ # end
150
+ #
151
+ # def test_build_aliased_unit
152
+ # Configurator.start(:system => System::SI, :dimension => Dimension::L) do
153
+ # base('meter', 'm', :detector => /\A(meters?|m)\Z/) do
154
+ # self.alias('decadecimeter')
155
+ # end
156
+ # end
157
+ # u0 = Unit[Dimension::L, System::SI, 'meter']
158
+ # assert_instance_of Unit, u = Unit[Dimension::L, System::SI, 'decadecimeter']
159
+ # assert_same System::SI, u.system
160
+ # assert_same Dimension::L, u.dimension
161
+ # assert_same u0, u.base
162
+ # assert_equal 1, u.factor
163
+ # end
164
+ #
165
+ # def test_build_referenced_unit
166
+ # Configurator.start(:system => System::SI, :dimension => Dimension::L) do
167
+ # base('meter', 'm', :detector => /\A(meters?|m)\Z/)
168
+ # system(:US) do
169
+ # reference('yard', 'yd', Unit[:L, :SI, 'meter'], 0.9144, :detector => /\A(yards?|yds?)\Z/)
170
+ # end
171
+ # end
172
+ # u0 = Unit[Dimension::L, System::SI, 'meter']
173
+ # assert_instance_of Unit, u = Unit[Dimension::L, System::US, 'yard']
174
+ # assert_equal 0.9144, u.factor
175
+ # assert_same u0, u.base
176
+ # end
177
+ #
178
+ # def test_build_combined_unit
179
+ # Configurator.start(:system => System::SI, :dimension => Dimension::L) do
180
+ # base('meter', 'm', :detector => /\A(meters?|m)\Z/)
181
+ # system(:US) do
182
+ # reference('yard', 'yd', Unit[:L, :SI, 'meter'], 0.9144, :detector => /\A(yards?|yds?)\Z/)
183
+ # dimension(:A) do
184
+ # combine('square yard', 'yd2', [Unit[:L, :US, 'yard'], Unit[:L, :US, 'yard']], :detector => /\A(yd|yard)2\Z/)
185
+ # end
186
+ # end
187
+ # end
188
+ # u1 = Unit[Dimension::L, System::US, 'yard']
189
+ # assert_instance_of Unit, u = Unit[:A, :US, 'square yard']
190
+ # assert_equal Dimension::A, u.dimension
191
+ # assert_equal 0.83612736, u.factor
192
+ # assert_equal [u1.base, u1.base], u.base
193
+ # end
194
+ #
195
+ # def test_add_default_preferences
196
+ # Configurator.start(:system => System::SI, :dimension => Dimension::L) do
197
+ # base('meter', 'm')
198
+ # end
199
+ # u = Unit[Dimension::L, System::SI, 'meter']
200
+ # m = Metric[:L]
201
+ # assert d = m.preferences(u)[:detector]
202
+ # assert_match d, 'meter'
203
+ # assert f = m.preferences(u)[:format]
204
+ # end
205
+ #
206
+ # def test_register_metric_options
207
+ # Configurator.start(:system => System::SI, :dimension => Dimension::L) do
208
+ # base('meter', 'm', :detector => /\A(meters?|m)\Z/) do
209
+ # prefer(:length_over_all, :precision => 0.01)
210
+ # end
211
+ # end
212
+ # u = Unit[:L, :SI, 'm']
213
+ # assert_instance_of Metric, m = Metric[:length_over_all]
214
+ # assert_same Metric[:L], m.parent
215
+ # assert_equal 0.01, m.preferences(u)[:precision]
216
+ # end
217
+ end
@@ -10,6 +10,8 @@ class ConfiguratorTest < Test::Unit::TestCase
10
10
  System.register('British Admiralty', 'BA')
11
11
  Dimension.register('Length')
12
12
  Dimension.register('Area', 'A', {Dimension::L => 2})
13
+ Dimension.register('Time', 'T')
14
+ Dimension.register('Acceleration', 'Accel', {Dimension::L => 1, Dimension::T => -2})
13
15
  Dimension.register('Mass')
14
16
  end
15
17
 
@@ -17,7 +19,6 @@ class ConfiguratorTest < Test::Unit::TestCase
17
19
  Dimension.reset!
18
20
  System.reset!
19
21
  Unit.reset!
20
- Metric.reset!
21
22
  end
22
23
 
23
24
  def test_create_configurator
@@ -47,7 +48,7 @@ class ConfiguratorTest < Test::Unit::TestCase
47
48
  test_context.assert_nil context.dimension
48
49
  end
49
50
  end
50
-
51
+
51
52
  def test_change_system_context_for_duration_of_block
52
53
  test_context = self
53
54
  Configurator.start do
@@ -76,15 +77,14 @@ class ConfiguratorTest < Test::Unit::TestCase
76
77
 
77
78
  def test_build_base_unit
78
79
  Configurator.start(:system => System::SI, :dimension => Dimension::L) do
79
- base('meter', 'm', :detector => /\A(meters?|m)\Z/)
80
+ base('meter', 'm', :detector => /\A(met(er|re)s?|m)\Z/)
80
81
  end
81
82
  assert_instance_of Unit, u = Unit[Dimension::L, System::SI, 'meter']
82
83
  assert_same System::SI, u.system
83
84
  assert_same Dimension::L, u.dimension
84
85
  assert u.base?
85
86
  assert_equal 'm', u.abbreviation
86
- assert_instance_of Metric, m = Metric[:L]
87
- assert m.preferences(u)[:detector]
87
+ assert_equal /\A(met(er|re)s?|m)\Z/, u.detector
88
88
  end
89
89
 
90
90
  def test_build_derived_unit
@@ -97,7 +97,7 @@ class ConfiguratorTest < Test::Unit::TestCase
97
97
  assert_instance_of Unit, u = Unit[Dimension::L, System::SI, 'centimeter']
98
98
  assert_same System::SI, u.system
99
99
  assert_same Dimension::L, u.dimension
100
- assert_same u0, u.base
100
+ assert_equal({u0 => 1}, u.base)
101
101
  assert_equal 1E-2, u.factor
102
102
  assert_equal 'cm', u.abbreviation
103
103
  end
@@ -112,7 +112,7 @@ class ConfiguratorTest < Test::Unit::TestCase
112
112
  assert_instance_of Unit, u = Unit[Dimension::L, System::SI, 'decadecimeter']
113
113
  assert_same System::SI, u.system
114
114
  assert_same Dimension::L, u.dimension
115
- assert_same u0, u.base
115
+ assert_equal({u0 => 1}, u.base)
116
116
  assert_equal 1, u.factor
117
117
  end
118
118
 
@@ -126,7 +126,7 @@ class ConfiguratorTest < Test::Unit::TestCase
126
126
  u0 = Unit[Dimension::L, System::SI, 'meter']
127
127
  assert_instance_of Unit, u = Unit[Dimension::L, System::US, 'yard']
128
128
  assert_equal 0.9144, u.factor
129
- assert_same u0, u.base
129
+ assert_equal({u0 => 1}, u.base)
130
130
  end
131
131
 
132
132
  def test_build_combined_unit
@@ -135,37 +135,32 @@ class ConfiguratorTest < Test::Unit::TestCase
135
135
  system(:US) do
136
136
  reference('yard', 'yd', Unit[:L, :SI, 'meter'], 0.9144, :detector => /\A(yards?|yds?)\Z/)
137
137
  dimension(:A) do
138
- combine('square yard', 'yd2', [Unit[:L, :US, 'yard'], Unit[:L, :US, 'yard']], :detector => /\A(yd|yard)2\Z/)
138
+ combine('square yard', 'yd2', {Unit[:L, :US, 'yard'] => 2}, :detector => /\A(yd|yard)2\Z/)
139
139
  end
140
140
  end
141
141
  end
142
- u1 = Unit[Dimension::L, System::US, 'yard']
142
+ u0 = Unit[Dimension::L, System::SI, 'meter']
143
143
  assert_instance_of Unit, u = Unit[:A, :US, 'square yard']
144
144
  assert_equal Dimension::A, u.dimension
145
145
  assert_equal 0.83612736, u.factor
146
- assert_equal [u1.base, u1.base], u.base
147
- end
148
-
149
- def test_add_default_preferences
150
- Configurator.start(:system => System::SI, :dimension => Dimension::L) do
151
- base('meter', 'm')
152
- end
153
- u = Unit[Dimension::L, System::SI, 'meter']
154
- m = Metric[:L]
155
- assert d = m.preferences(u)[:detector]
156
- assert_match d, 'meter'
157
- assert f = m.preferences(u)[:format]
146
+ assert_equal({u0 => 2}, u.base)
158
147
  end
159
148
 
160
- def test_register_metric_options
161
- Configurator.start(:system => System::SI, :dimension => Dimension::L) do
162
- base('meter', 'm', :detector => /\A(meters?|m)\Z/) do
163
- prefer(:length_over_all, :precision => 0.01)
149
+ def test_build_combined_unit_with_reference_factor
150
+ Configurator.start(:system => System::SI) do
151
+ dimension(:L) do
152
+ base('meter', 'm')
153
+ end
154
+ dimension(:T) do
155
+ base('second', 's')
156
+ end
157
+ dimension(:Accel) do
158
+ combine('gravity', 'g', {Unit[:L, :SI, :m] => 1, Unit[:T, :SI, :s] => -2}, :reference_factor => 9.8)
164
159
  end
165
160
  end
166
- u = Unit[:L, :SI, 'm']
167
- assert_instance_of Metric, m = Metric[:length_over_all]
168
- assert_same Metric[:L], m.parent
169
- assert_equal 0.01, m.preferences(u)[:precision]
161
+ u = Unit[Dimension::Accel, System::SI, :g]
162
+ assert_equal Dimension::Accel, u.dimension
163
+ assert_equal 9.8, u.factor
164
+ assert_equal({Unit[:L, :SI, :m] => 1, Unit[:T, :SI, :s] => -2}, u.base)
170
165
  end
171
166
  end