validating dimensional consistency of expression

105 views
Skip to first unread message

Ben

unread,
May 27, 2020, 4:37:33 PM5/27/20
to sympy
Hello,

I have a string written in Latex for which I know the dimensions of each symbol. My goal is to validate the dimensional consistency of the expression. I'm having trouble with substitution. For example,

>>> from sympy.physics.units import mass, length, time
>>> from sympy.physics.units.systems.si import dimsys_SI
>>> from sympy.parsing.latex import parse_latex
>>> eq = parse_latex("F = m a")
>>> eq
Eq(F, a*m)

I can get the symbols from that expression
>>> set_of_symbols_in_eq = eq.free_symbols

And for each symbol in the set I know what dimensions each has:
>>> Fdim = mass * length / time**2
>>> mdim = mass
>>> adim = length / time**2

When I try substituting the dimensions into the original expression, I get an error 
>>> eq.subs({F: Fdim, m: mdim, a: adim})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'F' is not defined

That is surprising, because F is a Symbol:
>>> eq.lhs
F
>>> type(eq.lhs)
<class 'sympy.core.symbol.Symbol'>

I think that error means that although F is a Symbol, there isn't a variable named F that points to the Symbol F?
If that's the case, I don't know how to access the symbols in the abstract syntax tree provided by eq
How would I indicate to SymPy that "F = m a" in eq has variables with certain dimensions?

My goal is to run 
>>> dimsys_SI.equivalent_dims(Fdim, mdim * adim)
True
without retyping the expression. 

I think I want something like the following, except with dimensions substituted for each symbol.
>>> dimsys_SI.equivalent_dims( eq.lhs, eq.rhs )
False

Aaron Meurer

unread,
May 27, 2020, 7:01:02 PM5/27/20
to sympy
You're right that you have to define the Python variable name to
access F like that. See
https://docs.sympy.org/latest/tutorial/gotchas.html.

You can get all the symbols in an expression with eq.free_symbols. Or
if you know the symbol is F you can just set

F = symbols('F')

since symbols with the same name are equal, so F will be the same as
the symbol F in the expression from parse_latex.

Aaron Meurer
> --
> You received this message because you are subscribed to the Google Groups "sympy" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to sympy+un...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/a4e2b3fe-27b2-45b8-a7f6-598caea772de%40googlegroups.com.

Ben

unread,
May 27, 2020, 8:35:32 PM5/27/20
to sympy
Thanks Aaron for your help. 

With your guidance, I solved my problem (though my use of eval() feels hacky).

>>> import sympy
>>> from sympy.physics.units import mass, length, time
>>> from sympy.physics.units.systems.si import dimsys_SI
>>> from sympy.parsing.latex import parse_latex
>>> eq = parse_latex("F = m a")
>>> F = mass * length / time**2
>>> m = mass
>>> a = length / time**2
>>> dimsys_SI.equivalent_dims( eval(str(eq.lhs)), eval(str(eq.rhs)) )
True


I used eval() rather than variable substitution
>>> Fdim = mass * length / time**2
>>> mdim = mass
>>> adim = length / time**2
>>> lhs_dim = eq.lhs.subs([(F, Fdim), (m, mdim), (a, adim)])
>>> rhs_dim = eq.rhs.subs([(F, Fdim), (m, mdim), (a, adim)])
because I was not able to figure out how to simplify the RHS 
>>> rhs_dim
Dimension(length/time**2)*Dimension(mass, M)

Even though the RHS dimensions simplify to be equivalent to the LHS, I get an error when I compare the LHS and RHS: 
>>> dimsys_SI.equivalent_dims( lhs_dim, rhs_dim )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/dist-packages/sympy/physics/units/dimensions.py", line 455, in equivalent_dims
    deps2 = self.get_dimensional_dependencies(dim2)
  File "/usr/local/lib/python3.6/dist-packages/sympy/physics/units/dimensions.py", line 448, in get_dimensional_dependencies
    dimdep = self._get_dimensional_dependencies_for_name(name)
  File "/usr/local/lib/python3.6/dist-packages/sympy/physics/units/dimensions.py", line 422, in _get_dimensional_dependencies_for_name
    for k, v in d.items():
AttributeError: 'NoneType' object has no attribute 'items'


On Wednesday, May 27, 2020 at 7:01:02 PM UTC-4, Aaron Meurer wrote:
You're right that you have to define the Python variable name to
access F like that. See
https://docs.sympy.org/latest/tutorial/gotchas.html.

