diff --git a/fit_raffreddamento.png b/fit_raffreddamento.png new file mode 100644 index 0000000..3235ca2 Binary files /dev/null and b/fit_raffreddamento.png differ diff --git a/fit_raffreddamento.py b/fit_raffreddamento.py new file mode 100644 index 0000000..3b88c56 --- /dev/null +++ b/fit_raffreddamento.py @@ -0,0 +1,67 @@ +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.png", dpi=150, bbox_inches="tight") +plt.show() diff --git a/report.md b/report.md index f9212c6..e4dc467 100644 --- a/report.md +++ b/report.md @@ -19,3 +19,41 @@ Questo approccio tiene conto del campionamento non uniforme: ogni campione pesa | T ambiente minima | 22.60 °C | | T ambiente massima | 23.80 °C | | **T ambiente media ponderata** | **22.99 °C** | + +--- + +## Fit esponenziale del raffreddamento + +### Contesto + +Dopo il picco termico, la scatola raffredda verso la temperatura ambiente seguendo un andamento esponenziale. A partire da **t₀ = 117.5 s** (inizio della fase di raffreddamento) è stato eseguito un fit con il modello di Newton per il raffreddamento: + +$$T(t) = T_{\infty} + A \cdot e^{-\frac{t - t_0}{\tau}}$$ + +### Parametri del modello + +| Parametro | Descrizione | Valore | +|---|---|---| +| $T_{\infty}$ | Temperatura di equilibrio (fissata) | 22.99 °C | +| $t_0$ | Inizio finestra di fit (fisso) | 117.5 s | +| $A$ | Sovratem­peratura iniziale rispetto all'ambiente | **154.94 °C** | +| $\tau$ | Costante di tempo del raffreddamento | **17.12 s** | + +### Metodo + +Nonlinear Least Squares con metodo **Trust Region Reflective (TRF)** (`scipy.optimize.curve_fit`). +Vincoli imposti: $A > 0$, $\tau > 0$. + +### Bontà del fit + +| Metrica | Valore | +|---|---| +| $R^2$ | **0.9981** | + +Il coefficiente di determinazione $R^2 = 0.9981$ indica che il modello esponenziale spiega il **99.81 %** della varianza dei dati di raffreddamento: il fit è eccellente. + +### Grafico + +![Fit raffreddamento esponenziale](fit_raffreddamento.png) + +*Dati raw `temp_obj IR [C]` (blu) e curva di fit esponenziale (rosso tratteggiato) a partire da t = 115 s.* diff --git a/requirements.txt b/requirements.txt index 5d56fdd..bef2cce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pandas matplotlib +scipy