diff --git a/config.py b/config.py index b4099d6..f3c1ef5 100644 --- a/config.py +++ b/config.py @@ -20,6 +20,9 @@ T_END = 10.0 # fine simulazione [s] NX = 250 # nodi spaziali NT = 15000 # passi temporali (verifica CFL automatica) +# Sorgente gaussiana (approssimazione continua del delta di Dirac) +GAUSS_SIGMA = 0.02 # larghezza del picco gaussiano [m] + # Architettura PINN HIDDEN_SIZE = 128 # neuroni per layer nascosto N_HIDDEN_LAYERS = 4 # numero di layer nascosti diff --git a/engine.py b/engine.py index cc3d6ea..0e30c21 100644 --- a/engine.py +++ b/engine.py @@ -117,19 +117,19 @@ def train_model(data, epochs=None, patience=None): _last = {} - for step in range(config.LBFGS_STEPS): - def closure(): - lbfgs.zero_grad() - loss, L_pde, L_ic, L_bc = heat_pinn_loss( - model, data['x_f'], data['t_f'], data['x_ic'], data['t_bc'] - ) - loss.backward() - _last['loss'] = loss.item() - _last['pde'] = L_pde.item() - _last['ic'] = L_ic.item() - _last['bc'] = L_bc.item() - return loss + def closure(): + lbfgs.zero_grad() + loss, L_pde, L_ic, L_bc = heat_pinn_loss( + model, data['x_f'], data['t_f'], data['x_ic'], data['t_bc'] + ) + loss.backward() + _last['loss'] = loss.item() + _last['pde'] = L_pde.item() + _last['ic'] = L_ic.item() + _last['bc'] = L_bc.item() + return loss + for step in range(config.LBFGS_STEPS): lbfgs.step(closure) if _last['loss'] < best_loss: best_loss = _last['loss'] diff --git a/model.py b/model.py index b0603b2..b4dbda3 100644 --- a/model.py +++ b/model.py @@ -22,14 +22,19 @@ class HeatPINN(nn.Module): return config.T_AMB + (config.Q_VAL * config.L / config.K) * self.net(xt_norm) +# Precomputed loss scales (depend only on config constants) +_T_char = config.Q_VAL * config.L / config.K # ~150 °C — temperature scale +_grad_char = (config.Q_VAL / config.K) ** 2 # ~22500 — gradient scale² +_pde_scale = (_T_char / config.T_END) ** 2 + 1e-8 + + def heat_pinn_loss(model, x_f, t_f, x_ic, t_bc, w_pde=None, w_ic=None, w_bc=None): if w_pde is None: w_pde = config.W_PDE if w_ic is None: w_ic = config.W_IC if w_bc is None: w_bc = config.W_BC - # Characteristic scales for normalization - T_char = config.Q_VAL * config.L / config.K # ~50 °C — temperature scale - grad_char = (config.Q_VAL / config.K) ** 2 # ~2500 — gradient scale² + T_char = _T_char + grad_char = _grad_char # PDE residual: dT/dt - alpha * d2T/dx2 - source(x,t) = 0 (normalized by T_char/t_char) x_f = x_f.detach().requires_grad_(True) @@ -37,19 +42,18 @@ def heat_pinn_loss(model, x_f, t_f, x_ic, t_bc, T_f = model(torch.stack([x_f, t_f], dim=1)) dT_dt, dT_dx = torch.autograd.grad(T_f.sum(), [t_f, x_f], create_graph=True) d2T_dx2 = torch.autograd.grad(dT_dx.sum(), x_f, create_graph=True)[0] - sigma = 0.02 Q_t_f = torch.where(t_f >= config.T_STEP, torch.tensor(config.Q_VAL, device=t_f.device, dtype=t_f.dtype), torch.tensor(0.0, device=t_f.device, dtype=t_f.dtype)) + sigma = config.GAUSS_SIGMA gauss = torch.exp(-0.5 * ((x_f - config.X_SRC) / sigma) ** 2) / (sigma * (2 * torch.pi) ** 0.5) source_term = (config.ALPHA / config.K) * Q_t_f * gauss - pde_scale = (T_char / config.T_END) ** 2 + 1e-8 - L_pde = ((dT_dt - config.ALPHA * d2T_dx2 - source_term) ** 2).mean() / pde_scale + L_pde = ((dT_dt - config.ALPHA * d2T_dx2 - source_term) ** 2).mean() / _pde_scale # IC: T(x, 0) = T0 — normalized by T_char² T_ic_pred = model(torch.stack([x_ic, torch.zeros_like(x_ic)], dim=1)) T_ic_true = torch.full_like(T_ic_pred, config.T0) - L_ic = ((T_ic_pred - T_ic_true) ** 2).mean() / (T_char ** 2 + 1e-8) + L_ic = ((T_ic_pred - T_ic_true) ** 2).mean() / (_T_char ** 2 + 1e-8) # BC x=0: Robin — dT/dx + H_CONV/K * (T(0,t) - T_AMB) = 0 x_left = torch.zeros(t_bc.shape[0], device=t_bc.device).requires_grad_(True)