Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add warning and improve docs for modify-then-query #2858

Merged
merged 3 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 65 additions & 16 deletions docs/src/manual/solutions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 23 additions & 15 deletions src/JuMP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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!` (" *
"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
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

"""
Expand Down