From 96cc0a01465f290e57447cb073eddbaff2aa398b Mon Sep 17 00:00:00 2001 From: Lara Pontes Date: Wed, 21 Jul 2021 14:43:24 -0300 Subject: [PATCH 1/6] first draft of MOI.ConstraintDual for Coluna.Optimizer --- src/MOIwrapper.jl | 8 +++++--- test/MathOptInterface/MOI_wrapper.jl | 12 ++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index 38211954f..2086a8381 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -742,9 +742,11 @@ end MOI.get(optimizer::Optimizer, ::MOI.NodeCount) = optimizer.env.kpis.node_count MOI.get(optimizer::Optimizer, ::MOI.SolveTime) = optimizer.env.kpis.elapsed_optimization_time -# function MOI.get(optimizer::Optimizer, ::MOI.ConstraintDual, index::MOI.ConstraintIndex) -# return 0.0 -# end +function MOI.get(optimizer::Optimizer, ::MOI.ConstraintDual, index::MOI.ConstraintIndex) + dualsols = get_lp_dual_sols(optimizer.result) + 1 <= index.value <= length(dualsols) && return getvalue(dualsols[index.value]) + return 0.0 +end # function MOI.get(optimizer::Optimizer, ::MOI.SolveTime) # return 0.0 diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index c64b7c177..0b1a86877 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -3,7 +3,7 @@ const OPTIMIZER = Coluna.Optimizer() MOI.set(OPTIMIZER, MOI.RawParameter("default_optimizer"), GLPK.Optimizer) -const CONFIG = MOIT.TestConfig(atol=1e-6, rtol=1e-6) +const CONFIG = MOIT.TestConfig(atol=1e-6, rtol=1e-6, infeas_certificates = false) @testset "SolverName" begin @@ -106,9 +106,7 @@ const UNSUPPORTED_TESTS = [ "get_objective_function", # Quandratic objective not supported "number_threads", # TODO : support of MOI.NumberOfThreads() "silent", # TODO : support of MOI.Silent() - "time_limit_sec", # TODO : support of MOI.TimeLimitSec() - "solve_time", # TODO : support of MOI.SolveTime() - "solve_twice" # TODO : fix + "time_limit_sec" # TODO : support of MOI.TimeLimitSec() ] MathOptInterface.Test.getconstraint @@ -175,8 +173,10 @@ MOI.set(BRIDGED, MOI.RawParameter("params"), CL.Params(solver = ClA.SolveIpForm( end # @testset "Unit LP" begin -# MOI.set(OPTIMIZER, MOI.RawParameter("params"), CL.Params(solver = ClA.SolveLpForm())) -# MOIT.unittest(OPTIMIZER, CONFIG, vcat(UNSUPPORTED_TESTS, MIP_TESTS, BASIC)) +# MOI.set(BRIDGED, MOI.RawParameter("params"), CL.Params(solver = ClA.SolveLpForm( +# update_ip_primal_solution=true, get_dual_solution=true +# ))) +# MOIT.unittest(BRIDGED, CONFIG, vcat(UNSUPPORTED_TESTS, MIP_TESTS, BASIC)) # end # @testset "Continuous Linear" begin From 389f43de5b4627fed9be55732022ac3dab339e34 Mon Sep 17 00:00:00 2001 From: Lara Pontes Date: Mon, 2 Aug 2021 14:08:22 -0300 Subject: [PATCH 2/6] fix MOI.ConstraintDual for ScalarAffine and add draft for SingleVariable --- src/MOIwrapper.jl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index 2086a8381..cff18fbf3 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -742,9 +742,21 @@ end MOI.get(optimizer::Optimizer, ::MOI.NodeCount) = optimizer.env.kpis.node_count MOI.get(optimizer::Optimizer, ::MOI.SolveTime) = optimizer.env.kpis.elapsed_optimization_time -function MOI.get(optimizer::Optimizer, ::MOI.ConstraintDual, index::MOI.ConstraintIndex) +function MOI.get( + optimizer::Optimizer, attr::MOI.ConstraintDual, + index::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64}} +) dualsols = get_lp_dual_sols(optimizer.result) - 1 <= index.value <= length(dualsols) && return getvalue(dualsols[index.value]) + if 1 <= attr.N <= length(dualsols) + return get(dualsols[attr.N], getid(optimizer.constrs[index]), 0.0) + end + return error("Invalid result index.") +end + +# fix +function MOI.get( + optimizer::Optimizer, attr::MOI.ConstraintDual, index::MOI.ConstraintIndex{F,S} +) where {F<:MOI.SingleVariable,S} return 0.0 end From a93a37523777cdcee57da1ae98ca8a859b1b9cc0 Mon Sep 17 00:00:00 2001 From: Lara Pontes Date: Thu, 5 Aug 2021 17:35:03 -0300 Subject: [PATCH 3/6] fix lists of var and constr indices and fix var primal --- src/MOIwrapper.jl | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index cff18fbf3..b865da51d 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -216,10 +216,10 @@ end function MOI.get(model::Coluna.Optimizer, ::MOI.ListOfVariableIndices) indices = Vector{MathOptInterface.VariableIndex}() - for (key,value) in model.moi_varids + for (_, value) in model.moi_varids push!(indices, value) end - return sort!(indices) + return sort!(indices, by = x -> x.value) end ############################################################################################ @@ -286,11 +286,10 @@ end function MOI.get( model::Coluna.Optimizer, ::MOI.ListOfConstraintIndices{F, S} ) where {F<:MOI.SingleVariable, S} - orig_form = get_original_formulation(model.inner) indices = MOI.ConstraintIndex{F,S}[] - for (id, var) in model.vars - if S == MathProg.convert_coluna_kind_to_moi(getperenkind(orig_form, var)) - push!(indices, MOI.ConstraintIndex{F,S}(id.value)) + for (id, _) in model.constrs_on_single_var_to_vars + if S == typeof(MOI.get(model, MOI.ConstraintSet(), id)) + push!(indices, id) end end return sort!(indices, by = x -> x.value) @@ -586,6 +585,8 @@ function MOI.empty!(model::Coluna.Optimizer) model.env.varids = CleverDicts.CleverDict{MOI.VariableIndex, VarId}() model.moi_varids = Dict{VarId, MOI.VariableIndex}() model.constrs = Dict{MOI.ConstraintIndex, Constraint}() + model.constrs_on_single_var_to_vars = Dict{MOI.ConstraintIndex, VarId}() + model.constrs_on_single_var_to_names = Dict{MOI.ConstraintIndex, String}() if model.default_optimizer_builder !== nothing set_default_optimizer_builder!(model.inner, model.default_optimizer_builder) end @@ -674,14 +675,13 @@ function MOI.get(optimizer::Optimizer, ::MOI.RelativeGap) return ip_gap(optimizer.result) end -function MOI.get(optimizer::Optimizer, ::MOI.VariablePrimal, ref::MOI.VariableIndex) +function MOI.get(optimizer::Optimizer, attr::MOI.VariablePrimal, ref::MOI.VariableIndex) id = getid(optimizer.vars[ref]) # This gets a coluna Id{Variable} - best_primal_sol = get_best_ip_primal_sol(optimizer.result) - if best_primal_sol === nothing - @warn "Coluna did not find a primal feasible solution." - return NaN + primalsols = get_ip_primal_sols(optimizer.result) + if 1 <= attr.N <= length(primalsols) + return get(primalsols[attr.N], id, 0.0) end - return get(best_primal_sol, id, 0.0) + return error("Invalid result index.") end function MOI.get(optimizer::Optimizer, ::MOI.VariablePrimal, refs::Vector{MOI.VariableIndex}) @@ -753,12 +753,11 @@ function MOI.get( return error("Invalid result index.") end -# fix -function MOI.get( - optimizer::Optimizer, attr::MOI.ConstraintDual, index::MOI.ConstraintIndex{F,S} -) where {F<:MOI.SingleVariable,S} - return 0.0 -end +# function MOI.get( +# optimizer::Optimizer, attr::MOI.ConstraintDual, index::MOI.ConstraintIndex{F,S} +# ) where {F<:MOI.SingleVariable,S} +# return 0.0 +# end # function MOI.get(optimizer::Optimizer, ::MOI.SolveTime) # return 0.0 From 90eaf48ce75096b7d972972b404bfdbbf010fccc Mon Sep 17 00:00:00 2001 From: Lara Pontes Date: Fri, 6 Aug 2021 02:05:20 -0300 Subject: [PATCH 4/6] add MOI.DualObjectiveValue and organize unsupported/failing tests --- src/MOIwrapper.jl | 4 ++ test/MathOptInterface/MOI_wrapper.jl | 70 ++++++++++++++++++++++------ 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index b865da51d..329ac0608 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -671,6 +671,10 @@ function MOI.get(optimizer::Optimizer, ::MOI.ObjectiveValue) return getvalue(get_ip_primal_bound(optimizer.result)) end +function MOI.get(optimizer::Optimizer, ::MOI.DualObjectiveValue) + return getvalue(get_lp_dual_bound(optimizer.result)) +end + function MOI.get(optimizer::Optimizer, ::MOI.RelativeGap) return ip_gap(optimizer.result) end diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index 0b1a86877..fe1ba72d8 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -106,7 +106,10 @@ const UNSUPPORTED_TESTS = [ "get_objective_function", # Quandratic objective not supported "number_threads", # TODO : support of MOI.NumberOfThreads() "silent", # TODO : support of MOI.Silent() - "time_limit_sec" # TODO : support of MOI.TimeLimitSec() + "time_limit_sec", # TODO : support of MOI.TimeLimitSec() + "solve_unbounded_model", # default lower bound 0 + "solve_duplicate_terms_obj", # duplicate terms not supported + "solve_duplicate_terms_scalar_affine" # duplicate terms not supported ] MathOptInterface.Test.getconstraint @@ -153,6 +156,40 @@ const LP_TESTS = [ "solve_affine_lessthan" ] +const CONSTRAINTDUAL_SINGLEVAR = [ + "solve_with_lowerbound", + "solve_singlevariable_obj", + "solve_constant_obj", + "solve_single_variable_dual_max", + "solve_single_variable_dual_min", + "solve_duplicate_terms_obj", + "solve_blank_obj", + "solve_with_upperbound", + "linear1", + "linear2", + "linear10b", + "linear14" +] + +const MODIFY_DELETE = [ + "linear1", # modify + "linear5", # modify + "linear11", # delete + "linear14" # delete +] + +const UNCOVERED_TERMINATION_STATUS = [ + "linear8b", # DUAL_INFEASIBLE or INFEASIBLE_OR_UNBOUNDED required + "linear8c" # DUAL_INFEASIBLE or INFEASIBLE_OR_UNBOUNDED required +] + +const SET_CONSTRAINTSET = [ + # could modify a constraint + "linear4", + "linear6", + "linear7" +] + @testset "Unit Basic/MIP" begin MOI.set(OPTIMIZER, MOI.RawParameter("params"), CL.Params(solver = ClA.SolveIpForm())) MOIT.unittest(OPTIMIZER, CONFIG, vcat(UNSUPPORTED_TESTS, LP_TESTS, MIP_TESTS)) @@ -172,15 +209,22 @@ MOI.set(BRIDGED, MOI.RawParameter("params"), CL.Params(solver = ClA.SolveIpForm( ]) end -# @testset "Unit LP" begin -# MOI.set(BRIDGED, MOI.RawParameter("params"), CL.Params(solver = ClA.SolveLpForm( -# update_ip_primal_solution=true, get_dual_solution=true -# ))) -# MOIT.unittest(BRIDGED, CONFIG, vcat(UNSUPPORTED_TESTS, MIP_TESTS, BASIC)) -# end - -# @testset "Continuous Linear" begin -# MOIT.contlineartest(OPTIMIZER, CONFIG, [ -# "partial_start" # VariablePrimalStart not supported -# ]) -# end +@testset "Unit LP" begin + MOI.set(BRIDGED, MOI.RawParameter("params"), CL.Params(solver = ClA.SolveLpForm( + update_ip_primal_solution=true, get_dual_solution=true, set_dual_bound=true + ))) + MOIT.unittest(BRIDGED, CONFIG, vcat(UNSUPPORTED_TESTS, MIP_TESTS, BASIC, CONSTRAINTDUAL_SINGLEVAR)) +end + +@testset "Continuous Linear" begin + MOIT.contlineartest(BRIDGED, CONFIG, vcat( + CONSTRAINTDUAL_SINGLEVAR, MODIFY_DELETE, UNCOVERED_TERMINATION_STATUS, SET_CONSTRAINTSET, [ + "partial_start", # VariablePrimalStart not supported + ### BUGS ### + "linear1", # redundant zero coefficients in objective function, that should catch solvers that don't handle duplicate coefficients correctly + # get(model, MOI.ConstraintFunction(), c2) -> View of a row not available in fill mode (Open an issue at https://github.com/atoptima/DynamicSparseArrays.jl if you need it). + "linear6", # get(model, MOI.ConstraintFunction(), c2) -> View of a row not available in fill mode (Open an issue at https://github.com/atoptima/DynamicSparseArrays.jl if you need it). + "linear10" # optimize twice changing sense from max to min + ] + )) +end From 385713e58b4da309e46071791ed9f3d5c5934824 Mon Sep 17 00:00:00 2001 From: Lara Pontes Date: Fri, 6 Aug 2021 18:20:24 -0300 Subject: [PATCH 5/6] close fill mode before viewing matrix in get MOI.ConstraintFunction --- src/MOIwrapper.jl | 2 ++ test/MathOptInterface/MOI_wrapper.jl | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index 329ac0608..e3ee4f3c7 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -301,6 +301,8 @@ function MOI.get( orig_form = get_original_formulation(model.inner) constrid = getid(model.constrs[index]) terms = MOI.ScalarAffineTerm{Float64}[] + coefmatrix = getcoefmatrix(orig_form) + coefmatrix.fillmode && closefillmode!(coefmatrix) for (varid, coeff) in @view getcoefmatrix(orig_form)[constrid, :] push!(terms, MOI.ScalarAffineTerm(coeff, model.moi_varids[varid])) end diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index fe1ba72d8..22dbc7bb7 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -220,11 +220,8 @@ end MOIT.contlineartest(BRIDGED, CONFIG, vcat( CONSTRAINTDUAL_SINGLEVAR, MODIFY_DELETE, UNCOVERED_TERMINATION_STATUS, SET_CONSTRAINTSET, [ "partial_start", # VariablePrimalStart not supported - ### BUGS ### - "linear1", # redundant zero coefficients in objective function, that should catch solvers that don't handle duplicate coefficients correctly - # get(model, MOI.ConstraintFunction(), c2) -> View of a row not available in fill mode (Open an issue at https://github.com/atoptima/DynamicSparseArrays.jl if you need it). - "linear6", # get(model, MOI.ConstraintFunction(), c2) -> View of a row not available in fill mode (Open an issue at https://github.com/atoptima/DynamicSparseArrays.jl if you need it). - "linear10" # optimize twice changing sense from max to min + "linear1", # require duplicate terms support + "linear10" # BUG: optimize twice changing sense from max to min fails ] )) end From ba3dc845f9cd75c69f7147e396639f1b7960a759 Mon Sep 17 00:00:00 2001 From: Lara Pontes Date: Mon, 9 Aug 2021 15:39:44 -0300 Subject: [PATCH 6/6] update comments --- src/MOIwrapper.jl | 4 ++-- test/MathOptInterface/MOI_wrapper.jl | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index e3ee4f3c7..4ce562aa2 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -303,8 +303,8 @@ function MOI.get( terms = MOI.ScalarAffineTerm{Float64}[] coefmatrix = getcoefmatrix(orig_form) coefmatrix.fillmode && closefillmode!(coefmatrix) - for (varid, coeff) in @view getcoefmatrix(orig_form)[constrid, :] - push!(terms, MOI.ScalarAffineTerm(coeff, model.moi_varids[varid])) + for (varid, coef) in @view coefmatrix[constrid, :] + push!(terms, MOI.ScalarAffineTerm(coef, model.moi_varids[varid])) end return MOI.ScalarAffineFunction(terms, 0.0) end diff --git a/test/MathOptInterface/MOI_wrapper.jl b/test/MathOptInterface/MOI_wrapper.jl index 22dbc7bb7..399baab49 100644 --- a/test/MathOptInterface/MOI_wrapper.jl +++ b/test/MathOptInterface/MOI_wrapper.jl @@ -108,8 +108,8 @@ const UNSUPPORTED_TESTS = [ "silent", # TODO : support of MOI.Silent() "time_limit_sec", # TODO : support of MOI.TimeLimitSec() "solve_unbounded_model", # default lower bound 0 - "solve_duplicate_terms_obj", # duplicate terms not supported - "solve_duplicate_terms_scalar_affine" # duplicate terms not supported + "solve_duplicate_terms_obj", # TODO: support duplicate terms + "solve_duplicate_terms_scalar_affine" # TODO: support duplicate terms ] MathOptInterface.Test.getconstraint @@ -172,6 +172,7 @@ const CONSTRAINTDUAL_SINGLEVAR = [ ] const MODIFY_DELETE = [ + # BUG "linear1", # modify "linear5", # modify "linear11", # delete @@ -184,7 +185,7 @@ const UNCOVERED_TERMINATION_STATUS = [ ] const SET_CONSTRAINTSET = [ - # could modify a constraint + # BUG "linear4", "linear6", "linear7" @@ -220,7 +221,7 @@ end MOIT.contlineartest(BRIDGED, CONFIG, vcat( CONSTRAINTDUAL_SINGLEVAR, MODIFY_DELETE, UNCOVERED_TERMINATION_STATUS, SET_CONSTRAINTSET, [ "partial_start", # VariablePrimalStart not supported - "linear1", # require duplicate terms support + "linear1", # TODO: support duplicate terms "linear10" # BUG: optimize twice changing sense from max to min fails ] ))