Skip to content

Commit c1ed8d7

Browse files
committed
ENH: integration tests.
1 parent 248e672 commit c1ed8d7

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

tests/integration_test.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import time
2+
import unittest
3+
import torch
4+
from torch import nn
5+
6+
from antsnormflows.utils.splines import search_sorted
7+
from antsnormflows.flows.affine.glow import GlowBlock3d
8+
from antsnormflows.core import ConditionalNormalizingFlow
9+
from antsnormflows.distributions.base import DiagGaussian, ConditionalDiagGaussian
10+
from antsnormflows.flows.neural_spline.wrapper import AutoregressiveRationalQuadraticSpline
11+
12+
class TestSearchsortedCorrectness(unittest.TestCase):
13+
def test_binary_search_correctness(self):
14+
"""Vérifie que search_sorted retourne les bons indices (Fix GB-4)."""
15+
bin_locations = torch.tensor([[0.0, 0.25, 0.5, 0.75, 1.0]])
16+
inputs = torch.tensor([[0.0, 0.1, 0.3, 0.6, 0.99]])
17+
expected = torch.tensor([[0, 0, 1, 2, 3]])
18+
19+
result = search_sorted(bin_locations, inputs)
20+
torch.testing.assert_close(result, expected)
21+
22+
def test_edge_cases(self):
23+
"""Test des cas limites (valeurs exactement sur les bornes)."""
24+
bin_locations = torch.tensor([[0.0, 0.5, 1.0]])
25+
inputs = torch.tensor([[1.0]])
26+
result = search_sorted(bin_locations, inputs)
27+
self.assertEqual(result.item(), 1)
28+
29+
30+
class TestNeuroImagingIntegration(unittest.TestCase):
31+
"""Tests spécifiques aux cas d'usage neuro-imagerie 3D."""
32+
33+
def test_glow_block_3d_forward_inverse(self):
34+
"""Teste GlowBlock3d sur un volume IRM synthétique miniature."""
35+
torch.manual_seed(42)
36+
C, D, H, W = 2, 16, 16, 16
37+
38+
block = GlowBlock3d(
39+
channels=C,
40+
hidden_channels=8,
41+
split_mode='channel',
42+
scale=True
43+
)
44+
45+
x = torch.randn(2, C, D, H, W)
46+
x_fwd, log_det_fwd = block(x)
47+
x_rec, log_det_inv = block.inverse(x_fwd)
48+
49+
# Tolérance augmentée pour le float32
50+
torch.testing.assert_close(x_rec, x, atol=1e-4, rtol=1e-4)
51+
torch.testing.assert_close(log_det_fwd + log_det_inv,
52+
torch.zeros_like(log_det_fwd), atol=1e-4, rtol=1e-4)
53+
54+
def test_conditional_flow_on_morphometric_features(self):
55+
"""Simule un conditionnement sur l'âge/sexe du patient."""
56+
torch.manual_seed(42)
57+
n_dims = 10
58+
n_context = 2
59+
n_samples = 50
60+
61+
layer = AutoregressiveRationalQuadraticSpline(
62+
num_input_channels=n_dims,
63+
num_blocks=2,
64+
num_hidden_channels=32,
65+
num_context_channels=n_context
66+
)
67+
base = DiagGaussian(n_dims)
68+
69+
# Ajout de la forme et de l'encodeur de contexte (mapping N -> 2 * D)
70+
context_encoder = torch.nn.Linear(n_context, 2 * n_dims)
71+
target = ConditionalDiagGaussian(shape=(n_dims,), context_encoder=context_encoder)
72+
73+
model = ConditionalNormalizingFlow(base, [layer], target)
74+
75+
x = torch.randn(n_samples, n_dims)
76+
context = torch.randn(n_samples, n_context)
77+
78+
log_p = model.log_prob(x, context=context)
79+
80+
self.assertEqual(log_p.shape, (n_samples,))
81+
self.assertFalse(torch.isnan(log_p).any())
82+
self.assertFalse(torch.isinf(log_p).any())
83+
84+
class TestPerformanceRegression(unittest.TestCase):
85+
"""Tests de non-régression de performance sur GPU."""
86+
87+
@classmethod
88+
def setUpClass(cls):
89+
if not torch.cuda.is_available():
90+
raise unittest.SkipTest("Tests de performance ignorés : GPU requis")
91+
cls.device = torch.device("cuda:0")
92+
93+
def test_nsf_memory_footprint(self):
94+
"""Vérifie la croissance linéaire de la mémoire avec le batch size."""
95+
n_dims = 8
96+
layer = AutoregressiveRationalQuadraticSpline(
97+
num_input_channels=n_dims, num_blocks=2, num_hidden_channels=32
98+
).to(self.device)
99+
100+
# Test petit batch
101+
torch.cuda.reset_peak_memory_stats()
102+
z_small = torch.randn(64, n_dims, device=self.device)
103+
_ = layer(z_small)
104+
mem_small = torch.cuda.max_memory_allocated() / 1e6
105+
106+
# Test grand batch
107+
torch.cuda.reset_peak_memory_stats()
108+
z_large = torch.randn(256, n_dims, device=self.device)
109+
_ = layer(z_large)
110+
mem_large = torch.cuda.max_memory_allocated() / 1e6
111+
112+
memory_ratio = mem_large / mem_small if mem_small > 0 else 1.0
113+
batch_ratio = 256 / 64 # = 4.0
114+
115+
# Vérifie que la mémoire ne croît pas de manière quadratique
116+
self.assertLess(memory_ratio, batch_ratio * 1.5)

0 commit comments

Comments
 (0)