analisis_pareto_3d.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. from mpl_toolkits.mplot3d import Axes3D
  4. from scipy.spatial import ConvexHull
  5. import warnings
  6. warnings.filterwarnings('ignore')
  7. # Leer el archivo manualmente
  8. data = []
  9. with open('resultados1.txt', 'r', encoding='utf-8') as f:
  10. header = f.readline().strip().split('\t')
  11. for line in f:
  12. values = line.strip().split() # Split por espacios/tabulaciones
  13. if len(values) >= 15: # Tenemos 15 columnas
  14. data.append(values)
  15. # Encontrar índices de columnas
  16. tf_idx = 2 # tf es la columna 3
  17. tw_idx = 3 # tw es la columna 4
  18. flecha_idx = 8 # Flecha_Media es la columna 9
  19. # Convertir datos a números
  20. def convert_value(x):
  21. """Convierte string a float, manejando formato con coma decimal"""
  22. try:
  23. return float(x.replace(',', '.').strip())
  24. except:
  25. return np.nan
  26. tf_list = []
  27. tw_list = []
  28. flecha_list = []
  29. for row in data:
  30. tf_val = convert_value(row[tf_idx])
  31. tw_val = convert_value(row[tw_idx])
  32. flecha_val = convert_value(row[flecha_idx])
  33. if not (np.isnan(tf_val) or np.isnan(tw_val) or np.isnan(flecha_val)):
  34. tf_list.append(tf_val)
  35. tw_list.append(tw_val)
  36. flecha_list.append(flecha_val)
  37. tf = np.array(tf_list)
  38. tw = np.array(tw_list)
  39. flecha_media = np.array(flecha_list)
  40. # RESTRICCIÓN DE DISEÑO
  41. LIMITE_FLECHA = -25.0 # Valores de flecha por debajo de este límite NO son válidos
  42. # Separar puntos válidos e inválidos
  43. valid_idx = flecha_media >= LIMITE_FLECHA
  44. invalid_idx = flecha_media < LIMITE_FLECHA
  45. tf_valid = tf[valid_idx]
  46. tw_valid = tw[valid_idx]
  47. flecha_valid = flecha_media[valid_idx]
  48. tf_invalid = tf[invalid_idx]
  49. tw_invalid = tw[invalid_idx]
  50. flecha_invalid = flecha_media[invalid_idx]
  51. print("="*70)
  52. print("ANÁLISIS DE FRONTERA DE PARETO - PROBLEMA DE MINIMIZACIÓN 3D")
  53. print("="*70)
  54. print(f"\n✓ Datos cargados exitosamente")
  55. print(f" Total de puntos: {len(tf)}")
  56. print(f"\n Rango de tf (espesor ala): [{tf.min():.6f}, {tf.max():.6f}]")
  57. print(f" Rango de tw (espesor alma): [{tw.min():.6f}, {tw.max():.6f}]")
  58. print(f" Rango de Flecha_Media: [{flecha_media.min():.2f}, {flecha_media.max():.2f}]")
  59. print(f"\n⚠ RESTRICCIÓN DE DISEÑO: Flecha_Media >= {LIMITE_FLECHA} mm")
  60. print(f" Puntos VÁLIDOS: {len(tf_valid)} ({100*len(tf_valid)/len(tf):.1f}%)")
  61. print(f" Puntos INVÁLIDOS: {len(tf_invalid)} ({100*len(tf_invalid)/len(tf):.1f}%)")
  62. print(f" Rango flecha válida: [{flecha_valid.min():.2f}, {flecha_valid.max():.2f}]")
  63. print(f" Rango flecha inválida: [{flecha_invalid.min():.2f}, {flecha_invalid.max():.2f}]")
  64. # Calcular la frontera de Pareto con algoritmo mejorado (solo para puntos VÁLIDOS)
  65. def compute_pareto_frontier(tf, tw, flecha):
  66. """
  67. Calcula los puntos que pertenecen a la frontera de Pareto.
  68. Objetivo: minimizar tf, tw y |flecha|
  69. """
  70. n = len(tf)
  71. is_pareto = np.ones(n, dtype=bool)
  72. # Usar valor absoluto para la comparación
  73. flecha_abs = np.abs(flecha)
  74. for i in range(n):
  75. if not is_pareto[i]:
  76. continue
  77. for j in range(n):
  78. if i != j and is_pareto[j]:
  79. # El punto j domina al i si: j <= i en todos los criterios
  80. # y j < i en al menos uno
  81. dom = (tf[j] <= tf[i]) and (tw[j] <= tw[i]) and (flecha_abs[j] <= flecha_abs[i])
  82. strict = (tf[j] < tf[i]) or (tw[j] < tw[i]) or (flecha_abs[j] < flecha_abs[i])
  83. if dom and strict:
  84. is_pareto[i] = False
  85. break
  86. return is_pareto
  87. # Calcular Pareto solo para puntos VÁLIDOS
  88. pareto_mask_valid = compute_pareto_frontier(tf_valid, tw_valid, flecha_valid)
  89. pareto_tf_valid = tf_valid[pareto_mask_valid]
  90. pareto_tw_valid = tw_valid[pareto_mask_valid]
  91. pareto_flecha_valid = flecha_valid[pareto_mask_valid]
  92. print(f"\n✓ Frontera de Pareto calculada (región válida)")
  93. print(f" Puntos en la frontera: {len(pareto_tf_valid)} ({100*len(pareto_tf_valid)/len(tf_valid):.1f}% de válidos)")
  94. # Crear figura 3D de alta calidad
  95. fig = plt.figure(figsize=(16, 12))
  96. ax = fig.add_subplot(111, projection='3d')
  97. # Graficar el PLANO DE RESTRICCIÓN en z = LIMITE_FLECHA
  98. xx = np.linspace(tf.min(), tf.max(), 20)
  99. yy = np.linspace(tw.min(), tw.max(), 20)
  100. XX, YY = np.meshgrid(xx, yy)
  101. ZZ = np.full_like(XX, LIMITE_FLECHA) # Plano en z = -25
  102. # Plotear el plano de restricción
  103. plane = ax.plot_surface(XX, YY, ZZ, alpha=0.3, color='orange', label='Plano de restricción')
  104. # Agregar wireframe al plano para hacerlo más visible
  105. ax.plot_wireframe(XX, YY, ZZ, color='darkorange', alpha=0.5, linewidth=0.5)
  106. # Graficar puntos INVÁLIDOS (debajo del plano)
  107. scatter_invalid = ax.scatter(tf_invalid, tw_invalid, flecha_invalid,
  108. c='red',
  109. marker='x',
  110. s=100,
  111. alpha=0.3,
  112. label=f'Puntos inválidos (n={len(tf_invalid)})',
  113. linewidth=2)
  114. # Graficar puntos VÁLIDOS (encima del plano)
  115. scatter_valid = ax.scatter(tf_valid, tw_valid, flecha_valid,
  116. c='lightgreen',
  117. marker='o',
  118. s=80,
  119. alpha=0.6,
  120. label=f'Puntos válidos (n={len(tf_valid)})',
  121. edgecolors='darkgreen',
  122. linewidth=0.5)
  123. # Intentar graficar la frontera de Pareto solo para puntos válidos
  124. if len(tf_valid) >= 4:
  125. try:
  126. # Crear matriz de coordenadas para la frontera de Pareto (solo válidos)
  127. points_pareto = np.column_stack((tf_valid, tw_valid, np.abs(flecha_valid)))
  128. # Calcular la envolvente convexa (convex hull)
  129. hull = ConvexHull(points_pareto)
  130. # Graficar la superficie
  131. for simplex in hull.simplices:
  132. triangle = points_pareto[simplex]
  133. # Graficar los bordes del triángulo
  134. for i in range(3):
  135. p1 = triangle[i]
  136. p2 = triangle[(i+1)%3]
  137. ax.plot([p1[0], p2[0]],
  138. [p1[1], p2[1]],
  139. [p1[2], p2[2]],
  140. 'b-', alpha=0.2, linewidth=0.5)
  141. print(f" Envolvente convexa (región válida): {len(hull.simplices)} triángulos")
  142. except Exception as e:
  143. print(f" ⚠ Envolvente convexa: No se pudo calcular ({str(e)[:50]})")
  144. # Etiquetas de ejes
  145. ax.set_xlabel('tf - Espesor del ala (mm)', fontsize=12, fontweight='bold')
  146. ax.set_ylabel('tw - Espesor del alma (mm)', fontsize=12, fontweight='bold')
  147. ax.set_zlabel('Flecha_Media - Deflexión (mm)', fontsize=12, fontweight='bold')
  148. ax.set_title(f'Frontera de Pareto con Restricción: Flecha_Media ≥ {LIMITE_FLECHA} mm\ntf vs tw vs Deflexión Media',
  149. fontsize=14, fontweight='bold', pad=20)
  150. # Leyenda mejorada
  151. ax.legend(loc='upper left', fontsize=11, framealpha=0.95)
  152. # Mejorar vista
  153. ax.view_init(elev=25, azim=45)
  154. ax.grid(True, alpha=0.3)
  155. # Agregar línea de referencia en el plano de restricción
  156. ax.text2D(0.02, 0.98, f'Plano de restricción: Flecha = {LIMITE_FLECHA} mm\n(Naranja)',
  157. transform=ax.transAxes, fontsize=10, verticalalignment='top',
  158. bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))
  159. plt.tight_layout()
  160. plt.savefig('grafico_3d_pareto_con_restriccion.png', dpi=300, bbox_inches='tight')
  161. print(f"\n✓ Gráfico guardado: 'grafico_3d_pareto_con_restriccion.png'")
  162. # Mostrar estadísticas de los puntos de Pareto
  163. print("\n" + "="*70)
  164. print("ESTADÍSTICAS DE LA FRONTERA DE PARETO (REGIÓN VÁLIDA)")
  165. print("="*70)
  166. print(f"{'Parámetro':<20} {'Mínimo':<15} {'Máximo':<15} {'Promedio':<15}")
  167. print("-"*70)
  168. print(f"{'tf':<20} {pareto_tf_valid.min():<15.6f} {pareto_tf_valid.max():<15.6f} {pareto_tf_valid.mean():<15.6f}")
  169. print(f"{'tw':<20} {pareto_tw_valid.min():<15.6f} {pareto_tw_valid.max():<15.6f} {pareto_tw_valid.mean():<15.6f}")
  170. print(f"{'Flecha_Media':<20} {pareto_flecha_valid.min():<15.2f} {pareto_flecha_valid.max():<15.2f} {pareto_flecha_valid.mean():<15.2f}")
  171. # Mostrar detalles de los puntos de Pareto VÁLIDOS
  172. print("\n" + "="*70)
  173. print(f"PUNTOS EN LA FRONTERA DE PARETO - REGIÓN VÁLIDA (primeros 30 de {len(pareto_tf_valid)})")
  174. print("="*70)
  175. print(f"{'#':<4} {'tf':<12} {'tw':<12} {'Flecha_Media':<15}")
  176. print("-"*70)
  177. for i in range(min(30, len(pareto_tf_valid))):
  178. print(f"{i+1:<4} {pareto_tf_valid[i]:<12.6f} {pareto_tw_valid[i]:<12.6f} {pareto_flecha_valid[i]:<15.2f}")
  179. if len(pareto_tf_valid) > 30:
  180. print(f"\n... y {len(pareto_tf_valid) - 30} puntos más")
  181. print("\n" + "="*70)
  182. plt.show()