9.4 KiB
Test
.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, «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); 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 §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).
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.