- rimossi scripts/, stadi.py, docs/USO.md, modelli non usati, partenze casuali, output ridondanti - uscita unica (report.md con grafici per dominio, risultati.json, proposta.png) - test: unitari su segnale sintetico, integrazione su dati/gen_*.csv (saltati se mancano) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
64 lines
3.2 KiB
Python
64 lines
3.2 KiB
Python
"""Analisi: fit dei modelli di ogni tipo di dominio su ogni (dominio, serie) e scelta con la regola di SPEC §5.3."""
|
|
|
|
import math
|
|
|
|
from .domini import sigma_rumore
|
|
from .fit import adatta
|
|
from .modelli import MODELLI
|
|
|
|
|
|
def valido(fit):
|
|
return (fit.get("convergenza") and not fit["parametri_a_limite"] and not fit["non_identificabile"]
|
|
and math.isfinite(fit["aicc"]))
|
|
|
|
|
|
def scegli(fit_list, sigma, cfg):
|
|
"""(fit scelto, motivo, adeguato); (None, motivo, False) se nessun fit è valido."""
|
|
f = cfg["fit"]
|
|
validi = [x for x in fit_list if valido(x)]
|
|
if not validi:
|
|
return None, "indeterminato: nessun modello convergente, identificabile e lontano dai limiti", False
|
|
adeguati = [x for x in validi if x["rmse"] <= f["rmse_vs_rumore_max"] * sigma]
|
|
pool = adeguati or validi
|
|
miglior = min(x["aicc"] for x in pool)
|
|
scelto = min((x for x in pool if x["aicc"] <= miglior + f["soglia_delta_aicc"]),
|
|
key=lambda x: (x["n_parametri"], x["aicc"]))
|
|
motivo = (f"{len(validi)}/{len(fit_list)} modelli validi; "
|
|
+ ("adeguato (RMSE ≤ soglia·σ)" if adeguati else "nessun modello adeguato (RMSE > soglia·σ)")
|
|
+ f"; meno parametri entro ΔAICc ≤ {f['soglia_delta_aicc']}")
|
|
return scelto, motivo, bool(adeguati)
|
|
|
|
|
|
def _serie(ty, yy, sigma, tipo, fissi, cfg):
|
|
voce = {"sigma": float(sigma), "fit": [], "scelta": None}
|
|
if len(yy) < cfg["domini"]["n_min_punti"]:
|
|
voce["motivo"] = f"indeterminato: {len(yy)} campioni < n_min_punti"
|
|
return voce
|
|
for m in cfg["fit"]["modelli_per_tipo"][tipo]:
|
|
if len(MODELLI[m][1]) <= len(yy) - 2:
|
|
voce["fit"].append(adatta(ty, yy, m, sigma, cfg, fissi if tipo == "riscaldamento" else None))
|
|
scelto, voce["motivo"], voce["adeguato"] = scegli(voce["fit"], sigma, cfg)
|
|
voce["scelta"] = scelto["modello"] if scelto else None
|
|
return voce
|
|
|
|
|
|
def analizza_domini(t, serie, domini, cfg):
|
|
"""Un risultato per dominio. I domini di raffreddamento si fittano per primi: i loro parametri
|
|
(`riscaldamento_fissa_da_raffreddamento`) vengono fissati nei domini di riscaldamento (stesse costanti fisiche)."""
|
|
scarta = cfg["domini"]["scarta_dopo_salto_n"]
|
|
risultati = [None] * len(domini)
|
|
fissi = {nome: {} for nome in serie} # serie -> parametri del raffreddamento
|
|
for idx in sorted(range(len(domini)), key=lambda i: domini[i]["tipo"] != "raffreddamento"):
|
|
d = domini[idx]
|
|
i0, i1, t0 = d["i0"] + (scarta if d["tipo"] != "riposo" else 0), d["i1"], t[d["i0"]]
|
|
voce = {"k": idx + 1, "tipo": d["tipo"], "t0_s": float(t0), "t1_s": float(t[i1 - 1]),
|
|
"n": int(i1 - d["i0"]), "n_fit": int(i1 - i0), "serie": {}}
|
|
for nome, y in serie.items():
|
|
yy = y[i0:i1]
|
|
vs = voce["serie"][nome] = _serie(t[i0:i1] - t0, yy, sigma_rumore(yy), d["tipo"], fissi[nome], cfg)
|
|
if d["tipo"] == "raffreddamento" and vs["scelta"] and not fissi[nome]:
|
|
par = next(x for x in vs["fit"] if x["modello"] == vs["scelta"])["parametri"]
|
|
fissi[nome] = {k: par[k] for k in cfg["fit"]["riscaldamento_fissa_da_raffreddamento"] if k in par}
|
|
risultati[idx] = voce
|
|
return risultati
|