ivoryos 1.3.9__tar.gz → 1.4.0__tar.gz
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 ivoryos might be problematic. Click here for more details.
- {ivoryos-1.3.9 → ivoryos-1.4.0}/PKG-INFO +3 -2
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/optimizer/ax_optimizer.py +44 -25
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/optimizer/base_optimizer.py +15 -1
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/optimizer/baybe_optimizer.py +24 -17
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/optimizer/nimo_optimizer.py +16 -16
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/data/data.py +27 -9
- ivoryos-1.4.0/ivoryos/routes/data/templates/components/step_card.html +78 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/data/templates/workflow_view.html +14 -5
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/design.py +31 -1
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/design_step.py +2 -1
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/edit_action_form.html +16 -3
- ivoryos-1.4.0/ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/experiment_builder.html +1 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/execute/execute.py +36 -7
- ivoryos-1.4.0/ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
- ivoryos-1.4.0/ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
- ivoryos-1.4.0/ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/execute/templates/components/tab_repeat.html +6 -2
- ivoryos-1.4.0/ivoryos/routes/execute/templates/experiment_run.html +30 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/socket_handlers.py +1 -1
- ivoryos-1.4.0/ivoryos/static/js/action_handlers.js +384 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/js/sortable_design.js +1 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/bo_campaign.py +17 -16
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/db_models.py +122 -18
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/decorators.py +1 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/form.py +22 -5
- ivoryos-1.4.0/ivoryos/utils/nest_script.py +314 -0
- ivoryos-1.4.0/ivoryos/utils/script_runner.py +789 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/utils.py +11 -1
- ivoryos-1.4.0/ivoryos/version.py +1 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos.egg-info/PKG-INFO +3 -2
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos.egg-info/SOURCES.txt +1 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos.egg-info/requires.txt +2 -1
- {ivoryos-1.3.9 → ivoryos-1.4.0}/pyproject.toml +2 -1
- ivoryos-1.3.9/ivoryos/routes/data/templates/components/step_card.html +0 -42
- ivoryos-1.3.9/ivoryos/routes/design/templates/components/python_code_overlay.html +0 -39
- ivoryos-1.3.9/ivoryos/routes/execute/templates/components/run_tabs.html +0 -17
- ivoryos-1.3.9/ivoryos/routes/execute/templates/components/tab_bayesian.html +0 -398
- ivoryos-1.3.9/ivoryos/routes/execute/templates/components/tab_configuration.html +0 -98
- ivoryos-1.3.9/ivoryos/routes/execute/templates/experiment_run.html +0 -294
- ivoryos-1.3.9/ivoryos/static/js/action_handlers.js +0 -250
- ivoryos-1.3.9/ivoryos/utils/script_runner.py +0 -497
- ivoryos-1.3.9/ivoryos/version.py +0 -1
- {ivoryos-1.3.9 → ivoryos-1.4.0}/LICENSE +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/README.md +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/app.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/config.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/optimizer/registry.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/api/api.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/auth/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/auth/auth.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/auth/templates/login.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/auth/templates/signup.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/control/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/control/control.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/control/control_file.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/control/control_new_device.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/control/templates/controllers.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/control/templates/controllers_new.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/control/utils.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/data/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/data/templates/workflow_database.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/design_file.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/action_form.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/actions_panel.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/autofill_toggle.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/canvas.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/canvas_footer.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/canvas_header.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/canvas_main.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/deck_selector.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/instruments_panel.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/modals/drop_modal.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/modals/json_modal.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/modals/new_script_modal.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/modals/rename_modal.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/modals/saveas_modal.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/modals.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/sidebar.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/design/templates/components/text_to_code_panel.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/execute/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/execute/execute_file.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/execute/templates/components/error_modal.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/execute/templates/components/logging_panel.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/execute/templates/components/progress_panel.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/execute/templates/components/run_panel.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/library/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/library/library.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/library/templates/library.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/main/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/main/main.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/main/templates/help.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/routes/main/templates/home.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/server.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/favicon.ico +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/gui_annotation/Slide1.png +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/js/db_delete.js +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/js/overlay.js +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/js/script_metadata.js +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/js/socket_handler.js +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/js/sortable_card.js +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/js/ui_state.js +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/logo.webp +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/static/style.css +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/templates/base.html +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/__init__.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/client_proxy.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/global_config.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/llm_agent.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/py_to_json.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/serilize.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos/utils/task_runner.py +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos.egg-info/dependency_links.txt +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/ivoryos.egg-info/top_level.txt +0 -0
- {ivoryos-1.3.9 → ivoryos-1.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
|
|
5
5
|
Author-email: Ivory Zhang <ivoryzhang@chem.ubc.ca>
|
|
6
6
|
License: MIT
|
|
@@ -17,6 +17,7 @@ Requires-Dist: Flask-SQLAlchemy
|
|
|
17
17
|
Requires-Dist: Flask-WTF
|
|
18
18
|
Requires-Dist: SQLAlchemy-Utils
|
|
19
19
|
Requires-Dist: python-dotenv
|
|
20
|
+
Requires-Dist: pandas
|
|
20
21
|
Requires-Dist: astor; python_version < "3.9"
|
|
21
22
|
Provides-Extra: optimizer-ax
|
|
22
23
|
Requires-Dist: ax-platform; extra == "optimizer-ax"
|
|
@@ -26,7 +27,7 @@ Provides-Extra: optimizer-nimo
|
|
|
26
27
|
Requires-Dist: nimo; extra == "optimizer-nimo"
|
|
27
28
|
Provides-Extra: optimizer
|
|
28
29
|
Requires-Dist: ax-platform>=1.1.2; extra == "optimizer"
|
|
29
|
-
Requires-Dist: baybe; extra == "optimizer"
|
|
30
|
+
Requires-Dist: baybe>=0.14.0; extra == "optimizer"
|
|
30
31
|
Provides-Extra: doc
|
|
31
32
|
Requires-Dist: sphinx; extra == "doc"
|
|
32
33
|
Requires-Dist: sphinx-rtd-theme; extra == "doc"
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
# optimizers/ax_optimizer.py
|
|
2
2
|
from typing import Dict
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
from pandas import DataFrame
|
|
5
5
|
|
|
6
6
|
from ivoryos.optimizer.base_optimizer import OptimizerBase
|
|
7
7
|
from ivoryos.utils.utils import install_and_import
|
|
8
8
|
|
|
9
9
|
class AxOptimizer(OptimizerBase):
|
|
10
|
-
def __init__(self, experiment_name, parameter_space, objective_config, optimizer_config=None
|
|
10
|
+
def __init__(self, experiment_name, parameter_space, objective_config, optimizer_config=None,
|
|
11
|
+
parameter_constraints:list=None, datapath=None):
|
|
12
|
+
self.trial_index_list = None
|
|
11
13
|
try:
|
|
12
14
|
from ax.api.client import Client
|
|
13
15
|
except ImportError as e:
|
|
14
16
|
install_and_import("ax", "ax-platform")
|
|
15
17
|
raise ImportError("Please install Ax with pip install ax-platform to use AxOptimizer. Attempting to install Ax...")
|
|
16
|
-
super().__init__(experiment_name, parameter_space, objective_config, optimizer_config)
|
|
18
|
+
super().__init__(experiment_name, parameter_space, objective_config, optimizer_config, parameter_constraints, )
|
|
17
19
|
|
|
18
20
|
self.client = Client()
|
|
19
21
|
# 2. Configure where Ax will search.
|
|
20
22
|
self.client.configure_experiment(
|
|
21
23
|
name=experiment_name,
|
|
22
|
-
parameters=self._convert_parameter_to_ax_format(parameter_space)
|
|
24
|
+
parameters=self._convert_parameter_to_ax_format(parameter_space),
|
|
25
|
+
parameter_constraints=parameter_constraints
|
|
23
26
|
)
|
|
24
27
|
# 3. Configure the objective function.
|
|
25
28
|
self.client.configure_optimization(objective=self._convert_objective_to_ax_format(objective_config))
|
|
@@ -48,12 +51,17 @@ class AxOptimizer(OptimizerBase):
|
|
|
48
51
|
ax_params = []
|
|
49
52
|
for p in parameter_space:
|
|
50
53
|
if p["type"] == "range":
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
# if step is used here, convert to ChoiceParameterConfig
|
|
55
|
+
if len(p["bounds"]) == 3:
|
|
56
|
+
values = self._create_discrete_search_space(range_with_step=p["bounds"],value_type=p["value_type"])
|
|
57
|
+
ax_params.append(ChoiceParameterConfig(name=p["name"], values=values, parameter_type="int", is_ordered=True))
|
|
58
|
+
else:
|
|
59
|
+
ax_params.append(
|
|
60
|
+
RangeParameterConfig(
|
|
61
|
+
name=p["name"],
|
|
62
|
+
bounds=tuple(p["bounds"]),
|
|
63
|
+
parameter_type=p["value_type"]
|
|
64
|
+
))
|
|
57
65
|
elif p["type"] == "choice":
|
|
58
66
|
ax_params.append(
|
|
59
67
|
ChoiceParameterConfig(
|
|
@@ -101,15 +109,22 @@ class AxOptimizer(OptimizerBase):
|
|
|
101
109
|
return GenerationStrategy(steps=[generator_1, generator_2])
|
|
102
110
|
|
|
103
111
|
def suggest(self, n=1):
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
trials = self.client.get_next_trials(n)
|
|
113
|
+
trial_index_list = []
|
|
114
|
+
param_list = []
|
|
115
|
+
for trial_index, params in trials.items():
|
|
116
|
+
trial_index_list.append(trial_index)
|
|
117
|
+
param_list.append(params)
|
|
118
|
+
self.trial_index_list = trial_index_list
|
|
119
|
+
return param_list
|
|
107
120
|
|
|
108
121
|
def observe(self, results):
|
|
109
|
-
self.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
122
|
+
for trial_index, result in zip(self.trial_index_list, results):
|
|
123
|
+
obj_only_result = {k: v for k, v in result.items() if k in [obj["name"] for obj in self.objective_config]}
|
|
124
|
+
self.client.complete_trial(
|
|
125
|
+
trial_index=trial_index,
|
|
126
|
+
raw_data=obj_only_result
|
|
127
|
+
)
|
|
113
128
|
|
|
114
129
|
@staticmethod
|
|
115
130
|
def get_schema():
|
|
@@ -117,29 +132,33 @@ class AxOptimizer(OptimizerBase):
|
|
|
117
132
|
"parameter_types": ["range", "choice"],
|
|
118
133
|
"multiple_objectives": True,
|
|
119
134
|
# "objective_weights": True,
|
|
135
|
+
"supports_continuous": True,
|
|
136
|
+
"supports_constraints": True,
|
|
120
137
|
"optimizer_config": {
|
|
121
138
|
"step_1": {"model": ["Sobol", "Uniform", "Factorial", "Thompson"], "num_samples": 5},
|
|
122
139
|
"step_2": {"model": ["BoTorch", "SAASBO", "SAAS_MTGP", "Legacy_GPEI", "EB", "EB_Ashr", "ST_MTGP", "BO_MIXED", "Contextual_SACBO"]}
|
|
123
140
|
},
|
|
141
|
+
|
|
124
142
|
}
|
|
125
143
|
|
|
126
|
-
def append_existing_data(self, existing_data):
|
|
144
|
+
def append_existing_data(self, existing_data:DataFrame):
|
|
127
145
|
"""
|
|
128
146
|
Append existing data to the Ax experiment.
|
|
129
147
|
:param existing_data: A dictionary containing existing data.
|
|
130
148
|
"""
|
|
131
|
-
|
|
132
|
-
if not existing_data:
|
|
133
|
-
return
|
|
149
|
+
|
|
134
150
|
if isinstance(existing_data, DataFrame):
|
|
151
|
+
if existing_data.empty:
|
|
152
|
+
return
|
|
135
153
|
existing_data = existing_data.to_dict(orient="records")
|
|
136
154
|
parameter_names = [i.get("name") for i in self.parameter_space]
|
|
137
155
|
objective_names = [i.get("name") for i in self.objective_config]
|
|
138
|
-
for
|
|
139
|
-
#
|
|
140
|
-
|
|
156
|
+
for entry in existing_data:
|
|
157
|
+
# for name, value in entry.items():
|
|
158
|
+
# First attach the trial and note the trial index
|
|
159
|
+
parameters = {name: value for name, value in entry.items() if name in parameter_names}
|
|
141
160
|
trial_index = self.client.attach_trial(parameters=parameters)
|
|
142
|
-
raw_data = {name: value for name in
|
|
161
|
+
raw_data = {name: value for name, value in entry.items() if name in objective_names}
|
|
143
162
|
# Then complete the trial with the existing data
|
|
144
163
|
self.client.complete_trial(trial_index=trial_index, raw_data=raw_data)
|
|
145
164
|
|
|
@@ -5,7 +5,8 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class OptimizerBase(ABC):
|
|
8
|
-
def __init__(self, experiment_name:str, parameter_space: list, objective_config: dict, optimizer_config: dict,
|
|
8
|
+
def __init__(self, experiment_name:str, parameter_space: list, objective_config: dict, optimizer_config: dict,
|
|
9
|
+
parameter_constraints:list=None, datapath:str=None):
|
|
9
10
|
"""
|
|
10
11
|
:param experiment_name: arbitrary name
|
|
11
12
|
:param parameter_space: list of parameter names
|
|
@@ -47,6 +48,19 @@ class OptimizerBase(ABC):
|
|
|
47
48
|
def append_existing_data(self, existing_data):
|
|
48
49
|
pass
|
|
49
50
|
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _create_discrete_search_space(range_with_step=None, value_type ="float"):
|
|
53
|
+
if range_with_step is None:
|
|
54
|
+
range_with_step = []
|
|
55
|
+
import numpy as np
|
|
56
|
+
low, high, step = range_with_step
|
|
57
|
+
values = np.arange(low, high + 1e-9 * step, step).tolist()
|
|
58
|
+
if value_type == "float":
|
|
59
|
+
values = [float(v) for v in values]
|
|
60
|
+
if value_type == "int":
|
|
61
|
+
values = [int(v) for v in values]
|
|
62
|
+
return values
|
|
63
|
+
|
|
50
64
|
@staticmethod
|
|
51
65
|
def get_schema():
|
|
52
66
|
"""
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
### Directory: ivoryos/optimizers/baybe_optimizer.py
|
|
2
2
|
from typing import Dict
|
|
3
3
|
|
|
4
|
+
from pandas import DataFrame
|
|
4
5
|
|
|
5
6
|
from ivoryos.utils.utils import install_and_import
|
|
6
7
|
from ivoryos.optimizer.base_optimizer import OptimizerBase
|
|
7
8
|
|
|
8
9
|
class BaybeOptimizer(OptimizerBase):
|
|
9
|
-
def __init__(self, experiment_name, parameter_space, objective_config, optimizer_config
|
|
10
|
+
def __init__(self, experiment_name, parameter_space, objective_config, optimizer_config,
|
|
11
|
+
parameter_constraints:list=None, datapath=None):
|
|
10
12
|
try:
|
|
11
13
|
from baybe import Campaign
|
|
12
14
|
except ImportError:
|
|
13
15
|
install_and_import("baybe")
|
|
14
16
|
print("Please install Baybe with pip install baybe to before register BaybeOptimizer.")
|
|
15
17
|
|
|
16
|
-
super().__init__(experiment_name, parameter_space, objective_config, optimizer_config)
|
|
18
|
+
super().__init__(experiment_name, parameter_space, objective_config, optimizer_config, parameter_constraints, )
|
|
17
19
|
self._trial_id = 0
|
|
18
20
|
self._trials = {}
|
|
19
21
|
|
|
@@ -25,8 +27,8 @@ class BaybeOptimizer(OptimizerBase):
|
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def suggest(self, n=1):
|
|
28
|
-
self.df = self.experiment.recommend(batch_size=n)
|
|
29
|
-
return self.
|
|
30
|
+
# self.df = self.experiment.recommend(batch_size=n)
|
|
31
|
+
return self.experiment.recommend(batch_size=n).to_dict(orient="records")
|
|
30
32
|
|
|
31
33
|
def observe(self, results, index=None):
|
|
32
34
|
"""
|
|
@@ -35,21 +37,19 @@ class BaybeOptimizer(OptimizerBase):
|
|
|
35
37
|
:param index: The index of the trial in the DataFrame, if applicable.
|
|
36
38
|
|
|
37
39
|
"""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
self.experiment.add_measurements(self.df)
|
|
40
|
+
df = DataFrame(results)
|
|
41
|
+
self.experiment.add_measurements(df)
|
|
41
42
|
|
|
42
|
-
def append_existing_data(self, existing_data:
|
|
43
|
+
def append_existing_data(self, existing_data: DataFrame):
|
|
43
44
|
"""
|
|
44
45
|
Append existing data to the Ax experiment.
|
|
45
46
|
:param existing_data: A dictionary containing existing data.
|
|
46
47
|
"""
|
|
47
|
-
|
|
48
|
-
if not existing_data:
|
|
48
|
+
if existing_data.empty:
|
|
49
49
|
return
|
|
50
50
|
# parameter_names = [i.get("name") for i in self.parameter_space]
|
|
51
51
|
# objective_names = [i.get("name") for i in self.objective_config]
|
|
52
|
-
self.experiment.add_measurements(
|
|
52
|
+
self.experiment.add_measurements(existing_data)
|
|
53
53
|
# for name, value in existing_data.items():
|
|
54
54
|
# # First attach the trial and note the trial index
|
|
55
55
|
# parameters = {name: value for name in existing_data if name in parameter_names}
|
|
@@ -65,9 +65,11 @@ class BaybeOptimizer(OptimizerBase):
|
|
|
65
65
|
:param parameter_space: The parameter space configuration.
|
|
66
66
|
[
|
|
67
67
|
{"name": "param_1", "type": "range", "bounds": [1.0, 2.0], "value_type": "float"},
|
|
68
|
-
{"name": "param_2", "type": "
|
|
69
|
-
|
|
70
|
-
{"name": "
|
|
68
|
+
{"name": "param_2", "type": "range", "bounds": [1.0, 2.0, 0.5], "value_type": "float"},
|
|
69
|
+
|
|
70
|
+
{"name": "param_3", "type": "choice", "bounds": ["a", "b", "c"], "value_type": "str"},
|
|
71
|
+
{"name": "param_4", "type": "range", "bounds": [0 10], "value_type": "int"},
|
|
72
|
+
{"name": "param_5", "type": "substance", "bounds": ["methanol", "water", "toluene"], "value_type": "str"} #TODO
|
|
71
73
|
]
|
|
72
74
|
:return: A list of Baybe parameters.
|
|
73
75
|
"""
|
|
@@ -77,7 +79,10 @@ class BaybeOptimizer(OptimizerBase):
|
|
|
77
79
|
parameters = []
|
|
78
80
|
for p in parameter_space:
|
|
79
81
|
if p["type"] == "range":
|
|
80
|
-
if p["
|
|
82
|
+
if len(p["bounds"]) == 3:
|
|
83
|
+
values = self._create_discrete_search_space(range_with_step=p["bounds"],value_type=p["value_type"])
|
|
84
|
+
parameters.append(NumericalDiscreteParameter(name=p["name"], values=values))
|
|
85
|
+
elif p["value_type"] == "float":
|
|
81
86
|
parameters.append(NumericalContinuousParameter(name=p["name"], bounds=p["bounds"]))
|
|
82
87
|
elif p["value_type"] == "int":
|
|
83
88
|
values = tuple([int(v) for v in range(p["bounds"][0], p["bounds"][1] + 1)])
|
|
@@ -106,10 +111,10 @@ class BaybeOptimizer(OptimizerBase):
|
|
|
106
111
|
weights = []
|
|
107
112
|
for obj in objective_config:
|
|
108
113
|
obj_name = obj.get("name")
|
|
109
|
-
minimize = obj.get("minimize",
|
|
114
|
+
minimize = obj.get("minimize", True)
|
|
110
115
|
weight = obj.get("weight", 1)
|
|
111
116
|
weights.append(weight)
|
|
112
|
-
targets.append(NumericalTarget(name=obj_name,
|
|
117
|
+
targets.append(NumericalTarget(name=obj_name, minimize=minimize))
|
|
113
118
|
|
|
114
119
|
if len(targets) == 1:
|
|
115
120
|
return SingleTargetObjective(target=targets[0])
|
|
@@ -156,6 +161,8 @@ class BaybeOptimizer(OptimizerBase):
|
|
|
156
161
|
return {
|
|
157
162
|
"parameter_types": ["range", "choice", "substance"],
|
|
158
163
|
"multiple_objectives": True,
|
|
164
|
+
"supports_continuous": True,
|
|
165
|
+
"supports_constraints": False,
|
|
159
166
|
"optimizer_config": {
|
|
160
167
|
"step_1": {"model": ["Random", "FPS"], "num_samples": 10},
|
|
161
168
|
"step_2": {"model": ["BOTorch", "Naive Hybrid Space"]}
|
|
@@ -7,7 +7,8 @@ from ivoryos.optimizer.base_optimizer import OptimizerBase
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class NIMOOptimizer(OptimizerBase):
|
|
10
|
-
def __init__(self, experiment_name:str, parameter_space: list, objective_config: list, optimizer_config: dict,
|
|
10
|
+
def __init__(self, experiment_name:str, parameter_space: list, objective_config: list, optimizer_config: dict,
|
|
11
|
+
parameter_constraints:list=None, datapath:str=None):
|
|
11
12
|
"""
|
|
12
13
|
:param experiment_name: arbitrary name
|
|
13
14
|
:param parameter_space: list of parameter names
|
|
@@ -33,7 +34,7 @@ class NIMOOptimizer(OptimizerBase):
|
|
|
33
34
|
self.objective_config = objective_config
|
|
34
35
|
self.optimizer_config = optimizer_config
|
|
35
36
|
|
|
36
|
-
super().__init__(experiment_name, parameter_space, objective_config, optimizer_config, datapath)
|
|
37
|
+
super().__init__(experiment_name, parameter_space, objective_config, optimizer_config, parameter_constraints, datapath)
|
|
37
38
|
|
|
38
39
|
os.makedirs(os.path.join(self.datapath, "nimo_data"), exist_ok=True)
|
|
39
40
|
|
|
@@ -52,6 +53,7 @@ class NIMOOptimizer(OptimizerBase):
|
|
|
52
53
|
# Extract parameter names and their possible values
|
|
53
54
|
import pandas as pd
|
|
54
55
|
import nimo
|
|
56
|
+
import numpy as np
|
|
55
57
|
if os.path.exists(self.candidates) and nimo.history(self.candidates, self.n_objectives):
|
|
56
58
|
return
|
|
57
59
|
param_names = [p["name"] for p in self.parameter_space]
|
|
@@ -60,11 +62,9 @@ class NIMOOptimizer(OptimizerBase):
|
|
|
60
62
|
for p in self.parameter_space:
|
|
61
63
|
if p["type"] == "choice" and isinstance(p["bounds"], list):
|
|
62
64
|
param_values.append(p["bounds"])
|
|
63
|
-
elif p["type"] == "range" and len(p["bounds"]) ==
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
step = (high - low) / (num_points - 1)
|
|
67
|
-
param_values.append([round(low + i * step, 4) for i in range(num_points)])
|
|
65
|
+
elif p["type"] == "range" and len(p["bounds"]) == 3:
|
|
66
|
+
values = self._create_discrete_search_space(range_with_step=p["bounds"],value_type=p["value_type"])
|
|
67
|
+
param_values.append(values)
|
|
68
68
|
else:
|
|
69
69
|
raise ValueError(f"Unsupported parameter format: {p}")
|
|
70
70
|
|
|
@@ -73,7 +73,6 @@ class NIMOOptimizer(OptimizerBase):
|
|
|
73
73
|
|
|
74
74
|
# Create a DataFrame with parameter columns
|
|
75
75
|
df = pd.DataFrame(combos, columns=param_names)
|
|
76
|
-
|
|
77
76
|
# Add empty objective columns
|
|
78
77
|
for obj in self.objective_config:
|
|
79
78
|
df[obj["name"]] = ""
|
|
@@ -101,20 +100,19 @@ class NIMOOptimizer(OptimizerBase):
|
|
|
101
100
|
for _, row in proposals_df.iterrows():
|
|
102
101
|
proposal = {name: row[name] for name in param_names}
|
|
103
102
|
proposals.append(proposal)
|
|
104
|
-
return proposals
|
|
103
|
+
return proposals
|
|
105
104
|
|
|
106
105
|
def _convert_observation_to_list(self, obs: dict) -> list:
|
|
107
106
|
obj_names = [o["name"] for o in self.objective_config]
|
|
108
107
|
return [obs.get(name, None) for name in obj_names]
|
|
109
108
|
|
|
110
|
-
def observe(self, results:
|
|
109
|
+
def observe(self, results: list):
|
|
111
110
|
"""
|
|
112
111
|
observe single output, nimo obj input is [1,2,3] or [[1, 2], [1, 2], [1, 2]] for MO
|
|
113
|
-
:param results: {"objective_name": "value"}
|
|
112
|
+
:param results: [{"objective_name": "value"}, {"objective_name": "value"}]]
|
|
114
113
|
"""
|
|
115
114
|
import nimo
|
|
116
|
-
nimo_objective_values = [self._convert_observation_to_list(results
|
|
117
|
-
|
|
115
|
+
nimo_objective_values = [self._convert_observation_to_list(result) for result in results]
|
|
118
116
|
nimo.output_update(input_file=self.proposals,
|
|
119
117
|
output_file=self.candidates,
|
|
120
118
|
num_objectives=self.n_objectives,
|
|
@@ -128,8 +126,10 @@ class NIMOOptimizer(OptimizerBase):
|
|
|
128
126
|
@staticmethod
|
|
129
127
|
def get_schema():
|
|
130
128
|
return {
|
|
131
|
-
"parameter_types": ["choice"],
|
|
129
|
+
"parameter_types": ["choice", "range"],
|
|
132
130
|
"multiple_objectives": True,
|
|
131
|
+
"supports_continuous": False,
|
|
132
|
+
"supports_constraints": False,
|
|
133
133
|
"optimizer_config": {
|
|
134
134
|
"step_1": {"model": ["RE", "ES"], "num_samples": 5},
|
|
135
135
|
"step_2": {"model": ["PHYSBO", "PDC", "BLOX", "PTR", "SLESA", "BOMP", "COMBI"]}
|
|
@@ -142,7 +142,7 @@ class NIMOOptimizer(OptimizerBase):
|
|
|
142
142
|
if __name__ == "__main__":
|
|
143
143
|
parameter_space = [
|
|
144
144
|
{"name": "silica", "type": "choice", "bounds": [100], "value_type": "float"},
|
|
145
|
-
{"name": "water", "type": "
|
|
145
|
+
{"name": "water", "type": "range", "bounds": [500, 900, 50], "value_type": "float"},
|
|
146
146
|
{"name": "PVA", "type": "choice", "bounds": [0, 0.005, 0.0075, 0.01, 0.05, 0.075, 0.1], "value_type": "float"},
|
|
147
147
|
{"name": "SDS", "type": "choice", "bounds": [0], "value_type": "float"},
|
|
148
148
|
{"name": "DTAB", "type": "choice", "bounds": [0, 0.005, 0.0075, 0.01, 0.05, 0.075, 0.1], "value_type": "float"},
|
|
@@ -160,5 +160,5 @@ if __name__ == "__main__":
|
|
|
160
160
|
nimo_optimizer = NIMOOptimizer(experiment_name="example_experiment", optimizer_config=optimizer_config, parameter_space=parameter_space, objective_config=objective_config)
|
|
161
161
|
nimo_optimizer.suggest(n=1)
|
|
162
162
|
nimo_optimizer.observe(
|
|
163
|
-
results={"objective": 1.0}
|
|
163
|
+
results=[{"objective": 1.0}]
|
|
164
164
|
)
|
|
@@ -118,25 +118,43 @@ def workflow_phase_data(workflow_id: int):
|
|
|
118
118
|
|
|
119
119
|
:param workflow_id: workflow id
|
|
120
120
|
"""
|
|
121
|
+
|
|
121
122
|
workflow = db.session.get(WorkflowRun, workflow_id)
|
|
122
123
|
if not workflow:
|
|
123
124
|
return jsonify({})
|
|
124
125
|
|
|
125
126
|
phase_data = {}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
WorkflowPhase.repeat_index).all()
|
|
127
|
+
main_phases = WorkflowPhase.query.filter_by(run_id=workflow_id, name='main') \
|
|
128
|
+
.order_by(WorkflowPhase.repeat_index).all()
|
|
129
129
|
|
|
130
130
|
for phase in main_phases:
|
|
131
131
|
outputs = phase.outputs or {}
|
|
132
132
|
phase_index = phase.repeat_index
|
|
133
|
-
# Convert each key to list of dicts for x (phase_index) and y (value)
|
|
134
133
|
phase_data[phase_index] = {}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
|
|
135
|
+
# Normalize everything to a list of dicts
|
|
136
|
+
if isinstance(outputs, dict):
|
|
137
|
+
outputs = [outputs]
|
|
138
|
+
elif isinstance(outputs, list):
|
|
139
|
+
# flatten if it’s nested like [[{...}, {...}]]
|
|
140
|
+
outputs = [
|
|
141
|
+
item for sublist in outputs
|
|
142
|
+
for item in (sublist if isinstance(sublist, list) else [sublist])
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
# convert each output entry to plotting format
|
|
146
|
+
for out in outputs:
|
|
147
|
+
if not isinstance(out, dict):
|
|
148
|
+
continue
|
|
149
|
+
for k, v in out.items():
|
|
150
|
+
if isinstance(v, (int, float)):
|
|
151
|
+
phase_data[phase_index].setdefault(k, []).append(
|
|
152
|
+
{"x": phase_index, "y": v}
|
|
153
|
+
)
|
|
154
|
+
elif isinstance(v, list) and all(isinstance(i, (int, float)) for i in v):
|
|
155
|
+
phase_data[phase_index].setdefault(k, []).extend(
|
|
156
|
+
{"x": phase_index, "y": val} for val in v
|
|
157
|
+
)
|
|
140
158
|
|
|
141
159
|
return jsonify(phase_data)
|
|
142
160
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<div class="card mb-2 {{ 'border-danger text-danger bg-light' if phase.run_error else 'border-secondary' }}">
|
|
2
|
+
<div class="card-body p-2">
|
|
3
|
+
<small class="text-muted">
|
|
4
|
+
<i class="fas fa-play-circle me-1"></i> Start: {{ phase.start_time.strftime('%H:%M:%S') if phase.start_time else 'N/A' }}
|
|
5
|
+
<i class="fas fa-stop-circle ms-2 me-1"></i> End: {{ phase.end_time.strftime('%H:%M:%S') if phase.end_time else 'N/A' }}
|
|
6
|
+
</small>
|
|
7
|
+
{% if phase.parameters %}
|
|
8
|
+
<div class="mt-2">
|
|
9
|
+
<strong>Parameters: </strong>
|
|
10
|
+
{% if phase.parameters is mapping %}
|
|
11
|
+
{% if phase.parameters %}
|
|
12
|
+
<div class="mt-2">
|
|
13
|
+
<strong>Parameters: </strong>
|
|
14
|
+
{% for key, value in phase.parameters.items() %}
|
|
15
|
+
<span class="badge bg-secondary me-1">{{ key }}: {{ value }}</span>
|
|
16
|
+
{% endfor %}
|
|
17
|
+
</div>
|
|
18
|
+
{% endif %}
|
|
19
|
+
{% else %}
|
|
20
|
+
{% for batch in phase.parameters %}
|
|
21
|
+
<div class="mt-1">
|
|
22
|
+
<span class="badge bg-info text-dark me-1">Batch {{ loop.index }}</span>
|
|
23
|
+
{% for key, value in batch.items() %}
|
|
24
|
+
<span class="badge bg-secondary me-1">{{ key }}: {{ value }}</span>
|
|
25
|
+
{% endfor %}
|
|
26
|
+
</div>
|
|
27
|
+
{% endfor %}
|
|
28
|
+
{% endif %}
|
|
29
|
+
</div>
|
|
30
|
+
{% endif %}
|
|
31
|
+
|
|
32
|
+
{% if phase.steps %}
|
|
33
|
+
<div class="mt-2">
|
|
34
|
+
<strong>Steps:</strong>
|
|
35
|
+
<ul class="mb-0">
|
|
36
|
+
{% for step in phase.steps %}
|
|
37
|
+
<li class="{{ 'text-danger' if step.run_error else '' }}">
|
|
38
|
+
{{ step.method_name }}
|
|
39
|
+
<small class="text-muted">
|
|
40
|
+
({{ step.start_time.strftime('%H:%M:%S') if step.start_time else 'N/A' }} –
|
|
41
|
+
{{ step.end_time.strftime('%H:%M:%S') if step.end_time else 'N/A' }})
|
|
42
|
+
</small>
|
|
43
|
+
</li>
|
|
44
|
+
{% endfor %}
|
|
45
|
+
</ul>
|
|
46
|
+
</div>
|
|
47
|
+
{% endif %}
|
|
48
|
+
{% if phase.outputs %}
|
|
49
|
+
<div class="mt-2">
|
|
50
|
+
<strong>Outputs:</strong>
|
|
51
|
+
|
|
52
|
+
{% if phase.outputs is mapping %}
|
|
53
|
+
{% for key, value in phase.outputs.items() %}
|
|
54
|
+
<span class="badge bg-success me-1">{{ key }}: {{ value }}</span>
|
|
55
|
+
{% endfor %}
|
|
56
|
+
|
|
57
|
+
{% elif phase.outputs is sequence %}
|
|
58
|
+
{% for batch in phase.outputs %}
|
|
59
|
+
<div class="mt-1">
|
|
60
|
+
<span class="badge bg-info text-dark me-1">Batch {{ loop.index }}</span>
|
|
61
|
+
{% if batch is mapping %}
|
|
62
|
+
{% for key, value in batch.items() %}
|
|
63
|
+
<span class="badge bg-success me-1">{{ key }}: {{ value }}</span>
|
|
64
|
+
{% endfor %}
|
|
65
|
+
{% elif batch is sequence %}
|
|
66
|
+
{% for kwargs in batch %}
|
|
67
|
+
{% for key, value in kwargs.items() %}
|
|
68
|
+
<span class="badge bg-success me-1">{{ key }}: {{ value }}</span>
|
|
69
|
+
{% endfor %}
|
|
70
|
+
{% endfor %}
|
|
71
|
+
{% endif %}
|
|
72
|
+
</div>
|
|
73
|
+
{% endfor %}
|
|
74
|
+
{% endif %}
|
|
75
|
+
</div>
|
|
76
|
+
{% endif %}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
@@ -276,12 +276,17 @@
|
|
|
276
276
|
repeatKeys.forEach(repeat_index => {
|
|
277
277
|
const arr = data[repeat_index][selectedKey];
|
|
278
278
|
if (arr && arr.length) {
|
|
279
|
-
arr.
|
|
279
|
+
const batchCount = arr.length;
|
|
280
|
+
|
|
281
|
+
arr.forEach((d, batchIdx) => {
|
|
282
|
+
// Compute fractional x for batch indexing: e.g. 1.1, 1.2, 1.3
|
|
283
|
+
const xVal = parseFloat(repeat_index) + (batchIdx + 1) / (batchCount + 1);
|
|
284
|
+
|
|
280
285
|
if (typeof d === 'object' && d.x !== undefined && d.y !== undefined) {
|
|
281
|
-
x.push(
|
|
286
|
+
x.push(xVal);
|
|
282
287
|
y.push(d.y);
|
|
283
288
|
} else if (typeof d === 'number') {
|
|
284
|
-
x.push(
|
|
289
|
+
x.push(xVal);
|
|
285
290
|
y.push(d);
|
|
286
291
|
}
|
|
287
292
|
});
|
|
@@ -293,12 +298,16 @@
|
|
|
293
298
|
y: y,
|
|
294
299
|
mode: 'markers',
|
|
295
300
|
name: selectedKey,
|
|
301
|
+
marker: { size: 8 },
|
|
302
|
+
line: { shape: 'linear', width: 1 },
|
|
296
303
|
};
|
|
297
304
|
|
|
298
305
|
const layout = {
|
|
299
306
|
xaxis: {
|
|
300
|
-
title: '
|
|
301
|
-
gridcolor: '#e9ecef'
|
|
307
|
+
title: 'Trial (Batch Sub-index)',
|
|
308
|
+
gridcolor: '#e9ecef',
|
|
309
|
+
tickvals: repeatKeys.map(k => parseFloat(k)),
|
|
310
|
+
ticktext: repeatKeys.map(k => `Trial ${k}`),
|
|
302
311
|
},
|
|
303
312
|
yaxis: {
|
|
304
313
|
title: selectedKey,
|
|
@@ -100,6 +100,27 @@ def experiment_builder():
|
|
|
100
100
|
script=script, defined_variables=deck_variables, buttons_dict=design_buttons,
|
|
101
101
|
local_variables=global_config.defined_variables, block_variables=global_config.building_blocks)
|
|
102
102
|
|
|
103
|
+
@design.route("/draft/code_preview", methods=["GET"])
|
|
104
|
+
@login_required
|
|
105
|
+
def compile_preview():
|
|
106
|
+
# Get mode and batch from query parameters
|
|
107
|
+
script = utils.get_script_file()
|
|
108
|
+
mode = request.args.get("mode", "single") # default to "single"
|
|
109
|
+
batch = request.args.get("batch", "sample") # default to "sample"
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
# Example: decide which code to return based on mode/batch
|
|
113
|
+
if mode == "single":
|
|
114
|
+
code = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
115
|
+
elif mode == "batch":
|
|
116
|
+
code = script.compile(current_app.config['SCRIPT_FOLDER'], batch=True, mode=batch)
|
|
117
|
+
else:
|
|
118
|
+
code = "Invalid mode. Please select 'single' or 'batch'."
|
|
119
|
+
except Exception as e:
|
|
120
|
+
code = f"Error compiling: {e}"
|
|
121
|
+
# print(code)
|
|
122
|
+
return jsonify(code=code)
|
|
123
|
+
|
|
103
124
|
|
|
104
125
|
@design.route("/draft/meta", methods=["PATCH"])
|
|
105
126
|
@login_required
|
|
@@ -320,13 +341,16 @@ def methods_handler(instrument: str = ''):
|
|
|
320
341
|
request.form
|
|
321
342
|
if "hidden_name" in request.form:
|
|
322
343
|
deck_snapshot = global_config.deck_snapshot
|
|
344
|
+
block_snapshot = global_config.building_blocks
|
|
323
345
|
method_name = request.form.get("hidden_name", None)
|
|
324
346
|
form = forms.get(method_name) if forms else None
|
|
325
347
|
insert_position = request.form.get("drop_target_id", None)
|
|
348
|
+
|
|
326
349
|
if form:
|
|
327
350
|
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
328
351
|
if form.validate_on_submit():
|
|
329
352
|
function_name = kwargs.pop("hidden_name")
|
|
353
|
+
batch_action = kwargs.pop("batch_action", False)
|
|
330
354
|
save_data = kwargs.pop('return', '')
|
|
331
355
|
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
332
356
|
|
|
@@ -335,11 +359,17 @@ def methods_handler(instrument: str = ''):
|
|
|
335
359
|
|
|
336
360
|
script.eval_list(kwargs, primitive_arg_types)
|
|
337
361
|
kwargs = script.validate_variables(kwargs)
|
|
362
|
+
coroutine = False
|
|
363
|
+
if instrument.startswith("deck") and deck_snapshot:
|
|
364
|
+
coroutine = deck_snapshot[instrument][function_name].get("coroutine", False)
|
|
365
|
+
elif instrument.startswith("blocks") and block_snapshot:
|
|
366
|
+
coroutine = block_snapshot[instrument][function_name].get("coroutine", False)
|
|
338
367
|
action = {"instrument": instrument, "action": function_name,
|
|
339
368
|
"args": kwargs,
|
|
340
369
|
"return": save_data,
|
|
341
370
|
'arg_types': primitive_arg_types,
|
|
342
|
-
"coroutine":
|
|
371
|
+
"coroutine": coroutine,
|
|
372
|
+
"batch_action": batch_action,
|
|
343
373
|
}
|
|
344
374
|
script.add_action(action=action, insert_position=insert_position)
|
|
345
375
|
else:
|
|
@@ -57,8 +57,9 @@ def save_step(uuid: int):
|
|
|
57
57
|
kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
|
|
58
58
|
if forms and forms.validate_on_submit():
|
|
59
59
|
save_as = kwargs.pop('return', '')
|
|
60
|
+
batch_action = kwargs.pop('batch_action', False)
|
|
60
61
|
kwargs = script.validate_variables(kwargs)
|
|
61
|
-
script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as)
|
|
62
|
+
script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as, batch_action=batch_action)
|
|
62
63
|
else:
|
|
63
64
|
warning = f"Compilation failed: {str(forms.errors)}"
|
|
64
65
|
utils.post_script_file(script)
|