ivoryos 1.2.1__tar.gz → 1.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ivoryos might be problematic. Click here for more details.

Files changed (117) hide show
  1. {ivoryos-1.2.1 → ivoryos-1.2.3}/PKG-INFO +18 -5
  2. ivoryos-1.2.3/ivoryos/optimizer/ax_optimizer.py +164 -0
  3. ivoryos-1.2.3/ivoryos/optimizer/base_optimizer.py +65 -0
  4. ivoryos-1.2.3/ivoryos/optimizer/baybe_optimizer.py +183 -0
  5. ivoryos-1.2.3/ivoryos/optimizer/registry.py +9 -0
  6. ivoryos-1.2.3/ivoryos/routes/api/api.py +56 -0
  7. ivoryos-1.2.3/ivoryos/routes/auth/templates/login.html +25 -0
  8. ivoryos-1.2.3/ivoryos/routes/auth/templates/signup.html +32 -0
  9. ivoryos-1.2.3/ivoryos/routes/control/templates/controllers.html +166 -0
  10. ivoryos-1.2.3/ivoryos/routes/control/templates/controllers_new.html +112 -0
  11. ivoryos-1.2.3/ivoryos/routes/data/templates/components/step_card.html +13 -0
  12. ivoryos-1.2.3/ivoryos/routes/data/templates/workflow_database.html +109 -0
  13. ivoryos-1.2.3/ivoryos/routes/data/templates/workflow_view.html +130 -0
  14. ivoryos-1.2.3/ivoryos/routes/design/templates/components/action_form.html +53 -0
  15. ivoryos-1.2.3/ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  16. ivoryos-1.2.3/ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  17. ivoryos-1.2.3/ivoryos/routes/design/templates/components/canvas.html +5 -0
  18. ivoryos-1.2.3/ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  19. ivoryos-1.2.3/ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  20. ivoryos-1.2.3/ivoryos/routes/design/templates/components/canvas_main.html +34 -0
  21. ivoryos-1.2.3/ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  22. ivoryos-1.2.3/ivoryos/routes/design/templates/components/edit_action_form.html +38 -0
  23. ivoryos-1.2.3/ivoryos/routes/design/templates/components/instruments_panel.html +66 -0
  24. ivoryos-1.2.3/ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  25. ivoryos-1.2.3/ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  26. ivoryos-1.2.3/ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  27. ivoryos-1.2.3/ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  28. ivoryos-1.2.3/ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  29. ivoryos-1.2.3/ivoryos/routes/design/templates/components/modals.html +6 -0
  30. ivoryos-1.2.3/ivoryos/routes/design/templates/components/python_code_overlay.html +39 -0
  31. ivoryos-1.2.3/ivoryos/routes/design/templates/components/sidebar.html +15 -0
  32. ivoryos-1.2.3/ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  33. ivoryos-1.2.3/ivoryos/routes/design/templates/experiment_builder.html +41 -0
  34. ivoryos-1.2.3/ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  35. ivoryos-1.2.3/ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
  36. ivoryos-1.2.3/ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  37. ivoryos-1.2.3/ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  38. ivoryos-1.2.3/ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
  39. ivoryos-1.2.3/ivoryos/routes/execute/templates/components/tab_bayesian.html +398 -0
  40. ivoryos-1.2.3/ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
  41. ivoryos-1.2.3/ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
  42. ivoryos-1.2.3/ivoryos/routes/execute/templates/experiment_run.html +294 -0
  43. ivoryos-1.2.3/ivoryos/routes/library/templates/library.html +92 -0
  44. ivoryos-1.2.3/ivoryos/routes/main/templates/help.html +141 -0
  45. ivoryos-1.2.3/ivoryos/routes/main/templates/home.html +103 -0
  46. ivoryos-1.2.3/ivoryos/static/favicon.ico +0 -0
  47. ivoryos-1.2.3/ivoryos/static/gui_annotation/Slide1.png +0 -0
  48. ivoryos-1.2.3/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  49. ivoryos-1.2.3/ivoryos/static/js/action_handlers.js +213 -0
  50. ivoryos-1.2.3/ivoryos/static/js/db_delete.js +23 -0
  51. ivoryos-1.2.3/ivoryos/static/js/overlay.js +12 -0
  52. ivoryos-1.2.3/ivoryos/static/js/script_metadata.js +39 -0
  53. ivoryos-1.2.3/ivoryos/static/js/socket_handler.js +125 -0
  54. ivoryos-1.2.3/ivoryos/static/js/sortable_card.js +24 -0
  55. ivoryos-1.2.3/ivoryos/static/js/sortable_design.js +138 -0
  56. ivoryos-1.2.3/ivoryos/static/js/ui_state.js +113 -0
  57. ivoryos-1.2.3/ivoryos/static/logo.webp +0 -0
  58. ivoryos-1.2.3/ivoryos/static/style.css +211 -0
  59. ivoryos-1.2.3/ivoryos/templates/base.html +157 -0
  60. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/serilize.py +4 -6
  61. ivoryos-1.2.3/ivoryos/version.py +1 -0
  62. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos.egg-info/PKG-INFO +18 -5
  63. ivoryos-1.2.3/ivoryos.egg-info/SOURCES.txt +103 -0
  64. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos.egg-info/requires.txt +5 -1
  65. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos.egg-info/top_level.txt +0 -1
  66. ivoryos-1.2.3/pyproject.toml +44 -0
  67. ivoryos-1.2.1/ivoryos/version.py +0 -1
  68. ivoryos-1.2.1/ivoryos.egg-info/SOURCES.txt +0 -54
  69. ivoryos-1.2.1/setup.py +0 -38
  70. ivoryos-1.2.1/tests/__init__.py +0 -0
  71. ivoryos-1.2.1/tests/conftest.py +0 -133
  72. ivoryos-1.2.1/tests/integration/__init__.py +0 -0
  73. ivoryos-1.2.1/tests/integration/test_route_auth.py +0 -80
  74. ivoryos-1.2.1/tests/integration/test_route_control.py +0 -94
  75. ivoryos-1.2.1/tests/integration/test_route_database.py +0 -61
  76. ivoryos-1.2.1/tests/integration/test_route_design.py +0 -36
  77. ivoryos-1.2.1/tests/integration/test_route_main.py +0 -35
  78. ivoryos-1.2.1/tests/integration/test_sockets.py +0 -26
  79. {ivoryos-1.2.1 → ivoryos-1.2.3}/LICENSE +0 -0
  80. {ivoryos-1.2.1 → ivoryos-1.2.3}/README.md +0 -0
  81. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/__init__.py +0 -0
  82. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/config.py +0 -0
  83. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/__init__.py +0 -0
  84. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/auth/__init__.py +0 -0
  85. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/auth/auth.py +0 -0
  86. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/control/__init__.py +0 -0
  87. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/control/control.py +0 -0
  88. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/control/control_file.py +0 -0
  89. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/control/control_new_device.py +0 -0
  90. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/control/utils.py +0 -0
  91. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/data/__init__.py +0 -0
  92. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/data/data.py +0 -0
  93. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/design/__init__.py +0 -0
  94. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/design/design.py +0 -0
  95. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/design/design_file.py +0 -0
  96. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/design/design_step.py +0 -0
  97. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/execute/__init__.py +0 -0
  98. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/execute/execute.py +0 -0
  99. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/execute/execute_file.py +0 -0
  100. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/library/__init__.py +0 -0
  101. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/library/library.py +0 -0
  102. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/main/__init__.py +0 -0
  103. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/routes/main/main.py +0 -0
  104. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/socket_handlers.py +0 -0
  105. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/__init__.py +0 -0
  106. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/bo_campaign.py +0 -0
  107. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/client_proxy.py +0 -0
  108. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/db_models.py +0 -0
  109. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/form.py +0 -0
  110. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/global_config.py +0 -0
  111. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/llm_agent.py +0 -0
  112. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/py_to_json.py +0 -0
  113. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/script_runner.py +0 -0
  114. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/task_runner.py +0 -0
  115. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos/utils/utils.py +0 -0
  116. {ivoryos-1.2.1 → ivoryos-1.2.3}/ivoryos.egg-info/dependency_links.txt +0 -0
  117. {ivoryos-1.2.1 → ivoryos-1.2.3}/setup.cfg +0 -0
