Nuova azione `python main.py csv`: esporta_csv.py integra il campo FEM e scrive output/csv/run_XXXX.csv con la temperatura vera della parete nel punto osservato, la lettura del sensore reale e il massimo nodale istante per istante, più un metadata.csv con una riga per analisi. La cartella è ricreata a ogni esecuzione. Le righe sono campionate a FEM["frequenza_campionamento_hz"], indipendente dal passo di integrazione, e FEM["num_run"] analisi vengono esportate di seguito: con la randomizzazione abilitata ognuna ha parametri diversi ma riproducibili dallo stesso seed. simula_campo_fem accetta salva_campi=False, perché all'export servono solo le serie scalari e non i campi nodali di ogni campione, e restituisce anche la temperatura nodale massima per campione. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
228 lines
13 KiB
Markdown
228 lines
13 KiB
Markdown
# 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).
|