phys-units 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,185 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # phys/units/unit_class.rb
4
+ #
5
+ # Copyright (c) 2001-2013 Masahiro Tanaka <masa16.tanaka@gmail.com>
6
+ #
7
+ # This program is free software.
8
+ # You can distribute/modify this program under the terms of
9
+ # the GNU General Public License version 3 or later.
10
+
11
+ module Phys
12
+
13
+ class Unit
14
+
15
+ class UnitError < StandardError; end
16
+ class UnitParseError < UnitError; end
17
+ class UnitConversionError < UnitError; end
18
+ class UnitOperationError < UnitError; end
19
+
20
+ class << self
21
+
22
+ def debug
23
+ false
24
+ end
25
+
26
+ def define(name,expr,v=nil)
27
+ if !(String===name)
28
+ raise TypeError,"unit name should be string : #{name.inspect}"
29
+ end
30
+ if /^(.*)-$/ =~ name
31
+ name = $1
32
+ if PREFIX[name]
33
+ warn "prefix definition is overwritten: #{name}" if debug
34
+ end
35
+ PREFIX[name] = self.new(name,expr)
36
+ else
37
+ if LIST[name]
38
+ warn "unit definition is overwritten: #{name}" if debug
39
+ end
40
+ if expr.kind_of?(String) && /^!/ =~ expr
41
+ dimless = (expr == "!dimensionless")
42
+ LIST[name] = BaseUnit.new(name,dimless,v)
43
+ else
44
+ LIST[name] = self.new(name,expr,v)
45
+ end
46
+ end
47
+ end
48
+
49
+ def cast(x)
50
+ if x.kind_of?(Unit)
51
+ x
52
+ else
53
+ Unit.new(x)
54
+ end
55
+ end
56
+
57
+ def word(x)
58
+ find_unit(x) || define(x)
59
+ end
60
+
61
+ def parse(x)
62
+ find_unit(x) || Parse.new.parse(x)
63
+ end
64
+
65
+ def find_unit(x)
66
+ if Numeric===x
67
+ Unit.new(x)
68
+ elsif x=='' || x.nil?
69
+ Unit.new(1)
70
+ else
71
+ x = x.to_s
72
+ LIST[x] || PREFIX[x] || find_prefix(x) || unit_stem(x)
73
+ end
74
+ end
75
+
76
+ alias [] find_unit
77
+
78
+ def unit_stem(x)
79
+ ( /(.{3,}(?:s|z|ch))es$/ =~ x && LIST[$1] ) ||
80
+ ( /(.{3,})s$/ =~ x && LIST[$1] )
81
+ end
82
+
83
+ def find_prefix(x)
84
+ Unit.prefix_regex =~ x
85
+ pre,post = $1,$2
86
+ if pre and pre and stem = (LIST[post] || unit_stem(post))
87
+ PREFIX[pre] * stem
88
+ end
89
+ end
90
+
91
+ #--
92
+
93
+ def unit_chars
94
+ '\\s*+\\/0-9<=>()\\[\\]^{|}~\\\\'
95
+ end
96
+
97
+ def control_units_dat(var,skip,line)
98
+ case line
99
+ when /!\s*end(\w+)/
100
+ skip.delete($1)
101
+ when /!\s*set\s+(\w+)\s+(\w+)/
102
+ if skip.empty?
103
+ var[$1] ||= $2
104
+ end
105
+ when /!var\s+(\w+)\s+(\w+)/
106
+ if var[$1] != $2
107
+ skip << 'var'
108
+ end
109
+ when /!\s*(\w+)(?:\s+(\w+))?/
110
+ code = $1
111
+ param = $2
112
+ #puts " code=#{code} param=#{param}"
113
+ if (var[code]) ? (param && var[code]!=param) : !param
114
+ skip << code
115
+ end
116
+ end
117
+ #puts line
118
+ #puts "skip=#{skip.inspect} var=#{var.inspect}"
119
+ end
120
+
121
+ def import_units(data=nil,locale=nil)
122
+ str = ""
123
+ locale ||= ENV['LC_ALL'] || ENV['LANG']
124
+ if /^(\w+)\./ =~ locale
125
+ locale = $1
126
+ end
127
+ var = {'locale'=>locale,'utf8'=>true}
128
+ case ENV['UNITS_ENGLISH']
129
+ when /US|GB/
130
+ var['UNITS_ENGLISH'] = ENV['UNITS_ENGLISH']
131
+ end
132
+ skip = []
133
+
134
+ data.each_line do |line|
135
+ line.chomp!
136
+ if /^!/ =~ line
137
+ control_units_dat(var,skip,line)
138
+ next
139
+ end
140
+ next if !skip.empty?
141
+
142
+ if /([^#]*)\s*#?/ =~ line
143
+ line = $1
144
+ end
145
+
146
+ if /(.*)\\$/ =~ line
147
+ str.concat $1+" "
148
+ next
149
+ else
150
+ str.concat line
151
+ end
152
+
153
+ if /^([^\s()\[\]{}!*|\/^#]+)\s+([^#]+)/ =~ str
154
+ name,repr = $1,$2.strip
155
+ Unit.define(name,repr)
156
+ elsif !str.strip.empty?
157
+ puts "unrecognized definition: '#{str}'" if debug
158
+ end
159
+ str = ""
160
+ end
161
+
162
+ x = PREFIX.keys.sort{|a,b|
163
+ s = b.size-a.size
164
+ (s==0) ? (a<=>b) : s
165
+ }.join("|")
166
+ @@prefix_regex = /^(#{x})(.+)$/
167
+
168
+ if debug
169
+ LIST.dup.each do |k,v|
170
+ if v.kind_of? Unit
171
+ begin
172
+ v.use_dimension
173
+ rescue
174
+ puts "!! no definition: #{v.inspect} !!"
175
+ end
176
+ end
177
+ p [k,v]
178
+ end
179
+ end
180
+ puts "#{LIST.size} units, #{PREFIX.size} prefixes" if debug
181
+ end
182
+
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,91 @@
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
+ class Unit
12
+ module Utils
13
+ module_function
14
+
15
+ def as_numeric(x)
16
+ case x
17
+ when Rational
18
+ if x.denominator==1
19
+ x.to_i
20
+ else
21
+ x
22
+ end
23
+ when Numeric
24
+ x
25
+ when Unit
26
+ x.to_num
27
+ else
28
+ raise "Not Numric or #{self.class}: #{x.inspect}"
29
+ end
30
+ end
31
+
32
+ def check_decimal(x)
33
+ while x%5==0
34
+ x/=5
35
+ end
36
+ while x%2==0
37
+ x/=2
38
+ end
39
+ x==1
40
+ end
41
+
42
+ def int_inspect(x)
43
+ if x.to_s.size > 5
44
+ "%g" % x.to_f
45
+ else
46
+ x.inspect
47
+ end
48
+ end
49
+
50
+ def n_trail_zero(x)
51
+ s = x.to_s
52
+ if /^([+-]?\d*[1-9])(0*)$/ =~ s
53
+ [$1.to_i, $2.size]
54
+ else
55
+ raise "cannot match with: '#{s}'"
56
+ end
57
+ end
58
+
59
+ def num_inspect(x)
60
+ if x.kind_of? Rational
61
+ d = x.denominator
62
+ n = x.numerator
63
+ if d==1
64
+ return int_inspect(n)
65
+ end
66
+ if check_decimal(d)
67
+ return x.to_f.inspect
68
+ end
69
+ if check_decimal(n)
70
+ if n==1
71
+ return "(1/"+int_inspect(d)+")"
72
+ else
73
+ return "(1/%s)" % Rational(d,n).to_f.inspect
74
+ end
75
+ end
76
+ ud,nd = n_trail_zero(d)
77
+ if nd > 3
78
+ return Rational(n,ud).inspect +
79
+ ("*%.0e"%10**(-nd))
80
+ end
81
+ un,nn = n_trail_zero(n)
82
+ if nn > 3
83
+ return Rational(un,d).inspect +
84
+ ("*%.0e"%10**(nn))
85
+ end
86
+ end
87
+ x.inspect
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,5 @@
1
+ module Phys
2
+ class Unit
3
+ VERSION = "0.9.0"
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'phys/units/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "phys-units"
8
+ spec.version = Phys::Unit::VERSION
9
+ spec.authors = ["Masahiro TANAKA"]
10
+ spec.email = ["masa16.tanaka@gmail.com"]
11
+ spec.description = %q{GNU Units-compatible library for Ruby, formarly 'Quanty' class library.}
12
+ spec.summary = %q{GNU Units-compatible library for Ruby, formarly 'Quanty' class library.}
13
+ spec.homepage = "https://github.com/masa16/phys-units"
14
+ spec.license = "GPL"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+ require "phys/units"
3
+ U = Phys::Unit
4
+ Q = Phys::Quantity
@@ -0,0 +1,111 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+ require "helper"
3
+
4
+ describe "Phys::Quantity" do
5
+
6
+ context "Dimensionless" do
7
+ describe Q[1] do
8
+ it {should be_an_instance_of Phys::Quantity}
9
+ its(:value) {should == 1}
10
+ its(:unit) {should be_an_instance_of Phys::Unit}
11
+ its(:unit) {should == U[1]}
12
+ end
13
+ describe Q[1,""] do
14
+ it {should be_an_instance_of Phys::Quantity}
15
+ its(:value) {should == 1}
16
+ its(:unit) {should be_an_instance_of Phys::Unit}
17
+ its(:unit) {should == U['']}
18
+ end
19
+ describe Q[1,""] do
20
+ before {@q=Q[1,""]}
21
+ it {@q.want(2).value.should == 0.5}
22
+ end
23
+ end
24
+
25
+ context "Length" do
26
+ describe Q[1,"km"] do
27
+ before { @q = Q[1,"km"] }
28
+ it {@q.want("m").value.should == 1000}
29
+ it {@q.want("cm").value.should == 100000}
30
+ end
31
+ describe Q[1,"au"] do
32
+ it {should == Q[149597870700,"m"]}
33
+ end
34
+ describe Q[1,"parsec"] do
35
+ it {should == Q[3.0856775814671916e+16,"m"]}
36
+ end
37
+ describe Q[1,"lightyear"] do
38
+ it {should == Q[9460730472580800,"m"]}
39
+ end
40
+ describe Q[1,"lightyear"].want(:m).value do
41
+ it {should == 9460730472580800}
42
+ end
43
+ describe Q[1,"inch"] do
44
+ it {should == Q[0.0254,"m"]}
45
+ end
46
+ describe Q[1,"feet"] do
47
+ it {should == Q[0.3048,"m"]}
48
+ end
49
+ describe Q[1,"mile"] do
50
+ it {should == Q[1609.344,"m"]}
51
+ end
52
+ end
53
+
54
+ context "Temperature" do
55
+ describe Q[1,"tempC"] - Q[1,"tempC"] do
56
+ it {should == Q[0,"tempC"]}
57
+ end
58
+ describe Q[50,"tempF"] + Q[10,"tempC"] do
59
+ it {should == Q[68,"tempF"]}
60
+ end
61
+ describe Q[0,"tempC"].want("tempF") do
62
+ its(:value) {should == 32}
63
+ end
64
+ describe Q[32,"tempF"].want("tempC") do
65
+ its(:value){should == 0}
66
+ end
67
+ describe 2 * Q[2,"tempF"] do
68
+ it {should == Q[4,"tempF"]}
69
+ end
70
+ describe Q[2.5,"tempC"] * 4 do
71
+ its(:value){should == 10}
72
+ end
73
+ describe Q[10.0,"tempC"] / 4 do
74
+ its(:value){should == 2.5}
75
+ end
76
+ describe "tempC*tempC" do
77
+ it {expect{Q[1,"tempC"]*Q[2,"tempC"]}.to raise_error}
78
+ end
79
+ describe "tempC*K" do
80
+ it {expect{Q[1,"tempC"]*Q[2,"K"]}.to raise_error}
81
+ end
82
+ describe "K*tempC" do
83
+ it {expect{Q[1,"K"]*Q[2,"tempC"]}.to raise_error}
84
+ end
85
+ describe "tempC**2" do
86
+ it {expect{Q[2,"tempC"]**2}.to raise_error}
87
+ end
88
+ describe "tempC/tempC" do
89
+ it {expect{Q[2,"tempC"]/Q[1,"tempC"]}.to raise_error}
90
+ end
91
+ end
92
+
93
+ context "Velocity" do
94
+ describe Q[36,"km/hour"] do
95
+ its(:to_base_unit){should == Q[10,"m/s"]}
96
+ end
97
+ describe Q[36,"km/hour"].want('m/s') do
98
+ its(:value){should == 10}
99
+ end
100
+ end
101
+
102
+ context "Radian" do
103
+ describe Q[1,"radian"].want("degree") do
104
+ its(:value){should be_within(1e-15).of Q[180,"1/pi"].to_f}
105
+ end
106
+ describe Math.sin(Q[30,"degree"].to_f) do
107
+ it{should be_within(1e-15).of 0.5 }
108
+ end
109
+ end
110
+
111
+ end
data/spec/unit_spec.rb ADDED
@@ -0,0 +1,234 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+ require "helper"
3
+
4
+ describe "Create Units" do
5
+
6
+ describe U[1] do
7
+ it {should be_an_instance_of Phys::Unit}
8
+ its(:factor) {should == 1}
9
+ its(:conversion_factor) {should == 1}
10
+ its(:name) {should be_nil}
11
+ its(:expr) {should be_nil}
12
+ its(:offset) {should be_nil}
13
+ its(:dimension) {should == {}}
14
+ its(:dimension_value) {should == 1}
15
+ its(:string_form) {should == ""}
16
+ it {should be_dimensionless}
17
+ it {should be_scalar}
18
+ it {should be_operable}
19
+ end
20
+
21
+ describe U[2] do
22
+ it {should be_an_instance_of Phys::Unit}
23
+ its(:factor) {should == 2}
24
+ its(:conversion_factor) {should == 2}
25
+ its(:name) {should be_nil}
26
+ its(:expr) {should be_nil}
27
+ its(:offset) {should be_nil}
28
+ its(:dimension) {should == {}}
29
+ its(:dimension_value) {should == 1}
30
+ its(:string_form) {should == "2"}
31
+ it {should be_dimensionless}
32
+ it {should_not be_scalar}
33
+ it {should be_operable}
34
+ end
35
+
36
+ describe U['pi'] do
37
+ it {should be_an_kind_of Phys::Unit}
38
+ its(:factor) {should == 1}
39
+ its(:conversion_factor) {should == Math::PI}
40
+ its(:name) {should == 'pi'}
41
+ its(:expr) {should be_nil}
42
+ its(:offset) {should be_nil}
43
+ its(:dimension) {should == {'pi'=>1}}
44
+ its(:dimension_value) {should == Math::PI}
45
+ its(:string_form) {should == "pi"}
46
+ it {should be_dimensionless}
47
+ it {should_not be_scalar}
48
+ it {should be_operable}
49
+ end
50
+
51
+ describe U['m'] do
52
+ it {should be_an_kind_of Phys::Unit}
53
+ its(:factor) {should == 1}
54
+ its(:conversion_factor) {should == 1}
55
+ its(:name) {should == 'm'}
56
+ its(:expr) {should be_nil}
57
+ its(:offset) {should be_nil}
58
+ its(:dimension) {should == {'m'=>1}}
59
+ its(:dimension_value) {should == 1}
60
+ its(:string_form) {should == "m"}
61
+ it {should_not be_dimensionless}
62
+ it {should_not be_scalar}
63
+ it {should be_operable}
64
+ end
65
+
66
+ describe U[:m] do
67
+ it {should == U["m"]}
68
+ end
69
+
70
+ describe U['miles'] do
71
+ it {should be_an_kind_of Phys::Unit}
72
+ its(:factor) {should == 1609.344}
73
+ its(:factor) {should be_an_instance_of Rational}
74
+ its(:conversion_factor) {should == 1609.344}
75
+ its(:name) {should == 'mile'}
76
+ its(:expr) {should == "5280 ft"}
77
+ its(:offset) {should be_nil}
78
+ its(:dimension) {should == {'m'=>1}}
79
+ its(:dimension_value) {should == 1}
80
+ its(:string_form) {should == "1609.344 m"}
81
+ it {should_not be_dimensionless}
82
+ it {should_not be_scalar}
83
+ it {should be_operable}
84
+ end
85
+
86
+ describe 1.609344*U['km'] do
87
+ it {should be_an_kind_of Phys::Unit}
88
+ it {should == Phys::Unit[:miles]}
89
+ its(:factor) {should == 1609.344}
90
+ its(:conversion_factor) {should == 1609.344}
91
+ its(:name) {should be_nil}
92
+ its(:expr) {should be_nil}
93
+ its(:offset) {should be_nil}
94
+ its(:dimension) {should == {'m'=>1}}
95
+ its(:dimension_value) {should == 1}
96
+ its(:string_form) {should == "1609.344 m"}
97
+ it {should_not be_dimensionless}
98
+ it {should_not be_scalar}
99
+ it {should be_operable}
100
+ end
101
+
102
+ describe U['g'] do
103
+ it {should be_an_instance_of Phys::Unit}
104
+ its(:factor) {should == Rational(1,1000)}
105
+ its(:conversion_factor) {should == Rational(1,1000)}
106
+ its(:name) {should == 'g'}
107
+ #its(:expr) {should == 'gram'}
108
+ its(:offset) {should be_nil}
109
+ its(:dimension) {should == {'kg'=>1}}
110
+ its(:dimension_value) {should == 1}
111
+ its(:string_form) {should == "0.001 kg"}
112
+ it {should_not be_dimensionless}
113
+ it {should_not be_scalar}
114
+ it {should be_operable}
115
+ end
116
+
117
+
118
+ describe U['h'] do
119
+ it {should be_an_instance_of Phys::Unit}
120
+ its(:factor) {should be_within(1e-16*1e-33).of 6.626069574766962e-34}
121
+ its(:conversion_factor) {should be_within(1e-16*1e-33).of 6.626069574766962e-34}
122
+ its(:name) {should == 'h'}
123
+ its(:expr) {should == "4.135667516e-15 eV s"}
124
+ its(:offset) {should be_nil}
125
+ its(:dimension) {should == {'kg'=>1,'m'=>2,'s'=>-1}}
126
+ its(:dimension_value) {should == 1}
127
+ its(:string_form) {should == "6.626069574766962e-34 s^-1 kg m^2"}
128
+ it {should_not be_dimensionless}
129
+ it {should_not be_scalar}
130
+ it {should be_operable}
131
+ end
132
+
133
+ describe U['e'] do
134
+ it {should be_an_instance_of Phys::Unit}
135
+ its(:factor) {should be_within(1e-15*1e-18).of 1.602176565e-19}
136
+ its(:conversion_factor) {should be_within(1e-15*1e-18).of 1.602176565e-19}
137
+ its(:name) {should == 'e'}
138
+ its(:expr) {should == "1.602176565e-19 C"}
139
+ its(:offset) {should be_nil}
140
+ its(:dimension) {should == {'A'=>1,'s'=>1}}
141
+ its(:dimension_value) {should == 1}
142
+ its(:string_form) {should == "1.602176565e-19 A s"}
143
+ it {should_not be_dimensionless}
144
+ it {should_not be_scalar}
145
+ it {should be_operable}
146
+ end
147
+
148
+ describe U.parse('123.5 s') do
149
+ it {should be_an_instance_of Phys::Unit}
150
+ its(:factor) {should == 123.5}
151
+ its(:conversion_factor) {should == 123.5}
152
+ its(:name) {should be_nil}
153
+ its(:expr) {should be_nil}
154
+ its(:offset) {should be_nil}
155
+ its(:dimension) {should == {'s'=>1}}
156
+ its(:dimension_value) {should == 1}
157
+ its(:string_form) {should == "123.5 s"}
158
+ it {should_not be_dimensionless}
159
+ it {should_not be_scalar}
160
+ it {should be_operable}
161
+ end
162
+
163
+ describe U['m']/U['s'] do
164
+ it {should be_an_instance_of Phys::Unit}
165
+ its(:factor) {should == 1}
166
+ its(:conversion_factor) {should == 1}
167
+ its(:name) {should be_nil}
168
+ its(:expr) {should be_nil}
169
+ its(:offset) {should be_nil}
170
+ its(:dimension) {should == {'m'=>1, 's'=>-1}}
171
+ its(:dimension_value) {should == 1}
172
+ its(:string_form) {should == "m s^-1"}
173
+ it {should_not be_dimensionless}
174
+ it {should_not be_scalar}
175
+ it {should be_operable}
176
+ end
177
+
178
+ describe U.parse('(m/s)**2') do
179
+ it {should be_an_instance_of Phys::Unit}
180
+ its(:factor) {should == 1}
181
+ its(:conversion_factor) {should == 1}
182
+ its(:name) {should be_nil}
183
+ its(:expr) {should be_nil}
184
+ its(:offset) {should be_nil}
185
+ its(:dimension) {should == {'m'=>2, 's'=>-2}}
186
+ its(:dimension_value) {should == 1}
187
+ its(:string_form) {should == "m^2 s^-2"}
188
+ it {should_not be_dimensionless}
189
+ it {should_not be_scalar}
190
+ it {should be_operable}
191
+ end
192
+
193
+ describe U.parse("3.6 km/hour") do
194
+ its(:string_form) {should == "m s^-1"}
195
+ end
196
+
197
+ describe U['tempC'] do
198
+ it {should be_an_instance_of Phys::OffsetUnit}
199
+ its(:factor) {should == 1}
200
+ its(:conversion_factor) {should == 1}
201
+ its(:name) {should == 'tempC'}
202
+ its(:expr) {should be_nil}
203
+ its(:offset) {should == 273.15}
204
+ its(:dimension) {should == {'K'=>1}}
205
+ its(:dimension_value) {should == 1}
206
+ its(:string_form) {should == "K"}
207
+ it {should_not be_dimensionless}
208
+ it {should_not be_scalar}
209
+ it {should_not be_operable}
210
+ end
211
+
212
+ describe U['tempF'] do
213
+ it {should be_an_instance_of Phys::OffsetUnit}
214
+ its(:factor) {should == Rational(5,9)}
215
+ its(:factor) {should be_an_instance_of Rational}
216
+ its(:conversion_factor) {should == Rational(5,9)}
217
+ its(:name) {should == 'tempF'}
218
+ its(:expr) {should be_nil}
219
+ its(:offset) {should == Rational(45967,180)}
220
+ its(:dimension) {should == {'K'=>1}}
221
+ its(:dimension_value) {should == 1}
222
+ its(:string_form) {should == "(1/1.8) K"}
223
+ it {should_not be_dimensionless}
224
+ it {should_not be_scalar}
225
+ it {should_not be_operable}
226
+ end
227
+
228
+ describe "temperature unit" do
229
+ it "operation error" do
230
+ expect {U['tempC']*2}.to raise_error(Phys::Unit::UnitOperationError)
231
+ end
232
+ end
233
+
234
+ end