# Generatore CSV `T` e `ΔT` con rumore realistico — specifica minima **Obiettivo**: un solo script, un solo file di configurazione, un CSV con la temperatura `T` e la differenza `ΔT = T − T_amb`, con rumore, quantizzazione e jitter dei tempi simili a un sensore IR reale. **Assunzione da confermare**: `T` = temperatura oggetto del sensore IR, `T_amb` = temperatura ambiente dello stesso sensore (non scritta nel CSV, serve solo per `ΔT`). ## 1. Output ``` t_ms,T_C,dT_C 150,24.50,1.80 301,24.41,1.71 ``` - `t_ms`: intero; `T_C`, `dT_C`: due decimali (`passo_T_c`). Separatore `,`, fine riga `\n`. - `ΔT` è calcolato **dopo** rumore e quantizzazione di entrambi i canali, quindi eredita il passo a gradini dell'ambiente. ## 2. Configurazione Tutti i valori sono in [`config.toml`](../config.toml) (unica fonte: questo documento non li ripete). Gruppi: tempi · ambiente (anche baseline di T) · rumore · gradini di riscaldamento e raffreddamento · variabilità tra corse · salti. Nessun valore è scritto nello script: chiave mancante ⇒ errore. ## 3. Modello (5 passi) 1. **Tempi**: `t_i = t_{i−1} + dt_ms + U(−jitter_ms, +jitter_ms)`, fino a `durata_s`. 2. **T vera** = ambiente vero + eccesso termico (T parte da `T_amb`, sale con i gradini e ridiscende fino a `T_amb`): - ambiente vero `B(t) = amb_inf + amb_A·exp(−t/τ_amb)` (stessa curva del passo 5, senza rumore né quantizzazione); - eccesso `x(t) = Σ_j ΔT_j · exp(−k·(t − t_j))` per `t ≥ t_j` (un gradino per ogni `t_impulsi_s`, poi raffreddamento esponenziale); - **variabilità**: a ogni corsa istanti, ampiezze e `k` dei gradini sono perturbati a caso (σ in `impulsi_jitter_*`, `k_jitter_rel`), quindi la forma cambia con il seed; - **salti** (stocastici, scarti casuali con rientro `exp(−t/τ)`, come i gradini irregolari osservati su un sensore reale): durante il **riscaldamento** (tra il primo e l'ultimo gradino) con `salti_risc_*`, ampiezza `N(media, σ²)` nei due versi; durante il **raffreddamento** (dopo l'ultimo gradino, mentre `x ≥ salti_x_min_c`) con `salti_*`; istanti di tipo Poisson in entrambi i casi; ampiezza limitata a ±`salti_max_c` (niente scarti estremi); - `T_vera = B + x + salti`. 3. **Risposta del sensore**: filtro del 1° ordine `y' = (T_vera − y)/τ_sens` (griglia fine, 5 ms, poi interpolazione ai `t_i`). 4. **Rumore di T**: `T_i = y_i + w_i + σ(x_i)·N(0,1)`, con - `σ(x) = σ + (σ_caldo − σ)·min(1, x/x_rif)`; - `w` = deriva lenta (Ornstein-Uhlenbeck): `w_{i+1} = φ·w_i + σ_w·√(1−φ²)·N(0,1)`, `φ = exp(−Δt/τ_w)`. Poi arrotondamento a `passo_T_c`. 5. **Ambiente e ΔT**: `amb_i = round_{amb_passo}( amb_inf + amb_A·exp(−t/τ_amb) + N(0, amb_sigma²) )`; `dT_i = round_{passo}( T_i − amb_i )`. ## 4. Uso (script unico, ambiente `.venv`) `genera.py` con dipendenza dichiarata in testa (PEP 723, usata anche da `uv run`); ambiente `.venv` creato con `uv venv` e `uv pip install numpy`: ```python # /// script # requires-python = ">=3.11" # dependencies = ["numpy"] # /// ``` ```bash python genera.py config.toml --seed 42 # riproducibile -> output/gen_42.csv python genera.py config.toml -o out.csv # percorso a scelta, seed casuale (stampato su stderr) python genera.py config.toml -o - > out.csv # stdout ``` Opzioni: `config` (posizionale), `-o/--output` (default `output/gen_.csv`, cartella creata se manca; `-` = stdout), `--seed` (default: casuale, stampato su stderr). Uscita con errore (exit 1) e messaggio in italiano se manca una chiave, se `t_impulsi_s` e `dT_impulsi_c` hanno lunghezze diverse o se il file non è scrivibile. La lettura TOML usa `tomllib` (standard library): unica dipendenza esterna `numpy`. ## 5. Controlli di accettazione (con la configurazione d'esempio) Verificati su 30 seed (valori min–max; picco e `T`/`ΔT` finali dipendono dalla variabilità dei gradini e dai salti): | Controllo | Atteso | |---|---| | `T` a inizio run | ≈ `T_amb` (≈ 22.65 °C) | | Rumore a freddo: `std(diff(T))/√2` prima del primo gradino (~15 s) | 0.09 – 0.13 | | Picco di `T` | 197 – 209 °C | | `T` a fine run | ≈ `T_amb`: 23.1 – 23.7 °C | | `ΔT` a inizio run | ≈ 0 °C (solo rumore) | | `ΔT` a fine run | 0.3 – 0.65 °C (residuo del raffreddamento) | | Intervalli `dt` | tutti in `dt_ms ± jitter_ms` (prima di arrotondare `t_ms` a intero: nel CSV le differenze sono 149–152) | | Stesso `--seed` e stessa config | file identico | ## 6. Limiti (accettati per semplicità) - Un solo tempo di raffreddamento esponenziale (nessuna distinzione conduzione/convezione). - Gradini istantanei (nessun transito sotto l'induttore e nessuna dipendenza dalla velocità). - L'ambiente non sale quando T è alta (nessun riscaldamento dell'ambiente del sensore): `ΔT` è leggermente sovrastimato durante e subito dopo il riscaldamento. - Nessun glitch di campionamento; i salti durante il raffreddamento sono un modello statistico semplice (rientro esponenziale), non un fenomeno fisico. - Rumore tarato su un solo sensore: ricalibrare `sigma_c`, `wander_*`, `amb_*` con i propri dati.