Class: Quantify::Unit::Base

Inherits:
Object
  • Object
show all
Extended by:
ExtendedMethods
Defined in:
lib/quantify/unit/base_unit.rb

Direct Known Subclasses

Compound, NonSI, Prefix::SI, SI

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = nil, &block) ⇒ Base

Create a new Unit::Base instance.

Valid options are: :name => The unit name, e.g. :kilometre

:dimensions        => The physical quantity represented
                      by the unit (e.g. force, mass).
                      This must be recognised as a member
                      of the Dimensions.dimensions array

:physical_quantity => Alias for :dimensions

:symbol            => The unit symbol, e.g. 'kg'

:factor            => The factor which relates the unit
                      to the SI unit for the same physical
                      quantity. For example the :factor for
                      a foot would be 0.3048, since a foot
                      = 0.3048 m (metre is the SI unit of
                      length). If no factor is set, it is
                      assumed to be 1 - which represents
                      an SI benchmark unit.

:scaling           => A scaling factor, used only by NonSI
                      temperature units

:label             => The label used by JScience for the
                      unit

The physical quantity option is used to locate the corresponding dimensional representation in the Dimensions class. This dimensions attribute is to provide much of the unit functionality



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/quantify/unit/base_unit.rb', line 102

def initialize(options=nil,&block)
  @acts_as_alternative_unit = true
  @acts_as_equivalent_unit = false
  self.factor = 1.0
  self.symbol = nil
  self.label = nil
  if options.is_a? Hash
    self.dimensions = options[:dimensions] || options[:physical_quantity]
    self.name = options[:name]
    self.factor = options[:factor] if options[:factor]
    self.symbol = options[:symbol] if options[:symbol]
    self.label = options[:label] if options[:label]
  end
  block.call(self) if block_given?
  valid?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object

Provides syntactic sugar for several methods. E.g.

Unit.metre.to_kilo

is equivalent to Unit.metre.with_prefix :kilo.

Unit.m.alternatives_by_name

is equaivalent to Unit.m.alternatives :name



591
592
593
594
595
596
597
598
599
600
# File 'lib/quantify/unit/base_unit.rb', line 591

def method_missing(method, *args, &block)
  if method.to_s =~ /(to_)(.*)/ && prefix = Prefix.for($2.to_sym)
    return self.with_prefix(prefix)
  elsif method.to_s =~ /(alternatives_by_)(.*)/ && self.respond_to?($2.to_sym)
    return self.alternatives($2.to_sym)
  elsif method.to_s =~ /(valid_prefixes_by_)(.*)/ && Prefix::Base.instance_methods.include?($2.to_s)
    return self.valid_prefixes($2.to_sym)
  end
  super
end

Instance Attribute Details

#acts_as_alternative_unitObject

Returns the value of attribute acts_as_alternative_unit.



68
69
70
# File 'lib/quantify/unit/base_unit.rb', line 68

def acts_as_alternative_unit
  @acts_as_alternative_unit
end

#acts_as_equivalent_unitObject

Returns the value of attribute acts_as_equivalent_unit.



68
69
70
# File 'lib/quantify/unit/base_unit.rb', line 68

def acts_as_equivalent_unit
  @acts_as_equivalent_unit
end

#dimensionsObject

Returns the value of attribute dimensions.



67
68
69
# File 'lib/quantify/unit/base_unit.rb', line 67

def dimensions
  @dimensions
end

#factorObject

Returns the value of attribute factor.



67
68
69
# File 'lib/quantify/unit/base_unit.rb', line 67

def factor
  @factor
end

#labelObject

Returns the value of attribute label.



67
68
69
# File 'lib/quantify/unit/base_unit.rb', line 67

def label
  @label
end

#nameObject

Returns the value of attribute name.



67
68
69
# File 'lib/quantify/unit/base_unit.rb', line 67

def name
  @name
end

#symbolObject

Returns the value of attribute symbol.



67
68
69
# File 'lib/quantify/unit/base_unit.rb', line 67

def symbol
  @symbol
end

Class Method Details

.configure(&block) ⇒ Object

Syntactic sugar for defining the units known to the system, enabling the required associated units to be loaded at runtime, e.g.

Unit::[Base|SI|NonSI].configure do |config|

  load :name => :metre, :physical_quantity => :length
  load :name => 'hectare', :physical_quantity => :area, :factor => 10000
  load :name => :watt, :physical_quantity => :power, :symbol => 'W'

end


63
64
65
# File 'lib/quantify/unit/base_unit.rb', line 63

def self.configure &block
  class_eval &block if block
