Coverage for NeuralTSNE/NeuralTSNE/TSNE/tests/test_helpers.py: 96%
48 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-18 16:32 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-18 16:32 +0000
1from unittest.mock import MagicMock, patch
3import numpy as np
4import pytest
5import torch
7from NeuralTSNE.TSNE.Helpers import Hbeta, x2p, x2p_job
10@pytest.mark.parametrize(
11 "D, beta, expected_H, expected_P",
12 [
13 (
14 torch.tensor([[0.0, 2.0], [2.0, 0.0]]),
15 0.5,
16 1.2754,
17 torch.tensor([[0.3655, 0.1345], [0.1345, 0.3655]]),
18 ),
19 (
20 torch.tensor([[0.0, 1.0, 2.0], [1.0, 0.0, 3.0], [2.0, 3.0, 0.0]]),
21 0.7,
22 1.9558,
23 torch.tensor(
24 [
25 [0.2114, 0.1050, 0.0521],
26 [0.1050, 0.2114, 0.0259],
27 [0.0521, 0.0259, 0.2114],
28 ]
29 ),
30 ),
31 ],
32)
33def test_Hbeta(D, beta, expected_H, expected_P):
34 H, P = Hbeta(D, beta)
35 assert torch.isclose(H, torch.tensor(expected_H), rtol=1e-3)
36 assert torch.allclose(P, expected_P, rtol=1e-3)
39@pytest.mark.parametrize("i", [7, 1, 21])
40@pytest.mark.parametrize("perplexity", [10, 50, 1000])
41@pytest.mark.parametrize("tolerance", [0.1, 0.01, 0.001, 1e-6])
42@pytest.mark.parametrize("max_iterations", [100, 200, 50])
43@pytest.mark.parametrize(
44 "D",
45 [
46 torch.tensor([17.0, 89.0, 123.0, 40.0, 67.0]),
47 torch.tensor([0.6, 0.2311, 0.456, 0.01, 1.53]),
48 ],
49) # TODO: CHECK IF THIS IS CORRECT
50def test_x2p_job(
51 i: int,
52 perplexity: int,
53 D: torch.Tensor,
54 tolerance: float,
55 max_iterations: int,
56):
57 logU = torch.tensor([np.log(perplexity)], dtype=torch.float32)
58 data = i, D, logU
59 result = x2p_job(data, tolerance, max_iterations)
60 i, P, Hdiff, iterations = result
61 assert i == i
62 if iterations != max_iterations: 62 ↛ 63line 62 didn't jump to line 63 because the condition on line 62 was never true
63 assert torch.allclose(Hdiff, torch.zeros_like(Hdiff), rtol=tolerance)
64 else:
65 estimated_tolerance = 10 ** torch.ceil(torch.log10(torch.abs(Hdiff)))[0]
66 assert torch.isclose(torch.zeros_like(Hdiff), Hdiff, atol=estimated_tolerance)
69@pytest.mark.parametrize(
70 "X, perplexity, tolerance, expected_shape",
71 [
72 [torch.tensor([[1.0, 2.0], [3.0, 4.0]]), 2, 0.1, (2, 2)],
73 [
74 torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]),
75 3,
76 0.01,
77 (3, 3),
78 ],
79 [
80 torch.tensor(
81 [
82 [1.0, 6.0, 4.0, 2.0, 5.0],
83 [7.0, 2.0, 6.0, 7.0, 3.0],
84 [8.0, 2.0, 1.0, 7.0, 9.0],
85 ]
86 ),
87 5,
88 0.001,
89 (3, 3),
90 ],
91 ],
92)
93@patch("NeuralTSNE.TSNE.Helpers.helpers.x2p_job")
94def test_x2p(
95 mock_x2p_job: MagicMock,
96 X: torch.Tensor,
97 perplexity: int,
98 tolerance: float,
99 expected_shape: tuple,
100):
101 log_perplexity = torch.tensor([np.log(perplexity)], dtype=torch.float32)
102 pair_squared_distances = torch.cdist(X, X, p=2) ** 2
104 pairwise_distances = pair_squared_distances[pair_squared_distances != 0].reshape(
105 pair_squared_distances.shape[0], -1
106 )
108 returned_P = [
109 (i, torch.ones(pairwise_distances.shape[1])) for i in range(X.shape[0])
110 ]
112 mock_x2p_job.side_effect = returned_P
114 P = x2p(X, perplexity, tolerance)
115 assert P.shape == expected_shape
116 assert mock_x2p_job.call_count == X.shape[0]
117 mock_x2p_calls = mock_x2p_job.call_args_list
119 P_diag = P.diag()
120 assert torch.allclose(P_diag, torch.zeros_like(P_diag), rtol=1e-5)
121 P_no_diag = P[P != 0].reshape(P.shape[0], -1)
122 assert torch.allclose(P_no_diag, torch.ones_like(P_no_diag), rtol=1e-5)
124 for k, call_args in enumerate(mock_x2p_calls):
125 data, tol = call_args[0]
126 i, D, logU = data
128 assert i == k
129 assert torch.allclose(D, pairwise_distances[k], rtol=1e-5)
130 assert torch.isclose(logU, log_perplexity, rtol=1e-5)
131 assert tol == tolerance