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.
Files changed (107) hide show
  1. docs/source/conf.py +84 -0
  2. ivoryos/__init__.py +17 -207
  3. ivoryos/app.py +154 -0
  4. ivoryos/config.py +1 -0
  5. ivoryos/optimizer/ax_optimizer.py +191 -0
  6. ivoryos/optimizer/base_optimizer.py +84 -0
  7. ivoryos/optimizer/baybe_optimizer.py +193 -0
  8. ivoryos/optimizer/nimo_optimizer.py +173 -0
  9. ivoryos/optimizer/registry.py +11 -0
  10. ivoryos/routes/auth/auth.py +43 -14
  11. ivoryos/routes/auth/templates/change_password.html +32 -0
  12. ivoryos/routes/control/control.py +101 -366
  13. ivoryos/routes/control/control_file.py +33 -0
  14. ivoryos/routes/control/control_new_device.py +152 -0
  15. ivoryos/routes/control/templates/controllers.html +193 -0
  16. ivoryos/routes/control/templates/controllers_new.html +112 -0
  17. ivoryos/routes/control/utils.py +40 -0
  18. ivoryos/routes/data/data.py +197 -0
  19. ivoryos/routes/data/templates/components/step_card.html +78 -0
  20. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  21. ivoryos/routes/data/templates/workflow_view.html +360 -0
  22. ivoryos/routes/design/__init__.py +4 -0
  23. ivoryos/routes/design/design.py +348 -657
  24. ivoryos/routes/design/design_file.py +68 -0
  25. ivoryos/routes/design/design_step.py +171 -0
  26. ivoryos/routes/design/templates/components/action_form.html +53 -0
  27. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  28. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  29. ivoryos/routes/design/templates/components/canvas.html +5 -0
  30. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  31. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  32. ivoryos/routes/design/templates/components/canvas_main.html +39 -0
  33. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  34. ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
  35. ivoryos/routes/design/templates/components/info_modal.html +318 -0
  36. ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
  37. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  38. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  39. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  40. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  41. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  42. ivoryos/routes/design/templates/components/modals.html +6 -0
  43. ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
  44. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  45. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  46. ivoryos/routes/design/templates/experiment_builder.html +44 -0
  47. ivoryos/routes/execute/__init__.py +0 -0
  48. ivoryos/routes/execute/execute.py +377 -0
  49. ivoryos/routes/execute/execute_file.py +78 -0
  50. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  51. ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
  52. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  53. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  54. ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
  55. ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
  56. ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
  57. ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
  58. ivoryos/routes/execute/templates/experiment_run.html +30 -0
  59. ivoryos/routes/library/__init__.py +0 -0
  60. ivoryos/routes/library/library.py +157 -0
  61. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
  62. ivoryos/routes/main/main.py +31 -3
  63. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  64. ivoryos/server.py +180 -0
  65. ivoryos/socket_handlers.py +52 -0
  66. ivoryos/static/ivoryos_logo.png +0 -0
  67. ivoryos/static/js/action_handlers.js +384 -0
  68. ivoryos/static/js/db_delete.js +23 -0
  69. ivoryos/static/js/script_metadata.js +39 -0
  70. ivoryos/static/js/socket_handler.js +40 -5
  71. ivoryos/static/js/sortable_design.js +107 -56
  72. ivoryos/static/js/ui_state.js +114 -0
  73. ivoryos/templates/base.html +67 -8
  74. ivoryos/utils/bo_campaign.py +180 -3
  75. ivoryos/utils/client_proxy.py +267 -36
  76. ivoryos/utils/db_models.py +300 -65
  77. ivoryos/utils/decorators.py +34 -0
  78. ivoryos/utils/form.py +63 -29
  79. ivoryos/utils/global_config.py +34 -1
  80. ivoryos/utils/nest_script.py +314 -0
  81. ivoryos/utils/py_to_json.py +295 -0
  82. ivoryos/utils/script_runner.py +599 -165
  83. ivoryos/utils/serilize.py +201 -0
  84. ivoryos/utils/task_runner.py +71 -21
  85. ivoryos/utils/utils.py +50 -6
  86. ivoryos/version.py +1 -1
  87. ivoryos-1.4.4.dist-info/METADATA +263 -0
  88. ivoryos-1.4.4.dist-info/RECORD +119 -0
  89. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
  90. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
  91. tests/unit/test_type_conversion.py +42 -0
  92. tests/unit/test_util.py +3 -0
  93. ivoryos/routes/control/templates/control/controllers.html +0 -78
  94. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  95. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  96. ivoryos/routes/database/database.py +0 -306
  97. ivoryos/routes/database/templates/database/step_card.html +0 -7
  98. ivoryos/routes/database/templates/database/workflow_view.html +0 -130
  99. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  100. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  101. ivoryos-1.0.9.dist-info/METADATA +0 -218
  102. ivoryos-1.0.9.dist-info/RECORD +0 -61
  103. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  104. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  105. /ivoryos/routes/{database → data}/__init__.py +0 -0
  106. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  107. {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
+ }
@@ -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/auth')
11
+ auth = Blueprint('auth', __name__, template_folder='templates')
10
12
 
11
13
 
12
- @auth.route('/auth/login', methods=['GET', 'POST'])
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
- session['user'] = username
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')), 401
52
+ return redirect(url_for('auth.login'))
50
53
  return render_template('login.html')
51
54
 
52
55
 
53
- @auth.route('/auth/signup', methods=['GET', 'POST'])
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
- @auth.route("/auth/logout")
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 %}