MCP variable-paired form¶
Many MPCC pairs have the form \(x_j \ge 0\ \perp\ F(x) \ge 0\) where the \(G\)-side is just a variable. Writing comp_G(x) = x[var_idxs] by hand is error-prone; comp_var_pairs declares the pairing at the variable level and the package fills in the identity Jacobian for \(G\) automatically.
Without comp_var_pairs¶
The traditional way — manually write comp_G as a slice and its Jacobian:
import warnings
import numpy as np
import pympcc
problem_a = pympcc.MPCCProblem(
n=3, n_comp=2,
x0=np.array([0.5, 0.5, 0.5]),
xl=np.zeros(3),
objective=lambda x: (x[0] - 1.0) ** 2 + (x[1] - 1.0) ** 2 + (x[2] - 0.5) ** 2,
gradient=lambda x: np.array([2 * (x[0] - 1), 2 * (x[1] - 1), 2 * (x[2] - 0.5)]),
# comp pair 1: x[0] >= 0 ⊥ x[1] >= 0
# comp pair 2: x[2] >= 0 ⊥ (x[0] + x[1]) >= 0
comp_G=lambda x: np.array([x[0], x[2]]),
comp_G_jacobian=lambda x: np.array([[1., 0., 0.], [0., 0., 1.]]),
comp_H=lambda x: np.array([x[1], x[0] + x[1]]),
comp_H_jacobian=lambda x: np.array([[0., 1., 0.], [1., 1., 0.]]),
)
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
r_a = pympcc.solve(problem_a, strategy="scholtes")
print(f"x* = {r_a.x}")
print(f"f* = {r_a.obj:.4f}")
x* = [1.99940693e-08 9.99999970e-01 1.99884686e-08]
f* = 1.2500
With comp_var_pairs¶
The same problem, declared as a list of (var_idx, h_fn[, h_jac_fn]) triples:
problem_b = pympcc.MPCCProblem(
n=3, n_comp=2,
x0=np.array([0.5, 0.5, 0.5]),
xl=np.zeros(3),
objective=lambda x: (x[0] - 1.0) ** 2 + (x[1] - 1.0) ** 2 + (x[2] - 0.5) ** 2,
gradient=lambda x: np.array([2 * (x[0] - 1), 2 * (x[1] - 1), 2 * (x[2] - 0.5)]),
comp_var_pairs=[
# x[0] >= 0 ⊥ x[1] >= 0
(0, lambda x: np.array([x[1]]),
lambda x: np.array([0., 1., 0.])),
# x[2] >= 0 ⊥ (x[0] + x[1]) >= 0
(2, lambda x: np.array([x[0] + x[1]]),
lambda x: np.array([1., 1., 0.])),
],
)
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
r_b = pympcc.solve(problem_b, strategy="scholtes")
print(f"x* = {r_b.x}")
print(f"f* = {r_b.obj:.4f}")
print(f"matches manual form: {np.allclose(r_a.x, r_b.x, atol=1e-5)}")
x* = [1.99940693e-08 9.99999970e-01 1.99884686e-08]
f* = 1.2500
matches manual form: True
What the package did for you¶
Identity G-Jacobians — every \(G\)-side is an indicator on
var_idx, so the package builds it as a constant sparse identity instead of calling your code.Bound clamp —
xl[var_idx]is silently clamped tomax(xl[var_idx], 0.0); you can omitxlfor var-paired indices and the lower bound is set automatically.FD fallback — drop the third element of each tuple and the H-side Jacobian falls back to forward finite differences.
When to use which¶
Form |
Use when |
|---|---|
|
Both sides are general functions of |
|
The \(G\)-side is exactly \(x[\text{var\_idx}]\). Common in MCPs and bilevel KKT systems. |
|
Same, but \(k \gtrsim 10^4\) pairs — see problem setup guide. |
For doubly-bounded \(\ell \le x \le u\ \perp\ F(x)\) form, see comp_box_pairs in the problem setup guide.