What is the difference between (matplotlib) and (SymPy Plotting Module)? - python

https://scicomp.stackexchange.com/questions/1144/how-can-i-plot-piece-wise-defined-function-in-some-easily-accessed-open-source-t
①Why is it that the above source code can be used to create a plot, but the following source code cannot?
②Can you point me to a web site that has a table comparing (matplotlib) and (SymPy Plotting Module)?
③(matplotlib) to (SymPy Plotting Module) converter and a (SymPy Plotting Module) to (matplotlib) converter would be helpful.
from sympy import *
def define_fn(n):
def fn(x):
if n <= x <= n + 1:
return float(x) - n
elif n + 1 <= x <= n + 2:
return 2.0 - x + n
else:
return 0.0
return fn
f3 = define_fn(3)
f8 = define_fn(8)
print("#",f3)
print("#",f8)
plot(f3,f8)
# <function define_fn.<locals>.fn at 0x000002474E838280>
# <function define_fn.<locals>.fn at 0x000002474E838310>
# SymPyDeprecationWarning: .............

Let's make sure you understand what the link and your adaptation is doing. Your question about the difference in the plot packages suggests you don't have a clear idea of what package is doing what.
You have defined two python functions, that take one number.
In [11]: f3(4)
Out[11]: 1.0
In [12]: f8(9)
Out[12]: 1.0
With just python we can create a list of x values:
In [17]: x = [i / 10 for i in range(120)]
the corresponding y values:
In [18]: y1 = [f3(i) for i in x]
In [19]: y2 = [f8(i) for i in x]
and plot them with:
In [20]: plt.plot(x, y1, x, y2)
Out[20]:
[<matplotlib.lines.Line2D at 0x7fe2589385e0>,
<matplotlib.lines.Line2D at 0x7fe258938610>]
# plotted as you show
The link use np.linspace to create the x values as an array, and np.vectorize to apply the function(s) to all these values. matplotlib works with both arrays and lists.
There's nothing fancy about this use of matplotlib.
Statements like:
if n <= x <= n + 1:
return float(x) - n
only work with single numbers. That's basic python. Using a numpy array for x or a sympy symbol or expression will result in an error.
If x is a sympy symbol:
In [3]: f3(x)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [3], in <module>
----> 1 f3(x)
Input In [1], in define_fn.<locals>.fn(x)
2 def fn(x):
----> 3 if n <= x <= n + 1:
4 return float(x) - n
5 elif n + 1 <= x <= n + 2:
File /usr/local/lib/python3.8/dist-packages/sympy/core/relational.py:398, in Relational.__bool__(self)
397 def __bool__(self):
--> 398 raise TypeError("cannot determine truth value of Relational")
TypeError: cannot determine truth value of Relational
and with an array:
In [4]: f3(np.arange(3))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Input In [4], in <module>
----> 1 f3(np.arange(3))
Input In [1], in define_fn.<locals>.fn(x)
2 def fn(x):
----> 3 if n <= x <= n + 1:
4 return float(x) - n
5 elif n + 1 <= x <= n + 2:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

