Skip to content

Commit 0234623

Browse files
authored
Improve coverage (#198)
* start print tests * add test * fix ListOfConstraintIndices * fix attributes conditionals and printing * fix con name * add more tests * fmt * fix tests * Add tests * Add tests and improve coverage * format
1 parent 3d352ec commit 0234623

File tree

4 files changed

+429
-30
lines changed

4 files changed

+429
-30
lines changed

src/MOI_wrapper.jl

Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function _is_variable(v::MOI.VariableIndex)
77
end
88

99
function _is_parameter(v::MOI.VariableIndex)
10-
return PARAMETER_INDEX_THRESHOLD < v.value < PARAMETER_INDEX_THRESHOLD_MAX
10+
return PARAMETER_INDEX_THRESHOLD < v.value <= PARAMETER_INDEX_THRESHOLD_MAX
1111
end
1212

1313
function _has_parameters(f::MOI.ScalarAffineFunction{T}) where {T}
@@ -567,8 +567,11 @@ function MOI.supports(
567567
# We can't tell at type-time whether the constraints will be quadratic or
568568
# lowered to affine, so we return the conservative choice for supports of
569569
# needing to support names for both quadratic and affine constraints.
570-
return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) &&
571-
MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S})
570+
if MOI.supports_constraint(model.optimizer, F, S)
571+
return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) &&
572+
MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S})
573+
end
574+
return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S})
572575
end
573576

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

587596
function MOI.set(
@@ -804,10 +813,11 @@ end
804813
function MOI.modify(
805814
model::Optimizer,
806815
c::MOI.ConstraintIndex{F,S},
807-
chg::MOI.ScalarCoefficientChange{T},
816+
chg::Union{MOI.ScalarConstantChange{T},MOI.ScalarCoefficientChange{T}},
808817
) where {F,S,T}
809-
if haskey(model.quadratic_constraint_cache, c) ||
810-
haskey(model.affine_constraint_cache, c)
818+
if haskey(model.quadratic_outer_to_inner, c) ||
819+
haskey(model.vector_quadratic_outer_to_inner, c) ||
820+
haskey(model.affine_outer_to_inner, c)
811821
error(
812822
"Parametric constraint cannot be modified in ParametricOptInterface, because it would conflict with parameter updates. You can update the parameters instead.",
813823
)
@@ -1032,8 +1042,6 @@ function MOI.add_constraint(
10321042
model.constraint_outer_to_inner[outer_ci] = inner_ci
10331043
model.quadratic_constraint_cache_set[inner_ci] = set
10341044
return outer_ci
1035-
else
1036-
return _add_constraint_direct_and_cache_map!(model, f, set)
10371045
end
10381046
return _add_constraint_direct_and_cache_map!(model, f, set)
10391047
else
@@ -1365,16 +1373,47 @@ end
13651373
function MOI.get(
13661374
model::Optimizer,
13671375
::MOI.ListOfConstraintAttributesSet{F,S},
1368-
) where {F,S}
1369-
if F <: MOI.ScalarQuadraticFunction
1370-
error(
1371-
"MOI.ListOfConstraintAttributesSet is not implemented for ScalarQuadraticFunction in ParametricOptInterface.",
1372-
)
1373-
elseif F <: MOI.VectorQuadraticFunction
1374-
error(
1375-
"MOI.ListOfConstraintAttributesSet is not implemented for VectorQuadraticFunction in ParametricOptInterface.",
1376-
)
1376+
) where {T,F<:MOI.ScalarQuadraticFunction{T},S}
1377+
if MOI.supports_constraint(model.optimizer, F, S)
1378+
# in this case we cant tell if the constraint will be quadratic or
1379+
# lowered to affine
1380+
if model.warn_quad_affine_ambiguous
1381+
println(
1382+
"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.",
1383+
)
1384+
end
1385+
return []
13771386
end
1387+
return MOI.get(
1388+
model.optimizer,
1389+
MOI.ListOfConstraintAttributesSet{MOI.ScalarAffineFunction{T},S}(),
1390+
)
1391+
end
1392+
1393+
function MOI.get(
1394+
model::Optimizer,
1395+
::MOI.ListOfConstraintAttributesSet{F,S},
1396+
) where {T,F<:MOI.VectorQuadraticFunction{T},S}
1397+
if MOI.supports_constraint(model.optimizer, F, S)
1398+
# in this case we cant tell if the constraint will be quadratic or
1399+
# lowered to affine
1400+
if model.warn_quad_affine_ambiguous
1401+
println(
1402+
"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.",
1403+
)
1404+
end
1405+
return []
1406+
end
1407+
return MOI.get(
1408+
model.optimizer,
1409+
MOI.ListOfConstraintAttributesSet{MOI.VectorAffineFunction{T},S}(),
1410+
)
1411+
end
1412+
1413+
function MOI.get(
1414+
model::Optimizer,
1415+
::MOI.ListOfConstraintAttributesSet{F,S},
1416+
) where {F,S}
13781417
return MOI.get(model.optimizer, MOI.ListOfConstraintAttributesSet{F,S}())
13791418
end
13801419

@@ -1405,7 +1444,7 @@ function MOI.get(
14051444
model::Optimizer,
14061445
::MOI.ListOfConstraintIndices{F,S},
14071446
) where {S,F}
1408-
list = collect(values(model.constraint_outer_to_inner[F, S]))
1447+
list = collect(keys(model.constraint_outer_to_inner[F, S]))
14091448
sort!(list, lt = (x, y) -> (x.value < y.value))
14101449
return list
14111450
end
@@ -1495,8 +1534,12 @@ function MOI.get(
14951534
return MOI.get(model.optimizer, attr)
14961535
end
14971536

1498-
function MOI.supports(model::Optimizer, attr::MOI.AbstractConstraintAttribute)
1499-
return MOI.supports(model.optimizer, attr)
1537+
function MOI.supports(
1538+
model::Optimizer,
1539+
attr::MOI.AbstractConstraintAttribute,
1540+
::Type{T},
1541+
) where {T}
1542+
return MOI.supports(model.optimizer, attr, T)
15001543
end
15011544

15021545
function MOI.get(
@@ -2115,3 +2158,27 @@ end
21152158
function MOI.Utilities.final_touch(model::Optimizer, index_map)
21162159
return MOI.Utilities.final_touch(model.optimizer, index_map)
21172160
end
2161+
2162+
"""
2163+
_WarnIfQuadraticOfAffineFunctionAmbiguous
2164+
2165+
Some attributes such as `MOI.ListOfConstraintAttributesSet` are ambiguous
2166+
when the model contains parametric quadratic functions that can be lowered
2167+
to affine functions. This attribute can be set to `false` to skip the warning
2168+
when such ambiguity arises. The default value is `true`.
2169+
"""
2170+
struct _WarnIfQuadraticOfAffineFunctionAmbiguous <:
2171+
MOI.AbstractOptimizerAttribute end
2172+
2173+
function MOI.set(
2174+
model::Optimizer,
2175+
::_WarnIfQuadraticOfAffineFunctionAmbiguous,
2176+
value::Bool,
2177+
)
2178+
model.warn_quad_affine_ambiguous = value
2179+
return
2180+
end
2181+
2182+
function MOI.get(model::Optimizer, ::_WarnIfQuadraticOfAffineFunctionAmbiguous)
2183+
return model.warn_quad_affine_ambiguous
2184+
end

src/ParametricOptInterface.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
179179

180180
parameters_in_conflict::Set{MOI.VariableIndex}
181181

182+
warn_quad_affine_ambiguous::Bool
183+
182184
# extension data
183185
ext::Dict{Symbol,Any}
184186
function Optimizer{T}(
@@ -243,6 +245,7 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
243245
ONLY_CONSTRAINTS,
244246
save_original_objective_and_constraints,
245247
Set{MOI.VariableIndex}(),
248+
true,
246249
Dict{Symbol,Any}(),
247250
)
248251
end

test/jump_tests.jl

Lines changed: 147 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,24 +1654,163 @@ function test_jump_errors()
16541654
SCS.Optimizer(),
16551655
)
16561656
optimizer1 = POI.Optimizer(cached1)
1657-
model1 = direct_model(optimizer1)
1657+
model = direct_model(optimizer1)
16581658
@test_throws MOI.UnsupportedAttribute MOI.get(
1659-
backend(model1),
1659+
backend(model),
16601660
MOI.NLPBlock(),
16611661
)
1662-
@test_throws ErrorException MOI.get(
1663-
backend(model1),
1662+
1663+
MOI.get(
1664+
backend(model),
16641665
MOI.ListOfConstraintAttributesSet{
16651666
MOI.VectorQuadraticFunction{Float64},
1666-
MOI.Nonpositives,
1667+
MOI.Nonnegatives,
16671668
}(),
16681669
)
1669-
@test_throws ErrorException MOI.get(
1670-
backend(model1),
1670+
1671+
MOI.get(
1672+
backend(model),
16711673
MOI.ListOfConstraintAttributesSet{
16721674
MOI.ScalarQuadraticFunction{Float64},
1673-
MOI.EqualTo{Float64},
1675+
MOI.LessThan{Float64},
1676+
}(),
1677+
)
1678+
1679+
MOI.set(
1680+
backend(model),
1681+
POI._WarnIfQuadraticOfAffineFunctionAmbiguous(),
1682+
false,
1683+
)
1684+
1685+
@test MOI.get(
1686+
backend(model),
1687+
POI._WarnIfQuadraticOfAffineFunctionAmbiguous(),
1688+
) == false
1689+
1690+
MOI.get(
1691+
backend(model),
1692+
MOI.ListOfConstraintAttributesSet{
1693+
MOI.VectorQuadraticFunction{Float64},
1694+
MOI.Nonnegatives,
1695+
}(),
1696+
)
1697+
1698+
MOI.get(
1699+
backend(model),
1700+
MOI.ListOfConstraintAttributesSet{
1701+
MOI.ScalarQuadraticFunction{Float64},
1702+
MOI.LessThan{Float64},
1703+
}(),
1704+
)
1705+
1706+
model = direct_model(POI.Optimizer(Ipopt.Optimizer()))
1707+
1708+
@test_throws MOI.GetAttributeNotAllowed MOI.get(
1709+
backend(model),
1710+
MOI.ListOfConstraintAttributesSet{
1711+
MOI.VectorQuadraticFunction{Float64},
1712+
MOI.Nonnegatives,
1713+
}(),
1714+
)
1715+
1716+
MOI.get(
1717+
backend(model),
1718+
MOI.ListOfConstraintAttributesSet{
1719+
MOI.ScalarQuadraticFunction{Float64},
1720+
MOI.LessThan{Float64},
1721+
}(),
1722+
)
1723+
1724+
MOI.set(
1725+
backend(model),
1726+
POI._WarnIfQuadraticOfAffineFunctionAmbiguous(),
1727+
false,
1728+
)
1729+
1730+
@test MOI.get(
1731+
backend(model),
1732+
POI._WarnIfQuadraticOfAffineFunctionAmbiguous(),
1733+
) == false
1734+
1735+
@test_throws MOI.GetAttributeNotAllowed MOI.get(
1736+
backend(model),
1737+
MOI.ListOfConstraintAttributesSet{
1738+
MOI.VectorQuadraticFunction{Float64},
1739+
MOI.Nonnegatives,
1740+
}(),
1741+
)
1742+
1743+
MOI.get(
1744+
backend(model),
1745+
MOI.ListOfConstraintAttributesSet{
1746+
MOI.ScalarQuadraticFunction{Float64},
1747+
MOI.LessThan{Float64},
1748+
}(),
1749+
)
1750+
1751+
return
1752+
end
1753+
1754+
function test_print()
1755+
model = direct_model(POI.Optimizer(HiGHS.Optimizer()))
1756+
@variable(model, p in MOI.Parameter(1.0))
1757+
@variable(model, x)
1758+
@constraint(model, con, x >= p + p * p + p * x)
1759+
@objective(model, Min, 1 + 2x)
1760+
filename = tempdir() * "/test.lp"
1761+
write_to_file(model, filename)
1762+
@test readlines(filename) == [
1763+
"minimize",
1764+
"obj: 1 + 2 x",
1765+
"subject to",
1766+
"con: 1 x - 1 p + [ -1 p * x - 1 p ^ 2 ] >= 0",
1767+
"Bounds",
1768+
"x free",
1769+
"p = 1",
1770+
"End",
1771+
]
1772+
return
1773+
end
1774+
1775+
function test_set_normalized_coefficient()
1776+
model = direct_model(POI.Optimizer(HiGHS.Optimizer()))
1777+
@variable(model, p in MOI.Parameter(1.0))
1778+
@variable(model, x)
1779+
@constraint(model, con, x >= p)
1780+
@constraint(model, con1, x >= 1)
1781+
@constraint(model, con2, x >= x * p)
1782+
@test_throws ErrorException set_normalized_coefficient(con, x, 2.0)
1783+
set_normalized_coefficient(con1, x, 2.0)
1784+
@test_throws ErrorException set_normalized_coefficient(con2, x, 2.0)
1785+
return
1786+
end
1787+
1788+
function test_ListOfConstraintAttributesSet()
1789+
cached = MOI.Utilities.CachingOptimizer(
1790+
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()),
1791+
MOI.Utilities.AUTOMATIC,
1792+
)
1793+
optimizer = POI.Optimizer(cached)
1794+
model = direct_model(optimizer)
1795+
@variable(model, p in MOI.Parameter(1.0))
1796+
@variable(model, x)
1797+
@constraint(model, con, [x * p] in MOI.Nonnegatives(1))
1798+
ret = get_attribute(
1799+
model,
1800+
MOI.ListOfConstraintAttributesSet{
1801+
MOI.VectorQuadraticFunction{Float64},
1802+
MOI.Nonnegatives,
1803+
}(),
1804+
)
1805+
@test ret == []
1806+
set_attribute(model, POI._WarnIfQuadraticOfAffineFunctionAmbiguous(), false)
1807+
ret = get_attribute(
1808+
model,
1809+
MOI.ListOfConstraintAttributesSet{
1810+
MOI.VectorQuadraticFunction{Float64},
1811+
MOI.Nonnegatives,
16741812
}(),
16751813
)
1814+
@test ret == []
16761815
return
16771816
end

0 commit comments

Comments
 (0)