Files

111 lines
9.4 KiB
Markdown
Raw Permalink 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.
# Test
```bash
.venv/bin/pytest # tutti (≈ 50 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)
```
## Tre livelli
| Livello | File | Dati | Richiede |
|---|---|---|---|
| **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 `extern/gen_data`, confrontati con i suoi **parametri veri** | `extern/gen_data`, da scaricare con `./scarica_gen_data.sh` (altrimenti i test sono saltati) |
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, **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`.
## Validazione contro i parametri veri (`test_validazione.py`)
`extern/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 |
|---|---|
| `test_tre_gradini` | 3 gradini → 7 domini: riposo, (riscaldamento, raffreddamento) × 3 |
| `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`, `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_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, vuoto, intervallo vuoto o tipo sconosciuto → `ValueError` |
## `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 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 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, il motivo cita `n_min_punti` |
| `test_dati_non_finiti_non_convergono_senza_eccezioni` | `NaN` in `y` → ogni modello restituisce `convergenza` falsa, senza eccezioni (regressione: `TypeError`) |
| `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: 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; TOML non valido; `unita_tempo` e `serie_riferimento` non ammessi; CSV senza righe di dati o con valori non numerici.
## `test_report.py` — forma del report e dei grafici
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); equazione `lineare` con segno; salto «misurato sui campioni»; grafici con campioni esclusi (`scarta_dopo_salto_n` > 0) e modelli alternativi tratteggiati.
## `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 o senza dati → exit 4; configurazione inesistente → exit 3; `domini.toml` non contiguo o inesistente → exit 6.
## Cosa non è coperto
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.