aggiunti script di fit esponenziale e report di analisi termica
- fit_raffreddamento_intero.py: fit TRF su finestra completa [115s–fine] con pesi nulli sulla zona di transizione [115.9–117.2s] - fit_raffreddamento_2tratto.py: fit TRF sul solo tratto di raffreddamento [117.5s–fine], pesi uniformi - report.md: analisi strutturata con calcolo T∞, parametri stimati e grafici per entrambi gli approcci
This commit is contained in:
BIN
fit_raffreddamento_2tratto.png
Normal file
BIN
fit_raffreddamento_2tratto.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
67
fit_raffreddamento_2tratto.py
Normal file
67
fit_raffreddamento_2tratto.py
Normal file
@@ -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_2tratto.png", dpi=150, bbox_inches="tight")
|
||||||
|
plt.show()
|
||||||
BIN
fit_raffreddamento_intero.png
Normal file
BIN
fit_raffreddamento_intero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
80
fit_raffreddamento_intero.py
Normal file
80
fit_raffreddamento_intero.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
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()
|
||||||
BIN
fit_raffreddamento_pesi.png
Normal file
BIN
fit_raffreddamento_pesi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
80
fit_raffreddamento_pesi.py
Normal file
80
fit_raffreddamento_pesi.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
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()
|
||||||
118
report.md
118
report.md
@@ -1,59 +1,105 @@
|
|||||||
# Report: Temperatura Ambiente Media
|
# Analisi termica — Scatola su linea di forno
|
||||||
|
|
||||||
## Metodologia
|
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).
|
||||||
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** |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Fit esponenziale del raffreddamento
|
## 1. Temperatura ambiente T∞
|
||||||
|
|
||||||
### Contesto
|
### Metodologia
|
||||||
|
|
||||||
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_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}}$$
|
$$T(t) = T_{\infty} + A \cdot e^{-\frac{t - t_0}{\tau}}$$
|
||||||
|
|
||||||
### Parametri del modello
|
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 |
|
| Parametro | Descrizione | Valore |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| $T_{\infty}$ | Temperatura di equilibrio (fissata) | 22.99 °C |
|
| $A$ | Sovratemperatura iniziale | **185.18 ± 0.27 °C** |
|
||||||
| $t_0$ | Inizio finestra di fit (fisso) | 117.5 s |
|
| $\tau$ | Costante di tempo | **16.27 ± 0.05 s** |
|
||||||
| $A$ | Sovratemperatura iniziale rispetto all'ambiente | **154.94 °C** |
|
|
||||||
| $\tau$ | Costante di tempo del raffreddamento | **17.12 s** |
|
|
||||||
|
|
||||||
### Metodo
|
#### Curva stimata
|
||||||
|
|
||||||
Nonlinear Least Squares con metodo **Trust Region Reflective (TRF)** (`scipy.optimize.curve_fit`).
|
$$T(t) = 22.99 + 185.18 \cdot e^{-\frac{t - 115.0}{16.27}} \quad [°C]$$
|
||||||
Vincoli imposti: $A > 0$, $\tau > 0$.
|
|
||||||
|
|
||||||
### Bontà del fit
|
#### Bontà del fit
|
||||||
|
|
||||||
|
| Metrica | Valore | Nota |
|
||||||
|
|---|---|---|
|
||||||
|
| $R^2$ | **0.9938** | Calcolato solo sui punti con peso pieno |
|
||||||
|
|
||||||
|
#### Grafico
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Dati raw (blu), zona di transizione esclusa (arancione), curva di fit TRF (rosso tratteggiato).*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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 |
|
| Metrica | Valore |
|
||||||
|---|---|
|
|---|---|
|
||||||
| $R^2$ | **0.9981** |
|
| $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.
|
$R^2 = 0.9981$: il modello spiega il **99.81 %** della varianza — fit eccellente sul tratto di puro raffreddamento.
|
||||||
|
|
||||||
### Grafico
|
#### Grafico
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*Dati raw `temp_obj IR [C]` (blu) e curva di fit esponenziale (rosso tratteggiato) a partire da t = 115 s.*
|
*Dati raw `temp_obj IR [C]` (blu) e curva di fit (rosso tratteggiato) a partire da t = 115 s.*
|
||||||
|
|||||||
Reference in New Issue
Block a user