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



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/quantify/unit/base_unit.rb', line 110

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
  self.name = nil
  self.base_unit = nil
  self.prefix = nil
  if options.is_a? Hash
    self.dimensions = options[:dimensions] || options[:physical_quantity] if options[:dimensions] || options[:physical_quantity] 
    self.factor = options[:factor] if options[:factor]
    self.name = options[:name] if options[:name]
    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 (private)

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



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

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)
  end
  super
end

Instance Attribute Details

#acts_as_alternative_unitObject

Returns the value of attribute acts_as_alternative_unit.



75
76
77
# File 'lib/quantify/unit/base_unit.rb', line 75

def acts_as_alternative_unit
  @acts_as_alternative_unit
end

#acts_as_equivalent_unitObject

Returns the value of attribute acts_as_equivalent_unit.



75
76
77
# File 'lib/quantify/unit/base_unit.rb', line 75

def acts_as_equivalent_unit
  @acts_as_equivalent_unit
end

#base_unitObject

Returns the value of attribute base_unit.



76
77
78
# File 'lib/quantify/unit/base_unit.rb', line 76

def base_unit
  @base_unit
end

#dimensionsObject

Returns the value of attribute dimensions.



74
75
76
# File 'lib/quantify/unit/base_unit.rb', line 74

def dimensions
  @dimensions
end

#factorObject

Returns the value of attribute factor.



74
75
76
# File 'lib/quantify/unit/base_unit.rb', line 74

def factor
  @factor
end

#labelObject

Returns the value of attribute label.



74
75
76
# File 'lib/quantify/unit/base_unit.rb', line 74

def label
  @label
end

#nameObject

Returns the value of attribute name.



74
75
76
# File 'lib/quantify/unit/base_unit.rb', line 74

def name
  @name
end

#prefixObject

Returns the value of attribute prefix.



76
77
78
# File 'lib/quantify/unit/base_unit.rb', line 76

def prefix
  @prefix
end

#symbolObject

Returns the value of attribute symbol.



74
75
76
# File 'lib/quantify/unit/base_unit.rb', line 74

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


70
71
72
# File 'lib/quantify/unit/base_unit.rb', line 70

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.construct(Unit.m**2).name           #=> "square metre"

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


53
54
55
56
57
# File 'lib/quantify/unit/base_unit.rb', line 53

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



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

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

.initialize_prefixed_version(prefix, unit) ⇒ Object



21
22
23
24
25
26
27
# File 'lib/quantify/unit/base_unit.rb', line 21

def self.initialize_prefixed_version(prefix,unit)
  prefix, unit = Prefix.for(prefix), Unit.for(unit)
  raise Exceptions::InvalidArgumentError, "Prefix is not known" if prefix.nil?
  raise Exceptions::InvalidArgumentError, "Unit is not known" if unit.nil?
  raise Exceptions::InvalidArgumentError, "Cannot add prefix where one already exists: #{unit.prefix.name}" if unit.prefix
  self.new &self.block_for_prefixed_version(prefix,unit)
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



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

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.



33
34
35
36
37
38
39
40
41
# File 'lib/quantify/unit/base_unit.rb', line 33

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



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

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

#canonical_label=(new_label) ⇒ Object

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



249
250
251
252
253
# File 'lib/quantify/unit/base_unit.rb', line 249

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"


529
530
531
532
533
534
535
# File 'lib/quantify/unit/base_unit.rb', line 529

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


206
207
208
209
# File 'lib/quantify/unit/base_unit.rb', line 206

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



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

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).



459
460
461
462
463
464
465
466
467
468
469
# File 'lib/quantify/unit/base_unit.rb', line 459

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)


382
383
384
# File 'lib/quantify/unit/base_unit.rb', line 382

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

#has_scaling?Boolean

Returns:

  • (Boolean)


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

def has_scaling?
  scaling != 0.0
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)


397
398
399
# File 'lib/quantify/unit/base_unit.rb', line 397

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)


305
306
307
# File 'lib/quantify/unit/base_unit.rb', line 305

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)


297
298
299
# File 'lib/quantify/unit/base_unit.rb', line 297

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)


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

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)


343
344
345
# File 'lib/quantify/unit/base_unit.rb', line 343

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)


312
313
314
# File 'lib/quantify/unit/base_unit.rb', line 312

def is_derived_unit?
  !is_base_unit?
end

#is_dimensionless?Boolean

Returns:

  • (Boolean)


347
348
349
# File 'lib/quantify/unit/base_unit.rb', line 347

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)


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

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)


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

def is_non_si_unit?
  self.is_a? NonSI
end

#is_prefixed_unit?Boolean

Determine if the unit is a prefixed unit

Returns:

  • (Boolean)


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

def is_prefixed_unit?
  self.prefix ? true : false
end

#is_si_unit?Boolean

Determine is a unit object represents an SI named unit

Returns:

  • (Boolean)


332
333
334
# File 'lib/quantify/unit/base_unit.rb', line 332

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.



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

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)


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

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



243
244
245
246
# File 'lib/quantify/unit/base_unit.rb', line 243

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


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

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.



444
445
446
447
448
449
# File 'lib/quantify/unit/base_unit.rb', line 444

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



290
291
292
# File 'lib/quantify/unit/base_unit.rb', line 290

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).



478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/quantify/unit/base_unit.rb', line 478

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



493
494
495
# File 'lib/quantify/unit/base_unit.rb', line 493

def reciprocalize
  Unit.unity / self
end

#refresh_attributesObject

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



185
186
187
188
189
# File 'lib/quantify/unit/base_unit.rb', line 185

def refresh_attributes
  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).



270
271
272
# File 'lib/quantify/unit/base_unit.rb', line 270

def scaling
  @scaling || 0.0
end

#si_unitObject

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



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

def si_unit
  @dimensions.si_unit
end

#to_hashObject

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



514
515
516
517
518
519
520
521
# File 'lib/quantify/unit/base_unit.rb', line 514

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

#unloadObject

Remove from system of known units.



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

def unload
  Unit.unload(self.label)
end

#valid?Boolean

Returns:

  • (Boolean)

Raises:



424
425
426
427
# File 'lib/quantify/unit/base_unit.rb', line 424

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)


429
430
431
432
433
434
435
# File 'lib/quantify/unit/base_unit.rb', line 429

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

#valid_dimensions?Boolean

Returns:

  • (Boolean)


437
438
439
# File 'lib/quantify/unit/base_unit.rb', line 437

def valid_dimensions?
  @dimensions.is_a? Dimensions
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..



500
501
502
# File 'lib/quantify/unit/base_unit.rb', line 500

def with_prefix(name_or_symbol)
  self.class.initialize_prefixed_version(name_or_symbol,self)
end

#with_prefixes(*prefixes) ⇒ Object

Return an array of new unit instances based upon self, together with the prefixes specified by prefixes



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

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