PINN: risolve problemi minori — sigma in config, scale precompilate, closure fuori loop
- config.py: aggiunge GAUSS_SIGMA = 0.02 nella sezione parametri fisici - model.py: T_char, grad_char, pde_scale diventano costanti di modulo (_T_char, _grad_char, _pde_scale) calcolate una sola volta all'import - engine.py: closure L-BFGS definita una volta sola fuori dal loop Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,9 @@ T_END = 10.0 # fine simulazione [s]
|
|||||||
NX = 250 # nodi spaziali
|
NX = 250 # nodi spaziali
|
||||||
NT = 15000 # passi temporali (verifica CFL automatica)
|
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
|
# Architettura PINN
|
||||||
HIDDEN_SIZE = 128 # neuroni per layer nascosto
|
HIDDEN_SIZE = 128 # neuroni per layer nascosto
|
||||||
N_HIDDEN_LAYERS = 4 # numero di layer nascosti
|
N_HIDDEN_LAYERS = 4 # numero di layer nascosti
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ def train_model(data, epochs=None, patience=None):
|
|||||||
|
|
||||||
_last = {}
|
_last = {}
|
||||||
|
|
||||||
for step in range(config.LBFGS_STEPS):
|
|
||||||
def closure():
|
def closure():
|
||||||
lbfgs.zero_grad()
|
lbfgs.zero_grad()
|
||||||
loss, L_pde, L_ic, L_bc = heat_pinn_loss(
|
loss, L_pde, L_ic, L_bc = heat_pinn_loss(
|
||||||
@@ -130,6 +129,7 @@ def train_model(data, epochs=None, patience=None):
|
|||||||
_last['bc'] = L_bc.item()
|
_last['bc'] = L_bc.item()
|
||||||
return loss
|
return loss
|
||||||
|
|
||||||
|
for step in range(config.LBFGS_STEPS):
|
||||||
lbfgs.step(closure)
|
lbfgs.step(closure)
|
||||||
if _last['loss'] < best_loss:
|
if _last['loss'] < best_loss:
|
||||||
best_loss = _last['loss']
|
best_loss = _last['loss']
|
||||||
|
|||||||
@@ -22,14 +22,19 @@ class HeatPINN(nn.Module):
|
|||||||
return config.T_AMB + (config.Q_VAL * config.L / config.K) * self.net(xt_norm)
|
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,
|
def heat_pinn_loss(model, x_f, t_f, x_ic, t_bc,
|
||||||
w_pde=None, w_ic=None, w_bc=None):
|
w_pde=None, w_ic=None, w_bc=None):
|
||||||
if w_pde is None: w_pde = config.W_PDE
|
if w_pde is None: w_pde = config.W_PDE
|
||||||
if w_ic is None: w_ic = config.W_IC
|
if w_ic is None: w_ic = config.W_IC
|
||||||
if w_bc is None: w_bc = config.W_BC
|
if w_bc is None: w_bc = config.W_BC
|
||||||
# Characteristic scales for normalization
|
T_char = _T_char
|
||||||
T_char = config.Q_VAL * config.L / config.K # ~50 °C — temperature scale
|
grad_char = _grad_char
|
||||||
grad_char = (config.Q_VAL / config.K) ** 2 # ~2500 — gradient scale²
|
|
||||||
|
|
||||||
# PDE residual: dT/dt - alpha * d2T/dx2 - source(x,t) = 0 (normalized by T_char/t_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)
|
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))
|
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)
|
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]
|
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,
|
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(config.Q_VAL, device=t_f.device, dtype=t_f.dtype),
|
||||||
torch.tensor(0.0, 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)
|
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
|
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²
|
# 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_pred = model(torch.stack([x_ic, torch.zeros_like(x_ic)], dim=1))
|
||||||
T_ic_true = torch.full_like(T_ic_pred, config.T0)
|
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
|
# 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)
|
x_left = torch.zeros(t_bc.shape[0], device=t_bc.device).requires_grad_(True)
|
||||||
|
|||||||
Reference in New Issue
Block a user