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.
- morphml/__init__.py +14 -0
- morphml/api/__init__.py +26 -0
- morphml/api/app.py +326 -0
- morphml/api/auth.py +193 -0
- morphml/api/client.py +338 -0
- morphml/api/models.py +132 -0
- morphml/api/rate_limit.py +192 -0
- morphml/benchmarking/__init__.py +36 -0
- morphml/benchmarking/comparison.py +430 -0
- morphml/benchmarks/__init__.py +56 -0
- morphml/benchmarks/comparator.py +409 -0
- morphml/benchmarks/datasets.py +280 -0
- morphml/benchmarks/metrics.py +199 -0
- morphml/benchmarks/openml_suite.py +201 -0
- morphml/benchmarks/problems.py +289 -0
- morphml/benchmarks/suite.py +318 -0
- morphml/cli/__init__.py +5 -0
- morphml/cli/commands/experiment.py +329 -0
- morphml/cli/main.py +457 -0
- morphml/cli/quickstart.py +312 -0
- morphml/config.py +278 -0
- morphml/constraints/__init__.py +19 -0
- morphml/constraints/handler.py +205 -0
- morphml/constraints/predicates.py +285 -0
- morphml/core/__init__.py +3 -0
- morphml/core/crossover.py +449 -0
- morphml/core/dsl/README.md +359 -0
- morphml/core/dsl/__init__.py +72 -0
- morphml/core/dsl/ast_nodes.py +364 -0
- morphml/core/dsl/compiler.py +318 -0
- morphml/core/dsl/layers.py +368 -0
- morphml/core/dsl/lexer.py +336 -0
- morphml/core/dsl/parser.py +455 -0
- morphml/core/dsl/search_space.py +386 -0
- morphml/core/dsl/syntax.py +199 -0
- morphml/core/dsl/type_system.py +361 -0
- morphml/core/dsl/validator.py +386 -0
- morphml/core/graph/__init__.py +40 -0
- morphml/core/graph/edge.py +124 -0
- morphml/core/graph/graph.py +507 -0
- morphml/core/graph/mutations.py +409 -0
- morphml/core/graph/node.py +196 -0
- morphml/core/graph/serialization.py +361 -0
- morphml/core/graph/visualization.py +431 -0
- morphml/core/objectives/__init__.py +20 -0
- morphml/core/search/__init__.py +33 -0
- morphml/core/search/individual.py +252 -0
- morphml/core/search/parameters.py +453 -0
- morphml/core/search/population.py +375 -0
- morphml/core/search/search_engine.py +340 -0
- morphml/distributed/__init__.py +76 -0
- morphml/distributed/fault_tolerance.py +497 -0
- morphml/distributed/health_monitor.py +348 -0
- morphml/distributed/master.py +709 -0
- morphml/distributed/proto/README.md +224 -0
- morphml/distributed/proto/__init__.py +74 -0
- morphml/distributed/proto/worker.proto +170 -0
- morphml/distributed/proto/worker_pb2.py +79 -0
- morphml/distributed/proto/worker_pb2_grpc.py +423 -0
- morphml/distributed/resource_manager.py +416 -0
- morphml/distributed/scheduler.py +567 -0
- morphml/distributed/storage/__init__.py +33 -0
- morphml/distributed/storage/artifacts.py +381 -0
- morphml/distributed/storage/cache.py +366 -0
- morphml/distributed/storage/checkpointing.py +329 -0
- morphml/distributed/storage/database.py +459 -0
- morphml/distributed/worker.py +549 -0
- morphml/evaluation/__init__.py +5 -0
- morphml/evaluation/heuristic.py +237 -0
- morphml/exceptions.py +55 -0
- morphml/execution/__init__.py +5 -0
- morphml/execution/local_executor.py +350 -0
- morphml/integrations/__init__.py +28 -0
- morphml/integrations/jax_adapter.py +206 -0
- morphml/integrations/pytorch_adapter.py +530 -0
- morphml/integrations/sklearn_adapter.py +206 -0
- morphml/integrations/tensorflow_adapter.py +230 -0
- morphml/logging_config.py +93 -0
- morphml/meta_learning/__init__.py +66 -0
- morphml/meta_learning/architecture_similarity.py +277 -0
- morphml/meta_learning/experiment_database.py +240 -0
- morphml/meta_learning/knowledge_base/__init__.py +19 -0
- morphml/meta_learning/knowledge_base/embedder.py +179 -0
- morphml/meta_learning/knowledge_base/knowledge_base.py +313 -0
- morphml/meta_learning/knowledge_base/meta_features.py +265 -0
- morphml/meta_learning/knowledge_base/vector_store.py +271 -0
- morphml/meta_learning/predictors/__init__.py +27 -0
- morphml/meta_learning/predictors/ensemble.py +221 -0
- morphml/meta_learning/predictors/gnn_predictor.py +552 -0
- morphml/meta_learning/predictors/learning_curve.py +231 -0
- morphml/meta_learning/predictors/proxy_metrics.py +261 -0
- morphml/meta_learning/strategy_evolution/__init__.py +27 -0
- morphml/meta_learning/strategy_evolution/adaptive_optimizer.py +226 -0
- morphml/meta_learning/strategy_evolution/bandit.py +276 -0
- morphml/meta_learning/strategy_evolution/portfolio.py +230 -0
- morphml/meta_learning/transfer.py +581 -0
- morphml/meta_learning/warm_start.py +286 -0
- morphml/optimizers/__init__.py +74 -0
- morphml/optimizers/adaptive_operators.py +399 -0
- morphml/optimizers/bayesian/__init__.py +52 -0
- morphml/optimizers/bayesian/acquisition.py +387 -0
- morphml/optimizers/bayesian/base.py +319 -0
- morphml/optimizers/bayesian/gaussian_process.py +635 -0
- morphml/optimizers/bayesian/smac.py +534 -0
- morphml/optimizers/bayesian/tpe.py +411 -0
- morphml/optimizers/differential_evolution.py +220 -0
- morphml/optimizers/evolutionary/__init__.py +61 -0
- morphml/optimizers/evolutionary/cma_es.py +416 -0
- morphml/optimizers/evolutionary/differential_evolution.py +556 -0
- morphml/optimizers/evolutionary/encoding.py +426 -0
- morphml/optimizers/evolutionary/particle_swarm.py +449 -0
- morphml/optimizers/genetic_algorithm.py +486 -0
- morphml/optimizers/gradient_based/__init__.py +22 -0
- morphml/optimizers/gradient_based/darts.py +550 -0
- morphml/optimizers/gradient_based/enas.py +585 -0
- morphml/optimizers/gradient_based/operations.py +474 -0
- morphml/optimizers/gradient_based/utils.py +601 -0
- morphml/optimizers/hill_climbing.py +169 -0
- morphml/optimizers/multi_objective/__init__.py +56 -0
- morphml/optimizers/multi_objective/indicators.py +504 -0
- morphml/optimizers/multi_objective/nsga2.py +647 -0
- morphml/optimizers/multi_objective/visualization.py +427 -0
- morphml/optimizers/nsga2.py +308 -0
- morphml/optimizers/random_search.py +172 -0
- morphml/optimizers/simulated_annealing.py +181 -0
- morphml/plugins/__init__.py +35 -0
- morphml/plugins/custom_evaluator_example.py +81 -0
- morphml/plugins/custom_optimizer_example.py +63 -0
- morphml/plugins/plugin_system.py +454 -0
- morphml/reports/__init__.py +30 -0
- morphml/reports/generator.py +362 -0
- morphml/tracking/__init__.py +7 -0
- morphml/tracking/experiment.py +309 -0
- morphml/tracking/logger.py +301 -0
- morphml/tracking/reporter.py +357 -0
- morphml/utils/__init__.py +6 -0
- morphml/utils/checkpoint.py +189 -0
- morphml/utils/comparison.py +390 -0
- morphml/utils/export.py +407 -0
- morphml/utils/progress.py +392 -0
- morphml/utils/validation.py +392 -0
- morphml/version.py +7 -0
- morphml/visualization/__init__.py +50 -0
- morphml/visualization/analytics.py +423 -0
- morphml/visualization/architecture_diagrams.py +353 -0
- morphml/visualization/architecture_plot.py +223 -0
- morphml/visualization/convergence_plot.py +174 -0
- morphml/visualization/crossover_viz.py +386 -0
- morphml/visualization/graph_viz.py +338 -0
- morphml/visualization/pareto_plot.py +149 -0
- morphml/visualization/plotly_dashboards.py +422 -0
- morphml/visualization/population.py +309 -0
- morphml/visualization/progress.py +260 -0
- morphml-1.0.0.dist-info/METADATA +434 -0
- morphml-1.0.0.dist-info/RECORD +158 -0
- morphml-1.0.0.dist-info/WHEEL +4 -0
- morphml-1.0.0.dist-info/entry_points.txt +3 -0
- 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
|