①Why is it that the above source code can be used to create a plot, but the following source code cannot?
The linked example uses matplotlib which works with numeric inputs. Matplotlib doesn't know about sympy's world.
Sympy's plot module converts sympy expressions to numeric approximations and plots them using matplotlib. Sympy's plot module abstracts away a lot of intricacies to make this work, and hides these from the casual user.
②Can you point me to a web site that has a table comparing (matplotlib) and (SymPy Plotting Module)?
Matplotlib is very huge. Sympy's plotting module uses a subset of functionality, carefully and ingeniously adapted to the symbolic world. Differences don't fit into a table. Matplotlib's extensive documentation can be found at matplotlib.org, but most people only look into the subset they are using. Sympy's plotting documentation fits onto one large webpage. For both libraries you will need extra tutorials, StackOverlow, and maybe diving into the freely available source code if you need functionality that isn't readily available.
③(matplotlib) to (SymPy Plotting Module) converter and a (SymPy Plotting Module) to (matplotlib) converter would be helpful.
That would be a titanic work, with lots of undefined cases. Sympy (as well as matplotlib) is developed by very talented volunteers, with limited resources.
Note that, if you really want to, you can "move" sympy plots to the matplotlib world and extend the plot there.
Here is how your source code could look like in sympy.
First some remarks:
sympy functions and expressions can't work with Python's if-tests, you need symbolic functions such as PieceWise instead.
float(x) doesn't work for sympy's symbols
in general, sympy tries to avoid floats as they are approximate by definition while sympy almost always looks for exact symbolic solutions
from sympy import plot, Symbol, Piecewise, And
def define_fn(n):
def fn(x):
return Piecewise((x - n, (n <= x) & (x <= n + 1)),
(2 - x + n, (n + 1 <= x) & (x <= n + 2)),
(0, True))
return fn
f3 = define_fn(3)
f8 = define_fn(8)
x = Symbol('x', real=True)
plot(f3(x), f8(x), (x, -1, 11))
Although this works, it is not sympy's standard way. Usually, functions are written as expressions. Note how f3 is used as an expression that contains x, instead of the less flexible f3(x) of the previous example.
from sympy import plot, Symbol, Piecewise, And
x = Symbol('x', real=True)
n = Symbol('n', real=True)
fn = Piecewise((x - n, (n <= x) & (x <= n + 1)),
(2 - x + n, (n + 1 <= x) & (x <= n + 2)),
(0, True))
f3 = fn.subs(n, 3) # Piecewise((x - 3, (x >= 3) & (x <= 4)), (5 - x, (x >= 4) & (x <= 5)), (0, True))
f8 = fn.subs(n, 8) # Piecewise((x - 8, (x >= 8) & (x <= 9)), (10 - x, (x >= 9) & (x <= 10)), (0, True))
plot(f3, f8, (x, -1, 11))

print("#",type(f3),f3)
print("#",type(f8),f8)
# <class 'function'> <function define_fn.<locals>.fn at 0x00000246464B9280>
# <class 'function'> <function define_fn.<locals>.fn at 0x00000246464B9310>
print("#",type(f3(x)),f3(x))
print("#",type(f8(x)),f8(x))
# Piecewise Piecewise((x - 3, (x >= 3) & (x <= 4)), (5 - x, (x >= 4) & (x <= 5)), (0, True))
# Piecewise Piecewise((x - 8, (x >= 8) & (x <= 9)), (10 - x, (x >= 9) & (x <= 10)), (0, True))
print("#",type(f3),f3)
print("#",type(f8),f8)
# Piecewise Piecewise((x - 3, (x >= 3) & (x <= 4)), (5 - x, (x >= 4) & (x <= 5)), (0, True))
# Piecewise Piecewise((x - 8, (x >= 8) & (x <= 9)), (10 - x, (x >= 9) & (x <= 10)), (0, True))

Related

Optimal way to convolute continuous functions in python

