Convert string of a named expression in sympy to the expression itself - python

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:
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.
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.
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.
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):
From string to sympy expression
https://docs.sympy.org/latest/tutorials/intro-tutorial/basic_operations.html
https://docs.sympy.org/latest/modules/parsing.html
https://docs.sympy.org/latest/modules/core.html#sympy.core.sympify.sympify
https://docs.sympy.org/latest/tutorials/intro-tutorial/manipulation.html

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

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.

Related

How can I dynamically generate a python symbol statement?

I am trying to write a routine that normalizes (rewrites) a mathematical equation that may have more than one symbol on the LHS so that it only has one.
The following code illustrates what I want to do
Assume I have an equation
ln(x)-ln(x1)= -(a+by)
I want to solve for x or return
x=x1*exp(-a+by)
Using sympy I can do the following
from sympy import *
formula=' log(x)-log(x1) =-(a+b*y)'
lhs,rhs=formula.split('=',1)
x,x_1,y,a,b,y=symbols('x x_1 y a b y')
r=sympy.solve(eval(lhs)-eval(rhs),x)
r
==>
Output: [x1*exp(-a - b*y)]
I am trying to automate this for a range of input lines as follows
from sympy import *
import re
# eventually to be read ina loop from a file
formula="DLOG(SAUMMCREDBISCN/SAUNECONPRVTXN) =-0.142368233181-0.22796245228*(LOG(SAUMMCREDBISCN(-1)/SAUNECONPRVTXN(-1))+0.2*((SAUMMLOANINTRCN(-1)-SAUINTR(-1))/100)-LOG(SAUNYGDPMKTPKN(-1)))+0.576050997065*SAUNYGDPGAP_/100"
#try to convert formula into a string containing just the synbols
sym1=formula.replace("*"," ")
sym1=sym1.replace("DLOG"," ")
sym1=sym1.replace("LOG"," ")
sym1=sym1.replace("EXP"," ")
sym1=sym1.replace("RECODE"," ")
sym1=re.sub('[()/+-\=]',' ',sym1)
sym1=re.sub(' +',' ',sym1)
#This logic works for this particular formula
sym1
#now generate a string that has, instead of spaces between symbols
ss2=sym1.replace(' ',',')
#This is the part that does not work I want to generate a command that effectively says
#symbol,symbol2,..,symboln=symbols('symbol1 symbol2 ... symboln')
#tried this but it fails
eval(ss2)=symbols(sym1)
Generates the result
eval(ss2)=symbols(sym1)
^
SyntaxError: can't assign to function call
Any help for this py noob, would be greatly appreciated.
var('a b c') will inject symbol name 'a', 'b', 'c' into the namespace but perhaps #Blorgbeard is asking about lists or dicts because instead of creating many symbols you could put the symbols in a dictionary and then access them by name:
>>> formula=' log(x)-log(x1) =-(a+b*y)'
>>> eq = Eq(*map(S, formula.split('=', 1)))
>>> v = dict([(i.name, i) for i in eq.free_symbols]); v
{'y': y, 'b': b, 'x': x, 'x1': x1, 'a': a}
>>> solve(eq, v['x'])
[x1*exp(-a - b*y)]
So it is not actually necessary to use eval or to have a variable matching a symbol name: S can convert a string to an expression and free_symbols can identify which symbols are present. Putting them in a dictionary with keys being the Symbol name allows them to be retrieved from the dictionary with a string.
I think what you want to generate a string from these two statements and then you can eval that string
str_eq = f'{ss2} = {symbols(sym1)}'
print(str_eq)
>>' ,SAUMMCREDBISCN,SAUNECONPRVTXN,SAUMMCREDBISCN,SAUNECONPRVTXN,SAUMMLOANINTRCN,SAUINTR,SAUNYGDPMKTPKN,SAUNYGDPGAP_, = (SAUMMCREDBISCN, SAUNECONPRVTXN, SAUMMCREDBISCN, SAUNECONPRVTXN, SAUMMLOANINTRCN, SAUINTR, SAUNYGDPMKTPKN, SAUNYGDPGAP_)'
The first line says - give me a string but run the python code between the {} before returning it. For instance
print(f'Adding two numbers: (2+3) = {2 + 3}')
>> Adding two numbers: (2+3) = 5
First, maybe this could help make your code a bit denser.
If I understand correctly, you're trying to assign a variable using a list of names. You could use vars()[x]=:
import re
from sympy import symbols
symbol_names = re.findall("SAU[A-Z_]+",formula)
symbol_objs = symbols(symbol_names)
for sym_name,sym_obj in zip(symbol_names,symbol_objs):
vars()[sym] = sym_obj
A colleague of mine (Ib Hansen) gave me a very nice and elegant solution to the original problem (how to solve for the complicated expression) that by-passed the string manipulation solution that my original question was struggling with and which most answers addressed.
His solution is to use sympify and solve from sympy
from sympy import sympify, solve
import re
def normalize(var,eq,simplify=False,manual=False):
'''normalize an equation with respect to var using sympy'''
lhs, rhs = eq.split('=')
kat = sympify(f'Eq({lhs},{rhs})')
var_sym = sympify(var)
out = str(solve(kat, var_sym,simplify=simplify,manual=manual))[1:-1]
return f'{var} = {out}'
This works perfectly
from sympy import sympify, solve
import re
def norm(var,eq,simplify=False,manual=False):
'''normalize an equation with respect to var using sympy'''
lhs, rhs = eq.split('=')
kat = sympify(f'Eq({lhs},{rhs})')
var_sym = sympify(var)
out = str(solve(kat, var_sym,simplify=simplify,manual=manual))[1:-1] # simplify takes for ever
return f'{var} = {out}'
``` Re write equation spelling out the dlog (delta log) function that python does no know, and putting log into lowe case '''
test8=norm('saummcredbiscn','log(saummcredbiscn/sauneconprvtxn) -log(saummcredbiscn(-1)/sauneconprvtxn(-1)) =-0.142368233181-0.22796245228*(log(saummcredbiscn(-1)/sauneconprvtxn(-1))+0.2*((saummloanintrcn(-1)-sauintr(-1))/100)-log(saunygdpmktpkn(-1)))+0.576050997065*saunygdpgap_/100')
print (test8)
with result
saummcredbiscn = 0.867301828366361*sauneconprvtxn*(saummcredbiscn(-1.0)/sauneconprvtxn(-1.0))**(19300938693/25000000000)*saunygdpmktpkn(-1.0)**(5699061307/25000000000)*exp(0.00576050997065*saunygdpgap_ + 0.00045592490456*sauintr(-1.0) - 0.00045592490456*saummloanintrcn(-1.0)

The derivative of the product of non-commutative functions

If I use functions in SymPy and call the diff method, the commutative property just gets ignored.
h = Function('h',real=True,commutative=False)(t)
R = Function('R',real=True,commutative=False)(t)
print(diff(R*h,t))
# returns:
R(t)*Derivative(h(t), t) + h(t)*Derivative(R(t), t)
Am I doing something wrong here? I just want the output to have R in the front always..
This is arguably a bug in SymPy, which determines the commutativity of a function from its arguments. See also this comment. It's not related to derivatives: simply printing h*R will expose the bug (the expression is presented as R(t)*h(t)).
Until this behavior is changed, it seems the only way to achieve the desired result is to declare t to be noncommutative:
t = Symbol('t', commutative=False)
h = Function('h', real=True)(t)
R = Function('R', real=True)(t)
print(diff(R*h, t))
prints
R(t)*Derivative(h(t), t) + Derivative(R(t), t)*h(t)

Preventing a multiplication expression evaluating in Sympy

I am generating an expression with two fractions, and want to pretty print as a whole expression with LaTeX, to then put on a worksheet.
E.g. in the form:
(5/7) * (3/4).
However, when I do the following:
fract1 = sympy.sympify(Fraction(5,7))
fract2 = sympy.sympify(Fraction(3,4))
expression = sympy.Mul(fract1,fract2,evaluate=False)
It returns
5*3/(7*4)
Clearly it is combining the fraction but not actually evaluating, but I want to be able to produce it in a format suitable as a question for a maths worksheet.
The next SymPy version will have UnevaluatedExpr:
In [4]: uexpr = UnevaluatedExpr(S.One*5/7)*UnevaluatedExpr(S.One*3/4)
In [7]: uexpr
Out[7]: 5/7⋅3/4
To release and evaluate it, just use .doit():
In [8]: uexpr.doit()
Out[8]:
15
──
28
LaTeX output looks like:
In [10]: print(latex(uexpr))
\frac{5}{7} \frac{3}{4}
This feature is available since SymPy 1.1. See the documentation to find out more.
Very hackish way to do it (just for your case of two fractions):
def print_fractions(expr):
print("({}) * ({})".format(*expr.args))
Works like this:
In: expr = sympy.Mul(sympy.S("5/7"), sympy.S("3/4"), evaluate=False)
In: expr
Out: 5*3/(7*4)
In: print_fractions(expr)
Out: (5/7) * (3/4)
You can check with srepr that the fractions are actually not combined in expr, it's just the way sympy decides to print it:
In: sympy.srepr(expr)
Out: 'Mul(Rational(5, 7), Rational(3, 4))'
Another approach is to extend sympy.Mul overriding __str__ method:
class MyMul(sympy.Mul):
def __str__(self):
return "({}) * ({})".format(*self.args)
Then you'll have:
In: expr = MyMul(sympy.S("5/7"), sympy.S("3/4"), evaluate=False)
In: print(expr)
Out: (5/7) * (3/4)
Eidt: how to make latex() to work
Hackish approach again, but:
class MyMul(Mul):
def _latex(self, _):
return r"\left({} \cdot {}\right)".format(*map(latex, self.args))
Now:
In: a = S("5/7")
In: b = S("3/4")
In: c = MyMul(a, b, evaluate=False)
In: print(latex(c))
Out: \left(\frac{5}{7} \cdot \frac{3}{4}\right)
Of course, you can change what exactly you're outputting in the definition of _latex above.

How to get the limsup / liminf value of a mathematical function in python?

For example, see the mathematica code below.
In[1]:= Max[Limit[Sin[x], x -> Infinity]]
Out[1]:= 1
In[2]:= Min[Limit[Sin[x], x -> Infinity]]
Out[2]:= -1
I want to do something like this in python(maybe Sympy).
Using sympy, evaluating limit(sin(x), x, oo) returns sin(oo), and it's not range or something.
So I'm confused how to get the upper/lower limit of it.
I think it's not so hard problem but I can find seldom information on that.
Can I do that in python?
Edit(2015/11/21)
I did some tries on the way #Teepeemm suggested, and still made no fruit.
This is the Try&error's product:
import sympy as sym
from scipy.optimize import differential_evolution
sym.init_printing()
x,y,z,l,u = sym.symbols('x y z l u')
f = sym.simplify(sym.sin(x) * sym.exp(-x))
df = sym.diff(f,x)
numeric_df = sym.lambdify(x, df)
numeric_sup_l_u = differential_evolution(numeric_df, [(l,u)])
sup_l_u = sym.sympify(numeric_sup_l_u["x"])
sup_l = sym.limit(sup_l_u, u, sym.oo)
print sym.simplify(sym.limit(sup_l, l, sym.oo))
The two problems on the way:
Using scipy functions require to lambdify expressions, but once I lambdify expressions, I cannot sympify valuables again.
I don't know how to give diferential_evolution to the range including sympy's valuable or +oo.

How to parse and simplify a string like '3cm/µs² + 4e-4 sqmiles/km/h**2' treating physical units correctly?

I'd like to split a string like 3cm/µs² + 4e-4 sqmiles/km/h**2 into its SI unit (in this case, m/s**2) and its magnitude (in multiples of that unit).
Since sympy provides both a parsing module and many physical units and SI prefixes, I guess using sympy would be a good idea. But what is a nice way to achieve this? I'd write an algorithm like the following, but I'd like to avoid reinventing a squared wheel:
Treat the transition between a number and a letter (except for the 4e-4 like syntax) and whitespace (unless its next to an explicit operator) as multiplication, then tokenize
Replace each non-numeric token by its SI representation (also checking for SI-prefixes)
Simplify the new expression down to Magnitude * some SI units (giving a meaningful error message on inconsistent units, e.g. Cannot add m**2 to s)
Can this be easily achieved via existing means? Or how would this be best implemented?
Units
A solution would be to gather all units from the SymPy units module and use them to substitute the symbols created by sympify
>>> import sympy.physics.units as u
... subs = {}
... for k, v in u.__dict__.items():
... if isinstance(v, Expr) and v.has(u.Unit):
... subs[Symbol(k)] = v # Map the `Symbol` for a unit to the unit
>>> # sympify returns `Symbol`s, `subs` maps them to `Unit`s
>>> print sympify('yard*millimeter/ly').subs(subs)
127*m/1313990343414000000000
If the symbol is not in units it will just be printed as unknown symbol (for instance barn)
>>> print sympify('barn/meter**2').subs(subs)
barn/m**2
But you can always add stuff to the subs dictionary.
>>> subs[Symbol('almost_meter')] = 0.9*u.meter
... sympify('almost_meter').subs(subs)
0.9*m
SI prefixes don't work exactly like you want them. You will need to add a multiplication sign (or hope that it is a common unit like km which is explicitly implemented). Moreover, as they are not Unit instances but rather Integer instance you will have to add them to subs:
>>> import sympy.physics.units as u
... subs = {}
... for k, v in u.__dict__.items():
... if (isinstance(v, Expr) and v.has(u.Unit)) or isinstance(v, Integer):
... subs[Symbol(k)] = v
>>> print sympify('mega*m').subs(subs)
1000000*m
For unicode you might need some preprocessing. I do not think SymPy makes any promises about unicode support.
If you implement new Units, please consider making a pull request with them on github. The file to edit should be sympy/physics/units.py.
Whitespaces and implicit multiplication
In the dev version of SymPy you can find code for assuming implicit multiplications where appropriate whitespaces are written:
>>> from sympy.parsing.sympy_parser import (parse_expr,
... standard_transformations, implicit_multiplication_application)
>>> parse_expr("10sin**2 x**2 + 3xyz + tan theta",
... transformations=(standard_transformations +
... (implicit_multiplication_application,)))
3*x*y*z + 10*sin(x**2)**2 + tan(theta)
Security
sympify uses eval which is exploitable if you are going to use it for a web facing app!
I've found astropy to have a good units module. After some preparation you can do
import astropy.units as u
from functools import reduce
u.Unit('MeV/fm').si #160.218 N
eval('1*MeV/fm+3*N',u.__dict__).si #163.21765649999998 N
from astropy.units import imperial
u.__dict__.update(imperial.__dict__)
u.sqmiles = u.mile**2
eval('3*cm/Ys**2 + 4e-4*sqmiles/km/h**2',u.__dict__).si #7.993790464000001e-08 m / s2
The following function adds scipy CODATA constants as quantities to astropy units
def units_and_constants():
"""
>>> u = units_and_constants()
>>> u.hartree_joule_relationship
<Quantity 4.35974434e-18 J>
>>> eval('1*MeV/fm+3*N',u.__dict__).si
<Quantity 163.21765649999998 N>
"""
import astropy.units as u
from astropy.units import imperial
u.__dict__.update(imperial.__dict__)
from scipy.constants import physical_constants, value, unit
import string
def qntty(x):
un = unit(x)
va = value(x)
if un:
return va*eval(un.strip().replace(' ','*').replace('^','**'),u.__dict__)
else:
return va
u.sr = u.radian**2
u.E_h = qntty('hartree-joule relationship')
u.c = qntty('speed of light in vacuum')
u.C_90 = (1+4.6e-8)*u.C
codata = {}
for n, t in physical_constants.items():
v = qntty(n)
for x in string.punctuation+' ':
n = n.replace(x,'_')
codata[n] = v
u.__dict__.update(codata)
return u
yt also tackles a problem similar to yours.
Have a look at the Test file to see how it is used.

Categories