import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial import ConvexHull
import warnings
warnings.filterwarnings('ignore')

# Leer el archivo manualmente
data = []
with open('resultados1.txt', 'r', encoding='utf-8') as f:
    header = f.readline().strip().split('\t')
    for line in f:
        values = line.strip().split()  # Split por espacios/tabulaciones
        if len(values) >= 15:  # Tenemos 15 columnas
            data.append(values)

# Encontrar índices de columnas
tf_idx = 2  # tf es la columna 3
tw_idx = 3  # tw es la columna 4
flecha_idx = 8  # Flecha_Media es la columna 9

# Convertir datos a números
def convert_value(x):
    """Convierte string a float, manejando formato con coma decimal"""
    try:
        return float(x.replace(',', '.').strip())
    except:
        return np.nan

tf_list = []
tw_list = []
flecha_list = []

for row in data:
    tf_val = convert_value(row[tf_idx])
    tw_val = convert_value(row[tw_idx])
    flecha_val = convert_value(row[flecha_idx])
    
    if not (np.isnan(tf_val) or np.isnan(tw_val) or np.isnan(flecha_val)):
        tf_list.append(tf_val)
        tw_list.append(tw_val)
        flecha_list.append(flecha_val)

tf = np.array(tf_list)
tw = np.array(tw_list)
flecha_media = np.array(flecha_list)

# RESTRICCIÓN DE DISEÑO
LIMITE_FLECHA = -25.0  # Valores de flecha por debajo de este límite NO son válidos

# Separar puntos válidos e inválidos
valid_idx = flecha_media >= LIMITE_FLECHA
invalid_idx = flecha_media < LIMITE_FLECHA

tf_valid = tf[valid_idx]
tw_valid = tw[valid_idx]
flecha_valid = flecha_media[valid_idx]

tf_invalid = tf[invalid_idx]
tw_invalid = tw[invalid_idx]
flecha_invalid = flecha_media[invalid_idx]

print("="*70)
print("ANÁLISIS DE FRONTERA DE PARETO - PROBLEMA DE MINIMIZACIÓN 3D")
print("="*70)
print(f"\n✓ Datos cargados exitosamente")
print(f"  Total de puntos: {len(tf)}")
print(f"\n  Rango de tf (espesor ala):       [{tf.min():.6f}, {tf.max():.6f}]")
print(f"  Rango de tw (espesor alma):     [{tw.min():.6f}, {tw.max():.6f}]")
print(f"  Rango de Flecha_Media:          [{flecha_media.min():.2f}, {flecha_media.max():.2f}]")

print(f"\n⚠ RESTRICCIÓN DE DISEÑO: Flecha_Media >= {LIMITE_FLECHA} mm")
print(f"  Puntos VÁLIDOS:                 {len(tf_valid)} ({100*len(tf_valid)/len(tf):.1f}%)")
print(f"  Puntos INVÁLIDOS:               {len(tf_invalid)} ({100*len(tf_invalid)/len(tf):.1f}%)")
print(f"  Rango flecha válida:            [{flecha_valid.min():.2f}, {flecha_valid.max():.2f}]")
print(f"  Rango flecha inválida:          [{flecha_invalid.min():.2f}, {flecha_invalid.max():.2f}]")


# Calcular la frontera de Pareto con algoritmo mejorado (solo para puntos VÁLIDOS)
def compute_pareto_frontier(tf, tw, flecha):
    """
    Calcula los puntos que pertenecen a la frontera de Pareto.
    Objetivo: minimizar tf, tw y |flecha|
    """
    n = len(tf)
    is_pareto = np.ones(n, dtype=bool)
    
    # Usar valor absoluto para la comparación
    flecha_abs = np.abs(flecha)
    
    for i in range(n):
        if not is_pareto[i]:
            continue
        for j in range(n):
            if i != j and is_pareto[j]:
                # El punto j domina al i si: j <= i en todos los criterios
                # y j < i en al menos uno
                dom = (tf[j] <= tf[i]) and (tw[j] <= tw[i]) and (flecha_abs[j] <= flecha_abs[i])
                strict = (tf[j] < tf[i]) or (tw[j] < tw[i]) or (flecha_abs[j] < flecha_abs[i])
                
                if dom and strict:
                    is_pareto[i] = False
                    break
    
    return is_pareto

# Calcular Pareto solo para puntos VÁLIDOS
pareto_mask_valid = compute_pareto_frontier(tf_valid, tw_valid, flecha_valid)
pareto_tf_valid = tf_valid[pareto_mask_valid]
pareto_tw_valid = tw_valid[pareto_mask_valid]
pareto_flecha_valid = flecha_valid[pareto_mask_valid]

print(f"\n✓ Frontera de Pareto calculada (región válida)")
print(f"  Puntos en la frontera: {len(pareto_tf_valid)} ({100*len(pareto_tf_valid)/len(tf_valid):.1f}% de válidos)")


# Crear figura 3D de alta calidad
fig = plt.figure(figsize=(16, 12))
ax = fig.add_subplot(111, projection='3d')

# Graficar el PLANO DE RESTRICCIÓN en z = LIMITE_FLECHA
xx = np.linspace(tf.min(), tf.max(), 20)
yy = np.linspace(tw.min(), tw.max(), 20)
XX, YY = np.meshgrid(xx, yy)
ZZ = np.full_like(XX, LIMITE_FLECHA)  # Plano en z = -25

