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,309 @@
|
|
|
1
|
+
"""Population visualization tools."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from morphml.core.search import Population
|
|
6
|
+
from morphml.logging_config import get_logger
|
|
7
|
+
|
|
8
|
+
logger = get_logger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PopulationVisualizer:
|
|
12
|
+
"""
|
|
13
|
+
Visualize population statistics and distributions.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> viz = PopulationVisualizer()
|
|
17
|
+
>>> viz.plot_fitness_distribution(population)
|
|
18
|
+
>>> viz.plot_age_distribution(population)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""Initialize visualizer."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def plot_fitness_distribution(
|
|
26
|
+
self, population: Population, output_path: Optional[str] = None
|
|
27
|
+
) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Plot fitness distribution histogram.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
population: Population to visualize
|
|
33
|
+
output_path: Path to save plot
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
import matplotlib.pyplot as plt
|
|
37
|
+
except ImportError:
|
|
38
|
+
logger.warning("matplotlib not available")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# Get fitness values
|
|
42
|
+
fitnesses = [
|
|
43
|
+
ind.fitness
|
|
44
|
+
for ind in population.individuals
|
|
45
|
+
if ind.is_evaluated() and ind.fitness is not None
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
if not fitnesses:
|
|
49
|
+
logger.warning("No evaluated individuals to plot")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
plt.figure(figsize=(10, 6))
|
|
53
|
+
plt.hist(fitnesses, bins=20, color="skyblue", edgecolor="black", alpha=0.7)
|
|
54
|
+
plt.axvline(
|
|
55
|
+
sum(fitnesses) / len(fitnesses),
|
|
56
|
+
color="red",
|
|
57
|
+
linestyle="--",
|
|
58
|
+
linewidth=2,
|
|
59
|
+
label=f"Mean: {sum(fitnesses)/len(fitnesses):.3f}",
|
|
60
|
+
)
|
|
61
|
+
plt.xlabel("Fitness", fontsize=12)
|
|
62
|
+
plt.ylabel("Count", fontsize=12)
|
|
63
|
+
plt.title(f"Fitness Distribution (N={len(fitnesses)})", fontsize=14, fontweight="bold")
|
|
64
|
+
plt.legend()
|
|
65
|
+
plt.grid(True, alpha=0.3, axis="y")
|
|
66
|
+
plt.tight_layout()
|
|
67
|
+
|
|
68
|
+
if output_path:
|
|
69
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
70
|
+
logger.info(f"Fitness distribution plot saved to {output_path}")
|
|
71
|
+
else:
|
|
72
|
+
plt.show()
|
|
73
|
+
|
|
74
|
+
def plot_age_distribution(
|
|
75
|
+
self, population: Population, output_path: Optional[str] = None
|
|
76
|
+
) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Plot age distribution.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
population: Population to visualize
|
|
82
|
+
output_path: Path to save plot
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
import matplotlib.pyplot as plt
|
|
86
|
+
except ImportError:
|
|
87
|
+
logger.warning("matplotlib not available")
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
ages = [ind.age for ind in population.individuals]
|
|
91
|
+
|
|
92
|
+
if not ages:
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
plt.figure(figsize=(10, 6))
|
|
96
|
+
plt.hist(ages, bins=range(max(ages) + 2), color="lightgreen", edgecolor="black", alpha=0.7)
|
|
97
|
+
plt.xlabel("Age (Generations)", fontsize=12)
|
|
98
|
+
plt.ylabel("Count", fontsize=12)
|
|
99
|
+
plt.title(
|
|
100
|
+
f"Age Distribution (Mean: {sum(ages)/len(ages):.1f})", fontsize=14, fontweight="bold"
|
|
101
|
+
)
|
|
102
|
+
plt.grid(True, alpha=0.3, axis="y")
|
|
103
|
+
plt.tight_layout()
|
|
104
|
+
|
|
105
|
+
if output_path:
|
|
106
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
107
|
+
logger.info(f"Age distribution plot saved to {output_path}")
|
|
108
|
+
else:
|
|
109
|
+
plt.show()
|
|
110
|
+
|
|
111
|
+
def plot_complexity_distribution(
|
|
112
|
+
self, population: Population, output_path: Optional[str] = None
|
|
113
|
+
) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Plot architecture complexity distribution.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
population: Population to visualize
|
|
119
|
+
output_path: Path to save plot
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
import matplotlib.pyplot as plt
|
|
123
|
+
except ImportError:
|
|
124
|
+
logger.warning("matplotlib not available")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
node_counts = [len(ind.graph.nodes) for ind in population.individuals]
|
|
128
|
+
param_counts = [ind.graph.estimate_parameters() for ind in population.individuals]
|
|
129
|
+
|
|
130
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
|
|
131
|
+
|
|
132
|
+
# Node count distribution
|
|
133
|
+
ax1.hist(node_counts, bins=20, color="coral", edgecolor="black", alpha=0.7)
|
|
134
|
+
ax1.set_xlabel("Number of Nodes", fontsize=12)
|
|
135
|
+
ax1.set_ylabel("Count", fontsize=12)
|
|
136
|
+
ax1.set_title("Node Count Distribution", fontsize=12, fontweight="bold")
|
|
137
|
+
ax1.grid(True, alpha=0.3, axis="y")
|
|
138
|
+
|
|
139
|
+
# Parameter count distribution
|
|
140
|
+
ax2.hist(param_counts, bins=20, color="lightblue", edgecolor="black", alpha=0.7)
|
|
141
|
+
ax2.set_xlabel("Parameters", fontsize=12)
|
|
142
|
+
ax2.set_ylabel("Count", fontsize=12)
|
|
143
|
+
ax2.set_title("Parameter Count Distribution", fontsize=12, fontweight="bold")
|
|
144
|
+
ax2.grid(True, alpha=0.3, axis="y")
|
|
145
|
+
|
|
146
|
+
plt.tight_layout()
|
|
147
|
+
|
|
148
|
+
if output_path:
|
|
149
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
150
|
+
logger.info(f"Complexity distribution plot saved to {output_path}")
|
|
151
|
+
else:
|
|
152
|
+
plt.show()
|
|
153
|
+
|
|
154
|
+
def plot_fitness_vs_complexity(
|
|
155
|
+
self, population: Population, output_path: Optional[str] = None
|
|
156
|
+
) -> None:
|
|
157
|
+
"""
|
|
158
|
+
Plot fitness vs complexity scatter.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
population: Population to visualize
|
|
162
|
+
output_path: Path to save plot
|
|
163
|
+
"""
|
|
164
|
+
try:
|
|
165
|
+
import matplotlib.pyplot as plt
|
|
166
|
+
except ImportError:
|
|
167
|
+
logger.warning("matplotlib not available")
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
# Get data
|
|
171
|
+
data = []
|
|
172
|
+
for ind in population.individuals:
|
|
173
|
+
if ind.is_evaluated() and ind.fitness is not None:
|
|
174
|
+
data.append(
|
|
175
|
+
{
|
|
176
|
+
"fitness": ind.fitness,
|
|
177
|
+
"nodes": len(ind.graph.nodes),
|
|
178
|
+
"params": ind.graph.estimate_parameters(),
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if not data:
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
fitnesses = [d["fitness"] for d in data]
|
|
186
|
+
nodes = [d["nodes"] for d in data]
|
|
187
|
+
params = [d["params"] for d in data]
|
|
188
|
+
|
|
189
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
|
|
190
|
+
|
|
191
|
+
# Fitness vs nodes
|
|
192
|
+
ax1.scatter(nodes, fitnesses, c="blue", s=100, alpha=0.6, edgecolors="black")
|
|
193
|
+
ax1.set_xlabel("Number of Nodes", fontsize=12)
|
|
194
|
+
ax1.set_ylabel("Fitness", fontsize=12)
|
|
195
|
+
ax1.set_title("Fitness vs Node Count", fontsize=12, fontweight="bold")
|
|
196
|
+
ax1.grid(True, alpha=0.3)
|
|
197
|
+
|
|
198
|
+
# Fitness vs parameters
|
|
199
|
+
ax2.scatter(params, fitnesses, c="red", s=100, alpha=0.6, edgecolors="black")
|
|
200
|
+
ax2.set_xlabel("Parameters", fontsize=12)
|
|
201
|
+
ax2.set_ylabel("Fitness", fontsize=12)
|
|
202
|
+
ax2.set_title("Fitness vs Parameters", fontsize=12, fontweight="bold")
|
|
203
|
+
ax2.grid(True, alpha=0.3)
|
|
204
|
+
|
|
205
|
+
plt.tight_layout()
|
|
206
|
+
|
|
207
|
+
if output_path:
|
|
208
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
209
|
+
logger.info(f"Fitness vs complexity plot saved to {output_path}")
|
|
210
|
+
else:
|
|
211
|
+
plt.show()
|
|
212
|
+
|
|
213
|
+
def plot_population_summary(
|
|
214
|
+
self, population: Population, output_path: Optional[str] = None
|
|
215
|
+
) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Plot comprehensive population summary.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
population: Population to visualize
|
|
221
|
+
output_path: Path to save plot
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
import matplotlib.pyplot as plt
|
|
225
|
+
except ImportError:
|
|
226
|
+
logger.warning("matplotlib not available")
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
fig = plt.figure(figsize=(16, 10))
|
|
230
|
+
gs = fig.add_gridspec(2, 3, hspace=0.3, wspace=0.3)
|
|
231
|
+
|
|
232
|
+
# Fitness distribution
|
|
233
|
+
ax1 = fig.add_subplot(gs[0, 0])
|
|
234
|
+
fitnesses = [
|
|
235
|
+
ind.fitness for ind in population.individuals if ind.is_evaluated() and ind.fitness
|
|
236
|
+
]
|
|
237
|
+
if fitnesses:
|
|
238
|
+
ax1.hist(fitnesses, bins=20, color="skyblue", edgecolor="black", alpha=0.7)
|
|
239
|
+
ax1.set_xlabel("Fitness")
|
|
240
|
+
ax1.set_ylabel("Count")
|
|
241
|
+
ax1.set_title("Fitness Distribution")
|
|
242
|
+
ax1.grid(True, alpha=0.3, axis="y")
|
|
243
|
+
|
|
244
|
+
# Age distribution
|
|
245
|
+
ax2 = fig.add_subplot(gs[0, 1])
|
|
246
|
+
ages = [ind.age for ind in population.individuals]
|
|
247
|
+
ax2.hist(ages, bins=range(max(ages) + 2), color="lightgreen", edgecolor="black", alpha=0.7)
|
|
248
|
+
ax2.set_xlabel("Age (Generations)")
|
|
249
|
+
ax2.set_ylabel("Count")
|
|
250
|
+
ax2.set_title("Age Distribution")
|
|
251
|
+
ax2.grid(True, alpha=0.3, axis="y")
|
|
252
|
+
|
|
253
|
+
# Node count distribution
|
|
254
|
+
ax3 = fig.add_subplot(gs[0, 2])
|
|
255
|
+
node_counts = [len(ind.graph.nodes) for ind in population.individuals]
|
|
256
|
+
ax3.hist(node_counts, bins=20, color="coral", edgecolor="black", alpha=0.7)
|
|
257
|
+
ax3.set_xlabel("Number of Nodes")
|
|
258
|
+
ax3.set_ylabel("Count")
|
|
259
|
+
ax3.set_title("Complexity Distribution")
|
|
260
|
+
ax3.grid(True, alpha=0.3, axis="y")
|
|
261
|
+
|
|
262
|
+
# Fitness vs nodes scatter
|
|
263
|
+
ax4 = fig.add_subplot(gs[1, :2])
|
|
264
|
+
data = [
|
|
265
|
+
(ind.fitness, len(ind.graph.nodes))
|
|
266
|
+
for ind in population.individuals
|
|
267
|
+
if ind.is_evaluated() and ind.fitness
|
|
268
|
+
]
|
|
269
|
+
if data:
|
|
270
|
+
fit_vals, node_vals = zip(*data)
|
|
271
|
+
ax4.scatter(node_vals, fit_vals, c="purple", s=100, alpha=0.6, edgecolors="black")
|
|
272
|
+
ax4.set_xlabel("Number of Nodes")
|
|
273
|
+
ax4.set_ylabel("Fitness")
|
|
274
|
+
ax4.set_title("Fitness vs Complexity")
|
|
275
|
+
ax4.grid(True, alpha=0.3)
|
|
276
|
+
|
|
277
|
+
# Statistics text
|
|
278
|
+
ax5 = fig.add_subplot(gs[1, 2])
|
|
279
|
+
ax5.axis("off")
|
|
280
|
+
stats = population.get_statistics()
|
|
281
|
+
stats_text = [
|
|
282
|
+
f"Population Size: {stats.get('size', 0)}",
|
|
283
|
+
f"Generation: {stats.get('generation', 0)}",
|
|
284
|
+
f"Evaluated: {stats.get('evaluated', 0)}",
|
|
285
|
+
"",
|
|
286
|
+
f"Best Fitness: {stats.get('best_fitness', 0):.4f}",
|
|
287
|
+
f"Mean Fitness: {stats.get('mean_fitness', 0):.4f}",
|
|
288
|
+
f"Worst Fitness: {stats.get('worst_fitness', 0):.4f}",
|
|
289
|
+
"",
|
|
290
|
+
f"Diversity: {population.get_diversity():.3f}",
|
|
291
|
+
]
|
|
292
|
+
ax5.text(
|
|
293
|
+
0.1,
|
|
294
|
+
0.9,
|
|
295
|
+
"\n".join(stats_text),
|
|
296
|
+
verticalalignment="top",
|
|
297
|
+
fontsize=11,
|
|
298
|
+
family="monospace",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
fig.suptitle(
|
|
302
|
+
f'Population Summary (Gen {stats.get("generation", 0)})', fontsize=16, fontweight="bold"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if output_path:
|
|
306
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
307
|
+
logger.info(f"Population summary saved to {output_path}")
|
|
308
|
+
else:
|
|
309
|
+
plt.show()
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Progress visualization for optimization."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from morphml.logging_config import get_logger
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProgressPlotter:
|
|
11
|
+
"""
|
|
12
|
+
Plot optimization progress.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> plotter = ProgressPlotter()
|
|
16
|
+
>>> plotter.plot_fitness_evolution(history)
|
|
17
|
+
>>> plotter.plot_diversity(history)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialize plotter."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def plot_fitness_evolution(
|
|
25
|
+
self, history: List[Dict], output_path: Optional[str] = None, show_bands: bool = True
|
|
26
|
+
) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Plot fitness evolution over generations.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
history: List of generation statistics
|
|
32
|
+
output_path: Path to save plot
|
|
33
|
+
show_bands: Show std deviation bands
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
import matplotlib.pyplot as plt
|
|
37
|
+
import numpy as np
|
|
38
|
+
except ImportError:
|
|
39
|
+
logger.warning("matplotlib not available")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
if not history:
|
|
43
|
+
logger.warning("No history to plot")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
generations = [h.get("generation", i) for i, h in enumerate(history)]
|
|
47
|
+
best_fitness = [h.get("best_fitness", 0) for h in history]
|
|
48
|
+
mean_fitness = [h.get("mean_fitness", 0) for h in history]
|
|
49
|
+
|
|
50
|
+
plt.figure(figsize=(12, 6))
|
|
51
|
+
|
|
52
|
+
# Plot best and mean
|
|
53
|
+
plt.plot(generations, best_fitness, "b-", linewidth=2, label="Best")
|
|
54
|
+
plt.plot(generations, mean_fitness, "g--", linewidth=2, label="Mean")
|
|
55
|
+
|
|
56
|
+
# Add worst if available
|
|
57
|
+
if "worst_fitness" in history[0]:
|
|
58
|
+
worst_fitness = [h["worst_fitness"] for h in history]
|
|
59
|
+
plt.plot(generations, worst_fitness, "r:", linewidth=1, label="Worst")
|
|
60
|
+
|
|
61
|
+
# Add std deviation bands
|
|
62
|
+
if show_bands and "mean_fitness" in history[0]:
|
|
63
|
+
stds = []
|
|
64
|
+
for h in history:
|
|
65
|
+
# Estimate std from mean and best
|
|
66
|
+
mean = h.get("mean_fitness", 0)
|
|
67
|
+
best = h.get("best_fitness", 0)
|
|
68
|
+
std_estimate = abs(best - mean) / 2
|
|
69
|
+
stds.append(std_estimate)
|
|
70
|
+
|
|
71
|
+
means = np.array(mean_fitness)
|
|
72
|
+
stds = np.array(stds)
|
|
73
|
+
|
|
74
|
+
plt.fill_between(
|
|
75
|
+
generations,
|
|
76
|
+
means - stds,
|
|
77
|
+
means + stds,
|
|
78
|
+
alpha=0.2,
|
|
79
|
+
color="green",
|
|
80
|
+
label="Mean ± Std",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
plt.xlabel("Generation", fontsize=12)
|
|
84
|
+
plt.ylabel("Fitness", fontsize=12)
|
|
85
|
+
plt.title("Fitness Evolution", fontsize=14, fontweight="bold")
|
|
86
|
+
plt.legend()
|
|
87
|
+
plt.grid(True, alpha=0.3)
|
|
88
|
+
plt.tight_layout()
|
|
89
|
+
|
|
90
|
+
if output_path:
|
|
91
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
92
|
+
logger.info(f"Fitness evolution plot saved to {output_path}")
|
|
93
|
+
else:
|
|
94
|
+
plt.show()
|
|
95
|
+
|
|
96
|
+
def plot_diversity(self, history: List[Dict], output_path: Optional[str] = None) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Plot population diversity over time.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
history: List of generation statistics
|
|
102
|
+
output_path: Path to save plot
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
import matplotlib.pyplot as plt
|
|
106
|
+
except ImportError:
|
|
107
|
+
logger.warning("matplotlib not available")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
if not history:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
# Extract diversity if available
|
|
114
|
+
if "diversity" not in history[0]:
|
|
115
|
+
logger.warning("No diversity data in history")
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
generations = [h.get("generation", i) for i, h in enumerate(history)]
|
|
119
|
+
diversity = [h["diversity"] for h in history]
|
|
120
|
+
|
|
121
|
+
plt.figure(figsize=(12, 6))
|
|
122
|
+
plt.plot(generations, diversity, "purple", linewidth=2)
|
|
123
|
+
plt.xlabel("Generation", fontsize=12)
|
|
124
|
+
plt.ylabel("Diversity", fontsize=12)
|
|
125
|
+
plt.title("Population Diversity", fontsize=14, fontweight="bold")
|
|
126
|
+
plt.grid(True, alpha=0.3)
|
|
127
|
+
plt.tight_layout()
|
|
128
|
+
|
|
129
|
+
if output_path:
|
|
130
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
131
|
+
logger.info(f"Diversity plot saved to {output_path}")
|
|
132
|
+
else:
|
|
133
|
+
plt.show()
|
|
134
|
+
|
|
135
|
+
def plot_combined(self, history: List[Dict], output_path: Optional[str] = None) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Plot fitness and diversity in subplots.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
history: List of generation statistics
|
|
141
|
+
output_path: Path to save plot
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
import matplotlib.pyplot as plt
|
|
145
|
+
except ImportError:
|
|
146
|
+
logger.warning("matplotlib not available")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
if not history:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
|
|
153
|
+
|
|
154
|
+
generations = [h.get("generation", i) for i, h in enumerate(history)]
|
|
155
|
+
|
|
156
|
+
# Fitness subplot
|
|
157
|
+
best_fitness = [h.get("best_fitness", 0) for h in history]
|
|
158
|
+
mean_fitness = [h.get("mean_fitness", 0) for h in history]
|
|
159
|
+
|
|
160
|
+
ax1.plot(generations, best_fitness, "b-", linewidth=2, label="Best")
|
|
161
|
+
ax1.plot(generations, mean_fitness, "g--", linewidth=2, label="Mean")
|
|
162
|
+
ax1.set_ylabel("Fitness", fontsize=12)
|
|
163
|
+
ax1.set_title("Fitness Evolution", fontsize=14, fontweight="bold")
|
|
164
|
+
ax1.legend()
|
|
165
|
+
ax1.grid(True, alpha=0.3)
|
|
166
|
+
|
|
167
|
+
# Diversity subplot
|
|
168
|
+
if "diversity" in history[0]:
|
|
169
|
+
diversity = [h["diversity"] for h in history]
|
|
170
|
+
ax2.plot(generations, diversity, "purple", linewidth=2)
|
|
171
|
+
ax2.set_xlabel("Generation", fontsize=12)
|
|
172
|
+
ax2.set_ylabel("Diversity", fontsize=12)
|
|
173
|
+
ax2.set_title("Population Diversity", fontsize=14, fontweight="bold")
|
|
174
|
+
ax2.grid(True, alpha=0.3)
|
|
175
|
+
|
|
176
|
+
plt.tight_layout()
|
|
177
|
+
|
|
178
|
+
if output_path:
|
|
179
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
180
|
+
logger.info(f"Combined plot saved to {output_path}")
|
|
181
|
+
else:
|
|
182
|
+
plt.show()
|
|
183
|
+
|
|
184
|
+
def plot_comparison(
|
|
185
|
+
self, histories: Dict[str, List[Dict]], output_path: Optional[str] = None
|
|
186
|
+
) -> None:
|
|
187
|
+
"""
|
|
188
|
+
Compare multiple optimization runs.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
histories: Dict mapping run names to history lists
|
|
192
|
+
output_path: Path to save plot
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
import matplotlib.pyplot as plt
|
|
196
|
+
except ImportError:
|
|
197
|
+
logger.warning("matplotlib not available")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
plt.figure(figsize=(14, 7))
|
|
201
|
+
|
|
202
|
+
for name, history in histories.items():
|
|
203
|
+
if not history:
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
generations = [h.get("generation", i) for i, h in enumerate(history)]
|
|
207
|
+
best_fitness = [h.get("best_fitness", 0) for h in history]
|
|
208
|
+
|
|
209
|
+
plt.plot(generations, best_fitness, linewidth=2, label=name, marker="o", markersize=3)
|
|
210
|
+
|
|
211
|
+
plt.xlabel("Generation", fontsize=12)
|
|
212
|
+
plt.ylabel("Best Fitness", fontsize=12)
|
|
213
|
+
plt.title("Optimization Comparison", fontsize=14, fontweight="bold")
|
|
214
|
+
plt.legend()
|
|
215
|
+
plt.grid(True, alpha=0.3)
|
|
216
|
+
plt.tight_layout()
|
|
217
|
+
|
|
218
|
+
if output_path:
|
|
219
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
220
|
+
logger.info(f"Comparison plot saved to {output_path}")
|
|
221
|
+
else:
|
|
222
|
+
plt.show()
|
|
223
|
+
|
|
224
|
+
def plot_pareto_front(
|
|
225
|
+
self, individuals: list, objectives: List[str], output_path: Optional[str] = None
|
|
226
|
+
) -> None:
|
|
227
|
+
"""
|
|
228
|
+
Plot Pareto front for multi-objective optimization.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
individuals: List of Individual instances
|
|
232
|
+
objectives: List of objective names (must be exactly 2)
|
|
233
|
+
output_path: Path to save plot
|
|
234
|
+
"""
|
|
235
|
+
try:
|
|
236
|
+
import matplotlib.pyplot as plt
|
|
237
|
+
except ImportError:
|
|
238
|
+
logger.warning("matplotlib not available")
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
if len(objectives) != 2:
|
|
242
|
+
logger.warning("Pareto front plotting requires exactly 2 objectives")
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
obj1_vals = [ind.get_metric(objectives[0], 0) for ind in individuals]
|
|
246
|
+
obj2_vals = [ind.get_metric(objectives[1], 0) for ind in individuals]
|
|
247
|
+
|
|
248
|
+
plt.figure(figsize=(10, 8))
|
|
249
|
+
plt.scatter(obj1_vals, obj2_vals, c="blue", s=100, alpha=0.6, edgecolors="black")
|
|
250
|
+
plt.xlabel(objectives[0], fontsize=12)
|
|
251
|
+
plt.ylabel(objectives[1], fontsize=12)
|
|
252
|
+
plt.title("Pareto Front", fontsize=14, fontweight="bold")
|
|
253
|
+
plt.grid(True, alpha=0.3)
|
|
254
|
+
plt.tight_layout()
|
|
255
|
+
|
|
256
|
+
if output_path:
|
|
257
|
+
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
258
|
+
logger.info(f"Pareto front plot saved to {output_path}")
|
|
259
|
+
else:
|
|
260
|
+
plt.show()
|