ivoryos 1.3.8__py3-none-any.whl → 1.4.0__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/optimizer/ax_optimizer.py +44 -25
- ivoryos/optimizer/base_optimizer.py +15 -1
- ivoryos/optimizer/baybe_optimizer.py +24 -17
- ivoryos/optimizer/nimo_optimizer.py +26 -24
- ivoryos/routes/data/data.py +27 -9
- ivoryos/routes/data/templates/components/step_card.html +47 -11
- ivoryos/routes/data/templates/workflow_view.html +14 -5
- ivoryos/routes/design/design.py +31 -1
- ivoryos/routes/design/design_step.py +2 -1
- ivoryos/routes/design/templates/components/edit_action_form.html +16 -3
- ivoryos/routes/design/templates/components/python_code_overlay.html +27 -10
- ivoryos/routes/design/templates/experiment_builder.html +1 -0
- ivoryos/routes/execute/execute.py +36 -7
- ivoryos/routes/execute/templates/components/run_tabs.html +45 -2
- ivoryos/routes/execute/templates/components/tab_bayesian.html +447 -325
- ivoryos/routes/execute/templates/components/tab_configuration.html +303 -18
- ivoryos/routes/execute/templates/components/tab_repeat.html +6 -2
- ivoryos/routes/execute/templates/experiment_run.html +0 -264
- ivoryos/socket_handlers.py +1 -1
- ivoryos/static/js/action_handlers.js +252 -118
- ivoryos/static/js/sortable_design.js +1 -0
- ivoryos/utils/bo_campaign.py +17 -16
- ivoryos/utils/db_models.py +122 -18
- ivoryos/utils/decorators.py +1 -0
- ivoryos/utils/form.py +22 -5
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/script_runner.py +447 -147
- ivoryos/utils/utils.py +11 -1
- ivoryos/version.py +1 -1
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/METADATA +3 -2
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/RECORD +34 -33
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/WHEEL +0 -0
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -23,11 +23,24 @@
|
|
|
23
23
|
{% else %}
|
|
24
24
|
{{ forms.hidden_tag() }}
|
|
25
25
|
{% for field in forms %}
|
|
26
|
+
|
|
26
27
|
{% if field.type not in ['CSRFTokenField'] %}
|
|
27
28
|
<div class="input-group mb-3">
|
|
28
|
-
<label class="input-group-text">{{ field.label.text }}</label>
|
|
29
|
-
{
|
|
30
|
-
|
|
29
|
+
<label class="input-group-text">{{ field.label.text | format_name }}</label>
|
|
30
|
+
{% if field.type == "BooleanField" %}
|
|
31
|
+
{{ field(class="form-check-input") }}
|
|
32
|
+
{% elif field.type == "FlexibleEnumField" %}
|
|
33
|
+
<input type="text" id="{{ field.id }}" name="{{ field.name }}" value="{{ field.data }}"
|
|
34
|
+
list="{{ field.id }}_options" placeholder="{{ field.render_kw.placeholder if field.render_kw and field.render_kw.placeholder }}"
|
|
35
|
+
class="form-control">
|
|
36
|
+
<datalist id="{{ field.id }}_options">
|
|
37
|
+
{% for key in field.choices %}
|
|
38
|
+
<option value="{{ key }}">{{ key }}</option>
|
|
39
|
+
{% endfor %}
|
|
40
|
+
</datalist>
|
|
41
|
+
{% else %}
|
|
42
|
+
{{ field(class="form-control") }}
|
|
43
|
+
{% endif %}
|
|
31
44
|
</div>
|
|
32
45
|
{% endif %}
|
|
33
46
|
{% endfor %}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
{# Python code overlay component #}
|
|
2
|
-
{#{% if session.get('show_code') %}#}
|
|
1
|
+
{# Python code overlay component - Pure HTML #}
|
|
3
2
|
<style>
|
|
4
3
|
.code-overlay {
|
|
5
4
|
position: fixed;
|
|
@@ -16,7 +15,7 @@
|
|
|
16
15
|
transform: translateX(0);
|
|
17
16
|
}
|
|
18
17
|
</style>
|
|
19
|
-
|
|
18
|
+
|
|
20
19
|
{% if session.get('show_code') %}
|
|
21
20
|
<div id="pythonCodeOverlay" class="code-overlay bg-light border-start show">
|
|
22
21
|
{% else %}
|
|
@@ -29,11 +28,29 @@
|
|
|
29
28
|
</button>
|
|
30
29
|
</div>
|
|
31
30
|
<div class="overlay-content p-3">
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
<!-- Top toggles (Single / Batch) -->
|
|
32
|
+
<div class="btn-group mb-3">
|
|
33
|
+
<button type="button" class="btn btn-outline-primary mode-toggle active" data-mode="single">Single</button>
|
|
34
|
+
<button type="button" class="btn btn-outline-primary mode-toggle" data-mode="batch">Batch</button>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
{# <!-- Secondary toggles (By Sample / By Step) TODO -->#}
|
|
38
|
+
{# <div class="btn-group mb-3" id="batch-options" style="display: none;">#}
|
|
39
|
+
{# <button type="button" class="btn btn-outline-secondary batch-toggle active" data-batch="sample">By Sample</button>#}
|
|
40
|
+
{# <button type="button" class="btn btn-outline-secondary batch-toggle" data-batch="step">By Step</button>#}
|
|
41
|
+
{# </div>#}
|
|
42
|
+
|
|
43
|
+
<!-- Code display -->
|
|
44
|
+
<pre><code id="python-code" class="language-python">Loading...</code></pre>
|
|
45
|
+
|
|
46
|
+
<!-- Action buttons -->
|
|
47
|
+
<div class="mt-2">
|
|
48
|
+
<button type="button" id="copy-code" class="btn btn-sm btn-outline-success">
|
|
49
|
+
<i class="bi bi-clipboard"></i> Copy
|
|
50
|
+
</button>
|
|
51
|
+
<button type="button" id="download-code" class="btn btn-sm btn-outline-primary">
|
|
52
|
+
<i class="bi bi-download"></i> Download
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
38
55
|
</div>
|
|
39
|
-
</div>
|
|
56
|
+
</div>
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
const scriptStepUrl = `{{ url_for('design.design_steps.get_step', uuid=0) }}`;
|
|
33
33
|
const scriptStepDupUrl = `{{ url_for('design.design_steps.duplicate_action', uuid=0) }}`;
|
|
34
34
|
const scriptDeleteUrl = "{{ url_for('design.clear_draft') }}";
|
|
35
|
+
const scriptCompileUrl = "{{ url_for('design.compile_preview') }}";
|
|
35
36
|
</script>
|
|
36
37
|
<script src="{{ url_for('static', filename='js/sortable_design.js') }}"></script>
|
|
37
38
|
<script src="{{ url_for('static', filename='js/action_handlers.js') }}"></script>
|
|
@@ -76,14 +76,17 @@ def experiment_run():
|
|
|
76
76
|
if isinstance(exec_string, dict):
|
|
77
77
|
for key, func_str in exec_string.items():
|
|
78
78
|
exec(func_str)
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
else:
|
|
81
81
|
# Handle string case - you might need to adjust this based on your needs
|
|
82
|
-
line_collection =
|
|
82
|
+
line_collection = {}
|
|
83
83
|
except Exception:
|
|
84
84
|
flash(f"Please check syntax!!")
|
|
85
85
|
return redirect(url_for("design.experiment_builder"))
|
|
86
86
|
|
|
87
|
+
|
|
88
|
+
line_collection = script.render_script_lines(script.script_dict)
|
|
89
|
+
|
|
87
90
|
run_name = script.name if script.name else "untitled"
|
|
88
91
|
|
|
89
92
|
dismiss = session.get("dismiss", None)
|
|
@@ -92,8 +95,13 @@ def experiment_run():
|
|
|
92
95
|
|
|
93
96
|
_, return_list = script.config_return()
|
|
94
97
|
config_list, config_type_list = script.config("script")
|
|
95
|
-
data_list = os.listdir(current_app.config['DATA_FOLDER'])
|
|
96
|
-
|
|
98
|
+
data_list = [f for f in os.listdir(current_app.config['DATA_FOLDER']) if f.endswith('.csv')]
|
|
99
|
+
# Remove .gitkeep if present
|
|
100
|
+
if ".gitkeep" in data_list:
|
|
101
|
+
data_list.remove(".gitkeep")
|
|
102
|
+
|
|
103
|
+
# Sort by creation time, newest first
|
|
104
|
+
data_list.sort(key=lambda f: os.path.getctime(os.path.join(current_app.config['DATA_FOLDER'], f)), reverse=True)
|
|
97
105
|
|
|
98
106
|
if deck is None:
|
|
99
107
|
no_deck_warning = True
|
|
@@ -115,21 +123,26 @@ def experiment_run():
|
|
|
115
123
|
elif "parameters" in payload_json:
|
|
116
124
|
bo_args = payload_json
|
|
117
125
|
repeat = payload_json.pop("repeat", None)
|
|
126
|
+
batch_size = payload_json.pop('batch_size', 1)
|
|
118
127
|
else:
|
|
119
128
|
if "bo" in request.form:
|
|
120
129
|
bo_args = request.form.to_dict()
|
|
121
130
|
existing_data = bo_args.pop("existing_data")
|
|
122
131
|
if "online-config" in request.form:
|
|
123
|
-
|
|
132
|
+
config_args = request.form.to_dict()
|
|
133
|
+
config_args.pop("batch_size", None)
|
|
134
|
+
config = utils.web_config_entry_wrapper(config_args, config_list)
|
|
135
|
+
batch_size = int(request.form.get('batch_size', 1))
|
|
124
136
|
repeat = request.form.get('repeat', None)
|
|
125
137
|
|
|
126
138
|
try:
|
|
139
|
+
# if True:
|
|
127
140
|
datapath = current_app.config["DATA_FOLDER"]
|
|
128
141
|
run_name = script.validate_function_name(run_name)
|
|
129
142
|
runner.run_script(script=script, run_name=run_name, config=config, bo_args=bo_args,
|
|
130
143
|
logger=g.logger, socketio=g.socketio, repeat_count=repeat,
|
|
131
144
|
output_path=datapath, compiled=compiled, history=existing_data,
|
|
132
|
-
current_app=current_app._get_current_object()
|
|
145
|
+
current_app=current_app._get_current_object(), batch_size=batch_size
|
|
133
146
|
)
|
|
134
147
|
if utils.check_config_duplicate(config):
|
|
135
148
|
flash(f"WARNING: Duplicate in config entries.")
|
|
@@ -176,19 +189,35 @@ def run_bo():
|
|
|
176
189
|
repeat = payload.pop("repeat", None)
|
|
177
190
|
optimizer_type = payload.pop("optimizer_type", None)
|
|
178
191
|
existing_data = payload.pop("existing_data", None)
|
|
192
|
+
batch_mode = payload.pop("batch_mode", None)
|
|
193
|
+
batch_size = payload.pop("batch_size", 1)
|
|
194
|
+
|
|
195
|
+
# Get constraint expressions (new single-line input)
|
|
196
|
+
constraint_exprs = request.form.getlist("constraint_expr")
|
|
197
|
+
constraints = [expr.strip() for expr in constraint_exprs if expr.strip()]
|
|
198
|
+
|
|
199
|
+
# Remove constraint_expr entries from payload before parsing parameters
|
|
200
|
+
for key in list(payload.keys()):
|
|
201
|
+
if key.startswith("constraint_expr"):
|
|
202
|
+
payload.pop(key, None)
|
|
203
|
+
|
|
179
204
|
parameters, objectives, steps = parse_optimization_form(payload)
|
|
180
205
|
try:
|
|
206
|
+
|
|
207
|
+
# if True:
|
|
181
208
|
datapath = current_app.config["DATA_FOLDER"]
|
|
182
209
|
run_name = script.validate_function_name(run_name)
|
|
183
210
|
Optimizer = global_config.optimizers.get(optimizer_type, None)
|
|
184
211
|
if not Optimizer:
|
|
185
212
|
raise ValueError(f"Optimizer {optimizer_type} is not supported or not found.")
|
|
186
213
|
optimizer = Optimizer(experiment_name=run_name, parameter_space=parameters, objective_config=objectives,
|
|
214
|
+
parameter_constraints = constraints,
|
|
187
215
|
optimizer_config=steps, datapath=datapath)
|
|
188
216
|
runner.run_script(script=script, run_name=run_name, optimizer=optimizer,
|
|
189
217
|
logger=g.logger, socketio=g.socketio, repeat_count=repeat,
|
|
190
218
|
output_path=datapath, compiled=False, history=existing_data,
|
|
191
|
-
current_app=current_app._get_current_object()
|
|
219
|
+
current_app=current_app._get_current_object(), batch_mode=batch_mode, batch_size=int(batch_size),
|
|
220
|
+
objectives=objectives
|
|
192
221
|
)
|
|
193
222
|
|
|
194
223
|
except Exception as e:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- Main template structure -->
|
|
2
2
|
<ul class="nav nav-tabs" id="myTabs" role="tablist">
|
|
3
3
|
<li class="nav-item" role="presentation">
|
|
4
4
|
<a class="nav-link {{ 'disabled' if config_list else '' }} {{ 'active' if not config_list else '' }}" id="tab1-tab" data-bs-toggle="tab" href="#tab1" role="tab" aria-controls="tab1" aria-selected="false">Repeat</a>
|
|
@@ -14,4 +14,47 @@
|
|
|
14
14
|
{% include 'components/tab_repeat.html' %}
|
|
15
15
|
{% include 'components/tab_configuration.html' %}
|
|
16
16
|
{% include 'components/tab_bayesian.html' %}
|
|
17
|
-
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<!-- ============================================ -->
|
|
20
|
+
<!-- SWITCH TO THE LAST ACTIVE TAB ON PAGE LOAD -->
|
|
21
|
+
<!-- ============================================ -->
|
|
22
|
+
<script>
|
|
23
|
+
(function() {
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
// Store active tab when changed
|
|
27
|
+
document.addEventListener('shown.bs.tab', function(e) {
|
|
28
|
+
const tabId = e.target.id.replace('-tab', '');
|
|
29
|
+
if (tabId === 'tab2' || tabId === 'tab3') {
|
|
30
|
+
localStorage.setItem('ivoryosLastTab', tabId);
|
|
31
|
+
console.log('Saved last active tab:', tabId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Optional: run specific load logic per tab
|
|
35
|
+
if (tabId === 'tab2' && typeof window.loadConfigData === 'function') {
|
|
36
|
+
window.loadConfigData();
|
|
37
|
+
} else if (tabId === 'tab3' && typeof window.saveBO === 'function') {
|
|
38
|
+
window.saveBO();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Restore tab from last session
|
|
43
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
44
|
+
const lastTab = localStorage.getItem('ivoryosLastTab');
|
|
45
|
+
const tabElement = lastTab ? document.getElementById(lastTab + '-tab') : null;
|
|
46
|
+
|
|
47
|
+
if (tabElement && !tabElement.classList.contains('disabled')) {
|
|
48
|
+
new bootstrap.Tab(tabElement).show();
|
|
49
|
+
console.log('Restored last active tab:', lastTab);
|
|
50
|
+
} else {
|
|
51
|
+
// Default to first non-disabled tab
|
|
52
|
+
const firstTab = document.querySelector('#myTabs .nav-link:not(.disabled)');
|
|
53
|
+
if (firstTab) {
|
|
54
|
+
new bootstrap.Tab(firstTab).show();
|
|
55
|
+
console.log('Defaulted to first available tab.');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
})();
|
|
60
|
+
</script>
|