lib/unitwise/term.rb
module Unitwise
# A Term is the combination of an atom, prefix, factor and annotation.
# Not all properties have to be present. Examples: 'g', 'mm', 'mi2', '4[pi]',
# 'kJ{Electric Potential}'
class Term < Liner.new(:atom, :prefix, :factor, :exponent, :annotation)
include Compatible
# Set the atom.
# @param value [String, Atom] Either a string representing an Atom, or an
# Atom
# @api public
def atom=(value)
value.is_a?(Atom) ? super(value) : super(Atom.find(value.to_s))
end
# Set the prefix.
# @param value [String, Prefix] Either a string representing a Prefix, or
# a Prefix
def prefix=(value)
value.is_a?(Prefix) ? super(value) : super(Prefix.find(value.to_s))
end
# Is this term special?
# @return [true, false]
def special?
atom.special? rescue false
end
# Determine how far away a unit is from a base unit.
# @return [Integer]
# @api public
def depth
atom ? atom.depth + 1 : 0
end
memoize :depth
# Determine if this is the last term in the scale chain
# @return [true, false]
# @api public
def terminal?
depth <= 3
end
# The multiplication factor for this term. The default value is 1.
# @return [Numeric]
# @api public
def factor
super || 1
end
# The exponent for this term. The default value is 1.
# @return [Numeric]
# @api public
def exponent
super || 1
end
# The unitless scalar value for this term.
# @param magnitude [Numeric] The magnitude to calculate the scalar for.
# @return [Numeric] The unitless linear scalar value.
# @api public
def scalar(magnitude = 1)
calculate(atom ? atom.scalar(magnitude) : magnitude)
end
# Calculate the magnitude for this term
# @param scalar [Numeric] The scalar for which you want the magnitude
# @return [Numeric] The magnitude on this scale.
# @api public
def magnitude(scalar = scalar())
calculate(atom ? atom.magnitude(scalar) : 1)
end
# The base units this term is derived from
# @return [Array] An array of Unitwise::Term
# @api public
def root_terms
if terminal?
[self]
else
atom.scale.root_terms.map do |t|
self.class.new(:atom => t.atom, :exponent => t.exponent * exponent)
end
end
end
memoize :root_terms
# Term multiplication. Multiply by a Unit, another Term, or a Numeric.
# params other [Unit, Term, Numeric]
# @return [Term]
def *(other)
operate('*', other) ||
fail(TypeError, "Can't multiply #{ self } by #{ other }.")
end
# Term division. Divide by a Unit, another Term, or a Numeric.
# params other [Unit, Term, Numeric]
# @return [Term]
def /(other)
operate('/', other) ||
fail(TypeError, "Can't divide #{ self } by #{ other }.")
end
# Term exponentiation. Raise a term to a numeric power.
# params other [Numeric]
# @return [Term]
def **(other)
if other.is_a?(Numeric)
self.class.new(to_hash.merge(:exponent => exponent * other))
else
fail TypeError, "Can't raise #{self} to #{other}."
end
end
def to_s(mode = :primary_code)
[
(factor if factor != 1),
(prefix.send(mode) if prefix),
(atom.send(mode) if atom),
(exponent if exponent != 1)
].compact.join('')
end
private
# @api private
def calculate(value)
(factor * (prefix ? prefix.scalar : 1) * value) ** exponent
end
# Multiply or divide a term
# @api private
def operate(operator, other)
exp = operator == '/' ? -1 : 1
if other.respond_to?(:terms)
Unit.new(other.terms.map { |t| t ** exp } << self)
elsif other.respond_to?(:atom)
Unit.new([self, other ** exp])
elsif other.is_a?(Numeric)
self.class.new(to_hash.merge(:factor => factor.send(operator, other)))
end
end
end
end