riorganizza struttura: sposta script in start/, aggiorna README, rimuove fit dalla root

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 4.6 committed 2026-05-14 09:46:13 +02:00
1 parent 191c37a38c
commit 73f37598bb
18 files changed
+38 -794

No files matched your search

+4
View File
@@ -12,10 +12,14 @@ pip install -r requirements.txt
## Utilizzo
### Plot dati raw
```bat
python plot_temperatura.py
```
Visualizza il profilo grezzo di `temp_obj IR` e `temp_amb IR` sull'intero intervallo temporale e salva il grafico in `plot_temperatura.png`.
## Dati
Il file `data.csv` contiene il campionamento con le colonne:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

-93
View File
@@ -1,93 +0,0 @@
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# --- Dati ---
df = pd.read_csv("data.csv")
df["time_s"] = df["time since start [ms]"] / 1000.0
T_INF = 22.99 # temperatura ambiente [°C]
T_START = 115.0 # inizio finestra di fit [s]
T1 = 115.0 # riferimento 1° esponenziale (fisso)
T2 = 117.5 # riferimento 2° esponenziale (fisso)
W_ZERO_START = 115.9
W_ZERO_END = 117.2
mask = df["time_s"] >= T_START
t_fit = df.loc[mask, "time_s"].values
T_fit = df.loc[mask, "temp_obj IR [C]"].values
# Pesi espliciti: w=0 nella zona di transizione
sigma = np.where(
(t_fit >= W_ZERO_START) & (t_fit <= W_ZERO_END),
1e10,
1.0
)
# --- Modello doppio esponenziale ---
def modello(t, A1, tau1, A2, tau2):
return (T_INF
+ A1 * np.exp(-(t - T1) / tau1)
+ A2 * np.exp(-(t - T2) / tau2))
# Stime iniziali dai fit singoli
p0 = [194.51, 13.17, 154.94, 17.12]
bounds = ([0, 0.1, 0, 0.1], [np.inf, np.inf, np.inf, np.inf])
popt, pcov = curve_fit(
modello, t_fit, T_fit,
p0=p0,
sigma=sigma,
absolute_sigma=True,
method="trf",
bounds=bounds
)
A1_fit, tau1_fit, A2_fit, tau2_fit = popt
perr = np.sqrt(np.diag(pcov))
# --- R² (solo punti con peso pieno) ---
mask_w = (t_fit < W_ZERO_START) | (t_fit > W_ZERO_END)
T_pred_w = modello(t_fit[mask_w], *popt)
ss_res = np.sum((T_fit[mask_w] - T_pred_w) ** 2)
ss_tot = np.sum((T_fit[mask_w] - T_fit[mask_w].mean()) ** 2)
r2 = 1 - ss_res / ss_tot
print(f"A1 = {A1_fit:.4f} ± {perr[0]:.4f} °C")
print(f"tau1 = {tau1_fit:.4f} ± {perr[1]:.4f} s")
print(f"A2 = {A2_fit:.4f} ± {perr[2]:.4f} °C")
print(f"tau2 = {tau2_fit:.4f} ± {perr[3]:.4f} s")
print(f"R² = {r2:.6f} (punti con peso pieno)")
# --- Curve per il plot ---
t_curve = np.linspace(T_START, df["time_s"].max(), 1000)
T_tot = modello(t_curve, *popt)
T_exp1 = T_INF + A1_fit * np.exp(-(t_curve - T1) / tau1_fit)
T_exp2 = T_INF + A2_fit * np.exp(-(t_curve - T2) / tau2_fit)
# --- Plot ---
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(t_fit, T_fit,
color="steelblue", linewidth=0.8, label="Dati raw (temp_obj)")
ax.axvspan(W_ZERO_START, W_ZERO_END,
color="orange", alpha=0.25, label=f"Zona esclusa [{W_ZERO_START}–{W_ZERO_END} s]")
ax.plot(t_curve, T_exp1,
color="tomato", linewidth=1.2, linestyle=":",
label=rf"$T_\infty + A_1 e^{{-(t-{T1})/\tau_1}}$ ($A_1$={A1_fit:.1f}, $\tau_1$={tau1_fit:.2f} s)")
ax.plot(t_curve, T_exp2,
color="seagreen", linewidth=1.2, linestyle=":",
label=rf"$T_\infty + A_2 e^{{-(t-{T2})/\tau_2}}$ ($A_2$={A2_fit:.1f}, $\tau_2$={tau2_fit:.2f} s)")
ax.plot(t_curve, T_tot,
color="purple", linewidth=2, linestyle="--",
label="Somma (fit totale)")
ax.set_xlabel("Tempo [s]")
ax.set_ylabel("Temperatura [°C]")
ax.set_title(f"Fit doppio esponenziale | R² = {r2:.4f}")
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("fit_doppio_esponenziale.png", dpi=150, bbox_inches="tight")
plt.show()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

