units-system 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -0
- data/Gemfile.lock +29 -0
- data/README.rdoc +2 -2
- data/Rakefile +21 -30
- data/VERSION +1 -1
- data/lib/units-system.rb +8 -648
- data/lib/units/definitions.rb +99 -0
- data/lib/units/math.rb +49 -0
- data/lib/units/measure.rb +263 -0
- data/lib/units/prefixes.rb +42 -0
- data/lib/units/system.rb +236 -0
- data/test/test_units-system.rb +46 -1
- data/units-system.gemspec +40 -28
- metadata +109 -55
- data/.gitignore +0 -21
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Units
|
4
|
+
|
5
|
+
# Units definitions
|
6
|
+
|
7
|
+
# declare SI base units
|
8
|
+
si_units :mass, :kg # m
|
9
|
+
si_units :length, :m # l
|
10
|
+
si_units :time, :s # t
|
11
|
+
si_units :electric_current, :A # I
|
12
|
+
si_units :temperature, :K # T
|
13
|
+
si_units :luminous_intensity, :cd # Iv
|
14
|
+
si_units :amount_of_substance, :mol # n
|
15
|
+
|
16
|
+
# define base units
|
17
|
+
define :g, 'gram', :mass, 1E-3
|
18
|
+
define :m, 'meter', :length, 1.0
|
19
|
+
define :s, 'second', :time, 1.0
|
20
|
+
define :A, 'ampere', :electric_current, 1.0
|
21
|
+
define :K, 'kelvin', :temperature, 1.0
|
22
|
+
define :cd,'candela',:luminous_intensity, 1.0
|
23
|
+
define :mol,'mole', :amount_of_substance, 1.0
|
24
|
+
|
25
|
+
# declare derived quantities with no named units
|
26
|
+
si_units :speed, u{m/s}
|
27
|
+
si_units :acceleration, u{m/s**2}
|
28
|
+
si_units :area, u{m**2}
|
29
|
+
si_units :volume, u{m**3}
|
30
|
+
|
31
|
+
# derived quantities with named units
|
32
|
+
define :W, 'Watt', :power, u{kg*m**2/s**3} # J/s
|
33
|
+
define :Hz, 'herz', :frequency, u{1/s}
|
34
|
+
define :N, 'newton', :force, u{m*kg/s**2}
|
35
|
+
define :Pa, 'pascal', :pressure, u{N/m**2}
|
36
|
+
define :J, 'joule', :energy, u{N*m}
|
37
|
+
define :C, 'coulomb', :electric_charge, u{s*A}
|
38
|
+
define :V, 'volt', :voltage, u{W/A}
|
39
|
+
define :F, 'farad', :electric_capacitance, u{C/V}
|
40
|
+
define :Ω, 'ohm', :electric_resistance, u{V/A} # ohm: Ω omega: Ω
|
41
|
+
define :S, 'siemens', :electric_condctance, u{1/Ω}
|
42
|
+
define :Wb, 'weber', :magnetic_flux, u{J/A}
|
43
|
+
define :T, 'tesla', :magnetic_field, u{N/(A*m)}
|
44
|
+
define :H, 'henry', :inductance, u{Wb/A}
|
45
|
+
define :rad, 'radian', :angle, u{m/m}
|
46
|
+
define :sr, 'steradian', :solid_angle, u{m**2/m**2}
|
47
|
+
define :lm, 'lumen', :luminous_flux, u{cd*sr}
|
48
|
+
define :lx, 'lux', :illuminance, u{lm/m**2}
|
49
|
+
define :Bq, 'bequerel', :radioactivity, u{1/s}
|
50
|
+
define :Gy, 'gray', :absorbed_dose, u{J/kg}
|
51
|
+
define :Sv, 'sievert', :equivalent_dose, u{J/kg}
|
52
|
+
define :kat, 'katal', :catalytic_activity, u{mol/s}
|
53
|
+
|
54
|
+
# Other units
|
55
|
+
|
56
|
+
define :min, 'minute', 60, :s
|
57
|
+
define :h, 'hour', 60, :min
|
58
|
+
define :d, 'day', 24, :h
|
59
|
+
define :mi, 'mile', 1.609344, :km
|
60
|
+
define :in, 'inch', 2.54, :cm
|
61
|
+
define :ft, 'foot', 0.3048, :m
|
62
|
+
define :inch, 'inch', 1, :in # alternative to avoid having to use self.in (in is a Ruby keyword)
|
63
|
+
define :lb, 'pound', 0.45359237, :kg
|
64
|
+
|
65
|
+
define :°C, 'degree Celsius', 1, :K, +273.15
|
66
|
+
define :°F, 'degree Fahrenheit', 5.0/9.0, :K, +459.67
|
67
|
+
define :R, 'rankine', 5.0/9.0, :K
|
68
|
+
|
69
|
+
define :l, 'litre', u{dm**3}
|
70
|
+
define :L, 'litre', 1, :l
|
71
|
+
|
72
|
+
define :°, 'degree', ::Math::PI/180.0, :rad
|
73
|
+
define :′, 'arc-minute', ::Math::PI/180.0/60.0, :rad
|
74
|
+
define :″, 'arc-second', ::Math::PI/180.0/3600.0, :rad
|
75
|
+
# not so cool, but easier to type alternatives:
|
76
|
+
define :deg, 'degree', 1, :°
|
77
|
+
define :arcmin, 'arc-minute', 1, :′
|
78
|
+
define :arcsec, 'arc-second', 1, :″
|
79
|
+
|
80
|
+
define :g0, 'standard gravity', u{9.80665*m/s**2}
|
81
|
+
|
82
|
+
define :bar, 'bar', 1E5, :Pa
|
83
|
+
define :atm, 'atmosphere', 101325.0, :Pa
|
84
|
+
define :mWC, 'meters of water column', u{1E3*kg*g0/m**2}
|
85
|
+
define :Torr, 'torricelli', u{atm/760}
|
86
|
+
define :mHg, 'mHg', u{13.5951E3*kg*g0/m**2}
|
87
|
+
|
88
|
+
# define :kp, 'kilopond', :force, u{kg*g0} # or define pond?
|
89
|
+
define :gf, 'gram-force', u{g*g0} # kilopond kp = kgf
|
90
|
+
define :lbf, 'pound-force', u{lb*g0}
|
91
|
+
|
92
|
+
define :dyn, 'dyne', 10, :µN # u{1*g*cm/s**2}
|
93
|
+
define :galUS, 'U.S. liquid gallon', u{231*self.in**3}
|
94
|
+
define :galUK, 'Imperial gallon', 4.546092, :l
|
95
|
+
define :hp, 'horsepower', u{550*ft*lbf/s}
|
96
|
+
|
97
|
+
define :psi, 'pounds-force per square inch', u{lbf/self.in**2}
|
98
|
+
|
99
|
+
end # Units
|
data/lib/units/math.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Units
|
4
|
+
|
5
|
+
module Math
|
6
|
+
|
7
|
+
[:sin, :cos, :tan].each do |fun|
|
8
|
+
define_method fun do |x|
|
9
|
+
x = Units.u{x.in(rad)} if x.kind_of?(Measure)
|
10
|
+
::Math.send(fun, x)
|
11
|
+
end
|
12
|
+
module_function fun
|
13
|
+
end
|
14
|
+
|
15
|
+
[:asin, :acos, :atan].each do |fun|
|
16
|
+
define_method fun do |x|
|
17
|
+
if x.kind_of?(Measure)
|
18
|
+
raise ArgumentError,"Invalid dimensions for #{fun} argument" unless x.dimensionless?
|
19
|
+
x = x.magnitude
|
20
|
+
end
|
21
|
+
Units.u{::Math.send(fun, x)*rad}
|
22
|
+
end
|
23
|
+
module_function fun
|
24
|
+
end
|
25
|
+
|
26
|
+
module_function
|
27
|
+
def atan2(x,y)
|
28
|
+
if x.kind_of?(Measure)
|
29
|
+
if y.kind_of?(Measure)
|
30
|
+
if x.base.to_si.units != y.base.to_si.units
|
31
|
+
raise ArgumentError,"Inconsistent units for atan2 arguments #{x.u}, #{y.u}"
|
32
|
+
end
|
33
|
+
# or x = x.to_si.magnitude, y=y.to_si.magnitude
|
34
|
+
y = y.in(x.units)
|
35
|
+
x = x.magnitude
|
36
|
+
else
|
37
|
+
raise ArgumentError,"Invalid dimensions for atan2 argument #{x.u}" unless x.dimensionless?
|
38
|
+
x = x.magnitude
|
39
|
+
end
|
40
|
+
elsif y.kind_of?(Measure)
|
41
|
+
raise ArgumentError,"Invalid dimensions for atan2 argument #{y.u}" unless y.dimensionless?
|
42
|
+
y = y.magnitude
|
43
|
+
end
|
44
|
+
Units.u{::Math.atan2(x,y)*rad}
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end # Units
|
@@ -0,0 +1,263 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Units
|
4
|
+
|
5
|
+
class Measure
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
if args.size==0
|
9
|
+
mag = 1.0
|
10
|
+
units = {}
|
11
|
+
elsif args.size==1
|
12
|
+
case args.first
|
13
|
+
when Numeric
|
14
|
+
mag = args.first
|
15
|
+
units = {}
|
16
|
+
else
|
17
|
+
mag = 1.0
|
18
|
+
units = args.first
|
19
|
+
end
|
20
|
+
elsif args.size==2
|
21
|
+
mag, units = args
|
22
|
+
else
|
23
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 0, 1 or 2)"
|
24
|
+
end
|
25
|
+
|
26
|
+
@magnitude = mag # rename to value?
|
27
|
+
case units
|
28
|
+
when Symbol
|
29
|
+
uinfo = Units.unit(units)
|
30
|
+
if uinfo
|
31
|
+
if uinfo.dim
|
32
|
+
@units = {uinfo.dim=>[units,1]}
|
33
|
+
else
|
34
|
+
@magnitude *= uinfo.factor
|
35
|
+
@units = {}
|
36
|
+
end
|
37
|
+
else
|
38
|
+
raise ArgumentError,"Invalid symbol for Measure definition: #{units.inspect} "
|
39
|
+
end
|
40
|
+
when Measure
|
41
|
+
@magnitude *= units.magnitude
|
42
|
+
@units = units.units
|
43
|
+
else
|
44
|
+
@units = units
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
include ModalSupport::StateEquivalent
|
49
|
+
include ModalSupport::BracketConstructor
|
50
|
+
|
51
|
+
attr_reader :magnitude, :units
|
52
|
+
|
53
|
+
# represent in text using Ruby notation
|
54
|
+
def to_s
|
55
|
+
return @magnitude.to_s if magnitude?
|
56
|
+
u_descr = Units.units_descr(@units)
|
57
|
+
"#{@magnitude}*#{u_descr}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# more verbose description (not grammatically perfect)
|
61
|
+
def describe
|
62
|
+
return @magnitude.to_s if magnitude?
|
63
|
+
u_descr = Units.units_descr(@units, true)
|
64
|
+
"#{@magnitude} #{u_descr}"
|
65
|
+
end
|
66
|
+
|
67
|
+
# more natural concise text representation
|
68
|
+
def abr
|
69
|
+
self.to_s.gsub('**','^').tr('*',' ')
|
70
|
+
end
|
71
|
+
|
72
|
+
# decompose compound units
|
73
|
+
def detailed_units(all_levels = false)
|
74
|
+
mag = @magnitude
|
75
|
+
prev_units = self.units
|
76
|
+
units = {}
|
77
|
+
loop do
|
78
|
+
compound = false
|
79
|
+
prev_units.each_pair do |dim, (unit,mul)|
|
80
|
+
ud = Units.unit(unit)
|
81
|
+
if ud.decomposition
|
82
|
+
compound = true
|
83
|
+
mag *= ud.decomposition.magnitude
|
84
|
+
ud.decomposition.units.each_pair do |d, (u,m)|
|
85
|
+
mag *= self.class.combine(units, d, u, m)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
mag *= self.class.combine(units, dim, unit, mul)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
if all_levels && compound
|
92
|
+
prev_units = units
|
93
|
+
units = {}
|
94
|
+
else
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
Measure.new mag, units
|
99
|
+
end
|
100
|
+
|
101
|
+
# decompose to base units
|
102
|
+
def base
|
103
|
+
detailed_units true
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.combine(units, dim, unit, mult)
|
107
|
+
factor = 1
|
108
|
+
if units[dim]
|
109
|
+
u,m = units[dim]
|
110
|
+
if u!=unit
|
111
|
+
factor *= Units.conversion_factor(unit, u)**mult
|
112
|
+
end
|
113
|
+
units[dim] = [u, m+mult]
|
114
|
+
else
|
115
|
+
units[dim] = [unit, mult]
|
116
|
+
end
|
117
|
+
factor
|
118
|
+
end
|
119
|
+
|
120
|
+
def /(other)
|
121
|
+
self * (other.kind_of?(Numeric) ? 1.0/other : other.inverse)
|
122
|
+
end
|
123
|
+
|
124
|
+
def inspect
|
125
|
+
"Measure(#{@magnitude.inspect}, #{@units.inspect})"
|
126
|
+
end
|
127
|
+
|
128
|
+
def *(other)
|
129
|
+
case other
|
130
|
+
when Numeric
|
131
|
+
mag = self.magnitude*other
|
132
|
+
units = self.units
|
133
|
+
else
|
134
|
+
mag = self.magnitude*other.magnitude
|
135
|
+
units = {}
|
136
|
+
(self.units.keys | other.units.keys).each do |dim|
|
137
|
+
other_u = other.units[dim] || [nil,0]
|
138
|
+
this_u = self.units[dim] || [other_u.first,0]
|
139
|
+
# mag *= Units.conversion_factor(this_u.first, other_u.first) if other_u.first
|
140
|
+
mult = this_u.last+other_u.last
|
141
|
+
mag *= Units.conversion_factor(other_u.first, this_u.first)**(other_u.last) if other_u.first
|
142
|
+
units[dim] = [this_u.first, mult]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
units.reject!{|dim,(u,m)| m==0}
|
146
|
+
Measure.new(mag, units)
|
147
|
+
end
|
148
|
+
|
149
|
+
def +(other)
|
150
|
+
Measure.new self.magnitude+other.to(self.units).magnitude, self.units
|
151
|
+
end
|
152
|
+
|
153
|
+
def -(other)
|
154
|
+
self + (-other)
|
155
|
+
end
|
156
|
+
|
157
|
+
def **(n)
|
158
|
+
raise ArgumentError,"Only integer powers are allowed for magnitudes" unless n.kind_of?(Integer)
|
159
|
+
units = @units.dup
|
160
|
+
units.each_pair do |dim, (u, m)|
|
161
|
+
units[dim] = [u, m*n]
|
162
|
+
end
|
163
|
+
Measure.new @magnitude, units
|
164
|
+
end
|
165
|
+
|
166
|
+
def inverse
|
167
|
+
#Measure.new(1.0/@magnitude, @units.map_hash{|unit,mult| [unit, -mult]})
|
168
|
+
units = {}
|
169
|
+
@units.each_pair do |dim, (unit, mult)|
|
170
|
+
units[dim] = [unit, -mult]
|
171
|
+
end
|
172
|
+
Measure.new(1.0/@magnitude, units)
|
173
|
+
end
|
174
|
+
|
175
|
+
def -@
|
176
|
+
Measure.new(-@magnitude, units)
|
177
|
+
end
|
178
|
+
|
179
|
+
def in(other, mode=:absolute)
|
180
|
+
other = Measure.new(1.0, other) unless other.kind_of?(Measure)
|
181
|
+
other = other.base
|
182
|
+
this = self.base
|
183
|
+
dims = this.units.keys | other.units.keys
|
184
|
+
mag = this.magnitude/other.magnitude
|
185
|
+
dims.each do |dim|
|
186
|
+
if !this.units[dim] || !other.units[dim] ||
|
187
|
+
(this.units[dim].last != other.units[dim].last)
|
188
|
+
raise "Inconsistent units #{Units.units_descr(this.units)} #{Units.units_descr(other.units)}"
|
189
|
+
end
|
190
|
+
this_u, mult = this.units[dim]
|
191
|
+
other_u = other.units[dim].first
|
192
|
+
mag *= Units.conversion_factor(this_u, other_u)**mult
|
193
|
+
end
|
194
|
+
if mode!=:relative && dims.size==1 && this.units[dims.first].last==1
|
195
|
+
# consider "level" conversion for biased units (otherwise consider interval or difference values)
|
196
|
+
mag += Units.conversion_bias(this.units[dims.first].first, other.units[dims.first].first)
|
197
|
+
end
|
198
|
+
mag
|
199
|
+
end
|
200
|
+
|
201
|
+
def to(units, mode=:absolute)
|
202
|
+
units = units.u if units.kind_of?(Measure)
|
203
|
+
Measure.new self.in(units, mode), units
|
204
|
+
end
|
205
|
+
|
206
|
+
def si_units
|
207
|
+
units = {}
|
208
|
+
@units.each_pair do |dim, (unit, mult)|
|
209
|
+
si_unit = SI_UNITS[dim]
|
210
|
+
if si_unit.kind_of?(Measure)
|
211
|
+
si_unit.units.each_pair do |d, (u,m)|
|
212
|
+
self.class.combine(units, d, u, m*mult)
|
213
|
+
end
|
214
|
+
else
|
215
|
+
self.class.combine(units, dim, si_unit, mult)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
units
|
219
|
+
end
|
220
|
+
|
221
|
+
def to_si
|
222
|
+
to(si_units)
|
223
|
+
end
|
224
|
+
|
225
|
+
def coerce(other)
|
226
|
+
[Measure.new(other, {}), self]
|
227
|
+
end
|
228
|
+
|
229
|
+
def u # dimension? unit? only_units? strip_units? units_measure?
|
230
|
+
Measure.new(1.0, self.units)
|
231
|
+
end
|
232
|
+
|
233
|
+
# dimension (quantity)
|
234
|
+
def dimension
|
235
|
+
q = nil
|
236
|
+
u = self.base.si_units
|
237
|
+
SI_UNITS.each_pair do |dim, unit|
|
238
|
+
unit = Measure.new(1.0, unit) unless unit.kind_of?(Measure)
|
239
|
+
if unit.base.si_units == u
|
240
|
+
q = dim
|
241
|
+
break
|
242
|
+
end
|
243
|
+
end
|
244
|
+
q
|
245
|
+
end
|
246
|
+
|
247
|
+
def dimensionless?
|
248
|
+
base.units.reject{|d,(u,m)| m==0}.empty?
|
249
|
+
end
|
250
|
+
|
251
|
+
# less strict dimensionless condition (e.g. an angle is not a pure magnitude in this sense)
|
252
|
+
def magnitude?
|
253
|
+
self.units.reject{|d,(u,m)| m==0}.empty?
|
254
|
+
end
|
255
|
+
|
256
|
+
end # Measure
|
257
|
+
|
258
|
+
def Measure(*args)
|
259
|
+
Measure[*args]
|
260
|
+
end
|
261
|
+
module_function :Measure
|
262
|
+
|
263
|
+
end # Units
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Units
|
4
|
+
|
5
|
+
PREFIXES = {
|
6
|
+
:y=>[1E-24, 'yocto'],
|
7
|
+
:z=>[1E-21, 'zepto'],
|
8
|
+
:a=>[1E-18, 'atto'],
|
9
|
+
:f=>[1E-15, 'femto'],
|
10
|
+
:p=>[1E-12, 'pico'],
|
11
|
+
:n=>[1E-09, 'nano'],
|
12
|
+
:"\302\265"=>[1E-06, 'micro'], # ASCII alternative: u; define it?
|
13
|
+
:m=>[1E-03, 'milli'],
|
14
|
+
:c=>[1E-02, 'centi'],
|
15
|
+
:d=>[1E-01, 'deci'],
|
16
|
+
:da=>[1E1, 'deca'],
|
17
|
+
:h=>[1E02, 'hecto'],
|
18
|
+
:k=>[1E03, 'kilo'],
|
19
|
+
:M=>[1E06, 'mega'],
|
20
|
+
:G=>[1E09, 'giga'],
|
21
|
+
:T=>[1E12, 'tera'],
|
22
|
+
:P=>[1E15, 'peta'],
|
23
|
+
:E=>[1E18, 'exa'],
|
24
|
+
:Z=>[1E21, 'zetta'],
|
25
|
+
:Y=>[1E24, 'yotta']
|
26
|
+
}
|
27
|
+
|
28
|
+
def self.prefix_factor(prefix)
|
29
|
+
pd = PREFIXES[prefix.to_sym]
|
30
|
+
pd && pd.first
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.prefix_name(prefix)
|
34
|
+
pd = PREFIXES[prefix.to_sym]
|
35
|
+
pd && pd.last
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.prefix_factor_and_name(prefix)
|
39
|
+
PREFIXES[prefix]
|
40
|
+
end
|
41
|
+
|
42
|
+
end # Units
|