alchemist-nrel 0.2.1__py3-none-any.whl → 0.3.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.
- alchemist_core/__init__.py +14 -7
- alchemist_core/acquisition/botorch_acquisition.py +14 -6
- alchemist_core/audit_log.py +594 -0
- alchemist_core/data/experiment_manager.py +69 -5
- alchemist_core/models/botorch_model.py +6 -4
- alchemist_core/models/sklearn_model.py +44 -6
- alchemist_core/session.py +600 -8
- alchemist_core/utils/doe.py +200 -0
- {alchemist_nrel-0.2.1.dist-info → alchemist_nrel-0.3.0.dist-info}/METADATA +57 -40
- alchemist_nrel-0.3.0.dist-info/RECORD +66 -0
- {alchemist_nrel-0.2.1.dist-info → alchemist_nrel-0.3.0.dist-info}/entry_points.txt +1 -0
- {alchemist_nrel-0.2.1.dist-info → alchemist_nrel-0.3.0.dist-info}/top_level.txt +1 -0
- api/main.py +19 -3
- api/models/requests.py +71 -0
- api/models/responses.py +144 -0
- api/routers/experiments.py +117 -5
- api/routers/sessions.py +329 -10
- api/routers/visualizations.py +10 -5
- api/services/session_store.py +210 -54
- api/static/NEW_ICON.ico +0 -0
- api/static/NEW_ICON.png +0 -0
- api/static/NEW_LOGO_DARK.png +0 -0
- api/static/NEW_LOGO_LIGHT.png +0 -0
- api/static/assets/api-vcoXEqyq.js +1 -0
- api/static/assets/index-C0_glioA.js +4084 -0
- api/static/assets/index-CB4V1LI5.css +1 -0
- api/static/index.html +14 -0
- api/static/vite.svg +1 -0
- run_api.py +55 -0
- ui/gpr_panel.py +7 -2
- ui/notifications.py +197 -10
- ui/ui.py +1117 -68
- ui/variables_setup.py +47 -2
- ui/visualizations.py +60 -3
- alchemist_nrel-0.2.1.dist-info/RECORD +0 -54
- {alchemist_nrel-0.2.1.dist-info → alchemist_nrel-0.3.0.dist-info}/WHEEL +0 -0
- {alchemist_nrel-0.2.1.dist-info → alchemist_nrel-0.3.0.dist-info}/licenses/LICENSE +0 -0
api/models/responses.py
CHANGED
|
@@ -88,6 +88,31 @@ class SessionInfoResponse(BaseModel):
|
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
class SessionStateResponse(BaseModel):
|
|
92
|
+
"""Current state of an optimization session for monitoring."""
|
|
93
|
+
session_id: str
|
|
94
|
+
n_variables: int
|
|
95
|
+
n_experiments: int
|
|
96
|
+
model_trained: bool
|
|
97
|
+
model_backend: Optional[str] = None
|
|
98
|
+
last_suggestion: Optional[Dict[str, Any]] = None
|
|
99
|
+
last_acquisition_value: Optional[float] = None
|
|
100
|
+
|
|
101
|
+
model_config = ConfigDict(
|
|
102
|
+
json_schema_extra={
|
|
103
|
+
"example": {
|
|
104
|
+
"session_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
105
|
+
"n_variables": 2,
|
|
106
|
+
"n_experiments": 15,
|
|
107
|
+
"model_trained": True,
|
|
108
|
+
"model_backend": "botorch",
|
|
109
|
+
"last_suggestion": {"temperature": 385.2, "flow_rate": 4.3},
|
|
110
|
+
"last_acquisition_value": 0.025
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
91
116
|
# ============================================================
|
|
92
117
|
# Variable Models
|
|
93
118
|
# ============================================================
|
|
@@ -112,6 +137,11 @@ class ExperimentResponse(BaseModel):
|
|
|
112
137
|
"""Response when adding an experiment."""
|
|
113
138
|
message: str = "Experiment added successfully"
|
|
114
139
|
n_experiments: int
|
|
140
|
+
model_trained: bool = Field(default=False, description="Whether model was auto-trained")
|
|
141
|
+
training_metrics: Optional[Dict[str, Any]] = Field(
|
|
142
|
+
None,
|
|
143
|
+
description="Training metrics if auto-train was enabled"
|
|
144
|
+
)
|
|
115
145
|
|
|
116
146
|
|
|
117
147
|
class ExperimentsListResponse(BaseModel):
|
|
@@ -146,6 +176,30 @@ class ExperimentsSummaryResponse(BaseModel):
|
|
|
146
176
|
)
|
|
147
177
|
|
|
148
178
|
|
|
179
|
+
# ============================================================
|
|
180
|
+
# Initial Design (DoE) Models
|
|
181
|
+
# ============================================================
|
|
182
|
+
|
|
183
|
+
class InitialDesignResponse(BaseModel):
|
|
184
|
+
"""Response containing generated initial design points."""
|
|
185
|
+
points: List[Dict[str, Any]] = Field(..., description="Generated experimental points")
|
|
186
|
+
method: str = Field(..., description="Sampling method used")
|
|
187
|
+
n_points: int = Field(..., description="Number of points generated")
|
|
188
|
+
|
|
189
|
+
model_config = ConfigDict(
|
|
190
|
+
json_schema_extra={
|
|
191
|
+
"example": {
|
|
192
|
+
"points": [
|
|
193
|
+
{"temperature": 350.2, "flow_rate": 2.47},
|
|
194
|
+
{"temperature": 421.8, "flow_rate": 7.92}
|
|
195
|
+
],
|
|
196
|
+
"method": "lhs",
|
|
197
|
+
"n_points": 2
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
149
203
|
# ============================================================
|
|
150
204
|
# Model Training Models
|
|
151
205
|
# ============================================================
|
|
@@ -290,3 +344,93 @@ class ErrorResponse(BaseModel):
|
|
|
290
344
|
}
|
|
291
345
|
}
|
|
292
346
|
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# ============================================================
|
|
350
|
+
# Audit Log & Session Management Responses
|
|
351
|
+
# ============================================================
|
|
352
|
+
|
|
353
|
+
class SessionMetadataResponse(BaseModel):
|
|
354
|
+
"""Response containing session metadata."""
|
|
355
|
+
session_id: str
|
|
356
|
+
name: str
|
|
357
|
+
created_at: str
|
|
358
|
+
last_modified: str
|
|
359
|
+
description: str
|
|
360
|
+
tags: List[str]
|
|
361
|
+
|
|
362
|
+
model_config = ConfigDict(
|
|
363
|
+
json_schema_extra={
|
|
364
|
+
"example": {
|
|
365
|
+
"session_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
366
|
+
"name": "Catalyst_Screening_Nov2025",
|
|
367
|
+
"created_at": "2025-11-19T09:00:00",
|
|
368
|
+
"last_modified": "2025-11-19T14:30:00",
|
|
369
|
+
"description": "Pt/Pd ratio optimization",
|
|
370
|
+
"tags": ["catalyst", "CO2"]
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class AuditEntryResponse(BaseModel):
|
|
377
|
+
"""Response containing a single audit log entry."""
|
|
378
|
+
timestamp: str
|
|
379
|
+
entry_type: str
|
|
380
|
+
parameters: Dict[str, Any]
|
|
381
|
+
hash: str
|
|
382
|
+
notes: str
|
|
383
|
+
|
|
384
|
+
model_config = ConfigDict(
|
|
385
|
+
json_schema_extra={
|
|
386
|
+
"example": {
|
|
387
|
+
"timestamp": "2025-11-19T09:15:00",
|
|
388
|
+
"entry_type": "data_locked",
|
|
389
|
+
"parameters": {
|
|
390
|
+
"n_experiments": 15,
|
|
391
|
+
"variables": [],
|
|
392
|
+
"data_hash": "abc123"
|
|
393
|
+
},
|
|
394
|
+
"hash": "a1b2c3d4",
|
|
395
|
+
"notes": "Initial screening dataset"
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class AuditLogResponse(BaseModel):
|
|
402
|
+
"""Response containing complete audit log."""
|
|
403
|
+
entries: List[AuditEntryResponse]
|
|
404
|
+
n_entries: int
|
|
405
|
+
|
|
406
|
+
model_config = ConfigDict(
|
|
407
|
+
json_schema_extra={
|
|
408
|
+
"example": {
|
|
409
|
+
"entries": [],
|
|
410
|
+
"n_entries": 0
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class LockDecisionResponse(BaseModel):
|
|
417
|
+
"""Response after locking a decision."""
|
|
418
|
+
success: bool
|
|
419
|
+
entry: AuditEntryResponse
|
|
420
|
+
message: str
|
|
421
|
+
|
|
422
|
+
model_config = ConfigDict(
|
|
423
|
+
json_schema_extra={
|
|
424
|
+
"example": {
|
|
425
|
+
"success": True,
|
|
426
|
+
"entry": {
|
|
427
|
+
"timestamp": "2025-11-19T09:15:00",
|
|
428
|
+
"entry_type": "data_locked",
|
|
429
|
+
"parameters": {},
|
|
430
|
+
"hash": "a1b2c3d4",
|
|
431
|
+
"notes": ""
|
|
432
|
+
},
|
|
433
|
+
"message": "Data decision locked successfully"
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
)
|
api/routers/experiments.py
CHANGED
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
Experiments router - Experimental data management.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from fastapi import APIRouter, Depends, UploadFile, File
|
|
6
|
-
from ..models.requests import AddExperimentRequest, AddExperimentsBatchRequest
|
|
7
|
-
from ..models.responses import
|
|
5
|
+
from fastapi import APIRouter, Depends, UploadFile, File, Query
|
|
6
|
+
from ..models.requests import AddExperimentRequest, AddExperimentsBatchRequest, InitialDesignRequest
|
|
7
|
+
from ..models.responses import (
|
|
8
|
+
ExperimentResponse,
|
|
9
|
+
ExperimentsListResponse,
|
|
10
|
+
ExperimentsSummaryResponse,
|
|
11
|
+
InitialDesignResponse
|
|
12
|
+
)
|
|
8
13
|
from ..dependencies import get_session
|
|
9
14
|
from ..middleware.error_handlers import NoVariablesError
|
|
10
15
|
from alchemist_core.session import OptimizationSession
|
|
@@ -12,6 +17,7 @@ import logging
|
|
|
12
17
|
import pandas as pd
|
|
13
18
|
import tempfile
|
|
14
19
|
import os
|
|
20
|
+
from typing import Optional
|
|
15
21
|
|
|
16
22
|
logger = logging.getLogger(__name__)
|
|
17
23
|
|
|
@@ -22,6 +28,9 @@ router = APIRouter()
|
|
|
22
28
|
async def add_experiment(
|
|
23
29
|
session_id: str,
|
|
24
30
|
experiment: AddExperimentRequest,
|
|
31
|
+
auto_train: bool = Query(False, description="Auto-train model after adding data"),
|
|
32
|
+
training_backend: Optional[str] = Query(None, description="Model backend (sklearn/botorch)"),
|
|
33
|
+
training_kernel: Optional[str] = Query(None, description="Kernel type (rbf/matern)"),
|
|
25
34
|
session: OptimizationSession = Depends(get_session)
|
|
26
35
|
):
|
|
27
36
|
"""
|
|
@@ -29,6 +38,11 @@ async def add_experiment(
|
|
|
29
38
|
|
|
30
39
|
The experiment must include values for all defined variables.
|
|
31
40
|
Output value is optional for candidate experiments.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
auto_train: If True, retrain model after adding data
|
|
44
|
+
training_backend: Model backend (uses last if None)
|
|
45
|
+
training_kernel: Kernel type (uses last or 'rbf' if None)
|
|
32
46
|
"""
|
|
33
47
|
# Check if variables are defined
|
|
34
48
|
if len(session.search_space.variables) == 0:
|
|
@@ -43,9 +57,34 @@ async def add_experiment(
|
|
|
43
57
|
n_experiments = len(session.experiment_manager.df)
|
|
44
58
|
logger.info(f"Added experiment to session {session_id}. Total: {n_experiments}")
|
|
45
59
|
|
|
60
|
+
# Auto-train if requested
|
|
61
|
+
model_trained = False
|
|
62
|
+
training_metrics = None
|
|
63
|
+
|
|
64
|
+
if auto_train and n_experiments >= 5: # Minimum data for training
|
|
65
|
+
try:
|
|
66
|
+
# Use previous config or provided config
|
|
67
|
+
backend = training_backend or (session.model_backend if session.model else "sklearn")
|
|
68
|
+
kernel = training_kernel or "rbf"
|
|
69
|
+
|
|
70
|
+
result = session.train_model(backend=backend, kernel=kernel)
|
|
71
|
+
model_trained = True
|
|
72
|
+
metrics = result.get("metrics", {})
|
|
73
|
+
training_metrics = {
|
|
74
|
+
"rmse": metrics.get("rmse"),
|
|
75
|
+
"r2": metrics.get("r2"),
|
|
76
|
+
"backend": backend
|
|
77
|
+
}
|
|
78
|
+
logger.info(f"Auto-trained model for session {session_id}: {training_metrics}")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.error(f"Auto-train failed for session {session_id}: {e}")
|
|
81
|
+
# Don't fail the whole request, just log it
|
|
82
|
+
|
|
46
83
|
return ExperimentResponse(
|
|
47
84
|
message="Experiment added successfully",
|
|
48
|
-
n_experiments=n_experiments
|
|
85
|
+
n_experiments=n_experiments,
|
|
86
|
+
model_trained=model_trained,
|
|
87
|
+
training_metrics=training_metrics
|
|
49
88
|
)
|
|
50
89
|
|
|
51
90
|
|
|
@@ -53,12 +92,20 @@ async def add_experiment(
|
|
|
53
92
|
async def add_experiments_batch(
|
|
54
93
|
session_id: str,
|
|
55
94
|
batch: AddExperimentsBatchRequest,
|
|
95
|
+
auto_train: bool = Query(False, description="Auto-train model after adding data"),
|
|
96
|
+
training_backend: Optional[str] = Query(None, description="Model backend (sklearn/botorch)"),
|
|
97
|
+
training_kernel: Optional[str] = Query(None, description="Kernel type (rbf/matern)"),
|
|
56
98
|
session: OptimizationSession = Depends(get_session)
|
|
57
99
|
):
|
|
58
100
|
"""
|
|
59
101
|
Add multiple experiments at once.
|
|
60
102
|
|
|
61
103
|
Useful for bulk data import or initialization.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
auto_train: If True, retrain model after adding data
|
|
107
|
+
training_backend: Model backend (uses last if None)
|
|
108
|
+
training_kernel: Kernel type (uses last or 'rbf' if None)
|
|
62
109
|
"""
|
|
63
110
|
# Check if variables are defined
|
|
64
111
|
if len(session.search_space.variables) == 0:
|
|
@@ -74,9 +121,74 @@ async def add_experiments_batch(
|
|
|
74
121
|
n_experiments = len(session.experiment_manager.df)
|
|
75
122
|
logger.info(f"Added {len(batch.experiments)} experiments to session {session_id}. Total: {n_experiments}")
|
|
76
123
|
|
|
124
|
+
# Auto-train if requested
|
|
125
|
+
model_trained = False
|
|
126
|
+
training_metrics = None
|
|
127
|
+
|
|
128
|
+
if auto_train and n_experiments >= 5: # Minimum data for training
|
|
129
|
+
try:
|
|
130
|
+
backend = training_backend or (session.model_backend if session.model else "sklearn")
|
|
131
|
+
kernel = training_kernel or "rbf"
|
|
132
|
+
|
|
133
|
+
result = session.train_model(backend=backend, kernel=kernel)
|
|
134
|
+
model_trained = True
|
|
135
|
+
metrics = result.get("metrics", {})
|
|
136
|
+
training_metrics = {
|
|
137
|
+
"rmse": metrics.get("rmse"),
|
|
138
|
+
"r2": metrics.get("r2"),
|
|
139
|
+
"backend": backend
|
|
140
|
+
}
|
|
141
|
+
logger.info(f"Auto-trained model for session {session_id}: {training_metrics}")
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f"Auto-train failed for session {session_id}: {e}")
|
|
144
|
+
|
|
77
145
|
return ExperimentResponse(
|
|
78
146
|
message=f"Added {len(batch.experiments)} experiments successfully",
|
|
79
|
-
n_experiments=n_experiments
|
|
147
|
+
n_experiments=n_experiments,
|
|
148
|
+
model_trained=model_trained,
|
|
149
|
+
training_metrics=training_metrics
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@router.post("/{session_id}/initial-design", response_model=InitialDesignResponse)
|
|
154
|
+
async def generate_initial_design(
|
|
155
|
+
session_id: str,
|
|
156
|
+
request: InitialDesignRequest,
|
|
157
|
+
session: OptimizationSession = Depends(get_session)
|
|
158
|
+
):
|
|
159
|
+
"""
|
|
160
|
+
Generate initial experimental design (DoE) for autonomous operation.
|
|
161
|
+
|
|
162
|
+
Generates space-filling experimental designs before Bayesian optimization begins.
|
|
163
|
+
Useful for autonomous controllers to get initial points to evaluate.
|
|
164
|
+
|
|
165
|
+
Methods:
|
|
166
|
+
- random: Random sampling
|
|
167
|
+
- lhs: Latin Hypercube Sampling (space-filling)
|
|
168
|
+
- sobol: Sobol sequence (quasi-random)
|
|
169
|
+
- halton: Halton sequence (quasi-random)
|
|
170
|
+
- hammersly: Hammersly sequence (quasi-random)
|
|
171
|
+
|
|
172
|
+
Returns list of experiments (input combinations) to evaluate.
|
|
173
|
+
"""
|
|
174
|
+
# Check if variables are defined
|
|
175
|
+
if len(session.search_space.variables) == 0:
|
|
176
|
+
raise NoVariablesError("No variables defined. Add variables to search space first.")
|
|
177
|
+
|
|
178
|
+
# Generate design
|
|
179
|
+
design_points = session.generate_initial_design(
|
|
180
|
+
method=request.method,
|
|
181
|
+
n_points=request.n_points,
|
|
182
|
+
random_seed=request.random_seed,
|
|
183
|
+
lhs_criterion=request.lhs_criterion
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
logger.info(f"Generated {len(design_points)} initial design points using {request.method} for session {session_id}")
|
|
187
|
+
|
|
188
|
+
return InitialDesignResponse(
|
|
189
|
+
points=design_points,
|
|
190
|
+
method=request.method,
|
|
191
|
+
n_points=len(design_points)
|
|
80
192
|
)
|
|
81
193
|
|
|
82
194
|
|