# -*- coding: utf-8 -*-
"""
Physical quantities.
"""
from __future__ import division
import numbers
from sympy import sympify, Expr, Number, Mul, Pow
from .units import Unit
#TODO: in operations, interpret a Unit as a quantity with factor 1
[docs]class Quantity(Expr):
"""
Physical quantity.
A quantity is defined from a factor and a unit.
"""
is_commutative = True
def __new__(cls, factor=1, unit=None, **assumptions):
if not isinstance(factor, str):
factor = sympify(factor)
# if the given unit is a number (because of some operations) and
# the factor is represented as a number, then return a number
if ((unit is None or isinstance(unit, (Number, numbers.Real)))
and isinstance(factor, (Number, numbers.Real))):
return factor * (unit or 1)
#TODO: if factor is of the form "1 m", parse the factor and the unit
if isinstance(factor, (Number, numbers.Real)):
if not isinstance(unit, Unit):
raise TypeError("'unit' should be a Unit instance; %s found"
% type(unit))
else:
raise NotImplementedError
obj = Expr.__new__(cls, factor, unit, **assumptions)
obj.factor, obj.unit = factor, unit
return obj
def __str__(self):
return "%g %s" % (self.factor, self.unit)
def __repr__(self):
return "%g %s" % (self.factor, repr(self.unit))
def __neg__(self):
return Quantity(-self.factor, self.unit)
[docs] def add(self, other):
"""
Add two quantities.
If the other object is not a quantity, raise an error.
Two quantities can be added only if they have the same unit: so we
convert first the other quantity to the same unit and, if it succedded,
then we add the factors.
"""
if isinstance(other, Quantity):
return Quantity(self.factor + other.convert_to(self.unit).factor,
self.unit)
else:
raise TypeError("Only quantities can be added")
def sub(self, other):
if isinstance(other, Quantity):
return Quantity(self.factor - other.convert_to(self.unit).factor,
self.unit)
else:
raise TypeError("Only quantities can be subtracted")
def mul(self, other):
other = sympify(other)
if isinstance(other, Quantity):
return Quantity(self.factor * other.factor,
self.unit.mul(other.unit))
elif isinstance(other, (Number, numbers.Real)):
return Quantity(self.factor * other, self.unit)
else:
return Mul(self, other)
def div(self, other):
other = sympify(other)
if isinstance(other, Quantity):
return Quantity(self.factor / other.factor,
self.unit.div(other.unit))
elif isinstance(other, (Number, numbers.Real)):
return Quantity(self.factor / other, self.unit)
else:
return Mul(self, Pow(other, -1))
def rdiv(self, other):
other = sympify(other)
if isinstance(other, Quantity):
return Quantity(other.factor / self.factor,
other.unit.div(self.unit))
elif isinstance(other, (Number, numbers.Real)):
return Quantity(other / self.factor, self.unit.pow(-1))
else:
return Mul(self**-1, other)
def pow(self, other):
other = sympify(other)
if isinstance(other, (Number, numbers.Real)):
f = self.factor**other
# without evalf a Pow instance is returned, and it can not be
# handled by Quantity.__new__
return Quantity(f.evalf(), self.unit.pow(other))
else:
return Pow(self, other)
@property
def as_unit(self):
"""
Convert the quantity to a unit.
"""
from .units import Unit
return Unit(self.unit, factor=self.factor)
[docs] def convert_to(self, unit):
"""
Convert the quantity to another (compatible) unit.
"""
if self.unit.is_compatible(unit) is False:
raise ValueError("Only compatible units can be converted; "
"'%s' found" % unit.dim)
return Quantity(self.factor * self.unit.factor / unit.factor, unit)