From 55722f0e137137376263911bb8390495721b9021 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Thu, 29 Oct 2026 09:51:01 +0100 Subject: [PATCH] Documentazione iniziale: specifica, guida all'uso e CLAUDE.md Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 41 ++++++++++++++++++ docs/SPEC.md | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ docs/USO.md | 27 ++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 CLAUDE.md create mode 100644 docs/SPEC.md create mode 100644 docs/USO.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1da48a0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,41 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +`fitseg` legge un CSV `t_ms,T_C,dT_C` (temperatura di un sensore IR che si scalda e si raffredda), lo divide in **domini** (riposo / riscaldamento = gradini di flusso in entrata / raffreddamento) e trova per ogni (dominio, serie) l'equazione che meglio lo approssima. Documentazione, commenti e messaggi sono in italiano: mantieni la lingua. + +Fonti di verità (leggile prima di cambiare il comportamento, non duplicarne il contenuto): +- [docs/SPEC.md](docs/SPEC.md) — specifica, stato di implementazione (§1), algoritmi, limiti noti. [docs/USO.md](docs/USO.md) — uso pratico. +- [config.toml](config.toml) — unica fonte dei valori. Nessun default nel codice: ogni chiave nuova va aggiunta a `CHIAVI` in [src/fitseg/config.py](src/fitseg/config.py) (mancante ⇒ exit 3) e descritta in SPEC.md. + +## Comandi + +```bash +uv venv .venv && uv pip install -e ".[dev]" +.venv/bin/fitseg domini dati/gen_1.csv # propone i domini → lavoro/domini.toml +.venv/bin/fitseg analizza dati/gen_1.csv [--domini lavoro/domini.toml] # → lavoro/out/ (report.md, modello_finale.json, fit_*.png) +.venv/bin/pytest # tutti i test; singolo: .venv/bin/pytest -k raffreddamento +``` +Opzioni globali `--config`/`--lavoro` vanno **prima** del sottocomando. `lavoro/` è ignorato da git. + +## Dati + +`dati/gen_.csv` sono prodotti dal repository fratello `../gen_data` (`.venv/bin/python genera.py config.toml --seed N -o ...`). Sono i dati dei test: verità nota τ_raffreddamento = 1/k = 15.9 s (k perturbato del 2 % per seed), 3 gradini a ≈15.7/17.8/21.7 s, T ambiente ≈ 23 °C. Il generatore inserisce anche scarti casuali (≤ 4 °C) che rendono i residui autocorrelati: è un limite noto, non un bug del fit. + +## Architettura (`src/fitseg/`) + +Flusso: `domini.proponi` → `pipeline.analizza_domini` (per ogni dominio e serie: `fit.adatta` su ogni modello abilitato → `selezione.scegli`) → `riepilogo` (report, JSON, grafici). `cli.py` è l'unico punto di I/O oltre a `riepilogo`/`dati`. + +- [domini.py](src/fitseg/domini.py): i domini derivano **solo dai gradini positivi** della `serie_riferimento` (`diff > soglia_gradino_c`), con arretramento ai campioni di transizione; non c'è più PELT/fusione BIC (presente nelle prime versioni della spec). `domini.toml` è modificabile e rientra con `--domini`; deve essere contiguo. +- [modelli.py](src/fitseg/modelli.py): registro `MODELLI` (funzione, parametri, equazione testuale, indici dei τ). Nuovo modello = voce qui + riga in SPEC §4. +- [fit.py](src/fitseg/fit.py): `least_squares` con perdita robusta; gli esponenziali partono da una griglia su τ (VARPRO). "Non identificabile" = condizione > soglia **o** IC 95 % > |valore| per un parametro diverso dal primo; la `logistica` ha `t_m` limitato a [0, L] altrimenti degenera in esponenziale. +- [selezione.py](src/fitseg/selezione.py): regola SPEC §5.3 (validi → adeguati se RMSE ≤ soglia·σ → entro ΔAICc vince chi ha meno parametri). +- σ del rumore è stimata **per dominio** da `MAD(d²y)`; il tempo del fit è traslato a `t' = t − t0` del dominio. + +## Gotcha + +- `scarta_dopo_salto_n` deve coprire i campioni di transizione del sensore (τ_sensore 0.07 s, dt 0.15 s ⇒ ≥ 2–3 campioni); se troppo basso, 1–2 punti a ~100 °C dal fit sporcano la fine del dominio. +- Domini di riscaldamento di 14–26 campioni: τ non identificabile; il modello scelto (`lineare`, a volte `logistica`) è solo descrittivo. +- Cambiando `config.toml` del generatore (gradini, rumore) ricontrolla `soglia_gradino_c` e i test in [tests/test_pipeline.py](tests/test_pipeline.py), che usano `dati/gen_{1,2,3}.csv`. +- Il parser CSV richiede la colonna tempo e tutte le `serie` in config; il tempo deve essere strettamente crescente (exit 4 altrimenti). +- Exit code: 0 ok · 2 uso · 3 config · 4 CSV · 6 domini non validi. diff --git a/docs/SPEC.md b/docs/SPEC.md new file mode 100644 index 0000000..a973d49 --- /dev/null +++ b/docs/SPEC.md @@ -0,0 +1,120 @@ +# `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. diff --git a/docs/USO.md b/docs/USO.md new file mode 100644 index 0000000..b74cbbe --- /dev/null +++ b/docs/USO.md @@ -0,0 +1,27 @@ +# 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_.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`: 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_abilitati`, `soglia_delta_aicc`, `rmse_vs_rumore_max`, `seed`.