Documentazione completa in docs/: assunzioni, algoritmi, moduli, configurazione, output, test, limiti
- docs/SPEC.md sostituito da un file per argomento (indice in docs/README.md) - assunzioni A1-A24 con dove nel codice e cosa succede se false - formule in LaTeX corrette (nessuno spazio prima del dollaro di chiusura), simboli e chiavi in tabella - diagrammi Mermaid per il flusso degli algoritmi e le dipendenze dei moduli - figura dei domini sul seed 1 (docs/img/domini_esempio.png) - README ridotto a indice con avvio rapido; CLAUDE.md e docstring allineati Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a4309364b
commit
f28bddc5a2
18 files changed
+892
-178
No files matched your search
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
`fitseg` legge un CSV `t_ms,T_C,dT_C` (temperatura di un sensore IR), lo divide in **domini** (riposo / riscaldamento = la salita rapida di un gradino, 2–3 campioni / raffreddamento = il decadimento dopo ogni gradino) 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à (non duplicarne il contenuto): [docs/SPEC.md](docs/SPEC.md) (algoritmi, limiti), [README.md](README.md) (uso, test), [config.toml](config.toml) (unica fonte dei valori: nessun default nel codice, ogni chiave nuova va in `CHIAVI` di [src/fitseg/config.py](src/fitseg/config.py) e in SPEC).
|
||||
Fonti di verità (non duplicarne il contenuto): la documentazione in [docs/](docs/README.md) — [assunzioni](docs/assunzioni.md) (A1–A24, richiamate nel codice), [algoritmi](docs/algoritmi.md), [moduli](docs/moduli.md), [configurazione](docs/configurazione.md), [output](docs/output.md), [test](docs/test.md), [limiti](docs/limiti.md) — e [config.toml](config.toml), unica fonte dei valori: nessun default nel codice, ogni chiave nuova va in `CHIAVI` di [src/fitseg/config.py](src/fitseg/config.py) e in [docs/configurazione.md](docs/configurazione.md). Il [README](README.md) è solo un indice con l'avvio rapido. **Quando cambi comportamento aggiorna il file di docs corrispondente** (e le assunzioni se ne cambia una).
|
||||
|
||||
## Comandi
|
||||
|
||||
@@ -24,7 +24,7 @@ uv venv .venv && uv pip install -e ".[dev]"
|
||||
- I domini derivano **solo dai gradini positivi** di `serie_riferimento` (`diff > soglia_gradino_c`): la salita (estesa ai campioni adiacenti ancora in salita) è un `riscaldamento`, il tratto dopo è un `raffreddamento`; con N gradini i domini sono 1 + 2N. `domini.toml` è modificabile e deve essere contiguo.
|
||||
- `analisi.analizza_domini`: il raffreddamento **finale** (ultimo) si fitta per primo, libero; i raffreddamenti intermedi fissano `c`/`tau` ai suoi valori (`raffreddamento_intermedio_fissa_da_finale`, `adatta(..., fissi=)`). Il `riscaldamento` non si fitta: `_gradino` ne riporta il salto (`vs["gradino"]`, `sigma` None). Modelli per tipo in `[fit.modelli_per_tipo]` (riposo, raffreddamento).
|
||||
- `fit.adatta`: `least_squares` con perdita robusta; l'esponenziale parte da una griglia su τ. "Non identificabile" = condizione > soglia **o** IC 95 % > |valore| per un parametro diverso da `a`/`c`. σ del rumore **per dominio** da `MAD(d²y)`; tempo traslato a `t' = t − t0`.
|
||||
- Nuovo modello = voce in `modelli.py` + riga in SPEC §3 + nome in `[fit.modelli_per_tipo]`. Non ci sono partenze casuali: il programma è deterministico.
|
||||
- Nuovo modello = voce in `modelli.py` + riga in [docs/algoritmi.md](docs/algoritmi.md) §5.1 e in [docs/moduli.md](docs/moduli.md) + nome in `[fit.modelli_per_tipo]`. Non ci sono partenze casuali: il programma è deterministico.
|
||||
|
||||
## Test
|
||||
|
||||
|
||||
@@ -1,106 +1,31 @@
|
||||
# fitseg
|
||||
|
||||
Legge un CSV `t_ms,T_C,dT_C` (temperatura di un sensore IR che si scalda e si raffredda), riconosce **riposo**, **riscaldamento** (la salita rapida di ogni gradino, 2–3 campioni) e **raffreddamento** (il decadimento esponenziale tra un gradino e l'altro e dopo l'ultimo), lo divide in **domini** e trova per ogni dominio e ogni serie l'equazione che lo approssima meglio, con minimi quadrati non lineari.
|
||||
Legge un CSV `t_ms,T_C,dT_C` (temperatura di un sensore IR che si scalda a gradini e si raffredda), lo divide in **domini** (riposo, riscaldamento, raffreddamento) e
|
||||
trova per ognuno l'equazione che lo approssima meglio. Produce, per ogni CSV, i grafici della suddivisione, la sovrapposizione dei dati grezzi con le equazioni trovate
|
||||
e un report in Markdown con formule in LaTeX.
|
||||
|
||||
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.
|
||||
## Documentazione
|
||||
|
||||
## Installazione
|
||||
Tutto è spiegato in **[docs/](docs/README.md)**: assunzioni, algoritmi, moduli, configurazione, output, test, limiti.
|
||||
|
||||
Python ≥ 3.11 e [uv](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`):
|
||||
| Se vuoi… | Vai a |
|
||||
|---|---|
|
||||
| capire cosa fa e vedere un esempio | [docs/panoramica.md](docs/panoramica.md) |
|
||||
| installare e lanciare i comandi | [docs/uso.md](docs/uso.md) |
|
||||
| sapere cosa si assume e quando non vale | [docs/assunzioni.md](docs/assunzioni.md) |
|
||||
| capire come si ottengono le equazioni | [docs/algoritmi.md](docs/algoritmi.md) |
|
||||
| orientarti nel codice | [docs/moduli.md](docs/moduli.md) |
|
||||
| cambiare un valore (`config.toml`) | [docs/configurazione.md](docs/configurazione.md) |
|
||||
| leggere i file e i grafici prodotti | [docs/output.md](docs/output.md) |
|
||||
| eseguire i test | [docs/test.md](docs/test.md) |
|
||||
| conoscere i limiti | [docs/limiti.md](docs/limiti.md) |
|
||||
|
||||
## Avvio rapido
|
||||
|
||||
```bash
|
||||
uv venv .venv
|
||||
uv pip install -e ".[dev]" # numpy, scipy, matplotlib; pytest e ruff per lo sviluppo
|
||||
uv venv .venv && uv pip install -e ".[dev]" # installazione: docs/uso.md §1
|
||||
.venv/bin/fitseg domini input/gen_1.csv # passo 1: come verrebbero suddivisi i dati
|
||||
.venv/bin/fitseg analizza input/gen_1.csv # tutti i passi → output/gen_1/{01_domini,02_sovrapposizione,03_report}
|
||||
.venv/bin/pytest # test: docs/test.md
|
||||
```
|
||||
|
||||
## Dati di ingresso
|
||||
|
||||
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`; **metti i CSV da analizzare nella cartella `input/`** (la cartella è nel repository, i file `*.csv` no); i test di integrazione usano i seed 1–3:
|
||||
|
||||
```bash
|
||||
mkdir -p input
|
||||
for s in 1 2 3; do
|
||||
(cd ../gen_data && .venv/bin/python genera.py config.toml --seed $s -o ../EquationMiner/input/gen_$s.csv)
|
||||
done
|
||||
```
|
||||
|
||||
## Uso
|
||||
|
||||
```bash
|
||||
.venv/bin/fitseg domini input/gen_1.csv # passo 1: come verrebbero suddivisi i dati (tabella, domini.png, domini.toml), nessun fit
|
||||
.venv/bin/fitseg analizza input/gen_1.csv # tutti i passi: domini, equazioni, sovrapposizione, report
|
||||
```
|
||||
`--config PATH` (default `config.toml`) e `--output DIR` (default `output`) vanno **prima** del sottocomando. I file di ogni analisi vanno in `output/<nome del csv>/`.
|
||||
|
||||
**Correggere i domini a mano**: apri `output/gen_1/01_domini/domini.toml`, modifica i tempi (i domini devono restare contigui) e rilancia
|
||||
```bash
|
||||
.venv/bin/fitseg analizza input/gen_1.csv --domini output/gen_1/01_domini/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)
|
||||
```
|
||||
|
||||
### Cosa produce
|
||||
|
||||
Una cartella per ogni CSV analizzato, con un passo per sottocartella (`output/` è ignorata da git):
|
||||
|
||||
```
|
||||
output/gen_1/
|
||||
├── 01_domini/ suddivisione dei dati
|
||||
│ ├── domini.png dati grezzi con i domini (colore per tipo, numerati)
|
||||
│ └── domini.toml domini usati, modificabili a mano
|
||||
├── 02_sovrapposizione/ dati grezzi contro le equazioni trovate
|
||||
│ ├── sovrapposizione_<serie>.png serie intera con le equazioni a tratti e i residui
|
||||
│ └── dominio<k>_<serie>.png un dominio: dati, modello scelto, altri modelli validi, residui
|
||||
└── 03_report/
|
||||
├── report.md report completo con tutte le immagini incorporate
|
||||
└── risultati.json tutti i fit con tutte le metriche
|
||||
```
|
||||
|
||||
`report.md` contiene: (1) suddivisione in domini, (2) tabella delle equazioni con parametri ± IC 95 %, (3) sovrapposizione dati–equazioni, (4) dettaglio per dominio con metriche e tabella di tutti i modelli provati. Tutte le grandezze matematiche (equazioni con i valori sostituiti, parametri ± IC, $\sigma$, $R^2$, $\rho_1$, $\Delta$, $t_0$, criteri di scelta) sono scritte in **LaTeX** (`$…$`, `$$…$$`): servono un visualizzatore Markdown con supporto matematico (GitHub, VS Code, Obsidian) e le immagini come link relativi.
|
||||
|
||||
Come leggere il report: **salto** = riscaldamento descritto ma non fittato; **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** (`domini.py`): un salto positivo tra due campioni consecutivi sopra `soglia_gradino_c` è un gradino. I campioni della salita formano un dominio `riscaldamento` (pochi punti, esteso ai campioni adiacenti ancora in salita). Prima del primo gradino: `riposo`; dopo ogni gradino: `raffreddamento`, fino al gradino successivo o alla fine. Con 3 gradini i domini sono 7: riposo, (riscaldamento, raffreddamento) × 3.
|
||||
2. **Modelli** (`modelli.py`): `riposo` → `costante`, `lineare`, `esp_decad`; `raffreddamento` → solo `esp_decad` `y = c + A·exp(−t'/τ)`, con `t' = t − t0` del dominio. Il `riscaldamento` ha troppo pochi campioni per un fit: è descritto come **salto** (Δ, da → a, numero di campioni, durata).
|
||||
3. **Fit** (`fit.py`): `scipy.optimize.least_squares` con perdita robusta; partenza dell'esponenziale da una griglia su τ. Il raffreddamento finale (il più lungo) si fitta per primo con parametri liberi; i raffreddamenti intermedi, tra due gradini (12–25 campioni), **fissano `c` e `τ`** ai suoi valori (stesse costanti fisiche; in pochi secondi 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.
|
||||
|
||||
Specifica e limiti: [docs/SPEC.md](docs/SPEC.md).
|
||||
|
||||
## Configurazione
|
||||
|
||||
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 esclusi all'inizio di ogni raffreddamento), `n_min_punti`; `[fit.modelli_per_tipo]` (modelli per tipo di dominio); `[fit] raffreddamento_intermedio_fissa_da_finale` (`["c", "tau"]`, `[]` = parametri liberi), `soglia_delta_aicc`, `rmse_vs_rumore_max`.
|
||||
|
||||
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 (≈ 4 s)
|
||||
.venv/bin/pytest tests/test_fit.py # un file
|
||||
.venv/bin/pytest -k raffreddamento # per nome
|
||||
.venv/bin/ruff check src tests
|
||||
```
|
||||
|
||||
| File | Verifica | Servono i CSV? |
|
||||
|---|---|---|
|
||||
| `test_domini.py` | 7 domini con 3 gradini (riscaldamento = pochi campioni), 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 `input/gen_{1,2,3}.csv`; CLI `domini` e `analizza --domini`; codici di uscita | sì (altrimenti saltati) |
|
||||
|
||||
I test unitari generano il segnale da soli (`tests/conftest.py`). Prova manuale: `fitseg analizza input/gen_1.csv` e controlla in `output/gen_1/03_report/report.md` 7 domini (riposo, poi riscaldamento e raffreddamento per ognuno dei 3 gradini) e τ ≈ 16 s nel raffreddamento finale; in `01_domini/domini.png` ogni raffreddamento ha un colore diverso.
|
||||
|
||||
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 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`.
|
||||
I CSV da analizzare vanno in `input/` (dati di prova da `../gen_data`: [docs/uso.md](docs/uso.md) §2). Il report è `output/<csv>/03_report/report.md`.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Documentazione di fitseg
|
||||
|
||||
Un file per argomento. Per un primo giro leggi **panoramica**, poi **uso**; per capire perché i numeri sono quelli che sono, **assunzioni** e **algoritmi**.
|
||||
|
||||
| File | Contenuto | Quando leggerlo |
|
||||
|---|---|---|
|
||||
| [panoramica.md](panoramica.md) | cosa fa il programma, il segnale, glossario, un esempio completo | per iniziare |
|
||||
| [uso.md](uso.md) | installazione, dati di ingresso, comandi, correggere i domini a mano, codici di uscita | per usarlo |
|
||||
| [assunzioni.md](assunzioni.md) | ogni assunzione (A1–A24): perché, dove nel codice, cosa succede se è falsa; origine dei dati di prova | per fidarsi (o no) dei risultati |
|
||||
| [algoritmi.md](algoritmi.md) | rilevamento dei gradini, domini, fit, incertezze, metriche, scelta del modello, con le formule | per capire i numeri |
|
||||
| [moduli.md](moduli.md) | un modulo alla volta: responsabilità, funzioni, strutture dati, come aggiungere un modello | per modificare il codice |
|
||||
| [configurazione.md](configurazione.md) | ogni chiave di `config.toml`, formato di `domini.toml`, esempi | per cambiare un valore |
|
||||
| [output.md](output.md) | file prodotti, colori, grafici, struttura del report, come leggere i messaggi | per leggere i risultati |
|
||||
| [test.md](test.md) | come eseguire i test, cosa verifica ognuno, il segnale sintetico | per sviluppare |
|
||||
| [limiti.md](limiti.md) | limiti noti, estensioni possibili, scelte progettuali | prima di estendere |
|
||||
|
||||
Convenzioni: le assunzioni hanno identificativi **A1…A24** richiamati dagli altri file e dai commenti nel codice; le chiavi di configurazione sono scritte
|
||||
`in_questo_formato`; le formule sono in LaTeX (servono un visualizzatore Markdown con supporto matematico: GitHub, VS Code, Obsidian).
|
||||
@@ -1,74 +0,0 @@
|
||||
# `fitseg` — specifica
|
||||
|
||||
**Obiettivo**: leggere un CSV (`t_ms,T_C,dT_C`, p.es. da `gen_data`), riconoscere **riposo**, **riscaldamento** (flusso in entrata: la salita rapida di ogni gradino, pochi campioni) e **raffreddamento** (decadimento esponenziale dopo ogni gradino), 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. Flusso
|
||||
|
||||
```
|
||||
CSV ─► [1 DOMINI] gradini ⇒ riposo | (riscaldamento, raffreddamento) per ogni gradino ─► output/<csv>/01_domini/ (domini.png, domini.toml modificabile)
|
||||
▼
|
||||
[2 FIT] per (dominio, serie): modelli del tipo ─► regola di scelta ─► 02_sovrapposizione/ (dati contro equazioni)
|
||||
▼
|
||||
[3 REPORT] 03_report/report.md (immagini incorporate, formule in LaTeX) e risultati.json
|
||||
```
|
||||
Tutto il calcolo è deterministico (nessuna partenza casuale, nessun LLM). Ogni CSV ha la sua cartella `output/<nome csv>/`.
|
||||
|
||||
## 2. Domini (`domini.py`)
|
||||
|
||||
Struttura fisica (vedi `gen_data/docs/SPEC.md`): T parte dall'ambiente, **gradini istantanei positivi** (2–4 s uno dall'altro): la salita dura 2–3 campioni ed è il **riscaldamento**; ognuno è seguito da un decadimento esponenziale, il **raffreddamento**, che dopo l'ultimo gradino prosegue fino all'ambiente. Compaiono anche scarti casuali (±4 °C, rientro < 1 s).
|
||||
|
||||
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. I campioni della salita formano il dominio `riscaldamento`: dal primo campione già in salita all'ultimo (si estende ai campioni adiacenti finché `d > soglia_bordo_sigma · σ_d`, σ_d = MAD dei salti).
|
||||
3. Prima del primo gradino `riposo`; dopo ogni gradino `raffreddamento`, fino al gradino successivo o alla fine. Con N gradini i domini sono 1 + 2N. Senza gradini: un solo `riposo`. Un raffreddamento senza campioni (gradini adiacenti) non viene creato.
|
||||
4. I primi `scarta_dopo_salto_n` campioni di ogni `raffreddamento` sono esclusi dal fit (sicurezza dopo la salita).
|
||||
|
||||
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` è modificabile (`tipo`, `t0_s` incluso, `t1_s` escluso; ultimo dominio fino a fine dati); i domini devono essere contigui, altrimenti exit 6.
|
||||
|
||||
## 3. Modelli (`modelli.py`, t' = t − t0 del dominio)
|
||||
|
||||
| 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, raffreddamento |
|
||||
|
||||
Il raffreddamento è esponenziale; il **riscaldamento** (2–3 campioni) non ha modelli: è descritto come salto (Δ = ultimo campione di salita − campione precedente, da → a, n campioni, durata). Nel raffreddamento `c` è l'asintoto (≈ ambiente) e `τ = 1/k`. Nuovo modello = voce in `MODELLI`, riga qui, nome in config.
|
||||
|
||||
## 4. Fit (`fit.py`)
|
||||
|
||||
1. Dati del dominio (raffreddamento: senza i primi `scarta_dopo_salto_n` campioni); tempo traslato.
|
||||
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|.
|
||||
|
||||
### 4.1 Costanti condivise tra i raffreddamenti (`analisi.py`)
|
||||
Ogni raffreddamento segue la stessa legge (stessa costante di tempo, stesso asintoto ≈ ambiente). Il raffreddamento **finale** (ultimo dominio, il più lungo) si fitta per primo con parametri liberi; i raffreddamenti **intermedi** (tra due gradini, 12–25 campioni) fissano i parametri di `raffreddamento_intermedio_fissa_da_finale` (default `c`, `tau`) ai suoi valori e hanno libera solo `A`. Motivo: con gli scarti casuali del sensore, in pochi secondi 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 finale valido, o la lista è vuota, i parametri restano liberi.
|
||||
|
||||
### 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.
|
||||
|
||||
## 5. CLI e file
|
||||
|
||||
| Comando | Funzione |
|
||||
|---|---|
|
||||
| `fitseg domini CSV` | passo 1: tabella, `01_domini/domini.png`, `01_domini/domini.toml` (nessun fit) |
|
||||
| `fitseg analizza CSV [--domini FILE]` | tutti i passi; con `--domini` usa i domini corretti a mano al posto della proposta |
|
||||
|
||||
File in `output/<csv>/`: `01_domini/{domini.png, domini.toml}` · `02_sovrapposizione/{sovrapposizione_<serie>.png, dominio<k>_<serie>.png}` · `03_report/{report.md, risultati.json}`. Il report scrive in LaTeX tutte le grandezze matematiche (`$…$` inline, `$$…$$` per l'equazione scelta con i valori sostituiti; il campo `equazione` di `MODELLI` è LaTeX). Il report (indice: 1 suddivisione, 2 equazioni, 3 sovrapposizione, 4 dettaglio per dominio) incorpora le immagini con percorsi relativi.
|
||||
|
||||
Opzioni globali prima del sottocomando: `--config PATH`, `--output DIR` (default `[output] cartella_output`). Exit: 0 ok · 3 config non valida · 4 CSV illeggibile · 6 domini non validi.
|
||||
|
||||
## 6. Test
|
||||
|
||||
Unitari su un segnale sintetico (`tests/conftest.py`: τ = 16 s, 3 gradini, nessun disturbo a salto) e di integrazione su `input/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.
|
||||
|
||||
## 7. Limiti noti
|
||||
|
||||
- **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 non fittato**: con 2–3 campioni si riporta solo il salto Δ; il valore dipende da quanta salita cade sul campione precedente (campioni di transizione), quindi è una stima per difetto dell'ampiezza reale del gradino.
|
||||
- **Raffreddamenti intermedi**: ACF1 alto e RMSE ≈ 2σ per gli scarti: «non adeguati» 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`.
|
||||
@@ -0,0 +1,220 @@
|
||||
# Algoritmi
|
||||
|
||||
Tutti i passi, nell'ordine in cui il programma li esegue. Le assunzioni richiamate (A1, A2, …) sono in [assunzioni.md](assunzioni.md); le
|
||||
chiavi di configurazione (`in_questo_formato`) in [configurazione.md](configurazione.md); il modulo che implementa ogni passo in
|
||||
[moduli.md](moduli.md).
|
||||
|
||||
## Visione d'insieme
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
CSV(["CSV in input/"]) --> P1
|
||||
|
||||
subgraph P1["PASSO 1 · Domini"]
|
||||
direction LR
|
||||
L["<b>1 · Lettura</b><br/>validazione"] --> S["<b>2 · Rumore</b><br/>stima di σ"] --> G["<b>3 · Gradini</b><br/>salti sopra soglia"] --> D["<b>4 · Domini</b><br/>divisione del tempo"]
|
||||
end
|
||||
|
||||
P1 --> RIP["<b>Riposo</b>"]
|
||||
P1 --> RIS["<b>Riscaldamento</b><br/>2–3 campioni"]
|
||||
P1 --> INT["<b>Raffreddamenti intermedi</b><br/>tra due gradini, corti"]
|
||||
P1 --> FIN["<b>Raffreddamento finale</b><br/>l'ultimo, il più lungo"]
|
||||
|
||||
subgraph P2["PASSO 2 · Equazioni"]
|
||||
direction TB
|
||||
F0["<b>5 · Fit</b><br/>costante, lineare,<br/>esponenziale"]
|
||||
F1["<b>5 · Fit esponenziale</b><br/>c, A, τ liberi"]
|
||||
F2["<b>5b · Fit esponenziale</b><br/>c e τ fissi, solo A libera"]
|
||||
SALTO["<b>7 · Salto Δ</b><br/>descritto, non fittato"]
|
||||
SC["<b>6 · Scelta del modello</b><br/>validi → adeguati → meno parametri"]
|
||||
F0 --> SC
|
||||
F1 --> SC
|
||||
F2 --> SC
|
||||
end
|
||||
|
||||
RIP --> F0
|
||||
FIN --> F1
|
||||
INT --> F2
|
||||
RIS --> SALTO
|
||||
F1 -. "passa c, τ" .-> F2
|
||||
|
||||
SC --> REP
|
||||
SALTO --> REP
|
||||
REP(["<b>PASSO 3</b> · Sovrapposizione e report<br/>dati grezzi contro equazioni<br/>report.md · grafici · risultati.json"])
|
||||
|
||||
classDef dato fill:#eceff1,stroke:#607d8b,color:#263238
|
||||
classDef calcolo fill:#e3f2fd,stroke:#1a73e8,color:#0d47a1
|
||||
classDef dominio fill:#fff8e1,stroke:#f9a825,color:#5d4037
|
||||
classDef uscita fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
|
||||
class CSV dato
|
||||
class L,S,G,D,F0,F1,F2,SC,SALTO calcolo
|
||||
class RIP,RIS,INT,FIN dominio
|
||||
class REP uscita
|
||||
|
||||
linkStyle 15 stroke:#e65100,stroke-width:2px,color:#e65100
|
||||
```
|
||||
|
||||
Come si legge: dall'alto in basso. I numeri nei riquadri sono i paragrafi di questo documento. Colori: **grigio** dati, **azzurro** calcoli,
|
||||
**giallo** tipi di dominio, **verde** risultati. Il Passo 1 produce i domini; ogni tipo ha il suo trattamento nel Passo 2; la freccia
|
||||
**arancione tratteggiata** indica che il raffreddamento finale **passa** $c$ e $\tau$ ai raffreddamenti intermedi (§5b), per questo si elabora per primo.
|
||||
Il riscaldamento non si fitta: se ne riporta il salto (§7).
|
||||
|
||||
## Simboli e chiavi di configurazione
|
||||
|
||||
Nelle formule si usano questi simboli; a destra la chiave di [configurazione.md](configurazione.md) con il valore.
|
||||
|
||||
| Simbolo | Chiave | Significato |
|
||||
|---|---|---|
|
||||
| $\theta_g$ | `soglia_gradino_c` | salto positivo minimo che segna un gradino [°C] |
|
||||
| $\theta_b$ | `soglia_bordo_sigma` | soglia, in multipli di $\sigma_d$, per estendere la salita ai campioni adiacenti |
|
||||
| $\theta_\rho$ | `soglia_acf` | soglia di $\rho_1$ oltre la quale si allargano gli intervalli |
|
||||
| $\kappa$ | `scala_perdita_sigma` | scala della perdita robusta, in multipli di $\sigma$ |
|
||||
| $\alpha_{\min},\ \alpha_{\max}$ | `tau_min_rel`, `tau_max_rel` | limiti di $\tau$ come frazione di $L$ |
|
||||
| $\lambda$ | `rmse_vs_rumore_max` | un modello è «adeguato» se $\mathrm{RMSE} \le \lambda\,\sigma$ |
|
||||
| $\delta$ | `soglia_delta_aicc` | tolleranza su $\Delta\mathrm{AICc}$ per preferire il modello più semplice |
|
||||
| $L$ | — | durata del dominio, $t_\text{ultimo} - t_\text{primo}$ |
|
||||
| $n$, $k$ | — | campioni usati nel fit; parametri **liberi** del modello |
|
||||
|
||||
|
||||
## 1. Lettura dei dati (`dati.leggi_csv`)
|
||||
|
||||
Legge l'intestazione, controlla che ci siano la colonna del tempo e tutte le serie (A2), converte il tempo in secondi (A3) e verifica che
|
||||
sia strettamente crescente (A1). Restituisce `t` (array in secondi) e un dizionario `serie` {nome: array}.
|
||||
|
||||
## 2. Stima del rumore (`domini.sigma_rumore`)
|
||||
|
||||
Per un rumore bianco $\varepsilon_i$ di deviazione $\sigma$, la **derivata seconda discreta** $\Delta^2 y_i = y_{i+2} - 2y_{i+1} + y_i$ elimina
|
||||
le componenti lisce (costante, lineare, e in buona parte l'esponenziale) e ha varianza $6\sigma^2$. Per renderla robusta agli outlier si usa la
|
||||
deviazione mediana assoluta:
|
||||
|
||||
$$\sigma = \frac{1.4826\,\mathrm{MAD}(\Delta^2 y)}{\sqrt{6}}, \qquad \mathrm{MAD}(x) = \mathrm{mediana}\,|x_i - \mathrm{mediana}(x)|$$
|
||||
|
||||
Il fattore 1.4826 rende la MAD uguale a $\sigma$ per dati gaussiani. La stima si calcola **per dominio** (A13) sui campioni usati nel fit
|
||||
e serve come scala della perdita robusta e come soglia di «adeguatezza» (§6). Ignora la deriva lenta (A14) e restituisce `nan` con meno
|
||||
di 4 campioni.
|
||||
|
||||
## 3. Rilevamento dei gradini (`domini.trova_gradini`)
|
||||
|
||||
Sulla `serie_riferimento`:
|
||||
|
||||
1. **Candidati**: $d_i = y_{i+1} - y_i$; sono forti gli indici con $d_i > \theta_g$ (solo salti positivi, A5, A12).
|
||||
2. **Raggruppamento**: candidati a meno di `distanza_min_salti_campioni` indici l'uno dall'altro appartengono allo stesso gradino (la salita
|
||||
si distribuisce su 2 campioni, ognuno con un salto grande).
|
||||
3. **Estensione ai bordi**: il gruppo si estende all'indietro e in avanti ai campioni adiacenti ancora in salita, cioè finché
|
||||
$d_i > \theta_b\,\sigma_d$, con $\sigma_d = 1.4826\,\mathrm{MAD}(d)$ calcolata su **tutti** i salti (A6). Serve a
|
||||
non lasciare un campione a metà salita dentro il tratto vicino.
|
||||
4. Ogni gradino diventa la coppia $(i_0, i_1)$: $i_0$ è il primo campione già in salita e $i_1$ il campione successivo all'ultimo in salita
|
||||
(estremo escluso). I **campioni di salita** sono quelli in $[i_0, i_1)$.
|
||||
|
||||
## 4. Costruzione dei domini (`domini.proponi`)
|
||||
|
||||
Dati $N$ gradini, in ordine di tempo:
|
||||
|
||||
- `riposo`: dall'inizio al primo gradino (o tutto il segnale se non ci sono gradini);
|
||||
- per ogni gradino: un dominio `riscaldamento` sui suoi campioni di salita, seguito da un dominio `raffreddamento` fino al gradino
|
||||
successivo (o alla fine). Un raffreddamento senza campioni (gradini adiacenti) non viene creato.
|
||||
|
||||
Con $N$ gradini i domini sono quindi $1 + 2N$. I domini sono contigui e coprono tutti i campioni. L'utente può sostituire la proposta con
|
||||
`domini.toml` (A21), che viene validato: tipi ammessi, intervalli non vuoti, contiguità.
|
||||
|
||||
## 5. Fit (`fit.adatta`)
|
||||
|
||||
### 5.1 Modelli (`modelli.MODELLI`, $t' = t - t_0$)
|
||||
|
||||
| Modello | Equazione | Parametri | Dove si prova |
|
||||
|---|---|---|---|
|
||||
| `costante` | $y = a$ | $a$ | riposo |
|
||||
| `lineare` | $y = a + b\,t'$ | $a, b$ | riposo |
|
||||
| `esp_decad` | $y = c + A\,e^{-t'/\tau}$ | $c, A, \tau$ | riposo, raffreddamento |
|
||||
|
||||
Nel raffreddamento $c$ è l'asintoto (≈ ambiente per `T_C`, ≈ 0 per `dT_C`), $A$ l'ampiezza a $t'=0$ e $\tau = 1/k$ la costante di tempo.
|
||||
|
||||
### 5.2 Partenza
|
||||
|
||||
- Polinomi (`costante`, `lineare`): soluzione dei minimi quadrati lineari.
|
||||
- `esp_decad`: **proiezione delle variabili**. Per $\tau$ fissato il modello è lineare in $(c, A)$. Si provano `n_griglia_tau` valori di $\tau$
|
||||
su una griglia logaritmica tra $\alpha_{\min} L$ e $\alpha_{\max} L$, per ognuno si
|
||||
risolvono $(c, A)$ in modo lineare e si tiene il $\tau$ con $\mathrm{RSS}$ minima. Poi il raffinamento non lineare parte da lì: nessuna
|
||||
partenza casuale (A22).
|
||||
- Se $c$ e $\tau$ sono fissi e solo $A$ è libero: $A = \sum (y - c)\,e^{-t'/\tau} \big/ \sum e^{-2t'/\tau}$.
|
||||
|
||||
### 5.3 Minimizzazione
|
||||
|
||||
`scipy.optimize.least_squares` sui soli parametri liberi, con `x_scale="jac"`, limiti $\tau \in [\alpha_{\min} L,\ \alpha_{\max} L]$ e
|
||||
**perdita robusta** (`perdita`, default `soft_l1`):
|
||||
|
||||
$$\min_p \sum_i \rho\!\left(\frac{r_i^2}{s^2}\right), \quad \rho(z) = 2\left(\sqrt{1+z}-1\right), \quad s = \kappa\,\sigma, \quad r_i = f(t'_i; p) - y_i$$
|
||||
|
||||
Per $|r_i| \ll s$ si comporta come i minimi quadrati; per $|r_i| \gg s$ cresce linearmente, così gli **scarti casuali** pesano molto meno (A14).
|
||||
Se $\sigma$ non è finita o è nulla la scala è 1 (A20).
|
||||
|
||||
### 5.4 Incertezze
|
||||
|
||||
$$s^2 = \frac{\mathrm{RSS}}{n-k}, \qquad \mathrm{cov}(p) = s^2\,(J^\top J)^{-1}, \qquad \mathrm{IC}_{95\%} = \pm 1.96\,\sqrt{\mathrm{diag}(\mathrm{cov})}\cdot f_\rho$$
|
||||
|
||||
$k$ è il numero di parametri **liberi**, $J$ lo jacobiano dei residui nella soluzione (con la pseudo-inversa di $J^\top J$), $\mathrm{RSS} = \sum r_i^2$ (A16).
|
||||
Il fattore di autocorrelazione vale $f_\rho = \sqrt{(1+\rho_1)/(1-\rho_1)}$ se `correggi_autocorrelazione` è attivo e $\rho_1 > \theta_\rho$, altrimenti 1 (A17).
|
||||
Per i parametri fissi l'incertezza è 0 (A18).
|
||||
|
||||
### 5.5 Metriche
|
||||
|
||||
| Metrica | Definizione |
|
||||
|---|---|
|
||||
| $\mathrm{RMSE}$ | $\sqrt{\mathrm{RSS}/n}$ |
|
||||
| $R^2$ | $1 - \mathrm{RSS}/\mathrm{TSS}$, con $\mathrm{TSS} = \sum (y_i - \bar y)^2$ |
|
||||
| $\rho_1$ | autocorrelazione dei residui a lag 1: $\sum_{i} \tilde r_{i+1}\tilde r_i \big/ \sum_i \tilde r_i^2$, con $\tilde r = r - \bar r$ |
|
||||
| $\mathrm{AICc}$ | $n\ln(\mathrm{RSS}/n) + 2k + \dfrac{2k(k+1)}{n-k-1}$ (infinito se $n-k-1 \le 0$) |
|
||||
| $\mathrm{BIC}$ | $n\ln(\mathrm{RSS}/n) + k\ln n$ |
|
||||
| condizione | numero di condizione di $J$ con le colonne normalizzate in norma euclidea |
|
||||
|
||||
AICc e BIC sono calcolati da $\mathrm{RSS}$, quindi senza peso robusto (A19).
|
||||
|
||||
### 5.6 Validità e identificabilità
|
||||
|
||||
Un fit è **valido** se converge, nessun parametro libero è sul limite (distanza dal limite inferiore a $10^{-3}$ volte il limite stesso: $\tau$ al bordo della griglia),
|
||||
il numero di condizione non supera `soglia_condizione` e l'AICc è finito. Un fit è **non identificabile** se la condizione supera la
|
||||
soglia **oppure** un parametro libero diverso da `a` e `c` ha $1.96\,\sigma_p > |p|$ (intervallo che include lo zero: il parametro non
|
||||
è distinguibile da 0).
|
||||
|
||||
## 5b. Costanti condivise (`analisi.analizza_domini`)
|
||||
|
||||
Per A8 i raffreddamenti hanno lo stesso $\tau$ e lo stesso $c$. L'ordine di elaborazione è quindi:
|
||||
|
||||
1. il **raffreddamento finale** (ultimo dominio di tipo `raffreddamento`) si fitta per primo con $c, A, \tau$ liberi;
|
||||
2. se la scelta è valida, i valori di $c$ e $\tau$ (parametri in `raffreddamento_intermedio_fissa_da_finale`) sono salvati per serie;
|
||||
3. ogni **raffreddamento intermedio** si fitta con quei valori **fissi**, e solo $A$ libera ($k = 1$).
|
||||
|
||||
Motivo: un intermedio ha 12–25 campioni con scarti casuali, e $\tau$ da solo non è identificabile (su dati puliti lo sarebbe: i test lo
|
||||
verificano). Se il finale non dà un fit valido o la lista è vuota gli intermedi restano liberi.
|
||||
|
||||
## 6. Scelta del modello (`analisi.scegli`)
|
||||
|
||||
Per ogni (dominio, serie) con più modelli candidati:
|
||||
|
||||
1. **Scarto**: via i fit non validi (§5.6). Se non ne resta nessuno: `indeterminato`.
|
||||
2. **Adeguatezza**: sono *adeguati* i fit con $\mathrm{RMSE} \le \lambda\,\sigma$. Se ce n'è almeno uno si sceglie tra questi, altrimenti tra
|
||||
tutti i validi (e il dominio è segnato *non adeguato*).
|
||||
3. **Parsimonia**: tra i candidati con $\mathrm{AICc} \le \mathrm{AICc}_{\min} + \delta$ vince quello con **meno parametri**
|
||||
(a parità, AICc minore).
|
||||
|
||||
Un dominio con meno di `n_min_punti` campioni non viene fittato (`indeterminato`); un modello con più parametri di $n - 2$ non viene provato.
|
||||
Con un solo modello candidato (raffreddamento) la scelta è lui, se valido: il flag *adeguato* resta informativo.
|
||||
|
||||
## 7. Riscaldamento come salto (`analisi._gradino`)
|
||||
|
||||
Il dominio di riscaldamento ha 2–3 campioni: nessun fit. Si riporta, per ogni serie, con $i_0, i_1$ gli estremi del dominio:
|
||||
|
||||
$$\Delta = y_{i_1-1} - y_{i_0-1}, \qquad \text{durata} = t_{i_1-1} - t_{i_0-1}$$
|
||||
|
||||
cioè il salto dal campione **prima** della salita all'ultimo campione di salita, e il tempo che separa i due (per 2 campioni di salita ≈ 2
|
||||
intervalli, circa 300 ms). Poiché parte della salita può cadere sul campione precedente, $\Delta$ è una stima per difetto dell'ampiezza del
|
||||
gradino (A23). `sigma` vale `null` e `fit` è vuoto.
|
||||
|
||||
## Esempio di calcolo completo (seed 1, `T_C`, raffreddamento finale)
|
||||
|
||||
1. $n = 651$ campioni da $t_0 = 22.12$ s (dopo `scarta_dopo_salto_n = 1`, 650 usati nel fit); $\sigma = 0.145$ °C.
|
||||
2. Griglia su $\tau \in [0.05 L,\ 50 L]$ con $L \approx 97.8$ s → $\tau_0$ vicino a 16 s, $c, A$ lineari.
|
||||
3. Raffinamento `soft_l1` con scala $3\sigma = 0.44$ °C: $c = 23.00 \pm 0.12$, $A = 179.6 \pm 0.48$, $\tau = 16.08 \pm 0.07$ s.
|
||||
4. Metriche: $\mathrm{RMSE} = 0.356$, $\rho_1 = 0.71$ (> 0.3: intervalli moltiplicati per $\sqrt{1.71/0.29} = 2.43$), $R^2 = 0.9999$.
|
||||
5. $\mathrm{RMSE} > 1.3 \cdot 0.145 = 0.189$: **non adeguato** (residui autocorrelati per gli scarti casuali), ma unico modello valido → scelto.
|
||||
6. Gli intermedi 3 e 5 usano $c = 23.00$, $\tau = 16.08$ e stimano solo $A$ ($84.3$ e $148.3$).
|
||||
@@ -0,0 +1,71 @@
|
||||
# Assunzioni
|
||||
|
||||
Ogni assunzione ha un identificativo (A1, A2, …) richiamato nel codice e negli altri documenti. Per ognuna: cosa si assume, perché è
|
||||
ragionevole, **dove** il codice ne dipende e **cosa succede se è falsa**. Le assunzioni sul segnale valgono per i dati del generatore
|
||||
`../gen_data` (sezione finale): su dati reali vanno verificate.
|
||||
|
||||
## Dati
|
||||
|
||||
| ID | Assunzione | Dove | Se è falsa |
|
||||
|---|---|---|---|
|
||||
| **A1** | Il tempo è **strettamente crescente**, anche non uniforme (jitter del campionamento) | `dati.leggi_csv` | il CSV è rifiutato (exit 4). Il programma non riordina né deduplica |
|
||||
| **A2** | Il CSV ha l'intestazione e tutte le colonne richieste da `[dati]` (tempo e serie), tutte numeriche, senza celle vuote | `dati.leggi_csv` | exit 4 con il nome della colonna mancante |
|
||||
| **A3** | Il tempo è in `ms` o `s` come dichiarato in `[dati] unita_tempo` | `dati.leggi_csv` | tutti i tempi e le costanti $\tau$ risultano scalati di 1000 senza avvisi: controlla l'unità |
|
||||
| **A4** | Le serie condividono gli **stessi domini** (stessi istanti di rottura) | `analisi.analizza_domini`, `domini.proponi` | i domini sono cercati solo su `serie_riferimento` (default `T_C`); una serie con gradini diversi non è gestita: si lancia l'analisi due volte |
|
||||
|
||||
## Struttura del segnale
|
||||
|
||||
| ID | Assunzione | Dove | Se è falsa |
|
||||
|---|---|---|---|
|
||||
| **A5** | I **gradini di riscaldamento** sono salti positivi tra due campioni consecutivi molto più grandi del rumore **e** degli scarti casuali: `soglia_gradino_c` (20 °C) $\gg$ ampiezza massima degli scarti (≤ 4 °C) | `domini.trova_gradini` | gradini più piccoli della soglia non sono visti (il segnale resta un unico tratto); scarti più grandi della soglia sono scambiati per gradini. Soluzione: adattare la soglia o correggere `domini.toml` a mano |
|
||||
| **A6** | La **salita dura pochi campioni** (2–3): il sensore ha costante di tempo ($\tau_\text{sensore} \approx 0.07$ s) minore dell'intervallo di campionamento (≈ 0.15 s) | `domini.trova_gradini` (estensione ai campioni adiacenti), `analisi._gradino` | con un sensore più lento la salita avrebbe più campioni: verrebbe comunque raggruppata finché il salto resta sopra `soglia_bordo_sigma`$\cdot\sigma$, ma non sarebbe fittata |
|
||||
| **A7** | Dopo ogni gradino la temperatura **decade esponenzialmente** verso un asintoto: $y = c + A\,e^{-t'/\tau}$ | `modelli.MODELLI["esp_decad"]` | residui sistematici, `ρ₁` alto, «modello non adeguato». Con più processi di raffreddamento servirebbe un modello a più esponenziali (non presente, vedi [limiti.md](limiti.md)) |
|
||||
| **A8** | Tutti i raffreddamenti hanno la **stessa costante di tempo $\tau$ e lo stesso asintoto $c$** (stesso processo fisico: dispersione verso l'ambiente). Anche nel generatore il decadimento dopo più gradini è la somma di esponenziali con lo stesso $k$, quindi un'unica esponenziale | `analisi.analizza_domini` (parametri `raffreddamento_intermedio_fissa_da_finale`) | gli intermedi sono forzati a $\tau$ e $c$ del finale: se in realtà differiscono, il fit dell'ampiezza $A$ è distorto e i residui lo mostrano. Per rimuovere il vincolo: `raffreddamento_intermedio_fissa_da_finale = []` |
|
||||
| **A9** | Il **raffreddamento finale** è l'ultimo dominio ed è abbastanza lungo (centinaia di campioni) da stimare $c$, $A$, $\tau$ **liberamente** | `analisi.analizza_domini` | se il CSV finisce subito dopo un gradino, il finale è corto e $\tau$ può risultare non identificabile; allora anche gli intermedi restano liberi e possono essere indeterminati |
|
||||
| **A10** | Il **riposo** (prima del primo gradino) è una baseline costante, lineare o esponenziale lenta (l'ambiente che si assesta) | `[fit.modelli_per_tipo] riposo` | il modello scelto è solo descrittivo: con una deriva lenta vince `lineare` o `esp_decad` |
|
||||
| **A11** | Ogni dominio è **indipendente**: nessun vincolo di continuità tra domini | `analisi` | i modelli adiacenti possono non combaciare ai confini; il «salto» tra due raffreddamenti è il gradino |
|
||||
| **A12** | Non esistono gradini **discendenti** (un calo brusco non è un evento): si cercano solo salti positivi | `domini.trova_gradini` (`d > soglia`) | un calo brusco resterebbe dentro un raffreddamento, aumentando RMSE e $\rho_1$ |
|
||||
|
||||
## Rumore
|
||||
|
||||
| ID | Assunzione | Dove | Se è falsa |
|
||||
|---|---|---|---|
|
||||
| **A13** | Il rumore è circa **gaussiano bianco**, con $\sigma$ stimabile per dominio da $\sigma = 1.4826\,\mathrm{MAD}(\Delta^2 y)/\sqrt{6}$ | `domini.sigma_rumore` | $\sigma$ sottostimata: più domini risultano «non adeguati». La stima ignora la parte lenta del rumore (vedi A14) |
|
||||
| **A14** | Sul rumore bianco si aggiungono **disturbi che il modello non descrive**: deriva lenta (Ornstein–Uhlenbeck, $\tau \approx 3.5$ s) e **scarti casuali** (±4 °C, rientro < 1 s) | `fit.adatta` (perdita robusta), `uscita` (residui) | sono trattati come outlier dalla perdita `soft_l1` (non spostano molto il fit) ma lasciano residui autocorrelati ($\rho_1 \approx 0.7$) e intervalli di confidenza troppo stretti. Non è un errore del fit |
|
||||
| **A15** | La **quantizzazione** (T a 0.01 °C, ambiente a 0.1 °C) è trascurabile rispetto al rumore | — | nessun trattamento specifico |
|
||||
|
||||
## Statistica
|
||||
|
||||
| ID | Assunzione | Dove | Se è falsa |
|
||||
|---|---|---|---|
|
||||
| **A16** | Gli intervalli al 95 % sono **gaussiani asintotici**: $\pm 1.96\,\sigma_p$ con $\mathrm{cov} = s^2 (J^\top J)^{-1}$, $s^2 = \mathrm{RSS}/(n-k)$ | `fit.adatta` | con pochi campioni o residui non gaussiani sono approssimati |
|
||||
| **A17** | L'autocorrelazione dei residui è di tipo AR(1): gli errori si allargano di $\sqrt{(1+\rho_1)/(1-\rho_1)}$ se $\rho_1$ supera `soglia_acf` | `fit.adatta` | correzione approssimata; con autocorrelazione di altra forma gli intervalli restano stretti |
|
||||
| **A18** | Per i parametri **fissi** l'incertezza non si propaga: gli intervalli di $A$ nei raffreddamenti intermedi sono **condizionati** a $c$ e $\tau$ del finale | `fit.adatta`, `uscita._params` | sono sottostimati rispetto all'incertezza totale |
|
||||
| **A19** | AICc e BIC si calcolano da $\mathrm{RSS}$ come se i residui fossero gaussiani, anche se il fit usa la perdita robusta | `fit.adatta` | confronto tra modelli approssimato; sensibile agli outlier |
|
||||
| **A20** | Le serie con $\sigma$ non finita o nulla (dominio troppo corto) usano scala 1 per la perdita robusta | `fit.adatta` | solo robustezza ridotta |
|
||||
|
||||
## Progetto
|
||||
|
||||
| ID | Assunzione | Dove |
|
||||
|---|---|---|
|
||||
| **A21** | La **suddivisione in domini la decide l'utente**: la proposta automatica è modificabile (`domini.toml`) e il fit non la cambia mai | `cli._passo_domini`, `domini.leggi_toml` |
|
||||
| **A22** | Il programma è **deterministico**: nessuna partenza casuale, nessun seme | `fit._partenza` (griglia su $\tau$, minimi quadrati lineari) |
|
||||
| **A23** | Il **riscaldamento non si fitta** (2–3 campioni): si riporta il salto $\Delta = y_\text{fine salita} - y_\text{prima}$, che è una stima per difetto del gradino reale perché parte della salita può cadere sul campione precedente | `analisi._gradino` |
|
||||
| **A24** | I **modelli provati** sono fissati per tipo di dominio in configurazione, non scelti dal programma | `[fit.modelli_per_tipo]` |
|
||||
|
||||
## Origine dei dati di prova (`../gen_data`)
|
||||
|
||||
I CSV in `input/gen_<seed>.csv` sono prodotti dal generatore con la sua configurazione d'esempio. Caratteristiche che fondano le
|
||||
assunzioni sopra (dettaglio: `../gen_data/docs/SPEC.md` e `../gen_data/config.toml`):
|
||||
|
||||
| Aspetto | Valore nel generatore | Assunzioni |
|
||||
|---|---|---|
|
||||
| intervallo di campionamento | 150.5 ms con jitter ±0.6 ms | A1, A6 |
|
||||
| gradini di riscaldamento | 3, a ≈ 15.7, 17.8, 21.7 s, ampiezza ≈ 88, 75, 65 °C (perturbati per seed) | A5, A7 |
|
||||
| raffreddamento | $k = 0.063\ \mathrm{s^{-1}}$, cioè $\tau = 15.9$ s (perturbato del 2 % per seed) | A7, A8 |
|
||||
| risposta del sensore | filtro del primo ordine, $\tau_\text{sensore} = 0.07$ s | A6 |
|
||||
| rumore bianco | $\sigma = 0.11$ °C a freddo, $0.20$ a caldo; la stima per dominio dà ≈ 0.08–0.3 | A13 |
|
||||
| deriva lenta | Ornstein–Uhlenbeck, $\sigma = 0.04$ °C, $\tau = 3.5$ s | A14 |
|
||||
| scarti casuali | in riscaldamento: 0.8 Hz, $\sigma$ 2.5 °C; in raffreddamento: 0.25 Hz, media −1 °C; ampiezza ≤ 4 °C, rientro 0.3–0.7 s | A5, A14 |
|
||||
| ambiente | $23.1 - 0.45\,e^{-t/58}$ °C, non sale quando T è alta | A10; $c$ di `dT_C` ≈ 0 |
|
||||
| `dT_C` | $T - T_\text{amb}$ calcolato dopo rumore e quantizzazione di entrambi i canali | A4 |
|
||||
@@ -0,0 +1,81 @@
|
||||
# Configurazione
|
||||
|
||||
Tutti i valori sono in [`config.toml`](../config.toml): **nessun default nel codice**, una chiave mancante è un errore (exit 3). Suffissi nei nomi: `_s`
|
||||
secondi, `_c` gradi, `_rel` frazione della durata del dominio, `_sigma` multiplo di $\sigma$, `_n` / `_campioni` numero di campioni. Per aggiungere una chiave:
|
||||
`config.toml`, `CHIAVI` in `config.py`, questo documento. Il modulo che la legge è indicato a destra.
|
||||
|
||||
Si usa un altro file con `fitseg --config altro.toml …`.
|
||||
|
||||
## `[dati]` — formato del CSV (`dati.py`)
|
||||
|
||||
| Chiave | Valore | Significato |
|
||||
|---|---|---|
|
||||
| `colonna_tempo` | `"t_ms"` | nome della colonna del tempo |
|
||||
| `unita_tempo` | `"ms"` \| `"s"` | unità del tempo nel CSV; internamente tutto è in secondi (A3) |
|
||||
| `serie` | `["T_C", "dT_C"]` | colonne da approssimare; ognuna ha le sue equazioni, tutte condividono i domini (A4) |
|
||||
| `separatore` | `","` | separatore dei campi |
|
||||
|
||||
## `[domini]` — riconoscimento dei domini (`domini.py`, `analisi.py`)
|
||||
|
||||
| Chiave | Default | Significato | Effetto se alzata / abbassata |
|
||||
|---|---|---|---|
|
||||
| `serie_riferimento` | `"T_C"` | serie in cui si cercano i gradini; deve essere in `serie` | — |
|
||||
| `soglia_gradino_c` | 20.0 | salto positivo minimo tra due campioni consecutivi per essere un gradino | alta: gradini piccoli non rilevati; bassa: scarti scambiati per gradini (A5) |
|
||||
| `soglia_bordo_sigma` | 6.0 | la salita si estende ai campioni adiacenti con salto $d_i > \theta_b\,\sigma_d$, dove $\theta_b$ è questa soglia e $\sigma_d = 1.4826\,\mathrm{MAD}$ dei salti ([algoritmi.md](algoritmi.md) §3) | alta: campioni di mezza salita restano nel raffreddamento; bassa: il riscaldamento si allarga troppo |
|
||||
| `distanza_min_salti_campioni` | 3 | candidati più vicini di così appartengono allo stesso gradino | alta: gradini ravvicinati fusi; bassa: una salita su 2 campioni spezzata |
|
||||
| `n_min_punti` | 6 | campioni minimi per fittare un dominio; sotto → `indeterminato` | alta: più domini non fittati |
|
||||
| `scarta_dopo_salto_n` | 1 | campioni esclusi dal fit **all'inizio di ogni raffreddamento** (sicurezza dopo la salita) | 0: nessuna esclusione; alta: perde campioni del decadimento |
|
||||
|
||||
## `[fit]` — regressione e scelta (`fit.py`, `analisi.py`)
|
||||
|
||||
| Chiave | Default | Significato |
|
||||
|---|---|---|
|
||||
| `raffreddamento_intermedio_fissa_da_finale` | `["c", "tau"]` | parametri che i raffreddamenti intermedi prendono dal raffreddamento finale ([algoritmi.md](algoritmi.md) §5b). `[]` = tutti liberi. Nomi ammessi: quelli del modello (`c`, `A`, `tau`) |
|
||||
| `perdita` | `"soft_l1"` | funzione di perdita di `least_squares`: `"linear"` (minimi quadrati), `"soft_l1"`, `"huber"` (robuste agli scarti, A14) |
|
||||
| `scala_perdita_sigma` | 3.0 | scala $s$ della perdita robusta, in multipli di $\sigma$: sotto $s$ come i minimi quadrati, sopra cresce linearmente |
|
||||
| `tau_min_rel`, `tau_max_rel` | 0.05, 50.0 | limiti di $\tau$ come frazione della durata del dominio $L$: $\tau \in [0.05L,\ 50L]$; un $\tau$ al bordo rende il fit non valido |
|
||||
| `n_griglia_tau` | 40 | punti della griglia logaritmica su $\tau$ per la partenza dell'esponenziale |
|
||||
| `soglia_condizione` | $10^8$ | numero di condizione dello jacobiano normalizzato oltre cui i parametri sono «non identificabili» |
|
||||
| `correggi_autocorrelazione` | `true` | allarga gli intervalli di $\sqrt{(1+\rho_1)/(1-\rho_1)}$ se $\rho_1$ supera `soglia_acf` (A17) |
|
||||
| `soglia_acf` | 0.3 | soglia di $\rho_1$ per la correzione |
|
||||
| `soglia_delta_aicc` | 2.0 | tra i modelli entro questo $\Delta\mathrm{AICc}$ dal migliore vince quello con meno parametri |
|
||||
| `rmse_vs_rumore_max` | 1.3 | un modello è «adeguato» se $\mathrm{RMSE} \le$ questo valore $\cdot\ \sigma$ |
|
||||
|
||||
### `[fit.modelli_per_tipo]` — modelli provati per tipo di dominio
|
||||
|
||||
| Chiave | Default | Significato |
|
||||
|---|---|---|
|
||||
| `riposo` | `["costante", "lineare", "esp_decad"]` | modelli provati sul riposo (A10) |
|
||||
| `raffreddamento` | `["esp_decad"]` | modelli provati sui raffreddamenti (A7) |
|
||||
|
||||
Entrambe le liste sono obbligatorie e non vuote. Non esiste la voce `riscaldamento`: quel dominio non si fitta (A23). Modelli ammessi: i nomi in
|
||||
`modelli.MODELLI`.
|
||||
|
||||
## `[output]` (`cli.py`)
|
||||
|
||||
| Chiave | Default | Significato |
|
||||
|---|---|---|
|
||||
| `cartella_output` | `"output"` | cartella madre; ogni analisi scrive in `<cartella_output>/<nome del csv senza estensione>/`. `--output DIR` la sostituisce |
|
||||
|
||||
## `domini.toml` — domini modificabili (`domini.py`)
|
||||
|
||||
Scritto in `output/<csv>/01_domini/domini.toml`, rilegibile con `fitseg analizza CSV --domini FILE` ([uso.md](uso.md)):
|
||||
|
||||
```toml
|
||||
[[dominio]]
|
||||
tipo = "raffreddamento" # riposo | riscaldamento | raffreddamento
|
||||
t0_s = 16.1000 # primo campione del dominio (incluso)
|
||||
t1_s = 17.9100 # primo campione del dominio successivo (escluso); per l'ultimo: ultimo campione
|
||||
```
|
||||
I domini devono essere contigui ($t_1$ di uno uguale a $t_0$ del successivo): altrimenti exit 6.
|
||||
|
||||
## Esempi
|
||||
|
||||
| Voglio… | Cambio |
|
||||
|---|---|
|
||||
| gradini più piccoli (es. 8 °C) | `[domini] soglia_gradino_c = 8.0` e verifica che gli scarti restino sotto la soglia (A5) |
|
||||
| tutti i raffreddamenti con parametri liberi | `[fit] raffreddamento_intermedio_fissa_da_finale = []` |
|
||||
| fissare solo la costante di tempo | `["tau"]` |
|
||||
| fit ai minimi quadrati puri | `perdita = "linear"` |
|
||||
| un'altra colonna del CSV | `[dati] serie = [...]` e, se serve, `[domini] serie_riferimento` |
|
||||
| tempo in secondi | `[dati] unita_tempo = "s"` e `colonna_tempo` |
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
@@ -0,0 +1,38 @@
|
||||
# Limiti noti ed estensioni
|
||||
|
||||
## Limiti noti
|
||||
|
||||
| Limite | Perché | Effetto | Dove |
|
||||
|---|---|---|---|
|
||||
| **Residui autocorrelati nei raffreddamenti** ($\rho_1 \approx 0.5$–$0.7$, $\mathrm{RMSE}$ 1.5–3 volte $\sigma$) | scarti casuali (±4 °C) e deriva lenta non sono nel modello (A14) | i parametri sono buoni ($\tau$ entro pochi % dal valore nominale), ma gli intervalli di confidenza sono troppo stretti nonostante la correzione AR(1) (A17) e i domini risultano «non adeguati» | [assunzioni.md](assunzioni.md) A14, A17 |
|
||||
| **Il $\sigma$ stimato ignora la parte lenta del rumore** | la derivata seconda elimina la deriva (A13) | $\sigma$ sottostimata → «non adeguato» più frequente | A13 |
|
||||
| **Il riscaldamento non è fittato** | 2–3 campioni, meno dei parametri utili (A23) | si riporta solo il salto $\Delta$, che sottostima l'ampiezza reale del gradino perché parte della salita cade sul campione precedente | [algoritmi.md](algoritmi.md) §7 |
|
||||
| **Incertezze condizionate nei raffreddamenti intermedi** | $c$ e $\tau$ sono fissi, la loro incertezza non si propaga (A18) | gli intervalli di $A$ sono troppo stretti | A18 |
|
||||
| **Nessun vincolo di continuità** | ogni dominio è indipendente (A11) | gli intermedi non sono vincolati a combaciare col gradino | A11 |
|
||||
| **Un solo esponenziale per raffreddamento** | modello `esp_decad` (A7) | un raffreddamento con due processi (conduzione e convezione) non è descritto bene | A7 |
|
||||
| **Gradini cercati su una sola serie** | `serie_riferimento` (A4) | serie con gradini diversi richiedono due analisi | A4 |
|
||||
| **Soglia dei gradini fissa** | `soglia_gradino_c` in config (A5) | va adattata a gradini più piccoli o scarti più grandi | [configurazione.md](configurazione.md) |
|
||||
| **Provato su pochi dati** | 3 seed di `gen_data` con la configurazione d'esempio | nessuna validazione su dati reali o su rumore molto diverso | [test.md](test.md) |
|
||||
|
||||
## Estensioni possibili (non presenti)
|
||||
|
||||
| Estensione | Cosa servirebbe |
|
||||
|---|---|
|
||||
| Modello a due esponenziali per il raffreddamento | voce in `modelli.MODELLI` con 5 parametri, partenza su griglia a due $\tau$, ordinamento $\tau_1 < \tau_2$, nome nelle liste di `modelli_per_tipo` ([moduli.md](moduli.md)) |
|
||||
| Modelli di transizione liscia (logistica) o polinomi superiori | stessa procedura; limitare il centro della logistica al dominio per evitare che degeneri in esponenziale |
|
||||
| Fit congiunto con continuità tra domini | un unico problema ai minimi quadrati con penalità sul salto residuo ai confini |
|
||||
| Fit del riscaldamento | richiede più campioni (sensore più lento o campionamento più fitto) |
|
||||
| Scelta dei modelli guidata da un agente (Claude Code) | era nel progetto iniziale e **è stata tolta**: la regola automatica ([algoritmi.md](algoritmi.md) §6) basta per questi dati |
|
||||
| Rumore colorato nel fit (minimi quadrati generalizzati) | modello AR(1) dei residui per correggere intervalli e pesi invece del solo fattore $\sqrt{(1+\rho_1)/(1-\rho_1)}$ |
|
||||
|
||||
## Scelte progettuali già fatte
|
||||
|
||||
| Scelta | Alternativa scartata | Motivo |
|
||||
|---|---|---|
|
||||
| Domini dai gradini di `T_C` | PELT + fusione per BIC | i regimi qui sono determinati dai gradini; PELT con costo quadratico spezzerebbe gli esponenziali lisci |
|
||||
| Riscaldamento non fittato, descritto come salto | fit su 2–3 punti | parametri non identificabili |
|
||||
| $c$ e $\tau$ fissati negli intermedi | $\tau$ libero ovunque | $\tau$ non identificabile con 12–25 campioni e scarti casuali |
|
||||
| Partenza per griglia su $\tau$ (proiezione delle variabili) | partenze casuali | deterministico e più robusto per $\tau$ |
|
||||
| Perdita robusta `soft_l1` | minimi quadrati | gli scarti casuali sono outlier |
|
||||
| Output per CSV in sottocartelle numerate | cartella piatta | ogni passo è ispezionabile separatamente |
|
||||
| Un modulo per responsabilità, CLI a due comandi | script per fase, pacchetti `motore/` e `uscita/` | meno codice, nessuna duplicazione |
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
# Moduli
|
||||
|
||||
Mappa del codice in `src/fitseg/`. Un modulo ha una sola responsabilità; le dipendenze vanno in una sola direzione.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
cli[cli.py] --> config[config.py]
|
||||
cli --> dati[dati.py]
|
||||
cli --> domini[domini.py]
|
||||
cli --> analisi[analisi.py]
|
||||
cli --> uscita[uscita.py]
|
||||
analisi --> domini
|
||||
analisi --> fit[fit.py]
|
||||
analisi --> modelli[modelli.py]
|
||||
fit --> modelli
|
||||
uscita --> analisi
|
||||
uscita --> modelli
|
||||
```
|
||||
`config.py`, `dati.py`, `domini.py` e `modelli.py` non importano niente del pacchetto; `fit.py` importa solo `modelli`; `analisi.py` importa `domini`, `fit`, `modelli`;
|
||||
`uscita.py` importa `analisi` (per `valido`) e `modelli`. Nessun modulo di calcolo scrive file.
|
||||
|
||||
## Strutture dati comuni
|
||||
|
||||
**Domini** (lista, ordinata e contigua): ogni elemento è `{"tipo": "riposo"|"riscaldamento"|"raffreddamento", "i0": int, "i1": int}`, con
|
||||
`i0` primo campione e `i1` escluso. `domini[k]["i1"] == domini[k+1]["i0"]`.
|
||||
|
||||
**Risultato** (lista, una voce per dominio; è anche il contenuto di `risultati.json`):
|
||||
|
||||
| Campo | Significato |
|
||||
|---|---|
|
||||
| `k` | numero del dominio (da 1) |
|
||||
| `tipo`, `t0_s`, `t1_s` | tipo; tempo del primo e dell'ultimo campione del dominio (in secondi) |
|
||||
| `n`, `n_fit` | campioni del dominio; campioni usati nel fit (meno `scarta_dopo_salto_n` nei raffreddamenti) |
|
||||
| `serie` | `{nome serie: voce}` (sotto) |
|
||||
|
||||
**Voce di serie**: `sigma` (σ del rumore, `null` nel riscaldamento), `fit` (lista dei fit provati), `scelta` (nome del modello o `null`), `motivo`
|
||||
(testo), `adeguato` (bool, assente nel riscaldamento e nei domini troppo corti); nel riscaldamento al posto dei fit c'è `gradino`:
|
||||
`{n, da, a, delta, durata_s}`.
|
||||
|
||||
**Fit** (elemento di `fit`): `modello`, `equazione` (LaTeX), `n_parametri` ($k$ liberi), `n`, `fissi` (nomi dei parametri fissi), `parametri`
|
||||
`{nome: valore}`, `ic95` `{nome: ±}`, `rss`, `rmse`, `r2`, `aicc`, `bic`, `acf1` ($\rho_1$), `condizione`, `parametri_a_limite`, `non_identificabile`,
|
||||
`convergenza`. Se la minimizzazione fallisce: solo `modello`, `convergenza: false`, `n_parametri`.
|
||||
|
||||
## `cli.py` — punto d'ingresso
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Responsabilità | argomenti da riga di comando, orchestrazione dei passi, codici di uscita |
|
||||
| Funzioni | `main(argv)`, `cmd_domini`, `cmd_analizza`, `_passo_domini(args)`, `errore(msg, codice)` |
|
||||
| Entra | `--config`, `--output`, sottocomando `domini CSV` o `analizza CSV [--domini FILE]` |
|
||||
| Esce | codici 0 ok, 2 uso errato (argparse), 3 `ErroreConfig`, 4 `ErroreDati`, 6 `domini.toml` non valido |
|
||||
|
||||
`_passo_domini` è il passo 1 comune ai due comandi: legge config e dati, crea `output/<nome csv>/` con le tre sottocartelle, ricava i domini
|
||||
(da `--domini` oppure dalla proposta), scrive `01_domini/domini.toml` e `domini.png`, stampa la tabella dei domini. `cmd_analizza` poi chiama
|
||||
`analizza_domini` e scrive sovrapposizione e report. Con `--domini` il file passato viene riscritto normalizzato in `01_domini/`.
|
||||
|
||||
## `config.py` — configurazione
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Responsabilità | leggere `config.toml`, rifiutare chiavi mancanti (**nessun default nel codice**) |
|
||||
| Funzioni | `leggi(percorso) → dict`; costante `CHIAVI` (elenco obbligatorio per sezione); eccezione `ErroreConfig` |
|
||||
| Controlli | chiavi presenti; `unita_tempo` ∈ {ms, s}; `serie_riferimento` ∈ `serie`; `modelli_per_tipo` ha liste non vuote per `riposo` e `raffreddamento` |
|
||||
|
||||
Una chiave nuova va aggiunta a `CHIAVI`, a `config.toml` e a [configurazione.md](configurazione.md).
|
||||
|
||||
## `dati.py` — lettura del CSV
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Funzioni | `leggi_csv(percorso, cfg) → (t, serie)`; eccezione `ErroreDati` |
|
||||
| Garanzie | `t` in secondi, strettamente crescente (A1); `serie` = `{nome: array}` per le colonne di `[dati] serie`; nessun valore mancante |
|
||||
|
||||
## `domini.py` — suddivisione in domini
|
||||
|
||||
| Funzione | Cosa fa | Vedi |
|
||||
|---|---|---|
|
||||
| `sigma_robusto(d)` | $1.4826\,\mathrm{MAD}$ di un campione | [algoritmi.md](algoritmi.md) §2 |
|
||||
| `sigma_rumore(y)` | $\sigma$ del rumore bianco da $\Delta^2 y$ | §2 |
|
||||
| `trova_gradini(y, cfg)` | lista di $(i_0, i_1)$ dei campioni di salita | §3 |
|
||||
| `proponi(t, serie, cfg)` | lista dei domini (riposo + riscaldamento/raffreddamento per gradino) | §4 |
|
||||
| `scrivi_toml(percorso, domini, t)` | scrive `domini.toml` (tempi con 4 decimali) | [uso.md](uso.md) |
|
||||
| `leggi_toml(percorso, t)` | rilegge e **valida** i domini (tipi, intervalli, contiguità); `ValueError` se non validi | [uso.md](uso.md) |
|
||||
|
||||
Nel `domini.toml` `t1_s` è l'estremo **escluso** (tempo del primo campione del dominio successivo); per l'ultimo dominio è l'ultimo campione. Gli
|
||||
indici si ricalcolano dal tempo con tolleranza $10^{-6}$ s.
|
||||
|
||||
## `modelli.py` — registro dei modelli
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Dati | `MODELLI = {nome: (funzione, [nomi parametri], equazione LaTeX, indice di τ o None)}` |
|
||||
| Funzioni | `valuta(nome, t, p)` valuta un modello in $t$ con parametri `p` (lista nell'ordine dei nomi) |
|
||||
|
||||
Contiene `costante`, `lineare`, `esp_decad`. **Aggiungere un modello**: una funzione, una voce in `MODELLI`, il nome nelle liste di
|
||||
`[fit.modelli_per_tipo]`, una riga in [algoritmi.md](algoritmi.md) §5.1; se il modello ha un nuovo tipo di parametro-tempo, indicare l'indice
|
||||
in `MODELLI` e la partenza in `fit._partenza`; in `uscita._eq_numerica` aggiungere il ramo per l'equazione con i valori.
|
||||
|
||||
## `fit.py` — regressione di un modello
|
||||
|
||||
| Funzione | Cosa fa |
|
||||
|---|---|
|
||||
| `adatta(t, y, nome, sigma, cfg, fissi=None) → dict` | fit di un modello su un dominio; `fissi = {parametro: valore}` tenuti costanti. Restituisce il dizionario *Fit* sopra |
|
||||
| `_partenza(t, y, nome, cfg, L)` | minimi quadrati per i polinomi, griglia su $\tau$ (VARPRO) per l'esponenziale |
|
||||
| `_acf1(r)` | autocorrelazione dei residui a lag 1 |
|
||||
|
||||
Dettagli: [algoritmi.md](algoritmi.md) §5. Nessun accesso a file, nessuna casualità.
|
||||
|
||||
## `analisi.py` — scelta e orchestrazione dei domini
|
||||
|
||||
| Funzione | Cosa fa |
|
||||
|---|---|
|
||||
| `valido(fit)` | convergente, nessun parametro a limite, identificabile, AICc finito |
|
||||
| `scegli(fit_list, sigma, cfg) → (fit, motivo, adeguato)` | regola di scelta ([algoritmi.md](algoritmi.md) §6) |
|
||||
| `_gradino(t, y, d)` | descrive il riscaldamento come salto ([algoritmi.md](algoritmi.md) §7) |
|
||||
| `_serie(ty, yy, sigma, tipo, fissi, cfg)` | fit di tutti i modelli del tipo per una serie e scelta; `indeterminato` se troppo corto |
|
||||
| `analizza_domini(t, serie, domini, cfg) → list` | un *Risultato* per dominio; ordine: raffreddamento finale, poi gli altri ([algoritmi.md](algoritmi.md) §5b) |
|
||||
|
||||
## `uscita.py` — cartelle, grafici, report
|
||||
|
||||
| Funzione | Cosa produce |
|
||||
|---|---|
|
||||
| `cartelle(base)` | crea `01_domini`, `02_sovrapposizione`, `03_report` e le restituisce |
|
||||
| `colori_domini(tipi)` | un colore per dominio (grigio riposo, rosso riscaldamento, colori diversi per ogni raffreddamento) |
|
||||
| `grafico_domini(percorso, t, serie, domini)` | passo 1: `domini.png` |
|
||||
| `sovrapposizione_generale(...)` | passo 2a: `sovrapposizione_<serie>.png` |
|
||||
| `sovrapposizione_domini(...)` | passo 2b: `dominio<k>_<serie>.png` |
|
||||
| `scrivi_report(cartella, risultati, nome_csv, serie_nomi, cfg)` | passo 3: `report.md` e `risultati.json` |
|
||||
| `_num`, `_params`, `_eq_numerica`, `_salto` | formattazione LaTeX (valori, parametri ± IC, equazione con i valori sostituiti, salto) |
|
||||
|
||||
Il report incorpora le immagini con percorsi **relativi** (`../01_domini/domini.png`, …): cambiando nomi di file o cartelle vanno aggiornati
|
||||
`PASSI` e i riferimenti. Le grandezze matematiche del report sono sempre in LaTeX e i `$` devono restare bilanciati per riga (c'è un test).
|
||||
Descrizione di file e grafici: [output.md](output.md).
|
||||
@@ -0,0 +1,80 @@
|
||||
# Output
|
||||
|
||||
Ogni analisi scrive in `output/<nome del csv>/` (la cartella `output/` è ignorata da git). Ogni passo ha la sua sottocartella, numerata
|
||||
nell'ordine di esecuzione. Chi produce cosa: [moduli.md](moduli.md) (`uscita.py`).
|
||||
|
||||
```
|
||||
output/gen_1/
|
||||
├── 01_domini/ passo 1 — suddivisione dei dati
|
||||
│ ├── domini.png dati grezzi con i domini (colore per tipo, numerati) + zoom sui gradini
|
||||
│ └── domini.toml domini usati, modificabili a mano ([uso.md](uso.md))
|
||||
├── 02_sovrapposizione/ passo 2 — dati grezzi contro le equazioni trovate
|
||||
│ ├── sovrapposizione_<serie>.png serie intera con le equazioni a tratti, residui e zoom
|
||||
│ └── dominio<k>_<serie>.png un dominio: dati, modello scelto, altri modelli validi, residui
|
||||
└── 03_report/ passo 3
|
||||
├── report.md report completo, immagini incorporate, formule in LaTeX
|
||||
└── risultati.json tutti i fit con tutte le metriche (schema in moduli.md)
|
||||
```
|
||||
`fitseg domini` scrive solo `01_domini/`; `fitseg analizza` scrive tutto.
|
||||
|
||||
## Colori
|
||||
|
||||
| Dominio | Colore |
|
||||
|---|---|
|
||||
| riposo | grigio |
|
||||
| riscaldamento | rosso (i suoi campioni sono evidenziati con pallini, perché la fascia è sottile) |
|
||||
| raffreddamento | un colore **diverso per ognuno**, in ordine: blu, verde, viola, arancio, ciano (poi si ricomincia) |
|
||||
|
||||
Le fasce sono numerate come nelle tabelle (il numero compare solo se la fascia è abbastanza larga per il grafico). Il raffreddamento *k*-esimo
|
||||
ha lo stesso colore in tutti i grafici.
|
||||
|
||||
## `01_domini/domini.png`
|
||||
|
||||
Una riga per serie. A sinistra l'intero segnale con le fasce dei domini; a destra lo **zoom** sulla zona dei gradini (da 2 s prima del primo
|
||||
riscaldamento a 8 s dopo l'ultimo), dove 7 domini in pochi secondi si distinguono. Serve a controllare la suddivisione *prima* di fidarsi
|
||||
delle equazioni: se è sbagliata si corregge `domini.toml` e si rilancia ([uso.md](uso.md) §3).
|
||||
|
||||
## `02_sovrapposizione/sovrapposizione_<serie>.png`
|
||||
|
||||
Sopra: dati grezzi (punti grigi), fasce dei domini e **curva dell'equazione scelta** per ogni dominio nel colore del dominio. Sotto: residui
|
||||
$y - \hat y$ dei soli campioni usati nel fit (i campioni esclusi all'inizio dei raffreddamenti non compaiono, per non schiacciare la scala).
|
||||
A destra lo stesso con lo zoom. Il riscaldamento non ha curva (non si fitta) e i suoi campioni sono i pallini rossi.
|
||||
|
||||
Come leggere i residui: attorno a zero e senza struttura = modello adeguato; ondulazioni o code verso il basso = **scarti casuali** del sensore
|
||||
non descritti dal modello (A14), non un errore del fit.
|
||||
|
||||
## `02_sovrapposizione/dominio<k>_<serie>.png`
|
||||
|
||||
- **Riposo e raffreddamento**: punti grigi = campioni esclusi dal fit; punti neri = campioni usati; **linea continua** = modello scelto
|
||||
(nel colore del dominio, con $\mathrm{RMSE}$ in legenda); **tratteggiate** = gli altri modelli validi (confronto); sotto i residui del modello scelto.
|
||||
- **Riscaldamento**: la salita (pallini rossi) con 8 campioni di contesto prima e dopo, e nel titolo $\Delta$, numero di campioni e durata. Nessuna curva.
|
||||
- Dominio indeterminato: solo i dati, con «indeterminato» nel titolo.
|
||||
|
||||
## `03_report/report.md`
|
||||
|
||||
Le immagini sono link relativi (`../01_domini/domini.png`); le grandezze matematiche sono in LaTeX (`$…$` nel testo, `$$…$$` per l'equazione scelta
|
||||
con i valori sostituiti): servono un visualizzatore Markdown con supporto matematico (GitHub, VS Code, Obsidian). Struttura:
|
||||
|
||||
| Sezione | Contenuto |
|
||||
|---|---|
|
||||
| intestazione | nome del CSV, data, serie, definizione $t' = t - t_0$, livello degli intervalli (95 %) |
|
||||
| 1. Suddivisione in domini | `domini.png` e tabella (#, tipo, $t_0$, $t_1$, campioni) |
|
||||
| 2. Equazioni trovate | criterio di scelta con le soglie correnti; tabella (dominio, tipo, serie, modello, equazione e parametri ± IC) con una riga per dominio e serie; i riscaldamenti hanno il salto |
|
||||
| 3. Sovrapposizione dati–equazioni | un grafico per serie |
|
||||
| 4. Dettaglio per dominio | per ogni dominio e serie: grafico, modello scelto, equazione con i valori ($$…$$), parametri ± IC, $\sigma_\text{rumore}$, $\mathrm{RMSE}$, $R^2$, $\rho_1$, motivo, tabella di tutti i modelli provati ($k$, RMSE, AICc, BIC, $\rho_1$, valido) |
|
||||
|
||||
### Come leggere i messaggi
|
||||
|
||||
| Messaggio | Significato | Cosa fare |
|
||||
|---|---|---|
|
||||
| **salto** (riscaldamento) | salita rapida descritta, non fittata (A23) | niente: $\Delta$ è per difetto |
|
||||
| **(fisso)** accanto a un parametro | tenuto costante al valore del raffreddamento finale (A8); l'IC di $A$ è condizionato (A18) | niente; per disattivarlo vedi [configurazione.md](configurazione.md) |
|
||||
| **modello non adeguato** ($\mathrm{RMSE} > 1.3\,\sigma$) | il modello scelto è il migliore tra quelli validi ma non spiega tutta la varianza | guarda $\rho_1$ e i residui: tipicamente scarti casuali (A14) |
|
||||
| **Indeterminato** | nessun modello valido, o meno di `n_min_punti` campioni | verifica la suddivisione (domini troppo corti?) |
|
||||
| «valido: no» in tabella | fit non convergente, $\tau$ al limite o non identificabile ([algoritmi.md](algoritmi.md) §5.6) | un modello più semplice può essere preferito |
|
||||
|
||||
## `03_report/risultati.json`
|
||||
|
||||
Lista di domini con tutti i fit e tutte le metriche, anche dei modelli scartati. Schema dei campi: [moduli.md](moduli.md), «Strutture dati comuni».
|
||||
È JSON valido (i campi non applicabili, come `sigma` del riscaldamento, sono `null`), adatto a elaborazioni successive (confronti tra
|
||||
analisi, tabelle).
|
||||
@@ -0,0 +1,63 @@
|
||||
# Panoramica
|
||||
|
||||
## Cosa fa
|
||||
|
||||
`fitseg` prende la serie temporale di un sensore IR che si scalda e si raffredda (CSV `t_ms,T_C,dT_C`) e per ogni **tratto** del segnale
|
||||
trova l'equazione che lo descrive meglio. Il lavoro è in tre passi:
|
||||
|
||||
1. **Domini**: divide il tempo in tratti omogenei (riposo, riscaldamento, raffreddamento).
|
||||
2. **Equazioni**: in ogni tratto fitta i modelli adatti con minimi quadrati non lineari e sceglie il migliore con una regola fissa.
|
||||
3. **Sovrapposizione e report**: confronta i dati grezzi con le equazioni trovate e scrive un report con grafici e formule.
|
||||
|
||||
Tutto è deterministico: stessi dati e stessa configurazione danno gli stessi risultati. Non c'è nessun modello linguistico né nessuna
|
||||
parte casuale.
|
||||
|
||||
## Il segnale
|
||||
|
||||
Il sensore parte dalla temperatura ambiente (**riposo**). Poi il campione viene scaldato a **gradini**: la temperatura sale in una frazione
|
||||
di secondo (pochi campioni, **riscaldamento**) e subito dopo decade verso l'ambiente (**raffreddamento**). Con più gradini ravvicinati
|
||||
(2–4 s) il decadimento di ognuno è interrotto dal successivo; dopo l'ultimo gradino il raffreddamento prosegue fino all'ambiente.
|
||||
|
||||

|
||||
|
||||
*Figura: dati reali di `input/gen_1.csv` (serie `T_C`) con i domini riconosciuti da `fitseg` e le equazioni trovate. In alto il segnale completo, al
|
||||
centro lo zoom sui tre gradini, in basso la tabella che associa a ogni numero tipo, intervallo ed equazione. Il riscaldamento è rosso, il riposo grigio e
|
||||
ogni raffreddamento ha un colore diverso. Con 3 gradini i domini sono $1 + 2\cdot 3 = 7$.*
|
||||
|
||||
Il generatore dei dati di prova è il repository fratello `../gen_data` (modello e parametri: `../gen_data/docs/SPEC.md`). Le sue
|
||||
caratteristiche che contano per `fitseg` sono elencate in [assunzioni.md](assunzioni.md), sezione «Origine dei dati di prova».
|
||||
|
||||
## Concetti
|
||||
|
||||
| Termine | Significato |
|
||||
|---|---|
|
||||
| **serie** | una colonna del CSV da approssimare (`T_C` temperatura, `dT_C` differenza dall'ambiente). Tutte le serie condividono gli stessi domini |
|
||||
| **gradino** | salto positivo di temperatura tra due campioni consecutivi, sopra `soglia_gradino_c` |
|
||||
| **dominio** | tratto contiguo di campioni con un tipo: `riposo`, `riscaldamento` o `raffreddamento` |
|
||||
| **riscaldamento** | la salita rapida di un gradino (2–3 campioni): troppo breve per un fit, è descritta come **salto** ($\Delta$, da → a, durata) |
|
||||
| **raffreddamento** | il decadimento dopo un gradino, fino al gradino successivo o alla fine; modello esponenziale |
|
||||
| **raffreddamento finale / intermedio** | il finale è l'ultimo dominio (il più lungo, parametri liberi); gli intermedi stanno tra due gradini (corti) e ricevono $c$ e $\tau$ dal finale |
|
||||
| **modello** | una famiglia di equazioni (`costante`, `lineare`, `esp_decad`); vedi [algoritmi.md](algoritmi.md) §5.1 |
|
||||
| **parametro fisso** | parametro tenuto costante nel fit (non conta nei gradi di libertà); marcato «(fisso)» nel report |
|
||||
| **indeterminato** | nessun modello valido, oppure dominio con meno di `n_min_punti` campioni |
|
||||
| **non adeguato** | modello scelto ma con $\mathrm{RMSE} > 1.3\,\sigma$: la forma non spiega tutto il rumore |
|
||||
| **tempo del dominio** | $t' = t - t_0$, con $t_0$ il tempo del primo campione del dominio |
|
||||
|
||||
## Esempio (seed 1 di `gen_data`)
|
||||
|
||||
La figura sopra è il risultato di `fitseg analizza input/gen_1.csv` su `T_C`: sul raffreddamento finale $\tau = 16.08 \pm 0.07$ s, $c = 23.00$ °C, $A = 179.6$ °C;
|
||||
i due raffreddamenti intermedi usano gli stessi $c$ e $\tau$ e stimano solo $A$ ($84.3$ e $148.3$ °C); i tre riscaldamenti sono salti di $+82.2$, $+71.1$ e $+60.8$ °C.
|
||||
Il valore nominale del generatore è $\tau = 1/k = 15.9$ s (con $k$ perturbato del 2 % per seed): il fit lo recupera entro l'1 %.
|
||||
|
||||
## Dove andare avanti
|
||||
|
||||
| Per… | Leggi |
|
||||
|---|---|
|
||||
| lanciare i comandi, correggere i domini a mano | [uso.md](uso.md) |
|
||||
| capire cosa si dà per vero e cosa succede se non lo è | [assunzioni.md](assunzioni.md) |
|
||||
| sapere come funzionano rilevamento, fit e scelta, con le formule | [algoritmi.md](algoritmi.md) |
|
||||
| orientarsi nel codice (un modulo alla volta) | [moduli.md](moduli.md) |
|
||||
| cambiare un valore (ogni chiave di `config.toml`) | [configurazione.md](configurazione.md) |
|
||||
| leggere i file prodotti e i grafici | [output.md](output.md) |
|
||||
| eseguire e capire i test | [test.md](test.md) |
|
||||
| conoscere limiti noti e possibili estensioni | [limiti.md](limiti.md) |
|
||||
@@ -0,0 +1,83 @@
|
||||
# Test
|
||||
|
||||
```bash
|
||||
.venv/bin/pytest # tutti (≈ 7 s)
|
||||
.venv/bin/pytest tests/test_fit.py # un file
|
||||
.venv/bin/pytest -k raffreddamento # per nome
|
||||
.venv/bin/pytest -x -q # ferma al primo errore, output breve
|
||||
.venv/bin/pytest -rs # mostra i test saltati e il motivo
|
||||
.venv/bin/ruff check src tests # stile e import (deve passare)
|
||||
```
|
||||
|
||||
## Due livelli
|
||||
|
||||
| Livello | File | Dati | Se i CSV mancano |
|
||||
|---|---|---|---|
|
||||
| **Unitari** | `test_domini`, `test_fit`, `test_selezione`, `test_config_dati` | segnale sintetico generato nei test (`conftest.py`) | girano comunque |
|
||||
| **Integrazione** | `test_integrazione` | `input/gen_{1,2,3}.csv` di `gen_data`; CLI | **saltati** (non falliti) |
|
||||
|
||||
Un clone nuovo non ha i CSV (`input/*.csv` è ignorato da git): i test unitari bastano per lavorare sul codice; per quelli di integrazione si generano
|
||||
i CSV come in [uso.md](uso.md) §2.
|
||||
|
||||
## Il segnale sintetico (`tests/conftest.py`)
|
||||
|
||||
$$T(t) = T_\text{amb} + \sum_j A_j\,e^{-(t-t_j)/\tau}\,\mathbb{1}[t \ge t_j] + \varepsilon, \qquad \varepsilon \sim \mathcal{N}(0, 0.1^2)$$
|
||||
|
||||
con $T_\text{amb} = 23$, $\tau = 16$ s, gradini $(t_j, A_j) = (15, 80), (18, 70), (22, 60)$, campionamento 0.15 s, 120 s, **nessun scarto casuale**. La verità è nota, quindi si
|
||||
possono controllare numericamente i parametri recuperati. La funzione `segnale(...)` accetta rumore, seme, gradini e durata per costruire casi particolari
|
||||
(nessun gradino, un gradino, gradino sotto soglia). `dT_C` = $T - T_\text{amb}$. Fixture: `cfg` (config reale), `sintetico`, `csv_gen` (un CSV `gen_data`, saltato se assente).
|
||||
|
||||
## `test_domini.py` — suddivisione (assunzioni A5, A6, A21)
|
||||
|
||||
| Test | Verifica |
|
||||
|---|---|
|
||||
| `test_tre_gradini` | 3 gradini → 7 domini: riposo, (riscaldamento, raffreddamento) × 3 |
|
||||
| `test_riscaldamento_e_la_salita_rapida` | ogni riscaldamento ha ≤ 3 campioni |
|
||||
| `test_confini_vicini_agli_istanti_dei_gradini` | l'inizio di ogni riscaldamento è entro 0.5 s dal gradino vero |
|
||||
| `test_domini_contigui_e_completi` | primo `i0 = 0`, ultimo `i1 = n`, nessun buco |
|
||||
| `test_senza_gradini_un_solo_riposo` | nessun gradino → solo riposo |
|
||||
| `test_un_solo_gradino` | riposo, riscaldamento, raffreddamento |
|
||||
| `test_gradino_sotto_soglia_ignorato` | un salto di 10 °C (< soglia) non è un gradino |
|
||||
| `test_domini_toml_andata_e_ritorno` | scrivere e rileggere `domini.toml` dà gli stessi domini |
|
||||
| `test_domini_toml_non_contigui_rifiutati` | domini non contigui → `ValueError` |
|
||||
| `test_domini_toml_tipo_non_valido` | tipo sconosciuto → `ValueError` |
|
||||
|
||||
## `test_fit.py` — fit e analisi (A7, A8, A13, A18, A22, A23)
|
||||
|
||||
| Test | Verifica |
|
||||
|---|---|
|
||||
| `test_modelli_valutazione` | valori dei modelli; il registro contiene esattamente `costante`, `lineare`, `esp_decad` |
|
||||
| `test_esp_decad_recupera_parametri` (5 seed) | $\tau$ entro il 2 %, $c$ entro 0.2, $A$ entro il 2 %, $\mathrm{RMSE} \approx \sigma$, fit valido |
|
||||
| `test_parametri_fissi_restano_fissi` | $c$ e $\tau$ fissi restano invariati, $k = 1$, $A$ recuperata entro il 3 % |
|
||||
| `test_tau_libero_non_identificabile_su_tratto_corto_e_rumoroso` (5 seed) | su 3 s con disturbi di 2 °C, $\tau$ libero è «non identificabile» (motivo del fissaggio) |
|
||||
| `test_analisi_completa_sintetico` | 7 domini; finale con $\tau$ entro il 3 % e nessun parametro fisso; intermedi con `c`, `tau` fissi uguali al finale; ampiezze $A$ degli intermedi entro il 5 % dal valore atteso |
|
||||
| `test_riscaldamento_descritto_come_salto_non_fittato` | `fit` vuoto, `scelta` assente, $\Delta$ entro il 15 % dell'ampiezza vera, ≤ 3 campioni |
|
||||
| `test_un_solo_raffreddamento_resta_libero` | un solo gradino → il raffreddamento (finale) non ha parametri fissi |
|
||||
| `test_dominio_troppo_corto_indeterminato` | 5 campioni → `indeterminato` |
|
||||
| `test_riproducibile` | due esecuzioni identiche danno risultati identici |
|
||||
|
||||
## `test_selezione.py` — regola di scelta ([algoritmi.md](algoritmi.md) §6)
|
||||
|
||||
Su fit finti costruiti a mano: vince il modello con meno parametri entro $\Delta\mathrm{AICc}$; vince il migliore oltre la soglia; si scartano non
|
||||
convergenti, a limite, non identificabili; nessun valido → indeterminato; si preferiscono i modelli adeguati anche con AICc peggiore; nessun adeguato →
|
||||
scelto ma segnalato.
|
||||
|
||||
## `test_config_dati.py` — errori di configurazione e CSV (A1, A2)
|
||||
|
||||
`config.toml` reale valido; chiave mancante; lista dei modelli vuota per un tipo; file di configurazione inesistente; CSV valido (conversione
|
||||
ms → s); colonna mancante; tempo non crescente.
|
||||
|
||||
## `test_integrazione.py` — su `gen_data` e CLI
|
||||
|
||||
| Test | Verifica |
|
||||
|---|---|
|
||||
| `test_domini_su_csv_gen` (3 seed) | 7 domini nell'ordine atteso |
|
||||
| `test_raffreddamento_tau_nominale` (3 seed) | raffreddamento finale: `esp_decad`, $\tau$ entro il 10 % di 15.9 s |
|
||||
| `test_raffreddamenti_intermedi_ereditano_costanti` (3 seed) | intermedi con `c`, `tau` fissi uguali al finale, $A > 0$ |
|
||||
| `test_cli_domini_e_analizza` | `fitseg domini` scrive solo `01_domini/`; `analizza --domini` scrive sovrapposizione e report; il report rimanda alle immagini giuste; formule principali in LaTeX e `$` bilanciati su ogni riga |
|
||||
| `test_cli_errori` | CSV inesistente → exit 4; configurazione inesistente → exit 3 |
|
||||
|
||||
## Cosa non è coperto
|
||||
|
||||
Seed oltre i tre di `gen_data`; rumore e gradini molto diversi dalla configurazione d'esempio; dati reali; i grafici (si controllano a vista, [output.md](output.md)).
|
||||
Prova manuale dopo una modifica: `fitseg analizza input/gen_1.csv` e controllare in `report.md` i 7 domini e $\tau \approx 16$ s nel finale.
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
# Uso
|
||||
|
||||
## 1. 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]" # numpy, scipy, matplotlib; pytest e ruff per lo sviluppo
|
||||
```
|
||||
Il comando si chiama `fitseg`: `.venv/bin/fitseg`, oppure dopo `source .venv/bin/activate` semplicemente `fitseg`.
|
||||
|
||||
## 2. Dati di ingresso
|
||||
|
||||
I CSV da analizzare vanno nella cartella **`input/`** (la cartella è nel repository, i file `*.csv` no). Formato: intestazione, tempo
|
||||
strettamente crescente, una colonna per serie, numeri senza celle vuote (A1, A2); i nomi delle colonne e l'unità del tempo sono in
|
||||
`[dati]` di [config.toml](../config.toml) ([configurazione.md](configurazione.md)).
|
||||
|
||||
```
|
||||
t_ms,T_C,dT_C
|
||||
151,22.90,0.20
|
||||
301,22.63,0.03
|
||||
```
|
||||
|
||||
I CSV di prova si generano con il repository fratello `../gen_data` (modello: `../gen_data/docs/SPEC.md`). I test di integrazione usano i seed 1–3:
|
||||
|
||||
```bash
|
||||
mkdir -p input
|
||||
for s in 1 2 3; do
|
||||
(cd ../gen_data && .venv/bin/python genera.py config.toml --seed $s -o ../EquationMiner/input/gen_$s.csv)
|
||||
done
|
||||
```
|
||||
|
||||
## 3. Comandi
|
||||
|
||||
```bash
|
||||
fitseg domini input/gen_1.csv # passo 1: come verrebbero suddivisi i dati (tabella, domini.png, domini.toml), nessun fit
|
||||
fitseg analizza input/gen_1.csv # tutti i passi: domini, equazioni, sovrapposizione, report
|
||||
fitseg analizza input/gen_1.csv --domini output/gen_1/01_domini/domini.toml # con i domini corretti a mano
|
||||
```
|
||||
|
||||
| Opzione | Dove | Significato |
|
||||
|---|---|---|
|
||||
| `--config PATH` | prima del sottocomando | file di configurazione (default `config.toml`) |
|
||||
| `--output DIR` | prima del sottocomando | cartella madre dell'output (default `[output] cartella_output`); i file vanno in `DIR/<nome csv>/` |
|
||||
| `--domini FILE` | solo `analizza` | usa questi domini invece della proposta automatica |
|
||||
|
||||
**Flusso consigliato**: lancia `fitseg domini`, guarda `01_domini/domini.png` (in particolare lo zoom); se la suddivisione è giusta lancia
|
||||
`fitseg analizza`, altrimenti correggi `domini.toml` e rilancia con `--domini`.
|
||||
|
||||
### Correggere i domini a mano
|
||||
|
||||
Apri `output/<csv>/01_domini/domini.toml`, modifica `t0_s` / `t1_s` o il `tipo` (`riposo`, `riscaldamento`, `raffreddamento`), aggiungi o togli blocchi
|
||||
`[[dominio]]`. I domini devono restare contigui. Formato e regole: [configurazione.md](configurazione.md) («domini.toml»). Con `--domini` il file viene
|
||||
riscritto normalizzato e il grafico rigenerato, quindi `domini.png` mostra sempre la suddivisione effettivamente usata.
|
||||
|
||||
## 4. Cosa esce
|
||||
|
||||
Una cartella per CSV con `01_domini/`, `02_sovrapposizione/`, `03_report/report.md`: [output.md](output.md). Il report si apre con un visualizzatore Markdown
|
||||
con supporto matematico (formule in LaTeX).
|
||||
|
||||
## 5. Codici di uscita
|
||||
|
||||
| Codice | Significato |
|
||||
|---|---|
|
||||
| 0 | ok |
|
||||
| 2 | uso errato della riga di comando (argparse) |
|
||||
| 3 | `config.toml` non valido: file illeggibile, TOML non valido, chiave mancante, valore non ammesso |
|
||||
| 4 | CSV illeggibile o non valido (colonna mancante, valori non numerici, tempo non crescente) |
|
||||
| 6 | `domini.toml` non valido (tipo non ammesso, intervallo vuoto, domini non contigui) |
|
||||
|
||||
Gli errori sono stampati su stderr in italiano come `Errore: …`.
|
||||
|
||||
## 6. Sviluppo
|
||||
|
||||
Test e controllo dello stile: [test.md](test.md). Mappa del codice e come aggiungere un modello: [moduli.md](moduli.md).
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Analisi: fit dei modelli per ogni dominio e serie e scelta con la regola di SPEC §4.2; il riscaldamento è descritto come salto."""
|
||||
"""Analisi: fit dei modelli per ogni dominio e serie e scelta con la regola di docs/algoritmi.md §6; il riscaldamento è descritto come salto."""
|
||||
|
||||
import math
|
||||
|
||||
|
||||
+1
-1
@@ -26,6 +26,6 @@ def leggi_csv(percorso, cfg):
|
||||
if d["unita_tempo"] == "ms":
|
||||
t = t / 1000.0
|
||||
if np.any(np.diff(t) <= 0):
|
||||
raise ErroreDati("il tempo non è strettamente crescente (ipotesi H1)")
|
||||
raise ErroreDati("il tempo non è strettamente crescente (assunzione A1, docs/assunzioni.md)")
|
||||
serie = {s: righe[:, intestazione.index(s)] for s in d["serie"]}
|
||||
return t, serie
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Registro dei modelli (t' = t − inizio del dominio). Nuovo modello = nuova voce qui, in docs/SPEC.md e in config.toml."""
|
||||
"""Registro dei modelli (t' = t − inizio del dominio). Nuovo modello = nuova voce qui, in docs/algoritmi.md (§5.1) e in config.toml."""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Regola di scelta del modello (SPEC §5.3) su fit finti."""
|
||||
"""Regola di scelta del modello (docs/algoritmi.md §6) su fit finti."""
|
||||
|
||||
from fitseg.analisi import scegli
|
||||
|
||||
|
||||
Reference in new issue
Block a user