units-system 0.0.5 → 0.1.0

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,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