From 4369d2799f730dcdc504e964a1b40a066b7bec4b Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Sun, 5 Jul 2026 22:00:06 +0200 Subject: [PATCH] Deriva la conduzione y da un'equazione di aletta sull'intero cilindro Sostituisce il parametro lunghezza_conduzione_y_mm con un termine q_y = -(h_esterno + h_interno)/spessore * (T - T_amb) ricavato dal bilancio sull'intero volume cilindrico, eliminando un parametro libero. Riduce anche l'inerzia del sensore IR a 0.01 s (pirometro fast-response). Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 2 +- README.md | 8 +++++--- config.py | 23 +++++++++++------------ plot_animazione.py | 6 +++--- simulate.py | 18 ++++++++++-------- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f07e41f..8770517 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,7 +48,7 @@ Generatore di dataset per misurazioni termiche pseudo-realistiche di una fascett - Le sorgenti gaussiane in movimento producono un profilo di flusso termico superficiale q(x) sul lato esterno, variabile nel tempo (`profilo_flusso_incidente_W_m2`) - Quel flusso è ridistribuito volumetricamente attraverso lo spessore con decadimento esponenziale in z (`profilo_deposizione_z_1_m`): q_vol(x, z) = q(x) · p(z) - Uno schema 2D a volumi finiti con Eulero implicito integra l'equazione del calore su `n_nodi_x × n_nodi_z` celle: `prepara_stato_termico` costruisce griglia, coefficienti e matrice sparsa fattorizzata LU una volta per run (`costruisci_solutore_implicito_2d`, che restituisce l'oggetto `splu`), poi `passo_implicito` avanza il campo risolvendo solo il sistema triangolare -- Le condizioni al contorno sono incorporate nella matrice: convezione su tutti e quattro i lati della sezione, più un termine di conduzione circonferenziale (y) verso il resto della fascetta assunto a temperatura ambiente (q_y = -k·(T - T_amb)/L_y², con L_y = `lunghezza_conduzione_y_mm`) +- Le condizioni al contorno sono incorporate nella matrice: convezione su tutti e quattro i lati della sezione, più un termine di conduzione circonferenziale (y) verso il resto della fascetta assunto a temperatura ambiente. Il termine è un'equazione di aletta ricavata sull'intero volume del cilindro: il calore conduce lungo y attraverso l'intero spessore mentre le superfici esterna e interna dell'intero cilindro scambiano per convezione, dando q_y = -(h_esterno + h_interno)/spessore · (T - T_amb), senza parametri di conduzione y configurabili a parte - La temperatura iniziale del campo è la temperatura ambiente (randomizzata per run) - L'output del sensore aggiunge inerzia del primo ordine, rumore gaussiano e quantizzazione diff --git a/README.md b/README.md index b66ac3f..d451704 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,11 @@ pratico di dataset. La catena di approssimazioni è la seguente. (`h_interno_W_m2K`) e sui due bordi in x (`h_bordi_W_m2K`); - **conduzione circonferenziale**: la sezione cede calore per conduzione lungo `y` al resto della fascetta, assunto a temperatura ambiente. Il termine è - `q_y = -k·(T - T_amb)/L_y²` con `L_y = lunghezza_conduzione_y_mm`, la distanza - caratteristica su cui si sviluppa il gradiente circonferenziale (valore più - piccolo = pozzo termico più aggressivo). + un'equazione di aletta ricavata sull'intero volume del cilindro: il calore + conduce lungo `y` attraverso l'intero spessore mentre le superfici esterna e + interna dell'intero cilindro perdono calore per convezione, dando + `q_y = -(h_esterno + h_interno)/spessore · (T - T_amb)` — nessun parametro di + conduzione `y` aggiuntivo da configurare. 5. **Temperatura iniziale** — il campo parte uniformemente alla temperatura ambiente del run (che è randomizzata, quindi varia run per run). diff --git a/config.py b/config.py index 5eececb..7dfbc89 100644 --- a/config.py +++ b/config.py @@ -15,8 +15,12 @@ # - Il riscaldamento a induzione è approssimato come riscaldamento volumetrico # che decade esponenzialmente con la profondità z secondo la skin depth. # - La sezione scambia per convezione con l'aria su tutti e quattro i lati -# (esterno, interno e i due bordi in x) e per conduzione lungo y con il -# resto della fascetta, assunto a temperatura ambiente. +# (esterno, interno e i due bordi in x). Scambia inoltre per conduzione +# lungo y con il resto della fascetta, assunto a temperatura ambiente: +# il calore conduce attraverso l'intero volume dello spessore mentre le +# superfici esterna e interna dell'intero cilindro perdono calore per +# convezione (equazione dell'aletta), derivato da h_esterno, h_interno e +# spessore_mm senza parametri di conduzione y aggiuntivi. # - La temperatura iniziale della fascetta è pari alla temperatura ambiente. # - Il sensore è un pirometro a infrarossi posto all'interno della fascetta, # a distanza "distanza_parete_mm" dalla parete interna: misura senza @@ -31,7 +35,7 @@ SIMULAZIONE = { # Numero di file CSV da generare. - "num_run": 2, + "num_run": 1, # Tempo simulato totale. "durata_s": 30.0, @@ -59,7 +63,7 @@ FASCETTA = { "lunghezza_mm": 100.0, # Spessore della parete [mm]. - "spessore_mm": 0.12, + "spessore_mm": 0.18, # Numero di celle del volume finito lungo x (lunghezza). "n_nodi_x": 100, @@ -68,11 +72,6 @@ FASCETTA = { # Più nodi = maggiore risoluzione spaziale, simulazione più lenta. "n_nodi_z": 15, - # Distanza caratteristica lungo y (direzione circonferenziale) su cui la - # sezione scambia calore per conduzione con il resto della fascetta, - # assunto a temperatura ambiente. - "lunghezza_conduzione_y_mm": 25.0, - # Deve corrispondere a una chiave in materials.py. "materiale": "banda_stagnata", } @@ -106,14 +105,14 @@ SORGENTE = { "offset_y_percorso_m": 0.0, # Velocità delle sorgenti lungo x. Il segno indica il verso di marcia. - "velocita_m_s": -2.0, + "velocita_m_s": -1.0, # Numero di sorgenti equidistanti che si muovono insieme come un gruppo # rigido (stessa velocità, sigma, flusso ed efficienza). "numero_sorgenti": 3, # Distanza lungo x tra sorgenti consecutive del gruppo. - "distanza_sorgenti_m": 0.5, + "distanza_sorgenti_m": 1.0, # Raggio del punto gaussiano. Valore più alto = impronta termica più larga. "sigma_punto_m": 0.012, @@ -146,7 +145,7 @@ SENSORE = { # Inerzia del sensore del primo ordine. # Valore più alto = risposta del sensore più lenta. - "costante_tempo_s": 0.05, + "costante_tempo_s": 0.01, # Deviazione standard del rumore gaussiano. "rumore_std_C": 0.15, diff --git a/plot_animazione.py b/plot_animazione.py index be58e21..8b63413 100644 --- a/plot_animazione.py +++ b/plot_animazione.py @@ -25,13 +25,13 @@ from simulate import ( T_INIZIO_ANIMAZIONE_S = 0.40 # Istante di fine dell'animazione. -T_FINE_ANIMAZIONE_S = 20 +T_FINE_ANIMAZIONE_S = 30 # Tempo simulato tra un fotogramma e il successivo. -DT_FRAME_S = 0.001 +DT_FRAME_S = 0.05 # Millisecondi tra i fotogrammi in riproduzione. -INTERVALLO_RIPRODUZIONE_MS = 1 +INTERVALLO_RIPRODUZIONE_MS = 30 def simula_campi(cfg_run: dict) -> dict: diff --git a/simulate.py b/simulate.py index 5e9b8f2..1cac1a5 100644 --- a/simulate.py +++ b/simulate.py @@ -140,11 +140,11 @@ def costruisci_solutore_implicito_2d( dt_s: float, dx_m: float, dz_m: float, + spessore_m: float, materiale: dict, h_esterno_W_m2K: float, h_interno_W_m2K: float, h_bordi_W_m2K: float, - lunghezza_conduzione_y_m: float, ): # Costruisce e fattorizza (LU sparsa) la matrice A per Eulero implicito 2D: # A * T_next = rhs @@ -153,8 +153,12 @@ def costruisci_solutore_implicito_2d( # appiattiti in ordine C (indice = i * n_z + j). Tutti e quattro i lati # della sezione includono la convezione verso l'ambiente; su ogni cella # agisce inoltre la conduzione circonferenziale (y) verso il resto della - # fascetta, assunto a temperatura ambiente e modellato come - # q_y = -k * (T - T_amb) / L_y². + # fascetta, assunto a temperatura ambiente. Il termine è ricavato + # considerando l'intero volume della fascia cilindrica (equazione + # dell'aletta): il calore conduce lungo y attraverso l'intero spessore + # mentre le superfici esterna e interna dell'intero cilindro scambiano + # per convezione, dando un sink distribuito uniformemente su ogni cella + # q_y = -(h_esterno + h_interno) / spessore * (T - T_amb). k = materiale["conducibilita_termica_W_mK"] rho = materiale["densita_kg_m3"] cp = materiale["calore_specifico_J_kgK"] @@ -165,7 +169,7 @@ def costruisci_solutore_implicito_2d( b_esterno = h_esterno_W_m2K * dt_s / (rho * cp * dz_m) b_interno = h_interno_W_m2K * dt_s / (rho * cp * dz_m) b_bordo = h_bordi_W_m2K * dt_s / (rho * cp * dx_m) - c_y = alpha * dt_s / (lunghezza_conduzione_y_m * lunghezza_conduzione_y_m) + c_y = (h_esterno_W_m2K + h_interno_W_m2K) * dt_s / (rho * cp * spessore_m) n = n_x * n_z scambio = np.full(n, c_y) @@ -194,7 +198,6 @@ def prepara_stato_termico(fascetta: dict, aria: dict, sorgente: dict) -> dict: n_z = fascetta["n_nodi_z"] dx = lunghezza / n_x dz = spessore / n_z - lunghezza_y = fascetta["lunghezza_conduzione_y_mm"] / 1000.0 dt = SIMULAZIONE["dt_interno_s"] @@ -224,7 +227,7 @@ def prepara_stato_termico(fascetta: dict, aria: dict, sorgente: dict) -> dict: "b_esterno": aria["h_esterno_W_m2K"] * dt / (rho * cp * dz), "b_interno": aria["h_interno_W_m2K"] * dt / (rho * cp * dz), "b_bordo": aria["h_bordi_W_m2K"] * dt / (rho * cp * dx), - "c_y": alpha * dt / (lunghezza_y * lunghezza_y), + "c_y": (aria["h_esterno_W_m2K"] + aria["h_interno_W_m2K"]) * dt / (rho * cp * spessore), "T_ambiente_C": aria["temperatura_ambiente_C"], "solutore": costruisci_solutore_implicito_2d( n_x=n_x, @@ -232,11 +235,11 @@ def prepara_stato_termico(fascetta: dict, aria: dict, sorgente: dict) -> dict: dt_s=dt, dx_m=dx, dz_m=dz, + spessore_m=spessore, materiale=materiale, h_esterno_W_m2K=aria["h_esterno_W_m2K"], h_interno_W_m2K=aria["h_interno_W_m2K"], h_bordi_W_m2K=aria["h_bordi_W_m2K"], - lunghezza_conduzione_y_m=lunghezza_y, ), } @@ -409,7 +412,6 @@ def simula_singolo(cfg_run: dict, output_csv: Path, rng: random.Random) -> dict: "diametro_m": fascetta["diametro_mm"] / 1000.0, "lunghezza_m": fascetta["lunghezza_mm"] / 1000.0, "spessore_m": fascetta["spessore_mm"] / 1000.0, - "lunghezza_conduzione_y_m": fascetta["lunghezza_conduzione_y_mm"] / 1000.0, "n_nodi_x": n_x, "n_nodi_z": n_z, "durata_s": durata,