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,364 @@
1
+ """Abstract Syntax Tree node definitions for MorphML DSL.
2
+
3
+ Defines the structure of the AST generated by the parser.
4
+
5
+ Author: Eshan Roy <eshanized@proton.me>
6
+ Organization: TONMOY INFRASTRUCTURE & VISION
7
+ """
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from morphml.core.dsl.syntax import EVOLUTION_STRATEGIES, LAYER_TYPES
13
+ from morphml.exceptions import ValidationError
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class ASTNode:
18
+ """
19
+ Base class for all AST nodes.
20
+
21
+ Uses visitor pattern for traversal and transformation.
22
+ Immutable to prevent accidental modification during compilation.
23
+ """
24
+
25
+ def accept(self, visitor: "ASTVisitor") -> Any:
26
+ """
27
+ Visitor pattern support for AST traversal.
28
+
29
+ Args:
30
+ visitor: Visitor instance
31
+
32
+ Returns:
33
+ Result from visitor
34
+
35
+ Raises:
36
+ NotImplementedError: If subclass doesn't implement
37
+ """
38
+ raise NotImplementedError(f"{self.__class__.__name__} must implement accept()")
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class ParamNode(ASTNode):
43
+ """
44
+ Represents a hyperparameter specification.
45
+
46
+ Can represent:
47
+ - Single value: filters=32
48
+ - List of values: filters=[32, 64, 128]
49
+ - Range: units=(64, 512)
50
+
51
+ Attributes:
52
+ name: Parameter name
53
+ values: List of possible values
54
+ param_type: Type ('categorical', 'integer', 'float', 'boolean')
55
+ """
56
+
57
+ name: str
58
+ values: List[Any]
59
+ param_type: Optional[str] = None
60
+
61
+ def __post_init__(self) -> None:
62
+ """Validate and infer parameter type."""
63
+ if not self.values:
64
+ raise ValidationError(f"Parameter '{self.name}' has no values")
65
+
66
+ # Infer type if not specified
67
+ if self.param_type is None:
68
+ object.__setattr__(self, "param_type", self._infer_type())
69
+
70
+ def _infer_type(self) -> str:
71
+ """Infer parameter type from values."""
72
+ if len(self.values) == 1:
73
+ value = self.values[0]
74
+ if isinstance(value, bool):
75
+ return "boolean"
76
+ elif isinstance(value, int):
77
+ return "integer"
78
+ elif isinstance(value, float):
79
+ return "float"
80
+ elif isinstance(value, str):
81
+ return "categorical"
82
+ else:
83
+ # Multiple values - check consistency
84
+ if all(isinstance(v, bool) for v in self.values):
85
+ return "boolean"
86
+ elif all(isinstance(v, int) for v in self.values):
87
+ return "integer"
88
+ elif all(isinstance(v, (int, float)) for v in self.values):
89
+ return "float"
90
+ else:
91
+ return "categorical"
92
+
93
+ return "categorical"
94
+
95
+ def accept(self, visitor: "ASTVisitor") -> Any:
96
+ """Accept visitor."""
97
+ return visitor.visit_param(self)
98
+
99
+ def __repr__(self) -> str:
100
+ """Readable representation."""
101
+ return f"ParamNode({self.name}={self.values})"
102
+
103
+
104
+ @dataclass(frozen=True)
105
+ class LayerNode(ASTNode):
106
+ """
107
+ Represents a layer specification in the search space.
108
+
109
+ Attributes:
110
+ layer_type: Type of layer ('conv2d', 'dense', etc.)
111
+ params: Dictionary of parameter specifications
112
+ metadata: Additional metadata
113
+
114
+ Example:
115
+ LayerNode('conv2d', {'filters': ParamNode('filters', [32, 64])})
116
+ """
117
+
118
+ layer_type: str
119
+ params: Dict[str, ParamNode] = field(default_factory=dict)
120
+ metadata: Dict[str, Any] = field(default_factory=dict)
121
+
122
+ def __post_init__(self) -> None:
123
+ """Validate layer type."""
124
+ if self.layer_type not in LAYER_TYPES:
125
+ raise ValidationError(
126
+ f"Unknown layer type: '{self.layer_type}'. "
127
+ f"Valid types: {', '.join(LAYER_TYPES)}"
128
+ )
129
+
130
+ def accept(self, visitor: "ASTVisitor") -> Any:
131
+ """Accept visitor."""
132
+ return visitor.visit_layer(self)
133
+
134
+ def get_param(self, name: str, default: Any = None) -> Optional[ParamNode]:
135
+ """Get parameter by name."""
136
+ return self.params.get(name, default)
137
+
138
+ def __repr__(self) -> str:
139
+ """Readable representation."""
140
+ param_strs = [f"{k}={v.values}" for k, v in self.params.items()]
141
+ return f"Layer.{self.layer_type}({', '.join(param_strs)})"
142
+
143
+
144
+ @dataclass(frozen=True)
145
+ class SearchSpaceNode(ASTNode):
146
+ """
147
+ Represents a complete search space definition.
148
+
149
+ Attributes:
150
+ layers: List of layer specifications
151
+ global_params: Global parameters (optimizer, LR, etc.)
152
+ name: Optional name for the search space
153
+ metadata: Additional metadata
154
+ """
155
+
156
+ layers: List[LayerNode]
157
+ global_params: Dict[str, ParamNode] = field(default_factory=dict)
158
+ name: Optional[str] = None
159
+ metadata: Dict[str, Any] = field(default_factory=dict)
160
+
161
+ def __post_init__(self) -> None:
162
+ """Validate search space."""
163
+ if not self.layers:
164
+ raise ValidationError("SearchSpace must contain at least one layer")
165
+
166
+ def accept(self, visitor: "ASTVisitor") -> Any:
167
+ """Accept visitor."""
168
+ return visitor.visit_search_space(self)
169
+
170
+ def __repr__(self) -> str:
171
+ """Readable representation."""
172
+ return (
173
+ f"SearchSpace(name={self.name}, "
174
+ f"layers={len(self.layers)}, "
175
+ f"params={list(self.global_params.keys())})"
176
+ )
177
+
178
+
179
+ @dataclass(frozen=True)
180
+ class EvolutionNode(ASTNode):
181
+ """
182
+ Represents evolution/optimization configuration.
183
+
184
+ Attributes:
185
+ strategy: Evolution strategy ('genetic', 'bayesian', etc.)
186
+ params: Strategy parameters
187
+ metadata: Additional metadata
188
+ """
189
+
190
+ strategy: str
191
+ params: Dict[str, Any] = field(default_factory=dict)
192
+ metadata: Dict[str, Any] = field(default_factory=dict)
193
+
194
+ def __post_init__(self) -> None:
195
+ """Validate evolution strategy."""
196
+ if self.strategy not in EVOLUTION_STRATEGIES:
197
+ raise ValidationError(
198
+ f"Unknown evolution strategy: '{self.strategy}'. "
199
+ f"Valid strategies: {', '.join(EVOLUTION_STRATEGIES)}"
200
+ )
201
+
202
+ def accept(self, visitor: "ASTVisitor") -> Any:
203
+ """Accept visitor."""
204
+ return visitor.visit_evolution(self)
205
+
206
+ def get_param(self, name: str, default: Any = None) -> Any:
207
+ """Get parameter by name."""
208
+ return self.params.get(name, default)
209
+
210
+ def __repr__(self) -> str:
211
+ """Readable representation."""
212
+ return f"Evolution(strategy={self.strategy}, params={list(self.params.keys())})"
213
+
214
+
215
+ @dataclass(frozen=True)
216
+ class ConstraintNode(ASTNode):
217
+ """
218
+ Represents a constraint on the search space.
219
+
220
+ Attributes:
221
+ constraint_type: Type of constraint ('max_depth', 'max_params', etc.)
222
+ params: Constraint parameters
223
+ """
224
+
225
+ constraint_type: str
226
+ params: Dict[str, Any] = field(default_factory=dict)
227
+
228
+ def accept(self, visitor: "ASTVisitor") -> Any:
229
+ """Accept visitor."""
230
+ return visitor.visit_constraint(self)
231
+
232
+ def __repr__(self) -> str:
233
+ """Readable representation."""
234
+ return f"Constraint({self.constraint_type}, {self.params})"
235
+
236
+
237
+ @dataclass(frozen=True)
238
+ class ExperimentNode(ASTNode):
239
+ """
240
+ Root node representing a complete experiment definition.
241
+
242
+ Attributes:
243
+ search_space: Search space specification
244
+ evolution: Evolution configuration
245
+ objectives: List of objectives to optimize
246
+ constraints: List of constraints
247
+ metadata: Additional metadata
248
+ """
249
+
250
+ search_space: SearchSpaceNode
251
+ evolution: Optional[EvolutionNode] = None
252
+ objectives: List[str] = field(default_factory=list)
253
+ constraints: List[ConstraintNode] = field(default_factory=list)
254
+ metadata: Dict[str, Any] = field(default_factory=dict)
255
+
256
+ def accept(self, visitor: "ASTVisitor") -> Any:
257
+ """Accept visitor."""
258
+ return visitor.visit_experiment(self)
259
+
260
+ def __repr__(self) -> str:
261
+ """Readable representation."""
262
+ return (
263
+ f"Experiment(\n"
264
+ f" search_space={self.search_space},\n"
265
+ f" evolution={self.evolution},\n"
266
+ f" objectives={self.objectives}\n"
267
+ f")"
268
+ )
269
+
270
+
271
+ # Visitor interface for AST traversal
272
+ class ASTVisitor:
273
+ """
274
+ Base class for AST visitors.
275
+
276
+ Implements the visitor pattern for traversing and transforming AST.
277
+ Subclass this to implement custom operations on the AST.
278
+
279
+ Example:
280
+ class PrintVisitor(ASTVisitor):
281
+ def visit_layer(self, node: LayerNode):
282
+ print(f"Layer: {node.layer_type}")
283
+ """
284
+
285
+ def visit_param(self, node: ParamNode) -> Any:
286
+ """Visit ParamNode."""
287
+ pass
288
+
289
+ def visit_layer(self, node: LayerNode) -> Any:
290
+ """Visit LayerNode."""
291
+ for param in node.params.values():
292
+ param.accept(self)
293
+
294
+ def visit_search_space(self, node: SearchSpaceNode) -> Any:
295
+ """Visit SearchSpaceNode."""
296
+ for layer in node.layers:
297
+ layer.accept(self)
298
+ for param in node.global_params.values():
299
+ param.accept(self)
300
+
301
+ def visit_evolution(self, node: EvolutionNode) -> Any:
302
+ """Visit EvolutionNode."""
303
+ pass
304
+
305
+ def visit_constraint(self, node: ConstraintNode) -> Any:
306
+ """Visit ConstraintNode."""
307
+ pass
308
+
309
+ def visit_experiment(self, node: ExperimentNode) -> Any:
310
+ """Visit ExperimentNode."""
311
+ node.search_space.accept(self)
312
+ if node.evolution:
313
+ node.evolution.accept(self)
314
+ for constraint in node.constraints:
315
+ constraint.accept(self)
316
+
317
+
318
+ # Helper functions for AST manipulation
319
+ def walk_ast(node: ASTNode, callback: callable) -> None:
320
+ """
321
+ Walk the AST and call callback for each node.
322
+
323
+ Args:
324
+ node: Root node to start from
325
+ callback: Function to call with each node
326
+ """
327
+
328
+ class WalkVisitor(ASTVisitor):
329
+ def visit_param(self, n: ParamNode) -> None:
330
+ callback(n)
331
+
332
+ def visit_layer(self, n: LayerNode) -> None:
333
+ callback(n)
334
+ super().visit_layer(n)
335
+
336
+ def visit_search_space(self, n: SearchSpaceNode) -> None:
337
+ callback(n)
338
+ super().visit_search_space(n)
339
+
340
+ def visit_evolution(self, n: EvolutionNode) -> None:
341
+ callback(n)
342
+
343
+ def visit_constraint(self, n: ConstraintNode) -> None:
344
+ callback(n)
345
+
346
+ def visit_experiment(self, n: ExperimentNode) -> None:
347
+ callback(n)
348
+ super().visit_experiment(n)
349
+
350
+ visitor = WalkVisitor()
351
+ node.accept(visitor)
352
+
353
+
354
+ def count_layers(ast: ASTNode) -> int:
355
+ """Count number of layers in AST."""
356
+ count = 0
357
+
358
+ def counter(node: ASTNode) -> None:
359
+ nonlocal count
360
+ if isinstance(node, LayerNode):
361
+ count += 1
362
+
363
+ walk_ast(ast, counter)
364
+ return count
@@ -0,0 +1,318 @@
1
+ """Compiler for MorphML DSL.
2
+
3
+ Compiles Abstract Syntax Tree into executable internal representation.
4
+
5
+ Author: Eshan Roy <eshanized@proton.me>
6
+ Organization: TONMOY INFRASTRUCTURE & VISION
7
+ """
8
+
9
+ from typing import Any, Dict, List
10
+
11
+ from morphml.core.dsl.ast_nodes import (
12
+ ConstraintNode,
13
+ EvolutionNode,
14
+ ExperimentNode,
15
+ LayerNode,
16
+ ParamNode,
17
+ SearchSpaceNode,
18
+ )
19
+ from morphml.core.dsl.layers import LayerSpec
20
+ from morphml.core.dsl.search_space import SearchSpace
21
+ from morphml.logging_config import get_logger
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class Compiler:
27
+ """
28
+ Compiles AST into internal representation.
29
+
30
+ Transforms the abstract syntax tree into executable objects:
31
+ - ParamNode → parameter ranges
32
+ - LayerNode → LayerSpec
33
+ - SearchSpaceNode → SearchSpace
34
+ - EvolutionNode → optimizer configuration
35
+
36
+ Example:
37
+ >>> compiler = Compiler()
38
+ >>> result = compiler.compile(ast)
39
+ >>> search_space = result['search_space']
40
+ """
41
+
42
+ def __init__(self) -> None:
43
+ """Initialize compiler."""
44
+ self.symbol_table: Dict[str, Any] = {}
45
+
46
+ def compile(self, ast: ExperimentNode) -> Dict[str, Any]:
47
+ """
48
+ Compile complete experiment AST.
49
+
50
+ Args:
51
+ ast: ExperimentNode from parser
52
+
53
+ Returns:
54
+ Dictionary with 'search_space', 'evolution', 'constraints'
55
+
56
+ Raises:
57
+ ValidationError: If compilation fails
58
+ """
59
+ logger.info("Starting compilation")
60
+
61
+ # Compile search space
62
+ search_space = self._compile_search_space(ast.search_space)
63
+
64
+ # Compile evolution config
65
+ evolution_config = None
66
+ if ast.evolution:
67
+ evolution_config = self._compile_evolution(ast.evolution)
68
+
69
+ # Compile constraints
70
+ constraints = [self._compile_constraint(c) for c in ast.constraints]
71
+
72
+ logger.info(
73
+ f"Compilation complete: {len(search_space.layers)} layers, "
74
+ f"{len(constraints)} constraints"
75
+ )
76
+
77
+ return {
78
+ "search_space": search_space,
79
+ "evolution": evolution_config,
80
+ "constraints": constraints,
81
+ "objectives": ast.objectives,
82
+ }
83
+
84
+ def _compile_search_space(self, node: SearchSpaceNode) -> SearchSpace:
85
+ """
86
+ Convert SearchSpaceNode to SearchSpace object.
87
+
88
+ Args:
89
+ node: SearchSpaceNode from AST
90
+
91
+ Returns:
92
+ SearchSpace instance
93
+ """
94
+ # Compile layers
95
+ layer_specs = [self._compile_layer(layer_node) for layer_node in node.layers]
96
+
97
+ # Create search space
98
+ search_space = SearchSpace(name=node.name or "compiled_space")
99
+
100
+ # Add layers
101
+ for spec in layer_specs:
102
+ search_space.layers.append(spec)
103
+
104
+ # Add global parameters if any
105
+ for param_name, param_node in node.global_params.items():
106
+ search_space.metadata[param_name] = self._compile_param_values(param_node)
107
+
108
+ return search_space
109
+
110
+ def _compile_layer(self, node: LayerNode) -> LayerSpec:
111
+ """
112
+ Convert LayerNode to LayerSpec.
113
+
114
+ Args:
115
+ node: LayerNode from AST
116
+
117
+ Returns:
118
+ LayerSpec instance
119
+ """
120
+ # Compile parameters
121
+ param_ranges: Dict[str, Any] = {}
122
+
123
+ for param_name, param_node in node.params.items():
124
+ param_ranges[param_name] = self._compile_param_values(param_node)
125
+
126
+ # Create LayerSpec
127
+ return LayerSpec(
128
+ operation=node.layer_type, param_ranges=param_ranges, metadata=node.metadata.copy()
129
+ )
130
+
131
+ def _compile_param_values(self, param_node: ParamNode) -> List[Any]:
132
+ """
133
+ Compile parameter values from ParamNode.
134
+
135
+ Args:
136
+ param_node: ParamNode from AST
137
+
138
+ Returns:
139
+ List of values (for sampling)
140
+ """
141
+ return param_node.values
142
+
143
+ def _compile_evolution(self, node: EvolutionNode) -> Dict[str, Any]:
144
+ """
145
+ Convert EvolutionNode to optimizer configuration.
146
+
147
+ Args:
148
+ node: EvolutionNode from AST
149
+
150
+ Returns:
151
+ Configuration dictionary
152
+ """
153
+ config = {"optimizer_type": node.strategy}
154
+
155
+ # Map DSL parameter names to internal names
156
+ param_mapping = {
157
+ "population_size": "population_size",
158
+ "num_generations": "num_generations",
159
+ "mutation_rate": "mutation_rate",
160
+ "crossover_rate": "crossover_rate",
161
+ "elite_size": "elitism",
162
+ "elitism": "elitism",
163
+ "selection": "selection_method",
164
+ "selection_strategy": "selection_method",
165
+ "tournament_size": "tournament_size",
166
+ "max_evaluations": "max_evaluations",
167
+ "early_stopping": "early_stopping_patience",
168
+ }
169
+
170
+ # Compile parameters
171
+ for param_name, param_value in node.params.items():
172
+ # Get internal parameter name
173
+ internal_name = param_mapping.get(param_name, param_name)
174
+
175
+ # Handle list values (take first if single element)
176
+ if isinstance(param_value, list):
177
+ if len(param_value) == 1:
178
+ param_value = param_value[0]
179
+
180
+ config[internal_name] = param_value
181
+
182
+ return config
183
+
184
+ def _compile_constraint(self, node: ConstraintNode) -> Dict[str, Any]:
185
+ """
186
+ Convert ConstraintNode to constraint configuration.
187
+
188
+ Args:
189
+ node: ConstraintNode from AST
190
+
191
+ Returns:
192
+ Constraint configuration dictionary
193
+ """
194
+ return {"type": node.constraint_type, "params": node.params.copy()}
195
+
196
+
197
+ class CompilationContext:
198
+ """
199
+ Context for compilation process.
200
+
201
+ Tracks symbols, types, and other compilation state.
202
+ """
203
+
204
+ def __init__(self) -> None:
205
+ """Initialize context."""
206
+ self.symbols: Dict[str, Any] = {}
207
+ self.errors: List[str] = []
208
+
209
+ def add_symbol(self, name: str, value: Any) -> None:
210
+ """Add symbol to context."""
211
+ self.symbols[name] = value
212
+
213
+ def get_symbol(self, name: str) -> Any:
214
+ """Get symbol from context."""
215
+ return self.symbols.get(name)
216
+
217
+ def add_error(self, message: str) -> None:
218
+ """Record compilation error."""
219
+ self.errors.append(message)
220
+
221
+ def has_errors(self) -> bool:
222
+ """Check if any errors occurred."""
223
+ return len(self.errors) > 0
224
+
225
+
226
+ def compile_dsl(source: str) -> Dict[str, Any]:
227
+ """
228
+ Convenience function to parse and compile DSL source.
229
+
230
+ Args:
231
+ source: DSL source code
232
+
233
+ Returns:
234
+ Compiled representation
235
+
236
+ Example:
237
+ >>> source = '''
238
+ ... SearchSpace(
239
+ ... layers=[
240
+ ... Layer.conv2d(filters=[32, 64], kernel_size=3),
241
+ ... Layer.relu()
242
+ ... ]
243
+ ... )
244
+ ... Evolution(strategy="genetic", population_size=50)
245
+ ... '''
246
+ >>> result = compile_dsl(source)
247
+ >>> search_space = result['search_space']
248
+ >>> config = result['evolution']
249
+ """
250
+ from morphml.core.dsl.parser import parse_dsl
251
+
252
+ # Parse source to AST
253
+ ast = parse_dsl(source)
254
+
255
+ # Compile AST
256
+ compiler = Compiler()
257
+ return compiler.compile(ast)
258
+
259
+
260
+ def compile_to_search_space(ast: ExperimentNode) -> SearchSpace:
261
+ """
262
+ Compile AST directly to SearchSpace.
263
+
264
+ Args:
265
+ ast: ExperimentNode
266
+
267
+ Returns:
268
+ SearchSpace instance
269
+ """
270
+ compiler = Compiler()
271
+ result = compiler.compile(ast)
272
+ return result["search_space"]
273
+
274
+
275
+ # Type inference helpers
276
+ def infer_parameter_type(values: List[Any]) -> str:
277
+ """
278
+ Infer parameter type from values.
279
+
280
+ Args:
281
+ values: List of parameter values
282
+
283
+ Returns:
284
+ Type string ('categorical', 'integer', 'float', 'boolean')
285
+ """
286
+ if not values:
287
+ return "categorical"
288
+
289
+ if all(isinstance(v, bool) for v in values):
290
+ return "boolean"
291
+ elif all(isinstance(v, int) for v in values):
292
+ return "integer"
293
+ elif all(isinstance(v, (int, float)) for v in values):
294
+ return "float"
295
+ else:
296
+ return "categorical"
297
+
298
+
299
+ def validate_parameter_range(param_type: str, values: List[Any]) -> bool:
300
+ """
301
+ Validate that parameter values match declared type.
302
+
303
+ Args:
304
+ param_type: Parameter type
305
+ values: Parameter values
306
+
307
+ Returns:
308
+ True if valid
309
+ """
310
+ if param_type == "boolean":
311
+ return all(isinstance(v, bool) for v in values)
312
+ elif param_type == "integer":
313
+ return all(isinstance(v, int) for v in values)
314
+ elif param_type == "float":
315
+ return all(isinstance(v, (int, float)) for v in values)
316
+ elif param_type == "categorical":
317
+ return True # Any type allowed
318
+ return False