A x=0 la normale uscente è -x, quindi la condizione Robin corretta è
dT/dx - (h/k)(T-T_amb) = 0, speculare a x=L dove vale dT/dx + (h/k)(T-T_amb) = 0.
Il segno errato causava T(0,t) sotto T_amb (~12°C) con sorgente attiva.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 16:12:38 +02:00
2 changed files with 554 additions and 1 deletions
Soluzione dell'equazione del calore 1D con sorgente puntuale tramite **Physics-Informed Neural Network (PINN)**, validata contro un solver numerico **FDM** (Finite Difference Method).
---
## Indice
1. [Problema fisico](#1-problema-fisico)
2. [Approccio: PINN vs FDM](#2-approccio-pinn-vs-fdm)
3. [Struttura del progetto](#3-struttura-del-progetto)
4. [Installazione](#4-installazione)
5. [Utilizzo](#5-utilizzo)
6. [Architettura della rete neurale](#6-architettura-della-rete-neurale)
7. [Funzione di loss](#7-funzione-di-loss)
8. [Training](#8-training)
9. [Solver FDM di riferimento](#9-solver-fdm-di-riferimento)
Una PINN è una rete neurale che impara a soddisfare un'equazione differenziale alle derivate parziali **senza dati sperimentali**. L'addestramento avviene minimizzando una funzione di loss che penalizza le violazioni della PDE, delle condizioni iniziali e delle condizioni al contorno in un insieme di **punti di collocazione** distribuiti nel dominio.
Vantaggi:
- Non richiede una griglia regolare
- Può essere addestrata su domini irregolari
- La soluzione è una funzione continua e differenziabile ovunque
In questo progetto la loss ha tre componenti:
1.**Residuo PDE** — la rete deve soddisfare l'equazione del calore
2.**Condizione iniziale** — la rete deve restituire `T₀` per `t = 0`
3.**Condizioni al contorno** — la rete deve soddisfare le Robin BC a `x=0` e `x=L`
### FDM (Finite Difference Method)
Il solver FDM (`fdm/`) implementa lo schema **FTCS** (Forward-Time Centered-Space) esplicito su una griglia regolare `NX × NT`. Non usa reti neurali: è una soluzione numerica classica che serve come **riferimento ad alta fedeltà** per validare la PINN.
La valutazione della PINN consiste nel calcolare l'errore relativo L2 tra la predizione della rete e la soluzione FDM interpolata sulla stessa griglia `100 × 100`.
---
## 3. Struttura del progetto
```
pinn/
├── config.py ← tutti i parametri fisici e numerici (modificare qui)
| `visualizer.py` | Plot interattivi HTML (PINN vs FDM) |
| `app.py` | Menu CLI per l'utente |
| `fdm/solver.py` | Solver numerico FTCS per la soluzione di riferimento |
| `fdm/visualizer.py`| Plot interattivi HTML (FDM standalone) |
| `fdm/app.py` | Menu CLI per il solver FDM |
---
## 4. Installazione
**Prerequisiti:** Python 3.10+, `pip`, `virtualenv` (o `venv`).
```bash
git clone <repository-url>
cd pinn
python -m venv .venv
source .venv/bin/activate # Linux / macOS
# .venv\Scripts\activate # Windows
pip install -r requirements.txt
```
Il progetto rileva automaticamente GPU/MPS/CPU all'avvio (vedi [engine.py — device detection](#12-dettagli-implementativi)).
---
## 5. Utilizzo
Attivare sempre il virtual environment prima di eseguire qualsiasi script:
```bash
source .venv/bin/activate
```
### PINN (`app.py`)
```bash
python app.py
```
```
1. Addestra nuovo modello
2. Valuta vs FDM (L2 error, max error)
3. Visualizza (genera 3 file HTML)
0. Esci
```
- **Opzione 1** — avvia l'addestramento (Adam + L-BFGS). Chiede il numero di epoche; premi Invio per usare il default (5000). Il modello migliore viene salvato in `models/best_heat_pinn_model.pth`. Per riaddestrare da zero: `rm models/best_heat_pinn_model.pth`.
- **Opzione 2** — carica il modello salvato, esegue il solver FDM, stampa l'errore relativo L2 e l'errore massimo assoluto.
- **Opzione 3** — genera tre file HTML interattivi in `results/pinn/<timestamp>/`.
### FDM (`fdm/app.py`)
```bash
python fdm/app.py
```
```
1. Risolvi (schema FTCS)
2. Visualizza (genera 3 file HTML)
0. Esci
```
- **Opzione 1** — esegue il solver e stampa shape della matrice e range di temperatura.
- **Opzione 2** — genera tre file HTML in `results/fdm/<timestamp>/`.
### Test
```bash
pytest tests/ -v # tutti i test (42)
pytest tests/test_model.py -v # rete e loss
pytest tests/test_engine_data.py -v # campionamento dati
./clear.sh # menu interattivo per eliminare models/, results/ o entrambi
```
---
## 6. Architettura della rete neurale
`HeatPINN` ([model.py](model.py)) è una rete fully connected a 5 layer:
```
Input (x, t)
│
[Linear 2→128, Tanh]
[Linear 128→128, Tanh] ×3
[Linear 128→1]
│
Output: T
```
La rete riceve le coordinate `(x, t)` e produce un unico scalare: la temperatura `T(x, t)`.
### Normalizzazione dell'input
Prima di entrare nella rete, le coordinate vengono normalizzate al range `[0, 1]`:
```python
x_norm=x/L# x ∈ [0, 1]
t_norm=t/T_END# t ∈ [0, 1]
```
Questo migliora il condizionamento numerico dell'ottimizzazione.
### Output scaling
La rete interna `net` non predice direttamente la temperatura: predice una **perturbazione adimensionale**. La temperatura fisica viene ricostruita con:
```python
T=T_AMB+(Q_VAL*L/K)*net(x_norm,t_norm)
```
dove `T_char = Q_VAL * L / K ≈ 150 °C` è la scala caratteristica di temperatura del problema.
**Perché questo scaling?**
- L'output della rete rimane nell'ordine di `[0, 1]`, rendendo il training più stabile.
- I gradienti `∂T/∂x` risultanti sono `O(1)` — la rete può imparare la struttura spaziale senza problemi di scala.
- Il termine di fondo `T_AMB` garantisce che la soluzione parta dalla condizione iniziale corretta anche con pesi random.
**Non rimuovere questo scaling**: senza di esso la rete deve imparare ordini di grandezza diversi tra le condizioni iniziali/al contorno e il gradiente interno, rendendo l'ottimizzazione molto più difficile.
---
## 7. Funzione di loss
`heat_pinn_loss()` ([model.py](model.py)) calcola quattro valori: `(total, L_pde, L_ic, L_bc)`.
```
total = W_PDE * L_pde + W_IC * L_ic + W_BC * L_bc
```
Ogni termine è **normalizzato automaticamente** da scale precompilate che dipendono solo dalle costanti in `config.py`. Cambiare `Q_VAL`, `K`, `H_CONV` o `L` ribilancia automaticamente la loss senza richiedere rituning manuale dei pesi.
### L_pde — Residuo PDE
Valutato su `N_F` punti di collocazione `(x_f, t_f)` distribuiti nel dominio:
```
residuo = dT/dt −α d²T/dx² − source(x, t)
```
Il termine sorgente usa un'approssimazione gaussiana al delta di Dirac (il delta non è differenziabile):
> **Nota sul segno:** la BC a sinistra (x=0) ha segno negativo davanti al termine convettivo perché il flusso uscente è orientato verso `−x`; a destra (x=L) il segno è positivo perché il flusso uscente è orientato verso `+x`.
---
## 8. Training
Il training è implementato in `train_model()` ([engine.py](engine.py)) e procede in due fasi.
L-BFGS è un ottimizzatore di secondo ordine (quasi-Newton) particolarmente efficace nella fase finale del training PINN perché sfrutta la curvatura della loss per convergere a minimi più precisi di quanto Adam riesca a fare.
**Meccanismo closure:** L-BFGS richiede di poter rivalutare la loss più volte per iterazione. La funzione `closure()` cattura i componenti della loss in un dizionario `_last` per poterli stampare senza ricalcolare il grafo computazionale fuori dal contesto di `backward()`.
### Campionamento dei punti di collocazione
`prepare_data()` genera i punti di collocazione con **clustering deliberato** nelle zone fisicamente più complesse:
Condizione necessaria per la stabilità dello schema esplicito:
```
r = α·dt/dx² ≤ 0.5
```
Se la condizione è violata, il solver stampa un avvertimento ma non si blocca. Con i parametri di default (`NX=250`, `NT=15000`) la condizione è soddisfatta. Se si riducono `NX` o `NT`, verificare che `r ≤ 0.5`.
### Condizioni al contorno Robin
Le BC sono applicate a ogni passo temporale usando uno schema centrato:
> **Se la loss diverge:** verificare che `T_char = Q_VAL * L / K` non sia vicino a zero. Questo valore è la scala caratteristica di temperatura usata per normalizzare tutti i termini.
---
## 12. Test
La test suite è in `tests/` e conta **42 test** organizzati in tre livelli:
pytest tests/ -k "model"# solo test con "model" nel nome
pytest tests/ --tb=short # traceback breve in caso di fallimento
```
---
## Dettagli implementativi
### Normalizzazione automatica della loss
Le scale sono precompilate una sola volta a import time in `model.py`:
```python
_T_char=Q_VAL*L/K# ~150 °C
_src_peak=ALPHA*Q_VAL/(K*GAUSS_SIGMA*sqrt(2π))
_pde_scale=max((_T_char/T_END)²,_src_peak²)+1e-8
_bc_scale=max(Q_VAL/K,H_CONV*_T_char/K)**2
```
Dividere ogni termine per la sua scala porta tutti i contributi a `O(1)`, rendendo i pesi `W_PDE`, `W_IC`, `W_BC` interpretabili come importanza relativa piuttosto che come fattori di scala assoluti.
### Rilevamento device
`engine.py` seleziona automaticamente il device più performante disponibile:
```python
CUDA→MPS(AppleSilicon)→CPU
```
Include un test di funzionamento effettivo della GPU prima di usarla, per evitare fallimenti silenziosi su driver incompleti.
### Closure L-BFGS
L-BFGS richiede una funzione `closure()` che esegue `zero_grad`, forward pass, `backward`, e restituisce la loss. I componenti della loss vengono catturati in un dizionario `_last` per permettere il logging a ogni step senza ricalcolare il grafo fuori dal contesto `backward`.
### Subsampling delle animazioni FDM
Se `NT > 200`, il visualizer FDM campiona ogni `n`-esimo frame (`n = NT // 200`) per mantenere le animazioni HTML leggere e fluide.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.