Skip to main content

Module inf_pr_floor

Module inf_pr_floor 

Source
Expand description

How long a solve has sat at a constraint violation it could not get below — the evidence a locally-infeasible verdict actually rests on.

The five reconstructed locally-infeasible gates in restoration (pounce_restoration::resto_inner_solver::run_inner_resto) all claim “the solve stalled at a violation it could not improve on”, and gh#661 is what happens when nothing measures that claim: they test only that the recovered violation is large, which a diverging restoration satisfies ever more emphatically the worse it gets.

The gh#661 divergence guard withholds those gates’ verdict when the restoration sub-solve ends far worse than the violation it was entered at. That guard needs an exemption for one shape of run: a solve that provably could not get below some floor, and whose restoration then blew up over a handful of iterations at the end. The blow-up is the tail of that trajectory, not a description of it.

gh#664 keyed that exemption on inner_iter_count >= 1000, which is a proxy for “it ran out of room” and a doubly loose one. A count is not a stall test — that substitution is the same class of error gh#661 fixed, where a size stood in for a stall test. And the counter it read is not what its comments claimed: the inner IPM’s iter_count is seeded from the outer’s (IpRestoMinC_1Nrm.cpp:181), so 1019 on issue_508_infeasible_gap_1em2 is 1015 outer iterations plus a four-iteration sub-solve, not a sub-solve that ran a thousand times.

InfPrFloor measures the property directly, at the scope where the long trajectory actually lives: the outer solve. It watches the original NLP’s scaled primal infeasibility at each outer iterate and counts how many of them sat within [FLOOR_BAND] of the floor the count is being measured against. A solve still finding its way down keeps clearing the band and restarting that count; one that is out of room accumulates it.

Two choices in that sentence are load-bearing rather than simplifications, and both were arrived at by measuring:

Cumulative, not consecutive. A real trajectory pinned at a floor does not sit there quietly. issue_508_infeasible_gap_1em2 returns to 1.0e-2 over and over across 1016 outer iterations while excursions to 9.56e1 break every run in between; simulated over its printed iteration trace, its longest consecutive stay is 39 — against 19 for pooling_rt2stp, which is feasible and must not be exempted. The consecutive measure does not separate them at all. Time spent at the floor does, by two orders of magnitude: 943 against 7.

Pinned reference, not running minimum. Measuring the band against the running minimum lets it chase the iterates, so a solve creeping downward by 0.9x per iteration is forever within a decade of its own previous best. Under that reading a 2000-iteration grind — which reduces the violation by eighty-eight orders of magnitude, i.e. is working — accumulates all 2000 and buys the exemption. Pinned, it accumulates one decade’s worth and resets.

Structs§

InfPrFloor
Running evidence, over one solve, that it found a floor on the constraint violation and could not get below it.