Coverage for src/cvxcla/operators/_core.py: 100%
13 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-11 09:58 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-11 09:58 +0000
1"""Operator protocol alias and the parametric-path helpers built on cvx-linalg.
3The parametric active-set path tracer reaches its Hessian through the cvx-linalg
4symmetric-operator protocol (``matvec`` / ``block_matvec`` / ``solve_free`` /
5``rcond_free``). :data:`QuadraticForm` is that contract; :data:`CovarianceOperator`
6is a backward-compatible alias for the portfolio (covariance) setting. The
7concrete backends live in :mod:`cvx.linalg`; :mod:`cvxcla.operators.builders`
8assembles them from CLA / LASSO inputs.
10The generic linear algebra -- the bordered KKT (Schur complement) solve and the
11affine projection -- also lives in :mod:`cvx.linalg`. What remains here is the
12thin homotopy-specific glue: adapting the loop's boolean masks to the operators'
13integer-index API, and packing the constant / ``lambda``-slope pair of a
14parametric segment into the shared multi-RHS solve.
15"""
17from __future__ import annotations
19import numpy as np
20from cvx.linalg import SymmetricOperator
21from cvx.linalg import bordered_solve as _bordered_solve
22from numpy.typing import NDArray
24# The Hessian contract for a parametric active-set path. In the CLA it is the
25# covariance ``Sigma``; in a LASSO / LARS path it is the Gram matrix ``X.T @ X``.
26QuadraticForm = SymmetricOperator
27CovarianceOperator = SymmetricOperator
30def cross(operator: SymmetricOperator, free: NDArray[np.bool_], x: NDArray[np.float64]) -> NDArray[np.float64]:
31 """Free-to-blocked cross product ``H[free][:, ~free] @ x[~free]`` from a boolean mask.
33 Adapts the boolean-mask indexing the path tracers use to the integer-index
34 :meth:`~cvx.linalg.SymmetricOperator.block_matvec` of a symmetric operator.
36 Args:
37 operator: The symmetric operator (Hessian) backend.
38 free: Boolean mask of shape ``(n,)`` selecting the free coordinates.
39 x: Full-length vector of shape ``(n,)``; only ``x[~free]`` enters the product.
41 Returns:
42 Vector of shape ``(n_free,)``.
43 """
44 result: NDArray[np.float64] = operator.block_matvec(np.flatnonzero(free), np.flatnonzero(~free), x[~free])
45 return result
48def bordered_solve(
49 quad: SymmetricOperator,
50 free: NDArray[np.bool_],
51 c_free: NDArray[np.float64],
52 rhs_const: NDArray[np.float64],
53 rhs_slope: NDArray[np.float64],
54 d_const: NDArray[np.float64],
55 d_slope: NDArray[np.float64],
56) -> tuple[
57 NDArray[np.float64],
58 NDArray[np.float64],
59 NDArray[np.float64],
60 NDArray[np.float64],
61]:
62 """Solve the bordered KKT system for a parametric segment's constant and slope parts.
64 A thin adapter over :func:`cvx.linalg.bordered_solve`: it converts the loop's
65 boolean *free* mask to integer indices and packs the constant and ``lambda``-slope
66 right-hand sides as the two columns of one multi-RHS solve, so ``H_FF`` and the
67 Schur complement are factorised once. Returns
68 ``(x_const, x_slope, nu_const, nu_slope)`` (multipliers empty when there are no
69 constraint rows).
70 """
71 x, nu = _bordered_solve(
72 quad,
73 np.flatnonzero(free),
74 c_free,
75 np.column_stack([rhs_const, rhs_slope]),
76 np.column_stack([d_const, d_slope]),
77 )
78 return x[:, 0], x[:, 1], nu[:, 0], nu[:, 1]