class Quantify::Dimensions
The Dimensions
class represents specfic physical quantities in terms of powers of their constituent base dimensions, e.g.:
area = length^2 force = mass^1 x length^1 x time^-2
Each dimension object is characterised by instance variables which describe the power (or index) of the respective base dimensions. Dimension objects can be manipulated - multiplied, divided, raised to powers, etc.
Standard physical quantities (e.g. length, acceleration, energy) are loaded into the @@dimensions class variable at runtime. These can be accessed, used and manipulated for arbitrary dimensional uses.
Instances of Dimensions
are also used as the basis for defining and manipulating objects of the Unit::Base
class.
Constants
- BASE_QUANTITIES
The
BASE_QUANTITIES
array specifies the system of base quantities upon which allDimensions
objects are defined.:information, :currency, :item represent tentative additions to the standard set of base quantities.
:item is intended to represent arbitrary 'things' for specifying quantities such as, for example:
'dollars per capita' (:currency => 1, :items => -1) 'trees per hectare' (:items => 1, :length => -2).
Attributes
Public Class Methods
Returns an array of Dimensions
objects representing just the base quantities, i.e. length, mass, time, temperature, etc.
# File lib/quantify/dimensions.rb, line 52 def self.base_dimensions @@dimensions.select do |dimensions| BASE_QUANTITIES.map {|quantity| quantity.remove_underscores}.include?(dimensions.describe) end end
Syntactic sugar for defining the known quantities. This method simply evaluates code within the context of the Dimensions
class, enabling the required quantities to be loaded at runtime, e.g.
Dimensions.configure do load :physical_quantity => :length, :length => 1 load :physical_quantity => :area, :length => 2 load :physical_quantity => :power, :mass => 1, :length => 2, :time => -3 end
# File lib/quantify/dimensions.rb, line 142 def self.configure(&block) self.class_eval(&block) if block end
Provides access the class array which holds all defined quantities
# File lib/quantify/dimensions.rb, line 45 def self.dimensions @@dimensions end
Retrieve a known quantity - returns a Dimensions
instance, which is a clone of the initialized instance of the specified quantity. This enables the object to be modified/manipulated without corrupting the representation of the quantity in the @@dimensions class array.
The required quantity name/descriptor can be specified as a symbol or a string, e.g.:
Dimensions.for :acceleration Dimensions.for 'luminous_flux'
These can be shortened to, e.g. Dimensions.acceleration by virtue of the method_missing class method (below)
# File lib/quantify/dimensions.rb, line 116 def self.for(name) return name if name.is_a? Dimensions if (name.is_a?(String) || name.is_a?(Symbol)) name = name.remove_underscores.downcase if quantity = @@dimensions.find { |quantity| quantity.physical_quantity == name } return quantity.clone else raise Exceptions::InvalidArgumentError, "Physical quantity not known: #{name}" end else raise Exceptions::InvalidArgumentError, "Argument must be a Symbol or String" end end
This method allows specific, named quantities to be initialized and loaded into the @@dimensions array. Quantities are specified by their consituent base dimensions, but must also include a name/description, i.e. 'acceleration', :force - indicated by the :physical_quantity key - in order to be included in the system of known dimensions, e.g.:
Dimensions.load :physical_quantity => :force, :length => 1, :mass => 1, :time => -2
Standard quantities such as force, energy, mass, etc. should not need to be defined as they are included in the set of quantities already defined (see config.rb) and automatically loaded. These can be removed, overridden or configured differently if desired.
# File lib/quantify/dimensions.rb, line 74 def self.load(options) if options[:physical_quantity] @@dimensions << Dimensions.new(options) else raise Exceptions::InvalidDimensionError, "Cannot load dimensions without physical quantity description" end end
Provides a shorthand for retrieving known quantities, e.g.:
Dimensions.force
is equivalent to
Dimensions.for :force
Both variants return a clone of the initialized dimensional representation of the specified physical quantity (i.e. force).
# File lib/quantify/dimensions.rb, line 157 def self.method_missing(method, *args, &block) if dimensions = self.for(method) return dimensions end super end
Initialize a new Dimension object.
The options argument is a hash which represents the base dimensions that define the physical quantity. Each key-value pair should consist of a key included in the BASE_QUANTITIES
array, and a value which represents the index/power of that base quantity.
In addition, a name or description of the physical quantity can be specified (i.e. 'acceleration', 'electric_current'). This is optional for creating a new Dimensions
instance, but required if that object is to be loaded into the @@dimensions class array. e.g.:
Dimensions.new :physical_quantity => :density, :mass => 1, :length => -3
# File lib/quantify/dimensions.rb, line 184 def initialize(options={}) if options.has_key?(:physical_quantity) @physical_quantity = options.delete(:physical_quantity).remove_underscores.downcase end enumerate_base_quantities(options) describe end
Returns an array containing the names/descriptions of all known (loaded) physical quantities, e.g.:
Dimensions.physical_quantities #=> [ 'acceleration', 'area', 'electric Current', ... ]
# File lib/quantify/dimensions.rb, line 98 def self.physical_quantities @@dimensions.map {|dimension| dimension.physical_quantity } end
Remove a dimension from the system of known dimensions
# File lib/quantify/dimensions.rb, line 83 def self.unload(*unloaded_dimensions) [unloaded_dimensions].flatten.each do |unloaded_dimension| unloaded_dimension = Dimensions.for(unloaded_dimension) @@dimensions.delete_if { |dimension| dimension.has_same_identity_as? unloaded_dimension } end end
Public Instance Methods
Compares the base quantities of two Dimensions
objects and returns true if they are the same. This indicates that the two objects represent the same physical quantity (irrespective of their names - @physical_quantity - being similar, different, or absent.
# File lib/quantify/dimensions.rb, line 330 def ==(other) self.to_hash == other.to_hash end
Return a description of what physical quantity self represents. If no value is found in the @physical_quantity instance variable, the task is delegated to the get_description
method.
# File lib/quantify/dimensions.rb, line 226 def describe @physical_quantity or get_description end
Similar to divide!
but returns a new Dimensions
instance representing the physical quantity which results from the division.
# File lib/quantify/dimensions.rb, line 399 def divide(other) Dimensions.new(self.to_hash).divide! other end
Similar to multiply!
but performs a division of self by the specified Dimensions
object.
# File lib/quantify/dimensions.rb, line 390 def divide!(other) enumerate_base_quantities(other.reciprocalize.to_hash) get_description return self end
Searches the system of known physical quantities (@@dimensions class array) looking for any which match self in terms of the configuration of base dimensions, i.e. an object which dimensionally represents the same thing.
If found, the name/description of that quantity is assigned to the @physical_quantity attribute of self.
This method is useful in cases where Dimensions
instances are manipulated using operators (e.g. multiply, divide, power, reciprocal), resulting in a change to the configuration of base dimensions (perhaps as a new instance). This method tries to find a description of the new quantity.
If none is found, self.physical_quantity is set to nil.
# File lib/quantify/dimensions.rb, line 245 def get_description similar = @@dimensions.find { |quantity| quantity == self } @physical_quantity = similar.nil? ? nil : similar.physical_quantity end
# File lib/quantify/dimensions.rb, line 218 def has_same_identity_as?(other) @physical_quantity == other.physical_quantity && !@physical_quantity.nil? end
Returns true if self represents one of the base quantities (i.e. length, mass, time, etc.)
# File lib/quantify/dimensions.rb, line 346 def is_base? base_quantities.size == 1 && self.instance_variable_get(base_quantities.first) == 1 ? true : false end
Returns true if self is a dimensionless quantity
# File lib/quantify/dimensions.rb, line 340 def is_dimensionless? base_quantities.empty? end
Returns true if the physical quantity that self represents is known
# File lib/quantify/dimensions.rb, line 335 def is_known? describe ? true : false end
Method for identifying quantities which are 'molar' quantities, i.e quantities which represent a quantity of something *per mole*
# File lib/quantify/dimensions.rb, line 361 def is_molar_quantity? denominator_quantities == [:@amount_of_substance] end
Method for identifying quantities which are 'specific' quantities, i.e quantities which represent a quantity of something *per unit mass*
# File lib/quantify/dimensions.rb, line 354 def is_specific_quantity? denominator_quantities == [:@mass] end
Load an already instantiated Dimensions
object into the @@dimensions class array, from which it will be accessible as a universal representation of that physical quantity.
Object
must include a non-nil @physical_quantity attribute, i.e. a name or description of the physical quantity represented.
# File lib/quantify/dimensions.rb, line 199 def load if describe && !loaded? @@dimensions << self elsif describe raise Exceptions::InvalidDimensionError, "A dimension instance with the same physical quantity already exists" else raise Exceptions::InvalidDimensionError, "Cannot load dimensions without physical quantity description" end end
# File lib/quantify/dimensions.rb, line 209 def loaded? Dimensions.dimensions.any? { |quantity| self.has_same_identity_as? quantity } end
Similar to multiply!
but returns a new Dimensions
instance representing the physical quantity which results from the multiplication.
# File lib/quantify/dimensions.rb, line 381 def multiply(other) Dimensions.new(self.to_hash).multiply! other end
Multiplies self by another Dimensions
object, returning self with an updated configuration of dimensions. Since this is likely to have resulted in the representation of a different physical quantity than was originally represented, the get_description
method is invoked to attempt to find a suitable description.
# File lib/quantify/dimensions.rb, line 372 def multiply!(other) enumerate_base_quantities(other.to_hash) get_description return self end
Similar to pow!
but returns a new Dimensions
instance representing the physical quantity which results from the raised power.
# File lib/quantify/dimensions.rb, line 423 def pow(power) Dimensions.new(self.to_hash).pow!(power) end
Raises self to the power provided. As with multiply and divide, the get_description
method is invoked to attempt to find a suitable description for the new quantity represented.
# File lib/quantify/dimensions.rb, line 408 def pow!(power) make_dimensionless if power == 0 if power < 0 self.reciprocalize! power *= -1 end original_dimensions = self.clone (power - 1).times { self.multiply!(original_dimensions) } get_description return self end
Similar to reciprocalize!
but returns a new Dimensions
instance representing the physical quantity which results from the inversion.
# File lib/quantify/dimensions.rb, line 444 def reciprocalize Dimensions.new(self.to_hash).reciprocalize! end
Inverts self, returning a representation of 1/self. This is equivalent to raising to the power -1. The get_description
method is invoked to attempt to find a suitable description for the new quantity represented.
# File lib/quantify/dimensions.rb, line 432 def reciprocalize! base_quantities.each do |variable| new_value = self.instance_variable_get(variable) * -1 self.instance_variable_set(variable, new_value) end get_description return self end
Returns an array representing the base SI units for the physical quantity described by self
If no argument is given, the array holds instances of Unit::Base
(or subclasses) which represent each base unit. Alternatively only the names or symbols of each unit can be returned by providing the appropriate unit attribute as a symbolized argument, e.g.
Dimensions.energy.si_base_units #=> [ #<Quantify::Unit: .. >, #<Quantify::Unit: .. >, ... ] Dimensions.energy.si_base_units :name #=> [ "metre squared", "per second squared", "kilogram"] # Dimensions.force.units :symbol #=> [ "m", "s^-2", "kg"]
# File lib/quantify/dimensions.rb, line 317 def si_base_units(by=nil) self.to_hash.map do |dimension,index| Unit.base_quantity_si_units.select do |unit| unit.measures == dimension.remove_underscores end.first.clone ** index end.map(&by).to_a end
Returns the SI unit for the physical quantity described by self.
Plane/solid angle are special cases which are dimensionless units, and so are handled explicitly. Otherwise, the si base units for each of the base dimensions of self are indentified and the corresponding compound unit is derived. If this new unit is the same as a known (SI derived) unit, the known unit is returned.
Dimensions.energy.units #=> #<Quantify::Dimensions: .. > Dimensions.energy.si_unit.name #=> 'joule' Dimensions.kinematic_viscosity.si_unit.name #=> 'metre squared per second'
# File lib/quantify/dimensions.rb, line 287 def si_unit return Unit.steridian if describe == 'solid angle' return Unit.radian if describe == 'plane angle' return si_base_units.inject(Unit.unity) do |compound,unit| compound * unit end.or_equivalent rescue return nil end
Returns an array containing the known units which represent the physical quantity described by self
If no argument is given, the array holds instances of Unit::Base
(or subclasses) which represent each unit. Alternatively only the names or symbols of each unit can be returned by providing the appropriate unit attribute as a symbolized argument, e.g.
Dimensions.energy.units #=> [ #<Quantify::Dimensions: .. >, #<Quantify::Dimensions: .. >, ... ] Dimensions.mass.units :name #=> [ 'kilogram', 'ounce', 'pound', ... ] Dimensions.length.units :symbol #=> [ 'm', 'ft', 'yd', ... ]
# File lib/quantify/dimensions.rb, line 267 def units(by=nil) Unit.units.select { |unit| unit.dimensions == self }.map(&by).to_a end
Remove from system of known units.
# File lib/quantify/dimensions.rb, line 214 def unload Dimensions.unload(self.physical_quantity) end
Protected Instance Methods
Returns an array containing the names of the instance variables which represent the base quantities of self. This enables various operations to be performed on these variables without touching the @physical_quantity variable.
# File lib/quantify/dimensions.rb, line 455 def base_quantities quantities = self.instance_variables if RUBY_VERSION < "1.9" quantities.delete("@physical_quantity") return quantities.map(&:to_sym) else quantities.delete(:@physical_quantity) return quantities end end
Just the base quantities which have negative indices
# File lib/quantify/dimensions.rb, line 472 def denominator_quantities base_quantities.select { |quantity| self.instance_variable_get(quantity) < 0 } end
Method for initializing the base quantities of self.
Where base quantities are already defined, the new indices are added to the existing ones. This represents the multiplication of base quantities (multiplication of similar quantities involves the addition of their powers).
This method is therefore used in the multiplication of Dimensions
objects, but also in divisions and raising of powers following other operations.
# File lib/quantify/dimensions.rb, line 498 def enumerate_base_quantities(options) options.each_pair do |base_quantity,index| base_quantity = base_quantity.to_s.downcase.to_sym unless index.is_a?(Integer) && BASE_QUANTITIES.include?(base_quantity) raise Exceptions::InvalidDimensionError, "An invalid base quantity was specified (#{base_quantity})" end variable = "@#{base_quantity}" if self.instance_variable_defined?(variable) new_index = self.instance_variable_get(variable) + index if new_index == 0 remove_instance_variable(variable) else self.instance_variable_set(variable, new_index) end else self.instance_variable_set(variable, index) end end end
Make object represent a dimensionless quantity.
# File lib/quantify/dimensions.rb, line 519 def make_dimensionless @physical_quantity = 'dimensionless' base_quantities.each { |var| remove_instance_variable(var) } end
Just the base quantities which have positive indices
# File lib/quantify/dimensions.rb, line 467 def numerator_quantities base_quantities.select { |quantity| self.instance_variable_get(quantity) > 0 } end
Returns a hash representation of the base dimensions of self. This is used in various operations and is useful for instantiating new objects with the same base dimensions.
# File lib/quantify/dimensions.rb, line 480 def to_hash hash = {} base_quantities.each do |variable| hash[variable.to_s.gsub("@","").to_sym] = self.instance_variable_get(variable) end return hash end