I am trying to numerically compute in python integrals of the form
To that aim, I first define two discrete sets of x and t values, let's say
x_samples = np.linspace(-10, 10, 100)
t_samples = np.linspace(0, 1, 100)
dx = x_samples[1]-x_samples[0]
dt = t_samples[1]-t_samples[0]
declare symbolically that the function g(x,t) is equal to 0 if t<0 and discretise the two functions to integrate as
discretG = g(x_samples[None, :], t_samples[:, None])
discretH = h(x_samples[None, :], t_samples[:, None])
I have then tried to run
discretF = signal.fftconvolve(discretG, discretH, mode='full') * dx * dt
Yet, on basic test functions such as
g(x,t) = lambda x,t: np.exp(-np.abs(x))+t
h(x,t) = lambda x,t: np.exp(-np.abs(x))-t
I don't find an agreement between the the numerical integration and the convolution using scipy and I would like to have a fairly fast way of computing these integrals, especially when I only have access to discretised representations of the functions rather than their symbolic one.
According to your code, I assume you want to conduct convolution on two function g and h that are non-zero only on [a, b]*[m,n].
Of course you can use signal.fftconvolve to compute the convolution. The key is don't forget the transformation between the indices inside discretF and the real coordinates. Here I use interpolation to compute for arbitrary (x,t).
import numpy as np
from scipy import signal, interpolate
a = -1
b = 2
m = -10
n = 15
samples_num = 1000
x_eval_index = 200
t_eval_index = 300
x_samples = np.linspace(a, b, samples_num)
t_samples = np.linspace(m, n, samples_num)
dx = x_samples[1]-x_samples[0]
dt = t_samples[1]-t_samples[0]
g = lambda x,t: np.exp(-np.abs(x))+t
h = lambda x,t: np.exp(-np.abs(x))-t
discretG = g(x_samples[None, :], t_samples[:, None])
discretH = h(x_samples[None, :], t_samples[:, None])
discretF = signal.fftconvolve(discretG, discretH, mode='full')
def compute_f(x, t):
if x < 2*a or x > 2*b or t < 2*m or t > 2*n:
return 0
# use interpolation t get data on new point
x_samples_for_conv = np.linspace(2*a, 2*b, 2*samples_num-1)
t_samples_for_conv = np.linspace(2*m, 2*n, 2*samples_num-1)
f = interpolate.RectBivariateSpline(x_samples_for_conv, t_samples_for_conv, discretF.T)
return f(x, t)[0, 0] * dx * dt
Note: you can extend my codes to compute convolution on a meshgrid defined by x and y, where x and y are 1D array. (In my code, x and y are float now)
You can use the following code to explore the "agreement" between "the numerical integration" and "the convolution using scipy" (and also, the correctness of compute_f function above):
# how the convolve work
# for 1D f[i]=sigma_{j} g[j]h[i-j]
sum = 0
for y_idx, y in enumerate(x_samples[0:]):
for s_idx, s in enumerate(t_samples[0:]):
if x_eval_index - y_idx < 0 or t_eval_index - s_idx < 0:
continue
if t_eval_index - s_idx >= len(x_samples[0:]) or x_eval_index - y_idx >= len(t_samples[0:]):
continue
sum += discretG[t_eval_index - s_idx, x_eval_index - y_idx] * discretH[s_idx, y_idx] * dx * dt
print("Do discrete convolution manually, I get: %f" % sum)
print("Do discrete convolution using scipy, I get: %f" % (discretF[t_eval_index, x_eval_index] * dx * dt))
# numerical integral
# the x_val and t_val
# take 1D convolution as example, function defined on [a, b], and index of your samples range from [0, samples_num-1]
# after convolution, function defined on [2a, 2b], index of your samples range from [0, 2*samples_num-2]
dx_prime = (b-a) / (samples_num-1)
dt_prime = (n-m) / (samples_num-1)
x_eval = 2*a + x_eval_index * dx_prime
t_eval = 2*m + t_eval_index * dt_prime
sum = 0
for y in x_samples[:]:
for s in t_samples[:]:
if x_eval - y < a or x_eval - y > b:
continue
if t_eval - s < m or t_eval - s > n:
continue
if y < a or y >= b:
continue
if s < m or s >= n:
continue
sum += g(x_eval - y, t_eval - s) * h(y, s) * dx * dt
print("Do numerical integration, I get: %f" % sum)
print("The convolution result of 'compute_f' is: %f" % compute_f(x_eval, t_eval))
Which gives:
Do discrete convolution manually, I get: -154.771369
Do discrete convolution using scipy, I get: -154.771369
Do numerical integration, I get: -154.771369
The convolution result of 'compute_f' is: -154.771369

Approximating sin using the Taylor series

I'm trying to calculate sin(x) using Taylor series without using factorials.
import math, time
import matplotlib.pyplot as plot
def sin3(x, i=30):
x %= 2 * math.pi
n = 0
dn = x**2 / 2
for c in range(4, 2 * i + 4, 2):
n += dn
dn *= -x**2 / ((c + 1) * (c + 2))
return x - n
def draw_graph(start = -800, end = 800):
y = [sin3(i/100) for i in range(start, end)]
x = [i/100 for i in range(start, end)]
y2 = [math.sin(i/100) for i in range(start, end)]
x2 = [i/100 for i in range(start, end)]
plot.fill_between(x, y, facecolor="none", edgecolor="red", lw=0.7)
plot.fill_between(x2, y2, facecolor="none", edgecolor="blue", lw=0.7)
plot.show()
When you run the draw_graph function it uses matplotlib to draw a graph, the redline is the output from my sin3 function, and the blue line is the correct output from the math.sin method.
As you can see the curve is not quite right, it's not high or low enough (seems to peak at 0.5), and also has strange behavior where it generates a small peak around 0.25 then drops down again. How can I adjust my function to match the correct output of math.sin?
You have the wrong equation for sin(x), and you also have a messed up loop invariant.
The formula for sin(x) is x/1! - x^3/3! + x^5/5! - x^7/7!..., so I really don't know why you're initializing dn to something involving x^2.
You also want to ask yourself: What is my loop invariant? What is the value of dn when I reach the start of my loop. It is clear from the way you update dn that you expect it to be something involving x^i / i!. Yet on the very first iteration of the loop, i=4, yet dn involves x^2.
Here is what you meant to write:
def sin3(x, i=30):
x %= 2 * math.pi
n = 0
dn = x
for c in range(1, 2 * i + 4, 2):
n += dn
dn *= -x**2 / ((c + 1) * (c + 2))
return n

