morphml 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of morphml might be problematic. Click here for more details.

Files changed (158) hide show
  1. morphml/__init__.py +14 -0
  2. morphml/api/__init__.py +26 -0
  3. morphml/api/app.py +326 -0
  4. morphml/api/auth.py +193 -0
  5. morphml/api/client.py +338 -0
  6. morphml/api/models.py +132 -0
  7. morphml/api/rate_limit.py +192 -0
  8. morphml/benchmarking/__init__.py +36 -0
  9. morphml/benchmarking/comparison.py +430 -0
  10. morphml/benchmarks/__init__.py +56 -0
  11. morphml/benchmarks/comparator.py +409 -0
  12. morphml/benchmarks/datasets.py +280 -0
  13. morphml/benchmarks/metrics.py +199 -0
  14. morphml/benchmarks/openml_suite.py +201 -0
  15. morphml/benchmarks/problems.py +289 -0
  16. morphml/benchmarks/suite.py +318 -0
  17. morphml/cli/__init__.py +5 -0
  18. morphml/cli/commands/experiment.py +329 -0
  19. morphml/cli/main.py +457 -0
  20. morphml/cli/quickstart.py +312 -0
  21. morphml/config.py +278 -0
  22. morphml/constraints/__init__.py +19 -0
  23. morphml/constraints/handler.py +205 -0
  24. morphml/constraints/predicates.py +285 -0
  25. morphml/core/__init__.py +3 -0
  26. morphml/core/crossover.py +449 -0
  27. morphml/core/dsl/README.md +359 -0
  28. morphml/core/dsl/__init__.py +72 -0
  29. morphml/core/dsl/ast_nodes.py +364 -0
  30. morphml/core/dsl/compiler.py +318 -0
  31. morphml/core/dsl/layers.py +368 -0
  32. morphml/core/dsl/lexer.py +336 -0
  33. morphml/core/dsl/parser.py +455 -0
  34. morphml/core/dsl/search_space.py +386 -0
  35. morphml/core/dsl/syntax.py +199 -0
  36. morphml/core/dsl/type_system.py +361 -0
  37. morphml/core/dsl/validator.py +386 -0
  38. morphml/core/graph/__init__.py +40 -0
  39. morphml/core/graph/edge.py +124 -0
  40. morphml/core/graph/graph.py +507 -0
  41. morphml/core/graph/mutations.py +409 -0
  42. morphml/core/graph/node.py +196 -0
  43. morphml/core/graph/serialization.py +361 -0
  44. morphml/core/graph/visualization.py +431 -0
  45. morphml/core/objectives/__init__.py +20 -0
  46. morphml/core/search/__init__.py +33 -0
  47. morphml/core/search/individual.py +252 -0
  48. morphml/core/search/parameters.py +453 -0
  49. morphml/core/search/population.py +375 -0
  50. morphml/core/search/search_engine.py +340 -0
  51. morphml/distributed/__init__.py +76 -0
  52. morphml/distributed/fault_tolerance.py +497 -0
  53. morphml/distributed/health_monitor.py +348 -0
  54. morphml/distributed/master.py +709 -0
  55. morphml/distributed/proto/README.md +224 -0
  56. morphml/distributed/proto/__init__.py +74 -0
  57. morphml/distributed/proto/worker.proto +170 -0
  58. morphml/distributed/proto/worker_pb2.py +79 -0
  59. morphml/distributed/proto/worker_pb2_grpc.py +423 -0
  60. morphml/distributed/resource_manager.py +416 -0
  61. morphml/distributed/scheduler.py +567 -0
  62. morphml/distributed/storage/__init__.py +33 -0
  63. morphml/distributed/storage/artifacts.py +381 -0
  64. morphml/distributed/storage/cache.py +366 -0
  65. morphml/distributed/storage/checkpointing.py +329 -0
  66. morphml/distributed/storage/database.py +459 -0
  67. morphml/distributed/worker.py +549 -0
  68. morphml/evaluation/__init__.py +5 -0
  69. morphml/evaluation/heuristic.py +237 -0
  70. morphml/exceptions.py +55 -0
  71. morphml/execution/__init__.py +5 -0
  72. morphml/execution/local_executor.py +350 -0
  73. morphml/integrations/__init__.py +28 -0
  74. morphml/integrations/jax_adapter.py +206 -0
  75. morphml/integrations/pytorch_adapter.py +530 -0
  76. morphml/integrations/sklearn_adapter.py +206 -0
  77. morphml/integrations/tensorflow_adapter.py +230 -0
  78. morphml/logging_config.py +93 -0
  79. morphml/meta_learning/__init__.py +66 -0
  80. morphml/meta_learning/architecture_similarity.py +277 -0
  81. morphml/meta_learning/experiment_database.py +240 -0
  82. morphml/meta_learning/knowledge_base/__init__.py +19 -0
  83. morphml/meta_learning/knowledge_base/embedder.py +179 -0
  84. morphml/meta_learning/knowledge_base/knowledge_base.py +313 -0
  85. morphml/meta_learning/knowledge_base/meta_features.py +265 -0
  86. morphml/meta_learning/knowledge_base/vector_store.py +271 -0
  87. morphml/meta_learning/predictors/__init__.py +27 -0
  88. morphml/meta_learning/predictors/ensemble.py +221 -0
  89. morphml/meta_learning/predictors/gnn_predictor.py +552 -0
  90. morphml/meta_learning/predictors/learning_curve.py +231 -0
  91. morphml/meta_learning/predictors/proxy_metrics.py +261 -0
  92. morphml/meta_learning/strategy_evolution/__init__.py +27 -0
  93. morphml/meta_learning/strategy_evolution/adaptive_optimizer.py +226 -0
  94. morphml/meta_learning/strategy_evolution/bandit.py +276 -0
  95. morphml/meta_learning/strategy_evolution/portfolio.py +230 -0
  96. morphml/meta_learning/transfer.py +581 -0
  97. morphml/meta_learning/warm_start.py +286 -0
  98. morphml/optimizers/__init__.py +74 -0
  99. morphml/optimizers/adaptive_operators.py +399 -0
  100. morphml/optimizers/bayesian/__init__.py +52 -0
  101. morphml/optimizers/bayesian/acquisition.py +387 -0
  102. morphml/optimizers/bayesian/base.py +319 -0
  103. morphml/optimizers/bayesian/gaussian_process.py +635 -0
  104. morphml/optimizers/bayesian/smac.py +534 -0
  105. morphml/optimizers/bayesian/tpe.py +411 -0
  106. morphml/optimizers/differential_evolution.py +220 -0
  107. morphml/optimizers/evolutionary/__init__.py +61 -0
  108. morphml/optimizers/evolutionary/cma_es.py +416 -0
  109. morphml/optimizers/evolutionary/differential_evolution.py +556 -0
  110. morphml/optimizers/evolutionary/encoding.py +426 -0
  111. morphml/optimizers/evolutionary/particle_swarm.py +449 -0
  112. morphml/optimizers/genetic_algorithm.py +486 -0
  113. morphml/optimizers/gradient_based/__init__.py +22 -0
  114. morphml/optimizers/gradient_based/darts.py +550 -0
  115. morphml/optimizers/gradient_based/enas.py +585 -0
  116. morphml/optimizers/gradient_based/operations.py +474 -0
  117. morphml/optimizers/gradient_based/utils.py +601 -0
  118. morphml/optimizers/hill_climbing.py +169 -0
  119. morphml/optimizers/multi_objective/__init__.py +56 -0
  120. morphml/optimizers/multi_objective/indicators.py +504 -0
  121. morphml/optimizers/multi_objective/nsga2.py +647 -0
  122. morphml/optimizers/multi_objective/visualization.py +427 -0
  123. morphml/optimizers/nsga2.py +308 -0
  124. morphml/optimizers/random_search.py +172 -0
  125. morphml/optimizers/simulated_annealing.py +181 -0
  126. morphml/plugins/__init__.py +35 -0
  127. morphml/plugins/custom_evaluator_example.py +81 -0
  128. morphml/plugins/custom_optimizer_example.py +63 -0
  129. morphml/plugins/plugin_system.py +454 -0
  130. morphml/reports/__init__.py +30 -0
  131. morphml/reports/generator.py +362 -0
  132. morphml/tracking/__init__.py +7 -0
  133. morphml/tracking/experiment.py +309 -0
  134. morphml/tracking/logger.py +301 -0
  135. morphml/tracking/reporter.py +357 -0
  136. morphml/utils/__init__.py +6 -0
  137. morphml/utils/checkpoint.py +189 -0
  138. morphml/utils/comparison.py +390 -0
  139. morphml/utils/export.py +407 -0
  140. morphml/utils/progress.py +392 -0
  141. morphml/utils/validation.py +392 -0
  142. morphml/version.py +7 -0
  143. morphml/visualization/__init__.py +50 -0
  144. morphml/visualization/analytics.py +423 -0
  145. morphml/visualization/architecture_diagrams.py +353 -0
  146. morphml/visualization/architecture_plot.py +223 -0
  147. morphml/visualization/convergence_plot.py +174 -0
  148. morphml/visualization/crossover_viz.py +386 -0
  149. morphml/visualization/graph_viz.py +338 -0
  150. morphml/visualization/pareto_plot.py +149 -0
  151. morphml/visualization/plotly_dashboards.py +422 -0
  152. morphml/visualization/population.py +309 -0
  153. morphml/visualization/progress.py +260 -0
  154. morphml-1.0.0.dist-info/METADATA +434 -0
  155. morphml-1.0.0.dist-info/RECORD +158 -0
  156. morphml-1.0.0.dist-info/WHEEL +4 -0
  157. morphml-1.0.0.dist-info/entry_points.txt +3 -0
  158. morphml-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,427 @@
