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