-65
View File
@@ -1,65 +0,0 @@
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# --- Dati ---
df = pd.read_csv("data.csv")
df["time_s"] = df["time since start [ms]"] / 1000.0
T_INF = 22.99 # temperatura ambiente media ponderata [°C]
T0 = 115.0 # inizio finestra di fit [s]
T_END = 115.9 # fine finestra di fit [s]
mask = (df["time_s"] >= T0) & (df["time_s"] <= T_END)
t_fit = df.loc[mask, "time_s"].values
T_fit = df.loc[mask, "temp_obj IR [C]"].values
# --- Modello (t0 fisso) ---
def modello(t, A, tau):
return T_INF + A * np.exp(-(t - T0) / tau)
A0 = T_fit[0] - T_INF
tau0 = 20.0
popt, pcov = curve_fit(
modello, t_fit, T_fit,
p0=[A0, tau0],
method="trf",
bounds=([0, 0.1], [np.inf, np.inf])
)
A_fit, tau_fit = popt
# --- R² ---
T_pred = modello(t_fit, *popt)
ss_res = np.sum((T_fit - T_pred) ** 2)
ss_tot = np.sum((T_fit - T_fit.mean()) ** 2)
r2 = 1 - ss_res / ss_tot
print(f"A = {A_fit:.4f} °C")
print(f"tau = {tau_fit:.4f} s")
print(f"R² = {r2:.6f}")
# --- Curva continua per il plot ---
t_curve = np.linspace(T0, T_END, 500)
T_curve = modello(t_curve, *popt)
# --- Plot ---
fig, ax = plt.subplots(figsize=(12, 5))
df_plot = df[(df["time_s"] >= T0) & (df["time_s"] <= T_END)]
ax.plot(df_plot["time_s"], df_plot["temp_obj IR [C]"],
color="steelblue", linewidth=0.8, label="Dati raw (temp_obj)")
ax.plot(t_curve, T_curve,
color="tomato", linewidth=2, linestyle="--",
label=f"Fit: $T_{{\\infty}}$ + {A_fit:.2f}·exp(-(t-{T0})/{tau_fit:.1f})")
ax.set_xlabel("Tempo [s]")
ax.set_ylabel("Temperatura [°C]")
ax.set_title(f"Fit 1° tratto [{T0}–{T_END} s] | R² = {r2:.4f}")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("fit_raffreddamento_1tratto.png", dpi=150, bbox_inches="tight")
plt.show()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

-67
View File
@@ -1,67 +0,0 @@
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# --- Dati ---
df = pd.read_csv("data.csv")
df["time_s"] = df["time since start [ms]"] / 1000.0
T_INF = 22.99 # temperatura ambiente media ponderata [°C]
T0 = 117.5 # inizio finestra di fit [s]
mask = df["time_s"] >= T0
t_fit = df.loc[mask, "time_s"].values
T_fit = df.loc[mask, "temp_obj IR [C]"].values
# --- Modello (t0 fisso) ---
def modello(t, A, tau):
return T_INF + A * np.exp(-(t - T0) / tau)
# Stima iniziale: A dal primo punto, tau arbitrario
A0 = T_fit[0] - T_INF
tau0 = 20.0
popt, pcov = curve_fit(
modello, t_fit, T_fit,
p0=[A0, tau0],
method="trf",
bounds=([0, 0.1], [np.inf, np.inf])
)
A_fit, tau_fit = popt
# --- R² ---
T_pred = modello(t_fit, *popt)
ss_res = np.sum((T_fit - T_pred) ** 2)
ss_tot = np.sum((T_fit - T_fit.mean()) ** 2)
r2 = 1 - ss_res / ss_tot
print(f"A = {A_fit:.4f} °C")
print(f"tau = {tau_fit:.4f} s")
print(f"R² = {r2:.6f}")
# --- Curva continua per il plot ---
t_curve = np.linspace(T0, df["time_s"].max(), 500)
T_curve = modello(t_curve, *popt)
# --- Plot ---
fig, ax = plt.subplots(figsize=(12, 5))
df_plot = df[df["time_s"] >= 115]
ax.plot(df_plot["time_s"], df_plot["temp_obj IR [C]"],
color="steelblue", linewidth=0.8, label="Dati raw (temp_obj)")
ax.plot(t_curve, T_curve,
color="tomato", linewidth=2, linestyle="--",
label=f"Fit: $T_{{\\infty}}$ + {A_fit:.2f}·exp(-(t-{T0})/{tau_fit:.1f})")
ax.axvline(T0, color="gray", linewidth=0.8, linestyle=":")
ax.text(T0 + 0.5, ax.get_ylim()[0], f"t₀ = {T0} s", color="gray", fontsize=8, va="bottom")
ax.set_xlabel("Tempo [s]")
ax.set_ylabel("Temperatura [°C]")
ax.set_title(f"Fit raffreddamento esponenziale | R² = {r2:.4f}")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("fit_raffreddamento_2tratto.png", dpi=150, bbox_inches="tight")
plt.show()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

