Class: Phys::Unit

Inherits:
Object
  • Object
show all
Defined in:
lib/phys/units/unit.rb,
lib/phys/units/parse.rb,
lib/phys/units/utils.rb,
lib/phys/units/version.rb,
lib/phys/units/unit_class.rb

Overview

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}
    

Examples:

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 Also:

Direct Known Subclasses

BaseUnit, OffsetUnit

Constant Summary collapse

LIST =

Hash table of registered units.

{}
PREFIX =

Hash table of registered prefixes.

{}
VERSION =
"0.9.9"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(factor, dimension = nil) ⇒ Unit #initialize(expr, name = nil) ⇒ Unit #initialize(unit, name = nil) ⇒ Unit

Initialize a new unit.

Overloads:

  • #initialize(factor, dimension = nil) ⇒ Unit

    Parameters:

    • factor (Numeric)

      Unit scale factor.

    • dimension (Hash) (defaults to: nil)

      Dimension hash.

  • #initialize(expr, name = nil) ⇒ Unit

    Parameters:

    • expr (String)

      Expression of the unit. It is parsed lazily, i.e., parsed not when this instance is created, but when @factor and @dim is used.

    • name (String) (defaults to: nil)

      Name of the unit.

  • #initialize(unit, name = nil) ⇒ Unit

    Parameters:

    • unit (Phys::Unit)

      Its contents is used for new unit.

    • name (String) (defaults to: nil)

      Name of the unit.

Raises:

  • (TypeError)

    if invalid arg types.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 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

Instance Attribute Details

#exprString, NilClass (readonly)

Expression of the unit.

Returns:

  • (String, NilClass)


90
91
92
# File 'lib/phys/units/unit.rb', line 90

def expr
  @expr
end

Class Method Details

.cast(x) ⇒ Phys::Unit

Force the argument to be Phys::Unit.

Parameters:

  • x (Object)

Returns:

Raises:

  • (TypeError)

    if invalid type for units.



57
58
59
60
61
62
63
64
# File 'lib/phys/units/unit_class.rb', line 57

def cast(x)
  case x
  when Unit
    x
  else
    Unit.new(x)
  end
end

.define(name, expr) ⇒ Object

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.

Parameters:

  • name (String, Symbol)

    Name of the unit.

  • expr (String)

    Expression of the unit.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 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_unit(x) ⇒ Phys::Unit, NilClass

Searches a registered unit.

Parameters:

Returns:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# 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

.import_units(data, locale = nil) ⇒ Object

Import Units.dat from text.

Parameters:

  • data (String)

    Text string of Units.dat.

  • locale (String) (defaults to: nil)

    (optional) Set “en_GB” for UK units.



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# 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

.parse(x) ⇒ Phys::Unit Also known as: []

Searches a registered unit and then parse as a unit string if not registered.

Parameters:

Returns:



76
77
78
79
80
# 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

.prefix_regexRegexp

Regex for registered prefixes.

Returns:

  • (Regexp)


53
54
55
# File 'lib/phys/units/unit.rb', line 53

def self.prefix_regex
  @@prefix_regex
end

Instance Method Details

#*(x) ⇒ Phys::Unit

Multiplication of units. Both units must be operable.

Parameters:

Returns:

Raises:



471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/phys/units/unit.rb', line 471

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) ⇒ Phys::Unit

Exponentiation of units. This units must be operable.

Parameters:

  • x (Numeric)

    numeric

Returns:

Raises:



518
519
520
521
522
523
# File 'lib/phys/units/unit.rb', line 518

def **(x)
  check_operable
  m = Utils.as_numeric(x)
  dims = dimension_uop{|a| a*m}
  Unit.new(@factor**m,dims)
end

#+(x) ⇒ Phys::Unit

Addition of units. Both units must be operable and conversion-allowed.

Parameters:

Returns:

Raises:



430
431
432
433
434
435
# File 'lib/phys/units/unit.rb', line 430

def +(x)
  x = Unit.cast(x)
  check_operable2(x)
  assert_same_dimension(x)
  Unit.new(@factor+x.factor,@dim.dup)
end

#+@Phys::Unit

Unary plus. Returns self.

Returns:



462
463
464
# File 'lib/phys/units/unit.rb', line 462

def +@
  self
end

#-(x) ⇒ Phys::Unit

Subtraction of units. Both units must be operable and conversion-allowed.

Parameters:

Returns:

Raises:



442
443
444
445
446
447
# File 'lib/phys/units/unit.rb', line 442

def -(x)
  x = Unit.cast(x)
  check_operable2(x)
  assert_same_dimension(x)
  Unit.new(@factor-x.factor,@dim.dup)
end

#-@Phys::Unit

Unary minus. This unit must be operable.

Returns:

Raises:



453
454
455
456
457
# File 'lib/phys/units/unit.rb', line 453

def -@
  check_operable
  use_dimension
  Unit.new(-@factor,@dim.dup)
end

#/(x) ⇒ Phys::Unit

Division of units. Both units must be operable.

Parameters:

Returns:

Raises:



489
490
491
492
493
494
495
496
497
498
499
500
# File 'lib/phys/units/unit.rb', line 489

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) ⇒ Boolean

Equality of units

Parameters:

  • x (Object)

    other unit or object

Returns:

  • (Boolean)


538
539
540
541
542
543
544
545
546
547
548
549
# File 'lib/phys/units/unit.rb', line 538

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) ⇒ Boolean Also known as: conformable?, compatible?, conversion_allowed?

