Semplifica: CLI a due comandi, moduli piatti, solo modelli usati; test unitari su segnale sintetico

- rimossi scripts/, stadi.py, docs/USO.md, modelli non usati, partenze casuali, output ridondanti
- uscita unica (report.md con grafici per dominio, risultati.json, proposta.png)
- test: unitari su segnale sintetico, integrazione su dati/gen_*.csv (saltati se mancano)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-29 10:13:01 +01:00
1 parent b8c8aeb60e
commit d92bcd198b
22 files changed
+678 -683

No files matched your search

+39 -90
View File
@@ -1,35 +1,21 @@
# fitseg
Legge un CSV `t_ms,T_C,dT_C` (temperatura di un sensore IR che si scalda e si raffredda), riconosce quando c'è **riscaldamento** (gradini di flusso in entrata) e quando c'è **raffreddamento**, divide il tempo in **domini** e trova per ogni dominio e ogni serie l'equazione che approssima meglio i dati, con minimi quadrati non lineari.
Legge un CSV `t_ms,T_C,dT_C` (temperatura di un sensore IR che si scalda e si raffredda), riconosce **riposo**, **riscaldamento** (gradini di flusso in entrata) e **raffreddamento**, lo divide in **domini** e trova per ogni dominio e ogni serie l'equazione che lo approssima meglio, con minimi quadrati non lineari.
Risultato per ogni dominio: modello scelto, parametri con intervallo di confidenza al 95 %, metriche (RMSE, R², AICc, BIC, autocorrelazione dei residui), grafici e un report in Markdown.
## Requisiti
- Python ≥ 3.11
- [uv](https://docs.astral.sh/uv/) (consigliato): `curl -LsSf https://astral.sh/uv/install.sh | sh`
- Dipendenze: `numpy`, `scipy`, `matplotlib` (più `pytest` e `ruff` per lo sviluppo)
Risultato per dominio: modello scelto, parametri con intervallo di confidenza al 95 %, metriche (RMSE, R², AICc, BIC, autocorrelazione dei residui), un grafico dati/equazioni e un report Markdown.
## Installazione
Python ≥ 3.11 e [uv](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`):
```bash
uv venv .venv
uv pip install -e ".[dev]"
uv pip install -e ".[dev]" # numpy, scipy, matplotlib; pytest e ruff per lo sviluppo
```
Il programma si lancia con `.venv/bin/fitseg` (oppure `source .venv/bin/activate` e poi `fitseg`).
## Dati di ingresso
Un CSV con intestazione, tempo crescente e una o più serie:
```
t_ms,T_C,dT_C
151,22.90,0.20
301,22.63,0.03
```
I nomi delle colonne, l'unità del tempo (`ms` o `s`) e il separatore sono in [config.toml](config.toml), sezione `[dati]`.
I CSV di prova vengono dal generatore `../gen_data` (repository separato). I test usano i seed 1, 2 e 3 in `dati/` (cartella ignorata da git):
CSV con intestazione, tempo crescente e le serie indicate in [config.toml](config.toml) (`[dati]`: colonne, unità `ms`/`s`, separatore). I CSV di prova vengono dal generatore `../gen_data`; i test di integrazione usano i seed 1–3 in `dati/` (ignorata da git):
```bash
mkdir -p dati
@@ -41,107 +27,70 @@ done
## Uso
```bash
fitseg domini dati/gen_1.csv # solo i domini: tabella a schermo + lavoro/domini.toml
fitseg analizza dati/gen_1.csv # domini + fit + scelta + report in lavoro/out/
fitseg modelli # catalogo dei modelli con equazioni e parametri
fitseg valuta lavoro/out/modello_finale.json --t 10 30 60 # valuta il modello a tratti in questi istanti
.venv/bin/fitseg domini dati/gen_1.csv # come verrebbero suddivisi i dati: tabella, lavoro/domini.toml, lavoro/proposta.png (nessun fit)
.venv/bin/fitseg analizza dati/gen_1.csv # domini + regressione + report in lavoro/out/
```
`--config PATH` (default `config.toml`) e `--lavoro DIR` (default `lavoro`) vanno **prima** del sottocomando.
Le opzioni globali `--config PATH` (default `config.toml`) e `--lavoro DIR` (default `lavoro`) vanno **prima** del sottocomando:
**Correggere i domini a mano**: apri `lavoro/domini.toml`, modifica i tempi (i domini devono restare contigui) e rilancia
```bash
fitseg --config altra.toml --lavoro prova1 analizza dati/gen_2.csv
.venv/bin/fitseg analizza dati/gen_1.csv --domini lavoro/domini.toml
```
### Correggere i domini a mano
`fitseg domini` (e `analizza`) scrive la proposta automatica in `lavoro/domini.toml`:
```toml
[[dominio]]
tipo = "riscaldamento" # riposo | riscaldamento | raffreddamento
t0_s = 15.8 # incluso
t1_s = 17.76 # escluso (l'ultimo dominio arriva a fine dati)
```
Modifica i tempi, aggiungi o togli blocchi `[[dominio]]` (devono restare contigui) e rilancia:
```bash
fitseg analizza dati/gen_1.csv --domini lavoro/domini.toml
```
## Cosa produce
Tutto in `lavoro/` (ignorata da git):
### Cosa produce (in `lavoro/`, ignorata da git)
| File | Contenuto |
|---|---|
| `domini.toml` | proposta dei domini (o quelli che hai usato) |
| `dati.csv` | copia del CSV analizzato |
| `out/report.md` | con i grafici incorporati (si vede bene in un visualizzatore Markdown); per dominio e serie: modello scelto, parametri ± IC 95 %, metriche, tabella di confronto di tutti i modelli |
| `out/equazioni.md` | solo le equazioni scelte |
| `out/modello_finale.json` | modello e parametri per dominio e serie (input di `valuta`) |
| `domini.toml`, `proposta.png` | domini proposti e grafico dei dati con la suddivisione |
| `out/report.md` | per dominio e serie: grafico, modello scelto con parametri ± IC 95 %, metriche, tabella di tutti i modelli provati |
| `out/dominio<k>_<serie>.png` | dati grezzi contro il modello scelto (colore del dominio) e gli altri modelli validi (tratteggiati), più residui; campioni esclusi dal fit in grigio |
| `out/risultati.json` | tutti i fit con tutte le metriche |
| `out/fit_<serie>.png` | panoramica: dati, fit a tratti (un colore per tipo di dominio) e residui |
| `out/dominio<k>_<serie>.png` | un grafico per dominio e serie: dati grezzi contro il modello scelto (rosso) e gli altri modelli validi (tratteggiati), più residui; i campioni esclusi dal fit sono in grigio |
### Come leggere il report
- **Scelto**: il modello con meno parametri tra quelli equivalenti per AICc (ΔAICc ≤ 2) e validi (convergenti, parametri lontani dai limiti, identificabili).
- **modello non adeguato**: l'RMSE supera 1.3 volte il rumore stimato. Il modello migliore disponibile non spiega tutto: guarda l'ACF1 dei residui.
- **Indeterminato**: nessun modello è valido, oppure il dominio ha meno di `n_min_punti` campioni.
- Nel raffreddamento, `τ` è la costante di tempo e `c` la temperatura asintotica (≈ ambiente per `T_C`, ≈ 0 per `dT_C`).
Come leggere il report: **non adeguato** = RMSE oltre 1.3 volte il rumore stimato (guarda l'ACF1 dei residui); **indeterminato** = nessun modello valido o dominio con meno di `n_min_punti` campioni. Nel raffreddamento `τ` è la costante di tempo e `c` l'asintoto (≈ ambiente per `T_C`, ≈ 0 per `dT_C`).
## Come funziona
1. **Domini**: un salto positivo tra due campioni consecutivi sopra `soglia_gradino_c` è un gradino di riscaldamento. Prima del primo gradino il dominio è `riposo`, tra due gradini `riscaldamento`, dopo l'ultimo `raffreddamento`.
2. **Fit**: in ogni dominio, con il tempo traslato a `t' = t − t0`, si fittano i modelli del suo tipo con `scipy.optimize.least_squares` e perdita robusta. Riscaldamento e raffreddamento sono esponenziali; nel riscaldamento `c` e `τ` sono fissati ai valori del raffreddamento (i tratti sono troppo corti per stimarli) e si stima solo l'ampiezza.
3. **Scelta**: regola automatica (SPEC §5.3), senza intervento umano.
1. **Domini** (`domini.py`): un salto positivo tra due campioni consecutivi sopra `soglia_gradino_c` è un gradino di riscaldamento. Prima del primo gradino: `riposo`; tra due gradini: `riscaldamento`; dopo l'ultimo: `raffreddamento`.
2. **Modelli** (`modelli.py`): `riposo` → `costante`, `lineare`, `esp_decad`; `riscaldamento` e `raffreddamento` → solo `esp_decad` `y = c + A·exp(−t'/τ)`, con `t' = t − t0` del dominio.
3. **Fit** (`fit.py`): `scipy.optimize.least_squares` con perdita robusta; partenza dell'esponenziale da una griglia su τ. I domini di raffreddamento si fittano per primi e nei domini di riscaldamento `c` e `τ` sono **fissati** ai loro valori (stesse costanti fisiche; in 2–4 s di dati con scarti casuali τ non si stima da solo), restando libera solo `A`. Gli IC di `A` sono condizionati ai valori fissi.
4. **Scelta** (`analisi.py`): scarta i modelli non convergenti, con parametri a limite o non identificabili; preferisce i modelli «adeguati» (RMSE ≤ `rmse_vs_rumore_max` × σ rumore); tra quelli entro `soglia_delta_aicc` dal miglior AICc vince chi ha meno parametri.
Dettagli, modelli e limiti: [docs/SPEC.md](docs/SPEC.md). Uso esteso: [docs/USO.md](docs/USO.md).
## Modelli
| Id | Equazione |
|---|---|
| `costante` | `y = a` |
| `lineare` | `y = a + b·t'` |
| `quadratico` | `y = a + b·t' + c·t'²` |
| `esp_decad` | `y = c + A·exp(−t'/τ)` |
| `esp_doppio` | `y = c + A1·exp(−t'/τ1) + A2·exp(−t'/τ2)` |
| `logistica` | `y = c + A / (1 + exp(−(t'−t_m)/w))` |
Per aggiungerne uno: nuova voce in `MODELLI` di [src/fitseg/modelli.py](src/fitseg/modelli.py), nuova riga in SPEC §4 e nome nelle liste di `[fit.modelli_per_tipo]` dei tipi di dominio in cui va provato.
Specifica e limiti: [docs/SPEC.md](docs/SPEC.md).
## Configurazione
Tutti i valori sono in [config.toml](config.toml); non c'è nessun default nel codice (chiave mancante = errore). Quelle che conviene conoscere:
Tutti i valori sono in [config.toml](config.toml), nessun default nel codice (chiave mancante = errore, exit 3). Le principali: `[domini] soglia_gradino_c` (20 °C), `scarta_dopo_salto_n` (campioni di transizione esclusi dal fit), `n_min_punti`; `[fit.modelli_per_tipo]` (modelli per tipo di dominio); `[fit] riscaldamento_fissa_da_raffreddamento` (`["c", "tau"]`, `[]` = parametri liberi), `soglia_delta_aicc`, `rmse_vs_rumore_max`.
| Chiave | Effetto |
|---|---|
| `[domini] soglia_gradino_c` | salto minimo che segna un gradino (20 °C); da adattare se i gradini reali sono più piccoli |
| `[domini] scarta_dopo_salto_n` | campioni esclusi all'inizio di un dominio dopo un gradino (transizione del sensore) |
| `[domini] n_min_punti` | campioni minimi per dominio |
| `[fit.modelli_per_tipo]` | modelli provati per tipo di dominio (riposo: costante/lineare/esponenziale; riscaldamento e raffreddamento: esponenziale) |
| `[fit] riscaldamento_fissa_da_raffreddamento` | parametri dei domini di riscaldamento fissati al valore del raffreddamento (`["c", "tau"]`; `[]` = liberi) |
| `[fit] soglia_delta_aicc` | entro questa differenza vince il modello con meno parametri |
| `[fit] rmse_vs_rumore_max` | soglia di «adeguato» (RMSE ≤ valore × σ rumore) |
| `[fit] seed` | seme delle partenze multiple: stessi dati e stessa config danno gli stessi risultati |
Nuovo modello: voce in `MODELLI` di [src/fitseg/modelli.py](src/fitseg/modelli.py), riga in SPEC §4 e nome in `[fit.modelli_per_tipo]`.
## Test
```bash
.venv/bin/pytest # tutti
.venv/bin/pytest -k raffreddamento # uno solo
.venv/bin/pytest # tutti (≈ 4 s)
.venv/bin/pytest tests/test_fit.py # un file
.venv/bin/pytest -k raffreddamento # per nome
.venv/bin/ruff check src tests
```
I test verificano, sui seed 1–3, che i domini siano `riposo, riscaldamento, riscaldamento, raffreddamento`, che nel raffreddamento vinca `esp_decad` con τ entro il 10 % di 15.9 s, che nel riscaldamento sia `esp_decad` con `c` e `τ` del raffreddamento e che il riposo sia `costante`. Richiedono `dati/gen_1.csv`, `gen_2.csv`, `gen_3.csv` (vedi sopra).
## Codici di uscita
| File | Verifica | Servono i CSV? |
|---|---|---|
| `test_domini.py` | 3 gradini, nessun gradino, un solo gradino, gradino sotto soglia, confini vicini ai gradini veri, `domini.toml` (andata e ritorno, non contiguo, tipo non valido) | no |
| `test_fit.py` | recupero di τ, `c`, `A` su segnale sintetico (τ = 16 s), parametri fissi, τ non identificabile su tratti corti e rumorosi, analisi completa, dominio troppo corto, riproducibilità | no |
| `test_selezione.py` | regola di scelta | no |
| `test_config_dati.py` | errori di config e CSV | no |
| `test_integrazione.py` | domini e τ su `dati/gen_{1,2,3}.csv`; CLI `domini` e `analizza --domini`; codici di uscita | sì (altrimenti saltati) |
0 ok · 2 uso errato · 3 configurazione non valida · 4 CSV illeggibile o non valido · 6 domini non validi.
I test unitari generano il segnale da soli (`tests/conftest.py`). Prova manuale: `fitseg analizza dati/gen_1.csv` e controlla in `lavoro/out/report.md` 4 domini (riposo, due riscaldamenti, raffreddamento) con τ ≈ 16 s.
Codici di uscita: 0 ok · 3 configurazione non valida · 4 CSV illeggibile/non valido · 6 `domini.toml` non valido.
## Limiti noti
- Nel raffreddamento i residui sono autocorrelati (ACF1 ≈ 0.7) per scarti casuali e deriva lenta del sensore: i parametri sono buoni, ma gli intervalli di confidenza sono troppo stretti e il dominio risulta «non adeguato».
- I domini di riscaldamento sono corti (2–4 s): τ non è identificabile da solo e viene preso dal raffreddamento; gli IC di `A` sono condizionati a quei valori fissi.
- Ogni dominio è fittato in modo indipendente: nessun vincolo di continuità tra domini.
- Provato su 3 seed; non su file senza gradini o con un solo gradino.
- Nel raffreddamento i residui sono autocorrelati (ACF1 ≈ 0.7) per scarti casuali e deriva lenta del sensore: i parametri sono buoni, ma gli IC sono troppo stretti e il dominio risulta «non adeguato».
- Ogni dominio è fittato in modo indipendente (nessun vincolo di continuità); provato su pochi seed e con la config d'esempio di `gen_data`.