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.
Files changed (37) hide show
  1. alchemist_core/__init__.py +2 -2
  2. alchemist_core/acquisition/botorch_acquisition.py +84 -126
  3. alchemist_core/data/experiment_manager.py +196 -20
  4. alchemist_core/models/botorch_model.py +292 -63
  5. alchemist_core/models/sklearn_model.py +175 -15
  6. alchemist_core/session.py +3532 -76
  7. alchemist_core/utils/__init__.py +3 -1
  8. alchemist_core/utils/acquisition_utils.py +60 -0
  9. alchemist_core/visualization/__init__.py +45 -0
  10. alchemist_core/visualization/helpers.py +130 -0
  11. alchemist_core/visualization/plots.py +1449 -0
  12. alchemist_nrel-0.3.2.dist-info/METADATA +185 -0
  13. {alchemist_nrel-0.3.0.dist-info → alchemist_nrel-0.3.2.dist-info}/RECORD +34 -29
  14. {alchemist_nrel-0.3.0.dist-info → alchemist_nrel-0.3.2.dist-info}/WHEEL +1 -1
  15. {alchemist_nrel-0.3.0.dist-info → alchemist_nrel-0.3.2.dist-info}/entry_points.txt +1 -1
  16. {alchemist_nrel-0.3.0.dist-info → alchemist_nrel-0.3.2.dist-info}/top_level.txt +0 -1
  17. api/example_client.py +7 -2
  18. api/main.py +3 -2
  19. api/models/requests.py +76 -1
  20. api/models/responses.py +102 -2
  21. api/routers/acquisition.py +25 -0
  22. api/routers/experiments.py +352 -11
  23. api/routers/sessions.py +195 -11
  24. api/routers/visualizations.py +6 -4
  25. api/routers/websocket.py +132 -0
  26. run_api.py → api/run_api.py +8 -7
  27. api/services/session_store.py +370 -71
  28. api/static/assets/index-B6Cf6s_b.css +1 -0
  29. api/static/assets/{index-C0_glioA.js → index-B7njvc9r.js} +223 -208
  30. api/static/index.html +2 -2
  31. ui/gpr_panel.py +11 -5
  32. ui/target_column_dialog.py +299 -0
  33. ui/ui.py +52 -5
  34. alchemist_core/models/ax_model.py +0 -159
  35. alchemist_nrel-0.3.0.dist-info/METADATA +0 -223
  36. api/static/assets/index-CB4V1LI5.css +0 -1
  37. {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
- run_api.py,sha256=-oxrFnDztyI0rfvZv0-QTBAbgW3OcCArdqg3c5l18qs,1849
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=UCjMEWTWPY9ywsuS1meWGF9hqVse7DaFFnhr2oW9qwc,48228
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=dGzXY7XMbrRzOIFC4UoA6mMYEDplKgb58ym_TzmiMss,31332
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=gyccrEq9ddTdKZ4zmCfGXB2bK7zZaCjL9a0FzZ9-eoY,8723
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=VbZjggt8PszTJeG0x1A1vGGM1kT95Bjset3HWT6PQEM,42263
19
- alchemist_core/models/sklearn_model.py,sha256=iCwZ-ZWZuYgFpRQDoWwaIBBWz9dPdf-6wNGPdUHM9cU,36698
20
- alchemist_core/utils/__init__.py,sha256=oQsvUqukRng8GgiZSPMM-xmB-Lv46XveJzYQr2MdkOc,99
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
- alchemist_nrel-0.3.0.dist-info/licenses/LICENSE,sha256=wdIWWEj59ztfQViDuT_9wG3L1K8afUpRSygimXw36wY,1511
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=aZMNuJmt00hpNEkijn5RnVEV7ZPfKzwQxucSasTrrdw,6645
26
- api/main.py,sha256=OHs00UruREujnNT2h1lXtK_ZiNz14lwYH67kqH8o78o,4215
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=Hez7Pnk1Mm7hjsbsG1Kv7pX7imsIkqovroGRmUaIL38,9938
31
- api/models/responses.py,sha256=5jzoIm7HiLWJPJ2U_iL3ACeixWdIuFwjMnhVNE9vCRE,12789
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=7c9ifuCFeDj8osWRHgCjAwbXjhd3iEBlVLW23FOAZXI,5710
34
- api/routers/experiments.py,sha256=q7mgGmzHqe9wr9njSbUw5gmWt2kGoSv86-qbSakSNe0,9467
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=5yvNNgQzZadEq6MU-rjV1DFgtH6awyxYne5UTVZ7NCU,15541
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=QPe1PkgGtzb4Oe4YYgFoi7J1oHJGS5pa1g75KKBDqUM,24180
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=bVzRaf4YFoNKkXuSmzs76F1qaIl_6lUUI3KZJJgVc3U,16255
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=P9QXmIzDTIx7ybgXS7xZXUpvYsmc19j2zaXVFgIJQjQ,499
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-C0_glioA.js,sha256=PDlXBE-WtR6W_pMcuQepY5R62Ih_mQC6GLf0FuszNks,5741783
49
- api/static/assets/index-CB4V1LI5.css,sha256=s-fUWesYGl8kRqel4-sVfuc-xgbVvoJkjocPZFG1y7k,19867
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=rQYCKZr8UlXVL3DVXTq4ArlIodzvDOtZXkYrdotERtw,26465
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/ui.py,sha256=Lvg2H0_LrsrxJYFYFHr2LQsnbJWW5Q2fYqk63qVUJv4,101727
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.0.dist-info/METADATA,sha256=lkBbue0m5K4jyGYvhygJixkTlQhFvQ3oaQHiETdZcEs,7450
63
- alchemist_nrel-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
64
- alchemist_nrel-0.3.0.dist-info/entry_points.txt,sha256=asivtbePfGKa57dNbOe43lVbfN51S9cuJwfUoaE9TOs,69
65
- alchemist_nrel-0.3.0.dist-info/top_level.txt,sha256=-T0oWa1AfOHigC-CJFatoYBZapTpqTBVccZ7eelT5jY,35
66
- alchemist_nrel-0.3.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,3 @@
1
1
  [console_scripts]
2
2
  alchemist = main:main
3
- alchemist-web = run_api:main
3
+ alchemist-web = api.run_api:main
@@ -1,5 +1,4 @@
1
1
  alchemist_core
2
2
  api
3
3
  main
4
- run_api
5
4
  ui
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
- n_added = response.json()["n_added"]
94
- print(f" ✓ Added {n_added} experiments")
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.0",
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(..., description="Session expiration timestamp")
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
+ )
@@ -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(