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,318 @@
1
+ """Benchmark suite for running comprehensive optimizer comparisons."""
2
+
3
+ import time
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from morphml.benchmarks.problems import BenchmarkProblem
7
+ from morphml.logging_config import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ class BenchmarkResult:
13
+ """Results from a single benchmark run."""
14
+
15
+ def __init__(
16
+ self,
17
+ problem_name: str,
18
+ optimizer_name: str,
19
+ best_fitness: float,
20
+ mean_fitness: float,
21
+ evaluations: int,
22
+ time_seconds: float,
23
+ convergence_generation: Optional[int] = None,
24
+ final_diversity: Optional[float] = None,
25
+ ):
26
+ """Initialize benchmark result."""
27
+ self.problem_name = problem_name
28
+ self.optimizer_name = optimizer_name
29
+ self.best_fitness = best_fitness
30
+ self.mean_fitness = mean_fitness
31
+ self.evaluations = evaluations
32
+ self.time_seconds = time_seconds
33
+ self.convergence_generation = convergence_generation
34
+ self.final_diversity = final_diversity
35
+
36
+ def to_dict(self) -> Dict[str, Any]:
37
+ """Convert to dictionary."""
38
+ return {
39
+ "problem": self.problem_name,
40
+ "optimizer": self.optimizer_name,
41
+ "best_fitness": self.best_fitness,
42
+ "mean_fitness": self.mean_fitness,
43
+ "evaluations": self.evaluations,
44
+ "time_seconds": self.time_seconds,
45
+ "convergence_generation": self.convergence_generation,
46
+ "final_diversity": self.final_diversity,
47
+ }
48
+
49
+ def __repr__(self) -> str:
50
+ return (
51
+ f"BenchmarkResult({self.optimizer_name} on {self.problem_name}: "
52
+ f"best={self.best_fitness:.4f}, time={self.time_seconds:.2f}s)"
53
+ )
54
+
55
+
56
+ class BenchmarkSuite:
57
+ """
58
+ Comprehensive benchmark suite for NAS optimizers.
59
+
60
+ Example:
61
+ >>> suite = BenchmarkSuite()
62
+ >>> suite.add_optimizer("GA", GeneticAlgorithm, {...})
63
+ >>> suite.add_optimizer("RS", RandomSearch, {...})
64
+ >>> results = suite.run(problems, num_runs=5)
65
+ >>> suite.print_summary(results)
66
+ """
67
+
68
+ def __init__(self):
69
+ """Initialize benchmark suite."""
70
+ self.optimizers: Dict[str, tuple] = {}
71
+ self.results: List[BenchmarkResult] = []
72
+
73
+ def add_optimizer(self, name: str, optimizer_class: type, config: Dict[str, Any]) -> None:
74
+ """
75
+ Add an optimizer to benchmark.
76
+
77
+ Args:
78
+ name: Optimizer name
79
+ optimizer_class: Optimizer class
80
+ config: Configuration dict
81
+ """
82
+ self.optimizers[name] = (optimizer_class, config)
83
+ logger.info(f"Added optimizer: {name}")
84
+
85
+ def run_single(
86
+ self,
87
+ problem: BenchmarkProblem,
88
+ optimizer_name: str,
89
+ optimizer_class: type,
90
+ config: Dict[str, Any],
91
+ ) -> BenchmarkResult:
92
+ """Run single benchmark."""
93
+ logger.info(f"Running {optimizer_name} on {problem.name}...")
94
+
95
+ start_time = time.time()
96
+
97
+ # Create optimizer
98
+ optimizer = optimizer_class(search_space=problem.search_space, **config)
99
+
100
+ # Run optimization
101
+ try:
102
+ if hasattr(optimizer, "optimize"):
103
+ best = optimizer.optimize(problem.evaluate)
104
+ else:
105
+ raise ValueError(f"Optimizer {optimizer_name} has no optimize method")
106
+
107
+ end_time = time.time()
108
+ elapsed = end_time - start_time
109
+
110
+ # Collect metrics
111
+ best_fitness = best.fitness if best else 0.0
112
+
113
+ # Get population stats if available
114
+ mean_fitness = 0.0
115
+ final_diversity = None
116
+ convergence_gen = None
117
+
118
+ if hasattr(optimizer, "population"):
119
+ pop = optimizer.population
120
+ if pop.size() > 0:
121
+ stats = pop.get_statistics()
122
+ mean_fitness = stats.get("mean_fitness", 0.0)
123
+ final_diversity = pop.get_diversity()
124
+ convergence_gen = pop.generation
125
+
126
+ # Count evaluations
127
+ evaluations = config.get("num_generations", 0) * config.get("population_size", 1)
128
+ if hasattr(optimizer, "num_samples"):
129
+ evaluations = config.get("num_samples", 0)
130
+
131
+ result = BenchmarkResult(
132
+ problem_name=problem.name,
133
+ optimizer_name=optimizer_name,
134
+ best_fitness=best_fitness,
135
+ mean_fitness=mean_fitness,
136
+ evaluations=evaluations,
137
+ time_seconds=elapsed,
138
+ convergence_generation=convergence_gen,
139
+ final_diversity=final_diversity,
140
+ )
141
+
142
+ logger.info(f"Completed: {result}")
143
+ return result
144
+
145
+ except Exception as e:
146
+ logger.error(f"Benchmark failed: {e}")
147
+ # Return failure result
148
+ return BenchmarkResult(
149
+ problem_name=problem.name,
150
+ optimizer_name=optimizer_name,
151
+ best_fitness=0.0,
152
+ mean_fitness=0.0,
153
+ evaluations=0,
154
+ time_seconds=time.time() - start_time,
155
+ )
156
+
157
+ def run(self, problems: List[BenchmarkProblem], num_runs: int = 5) -> List[BenchmarkResult]:
158
+ """
159
+ Run full benchmark suite.
160
+
161
+ Args:
162
+ problems: List of benchmark problems
163
+ num_runs: Number of runs per optimizer-problem pair
164
+
165
+ Returns:
166
+ List of benchmark results
167
+ """
168
+ all_results = []
169
+
170
+ total_runs = len(self.optimizers) * len(problems) * num_runs
171
+ current_run = 0
172
+
173
+ logger.info(f"Starting benchmark: {total_runs} total runs")
174
+
175
+ for problem in problems:
176
+ for opt_name, (opt_class, config) in self.optimizers.items():
177
+ for run in range(num_runs):
178
+ current_run += 1
179
+ logger.info(
180
+ f"Run {current_run}/{total_runs}: "
181
+ f"{opt_name} on {problem.name} (run {run + 1}/{num_runs})"
182
+ )
183
+
184
+ result = self.run_single(problem, opt_name, opt_class, config)
185
+ all_results.append(result)
186
+
187
+ self.results = all_results
188
+ logger.info("Benchmark complete!")
189
+
190
+ return all_results
191
+
192
+ def get_summary(self, results: Optional[List[BenchmarkResult]] = None) -> Dict[str, Any]:
193
+ """
194
+ Get summary statistics.
195
+
196
+ Args:
197
+ results: Results to summarize (uses self.results if None)
198
+
199
+ Returns:
200
+ Summary dictionary
201
+ """
202
+ if results is None:
203
+ results = self.results
204
+
205
+ if not results:
206
+ return {}
207
+
208
+ summary = {}
209
+
210
+ # Group by optimizer and problem
211
+ for result in results:
212
+ key = (result.optimizer_name, result.problem_name)
213
+
214
+ if key not in summary:
215
+ summary[key] = {
216
+ "optimizer": result.optimizer_name,
217
+ "problem": result.problem_name,
218
+ "best_fitnesses": [],
219
+ "mean_fitnesses": [],
220
+ "times": [],
221
+ "evaluations": [],
222
+ }
223
+
224
+ summary[key]["best_fitnesses"].append(result.best_fitness)
225
+ summary[key]["mean_fitnesses"].append(result.mean_fitness)
226
+ summary[key]["times"].append(result.time_seconds)
227
+ summary[key]["evaluations"].append(result.evaluations)
228
+
229
+ # Compute statistics
230
+ import statistics
231
+
232
+ for key, data in summary.items():
233
+ data["mean_best_fitness"] = statistics.mean(data["best_fitnesses"])
234
+ data["std_best_fitness"] = (
235
+ statistics.stdev(data["best_fitnesses"]) if len(data["best_fitnesses"]) > 1 else 0.0
236
+ )
237
+ data["median_best_fitness"] = statistics.median(data["best_fitnesses"])
238
+ data["mean_time"] = statistics.mean(data["times"])
239
+ data["mean_evaluations"] = statistics.mean(data["evaluations"])
240
+
241
+ return summary
242
+
243
+ def print_summary(self, results: Optional[List[BenchmarkResult]] = None) -> None:
244
+ """Print summary of results."""
245
+ summary = self.get_summary(results)
246
+
247
+ if not summary:
248
+ print("No results to summarize")
249
+ return
250
+
251
+ print("\n" + "=" * 80)
252
+ print("BENCHMARK SUMMARY")
253
+ print("=" * 80)
254
+
255
+ # Group by problem
256
+ problems = {data["problem"] for data in summary.values()}
257
+
258
+ for problem in sorted(problems):
259
+ print(f"\n{problem}")
260
+ print("-" * 80)
261
+ print(f"{'Optimizer':<20} {'Best (Mean±Std)':<25} {'Median':<10} {'Time (s)':<12}")
262
+ print("-" * 80)
263
+
264
+ problem_results = [
265
+ (data["optimizer"], data)
266
+ for key, data in summary.items()
267
+ if data["problem"] == problem
268
+ ]
269
+
270
+ # Sort by mean best fitness (descending)
271
+ problem_results.sort(key=lambda x: x[1]["mean_best_fitness"], reverse=True)
272
+
273
+ for opt_name, data in problem_results:
274
+ mean_best = data["mean_best_fitness"]
275
+ std_best = data["std_best_fitness"]
276
+ median_best = data["median_best_fitness"]
277
+ mean_time = data["mean_time"]
278
+
279
+ print(
280
+ f"{opt_name:<20} "
281
+ f"{mean_best:.4f}±{std_best:.4f} "
282
+ f"{median_best:.4f} "
283
+ f"{mean_time:>10.2f}"
284
+ )
285
+
286
+ print("\n" + "=" * 80)
287
+
288
+ def get_winner(
289
+ self, problem_name: str, results: Optional[List[BenchmarkResult]] = None
290
+ ) -> Optional[str]:
291
+ """Get best optimizer for a problem."""
292
+ summary = self.get_summary(results)
293
+
294
+ problem_results = [
295
+ (data["optimizer"], data["mean_best_fitness"])
296
+ for key, data in summary.items()
297
+ if data["problem"] == problem_name
298
+ ]
299
+
300
+ if not problem_results:
301
+ return None
302
+
303
+ problem_results.sort(key=lambda x: x[1], reverse=True)
304
+ return problem_results[0][0]
305
+
306
+ def export_results(self, filename: str) -> None:
307
+ """Export results to JSON."""
308
+ import json
309
+
310
+ data = {
311
+ "results": [r.to_dict() for r in self.results],
312
+ "summary": {str(k): v for k, v in self.get_summary().items()},
313
+ }
314
+
315
+ with open(filename, "w") as f:
316
+ json.dump(data, f, indent=2)
317
+
318
+ logger.info(f"Results exported to {filename}")
@@ -0,0 +1,5 @@
1
+ """Command-line interface for MorphML."""
2
+
3
+ from morphml.cli.main import cli
4
+
5
+ __all__ = ["cli"]
@@ -0,0 +1,329 @@
1
+ """Enhanced experiment management CLI commands.
2
+
3
+ Provides interactive commands for managing NAS experiments.
4
+
5
+ Example:
6
+ morphml experiment create --name my-experiment
7
+ morphml experiment list
8
+ morphml experiment show exp_123
9
+ """
10
+
11
+ from typing import Optional
12
+
13
+ import click
14
+ from rich.console import Console
15
+ from rich.progress import Progress
16
+ from rich.prompt import Confirm, Prompt
17
+ from rich.table import Table
18
+
19
+ from morphml.logging_config import get_logger
20
+
21
+ logger = get_logger(__name__)
22
+ console = Console()
23
+
24
+
25
+ @click.group()
26
+ def experiment():
27
+ """Manage NAS experiments."""
28
+ pass
29
+
30
+
31
+ @experiment.command()
32
+ @click.option("--name", "-n", help="Experiment name")
33
+ @click.option("--optimizer", "-o", default="genetic", help="Optimizer type")
34
+ @click.option("--budget", "-b", default=500, type=int, help="Evaluation budget")
35
+ @click.option("--interactive", "-i", is_flag=True, help="Interactive mode")
36
+ def create(name: Optional[str], optimizer: str, budget: int, interactive: bool):
37
+ """
38
+ Create a new experiment.
39
+
40
+ Example:
41
+ morphml experiment create --name cifar10-search
42
+ morphml experiment create -i # Interactive mode
43
+ """
44
+ console.print("[bold cyan]Create New Experiment[/bold cyan]\n")
45
+
46
+ # Interactive mode
47
+ if interactive or not name:
48
+ name = Prompt.ask("Experiment name", default="my-experiment")
49
+ optimizer = Prompt.ask(
50
+ "Optimizer",
51
+ choices=["genetic", "random", "hillclimbing", "bayesian"],
52
+ default="genetic",
53
+ )
54
+ budget = int(Prompt.ask("Evaluation budget", default="500"))
55
+
56
+ # Define search space interactively
57
+ console.print("\n[bold]Define Search Space[/bold]")
58
+
59
+ layers = []
60
+
61
+ # Input layer
62
+ console.print("Input layer:")
63
+ input_shape = Prompt.ask(" Input shape (e.g., 3,32,32)", default="3,32,32")
64
+ shape = tuple(map(int, input_shape.split(",")))
65
+ layers.append({"type": "input", "shape": shape})
66
+
67
+ # Add layers
68
+ while True:
69
+ add_layer = Confirm.ask("Add a layer?", default=True)
70
+ if not add_layer:
71
+ break
72
+
73
+ layer_type = Prompt.ask(
74
+ " Layer type",
75
+ choices=["conv2d", "dense", "maxpool", "flatten", "relu", "dropout"],
76
+ default="conv2d",
77
+ )
78
+
79
+ layer_config = {"type": layer_type}
80
+
81
+ if layer_type == "conv2d":
82
+ filters = int(Prompt.ask(" Filters", default="64"))
83
+ kernel = int(Prompt.ask(" Kernel size", default="3"))
84
+ layer_config.update({"filters": filters, "kernel_size": kernel})
85
+
86
+ elif layer_type == "dense":
87
+ units = int(Prompt.ask(" Units", default="128"))
88
+ layer_config.update({"units": units})
89
+
90
+ elif layer_type == "maxpool":
91
+ pool_size = int(Prompt.ask(" Pool size", default="2"))
92
+ layer_config.update({"pool_size": pool_size})
93
+
94
+ elif layer_type == "dropout":
95
+ rate = float(Prompt.ask(" Dropout rate", default="0.5"))
96
+ layer_config.update({"rate": rate})
97
+
98
+ layers.append(layer_config)
99
+
100
+ # Create experiment config
101
+
102
+ # Display summary
103
+ console.print("\n[bold green]Experiment Configuration:[/bold green]")
104
+ console.print(f" Name: {name}")
105
+ console.print(f" Optimizer: {optimizer}")
106
+ console.print(f" Budget: {budget}")
107
+ console.print(f" Layers: {len(layers)}")
108
+
109
+ # Confirm
110
+ if Confirm.ask("\nCreate experiment?", default=True):
111
+ # TODO: Actually create experiment via API or directly
112
+ console.print(f"\n[green]✓ Created experiment: {name}[/green]")
113
+ console.print(f" Run with: morphml experiment start {name}")
114
+ else:
115
+ console.print("[yellow]Cancelled[/yellow]")
116
+
117
+
118
+ @experiment.command()
119
+ @click.option("--status", "-s", help="Filter by status")
120
+ @click.option("--limit", "-l", default=20, help="Maximum results")
121
+ def list(status: Optional[str], limit: int):
122
+ """
123
+ List all experiments.
124
+
125
+ Example:
126
+ morphml experiment list
127
+ morphml experiment list --status running
128
+ """
129
+ console.print("[bold cyan]Experiments[/bold cyan]\n")
130
+
131
+ # TODO: Fetch from API
132
+ # For now, show example data
133
+ experiments = [
134
+ {
135
+ "id": "exp_abc123",
136
+ "name": "CIFAR-10 Search",
137
+ "status": "running",
138
+ "best_accuracy": 0.9234,
139
+ "generation": 25,
140
+ "total_generations": 50,
141
+ },
142
+ {
143
+ "id": "exp_def456",
144
+ "name": "ImageNet Transfer",
145
+ "status": "completed",
146
+ "best_accuracy": 0.8567,
147
+ "generation": 100,
148
+ "total_generations": 100,
149
+ },
150
+ {
151
+ "id": "exp_ghi789",
152
+ "name": "Quick Test",
153
+ "status": "failed",
154
+ "best_accuracy": None,
155
+ "generation": 5,
156
+ "total_generations": 20,
157
+ },
158
+ ]
159
+
160
+ # Filter by status
161
+ if status:
162
+ experiments = [e for e in experiments if e["status"] == status]
163
+
164
+ # Limit results
165
+ experiments = experiments[:limit]
166
+
167
+ # Create table
168
+ table = Table(title=f"Experiments ({len(experiments)} total)")
169
+ table.add_column("ID", style="cyan")
170
+ table.add_column("Name", style="white")
171
+ table.add_column("Status", style="yellow")
172
+ table.add_column("Progress", style="blue")
173
+ table.add_column("Best Accuracy", style="green")
174
+
175
+ for exp in experiments:
176
+ # Status color
177
+ status_color = {
178
+ "running": "yellow",
179
+ "completed": "green",
180
+ "failed": "red",
181
+ "pending": "blue",
182
+ }.get(exp["status"], "white")
183
+
184
+ # Progress
185
+ progress = f"{exp['generation']}/{exp['total_generations']}"
186
+
187
+ # Accuracy
188
+ accuracy = f"{exp['best_accuracy']:.4f}" if exp["best_accuracy"] else "-"
189
+
190
+ table.add_row(
191
+ exp["id"],
192
+ exp["name"],
193
+ f"[{status_color}]{exp['status']}[/{status_color}]",
194
+ progress,
195
+ accuracy,
196
+ )
197
+
198
+ console.print(table)
199
+
200
+ console.print(f"\n[dim]Showing {len(experiments)} of {len(experiments)} experiments[/dim]")
201
+
202
+
203
+ @experiment.command()
204
+ @click.argument("experiment_id")
205
+ def show(experiment_id: str):
206
+ """
207
+ Show detailed experiment information.
208
+
209
+ Example:
210
+ morphml experiment show exp_abc123
211
+ """
212
+ console.print(f"[bold cyan]Experiment: {experiment_id}[/bold cyan]\n")
213
+
214
+ # TODO: Fetch from API
215
+ # Example data
216
+ exp = {
217
+ "id": experiment_id,
218
+ "name": "CIFAR-10 Search",
219
+ "status": "running",
220
+ "optimizer": "genetic",
221
+ "created_at": "2024-11-11T05:00:00Z",
222
+ "started_at": "2024-11-11T05:01:00Z",
223
+ "best_accuracy": 0.9234,
224
+ "generation": 25,
225
+ "total_generations": 50,
226
+ "population_size": 20,
227
+ "best_architecture": {"parameters": 1234567, "depth": 8, "nodes": 12},
228
+ }
229
+
230
+ # Display info
231
+ console.print(f"[bold]Name:[/bold] {exp['name']}")
232
+ console.print(f"[bold]Status:[/bold] {exp['status']}")
233
+ console.print(f"[bold]Optimizer:[/bold] {exp['optimizer']}")
234
+ console.print(f"[bold]Created:[/bold] {exp['created_at']}")
235
+ console.print(
236
+ f"[bold]Progress:[/bold] {exp['generation']}/{exp['total_generations']} generations"
237
+ )
238
+
239
+ console.print("\n[bold green]Best Architecture:[/bold green]")
240
+ console.print(f" Accuracy: {exp['best_accuracy']:.4f}")
241
+ console.print(f" Parameters: {exp['best_architecture']['parameters']:,}")
242
+ console.print(f" Depth: {exp['best_architecture']['depth']}")
243
+ console.print(f" Nodes: {exp['best_architecture']['nodes']}")
244
+
245
+
246
+ @experiment.command()
247
+ @click.argument("experiment_id")
248
+ def start(experiment_id: str):
249
+ """
250
+ Start an experiment.
251
+
252
+ Example:
253
+ morphml experiment start exp_abc123
254
+ """
255
+ console.print(f"[bold cyan]Starting experiment: {experiment_id}[/bold cyan]\n")
256
+
257
+ with Progress() as progress:
258
+ task = progress.add_task("[cyan]Initializing...", total=100)
259
+
260
+ # Simulate startup
261
+ import time
262
+
263
+ for _i in range(100):
264
+ time.sleep(0.01)
265
+ progress.update(task, advance=1)
266
+
267
+ console.print("[green]✓ Experiment started[/green]")
268
+ console.print(f" Monitor with: morphml experiment show {experiment_id}")
269
+
270
+
271
+ @experiment.command()
272
+ @click.argument("experiment_id")
273
+ def stop(experiment_id: str):
274
+ """
275
+ Stop a running experiment.
276
+
277
+ Example:
278
+ morphml experiment stop exp_abc123
279
+ """
280
+ if Confirm.ask(f"Stop experiment {experiment_id}?"):
281
+ console.print(f"[yellow]Stopping experiment: {experiment_id}[/yellow]")
282
+ # TODO: Call API to stop
283
+ console.print("[green]✓ Experiment stopped[/green]")
284
+ else:
285
+ console.print("[dim]Cancelled[/dim]")
286
+
287
+
288
+ @experiment.command()
289
+ @click.argument("experiment_id")
290
+ @click.option("--force", "-f", is_flag=True, help="Force delete without confirmation")
291
+ def delete(experiment_id: str, force: bool):
292
+ """
293
+ Delete an experiment.
294
+
295
+ Example:
296
+ morphml experiment delete exp_abc123
297
+ morphml experiment delete exp_abc123 --force
298
+ """
299
+ if force or Confirm.ask(f"[red]Delete experiment {experiment_id}?[/red]"):
300
+ console.print(f"[red]Deleting experiment: {experiment_id}[/red]")
301
+ # TODO: Call API to delete
302
+ console.print("[green]✓ Experiment deleted[/green]")
303
+ else:
304
+ console.print("[dim]Cancelled[/dim]")
305
+
306
+
307
+ @experiment.command()
308
+ @click.argument("experiment_id")
309
+ @click.option("--format", "-f", type=click.Choice(["pytorch", "keras", "both"]), default="pytorch")
310
+ @click.option("--output", "-o", help="Output file path")
311
+ def export(experiment_id: str, format: str, output: Optional[str]):
312
+ """
313
+ Export best architecture from experiment.
314
+
315
+ Example:
316
+ morphml experiment export exp_abc123 --format pytorch
317
+ morphml experiment export exp_abc123 --format keras -o model.py
318
+ """
319
+ console.print(f"[bold cyan]Exporting architecture from: {experiment_id}[/bold cyan]\n")
320
+
321
+ # TODO: Fetch best architecture and export
322
+
323
+ if not output:
324
+ output = f"{experiment_id}_model.py"
325
+
326
+ console.print(f"[bold]Format:[/bold] {format}")
327
+ console.print(f"[bold]Output:[/bold] {output}")
328
+
329
+ console.print(f"\n[green]✓ Exported to {output}[/green]")