import json
import math
from pathlib import Path
import numpy as np
from pounce import qp as pq
HERE = Path(__file__).resolve().parent
OUT = HERE / "qp_python.json"
SQRT2 = 2.0**0.5
LP = dict(c=[-1.0, -2.0], A=[[1.0, -1.0]], b=[0.0], G=[[1.0, 1.0]], h=[1.0], lb=[0.0, 0.0])
QP = dict(
P=np.eye(2), c=[-1.0, -1.0], G=[[1.0, 1.0]], h=[1.0], lb=[0.0, 0.0], ub=[10.0, 10.0]
)
QP_COUPLED = dict(P=[[2.0, -1.0], [-1.0, 2.0]], c=[-1.0, -1.0])
INDEFINITE = dict(P=[[1.0, 0.0], [0.0, -1.0]], c=[0.0, 0.0], lb=[-1.0, -1.0], ub=[1.0, 1.0])
QP_INFEASIBLE = dict(c=[1.0], G=[[-1.0]], h=[-1.0], ub=[0.0])
QP_UNBOUNDED = dict(c=[-1.0], lb=[0.0])
SOC_DISTANCE = dict(
c=[1.0, 0.0, 0.0],
A=[[0.0, 1.0, 1.0]],
b=[0.0],
G=-np.eye(3),
h=[0.0, -2.0, 1.0],
cones=[("soc", 3)],
)
G_MIXED = [[1.0, 0.0], [0.0, 0.0], [0.0, -1.0]]
ROWS = [
("lp_ipm", "qp", LP, {}),
("lp_presolve_crossover", "qp", LP, dict(qp_presolve=True, qp_crossover=True)),
("lp_direct", "qp", LP, dict(qp_hsde=False)),
("lp_active_set", "qp", LP, dict(method="active-set")),
("qp_ipm", "qp", QP, {}),
("qp_ipm_tau", "qp", QP, dict(tau=0.9, tau_max=0.99)),
("qp_ipm_reg_no_equilibrate", "qp", QP, dict(qp_reg=1e-8, qp_equilibrate=False, tol=1e-10)),
("qp_active_set_presolve", "qp", QP, dict(method="active-set", qp_presolve=True)),
("qp_coupled", "qp", QP_COUPLED, {}),
("indefinite_active_set", "qp", INDEFINITE, dict(method="active-set")),
("qp_iteration_limit", "qp", QP, dict(max_iter=1)),
("qp_primal_infeasible", "qp", QP_INFEASIBLE, {}),
("qp_dual_infeasible", "qp", QP_UNBOUNDED, {}),
(
"nonneg",
"socp",
dict(c=[-1.0, -1.0], G=np.eye(2), h=[1.0, 2.0], cones=[("nonneg", 2)]),
{},
),
("soc_distance", "socp", SOC_DISTANCE, {}),
("soc_distance_tol", "socp", SOC_DISTANCE, dict(tol=1e-10)),
(
"soc_projection",
"socp",
dict(P=np.eye(3), c=[-1.0, -2.0, 0.0], G=-np.eye(3), h=[0.0, 0.0, 0.0], cones=[("soc", 3)]),
{},
),
(
"nonneg_and_soc",
"socp",
dict(c=[-1.0, -1.0], G=G_MIXED, h=[1.0, 1.0, 0.0], cones=[("nonneg", 1), ("soc", 2)]),
{},
),
(
"exp_geometric_program",
"socp",
dict(
c=[0.0, 1.0, 1.0],
G=[
[-1.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, -1.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, -1.0],
],
h=[0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
cones=[("exp", 3), ("exp", 3)],
),
{},
),
(
"soc_and_exp",
"socp",
dict(
c=[1.0, 1.0],
G=[[-1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, -1.0]],
h=[0.0, 3.0, 4.0, 1.0, 1.0, 0.0],
cones=[("soc", 3), ("exp", 3)],
),
{},
),
(
"power",
"socp",
dict(
c=[-1.0, 0.0, 0.0],
A=[[0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
b=[2.0, 0.5],
G=-np.eye(3),
h=[0.0, 0.0, 0.0],
cones=[("pow", 0.3)],
),
{},
),
(
"psd",
"socp",
dict(c=[-1.0], G=[[1.0], [0.0], [1.0]], h=[2.0, SQRT2, 2.0], cones=[("psd", 2)]),
{},
),
(
"soc_primal_infeasible",
"socp",
dict(c=[0.0, 0.0], G=[[1.0, 0.0], [-1.0, 0.0], [0.0, -1.0]], h=[-1.0, 0.0, 0.0],
cones=[("nonneg", 1), ("soc", 2)]),
{},
),
(
"soc_dual_infeasible",
"socp",
dict(c=[0.0, -1.0], G=-np.eye(2), h=[0.0, 0.0], cones=[("soc", 2)]),
{},
),
("soc_iteration_limit", "socp", SOC_DISTANCE, dict(max_iter=2)),
]
def finite_or_null(v):
return [None if math.isinf(x) else float(x) for x in v]
def model_of(kind, m):
c = np.asarray(m["c"], dtype=np.float64).ravel()
n = c.shape[0]
pr, pc, pv = pq._lower_triangle_coo(m.get("P"), n)
ar, ac, av = pq._coo(m.get("A"), n, "A")
gr, gc, gv = pq._coo(m.get("G"), n, "G")
vec = lambda k: [] if m.get(k) is None else np.asarray(m[k], np.float64).ravel().tolist() out = dict(
n=n,
m_eq=len(vec("b")),
m_ineq=len(vec("h")),
p=[pr, pc, pv],
c=c.tolist(),
a=[ar, ac, av],
b=vec("b"),
g=[gr, gc, gv],
h=vec("h"),
lb=finite_or_null(vec("lb")),
ub=finite_or_null(vec("ub")),
cones=[],
)
if kind == "socp":
out["cones"] = [[k, float(v)] for k, v in pq._normalize_cones(m["cones"])]
return out
def result_of(r):
f = lambda v: None if v is None else float(v) vec = lambda v: [float(x) for x in np.asarray(v).ravel()] res = r.residuals
return dict(
status=r.status,
obj=float(r.obj),
iters=int(r.iters),
x=vec(r.x),
y=vec(r.y),
z=vec(r.z),
z_lb=vec(r.z_lb),
z_ub=vec(r.z_ub),
residuals={k: float(res[k]) for k in sorted(res)},
tau=f(r.tau),
kappa=f(r.kappa),
certificate_scale=f(r.certificate_scale),
)
def main():
rows = {}
for name, kind, model, opts in ROWS:
assert name not in rows, name
kw = {k: v for k, v in model.items() if k != "cones"}
if kind == "qp":
r = pq.solve_qp(**kw, **opts)
else:
r = pq.solve_socp(**kw, cones=model["cones"], **opts)
rows[name] = dict(
entry=kind,
options={k: v for k, v in opts.items()},
model=model_of(kind, model),
result=result_of(r),
)
doc = dict(
generator="crates/pounce-cinterface/tests/common/gen_qp_python.py",
platform="linux-x86_64",
rows=rows,
)
OUT.write_text(json.dumps(doc, indent=1, allow_nan=False) + "\n")
print(f"wrote {len(rows)} rows to {OUT}")
if __name__ == "__main__":
main()