Direct-x Cones¶
Moreau’s base conic form wraps every conic constraint in a slack
variable s:
When a cone constrains a sub-vector of x directly — e.g. x[J] ≥ 0
or x[J] ∈ SOC — adding a slack variable inflates the problem with an
extra row of A, an extra slack, and an extra cone block. The
direct-x API lets you declare such constraints inline:
Slack and direct-x cones can be mixed freely. Direct-x typically solves faster than the slack equivalent — single-SOC measured ~2.5× on CPU; batched-CUDA wins are larger for cones with many active constraints.
Python API¶
Declare direct-x cones via XConeSpec entries on Cones.x_cones:
import numpy as np
from scipy import sparse
import moreau
# min 0.5 x'Px + q'x s.t. x[0] >= 0, x[1:4] in SOC_3
P = sparse.diags([1.0, 1.0, 1.0, 1.0], format='csr')
q = np.array([2.0, 1.0, 0.0, 0.0])
A = sparse.csr_matrix(np.zeros((0, 4)))
b = np.array([])
cones = moreau.Cones(
x_cones=[
moreau.XConeSpec(kind='nonneg', indices=[0]),
moreau.XConeSpec(kind='soc', indices=[1, 2, 3]),
],
)
solver = moreau.Solver(P, q, A, b, cones, moreau.Settings(device='cpu'))
sol = solver.solve()
print(sol.x)
XConeSpec fields:
kind: one of'nonneg','soc','psd_triangle','exp','power','gen_power'.indices: list of distinct, non-negative indices intox. Must stay within[0, n). Indices across allx_conesmust be pairwise disjoint. Order matters for SOC (first entry is the scalar \(t\), rest form the vector) and for PSD (column-majorsvec). Forgen_power, the firstlen(alphas)entries are the \(p_i\) components and the remainder are the \(w\) components.psd_k: required whenkind='psd_triangle'; the side length of the PSD matrix. Requireslen(indices) == psd_k * (psd_k + 1) // 2.alpha: required whenkind='power'; the power parameter \(\alpha \in (0, 1)\).alphas: required whenkind='gen_power'; list of positive floats summing to 1.len(alphas)is \(\text{dim}_1\).dim2: required whenkind='gen_power'; the size of the \(w\)-block, \(\text{dim}_2 \geq 1\). Requireslen(indices) == len(alphas) + dim2.
Supported configurations¶
Cone type |
CPU forward |
CPU backward |
CUDA forward |
CUDA backward |
torch / JAX |
|---|---|---|---|---|---|
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ (dense) |
✓ (dense) |
✓ |
|
✓ |
✓ |
✓ (rank-2) |
✓ (rank-2) |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
All cone kinds and surfaces above are wired end-to-end. Batched direct-x
(CompiledSolver with batch_size > 1) works on CPU and CUDA for every
listed kind.
KKT solver compatibility¶
Linear solver |
direct-x support |
|---|---|
|
✓ |
Pardiso MKL / Panua |
✓ (untested) |
|
✓ (CUDA-only) |
|
✓ for nonneg direct-x only (CUDA-only) |
|
✗ — block-tridiag structure breaks |
woodbury exploits diagonal P + nonneg-only cone structure; with nonneg
direct-x cones we add their per-iteration diagonal Hs contribution into
the same Schur-complement diagonal that the slack nonneg path builds.
SOC, PSD, exp, power, and gen_power direct-x are not Woodbury-compatible
because they introduce non-diagonal terms in the (1,1) block.
riccati aren’t valid direct_solve_method values on CPU; cudss and
woodbury are CUDA-only and rejected on CPU at settings validation.
Equilibration caveat¶
Moreau uses Ruiz equilibration by default. For SOC and PSD direct-x cones, the cone constraint isn’t invariant under arbitrary per-entry diagonal scaling, so Moreau replaces the per-entry Ruiz scalings on each cone’s indices with their geometric mean. This is automatic; no configuration needed.
Chordal decomposition and direct-x PSD¶
Chordal decomposition in Moreau analyses the sparsity of the rows of
[A ; b] restricted to each slack PSD cone’s row range, and splits
decomposable cones into smaller blocks. Direct-x PSD cones are not
chordally decomposed: they live in the primal vector rather than in a
slack-row block, so the sparsity-driven analysis does not apply. If
your PSD matrix variable has known block-diagonal structure, declare
the blocks as separate direct-x cones with disjoint indices.
What direct-x does for you¶
A direct-x cone constrains a subvector of x in place, without
introducing the extra slack variable, A row, and b entry that the
equivalent slack constraint would require. The smaller problem is
typically faster to solve and to differentiate. Direct-x duals are
returned alongside the usual primal/dual solution as solution.z_x
(see :doc:../api/core).