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,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()