ivoryos 1.0.9__py3-none-any.whl → 1.2.0b1__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.

Files changed (87) hide show
  1. ivoryos/__init__.py +26 -7
  2. ivoryos/routes/api/api.py +56 -0
  3. ivoryos/routes/auth/auth.py +5 -5
  4. ivoryos/routes/control/control.py +77 -372
  5. ivoryos/routes/control/control_file.py +36 -0
  6. ivoryos/routes/control/control_new_device.py +142 -0
  7. ivoryos/routes/control/templates/controllers.html +166 -0
  8. ivoryos/routes/control/templates/controllers_new.html +112 -0
  9. ivoryos/routes/control/utils.py +38 -0
  10. ivoryos/routes/data/data.py +129 -0
  11. ivoryos/routes/data/templates/components/step_card.html +13 -0
  12. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  13. ivoryos/routes/{database/templates/database → data/templates}/workflow_view.html +3 -3
  14. ivoryos/routes/design/__init__.py +4 -0
  15. ivoryos/routes/design/design.py +298 -656
  16. ivoryos/routes/design/design_file.py +68 -0
  17. ivoryos/routes/design/design_step.py +145 -0
  18. ivoryos/routes/design/templates/components/action_form.html +53 -0
  19. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  20. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  21. ivoryos/routes/design/templates/components/canvas.html +5 -0
  22. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  23. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  24. ivoryos/routes/design/templates/components/canvas_main.html +34 -0
  25. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  26. ivoryos/routes/design/templates/components/edit_action_form.html +38 -0
  27. ivoryos/routes/design/templates/components/instruments_panel.html +66 -0
  28. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  29. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  30. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  31. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  32. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  33. ivoryos/routes/design/templates/components/modals.html +6 -0
  34. ivoryos/routes/design/templates/components/python_code_overlay.html +39 -0
  35. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  36. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  37. ivoryos/routes/design/templates/experiment_builder.html +41 -0
  38. ivoryos/routes/execute/__init__.py +0 -0
  39. ivoryos/routes/execute/execute.py +317 -0
  40. ivoryos/routes/execute/execute_file.py +78 -0
  41. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  42. ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
  43. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  44. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  45. ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
  46. ivoryos/routes/execute/templates/components/tab_bayesian.html +399 -0
  47. ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
  48. ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
  49. ivoryos/routes/execute/templates/experiment_run.html +294 -0
  50. ivoryos/routes/library/__init__.py +0 -0
  51. ivoryos/routes/library/library.py +159 -0
  52. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +30 -22
  53. ivoryos/routes/main/main.py +1 -1
  54. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  55. ivoryos/socket_handlers.py +52 -0
  56. ivoryos/static/js/action_handlers.js +213 -0
  57. ivoryos/static/js/db_delete.js +23 -0
  58. ivoryos/static/js/script_metadata.js +39 -0
  59. ivoryos/static/js/sortable_design.js +89 -56
  60. ivoryos/static/js/ui_state.js +113 -0
  61. ivoryos/templates/base.html +4 -4
  62. ivoryos/utils/bo_campaign.py +179 -3
  63. ivoryos/utils/db_models.py +14 -5
  64. ivoryos/utils/form.py +5 -9
  65. ivoryos/utils/global_config.py +13 -1
  66. ivoryos/utils/py_to_json.py +225 -0
  67. ivoryos/utils/script_runner.py +49 -7
  68. ivoryos/utils/serilize.py +203 -0
  69. ivoryos/utils/task_runner.py +4 -1
  70. ivoryos/version.py +1 -1
  71. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/METADATA +5 -8
  72. ivoryos-1.2.0b1.dist-info/RECORD +105 -0
  73. ivoryos/routes/control/templates/control/controllers.html +0 -78
  74. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  75. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  76. ivoryos/routes/database/database.py +0 -306
  77. ivoryos/routes/database/templates/database/step_card.html +0 -7
  78. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  79. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  80. ivoryos-1.0.9.dist-info/RECORD +0 -61
  81. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  82. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  83. /ivoryos/routes/{database → data}/__init__.py +0 -0
  84. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  85. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/LICENSE +0 -0
  86. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/WHEEL +0 -0
  87. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
