From b500aafb41506e19cbfb9f25863dafc441a5a9ac Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Fri, 21 May 2021 19:24:29 +0200 Subject: [PATCH 01/11] Start example --- test/interfaces/model.jl | 58 ++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 2 files changed, 59 insertions(+) create mode 100644 test/interfaces/model.jl diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl new file mode 100644 index 000000000..0ffe9f2a9 --- /dev/null +++ b/test/interfaces/model.jl @@ -0,0 +1,58 @@ +# In this test, we use the Martinelli's knapsack lib to test the interface +# to custom models/solvers : https://github.com/rafaelmartinelli/KnapsackLib.jl + +#using KnapsackLib + +mutable struct KnapsackLibModel <: Coluna.MathProg.AbstractFormulation + nbitems::Int + costs::Vector{Float64} + weights::Vector{Float64} + capacity::Float64 +end +KnapsackLibModel(nbitems) = KnapsackLibModel(nbitems, zeros(Float64, nbitems), zeros(Float64, nbitems), 0.0) +setcapacity!(model::KnapsackLibModel, cap) = model.capacity = cap +setweight!(model::KnapsackLibModel, j::Int, w) = model.weights[j] = w +setcost!(model::KnapsackLibModel, j::Int, c) = model.costs[j] = c + +mutable struct KnapsackLibOptimizer <: BlockDecomposition.AbstractCustomOptimizer + +end + +function knpcustommodel() + @testset "knapsack custom model" begin + data = CLD.GeneralizedAssignment.data("play2.txt") + coluna = JuMP.optimizer_with_attributes( + Coluna.Optimizer, + "params" => CL.Params(solver = ClA.TreeSearchAlgorithm()), + "default_optimizer" => GLPK.Optimizer + ) + + model = BlockModel(coluna; direct_model = true) + @axis(M, data.machines) + @variable(model, x[m in M, j in data.jobs], Bin) + @constraint(model, + sp[j in data.jobs], sum(x[m,j] for m in data.machines) == 1 + ) + @objective(model, Min, + sum(data.cost[j,m]*x[m,j] for m in M, j in data.jobs) + ) + + @dantzig_wolfe_decomposition(model, dec, M) + + sp = getsubproblems(dec) + for m in M + knp_model = KnapsackLibModel(length(data.jobs)) + setcapacity!(knp_model, data.capacity[m]) + for j in data.jobs + setweight!(knp_model, j, data.weight[j,m]) + setcost!(knp_model, j, data.cost[j,m]) + end + specify!(sp[m], solver = KnapsackLibOptimizer) ##model = knp_model) + end + + optimize!(model) + end + exit() +end + +knpcustommodel() \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index ed6f3dbf4..66f4a1065 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -23,6 +23,7 @@ const ClA = Coluna.Algorithm include("unit/unit_tests.jl") include("MathOptInterface/MOI_wrapper.jl") +include("interfaces/model.jl") include("issues_tests.jl") include("show_functions_tests.jl") include("full_instances_tests.jl") From 4308844bb6c8f1b405e5063f1f0f51b0181bb065 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Wed, 26 May 2021 13:11:02 +0200 Subject: [PATCH 02/11] wip draft --- src/Algorithm/basic/solveipform.jl | 18 ++++++++++-------- src/MathProg/MathProg.jl | 7 +++++-- src/MathProg/optimizerwrappers.jl | 9 +++++++++ src/decomposition.jl | 1 + test/interfaces/model.jl | 23 +++++++++++++++++++---- 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/Algorithm/basic/solveipform.jl b/src/Algorithm/basic/solveipform.jl index f8784bccc..50458d98a 100644 --- a/src/Algorithm/basic/solveipform.jl +++ b/src/Algorithm/basic/solveipform.jl @@ -77,14 +77,14 @@ end # SolveIpForm does not have child algorithms, therefore get_child_algorithms() is not defined # Dispatch on the type of the optimizer to return the parameters -_optimizer_params(algo::SolveIpForm, ::MoiOptimizer) = algo.moi_params -_optimizer_params(algo::SolveIpForm, ::UserOptimizer) = algo.user_params -# TODO : custom optimizer -_optimizer_params(::SolveIpForm, ::NoOptimizer) = nothing +_optimizer_params(::Formulation, algo::SolveIpForm, ::MoiOptimizer) = algo.moi_params +_optimizer_params(::Formulation, algo::SolveIpForm, ::UserOptimizer) = algo.user_params +_optimizer_params(form::Formulation, algo::SolveIpForm, ::CustomOptimizer) = getinner(getoptimizer(form, algo.optimizer_id)) +_optimizer_params(::Formulation, ::SolveIpForm, ::NoOptimizer) = nothing function run!(algo::SolveIpForm, env::Env, form::Formulation, input::OptimizationInput)::OptimizationOutput opt = getoptimizer(form, algo.optimizer_id) - params = _optimizer_params(algo, opt) + params = _optimizer_params(form, algo, opt) if params !== nothing return run!(params, env, form, input; optimizer_id = algo.optimizer_id) end @@ -99,7 +99,7 @@ run!(algo::SolveIpForm, env::Env, reform::Reformulation, input::OptimizationInpu ################################################################################ function get_units_usage(algo::SolveIpForm, form::Formulation) opt = getoptimizer(form, algo.optimizer_id) - params = _optimizer_params(algo, opt) + params = _optimizer_params(form, algo, opt) if params !== nothing return get_units_usage(params, form) end @@ -135,7 +135,8 @@ function get_units_usage(::UserOptimize, spform::Formulation{DwSp}) return units_usage end -# TODO : get_units_usage of CustomOptimize +# no get_units_usage of CustomOptimize because it directly calls the +# get_units_usage of the custom optimizer ################################################################################ # run! methods (depends on the type of the optimizer) @@ -270,4 +271,5 @@ function run!( return OptimizationOutput(result) end -# TODO : run! of CustomOptimize \ No newline at end of file +# No run! method for CustomOptimize because it directly calls the run! method +# of the custom optimizer \ No newline at end of file diff --git a/src/MathProg/MathProg.jl b/src/MathProg/MathProg.jl index 04a5d4135..f3c46cc93 100644 --- a/src/MathProg/MathProg.jl +++ b/src/MathProg/MathProg.jl @@ -43,10 +43,10 @@ include("MOIinterface.jl") # TODO : clean up # Types -export MaxSense, MinSense, MoiOptimizer, +export MaxSense, MinSense, Id, ConstrSense, VarSense, FormId, FormulationPhase, Annotations, - Counter, UserOptimizer, NoOptimizer, MoiObjective + Counter, MoiObjective # Methods export no_optimizer_builder, set_original_formulation!, @@ -110,4 +110,7 @@ export PrimalBound, DualBound, PrimalSolution, DualSolution, ObjValues, # Methods related to projections export projection_is_possible, proj_cols_on_rep +# Optimizers of formulations +export MoiOptimizer, CustomOptimizer, UserOptimizer, NoOptimizer + end diff --git a/src/MathProg/optimizerwrappers.jl b/src/MathProg/optimizerwrappers.jl index a8944417d..c6b3c3b23 100644 --- a/src/MathProg/optimizerwrappers.jl +++ b/src/MathProg/optimizerwrappers.jl @@ -147,3 +147,12 @@ function write_to_LP_file(form::Formulation, optimizer::MoiOptimizer, filename:: MOI.copy_to(dest, src) MOI.write_to_file(dest, filename) end + +""" + CustomOptimizer <: AbstractOptimizer +""" +struct CustomOptimizer <: AbstractOptimizer + inner::BD.AbstractCustomOptimizer +end + +getinner(optimizer::CustomOptimizer) = optimizer.inner diff --git a/src/decomposition.jl b/src/decomposition.jl index aa1542d45..9c7e29dac 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -427,6 +427,7 @@ end _optimizerbuilder(opt::Function) = () -> UserOptimizer(opt) _optimizerbuilder(opt::MOI.AbstractOptimizer) = () -> MoiOptimizer(opt) +_optimizerbuilder(opt::BD.AbstractCustomOptimizer) = () -> CustomOptimizer(opt) function getoptimizerbuilders(prob::Problem, ann::BD.Annotation) optimizers = BD.getoptimizerbuilders(ann) diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl index 0ffe9f2a9..5fbe07cc8 100644 --- a/test/interfaces/model.jl +++ b/test/interfaces/model.jl @@ -1,8 +1,7 @@ -# In this test, we use the Martinelli's knapsack lib to test the interface -# to custom models/solvers : https://github.com/rafaelmartinelli/KnapsackLib.jl +# In this test, we use the Martinelli's knapsack solver pkg ( https://github.com/rafaelmartinelli/KnapsackLib.jl) +# to test the interface of custom models/solvers. #using KnapsackLib - mutable struct KnapsackLibModel <: Coluna.MathProg.AbstractFormulation nbitems::Int costs::Vector{Float64} @@ -15,9 +14,24 @@ setweight!(model::KnapsackLibModel, j::Int, w) = model.weights[j] = w setcost!(model::KnapsackLibModel, j::Int, c) = model.costs[j] = c mutable struct KnapsackLibOptimizer <: BlockDecomposition.AbstractCustomOptimizer + model::KnapsackLibModel +end + +function Coluna.Algorithm.get_units_usage(opt::KnapsackLibOptimizer, form) # form is Coluna Formulation + println("\e[41m get units usage \e[00m") + units_usage = Tuple{AbstractModel, Coluna.ColunaBase.UnitType, Coluna.ColunaBase.UnitAccessMode}[] + # TODO : the abstract model is KnapsackLibModel (opt.model) + return units_usage +end +function Coluna.Algorithm.run!(opt::KnapsackLibOptimizer, ::Coluna.Env, ::Coluna.MathProg.Formulation, input::Coluna.Algorithm.OptimizationInput; kw...) + @show opt.model + error("run! method of custom optimizer reached !") end +################################################################################ +# User model +################################################################################ function knpcustommodel() @testset "knapsack custom model" begin data = CLD.GeneralizedAssignment.data("play2.txt") @@ -47,7 +61,8 @@ function knpcustommodel() setweight!(knp_model, j, data.weight[j,m]) setcost!(knp_model, j, data.cost[j,m]) end - specify!(sp[m], solver = KnapsackLibOptimizer) ##model = knp_model) + knp_optimizer = KnapsackLibOptimizer(knp_model) + specify!(sp[m], solver = knp_optimizer) ##model = knp_model) end optimize!(model) From 5f3e4f85aca1f427b8d3918eeaadbcafa57ff19b Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Wed, 26 May 2021 19:04:56 +0200 Subject: [PATCH 03/11] continue --- Project.toml | 5 +++-- test/Project.toml | 3 ++- test/interfaces/model.jl | 30 +++++++++++++++++++++++++++--- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/Project.toml b/Project.toml index 750fce8df..bbec65bd1 100644 --- a/Project.toml +++ b/Project.toml @@ -10,6 +10,7 @@ DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" DynamicSparseArrays = "8086fd22-9a0c-46a5-a6c8-6e24676501fe" +KnapsackLib = "86859df6-51c5-4863-9ac2-2c1ab8e53eb2" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" @@ -18,7 +19,6 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [compat] -BlockDecomposition = "~1.3" DataStructures = "0.17, 0.18" DynamicSparseArrays = "0.5" MathOptInterface = "~0.9.10" @@ -30,7 +30,8 @@ julia = "1" ColunaDemos = "a54e61d4-7723-11e9-2469-af255fcaa246" GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +KnapsackLib = "86859df6-51c5-4863-9ac2-2c1ab8e53eb2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["ColunaDemos", "GLPK", "JuMP", "Test"] +test = ["ColunaDemos", "GLPK", "JuMP", "KnapsackLib", "Test"] diff --git a/test/Project.toml b/test/Project.toml index e1aa822be..592c7fb25 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -8,4 +8,5 @@ MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d" -Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" \ No newline at end of file +Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" +KnapsackLib = "86859df6-51c5-4863-9ac2-2c1ab8e53eb2" \ No newline at end of file diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl index 5fbe07cc8..a7ef95657 100644 --- a/test/interfaces/model.jl +++ b/test/interfaces/model.jl @@ -1,12 +1,13 @@ # In this test, we use the Martinelli's knapsack solver pkg ( https://github.com/rafaelmartinelli/KnapsackLib.jl) # to test the interface of custom models/solvers. -#using KnapsackLib +using KnapsackLib mutable struct KnapsackLibModel <: Coluna.MathProg.AbstractFormulation nbitems::Int costs::Vector{Float64} weights::Vector{Float64} capacity::Float64 + #map::Dict{VarId,Float64} end KnapsackLibModel(nbitems) = KnapsackLibModel(nbitems, zeros(Float64, nbitems), zeros(Float64, nbitems), 0.0) setcapacity!(model::KnapsackLibModel, cap) = model.capacity = cap @@ -24,9 +25,32 @@ function Coluna.Algorithm.get_units_usage(opt::KnapsackLibOptimizer, form) # for return units_usage end -function Coluna.Algorithm.run!(opt::KnapsackLibOptimizer, ::Coluna.Env, ::Coluna.MathProg.Formulation, input::Coluna.Algorithm.OptimizationInput; kw...) - @show opt.model +function _rfl(val::Float64)::Integer + rf_val = Integer(floor(val + val * 1e-10 + 1e-6)) + rf_val += rf_val < val - 1 + 1e-6 ? 1 : 0 + return rf_val +end + +function _scale_to_int(vals...) + max_val = maximum(vals) + scaling_factor = typemax(Int) / (length(vals) + 1) / max_val + return map(x -> _rfl(scaling_factor * x), vals) +end + +function Coluna.Algorithm.run!( + opt::KnapsackLibOptimizer, ::Coluna.Env, ::Coluna.MathProg.Formulation, + input::Coluna.Algorithm.OptimizationInput; kw... +) + ws = _scale_to_int(opt.model.capacity, opt.model.weights...) + cs = _scale_to_int(opt.model.costs...) + items = [KnapItem(w,c) for (w,c) in zip(ws[2:end], cs)] + data = KnapData(ws[1], items) + _, selected = solveKnapExpCore(data) + optimal = sum(opt.model.costs[j] for j in selected) + @show optimal + @show selected error("run! method of custom optimizer reached !") + return end ################################################################################ From c0608c613489d2f158340f41824e51093b7eb924 Mon Sep 17 00:00:00 2001 From: Lara Pontes Date: Thu, 27 May 2021 15:10:16 -0300 Subject: [PATCH 04/11] add map --- test/interfaces/model.jl | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl index a7ef95657..d9769a2d2 100644 --- a/test/interfaces/model.jl +++ b/test/interfaces/model.jl @@ -7,12 +7,22 @@ mutable struct KnapsackLibModel <: Coluna.MathProg.AbstractFormulation costs::Vector{Float64} weights::Vector{Float64} capacity::Float64 - #map::Dict{VarId,Float64} + varids::Vector{Coluna.MathProg.VarId} + map::Dict{Coluna.MathProg.VarId,Float64} end -KnapsackLibModel(nbitems) = KnapsackLibModel(nbitems, zeros(Float64, nbitems), zeros(Float64, nbitems), 0.0) +KnapsackLibModel(nbitems) = KnapsackLibModel( + nbitems, zeros(Float64, nbitems), zeros(Float64, nbitems), 0.0, + Vector{Coluna.MathProg.VarId}[], Dict{Coluna.MathProg.VarId,Float64}() +) setcapacity!(model::KnapsackLibModel, cap) = model.capacity = cap setweight!(model::KnapsackLibModel, j::Int, w) = model.weights[j] = w setcost!(model::KnapsackLibModel, j::Int, c) = model.costs[j] = c +pushvarid!(model::KnapsackLibModel, x::VariableRef) = push!( + model.varids, x.model.moi_backend.varids[x.index] +) +map!(model::KnapsackLibModel, j::Int) = push!( + model.map, model.varids[j] => 1.0 +) mutable struct KnapsackLibOptimizer <: BlockDecomposition.AbstractCustomOptimizer model::KnapsackLibModel @@ -49,6 +59,10 @@ function Coluna.Algorithm.run!( optimal = sum(opt.model.costs[j] for j in selected) @show optimal @show selected + for j in selected + map!(opt.model, j) + end + @show opt.model.map error("run! method of custom optimizer reached !") return end @@ -84,6 +98,7 @@ function knpcustommodel() for j in data.jobs setweight!(knp_model, j, data.weight[j,m]) setcost!(knp_model, j, data.cost[j,m]) + pushvarid!(knp_model, x[m,j]) end knp_optimizer = KnapsackLibOptimizer(knp_model) specify!(sp[m], solver = knp_optimizer) ##model = knp_model) From c61e8492b41f8f9aedd952dd1481cc3e46bc4e00 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Fri, 28 May 2021 16:25:21 +0200 Subject: [PATCH 05/11] works with caching optimizer --- test/interfaces/model.jl | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl index d9769a2d2..1def22772 100644 --- a/test/interfaces/model.jl +++ b/test/interfaces/model.jl @@ -7,23 +7,28 @@ mutable struct KnapsackLibModel <: Coluna.MathProg.AbstractFormulation costs::Vector{Float64} weights::Vector{Float64} capacity::Float64 - varids::Vector{Coluna.MathProg.VarId} - map::Dict{Coluna.MathProg.VarId,Float64} + job_to_jumpvar::Dict{Int, JuMP.VariableRef} + #varids::Vector{Coluna.MathProg.VarId} + #map::Dict{Coluna.MathProg.VarId,Float64} end KnapsackLibModel(nbitems) = KnapsackLibModel( nbitems, zeros(Float64, nbitems), zeros(Float64, nbitems), 0.0, - Vector{Coluna.MathProg.VarId}[], Dict{Coluna.MathProg.VarId,Float64}() + Dict{Int, JuMP.VariableRef}() ) setcapacity!(model::KnapsackLibModel, cap) = model.capacity = cap setweight!(model::KnapsackLibModel, j::Int, w) = model.weights[j] = w setcost!(model::KnapsackLibModel, j::Int, c) = model.costs[j] = c -pushvarid!(model::KnapsackLibModel, x::VariableRef) = push!( - model.varids, x.model.moi_backend.varids[x.index] -) -map!(model::KnapsackLibModel, j::Int) = push!( - model.map, model.varids[j] => 1.0 -) +map!(model::KnapsackLibModel, j::Int, x::JuMP.VariableRef) = model.job_to_jumpvar[j] = x + +coluna_backend(model::MOI.Utilities.CachingOptimizer) = coluna_backend(model.optimizer) +coluna_backend(b::MOI.Bridges.AbstractBridgeOptimizer) = coluna_backend(b.model) +coluna_backend(model) = model +function get_coluna_varid(model::KnapsackLibModel, form, j::Int) + jumpvar = model.job_to_jumpvar[j] + opt = coluna_backend(backend(jumpvar.model)) + return Coluna._get_orig_varid_in_form(opt, form, jumpvar.index) +end mutable struct KnapsackLibOptimizer <: BlockDecomposition.AbstractCustomOptimizer model::KnapsackLibModel end @@ -48,7 +53,7 @@ function _scale_to_int(vals...) end function Coluna.Algorithm.run!( - opt::KnapsackLibOptimizer, ::Coluna.Env, ::Coluna.MathProg.Formulation, + opt::KnapsackLibOptimizer, ::Coluna.Env, form::Coluna.MathProg.Formulation, input::Coluna.Algorithm.OptimizationInput; kw... ) ws = _scale_to_int(opt.model.capacity, opt.model.weights...) @@ -60,9 +65,9 @@ function Coluna.Algorithm.run!( @show optimal @show selected for j in selected - map!(opt.model, j) + @show Coluna.MathProg.getname(form, get_coluna_varid(opt.model, form, j)) end - @show opt.model.map + error("run! method of custom optimizer reached !") return end @@ -98,7 +103,7 @@ function knpcustommodel() for j in data.jobs setweight!(knp_model, j, data.weight[j,m]) setcost!(knp_model, j, data.cost[j,m]) - pushvarid!(knp_model, x[m,j]) + map!(knp_model, j, x[m,j]) end knp_optimizer = KnapsackLibOptimizer(knp_model) specify!(sp[m], solver = knp_optimizer) ##model = knp_model) From 3546d62f6726467f9f08cb0aa382a7794f9a8712 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sun, 30 May 2021 13:59:08 +0200 Subject: [PATCH 06/11] varids in Env --- src/Coluna.jl | 12 ++++++++---- src/MOIwrapper.jl | 11 +++++------ src/MathProg/formulation.jl | 2 +- src/MathProg/problem.jl | 4 ++-- test/interfaces/model.jl | 5 ++++- test/preprocessing_tests.jl | 2 +- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Coluna.jl b/src/Coluna.jl index 2594af1c6..a8ed0640d 100644 --- a/src/Coluna.jl +++ b/src/Coluna.jl @@ -36,21 +36,25 @@ include("parameters.jl") include("ColunaBase/ColunaBase.jl") using .ColunaBase +include("MathProg/MathProg.jl") +using .MathProg + mutable struct Env env_starting_time::DateTime optim_starting_time::Union{Nothing, DateTime} params::Params kpis::Kpis form_counter::Int # 0 is for original form + varids::MOI.Utilities.CleverDicts.CleverDict{MOI.VariableIndex, MathProg.VarId} end -Env(params::Params) = Env(now(), nothing, params, Kpis(nothing, nothing), 0) +Env(params::Params) = Env( + now(), nothing, params, Kpis(nothing, nothing), 0, + MOI.Utilities.CleverDicts.CleverDict{MOI.VariableIndex, MathProg.VarId}() +) set_optim_start_time!(env::Env) = env.optim_starting_time = now() elapsed_optim_time(env::Env) = Dates.toms(now() - env.optim_starting_time) / Dates.toms(Second(1)) Base.isinteger(x::Float64, tol::Float64) = abs(round(x) - x) < tol -include("MathProg/MathProg.jl") -using .MathProg - include("Algorithm/Algorithm.jl") using .Algorithm diff --git a/src/MOIwrapper.jl b/src/MOIwrapper.jl index 2ad3e2443..75de5f631 100644 --- a/src/MOIwrapper.jl +++ b/src/MOIwrapper.jl @@ -21,7 +21,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer annotations::Annotations #varmap::Dict{MOI.VariableIndex,VarId} # For the user to get VariablePrimal vars::CleverDicts.CleverDict{MOI.VariableIndex, Variable} - varids::CleverDicts.CleverDict{MOI.VariableIndex, VarId} + #varids::CleverDicts.CleverDict{MOI.VariableIndex, VarId} moi_varids::Dict{VarId, MOI.VariableIndex} names_to_vars::Dict{String, MOI.VariableIndex} constrs::Dict{MOI.ConstraintIndex, Constraint} @@ -33,14 +33,13 @@ mutable struct Optimizer <: MOI.AbstractOptimizer feasibility_sense::Bool # Coluna supports only Max or Min. - function Optimizer() model = new() model.env = Env(Params()) model.inner = Problem(model.env) model.annotations = Annotations() model.vars = CleverDicts.CleverDict{MOI.VariableIndex, Variable}() - model.varids = CleverDicts.CleverDict{MOI.VariableIndex, VarId}() # TODO : check if necessary to have two dicts for variables + #model.varids = CleverDicts.CleverDict{MOI.VariableIndex, VarId}() # TODO : check if necessary to have two dicts for variables model.moi_varids = Dict{VarId, MOI.VariableIndex}() model.names_to_vars = Dict{String, MOI.VariableIndex}() model.constrs = Dict{MOI.ConstraintIndex, Union{Constraint, Nothing}}() @@ -79,7 +78,7 @@ end function _get_orig_varid(optimizer::Optimizer, x::MOI.VariableIndex) if haskey(optimizer.vars, x) - return optimizer.varids[x] + return optimizer.env.varids[x] end throw(MOI.InvalidIndex(x)) return origid @@ -113,7 +112,7 @@ function MOI.add_variable(model::Coluna.Optimizer) var = setvar!(orig_form, "v", OriginalVar) index = CleverDicts.add_item(model.vars, var) model.moi_varids[getid(var)] = index - index2 = CleverDicts.add_item(model.varids, getid(var)) + index2 = CleverDicts.add_item(model.env.varids, getid(var)) @assert index == index2 return index end @@ -581,7 +580,7 @@ function MOI.empty!(model::Coluna.Optimizer) model.inner = Problem(model.env) model.annotations = Annotations() model.vars = CleverDicts.CleverDict{MOI.VariableIndex, Variable}() - model.varids = CleverDicts.CleverDict{MOI.VariableIndex, VarId}() + model.env.varids = CleverDicts.CleverDict{MOI.VariableIndex, VarId}() model.moi_varids = Dict{VarId, MOI.VariableIndex}() model.constrs = Dict{MOI.ConstraintIndex, Constraint}() if model.default_optimizer_builder !== nothing diff --git a/src/MathProg/formulation.jl b/src/MathProg/formulation.jl index 9c62e5208..bb5a0df50 100644 --- a/src/MathProg/formulation.jl +++ b/src/MathProg/formulation.jl @@ -25,7 +25,7 @@ Create a new formulation in the Coluna's environment `env` with duty `duty`, parent formulation `parent_formulation`, and objective sense `obj_sense`. """ function create_formulation!( - env::Coluna.Env, + env, duty::Type{<:AbstractFormDuty}; parent_formulation = nothing, obj_sense::Type{<:Coluna.AbstractSense} = MinSense diff --git a/src/MathProg/problem.jl b/src/MathProg/problem.jl index 592129c1e..c45630632 100644 --- a/src/MathProg/problem.jl +++ b/src/MathProg/problem.jl @@ -7,11 +7,11 @@ mutable struct Problem <: AbstractProblem end """ - Problem() + Problem(env) Constructs an empty `Problem`. """ -function Problem(env::Coluna.Env) +function Problem(env) original_formulation = create_formulation!(env, Original) return Problem( nothing, nothing, original_formulation, nothing, diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl index 1def22772..68602801d 100644 --- a/test/interfaces/model.jl +++ b/test/interfaces/model.jl @@ -53,7 +53,7 @@ function _scale_to_int(vals...) end function Coluna.Algorithm.run!( - opt::KnapsackLibOptimizer, ::Coluna.Env, form::Coluna.MathProg.Formulation, + opt::KnapsackLibOptimizer, env::Coluna.Env, form::Coluna.MathProg.Formulation, input::Coluna.Algorithm.OptimizationInput; kw... ) ws = _scale_to_int(opt.model.capacity, opt.model.weights...) @@ -62,6 +62,9 @@ function Coluna.Algorithm.run!( data = KnapData(ws[1], items) _, selected = solveKnapExpCore(data) optimal = sum(opt.model.costs[j] for j in selected) + + @show env.varids + @show optimal @show selected for j in selected diff --git a/test/preprocessing_tests.jl b/test/preprocessing_tests.jl index f9a28c899..00b5f708a 100644 --- a/test/preprocessing_tests.jl +++ b/test/preprocessing_tests.jl @@ -111,7 +111,7 @@ function test_random_gap_instance() nb_prep_vars = 0 coluna_optimizer = problem.moi_backend master = CL.getmaster(coluna_optimizer.inner.re_formulation) - for (moi_index, varid) in coluna_optimizer.varids + for (moi_index, varid) in coluna_optimizer.env.varids var = CL.getvar(master, varid) if CL.getcurlb(master, var) == CL.getcurub(master, var) var_name = CL.getname(master, var) From a6461bbceac289f086419e03f8b4633458b93b8c Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Sun, 30 May 2021 14:37:26 +0200 Subject: [PATCH 07/11] wrong result --- test/interfaces/model.jl | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl index 68602801d..bd99f3ac6 100644 --- a/test/interfaces/model.jl +++ b/test/interfaces/model.jl @@ -52,29 +52,52 @@ function _scale_to_int(vals...) return map(x -> _rfl(scaling_factor * x), vals) end +_getvarid(model::KnapsackLibModel, form, env::Env, j::Int) = Coluna.MathProg.getid(Coluna.MathProg.getvar(form, env.varids[model.job_to_jumpvar[j].index])) + function Coluna.Algorithm.run!( opt::KnapsackLibOptimizer, env::Coluna.Env, form::Coluna.MathProg.Formulation, input::Coluna.Algorithm.OptimizationInput; kw... ) + costs = [Coluna.MathProg.getcurcost(form, _getvarid(opt.model, form, env, j)) for j in 1:length(opt.model.costs)] ws = _scale_to_int(opt.model.capacity, opt.model.weights...) - cs = _scale_to_int(opt.model.costs...) + cs = _scale_to_int(costs...) items = [KnapItem(w,c) for (w,c) in zip(ws[2:end], cs)] data = KnapData(ws[1], items) _, selected = solveKnapExpCore(data) - optimal = sum(opt.model.costs[j] for j in selected) - @show env.varids + # setup variable (issue https://github.com/atoptima/Coluna.jl/issues/283) + setup_var_id = [id for (id,v) in Iterators.filter( + v -> ( + Coluna.MathProg.iscuractive(form, v.first) && + Coluna.MathProg.isexplicit(form, v.first) && + Coluna.MathProg.getduty(v.first) <= Coluna.DwSpSetupVar + ), + Coluna.MathProg.getvars(form) + )][1] + + cost = sum(opt.model.costs[j] for j in selected) + Coluna.MathProg.getcurcost(form, setup_var_id) + + varids = Coluna.MathProg.VarId[] + varvals = Float64[] - @show optimal - @show selected for j in selected - @show Coluna.MathProg.getname(form, get_coluna_varid(opt.model, form, j)) + push!(varids, _getvarid(opt.model, form, env, j)) + push!(varvals, 1) end - error("run! method of custom optimizer reached !") - return + push!(varids, setup_var_id) + push!(varvals, 1) + + sol = Coluna.MathProg.PrimalSolution(form, varids, varvals, cost, Coluna.MathProg.FEASIBLE_SOL) + + result = Coluna.Algorithm.OptimizationState(form; termination_status = Coluna.MathProg.OPTIMAL) + Coluna.Algorithm.add_ip_primal_sol!(result, sol) + dual_bound = Coluna.getvalue(Coluna.Algorithm.get_ip_primal_bound(result)) + Coluna.Algorithm.set_ip_dual_bound!(result, Coluna.DualBound(form, dual_bound)) + return Coluna.Algorithm.OptimizationOutput(result) end + ################################################################################ # User model ################################################################################ From e6994fa538e1418c5af85746cec7e363adf4ddc9 Mon Sep 17 00:00:00 2001 From: Lara Pontes Date: Tue, 1 Jun 2021 10:18:54 -0300 Subject: [PATCH 08/11] multiply costs by -1 --- test/interfaces/model.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl index bd99f3ac6..f02d6475d 100644 --- a/test/interfaces/model.jl +++ b/test/interfaces/model.jl @@ -47,7 +47,7 @@ function _rfl(val::Float64)::Integer end function _scale_to_int(vals...) - max_val = maximum(vals) + max_val = maximum(abs.(vals)) scaling_factor = typemax(Int) / (length(vals) + 1) / max_val return map(x -> _rfl(scaling_factor * x), vals) end @@ -58,7 +58,7 @@ function Coluna.Algorithm.run!( opt::KnapsackLibOptimizer, env::Coluna.Env, form::Coluna.MathProg.Formulation, input::Coluna.Algorithm.OptimizationInput; kw... ) - costs = [Coluna.MathProg.getcurcost(form, _getvarid(opt.model, form, env, j)) for j in 1:length(opt.model.costs)] + costs = -[Coluna.MathProg.getcurcost(form, _getvarid(opt.model, form, env, j)) for j in 1:length(opt.model.costs)] ws = _scale_to_int(opt.model.capacity, opt.model.weights...) cs = _scale_to_int(costs...) items = [KnapItem(w,c) for (w,c) in zip(ws[2:end], cs)] From 8cc1db1c2d5186bae312c184cde5637d5c861967 Mon Sep 17 00:00:00 2001 From: Lara Pontes Date: Wed, 2 Jun 2021 10:52:44 -0300 Subject: [PATCH 09/11] fix scaling --- test/interfaces/model.jl | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl index f02d6475d..64dd1fc15 100644 --- a/test/interfaces/model.jl +++ b/test/interfaces/model.jl @@ -40,16 +40,8 @@ function Coluna.Algorithm.get_units_usage(opt::KnapsackLibOptimizer, form) # for return units_usage end -function _rfl(val::Float64)::Integer - rf_val = Integer(floor(val + val * 1e-10 + 1e-6)) - rf_val += rf_val < val - 1 + 1e-6 ? 1 : 0 - return rf_val -end - function _scale_to_int(vals...) - max_val = maximum(abs.(vals)) - scaling_factor = typemax(Int) / (length(vals) + 1) / max_val - return map(x -> _rfl(scaling_factor * x), vals) + return map(x -> Integer(round(10000x)), vals) end _getvarid(model::KnapsackLibModel, form, env::Env, j::Int) = Coluna.MathProg.getid(Coluna.MathProg.getvar(form, env.varids[model.job_to_jumpvar[j].index])) @@ -75,7 +67,7 @@ function Coluna.Algorithm.run!( Coluna.MathProg.getvars(form) )][1] - cost = sum(opt.model.costs[j] for j in selected) + Coluna.MathProg.getcurcost(form, setup_var_id) + cost = sum(-costs[j] for j in selected) + Coluna.MathProg.getcurcost(form, setup_var_id) varids = Coluna.MathProg.VarId[] varvals = Float64[] @@ -136,8 +128,9 @@ function knpcustommodel() end optimize!(model) + + @test JuMP.objective_value(model) ≈ 75.0 end - exit() end knpcustommodel() \ No newline at end of file From 77a695ecfc4526e0df27dce2881c54d374081756 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Wed, 2 Jun 2021 19:40:15 +0200 Subject: [PATCH 10/11] Apply suggestions from code review --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index bbec65bd1..fd4180820 100644 --- a/Project.toml +++ b/Project.toml @@ -10,7 +10,6 @@ DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" DynamicSparseArrays = "8086fd22-9a0c-46a5-a6c8-6e24676501fe" -KnapsackLib = "86859df6-51c5-4863-9ac2-2c1ab8e53eb2" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" @@ -19,6 +18,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [compat] +BlockDecomposition = "~1.3" DataStructures = "0.17, 0.18" DynamicSparseArrays = "0.5" MathOptInterface = "~0.9.10" From 622082f7775aa41a95b1f7d79e70f5c3c31b2407 Mon Sep 17 00:00:00 2001 From: Guillaume Marques Date: Thu, 3 Jun 2021 10:01:10 +0200 Subject: [PATCH 11/11] Update test/interfaces/model.jl --- test/interfaces/model.jl | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/interfaces/model.jl b/test/interfaces/model.jl index 64dd1fc15..21bf0e3d0 100644 --- a/test/interfaces/model.jl +++ b/test/interfaces/model.jl @@ -24,11 +24,6 @@ coluna_backend(model::MOI.Utilities.CachingOptimizer) = coluna_backend(model.opt coluna_backend(b::MOI.Bridges.AbstractBridgeOptimizer) = coluna_backend(b.model) coluna_backend(model) = model -function get_coluna_varid(model::KnapsackLibModel, form, j::Int) - jumpvar = model.job_to_jumpvar[j] - opt = coluna_backend(backend(jumpvar.model)) - return Coluna._get_orig_varid_in_form(opt, form, jumpvar.index) -end mutable struct KnapsackLibOptimizer <: BlockDecomposition.AbstractCustomOptimizer model::KnapsackLibModel end @@ -133,4 +128,4 @@ function knpcustommodel() end end -knpcustommodel() \ No newline at end of file +knpcustommodel()