-80
View File
@@ -1,80 +0,0 @@
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# --- Dati ---
df = pd.read_csv("data.csv")
df["time_s"] = df["time since start [ms]"] / 1000.0
T_INF = 22.99 # temperatura ambiente media ponderata [°C]
T_START = 115.0 # inizio finestra di fit [s]
T0 = T_START # t0 coincide con il primo punto
W_ZERO_START = 115.9
W_ZERO_END = 117.2
mask = df["time_s"] >= T_START
t_fit = df.loc[mask, "time_s"].values
T_fit = df.loc[mask, "temp_obj IR [C]"].values
# Pesi espliciti: w=0 nell'intervallo escluso, w=1 altrove
# curve_fit usa sigma come deviazione standard -> sigma grande = peso nullo
sigma = np.where(
(t_fit >= W_ZERO_START) & (t_fit <= W_ZERO_END),
1e10, # peso ~ 0
1.0 # peso pieno
)
# --- Modello con A e tau liberi (ottimizza R²) ---
def modello(t, A, tau):
return T_INF + A * np.exp(-(t - T0) / tau)
A0 = T_fit[0] - T_INF
tau0 = 20.0
popt, pcov = curve_fit(
modello, t_fit, T_fit,
p0=[A0, tau0],
sigma=sigma,
absolute_sigma=True,
method="trf",
bounds=([0, 0.1], [np.inf, np.inf])
)
A_fit, tau_fit = popt
perr = np.sqrt(np.diag(pcov))
# --- R² (solo sui punti con peso pieno) ---
mask_w = (t_fit < W_ZERO_START) | (t_fit > W_ZERO_END)
T_pred_w = modello(t_fit[mask_w], *popt)
ss_res = np.sum((T_fit[mask_w] - T_pred_w) ** 2)
ss_tot = np.sum((T_fit[mask_w] - T_fit[mask_w].mean()) ** 2)
r2 = 1 - ss_res / ss_tot
print(f"A = {A_fit:.4f} ± {perr[0]:.4f} °C")
print(f"tau = {tau_fit:.4f} ± {perr[1]:.4f} s")
print(f"R² = {r2:.6f} (calcolato sui punti con peso pieno)")
# --- Curva continua ---
t_curve = np.linspace(T_START, df["time_s"].max(), 500)
T_curve = modello(t_curve, *popt)
# --- Plot ---
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(t_fit, T_fit,
color="steelblue", linewidth=0.8, label="Dati raw (temp_obj)")
ax.axvspan(W_ZERO_START, W_ZERO_END,
color="orange", alpha=0.25, label=f"Zona esclusa [{W_ZERO_START}–{W_ZERO_END} s]")
ax.plot(t_curve, T_curve,
color="tomato", linewidth=2, linestyle="--",
label=f"Fit: $T_{{\\infty}}$ + {A_fit:.2f}·exp(-(t-{T0})/{tau_fit:.2f})")
ax.set_xlabel("Tempo [s]")
ax.set_ylabel("Temperatura [°C]")
ax.set_title(f"Fit con pesi espliciti (w=0 in [{W_ZERO_START}–{W_ZERO_END} s]) | R² = {r2:.4f}")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("fit_raffreddamento_intero.png", dpi=150, bbox_inches="tight")
plt.show()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

