Ask Your Question

Revision history [back]

pint is not part of Sage. Here you try to substitute some symbolic variables of a symbolic expression by elements of the pint module. This could only work if you could coerce pint units into the Symbolic Ring.

The way to do this is to define a coercion between pint units and the Symbolic Ring, you can have a look at the following docs:

  • http://www.sagemath.org/doc/tutorial/tour_coercion.html
  • http://www.sagemath.org/doc/reference/coercion/sage/structure/coerce.html
  • http://www.sagemath.org/doc/reference/coercion/

This should be easily doable by using the Sage units but i guess it is not what you would like to do since you will lose the consistency checking of pint, for example with Sage units, you can do:

sage: units.length.meter + units.time.second
meter + second

So perhaps a substitution i not what you would like to do. Perhaps, you should instead create a .physical_consistency() or .pint() (or whatever suitable name) method for symbolic elements, that recurses to the tree of the symbolic expression and make the units replacement from your dictionary.

Note that for powers you will experience some problems because the .__pow__() method is not able to deal with Sage integers:

sage: sage: ureg.meter^2
TypeError: Cannot power UnitsContainer by <type 'sage.rings.integer.Integer'>

pint is not part of Sage. Here you try to substitute some symbolic variables of a symbolic expression by elements of the pint module. This could only work if you could coerce pint units into the Symbolic Ring.

The way to do this is to define a coercion between pint units and the Symbolic Ring, you can have a look at the following docs:

  • http://www.sagemath.org/doc/tutorial/tour_coercion.html
  • http://www.sagemath.org/doc/reference/coercion/sage/structure/coerce.html
  • http://www.sagemath.org/doc/reference/coercion/

This should be easily doable by using the Sage units but i guess it is not what you would like to do since you will lose the consistency checking of pint, for example with Sage units, you can do:

sage: units.length.meter + units.time.second
meter + second

So perhaps a substitution i not what you would like to do. Perhaps, you should instead create a .physical_consistency() or .pint() (or whatever suitable name) method for symbolic elements, that recurses to the tree of the symbolic expression and make the units replacement from your dictionary.

Note that for powers you will experience some problems because the .__pow__() method is not able to deal with Sage integers:

sage: sage: ureg.meter^2
TypeError: Cannot power UnitsContainer by <type 'sage.rings.integer.Integer'>

EDIT Here is a prototype. The idea is to recursively unfold the symbolic expression and refold it with units:

