Regularized SDPs#
Regularization adds a spectral penalty to the primal SDP. The unregularized problem is
Write the eigendecomposition of the primal as
A scalar convex \(\varphi\) is applied to \(X\) spectrally, so the penalty is a sum over eigenvalues,
and the regularized primal objective at strength \(\varepsilon > 0\) is
Dual-side view#
Write the dual slack as
The regularized dual is unconstrained and differentiable,
where \(\psi\) is the Legendre transform of \(\varphi\). The map
primal_from_dual inverts the first-order relation in the eigenbasis of
\(S\),
Built-in regularizers#
Entropy regularizer, \(\varphi(t) = t(\log t - 1)\). |
|
Quadratic spectral regularizer on nonnegative spectra. |
Entropy regularization uses
Quadratic regularization uses
In both cases, the scalar formulas are lifted to matrices through the eigenvalues of Hermitian matrices.
Fixed-trace recovery#
Every method that recovers a primal takes normalized. It selects the
fixed-trace conjugate – the supremum taken over unit-trace primals only –
rather than the free one, so it changes the objective and not merely the
reported \(X\):
The trace constraint carries a multiplier that enters additively in the argument, \(X = \psi'((S - \theta)/\varepsilon)\) with \(\theta\) fixed by \(\sum_i \psi'((s_i - \theta)/\varepsilon) = 1\). Dividing \(\psi'(S/\varepsilon)\) by its trace is not the same operation: a division preserves the ratios \(\lambda_i/\lambda_j\) and the zero pattern, while the shift changes both. For entropy the two coincide – a shift rescales \(\exp\) globally, so the answer is \(\operatorname{softmax}(S/\varepsilon)\), the Gibbs state. For the quadratic penalty the shift moves the clip point instead, so the answer is the projection onto the simplex and is genuinely low rank.
Solving#
Couple a problem to a regularizer, bind \(\varepsilon\), and hand the
result to the solver. bind returns a standard single-argument
spacecore.Functional, so the spacecore.optimize drivers consume it
directly.
from sdplab import EntropyReg, RegularizedSDPDualFunctional, run_regularized_solver
from sdplab.examples import generate_max_cut
problem = generate_max_cut(8, seed=0, unit_trace=True)
dual = RegularizedSDPDualFunctional(problem, EntropyReg(problem.dom))
result = run_regularized_solver(dual.bind(0.1), verbose=0)
X = dual.primal_from_dual(result.dual, 0.1)
problem.primal_objective(X) # -5.0859, against -5.0990 from CVXPY
\(\varepsilon\) is supplied per call rather than stored, so a continuation
schedule can lower it without rebuilding the functional. The instance is built
with unit_trace=True because primal_from_dual normalizes to
\(\operatorname{Tr}[X] = 1\): on a problem whose feasible set has a different
trace the recovered \(X\) would not be feasible for it.