- 1 + 2N domini: riposo, poi (riscaldamento, raffreddamento) per ogni gradino - riscaldamento non fittato: descritto come salto (delta, da -> a, durata) - raffreddamenti intermedi fissano c e tau a quelli del raffreddamento finale - un colore per ogni raffreddamento nei grafici, zoom sui gradini - sigma None (non NaN) nei domini non fittati: JSON valido Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
89 lines
3.7 KiB
Python
89 lines
3.7 KiB
Python
"""Riconoscimento dei domini: riposo, riscaldamento (salita rapida), raffreddamento (decadimento) — nessun LLM."""
|
||
|
||
import numpy as np
|
||
|
||
|
||
def sigma_robusto(d):
|
||
"""σ robusto di un campione di differenze (MAD)."""
|
||
return 1.4826 * np.median(np.abs(d - np.median(d)))
|
||
|
||
|
||
def sigma_rumore(y):
|
||
"""σ del rumore bianco dalla derivata seconda discreta: 1.4826·MAD(d²y)/√6."""
|
||
if len(y) < 4:
|
||
return float("nan")
|
||
return sigma_robusto(np.diff(y, 2)) / np.sqrt(6)
|
||
|
||
|
||
def trova_gradini(y, cfg):
|
||
"""(i0, i1) dei campioni di salita di ogni gradino di riscaldamento (i1 escluso): salto positivo > soglia,
|
||
esteso ai campioni adiacenti ancora in salita (salto > soglia_bordo_sigma · σ dei salti)."""
|
||
c = cfg["domini"]
|
||
d = np.diff(y)
|
||
forti = np.flatnonzero(d > c["soglia_gradino_c"])
|
||
if len(forti) == 0:
|
||
return []
|
||
soglia_bordo = c["soglia_bordo_sigma"] * sigma_robusto(d)
|
||
gruppi, corrente = [], [forti[0]]
|
||
for i in forti[1:]:
|
||
if i - corrente[-1] < c["distanza_min_salti_campioni"]:
|
||
corrente.append(i)
|
||
else:
|
||
gruppi.append(corrente)
|
||
corrente = [i]
|
||
gruppi.append(corrente)
|
||
gradini = []
|
||
for g in gruppi:
|
||
inizio, fine = int(g[0]), int(g[-1])
|
||
while inizio > 0 and d[inizio - 1] > soglia_bordo:
|
||
inizio -= 1
|
||
while fine + 1 < len(d) and d[fine + 1] > soglia_bordo:
|
||
fine += 1
|
||
gradini.append((inizio + 1, min(fine + 2, len(y))))
|
||
return gradini
|
||
|
||
|
||
def proponi(t, serie, cfg):
|
||
"""Lista di domini {tipo, i0, i1} (i1 escluso): riposo, poi per ogni gradino un `riscaldamento` (la salita rapida,
|
||
pochi campioni) seguito da un `raffreddamento` (il decadimento fino al gradino successivo o alla fine)."""
|
||
y = serie[cfg["domini"]["serie_riferimento"]]
|
||
domini, prec = [], 0
|
||
for g0, g1 in trova_gradini(y, cfg):
|
||
if g0 > prec:
|
||
domini.append({"tipo": "riposo" if not domini else "raffreddamento", "i0": prec, "i1": g0})
|
||
domini.append({"tipo": "riscaldamento", "i0": g0, "i1": g1})
|
||
prec = g1
|
||
if prec < len(y):
|
||
domini.append({"tipo": "riposo" if not domini else "raffreddamento", "i0": prec, "i1": len(y)})
|
||
return domini
|
||
|
||
|
||
def scrivi_toml(percorso, domini, t):
|
||
"""domini.toml: modificabile a mano (tipo, t0_s, t1_s); gli indici si ricalcolano dal tempo."""
|
||
righe = ["# domini.toml — proposta automatica, modificabile: t0_s incluso, t1_s escluso (ultimo dominio: incluso)\n"]
|
||
for d in domini:
|
||
t1 = t[d["i1"]] if d["i1"] < len(t) else t[-1]
|
||
righe.append(f'[[dominio]]\ntipo = "{d["tipo"]}" # riposo | riscaldamento | raffreddamento\n'
|
||
f't0_s = {t[d["i0"]]:.4f}\nt1_s = {t1:.4f}\n')
|
||
percorso.write_text("\n".join(righe), encoding="utf-8")
|
||
|
||
|
||
def leggi_toml(percorso, t):
|
||
import tomllib
|
||
with open(percorso, "rb") as f:
|
||
voci = tomllib.load(f).get("dominio", [])
|
||
if not voci:
|
||
raise ValueError("domini.toml senza [[dominio]]")
|
||
domini = []
|
||
for k, v in enumerate(voci):
|
||
if v["tipo"] not in ("riposo", "riscaldamento", "raffreddamento"):
|
||
raise ValueError(f"dominio {k + 1}: tipo '{v['tipo']}' non valido")
|
||
i0 = int(np.searchsorted(t, v["t0_s"] - 1e-6))
|
||
i1 = len(t) if k == len(voci) - 1 else int(np.searchsorted(t, v["t1_s"] - 1e-6))
|
||
if i1 <= i0:
|
||
raise ValueError(f"dominio {k + 1}: intervallo vuoto o in ordine errato")
|
||
domini.append({"tipo": v["tipo"], "i0": i0, "i1": i1})
|
||
if any(domini[k]["i1"] != domini[k + 1]["i0"] for k in range(len(domini) - 1)):
|
||
raise ValueError("i domini devono essere contigui (t1_s di uno = t0_s del successivo)")
|
||
return domini
|