end

.construct(unit, &block) ⇒ Object

Define a new unit in terms of an already instantiated compound unit. This unit becomes a representation of the compound - without explicitly holding the base units, e.g.

Unit::Base.define(Unit.m**2).name              #=> "square metre"

Unit::Base.define(Unit**3) do |unit|
  unit.name = "metres cubed"
end.name                                       #=> "metres cubed"


46
47
48
49
50
# File 'lib/quantify/unit/base_unit.rb', line 46

def self.construct(unit,&block)
  new_unit = self.new unit.to_hash
  block.call(new_unit) if block_given?
  return new_unit
end

.construct_and_load(unit, &block) ⇒ Object



18
19
20
# File 'lib/quantify/unit/base_unit.rb', line 18

def self.construct_and_load(unit,&block)
  self.construct(unit, &block).load
end

.load(options = nil, &block) ⇒ Object

Create a new instance of self (i.e. Base or an inherited class) and load into the system of known units. See initialize for details of options



14
15
16
# File 'lib/quantify/unit/base_unit.rb', line 14

def self.load(options=nil,&block)
  self.new(options,&block).load
end

.prefix_and_load(prefixes, units) ⇒ Object

Mass load prefixed units. First argument is a single or array of units. Second argument is a single or array of prefixes. All specfied units will be loaded with all specified prefixes.



26
27
28
29
30
31
32
33
34
# File 'lib/quantify/unit/base_unit.rb', line 26

def self.prefix_and_load(prefixes,units)
  [units].flatten.each do |unit|
    unit = Unit.for(unit)
    [prefixes].flatten.each do |prefix|
      prefixed_unit = unit.with_prefix(prefix) rescue unit
      prefixed_unit.load unless prefixed_unit.loaded?
    end
  end
end

Instance Method Details

#alternatives(by = nil) ⇒ Object

List the alternative units for self, i.e. the other units which share the same dimensions.

The list can be returned containing the alternative unit names, symbols or JScience labels by providing the required format as a symbolized argument.

If no format is provide, the full unit objects for all alternative units are returned within the array



402
403
404
405
406
# File 'lib/quantify/unit/base_unit.rb', line 402

def alternatives(by=nil)
  @dimensions.units(nil).reject do |unit|
    unit.is_equivalent_to?(self) || !unit.acts_as_alternative_unit
  end.map(&by)
end

#canonical_label=(new_label) ⇒ Object

Set the canonical unit label - the unique unit identifier - to a new value



238
239
240
241
242
# File 'lib/quantify/unit/base_unit.rb', line 238

def canonical_label=(new_label)
  unload if loaded?
  self.label = new_label
  load
end

#coerce(object) ⇒ Object

Enables shorthand for reciprocal of a unit, e.g.

unit = Unit.m

(1/unit).symbol                     #=> "m^-1"


557
558
559
560
561
562
563
# File 'lib/quantify/unit/base_unit.rb', line 557

def coerce(object)
  if object.kind_of?(Numeric) && object == 1
    return Unit.unity, self
  else
    raise Exceptions::InvalidArgumentError, "Cannot coerce #{self.class} into #{object.class}"
  end
end

#configure(&block) ⇒ Object

Permits a block to be used, operating on self. This is useful for modifying the attributes of an already instantiated unit, especially when defining units on the basis of operation on existing units for adding specific (rather than derived) names or symbols, e.g.

(Unit.pound_force/(Unit.in**2)).configure do |unit|
  unit.symbol = 'psi'
  unit.label = 'psi'
  unit.name = 'pound per square inch'
end


195
196
197
198
# File 'lib/quantify/unit/base_unit.rb', line 195

def configure(&block)
  block.call(self) if block_given?
  return self if valid?
end

#configure_as_canonical(&block) ⇒ Object

Similar to #configure but makes the new unit configuration the canonical unit for self.label



203
204
205
206
207
# File 'lib/quantify/unit/base_unit.rb', line 203

def configure_as_canonical(&block)
  unload if loaded?
  configure(&block) if block_given?
  make_canonical
end

#divide(other) ⇒ Object Also known as: /

Divide one unit by another. This results in the generation of a compound unit.

In the event that the new unit represents a known unit, the non-compound representation is returned (i.e. of the SI or NonSI class).



474
475
476
477
478
479
480
481
482
483
484
# File 'lib/quantify/unit/base_unit.rb', line 474

def divide(other)
  options = []
  self.instance_of?(Unit::Compound) ? options += @base_units : options << self

  if other.instance_of? Unit::Compound
    options += other.base_units.map { |base| base.index *= -1; base }
  else
    options << CompoundBaseUnit.new(other,-1)
  end
  Unit::Compound.new(*options)
