ivoryos 1.2.1__py3-none-any.whl → 1.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- ivoryos/optimizer/ax_optimizer.py +164 -0
- ivoryos/optimizer/base_optimizer.py +65 -0
- ivoryos/optimizer/baybe_optimizer.py +183 -0
- ivoryos/optimizer/registry.py +9 -0
- ivoryos/routes/api/api.py +56 -0
- ivoryos/routes/auth/templates/login.html +25 -0
- ivoryos/routes/auth/templates/signup.html +32 -0
- ivoryos/routes/control/templates/controllers.html +166 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/data/templates/components/step_card.html +13 -0
- ivoryos/routes/data/templates/workflow_database.html +109 -0
- ivoryos/routes/data/templates/workflow_view.html +130 -0
- ivoryos/routes/design/templates/components/action_form.html +53 -0
- ivoryos/routes/design/templates/components/actions_panel.html +25 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
- ivoryos/routes/design/templates/components/canvas.html +5 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
- ivoryos/routes/design/templates/components/canvas_header.html +75 -0
- ivoryos/routes/design/templates/components/canvas_main.html +34 -0
- ivoryos/routes/design/templates/components/deck_selector.html +10 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +38 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +66 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +39 -0
- ivoryos/routes/design/templates/components/sidebar.html +15 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +41 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +398 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
- ivoryos/routes/execute/templates/experiment_run.html +294 -0
- ivoryos/routes/library/templates/library.html +92 -0
- ivoryos/routes/main/templates/help.html +141 -0
- ivoryos/routes/main/templates/home.html +103 -0
- ivoryos/static/favicon.ico +0 -0
- ivoryos/static/gui_annotation/Slide1.png +0 -0
- ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- ivoryos/static/js/action_handlers.js +213 -0
- ivoryos/static/js/db_delete.js +23 -0
- ivoryos/static/js/overlay.js +12 -0
- ivoryos/static/js/script_metadata.js +39 -0
- ivoryos/static/js/socket_handler.js +125 -0
- ivoryos/static/js/sortable_card.js +24 -0
- ivoryos/static/js/sortable_design.js +138 -0
- ivoryos/static/js/ui_state.js +113 -0
- ivoryos/static/logo.webp +0 -0
- ivoryos/static/style.css +211 -0
- ivoryos/templates/base.html +157 -0
- ivoryos/utils/serilize.py +4 -6
- ivoryos/version.py +1 -1
- {ivoryos-1.2.1.dist-info → ivoryos-1.2.3.dist-info}/METADATA +9 -5
- ivoryos-1.2.3.dist-info/RECORD +100 -0
- {ivoryos-1.2.1.dist-info → ivoryos-1.2.3.dist-info}/WHEEL +1 -1
- {ivoryos-1.2.1.dist-info → ivoryos-1.2.3.dist-info}/top_level.txt +0 -1
- ivoryos-1.2.1.dist-info/RECORD +0 -51
- tests/__init__.py +0 -0
- tests/conftest.py +0 -133
- tests/integration/__init__.py +0 -0
- tests/integration/test_route_auth.py +0 -80
- tests/integration/test_route_control.py +0 -94
- tests/integration/test_route_database.py +0 -61
- tests/integration/test_route_design.py +0 -36
- tests/integration/test_route_main.py +0 -35
- tests/integration/test_sockets.py +0 -26
- {ivoryos-1.2.1.dist-info → ivoryos-1.2.3.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# optimizers/ax_optimizer.py
|
|
2
|
+
from typing import Dict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from ivoryos.optimizer.base_optimizer import OptimizerBase
|
|
7
|
+
from ivoryos.utils.utils import install_and_import
|
|
8
|
+
|
|
9
|
+
class AxOptimizer(OptimizerBase):
|
|
10
|
+
def __init__(self, experiment_name, parameter_space, objective_config, optimizer_config=None):
|
|
11
|
+
try:
|
|
12
|
+
from ax.api.client import Client
|
|
13
|
+
except ImportError as e:
|
|
14
|
+
install_and_import("ax", "ax-platform")
|
|
15
|
+
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)
|
|
17
|
+
|
|
18
|
+
self.client = Client()
|
|
19
|
+
# 2. Configure where Ax will search.
|
|
20
|
+
self.client.configure_experiment(
|
|
21
|
+
name=experiment_name,
|
|
22
|
+
parameters=self._convert_parameter_to_ax_format(parameter_space)
|
|
23
|
+
)
|
|
24
|
+
# 3. Configure the objective function.
|
|
25
|
+
self.client.configure_optimization(objective=self._convert_objective_to_ax_format(objective_config))
|
|
26
|
+
if optimizer_config:
|
|
27
|
+
self.client.set_generation_strategy(self._convert_generator_to_ax_format(optimizer_config))
|
|
28
|
+
self.generators = self._create_generator_mapping()
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def _create_generator_mapping():
|
|
32
|
+
"""Create a mapping from string values to Generator enum members."""
|
|
33
|
+
from ax.modelbridge import Generators
|
|
34
|
+
return {member.value: member for member in Generators}
|
|
35
|
+
|
|
36
|
+
def _convert_parameter_to_ax_format(self, parameter_space):
|
|
37
|
+
"""
|
|
38
|
+
Converts the parameter space configuration to Baybe format.
|
|
39
|
+
:param parameter_space: The parameter space configuration.
|
|
40
|
+
[
|
|
41
|
+
{"name": "param_1", "type": "range", "bounds": [1.0, 2.0], "value_type": "float"},
|
|
42
|
+
{"name": "param_2", "type": "choice", "bounds": ["a", "b", "c"], "value_type": "str"},
|
|
43
|
+
{"name": "param_3", "type": "range", "bounds": [0 10], "value_type": "int"},
|
|
44
|
+
]
|
|
45
|
+
:return: A list of Baybe parameters.
|
|
46
|
+
"""
|
|
47
|
+
from ax import RangeParameterConfig, ChoiceParameterConfig
|
|
48
|
+
ax_params = []
|
|
49
|
+
for p in parameter_space:
|
|
50
|
+
if p["type"] == "range":
|
|
51
|
+
ax_params.append(
|
|
52
|
+
RangeParameterConfig(
|
|
53
|
+
name=p["name"],
|
|
54
|
+
bounds=tuple(p["bounds"]),
|
|
55
|
+
parameter_type=p["value_type"]
|
|
56
|
+
))
|
|
57
|
+
elif p["type"] == "choice":
|
|
58
|
+
ax_params.append(
|
|
59
|
+
ChoiceParameterConfig(
|
|
60
|
+
name=p["name"],
|
|
61
|
+
values=p["bounds"],
|
|
62
|
+
parameter_type=p["value_type"],
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
return ax_params
|
|
66
|
+
|
|
67
|
+
def _convert_objective_to_ax_format(self, objective_config: list):
|
|
68
|
+
"""
|
|
69
|
+
Converts the objective configuration to Baybe format.
|
|
70
|
+
:param parameter_space: The parameter space configuration.
|
|
71
|
+
[
|
|
72
|
+
{"name": "obj_1", "minimize": True, "weight": 1},
|
|
73
|
+
{"name": "obj_2", "minimize": False, "weight": 2}
|
|
74
|
+
]
|
|
75
|
+
:return: Ax objective configuration. "-cost, utility"
|
|
76
|
+
"""
|
|
77
|
+
objectives = []
|
|
78
|
+
for obj in objective_config:
|
|
79
|
+
obj_name = obj.get("name")
|
|
80
|
+
minimize = obj.get("minimize", True)
|
|
81
|
+
weight = obj.get("weight", 1)
|
|
82
|
+
sign = "-" if minimize else ""
|
|
83
|
+
objectives.append(f"{sign}{weight} * {obj_name}")
|
|
84
|
+
return ", ".join(objectives)
|
|
85
|
+
|
|
86
|
+
def _convert_generator_to_ax_format(self, optimizer_config):
|
|
87
|
+
"""
|
|
88
|
+
Converts the optimizer configuration to Ax format.
|
|
89
|
+
:param optimizer_config: The optimizer configuration.
|
|
90
|
+
:return: Ax generator configuration.
|
|
91
|
+
"""
|
|
92
|
+
from ax.generation_strategy.generation_node import GenerationStep
|
|
93
|
+
from ax.generation_strategy.generation_strategy import GenerationStrategy
|
|
94
|
+
generators = self._create_generator_mapping()
|
|
95
|
+
step_1 = optimizer_config.get("step_1", {})
|
|
96
|
+
step_2 = optimizer_config.get("step_2", {})
|
|
97
|
+
step_1_generator = step_1.get("model", "Sobol")
|
|
98
|
+
step_2_generator = step_2.get("model", "BOTorch")
|
|
99
|
+
generator_1 = GenerationStep(model=generators.get(step_1_generator), num_trials=step_1.get("num_samples", 5))
|
|
100
|
+
generator_2 = GenerationStep(model=generators.get(step_2_generator), num_trials=step_2.get("num_samples", -1))
|
|
101
|
+
return GenerationStrategy(steps=[generator_1, generator_2])
|
|
102
|
+
|
|
103
|
+
def suggest(self, n=1):
|
|
104
|
+
trial_index, params = self.client.get_next_trials(1).popitem()
|
|
105
|
+
self.trial_index = trial_index
|
|
106
|
+
return params
|
|
107
|
+
|
|
108
|
+
def observe(self, results):
|
|
109
|
+
self.client.complete_trial(
|
|
110
|
+
trial_index=self.trial_index,
|
|
111
|
+
raw_data=results
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def get_schema():
|
|
116
|
+
return {
|
|
117
|
+
"parameter_types": ["range", "choice"],
|
|
118
|
+
"multiple_objectives": True,
|
|
119
|
+
# "objective_weights": True,
|
|
120
|
+
"optimizer_config": {
|
|
121
|
+
"step_1": {"model": ["Sobol", "Uniform", "Factorial", "Thompson"], "num_samples": 5},
|
|
122
|
+
"step_2": {"model": ["BoTorch", "SAASBO", "SAAS_MTGP", "Legacy_GPEI", "EB", "EB_Ashr", "ST_MTGP", "BO_MIXED", "Contextual_SACBO"]}
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
def append_existing_data(self, existing_data):
|
|
127
|
+
"""
|
|
128
|
+
Append existing data to the Ax experiment.
|
|
129
|
+
:param existing_data: A dictionary containing existing data.
|
|
130
|
+
"""
|
|
131
|
+
from pandas import DataFrame
|
|
132
|
+
if not existing_data:
|
|
133
|
+
return
|
|
134
|
+
if isinstance(existing_data, DataFrame):
|
|
135
|
+
existing_data = existing_data.to_dict(orient="records")
|
|
136
|
+
parameter_names = [i.get("name") for i in self.parameter_space]
|
|
137
|
+
objective_names = [i.get("name") for i in self.objective_config]
|
|
138
|
+
for name, value in existing_data.items():
|
|
139
|
+
# First attach the trial and note the trial index
|
|
140
|
+
parameters = {name: value for name in existing_data if name in parameter_names}
|
|
141
|
+
trial_index = self.client.attach_trial(parameters=parameters)
|
|
142
|
+
raw_data = {name: value for name in existing_data if name in objective_names}
|
|
143
|
+
# Then complete the trial with the existing data
|
|
144
|
+
self.client.complete_trial(trial_index=trial_index, raw_data=raw_data)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
# Example usage
|
|
149
|
+
optimizer = AxOptimizer(
|
|
150
|
+
experiment_name="example_experiment",
|
|
151
|
+
parameter_space=[
|
|
152
|
+
{"name": "param_1", "type": "range", "bounds": [0.0, 1.0], "value_type": "float"},
|
|
153
|
+
{"name": "param_2", "type": "choice", "bounds": ["a", "b", "c"], "value_type": "str"}
|
|
154
|
+
],
|
|
155
|
+
objective_config=[
|
|
156
|
+
{"name": "objective_1", "minimize": True},
|
|
157
|
+
{"name": "objective_2", "minimize": False}
|
|
158
|
+
],
|
|
159
|
+
optimizer_config={
|
|
160
|
+
"step_1": {"model": "Sobol", "num_samples": 5},
|
|
161
|
+
"step_2": {"model": "BoTorch"}
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
print(optimizer._create_generator_mapping())
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
### ivoryos/optimizers/base.py
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OptimizerBase(ABC):
|
|
8
|
+
def __init__(self, experiment_name:str, parameter_space: list, objective_config: dict, optimizer_config: dict):
|
|
9
|
+
"""
|
|
10
|
+
:param experiment_name: arbitrary name
|
|
11
|
+
:param parameter_space: list of parameter names
|
|
12
|
+
[
|
|
13
|
+
{"name": "param_1", "type": "range", "bounds": [1.0, 2.0], "value_type": "float"},
|
|
14
|
+
{"name": "param_2", "type": "choice", "bounds": ["a", "b", "c"], "value_type": "str"},
|
|
15
|
+
{"name": "param_3", "type": "range", "bounds": [0 10], "value_type": "int"},
|
|
16
|
+
]
|
|
17
|
+
:param objective_config: objective configuration
|
|
18
|
+
[
|
|
19
|
+
{"name": "obj_1", "minimize": True, "weight": 1},
|
|
20
|
+
{"name": "obj_2", "minimize": False, "weight": 1}
|
|
21
|
+
]
|
|
22
|
+
:param optimizer_config: optimizer configuration
|
|
23
|
+
optimizer_config={
|
|
24
|
+
"step_1": {"model": "Random", "num_samples": 10},
|
|
25
|
+
"step_2": {"model": "BOTorch"}
|
|
26
|
+
}
|
|
27
|
+
"""
|
|
28
|
+
self.experiment_name = experiment_name
|
|
29
|
+
self.parameter_space = parameter_space
|
|
30
|
+
self.objective_config = objective_config
|
|
31
|
+
self.optimizer_config = optimizer_config
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def suggest(self, n=1):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def observe(self, results: dict):
|
|
39
|
+
"""
|
|
40
|
+
observe
|
|
41
|
+
:param results: {"objective_name": "value"}
|
|
42
|
+
"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def append_existing_data(self, existing_data):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def get_schema():
|
|
51
|
+
"""
|
|
52
|
+
Returns a template for the optimizer configuration.
|
|
53
|
+
"""
|
|
54
|
+
return {
|
|
55
|
+
"parameter_types": ["range", "choice"],
|
|
56
|
+
"multiple_objectives": True,
|
|
57
|
+
# "objective_weights": True,
|
|
58
|
+
"optimizer_config": {
|
|
59
|
+
"step_1": {"model": [], "num_samples": 10},
|
|
60
|
+
"step_2": {"model": []}
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
### Directory: ivoryos/optimizers/baybe_optimizer.py
|
|
2
|
+
from typing import Dict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from ivoryos.utils.utils import install_and_import
|
|
6
|
+
from ivoryos.optimizer.base_optimizer import OptimizerBase
|
|
7
|
+
|
|
8
|
+
class BaybeOptimizer(OptimizerBase):
|
|
9
|
+
def __init__(self, experiment_name, parameter_space, objective_config, optimizer_config):
|
|
10
|
+
try:
|
|
11
|
+
from baybe import Campaign
|
|
12
|
+
except ImportError:
|
|
13
|
+
install_and_import("baybe")
|
|
14
|
+
print("Please install Baybe with pip install baybe to before register BaybeOptimizer.")
|
|
15
|
+
|
|
16
|
+
super().__init__(experiment_name, parameter_space, objective_config, optimizer_config)
|
|
17
|
+
self._trial_id = 0
|
|
18
|
+
self._trials = {}
|
|
19
|
+
|
|
20
|
+
self.experiment = Campaign(
|
|
21
|
+
searchspace=self._convert_parameter_to_searchspace(parameter_space),
|
|
22
|
+
objective=self._convert_objective_to_baybe_format(objective_config),
|
|
23
|
+
recommender=self._convert_recommender_to_baybe_format(optimizer_config),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def suggest(self, n=1):
|
|
28
|
+
self.df = self.experiment.recommend(batch_size=n)
|
|
29
|
+
return self.df.to_dict(orient="records")[0]
|
|
30
|
+
|
|
31
|
+
def observe(self, results, index=None):
|
|
32
|
+
"""
|
|
33
|
+
Observes the results of a trial and updates the experiment.
|
|
34
|
+
:param results: A dictionary containing the results of the trial.
|
|
35
|
+
:param index: The index of the trial in the DataFrame, if applicable.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
for name, value in results.items():
|
|
39
|
+
self.df[name] = [value]
|
|
40
|
+
self.experiment.add_measurements(self.df)
|
|
41
|
+
|
|
42
|
+
def append_existing_data(self, existing_data: Dict):
|
|
43
|
+
"""
|
|
44
|
+
Append existing data to the Ax experiment.
|
|
45
|
+
:param existing_data: A dictionary containing existing data.
|
|
46
|
+
"""
|
|
47
|
+
import pandas as pd
|
|
48
|
+
if not existing_data:
|
|
49
|
+
return
|
|
50
|
+
# parameter_names = [i.get("name") for i in self.parameter_space]
|
|
51
|
+
# objective_names = [i.get("name") for i in self.objective_config]
|
|
52
|
+
self.experiment.add_measurements(pd.DataFrame(existing_data))
|
|
53
|
+
# for name, value in existing_data.items():
|
|
54
|
+
# # First attach the trial and note the trial index
|
|
55
|
+
# parameters = {name: value for name in existing_data if name in parameter_names}
|
|
56
|
+
# trial_index = self.client.attach_trial(parameters=parameters)
|
|
57
|
+
# raw_data = {name: value for name in existing_data if name in objective_names}
|
|
58
|
+
# # Then complete the trial with the existing data
|
|
59
|
+
# self.client.complete_trial(trial_index=trial_index, raw_data=raw_data)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _convert_parameter_to_searchspace(self, parameter_space):
|
|
63
|
+
"""
|
|
64
|
+
Converts the parameter space configuration to Baybe format.
|
|
65
|
+
:param parameter_space: The parameter space configuration.
|
|
66
|
+
[
|
|
67
|
+
{"name": "param_1", "type": "range", "bounds": [1.0, 2.0], "value_type": "float"},
|
|
68
|
+
{"name": "param_2", "type": "choice", "bounds": ["a", "b", "c"], "value_type": "str"},
|
|
69
|
+
{"name": "param_3", "type": "range", "bounds": [0 10], "value_type": "int"},
|
|
70
|
+
{"name": "param_4", "type": "substance", "bounds": ["methanol", "water", "toluene"], "value_type": "str"} #TODO
|
|
71
|
+
]
|
|
72
|
+
:return: A list of Baybe parameters.
|
|
73
|
+
"""
|
|
74
|
+
from baybe.parameters.categorical import CategoricalParameter
|
|
75
|
+
from baybe.parameters.numerical import NumericalContinuousParameter, NumericalDiscreteParameter
|
|
76
|
+
from baybe.searchspace import SearchSpace
|
|
77
|
+
parameters = []
|
|
78
|
+
for p in parameter_space:
|
|
79
|
+
if p["type"] == "range":
|
|
80
|
+
if p["value_type"] == "float":
|
|
81
|
+
parameters.append(NumericalContinuousParameter(name=p["name"], bounds=p["bounds"]))
|
|
82
|
+
elif p["value_type"] == "int":
|
|
83
|
+
values = tuple([int(v) for v in range(p["bounds"][0], p["bounds"][1] + 1)])
|
|
84
|
+
parameters.append(NumericalDiscreteParameter(name=p["name"], values=values))
|
|
85
|
+
|
|
86
|
+
elif p["type"] == "choice":
|
|
87
|
+
if p["value_type"] == "str":
|
|
88
|
+
parameters.append(CategoricalParameter(name=p["name"], values=p["bounds"]))
|
|
89
|
+
elif p["value_type"] in ["int", "float"]:
|
|
90
|
+
parameters.append(NumericalDiscreteParameter(name=p["name"], values=p["bounds"]))
|
|
91
|
+
return SearchSpace.from_product(parameters)
|
|
92
|
+
|
|
93
|
+
def _convert_objective_to_baybe_format(self, objective_config):
|
|
94
|
+
"""
|
|
95
|
+
Converts the objective configuration to Baybe format.
|
|
96
|
+
:param parameter_space: The parameter space configuration.
|
|
97
|
+
[
|
|
98
|
+
{"name": "obj_1", "minimize": True},
|
|
99
|
+
{"name": "obj_2", "minimize": False}
|
|
100
|
+
]
|
|
101
|
+
:return: A Baybe objective configuration.
|
|
102
|
+
"""
|
|
103
|
+
from baybe.targets import NumericalTarget
|
|
104
|
+
from baybe.objectives import SingleTargetObjective, DesirabilityObjective, ParetoObjective
|
|
105
|
+
targets = []
|
|
106
|
+
weights = []
|
|
107
|
+
for obj in objective_config:
|
|
108
|
+
obj_name = obj.get("name")
|
|
109
|
+
minimize = obj.get("minimize", False)
|
|
110
|
+
weight = obj.get("weight", 1)
|
|
111
|
+
weights.append(weight)
|
|
112
|
+
targets.append(NumericalTarget(name=obj_name, mode="MAX" if minimize else "MIN"))
|
|
113
|
+
|
|
114
|
+
if len(targets) == 1:
|
|
115
|
+
return SingleTargetObjective(target=targets[0])
|
|
116
|
+
else:
|
|
117
|
+
# Handle multiple objectives
|
|
118
|
+
return ParetoObjective(targets=targets)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _convert_recommender_to_baybe_format(self, recommender_config):
|
|
122
|
+
"""
|
|
123
|
+
Converts the recommender configuration to Baybe format.
|
|
124
|
+
:param recommender_config: The recommender configuration.
|
|
125
|
+
:return: A Baybe recommender configuration.
|
|
126
|
+
"""
|
|
127
|
+
from baybe.recommenders import (
|
|
128
|
+
BotorchRecommender,
|
|
129
|
+
FPSRecommender,
|
|
130
|
+
TwoPhaseMetaRecommender,
|
|
131
|
+
RandomRecommender,
|
|
132
|
+
NaiveHybridSpaceRecommender
|
|
133
|
+
)
|
|
134
|
+
step_1 = recommender_config.get("step_1", {})
|
|
135
|
+
step_2 = recommender_config.get("step_2", {})
|
|
136
|
+
step_1_recommender = step_1.get("model", "Random")
|
|
137
|
+
step_2_recommender = step_2.get("model", "BOTorch")
|
|
138
|
+
if step_1.get("model") == "Random":
|
|
139
|
+
step_1_recommender = RandomRecommender()
|
|
140
|
+
elif step_1.get("model") == "FPS":
|
|
141
|
+
step_1_recommender = FPSRecommender()
|
|
142
|
+
if step_2.get("model") == "Naive Hybrid Space":
|
|
143
|
+
step_2_recommender = NaiveHybridSpaceRecommender()
|
|
144
|
+
elif step_2.get("model") == "BOTorch":
|
|
145
|
+
step_2_recommender = BotorchRecommender()
|
|
146
|
+
return TwoPhaseMetaRecommender(
|
|
147
|
+
initial_recommender=step_1_recommender,
|
|
148
|
+
recommender=step_2_recommender
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def get_schema():
|
|
153
|
+
"""
|
|
154
|
+
Returns a template for the optimizer configuration.
|
|
155
|
+
"""
|
|
156
|
+
return {
|
|
157
|
+
"parameter_types": ["range", "choice", "substance"],
|
|
158
|
+
"multiple_objectives": True,
|
|
159
|
+
"optimizer_config": {
|
|
160
|
+
"step_1": {"model": ["Random", "FPS"], "num_samples": 10},
|
|
161
|
+
"step_2": {"model": ["BOTorch", "Naive Hybrid Space"]}
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
# Example usage
|
|
167
|
+
baybe_optimizer = BaybeOptimizer(
|
|
168
|
+
experiment_name="example_experiment",
|
|
169
|
+
parameter_space=[
|
|
170
|
+
{"name": "param_1", "type": "range", "bounds": [1.0, 2.0], "value_type": "float"},
|
|
171
|
+
{"name": "param_2", "type": "choice", "bounds": ["a", "b", "c"], "value_type": "str"},
|
|
172
|
+
{"name": "param_3", "type": "range", "bounds": [0, 10], "value_type": "int"}
|
|
173
|
+
],
|
|
174
|
+
objective_config=[
|
|
175
|
+
{"name": "obj_1", "minimize": True},
|
|
176
|
+
{"name": "obj_2", "minimize": False}
|
|
177
|
+
],
|
|
178
|
+
optimizer_config={
|
|
179
|
+
"step_1": {"model": "Random", "num_samples": 10},
|
|
180
|
+
"step_2": {"model": "BOTorch"}
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
print(baybe_optimizer.suggest(5))
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from flask import Blueprint, jsonify, request, current_app
|
|
3
|
+
|
|
4
|
+
from ivoryos.routes.control.control import find_instrument_by_name
|
|
5
|
+
from ivoryos.utils.form import create_form_from_module
|
|
6
|
+
from ivoryos.utils.global_config import GlobalConfig
|
|
7
|
+
from ivoryos.utils.db_models import Script, WorkflowRun, SingleStep, WorkflowStep
|
|
8
|
+
|
|
9
|
+
from ivoryos.socket_handlers import abort_pending, abort_current, pause, retry, runner
|
|
10
|
+
from ivoryos.utils.task_runner import TaskRunner
|
|
11
|
+
|
|
12
|
+
api = Blueprint('api', __name__)
|
|
13
|
+
global_config = GlobalConfig()
|
|
14
|
+
task_runner = TaskRunner()
|
|
15
|
+
|
|
16
|
+
#TODO: add authentication and authorization to the API endpoints
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@api.route("/control/", strict_slashes=False, methods=['GET'])
|
|
20
|
+
@api.route("/control/<string:instrument>", methods=['POST'])
|
|
21
|
+
def backend_control(instrument: str=None):
|
|
22
|
+
"""
|
|
23
|
+
.. :quickref: Backend Control; backend control
|
|
24
|
+
|
|
25
|
+
backend control through http requests
|
|
26
|
+
|
|
27
|
+
.. http:get:: /api/control/
|
|
28
|
+
|
|
29
|
+
:param instrument: instrument name
|
|
30
|
+
:type instrument: str
|
|
31
|
+
|
|
32
|
+
.. http:post:: /api/control/
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
if instrument:
|
|
36
|
+
inst_object = find_instrument_by_name(instrument)
|
|
37
|
+
forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
|
|
38
|
+
|
|
39
|
+
if request.method == 'POST':
|
|
40
|
+
method_name = request.form.get("hidden_name", None)
|
|
41
|
+
form = forms.get(method_name, None)
|
|
42
|
+
if form:
|
|
43
|
+
kwargs = {field.name: field.data for field in form if field.name not in ['csrf_token', 'hidden_name']}
|
|
44
|
+
wait = request.form.get("hidden_wait", "true") == "true"
|
|
45
|
+
output = task_runner.run_single_step(component=instrument, method=method_name, kwargs=kwargs, wait=wait,
|
|
46
|
+
current_app=current_app._get_current_object())
|
|
47
|
+
return jsonify(output), 200
|
|
48
|
+
|
|
49
|
+
snapshot = global_config.deck_snapshot.copy()
|
|
50
|
+
# Iterate through each instrument in the snapshot
|
|
51
|
+
for instrument_key, instrument_data in snapshot.items():
|
|
52
|
+
# Iterate through each function associated with the current instrument
|
|
53
|
+
for function_key, function_data in instrument_data.items():
|
|
54
|
+
# Convert the function signature to a string representation
|
|
55
|
+
function_data['signature'] = str(function_data['signature'])
|
|
56
|
+
return jsonify(snapshot), 200
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}IvoryOS | Login{% endblock %}
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
{% block body %}
|
|
6
|
+
<div class= "login">
|
|
7
|
+
<div class="bg-white rounded shadow-sm flex-fill">
|
|
8
|
+
<div class="p-4" style="align-items: center">
|
|
9
|
+
<h5>Log in</h5>
|
|
10
|
+
<form role="form" method='POST' name="login" action="{{ url_for('auth.login') }}">
|
|
11
|
+
<div class="input-group mb-3">
|
|
12
|
+
<label class="input-group-text" for="username">Username</label>
|
|
13
|
+
<input class="form-control" type="text" id="username" name="username">
|
|
14
|
+
</div>
|
|
15
|
+
<div class="input-group mb-3">
|
|
16
|
+
<label class="input-group-text" for="password">Password</label>
|
|
17
|
+
<input class="form-control" type="password" id="password" name="password">
|
|
18
|
+
</div>
|
|
19
|
+
<button type="submit" class="btn btn-secondary" name="login" style="width: 100%;">login</button>
|
|
20
|
+
</form>
|
|
21
|
+
<p class="message">Not registered? <a href="{{ url_for('auth.signup') }}">Create a new account</a>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
{% endblock %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}IvoryOS | Signup{% endblock %}
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
{% block body %}
|
|
6
|
+
<div class= "login">
|
|
7
|
+
<div class="bg-white rounded shadow-sm flex-fill">
|
|
8
|
+
<div class="p-4" style="align: center">
|
|
9
|
+
<h5>Create a new account</h5>
|
|
10
|
+
<form role="form" method='POST' name="signup" action="{{ url_for('auth.signup') }}">
|
|
11
|
+
|
|
12
|
+
<div class="input-group mb-3">
|
|
13
|
+
<label class="input-group-text" for="username">Username</label>
|
|
14
|
+
<input class="form-control" type="text" id="username" name="username" required>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="input-group mb-3">
|
|
17
|
+
<label class="input-group-text" for="password">Password</label>
|
|
18
|
+
<input class="form-control" type="password" id="password" name="password" required>
|
|
19
|
+
</div>
|
|
20
|
+
{# <div class="input-group mb-3">#}
|
|
21
|
+
{# <label class="input-group-text" for="confirm_password">Confirm Password</label>#}
|
|
22
|
+
{# <input class="form-control" type="confirm_password" id="confirm_password" name="confirm_password">#}
|
|
23
|
+
{# </div>#}
|
|
24
|
+
|
|
25
|
+
<button type="submit" class="btn btn-secondary" name="login" style="width: 100%;">Sign up</button>
|
|
26
|
+
</form>
|
|
27
|
+
<p class="message" >Already registered? <a href="{{ url_for('auth.login') }}">Sign In</a></p>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
{% endblock %}
|