# Analisi Termica FEM — Fascetta, Sorgenti a Induzione Mobili, Sensore IR Fisso Questo progetto calcola il transitorio termico di una fascetta (anello cilindrico sottile) riscaldata da un gruppo di sorgenti a induzione in movimento, osservata da un sensore a infrarossi fisso. La fascetta è discretizzata con una mesh a elementi shell quadrangolari e il campo di temperatura è risolto a elementi finiti, poi visualizzato in un'animazione 3D isometrica o esportato in CSV come serie temporale (temperatura vera della parete e lettura del sensore). ## Geometria La fascetta è un anello cilindrico definito da tre dimensioni: - **diametro** (default 70 mm) — il diametro esterno del cilindro; - **spessore** (default 0.18 mm) — lo spessore della parete; - **lunghezza** (default 100 mm) — l'estensione assiale lungo `x`. Il dominio discretizzato è la **superficie media** del cilindro (raggio = (diametro − spessore)/2). Lo spessore non è discretizzato: è un attributo degli elementi shell. ```text sorgenti (induttori), in moto lungo x sulla superficie esterna ▼ ▼ ▼ ┌───────────────────────────────────┐ x = 0 │ superficie media del cilindro │ x = lunghezza └───────────────────────────────────┘ ┆ linea di vista ▲ sensore IR (fisso, x = 50 mm, theta = 0, a 10 mm dalla parete interna) ``` - **x** = asse del cilindro, da `0` a `lunghezza`. - **theta** = coordinata circonferenziale, con `theta = 0` sul piano del sensore; nelle formule si usa la lunghezza d'arco `s = R·theta`. - Coordinate globali per la visualizzazione: `x`, `y = R·sin(theta)`, `z = R·cos(theta)`. Il **sensore** è un pirometro a infrarossi posto all'interno della fascetta, a una distanza configurabile dalla parete interna (default 10 mm). Essendo senza contatto, la distanza non influenza la misura: il sensore legge la temperatura della parete nel nodo più vicino a `x = 50 mm` sul piano `theta = 0`. ## Modello fisico L'equazione risolta è quella del calore per unità di superficie media: ```text rho·cp·t ∂T/∂t = k·t ∇²T + q(x, s, t) − (h_est + h_int)·(T − T_amb) ``` con `t` = spessore della parete e `∇²` il laplaciano nel piano `(x, s)`. 1. **Collasso dello spessore** — la temperatura è uniforme attraverso la parete. Con spessore 0.18 mm il numero di Biot `h·t/k` vale ~1e-7 e il tempo di diffusione attraverso la parete `t²/alpha` è di pochi millisecondi, molto più rapido del transito delle sorgenti. Di conseguenza la profondità di penetrazione del riscaldamento a induzione non entra nel modello: conta solo il flusso totale assorbito per unità di superficie. La skin depth `δ = √(2ρₑ/(ωμ))` è comunque calcolata dalla frequenza dell'induttore (`frequenza_hz`, oppure imposta con `skin_depth_fissa_m`) e riportata come grandezza diagnostica: per la banda stagnata a 20 kHz risulta ≈ 0.14 mm, confrontabile con lo spessore, il che conferma che la parete è quasi isoterma attraverso lo spessore. 2. **Sorgenti gaussiane in moto** — ogni sorgente ha un'impronta gaussiana isotropa nel piano `(x, s)` di raggio `sigma_punto_m`. Un gruppo di `numero_sorgenti` sorgenti equidistanti (`distanza_sorgenti_m`) si muove rigidamente a velocità costante: `q(x, s, t) = Σᵢ q_picco · efficienza · exp(-((x - xᵢ(t))² + (s - s₀)²) / (2σ²))`. La distanza circonferenziale è valutata sull'immagine più vicina, perché la superficie è chiusa. L'offset `s₀` del percorso rispetto al piano del sensore è `offset_y_percorso_m`. 3. **Scambi con l'ambiente** — convezione sulla faccia esterna (`h_esterno_W_m2K`) e su quella interna (`h_interno_W_m2K`) su tutta la superficie, più convezione sui due bordi anulari `x = 0` e `x = lunghezza` (`h_bordi_W_m2K`, su un'area pari a spessore × perimetro). 4. **Temperatura iniziale** — il campo parte uniformemente alla temperatura ambiente dell'analisi (randomizzabile). 5. **Sensore reale** — la lettura aggiunge alla temperatura vera della parete nel punto osservato: inerzia del primo ordine (`costante_tempo_s`), rumore gaussiano (`rumore_std_C`) e quantizzazione (`quantizzazione_C`), gli ultimi due applicati a ogni campionamento. 6. **Randomizzazione** — con `RANDOMIZZAZIONE["abilitata"]` ogni analisi perturba velocità, flusso di picco, sigma dello spot, offset y del percorso, temperatura ambiente e rumore del sensore con estrazioni gaussiane/uniformi da un RNG con seed fisso (`FEM["seed"]`): analisi diverse dello stesso setup differiscono come differirebbero due passaggi reali, restando riproducibili. Irraggiamento non modellato e proprietà dei materiali costanti con la temperatura: a ~210 °C le perdite radiative non sono del tutto trascurabili rispetto alla convezione, e per gli acciai ferromagnetici la skin depth reale varia fortemente con temperatura e campo (punto di Curie non modellato). ## Metodo numerico - **Mesh** (`mesh.py`): griglia strutturata `n_elementi_x × n_elementi_circonferenza` di quadrilateri a 4 nodi (default 40 × 48), chiusa lungo la circonferenza senza nodi duplicati sulla cucitura. - **Elementi**: shell bilineari a 4 nodi. Tutti gli elementi sono rettangoli identici `dx × ds`, quindi le matrici di elemento (massa e rigidezza) sono calcolate una volta sola e assemblate in forma vettorizzata. La matrice di massa di superficie è riusata per capacità termica, convezione sulle facce e carico della sorgente; la convezione sui bordi anulari usa una matrice di massa 1D sull'anello di nodi. - **Integrazione temporale**: Eulero implicito con passo `FEM["dt_s"]` (default 1 ms). Tutti i termini sono lineari e costanti, quindi la matrice di sistema è **assemblata e fattorizzata LU una sola volta** (`scipy.sparse.linalg.splu`) in `prepara_stato_fem`; ogni passo assembla solo il termine noto e risolve i sistemi triangolari. - **Flusso nodale**: l'impronta gaussiana isotropa è separabile, `q(x, s) = q_x(x) · q_s(s)`, e il fattore circonferenziale è costante nel tempo (precalcolato). A ogni passo si valuta quindi solo il fattore assiale sui `n_x + 1` nodi distinti in x, espanso con un prodotto esterno. - Se in futuro si introducessero proprietà dipendenti dalla temperatura, la matrice andrebbe ricostruita e rifattorizzata a ogni passo. ## File ```text main.py punto di ingresso da riga di comando config.py tutti i parametri (geometria, mesh, FEM, aria, sorgente, sensore) materials.py proprietà termofisiche ed elettriche dei materiali mesh.py generazione della mesh a elementi shell fem.py solutore termico transitorio a elementi finiti plot_mesh.py disegno della sola mesh plot_animazione_fem.py animazione 3D del campo di temperatura esporta_csv.py export CSV della serie temporale output/ immagini e GIF salvate quando il backend non è interattivo output/csv/ CSV esportati (ricreata a ogni esecuzione) ``` ## Installazione ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` ## Uso ```bash python main.py # elenco delle azioni disponibili python main.py mesh # disegna la sola mesh a elementi shell python main.py fem # integra il campo FEM e lo anima in 3D python main.py csv # esporta la serie temporale in output/csv/ ``` Ogni modulo resta eseguibile anche direttamente (`python plot_mesh.py`, `python plot_animazione_fem.py`, `python esporta_csv.py`). Gli script aprono finestre interattive (backend Qt); se il backend non è interattivo (es. sessione senza display) salvano automaticamente PNG/GIF in `output/`. L'animazione FEM mostra due pannelli: la fascetta in vista isometrica con il campo di temperatura dipinto sugli elementi shell e i marker delle sorgenti in transito, e la storia della temperatura nel punto osservato dal sensore (valore nodale vero e lettura del sensore reale). Finestra temporale iniziale e cadenza dei fotogrammi si regolano con le costanti in testa a `plot_animazione_fem.py`; la durata simulata è `FEM["durata_s"]`. ## Output CSV `python main.py csv` cancella e ricrea `output/csv/`, poi esporta `FEM["num_run"]` analisi (una per file `run_XXXX.csv`) più un `metadata.csv` con una riga per analisi. Le righe sono campionate a `FEM["frequenza_campionamento_hz"]`, indipendente dal passo di integrazione. ### `output/csv/run_XXXX.csv` — serie temporale | Colonna | Significato | |-------------------------|-----------------------------------------------------------------| | `id_run` | identificativo dell'analisi | | `tempo_s` | tempo simulato | | `x_sorgente_m` | posizione della sorgente di riferimento del gruppo | | `T_vera_sensore_C` | temperatura vera della parete nel punto osservato (valore nodale FEM) | | `T_misurata_sensore_C` | lettura del sensore (inerzia + rumore + quantizzazione) | | `T_massima_fascetta_C` | temperatura nodale massima su tutta la fascetta in quell'istante | | `T_ambiente_C` | temperatura ambiente dell'analisi | | `offset_y_sorgente_m`, `velocita_m_s`, `sigma_punto_m`, `flusso_picco_W_m2` | parametri effettivi dell'analisi (variano se la randomizzazione è attiva) | | `skin_depth_m` | skin depth diagnostica | | `materiale` | chiave del materiale | ### `output/csv/metadata.csv` — una riga per analisi Tutti i parametri effettivi (geometria, mesh, coefficienti di scambio, sorgenti, sensore) e i valori di picco: `T_vera_max_sensore_C`, `T_misurata_max_sensore_C`, `T_massima_fascetta_C`. ## Configurazione Tutto si modifica in `config.py`. I dizionari principali: | Dizionario | Contenuto | |-------------|---------------------------------------------------------------------------| | `FASCETTA` | diametro, lunghezza, spessore, materiale | | `MESH` | numero di elementi shell lungo x e lungo la circonferenza | | `FEM` | passo temporale, durata, seed, campionamento CSV, numero di analisi | | `ARIA` | temperatura ambiente e coefficienti di convezione (facce e bordi) | | `SORGENTE` | corsa, velocità (il segno dà il verso), gruppo di sorgenti, gaussiana, flusso, frequenza | | `SENSORE` | posizione (x e distanza dalla parete), inerzia, rumore, quantizzazione | | `RANDOMIZZAZIONE` | entità delle perturbazioni per analisi | | `USCITA` | cartella per immagini e animazioni salvate | Punti da conoscere: - **Corsa delle sorgenti**: `x_inizio_m` è la distanza dal sensore, all'inizio della corsa, della sorgente più avanzata (quella che lo raggiunge per prima); `x_fine_m` è la distanza dal sensore, a fine corsa, della sorgente più arretrata (quella che lo supera per ultima). Con `zero_dopo_fine` ogni sorgente si spegne alla fine della propria corsa. - **Materiale**: `FASCETTA["materiale"]` deve essere una chiave di `MATERIALI` in `materials.py`. Per aggiungere un materiale basta una nuova voce nel dizionario (conducibilità termica, densità, calore specifico, resistività elettrica, permeabilità relativa). - **Proprietà di `banda_stagnata`**: la banda stagnata è un nastro di acciaio a basso tenore di carbonio (0,15–0,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).