|
3 | 3 | import numpy as np |
4 | 4 | import pytest |
5 | 5 |
|
6 | | -from pysensors.optimizers import CCQR, GQR, QR, qr_reflector |
| 6 | +from pysensors.optimizers import CCQR, GQR, QR, TPGR, qr_reflector |
7 | 7 |
|
8 | 8 |
|
9 | 9 | def test_num_sensors(data_vandermonde): |
@@ -221,3 +221,124 @@ def test_ccqr_fit_raises_error_for_mismatched_dimensions(): |
221 | 221 | ccqr.fit(basis_matrix) |
222 | 222 | assert hasattr(ccqr, "pivots_") |
223 | 223 | 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