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

DNM: Add unsafe_(g|s)et_backend_attribute #3685

Closed
wants to merge 3 commits into from
Closed
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
139 changes: 139 additions & 0 deletions src/optimizer_interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
126 changes: 126 additions & 0 deletions test/test_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading