Reputation: 65
I have a long expression in sympy that looks something like this:
2𝑤^2𝑥𝑦𝑧 + 3𝑤^2 𝑥𝑧^2 − 4𝑤𝑥𝑦^2 − 5𝑤𝑥𝑦𝑧 + 2𝑤𝑥𝑧^2 − 3𝑥𝑦^2 − 4𝑥𝑦𝑧 − 5𝑥𝑧^2 ...
where the variables are combinations of w,x,y,z and can also have powers of 2.
I would like to collect terms [x,y,z] such that I end up with a quadratic polynomial in w as the coefficient to each term, for example:
xyz(−0.285𝑤^2 - 1.09⋅10−5𝑤, 1.60⋅10−10) + xy^2(...w^2, ...w, ...) + x^2y(...) + xz^2(...) + ...
When I use collect(exp, [x,y,z]) I'm not able to achieve this; sympy seems to want to nest some terms:
𝑥(𝑦2(−0.00341083824360158𝑤−2.60668077412341⋅10−8) +
𝑦𝑧(−0.28569359766975𝑤2−1.09161331685904⋅10−5𝑤−1.60378772636814⋅10−10) + 𝑧2(0.000708269071473656𝑤2+1.72432957139821⋅10−10𝑤−2.29362549750881⋅10−13))
I've also tried several combinations of collecting terms one at a time, simplifying, expanding etc. but to no avail. Any help is greatly appreciated!
Thanks!
Upvotes: 3
Views: 2644
Reputation: 760
I faced the same problem and wrote this recursive function.
def collect_with_respect_to_vars(eq, vars):
assert isinstance(vars, list)
eq = eq.expand()
if len(vars) == 0:
return {1: eq}
var_map = eq.collect(vars[0], evaluate=False)
final_var_map = {}
for var_power in var_map:
sub_expression = var_map[var_power]
sub_var_map = collect_with_respect_to_vars(sub_expression, vars[1:])
for sub_var_power in sub_var_map:
final_var_map[var_power*sub_var_power] = sub_var_map[sub_var_power]
return final_var_map
It returns a map with all the combined powers of the given vars as keys.
collect_with_respect_to_vars(x*2 + y**2*z*5 + x**3*y*4, [x, y, z])
=> {x: 2, x**3*y: 4, y**2*z: 5}
collect_with_respect_to_vars(x*2 + x*4 + z**3 + x*z*2, [x, y])
=> {x: 2*z + 6, 1: z**3}
I suppose in your case, you can use it like this.
collect_with_respect_to_vars(your_expr, [x, y, z])
Upvotes: 3
Reputation: 19077
As a reiteration of Oscar's point about getting SymPy to do what you want, here is another approach using cse
which collects terms of more than 1 operation which are repeated in an expression. Since it will also collect squares that are repeated we will replace w**2
with u
so it won't be collected.
>>> r, e = cse(expr.subs(w**2,u))
Here's the output of cse
>>> r
[(x0, x*y*z), (x1, x*y**2), (x2, x*z**2)]
>>> e
[2*u*x0 + 3*u*x2 - 5*w*x0 - 4*w*x1 + 2*w*x2 - 4*x0 - 3*x1 - 5*x2]
Now collect on the patterns:
>>> collect(e, [v for v, _ in r])
And restore the originals
>>> _.subs(list(reversed(r))).subs(u, w**2)
x*y**2*(-4*w - 3) + x*y*z*(2*w**2 - 5*w - 4) + x*z**2*(3*w**2 + 2*w - 5)
This collection scheme works because there are multi-factor coefficients of w
-related factors in all cases. If there were some linear factors on w that would need some additional wrangling (for this and the other approach):
>>> expr += x*w + x*w**2 + y + y*w**2
>>> terms = set(e.as_coeff_Mul()[1] for c in expr.as_poly(w).all_coeffs() for e in Add.make_args(c))
>>> new_expr = sum(t * expr.coeff(t) for t in terms)
>>> new_expr.equals(expr)
False
Upvotes: 2
Reputation: 14480
I don't know that there is a ready function for this sort of thing in SymPy. What I like about SymPy though is that once you familiarise yourself with the internals it gives you a lot of power to define elementary operations like this.
This might work or hopefully at least gives a starting point:
In [124]: x, y, z, w = symbols('x, y, z, w')
In [125]: expr = 2*w**2*x*y*z + 3*w**2*x*z**2 - 4*w*x*y**2 - 5*w*x*y*z + 2*w*x*z**2 - 3*x*y**2 - 4*x*y*z - 5*x*z**2
In [126]: expr
Out[126]:
2 2 2 2 2 2 2
2⋅w ⋅x⋅y⋅z + 3⋅w ⋅x⋅z - 4⋅w⋅x⋅y - 5⋅w⋅x⋅y⋅z + 2⋅w⋅x⋅z - 3⋅x⋅y - 4⋅x⋅y⋅z - 5⋅x⋅z
In [127]: terms = set(e.as_coeff_Mul()[1] for c in expr.as_poly(w).all_coeffs() for e in Add.make_args(c))
In [128]: new_expr = sum(t * expr.coeff(t) for t in terms)
In [129]: new_expr
Out[129]:
2 ⎛ 2 ⎞ 2 ⎛ 2 ⎞
x⋅y ⋅(-4⋅w - 3) + x⋅y⋅z⋅⎝2⋅w - 5⋅w - 4⎠ + x⋅z ⋅⎝3⋅w + 2⋅w - 5⎠
Upvotes: 2