def physical(expr, dico):
    r"""

    INPUT:

        - expr is a symbolic expression.
        - dico is a dictionnary that maps symbols to units

    The units are from

    OUTPUT:

        The resulting unit (with magnitude) or raise an error.    
    """
    op = expr.operator()
    dimensionless = (dico.values()[0] / dico.values()[0])
    if op:
        if op == operator.add:
            return sum([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.mul:
            return prod([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.pow: # todo : improve for dimensionless powers, like for log.
            return is_physical(expr.operands()[0], dico) ** float(expr.operands()[1])
        elif op in [exp, log, cos, sin]: # to enlarge
            if is_physical(expr.operands()[0], dico).dimensionality == dimensionless.dimensionality:
                return op(is_physical(expr.operands()[0], dico).magnitude) * dimensionless
            else:
                raise ValueError('The {} can only eat dimensionless numbers.'.format(op))
        else:
            raise ValueError('I do not know how to deal with {} operator (yet).'.format(op))
    else:
        if expr.is_symbol():
            if expr in dico:
                return dico[expr]
            else:
                raise ValueError('The symbol {} did not get any unit.'.format(expr))
        elif expr.is_numeric():
            return float(expr) * dimensionless
        elif expr in [e, pi]:
            return float(expr) * dimensionless
        else:
            raise ValueError('I do not know how to deal with {} operand (yet).'.format(expr))

The reason why i did not use expr.is_numeric(), but explicitely list e and pi is because things like NaN are constants, and i am not sure there will be physical constants (that are not dimensionless). If complex numbers might be involved, it could be nice to replace float by complex.

Here is a sample session:

sage: from pint import UnitRegistry
sage: ureg = UnitRegistry()    
sage: var('a b')
(a, b)
sage: d = {a: ureg.meter, b: ureg.meter^int(2)}
sage: physical(a^2, d)
<Quantity(1.0, 'meter ** 2')>
sage: physical(a*b, d)
<Quantity(1, 'meter ** 3')>
sage: physical(a+b, d)
DimensionalityError: Cannot convert from 'meter' ([length]) to 'meter ** 2' ([length] ** 2)
sage: physical(a^2+2*cos(b), d)
ValueError: The cos can only eat dimensionless numbers.
sage: physical(log(a^2/(pi*b)), d)
<Quantity(-1.14472988585, 'dimensionless')>
sage: physical(a^2+2*b, d)
<Quantity(3.0, 'meter ** 2')>
sage: physical(a^2+2*b*cos(pi+2), d)
<Quantity(1.83229367309, 'meter ** 2')>

If this corresponds to what you expected, we can try to improve and merge it within Sage, though i would have preferred to have Sage units working that way (which seems very hard within the Symbolic Ring framework that automatically simplifies too much).

pint is not part of Sage. Here you try to substitute some symbolic variables of a symbolic expression by elements of the pint module. This could only work if you could coerce pint units into the Symbolic Ring.

The way to do this is to define a coercion between pint units and the Symbolic Ring, you can have a look at the following docs:

  • http://www.sagemath.org/doc/tutorial/tour_coercion.html
  • http://www.sagemath.org/doc/reference/coercion/sage/structure/coerce.html
  • http://www.sagemath.org/doc/reference/coercion/

This should be easily doable by using the Sage units but i guess it is not what you would like to do since you will lose the consistency checking of pint, for example with Sage units, you can do:

sage: units.length.meter + units.time.second
meter + second

So perhaps a substitution i not what you would like to do. Perhaps, you should instead create a .physical_consistency() or .pint() (or whatever suitable name) method for symbolic elements, that recurses to the tree of the symbolic expression and make the units replacement from your dictionary.

Note that for powers you will experience some problems because the .__pow__() method is not able to deal with Sage integers:

sage: sage: ureg.meter^2
TypeError: Cannot power UnitsContainer by <type 'sage.rings.integer.Integer'>

EDIT Here is a prototype. The idea is to recursively unfold the symbolic expression and refold it with units:

def physical(expr, dico):
    r"""
     INPUT:

        - expr is a symbolic expression.
        - dico is a dictionnary that maps symbols to units

    The units are from
from the pint package, please do `sage -pip install pint` to let this function work.

    OUTPUT:

        The resulting unit (with magnitude) or raise an error.    
    """
    op = expr.operator()
    dimensionless = (dico.values()[0] / dico.values()[0])
    if op:
        if op == operator.add:
            return sum([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.mul:
            return prod([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.pow: # todo : improve for dimensionless powers, like for log.
            return is_physical(expr.operands()[0], dico) ** float(expr.operands()[1])
        elif op in [exp, log, cos, sin]: # to enlarge
            if is_physical(expr.operands()[0], dico).dimensionality == dimensionless.dimensionality:
                return op(is_physical(expr.operands()[0], dico).magnitude) * dimensionless
            else:
                raise ValueError('The {} can only eat dimensionless numbers.'.format(op))
        else:
            raise ValueError('I do not know how to deal with {} operator (yet).'.format(op))
    else:
        if expr.is_symbol():
            if expr in dico:
                return dico[expr]
            else:
                raise ValueError('The symbol {} did not get any unit.'.format(expr))
        elif expr.is_numeric():
            return float(expr) * dimensionless
        elif expr in [e, pi]:
            return float(expr) * dimensionless
        else:
            raise ValueError('I do not know how to deal with {} operand (yet).'.format(expr))

The reason why i did not use expr.is_numeric(), but explicitely list e and pi is because things like NaN are constants, and i am not sure there will be physical constants (that are not dimensionless). If complex numbers might be involved, it could be nice to replace float by complex.

Here is a sample session:

sage: from pint import UnitRegistry
sage: ureg = UnitRegistry()    
sage: var('a b')
(a, b)
sage: d = {a: ureg.meter, b: ureg.meter^int(2)}
sage: physical(a^2, d)
<Quantity(1.0, 'meter ** 2')>
sage: physical(a*b, d)
<Quantity(1, 'meter ** 3')>
sage: physical(a+b, d)
DimensionalityError: Cannot convert from 'meter' ([length]) to 'meter ** 2' ([length] ** 2)
sage: physical(a^2+2*cos(b), d)
ValueError: The cos can only eat dimensionless numbers.
sage: physical(log(a^2/(pi*b)), d)
<Quantity(-1.14472988585, 'dimensionless')>
sage: physical(a^2+2*b, d)
<Quantity(3.0, 'meter ** 2')>
sage: physical(a^2+2*b*cos(pi+2), d)
<Quantity(1.83229367309, 'meter ** 2')>

If this corresponds to what you expected, we can try to improve and merge it within Sage, though i would have preferred to have Sage units working that way (which seems very hard within the Symbolic Ring framework that automatically simplifies too much).

pint is not part of Sage. Here you try to substitute some symbolic variables of a symbolic expression by elements of the pint module. This could only work if you could coerce pint units into the Symbolic Ring.

. The way to do this is to define define a coercion between pint units and the Symbolic Ring, you can have a look at the following docs:

  • http://www.sagemath.org/doc/tutorial/tour_coercion.html
  • http://www.sagemath.org/doc/reference/coercion/sage/structure/coerce.html
  • http://www.sagemath.org/doc/reference/coercion/

This should be easily doable by using the Sage units but i guess it is not what you would like to do since you will lose the consistency checking of pint, for example with Sage units, you can do:

sage: units.length.meter + units.time.second
meter + second

So perhaps a substitution i not what you would like to do. Perhaps, you should instead create a .physical_consistency() or .pint() (or whatever suitable name) method for symbolic elements, that recurses to the tree of the symbolic expression and make the units replacement from your dictionary.

Note that for powers you will experience some problems because the .__pow__() method is not able to deal with Sage integers:

sage: sage: ureg.meter^2
TypeError: Cannot power UnitsContainer by <type 'sage.rings.integer.Integer'>

EDIT Here is a prototype. The idea is to recursively unfold the symbolic expression and refold it with units:

def physical(expr, dico):
    r"""
    INPUT:

        - expr is a symbolic expression.
        - dico is a dictionnary that maps symbols to units

    The units are from the pint package, please do `sage -pip install pint` to let this function work.

    OUTPUT:

        The resulting unit (with magnitude) or raise an error.    
    """
    op = expr.operator()
    dimensionless = (dico.values()[0] / dico.values()[0])
    if op:
        if op == operator.add:
            return sum([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.mul:
            return prod([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.pow: # todo : improve for dimensionless powers, like for log.
            return is_physical(expr.operands()[0], dico) ** float(expr.operands()[1])
        elif op in [exp, log, cos, sin]: # to enlarge
            if is_physical(expr.operands()[0], dico).dimensionality == dimensionless.dimensionality:
                return op(is_physical(expr.operands()[0], dico).magnitude) * dimensionless
            else:
                raise ValueError('The {} can only eat dimensionless numbers.'.format(op))
        else:
            raise ValueError('I do not know how to deal with {} operator (yet).'.format(op))
    else:
        if expr.is_symbol():
            if expr in dico:
                return dico[expr]
            else:
                raise ValueError('The symbol {} did not get any unit.'.format(expr))
        elif expr.is_numeric():
            return float(expr) * dimensionless
        elif expr in [e, pi]:
            return float(expr) * dimensionless
        else:
            raise ValueError('I do not know how to deal with {} operand (yet).'.format(expr))

The reason why i did not use expr.is_numeric(), but explicitely list e and pi is because things like NaN are constants, and i am not sure there will be physical constants (that are not dimensionless). If complex numbers might be involved, it could be nice to replace float by complex.

Here is a sample session:

sage: from pint import UnitRegistry
sage: ureg = UnitRegistry()    
sage: var('a b')
(a, b)
sage: d = {a: ureg.meter, b: ureg.meter^int(2)}
sage: physical(a^2, d)
<Quantity(1.0, 'meter ** 2')>
sage: physical(a*b, d)
<Quantity(1, 'meter ** 3')>
sage: physical(a+b, d)
DimensionalityError: Cannot convert from 'meter' ([length]) to 'meter ** 2' ([length] ** 2)
sage: physical(a^2+2*cos(b), d)
ValueError: The cos can only eat dimensionless numbers.
sage: physical(log(a^2/(pi*b)), d)
<Quantity(-1.14472988585, 'dimensionless')>
sage: physical(a^2+2*b, d)
<Quantity(3.0, 'meter ** 2')>
sage: physical(a^2+2*b*cos(pi+2), d)
<Quantity(1.83229367309, 'meter ** 2')>

If this corresponds to what you expected, we can try to improve and merge it within Sage, though i would have preferred to have Sage units working that way (which seems very hard within the Symbolic Ring framework that automatically simplifies too much).

pint is not part of Sage. Here you try to substitute some symbolic variables of a symbolic expression by elements of the pint module. This could only work if you could coerce pint units into the Symbolic Ring. The way to do this is to define a coercion between pint units and the Symbolic Ring, you can have a look at the following docs:

  • http://www.sagemath.org/doc/tutorial/tour_coercion.html
  • http://www.sagemath.org/doc/reference/coercion/sage/structure/coerce.html
  • http://www.sagemath.org/doc/reference/coercion/

This should be easily doable by using the Sage units but i guess it is not what you would like to do since you will lose the consistency checking of pint, for example with Sage units, you can do:

sage: units.length.meter + units.time.second
meter + second

So perhaps a substitution i not what you would like to do. Perhaps, you should instead create a .physical_consistency() or .pint() (or whatever suitable name) method for symbolic elements, that recurses to the tree of the symbolic expression and make the units replacement from your dictionary.

Note that for powers you will experience some problems because the .__pow__() method is not able to deal with Sage integers:

sage: sage: ureg.meter^2
TypeError: Cannot power UnitsContainer by <type 'sage.rings.integer.Integer'>

EDIT Here is a prototype. The idea is to recursively unfold the symbolic expression and refold it with units:

def physical(expr, dico):
    r"""
    INPUT:

        - expr is a symbolic expression.
        - dico is a dictionnary that maps symbols to units

    The units are from the pint package, please do `sage -pip install pint` to let this function work.

    OUTPUT:

        The resulting unit (with magnitude) or raise an error.    
    """
    op = expr.operator()
    dimensionless = (dico.values()[0] / dico.values()[0])
    if op:
        if op == operator.add:
            return sum([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.mul:
            return prod([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.pow: # todo : improve for dimensionless powers, like for log.
            return is_physical(expr.operands()[0], dico) ** float(expr.operands()[1])
        elif op in [exp, log, cos, sin]: # to enlarge
            if is_physical(expr.operands()[0], dico).dimensionality == dimensionless.dimensionality:
                return op(is_physical(expr.operands()[0], dico).magnitude) * dimensionless
            else:
                raise ValueError('The {} can only eat dimensionless numbers.'.format(op))
        else:
            raise ValueError('I do not know how to deal with {} operator (yet).'.format(op))
    else:
        if expr.is_symbol():
            if expr in dico:
                return dico[expr]
            else:
                raise ValueError('The symbol {} did not get any unit.'.format(expr))
        elif expr.is_numeric():
            return float(expr) * dimensionless
        elif expr in [e, pi]:
            return float(expr) * dimensionless
        else:
            raise ValueError('I do not know how to deal with {} operand (yet).'.format(expr))

The reason why i did not use expr.is_numeric()expr.is__constant(), but explicitely list e and pi is because things like NaN are constants, and i am not sure there will not be physical constants (that are not dimensionless). If complex numbers might be involved, it could be nice better to replace float by complex.

Here is a sample session:

sage: from pint import UnitRegistry
sage: ureg = UnitRegistry()    
sage: var('a b')
(a, b)
sage: d = {a: ureg.meter, b: ureg.meter^int(2)}
sage: physical(a^2, d)
<Quantity(1.0, 'meter ** 2')>
sage: physical(a*b, d)
<Quantity(1, 'meter ** 3')>
sage: physical(a+b, d)
DimensionalityError: Cannot convert from 'meter' ([length]) to 'meter ** 2' ([length] ** 2)
sage: physical(a^2+2*cos(b), d)
ValueError: The cos can only eat dimensionless numbers.
sage: physical(log(a^2/(pi*b)), d)
<Quantity(-1.14472988585, 'dimensionless')>
sage: physical(a^2+2*b, d)
<Quantity(3.0, 'meter ** 2')>
sage: physical(a^2+2*b*cos(pi+2), d)
<Quantity(1.83229367309, 'meter ** 2')>

If this corresponds to what you expected, we can try to improve and merge it within Sage, though i would have preferred to have Sage units working that way (which seems very hard within the Symbolic Ring framework that automatically simplifies too much).

pint is not part of Sage. Here you try to substitute some symbolic variables of a symbolic expression by elements of the pint module. This could only work if you could coerce pint units into the Symbolic Ring. The way to do this is to define a coercion between pint units and the Symbolic Ring, you can have a look at the following docs:

  • http://www.sagemath.org/doc/tutorial/tour_coercion.html
  • http://www.sagemath.org/doc/reference/coercion/sage/structure/coerce.html
  • http://www.sagemath.org/doc/reference/coercion/

This should be easily doable by using the Sage units but i guess it is not what you would like to do since you will lose the consistency checking of pint, for example with Sage units, you can do:

sage: units.length.meter + units.time.second
meter + second

So perhaps a substitution i not what you would like to do. Perhaps, you should instead create a .physical_consistency() or .pint() (or whatever suitable name) method for symbolic elements, that recurses to the tree of the symbolic expression and make the units replacement from your dictionary.

Note that for powers you will experience some problems because the .__pow__() method is not able to deal with Sage integers:

sage: sage: ureg.meter^2
TypeError: Cannot power UnitsContainer by <type 'sage.rings.integer.Integer'>

EDIT Here is a prototype. The idea is to recursively unfold the symbolic expression and refold it with units:

def physical(expr, dico):
    r"""
    INPUT:

        - expr is a symbolic expression.
        - dico is a dictionnary that maps symbols to units
units (possibly with magnitude)

    The units are from the pint package, please do `sage -pip install pint` to let this function work.

    OUTPUT:

        The resulting unit (with magnitude) or raise an error.    
    """
    op = expr.operator()
    dimensionless = (dico.values()[0] / dico.values()[0])
    if op:
        if op == operator.add:
            return sum([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.mul:
            return prod([is_physical(i, dico) for i in expr.operands()])
        elif op == operator.pow: # todo : improve for dimensionless powers, like for log.
            return is_physical(expr.operands()[0], dico) ** float(expr.operands()[1])
        elif op in [exp, log, cos, sin]: # to enlarge
            if is_physical(expr.operands()[0], dico).dimensionality == dimensionless.dimensionality:
                return op(is_physical(expr.operands()[0], dico).magnitude) * dimensionless
            else:
                raise ValueError('The {} can only eat dimensionless numbers.'.format(op))
        else:
            raise ValueError('I do not know how to deal with {} operator (yet).'.format(op))
    else:
        if expr.is_symbol():
            if expr in dico:
                return dico[expr]
            else:
                raise ValueError('The symbol {} did not get any unit.'.format(expr))
        elif expr.is_numeric():
            return float(expr) * dimensionless
        elif expr in [e, pi]:
            return float(expr) * dimensionless
        else:
            raise ValueError('I do not know how to deal with {} operand (yet).'.format(expr))

The reason why i did not use expr.is__constant(), but explicitely list e and pi is because things like NaN are constants, and i am not sure there will not be physical constants (that are not dimensionless). If complex numbers might be involved, it could be better to replace float by complex.

Here is a sample session:

sage: from pint import UnitRegistry
sage: ureg = UnitRegistry()    
sage: var('a b')
(a, b)
sage: d = {a: ureg.meter, b: ureg.meter^int(2)}
sage: physical(a^2, d)
<Quantity(1.0, 'meter ** 2')>
sage: physical(a*b, d)
<Quantity(1, 'meter ** 3')>
sage: physical(a+b, d)
DimensionalityError: Cannot convert from 'meter' ([length]) to 'meter ** 2' ([length] ** 2)
sage: physical(a^2+2*cos(b), d)
ValueError: The cos can only eat dimensionless numbers.
sage: physical(log(a^2/(pi*b)), d)
<Quantity(-1.14472988585, 'dimensionless')>
sage: physical(a^2+2*b, d)
<Quantity(3.0, 'meter ** 2')>
sage: physical(a^2+2*b*cos(pi+2), d)
<Quantity(1.83229367309, 'meter ** 2')>

If this corresponds to what you expected, we can try to improve and merge it within Sage, though i would have preferred to have Sage units working that way (which seems very hard within the Symbolic Ring framework that automatically simplifies too much).