class Phys::Unit
Phys::Unit
is a class to represent Physical Units of measurement. This class is used in the Phys::Quantity
for calculations with Units, and users do not always need to know its mechanism. It has Factor and Dimension:
-
Factor of the unit is a scale factor relative to its base unit.
Phys::Unit["km"].factor #=> 1000
-
Dimension of the unit is a hash table containing base units and dimensions as key-value pairs.
Phys::Unit["N"].dimension #=> {"kg"=>1, "m"=>1, "s"=>-2}
@example
require "phys/units" Q = Phys::Quantity U = Phys::Unit U["miles"] / U["hr"] #=> #<Phys::Unit 0.44704,{"m"=>1, "s"=>-1}> U["hr"] + U["30 min"] #=> #<Phys::Unit 5400,{"s"=>1}> U["(m/s)"]**2 #=> #<Phys::Unit 1,{"m"=>2, "s"=>-2}> U["m/s"] === Q[1,'miles/hr'] #=> true case Q[1,"miles/hr"] when U["m"] "length" when U["s"] "time" when U["m/s"] "velocity" else "other" end #=> "velocity"
@see Quantity
Constants
- LIST
Hash table of registered units.
- PREFIX
Hash table of registered prefixes.
- VERSION
Attributes
@visibility private
@visibility private
Public Class Methods
Force the argument to be Phys::Unit
. @param [Object] x @return [Phys::Unit] @raise [TypeError] if invalid type for units.
# File lib/phys/units/unit_class.rb, line 57 def cast(x) case x when Unit x else Unit.new(x) end end
@visibility private
# File lib/phys/units/unit_class.rb, line 131 def control_units_dat(var,skip,line) case line when /!\s*end(\w+)/ skip.delete($1) when /!\s*set\s+(\w+)\s+(\w+)/ if skip.empty? var[$1] ||= $2 end when /!var\s+(\w+)\s+(\w+)/ if var[$1] != $2 skip << 'var' end when /!\s*(\w+)(?:\s+(\w+))?/ code = $1 param = $2 #puts " code=#{code} param=#{param}" if (var[code]) ? (param && var[code]!=param) : !param skip << code end end #puts line #puts "skip=#{skip.inspect} var=#{var.inspect}" end
@visibility private
# File lib/phys/units/unit_class.rb, line 17 def debug false end
Define a new Unit
. Expression is parsed lazily, i.e., parsed not when this method is called, but when @factor and @dim is used. Note that the result of unit calculation depends on the timing of unit definition. @param [String,Symbol] name Name of the unit. @param [String] expr Expression of the unit.
# File lib/phys/units/unit_class.rb, line 26 def define(name,expr) case name when String when Symbol name = name.to_s else raise TypeError,"Unit name must be String or Symbol: #{name.inspect}" end if /^(.*)-$/ =~ name name = $1 if PREFIX[name] warn "prefix definition is overwritten: #{name}" if debug end PREFIX[name] = self.new(expr,name) else if LIST[name] warn "unit definition is overwritten: #{name}" if debug end if expr.kind_of?(String) && /^!/ =~ expr LIST[name] = BaseUnit.new(expr,name) else LIST[name] = self.new(expr,name) end end end
@visibility private
# File lib/phys/units/unit_class.rb, line 115 def find_prefix(x) Unit.prefix_regex =~ x pre,post = $1,$2 if pre and post and stem = (LIST[post] || unit_stem(post)) Unit.new(stem * PREFIX[pre].factor, x) end end
Searches a registered unit. @param [String,Symbol,Numeric,Unit,Quantity,NilClass] x @return [Phys::Unit, NilClass]
# File lib/phys/units/unit_class.rb, line 86 def find_unit(x) case x when String,Symbol x = x.to_s.strip if x=='' Unit.new(1) else LIST[x] || PREFIX[x] || find_prefix(x) || unit_stem(x) end when Numeric Unit.new(x) when NilClass Unit.new(1) when Unit x when Quantity x.unit else raise TypeError, "Invalid argument: #{x.inspect}" end end
@visibility private
# File lib/phys/units/unit.rb, line 528 def self.func(fn, x) m = Unit.new(x).to_numeric if fn == 'log2' && RUBY_VERSION<'1.9.0' return Unit.new( Math.log(m)/Math.log(2) ) end fn = 'log' if fn == 'ln' Unit.new( Math.send(fn,m) ) end
Import Units.dat from text. @param [String] data Text string of Units.dat. @param [String] locale (optional) Set “en_GB” for UK units.
# File lib/phys/units/unit_class.rb, line 158 def import_units(data,locale=nil) str = "" locale ||= ENV['LC_ALL'] || ENV['LANG'] if /^(\w+)\./ =~ locale locale = $1 end var = {'locale'=>locale,'utf8'=>true} case ENV['UNITS_ENGLISH'] when /US|GB/ var['UNITS_ENGLISH'] = ENV['UNITS_ENGLISH'] end skip = [] data.each_line do |line| line.chomp! if /^!/ =~ line control_units_dat(var,skip,line) next end next if !skip.empty? if /([^#]*)\s*#?/ =~ line line = $1 end if /(.*)\\$/ =~ line str.concat $1+" " next else str.concat line end if /^([^\s()\[\]{}!*|\/^#]+)\s+([^#]+)/ =~ str Unit.define($1,$2.strip) elsif !str.strip.empty? puts "unrecognized definition: '#{str}'" if debug end str = "" end x = PREFIX.keys.sort{|a,b| s = b.size-a.size (s==0) ? (a<=>b) : s }.join("|") @@prefix_regex = /^(#{x})(.+)$/ if debug LIST.dup.each do |k,v| if v.kind_of? Unit begin v.use_dimension rescue puts "!! no definition: #{v.inspect} !!" end end p [k,v] end end puts "#{LIST.size} units, #{PREFIX.size} prefixes" if debug end
Initialize a new unit. @overload initialize(factor,dimension=nil)
@param [Numeric] factor Unit scale factor. @param [Hash] dimension Dimension hash.
@overload initialize(expr,name=nil)
@param [String] expr Expression of the unit. It is parsed lazily, i.e., parsed not when this instance is created, but when @factor and @dim is used. @param [String] name Name of the unit.
@overload initialize(unit,name=nil)
@param [Phys::Unit] unit Its contents is used for new unit. @param [String] name Name of the unit.
@raise [TypeError] if invalid arg types.
# File lib/phys/units/unit.rb, line 70 def initialize(a1,a2=nil) case a1 when Numeric a1 = Rational(a1) if Integer===a1 @factor = a1 alloc_dim(a2) when Phys::Unit @factor = a1.factor alloc_dim a1.dim @name = a2.strip if a2 when String @expr = a1.strip @name = a2.strip if a2 else raise TypeError,"Invalid argument: #{a1.inspect}" end end
Searches a registered unit and then parse as a unit string if not registered. @param [String,Symbol,Numeric,Unit,Quantity,NilClass] x @return [Phys::Unit]
# File lib/phys/units/unit_class.rb, line 76 def parse(x) find_unit(x) || Unit.cast(Parse.new.parse(x)) rescue UnitError,Racc::ParseError => e raise UnitError,e.to_s.sub(/^\s+/,"") end
Regex for registered prefixes. @return [Regexp]
# File lib/phys/units/unit.rb, line 53 def self.prefix_regex @@prefix_regex end
@visibility private
# File lib/phys/units/unit_class.rb, line 126 def unit_exclude_chars '\\s*+\\/<=>()\\[\\]^{|}~\\\\' end
@visibility private
# File lib/phys/units/unit_class.rb, line 109 def unit_stem(x) ( /(.{2,}(?:s|z|ch))es$/ =~ x && LIST[$1] ) || ( /(.{2,})s$/ =~ x && LIST[$1] ) end
Used in Parser. @visibility private
# File lib/phys/units/unit_class.rb, line 68 def word(x) find_unit(x) or raise UnitError, "Undefined unit: #{x.inspect}" end
Public Instance Methods
Multiplication of units. Both units must be operable. @param [Phys::Unit, Numeric] x other unit @return [Phys::Unit] @raise [Phys::UnitError] if not operable.
# File lib/phys/units/unit.rb, line 473 def *(x) x = Unit.cast(x) if scalar? return x elsif x.scalar? return self.dup end check_operable2(x) dims = dimension_binop(x){|a,b| a+b} factor = self.factor * x.factor Unit.new(factor,dims) end
Exponentiation of units. This units must be operable. @param [Numeric] x numeric @return [Phys::Unit] @raise [Phys::UnitError] if not operable.
# File lib/phys/units/unit.rb, line 520 def **(x) check_operable m = Utils.as_numeric(x) dims = dimension_uop{|a| a*m} Unit.new(@factor**m,dims) end
Addition of units. Both units must be operable and conversion-allowed. @param [Phys::Unit, Numeric] x other unit @return [Phys::Unit] @raise [Phys::UnitError] if unit conversion is failed.
# File lib/phys/units/unit.rb, line 432 def +(x) x = Unit.cast(x) check_operable2(x) assert_same_dimension(x) Unit.new(@factor+x.factor,@dim.dup) end
Unary plus. Returns self. @return [Phys::Unit]
# File lib/phys/units/unit.rb, line 464 def +@ self end
Subtraction of units. Both units must be operable and conversion-allowed. @param [Phys::Unit, Numeric] x other unit @return [Phys::Unit] @raise [Phys::UnitError] if not conformable unit conversion is failed.
# File lib/phys/units/unit.rb, line 444 def -(x) x = Unit.cast(x) check_operable2(x) assert_same_dimension(x) Unit.new(@factor-x.factor,@dim.dup) end
Unary minus. This unit must be operable. @return [Phys::Unit] @raise [Phys::UnitError] if not operable.
# File lib/phys/units/unit.rb, line 455 def -@ check_operable use_dimension Unit.new(-@factor,@dim.dup) end
Division of units. Both units must be operable. @param [Phys::Unit, Numeric] x other unit @return [Phys::Unit] @raise [Phys::UnitError] if not operable.
# File lib/phys/units/unit.rb, line 491 def /(x) x = Unit.cast(x) if scalar? return x.inverse elsif x.scalar? return self.dup end check_operable2(x) dims = dimension_binop(x){|a,b| a-b} factor = self.factor / x.factor Unit.new(factor,dims) end
Equality of units @param [Object] x other unit or object @return [Boolean]
# File lib/phys/units/unit.rb, line 540 def ==(x) case x when Numeric x = Unit.cast(x) when Unit else return false end use_dimension @factor == x.factor && @dim == x.dim && offset == x.offset && dimension_value == x.dimension_value end
Comformability of units. Returns true if unit conversion between self
and x
is possible. @param [Object] x other object (Unit
or Quantity
or Numeric
or something else) @return [Boolean]
# File lib/phys/units/unit.rb, line 284 def ===(x) case x when Unit dimensionless_deleted == x.dimensionless_deleted when Quantity dimensionless_deleted == x.unit.dimensionless_deleted when Numeric dimensionless? else false end end
(internal use) @visibility private
# File lib/phys/units/unit.rb, line 130 def alloc_dim(hash=nil) case hash when Hash @dim = hash.dup else @dim = {} end @dim.default = 0 end
(internal use) @visibility private @raise [UnitError] if not dimensionless.
# File lib/phys/units/unit.rb, line 266 def assert_dimensionless if !dimensionless? raise UnitError,"Not dimensionless: #{self.inspect}" end end
(internal use) @visibility private @raise [UnitError] if different dimensions.
# File lib/phys/units/unit.rb, line 275 def assert_same_dimension(unit) if !same_dimension?(unit) raise UnitError,"Different dimension: #{self.inspect} and #{unit.inspect}" end end
Returns Base Unit
excluding dimensionless-units. @return [Phys::Unit]
# File lib/phys/units/unit.rb, line 355 def base_unit Unit.new(1,dimensionless_deleted) end
Raise error if this unit is not operable. @return [nil] @visibility private
# File lib/phys/units/unit.rb, line 370 def check_operable if !operable? raise UnitError,"non-operable for #{inspect}" end nil end
Raise error if this unit or argument is not operable. @return [nil] @visibility private
# File lib/phys/units/unit.rb, line 380 def check_operable2(unit) if !(operable? && unit.operable?) raise UnitError,"non-operable: #{inspect} and #{unit.inspect}" end nil end
Coerce. @return [Array]
# File lib/phys/units/unit.rb, line 555 def coerce(x) [Unit.find_unit(x), self] end
Conversion Factor to base unit, including dimension-value. @return [Numeric] @example
Phys::Unit["deg"].dimension #=> {"pi"=>1, "radian"=>1} Phys::Unit["deg"].factor #=> (1/180) Phys::Unit["deg"].conversion_factor #=> 0.017453292519943295
@see factor
# File lib/phys/units/unit.rb, line 219 def conversion_factor use_dimension f = @factor @dim.each do |k,d| if d != 0 u = LIST[k] if u.dimensionless? f *= u.dimension_value**d end end end f end
Convert a quantity to this unit. @param [Phys::Quantity] quantity to be converted. @return [Phys::Quantity] @raise [UnitError] if unit conversion is failed.
# File lib/phys/units/unit.rb, line 304 def convert(quantity) if Quantity===quantity assert_same_dimension(quantity.unit) v = quantity.unit.convert_value_to_base_unit(quantity.value) convert_value_from_base_unit(v) else quantity / to_numeric end end
Convert a quantity to this unit only in scale. @param [Phys::Quantity] quantity to be converted. @return [Phys::Quantity] @raise [UnitError] if unit conversion is failed.
# File lib/phys/units/unit.rb, line 318 def convert_scale(quantity) if Quantity===quantity assert_same_dimension(quantity.unit) v = quantity.value * quantity.unit.conversion_factor v = v / self.conversion_factor else quantity / to_numeric end end
Convert from a value in base unit to a value in this unit. @param [Numeric] value @return [Numeric]
# File lib/phys/units/unit.rb, line 338 def convert_value_from_base_unit(value) value / conversion_factor end
Convert from a value in this unit to a value in base unit. @param [Numeric] value @return [Numeric]
# File lib/phys/units/unit.rb, line 331 def convert_value_to_base_unit(value) value * conversion_factor end
Dimension hash. @example
Phys::Unit["N"].dimension #=> {"kg"=>1, "m"=>1, "s"=>-2}
@return [Hash]
# File lib/phys/units/unit.rb, line 103 def dimension use_dimension @dim end
(internal use) @visibility private
# File lib/phys/units/unit.rb, line 389 def dimension_binop(other) x = self.dim y = other.dim if Hash===x if Hash===y keys = x.keys | y.keys dims = {} dims.default = 0 keys.each do |k| v = yield( x[k]||0, y[k]||0 ) dims[k] = v if v!=0 end dims else x.dup end else raise "dimension not defined" end end
(internal use) @visibility private
# File lib/phys/units/unit.rb, line 412 def dimension_uop x = self.dim if Hash===x dims = {} dims.default = 0 x.each do |k,d| v = yield( d ) dims[k] = v if v!=0 end dims else raise "dimension not defined" end end
Dimension value. Always returns 1 unless BaseUnit
. @return [Numeric] @see BaseUnit#dimension_value
# File lib/phys/units/unit.rb, line 124 def dimension_value 1 end
(internal use)
# File lib/phys/units/unit.rb, line 251 def dimensionless? use_dimension @dim.each_key.all?{|k| LIST[k].dimensionless?} end
(internal use) @visibility private
# File lib/phys/units/unit.rb, line 244 def dimensionless_deleted use_dimension hash = @dim.dup hash.delete_if{|k,v| LIST[k].dimensionless?} end
Expression of the unit. @return [String, NilClass]
# File lib/phys/units/unit.rb, line 90 def expr @expr || @name || string_form end
Scale factor excluding the dimension-value. @return [Numeric] @example
Phys::Unit["deg"].dimension #=> {"pi"=>1, "radian"=>1} Phys::Unit["deg"].factor #=> (1/180) Phys::Unit["deg"].conversion_factor #=> 0.017453292519943295
@see conversion_factor
# File lib/phys/units/unit.rb, line 116 def factor use_dimension @factor end
Inspect string. @return [String] @example
Phys::Unit["N"].inspect #=> '#<Phys::Unit 1,{"kg"=>1, "m"=>1, "s"=>-2},@expr="newton">'
# File lib/phys/units/unit.rb, line 173 def inspect use_dimension a = [Utils.num_inspect(@factor), @dim.inspect] a << "@name="+@name.inspect if @name a << "@expr="+@expr.inspect if @expr a << "@offset="+@offset.inspect if @offset a << "@dimensionless=true" if @dimensionless if @dimension_value && @dimension_value!=1 a << "@dimension_value="+@dimension_value.inspect end s = a.join(",") "#<#{self.class} #{s}>" end
Inverse of units. This unit must be operable. @param [Phys::Unit, Numeric] unit @return [Phys::Unit] @raise [Phys::UnitError] if not operable.
# File lib/phys/units/unit.rb, line 509 def inverse check_operable dims = dimension_uop{|a| -a} Unit.new(Rational(1,self.factor), dims) end
Return true if this unit is operable. @return [Boolean]
# File lib/phys/units/unit.rb, line 363 def operable? true end
(internal use) @visibility private
# File lib/phys/units/unit.rb, line 258 def same_dimension?(unit) dimensionless_deleted == unit.dimensionless_deleted end
Returns true if scalar unit. Scalar means this unit does not have any dimension including dimensionless-units, and its factor is one. @return [Boolean]
# File lib/phys/units/unit.rb, line 237 def scalar? use_dimension (@dim.nil? || @dim.empty?) && @factor==1 end
Returns numeric value of this unit, i.e. conversion factor. Raises UnitError
if not dimensionless. @return [Numeric] @raise [UnitError] if not dimensionless.
# File lib/phys/units/unit.rb, line 346 def to_numeric assert_dimensionless conversion_factor end
Returns self name or unit_string
@return [String]
# File lib/phys/units/unit.rb, line 208 def to_s @name || unit_string end
Make a string of this unit expressed in base units. @return [String] @example
Phys::Unit["psi"].string_form #=> "(8896443230521/129032)*1e-04 kg m^-1 s^-2"
# File lib/phys/units/unit.rb, line 191 def unit_string use_dimension a = [] a << Utils.num_inspect(@factor) if @factor!=1 a += @dim.map do |k,d| if d==1 k else "#{k}^#{d}" end end a.join(" ") end
(internal use) Parse
@expr string if it has not been parsed yet. This function must be called before access to @dim or @factor. @return [nil] @raise [UnitError] if unit parse error. @visibility private
# File lib/phys/units/unit.rb, line 146 def use_dimension return if @dim && @factor if @expr && @dim.nil? #puts "unit='#{@name}', parsing '#{@expr}'..." if Unit.debug unit = Unit.parse(@expr) case unit when Unit @dim = unit.dim @factor = unit.factor if @dim.nil? || @factor.nil? raise UnitError,"parse error : #{unit.inspect}" end when Numeric @factor = unit alloc_dim else raise UnitError,"parse error : #{self.inspect}" end else raise UnitError,"undefined unit?: #{self.inspect}" end end