jetclustering / tests /test_object_cond.py
gregorkrzmanc's picture
Sync to HF Space
bbae87d
Raw
History Blame Contribute Delete
9.3 kB
"""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)