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
morphml/cli/main.py ADDED
@@ -0,0 +1,457 @@
1
+ """Main CLI entry point for MorphML."""
2
+
3
+ import importlib.util
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+ from rich.console import Console
10
+ from rich.progress import (
11
+ BarColumn,
12
+ Progress,
13
+ SpinnerColumn,
14
+ TaskProgressColumn,
15
+ TextColumn,
16
+ TimeElapsedColumn,
17
+ )
18
+ from rich.table import Table
19
+
20
+ from morphml import __version__
21
+ from morphml.evaluation import HeuristicEvaluator
22
+ from morphml.logging_config import get_logger, setup_logging
23
+ from morphml.optimizers import (
24
+ DARTS,
25
+ ENAS,
26
+ DifferentialEvolution,
27
+ GaussianProcessOptimizer,
28
+ GeneticAlgorithm,
29
+ HillClimbing,
30
+ NSGA2Optimizer,
31
+ RandomSearch,
32
+ SimulatedAnnealing,
33
+ SMACOptimizer,
34
+ TPEOptimizer,
35
+ )
36
+ from morphml.utils import ArchitectureExporter, Checkpoint
37
+
38
+ console = Console()
39
+ logger = get_logger(__name__)
40
+
41
+
42
+ @click.group()
43
+ @click.version_option(version=__version__)
44
+ def cli():
45
+ """
46
+ MorphML - Evolutionary AutoML Construction Kit
47
+
48
+ Use 'morphml COMMAND --help' for more information on a command.
49
+ """
50
+ pass
51
+
52
+
53
+ @cli.command()
54
+ @click.argument("experiment_file", type=click.Path(exists=True))
55
+ @click.option("--output-dir", "-o", default="./results", help="Output directory")
56
+ @click.option("--checkpoint-dir", "-c", default=None, help="Checkpoint directory")
57
+ @click.option("--resume", "-r", type=click.Path(exists=True), help="Resume from checkpoint")
58
+ @click.option("--verbose", "-v", is_flag=True, help="Verbose logging")
59
+ @click.option(
60
+ "--export-format",
61
+ "-e",
62
+ type=click.Choice(["pytorch", "keras", "both"]),
63
+ default="both",
64
+ help="Export format",
65
+ )
66
+ @click.option(
67
+ "--optimizer",
68
+ type=click.Choice(
69
+ ["ga", "rs", "hc", "sa", "de", "gp", "tpe", "smac", "nsga2", "darts", "enas"]
70
+ ),
71
+ default="ga",
72
+ help="Optimizer to use (ga=Genetic Algorithm, rs=Random Search, hc=Hill Climbing, "
73
+ "sa=Simulated Annealing, de=Differential Evolution, gp=Gaussian Process, "
74
+ "tpe=Tree-structured Parzen Estimator, smac=SMAC, nsga2=NSGA-II, "
75
+ "darts=DARTS, enas=ENAS)",
76
+ )
77
+ def run(
78
+ experiment_file: str,
79
+ output_dir: str,
80
+ checkpoint_dir: str,
81
+ resume: str,
82
+ verbose: bool,
83
+ export_format: str,
84
+ optimizer: str,
85
+ ):
86
+ """
87
+ Run an experiment from a Python file.
88
+
89
+ Example:
90
+ morphml run experiment.py --output-dir ./results
91
+ """
92
+ # Setup logging
93
+ setup_logging(level="DEBUG" if verbose else "INFO")
94
+
95
+ console.print(f"[bold cyan]MorphML v{__version__}[/bold cyan]")
96
+ console.print("[cyan]Author:[/cyan] Eshan Roy <eshanized@proton.me>")
97
+ console.print("[cyan]Organization:[/cyan] TONMOY INFRASTRUCTURE & VISION\n")
98
+ console.print(f"Running experiment: [yellow]{experiment_file}[/yellow]\n")
99
+
100
+ # Load experiment definition
101
+ try:
102
+ spec = importlib.util.spec_from_file_location("experiment", experiment_file)
103
+ module = importlib.util.module_from_spec(spec)
104
+ spec.loader.exec_module(module)
105
+ except Exception as e:
106
+ console.print(f"[bold red]Error loading experiment:[/bold red] {e}")
107
+ sys.exit(1)
108
+
109
+ # Extract components
110
+ if not hasattr(module, "search_space"):
111
+ console.print("[bold red]Error:[/bold red] experiment file must define 'search_space'")
112
+ sys.exit(1)
113
+
114
+ search_space = module.search_space
115
+ optimizer_config = getattr(
116
+ module,
117
+ "optimizer_config",
118
+ {
119
+ "population_size": 20,
120
+ "num_generations": 50,
121
+ "elite_size": 2,
122
+ "mutation_rate": 0.15,
123
+ "crossover_rate": 0.7,
124
+ },
125
+ )
126
+ getattr(module, "max_evaluations", None)
127
+
128
+ # Create output directory
129
+ output_path = Path(output_dir)
130
+ output_path.mkdir(parents=True, exist_ok=True)
131
+
132
+ # Initialize components
133
+ optimizer_instance = _create_optimizer(optimizer, search_space, optimizer_config)
134
+ evaluator = HeuristicEvaluator()
135
+
136
+ # Run experiment with progress bar
137
+ try:
138
+ console.print("[bold green]Starting optimization...[/bold green]\n")
139
+
140
+ with Progress(
141
+ SpinnerColumn(),
142
+ TextColumn("[progress.description]{task.description}"),
143
+ BarColumn(),
144
+ TaskProgressColumn(),
145
+ TimeElapsedColumn(),
146
+ console=console,
147
+ ) as progress:
148
+ task = progress.add_task(
149
+ "[cyan]Evolving architectures...", total=optimizer_config.get("num_generations", 50)
150
+ )
151
+
152
+ # Optimize with callback
153
+ def callback(generation, population):
154
+ progress.update(task, advance=1)
155
+ if hasattr(population, "get_statistics"):
156
+ stats = population.get_statistics()
157
+ progress.console.print(
158
+ f" Gen {generation}: best={stats['best_fitness']:.4f}, "
159
+ f"mean={stats['mean_fitness']:.4f}, "
160
+ f"diversity={population.get_diversity():.3f}"
161
+ )
162
+ else:
163
+ progress.console.print(f" Iteration {generation}")
164
+
165
+ # Checkpoint
166
+ if checkpoint_dir and generation % 10 == 0:
167
+ cp_path = Path(checkpoint_dir) / f"checkpoint_gen_{generation}.json"
168
+ cp_path.parent.mkdir(parents=True, exist_ok=True)
169
+ Checkpoint.save(optimizer_instance, str(cp_path))
170
+
171
+ best = optimizer_instance.optimize(evaluator, callback=callback)
172
+
173
+ console.print("\n[bold green]✓ Optimization complete![/bold green]\n")
174
+
175
+ except KeyboardInterrupt:
176
+ console.print("\n[yellow]Interrupted by user[/yellow]")
177
+ sys.exit(1)
178
+
179
+ # Save results
180
+ _save_results(best, optimizer_instance, output_path, export_format)
181
+
182
+ # Display summary
183
+ _display_results_summary(best, optimizer_instance, output_path)
184
+
185
+
186
+ def _create_optimizer(optimizer_name: str, search_space, config: dict):
187
+ """Factory function to create optimizer instances.
188
+
189
+ Args:
190
+ optimizer_name: Short name of optimizer (ga, rs, hc, etc.)
191
+ search_space: SearchSpace to optimize
192
+ config: Configuration dict from experiment file
193
+
194
+ Returns:
195
+ Optimizer instance
196
+ """
197
+ optimizer_map = {
198
+ "ga": GeneticAlgorithm,
199
+ "rs": RandomSearch,
200
+ "hc": HillClimbing,
201
+ "sa": SimulatedAnnealing,
202
+ "de": DifferentialEvolution,
203
+ "gp": GaussianProcessOptimizer,
204
+ "tpe": TPEOptimizer,
205
+ "smac": SMACOptimizer,
206
+ "nsga2": NSGA2Optimizer,
207
+ }
208
+
209
+ # GPU-dependent optimizers
210
+ if optimizer_name == "darts":
211
+ if DARTS is None:
212
+ console.print(
213
+ "[red]Error:[/red] DARTS requires PyTorch. Install with: pip install 'morphml[gpu]'"
214
+ )
215
+ sys.exit(1)
216
+ console.print("[yellow]Warning:[/yellow] DARTS requires GPU for optimal performance")
217
+ return DARTS(search_space, config)
218
+
219
+ if optimizer_name == "enas":
220
+ if ENAS is None:
221
+ console.print(
222
+ "[red]Error:[/red] ENAS requires PyTorch. Install with: pip install 'morphml[gpu]'"
223
+ )
224
+ sys.exit(1)
225
+ console.print("[yellow]Warning:[/yellow] ENAS requires GPU for optimal performance")
226
+ return ENAS(search_space, config)
227
+
228
+ # Standard optimizers
229
+ optimizer_class = optimizer_map.get(optimizer_name)
230
+ if optimizer_class is None:
231
+ console.print(f"[red]Error:[/red] Unknown optimizer: {optimizer_name}")
232
+ sys.exit(1)
233
+
234
+ # Bayesian optimizers use different config structure
235
+ if optimizer_name in ["gp", "tpe", "smac"]:
236
+ return optimizer_class(search_space, config=config)
237
+
238
+ # NSGA2 uses different config structure
239
+ if optimizer_name == "nsga2":
240
+ return optimizer_class(search_space, config=config)
241
+
242
+ # Phase 1 optimizers use **kwargs
243
+ return optimizer_class(search_space, **config)
244
+
245
+
246
+ def _save_results(best, optimizer, output_path: Path, export_format: str):
247
+ """Save experiment results."""
248
+ console.print("[cyan]Saving results...[/cyan]")
249
+
250
+ # Save best model graph
251
+ best_model_path = output_path / "best_model.json"
252
+ with open(best_model_path, "w") as f:
253
+ json.dump(best.graph.to_dict(), f, indent=2)
254
+ console.print(f" ✓ Best model: [yellow]{best_model_path}[/yellow]")
255
+
256
+ # Export architecture
257
+ exporter = ArchitectureExporter()
258
+
259
+ if export_format in ("pytorch", "both"):
260
+ pytorch_path = output_path / "best_model_pytorch.py"
261
+ pytorch_code = exporter.to_pytorch(best.graph, "BestModel")
262
+ with open(pytorch_path, "w") as f:
263
+ f.write(pytorch_code)
264
+ console.print(f" ✓ PyTorch export: [yellow]{pytorch_path}[/yellow]")
265
+
266
+ if export_format in ("keras", "both"):
267
+ keras_path = output_path / "best_model_keras.py"
268
+ keras_code = exporter.to_keras(best.graph, "best_model")
269
+ with open(keras_path, "w") as f:
270
+ f.write(keras_code)
271
+ console.print(f" ✓ Keras export: [yellow]{keras_path}[/yellow]")
272
+
273
+ # Save history if available
274
+ if hasattr(optimizer, "get_history"):
275
+ history = optimizer.get_history()
276
+ history_path = output_path / "history.json"
277
+ with open(history_path, "w") as f:
278
+ json.dump(history, f, indent=2)
279
+ console.print(f" ✓ History: [yellow]{history_path}[/yellow]")
280
+
281
+ # Save summary
282
+ summary = {
283
+ "best_fitness": best.fitness if hasattr(best, "fitness") else str(best),
284
+ }
285
+
286
+ # Add population stats if available
287
+ if hasattr(optimizer, "population"):
288
+ summary["final_generation"] = optimizer.population.generation
289
+ summary["population_size"] = optimizer.population.size()
290
+ summary["statistics"] = optimizer.population.get_statistics()
291
+
292
+ summary_path = output_path / "summary.json"
293
+ with open(summary_path, "w") as f:
294
+ json.dump(summary, f, indent=2)
295
+ console.print(f" ✓ Summary: [yellow]{summary_path}[/yellow]\n")
296
+
297
+
298
+ def _display_results_summary(best, optimizer, output_path: Path):
299
+ """Display results in terminal."""
300
+ table = Table(title="Experiment Results", show_header=True, header_style="bold magenta")
301
+ table.add_column("Metric", style="cyan", width=30)
302
+ table.add_column("Value", style="green", width=20)
303
+
304
+ # Add fitness
305
+ table.add_row("Best Fitness", f"{best.fitness:.6f}" if hasattr(best, "fitness") else str(best))
306
+
307
+ # Add population stats if available
308
+ if hasattr(optimizer, "population"):
309
+ stats = optimizer.population.get_statistics()
310
+ table.add_row("Mean Fitness", f"{stats['mean_fitness']:.6f}")
311
+ table.add_row("Final Generation", str(optimizer.population.generation))
312
+ table.add_row("Population Size", str(optimizer.population.size()))
313
+ table.add_row("Best Model Nodes", str(len(best.graph.nodes)))
314
+ table.add_row("Best Model Depth", str(best.graph.get_depth()))
315
+ table.add_row("Est. Parameters", f"{best.graph.estimate_parameters():,}")
316
+ table.add_row("Output Directory", str(output_path))
317
+
318
+ console.print(table)
319
+
320
+
321
+ @cli.command()
322
+ @click.argument("results_dir", type=click.Path(exists=True))
323
+ def status(results_dir: str):
324
+ """Show status of an experiment."""
325
+ results_path = Path(results_dir)
326
+ summary_path = results_path / "summary.json"
327
+
328
+ if not summary_path.exists():
329
+ console.print(f"[red]No results found in {results_dir}[/red]")
330
+ sys.exit(1)
331
+
332
+ with open(summary_path) as f:
333
+ summary = json.load(f)
334
+
335
+ table = Table(
336
+ title=f"Experiment Status: {results_dir}", show_header=True, header_style="bold magenta"
337
+ )
338
+ table.add_column("Metric", style="cyan", width=30)
339
+ table.add_column("Value", style="green", width=30)
340
+
341
+ for key, value in summary.items():
342
+ if isinstance(value, dict):
343
+ table.add_row(key, json.dumps(value, indent=2))
344
+ elif isinstance(value, float):
345
+ table.add_row(key, f"{value:.6f}")
346
+ else:
347
+ table.add_row(key, str(value))
348
+
349
+ console.print(table)
350
+
351
+ # Show best model info if available
352
+ best_model_path = results_path / "best_model.json"
353
+ if best_model_path.exists():
354
+ console.print(f"\n[cyan]Best model saved at:[/cyan] [yellow]{best_model_path}[/yellow]")
355
+
356
+
357
+ @cli.command()
358
+ @click.option("--key", "-k", default=None, help="Show specific config key")
359
+ def config(key: str):
360
+ """Show current configuration."""
361
+ from morphml.config import get_config
362
+
363
+ cfg = get_config()
364
+
365
+ if key:
366
+ value = cfg.get(key)
367
+ console.print(f"[cyan]{key}:[/cyan] [green]{value}[/green]")
368
+ else:
369
+ table = Table(title="MorphML Configuration", show_header=True, header_style="bold magenta")
370
+ table.add_column("Key", style="cyan", width=40)
371
+ table.add_column("Value", style="green", width=40)
372
+
373
+ # Show some default configurations
374
+ default_config = {
375
+ "version": __version__,
376
+ "logging.level": "INFO",
377
+ "execution.max_workers": 4,
378
+ "search.population_size": 50,
379
+ "search.max_generations": 100,
380
+ }
381
+
382
+ for k, v in default_config.items():
383
+ table.add_row(k, str(v))
384
+
385
+ console.print(table)
386
+
387
+
388
+ @cli.command()
389
+ @click.argument("architecture_file", type=click.Path(exists=True))
390
+ @click.option(
391
+ "--format",
392
+ "-f",
393
+ type=click.Choice(["pytorch", "keras", "both"]),
394
+ default="pytorch",
395
+ help="Export format",
396
+ )
397
+ @click.option("--output", "-o", default=None, help="Output file path")
398
+ def export(architecture_file: str, format: str, output: str):
399
+ """Export architecture to framework-specific code."""
400
+ console.print(f"[cyan]Exporting architecture from:[/cyan] {architecture_file}\n")
401
+
402
+ # Load architecture
403
+ with open(architecture_file, "r") as f:
404
+ arch_data = json.load(f)
405
+
406
+ from morphml.core.graph import ModelGraph
407
+
408
+ graph = ModelGraph.from_dict(arch_data)
409
+
410
+ exporter = ArchitectureExporter()
411
+
412
+ if format in ("pytorch", "both"):
413
+ code = exporter.to_pytorch(graph, "ExportedModel")
414
+ if output:
415
+ with open(output, "w") as f:
416
+ f.write(code)
417
+ console.print(f"[green]✓ PyTorch code exported to:[/green] [yellow]{output}[/yellow]")
418
+ else:
419
+ console.print("[bold]PyTorch Code:[/bold]\n")
420
+ console.print(code)
421
+
422
+ if format in ("keras", "both") and format != "pytorch":
423
+ code = exporter.to_keras(graph, "exported_model")
424
+ output_keras = output.replace(".py", "_keras.py") if output else None
425
+ if output_keras:
426
+ with open(output_keras, "w") as f:
427
+ f.write(code)
428
+ console.print(
429
+ f"[green]✓ Keras code exported to:[/green] [yellow]{output_keras}[/yellow]"
430
+ )
431
+ else:
432
+ console.print("\n[bold]Keras Code:[/bold]\n")
433
+ console.print(code)
434
+
435
+
436
+ @cli.command()
437
+ def version():
438
+ """Show MorphML version and system info."""
439
+ import platform
440
+ import sys
441
+
442
+ table = Table(title="MorphML System Information", show_header=True, header_style="bold magenta")
443
+ table.add_column("Item", style="cyan", width=30)
444
+ table.add_column("Value", style="green", width=50)
445
+
446
+ table.add_row("MorphML Version", __version__)
447
+ table.add_row("Author", "Eshan Roy <eshanized@proton.me>")
448
+ table.add_row("Organization", "TONMOY INFRASTRUCTURE & VISION")
449
+ table.add_row("Repository", "https://github.com/TIVerse/MorphML")
450
+ table.add_row("Python Version", sys.version.split()[0])
451
+ table.add_row("Platform", platform.platform())
452
+
453
+ console.print(table)
454
+
455
+
456
+ if __name__ == "__main__":
457
+ cli()