diff --git a/src/optimizer_interface.jl b/src/optimizer_interface.jl index aa42f18186b..98746686227 100644 --- a/src/optimizer_interface.jl +++ b/src/optimizer_interface.jl @@ -1283,3 +1283,142 @@ function _get_start_values( end return end + +""" + unsafe_get_backend_attribute( + model::GenericModel, + attr::Union{MOI.AbstractModelAttribute,MOI.AbstractOptimizerAttribute}, + ) + unsafe_get_backend_attribute( + x::GenericVariableRef, + attr::MOI.AbstractVariableAttribute, + ) + unsafe_get_backend_attribute( + c::ConstraintRef, + attr::MOI.AbstractConstraintAttribute, + ) + +Get the attribute `attr` directly from the [`unsafe_backend`](@ref) of the +associated model, skipping safety checks, such as whether the model was +previously solved. + +## Unsafe behavior + +This method is unsafe because its behavior depends on the implementation of the +backend solver. + +Where possible, use [`get_attribute`](@ref) instead. +""" +function unsafe_get_backend_attribute( + model::GenericModel, + attr::Union{MOI.AbstractModelAttribute,MOI.AbstractOptimizerAttribute}, +) + return _unsafe_get_backend_attribute(backend(model), attr) +end + +function unsafe_get_backend_attribute( + x::GenericVariableRef, + attr::MOI.AbstractVariableAttribute, +) + b = backend(owner_model(x)) + return _unsafe_get_backend_attribute(b, attr, index(x)) +end + +function unsafe_get_backend_attribute( + c::ConstraintRef, + attr::MOI.AbstractConstraintAttribute, +) + b = backend(owner_model(c)) + return _unsafe_get_backend_attribute(b, attr, index(c)) +end + +function _unsafe_get_backend_attribute(model::MOI.ModelLike, args...) + return MOI.get(model, args...) +end + +function _unsafe_get_backend_attribute( + model::MOI.Utilities.CachingOptimizer, + attr, + args..., +) + if MOI.Utilities.state(model) == MOI.Utilities.EMPTY_OPTIMIZER + MOI.Utilities.attach_optimizer(model) + end + if MOI.Utilities.state(model) != MOI.Utilities.ATTACHED_OPTIMIZER + error("Cannot get backend attribute because no optimizer is attached") + end + return MOI.get(model, MOI.Utilities.AttributeFromOptimizer(attr), args...) +end + +""" + unsafe_set_backend_attribute( + model::GenericModel, + attr::Union{MOI.AbstractModelAttribute,MOI.AbstractOptimizerAttribute}, + value, + ) + unsafe_set_backend_attribute( + x::GenericVariableRef, + attr::MOI.AbstractVariableAttribute, + value, + ) + unsafe_set_backend_attribute( + c::ConstraintRef, + attr::MOI.AbstractConstraintAttribute, + value, + ) + +Set the attribute `attr` of the [`unsafe_backend`](@ref) of the associated model +to `value`, skipping safety checks, such as whether the model was previously +solved. + +## Unsafe behavior + +This method is unsafe because its behavior depends on the implementation of the +backend solver. In addition, the attribute is not cached, and so subsequent +modifications to the model may silently discard the set value of the attribute. + +Where possible, use [`set_attribute`](@ref) instead. +""" +function unsafe_set_backend_attribute( + model::GenericModel, + attr::Union{MOI.AbstractModelAttribute,MOI.AbstractOptimizerAttribute}, + value, +) + return _unsafe_set_backend_attribute(backend(model), attr, value) +end + +function unsafe_set_backend_attribute( + x::GenericVariableRef, + attr::MOI.AbstractVariableAttribute, + value, +) + b = backend(owner_model(x)) + return _unsafe_set_backend_attribute(b, attr, index(x), value) +end + +function unsafe_set_backend_attribute( + c::ConstraintRef, + attr::MOI.AbstractConstraintAttribute, + value, +) + b = backend(owner_model(c)) + return _unsafe_set_backend_attribute(b, attr, index(c), value) +end + +function _unsafe_set_backend_attribute(model::MOI.ModelLike, args...) + return MOI.set(model, args...) +end + +function _unsafe_set_backend_attribute( + model::MOI.Utilities.CachingOptimizer, + attr, + args..., +) + if MOI.Utilities.state(model) == MOI.Utilities.EMPTY_OPTIMIZER + MOI.Utilities.attach_optimizer(model) + end + if MOI.Utilities.state(model) != MOI.Utilities.ATTACHED_OPTIMIZER + error("Cannot set backend attribute because no optimizer is attached") + end + return MOI.set(model, MOI.Utilities.AttributeFromOptimizer(attr), args...) +end diff --git a/test/test_model.jl b/test/test_model.jl index c9134eab2ad..cdd9ece2a61 100644 --- a/test/test_model.jl +++ b/test/test_model.jl @@ -1313,4 +1313,130 @@ function test_is_solved_and_feasible() return end +struct ModelAttribute3684 <: MOI.AbstractModelAttribute end + +MOI.get(::MOI.Utilities.MockOptimizer, ::ModelAttribute3684) = "m3684" + +struct VariableAttribute3684 <: MOI.AbstractVariableAttribute end + +function MOI.get( + ::MOI.Utilities.MockOptimizer, + ::VariableAttribute3684, + x::MOI.VariableIndex, +) + return "x3684-$(x.value)" +end + +struct ConstraintAttribute3684 <: MOI.AbstractConstraintAttribute end + +function MOI.get( + ::MOI.Utilities.MockOptimizer, + ::ConstraintAttribute3684, + c::MOI.ConstraintIndex, +) + return "c3684-$(c.value)" +end + +function test_get_backend_attribute_caching_optimizer() + mock = MOIU.UniversalFallback(MOIU.Model{Float64}()) + model = Model(() -> MOIU.MockOptimizer(mock)) + @variable(model, x) + @constraint(model, c, x <= 1) + # ModelAttribute + attr = ModelAttribute3684() + @test get_attribute(model, attr) === nothing + @test unsafe_get_backend_attribute(model, attr) == "m3684" + @test get_attribute(model, MOI.Name()) == "" + @test unsafe_get_backend_attribute(model, MOI.Name()) == "" + unsafe_set_backend_attribute(model, MOI.Name(), "foo") + @test get_attribute(model, MOI.Name()) == "" + @test unsafe_get_backend_attribute(model, MOI.Name()) == "foo" + # VariableAttribute + attr = VariableAttribute3684() + @test get_attribute(x, attr) === nothing + @test startswith(unsafe_get_backend_attribute(x, attr), "x3684-") + @test get_attribute(x, MOI.VariableName()) == "x" + @test unsafe_get_backend_attribute(x, MOI.VariableName()) == "x" + unsafe_set_backend_attribute(x, MOI.VariableName(), "foo") + @test get_attribute(x, MOI.VariableName()) == "x" + @test unsafe_get_backend_attribute(x, MOI.VariableName()) == "foo" + # ConnstraintAttribute + attr = ConstraintAttribute3684() + @test get_attribute(c, attr) === nothing + @test startswith(unsafe_get_backend_attribute(c, attr), "c3684-") + @test get_attribute(c, MOI.ConstraintName()) == "c" + @test unsafe_get_backend_attribute(c, MOI.ConstraintName()) == "c" + unsafe_set_backend_attribute(c, MOI.ConstraintName(), "foo") + @test get_attribute(c, MOI.ConstraintName()) == "c" + @test unsafe_get_backend_attribute(c, MOI.ConstraintName()) == "foo" + return +end + +function test_set_backend_attribute_caching_optimizer() + mock = MOIU.UniversalFallback(MOIU.Model{Float64}()) + model = Model(() -> MOIU.MockOptimizer(mock)) + # Call sett first, to check that we attach the CachingOptimizer + unsafe_set_backend_attribute(model, MOI.Name(), "foo") + @test get_attribute(model, MOI.Name()) == "" + @test unsafe_get_backend_attribute(model, MOI.Name()) == "foo" + return +end + +function test_get_backend_attribute_direct() + mock = MOIU.UniversalFallback(MOIU.Model{Float64}()) + model = direct_model(MOIU.MockOptimizer(mock)) + @variable(model, x) + @constraint(model, c, x <= 1) + # ModelAttribute + attr = ModelAttribute3684() + @test get_attribute(model, attr) === "m3684" + @test unsafe_get_backend_attribute(model, attr) == "m3684" + @test get_attribute(model, MOI.Name()) == "" + @test unsafe_get_backend_attribute(model, MOI.Name()) == "" + unsafe_set_backend_attribute(model, MOI.Name(), "foo") + @test get_attribute(model, MOI.Name()) == "foo" + @test unsafe_get_backend_attribute(model, MOI.Name()) == "foo" + # VariableAttribute + attr = VariableAttribute3684() + @test startswith(get_attribute(x, attr), "x3684-") + @test startswith(unsafe_get_backend_attribute(x, attr), "x3684-") + @test get_attribute(x, MOI.VariableName()) == "x" + @test unsafe_get_backend_attribute(x, MOI.VariableName()) == "x" + unsafe_set_backend_attribute(x, MOI.VariableName(), "foo") + @test get_attribute(x, MOI.VariableName()) == "foo" + @test unsafe_get_backend_attribute(x, MOI.VariableName()) == "foo" + # ConnstraintAttribute + attr = ConstraintAttribute3684() + @test startswith(get_attribute(c, attr), "c3684-") + @test startswith(unsafe_get_backend_attribute(c, attr), "c3684-") + @test get_attribute(c, MOI.ConstraintName()) == "c" + @test unsafe_get_backend_attribute(c, MOI.ConstraintName()) == "c" + unsafe_set_backend_attribute(c, MOI.ConstraintName(), "foo") + @test get_attribute(c, MOI.ConstraintName()) == "foo" + @test unsafe_get_backend_attribute(c, MOI.ConstraintName()) == "foo" + return +end + +function test_get_backend_attribute_no_optimizer() + model = Model() + @variable(model, x) + @constraint(model, c, x <= 1) + get_err = ErrorException( + "Cannot get backend attribute because no optimizer is attached", + ) + set_err = ErrorException( + "Cannot set backend attribute because no optimizer is attached", + ) + attr = ModelAttribute3684() + @test_throws get_err unsafe_get_backend_attribute(model, attr) + @test_throws set_err unsafe_set_backend_attribute(model, attr, "a") + attr = VariableAttribute3684() + @test_throws get_err unsafe_get_backend_attribute(x, attr) + @test_throws set_err unsafe_set_backend_attribute(x, attr, "a") + attr = ConstraintAttribute3684() + @test_throws get_err unsafe_get_backend_attribute(c, attr) + @test_throws set_err unsafe_set_backend_attribute(c, attr, "a") + return +end + end # module TestModels