end

#has_same_identity_as?(other) ⇒ Boolean Also known as: ==

Check if unit has the identity as another, i.e. the same label. This is used to determine if a unit with the same accessors already exists in the module variable @@units

Returns:

  • (Boolean)


373
374
375
# File 'lib/quantify/unit/base_unit.rb', line 373

def has_same_identity_as?(other)
  @label == other.label && !@label.nil?
end

#has_scaling?Boolean

Returns:

  • (Boolean)


263
264
265
# File 'lib/quantify/unit/base_unit.rb', line 263

def has_scaling?
  scaling != 0.0
end

#initialize_copy(source) ⇒ Object

Clone self and explicitly clone the associated Dimensions object located at @dimensions.

This enables full or ā€˜deepā€™ copies of the already initialized units to be retrieved and manipulated without corrupting the known unit representations. (self.clone makes only a shallow copy, i.e. clones attributes but not referenced objects)



573
574
575
576
577
578
579
# File 'lib/quantify/unit/base_unit.rb', line 573

def initialize_copy(source)
  super
  instance_variable_set("@dimensions", @dimensions.clone)
  if self.is_compound_unit?
    instance_variable_set("@base_units", @base_units.map {|base| base.clone })
  end
end

#is_alternative_for?(other) ⇒ Boolean

Determine if another unit is an alternative unit for self, i.e. do the two units represent the same physical quantity. This is established by compraing their dimensions attributes. E.g.

Unit.metre.is_alternative_for? Unit.foot    #=> true

Unit.metre.is_alternative_for? Unit.gram    #=> false

Unit.metre.is_alternative_for? Unit.metre   #=> true

Returns:

  • (Boolean)


388
389
390
# File 'lib/quantify/unit/base_unit.rb', line 388

def is_alternative_for?(other)
  other.dimensions == @dimensions
end

#is_base_quantity_si_unit?Boolean

Determine if the unit is THE canonical SI unit for a base quantity (length, mass, time, etc.). This method ignores prefixed versions of SI base units, returning true only for metre, kilogram, second, Kelvin, etc.

Returns:

  • (Boolean)


294
295
296
# File 'lib/quantify/unit/base_unit.rb', line 294

def is_base_quantity_si_unit?
  is_si_unit? && is_base_unit? && is_benchmark_unit?
end

#is_base_unit?Boolean

Determine if the unit represents one of the base quantities, length, mass, time, temperature, etc.

Returns:

  • (Boolean)


286
287
288
# File 'lib/quantify/unit/base_unit.rb', line 286

def is_base_unit?
  Dimensions::BASE_QUANTITIES.map {|base| base.remove_underscores }.include? self.measures
end

#is_benchmark_unit?Boolean

Determine if the unit is one of the units against which all other units of the same physical quantity are defined. These units are almost entirely equivalent to the non-prefixed, SI units, but the one exception is the kilogram, which is an oddity in being THE canonical SI unit for mass, yet containing a prefix. This oddity makes this method useful/necessary.

Returns:

  • (Boolean)


318
319
320
# File 'lib/quantify/unit/base_unit.rb', line 318

def is_benchmark_unit?
  @factor == 1.0
end

#is_compound_unit?Boolean

Determine is a unit object represents an compound unit consisting of SI or non-SI named units

Returns:

  • (Boolean)


334
335
336
# File 'lib/quantify/unit/base_unit.rb', line 334

def is_compound_unit?
  self.is_a? Compound
end

#is_derived_unit?Boolean

Determine is the unit is a derived unit - that is, a unit made up of more than one of the base quantities

Returns:

  • (Boolean)


301
302
303
# File 'lib/quantify/unit/base_unit.rb', line 301

def is_derived_unit?
  !is_base_unit?
end

#is_dimensionless?Boolean

Returns:

  • (Boolean)


338
339
340
# File 'lib/quantify/unit/base_unit.rb', line 338

def is_dimensionless?
  @dimensions.is_dimensionless?
end

#is_equivalent_to?(other) ⇒ Boolean

Determine if self is equivalent to another. Equivalency is based on representing the same physical quantity (i.e. dimensions) and the same factor and scaling values.

Unit.metre.is_equivalent_to? Unit.foot    #=> false

Unit.metre.is_equivalent_to? Unit.gram    #=> false

Unit.metre.is_equivalent_to? Unit.metre   #=> true