# Plotear el plano de restricción
plane = ax.plot_surface(XX, YY, ZZ, alpha=0.3, color='orange', label='Plano de restricción')
# Agregar wireframe al plano para hacerlo más visible
ax.plot_wireframe(XX, YY, ZZ, color='darkorange', alpha=0.5, linewidth=0.5)

# Graficar puntos INVÁLIDOS (debajo del plano)
scatter_invalid = ax.scatter(tf_invalid, tw_invalid, flecha_invalid, 
                             c='red', 
                             marker='x', 
                             s=100, 
                             alpha=0.3, 
                             label=f'Puntos inválidos (n={len(tf_invalid)})',
                             linewidth=2)

# Graficar puntos VÁLIDOS (encima del plano)
scatter_valid = ax.scatter(tf_valid, tw_valid, flecha_valid, 
                           c='lightgreen', 
                           marker='o', 
                           s=80, 
                           alpha=0.6, 
                           label=f'Puntos válidos (n={len(tf_valid)})',
                           edgecolors='darkgreen',
                           linewidth=0.5)

# Intentar graficar la frontera de Pareto solo para puntos válidos
if len(tf_valid) >= 4:
    try:
        # Crear matriz de coordenadas para la frontera de Pareto (solo válidos)
        points_pareto = np.column_stack((tf_valid, tw_valid, np.abs(flecha_valid)))
        
        # Calcular la envolvente convexa (convex hull)
        hull = ConvexHull(points_pareto)
        
        # Graficar la superficie
        for simplex in hull.simplices:
            triangle = points_pareto[simplex]
            # Graficar los bordes del triángulo
            for i in range(3):
                p1 = triangle[i]
                p2 = triangle[(i+1)%3]
                ax.plot([p1[0], p2[0]], 
                       [p1[1], p2[1]], 
                       [p1[2], p2[2]], 
                       'b-', alpha=0.2, linewidth=0.5)
        
        print(f"  Envolvente convexa (región válida): {len(hull.simplices)} triángulos")
    except Exception as e:
        print(f"  ⚠ Envolvente convexa: No se pudo calcular ({str(e)[:50]})")

# Etiquetas de ejes
ax.set_xlabel('tf - Espesor del ala (mm)', fontsize=12, fontweight='bold')
ax.set_ylabel('tw - Espesor del alma (mm)', fontsize=12, fontweight='bold')
ax.set_zlabel('Flecha_Media - Deflexión (mm)', fontsize=12, fontweight='bold')
ax.set_title(f'Frontera de Pareto con Restricción: Flecha_Media ≥ {LIMITE_FLECHA} mm\ntf vs tw vs Deflexión Media', 
             fontsize=14, fontweight='bold', pad=20)

# Leyenda mejorada
ax.legend(loc='upper left', fontsize=11, framealpha=0.95)

# Mejorar vista
ax.view_init(elev=25, azim=45)
ax.grid(True, alpha=0.3)

# Agregar línea de referencia en el plano de restricción
ax.text2D(0.02, 0.98, f'Plano de restricción: Flecha = {LIMITE_FLECHA} mm\n(Naranja)', 
          transform=ax.transAxes, fontsize=10, verticalalignment='top',
          bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))

plt.tight_layout()
plt.savefig('grafico_3d_pareto_con_restriccion.png', dpi=300, bbox_inches='tight')
print(f"\n✓ Gráfico guardado: 'grafico_3d_pareto_con_restriccion.png'")

# Mostrar estadísticas de los puntos de Pareto
print("\n" + "="*70)
print("ESTADÍSTICAS DE LA FRONTERA DE PARETO (REGIÓN VÁLIDA)")
print("="*70)
print(f"{'Parámetro':<20} {'Mínimo':<15} {'Máximo':<15} {'Promedio':<15}")
print("-"*70)
print(f"{'tf':<20} {pareto_tf_valid.min():<15.6f} {pareto_tf_valid.max():<15.6f} {pareto_tf_valid.mean():<15.6f}")
print(f"{'tw':<20} {pareto_tw_valid.min():<15.6f} {pareto_tw_valid.max():<15.6f} {pareto_tw_valid.mean():<15.6f}")
print(f"{'Flecha_Media':<20} {pareto_flecha_valid.min():<15.2f} {pareto_flecha_valid.max():<15.2f} {pareto_flecha_valid.mean():<15.2f}")

# Mostrar detalles de los puntos de Pareto VÁLIDOS
print("\n" + "="*70)
print(f"PUNTOS EN LA FRONTERA DE PARETO - REGIÓN VÁLIDA (primeros 30 de {len(pareto_tf_valid)})")
print("="*70)
print(f"{'#':<4} {'tf':<12} {'tw':<12} {'Flecha_Media':<15}")
print("-"*70)
for i in range(min(30, len(pareto_tf_valid))):
    print(f"{i+1:<4} {pareto_tf_valid[i]:<12.6f} {pareto_tw_valid[i]:<12.6f} {pareto_flecha_valid[i]:<15.2f}")

if len(pareto_tf_valid) > 30:
    print(f"\n... y {len(pareto_tf_valid) - 30} puntos más")

print("\n" + "="*70)
plt.show()
