alchemist-nrel 0.3.0__py3-none-any.whl → 0.3.2__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 +2 -2
- alchemist_core/acquisition/botorch_acquisition.py +84 -126
- alchemist_core/data/experiment_manager.py +196 -20
- alchemist_core/models/botorch_model.py +292 -63
- alchemist_core/models/sklearn_model.py +175 -15
- alchemist_core/session.py +3532 -76
- alchemist_core/utils/__init__.py +3 -1
- alchemist_core/utils/acquisition_utils.py +60 -0
- alchemist_core/visualization/__init__.py +45 -0
- alchemist_core/visualization/helpers.py +130 -0
- alchemist_core/visualization/plots.py +1449 -0
- alchemist_nrel-0.3.2.dist-info/METADATA +185 -0
- {alchemist_nrel-0.3.0.dist-info → alchemist_nrel-0.3.2.dist-info}/RECORD +34 -29
- {alchemist_nrel-0.3.0.dist-info → alchemist_nrel-0.3.2.dist-info}/WHEEL +1 -1
- {alchemist_nrel-0.3.0.dist-info → alchemist_nrel-0.3.2.dist-info}/entry_points.txt +1 -1
- {alchemist_nrel-0.3.0.dist-info → alchemist_nrel-0.3.2.dist-info}/top_level.txt +0 -1
- api/example_client.py +7 -2
- api/main.py +3 -2
- api/models/requests.py +76 -1
- api/models/responses.py +102 -2
- api/routers/acquisition.py +25 -0
- api/routers/experiments.py +352 -11
- api/routers/sessions.py +195 -11
- api/routers/visualizations.py +6 -4
- api/routers/websocket.py +132 -0
- run_api.py → api/run_api.py +8 -7
- api/services/session_store.py +370 -71
- api/static/assets/index-B6Cf6s_b.css +1 -0
- api/static/assets/{index-C0_glioA.js → index-B7njvc9r.js} +223 -208
- api/static/index.html +2 -2
- ui/gpr_panel.py +11 -5
- ui/target_column_dialog.py +299 -0
- ui/ui.py +52 -5
- alchemist_core/models/ax_model.py +0 -159
- alchemist_nrel-0.3.0.dist-info/METADATA +0 -223
- api/static/assets/index-CB4V1LI5.css +0 -1
- {alchemist_nrel-0.3.0.dist-info → alchemist_nrel-0.3.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: alchemist-nrel
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Active learning and optimization toolkit for chemical and materials research
|
|
5
|
+
Author-email: Caleb Coatney <caleb.coatney@nrel.gov>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/NatLabRockies/ALchemist
|
|
8
|
+
Project-URL: Documentation, https://natlabrockies.github.io/ALchemist/
|
|
9
|
+
Project-URL: Source, https://github.com/NatLabRockies/ALchemist
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/NatLabRockies/ALchemist/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/NatLabRockies/ALchemist/releases
|
|
12
|
+
Keywords: active learning,bayesian optimization,gaussian processes,materials science,chemistry
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: pandas
|
|
27
|
+
Requires-Dist: scipy
|
|
28
|
+
Requires-Dist: matplotlib
|
|
29
|
+
Requires-Dist: mplcursors
|
|
30
|
+
Requires-Dist: scikit-learn
|
|
31
|
+
Requires-Dist: scikit-optimize
|
|
32
|
+
Requires-Dist: botorch
|
|
33
|
+
Requires-Dist: torch
|
|
34
|
+
Requires-Dist: gpytorch
|
|
35
|
+
Requires-Dist: ax-platform
|
|
36
|
+
Requires-Dist: customtkinter
|
|
37
|
+
Requires-Dist: tksheet
|
|
38
|
+
Requires-Dist: tabulate
|
|
39
|
+
Requires-Dist: ctkmessagebox
|
|
40
|
+
Requires-Dist: joblib
|
|
41
|
+
Requires-Dist: fastapi>=0.109.0
|
|
42
|
+
Requires-Dist: uvicorn[standard]>=0.27.0
|
|
43
|
+
Requires-Dist: pydantic>=2.5.0
|
|
44
|
+
Requires-Dist: python-multipart>=0.0.6
|
|
45
|
+
Requires-Dist: requests
|
|
46
|
+
Provides-Extra: test
|
|
47
|
+
Requires-Dist: pytest>=8.0.0; extra == "test"
|
|
48
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
49
|
+
Requires-Dist: pytest-anyio>=0.0.0; extra == "test"
|
|
50
|
+
Requires-Dist: httpx>=0.25.0; extra == "test"
|
|
51
|
+
Requires-Dist: requests>=2.31.0; extra == "test"
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: pytest-anyio>=0.0.0; extra == "dev"
|
|
56
|
+
Requires-Dist: httpx>=0.25.0; extra == "dev"
|
|
57
|
+
Requires-Dist: requests>=2.31.0; extra == "dev"
|
|
58
|
+
Dynamic: license-file
|
|
59
|
+
|
|
60
|
+
<img src="docs/assets/NEW_LOGO_LIGHT.png" alt="ALchemist" width="50%" />
|
|
61
|
+
|
|
62
|
+
**ALchemist: Active Learning Toolkit for Chemical and Materials Research**
|
|
63
|
+
|
|
64
|
+
ALchemist is a modular Python toolkit that brings active learning and Bayesian optimization to experimental design in chemical and materials research. It is designed for scientists and engineers who want to efficiently explore or optimize high-dimensional variable spaces—using intuitive graphical interfaces, programmatic APIs, or autonomous optimization workflows.
|
|
65
|
+
|
|
66
|
+
**NLR Software Record:** SWR-25-102
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Documentation
|
|
71
|
+
|
|
72
|
+
Full user guide and documentation:
|
|
73
|
+
[https://natlabrockies.github.io/ALchemist/](https://natlabrockies.github.io/ALchemist/)
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Overview
|
|
78
|
+
|
|
79
|
+
**Key Features:**
|
|
80
|
+
|
|
81
|
+
- **Flexible variable space definition**: Real, integer, and categorical variables with bounds or discrete values
|
|
82
|
+
- **Probabilistic surrogate modeling**: Gaussian process regression via BoTorch or scikit-learn backends
|
|
83
|
+
- **Advanced acquisition strategies**: Efficient sampling using qEI, qPI, qUCB, and qNegIntegratedPosteriorVariance
|
|
84
|
+
- **Modern web interface**: React-based UI with FastAPI backend for seamless active learning workflows
|
|
85
|
+
- **Desktop GUI**: CustomTkinter desktop application for offline optimization
|
|
86
|
+
- **Session management**: Save/load optimization sessions with audit logs for reproducibility
|
|
87
|
+
- **Multiple interfaces**: No-code GUI, Python Session API, or REST API for different use cases
|
|
88
|
+
- **Autonomous optimization**: Human-out-of-the-loop operation for real-time process control
|
|
89
|
+
- **Experiment tracking**: CSV logging, reproducible random seeds, and comprehensive audit trails
|
|
90
|
+
- **Extensibility**: Abstract interfaces for models and acquisition functions enable future backend and workflow expansion
|
|
91
|
+
**Architecture:**
|
|
92
|
+
|
|
93
|
+
ALchemist is built on a clean, modular architecture:
|
|
94
|
+
|
|
95
|
+
- **Core Session API**: Headless Bayesian optimization engine (`alchemist_core`) that powers all interfaces
|
|
96
|
+
- **Desktop Application**: CustomTkinter GUI using the Core Session API, designed for human-in-the-loop and offline optimization
|
|
97
|
+
- **REST API**: FastAPI server providing a thin wrapper around the Core Session API for remote access
|
|
98
|
+
- **Web Application**: React UI consuming the REST API, supporting both interactive and autonomous optimization workflows
|
|
99
|
+
|
|
100
|
+
Session files (JSON format) are fully interoperable between desktop and web applications, enabling seamless workflow transitions.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Use Cases
|
|
105
|
+
|
|
106
|
+
- **Interactive Optimization**: Use desktop or web GUI for manual experiment design and human-in-the-loop optimization
|
|
107
|
+
- **Programmatic Workflows**: Import the Session API in Python scripts or Jupyter notebooks for batch processing
|
|
108
|
+
- **Autonomous Optimization**: Use the REST API to integrate ALchemist with automated laboratory equipment for real-time process control
|
|
109
|
+
- **Remote Monitoring**: Web dashboard provides read-only monitoring mode when ALchemist is being remote-controlled
|
|
110
|
+
|
|
111
|
+
For detailed application examples, see [Use Cases](https://natlabrockies.github.io/ALchemist/use_cases/) in the documentation.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Installation
|
|
116
|
+
|
|
117
|
+
**Requirements:** Python 3.11 or higher
|
|
118
|
+
|
|
119
|
+
**Recommended (Optional):** We recommend using [Anaconda](https://www.anaconda.com/products/distribution) to manage Python environments:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
conda create -n alchemist-env python=3.11
|
|
123
|
+
conda activate alchemist-env
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Basic Installation:**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pip install alchemist-nrel
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**From GitHub:**
|
|
133
|
+
> *Note: This installs the latest unreleased version. The web application is not pre-built with this method because static build files are not included in the repository.*
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pip install git+https://github.com/NatLabRockies/ALchemist.git
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
For advanced installation options, Docker deployment, and development setup, see the [Advanced Installation Guide](https://natlabrockies.github.io/ALchemist/#advanced-installation) in the documentation.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Running ALchemist
|
|
144
|
+
|
|
145
|
+
**Web Application:**
|
|
146
|
+
```bash
|
|
147
|
+
alchemist-web
|
|
148
|
+
```
|
|
149
|
+
Opens at [http://localhost:8000/app](http://localhost:8000/app)
|
|
150
|
+
|
|
151
|
+
**Desktop Application:**
|
|
152
|
+
```bash
|
|
153
|
+
alchemist
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
For detailed usage instructions, see [Getting Started](https://natlabrockies.github.io/ALchemist/) in the documentation.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Development Status
|
|
161
|
+
|
|
162
|
+
ALchemist is under active development at NLR as part of the DataHub project within the ChemCatBio consortium.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Issues & Troubleshooting
|
|
167
|
+
|
|
168
|
+
If you encounter any issues or have questions, please [open an issue on GitHub](https://github.com/NatLabRockies/ALchemist/issues) or contact ccoatney@nrel.gov.
|
|
169
|
+
|
|
170
|
+
For the latest known issues and troubleshooting tips, see the [Issues & Troubleshooting Log](docs/ISSUES_LOG.md).
|
|
171
|
+
|
|
172
|
+
We appreciate your feedback and bug reports to help improve ALchemist!
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
This project is licensed under the BSD 3-Clause License. See the [LICENSE](LICENSE) file for details.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Repository
|
|
183
|
+
|
|
184
|
+
[https://github.com/NatLabRockies/ALchemist](https://github.com/NatLabRockies/ALchemist)
|
|
185
|
+
|
|
@@ -1,66 +1,71 @@
|
|
|
1
1
|
main.py,sha256=3sAO2QZxxibs4WRT82i2w6KVBBFmYEMNUoGiMYFowOw,126
|
|
2
|
-
|
|
3
|
-
alchemist_core/__init__.py,sha256=jYIygJyhCXUmX3oAaxw687uLLQcRSuxNRQrMeuWuuuI,2023
|
|
2
|
+
alchemist_core/__init__.py,sha256=ajBg3qjyLqyDwkBxZdf161TrTPhBE6TcQZXb7Wr2fYs,2014
|
|
4
3
|
alchemist_core/audit_log.py,sha256=s8h3YKBgvcu_tgIrjP69rNr6yOnbks5J2RR_m2bwB4Q,22531
|
|
5
4
|
alchemist_core/config.py,sha256=Sk5eM1okktO5bUMlMPv9yzF2fpuiyGr9LUtlCWIBDc8,3366
|
|
6
5
|
alchemist_core/events.py,sha256=ty9nRzfZGHzk6b09dALIwrMY_5PYSv0wMaw94JLDjSk,6717
|
|
7
|
-
alchemist_core/session.py,sha256=
|
|
6
|
+
alchemist_core/session.py,sha256=nTuRHNIPiwBXvETGgCkLYWgg3Q85z5d84nDdFjdq-NU,199174
|
|
8
7
|
alchemist_core/acquisition/__init__.py,sha256=3CYGI24OTBS66ETrlGFyHCNpfS6DBMP41MZDhvjFEzg,32
|
|
9
8
|
alchemist_core/acquisition/base_acquisition.py,sha256=s51vGx0b0Nt91lSCiVwYP9IClugVg2VJ21dn2n_4LIs,483
|
|
10
|
-
alchemist_core/acquisition/botorch_acquisition.py,sha256=
|
|
9
|
+
alchemist_core/acquisition/botorch_acquisition.py,sha256=lnb3imslnihrRJO66SiIpNvAB6ljo0GY8M_lZ7xOwO4,29176
|
|
11
10
|
alchemist_core/acquisition/skopt_acquisition.py,sha256=YRdANqgiN3GWd4sn16oruN6jVnI4RLmvLhBMUfYyLp4,13115
|
|
12
11
|
alchemist_core/data/__init__.py,sha256=wgEb03x0RzVCi0uJXOzEKXkbA2oNHom5EgSB3tKgl1E,256
|
|
13
|
-
alchemist_core/data/experiment_manager.py,sha256=
|
|
12
|
+
alchemist_core/data/experiment_manager.py,sha256=67kSnGq0uvCiMF9B3BZRMQqJBII2fpzkUJqoQhysyBs,16465
|
|
14
13
|
alchemist_core/data/search_space.py,sha256=oA9YEF3JRWpRklHzSo_Uxlmfy7bHwZfLZFDf4_nl4ew,6230
|
|
15
14
|
alchemist_core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
alchemist_core/models/ax_model.py,sha256=Fnu19Et376WxLXpApZSalIbWHQE9OQnMk41_PW69XnQ,6040
|
|
17
15
|
alchemist_core/models/base_model.py,sha256=gIpC2eoTZcp4ozI0Rctcxt_4bLhgaE6ALYCzvuIrMZw,3145
|
|
18
|
-
alchemist_core/models/botorch_model.py,sha256=
|
|
19
|
-
alchemist_core/models/sklearn_model.py,sha256=
|
|
20
|
-
alchemist_core/utils/__init__.py,sha256=
|
|
16
|
+
alchemist_core/models/botorch_model.py,sha256=YPkRjGFqig-ABhOt4L4XSzoIACpWgxzDGz43pqC1tCU,52524
|
|
17
|
+
alchemist_core/models/sklearn_model.py,sha256=k72kiBxYWC6CTjx4FX2yf7Q5CemhxZXqH1l3WwPJNZ4,43498
|
|
18
|
+
alchemist_core/utils/__init__.py,sha256=Lhko6s67xrX1EIAevGs059w2WWMWTA3eLMa2urzbbE8,174
|
|
19
|
+
alchemist_core/utils/acquisition_utils.py,sha256=oG8cegGnFuvlJlcswdxQcI1EKiQgeWFFor4YAkwHgr4,2433
|
|
21
20
|
alchemist_core/utils/doe.py,sha256=hnrhIzm3Nz0qZBW4PXaNQ6pTDgiLn_lUhbMPmWmk_6Y,7110
|
|
22
|
-
|
|
21
|
+
alchemist_core/visualization/__init__.py,sha256=b0PsJOe3K4ErQF6lxsFuLo0mwWeY99nwX2gQ_y-2_-E,1175
|
|
22
|
+
alchemist_core/visualization/helpers.py,sha256=RL0DMbblglFtYP1po79gtS_RfYwschMG75I3N5SdFKU,3796
|
|
23
|
+
alchemist_core/visualization/plots.py,sha256=l9X8R9Jdz_vT7nAiNeA3O3tGA7aWIrL6omG5yb0_4Fo,51929
|
|
24
|
+
alchemist_nrel-0.3.2.dist-info/licenses/LICENSE,sha256=wdIWWEj59ztfQViDuT_9wG3L1K8afUpRSygimXw36wY,1511
|
|
23
25
|
api/__init__.py,sha256=ODc6pq4OSImgK4xvhX_zMhqUjIc7JvLfqxKF_-Ubw7g,49
|
|
24
26
|
api/dependencies.py,sha256=sF1YYjnFRaw7nj7Y7URKLF2Ek-EfaXqjOyV1Zbktz2g,1090
|
|
25
|
-
api/example_client.py,sha256=
|
|
26
|
-
api/main.py,sha256=
|
|
27
|
+
api/example_client.py,sha256=RjEOvZItzgmGdP6V5j106LQqmQg0WIEr72xf4oMJZHo,6924
|
|
28
|
+
api/main.py,sha256=gVIWLv5AZdXxTKC1oOr8cku2SBsGaGDnLUJjZGkvB98,4301
|
|
29
|
+
api/run_api.py,sha256=YGyLUJNbdsDGEKsd_EsrA1gGheGW53etn8F3ku44q1g,1993
|
|
27
30
|
api/middleware/__init__.py,sha256=WM4JEg3DibymvEvQ6hx_FJkv8lKLjHD48qNouSORGxA,313
|
|
28
31
|
api/middleware/error_handlers.py,sha256=k5hNo6W5QDjGRHUY8Le-t7ubWkwSZBFpsTKcEF0nweI,4545
|
|
29
32
|
api/models/__init__.py,sha256=YFtp8989mH9Zjzvd8W6pXklQJXTf8zWj4I2YWnLegDQ,1204
|
|
30
|
-
api/models/requests.py,sha256=
|
|
31
|
-
api/models/responses.py,sha256=
|
|
33
|
+
api/models/requests.py,sha256=d8Mt4rUDrXZ7ylUBaaTUGEVPBzW77HksrLY991KkaFw,12789
|
|
34
|
+
api/models/responses.py,sha256=Ce_EFxpzeFev1OfwcxdeuFAx5EGf8cLHh1cpz60DzSk,16654
|
|
32
35
|
api/routers/__init__.py,sha256=Mhg62NdA6iaPy-L5HLVp_dd9aUHmJ72KtMSRyO2kusA,180
|
|
33
|
-
api/routers/acquisition.py,sha256=
|
|
34
|
-
api/routers/experiments.py,sha256=
|
|
36
|
+
api/routers/acquisition.py,sha256=GzncAXsQzhldB-gngpMcUa5choaoAqMqLrpqszRjMFc,6683
|
|
37
|
+
api/routers/experiments.py,sha256=uy7aHUKE9E-BKacBssQbVyCix44_gEWTOVLPs9UV-L4,22421
|
|
35
38
|
api/routers/models.py,sha256=32Ln0MtlnCEjfN3Q6Io_EBwwwGoJXb73UbQEMIcVGjI,3651
|
|
36
|
-
api/routers/sessions.py,sha256=
|
|
39
|
+
api/routers/sessions.py,sha256=6pnyi0-63yL7tC8bFdDjOpG6lQXZh-egFvhm6M1OLuE,21724
|
|
37
40
|
api/routers/variables.py,sha256=TiByX1ITabBDdTSMGAPa1lGd0LBipNgDfmPsbvTEAdE,10108
|
|
38
|
-
api/routers/visualizations.py,sha256=
|
|
41
|
+
api/routers/visualizations.py,sha256=sKw_edHHFOuW5tLm_oK_i7Ty1e6AHcqgQtL-qzH1tSw,24365
|
|
42
|
+
api/routers/websocket.py,sha256=pw-HWf7Sd_cVroQO9uF7KDwQFJEyItFluPcqS1Zst0o,4363
|
|
39
43
|
api/services/__init__.py,sha256=0jw0tkL-8CtChv5ytdXRFeIz1OTVz7Vw1UaaDo86PQs,106
|
|
40
|
-
api/services/session_store.py,sha256=
|
|
44
|
+
api/services/session_store.py,sha256=vybQYqw2319WFTJd8P7v2-5mdAh4fo8qhZGh0YdDeUo,27888
|
|
41
45
|
api/static/NEW_ICON.ico,sha256=V4zY86qhPT24SSYK8VL5Ax5AezWxOfvfeHWBXisudOU,247870
|
|
42
46
|
api/static/NEW_ICON.png,sha256=7UUPRgQ6-Ncv1xvB_57QMfrMY8xxHn16mLHc_zUGmCE,62788
|
|
43
47
|
api/static/NEW_LOGO_DARK.png,sha256=O4p2tfTBuChSSPRl-Fzue1qoQdLkqXHBofNyiEzRNLs,128146
|
|
44
48
|
api/static/NEW_LOGO_LIGHT.png,sha256=XBcv5-snGDTpjCrk7UfuJiFbSqrsGRafk7vNBj9eJnM,131686
|
|
45
|
-
api/static/index.html,sha256=
|
|
49
|
+
api/static/index.html,sha256=xUHF19OS1aLKBWxz177l_mG6nFdAhwJXA4CYUqu5DhA,499
|
|
46
50
|
api/static/vite.svg,sha256=SnSK_UQ5GLsWWRyDTEAdrjPoeGGrXbrQgRw6O0qSFPs,1497
|
|
47
51
|
api/static/assets/api-vcoXEqyq.js,sha256=LDSOiSvi1Zc7SRry3ldpA__egc6GAFbzQt9QzBzbTIQ,303
|
|
48
|
-
api/static/assets/index-
|
|
49
|
-
api/static/assets/index-
|
|
52
|
+
api/static/assets/index-B6Cf6s_b.css,sha256=NfVJy21eVUVIacLpJRSBelEVdkotUj8V-T3b0zEv5Qs,24358
|
|
53
|
+
api/static/assets/index-B7njvc9r.js,sha256=WZ5GDnb4fcANEKDpBAcKg44skpdcQwpv98QX-m0pUug,5757510
|
|
50
54
|
ui/__init__.py,sha256=H4kWlVey7KKf3iPQi74zuM7FSOg5Gh-ii3UwSTuIp8A,1203
|
|
51
55
|
ui/acquisition_panel.py,sha256=zF-mQDrs-Y7sf2GXYF-bPlO9UXZMTzYRMDN-Wn5FyWw,39647
|
|
52
56
|
ui/custom_widgets.py,sha256=UXNv4DiTw3tFC0VaN1Qtcf_-9umX34uDn46-cEA6cs0,3812
|
|
53
57
|
ui/experiment_logger.py,sha256=dP3IGaQ31sURyz7awd_VrZBWaKLH2xXEeRalWZpvVcQ,8366
|
|
54
|
-
ui/gpr_panel.py,sha256=
|
|
58
|
+
ui/gpr_panel.py,sha256=nSz7kWdmXFh7GjgVjFGA1CQulTLLpiiYkx5Jtm1KuUI,26779
|
|
55
59
|
ui/notifications.py,sha256=hpUDo52_cQd7e8j7lOZikVRu1yLqRwlNR9vYNZdF5VM,35301
|
|
56
60
|
ui/pool_viz.py,sha256=RwjggEfRSSEe-4nGjxc-I-1e5_aH64DgypT_YoubLIU,8765
|
|
57
|
-
ui/
|
|
61
|
+
ui/target_column_dialog.py,sha256=We1eHAfkdRtpkMbC_w8v8qIyZVdruNuYy8ciuSKXH8Y,10496
|
|
62
|
+
ui/ui.py,sha256=IM2fFZVKtn25GVxE-BFe1yTxyUM6Zj_Rbl0uXzTbKOw,104512
|
|
58
63
|
ui/ui_utils.py,sha256=yud2-9LvT4XBcjTyfwUX5tYGNZRlAUVlu2YpcNY1HKA,658
|
|
59
64
|
ui/utils.py,sha256=m19YFFkEUAY46YSj6S5RBmfUFjIWOk7F8CB4oKDRRZw,1078
|
|
60
65
|
ui/variables_setup.py,sha256=6hphCy66uLsjIX7FjFtY6-fBfZ6cgfpviXXX9JBhuc4,23618
|
|
61
66
|
ui/visualizations.py,sha256=FCpuehMi2Cf3Jpuycqoj43oJoCU-QAr8-6Sp6LCO4hE,70371
|
|
62
|
-
alchemist_nrel-0.3.
|
|
63
|
-
alchemist_nrel-0.3.
|
|
64
|
-
alchemist_nrel-0.3.
|
|
65
|
-
alchemist_nrel-0.3.
|
|
66
|
-
alchemist_nrel-0.3.
|
|
67
|
+
alchemist_nrel-0.3.2.dist-info/METADATA,sha256=axydXtcrcxtmqMZfvYlg9pgc4_Dg3U34lh0wCKNNhl8,7248
|
|
68
|
+
alchemist_nrel-0.3.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
69
|
+
alchemist_nrel-0.3.2.dist-info/entry_points.txt,sha256=e2QcTxh-pidX_eJlQRk9yE3hLMYA0YnUzSh3sxqvdzY,73
|
|
70
|
+
alchemist_nrel-0.3.2.dist-info/top_level.txt,sha256=dwh-oxj7H6oAGYchcUDyfiu9UxxzCn4hUDx1oeM2k-8,27
|
|
71
|
+
alchemist_nrel-0.3.2.dist-info/RECORD,,
|
api/example_client.py
CHANGED
|
@@ -90,8 +90,13 @@ def main():
|
|
|
90
90
|
json={"experiments": experiments}
|
|
91
91
|
)
|
|
92
92
|
response.raise_for_status()
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
batch_result = response.json()
|
|
94
|
+
n_added = batch_result.get("n_added", len(experiments))
|
|
95
|
+
total_experiments = batch_result.get("n_experiments")
|
|
96
|
+
if total_experiments is not None and total_experiments >= n_added:
|
|
97
|
+
print(f" ✓ Added {n_added} experiments (total: {total_experiments})")
|
|
98
|
+
else:
|
|
99
|
+
print(f" ✓ Added {n_added} experiments")
|
|
95
100
|
|
|
96
101
|
# Get data summary
|
|
97
102
|
response = requests.get(f"{BASE_URL}/sessions/{session_id}/experiments/summary")
|
api/main.py
CHANGED
|
@@ -16,7 +16,7 @@ from fastapi import FastAPI
|
|
|
16
16
|
from fastapi.middleware.cors import CORSMiddleware
|
|
17
17
|
from fastapi.staticfiles import StaticFiles
|
|
18
18
|
from fastapi.responses import FileResponse
|
|
19
|
-
from .routers import sessions, variables, experiments, models, acquisition, visualizations
|
|
19
|
+
from .routers import sessions, variables, experiments, models, acquisition, visualizations, websocket
|
|
20
20
|
from .middleware.error_handlers import add_exception_handlers
|
|
21
21
|
import logging
|
|
22
22
|
|
|
@@ -32,7 +32,7 @@ logger = logging.getLogger(__name__)
|
|
|
32
32
|
app = FastAPI(
|
|
33
33
|
title="ALchemist API",
|
|
34
34
|
description="REST API for Bayesian optimization and active learning",
|
|
35
|
-
version="0.3.
|
|
35
|
+
version="0.3.2",
|
|
36
36
|
docs_url="/api/docs",
|
|
37
37
|
redoc_url="/api/redoc",
|
|
38
38
|
openapi_url="/api/openapi.json"
|
|
@@ -64,6 +64,7 @@ app.include_router(experiments.router, prefix="/api/v1/sessions", tags=["Experim
|
|
|
64
64
|
app.include_router(models.router, prefix="/api/v1/sessions", tags=["Models"])
|
|
65
65
|
app.include_router(acquisition.router, prefix="/api/v1/sessions", tags=["Acquisition"])
|
|
66
66
|
app.include_router(visualizations.router, prefix="/api/v1/sessions", tags=["Visualizations"])
|
|
67
|
+
app.include_router(websocket.router, prefix="/api/v1", tags=["WebSocket"])
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
@app.get("/")
|
api/models/requests.py
CHANGED
|
@@ -93,13 +93,69 @@ class AddExperimentRequest(BaseModel):
|
|
|
93
93
|
inputs: Dict[str, Union[float, int, str]] = Field(..., description="Variable values")
|
|
94
94
|
output: Optional[float] = Field(None, description="Target/output value")
|
|
95
95
|
noise: Optional[float] = Field(None, description="Measurement uncertainty")
|
|
96
|
+
iteration: Optional[int] = Field(None, description="Iteration number (auto-assigned if None)")
|
|
97
|
+
reason: Optional[str] = Field(None, description="Reason for this experiment")
|
|
96
98
|
|
|
97
99
|
model_config = ConfigDict(
|
|
98
100
|
json_schema_extra={
|
|
99
101
|
"example": {
|
|
100
102
|
"inputs": {"temperature": 350, "catalyst": "A"},
|
|
101
103
|
"output": 0.85,
|
|
102
|
-
"noise": 0.02
|
|
104
|
+
"noise": 0.02,
|
|
105
|
+
"iteration": 1,
|
|
106
|
+
"reason": "Initial Design"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class StageExperimentRequest(BaseModel):
|
|
113
|
+
"""Request to stage an experiment for later execution."""
|
|
114
|
+
inputs: Dict[str, Union[float, int, str]] = Field(..., description="Variable values")
|
|
115
|
+
reason: Optional[str] = Field(None, description="Reason for this experiment (e.g., acquisition strategy)")
|
|
116
|
+
|
|
117
|
+
model_config = ConfigDict(
|
|
118
|
+
json_schema_extra={
|
|
119
|
+
"example": {
|
|
120
|
+
"inputs": {"temperature": 375.2, "catalyst": "B"},
|
|
121
|
+
"reason": "qEI"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class StageExperimentsBatchRequest(BaseModel):
|
|
128
|
+
"""Request to stage multiple experiments at once."""
|
|
129
|
+
experiments: List[Dict[str, Union[float, int, str]]] = Field(..., description="List of experiment inputs")
|
|
130
|
+
reason: Optional[str] = Field(None, description="Reason for these experiments")
|
|
131
|
+
|
|
132
|
+
model_config = ConfigDict(
|
|
133
|
+
json_schema_extra={
|
|
134
|
+
"example": {
|
|
135
|
+
"experiments": [
|
|
136
|
+
{"temperature": 375.2, "catalyst": "B"},
|
|
137
|
+
{"temperature": 412.8, "catalyst": "A"}
|
|
138
|
+
],
|
|
139
|
+
"reason": "qEI batch"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class CompleteStagedExperimentsRequest(BaseModel):
|
|
146
|
+
"""Request to complete staged experiments with outputs."""
|
|
147
|
+
outputs: List[float] = Field(..., description="Output values for staged experiments (same order)")
|
|
148
|
+
noises: Optional[List[float]] = Field(None, description="Measurement uncertainties (optional)")
|
|
149
|
+
iteration: Optional[int] = Field(None, description="Iteration number (auto-assigned if None)")
|
|
150
|
+
reason: Optional[str] = Field(None, description="Reason (uses staged reason if not provided)")
|
|
151
|
+
|
|
152
|
+
model_config = ConfigDict(
|
|
153
|
+
json_schema_extra={
|
|
154
|
+
"example": {
|
|
155
|
+
"outputs": [0.87, 0.92],
|
|
156
|
+
"noises": [0.02, 0.03],
|
|
157
|
+
"iteration": 5,
|
|
158
|
+
"reason": "qEI"
|
|
103
159
|
}
|
|
104
160
|
}
|
|
105
161
|
)
|
|
@@ -271,3 +327,22 @@ class LockDecisionRequest(BaseModel):
|
|
|
271
327
|
}
|
|
272
328
|
}
|
|
273
329
|
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# ============================================================
|
|
333
|
+
# Session Lock Models
|
|
334
|
+
# ============================================================
|
|
335
|
+
|
|
336
|
+
class SessionLockRequest(BaseModel):
|
|
337
|
+
"""Request to lock a session for programmatic control."""
|
|
338
|
+
locked_by: str = Field(..., description="Identifier of the client locking the session")
|
|
339
|
+
client_id: Optional[str] = Field(None, description="Optional unique client identifier")
|
|
340
|
+
|
|
341
|
+
model_config = ConfigDict(
|
|
342
|
+
json_schema_extra={
|
|
343
|
+
"example": {
|
|
344
|
+
"locked_by": "Reactor Controller v1.2",
|
|
345
|
+
"client_id": "lab-3-workstation"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
)
|
api/models/responses.py
CHANGED
|
@@ -15,7 +15,7 @@ class SessionCreateResponse(BaseModel):
|
|
|
15
15
|
"""Response when creating a new session."""
|
|
16
16
|
session_id: str = Field(..., description="Unique session identifier")
|
|
17
17
|
created_at: str = Field(..., description="Session creation timestamp")
|
|
18
|
-
expires_at: str = Field(
|
|
18
|
+
expires_at: Optional[str] = Field(None, description="Legacy field - no longer used (sessions don't expire)")
|
|
19
19
|
|
|
20
20
|
model_config = ConfigDict(
|
|
21
21
|
json_schema_extra={
|
|
@@ -58,7 +58,7 @@ class SessionInfoResponse(BaseModel):
|
|
|
58
58
|
session_id: str
|
|
59
59
|
created_at: str
|
|
60
60
|
last_accessed: str
|
|
61
|
-
expires_at: str
|
|
61
|
+
expires_at: Optional[str] = None # Legacy field - no longer used
|
|
62
62
|
search_space: Dict[str, Any]
|
|
63
63
|
data: DataSummary
|
|
64
64
|
model: Optional[ModelSummary]
|
|
@@ -200,6 +200,83 @@ class InitialDesignResponse(BaseModel):
|
|
|
200
200
|
)
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
# ============================================================
|
|
204
|
+
# Staged Experiments Models
|
|
205
|
+
# ============================================================
|
|
206
|
+
|
|
207
|
+
class StagedExperimentResponse(BaseModel):
|
|
208
|
+
"""Response when staging an experiment."""
|
|
209
|
+
message: str = "Experiment staged successfully"
|
|
210
|
+
n_staged: int = Field(..., description="Total number of staged experiments")
|
|
211
|
+
staged_inputs: Dict[str, Any] = Field(..., description="The staged experiment inputs")
|
|
212
|
+
|
|
213
|
+
model_config = ConfigDict(
|
|
214
|
+
json_schema_extra={
|
|
215
|
+
"example": {
|
|
216
|
+
"message": "Experiment staged successfully",
|
|
217
|
+
"n_staged": 3,
|
|
218
|
+
"staged_inputs": {"temperature": 375.2, "catalyst": "B"}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class StagedExperimentsListResponse(BaseModel):
|
|
225
|
+
"""Response containing all staged experiments."""
|
|
226
|
+
experiments: List[Dict[str, Any]] = Field(..., description="List of staged experiment inputs (variable values only)")
|
|
227
|
+
n_staged: int = Field(..., description="Number of staged experiments")
|
|
228
|
+
reason: Optional[str] = Field(None, description="Reason/strategy for these staged experiments")
|
|
229
|
+
|
|
230
|
+
model_config = ConfigDict(
|
|
231
|
+
json_schema_extra={
|
|
232
|
+
"example": {
|
|
233
|
+
"experiments": [
|
|
234
|
+
{"temperature": 375.2, "catalyst": "B"},
|
|
235
|
+
{"temperature": 412.8, "catalyst": "A"}
|
|
236
|
+
],
|
|
237
|
+
"n_staged": 2,
|
|
238
|
+
"reason": "qEI"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class StagedExperimentsClearResponse(BaseModel):
|
|
245
|
+
"""Response when clearing staged experiments."""
|
|
246
|
+
message: str = "Staged experiments cleared"
|
|
247
|
+
n_cleared: int = Field(..., description="Number of experiments cleared")
|
|
248
|
+
|
|
249
|
+
model_config = ConfigDict(
|
|
250
|
+
json_schema_extra={
|
|
251
|
+
"example": {
|
|
252
|
+
"message": "Staged experiments cleared",
|
|
253
|
+
"n_cleared": 3
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class StagedExperimentsCompletedResponse(BaseModel):
|
|
260
|
+
"""Response when completing staged experiments with outputs."""
|
|
261
|
+
message: str = "Staged experiments completed and added to dataset"
|
|
262
|
+
n_added: int = Field(..., description="Number of experiments added")
|
|
263
|
+
n_experiments: int = Field(..., description="Total experiments in dataset")
|
|
264
|
+
model_trained: bool = Field(default=False, description="Whether model was auto-trained")
|
|
265
|
+
training_metrics: Optional[Dict[str, Any]] = Field(None, description="Training metrics if auto-trained")
|
|
266
|
+
|
|
267
|
+
model_config = ConfigDict(
|
|
268
|
+
json_schema_extra={
|
|
269
|
+
"example": {
|
|
270
|
+
"message": "Staged experiments completed and added to dataset",
|
|
271
|
+
"n_added": 2,
|
|
272
|
+
"n_experiments": 15,
|
|
273
|
+
"model_trained": True,
|
|
274
|
+
"training_metrics": {"rmse": 0.05, "r2": 0.92}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
203
280
|
# ============================================================
|
|
204
281
|
# Model Training Models
|
|
205
282
|
# ============================================================
|
|
@@ -434,3 +511,26 @@ class LockDecisionResponse(BaseModel):
|
|
|
434
511
|
}
|
|
435
512
|
}
|
|
436
513
|
)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
# ============================================================
|
|
517
|
+
# Session Lock Models
|
|
518
|
+
# ============================================================
|
|
519
|
+
|
|
520
|
+
class SessionLockResponse(BaseModel):
|
|
521
|
+
"""Response for session lock operations."""
|
|
522
|
+
locked: bool = Field(..., description="Whether the session is locked")
|
|
523
|
+
locked_by: Optional[str] = Field(None, description="Identifier of who locked the session")
|
|
524
|
+
locked_at: Optional[str] = Field(None, description="When the session was locked")
|
|
525
|
+
lock_token: Optional[str] = Field(None, description="Token for unlocking (only on lock)")
|
|
526
|
+
|
|
527
|
+
model_config = ConfigDict(
|
|
528
|
+
json_schema_extra={
|
|
529
|
+
"example": {
|
|
530
|
+
"locked": True,
|
|
531
|
+
"locked_by": "Reactor Controller v1.2",
|
|
532
|
+
"locked_at": "2025-12-04T16:30:00",
|
|
533
|
+
"lock_token": "550e8400-e29b-41d4-a716-446655440000"
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
)
|
api/routers/acquisition.py
CHANGED
|
@@ -59,6 +59,31 @@ async def suggest_next_experiments(
|
|
|
59
59
|
# Convert to list of dicts
|
|
60
60
|
suggestions = suggestions_df.to_dict('records')
|
|
61
61
|
|
|
62
|
+
# Record acquisition in audit log
|
|
63
|
+
if suggestions:
|
|
64
|
+
# Get current max iteration from experiments
|
|
65
|
+
iteration = None
|
|
66
|
+
if not session.experiment_manager.df.empty and 'Iteration' in session.experiment_manager.df.columns:
|
|
67
|
+
iteration = int(session.experiment_manager.df['Iteration'].max()) + 1
|
|
68
|
+
|
|
69
|
+
# Build parameters dict with only fields that exist
|
|
70
|
+
acq_params = {
|
|
71
|
+
"goal": request.goal,
|
|
72
|
+
"n_suggestions": request.n_suggestions
|
|
73
|
+
}
|
|
74
|
+
if request.xi is not None:
|
|
75
|
+
acq_params["xi"] = request.xi
|
|
76
|
+
if request.kappa is not None:
|
|
77
|
+
acq_params["kappa"] = request.kappa
|
|
78
|
+
|
|
79
|
+
session.audit_log.lock_acquisition(
|
|
80
|
+
strategy=request.strategy,
|
|
81
|
+
parameters=acq_params,
|
|
82
|
+
suggestions=suggestions,
|
|
83
|
+
iteration=iteration,
|
|
84
|
+
notes=f"Suggested {len(suggestions)} point(s) using {request.strategy}"
|
|
85
|
+
)
|
|
86
|
+
|
|
62
87
|
logger.info(f"Generated {len(suggestions)} suggestions for session {session_id} using {request.strategy}")
|
|
63
88
|
|
|
64
89
|
return AcquisitionResponse(
|