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,416 @@
1
+ """CMA-ES (Covariance Matrix Adaptation Evolution Strategy) for NAS.
2
+
3
+ CMA-ES is a state-of-the-art evolutionary strategy that adapts the full covariance
4
+ matrix of the search distribution. It's particularly effective for ill-conditioned
5
+ and non-separable problems.
6
+
7
+ Key Features:
8
+ - Adaptive covariance matrix (learns problem structure)
9
+ - Self-adaptive step-size control
10
+ - Invariant to rotations and scalings
11
+ - No gradient information needed
12
+ - Proven convergence properties
13
+
14
+ Reference:
15
+ Hansen, N., and Ostermeier, A. "Completely Derandomized Self-Adaptation
16
+ in Evolution Strategies." Evolutionary Computation, 2001.
17
+
18
+ Author: Eshan Roy <eshanized@proton.me>
19
+ Organization: TONMOY INFRASTRUCTURE & VISION
20
+ """
21
+
22
+ from typing import Any, Callable, Dict, List, Optional
23
+
24
+ import numpy as np
25
+
26
+ from morphml.core.dsl import SearchSpace
27
+ from morphml.core.search import Individual
28
+ from morphml.logging_config import get_logger
29
+ from morphml.optimizers.evolutionary.encoding import ArchitectureEncoder
30
+
31
+ logger = get_logger(__name__)
32
+
33
+
34
+ class CMAES:
35
+ """
36
+ CMA-ES (Covariance Matrix Adaptation Evolution Strategy).
37
+
38
+ CMA-ES maintains and adapts a multivariate normal distribution
39
+ N(m, σ² C) where:
40
+ - m: Mean vector (center of search)
41
+ - σ: Step-size (scale of search)
42
+ - C: Covariance matrix (shape of search distribution)
43
+
44
+ The algorithm:
45
+ 1. Samples offspring from N(m, σ² C)
46
+ 2. Evaluates and ranks offspring
47
+ 3. Updates m using weighted recombination of best offspring
48
+ 4. Updates evolution paths (momentum-like)
49
+ 5. Adapts C based on successful steps
50
+ 6. Adapts σ to maintain proper step-size
51
+
52
+ Key Components:
53
+ - **Weighted Recombination:** Best offspring weighted by log-rank
54
+ - **Cumulative Step-size Adaptation:** Adapts σ based on path length
55
+ - **Rank-μ Update:** Updates C using μ best offspring
56
+ - **Evolution Paths:** Track successful mutation directions
57
+
58
+ Configuration:
59
+ population_size: Offspring per generation (default: 4+⌊3*ln(n)⌋)
60
+ sigma: Initial step-size (default: 0.3)
61
+ max_generations: Maximum generations (default: 100)
62
+
63
+ Example:
64
+ >>> from morphml.optimizers.evolutionary import CMAES
65
+ >>> optimizer = CMAES(
66
+ ... search_space=space,
67
+ ... config={'sigma': 0.3, 'population_size': 20}
68
+ ... )
69
+ >>> best = optimizer.optimize(evaluator)
70
+ """
71
+
72
+ def __init__(self, search_space: SearchSpace, config: Optional[Dict[str, Any]] = None):
73
+ """
74
+ Initialize CMA-ES optimizer.
75
+
76
+ Args:
77
+ search_space: SearchSpace for architecture sampling
78
+ config: Configuration dictionary
79
+ """
80
+ self.search_space = search_space
81
+ self.config = config or {}
82
+
83
+ # Architecture encoding
84
+ self.max_nodes = self.config.get("max_nodes", 20)
85
+ self.encoder = ArchitectureEncoder(search_space, self.max_nodes)
86
+ self.dim = self.encoder.get_dimension()
87
+
88
+ # Population parameters
89
+ self.lambda_ = self.config.get("population_size", 4 + int(3 * np.log(self.dim)))
90
+ self.mu = self.lambda_ // 2 # Number of parents
91
+
92
+ # Recombination weights (log-rank weighted)
93
+ self.weights = np.log(self.mu + 0.5) - np.log(np.arange(1, self.mu + 1))
94
+ self.weights /= self.weights.sum()
95
+ self.mu_eff = 1.0 / (self.weights**2).sum() # Variance effective selection mass
96
+
97
+ # Step-size control parameters
98
+ self.sigma = self.config.get("sigma", 0.3)
99
+ self.cs = (self.mu_eff + 2) / (self.dim + self.mu_eff + 5)
100
+ self.damps = 1 + 2 * max(0, np.sqrt((self.mu_eff - 1) / (self.dim + 1)) - 1) + self.cs
101
+
102
+ # Covariance matrix adaptation parameters
103
+ self.cc = (4 + self.mu_eff / self.dim) / (self.dim + 4 + 2 * self.mu_eff / self.dim)
104
+ self.c1 = 2 / ((self.dim + 1.3) ** 2 + self.mu_eff) # Rank-one update
105
+ self.cmu = min(
106
+ 1 - self.c1,
107
+ 2 * (self.mu_eff - 2 + 1 / self.mu_eff) / ((self.dim + 2) ** 2 + self.mu_eff),
108
+ ) # Rank-μ update
109
+
110
+ # Dynamic strategy parameters
111
+ self.chiN = np.sqrt(self.dim) * (1 - 1 / (4 * self.dim) + 1 / (21 * self.dim**2))
112
+
113
+ # Initialize distribution
114
+ self.mean = np.random.rand(self.dim) # Start at random point
115
+ self.C = np.eye(self.dim) # Covariance matrix (initially identity)
116
+ self.pc = np.zeros(self.dim) # Evolution path for C
117
+ self.ps = np.zeros(self.dim) # Evolution path for sigma
118
+
119
+ # Eigendecomposition (for efficient sampling)
120
+ self.B = np.eye(self.dim) # Eigenvectors
121
+ self.D = np.ones(self.dim) # Eigenvalues
122
+ self.BD = self.B * self.D # B * D for efficient sampling
123
+
124
+ self.eigeneval_count = 0
125
+ self.eigeneval_interval = int(1 / (self.c1 + self.cmu) / self.dim / 10)
126
+
127
+ # Optimization state
128
+ self.max_generations = self.config.get("max_generations", 100)
129
+ self.generation = 0
130
+
131
+ self.best_individual: Optional[Individual] = None
132
+ self.best_fitness: float = -np.inf
133
+ self.best_vector: Optional[np.ndarray] = None
134
+
135
+ # History
136
+ self.history: List[Dict[str, Any]] = []
137
+
138
+ logger.info(
139
+ f"Initialized CMA-ES: dim={self.dim}, lambda={self.lambda_}, "
140
+ f"mu={self.mu}, sigma={self.sigma:.3f}"
141
+ )
142
+
143
+ def sample_offspring(self) -> np.ndarray:
144
+ """
145
+ Sample offspring from N(m, σ² C).
146
+
147
+ Uses eigendecomposition for efficient sampling:
148
+ x = m + σ * B * D * z where z ~ N(0, I)
149
+
150
+ Returns:
151
+ Offspring vector
152
+ """
153
+ z = np.random.randn(self.dim)
154
+ y = self.BD @ z # B * D * z
155
+ x = self.mean + self.sigma * y
156
+
157
+ # Clamp to bounds [0, 1]
158
+ x = np.clip(x, 0.0, 1.0)
159
+
160
+ return x
161
+
162
+ def update_distribution(self, offspring_vectors: List[np.ndarray]) -> None:
163
+ """
164
+ Update mean, evolution paths, covariance matrix, and step-size.
165
+
166
+ Args:
167
+ offspring_vectors: Sorted offspring vectors (best first)
168
+ """
169
+ # Store old mean
170
+ old_mean = self.mean.copy()
171
+
172
+ # Recombination: weighted average of best μ offspring
173
+ self.mean = sum(w * x for w, x in zip(self.weights, offspring_vectors[: self.mu]))
174
+
175
+ # Cumulation: update evolution paths
176
+ mean_shift = (self.mean - old_mean) / self.sigma
177
+
178
+ # C^(-1/2) * mean_shift for ps
179
+ self.ps = (1 - self.cs) * self.ps + np.sqrt(self.cs * (2 - self.cs) * self.mu_eff) * (
180
+ self.B @ (mean_shift / self.D)
181
+ )
182
+
183
+ # Cumulation for pc (covariance path)
184
+ hsig = np.linalg.norm(self.ps) / np.sqrt(
185
+ 1 - (1 - self.cs) ** (2 * self.generation + 2)
186
+ ) < self.chiN * (1.4 + 2 / (self.dim + 1))
187
+
188
+ self.pc = (1 - self.cc) * self.pc + hsig * np.sqrt(
189
+ self.cc * (2 - self.cc) * self.mu_eff
190
+ ) * mean_shift
191
+
192
+ # Adapt covariance matrix
193
+ # Rank-one update
194
+ rank_one = np.outer(self.pc, self.pc)
195
+
196
+ # Rank-μ update
197
+ rank_mu = sum(
198
+ w * np.outer((x - old_mean) / self.sigma, (x - old_mean) / self.sigma)
199
+ for w, x in zip(self.weights, offspring_vectors[: self.mu])
200
+ )
201
+
202
+ self.C = (1 - self.c1 - self.cmu) * self.C + self.c1 * rank_one + self.cmu * rank_mu
203
+
204
+ # Adapt step-size using cumulative step-size adaptation (CSA)
205
+ self.sigma *= np.exp((self.cs / self.damps) * (np.linalg.norm(self.ps) / self.chiN - 1))
206
+
207
+ # Update eigendecomposition
208
+ if self.generation - self.eigeneval_count > self.eigeneval_interval:
209
+ self.eigeneval_count = self.generation
210
+
211
+ # Enforce symmetry
212
+ self.C = np.triu(self.C) + np.triu(self.C, 1).T
213
+
214
+ # Eigendecomposition
215
+ self.D, self.B = np.linalg.eigh(self.C)
216
+ self.D = np.sqrt(np.maximum(self.D, 0)) # Ensure positive
217
+ self.BD = self.B * self.D
218
+
219
+ def optimize(self, evaluator: Callable) -> Individual:
220
+ """
221
+ Run CMA-ES optimization.
222
+
223
+ Args:
224
+ evaluator: Function that evaluates ModelGraph -> fitness
225
+
226
+ Returns:
227
+ Best Individual found
228
+
229
+ Example:
230
+ >>> def my_evaluator(graph):
231
+ ... return train_and_evaluate(graph)
232
+ >>> best = optimizer.optimize(my_evaluator)
233
+ >>> print(f"Best fitness: {best.fitness:.4f}")
234
+ """
235
+ logger.info(f"Starting CMA-ES optimization for {self.max_generations} generations")
236
+
237
+ # Main CMA-ES loop
238
+ for generation in range(self.max_generations):
239
+ self.generation = generation + 1
240
+
241
+ # Sample offspring
242
+ offspring_vectors = [self.sample_offspring() for _ in range(self.lambda_)]
243
+
244
+ # Evaluate offspring
245
+ offspring_fitnesses = []
246
+ offspring_individuals = []
247
+
248
+ for vector in offspring_vectors:
249
+ graph = self.encoder.decode(vector)
250
+ fitness = evaluator(graph)
251
+
252
+ offspring_fitnesses.append(fitness)
253
+
254
+ individual = Individual(graph)
255
+ individual.fitness = fitness
256
+ offspring_individuals.append(individual)
257
+
258
+ # Update global best
259
+ if fitness > self.best_fitness:
260
+ self.best_fitness = fitness
261
+ self.best_vector = vector.copy()
262
+ self.best_individual = individual
263
+
264
+ # Sort by fitness (descending)
265
+ sorted_indices = np.argsort(offspring_fitnesses)[::-1]
266
+ sorted_vectors = [offspring_vectors[i] for i in sorted_indices]
267
+ [offspring_fitnesses[i] for i in sorted_indices]
268
+
269
+ # Update distribution
270
+ self.update_distribution(sorted_vectors)
271
+
272
+ # Record history
273
+ avg_fitness = np.mean(offspring_fitnesses)
274
+
275
+ self.history.append(
276
+ {
277
+ "generation": generation,
278
+ "best_fitness": self.best_fitness,
279
+ "avg_fitness": avg_fitness,
280
+ "sigma": self.sigma,
281
+ "condition_number": np.max(self.D) / np.min(self.D)
282
+ if np.min(self.D) > 0
283
+ else np.inf,
284
+ }
285
+ )
286
+
287
+ # Logging
288
+ if generation % 10 == 0 or generation == self.max_generations - 1:
289
+ logger.info(
290
+ f"Generation {generation}/{self.max_generations}: "
291
+ f"best={self.best_fitness:.4f}, "
292
+ f"avg={avg_fitness:.4f}, "
293
+ f"sigma={self.sigma:.4f}"
294
+ )
295
+
296
+ logger.info(f"CMA-ES complete. Best fitness: {self.best_fitness:.4f}")
297
+
298
+ return self.best_individual
299
+
300
+ def get_history(self) -> List[Dict[str, Any]]:
301
+ """Get optimization history."""
302
+ return self.history
303
+
304
+ def plot_convergence(self, save_path: Optional[str] = None) -> None:
305
+ """
306
+ Plot CMA-ES convergence.
307
+
308
+ Args:
309
+ save_path: Optional path to save plot
310
+ """
311
+ try:
312
+ import matplotlib.pyplot as plt
313
+ except ImportError:
314
+ logger.warning("matplotlib not available, cannot plot")
315
+ return
316
+
317
+ if not self.history:
318
+ logger.warning("No history to plot")
319
+ return
320
+
321
+ generations = [h["generation"] for h in self.history]
322
+ best_fitness = [h["best_fitness"] for h in self.history]
323
+ avg_fitness = [h["avg_fitness"] for h in self.history]
324
+ sigma = [h["sigma"] for h in self.history]
325
+
326
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10))
327
+
328
+ # Fitness plot
329
+ ax1.plot(generations, best_fitness, "b-", linewidth=2, label="Best Fitness")
330
+ ax1.plot(generations, avg_fitness, "r--", linewidth=2, label="Average Fitness")
331
+ ax1.set_xlabel("Generation", fontsize=12)
332
+ ax1.set_ylabel("Fitness", fontsize=12)
333
+ ax1.set_title("CMA-ES Fitness Convergence", fontsize=14, fontweight="bold")
334
+ ax1.legend()
335
+ ax1.grid(True, alpha=0.3)
336
+
337
+ # Sigma plot
338
+ ax2.plot(generations, sigma, "g-", linewidth=2)
339
+ ax2.set_xlabel("Generation", fontsize=12)
340
+ ax2.set_ylabel("Step-size (σ)", fontsize=12)
341
+ ax2.set_title("Step-size Adaptation", fontsize=14, fontweight="bold")
342
+ ax2.grid(True, alpha=0.3)
343
+
344
+ plt.tight_layout()
345
+
346
+ if save_path:
347
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
348
+ logger.info(f"Convergence plot saved to {save_path}")
349
+ else:
350
+ plt.show()
351
+
352
+ plt.close()
353
+
354
+ def __repr__(self) -> str:
355
+ """String representation."""
356
+ return (
357
+ f"CMAES("
358
+ f"dim={self.dim}, "
359
+ f"lambda={self.lambda_}, "
360
+ f"mu={self.mu}, "
361
+ f"sigma={self.sigma:.3f})"
362
+ )
363
+
364
+
365
+ # Convenience function
366
+ def optimize_with_cmaes(
367
+ search_space: SearchSpace,
368
+ evaluator: Callable,
369
+ population_size: Optional[int] = None,
370
+ max_generations: int = 100,
371
+ sigma: float = 0.3,
372
+ verbose: bool = True,
373
+ ) -> Individual:
374
+ """
375
+ Quick CMA-ES optimization with sensible defaults.
376
+
377
+ Args:
378
+ search_space: SearchSpace to optimize over
379
+ evaluator: Fitness evaluation function
380
+ population_size: Offspring per generation (None = auto)
381
+ max_generations: Maximum generations
382
+ sigma: Initial step-size
383
+ verbose: Print progress
384
+
385
+ Returns:
386
+ Best Individual found
387
+
388
+ Example:
389
+ >>> from morphml.optimizers.evolutionary import optimize_with_cmaes
390
+ >>> best = optimize_with_cmaes(
391
+ ... search_space=space,
392
+ ... evaluator=my_evaluator,
393
+ ... max_generations=50,
394
+ ... sigma=0.3
395
+ ... )
396
+ """
397
+ config = {"max_generations": max_generations, "sigma": sigma}
398
+
399
+ if population_size is not None:
400
+ config["population_size"] = population_size
401
+
402
+ optimizer = CMAES(search_space=search_space, config=config)
403
+
404
+ best = optimizer.optimize(evaluator)
405
+
406
+ if verbose:
407
+ print(f"\n{'='*60}")
408
+ print("CMA-ES Optimization Complete")
409
+ print(f"{'='*60}")
410
+ print(f"Best Fitness: {best.fitness:.4f}")
411
+ print(f"Generations: {max_generations}")
412
+ print(f"Population Size: {optimizer.lambda_}")
413
+ print(f"Final σ: {optimizer.sigma:.4f}")
414
+ print(f"{'='*60}\n")
415
+
416
+ return best