Using a numpy array in if condition cause problems, how to fix it?

I would like to test every single element of "z", with the "if else" condition and return desired equation. My implementation is resulting with an error. I tried "z.all" and "z.any" functions but these two are converting "z" to a boolean array. I do not want z as boolean array. How can I do this, while z remain as a numpy array?
z is a numpy array and its shape is (10610, ))
def function(z):
alpha = 1
if (z < 0):
return -alpha * z
elif (0 <= z <= 1):
return (3 * z ** 3) - (4 * z ** 2) + (2 * z)
else:
return z
You can use numpy.where for the two conditions:
np.where(
z < 0,
-alpha * z,
np.where(
z <= 1,
(3 * z ** 3) - (4 * z ** 2) + (2 * z),
z))
If I understand correctly you want to apply your function to all elements in the array z right? If so you might want to look into numpys vectorize function.
For your code:
vfunc = np.vectorize(function)
z = vfunc(z)
If speed and efficiency are important you can refer to this other answer where different methods are compared.
Using boolean indexing:
def function(z):
alpha = 1
m = (z < 0)
z[m] = -alpha * z[m]
m = ((z >= 0) & (z <= 1))
z[m] = (3 * z[m] ** 3) - (4 * z[m] ** 2) + (2 * z[m])
return z

Construct a symbolic interpolating spline through given points using SymPy

Pretend I start with some simple dataset which is defined on R2 follows:
DataPointsDomain = [0,1,2,3,4,5]
DataPointsRange = [3,6,5,7,9,1]
With scipy I can make a lazy polynomial spline using the following:
ScipySplineObject = scipy.interpolate.InterpolatedUnivariateSpline(
DataPointsDomain,
DataPointsRange,
k = 1, )
What is the equivalent object in sympy??
SympySplineObject = ...???
(I want to define this object and do analytic sympy manipulation like taking integrals, derivatives, etc... on the sympy object )
In SymPy versions above 1.1.1, including the current development version, there is a built-in method interpolating_spline which takes four arguments: the spline degree, the variable, domain values and range values.
from sympy import *
DataPointsDomain = [0,1,2,3,4,5]
DataPointsRange = [3,6,5,7,9,1]
x = symbols('x')
s = interpolating_spline(3, x, DataPointsDomain, DataPointsRange)
This returns
Piecewise((23*x**3/15 - 33*x**2/5 + 121*x/15 + 3, (x >= 0) & (x <= 2)),
(-2*x**3/3 + 33*x**2/5 - 55*x/3 + 103/5, (x >= 2) & (x <= 3)),
(-28*x**3/15 + 87*x**2/5 - 761*x/15 + 53, (x >= 3) & (x <= 5)))
which is a "not a knot" cubic spline through the given points.
Old answer
An interpolating spline can be constructed with SymPy, but this takes some effort. The method bspline_basis_set returns the basis of B-splines for given x-values, but then it's up to you to find their coefficients.
First, we need the list of knots, which is not exactly the same as the list of x-values (xv below). The endpoints xv[0] and xv[-1] will appear deg+1 times where deg is the degree of the spline, because at the endpoints all the coefficients change values (from something to zero). Also, some of the x-values close to them may not appear at all, as there will be no changes of coefficients there ("not a knot" conditions). Finally, for even-degree splines (yuck) the interior knots are placed midway between data points. So we need this helper function:
from sympy import *
def knots(xv, deg):
if deg % 2 == 1:
j = (deg+1) // 2
interior_knots = xv[j:-j]
else:
j = deg // 2
interior_knots = [Rational(a+b, 2) for a, b in zip(xv[j:-j-1], xv[j+1:-j])]
return [xv[0]] * (deg+1) + interior_knots + [xv[-1]] * (deg+1)
After getting b-splines from bspline_basis_set method, one has to plug in the x-values and form a linear system from which to find the coefficients coeff. At last, the spline is constructed:
xv = [0, 1, 2, 3, 4, 5]
yv = [3, 6, 5, 7, 9, 1]
deg = 3
x = Symbol("x")
basis = bspline_basis_set(deg, knots(xv, deg), x)
A = [[b.subs(x, v) for b in basis] for v in xv]
coeff = linsolve((Matrix(A), Matrix(yv)), symbols('c0:{}'.format(len(xv))))
spline = sum([c*b for c, b in zip(list(coeff)[0], basis)])
print(spline)
This spline is a SymPy object. Here it is for degree 3:
3*Piecewise((-x**3/8 + 3*x**2/4 - 3*x/2 + 1, (x >= 0) & (x <= 2)), (0, True)) + Piecewise((x**3/8 - 9*x**2/8 + 27*x/8 - 27/8, (x >= 3) & (x <= 5)), (0, True)) + 377*Piecewise((19*x**3/72 - 5*x**2/4 + 3*x/2, (x >= 0) & (x <= 2)), (-x**3/9 + x**2 - 3*x + 3, (x >= 2) & (x <= 3)), (0, True))/45 + 547*Piecewise((x**3/9 - 2*x**2/3 + 4*x/3 - 8/9, (x >= 2) & (x <= 3)), (-19*x**3/72 + 65*x**2/24 - 211*x/24 + 665/72, (x >= 3) & (x <= 5)), (0, True))/45 + 346*Piecewise((x**3/30, (x >= 0) & (x <= 2)), (-11*x**3/45 + 5*x**2/3 - 10*x/3 + 20/9, (x >= 2) & (x <= 3)), (31*x**3/180 - 25*x**2/12 + 95*x/12 - 325/36, (x >= 3) & (x <= 5)), (0, True))/45 + 146*Piecewise((-31*x**3/180 + x**2/2, (x >= 0) & (x <= 2)), (11*x**3/45 - 2*x**2 + 5*x - 10/3, (x >= 2) & (x <= 3)), (-x**3/30 + x**2/2 - 5*x/2 + 25/6, (x >= 3) & (x <= 5)), (0, True))/45
You can differentiate it, with
spline.diff(x)
You can integrate it:
integrate(spline, (x, 0, 5)) # 197/3
You can plot it and see it indeed interpolates the given values:
plot(spline, (x, 0, 5))
I even plotted them for degrees 1,2,3 together:
Disclaimers:
The code given above works in the development version of SymPy, and should work in 1.1.2+; there was a bug in B-spline method in previous versions.
Some of this takes a good deal of time because Piecewise objects are slow. In my experience, the basis construction takes longest.

