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.
@@ -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