Нелинейное моделирование
Задание нелинейной целевой функции
Чтобы задать нелинейную целевую функцию, используйте макрос @objective.
julia> model = Model();
julia> @variable(model, x[1:2]);
julia> @objective(model, Min, exp(x[1]) - sqrt(x[2]))
exp(x[1]) - sqrt(x[2])
Чтобы изменить нелинейную целевую функцию, еще раз вызовите @objective.
Добавление нелинейного ограничения
Для добавления нелинейного ограничения используйте макрос @constraint.
julia> model = Model();
julia> @variable(model, x[1:2]);
julia> @constraint(model, exp(x[1]) <= 1)
exp(x[1]) - 1.0 ≤ 0
julia> @constraint(model, con[i = 1:2], 2^x[i] >= i)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarNonlinearFunction, MathOptInterface.GreaterThan{Float64}}, ScalarShape}}:
con[1] : (2.0 ^ x[1]) - 1.0 ≥ 0
con[2] : (2.0 ^ x[2]) - 2.0 ≥ 0
Чтобы удалить нелинейное ограничение, используйте метод delete:
julia> delete(model, con[1])
Добавление параметра
Некоторые решатели явным образом поддерживают параметры, представляющие собой константы в модели, которые можно эффективно обновлять между решениями.
Параметры реализуются в JuMP посредством переменной решения, ограниченной при создании множеством Parameter.
julia> model = Model();
julia> @variable(model, x);
julia> @variable(model, p[i = 1:2] in Parameter(i))
2-element Vector{VariableRef}:
p[1]
p[2]
julia> parameter_value(p[1])
1.0
julia> set_parameter_value(p[1], 3.5)
julia> @objective(model, Max, log(p[1] * x + p[2]))
log(p[1]*x + p[2])
Дополнительные сведения о создании параметров и управлении ими см. в разделе Параметры.
Параметры наиболее полезны при решении нелинейных моделей в следующем порядке.
julia> using JuMP, Ipopt
julia> model = Model(Ipopt.Optimizer);
julia> set_silent(model)
julia> @variable(model, x)
x
julia> @variable(model, p in Parameter(1.0))
p
julia> @objective(model, Min, (x - p)^2)
x² - 2 p*x + p²
julia> optimize!(model)
julia> value(x)
1.0
julia> set_parameter_value(p, 5.0)
julia> optimize!(model)
julia> value(x)
5.0
Использование параметров может быть быстрее, чем создание модели с нуля с обновленными данными, так как позволяет JuMP избежать повторения ряда шагов при обработке модели перед ее передачей решателю.
Создание нелинейного выражения
Для создания объектов нелинейных выражений используйте макрос @expression.
julia> model = Model();
julia> @variable(model, x[1:2]);
julia> expr = @expression(model, exp(x[1]) + sqrt(x[2]))
exp(x[1]) + sqrt(x[2])
julia> my_anon_expr = @expression(model, [i = 1:2], sin(x[i]))
2-element Vector{NonlinearExpr}:
sin(x[1])
sin(x[2])
julia> @expression(model, my_expr[i = 1:2], sin(x[i]))
2-element Vector{NonlinearExpr}:
sin(x[1])
sin(x[2])
Выражение NonlinearExpr можно использовать в @objective, @constraint и даже вкладывать в другие выражения @expression.
julia> @objective(model, Min, expr^2 + 1)
((exp(x[1]) + sqrt(x[2])) ^ 2.0) + 1.0
julia> @constraint(model, [i = 1:2], my_expr[i] <= i)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarNonlinearFunction, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
sin(x[1]) - 1.0 ≤ 0
sin(x[2]) - 2.0 ≤ 0
julia> @expression(model, nested[i = 1:2], sin(my_expr[i]))
2-element Vector{NonlinearExpr}:
sin(sin(x[1]))
sin(sin(x[2]))
Чтобы запросить значение нелинейного выражения, используйте метод value:
julia> set_start_value(x[1], 1.0)
julia> value(start_value, nested[1])
0.7456241416655579
julia> sin(sin(1.0))
0.7456241416655579
Подробные сведения о нелинейных выражениях
Нелинейные выражения представлены в JuMP объектом NonlinearExpr.
Конструкторы
Нелинейные выражения можно создавать с помощью конструкторов NonlinearExpr:
julia> model = Model();
julia> @variable(model, x);
julia> expr = NonlinearExpr(:sin, Any[x])
sin(x)
или путем перегрузки операторов:
julia> model = Model();
julia> @variable(model, x);
julia> expr = sin(x)
sin(x)
Поддерживаемые аргументы
Нелинейные выражения могут содержать сочетание чисел, выражений AffExpr, QuadExpr и других выражений NonlinearExpr:
julia> model = Model();
julia> @variable(model, x);
julia> aff = x + 1;
julia> quad = x^2 + x;
julia> expr = cos(x) * sin(quad) + aff
(cos(x) * sin(x² + x)) + (x + 1)
Поддерживаемые операторы
Список поддерживаемых операторов зависит от решателя. Для определенного оптимизатора список поддерживаемых операторов можно запросить с помощью MOI.ListOfSupportedNonlinearOperators:
julia> import Ipopt
julia> import MathOptInterface as MOI
julia> MOI.get(Ipopt.Optimizer(), MOI.ListOfSupportedNonlinearOperators())
85-element Vector{Symbol}:
:+
:-
:abs
:sqrt
:cbrt
:abs2
:inv
:log
:log10
:log2
⋮
:min
:max
:&&
:||
:<=
:(==)
:>=
:<
:>
В некоторых одномерных случаях оператор определен в SpecialFunctions.jl. Для использования этих функций необходимо импортировать SpecialFunctions.jl явным образом.
julia> import Ipopt
julia> op = MOI.get(Ipopt.Optimizer(), MOI.ListOfSupportedNonlinearOperators());
julia> :erfcx in op
true
julia> :dawson in op
true
julia> import SpecialFunctions
julia> model = Model();
julia> @variable(model, x)
x
julia> @expression(model, SpecialFunctions.erfcx(x))
erfcx(x)
julia> @expression(model, SpecialFunctions.dawson(x))
dawson(x)
Ограничения
Некоторые нелинейные выражения невозможно создать путем перегрузки операторов. Например, чтобы свести к минимуму вероятность ошибок в пользовательском коде, мы не стали перегружать операторы сравнения объектов JuMP, такие как < и >=:
julia> model = Model();
julia> @variable(model, x);
julia> x < 1
ERROR: Cannot evaluate `<` between a variable and a number.
[...]
Вместо этого заключите выражение в макрос @expression:
julia> model = Model();
julia> @variable(model, x);
julia> expr = @expression(model, x < 1)
x < 1
По техническим причинам в число операторов, не допускающих перегрузки, также включены ||, && и ifelse.
julia> model = Model();
julia> @variable(model, x);
julia> expr = @expression(model, ifelse(x < -1 || x >= 1, x^2, 0.0))
ifelse((x < -1) || (x >= 1), x², 0.0)
В качестве альтернативы используйте функции JuMP.op_, которые по умолчанию используют различные операторы сравнения и логические операторы:
julia> model = Model();
julia> @variable(model, x);
julia> expr = op_ifelse(
op_or(op_strictly_less_than(x, -1), op_greater_than_or_equal_to(x, 1)),
x^2,
0.0,
)
ifelse((x < -1) || (x >= 1), x², 0.0)
Доступные функции:
| Функция JuMP | Функция Julia |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Поля
У каждого выражения NonlinearExpr есть два поля.
Поле .head — это символ Symbol, представляющий вызываемый оператор:
julia> expr.head
:sin
Поле .args — это вектор Vector{Any}, содержащий аргументы оператора:
julia> expr.args
1-element Vector{Any}:
x
Принудительное создание нелинейных выражений
При использовании макросов JuMP и перегрузки операторов в первую очередь создаются аффинные (GenericAffExpr) и квадратичные (GenericQuadExpr) выражения, а не GenericNonlinearExpr. Например:
julia> model = Model();
julia> @variable(model, x);
julia> f = (x - 0.1)^2
x² - 0.2 x + 0.010000000000000002
julia> typeof(f)
QuadExpr (alias for GenericQuadExpr{Float64, GenericVariableRef{Float64}})
Чтобы переопределить это поведение, используйте макрос @force_nonlinear:
julia> g = @force_nonlinear((x - 0.1)^2)
(x - 0.1) ^ 2
julia> typeof(g)
NonlinearExpr (alias for GenericNonlinearExpr{GenericVariableRef{Float64}})
|
Используйте этот макрос только при необходимости. Дополнительные сведения о том, когда его следует использовать, см. в docstring метода |
Трассировка функций
Нелинейные выражения можно создавать путем трассировки функций. Трассировка функции заключается в вызове обычной функции Julia с переменными JuMP в качестве аргументов. При этом функция создает нелинейное выражение посредством перегрузки операторов. Например:
julia> using JuMP
julia> model = Model();
julia> @variable(model, x[1:2]);
julia> f(x::Vector{VariableRef}) = 2 * sin(x[1]^2) + sqrt(x[2])
f (generic function with 1 method)
julia> y = f(x)
(2.0 * sin(x[1]²)) + sqrt(x[2])
julia> typeof(y)
NonlinearExpr (alias for GenericNonlinearExpr{GenericVariableRef{Float64}})
julia> @objective(model, Max, f(x))
(2.0 * sin(x[1]²)) + sqrt(x[2])
Трассировка функций поддерживает функции, которые возвращают векторы или массивы выражений NonlinearExpr:
julia> using JuMP
julia> model = Model();
julia> @variable(model, x[1:2]);
julia> f(x::Vector{VariableRef}) = sqrt.(x)
f (generic function with 1 method)
julia> y = f(x)
2-element Vector{NonlinearExpr}:
sqrt(x[1])
sqrt(x[2])
julia> typeof(y)
Vector{NonlinearExpr} (alias for Array{GenericNonlinearExpr{GenericVariableRef{Float64}}, 1})
julia> @constraint(model, f(x) .<= 2)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarNonlinearFunction, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
sqrt(x[1]) - 2.0 ≤ 0
sqrt(x[2]) - 2.0 ≤ 0
julia> @objective(model, Max, sum(f(x)))
0.0 + sqrt(x[2]) + sqrt(x[1])
Так как при трассировке функций применяется перегрузка операторов, для многих функций она непригодна. Например:
julia> using JuMP
julia> model = Model();
julia> @variable(model, x[1:2]);
julia> f(x::Vector{VariableRef}) = x[1] > 1 ? 0 : x[2]
f (generic function with 1 method)
julia> f(x)
ERROR: Cannot evaluate `>` between a variable and a number.
[...]
В таких случаях следует определить пользовательский оператор с помощью макроса @operator.
Пользовательские операторы
Помимо стандартного списка одномерных и многомерных операторов, распознаваемых подмодулем MOI.Nonlinear, JuMP поддерживает пользовательские операторы, позволяющие представлять нелинейные функции, которые не могут (или не должны) трассироваться, например, потому, что они опираются на подпрограммы, не относящиеся к Julia.
|
Пользовательские операторы должны возвращать скалярный результат. Обходное решение см. в разделе User-defined operators with vector outputs. |
Добавление оператора
Добавить пользовательский оператор можно с помощью макроса @operator:
julia> using JuMP
julia> square(x) = x^2
square (generic function with 1 method)
julia> f(x, y) = (x - 1)^2 + (y - 2)^2
f (generic function with 1 method)
julia> model = Model();
julia> @operator(model, op_square, 1, square)
NonlinearOperator(square, :op_square)
julia> @operator(model, op_f, 2, f)
NonlinearOperator(f, :op_f)
julia> @variable(model, x[1:2]);
julia> @objective(model, Min, op_f(x[1], op_square(x[2])))
op_f(x[1], op_square(x[2]))
Макрос @operator имеет следующие аргументы:
-
Модель, в которую добавляется оператор.
-
Объект символа Julia, который служит именем пользовательского оператора
в выражениях JuMP. Имя не должно совпадать с именем функции.
-
Количество принимаемых функцией скалярных входных аргументов.
-
Метод Julia, вычисляющий функцию.
|
Пользовательские операторы нельзя удалить. |
Получить ссылку на оператор можно посредством синтаксиса model[:key]:
julia> using JuMP
julia> square(x) = x^2
square (generic function with 1 method)
julia> model = Model();
julia> @operator(model, op_square, 1, square)
NonlinearOperator(square, :op_square)
julia> op_square_2 = model[:op_square]
NonlinearOperator(square, :op_square)
Автоматическое дифференцирование
JuMP вычисляет производные первого и второго порядка для выражений, используя подмодуль ReverseAD, который реализует разреженное автоматическое дифференцирование в обратном режиме. Однако, поскольку подмодулю ReverseAD требуется алгебраическое выражение в качестве входных данных, JuMP не может использовать ReverseAD для дифференцирования пользовательских операторов.
Вместо этого, если явно не предоставлены градиенты и гессианы (Gradients and Hessians), пользовательские операторы должны поддерживать автоматическое дифференцирование с помощью ForwardDiff.jl.
Использование FowardDiff.jl имеет два важных следствия:
-
ForwardDiff.jl поддерживает только ограниченное подмножество операторов Julia. Если при добавлении
оператора происходит ошибка, см. раздел Common mistakes when writing a user-defined operator.
-
Дифференцирование операторов со множеством аргументов происходит медленно. В общем случае
следует стараться, чтобы количество аргументов было меньше 100, а в идеале — меньше 10.
Из-за использования ForwardDiff в большинстве случаев следует предпочесть трассировку функций вместо определения пользовательского оператора.
Добавление оператора без макроса
Макрос @operator — это синтаксический сахар для add_nonlinear_operator. Версия предыдущего примера без макроса выглядит следующим образом:
julia> using JuMP
julia> square(x) = x^2
square (generic function with 1 method)
julia> f(x, y) = (x - 1)^2 + (y - 2)^2
f (generic function with 1 method)
julia> model = Model();
julia> op_square = add_nonlinear_operator(model, 1, square; name = :op_square)
NonlinearOperator(square, :op_square)
julia> model[:op_square] = op_square
NonlinearOperator(square, :op_square)
julia> op_f = add_nonlinear_operator(model, 2, f; name = :op_f)
NonlinearOperator(f, :op_f)
julia> model[:op_f] = op_f
NonlinearOperator(f, :op_f)
julia> @variable(model, x[1:2]);
julia> @objective(model, Min, op_f(x[1], op_square(x[2])))
op_f(x[1], op_square(x[2]))
Операторы с тем же именем, что и у существующей функции
Зачастую возникает следующая ошибка:
julia> using JuMP
julia> model = Model();
julia> f(x) = x^2
f (generic function with 1 method)
julia> @operator(model, f, 1, f)
ERROR: Unable to add the nonlinear operator `:f` with the same name as
an existing function.
[...]
Причина этой ошибки в том, что вызов @operator(model, f, 1, f) равносилен следующему:
julia> f = add_nonlinear_operator(model, 1, f; name = :f)
но функция f уже есть в Julia.
Если вычислить эту функцию, не добавляя ее в качестве оператора, JuMP выполнит трассировку функции посредством перегрузки операторов:
julia> @variable(model, x);
julia> f(x)
x²
Чтобы среда JuMP воспринимала функцию f как пользовательский оператор и не отслеживала ее, добавьте оператор с помощью метода add_nonlinear_operator и определите новый метод, который вручную создает выражение NonlinearExpr:
julia> _ = add_nonlinear_operator(model, 1, f; name = :f)
NonlinearOperator(f, :f)
julia> f(x::AbstractJuMPScalar) = NonlinearExpr(:f, Any[x])
f (generic function with 2 methods)
julia> @expression(model, log(f(x)))
log(f(x))
Градиенты и гессианы
По умолчанию в JuMP используется автоматическое дифференцирование для вычисления градиента и гессиана пользовательских операторов. Если функция не поддается автоматическому дифференцированию по умолчанию или можно вычислить аналитические производные, то можно передать дополнительные аргументы в @operator для вычисления первой и второй производных.
|
В руководстве Automatic differentiation of user-defined operators приведены примеры использования сторонних пакетов Julia для автоматического вычисления производных. |
Одномерные функции
Для одномерных функций функция градиента ∇f возвращает число, представляющее производную первого порядка. Помимо этого, можно передать третью функцию, которая возвращает число, представляющее производную второго порядка:
julia> using JuMP
julia> f(x) = x^2
f (generic function with 1 method)
julia> ∇f(x) = 2x
∇f (generic function with 1 method)
julia> ∇²f(x) = 2
∇²f (generic function with 1 method)
julia> model = Model();
julia> @operator(model, op_f, 1, f, ∇f, ∇²f) # Предоставлять ∇²f необязательно
NonlinearOperator(f, :op_f)
julia> @variable(model, x)
x
julia> @objective(model, Min, op_f(x))
op_f(x)
Многомерные функции
Для многомерных функций функция градиента ∇f должна принимать вектор AbstractVector в качестве первого аргумента, который заполняется на месте. Функция гессиана ∇²f должна принимать в качестве первого аргумента матрицу AbstractMatrix, нижний треугольник которой заполняется на месте:
julia> using JuMP
julia> f(x...) = (1 - x[1])^2 + 100 * (x[2] - x[1]^2)^2
f (generic function with 1 method)
julia> function ∇f(g::AbstractVector{T}, x::T...) where {T}
g[1] = 400 * x[1]^3 - 400 * x[1] * x[2] + 2 * x[1] - 2
g[2] = 200 * (x[2] - x[1]^2)
return
end
∇f (generic function with 1 method)
julia> function ∇²f(H::AbstractMatrix{T}, x::T...) where {T}
H[1, 1] = 1200 * x[1]^2 - 400 * x[2] + 2
# H[1, 2] = -400 * x[1] <-- Не требуется. Заполняется только нижний треугольник.
H[2, 1] = -400 * x[1]
H[2, 2] = 200.0
return
end
∇²f (generic function with 1 method)
julia> model = Model();
julia> @operator(model, rosenbrock, 2, f, ∇f, ∇²f) # Предоставлять ∇²f необязательно
NonlinearOperator(f, :rosenbrock)
julia> @variable(model, x[1:2])
2-element Vector{VariableRef}:
x[1]
x[2]
julia> @objective(model, Min, rosenbrock(x[1], x[2]))
rosenbrock(x[1], x[2])
Предполагается, что гессианова матрица H инициализирована нулями, и поскольку матрица H симметрична, достаточно заполнить ненулевыми значениями элементы нижнего треугольника. Тип передаваемой матрицы H зависит от системы автоматического дифференцирования, поэтому первый аргумент функции гессиана должен поддерживать тип AbstractMatrix (он может быть отличен от Matrix{Float64}). Более того, изначально предполагается только то, что H поддерживает size(H) и setindex!. Наконец, матрица считается плотной, поэтому для функций с входными данными высокой размерности производительность будет низкой.
Пользовательские операторы с векторными входными данными
Пользовательские операторы, которые принимают векторы в качестве входных аргументов (например, f(x::Vector)), не поддерживаются. Вместо этого используйте синтаксис распаковки Julia для создания функции со скалярными аргументами. Например, вместо следующего кода:
f(x::Vector) = sum(x[i]^i for i in 1:length(x))
определите:
f(x...) = sum(x[i]^i for i in 1:length(x))
Еще один подход заключается в определении распаковываемой функции как анонимной:
julia> using JuMP
julia> model = Model();
julia> @variable(model, x[1:5])
5-element Vector{VariableRef}:
x[1]
x[2]
x[3]
x[4]
x[5]
julia> f(x::Vector) = sum(x[i]^i for i in 1:length(x))
f (generic function with 1 method)
julia> @operator(model, op_f, 5, (x...) -> f(collect(x)))
NonlinearOperator(#6, :op_f)
julia> @objective(model, Min, op_f(x...))
op_f(x[1], x[2], x[3], x[4], x[5])
Если оператор принимает несколько входных векторов, напишите функцию, которая принимает распакованные аргументы и воссоздает требуемые входные векторы:
julia> using JuMP
julia> model = Model();
julia> @variable(model, x[1:2]);
julia> @variable(model, y[1:2]);
julia> @variable(model, z);
julia> f(x::Vector, y::Vector, z) = sum((x[i] * y[i])^z for i in 1:2)
f (generic function with 1 method)
julia> f(x, y, z)
((x[1]*y[1]) ^ z) + ((x[2]*y[2]) ^ z)
julia> f_splat(args...) = f(collect(args[1:2]), collect(args[3:4]), args[5])
f_splat (generic function with 1 method)
julia> f_splat(x..., y..., z)
((x[1]*y[1]) ^ z) + ((x[2]*y[2]) ^ z)
julia> @operator(model, op_f, 5, f_splat)
NonlinearOperator(f_splat, :op_f)
julia> @objective(model, Min, op_f(x..., y..., z))
op_f(x[1], x[2], y[1], y[2], z)
Распространенные ошибки при написании пользовательских операторов
JuMP использует ForwardDiff.jl для вычисления производных первого порядка пользовательских операторов. ForwardDiff имеет ряд ограничений, о которых следует помнить при написании пользовательских операторов.
В оставшейся части этого раздела приводятся советы по отладке и разъясняются некоторые распространенные ошибки.
|
Получаете ошибку наподобие |
Отладка
При добавлении оператора, который не поддерживает ForwardDiff, выводится длинное сообщение об ошибке. Для получения дополнительной информации можно просмотреть трассировку стека, но зачастую бывает сложно понять, почему и где функция дает сбой.
Может оказаться полезным выполнить отладку оператора вне JuMP, как описано ниже.
Если оператор одномерный, выполните следующий код:
julia> import ForwardDiff
julia> my_operator(a) = a^2
my_operator (generic function with 1 method)
julia> ForwardDiff.derivative(my_operator, 1.0)
2.0
Если оператор многомерный, выполните следующий код:
julia> import ForwardDiff
julia> my_operator(a, b) = a^2 + b^2
my_operator (generic function with 1 method)
julia> ForwardDiff.gradient(x -> my_operator(x...), [1.0, 2.0])
2-element Vector{Float64}:
2.0
4.0
Обратите внимание: хотя оператор принимает распакованные аргументы, ForwardDiff.gradient требует вектор в качестве входных данных.
Оператор вызывает что-то, не поддерживаемое ForwardDiff
ForwardDiff работает путем перегрузки многих функций Julia для специального типа ForwardDiff.Dual <: Real. Если ваш оператор попытается вызвать функцию, для которой не определена перегрузка, будет выдана ошибка MethodError.
Например, оператор не может вызывать внешние функции C или выступать в качестве оптимального целевого значения модели JuMP.
julia> import ForwardDiff
julia> my_operator_bad(x) = @ccall sqrt(x::Cdouble)::Cdouble
my_operator_bad (generic function with 1 method)
julia> ForwardDiff.derivative(my_operator_bad, 1.0)
ERROR: MethodError: no method matching Float64(::ForwardDiff.Dual{ForwardDiff.Tag{typeof(my_operator_bad), Float64}, Float64, 1})
[...]
К сожалению, список вызовов, поддерживаемых ForwardDiff, слишком велик, чтобы перечислить все разрешенные и запрещенные варианты. Поэтому лучше всего попробовать на практике и посмотреть, что получится.
Оператор не принимает распакованные входные данные
Оператор принимает в качестве входных данных f(x::Vector), а не распакованные аргументы f(x...).
julia> import ForwardDiff
julia> my_operator_bad(x::Vector) = sum(x[i]^2 for i in eachindex(x))
my_operator_bad (generic function with 1 method)
julia> my_operator_good(x...) = sum(x[i]^2 for i in eachindex(x))
my_operator_good (generic function with 1 method)
julia> ForwardDiff.gradient(x -> my_operator_bad(x...), [1.0, 2.0])
ERROR: MethodError: no method matching my_operator_bad(::ForwardDiff.Dual{ForwardDiff.Tag{var"#5#6", Float64}, Float64, 2}, ::ForwardDiff.Dual{ForwardDiff.Tag{var"#5#6", Float64}, Float64, 2})
[...]
julia> ForwardDiff.gradient(x -> my_operator_good(x...), [1.0, 2.0])
2-element Vector{Float64}:
2.0
4.0
Оператор ожидает входные данные типа Float64
Оператор предполагает, что в него будут переданы входные данные типа Float64, но должен работать с любым универсальным типом Real.
julia> import ForwardDiff
julia> my_operator_bad(x::Float64...) = sum(x[i]^2 for i in eachindex(x))
my_operator_bad (generic function with 1 method)
julia> my_operator_good(x::Real...) = sum(x[i]^2 for i in eachindex(x))
my_operator_good (generic function with 1 method)
julia> ForwardDiff.gradient(x -> my_operator_bad(x...), [1.0, 2.0])
ERROR: MethodError: no method matching my_operator_bad(::ForwardDiff.Dual{ForwardDiff.Tag{var"#5#6", Float64}, Float64, 2}, ::ForwardDiff.Dual{ForwardDiff.Tag{var"#5#6", Float64}, Float64, 2})
[...]
julia> ForwardDiff.gradient(x -> my_operator_good(x...), [1.0, 2.0])
2-element Vector{Float64}:
2.0
4.0
Оператор выделяет память для типа Float64
Оператор временно выделяет память с помощью zeros(3) или аналогичного метода. По умолчанию предполагается тип Float64, поэтому используйте вместо этого вызов zeros(T, 3).
julia> import ForwardDiff
julia> function my_operator_bad(x::Real...)
# В этой строке есть проблема. zeros(n) — это краткая форма записи для zeros(Float64, n)
y = zeros(length(x))
for i in eachindex(x)
y[i] = x[i]^2
end
return sum(y)
end
my_operator_bad (generic function with 1 method)
julia> function my_operator_good(x::T...) where {T<:Real}
y = zeros(T, length(x))
for i in eachindex(x)
y[i] = x[i]^2
end
return sum(y)
end
my_operator_good (generic function with 1 method)
julia> ForwardDiff.gradient(x -> my_operator_bad(x...), [1.0, 2.0])
ERROR: MethodError: no method matching Float64(::ForwardDiff.Dual{ForwardDiff.Tag{var"#1#2", Float64}, Float64, 2})
[...]
julia> ForwardDiff.gradient(x -> my_operator_good(x...), [1.0, 2.0])
2-element Vector{Float64}:
2.0
4.0