Solvers

A Solver is anything callable with the signature (Problem, Hyperparam, *, x0=None, tol=None) -> SolverResult. It is the boundary between sparho’s bilevel machinery and a concrete inner-problem fitter (sklearn, celer, or a user callable).

class sparho.Solver[source]

Bases: Protocol

Anything callable with the signature (Problem, Hyperparam, *, x0=None) -> SolverResult.

The Solver is the boundary between sparho’s bilevel machinery and the underlying inner-problem solver (sklearn, celer, or a user callable). It has no awareness of the outer loop — it just returns a converged inner solution at the given hyperparameter, alongside the active set and dual gap needed by the hypergradient.

Warm-start: x0 is an optional initial guess for the coefficients (e.g. β* from the previous outer iteration at a nearby hyperparameter). Solvers may use it to seed the inner iteration and converge faster; those that can’t are free to ignore it. The criterion / outer loop owns the decision of when to warm-start; the solver just honors x0 if given.

Tolerance override: tol is an optional inner-solver tolerance that supersedes whatever default the adapter holds (e.g. SklearnLasso.tol). HOAG-style outer loops (sparho.search.hoag_search()) use this to shrink the inner tolerance across iterations — loose early when only the gradient direction matters, tight late when the criterion value resolution drives convergence. Adapters that have no meaningful tolerance setting may ignore it.

__call__(problem, hyperparam, /, *, x0=None, tol=None)[source]

Call self as a function.

Parameters:
Return type:

SolverResult

__init__(*args, **kwargs)

sklearn adapters

class sparho.adapters.SklearnLasso[source]

Bases: object

Adapter for Problem(SquaredLoss, L1, X, y) via sklearn.linear_model.Lasso.

When the criterion uses warm-start, prefer tol 1e-8: sklearn’s convergence check is dual_gap < tol · ||y||², so on small-||y|| problems a loose tol lets the warm coef pass the check immediately and the inner solver returns it unchanged — which makes nearby CV evaluations indistinguishable and stalls outer line searches.

tol: float
max_iter: int
__init__(tol=1e-06, max_iter=10000)
Parameters:
Return type:

None

class sparho.adapters.SklearnElasticNet[source]

Bases: object

Adapter for Problem(SquaredLoss, ElasticNet(rho), X, y) via sklearn.

tol: float
max_iter: int
__init__(tol=1e-06, max_iter=10000)
Parameters:
Return type:

None

class sparho.adapters.SklearnWeightedLasso[source]

Bases: object

Adapter for Problem(SquaredLoss, WeightedL1, X, y) via column-rescaling.

sklearn’s Lasso only supports a scalar α. We solve the equivalent problem with rescaled design X' = X · diag(1/α_vec) and unit α=1; the recovered β' is rescaled back as β = β' / α_vec. Coefficients where α_j = 0 (no regularization) are not supported in this adapter — sparse-ho’s behavior was the same.

tol: float
max_iter: int
__init__(tol=1e-06, max_iter=10000)
Parameters:
Return type:

None

class sparho.adapters.SklearnLogisticRegression[source]

Bases: object

Adapter for Problem(LogisticLoss, L1, X, y) via LogisticRegression(penalty='l1').

Assumes binary y {−1, +1}. sklearn relabels internally. The dual gap is not exposed by sklearn for logistic regression; we report a stationarity proxy ||X_A^T (σ(Xβ) y₀₁) + α sign(β_A)||_∞ instead, which is zero at a KKT-optimal point.

x0 is accepted for protocol conformance but ignored — sklearn’s LogisticRegression(solver='liblinear') does not support warm-start.

tol: float
max_iter: int
__init__(tol=1e-06, max_iter=10000)
Parameters:
Return type:

None

celer adapters

celer adapters are available behind the [celer] extra. The celer package is imported lazily so the module loads even when the extra is not installed.

class sparho.adapters.celer.CelerLasso[source]

Bases: object

Adapter for Problem(SquaredLoss, L1, X, y) via celer.Lasso.

tol: float
max_iter: int
__init__(tol=1e-06, max_iter=100)
Parameters:
Return type:

None

class sparho.adapters.celer.CelerElasticNet[source]

Bases: object

Adapter for Problem(SquaredLoss, ElasticNet(rho), X, y) via celer.

tol: float
max_iter: int
__init__(tol=1e-06, max_iter=100)
Parameters:
Return type:

None

Wrapping an arbitrary callable

sparho.adapters.as_solver(fn, *, name='<callable>')[source]

Wrap fn as a Solver. The wrapper is frozen so it can be hashed and reprd.

Optional Solver-protocol kwargs (x0 for warm-start, tol for an inner-tolerance override) are forwarded only when fn declares them in its signature. A plain (problem, hp) -> SolverResult callable keeps working — the wrapper silently drops kwargs the function doesn’t accept.

Parameters:
Return type:

_CallableSolver