Files
EquationMiner/docs/algoritmi.md
T
davideandClaude Sonnet 5.5 f28bddc5a2 Documentazione completa in docs/: assunzioni, algoritmi, moduli, configurazione, output, test, limiti
- 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>
2026-10-29 10:44:41 +01:00

221 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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$).