ivoryos 1.2.0b1__py3-none-any.whl → 1.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- ivoryos/__init__.py +22 -1
- ivoryos/config.py +1 -0
- 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/auth/auth.py +3 -1
- ivoryos/routes/data/data.py +2 -0
- ivoryos/routes/design/design.py +4 -4
- ivoryos/routes/library/library.py +4 -4
- ivoryos/utils/script_runner.py +1 -1
- ivoryos/utils/serilize.py +4 -6
- ivoryos/utils/utils.py +2 -1
- ivoryos/version.py +1 -1
- {ivoryos-1.2.0b1.dist-info → ivoryos-1.2.2.dist-info}/METADATA +60 -35
- ivoryos-1.2.2.dist-info/RECORD +47 -0
- {ivoryos-1.2.0b1.dist-info → ivoryos-1.2.2.dist-info}/WHEEL +1 -1
- {ivoryos-1.2.0b1.dist-info → ivoryos-1.2.2.dist-info}/top_level.txt +0 -1
- ivoryos/routes/auth/templates/login.html +0 -25
- ivoryos/routes/auth/templates/signup.html +0 -32
- ivoryos/routes/control/templates/controllers.html +0 -166
- ivoryos/routes/control/templates/controllers_new.html +0 -112
- ivoryos/routes/data/templates/components/step_card.html +0 -13
- ivoryos/routes/data/templates/workflow_database.html +0 -109
- ivoryos/routes/data/templates/workflow_view.html +0 -130
- ivoryos/routes/design/templates/components/action_form.html +0 -53
- ivoryos/routes/design/templates/components/actions_panel.html +0 -25
- ivoryos/routes/design/templates/components/autofill_toggle.html +0 -10
- ivoryos/routes/design/templates/components/canvas.html +0 -5
- ivoryos/routes/design/templates/components/canvas_footer.html +0 -9
- ivoryos/routes/design/templates/components/canvas_header.html +0 -75
- ivoryos/routes/design/templates/components/canvas_main.html +0 -34
- ivoryos/routes/design/templates/components/deck_selector.html +0 -10
- ivoryos/routes/design/templates/components/edit_action_form.html +0 -38
- ivoryos/routes/design/templates/components/instruments_panel.html +0 -66
- ivoryos/routes/design/templates/components/modals/drop_modal.html +0 -17
- ivoryos/routes/design/templates/components/modals/json_modal.html +0 -22
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +0 -17
- ivoryos/routes/design/templates/components/modals/rename_modal.html +0 -23
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +0 -27
- ivoryos/routes/design/templates/components/modals.html +0 -6
- ivoryos/routes/design/templates/components/python_code_overlay.html +0 -39
- ivoryos/routes/design/templates/components/sidebar.html +0 -15
- ivoryos/routes/design/templates/components/text_to_code_panel.html +0 -20
- ivoryos/routes/design/templates/experiment_builder.html +0 -41
- ivoryos/routes/execute/templates/components/error_modal.html +0 -20
- ivoryos/routes/execute/templates/components/logging_panel.html +0 -31
- ivoryos/routes/execute/templates/components/progress_panel.html +0 -27
- ivoryos/routes/execute/templates/components/run_panel.html +0 -9
- ivoryos/routes/execute/templates/components/run_tabs.html +0 -17
- ivoryos/routes/execute/templates/components/tab_bayesian.html +0 -399
- ivoryos/routes/execute/templates/components/tab_configuration.html +0 -98
- ivoryos/routes/execute/templates/components/tab_repeat.html +0 -14
- ivoryos/routes/execute/templates/experiment_run.html +0 -294
- ivoryos/routes/library/templates/library.html +0 -91
- ivoryos/routes/main/templates/help.html +0 -141
- ivoryos/routes/main/templates/home.html +0 -103
- 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 +0 -213
- ivoryos/static/js/db_delete.js +0 -23
- ivoryos/static/js/overlay.js +0 -12
- ivoryos/static/js/script_metadata.js +0 -39
- ivoryos/static/js/socket_handler.js +0 -125
- ivoryos/static/js/sortable_card.js +0 -24
- ivoryos/static/js/sortable_design.js +0 -138
- ivoryos/static/js/ui_state.js +0 -113
- ivoryos/static/logo.webp +0 -0
- ivoryos/static/style.css +0 -211
- ivoryos/templates/base.html +0 -157
- ivoryos-1.2.0b1.dist-info/RECORD +0 -105
- 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.0b1.dist-info → ivoryos-1.2.2.dist-info/licenses}/LICENSE +0 -0
ivoryos/__init__.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
+
import uuid
|
|
3
4
|
from typing import Union
|
|
4
5
|
|
|
5
|
-
from flask import Flask, redirect, url_for, g, Blueprint
|
|
6
|
+
from flask import Flask, redirect, url_for, g, Blueprint, session
|
|
7
|
+
from flask_login import AnonymousUserMixin
|
|
8
|
+
|
|
6
9
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
|
7
10
|
|
|
8
11
|
from ivoryos.config import Config, get_config
|
|
@@ -90,6 +93,23 @@ def create_app(config_class=None):
|
|
|
90
93
|
"""
|
|
91
94
|
g.logger = logger
|
|
92
95
|
g.socketio = socketio
|
|
96
|
+
session.permanent = False
|
|
97
|
+
# DEMO_MODE: Simulate logged-in user per session
|
|
98
|
+
if app.config.get("DEMO_MODE", False):
|
|
99
|
+
if "demo_user_id" not in session:
|
|
100
|
+
session["demo_user_id"] = f"demo_{str(uuid.uuid4())[:8]}"
|
|
101
|
+
|
|
102
|
+
class SessionDemoUser(AnonymousUserMixin):
|
|
103
|
+
@property
|
|
104
|
+
def is_authenticated(self):
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
def get_id(self):
|
|
108
|
+
return session.get("demo_user_id")
|
|
109
|
+
|
|
110
|
+
login_manager.anonymous_user = SessionDemoUser
|
|
111
|
+
|
|
112
|
+
|
|
93
113
|
|
|
94
114
|
@app.route('/')
|
|
95
115
|
def redirect_to_prefix():
|
|
@@ -101,6 +121,7 @@ def create_app(config_class=None):
|
|
|
101
121
|
text = ' '.join(word for word in name.split('_'))
|
|
102
122
|
return text.capitalize()
|
|
103
123
|
|
|
124
|
+
# app.config.setdefault("DEMO_MODE", False)
|
|
104
125
|
return app
|
|
105
126
|
|
|
106
127
|
|
ivoryos/config.py
CHANGED
|
@@ -43,6 +43,7 @@ class TestingConfig(Config):
|
|
|
43
43
|
|
|
44
44
|
class DemoConfig(Config):
|
|
45
45
|
DEBUG = False
|
|
46
|
+
DEMO_MODE = True
|
|
46
47
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
|
|
47
48
|
OUTPUT_FOLDER = os.path.join(os.path.abspath(os.curdir), '/tmp/ivoryos_data')
|
|
48
49
|
CSV_FOLDER = os.path.join(OUTPUT_FOLDER, 'config_csv/')
|
|
@@ -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))
|
ivoryos/routes/auth/auth.py
CHANGED
|
@@ -5,6 +5,7 @@ import bcrypt
|
|
|
5
5
|
from ivoryos.utils.db_models import Script, User, db
|
|
6
6
|
from ivoryos.utils.utils import post_script_file
|
|
7
7
|
login_manager = LoginManager()
|
|
8
|
+
# from flask import g
|
|
8
9
|
|
|
9
10
|
auth = Blueprint('auth', __name__, template_folder='templates')
|
|
10
11
|
|
|
@@ -37,7 +38,8 @@ def login():
|
|
|
37
38
|
# password.encode("utf-8")
|
|
38
39
|
# user = User(username, password.encode("utf-8"))
|
|
39
40
|
login_user(user)
|
|
40
|
-
|
|
41
|
+
# g.user = user
|
|
42
|
+
# session['user'] = username
|
|
41
43
|
script_file = Script(author=username)
|
|
42
44
|
session["script"] = script_file.as_dict()
|
|
43
45
|
session['hidden_functions'], session['card_order'], session['prompt'] = {}, {}, {}
|
ivoryos/routes/data/data.py
CHANGED
|
@@ -10,6 +10,7 @@ data = Blueprint('data', __name__, template_folder='templates')
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@data.route('/executions/records')
|
|
13
|
+
@login_required
|
|
13
14
|
def list_workflows():
|
|
14
15
|
"""
|
|
15
16
|
.. :quickref: Workflow Execution Database; list all workflow execution records
|
|
@@ -113,6 +114,7 @@ def delete_workflow_record(workflow_id: int):
|
|
|
113
114
|
|
|
114
115
|
|
|
115
116
|
@data.route('/files/execution-data/<string:filename>')
|
|
117
|
+
@login_required
|
|
116
118
|
def download_results(filename:str):
|
|
117
119
|
"""
|
|
118
120
|
.. :quickref: Workflow data; download a workflow data file (.CSV)
|
ivoryos/routes/design/design.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
from flask import Blueprint, redirect, url_for, flash, jsonify, request, render_template, session, current_app
|
|
4
|
-
from flask_login import login_required
|
|
4
|
+
from flask_login import login_required, current_user
|
|
5
5
|
|
|
6
6
|
from ivoryos.routes.library.library import publish
|
|
7
7
|
from ivoryos.utils import utils
|
|
@@ -64,7 +64,7 @@ def experiment_builder():
|
|
|
64
64
|
if deck and script.deck is None:
|
|
65
65
|
script.deck = os.path.splitext(os.path.basename(deck.__file__))[
|
|
66
66
|
0] if deck.__name__ == "__main__" else deck.__name__
|
|
67
|
-
|
|
67
|
+
utils.post_script_file(script)
|
|
68
68
|
pseudo_deck_name = session.get('pseudo_deck', '')
|
|
69
69
|
pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
|
|
70
70
|
off_line = current_app.config["OFF_LINE"]
|
|
@@ -238,7 +238,7 @@ def clear_draft():
|
|
|
238
238
|
0] if deck.__name__ == "__main__" else deck.__name__
|
|
239
239
|
else:
|
|
240
240
|
deck_name = session.get("pseudo_deck", "")
|
|
241
|
-
script = Script(deck=deck_name, author=
|
|
241
|
+
script = Script(deck=deck_name, author=current_user.get_id())
|
|
242
242
|
utils.post_script_file(script)
|
|
243
243
|
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
244
244
|
session['python_code'] = exec_string
|
|
@@ -265,7 +265,7 @@ def submit_script():
|
|
|
265
265
|
"""
|
|
266
266
|
deck = global_config.deck
|
|
267
267
|
deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
|
|
268
|
-
script = Script(author=
|
|
268
|
+
script = Script(author=current_user.get_id(), deck=deck_name)
|
|
269
269
|
script_collection = request.get_json()
|
|
270
270
|
workflow_name = script_collection.pop("workflow_name")
|
|
271
271
|
script.python_script = script_collection
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify
|
|
2
|
-
from flask_login import login_required
|
|
2
|
+
from flask_login import login_required, current_user
|
|
3
3
|
|
|
4
4
|
from ivoryos.utils.db_models import Script, db, WorkflowRun, WorkflowStep
|
|
5
5
|
from ivoryos.utils.utils import get_script_file, post_script_file
|
|
@@ -66,14 +66,14 @@ def publish():
|
|
|
66
66
|
script = get_script_file()
|
|
67
67
|
|
|
68
68
|
if script.author is None:
|
|
69
|
-
script.author =
|
|
69
|
+
script.author = current_user.get_id()
|
|
70
70
|
if not script.name or not script.deck:
|
|
71
71
|
return {"success": False, "error": "Deck cannot be empty, try to re-submit deck configuration on the left panel"}
|
|
72
72
|
row = Script.query.get(script.name)
|
|
73
73
|
if row and row.status == "finalized":
|
|
74
74
|
return {"success": False, "error": "This is a protected script, use save as to rename."}
|
|
75
75
|
|
|
76
|
-
elif row and
|
|
76
|
+
elif row and current_user.get_id() != row.author:
|
|
77
77
|
return {"success": False, "error": "You are not the author, use save as to rename."}
|
|
78
78
|
else:
|
|
79
79
|
db.session.merge(script)
|
|
@@ -145,7 +145,7 @@ def save_as():
|
|
|
145
145
|
script = get_script_file()
|
|
146
146
|
script.save_as(run_name)
|
|
147
147
|
script.registered = register_workflow == "on"
|
|
148
|
-
script.author =
|
|
148
|
+
script.author = current_user.get_id()
|
|
149
149
|
post_script_file(script)
|
|
150
150
|
status = publish()
|
|
151
151
|
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
ivoryos/utils/script_runner.py
CHANGED
|
@@ -66,7 +66,7 @@ class ScriptRunner:
|
|
|
66
66
|
global deck
|
|
67
67
|
if deck is None:
|
|
68
68
|
deck = global_config.deck
|
|
69
|
-
print("history", history)
|
|
69
|
+
# print("history", history)
|
|
70
70
|
if self.current_app is None:
|
|
71
71
|
self.current_app = current_app
|
|
72
72
|
# time.sleep(1) # Optional: may help ensure deck readiness
|
ivoryos/utils/serilize.py
CHANGED
|
@@ -7,8 +7,8 @@ import sys
|
|
|
7
7
|
|
|
8
8
|
import flask
|
|
9
9
|
|
|
10
|
-
from example.abstract_sdl_example
|
|
11
|
-
|
|
10
|
+
from example.abstract_sdl_example import abstract_sdl as deck
|
|
11
|
+
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ScriptAnalyzer:
|
|
@@ -179,14 +179,12 @@ class ScriptAnalyzer:
|
|
|
179
179
|
print(f" {name}: {error}")
|
|
180
180
|
|
|
181
181
|
if __name__ == "__main__":
|
|
182
|
-
balance = AbstractBalance("re")
|
|
183
|
-
pump = AbstractPump("re")
|
|
184
182
|
|
|
185
183
|
_analyzer = ScriptAnalyzer()
|
|
186
|
-
module = sys.modules[
|
|
184
|
+
# module = sys.modules[deck]
|
|
187
185
|
try:
|
|
188
186
|
|
|
189
|
-
result, with_warnings, failed, _ = _analyzer.analyze_module(
|
|
187
|
+
result, with_warnings, failed, _ = _analyzer.analyze_module(deck)
|
|
190
188
|
|
|
191
189
|
output_path = f"analysis.json"
|
|
192
190
|
_analyzer.save_to_json(result, output_path)
|
ivoryos/utils/utils.py
CHANGED
|
@@ -11,6 +11,7 @@ from collections import Counter
|
|
|
11
11
|
|
|
12
12
|
import flask
|
|
13
13
|
from flask import session
|
|
14
|
+
from flask_login import current_user
|
|
14
15
|
from flask_socketio import SocketIO
|
|
15
16
|
|
|
16
17
|
from ivoryos.utils.db_models import Script
|
|
@@ -24,7 +25,7 @@ def get_script_file():
|
|
|
24
25
|
s.__dict__.update(**session_script)
|
|
25
26
|
return s
|
|
26
27
|
else:
|
|
27
|
-
return Script(author=
|
|
28
|
+
return Script(author=current_user.get_id(),)
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def post_script_file(script, is_dict=False):
|
ivoryos/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.2.
|
|
1
|
+
__version__ = "1.2.2"
|