SymPy - Treating numbers as symbols - python

How can I treat numbers as symbols in SymPy?
For example, if I am performing a factorization with symbols I get:
from sympy import factor
factor('a*c*d + a*c*e + a*c*f + b*c*d + b*c*e + b*c*f')
c*(a + b)*(d + e + f)
I would like the same behaviour when I am using numbers in the expression.
Instead of
from sympy import factor
factor('2006*c*d + 2006*c*e + 2006*c*f + 2007*c*d + 2007*c*e + 2007*c*f')
4013*c*(d + e + f)
I would like to get
from sympy import factor
factor('2006*c*d + 2006*c*e + 2006*c*f + 2007*c*d + 2007*c*e + 2007*c*f')
c*(2006 + 2007)*(d + e + f)

Replace each constant with a unique symbol.
Factor the resulting expression.
Replace the unique symbols with the constants.
For your given case, something like this:
simple = factor('const2006*c*d + const2006*c*e + const2006*c*f + const2007*c*d + const2007*c*e + const2007*c*f')
simple.replace("const", '')
print(simple)
This should give you the desired output. You can identify numeric tokens in the expression with a straightforward regex or trivial parser -- either of which is covered in many other locations.

Symbol trickery to the rescue: replace your numbers with Symbols having a name given by the number. In your case you don't have to watch for negative versions so the following is straightforward:
>>> s = '2006*c*d + 2006*c*e + 2006*c*f + 2007*c*d + 2007*c*e + 2007*c*f'
>>> eq = S(s, evaluate=False); eq
2006*c*d + 2007*c*d + 2006*c*e + 2007*c*e + 2006*c*f + 2007*c*f
>>> reps = dict([(i,Symbol(str(i))) for i in _.atoms(Integer)]); reps
{2006: 2006, 2007: 2007}
>>> factor(eq.subs(reps))
c*(2006 + 2007)*(d + e + f)
Note: the evaluate=False is used to keep the like-terms from combining to give 4013*c*d + 4013*c*e + 4013*c*f.

Related

Python: replace function by its expression, in mathematical expression as string, with Sympy?

I have a function definition, as a string, say func_def = "f(x, y) = x + 2*y + 3".
I have a mathematical expression, as a string, including that function, say
expr = '2*a*b + f(b+a, 2*b)'.
How can I replace, in my expression, the function by its value?
In other words, how do I get a string expr_eval = "2*a*b + ( (b+a) + 2*(2*b) + 3 )"?
Is there an easy solution for this, for example with Sympy using a combination of sympify and subs and some other function? I feel there should be one, but can't find it. I have to do this for quite a few equations containing many symbols, so that creating Sympy symbols for each of them separately does not seem like a great option.
For now, I use regex, but I find this solution complicated to come up with, to generalise (other functions with other numbers of variables), to scale, and to read. It goes like this:
import re
func_def = "f(x, y) = x + 2*y + 3"
expr = '2*a*b + f(b+a, 2*b)'
# extract substring with function
str_to_replace = re.findall(r'f\([a-zA-z0-9\*\+\"\']*, [a-zA-z0-9\*\+\"\'/]*\)', expr)
str_to_replace = str_to_replace[0]
# extract function name and arguments in substring
func_name, args, _ = re.split('\(|\)', str_to_replace)
args = args.split(',')
args = [i.strip() for i in args]
# parse the function definition
func_def_lhs, func_def_rhs = func_def.split('=')
func_def_name, args_def, _ = re.split('\(|\)', func_def_lhs)
args_def = args_def.split(',')
args_def = [i.strip() for i in args_def]
# replace each argument in the definition by its expression
for i, arg_def in enumerate(args_def) :
func_def_rhs = func_def_rhs.replace(arg_def, '({})' .format(args[i]))
expr_eval = expr.replace(str_to_replace, '({})' .format(func_def_rhs))
You can use a re.sub:
import re
strip_f = lambda f: (f.split("(")[0], f.split("(")[1][:-1].split(", "))
def explicit(expr, functions):
d = {}
for f in functions:
func_vars, func_def = f.split(" = ")
func_name, func_vars = strip_f(func_vars)
d[func_name] = (func_vars, func_def)
def replace(match):
m = match.groups()[0]
fn_name, expr_vars = strip_f(m)
func_vars, s = d[fn_name]
for fv, ev in zip(func_vars, expr_vars):
s = s.replace(fv, "("+ev+")")
s = "("+s+")"
return s
return re.sub(r"(.\(([^\)]+,?)+?\))", replace, expr)
expr = '2*a*b + f(b+a, 2*b) + g(5, 2*c, 3+a)'
f1 = "f(x, y) = x + 2*y + 3"
f2 = "g(x, y, z) = x + y + z - 2"
print(explicit(expr, [f1, f2]))
Displays:
2*a*b + ((b+a) + 2*(2*b) + 3) + ((5) + (2*c) + (3+a) - 2)
The regex, broken down:
( begin capturing group for function
. match any character (function nname)
\( match open parenthesis
( begin capturing group for variable
[^\)]+ match at least one non-parenthesis character
,? match a comma if it's there
) end variable capturing
+? match at least one variable
\) match close parenthesis
) end capturing group
If you don't mind if the output is simplified, you can use the sympy methods that you mentioned:
import sympy
expr = '2*a*b + f(b+a, 2*b) + g(5, 2*c, 3+a)'
f1 = "f(x, y) = x + 2*y + 3"
f2 = "g(x, y, z) = x + y + z - 2"
def split(fn):
return str(fn).partition("(")
def check_fn(ex):
name, sep, rest = split(ex)
return name and sep
def parse_functions(functions):
fns = {}
for f in functions:
name, _, rest = split(f)
fn = rest.split(" = ")
fns[name] = fn[0][:-1].split(", "), fn[1]
return fns
def expand(expr, functions):
fns = parse_functions(functions)
def sub_fn(ex):
with sympy.evaluate(False):
vs, fn = fns[split(ex)[0]]
fn = sympy.UnevaluatedExpr(sympy.sympify(fn))
return fn.subs(dict(zip(vs, str(ex)[2:-1].split(", "))))
return sympy.sympify(expr).replace(check_fn, sub_fn)
print(sympy.StrPrinter({'order':'none'})._print(expand(expr, [f1, f2])))
Displays:
2*a*b + a + b + 2*(2*b) + 3 + 5 + 2*c + a + 3 - 2
Note that this assumes that you want the full, unsimplified, unordered equation.

