Class: UOM::Unit
- Inherits:
-
Object
- Object
- UOM::Unit
- Defined in:
- lib/uom/unit.rb
Overview
A Unit demarcates a standard magnitude on a Measurement Dimension. A base unit is an unscaled dimensional Unit, e.g. METER. A derived unit is composed of other units. The derived unit includes a required dimensional axis unit and an optional scalar Factor, e.g. the millimeter
unit is derived from the meter
axis and the milli
scaling factor.
The axis is orthogonal to the scalar. There is a distinct unit for each axis and scalar. The base unit specifies the permissible scalar factors.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#abbreviations ⇒ Object
readonly
Returns the value of attribute abbreviations.
-
#axis ⇒ Object
readonly
Returns the value of attribute axis.
-
#dimension ⇒ Object
readonly
Returns the value of attribute dimension.
-
#label ⇒ Object
readonly
Returns the value of attribute label.
-
#permissible_factors ⇒ Object
readonly
Returns the value of attribute permissible_factors.
-
#scalar ⇒ Object
readonly
Returns the value of attribute scalar.
Instance Method Summary collapse
-
#*(other) ⇒ Object
Returns a product CompositeUnit consisting of this unit and the other unit, e.g.: (Unit.for(:pound) * Unit.for(:inch)).label #=> foot_pound.
-
#/(other) ⇒ Object
Returns a division CompositeUnit consisting of this unit and the other unit, e.g.: (Unit.for(:gram) / Unit.for(:liter)).label #=> gram_per_liter.
- #add_abbreviation(abbrev) ⇒ Object
-
#add_converter(other, &converter) ⇒ Object
Defines a conversion from this unit to the other unit.
-
#as(quantity, unit) ⇒ Object
Returns the given quantity converted from this Unit into the given unit.
-
#basic? ⇒ Boolean
Returns whether this unit’s axis is the unit itself.
-
#basis ⇒ Object
Returns the Unit which is the basis for a derived unit.
-
#initialize(*params, &converter) ⇒ Unit
constructor
Creates the Unit with the given label and parameters.
- #inspect ⇒ Object
- #to_s(quantity = nil) ⇒ Object
Constructor Details
#initialize(*params, &converter) ⇒ Unit
Creates the Unit with the given label and parameters. The params include the following:
-
a unit label followed by zero, one or more Symbol unit abbreviations
-
one or more Dimension objects for a basic unit
-
an optional axis Unit for a derived unit
-
an optional scaling Factor for a derived unit
-
an optional normalization multiplier which converts this unit to the axis
For example, a second is defined as:
SECOND = Unit.new(:second, :sec, Dimension::TIME, MILLI, MICRO, NANO, PICO, FEMTO)
and a millisecond is defined as:
Unit.new(MILLI, UOM::SECOND)
In most cases, a derived unit does not need to define a label or abbreviation since these are inferred from the axis and factor.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/uom/unit.rb', line 37 def initialize(*params, &converter) # this long initializer ensures that every unit is correct by construction # the first symbol is the label labels = params.select { |param| Symbol === param } @label = labels.first # the optional Factor parameters are the permissible scaling factors factors = params.select { |param| Factor === param }.to_set # a convertable unit must have a unique factor if converter and factors.size > 1 then raise MeasurementError.new("Derived unit #{label} can have at most one scalar: #{factors.to_a.join(', ')}") @permissible_factors = [] else @permissible_factors = factors end # a Numeric parameter indicates a conversion multiplier instead of a converter block multiplier = params.detect { |param| Numeric === param } if multiplier then # there can't be both a converter and a multiplier if converter then raise MeasurementError.new("Derived unit #{label} specifies both a conversion multiplier constant and a converter block") end # make the converter block from the multiplier converter = lambda { |n| n * multiplier } end # the optional single Unit parameter is the axis for a derived unit axes = params.select { |param| Unit === param } raise MeasurementError.new("Unit #{label} can have at most one axis: #{axes.join(', ')}") if axes.size > 1 @axis = axes.first # validate that a convertable unit has an axis; the converter argument is an axis quantity raise MeasurementError.new("Derived unit #{label} has a converter but does not have an axis unit") if @default_converter and @axis.nil? # the axis of an underived base unit is the unit itself @axis ||= self # validate that there is not a converter on self raise MeasurementError.new("Unit #{label} specifies a converter but not a conversion unit") unless converter.nil? if @axis == self # the default converter for a derived unit is identity converter ||= lambda { |n| n } unless @axis == self # the scalar is the first specified factor, or UNIT if there are multiple permissible factors @scalar = @permissible_factors.size == 1 ? @permissible_factors.to_a.first : UNIT # validate the scalar if @axis == self then #a derived unit cannot have a scalar factor raise MeasurementError.new("Base unit #{label} cannot have a scalar value - #{@scalar}") unless @scalar == UNIT elsif @scalar != UNIT and not @axis.permissible_factors.include?(@scalar) then # a derived unit scalar factor must be in the axis permissible factors raise MeasurementError.new("Derived unit #{label} scalar #{scalar} not a #{@axis} permissible factor #{@axis.permissible_factors.to_a.join(', ')}") end # if a scalar is defined, then adjust the converter scaled_converter = @scalar == UNIT ? converter : lambda { |n| @scalar.as(@axis.scalar) * converter.call(n) } # add the axis converter to the converters hash @converters = {} @converters[@axis] = scaled_converter if converter # define the multiplier converter inverse @axis.add_converter(self) { |n| 1.0 / scaled_converter.call(1.0 / n) } unless @scalar.nil? and multiplier.nil? # make the label from the scalar and axis @label ||= create_label # validate label existence raise MeasurementError.new("Unit does not have a label") if self.label.nil? # validate label uniqueness if Unit.extent.association.has_key?(@label) then raise MeasurementError.new("Unit label #{@label} conflicts with existing unit #{Unit.extent.association[@label].inspect}") end # get the dimension dimensions = params.select { |param| Dimension === param } if dimensions.empty? then # a base unit must have a dimension raise MeasurementError.new("Base unit #{label} is missing a dimension") if @axis == self # a derived unit dimension is the axis dimension @dimension = axis.dimension elsif dimensions.size > 1 then # there can be at most one dimension raise MeasurementError.new("Unit #{label} can have at most one dimension") else # the sole specified dimension @dimension = dimensions.first end # the remaining symbols are abbreviations @abbreviations = labels.size < 2 ? [] : labels[1..-1] # validate abbreviation uniqueness conflict = @abbreviations.detect { |abbrev| Unit.extent.association.has_key?(abbrev) } raise MeasurementError.new("Unit label #{@label} conflicts with an existing unit") if conflict # add this Unit to the extent Unit << self end |
Instance Attribute Details
#abbreviations ⇒ Object (readonly)
Returns the value of attribute abbreviations.
23 24 25 |
# File 'lib/uom/unit.rb', line 23 def abbreviations @abbreviations end |
#axis ⇒ Object (readonly)
Returns the value of attribute axis.
23 24 25 |
# File 'lib/uom/unit.rb', line 23 def axis @axis end |
#dimension ⇒ Object (readonly)
Returns the value of attribute dimension.
23 24 25 |
# File 'lib/uom/unit.rb', line 23 def dimension @dimension end |
#label ⇒ Object (readonly)
Returns the value of attribute label.
23 24 25 |
# File 'lib/uom/unit.rb', line 23 def label @label end |
#permissible_factors ⇒ Object (readonly)
Returns the value of attribute permissible_factors.
23 24 25 |
# File 'lib/uom/unit.rb', line 23 def permissible_factors @permissible_factors end |
#scalar ⇒ Object (readonly)
Returns the value of attribute scalar.
23 24 25 |
# File 'lib/uom/unit.rb', line 23 def scalar @scalar end |
Instance Method Details
#*(other) ⇒ Object
151 152 153 |
# File 'lib/uom/unit.rb', line 151 def *(other) CompositeUnit.for(self, other, :*) end |
#/(other) ⇒ Object
145 146 147 |
# File 'lib/uom/unit.rb', line 145 def /(other) CompositeUnit.for(self, other, :/) end |
#add_abbreviation(abbrev) ⇒ Object
133 134 135 136 |
# File 'lib/uom/unit.rb', line 133 def add_abbreviation(abbrev) @abbreviations << abbrev.to_sym Unit.extent.association[abbrev] = self end |
#add_converter(other, &converter) ⇒ Object
Defines a conversion from this unit to the other unit.
139 140 141 |
# File 'lib/uom/unit.rb', line 139 def add_converter(other, &converter) @converters[other] = converter end |
#as(quantity, unit) ⇒ Object
Returns the given quantity converted from this Unit into the given unit.
156 157 158 159 160 161 162 |
# File 'lib/uom/unit.rb', line 156 def as(quantity, unit) begin convert(quantity, unit) rescue MeasurementError => e raise MeasurementError.new("No conversion path from #{self} to #{unit} - #{e}") end end |
#basic? ⇒ Boolean
Returns whether this unit’s axis is the unit itself.
129 130 131 |
# File 'lib/uom/unit.rb', line 129 def basic? self == axis end |
#basis ⇒ Object
Returns the Unit which is the basis for a derived unit. If this unit’s axis is the axis itself, then that is the basis. Otherwise, the basis is this unit’s axis basis.
124 125 126 |
# File 'lib/uom/unit.rb', line 124 def basis basic? ? self : axis.basis end |
#inspect ⇒ Object
168 169 170 |
# File 'lib/uom/unit.rb', line 168 def inspect "#{self.class.name}@#{self.object_id}[#{([label] + abbreviations).join(', ')}]" end |
#to_s(quantity = nil) ⇒ Object
164 165 166 |
# File 'lib/uom/unit.rb', line 164 def to_s(quantity=nil) (quantity.nil? or quantity == 1) ? label.to_s : label.to_s.pluralize end |