-42
View File
@@ -1,42 +0,0 @@
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# --- Dati ---
df = pd.read_csv("data.csv")
df["time_s"] = df["time since start [ms]"] / 1000.0
df_plot = df[df["time_s"] >= 114]
# --- Parametri fit ---
T_INF = 22.99
t_full = np.linspace(114, df["time_s"].max(), 1000)
# 1° tratto: t0=115.0, A=194.51, tau=13.17
T_curve_1 = T_INF + 194.51 * np.exp(-(t_full - 115.0) / 13.17)
# 2° tratto: t0=117.5, A=154.94, tau=17.12
T_curve_2 = T_INF + 154.94 * np.exp(-(t_full - 117.5) / 17.12)
# --- Plot ---
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(df_plot["time_s"], df_plot["temp_obj IR [C]"],
color="steelblue", linewidth=0.8, label="Dati raw (temp_obj)")
ax.plot(t_full, T_curve_1,
color="tomato", linewidth=2, linestyle="--",
label=r"Fit 1° tratto: $22.99 + 194.51\cdot e^{-(t-115)/13.17}$")
ax.plot(t_full, T_curve_2,
color="seagreen", linewidth=2, linestyle="--",
label=r"Fit 2° tratto: $22.99 + 154.94\cdot e^{-(t-117.5)/17.12}$")
ax.axvspan(115.0, 115.9, color="tomato", alpha=0.10, label="Finestra fit 1° tratto")
ax.axvspan(117.5, df["time_s"].max(), color="seagreen", alpha=0.07, label="Finestra fit 2° tratto")
ax.set_xlabel("Tempo [s]")
ax.set_ylabel("Temperatura [°C]")
ax.set_title("Confronto curve di fit sui dati raw")
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("plot_confronto_fit.png", dpi=150, bbox_inches="tight")
plt.show()
-194
View File
@@ -1,194 +0,0 @@
# Analisi termica — Scatola su linea di forno
Campionamento IR della temperatura di una scatola che attraversa un forno su linea di produzione.
Finestra di osservazione: **0.2 s → 133.7 s** (133.5 s totali, 888 campioni).
---
## 1. Dati e analisi preliminare
### 1.1 Dati grezzi
Il grafico seguente riporta il profilo termico completo acquisito dal sensore IR, a partire da t = 105 s.
Sono mostrate le due curve: temperatura ambiente e temperatura della scatola.
![Profilo termico grezzo](plot_temperatura.png)
*Temperatura ambiente (blu) e temperatura scatola (arancione).*
---
### 1.2 Temperatura ambiente T∞
#### Metodologia
`T_inf` è usata come temperatura di equilibrio nel modello di raffreddamento.
È calcolata come **media ponderata sul tempo** sull'intera finestra di osservazione, con la regola dei trapezi:
$$T_{\infty} = \frac{\int_{t_i}^{t_f} T_{amb}(t)\, dt}{t_f - t_i}$$
Questo approccio è corretto con campionamento non uniforme: ogni campione pesa proporzionalmente all'intervallo di tempo che copre.
#### Risultati
| Parametro | Valore |
|---|---|
| T ambiente minima | 22.60 °C |
| T ambiente massima | 23.80 °C |
| **T∞ (media ponderata)** | **22.99 °C** |
---
## 2. Raffreddamento
Il profilo di raffreddamento è modellato con la legge di Newton:
$$T(t) = T_{\infty} + A \cdot e^{-\frac{t - t_0}{\tau}}$$
con $T_{\infty} = 22.99\ °C$ fisso. Il metodo di stima è in tutti i casi **Nonlinear Least Squares con Trust Region Reflective (TRF)** — `scipy.optimize.curve_fit(..., method="trf")`.
### 2.1 Raffreddamento intero
Fit sulla finestra completa **t₀ = 115.0 s → fine osservazione**, con pesi espliciti per escludere la zona di transizione in uscita dal forno.
**Schema dei pesi:**
| Intervallo | Peso | Motivazione |
|---|---|---|
| [115.0, 115.9) s | w = 1 | Raffreddamento regolare |
| [115.9, 117.2] s | w = 0 (σ = 10¹⁰) | ERRORE DI MISURA |
| (117.2, fine] s | w = 1 | Raffreddamento regolare |
I punti nella zona arancione ricevono peso nullo: assegnando σ = 10¹⁰ il termine (residuo/σ)² → 0, rendendoli ininfluenti sul costo del fit. Entrambi i parametri $A$ e $\tau$ sono liberi.
#### Parametri stimati
| Parametro | Descrizione | Valore |
|---|---|---|
| $A$ | Sovratemperatura iniziale | **185.18 ± 0.27 °C** |
| $\tau$ | Costante di tempo | **16.27 ± 0.05 s** |
#### Curva stimata
$$T(t) = 22.99 + 185.18 \cdot e^{-\frac{t - 115.0}{16.27}} \quad [°C]$$
#### Bontà del fit
| Metrica | Valore | Nota |
|---|---|---|
| $R^2$ | **0.9938** | Calcolato solo sui punti con peso pieno |
#### Grafico
![Fit raffreddamento intero](fit_raffreddamento_intero.png)
*Dati raw (blu), zona di transizione esclusa (arancione), curva di fit TRF (rosso tratteggiato).*
---
### 2.2 Raffreddamento 1° tratto
Fit sul primo sotto-tratto di raffreddamento **[115.0, 115.9 s]**, la finestra precedente alla zona di transizione. Pesi uniformi (w = 1 su tutti i punti). Parametri liberi: $A$, $\tau$.
**Finestra:** t₀ = 115.0 s → 115.9 s.
#### Parametri stimati
| Parametro | Descrizione | Valore |
|---|---|---|
| $A$ | Sovratemperatura iniziale | **194.51 °C** |
| $\tau$ | Costante di tempo | **13.17 s** |
#### Curva stimata
$$T(t) = 22.99 + 194.51 \cdot e^{-\frac{t - 115.0}{13.17}} \quad [°C]$$
#### Bontà del fit
| Metrica | Valore |
|---|---|
| $R^2$ | **0.9998** |
#### Grafico
![Fit 1° tratto](fit_raffreddamento_1tratto.png)
*Dati raw `temp_obj IR [C]` (blu) e curva di fit (rosso tratteggiato) nella finestra [115.0–115.9 s].*
---
### 2.3 Raffreddamento 2° tratto
Fit sul solo tratto di raffreddamento stazionario, a partire dall'istante in cui la scatola ha completato l'uscita dal forno. In questa finestra i dati seguono il modello esponenziale senza discontinuità, quindi non sono necessari pesi espliciti.
**Finestra:** t₀ = 117.5 s → fine osservazione. Pesi uniformi (w = 1 su tutti i punti). Parametri liberi: $A$, $\tau$.
#### Parametri stimati
| Parametro | Descrizione | Valore |
|---|---|---|
| $A$ | Sovratemperatura iniziale | **154.94 °C** |
| $\tau$ | Costante di tempo | **17.12 s** |
#### Curva stimata
$$T(t) = 22.99 + 154.94 \cdot e^{-\frac{t - 117.5}{17.12}} \quad [°C]$$
#### Bontà del fit
| Metrica | Valore |
|---|---|
| $R^2$ | **0.9981** |
$R^2 = 0.9981$: il modello spiega il **99.81 %** della varianza — fit eccellente sul tratto di puro raffreddamento.
#### Grafico
![Fit raffreddamento 2° tratto](fit_raffreddamento_2tratto.png)
*Dati raw `temp_obj IR [C]` (blu) e curva di fit (rosso tratteggiato) a partire da t = 115 s.*
---
### 2.4 Raffreddamento doppio esponenziale
#### Motivazione
Osservando i fit singoli sovrapposti ai dati raw, si nota che nessuno dei due esponenziali riesce a descrivere l'intera curva: il fit del 1° tratto (τ₁ ≈ 13 s) decade troppo rapidamente nella fase finale, mentre il fit del 2° tratto (τ₂ ≈ 17 s) non coglie la dinamica iniziale più ripida. Questo suggerisce la presenza di **due contributi termici sovrapposti** con costanti di tempo diverse.
![Confronto fit singoli sui dati raw](plot_confronto_fit.png)
*Confronto tra il fit del 1° tratto (rosso) e del 2° tratto (verde) sovrapposti ai dati raw: nessuno dei due descrive correttamente l'intera curva.*
Il raffreddamento di un corpo che ha subito un processo termico complesso può essere descritto dalla **combinazione lineare di due esponenziali**: il primo termine cattura una componente rapida (raffreddamento superficiale immediato), il secondo una componente lenta (dissipazione termica del nucleo della scatola). Il modello adottato è:
$$T(t) = T_{\infty} + A_1 \cdot e^{-\frac{t - t_1}{\tau_1}} + A_2 \cdot e^{-\frac{t - t_2}{\tau_2}}$$
con $T_{\infty} = 22.99\ °C$, $t_1 = 115.0\ s$ e $t_2 = 117.5\ s$ fissi. La zona [115.9, 117.2 s] è esclusa con pesi nulli (σ = 10¹⁰), come nei fit precedenti.
#### Parametri stimati
| Parametro | Descrizione | Valore |
|---|---|---|
| $A_1$ | Ampiezza componente rapida | **20.73 ± 0.93 °C** |
| $\tau_1$ | Costante di tempo rapida | **1.80 ± 0.17 s** |
| $A_2$ | Ampiezza componente lenta | **152.44 ± 0.65 °C** |
| $\tau_2$ | Costante di tempo lenta | **17.60 ± 0.13 s** |
#### Curva stimata
$$T(t) = 22.99 + 20.73 \cdot e^{-\frac{t - 115.0}{1.80}} + 152.44 \cdot e^{-\frac{t - 117.5}{17.60}} \quad [°C]$$
#### Bontà del fit
| Metrica | Valore | Nota |
|---|---|---|
| $R^2$ | **0.9991** | Calcolato solo sui punti con peso pieno |
Rispetto al singolo esponenziale (R² = 0.9938 nel fit intero), il doppio esponenziale migliora significativamente la bontà del fit catturando la dinamica iniziale rapida ($\tau_1 \approx 1.8\ s$) che il modello a un solo termine non riusciva a descrivere.
#### Grafico
![Fit doppio esponenziale](fit_doppio_esponenziale.png)
*Dati raw (blu), contributo rapido (rosso punteggiato), contributo lento (verde punteggiato), somma totale (viola tratteggiato), zona esclusa (arancione).*
BIN
View File
Binary file not shown.
-247
View File
@@ -1,247 +0,0 @@
\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[italian]{babel}
\usepackage{amsmath}
\usepackage{graphicx}
\usepackage{booktabs}
\usepackage{geometry}
\usepackage{caption}
\usepackage{hyperref}
\geometry{margin=2.5cm}
\title{\textbf{Analisi termica} \\ \large Scatola su linea di forno}
\author{Davide Grilli}
\date{Aprile 2026}
\begin{document}
\maketitle
Campionamento IR della temperatura di una scatola che attraversa un forno su linea di produzione.
Finestra di osservazione: \textbf{0.2\,s $\rightarrow$ 133.7\,s} (133.5\,s totali, 888 campioni).
% ─────────────────────────────────────────────
\section{Dati e analisi preliminare}
\subsection{Dati grezzi}
Il grafico seguente riporta il profilo termico completo acquisito dal sensore IR, a partire da $t = 105\,\mathrm{s}$.
Sono mostrate le due curve: temperatura ambiente ($T_{\mathrm{amb}}$) e temperatura della scatola ($T_{\mathrm{obj}}$).
\begin{figure}[h!]
\centering
\includegraphics[width=\textwidth]{plot_temperatura.png}
\caption{Profilo termico grezzo: temperatura ambiente (blu) e temperatura scatola (arancione).}
\end{figure}
% ─────────────────────────────────────────────
\subsection{Temperatura ambiente $T_\infty$}
\subsubsection*{Metodologia}
$T_\infty$ è usata come temperatura di equilibrio nel modello di raffreddamento.
È calcolata come \textbf{media ponderata sul tempo} sull'intera finestra di osservazione, con la regola dei trapezi:
\begin{equation}
T_{\infty} = \frac{\int_{t_i}^{t_f} T_{\mathrm{amb}}(t)\,dt}{t_f - t_i}
\end{equation}
Questo approccio è corretto con campionamento non uniforme: ogni campione pesa proporzionalmente all'intervallo di tempo che copre.
\subsubsection*{Risultati}
\begin{table}[h!]
\centering
\begin{tabular}{ll}
\toprule
Parametro & Valore \\
\midrule
$T_{\mathrm{amb}}$ minima & 22.60\,°C \\
$T_{\mathrm{amb}}$ massima & 23.80\,°C \\
\textbf{$T_\infty$ (media ponderata)} & \textbf{22.99\,°C} \\
\bottomrule
\end{tabular}
\end{table}
% ─────────────────────────────────────────────
\section{Raffreddamento}
Il profilo di raffreddamento è modellato con la legge di raffreddamento di Newton:
\begin{equation}
T(t) = T_{\infty} + A \cdot e^{-\frac{t - t_0}{\tau}}
\end{equation}
con $T_{\infty} = 22.99\,°C$ fisso. Il metodo di stima è in tutti i casi \textbf{Nonlinear Least Squares con Trust Region Reflective (TRF)} (\texttt{scipy.optimize.curve\_fit(..., method="trf")}).
% ─────────────────────────────────────────────
\subsection{Raffreddamento intero}
Fit sulla finestra completa $t_0 = 115.0\,\mathrm{s} \rightarrow$ fine osservazione, con pesi espliciti per escludere la zona di transizione in uscita dal forno.
\begin{table}[h!]
\centering
\caption{Schema dei pesi}
\begin{tabular}{lll}
\toprule
Intervallo & Peso & Motivazione \\
\midrule
$[115.0,\ 115.9)$\,s & $w = 1$ & Raffreddamento regolare \\
$[115.9,\ 117.2]$\,s & $w = 0\ (\sigma = 10^{10})$ & Errore di misura \\
$(117.2,\ \mathrm{fine}]$\,s & $w = 1$ & Raffreddamento regolare \\
\bottomrule
\end{tabular}
\end{table}
I punti nella zona esclusa ricevono peso nullo: assegnando $\sigma = 10^{10}$ il termine $(\mathrm{residuo}/\sigma)^2 \rightarrow 0$, rendendoli ininfluenti sul costo del fit. Entrambi i parametri $A$ e $\tau$ sono liberi.
\begin{table}[h!]
\centering
\caption{Parametri stimati — Raffreddamento intero}
\begin{tabular}{lll}
\toprule
Parametro & Descrizione & Valore \\
\midrule
$A$ & Sovratemperatura iniziale & $185.18 \pm 0.27\,°C$ \\
$\tau$ & Costante di tempo & $16.27 \pm 0.05\,\mathrm{s}$ \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Curva stimata:}
\begin{equation}
T(t) = 22.99 + 185.18 \cdot e^{-\frac{t - 115.0}{16.27}} \quad [°C]
\end{equation}
\textbf{Bontà del fit:} $R^2 = 0.9938$ (calcolato solo sui punti con peso pieno).
\begin{figure}[h!]
\centering
\includegraphics[width=\textwidth]{fit_raffreddamento_intero.png}
\caption{Raffreddamento intero: dati raw (blu), zona esclusa (arancione), curva di fit TRF (rosso tratteggiato).}
\end{figure}
\clearpage
% ─────────────────────────────────────────────
\subsection{Raffreddamento 1° tratto}
Fit sul primo sotto-tratto $[115.0,\ 115.9\,\mathrm{s}]$, la finestra precedente alla zona di transizione. Pesi uniformi ($w = 1$ su tutti i punti). Parametri liberi: $A$, $\tau$.
\begin{table}[h!]
\centering
\caption{Parametri stimati — 1° tratto}
\begin{tabular}{lll}
\toprule
Parametro & Descrizione & Valore \\
\midrule
$A$ & Sovratemperatura iniziale & $194.51\,°C$ \\
$\tau$ & Costante di tempo & $13.17\,\mathrm{s}$ \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Curva stimata:}
\begin{equation}
T(t) = 22.99 + 194.51 \cdot e^{-\frac{t - 115.0}{13.17}} \quad [°C]
\end{equation}
\textbf{Bontà del fit:} $R^2 = 0.9998$.
\begin{figure}[h!]
\centering
\includegraphics[width=\textwidth]{fit_raffreddamento_1tratto.png}
\caption{1° tratto $[115.0\text{--}115.9\,\mathrm{s}]$: dati raw (blu) e curva di fit (rosso tratteggiato).}
\end{figure}
\clearpage
% ─────────────────────────────────────────────
\subsection{Raffreddamento 2° tratto}
Fit sul solo tratto di raffreddamento stazionario, a partire dall'istante in cui la scatola ha completato l'uscita dal forno. In questa finestra i dati seguono il modello esponenziale senza discontinuità, quindi non sono necessari pesi espliciti.
Finestra: $t_0 = 117.5\,\mathrm{s} \rightarrow$ fine osservazione. Pesi uniformi ($w = 1$). Parametri liberi: $A$, $\tau$.
\begin{table}[h!]
\centering
\caption{Parametri stimati — 2° tratto}
\begin{tabular}{lll}
\toprule
Parametro & Descrizione & Valore \\
\midrule
$A$ & Sovratemperatura iniziale & $154.94\,°C$ \\
$\tau$ & Costante di tempo & $17.12\,\mathrm{s}$ \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Curva stimata:}
\begin{equation}
T(t) = 22.99 + 154.94 \cdot e^{-\frac{t - 117.5}{17.12}} \quad [°C]
\end{equation}
\textbf{Bontà del fit:} $R^2 = 0.9981$ — il modello spiega il 99.81\% della varianza, fit eccellente.
\begin{figure}[h!]
\centering
\includegraphics[width=\textwidth]{fit_raffreddamento_2tratto.png}
\caption{2° tratto $[117.5\,\mathrm{s}\text{--fine}]$: dati raw (blu) e curva di fit (rosso tratteggiato).}
\end{figure}
\clearpage
% ─────────────────────────────────────────────
\subsection{Raffreddamento doppio esponenziale}
\subsubsection*{Motivazione}
Osservando i fit singoli sovrapposti ai dati raw, si nota che nessuno dei due esponenziali riesce a descrivere l'intera curva: il fit del 1° tratto ($\tau_1 \approx 13\,\mathrm{s}$) decade troppo rapidamente nella fase finale, mentre il fit del 2° tratto ($\tau_2 \approx 17\,\mathrm{s}$) non coglie la dinamica iniziale più ripida. Questo suggerisce la presenza di due contributi termici sovrapposti con costanti di tempo diverse.
\begin{figure}[h!]
\centering
\includegraphics[width=\textwidth]{plot_confronto_fit.png}
\caption{Confronto tra il fit del 1° tratto (rosso) e del 2° tratto (verde) sovrapposti ai dati raw: nessuno dei due descrive correttamente l'intera curva.}
\end{figure}
Il raffreddamento viene quindi modellato come \textbf{combinazione lineare di due esponenziali}: il primo termine cattura una componente rapida (raffreddamento superficiale immediato), il secondo una componente lenta (dissipazione termica del nucleo della scatola):
\begin{equation}
T(t) = T_{\infty} + A_1 \cdot e^{-\frac{t - t_1}{\tau_1}} + A_2 \cdot e^{-\frac{t - t_2}{\tau_2}}
\end{equation}
con $T_{\infty} = 22.99\,°C$, $t_1 = 115.0\,\mathrm{s}$ e $t_2 = 117.5\,\mathrm{s}$ fissi. La zona $[115.9,\ 117.2\,\mathrm{s}]$ è esclusa con pesi nulli ($\sigma = 10^{10}$).
\subsubsection*{Parametri stimati}
\begin{table}[h!]
\centering
\caption{Parametri stimati — doppio esponenziale}
\begin{tabular}{lll}
\toprule
Parametro & Descrizione & Valore \\
\midrule
$A_1$ & Ampiezza componente rapida & $20.73 \pm 0.93\,°C$ \\
$\tau_1$ & Costante di tempo rapida & $1.80 \pm 0.17\,\mathrm{s}$ \\
$A_2$ & Ampiezza componente lenta & $152.44 \pm 0.65\,°C$ \\
$\tau_2$ & Costante di tempo lenta & $17.60 \pm 0.13\,\mathrm{s}$ \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Curva stimata:}
\begin{equation}
T(t) = 22.99 + 20.73 \cdot e^{-\frac{t - 115.0}{1.80}} + 152.44 \cdot e^{-\frac{t - 117.5}{17.60}} \quad [°C]
\end{equation}
\textbf{Bontà del fit:} $R^2 = 0.9991$ (punti con peso pieno). Rispetto al singolo esponenziale ($R^2 = 0.9938$), il doppio esponenziale migliora significativamente il fit catturando la dinamica iniziale rapida ($\tau_1 \approx 1.8\,\mathrm{s}$).
\begin{figure}[h!]
\centering
\includegraphics[width=\textwidth]{fit_doppio_esponenziale.png}
\caption{Fit doppio esponenziale: dati raw (blu), componente rapida (rosso punteggiato), componente lenta (verde punteggiato), somma totale (viola tratteggiato), zona esclusa (arancione).}
\end{figure}
\end{document}
+5 -3
View File
@@ -1,7 +1,9 @@
import pandas as pd
import numpy as np
from pathlib import Path
df = pd.read_csv("data.csv")
ROOT = Path(__file__).parent.parent
df = pd.read_csv(ROOT / "data.csv")
df["time_s"] = df["time since start [ms]"] / 1000.0
t = df["time_s"].values
@@ -19,13 +21,13 @@ t_inizio = t[0]
t_fine = t[-1]
n_campioni = len(df)
print(f"Finestra di osservazione: {t_inizio:.1f} s → {t_fine:.1f} s ({durata:.1f} s)")
print(f"Finestra di osservazione: {t_inizio:.1f} s -> {t_fine:.1f} s ({durata:.1f} s)")
print(f"Campioni: {n_campioni}")
print(f"T ambiente min: {t_min:.2f} °C")
print(f"T ambiente max: {t_max:.2f} °C")
print(f"T ambiente media ponderata: {t_media:.2f} °C")
with open("report.md", "w", encoding="utf-8") as f:
with open(Path(__file__).parent / "report.md", "w", encoding="utf-8") as f:
f.write("# Report: Temperatura Ambiente Media\n\n")
f.write("## Metodologia\n\n")
f.write("La media è calcolata come **media ponderata sul tempo** (regola dei trapezi):\n\n")
File renamed without changes.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