Factoring polys in sympy

I'm doing a very simple probability calculations of getting subset of X, Y, Z from set of A-Z (with corresponding probabilities x, y, z).
And because of very heavy formulas, in order to handle them, I'm trying to simplify (or collect or factor - I dont know the exact definition) these polynomial expressions using sympy.
So.. having this (a very simple probability calculation expression of getting subset of X,Y,Z from set of A-Z with corresponding probabilities x, y, z)
import sympy as sp
x, y, z = sp.symbols('x y z')
expression = (
x * (1 - x) * y * (1 - x - y) * z +
x * (1 - x) * z * (1 - x - z) * y +
y * (1 - y) * x * (1 - y - x) * z +
y * (1 - y) * z * (1 - y - z) * x +
z * (1 - z) * y * (1 - z - y) * x +
z * (1 - z) * x * (1 - z - x) * y
)
I want to get something like this
x * y * z * (6 * (1 - x - y - z) + (x + y) ** 2 + (y + z) ** 2 + (x + z) ** 2)
a poly, rewritten in way to have as few operations (+, -, *, **, ...) as possible
I tried factor(), collect(), simplify(). But result differs from my expectations. Mostly I get
2*x*y*z*(x**2 + x*y + x*z - 3*x + y**2 + y*z - 3*y + z**2 - 3*z + 3)
I know that sympy can combine polynomials into simple forms:
sp.factor(x**2 + 2*x*y + y**2) # gives (x + y)**2
But how to make sympy to combine polynomials from expressions above?
If this is impossible task in sympy, may be there are any other options?
Putting together some of the methods happens to give a nice answer this time. It would be interesting to see if this strategy works more often than not on the equations you generate or if, as the name implies, this is just a lucky result this time.
def iflfactor(eq):
"""Return the "I'm feeling lucky" factored form of eq."""
e = Mul(*[horner(e) if e.is_Add else e for e in
Mul.make_args(factor_terms(expand(eq)))])
r, e = cse(e)
s = [ri[0] for ri in r]
e = Mul(*[collect(ei.expand(), s) if ei.is_Add else ei for ei in
Mul.make_args(e[0])]).subs(r)
return e
>>> iflfactor(eq) # using your equation as eq
2*x*y*z*(x**2 + x*y + y**2 + (z - 3)*(x + y + z) + 3)
>>> _.count_ops()
15
BTW, a difference between factor_terms and gcd_terms is that factor_terms will work harder to pull out common terms while retaining the original structure of the expression, very much like you would do by hand (i.e. looking for common terms in Adds that can be pulled out).
>>> factor_terms(x/(z+z*y)+x/z)
x*(1 + 1/(y + 1))/z
>>> gcd_terms(x/(z+z*y)+x/z)
x*(y*z + 2*z)/(z*(y*z + z))
For what it's worth,
Chris
As far as I know, there is no function that does exactly that. I believe it is actually a very hard problem. See Reduce the number of operations on a simple expression for some discussion on it.
There are however, quite a few simplification functions in SymPy that you can try. One that you haven't mentioned that gives a different result is gcd_terms, which factorizes out a symbolic gcd without doing an expansions. It gives
>>> gcd_terms(expression)
x*y*z*((-x + 1)*(-x - y + 1) + (-x + 1)*(-x - z + 1) + (-y + 1)*(-x - y + 1) + (-y + 1)*(-y - z + 1) + (-z + 1)*(-x - z + 1) + (-z + 1)*(-y - z + 1))
Another useful function is .count_ops, which counts the number of operations in an expression. For example
>>> expression.count_ops()
47
>>> factor(expression).count_ops()
22
>>> e = x * y * z * (6 * (1 - x - y - z) + (x + y) ** 2 + (y + z) ** 2 + (x + z) ** 2)
>>> e.count_ops()
18
(note that e.count_ops() is not the same as you counted yourself, because SymPy automatically distributes the 6*(1 - x - y - z) to 6 - 6*x - 6*y - 6*z).
Other useful functions:
cse: Performs a common subexpression elimination on the expression. Sometimes you can simplify the individual parts and then put it back together. This also helps in general to avoid duplicate computations.
horner: Applies the Horner scheme to a polynomial. This minimizes the number of operations if the polynomial is in one variable.
factor_terms: Similar to gcd_terms. I'm actually not entirely clear what the difference is.
Note that by default, simplify will try several simplifications, and return the one that is minimized by count_ops.
I have had a similar problem, and ended up implementing my own solution before I stumbled across this one. Mine seems to do a much better job reducing the number of operations. However, mine also does a brute-force style set of collections over all combinations of variables. Thus, it's runtime grows super-exponentially in the number of variables. OTOH, I've managed to run it on equations with 7 variables in a not-unreasonable (but far from real-time) amount of time.
It is possible that there are some ways to prune some of the search branches here, but I haven't bothered with it. Further optimizations are welcome.
def collect_best(expr, measure=sympy.count_ops):
# This method performs sympy.collect over all permutations of the free variables, and returns the best collection
best = expr
best_score = measure(expr)
perms = itertools.permutations(expr.free_symbols)
permlen = np.math.factorial(len(expr.free_symbols))
print(permlen)
for i, perm in enumerate(perms):
if (permlen > 1000) and not (i%int(permlen/100)):
print(i)
collected = sympy.collect(expr, perm)
if measure(collected) < best_score:
best_score = measure(collected)
best = collected
return best
def product(args):
arg = next(args)
try:
return arg*product(args)
except:
return arg
def rcollect_best(expr, measure=sympy.count_ops):
# This method performs collect_best recursively on the collected terms
best = collect_best(expr, measure)
best_score = measure(best)
if expr == best:
return best
if isinstance(best, sympy.Mul):
return product(map(rcollect_best, best.args))
if isinstance(best, sympy.Add):
return sum(map(rcollect_best, best.args))
To illustrate the performance, this paper(paywalled, sorry) has 7 formulae that are 5th degree polynomials in 7 variables with up to 29 terms and 158 operations in the expanded forms. After applying both rcollect_best and #smichr's iflfactor, the number of operations in the 7 formulae are:
[6, 15, 100, 68, 39, 13, 2]
and
[32, 37, 113, 73, 40, 15, 2]
respectively. iflfactor has 433% more operations than rcollect_best for one of the formulae. Also, the number of operations in the expanded formulae are:
[39, 49, 158, 136, 79, 27, 2]

Categories

Resources