The base_units attr of Compound units are not compared. Neither are the names or symbols. This is because we want to recognise cases where units derived from operations and defined as compound units (therefore having compounded names and symbols) are the same as known, named units. For example, if we build a unit for energy using only SI units, we want to recognise this as a joule, rather than a kg m^2 s^-2, e.g.

(Unit.kg*Unit.m*Unit.m/Unit.s/Unit.s).is_equivalent_to? Unit.joule

                                   #=> true

Returns:

  • (Boolean)


363
364
365
366
367
# File 'lib/quantify/unit/base_unit.rb', line 363

def is_equivalent_to?(other)
  [:dimensions,:factor,:scaling].all? do |attr|
    self.send(attr) == other.send(attr)
  end
end

#is_non_si_unit?Boolean

Determine is a unit object represents an NonSI named unit

Returns:

  • (Boolean)


328
329
330
# File 'lib/quantify/unit/base_unit.rb', line 328

def is_non_si_unit?
  self.is_a? NonSI
end

#is_prefixed_unit?Boolean

Determine if the unit is a prefixed unit

Returns:

  • (Boolean)


306
307
308
309
310
# File 'lib/quantify/unit/base_unit.rb', line 306

def is_prefixed_unit?
  return true if valid_prefixes.size > 0 &&
    self.name =~ /\A(#{valid_prefixes.map {|p| p.name}.join("|")})/
  return false
end

#is_si_unit?Boolean

Determine is a unit object represents an SI named unit

Returns:

  • (Boolean)


323
324
325
# File 'lib/quantify/unit/base_unit.rb', line 323

def is_si_unit?
  self.is_a? SI
end

#load(&block) ⇒ Object

Load an initialized Unit into the system of known units.

If a block is given, the unit can be configured prior to loading, in a similar to way to the #configure method.



214
215
216
217
218
219
# File 'lib/quantify/unit/base_unit.rb', line 214

def load(&block)
  block.call(self) if block_given?
  raise Exceptions::InvalidArgumentError, "A unit with the same label: #{self.name}) already exists" if loaded?
  Quantify::Unit.units << self if valid?
  return self
end

#loaded?Boolean

check if an object with the same label already exists

Returns:

  • (Boolean)


227
228
229
# File 'lib/quantify/unit/base_unit.rb', line 227

def loaded?
  Unit.units.any? { |unit| self.has_same_identity_as? unit }
end

#make_canonicalObject

Make self the canonical representation of the unit defined by self#label



232
233
234
235
# File 'lib/quantify/unit/base_unit.rb', line 232

def make_canonical
  unload if loaded?
  load
end

#measuresObject

Describes what the unit measures/represents. This is taken from the Dimensions object, e.g.

Unit.metre.measures                      #=> :length

Unit.J.measures                          #=> :energy


275
276
277
# File 'lib/quantify/unit/base_unit.rb', line 275

def measures
  @dimensions.describe
end

#multiply(other) ⇒ Object Also known as: times, *

Multiply two units together. This results in the generation of a compound unit.



459
460
461
462
463
464
# File 'lib/quantify/unit/base_unit.rb', line 459

def multiply(other)
  options = []
  self.instance_of?(Unit::Compound) ? options += @base_units : options << self
  other.instance_of?(Unit::Compound) ? options += other.base_units : options << other
  Unit::Compound.new(*options)
end

#pluralized_nameObject



279
280
281
# File 'lib/quantify/unit/base_unit.rb', line 279

def pluralized_name
  @name.pluralize
end

#pow(power) ⇒ Object Also known as: **

Raise a unit to a power. This results in the generation of a compound unit, e.g. m^3.

In the event that the new unit represents a known unit, the non-compound representation is returned (i.e. of the SI or NonSI class).



493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/quantify/unit/base_unit.rb', line 493

def pow(power)
  return nil if power == 0
  original_unit = self.clone
  if power > 0
    new_unit = self.clone
    (power - 1).times { new_unit *= original_unit }
  elsif power < 0
    new_unit = reciprocalize
    ((power.abs) - 1).times { new_unit /= original_unit }
  end
  return new_unit
end

#reciprocalizeObject

Return new unit representing the reciprocal of self, i.e. 1/self



508
509
510
# File 'lib/quantify/unit/base_unit.rb', line 508

def reciprocalize
  Unit.unity / self
end

#refresh_identifiers!Object

Refresh the name, symbol and label attributes of self with respect to the configuration found in Quantify.use_superscript_characters?



174
175
176
177
178
# File 'lib/quantify/unit/base_unit.rb', line 174

def refresh_identifiers!
  self.name = name
  self.symbol = symbol
  self.label = label
end

#scalingObject

Returns the scaling factor for the unit with repsect to its SI alternative.

For example the scaling factor for degrees celsius is 273.15, i.e. celsius is a value of 273.15 greater than kelvin (but with no multiplicative factor).



259
260
261
# File 'lib/quantify/unit/base_unit.rb', line 259

def scaling
  @scaling || 0.0
end

#si_unitObject

Returns the SI unit for the same physical quantity which is represented by self, e.g.



411
412
413
# File 'lib/quantify/unit/base_unit.rb', line 411

def si_unit
  @dimensions.si_unit
end

#to_hashObject

Return a hash representation of self containing each unit attribute (i.e each instance variable)



542
543
544
545
546
547
548
549
# File 'lib/quantify/unit/base_unit.rb', line 542

def to_hash
  hash = {}
  self.instance_variables.each do |var|
    symbol = var.gsub("@","").to_sym
    hash[symbol] = send symbol
  end
  return hash
end

#unloadObject

Remove from system of known units.



222
223
224
# File 'lib/quantify/unit/base_unit.rb', line 222

def unload
  Unit.unload(self.label)
end

#valid?Boolean

Returns:

  • (Boolean)

Raises:



415
416
417
418
# File 'lib/quantify/unit/base_unit.rb', line 415

def valid?
  return true if valid_descriptors? && valid_dimensions?
  raise Exceptions::InvalidArgumentError, "Unit definition must include a name, a symbol, a label and physical quantity"
end

#valid_descriptors?Boolean

Returns:

  • (Boolean)


420
421
422
423
424
425
# File 'lib/quantify/unit/base_unit.rb', line 420

def valid_descriptors?
  [:name, :symbol, :label].all? do |attr|
    attribute = send(attr)
    attribute.is_a?(String) && !attribute.empty?
  end
end

#valid_dimensions?Boolean

Returns:

  • (Boolean)


427
428
429
# File 'lib/quantify/unit/base_unit.rb', line 427

def valid_dimensions?
  @dimensions.is_a? Dimensions
end

#valid_prefixes(by = nil) ⇒ Object

Returns an array representing the valid prefixes for the unit described by self

If no argument is given, the array holds instances of Prefix::Base (or subclasses; SI, NonSIā€¦). Alternatively only the names or symbols of each prefix can be returned by providing the appropriate prefix attribute as a symbolized argument, e.g.

Unit.m.valid_prefixes                 #=> [ #<Quantify::Prefix: .. >,
                                            #<Quantify::Prefix: .. >,
                                            ... ]

