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

1"""Operator protocol alias and the parametric-path helpers built on cvx-linalg. 

2 

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. 

9 

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""" 

16 

17from __future__ import annotations 

18 

19import numpy as np 

20from cvx.linalg import SymmetricOperator 

21from cvx.linalg import bordered_solve as _bordered_solve 

22from numpy.typing import NDArray 

23 

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 

28 

29 

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. 

32 

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. 

35 

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. 

40 

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 

46 

47 

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. 

63 

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]