Files
LOFM/test_statistica.py

296 lines
8.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import marimo
__generated_with = "0.25.0"
app = marimo.App(width="medium")
@app.cell
def _():
import marimo as mo
return (mo,)
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
# Test del modulo `statistica.py`
Questo notebook importa `statistica.py` (notebook marimo) e testa **ogni
funzione** definita al suo interno, verificando che i risultati siano
corretti e segnalando eventuali errori.
Poiché in `statistica.py` le funzioni sono definite dentro alle celle
`@app.cell` (non con `@app.function`), per poterle riutilizzare qui
eseguiamo l'intero notebook con `app.run()`, che restituisce un
dizionario con tutte le definizioni (`defs`). Solo la classe `Data`
(definita con `@app.class_definition`) sarebbe importabile direttamente.
""")
return
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
## Import e caricamento del modulo da testare
""")
return
@app.cell
def _():
import matplotlib
import numpy as np
import pandas as pd
import sympy as sp
import statistica
# Esegue tutte le celle del notebook importato e recupera le definizioni
_outputs, defs = statistica.app.run()
Data = defs["Data"]
compat = defs["compat"]
stat_chauv = defs["stat_chauv"]
calc_var = defs["calc_var"]
reg_lin = defs["reg_lin"]
plot_gauss = defs["plot_gauss"]
trova_outlier = defs["trova_outlier"]
rimuovi_outlier = defs["rimuovi_outlier"]
return Data, compat, np, pd, plot_gauss, sp
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
## Helper di test
""")
return
@app.cell
def _(mo):
risultati_test = []
def esegui_test(nome, fn):
"""Esegue fn(), registra esito e ritorna un mo.md con il risultato."""
try:
dettaglio = fn()
risultati_test.append((nome, True, ""))
corpo = f"✅ **{nome}**: OK"
if dettaglio:
corpo += f"\n\n{dettaglio}"
return mo.md(corpo)
except Exception as e:
import traceback
tb = traceback.format_exc()
risultati_test.append((nome, False, str(e)))
return mo.md(f"❌ **{nome}**: ERRORE — `{e}`\n\n```\n{tb}\n```")
return esegui_test, risultati_test
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
## Test 1 — `compat(x1, x2, u1, u2)`
""")
return
@app.cell
def _(compat, esegui_test, np):
def _test_compat():
k, diff = compat(10.0, 10.5, 0.2, 0.3)
assert np.isclose(diff, -0.5), f"diff errato: {diff}"
atteso_k = 0.5 / np.sqrt(0.2**2 + 0.3**2)
assert np.isclose(k, atteso_k), f"k errato: {k} atteso {atteso_k}"
return f"k = {k:.4f}, diff = {diff:.4f} (attesi: k={atteso_k:.4f}, diff=-0.5)"
esegui_test("compat", _test_compat)
return
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
## Test 2 — `Data.stat_chauv(prefissi, err_strumentale)`
Costruiamo un campione con 4 punti sperimentali, ciascuno con 8 misure
ripetute della grandezza `T`. Nel primo punto inseriamo un valore
anomalo (~10σ) che il criterio di Chauvenet dovrebbe individuare e
rimuovere.
""")
return
@app.cell
def _(Data, esegui_test, np, pd):
np.random.seed(0)
_righe = [np.random.normal(10, 0.05, size=8) for _ in range(4)]
_righe[0][0] = 10.5 # outlier moderato ma rilevabile
campione_chauv = pd.DataFrame(_righe, columns=[f"T{i+1}" for i in range(8)])
dato_chauv = Data(campione_chauv)
def _test_stat_chauv():
dato_chauv.stat_chauv("T", 0.02)
colonne_attese = {"T", "uT_strum", "uT_stat", "uT", "nT", "outT"}
mancanti = colonne_attese - set(dato_chauv.analisi_stat.columns)
assert not mancanti, f"colonne mancanti in analisi_stat: {mancanti}"
assert len(dato_chauv.analisi_stat.at[0, "outT"]) >= 1, (
"l'outlier atteso non è stato rimosso"
)
return dato_chauv.analisi_stat.to_markdown()
esegui_test("Data.stat_chauv", _test_stat_chauv)
return campione_chauv, dato_chauv
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
## Test 3 — `Data.calc_var(funz, nome_risultato)`
Aggiungiamo una seconda grandezza `P` allo stesso `Data` e calcoliamo
`Z = T * P` con propagazione simbolica dell'incertezza.
""")
return
@app.cell
def _(campione_chauv, dato_chauv, esegui_test, np, pd, sp):
def _test_calc_var():
np.random.seed(1)
campione_p = pd.DataFrame(
np.random.normal(2, 0.02, size=(4, 8)),
columns=[f"P{i+1}" for i in range(8)],
)
dato_chauv.campione = pd.concat([campione_chauv, campione_p], axis=1)
dato_chauv.stat_chauv(["T", "P"], [0.02, 0.01])
T, P = sp.symbols("T P", positive=True)
dato_chauv.calc_var(T * P, "Z")
for col in ["Z", "uZ", "%uZ_T", "%uZ_P"]:
assert col in dato_chauv.analisi_stat.columns, f"colonna mancante: {col}"
T_val = dato_chauv.analisi_stat["T"].astype(float)
P_val = dato_chauv.analisi_stat["P"].astype(float)
Z_val = dato_chauv.analisi_stat["Z"].astype(float)
assert np.allclose(Z_val, T_val * P_val), "Z != T*P: propagazione errata"
return dato_chauv.analisi_stat[["T", "uT", "P", "uP", "Z", "uZ", "%uZ_T", "%uZ_P"]].to_markdown()
esegui_test("Data.calc_var", _test_calc_var)
return
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
## Test 4 — `Data.reg_lin(...)` (incl. `residui`, `plot_reg`, `plot_residui`)
Generiamo dati sintetici y = 2x + 1 con rumore gaussiano e verifichiamo
che la regressione pesata recuperi correttamente A e B, e che vengano
prodotti anche i grafici e i residui.
""")
return
@app.cell
def _(Data, esegui_test, np, pd):
def _test_reg_lin():
np.random.seed(2)
n = 12
A_vero, B_vero = 2.0, 1.0
x = np.linspace(0, 10, n)
ux = np.full(n, 0.1)
uy = np.full(n, 0.2)
y = A_vero * x + B_vero + np.random.normal(0, 0.2, n)
dato_reg = Data(pd.DataFrame({"x": x, "y": y, "ux": ux, "uy": uy}))
dato_reg.analisi_stat = dato_reg.campione.copy()
dato_reg.reg_lin(
stampa_param=True,
plot_regressione=True,
calc_residui=True,
x_label="x",
y_label="y",
r_label="residuo",
titolo_reg="Test regressione",
titolo_residui="Test residui",
)
assert dato_reg.param_reg is not None, "param_reg non impostato"
for chiave in ["A", "B", "uA", "uB", "covAB", "x2", "P"]:
assert chiave in dato_reg.param_reg.index, f"manca {chiave} in param_reg"
errore_A = abs(dato_reg.param_reg["A"] - A_vero)
assert errore_A < 0.3, f"A stimato troppo lontano dal vero: {dato_reg.param_reg['A']}"
assert "r" in dato_reg.analisi_stat.columns
assert "ur" in dato_reg.analisi_stat.columns
p = dato_reg.param_reg
return (
f"A = {p['A']:.4f} ± {p['uA']:.4f} (vero: {A_vero}) \n"
f"B = {p['B']:.4f} ± {p['uB']:.4f} (vero: {B_vero}) \n"
f"chi² = {p['x2']:.3f}, P = {p['P']:.3f}"
)
esegui_test("Data.reg_lin", _test_reg_lin)
return
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
## Test 5 — `plot_gauss(gaussiane)`
""")
return
@app.cell
def _(esegui_test, plot_gauss):
def _test_plot_gauss():
gaussiane = [
(0, 1, 0, "N(0,1)"),
(2, 0.5, 1, "N(2, 0.5)"),
]
plot_gauss(gaussiane)
return "Grafico generato senza errori per 2 gaussiane."
esegui_test("plot_gauss", _test_plot_gauss)
return
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""
## Riepilogo finale
""")
return
@app.cell
def _(mo, risultati_test):
_n_ok = sum(1 for _, ok, _ in risultati_test if ok)
_n_tot = len(risultati_test)
_righe = [f"| {nome} | {'✅ OK' if ok else '❌ ERRORE: ' + err} |" for nome, ok, err in risultati_test]
_tabella = "| Funzione | Esito |\n|---|---|\n" + "\n".join(_righe)
_titolo = (
f"### ✅ Tutti i test sono passati ({_n_ok}/{_n_tot})"
if _n_ok == _n_tot
else f"### ⚠️ {_n_tot - _n_ok} test falliti su {_n_tot}"
)
mo.md(f"{_titolo}\n\n{_tabella}")
return
if __name__ == "__main__":
app.run()