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