diff --git a/src/Nonlinear/ReverseAD/forward_over_reverse.jl b/src/Nonlinear/ReverseAD/forward_over_reverse.jl index be9e9a956f..f421fb11c9 100644 --- a/src/Nonlinear/ReverseAD/forward_over_reverse.jl +++ b/src/Nonlinear/ReverseAD/forward_over_reverse.jl @@ -329,15 +329,12 @@ function _forward_eval_ϵ( d.user_output_buffer, n_children, ) - has_hessian = Nonlinear.eval_multivariate_hessian( + Nonlinear.eval_multivariate_hessian( d.data.operators, d.data.operators.multivariate_operators[node.index], H, f_input, ) - # This might be `false` if we extend this code to all - # multivariate functions. - @assert has_hessian for col in 1:n_children dual = zero(P) for row in 1:n_children diff --git a/src/Nonlinear/operators.jl b/src/Nonlinear/operators.jl index 857f873133..96a74cef62 100644 --- a/src/Nonlinear/operators.jl +++ b/src/Nonlinear/operators.jl @@ -916,7 +916,7 @@ function eval_multivariate_hessian( H, x::AbstractVector{T}, ) where {T} - if op in (:+, :-, :ifelse) + if op in (:+, :-, :ifelse, :min, :max) return false end if op == :* @@ -980,12 +980,6 @@ function eval_multivariate_hessian( H[1, 1] = -2 * x[2] * x[1] / base H[2, 1] = (x[1]^2 - x[2]^2) / base H[2, 2] = 2 * x[2] * x[1] / base - elseif op == :min - _, i = findmin(x) - H[i, i] = one(T) - elseif op == :max - _, i = findmax(x) - H[i, i] = one(T) else id = registry.multivariate_operator_to_id[op] offset = id - registry.multivariate_user_operator_start diff --git a/test/Nonlinear/test_Nonlinear.jl b/test/Nonlinear/test_Nonlinear.jl index 65c9a30c32..e335c2ced5 100644 --- a/test/Nonlinear/test_Nonlinear.jl +++ b/test/Nonlinear/test_Nonlinear.jl @@ -1034,7 +1034,6 @@ function test_min_operator() evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) @test MOI.objective_expr(evaluator) == :(min(x[$x], x[$y])) - r = Nonlinear.OperatorRegistry() x = [1.1, 2.2] @test Nonlinear.eval_multivariate_function(r, :min, x) == 1.1 @@ -1042,19 +1041,16 @@ function test_min_operator() Nonlinear.eval_multivariate_gradient(r, :min, g, x) @test g == [1.0, 0.0] H = LinearAlgebra.LowerTriangular(zeros(2, 2)) - @test Nonlinear.eval_multivariate_hessian(r, :min, H, x) - @test H[1, 1] == 1.0 - @test H[2, 1] == H[2, 2] == 0.0 - + @test !Nonlinear.eval_multivariate_hessian(r, :min, H, x) + @test iszero(H) x = [1.1, -2.2] @test Nonlinear.eval_multivariate_function(r, :min, x) == -2.2 g = zeros(2) Nonlinear.eval_multivariate_gradient(r, :min, g, x) @test g == [0.0, 1.0] H = LinearAlgebra.LowerTriangular(zeros(2, 2)) - @test Nonlinear.eval_multivariate_hessian(r, :min, H, x) - @test H[2, 2] == 1.0 - @test H[1, 1] == H[2, 1] == 0.0 + @test !Nonlinear.eval_multivariate_hessian(r, :min, H, x) + @test iszero(H) return end @@ -1066,7 +1062,6 @@ function test_max_operator() evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) @test MOI.objective_expr(evaluator) == :(max(x[$x], x[$y])) - r = Nonlinear.OperatorRegistry() x = [1.1, -2.2] @test Nonlinear.eval_multivariate_function(r, :max, x) == 1.1 @@ -1074,19 +1069,16 @@ function test_max_operator() Nonlinear.eval_multivariate_gradient(r, :max, g, x) @test g == [1.0, 0.0] H = LinearAlgebra.LowerTriangular(zeros(2, 2)) - @test Nonlinear.eval_multivariate_hessian(r, :max, H, x) - @test H[1, 1] == 1.0 - @test H[2, 1] == H[2, 2] == 0.0 - + @test !Nonlinear.eval_multivariate_hessian(r, :max, H, x) + @test iszero(H) x = [1.1, 2.2] @test Nonlinear.eval_multivariate_function(r, :max, x) == 2.2 g = zeros(2) Nonlinear.eval_multivariate_gradient(r, :max, g, x) @test g == [0.0, 1.0] H = LinearAlgebra.LowerTriangular(zeros(2, 2)) - @test Nonlinear.eval_multivariate_hessian(r, :max, H, x) - @test H[2, 2] == 1.0 - @test H[1, 1] == H[2, 1] == 0.0 + @test !Nonlinear.eval_multivariate_hessian(r, :max, H, x) + @test iszero(H) return end diff --git a/test/Nonlinear/test_ReverseAD.jl b/test/Nonlinear/test_ReverseAD.jl index f1a6cc4fb0..673b08f0c0 100644 --- a/test/Nonlinear/test_ReverseAD.jl +++ b/test/Nonlinear/test_ReverseAD.jl @@ -1458,6 +1458,63 @@ function test_issue_2897() return end +function test_hessian_min() + model = MOI.Nonlinear.Model() + x, y = MOI.VariableIndex.(1:2) + MOI.Nonlinear.set_objective(model, :(min($x^2, $y^2))) + evaluator = MOI.Nonlinear.Evaluator( + model, + MOI.Nonlinear.SparseReverseMode(), + [x, y], + ) + MOI.initialize(evaluator, [:Grad, :Hess]) + @test MOI.hessian_lagrangian_structure(evaluator) == [(1, 1), (2, 2)] + H = zeros(2) + MOI.eval_hessian_lagrangian(evaluator, H, [1.1, 2.3], 1.5, Float64[]) + @test isapprox(H, [3.0, 0.0]) + MOI.eval_hessian_lagrangian(evaluator, H, [2.3, 1.5], 1.2, Float64[]) + @test isapprox(H, [0.0, 2.4]) + return +end + +function test_hessian_max() + model = MOI.Nonlinear.Model() + x, y = MOI.VariableIndex.(1:2) + MOI.Nonlinear.set_objective(model, :(max($x^2, $y^2))) + evaluator = MOI.Nonlinear.Evaluator( + model, + MOI.Nonlinear.SparseReverseMode(), + [x, y], + ) + MOI.initialize(evaluator, [:Grad, :Hess]) + @test MOI.hessian_lagrangian_structure(evaluator) == [(1, 1), (2, 2)] + H = zeros(2) + MOI.eval_hessian_lagrangian(evaluator, H, [1.1, 2.3], 1.5, Float64[]) + @test isapprox(H, [0.0, 3.0]) + MOI.eval_hessian_lagrangian(evaluator, H, [2.3, 1.5], 1.2, Float64[]) + @test isapprox(H, [2.4, 0.0]) + return +end + +function test_hessian_ifelse() + model = MOI.Nonlinear.Model() + x, y = MOI.VariableIndex.(1:2) + MOI.Nonlinear.set_objective(model, :(ifelse($x < $y, $x^2, $y^2))) + evaluator = MOI.Nonlinear.Evaluator( + model, + MOI.Nonlinear.SparseReverseMode(), + [x, y], + ) + MOI.initialize(evaluator, [:Grad, :Hess]) + @test MOI.hessian_lagrangian_structure(evaluator) == [(1, 1), (2, 2)] + H = zeros(2) + MOI.eval_hessian_lagrangian(evaluator, H, [1.1, 2.3], 1.5, Float64[]) + @test isapprox(H, [3.0, 0.0]) + MOI.eval_hessian_lagrangian(evaluator, H, [2.3, 1.5], 1.2, Float64[]) + @test isapprox(H, [0.0, 2.4]) + return +end + end # module TestReverseAD.runtests()