Tutorial: reading convergence, choosing options¶
The anytime history¶
Result.history stores (seconds, evaluations, best certified value) along the run. The value is in the internal
normalized units; Result.bound is in your units. To see the bound over time in your units:
import itertools
import numpy as np
import moment_sos as po
n = 8
s = po.variables(n)
rng = np.random.default_rng(0)
W = rng.choice([-1.0, 0.0, 1.0], size=(n, n))
f = sum(W[i, j] * (1 - s[i]*s[j]) / 2 for i, j in itertools.combinations(range(n), 2))
problem = po.Problem(f, boolean=True)
res = po.solve(problem, order=2)
P = problem
bound_over_time = [(t, P.f_off + P.f_scale * u) for t, _, u in res.history] # max problem: internal -> original
assert abs(bound_over_time[-1][1] - res.bound) < 1e-9
assert all(b >= a - 1e-9 for a, b in zip([b for _, b in bound_over_time][1:], [b for _, b in bound_over_time])) # non-increasing
print(len(res.history), "points; first", round(bound_over_time[0][1], 3), "last", round(bound_over_time[-1][1], 5))
The best certified bound never increases, so you can stop whenever the bound is good enough for your purpose.
Picking the solver and the schedule¶
situation |
recommendation |
|---|---|
quick certified bound, MAX-CUT-like Boolean problem |
|
integer optimum expected |
|
need the relaxation value to \(10^{-6}\) or better |
|
many inequality or equality constraints |
|
big problem with weak coupling |
|
huge weights spanning orders of magnitude |
expect lower accuracy from |
Checking a result against an interior-point solver¶
For problems small enough, po.solve_dual_ipm solves the same dual exactly with MOSEK (or Clarabel when MOSEK is not
installed). It needs cvxpy and a solver (pip install cvxpy clarabel):
import itertools
import moment_sos as po
s = po.variables(6)
problem = po.Problem(sum((1 - s[i]*s[j]) / 2 for i, j in itertools.combinations(range(6), 2)), boolean=True)
res = po.solve_alm(problem, order=2)
relax = po.Relaxation(problem, 2)
value, seconds = po.solve_dual_ipm(relax)
value = problem.f_off + problem.f_scale * value # back to original units
print(res.bound, value)
assert abs(res.bound - value) < 1e-4
Common problems¶
The bound barely moves from the order-1 value. At order 2 with the smoothing solver, the first stages may sit at the order-1 value until \(\eta\) is small; raise
time_limitor usesolve_alm.The bound is far from the feasible value. Increase the order, tighten the box, or check
res.historyfor convergence.ValueErrormentioning cliques. A constraint or objective monomial spans two cliques; merge them or use"dense".Memory. The moment block takes \(8s^2\) bytes per copy and the solver keeps several; budget about \(50s^2\) bytes.