b8301a4329
- 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>
66 lines
2.3 KiB
Python
66 lines
2.3 KiB
Python
import sys
|
|
import os
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import torch
|
|
from engine import prepare_data
|
|
from model import HeatPINN, heat_pinn_loss
|
|
|
|
|
|
def test_data_to_model_forward():
|
|
"""prepare_data → forward: shape e device coerenti, nessun NaN."""
|
|
data = prepare_data(N_f=100, N_ic=50, N_bc=50)
|
|
model = HeatPINN().to(data['device'])
|
|
xt = torch.stack([data['x_f'], data['t_f']], dim=1)
|
|
out = model(xt)
|
|
assert out.shape == (data['x_f'].shape[0], 1)
|
|
assert out.device.type == data['device'].type
|
|
assert torch.isfinite(out).all()
|
|
|
|
|
|
def test_full_loss_pipeline():
|
|
"""prepare_data → heat_pinn_loss: tutti i componenti finiti e non-negativi."""
|
|
data = prepare_data(N_f=100, N_ic=50, N_bc=50)
|
|
model = HeatPINN().to(data['device'])
|
|
total, L_pde, L_ic, L_bc = heat_pinn_loss(
|
|
model, data['x_f'], data['t_f'], data['x_ic'], data['t_bc']
|
|
)
|
|
for name, v in [('total', total), ('L_pde', L_pde), ('L_ic', L_ic), ('L_bc', L_bc)]:
|
|
assert torch.isfinite(v), f"{name} non è finita"
|
|
assert v.item() >= 0.0, f"{name} è negativa"
|
|
|
|
|
|
def test_backward_gradients_finite():
|
|
"""Il backward della loss non produce NaN/Inf nei parametri."""
|
|
data = prepare_data(N_f=100, N_ic=50, N_bc=50)
|
|
model = HeatPINN().to(data['device'])
|
|
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
|
|
optimizer.zero_grad()
|
|
total, _, _, _ = heat_pinn_loss(
|
|
model, data['x_f'], data['t_f'], data['x_ic'], data['t_bc']
|
|
)
|
|
total.backward()
|
|
for p in model.parameters():
|
|
assert p.grad is not None
|
|
assert torch.isfinite(p.grad).all(), "gradiente NaN/Inf"
|
|
|
|
|
|
def test_training_loop_stable():
|
|
"""20 step di Adam non producono NaN/Inf nei parametri né nella loss."""
|
|
data = prepare_data(N_f=200, N_ic=100, N_bc=100)
|
|
model = HeatPINN().to(data['device'])
|
|
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
|
|
args = (model, data['x_f'], data['t_f'], data['x_ic'], data['t_bc'])
|
|
|
|
for _ in range(20):
|
|
optimizer.zero_grad()
|
|
loss, _, _, _ = heat_pinn_loss(*args)
|
|
loss.backward()
|
|
optimizer.step()
|
|
|
|
for p in model.parameters():
|
|
assert torch.isfinite(p).all(), "parametro NaN/Inf dopo training"
|
|
|
|
total, _, _, _ = heat_pinn_loss(*args)
|
|
assert torch.isfinite(total)
|