test: aggiunge suite completa — unit, integration ed e2e (42 test)

- pytest.ini: configura testpaths, marker slow, output verboso
- tests/conftest.py: fixture condivise (device, small_data, pinn_model)
- tests/test_config.py: sanità parametri fisici e numerici, CFL, _pde_scale
- tests/test_model.py: HeatPINN.forward e heat_pinn_loss (shape, finiti,
  zero-weight analytici per IC e BC, scaling dei pesi)
- tests/test_engine_data.py: set_seed, _get_device, prepare_data
  (shape, bounds, device consistency, determinismo)
- tests/test_integration_pinn.py: pipeline dati→modello→loss→backward
- tests/test_e2e.py: FDM completo, visualizer FDM/PINN con tmp_path,
  training breve (2 test @slow)
- requirements.txt: aggiunge pytest>=7.0.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 15:50:54 +02:00
parent 1237a57290
commit b8301a4329
8 changed files with 424 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import pytest
import numpy as np
import torch
import config
# ── FDM ───────────────────────────────────────────────────────────────────────
def test_fdm_full_run():
"""Il solver FDM produce un campo di temperatura fisicamente corretto."""
from fdm.solver import solve
T, x, t = solve()
assert T.shape == (config.NX, config.NT)
assert np.isfinite(T).all()
assert T[:, -1].mean() > config.T0 # la sorgente ha scaldato il dominio
assert T.max() > config.T0 # picco sopra la IC
assert T.min() >= config.T0 - 1e-6 # nessun raffreddamento sotto T0 (no sorgenti fredde)
def test_fdm_visualizer_creates_html(tmp_path, monkeypatch):
"""Il visualizer FDM scrive almeno un file HTML senza errori."""
import fdm.visualizer as fdm_vis
monkeypatch.setattr(fdm_vis, 'BASE_DIR', str(tmp_path))
from fdm.solver import solve
T, x, t = solve()
fdm_vis.visualize_fdm(T, x, t)
html_files = list(tmp_path.rglob('*.html'))
assert len(html_files) >= 1, "Nessun file HTML generato dal visualizer FDM"
def test_pinn_visualizer_creates_html(tmp_path, monkeypatch):
"""Il visualizer PINN scrive i tre file HTML senza errori."""
import visualizer as pinn_vis
monkeypatch.setattr(pinn_vis, 'BASE_DIR', str(tmp_path))
from fdm.solver import solve as fdm_solve
T_fdm, _, _ = fdm_solve()
nx, nt = 20, 20
x_vals = np.linspace(0, config.L, nx)
t_vals = np.linspace(0, config.T_END, nt)
T_pred = np.full((nx, nt), config.T_AMB) # predizione costante (dummy)
pinn_vis.visualize_heat_field(T_pred, x_vals, t_vals, T_fdm)
html_files = list(tmp_path.rglob('*.html'))
assert len(html_files) == 3, f"Attesi 3 HTML, trovati {len(html_files)}"
# ── PINN training (lento) ─────────────────────────────────────────────────────
@pytest.mark.slow
def test_pinn_training_saves_checkpoint(tmp_path, monkeypatch):
"""Training per 30 epoche: il checkpoint viene salvato."""
import engine
save_path = str(tmp_path / 'model.pth')
monkeypatch.setattr(engine, 'MODEL_SAVE_PATH', save_path)
monkeypatch.setattr(engine, 'MODELS_DIR', str(tmp_path))
from engine import prepare_data, train_model
data = prepare_data(N_f=300, N_ic=100, N_bc=100)
train_model(data, epochs=30, patience=30)
assert os.path.exists(save_path)
ckpt = torch.load(save_path, map_location='cpu', weights_only=True)
assert 'state_dict' in ckpt
@pytest.mark.slow
def test_pinn_evaluate_after_training(tmp_path, monkeypatch):
"""evaluate_model gira senza errori dopo un training minimo."""
import engine
save_path = str(tmp_path / 'model.pth')
monkeypatch.setattr(engine, 'MODEL_SAVE_PATH', save_path)
monkeypatch.setattr(engine, 'MODELS_DIR', str(tmp_path))
from engine import prepare_data, train_model, evaluate_model
data = prepare_data(N_f=300, N_ic=100, N_bc=100)
train_model(data, epochs=30, patience=30)
evaluate_model(data)