diff --git a/src/JuMP.jl b/src/JuMP.jl index 9c371eaa169..b08eb00ca74 100644 --- a/src/JuMP.jl +++ b/src/JuMP.jl @@ -549,21 +549,58 @@ function dual(cr::ConstraintRef{Model, <:MOICON}) dual_shape(cr.shape)) end +""" + struct OptimizeNotCalled <: Exception end + +A result attribute cannot be queried before [`optimize!`](@ref) is called. +""" +struct OptimizeNotCalled <: Exception end + +# Throws an error if `optimize!` has not been called, i.e., if there is no +# optimizer attached or if the termination statis is `MOI.OPTIMIZE_NOT_CALLED`. +function moi_get_result(model::MOI.ModelLike, args...) + if MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + throw(OptimizeNotCalled()) + end + return MOI.get(model, args...) +end +function moi_get_result(model::MOIU.CachingOptimizer, args...) + if MOIU.state(model) == MOIU.NO_OPTIMIZER || + MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + throw(OptimizeNotCalled()) + end + return MOI.get(model, args...) +end + """ get(m::JuMP.Model, attr::MathOptInterface.AbstractModelAttribute) Return the value of the attribute `attr` from model's MOI backend. """ -MOI.get(m::Model, attr::MOI.AbstractModelAttribute) = MOI.get(backend(m), attr) +function MOI.get(model::Model, attr::MOI.AbstractModelAttribute) + if MOI.is_set_by_optimize(attr) + moi_get_result(backend(model), attr) + else + MOI.get(backend(model), attr) + end +end function MOI.get(model::Model, attr::MOI.AbstractVariableAttribute, v::VariableRef) check_belongs_to_model(v, model) - return MOI.get(backend(model), attr, index(v)) + if MOI.is_set_by_optimize(attr) + return moi_get_result(backend(model), attr, index(v)) + else + return MOI.get(backend(model), attr, index(v)) + end end function MOI.get(model::Model, attr::MOI.AbstractConstraintAttribute, cr::ConstraintRef) check_belongs_to_model(cr, model) - return MOI.get(backend(model), attr, index(cr)) + if MOI.is_set_by_optimize(attr) + return moi_get_result(backend(model), attr, index(cr)) + else + return MOI.get(backend(model), attr, index(cr)) + end end MOI.set(m::Model, attr::MOI.AbstractModelAttribute, value) = MOI.set(backend(m), attr, value) diff --git a/test/model.jl b/test/model.jl index 63b7504af1c..a62caa20a7b 100644 --- a/test/model.jl +++ b/test/model.jl @@ -42,6 +42,19 @@ end include("nonnegative_bridge.jl") function test_model() + @testset "Result attributes" begin + err = JuMP.OptimizeNotCalled() + model = Model() + @variable(model, x) + c = @constraint(model, x ≤ 0) + @objective(model, Max, x) + @test_throws err JuMP.objective_value(model) + @test_throws err JuMP.objective_bound(model) + @test_throws err JuMP.value(x) + @test_throws err JuMP.value(c) + @test_throws err JuMP.dual(c) + end + @testset "Test variable/model 'hygiene'" begin model_x = Model() @variable(model_x, x)