Files
EquationMiner/docs/SPEC.md
T

121 lines
7.4 KiB
Markdown
Raw 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.
# `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_<serie>.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_<serie>.png` | dati, fit a tratti (colore per tipo di dominio) e residui |
## 8. Test (`uv run pytest`, dati in `dati/gen_<seed>.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.