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,199 @@
1
+ """Performance metrics for benchmarking.
2
+
3
+ Author: Eshan Roy <eshanized@proton.me>
4
+ Organization: TONMOY INFRASTRUCTURE & VISION
5
+ """
6
+
7
+ from typing import Dict, List, Optional
8
+
9
+ import numpy as np
10
+
11
+ from morphml.logging_config import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ def sample_efficiency(history: List[Dict], target_fitness: float) -> int:
17
+ """
18
+ Calculate number of evaluations to reach target fitness.
19
+
20
+ Args:
21
+ history: Optimization history
22
+ target_fitness: Target fitness threshold
23
+
24
+ Returns:
25
+ Number of evaluations to reach target, or -1 if not reached
26
+ """
27
+ for i, entry in enumerate(history):
28
+ if entry.get("best_fitness", 0) >= target_fitness:
29
+ return i + 1
30
+ return -1
31
+
32
+
33
+ def convergence_rate(history: List[Dict]) -> float:
34
+ """
35
+ Calculate convergence rate (improvement per evaluation).
36
+
37
+ Args:
38
+ history: Optimization history
39
+
40
+ Returns:
41
+ Average improvement per evaluation
42
+ """
43
+ if len(history) < 2:
44
+ return 0.0
45
+
46
+ fitnesses = [entry.get("best_fitness", 0) for entry in history]
47
+ improvements = np.diff(fitnesses)
48
+
49
+ return np.mean(improvements[improvements > 0]) if len(improvements) > 0 else 0.0
50
+
51
+
52
+ def final_best_fitness(history: List[Dict]) -> float:
53
+ """
54
+ Get final best fitness achieved.
55
+
56
+ Args:
57
+ history: Optimization history
58
+
59
+ Returns:
60
+ Best fitness value
61
+ """
62
+ if not history:
63
+ return 0.0
64
+
65
+ return max(entry.get("best_fitness", 0) for entry in history)
66
+
67
+
68
+ def time_efficiency(history: List[Dict], target_fitness: float) -> float:
69
+ """
70
+ Calculate time to reach target fitness.
71
+
72
+ Args:
73
+ history: Optimization history
74
+ target_fitness: Target fitness threshold
75
+
76
+ Returns:
77
+ Time in seconds, or -1 if not reached
78
+ """
79
+ for entry in history:
80
+ if entry.get("best_fitness", 0) >= target_fitness:
81
+ return entry.get("time_elapsed", -1)
82
+ return -1.0
83
+
84
+
85
+ def stability_score(histories: List[List[Dict]]) -> float:
86
+ """
87
+ Calculate stability across multiple runs.
88
+
89
+ Args:
90
+ histories: List of optimization histories from multiple runs
91
+
92
+ Returns:
93
+ Stability score (1 - coefficient of variation of final fitness)
94
+ """
95
+ if not histories:
96
+ return 0.0
97
+
98
+ final_fitnesses = [final_best_fitness(h) for h in histories]
99
+
100
+ mean = np.mean(final_fitnesses)
101
+ std = np.std(final_fitnesses)
102
+
103
+ if mean == 0:
104
+ return 0.0
105
+
106
+ cv = std / mean # Coefficient of variation
107
+ stability = max(0.0, 1.0 - cv)
108
+
109
+ return stability
110
+
111
+
112
+ def area_under_curve(history: List[Dict]) -> float:
113
+ """
114
+ Calculate area under convergence curve (AUC).
115
+
116
+ Higher AUC indicates faster convergence.
117
+
118
+ Args:
119
+ history: Optimization history
120
+
121
+ Returns:
122
+ AUC value
123
+ """
124
+ if not history:
125
+ return 0.0
126
+
127
+ fitnesses = [entry.get("best_fitness", 0) for entry in history]
128
+
129
+ # Trapezoidal integration
130
+ auc = np.trapz(fitnesses)
131
+
132
+ return auc
133
+
134
+
135
+ def success_rate(histories: List[List[Dict]], target_fitness: float) -> float:
136
+ """
137
+ Calculate success rate (fraction of runs reaching target).
138
+
139
+ Args:
140
+ histories: List of optimization histories
141
+ target_fitness: Target fitness threshold
142
+
143
+ Returns:
144
+ Success rate [0, 1]
145
+ """
146
+ if not histories:
147
+ return 0.0
148
+
149
+ successes = sum(1 for h in histories if final_best_fitness(h) >= target_fitness)
150
+
151
+ return successes / len(histories)
152
+
153
+
154
+ def compute_all_metrics(
155
+ history: List[Dict], target_fitness: Optional[float] = None
156
+ ) -> Dict[str, float]:
157
+ """
158
+ Compute all available metrics for an optimization run.
159
+
160
+ Args:
161
+ history: Optimization history
162
+ target_fitness: Optional target fitness for efficiency metrics
163
+
164
+ Returns:
165
+ Dictionary of metric names and values
166
+ """
167
+ metrics = {
168
+ "final_best_fitness": final_best_fitness(history),
169
+ "convergence_rate": convergence_rate(history),
170
+ "auc": area_under_curve(history),
171
+ "num_evaluations": len(history),
172
+ }
173
+
174
+ if target_fitness is not None:
175
+ metrics["sample_efficiency"] = sample_efficiency(history, target_fitness)
176
+ metrics["time_efficiency"] = time_efficiency(history, target_fitness)
177
+
178
+ return metrics
179
+
180
+
181
+ def compare_optimizers(
182
+ results: Dict[str, List[Dict]], target_fitness: Optional[float] = None
183
+ ) -> Dict[str, Dict[str, float]]:
184
+ """
185
+ Compare multiple optimizers across metrics.
186
+
187
+ Args:
188
+ results: Dict mapping optimizer names to their histories
189
+ target_fitness: Optional target for efficiency metrics
190
+
191
+ Returns:
192
+ Dict of optimizer names to their metric dictionaries
193
+ """
194
+ comparison = {}
195
+
196
+ for optimizer_name, history in results.items():
197
+ comparison[optimizer_name] = compute_all_metrics(history, target_fitness)
198
+
199
+ return comparison
@@ -0,0 +1,201 @@
1
+ """OpenML integration for benchmarking.
2
+
3
+ Author: Eshan Roy <eshanized@proton.me>
4
+ Organization: TONMOY INFRASTRUCTURE & VISION
5
+ """
6
+
7
+ from typing import Dict, List, Tuple
8
+
9
+ import numpy as np
10
+
11
+ from morphml.logging_config import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+ # Check if OpenML is available
16
+ try:
17
+ import openml
18
+
19
+ OPENML_AVAILABLE = True
20
+ except ImportError:
21
+ OPENML_AVAILABLE = False
22
+ logger.warning("OpenML not available. Install with: pip install openml")
23
+
24
+
25
+ class OpenMLSuite:
26
+ """
27
+ OpenML benchmark suite for NAS evaluation.
28
+
29
+ Provides access to curated machine learning datasets from OpenML.
30
+
31
+ Example:
32
+ >>> suite = OpenMLSuite()
33
+ >>> task = suite.get_task(3) # CIFAR-10
34
+ >>> X_train, y_train, X_test, y_test = suite.load_task_data(task)
35
+ """
36
+
37
+ # Curated task IDs for benchmarking
38
+ BENCHMARK_TASKS = {
39
+ "mnist": 3573,
40
+ "fashion_mnist": 146825,
41
+ "cifar10": 167124,
42
+ "svhn": 168757,
43
+ }
44
+
45
+ def __init__(self):
46
+ """Initialize OpenML suite."""
47
+ if not OPENML_AVAILABLE:
48
+ raise ImportError(
49
+ "OpenML required for benchmark suite. " "Install with: pip install openml"
50
+ )
51
+
52
+ self.tasks = {}
53
+ logger.info("Initialized OpenML benchmark suite")
54
+
55
+ def get_task(self, task_id: int):
56
+ """
57
+ Get an OpenML task.
58
+
59
+ Args:
60
+ task_id: OpenML task ID
61
+
62
+ Returns:
63
+ OpenML task object
64
+ """
65
+ if task_id in self.tasks:
66
+ return self.tasks[task_id]
67
+
68
+ logger.info(f"Fetching OpenML task {task_id}...")
69
+ task = openml.tasks.get_task(task_id)
70
+ self.tasks[task_id] = task
71
+
72
+ return task
73
+
74
+ def get_task_by_name(self, name: str):
75
+ """
76
+ Get a benchmark task by name.
77
+
78
+ Args:
79
+ name: Task name (e.g., 'mnist', 'cifar10')
80
+
81
+ Returns:
82
+ OpenML task object
83
+ """
84
+ task_id = self.BENCHMARK_TASKS.get(name.lower())
85
+ if task_id is None:
86
+ raise ValueError(f"Unknown benchmark task: {name}")
87
+
88
+ return self.get_task(task_id)
89
+
90
+ def load_task_data(
91
+ self, task, normalize: bool = True
92
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
93
+ """
94
+ Load data for a task.
95
+
96
+ Args:
97
+ task: OpenML task object
98
+ normalize: Whether to normalize features
99
+
100
+ Returns:
101
+ Tuple of (X_train, y_train, X_test, y_test)
102
+ """
103
+ logger.info(f"Loading data for task {task.task_id}...")
104
+
105
+ dataset = task.get_dataset()
106
+ X, y, _, _ = dataset.get_data(target=task.target_name)
107
+
108
+ X = np.array(X)
109
+ y = np.array(y)
110
+
111
+ if normalize and X.dtype in [np.float32, np.float64]:
112
+ X = (X - X.mean(axis=0)) / (X.std(axis=0) + 1e-8)
113
+
114
+ # Split train/test (80/20)
115
+ split_idx = int(0.8 * len(X))
116
+ X_train, X_test = X[:split_idx], X[split_idx:]
117
+ y_train, y_test = y[:split_idx], y[split_idx:]
118
+
119
+ logger.info(f"Loaded task data: train={X_train.shape}, test={X_test.shape}")
120
+
121
+ return X_train, y_train, X_test, y_test
122
+
123
+ def list_benchmark_tasks(self) -> List[str]:
124
+ """List available benchmark tasks."""
125
+ return list(self.BENCHMARK_TASKS.keys())
126
+
127
+ def get_task_info(self, task_name: str) -> Dict:
128
+ """
129
+ Get information about a benchmark task.
130
+
131
+ Args:
132
+ task_name: Task name
133
+
134
+ Returns:
135
+ Dictionary with task metadata
136
+ """
137
+ task_id = self.BENCHMARK_TASKS.get(task_name)
138
+ if task_id is None:
139
+ return {}
140
+
141
+ try:
142
+ task = self.get_task(task_id)
143
+ dataset = task.get_dataset()
144
+
145
+ return {
146
+ "name": task_name,
147
+ "task_id": task_id,
148
+ "dataset_name": dataset.name,
149
+ "num_instances": dataset.qualities.get("NumberOfInstances"),
150
+ "num_features": dataset.qualities.get("NumberOfFeatures"),
151
+ "num_classes": dataset.qualities.get("NumberOfClasses"),
152
+ }
153
+ except Exception as e:
154
+ logger.error(f"Error fetching task info: {e}")
155
+ return {"name": task_name, "task_id": task_id, "error": str(e)}
156
+
157
+
158
+ def run_openml_benchmark(optimizer, task_name: str, evaluator, num_runs: int = 5) -> List[Dict]:
159
+ """
160
+ Run optimizer on an OpenML benchmark task.
161
+
162
+ Args:
163
+ optimizer: Optimizer instance
164
+ task_name: OpenML task name
165
+ evaluator: Evaluator function
166
+ num_runs: Number of independent runs
167
+
168
+ Returns:
169
+ List of result dictionaries
170
+ """
171
+ if not OPENML_AVAILABLE:
172
+ raise ImportError("OpenML required")
173
+
174
+ OpenMLSuite()
175
+
176
+ logger.info(f"Running benchmark: {task_name} with {num_runs} runs")
177
+
178
+ results = []
179
+
180
+ for run in range(num_runs):
181
+ logger.info(f"Run {run + 1}/{num_runs}")
182
+
183
+ # Reset optimizer for each run
184
+ if hasattr(optimizer, "reset"):
185
+ optimizer.reset()
186
+
187
+ # Run optimization
188
+ best = optimizer.optimize(evaluator)
189
+
190
+ result = {
191
+ "run": run + 1,
192
+ "task": task_name,
193
+ "best_fitness": best.fitness if hasattr(best, "fitness") else 0.0,
194
+ "history": optimizer.get_history() if hasattr(optimizer, "get_history") else [],
195
+ }
196
+
197
+ results.append(result)
198
+
199
+ logger.info(f"Benchmark complete: {task_name}")
200
+
201
+ return results
@@ -0,0 +1,289 @@
1
+ """Benchmark problems for optimizer evaluation."""
2
+
3
+ import math
4
+
5
+ from morphml.core.dsl import Layer, SearchSpace
6
+ from morphml.core.graph import ModelGraph
7
+
8
+
9
+ class BenchmarkProblem:
10
+ """Base class for benchmark problems."""
11
+
12
+ def __init__(self, name: str, search_space: SearchSpace):
13
+ """Initialize benchmark problem."""
14
+ self.name = name
15
+ self.search_space = search_space
16
+
17
+ def evaluate(self, graph: ModelGraph) -> float:
18
+ """Evaluate architecture on this problem."""
19
+ raise NotImplementedError
20
+
21
+ def get_optimal_fitness(self) -> float:
22
+ """Get known optimal fitness if available."""
23
+ return 1.0
24
+
25
+
26
+ class SimpleProblem(BenchmarkProblem):
27
+ """Simple benchmark problem."""
28
+
29
+ def __init__(self):
30
+ """Initialize simple problem."""
31
+ space = SearchSpace("simple_problem")
32
+ space.add_layers(
33
+ Layer.input(shape=(3, 32, 32)),
34
+ Layer.conv2d(filters=[32, 64], kernel_size=3),
35
+ Layer.relu(),
36
+ Layer.output(units=10),
37
+ )
38
+ super().__init__("SimpleProblem", space)
39
+
40
+ def evaluate(self, graph: ModelGraph) -> float:
41
+ """Simple evaluation based on node count."""
42
+ num_nodes = len(graph.nodes)
43
+ depth = graph.get_depth()
44
+
45
+ # Prefer moderate complexity
46
+ node_score = 1.0 / (1.0 + abs(num_nodes - 5))
47
+ depth_score = 1.0 / (1.0 + abs(depth - 4))
48
+
49
+ return 0.5 * node_score + 0.5 * depth_score
50
+
51
+
52
+ class ComplexProblem(BenchmarkProblem):
53
+ """Complex benchmark problem with multiple criteria."""
54
+
55
+ def __init__(self):
56
+ """Initialize complex problem."""
57
+ space = SearchSpace("complex_problem")
58
+ space.add_layers(
59
+ Layer.input(shape=(3, 224, 224)),
60
+ Layer.conv2d(filters=[32, 64, 128, 256], kernel_size=[3, 5, 7]),
61
+ Layer.relu(),
62
+ Layer.batchnorm(),
63
+ Layer.maxpool(pool_size=[2, 3]),
64
+ Layer.conv2d(filters=[64, 128, 256, 512], kernel_size=[3, 5]),
65
+ Layer.relu(),
66
+ Layer.batchnorm(),
67
+ Layer.maxpool(pool_size=2),
68
+ Layer.dense(units=[256, 512, 1024, 2048]),
69
+ Layer.dropout(rate=[0.3, 0.5, 0.7]),
70
+ Layer.dense(units=[128, 256, 512]),
71
+ Layer.output(units=1000),
72
+ )
73
+ super().__init__("ComplexProblem", space)
74
+
75
+ def evaluate(self, graph: ModelGraph) -> float:
76
+ """Complex evaluation considering multiple factors."""
77
+ num_nodes = len(graph.nodes)
78
+ depth = graph.get_depth()
79
+ width = graph.get_max_width()
80
+ params = graph.estimate_parameters()
81
+
82
+ # Multiple objectives
83
+ node_score = math.exp(-abs(num_nodes - 12) / 5.0)
84
+ depth_score = math.exp(-abs(depth - 8) / 3.0)
85
+ width_score = math.exp(-abs(width - 4) / 2.0)
86
+ param_score = 1.0 / (1.0 + params / 5000000.0)
87
+
88
+ # Weighted combination
89
+ score = 0.25 * node_score + 0.25 * depth_score + 0.25 * width_score + 0.25 * param_score
90
+
91
+ return min(1.0, max(0.0, score))
92
+
93
+
94
+ class MultiModalProblem(BenchmarkProblem):
95
+ """Multi-modal problem with multiple local optima."""
96
+
97
+ def __init__(self):
98
+ """Initialize multi-modal problem."""
99
+ space = SearchSpace("multimodal_problem")
100
+ space.add_layers(
101
+ Layer.input(shape=(3, 32, 32)),
102
+ Layer.conv2d(filters=[16, 32, 64, 128, 256], kernel_size=[3, 5, 7]),
103
+ Layer.relu(),
104
+ Layer.maxpool(pool_size=[2, 3]),
105
+ Layer.conv2d(filters=[32, 64, 128, 256], kernel_size=[3, 5]),
106
+ Layer.relu(),
107
+ Layer.dense(units=[64, 128, 256, 512, 1024]),
108
+ Layer.dropout(rate=[0.2, 0.3, 0.4, 0.5, 0.6]),
109
+ Layer.output(units=10),
110
+ )
111
+ super().__init__("MultiModalProblem", space)
112
+
113
+ def evaluate(self, graph: ModelGraph) -> float:
114
+ """Evaluation with multiple peaks."""
115
+ num_nodes = len(graph.nodes)
116
+ depth = graph.get_depth()
117
+
118
+ # Create multiple peaks
119
+ peak1 = math.exp(-((num_nodes - 6) ** 2 + (depth - 3) ** 2) / 4.0)
120
+ peak2 = 0.8 * math.exp(-((num_nodes - 9) ** 2 + (depth - 5) ** 2) / 3.0)
121
+ peak3 = 0.6 * math.exp(-((num_nodes - 12) ** 2 + (depth - 7) ** 2) / 5.0)
122
+
123
+ return max(peak1, peak2, peak3)
124
+
125
+
126
+ class ConstrainedProblem(BenchmarkProblem):
127
+ """Problem with hard constraints."""
128
+
129
+ def __init__(self):
130
+ """Initialize constrained problem."""
131
+ space = SearchSpace("constrained_problem")
132
+ space.add_layers(
133
+ Layer.input(shape=(3, 32, 32)),
134
+ Layer.conv2d(filters=[32, 64, 128], kernel_size=3),
135
+ Layer.relu(),
136
+ Layer.maxpool(pool_size=2),
137
+ Layer.conv2d(filters=[64, 128, 256], kernel_size=3),
138
+ Layer.relu(),
139
+ Layer.dense(units=[128, 256, 512]),
140
+ Layer.output(units=10),
141
+ )
142
+
143
+ # Add constraints
144
+ def max_depth_constraint(g):
145
+ return g.get_depth() <= 8
146
+
147
+ def max_params_constraint(g):
148
+ return g.estimate_parameters() <= 1000000
149
+
150
+ space.add_constraint(max_depth_constraint)
151
+ space.add_constraint(max_params_constraint)
152
+
153
+ super().__init__("ConstrainedProblem", space)
154
+
155
+ def evaluate(self, graph: ModelGraph) -> float:
156
+ """Evaluation with penalty for constraint violation."""
157
+ # Check constraints
158
+ depth = graph.get_depth()
159
+ params = graph.estimate_parameters()
160
+
161
+ penalty = 0.0
162
+
163
+ if depth > 8:
164
+ penalty += 0.5 * (depth - 8) / 8.0
165
+
166
+ if params > 1000000:
167
+ penalty += 0.5 * (params - 1000000) / 1000000.0
168
+
169
+ # Base fitness
170
+ base_fitness = 0.5 + 0.05 * len(graph.nodes)
171
+
172
+ # Apply penalty
173
+ return max(0.0, base_fitness - penalty)
174
+
175
+
176
+ class NoisyProblem(BenchmarkProblem):
177
+ """Problem with noisy evaluations."""
178
+
179
+ def __init__(self, noise_level: float = 0.1):
180
+ """Initialize noisy problem."""
181
+ space = SearchSpace("noisy_problem")
182
+ space.add_layers(
183
+ Layer.input(shape=(3, 32, 32)),
184
+ Layer.conv2d(filters=[32, 64], kernel_size=3),
185
+ Layer.relu(),
186
+ Layer.maxpool(pool_size=2),
187
+ Layer.dense(units=[128, 256]),
188
+ Layer.output(units=10),
189
+ )
190
+ super().__init__("NoisyProblem", space)
191
+ self.noise_level = noise_level
192
+
193
+ def evaluate(self, graph: ModelGraph) -> float:
194
+ """Evaluation with added noise."""
195
+ import random
196
+
197
+ # Base evaluation
198
+ num_nodes = len(graph.nodes)
199
+ base_fitness = 0.6 + 0.04 * num_nodes
200
+
201
+ # Add noise
202
+ noise = random.gauss(0, self.noise_level)
203
+ noisy_fitness = base_fitness + noise
204
+
205
+ return max(0.0, min(1.0, noisy_fitness))
206
+
207
+
208
+ class RastriginProblem(BenchmarkProblem):
209
+ """Rastrigin-like function adapted for graphs."""
210
+
211
+ def __init__(self):
212
+ """Initialize Rastrigin problem."""
213
+ space = SearchSpace("rastrigin_problem")
214
+ space.add_layers(
215
+ Layer.input(shape=(3, 32, 32)),
216
+ Layer.conv2d(filters=[16, 32, 64, 128], kernel_size=[3, 5, 7]),
217
+ Layer.relu(),
218
+ Layer.maxpool(pool_size=[2, 3]),
219
+ Layer.dense(units=[64, 128, 256, 512]),
220
+ Layer.output(units=10),
221
+ )
222
+ super().__init__("RastriginProblem", space)
223
+
224
+ def evaluate(self, graph: ModelGraph) -> float:
225
+ """Rastrigin-like evaluation."""
226
+ num_nodes = len(graph.nodes)
227
+ depth = graph.get_depth()
228
+
229
+ # Rastrigin-like function
230
+ A = 10
231
+ x = (num_nodes - 8) / 4.0
232
+ y = (depth - 5) / 3.0
233
+
234
+ value = 2 * A
235
+ value += x**2 - A * math.cos(2 * math.pi * x)
236
+ value += y**2 - A * math.cos(2 * math.pi * y)
237
+
238
+ # Convert to fitness (0-1 range, higher is better)
239
+ fitness = 1.0 / (1.0 + value / 20.0)
240
+
241
+ return fitness
242
+
243
+
244
+ class RosenbrockProblem(BenchmarkProblem):
245
+ """Rosenbrock-like function adapted for graphs."""
246
+
247
+ def __init__(self):
248
+ """Initialize Rosenbrock problem."""
249
+ space = SearchSpace("rosenbrock_problem")
250
+ space.add_layers(
251
+ Layer.input(shape=(3, 32, 32)),
252
+ Layer.conv2d(filters=[32, 64, 128], kernel_size=[3, 5]),
253
+ Layer.relu(),
254
+ Layer.dense(units=[128, 256, 512]),
255
+ Layer.output(units=10),
256
+ )
257
+ super().__init__("RosenbrockProblem", space)
258
+
259
+ def evaluate(self, graph: ModelGraph) -> float:
260
+ """Rosenbrock-like evaluation."""
261
+ num_nodes = len(graph.nodes)
262
+ depth = graph.get_depth()
263
+
264
+ # Rosenbrock-like function
265
+ a = 1
266
+ b = 100
267
+
268
+ x = (num_nodes - 7) / 3.0
269
+ y = (depth - 4) / 2.0
270
+
271
+ value = (a - x) ** 2 + b * (y - x**2) ** 2
272
+
273
+ # Convert to fitness
274
+ fitness = 1.0 / (1.0 + value / 50.0)
275
+
276
+ return fitness
277
+
278
+
279
+ def get_all_problems() -> list:
280
+ """Get all benchmark problems."""
281
+ return [
282
+ SimpleProblem(),
283
+ ComplexProblem(),
284
+ MultiModalProblem(),
285
+ ConstrainedProblem(),
286
+ NoisyProblem(noise_level=0.1),
287
+ RastriginProblem(),
288
+ RosenbrockProblem(),
289
+ ]