dimensional 0.0.2

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.
@@ -0,0 +1,90 @@
1
+ require 'dimensional/dimension'
2
+ require 'dimensional/system'
3
+ require 'set'
4
+ require 'enumerator'
5
+
6
+ module Dimensional
7
+ # A standard scale unit for measuring physical quantities. In addition to the Dimension and System attribute
8
+ # that are well-defined by classes above, the user-defined metric attribute is available to identify units as
9
+ # belonging to an arbitrary metric like length, draft or property size. Effective use of the metric attribute
10
+ # can simplify presentation of Measures and make parsing of user input more accurate.
11
+ # Reference: http://en.wikipedia.org/wiki/Units_of_measurement
12
+ class Unit
13
+ @store = Set.new
14
+
15
+ def self.register(*args)
16
+ u = new(*args)
17
+ raise "Namespace collision: #{u.dimension}:#{u.system}:#{u.name}" if self[u.dimension, u.system, u.name.to_sym]
18
+ raise "Namespace collision: #{u.dimension}:#{u.system}:#{u.abbreviation}" if self[u.dimension, u.system, u.abbreviation.to_sym]
19
+ @store << u
20
+ u
21
+ end
22
+
23
+ # Lookup the unit by name or abbreviation, scoped by dimension and system
24
+ def self.[](dim, sys, sym)
25
+ dim = Dimension[dim] unless dim.kind_of?(Dimension)
26
+ sys = System[sys] unless sys.kind_of?(System)
27
+ sym = sym.to_sym
28
+ @store.select{|u| u.dimension == dim}.select{|u| u.system == sys}.detect{|u| sym == u.abbreviation.to_sym || sym == u.name.to_sym}
29
+ end
30
+
31
+ def self.reset!
32
+ @store.clear
33
+ end
34
+
35
+ attr_reader :name, :abbreviation, :format
36
+ attr_reader :system, :dimension
37
+ attr_reader :reference_factor, :reference_unit
38
+
39
+ def initialize(name, system, dimension, options = {})
40
+ @name = name.to_s
41
+ @system = system
42
+ @dimension = dimension
43
+ @reference_factor = options[:reference_factor]
44
+ @reference_unit = options[:reference_unit]
45
+ @detector = options[:detector] || /\A#{self.name}\Z/
46
+ @abbreviation = (options[:abbreviation] || self.name).to_s
47
+ @format = options[:format] || dimension.nil? ? "%s %U" : "%s%U"
48
+ end
49
+
50
+ def match(s)
51
+ @detector.match(s)
52
+ end
53
+
54
+ # If no reference was provided during initialization, this unit must itself be a base unit.
55
+ def base?
56
+ !reference_unit
57
+ end
58
+
59
+ # Returns the unit or array of units on which this unit's scale is ultimately based.
60
+ def base
61
+ return self if base?
62
+ @base ||= reference_unit.kind_of?(Enumerable) ? reference_unit.map{|ru| ru.base} : reference_unit.base
63
+ end
64
+
65
+ # The conversion factor relative to the base unit.
66
+ def factor
67
+ return 1 if base?
68
+ @factor ||= reference_factor * (reference_unit.kind_of?(Enumerable) ? reference_unit.inject(1){|f, ru| f * ru.factor} : reference_unit.factor)
69
+ end
70
+
71
+ # Returns the conversion factor to convert to the other unit
72
+ def convert(other)
73
+ raise "Units #{self} and #{other} are not commensurable" unless commensurable?(other)
74
+ return 1 if self == other
75
+ self.factor / other.factor
76
+ end
77
+
78
+ def commensurable?(other)
79
+ dimension == other.dimension
80
+ end
81
+
82
+ def to_s
83
+ name rescue super
84
+ end
85
+
86
+ def inspect
87
+ "#<#{self.class.inspect}: #{dimension.to_s}:#{system.to_s}:#{to_s}>"
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,144 @@
1
+ require 'test/unit'
2
+ require 'dimensional/configurator'
3
+
4
+ class ConfiguratorTest < Test::Unit::TestCase
5
+ include Dimensional
6
+
7
+ def setup
8
+ System.register('International System', 'SI')
9
+ System.register('United States Customary', 'US')
10
+ System.register('British Admiralty', 'BA')
11
+ Dimension.register('Length')
12
+ Dimension.register('Area', 'A', {Dimension::L => 2})
13
+ Dimension.register('Mass')
14
+ end
15
+
16
+ def teardown
17
+ Dimension.reset!
18
+ System.reset!
19
+ Unit.reset!
20
+ Metric.reset!
21
+ end
22
+
23
+ def test_create_configurator
24
+ assert_instance_of Configurator, c = Configurator.new
25
+ assert_nil c.context.dimension
26
+ assert_nil c.context.system
27
+ assert_nil c.context.unit
28
+ end
29
+
30
+ def test_start_configurator
31
+ assert Configurator.start
32
+ assert Configurator.start {true}
33
+ assert !Configurator.start {false}
34
+ end
35
+
36
+ def test_start_configurator_with_context_args
37
+ assert_same Dimension::L, Configurator.start(:dimension => Dimension::L){context.dimension}
38
+ end
39
+
40
+ def test_change_dimension_context_for_duration_of_block
41
+ test_context = self
42
+ Configurator.start do
43
+ dimension(Dimension::L) do
44
+ test_context.assert_equal Dimension::L, context.dimension
45
+ true
46
+ end
47
+ test_context.assert_nil context.dimension
48
+ end
49
+ end
50
+
51
+ def test_change_system_context_for_duration_of_block
52
+ test_context = self
53
+ Configurator.start do
54
+ system(System::SI) do
55
+ test_context.assert_equal System::SI, context.system
56
+ true
57
+ end
58
+ test_context.assert_nil context.system
59
+ end
60
+ end
61
+
62
+ def test_build_base_unit
63
+ Configurator.start(:system => System::SI, :dimension => Dimension::L) do
64
+ base('meter', :detector => /\A(meters?|m)\Z/, :abbreviation => 'm')
65
+ end
66
+ assert_instance_of Unit, u = Unit[Dimension::L, System::SI, 'meter']
67
+ assert_same System::SI, u.system
68
+ assert_same Dimension::L, u.dimension
69
+ assert u.base?
70
+ assert_instance_of Metric, Metric[:L]
71
+ end
72
+
73
+ def test_build_derived_unit
74
+ Configurator.start(:system => System::SI, :dimension => Dimension::L) do
75
+ base('meter', :detector => /\A(meters?|m)\Z/, :abbreviation => 'm') do
76
+ derive('centimeter', 1e-2, :detector => /\A(centimeters?|cm)\Z/, :abbreviation => 'cm')
77
+ end
78
+ end
79
+ u0 = Unit[Dimension::L, System::SI, 'meter']
80
+ assert_instance_of Unit, u = Unit[Dimension::L, System::SI, 'centimeter']
81
+ assert_same System::SI, u.system
82
+ assert_same Dimension::L, u.dimension
83
+ assert_same u0, u.base
84
+ assert_equal 1E-2, u.factor
85
+ assert_equal 'cm', u.abbreviation
86
+ assert u.match("centimeters")
87
+ end
88
+
89
+ def test_build_aliased_unit
90
+ Configurator.start(:system => System::SI, :dimension => Dimension::L) do
91
+ base('meter', :detector => /\A(meters?|m)\Z/, :abbreviation => 'm') do
92
+ self.alias('decadecimeter')
93
+ end
94
+ end
95
+ u0 = Unit[Dimension::L, System::SI, 'meter']
96
+ assert_instance_of Unit, u = Unit[Dimension::L, System::SI, 'decadecimeter']
97
+ assert_same System::SI, u.system
98
+ assert_same Dimension::L, u.dimension
99
+ assert_same u0, u.base
100
+ assert_equal 1, u.factor
101
+ end
102
+
103
+ def test_build_referenced_unit
104
+ Configurator.start(:system => System::SI, :dimension => Dimension::L) do
105
+ base('meter', :detector => /\A(meters?|m)\Z/, :abbreviation => 'm')
106
+ system(:US) do
107
+ reference('yard', Unit[:L, :SI, 'meter'], 0.9144, :detector => /\A(yards?|yds?)\Z/, :abbreviation => 'yd')
108
+ end
109
+ end
110
+ u0 = Unit[Dimension::L, System::SI, 'meter']
111
+ assert_instance_of Unit, u = Unit[Dimension::L, System::US, 'yard']
112
+ assert_equal 0.9144, u.factor
113
+ assert_same u0, u.base
114
+ end
115
+
116
+ def test_build_combined_unit
117
+ Configurator.start(:system => System::SI, :dimension => Dimension::L) do
118
+ base('meter', :detector => /\A(meters?|m)\Z/, :abbreviation => 'm')
119
+ system(:US) do
120
+ reference('yard', Unit[:L, :SI, 'meter'], 0.9144, :detector => /\A(yards?|yds?)\Z/, :abbreviation => 'yd')
121
+ dimension(:A) do
122
+ combine('square yard', [Unit[:L, :US, 'yard'], Unit[:L, :US, 'yard']], :detector => //, :abbreviation => 'yd2')
123
+ end
124
+ end
125
+ end
126
+ u1 = Unit[Dimension::L, System::US, 'yard']
127
+ assert_instance_of Unit, u = Unit[:A, :US, 'square yard']
128
+ assert_equal Dimension::A, u.dimension
129
+ assert_equal 0.83612736, u.factor
130
+ assert_equal [u1.base, u1.base], u.base
131
+ end
132
+
133
+ def test_register_metric_options
134
+ Configurator.start(:system => System::SI, :dimension => Dimension::L) do
135
+ base('meter', :detector => /\A(meters?|m)\Z/, :abbreviation => 'm') do
136
+ prefer(:length_over_all, :precision => 0.01)
137
+ end
138
+ end
139
+ u = Unit[:L, :SI, 'm']
140
+ assert_instance_of Metric, m = Metric[:length_over_all]
141
+ assert_same Metric[:L], m.parent
142
+ assert_equal 0.01, m.preferences(u)[:precision]
143
+ end
144
+ end
data/test/demo.rb ADDED
@@ -0,0 +1,140 @@
1
+ require 'dimensional/configurator'
2
+ require 'rational'
3
+
4
+ # Define fundamental dimensions and composite dimensions
5
+ # Reference: http://en.wikipedia.org/wiki/Dimensional_Analysis
6
+ Dimensional::Dimension.register('Length')
7
+ Dimensional::Dimension.register('Mass')
8
+ Dimensional::Dimension.register('Time')
9
+ Dimensional::Dimension.register('Temperature', 'Temp') # Θ is the proper symbol, but it can't be represented in US ASCII
10
+ Dimensional::Dimension.register('Electric Charge', 'Q')
11
+ Dimensional::Dimension.register('Area', 'A', {Dimensional::Dimension[:L] => 2})
12
+ Dimensional::Dimension.register('Volume', 'V', {Dimensional::Dimension[:L] => 3})
13
+
14
+ # Define common Systems of Measurement
15
+ Dimensional::System.register('SI - International System (kg, tonne, m)', 'SI')
16
+ Dimensional::System.register('US Customary (lbs, ton, ft)', 'US') # http://en.wikipedia.org/wiki/United_States_customary_units
17
+ Dimensional::System.register('US Customary Troy (oz)', 'USt') # http://en.wikipedia.org/wiki/United_States_customary_units
18
+ Dimensional::System.register('British Imperial (lbs, ton, ft)', 'Imp') # http://en.wikipedia.org/wiki/Imperial_units
19
+
20
+ Dimensional::Configurator.start do
21
+ dimension(:L) do
22
+ system(:SI) do
23
+ base('meter', :detector => /\A(meters?|m)\Z/, :abbreviation => 'm') do
24
+ derive('centimeter', 1e-2, :detector => /\A(centimeters?|cm)\Z/, :abbreviation => 'cm')
25
+ derive('kilometer', 1e3, :detector => /\A(kilometers?|km)\Z/, :abbreviation => 'km')
26
+ end
27
+ end
28
+ system(:US) do # As of 1 July 1959 (http://en.wikipedia.org/wiki/United_States_customary_units#Units_of_length)
29
+ reference('yard', Dimensional::Unit[:L, :SI, 'meter'], 0.9144, :detector => /\A(yards?|yds?)\Z/, :abbreviation => 'yd') do
30
+ derive('foot', Rational(1,3), :detector => /\A(foot|feet|ft|')\Z/, :abbreviation => "ft", :format => "%p'") do
31
+ prefer(:hull)
32
+ derive('inch', Rational(1,12), :detector => /\A(inch|inches|in|")\Z/, :abbreviation =>"in", :format => "%p\"")
33
+ end
34
+ derive('furlong', 220, :detector => /\A(furlongs?)\Z/) do
35
+ derive('mile', 8, :detector => /\Amiles?\Z/, :abbreviation => 'mi')
36
+ end
37
+ end
38
+ end
39
+ end
40
+ dimension(:M) do
41
+ system(:SI) do
42
+ base('kilogram', :detector => /\A(kilograms?|kg)\Z/, :abbreviation => 'kg') do
43
+ derive('tonne', 1e3, :detector => /\A(tonnes?)\Z/, :abbreviation => 't') do # metric ton
44
+ prefer(:displacement)
45
+ end
46
+ derive('gram', 1e-3, :detector => /\A(grams?|g)\Z/, :abbreviation => 'g')
47
+ end
48
+ end
49
+ system(:US) do # Common units for mass and, occasionally, force/weight (http://en.wikipedia.org/wiki/United_States_customary_units#Units_of_mass)
50
+ reference('pound', Dimensional::Unit[:M, :SI, 'gram'], 453.59237, :detector => /\A(pounds?|lbs?|#)\Z/, :abbreviation => 'lb') do # avoirdupois
51
+ derive('hundredweight', 100, :detector => /\A(hundredweights?|cwt)\Z/, :abbreviation => 'cwt') do
52
+ derive('ton', 20, :detector => /\A(tons?|t)\Z/, :abbreviation => 't') do # short ton
53
+ prefer(:displacement)
54
+ end
55
+ end
56
+ derive('grain', 7000**-1, :detector => /\A(grains?|gr)\Z/, :abbreviation => 'gr') do
57
+ derive('dram', 27 + Rational(11, 32), :detector => /\A(drams?|dr)\Z/, :abbreviation => 'dr') do
58
+ derive('ounce', :detector => /\A(ounces?|ozs?)\Z/, :conversion => [437.5, :grain], :abbreviation => 'oz')
59
+ end
60
+ end
61
+ end
62
+ end
63
+ system(:Imp) do
64
+ reference('pound', Dimensional::Unit[:M, :SI, 'gram'], 453.59237, :detector => /\A(pounds?|lbs?|#)\Z/, :abbreviation => 'lb') do
65
+ derive('grain', 7000**-1, :detector => /\A(grains?|gr)\Z/, :abbreviation => 'gr')
66
+ derive('drachm', 256**-1, :detector => /\A(drachms?|dr)\Z/, :abbreviation => 'dr')
67
+ derive('ounce', 16**-1, :detector => /\A(ounces?|ozs?)\Z/, :abbreviation => 'oz')
68
+ derive('stone', 14, :detector => /\A(stones?)\Z/)
69
+ derive('quarter', 28, :detector => /\A(quarters?)\Z/)
70
+ derive('hundredweight', 112, :detector => /\A(hundredweights?|cwt)\Z/, :abbreviation => 'cwt')
71
+ derive('ton', 2240, :detector => /\A(tons?|t)\Z/, :abbreviation => 't') do # long ton
72
+ prefer(:displacement)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ dimension(:A) do
78
+ system(:SI) do
79
+ combine('square meter', %w(meter meter).map{|name| Dimensional::Unit[:L, :SI, name]}, :detector => /\A(sq\.?\s?meters?|m2)\Z/, :abbreviation => 'm2') do
80
+ derive('hectare', 10000, :format => "%.4f%U", :abbreviation => 'ha') do
81
+ prefer(:forestry, :precision => -4, :format => "%s%U")
82
+ end
83
+ end
84
+ end
85
+ system(:US) do # All measures below are approximations due to the difference between a survey foot and an international foot.
86
+ combine('square yard', %w(yard yard).map{|name| Dimensional::Unit[:L, :US, name]}, :detector => /yd2/, :abbreviation => 'yd2') do
87
+ derive('acre', 4840.0)
88
+ end
89
+ combine('square mile', %w(mile mile).map{|name| Dimensional::Unit[:L, :US, name]}, :detector => /\A(sq(uare|\.)?\s?miles?)\Z/) do
90
+ self.alias('section', :detector => /\Asections?\Z/) do
91
+ derive('township', 36, :detector => /\Atownships?\Z/)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ dimension(:V) do
98
+ system(:SI) do
99
+ combine('cubic meter', %w(meter meter meter).map{|name| Dimensional::Unit[:L, :SI, name]}, :detector => /\A(cubic meters?|m3)\Z/, :abbreviation => "m3") do
100
+ derive('cubic decimeter', 1e-3, :detector => /\A(cubic decimeters?|dm3)\Z/, :abbreviation => "dm3") do
101
+ self.alias('liter', :detector => /\A(liters?|l|L)\Z/, :abbreviation => "l") do
102
+ derive('milliliter', 1E-3, :detector => /\A(milliliters?|ml|mL)\Z/, :abbreviation => "ml")
103
+ end
104
+ end
105
+ end
106
+ end
107
+ system(:Imp) do
108
+ reference('ounce', Dimensional::Unit[:V, :SI, 'milliliter'], 28.4130625, :detector => /\A((fluid )?ounces?|oz)\Z/, :abbreviation => "fl oz")
109
+ # register :ounce, :conversion => [28.4130625, :milliliter], :detector => /\A(imperial\s(fluid )?imp\.\sounces?|imp\.\soz)\Z/, :abbreviation => "imp. oz"
110
+ # register :gill, :conversion => [5, :ounce], :detector => /\A(gills?|gi)\Z/, :abbreviation => "gi"
111
+ # register :cup, :conversion => [2, :gill], :detector => /\A(cups?)\Z/, :abbreviation => "cp"
112
+ # register :pint, :conversion => [2, :cup], :detector => /\A(pints?|pt)\Z/, :abbreviation => "pt"
113
+ # register :quart, :conversion => [2, :pint], :detector => /\A(quarts?|qt)\Z/, :abbreviation => "qt"
114
+ # register :gallon, :conversion => [4, :quart], :detector => /\A(gallons?|gal)\Z/, :abbreviation => "gal"
115
+ end
116
+ system(:US) do
117
+ # # Common US Customary units for volume, based on the SI Base Unit 'liter'
118
+ # register :minim, :conversion => [0.00006161152, :liter], :abbreviation => "min" # Base Unit
119
+ # register :dram, :conversion => [60, :minim], :abbreviation => "fl dr"
120
+ # register :ounce, :conversion => [8, :dram], :detector => /\A((fluid )?ounces?|oz)\Z/, :abbreviation => "fl oz"
121
+ # register :gill, :conversion => [4, :ounce], :detector => /\A(gills?|gi)\Z/, :abbreviation => "gi"
122
+ # register :cup, :conversion => [2, :gill], :detector => /\A(cups?)\Z/, :abbreviation => "cp"
123
+ # register :pint, :conversion => [2, :cup], :detector => /\A(pints?|pt)\Z/, :abbreviation => "pt"
124
+ # register :quart, :conversion => [2, :pint], :detector => /\A(quarts?|qt)\Z/, :abbreviation => "qt"
125
+ # register :gallon, :conversion => [4, :quart], :detector => /\A(gallons?|gal)\Z/, :abbreviation => "gal"
126
+ end
127
+ end
128
+
129
+ dimension(nil) do
130
+ system(:US) do
131
+ base('each', :detector => /\Aea(ch)?\Z/, :abbreviation => 'ea') do
132
+ derive('pair', 2, :detector => /\A(pr|pair)\Z/, :abbreviation => 'pr')
133
+ derive('dozen', 12, :detector => /\A(dz|dozen)\Z/, :abbreviation => 'dz') do
134
+ derive('gross', 12)
135
+ end
136
+ derive('score', 20)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,55 @@
1
+ require 'test/unit'
2
+ require 'dimensional/dimension'
3
+
4
+ class DimensionTest < Test::Unit::TestCase
5
+ include Dimensional
6
+
7
+ def teardown
8
+ Dimension.reset!
9
+ end
10
+
11
+ def test_create_new_fundamental_dimension
12
+ assert_instance_of Dimension, d = Dimension.new('Mass')
13
+ assert d.fundamental?
14
+ assert_equal 'Mass', d.name
15
+ assert_equal 'M', d.symbol
16
+ assert_equal Hash.new, d.exponents
17
+ end
18
+
19
+ def test_create_new_composite_dimension
20
+ l = Dimension.new('Length')
21
+ assert_instance_of Dimension, a = Dimension.new('Hyperspace', 'H4', {l => 4})
22
+ assert !a.fundamental?
23
+ assert a.exponents.has_key?(l)
24
+ assert_equal 4, a.exponents[l]
25
+ end
26
+
27
+ def test_register_new_dimension
28
+ assert d = Dimension.register('Length')
29
+ assert_instance_of Dimension, d
30
+ assert_same d, Dimension['Length']
31
+ assert_same d, Dimension['L']
32
+ assert defined?(Dimension::L)
33
+ assert_same d, Dimension::L
34
+ end
35
+
36
+ def test_register_new_dimension_with_alternate_symbol
37
+ assert d = Dimension.register('Electric Charge', 'Q')
38
+ assert_instance_of Dimension, d
39
+ assert_same d, Dimension['Electric Charge']
40
+ assert_same d, Dimension['Q']
41
+ assert defined?(Dimension::Q)
42
+ assert_same d, Dimension::Q
43
+ end
44
+
45
+ def test_register_new_dimension_with_symbol_that_is_not_a_valid_constant
46
+ assert d = Dimension.register('Temperature', 'Θ')
47
+ assert_instance_of Dimension, d
48
+ assert_same d, Dimension['Temperature']
49
+ assert_same d, Dimension['Θ']
50
+ end
51
+
52
+ def test_return_nil_on_nil_lookup
53
+ assert_nil Dimension[nil]
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ require 'test/unit'
2
+ require 'dimensional'
3
+
4
+ class DimensionalTest < Test::Unit::TestCase
5
+ include Dimensional
6
+
7
+ def setup
8
+ load 'test/demo.rb'
9
+ end
10
+
11
+ def teardown
12
+ Dimension.reset!
13
+ System.reset!
14
+ Unit.reset!
15
+ Metric.reset!
16
+ end
17
+
18
+ def test_a_lot
19
+ 100.times do
20
+ assert m0 = Measure.parse("1 acre", Metric[:forestry])
21
+ assert u2 = Unit[:A, :SI, 'hectare']
22
+ assert m2 = m0.convert(u2)
23
+ assert_in_delta 0.40468564224, m2, 0.00001
24
+ assert_equal "0.4047ha", m2.to_s
25
+ end
26
+ end
27
+ end