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:

@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

name[R]

@visibility private

offset[R]

@visibility private

Public Class Methods

[](x)
Alias for: parse
cast(x) click to toggle source

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
control_units_dat(var,skip,line) click to toggle source

@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
debug() click to toggle source

@visibility private

# File lib/phys/units/unit_class.rb, line 17
def debug
  false
end
define(name,expr) click to toggle source

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
find_prefix(x) click to toggle source

@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
find_unit(x) click to toggle source

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
func(fn, x) click to toggle source

@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(data,locale=nil) click to toggle source

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
new(a1,a2=nil) click to toggle source

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
parse(x) click to toggle source

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
Also aliased as: []
prefix_regex() click to toggle source

Regex for registered prefixes. @return [Regexp]

# File lib/phys/units/unit.rb, line 53
def self.prefix_regex
  @@prefix_regex
end
unit_exclude_chars() click to toggle source

@visibility private

# File lib/phys/units/unit_class.rb, line 126
def unit_exclude_chars
  '\\s*+\\/<=>()\\[\\]^{|}~\\\\'
end
unit_stem(x) click to toggle source

@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
word(x) click to toggle source

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

*(x) click to toggle source

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
**(x) click to toggle source

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
+(x) click to toggle source

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
+@() click to toggle source

Unary plus. Returns self. @return [Phys::Unit]

# File lib/phys/units/unit.rb, line 464
def +@
  self
end
-(x) click to toggle source

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
-@() click to toggle source

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
/(x) click to toggle source

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
==(x) click to toggle source

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
===(x) click to toggle source

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
alloc_dim(hash=nil) click to toggle source

(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
assert_dimensionless() click to toggle source

(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
assert_same_dimension(unit) click to toggle source

(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
base_unit() click to toggle source

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
check_operable() click to toggle source

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
check_operable2(unit) click to toggle source

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(x) click to toggle source

Coerce. @return [Array]

# File lib/phys/units/unit.rb, line 555
def coerce(x)
  [Unit.find_unit(x), self]
end
compatible?(x)
Alias for: ===
conformable?(x)
Alias for: ===
conversion_allowed?(x)
Alias for: ===
conversion_factor() click to toggle source

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(quantity) click to toggle source

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_scale(quantity) click to toggle source

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_value_from_base_unit(value) click to toggle source

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_value_to_base_unit(value) click to toggle source

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
dim()
Alias for: dimension
dimension() click to toggle source

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
Also aliased as: dim
dimension_binop(other) { |x||0, y||0| ... } click to toggle source

(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
dimension_uop() { |d| ... } click to toggle source

(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() click to toggle source

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
dimensionless?() click to toggle source

(internal use)

# File lib/phys/units/unit.rb, line 251
def dimensionless?
  use_dimension
  @dim.each_key.all?{|k| LIST[k].dimensionless?}
end
dimensionless_deleted() click to toggle source

(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
expr() click to toggle source

Expression of the unit. @return [String, NilClass]

# File lib/phys/units/unit.rb, line 90
def expr
  @expr || @name || string_form
end
factor() click to toggle source

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() click to toggle source

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() click to toggle source

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
operable?() click to toggle source

Return true if this unit is operable. @return [Boolean]

# File lib/phys/units/unit.rb, line 363
def operable?
  true
end
same_dim?(unit)
Alias for: same_dimension?
same_dimension?(unit) click to toggle source

(internal use) @visibility private

# File lib/phys/units/unit.rb, line 258
def same_dimension?(unit)
  dimensionless_deleted == unit.dimensionless_deleted
end
Also aliased as: same_dim?
scalar?() click to toggle source

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
string_form()
Alias for: unit_string
to_num()
Alias for: to_numeric
to_numeric() click to toggle source

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
Also aliased as: to_num
to_s() click to toggle source

Returns self name or unit_string @return [String]

# File lib/phys/units/unit.rb, line 208
def to_s
  @name || unit_string
end
unit_string() click to toggle source

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
Also aliased as: string_form
use_dimension() click to toggle source

(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