@@ -1,9 +1,14 @@
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
df = pd.read_csv("data.csv")
ROOT = Path(__file__).parent.parent
df = pd.read_csv(ROOT / "data.csv")
df["time_s"] = df["time since start [ms]"] / 1000.0
df = df[df["time_s"] >= 105]
T0 = 105.0 # inizio finestra [s]
T_END = 133.7 # fine finestra [s] — ultimo campione nel dataset
df = df[(df["time_s"] >= T0) & (df["time_s"] <= T_END)]
fig, ax = plt.subplots(figsize=(12, 5))
@@ -17,5 +22,5 @@ ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("plot_temperatura.png", dpi=150, bbox_inches="tight")
plt.savefig(Path(__file__).parent / "plot_temperatura.png", dpi=150, bbox_inches="tight")
plt.show()
+21
View File
@@ -0,0 +1,21 @@
# Report: Temperatura Ambiente Media
## Metodologia
La media è calcolata come **media ponderata sul tempo** (regola dei trapezi):
$$T_{avg} = \frac{\int T(t)\, dt}{t_{fine} - t_{inizio}}$$
Questo approccio tiene conto del campionamento non uniforme: ogni campione pesa proporzionalmente all'intervallo di tempo che copre.
## Risultati
| Parametro | Valore |
|---|---|
| Inizio osservazione | 0.2 s |
| Fine osservazione | 133.7 s |
| Durata totale | 133.5 s |
| Numero campioni | 888 |
| T ambiente minima | 22.60 °C |
| T ambiente massima | 23.80 °C |
| **T ambiente media ponderata** | **22.99 °C** |