Coverage for src/cvx/linalg/decomposition/cholesky.py: 100%

23 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-03 18:56 +0000

1"""Cholesky decomposition utilities for covariance matrices.""" 

2 

3from __future__ import annotations 

4 

5import warnings 

6from typing import cast 

7 

8import numpy as np 

9from numpy.linalg import cholesky as _cholesky 

10 

11from ..core.types import Matrix, Vector 

12 

13 

14def cholesky(cov: Matrix, rhs: Vector | Matrix | None = None) -> Vector | Matrix: 

15 """Compute the upper triangular Cholesky factor of a covariance matrix. 

16 

17 Returns the upper triangular factor R such that R.T @ R = cov. 

18 

19 Args: 

20 cov: A positive definite covariance matrix of shape (n, n). 

21 rhs: Deprecated. When provided the system ``cov @ x = rhs`` is solved 

22 and *x* is returned; use :func:`cholesky_solve` instead. This 

23 parameter will be removed in 1.0. 

24 

25 Returns: 

26 The upper triangular Cholesky factor R when *rhs* is ``None``, or the 

27 solution x to ``cov @ x = rhs`` otherwise (deprecated). 

28 

29 Raises: 

30 np.linalg.LinAlgError: When *rhs* is ``None`` and *cov* is not 

31 positive-definite, or when *rhs* is given and both Cholesky and 

32 LU-based solves fail. 

33 

34 Warns: 

35 DeprecationWarning: When *rhs* is given. 

36 

37 Example: 

38 >>> import numpy as np 

39 >>> from cvx.linalg import cholesky 

40 >>> cov = np.array([[4.0, 2.0], [2.0, 5.0]]) 

41 >>> R = cholesky(cov) 

42 >>> np.allclose(R.T @ R, cov) 

43 True 

44 """ 

45 if rhs is None: 

46 return cast("Matrix", _cholesky(cov).transpose()) 

47 warnings.warn( 

48 "Passing 'rhs' to cholesky() is deprecated and will be removed in 1.0; use cholesky_solve(cov, rhs) instead.", 

49 DeprecationWarning, 

50 stacklevel=2, 

51 ) 

52 return cholesky_solve(cov, rhs) 

53 

54 

55def cholesky_solve(cov: Matrix, rhs: Vector | Matrix) -> Vector | Matrix: 

56 """Solve ``cov @ x = rhs`` using the Cholesky decomposition. 

57 

58 The Cholesky factorisation is attempted first for numerical stability; 

59 when *cov* is not positive-definite the solve falls back to LU 

60 decomposition. 

61 

62 Args: 

63 cov: A positive definite covariance matrix of shape (n, n). 

64 rhs: Right-hand side vector of length n or matrix of shape (n, k). 

65 

66 Returns: 

67 The solution x to ``cov @ x = rhs`` with the same shape as *rhs*. 

68 

69 Raises: 

70 np.linalg.LinAlgError: When both the Cholesky and LU-based solves fail. 

71 

72 Example: 

73 >>> import numpy as np 

74 >>> from cvx.linalg import cholesky_solve 

75 >>> cholesky_solve(np.eye(2), np.array([1.0, 2.0])).tolist() 

76 [1.0, 2.0] 

77 >>> cholesky_solve(np.array([[4.0, 0.0], [0.0, 9.0]]), np.array([8.0, 27.0])).tolist() 

78 [2.0, 3.0] 

79 """ 

80 try: 

81 upper = _cholesky(cov).transpose() 

82 return cast("Vector | Matrix", np.linalg.solve(upper, np.linalg.solve(upper.T, rhs))) 

83 except np.linalg.LinAlgError: 

84 return cast("Vector | Matrix", np.linalg.solve(cov, rhs)) 

85 

86 

87def is_positive_definite(matrix: Matrix) -> bool: 

88 """Return True if *matrix* is symmetric positive-definite, False otherwise. 

89 

90 The check is performed via an attempted Cholesky decomposition — the most 

91 numerically reliable way to test positive-definiteness for symmetric matrices. 

92 

93 This function is side-effect-free: it raises no exceptions and emits no 

94 warnings. It is suitable for use as a guard before passing a matrix to a 

95 linear solver. 

96 

97 Args: 

98 matrix: Square matrix to test. 

99 

100 Returns: 

101 ``True`` if the matrix is positive-definite, ``False`` otherwise. 

102 

103 Example: 

104 >>> import numpy as np 

105 >>> from cvx.linalg import is_positive_definite 

106 >>> is_positive_definite(np.eye(3)) 

107 True 

108 >>> is_positive_definite(np.array([[1.0, 2.0], [2.0, 1.0]])) 

109 False 

110 >>> is_positive_definite(np.array([[1.0, 0.5], [0.5, 1.0]])) 

111 True 

112 """ 

113 try: 

114 cholesky(matrix) 

115 except np.linalg.LinAlgError: 

116 return False 

117 else: 

118 return True