Break-even points, implied rates and equilibrium conditions are equations without closed-form solutions.
A root of f is an x where f(x) = 0. To solve g(x) = c, find the root of g(x) - c.
For one variable, the safest approach is a bracketing method: find a, b where f(a) and f(b) have opposite signs, and optimize.brentq(f, a, b) is guaranteed to converge to a root inside. optimize.root_scalar(f, bracket=[a, b]) is the general interface; it returns a result with root, converged and iterations. Without a bracket, root_scalar(f, x0=..., fprime=...) uses Newton's method (or the secant method if no derivative is given), which is fast but can diverge from a poor start.
For systems of equations, optimize.root(fun, x0) takes a function returning a vector of residuals, one per equation, and solves them simultaneously. Methods include "hybr" (default) and "lm". Check res.success. fsolve is the older interface to the same algorithm.
Tips:
- Plot or tabulate
ffirst to find brackets and see how many roots exist. - Root finders return one root; to find several, bracket each separately.
- For polynomials,
np.rootsreturns all roots at once. - Minimisation is different:
minimizelooks for flat points of a function, not zeros.
Example
import numpy as np
from scipy import optimize
# Interest rate r such that 1000 now grows to 1500 in 7 years
f = lambda r: 1000 * (1 + r) ** 7 - 1500
r = optimize.brentq(f, 0.0, 1.0) # f(0) < 0 < f(1)
print(round(r, 5)) # 0.05963
sol = optimize.root_scalar(np.cos, bracket=[0, 3])
print(sol.converged, round(sol.root, 6)) # True 1.570796
def system(v): # x + y = 3 and x*y = 2
x, y = v
return [x + y - 3, x * y - 2]
res = optimize.root(system, x0=[0.5, 3.0])
print(res.success, res.x.round(6)) # True [1. 2.]Remember
- Bracketing (
brentq,root_scalar(bracket=...)) is robust when signs differ at the ends - Newton/secant are fast but need a good start
optimize.rootsolves systems: return one residual per equation
Watch out for
- Expecting a root finder to return all roots instead of the one it converged to.