0

Description of the practical problem:
I have defined many expression using sympy, as in

import sympy as sp
a, b = sp.symbols('a,b', real=True, positive=True)
Xcharles_YclassA_Zregion1 = 1.01 * a**1.01 * b**0.99
Xbob_YclassA_Zregion1 = 1.009999 * a**1.01 * b**0.99
Xbob_YclassA_Zregion2 = 1.009999 * a**1.01 * b**0.99000000001
...

So I have used the names of the expressions to describe options (e.g., charles, bob) within categories (e.g., X).

Now I want a function that takes two strings (e.g., 'Xcharles_YclassA_Zregion1' and 'Xbob_YclassA_Zregion1') and returns its simplified ratio (in this example, 1.00000099009999), so I can quickly check "how different" they are, in terms of result, not in terms of how they are written. E.g., 2*a and a*2 are the same for my objective.

How can I achieve this?

Notes:

  1. The expressions in the example are hardcoded for the sake of simplicity. But in my actual case they come from a sequence of many other expressions and operations.
  2. Not all combinations of options for all categories would exist. E.g., Xcharles_YclassA_Zregion2 may not exist. Actually, if I were to write a table for existing expression names, it would be sparsely filled.
  3. I guess rewriting my code using dict to store the table might solve my problem. But I would have to modify a lot of code for that.
  4. Besides the practical aspects of my objective, I don't know if there is any formal difference between Symbol (which is a specific class) and expression. From the sources I read (e.g., this) I did not arrive to a conclusion. This understanding may help in solving the question.

TL;DR - What I tried

I aimed at something like

def verify_ratio(vstr1, vstr2):
    """Compare the result of two different computations of the same quantity"""
    ratio = sp.N(sp.parsing.sympy_parser.parse_expr(vstr1)) / sp.parsing.sympy_parser.parse_expr(vstr2)
    print(vstr1 + ' / ' + vstr2, '=', sp.N(ratio))
    return

This did not work. Code below shows why

import sympy as sp
a, b = sp.symbols('a,b', real=True, positive=True)
expr2 = 1.01 * a**1.01 * b**0.99
print(type(expr2), '->', expr2)
    
expr2b = sp.parsing.sympy_parser.parse_expr('expr2')
print(type(expr2b), '->', expr2b)

expr2c = sp.N(sp.parsing.sympy_parser.parse_expr('expr2'))
print(type(expr2c), '->', expr2c)
#print(sp.N(sp.parsing.sympy_parser.parse_expr('expr2')))

expr2d = sp.sympify('expr2')
print(type(expr2d), '->', expr2d)

with output

<class 'sympy.core.mul.Mul'> -> 1.01*a**1.01*b**0.99
<class 'sympy.core.symbol.Symbol'> -> expr2
<class 'sympy.core.symbol.Symbol'> -> expr2
<class 'sympy.core.symbol.Symbol'> -> expr2

I need something that takes the string 'expr2' and returns the expression 1.01 * a**1.01 * b**0.99.


None of my attempts achieved the objective. Questions or links which did not help (at least for me):

  1. From string to sympy expression
  2. https://docs.sympy.org/latest/tutorials/intro-tutorial/basic_operations.html
  3. https://docs.sympy.org/latest/modules/parsing.html
  4. https://docs.sympy.org/latest/modules/core.html#sympy.core.sympify.sympify
  5. https://docs.sympy.org/latest/tutorials/intro-tutorial/manipulation.html
  • I think you're trying to blur the line between python names, like `expr2`, and the variables within sympy expressions, like `a` and `b` in your example. Don't think that's a good design intent... – Marcus Müller Jan 09 '23 at 12:47
  • @MarcusMüller - I heavily edited the OP, based on your comment. I have a practical objective. If that implies "blurring the line...", I am not sure. If that can be done, I would be glad to hear how. If that has associated risks/non-working cases/etc. (is that what you mean by not "a good design intent"? or what would that be?), I would also like to know them. Perhaps they are removed enough from my uses so I don't care. If there are good alternatives, I also welcome them; I would try rewriting as little code as possible, contingent upon other considerations. – sancho.s ReinstateMonicaCellio Jan 09 '23 at 13:30
  • Put your expressions into a data structure like a dict rather than loads of global variables. – Oscar Benjamin Jan 09 '23 at 16:10
  • @OscarBenjamin you could post `eval` as an answer. It works. – sancho.s ReinstateMonicaCellio Jan 09 '23 at 17:58

2 Answers2

1

If, when parsing, you want to use the expression that has been mapped to a variable you have to pass the dictionary that python uses to keep track of those mappings, i.e. locals()

>>> from sympy.abc import x
>>> from sympy import sympify, parse_expr
>>> y = x + 2
>>> sympify('y')
y
>>> sympify('y', locals=locals())
x + 2
>>> parse_expr('y', local_dict=locals())
x + 2
smichr
  • 16,948
  • 2
  • 27
  • 34
  • This worked. Of course, given I use `parse_expr` within my function `verify_ratio`, I have to pass `locals()` as another of its parameters. This is not very convenient, but not a no go either. – sancho.s ReinstateMonicaCellio Jan 09 '23 at 17:54
0

As suggested by Oscar Benjamin from the Sympy Google group, eval does the job

def verify_ratio(vstr1, vstr2):
    """Compare the result of two different computations of the same quantity"""
    print(vstr1 + ' / ' + vstr2, '=', eval(vstr1 + ' / ' + vstr2))
    return

What shows this would work is

>>> import sys
>>> import sympy as sp
>>> a, b = sp.symbols('a,b', real=True, positive=True)
>>> a is eval('a')
True

In my case, all expressions and symbols I am using in vstr1 and vstr2 are global. If nesting within other functions, I might need to pass further parameters to verify_ratio, as in the solution by smichr.