# `fitseg` — domini di riscaldamento/raffreddamento e equazione per ciascuno — specifica **Obiettivo**: leggere un CSV (`t_ms,T_C,dT_C`, p.es. prodotto da `gen_data`), riconoscere quando c'è **riscaldamento** (flusso in entrata: gradini di temperatura) e quando c'è **raffreddamento**, dividere il tempo in **domini**, e per ogni (dominio, serie) trovare con minimi quadrati non lineari l'equazione che approssima meglio i dati. Valori configurabili: solo in [`config.toml`](../config.toml). Uso pratico: [USO.md](USO.md). ## 1. Stato di implementazione | Parte | Stato | |---|---| | Riconoscimento dei domini (§3), fit (§5), regola di scelta (§5.3), report, grafici, `valuta` | **implementato** (`src/fitseg/`) | | Modifica manuale dei domini (`domini.toml` + `analizza --domini`) | **implementato** (senza editor interattivo) | | PELT/fusione BIC per cambi di regime non legati ai gradini | **eliminato**: i domini derivano dai gradini di riscaldamento (§3) | ## 2. Flusso ``` CSV ─► [1 DOMINI] gradini ⇒ riposo | riscaldamento… | raffreddamento ─► lavoro/domini.toml (modificabile) ▼ [2 FIT] per ogni (dominio, serie): tutti i modelli abilitati ─► regola di scelta ─► lavoro/out/ ▼ [3 RIEPILOGO] report.md, equazioni.md, modello_finale.json, risultati.json, fit_.png ``` Il **motore Python** fa tutti i calcoli (deterministici, seed in config); la scelta del modello segue una regola automatica (§5.3). ## 3. Domini (`fitseg domini`) Struttura fisica del segnale (vedi `gen_data/docs/SPEC.md`): T parte dall'ambiente, **gradini istantanei positivi** (riscaldamento, 2–4 s uno dall'altro) ognuno seguito da un decadimento esponenziale; dopo l'ultimo gradino resta solo il **raffreddamento** esponenziale verso l'ambiente. Durante il riscaldamento e il raffreddamento compaiono anche scarti casuali (±4 °C, rientro < 1 s). Algoritmo (nessun LLM), sulla serie `serie_riferimento`: 1. `d = diff(y)`; gradino = `d > soglia_gradino_c`; rilevazioni più vicine di `distanza_min_salti_campioni` sono lo stesso gradino. 2. Il gradino parte dal primo campione già in salita: si arretra finché `d > soglia_bordo_sigma · σ_d` (σ_d = MAD dei salti). Quel campione apre il nuovo dominio; i primi `scarta_dopo_salto_n` campioni del dominio sono esclusi dal fit (transizione del sensore). 3. Tipi: prima del primo gradino `riposo`; tra due gradini `riscaldamento`; dopo l'ultimo `raffreddamento`. Senza gradini: un solo `riposo`. Ipotesi: tempo crescente (H1); rumore circa gaussiano con σ stimato per dominio da `1.4826·MAD(d²y)/√6` (H2); le serie condividono i domini (H3); i gradini sono salti positivi ≫ rumore e scarti casuali (H4, `soglia_gradino_c` ≫ `salti_max_c` del generatore). `domini.toml` (scritto in `lavoro/`, modificabile; `analizza --domini FILE` lo usa al posto della proposta): ```toml [[dominio]] tipo = "riscaldamento" # riposo | riscaldamento | raffreddamento t0_s = 15.8 # incluso t1_s = 17.76 # escluso (ultimo dominio: fino a fine dati) ``` I domini devono essere contigui; in caso contrario exit 6. ## 4. Modelli (`modelli.py`, t' = t − t0 del dominio) | Id | Equazione | Parametri | |---|---|---| | `costante` | `y = a` | a | | `lineare` | `y = a + b·t'` | a, b | | `quadratico` | `y = a + b·t' + c·t'²` | a, b, c | | `esp_decad` | `y = c + A·exp(−t'/τ)` | c, A, τ | | `esp_doppio` | `y = c + A1·exp(−t'/τ1) + A2·exp(−t'/τ2)`, τ1 < τ2 | c, A1, τ1, A2, τ2 | | `logistica` | `y = c + A / (1 + exp(−(t'−t_m)/w))`, 0 ≤ t_m ≤ L | c, A, t_m, w | Nuovo modello = nuova voce nel registro `MODELLI` e nuova riga qui. Nel raffreddamento `c` è la temperatura asintotica (≈ ambiente) e `τ = 1/k`; nel riposo `costante`/`lineare` descrivono la baseline. ## 5. Fit 1. Dati del dominio senza i primi `scarta_dopo_salto_n` campioni (se non `riposo`); tempo traslato. 2. Partenze: lineari per i polinomi; **proiezione delle variabili** per `esp_decad`/`esp_doppio` (griglia log-spaziata su τ tra `tau_min_rel·L` e `tau_max_rel·L`, L = durata, ampiezze per minimi quadrati lineari); `n_start` partenze casuali per `logistica`. 3. `scipy.optimize.least_squares` con perdita `perdita` (robusta `soft_l1`, scala `scala_perdita_sigma·σ`) e limiti sui parametri. 4. Incertezze: `s²(JᵀJ)⁻¹`, IC 95 % = ±1.96σ_p; con `correggi_autocorrelazione` e ACF1 > `soglia_acf` moltiplicate per `√((1+ρ)/(1−ρ))`. 5. Metriche: RSS, RMSE, R², AICc, BIC, ACF1 dei residui, parametri a limite, numero di condizione (jacobiano normalizzato). **Non identificabile** se condizione > `soglia_condizione` oppure un parametro (escluso il primo) ha IC 95 % > |valore|. ### 5.3 Regola di scelta (`selezione.py`) Scarta non convergenti, con parametri a limite o non identificabili. Se esistono modelli con `RMSE ≤ rmse_vs_rumore_max·σ` ("adeguati") si sceglie tra questi, altrimenti tra tutti i validi; tra i modelli entro `soglia_delta_aicc` dal miglior AICc vince quello con meno parametri. Se nessun modello è valido il dominio è **indeterminato**; un dominio con meno di `n_min_punti` campioni non viene fittato. Un modello scelto ma con RMSE > soglia·σ è segnalato come **non adeguato** nel report. ## 6. CLI | Comando | Funzione | |---|---| | `fitseg domini CSV` | Propone i domini, stampa la tabella, scrive `lavoro/domini.toml` | | `fitseg analizza CSV [--domini FILE]` | Domini + fit + scelta + report in `lavoro/out/` | | `fitseg modelli` | Elenca il catalogo | | `fitseg valuta modello_finale.json --t 12.5 …` | Valuta il modello a tratti agli istanti dati (NaN se indeterminato) | Opzioni globali (prima del sottocomando): `--config PATH` (default `config.toml`), `--lavoro DIR`. Exit code: 0 ok · 2 uso errato · 3 config non valida · 4 CSV illeggibile · 6 domini non validi. ## 7. File in `lavoro/` | File | Contenuto | |---|---| | `domini.toml` | proposta (o input) dei domini | | `dati.csv` | copia del CSV analizzato | | `out/risultati.json` | tutti i fit per (dominio, serie), con metriche e scelta | | `out/modello_finale.json` | per dominio e serie: modello scelto e parametri | | `out/report.md`, `out/equazioni.md` | tabelle di confronto, equazioni con IC 95 % | | `out/fit_.png` | dati, fit a tratti (colore per tipo di dominio) e residui | ## 8. Test (`uv run pytest`, dati in `dati/gen_.csv`) | ID | Test | Esito | |---|---|---| | T1 | Domini sui seed 1–3 | `riposo, riscaldamento, riscaldamento, raffreddamento` ✔ | | T2 | Raffreddamento | `esp_decad` scelto per T e ΔT, τ entro il 10 % di 15.9 s ✔ (seed 1: τ = 16.08 ± 0.07 s) | | T3 | Riposo | `costante` per ΔT ✔ | | — | da fare | più seed, robustezza a config diverse (nessun gradino, un solo gradino), `domini.toml` manuale, riproducibilità | ## 9. Limiti noti - **Residui autocorrelati nel raffreddamento** (ACF1 ≈ 0.7, RMSE ≈ 0.36 contro σ ≈ 0.15): scarti casuali e deriva lenta non sono nel modello; i parametri sono buoni ma gli IC 95 % sono troppo stretti (nonostante la correzione) e il dominio risulta "non adeguato". - **Domini di riscaldamento corti** (14–26 campioni, 2–4 s): il decadimento è quasi lineare, τ non identificabile; vince un modello semplice (`lineare`, talvolta `logistica`, che qui è solo una curva flessibile e non ha significato fisico). - La soglia `soglia_gradino_c` va adattata se i gradini reali sono più piccoli di 20 °C o gli scarti più grandi. - Il vincolo di continuità tra domini è solo ignorato: ogni dominio è fittato in modo indipendente.