Scambia calore su tutto il contorno e per conduzione circonferenziale

- Convezione anche sui bordi laterali x = 0 e x = lunghezza (nuovo
  coefficiente h_bordi_W_m2K in ARIA)
- Termine di conduzione lungo y verso il resto della fascetta, assunto a
  temperatura ambiente: q_y = -k*(T - T_amb)/L_y^2 con L_y configurabile
  (lunghezza_conduzione_y_mm in FASCETTA)
- La temperatura iniziale del campo e' la temperatura ambiente del run
  (rimosso temperatura_iniziale_C)
- Fisica estratta in prepara_stato_termico e passo_implicito, condivise
  tra simulate.py e plot_animazione.py per evitare duplicazione
- Entrambi i termini sono lineari e restano nella matrice fattorizzata
  una volta per run

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 19:22:51 +02:00
parent c92fd26864
commit c67f6f8da6
4 changed files with 130 additions and 101 deletions
+11 -45
View File
@@ -14,12 +14,10 @@ import numpy as np
from matplotlib.animation import FuncAnimation, PillowWriter
from config import SIMULAZIONE
from materials import MATERIALI
from simulate import (
calcola_skin_depth_m,
configurazione_randomizzata,
costruisci_solutore_implicito_2d,
profilo_deposizione_z_1_m,
passo_implicito,
prepara_stato_termico,
profilo_flusso_incidente_W_m2,
)
@@ -43,45 +41,17 @@ def simula_campi(cfg_run: dict) -> dict:
aria = cfg_run["aria"]
sorgente = cfg_run["sorgente"]
sensore = cfg_run["sensore"]
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
x_centri = (np.arange(n_x) + 0.5) * dx
z_centri = (np.arange(n_z) + 0.5) * dz
stato = prepara_stato_termico(fascetta, aria, sorgente)
n_x = stato["n_x"]
n_z = stato["n_z"]
x_centri = stato["x_centri_m"]
dt = stato["dt_s"]
x_sensore = sensore["x_mm"] / 1000.0
i_sensore = min(n_x - 1, max(0, int(x_sensore / dx)))
i_sensore = min(n_x - 1, max(0, int(x_sensore / stato["dx_m"])))
dt = SIMULAZIONE["dt_interno_s"]
if sorgente["skin_depth_fissa_m"] is None:
skin_depth = calcola_skin_depth_m(materiale, sorgente["frequenza_hz"])
else:
skin_depth = float(sorgente["skin_depth_fissa_m"])
solutore = costruisci_solutore_implicito_2d(
n_x=n_x,
n_z=n_z,
dt_s=dt,
dx_m=dx,
dz_m=dz,
materiale=materiale,
h_esterno_W_m2K=aria["h_esterno_W_m2K"],
h_interno_W_m2K=aria["h_interno_W_m2K"],
)
rho = materiale["densita_kg_m3"]
cp = materiale["calore_specifico_J_kgK"]
b_esterno = aria["h_esterno_W_m2K"] * dt / (rho * cp * dz)
b_interno = aria["h_interno_W_m2K"] * dt / (rho * cp * dz)
profilo_z = profilo_deposizione_z_1_m(z_centri, spessore, skin_depth)
T = np.full((n_x, n_z), fascetta["temperatura_iniziale_C"], dtype=float)
T = np.full((n_x, n_z), aria["temperatura_ambiente_C"], dtype=float)
T_sensore = T[i_sensore, -1]
tau_sensore = max(sensore["costante_tempo_s"], 1e-9)
@@ -92,11 +62,7 @@ def simula_campi(cfg_run: dict) -> dict:
t = 0.0
while t <= T_FINE_ANIMAZIONE_S + 1e-12:
x_rif, q_x = profilo_flusso_incidente_W_m2(sorgente, x_sensore, t, x_centri)
rhs = T + (dt / (rho * cp)) * q_x[:, None] * profilo_z[None, :]
rhs[:, 0] += b_esterno * aria["temperatura_ambiente_C"]
rhs[:, -1] += b_interno * aria["temperatura_ambiente_C"]
T = solutore.solve(rhs.ravel()).reshape(n_x, n_z)
T = passo_implicito(stato, T, q_x)
T_sensore += (T[i_sensore, -1] - T_sensore) * dt / tau_sensore
@@ -176,7 +142,7 @@ def main() -> None:
extent=(0.0, lunghezza_mm, spessore_mm, 0.0),
aspect="auto",
cmap="inferno",
vmin=cfg_run["fascetta"]["temperatura_iniziale_C"],
vmin=cfg_run["aria"]["temperatura_ambiente_C"],
vmax=T_max,
interpolation="bilinear",
)