Comformability of units. Returns true if unit conversion between self and x is possible.

Parameters:

  • x (Object)

    other object (Unit or Quantity or Numeric or something else)

Returns:

  • (Boolean)


282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/phys/units/unit.rb', line 282

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

#base_unitPhys::Unit

Returns Base Unit excluding dimensionless-units.

Returns:



353
354
355
# File 'lib/phys/units/unit.rb', line 353

def base_unit
  Unit.new(1,dimensionless_deleted)
end

#coerce(x) ⇒ Array

Coerce.

Returns:

  • (Array)


553
554
555
# File 'lib/phys/units/unit.rb', line 553

def coerce(x)
  [Unit.find_unit(x), self]
end

#conversion_factorNumeric

Conversion Factor to base unit, including dimension-value.

Examples:

Phys::Unit["deg"].dimension         #=> {"pi"=>1, "radian"=>1}
Phys::Unit["deg"].factor            #=> (1/180)
Phys::Unit["deg"].conversion_factor #=> 0.017453292519943295

Returns:

  • (Numeric)

See Also:



217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/phys/units/unit.rb', line 217

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) ⇒ Phys::Quantity

Convert a quantity to this unit.

Parameters:

Returns:

Raises:

  • (UnitError)

    if unit conversion is failed.



302
303
304
305
306
307
308
309
310
# File 'lib/phys/units/unit.rb', line 302

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) ⇒ Phys::Quantity

Convert a quantity to this unit only in scale.

Parameters:

Returns:

Raises:

  • (UnitError)

    if unit conversion is failed.



316
317
318
319
320
321
322
323
324
# File 'lib/phys/units/unit.rb', line 316

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) ⇒ Numeric

Convert from a value in base unit to a value in this unit.

Parameters:

  • value (Numeric)

Returns:

  • (Numeric)


336
337
338
# File 'lib/phys/units/unit.rb', line 336

def convert_value_from_base_unit(value)
  value / conversion_factor
end

#convert_value_to_base_unit(value) ⇒ Numeric

Convert from a value in this unit to a value in base unit.

Parameters:

  • value (Numeric)

Returns:

  • (Numeric)


329
330
331
# File 'lib/phys/units/unit.rb', line 329

def convert_value_to_base_unit(value)
  value * conversion_factor
end

#dimensionHash Also known as: dim

Dimension hash.

Examples:

Phys::Unit["N"].dimension #=> {"kg"=>1, "m"=>1, "s"=>-2}

Returns:

  • (Hash)


101
102
103
104
# File 'lib/phys/units/unit.rb', line 101

def dimension
  use_dimension
  @dim
end

#dimension_valueNumeric

Dimension value. Always returns 1 unless BaseUnit.

Returns:

  • (Numeric)

See Also:



122
123
124
# File 'lib/phys/units/unit.rb', line 122

def dimension_value
  1
end

#dimensionless?Boolean

(internal use)

Returns:

  • (Boolean)


249
250
251
252
# File 'lib/phys/units/unit.rb', line 249

def dimensionless?
  use_dimension
  @dim.each_key.all?{|k| LIST[k].dimensionless?}
end

#factorNumeric

Scale factor excluding the dimension-value.

Examples:

Phys::Unit["deg"].dimension         #=> {"pi"=>1, "radian"=>1}
Phys::Unit["deg"].factor            #=> (1/180)
Phys::Unit["deg"].conversion_factor #=> 0.017453292519943295

Returns:

  • (Numeric)

See Also:



114
115
116
117
# File 'lib/phys/units/unit.rb', line 114

def factor
  use_dimension
  @factor
end

#inspectString

Inspect string.

Examples:

Phys::Unit["N"].inspect #=> '#<Phys::Unit 1,{"kg"=>1, "m"=>1, "s"=>-2},@expr="newton">'

Returns:

  • (String)


171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/phys/units/unit.rb', line 171

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

#inversePhys::Unit

Inverse of units. This unit must be operable.

Parameters:

Returns:

Raises:



507
508
509
510
511
# File 'lib/phys/units/unit.rb', line 507

def inverse
  check_operable
  dims = dimension_uop{|a| -a}
  Unit.new(Rational(1,self.factor), dims)
end

#operable?Boolean

Return true if this unit is operable.

Returns:

  • (Boolean)


361
362
363
# File 'lib/phys/units/unit.rb', line 361

def operable?
  true
end

#scalar?Boolean

Returns true if scalar unit. Scalar means this unit does not have any dimension including dimensionless-units, and its factor is one.

Returns:

  • (Boolean)


235
236
237
238
# File 'lib/phys/units/unit.rb', line 235

def scalar?
  use_dimension
  (@dim.nil? || @dim.empty?) && @factor==1
end

#to_numericNumeric Also known as: to_num

Returns numeric value of this unit, i.e. conversion factor. Raises UnitError if not dimensionless.

Returns:

  • (Numeric)

Raises:



344
345
346
347
# File 'lib/phys/units/unit.rb', line 344

def to_numeric
  assert_dimensionless
  conversion_factor
end

#to_sString

Returns self name or unit_string

Returns:

  • (String)


206
207
208
# File 'lib/phys/units/unit.rb', line 206

def to_s
  @name || unit_string
end

#unit_stringString Also known as: string_form

Make a string of this unit expressed in base units.

Examples:

Phys::Unit["psi"].string_form #=> "(8896443230521/129032)*1e-04 kg m^-1 s^-2"

Returns:

  • (String)


189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/phys/units/unit.rb', line 189

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