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,459 @@
|
|
|
1
|
+
"""PostgreSQL database backend for experiment results.
|
|
2
|
+
|
|
3
|
+
Stores structured experiment data with SQL queries for analysis.
|
|
4
|
+
|
|
5
|
+
Author: Eshan Roy <eshanized@proton.me>
|
|
6
|
+
Organization: TONMOY INFRASTRUCTURE & VISION
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import datetime
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from sqlalchemy import (
|
|
16
|
+
Column,
|
|
17
|
+
DateTime,
|
|
18
|
+
Float,
|
|
19
|
+
Integer,
|
|
20
|
+
String,
|
|
21
|
+
Text,
|
|
22
|
+
create_engine,
|
|
23
|
+
)
|
|
24
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
25
|
+
from sqlalchemy.orm import Session, sessionmaker
|
|
26
|
+
|
|
27
|
+
SQLALCHEMY_AVAILABLE = True
|
|
28
|
+
Base = declarative_base()
|
|
29
|
+
except ImportError:
|
|
30
|
+
SQLALCHEMY_AVAILABLE = False
|
|
31
|
+
Base = object # type: ignore
|
|
32
|
+
|
|
33
|
+
from morphml.core.graph import ModelGraph
|
|
34
|
+
from morphml.exceptions import DistributedError
|
|
35
|
+
from morphml.logging_config import get_logger
|
|
36
|
+
|
|
37
|
+
logger = get_logger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if SQLALCHEMY_AVAILABLE:
|
|
41
|
+
|
|
42
|
+
class Experiment(Base): # type: ignore
|
|
43
|
+
"""
|
|
44
|
+
Experiment table.
|
|
45
|
+
|
|
46
|
+
Stores high-level experiment metadata.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
__tablename__ = "experiments"
|
|
50
|
+
|
|
51
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
52
|
+
name = Column(String(255), unique=True, nullable=False, index=True)
|
|
53
|
+
search_space = Column(Text) # JSON string
|
|
54
|
+
optimizer = Column(String(100))
|
|
55
|
+
config = Column(Text) # JSON string
|
|
56
|
+
status = Column(String(50), default="running") # running, completed, failed
|
|
57
|
+
num_evaluations = Column(Integer, default=0)
|
|
58
|
+
best_fitness = Column(Float)
|
|
59
|
+
created_at = Column(DateTime, default=datetime.datetime.utcnow)
|
|
60
|
+
updated_at = Column(
|
|
61
|
+
DateTime,
|
|
62
|
+
default=datetime.datetime.utcnow,
|
|
63
|
+
onupdate=datetime.datetime.utcnow,
|
|
64
|
+
)
|
|
65
|
+
completed_at = Column(DateTime)
|
|
66
|
+
|
|
67
|
+
class Architecture(Base): # type: ignore
|
|
68
|
+
"""
|
|
69
|
+
Architecture evaluation table.
|
|
70
|
+
|
|
71
|
+
Stores individual architecture evaluations.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
__tablename__ = "architectures"
|
|
75
|
+
|
|
76
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
77
|
+
experiment_id = Column(Integer, nullable=False, index=True)
|
|
78
|
+
architecture_hash = Column(String(64), unique=True, nullable=False, index=True)
|
|
79
|
+
architecture_json = Column(Text, nullable=False)
|
|
80
|
+
fitness = Column(Float, nullable=False, index=True)
|
|
81
|
+
metrics = Column(Text) # JSON string
|
|
82
|
+
generation = Column(Integer, default=0, index=True)
|
|
83
|
+
worker_id = Column(String(100))
|
|
84
|
+
duration = Column(Float) # seconds
|
|
85
|
+
evaluated_at = Column(DateTime, default=datetime.datetime.utcnow)
|
|
86
|
+
|
|
87
|
+
else:
|
|
88
|
+
# Dummy classes when SQLAlchemy not available
|
|
89
|
+
class Experiment: # type: ignore
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
class Architecture: # type: ignore
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class DatabaseManager:
|
|
97
|
+
"""
|
|
98
|
+
Manage PostgreSQL database for experiments.
|
|
99
|
+
|
|
100
|
+
Provides persistent storage for experiment results with SQL queries.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
connection_string: PostgreSQL connection string
|
|
104
|
+
Format: postgresql://user:password@host:port/database
|
|
105
|
+
pool_size: Connection pool size (default: 20)
|
|
106
|
+
echo: Echo SQL queries for debugging (default: False)
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
>>> db = DatabaseManager('postgresql://localhost/morphml')
|
|
110
|
+
>>> exp_id = db.create_experiment('cifar10_search', {})
|
|
111
|
+
>>> db.save_architecture(exp_id, graph, 0.95, metrics, 1, 'worker-1')
|
|
112
|
+
>>> best = db.get_best_architectures(exp_id, top_k=10)
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
connection_string: str,
|
|
118
|
+
pool_size: int = 20,
|
|
119
|
+
echo: bool = False,
|
|
120
|
+
):
|
|
121
|
+
"""Initialize database manager."""
|
|
122
|
+
if not SQLALCHEMY_AVAILABLE:
|
|
123
|
+
raise DistributedError(
|
|
124
|
+
"SQLAlchemy not available. Install with: pip install sqlalchemy psycopg2-binary"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
self.connection_string = connection_string
|
|
128
|
+
self.engine = create_engine(connection_string, pool_size=pool_size, echo=echo)
|
|
129
|
+
|
|
130
|
+
# Create tables
|
|
131
|
+
Base.metadata.create_all(self.engine)
|
|
132
|
+
|
|
133
|
+
# Create session factory
|
|
134
|
+
self.Session = sessionmaker(bind=self.engine)
|
|
135
|
+
|
|
136
|
+
logger.info(f"Connected to database: {connection_string.split('@')[-1]}")
|
|
137
|
+
|
|
138
|
+
def get_session(self) -> Session:
|
|
139
|
+
"""Get a new database session."""
|
|
140
|
+
return self.Session()
|
|
141
|
+
|
|
142
|
+
def create_experiment(self, name: str, config: Dict[str, Any]) -> int:
|
|
143
|
+
"""
|
|
144
|
+
Create new experiment.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
name: Experiment name (must be unique)
|
|
148
|
+
config: Experiment configuration
|
|
149
|
+
- search_space: Search space definition
|
|
150
|
+
- optimizer: Optimizer name
|
|
151
|
+
- Any other config fields
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Experiment ID
|
|
155
|
+
"""
|
|
156
|
+
session = self.get_session()
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
exp = Experiment(
|
|
160
|
+
name=name,
|
|
161
|
+
search_space=json.dumps(config.get("search_space", {})),
|
|
162
|
+
optimizer=config.get("optimizer", "unknown"),
|
|
163
|
+
config=json.dumps(config),
|
|
164
|
+
status="running",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
session.add(exp)
|
|
168
|
+
session.commit()
|
|
169
|
+
|
|
170
|
+
exp_id = exp.id
|
|
171
|
+
|
|
172
|
+
logger.info(f"Created experiment '{name}' with ID {exp_id}")
|
|
173
|
+
|
|
174
|
+
return exp_id
|
|
175
|
+
|
|
176
|
+
finally:
|
|
177
|
+
session.close()
|
|
178
|
+
|
|
179
|
+
def save_architecture(
|
|
180
|
+
self,
|
|
181
|
+
experiment_id: int,
|
|
182
|
+
architecture: ModelGraph,
|
|
183
|
+
fitness: float,
|
|
184
|
+
metrics: Dict[str, float],
|
|
185
|
+
generation: int = 0,
|
|
186
|
+
worker_id: str = "unknown",
|
|
187
|
+
duration: float = 0.0,
|
|
188
|
+
) -> int:
|
|
189
|
+
"""
|
|
190
|
+
Save architecture evaluation.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
experiment_id: Experiment ID
|
|
194
|
+
architecture: ModelGraph
|
|
195
|
+
fitness: Fitness score
|
|
196
|
+
metrics: Additional metrics (accuracy, params, etc.)
|
|
197
|
+
generation: Generation number
|
|
198
|
+
worker_id: Worker that evaluated
|
|
199
|
+
duration: Evaluation duration (seconds)
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Architecture ID
|
|
203
|
+
"""
|
|
204
|
+
session = self.get_session()
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
# Compute architecture hash
|
|
208
|
+
arch_hash = self._hash_architecture(architecture)
|
|
209
|
+
|
|
210
|
+
# Check if already evaluated (deduplication)
|
|
211
|
+
existing = session.query(Architecture).filter_by(architecture_hash=arch_hash).first()
|
|
212
|
+
|
|
213
|
+
if existing:
|
|
214
|
+
logger.debug(f"Architecture {arch_hash[:12]} already evaluated")
|
|
215
|
+
return existing.id
|
|
216
|
+
|
|
217
|
+
# Save new architecture
|
|
218
|
+
arch = Architecture(
|
|
219
|
+
experiment_id=experiment_id,
|
|
220
|
+
architecture_hash=arch_hash,
|
|
221
|
+
architecture_json=architecture.to_json(),
|
|
222
|
+
fitness=fitness,
|
|
223
|
+
metrics=json.dumps(metrics),
|
|
224
|
+
generation=generation,
|
|
225
|
+
worker_id=worker_id,
|
|
226
|
+
duration=duration,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
session.add(arch)
|
|
230
|
+
session.commit()
|
|
231
|
+
|
|
232
|
+
# Update experiment stats
|
|
233
|
+
self._update_experiment_stats(experiment_id, fitness)
|
|
234
|
+
|
|
235
|
+
logger.debug(
|
|
236
|
+
f"Saved architecture {arch_hash[:12]}: "
|
|
237
|
+
f"fitness={fitness:.4f}, generation={generation}"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return arch.id
|
|
241
|
+
|
|
242
|
+
finally:
|
|
243
|
+
session.close()
|
|
244
|
+
|
|
245
|
+
def get_best_architectures(self, experiment_id: int, top_k: int = 10) -> List[Architecture]:
|
|
246
|
+
"""
|
|
247
|
+
Get top-k architectures by fitness.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
experiment_id: Experiment ID
|
|
251
|
+
top_k: Number of architectures to return
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
List of Architecture objects
|
|
255
|
+
"""
|
|
256
|
+
session = self.get_session()
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
architectures = (
|
|
260
|
+
session.query(Architecture)
|
|
261
|
+
.filter_by(experiment_id=experiment_id)
|
|
262
|
+
.order_by(Architecture.fitness.desc())
|
|
263
|
+
.limit(top_k)
|
|
264
|
+
.all()
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return architectures
|
|
268
|
+
|
|
269
|
+
finally:
|
|
270
|
+
session.close()
|
|
271
|
+
|
|
272
|
+
def get_architecture_by_hash(self, arch_hash: str) -> Optional[Architecture]:
|
|
273
|
+
"""
|
|
274
|
+
Get architecture by hash (for deduplication).
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
arch_hash: Architecture hash
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Architecture object or None
|
|
281
|
+
"""
|
|
282
|
+
session = self.get_session()
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
return session.query(Architecture).filter_by(architecture_hash=arch_hash).first()
|
|
286
|
+
|
|
287
|
+
finally:
|
|
288
|
+
session.close()
|
|
289
|
+
|
|
290
|
+
def get_experiment(self, experiment_id: int) -> Optional[Experiment]:
|
|
291
|
+
"""
|
|
292
|
+
Get experiment by ID.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
experiment_id: Experiment ID
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Experiment object or None
|
|
299
|
+
"""
|
|
300
|
+
session = self.get_session()
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
return session.query(Experiment).filter_by(id=experiment_id).first()
|
|
304
|
+
|
|
305
|
+
finally:
|
|
306
|
+
session.close()
|
|
307
|
+
|
|
308
|
+
def get_experiment_by_name(self, name: str) -> Optional[Experiment]:
|
|
309
|
+
"""
|
|
310
|
+
Get experiment by name.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
name: Experiment name
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Experiment object or None
|
|
317
|
+
"""
|
|
318
|
+
session = self.get_session()
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
return session.query(Experiment).filter_by(name=name).first()
|
|
322
|
+
|
|
323
|
+
finally:
|
|
324
|
+
session.close()
|
|
325
|
+
|
|
326
|
+
def update_experiment_status(self, experiment_id: int, status: str) -> None:
|
|
327
|
+
"""
|
|
328
|
+
Update experiment status.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
experiment_id: Experiment ID
|
|
332
|
+
status: New status ('running', 'completed', 'failed')
|
|
333
|
+
"""
|
|
334
|
+
session = self.get_session()
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
exp = session.query(Experiment).filter_by(id=experiment_id).first()
|
|
338
|
+
|
|
339
|
+
if exp:
|
|
340
|
+
exp.status = status
|
|
341
|
+
|
|
342
|
+
if status == "completed":
|
|
343
|
+
exp.completed_at = datetime.datetime.utcnow()
|
|
344
|
+
|
|
345
|
+
session.commit()
|
|
346
|
+
|
|
347
|
+
logger.info(f"Experiment {experiment_id} status: {status}")
|
|
348
|
+
|
|
349
|
+
finally:
|
|
350
|
+
session.close()
|
|
351
|
+
|
|
352
|
+
def get_experiment_statistics(self, experiment_id: int) -> Dict[str, Any]:
|
|
353
|
+
"""
|
|
354
|
+
Get experiment statistics.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
experiment_id: Experiment ID
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Statistics dictionary
|
|
361
|
+
"""
|
|
362
|
+
session = self.get_session()
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
exp = session.query(Experiment).filter_by(id=experiment_id).first()
|
|
366
|
+
|
|
367
|
+
if not exp:
|
|
368
|
+
return {}
|
|
369
|
+
|
|
370
|
+
architectures = session.query(Architecture).filter_by(experiment_id=experiment_id).all()
|
|
371
|
+
|
|
372
|
+
if not architectures:
|
|
373
|
+
return {
|
|
374
|
+
"experiment_id": experiment_id,
|
|
375
|
+
"num_evaluations": 0,
|
|
376
|
+
"status": exp.status,
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
fitnesses = [a.fitness for a in architectures]
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
"experiment_id": experiment_id,
|
|
383
|
+
"name": exp.name,
|
|
384
|
+
"status": exp.status,
|
|
385
|
+
"num_evaluations": len(architectures),
|
|
386
|
+
"best_fitness": max(fitnesses),
|
|
387
|
+
"avg_fitness": sum(fitnesses) / len(fitnesses),
|
|
388
|
+
"min_fitness": min(fitnesses),
|
|
389
|
+
"created_at": exp.created_at.isoformat() if exp.created_at else None,
|
|
390
|
+
"updated_at": exp.updated_at.isoformat() if exp.updated_at else None,
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
finally:
|
|
394
|
+
session.close()
|
|
395
|
+
|
|
396
|
+
def _hash_architecture(self, architecture: ModelGraph) -> str:
|
|
397
|
+
"""
|
|
398
|
+
Compute unique hash for architecture.
|
|
399
|
+
|
|
400
|
+
Uses SHA256 of JSON representation.
|
|
401
|
+
"""
|
|
402
|
+
arch_json = architecture.to_json()
|
|
403
|
+
return hashlib.sha256(arch_json.encode()).hexdigest()
|
|
404
|
+
|
|
405
|
+
def _update_experiment_stats(self, experiment_id: int, new_fitness: float) -> None:
|
|
406
|
+
"""Update experiment statistics."""
|
|
407
|
+
session = self.get_session()
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
exp = session.query(Experiment).filter_by(id=experiment_id).first()
|
|
411
|
+
|
|
412
|
+
if exp:
|
|
413
|
+
exp.num_evaluations = (exp.num_evaluations or 0) + 1
|
|
414
|
+
|
|
415
|
+
if exp.best_fitness is None or new_fitness > exp.best_fitness:
|
|
416
|
+
exp.best_fitness = new_fitness
|
|
417
|
+
|
|
418
|
+
session.commit()
|
|
419
|
+
|
|
420
|
+
finally:
|
|
421
|
+
session.close()
|
|
422
|
+
|
|
423
|
+
def list_experiments(self) -> List[Experiment]:
|
|
424
|
+
"""
|
|
425
|
+
List all experiments.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
List of Experiment objects
|
|
429
|
+
"""
|
|
430
|
+
session = self.get_session()
|
|
431
|
+
|
|
432
|
+
try:
|
|
433
|
+
return session.query(Experiment).order_by(Experiment.created_at.desc()).all()
|
|
434
|
+
|
|
435
|
+
finally:
|
|
436
|
+
session.close()
|
|
437
|
+
|
|
438
|
+
def delete_experiment(self, experiment_id: int) -> None:
|
|
439
|
+
"""
|
|
440
|
+
Delete experiment and all associated architectures.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
experiment_id: Experiment ID
|
|
444
|
+
"""
|
|
445
|
+
session = self.get_session()
|
|
446
|
+
|
|
447
|
+
try:
|
|
448
|
+
# Delete architectures
|
|
449
|
+
session.query(Architecture).filter_by(experiment_id=experiment_id).delete()
|
|
450
|
+
|
|
451
|
+
# Delete experiment
|
|
452
|
+
session.query(Experiment).filter_by(id=experiment_id).delete()
|
|
453
|
+
|
|
454
|
+
session.commit()
|
|
455
|
+
|
|
456
|
+
logger.info(f"Deleted experiment {experiment_id}")
|
|
457
|
+
|
|
458
|
+
finally:
|
|
459
|
+
session.close()
|