phys-units 0.9.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.
- checksums.yaml +7 -0
- data/.gitignore +30 -0
- data/COPYING +674 -0
- data/Gemfile +4 -0
- data/README.md +51 -0
- data/Rakefile +1 -0
- data/lib/phys/units.rb +7 -0
- data/lib/phys/units/Makefile +37 -0
- data/lib/phys/units/load_units.rb +5406 -0
- data/lib/phys/units/mixin.rb +12 -0
- data/lib/phys/units/parse.rb +777 -0
- data/lib/phys/units/parse.y +123 -0
- data/lib/phys/units/quantity.rb +252 -0
- data/lib/phys/units/unit.rb +446 -0
- data/lib/phys/units/unit_class.rb +185 -0
- data/lib/phys/units/utils.rb +91 -0
- data/lib/phys/units/version.rb +5 -0
- data/phys-units.gemspec +23 -0
- data/spec/helper.rb +4 -0
- data/spec/quantity_spec.rb +111 -0
- data/spec/unit_spec.rb +234 -0
- data/spec/utils_spec.rb +16 -0
- metadata +97 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
# parse.y
|
2
|
+
#
|
3
|
+
# Copyright (c) 2001-2013 Masahiro Tanaka <masa16.tanaka@gmail.com>
|
4
|
+
#
|
5
|
+
# This program is free software.
|
6
|
+
# You can distribute/modify this program under the terms of
|
7
|
+
# the GNU General Public License version 3 or later.
|
8
|
+
|
9
|
+
class Parse
|
10
|
+
|
11
|
+
prechigh
|
12
|
+
left '|'
|
13
|
+
right POW
|
14
|
+
nonassoc '(' WORD NUMBER UFUNC
|
15
|
+
left '*' MULTIPLY
|
16
|
+
left DIV
|
17
|
+
left UNARY
|
18
|
+
left '+' '-'
|
19
|
+
preclow
|
20
|
+
|
21
|
+
rule
|
22
|
+
|
23
|
+
target: expr
|
24
|
+
| DIV list { result = Unit.inv(val[1]) }
|
25
|
+
;
|
26
|
+
|
27
|
+
expr: list
|
28
|
+
| '-' list = UNARY { result = -val[1] }
|
29
|
+
| expr '+' expr { result = val[0] + val[2] }
|
30
|
+
| expr '-' expr { result = val[0] - val[2] }
|
31
|
+
| expr '*' expr { result = val[0] * val[2] }
|
32
|
+
| expr DIV expr { result = val[0] / val[2] }
|
33
|
+
;
|
34
|
+
|
35
|
+
numexpr: NUMBER
|
36
|
+
| numexpr '|' numexpr { result = Unit.rdiv(val[0],val[2]) }
|
37
|
+
;
|
38
|
+
|
39
|
+
pexpr: '(' expr ')' { result = val[1] }
|
40
|
+
;
|
41
|
+
|
42
|
+
list: numexpr
|
43
|
+
| pexpr
|
44
|
+
| WORD { result = Unit.word(val[0]) }
|
45
|
+
| list list = MULTIPLY { result = val[0] * val[1] }
|
46
|
+
| list POW list { result = val[0]** val[2] }
|
47
|
+
| list POW '-' list = POW { result = val[0]**(-val[3]) }
|
48
|
+
| UFUNC pexpr { result = Unit.func(val[0],val[1]) }
|
49
|
+
;
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
---- header ----
|
54
|
+
|
55
|
+
# -*- coding: utf-8 -*-
|
56
|
+
# parse.y, parse.rb
|
57
|
+
#
|
58
|
+
# by Masahiro Tanaka <masa16.tanaka@gmail.com>
|
59
|
+
#
|
60
|
+
module Phys
|
61
|
+
class Unit
|
62
|
+
---- inner ----
|
63
|
+
|
64
|
+
def build_num(ov,ud,pw)
|
65
|
+
if ud.nil? && pw.nil?
|
66
|
+
#ov.to_i
|
67
|
+
Rational(ov.to_i)
|
68
|
+
else
|
69
|
+
m1 = ud ? ud.size : 0
|
70
|
+
pw = pw ? pw.to_i : 0
|
71
|
+
m2 = pw-m1
|
72
|
+
ov = ov ? ov.to_i : 0
|
73
|
+
ud = ud ? ud.to_i : 0
|
74
|
+
a = ov*10**m1 + ud
|
75
|
+
b = 1
|
76
|
+
a *= 10**m2 if m2>0
|
77
|
+
b *= 10**(-m2) if m2<0
|
78
|
+
Rational(a,b)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse( str )
|
83
|
+
return Unit.new(str) if str.empty?
|
84
|
+
#p str
|
85
|
+
@q = []
|
86
|
+
|
87
|
+
c = Unit.unit_chars
|
88
|
+
|
89
|
+
while str.size > 0 do
|
90
|
+
case str
|
91
|
+
when /\A[\s]+/o
|
92
|
+
when /\A(\d+)(?:(?:\.(\d*))?(?:[eE]([+-]?\d+))?)/o
|
93
|
+
@q.push [:NUMBER, build_num($1,$2,$3)]
|
94
|
+
when /\A(sin|cos|tan|log|ln|log2)\b/o
|
95
|
+
@q.push [:UFUNC, $&]
|
96
|
+
when /\A\//o
|
97
|
+
@q.push [:DIV, $&]
|
98
|
+
when /\Aper\b/o
|
99
|
+
@q.push [:DIV, $&]
|
100
|
+
when /\A[^#{c}0-9,.-]+([^#{c}$-]*[^#{c}1-9,.])?/o
|
101
|
+
@q.push [:WORD, $&]
|
102
|
+
when /\A[%'"]'?/o
|
103
|
+
@q.push [:WORD, $&]
|
104
|
+
when /\A\^|\A\*\*/o
|
105
|
+
@q.push [:POW, $&]
|
106
|
+
when /\A./o
|
107
|
+
@q.push [$&,$&]
|
108
|
+
end
|
109
|
+
str = $' #'
|
110
|
+
end
|
111
|
+
@q.push [false, '$end']
|
112
|
+
|
113
|
+
do_parse
|
114
|
+
end
|
115
|
+
|
116
|
+
def next_token
|
117
|
+
@q.shift
|
118
|
+
end
|
119
|
+
|
120
|
+
---- footer ----
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
#
|
2
|
+
# phys/units/quantity.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2001-2013 Masahiro Tanaka <masa16.tanaka@gmail.com>
|
5
|
+
#
|
6
|
+
# This program is free software.
|
7
|
+
# You can distribute/modify this program under the terms of
|
8
|
+
# the GNU General Public License version 3 or later.
|
9
|
+
|
10
|
+
module Phys
|
11
|
+
|
12
|
+
def Quantity(*a)
|
13
|
+
Quantity.new(*a)
|
14
|
+
end
|
15
|
+
|
16
|
+
#== Usage
|
17
|
+
# require 'phys/units'
|
18
|
+
# Q=Phys::Quantity
|
19
|
+
# Q[1.23,'km'] + Q[4.56,'m'] #=> Phys::Quanty[1.23456,'km']
|
20
|
+
# Q[123,'mile'] / Q[2,'hr'] #=> Phys::Quanty[61,'mile/hr']
|
21
|
+
# Q[61,'miles/hr'].want('m/s') #=> Phys::Quanty[27.26944,'m/s']
|
22
|
+
# Q[1.0,'are'] == Q[10,'m']**2 #=> true
|
23
|
+
# Q[70,'tempF'] + Q[10,'tempC'] #=> Phys::Quantity[88,'tempF']
|
24
|
+
# Q[20,'tempC'].want('tempF') #=> Phys::Quantity[68,'tempF']
|
25
|
+
# Math.cos(Q[60,'degree'].to_f) #=> 0.5
|
26
|
+
class Quantity
|
27
|
+
|
28
|
+
class << self
|
29
|
+
# Same as Quantity.new.
|
30
|
+
def [](*a)
|
31
|
+
self.new(*a)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Initialize a new quantity.
|
36
|
+
# _value_: Numeric value of quantity.
|
37
|
+
# _expr_: Unit string. Result of Unit.parse(_expr_) is used as a unit.
|
38
|
+
# _unit_: (optional) Unit of quantity instead of parsing _expr_.
|
39
|
+
def initialize(value,expr=nil,unit=nil)
|
40
|
+
@val = value
|
41
|
+
expr = expr.to_s if Symbol===expr
|
42
|
+
@expr = (expr=='') ? nil : expr
|
43
|
+
@unit = unit
|
44
|
+
if @unit.nil?
|
45
|
+
@unit = Unit.parse(@expr||1)
|
46
|
+
elsif !@unit.kind_of? Unit
|
47
|
+
raise ArgumentError, "third arg must be Phys::Unit"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :val
|
52
|
+
attr_reader :expr
|
53
|
+
attr_reader :unit
|
54
|
+
|
55
|
+
# Returns the value of the quantity.
|
56
|
+
alias value val
|
57
|
+
|
58
|
+
# Conversion to a quantity in another _expr_ unit.
|
59
|
+
def want(expr)
|
60
|
+
unit = Unit.parse(expr)
|
61
|
+
val = unit.convert(self)
|
62
|
+
self.class.new( val, expr, unit )
|
63
|
+
end
|
64
|
+
alias convert want
|
65
|
+
|
66
|
+
# Addition of two quantities.
|
67
|
+
# Operation is made after the unit of _other_ is
|
68
|
+
# converted to the unit of _self_.
|
69
|
+
# Exception is raised if unit conversion is failed.
|
70
|
+
# Returns an instance of Quantity class in the unit of former quantity.
|
71
|
+
def +(other)
|
72
|
+
val = @val + @unit.convert_scale(other)
|
73
|
+
self.class.new( val, @expr, @unit )
|
74
|
+
end
|
75
|
+
|
76
|
+
# Subtraction of two quantities.
|
77
|
+
# Operation is made after the unit of _other_ is
|
78
|
+
# converted to the unit of _self_.
|
79
|
+
# Exception is raised if unit conversion is failed.
|
80
|
+
# Returns an instance of Quantity class in the unit of former quantity.
|
81
|
+
def -(other)
|
82
|
+
val = @val - @unit.convert_scale(other)
|
83
|
+
self.class.new( val, @expr, @unit )
|
84
|
+
end
|
85
|
+
|
86
|
+
%w[abs ceil round floor truncate].each do |s|
|
87
|
+
define_method(s) do
|
88
|
+
self.class.new( @val.send(s), @expr, @unit )
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Unary Plus. Returns self.
|
93
|
+
def +@ ; self.class.new( @val, @expr, @unit ) end
|
94
|
+
|
95
|
+
# Unary Minus. Returns the negated quantity.
|
96
|
+
def -@ ; self.class.new( -@val, @expr, @unit ) end
|
97
|
+
|
98
|
+
# Comparison of quantities. Comparison is made after
|
99
|
+
# converting _other_ to a quantity in the unit of _self_.
|
100
|
+
def <=> (other); @val <=> @unit.convert(other) end
|
101
|
+
|
102
|
+
# Comparison of quantities. Comparison is made after
|
103
|
+
# converting _other_ to a quantity in the unit of _self_.
|
104
|
+
def == (other); @val == @unit.convert(other) end
|
105
|
+
|
106
|
+
# Comparison of quantities. Comparison is made after
|
107
|
+
# converting _other_ to a quantity in the unit of _self_.
|
108
|
+
def >= (other); @val >= @unit.convert(other) end
|
109
|
+
|
110
|
+
# Comparison of quantities. Comparison is made after
|
111
|
+
# converting _other_ to a quantity in the unit of _self_.
|
112
|
+
def <= (other); @val <= @unit.convert(other) end
|
113
|
+
|
114
|
+
# Comparison of quantities. Comparison is made after
|
115
|
+
# converting _other_ to a quantity in the unit of _self_.
|
116
|
+
def < (other); @val < @unit.convert(other) end
|
117
|
+
|
118
|
+
# Comparison of quantities. Comparison is made after
|
119
|
+
# converting _other_ to a quantity in the unit of _self_.
|
120
|
+
def > (other); @val > @unit.convert(other) end
|
121
|
+
|
122
|
+
# Power of a quantity.
|
123
|
+
# Returns an instance of Quantity class in a powerd unit.
|
124
|
+
def **(n)
|
125
|
+
if @expr.nil?
|
126
|
+
expr = nil
|
127
|
+
elsif /^[A-Za-z_]+&/o =~ @expr
|
128
|
+
expr = @expr+'^'+n.to_s
|
129
|
+
else
|
130
|
+
expr = '('+@expr+')^'+n.to_s+''
|
131
|
+
end
|
132
|
+
self.class.new( @val**n, expr, @unit**n )
|
133
|
+
end
|
134
|
+
|
135
|
+
def enclose_expr #:nodoc:
|
136
|
+
return nil if @expr.nil?
|
137
|
+
if /\/|\||per/o =~ @expr
|
138
|
+
'('+@expr+')'
|
139
|
+
else
|
140
|
+
@expr
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def enclose_expr_div #:nodoc:
|
145
|
+
return nil if @expr.nil?
|
146
|
+
if /\w[^\w]+\w/o =~ @expr
|
147
|
+
'/('+@expr+')'
|
148
|
+
else
|
149
|
+
'/'+@expr
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Multiplication of two quantities.
|
154
|
+
# Returns an instance of Quantity class in a multiplied unit.
|
155
|
+
def *(other)
|
156
|
+
if Quantity===other
|
157
|
+
a = [self.enclose_expr, other.enclose_expr]
|
158
|
+
a.delete(nil)
|
159
|
+
self.class.new( @val*other.val, a.join(' '), @unit*other.unit )
|
160
|
+
else
|
161
|
+
self.class.new( @val*other, @expr, @unit )
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Division of two quantities.
|
166
|
+
# Returns an instance of Quantity class in a divided unit.
|
167
|
+
%w[/ div quo].each do |s|
|
168
|
+
define_method(s) do |other|
|
169
|
+
if Quantity===other
|
170
|
+
a = [self.enclose_expr, other.enclose_expr_div]
|
171
|
+
a.delete(nil)
|
172
|
+
self.class.new( @val.send(s,other.val), a.join, @unit/other.unit )
|
173
|
+
else
|
174
|
+
self.class.new( @val.send(s,other), @expr, @unit )
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
alias fdiv quo
|
179
|
+
|
180
|
+
%w[% remainder].each do |s|
|
181
|
+
define_method(s) do |other|
|
182
|
+
other = (Quantity===other) ? other.val : other
|
183
|
+
self.class.new( @val.send(s,other), @expr, @unit )
|
184
|
+
end
|
185
|
+
end
|
186
|
+
alias modulo %
|
187
|
+
|
188
|
+
def coerce(other)
|
189
|
+
[ self.class.new(other), self ]
|
190
|
+
end
|
191
|
+
|
192
|
+
def abs
|
193
|
+
self.class.new( @val.abs, @expr, @unit )
|
194
|
+
end
|
195
|
+
|
196
|
+
def abs2
|
197
|
+
self**2
|
198
|
+
end
|
199
|
+
|
200
|
+
# Conversion to base unit.
|
201
|
+
# Returns the quantity converted to a base unit.
|
202
|
+
def to_base_unit
|
203
|
+
unit = @unit.base_unit
|
204
|
+
val = unit.convert(self)
|
205
|
+
expr = unit.unit_string
|
206
|
+
self.class.new( val, expr, unit )
|
207
|
+
end
|
208
|
+
alias to_si to_base_unit
|
209
|
+
alias to_SI to_base_unit
|
210
|
+
|
211
|
+
# Conversion to Numeric.
|
212
|
+
# Returns Numeric if the unit is dimensionless.
|
213
|
+
# Raises an Error if the unit is non-dminensionless.
|
214
|
+
def to_numeric
|
215
|
+
@unit.convert_to_numeric(@val)
|
216
|
+
end
|
217
|
+
|
218
|
+
def to_f
|
219
|
+
to_numeric.to_f
|
220
|
+
end
|
221
|
+
alias to_float to_f
|
222
|
+
|
223
|
+
def to_i
|
224
|
+
to_numeric.to_i
|
225
|
+
end
|
226
|
+
alias to_int to_i
|
227
|
+
alias to_integer to_i
|
228
|
+
|
229
|
+
def to_r
|
230
|
+
to_numeric.to_r
|
231
|
+
end
|
232
|
+
alias to_rational to_r
|
233
|
+
|
234
|
+
def to_s
|
235
|
+
if @expr
|
236
|
+
expr = ",'" +@expr+"'"
|
237
|
+
else
|
238
|
+
expr = ""
|
239
|
+
end
|
240
|
+
self.class.to_s+"["+Unit::Utils.num_inspect(@val)+expr+"]"
|
241
|
+
end
|
242
|
+
|
243
|
+
def inspect
|
244
|
+
if @expr
|
245
|
+
expr = "," +@expr.inspect
|
246
|
+
else
|
247
|
+
expr = ""
|
248
|
+
end
|
249
|
+
"#<"+self.class.to_s+" "+Unit::Utils.num_inspect(@val)+expr+", "+@unit.inspect+">"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
@@ -0,0 +1,446 @@
|
|
1
|
+
#
|
2
|
+
# phys/units/unit.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2001-2013 Masahiro Tanaka <masa16.tanaka@gmail.com>
|
5
|
+
#
|
6
|
+
# This program is free software.
|
7
|
+
# You can distribute/modify this program under the terms of
|
8
|
+
# the GNU General Public License version 3 or later.
|
9
|
+
|
10
|
+
module Phys
|
11
|
+
|
12
|
+
class Unit
|
13
|
+
|
14
|
+
LIST = {}
|
15
|
+
PREFIX = {}
|
16
|
+
|
17
|
+
def self.prefix_regex
|
18
|
+
@@prefix_regex
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(arg,expr=nil,offset=nil)
|
22
|
+
case arg
|
23
|
+
when Numeric
|
24
|
+
arg = Rational(arg) if Integer===arg
|
25
|
+
@factor = arg
|
26
|
+
alloc_dim(expr)
|
27
|
+
when Phys::Unit
|
28
|
+
replace(arg)
|
29
|
+
when String
|
30
|
+
@name = arg
|
31
|
+
if expr.kind_of? Phys::Unit
|
32
|
+
@factor = expr.factor
|
33
|
+
@offset = expr.offset
|
34
|
+
alloc_dim expr.dim
|
35
|
+
else
|
36
|
+
@expr = expr
|
37
|
+
end
|
38
|
+
else
|
39
|
+
raise TypeError,"invalid argument : #{arg.inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :name, :offset, :expr
|
44
|
+
|
45
|
+
def dim
|
46
|
+
use_dimension
|
47
|
+
@dim
|
48
|
+
end
|
49
|
+
alias dimension dim
|
50
|
+
|
51
|
+
def factor
|
52
|
+
use_dimension
|
53
|
+
@factor
|
54
|
+
end
|
55
|
+
|
56
|
+
def dimension_value
|
57
|
+
1
|
58
|
+
end
|
59
|
+
|
60
|
+
def replace(x)
|
61
|
+
@name = x.name.dup if x.name
|
62
|
+
@factor = x.factor
|
63
|
+
@offset = x.offset
|
64
|
+
alloc_dim x.dim
|
65
|
+
end
|
66
|
+
|
67
|
+
def alloc_dim(hash=nil)
|
68
|
+
case hash
|
69
|
+
when Hash
|
70
|
+
@dim = hash.dup
|
71
|
+
else
|
72
|
+
@dim = {}
|
73
|
+
end
|
74
|
+
@dim.default = 0
|
75
|
+
end
|
76
|
+
|
77
|
+
def use_dimension
|
78
|
+
return if @dim && @factor
|
79
|
+
if @expr && @dim.nil?
|
80
|
+
puts "unit='#{@name}', parsing '#{@expr}'..." if Unit.debug
|
81
|
+
unit = Unit.parse(@expr)
|
82
|
+
case unit
|
83
|
+
when Unit
|
84
|
+
@dim = unit.dim
|
85
|
+
@factor = unit.factor
|
86
|
+
if @dim.nil? || @factor.nil?
|
87
|
+
raise UnitParseError,"parse error : #{unit.inspect}"
|
88
|
+
end
|
89
|
+
when Numeric
|
90
|
+
@factor = unit
|
91
|
+
alloc_dim
|
92
|
+
else
|
93
|
+
raise UnitParseError,"parse error : #{self.inspect}"
|
94
|
+
end
|
95
|
+
else
|
96
|
+
raise UnitParseError,"undefined unit?: #{self.inspect}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def inspect
|
101
|
+
a = [Utils.num_inspect(@factor), @dim.inspect]
|
102
|
+
a << "@name="+@name.inspect if @name
|
103
|
+
a << "@expr="+@expr.inspect if @expr
|
104
|
+
a << "@offset="+@offset.inspect if @offset
|
105
|
+
a << "@dimensionless=true" if @dimensionless
|
106
|
+
if @dimension_value && @dimension_value!=1
|
107
|
+
a << "@dimension_value="+@dimension_value.inspect
|
108
|
+
end
|
109
|
+
s = a.join(",")
|
110
|
+
"#<#{self.class} #{s}>"
|
111
|
+
end
|
112
|
+
|
113
|
+
def unit_string
|
114
|
+
use_dimension
|
115
|
+
a = []
|
116
|
+
a << Utils.num_inspect(@factor) if @factor!=1
|
117
|
+
a += @dim.map do |k,d|
|
118
|
+
if d==1
|
119
|
+
k
|
120
|
+
else
|
121
|
+
"#{k}^#{d}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
a.join(" ")
|
125
|
+
end
|
126
|
+
alias string_form unit_string
|
127
|
+
|
128
|
+
# Unit conversion
|
129
|
+
|
130
|
+
def conversion_factor
|
131
|
+
use_dimension
|
132
|
+
f = @factor
|
133
|
+
@dim.each do |k,d|
|
134
|
+
if d != 0
|
135
|
+
u = LIST[k]
|
136
|
+
if u.dimensionless?
|
137
|
+
f *= u.dimension_value**d
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
f
|
142
|
+
end
|
143
|
+
|
144
|
+
def scalar?
|
145
|
+
use_dimension
|
146
|
+
(@dim.nil? || @dim.empty?) && @factor==1
|
147
|
+
end
|
148
|
+
|
149
|
+
def dimensionless_deleted
|
150
|
+
use_dimension
|
151
|
+
hash = @dim.dup
|
152
|
+
hash.delete_if{|k,v| LIST[k].dimensionless?}
|
153
|
+
end
|
154
|
+
|
155
|
+
def dimensionless?
|
156
|
+
use_dimension
|
157
|
+
@dim.each_key.all?{|k| LIST[k].dimensionless?}
|
158
|
+
end
|
159
|
+
|
160
|
+
def same_dimension?(x)
|
161
|
+
dimensionless_deleted == x.dimensionless_deleted
|
162
|
+
end
|
163
|
+
alias same_dim? same_dimension?
|
164
|
+
|
165
|
+
def assert_dimensionless
|
166
|
+
if !dimensionless?
|
167
|
+
raise UnitConversionError,"Not dimensionless: #{self.inspect}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def assert_same_dimension(x)
|
172
|
+
if !same_dimension?(x)
|
173
|
+
raise UnitConversionError,"Different dimension: #{self.inspect} and #{x.inspect}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def convert(q)
|
178
|
+
if Quantity===q
|
179
|
+
assert_same_dimension(q.unit)
|
180
|
+
v = q.unit.convert_to_base(q.value)
|
181
|
+
convert_from_base(v)
|
182
|
+
else
|
183
|
+
q / to_num
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def convert_scale(q)
|
188
|
+
convert(q)
|
189
|
+
end
|
190
|
+
|
191
|
+
def convert_to_base(x)
|
192
|
+
x * conversion_factor
|
193
|
+
end
|
194
|
+
|
195
|
+
def convert_from_base(x)
|
196
|
+
x / conversion_factor
|
197
|
+
end
|
198
|
+
|
199
|
+
def convert_to_numeric(x)
|
200
|
+
assert_dimensionless
|
201
|
+
x * conversion_factor
|
202
|
+
end
|
203
|
+
|
204
|
+
def to_num
|
205
|
+
assert_dimensionless
|
206
|
+
conversion_factor
|
207
|
+
end
|
208
|
+
|
209
|
+
def convert_to_float(x)
|
210
|
+
convert_to_numeric(x).to_f
|
211
|
+
end
|
212
|
+
|
213
|
+
def base_unit
|
214
|
+
Unit.new(1,dim)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Unit operation
|
218
|
+
|
219
|
+
def operable?
|
220
|
+
true
|
221
|
+
end
|
222
|
+
|
223
|
+
def check_operable
|
224
|
+
if !operable?
|
225
|
+
raise UnitOperationError,"non-operable for #{inspect}"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def check_operable2(x)
|
230
|
+
if !(operable? && x.operable?)
|
231
|
+
raise UnitOperationError,"non-operable: #{inspect} and #{x.inspect}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def dimension_binop(other)
|
236
|
+
x = self.dim
|
237
|
+
y = other.dim
|
238
|
+
if Hash===x
|
239
|
+
if Hash===y
|
240
|
+
keys = x.keys | y.keys
|
241
|
+
dims = {}
|
242
|
+
dims.default = 0
|
243
|
+
keys.each do |k|
|
244
|
+
v = yield( x[k]||0, y[k]||0 )
|
245
|
+
dims[k] = v if v!=0
|
246
|
+
end
|
247
|
+
dims
|
248
|
+
else
|
249
|
+
x.dup
|
250
|
+
end
|
251
|
+
else
|
252
|
+
raise "dimensin not defined"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def dimension_uop
|
257
|
+
x = self.dim
|
258
|
+
if Hash===x
|
259
|
+
dims = {}
|
260
|
+
dims.default = 0
|
261
|
+
x.each do |k,d|
|
262
|
+
v = yield( d )
|
263
|
+
dims[k] = v if v!=0
|
264
|
+
end
|
265
|
+
dims
|
266
|
+
else
|
267
|
+
raise "dimensin not defined"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def +(x)
|
272
|
+
x = Unit.cast(x)
|
273
|
+
check_operable2(x)
|
274
|
+
assert_same_dimension(x)
|
275
|
+
Unit.new(@factor+x.factor,@dim.dup)
|
276
|
+
end
|
277
|
+
|
278
|
+
def -(x)
|
279
|
+
x = Unit.cast(x)
|
280
|
+
check_operable2(x)
|
281
|
+
assert_same_dimension(x)
|
282
|
+
Unit.new(@factor-x.factor,@dim.dup)
|
283
|
+
end
|
284
|
+
|
285
|
+
def -@
|
286
|
+
check_operable
|
287
|
+
use_dimension
|
288
|
+
Unit.new(-@factor,@dim.dup)
|
289
|
+
end
|
290
|
+
|
291
|
+
def +@
|
292
|
+
self
|
293
|
+
end
|
294
|
+
|
295
|
+
def *(x)
|
296
|
+
y = Unit.cast(x)
|
297
|
+
if scalar?
|
298
|
+
return y
|
299
|
+
elsif y.scalar?
|
300
|
+
return self
|
301
|
+
end
|
302
|
+
check_operable2(y)
|
303
|
+
dims = dimension_binop(y){|a,b| a+b}
|
304
|
+
factor = self.factor * y.factor
|
305
|
+
Unit.new(factor,dims)
|
306
|
+
end
|
307
|
+
|
308
|
+
def /(x)
|
309
|
+
y = Unit.cast(x)
|
310
|
+
if scalar?
|
311
|
+
return y.inv
|
312
|
+
elsif y.scalar?
|
313
|
+
return self
|
314
|
+
end
|
315
|
+
check_operable2(y)
|
316
|
+
dims = dimension_binop(y){|a,b| a-b}
|
317
|
+
factor = self.factor / y.factor
|
318
|
+
Unit.new(factor,dims)
|
319
|
+
end
|
320
|
+
|
321
|
+
def rdiv(x)
|
322
|
+
y = Unit.cast(x)
|
323
|
+
if scalar?
|
324
|
+
return y.inv
|
325
|
+
elsif y.scalar?
|
326
|
+
return self
|
327
|
+
end
|
328
|
+
check_operable2(y)
|
329
|
+
dims = dimension_binop(y){|a,b| a-b}
|
330
|
+
factor = Rational(self.factor,x.factor)
|
331
|
+
Unit.new(factor,dims)
|
332
|
+
end
|
333
|
+
|
334
|
+
def self.rdiv(x,y)
|
335
|
+
Unit.cast(x).rdiv(y)
|
336
|
+
end
|
337
|
+
|
338
|
+
def inv
|
339
|
+
check_operable
|
340
|
+
dims = dimension_uop{|a| -a}
|
341
|
+
Unit.new(Rational(1,self.factor), dims)
|
342
|
+
end
|
343
|
+
|
344
|
+
def **(x)
|
345
|
+
check_operable
|
346
|
+
m = Utils.as_numeric(x)
|
347
|
+
dims = dimension_uop{|a| a*m}
|
348
|
+
Unit.new(@factor**m,dims)
|
349
|
+
end
|
350
|
+
|
351
|
+
def self.func(fn, x)
|
352
|
+
fn = 'log' if fn == 'ln'
|
353
|
+
m = Unit.new(x).to_num
|
354
|
+
Unit.new( Math.send(fn,m) )
|
355
|
+
end
|
356
|
+
|
357
|
+
def ==(x)
|
358
|
+
use_dimension
|
359
|
+
@factor == x.factor && @dim == x.dim &&
|
360
|
+
offset == x.offset && dimension_value == x.dimension_value
|
361
|
+
end
|
362
|
+
|
363
|
+
def coerce(x)
|
364
|
+
[Unit.find_unit(x), self]
|
365
|
+
end
|
366
|
+
|
367
|
+
end # Unit
|
368
|
+
|
369
|
+
|
370
|
+
class BaseUnit < Unit
|
371
|
+
def initialize(s,dimless=false,v=nil)
|
372
|
+
case s
|
373
|
+
when String
|
374
|
+
@name = s
|
375
|
+
@factor = 1
|
376
|
+
@dim = {s=>1}
|
377
|
+
@dim.default = 0
|
378
|
+
@dimensionless = dimless
|
379
|
+
@dimension_value = v || 1
|
380
|
+
else
|
381
|
+
raise ArgumentError "BaseUnit#initialize: arg must be string: #{s}"
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def replace(x)
|
386
|
+
super(x)
|
387
|
+
@dimensionless = x.dimensionless
|
388
|
+
@dimension_value = x.dimension_value
|
389
|
+
end
|
390
|
+
|
391
|
+
def use_dimension
|
392
|
+
end
|
393
|
+
|
394
|
+
def dimensionless?
|
395
|
+
@dimensionless
|
396
|
+
end
|
397
|
+
|
398
|
+
def dimensionless_deleted
|
399
|
+
if @dimensionless
|
400
|
+
{}
|
401
|
+
else
|
402
|
+
@dim.dup
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
attr_reader :dimension_value
|
407
|
+
end
|
408
|
+
|
409
|
+
|
410
|
+
class OffsetUnit < Unit
|
411
|
+
|
412
|
+
def self.define(name,unit,offset=nil)
|
413
|
+
LIST[name] = self.new(name,unit,offset)
|
414
|
+
end
|
415
|
+
|
416
|
+
def initialize(name,arg,offset)
|
417
|
+
super(name,arg)
|
418
|
+
@offset = offset
|
419
|
+
if offset.nil?
|
420
|
+
raise ArgumentError,"offset is not supplied"
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def convert_to_base(x)
|
425
|
+
x * conversion_factor + @offset
|
426
|
+
end
|
427
|
+
|
428
|
+
def convert_from_base(x)
|
429
|
+
(x - @offset) / conversion_factor
|
430
|
+
end
|
431
|
+
|
432
|
+
def convert_scale(q)
|
433
|
+
if Quantity===q
|
434
|
+
assert_same_dimension(q.unit)
|
435
|
+
v = q.value * q.unit.conversion_factor
|
436
|
+
v = v / self.conversion_factor
|
437
|
+
else
|
438
|
+
raise TypeError,"not Quantitiy: #{q.inspect}"
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def operable?
|
443
|
+
false
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|