1
+ from typing import Dict, Any
2
+ import re
1
3
  from ivoryos.utils.utils import install_and_import
2
4
 
3
5
 
4
- def ax_init_form(data, arg_types):
6
+ def ax_init_form(data, arg_types, previous_data_len=0):
5
7
  """
6
8
  create Ax campaign from the web form input
7
9
  :param data:
@@ -9,7 +11,11 @@ def ax_init_form(data, arg_types):
9
11
  install_and_import("ax", "ax-platform")
10
12
  parameter, objectives = ax_wrapper(data, arg_types)
11
13
  from ax.service.ax_client import AxClient
12
- ax_client = AxClient()
14
+ if previous_data_len > 0:
15
+ gs = exisitng_data_gs(previous_data_len)
16
+ ax_client = AxClient(generation_strategy=gs)
17
+ else:
18
+ ax_client = AxClient()
13
19
  ax_client.create_experiment(parameter, objectives=objectives)
14
20
  return ax_client
15
21
 
@@ -84,4 +90,174 @@ def ax_init_opc(bo_args):
84
90
  bo_args["objectives"] = objectives_formatted
85
91
  ax_client.create_experiment(**bo_args)
86
92
 
87
- return ax_client
93
+ return ax_client
94
+
95
+
96
+ def exisitng_data_gs(data_len):
97
+ """
98
+ temporal generation strategy for existing data
99
+ """
100
+ from ax.generation_strategy.generation_node import GenerationStep
101
+ from ax.generation_strategy.generation_strategy import GenerationStrategy
102
+ from ax.modelbridge.registry import Generators
103
+ if data_len > 4:
104
+ gs = GenerationStrategy(
105
+ steps=[
106
+ GenerationStep(
107
+ model=Generators.BOTORCH_MODULAR,
108
+ num_trials=-1,
109
+ max_parallelism=3,
110
+ ),
111
+ ]
112
+ )
113
+ else:
114
+ gs = GenerationStrategy(
115
+ steps=[
116
+ GenerationStep(
117
+ model=Generators.SOBOL,
118
+ num_trials=5-data_len, # how many sobol trials to perform (rule of thumb: 2 * number of params)
119
+ max_parallelism=5,
120
+ model_kwargs={"seed": 999},
121
+ ),
122
+ GenerationStep(
123
+ model=Generators.BOTORCH_MODULAR,
124
+ num_trials=-1,
125
+ max_parallelism=3,
126
+ ),
127
+ ]
128
+ )
129
+ return gs
130
+
131
+
132
+ def parse_optimization_form(form_data: Dict[str, str]):
133
+ """
134
+ Parse dynamic form data into structured optimization configuration.
135
+
136
+ Expected form field patterns:
137
+ - Objectives: {name}_min, {name}_weight
138
+ - Parameters: {name}_type, {name}_min, {name}_max, {name}_choices, {name}_value_type
139
+ - Config: step{n}_model, step{n}_num_samples
140
+ """
141
+
142
+ objectives = []
143
+ parameters = []
144
+ config = {}
145
+
146
+ # Track processed field names to avoid duplicates
147
+ processed_objectives = set()
148
+ processed_parameters = set()
149
+
150
+ # Parse objectives
151
+ for field_name, value in form_data.items():
152
+ if field_name.endswith('_min') and value:
153
+ # Extract objective name
154
+ obj_name = field_name.replace('_min', '')
155
+ if obj_name in processed_objectives:
156
+ continue
157
+
158
+ # Check if corresponding weight exists
159
+ weight_field = f"{obj_name}_weight"
160
+ if weight_field in form_data and form_data[weight_field]:
161
+ objectives.append({
162
+ "name": obj_name,
163
+ "minimize": value == "minimize",
164
+ "weight": float(form_data[weight_field])
165
+ })
166
+ else:
167
+ objectives.append({
168
+ "name": obj_name,
169
+ "minimize": value == "minimize",
170
+ })
171
+ processed_objectives.add(obj_name)
172
+
173
+ # Parse parameters
174
+ for field_name, value in form_data.items():
175
+ if field_name.endswith('_type') and value:
176
+ # Extract parameter name
177
+ param_name = field_name.replace('_type', '')
178
+ if param_name in processed_parameters:
179
+ continue
180
+
181
+ parameter = {
182
+ "name": param_name,
183
+ "type": value
184
+ }
185
+
186
+ # Get value type (default to float)
187
+ value_type_field = f"{param_name}_value_type"
188
+ value_type = form_data.get(value_type_field, "float")
189
+ parameter["value_type"] = value_type
190
+
191
+ # Handle different parameter types
192
+ if value == "range":
193
+ min_field = f"{param_name}_min"
194
+ max_field = f"{param_name}_max"
195
+
196
+ if min_field in form_data and max_field in form_data:
197
+ min_val = form_data[min_field]
198
+ max_val = form_data[max_field]
199
+
200
+ if min_val and max_val:
201
+ # Convert based on value_type
202
+ if value_type == "int":
203
+ bounds = [int(min_val), int(max_val)]
204
+ elif value_type == "float":
205
+ bounds = [float(min_val), float(max_val)]
206
+ else: # string
207
+ bounds = [str(min_val), str(max_val)]
208
+
209
+ parameter["bounds"] = bounds
210
+
211
+ elif value == "choice":
212
+ choices_field = f"{param_name}_choices"
213
+ if choices_field in form_data and form_data[choices_field]:
214
+ # Split choices by comma and clean whitespace
215
+ choices = [choice.strip() for choice in form_data[choices_field].split(',')]
216
+
217
+ # Convert choices based on value_type
218
+ if value_type == "int":
219
+ choices = [int(choice) for choice in choices if choice.isdigit()]
220
+ elif value_type == "float":
221
+ choices = [float(choice) for choice in choices if
222
+ choice.replace('.', '').replace('-', '').isdigit()]
223
+ # For string, keep as is
224
+
225
+ parameter["bounds"] = choices
226
+
227
+ elif value == "fixed":
228
+ fixed_field = f"{param_name}_value"
229
+ if fixed_field in form_data and form_data[fixed_field]:
230
+ fixed_val = form_data[fixed_field]
231
+
232
+ # Convert based on value_type
233
+ if value_type == "int":
234
+ parameter["value"] = int(fixed_val)
235
+ elif value_type == "float":
236
+ parameter["value"] = float(fixed_val)
237
+ else:
238
+ parameter["value"] = str(fixed_val)
239
+
240
+ parameters.append(parameter)
241
+ processed_parameters.add(param_name)
242
+
243
+ # Parse configuration steps
244
+ step_pattern = re.compile(r'step(\d+)_(.+)')
245
+ steps = {}
246
+
247
+ for field_name, value in form_data.items():
248
+ match = step_pattern.match(field_name)
249
+ if match and value:
250
+ step_num = int(match.group(1))
251
+ step_attr = match.group(2)
252
+ step_key = f"step_{step_num}"
253
+
254
+ if step_key not in steps:
255
+ steps[step_key] = {}
256
+
257
+ # Convert num_samples to int if it's a number field
258
+ if step_attr == "num_samples":
259
+ steps[step_key][step_attr] = int(value)
260
+ else:
261
+ steps[step_key][step_attr] = value
262
+
263
+ return parameters, objectives, steps
@@ -265,11 +265,20 @@ class Script(db.Model):
265
265
  output_variables: Dict[str, str] = self.get_variables()
266
266
  # print(output_variables)
267
267
  for key, value in kwargs.items():
268
- if type(value) is str and value in output_variables:
269
- var_type = output_variables[value]
270
- kwargs[key] = {value: var_type}
271
- if isinstance(value, str) and value.startswith("#"):
272
- kwargs[key] = f"#{self.validate_function_name(value[1:])}"
268
+ if isinstance(value, str):
269
+ if value in output_variables:
270
+ var_type = output_variables[value]
271
+ kwargs[key] = {value: var_type}
272
+ elif value.startswith("#"):
273
+ kwargs[key] = f"#{self.validate_function_name(value[1:])}"
274
+ else:
275
+ # attempt to convert to numerical or bool value for args with no type hint
276
+ try:
277
+ converted = ast.literal_eval(value)
278
+ if isinstance(converted, (int, float, bool)):
279
+ kwargs[key] = converted
280
+ except (ValueError, SyntaxError):
281
+ pass
273
282
  return kwargs
274
283
 
275
284
  def add_logic_action(self, logic_type: str, statement, insert_position=None):
ivoryos/utils/form.py CHANGED
@@ -214,11 +214,6 @@ class FlexibleEnumField(StringField):
214
214
  f"Invalid choice: '{key}'. Must match one of {list(self.enum_class.__members__.keys())}")
215
215
 
216
216
 
217
- def format_name(name):
218
- """Converts 'example_name' to 'Example Name'."""
219
- name = name.split(".")[-1]
220
- text = ' '.join(word for word in name.split('_'))
221
- return text.capitalize()
222
217
 
223
218
  def parse_annotation(annotation):
224
219
  """
