Closed-form ODE solutions give insight and exact references that numerical integrators cannot.
To write an ODE, make an undefined function with f = Function("f") and use f(x) in an equation with its derivatives: Eq(f(x).diff(x, 2) + f(x), 0). Then dsolve(eq, f(x)) classifies the equation and applies a matching method.
The answer is an Eq with arbitrary constants C1, C2, ... for the general solution. Initial or boundary conditions go in the ics dict, using function values or derivatives at points:
dsolve(eq, f(x), ics={f(0): 1, f(x).diff(x).subs(x, 0): 0})Useful companions:
classify_ode(eq, f(x))lists the methods SymPy recognises (separable, linear first order, Bernoulli, constant-coefficient linear, ...), and you can passhint=to pick one.checkodesol(eq, sol)substitutes the solution back and returns(True, 0)when it satisfies the equation. Use it, especially after implicit solutions.- Systems of linear ODEs can be passed as a list of equations.
Not every ODE has a closed form. dsolve raises NotImplementedError for equations it cannot classify, and may return an implicit solution such as Eq(g(f(x), x), C1). For those cases, switch to a numerical solver such as SciPy's solve_ivp, optionally using lambdify to build the right-hand side.
Example
from sympy import Function, dsolve, Eq, symbols, checkodesol, exp
x = symbols("x")
f = Function("f")
ode = Eq(f(x).diff(x, 2) + f(x), 0)
print(dsolve(ode, f(x))) # Eq(f(x), C1*sin(x) + C2*cos(x))
sol = dsolve(ode, f(x), ics={f(0): 1, f(x).diff(x).subs(x, 0): 0})
print(sol) # Eq(f(x), cos(x))
print(checkodesol(ode, sol)) # (True, 0)
decay = Eq(f(x).diff(x), -2 * f(x))
print(dsolve(decay, f(x), ics={f(0): 5})) # Eq(f(x), 5*exp(-2*x))Remember
- Represent the unknown as f = Function('f') and use f(x) and f(x).diff(x)
- Pass initial conditions via ics={...}
- Verify results with checkodesol; fall back to numerics when there is no closed form
Watch out for
- Passing a Symbol instead of an applied function f(x) as the unknown.