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.
- morphml/__init__.py +14 -0
- morphml/api/__init__.py +26 -0
- morphml/api/app.py +326 -0
- morphml/api/auth.py +193 -0
- morphml/api/client.py +338 -0
- morphml/api/models.py +132 -0
- morphml/api/rate_limit.py +192 -0
- morphml/benchmarking/__init__.py +36 -0
- morphml/benchmarking/comparison.py +430 -0
- morphml/benchmarks/__init__.py +56 -0
- morphml/benchmarks/comparator.py +409 -0
- morphml/benchmarks/datasets.py +280 -0
- morphml/benchmarks/metrics.py +199 -0
- morphml/benchmarks/openml_suite.py +201 -0
- morphml/benchmarks/problems.py +289 -0
- morphml/benchmarks/suite.py +318 -0
- morphml/cli/__init__.py +5 -0
- morphml/cli/commands/experiment.py +329 -0
- morphml/cli/main.py +457 -0
- morphml/cli/quickstart.py +312 -0
- morphml/config.py +278 -0
- morphml/constraints/__init__.py +19 -0
- morphml/constraints/handler.py +205 -0
- morphml/constraints/predicates.py +285 -0
- morphml/core/__init__.py +3 -0
- morphml/core/crossover.py +449 -0
- morphml/core/dsl/README.md +359 -0
- morphml/core/dsl/__init__.py +72 -0
- morphml/core/dsl/ast_nodes.py +364 -0
- morphml/core/dsl/compiler.py +318 -0
- morphml/core/dsl/layers.py +368 -0
- morphml/core/dsl/lexer.py +336 -0
- morphml/core/dsl/parser.py +455 -0
- morphml/core/dsl/search_space.py +386 -0
- morphml/core/dsl/syntax.py +199 -0
- morphml/core/dsl/type_system.py +361 -0
- morphml/core/dsl/validator.py +386 -0
- morphml/core/graph/__init__.py +40 -0
- morphml/core/graph/edge.py +124 -0
- morphml/core/graph/graph.py +507 -0
- morphml/core/graph/mutations.py +409 -0
- morphml/core/graph/node.py +196 -0
- morphml/core/graph/serialization.py +361 -0
- morphml/core/graph/visualization.py +431 -0
- morphml/core/objectives/__init__.py +20 -0
- morphml/core/search/__init__.py +33 -0
- morphml/core/search/individual.py +252 -0
- morphml/core/search/parameters.py +453 -0
- morphml/core/search/population.py +375 -0
- morphml/core/search/search_engine.py +340 -0
- morphml/distributed/__init__.py +76 -0
- morphml/distributed/fault_tolerance.py +497 -0
- morphml/distributed/health_monitor.py +348 -0
- morphml/distributed/master.py +709 -0
- morphml/distributed/proto/README.md +224 -0
- morphml/distributed/proto/__init__.py +74 -0
- morphml/distributed/proto/worker.proto +170 -0
- morphml/distributed/proto/worker_pb2.py +79 -0
- morphml/distributed/proto/worker_pb2_grpc.py +423 -0
- morphml/distributed/resource_manager.py +416 -0
- morphml/distributed/scheduler.py +567 -0
- morphml/distributed/storage/__init__.py +33 -0
- morphml/distributed/storage/artifacts.py +381 -0
- morphml/distributed/storage/cache.py +366 -0
- morphml/distributed/storage/checkpointing.py +329 -0
- morphml/distributed/storage/database.py +459 -0
- morphml/distributed/worker.py +549 -0
- morphml/evaluation/__init__.py +5 -0
- morphml/evaluation/heuristic.py +237 -0
- morphml/exceptions.py +55 -0
- morphml/execution/__init__.py +5 -0
- morphml/execution/local_executor.py +350 -0
- morphml/integrations/__init__.py +28 -0
- morphml/integrations/jax_adapter.py +206 -0
- morphml/integrations/pytorch_adapter.py +530 -0
- morphml/integrations/sklearn_adapter.py +206 -0
- morphml/integrations/tensorflow_adapter.py +230 -0
- morphml/logging_config.py +93 -0
- morphml/meta_learning/__init__.py +66 -0
- morphml/meta_learning/architecture_similarity.py +277 -0
- morphml/meta_learning/experiment_database.py +240 -0
- morphml/meta_learning/knowledge_base/__init__.py +19 -0
- morphml/meta_learning/knowledge_base/embedder.py +179 -0
- morphml/meta_learning/knowledge_base/knowledge_base.py +313 -0
- morphml/meta_learning/knowledge_base/meta_features.py +265 -0
- morphml/meta_learning/knowledge_base/vector_store.py +271 -0
- morphml/meta_learning/predictors/__init__.py +27 -0
- morphml/meta_learning/predictors/ensemble.py +221 -0
- morphml/meta_learning/predictors/gnn_predictor.py +552 -0
- morphml/meta_learning/predictors/learning_curve.py +231 -0
- morphml/meta_learning/predictors/proxy_metrics.py +261 -0
- morphml/meta_learning/strategy_evolution/__init__.py +27 -0
- morphml/meta_learning/strategy_evolution/adaptive_optimizer.py +226 -0
- morphml/meta_learning/strategy_evolution/bandit.py +276 -0
- morphml/meta_learning/strategy_evolution/portfolio.py +230 -0
- morphml/meta_learning/transfer.py +581 -0
- morphml/meta_learning/warm_start.py +286 -0
- morphml/optimizers/__init__.py +74 -0
- morphml/optimizers/adaptive_operators.py +399 -0
- morphml/optimizers/bayesian/__init__.py +52 -0
- morphml/optimizers/bayesian/acquisition.py +387 -0
- morphml/optimizers/bayesian/base.py +319 -0
- morphml/optimizers/bayesian/gaussian_process.py +635 -0
- morphml/optimizers/bayesian/smac.py +534 -0
- morphml/optimizers/bayesian/tpe.py +411 -0
- morphml/optimizers/differential_evolution.py +220 -0
- morphml/optimizers/evolutionary/__init__.py +61 -0
- morphml/optimizers/evolutionary/cma_es.py +416 -0
- morphml/optimizers/evolutionary/differential_evolution.py +556 -0
- morphml/optimizers/evolutionary/encoding.py +426 -0
- morphml/optimizers/evolutionary/particle_swarm.py +449 -0
- morphml/optimizers/genetic_algorithm.py +486 -0
- morphml/optimizers/gradient_based/__init__.py +22 -0
- morphml/optimizers/gradient_based/darts.py +550 -0
- morphml/optimizers/gradient_based/enas.py +585 -0
- morphml/optimizers/gradient_based/operations.py +474 -0
- morphml/optimizers/gradient_based/utils.py +601 -0
- morphml/optimizers/hill_climbing.py +169 -0
- morphml/optimizers/multi_objective/__init__.py +56 -0
- morphml/optimizers/multi_objective/indicators.py +504 -0
- morphml/optimizers/multi_objective/nsga2.py +647 -0
- morphml/optimizers/multi_objective/visualization.py +427 -0
- morphml/optimizers/nsga2.py +308 -0
- morphml/optimizers/random_search.py +172 -0
- morphml/optimizers/simulated_annealing.py +181 -0
- morphml/plugins/__init__.py +35 -0
- morphml/plugins/custom_evaluator_example.py +81 -0
- morphml/plugins/custom_optimizer_example.py +63 -0
- morphml/plugins/plugin_system.py +454 -0
- morphml/reports/__init__.py +30 -0
- morphml/reports/generator.py +362 -0
- morphml/tracking/__init__.py +7 -0
- morphml/tracking/experiment.py +309 -0
- morphml/tracking/logger.py +301 -0
- morphml/tracking/reporter.py +357 -0
- morphml/utils/__init__.py +6 -0
- morphml/utils/checkpoint.py +189 -0
- morphml/utils/comparison.py +390 -0
- morphml/utils/export.py +407 -0
- morphml/utils/progress.py +392 -0
- morphml/utils/validation.py +392 -0
- morphml/version.py +7 -0
- morphml/visualization/__init__.py +50 -0
- morphml/visualization/analytics.py +423 -0
- morphml/visualization/architecture_diagrams.py +353 -0
- morphml/visualization/architecture_plot.py +223 -0
- morphml/visualization/convergence_plot.py +174 -0
- morphml/visualization/crossover_viz.py +386 -0
- morphml/visualization/graph_viz.py +338 -0
- morphml/visualization/pareto_plot.py +149 -0
- morphml/visualization/plotly_dashboards.py +422 -0
- morphml/visualization/population.py +309 -0
- morphml/visualization/progress.py +260 -0
- morphml-1.0.0.dist-info/METADATA +434 -0
- morphml-1.0.0.dist-info/RECORD +158 -0
- morphml-1.0.0.dist-info/WHEEL +4 -0
- morphml-1.0.0.dist-info/entry_points.txt +3 -0
- 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)
|