Python: Substituting variables with functions SymPy

I'm writing a code where I need to substitute variables of a function with multiple functions.
For example, I have B=x1**2+x2**2+x3**2 where I need to substitute x1=cos(x1+x2), x2=sin(x2+x3) and x3=x1 so as to get this value: cos(x1+x2)**2+sin(x2+x3)**2+x1**2
However, when I do this iteratively like this:
for j in range(nvar):
B=expand(B.subs(x[j],f[j]))
where nvar=3 and x is defined as a list of symbols and f as a list of symbolic functions, at each iteration, x[j] from the previous substitution is replaced and gives a wrong answer: x1**2 + sin(x1 + cos(x1 + sin(x1 + x2)))**2 + cos(x1 + sin(x1 + cos(x1 + sin(x1 + x2))))**2
How can I perform this substitution simultaneously?
You can use the simultaneous keyword for subs which was made for cases like this:
>>> (x1**2+x2**2+x3**2).subs(dict(x1=cos(x1+x2), x2=sin(x2+x3), x3=x1), simultaneous=True)
x1**2 + sin(x2 + x3)**2 + cos(x1 + x2)**2
Or, if x and f contain all instances of replacements you are interested in,
>>> reps = dict(zip(x, f))
>>> B = expand(B.subs(reps, simultaneous=True)

Leading/prefix 0s in out of for loop

I am writing a four loop in my program that writes data to a file. I'm wanting for the output to be formatted as follows
frame001 + K.1
frame002 + K.2
...
frame099 + K.99
frame100 + K.100
So far I am doing
for f in range(1, 100):
file.write('frame' + str(f) + ' + K.' + str(f) + '\n')
I have no problem having the K part come out correctly as K.1-K.100, but I don't know how to have prefix zeros/have it output also frame00F to frameFFF with the appropriate amount of preceding zeros.
Using str.format:
>>> 'frame{0:03d} + K.{0}\n'.format(1)
'frame001 + K.1\n'
>>> 'frame{0:03d} + K.{0}\n'.format(100)
'frame100 + K.100\n'
BTW, range(1, 100) will not yield 100. If you want 100 to be included, that should be range(1, 101).
If you are using old version of Python (Python 2.5-), use % operator (String formatting operator) instead (need to specify multiple argument unlike str.format)
>>> 'frame%03d + K.%d\n' % (1, 1)
'frame001 + K.1\n'
>>> 'frame%03d + K.%d\n' % (100, 100)
'frame100 + K.100\n'
If you don't want to repeat arguments, you can pass mapping instead with slightly different format specifier:
>>> 'frame%(i)03d + K.%(i)d\n' % {'i': 1}
'frame001 + K.1\n'

Extract first N position of floating number

I have a list of floating point value, and I'd like to plot it onto a graph. How can we extract the first 4 precision from a floating number, before putting into another new list?
C = []
C.append(23.1234567890)
C.append(14.1234567890)
print ('C - ' + str(C))
D = []
D.append(C[0])
D.append(C[1])
print ('D - ' + str(D))
Got
C - [23.123456789, 14.123456789]
D - [23.123456789, 14.123456789]
Expecting
C - [23.123456789, 14.123456789]
D - [23.1235, 14.1235]
You can get a string representation pretty easily:
>>> '{:.4f}'.format(23.1234567890)
'23.1235'
You can also use the round function:
>>> round(23.1234567890, 4)
23.1235
Note that if you use round, the number will almost never be exactly to the 4 digits of precision that you want due to the representation of floating point numbers.
You can do it, for example, through string manipulation:
strC = split(str(C), ".")
strC = strC[0] + "." + strC[1][:5]
print ('C - ' + strC)
You can do it using string formatting:
C = []
C.append(23.1234567890)
C.append(14.1234567890)
print ('C - ' + str(C))
D = []
D.append(float("%.4f" % C[0]))
D.append(float("%.4f" % C[1]))
print ('D - ' + str(D))
You can mathematically remove the unneeded digits, using the modulo % function:
C = []
C.append(23.1234567890)
C.append(14.1234567890)
print ('C - ' + str(C))
D = []
for i in C:
D.append(i - (i% (10**-4)))
print ('D - ' + str(D))
Where -4 is the number of digits you want to keep. 10**-4 is 0.0001.

Organize dictionary by frequency

I create a dictionary for the most used words and get the top ten. I need to sort this for the list, which should be in order. I can't do that without making a list, which I can't use. Here is my code. I am away dictionaries cannot be sorted, but i still need help.
most_used_words = Counter()
zewDict = Counter(most_used_words).most_common(10)
newDict = dict(zewDict)
keys = newDict.keys()
values = newDict.values()
msg = ('Here is your breakdown of your most used words: \n\n'
'Word | Times Used'
'\n:--:|:--:'
'\n' + str(keys[0]).capitalize() + '|' + str(values[0]) +
'\n' + str(keys[1]).capitalize() + '|' + str(values[1]) +
'\n' + str(keys[2]).capitalize() + '|' + str(values[2]) +
'\n' + str(keys[3]).capitalize() + '|' + str(values[3]) +
'\n' + str(keys[4]).capitalize() + '|' + str(values[4]) +
'\n' + str(keys[5]).capitalize() + '|' + str(values[5]) +
'\n' + str(keys[6]).capitalize() + '|' + str(values[6]) +
'\n' + str(keys[7]).capitalize() + '|' + str(values[7]) +
'\n' + str(keys[8]).capitalize() + '|' + str(values[8]) +
'\n' + str(keys[9]).capitalize() + '|' + str(values[9]))
r.send_message(user, 'Most Used Words', msg)
How would I do it so the msg prints the words in order from most used word on the top to least on the bottom with the correct values for the word?
Edit: I know dictionaries cannot be sorted on their own, so can I work around this somehow?
Once you have the values it's as simple as:
print('Word | Times Used')
for e, t in collections.Counter(values).most_common(10):
print("%s|%d" % (e,t))
Print something like:
Word | Times Used
e|4
d|3
a|2
c|2
From the Docs: most_common([n])
Return a list of the n most common elements and their counts from the
most common to the least. If n is not specified, most_common() returns
all elements in the counter. Elements with equal counts are ordered
arbitrarily:
>>> Counter('abracadabra').most_common(3)
[('a', 5), ('r', 2), ('b', 2)]
Your code can be:
from collections import Counter
c = Counter(most_used_words)
msg = "Here is your breakdown of your most used words:\n\nWords | Times Used\n:--:|:--:\n"
msg += '\n'.join('%s|%s' % (k.capitalize(), v) for (k, v) in c.most_common(10))
r.send_message(user, 'Most Used Words', msg)
import operator
newDict = dict(zewDict)
sorted_newDict = sorted(newDict.iteritems(), key=operator.itemgetter(1))
msg = ''
for key, value in sorted_newDict:
msg.append('\n' + str(key).capitalize() + '|' + str(value))
This will sort by the dictionary values. If you want it in the other order add reverse=True to sorted().

Categories