Performance: cse, evaluate=False, Poly

SymPy · Expert · 13 min

Real derivations blow up in size and time; experts know where SymPy spends effort and how to avoid it.

SymPy is pure Python, and symbolic algorithms are often exponential in expression size, so performance work is mostly about keeping expressions small and avoiding unnecessary work.

  • Avoid blanket simplify. It tries many algorithms. Use the targeted function you need, or simplify(e, ratio=...) to bound effort.
  • cse. cse(exprs) finds repeated subexpressions and returns (replacements, reduced). Large Jacobians often shrink dramatically, and generated code computes each piece once. lambdify(..., cse=True) applies it automatically.
  • Control evaluation. Constructing an Add or Mul sorts and combines arguments. Building huge sums term by term with + is quadratic; Add(*terms) builds one node at once. evaluate=False (or with evaluate(False):) skips automatic evaluation entirely, useful for display or for staged construction.
  • Use the polys module. Poly(e, x, domain="QQ") represents polynomials in dense or sparse form with fast arithmetic in a fixed domain. factor, gcd and cancel are built on it, so converting once and staying in Poly avoids repeated conversion.
  • Expand late, substitute numbers early where exactness is not needed, and use xreplace instead of subs in tight loops.
  • The cache speeds up repeated construction but costs memory; SYMPY_USE_CACHE=no disables it.

When pure Python is the bottleneck, the C++ library SymEngine (symengine package) implements core expression manipulation much faster with a similar API.

Example

from sympy import symbols, cse, sin, cos, Matrix, Poly, Add, sqrt

x, y = symbols("x y")
r = sqrt(x**2 + y**2)
J = Matrix([x * sin(r), y * cos(r)]).jacobian([x, y])

repl, reduced = cse(J)
for name, sub in repl:
    print(name, "=", sub)
# x0 = x**2
# x1 = y**2
# x2 = sqrt(x0 + x1)
# x3 = sin(x2) ... (each shared piece computed once)
print(len(repl) > 0)                       # True

p = Poly(x**3 - 2*x + 1, x, domain="QQ")
print(p.degree(), p.all_coeffs())          # 3 [1, 0, -2, 1]
print(Add(*[x**k for k in range(4)]))      # x**3 + x**2 + x + 1
print(Add(x, x, evaluate=False))           # x + x

Remember

  • cse() removes repeated subexpressions before codegen or numerics
  • Use targeted functions instead of simplify; build big sums with Add(*terms)
  • Poly with a fixed domain is the fast path for polynomial algebra

Watch out for

  • Accumulating thousands of terms with total = total + term, which re-sorts every time.

Check yourself

  1. What does cse(exprs) return?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.sympy.org/latest/modules/rewriting.html