lib/unitwise/atom.rb
module Unitwise
# Atoms are the most basic elements in Unitwise. They are named coded and
# scaled units without prefixes, multipliers, exponents, etc. Examples are
# 'meter', 'hour', 'pound force'.
class Atom < Base
liner :classification, :property, :metric, :special, :arbitrary, :dim
include Compatible
class << self
# Array of hashes representing default atom properties.
# @api private
def data
@data ||= begin
properties = data_files.map do |file|
f = File.open(file)
::YAML.respond_to?(:unsafe_load) ? ::YAML.unsafe_load(f) : ::YAML.load(f)
end
properties.flatten
end
end
# Data files containing default atom data
# @api private
def data_files
%w(base_unit derived_unit).map { |type| Unitwise.data_file type }
end
end
# Determine if an atom is base level. All atoms that are not base are
# defined directly or indirectly in reference to a base atom.
# @return [true, false]
# @api public
def base?
!!(@dim && !scale)
end
# Determine if an atom is derived. Derived atoms are defined with respect
# to other atoms.
# @return [true, false]
# @api public
def derived?
!base?
end
# Determine if an atom is metric. Metric atoms can be combined with metric
# prefixes.
# @return [true, false]
# @api public
def metric
base? ? true : !!@metric
end
alias_method :metric?, :metric
# Determine if a unit is special. Special atoms are not defined on a
# traditional ratio scale.
# @return [true, false]
# @api public
def special
!!@special
end
alias_method :special?, :special
# Determine if a unit is arbitrary. Arbitrary atoms are not of any specific
# dimension and have no general meaning, therefore cannot be compared with
# any other unit.
# @return [true, false]
# @api public
def arbitrary
!!@arbitrary
end
alias_method :arbitrary?, :arbitrary
# Determine how far away a unit is from a base unit.
# @return [Integer]
# @api public
def depth
base? ? 0 : scale.depth + 1
end
memoize :depth
# Determine if this is the last atom in the scale chain
# @return [true, false]
# @api public
def terminal?
depth <= 3
end
# A representation of an atoms composition. Used to determine if two
# different atoms are compatible.
# @return [String]
# @api public
def dim
terminal? ? @dim || property : composition_string
end
# Set the atom's scale. It can be set as a Scale or a Functional
# @return [Unitwise::Functional, Unitwise::Scale]
# @api public
def scale=(attrs)
@scale = if attrs[:function_code]
Functional.new(attrs[:value], attrs[:unit_code], attrs[:function_code])
else
Scale.new(attrs[:value], attrs[:unit_code])
end
end
# Get a numeric value that can be used to with other atoms to compare with
# or operate on. Base units have a scalar of 1.
# @return [Numeric]
# @api public
def scalar(magnitude = 1)
base? ? magnitude : scale.scalar(magnitude)
end
def magnitude(scalar = scalar())
special? ? scale.magnitude(scalar) : 1
end
# An atom may have a complex scale with several base atoms at various
# depths. This method returns all of this atoms base level terms.
# @return [Array] An array containing base Unitwise::Term
def root_terms
base? ? [Term.new(:atom_code => primary_code)] : scale.root_terms
end
memoize :root_terms
# A basic validator for atoms. It checks for the bare minimum properties
# and that it's scalar and magnitude can be resolved. Note that this method
# requires the units it depends on to already exist, so it is not used
# when loading the initial data from UCUM.
# @return [true] returns true if the atom is valid
# @raise [Unitwise::DefinitionError]
def validate!
missing_properties = %i{primary_code names scale}.select do |prop|
val = liner_get(prop)
val.nil? || (val.respond_to?(:empty) && val.empty?)
end
if !missing_properties.empty?
missing_list = missing_properties.join(',')
raise Unitwise::DefinitionError,
"Atom has missing properties: #{missing_list}."
end
msg = "Atom definition could not be resolved. Ensure that it is a base " \
"unit or is defined relative to existing units."
begin
!scalar.nil? && !magnitude.nil? || raise(Unitwise::DefinitionError, msg)
rescue Unitwise::ExpressionError
raise Unitwise::DefinitionError, msg
end
end
end
end