- docs/SPEC.md sostituito da un file per argomento (indice in docs/README.md) - assunzioni A1-A24 con dove nel codice e cosa succede se false - formule in LaTeX corrette (nessuno spazio prima del dollaro di chiusura), simboli e chiavi in tabella - diagrammi Mermaid per il flusso degli algoritmi e le dipendenze dei moduli - figura dei domini sul seed 1 (docs/img/domini_esempio.png) - README ridotto a indice con avvio rapido; CLAUDE.md e docstring allineati Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
221 lines
12 KiB
Markdown
221 lines
12 KiB
Markdown
# 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["<b>1 · Lettura</b><br/>validazione"] --> S["<b>2 · Rumore</b><br/>stima di σ"] --> G["<b>3 · Gradini</b><br/>salti sopra soglia"] --> D["<b>4 · Domini</b><br/>divisione del tempo"]
|
||
end
|
||
|
||
P1 --> RIP["<b>Riposo</b>"]
|
||
P1 --> RIS["<b>Riscaldamento</b><br/>2–3 campioni"]
|
||
P1 --> INT["<b>Raffreddamenti intermedi</b><br/>tra due gradini, corti"]
|
||
P1 --> FIN["<b>Raffreddamento finale</b><br/>l'ultimo, il più lungo"]
|
||
|
||
subgraph P2["PASSO 2 · Equazioni"]
|
||
direction TB
|
||
F0["<b>5 · Fit</b><br/>costante, lineare,<br/>esponenziale"]
|
||
F1["<b>5 · Fit esponenziale</b><br/>c, A, τ liberi"]
|
||
F2["<b>5b · Fit esponenziale</b><br/>c e τ fissi, solo A libera"]
|
||
SALTO["<b>7 · Salto Δ</b><br/>descritto, non fittato"]
|
||
SC["<b>6 · Scelta del modello</b><br/>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(["<b>PASSO 3</b> · Sovrapposizione e report<br/>dati grezzi contro equazioni<br/>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$).
|