Оптимизируем параметры трубопровода
Оптимизация геометрии трубопровода
В этом проекте мы оптимизируем параметры геометрии трубопровода для достижения нужных параметров потока.
Рассмотрим простую модель, где в трубопроводе происходит Т-образное разветвление, после которого идут две изогнутые трубы. Меняя их угол изгиба в диапазоне от 0 до 120 градусов мы будем добиваться заданной разницы расходов через каждое ответвление системы.
Описание модели
Загрузим несколько библиотек:
Pkg.add("Optim")
using DataFrames, CSV, Optim
Откроем нашу модель.
engee.open( "$(@__DIR__)/" * "pipe_circuit_optimization.engee");

Когда изгиб обеих труб одинаковый, через каждую трубу идет одинаковый расход жидкости, равный половине входного потока.
Оптимизация параметров модели
Сформулируем задачу довольно простым образом: мы заранее запустили модель, в которой изгиб труб был равен 30 и 60 градусов. Полученные значения выставим в качестве целевых критериев оптимизации (расход 150.6 и 136.6 кг/с) и установим начальные значения изгиба каждой трубы равными 90 градусов.
Задача оптимизации потребует определения свободных переменных ("независимых", значения которых мы будем постепенно менять) и зависимых переменных (с целевыми значениями).
Свободные переменные – это некоторые внутренние свойства некоторых блоков с целевыми значениями и физическими ограничениями.
cd( @__DIR__ )
adj = CSV.read("adjustable_parameters.csv", DataFrame)
Зависимые переменные – это желаемые значения некоторых сигналов в финальный момент времени.
tgt = CSV.read("target_parameters.csv", DataFrame)
Запустим процесс оптимизации, в ходе которого мы будем искать значения свободных параметров adj (adjustable), позволяющие добиться нужных значений зависимых параметров tgt (target).
using Optim
function f(x)
# Выставим значения параметров блока (свободные переменные)
for (i, (block, param, unit, _)) in enumerate(eachrow(adj))
engee.set_param!( String(block), String(param) => Dict("value" => string(x[i]), "unit" => String(unit)) )
end
# Выполним модель с новыми параметрами
data = engee.run("pipe_circuit_optimization");
# Получим значения зависимых переменных в последний момент времени
out_vector = [data[String(name)].value[end] for name in tgt.names]
# Вычислим критерий оптимизации
return sum((out_vector .- tgt.values) .^ 2)
end
# Оптимизатор граничных условий
inner_optimizer = GradientDescent()
# Общие настройки процесса оптимизации
options = Optim.Options(iterations = 4, outer_iterations = 4,
x_abstol = 5.0, outer_x_abstol = 5.0,
store_trace=true, trace_simplex=true, extended_trace=true )
# Запускаем оптимизацию
result = optimize(f, adj.lower, adj.upper, adj.x0, Fminbox(inner_optimizer), options)

Напоследок убедимся, что поиск решения действительно шёл в правильном направлении:
gr()
loss_value = getfield.( result.trace, :value )
xs = first.( trace_point.metadata["x"] for trace_point in Optim.trace(result) );
ys = last.( trace_point.metadata["x"] for trace_point in Optim.trace(result) );
plot(
plot(loss_value, c=:Black, left_margin=10Plots.mm, bottom_margin=10Plots.mm, xlabel="Итерации", ylabel="Критерий оптимизации", title="График хода оптимизации (критерий)"),
plot( xs, ys, c=:Black, markershape=:+, mc=:Red, bottom_margin=10Plots.mm, xlabel=adj.blocks[1], ylabel=adj.blocks[2], title="Траектория процесса оптимизации" ),
leg=false, size=(900,300), guidefont=font(6), titlefont=font(9)
)
Заключение
Мы изучили, каким образом можно обернуть модель в высокоуровневую функцию оптимизации и теперь можем перебирать переменные для оптимизации и выбирать оптимизаторы.
