pympcc.verify_b_stationarity

pympcc.verify_b_stationarity(result, problem, *, tol=1e-06, max_biactive=10)[source]

Test B-stationarity of an MPCC solution by enumerating linearised-MPCC tangent-cone branches and solving an LP per branch.

For each biactive pair i I_00 = {i : G_i(x*) tol AND H_i(x*) tol} the linearised tangent cone splits into two branches:

  • G branch (G stays active): ∇G_iᵀ d = 0,  ∇H_iᵀ d 0

  • H branch (H stays active): ∇H_iᵀ d = 0,  ∇G_iᵀ d 0

For every assignment of branches to the |I_00| pairs (2^|I_00| LPs), solve:

min  ∇f(x*)ᵀ d
s.t. ∇g_jᵀ d ≤ 0          j active inequality (g_j(x*) ≥ -tol)
     ∇hᵀ d   = 0
     ∇G_iᵀ d = 0          i ∈ I_0+ (G_i = 0, H_i > 0 — H>0 locally
                                      forces G_i to stay zero)
     ∇H_iᵀ d = 0          i ∈ I_+0 (G_i > 0, H_i = 0 — G>0 locally
                                      forces H_i to stay zero)
     branch-specific G/H linearisations for i ∈ I_00
     d_j ≥ 0  if x*_j is at xl_j;  d_j ≤ 0 if at xu_j
     ‖d‖_∞ ≤ 1            (normalisation; LP would be unbounded)

x* is B-stationary iff every branch’s LP minimum is ≥ 0 (no feasible first-order descent direction in the linearised cone).

Parameters:
  • result (MPCCResult) – A solved MPCC result (result.x, result.G, result.H, result.success are read).

  • problem (MPCCProblem) – The problem instance used to produce the result.

  • tol (float, optional) – Tolerance for the active-set partition (default 1e-6). Pair i is biactive when G_i tol AND H_i tol; a constraint g_j is active when g_j -tol; a variable bound is active when |x_j xl_j| tol (resp. xu).

  • max_biactive (int, optional) – Skip when |I_00| > max_biactive (default 10 → up to 1024 LPs).

Returns:

Result dict with keys:

  • status — one of "B-stationary" (every branch min ≥ -tol), "not B-stationary" (at least one branch admits descent), "intractable" (|I_00| > max_biactive; LP enumeration skipped), or "unknown" (result.success is False).

  • n_biactive|I_00|.

  • n_branches_checked — LPs actually solved (0 on early exit).

  • min_descent — minimum ∇fᵀd over branches (None on early exit).

  • witness_branch — tuple of 'G' / 'H' chars indexed by the biactive pairs in I_00 order; None if B-stationary.

  • witness_d — ndarray, shape (n,) — the descent direction; None if B-stationary.

Return type:

dict

Notes

Uses dense LPs (scipy.optimize.linprog with method "highs"). Intended as a diagnostic for small/medium MPCCs; cost is O(2^|I_00| · LP) and the per-LP build is dense in n.

The strongest stationarity level reachable from KKT multipliers alone (classify_stationarity) is S, which under MPCC-LICQ is equivalent to B-stationarity. This routine directly certifies B-stationarity without needing MPCC-LICQ to hold.