Unit.m.valid_prefixes :name           #=> [ "deca", "hecto", "kilo", 
                                            "mega", "giga", "tera"
                                            ... ]    

Unit.m.valid_prefixes :symbol         #=> [ "da", "h", "k", "M", "G",
                                            "T", "P" ... ]


450
451
452
453
454
# File 'lib/quantify/unit/base_unit.rb', line 450

def valid_prefixes(by=nil)
  return [] if self.is_compound_unit?
  return Unit::Prefix.si_prefixes.map(&by) if is_si_unit?
  return Unit::Prefix.non_si_prefixes.map(&by) if is_non_si_unit?
end

#with_prefix(name_or_symbol) ⇒ Object

Apply a prefix to self. Returns new unit according to the prefixed version of self, complete with modified name, symbol, factor, etc..



515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
# File 'lib/quantify/unit/base_unit.rb', line 515

def with_prefix(name_or_symbol)
  if @name =~ /\A(#{valid_prefixes(:name).join("|")})/
    raise Exceptions::InvalidArgumentError, "Cannot add prefix where one already exists: #{self.name}"
  end
  
  prefix = Unit::Prefix.for(name_or_symbol,valid_prefixes)

  unless prefix.nil?
    new_unit_options = {}
    new_unit_options[:name] = "#{prefix.name}#{@name}"
    new_unit_options[:symbol] = "#{prefix.symbol}#{@symbol}"
    new_unit_options[:label] = "#{prefix.symbol}#{@label}"
    new_unit_options[:factor] = prefix.factor * @factor
    new_unit_options[:physical_quantity] = @dimensions
    self.class.new(new_unit_options)
  else
    raise Exceptions::InvalidArgumentError, "Prefix unit is not known: #{prefix}"
  end
end

#with_prefixes(*prefixes) ⇒ Object



535
536
537
# File 'lib/quantify/unit/base_unit.rb', line 535

def with_prefixes(*prefixes)
  [prefixes].map { |prefix| self.with_prefix(prefix) }
end