temp
This commit is contained in:
+146
-246
@@ -1,3 +1,11 @@
|
||||
# Motore principale: genera N run randomizzati della fascetta riscaldata da
|
||||
# sorgenti a induzione in movimento e scrive i CSV del dataset.
|
||||
#
|
||||
# Il modello termico è una shell a elementi finiti quadrilateri sull'intera
|
||||
# superficie cilindrica, con una temperatura per nodo. La pipeline di un run è
|
||||
# in prepara_stato_termico() e passo_termico(), condivise con i moduli di
|
||||
# animazione: ogni modifica alla fisica va fatta lì, non duplicata.
|
||||
|
||||
import csv
|
||||
import math
|
||||
import os
|
||||
@@ -8,11 +16,14 @@ from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import scipy.sparse as sp
|
||||
from scipy.sparse.linalg import splu
|
||||
|
||||
import assemblaggio
|
||||
import sensore as sens
|
||||
import solutore as sol
|
||||
import sorgente as src
|
||||
from config import ARIA, FASCETTA, RANDOMIZZAZIONE, SENSORE, SIMULAZIONE, SORGENTE
|
||||
from materials import MATERIALI
|
||||
from mesh import costruisci_mesh_cilindrica
|
||||
|
||||
|
||||
MU0 = 4.0 * math.pi * 1e-7
|
||||
@@ -31,241 +42,120 @@ def calcola_skin_depth_m(materiale: dict, frequenza_hz: float) -> float:
|
||||
return math.sqrt(2.0 * rho_e / (omega * mu))
|
||||
|
||||
|
||||
def _spread_sorgenti_m(sorgente: dict) -> float:
|
||||
# Distanza lungo x tra la prima e l'ultima sorgente del gruppo.
|
||||
numero_sorgenti = sorgente.get("numero_sorgenti", 1)
|
||||
distanza = sorgente.get("distanza_sorgenti_m", 0.0)
|
||||
return (numero_sorgenti - 1) * distanza
|
||||
|
||||
|
||||
def _x_riferimento_iniziale_m(sorgente: dict, x_sensore_m: float) -> float:
|
||||
# Posizione a t=0 della sorgente di indice 0. Le sorgenti i sono a
|
||||
# x_i = riferimento + i * distanza, quindi per v >= 0 l'indice 0 è la
|
||||
# più arretrata nel verso di marcia, per v < 0 è la più avanzata.
|
||||
# x_inizio_m è la distanza dal sensore della sorgente più avanzata
|
||||
# (quella che lo raggiunge per prima).
|
||||
spread = _spread_sorgenti_m(sorgente)
|
||||
x_inizio = sorgente["x_inizio_m"]
|
||||
if sorgente["velocita_m_s"] >= 0:
|
||||
return (x_sensore_m - x_inizio) - spread
|
||||
return x_sensore_m + x_inizio
|
||||
|
||||
|
||||
def _x_riferimento_finale_m(sorgente: dict, x_sensore_m: float) -> float:
|
||||
# Posizione di fine corsa della sorgente di indice 0. x_fine_m è la
|
||||
# distanza dal sensore della sorgente più arretrata nel verso di marcia
|
||||
# (quella che lo supera per ultima).
|
||||
spread = _spread_sorgenti_m(sorgente)
|
||||
x_fine = sorgente["x_fine_m"]
|
||||
if sorgente["velocita_m_s"] >= 0:
|
||||
return x_sensore_m + x_fine
|
||||
return (x_sensore_m - x_fine) - spread
|
||||
|
||||
|
||||
def _intervallo_attivo(inizio: float, fine: float, v: float, x_m: float) -> bool:
|
||||
if v >= 0:
|
||||
return inizio <= x_m <= fine
|
||||
return fine <= x_m <= inizio
|
||||
|
||||
|
||||
def profilo_flusso_incidente_W_m2(
|
||||
sorgente: dict,
|
||||
x_sensore_m: float,
|
||||
t_s: float,
|
||||
x_centri_m: np.ndarray,
|
||||
) -> tuple[float, np.ndarray]:
|
||||
# Restituisce x_sorgente_m (posizione della sorgente di riferimento) e
|
||||
# il profilo di flusso termico efficace q(x) [W/m²] sul lato esterno,
|
||||
# somma dei contributi di tutte le sorgenti attive.
|
||||
def numero_fourier_spessore(
|
||||
materiale: dict, spessore_m: float, sorgente: dict
|
||||
) -> float:
|
||||
# Verifica quantitativa dell'ipotesi di parete termicamente sottile:
|
||||
#
|
||||
# Ogni sorgente in moto ha un'impronta gaussiana lungo x, valutata sui
|
||||
# centri cella della sezione. L'offset circonferenziale y non è risolto
|
||||
# spazialmente: entra come attenuazione gaussiana del flusso.
|
||||
x_rif_iniziale = _x_riferimento_iniziale_m(sorgente, x_sensore_m)
|
||||
x_rif_finale = _x_riferimento_finale_m(sorgente, x_sensore_m)
|
||||
x_riferimento = x_rif_iniziale + sorgente["velocita_m_s"] * t_s
|
||||
|
||||
numero_sorgenti = sorgente.get("numero_sorgenti", 1)
|
||||
distanza = sorgente.get("distanza_sorgenti_m", 0.0)
|
||||
v = sorgente["velocita_m_s"]
|
||||
zero_dopo_fine = sorgente.get("zero_dopo_fine", True)
|
||||
dy = sorgente["offset_y_percorso_m"]
|
||||
sigma = sorgente["sigma_punto_m"]
|
||||
|
||||
q_picco = sorgente["flusso_termico_picco_W_m2"] * sorgente["efficienza_riscaldamento"]
|
||||
attenuazione_y = math.exp(-0.5 * (dy * dy) / (sigma * sigma))
|
||||
|
||||
q_x = np.zeros_like(x_centri_m)
|
||||
for i in range(numero_sorgenti):
|
||||
x_i = x_riferimento + i * distanza
|
||||
|
||||
if zero_dopo_fine:
|
||||
inizio_i = x_rif_iniziale + i * distanza
|
||||
fine_i = x_rif_finale + i * distanza
|
||||
if not _intervallo_attivo(inizio_i, fine_i, v, x_i):
|
||||
continue
|
||||
|
||||
dx = x_centri_m - x_i
|
||||
q_x += q_picco * attenuazione_y * np.exp(-0.5 * (dx * dx) / (sigma * sigma))
|
||||
|
||||
return x_riferimento, q_x
|
||||
|
||||
|
||||
def profilo_deposizione_z_1_m(
|
||||
z_centri_m: np.ndarray,
|
||||
spessore_m: float,
|
||||
skin_depth_m: float,
|
||||
) -> np.ndarray:
|
||||
# Profilo di deposizione volumetrica del flusso superficiale [1/m]:
|
||||
# Fo = alpha * (sigma / |v|) / spessore^2
|
||||
#
|
||||
# p(z) = exp(-z / delta) / (delta * (1 - exp(-spessore / delta)))
|
||||
#
|
||||
# Normalizzato in modo che integrale_0^spessore p(z) dz = 1, così che
|
||||
# q_vol(x, z) = q(x) * p(z) conservi il flusso superficiale.
|
||||
delta = max(skin_depth_m, 1e-9)
|
||||
normalizzazione = delta * (1.0 - math.exp(-spessore_m / delta))
|
||||
return np.exp(-z_centri_m / delta) / normalizzazione
|
||||
|
||||
|
||||
def _laplaciano_1d(n: int) -> sp.spmatrix:
|
||||
# Operatore alle differenze -T'' su n celle con bordi adiabatici (Neumann).
|
||||
diagonale = np.full(n, 2.0)
|
||||
diagonale[0] = 1.0
|
||||
diagonale[-1] = 1.0
|
||||
fuori = -np.ones(n - 1)
|
||||
return sp.diags([fuori, diagonale, fuori], [-1, 0, 1])
|
||||
|
||||
|
||||
def costruisci_solutore_implicito_2d(
|
||||
n_x: int,
|
||||
n_z: int,
|
||||
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,
|
||||
):
|
||||
# Costruisce e fattorizza (LU sparsa) la matrice A per Eulero implicito 2D:
|
||||
# A * T_next = rhs
|
||||
#
|
||||
# Le incognite sono i centri cella T[i, j] con i lungo x e j lungo z,
|
||||
# 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. 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"]
|
||||
alpha = k / (rho * cp)
|
||||
|
||||
r_x = alpha * dt_s / (dx_m * dx_m)
|
||||
r_z = alpha * dt_s / (dz_m * dz_m)
|
||||
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 = (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)
|
||||
scambio[0::n_z] += b_esterno
|
||||
scambio[n_z - 1::n_z] += b_interno
|
||||
scambio[:n_z] += b_bordo
|
||||
scambio[n - n_z:] += b_bordo
|
||||
|
||||
A = (
|
||||
sp.identity(n)
|
||||
+ r_x * sp.kron(_laplaciano_1d(n_x), sp.identity(n_z))
|
||||
+ r_z * sp.kron(sp.identity(n_x), _laplaciano_1d(n_z))
|
||||
+ sp.diags(scambio)
|
||||
# confronta il tempo di diffusione attraverso lo spessore con il tempo di
|
||||
# transito della sorgente. Per Fo >> 1 lo spessore si equilibra molto
|
||||
# prima che la sorgente sia passata e una shell a una temperatura per
|
||||
# nodo è adeguata; scendendo verso Fo ~ 1 servirebbe una formulazione
|
||||
# multistrato.
|
||||
alpha = materiale["conducibilita_termica_W_mK"] / (
|
||||
materiale["densita_kg_m3"] * materiale["calore_specifico_J_kgK"]
|
||||
)
|
||||
return splu(sp.csc_matrix(A))
|
||||
velocita = abs(sorgente["velocita_m_s"])
|
||||
if velocita <= 0.0:
|
||||
return float("inf")
|
||||
tempo_transito = sorgente["sigma_punto_m"] / velocita
|
||||
return alpha * tempo_transito / (spessore_m * spessore_m)
|
||||
|
||||
|
||||
def prepara_stato_termico(fascetta: dict, aria: dict, sorgente: dict) -> dict:
|
||||
# Prepara griglia, coefficienti e solutore fattorizzato per un run:
|
||||
def prepara_stato_termico(
|
||||
fascetta: dict, aria: dict, sorgente: dict, sensore: dict
|
||||
) -> dict:
|
||||
# Costruisce mesh, matrici globali e solutore fattorizzato per un run:
|
||||
# tutto ciò che resta costante durante l'integrazione temporale.
|
||||
materiale = MATERIALI[fascetta["materiale"]]
|
||||
|
||||
lunghezza = fascetta["lunghezza_mm"] / 1000.0
|
||||
spessore = fascetta["spessore_mm"] / 1000.0
|
||||
n_x = fascetta["n_nodi_x"]
|
||||
n_z = fascetta["n_nodi_z"]
|
||||
dx = lunghezza / n_x
|
||||
dz = spessore / n_z
|
||||
lunghezza_m = fascetta["lunghezza_mm"] / 1000.0
|
||||
spessore_m = fascetta["spessore_mm"] / 1000.0
|
||||
raggio_medio_m = fascetta["diametro_medio_mm"] / 2000.0
|
||||
|
||||
dt = SIMULAZIONE["dt_interno_s"]
|
||||
mesh = costruisci_mesh_cilindrica(
|
||||
lunghezza_m=lunghezza_m,
|
||||
raggio_medio_m=raggio_medio_m,
|
||||
n_elementi_x=fascetta["n_elementi_x"],
|
||||
n_elementi_theta=fascetta["n_elementi_theta"],
|
||||
)
|
||||
|
||||
C = assemblaggio.assembla_capacita(mesh, materiale, spessore_m)
|
||||
K_cond = assemblaggio.assembla_conduzione(mesh, materiale, spessore_m)
|
||||
K_conv, f_ambiente = assemblaggio.assembla_convezione(mesh, aria, spessore_m)
|
||||
|
||||
dt_s = SIMULAZIONE["dt_interno_s"]
|
||||
solutore = sol.costruisci_solutore(C, K_cond + K_conv, dt_s)
|
||||
|
||||
assemblatore = assemblaggio.prepara_assemblatore_sorgente(mesh)
|
||||
|
||||
# Il fattore circonferenziale del flusso non dipende dal tempo: le
|
||||
# sorgenti traslano solo lungo x.
|
||||
fattore_arco = src.fattore_circonferenziale(
|
||||
sorgente, raggio_medio_m, assemblatore["arco_gauss_m"]
|
||||
)
|
||||
attenuazione_al_sensore = float(
|
||||
src.fattore_circonferenziale(sorgente, raggio_medio_m, np.zeros(1))[0]
|
||||
)
|
||||
|
||||
if sorgente["skin_depth_fissa_m"] is None:
|
||||
skin_depth = calcola_skin_depth_m(materiale, sorgente["frequenza_hz"])
|
||||
skin_depth_m = calcola_skin_depth_m(materiale, sorgente["frequenza_hz"])
|
||||
else:
|
||||
skin_depth = float(sorgente["skin_depth_fissa_m"])
|
||||
skin_depth_m = float(sorgente["skin_depth_fissa_m"])
|
||||
|
||||
rho = materiale["densita_kg_m3"]
|
||||
cp = materiale["calore_specifico_J_kgK"]
|
||||
alpha = materiale["conducibilita_termica_W_mK"] / (rho * cp)
|
||||
|
||||
z_centri = (np.arange(n_z) + 0.5) * dz
|
||||
x_sensore_m = sensore["x_mm"] / 1000.0
|
||||
|
||||
return {
|
||||
"n_x": n_x,
|
||||
"n_z": n_z,
|
||||
"dx_m": dx,
|
||||
"dz_m": dz,
|
||||
"dt_s": dt,
|
||||
"x_centri_m": (np.arange(n_x) + 0.5) * dx,
|
||||
"z_centri_m": z_centri,
|
||||
"skin_depth_m": skin_depth,
|
||||
"rho": rho,
|
||||
"cp": cp,
|
||||
"profilo_z": profilo_deposizione_z_1_m(z_centri, spessore, skin_depth),
|
||||
"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": (aria["h_esterno_W_m2K"] + aria["h_interno_W_m2K"]) * dt / (rho * cp * spessore),
|
||||
"mesh": mesh,
|
||||
"materiale": materiale,
|
||||
"spessore_m": spessore_m,
|
||||
"raggio_medio_m": raggio_medio_m,
|
||||
"dt_s": dt_s,
|
||||
"solutore": solutore,
|
||||
"assemblatore": assemblatore,
|
||||
"f_ambiente": f_ambiente,
|
||||
"fattore_arco": fattore_arco,
|
||||
"attenuazione_al_sensore": attenuazione_al_sensore,
|
||||
"x_sensore_m": x_sensore_m,
|
||||
"interpolatore_sensore": sens.prepara_interpolatore(mesh, x_sensore_m, 0.0),
|
||||
"sorgente": sorgente,
|
||||
"T_ambiente_C": aria["temperatura_ambiente_C"],
|
||||
"solutore": costruisci_solutore_implicito_2d(
|
||||
n_x=n_x,
|
||||
n_z=n_z,
|
||||
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"],
|
||||
"skin_depth_m": skin_depth_m,
|
||||
"numero_fourier_spessore": numero_fourier_spessore(
|
||||
materiale, spessore_m, sorgente
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def passo_implicito(stato: dict, T: np.ndarray, q_x: np.ndarray) -> np.ndarray:
|
||||
# Avanza il campo di temperatura di un passo dt: assembla il termine noto
|
||||
# (sorgente volumetrica, convezione sui quattro lati, conduzione
|
||||
# circonferenziale verso l'ambiente) e risolve il sistema implicito.
|
||||
T_amb = stato["T_ambiente_C"]
|
||||
rhs = T + (stato["dt_s"] / (stato["rho"] * stato["cp"])) * (
|
||||
q_x[:, None] * stato["profilo_z"][None, :]
|
||||
def campo_iniziale(stato: dict) -> np.ndarray:
|
||||
return np.full(stato["mesh"]["n_nodi"], stato["T_ambiente_C"], dtype=float)
|
||||
|
||||
|
||||
def passo_termico(stato: dict, T: np.ndarray, t_s: float) -> tuple[np.ndarray, dict]:
|
||||
# Avanza il campo di temperatura di un passo dt e restituisce anche lo
|
||||
# stato della sorgente in quell'istante.
|
||||
sorgente = stato["sorgente"]
|
||||
x_riferimento_m, flusso_x = src.profilo_flusso_x_W_m2(
|
||||
sorgente, stato["x_sensore_m"], t_s, stato["assemblatore"]["x_gauss_m"]
|
||||
)
|
||||
f_sorgente = assemblaggio.assembla_sorgente(
|
||||
stato["assemblatore"], flusso_x, stato["fattore_arco"]
|
||||
)
|
||||
T_next = sol.passo_implicito(
|
||||
stato["solutore"], T, stato["f_ambiente"], f_sorgente
|
||||
)
|
||||
rhs += stato["c_y"] * T_amb
|
||||
rhs[:, 0] += stato["b_esterno"] * T_amb
|
||||
rhs[:, -1] += stato["b_interno"] * T_amb
|
||||
rhs[0, :] += stato["b_bordo"] * T_amb
|
||||
rhs[-1, :] += stato["b_bordo"] * T_amb
|
||||
return stato["solutore"].solve(rhs.ravel()).reshape(T.shape)
|
||||
|
||||
|
||||
def quantizza(valore: float, passo: float) -> float:
|
||||
if passo <= 0.0:
|
||||
return valore
|
||||
return round(valore / passo) * passo
|
||||
_, flusso_al_sensore = src.profilo_flusso_x_W_m2(
|
||||
sorgente, stato["x_sensore_m"], t_s, np.array([stato["x_sensore_m"]])
|
||||
)
|
||||
return T_next, {
|
||||
"x_riferimento_m": x_riferimento_m,
|
||||
"flusso_al_sensore_W_m2": float(
|
||||
flusso_al_sensore[0] * stato["attenuazione_al_sensore"]
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def configurazione_randomizzata(indice_run: int, rng: random.Random) -> dict:
|
||||
@@ -323,25 +213,22 @@ def simula_singolo(cfg_run: dict, output_csv: Path, rng: random.Random) -> dict:
|
||||
|
||||
nome_materiale = fascetta["materiale"]
|
||||
|
||||
stato = prepara_stato_termico(fascetta, aria, sorgente)
|
||||
n_x = stato["n_x"]
|
||||
n_z = stato["n_z"]
|
||||
x_centri = stato["x_centri_m"]
|
||||
stato = prepara_stato_termico(fascetta, aria, sorgente, sensore)
|
||||
mesh = stato["mesh"]
|
||||
interpolatore = stato["interpolatore_sensore"]
|
||||
skin_depth = stato["skin_depth_m"]
|
||||
|
||||
x_sensore = sensore["x_mm"] / 1000.0
|
||||
i_sensore = min(n_x - 1, max(0, int(x_sensore / stato["dx_m"])))
|
||||
|
||||
dt = stato["dt_s"]
|
||||
durata = SIMULAZIONE["durata_s"]
|
||||
periodo_campionamento = 1.0 / SIMULAZIONE["frequenza_campionamento_hz"]
|
||||
|
||||
T = np.full((n_x, n_z), aria["temperatura_ambiente_C"], dtype=float)
|
||||
T_sensore = T[i_sensore, -1]
|
||||
T = campo_iniziale(stato)
|
||||
T_letta = sens.temperatura_osservata_C(interpolatore, T)
|
||||
|
||||
prossimo_campione_t = 0.0
|
||||
T_vera_max = T[i_sensore, -1]
|
||||
T_misurata_max = T_sensore
|
||||
T_vera_max = T_letta
|
||||
T_misurata_max = T_letta
|
||||
T_fascetta_max = float(T.max())
|
||||
|
||||
output_csv.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -356,7 +243,8 @@ def simula_singolo(cfg_run: dict, output_csv: Path, rng: random.Random) -> dict:
|
||||
"skin_depth_m",
|
||||
"T_vera_lato_sensore_C",
|
||||
"T_misurata_sensore_C",
|
||||
"T_lato_caldo_C",
|
||||
"T_max_fascetta_C",
|
||||
"T_media_fascetta_C",
|
||||
"T_ambiente_C",
|
||||
"velocita_m_s",
|
||||
"sigma_punto_m",
|
||||
@@ -366,37 +254,41 @@ def simula_singolo(cfg_run: dict, output_csv: Path, rng: random.Random) -> dict:
|
||||
|
||||
t = 0.0
|
||||
while t <= durata + 1e-12:
|
||||
x_sorgente, q_x = profilo_flusso_incidente_W_m2(
|
||||
sorgente, x_sensore, t, x_centri
|
||||
)
|
||||
T = passo_implicito(stato, T, q_x)
|
||||
T, info = passo_termico(stato, T, t)
|
||||
|
||||
# Temperatura vera della superficie interna nel punto osservato
|
||||
# dal sensore infrarosso.
|
||||
T_vera_lato_sensore = T[i_sensore, -1]
|
||||
T_vera = sens.temperatura_osservata_C(interpolatore, T)
|
||||
T_letta = sens.aggiorna_inerzia_C(
|
||||
T_letta, T_vera, dt, sensore["costante_tempo_s"]
|
||||
)
|
||||
|
||||
# Inerzia del sensore del primo ordine.
|
||||
tau_sensore = max(sensore["costante_tempo_s"], 1e-9)
|
||||
T_sensore += (T_vera_lato_sensore - T_sensore) * dt / tau_sensore
|
||||
# I massimi delle temperature vere sono aggiornati a ogni passo
|
||||
# interno, non solo agli istanti di campionamento: servono come
|
||||
# ground truth e il picco è stretto rispetto al periodo di
|
||||
# campionamento CSV, che da solo lo taglierebbe di oltre un grado.
|
||||
T_max = float(T.max())
|
||||
T_vera_max = max(T_vera_max, T_vera)
|
||||
T_fascetta_max = max(T_fascetta_max, T_max)
|
||||
|
||||
# Campionamento CSV.
|
||||
if t + 1e-12 >= prossimo_campione_t:
|
||||
misurata = T_sensore + rng.gauss(0.0, sensore["rumore_std_C"])
|
||||
misurata = quantizza(misurata, sensore["quantizzazione_C"])
|
||||
misurata = T_letta + rng.gauss(0.0, sensore["rumore_std_C"])
|
||||
misurata = sens.quantizza(misurata, sensore["quantizzazione_C"])
|
||||
|
||||
T_vera_max = max(T_vera_max, T_vera_lato_sensore)
|
||||
T_misurata_max = max(T_misurata_max, misurata)
|
||||
|
||||
writer.writerow([
|
||||
cfg_run["id_run"],
|
||||
f"{t:.6f}",
|
||||
f"{x_sorgente:.9f}",
|
||||
f"{info['x_riferimento_m']:.9f}",
|
||||
f"{sorgente['offset_y_percorso_m']:.9f}",
|
||||
f"{q_x[i_sensore]:.6f}",
|
||||
f"{info['flusso_al_sensore_W_m2']:.6f}",
|
||||
f"{skin_depth:.9e}",
|
||||
f"{T_vera_lato_sensore:.6f}",
|
||||
f"{T_vera:.6f}",
|
||||
f"{misurata:.6f}",
|
||||
f"{T[i_sensore, 0]:.6f}",
|
||||
f"{T_max:.6f}",
|
||||
f"{float(T.mean()):.6f}",
|
||||
f"{aria['temperatura_ambiente_C']:.6f}",
|
||||
f"{sorgente['velocita_m_s']:.9f}",
|
||||
f"{sorgente['sigma_punto_m']:.9f}",
|
||||
@@ -411,11 +303,14 @@ def simula_singolo(cfg_run: dict, output_csv: Path, rng: random.Random) -> dict:
|
||||
"id_run": cfg_run["id_run"],
|
||||
"file_csv": str(output_csv.name),
|
||||
"materiale": nome_materiale,
|
||||
"diametro_m": fascetta["diametro_mm"] / 1000.0,
|
||||
"diametro_medio_m": fascetta["diametro_medio_mm"] / 1000.0,
|
||||
"lunghezza_m": fascetta["lunghezza_mm"] / 1000.0,
|
||||
"spessore_m": fascetta["spessore_mm"] / 1000.0,
|
||||
"n_nodi_x": n_x,
|
||||
"n_nodi_z": n_z,
|
||||
"n_elementi_x": mesh["n_elementi_x"],
|
||||
"n_elementi_theta": mesh["n_elementi_theta"],
|
||||
"n_nodi": mesh["n_nodi"],
|
||||
"lato_elemento_x_m": mesh["lato_x_m"],
|
||||
"lato_elemento_arco_m": mesh["lato_arco_m"],
|
||||
"durata_s": durata,
|
||||
"frequenza_campionamento_hz": SIMULAZIONE["frequenza_campionamento_hz"],
|
||||
"dt_interno_s": dt,
|
||||
@@ -425,9 +320,12 @@ def simula_singolo(cfg_run: dict, output_csv: Path, rng: random.Random) -> dict:
|
||||
"h_bordi_W_m2K": aria["h_bordi_W_m2K"],
|
||||
"x_inizio_m": sorgente["x_inizio_m"],
|
||||
"x_fine_m": sorgente["x_fine_m"],
|
||||
"x_sensore_m": x_sensore,
|
||||
"x_sensore_m": stato["x_sensore_m"],
|
||||
"distanza_sensore_parete_m": sensore["distanza_parete_mm"] / 1000.0,
|
||||
"offset_y_percorso_m": sorgente["offset_y_percorso_m"],
|
||||
"theta_percorso_rad": src.theta_percorso_rad(
|
||||
sorgente, stato["raggio_medio_m"]
|
||||
),
|
||||
"velocita_m_s": sorgente["velocita_m_s"],
|
||||
"numero_sorgenti": sorgente.get("numero_sorgenti", 1),
|
||||
"distanza_sorgenti_m": sorgente.get("distanza_sorgenti_m", 0.0),
|
||||
@@ -436,11 +334,13 @@ def simula_singolo(cfg_run: dict, output_csv: Path, rng: random.Random) -> dict:
|
||||
"efficienza_riscaldamento": sorgente["efficienza_riscaldamento"],
|
||||
"frequenza_hz": sorgente["frequenza_hz"],
|
||||
"skin_depth_m": skin_depth,
|
||||
"numero_fourier_spessore": stato["numero_fourier_spessore"],
|
||||
"costante_tempo_sensore_s": sensore["costante_tempo_s"],
|
||||
"rumore_std_sensore_C": sensore["rumore_std_C"],
|
||||
"quantizzazione_sensore_C": sensore["quantizzazione_C"],
|
||||
"T_vera_max_lato_sensore_C": T_vera_max,
|
||||
"T_misurata_max_sensore_C": T_misurata_max,
|
||||
"T_max_fascetta_C": T_fascetta_max,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user