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:
1 parent
b8c8aeb60e
commit
d92bcd198b
22 files changed
+678
-683
No files matched your search
+40
-101
@@ -1,129 +1,68 @@
|
||||
# `fitseg` — domini di riscaldamento/raffreddamento e equazione per ciascuno — specifica
|
||||
# `fitseg` — 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).
|
||||
**Obiettivo**: leggere un CSV (`t_ms,T_C,dT_C`, p.es. da `gen_data`), riconoscere **riposo**, **riscaldamento** (flusso in entrata: gradini di temperatura) e **raffreddamento**, dividere il tempo in **domini** e trovare per ogni (dominio, serie) l'equazione che lo approssima meglio. Valori configurabili: solo in [`config.toml`](../config.toml). Uso: [README](../README.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
|
||||
## 1. Flusso
|
||||
|
||||
```
|
||||
CSV ─► [1 DOMINI] gradini ⇒ riposo | riscaldamento… | raffreddamento ─► lavoro/domini.toml (modificabile)
|
||||
CSV ─► [DOMINI] gradini ⇒ riposo | riscaldamento… | raffreddamento ─► lavoro/domini.toml (modificabile), proposta.png
|
||||
▼
|
||||
[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
|
||||
[FIT] per (dominio, serie): modelli del tipo ─► regola di scelta ─► lavoro/out/report.md, risultati.json, dominio*_*.png
|
||||
```
|
||||
Il **motore Python** fa tutti i calcoli (deterministici, seed in config); la scelta del modello segue una regola automatica (§5.3).
|
||||
Tutto il calcolo è deterministico (nessuna partenza casuale, nessun LLM).
|
||||
|
||||
## 3. Domini (`fitseg domini`)
|
||||
## 2. Domini (`domini.py`)
|
||||
|
||||
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).
|
||||
Struttura fisica (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 resta solo il **raffreddamento** esponenziale verso l'ambiente. 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`.
|
||||
1. `d = diff(y)` sulla `serie_riferimento`; 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 sono esclusi dal fit (transizione del sensore).
|
||||
3. 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).
|
||||
Ipotesi: tempo crescente; rumore circa gaussiano, σ stimato per dominio da `1.4826·MAD(d²y)/√6`; le serie condividono i domini; i gradini sono salti positivi ≫ rumore e scarti casuali (`soglia_gradino_c` ≫ ampiezza degli scarti).
|
||||
|
||||
`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.
|
||||
`domini.toml` è modificabile (`tipo`, `t0_s` incluso, `t1_s` escluso; ultimo dominio fino a fine dati); i domini devono essere contigui, altrimenti exit 6.
|
||||
|
||||
## 4. Modelli (`modelli.py`, t' = t − t0 del dominio)
|
||||
## 3. 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 |
|
||||
| Id | Equazione | Parametri | Tipi di dominio (`[fit.modelli_per_tipo]`) |
|
||||
|---|---|---|---|
|
||||
| `costante` | `y = a` | a | riposo |
|
||||
| `lineare` | `y = a + b·t'` | a, b | riposo |
|
||||
| `esp_decad` | `y = c + A·exp(−t'/τ)` | c, A, τ | riposo, riscaldamento, raffreddamento |
|
||||
|
||||
Nuovo modello = nuova voce nel registro `MODELLI` e nuova riga qui. I modelli provati dipendono dal tipo di dominio (`[fit.modelli_per_tipo]`): `riposo` → `costante`, `lineare`, `esp_decad`; `riscaldamento` e `raffreddamento` → solo `esp_decad` (sono decadimenti esponenziali). Nel raffreddamento `c` è la temperatura asintotica (≈ ambiente) e
|
||||
`τ = 1/k`; nel riposo `costante`/`lineare` descrivono la baseline.
|
||||
Riscaldamento e raffreddamento sono esponenziali. Nel raffreddamento `c` è l'asintoto (≈ ambiente) e `τ = 1/k`. Nuovo modello = voce in `MODELLI`, riga qui, nome in config.
|
||||
|
||||
## 5. Fit
|
||||
## 4. Fit (`fit.py`)
|
||||
|
||||
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|.
|
||||
2. Partenza: polinomi per minimi quadrati; esponenziale con griglia log-spaziata su τ (`tau_min_rel·L` … `tau_max_rel·L`, L = durata) e `c`, `A` lineari.
|
||||
3. `scipy.optimize.least_squares`, perdita `perdita` (robusta `soft_l1`, scala `scala_perdita_sigma·σ`), limiti su τ.
|
||||
4. IC 95 % = ±1.96·σ_p da `s²(JᵀJ)⁻¹`, moltiplicati per `√((1+ρ)/(1−ρ))` se ACF1 > `soglia_acf` (`correggi_autocorrelazione`).
|
||||
5. Metriche: RSS, RMSE, R², AICc, BIC, ACF1, parametri a limite, numero di condizione. **Non identificabile** se condizione > `soglia_condizione` o un parametro (esclusi `a`, `c`) ha IC 95 % > |valore|.
|
||||
|
||||
### 5.2 Costanti condivise tra riscaldamento e raffreddamento
|
||||
Dopo ogni gradino la temperatura decade con la stessa legge del raffreddamento finale (stessa costante di tempo, stesso asintoto ≈ ambiente).
|
||||
I domini di raffreddamento si fittano per primi; nei domini di riscaldamento i parametri di `riscaldamento_fissa_da_raffreddamento`
|
||||
(default `c` e `tau`) sono **fissati** ai valori del raffreddamento e resta libera solo l'ampiezza `A`. Motivo: in 2–4 s di dati τ non si
|
||||
identifica da solo. Gli IC 95 % di `A` sono condizionati ai valori fissi (l'incertezza di `c` e `τ` non si propaga); i parametri fissi sono
|
||||
marcati "(fisso)" nel report. Se manca un raffreddamento valido, o la lista è vuota, i parametri restano liberi. Con `[]` si torna al fit libero.
|
||||
### 4.1 Costanti condivise tra riscaldamento e raffreddamento (`analisi.py`)
|
||||
Dopo ogni gradino la temperatura decade con la stessa legge del raffreddamento finale. I domini di raffreddamento si fittano per primi; nei domini di riscaldamento i parametri di `riscaldamento_fissa_da_raffreddamento` (default `c`, `tau`) sono **fissati** ai loro valori e resta libera solo `A`. Motivo: con gli scarti casuali del sensore, in 2–4 s di dati τ non si identifica da solo (su dati puliti sì). Gli IC di `A` sono condizionati ai valori fissi; i parametri fissi sono marcati «(fisso)» nel report. Se manca un raffreddamento valido, o la lista è vuota, i parametri restano liberi.
|
||||
|
||||
### 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.
|
||||
### 4.2 Regola di scelta (`analisi.scegli`)
|
||||
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. Nessun modello valido ⇒ **indeterminato**; meno di `n_min_punti` campioni ⇒ non fittato. Scelto ma con RMSE > soglia·σ ⇒ «non adeguato» nel report.
|
||||
|
||||
## 6. CLI
|
||||
## 5. CLI e file
|
||||
|
||||
| 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) |
|
||||
| `fitseg domini CSV` | propone i domini: tabella, `lavoro/domini.toml`, `lavoro/proposta.png` |
|
||||
| `fitseg analizza CSV [--domini FILE]` | domini (proposti o da FILE) + fit + `lavoro/out/{report.md, risultati.json, dominio<k>_<serie>.png}` |
|
||||
|
||||
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.
|
||||
Opzioni globali prima del sottocomando: `--config PATH`, `--lavoro DIR`. Exit: 0 ok · 3 config non valida · 4 CSV illeggibile · 6 domini non validi.
|
||||
|
||||
## 7. File in `lavoro/`
|
||||
## 6. Test
|
||||
|
||||
| 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` | panoramica: dati, fit a tratti (colore per tipo di dominio) e residui |
|
||||
| `out/dominio<k>_<serie>.png` | per dominio e serie: dati grezzi vs modello scelto e altri modelli validi, residui; incorporati nel `report.md` |
|
||||
Unitari su un segnale sintetico (`tests/conftest.py`: τ = 16 s, 3 gradini, nessun disturbo a salto) e di integrazione su `dati/gen_{1,2,3}.csv` (saltati se mancano). Dettaglio: README, «Test». Coperto: domini (3, 0, 1 gradino, sotto soglia, confini), recupero di τ/`c`/`A`, parametri fissi e ereditati dal raffreddamento, regola di scelta, errori di config/CSV/`domini.toml`, CLI.
|
||||
|
||||
## 8. Test (`uv run pytest`, dati in `dati/gen_<seed>.csv`)
|
||||
## 7. Limiti noti
|
||||
|
||||
| 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) |
|
||||
| T2b | Riscaldamento | `esp_decad` con `c` e `τ` fissi uguali a quelli del raffreddamento, `A` > 0 ✔ |
|
||||
| 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): τ non è identificabile da solo, per questo è fissato al valore del raffreddamento (§5.2).
|
||||
Il secondo tratto di riscaldamento ha ACF1 ≈ 0.7 e RMSE ≈ 2 σ (scarti casuali): risulta "non adeguato" anche se la forma è giusta.
|
||||
- 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.
|
||||
- **Residui autocorrelati nel raffreddamento** (ACF1 ≈ 0.7, RMSE ≈ 0.36 contro σ ≈ 0.15): scarti casuali e deriva lenta non sono nel modello; parametri buoni, IC troppo stretti, dominio «non adeguato».
|
||||
- **Riscaldamento**: secondo tratto con ACF1 ≈ 0.7 e RMSE ≈ 2σ per gli scarti: «non adeguato» anche se la forma è giusta.
|
||||
- `soglia_gradino_c` va adattata se i gradini reali sono più piccoli di 20 °C o gli scarti più grandi.
|
||||
- Nessun vincolo di continuità tra domini. Provato su pochi seed e con la config d'esempio di `gen_data`.
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
# Uso di `fitseg`
|
||||
|
||||
## 1. Ambiente
|
||||
```bash
|
||||
uv venv .venv && uv pip install -e ".[dev]" # numpy, scipy, matplotlib, pytest, ruff
|
||||
```
|
||||
|
||||
## 2. Generare i dati
|
||||
I CSV vengono da `../gen_data` (repository separato); `dati/gen_<seed>.csv` ne contiene tre già pronti. Per rigenerarli:
|
||||
```bash
|
||||
cd ../gen_data && .venv/bin/python genera.py config.toml --seed 4 -o ../EquationMiner/dati/gen_4.csv
|
||||
```
|
||||
|
||||
## 3. Analisi
|
||||
```bash
|
||||
.venv/bin/fitseg domini dati/gen_1.csv # solo i domini → lavoro/domini.toml
|
||||
.venv/bin/fitseg analizza dati/gen_1.csv # domini + fit + report in lavoro/out/
|
||||
.venv/bin/fitseg analizza dati/gen_1.csv --domini lavoro/domini.toml # con domini corretti a mano
|
||||
.venv/bin/fitseg valuta lavoro/out/modello_finale.json --t 10 20 50
|
||||
.venv/bin/pytest # test (singolo: -k raffreddamento)
|
||||
```
|
||||
Il report è in `lavoro/out/report.md` (con i grafici dati-contro-equazioni incorporati, uno per dominio e serie): per ogni dominio e serie, modello scelto con parametri ± IC 95 %, metriche e tabella di
|
||||
confronto di tutti i modelli. Leggi sempre la riga "non adeguato" e l'ACF1: indicano residui che il modello non spiega.
|
||||
|
||||
## 4. Configurazione
|
||||
Tutto in [config.toml](../config.toml) (nessun default nel codice). Chiavi di rilievo: `[domini] soglia_gradino_c`,
|
||||
`scarta_dopo_salto_n`, `n_min_punti`; `[fit.modelli_per_tipo]`, `riscaldamento_fissa_da_raffreddamento`, `soglia_delta_aicc`, `rmse_vs_rumore_max`, `seed`.
|
||||
Reference in new issue
Block a user