Files
EquationMiner/docs/test.md
T

9.4 KiB
Raw Blame History

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.