From 6b9281454d80e3a3a1deca9782e38dd6d315931f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 25 Mar 2024 16:02:41 +0100 Subject: [PATCH 1/4] [QCQP] Implement variable and constraint bridges --- src/QCQP/MOI_wrapper.jl | 108 +++++++++++++++++++++++++++++++++++----- test/qcqp.jl | 64 +++++++++++++++++++----- test/qcqp_extra.jl | 5 +- 3 files changed, 150 insertions(+), 27 deletions(-) diff --git a/src/QCQP/MOI_wrapper.jl b/src/QCQP/MOI_wrapper.jl index 8eedbad..4d27f20 100644 --- a/src/QCQP/MOI_wrapper.jl +++ b/src/QCQP/MOI_wrapper.jl @@ -1,12 +1,12 @@ import MathOptInterface as MOI +const _Model{F,S} = MOI.Utilities.UniversalFallback{MOI.Utilities.VectorOfConstraints{F,S}} + mutable struct Optimizer{T,O<:MOI.ModelLike} <: MOI.AbstractOptimizer model::O objective::Union{Nothing,PolyJuMP.ScalarPolynomialFunction{T}} - constraints::DataStructures.OrderedDict{ - Type, - Tuple{Type,MOI.Utilities.VectorOfConstraints}, - } + constraints::DataStructures.OrderedDict{Type,Tuple{Type,_Model}} + index_map::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex} end function Optimizer{T}(model::MOI.ModelLike) where {T} @@ -14,6 +14,7 @@ function Optimizer{T}(model::MOI.ModelLike) where {T} model, nothing, DataStructures.OrderedDict{Type,MOI.Utilities.VectorOfConstraints}(), + Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}(), ) end @@ -34,6 +35,7 @@ function MOI.empty!(model::Optimizer) MOI.empty!(model.model) model.objective = nothing empty!(model.constraints) + empty!(model.index_map) return end @@ -46,6 +48,32 @@ function MOI.is_valid( MOI.is_valid(model.constraints[S][2], ci) end +function MOI.supports( + model::Optimizer, + attr::MOI.AbstractConstraintAttribute, + C::Type{<:MOI.ConstraintIndex}, +) + return MOI.supports(model.model, attr, C) +end + +function MOI.set( + model::Optimizer, + attr::MOI.AbstractConstraintAttribute, + ci::MOI.ConstraintIndex, + value, +) + return MOI.set(model.model, attr, ci, value) +end + +function MOI.set( + model::Optimizer{T}, + attr::MOI.AbstractConstraintAttribute, + ci::MOI.ConstraintIndex{<:PolyJuMP.ScalarPolynomialFunction{T},S}, + value, +) where {T,S<:MOI.AbstractScalarSet} + return MOI.get(model.constraints[S][2], attr, model.index_map[ci], value) +end + function MOI.get( model::Optimizer, attr::MOI.AbstractConstraintAttribute, @@ -54,8 +82,28 @@ function MOI.get( return MOI.get(model.model, attr, ci) end +function MOI.get( + model::Optimizer{T}, + attr::MOI.AbstractConstraintAttribute, + ci::MOI.ConstraintIndex{<:PolyJuMP.ScalarPolynomialFunction{T},S}, +) where {T,S<:MOI.AbstractScalarSet} + if MOI.is_set_by_optimize(attr) + return MOI.get(model.model, attr, model.index_map[ci]) + else + return MOI.get(model.constraints[S][2], attr, ci) + end +end + MOI.add_variable(model::Optimizer) = MOI.add_variable(model.model) +function MOI.supports( + model::Optimizer, + attr::MOI.AbstractVariableAttribute, + ::Type{MOI.VariableIndex}, +) + return MOI.supports(model.model, attr, MOI.VariableIndex) +end + function MOI.set( model::Optimizer, attr::MOI.AbstractVariableAttribute, @@ -158,7 +206,7 @@ function MOI.add_constraint( F = typeof(func) S = typeof(set) if !haskey(model.constraints, S) - con = MOI.Utilities.VectorOfConstraints{F,S}() + con = MOI.Utilities.UniversalFallback(MOI.Utilities.VectorOfConstraints{F,S}()) model.constraints[S] = (P, con) end return MOI.add_constraint(model.constraints[S][2], func, set) @@ -168,7 +216,7 @@ function MOI.get( model::Optimizer{T}, attr::Union{MOI.ConstraintFunction,MOI.ConstraintSet}, ci::MOI.ConstraintIndex{<:PolyJuMP.ScalarPolynomialFunction{T},S}, -) where {T,S} +) where {T,S<:MOI.AbstractScalarSet} return MOI.get(model.constraints[S][2], attr, ci) end @@ -296,6 +344,7 @@ function monomial_variable_index( # If we don't have a variable for `mono` yet, # we create one now by equal to `x * y`. mono_bounds = IntervalArithmetic.interval(one(T)) + mono_start = one(T) for var in MP.variables(mono) deg = MP.degree(mono, var) if deg == 0 @@ -309,6 +358,15 @@ function monomial_variable_index( ub_var == typemax(ub_var) ? typemax(F) : float(ub_var), ) mono_bounds *= var_bounds^deg + attr = MOI.VariablePrimalStart() + if !isnothing(mono_start) && MOI.supports(model, attr, MOI.VariableIndex) + start = MOI.get(model, attr, vi) + if isnothing(start) + mono_start = nothing + else + mono_start *= start^deg + end + end end x = div[mono] vx = monomial_variable_index(model, d, div, x) @@ -338,6 +396,9 @@ function monomial_variable_index( else d[mono], _ = MOI.add_constrained_variable(model.model, set) end + if !isnothing(mono_start) && MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex) + MOI.set(model.model, MOI.VariablePrimalStart(), d[mono], mono_start) + end MOI.Utilities.normalize_and_add_constraint( model, MA.@rewrite(one(T) * d[mono] - one(T) * vx * vy), @@ -348,14 +409,36 @@ function monomial_variable_index( return d[mono] end -function _add_constraints(model::Optimizer, cis, index_to_var, d, div) - for ci in cis - func = MOI.get(model, MOI.ConstraintFunction(), ci) - set = MOI.get(model, MOI.ConstraintSet(), ci) +function _add_constraints(dest, src, index_map, cis_src::Vector{MOI.ConstraintIndex{F,S}}, index_to_var, d, div) where {F,S} + for ci in cis_src + func = MOI.get(src, MOI.ConstraintFunction(), ci) + set = MOI.get(src, MOI.ConstraintSet(), ci) func, index_to_var = _subs!(func, index_to_var) quad = _quad_convert(func.polynomial, d, div) - MOI.Utilities.normalize_and_add_constraint(model, quad, set) + dest_ci = MOI.Utilities.normalize_and_add_constraint(dest, quad, set) + index_map[ci] = dest_ci end + # `Utilities.pass_attributes` needs `index_map` to be an `IndexMap` :( + #MOI.Utilities.pass_attributes(dest, src, index_map, cis_src) + # `ListOfConstraintAttributesSet` not defined for `VectorOfConstraints` +# for attr in MOI.get(src, MOI.ListOfConstraintAttributesSet{F,S}()) +# if !MOI.supports(dest, attr) +# if attr == MOI.Name() +# continue # Skipping names is okay. +# end +# end +# for ci in cis_src +# value = MOI.get(src, attr, ci) +# if value !== nothing +# MOI.set( +# dest, +# attr, +# index_map[ci], +# MOI.Utilities.map_indices(index_map, attr, value), +# ) +# end +# end +# end return end @@ -396,7 +479,8 @@ function MOI.Utilities.final_touch(model::Optimizer{T}, _) where {T} for S in keys(model.constraints) F = PolyJuMP.ScalarPolynomialFunction{T,model.constraints[S][1]} cis = MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) - _add_constraints(model, cis, index_to_var, vars, div) + src = model.constraints[S][2] + _add_constraints(model.model, src, model.index_map, cis, index_to_var, vars, div) end end return diff --git a/test/qcqp.jl b/test/qcqp.jl index acb4d1c..68e0adc 100644 --- a/test/qcqp.jl +++ b/test/qcqp.jl @@ -5,6 +5,7 @@ using Test import MathOptInterface as MOI import MultivariatePolynomials as MP import PolyJuMP +import JuMP function _test_decompose(monos, exps) vars = MP.variables(monos) @@ -49,7 +50,7 @@ end function _test_objective_or_constraint(x, y, T, obj::Bool) inner = Model{T}() optimizer = MOI.Utilities.MockOptimizer(inner) - model = PolyJuMP.JuMP.GenericModel{T}( + model = JuMP.GenericModel{T}( () -> PolyJuMP.QCQP.Optimizer{T}(optimizer), ) PolyJuMP.@variable(model, 1 <= a <= 2) @@ -100,7 +101,7 @@ end function test_objective_and_constraint(x, y, T) inner = Model{T}() optimizer = MOI.Utilities.MockOptimizer(inner) - model = PolyJuMP.JuMP.GenericModel{T}( + model = JuMP.GenericModel{T}( () -> PolyJuMP.QCQP.Optimizer{T}(optimizer), ) PolyJuMP.@variable(model, -2 <= a <= 3) @@ -146,7 +147,7 @@ end function test_no_monomials(x, y, T) inner = Model{T}() - model = PolyJuMP.JuMP.GenericModel{T}() do + model = JuMP.GenericModel{T}() do return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner)) end PolyJuMP.@variable(model, 0 <= x[1:2] <= 2) @@ -159,7 +160,7 @@ end function test_scalar_constant_not_zero(x, y, T) inner = Model{T}() - model = PolyJuMP.JuMP.GenericModel{T}() do + model = JuMP.GenericModel{T}() do return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner)) end PolyJuMP.@variable(model, -1 <= x[1:3] <= 2) @@ -179,7 +180,7 @@ end function test_unbound_polynomial(x, y, T) inner = Model{T}() - model = PolyJuMP.JuMP.GenericModel{T}() do + model = JuMP.GenericModel{T}() do return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner)) end PolyJuMP.@variable(model, x >= 0) @@ -201,7 +202,7 @@ end function test_scalar_nonlinear_function(x, y, T) inner = Model{T}() - model = PolyJuMP.JuMP.GenericModel{T}() do + model = JuMP.GenericModel{T}() do return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner)) end PolyJuMP.@variable(model, 0 <= x <= 1) @@ -217,7 +218,7 @@ end function test_scalar_nonlinear_function_div_rem_zero(x, y, T) inner = Model{T}() - model = PolyJuMP.JuMP.GenericModel{T}() do + model = JuMP.GenericModel{T}() do return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner)) end PolyJuMP.@variable(model, x) @@ -233,7 +234,7 @@ end function test_scalar_nonlinear_function_div_rem_err(x, y, T) inner = Model{T}() - model = PolyJuMP.JuMP.GenericModel{T}() do + model = JuMP.GenericModel{T}() do return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner)) end PolyJuMP.@variable(model, x) @@ -249,7 +250,7 @@ end function test_scalar_nonlinear_function_div_rem_number(x, y, T) inner = Model{T}() - model = PolyJuMP.JuMP.GenericModel{T}() do + model = JuMP.GenericModel{T}() do return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner)) end PolyJuMP.@variable(model, x) @@ -264,15 +265,52 @@ end function test_variable_primal(x, y, T) inner = Model{T}() optimizer = MOI.Utilities.MockOptimizer(inner) - model = PolyJuMP.JuMP.direct_generic_model( + model = JuMP.direct_generic_model( T, - PolyJuMP.QCQP.Optimizer{T}(optimizer), + MOI.instantiate( + () -> PolyJuMP.QCQP.Optimizer{T}(optimizer), + with_bridge_type = T, + ), ) - PolyJuMP.@variable(model, 1 <= a <= 3) + JuMP.@variable(model, 1 <= a <= 3) + aff = JuMP.@constraint(model, a <= 1) + cub = JuMP.@constraint(model, a^3 <= 1) MOI.set(model, MOI.VariablePrimal(), a, T(2)) MOI.set(model, MOI.TerminationStatus(), MOI.OPTIMAL) + MOI.set(model, MOI.ConstraintDual(), aff, T(3)) + MOI.Utilities.final_touch(JuMP.backend(model), nothing) + inner = JuMP.backend(model).model + F = MOI.ScalarQuadraticFunction{T} + S = MOI.LessThan{T} + ci = first(MOI.get(inner, MOI.ListOfConstraintIndices{F,S}())) + MOI.set(inner, MOI.ConstraintDual(), ci, T(4)) @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL - @test PolyJuMP.value(a) == 2 + @test JuMP.value(a) == 2 + @test JuMP.dual(aff) == 3 + @test JuMP.dual(cub) == 4 +end + +# We test names as it's supported by `MOI.Utilities.Model` +function test_name(x, y, T) + model = JuMP.GenericModel{T}() + JuMP.@variable(model, 1 <= a <= 3) + JuMP.@variable(model, 1 <= b <= 3) + JuMP.@constraint(model, aff, a >= b) + JuMP.@constraint(model, con_ref, a^3 >= a*b^4) + inner = Model{T}() + qcqp = MOI.instantiate(() -> PolyJuMP.QCQP.Optimizer{T}(inner), with_bridge_type = T) + idxmap = MOI.copy_to(qcqp, JuMP.backend(model)) + attr = MOI.VariableName() + @test MOI.get(qcqp, attr, idxmap[JuMP.index(a)]) == "a" + @test MOI.get(qcqp, attr, idxmap[JuMP.index(b)]) == "b" + attr = MOI.ConstraintName() + @test MOI.get(qcqp, attr, idxmap[JuMP.index(aff)]) == "aff" + @test MOI.get(qcqp, attr, idxmap[JuMP.index(con_ref)]) == "con_ref" + inner = qcqp.model.model + F = MOI.ScalarQuadraticFunction{T} + S = MOI.GreaterThan{T} + ci = first(MOI.get(inner, MOI.ListOfConstraintIndices{F,S}())) + @test_broken MOI.get(inner, attr, ci) == "con_ref" end function runtests(x, y) diff --git a/test/qcqp_extra.jl b/test/qcqp_extra.jl index 22b0c1e..d234bc7 100644 --- a/test/qcqp_extra.jl +++ b/test/qcqp_extra.jl @@ -5,6 +5,7 @@ using Test import MathOptInterface as MOI import MultivariatePolynomials as MP import PolyJuMP +import JuMP import Random MOI.Utilities.@model( @@ -63,7 +64,7 @@ end function test_unconstrained_before_projection(T) inner = Model{T}() optimizer = MOI.Utilities.MockOptimizer(inner) - model = PolyJuMP.JuMP.GenericModel{T}( + model = JuMP.GenericModel{T}( () -> PolyJuMP.QCQP.Optimizer{T}(optimizer), ) PolyJuMP.@variable(model, -1 <= a[1:2] <= 1) @@ -81,7 +82,7 @@ end function test_unconstrained_after_projection(T) inner = Model{T}() optimizer = MOI.Utilities.MockOptimizer(inner) - model = PolyJuMP.JuMP.GenericModel{T}( + model = JuMP.GenericModel{T}( () -> PolyJuMP.QCQP.Optimizer{T}(optimizer), ) PolyJuMP.@variable(model, -1 <= a <= 1) From ddd80c16d38bbe7cbcd514fe719f1dfa22882b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 25 Mar 2024 16:32:49 +0100 Subject: [PATCH 2/4] Fix format --- src/QCQP/MOI_wrapper.jl | 69 +++++++++++++++++++++++++++-------------- test/qcqp.jl | 15 +++++---- test/qcqp_extra.jl | 8 ++--- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/QCQP/MOI_wrapper.jl b/src/QCQP/MOI_wrapper.jl index 4d27f20..352eac3 100644 --- a/src/QCQP/MOI_wrapper.jl +++ b/src/QCQP/MOI_wrapper.jl @@ -1,6 +1,7 @@ import MathOptInterface as MOI -const _Model{F,S} = MOI.Utilities.UniversalFallback{MOI.Utilities.VectorOfConstraints{F,S}} +const _Model{F,S} = + MOI.Utilities.UniversalFallback{MOI.Utilities.VectorOfConstraints{F,S}} mutable struct Optimizer{T,O<:MOI.ModelLike} <: MOI.AbstractOptimizer model::O @@ -206,7 +207,9 @@ function MOI.add_constraint( F = typeof(func) S = typeof(set) if !haskey(model.constraints, S) - con = MOI.Utilities.UniversalFallback(MOI.Utilities.VectorOfConstraints{F,S}()) + con = MOI.Utilities.UniversalFallback( + MOI.Utilities.VectorOfConstraints{F,S}(), + ) model.constraints[S] = (P, con) end return MOI.add_constraint(model.constraints[S][2], func, set) @@ -359,7 +362,8 @@ function monomial_variable_index( ) mono_bounds *= var_bounds^deg attr = MOI.VariablePrimalStart() - if !isnothing(mono_start) && MOI.supports(model, attr, MOI.VariableIndex) + if !isnothing(mono_start) && + MOI.supports(model, attr, MOI.VariableIndex) start = MOI.get(model, attr, vi) if isnothing(start) mono_start = nothing @@ -396,7 +400,8 @@ function monomial_variable_index( else d[mono], _ = MOI.add_constrained_variable(model.model, set) end - if !isnothing(mono_start) && MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex) + if !isnothing(mono_start) && + MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex) MOI.set(model.model, MOI.VariablePrimalStart(), d[mono], mono_start) end MOI.Utilities.normalize_and_add_constraint( @@ -409,7 +414,15 @@ function monomial_variable_index( return d[mono] end -function _add_constraints(dest, src, index_map, cis_src::Vector{MOI.ConstraintIndex{F,S}}, index_to_var, d, div) where {F,S} +function _add_constraints( + dest, + src, + index_map, + cis_src::Vector{MOI.ConstraintIndex{F,S}}, + index_to_var, + d, + div, +) where {F,S} for ci in cis_src func = MOI.get(src, MOI.ConstraintFunction(), ci) set = MOI.get(src, MOI.ConstraintSet(), ci) @@ -421,24 +434,24 @@ function _add_constraints(dest, src, index_map, cis_src::Vector{MOI.ConstraintIn # `Utilities.pass_attributes` needs `index_map` to be an `IndexMap` :( #MOI.Utilities.pass_attributes(dest, src, index_map, cis_src) # `ListOfConstraintAttributesSet` not defined for `VectorOfConstraints` -# for attr in MOI.get(src, MOI.ListOfConstraintAttributesSet{F,S}()) -# if !MOI.supports(dest, attr) -# if attr == MOI.Name() -# continue # Skipping names is okay. -# end -# end -# for ci in cis_src -# value = MOI.get(src, attr, ci) -# if value !== nothing -# MOI.set( -# dest, -# attr, -# index_map[ci], -# MOI.Utilities.map_indices(index_map, attr, value), -# ) -# end -# end -# end + # for attr in MOI.get(src, MOI.ListOfConstraintAttributesSet{F,S}()) + # if !MOI.supports(dest, attr) + # if attr == MOI.Name() + # continue # Skipping names is okay. + # end + # end + # for ci in cis_src + # value = MOI.get(src, attr, ci) + # if value !== nothing + # MOI.set( + # dest, + # attr, + # index_map[ci], + # MOI.Utilities.map_indices(index_map, attr, value), + # ) + # end + # end + # end return end @@ -480,7 +493,15 @@ function MOI.Utilities.final_touch(model::Optimizer{T}, _) where {T} F = PolyJuMP.ScalarPolynomialFunction{T,model.constraints[S][1]} cis = MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) src = model.constraints[S][2] - _add_constraints(model.model, src, model.index_map, cis, index_to_var, vars, div) + _add_constraints( + model.model, + src, + model.index_map, + cis, + index_to_var, + vars, + div, + ) end end return diff --git a/test/qcqp.jl b/test/qcqp.jl index 68e0adc..43431eb 100644 --- a/test/qcqp.jl +++ b/test/qcqp.jl @@ -50,9 +50,7 @@ end function _test_objective_or_constraint(x, y, T, obj::Bool) inner = Model{T}() optimizer = MOI.Utilities.MockOptimizer(inner) - model = JuMP.GenericModel{T}( - () -> PolyJuMP.QCQP.Optimizer{T}(optimizer), - ) + model = JuMP.GenericModel{T}(() -> PolyJuMP.QCQP.Optimizer{T}(optimizer)) PolyJuMP.@variable(model, 1 <= a <= 2) PolyJuMP.@variable(model, -5 <= b <= 3) PolyJuMP.@constraint(model, a + b >= 1) @@ -101,9 +99,7 @@ end function test_objective_and_constraint(x, y, T) inner = Model{T}() optimizer = MOI.Utilities.MockOptimizer(inner) - model = JuMP.GenericModel{T}( - () -> PolyJuMP.QCQP.Optimizer{T}(optimizer), - ) + model = JuMP.GenericModel{T}(() -> PolyJuMP.QCQP.Optimizer{T}(optimizer)) PolyJuMP.@variable(model, -2 <= a <= 3) PolyJuMP.@variable(model, 5 <= b <= 7) PolyJuMP.@constraint(model, 0 <= a^3 <= 1) @@ -296,9 +292,12 @@ function test_name(x, y, T) JuMP.@variable(model, 1 <= a <= 3) JuMP.@variable(model, 1 <= b <= 3) JuMP.@constraint(model, aff, a >= b) - JuMP.@constraint(model, con_ref, a^3 >= a*b^4) + JuMP.@constraint(model, con_ref, a^3 >= a * b^4) inner = Model{T}() - qcqp = MOI.instantiate(() -> PolyJuMP.QCQP.Optimizer{T}(inner), with_bridge_type = T) + qcqp = MOI.instantiate( + () -> PolyJuMP.QCQP.Optimizer{T}(inner), + with_bridge_type = T, + ) idxmap = MOI.copy_to(qcqp, JuMP.backend(model)) attr = MOI.VariableName() @test MOI.get(qcqp, attr, idxmap[JuMP.index(a)]) == "a" diff --git a/test/qcqp_extra.jl b/test/qcqp_extra.jl index d234bc7..30b5451 100644 --- a/test/qcqp_extra.jl +++ b/test/qcqp_extra.jl @@ -64,9 +64,7 @@ end function test_unconstrained_before_projection(T) inner = Model{T}() optimizer = MOI.Utilities.MockOptimizer(inner) - model = JuMP.GenericModel{T}( - () -> PolyJuMP.QCQP.Optimizer{T}(optimizer), - ) + model = JuMP.GenericModel{T}(() -> PolyJuMP.QCQP.Optimizer{T}(optimizer)) PolyJuMP.@variable(model, -1 <= a[1:2] <= 1) PolyJuMP.@objective(model, Min, a[1]^2 * a[2]^2) PolyJuMP.optimize!(model) @@ -82,9 +80,7 @@ end function test_unconstrained_after_projection(T) inner = Model{T}() optimizer = MOI.Utilities.MockOptimizer(inner) - model = JuMP.GenericModel{T}( - () -> PolyJuMP.QCQP.Optimizer{T}(optimizer), - ) + model = JuMP.GenericModel{T}(() -> PolyJuMP.QCQP.Optimizer{T}(optimizer)) PolyJuMP.@variable(model, -1 <= a <= 1) PolyJuMP.@objective(model, Min, a^2) PolyJuMP.optimize!(model) From d1e34b22289e1eeeeb900134625d4b1d61d828f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 25 Mar 2024 17:03:02 +0100 Subject: [PATCH 3/4] Add tests --- test/qcqp.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/qcqp.jl b/test/qcqp.jl index 43431eb..87862e7 100644 --- a/test/qcqp.jl +++ b/test/qcqp.jl @@ -312,6 +312,20 @@ function test_name(x, y, T) @test_broken MOI.get(inner, attr, ci) == "con_ref" end +function test_start(x, y, T) + inner = MOI.Utilities.UniversalFallback(Model{T}()) + model = PolyJuMP.QCQP.Optimizer{T}(inner) + a = MOI.add_variable(model) + MOI.set(model, MOI.VariablePrimalStart(), a, 2one(T)) + b = MOI.add_variable(model) + MOI.set(model, MOI.VariablePrimalStart(), b, 3one(T)) + p = PolyJuMP.ScalarPolynomialFunction(one(T) * x^3 - x * y^2, [a, b]) + MOI.add_constraint(model, p, MOI.LessThan(zero(T))) + MOI.Utilities.final_touch(model, nothing) + vis = MOI.get(inner, MOI.ListOfVariableIndices()) + @test sort(MOI.get(inner, MOI.VariablePrimalStart(), vis)) == T[2, 3, 4, 9] +end + function runtests(x, y) for name in names(@__MODULE__; all = true) if startswith("$name", "test_") From 05e6f992f8de1821f904df04327c5245a693c046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 26 Mar 2024 15:13:00 +0100 Subject: [PATCH 4/4] Add tests --- src/QCQP/MOI_wrapper.jl | 2 +- test/qcqp.jl | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/QCQP/MOI_wrapper.jl b/src/QCQP/MOI_wrapper.jl index 352eac3..d64e37f 100644 --- a/src/QCQP/MOI_wrapper.jl +++ b/src/QCQP/MOI_wrapper.jl @@ -43,7 +43,7 @@ end MOI.is_valid(model::Optimizer, i::MOI.Index) = MOI.is_valid(model.model, i) function MOI.is_valid( model::Optimizer{T}, - ::MOI.ConstraintIndex{PolyJuMP.ScalarPolynomialFunction{T},S}, + ci::MOI.ConstraintIndex{<:PolyJuMP.ScalarPolynomialFunction{T},S}, ) where {T,S<:MOI.AbstractScalarSet} return haskey(model.constraints, S) && MOI.is_valid(model.constraints[S][2], ci) diff --git a/test/qcqp.jl b/test/qcqp.jl index 87862e7..d08d3fb 100644 --- a/test/qcqp.jl +++ b/test/qcqp.jl @@ -7,6 +7,15 @@ import MultivariatePolynomials as MP import PolyJuMP import JuMP +function test_solver_name(_, _, _) + model = Model{Float64}() + inner = MOI.Utilities.MockOptimizer(model) + # We don't specify `T` to test the fallback + optimizer = PolyJuMP.QCQP.Optimizer(inner) + @test optimizer isa PolyJuMP.QCQP.Optimizer{Float64} + @test MOI.get(optimizer, MOI.SolverName()) == "PolyJuMP.QCQP with Mock" +end + function _test_decompose(monos, exps) vars = MP.variables(monos) M = eltype(monos) @@ -320,7 +329,8 @@ function test_start(x, y, T) b = MOI.add_variable(model) MOI.set(model, MOI.VariablePrimalStart(), b, 3one(T)) p = PolyJuMP.ScalarPolynomialFunction(one(T) * x^3 - x * y^2, [a, b]) - MOI.add_constraint(model, p, MOI.LessThan(zero(T))) + ci = MOI.add_constraint(model, p, MOI.LessThan(zero(T))) + @test MOI.is_valid(model, ci) MOI.Utilities.final_touch(model, nothing) vis = MOI.get(inner, MOI.ListOfVariableIndices()) @test sort(MOI.get(inner, MOI.VariablePrimalStart(), vis)) == T[2, 3, 4, 9]