Документация Engee

Нелинейная оптимизация с ограничениями

Интерфейс нелинейной оптимизации с ограничениями в Optim предполагает, что пользователь может написать задачу оптимизации следующим образом.

Для ограничений в виде равенства для или вы устанавливаете эти конкретные записи границ равными, . Аналогично, задание или означает, что ограничение не ограничено снизу или сверху, соответственно.

Оптимизация с ограничениями с IPNewton

Мы рассмотрим примеры использования интерфейса ограничений с алгоритмом оптимизации Ньютона с внутренней точкой IPNewton.

В этих примерах мы работаем со стандартной функцией Розенброка. Цель и ее производные определяются следующим образом:

fun(x) =  (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2

function fun_grad!(g, x)
g[1] = -2.0 * (1.0 - x[1]) - 400.0 * (x[2] - x[1]^2) * x[1]
g[2] = 200.0 * (x[2] - x[1]^2)
end

function fun_hess!(h, x)
h[1, 1] = 2.0 - 400.0 * x[2] + 1200.0 * x[1]^2
h[1, 2] = -400.0 * x[1]
h[2, 1] = -400.0 * x[1]
h[2, 2] = 200.0
end;

Интерфейс оптимизации

Для решения задачи оптимизации с ограничениями мы вызываем метод optimize.

optimize(d::AbstractObjective, constraints::AbstractConstraints, initial_x::Tx, method::ConstrainedOptimizer, options::Options)

Мы можем создать экземпляры AbstractObjective и AbstractConstraints, используя типы TwiceDifferentiable и TwiceDifferentiableConstraints из пакета NLSolversBase.jl.

Минимизация блока

Мы хотим оптимизировать функцию Розенброка в блоке , начиная с точки . Ограничения блока заданы, например, с помощью TwiceDifferentiableConstraints(lx, ux).

x0 = [0.0, 0.0]
df = TwiceDifferentiable(fun, fun_grad!, fun_hess!, x0)

lx = [-0.5, -0.5]; ux = [0.5, 0.5]
dfc = TwiceDifferentiableConstraints(lx, ux)

res = optimize(df, dfc, x0, IPNewton())
 * Status: success

 * Candidate solution
    Final objective value:     2.500000e-01

 * Found with
    Algorithm:     Interior Point Newton

 * Convergence measures
    |x - x'|               = 4.39e-10 ≰ 0.0e+00
    |x - x'|/|x'|          = 8.79e-10 ≰ 0.0e+00
    |f(x) - f(x')|         = 0.00e+00 ≤ 0.0e+00
    |f(x) - f(x')|/|f(x')| = 0.00e+00 ≤ 0.0e+00
    |g(x)|                 = 1.00e+00 ≰ 1.0e-08

 * Work counters
    Seconds run:   1  (vs limit Inf)
    Iterations:    43
    f(x) calls:    68
    ∇f(x) calls:   68

Как и в случае с остальной частью Optim, вы также можете использовать autodiff=:forward и просто передать fun.

Чтобы задать только нижние границы, используйте ux = fill(Inf, 2).

ux = fill(Inf, 2)
dfc = TwiceDifferentiableConstraints(lx, ux)

clear!(df)
res = optimize(df, dfc, x0, IPNewton())
 * Status: success (objective increased between iterations)

 * Candidate solution
    Final objective value:     7.987239e-20

 * Found with
    Algorithm:     Interior Point Newton

 * Convergence measures
    |x - x'|               = 3.54e-10 ≰ 0.0e+00
    |x - x'|/|x'|          = 3.54e-10 ≰ 0.0e+00
    |f(x) - f(x')|         = 2.40e-19 ≰ 0.0e+00
    |f(x) - f(x')|/|f(x')| = 3.00e+00 ≰ 0.0e+00
    |g(x)|                 = 8.83e-09 ≤ 1.0e-08

 * Work counters
    Seconds run:   0  (vs limit Inf)
    Iterations:    35
    f(x) calls:    63
    ∇f(x) calls:   63

Определение задач без ограничений

Задачу без ограничений можно определить, передав либо границы Inf, либо пустые массивы. Обратите внимание, что мы должны передать правильную информацию о типе в пустые lx и ux

lx = fill(-Inf, 2); ux = fill(Inf, 2)
dfc = TwiceDifferentiableConstraints(lx, ux)

clear!(df)
res = optimize(df, dfc, x0, IPNewton())

lx = Float64[]; ux = Float64[]
dfc = TwiceDifferentiableConstraints(lx, ux)

clear!(df)
res = optimize(df, dfc, x0, IPNewton())
 * Status: success

 * Candidate solution
    Final objective value:     5.998937e-19

 * Found with
    Algorithm:     Interior Point Newton

 * Convergence measures
    |x - x'|               = 1.50e-09 ≰ 0.0e+00
    |x - x'|/|x'|          = 1.50e-09 ≰ 0.0e+00
    |f(x) - f(x')|         = 1.80e-18 ≰ 0.0e+00
    |f(x) - f(x')|/|f(x')| = 3.00e+00 ≰ 0.0e+00
    |g(x)|                 = 7.92e-09 ≤ 1.0e-08

 * Work counters
    Seconds run:   0  (vs limit Inf)
    Iterations:    34
    f(x) calls:    63
    ∇f(x) calls:   63

Универсальные нелинейные ограничения

Теперь рассмотрим задачу Розенброка с ограничением в

Мы передаем информацию об ограничениях в optimize, определяя вектор-функцию c(x) и ее якобиан J(x).

Информация о гессиане обрабатывается по-другому, и при этом принимается во внимание лагранжиан соответствующей преобразованной задачи оптимизации со слабыми переменными. Процесс похож на работу библиотеки CUTEst. Пусть представляет собой гессиан -й компоненты универсальных ограничений, а  — соответствующую двойственную переменную в лагранжиане. Затем нам нужно, чтобы объект constraint добавил значения к гессиану цели, взвешенному с помощью .

Далее форма Julia для заданной функции и информация о производной добавляются следующим образом.

con_c!(c, x) = (c[1] = x[1]^2 + x[2]^2; c)
function con_jacobian!(J, x)
    J[1,1] = 2*x[1]
    J[1,2] = 2*x[2]
    J
end
function con_h!(h, x, λ)
    h[1,1] += λ[1]*2
    h[2,2] += λ[1]*2
end;

Обратите внимание, что con_h! добавляет λ-взвешенное значение гессиана каждого элемента c(x) к гессиану fun.

Затем мы можем оптимизировать функцию Розенброка внутри шара радиусом .

lx = Float64[]; ux = Float64[]
lc = [-Inf]; uc = [0.5^2]
dfc = TwiceDifferentiableConstraints(con_c!, con_jacobian!, con_h!,
                                     lx, ux, lc, uc)
res = optimize(df, dfc, x0, IPNewton())
 * Status: success

 * Candidate solution
    Final objective value:     2.966216e-01

 * Found with
    Algorithm:     Interior Point Newton

 * Convergence measures
    |x - x'|               = 0.00e+00 ≤ 0.0e+00
    |x - x'|/|x'|          = 0.00e+00 ≤ 0.0e+00
    |f(x) - f(x')|         = 0.00e+00 ≤ 0.0e+00
    |f(x) - f(x')|/|f(x')| = 0.00e+00 ≤ 0.0e+00
    |g(x)|                 = 7.71e-01 ≰ 1.0e-08

 * Work counters
    Seconds run:   0  (vs limit Inf)
    Iterations:    32
    f(x) calls:    111
    ∇f(x) calls:   111

Мы можем добавить нижнюю границу к ограничению и таким образом оптимизировать цель на кольце с внутренним и внешним радиусами и , соответственно.

lc = [0.1^2]
dfc = TwiceDifferentiableConstraints(con_c!, con_jacobian!, con_h!,
                                     lx, ux, lc, uc)
res = optimize(df, dfc, x0, IPNewton())
 * Status: success

 * Candidate solution
    Final objective value:     2.966216e-01

 * Found with
    Algorithm:     Interior Point Newton

 * Convergence measures
    |x - x'|               = 0.00e+00 ≤ 0.0e+00
    |x - x'|/|x'|          = 0.00e+00 ≤ 0.0e+00
    |f(x) - f(x')|         = 0.00e+00 ≤ 0.0e+00
    |f(x) - f(x')|/|f(x')| = 0.00e+00 ≤ 0.0e+00
    |g(x)|                 = 7.71e-01 ≰ 1.0e-08

 * Work counters
    Seconds run:   0  (vs limit Inf)
    Iterations:    36
    f(x) calls:    162
    ∇f(x) calls:   162

Обратите внимание, что алгоритм предупреждает, что начальное предположение не является внутренней точкой. Часто IPNewton может справиться с этим, однако, если начальная догадка такова, что c(x) = u_c, то алгоритм в настоящий момент завершается сбоем. Возможно, в будущем мы это исправим.

Несколько ограничений

В следующем примере показано, как добавить дополнительное ограничение. В частности, мы добавляем функцию ограничений

function con2_c!(c, x)
    c[1] = x[1]^2 + x[2]^2     ## Первое ограничение
    c[2] = x[2]*sin(x[1])-x[1] ## Второе ограничение
    c
end
function con2_jacobian!(J, x)
    # Первое ограничение
    J[1,1] = 2*x[1]
    J[1,2] = 2*x[2]
    # Второе ограничение
    J[2,1] = x[2]*cos(x[1])-1.0
    J[2,2] = sin(x[1])
    J
end
function con2_h!(h, x, λ)
    # Первое ограничение
    h[1,1] += λ[1]*2
    h[2,2] += λ[1]*2
    # Второе ограничение
    h[1,1] += λ[2]*x[2]*-sin(x[1])
    h[1,2] += λ[2]*cos(x[1])
    # Симметризировать h
    h[2,1]  = h[1,2]
    h
end;

Мы создаем объекты ограничений и вызываем IPNewton с начальным предположением .

x0 = [0.25, 0.25]
lc = [-Inf, 0.0]; uc = [0.5^2, 0.0]
dfc = TwiceDifferentiableConstraints(con2_c!, con2_jacobian!, con2_h!,
                                     lx, ux, lc, uc)
res = optimize(df, dfc, x0, IPNewton())
 * Status: success

 * Candidate solution
    Final objective value:     1.000000e+00

 * Found with
    Algorithm:     Interior Point Newton

 * Convergence measures
    |x - x'|               = 6.90e-10 ≰ 0.0e+00
    |x - x'|/|x'|          = 3.55e+08 ≰ 0.0e+00
    |f(x) - f(x')|         = 1.38e-09 ≰ 0.0e+00
    |f(x) - f(x')|/|f(x')| = 1.38e-09 ≰ 0.0e+00
    |g(x)|                 = 2.00e+00 ≰ 1.0e-08

 * Work counters
    Seconds run:   0  (vs limit Inf)
    Iterations:    29
    f(x) calls:    219
    ∇f(x) calls:   219

Простая программа

Ниже приводится версия программы без комментариев. Файл также доступен здесь: ipnewton_basics.jl

using Optim, NLSolversBase #hide
import NLSolversBase: clear! #hide

fun(x) =  (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2

function fun_grad!(g, x)
g[1] = -2.0 * (1.0 - x[1]) - 400.0 * (x[2] - x[1]^2) * x[1]
g[2] = 200.0 * (x[2] - x[1]^2)
end

function fun_hess!(h, x)
h[1, 1] = 2.0 - 400.0 * x[2] + 1200.0 * x[1]^2
h[1, 2] = -400.0 * x[1]
h[2, 1] = -400.0 * x[1]
h[2, 2] = 200.0
end;

x0 = [0.0, 0.0]
df = TwiceDifferentiable(fun, fun_grad!, fun_hess!, x0)

lx = [-0.5, -0.5]; ux = [0.5, 0.5]
dfc = TwiceDifferentiableConstraints(lx, ux)

res = optimize(df, dfc, x0, IPNewton())

ux = fill(Inf, 2)
dfc = TwiceDifferentiableConstraints(lx, ux)

clear!(df)
res = optimize(df, dfc, x0, IPNewton())

lx = fill(-Inf, 2); ux = fill(Inf, 2)
dfc = TwiceDifferentiableConstraints(lx, ux)

clear!(df)
res = optimize(df, dfc, x0, IPNewton())

lx = Float64[]; ux = Float64[]
dfc = TwiceDifferentiableConstraints(lx, ux)

clear!(df)
res = optimize(df, dfc, x0, IPNewton())

con_c!(c, x) = (c[1] = x[1]^2 + x[2]^2; c)
function con_jacobian!(J, x)
    J[1,1] = 2*x[1]
    J[1,2] = 2*x[2]
    J
end
function con_h!(h, x, λ)
    h[1,1] += λ[1]*2
    h[2,2] += λ[1]*2
end;

lx = Float64[]; ux = Float64[]
lc = [-Inf]; uc = [0.5^2]
dfc = TwiceDifferentiableConstraints(con_c!, con_jacobian!, con_h!,
                                     lx, ux, lc, uc)
res = optimize(df, dfc, x0, IPNewton())

lc = [0.1^2]
dfc = TwiceDifferentiableConstraints(con_c!, con_jacobian!, con_h!,
                                     lx, ux, lc, uc)
res = optimize(df, dfc, x0, IPNewton())

function con2_c!(c, x)
    c[1] = x[1]^2 + x[2]^2     ## Первое ограничение
    c[2] = x[2]*sin(x[1])-x[1] ## Второе ограничение
    c
end
function con2_jacobian!(J, x)
    # Первое ограничение
    J[1,1] = 2*x[1]
    J[1,2] = 2*x[2]
    # Второе ограничение
    J[2,1] = x[2]*cos(x[1])-1.0
    J[2,2] = sin(x[1])
    J
end
function con2_h!(h, x, λ)
    # Первое ограничение
    h[1,1] += λ[1]*2
    h[2,2] += λ[1]*2
    # Второе ограничение
    h[1,1] += λ[2]*x[2]*-sin(x[1])
    h[1,2] += λ[2]*cos(x[1])
    # Симметризировать h
    h[2,1]  = h[1,2]
    h
end;

x0 = [0.25, 0.25]
lc = [-Inf, 0.0]; uc = [0.5^2, 0.0]
dfc = TwiceDifferentiableConstraints(con2_c!, con2_jacobian!, con2_h!,
                                     lx, ux, lc, uc)
res = optimize(df, dfc, x0, IPNewton())

# Этот файл был создан с помощью Literate.jl, https://github.com/fredrikekre/Literate.jl.

Эта страница была создана с помощью Literate.jl.