Skip to main content

omnibias.convex.problem

0.1.0a2AlphaPython >=3.10AGPL / commercial

Differentiable LP and QP solver paths, barrier methods, KKT sensitivities, and scoped optimality evidence.

import omnibias.convex.problem
Module 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.

CLASS

BarrierOptions​

omnibias.convex.BarrierOptionsSource ↗
class BarrierOptions

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.

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

NameType & defaultContract
toldataclass fieldfloatDefault: 1e-09See the operation contract and implementation below.
mudataclass fieldfloatDefault: 10.0See the operation contract and implementation below.
t0dataclass fieldfloatDefault: 1.0See the operation contract and implementation below.
max_outerdataclass fieldintDefault: 60See the operation contract and implementation below.
newton_itersdataclass fieldintDefault: 50See the operation contract and implementation below.
newton_toldataclass fieldfloatDefault: 1e-12See the operation contract and implementation below.
backtrack_alphadataclass fieldfloatDefault: 0.25See the operation contract and implementation below.
backtrack_betadataclass fieldfloatDefault: 0.5See the operation contract and implementation below.
dampingdataclass fieldfloatDefault: 1e-12See 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.gap
python -m pytest packages/omnibias-convex/tests/test_certify.py -k test_certificate_gap_shrinks_with_tighter_solve -q

Fields & methods

tolfield
tol: float = 1e-09

Declared float field on BarrierOptions. Default expression: 1e-09.

View source ↗
mufield
mu: float = 10.0

Declared float field on BarrierOptions. Default expression: 10.0.

View source ↗
t0field
t0: float = 1.0

Declared float field on BarrierOptions. Default expression: 1.0.

View source ↗
max_outerfield
max_outer: int = 60

Declared int field on BarrierOptions. Default expression: 60.

View source ↗
newton_itersfield
newton_iters: int = 50

Declared int field on BarrierOptions. Default expression: 50.

View source ↗
newton_tolfield
newton_tol: float = 1e-12

Declared float field on BarrierOptions. Default expression: 1e-12.

View source ↗
backtrack_alphafield
backtrack_alpha: float = 0.25

Declared float field on BarrierOptions. Default expression: 0.25.

View source ↗
backtrack_betafield
backtrack_beta: float = 0.5

Declared float field on BarrierOptions. Default expression: 0.5.

View source ↗
dampingfield
damping: float = 1e-12

Declared float field on BarrierOptions. Default expression: 1e-12.

View source ↗
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-12
Other public import paths
  • omnibias.convex.BarrierOptions
  • omnibias.convex.problem.BarrierOptions
CLASS

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

NameType & defaultContract
xdataclass fieldArrayTRequiredPrimal optimum, shape (n,).
dualdataclass fieldArrayTRequiredDual multipliers lambda >= 0 for A x <= b, shape (m,) (lambda = 1 / (t * s) on the central path).
slackdataclass fieldArrayTRequiredConstraint slacks s = b - A x >= 0, shape (m,).
objdataclass fieldArrayTRequiredObjective value 1/2 x^T Q x + c^T x at x.
gapdataclass fieldfloatRequiredSurrogate duality gap m / t of the final central-path point.
iterationsdataclass fieldintRequiredNumber of outer (barrier) iterations taken.
convergeddataclass fieldboolRequiredWhether gap <= tol was reached within the iteration budget.
newton_iterationsdataclass fieldintDefault: 0Total inner (centering) Newton steps across all outer iterations -- the main cost a warm start reduces.
eq_dualdataclass fieldArrayT | NoneDefault: 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.

Fields & methods

xfield
x: ArrayT

Primal optimum, shape (n,).

View source ↗
dualfield
dual: ArrayT

Dual multipliers lambda >= 0 for A x <= b, shape (m,) (lambda = 1 / (t * s) on the central path).

View source ↗
slackfield
slack: ArrayT

Constraint slacks s = b - A x >= 0, shape (m,).

View source ↗
objfield
obj: ArrayT

Objective value 1/2 x^T Q x + c^T x at x.

View source ↗
gapfield
gap: float

Surrogate duality gap m / t of the final central-path point.

View source ↗
iterationsfield
iterations: int

Number of outer (barrier) iterations taken.

View source ↗
convergedfield
converged: bool

Whether gap <= tol was reached within the iteration budget.

View source ↗
newton_iterationsfield
newton_iterations: int = 0

Total inner (centering) Newton steps across all outer iterations -- the main cost a warm start reduces.

View source ↗
eq_dualfield
eq_dual: ArrayT | None = 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.

View source ↗
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 = None
Other public import paths
  • omnibias.convex.ConvexSolution
  • omnibias.convex.problem.ConvexSolution
