ivoryos 1.0.9__py3-none-any.whl → 1.4.4__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.
- docs/source/conf.py +84 -0
- ivoryos/__init__.py +17 -207
- ivoryos/app.py +154 -0
- ivoryos/config.py +1 -0
- ivoryos/optimizer/ax_optimizer.py +191 -0
- ivoryos/optimizer/base_optimizer.py +84 -0
- ivoryos/optimizer/baybe_optimizer.py +193 -0
- ivoryos/optimizer/nimo_optimizer.py +173 -0
- ivoryos/optimizer/registry.py +11 -0
- ivoryos/routes/auth/auth.py +43 -14
- ivoryos/routes/auth/templates/change_password.html +32 -0
- ivoryos/routes/control/control.py +101 -366
- ivoryos/routes/control/control_file.py +33 -0
- ivoryos/routes/control/control_new_device.py +152 -0
- ivoryos/routes/control/templates/controllers.html +193 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +40 -0
- ivoryos/routes/data/data.py +197 -0
- ivoryos/routes/data/templates/components/step_card.html +78 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
- ivoryos/routes/data/templates/workflow_view.html +360 -0
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +348 -657
- ivoryos/routes/design/design_file.py +68 -0
- ivoryos/routes/design/design_step.py +171 -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 +39 -0
- ivoryos/routes/design/templates/components/deck_selector.html +10 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
- ivoryos/routes/design/templates/components/info_modal.html +318 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +88 -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 +56 -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 +44 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +377 -0
- ivoryos/routes/execute/execute_file.py +78 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +56 -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 +60 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
- ivoryos/routes/execute/templates/experiment_run.html +30 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/library/library.py +157 -0
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
- ivoryos/routes/main/main.py +31 -3
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/server.py +180 -0
- ivoryos/socket_handlers.py +52 -0
- ivoryos/static/ivoryos_logo.png +0 -0
- ivoryos/static/js/action_handlers.js +384 -0
- ivoryos/static/js/db_delete.js +23 -0
- ivoryos/static/js/script_metadata.js +39 -0
- ivoryos/static/js/socket_handler.js +40 -5
- ivoryos/static/js/sortable_design.js +107 -56
- ivoryos/static/js/ui_state.js +114 -0
- ivoryos/templates/base.html +67 -8
- ivoryos/utils/bo_campaign.py +180 -3
- ivoryos/utils/client_proxy.py +267 -36
- ivoryos/utils/db_models.py +300 -65
- ivoryos/utils/decorators.py +34 -0
- ivoryos/utils/form.py +63 -29
- ivoryos/utils/global_config.py +34 -1
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/py_to_json.py +295 -0
- ivoryos/utils/script_runner.py +599 -165
- ivoryos/utils/serilize.py +201 -0
- ivoryos/utils/task_runner.py +71 -21
- ivoryos/utils/utils.py +50 -6
- ivoryos/version.py +1 -1
- ivoryos-1.4.4.dist-info/METADATA +263 -0
- ivoryos-1.4.4.dist-info/RECORD +119 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
- tests/unit/test_type_conversion.py +42 -0
- tests/unit/test_util.py +3 -0
- ivoryos/routes/control/templates/control/controllers.html +0 -78
- ivoryos/routes/control/templates/control/controllers_home.html +0 -55
- ivoryos/routes/control/templates/control/controllers_new.html +0 -89
- ivoryos/routes/database/database.py +0 -306
- ivoryos/routes/database/templates/database/step_card.html +0 -7
- ivoryos/routes/database/templates/database/workflow_view.html +0 -130
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.9.dist-info/METADATA +0 -218
- ivoryos-1.0.9.dist-info/RECORD +0 -61
- /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
- /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
- /ivoryos/routes/{database → data}/__init__.py +0 -0
- /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
parameter_constraints:list=None, datapath:str=None):
|
|
10
|
+
"""
|
|
11
|
+
:param experiment_name: arbitrary name
|
|
12
|
+
:param parameter_space: list of parameter names
|
|
13
|
+
[
|
|
14
|
+
{"name": "param_1", "type": "range", "bounds": [1.0, 2.0], "value_type": "float"},
|
|
15
|
+
{"name": "param_2", "type": "choice", "bounds": ["a", "b", "c"], "value_type": "str"},
|
|
16
|
+
{"name": "param_3", "type": "range", "bounds": [0 10], "value_type": "int"},
|
|
17
|
+
]
|
|
18
|
+
:param objective_config: objective configuration
|
|
19
|
+
[
|
|
20
|
+
{"name": "obj_1", "minimize": True, "weight": 1},
|
|
21
|
+
{"name": "obj_2", "minimize": False, "weight": 1}
|
|
22
|
+
]
|
|
23
|
+
:param optimizer_config: optimizer configuration
|
|
24
|
+
optimizer_config={
|
|
25
|
+
"step_1": {"model": "Random", "num_samples": 10},
|
|
26
|
+
"step_2": {"model": "BOTorch"}
|
|
27
|
+
}
|
|
28
|
+
"""
|
|
29
|
+
self.experiment_name = experiment_name
|
|
30
|
+
self.parameter_space = parameter_space
|
|
31
|
+
self.objective_config = objective_config
|
|
32
|
+
self.optimizer_config = optimizer_config
|
|
33
|
+
self.datapath = datapath
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def suggest(self, n=1):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def observe(self, results: dict):
|
|
41
|
+
"""
|
|
42
|
+
observe
|
|
43
|
+
:param results: {"objective_name": "value"}
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def append_existing_data(self, existing_data):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def get_plots(self, plot_type):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _create_discrete_search_space(range_with_step=None, value_type ="float"):
|
|
57
|
+
if range_with_step is None:
|
|
58
|
+
range_with_step = []
|
|
59
|
+
import numpy as np
|
|
60
|
+
low, high, step = range_with_step
|
|
61
|
+
values = np.arange(low, high + 1e-9 * step, step).tolist()
|
|
62
|
+
if value_type == "float":
|
|
63
|
+
values = [float(v) for v in values]
|
|
64
|
+
if value_type == "int":
|
|
65
|
+
values = [int(v) for v in values]
|
|
66
|
+
return values
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def get_schema():
|
|
70
|
+
"""
|
|
71
|
+
Returns a template for the optimizer configuration.
|
|
72
|
+
"""
|
|
73
|
+
return {
|
|
74
|
+
"parameter_types": ["range", "choice"],
|
|
75
|
+
"multiple_objectives": True,
|
|
76
|
+
# "objective_weights": True,
|
|
77
|
+
"optimizer_config": {
|
|
78
|
+
"step_1": {"model": [], "num_samples": 10},
|
|
79
|
+
"step_2": {"model": []}
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
### Directory: ivoryos/optimizers/baybe_optimizer.py
|
|
2
|
+
from typing import Dict
|
|
3
|
+
|
|
4
|
+
from pandas import DataFrame
|
|
5
|
+
|
|
6
|
+
from ivoryos.utils.utils import install_and_import
|
|
7
|
+
from ivoryos.optimizer.base_optimizer import OptimizerBase
|
|
8
|
+
|
|
9
|
+
class BaybeOptimizer(OptimizerBase):
|
|
10
|
+
def __init__(self, experiment_name, parameter_space, objective_config, optimizer_config,
|
|
11
|
+
parameter_constraints:list=None, datapath=None):
|
|
12
|
+
try:
|
|
13
|
+
from baybe import Campaign
|
|
14
|
+
except ImportError:
|
|
15
|
+
install_and_import("baybe")
|
|
16
|
+
print("Please install Baybe with pip install baybe to before register BaybeOptimizer.")
|
|
17
|
+
|
|
18
|
+
super().__init__(experiment_name, parameter_space, objective_config, optimizer_config, parameter_constraints, )
|
|
19
|
+
self._trial_id = 0
|
|
20
|
+
self._trials = {}
|
|
21
|
+
|
|
22
|
+
self.experiment = Campaign(
|
|
23
|
+
searchspace=self._convert_parameter_to_searchspace(parameter_space),
|
|
24
|
+
objective=self._convert_objective_to_baybe_format(objective_config),
|
|
25
|
+
recommender=self._convert_recommender_to_baybe_format(optimizer_config),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def suggest(self, n=1):
|
|
30
|
+
# self.df = self.experiment.recommend(batch_size=n)
|
|
31
|
+
return self.experiment.recommend(batch_size=n).to_dict(orient="records")
|
|
32
|
+
|
|
33
|
+
def observe(self, results, index=None):
|
|
34
|
+
"""
|
|
35
|
+
Observes the results of a trial and updates the experiment.
|
|
36
|
+
:param results: A dictionary containing the results of the trial.
|
|
37
|
+
:param index: The index of the trial in the DataFrame, if applicable.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
df = DataFrame(results)
|
|
41
|
+
self.experiment.add_measurements(df)
|
|
42
|
+
|
|
43
|
+
def append_existing_data(self, existing_data: DataFrame):
|
|
44
|
+
"""
|
|
45
|
+
Append existing data to the Ax experiment.
|
|
46
|
+
:param existing_data: A dictionary containing existing data.
|
|
47
|
+
"""
|
|
48
|
+
if existing_data.empty:
|
|
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(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": "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
|
|
73
|
+
]
|
|
74
|
+
:return: A list of Baybe parameters.
|
|
75
|
+
"""
|
|
76
|
+
from baybe.parameters.categorical import CategoricalParameter
|
|
77
|
+
from baybe.parameters.numerical import NumericalContinuousParameter, NumericalDiscreteParameter
|
|
78
|
+
from baybe.searchspace import SearchSpace
|
|
79
|
+
parameters = []
|
|
80
|
+
for p in parameter_space:
|
|
81
|
+
if p["type"] == "range":
|
|
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":
|
|
86
|
+
parameters.append(NumericalContinuousParameter(name=p["name"], bounds=p["bounds"]))
|
|
87
|
+
elif p["value_type"] == "int":
|
|
88
|
+
values = tuple([int(v) for v in range(p["bounds"][0], p["bounds"][1] + 1)])
|
|
89
|
+
parameters.append(NumericalDiscreteParameter(name=p["name"], values=values))
|
|
90
|
+
|
|
91
|
+
elif p["type"] == "choice":
|
|
92
|
+
if p["value_type"] == "str":
|
|
93
|
+
parameters.append(CategoricalParameter(name=p["name"], values=p["bounds"]))
|
|
94
|
+
elif p["value_type"] in ["int", "float"]:
|
|
95
|
+
parameters.append(NumericalDiscreteParameter(name=p["name"], values=p["bounds"]))
|
|
96
|
+
return SearchSpace.from_product(parameters)
|
|
97
|
+
|
|
98
|
+
def _convert_objective_to_baybe_format(self, objective_config):
|
|
99
|
+
"""
|
|
100
|
+
Converts the objective configuration to Baybe format.
|
|
101
|
+
:param parameter_space: The parameter space configuration.
|
|
102
|
+
[
|
|
103
|
+
{"name": "obj_1", "minimize": True},
|
|
104
|
+
{"name": "obj_2", "minimize": False}
|
|
105
|
+
]
|
|
106
|
+
:return: A Baybe objective configuration.
|
|
107
|
+
"""
|
|
108
|
+
from baybe.targets import NumericalTarget
|
|
109
|
+
from baybe.objectives import SingleTargetObjective, DesirabilityObjective, ParetoObjective
|
|
110
|
+
targets = []
|
|
111
|
+
weights = []
|
|
112
|
+
for obj in objective_config:
|
|
113
|
+
obj_name = obj.get("name")
|
|
114
|
+
minimize = obj.get("minimize", True)
|
|
115
|
+
weight = obj.get("weight", 1)
|
|
116
|
+
weights.append(weight)
|
|
117
|
+
targets.append(NumericalTarget(name=obj_name, minimize=minimize))
|
|
118
|
+
|
|
119
|
+
if len(targets) == 1:
|
|
120
|
+
return SingleTargetObjective(target=targets[0])
|
|
121
|
+
else:
|
|
122
|
+
# Handle multiple objectives
|
|
123
|
+
return ParetoObjective(targets=targets)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _convert_recommender_to_baybe_format(self, recommender_config):
|
|
127
|
+
"""
|
|
128
|
+
Converts the recommender configuration to Baybe format.
|
|
129
|
+
:param recommender_config: The recommender configuration.
|
|
130
|
+
:return: A Baybe recommender configuration.
|
|
131
|
+
"""
|
|
132
|
+
from baybe.recommenders import (
|
|
133
|
+
BotorchRecommender,
|
|
134
|
+
FPSRecommender,
|
|
135
|
+
TwoPhaseMetaRecommender,
|
|
136
|
+
RandomRecommender,
|
|
137
|
+
NaiveHybridSpaceRecommender
|
|
138
|
+
)
|
|
139
|
+
step_1 = recommender_config.get("step_1", {})
|
|
140
|
+
step_2 = recommender_config.get("step_2", {})
|
|
141
|
+
step_1_recommender = step_1.get("model", "Random")
|
|
142
|
+
step_2_recommender = step_2.get("model", "BOTorch")
|
|
143
|
+
if step_1.get("model") == "Random":
|
|
144
|
+
step_1_recommender = RandomRecommender()
|
|
145
|
+
elif step_1.get("model") == "FPS":
|
|
146
|
+
step_1_recommender = FPSRecommender()
|
|
147
|
+
if step_2.get("model") == "Naive Hybrid Space":
|
|
148
|
+
step_2_recommender = NaiveHybridSpaceRecommender()
|
|
149
|
+
elif step_2.get("model") == "BOTorch":
|
|
150
|
+
step_2_recommender = BotorchRecommender()
|
|
151
|
+
return TwoPhaseMetaRecommender(
|
|
152
|
+
initial_recommender=step_1_recommender,
|
|
153
|
+
recommender=step_2_recommender
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def get_plots(self, plot_type):
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def get_schema():
|
|
161
|
+
"""
|
|
162
|
+
Returns a template for the optimizer configuration.
|
|
163
|
+
"""
|
|
164
|
+
return {
|
|
165
|
+
"parameter_types": ["range", "choice", "substance"],
|
|
166
|
+
"multiple_objectives": True,
|
|
167
|
+
"supports_continuous": True,
|
|
168
|
+
"supports_constraints": False,
|
|
169
|
+
"optimizer_config": {
|
|
170
|
+
"step_1": {"model": ["Random", "FPS"], "num_samples": 10},
|
|
171
|
+
"step_2": {"model": ["BOTorch", "Naive Hybrid Space"]}
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
# Example usage
|
|
177
|
+
baybe_optimizer = BaybeOptimizer(
|
|
178
|
+
experiment_name="example_experiment",
|
|
179
|
+
parameter_space=[
|
|
180
|
+
{"name": "param_1", "type": "range", "bounds": [1.0, 2.0], "value_type": "float"},
|
|
181
|
+
{"name": "param_2", "type": "choice", "bounds": ["a", "b", "c"], "value_type": "str"},
|
|
182
|
+
{"name": "param_3", "type": "range", "bounds": [0, 10], "value_type": "int"}
|
|
183
|
+
],
|
|
184
|
+
objective_config=[
|
|
185
|
+
{"name": "obj_1", "minimize": True},
|
|
186
|
+
{"name": "obj_2", "minimize": False}
|
|
187
|
+
],
|
|
188
|
+
optimizer_config={
|
|
189
|
+
"step_1": {"model": "Random", "num_samples": 10},
|
|
190
|
+
"step_2": {"model": "BOTorch"}
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
print(baybe_optimizer.suggest(5))
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
### ivoryos/optimizers/nimo_optimizer.py
|
|
2
|
+
import glob
|
|
3
|
+
import itertools
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from ivoryos.optimizer.base_optimizer import OptimizerBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NIMOOptimizer(OptimizerBase):
|
|
11
|
+
def __init__(self, experiment_name:str, parameter_space: list, objective_config: list, optimizer_config: dict,
|
|
12
|
+
parameter_constraints:list=None, datapath:str=None):
|
|
13
|
+
"""
|
|
14
|
+
:param experiment_name: arbitrary name
|
|
15
|
+
:param parameter_space: list of parameter names
|
|
16
|
+
[
|
|
17
|
+
{"name": "param_1", "type": "range", "bounds": [1.0, 2.0], "value_type": "float"},
|
|
18
|
+
{"name": "param_2", "type": "choice", "bounds": ["a", "b", "c"], "value_type": "str"},
|
|
19
|
+
{"name": "param_3", "type": "range", "bounds": [0 10], "value_type": "int"},
|
|
20
|
+
]
|
|
21
|
+
:param objective_config: objective configuration
|
|
22
|
+
[
|
|
23
|
+
{"name": "obj_1", "minimize": True, "weight": 1},
|
|
24
|
+
{"name": "obj_2", "minimize": False, "weight": 1}
|
|
25
|
+
]
|
|
26
|
+
:param optimizer_config: optimizer configuration
|
|
27
|
+
{
|
|
28
|
+
"step_1": {"model": "RE", "num_samples": 10},
|
|
29
|
+
"step_2": {"model": "PDC"}
|
|
30
|
+
}
|
|
31
|
+
"""
|
|
32
|
+
self.current_step = 0
|
|
33
|
+
self.experiment_name = experiment_name
|
|
34
|
+
self.parameter_space = parameter_space
|
|
35
|
+
self.objective_config = objective_config
|
|
36
|
+
self.optimizer_config = optimizer_config
|
|
37
|
+
|
|
38
|
+
super().__init__(experiment_name, parameter_space, objective_config, optimizer_config, parameter_constraints, datapath)
|
|
39
|
+
|
|
40
|
+
os.makedirs(os.path.join(self.datapath, "nimo_data"), exist_ok=True)
|
|
41
|
+
|
|
42
|
+
step_1 = optimizer_config.get("step_1", {})
|
|
43
|
+
step_2 = optimizer_config.get("step_2", {})
|
|
44
|
+
self.step_1_generator = step_1.get("model", "RE")
|
|
45
|
+
self.step_1_batch_num = step_1.get("num_samples", 1)
|
|
46
|
+
self.step_2_generator = step_2.get("model", "PDC")
|
|
47
|
+
self.candidates = os.path.join(self.datapath, "nimo_data", f"{self.experiment_name}_candidates.csv")
|
|
48
|
+
self.proposals = os.path.join(self.datapath, "nimo_data", f"{self.experiment_name}_proposals.csv")
|
|
49
|
+
self.n_objectives = len(self.objective_config)
|
|
50
|
+
self._create_candidates_csv()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _create_candidates_csv(self):
|
|
54
|
+
# Extract parameter names and their possible values
|
|
55
|
+
import pandas as pd
|
|
56
|
+
import nimo
|
|
57
|
+
import numpy as np
|
|
58
|
+
if os.path.exists(self.candidates) and nimo.history(self.candidates, self.n_objectives):
|
|
59
|
+
return
|
|
60
|
+
param_names = [p["name"] for p in self.parameter_space]
|
|
61
|
+
|
|
62
|
+
param_values = []
|
|
63
|
+
for p in self.parameter_space:
|
|
64
|
+
if p["type"] == "choice" and isinstance(p["bounds"], list):
|
|
65
|
+
param_values.append(p["bounds"])
|
|
66
|
+
elif p["type"] == "range" and len(p["bounds"]) == 3:
|
|
67
|
+
values = self._create_discrete_search_space(range_with_step=p["bounds"],value_type=p["value_type"])
|
|
68
|
+
param_values.append(values)
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError(f"Unsupported parameter format: {p}")
|
|
71
|
+
|
|
72
|
+
# Generate all possible combinations
|
|
73
|
+
combos = list(itertools.product(*param_values))
|
|
74
|
+
|
|
75
|
+
# Create a DataFrame with parameter columns
|
|
76
|
+
df = pd.DataFrame(combos, columns=param_names)
|
|
77
|
+
# Add empty objective columns
|
|
78
|
+
for obj in self.objective_config:
|
|
79
|
+
df[obj["name"]] = ""
|
|
80
|
+
|
|
81
|
+
# Save to CSV
|
|
82
|
+
df.to_csv(self.candidates, index=False)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def suggest(self, n=1):
|
|
86
|
+
import pandas as pd
|
|
87
|
+
import nimo
|
|
88
|
+
method = self.step_1_generator if self.current_step <= self.step_1_batch_num else self.step_2_generator
|
|
89
|
+
nimo.selection(method = method,
|
|
90
|
+
input_file = self.candidates,
|
|
91
|
+
output_file = self.proposals,
|
|
92
|
+
num_objectives = self.n_objectives,
|
|
93
|
+
num_proposals = n)
|
|
94
|
+
self.current_step += 1
|
|
95
|
+
# Read proposals from CSV file
|
|
96
|
+
proposals_df = pd.read_csv(self.proposals)
|
|
97
|
+
# Get parameter names
|
|
98
|
+
param_names = [p["name"] for p in self.parameter_space]
|
|
99
|
+
# Convert proposals to list of parameter dictionaries
|
|
100
|
+
proposals = []
|
|
101
|
+
for _, row in proposals_df.iterrows():
|
|
102
|
+
proposal = {name: row[name] for name in param_names}
|
|
103
|
+
proposals.append(proposal)
|
|
104
|
+
return proposals
|
|
105
|
+
|
|
106
|
+
def _convert_observation_to_list(self, obs: dict) -> list:
|
|
107
|
+
obj_names = [o["name"] for o in self.objective_config]
|
|
108
|
+
return [obs.get(name, None) for name in obj_names]
|
|
109
|
+
|
|
110
|
+
def observe(self, results: list):
|
|
111
|
+
"""
|
|
112
|
+
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"}, {"objective_name": "value"}]]
|
|
114
|
+
"""
|
|
115
|
+
import nimo
|
|
116
|
+
nimo_objective_values = [self._convert_observation_to_list(result) for result in results]
|
|
117
|
+
nimo.output_update(input_file=self.proposals,
|
|
118
|
+
output_file=self.candidates,
|
|
119
|
+
num_objectives=self.n_objectives,
|
|
120
|
+
objective_values=nimo_objective_values)
|
|
121
|
+
|
|
122
|
+
def append_existing_data(self, existing_data):
|
|
123
|
+
# TODO, history is part of the candidate file, we probably won't need this
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
def get_plots(self, plot_type):
|
|
127
|
+
import nimo
|
|
128
|
+
nimo.visualization.plot_phase_diagram.plot(input_file=self.candidates,
|
|
129
|
+
fig_folder=os.path.join(self.datapath, "nimo_data"))
|
|
130
|
+
files = sorted(glob.glob(os.path.join(os.path.join(self.datapath, "nimo_data"), "phase_diagram_*.png")))
|
|
131
|
+
if not files:
|
|
132
|
+
return None
|
|
133
|
+
return files[-1]
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def get_schema():
|
|
137
|
+
return {
|
|
138
|
+
"parameter_types": ["choice", "range"],
|
|
139
|
+
"multiple_objectives": True,
|
|
140
|
+
"supports_continuous": False,
|
|
141
|
+
"supports_constraints": False,
|
|
142
|
+
"optimizer_config": {
|
|
143
|
+
"step_1": {"model": ["RE", "ES"], "num_samples": 5},
|
|
144
|
+
"step_2": {"model": ["PHYSBO", "PDC", "BLOX", "PTR", "SLESA", "BOMP", "COMBI"]}
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
parameter_space = [
|
|
153
|
+
{"name": "silica", "type": "choice", "bounds": [100], "value_type": "float"},
|
|
154
|
+
{"name": "water", "type": "range", "bounds": [500, 900, 50], "value_type": "float"},
|
|
155
|
+
{"name": "PVA", "type": "choice", "bounds": [0, 0.005, 0.0075, 0.01, 0.05, 0.075, 0.1], "value_type": "float"},
|
|
156
|
+
{"name": "SDS", "type": "choice", "bounds": [0], "value_type": "float"},
|
|
157
|
+
{"name": "DTAB", "type": "choice", "bounds": [0, 0.005, 0.0075, 0.01, 0.05, 0.075, 0.1], "value_type": "float"},
|
|
158
|
+
{"name": "PVP", "type": "choice", "bounds": [0], "value_type": "float"},
|
|
159
|
+
]
|
|
160
|
+
objective_config = [
|
|
161
|
+
{"name": "objective", "minimize": False, "weight": 1},
|
|
162
|
+
|
|
163
|
+
]
|
|
164
|
+
optimizer_config = {
|
|
165
|
+
"step_1": {"model": "RE", "num_samples": 10},
|
|
166
|
+
"step_2": {"model": "PDC"}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
nimo_optimizer = NIMOOptimizer(experiment_name="example_experiment", optimizer_config=optimizer_config, parameter_space=parameter_space, objective_config=objective_config)
|
|
170
|
+
nimo_optimizer.suggest(n=1)
|
|
171
|
+
nimo_optimizer.observe(
|
|
172
|
+
results=[{"objective": 1.0}]
|
|
173
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# optimizers/registry.py
|
|
2
|
+
|
|
3
|
+
from ivoryos.optimizer.ax_optimizer import AxOptimizer
|
|
4
|
+
from ivoryos.optimizer.baybe_optimizer import BaybeOptimizer
|
|
5
|
+
from ivoryos.optimizer.nimo_optimizer import NIMOOptimizer
|
|
6
|
+
|
|
7
|
+
OPTIMIZER_REGISTRY = {
|
|
8
|
+
"ax": AxOptimizer,
|
|
9
|
+
"baybe": BaybeOptimizer,
|
|
10
|
+
"nimo": NIMOOptimizer,
|
|
11
|
+
}
|
ivoryos/routes/auth/auth.py
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
from flask import Blueprint, redirect, url_for, flash, request, render_template, session
|
|
2
|
-
from flask_login import login_required, login_user, logout_user, LoginManager
|
|
2
|
+
from flask_login import login_required, login_user, logout_user, LoginManager, current_user
|
|
3
3
|
import bcrypt
|
|
4
|
+
from sqlalchemy_utils.types import password
|
|
4
5
|
|
|
5
6
|
from ivoryos.utils.db_models import Script, User, db
|
|
6
7
|
from ivoryos.utils.utils import post_script_file
|
|
7
8
|
login_manager = LoginManager()
|
|
9
|
+
# from flask import g
|
|
8
10
|
|
|
9
|
-
auth = Blueprint('auth', __name__, template_folder='templates
|
|
11
|
+
auth = Blueprint('auth', __name__, template_folder='templates')
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
@auth.route('/
|
|
14
|
+
@auth.route('/login', methods=['GET', 'POST'])
|
|
13
15
|
def login():
|
|
14
16
|
"""
|
|
15
17
|
.. :quickref: User; login user
|
|
16
18
|
|
|
17
|
-
.. http:get:: /login
|
|
19
|
+
.. http:get:: /auth/login
|
|
18
20
|
|
|
19
21
|
load user login form.
|
|
20
22
|
|
|
21
|
-
.. http:post:: /login
|
|
23
|
+
.. http:post:: /auth/login
|
|
22
24
|
|
|
23
25
|
:form username: username
|
|
24
26
|
:form password: password
|
|
25
27
|
:status 302: and then redirects to homepage
|
|
26
|
-
:status 401: incorrect password, redirects to :http:get:`/ivoryos/login`
|
|
28
|
+
:status 401: incorrect password, redirects to :http:get:`/ivoryos/auth/login`
|
|
27
29
|
"""
|
|
28
30
|
if request.method == 'POST':
|
|
29
31
|
username = request.form.get('username')
|
|
@@ -37,7 +39,8 @@ def login():
|
|
|
37
39
|
# password.encode("utf-8")
|
|
38
40
|
# user = User(username, password.encode("utf-8"))
|
|
39
41
|
login_user(user)
|
|
40
|
-
|
|
42
|
+
# g.user = user
|
|
43
|
+
# session['user'] = username
|
|
41
44
|
script_file = Script(author=username)
|
|
42
45
|
session["script"] = script_file.as_dict()
|
|
43
46
|
session['hidden_functions'], session['card_order'], session['prompt'] = {}, {}, {}
|
|
@@ -46,25 +49,25 @@ def login():
|
|
|
46
49
|
return redirect(url_for('main.index'))
|
|
47
50
|
else:
|
|
48
51
|
flash("Incorrect username or password")
|
|
49
|
-
return redirect(url_for('auth.login'))
|
|
52
|
+
return redirect(url_for('auth.login'))
|
|
50
53
|
return render_template('login.html')
|
|
51
54
|
|
|
52
55
|
|
|
53
|
-
@auth.route('/
|
|
56
|
+
@auth.route('/signup', methods=['GET', 'POST'])
|
|
54
57
|
def signup():
|
|
55
58
|
"""
|
|
56
59
|
.. :quickref: User; signup for a new account
|
|
57
60
|
|
|
58
|
-
.. http:get:: /signup
|
|
61
|
+
.. http:get:: /auth/signup
|
|
59
62
|
|
|
60
63
|
load user sighup
|
|
61
64
|
|
|
62
|
-
.. http:post:: /signup
|
|
65
|
+
.. http:post:: /auth/signup
|
|
63
66
|
|
|
64
67
|
:form username: username
|
|
65
68
|
:form password: password
|
|
66
|
-
:status 302: and then redirects to :http:get:`/ivoryos/login`
|
|
67
|
-
:status 409: when user already exists, redirects to :http:get:`/ivoryos/signup`
|
|
69
|
+
:status 302: and then redirects to :http:get:`/ivoryos/auth/login`
|
|
70
|
+
:status 409: when user already exists, redirects to :http:get:`/ivoryos/auth/signup`
|
|
68
71
|
"""
|
|
69
72
|
if request.method == 'POST':
|
|
70
73
|
username = request.form.get('username')
|
|
@@ -83,13 +86,39 @@ def signup():
|
|
|
83
86
|
return redirect(url_for('auth.login'))
|
|
84
87
|
return render_template('signup.html')
|
|
85
88
|
|
|
89
|
+
@auth.route("/change-password", methods=['GET', 'POST'])
|
|
90
|
+
@login_required
|
|
91
|
+
def change_password():
|
|
92
|
+
"""
|
|
93
|
+
.. :quickref: User; change password
|
|
94
|
+
|
|
95
|
+
.. http:get:: /auth/change-password
|
|
96
|
+
|
|
97
|
+
.. http:post:: /auth/change-password
|
|
86
98
|
|
|
87
|
-
|
|
99
|
+
change password
|
|
100
|
+
"""
|
|
101
|
+
if request.method == "POST":
|
|
102
|
+
old_password = request.form.get("old_password")
|
|
103
|
+
new_password = request.form.get("new_password")
|
|
104
|
+
# confirm_password = request.form.get("confirm_password")
|
|
105
|
+
user = User.query.filter_by(username=current_user.get_id()).first()
|
|
106
|
+
if not bcrypt.checkpw(old_password.encode('utf-8'), user.hashPassword):
|
|
107
|
+
flash("Incorrect password")
|
|
108
|
+
return redirect(url_for("auth.change_password"))
|
|
109
|
+
user.hashPassword = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt())
|
|
110
|
+
db.session.commit()
|
|
111
|
+
return redirect(url_for("main.index"))
|
|
112
|
+
return render_template("change_password.html")
|
|
113
|
+
|
|
114
|
+
@auth.route("/logout")
|
|
88
115
|
@login_required
|
|
89
116
|
def logout():
|
|
90
117
|
"""
|
|
91
118
|
.. :quickref: User; logout the user
|
|
92
119
|
|
|
120
|
+
.. http:get:: /auth/logout
|
|
121
|
+
|
|
93
122
|
logout the current user, clear session info, and redirect to the login page.
|
|
94
123
|
"""
|
|
95
124
|
logout_user()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}IvoryOS | Change Password{% 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>Change Password</h5>
|
|
10
|
+
<form role="form" method='POST' name="signup" action="{{ url_for('auth.change_password') }}">
|
|
11
|
+
|
|
12
|
+
<div class="input-group mb-3">
|
|
13
|
+
<label class="input-group-text" for="old_password">Old Password</label>
|
|
14
|
+
<input class="form-control" type="password" id="old_password" name="old_password" required>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="input-group mb-3">
|
|
17
|
+
<label class="input-group-text" for="new_password">New Password</label>
|
|
18
|
+
<input class="form-control" type="password" id="new_password" name="new_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%;">Confirm</button>
|
|
26
|
+
</form>
|
|
27
|
+
<p class="message" >Need another account? <a href="{{ url_for('auth.signup') }}">Sign up</a></p>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
{% endblock %}
|