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 =
"1.0.1"

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

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:



473
474
475
476
477
478
479
480
481
482
483
484
# 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) ⇒ Phys::Unit

Exponentiation of units. This units must be operable.

Parameters:

  • x (Numeric)

    numeric

Returns:

Raises:



520
521
522
523
524
525
# 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) ⇒ Phys::Unit

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

Parameters:

Returns:

Raises:



432
433
434
435
436
437
# 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

#+@Phys::Unit

Unary plus. Returns self.

Returns:



464
465
466
# File 'lib/phys/units/unit.rb', line 464

def +@
  self
end

#-(x) ⇒ Phys::Unit

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

Parameters:

Returns:

Raises:



444
445
446
447
448
449
# 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

#-@Phys::Unit

Unary minus. This unit must be operable.

Returns:

Raises:



455
456
457
458
459
# File 'lib/phys/units/unit.rb', line 455

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:



491
492
493
494
495
496
497
498
499
500
501
502
# 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) ⇒ Boolean

Equality of units

Parameters:

  • x (Object)

    other unit or object

Returns:

  • (Boolean)


540
541
542
543
544
545
546
547
548
549
550
551
# 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) ⇒ 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)


284
285
286
287
288
289
290
291
292
293
294
295
# 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

#base_unitPhys::Unit

Returns Base Unit excluding dimensionless-units.

Returns:



355
356
357
# File 'lib/phys/units/unit.rb', line 355

def base_unit
  Unit.new(1,dimensionless_deleted)
end

#coerce(x) ⇒ Array

Coerce.

Returns:

  • (Array)


555
556
557
# File 'lib/phys/units/unit.rb', line 555

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:



219
220
221
222
223
224
225
226
227
228
229
230
231
# 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) ⇒ Phys::Quantity

Convert a quantity to this unit.

Parameters:

Returns:

Raises:

  • (UnitError)

    if unit conversion is failed.



304
305
306
307
308
309
310
311
312
# 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) ⇒ Phys::Quantity

Convert a quantity to this unit only in scale.

Parameters:

Returns:

Raises:

  • (UnitError)

    if unit conversion is failed.



318
319
320
321
322
323
324
325
326
# 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) ⇒ Numeric

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

Parameters:

  • value (Numeric)

Returns:

  • (Numeric)


338
339
340
# 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) ⇒ Numeric

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

Parameters:

  • value (Numeric)

Returns:

  • (Numeric)


331
332
333
# File 'lib/phys/units/unit.rb', line 331

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)


103
104
105
106
# File 'lib/phys/units/unit.rb', line 103

def dimension
  use_dimension
  @dim
end

#dimension_valueNumeric

Dimension value. Always returns 1 unless BaseUnit.

Returns:

  • (Numeric)

See Also:



124
125
126
# File 'lib/phys/units/unit.rb', line 124

def dimension_value
  1
end

#dimensionless?Boolean

(internal use)

Returns:

  • (Boolean)


251
252
253
254
# File 'lib/phys/units/unit.rb', line 251

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

#exprString, NilClass

Expression of the unit.

Returns:

  • (String, NilClass)


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

def expr
  @expr || @name || string_form
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:



116
117
118
119
# File 'lib/phys/units/unit.rb', line 116

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)


173
174
175
176
177
178
179
180
181
182
183
184
185
# 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

#inversePhys::Unit

Inverse of units. This unit must be operable.

Parameters:

Returns:

Raises:



509
510
511
512
513
# 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?Boolean

Return true if this unit is operable.

Returns:

  • (Boolean)


363
364
365
# File 'lib/phys/units/unit.rb', line 363

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)


237
238
239
240
# File 'lib/phys/units/unit.rb', line 237

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:



346
347
348
349
# File 'lib/phys/units/unit.rb', line 346

def to_numeric
  assert_dimensionless
  conversion_factor
end

#to_sString

Returns self name or unit_string

Returns:

  • (String)


208
209
210
# File 'lib/phys/units/unit.rb', line 208

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)


191
192
193
194
195
196
197
198
199
200
201
202
203
# 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