Spaces:
Runtime error
Runtime error
| """Tests for object condensation layer utilities: calc_eta_phi, huber, clustering, isin, reincrementalize.""" | |
| import math | |
| import numpy as np | |
| import pytest | |
| import torch | |
| from src.layers.object_cond import ( | |
| calc_eta_phi, | |
| huber, | |
| safe_index, | |
| assert_no_nans, | |
| isin, | |
| reincrementalize, | |
| get_clustering_np, | |
| get_clustering, | |
| scatter_counts_to_indices, | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # calc_eta_phi | |
| # --------------------------------------------------------------------------- | |
| class TestCalcEtaPhi: | |
| def test_output_shape_stacked(self): | |
| coords = torch.randn(10, 3) | |
| result = calc_eta_phi(coords, return_stacked=True) | |
| assert result.shape == (10, 2) | |
| def test_output_shape_unstacked(self): | |
| coords = torch.randn(10, 3) | |
| eta, phi = calc_eta_phi(coords, return_stacked=False) | |
| assert eta.shape == (10,) | |
| assert phi.shape == (10,) | |
| def test_phi_range(self): | |
| coords = torch.tensor([ | |
| [1.0, 0.0, 0.0], | |
| [0.0, 1.0, 0.0], | |
| [-1.0, 0.0, 0.0], | |
| [0.0, -1.0, 0.0], | |
| ]) | |
| result = calc_eta_phi(coords) | |
| phi = result[:, 1] | |
| assert phi[0] == pytest.approx(0.0, abs=1e-6) | |
| assert phi[1] == pytest.approx(math.pi / 2, abs=1e-6) | |
| assert abs(phi[2]) == pytest.approx(math.pi, abs=1e-6) | |
| assert phi[3] == pytest.approx(-math.pi / 2, abs=1e-6) | |
| def test_eta_zero_at_midplane(self): | |
| """Points in the xy-plane (z=0) should have eta ≈ 0.""" | |
| coords = torch.tensor([[1.0, 1.0, 0.0], [3.0, 4.0, 0.0]]) | |
| result = calc_eta_phi(coords) | |
| eta = result[:, 0] | |
| assert eta[0] == pytest.approx(0.0, abs=1e-6) | |
| assert eta[1] == pytest.approx(0.0, abs=1e-6) | |
| def test_eta_sign(self): | |
| """Positive z → positive eta, negative z → negative eta.""" | |
| coords = torch.tensor([[1.0, 0.0, 1.0], [1.0, 0.0, -1.0]]) | |
| eta, _ = calc_eta_phi(coords, return_stacked=False) | |
| assert eta[0] > 0 | |
| assert eta[1] < 0 | |
| def test_consistency_stacked_vs_unstacked(self): | |
| coords = torch.randn(5, 3) | |
| stacked = calc_eta_phi(coords, return_stacked=True) | |
| eta, phi = calc_eta_phi(coords, return_stacked=False) | |
| torch.testing.assert_close(stacked[:, 0], eta) | |
| torch.testing.assert_close(stacked[:, 1], phi) | |
| # --------------------------------------------------------------------------- | |
| # huber | |
| # --------------------------------------------------------------------------- | |
| class TestHuber: | |
| def test_quadratic_region(self): | |
| d = torch.tensor([0.5]) | |
| delta = 1.0 | |
| result = huber(d, delta) | |
| assert result.item() == pytest.approx(0.25) | |
| def test_linear_region(self): | |
| d = torch.tensor([3.0]) | |
| delta = 1.0 | |
| result = huber(d, delta) | |
| expected = 2.0 * delta * (abs(3.0) - delta) | |
| assert result.item() == pytest.approx(expected) | |
| def test_at_boundary(self): | |
| d = torch.tensor([1.0]) | |
| delta = 1.0 | |
| result = huber(d, delta) | |
| assert result.item() == pytest.approx(1.0) | |
| def test_negative_values(self): | |
| d = torch.tensor([-2.0]) | |
| delta = 1.0 | |
| result = huber(d, delta) | |
| expected = 2.0 * delta * (2.0 - delta) | |
| assert result.item() == pytest.approx(expected) | |
| def test_zero(self): | |
| d = torch.tensor([0.0]) | |
| delta = 1.0 | |
| assert huber(d, delta).item() == pytest.approx(0.0) | |
| def test_batch(self): | |
| d = torch.tensor([0.0, 0.5, 1.0, 2.0]) | |
| delta = 1.0 | |
| result = huber(d, delta) | |
| assert result.shape == (4,) | |
| # --------------------------------------------------------------------------- | |
| # safe_index | |
| # --------------------------------------------------------------------------- | |
| class TestSafeIndex: | |
| def test_present(self): | |
| assert safe_index([10, 20, 30], 20) == 2 # 1-indexed | |
| def test_absent(self): | |
| assert safe_index([10, 20, 30], 99) == 0 | |
| def test_first_element(self): | |
| assert safe_index([5, 6, 7], 5) == 1 | |
| # --------------------------------------------------------------------------- | |
| # assert_no_nans | |
| # --------------------------------------------------------------------------- | |
| class TestAssertNoNans: | |
| def test_clean_tensor(self): | |
| assert_no_nans(torch.tensor([1.0, 2.0, 3.0])) | |
| def test_nan_tensor(self): | |
| with pytest.raises(AssertionError): | |
| assert_no_nans(torch.tensor([1.0, float("nan"), 3.0])) | |
| # --------------------------------------------------------------------------- | |
| # isin | |
| # --------------------------------------------------------------------------- | |
| class TestIsin: | |
| def test_basic(self): | |
| ar1 = torch.tensor([1, 2, 3, 4, 5]) | |
| ar2 = torch.tensor([2, 4]) | |
| result = isin(ar1, ar2) | |
| expected = torch.tensor([False, True, False, True, False]) | |
| torch.testing.assert_close(result, expected) | |
| def test_empty_ar2(self): | |
| ar1 = torch.tensor([1, 2, 3]) | |
| ar2 = torch.tensor([]) | |
| result = isin(ar1, ar2) | |
| assert not result.any() | |
| def test_all_present(self): | |
| ar1 = torch.tensor([1, 2, 3]) | |
| ar2 = torch.tensor([1, 2, 3]) | |
| result = isin(ar1, ar2) | |
| assert result.all() | |
| # --------------------------------------------------------------------------- | |
| # scatter_counts_to_indices | |
| # --------------------------------------------------------------------------- | |
| class TestScatterCountsToIndices: | |
| def test_basic(self): | |
| counts = torch.tensor([3, 2, 2]) | |
| result = scatter_counts_to_indices(counts) | |
| expected = torch.tensor([0, 0, 0, 1, 1, 2, 2]) | |
| torch.testing.assert_close(result, expected) | |
| def test_single_group(self): | |
| counts = torch.tensor([5]) | |
| result = scatter_counts_to_indices(counts) | |
| expected = torch.tensor([0, 0, 0, 0, 0]) | |
| torch.testing.assert_close(result, expected) | |
| def test_empty(self): | |
| counts = torch.tensor([0, 3]) | |
| result = scatter_counts_to_indices(counts) | |
| expected = torch.tensor([1, 1, 1]) | |
| torch.testing.assert_close(result, expected) | |
| # --------------------------------------------------------------------------- | |
| # get_clustering_np | |
| # --------------------------------------------------------------------------- | |
| class TestGetClusteringNp: | |
| def test_two_clusters(self): | |
| betas = np.array([0.9, 0.01, 0.01, 0.8, 0.01, 0.01]) | |
| X = np.array([ | |
| [0.0, 0.0], | |
| [0.1, 0.1], | |
| [0.2, 0.0], | |
| [5.0, 5.0], | |
| [5.1, 5.1], | |
| [5.2, 5.0], | |
| ]) | |
| clustering = get_clustering_np(betas, X, tbeta=0.5, td=1.0) | |
| assert clustering[0] == 0 | |
| assert clustering[1] == 0 | |
| assert clustering[2] == 0 | |
| assert clustering[3] == 3 | |
| assert clustering[4] == 3 | |
| assert clustering[5] == 3 | |
| def test_no_condpoints(self): | |
| betas = np.array([0.01, 0.02, 0.03]) | |
| X = np.array([[0, 0], [1, 1], [2, 2]]) | |
| clustering = get_clustering_np(betas, X, tbeta=0.5, td=1.0) | |
| assert (clustering == -1).all() | |
| def test_all_background(self): | |
| betas = np.array([0.9, 0.01]) | |
| X = np.array([[0, 0], [100, 100]]) | |
| clustering = get_clustering_np(betas, X, tbeta=0.5, td=0.5) | |
| assert clustering[0] == 0 | |
| assert clustering[1] == -1 | |
| # --------------------------------------------------------------------------- | |
| # get_clustering (torch version) | |
| # --------------------------------------------------------------------------- | |
| class TestGetClustering: | |
| def test_two_clusters(self): | |
| betas = torch.tensor([0.9, 0.01, 0.01, 0.8, 0.01, 0.01]) | |
| X = torch.tensor([ | |
| [0.0, 0.0], | |
| [0.1, 0.1], | |
| [0.2, 0.0], | |
| [5.0, 5.0], | |
| [5.1, 5.1], | |
| [5.2, 5.0], | |
| ]) | |
| clustering = get_clustering(betas, X, tbeta=0.5, td=1.0) | |
| assert clustering[0].item() == 0 | |
| assert clustering[1].item() == 0 | |
| assert clustering[3].item() == 3 | |
| assert clustering[4].item() == 3 | |
| def test_no_condpoints(self): | |
| betas = torch.tensor([0.01, 0.02]) | |
| X = torch.tensor([[0.0, 0.0], [1.0, 1.0]]) | |
| clustering = get_clustering(betas, X, tbeta=0.5, td=1.0) | |
| assert (clustering == -1).all() | |
| # --------------------------------------------------------------------------- | |
| # reincrementalize | |
| # --------------------------------------------------------------------------- | |
| class TestReincrementalize: | |
| def test_docstring_example(self): | |
| y = torch.LongTensor([0, 0, 0, 1, 1, 3, 3, 0, 0, 0, 0, 0, 2, 2, 3, 3, 0, 0, 1, 1]) | |
| batch = torch.LongTensor([0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2]) | |
| result = reincrementalize(y, batch) | |
| expected = torch.tensor([0, 0, 0, 1, 1, 2, 2, 0, 0, 0, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1]) | |
| torch.testing.assert_close(result, expected) | |
| def test_no_holes(self): | |
| y = torch.LongTensor([0, 0, 1, 1]) | |
| batch = torch.LongTensor([0, 0, 0, 0]) | |
| result = reincrementalize(y, batch) | |
| expected = torch.tensor([0, 0, 1, 1]) | |
| torch.testing.assert_close(result, expected) | |