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
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
jeweler (1.8.3)
|
6
|
+
bundler (~> 1.0)
|
7
|
+
git (>= 1.2.5)
|
8
|
+
rake
|
9
|
+
rdoc
|
10
|
+
json (1.6.6)
|
11
|
+
modalsupport (0.8.3)
|
12
|
+
rake (0.9.2.2)
|
13
|
+
rdoc (3.12)
|
14
|
+
json (~> 1.4)
|
15
|
+
shoulda (3.0.1)
|
16
|
+
shoulda-context (~> 1.0.0)
|
17
|
+
shoulda-matchers (~> 1.0.0)
|
18
|
+
shoulda-context (1.0.0)
|
19
|
+
shoulda-matchers (1.0.0)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
bundler (~> 1)
|
26
|
+
jeweler (~> 1.8.3)
|
27
|
+
modalsupport
|
28
|
+
rdoc (~> 3.12)
|
29
|
+
shoulda
|
data/README.rdoc
CHANGED
@@ -59,10 +59,10 @@ local variables.
|
|
59
59
|
# Now let's convert som imperial units to SI:
|
60
60
|
puts (100*mi/h).to_si # => 44.704*m/s
|
61
61
|
|
62
|
-
# Note that in is a Ruby keyword, so to use inches you must use self.in
|
62
|
+
# Note that +in+ is a Ruby keyword, so to use inches you must use +self.in+:
|
63
63
|
puts (10*cm).to(self.in) # => 3.93700787401575*in
|
64
64
|
# ...or use tha alternative nonstandard name +inch+
|
65
|
-
puts (10*cm).to(
|
65
|
+
puts (10*cm).to(inch) # => 3.93700787401575*inch
|
66
66
|
|
67
67
|
# Now let's use derived units, e.g. power units:
|
68
68
|
x = 10*kW
|
data/Rakefile
CHANGED
@@ -1,21 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
2
12
|
require 'rake'
|
3
13
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
-
end
|
15
|
-
Jeweler::GemcutterTasks.new
|
16
|
-
rescue LoadError
|
17
|
-
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
gem.name = "units-system"
|
17
|
+
gem.summary = %Q{Arithmetic with units of measure}
|
18
|
+
gem.description = %Q{Experimental unit conversion & arithmetic for Ruby 1.9}
|
19
|
+
gem.email = "jgoizueta@gmail.com"
|
20
|
+
gem.homepage = "http://github.com/jgoizueta/units-system"
|
21
|
+
gem.authors = ["Javier Goizueta"]
|
22
|
+
# dependencies defined in Gemfile
|
18
23
|
end
|
24
|
+
Jeweler::RubygemsDotOrgTasks.new
|
19
25
|
|
20
26
|
require 'rake/testtask'
|
21
27
|
Rake::TestTask.new(:test) do |test|
|
@@ -24,24 +30,9 @@ Rake::TestTask.new(:test) do |test|
|
|
24
30
|
test.verbose = true
|
25
31
|
end
|
26
32
|
|
27
|
-
begin
|
28
|
-
require 'rcov/rcovtask'
|
29
|
-
Rcov::RcovTask.new do |test|
|
30
|
-
test.libs << 'test'
|
31
|
-
test.pattern = 'test/**/test_*.rb'
|
32
|
-
test.verbose = true
|
33
|
-
end
|
34
|
-
rescue LoadError
|
35
|
-
task :rcov do
|
36
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
task :test => :check_dependencies
|
41
|
-
|
42
33
|
task :default => :test
|
43
34
|
|
44
|
-
require '
|
35
|
+
require 'rdoc/task'
|
45
36
|
Rake::RDocTask.new do |rdoc|
|
46
37
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
47
38
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/lib/units-system.rb
CHANGED
@@ -1,561 +1,13 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
3
|
+
require 'modalsupport'
|
4
|
+
require 'units/measure'
|
5
|
+
require 'units/prefixes'
|
6
|
+
require 'units/system'
|
7
|
+
require 'units/math'
|
4
8
|
|
5
9
|
module Units
|
6
|
-
|
7
|
-
class Measure
|
8
10
|
|
9
|
-
def initialize(mag=1.0, units={})
|
10
|
-
@magnitude = mag # rename to value?
|
11
|
-
case units
|
12
|
-
when Symbol
|
13
|
-
uinfo = Units.unit(units)
|
14
|
-
if uinfo
|
15
|
-
if uinfo.dim
|
16
|
-
@units = {uinfo.dim=>[units,1]}
|
17
|
-
else
|
18
|
-
@magnitude *= uinfo.factor
|
19
|
-
@units = {}
|
20
|
-
end
|
21
|
-
else
|
22
|
-
raise ArgumentError,"Invalid symbol for Measure definition: #{units.inspect} "
|
23
|
-
end
|
24
|
-
when Measure
|
25
|
-
@magnitude *= units.magnitude
|
26
|
-
@units = units.units
|
27
|
-
else
|
28
|
-
@units = units
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
attr_reader :magnitude, :units
|
33
|
-
|
34
|
-
# represent in text using Ruby notation
|
35
|
-
def to_s
|
36
|
-
return @magnitude.to_s if magnitude?
|
37
|
-
u_descr = Units.units_descr(@units)
|
38
|
-
"#{@magnitude}*#{u_descr}"
|
39
|
-
end
|
40
|
-
|
41
|
-
# more verbose description (not grammatically perfect)
|
42
|
-
def describe
|
43
|
-
return @magnitude.to_s if magnitude?
|
44
|
-
u_descr = Units.units_descr(@units, true)
|
45
|
-
"#{@magnitude} #{u_descr}"
|
46
|
-
end
|
47
|
-
|
48
|
-
# more natural concise text representation
|
49
|
-
def abr
|
50
|
-
self.to_s.gsub('**','^').tr('*',' ')
|
51
|
-
end
|
52
|
-
|
53
|
-
# decompose compound units
|
54
|
-
def detailed_units(all_levels = false)
|
55
|
-
mag = @magnitude
|
56
|
-
prev_units = self.units
|
57
|
-
units = {}
|
58
|
-
loop do
|
59
|
-
compound = false
|
60
|
-
prev_units.each_pair do |dim, (unit,mul)|
|
61
|
-
ud = Units.unit(unit)
|
62
|
-
if ud.decomposition
|
63
|
-
compound = true
|
64
|
-
mag *= ud.decomposition.magnitude
|
65
|
-
ud.decomposition.units.each_pair do |d, (u,m)|
|
66
|
-
mag *= self.class.combine(units, d, u, m)
|
67
|
-
end
|
68
|
-
else
|
69
|
-
mag *= self.class.combine(units, dim, unit, mul)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
if all_levels && compound
|
73
|
-
prev_units = units
|
74
|
-
units = {}
|
75
|
-
else
|
76
|
-
break
|
77
|
-
end
|
78
|
-
end
|
79
|
-
Measure.new mag, units
|
80
|
-
end
|
81
|
-
|
82
|
-
# decompose to base units
|
83
|
-
def base
|
84
|
-
detailed_units true
|
85
|
-
end
|
86
|
-
|
87
|
-
def self.combine(units, dim, unit, mult)
|
88
|
-
factor = 1
|
89
|
-
if units[dim]
|
90
|
-
u,m = units[dim]
|
91
|
-
if u!=unit
|
92
|
-
factor *= Units.conversion_factor(unit, u)**mult
|
93
|
-
end
|
94
|
-
units[dim] = [u, m+mult]
|
95
|
-
else
|
96
|
-
units[dim] = [unit, mult]
|
97
|
-
end
|
98
|
-
factor
|
99
|
-
end
|
100
|
-
|
101
|
-
def /(other)
|
102
|
-
self * (other.kind_of?(Numeric) ? 1.0/other : other.inverse)
|
103
|
-
end
|
104
|
-
|
105
|
-
def inspect
|
106
|
-
"Measure(#{@magnitude.inspect}, #{@units.inspect})"
|
107
|
-
end
|
108
|
-
|
109
|
-
def *(other)
|
110
|
-
case other
|
111
|
-
when Numeric
|
112
|
-
mag = self.magnitude*other
|
113
|
-
units = self.units
|
114
|
-
else
|
115
|
-
mag = self.magnitude*other.magnitude
|
116
|
-
units = {}
|
117
|
-
(self.units.keys | other.units.keys).each do |dim|
|
118
|
-
other_u = other.units[dim] || [nil,0]
|
119
|
-
this_u = self.units[dim] || [other_u.first,0]
|
120
|
-
# mag *= Units.conversion_factor(this_u.first, other_u.first) if other_u.first
|
121
|
-
mult = this_u.last+other_u.last
|
122
|
-
mag *= Units.conversion_factor(other_u.first, this_u.first)**(other_u.last) if other_u.first
|
123
|
-
units[dim] = [this_u.first, mult]
|
124
|
-
end
|
125
|
-
end
|
126
|
-
units.reject!{|dim,(u,m)| m==0}
|
127
|
-
Measure.new(mag, units)
|
128
|
-
end
|
129
|
-
|
130
|
-
def +(other)
|
131
|
-
Measure.new self.magnitude+other.to(self.units).magnitude, self.units
|
132
|
-
end
|
133
|
-
|
134
|
-
def -(other)
|
135
|
-
self + (-other)
|
136
|
-
end
|
137
|
-
|
138
|
-
def **(n)
|
139
|
-
raise ArgumentError,"Only integer powers are allowed for magnitudes" unless n.kind_of?(Integer)
|
140
|
-
units = @units.dup
|
141
|
-
units.each_pair do |dim, (u, m)|
|
142
|
-
units[dim] = [u, m*n]
|
143
|
-
end
|
144
|
-
Measure.new @magnitude, units
|
145
|
-
end
|
146
|
-
|
147
|
-
def inverse
|
148
|
-
#Measure.new(1.0/@magnitude, @units.map_hash{|unit,mult| [unit, -mult]})
|
149
|
-
units = {}
|
150
|
-
@units.each_pair do |dim, (unit, mult)|
|
151
|
-
units[dim] = [unit, -mult]
|
152
|
-
end
|
153
|
-
Measure.new(1.0/@magnitude, units)
|
154
|
-
end
|
155
|
-
|
156
|
-
def -@
|
157
|
-
Measure.new(-@magnitude, units)
|
158
|
-
end
|
159
|
-
|
160
|
-
def in(other, mode=:absolute)
|
161
|
-
other = Measure.new(1.0, other) unless other.kind_of?(Measure)
|
162
|
-
other = other.base
|
163
|
-
this = self.base
|
164
|
-
dims = this.units.keys | other.units.keys
|
165
|
-
mag = this.magnitude/other.magnitude
|
166
|
-
dims.each do |dim|
|
167
|
-
if !this.units[dim] || !other.units[dim] ||
|
168
|
-
(this.units[dim].last != other.units[dim].last)
|
169
|
-
raise "Inconsistent units #{Units.units_descr(this.units)} #{Units.units_descr(other.units)}"
|
170
|
-
end
|
171
|
-
this_u, mult = this.units[dim]
|
172
|
-
other_u = other.units[dim].first
|
173
|
-
mag *= Units.conversion_factor(this_u, other_u)**mult
|
174
|
-
end
|
175
|
-
if mode!=:relative && dims.size==1 && this.units[dims.first].last==1
|
176
|
-
# consider "level" conversion for biased units (otherwise consider interval or difference values)
|
177
|
-
mag += Units.conversion_bias(this.units[dims.first].first, other.units[dims.first].first)
|
178
|
-
end
|
179
|
-
mag
|
180
|
-
end
|
181
|
-
|
182
|
-
def to(units, mode=:absolute)
|
183
|
-
units = units.u if units.kind_of?(Measure)
|
184
|
-
Measure.new self.in(units, mode), units
|
185
|
-
end
|
186
|
-
|
187
|
-
def si_units
|
188
|
-
units = {}
|
189
|
-
@units.each_pair do |dim, (unit, mult)|
|
190
|
-
si_unit = SI_UNITS[dim]
|
191
|
-
if si_unit.kind_of?(Measure)
|
192
|
-
si_unit.units.each_pair do |d, (u,m)|
|
193
|
-
self.class.combine(units, d, u, m*mult)
|
194
|
-
end
|
195
|
-
else
|
196
|
-
self.class.combine(units, dim, si_unit, mult)
|
197
|
-
end
|
198
|
-
end
|
199
|
-
units
|
200
|
-
end
|
201
|
-
|
202
|
-
def to_si
|
203
|
-
to(si_units)
|
204
|
-
end
|
205
|
-
|
206
|
-
def coerce(other)
|
207
|
-
[Measure.new(other, {}), self]
|
208
|
-
end
|
209
|
-
|
210
|
-
def u # dimension? unit? only_units? strip_units? units_measure?
|
211
|
-
Measure.new(1.0, self.units)
|
212
|
-
end
|
213
|
-
|
214
|
-
# dimension (quantity)
|
215
|
-
def dimension
|
216
|
-
q = nil
|
217
|
-
u = self.base.si_units
|
218
|
-
SI_UNITS.each_pair do |dim, unit|
|
219
|
-
unit = Measure.new(1.0, unit) unless unit.kind_of?(Measure)
|
220
|
-
if unit.base.si_units == u
|
221
|
-
q = dim
|
222
|
-
break
|
223
|
-
end
|
224
|
-
end
|
225
|
-
q
|
226
|
-
end
|
227
|
-
|
228
|
-
def dimensionless?
|
229
|
-
base.units.reject{|d,(u,m)| m==0}.empty?
|
230
|
-
end
|
231
|
-
|
232
|
-
# less strict dimensionless condition (e.g. an angle is not a pure magnitude in this sense)
|
233
|
-
def magnitude?
|
234
|
-
self.units.reject{|d,(u,m)| m==0}.empty?
|
235
|
-
end
|
236
|
-
|
237
|
-
end # Measure
|
238
|
-
|
239
|
-
def Measure(*args)
|
240
|
-
if args.size==1
|
241
|
-
case args.first
|
242
|
-
when Numeric
|
243
|
-
m = args.first
|
244
|
-
u = {}
|
245
|
-
else
|
246
|
-
m = 1.0
|
247
|
-
u = args.first
|
248
|
-
end
|
249
|
-
args = [m,u]
|
250
|
-
end
|
251
|
-
Units::Measure.new(*args)
|
252
|
-
end
|
253
|
-
module_function :Measure
|
254
|
-
|
255
|
-
PREFIXES = {
|
256
|
-
:y=>[1E-24, 'yocto'],
|
257
|
-
:z=>[1E-21, 'zepto'],
|
258
|
-
:a=>[1E-18, 'atto'],
|
259
|
-
:f=>[1E-15, 'femto'],
|
260
|
-
:p=>[1E-12, 'pico'],
|
261
|
-
:n=>[1E-09, 'nano'],
|
262
|
-
:"\302\265"=>[1E-06, 'micro'], # ASCII alternative: u; define it?
|
263
|
-
:m=>[1E-03, 'milli'],
|
264
|
-
:c=>[1E-02, 'centi'],
|
265
|
-
:d=>[1E-01, 'deci'],
|
266
|
-
:da=>[1E1, 'deca'],
|
267
|
-
:h=>[1E02, 'hecto'],
|
268
|
-
:k=>[1E03, 'kilo'],
|
269
|
-
:M=>[1E06, 'mega'],
|
270
|
-
:G=>[1E09, 'giga'],
|
271
|
-
:T=>[1E12, 'tera'],
|
272
|
-
:P=>[1E15, 'peta'],
|
273
|
-
:E=>[1E18, 'exa'],
|
274
|
-
:Z=>[1E21, 'zetta'],
|
275
|
-
:Y=>[1E24, 'yotta']
|
276
|
-
}
|
277
|
-
|
278
|
-
def self.prefix_factor(prefix)
|
279
|
-
pd = PREFIXES[prefix.to_sym]
|
280
|
-
pd && pd.first
|
281
|
-
end
|
282
|
-
|
283
|
-
def self.prefix_name(prefix)
|
284
|
-
pd = PREFIXES[prefix.to_sym]
|
285
|
-
pd && pd.last
|
286
|
-
end
|
287
|
-
|
288
|
-
def self.prefix_factor_and_name(prefix)
|
289
|
-
PREFIXES[prefix]
|
290
|
-
end
|
291
|
-
|
292
|
-
# mathematical functions available to unit blocks are defined here
|
293
|
-
module Math
|
294
|
-
end
|
295
|
-
|
296
|
-
# unit methods and constants are defined here to be injected in units blocks
|
297
|
-
module System
|
298
|
-
|
299
|
-
extend Math
|
300
|
-
|
301
|
-
def self.define(unit)
|
302
|
-
define_var unit
|
303
|
-
PREFIXES.each do |prefix, (factor, name)|
|
304
|
-
define_var "#{prefix}#{unit}".to_sym
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
class <<self
|
309
|
-
private
|
310
|
-
def define_var(name)
|
311
|
-
name_initial = name.to_s[0,1]
|
312
|
-
if name_initial==name_initial.upcase && name_initial!=name_initial.downcase
|
313
|
-
# we could define a method with the requested name, but it would
|
314
|
-
# no be usable without qualification (System.X) or brackets (X())
|
315
|
-
# so we define a constant. But this requires Ruby 1.9 to be useful;
|
316
|
-
# otherwise the constant is not accesible without qualification in units blocks.
|
317
|
-
System.const_set name, Units.Measure(name)
|
318
|
-
end
|
319
|
-
self.class_eval do
|
320
|
-
define_method name do
|
321
|
-
Units.Measure(name)
|
322
|
-
end
|
323
|
-
module_function name
|
324
|
-
end
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
end # Units::System
|
329
|
-
|
330
|
-
def units(&blk)
|
331
|
-
Units::System.class_eval(&blk)
|
332
|
-
end
|
333
|
-
alias :u :units
|
334
|
-
module_function :units, :u
|
335
|
-
|
336
|
-
UnitDefinition = Struct.new(:dim, :factor, :name, :decomposition, :bias)
|
337
|
-
|
338
|
-
UNITS = {} # Hash.new{|h,k| h[k]=UnitDefinition.new()}
|
339
|
-
|
340
|
-
# get unit definition
|
341
|
-
def self.unit(unit_symbol)
|
342
|
-
ud = UNITS[unit_symbol]
|
343
|
-
if ud.nil?
|
344
|
-
factor = 1.0
|
345
|
-
if factor_name = PREFIXES[unit_symbol]
|
346
|
-
ud = UnitDefinition.new(nil, *factor_name)
|
347
|
-
else
|
348
|
-
u = unit_symbol.to_s
|
349
|
-
PREFIXES.each_pair do |prefix, (f,name)|
|
350
|
-
prefix = prefix.to_s
|
351
|
-
if u[0...prefix.length] == prefix
|
352
|
-
factor = f
|
353
|
-
ud = UNITS[u[prefix.length..-1].to_sym]
|
354
|
-
if ud
|
355
|
-
ud = ud.dup
|
356
|
-
ud.name = "#{name}#{ud.name}"
|
357
|
-
break
|
358
|
-
end
|
359
|
-
end
|
360
|
-
end
|
361
|
-
end
|
362
|
-
ud.factor *= factor if ud
|
363
|
-
ud.decomposition *= factor if ud && ud.decomposition
|
364
|
-
end
|
365
|
-
raise ArgumentError,"Invalid Units #{unit_symbol}" unless ud
|
366
|
-
ud
|
367
|
-
end
|
368
|
-
|
369
|
-
# Define new units.
|
370
|
-
# Define a base unit (with a factor for conversion to SI units)
|
371
|
-
# Units.define :unit_symbol, 'unit-name', :quantity, si_units_per_this_unit
|
372
|
-
# Define a unit in terms or another (valid for base or derived units)
|
373
|
-
# Units.define :unit_symbol, 'unit-name', value, :in_units
|
374
|
-
# Define a base unit as a measure-expression
|
375
|
-
# Units.define :unit_symbol, 'unit_name', u{...}
|
376
|
-
# Define a derived unit as a measure-expression
|
377
|
-
# Units.define :unit_symbol, 'unit_name', :quantity, u{...}
|
378
|
-
# For base dimensions the SI unit for a quantity must also be stablished with Unit.si_units;
|
379
|
-
# for derived units, SI units are automatically taken to be the first define unit of the quantity
|
380
|
-
# with unitary value in SI base units.
|
381
|
-
def self.define(unit_symbol, name, *args)
|
382
|
-
eqhivalence = nil
|
383
|
-
si_unit = false
|
384
|
-
bias = nil
|
385
|
-
if args.first.kind_of?(Symbol)
|
386
|
-
dim = args.shift
|
387
|
-
if args.first.kind_of?(Numeric)
|
388
|
-
# simple units
|
389
|
-
factor = args.shift
|
390
|
-
factor_units = args.shift
|
391
|
-
if factor_units
|
392
|
-
ud = unit(factor_units)
|
393
|
-
if ud.dim != dim
|
394
|
-
raise ArgumentError, "Inconsistent units #{factor_units} in definition of #{unit_symbol}"
|
395
|
-
end
|
396
|
-
# maybe it was not simple after all...
|
397
|
-
equivalence = factor*ud.decomposition if ud.decomposition
|
398
|
-
factor *= ud.factor
|
399
|
-
end
|
400
|
-
# si_unit = (factor==1.0) # to save si_units definitions # TODO: tolerance?
|
401
|
-
else
|
402
|
-
# compound unit
|
403
|
-
equivalence = args.shift
|
404
|
-
factor = equivalence.to_si.magnitude
|
405
|
-
si_unit = (factor==1.0) # TODO: tolerance?
|
406
|
-
if equivalence.units.empty?
|
407
|
-
# dimensionless compound dimension... (special case for angular units)
|
408
|
-
equivalence = nil
|
409
|
-
end
|
410
|
-
end
|
411
|
-
elsif args.first.kind_of?(Numeric)
|
412
|
-
# unit define in terms of other unit
|
413
|
-
factor = args.shift
|
414
|
-
factor_units = args.shift
|
415
|
-
u = unit(factor_units)
|
416
|
-
dim = u.dim
|
417
|
-
equivalence = factor*u.decomposition if u.decomposition
|
418
|
-
factor *= u.factor
|
419
|
-
bias = args.shift
|
420
|
-
else
|
421
|
-
# unit defined from a measure expression; the dimension must be already known or defined
|
422
|
-
# here (as as symbol preceding the expression).
|
423
|
-
definition = args.shift
|
424
|
-
dim = definition.dimension
|
425
|
-
raise ArgumentError,"To define a new compound unit a dimension must be specified" unless dim
|
426
|
-
equivalence = definition
|
427
|
-
factor = definition.to_si.magnitude
|
428
|
-
# si_unit = (factor==1.0) # to save si_units definitions # TODO: tolerance?
|
429
|
-
end
|
430
|
-
unit_def = UnitDefinition.new(dim, factor, name, equivalence, bias)
|
431
|
-
if UNITS.has_key?(unit_symbol)
|
432
|
-
raise "Redefinition of #{unit_symbol} as #{unit_def} (previously defined as #{UNITS[unit_symbol]})"
|
433
|
-
end
|
434
|
-
UNITS[unit_symbol] = unit_def
|
435
|
-
System.define unit_symbol
|
436
|
-
Units.si_units unit_def.dim, unit_symbol if si_unit && !SI_UNITS.has_key?(unit_def.dim)
|
437
|
-
end
|
438
|
-
|
439
|
-
SI_UNITS = {}
|
440
|
-
|
441
|
-
def self.si_units(dim, unit)
|
442
|
-
SI_UNITS[dim] = unit
|
443
|
-
end
|
444
|
-
|
445
|
-
def self.dimension(u)
|
446
|
-
unit(u).dim
|
447
|
-
end
|
448
|
-
|
449
|
-
def self.conversion_factor(from, to)
|
450
|
-
from_u = unit(from)
|
451
|
-
to_u = unit(to)
|
452
|
-
raise ArgumentError,"Inconsistent Units (#{from}, #{to})" if from_u.dim!=to_u.dim
|
453
|
-
from_u.factor/to_u.factor
|
454
|
-
end
|
455
|
-
|
456
|
-
def self.conversion_bias(from, to)
|
457
|
-
from_u = unit(from)
|
458
|
-
to_u = unit(to)
|
459
|
-
raise ArgumentError,"Inconsistent Units (#{from}, #{to})" if from_u.dim!=to_u.dim
|
460
|
-
factor = from_u.factor/to_u.factor
|
461
|
-
(from_u.bias||0)*factor - (to_u.bias||0)
|
462
|
-
end
|
463
|
-
|
464
|
-
# simple unit name
|
465
|
-
def self.unit_name(u)
|
466
|
-
uinfo = Units.unit(u)
|
467
|
-
uinfo && uinfo.name
|
468
|
-
end
|
469
|
-
|
470
|
-
def self.unit_descr(u, long=false, mult=1)
|
471
|
-
if long
|
472
|
-
u = unit_name(u)
|
473
|
-
if mult!=1
|
474
|
-
case mult
|
475
|
-
when 2
|
476
|
-
"squared #{u}"
|
477
|
-
when 3
|
478
|
-
"cubed #{u}"
|
479
|
-
else
|
480
|
-
"#{u} to the #{mult} power"
|
481
|
-
end
|
482
|
-
else
|
483
|
-
u
|
484
|
-
end
|
485
|
-
else
|
486
|
-
if mult!=1
|
487
|
-
"#{u}**#{mult}"
|
488
|
-
else
|
489
|
-
u.to_s
|
490
|
-
end
|
491
|
-
end
|
492
|
-
end
|
493
|
-
|
494
|
-
def self.units_descr(units, long=false)
|
495
|
-
units = units.values.sort_by{|u,m| -m}
|
496
|
-
pos_units = units.select{|u| u.last>0}
|
497
|
-
neg_units = units.select{|u| u.last<0}
|
498
|
-
times = long ? " " : "*"
|
499
|
-
num = pos_units.map{|u,m| unit_descr(u,long,m)}.join(times)
|
500
|
-
num = "(#{num})" if pos_units.size>1 && !neg_units.empty? && !long
|
501
|
-
den = neg_units.map{|u,m| unit_descr(u,long,-m)}.join("*")
|
502
|
-
den = "(#{den})" if neg_units.size>1 && !long
|
503
|
-
if pos_units.empty?
|
504
|
-
u_descr = "1/#{den}"
|
505
|
-
elsif neg_units.empty?
|
506
|
-
u_descr = num
|
507
|
-
else
|
508
|
-
connector = long ? " per " : "/"
|
509
|
-
u_descr = "#{num}#{connector}#{den}"
|
510
|
-
end
|
511
|
-
u_descr
|
512
|
-
end
|
513
|
-
|
514
|
-
module Math
|
515
|
-
|
516
|
-
[:sin, :cos, :tan].each do |fun|
|
517
|
-
define_method fun do |x|
|
518
|
-
x = Units.u{x.in(rad)} if x.kind_of?(Measure)
|
519
|
-
::Math.send(fun, x)
|
520
|
-
end
|
521
|
-
module_function fun
|
522
|
-
end
|
523
|
-
|
524
|
-
[:asin, :acos, :atan].each do |fun|
|
525
|
-
define_method fun do |x|
|
526
|
-
if x.kind_of?(Measure)
|
527
|
-
raise ArgumentError,"Invalid dimensions for #{fun} argument" unless x.dimensionless?
|
528
|
-
x = x.magnitude
|
529
|
-
end
|
530
|
-
Units.u{::Math.send(fun, x)*rad}
|
531
|
-
end
|
532
|
-
module_function fun
|
533
|
-
end
|
534
|
-
|
535
|
-
module_function
|
536
|
-
def atan2(x,y)
|
537
|
-
if x.kind_of?(Measure)
|
538
|
-
if y.kind_of?(Measure)
|
539
|
-
if x.base.to_si.units != y.base.to_si.units
|
540
|
-
raise ArgumentError,"Inconsistent units for atan2 arguments #{x.u}, #{y.u}"
|
541
|
-
end
|
542
|
-
# or x = x.to_si.magnitude, y=y.to_si.magnitude
|
543
|
-
y = y.in(x.units)
|
544
|
-
x = x.magnitude
|
545
|
-
else
|
546
|
-
raise ArgumentError,"Invalid dimensions for atan2 argument #{x.u}" unless x.dimensionless?
|
547
|
-
x = x.magnitude
|
548
|
-
end
|
549
|
-
elsif y.kind_of?(Measure)
|
550
|
-
raise ArgumentError,"Invalid dimensions for atan2 argument #{y.u}" unless y.dimensionless?
|
551
|
-
y = y.magnitude
|
552
|
-
end
|
553
|
-
Units.u{::Math.atan2(x,y)*rad}
|
554
|
-
end
|
555
|
-
|
556
|
-
end
|
557
|
-
|
558
|
-
|
559
11
|
# This must be included in any module or class from which units expressions
|
560
12
|
# are to be used in units or u blocks.
|
561
13
|
# It is not needed in Ruby 1.9.1 due to they way constant loop-up is done in that version,
|
@@ -571,101 +23,9 @@ module Units
|
|
571
23
|
end
|
572
24
|
end
|
573
25
|
end
|
574
|
-
|
575
|
-
include UseBlocks
|
576
|
-
|
577
|
-
# Units definitions
|
578
|
-
|
579
|
-
# declare SI base units
|
580
|
-
si_units :mass, :kg # m
|
581
|
-
si_units :length, :m # l
|
582
|
-
si_units :time, :s # t
|
583
|
-
si_units :electric_current, :A # I
|
584
|
-
si_units :temperature, :K # T
|
585
|
-
si_units :luminous_intensity, :cd # Iv
|
586
|
-
si_units :amount_of_substance, :mol # n
|
587
|
-
|
588
|
-
# define base units
|
589
|
-
define :g, 'gram', :mass, 1E-3
|
590
|
-
define :m, 'meter', :length, 1.0
|
591
|
-
define :s, 'second', :time, 1.0
|
592
|
-
define :A, 'ampere', :electric_current, 1.0
|
593
|
-
define :K, 'kelvin', :temperature, 1.0
|
594
|
-
define :cd,'candela',:luminous_intensity, 1.0
|
595
|
-
define :mol,'mole', :amount_of_substance, 1.0
|
596
|
-
|
597
|
-
# declare derived quantities with no named units
|
598
|
-
si_units :speed, u{m/s}
|
599
|
-
si_units :acceleration, u{m/s**2}
|
600
|
-
si_units :area, u{m**2}
|
601
|
-
si_units :volume, u{m**3}
|
602
|
-
|
603
|
-
# derived quantities with named units
|
604
|
-
define :W, 'Watt', :power, u{kg*m**2/s**3} # J/s
|
605
|
-
define :Hz, 'herz', :frequency, u{1/s}
|
606
|
-
define :N, 'newton', :force, u{m*kg/s**2}
|
607
|
-
define :Pa, 'pascal', :pressure, u{N/m**2}
|
608
|
-
define :J, 'joule', :energy, u{N*m}
|
609
|
-
define :C, 'coulomb', :electric_charge, u{s*A}
|
610
|
-
define :V, 'volt', :voltage, u{W/A}
|
611
|
-
define :F, 'farad', :electric_capacitance, u{C/V}
|
612
|
-
define :Ω, 'ohm', :electric_resistance, u{V/A} # ohm: Ω omega: Ω
|
613
|
-
define :S, 'siemens', :electric_condctance, u{1/Ω}
|
614
|
-
define :Wb, 'weber', :magnetic_flux, u{J/A}
|
615
|
-
define :T, 'tesla', :magnetic_field, u{N/(A*m)}
|
616
|
-
define :H, 'henry', :inductance, u{Wb/A}
|
617
|
-
define :rad, 'radian', :angle, u{m/m}
|
618
|
-
define :sr, 'steradian', :solid_angle, u{m**2/m**2}
|
619
|
-
define :lm, 'lumen', :luminous_flux, u{cd*sr}
|
620
|
-
define :lx, 'lux', :illuminance, u{lm/m**2}
|
621
|
-
define :Bq, 'bequerel', :radioactivity, u{1/s}
|
622
|
-
define :Gy, 'gray', :absorbed_dose, u{J/kg}
|
623
|
-
define :Sv, 'sievert', :equivalent_dose, u{J/kg}
|
624
|
-
define :kat, 'katal', :catalytic_activity, u{mol/s}
|
625
|
-
|
626
|
-
# Other units
|
627
|
-
|
628
|
-
define :min, 'minute', 60, :s
|
629
|
-
define :h, 'hour', 60, :min
|
630
|
-
define :d, 'day', 24, :h
|
631
|
-
define :mi, 'mile', 1.609344, :km
|
632
|
-
define :in, 'inch', 2.54, :cm
|
633
|
-
define :ft, 'foot', 0.3048, :m
|
634
|
-
define :inch, 'inch', 1, :in # alternative to avoid having to use self.in (in is a Ruby keyword)
|
635
|
-
define :lb, 'pound', 0.45359237, :kg
|
636
26
|
|
637
|
-
|
638
|
-
define :°F, 'degree Fahrenheit', 5.0/9.0, :K, +459.67
|
639
|
-
define :R, 'rankine', 5.0/9.0, :K
|
640
|
-
|
641
|
-
define :l, 'litre', u{dm**3}
|
642
|
-
define :L, 'litre', 1, :l
|
643
|
-
|
644
|
-
define :°, 'degree', ::Math::PI/180.0, :rad
|
645
|
-
define :′, 'arc-minute', ::Math::PI/180.0/60.0, :rad
|
646
|
-
define :″, 'arc-second', ::Math::PI/180.0/3600.0, :rad
|
647
|
-
# not so cool, but easier to type alternatives:
|
648
|
-
define :deg, 'degree', 1, :°
|
649
|
-
define :arcmin, 'arc-minute', 1, :′
|
650
|
-
define :arcsec, 'arc-second', 1, :″
|
651
|
-
|
652
|
-
define :g0, 'standard gravity', u{9.80665*m/s**2}
|
653
|
-
|
654
|
-
define :bar, 'bar', 1E5, :Pa
|
655
|
-
define :atm, 'atmosphere', 101325.0, :Pa
|
656
|
-
define :mWC, 'meters of water column', u{1E3*kg*g0/m**2}
|
657
|
-
define :Torr, 'torricelli', u{atm/760}
|
658
|
-
define :mHg, 'mHg', u{13.5951E3*kg*g0/m**2}
|
659
|
-
|
660
|
-
# define :kp, 'kilopond', :force, u{kg*g0} # or define pond?
|
661
|
-
define :gf, 'gram-force', u{g*g0} # kilopond kp = kgf
|
662
|
-
define :lbf, 'pound-force', u{lb*g0}
|
663
|
-
|
664
|
-
define :dyn, 'dyne', 10, :µN # u{1*g*cm/s**2}
|
665
|
-
define :galUS, 'U.S. liquid gallon', u{231*self.in**3}
|
666
|
-
define :galUK, 'Imperial gallon', 4.546092, :l
|
667
|
-
define :hp, 'horsepower', u{550*ft*lbf/s}
|
668
|
-
|
669
|
-
define :psi, 'pounds-force per square inch', u{lbf/self.in**2}
|
27
|
+
include UseBlocks
|
670
28
|
|
671
29
|
end # Units
|
30
|
+
|
31
|
+
require 'units/definitions'
|