| """ |
| Common patterns for constrained geometric optimization using scipy. |
| |
| This module shows how to use scipy.optimize.minimize with inequality |
| constraints and the SLSQP solver — useful for any problem where you |
| need to maximize/minimize an objective subject to geometric constraints. |
| """ |
|
|
| import numpy as np |
| from scipy.optimize import minimize |
|
|
|
|
| def example_constrained_optimization(): |
| """ |
| Template: pack n objects by optimizing positions + sizes jointly. |
| |
| Decision vector: x = [pos_0, pos_1, ..., pos_{n-1}, size_0, ..., size_{n-1}] |
| Objective: maximize sum(sizes) => minimize -sum(sizes) |
| Constraints: non-overlap + boundary containment (all >= 0) |
| """ |
| n = 10 |
|
|
| |
| def objective(x): |
| sizes = x[2 * n:] |
| return -np.sum(sizes) |
|
|
| |
| def constraints_fn(x): |
| positions = x[:2 * n].reshape(n, 2) |
| sizes = x[2 * n:] |
|
|
| c = [] |
| |
| for i in range(n): |
| for j in range(i + 1, n): |
| dist = np.linalg.norm(positions[i] - positions[j]) |
| c.append(dist - sizes[i] - sizes[j]) |
|
|
| |
| for i in range(n): |
| c.append(positions[i, 0] - sizes[i]) |
| c.append(1 - positions[i, 0] - sizes[i]) |
| c.append(positions[i, 1] - sizes[i]) |
| c.append(1 - positions[i, 1] - sizes[i]) |
|
|
| return np.array(c) |
|
|
| |
| x0_pos = np.random.rand(n, 2) * 0.6 + 0.2 |
| x0_sizes = np.full(n, 0.05) |
| x0 = np.concatenate([x0_pos.flatten(), x0_sizes]) |
|
|
| |
| pos_bounds = [(0, 1)] * (2 * n) |
| size_bounds = [(0.01, 0.25)] * n |
| bounds = pos_bounds + size_bounds |
|
|
| |
| result = minimize( |
| objective, |
| x0, |
| method="SLSQP", |
| bounds=bounds, |
| constraints={"type": "ineq", "fun": constraints_fn}, |
| options={"maxiter": 1000, "ftol": 1e-9}, |
| ) |
|
|
| opt_positions = result.x[:2 * n].reshape(n, 2) |
| opt_sizes = result.x[2 * n:] |
| return opt_positions, opt_sizes, -result.fun |
|
|
|
|
| def multi_start_optimization(objective, constraint_fn, bounds, n_starts=5): |
| """ |
| Run SLSQP from multiple random starts and keep the best. |
| |
| This helps escape local optima — the solver is gradient-based |
| and sensitive to the initial guess. |
| """ |
| best_result = None |
| for _ in range(n_starts): |
| x0 = np.array([np.random.uniform(lo, hi) for lo, hi in bounds]) |
| result = minimize( |
| objective, |
| x0, |
| method="SLSQP", |
| bounds=bounds, |
| constraints={"type": "ineq", "fun": constraint_fn}, |
| options={"maxiter": 500, "ftol": 1e-8}, |
| ) |
| if best_result is None or result.fun < best_result.fun: |
| best_result = result |
| return best_result |
|
|