CLASS

PenaltyOptions​

omnibias.convex.problem.PenaltyOptionsSource ↗
class PenaltyOptions

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.

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

NameType & defaultContract
beta0dataclass fieldfloatDefault: 1.0Initial sharpness and geometric growth factor per homotopy stage.
beta_growthdataclass fieldfloatDefault: 3.0See the operation contract and implementation below.
penalty0dataclass fieldfloatDefault: 1.0Initial penalty weight mu and its geometric growth factor.
penalty_growthdataclass fieldfloatDefault: 1.6See the operation contract and implementation below.
stagesdataclass fieldintDefault: 11Number of homotopy stages (beta/mu increases).
gd_stepsdataclass fieldintDefault: 3000Accelerated-gradient steps per stage (each stage warm-starts the next).
step_safetydataclass fieldfloatDefault: 0.9Fraction of the Lipschitz step 1 / L to take (0 < step_safety <= 1).
proxdataclass fieldfloatDefault: 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).
toldataclass fieldfloatDefault: 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_toldataclass fieldfloatDefault: 0.0001Maximum 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" -q
Regression 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 -q

Fields & methods

beta0field
beta0: float = 1.0

Initial sharpness and geometric growth factor per homotopy stage.

View source ↗
beta_growthfield
beta_growth: float = 3.0

Initial sharpness and geometric growth factor per homotopy stage.

View source ↗
penalty0field
penalty0: float = 1.0

Initial penalty weight mu and its geometric growth factor.

View source ↗
penalty_growthfield
penalty_growth: float = 1.6

Initial penalty weight mu and its geometric growth factor.

View source ↗
stagesfield
stages: int = 11

Number of homotopy stages (beta/mu increases).

View source ↗
gd_stepsfield
gd_steps: int = 3000

Accelerated-gradient steps per stage (each stage warm-starts the next).

View source ↗
step_safetyfield
step_safety: float = 0.9

Fraction of the Lipschitz step 1 / L to take (0 < step_safety <= 1).

View source ↗
proxfield
prox: float = 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).

View source ↗
tolfield
tol: float = 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).

View source ↗
feas_tolfield
feas_tol: float = 0.0001

Maximum constraint violation max_i (a_i^T x - b_i)_+ accepted for the converged flag.

View source ↗
__post_init__method
__post_init__(self) -> None

See 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, raises ValueError('beta0 and penalty0 must be > 0').
  • When self.beta_growth < 1.0 or self.penalty_growth < 1.0, raises ValueError('beta_growth and penalty_growth must be >= 1').
  • When self.stages < 1 or self.gd_steps < 1, raises ValueError('stages and gd_steps must be >= 1').
  • When not 0.0 < self.step_safety <= 1.0, raises ValueError('step_safety must be in (0, 1]').
  • When self.prox < 0.0, raises ValueError('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" -q
Method 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")
View source ↗
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")
FUNCTION

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) -> 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).

Parameters

NameType & defaultContract
npositional-or-keywordintRequiredSee the operation contract and implementation below.
mpositional-or-keywordintRequiredSee the operation contract and implementation below.
A_shapekeyword-onlytuple[int, ...]RequiredSee the operation contract and implementation below.
b_shapekeyword-onlytuple[int, ...]RequiredSee the operation contract and implementation below.
c_shapekeyword-onlytuple[int, ...]RequiredSee the operation contract and implementation below.
Q_shapekeyword-onlytuple[int, ...] | NoneDefault: NoneSee the operation contract and implementation below.
A_eq_shapekeyword-onlytuple[int, ...] | NoneDefault: NoneSee the operation contract and implementation below.
b_eq_shapekeyword-onlytuple[int, ...] | NoneDefault: NoneSee the operation contract and implementation below.
ReturnsNone
Explicit validation & failure cases
  • When A_shape != (m, n), raises ValueError(f'A must have shape (m, n) = ({m}, {n}), got {A_shape}').
  • When b_shape != (m,), raises ValueError(f'b must have shape (m,) = ({m},), got {b_shape}').
  • When c_shape != (n,), raises ValueError(f'c must have shape (n,) = ({n},), got {c_shape}').
  • When Q_shape is not None and Q_shape != (n, n), raises ValueError(f'Q must have shape (n, n) = ({n}, {n}), got {Q_shape}').
  • When A_eq_shape is None or b_eq_shape is None, raises ValueError('A_eq and b_eq must be supplied together').
  • When len(b_eq_shape) != 1, raises ValueError(f'b_eq must have shape (m_eq,), got {b_eq_shape}').
  • When A_eq_shape != (m_eq, n), raises ValueError(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" -q
Read 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}"
            )