# -*- coding: utf-8 -*-
"""
Physical quantities.
"""
from __future__ import division
from sympy import (Abs, Add, AtomicExpr, Basic, Derivative, Function, Mul,
Pow, S, Symbol, sympify)
from sympy.core.compatibility import string_types
from sympy.physics.units import Dimension, dimensions
from sympy.physics.units.dimensions import dimsys_default, DimensionSystem
from sympy.physics.units.prefixes import Prefix
from sympy.utilities.exceptions import SymPyDeprecationWarning
[docs]class Quantity(AtomicExpr):
"""
Physical quantity: can be a unit of measure, a constant or a generic quantity.
"""
is_commutative = True
is_real = True
is_number = False
is_nonzero = True
_diff_wrt = True
def __new__(cls, name, abbrev=None, dimension=None, scale_factor=None,
latex_repr=None, pretty_unicode_repr=None,
pretty_ascii_repr=None, mathml_presentation_repr=None,
**assumptions):
if not isinstance(name, Symbol):
name = Symbol(name)
# For Quantity(name, dim, scale, abbrev) to work like in the
# old version of Sympy:
if not isinstance(abbrev, string_types) and not \
isinstance(abbrev, Symbol):
dimension, scale_factor, abbrev = abbrev, dimension, scale_factor
if dimension is not None:
SymPyDeprecationWarning(
deprecated_since_version="1.3",
issue=14319,
feature="Quantity arguments",
useinstead="SI_quantity_dimension_map",
).warn()
if scale_factor is not None:
SymPyDeprecationWarning(
deprecated_since_version="1.3",
issue=14319,
feature="Quantity arguments",
useinstead="SI_quantity_scale_factors",
).warn()
if abbrev is None:
abbrev = name
elif isinstance(abbrev, string_types):
abbrev = Symbol(abbrev)
obj = AtomicExpr.__new__(cls, name, abbrev)
obj._name = name
obj._abbrev = abbrev
obj._latex_repr = latex_repr
obj._unicode_repr = pretty_unicode_repr
obj._ascii_repr = pretty_ascii_repr
obj._mathml_repr = mathml_presentation_repr
if dimension is not None:
# TODO: remove after deprecation:
obj.set_dimension(dimension)
if scale_factor is not None:
# TODO: remove after deprecation:
obj.set_scale_factor(scale_factor)
return obj
### Currently only SI is supported: ###
# Dimensional representations for the SI units:
SI_quantity_dimension_map = {}
# Scale factors in SI units:
SI_quantity_scale_factors = {}
def set_dimension(self, dimension, unit_system="SI"):
from sympy.physics.units.dimensions import dimsys_default, DimensionSystem
if unit_system != "SI":
# TODO: add support for more units and dimension systems:
raise NotImplementedError("Currently only SI is supported")
dim_sys = dimsys_default
if not isinstance(dimension, dimensions.Dimension):
if dimension == 1:
dimension = Dimension(1)
else:
raise ValueError("expected dimension or 1")
else:
for dim_sym in dimension.name.atoms(Dimension):
if dim_sym not in [i.name for i in dim_sys._dimensional_dependencies]:
raise ValueError("Dimension %s is not registered in the "
"dimensional dependency tree." % dim_sym)
Quantity.SI_quantity_dimension_map[self] = dimension
def set_scale_factor(self, scale_factor, unit_system="SI"):
if unit_system != "SI":
# TODO: add support for more units and dimension systems:
raise NotImplementedError("Currently only SI is supported")
scale_factor = sympify(scale_factor)
# replace all prefixes by their ratio to canonical units:
scale_factor = scale_factor.replace(lambda x: isinstance(x, Prefix), lambda x: x.scale_factor)
# replace all quantities by their ratio to canonical units:
scale_factor = scale_factor.replace(lambda x: isinstance(x, Quantity), lambda x: x.scale_factor)
Quantity.SI_quantity_scale_factors[self] = scale_factor
@property
def name(self):
return self._name
@property
def dimension(self):
# TODO: add support for units other than SI:
return Quantity.SI_quantity_dimension_map[self]
@property
def abbrev(self):
"""
Symbol representing the unit name.
Prepend the abbreviation with the prefix symbol if it is defines.
"""
return self._abbrev
@property
def scale_factor(self):
"""
Overall magnitude of the quantity as compared to the canonical units.
"""
return Quantity.SI_quantity_scale_factors.get(self, S.One)
def _eval_is_positive(self):
return self.scale_factor.is_positive
def _eval_is_constant(self):
return self.scale_factor.is_constant()
def _eval_Abs(self):
scale_factor = Abs(self.scale_factor)
if scale_factor == self.scale_factor:
return self
return None
q = self.func(self.name, self.abbrev)
def _eval_subs(self, old, new):
if isinstance(new, Quantity) and self != old:
return self
@staticmethod
def get_dimensional_expr(expr):
if isinstance(expr, Mul):
return Mul(*[Quantity.get_dimensional_expr(i) for i in expr.args])
elif isinstance(expr, Pow):
return Quantity.get_dimensional_expr(expr.base) ** expr.exp
elif isinstance(expr, Add):
return Quantity.get_dimensional_expr(expr.args[0])
elif isinstance(expr, Derivative):
dim = Quantity.get_dimensional_expr(expr.expr)
for independent, count in expr.variable_count:
dim /= Quantity.get_dimensional_expr(independent)**count
return dim
elif isinstance(expr, Function):
args = [Quantity.get_dimensional_expr(arg) for arg in expr.args]
if all(i == 1 for i in args):
return S.One
return expr.func(*args)
elif isinstance(expr, Quantity):
return expr.dimension.name
return S.One
@staticmethod
def _collect_factor_and_dimension(expr):
"""Return tuple with factor expression and dimension expression."""
if isinstance(expr, Quantity):
return expr.scale_factor, expr.dimension
elif isinstance(expr, Mul):
factor = 1
dimension = Dimension(1)
for arg in expr.args:
arg_factor, arg_dim = Quantity._collect_factor_and_dimension(arg)
factor *= arg_factor
dimension *= arg_dim
return factor, dimension
elif isinstance(expr, Pow):
factor, dim = Quantity._collect_factor_and_dimension(expr.base)
exp_factor, exp_dim = Quantity._collect_factor_and_dimension(expr.exp)
if exp_dim.is_dimensionless:
exp_dim = 1
return factor ** exp_factor, dim ** (exp_factor * exp_dim)
elif isinstance(expr, Add):
factor, dim = Quantity._collect_factor_and_dimension(expr.args[0])
for addend in expr.args[1:]:
addend_factor, addend_dim = \
Quantity._collect_factor_and_dimension(addend)
if dim != addend_dim:
raise ValueError(
'Dimension of "{0}" is {1}, '
'but it should be {2}'.format(
addend, addend_dim.name, dim.name))
factor += addend_factor
return factor, dim
elif isinstance(expr, Derivative):
factor, dim = Quantity._collect_factor_and_dimension(expr.args[0])
for independent, count in expr.variable_count:
ifactor, idim = Quantity._collect_factor_and_dimension(independent)
factor /= ifactor**count
dim /= idim**count
return factor, dim
elif isinstance(expr, Function):
fds = [Quantity._collect_factor_and_dimension(
arg) for arg in expr.args]
return (expr.func(*(f[0] for f in fds)),
expr.func(*(d[1] for d in fds)))
elif isinstance(expr, Dimension):
return 1, expr
else:
return expr, Dimension(1)
def _latex(self, printer):
if self._latex_repr:
return self._latex_repr
else:
return r'\text{{{}}}'.format(self.args[1] \
if len(self.args) >= 2 else self.args[0])
[docs] def convert_to(self, other):
"""
Convert the quantity to another quantity of same dimensions.
Examples
========
>>> from sympy.physics.units import speed_of_light, meter, second
>>> speed_of_light
speed_of_light
>>> speed_of_light.convert_to(meter/second)
299792458*meter/second
>>> from sympy.physics.units import liter
>>> liter.convert_to(meter**3)
meter**3/1000
"""
from .util import convert_to
return convert_to(self, other)
@property
def free_symbols(self):
"""Return free symbols from quantity."""
return self.scale_factor.free_symbols