temp
This commit is contained in:
1 parent
560c6a6a62
commit
7883f3dead
13 files changed
+1460
-488
No files matched your search
+37
-47
@@ -1,26 +1,26 @@
|
||||
# Animazione 3D del barattolo in vista isometrica: colormap della temperatura
|
||||
# sulla superficie esterna.
|
||||
# sulla superficie cilindrica.
|
||||
#
|
||||
# Il modello risolve solo la sezione x-z (vedi CLAUDE.md): la coordinata
|
||||
# circonferenziale y non è simulata, è collassata in un'attenuazione
|
||||
# gaussiana del flusso. Per estrudere il campo attorno alla circonferenza si
|
||||
# riusa la stessa gaussiana: la sovratemperatura rispetto al piano y=0 (dove
|
||||
# si trova il sensore) viene scalata in funzione della distanza
|
||||
# circonferenziale dal percorso delle sorgenti. È quindi una ricostruzione
|
||||
# visiva, non un calcolo di diffusione in y.
|
||||
# Il modello shell risolve la temperatura sull'intera superficie, quindi il
|
||||
# campo disegnato qui è quello calcolato: non c'è più nessuna ricostruzione
|
||||
# della distribuzione circonferenziale. La faccia esterna e quella interna
|
||||
# hanno la stessa temperatura (una temperatura per nodo), quindi la superficie
|
||||
# mostrata vale per entrambe.
|
||||
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from matplotlib import cm
|
||||
from matplotlib.animation import FuncAnimation, PillowWriter
|
||||
from pathlib import Path
|
||||
|
||||
from config import FASCETTA, SIMULAZIONE
|
||||
import sorgente as src
|
||||
from config import SIMULAZIONE
|
||||
from mesh import chiudi_campo, coordinate_3d
|
||||
from plot_animazione import DT_FRAME_S as DT_FRAME_S_BASE
|
||||
from plot_animazione import T_FINE_ANIMAZIONE_S, T_INIZIO_ANIMAZIONE_S, simula_campi
|
||||
from plot_animazione import T_INIZIO_ANIMAZIONE_S, simula_campi
|
||||
from simulate import configurazione_randomizzata
|
||||
|
||||
# Tempo simulato tra un fotogramma e il successivo dell'animazione 3D. Più
|
||||
@@ -31,19 +31,12 @@ DT_FRAME_S = 0.15
|
||||
# Millisecondi tra i fotogrammi in riproduzione.
|
||||
INTERVALLO_RIPRODUZIONE_MS = 60
|
||||
|
||||
# Numero di punti lungo la circonferenza per l'estrusione della superficie.
|
||||
N_THETA = 72
|
||||
|
||||
|
||||
def attenuazione_circonferenziale(
|
||||
y_m: np.ndarray, offset_y_m: float, sigma_m: float
|
||||
) -> np.ndarray:
|
||||
# Fattore che scala la sovratemperatura (T - T_ambiente) del piano y=0
|
||||
# in funzione della distanza circonferenziale y dal piano stesso,
|
||||
# normalizzato in modo da valere 1 in y=0.
|
||||
sigma = max(sigma_m, 1e-9)
|
||||
esponente = -0.5 * ((y_m - offset_y_m) ** 2 - offset_y_m ** 2) / (sigma * sigma)
|
||||
return np.exp(esponente)
|
||||
# Disegna i bordi degli elementi shell sulla superficie del cilindro.
|
||||
# Con la mesh di default (100 x 110, elementi di 1 x 2 mm) il reticolo satura
|
||||
# il disegno e sbiadisce i colori: per ispezionare la mesh conviene attivarlo
|
||||
# e insieme abbassare n_elementi_x e n_elementi_theta in config.py, per
|
||||
# esempio a 25 x 28.
|
||||
MOSTRA_MESH = False
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -56,26 +49,19 @@ def main() -> None:
|
||||
passo = max(1, round(DT_FRAME_S / DT_FRAME_S_BASE))
|
||||
indici_frame = list(range(indice_inizio, len(tempi), passo))
|
||||
|
||||
T_ambiente = cfg_run["aria"]["temperatura_ambiente_C"]
|
||||
sorgente = cfg_run["sorgente"]
|
||||
sigma_m = sorgente["sigma_punto_m"]
|
||||
offset_y_m = sorgente["offset_y_percorso_m"]
|
||||
numero_sorgenti = sorgente.get("numero_sorgenti", 1)
|
||||
distanza_m = sorgente.get("distanza_sorgenti_m", 0.0)
|
||||
mesh = dati["mesh"]
|
||||
# simula_campi ruota le colonne per centrare il sensore nella mappa
|
||||
# sviluppata: qui serve l'ordine originale della mesh.
|
||||
rotazione = dati["rotazione"]
|
||||
|
||||
raggio_m = (FASCETTA["diametro_mm"] / 1000.0) / 2.0
|
||||
x_centri_m = dati["x_centri_mm"] / 1000.0
|
||||
lunghezza_m = dati["lunghezza_mm"] / 1000.0
|
||||
T_ambiente = dati["T_ambiente_C"]
|
||||
sorgente = dati["sorgente"]
|
||||
raggio_m = mesh["raggio_medio_m"]
|
||||
lunghezza_m = mesh["lunghezza_m"]
|
||||
|
||||
theta = np.linspace(-np.pi, np.pi, N_THETA)
|
||||
y_circ_m = theta * raggio_m
|
||||
attenuazione = attenuazione_circonferenziale(y_circ_m, offset_y_m, sigma_m)
|
||||
Xm, Ym, Zm = coordinate_3d(mesh)
|
||||
|
||||
Xm, Thetam = np.meshgrid(x_centri_m, theta)
|
||||
Ym = raggio_m * np.sin(Thetam)
|
||||
Zm = raggio_m * np.cos(Thetam)
|
||||
|
||||
T_max = max(c[:, 0].max() for c in dati["campi"])
|
||||
T_max = max(c.max() for c in dati["campi"])
|
||||
# vmin più basso della temperatura ambiente reale: altrimenti le zone
|
||||
# fredde cadrebbero sul nero puro di "inferno" e, essendo lo shading
|
||||
# moltiplicativo, nessuna illuminazione basterebbe a renderle visibili.
|
||||
@@ -101,25 +87,29 @@ def main() -> None:
|
||||
barra = fig.colorbar(mappabile, ax=ax, shrink=0.6, pad=0.05)
|
||||
barra.set_label("T [°C]")
|
||||
|
||||
theta_sorgente = src.theta_percorso_rad(sorgente, raggio_m)
|
||||
|
||||
def disegna_frame(k: int):
|
||||
ax.cla()
|
||||
ax.view_init(elev=35.264, azim=45)
|
||||
ax.set_box_aspect((lunghezza_m, 2 * raggio_m, 2 * raggio_m))
|
||||
ax.set_axis_off()
|
||||
|
||||
T_lato_esterno = dati["campi"][k][:, 0]
|
||||
T_superficie = T_ambiente + (T_lato_esterno[None, :] - T_ambiente) * attenuazione[:, None]
|
||||
colori = cmap(norm(T_superficie))
|
||||
campo = np.roll(dati["campi"][k], -rotazione, axis=1)
|
||||
colori = cmap(norm(chiudi_campo(campo)))
|
||||
|
||||
# Ogni faccia disegnata è un elemento shell della mesh: con
|
||||
# MOSTRA_MESH se ne vedono i bordi.
|
||||
bordi = {"edgecolor": (1.0, 1.0, 1.0, 0.25), "linewidth": 0.15}
|
||||
ax.plot_surface(
|
||||
Xm, Ym, Zm, facecolors=colori, rstride=1, cstride=1,
|
||||
antialiased=False, shade=True, lightsource=lightsource, linewidth=0,
|
||||
antialiased=False, shade=True, lightsource=lightsource,
|
||||
**(bordi if MOSTRA_MESH else {"linewidth": 0}),
|
||||
)
|
||||
|
||||
x_sorgenti_m = dati["x_riferimenti"][k] + np.arange(numero_sorgenti) * distanza_m
|
||||
x_sorgenti_m = src.posizioni_sorgenti_m(sorgente, dati["x_riferimenti"][k])
|
||||
visibili = (x_sorgenti_m >= 0.0) & (x_sorgenti_m <= lunghezza_m)
|
||||
if visibili.any():
|
||||
theta_sorgente = offset_y_m / raggio_m
|
||||
ax.scatter(
|
||||
x_sorgenti_m[visibili],
|
||||
np.full(int(visibili.sum()), raggio_m * np.sin(theta_sorgente) * 1.05),
|
||||
|
||||
Reference in new issue
Block a user