acme¶
cvxpy-style modelling of convex (quadratic) optimisation problems with automatic solver dispatch:
- Polyhedral feasible set (affine
==/<=/>=, piecewise-linear atoms such asabs,pos,maximum,norm1,norm_inf) with a linear or convex quadratic objective → acme's own primal active-set QP solver (dense numpy, warm-startable). - Anything else (second-order, PSD, exponential and power cones) → Clarabel.
Installation¶
Quick start¶
>>> import numpy as np
>>> import acme as ak
>>> n = 10
>>> rng = np.random.default_rng(0)
>>> f = rng.standard_normal((n, n))
>>> sigma, mu = f @ f.T / n, 0.1 * rng.standard_normal(n)
>>> w = ak.Variable(n)
>>> budget = ak.sum(w) == 1
>>> # Polyhedral → active-set
>>> prob = ak.Problem(ak.Minimize(ak.quad_form(w, sigma) - mu @ w), [budget, w >= 0, w <= 0.3])
>>> prob.solve()
-0.00568...
>>> prob.status
'optimal'
>>> prob.solver_stats.solver_name
'ACTIVE_SET'
>>> np.round(w.value, 3) + 0.0 # + 0.0 shows -0.0 as 0.0
array([0.3 , 0.266, 0.151, 0. , 0. , 0. , 0.075, 0.165, 0. ,
0.043])
>>> round(float(budget.dual_value), 4)
-0.0508
>>> # Second-order cone risk limit → Clarabel
>>> chol = np.linalg.cholesky(sigma)
>>> prob = ak.Problem(ak.Maximize(mu @ w), [ak.sum(w) == 1, w >= 0, ak.norm2(chol.T @ w) <= 0.2])
>>> prob.solve()
0.04907...
>>> prob.solver_stats.solver_name
'CLARABEL'
Modelling¶
| Category | Available |
|---|---|
| Variables | Variable(shape, name, nonneg=, nonpos=, symmetric=, PSD=) — scalars, vectors, matrices |
| Affine | + - * / @, indexing, .T, sum(x, axis), trace, diag, reshape, vec, hstack, vstack, multiply |
| Piecewise linear | abs, pos, maximum, minimum, max, min, norm1, norm_inf |
| Quadratic | sum_squares, square, x**2, quad_form(x, S) |
| Second-order cone | norm2 / norm(x, 2), quadratic atoms used in constraints |
| Exponential cone | exp, log, entr, log_sum_exp |
| Power cone | power(x, p), sqrt, inv_pos, pnorm(x, p) |
| Semidefinite | X >> Y, X << Y, lambda_max, lambda_min |
| Explicit cones | SOC(t, x), ExpCone(x, y, z), PowCone3D(x, y, z, alpha) |
Rules (light DCP)¶
Minimizetakes a convex objective andMaximizea concave one. A constraintlhs <= rhsneedslhs - rhsto be convex, and==/>>need affine sides.- Atom arguments have to be affine. Nested atoms such as
exp(abs(x))raiseDCPError. - Nonlinear terms can only be combined by sums and by constant linear maps with a single sign
(for example
2 * sum(exp(x)) - log(y)). power(x, p)withp > 1means|x|^p.
Solving¶
>>> prob.solve() # automatic: ACTIVE_SET if polyhedral, else CLARABEL
0.04907...
>>> prob.solve(solver="CLARABEL", tol_gap_rel=1e-10) # force a backend; extra kwargs are Clarabel settings
0.04907...
>>> prob.solve(warm_start=True) # active-set starts from the variables' current values
0.04907...
>>> prob.is_polyhedral() # which backend the automatic choice picks
False
>>> data = prob.get_problem_data() # conic standard form: min ½x'Px + q'x s.t. Ax + s = b, s ∈ K
After a solve, prob.status holds one of the cvxpy statuses (optimal, infeasible, unbounded,
optimal_inaccurate, user_limit, ...). prob.value is the optimal value (±inf when the problem
is infeasible or unbounded). Each variable's .value and each constraint's .dual_value are filled
in, and the duals follow cvxpy's sign conventions.
Using the active-set solver directly¶
>>> from acme import solve_qp
>>> # min ½x'Px + q'x s.t. E x = f, G x <= h (P positive semidefinite)
>>> p, q = np.diag([2.0, 4.0]), np.array([-2.0, -4.0])
>>> e, f = np.array([[1.0, 1.0]]), np.array([1.0])
>>> g, h = -np.eye(2), np.zeros(2)
>>> res = solve_qp(p, q, e, f, g, h, x0=None, working_set=None)
>>> res.status, res.working_set
('optimal', [])
>>> res.x
array([0.33333333, 0.66666667])
>>> res.y_eq, res.y_ineq
(array([1.33333333]), array([0., 0.]))
The solver uses the primal active-set method (Nocedal & Wright, Alg. 16.3) in the null space of the working set:
- On a positive definite reduced Hessian it takes a Newton step. Along directions of zero curvature
it follows a descent ray, which lets it handle LPs and semidefinite
P. A ray that no constraint blocks means the problem is unbounded. - It finds a feasible starting point with a phase-1 problem
min τ s.t. Gx − τ ≤ h. - It drops redundant equality rows, and reports inconsistent ones as infeasible.
- It can warm start from a previous point and working set.
It is dense, so it suits small and medium problems.