You can get all the symbols in an expression with eq.free_symbols. Or
if you know the symbol is F you can just set

F = symbols('F')

since symbols with the same name are equal, so F will be the same as
the symbol F in the expression from parse_latex.

Aaron Meurer

> To unsubscribe from this group and stop receiving emails from it, send an email to sy...@googlegroups.com.

Aaron Meurer

unread,
May 27, 2020, 8:59:42 PM5/27/20
to sympy
Why are you using eval(str(eq.lhs))? That should just give back eq.lhs.

Aaron Meurer
> To unsubscribe from this group and stop receiving emails from it, send an email to sympy+un...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/6e6c4358-7894-4099-9d27-74c5fefe2a31%40googlegroups.com.

Ben

unread,
May 27, 2020, 9:48:02 PM5/27/20
to sympy
I agree with your assessment. 

Here's my minimum working example where I don't use eval(str()) and get the problem.
>>> import sys
>>> sys.version
'3.6.9 (default, Apr 18 2020, 01:56:04) \n[GCC 8.4.0]'
>>> import sympy
>>> sympy.__version__
'1.5.1'
>>> from sympy.physics.units import mass, length, time
>>> from sympy.physics.units.systems.si import dimsys_SI
>>> from sympy.parsing.latex import parse_latex
>>> eq = parse_latex("F = m a")
>>> F = mass * length / time**2
>>> m = mass
>>> a = length / time**2
>>> dimsys_SI.equivalent_dims( eq.lhs, eq.rhs )
False

For context, here are the variables
>>> eq.lhs
F
>>> eq.rhs
a*m
>>> F
Dimension(length*mass/time**2)
>>> a
Dimension(length/time**2)
>>> a*m
Dimension(length*mass/time**2)
>>> m
Dimension(mass, M)


On Wednesday, May 27, 2020 at 8:59:42 PM UTC-4, Aaron Meurer wrote:
Why are you using eval(str(eq.lhs))? That should just give back eq.lhs.

Aaron Meurer

Ben

unread,
May 28, 2020, 8:08:25 AM5/28/20
to sympy
Good morning,

To verify that the issue is not due to using an old version, I re-ran the same example with the current development version of SymPy.

>>> import sys
>>> sys.version
'3.6.9 (default, Apr 18 2020, 01:56:04) \n[GCC 8.4.0]'
>>> sympy.__version__
>>> from sympy.physics.units import mass, length, time
>>> from sympy.physics.units.systems.si import dimsys_SI
>>> from sympy.parsing.latex import parse_latex
>>> eq = parse_latex("F = m a")
>>> F = mass * length / time**2
>>> m = mass
>>> a = length / time**2
>>> dimsys_SI.equivalent_dims( eq.lhs, eq.rhs )
False
>>> dimsys_SI.equivalent_dims( eval(str(eq.lhs)), eval(str(eq.rhs)) )
True

Should I open an issue on github for this?

Aaron Meurer

unread,
Jun 2, 2020, 11:46:41 AM6/2/20
to sympy
The issue here is you're confusing Python variables and SymPy symbols.
The use of eval() isn't helping, because eval() evaluates based on the
Python variables (I would try to avoid eval() if possible). See
https://docs.sympy.org/latest/tutorial/gotchas.html. Once you define
F, m, and a, they have no bearing on the symbols that are present in
eq. SymPy has no way to know that there are Python variables named F,
m, and a when looking at eq.

What you can do is

F, m, a = symbols('F m a')
eq.subs({F: mass*length/time**2, m: mass, a: length/time**2}).

to replace the symbols with units in the equation.

Aaron Meurer
> To unsubscribe from this group and stop receiving emails from it, send an email to sympy+un...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/35ed29bb-4bdf-4229-92a0-26b3b1449092%40googlegroups.com.

Ben

unread,
Jun 2, 2020, 3:01:19 PM6/2/20
to sympy
Hello,

I appreciate your patience in explaining my issue. Checking dimensions in Sympy is useful for my work, so I am seeking to implement the check correctly.
I understand what you're saying about Python variables versus Sympy Symbols.  There are two examples in my response below. The first ("Example 1") is an attempt to use the substitution-based approach.  In Example 2 I realized I could reduce my issue to three lines (which exclude irrelevant aspects of Latex and Python variables). 

