Documentazione aggiornata: assunzioni A17/A25, limiti, algoritmi, test, output e figura
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b223cf5fdc
commit
c37ca1e03d
13 files changed
+241
-144
No files matched your search
@@ -2,18 +2,19 @@
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
`fitseg` legge un CSV `t_ms,T_C,dT_C` (temperatura di un sensore IR), lo divide in **domini** (riposo / riscaldamento = la salita rapida di un gradino, 2–3 campioni / raffreddamento = il decadimento dopo ogni gradino) e trova per ogni (dominio, serie) l'equazione che meglio lo approssima. Documentazione, commenti e messaggi sono in italiano: mantieni la lingua.
|
||||
`fitseg` legge un CSV `t_ms,T_C,dT_C` (temperatura di un sensore IR), lo divide in **domini** (riposo / riscaldamento = la salita rapida di un gradino, 2–4 campioni / raffreddamento = il decadimento dopo ogni gradino) e trova per ogni (dominio, serie) l'equazione che meglio lo approssima. Documentazione, commenti e messaggi sono in italiano: mantieni la lingua.
|
||||
|
||||
Fonti di verità (non duplicarne il contenuto): la documentazione in [docs/](docs/README.md) — [assunzioni](docs/assunzioni.md) (A1–A24, richiamate nel codice), [algoritmi](docs/algoritmi.md), [moduli](docs/moduli.md), [configurazione](docs/configurazione.md), [output](docs/output.md), [test](docs/test.md), [limiti](docs/limiti.md) — e [config.toml](config.toml), unica fonte dei valori: nessun default nel codice, ogni chiave nuova va in `CHIAVI` di [src/fitseg/config.py](src/fitseg/config.py) e in [docs/configurazione.md](docs/configurazione.md). Il [README](README.md) è solo un indice con l'avvio rapido. **Quando cambi comportamento aggiorna il file di docs corrispondente** (e le assunzioni se ne cambia una).
|
||||
Fonti di verità (non duplicarne il contenuto): la documentazione in [docs/](docs/README.md) — [assunzioni](docs/assunzioni.md) (A1–A25, richiamate nel codice), [algoritmi](docs/algoritmi.md), [moduli](docs/moduli.md), [configurazione](docs/configurazione.md), [output](docs/output.md), [test](docs/test.md), [limiti](docs/limiti.md) — e [config.toml](config.toml), unica fonte dei valori: nessun default nel codice, ogni chiave nuova va in `CHIAVI` di [src/fitseg/config.py](src/fitseg/config.py) e in [docs/configurazione.md](docs/configurazione.md). Il [README](README.md) è solo un indice con l'avvio rapido. **Quando cambi comportamento aggiorna il file di docs corrispondente** (e le assunzioni se ne cambia una).
|
||||
|
||||
## Comandi
|
||||
|
||||
```bash
|
||||
uv venv .venv && uv pip install -e ".[dev]"
|
||||
.venv/bin/fitseg domini input/gen_1.csv # passo 1: output/gen_1/01_domini/ (domini.png, domini.toml), nessun fit
|
||||
.venv/bin/fitseg analizza input/gen_1.csv [--domini output/gen_1/01_domini/domini.toml] # tutti i passi → output/gen_1/{01_domini,02_sovrapposizione,03_report}
|
||||
.venv/bin/fitseg domini input/gen_7.csv # passo 1: output/gen_7/01_domini/ (domini.png, domini.toml), nessun fit
|
||||
.venv/bin/fitseg analizza input/gen_7.csv [--domini output/gen_7/01_domini/domini.toml] # tutti i passi → output/gen_7/{01_domini,02_sovrapposizione,03_report}
|
||||
.venv/bin/pytest # tutti; singolo: -k raffreddamento o tests/test_fit.py
|
||||
.venv/bin/ruff check src tests
|
||||
./pulisci.sh [-n] [-y] [--solo-output] # svuota output/ e cancella input/*.csv (con conferma); -n dry-run, --solo-output lascia input/
|
||||
```
|
||||
`--config`/`--output` vanno **prima** del sottocomando. `input/` contiene i CSV da analizzare; `output/` e `input/*.csv` sono ignorati da git. Ogni analisi scrive in `output/<nome csv>/` (`01_domini`, `02_sovrapposizione`, `03_report`, vedi `uscita.PASSI`).
|
||||
|
||||
@@ -28,11 +29,18 @@ uv venv .venv && uv pip install -e ".[dev]"
|
||||
|
||||
## Test
|
||||
|
||||
Unitari su un segnale sintetico generato in [tests/conftest.py](tests/conftest.py) (τ = 16 s, 3 gradini, nessun disturbo a salto): non servono i CSV. Integrazione su `input/gen_{1,2,3}.csv` (prodotti da `../gen_data`: `.venv/bin/python genera.py config.toml --seed N -o ...`) e sulla CLI: saltati se i CSV mancano. Verità di `gen_data`: τ = 1/k = 15.9 s (k perturbato del 2 % per seed), 3 gradini a ≈15.7/17.8/21.7 s, ambiente ≈ 23 °C, scarti casuali ≤ 4 °C che rendono i residui autocorrelati (limite noto, non un bug).
|
||||
`.venv/bin/pytest` (≈ 30 s; nessun file esterno richiesto). Unitari su un segnale sintetico in [tests/conftest.py](tests/conftest.py) (τ = 16 s, 3 gradini, nessun disturbo a salto);
|
||||
[tests/test_validazione.py](tests/test_validazione.py) genera 20 seed con `../gen_data` (saltato se manca) e confronta gradini, τ, ampiezze e salti con i **parametri veri** sorteggiati
|
||||
(`genera.sorteggia`): è il test che protegge da regressioni sul comportamento reale. [tests/test_report.py](tests/test_report.py) e [tests/test_cli.py](tests/test_cli.py) controllano report, grafici e CLI su CSV sintetici.
|
||||
`gen_data` sorteggia a ogni seed numero di gradini (1–3), k (τ = 11–20 s), rumore e scarti (≤ 4 °C, residui autocorrelati: limite noto, non un bug).
|
||||
|
||||
## Gotcha
|
||||
|
||||
- La salita del gradino (campioni di transizione del sensore, τ_sensore 0.07 s, dt 0.15 s) appartiene al `riscaldamento`, non al raffreddamento: se `soglia_bordo_sigma` è troppo alta un campione parziale può finire nel raffreddamento (`scarta_dopo_salto_n` lo esclude dal fit).
|
||||
- Raffreddamenti intermedi di 12–25 campioni: con gli scarti casuali τ non è identificabile da solo (su dati puliti sì), quindi è fisso al valore del finale; gli IC di `A` sono condizionati. Senza un finale valido tornano liberi e possono risultare indeterminati.
|
||||
- Cambiando la config di `gen_data` (gradini, rumore) ricontrolla `soglia_gradino_c` e i test di integrazione.
|
||||
- Il gradino si rileva sommando i campioni consecutivi in salita (`soglia_bordo_sigma`): la soglia `soglia_gradino_c` è sulla **salita complessiva**, non sul singolo salto (un gradino su 2 campioni può avere ogni salto sotto soglia). La salita appartiene al `riscaldamento`; se `soglia_bordo_sigma` è troppo alta un campione parziale può finire nel raffreddamento (`scarta_dopo_salto_n` lo esclude dal fit).
|
||||
- Raffreddamenti intermedi corti: con gli scarti casuali τ non è identificabile da solo (su dati puliti sì), quindi c e τ sono fissi ai valori del finale e basta `n_min_punti_parametri_fissi` (4) campioni invece di `n_min_punti` (6); gli IC di `A` sono condizionati. Senza un finale valido tornano liberi e possono risultare indeterminati.
|
||||
- Il salto Δ del riscaldamento è stimato dai modelli adiacenti (`analisi._affina_salti`, `stima` = `"modelli"`); se manca un modello vicino, dalla misura riportata all'istante del gradino con c e τ del finale (`"misura corretta"`); altrimenti dalla misura sui campioni, che è per difetto (fino a ≈ 10–20 %).
|
||||
- **σ si stima con almeno 8 campioni** (`domini.N_MIN_SIGMA`): con meno la MAD di pochi d²y quantizzati può annullarsi e la perdita robusta degenera (fit scartato come «non identificabile»). Sotto 8, `sigma` è `null` e l'adeguatezza `null`. `risultati.json` non deve mai contenere NaN (`uscita._senza_nan`, `allow_nan=False`).
|
||||
- «Adeguato» = scala **robusta** dei residui (1.4826·MAD) ≤ `scala_residui_vs_rumore_max`·σ, non la RMSE: gli scarti casuali gonfiano la RMSE ma non la scala robusta (vedi docs/algoritmi.md §6). `scarta_dopo_salto_n` vale 0 (la salita è già nel riscaldamento).
|
||||
- Gli intervalli di confidenza coprono il τ vero solo nel 73 % dei casi (disturbi non stazionari, concentrati a inizio raffreddamento): correzione a più lag e sandwich HAC provate e scartate (docs/limiti.md). Non cambiare lo stimatore senza rimisurare la copertura.
|
||||
- Cambiando la config di `gen_data` (gradini, rumore) ricontrolla `soglia_gradino_c` e le tolleranze di `tests/test_validazione.py`.
|
||||
- Exit code: 0 ok · 2 uso · 3 config · 4 CSV · 6 domini non validi.
|
||||
@@ -24,8 +24,9 @@ Tutto è spiegato in **[docs/](docs/README.md)**: assunzioni, algoritmi, moduli,
|
||||
|
||||
```bash
|
||||
uv venv .venv && uv pip install -e ".[dev]" # installazione: docs/uso.md §1
|
||||
.venv/bin/fitseg domini input/gen_1.csv # passo 1: come verrebbero suddivisi i dati
|
||||
.venv/bin/fitseg analizza input/gen_1.csv # tutti i passi → output/gen_1/{01_domini,02_sovrapposizione,03_report}
|
||||
.venv/bin/fitseg domini input/gen_7.csv # passo 1: come verrebbero suddivisi i dati
|
||||
.venv/bin/fitseg analizza input/gen_7.csv # tutti i passi → output/gen_7/{01_domini,02_sovrapposizione,03_report}
|
||||
.venv/bin/pytest # test: docs/test.md
|
||||
```
|
||||
I CSV da analizzare vanno in `input/` (dati di prova da `../gen_data`: [docs/uso.md](docs/uso.md) §2). Il report è `output/<csv>/03_report/report.md`.
|
||||
`./pulisci.sh` pulisce i dati: svuota `output/` e cancella `input/*.csv`, con conferma ([docs/uso.md](docs/uso.md) §6).
|
||||
I CSV da analizzare vanno in `input/` (dati di prova: `../gen_data genera.py --seed 7`, vedi [docs/uso.md](docs/uso.md) §2). Il report è `output/<csv>/03_report/report.md`.
|
||||
+1
-1
@@ -6,7 +6,7 @@ Un file per argomento. Per un primo giro leggi **panoramica**, poi **uso**; per
|
||||
|---|---|---|
|
||||
| [panoramica.md](panoramica.md) | cosa fa il programma, il segnale, glossario, un esempio completo | per iniziare |
|
||||
| [uso.md](uso.md) | installazione, dati di ingresso, comandi, correggere i domini a mano, codici di uscita | per usarlo |
|
||||
| [assunzioni.md](assunzioni.md) | ogni assunzione (A1–A24): perché, dove nel codice, cosa succede se è falsa; origine dei dati di prova | per fidarsi (o no) dei risultati |
|
||||
| [assunzioni.md](assunzioni.md) | ogni assunzione (A1–A25): perché, dove nel codice, cosa succede se è falsa; origine dei dati di prova | per fidarsi (o no) dei risultati |
|
||||
| [algoritmi.md](algoritmi.md) | rilevamento dei gradini, domini, fit, incertezze, metriche, scelta del modello, con le formule | per capire i numeri |
|
||||
| [moduli.md](moduli.md) | un modulo alla volta: responsabilità, funzioni, strutture dati, come aggiungere un modello | per modificare il codice |
|
||||
| [configurazione.md](configurazione.md) | ogni chiave di `config.toml`, formato di `domini.toml`, esempi | per cambiare un valore |
|
||||
|
||||
+66
-32
@@ -12,11 +12,11 @@ flowchart TD
|
||||
|
||||
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"]
|
||||
L["<b>1 · Lettura</b><br/>validazione"] --> S["<b>2 · Rumore</b><br/>stima di σ"] --> G["<b>3 · Gradini</b><br/>salite 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 --> RIS["<b>Riscaldamento</b><br/>2–4 campioni"]
|
||||
P1 --> INT["<b>Raffreddamenti intermedi</b><br/>tra due gradini, corti"]
|
||||
P1 --> FIN["<b>Raffreddamento finale</b><br/>l'ultimo, il più lungo"]
|
||||
|
||||
@@ -65,12 +65,12 @@ Nelle formule si usano questi simboli; a destra la chiave di [configurazione.md]
|
||||
|
||||
| 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_g$ | `soglia_gradino_c` | salita complessiva minima che segna un gradino [°C] |
|
||||
| $\theta_b$ | `soglia_bordo_sigma` | soglia, in multipli di $\sigma_d$, sopra la quale un campione è «in salita» |
|
||||
| $\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$ |
|
||||
| $\lambda$ | `scala_residui_vs_rumore_max` | un modello è «adeguato» se $\hat\sigma_\text{res} \le \lambda\,\sigma$ (scala robusta dei residui, §5.5) |
|
||||
| $\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 |
|
||||
@@ -90,19 +90,22 @@ 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.
|
||||
e serve come scala della perdita robusta e come riferimento per l'«adeguatezza» (§6). Ignora la deriva lenta (A14).
|
||||
|
||||
**Servono almeno 8 campioni** (`domini.N_MIN_SIGMA`): con meno, la MAD di pochi $\Delta^2 y$ può annullarsi, perché i valori sono quantizzati e
|
||||
due $\Delta^2 y$ su tre possono coincidere. Con $\sigma \approx 0$ la scala della perdita robusta diventa quasi nulla, ogni residuo è trattato come
|
||||
outlier, lo jacobiano pesato tende a zero e gli intervalli di confidenza esplodono (fit scartato come «non identificabile» pur essendo corretto). Sotto 8
|
||||
campioni $\sigma$ è «non stimabile»: `sigma` vale `null`, la scala della perdita è 1 e l'adeguatezza non è valutabile.
|
||||
|
||||
## 3. Rilevamento dei gradini (`domini.trova_gradini`)
|
||||
|
||||
Sulla `serie_riferimento`:
|
||||
Sulla `serie_riferimento`, con $d_i = y_{i+1} - y_i$ e $\sigma_d = 1.4826\,\mathrm{MAD}(d)$ calcolata su **tutti** i salti:
|
||||
|
||||
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.
|
||||
1. **Campioni in salita**: gli indici con $d_i > \theta_b\,\sigma_d$ (solo salite, A12). Il rumore bianco e la pendenza del raffreddamento restano sotto questa soglia.
|
||||
2. **Raggruppamento**: i campioni in salita a meno di `distanza_min_salti_campioni` indici l'uno dall'altro formano una **salita** (un gradino si
|
||||
distribuisce su 2–4 campioni, A6).
|
||||
3. **Gradino**: una salita è un gradino se la sua **salita complessiva** $y_{\text{ultimo}+1} - y_{\text{primo}}$ supera $\theta_g$ (A5). Si somma su tutta la
|
||||
salita perché ogni singolo salto può restare sotto soglia: un gradino di 38 °C diviso in 19 + 19 è un gradino, e uno scarto casuale (≤ 4 °C) no.
|
||||
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)$.
|
||||
|
||||
@@ -162,6 +165,7 @@ Per i parametri fissi l'incertezza è 0 (A18).
|
||||
|---|---|
|
||||
| $\mathrm{RMSE}$ | $\sqrt{\mathrm{RSS}/n}$ |
|
||||
| $R^2$ | $1 - \mathrm{RSS}/\mathrm{TSS}$, con $\mathrm{TSS} = \sum (y_i - \bar y)^2$ |
|
||||
| $\hat\sigma_\text{res}$ | **scala robusta dei residui**: $1.4826\,\mathrm{MAD}(r)$, con $r$ i residui. Come la perdita del fit, ignora gli scarti casuali (la RMSE no) |
|
||||
| $\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$ |
|
||||
@@ -184,37 +188,67 @@ Per A8 i raffreddamenti hanno lo stesso $\tau$ e lo stesso $c$. L'ordine di elab
|
||||
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.
|
||||
Motivo: un intermedio ha pochi campioni (da 4 a qualche decina) con scarti casuali, e $\tau$ da solo non è identificabile (su dati puliti lo sarebbe: i test lo
|
||||
verificano). Con un solo parametro libero bastano pochi campioni: il minimo per questi fit è `n_min_punti_parametri_fissi` (default 4) invece di
|
||||
`n_min_punti` (6), così anche i tratti tra gradini ravvicinati (1–2 s) si fittano. 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*).
|
||||
2. **Adeguatezza**: sono *adeguati* i fit con $\hat\sigma_\text{res} \le \lambda\,\sigma$ (scala **robusta** dei residui, §5.5). Se ce n'è almeno uno si
|
||||
sceglie tra questi, altrimenti tra tutti i validi (e il dominio è segnato *non adeguato*). Se $\sigma$ non è stimabile (meno di 8 campioni)
|
||||
l'adeguatezza non è valutabile (`adeguato` = `null`) e si sceglie tra tutti i validi.
|
||||
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.
|
||||
Un dominio con meno di `n_min_punti` campioni (`n_min_punti_parametri_fissi` se $c$ e $\tau$ sono fissi) non viene fittato (`indeterminato`); un modello con più di $n - 2$ parametri **liberi** 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`)
|
||||
*Perché la scala robusta e non la RMSE*: gli scarti casuali del sensore gonfiano la RMSE ($\mathrm{RMSE}/\sigma$ da 1.07 a 5.26 per il modello **giusto**) ma
|
||||
non la scala robusta ($\hat\sigma_\text{res}/\sigma$ da 0.97 a 1.40 su 120 raffreddamenti finali). Con un modello sbagliato (lineare o costante sullo stesso tratto) la scala
|
||||
robusta vale da 11 a 100 volte $\sigma$, quindi discrimina bene. Con la RMSE solo il 27 % dei fit corretti risultava «adeguato»; con la scala robusta il 100 %.
|
||||
|
||||
Il dominio di riscaldamento ha 2–3 campioni: nessun fit. Si riporta, per ogni serie, con $i_0, i_1$ gli estremi del dominio:
|
||||
## 7. Riscaldamento come salto (`analisi._gradino`, `analisi._affina_salti`)
|
||||
|
||||
$$\Delta = y_{i_1-1} - y_{i_0-1}, \qquad \text{durata} = t_{i_1-1} - t_{i_0-1}$$
|
||||
Il dominio di riscaldamento ha 2–4 campioni: nessun fit. Per ogni serie si riporta il salto $\Delta$ con due stime.
|
||||
|
||||
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.
|
||||
**Misura sui campioni** (`delta_misurato`), con $i_0, i_1$ gli estremi del dominio:
|
||||
|
||||
## Esempio di calcolo completo (seed 1, `T_C`, raffreddamento finale)
|
||||
$$\Delta_\text{misurato} = y_{i_1-1} - y_{i_0-1}, \qquad \text{durata} = t_{i_1-1} - t_{i_0-1}$$
|
||||
|
||||
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$).
|
||||
cioè dal campione **prima** della salita all'ultimo campione di salita. È una stima **per difetto**: l'ultimo campione di salita ha già cominciato a decadere
|
||||
(0.3–0.45 s dopo il gradino) e l'errore cresce con la temperatura, fino a ≈ 10 % (A23).
|
||||
|
||||
**Stima dai modelli adiacenti** (`delta`, usata quando disponibile). L'istante del gradino $t_s$ è il punto medio tra l'ultimo campione prima della salita e
|
||||
il primo in salita. Con $f_\text{prima}$ e $f_\text{dopo}$ i modelli scelti per il dominio precedente (riposo o raffreddamento) e per il successivo
|
||||
(raffreddamento), valutati in $t_s$ con $t' = t - t_0$ del rispettivo dominio:
|
||||
|
||||
$$\Delta = f_\text{dopo}(t_s) - f_\text{prima}(t_s)$$
|
||||
|
||||
Per due raffreddamenti, che condividono $c$ e $\tau$, equivale a $A_\text{dopo}\,e^{(t_{0,\text{dopo}}-t_s)/\tau} - A_\text{prima}\,e^{-(t_s-t_{0,\text{prima}})/\tau}$.
|
||||
I campi `da` e `a` sono allora i valori dei due modelli in $t_s$.
|
||||
|
||||
**Misura corretta** (se manca un modello adiacente, p.es. un tratto indeterminato, ma c'è il raffreddamento finale). Si riportano i due campioni all'istante del
|
||||
gradino con la legge di decadimento del finale ($c$ e $\tau$ condivisi):
|
||||
|
||||
$$y_\text{prima}^* = c + (y_{i_0-1} - c)\,e^{-(t_s - t_{i_0-1})/\tau}, \qquad y_\text{dopo}^* = c + (y_{i_1-1} - c)\,e^{(t_{i_1-1} - t_s)/\tau}, \qquad \Delta = y_\text{dopo}^* - y_\text{prima}^*$$
|
||||
|
||||
**Ripiego finale**: la misura sui campioni, se non c'è nemmeno il raffreddamento finale (gradino senza fit dopo), o se la stima si discosta più del 50 % dalla misura
|
||||
(guardia contro fit anomali). Il campo `stima` vale `"modelli"`, `"misura corretta"` o `"misura"`; il report mostra sempre anche la misura. `sigma` vale `null` e `fit` è vuoto.
|
||||
|
||||
**Accuratezza** (validazione su 60 seed di `gen_data`, [test.md](test.md)): con la stima dai modelli l'errore sull'ampiezza vera del gradino ha media +0.33 %, scarto
|
||||
quadratico medio 0.86 % e massimo 3.16 % su 124 salti (con `scarta_dopo_salto_n = 0` nessun salto ha richiesto il ripiego). Con la sola misura l'errore medio era −5.5 % e
|
||||
arrivava a −20 %; la misura corretta, usata da sola, ha errore mediano 1.85 % e massimo 11.9 % (è un ripiego, non un sostituto dei modelli).
|
||||
|
||||
## Esempio di calcolo completo (seed 7 di `gen_data`, `T_C`, raffreddamento finale)
|
||||
|
||||
1. $n = 620$ campioni da $t_0 = 26.79$ s (`scarta_dopo_salto_n = 0`: tutti usati nel fit); $\sigma = 0.115$ °C.
|
||||
2. Griglia su $\tau \in [0.05 L,\ 50 L]$ con $L \approx 93$ s → $\tau_0$ vicino a 12 s, $c, A$ lineari.
|
||||
3. Raffinamento `soft_l1` con scala $3\sigma = 0.35$ °C: $c = 23.92 \pm 0.06$, $A = 163.2 \pm 0.33$, $\tau = 12.12 \pm 0.04$ s (valore vero $\tau = 12.07$ s).
|
||||
4. Metriche: $\mathrm{RMSE} = 0.247$, $\hat\sigma_\text{res} = 0.159$, $\rho_1 = 0.61$ (> 0.3: intervalli moltiplicati per $\sqrt{1.61/0.39} = 2.03$).
|
||||
5. $\hat\sigma_\text{res} = 0.159 \le 1.5 \cdot 0.115 = 0.173$: **adeguato**, anche se $\mathrm{RMSE} = 0.247 > 1.5\,\sigma$ per gli scarti casuali (con il vecchio criterio sarebbe stato scartato).
|
||||
6. Gli intermedi 3 e 5 usano $c = 23.92$, $\tau = 12.12$ e stimano solo $A$ ($76.7$ e $124.5$).
|
||||
7. Il salto del terzo gradino: misurato $+80.9$, stimato dai modelli $+90.2$ (ampiezza vera $+89.4$).
|
||||
+22
-17
@@ -17,20 +17,21 @@ ragionevole, **dove** il codice ne dipende e **cosa succede se è falsa**. Le as
|
||||
|
||||
| ID | Assunzione | Dove | Se è falsa |
|
||||
|---|---|---|---|
|
||||
| **A5** | I **gradini di riscaldamento** sono salti positivi tra due campioni consecutivi molto più grandi del rumore **e** degli scarti casuali: `soglia_gradino_c` (20 °C) $\gg$ ampiezza massima degli scarti (≤ 4 °C) | `domini.trova_gradini` | gradini più piccoli della soglia non sono visti (il segnale resta un unico tratto); scarti più grandi della soglia sono scambiati per gradini. Soluzione: adattare la soglia o correggere `domini.toml` a mano |
|
||||
| **A6** | La **salita dura pochi campioni** (2–3): il sensore ha costante di tempo ($\tau_\text{sensore} \approx 0.07$ s) minore dell'intervallo di campionamento (≈ 0.15 s) | `domini.trova_gradini` (estensione ai campioni adiacenti), `analisi._gradino` | con un sensore più lento la salita avrebbe più campioni: verrebbe comunque raggruppata finché il salto resta sopra `soglia_bordo_sigma`$\cdot\sigma$, ma non sarebbe fittata |
|
||||
| **A5** | I **gradini di riscaldamento** sono salite positive molto più grandi del rumore **e** degli scarti casuali: la **salita complessiva** dei campioni consecutivi in salita supera `soglia_gradino_c` (20 °C), mentre uno scarto casuale è ≤ 4 °C | `domini.trova_gradini` | gradini con salita complessiva sotto la soglia non sono visti (il segnale resta un unico tratto); scarti consecutivi che sommati superano la soglia sono scambiati per gradini. Soluzione: adattare la soglia o correggere `domini.toml` a mano |
|
||||
| **A6** | La **salita dura pochi campioni** (2–4): la costante di tempo del sensore ($\tau_\text{sensore} \approx 0.04$–$0.12$ s) è dell'ordine dell'intervallo di campionamento (≈ 0.15 s), quindi un gradino si distribuisce su 2–4 campioni, anche ognuno sotto la soglia | `domini.trova_gradini` (campioni in salita raggruppati, soglia sulla salita complessiva), `analisi._gradino` | con un sensore più lento la salita avrebbe più campioni: sarebbe comunque raggruppata finché ogni salto supera `soglia_bordo_sigma`$\cdot\sigma_d$ e i campioni in salita distano meno di `distanza_min_salti_campioni`, ma non sarebbe fittata |
|
||||
| **A7** | Dopo ogni gradino la temperatura **decade esponenzialmente** verso un asintoto: $y = c + A\,e^{-t'/\tau}$ | `modelli.MODELLI["esp_decad"]` | residui sistematici, `ρ₁` alto, «modello non adeguato». Con più processi di raffreddamento servirebbe un modello a più esponenziali (non presente, vedi [limiti.md](limiti.md)) |
|
||||
| **A8** | Tutti i raffreddamenti hanno la **stessa costante di tempo $\tau$ e lo stesso asintoto $c$** (stesso processo fisico: dispersione verso l'ambiente). Anche nel generatore il decadimento dopo più gradini è la somma di esponenziali con lo stesso $k$, quindi un'unica esponenziale | `analisi.analizza_domini` (parametri `raffreddamento_intermedio_fissa_da_finale`) | gli intermedi sono forzati a $\tau$ e $c$ del finale: se in realtà differiscono, il fit dell'ampiezza $A$ è distorto e i residui lo mostrano. Per rimuovere il vincolo: `raffreddamento_intermedio_fissa_da_finale = []` |
|
||||
| **A9** | Il **raffreddamento finale** è l'ultimo dominio ed è abbastanza lungo (centinaia di campioni) da stimare $c$, $A$, $\tau$ **liberamente** | `analisi.analizza_domini` | se il CSV finisce subito dopo un gradino, il finale è corto e $\tau$ può risultare non identificabile; allora anche gli intermedi restano liberi e possono essere indeterminati |
|
||||
| **A9** | Il **raffreddamento finale** è l'ultimo dominio ed è abbastanza lungo (centinaia di campioni) da stimare $c$, $A$, $\tau$ **liberamente**; i raffreddamenti intermedi, anche di 4–6 campioni, bastano per stimare la sola $A$ con $c$ e $\tau$ fissi (`n_min_punti_parametri_fissi`) | `analisi.analizza_domini`, `analisi._serie` | se il CSV finisce subito dopo un gradino, il finale è corto e $\tau$ può risultare non identificabile; allora anche gli intermedi restano liberi (con `n_min_punti`) e possono essere indeterminati. Tratti intermedi con meno di 4 campioni (gradini a meno di ≈ 1.2 s) restano indeterminati |
|
||||
| **A10** | Il **riposo** (prima del primo gradino) è una baseline costante, lineare o esponenziale lenta (l'ambiente che si assesta) | `[fit.modelli_per_tipo] riposo` | il modello scelto è solo descrittivo: con una deriva lenta vince `lineare` o `esp_decad` |
|
||||
| **A11** | Ogni dominio è **indipendente**: nessun vincolo di continuità tra domini | `analisi` | i modelli adiacenti possono non combaciare ai confini; il «salto» tra due raffreddamenti è il gradino |
|
||||
| **A12** | Non esistono gradini **discendenti** (un calo brusco non è un evento): si cercano solo salti positivi | `domini.trova_gradini` (`d > soglia`) | un calo brusco resterebbe dentro un raffreddamento, aumentando RMSE e $\rho_1$ |
|
||||
| **A25** | Il **ritardo di risposta del sensore** ($\tau_\text{sensore} \approx 0.04$–$0.12$ s) è trascurabile rispetto a $\tau$ del raffreddamento: il modello `esp_decad` non lo include | `modelli.MODELLI` (`esp_decad`) | un esponenziale filtrato da un filtro del primo ordine ha lo stesso $\tau$ ma ampiezza moltiplicata per $1/(1-\tau_\text{sensore}/\tau)$: $A$ risulta sovrastimata di circa $\tau_\text{sensore}/\tau \approx 0.5$ % (misurato: +0.45 % in media su 60 seed). $\tau$ non è affetta |
|
||||
|
||||
## Rumore
|
||||
|
||||
| ID | Assunzione | Dove | Se è falsa |
|
||||
|---|---|---|---|
|
||||
| **A13** | Il rumore è circa **gaussiano bianco**, con $\sigma$ stimabile per dominio da $\sigma = 1.4826\,\mathrm{MAD}(\Delta^2 y)/\sqrt{6}$ | `domini.sigma_rumore` | $\sigma$ sottostimata: più domini risultano «non adeguati». La stima ignora la parte lenta del rumore (vedi A14) |
|
||||
| **A13** | Il rumore è circa **gaussiano bianco**, con $\sigma$ stimabile per dominio da $\sigma = 1.4826\,\mathrm{MAD}(\Delta^2 y)/\sqrt{6}$ **su almeno 8 campioni**: con meno, la MAD di pochi $\Delta^2 y$ quantizzati può annullarsi | `domini.sigma_rumore` (`N_MIN_SIGMA`) | $\sigma$ sottostimata: l'adeguatezza è valutata male. Sotto 8 campioni $\sigma$ è «non stimabile» (`null`): scala della perdita 1, adeguatezza non valutabile. La stima ignora la parte lenta del rumore (vedi A14) |
|
||||
| **A14** | Sul rumore bianco si aggiungono **disturbi che il modello non descrive**: deriva lenta (Ornstein–Uhlenbeck, $\tau \approx 3.5$ s) e **scarti casuali** (±4 °C, rientro < 1 s) | `fit.adatta` (perdita robusta), `uscita` (residui) | sono trattati come outlier dalla perdita `soft_l1` (non spostano molto il fit) ma lasciano residui autocorrelati ($\rho_1 \approx 0.7$) e intervalli di confidenza troppo stretti. Non è un errore del fit |
|
||||
| **A15** | La **quantizzazione** (T a 0.01 °C, ambiente a 0.1 °C) è trascurabile rispetto al rumore | — | nessun trattamento specifico |
|
||||
|
||||
@@ -39,10 +40,10 @@ ragionevole, **dove** il codice ne dipende e **cosa succede se è falsa**. Le as
|
||||
| ID | Assunzione | Dove | Se è falsa |
|
||||
|---|---|---|---|
|
||||
| **A16** | Gli intervalli al 95 % sono **gaussiani asintotici**: $\pm 1.96\,\sigma_p$ con $\mathrm{cov} = s^2 (J^\top J)^{-1}$, $s^2 = \mathrm{RSS}/(n-k)$ | `fit.adatta` | con pochi campioni o residui non gaussiani sono approssimati |
|
||||
| **A17** | L'autocorrelazione dei residui è di tipo AR(1): gli errori si allargano di $\sqrt{(1+\rho_1)/(1-\rho_1)}$ se $\rho_1$ supera `soglia_acf` | `fit.adatta` | correzione approssimata; con autocorrelazione di altra forma gli intervalli restano stretti |
|
||||
| **A17** | L'autocorrelazione dei residui è di tipo AR(1): gli errori si allargano di $\sqrt{(1+\rho_1)/(1-\rho_1)}$ se $\rho_1$ supera `soglia_acf` | `fit.adatta` | **approssimazione insufficiente**: su 60 seed il $\tau$ vero cade nell'intervallo al 95 % nel 73 % dei casi. La deviazione standard dell'errore su $\tau$ è 3.7 volte l'errore standard non corretto, perché gli scarti casuali sono concentrati all'inizio del raffreddamento (dove la sensibilità a $\tau$ è massima), quindi non stazionari. Provate e scartate: correzione a più lag di Bartlett (77 %) e stimatore sandwich HAC (≤ 62 %). Vedi [limiti.md](limiti.md) |
|
||||
| **A18** | Per i parametri **fissi** l'incertezza non si propaga: gli intervalli di $A$ nei raffreddamenti intermedi sono **condizionati** a $c$ e $\tau$ del finale | `fit.adatta`, `uscita._params` | sono sottostimati rispetto all'incertezza totale |
|
||||
| **A19** | AICc e BIC si calcolano da $\mathrm{RSS}$ come se i residui fossero gaussiani, anche se il fit usa la perdita robusta | `fit.adatta` | confronto tra modelli approssimato; sensibile agli outlier |
|
||||
| **A20** | Le serie con $\sigma$ non finita o nulla (dominio troppo corto) usano scala 1 per la perdita robusta | `fit.adatta` | solo robustezza ridotta |
|
||||
| **A20** | Se $\sigma$ non è stimabile (meno di 8 campioni, o nulla) la scala della perdita robusta è 1 e l'adeguatezza non è valutabile | `fit.adatta`, `analisi.scegli` | solo robustezza ridotta; il fit resta valido |
|
||||
|
||||
## Progetto
|
||||
|
||||
@@ -50,22 +51,26 @@ ragionevole, **dove** il codice ne dipende e **cosa succede se è falsa**. Le as
|
||||
|---|---|---|
|
||||
| **A21** | La **suddivisione in domini la decide l'utente**: la proposta automatica è modificabile (`domini.toml`) e il fit non la cambia mai | `cli._passo_domini`, `domini.leggi_toml` |
|
||||
| **A22** | Il programma è **deterministico**: nessuna partenza casuale, nessun seme | `fit._partenza` (griglia su $\tau$, minimi quadrati lineari) |
|
||||
| **A23** | Il **riscaldamento non si fitta** (2–3 campioni): si riporta il salto $\Delta = y_\text{fine salita} - y_\text{prima}$, che è una stima per difetto del gradino reale perché parte della salita può cadere sul campione precedente | `analisi._gradino` |
|
||||
| **A23** | Il **riscaldamento non si fitta** (2–4 campioni). Il salto $\Delta$ si stima nell'ordine da: modelli dei domini adiacenti valutati all'istante del gradino; misura riportata a quell'istante con $c$ e $\tau$ del raffreddamento finale; misura sui campioni ($y_\text{fine salita} - y_\text{prima}$), che è per difetto (fino a ≈ 10–20 %: parte della salita cade sul campione precedente e l'ultimo campione è già decaduto) | `analisi._gradino`, `analisi._affina_salti` |
|
||||
| **A24** | I **modelli provati** sono fissati per tipo di dominio in configurazione, non scelti dal programma | `[fit.modelli_per_tipo]` |
|
||||
|
||||
## Origine dei dati di prova (`../gen_data`)
|
||||
|
||||
I CSV in `input/gen_<seed>.csv` sono prodotti dal generatore con la sua configurazione d'esempio. Caratteristiche che fondano le
|
||||
assunzioni sopra (dettaglio: `../gen_data/docs/SPEC.md` e `../gen_data/config.toml`):
|
||||
I CSV di prova si generano con `../gen_data`, che a ogni corsa (seed) **sorteggia** i parametri da intervalli: ogni seed è un segnale diverso
|
||||
(dettaglio: `../gen_data/docs/SPEC.md` e `../gen_data/config.toml`). Gli intervalli che fondano le assunzioni sopra:
|
||||
|
||||
| Aspetto | Valore nel generatore | Assunzioni |
|
||||
|---|---|---|
|
||||
| intervallo di campionamento | 150.5 ms con jitter ±0.6 ms | A1, A6 |
|
||||
| gradini di riscaldamento | 3, a ≈ 15.7, 17.8, 21.7 s, ampiezza ≈ 88, 75, 65 °C (perturbati per seed) | A5, A7 |
|
||||
| raffreddamento | $k = 0.063\ \mathrm{s^{-1}}$, cioè $\tau = 15.9$ s (perturbato del 2 % per seed) | A7, A8 |
|
||||
| risposta del sensore | filtro del primo ordine, $\tau_\text{sensore} = 0.07$ s | A6 |
|
||||
| rumore bianco | $\sigma = 0.11$ °C a freddo, $0.20$ a caldo; la stima per dominio dà ≈ 0.08–0.3 | A13 |
|
||||
| deriva lenta | Ornstein–Uhlenbeck, $\sigma = 0.04$ °C, $\tau = 3.5$ s | A14 |
|
||||
| scarti casuali | in riscaldamento: 0.8 Hz, $\sigma$ 2.5 °C; in raffreddamento: 0.25 Hz, media −1 °C; ampiezza ≤ 4 °C, rientro 0.3–0.7 s | A5, A14 |
|
||||
| ambiente | $23.1 - 0.45\,e^{-t/58}$ °C, non sale quando T è alta | A10; $c$ di `dT_C` ≈ 0 |
|
||||
| intervallo di campionamento | 150.5 ms con jitter ±0.6 ms (fisso) | A1, A6 |
|
||||
| numero e istanti dei gradini | 1–3; il primo a 8–25 s, distanza tra gradini 1–6 s | A5, A9 |
|
||||
| ampiezza dei gradini | 40–90 °C | A5, A7 |
|
||||
| raffreddamento | $k = 0.05$–$0.09\ \mathrm{s^{-1}}$, cioè $ au = 1/k = 11$–$20$ s | A7, A8 |
|
||||
| risposta del sensore | filtro del primo ordine, $ au_ ext{sensore} = 0.04$–$0.12$ s | A6 |
|
||||
| rumore bianco | $\sigma = 0.06$–$0.16$ °C a freddo, $0.15$–$0.30$ a caldo | A13 |
|
||||
| deriva lenta | Ornstein–Uhlenbeck, $\sigma = 0.02$–$0.08$ °C, $ au = 2$–$6$ s | A14 |
|
||||
| scarti casuali | in riscaldamento: 0.4–1.2 Hz, $\sigma$ 1.5–3.5 °C, rientro 0.3 s; in raffreddamento: 0.1–0.5 Hz, media −1 °C, $\sigma$ 1.5–3.5 °C, rientro 0.7 s; ampiezza ≤ 4 °C | A5, A14 |
|
||||
| ambiente | 20–26 °C a regime, deriva iniziale ±0.8 °C con $ au$ 30–90 s; non sale quando T è alta | A10; $c$ di `dT_C` ≈ 0 |
|
||||
| `dT_C` | $T - T_\text{amb}$ calcolato dopo rumore e quantizzazione di entrambi i canali | A4 |
|
||||
|
||||
Poiché il sorteggio è riproducibile dal seed (`genera.sorteggia`), per ogni seed si conoscono i **valori veri** (gradini, ampiezze, $\tau$): il test
|
||||
`tests/test_validazione.py` li confronta con i risultati di `fitseg` su 20 seed ([test.md](test.md)).
|
||||
@@ -20,11 +20,12 @@ Si usa un altro file con `fitseg --config altro.toml …`.
|
||||
| Chiave | Default | Significato | Effetto se alzata / abbassata |
|
||||
|---|---|---|---|
|
||||
| `serie_riferimento` | `"T_C"` | serie in cui si cercano i gradini; deve essere in `serie` | — |
|
||||
| `soglia_gradino_c` | 20.0 | salto positivo minimo tra due campioni consecutivi per essere un gradino | alta: gradini piccoli non rilevati; bassa: scarti scambiati per gradini (A5) |
|
||||
| `soglia_bordo_sigma` | 6.0 | la salita si estende ai campioni adiacenti con salto $d_i > \theta_b\,\sigma_d$, dove $\theta_b$ è questa soglia e $\sigma_d = 1.4826\,\mathrm{MAD}$ dei salti ([algoritmi.md](algoritmi.md) §3) | alta: campioni di mezza salita restano nel raffreddamento; bassa: il riscaldamento si allarga troppo |
|
||||
| `distanza_min_salti_campioni` | 3 | candidati più vicini di così appartengono allo stesso gradino | alta: gradini ravvicinati fusi; bassa: una salita su 2 campioni spezzata |
|
||||
| `n_min_punti` | 6 | campioni minimi per fittare un dominio; sotto → `indeterminato` | alta: più domini non fittati |
|
||||
| `scarta_dopo_salto_n` | 1 | campioni esclusi dal fit **all'inizio di ogni raffreddamento** (sicurezza dopo la salita) | 0: nessuna esclusione; alta: perde campioni del decadimento |
|
||||
| `soglia_gradino_c` | 20.0 | **salita complessiva** minima (somma dei salti dei campioni consecutivi in salita, in °C) per essere un gradino | alta: gradini piccoli non rilevati; bassa: scarti scambiati per gradini (A5) |
|
||||
| `soglia_bordo_sigma` | 6.0 | un campione è «in salita» se il salto verso il successivo supera $\theta_b\,\sigma_d$, con $\theta_b$ questa soglia e $\sigma_d = 1.4826\,\mathrm{MAD}$ dei salti ([algoritmi.md](algoritmi.md) §3) | alta: i campioni di una salita distribuita non sono riconosciuti tutti; bassa: il rumore genera falsi campioni in salita |
|
||||
| `distanza_min_salti_campioni` | 3 | campioni in salita a meno di questa distanza (in indici) appartengono alla stessa salita | alta: gradini ravvicinati fusi; bassa: una salita con un campione piatto in mezzo viene spezzata |
|
||||
| `n_min_punti` | 6 | campioni minimi per fittare un dominio con parametri liberi; sotto → `indeterminato` | alta: più domini non fittati |
|
||||
| `n_min_punti_parametri_fissi` | 4 | campioni minimi quando $c$ e $\tau$ sono fissi e si stima solo $A$ (raffreddamenti intermedi corti); sotto → `indeterminato`. Con 4–7 campioni $\sigma$ non è stimabile (servono 8, A13): il fit si fa comunque, l'adeguatezza no | 3: ancora fittabile ma con 1 grado di libertà; alta: tratti corti tra gradini ravvicinati restano indeterminati |
|
||||
| `scarta_dopo_salto_n` | 0 | campioni esclusi dal fit **all'inizio di ogni raffreddamento**. 0 perché la salita è già nel dominio di riscaldamento: su 45 seed con 0 gli errori su $A$, $\tau$ e salti sono i migliori e i tratti indeterminati i meno numerosi; con 2 peggiorano molto | alta: perde campioni del decadimento (e rende indeterminati i tratti corti) |
|
||||
|
||||
## `[fit]` — regressione e scelta (`fit.py`, `analisi.py`)
|
||||
|
||||
@@ -39,7 +40,7 @@ Si usa un altro file con `fitseg --config altro.toml …`.
|
||||
| `correggi_autocorrelazione` | `true` | allarga gli intervalli di $\sqrt{(1+\rho_1)/(1-\rho_1)}$ se $\rho_1$ supera `soglia_acf` (A17) |
|
||||
| `soglia_acf` | 0.3 | soglia di $\rho_1$ per la correzione |
|
||||
| `soglia_delta_aicc` | 2.0 | tra i modelli entro questo $\Delta\mathrm{AICc}$ dal migliore vince quello con meno parametri |
|
||||
| `rmse_vs_rumore_max` | 1.3 | un modello è «adeguato» se $\mathrm{RMSE} \le$ questo valore $\cdot\ \sigma$ |
|
||||
| `scala_residui_vs_rumore_max` | 1.5 | un modello è «adeguato» se $\hat\sigma_\text{res} \le$ questo valore $\cdot\ \sigma$, con $\hat\sigma_\text{res} = 1.4826\,\mathrm{MAD}(r)$ la scala **robusta** dei residui ([algoritmi.md](algoritmi.md) §5.5, §6). Osservato per il modello giusto: 0.97–1.40; per modelli sbagliati: 11–100 |
|
||||
|
||||
### `[fit.modelli_per_tipo]` — modelli provati per tipo di dominio
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 161 KiB |
+6
-4
@@ -4,15 +4,17 @@
|
||||
|
||||
| Limite | Perché | Effetto | Dove |
|
||||
|---|---|---|---|
|
||||
| **Residui autocorrelati nei raffreddamenti** ($\rho_1 \approx 0.5$–$0.7$, $\mathrm{RMSE}$ 1.5–3 volte $\sigma$) | scarti casuali (±4 °C) e deriva lenta non sono nel modello (A14) | i parametri sono buoni ($\tau$ entro pochi % dal valore nominale), ma gli intervalli di confidenza sono troppo stretti nonostante la correzione AR(1) (A17) e i domini risultano «non adeguati» | [assunzioni.md](assunzioni.md) A14, A17 |
|
||||
| **Il $\sigma$ stimato ignora la parte lenta del rumore** | la derivata seconda elimina la deriva (A13) | $\sigma$ sottostimata → «non adeguato» più frequente | A13 |
|
||||
| **Il riscaldamento non è fittato** | 2–3 campioni, meno dei parametri utili (A23) | si riporta solo il salto $\Delta$, che sottostima l'ampiezza reale del gradino perché parte della salita cade sul campione precedente | [algoritmi.md](algoritmi.md) §7 |
|
||||
| **Intervalli di confidenza troppo stretti** | l'errore su $\tau$ ha deviazione standard 3.7 volte l'errore standard calcolato: gli scarti casuali sono concentrati all'inizio del raffreddamento (dove la sensibilità a $\tau$ è massima), quindi non stazionari (A14, A17) | su 60 seed il $\tau$ vero cade nell'intervallo al 95 % nel **73 %** dei casi (88 su 120), con errori comunque sotto il 2 %. **Provati e scartati**: correzione a più lag di Bartlett (77 %) e stimatore sandwich HAC (≤ 62 %, peggio dell'attuale AR(1)). Non si risolve con un fattore di correzione scalare: servirebbe un modello dei disturbi | [assunzioni.md](assunzioni.md) A14, A17 |
|
||||
| **Il $\sigma$ stimato ignora la parte lenta del rumore** | la derivata seconda elimina la deriva (A13) | $\sigma$ sottostimata di ≈ 0–25 % (rapporto stimato/vero 0.73–1.35, mediana 1.00); servono 8 campioni, sotto $\sigma$ è «non stimabile» e l'adeguatezza non valutabile | A13 |
|
||||
| **Il riscaldamento non è fittato** | 2–4 campioni, meno dei parametri utili (A23) | si riporta il salto $\Delta$: dai modelli adiacenti (errore medio +0.33 %, massimo 3.16 % su 124 salti), altrimenti dalla misura corretta con $c$, $\tau$ del finale, altrimenti misurato sui campioni (per difetto, fino a ≈ 10–20 %) | [algoritmi.md](algoritmi.md) §7 |
|
||||
| **Incertezze condizionate nei raffreddamenti intermedi** | $c$ e $\tau$ sono fissi, la loro incertezza non si propaga (A18) | gli intervalli di $A$ sono troppo stretti | A18 |
|
||||
| **Nessun vincolo di continuità** | ogni dominio è indipendente (A11) | gli intermedi non sono vincolati a combaciare col gradino | A11 |
|
||||
| **Un solo esponenziale per raffreddamento** | modello `esp_decad` (A7) | un raffreddamento con due processi (conduzione e convezione) non è descritto bene | A7 |
|
||||
| **Gradini cercati su una sola serie** | `serie_riferimento` (A4) | serie con gradini diversi richiedono due analisi | A4 |
|
||||
| **Soglia dei gradini fissa** | `soglia_gradino_c` in config (A5) | va adattata a gradini più piccoli o scarti più grandi | [configurazione.md](configurazione.md) |
|
||||
| **Provato su pochi dati** | 3 seed di `gen_data` con la configurazione d'esempio | nessuna validazione su dati reali o su rumore molto diverso | [test.md](test.md) |
|
||||
| **Validato solo su dati sintetici** | 60 seed di `gen_data` (gradini, $\tau$, rumore e scarti sorteggiati): gradini 60/60, $\tau$ con scarto quadratico medio 0.39 % (massimo 1.99 %), ampiezze $A$ 0.59 % (massimo 2.14 %), nessun tratto indeterminato | nessuna validazione su dati reali o su rumore molto diverso dagli intervalli del generatore | [test.md](test.md) |
|
||||
| **Piccola sovrastima di $A$** | il modello non include il ritardo di risposta del sensore (A25) | $A$ è sovrastimata di circa $\tau_\text{sensore}/\tau \approx 0.5$ % (misurato +0.45 % in media); $\tau$ non è affetta | A25 |
|
||||
| **Tratti tra gradini troppo ravvicinati** | meno di `n_min_punti_parametri_fissi` (4) campioni, cioè gradini a meno di ≈ 1 s l'uno dall'altro (nel generatore la distanza minima è 1 s: nei 60 seed nessun caso) | il raffreddamento tra i due gradini è indeterminato; i salti adiacenti usano la misura corretta con $c$, $\tau$ del finale, o la sola misura | [configurazione.md](configurazione.md) |
|
||||
|
||||
## Estensioni possibili (non presenti)
|
||||
|
||||
|
||||
+15
-10
@@ -17,7 +17,7 @@ flowchart TD
|
||||
uscita --> modelli
|
||||
```
|
||||
`config.py`, `dati.py`, `domini.py` e `modelli.py` non importano niente del pacchetto; `fit.py` importa solo `modelli`; `analisi.py` importa `domini`, `fit`, `modelli`;
|
||||
`uscita.py` importa `analisi` (per `valido`) e `modelli`. Nessun modulo di calcolo scrive file.
|
||||
`uscita.py` importa `analisi` (per `valido` e `fit_scelto`) e `modelli`. Nessun modulo di calcolo scrive file.
|
||||
|
||||
## Strutture dati comuni
|
||||
|
||||
@@ -33,12 +33,14 @@ flowchart TD
|
||||
| `n`, `n_fit` | campioni del dominio; campioni usati nel fit (meno `scarta_dopo_salto_n` nei raffreddamenti) |
|
||||
| `serie` | `{nome serie: voce}` (sotto) |
|
||||
|
||||
**Voce di serie**: `sigma` (σ del rumore, `null` nel riscaldamento), `fit` (lista dei fit provati), `scelta` (nome del modello o `null`), `motivo`
|
||||
(testo), `adeguato` (bool, assente nel riscaldamento e nei domini troppo corti); nel riscaldamento al posto dei fit c'è `gradino`:
|
||||
`{n, da, a, delta, durata_s}`.
|
||||
**Voce di serie**: `sigma` (σ del rumore; `null` nel riscaldamento e se non è stimabile, cioè meno di 8 campioni), `fit` (lista dei fit provati), `scelta` (nome del modello o
|
||||
`null`), `motivo` (testo), `adeguato` (`true`/`false`; `null` se σ non è stimabile; assente nel riscaldamento e nei domini troppo corti); nel riscaldamento al posto dei fit c'è `gradino`:
|
||||
`{n, da, a, delta, delta_misurato, stima, durata_s, istante_s}`: `delta` è il salto usato; `stima` vale `"modelli"` (differenza dei modelli adiacenti all'istante
|
||||
`istante_s`), `"misura corretta"` (misura riportata a quell'istante con $c$ e $\tau$ del raffreddamento finale) o `"misura"` (sui campioni); `da`/`a` sono i valori
|
||||
prima/dopo usati.
|
||||
|
||||
**Fit** (elemento di `fit`): `modello`, `equazione` (LaTeX), `n_parametri` ($k$ liberi), `n`, `fissi` (nomi dei parametri fissi), `parametri`
|
||||
`{nome: valore}`, `ic95` `{nome: ±}`, `rss`, `rmse`, `r2`, `aicc`, `bic`, `acf1` ($\rho_1$), `condizione`, `parametri_a_limite`, `non_identificabile`,
|
||||
`{nome: valore}`, `ic95` `{nome: ±}`, `rss`, `rmse`, `scala_residui` ($\hat\sigma_\text{res} = 1.4826\,\mathrm{MAD}$ dei residui), `r2`, `aicc`, `bic`, `acf1` ($\rho_1$), `condizione`, `parametri_a_limite`, `non_identificabile`,
|
||||
`convergenza`. Se la minimizzazione fallisce: solo `modello`, `convergenza: false`, `n_parametri`.
|
||||
|
||||
## `cli.py` — punto d'ingresso
|
||||
@@ -76,8 +78,8 @@ Una chiave nuova va aggiunta a `CHIAVI`, a `config.toml` e a [configurazione.md]
|
||||
| Funzione | Cosa fa | Vedi |
|
||||
|---|---|---|
|
||||
| `sigma_robusto(d)` | $1.4826\,\mathrm{MAD}$ di un campione | [algoritmi.md](algoritmi.md) §2 |
|
||||
| `sigma_rumore(y)` | $\sigma$ del rumore bianco da $\Delta^2 y$ | §2 |
|
||||
| `trova_gradini(y, cfg)` | lista di $(i_0, i_1)$ dei campioni di salita | §3 |
|
||||
| `sigma_rumore(y)` | $\sigma$ del rumore bianco da $\Delta^2 y$; `nan` con meno di `N_MIN_SIGMA` = 8 campioni | §2 |
|
||||
| `trova_gradini(y, cfg)` | lista di $(i_0, i_1)$ dei campioni di salita: campioni in salita raggruppati, gradino se la salita complessiva supera `soglia_gradino_c` | §3 |
|
||||
| `proponi(t, serie, cfg)` | lista dei domini (riposo + riscaldamento/raffreddamento per gradino) | §4 |
|
||||
| `scrivi_toml(percorso, domini, t)` | scrive `domini.toml` (tempi con 4 decimali) | [uso.md](uso.md) |
|
||||
| `leggi_toml(percorso, t)` | rilegge e **valida** i domini (tipi, intervalli, contiguità); `ValueError` se non validi | [uso.md](uso.md) |
|
||||
@@ -111,9 +113,11 @@ Dettagli: [algoritmi.md](algoritmi.md) §5. Nessun accesso a file, nessuna casua
|
||||
| Funzione | Cosa fa |
|
||||
|---|---|
|
||||
| `valido(fit)` | convergente, nessun parametro a limite, identificabile, AICc finito |
|
||||
| `scegli(fit_list, sigma, cfg) → (fit, motivo, adeguato)` | regola di scelta ([algoritmi.md](algoritmi.md) §6) |
|
||||
| `_gradino(t, y, d)` | descrive il riscaldamento come salto ([algoritmi.md](algoritmi.md) §7) |
|
||||
| `_serie(ty, yy, sigma, tipo, fissi, cfg)` | fit di tutti i modelli del tipo per una serie e scelta; `indeterminato` se troppo corto |
|
||||
| `fit_scelto(voce)` | il fit scelto di una voce di serie, o `None` (indeterminato, riscaldamento) |
|
||||
| `scegli(fit_list, sigma, cfg) → (fit, motivo, adeguato)` | regola di scelta ([algoritmi.md](algoritmi.md) §6); adeguato = scala robusta dei residui ≤ soglia·σ; `None` se σ non stimabile |
|
||||
| `_gradino(t, y, d)` | descrive il riscaldamento come salto misurato sui campioni ([algoritmi.md](algoritmi.md) §7) |
|
||||
| `_valore(dominio, nome, t)`, `_finale(risultati)`, `_affina_salti(t, serie, risultati, domini)` | valore del modello scelto in $t$; raffreddamento finale; sostituisce la misura del salto con la stima dai modelli adiacenti o, in mancanza, con la misura corretta con $c$, $\tau$ del finale (§7) |
|
||||
| `_serie(ty, yy, sigma, tipo, fissi, cfg)` | fit di tutti i modelli del tipo per una serie e scelta; `indeterminato` se i campioni sono meno di `n_min_punti` (`n_min_punti_parametri_fissi` con parametri fissi) |
|
||||
| `analizza_domini(t, serie, domini, cfg) → list` | un *Risultato* per dominio; ordine: raffreddamento finale, poi gli altri ([algoritmi.md](algoritmi.md) §5b) |
|
||||
|
||||
## `uscita.py` — cartelle, grafici, report
|
||||
@@ -126,6 +130,7 @@ Dettagli: [algoritmi.md](algoritmi.md) §5. Nessun accesso a file, nessuna casua
|
||||
| `sovrapposizione_generale(...)` | passo 2a: `sovrapposizione_<serie>.png` |
|
||||
| `sovrapposizione_domini(...)` | passo 2b: `dominio<k>_<serie>.png` |
|
||||
| `scrivi_report(cartella, risultati, nome_csv, serie_nomi, cfg)` | passo 3: `report.md` e `risultati.json` |
|
||||
| `_senza_nan(x)` | copia con NaN/∞ sostituiti da `None`: `risultati.json` è sempre JSON valido (`allow_nan=False`) |
|
||||
| `_num`, `_params`, `_eq_numerica`, `_salto` | formattazione LaTeX (valori, parametri ± IC, equazione con i valori sostituiti, salto) |
|
||||
|
||||
Il report incorpora le immagini con percorsi **relativi** (`../01_domini/domini.png`, …): cambiando nomi di file o cartelle vanno aggiornati
|
||||
|
||||
+8
-8
@@ -4,7 +4,7 @@ Ogni analisi scrive in `output/<nome del csv>/` (la cartella `output/` è ignora
|
||||
nell'ordine di esecuzione. Chi produce cosa: [moduli.md](moduli.md) (`uscita.py`).
|
||||
|
||||
```
|
||||
output/gen_1/
|
||||
output/gen_7/
|
||||
├── 01_domini/ passo 1 — suddivisione dei dati
|
||||
│ ├── domini.png dati grezzi con i domini (colore per tipo, numerati) + zoom sui gradini
|
||||
│ └── domini.toml domini usati, modificabili a mano ([uso.md](uso.md))
|
||||
@@ -45,10 +45,10 @@ non descritti dal modello (A14), non un errore del fit.
|
||||
|
||||
## `02_sovrapposizione/dominio<k>_<serie>.png`
|
||||
|
||||
- **Riposo e raffreddamento**: punti grigi = campioni esclusi dal fit; punti neri = campioni usati; **linea continua** = modello scelto
|
||||
- **Riposo e raffreddamento**: punti neri = campioni usati nel fit; punti grigi = campioni esclusi (solo se `scarta_dopo_salto_n` > 0); **linea continua** = modello scelto
|
||||
(nel colore del dominio, con $\mathrm{RMSE}$ in legenda); **tratteggiate** = gli altri modelli validi (confronto); sotto i residui del modello scelto.
|
||||
- **Riscaldamento**: la salita (pallini rossi) con 8 campioni di contesto prima e dopo, e nel titolo $\Delta$, numero di campioni e durata. Nessuna curva.
|
||||
- Dominio indeterminato: solo i dati, con «indeterminato» nel titolo.
|
||||
- Dominio indeterminato: solo i dati (un pannello, senza residui), con «indeterminato» nel titolo.
|
||||
|
||||
## `03_report/report.md`
|
||||
|
||||
@@ -61,20 +61,20 @@ con i valori sostituiti): servono un visualizzatore Markdown con supporto matema
|
||||
| 1. Suddivisione in domini | `domini.png` e tabella (#, tipo, $t_0$, $t_1$, campioni) |
|
||||
| 2. Equazioni trovate | criterio di scelta con le soglie correnti; tabella (dominio, tipo, serie, modello, equazione e parametri ± IC) con una riga per dominio e serie; i riscaldamenti hanno il salto |
|
||||
| 3. Sovrapposizione dati–equazioni | un grafico per serie |
|
||||
| 4. Dettaglio per dominio | per ogni dominio e serie: grafico, modello scelto, equazione con i valori ($$…$$), parametri ± IC, $\sigma_\text{rumore}$, $\mathrm{RMSE}$, $R^2$, $\rho_1$, motivo, tabella di tutti i modelli provati ($k$, RMSE, AICc, BIC, $\rho_1$, valido) |
|
||||
| 4. Dettaglio per dominio | per ogni dominio e serie: grafico, modello scelto, equazione con i valori ($$…$$), parametri ± IC, $\sigma_\text{rumore}$ (se stimabile), $\hat\sigma_\text{res}$ (scala robusta dei residui), $\mathrm{RMSE}$, $R^2$ (non per `costante`, per cui non ha senso), $\rho_1$, motivo, tabella di tutti i modelli provati ($k$, RMSE, $\hat\sigma_\text{res}$, AICc, BIC, $\rho_1$, valido); il titolo riporta l'intervallo $t_0 \le t \le t_1$ (primo e ultimo campione del dominio) |
|
||||
|
||||
### Come leggere i messaggi
|
||||
|
||||
| Messaggio | Significato | Cosa fare |
|
||||
|---|---|---|
|
||||
| **salto** (riscaldamento) | salita rapida descritta, non fittata (A23) | niente: $\Delta$ è per difetto |
|
||||
| **salto** (riscaldamento) | salita rapida descritta, non fittata (A23): $\Delta$ stimato dai modelli adiacenti (misura accanto); «misura riportata all'istante del gradino con $c$ e $\tau$ del raffreddamento finale» se manca un modello vicino; «misurato sui campioni» se manca anche quello | niente: la misura sui campioni è per difetto (fino a ≈ 10–20 %), le altre due stime no (errore tipico < 1 %, massimo ≈ 3 % su 60 seed) |
|
||||
| **(fisso)** accanto a un parametro | tenuto costante al valore del raffreddamento finale (A8); l'IC di $A$ è condizionato (A18) | niente; per disattivarlo vedi [configurazione.md](configurazione.md) |
|
||||
| **modello non adeguato** ($\mathrm{RMSE} > 1.3\,\sigma$) | il modello scelto è il migliore tra quelli validi ma non spiega tutta la varianza | guarda $\rho_1$ e i residui: tipicamente scarti casuali (A14) |
|
||||
| **Indeterminato** | nessun modello valido, o meno di `n_min_punti` campioni | verifica la suddivisione (domini troppo corti?) |
|
||||
| **modello non adeguato** ($\hat\sigma_\text{res} > 1.5\,\sigma$) | anche la scala **robusta** dei residui ($1.4826\,\mathrm{MAD}$, che ignora gli scarti casuali) supera il rumore: il modello scelto non spiega i dati | verifica la suddivisione dei domini e i residui: con modello e domini giusti non succede (0 su 120 raffreddamenti finali in validazione) |
|
||||
| **Indeterminato** | nessun modello valido, oppure meno di `n_min_punti` campioni (`n_min_punti_parametri_fissi` nei raffreddamenti con $c$ e $\tau$ fissi): il motivo indica quale | verifica la suddivisione: tra gradini a meno di ≈ 1.2 s restano meno di 4 campioni |
|
||||
| «valido: no» in tabella | fit non convergente, $\tau$ al limite o non identificabile ([algoritmi.md](algoritmi.md) §5.6) | un modello più semplice può essere preferito |
|
||||
|
||||
## `03_report/risultati.json`
|
||||
|
||||
Lista di domini con tutti i fit e tutte le metriche, anche dei modelli scartati. Schema dei campi: [moduli.md](moduli.md), «Strutture dati comuni».
|
||||
È JSON valido (i campi non applicabili, come `sigma` del riscaldamento, sono `null`), adatto a elaborazioni successive (confronti tra
|
||||
È JSON valido anche in presenza di NaN (i campi non applicabili o non calcolabili, come `sigma` del riscaldamento o con meno di 8 campioni, sono `null`), adatto a elaborazioni successive (confronti tra
|
||||
analisi, tabelle).
|
||||
+13
-11
@@ -18,36 +18,38 @@ Il sensore parte dalla temperatura ambiente (**riposo**). Poi il campione viene
|
||||
di secondo (pochi campioni, **riscaldamento**) e subito dopo decade verso l'ambiente (**raffreddamento**). Con più gradini ravvicinati
|
||||
(2–4 s) il decadimento di ognuno è interrotto dal successivo; dopo l'ultimo gradino il raffreddamento prosegue fino all'ambiente.
|
||||
|
||||

|
||||

|
||||
|
||||
*Figura: dati reali di `input/gen_1.csv` (serie `T_C`) con i domini riconosciuti da `fitseg` e le equazioni trovate. In alto il segnale completo, al
|
||||
*Figura: dati reali di `input/gen_7.csv` (serie `T_C`, seed 7 di `gen_data`) con i domini riconosciuti da `fitseg` e le equazioni trovate. In alto il segnale completo, al
|
||||
centro lo zoom sui tre gradini, in basso la tabella che associa a ogni numero tipo, intervallo ed equazione. Il riscaldamento è rosso, il riposo grigio e
|
||||
ogni raffreddamento ha un colore diverso. Con 3 gradini i domini sono $1 + 2\cdot 3 = 7$.*
|
||||
|
||||
Il generatore dei dati di prova è il repository fratello `../gen_data` (modello e parametri: `../gen_data/docs/SPEC.md`). Le sue
|
||||
caratteristiche che contano per `fitseg` sono elencate in [assunzioni.md](assunzioni.md), sezione «Origine dei dati di prova».
|
||||
Il generatore dei dati di prova è il repository fratello `../gen_data` (modello e parametri: `../gen_data/docs/SPEC.md`): a ogni seed sorteggia numero
|
||||
dei gradini (1–3), costante di tempo, rumore e disturbi, quindi ogni file ha una struttura diversa. Le sue caratteristiche che contano per `fitseg` sono
|
||||
elencate in [assunzioni.md](assunzioni.md), sezione «Origine dei dati di prova».
|
||||
|
||||
## Concetti
|
||||
|
||||
| Termine | Significato |
|
||||
|---|---|
|
||||
| **serie** | una colonna del CSV da approssimare (`T_C` temperatura, `dT_C` differenza dall'ambiente). Tutte le serie condividono gli stessi domini |
|
||||
| **gradino** | salto positivo di temperatura tra due campioni consecutivi, sopra `soglia_gradino_c` |
|
||||
| **gradino** | salita positiva di temperatura, distribuita su 2–4 campioni, la cui salita complessiva supera `soglia_gradino_c` |
|
||||
| **dominio** | tratto contiguo di campioni con un tipo: `riposo`, `riscaldamento` o `raffreddamento` |
|
||||
| **riscaldamento** | la salita rapida di un gradino (2–3 campioni): troppo breve per un fit, è descritta come **salto** ($\Delta$, da → a, durata) |
|
||||
| **riscaldamento** | la salita rapida di un gradino (2–4 campioni): troppo breve per un fit, è descritta come **salto** ($\Delta$, da → a, durata) |
|
||||
| **raffreddamento** | il decadimento dopo un gradino, fino al gradino successivo o alla fine; modello esponenziale |
|
||||
| **raffreddamento finale / intermedio** | il finale è l'ultimo dominio (il più lungo, parametri liberi); gli intermedi stanno tra due gradini (corti) e ricevono $c$ e $\tau$ dal finale |
|
||||
| **modello** | una famiglia di equazioni (`costante`, `lineare`, `esp_decad`); vedi [algoritmi.md](algoritmi.md) §5.1 |
|
||||
| **parametro fisso** | parametro tenuto costante nel fit (non conta nei gradi di libertà); marcato «(fisso)» nel report |
|
||||
| **indeterminato** | nessun modello valido, oppure dominio con meno di `n_min_punti` campioni |
|
||||
| **non adeguato** | modello scelto ma con $\mathrm{RMSE} > 1.3\,\sigma$: la forma non spiega tutto il rumore |
|
||||
| **non adeguato** | modello scelto ma con $\hat\sigma_\text{res} > 1.5\,\sigma$ (scala robusta dei residui, che ignora gli scarti casuali): il modello non spiega i dati |
|
||||
| **tempo del dominio** | $t' = t - t_0$, con $t_0$ il tempo del primo campione del dominio |
|
||||
|
||||
## Esempio (seed 1 di `gen_data`)
|
||||
## Esempio (seed 7 di `gen_data`)
|
||||
|
||||
La figura sopra è il risultato di `fitseg analizza input/gen_1.csv` su `T_C`: sul raffreddamento finale $\tau = 16.08 \pm 0.07$ s, $c = 23.00$ °C, $A = 179.6$ °C;
|
||||
i due raffreddamenti intermedi usano gli stessi $c$ e $\tau$ e stimano solo $A$ ($84.3$ e $148.3$ °C); i tre riscaldamenti sono salti di $+82.2$, $+71.1$ e $+60.8$ °C.
|
||||
Il valore nominale del generatore è $\tau = 1/k = 15.9$ s (con $k$ perturbato del 2 % per seed): il fit lo recupera entro l'1 %.
|
||||
La figura sopra è il risultato di `fitseg analizza input/gen_7.csv` su `T_C`: sul raffreddamento finale $\tau = 12.12 \pm 0.04$ s, $c = 23.92$ °C, $A = 163.2$ °C;
|
||||
i due raffreddamenti intermedi usano gli stessi $c$ e $\tau$ e stimano solo $A$ ($76.7$ e $124.5$ °C); i tre riscaldamenti sono salti di $+79.7$, $+71.4$ e $+90.2$ °C.
|
||||
I valori veri del seed 7 sono $\tau = 1/k = 12.07$ s e salti di $79.6$, $71.1$ e $89.4$ °C: il fit li recupera entro lo 0.5 % ($\tau$) e l'1 % (salti).
|
||||
Su 60 seed diversi l'errore su $\tau$ resta sotto il 2 % ([test.md](test.md)).
|
||||
|
||||
## Dove andare avanti
|
||||
|
||||
|
||||
+62
-36
@@ -1,33 +1,52 @@
|
||||
# Test
|
||||
|
||||
```bash
|
||||
.venv/bin/pytest # tutti (≈ 7 s)
|
||||
.venv/bin/pytest # tutti (≈ 30 s)
|
||||
.venv/bin/pytest tests/test_fit.py # un file
|
||||
.venv/bin/pytest -k raffreddamento # per nome
|
||||
.venv/bin/pytest -k validazione # solo la validazione contro i parametri veri (la parte lenta, ≈ 25 s)
|
||||
.venv/bin/pytest -x -q # ferma al primo errore, output breve
|
||||
.venv/bin/pytest -rs # mostra i test saltati e il motivo
|
||||
.venv/bin/ruff check src tests # stile e import (deve passare)
|
||||
```
|
||||
|
||||
## Due livelli
|
||||
## Tre livelli
|
||||
|
||||
| Livello | File | Dati | Se i CSV mancano |
|
||||
| Livello | File | Dati | Richiede |
|
||||
|---|---|---|---|
|
||||
| **Unitari** | `test_domini`, `test_fit`, `test_selezione`, `test_config_dati` | segnale sintetico generato nei test (`conftest.py`) | girano comunque |
|
||||
| **Integrazione** | `test_integrazione` | `input/gen_{1,2,3}.csv` di `gen_data`; CLI | **saltati** (non falliti) |
|
||||
| **Unitari** | `test_domini`, `test_fit`, `test_selezione`, `test_config_dati` | segnale sintetico generato nei test (`conftest.py`) | niente |
|
||||
| **Report e CLI** | `test_report`, `test_cli` | CSV sintetici scritti in una cartella temporanea | niente |
|
||||
| **Validazione** | `test_validazione` | 20 seed generati al volo da `../gen_data`, confrontati con i suoi **parametri veri** | il repository fratello `../gen_data` (altrimenti i test sono saltati) |
|
||||
|
||||
Un clone nuovo non ha i CSV (`input/*.csv` è ignorato da git): i test unitari bastano per lavorare sul codice; per quelli di integrazione si generano
|
||||
i CSV come in [uso.md](uso.md) §2.
|
||||
Nessun test usa i file in `input/`: i test girano su un clone nuovo e non dipendono dal contenuto di quella cartella.
|
||||
|
||||
## Il segnale sintetico (`tests/conftest.py`)
|
||||
|
||||
$$T(t) = T_\text{amb} + \sum_j A_j\,e^{-(t-t_j)/\tau}\,\mathbb{1}[t \ge t_j] + \varepsilon, \qquad \varepsilon \sim \mathcal{N}(0, 0.1^2)$$
|
||||
|
||||
con $T_\text{amb} = 23$, $\tau = 16$ s, gradini $(t_j, A_j) = (15, 80), (18, 70), (22, 60)$, campionamento 0.15 s, 120 s, **nessun scarto casuale**. La verità è nota, quindi si
|
||||
possono controllare numericamente i parametri recuperati. La funzione `segnale(...)` accetta rumore, seme, gradini e durata per costruire casi particolari
|
||||
(nessun gradino, un gradino, gradino sotto soglia). `dT_C` = $T - T_\text{amb}$. Fixture: `cfg` (config reale), `sintetico`, `csv_gen` (un CSV `gen_data`, saltato se assente).
|
||||
con $T_\text{amb} = 23$, $\tau = 16$ s, gradini $(t_j, A_j) = (15, 80), (18, 70), (22, 60)$, campionamento 0.15 s, 120 s, **nessuno scarto casuale e nessuna risposta del sensore**
|
||||
(il gradino è un solo campione). La verità è nota, quindi si controllano i parametri recuperati. `segnale(...)` accetta rumore, seme, gradini e durata per
|
||||
costruire casi particolari; `scrivi_csv(...)` scrive un CSV nel formato di `gen_data`. `dT_C` = $T - T_\text{amb}$. Fixture: `cfg` (config reale), `sintetico`.
|
||||
|
||||
## `test_domini.py` — suddivisione (assunzioni A5, A6, A21)
|
||||
## Validazione contro i parametri veri (`test_validazione.py`)
|
||||
|
||||
`../gen_data` sorteggia a ogni seed i parametri di una corsa (numero e istanti dei gradini, ampiezze, $k$, rumore, scarti: [assunzioni.md](assunzioni.md), «Origine dei dati di
|
||||
prova»). Poiché il sorteggio è riproducibile (`genera.sorteggia(cfg, default_rng(seed))`), per ogni seed si conoscono i valori veri. Il test, per i seed 1–20,
|
||||
genera il CSV, lancia l'analisi e controlla:
|
||||
|
||||
| Controllo | Tolleranza | Osservato su 60 seed |
|
||||
|---|---|---|
|
||||
| numero di gradini trovati = numero vero | esatto | 60/60 |
|
||||
| istante di ogni gradino | < 0.3 s (due campioni) | ≤ 0.15 s |
|
||||
| $\tau$ del raffreddamento finale ($\tau\cdot k = 1$) | ± 3 % | massimo 1.99 %, scarto quadratico medio 0.39 % |
|
||||
| ampiezza $A$ di ogni raffreddamento (su `dT_C`) = somma dei decadimenti dei gradini precedenti | ± 4 % | massimo 2.14 %, media +0.45 % (ritardo del sensore, A25) |
|
||||
| salto $\Delta$ stimato (modelli o misura corretta) = ampiezza vera del gradino | ± 5 % | massimo 3.16 %, media +0.33 % (124 salti, tutti dai modelli) |
|
||||
| un raffreddamento è indeterminato solo se ha meno campioni del minimo con parametri fissi | esatto | nessun caso |
|
||||
|
||||
Misure fatte per decidere, non verificate dal test: il $\tau$ vero cade nell'intervallo al 95 % nel 73 % dei casi ([limiti.md](limiti.md)); la scala robusta dei residui del
|
||||
modello giusto è 0.97–1.40 volte $\sigma$ su 120 raffreddamenti finali. Le tolleranze lasciano margine: se un seed nuovo le supera è un segnale di regressione o di un caso non coperto.
|
||||
|
||||
## `test_domini.py` — suddivisione (A5, A6, A21)
|
||||
|
||||
| Test | Verifica |
|
||||
|---|---|
|
||||
@@ -35,49 +54,56 @@ possono controllare numericamente i parametri recuperati. La funzione `segnale(.
|
||||
| `test_riscaldamento_e_la_salita_rapida` | ogni riscaldamento ha ≤ 3 campioni |
|
||||
| `test_confini_vicini_agli_istanti_dei_gradini` | l'inizio di ogni riscaldamento è entro 0.5 s dal gradino vero |
|
||||
| `test_domini_contigui_e_completi` | primo `i0 = 0`, ultimo `i1 = n`, nessun buco |
|
||||
| `test_senza_gradini_un_solo_riposo` | nessun gradino → solo riposo |
|
||||
| `test_un_solo_gradino` | riposo, riscaldamento, raffreddamento |
|
||||
| `test_senza_gradini_un_solo_riposo`, `test_un_solo_gradino` | nessun gradino → riposo; un gradino → riposo, riscaldamento, raffreddamento |
|
||||
| `test_gradino_sotto_soglia_ignorato` | un salto di 10 °C (< soglia) non è un gradino |
|
||||
| `test_domini_toml_andata_e_ritorno` | scrivere e rileggere `domini.toml` dà gli stessi domini |
|
||||
| `test_domini_toml_non_contigui_rifiutati` | domini non contigui → `ValueError` |
|
||||
| `test_domini_toml_tipo_non_valido` | tipo sconosciuto → `ValueError` |
|
||||
| `test_gradino_distribuito_su_due_campioni_sotto_soglia` | salita di 38 °C divisa in 19 + 19: nessun salto supera la soglia ma la salita complessiva sì → è un gradino |
|
||||
| `test_salita_complessiva_sotto_soglia_non_e_un_gradino` | 16 °C in due campioni (< 20) → nessun gradino |
|
||||
| `test_domini_toml_*` | andata e ritorno; domini non contigui → `ValueError`; tipo sconosciuto → `ValueError` |
|
||||
|
||||
## `test_fit.py` — fit e analisi (A7, A8, A13, A18, A22, A23)
|
||||
## `test_fit.py` — fit e analisi (A7, A8, A9, A13, A18, A22, A23)
|
||||
|
||||
| Test | Verifica |
|
||||
|---|---|
|
||||
| `test_modelli_valutazione` | valori dei modelli; il registro contiene esattamente `costante`, `lineare`, `esp_decad` |
|
||||
| `test_esp_decad_recupera_parametri` (5 seed) | $\tau$ entro il 2 %, $c$ entro 0.2, $A$ entro il 2 %, $\mathrm{RMSE} \approx \sigma$, fit valido |
|
||||
| `test_parametri_fissi_restano_fissi` | $c$ e $\tau$ fissi restano invariati, $k = 1$, $A$ recuperata entro il 3 % |
|
||||
| `test_parametri_fissi_restano_fissi` | $c$ e $\tau$ fissi invariati, $k = 1$, $A$ entro il 3 % |
|
||||
| `test_tau_libero_non_identificabile_su_tratto_corto_e_rumoroso` (5 seed) | su 3 s con disturbi di 2 °C, $\tau$ libero è «non identificabile» (motivo del fissaggio) |
|
||||
| `test_analisi_completa_sintetico` | 7 domini; finale con $\tau$ entro il 3 % e nessun parametro fisso; intermedi con `c`, `tau` fissi uguali al finale; ampiezze $A$ degli intermedi entro il 5 % dal valore atteso |
|
||||
| `test_riscaldamento_descritto_come_salto_non_fittato` | `fit` vuoto, `scelta` assente, $\Delta$ entro il 15 % dell'ampiezza vera, ≤ 3 campioni |
|
||||
| `test_analisi_completa_sintetico` | 7 domini; finale con $\tau$ entro il 3 % e nessun parametro fisso; intermedi con `c`, `tau` fissi uguali al finale e $A$ entro il 5 % |
|
||||
| `test_riscaldamento_descritto_come_salto_non_fittato` | `fit` vuoto, `stima` = `modelli`, $\Delta$ entro il 3 % dall'ampiezza vera, misura entro il 15 % |
|
||||
| `test_salto_con_tratto_adiacente_indeterminato_usa_la_misura_corretta` | tratto intermedio indeterminato: `stima` = `misura corretta`, più vicina al vero della misura e entro il 3 % |
|
||||
| `test_salto_senza_modelli_adiacenti_resta_la_misura` | gradino senza raffreddamento dopo: `stima` = `misura`, $\Delta$ = misura |
|
||||
| `test_raffreddamento_corto_con_parametri_fissi_si_fitta` | tratto intermedio sotto `n_min_punti` ma sopra il minimo con parametri fissi: fittato, $A$ entro il 3 % |
|
||||
| `test_troppo_corto_anche_per_i_parametri_fissi` | tratto di 1–2 campioni → indeterminato, il motivo cita `n_min_punti_parametri_fissi` |
|
||||
| `test_un_solo_raffreddamento_resta_libero` | un solo gradino → il raffreddamento (finale) non ha parametri fissi |
|
||||
| `test_dominio_troppo_corto_indeterminato` | 5 campioni → `indeterminato` |
|
||||
| `test_dominio_troppo_corto_indeterminato` | 5 campioni → indeterminato, il motivo cita `n_min_punti` |
|
||||
| `test_riproducibile` | due esecuzioni identiche danno risultati identici |
|
||||
| `test_sigma_non_stimabile_con_pochi_campioni` | meno di 8 campioni → σ `nan`, da 8 in su finita |
|
||||
| `test_tratto_corto_e_quantizzato_non_diventa_non_identificabile` | regressione: 5 campioni quantizzati (σ ≈ 0 con la MAD) → σ `null`, adeguatezza `null`, fit valido con $A$ corretta |
|
||||
| `test_adeguatezza_ignora_gli_scarti_casuali` | modello giusto con 4 scarti casuali: $\mathrm{RMSE} > 1.3\,\sigma$ ma $\hat\sigma_\text{res} \le 1.5\,\sigma$ → adeguato, $\tau$ entro il 3 % |
|
||||
|
||||
## `test_selezione.py` — regola di scelta ([algoritmi.md](algoritmi.md) §6)
|
||||
|
||||
Su fit finti costruiti a mano: vince il modello con meno parametri entro $\Delta\mathrm{AICc}$; vince il migliore oltre la soglia; si scartano non
|
||||
convergenti, a limite, non identificabili; nessun valido → indeterminato; si preferiscono i modelli adeguati anche con AICc peggiore; nessun adeguato →
|
||||
scelto ma segnalato.
|
||||
Su fit finti: vince il modello con meno parametri entro $\Delta\mathrm{AICc}$; vince il migliore oltre la soglia; si scartano non convergenti, a limite, non identificabili; nessun valido
|
||||
→ indeterminato; si preferiscono i modelli adeguati (scala robusta dei residui) anche con AICc peggiore; l'adeguatezza usa la scala robusta e non la RMSE; nessun adeguato → scelto
|
||||
ma segnalato (`adeguato` = `false`); σ non stimabile → adeguatezza non valutabile (`null`); il motivo cita la parsimonia solo se ci sono più modelli equivalenti.
|
||||
|
||||
## `test_config_dati.py` — errori di configurazione e CSV (A1, A2)
|
||||
|
||||
`config.toml` reale valido; chiave mancante; lista dei modelli vuota per un tipo; file di configurazione inesistente; CSV valido (conversione
|
||||
ms → s); colonna mancante; tempo non crescente.
|
||||
`config.toml` reale valido; chiave mancante; lista dei modelli vuota per un tipo; file di configurazione inesistente; CSV valido (conversione ms → s); colonna
|
||||
mancante; tempo non crescente.
|
||||
|
||||
## `test_integrazione.py` — su `gen_data` e CLI
|
||||
## `test_report.py` — forma del report e dei grafici
|
||||
|
||||
| Test | Verifica |
|
||||
|---|---|
|
||||
| `test_domini_su_csv_gen` (3 seed) | 7 domini nell'ordine atteso |
|
||||
| `test_raffreddamento_tau_nominale` (3 seed) | raffreddamento finale: `esp_decad`, $\tau$ entro il 10 % di 15.9 s |
|
||||
| `test_raffreddamenti_intermedi_ereditano_costanti` (3 seed) | intermedi con `c`, `tau` fissi uguali al finale, $A > 0$ |
|
||||
| `test_cli_domini_e_analizza` | `fitseg domini` scrive solo `01_domini/`; `analizza --domini` scrive sovrapposizione e report; il report rimanda alle immagini giuste; formule principali in LaTeX e `$` bilanciati su ogni riga |
|
||||
| `test_cli_errori` | CSV inesistente → exit 4; configurazione inesistente → exit 3 |
|
||||
Su CSV sintetici: nessun «indeterminato: indeterminato» e il motivo cita il minimo; `risultati.json` è JSON valido anche con domini indeterminati (nessun NaN, `sigma` `null`); `_senza_nan`; la scala robusta dei residui è nel report; nessun $R^2 = -0.0000$ e nessun $R^2$ per `costante`; intervallo del dominio
|
||||
$t_0 \le t \le t_1$; salto «stimato dai modelli adiacenti (misurato sui campioni: …)»; motivo senza clausola di parsimonia con un solo modello; il grafico di un
|
||||
dominio indeterminato ha un solo pannello (senza residui).
|
||||
|
||||
## `test_cli.py` — CLI
|
||||
|
||||
`fitseg domini` scrive solo `01_domini/`; `analizza --domini` (domini corretti a mano) scrive sovrapposizione e report, che rimanda alle immagini giuste e ha le formule
|
||||
principali in LaTeX con i `$` bilanciati su ogni riga; CSV inesistente → exit 4; configurazione inesistente → exit 3.
|
||||
|
||||
## Cosa non è coperto
|
||||
|
||||
Seed oltre i tre di `gen_data`; rumore e gradini molto diversi dalla configurazione d'esempio; dati reali; i grafici (si controllano a vista, [output.md](output.md)).
|
||||
Prova manuale dopo una modifica: `fitseg analizza input/gen_1.csv` e controllare in `report.md` i 7 domini e $\tau \approx 16$ s nel finale.
|
||||
Dati reali; rumore e disturbi molto diversi dagli intervalli di `gen_data`; i grafici nei dettagli (si controllano a vista, [output.md](output.md)).
|
||||
Prova manuale dopo una modifica: `fitseg analizza input/gen_7.csv` e controllare in `report.md` i 7 domini e $\tau \approx 12.1$ s nel finale.
|
||||
+21
-8
@@ -22,21 +22,21 @@ t_ms,T_C,dT_C
|
||||
301,22.63,0.03
|
||||
```
|
||||
|
||||
I CSV di prova si generano con il repository fratello `../gen_data` (modello: `../gen_data/docs/SPEC.md`). I test di integrazione usano i seed 1–3:
|
||||
I CSV di prova si generano con il repository fratello `../gen_data` (modello: `../gen_data/docs/SPEC.md`). A ogni seed il generatore sorteggia numero dei gradini
|
||||
(1–3), costante di tempo, rumore e disturbi: ogni file ha una struttura diversa.
|
||||
|
||||
```bash
|
||||
mkdir -p input
|
||||
for s in 1 2 3; do
|
||||
(cd ../gen_data && .venv/bin/python genera.py config.toml --seed $s -o ../EquationMiner/input/gen_$s.csv)
|
||||
done
|
||||
(cd ../gen_data && .venv/bin/python genera.py config.toml --seed 7 -o ../EquationMiner/input/gen_7.csv)
|
||||
```
|
||||
I test **non** richiedono questi file: generano i dati da soli ([test.md](test.md)).
|
||||
|
||||
## 3. Comandi
|
||||
|
||||
```bash
|
||||
fitseg domini input/gen_1.csv # passo 1: come verrebbero suddivisi i dati (tabella, domini.png, domini.toml), nessun fit
|
||||
fitseg analizza input/gen_1.csv # tutti i passi: domini, equazioni, sovrapposizione, report
|
||||
fitseg analizza input/gen_1.csv --domini output/gen_1/01_domini/domini.toml # con i domini corretti a mano
|
||||
fitseg domini input/gen_7.csv # passo 1: come verrebbero suddivisi i dati (tabella, domini.png, domini.toml), nessun fit
|
||||
fitseg analizza input/gen_7.csv # tutti i passi: domini, equazioni, sovrapposizione, report
|
||||
fitseg analizza input/gen_7.csv --domini output/gen_7/01_domini/domini.toml # con i domini corretti a mano
|
||||
```
|
||||
|
||||
| Opzione | Dove | Significato |
|
||||
@@ -71,6 +71,19 @@ con supporto matematico (formule in LaTeX).
|
||||
|
||||
Gli errori sono stampati su stderr in italiano come `Errore: …`.
|
||||
|
||||
## 6. Sviluppo
|
||||
## 6. Pulizia dei dati
|
||||
|
||||
```bash
|
||||
./pulisci.sh # svuota output/ e cancella input/*.csv, chiede conferma
|
||||
./pulisci.sh -n # dry-run: mostra cosa cancellerebbe, senza cancellare
|
||||
./pulisci.sh --solo-output # pulisce solo output/, lascia input/ com'è
|
||||
./pulisci.sh -y # tutto, senza chiedere conferma
|
||||
```
|
||||
Cancella: il contenuto di `output/` (cartella di `[output] cartella_output`: risultati che si rifanno rilanciando `fitseg analizza`) e i file `*.csv` in `input/`
|
||||
(i dati grezzi: **non** si rigenerano da soli, si rifanno con `../gen_data`, §2, per questo chiede conferma salvo `-y`). Le cartelle e i loro `.gitkeep`
|
||||
restano; i file di `input/` che non sono `.csv` non si toccano. Rifiuta percorsi di output assoluti o con `..`. Codici di uscita: 0 ok (o niente da pulire), 1 annullato o
|
||||
percorso non sicuro, 2 opzione sconosciuta.
|
||||
|
||||
## 7. Sviluppo
|
||||
|
||||
Test e controllo dello stile: [test.md](test.md). Mappa del codice e come aggiungere un modello: [moduli.md](moduli.md).
|
||||
Reference in new issue
Block a user