Repository: sympy/sympy

Issue:
Implement Abs() in Quantity.get_dimensional_expr
On the current master, I get this exception:
```
# This should not fail
from sympy.physics.units import Quantity, Dimension, length, time, meter, second
from sympy import Abs
v_w1 = Quantity('v_w1', length/time, meter/second)
v_w2 = Quantity('v_w2', length/time, meter/second)
v_w3 = Quantity('v_w3', length/time, meter/second)
try: expr = v_w3 - Abs(v_w1 - v_w2)
except Exception, error: print error

summation of quantities of incompatible dimensions
```
This is because:
```
expr = Abs(v_w1 - v_w2)
print Quantity.get_dimensional_expr(expr)

1
```
This was also shown in [PR#12961](https://github.com/sympy/sympy/pull/12961), where the same problem was resolved for `Quantity._collect_factor_and_dimension()`:
```
print Quantity._collect_factor_and_dimension(expr)

(0, Dimension(length/time))
```


Discussion:
schymans: To confirm, this works now:
```
from sympy.physics.units import Quantity, Dimension, length, time, meter, second
from sympy import Abs
v_w1 = Quantity('v_w1', length/time, meter/second)
v_w2 = Quantity('v_w2', length/time, meter/second)
v_w3 = Quantity('v_w3', length/time, meter/second)
expr = v_w3 - Abs(v_w1 - v_w2)
print Quantity.get_dimensional_expr(expr)

Abs(length/time)
```

schymans: Here is another use case confirming the utility of this PR. The pH value of a solution is defined as the negative logarithm of the hydrogen ion concentration (cH):
```
from sympy import log, exp
from sympy.physics.units import amount_of_substance, volume, mol,\
    meter, Dimension, Quantity, convert_to
cH = Quantity('cH', amount_of_substance/volume)
pH = -log(cH)
# Obtaining the dimension of pH:
dim_pH = Quantity.get_dimensional_expr(pH)
print dim_pH
# Defining a new quantity, pH, with the correct dimensions
pH = Quantity('pH', Dimension(dim_pH))
print Quantity.get_dimensional_expr(pH)
# Getting the dimensions of the underlying cH:
print Quantity.get_dimensional_expr(exp(pH))
```
On the current master, the above returns a meaningless result:
```
1
1
1
```
Using this PR, we obtain:
```
log(amount_of_substance/volume)
log(amount_of_substance/volume)
amount_of_substance/volume
```
Actually, the above is not quite correct, as the pH is defined as the negative log to base 10. So it should be written in the following way:
```
pH = -log(cH, 10)
print pH
print 10**(-pH)
# Obtaining the dimension of pH:
dim_pH = Quantity.get_dimensional_expr(pH)
print dim_pH
```
This returns:
```
-log(cH)/log(10)
cH
zoo*log(amount_of_substance/volume)
```
`zoo` stands for complex infinity. The correct result should be: 
```
log(amount_of_substance/volume)/log(10)
```



Patch:
diff --git a/sympy/physics/units/dimensions.py b/sympy/physics/units/dimensions.py
index aeb624fa66..f0d9a42204 100644
--- a/sympy/physics/units/dimensions.py
+++ b/sympy/physics/units/dimensions.py
@@ -17,7 +17,7 @@
 import collections
 
 from sympy.core.compatibility import reduce, string_types
-from sympy import sympify, Integer, Matrix, Symbol, S
+from sympy import sympify, Integer, Matrix, Symbol, S, Abs
 from sympy.core.expr import Expr
 
 
@@ -185,8 +185,14 @@ def get_dimensional_dependencies(self, mark_dimensionless=False):
             return {'dimensionless': 1}
         return dimdep
 
-    @staticmethod
-    def _get_dimensional_dependencies_for_name(name):
+    @classmethod
+    def _from_dimensional_dependencies(cls, dependencies):
+        return reduce(lambda x, y: x * y, (
+            Dimension(d)**e for d, e in dependencies.items()
+        ))
+
+    @classmethod
+    def _get_dimensional_dependencies_for_name(cls, name):
 
         if name.is_Symbol:
             if name.name in Dimension._dimensional_dependencies:
@@ -211,6 +217,17 @@ def _get_dimensional_dependencies_for_name(name):
             dim = Dimension._get_dimensional_dependencies_for_name(name.base)
             return {k: v*name.exp for (k, v) in dim.items()}
 
+        if name.is_Function:
+            args = (Dimension._from_dimensional_dependencies(
+                Dimension._get_dimensional_dependencies_for_name(arg)
+            ) for arg in name.args)
+            result = name.func(*args)
+
+            if isinstance(result, cls):
+                return result.get_dimensional_dependencies()
+            # TODO shall we consider a result that is not a dimension?
+            # return Dimension._get_dimensional_dependencies_for_name(result)
+
     @property
     def is_dimensionless(self):
         """
diff --git a/sympy/physics/units/quantities.py b/sympy/physics/units/quantities.py
index 28df5e26c4..4c60bb54d2 100644
--- a/sympy/physics/units/quantities.py
+++ b/sympy/physics/units/quantities.py
@@ -7,7 +7,7 @@
 from __future__ import division
 
 from sympy.core.compatibility import string_types
-from sympy import sympify, Mul, Pow, S, Symbol, Add, AtomicExpr, Basic
+from sympy import Abs, sympify, Mul, Pow, S, Symbol, Add, AtomicExpr, Basic, Function
 from sympy.physics.units import Dimension
 from sympy.physics.units import dimensions
 from sympy.physics.units.prefixes import Prefix
@@ -88,6 +88,11 @@ def _eval_is_positive(self):
     def _eval_is_constant(self):
         return self.scale_factor.is_constant()
 
+    def _eval_Abs(self):
+        # FIXME prefer usage of self.__class__ or type(self) instead
+        return self.func(self.name, self.dimension, Abs(self.scale_factor),
+                         self.abbrev)
+
     @staticmethod
     def get_dimensional_expr(expr):
         if isinstance(expr, Mul):
@@ -96,6 +101,9 @@ def get_dimensional_expr(expr):
             return Quantity.get_dimensional_expr(expr.base) ** expr.exp
         elif isinstance(expr, Add):
             return Quantity.get_dimensional_expr(expr.args[0])
+        elif isinstance(expr, Function):
+            fds = [Quantity.get_dimensional_expr(arg) for arg in expr.args]
+            return expr.func(*fds)
         elif isinstance(expr, Quantity):
             return expr.dimension.name
         return 1


Test patch:
diff --git a/sympy/physics/units/tests/test_quantities.py b/sympy/physics/units/tests/test_quantities.py
index 560683b00c..d1d672b461 100644
--- a/sympy/physics/units/tests/test_quantities.py
+++ b/sympy/physics/units/tests/test_quantities.py
@@ -110,6 +110,19 @@ def test_add_sub():
     # TODO: eventually add this:
     # assert (u - v).convert_to(u) == S.Half*u
 
+def test_abs():
+    v_w1 = Quantity('v_w1', length/time, meter/second)
+    v_w2 = Quantity('v_w2', length/time, meter/second)
+    v_w3 = Quantity('v_w3', length/time, meter/second)
+    expr = v_w3 - Abs(v_w1 - v_w2)
+
+    Dq = Dimension(Quantity.get_dimensional_expr(expr))
+    assert Dimension.get_dimensional_dependencies(Dq) == {
+        'length': 1,
+        'time': -1,
+    }
+    assert meter == sqrt(meter**2)
+
 
 def test_check_unit_consistency():
     return  # TODO remove