Skip to content
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
99 changes: 82 additions & 17 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -567,8 +567,11 @@ function MOI.supports(
# We can't tell at type-time whether the constraints will be quadratic or
# lowered to affine, so we return the conservative choice for supports of
# needing to support names for both quadratic and affine constraints.
return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) &&
MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S})
if MOI.supports_constraint(model.optimizer, F, S)
return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) &&
MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S})
end
return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S})
end

function MOI.supports(
Expand All @@ -580,8 +583,14 @@ function MOI.supports(
# We can't tell at type-time whether the constraints will be quadratic or
# lowered to affine, so we return the conservative choice for supports of
# needing to support names for both quadratic and affine constraints.
return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) &&
MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S})
# TODO:
# switch to only check support name for the case of linear
# is a solver does not support quadratic constraints it will fain in add_
if MOI.supports_constraint(model.optimizer, F, S)
return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) &&
MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S})
end
return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S})
end

function MOI.set(
Expand Down Expand Up @@ -804,10 +813,11 @@ end
function MOI.modify(
model::Optimizer,
c::MOI.ConstraintIndex{F,S},
chg::MOI.ScalarCoefficientChange{T},
chg::Union{MOI.ScalarConstantChange{T},MOI.ScalarCoefficientChange{T}},
) where {F,S,T}
if haskey(model.quadratic_constraint_cache, c) ||
haskey(model.affine_constraint_cache, c)
if haskey(model.quadratic_outer_to_inner, c) ||
haskey(model.vector_quadratic_outer_to_inner, c) ||
haskey(model.affine_outer_to_inner, c)
error(
"Parametric constraint cannot be modified in ParametricOptInterface, because it would conflict with parameter updates. You can update the parameters instead.",
)
Expand Down Expand Up @@ -1365,16 +1375,47 @@ end
function MOI.get(
model::Optimizer,
::MOI.ListOfConstraintAttributesSet{F,S},
) where {F,S}
if F <: MOI.ScalarQuadraticFunction
error(
"MOI.ListOfConstraintAttributesSet is not implemented for ScalarQuadraticFunction in ParametricOptInterface.",
)
elseif F <: MOI.VectorQuadraticFunction
error(
"MOI.ListOfConstraintAttributesSet is not implemented for VectorQuadraticFunction in ParametricOptInterface.",
)
) where {T,F<:MOI.ScalarQuadraticFunction{T},S}
if MOI.supports_constraint(model.optimizer, F, S)
# in this case we cant tell if the constraint will be quadratic or
# lowered to affine
if model.warn_quad_affine_ambiguous
println(
"MOI.ListOfConstraintAttributesSet is not supported for ScalarQuadraticFunction in ParametricOptInterface, an empty list will be returned. This message can be suppressed by setting `POI._WarnIfQuadraticOfAffineFunctionAmbiguous` to false.",
)
end
return []
end
return MOI.get(
model.optimizer,
MOI.ListOfConstraintAttributesSet{MOI.ScalarAffineFunction{T},S}(),
)
end

function MOI.get(
model::Optimizer,
::MOI.ListOfConstraintAttributesSet{F,S},
) where {T,F<:MOI.VectorQuadraticFunction{T},S}
if MOI.supports_constraint(model.optimizer, F, S)
# in this case we cant tell if the constraint will be quadratic or
# lowered to affine
if model.warn_quad_affine_ambiguous
println(
"MOI.ListOfConstraintAttributesSet is not supported for VectorQuadraticFunction in ParametricOptInterface, an empty list will be returned. This message can be suppressed by setting `POI._WarnIfQuadraticOfAffineFunctionAmbiguous` to false.",
)
end
return []
end
return MOI.get(
model.optimizer,
MOI.ListOfConstraintAttributesSet{MOI.VectorAffineFunction{T},S}(),
)
end

function MOI.get(
model::Optimizer,
::MOI.ListOfConstraintAttributesSet{F,S},
) where {F,S}
return MOI.get(model.optimizer, MOI.ListOfConstraintAttributesSet{F,S}())
end

Expand Down Expand Up @@ -1405,7 +1446,7 @@ function MOI.get(
model::Optimizer,
::MOI.ListOfConstraintIndices{F,S},
) where {S,F}
list = collect(values(model.constraint_outer_to_inner[F, S]))
list = collect(keys(model.constraint_outer_to_inner[F, S]))
sort!(list, lt = (x, y) -> (x.value < y.value))
return list
end
Expand Down Expand Up @@ -2115,3 +2156,27 @@ end
function MOI.Utilities.final_touch(model::Optimizer, index_map)
return MOI.Utilities.final_touch(model.optimizer, index_map)
end

"""
_WarnIfQuadraticOfAffineFunctionAmbiguous

Some attributes such as `MOI.ListOfConstraintAttributesSet` are ambiguous
when the model contains parametric quadratic functions that can be lowered
to affine functions. This attribute can be set to `false` to skip the warning
when such ambiguity arises. The default value is `true`.
"""
struct _WarnIfQuadraticOfAffineFunctionAmbiguous <:
MOI.AbstractOptimizerAttribute end

function MOI.set(
model::Optimizer,
::_WarnIfQuadraticOfAffineFunctionAmbiguous,
value::Bool,
)
model.warn_quad_affine_ambiguous = value
return
end

