This commit is contained in:
2026-04-01 10:45:31 +02:00
parent 3ac42966b1
commit d7bc39e2ba
6 changed files with 44 additions and 95 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -7,10 +7,11 @@ from scipy.optimize import curve_fit
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]
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
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
@@ -18,8 +19,7 @@ T_fit = df.loc[mask, "temp_obj IR [C]"].values
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
A0 = T_fit[0] - T_INF
tau0 = 20.0
popt, pcov = curve_fit(
@@ -31,37 +31,35 @@ popt, pcov = curve_fit(
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
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 = 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"] >= 115]
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.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.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.png", dpi=150, bbox_inches="tight")
plt.savefig("fit_raffreddamento_1tratto.png", dpi=150, bbox_inches="tight")
plt.show()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

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_pesi.png", dpi=150, bbox_inches="tight")
plt.show()

View File

@@ -73,6 +73,37 @@ $$T(t) = 22.99 + 185.18 \cdot e^{-\frac{t - 115.0}{16.27}} \quad [°C]$$
---
### 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.0115.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.