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,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()