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,312 @@
1
+ """Quick-start CLI helper for MorphML.
2
+
3
+ Provides interactive setup and template generation for common NAS tasks.
4
+
5
+ Run:
6
+ morphml quickstart
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ import click
13
+
14
+ from morphml.logging_config import get_logger
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ TEMPLATES = {
20
+ "cnn_classification": """\"\"\"CNN Image Classification NAS Example.
21
+
22
+ Generated by: morphml quickstart
23
+ \"\"\"
24
+
25
+ from morphml.core.dsl import Layer, SearchSpace
26
+ from morphml.constraints import ConstraintHandler, MaxParametersConstraint
27
+ from morphml.evaluation import HeuristicEvaluator
28
+ from morphml.optimizers import GeneticAlgorithm
29
+ from morphml.utils import ArchitectureExporter
30
+
31
+ # Define search space
32
+ space = SearchSpace("{name}")
33
+ space.add_layers(
34
+ Layer.input(shape=(3, 32, 32)),
35
+
36
+ # First conv block
37
+ Layer.conv2d(filters=[32, 64], kernel_size=[3, 5]),
38
+ Layer.batchnorm(),
39
+ Layer.relu(),
40
+ Layer.maxpool(pool_size=2),
41
+
42
+ # Second conv block
43
+ Layer.conv2d(filters=[64, 128], kernel_size=3),
44
+ Layer.batchnorm(),
45
+ Layer.relu(),
46
+ Layer.maxpool(pool_size=2),
47
+
48
+ # Classification head
49
+ Layer.flatten(),
50
+ Layer.dense(units=[128, 256, 512]),
51
+ Layer.relu(),
52
+ Layer.dropout(rate=[0.3, 0.5]),
53
+ Layer.dense(units={num_classes}),
54
+ Layer.softmax(),
55
+ )
56
+
57
+ # Set up constraints
58
+ handler = ConstraintHandler()
59
+ handler.add_constraint(MaxParametersConstraint(max_params={max_params}))
60
+
61
+ # Create evaluator
62
+ evaluator = HeuristicEvaluator()
63
+
64
+ # Configure optimizer
65
+ optimizer = GeneticAlgorithm(
66
+ search_space=space,
67
+ config={{
68
+ "population_size": {population_size},
69
+ "num_generations": {num_generations},
70
+ "mutation_rate": 0.2,
71
+ "crossover_rate": 0.8,
72
+ }}
73
+ )
74
+
75
+ # Run optimization
76
+ print(f"Starting NAS: {{optimizer.config['num_generations']}} generations...")
77
+ best = optimizer.optimize(evaluator)
78
+
79
+ # Check constraints
80
+ if not handler.check(best.graph):
81
+ print("\\nWarning: Best architecture violates constraints:")
82
+ print(handler.format_violations(best.graph))
83
+ else:
84
+ print("\\n✓ Best architecture satisfies all constraints")
85
+
86
+ # Show results
87
+ print(f"\\nBest Architecture:")
88
+ print(f" Fitness: {{best.fitness:.4f}}")
89
+ print(f" Parameters: {{best.graph.estimate_parameters():,}}")
90
+ print(f" Depth: {{best.graph.depth()}}")
91
+
92
+ # Export architecture
93
+ exporter = ArchitectureExporter()
94
+ pytorch_code = exporter.to_pytorch(best.graph, "{name}_model")
95
+
96
+ output_file = "{name}_model.py"
97
+ with open(output_file, 'w') as f:
98
+ f.write(pytorch_code)
99
+
100
+ print(f"\\n✓ Exported to {{output_file}}")
101
+ """,
102
+ "simple_nas": """\"\"\"Simple NAS Example.
103
+
104
+ Generated by: morphml quickstart
105
+ \"\"\"
106
+
107
+ from morphml.core.dsl import Layer, SearchSpace
108
+ from morphml.evaluation import HeuristicEvaluator
109
+ from morphml.optimizers import GeneticAlgorithm
110
+
111
+ # Define search space
112
+ space = SearchSpace("{name}")
113
+ space.add_layers(
114
+ Layer.input(shape=(3, 32, 32)),
115
+ Layer.conv2d(filters=[32, 64, 128], kernel_size=3),
116
+ Layer.relu(),
117
+ Layer.maxpool(pool_size=2),
118
+ Layer.flatten(),
119
+ Layer.dense(units=[128, 256]),
120
+ Layer.dense(units={num_classes}),
121
+ )
122
+
123
+ # Run quick search
124
+ evaluator = HeuristicEvaluator()
125
+ optimizer = GeneticAlgorithm(space, config={{
126
+ "population_size": 10,
127
+ "num_generations": 20,
128
+ }})
129
+
130
+ best = optimizer.optimize(evaluator)
131
+ print(f"Best fitness: {{best.fitness:.4f}}")
132
+ """,
133
+ "advanced_nas": """\"\"\"Advanced NAS with Adaptive Operators.
134
+
135
+ Generated by: morphml quickstart
136
+ \"\"\"
137
+
138
+ from morphml.core.dsl import Layer, SearchSpace
139
+ from morphml.constraints import ConstraintHandler, MaxParametersConstraint, DepthConstraint
140
+ from morphml.evaluation import HeuristicEvaluator
141
+ from morphml.optimizers import GeneticAlgorithm
142
+ from morphml.optimizers.adaptive_operators import AdaptiveOperatorScheduler
143
+ from morphml.visualization.crossover_viz import quick_crossover_viz
144
+ from morphml.utils import ArchitectureExporter
145
+
146
+ # Define search space
147
+ space = SearchSpace("{name}")
148
+ space.add_layers(
149
+ Layer.input(shape=(3, 32, 32)),
150
+ Layer.conv2d(filters=[32, 64, 128], kernel_size=[3, 5, 7]),
151
+ Layer.batchnorm(),
152
+ Layer.relu(),
153
+ Layer.maxpool(pool_size=2),
154
+ Layer.conv2d(filters=[64, 128, 256], kernel_size=[3, 5]),
155
+ Layer.batchnorm(),
156
+ Layer.relu(),
157
+ Layer.maxpool(pool_size=2),
158
+ Layer.flatten(),
159
+ Layer.dense(units=[128, 256, 512, 1024]),
160
+ Layer.relu(),
161
+ Layer.dropout(rate=[0.3, 0.5, 0.7]),
162
+ Layer.dense(units={num_classes}),
163
+ )
164
+
165
+ # Set up constraints
166
+ handler = ConstraintHandler()
167
+ handler.add_constraint(MaxParametersConstraint(max_params={max_params}))
168
+ handler.add_constraint(DepthConstraint(min_depth=5, max_depth=20))
169
+
170
+ # Create adaptive scheduler
171
+ scheduler = AdaptiveOperatorScheduler(
172
+ initial_crossover=0.8,
173
+ initial_mutation=0.2
174
+ )
175
+
176
+ # Run optimization
177
+ evaluator = HeuristicEvaluator()
178
+ optimizer = GeneticAlgorithm(space, config={{
179
+ "population_size": {population_size},
180
+ "num_generations": {num_generations},
181
+ }})
182
+
183
+ print("Running advanced NAS with adaptive operators...")
184
+ best = optimizer.optimize(evaluator)
185
+
186
+ # Visualize if parents available
187
+ if best.parent_ids and len(best.parent_ids) == 2:
188
+ try:
189
+ quick_crossover_viz(
190
+ optimizer.population.get(best.parent_ids[0]).graph,
191
+ optimizer.population.get(best.parent_ids[1]).graph,
192
+ "best_parents_crossover.png"
193
+ )
194
+ print("\\n✓ Saved crossover visualization")
195
+ except Exception as e:
196
+ print(f"\\nVisualization skipped: {{e}}")
197
+
198
+ # Export
199
+ exporter = ArchitectureExporter()
200
+ code = exporter.to_pytorch(best.graph, "{name}_model")
201
+ with open("{name}_model.py", 'w') as f:
202
+ f.write(code)
203
+
204
+ print(f"\\n✓ Best architecture exported!")
205
+ print(f" Fitness: {{best.fitness:.4f}}")
206
+ print(f" Parameters: {{best.graph.estimate_parameters():,}}")
207
+ """,
208
+ }
209
+
210
+
211
+ @click.command()
212
+ @click.option(
213
+ "--template",
214
+ "-t",
215
+ type=click.Choice(["cnn_classification", "simple_nas", "advanced_nas"]),
216
+ default="cnn_classification",
217
+ help="Template type",
218
+ )
219
+ @click.option("--name", "-n", default="my_nas", help="Project name")
220
+ @click.option("--output", "-o", default=None, help="Output file path")
221
+ @click.option("--num-classes", default=10, help="Number of output classes")
222
+ @click.option("--max-params", default=1000000, help="Maximum parameters constraint")
223
+ @click.option("--population-size", default=20, help="Population size")
224
+ @click.option("--num-generations", default=50, help="Number of generations")
225
+ @click.option("--interactive", "-i", is_flag=True, help="Interactive mode")
226
+ def quickstart(
227
+ template: str,
228
+ name: str,
229
+ output: Optional[str],
230
+ num_classes: int,
231
+ max_params: int,
232
+ population_size: int,
233
+ num_generations: int,
234
+ interactive: bool,
235
+ ):
236
+ """
237
+ Generate a quick-start NAS project from template.
238
+
239
+ Examples:
240
+ morphml quickstart
241
+ morphml quickstart -t simple_nas -n my_project
242
+ morphml quickstart -i # Interactive mode
243
+ """
244
+ # Interactive mode
245
+ if interactive:
246
+ click.echo("🧬 MorphML Quick Start - Interactive Mode\n")
247
+
248
+ template = click.prompt(
249
+ "Select template",
250
+ type=click.Choice(["cnn_classification", "simple_nas", "advanced_nas"]),
251
+ default="cnn_classification",
252
+ )
253
+ name = click.prompt("Project name", default="my_nas")
254
+ num_classes = click.prompt("Number of classes", default=10, type=int)
255
+
256
+ if template in ["cnn_classification", "advanced_nas"]:
257
+ max_params = click.prompt("Max parameters", default=1000000, type=int)
258
+ population_size = click.prompt("Population size", default=20, type=int)
259
+ num_generations = click.prompt("Number of generations", default=50, type=int)
260
+
261
+ # Generate code from template
262
+ template_code = TEMPLATES[template]
263
+ code = template_code.format(
264
+ name=name,
265
+ num_classes=num_classes,
266
+ max_params=max_params,
267
+ population_size=population_size,
268
+ num_generations=num_generations,
269
+ )
270
+
271
+ # Determine output file
272
+ if output is None:
273
+ output = f"{name}.py"
274
+
275
+ output_path = Path(output)
276
+
277
+ # Check if file exists
278
+ if output_path.exists():
279
+ if not click.confirm(f"File {output} already exists. Overwrite?"):
280
+ click.echo("Aborted.")
281
+ return
282
+
283
+ # Write file
284
+ output_path.write_text(code)
285
+
286
+ # Success message
287
+ click.echo(f"\n✓ Created {output}")
288
+ click.echo(f"\nTemplate: {template}")
289
+ click.echo(f"Project: {name}")
290
+ click.echo(f"Classes: {num_classes}")
291
+
292
+ if template in ["cnn_classification", "advanced_nas"]:
293
+ click.echo(f"Max params: {max_params:,}")
294
+ click.echo(f"Population: {population_size}")
295
+ click.echo(f"Generations: {num_generations}")
296
+
297
+ click.echo("\nNext steps:")
298
+ click.echo(f" 1. Review and customize {output}")
299
+ click.echo(f" 2. Run: python {output}")
300
+ click.echo(" 3. Check exported model file")
301
+
302
+ # Show template description
303
+ descriptions = {
304
+ "cnn_classification": "Full CNN classification with constraints and export",
305
+ "simple_nas": "Minimal NAS example for quick testing",
306
+ "advanced_nas": "Advanced NAS with adaptive operators and visualization",
307
+ }
308
+ click.echo(f"\nTemplate: {descriptions[template]}")
309
+
310
+
311
+ if __name__ == "__main__":
312
+ quickstart()
morphml/config.py ADDED
@@ -0,0 +1,278 @@
1
+ """Configuration management for MorphML."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Optional
6
+
7
+ import yaml
8
+ from pydantic import BaseModel, Field
9
+
10
+ from morphml.exceptions import ConfigurationError
11
+
12
+
13
+ class OptimizerConfig(BaseModel):
14
+ """Configuration for optimizers."""
15
+
16
+ name: str = Field(description="Optimizer name (genetic, bayesian, darts, etc.)")
17
+ population_size: int = Field(default=50, ge=1, description="Population size")
18
+ num_generations: int = Field(default=100, ge=1, description="Number of generations")
19
+ mutation_rate: float = Field(default=0.1, ge=0.0, le=1.0, description="Mutation probability")
20
+ crossover_rate: float = Field(default=0.8, ge=0.0, le=1.0, description="Crossover probability")
21
+
22
+ class Config:
23
+ extra = "allow" # Allow additional fields for optimizer-specific config
24
+
25
+
26
+ class EvaluationConfig(BaseModel):
27
+ """Configuration for architecture evaluation."""
28
+
29
+ num_epochs: int = Field(default=50, ge=1, description="Training epochs")
30
+ batch_size: int = Field(default=128, ge=1, description="Batch size")
31
+ learning_rate: float = Field(default=0.001, gt=0.0, description="Learning rate")
32
+ device: str = Field(default="cuda", description="Device (cuda or cpu)")
33
+ num_workers: int = Field(default=4, ge=0, description="Data loader workers")
34
+
35
+
36
+ class LoggingConfig(BaseModel):
37
+ """Configuration for logging."""
38
+
39
+ level: str = Field(default="INFO", description="Log level")
40
+ format: str = Field(
41
+ default="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
42
+ description="Log format",
43
+ )
44
+ file: Optional[str] = Field(default=None, description="Log file path")
45
+ console: bool = Field(default=True, description="Enable console logging")
46
+
47
+
48
+ class ExperimentConfig(BaseModel):
49
+ """Main experiment configuration."""
50
+
51
+ name: str = Field(description="Experiment name")
52
+ seed: int = Field(default=42, description="Random seed for reproducibility")
53
+ output_dir: Path = Field(default=Path("./experiments"), description="Output directory")
54
+
55
+ optimizer: OptimizerConfig
56
+ evaluation: EvaluationConfig = Field(default_factory=EvaluationConfig)
57
+ logging: LoggingConfig = Field(default_factory=LoggingConfig)
58
+
59
+ class Config:
60
+ arbitrary_types_allowed = True
61
+
62
+
63
+ class ConfigManager:
64
+ """
65
+ Manage MorphML configuration.
66
+
67
+ Supports loading from:
68
+ - YAML files
69
+ - Environment variables
70
+ - Python dictionaries
71
+
72
+ Usage:
73
+ config = ConfigManager.from_yaml("config.yaml")
74
+ config.get("optimizer.population_size")
75
+ config.set("optimizer.mutation_rate", 0.2)
76
+ """
77
+
78
+ def __init__(self, config_dict: Optional[Dict[str, Any]] = None):
79
+ """
80
+ Initialize configuration manager.
81
+
82
+ Args:
83
+ config_dict: Configuration dictionary
84
+ """
85
+ self._config = config_dict or {}
86
+ self._experiment_config: Optional[ExperimentConfig] = None
87
+
88
+ @classmethod
89
+ def from_yaml(cls, config_path: str) -> "ConfigManager":
90
+ """
91
+ Load configuration from YAML file.
92
+
93
+ Args:
94
+ config_path: Path to YAML file
95
+
96
+ Returns:
97
+ ConfigManager instance
98
+
99
+ Raises:
100
+ ConfigurationError: If file cannot be loaded
101
+ """
102
+ try:
103
+ with open(config_path, "r") as f:
104
+ config_dict = yaml.safe_load(f)
105
+ return cls(config_dict)
106
+ except Exception as e:
107
+ raise ConfigurationError(f"Failed to load config from {config_path}: {e}") from e
108
+
109
+ @classmethod
110
+ def from_dict(cls, config_dict: Dict[str, Any]) -> "ConfigManager":
111
+ """Create configuration from dictionary."""
112
+ return cls(config_dict)
113
+
114
+ def get(self, key: str, default: Any = None) -> Any:
115
+ """
116
+ Get configuration value by dot-separated key.
117
+
118
+ Args:
119
+ key: Dot-separated key (e.g., "optimizer.population_size")
120
+ default: Default value if key not found
121
+
122
+ Returns:
123
+ Configuration value
124
+
125
+ Example:
126
+ >>> config.get("optimizer.mutation_rate")
127
+ 0.1
128
+ """
129
+ keys = key.split(".")
130
+ value: Any = self._config
131
+
132
+ for k in keys:
133
+ if isinstance(value, dict):
134
+ value = value.get(k)
135
+ if value is None:
136
+ return default
137
+ else:
138
+ return default
139
+
140
+ return value
141
+
142
+ def set(self, key: str, value: Any) -> None:
143
+ """
144
+ Set configuration value by dot-separated key.
145
+
146
+ Args:
147
+ key: Dot-separated key
148
+ value: Value to set
149
+ """
150
+ keys = key.split(".")
151
+ config = self._config
152
+
153
+ for k in keys[:-1]:
154
+ if k not in config:
155
+ config[k] = {}
156
+ config = config[k]
157
+
158
+ config[keys[-1]] = value
159
+
160
+ def validate(self) -> ExperimentConfig:
161
+ """
162
+ Validate configuration using Pydantic model.
163
+
164
+ Returns:
165
+ Validated ExperimentConfig
166
+
167
+ Raises:
168
+ ConfigurationError: If validation fails
169
+ """
170
+ try:
171
+ self._experiment_config = ExperimentConfig(**self._config)
172
+ return self._experiment_config
173
+ except Exception as e:
174
+ raise ConfigurationError(f"Configuration validation failed: {e}") from e
175
+
176
+ def to_dict(self) -> Dict[str, Any]:
177
+ """Export configuration as dictionary."""
178
+ return self._config.copy()
179
+
180
+ def save(self, output_path: str) -> None:
181
+ """
182
+ Save configuration to YAML file.
183
+
184
+ Args:
185
+ output_path: Output file path
186
+ """
187
+ with open(output_path, "w") as f:
188
+ yaml.dump(self._config, f, default_flow_style=False, sort_keys=False)
189
+
190
+ def merge(self, other_config: Dict[str, Any]) -> None:
191
+ """
192
+ Merge another configuration into this one.
193
+
194
+ Args:
195
+ other_config: Configuration dictionary to merge
196
+ """
197
+ self._deep_merge(self._config, other_config)
198
+
199
+ @staticmethod
200
+ def _deep_merge(base: Dict, update: Dict) -> None:
201
+ """Deep merge update dict into base dict."""
202
+ for key, value in update.items():
203
+ if key in base and isinstance(base[key], dict) and isinstance(value, dict):
204
+ ConfigManager._deep_merge(base[key], value)
205
+ else:
206
+ base[key] = value
207
+
208
+
209
+ def load_config_from_env() -> Dict[str, Any]:
210
+ """
211
+ Load configuration from environment variables.
212
+
213
+ Environment variables should be prefixed with MORPHML_
214
+ Example: MORPHML_OPTIMIZER_POPULATION_SIZE=100
215
+
216
+ Returns:
217
+ Configuration dictionary
218
+ """
219
+ config: Dict[str, Any] = {}
220
+ prefix = "MORPHML_"
221
+
222
+ for key, value in os.environ.items():
223
+ if key.startswith(prefix):
224
+ # Remove prefix and convert to lowercase with dots
225
+ config_key = key[len(prefix) :].lower().replace("_", ".")
226
+
227
+ # Try to convert to int/float
228
+ try:
229
+ if "." in value:
230
+ value = float(value) # type: ignore
231
+ else:
232
+ value = int(value) # type: ignore
233
+ except ValueError:
234
+ pass
235
+
236
+ # Set in config
237
+ keys = config_key.split(".")
238
+ current = config
239
+ for k in keys[:-1]:
240
+ if k not in current:
241
+ current[k] = {}
242
+ current = current[k]
243
+ current[keys[-1]] = value
244
+
245
+ return config
246
+
247
+
248
+ # Default configuration template
249
+ DEFAULT_CONFIG = {
250
+ "name": "morphml_experiment",
251
+ "seed": 42,
252
+ "output_dir": "./experiments",
253
+ "optimizer": {
254
+ "name": "genetic",
255
+ "population_size": 50,
256
+ "num_generations": 100,
257
+ "mutation_rate": 0.1,
258
+ "crossover_rate": 0.8,
259
+ },
260
+ "evaluation": {
261
+ "num_epochs": 50,
262
+ "batch_size": 128,
263
+ "learning_rate": 0.001,
264
+ "device": "cuda",
265
+ "num_workers": 4,
266
+ },
267
+ "logging": {"level": "INFO", "console": True, "file": None},
268
+ }
269
+
270
+
271
+ def get_config() -> ConfigManager:
272
+ """
273
+ Get default configuration manager.
274
+
275
+ Returns:
276
+ ConfigManager instance with default configuration
277
+ """
278
+ return ConfigManager.from_dict(DEFAULT_CONFIG)
@@ -0,0 +1,19 @@
1
+ """Constraint handling for architecture search."""
2
+
3
+ from morphml.constraints.handler import ConstraintHandler
4
+ from morphml.constraints.predicates import (
5
+ DepthConstraint,
6
+ MaxParametersConstraint,
7
+ MinParametersConstraint,
8
+ OperationConstraint,
9
+ WidthConstraint,
10
+ )
11
+
12
+ __all__ = [
13
+ "ConstraintHandler",
14
+ "MaxParametersConstraint",
15
+ "MinParametersConstraint",
16
+ "DepthConstraint",
17
+ "WidthConstraint",
18
+ "OperationConstraint",
19
+ ]