From 90b8646bc573be91250e59446a3cf3c83fd13ad8 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 1 Feb 2022 10:24:15 +1300 Subject: [PATCH 1/3] Add warning and improve docs for modify-then-query --- docs/src/manual/solutions.md | 81 +++++++++++++++++++++++++++++------- src/JuMP.jl | 38 ++++++++++------- 2 files changed, 88 insertions(+), 31 deletions(-) diff --git a/docs/src/manual/solutions.md b/docs/src/manual/solutions.md index 33a3f8f4c75..ca3d4472441 100644 --- a/docs/src/manual/solutions.md +++ b/docs/src/manual/solutions.md @@ -286,30 +286,79 @@ Solution is optimal ## OptimizeNotCalled errors -Modifying a model after calling [`optimize!`](@ref) will reset the model into -the `MOI.OPTIMIZE_NOT_CALLED` state. If you attempt to query solution +Due to differences in how solvers cache solutions internally, modifying a model +after calling [`optimize!`](@ref) will reset the model into the +`MOI.OPTIMIZE_NOT_CALLED` state. If you then attempt to query solution information, an `OptimizeNotCalled` error will be thrown. If you are iteratively querying solution information and modifying a model, query all the results first, then modify the problem. For example, instead of: -```julia -model = Model(GLPK.Optimizer) -@variable(model, x >= 0) -optimize!(model) -set_lower_bound(x, 1) # This will modify the model -x_val = value(x) # This will fail because the model has been modified -set_start_value(x, x_val) +```jldoctest +julia> model = Model(GLPK.Optimizer); + +julia> @variable(model, x >= 0); + +julia> optimize!(model); + +julia> termination_status(model) +OPTIMAL::TerminationStatusCode = 1 + +julia> set_upper_bound(x, 1) + +julia> x_val = value(x) +ERROR: OptimizeNotCalled() +Stacktrace: +[...] + +julia> termination_status(model) +OPTIMIZE_NOT_CALLED::TerminationStatusCode = 0 ``` do -```julia -model = Model(GLPK.Optimizer) -@variable(model, x >= 0) -optimize!(model) -x_val = value(x) -set_lower_bound(x, 1) -set_start_value(x, x_val) +```jldoctest +julia> model = Model(GLPK.Optimizer); + +julia> @variable(model, x >= 0); + +julia> optimize!(model); + +julia> x_val = value(x) +0.0 + +julia> termination_status(model) +OPTIMAL::TerminationStatusCode = 1 + +julia> set_upper_bound(x, 1) + +julia> set_lower_bound(x, x_val) + +julia> termination_status(model) +OPTIMIZE_NOT_CALLED::TerminationStatusCode = 0 +``` + +If you know that your particular solver supports querying solution information +after modifications, you can use [`direct_model`](@ref) to bypass the +`MOI.OPTIMIZE_NOT_CALLED` state: +```jldoctest +julia> model = direct_model(GLPK.Optimizer()); + +julia> @variable(model, x >= 0); + +julia> optimize!(model) + +julia> termination_status(model) +OPTIMAL::TerminationStatusCode = 1 + +julia> set_upper_bound(x, 1) + +julia> x_val = value(x) +0.0 + +julia> set_lower_bound(x, x_val) + +julia> termination_status(model) +OPTIMAL::TerminationStatusCode = 1 ``` ```@meta diff --git a/src/JuMP.jl b/src/JuMP.jl index ef353cc3c6d..91c6ab91fc2 100644 --- a/src/JuMP.jl +++ b/src/JuMP.jl @@ -1234,22 +1234,30 @@ Return the value of the attribute `attr` from the model's MOI backend. function MOI.get(model::Model, attr::MOI.AbstractModelAttribute) if !MOI.is_set_by_optimize(attr) return MOI.get(backend(model), attr) - elseif attr isa MOI.TerminationStatus - if model.is_model_dirty && mode(model) != DIRECT - return MOI.OPTIMIZE_NOT_CALLED - end - return MOI.get(backend(model), attr) - elseif attr isa MOI.PrimalStatus || attr isa MOI.DualStatus - if model.is_model_dirty && mode(model) != DIRECT - return MOI.NO_SOLUTION - end - return MOI.get(backend(model), attr) - else - if model.is_model_dirty && mode(model) != DIRECT - throw(OptimizeNotCalled()) - end - return _moi_get_result(backend(model), attr) + elseif model.is_model_dirty && mode(model) != DIRECT + @warn( + "The model has been modified since the last call to `optimize!`. " * + "If you are iteratively querying solution information and " * + "modifying a model, query all the results first, then modify the " * + "model.", + ) + throw(OptimizeNotCalled()) end + return _moi_get_result(backend(model), attr) +end + +function MOI.get(model::Model, attr::MOI.TerminationStatus) + if model.is_model_dirty && mode(model) != DIRECT + return MOI.OPTIMIZE_NOT_CALLED + end + return MOI.get(backend(model), attr) +end + +function MOI.get(model::Model, attr::Union{MOI.PrimalStatus,MOI.DualStatus}) + if model.is_model_dirty && mode(model) != DIRECT + return MOI.NO_SOLUTION + end + return MOI.get(backend(model), attr) end """ From 9c264f022cddabc21ca0193310e4695d3ba0f238 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 3 Feb 2022 13:18:22 +1300 Subject: [PATCH 2/3] Update JuMP.jl --- src/JuMP.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/JuMP.jl b/src/JuMP.jl index 91c6ab91fc2..7bb227f527e 100644 --- a/src/JuMP.jl +++ b/src/JuMP.jl @@ -1236,10 +1236,10 @@ function MOI.get(model::Model, attr::MOI.AbstractModelAttribute) return MOI.get(backend(model), attr) elseif model.is_model_dirty && mode(model) != DIRECT @warn( - "The model has been modified since the last call to `optimize!`. " * - "If you are iteratively querying solution information and " * - "modifying a model, query all the results first, then modify the " * - "model.", + "The model has been modified since the last call to `optimize!` (" * + "or `optimize!` has not been called yet. If you are iteratively " * + "querying solution information and modifying a model, query all " * + "the results first, then modify the model.", ) throw(OptimizeNotCalled()) end From 65780e9750f324d4305fa9ffa6eac6e7a94e6b51 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 3 Feb 2022 13:18:50 +1300 Subject: [PATCH 3/3] Update src/JuMP.jl --- src/JuMP.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JuMP.jl b/src/JuMP.jl index 7bb227f527e..c07f0e393d7 100644 --- a/src/JuMP.jl +++ b/src/JuMP.jl @@ -1237,7 +1237,7 @@ function MOI.get(model::Model, attr::MOI.AbstractModelAttribute) elseif model.is_model_dirty && mode(model) != DIRECT @warn( "The model has been modified since the last call to `optimize!` (" * - "or `optimize!` has not been called yet. If you are iteratively " * + "or `optimize!` has not been called yet). If you are iteratively " * "querying solution information and modifying a model, query all " * "the results first, then modify the model.", )