Example 1 using latex to Sympy to dimensions
Here's what I get when I use substitution of dimensions:
>>> import sys
>>> sys.version
'3.6.9 (default, Apr 18 2020, 01:56:04) \n[GCC 8.4.0]'
>>> import sympy
>>> sympy.__version__
>>> from sympy.physics.units import mass, length, time
>>> from sympy.physics.units.systems.si import dimsys_SI
>>> from sympy.parsing.latex import parse_latex

Define the expression in Latex, then convert to Sympy expression
>>> eq = parse_latex("F = m a")
>>> F, m, a = sympy.symbols('F m a')
These three on the left side are Python variables

For each Sympy symbol in eq, replace with the Sympy dimensions
>>> eq.subs({F: mass*length/time**2, m: mass, a: length/time**2})
False
That boolean return wasn't what I was aiming for, so below I break the process down

Separate substitution into two steps and review the output
>>> eq.lhs.subs({F: mass*length/time**2, m: mass, a: length/time**2})
Dimension(length*mass/time**2)
>>> eq.rhs.subs({F: mass*length/time**2, m: mass, a: length/time**2})
Dimension(length/time**2)*Dimension(mass, M)

Now that I have confidence the Sympy substitution is behaving as expected, save the results to two Python variables
>>> RHS = eq.rhs.subs({F: mass*length/time**2, m: mass, a: length/time**2})
>>> RHS
Dimension(length/time**2)*Dimension(mass, M)
>>> type(RHS)
<class 'sympy.core.mul.Mul'>
That Mul is potentially the cause of my issue with dimsys_SI.equivalent_dims below. If there is a way to simplify the Mul of Dimension class objects, that would probably eliminate the issue.

The left-hand side of eq returns a Dimension class object
>>> LHS = eq.lhs.subs({F: mass*length/time**2, m: mass, a: length/time**2})
>>> LHS
Dimension(length*mass/time**2)
>>> type(LHS)
<class 'sympy.physics.units.dimensions.Dimension'>

The issue of failing to compare the left-side dimensions and the right-side dimensions could be due to the Mul, but I do not know how to simplify RHS. Or I may be incorrectly supplying Python variables LHS and RHS
>>> dimsys_SI.equivalent_dims( RHS, LHS )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/dist-packages/sympy/physics/units/dimensions.py", line 454, in equivalent_dims
    deps1 = self.get_dimensional_dependencies(dim1)
  File "/usr/local/lib/python3.6/dist-packages/sympy/physics/units/dimensions.py", line 448, in get_dimensional_dependencies
    dimdep = self._get_dimensional_dependencies_for_name(name)
  File "/usr/local/lib/python3.6/dist-packages/sympy/physics/units/dimensions.py", line 422, in _get_dimensional_dependencies_for_name
    for k, v in d.items():
AttributeError: 'NoneType' object has no attribute 'items'

After writing my example 1, I realized there is a simpler example of the issue.

Example 2: the short version

>>> import sys
>>> sys.version
'3.6.9 (default, Apr 18 2020, 01:56:04) \n[GCC 8.4.0]'
>>> import sympy
>>> sympy.__version__
>>> from sympy.physics.units import mass, length
>>> from sympy.physics.units.systems.si import dimsys_SI
>>> sympy.Mul(mass, length)
Dimension(length, L)*Dimension(mass, M)
>>> mass*length
Dimension(length*mass)
The above two lines appear equivalent but not equal

Same issue as in Example 1,
>>> dimsys_SI.equivalent_dims( sympy.Mul(mass, length), mass*length )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/sympy/physics/units/dimensions.py", line 461, in equivalent_dims
    deps1 = self.get_dimensional_dependencies(dim1)
  File "/opt/sympy/physics/units/dimensions.py", line 455, in get_dimensional_dependencies
    dimdep = self._get_dimensional_dependencies_for_name(name)
  File "/opt/sympy/physics/units/dimensions.py", line 429, in _get_dimensional_dependencies_for_name
    for k, v in d.items():
AttributeError: 'NoneType' object has no attribute 'items'

I think the reason is the Mul. However, I do not know how to simplify to Dimension
>>> type(mass*length)
<class 'sympy.physics.units.dimensions.Dimension'>
>>> type(sympy.Mul(mass, length))
<class 'sympy.core.mul.Mul'>
>>> sympy.simplify(sympy.Mul(mass, length))
Dimension(length, L)*Dimension(mass, M)


Kindly,

Ben
Reply all
Reply to author
Forward
0 new messages