Skip to content

Commit e831b6a

Browse files
author
Hông-Lan Botterman
committed
ENH: exceptions to warnings in RPCA
1 parent 3c667cd commit e831b6a

File tree

6 files changed

+79
-32
lines changed

6 files changed

+79
-32
lines changed

examples/benchmark.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,13 @@ results
234234
df_plot = results.loc["KL_columnwise",'TEMP']
235235
plt.barh(df_plot.index, df_plot, color=tab10(0))
236236
plt.title('TEMP')
237+
plt.xlabel("KL")
237238
plt.show()
238239

239240
df_plot = results.loc["KL_columnwise",'PRES']
240241
plt.barh(df_plot.index, df_plot, color=tab10(0))
241242
plt.title('PRES')
243+
plt.xlabel("KL")
242244
plt.show()
243245
```
244246

qolmat/imputations/rpca/rpca_noisy.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import warnings
34
from typing import Dict, List, Optional, Tuple, Union
45

56
import numpy as np
@@ -9,7 +10,6 @@
910

1011
from qolmat.imputations.rpca import rpca_utils
1112
from qolmat.imputations.rpca.rpca import RPCA
12-
from qolmat.utils.exceptions import CostFunctionRPCANotMinimized
1313

1414

1515
class RPCANoisy(RPCA):
@@ -457,4 +457,7 @@ def _check_cost_function_minimized(
457457
function_str += f"{eta} ||XH||_{self.norm}"
458458

459459
if (round(cost_start, 4) - round(cost_end, 4)) <= -1e-2:
460-
raise CostFunctionRPCANotMinimized(function_str, float(cost_start), float(cost_end))
460+
warnings.warn(
461+
f"RPCA algorithm may provide bad results. Function {function_str} increased from"
462+
f" {cost_start} to {cost_end} instead of decreasing!"
463+
)

qolmat/imputations/rpca/rpca_pcp.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import warnings
34
from typing import Optional, Tuple, Union
45

56
import numpy as np
@@ -8,7 +9,6 @@
89

910
from qolmat.imputations.rpca import rpca_utils
1011
from qolmat.imputations.rpca.rpca import RPCA
11-
from qolmat.utils.exceptions import CostFunctionRPCANotMinimized
1212

1313

1414
class RPCAPCP(RPCA):
@@ -114,4 +114,7 @@ def _check_cost_function_minimized(
114114
cost_end = np.linalg.norm(low_rank, "nuc") + lam * np.sum(np.abs(anomalies))
115115
if round(cost_start, 4) - round(cost_end, 4) <= -1e-2:
116116
function_str = "||D||_* + lam ||A||_1"
117-
raise CostFunctionRPCANotMinimized(function_str, float(cost_start), float(cost_end))
117+
warnings.warn(
118+
f"RPCA algorithm may provide bad results. Function {function_str} increased from"
119+
f" {cost_start} to {cost_end} instead of decreasing!"
120+
)

qolmat/utils/exceptions.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from typing import Any, List, Tuple, Type
2-
from numpy.typing import NDArray
32

43

54
class PyTorchExtraNotInstalled(Exception):
@@ -31,14 +30,6 @@ def __init__(self, subset: Any):
3130
super().__init__(f"Provided subset `{subset}` should be None or a list!")
3231

3332

34-
class CostFunctionRPCANotMinimized(Exception):
35-
def __init__(self, name_fct: str, value_start: float, value_end: float):
36-
super().__init__(
37-
f"RPCA algorithm may provide bad results. Function {name_fct} increased from"
38-
f" {value_start} to {value_end} instead of decreasing!"
39-
)
40-
41-
4233
class NotDimension2(Exception):
4334
def __init__(self, shape: Tuple[int, ...]):
4435
super().__init__(f"Provided matrix is of shape {shape}, which is not of dimension 2!")

tests/imputations/rpca/test_rpca_noisy.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
13
import numpy as np
24
import pytest
35
from numpy.typing import NDArray
@@ -6,7 +8,6 @@
68
from qolmat.imputations.rpca.rpca_noisy import RPCANoisy
79
from qolmat.utils import utils
810
from qolmat.utils.data import generate_artificial_ts
9-
from qolmat.utils.exceptions import CostFunctionRPCANotMinimized
1011

1112
X_complete = np.array([[1, 2], [3, 1]], dtype=float)
1213
X_incomplete = np.array([[1, 2], [3, np.nan]], dtype=float)
@@ -45,13 +46,35 @@ def synthetic_temporal_data():
4546
)
4647
],
4748
)
48-
def test_check_cost_function_minimized_raise_expection(
49+
def test_check_cost_function_minimized_warning(
50+
obs: NDArray, lr: NDArray, ano: NDArray, omega: NDArray, lam: float, tau: float, norm: str
51+
):
52+
"""Test warning when the cost function is not minimized."""
53+
with pytest.warns(UserWarning):
54+
RPCANoisy()._check_cost_function_minimized(obs, lr, ano, omega, lam, tau)
55+
56+
57+
@pytest.mark.parametrize(
58+
"obs, lr, ano, omega, lam, tau, norm",
59+
[
60+
(
61+
np.array([[1, 1], [1, 1]], dtype=float),
62+
np.array([[0, 0], [0, 0]], dtype=float),
63+
np.array([[0, 0], [0, 0]], dtype=float),
64+
True * np.ones((2, 2)),
65+
5,
66+
0,
67+
"L2",
68+
)
69+
],
70+
)
71+
def test_check_cost_function_minimized_no_warning(
4972
obs: NDArray, lr: NDArray, ano: NDArray, omega: NDArray, lam: float, tau: float, norm: str
5073
):
51-
"""Test if exception is raised when the cost function is not minimized."""
52-
rpca = RPCANoisy()
53-
with pytest.raises(CostFunctionRPCANotMinimized):
54-
rpca._check_cost_function_minimized(obs, lr, ano, omega, lam, tau)
74+
"""Test no warning when the cost function is minimized."""
75+
with warnings.catch_warnings(record=True) as record:
76+
RPCANoisy()._check_cost_function_minimized(obs, lr, ano, omega, lam, tau)
77+
assert len(record) == 0
5578

5679

5780
@pytest.mark.parametrize("X", [X_complete])

tests/imputations/rpca/test_rpca_pcp.py

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import warnings
2+
13
import numpy as np
24
import pytest
35
from numpy.typing import NDArray
46

57
from qolmat.imputations.rpca.rpca_pcp import RPCAPCP
68
from qolmat.utils import utils
79
from qolmat.utils.data import generate_artificial_ts
8-
from qolmat.utils.exceptions import CostFunctionRPCANotMinimized
910

1011
X_complete = np.array([[1, 2], [3, 1]], dtype=float)
1112
X_incomplete = np.array([[1, 2], [3, np.nan], [np.nan, 4]], dtype=float)
@@ -41,41 +42,62 @@ def synthetic_temporal_data():
4142
)
4243
],
4344
)
44-
def test_check_cost_function_minimized_raise_expection(
45+
def test_check_cost_function_minimized_warning(
46+
obs: NDArray, lr: NDArray, ano: NDArray, lam: float
47+
):
48+
"""Test warning when the cost function is minimized."""
49+
with pytest.warns(UserWarning):
50+
RPCAPCP()._check_cost_function_minimized(obs, lr, ano, lam)
51+
52+
53+
@pytest.mark.parametrize(
54+
"obs, lr, ano, lam",
55+
[
56+
(
57+
np.array([[1, 1], [1, 1]], dtype=float),
58+
np.array([[0, 0], [0, 0]], dtype=float),
59+
np.array([[2, 2], [2, 2]], dtype=float),
60+
0,
61+
)
62+
],
63+
)
64+
def test_check_cost_function_minimized_no_warning(
4565
obs: NDArray, lr: NDArray, ano: NDArray, lam: float
4666
):
47-
function_str = "||D||_* + lam ||A||_1"
48-
rpca = RPCAPCP()
49-
with pytest.raises(
50-
CostFunctionRPCANotMinimized,
51-
match="PCA algorithm may provide bad results. "
52-
f"{function_str} is larger at the end "
53-
"of the algorithm than at the start.",
54-
):
55-
rpca._check_cost_function_minimized(obs, lr, ano, lam)
67+
"""Test no warning when the cost function is minimized."""
68+
with warnings.catch_warnings(record=True) as record:
69+
RPCAPCP()._check_cost_function_minimized(obs, lr, ano, lam)
70+
assert len(record) == 0
5671

5772

5873
@pytest.mark.parametrize("X", [X_complete])
5974
def test_rpca_rpca_pcp_get_params_scale(X: NDArray):
75+
"""Test the parameters are well scaled."""
6076
rpca_pcp = RPCAPCP(max_iterations=max_iterations, mu=0.5, lam=0.1)
6177
result_dict = rpca_pcp.get_params_scale(X)
6278
result = list(result_dict.values())
6379
params_expected = [1 / 7, np.sqrt(2) / 2]
6480
np.testing.assert_allclose(result, params_expected, atol=1e-4)
6581

6682

67-
# The problem is ill-conditioned and the result depends on the parameter mu
6883
@pytest.mark.parametrize("X, mu", [(X_complete, small_mu)])
6984
def test_rpca_rpca_pcp_zero_lambda_small_mu(X: NDArray, mu: float):
85+
"""Test RPCA PCP results if lambda equals zero.
86+
The problem is ill-conditioned and the result depends
87+
on the parameter mu; case when mu is small.
88+
"""
7089
rpca_pcp = RPCAPCP(lam=0, mu=mu)
7190
X_result, A_result = rpca_pcp.decompose_rpca_signal(X)
7291
np.testing.assert_allclose(X_result, np.full_like(X, 0), atol=1e-4)
7392
np.testing.assert_allclose(A_result, X, atol=1e-4)
7493

7594

76-
# The problem is ill-conditioned and the result depends on the parameter mu
7795
@pytest.mark.parametrize("X, mu", [(X_complete, large_mu)])
7896
def test_rpca_rpca_pcp_zero_lambda_large_mu(X: NDArray, mu: float):
97+
"""Test RPCA PCP results if lambda equals zero.
98+
The problem is ill-conditioned and the result depends
99+
on the parameter mu; case when mu is large.
100+
"""
79101
rpca_pcp = RPCAPCP(lam=0, mu=mu)
80102
X_result, A_result = rpca_pcp.decompose_rpca_signal(X)
81103
np.testing.assert_allclose(X_result, X, atol=1e-4)
@@ -84,13 +106,16 @@ def test_rpca_rpca_pcp_zero_lambda_large_mu(X: NDArray, mu: float):
84106

85107
@pytest.mark.parametrize("X, mu", [(X_complete, large_mu)])
86108
def test_rpca_rpca_pcp_large_lambda_small_mu(X: NDArray, mu: float):
109+
"""Test RPCA PCP results with large lambda and small mu."""
87110
rpca_pcp = RPCAPCP(lam=1e3, mu=mu)
88111
X_result, A_result = rpca_pcp.decompose_rpca_signal(X)
89112
np.testing.assert_allclose(X_result, X, atol=1e-4)
90113
np.testing.assert_allclose(A_result, np.full_like(X, 0), atol=1e-4)
91114

92115

93116
def test_rpca_temporal_signal(synthetic_temporal_data):
117+
"""Test RPCA PCP results for time series data.
118+
Check if the cost function is smaller at the end than at the start."""
94119
signal = synthetic_temporal_data
95120
period = 100
96121
lam = 0.1

0 commit comments

Comments
 (0)