Skip to content

Commit ca0456d

Browse files
committed
TST: add new tests for TPGR, landscapes, UQ and regularized reconstruction
1 parent 31c9e23 commit ca0456d

File tree

4 files changed

+360
-26
lines changed

4 files changed

+360
-26
lines changed

pysensors/reconstruction/_sspor.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,9 @@ def one_pt_energy_landscape(self, prior="decreasing", noise=None):
695695
if isinstance(self.optimizer, TPGR):
696696
check_is_fitted(self, "optimizer")
697697
else:
698-
"Energy landscapes can only be computed if TPGR optimizer is used."
698+
raise TypeError(
699+
"Energy landscapes can only be computed if TPGR optimizer is used."
700+
)
699701
if isinstance(prior, str) and prior == "decreasing":
700702
computed_prior = self.singular_values
701703
elif isinstance(prior, np.ndarray):
@@ -756,7 +758,9 @@ def two_pt_energy_landscape(self, selected_sensors, prior="decreasing", noise=No
756758
if isinstance(self.optimizer, TPGR):
757759
check_is_fitted(self, "optimizer")
758760
else:
759-
"Energy landscapes can only be computed if TPGR optimizer is used."
761+
raise TypeError(
762+
"Energy landscapes can only be computed if TPGR optimizer is used."
763+
)
760764
check_is_fitted(self, "optimizer")
761765
if isinstance(prior, str) and prior == "decreasing":
762766
computed_prior = self.singular_values

tests/optimizers/test_optimizers.py

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy as np
44
import pytest
55

6-
from pysensors.optimizers import CCQR, GQR, QR, qr_reflector
6+
from pysensors.optimizers import CCQR, GQR, QR, TPGR, qr_reflector
77

88

99
def test_num_sensors(data_vandermonde):
@@ -221,3 +221,124 @@ def test_ccqr_fit_raises_error_for_mismatched_dimensions():
221221
ccqr.fit(basis_matrix)
222222
assert hasattr(ccqr, "pivots_")
223223
assert ccqr.pivots_.shape == (4,)
224+
225+
226+
def test_tpgr_init_parameters():
227+
tpgr_default = TPGR(n_sensors=3)
228+
229+
flat_prior = np.full(3, 1)
230+
tpgr_custom = TPGR(n_sensors=5, prior=flat_prior, noise=0.1)
231+
232+
assert tpgr_default.n_sensors == 3
233+
assert tpgr_default.prior == "decreasing"
234+
assert tpgr_default.noise is None
235+
assert tpgr_default.sensors_ is None
236+
assert tpgr_custom.n_sensors == 5
237+
assert tpgr_custom.noise == 0.1
238+
np.testing.assert_array_equal(tpgr_custom.prior, flat_prior)
239+
240+
241+
def test_tpgr_fit_decreasing_prior(data_random):
242+
x = data_random
243+
n_sensors = 5
244+
singular_values = np.linspace(1.0, 0.1, x.shape[1])
245+
tpgr = TPGR(n_sensors=n_sensors, prior="decreasing", noise=0.1)
246+
tpgr.fit(x, singular_values)
247+
248+
sensors = tpgr.get_sensors()
249+
250+
assert len(sensors) == n_sensors
251+
assert len(set(sensors)) == n_sensors # All unique
252+
253+
254+
def test_tpgr_fit_flat_prior(data_random):
255+
x = data_random
256+
n_sensors = 3
257+
flat_prior = np.linspace(0.9, 0.1, x.shape[1])
258+
tpgr = TPGR(n_sensors=n_sensors, prior=flat_prior, noise=0.1)
259+
# flat prior will not use singular values
260+
tpgr.fit(x, singular_values=np.ones(x.shape[1]))
261+
262+
sensors = tpgr.get_sensors()
263+
264+
assert len(sensors) == n_sensors
265+
assert len(set(sensors)) == n_sensors
266+
267+
268+
def test_tpgr_none_noise(data_random):
269+
x = data_random
270+
singular_values = np.linspace(1.0, 0.1, x.shape[1])
271+
tpgr = TPGR(n_sensors=3)
272+
273+
with pytest.warns(UserWarning):
274+
tpgr.fit(x, singular_values)
275+
276+
# Check that noise was set to the mean of computed prior
277+
expected_noise = singular_values.mean()
278+
assert abs(tpgr.noise - expected_noise) < 1e-10
279+
280+
281+
def test_tpgr_invalid_prior(data_random):
282+
x = data_random
283+
singular_values = np.ones(x.shape[1])
284+
285+
# Invalid string
286+
with pytest.raises(ValueError):
287+
tpgr = TPGR(n_sensors=3, prior="invalid")
288+
tpgr.fit(x, singular_values)
289+
290+
# Invalid 2D prior
291+
invalid_prior_2d = np.random.rand(2, 2)
292+
with pytest.raises(ValueError):
293+
tpgr = TPGR(n_sensors=3, prior=invalid_prior_2d)
294+
tpgr.fit(x, singular_values)
295+
296+
# Prior with invalid shape
297+
wrong_shape_prior = np.full(3, 1) # Length 3 instead of x.shape[1]
298+
with pytest.raises(ValueError):
299+
tpgr = TPGR(n_sensors=3, prior=wrong_shape_prior, noise=0.1)
300+
tpgr.fit(x, singular_values)
301+
302+
303+
def test_tpgr_reproducibility(data_random):
304+
"""Test that TPGR results are reproducible with same input."""
305+
x = data_random
306+
singular_values = np.linspace(1.0, 0.1, x.shape[1])
307+
308+
tpgr1 = TPGR(n_sensors=3, noise=0.1)
309+
tpgr2 = TPGR(n_sensors=3, noise=0.1)
310+
311+
tpgr1.fit(x, singular_values)
312+
tpgr2.fit(x, singular_values)
313+
314+
assert tpgr1.sensors_ == tpgr2.sensors_
315+
316+
317+
def test_tpgr_one_pt_energy(data_random):
318+
"""Test TPGR one-point energy calculation."""
319+
x = data_random
320+
singular_values = np.linspace(1.0, 0.1, x.shape[1])
321+
tpgr = TPGR(n_sensors=3, noise=0.1)
322+
G = x @ np.diag(singular_values)
323+
324+
one_pt_energy = tpgr._one_pt_energy(G)
325+
326+
assert one_pt_energy.shape == (x.shape[0],)
327+
assert np.all(one_pt_energy <= 0) # All 1-pt energies should be negative
328+
329+
330+
def test_tpgr_two_pt_energy(data_random):
331+
"""Test TPGR two-point energy calculation."""
332+
x = data_random
333+
singular_values = np.linspace(1.0, 0.1, x.shape[1])
334+
tpgr = TPGR(n_sensors=3, noise=0.1)
335+
G = x @ np.diag(singular_values)
336+
337+
# Select first sensor manually
338+
G_selected = G[[0], :]
339+
G_remaining = G[1:, :]
340+
341+
two_pt_energy = tpgr._two_pt_energy(G_selected, G_remaining)
342+
343+
assert two_pt_energy.shape == (x.shape[0] - 1,)
344+
assert np.all(two_pt_energy >= 0) # All 2-pt energies should be non-negative

0 commit comments

Comments
 (0)