1
+ """Visualization tools for multi-objective optimization results.
2
+
3
+ Provides plotting functions for Pareto fronts, objective trade-offs,
4
+ and optimization convergence.
5
+
6
+ Author: Eshan Roy <eshanized@proton.me>
7
+ Organization: TONMOY INFRASTRUCTURE & VISION
8
+ """
9
+
10
+ from typing import List, Optional
11
+
12
+ import numpy as np
13
+
14
+ from morphml.logging_config import get_logger
15
+ from morphml.optimizers.multi_objective.nsga2 import MultiObjectiveIndividual
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class ParetoVisualizer:
21
+ """
22
+ Visualization tools for Pareto fronts.
23
+
24
+ Provides various plotting methods for multi-objective optimization results.
25
+
26
+ Example:
27
+ >>> visualizer = ParetoVisualizer()
28
+ >>> visualizer.plot_2d(pareto_front, 'accuracy', 'latency')
29
+ >>> visualizer.plot_3d(pareto_front, 'accuracy', 'latency', 'params')
30
+ """
31
+
32
+ @staticmethod
33
+ def plot_2d(
34
+ pareto_front: List[MultiObjectiveIndividual],
35
+ obj1_name: str,
36
+ obj2_name: str,
37
+ title: Optional[str] = None,
38
+ save_path: Optional[str] = None,
39
+ show_labels: bool = False,
40
+ ) -> None:
41
+ """
42
+ Plot 2D Pareto front.
43
+
44
+ Args:
45
+ pareto_front: List of Pareto-optimal individuals
46
+ obj1_name: Name of first objective (x-axis)
47
+ obj2_name: Name of second objective (y-axis)
48
+ title: Optional plot title
49
+ save_path: Optional path to save figure
50
+ show_labels: Whether to show point labels
51
+
52
+ Example:
53
+ >>> ParetoVisualizer.plot_2d(
54
+ ... pareto_front,
55
+ ... obj1_name='accuracy',
56
+ ... obj2_name='latency',
57
+ ... save_path='pareto_front.png'
58
+ ... )
59
+ """
60
+ try:
61
+ import matplotlib.pyplot as plt
62
+ except ImportError:
63
+ logger.warning("matplotlib not available, cannot plot")
64
+ return
65
+
66
+ # Extract objective values
67
+ x = [ind.objectives[obj1_name] for ind in pareto_front]
68
+ y = [ind.objectives[obj2_name] for ind in pareto_front]
69
+
70
+ # Create plot
71
+ plt.figure(figsize=(10, 6))
72
+ plt.scatter(x, y, s=100, alpha=0.6, c="blue", edgecolors="black", linewidth=1.5)
73
+
74
+ # Connect points to show Pareto front
75
+ sorted_indices = np.argsort(x)
76
+ x_sorted = [x[i] for i in sorted_indices]
77
+ y_sorted = [y[i] for i in sorted_indices]
78
+ plt.plot(x_sorted, y_sorted, "r--", alpha=0.3, linewidth=1)
79
+
80
+ # Labels
81
+ plt.xlabel(obj1_name.replace("_", " ").title(), fontsize=14, fontweight="bold")
82
+ plt.ylabel(obj2_name.replace("_", " ").title(), fontsize=14, fontweight="bold")
83
+
84
+ if title is None:
85
+ title = f"Pareto Front: {obj1_name} vs {obj2_name}"
86
+ plt.title(title, fontsize=16, fontweight="bold")
87
+
88
+ # Add labels if requested
89
+ if show_labels:
90
+ for i, (xi, yi) in enumerate(zip(x, y)):
91
+ plt.annotate(f"{i}", (xi, yi), fontsize=8, alpha=0.7)
92
+
93
+ plt.grid(True, alpha=0.3, linestyle="--")
94
+ plt.tight_layout()
95
+
96
+ # Save or show
97
+ if save_path:
98
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
99
+ logger.info(f"Saved plot to {save_path}")
100
+ else:
101
+ plt.show()
102
+
103
+ plt.close()
104
+
105
+ @staticmethod
106
+ def plot_3d(
107
+ pareto_front: List[MultiObjectiveIndividual],
108
+ obj1_name: str,
109
+ obj2_name: str,
110
+ obj3_name: str,
111
+ title: Optional[str] = None,
112
+ save_path: Optional[str] = None,
113
+ ) -> None:
114
+ """
115
+ Plot 3D Pareto front.
116
+
117
+ Args:
118
+ pareto_front: List of Pareto-optimal individuals
119
+ obj1_name: Name of first objective (x-axis)
120
+ obj2_name: Name of second objective (y-axis)
121
+ obj3_name: Name of third objective (z-axis)
122
+ title: Optional plot title
123
+ save_path: Optional path to save figure
124
+
125
+ Example:
126
+ >>> ParetoVisualizer.plot_3d(
127
+ ... pareto_front,
128
+ ... 'accuracy', 'latency', 'params'
129
+ ... )
130
+ """
131
+ try:
132
+ import matplotlib.pyplot as plt
133
+ except ImportError:
134
+ logger.warning("matplotlib not available, cannot plot")
135
+ return
136
+
137
+ # Extract objective values
138
+ x = [ind.objectives[obj1_name] for ind in pareto_front]
139
+ y = [ind.objectives[obj2_name] for ind in pareto_front]
140
+ z = [ind.objectives[obj3_name] for ind in pareto_front]
141
+
142
+ # Create 3D plot
143
+ fig = plt.figure(figsize=(12, 9))
144
+ ax = fig.add_subplot(111, projection="3d")
145
+
146
+ # Scatter plot colored by third objective
147
+ scatter = ax.scatter(
148
+ x, y, z, s=100, c=z, cmap="viridis", alpha=0.7, edgecolors="black", linewidth=1
149
+ )
150
+
151
+ # Labels
152
+ ax.set_xlabel(obj1_name.replace("_", " ").title(), fontsize=12, fontweight="bold")
153
+ ax.set_ylabel(obj2_name.replace("_", " ").title(), fontsize=12, fontweight="bold")
154
+ ax.set_zlabel(obj3_name.replace("_", " ").title(), fontsize=12, fontweight="bold")
155
+
156
+ if title is None:
157
+ title = f"3D Pareto Front: {obj1_name}, {obj2_name}, {obj3_name}"
158
+ ax.set_title(title, fontsize=14, fontweight="bold", pad=20)
159
+
160
+ # Colorbar
161
+ cbar = plt.colorbar(scatter, ax=ax, pad=0.1)
162
+ cbar.set_label(obj3_name.replace("_", " ").title(), fontsize=11)
163
+
164
+ # Grid
165
+ ax.grid(True, alpha=0.3)
166
+
167
+ # Save or show
168
+ if save_path:
169
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
170
+ logger.info(f"Saved plot to {save_path}")
171
+ else:
172
+ plt.show()
173
+
174
+ plt.close()
175
+
176
+ @staticmethod
177
+ def plot_parallel_coordinates(
178
+ pareto_front: List[MultiObjectiveIndividual],
179
+ objective_names: Optional[List[str]] = None,
180
+ title: Optional[str] = None,
181
+ save_path: Optional[str] = None,
182
+ ) -> None:
183
+ """
184
+ Parallel coordinates plot for multiple objectives.
185
+
186
+ Each line represents a solution, each axis represents an objective.
187
+ Useful for visualizing high-dimensional Pareto fronts.
188
+
189
+ Args:
190
+ pareto_front: List of Pareto-optimal individuals
191
+ objective_names: List of objectives to plot (None = all)
192
+ title: Optional plot title
193
+ save_path: Optional path to save figure
194
+
195
+ Example:
196
+ >>> ParetoVisualizer.plot_parallel_coordinates(
197
+ ... pareto_front,
198
+ ... objective_names=['accuracy', 'latency', 'params', 'flops']
199
+ ... )
200
+ """
201
+ try:
202
+ import matplotlib.pyplot as plt
203
+ import pandas as pd
204
+ from pandas.plotting import parallel_coordinates
205
+ except ImportError:
206
+ logger.warning("matplotlib/pandas not available, cannot plot")
207
+ return
208
+
209
+ # Extract objective values
210
+ if objective_names is None:
211
+ objective_names = list(pareto_front[0].objectives.keys())
212
+
213
+ data = []
214
+ for i, ind in enumerate(pareto_front):
215
+ row = {name: ind.objectives[name] for name in objective_names}
216
+ row["solution"] = i
217
+ data.append(row)
218
+
219
+ df = pd.DataFrame(data)
220
+
221
+ # Normalize objectives to [0, 1] for better visualization
222
+ for col in objective_names:
223
+ min_val = df[col].min()
224
+ max_val = df[col].max()
225
+ if max_val > min_val:
226
+ df[col] = (df[col] - min_val) / (max_val - min_val)
227
+
228
+ # Create plot
229
+ plt.figure(figsize=(14, 7))
230
+ parallel_coordinates(df, "solution", colormap="viridis", alpha=0.6, linewidth=1.5)
231
+
232
+ # Labels
233
+ if title is None:
234
+ title = "Pareto Front - Parallel Coordinates"
235
+ plt.title(title, fontsize=16, fontweight="bold")
236
+
237
+ plt.ylabel("Normalized Objective Value", fontsize=12)
238
+ plt.legend().remove() # Remove legend (too many solutions)
239
+ plt.grid(True, alpha=0.3, axis="y")
240
+ plt.tight_layout()
241
+
242
+ # Save or show
243
+ if save_path:
244
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
245
+ logger.info(f"Saved plot to {save_path}")
246
+ else:
247
+ plt.show()
248
+
249
+ plt.close()
250
+
251
+ @staticmethod
252
+ def plot_convergence(history: List[dict], save_path: Optional[str] = None) -> None:
253
+ """
254
+ Plot optimization convergence over generations.
255
+
256
+ Shows how Pareto front size and quality evolve.
257
+
258
+ Args:
259
+ history: Optimization history from NSGA2Optimizer
260
+ save_path: Optional path to save figure
261
+
262
+ Example:
263
+ >>> history = optimizer.get_history()
264
+ >>> ParetoVisualizer.plot_convergence(history)
265
+ """
266
+ try:
267
+ import matplotlib.pyplot as plt
268
+ except ImportError:
269
+ logger.warning("matplotlib not available, cannot plot")
270
+ return
271
+
272
+ if not history:
273
+ logger.warning("No history to plot")
274
+ return
275
+
276
+ generations = [h["generation"] for h in history]
277
+ pareto_sizes = [h["pareto_size"] for h in history]
278
+
279
+ plt.figure(figsize=(10, 6))
280
+ plt.plot(generations, pareto_sizes, "b-o", linewidth=2, markersize=6)
281
+
282
+ plt.xlabel("Generation", fontsize=14, fontweight="bold")
283
+ plt.ylabel("Pareto Front Size", fontsize=14, fontweight="bold")
284
+ plt.title("NSGA-II Convergence", fontsize=16, fontweight="bold")
285
+ plt.grid(True, alpha=0.3)
286
+ plt.tight_layout()
287
+
288
+ if save_path:
289
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
290
+ logger.info(f"Saved convergence plot to {save_path}")
291
+ else:
292
+ plt.show()
293
+
294
+ plt.close()
295
+
296
+ @staticmethod
297
+ def plot_objective_distribution(
298
+ pareto_front: List[MultiObjectiveIndividual],
299
+ objective_name: str,
300
+ title: Optional[str] = None,
301
+ save_path: Optional[str] = None,
302
+ ) -> None:
303
+ """
304
+ Plot distribution of a single objective across Pareto front.
305
+
306
+ Args:
307
+ pareto_front: List of Pareto-optimal individuals
308
+ objective_name: Objective to visualize
309
+ title: Optional plot title
310
+ save_path: Optional path to save figure
311
+ """
312
+ try:
313
+ import matplotlib.pyplot as plt
314
+ except ImportError:
315
+ logger.warning("matplotlib not available, cannot plot")
316
+ return
317
+
318
+ values = [ind.objectives[objective_name] for ind in pareto_front]
319
+
320
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
321
+
322
+ # Histogram
323
+ ax1.hist(values, bins=20, alpha=0.7, color="blue", edgecolor="black")
324
+ ax1.set_xlabel(objective_name.replace("_", " ").title(), fontsize=12)
325
+ ax1.set_ylabel("Count", fontsize=12)
326
+ ax1.set_title("Distribution", fontsize=14, fontweight="bold")
327
+ ax1.grid(True, alpha=0.3, axis="y")
328
+
329
+ # Box plot
330
+ ax2.boxplot(
331
+ values, vert=True, patch_artist=True, boxprops={"facecolor": "lightblue", "alpha": 0.7}
332
+ )
333
+ ax2.set_ylabel(objective_name.replace("_", " ").title(), fontsize=12)
334
+ ax2.set_title("Box Plot", fontsize=14, fontweight="bold")
335
+ ax2.grid(True, alpha=0.3, axis="y")
336
+
337
+ if title:
338
+ fig.suptitle(title, fontsize=16, fontweight="bold")
339
+
340
+ plt.tight_layout()
341
+
342
+ if save_path:
343
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
344
+ logger.info(f"Saved distribution plot to {save_path}")
345
+ else:
346
+ plt.show()
347
+
348
+ plt.close()
349
+
350
+ @staticmethod
351
+ def plot_tradeoff_matrix(
352
+ pareto_front: List[MultiObjectiveIndividual],
353
+ objective_names: Optional[List[str]] = None,
354
+ save_path: Optional[str] = None,
355
+ ) -> None:
356
+ """
357
+ Plot pairwise trade-off matrix for all objectives.
358
+
359
+ Creates a grid of scatter plots showing relationships between
360
+ all pairs of objectives.
361
+
362
+ Args:
363
+ pareto_front: List of Pareto-optimal individuals
364
+ objective_names: List of objectives (None = all)
365
+ save_path: Optional path to save figure
366
+
367
+ Example:
368
+ >>> ParetoVisualizer.plot_tradeoff_matrix(pareto_front)
369
+ """
370
+ try:
371
+ import matplotlib.pyplot as plt
372
+ except ImportError:
373
+ logger.warning("matplotlib not available, cannot plot")
374
+ return
375
+
376
+ if objective_names is None:
377
+ objective_names = list(pareto_front[0].objectives.keys())
378
+
379
+ n = len(objective_names)
380
+ fig, axes = plt.subplots(n, n, figsize=(4 * n, 4 * n))
381
+
382
+ for i, obj1 in enumerate(objective_names):
383
+ for j, obj2 in enumerate(objective_names):
384
+ ax = axes[i, j] if n > 1 else axes
385
+
386
+ if i == j:
387
+ # Diagonal: histogram
388
+ values = [ind.objectives[obj1] for ind in pareto_front]
389
+ ax.hist(values, bins=15, alpha=0.7, color="blue", edgecolor="black")
390
+ ax.set_ylabel("Count", fontsize=10)
391
+ else:
392
+ # Off-diagonal: scatter plot
393
+ x = [ind.objectives[obj2] for ind in pareto_front]
394
+ y = [ind.objectives[obj1] for ind in pareto_front]
395
+ ax.scatter(x, y, s=50, alpha=0.6, c="blue", edgecolors="black")
396
+
397
+ # Labels
398
+ if i == n - 1:
399
+ ax.set_xlabel(obj2.replace("_", " ").title(), fontsize=10)
400
+ if j == 0:
401
+ ax.set_ylabel(obj1.replace("_", " ").title(), fontsize=10)
402
+
403
+ ax.grid(True, alpha=0.3)
404
+
405
+ plt.suptitle("Objective Trade-off Matrix", fontsize=18, fontweight="bold")
406
+ plt.tight_layout()
407
+
408
+ if save_path:
409
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
410
+ logger.info(f"Saved trade-off matrix to {save_path}")
411
+ else:
412
+ plt.show()
413
+
414
+ plt.close()
415
+
416
+
417
+ # Convenience functions
418
+ def quick_visualize_2d(pareto_front: List[MultiObjectiveIndividual], obj1: str, obj2: str) -> None:
419
+ """Quick 2D visualization."""
420
+ ParetoVisualizer.plot_2d(pareto_front, obj1, obj2)
421
+
422
+
423
+ def quick_visualize_3d(
424
+ pareto_front: List[MultiObjectiveIndividual], obj1: str, obj2: str, obj3: str
425
+ ) -> None:
426
+ """Quick 3D visualization."""
427
+ ParetoVisualizer.plot_3d(pareto_front, obj1, obj2, obj3)