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,237 @@
1
+ """Heuristic evaluators for fast architecture assessment.
2
+
3
+ These evaluators estimate architecture quality without training,
4
+ enabling rapid architecture search and development.
5
+
6
+ Example:
7
+ >>> from morphml.evaluation import HeuristicEvaluator
8
+ >>>
9
+ >>> evaluator = HeuristicEvaluator()
10
+ >>> score = evaluator.combined_score(graph)
11
+ """
12
+
13
+ from typing import Dict
14
+
15
+ from morphml.core.graph import ModelGraph
16
+ from morphml.logging_config import get_logger
17
+
18
+ logger = get_logger(__name__)
19
+
20
+
21
+ class HeuristicEvaluator:
22
+ """
23
+ Fast heuristic evaluation without training.
24
+
25
+ Provides multiple proxy metrics that correlate with actual
26
+ performance, enabling rapid architecture assessment.
27
+
28
+ Metrics:
29
+ - Parameter count
30
+ - FLOPs estimation
31
+ - Graph depth
32
+ - Graph width
33
+ - Connectivity ratio
34
+
35
+ Example:
36
+ >>> evaluator = HeuristicEvaluator(
37
+ ... param_weight=0.3,
38
+ ... depth_weight=0.3,
39
+ ... width_weight=0.2
40
+ ... )
41
+ >>> score = evaluator(graph)
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ param_weight: float = 0.3,
47
+ depth_weight: float = 0.3,
48
+ width_weight: float = 0.2,
49
+ connectivity_weight: float = 0.2,
50
+ target_params: int = 1000000,
51
+ target_depth: int = 20,
52
+ ):
53
+ """
54
+ Initialize heuristic evaluator.
55
+
56
+ Args:
57
+ param_weight: Weight for parameter penalty
58
+ depth_weight: Weight for depth score
59
+ width_weight: Weight for width score
60
+ connectivity_weight: Weight for connectivity
61
+ target_params: Target parameter count
62
+ target_depth: Target network depth
63
+ """
64
+ self.param_weight = param_weight
65
+ self.depth_weight = depth_weight
66
+ self.width_weight = width_weight
67
+ self.connectivity_weight = connectivity_weight
68
+ self.target_params = target_params
69
+ self.target_depth = target_depth
70
+
71
+ def __call__(self, graph: ModelGraph) -> float:
72
+ """Evaluate using combined score."""
73
+ return self.combined_score(graph)
74
+
75
+ def parameter_score(self, graph: ModelGraph) -> float:
76
+ """
77
+ Score based on parameter count.
78
+
79
+ Penalizes large models, rewards compact ones.
80
+
81
+ Args:
82
+ graph: Architecture to evaluate
83
+
84
+ Returns:
85
+ Score in [0, 1]
86
+ """
87
+ params = graph.estimate_parameters()
88
+
89
+ # Sigmoid-like scoring
90
+ ratio = params / self.target_params
91
+ if ratio < 0.5:
92
+ score = 1.0
93
+ elif ratio < 1.0:
94
+ score = 1.0 - 0.5 * (ratio - 0.5)
95
+ elif ratio < 2.0:
96
+ score = 0.75 - 0.5 * (ratio - 1.0)
97
+ else:
98
+ score = max(0.1, 0.25 - 0.1 * (ratio - 2.0))
99
+
100
+ return score
101
+
102
+ def depth_score(self, graph: ModelGraph) -> float:
103
+ """
104
+ Score based on network depth.
105
+
106
+ Moderate depth is preferred.
107
+
108
+ Args:
109
+ graph: Architecture to evaluate
110
+
111
+ Returns:
112
+ Score in [0, 1]
113
+ """
114
+ depth = graph.get_depth()
115
+
116
+ if depth < 5:
117
+ score = 0.5 + 0.1 * depth # Too shallow
118
+ elif depth <= self.target_depth:
119
+ score = 0.8 + 0.01 * depth # Good range
120
+ else:
121
+ score = max(0.3, 1.0 - 0.02 * (depth - self.target_depth)) # Too deep
122
+
123
+ return min(1.0, score)
124
+
125
+ def width_score(self, graph: ModelGraph) -> float:
126
+ """
127
+ Score based on network width.
128
+
129
+ Moderate width is preferred.
130
+
131
+ Args:
132
+ graph: Architecture to evaluate
133
+
134
+ Returns:
135
+ Score in [0, 1]
136
+ """
137
+ width = graph.get_max_width()
138
+
139
+ if width < 3:
140
+ score = 0.5 + 0.1 * width
141
+ elif width <= 10:
142
+ score = 0.8 + 0.02 * width
143
+ else:
144
+ score = max(0.5, 1.0 - 0.02 * (width - 10))
145
+
146
+ return min(1.0, score)
147
+
148
+ def connectivity_score(self, graph: ModelGraph) -> float:
149
+ """
150
+ Score based on connectivity ratio.
151
+
152
+ Measures how well-connected the graph is.
153
+
154
+ Args:
155
+ graph: Architecture to evaluate
156
+
157
+ Returns:
158
+ Score in [0, 1]
159
+ """
160
+ num_nodes = len(graph.nodes)
161
+ num_edges = len(graph.edges)
162
+
163
+ if num_nodes <= 1:
164
+ return 0.5
165
+
166
+ # Expected edges for DAG: between n-1 (linear) and n*(n-1)/2 (complete)
167
+ min_edges = num_nodes - 1
168
+ max_edges = num_nodes * (num_nodes - 1) / 2
169
+
170
+ if num_edges < min_edges:
171
+ return 0.0 # Disconnected
172
+
173
+ # Normalize
174
+ connectivity_ratio = (num_edges - min_edges) / (max_edges - min_edges + 1)
175
+
176
+ # Prefer moderate connectivity
177
+ if connectivity_ratio < 0.3:
178
+ score = 0.6 + connectivity_ratio
179
+ elif connectivity_ratio < 0.7:
180
+ score = 0.9
181
+ else:
182
+ score = 0.9 - 0.5 * (connectivity_ratio - 0.7)
183
+
184
+ return score
185
+
186
+ def combined_score(self, graph: ModelGraph) -> float:
187
+ """
188
+ Combined heuristic score.
189
+
190
+ Weighted combination of all metrics.
191
+
192
+ Args:
193
+ graph: Architecture to evaluate
194
+
195
+ Returns:
196
+ Combined score in [0, 1]
197
+ """
198
+ param_score = self.parameter_score(graph)
199
+ depth_score = self.depth_score(graph)
200
+ width_score = self.width_score(graph)
201
+ connectivity_score = self.connectivity_score(graph)
202
+
203
+ combined = (
204
+ self.param_weight * param_score
205
+ + self.depth_weight * depth_score
206
+ + self.width_weight * width_score
207
+ + self.connectivity_weight * connectivity_score
208
+ )
209
+
210
+ # Ensure in [0, 1]
211
+ combined = max(0.0, min(1.0, combined))
212
+
213
+ logger.debug(
214
+ f"Heuristic scores: param={param_score:.3f}, depth={depth_score:.3f}, "
215
+ f"width={width_score:.3f}, connectivity={connectivity_score:.3f}, "
216
+ f"combined={combined:.3f}"
217
+ )
218
+
219
+ return combined
220
+
221
+ def get_all_scores(self, graph: ModelGraph) -> Dict[str, float]:
222
+ """
223
+ Get all individual scores.
224
+
225
+ Args:
226
+ graph: Architecture to evaluate
227
+
228
+ Returns:
229
+ Dictionary of all scores
230
+ """
231
+ return {
232
+ "parameter": self.parameter_score(graph),
233
+ "depth": self.depth_score(graph),
234
+ "width": self.width_score(graph),
235
+ "connectivity": self.connectivity_score(graph),
236
+ "combined": self.combined_score(graph),
237
+ }
morphml/exceptions.py ADDED
@@ -0,0 +1,55 @@
1
+ """Custom exceptions for MorphML."""
2
+
3
+
4
+ class MorphMLError(Exception):
5
+ """Base exception for all MorphML errors."""
6
+
7
+ pass
8
+
9
+
10
+ class DSLError(MorphMLError):
11
+ """Raised when DSL parsing or compilation fails."""
12
+
13
+ pass
14
+
15
+
16
+ class GraphError(MorphMLError):
17
+ """Raised when graph operations fail."""
18
+
19
+ pass
20
+
21
+
22
+ class SearchSpaceError(MorphMLError):
23
+ """Raised when search space definition is invalid."""
24
+
25
+ pass
26
+
27
+
28
+ class OptimizerError(MorphMLError):
29
+ """Raised when optimizer encounters an error."""
30
+
31
+ pass
32
+
33
+
34
+ class EvaluationError(MorphMLError):
35
+ """Raised when architecture evaluation fails."""
36
+
37
+ pass
38
+
39
+
40
+ class ConfigurationError(MorphMLError):
41
+ """Raised when configuration is invalid."""
42
+
43
+ pass
44
+
45
+
46
+ class DistributedError(MorphMLError):
47
+ """Raised when distributed operations fail."""
48
+
49
+ pass
50
+
51
+
52
+ class ValidationError(MorphMLError):
53
+ """Raised when validation fails."""
54
+
55
+ pass
@@ -0,0 +1,5 @@
1
+ """Execution engine for running experiments."""
2
+
3
+ from morphml.execution.local_executor import LocalExecutor, run_experiment
4
+
5
+ __all__ = ["LocalExecutor", "run_experiment"]
@@ -0,0 +1,350 @@
1
+ """Local executor for running experiments on a single machine.
2
+
3
+ Provides LocalExecutor class for orchestrating the complete
4
+ experiment lifecycle on a single machine.
5
+
6
+ Author: Eshan Roy <eshanized@proton.me>
7
+ Organization: TONMOY INFRASTRUCTURE & VISION
8
+ """
9
+
10
+ import time
11
+ from pathlib import Path
12
+ from typing import Any, Callable, Dict, Optional
13
+
14
+ from morphml.config import get_config
15
+ from morphml.core.dsl.search_space import SearchSpace
16
+ from morphml.core.search.search_engine import SearchEngine
17
+ from morphml.evaluation.heuristic import HeuristicEvaluator
18
+ from morphml.logging_config import get_logger
19
+ from morphml.utils.checkpoint import Checkpoint
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class LocalExecutor:
25
+ """
26
+ Executes experiments on local machine.
27
+
28
+ Manages the complete experiment lifecycle:
29
+ 1. Initialize search from search space
30
+ 2. Run optimizer for multiple generations
31
+ 3. Evaluate candidates using evaluator
32
+ 4. Track results and save checkpoints
33
+ 5. Return best model
34
+
35
+ Example:
36
+ >>> executor = LocalExecutor()
37
+ >>> results = executor.run(
38
+ ... search_space=space,
39
+ ... optimizer=ga,
40
+ ... evaluator=evaluator,
41
+ ... max_evaluations=1000
42
+ ... )
43
+ >>> best_model = results['best_graph']
44
+ """
45
+
46
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
47
+ """
48
+ Initialize local executor.
49
+
50
+ Args:
51
+ config: Configuration dictionary (uses global config if None)
52
+ """
53
+ self.config = config or get_config()
54
+ self.logger = logger
55
+ self.checkpoint_dir = Path(self.config.get("checkpoints_dir", "./checkpoints"))
56
+ self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
57
+
58
+ def run(
59
+ self,
60
+ search_space: SearchSpace,
61
+ optimizer: SearchEngine,
62
+ evaluator: Optional[Callable] = None,
63
+ max_evaluations: int = 1000,
64
+ checkpoint_interval: int = 100,
65
+ callbacks: Optional[list] = None,
66
+ verbose: bool = True,
67
+ ) -> Dict[str, Any]:
68
+ """
69
+ Run complete experiment.
70
+
71
+ Args:
72
+ search_space: Search space definition
73
+ optimizer: Optimizer instance (e.g., GeneticAlgorithm)
74
+ evaluator: Evaluator for fitness computation (defaults to HeuristicEvaluator)
75
+ max_evaluations: Budget in number of evaluations
76
+ checkpoint_interval: Save checkpoint every N evaluations
77
+ callbacks: Optional callbacks to call each generation
78
+ verbose: Print progress information
79
+
80
+ Returns:
81
+ Dictionary with results:
82
+ - best_graph: Best architecture found
83
+ - best_fitness: Best fitness value
84
+ - num_evaluations: Total evaluations performed
85
+ - elapsed_time: Total time in seconds
86
+ - final_generation: Final generation number
87
+ - statistics: Final statistics
88
+ - history: Complete history
89
+
90
+ Example:
91
+ >>> executor = LocalExecutor()
92
+ >>> results = executor.run(
93
+ ... search_space=space,
94
+ ... optimizer=GeneticAlgorithm(space, config),
95
+ ... max_evaluations=500
96
+ ... )
97
+ """
98
+ if verbose:
99
+ self.logger.info("=" * 70)
100
+ self.logger.info("Starting Experiment")
101
+ self.logger.info("=" * 70)
102
+ self.logger.info(f"Search space: {search_space.name}")
103
+ self.logger.info(f"Optimizer: {optimizer.__class__.__name__}")
104
+ self.logger.info(f"Budget: {max_evaluations} evaluations")
105
+
106
+ # Use heuristic evaluator if none provided
107
+ if evaluator is None:
108
+ if verbose:
109
+ self.logger.info("No evaluator provided, using HeuristicEvaluator")
110
+ evaluator = HeuristicEvaluator()
111
+
112
+ # Configure optimizer
113
+ optimizer.config["max_evaluations"] = max_evaluations
114
+
115
+ # Track progress
116
+ start_time = time.time()
117
+ num_evaluations = 0
118
+ last_checkpoint = 0
119
+
120
+ # Initialize population
121
+ if verbose:
122
+ self.logger.info("Initializing population...")
123
+
124
+ population = optimizer.initialize_population(optimizer.config.get("population_size", 50))
125
+
126
+ if verbose:
127
+ self.logger.info(f"Population initialized with {len(population)} individuals")
128
+
129
+ # Evaluate initial population
130
+ for individual in population.individuals:
131
+ if individual.fitness is None:
132
+ individual.set_fitness(evaluator(individual.graph))
133
+ num_evaluations += 1
134
+
135
+ # Track best
136
+ optimizer._update_best(population)
137
+ optimizer._record_history(population)
138
+
139
+ if verbose:
140
+ self.logger.info(
141
+ f"Initial population evaluated. Best fitness: {population.best_fitness():.4f}"
142
+ )
143
+ self.logger.info("")
144
+ self.logger.info("Starting evolution...")
145
+
146
+ # Main evolution loop
147
+ generation = 0
148
+ callbacks = callbacks or []
149
+
150
+ while num_evaluations < max_evaluations and not optimizer.should_stop():
151
+ # Evolution step
152
+ population = optimizer.step(population, evaluator)
153
+
154
+ # Evaluate new individuals
155
+ for individual in population.individuals:
156
+ if individual.fitness is None and num_evaluations < max_evaluations:
157
+ individual.set_fitness(evaluator(individual.graph))
158
+ num_evaluations += 1
159
+
160
+ # Update tracking
161
+ optimizer._update_best(population)
162
+ optimizer._record_history(population)
163
+ optimizer.generation = generation
164
+ optimizer.num_evaluations = num_evaluations
165
+
166
+ # Call callbacks
167
+ for callback in callbacks:
168
+ callback(optimizer, population)
169
+
170
+ # Checkpoint
171
+ if num_evaluations - last_checkpoint >= checkpoint_interval:
172
+ self._save_checkpoint(optimizer, num_evaluations)
173
+ last_checkpoint = num_evaluations
174
+
175
+ # Log progress
176
+ if verbose and generation % 10 == 0:
177
+ stats = optimizer.get_statistics()
178
+ elapsed = time.time() - start_time
179
+ evals_per_sec = num_evaluations / elapsed if elapsed > 0 else 0
180
+
181
+ self.logger.info(
182
+ f"Gen {generation:3d}: "
183
+ f"best={stats['best_fitness']:.4f}, "
184
+ f"mean={stats['mean_fitness']:.4f}, "
185
+ f"evals={num_evaluations}/{max_evaluations}, "
186
+ f"({evals_per_sec:.1f} eval/s)"
187
+ )
188
+
189
+ generation += 1
190
+
191
+ # Finalize
192
+ elapsed_time = time.time() - start_time
193
+ best_graph = optimizer.get_best()
194
+ final_stats = optimizer.get_statistics()
195
+
196
+ results = {
197
+ "best_graph": best_graph,
198
+ "best_fitness": final_stats["best_fitness"],
199
+ "num_evaluations": num_evaluations,
200
+ "elapsed_time": elapsed_time,
201
+ "final_generation": generation,
202
+ "statistics": final_stats,
203
+ "history": optimizer.get_history(),
204
+ }
205
+
206
+ if verbose:
207
+ self.logger.info("")
208
+ self.logger.info("=" * 70)
209
+ self.logger.info("Experiment Complete")
210
+ self.logger.info("=" * 70)
211
+ self.logger.info(f"Best fitness: {final_stats['best_fitness']:.4f}")
212
+ self.logger.info(f"Evaluations: {num_evaluations}")
213
+ self.logger.info(f"Time: {elapsed_time:.2f}s")
214
+ self.logger.info(f"Avg time per evaluation: {elapsed_time/num_evaluations*1000:.2f}ms")
215
+ self.logger.info("=" * 70)
216
+
217
+ return results
218
+
219
+ def _save_checkpoint(self, optimizer: SearchEngine, num_evals: int) -> None:
220
+ """
221
+ Save optimizer state to checkpoint.
222
+
223
+ Args:
224
+ optimizer: Optimizer to checkpoint
225
+ num_evals: Current number of evaluations
226
+ """
227
+ checkpoint_path = self.checkpoint_dir / f"checkpoint_{num_evals}.pkl"
228
+
229
+ try:
230
+ Checkpoint.save(optimizer, str(checkpoint_path))
231
+ self.logger.debug(f"Checkpoint saved: {checkpoint_path}")
232
+ except Exception as e:
233
+ self.logger.warning(f"Failed to save checkpoint: {e}")
234
+
235
+ def load_checkpoint(self, checkpoint_path: str) -> SearchEngine:
236
+ """
237
+ Load optimizer from checkpoint.
238
+
239
+ Args:
240
+ checkpoint_path: Path to checkpoint file
241
+
242
+ Returns:
243
+ Restored optimizer
244
+
245
+ Example:
246
+ >>> executor = LocalExecutor()
247
+ >>> optimizer = executor.load_checkpoint("checkpoint_500.pkl")
248
+ """
249
+ checkpoint_path = Path(checkpoint_path)
250
+
251
+ if not checkpoint_path.exists():
252
+ raise FileNotFoundError(f"Checkpoint not found: {checkpoint_path}")
253
+
254
+ self.logger.info(f"Loading checkpoint: {checkpoint_path}")
255
+ optimizer = Checkpoint.load(str(checkpoint_path))
256
+
257
+ self.logger.info(
258
+ f"Checkpoint loaded: generation {optimizer.generation}, "
259
+ f"{optimizer.num_evaluations} evaluations"
260
+ )
261
+
262
+ return optimizer
263
+
264
+ def resume(
265
+ self,
266
+ checkpoint_path: str,
267
+ search_space: SearchSpace,
268
+ evaluator: Optional[Callable] = None,
269
+ max_additional_evaluations: int = 500,
270
+ verbose: bool = True,
271
+ ) -> Dict[str, Any]:
272
+ """
273
+ Resume experiment from checkpoint.
274
+
275
+ Args:
276
+ checkpoint_path: Path to checkpoint file
277
+ search_space: Search space (must match checkpointed experiment)
278
+ evaluator: Evaluator for fitness computation
279
+ max_additional_evaluations: Additional evaluation budget
280
+ verbose: Print progress information
281
+
282
+ Returns:
283
+ Results dictionary
284
+
285
+ Example:
286
+ >>> executor = LocalExecutor()
287
+ >>> results = executor.resume(
288
+ ... "checkpoint_500.pkl",
289
+ ... search_space=space,
290
+ ... max_additional_evaluations=500
291
+ ... )
292
+ """
293
+ # Load checkpoint
294
+ optimizer = self.load_checkpoint(checkpoint_path)
295
+
296
+ # Calculate remaining budget
297
+ prev_evals = optimizer.num_evaluations
298
+ total_budget = prev_evals + max_additional_evaluations
299
+
300
+ if verbose:
301
+ self.logger.info(f"Resuming from {prev_evals} evaluations")
302
+ self.logger.info(f"Additional budget: {max_additional_evaluations}")
303
+ self.logger.info(f"Total budget: {total_budget}")
304
+
305
+ # Continue execution
306
+ return self.run(
307
+ search_space=search_space,
308
+ optimizer=optimizer,
309
+ evaluator=evaluator,
310
+ max_evaluations=total_budget,
311
+ verbose=verbose,
312
+ )
313
+
314
+
315
+ # Convenience function
316
+ def run_experiment(
317
+ search_space: SearchSpace,
318
+ optimizer: SearchEngine,
319
+ evaluator: Optional[Callable] = None,
320
+ max_evaluations: int = 1000,
321
+ **kwargs,
322
+ ) -> Dict[str, Any]:
323
+ """
324
+ Convenience function to run experiment.
325
+
326
+ Args:
327
+ search_space: Search space definition
328
+ optimizer: Optimizer instance
329
+ evaluator: Evaluator function
330
+ max_evaluations: Evaluation budget
331
+ **kwargs: Additional arguments for LocalExecutor.run()
332
+
333
+ Returns:
334
+ Results dictionary
335
+
336
+ Example:
337
+ >>> results = run_experiment(
338
+ ... search_space=space,
339
+ ... optimizer=ga,
340
+ ... max_evaluations=1000
341
+ ... )
342
+ """
343
+ executor = LocalExecutor()
344
+ return executor.run(
345
+ search_space=search_space,
346
+ optimizer=optimizer,
347
+ evaluator=evaluator,
348
+ max_evaluations=max_evaluations,
349
+ **kwargs,
350
+ )
@@ -0,0 +1,28 @@
1
+ """Framework integrations for MorphML.
2
+
3
+ Provides adapters to convert ModelGraph architectures to various ML frameworks:
4
+ - PyTorch (with training support)
5
+ - TensorFlow/Keras
6
+ - JAX/Flax
7
+ - Scikit-learn
8
+
9
+ Example:
10
+ >>> from morphml.integrations import PyTorchAdapter
11
+ >>> adapter = PyTorchAdapter()
12
+ >>> model = adapter.build_model(graph)
13
+ >>> trainer = adapter.get_trainer(model, config)
14
+ >>> results = trainer.train(train_loader, val_loader)
15
+ """
16
+
17
+ from morphml.integrations.jax_adapter import JAXAdapter
18
+ from morphml.integrations.pytorch_adapter import PyTorchAdapter, PyTorchTrainer
19
+ from morphml.integrations.sklearn_adapter import SklearnAdapter
20
+ from morphml.integrations.tensorflow_adapter import TensorFlowAdapter
21
+
22
+ __all__ = [
23
+ "PyTorchAdapter",
24
+ "PyTorchTrainer",
25
+ "TensorFlowAdapter",
26
+ "JAXAdapter",
27
+ "SklearnAdapter",
28
+ ]