From 90f8eb589339ab1d3f1361c6feddeb1491ec82a9 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Thu, 29 Oct 2026 08:56:08 +0100 Subject: [PATCH] Accorcia la corsa a 120 s, aggiunge grafico.py e istruzioni per installare uv Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 2 +- README.md | 22 ++++++++++++++++- config.toml | 4 +-- docs/SPEC.md | 7 +++--- grafico.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 grafico.py diff --git a/CLAUDE.md b/CLAUDE.md index 35eb253..0d67fe9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Stato -[genera.py](genera.py) è lo script unico (PEP 723, `uv run genera.py config.toml --seed 42`, scrive in `output/`); implementa [docs/SPEC.md](docs/SPEC.md), unica fonte di verità su output, modello, CLI, errori e controlli di accettazione. Leggila per intero prima di scrivere codice. Documentazione, commenti e messaggi di errore sono in italiano. +[genera.py](genera.py) è lo script unico (PEP 723, `uv run genera.py config.toml --seed 42`, scrive in `output/`). [grafico.py](grafico.py) disegna un CSV di output (script separato, dipende da matplotlib; `genera.py` resta con solo numpy); `genera.py` implementa [docs/SPEC.md](docs/SPEC.md), unica fonte di verità su output, modello, CLI, errori e controlli di accettazione. Leggila per intero prima di scrivere codice. Documentazione, commenti e messaggi di errore sono in italiano. ## File diff --git a/README.md b/README.md index 25371f6..5bdcb64 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,17 @@ Genera un CSV con la temperatura `T` di un sensore IR e la differenza `ΔT = T ## Requisiti -[uv](https://docs.astral.sh/uv/). Lo script dichiara le sue dipendenze (Python ≥ 3.11, `numpy`): al primo avvio uv crea da solo l'ambiente virtuale, non serve installare altro. +[uv](https://docs.astral.sh/uv/). Gli script dichiarano le loro dipendenze (Python ≥ 3.11, `numpy`, `matplotlib` per il grafico): al primo avvio uv crea da solo l'ambiente virtuale e, se serve, scarica Python. Non serve installare altro. + +### Installare uv + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh # Linux e macOS +powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows +pip install uv # alternativa, se hai già Python +``` + +Apri un nuovo terminale e verifica con `uv --version`. Altre opzioni (Homebrew, WinGet, ecc.) sono nella [guida ufficiale](https://docs.astral.sh/uv/getting-started/installation/). ## Uso @@ -23,6 +33,16 @@ uv run genera.py config.toml -o - > out.csv # su stdout Stesso seed e stessa configurazione danno sempre lo stesso file. La cartella `output/` è ignorata da git. +## Grafico + +```bash +uv run grafico.py # apre il grafico dell'ultimo CSV in output/ +uv run grafico.py output/gen_42.csv # un CSV preciso +uv run grafico.py -o grafico.png # salva l'immagine (png, pdf, svg) invece di aprire la finestra +``` + +Mostra `T` e `ΔT` in funzione del tempo. `grafico.py` ha le sue dipendenze (`numpy`, `matplotlib`), gestite da uv come per `genera.py`; senza schermo usa `-o`. + ## Output ``` diff --git a/config.toml b/config.toml index cd5ec2a..7cbccb2 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,6 @@ # config.toml — unica fonte dei valori (esempio). Unità nei suffissi: _s secondi, _ms millisecondi, _c gradi C, _hz 1/s. -durata_s = 300.0 # abbastanza lunga perché T torni a T_amb dopo il raffreddamento +durata_s = 120.0 # basta a far tornare T vicino a T_amb dopo il raffreddamento dt_ms = 150.5 # intervallo medio tra campioni jitter_ms = 0.6 # variazione uniforme ±jitter dell'intervallo @@ -20,7 +20,7 @@ wander_tau_s = 3.5 passo_T_c = 0.01 # risoluzione di T e dT nel CSV # Riscaldamento e raffreddamento -t_impulsi_s = [110.7, 111.9, 114.1] # istanti dei gradini di riscaldamento ([] = nessun riscaldamento) +t_impulsi_s = [15.7, 16.9, 19.1] # istanti dei gradini di riscaldamento ([] = nessun riscaldamento) dT_impulsi_c = [88.0, 75.0, 65.0] # ampiezza di ogni gradino (stessa lunghezza di t_impulsi_s) k_raffreddamento_hz = 0.063 # raffreddamento esponenziale: tau = 1/k tau_sensore_s = 0.07 # ritardo di risposta del sensore diff --git a/docs/SPEC.md b/docs/SPEC.md index 9d3d554..6c6b874 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -61,12 +61,11 @@ Verificati su un prototipo (30 seed): | Controllo | Atteso | |---|---| | `T` a inizio run | ≈ `T_amb` (≈ 22.65 °C) | -| Rumore a freddo: `std(diff(T))/√2` sui primi ~100 s | 0.105 – 0.115 | +| Rumore a freddo: `std(diff(T))/√2` prima del primo gradino (~15 s) | 0.09 – 0.13 | | Picco di `T` | ≈ 220 – 222 °C | -| `T` a 134 s | ≈ 80.5 °C | -| `T` a fine run | ≈ `T_amb` (≈ 23.1 °C, entro ~0.1 °C) | +| `T` a fine run | ≈ `T_amb` (≈ 23.4 °C, entro ~0.5 °C) | | `ΔT` a inizio run | ≈ 0 °C (solo rumore) | -| `ΔT` a fine run | ≈ 0 °C (solo rumore) | +| `ΔT` a fine run | ≈ 0.4 °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 | diff --git a/grafico.py b/grafico.py new file mode 100644 index 0000000..f914ad5 --- /dev/null +++ b/grafico.py @@ -0,0 +1,70 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = ["numpy", "matplotlib"] +# /// +"""Mostra o salva il grafico di T e ΔT di un CSV prodotto da genera.py.""" + +import argparse +import sys +from pathlib import Path + +import matplotlib +import numpy as np + +CARTELLA_OUTPUT = Path("output") + + +def errore(msg): + print(f"Errore: {msg}", file=sys.stderr) + sys.exit(1) + + +def ultimo_csv(): + csv = sorted(CARTELLA_OUTPUT.glob("*.csv"), key=lambda p: p.stat().st_mtime) + if not csv: + errore(f"nessun CSV in '{CARTELLA_OUTPUT}': genera prima i dati con genera.py") + return csv[-1] + + +def main(): + ap = argparse.ArgumentParser(description="Grafico di T e ΔT da un CSV di genera.py.") + ap.add_argument("csv", nargs="?", type=Path, help="CSV da disegnare (default: l'ultimo in output/)") + ap.add_argument("-o", "--output", type=Path, help="salva l'immagine (png, pdf, svg) invece di aprire la finestra") + args = ap.parse_args() + + percorso = args.csv or ultimo_csv() + try: + t_ms, T, dT = np.loadtxt(percorso, delimiter=",", skiprows=1, unpack=True) + except OSError as e: + errore(f"impossibile leggere '{percorso}': {e.strerror}") + except ValueError: + errore(f"'{percorso}' non ha il formato t_ms,T_C,dT_C") + + if args.output: + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + fig, (ax_T, ax_dT) = plt.subplots(2, 1, sharex=True, figsize=(10, 6)) + t_s = t_ms / 1000 + ax_T.plot(t_s, T, lw=0.8) + ax_T.set_ylabel("T [°C]") + ax_dT.plot(t_s, dT, lw=0.8, color="tab:orange") + ax_dT.set_ylabel("ΔT = T − T_amb [°C]") + ax_dT.set_xlabel("tempo [s]") + for ax in (ax_T, ax_dT): + ax.grid(alpha=0.3) + fig.suptitle(percorso.name) + fig.tight_layout() + + if args.output: + try: + fig.savefig(args.output, dpi=150) + except OSError as e: + errore(f"impossibile scrivere '{args.output}': {e.strerror}") + print(f"scritto: {args.output}", file=sys.stderr) + else: + plt.show() + + +if __name__ == "__main__": + main()