# Algoritmi Tutti i passi, nell'ordine in cui il programma li esegue. Le assunzioni richiamate (A1, A2, …) sono in [assunzioni.md](assunzioni.md); le chiavi di configurazione (`in_questo_formato`) in [configurazione.md](configurazione.md); il modulo che implementa ogni passo in [moduli.md](moduli.md). ## Visione d'insieme ```mermaid flowchart TD CSV(["CSV in input/"]) --> P1 subgraph P1["PASSO 1 · Domini"] direction LR L["1 · Lettura
validazione"] --> S["2 · Rumore
stima di σ"] --> G["3 · Gradini
salti sopra soglia"] --> D["4 · Domini
divisione del tempo"] end P1 --> RIP["Riposo"] P1 --> RIS["Riscaldamento
2–3 campioni"] P1 --> INT["Raffreddamenti intermedi
tra due gradini, corti"] P1 --> FIN["Raffreddamento finale
l'ultimo, il più lungo"] subgraph P2["PASSO 2 · Equazioni"] direction TB F0["5 · Fit
costante, lineare,
esponenziale"] F1["5 · Fit esponenziale
c, A, τ liberi"] F2["5b · Fit esponenziale
c e τ fissi, solo A libera"] SALTO["7 · Salto Δ
descritto, non fittato"] SC["6 · Scelta del modello
validi → adeguati → meno parametri"] F0 --> SC F1 --> SC F2 --> SC end RIP --> F0 FIN --> F1 INT --> F2 RIS --> SALTO F1 -. "passa c, τ" .-> F2 SC --> REP SALTO --> REP REP(["PASSO 3 · Sovrapposizione e report
dati grezzi contro equazioni
report.md · grafici · risultati.json"]) classDef dato fill:#eceff1,stroke:#607d8b,color:#263238 classDef calcolo fill:#e3f2fd,stroke:#1a73e8,color:#0d47a1 classDef dominio fill:#fff8e1,stroke:#f9a825,color:#5d4037 classDef uscita fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20 class CSV dato class L,S,G,D,F0,F1,F2,SC,SALTO calcolo class RIP,RIS,INT,FIN dominio class REP uscita linkStyle 15 stroke:#e65100,stroke-width:2px,color:#e65100 ``` Come si legge: dall'alto in basso. I numeri nei riquadri sono i paragrafi di questo documento. Colori: **grigio** dati, **azzurro** calcoli, **giallo** tipi di dominio, **verde** risultati. Il Passo 1 produce i domini; ogni tipo ha il suo trattamento nel Passo 2; la freccia **arancione tratteggiata** indica che il raffreddamento finale **passa** $c$ e $\tau$ ai raffreddamenti intermedi (§5b), per questo si elabora per primo. Il riscaldamento non si fitta: se ne riporta il salto (§7). ## Simboli e chiavi di configurazione Nelle formule si usano questi simboli; a destra la chiave di [configurazione.md](configurazione.md) con il valore. | Simbolo | Chiave | Significato | |---|---|---| | $\theta_g$ | `soglia_gradino_c` | salto positivo minimo che segna un gradino [°C] | | $\theta_b$ | `soglia_bordo_sigma` | soglia, in multipli di $\sigma_d$, per estendere la salita ai campioni adiacenti | | $\theta_\rho$ | `soglia_acf` | soglia di $\rho_1$ oltre la quale si allargano gli intervalli | | $\kappa$ | `scala_perdita_sigma` | scala della perdita robusta, in multipli di $\sigma$ | | $\alpha_{\min},\ \alpha_{\max}$ | `tau_min_rel`, `tau_max_rel` | limiti di $\tau$ come frazione di $L$ | | $\lambda$ | `rmse_vs_rumore_max` | un modello è «adeguato» se $\mathrm{RMSE} \le \lambda\,\sigma$ | | $\delta$ | `soglia_delta_aicc` | tolleranza su $\Delta\mathrm{AICc}$ per preferire il modello più semplice | | $L$ | — | durata del dominio, $t_\text{ultimo} - t_\text{primo}$ | | $n$, $k$ | — | campioni usati nel fit; parametri **liberi** del modello | ## 1. Lettura dei dati (`dati.leggi_csv`) Legge l'intestazione, controlla che ci siano la colonna del tempo e tutte le serie (A2), converte il tempo in secondi (A3) e verifica che sia strettamente crescente (A1). Restituisce `t` (array in secondi) e un dizionario `serie` {nome: array}. ## 2. Stima del rumore (`domini.sigma_rumore`) Per un rumore bianco $\varepsilon_i$ di deviazione $\sigma$, la **derivata seconda discreta** $\Delta^2 y_i = y_{i+2} - 2y_{i+1} + y_i$ elimina le componenti lisce (costante, lineare, e in buona parte l'esponenziale) e ha varianza $6\sigma^2$. Per renderla robusta agli outlier si usa la deviazione mediana assoluta: $$\sigma = \frac{1.4826\,\mathrm{MAD}(\Delta^2 y)}{\sqrt{6}}, \qquad \mathrm{MAD}(x) = \mathrm{mediana}\,|x_i - \mathrm{mediana}(x)|$$ Il fattore 1.4826 rende la MAD uguale a $\sigma$ per dati gaussiani. La stima si calcola **per dominio** (A13) sui campioni usati nel fit e serve come scala della perdita robusta e come soglia di «adeguatezza» (§6). Ignora la deriva lenta (A14) e restituisce `nan` con meno di 4 campioni. ## 3. Rilevamento dei gradini (`domini.trova_gradini`) Sulla `serie_riferimento`: 1. **Candidati**: $d_i = y_{i+1} - y_i$; sono forti gli indici con $d_i > \theta_g$ (solo salti positivi, A5, A12). 2. **Raggruppamento**: candidati a meno di `distanza_min_salti_campioni` indici l'uno dall'altro appartengono allo stesso gradino (la salita si distribuisce su 2 campioni, ognuno con un salto grande). 3. **Estensione ai bordi**: il gruppo si estende all'indietro e in avanti ai campioni adiacenti ancora in salita, cioè finché $d_i > \theta_b\,\sigma_d$, con $\sigma_d = 1.4826\,\mathrm{MAD}(d)$ calcolata su **tutti** i salti (A6). Serve a non lasciare un campione a metà salita dentro il tratto vicino. 4. Ogni gradino diventa la coppia $(i_0, i_1)$: $i_0$ è il primo campione già in salita e $i_1$ il campione successivo all'ultimo in salita (estremo escluso). I **campioni di salita** sono quelli in $[i_0, i_1)$. ## 4. Costruzione dei domini (`domini.proponi`) Dati $N$ gradini, in ordine di tempo: - `riposo`: dall'inizio al primo gradino (o tutto il segnale se non ci sono gradini); - per ogni gradino: un dominio `riscaldamento` sui suoi campioni di salita, seguito da un dominio `raffreddamento` fino al gradino successivo (o alla fine). Un raffreddamento senza campioni (gradini adiacenti) non viene creato. Con $N$ gradini i domini sono quindi $1 + 2N$. I domini sono contigui e coprono tutti i campioni. L'utente può sostituire la proposta con `domini.toml` (A21), che viene validato: tipi ammessi, intervalli non vuoti, contiguità. ## 5. Fit (`fit.adatta`) ### 5.1 Modelli (`modelli.MODELLI`, $t' = t - t_0$) | Modello | Equazione | Parametri | Dove si prova | |---|---|---|---| | `costante` | $y = a$ | $a$ | riposo | | `lineare` | $y = a + b\,t'$ | $a, b$ | riposo | | `esp_decad` | $y = c + A\,e^{-t'/\tau}$ | $c, A, \tau$ | riposo, raffreddamento | Nel raffreddamento $c$ è l'asintoto (≈ ambiente per `T_C`, ≈ 0 per `dT_C`), $A$ l'ampiezza a $t'=0$ e $\tau = 1/k$ la costante di tempo. ### 5.2 Partenza - Polinomi (`costante`, `lineare`): soluzione dei minimi quadrati lineari. - `esp_decad`: **proiezione delle variabili**. Per $\tau$ fissato il modello è lineare in $(c, A)$. Si provano `n_griglia_tau` valori di $\tau$ su una griglia logaritmica tra $\alpha_{\min} L$ e $\alpha_{\max} L$, per ognuno si risolvono $(c, A)$ in modo lineare e si tiene il $\tau$ con $\mathrm{RSS}$ minima. Poi il raffinamento non lineare parte da lì: nessuna partenza casuale (A22). - Se $c$ e $\tau$ sono fissi e solo $A$ è libero: $A = \sum (y - c)\,e^{-t'/\tau} \big/ \sum e^{-2t'/\tau}$. ### 5.3 Minimizzazione `scipy.optimize.least_squares` sui soli parametri liberi, con `x_scale="jac"`, limiti $\tau \in [\alpha_{\min} L,\ \alpha_{\max} L]$ e **perdita robusta** (`perdita`, default `soft_l1`): $$\min_p \sum_i \rho\!\left(\frac{r_i^2}{s^2}\right), \quad \rho(z) = 2\left(\sqrt{1+z}-1\right), \quad s = \kappa\,\sigma, \quad r_i = f(t'_i; p) - y_i$$ Per $|r_i| \ll s$ si comporta come i minimi quadrati; per $|r_i| \gg s$ cresce linearmente, così gli **scarti casuali** pesano molto meno (A14). Se $\sigma$ non è finita o è nulla la scala è 1 (A20). ### 5.4 Incertezze $$s^2 = \frac{\mathrm{RSS}}{n-k}, \qquad \mathrm{cov}(p) = s^2\,(J^\top J)^{-1}, \qquad \mathrm{IC}_{95\%} = \pm 1.96\,\sqrt{\mathrm{diag}(\mathrm{cov})}\cdot f_\rho$$ $k$ è il numero di parametri **liberi**, $J$ lo jacobiano dei residui nella soluzione (con la pseudo-inversa di $J^\top J$), $\mathrm{RSS} = \sum r_i^2$ (A16). Il fattore di autocorrelazione vale $f_\rho = \sqrt{(1+\rho_1)/(1-\rho_1)}$ se `correggi_autocorrelazione` è attivo e $\rho_1 > \theta_\rho$, altrimenti 1 (A17). Per i parametri fissi l'incertezza è 0 (A18). ### 5.5 Metriche | Metrica | Definizione | |---|---| | $\mathrm{RMSE}$ | $\sqrt{\mathrm{RSS}/n}$ | | $R^2$ | $1 - \mathrm{RSS}/\mathrm{TSS}$, con $\mathrm{TSS} = \sum (y_i - \bar y)^2$ | | $\rho_1$ | autocorrelazione dei residui a lag 1: $\sum_{i} \tilde r_{i+1}\tilde r_i \big/ \sum_i \tilde r_i^2$, con $\tilde r = r - \bar r$ | | $\mathrm{AICc}$ | $n\ln(\mathrm{RSS}/n) + 2k + \dfrac{2k(k+1)}{n-k-1}$ (infinito se $n-k-1 \le 0$) | | $\mathrm{BIC}$ | $n\ln(\mathrm{RSS}/n) + k\ln n$ | | condizione | numero di condizione di $J$ con le colonne normalizzate in norma euclidea | AICc e BIC sono calcolati da $\mathrm{RSS}$, quindi senza peso robusto (A19). ### 5.6 Validità e identificabilità Un fit è **valido** se converge, nessun parametro libero è sul limite (distanza dal limite inferiore a $10^{-3}$ volte il limite stesso: $\tau$ al bordo della griglia), il numero di condizione non supera `soglia_condizione` e l'AICc è finito. Un fit è **non identificabile** se la condizione supera la soglia **oppure** un parametro libero diverso da `a` e `c` ha $1.96\,\sigma_p > |p|$ (intervallo che include lo zero: il parametro non è distinguibile da 0). ## 5b. Costanti condivise (`analisi.analizza_domini`) Per A8 i raffreddamenti hanno lo stesso $\tau$ e lo stesso $c$. L'ordine di elaborazione è quindi: 1. il **raffreddamento finale** (ultimo dominio di tipo `raffreddamento`) si fitta per primo con $c, A, \tau$ liberi; 2. se la scelta è valida, i valori di $c$ e $\tau$ (parametri in `raffreddamento_intermedio_fissa_da_finale`) sono salvati per serie; 3. ogni **raffreddamento intermedio** si fitta con quei valori **fissi**, e solo $A$ libera ($k = 1$). Motivo: un intermedio ha 12–25 campioni con scarti casuali, e $\tau$ da solo non è identificabile (su dati puliti lo sarebbe: i test lo verificano). Se il finale non dà un fit valido o la lista è vuota gli intermedi restano liberi. ## 6. Scelta del modello (`analisi.scegli`) Per ogni (dominio, serie) con più modelli candidati: 1. **Scarto**: via i fit non validi (§5.6). Se non ne resta nessuno: `indeterminato`. 2. **Adeguatezza**: sono *adeguati* i fit con $\mathrm{RMSE} \le \lambda\,\sigma$. Se ce n'è almeno uno si sceglie tra questi, altrimenti tra tutti i validi (e il dominio è segnato *non adeguato*). 3. **Parsimonia**: tra i candidati con $\mathrm{AICc} \le \mathrm{AICc}_{\min} + \delta$ vince quello con **meno parametri** (a parità, AICc minore). Un dominio con meno di `n_min_punti` campioni non viene fittato (`indeterminato`); un modello con più parametri di $n - 2$ non viene provato. Con un solo modello candidato (raffreddamento) la scelta è lui, se valido: il flag *adeguato* resta informativo. ## 7. Riscaldamento come salto (`analisi._gradino`) Il dominio di riscaldamento ha 2–3 campioni: nessun fit. Si riporta, per ogni serie, con $i_0, i_1$ gli estremi del dominio: $$\Delta = y_{i_1-1} - y_{i_0-1}, \qquad \text{durata} = t_{i_1-1} - t_{i_0-1}$$ cioè il salto dal campione **prima** della salita all'ultimo campione di salita, e il tempo che separa i due (per 2 campioni di salita ≈ 2 intervalli, circa 300 ms). Poiché parte della salita può cadere sul campione precedente, $\Delta$ è una stima per difetto dell'ampiezza del gradino (A23). `sigma` vale `null` e `fit` è vuoto. ## Esempio di calcolo completo (seed 1, `T_C`, raffreddamento finale) 1. $n = 651$ campioni da $t_0 = 22.12$ s (dopo `scarta_dopo_salto_n = 1`, 650 usati nel fit); $\sigma = 0.145$ °C. 2. Griglia su $\tau \in [0.05 L,\ 50 L]$ con $L \approx 97.8$ s → $\tau_0$ vicino a 16 s, $c, A$ lineari. 3. Raffinamento `soft_l1` con scala $3\sigma = 0.44$ °C: $c = 23.00 \pm 0.12$, $A = 179.6 \pm 0.48$, $\tau = 16.08 \pm 0.07$ s. 4. Metriche: $\mathrm{RMSE} = 0.356$, $\rho_1 = 0.71$ (> 0.3: intervalli moltiplicati per $\sqrt{1.71/0.29} = 2.43$), $R^2 = 0.9999$. 5. $\mathrm{RMSE} > 1.3 \cdot 0.145 = 0.189$: **non adeguato** (residui autocorrelati per gli scarti casuali), ma unico modello valido → scelto. 6. Gli intermedi 3 e 5 usano $c = 23.00$, $\tau = 16.08$ e stimano solo $A$ ($84.3$ e $148.3$).