Output
This section documents Sleipnir's diagnostic output when the diagnostics option is set to true. We'll show the diagnostics for the following problem:
max xy
x,y
subject to x + 3y = 36
from sleipnir.optimization import Problem
problem = Problem()
x, y = problem.decision_variable(2)
problem.maximize(x * y)
problem.subject_to(x + 3 * y == 36)
problem.solve(diagnostics=True)
Exit conditions
First, Sleipnir prints the user-configured exit conditions.
User-configured exit conditions:
↳ error below 1e-08
↳ iteration callback requested stop
↳ executed 5000 iterations
The user-configurable exit conditions include the error tolerance, maximum iterations, and timeout passed to the solve() call; and iteration callbacks added to the Problem returning true.
Problem size and structure
Then, Sleipnir prints the problem's size and structure.
Problem structure:
↳ quadratic cost function
↳ linear equality constraints
↳ no inequality constraints
2 decision variables
1 equality constraint
↳ 1 linear
0 inequality constraints
Then, Sleipnir prints the solver selected based on that information:
Available solvers include:
- No-op for trivial problems
- Newton for unconstrained problems
- Sequential Quadratic Programming (SQP) for equality-constrained problems
- Interior-point method (IPM) for inequality-constrained problems
Iterations
After the solver takes each step, it prints a row of iteration diagnostics in a table format.
┏━━━━┯━━━━┯━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━┯━━━━━━━━┯━━━━━┯━━━━━━━━┯━━━━━━━━┯━━┓
┃iter│type│time (ms)│ error │ cost │ infeas. │complement. │ μ │ reg │primal α│ dual α │↩ ┃
┡━━━━┷━━━━┷━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━┷━━━━━━━━┷━━━━━┷━━━━━━━━┷━━━━━━━━┷━━┩
│ 0 norm 0.021 1.799760e-03 -1.080000e+02 6.016734e-10 0.000000e+00 0.00e+00 10⁻⁴ 1.00e+00 1.00e+00 0│
│ 1 norm 0.005 1.199700e-07 -1.080000e+02 9.947598e-14 0.000000e+00 0.00e+00 10⁻⁴ 1.00e+00 1.00e+00 0│
│ 2 norm 0.002 4.998668e-12 -1.080000e+02 0.000000e+00 0.000000e+00 0.00e+00 10⁻⁴ 1.00e+00 1.00e+00 0│
└────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
The headings are defined as follows:
| Heading | Description |
| iter | Iteration number |
| type | Iteration type
-
norm = normal
-
✓SOC = accepted second-order correction
-
XSOC = rejected second-order correction
-
rest = feasibility restoration
|
| time (ms) | Duration of iteration in milliseconds |
| error | Infinity norm of scaled KKT condition errors |
| cost | Cost function value at current iterate |
| infeas. | Constraint infeasibility at current iterate |
| complement. | Complementary slackness at current iterate (sᵀz) |
| μ | Barrier parameter |
| reg | Iteration matrix regularization |
| primal α | Primal step size |
| dual α | Dual step size |
| ↩ | Number of line search backtracks |
Time traces
At the end of the solve, the solver prints time traces of itself and the autodiff.
┏━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━┯━━━━┓
┃ solver trace │ percent │total (ms)│each (ms)│runs┃
┡━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━┷━━━━┩
│solver 100.00%▕█████████▏ 0.065 0.065 1│
│↳ setup 4.62%▕▍ ▏ 0.003 0.003 1│
│↳ iteration 44.62%▕████ ▏ 0.029 0.009 3│
│ ↳ feasibility check 7.69%▕▋ ▏ 0.005 0.001 3│
│ ↳ callbacks 0.00%▕ ▏ 0.000 0.000 3│
│ ↳ KKT matrix build 3.08%▕▎ ▏ 0.002 0.000 3│
│ ↳ KKT matrix decomp 6.15%▕▌ ▏ 0.004 0.001 3│
│ ↳ KKT system solve 3.08%▕▎ ▏ 0.002 0.000 3│
│ ↳ line search 13.85%▕█▏ ▏ 0.009 0.003 3│
│ ↳ SOC 0.00%▕ ▏ 0.000 0.000 0│
│ ↳ next iter prep 0.00%▕ ▏ 0.000 0.000 3│
│ ↳ f(x) 0.00%▕ ▏ 0.000 0.000 7│
│ ↳ ∇f(x) 3.08%▕▎ ▏ 0.002 0.000 4│
│ ↳ ∇²ₓₓL 0.00%▕ ▏ 0.000 0.000 4│
│ ↳ ∇²ₓₓL_c 0.00%▕ ▏ 0.000 0.000 0│
│ ↳ cₑ(x) 1.54%▕▏ ▏ 0.001 0.000 7│
│ ↳ ∂cₑ/∂x 0.00%▕ ▏ 0.000 0.000 4│
└──────────────────────────────────────────────────────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━┯━━━━━━━━━┯━━━━┓
┃ autodiff trace │ percent │total (ms)│each (ms)│runs┃
┡━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━┷━━━━━━━━━┷━━━━┩
│setup 100.00%▕█████████▏ 0.017 0.017 1│
│↳ ∇f(x) 5.88%▕▌ ▏ 0.001 0.001 1│
│↳ ∇²ₓₓL 35.29%▕███▏ ▏ 0.006 0.006 1│
│↳ ∇²ₓₓL_c 11.76%▕█ ▏ 0.002 0.002 1│
│↳ ∂cₑ/∂x 5.88%▕▌ ▏ 0.001 0.001 1│
└──────────────────────────────────────────────────────────────────┘
The function evaluations are defined as follows:
| Function | Description |
| f(x) | Cost function value |
| ∇f(x) | Cost function gradient |
| ∇²ₓₓL | Lagrangian Hessian |
| ∇²ₓₓL_c | Constraint part of Lagrangian Hessian |
| cₑ(x) | Equality constraint value |
| ∂cₑ/∂x | Equality constraint Jacobian |
| cᵢ(x) | Inequality constraint value |
| ∂cᵢ/∂x | Inequality constraint Jacobian |
Exit status
Finally, the solver prints its exit status.
Possible exit statuses include:
| Status | Value | Description |
| SUCCESS | 0 | Solved the problem to the desired tolerance. |
| CALLBACK_REQUESTED_STOP | 1 | The solver returned its solution so far after the user requested a stop. |
| TOO_FEW_DOFS | -1 | The solver determined the problem to be overconstrained and gave up. |
| LOCALLY_INFEASIBLE | -2 | The solver determined the problem to be locally infeasible and gave up. |
| GLOBALLY_INFEASIBLE | -3 | The problem setup frontend determined the problem to have an empty feasible region. |
| FACTORIZATION_FAILED | -4 | The linear system factorization failed. |
| FEASIBILITY_RESTORATION_FAILED | -5 | The solver failed to reach the desired tolerance, and feasibility restoration failed to converge. |
| NONFINITE_INITIAL_GUESS | -6 | The solver encountered nonfinite initial cost, constraints, or derivatives and gave up. |
| DIVERGING_ITERATES | -7 | The solver encountered diverging primal iterates xₖ and/or sₖ and gave up. |
| MAX_ITERATIONS_EXCEEDED | -8 | The solver returned its solution so far after exceeding the maximum number of iterations. |
| TIMEOUT | -9 | The solver returned its solution so far after exceeding the maximum elapsed wall clock time. |
Negative values indicate errors.
Problem formulation tips
Optimizing the problem formulation
Cost functions and constraints can have the following orders:
- none (i.e., there is no cost function or are no constraints)
- constant
- linear
- quadratic
- nonlinear
For nonlinear problems, the solver calculates the Hessian of the cost function and the Jacobians of the constraints at each iteration. However, problems with lower order cost functions and constraints can be solved faster. For example, the following only need to be computed once because they're constant:
- the Hessian of a quadratic or lower cost function
- the Jacobian of linear or lower constraints
A problem is constant if:
- the cost function is constant or lower
- the equality constraints are constant or lower
- the inequality constraints are constant or lower
A problem is a linear program (LP) if:
- the cost function is linear
- the equality constraints are linear or lower
- the inequality constraints are linear or lower
A problem is a quadratic program (QP) if:
- the cost function is quadratic
- the equality constraints are linear or lower
- the inequality constraints are linear or lower
All other problems are nonlinear programs (NLPs).
Avoiding numerical issues
Instead of using distance (2-norm) for the cost function, use sum-of-squares. The distance calculation's square root is nonlinear and has a limited domain, whereas sum-of-squares has the same minimum, is quadratic, and has no domain restriction. In other words, use minimize(x ** 2 + y ** 2 + z ** 2) instead of minimize(hypot(x, y, z)).
Deduplicating autodiff work
Store common subexpressions in intermediate variables and reuse them instead of writing out the subexpressions each time. This ensures common subexpressions in the expression tree are only traversed and updated once.
Minimum-time problems
The obvious problem formulation for minimum-time problems uses one dt shared across all timesteps.
import sleipnir as slp
N = 100
T_max = 5.0
problem = slp.optimization.Problem()
x = problem.decision_variable(N + 1)
v = problem.decision_variable(N)
dt = problem.decision_variable()
dt.set_value(T_max / N)
problem.subject_to(dt > 0)
problem.subject_to(dt < T_max / N)
for k in range(N):
problem.subject_to(x[k + 1] == x[k] + v[k] * dt)
problem.minimize(dt)
problem.solve()
The nonzero initial value for dt avoids a degenerate case, and the upper bound prevents the solver exploiting discretization artifacts.
This formulation can have feasibility issues though per section 15.3 "Elimination of variables" of "Numerical Optimization, 2nd Ed.". Instead, we recommend using a separate dt for each timestep, with them all equality-constrained.
import sleipnir as slp
N = 100
T_max = 5.0
problem = slp.optimization.Problem()
x = problem.decision_variable(N + 1)
v = problem.decision_variable(N)
dt = problem.decision_variable(N)
problem.subject_to(dt > 0)
problem.subject_to(dt < T_max / N)
for k in range(N - 1):
problem.subject_to(dt[k] == dt[k + 1])
for k in range(N):
dt[k].set_value(T_max / N)
problem.subject_to(x[k + 1] == x[k] + v[k] * dt[k])
problem.minimize(sum(dt))
problem.solve()