You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
5.4 KiB
Python

import numpy as np
def calcular_pendiente(df, x_col, y_col):
"""
Calcular pendiente de ajuste lineal
"""
x = df[x_col].values
y = df[y_col].values
m, b = np.polyfit(x, y, 1)
return m, b
def filtrar_por_limites(df, x_col, v_min=None, v_max=None):
out = df.copy()
if v_min is not None:
out = out[out[x_col] >= v_min]
if v_max is not None:
out = out[out[x_col] <= v_max]
return out.reset_index(drop=True)
def calcular_r2(df, x_col, y_col):
"""
Calcular coeficiente de determinación R^2.
"""
x = df[x_col].values
y = df[y_col].values
m, b = np.polyfit(x, y, 1)
y_pred = m * x + b
ss_res = np.sum((y - y_pred) ** 2)
ss_tot = np.sum((y - np.mean(y)) ** 2)
r2 = 1 - (ss_res / ss_tot)
return r2
def derivada_numerica(df, x_col, y_col):
"""
Calcula primera derivada númerica dy/dx
"""
x = np.asanyarray(df[x_col].values, dtype=float)
y = np.asanyarray(df[y_col].values, dtype=float)
idx = np.argsort(x)
x = x[idx]
y = y[idx]
return x, y, np.gradient(y,x)
def segunda_derivada(df, x_col, y_col):
"""
Calcula segunda derivada númerica d^2y/d^2x
"""
x = np.asanyarray(df[x_col].values, dtype=float)
y = np.asanyarray(df[y_col].values, dtype=float)
idx = np.argsort(x)
x = x[idx]
y = x[idx]
dy_dx = np.gradient(y, x)
d2y_dx2 = np.gradient(dy_dx, x)
return x, y, dy_dx, d2y_dx2
def find_local_peaks(signal):
"""
Encuentra indices de maximos locales simples
"""
peaks = []
for i in range(1, len(signal) - 1):
if signal[i] > signal[i - 1] and signal[i] > signal[i + 1]:
peaks.append(i)
return np.array(peaks, dtype=int)
def variación_resistencia(df, col_res="Resistencia"):
"""
Calcula variación relativa de resistencia.
"""
R = df[col_res].values
R0 = R[0]
delta_R = (R - R0) / R0
return delta_R
def comparar_pendientes(data, rondas, experimentos, x_col, y_col):
"""
Compara pendientes entre diferentes experimentos o rondas
"""
resultados = {}
for r in rondas:
for e in experimentos:
df = data[(r, e)]
m, b = calcular_pendiente(df, x_col, y_col)
resultados[(r, e)] = m
return resultados
def zonas_manual(df, x_col, limites):
"""
Divide el dataset en zonas usando límites definidos manualmente
"""
zonas = {}
for i in range(len(limites) - 1):
v_min = limites[i]
v_max = limites[i + 1]
zona_df = df[(df[x_col] >= v_min) & (df[x_col] < v_max)]
zonas[f"Zona_{i+1}"] = zona_df
return zonas
def detectar_zonas_auto(df, x_col, y_col, n_ruido=8):
"""
Detecta automaticamente los limites de 4 zonas:
1) No conduccion
2) Conduccion
3) Generacion H
4) Saturacion
"""
x, y, dy_dx, d2y_d2x = segunda_derivada(df, x_col=x_col, y_col=y_col)
#===============================
#Estimacion automatica del ruido
#===============================
n_ruido = min(n_ruido, len(y))
ruido_base = np.std(y[:n_ruido])
#Umbral fisico basado en ruido experimental
tol_corriente = max(3 * ruido_base, 1e-12)
#=============================
# Fin de no conduccion
#=============================
idx_no_conduccion = None
for i in range(len(y)):
if np.abs(y[i]) > tol_corriente:
idx_no_conduccion = i
break
if idx_no_conduccion is None:
return{
"Zona no Conduccion": None,
"Zona Conduccion": None,
"Generacion": None,
"Saturacion": None,
}
#=====================
# Picos de Curvatura
#=====================
abs_d2 = np.abs(d2y_d2x)
peaks = find_local_peaks(abs_d2)
# Nos quedamos solo con picos posteriores a no conduccion
peaks = peaks[peaks > idx_no_conduccion]
# Si no hay suficientes picos, usamos la estrategia de respaldo
if len(peaks) < 2:
idx_conduccion = idx_no_conduccion
# Generación: punto donde la pendiente alcanza su maximo crecimiento relativo
idx_generacion = np.argmax(np.abs(dy_dx[idx_no_conduccion:])) + idx_no_conduccion
# Saturación: máximo de segunda derivada despues de generacion
idx_saturacion = np.argmax(abs_d2[idx_generacion]) + idx_generacion
else:
# Primer pico importante de curvatura - transicion a conduccion
idx_conduccion = peaks[0]
# Segundo pico importante - inicio de generacion
idx_generacion = peaks[1]
# Saturacion: buscar el mayro pico de curvatura despues de generacion
peaks_after_gen = peaks[peaks > idx_generacion]
if len(peaks_after_gen) > 0:
idx_saturacion = peaks_after_gen[np.argmax(abs_d2[peaks_after_gen])]
else:
idx_saturacion = np.argmax(abs_d2[idx_generacion:]) + idx_generacion
limites = {
"Zona_no_conduccion": float(x[idx_no_conduccion]),
"Zona_conduccion" : float(x[idx_conduccion]),
"Zona_Generacion" : float(x[idx_generacion]),
"Zona_Saturacion" : float(x[idx_saturacion]),
}
return limites