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