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
morphml/api/client.py
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""API client library for MorphML.
|
|
2
|
+
|
|
3
|
+
Provides a Python client for interacting with the MorphML REST API.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
>>> from morphml.api.client import MorphMLClient
|
|
7
|
+
>>> client = MorphMLClient("http://localhost:8000")
|
|
8
|
+
>>> experiments = client.list_experiments()
|
|
9
|
+
>>> exp = client.create_experiment("my-experiment", search_space={...})
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
import requests
|
|
15
|
+
from requests.exceptions import RequestException
|
|
16
|
+
|
|
17
|
+
from morphml.logging_config import get_logger
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MorphMLClient:
|
|
23
|
+
"""
|
|
24
|
+
Python client for MorphML REST API.
|
|
25
|
+
|
|
26
|
+
Provides convenient methods for all API endpoints.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
base_url: Base URL of the API
|
|
30
|
+
token: Optional authentication token
|
|
31
|
+
session: Requests session
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> client = MorphMLClient("http://localhost:8000")
|
|
35
|
+
>>> client.login("user@example.com", "password")
|
|
36
|
+
>>> experiments = client.list_experiments()
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, base_url: str = "http://localhost:8000", token: Optional[str] = None):
|
|
40
|
+
"""
|
|
41
|
+
Initialize API client.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
base_url: Base URL of the API
|
|
45
|
+
token: Optional authentication token
|
|
46
|
+
"""
|
|
47
|
+
self.base_url = base_url.rstrip("/")
|
|
48
|
+
self.token = token
|
|
49
|
+
self.session = requests.Session()
|
|
50
|
+
|
|
51
|
+
if token:
|
|
52
|
+
self.session.headers.update({"Authorization": f"Bearer {token}"})
|
|
53
|
+
|
|
54
|
+
logger.info(f"Initialized MorphML client for {base_url}")
|
|
55
|
+
|
|
56
|
+
def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
|
|
57
|
+
"""
|
|
58
|
+
Make HTTP request to API.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
method: HTTP method
|
|
62
|
+
endpoint: API endpoint
|
|
63
|
+
**kwargs: Additional request arguments
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Response JSON
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
RequestException: If request fails
|
|
70
|
+
"""
|
|
71
|
+
url = f"{self.base_url}{endpoint}"
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
response = self.session.request(method, url, **kwargs)
|
|
75
|
+
response.raise_for_status()
|
|
76
|
+
return response.json()
|
|
77
|
+
|
|
78
|
+
except RequestException as e:
|
|
79
|
+
logger.error(f"API request failed: {e}")
|
|
80
|
+
raise
|
|
81
|
+
|
|
82
|
+
def login(self, username: str, password: str) -> str:
|
|
83
|
+
"""
|
|
84
|
+
Login and get authentication token.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
username: Username
|
|
88
|
+
password: Password
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Access token
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
>>> token = client.login("user@example.com", "password")
|
|
95
|
+
"""
|
|
96
|
+
response = self._request(
|
|
97
|
+
"POST", "/api/v1/auth/login", json={"username": username, "password": password}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self.token = response["access_token"]
|
|
101
|
+
self.session.headers.update({"Authorization": f"Bearer {self.token}"})
|
|
102
|
+
|
|
103
|
+
logger.info(f"Logged in as {username}")
|
|
104
|
+
|
|
105
|
+
return self.token
|
|
106
|
+
|
|
107
|
+
# Experiment endpoints
|
|
108
|
+
|
|
109
|
+
def create_experiment(
|
|
110
|
+
self,
|
|
111
|
+
name: str,
|
|
112
|
+
search_space: Dict[str, Any],
|
|
113
|
+
optimizer: str = "genetic",
|
|
114
|
+
budget: int = 500,
|
|
115
|
+
config: Optional[Dict[str, Any]] = None,
|
|
116
|
+
) -> Dict[str, Any]:
|
|
117
|
+
"""
|
|
118
|
+
Create a new experiment.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
name: Experiment name
|
|
122
|
+
search_space: Search space configuration
|
|
123
|
+
optimizer: Optimizer type
|
|
124
|
+
budget: Evaluation budget
|
|
125
|
+
config: Optional additional configuration
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Created experiment
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
>>> exp = client.create_experiment(
|
|
132
|
+
... "cifar10-search",
|
|
133
|
+
... search_space={"layers": [...]},
|
|
134
|
+
... optimizer="genetic"
|
|
135
|
+
... )
|
|
136
|
+
"""
|
|
137
|
+
return self._request(
|
|
138
|
+
"POST",
|
|
139
|
+
"/api/v1/experiments",
|
|
140
|
+
json={
|
|
141
|
+
"name": name,
|
|
142
|
+
"search_space": search_space,
|
|
143
|
+
"optimizer": optimizer,
|
|
144
|
+
"budget": budget,
|
|
145
|
+
"config": config or {},
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def list_experiments(
|
|
150
|
+
self, status: Optional[str] = None, limit: int = 100, offset: int = 0
|
|
151
|
+
) -> List[Dict[str, Any]]:
|
|
152
|
+
"""
|
|
153
|
+
List experiments.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
status: Optional status filter
|
|
157
|
+
limit: Maximum results
|
|
158
|
+
offset: Offset for pagination
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
List of experiments
|
|
162
|
+
|
|
163
|
+
Example:
|
|
164
|
+
>>> experiments = client.list_experiments(status="running")
|
|
165
|
+
"""
|
|
166
|
+
params = {"limit": limit, "offset": offset}
|
|
167
|
+
if status:
|
|
168
|
+
params["status"] = status
|
|
169
|
+
|
|
170
|
+
return self._request("GET", "/api/v1/experiments", params=params)
|
|
171
|
+
|
|
172
|
+
def get_experiment(self, experiment_id: str) -> Dict[str, Any]:
|
|
173
|
+
"""
|
|
174
|
+
Get experiment details.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
experiment_id: Experiment ID
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Experiment details
|
|
181
|
+
|
|
182
|
+
Example:
|
|
183
|
+
>>> exp = client.get_experiment("exp_abc123")
|
|
184
|
+
"""
|
|
185
|
+
return self._request("GET", f"/api/v1/experiments/{experiment_id}")
|
|
186
|
+
|
|
187
|
+
def start_experiment(self, experiment_id: str) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Start an experiment.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
experiment_id: Experiment ID
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Updated experiment
|
|
196
|
+
|
|
197
|
+
Example:
|
|
198
|
+
>>> client.start_experiment("exp_abc123")
|
|
199
|
+
"""
|
|
200
|
+
return self._request("POST", f"/api/v1/experiments/{experiment_id}/start")
|
|
201
|
+
|
|
202
|
+
def stop_experiment(self, experiment_id: str) -> Dict[str, Any]:
|
|
203
|
+
"""
|
|
204
|
+
Stop a running experiment.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
experiment_id: Experiment ID
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Updated experiment
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
>>> client.stop_experiment("exp_abc123")
|
|
214
|
+
"""
|
|
215
|
+
return self._request("POST", f"/api/v1/experiments/{experiment_id}/stop")
|
|
216
|
+
|
|
217
|
+
def delete_experiment(self, experiment_id: str) -> None:
|
|
218
|
+
"""
|
|
219
|
+
Delete an experiment.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
experiment_id: Experiment ID
|
|
223
|
+
|
|
224
|
+
Example:
|
|
225
|
+
>>> client.delete_experiment("exp_abc123")
|
|
226
|
+
"""
|
|
227
|
+
self._request("DELETE", f"/api/v1/experiments/{experiment_id}")
|
|
228
|
+
logger.info(f"Deleted experiment: {experiment_id}")
|
|
229
|
+
|
|
230
|
+
# Architecture endpoints
|
|
231
|
+
|
|
232
|
+
def list_architectures(
|
|
233
|
+
self,
|
|
234
|
+
experiment_id: Optional[str] = None,
|
|
235
|
+
min_fitness: Optional[float] = None,
|
|
236
|
+
limit: int = 100,
|
|
237
|
+
offset: int = 0,
|
|
238
|
+
) -> List[Dict[str, Any]]:
|
|
239
|
+
"""
|
|
240
|
+
List architectures.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
experiment_id: Optional experiment filter
|
|
244
|
+
min_fitness: Optional minimum fitness filter
|
|
245
|
+
limit: Maximum results
|
|
246
|
+
offset: Offset for pagination
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
List of architectures
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
>>> archs = client.list_architectures(
|
|
253
|
+
... experiment_id="exp_abc123",
|
|
254
|
+
... min_fitness=0.9
|
|
255
|
+
... )
|
|
256
|
+
"""
|
|
257
|
+
params = {"limit": limit, "offset": offset}
|
|
258
|
+
if experiment_id:
|
|
259
|
+
params["experiment_id"] = experiment_id
|
|
260
|
+
if min_fitness is not None:
|
|
261
|
+
params["min_fitness"] = min_fitness
|
|
262
|
+
|
|
263
|
+
return self._request("GET", "/api/v1/architectures", params=params)
|
|
264
|
+
|
|
265
|
+
def get_architecture(self, architecture_id: str) -> Dict[str, Any]:
|
|
266
|
+
"""
|
|
267
|
+
Get architecture details.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
architecture_id: Architecture ID
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Architecture details
|
|
274
|
+
|
|
275
|
+
Example:
|
|
276
|
+
>>> arch = client.get_architecture("arch_xyz789")
|
|
277
|
+
"""
|
|
278
|
+
return self._request("GET", f"/api/v1/architectures/{architecture_id}")
|
|
279
|
+
|
|
280
|
+
# Utility methods
|
|
281
|
+
|
|
282
|
+
def health_check(self) -> Dict[str, Any]:
|
|
283
|
+
"""
|
|
284
|
+
Check API health.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Health status
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
>>> health = client.health_check()
|
|
291
|
+
>>> print(health["status"])
|
|
292
|
+
"""
|
|
293
|
+
return self._request("GET", "/health")
|
|
294
|
+
|
|
295
|
+
def get_optimizers(self) -> List[Dict[str, Any]]:
|
|
296
|
+
"""
|
|
297
|
+
Get list of available optimizers.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
List of optimizer information
|
|
301
|
+
|
|
302
|
+
Example:
|
|
303
|
+
>>> optimizers = client.get_optimizers()
|
|
304
|
+
>>> for opt in optimizers:
|
|
305
|
+
... print(opt["name"])
|
|
306
|
+
"""
|
|
307
|
+
return self._request("GET", "/api/v1/optimizers")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def create_client(
|
|
311
|
+
base_url: str = "http://localhost:8000",
|
|
312
|
+
username: Optional[str] = None,
|
|
313
|
+
password: Optional[str] = None,
|
|
314
|
+
) -> MorphMLClient:
|
|
315
|
+
"""
|
|
316
|
+
Create and optionally authenticate API client.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
base_url: Base URL of the API
|
|
320
|
+
username: Optional username for authentication
|
|
321
|
+
password: Optional password for authentication
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
MorphMLClient instance
|
|
325
|
+
|
|
326
|
+
Example:
|
|
327
|
+
>>> client = create_client(
|
|
328
|
+
... "http://localhost:8000",
|
|
329
|
+
... "user@example.com",
|
|
330
|
+
... "password"
|
|
331
|
+
... )
|
|
332
|
+
"""
|
|
333
|
+
client = MorphMLClient(base_url)
|
|
334
|
+
|
|
335
|
+
if username and password:
|
|
336
|
+
client.login(username, password)
|
|
337
|
+
|
|
338
|
+
return client
|
morphml/api/models.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Pydantic models for API requests and responses."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ExperimentCreate(BaseModel):
|
|
10
|
+
"""Request model for creating an experiment."""
|
|
11
|
+
|
|
12
|
+
name: str = Field(..., description="Experiment name")
|
|
13
|
+
search_space_config: Dict[str, Any] = Field(..., description="Search space configuration")
|
|
14
|
+
optimizer_type: str = Field("genetic", description="Optimizer type")
|
|
15
|
+
optimizer_config: Dict[str, Any] = Field(
|
|
16
|
+
default_factory=dict, description="Optimizer configuration"
|
|
17
|
+
)
|
|
18
|
+
budget: int = Field(100, description="Evaluation budget")
|
|
19
|
+
constraints: Optional[List[Dict[str, Any]]] = Field(None, description="Constraints")
|
|
20
|
+
|
|
21
|
+
class Config:
|
|
22
|
+
schema_extra = {
|
|
23
|
+
"example": {
|
|
24
|
+
"name": "cifar10-search",
|
|
25
|
+
"search_space_config": {
|
|
26
|
+
"layers": [
|
|
27
|
+
{"type": "input", "shape": [3, 32, 32]},
|
|
28
|
+
{"type": "conv2d", "filters": [32, 64], "kernel_size": 3},
|
|
29
|
+
{"type": "flatten"},
|
|
30
|
+
{"type": "dense", "units": [128, 256]},
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"optimizer_type": "genetic",
|
|
34
|
+
"optimizer_config": {"population_size": 20, "num_generations": 50},
|
|
35
|
+
"budget": 1000,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ExperimentResponse(BaseModel):
|
|
41
|
+
"""Response model for experiment."""
|
|
42
|
+
|
|
43
|
+
id: str
|
|
44
|
+
name: str
|
|
45
|
+
status: str # pending, running, completed, failed
|
|
46
|
+
created_at: datetime
|
|
47
|
+
started_at: Optional[datetime] = None
|
|
48
|
+
completed_at: Optional[datetime] = None
|
|
49
|
+
best_fitness: Optional[float] = None
|
|
50
|
+
generations_completed: int = 0
|
|
51
|
+
total_generations: int
|
|
52
|
+
|
|
53
|
+
class Config:
|
|
54
|
+
schema_extra = {
|
|
55
|
+
"example": {
|
|
56
|
+
"id": "exp_123abc",
|
|
57
|
+
"name": "cifar10-search",
|
|
58
|
+
"status": "running",
|
|
59
|
+
"created_at": "2024-11-11T05:00:00Z",
|
|
60
|
+
"started_at": "2024-11-11T05:01:00Z",
|
|
61
|
+
"best_fitness": 0.9523,
|
|
62
|
+
"generations_completed": 25,
|
|
63
|
+
"total_generations": 50,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ArchitectureResponse(BaseModel):
|
|
69
|
+
"""Response model for architecture."""
|
|
70
|
+
|
|
71
|
+
id: str
|
|
72
|
+
experiment_id: str
|
|
73
|
+
fitness: float
|
|
74
|
+
parameters: int
|
|
75
|
+
depth: int
|
|
76
|
+
nodes: int
|
|
77
|
+
edges: int
|
|
78
|
+
created_at: datetime
|
|
79
|
+
graph_data: Optional[Dict[str, Any]] = None
|
|
80
|
+
|
|
81
|
+
class Config:
|
|
82
|
+
schema_extra = {
|
|
83
|
+
"example": {
|
|
84
|
+
"id": "arch_456def",
|
|
85
|
+
"experiment_id": "exp_123abc",
|
|
86
|
+
"fitness": 0.9523,
|
|
87
|
+
"parameters": 1234567,
|
|
88
|
+
"depth": 8,
|
|
89
|
+
"nodes": 12,
|
|
90
|
+
"edges": 11,
|
|
91
|
+
"created_at": "2024-11-11T05:15:00Z",
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class SearchSpaceCreate(BaseModel):
|
|
97
|
+
"""Request model for creating a search space."""
|
|
98
|
+
|
|
99
|
+
name: str
|
|
100
|
+
layers: List[Dict[str, Any]]
|
|
101
|
+
constraints: Optional[List[Dict[str, Any]]] = None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class OptimizerInfo(BaseModel):
|
|
105
|
+
"""Information about an optimizer."""
|
|
106
|
+
|
|
107
|
+
name: str
|
|
108
|
+
type: str
|
|
109
|
+
description: str
|
|
110
|
+
parameters: Dict[str, Any]
|
|
111
|
+
|
|
112
|
+
class Config:
|
|
113
|
+
schema_extra = {
|
|
114
|
+
"example": {
|
|
115
|
+
"name": "GeneticAlgorithm",
|
|
116
|
+
"type": "evolutionary",
|
|
117
|
+
"description": "Genetic algorithm for neural architecture search",
|
|
118
|
+
"parameters": {
|
|
119
|
+
"population_size": {"type": "int", "default": 20},
|
|
120
|
+
"num_generations": {"type": "int", "default": 50},
|
|
121
|
+
"mutation_rate": {"type": "float", "default": 0.2},
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class HealthResponse(BaseModel):
|
|
128
|
+
"""Health check response."""
|
|
129
|
+
|
|
130
|
+
status: str
|
|
131
|
+
version: str
|
|
132
|
+
timestamp: datetime
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Rate limiting middleware for MorphML API.
|
|
2
|
+
|
|
3
|
+
Prevents API abuse by limiting request rates per client.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
>>> from morphml.api.rate_limit import RateLimitMiddleware
|
|
7
|
+
>>> app.add_middleware(RateLimitMiddleware, requests_per_minute=60)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from typing import Dict, Tuple
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from fastapi import HTTPException, Request, status
|
|
16
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
17
|
+
|
|
18
|
+
FASTAPI_AVAILABLE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
FASTAPI_AVAILABLE = False
|
|
21
|
+
Request = None
|
|
22
|
+
HTTPException = None
|
|
23
|
+
BaseHTTPMiddleware = None
|
|
24
|
+
|
|
25
|
+
from morphml.logging_config import get_logger
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RateLimiter:
|
|
31
|
+
"""
|
|
32
|
+
Simple in-memory rate limiter.
|
|
33
|
+
|
|
34
|
+
Tracks request counts per client IP address.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
requests_per_minute: Maximum requests per minute
|
|
38
|
+
requests: Dictionary tracking requests per IP
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, requests_per_minute: int = 60):
|
|
42
|
+
"""
|
|
43
|
+
Initialize rate limiter.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
requests_per_minute: Maximum requests per minute per IP
|
|
47
|
+
"""
|
|
48
|
+
self.requests_per_minute = requests_per_minute
|
|
49
|
+
self.requests: Dict[str, list] = {}
|
|
50
|
+
self.cleanup_interval = 60 # Cleanup old entries every 60 seconds
|
|
51
|
+
self.last_cleanup = time.time()
|
|
52
|
+
|
|
53
|
+
def is_allowed(self, client_ip: str) -> Tuple[bool, int]:
|
|
54
|
+
"""
|
|
55
|
+
Check if request is allowed for client.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
client_ip: Client IP address
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Tuple of (is_allowed, remaining_requests)
|
|
62
|
+
"""
|
|
63
|
+
now = datetime.now()
|
|
64
|
+
minute_ago = now - timedelta(minutes=1)
|
|
65
|
+
|
|
66
|
+
# Cleanup old entries periodically
|
|
67
|
+
if time.time() - self.last_cleanup > self.cleanup_interval:
|
|
68
|
+
self._cleanup()
|
|
69
|
+
|
|
70
|
+
# Get or create request list for this IP
|
|
71
|
+
if client_ip not in self.requests:
|
|
72
|
+
self.requests[client_ip] = []
|
|
73
|
+
|
|
74
|
+
# Remove requests older than 1 minute
|
|
75
|
+
self.requests[client_ip] = [
|
|
76
|
+
req_time for req_time in self.requests[client_ip] if req_time > minute_ago
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
# Check if limit exceeded
|
|
80
|
+
request_count = len(self.requests[client_ip])
|
|
81
|
+
|
|
82
|
+
if request_count >= self.requests_per_minute:
|
|
83
|
+
return False, 0
|
|
84
|
+
|
|
85
|
+
# Add current request
|
|
86
|
+
self.requests[client_ip].append(now)
|
|
87
|
+
|
|
88
|
+
remaining = self.requests_per_minute - request_count - 1
|
|
89
|
+
|
|
90
|
+
return True, remaining
|
|
91
|
+
|
|
92
|
+
def _cleanup(self):
|
|
93
|
+
"""Remove old entries to prevent memory leak."""
|
|
94
|
+
now = datetime.now()
|
|
95
|
+
minute_ago = now - timedelta(minutes=1)
|
|
96
|
+
|
|
97
|
+
# Remove IPs with no recent requests
|
|
98
|
+
ips_to_remove = []
|
|
99
|
+
for ip, req_times in self.requests.items():
|
|
100
|
+
recent_requests = [t for t in req_times if t > minute_ago]
|
|
101
|
+
if not recent_requests:
|
|
102
|
+
ips_to_remove.append(ip)
|
|
103
|
+
else:
|
|
104
|
+
self.requests[ip] = recent_requests
|
|
105
|
+
|
|
106
|
+
for ip in ips_to_remove:
|
|
107
|
+
del self.requests[ip]
|
|
108
|
+
|
|
109
|
+
self.last_cleanup = time.time()
|
|
110
|
+
|
|
111
|
+
if ips_to_remove:
|
|
112
|
+
logger.debug(f"Cleaned up {len(ips_to_remove)} inactive IPs")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class RateLimitMiddleware(BaseHTTPMiddleware if FASTAPI_AVAILABLE else object):
|
|
116
|
+
"""
|
|
117
|
+
FastAPI middleware for rate limiting.
|
|
118
|
+
|
|
119
|
+
Automatically limits requests per client IP.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
>>> app.add_middleware(
|
|
123
|
+
... RateLimitMiddleware,
|
|
124
|
+
... requests_per_minute=60
|
|
125
|
+
... )
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __init__(self, app, requests_per_minute: int = 60):
|
|
129
|
+
"""
|
|
130
|
+
Initialize middleware.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
app: FastAPI application
|
|
134
|
+
requests_per_minute: Maximum requests per minute
|
|
135
|
+
"""
|
|
136
|
+
if not FASTAPI_AVAILABLE:
|
|
137
|
+
raise ImportError("FastAPI required for rate limiting")
|
|
138
|
+
|
|
139
|
+
super().__init__(app)
|
|
140
|
+
self.limiter = RateLimiter(requests_per_minute)
|
|
141
|
+
logger.info(f"Rate limiting enabled: {requests_per_minute} requests/minute")
|
|
142
|
+
|
|
143
|
+
async def dispatch(self, request: Request, call_next):
|
|
144
|
+
"""
|
|
145
|
+
Process request with rate limiting.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
request: Incoming request
|
|
149
|
+
call_next: Next middleware/handler
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Response
|
|
153
|
+
"""
|
|
154
|
+
# Get client IP
|
|
155
|
+
client_ip = request.client.host
|
|
156
|
+
|
|
157
|
+
# Check rate limit
|
|
158
|
+
allowed, remaining = self.limiter.is_allowed(client_ip)
|
|
159
|
+
|
|
160
|
+
if not allowed:
|
|
161
|
+
logger.warning(f"Rate limit exceeded for {client_ip}")
|
|
162
|
+
raise HTTPException(
|
|
163
|
+
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
164
|
+
detail="Rate limit exceeded. Please try again later.",
|
|
165
|
+
headers={"Retry-After": "60"},
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Process request
|
|
169
|
+
response = await call_next(request)
|
|
170
|
+
|
|
171
|
+
# Add rate limit headers
|
|
172
|
+
response.headers["X-RateLimit-Limit"] = str(self.limiter.requests_per_minute)
|
|
173
|
+
response.headers["X-RateLimit-Remaining"] = str(remaining)
|
|
174
|
+
|
|
175
|
+
return response
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def create_rate_limiter(requests_per_minute: int = 60) -> RateLimiter:
|
|
179
|
+
"""
|
|
180
|
+
Create a rate limiter instance.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
requests_per_minute: Maximum requests per minute
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
RateLimiter instance
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
>>> limiter = create_rate_limiter(100)
|
|
190
|
+
>>> allowed, remaining = limiter.is_allowed("192.168.1.1")
|
|
191
|
+
"""
|
|
192
|
+
return RateLimiter(requests_per_minute)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Benchmarking and comparison tools for optimizer evaluation.
|
|
2
|
+
|
|
3
|
+
This module provides tools to systematically compare and evaluate
|
|
4
|
+
different optimization algorithms.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Optimizer comparison with statistical analysis
|
|
8
|
+
- Convergence visualization
|
|
9
|
+
- Sample efficiency analysis
|
|
10
|
+
- Result reporting
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
>>> from morphml.benchmarking import OptimizerComparison, compare_optimizers
|
|
14
|
+
>>> from morphml.optimizers import GeneticAlgorithm, optimize_with_pso
|
|
15
|
+
>>>
|
|
16
|
+
>>> # Quick comparison
|
|
17
|
+
>>> results = compare_optimizers(
|
|
18
|
+
... optimizers={
|
|
19
|
+
... 'GA': GeneticAlgorithm(space, config),
|
|
20
|
+
... 'PSO': ParticleSwarmOptimizer(space, config)
|
|
21
|
+
... },
|
|
22
|
+
... search_space=space,
|
|
23
|
+
... evaluator=my_evaluator,
|
|
24
|
+
... budget=100
|
|
25
|
+
... )
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from morphml.benchmarking.comparison import (
|
|
29
|
+
OptimizerComparison,
|
|
30
|
+
compare_optimizers,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"OptimizerComparison",
|
|
35
|
+
"compare_optimizers",
|
|
36
|
+
]
|