Rimuove il modello ai volumi finiti lasciando la sola analisi FEM
Il progetto conteneva due discretizzazioni indipendenti della stessa fisica: i volumi finiti sulla sezione x-z di simulate.py (con generazione del dataset CSV e le sue animazioni 2D/3D) e gli elementi finiti sulla mesh shell di fem.py. Resta solo il secondo, che risolve la circonferenza invece di collassarla in un'attenuazione gaussiana. Eliminati simulate.py, plot_csv.py, plot_animazione.py e plot_animazione_3d.py. Il FEM dipendeva da simulate.py per la cinematica del gruppo di sorgenti e per la configurazione del run, quindi fem.py assorbe _x_riferimento_iniziale_m, _x_riferimento_finale_m, _intervallo_attivo e configurazione_randomizzata; la lettura del sensore (inerzia, rumore, quantizzazione) è ora applicata dentro simula_campo_fem e la skin depth resta calcolata dalla frequenza dell'induttore come sola grandezza diagnostica, dato che nel modello shell lo spessore è collassato. config.py perde i parametri che servivano solo ai volumi finiti (griglia n_nodi_x/n_nodi_z, campionamento e cartella del dataset, numero di processi) e guadagna FEM["durata_s"], FEM["seed"] e USCITA["cartella"]: le immagini e le animazioni vanno in output/ invece che in dataset/. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-26
@@ -1,17 +1,15 @@
|
||||
# Animazione 3D isometrica del barattolo con il campo di temperatura calcolato
|
||||
# Animazione 3D isometrica della fascetta con il campo di temperatura calcolato
|
||||
# a elementi finiti sulla mesh shell (fem.py).
|
||||
#
|
||||
# È l'equivalente di plot_animazione_3d.py ma senza ricostruzioni: lì il campo
|
||||
# circonferenziale era estruso riusando la gaussiana della sorgente, qui la
|
||||
# coordinata circonferenziale è una direzione risolta del modello FEM, quindi
|
||||
# la temperatura dipinta su ogni elemento è quella effettivamente calcolata.
|
||||
# La coordinata circonferenziale è una direzione risolta del modello FEM,
|
||||
# quindi la temperatura dipinta su ogni elemento è quella effettivamente
|
||||
# calcolata, senza ricostruzioni.
|
||||
#
|
||||
# Ogni elemento shell è disegnato come faccia piana con colore pari alla media
|
||||
# dei suoi quattro valori nodali. L'ombreggiatura è calcolata a mano dalla
|
||||
# normale radiale dell'elemento: Poly3DCollection ombreggia solo alla
|
||||
# creazione, mentre qui i colori delle facce cambiano a ogni fotogramma.
|
||||
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
@@ -21,11 +19,13 @@ from matplotlib import cm
|
||||
from matplotlib.animation import FuncAnimation, PillowWriter
|
||||
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
|
||||
|
||||
from config import SIMULAZIONE
|
||||
from config import USCITA
|
||||
from fem import simula_campo_fem
|
||||
from mesh import genera_mesh, riepilogo_mesh
|
||||
from plot_animazione import T_FINE_ANIMAZIONE_S, T_INIZIO_ANIMAZIONE_S
|
||||
from simulate import configurazione_randomizzata
|
||||
|
||||
# Istante del primo fotogramma mostrato: i primi istanti sono ancora uniformi
|
||||
# alla temperatura ambiente.
|
||||
T_INIZIO_ANIMAZIONE_S = 0.40
|
||||
|
||||
# Tempo simulato tra un fotogramma e il successivo.
|
||||
DT_FRAME_S = 0.15
|
||||
@@ -65,16 +65,8 @@ def main() -> None:
|
||||
mesh_dati = genera_mesh()
|
||||
print(riepilogo_mesh(mesh_dati))
|
||||
|
||||
rng = random.Random(SIMULAZIONE["seed"])
|
||||
cfg_run = configurazione_randomizzata(1, rng)
|
||||
|
||||
print("Integrazione FEM in corso...")
|
||||
dati = simula_campo_fem(
|
||||
cfg_run,
|
||||
durata_s=T_FINE_ANIMAZIONE_S,
|
||||
dt_frame_s=DT_FRAME_S,
|
||||
mesh_dati=mesh_dati,
|
||||
)
|
||||
dati = simula_campo_fem(dt_frame_s=DT_FRAME_S, mesh_dati=mesh_dati)
|
||||
|
||||
tempi = dati["tempi"]
|
||||
indice_inizio = int(np.searchsorted(tempi, T_INIZIO_ANIMAZIONE_S))
|
||||
@@ -84,7 +76,8 @@ def main() -> None:
|
||||
T_max = max(campo.max() for campo in dati["campi"])
|
||||
print(
|
||||
f"T massima nodale: {T_max:.1f} °C — "
|
||||
f"T massima nel punto del sensore: {dati['T_vere'].max():.1f} °C"
|
||||
f"T massima nel punto del sensore: {dati['T_vere'].max():.1f} °C\n"
|
||||
f"Skin depth (diagnostica): {1000.0 * dati['skin_depth_m']:.3f} mm"
|
||||
)
|
||||
|
||||
elementi = mesh_dati["elementi"]
|
||||
@@ -133,17 +126,17 @@ def main() -> None:
|
||||
|
||||
# Pannello inferiore: storia della temperatura nel punto osservato dal
|
||||
# sensore. T_vere è il valore nodale calcolato dal FEM, T_lette lo stesso
|
||||
# segnale filtrato dall'inerzia del primo ordine del sensore.
|
||||
# Con la costante di tempo del sensore le due curve sono quasi
|
||||
# sovrapposte: la prima è tracciata spessa e trasparente perché la seconda,
|
||||
# tratteggiata, resti leggibile sopra di essa.
|
||||
# segnale degradato dal sensore reale (inerzia, rumore, quantizzazione).
|
||||
# A parte il rumore le due curve sono quasi sovrapposte: la prima è
|
||||
# tracciata spessa e trasparente perché la seconda resti leggibile sopra
|
||||
# di essa.
|
||||
linea_vera, = ax_storia.plot(
|
||||
[], [], color="tab:blue", linewidth=3.0, alpha=0.4,
|
||||
label="T nodo del sensore (FEM)",
|
||||
)
|
||||
linea_letta, = ax_storia.plot(
|
||||
[], [], color="tab:red", linewidth=1.2, linestyle="--",
|
||||
label="T misurata dal sensore (con inerzia)",
|
||||
[], [], color="tab:red", linewidth=1.0,
|
||||
label="T misurata dal sensore (inerzia + rumore + quantizzazione)",
|
||||
)
|
||||
cursore = ax_storia.axvline(tempi[indice_inizio], color="gray", linewidth=0.8)
|
||||
ax_storia.set_xlim(0.0, tempi[-1])
|
||||
@@ -189,7 +182,7 @@ def main() -> None:
|
||||
)
|
||||
|
||||
if matplotlib.get_backend().lower() == "agg":
|
||||
cartella = Path("dataset")
|
||||
cartella = Path(USCITA["cartella"])
|
||||
cartella.mkdir(parents=True, exist_ok=True)
|
||||
percorso = cartella / "animazione_fem.gif"
|
||||
animazione.save(
|
||||
|
||||
Reference in New Issue
Block a user