Приложение: Простой планировщик экспериментов
作者
using GenieFramework, StippleUI, DelimitedFiles
@app begin
# Входные данные
@in free_vars_input = ""
@in output_vars_input = ""
@in press_btn = false
@in save_settings_btn = false
@in load_settings_btn = false
@out status_message = "Нажмите 'Загрузить' для восстановления настроек"
@out progress_info = ""
@out results_display = ""
# Обработчик кнопки загрузки настроек
@onbutton load_settings_btn begin
try
saved_free = engee.eval("_em_free_vars")
if saved_free != nothing && saved_free != ""
free_vars_input = saved_free
status_message = "✓ Настройки загружены"
else
status_message = "Нет сохраненных настроек"
end
catch
status_message = "Нет сохраненных настроек"
end
try
saved_output = engee.eval("_em_output_vars")
if saved_output != nothing && saved_output != ""
output_vars_input = saved_output
end
catch
# Переменная не существует
end
end
# Обработчик кнопки сохранения настроек
@onbutton save_settings_btn begin
try
engee.eval("_em_free_vars = \"\"\"$free_vars_input\"\"\"")
engee.eval("_em_output_vars = \"\"\"$output_vars_input\"\"\"")
status_message = "✓ Настройки сохранены"
catch e
status_message = "✗ Ошибка сохранения: $e"
end
end
# Парсер YAML-подобного синтаксиса
function parse_yaml(text)
keys = String[]
values = String[]
for line in split(text, "\n")
line = strip(line)
isempty(line) && continue
startswith(line, "#") && continue
parts = split(line, ":"; limit=2)
length(parts) != 2 && continue
key = strip(parts[1])
value = strip(parts[2])
value = replace(value, r"^[\"']|[\"']$" => "")
push!(keys, key)
push!(values, value)
end
return keys, values
end
# Обработчик кнопки запуска
@onbutton press_btn begin
try
status_message = "Запуск эксперимента..."
progress_info = "Парсинг входных данных..."
# Парсим свободные переменные
free_keys, free_values = parse_yaml(free_vars_input)
var_names = String[]
var_ranges = Vector[]
for i in 1:length(free_keys)
push!(var_names, free_keys[i])
result = engee.eval("collect($(free_values[i]))")
push!(var_ranges, [result...])
end
# Парсим выходные переменные
output_keys, output_values = parse_yaml(output_vars_input)
output_vars = String[]
output_ops = String[]
for i in 1:length(output_keys)
push!(output_vars, output_keys[i])
push!(output_ops, output_values[i])
end
if isempty(var_names)
status_message = "Ошибка: не заданы входные переменные"
return
end
if isempty(output_vars)
status_message = "Ошибка: не заданы выходные переменные"
return
end
# Вычисляем длины диапазонов
range_lengths = Int[length(r) for r in var_ranges]
total_combinations = 1
for len in range_lengths
total_combinations *= len
end
progress_info = "Генерация $total_combinations комбинаций..."
# Генерируем все комбинации входных данных
all_combinations = Vector{Float64}[]
indices = ones(Int, length(var_ranges))
finished = false
while !finished
current_values = [Float64(var_ranges[i][indices[i]]) for i in 1:length(var_ranges)]
push!(all_combinations, current_values)
carry = 1
for dim in length(indices):-1:1
indices[dim] += carry
if indices[dim] > range_lengths[dim]
indices[dim] = 1
carry = 1
else
carry = 0
break
end
end
if carry == 1
finished = true
end
end
# Инициализируем матрицу результатов
output_col_names = [string(output_vars[i], "_", output_ops[i]) for i in 1:length(output_vars)]
all_col_names = vcat(var_names, output_col_names)
total_cols = length(all_col_names)
results_matrix = zeros(total_combinations, total_cols)
# Заполняем входные данные
for i in 1:total_combinations
for j in 1:length(var_names)
results_matrix[i, j] = all_combinations[i][j]
end
end
progress_info = "Начало эксперимента: 0 из $total_combinations..."
# Запускаем модель для каждой комбинации
for combination_num in 1:total_combinations
current_values = all_combinations[combination_num]
if combination_num % max(1, div(total_combinations, 100)) == 0
progress_info = "Выполнение $combination_num из $total_combinations ($(round(100*combination_num/total_combinations))%)..."
end
# Устанавливаем значения переменных в Engee
for j in 1:length(var_names)
engee.eval("$(var_names[j]) = $(current_values[j])")
end
# Запускаем модель
engee.eval("data = engee.run();")
# Извлекаем результаты
for (j, out_var) in enumerate(output_vars)
values = engee.eval("data[\"$out_var\"].value")
op = output_ops[j]
result_value = if op == "last"
Float64(values[end])
elseif op == "mean"
Float64(sum(values) / length(values))
elseif op == "max"
Float64(maximum(values))
elseif op == "min"
Float64(minimum(values))
elseif op == "first"
Float64(values[1])
else
Float64(values[end])
end
results_matrix[combination_num, length(var_names) + j] = result_value
end
# Сохраняем промежуточные результаты в Engee
if combination_num % 10 == 0 || combination_num == total_combinations
engee.eval("_em_experiment_data = $results_matrix")
engee.eval("_em_column_names = $all_col_names")
end
end
progress_info = "Сохранение результатов в CSV..."
# Сохраняем финальную матрицу в Engee
engee.eval("_em_experiment_data = $results_matrix")
engee.eval("_em_column_names = $all_col_names")
# Сохраняем CSV напрямую из Genie (не через engee.eval)
open("experiment_results.csv", "w") do io
# Записываем заголовок
write(io, join(all_col_names, ",") * "\n")
# Записываем данные
writedlm(io, results_matrix, ',')
end
progress_info = "Формирование отчета..."
# Отображаем результаты
results_display = "✓ Эксперимент завершен!\n\n"
results_display *= "Входные переменные: $(join(var_names, ", "))\n"
results_display *= "Выходные переменные: $(join(output_vars, ", "))\n"
results_display *= "Комбинаций: $total_combinations\n"
results_display *= "Результаты сохранены в experiment_results.csv\n"
results_display *= "Матрица доступна как _em_experiment_data\n\n"
show_count = min(5, total_combinations)
if show_count > 0
results_display *= "Первые $show_count результатов:\n\n"
# Заголовок
header = join(rpad.(var_names, 8)) * " │ " * join(rpad.(output_col_names, 12))
results_display *= header * "\n"
results_display *= repeat("─", length(header)) * "\n"
for row in 1:show_count
# Входные переменные
input_parts = String[]
for col in 1:length(var_names)
push!(input_parts, rpad(string(round(results_matrix[row, col], digits=4)), 8))
end
input_str = join(input_parts)
# Выходные переменные
output_parts = String[]
for col in (length(var_names)+1):total_cols
push!(output_parts, rpad(string(round(results_matrix[row, col], digits=4)), 12))
end
output_str = join(output_parts)
results_display *= input_str * " │ " * output_str * "\n"
end
end
status_message = "✓ Эксперимент успешно завершен!"
progress_info = "Готово! Выполнено $total_combinations комбинаций."
catch e
status_message = "✗ Ошибка: $e"
progress_info = "Эксперимент прерван! Проверьте правильность ввода."
end
end
end
function ui()
# Основной контейнер
Html.div([
# Заголовок с иконкой
row([
cell(class="col", [
h5("План эксперимента", style="margin: 0; color: #1565C0; font-weight: 600;")
])
], style="margin-bottom: 20px; align-items: center;"),
# Свободные переменные
Html.div([
Html.div(style="display: flex; align-items: center; margin-bottom: 5px;", [
icon("tune", style="font-size: 18px; color: #FF9800; margin-right: 8px;"),
span("Свободные переменные", style="font-weight: 600; font-size: 14px; color: #455A64;")
]),
textarea("", :free_vars_input,
rows=4,
placeholder="In1: 1:10\nIn2: [1,4,5,8]\nIn3: 20:5:100",
style="width: 100%; font-family: 'Fira Code', monospace; font-size: 13px;
background: linear-gradient(135deg, #fffef9 0%, #fffde7 100%);
border: none; border-radius: 8px; padding: 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);")
], style="margin-bottom: 15px;"),
# Выходные переменные
Html.div([
Html.div(style="display: flex; align-items: center; margin-bottom: 5px;", [
icon("assessment", style="font-size: 18px; color: #4CAF50; margin-right: 8px;"),
span("Выходные переменные", style="font-weight: 600; font-size: 14px; color: #455A64;")
]),
textarea("", :output_vars_input,
rows=3,
placeholder="Out1: last\nOut2: mean\nOut3: max",
style="width: 100%; font-family: 'Fira Code', monospace; font-size: 13px;
background: linear-gradient(135deg, #f5fff9 0%, #e8f5e9 100%);
border: none; border-radius: 8px; padding: 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);")
], style="margin-bottom: 15px;"),
# Кнопки управления
row([
cell(class="col-2", [
btn([
icon("file_upload", style="font-size: 20px; color: #1565C0;")
],
@click(:load_settings_btn),
title="Загрузить настройки",
style="width: 100%; background: linear-gradient(135deg, #E3F2FD 0%, #BBDEFB 100%);
padding: 12px 5px; font-size: 15px;
border: none; border-radius: 8px; box-shadow: 0 2px 4px rgba(66,165,245,0.2);")
]),
cell(class="col-8", [
btn("ЗАПУСТИТЬ ПРОГОН",
@click(:press_btn),
style="width: 100%; background: linear-gradient(0deg, #1A911A 0%, #7DE391 100%);
color: white; padding: 12px; font-size: 15px; font-weight: 700;
border: none; border-radius: 8px; box-shadow: 0 2px 6px rgba(76,175,80,0.3);
letter-spacing: 1px;")
]),
cell(class="col-2", [
btn([
icon("file_download", style="font-size: 20px; color: #E65100;")
],
@click(:save_settings_btn),
title="Сохранить настройки",
style="width: 100%; background: linear-gradient(135deg, #FFF3E0 0%, #FFE0B2 100%);
padding: 12px 5px; font-size: 15px;
border: none; border-radius: 8px; box-shadow: 0 2px 4px rgba(255,167,38,0.2);")
])
], style="margin-bottom: 15px;"),
# Статус и прогресс
Html.div([
p("{{status_message}}", style="font-weight: 600; margin: 5px 0; font-size: 14px;
padding: 8px 12px; border-radius: 6px;
background: rgba(255,255,255,0.8);
backdrop-filter: blur(5px);
color: #37474F;"),
p("{{progress_info}}", style="color: #607D8B; margin: 0; font-size: 12px; font-style: italic; padding: 0 12px;")
], style="margin-bottom: 15px;"),
# Результаты
Html.div([
Html.div(style="display: flex; align-items: center; margin-bottom: 5px;", [
icon("table_chart", style="font-size: 18px; color: #7B1FA2; margin-right: 8px;"),
span("Результаты", style="font-weight: 600; font-size: 14px; color: #455A64;")
]),
pre("{{results_display}}",
style="background: rgba(255,255,255,0.9);
padding: 12px; border-radius: 8px; white-space: pre-wrap;
font-family: 'Fira Code', monospace; font-size: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
max-height: 300px; overflow-y: auto;
color: #37474F;")
])
], style="background: linear-gradient(135deg, #edeff7 0%, #fafcfe 100%); padding: 20px; border-radius: 12px; min-height: 100vh;")
end
@page("/", ui)