Real problems have limits (budgets, physical bounds) and many local minima that basic minimize misses.
Bounds restrict each variable: minimize(f, x0, bounds=[(0, None), (-1, 1)], method="L-BFGS-B"). None means unbounded on that side.
Constraints go in a list: {"type": "ineq", "fun": g} means g(x) >= 0, "eq" means g(x) = 0, or use NonlinearConstraint(g, lb, ub) and LinearConstraint(A, lb, ub). Methods that handle general constraints are "SLSQP", "trust-constr" and "COBYQA".
Gradients make optimisers faster and more reliable. Pass jac=grad_f, or jac=True when fun returns (value, gradient). Without them SciPy uses finite differences, which costs extra evaluations per step and loses accuracy. Check hand-written gradients with optimize.check_grad.
Linear and integer problems have dedicated solvers: optimize.linprog (HiGHS) for linear programs and optimize.milp for mixed-integer linear programs. They are far more efficient than generic minimize for such problems.
Global optimisation for multimodal functions within bounds:
differential_evolution: population-based, robust, parallelisable viaworkers,dual_annealing,basinhopping,shgo,direct.
They cost many more evaluations, so they suit cheap functions with few variables. A common pattern is a global search followed by a local minimize polish, which differential_evolution does by default (polish=True).
Example
import numpy as np
from scipy import optimize
# maximise x*y subject to x + 2y <= 10, x, y >= 0 (minimise -x*y)
cons = [{"type": "ineq", "fun": lambda v: 10 - v[0] - 2 * v[1]}]
res = optimize.minimize(lambda v: -v[0] * v[1], x0=[1, 1],
bounds=[(0, None), (0, None)], constraints=cons, method="SLSQP")
print(res.x.round(3), round(-res.fun, 3)) # [5. 2.5] 12.5
def rastrigin(x): # many local minima, global min at 0
return 10 * len(x) + np.sum(x ** 2 - 10 * np.cos(2 * np.pi * x))
local = optimize.minimize(rastrigin, x0=[3.2, -2.8])
glob = optimize.differential_evolution(rastrigin, bounds=[(-5, 5)] * 2, rng=0)
print(round(local.fun, 2), round(glob.fun, 6)) # 17.91 0.0
lp = optimize.linprog(c=[-3, -2], A_ub=[[1, 1], [1, 3]], b_ub=[4, 6], bounds=[(0, None)] * 2)
print(lp.x, -lp.fun) # [4. 0.] 12.0Remember
- Bounds need a method like L-BFGS-B; general constraints need SLSQP, trust-constr or COBYQA
- Supply
jacfor speed and accuracy - Use
linprog/milpfor linear problems and global optimisers for multimodal ones
Watch out for
- Using a local optimiser on a function with many minima and trusting the first answer.