Compare commits

...

3 Commits

Author SHA1 Message Date
davide 4d83ac4e70 Allinea le proprietà della banda stagnata all'acciaio dolce reale
Il rivestimento di stagno è troppo sottile per influire sulle proprietà
in massa: la banda stagnata è fisicamente acciaio a basso tenore di
carbonio, quindi ne eredita conducibilità, densità, calore specifico,
resistività e permeabilità. Ricalibra il flusso di picco della sorgente
di conseguenza e documenta le fonti in README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 22:37:21 +02:00
davide a04465a674 Calibra sorgente e disabilita randomizzazione per validare contro dati reali
Riduce il flusso di picco e riavvicina la sorgente per riprodurre il
picco e il decadimento osservati nel pirometro reale, mantenendo
velocità e distanza tra sorgenti invariate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 22:25:12 +02:00
davide 4369d2799f 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 <noreply@anthropic.com>
2026-07-05 22:00:06 +02:00
6 changed files with 47 additions and 35 deletions
+1 -1
View File
@@ -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
+14 -3
View File
@@ -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).
@@ -162,6 +164,15 @@ Punti da conoscere:
- **Materiale**: `FASCETTA["materiale"]` deve essere una chiave di `MATERIALI` in
`materials.py`. Per aggiungere un materiale basta una nuova voce nel dizionario
(conducibilità, densità, calore specifico, resistività elettrica, permeabilità).
- **Proprietà di `banda_stagnata`**: la banda stagnata è un nastro di acciaio a basso
tenore di carbonio (0,150,25% C) rivestito su entrambe le facce da un sottile
strato di stagno elettrolitico, dello spessore di pochi micrometri — trascurabile
rispetto allo spessore tipico della parete (es. 0,18 mm) e quindi ininfluente sulle
proprietà termiche, elettriche e magnetiche in massa. I valori in `materials.py`
sono quindi quelli dell'acciaio dolce sottostante:
[Banda stagnata: tutto quello che c'è da sapere (MUNDOLATAS)](https://mundolatas.com/it/banda-stagnata-tutto-quello-che-ce-da-sapere/),
[Bande stagnate elettrolitiche (EUROPERF)](https://www.europerf.it/it/banda-stagnata-elettrolitica.php),
[Differenza tra banda stagnata e acciaio inossidabile (Wuxi Bright Packing)](https://it.brightmetalcan.com/info/difference-between-tinplate-and-stainless-stee-48700260.html).
- **Randomizzazione**: ogni run perturba velocità, flusso di picco, sigma, offset y,
temperatura ambiente e rumore del sensore con estrazioni da un RNG a seed fisso
(`SIMULAZIONE["seed"]`): il dataset è riproducibile.
+14 -15
View File
@@ -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",
}
@@ -94,7 +93,7 @@ ARIA = {
SORGENTE = {
# Distanza dal sensore, all'inizio della corsa, della sorgente del
# gruppo più vicina al sensore (quella che lo raggiunge per prima).
"x_inizio_m": 1.0,
"x_inizio_m": 5.0,
# Distanza dal sensore, alla fine della corsa, della sorgente del
# gruppo più lontana dal sensore (quella che lo supera per ultima).
@@ -106,20 +105,20 @@ 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,
# Flusso termico incidente massimo prima della correzione per efficienza.
"flusso_termico_picco_W_m2": 5500000.0,
"flusso_termico_picco_W_m2": 4434000.0,
# Frazione del flusso incidente che diventa effettivamente calore nella fascetta.
"efficienza_riscaldamento": 0.35,
@@ -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,
@@ -159,7 +158,7 @@ SENSORE = {
RANDOMIZZAZIONE = {
# Se abilitata, ogni run varia leggermente alcuni parametri.
"abilitata": True,
"abilitata": False,
# Deviazioni standard relative.
"velocita_std_rel": 0.007,
+5 -5
View File
@@ -37,10 +37,10 @@ MATERIALI = {
"permeabilita_relativa": 1.0,
},
"banda_stagnata": {
"conducibilita_termica_W_mK": 60.0,
"densita_kg_m3": 7860.0,
"calore_specifico_J_kgK": 460.0,
"resistivita_elettrica_ohm_m": 1.2e-7,
"permeabilita_relativa": 150.0,
"conducibilita_termica_W_mK": 54.0,
"densita_kg_m3": 7850.0,
"calore_specifico_J_kgK": 470.0,
"resistivita_elettrica_ohm_m": 1.6e-7,
"permeabilita_relativa": 100.0,
},
}
+3 -3
View File
@@ -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:
+10 -8
View File
@@ -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,