Failure modes and degeneracy¶
What COSA does on the hard instances, why, and which mitigation is responsible. This is #36 and §12.4 of the plan, and it exists to answer Success Criterion 7's neighbour — "COSA's numerical behaviour on degenerate and ill-conditioned problems is understood".
Every number here is generated. cosa.experiments.failures.report() produces this
table; nothing below is typed by hand. A claim about numerical behaviour that is written
down once and never re-run stops being true silently, which is the failure mode this
document is most at risk of.
The result¶
Thirteen families, three seeds each, twenty assets. Thirty-six solved, three wrong.
| verdict | meaning | count |
|---|---|---|
solved |
optimal, §6's residuals certify it, and a reference solver agrees | 36 |
wrong |
optimal and certified, and disagreeing with the reference | 3 |
loose |
the loop called it optimal, the residuals do not quite agree | 0 |
unchecked |
no reference was available, so nothing was verified | 0 |
diagnosed |
stopped, and named what happened | 0 |
undiagnosed |
the iteration limit, which names nothing | 0 |
wrong is the category that matters, and it did not exist until the study stopped
certifying itself. A diagnosed stop is honest and an undiagnosed one is at least
visible. A wrong answer looks like success: the loop reports optimal, all five of §6's
residuals are inside their tolerance, and the answer is several percent away from the
reference's. Only a cross-check catches it — which is what Success Criterion 5 has always
asked for, and what this study was not doing.
Per family¶
| family | verdict | residual | iterations | notes |
|---|---|---|---|---|
| basic | solved | 5e-11 | 90 | |
| box | solved | 8e-11 | 48 | |
| sector | solved | 5e-11 | 90 | |
| factor exposure | solved | 2e-10 | 139 | rank-deficient Sigma |
| turnover | solved | 2e-10 | 336 | most iterations of the structured families |
| large | solved | 7e-11 | 33 | optimum at the apex |
| nearly redundant | solved | 9e-11 | 49 | |
| highly correlated | solved | 1e-15 | 40 | |
| ill conditioned | solved | 9e-11 | 174 | cond(Sigma) around 1e10 |
| nearly active cone | solved | 5e-11 | 90 | §8.2's band |
| degenerate optimum | solved | 2e-16 | 4 | dependent active set |
| many active bounds | solved | 1e-10 | 42 | |
| badly scaled | wrong | 1e-11 residual, 3.4e-2 gap | 1000 (the whole budget) | see below |
Behaviour at the apex¶
large and factor exposure build a rank-k covariance over n >> k assets, so L has
an (n-k)-dimensional null space, the minimum-risk portfolio has risk exactly zero, and
lam > 0 takes it. Their optima are at the apex, and nobody designed them that way —
it is a consequence of low-rank covariance, which is how factor models are built.
large is the fastest family in the table, at 33 iterations. That is worth stating
plainly because the apex is where the tangent representation has nothing to say and where
#24's branch takes over: exact membership on the
direction, the normal cone on the multiplier. On these instances the branch is not a
degenerate case being survived, it is the fast path.
The apex failure that does occur is blocked-at-apex, and it does not appear here at all —
it appears on §16.3's randomized generator, at three instances in two hundred. What happens
is that the branch cannot justify holding the apex and cannot release it either: on eq. (7)
the released direction is infeasible by arithmetic, because t appears in exactly two
places and dropping the factor forces d_t = -lam/rho < 0. That is
#39, it is a genuine limitation, and the solver
reports it rather than looping.
Behaviour on rank-deficient Sigma¶
Covered above and by highly correlated, whose residual is at rounding level. Rank
deficiency turns out not to be a difficulty for this formulation: covariance_factor
handles any Sigma ⪰ 0 by eigendecomposition and simply produces a shorter L, and a
shorter L makes the cone smaller, not worse conditioned. The KKT system does not inherit
the covariance's conditioning at all — the tangent representation puts L into the system as
a single row, and one row has no spectrum.
Behaviour on degenerate optima and dependent active sets¶
degenerate optimum solves in four iterations with a residual at rounding level, which
looks like the study failing to find anything hard. It is not: the family's difficulty is
that the active rows at the optimum are linearly dependent, so the KKT matrix is singular
and the multipliers are not determined. §8.3's dependent-row removal repairs the working set
before the direction solve, and the repair is exact — which is why the answer is better
than the well-conditioned families' rather than worse.
This family is also the one that found the bug: #12's
singular-KKT guard never fired, because LAPACK raises only on an exactly zero pivot and a
dependent set gives 1e-18. The guard became an explicit rank test. The family existed for
about ten minutes before it earned its place.
The one failure: badly scaled, and two wrong diagnoses before the right one¶
This section has had three conclusions. The first two were wrong, and how they were wrong is more useful than the current one.
badly scaled puts a constraint matrix spanning fourteen orders of magnitude in front of the
solver — |A| around 1e-4, |G| and |c| around 1e6.
First conclusion: a conditioning failure that scaling fixes. The family stalled at a
residual around 5e-2, and §13.3's Ruiz equilibration converted the stall into an apparent
optimum at 9.7e-7. Clean before-and-after, and wrong.
Second conclusion: an initialization bug, and it solves unaided. Building the public
interface exposed that raise_free_heads — the routine that repairs conic feasibility by
setting t = ||Lx|| — required the cone's head row to select its variable with a coefficient
of exactly one:
Nothing needs that; the head is coefficient * t + h_head and solving head >= ||tail|| is
one division. But no rescaled instance satisfies it, so _heads_are_free reported False,
the retraction was silently unavailable, and an iterate on the cone's boundary could not
move at all. That is Risk 1 — the boundary-immobility result — arriving with its remedy
switched off, and equilibration had been "fixing" it by perturbing the head row's
coefficient. With the restriction removed the family runs to completion with every residual
under 1e-11, which the study reported as solved.
It was not solved. That verdict was self-certified: the study trusted §6's residuals and never asked a reference solver. It should have.
| objective | feasibility of that point | |
|---|---|---|
| COSA | −0.00524 | worst Az−b = −2.5e-2, \|Ez−d\| = 1.6e-15, cone slack 0 |
| CLARABEL | −0.03956 | worst Az−b = −5.0e-11, \|Ez−d\| = 2.2e-16, cone slack 1.5e-12 |
The reference's point is feasible for COSA's own feasibility check to 1e-11, and its
objective is 7.5× better. The direction from COSA's point to it is a feasible descent
direction with derivative −0.0343. No tolerance argument survives that: a better feasible
point demonstrably exists.
Why the residual does not notice¶
Both statements are true at once, and that is the whole difficulty.
- The stationarity residual there is 1.93e-05 absolute.
- §14.2 reports it relative to the objective's scale, dividing by
max(1, |c|_inf)— and|c|_infis2e6. So it prints as 9.7e-12.
A convex problem cannot have an exactly satisfied KKT system at a suboptimal point, and
this one does not: the residual is real. It is simply small enough, relative to data of size
1e6, that a relative certificate cannot distinguish it from zero — while the conditioning
amplifies it into a 3.4% objective error. The certificate is relatively satisfied and the
answer is wrong, and neither of those is a mistake in the other.
That is a statement about what a relative KKT residual can promise on an ill-conditioned instance, and it is the most important thing in this document. It also means §6's tolerance is not a sufficient stopping criterion on its own for instances of this kind — a finding for #22 and #41 rather than one this study can fix.
And equilibration still does not help¶
Worth stating, since it was the first conclusion. Equilibrated, the same instance lands at −0.00540 against the reference's −0.03956 — a gap of 3.4e-2, indistinguishable from the unscaled solve's. Scaling changed the appearance of the residual and never the answer.
Which mitigation addressed which failure¶
§12.4 asks for this specifically. A mitigation is measured by removing it and re-solving.
| mitigation | changed an outcome | changed the cost |
|---|---|---|
| §8.3 regularization | no | no |
| §13.2 factorization reuse | no | factorizations 98.9% → 1.4%, iterations +30% |
| §13.3 Ruiz equilibration | no | iterations +30% to +80%, and one residual five orders worse |
No mitigation changes an outcome, including for the family that fails: nothing rescues
badly scaled, equilibration included.
Two more things it says that are easy to miss.
§8.3's regularization never changes an outcome, and that is the right result rather than a disappointing one. The loop tries dependent-row removal first and falls back to regularization only when the dependency lies among rows it may not drop — equalities, and the cone's own rows. On these families removal always succeeds. Regularization is the answer to a nearby question and it is correct that the solver never has to ask one.
§13.2's reuse changes no verdict by design, and does change the iteration count. It is a
cost policy, not a numerical one: it computes the same direction by a different route,
agreeing to 1e-16 at any given point. But agreeing to 1e-16 is not agreeing exactly, and
over a few hundred iterations the two trajectories separate — about 30% more iterations
under reuse on these families, against 98.9% fewer factorizations. Both halves are in
architecture.md.
What is not ablated, and why¶
Four things that protect this solver cannot be switched off through the public interface, and the reason is the same in each case: a solver without them is not a worse solver but a different one that does not converge, and an ablation that always reports "iteration limit" measures nothing.
- #23's Lagrangian curvature. Measured where it landed: across the eleven families
9738 iterations to 4691, with four
ill conditionedinstances going from the iteration limit to an answer. - #29's anti-cycling guard and no-progress rule. Measured where they landed: the worst randomized instance's returns to a single working set, 486 to 2.
- #18's exact conic step and #20's retraction, which are the step, not a policy about it.
Their before-and-after evidence lives in tests/test_conic_logic.py and
tests/test_anticycling.py, next to the code each one changed.
Open¶
badly scaledterminates claiming optimality at a wrong answer. The open question is not the family, it is the criterion: a relative KKT residual cannot certify an instance whose data spans fourteen orders of magnitude, and §6 currently has no absolute floor. That belongs to #22.- Every "solved" verdict now requires a reference solver to agree, so a study run without
one reports
uncheckedrather thansolved. The self-certified version of this study was wrong for two waves in two different directions. blocked-at-apexon 3 of 200 randomized instances. #39. Not a tolerance problem and not fixable by a working-set rule; the release the multiplier authorizes is arithmetically unavailable.- Slow convergence on 5 of 200 randomized instances, which run to the iteration limit with one working set and no cycling. A convergence-rate question rather than a robustness one, and the natural place to look is the retraction: it is a first-order process on a curved boundary, and #23's curvature improved the direction without changing that.