function MOI.get(model::Optimizer, ::_WarnIfQuadraticOfAffineFunctionAmbiguous)
return model.warn_quad_affine_ambiguous
end
3 changes: 3 additions & 0 deletions src/ParametricOptInterface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer

parameters_in_conflict::Set{MOI.VariableIndex}

warn_quad_affine_ambiguous::Bool

# extension data
ext::Dict{Symbol,Any}
function Optimizer{T}(
Expand Down Expand Up @@ -243,6 +245,7 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
ONLY_CONSTRAINTS,
save_original_objective_and_constraints,
Set{MOI.VariableIndex}(),
true,
Dict{Symbol,Any}(),
)
end
Expand Down
155 changes: 147 additions & 8 deletions test/jump_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1654,24 +1654,163 @@ function test_jump_errors()
SCS.Optimizer(),
)
optimizer1 = POI.Optimizer(cached1)
model1 = direct_model(optimizer1)
model = direct_model(optimizer1)
@test_throws MOI.UnsupportedAttribute MOI.get(
backend(model1),
backend(model),
MOI.NLPBlock(),
)
@test_throws ErrorException MOI.get(
backend(model1),

MOI.get(
backend(model),
MOI.ListOfConstraintAttributesSet{
MOI.VectorQuadraticFunction{Float64},
MOI.Nonpositives,
MOI.Nonnegatives,
}(),
)
@test_throws ErrorException MOI.get(
backend(model1),

MOI.get(
backend(model),
MOI.ListOfConstraintAttributesSet{
MOI.ScalarQuadraticFunction{Float64},
MOI.EqualTo{Float64},
MOI.LessThan{Float64},
}(),
)

MOI.set(
backend(model),
POI._WarnIfQuadraticOfAffineFunctionAmbiguous(),
false,
)

@test MOI.get(
backend(model),
POI._WarnIfQuadraticOfAffineFunctionAmbiguous(),
) == false

MOI.get(
backend(model),
MOI.ListOfConstraintAttributesSet{
MOI.VectorQuadraticFunction{Float64},
MOI.Nonnegatives,
}(),
)

MOI.get(
backend(model),
MOI.ListOfConstraintAttributesSet{
MOI.ScalarQuadraticFunction{Float64},
MOI.LessThan{Float64},
}(),
)

model = direct_model(POI.Optimizer(Ipopt.Optimizer()))

@test_throws MOI.GetAttributeNotAllowed MOI.get(
backend(model),
MOI.ListOfConstraintAttributesSet{
MOI.VectorQuadraticFunction{Float64},
MOI.Nonnegatives,
}(),
)

MOI.get(
backend(model),
MOI.ListOfConstraintAttributesSet{
MOI.ScalarQuadraticFunction{Float64},
MOI.LessThan{Float64},
}(),
)

MOI.set(
backend(model),
POI._WarnIfQuadraticOfAffineFunctionAmbiguous(),
false,
)

@test MOI.get(
backend(model),
POI._WarnIfQuadraticOfAffineFunctionAmbiguous(),
) == false

@test_throws MOI.GetAttributeNotAllowed MOI.get(
backend(model),
MOI.ListOfConstraintAttributesSet{
MOI.VectorQuadraticFunction{Float64},
MOI.Nonnegatives,
}(),
)

MOI.get(
backend(model),
MOI.ListOfConstraintAttributesSet{
MOI.ScalarQuadraticFunction{Float64},
MOI.LessThan{Float64},
}(),
)

return
end

function test_print()
model = direct_model(POI.Optimizer(HiGHS.Optimizer()))
@variable(model, p in MOI.Parameter(1.0))
@variable(model, x)
@constraint(model, con, x >= p + p * p + p * x)
@objective(model, Min, 1 + 2x)
filename = tempdir() * "/test.lp"
write_to_file(model, filename)
@test readlines(filename) == [
"minimize",
"obj: 1 + 2 x",
"subject to",
"con: 1 x - 1 p + [ -1 p * x - 1 p ^ 2 ] >= 0",
"Bounds",
"x free",
"p = 1",
"End",
]
return
end

function test_set_normalized_coefficient()
model = direct_model(POI.Optimizer(HiGHS.Optimizer()))
@variable(model, p in MOI.Parameter(1.0))
@variable(model, x)
@constraint(model, con, x >= p)
@constraint(model, con1, x >= 1)
@constraint(model, con2, x >= x * p)
@test_throws ErrorException set_normalized_coefficient(con, x, 2.0)
set_normalized_coefficient(con1, x, 2.0)
@test_throws ErrorException set_normalized_coefficient(con2, x, 2.0)
return
end

function test_ListOfConstraintAttributesSet()
cached = MOI.Utilities.CachingOptimizer(
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()),
MOI.Utilities.AUTOMATIC,
)
optimizer = POI.Optimizer(cached)
model = direct_model(optimizer)
@variable(model, p in MOI.Parameter(1.0))
@variable(model, x)
@constraint(model, con, [x * p] in MOI.Nonnegatives(1))
ret = get_attribute(
model,
MOI.ListOfConstraintAttributesSet{
MOI.VectorQuadraticFunction{Float64},
MOI.Nonnegatives,
}(),
)
@test ret == []
set_attribute(model, POI._WarnIfQuadraticOfAffineFunctionAmbiguous(), false)
ret = get_attribute(
model,
MOI.ListOfConstraintAttributesSet{
MOI.VectorQuadraticFunction{Float64},
MOI.Nonnegatives,
}(),
)
@test ret == []
return
end
Loading