"""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