@@ -264,7 +259,7 @@ def create_form_for_method(method, autofill, script=None, design=True):
264
259
  for param in sig.parameters.values():
265
260
  if param.name == 'self':
266
261
  continue
267
- formatted_param_name = format_name(param.name)
262
+ # formatted_param_name = format_name(param.name)
268
263
 
269
264
  default_value = None
270
265
  if autofill:
@@ -277,7 +272,7 @@ def create_form_for_method(method, autofill, script=None, design=True):
277
272
  default_value = param.default
278
273
 
279
274
  field_kwargs = {
280
- "label": formatted_param_name,
275
+ "label": param.name,
281
276
  "default": default_value,
282
277
  "validators": [InputRequired()] if param.default is param.empty else [Optional()],
283
278
  **({"script": script} if (autofill or design) else {})
@@ -321,6 +316,7 @@ def create_add_form(attr, attr_name, autofill: bool, script=None, design: bool =
321
316
  """
322
317
  signature = attr.get('signature', {})
323
318
  docstring = attr.get('docstring', "")
319
+ # print(signature, docstring)
324
320
  dynamic_form = create_form_for_method(signature, autofill, script, design)
325
321
  if design:
326
322
  return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
@@ -395,12 +391,12 @@ def create_form_from_action(action: dict, script=None, design=True):
395
391
  }
396
392
 
397
393
  for name, param_type in arg_types.items():
398
- formatted_param_name = format_name(name)
394
+ # formatted_param_name = format_name(name)
399
395
  value = args.get(name, "")
400
396
  if type(value) is dict:
401
397
  value = next(iter(value))
402
398
  field_kwargs = {
403
- "label": formatted_param_name,
399
+ "label": name,
404
400
  "default": f'{value}',
405
401
  "validators": [InputRequired()],
406
402
  **({"script": script})
@@ -15,6 +15,7 @@ class GlobalConfig:
15
15
  cls._instance._deck_snapshot = {}
16
16
  cls._instance._runner_lock = threading.Lock()
17
17
  cls._instance._runner_status = None
18
+ cls._instance._optimizers = {}
18
19
  return cls._instance
19
20
 
20
21
  @property
@@ -84,4 +85,15 @@ class GlobalConfig:
84
85
 
85
86
  @runner_status.setter
86
87
  def runner_status(self, value):
87
- self._runner_status = value
88
+ self._runner_status = value
89
+
90
+ @property
91
+ def optimizers(self):
92
+ return self._optimizers
93
+
94
+ @optimizers.setter
95
+ def optimizers(self, value):
96
+ if isinstance(value, dict):
97
+ self._optimizers = value
98
+ else:
99
+ raise ValueError("Optimizers must be a dictionary.")
@@ -0,0 +1,225 @@
1
+ import ast
2
+ import json
3
+ import uuid
4
+
5
+ def generate_uuid():
6
+ return int(str(uuid.uuid4().int)[:15])
7
+
8
+ def infer_type(value):
9
+ if isinstance(value, bool):
10
+ return "bool"
11
+ elif isinstance(value, int):
12
+ return "int"
13
+ elif isinstance(value, float):
14
+ return "float"
15
+ elif isinstance(value, str):
16
+ return "str"
17
+ elif isinstance(value, (ast.Name, str)) and str(value).startswith("#"):
18
+ return "float" # default fallback for variables
19
+ else:
20
+ return "unknown"
21
+
22
+ def convert_to_cards(source_code: str):
23
+ tree = ast.parse(source_code)
24
+ cards = []
25
+ card_id = 1
26
+ block_stack = [] # to track control flow UUIDs
27
+
28
+ def new_id():
29
+ nonlocal card_id
30
+ val = card_id
31
+ card_id += 1
32
+ return val
33
+
34
+ def add_card(card):
35
+ cards.append(card)
36
+
37
+ def is_supported_assignment(node):
38
+ return (
39
+ isinstance(node.targets[0], ast.Name) and
40
+ isinstance(node.value, (ast.Constant, ast.Num, ast.Str, ast.NameConstant))
41
+ )
42
+
43
+ class CardVisitor(ast.NodeVisitor):
44
+ def visit_FunctionDef(self, node):
45
+ self.defined_types = {
46
+ arg.arg: ast.unparse(arg.annotation) if arg.annotation else "float"
47
+ for arg in node.args.args
48
+ }
49
+ for stmt in node.body:
50
+ self.visit(stmt)
51
+
52
+ def visit_If(self, node):
53
+ uuid_ = generate_uuid()
54
+ block_stack.append(("if", uuid_))
55
+
56
+ add_card({
57
+ "action": "if",
58
+ "arg_types": {"statement": ""},
59
+ "args": {"statement": ast.unparse(node.test)},
60
+ "id": new_id(),
61
+ "instrument": "if",
62
+ "return": "",
63
+ "uuid": uuid_
64
+ })
65
+
66
+ for stmt in node.body:
67
+ self.visit(stmt)
68
+
69
+ if node.orelse:
70
+ add_card({
71
+ "action": "else",
72
+ "args": {},
73
+ "id": new_id(),
74
+ "instrument": "if",
75
+ "return": "",
76
+ "uuid": uuid_
77
+ })
78
+ for stmt in node.orelse:
79
+ self.visit(stmt)
80
+
81
+ _, block_uuid = block_stack.pop()
82
+ add_card({
83
+ "action": "endif",
84
+ "args": {},
85
+ "id": new_id(),
86
+ "instrument": "if",
87
+ "return": "",
88
+ "uuid": block_uuid
89
+ })
90
+
91
+ def visit_While(self, node):
92
+ uuid_ = generate_uuid()
93
+ block_stack.append(("while", uuid_))
94
+
95
+ add_card({
96
+ "action": "while",
97
+ "arg_types": {"statement": ""},
98
+ "args": {"statement": ast.unparse(node.test)},
99
+ "id": new_id(),
100
+ "instrument": "while",
101
+ "return": "",
102
+ "uuid": uuid_
103
+ })
104
+
105
+ for stmt in node.body:
106
+ self.visit(stmt)
107
+
108
+ _, block_uuid = block_stack.pop()
109
+ add_card({
110
+ "action": "endwhile",
111
+ "args": {},
112
+ "id": new_id(),
113
+ "instrument": "while",
114
+ "return": "",
115
+ "uuid": block_uuid
116
+ })
117
+
118
+ def visit_Assign(self, node):
119
+ if is_supported_assignment(node):
120
+ var_name = node.targets[0].id
121
+ value = node.value.value
122
+ add_card({
123
+ "action": var_name,
124
+ "arg_types": {"statement": infer_type(value)},
125
+ "args": {"statement": value},
126
+ "id": new_id(),
127
+ "instrument": "variable",
128
+ "return": "",
129
+ "uuid": generate_uuid()
130
+ })
131
+ elif isinstance(node.value, ast.Call):
132
+ self.handle_call(node.value, ret_var=node.targets[0].id)
133
+
134
+ def visit_Expr(self, node):
135
+ if isinstance(node.value, ast.Call):
136
+ self.handle_call(node.value)
137
+
138
+ def handle_call(self, node, ret_var=""):
139
+ func_parts = []
140
+ f = node.func
141
+ while isinstance(f, ast.Attribute):
142
+ func_parts.insert(0, f.attr)
143
+ f = f.value
144
+ if isinstance(f, ast.Name):
145
+ func_parts.insert(0, f.id)
146
+ if not func_parts or not func_parts[0].startswith("deck"):
147
+ return
148
+
149
+ instrument = ".".join(func_parts[:-1])
150
+ action = func_parts[-1]
151
+
152
+ args = {}
153
+ arg_types = {}
154
+
155
+ for kw in node.keywords:
156
+ if kw.arg is None and isinstance(kw.value, ast.Dict):
157
+ for k_node, v_node in zip(kw.value.keys, kw.value.values):
158
+ key = k_node.s if isinstance(k_node, ast.Constant) else ast.unparse(k_node)
159
+ if isinstance(v_node, ast.Constant):
160
+ value = v_node.value
161
+ elif isinstance(v_node, ast.Name):
162
+ value = f"#{v_node.id}"
163
+ else:
164
+ value = ast.unparse(v_node)
165
+ args[key] = value
166
+ arg_types[key] = infer_type(value)
167
+ else:
168
+ if isinstance(kw.value, ast.Constant):
169
+ value = kw.value.value
170
+ elif isinstance(kw.value, ast.Name):
171
+ value = f"#{kw.value.id}"
172
+ else:
173
+ value = ast.unparse(kw.value)
174
+ args[kw.arg] = value
175
+ arg_types[kw.arg] = (
176
+ self.defined_types.get(kw.value.id, "float")
177
+ if isinstance(kw.value, ast.Name)
178
+ else infer_type(value)
179
+ )
180
+
181
+ add_card({
182
+ "action": action,
183
+ "arg_types": arg_types,
184
+ "args": args,
185
+ "id": new_id(),
186
+ "instrument": instrument,
187
+ "return": ret_var,
188
+ "uuid": generate_uuid()
189
+ })
190
+
191
+ CardVisitor().visit(tree)
192
+ return cards
193
+
194
+
195
+ if __name__ == "__main__":
196
+ test = '''def workflow_dynamic(solid_amount_mg, methanol_amount_ml):
197
+ """
198
+ SDL workflow: dose solid, add methanol, equilibrate, and analyze
199
+
200
+ Args:
201
+ solid_amount_mg (float): Amount of solid to dose in mg
202
+ methanol_amount_ml (float): Amount of methanol to dose in ml
203
+
204
+ Returns:
205
+ dict: Results containing analysis data
206
+ """
207
+ # Step 1: Dose solid material
208
+ deck.sdl.dose_solid(amount_in_mg=solid_amount_mg)
209
+
210
+ # Step 2: Add methanol solvent
211
+ deck.sdl.dose_solvent(solvent_name='Methanol', amount_in_ml=methanol_amount_ml)
212
+
213
+ # Step 3: Equilibrate at room temperature (assuming ~23°C) for 20 seconds
214
+ deck.sdl.equilibrate(temp=23.0, duration=20.0)
215
+
216
+ # Step 4: Analyze the sample
217
+ analysis_results = deck.sdl.analyze(param_1=1, param_2=2)
218
+
219
+ # Brief pause for system stability
220
+ time.sleep(1.0)
221
+
222
+ # Return only analysis results
223
+ return {'analysis_results': analysis_results}
224
+ '''
225
+ print(json.dumps(convert_to_cards(test)))
@@ -62,11 +62,11 @@ class ScriptRunner:
62
62
 
63
63
 
64
64
  def run_script(self, script, repeat_count=1, run_name=None, logger=None, socketio=None, config=None, bo_args=None,
65
- output_path="", compiled=False, current_app=None):
65
+ output_path="", compiled=False, current_app=None, history=None, optimizer=None):
66
66
  global deck
67
67
  if deck is None:
68
68
  deck = global_config.deck
69
-
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
@@ -81,7 +81,8 @@ class ScriptRunner:
81
81
 
82
82
  thread = threading.Thread(
83
83
  target=self._run_with_stop_check,
84
- args=(script, repeat_count, run_name, logger, socketio, config, bo_args, output_path, current_app, compiled)
84
+ args=(script, repeat_count, run_name, logger, socketio, config, bo_args, output_path, current_app, compiled,
85
+ history, optimizer)
85
86
  )
86
87
  thread.start()
87
88
  return thread
@@ -182,7 +183,7 @@ class ScriptRunner:
182
183
  return exec_locals # Return the 'results' variable
183
184
 
184
185
  def _run_with_stop_check(self, script: Script, repeat_count: int, run_name: str, logger, socketio, config, bo_args,
185
- output_path, current_app, compiled):
186
+ output_path, current_app, compiled, history=None, optimizer=None):
186
187
  time.sleep(1)
187
188
  # _func_str = script.compile()
188
189
  # step_list_dict: dict = script.convert_to_lines(_func_str)
@@ -205,7 +206,8 @@ class ScriptRunner:
205
206
  # Run "script" section multiple times
206
207
  if repeat_count:
207
208
  self._run_repeat_section(repeat_count, arg_type, bo_args, output_list, script,
208
- run_name, return_list, compiled, logger, socketio, run_id=run_id)
209
+ run_name, return_list, compiled, logger, socketio,
210
+ history, output_path, run_id=run_id, optimizer=optimizer)
209
211
  elif config:
210
212
  self._run_config_section(config, arg_type, output_list, script, run_name, logger,
211
213
  socketio, run_id=run_id, compiled=compiled)
@@ -262,13 +264,36 @@ class ScriptRunner:
262
264
  output_list.append(output)
263
265
 
264
266
  def _run_repeat_section(self, repeat_count, arg_types, bo_args, output_list, script, run_name, return_list, compiled,
265
- logger, socketio, run_id):
267
+ logger, socketio, history, output_path, run_id, optimizer=None):
266
268
  if bo_args:
267
269
  logger.info('Initializing optimizer...')
268
270
  if compiled:
269
271
  ax_client = bo_campaign.ax_init_opc(bo_args)
270
272
  else:
271
- ax_client = bo_campaign.ax_init_form(bo_args, arg_types)
273
+ if history:
274
+ import pandas as pd
275
+ file_path = os.path.join(output_path, history)
276
+ previous_runs = pd.read_csv(file_path).to_dict(orient='records')
277
+ ax_client = bo_campaign.ax_init_form(bo_args, arg_types, len(previous_runs))
278
+ for row in previous_runs:
279
+ parameter = {key: value for key, value in row.items() if key in arg_types.keys()}
280
+ raw_data = {key: value for key, value in row.items() if key in return_list}
281
+ _, trial_index = ax_client.attach_trial(parameter)
282
+ ax_client.complete_trial(trial_index=trial_index, raw_data=raw_data)
283
+ output_list.append(row)
284
+ else:
285
+ ax_client = bo_campaign.ax_init_form(bo_args, arg_types)
286
+ elif optimizer and history:
287
+ import pandas as pd
288
+ file_path = os.path.join(output_path, history)
289
+
290
+ previous_runs = pd.read_csv(file_path)
291
+ optimizer.append_existing_data(previous_runs)
292
+ for row in previous_runs:
293
+ output_list.append(row)
294
+
295
+
296
+
272
297
  for i_progress in range(int(repeat_count)):
273
298
  if self.stop_pending_event.is_set():
274
299
  logger.info(f'Stopping execution during {run_name}: {i_progress + 1}/{int(repeat_count)}')
@@ -290,6 +315,17 @@ class ScriptRunner:
290
315
  except Exception as e:
291
316
  logger.info(f'Optimization error: {e}')
292
317
  break
318
+ elif optimizer:
319
+ try:
320
+ parameters = optimizer.suggest(1)
321
+ logger.info(f'Output value: {parameters}')
322
+ output = self.exec_steps(script, "script", logger, socketio, run_id, i_progress, **parameters)
323
+ if output:
324
+ optimizer.observe(output)
325
+ output.update(parameters)
326
+ except Exception as e:
327
+ logger.info(f'Optimization error: {e}')
328
+ break
293
329
  else:
294
330
  # fname = f"{run_name}_script"
295
331
  # function = self.globals_dict[fname]
@@ -298,6 +334,12 @@ class ScriptRunner:
298
334
  if output:
299
335
  output_list.append(output)
300
336
  logger.info(f'Output value: {output}')
337
+
338
+ if bo_args:
339
+ ax_client.save_to_json_file(os.path.join(output_path, f"{run_name}_ax_client.json"))
340
+ logger.info(
341
+ f'Optimization complete. Results saved to {os.path.join(output_path, f"{run_name}_ax_client.json")}'
342
+ )
301
343
  return output_list
302
344
 
303
345
  @staticmethod