omnibias.convex.problem
Differentiable LP and QP solver paths, barrier methods, KKT sensitivities, and scoped optimality evidence.
import omnibias.convex.problemModule contract & mathematical conventions
Backend-agnostic problem / solution containers for omnibias-convex.
The numeric solvers live in omnibias.convex.jax and
omnibias.convex.torch; these containers only hold the data so the two
backends present an identical surface. Arrays are stored untyped (Any) so the
same ConvexSolution works for jax.Array and torch.Tensor.
Shared formulas do not imply universal floating-point identity. Verify dtype, shape, derivative-order support, and the stated numerical assumptions for the path you use.
BarrierOptions
omnibias.convex.BarrierOptionsSource ↗class BarrierOptionsDataclass constructor fields; inherited fields, if any, are declared by the base class. A field(...) expression describes a default factory or field options, not a literal argument default.
Tuning knobs for the log-barrier interior-point solver.
Defaults follow the textbook path-following method (Boyd & Vandenberghe,
*Convex Optimization*, ch. 11): increase the barrier weight t by mu
each outer step, re-centre with a damped Newton phase, and stop when the
surrogate gap m / t drops below tol.
Parameters
| Name | Type & default | Contract |
|---|---|---|
toldataclass field | floatDefault: 1e-09 | See the operation contract and implementation below. |
mudataclass field | floatDefault: 10.0 | See the operation contract and implementation below. |
t0dataclass field | floatDefault: 1.0 | See the operation contract and implementation below. |
max_outerdataclass field | intDefault: 60 | See the operation contract and implementation below. |
newton_itersdataclass field | intDefault: 50 | See the operation contract and implementation below. |
newton_toldataclass field | floatDefault: 1e-12 | See the operation contract and implementation below. |
backtrack_alphadataclass field | floatDefault: 0.25 | See the operation contract and implementation below. |
backtrack_betadataclass field | floatDefault: 0.5 | See the operation contract and implementation below. |
dampingdataclass field | floatDefault: 1e-12 | See the operation contract and implementation below. |
Regression example · certificate gap shrinks with tighter solve
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_certificate_gap_shrinks_with_tighter_solve() -> None:
from omnibias.convex import BarrierOptions
rng = np.random.default_rng(8)
n = 3
M = rng.standard_normal((n, n))
Q = M @ M.T + 2.0 * n * np.eye(n)
c = rng.standard_normal(n)
A = np.vstack([np.eye(n), -np.eye(n)])
b = 2.0 * np.ones(2 * n)
loose = solve_qp(_arr(Q), _arr(c), _arr(A), _arr(b), options=BarrierOptions(tol=1e-2))
tight = solve_qp(_arr(Q), _arr(c), _arr(A), _arr(b), options=BarrierOptions(tol=1e-10))
cert_loose = certify_qp_optimum(Q, c, A, b, np.asarray(loose.x), np.asarray(loose.dual))
cert_tight = certify_qp_optimum(Q, c, A, b, np.asarray(tight.x), np.asarray(tight.dual))
assert cert_tight.gap <= cert_loose.gappython -m pytest packages/omnibias-convex/tests/test_certify.py -k test_certificate_gap_shrinks_with_tighter_solve -qFields & methods
tolfield
tol: float = 1e-09Declared float field on BarrierOptions. Default expression: 1e-09.
mufield
mu: float = 10.0Declared float field on BarrierOptions. Default expression: 10.0.
max_outerfield
max_outer: int = 60Declared int field on BarrierOptions. Default expression: 60.
newton_itersfield
newton_iters: int = 50Declared int field on BarrierOptions. Default expression: 50.
newton_tolfield
newton_tol: float = 1e-12Declared float field on BarrierOptions. Default expression: 1e-12.
backtrack_alphafield
backtrack_alpha: float = 0.25Declared float field on BarrierOptions. Default expression: 0.25.
backtrack_betafield
backtrack_beta: float = 0.5Declared float field on BarrierOptions. Default expression: 0.5.
dampingfield
damping: float = 1e-12Declared float field on BarrierOptions. Default expression: 1e-12.
Read the implementation
class BarrierOptions:
"""Tuning knobs for the log-barrier interior-point solver.
Defaults follow the textbook path-following method (Boyd & Vandenberghe,
*Convex Optimization*, ch. 11): increase the barrier weight ``t`` by ``mu``
each outer step, re-centre with a damped Newton phase, and stop when the
surrogate gap ``m / t`` drops below ``tol``.
"""
tol: float = 1e-9
mu: float = 10.0
t0: float = 1.0
max_outer: int = 60
newton_iters: int = 50
newton_tol: float = 1e-12
backtrack_alpha: float = 0.25
backtrack_beta: float = 0.5
damping: float = 1e-12Other public import paths
omnibias.convex.BarrierOptionsomnibias.convex.problem.BarrierOptions
ConvexSolution
omnibias.convex.ConvexSolutionSource ↗class ConvexSolution(Generic[ArrayT])Dataclass constructor fields; inherited fields, if any, are declared by the base class. A field(...) expression describes a default factory or field options, not a literal argument default.
Result of an LP / QP solve.
Attributes
x: Primal optimum, shape (n,).
dual: Dual multipliers lambda >= 0 for A x <= b, shape (m,) (lambda = 1 / (t * s) on the central path).
slack: Constraint slacks s = b - A x >= 0, shape (m,).
obj: Objective value 1/2 x^T Q x + c^T x at x.
gap: Surrogate duality gap m / t of the final central-path point.
iterations: Number of outer (barrier) iterations taken.
converged: Whether gap <= tol was reached within the iteration budget.
newton_iterations: Total inner (centering) Newton steps across all outer iterations -- the main cost a warm start reduces.
eq_dual: Dual multipliers nu for the optional equality constraints A_eq x = b_eq, shape (m_eq,) (the quadratic-penalty estimate nu = mu (A_eq x - b_eq)); None when no equalities were supplied. Unlike dual it is sign-free.
Parameters
| Name | Type & default | Contract |
|---|---|---|
xdataclass field | ArrayTRequired | Primal optimum, shape (n,). |
dualdataclass field | ArrayTRequired | Dual multipliers lambda >= 0 for A x <= b, shape (m,) (lambda = 1 / (t * s) on the central path). |
slackdataclass field | ArrayTRequired | Constraint slacks s = b - A x >= 0, shape (m,). |
objdataclass field | ArrayTRequired | Objective value 1/2 x^T Q x + c^T x at x. |
gapdataclass field | floatRequired | Surrogate duality gap m / t of the final central-path point. |
iterationsdataclass field | intRequired | Number of outer (barrier) iterations taken. |
convergeddataclass field | boolRequired | Whether gap <= tol was reached within the iteration budget. |
newton_iterationsdataclass field | intDefault: 0 | Total inner (centering) Newton steps across all outer iterations -- the main cost a warm start reduces. |
eq_dualdataclass field | ArrayT | NoneDefault: None | Dual multipliers nu for the optional equality constraints A_eq x = b_eq, shape (m_eq,) (the quadratic-penalty estimate nu = mu (A_eq x - b_eq)); None when no equalities were supplied. Unlike dual it is sign-free. |
Fields & methods
dualfield
dual: ArrayTDual multipliers lambda >= 0 for A x <= b, shape (m,) (lambda = 1 / (t * s) on the central path).
convergedfield
converged: boolWhether gap <= tol was reached within the iteration budget.
newton_iterationsfield
newton_iterations: int = 0Total inner (centering) Newton steps across all outer iterations -- the main cost a warm start reduces.
eq_dualfield
eq_dual: ArrayT | None = NoneDual multipliers nu for the optional equality constraints A_eq x = b_eq, shape (m_eq,) (the quadratic-penalty estimate nu = mu (A_eq x - b_eq)); None when no equalities were supplied. Unlike dual it is sign-free.
Read the implementation
class ConvexSolution(Generic[ArrayT]):
"""Result of an LP / QP solve.
Attributes
----------
x:
Primal optimum, shape ``(n,)``.
dual:
Dual multipliers ``lambda >= 0`` for ``A x <= b``, shape ``(m,)``
(``lambda = 1 / (t * s)`` on the central path).
slack:
Constraint slacks ``s = b - A x >= 0``, shape ``(m,)``.
obj:
Objective value ``1/2 x^T Q x + c^T x`` at ``x``.
gap:
Surrogate duality gap ``m / t`` of the final central-path point.
iterations:
Number of outer (barrier) iterations taken.
converged:
Whether ``gap <= tol`` was reached within the iteration budget.
newton_iterations:
Total inner (centering) Newton steps across all outer iterations -- the
main cost a warm start reduces.
eq_dual:
Dual multipliers ``nu`` for the optional equality constraints
``A_eq x = b_eq``, shape ``(m_eq,)`` (the quadratic-penalty estimate
``nu = mu (A_eq x - b_eq)``); ``None`` when no equalities were supplied.
Unlike ``dual`` it is sign-free.
"""
x: ArrayT
dual: ArrayT
slack: ArrayT
obj: ArrayT
gap: float
iterations: int
converged: bool
newton_iterations: int = 0
eq_dual: ArrayT | None = NoneOther public import paths
omnibias.convex.ConvexSolutionomnibias.convex.problem.ConvexSolution
PenaltyOptions
omnibias.convex.problem.PenaltyOptionsSource ↗class PenaltyOptionsDataclass constructor fields; inherited fields, if any, are declared by the base class. A field(...) expression describes a default factory or field options, not a literal argument default.
Tuning knobs for the tempered temperature-collapse (gradient-descent) solver.
The first-order solver minimises the smooth *exterior* penalty
F(x) = c^\top x + \tfrac12 x^\top Q x
+ \mu \sum_i \tfrac1\beta \operatorname{softplus}(\beta (a_i^\top x - b_i))by accelerated (Nesterov) gradient descent with a closed-form Lipschitz step
eta = step_safety / L, L = ||Q||_2 + (mu * beta / 4) ||A||_2^2 (the
penalty Hessian is mu beta A^T diag(sigma') A with sigma' <= 1/4), over
a homotopy that grows the sharpness beta and the penalty weight mu
geometrically. beta -> inf is the *temperature-collapse limit*: each softplus edge
collapses onto its exact constraint hyperplane a_i^T x = b_i (the same
beta -> inf homotopy as omnibias.binary's tanh surrogate).
(Terminology: this is the beta -> inf **feasibility** sense of
"collapse" -- a 0/1 step; *not* the **founding bias collapse**, the
multi-bias delta -> 0 limit sum_k s_k sigma(z + b_k) -> sigma^(K-1)
that yields a derivative. See docs/derivatives.md and
omnibias.torch.unit.)
Attributes
beta0, beta_growth: Initial sharpness and geometric growth factor per homotopy stage.
penalty0, penalty_growth: Initial penalty weight mu and its geometric growth factor.
stages: Number of homotopy stages (beta/mu increases).
gd_steps: Accelerated-gradient steps per stage (each stage warm-starts the next).
step_safety: Fraction of the Lipschitz step 1 / L to take (0 < step_safety <= 1).
prox: Strength of the proximal-point term (prox / 2) ||x - x_prev||^2 that anchors each stage to the previous iterate. It makes every subproblem coercive (so the exterior penalty cannot run off to infinity while mu is below the largest optimal multiplier) and vanishes at a fixed point, so the recovered optimum is unbiased. Must be > 0 for an LP (Q = 0).
tol: Path-settled tolerance ||x_k - x_{k-1}||_inf; the homotopy stops early once the path stops moving (and the point is feasible).
feas_tol: Maximum constraint violation max_i (a_i^T x - b_i)_+ accepted for the converged flag.
Parameters
| Name | Type & default | Contract |
|---|---|---|
beta0dataclass field | floatDefault: 1.0 | Initial sharpness and geometric growth factor per homotopy stage. |
beta_growthdataclass field | floatDefault: 3.0 | See the operation contract and implementation below. |
penalty0dataclass field | floatDefault: 1.0 | Initial penalty weight mu and its geometric growth factor. |
penalty_growthdataclass field | floatDefault: 1.6 | See the operation contract and implementation below. |
stagesdataclass field | intDefault: 11 | Number of homotopy stages (beta/mu increases). |
gd_stepsdataclass field | intDefault: 3000 | Accelerated-gradient steps per stage (each stage warm-starts the next). |
step_safetydataclass field | floatDefault: 0.9 | Fraction of the Lipschitz step 1 / L to take (0 < step_safety <= 1). |
proxdataclass field | floatDefault: 1.0 | Strength of the proximal-point term (prox / 2) ||x - x_prev||^2 that anchors each stage to the previous iterate. It makes every subproblem coercive (so the exterior penalty cannot run off to infinity while mu is below the largest optimal multiplier) and vanishes at a fixed point, so the recovered optimum is unbiased. Must be > 0 for an LP (Q = 0). |
toldataclass field | floatDefault: 0.0001 | Path-settled tolerance ||x_k - x_{k-1}||_inf; the homotopy stops early once the path stops moving (and the point is feasible). |
feas_toldataclass field | floatDefault: 0.0001 | Maximum constraint violation max_i (a_i^T x - b_i)_+ accepted for the converged flag. |
Executed regression · negative order options rejected
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_negative_order_options_rejected() -> None:
with pytest.raises(ValueError):
PenaltyOptions(prox=-1.0)
with pytest.raises(ValueError):
PenaltyOptions(step_safety=2.0)python -m pytest "packages/omnibias-convex/tests/test_penalty_jax.py::test_negative_order_options_rejected" -qRegression example · torch equality projection matches closed form
This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_torch_equality_projection_matches_closed_form() -> None:
Q, c, A, b, A_eq, b_eq = _hyperplane_qp()
sol = tpen.solve_qp_penalty(Q, c, A, b, A_eq=A_eq, b_eq=b_eq, options=_EQ_OPTS)
x_star = _project_hyperplane(-c)
assert sol.converged
assert sol.eq_dual is not None
np.testing.assert_allclose(sol.x.numpy(), x_star, atol=5e-3)python -m pytest packages/omnibias-convex/tests/test_penalty_equality.py -k test_torch_equality_projection_matches_closed_form -qFields & methods
beta0field
beta0: float = 1.0Initial sharpness and geometric growth factor per homotopy stage.
beta_growthfield
beta_growth: float = 3.0Initial sharpness and geometric growth factor per homotopy stage.
penalty0field
penalty0: float = 1.0Initial penalty weight mu and its geometric growth factor.
penalty_growthfield
penalty_growth: float = 1.6Initial penalty weight mu and its geometric growth factor.
gd_stepsfield
gd_steps: int = 3000Accelerated-gradient steps per stage (each stage warm-starts the next).
step_safetyfield
step_safety: float = 0.9Fraction of the Lipschitz step 1 / L to take (0 < step_safety <= 1).
proxfield
prox: float = 1.0Strength of the proximal-point term (prox / 2) ||x - x_prev||^2 that anchors each stage to the previous iterate. It makes every subproblem coercive (so the exterior penalty cannot run off to infinity while mu is below the largest optimal multiplier) and vanishes at a fixed point, so the recovered optimum is unbiased. Must be > 0 for an LP (Q = 0).
tolfield
tol: float = 0.0001Path-settled tolerance ||x_k - x_{k-1}||_inf; the homotopy stops early once the path stops moving (and the point is feasible).
feas_tolfield
feas_tol: float = 0.0001Maximum constraint violation max_i (a_i^T x - b_i)_+ accepted for the converged flag.
__post_init__method
__post_init__(self) -> NoneSee the class contract above and the exact method implementation below.
Returns: None
Validation & failure cases
- When
self.beta0 <= 0.0 or self.penalty0 <= 0.0, raisesValueError('beta0 and penalty0 must be > 0'). - When
self.beta_growth < 1.0 or self.penalty_growth < 1.0, raisesValueError('beta_growth and penalty_growth must be >= 1'). - When
self.stages < 1 or self.gd_steps < 1, raisesValueError('stages and gd_steps must be >= 1'). - When
not 0.0 < self.step_safety <= 1.0, raisesValueError('step_safety must be in (0, 1]'). - When
self.prox < 0.0, raisesValueError('prox must be >= 0').
Executed regression · negative order options rejected
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_negative_order_options_rejected() -> None:
with pytest.raises(ValueError):
PenaltyOptions(prox=-1.0)
with pytest.raises(ValueError):
PenaltyOptions(step_safety=2.0)python -m pytest "packages/omnibias-convex/tests/test_penalty_jax.py::test_negative_order_options_rejected" -qMethod implementation
def __post_init__(self) -> None:
if self.beta0 <= 0.0 or self.penalty0 <= 0.0:
raise ValueError("beta0 and penalty0 must be > 0")
if self.beta_growth < 1.0 or self.penalty_growth < 1.0:
raise ValueError("beta_growth and penalty_growth must be >= 1")
if self.stages < 1 or self.gd_steps < 1:
raise ValueError("stages and gd_steps must be >= 1")
if not 0.0 < self.step_safety <= 1.0:
raise ValueError("step_safety must be in (0, 1]")
if self.prox < 0.0:
raise ValueError("prox must be >= 0")Read the implementation
class PenaltyOptions:
r"""Tuning knobs for the tempered temperature-collapse (gradient-descent) solver.
The first-order solver minimises the smooth *exterior* penalty
.. math::
F(x) = c^\top x + \tfrac12 x^\top Q x
+ \mu \sum_i \tfrac1\beta \operatorname{softplus}(\beta (a_i^\top x - b_i))
by accelerated (Nesterov) gradient descent with a closed-form Lipschitz step
``eta = step_safety / L``, ``L = ||Q||_2 + (mu * beta / 4) ||A||_2^2`` (the
penalty Hessian is ``mu beta A^T diag(sigma') A`` with ``sigma' <= 1/4``), over
a homotopy that grows the sharpness ``beta`` and the penalty weight ``mu``
geometrically. ``beta -> inf`` is the *temperature-collapse limit*: each softplus edge
collapses onto its exact constraint hyperplane ``a_i^T x = b_i`` (the same
``beta -> inf`` homotopy as :mod:`omnibias.binary`'s tanh surrogate).
(Terminology: this is the ``beta -> inf`` **feasibility** sense of
"collapse" -- a 0/1 step; *not* the **founding bias collapse**, the
multi-bias ``delta -> 0`` limit ``sum_k s_k sigma(z + b_k) -> sigma^(K-1)``
that yields a derivative. See ``docs/derivatives.md`` and
:mod:`omnibias.torch.unit`.)
Attributes
----------
beta0, beta_growth:
Initial sharpness and geometric growth factor per homotopy stage.
penalty0, penalty_growth:
Initial penalty weight ``mu`` and its geometric growth factor.
stages:
Number of homotopy stages (``beta``/``mu`` increases).
gd_steps:
Accelerated-gradient steps per stage (each stage warm-starts the next).
step_safety:
Fraction of the Lipschitz step ``1 / L`` to take (``0 < step_safety <= 1``).
prox:
Strength of the proximal-point term ``(prox / 2) ||x - x_prev||^2`` that
anchors each stage to the previous iterate. It makes every subproblem
coercive (so the exterior penalty cannot run off to infinity while ``mu``
is below the largest optimal multiplier) and vanishes at a fixed point, so
the recovered optimum is unbiased. Must be ``> 0`` for an LP (``Q = 0``).
tol:
Path-settled tolerance ``||x_k - x_{k-1}||_inf``; the homotopy stops early
once the path stops moving (and the point is feasible).
feas_tol:
Maximum constraint violation ``max_i (a_i^T x - b_i)_+`` accepted for the
``converged`` flag.
"""
beta0: float = 1.0
beta_growth: float = 3.0
penalty0: float = 1.0
penalty_growth: float = 1.6
stages: int = 11
gd_steps: int = 3000
step_safety: float = 0.9
prox: float = 1.0
tol: float = 1e-4
feas_tol: float = 1e-4
def __post_init__(self) -> None:
if self.beta0 <= 0.0 or self.penalty0 <= 0.0:
raise ValueError("beta0 and penalty0 must be > 0")
if self.beta_growth < 1.0 or self.penalty_growth < 1.0:
raise ValueError("beta_growth and penalty_growth must be >= 1")
if self.stages < 1 or self.gd_steps < 1:
raise ValueError("stages and gd_steps must be >= 1")
if not 0.0 < self.step_safety <= 1.0:
raise ValueError("step_safety must be in (0, 1]")
if self.prox < 0.0:
raise ValueError("prox must be >= 0")validate_shapes
omnibias.convex.problem.validate_shapesSource ↗validate_shapes(n: int, m: int, *, A_shape: tuple[int, ...], b_shape: tuple[int, ...], c_shape: tuple[int, ...], Q_shape: tuple[int, ...] | None=None, A_eq_shape: tuple[int, ...] | None=None, b_eq_shape: tuple[int, ...] | None=None) -> NoneRaise ValueError if the LP/QP data shapes are inconsistent.
A_eq_shape / b_eq_shape are optional equality-constraint shapes; when
either is given both must be, with A_eq of shape (m_eq, n) and b_eq
of shape (m_eq,) (m_eq inferred from b_eq).
Parameters
| Name | Type & default | Contract |
|---|---|---|
npositional-or-keyword | intRequired | See the operation contract and implementation below. |
mpositional-or-keyword | intRequired | See the operation contract and implementation below. |
A_shapekeyword-only | tuple[int, ...]Required | See the operation contract and implementation below. |
b_shapekeyword-only | tuple[int, ...]Required | See the operation contract and implementation below. |
c_shapekeyword-only | tuple[int, ...]Required | See the operation contract and implementation below. |
Q_shapekeyword-only | tuple[int, ...] | NoneDefault: None | See the operation contract and implementation below. |
A_eq_shapekeyword-only | tuple[int, ...] | NoneDefault: None | See the operation contract and implementation below. |
b_eq_shapekeyword-only | tuple[int, ...] | NoneDefault: None | See the operation contract and implementation below. |
NoneExplicit validation & failure cases
- When
A_shape != (m, n), raisesValueError(f'A must have shape (m, n) = ({m}, {n}), got {A_shape}'). - When
b_shape != (m,), raisesValueError(f'b must have shape (m,) = ({m},), got {b_shape}'). - When
c_shape != (n,), raisesValueError(f'c must have shape (n,) = ({n},), got {c_shape}'). - When
Q_shape is not None and Q_shape != (n, n), raisesValueError(f'Q must have shape (n, n) = ({n}, {n}), got {Q_shape}'). - When
A_eq_shape is None or b_eq_shape is None, raisesValueError('A_eq and b_eq must be supplied together'). - When
len(b_eq_shape) != 1, raisesValueError(f'b_eq must have shape (m_eq,), got {b_eq_shape}'). - When
A_eq_shape != (m_eq, n), raisesValueError(f'A_eq must have shape (m_eq, n) = ({m_eq}, {n}), got {A_eq_shape}').
These are explicit source guards. Backend operations may also raise for invalid shapes, dtypes, or unsupported devices.
Executed regression · infeasible raises
Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.
def test_infeasible_raises() -> None:
c = np.array([1.0])
A = np.array([[1.0], [-1.0]])
b = np.array([-1.0, -1.0])
with pytest.raises(InfeasibleProblemError):
solve_lp(_t(c), _t(A), _t(b))python -m pytest "packages/omnibias-convex/tests/test_solver_torch.py::test_infeasible_raises" -qRead the implementation
def validate_shapes(
n: int,
m: int,
*,
A_shape: tuple[int, ...],
b_shape: tuple[int, ...],
c_shape: tuple[int, ...],
Q_shape: tuple[int, ...] | None = None,
A_eq_shape: tuple[int, ...] | None = None,
b_eq_shape: tuple[int, ...] | None = None,
) -> None:
"""Raise ``ValueError`` if the LP/QP data shapes are inconsistent.
``A_eq_shape`` / ``b_eq_shape`` are optional equality-constraint shapes; when
either is given both must be, with ``A_eq`` of shape ``(m_eq, n)`` and ``b_eq``
of shape ``(m_eq,)`` (``m_eq`` inferred from ``b_eq``).
"""
if A_shape != (m, n):
raise ValueError(f"A must have shape (m, n) = ({m}, {n}), got {A_shape}")
if b_shape != (m,):
raise ValueError(f"b must have shape (m,) = ({m},), got {b_shape}")
if c_shape != (n,):
raise ValueError(f"c must have shape (n,) = ({n},), got {c_shape}")
if Q_shape is not None and Q_shape != (n, n):
raise ValueError(f"Q must have shape (n, n) = ({n}, {n}), got {Q_shape}")
if A_eq_shape is not None or b_eq_shape is not None:
if A_eq_shape is None or b_eq_shape is None:
raise ValueError("A_eq and b_eq must be supplied together")
if len(b_eq_shape) != 1:
raise ValueError(f"b_eq must have shape (m_eq,), got {b_eq_shape}")
m_eq = b_eq_shape[0]
if A_eq_shape != (m_eq, n):
raise ValueError(
f"A_eq must have shape (m_eq, n) = ({m_eq}, {n}), got {A_eq_shape}"
)