5.4.20 · D5Scientific Computing (Python)

Question bank — SymPy — symbolic algebra, calculus, ODE solving

1,612 words7 min readBack to topic

Before we start, one word we lean on everywhere: a symbol is a named mathematical unknown (like ), and a SymPy expression is a tree of such symbols glued together by operations (, , powers). Nothing in that tree has a numeric value until you ask for one. Keep that picture — a tree standing quietly, not a number — in mind for every question.


True or false — justify

True or false: writing x = 5 in Python gives you a symbolic unknown you can differentiate.
False — x = 5 stores the integer 5, so x**2 is just 25 and the algebra is gone. You need x = symbols('x') to keep as an unknown tree.
True or false: simplify(e) always returns the one canonical simplest form of e.
False — "simplest" is not well-defined, so simplify is a heuristic that may return different-looking results and occasionally a worse form. For a guaranteed target use factor, expand, or trigsimp.
True or false: solve(x**2 - 4, x) treats the input as the equation x**2 - 4 = 0.
True — solve reads a bare expression as "set this equal to zero," because any equation becomes , the universal root form.
True or false: (x+1)**2 == x**2 + 2*x + 1 returns True.
False — == checks structural identity of the two trees, and an unexpanded square is a different tree than the expanded polynomial. Use simplify(a-b)==0 or a.equals(b) for math equality.
True or false: integrate(x**2, (x, 0, 1)) returns 0.3333....
False — it returns the exact rational 1/3; SymPy stays symbolic and only gives a float if you call .evalf() or N(...).
True or false: diff proves the power rule by assuming the power rule.
False (that would be circular) — the rules SymPy applies are themselves derived from the limit definition, and you can recover them with limit(((x+h)**2-x**2)/h, h, 0)2*x.
True or false: dsolve(Eq(y(x).diff(x,2)+y(x),0), y(x)) returns a single specific function.
False — it returns a whole family Eq(y(x), C1*sin(x)+C2*cos(x)) with arbitrary constants; you pin them with initial conditions via ics=.
True or false: .subs(x, sqrt(2)) immediately turns the expression into a decimal.
False — substitution swaps the symbol but keeps sqrt(2) exact; you'd see something like (sqrt(2)+1)**2, not 5.828.
True or false: solve always hands back plain numbers you can feed into numpy.
False — it returns a list of symbolic roots that may contain sqrt, I, or symbols; convert with sol[0].evalf() only when you truly want floats.
True or false: integrate(f, x) includes the + C constant of integration.
False — SymPy omits +C; it hands you one antiderivative. The constant only reappears (as C1) in dsolve outputs, where it genuinely matters.

Spot the error

Someone writes x = 5 then factor(x**2 - 5*x + 6) and is confused it returns 6. What went wrong?
x was bound to the number 5, so the expression evaluated to before factor ever saw a symbol. Declare x = symbols('x') first.
Someone checks two expressions with if a == b: and their correct simplification never triggers. Fix it.
== compares tree structure, not mathematical value; equal-but-differently-written trees give False. Use simplify(a-b)==0 or a.equals(b).
Someone calls dsolve(Eq(y.diff(x,2)+y,0), y) with y = symbols('y') and gets an error. Why?
For ODEs the unknown must be an undefined function, y = Function('y'), used as y(x) — a plain symbol y has no derivative in to solve for.
Someone writes solve(x**2 - 5*x + 6 = 0, x). What's the mistake?
= 0 is a Python assignment, not an equation, and is a syntax error inside a call. Pass the bare expression solve(x**2-5*x+6, x) or wrap it as solve(Eq(x**2-5*x+6, 0), x).
Someone does sol = solve(x**2-2, x) then sqrt_val = sol + 1 expecting a number. Why does this fail?
sol is a list of roots, so sol + 1 tries to add an int to a list. Index it first: sol[0] + 1, and .evalf() if a float is needed.
Someone expects simplify(sin(x)**2 + cos(x)**2) to fail because SymPy "only does algebra." What's wrong with that expectation?
SymPy knows trig identities too, so it simplifies this to 1. Symbolic manipulation covers algebra, trig, calculus — not just polynomials.
Someone writes expr.subs(x, 5) but keeps using expr expecting it to now hold 5. What's the misunderstanding?
SymPy expressions are immutable; .subs returns a new object and leaves expr untouched. Capture it: expr2 = expr.subs(x, 5).

Why questions

Why does SymPy store expressions as trees instead of evaluating them immediately?
Because the structure is what lets it later factor, differentiate, or solve — a plain number 25 has thrown that structure away and can no longer become .
Why does solve reduce every equation to the form expression = 0?
Any rewrites as , so "find the roots of one expression" is a single universal problem the solver can always attack.
Why does the general solution of contain two constants but only one?
The number of arbitrary constants equals the order of the ODE (how many times you'd integrate); second-order needs two, first-order needs one.
Why do the complex roots of the characteristic equation produce and ?
Because , so an imaginary exponent means oscillation rather than growth or decay — the real and imaginary parts give the two real oscillating solutions.
Why is a Taylor series just "repeated differentiation" in disguise?
Each coefficient is , so building the series is differentiating over and over and dividing by factorials — near a point a smooth function looks like a polynomial made of its own derivatives.
Why can SymPy give 1/3 for while NumPy gives 0.3333...?
SymPy applies the Fundamental Theorem symbolically, , producing the exact rational; NumPy adds up floating-point slices and inherits rounding.
Why prefer simplify(a-b)==0 over a==b to test equality?
== only asks "are these the same tree?"; subtracting and simplifying asks the real question "is the difference mathematically zero?", catching equal values written differently.

Edge cases

What does solve(x**2 + 1, x) return, and is that a bug?
It returns [-I, I] — the two imaginary roots. Not a bug: SymPy works over the complex numbers by default, since has no real solution.
What happens with limit(1/x, x, 0) from an unspecified side?
SymPy defaults to the right-hand limit and returns oo (infinity); pass dir='-' for the left side, which gives -oo. The two-sided limit does not exist.
What does integrate(1/x, x) give, including for negative ?
It returns log(x); the full real antiderivative is , so for negative inputs you must remember the absolute value SymPy's bare form omits.
If you call dsolve on a second-order ODE but supply only one initial condition, what happens to the constants?
Only one constant gets pinned; the other stays as a symbol like C2, because a second-order equation needs two conditions to be fully determined.
What does series(f, x, 0, n) leave behind at the end, and why does it matter?
A big-O term O(x**n) marking the truncation — everything of that order and higher is dropped, so the series is a local approximation near , not an exact equality.
What is the result of symbols('x', positive=True) versus plain symbols('x') when simplifying sqrt(x**2)?
With positive=True it simplifies to x; plain x (which could be negative or complex) stays sqrt(x**2) or becomes Abs(x), because assumptions change what's provably true.

Active Recall

Recall Why is

== the wrong tool for "are these expressions mathematically equal?" == compares tree structure, so equal values written differently return False; use simplify(a-b)==0 or a.equals(b).

Recall Why does a second-order ODE need two initial conditions?

The number of arbitrary constants equals the ODE's order, and each condition removes one constant — two constants, two conditions.

Recall What does

solve return for x**2 + 1, and why isn't it empty? [-I, I] — SymPy solves over the complex numbers, so imaginary roots count as valid solutions.

Connections

  • NumPy — numerical arrays (the float world these traps contrast against)
  • Limits and the definition of the derivative
  • Taylor and Maclaurin Series
  • Ordinary Differential Equations — characteristic equation method
  • Lambdify — bridging SymPy to NumPy for plotting