From ddbcba30cd48a35f210daa67139c7ce21fe28765 Mon Sep 17 00:00:00 2001 From: odow Date: Sun, 14 Jul 2024 12:20:11 +1200 Subject: [PATCH 1/4] [Nonlinear.ReverseAD] fix NLPBlock and final_touch bridges --- src/Nonlinear/ReverseAD/reverse_mode.jl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Nonlinear/ReverseAD/reverse_mode.jl b/src/Nonlinear/ReverseAD/reverse_mode.jl index ca7fa1e0f8..9b09a74a82 100644 --- a/src/Nonlinear/ReverseAD/reverse_mode.jl +++ b/src/Nonlinear/ReverseAD/reverse_mode.jl @@ -54,6 +54,26 @@ function _reverse_mode(d::NLPEvaluator, x) for con in d.constraints _reverse_eval(con) end + # If a JuMP model uses the legacy nonlinear interface, then JuMP constructs + # a NLPEvaluator at the start of a call to `JuMP.optimize!` and it passes in + # the list of variables in the JuMP model to `.ordered_variables`. + # + # During `MOI.initialize`, `.last_x` gets filled with `NaN` to match the + # length of `ordered_variables`. + # + # However, if the model includes a bridge that adds new decision variables + # in `Utilities.final_touch`, then the total number of variables (in `x`) + # might be larger than the cache in `last_x`. + # + # It is safe to resize `last_x` because only the variables in + # `ordered_variables` can appear in the NLPBlock. + # + # I don't think we need any other fixes becasue callers to things like + # `eval_objective` can pass in a longer input `x` vector without fear + # because the excess elements won't be used. + if length(d.last_x) < length(x) + resize!(d.last_x, length(x)) + end copyto!(d.last_x, x) return end From a3654f27e73fd8b9d6e066187135097254f65755 Mon Sep 17 00:00:00 2001 From: odow Date: Sun, 14 Jul 2024 15:55:22 +1200 Subject: [PATCH 2/4] Update --- test/Nonlinear/ReverseAD.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/Nonlinear/ReverseAD.jl b/test/Nonlinear/ReverseAD.jl index 9f3cc632fd..8eb597f873 100644 --- a/test/Nonlinear/ReverseAD.jl +++ b/test/Nonlinear/ReverseAD.jl @@ -1121,6 +1121,20 @@ function test_timers() return end +function test_varying_length_x() + model = MOI.Nonlinear.Model() + x = MOI.VariableIndex(1) + MOI.Nonlinear.set_objective(model, :(sin($x))) + evaluator = + MOI.Nonlinear.Evaluator(model, MOI.Nonlinear.SparseReverseMode(), [x]) + MOI.initialize(evaluator, Symbol[:Jac]) + ∇f = [NaN] + MOI.eval_objective_gradient(evaluator, ∇f, [1.0, 2.0]) + @test length(∇f) == 1 + @test ∇f[1] ≈ cos(1.0) + return +end + end # module TestReverseAD.runtests() From 3494ae1e38d3943b1df55b308a66f2c1f7e655a5 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sun, 14 Jul 2024 17:44:33 +1200 Subject: [PATCH 3/4] Update src/Nonlinear/ReverseAD/reverse_mode.jl --- src/Nonlinear/ReverseAD/reverse_mode.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nonlinear/ReverseAD/reverse_mode.jl b/src/Nonlinear/ReverseAD/reverse_mode.jl index 9b09a74a82..b1ea0f9773 100644 --- a/src/Nonlinear/ReverseAD/reverse_mode.jl +++ b/src/Nonlinear/ReverseAD/reverse_mode.jl @@ -68,7 +68,7 @@ function _reverse_mode(d::NLPEvaluator, x) # It is safe to resize `last_x` because only the variables in # `ordered_variables` can appear in the NLPBlock. # - # I don't think we need any other fixes becasue callers to things like + # I don't think we need any other fixes because callers to things like # `eval_objective` can pass in a longer input `x` vector without fear # because the excess elements won't be used. if length(d.last_x) < length(x) From 1044004772738d82c51bdbcaa0a972b746bf0782 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 15 Jul 2024 11:02:51 +1200 Subject: [PATCH 4/4] Update reverse_mode.jl --- src/Nonlinear/ReverseAD/reverse_mode.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Nonlinear/ReverseAD/reverse_mode.jl b/src/Nonlinear/ReverseAD/reverse_mode.jl index b1ea0f9773..5487da2237 100644 --- a/src/Nonlinear/ReverseAD/reverse_mode.jl +++ b/src/Nonlinear/ReverseAD/reverse_mode.jl @@ -59,11 +59,12 @@ function _reverse_mode(d::NLPEvaluator, x) # the list of variables in the JuMP model to `.ordered_variables`. # # During `MOI.initialize`, `.last_x` gets filled with `NaN` to match the - # length of `ordered_variables`. + # length of `ordered_variables`, that is, the number of variables in the + # JuMP model. # # However, if the model includes a bridge that adds new decision variables - # in `Utilities.final_touch`, then the total number of variables (in `x`) - # might be larger than the cache in `last_x`. + # then the total number of variables in the optimizer (in `x`) will be + # larger than the cache in `last_x`. # # It is safe to resize `last_x` because only the variables in # `ordered_variables` can appear in the NLPBlock.