@@ -1,13 +1,26 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: ivoryos
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
5
- Home-page: https://gitlab.com/heingroup/ivoryos
6
- Author: Ivory Zhang
7
- Author-email: ivoryzhang@chem.ubc.ca
5
+ Author-email: Ivory Zhang <ivoryzhang@chem.ubc.ca>
8
6
  License: MIT
7
+ Project-URL: Homepage, https://gitlab.com/heingroup/ivoryos
8
+ Requires-Python: >=3.7
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
+ Requires-Dist: bcrypt
12
+ Requires-Dist: Flask-Login
13
+ Requires-Dist: Flask-Session
14
+ Requires-Dist: Flask-SocketIO
15
+ Requires-Dist: Flask-SQLAlchemy
16
+ Requires-Dist: Flask-WTF
17
+ Requires-Dist: SQLAlchemy-Utils
18
+ Requires-Dist: python-dotenv
19
+ Requires-Dist: astor; python_version < "3.9"
20
+ Provides-Extra: optimizer
21
+ Requires-Dist: ax-platform; extra == "optimizer"
22
+ Requires-Dist: baybe; extra == "optimizer"
23
+ Dynamic: license-file
11
24
 
12
25
  [![Documentation Status](https://readthedocs.org/projects/ivoryos/badge/?version=latest)](https://ivoryos.readthedocs.io/en/latest/?badge=latest)
13
26
  [![PyPI version](https://img.shields.io/pypi/v/ivoryos)](https://pypi.org/project/ivoryos/)
@@ -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,9 @@
1
+ # optimizers/registry.py
2
+
3
+ from ivoryos.optimizer.ax_optimizer import AxOptimizer
4
+ from ivoryos.optimizer.baybe_optimizer import BaybeOptimizer
5
+
6
+ OPTIMIZER_REGISTRY = {
7
+ "ax": AxOptimizer,
8
+ "baybe": BaybeOptimizer
9
+ }
@@ -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 %}