From c09cd5cb90cbd342a766d4880ce46580871fa89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Kr=C3=B6ger?= Date: Fri, 26 Jun 2020 20:16:20 +0200 Subject: [PATCH] Indicator update best bound (#175) * benchmark to compare v0.2.1 with v0.2.2 * update best bound v0.2.2 * using Cbc * use Cbc at the right spot... * branch variable dependent on objective * have the change in `add_var!` * changelog and v0.2.2 --- CHANGELOG.md | 6 +++++- Project.toml | 2 +- benchmark/benchmarks.jl | 4 +++- benchmark/eternity/benchmark.jl | 28 +++++++++++++++++++++++++--- benchmark/run_benchmarks.jl | 2 +- src/ConstraintSolver.jl | 9 ++------- src/MOI_wrapper/objective.jl | 2 ++ src/MOI_wrapper/variables.jl | 1 + src/branching.jl | 23 +++++++++++++++++------ src/constraints/indicator.jl | 2 -- src/type_inits.jl | 3 ++- src/types.jl | 1 + 12 files changed, 60 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5996189..f9247575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # ConstrainSolver.jl - Changelog -## v0.2.1 +## v0.2.2 (26th of June 2020) +- Actually use best bound [#175](https://github.com/Wikunia/ConstraintSolver.jl/pull/175) +- Select next var based on objective (still hacky solution) [#176](https://github.com/Wikunia/ConstraintSolver.jl/issues/176) + +## v0.2.1 (26th of June 2020) - Bugfixes in indicator constraint [#170](https://github.com/Wikunia/ConstraintSolver.jl/issues/170) - Calling finished constraints and other functions for i.e `TableConstraint` as an inner constraint - Use correct best bound when inactive vs active diff --git a/Project.toml b/Project.toml index 836e4a26..86b063e2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstraintSolver" uuid = "e0e52ebd-5523-408d-9ca3-7641f1cd1405" authors = ["Ole Kröger "] -version = "0.2.1" +version = "0.2.2" [deps] Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0" diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 0a017ad2..c5d88701 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -1,6 +1,6 @@ using BenchmarkTools using ConstraintSolver, JuMP, MathOptInterface -using GLPK, JSON +using GLPK, JSON, Cbc const CS = ConstraintSolver const MOI = MathOptInterface @@ -23,6 +23,8 @@ SUITE["eternity"] = BenchmarkGroup(["alldifferent", "table", "equal"]) # compiling run solve_eternity("eternity_6x5"; height=6, width=5) SUITE["eternity"]["6x5"] = @benchmarkable solve_eternity("eternity_6x5"; height=6, width=5) seconds=30 +SUITE["eternity"]["5x5_opt"] = @benchmarkable solve_eternity("eternity_5x5"; height=5, width=5, optimize=true) seconds=120 +SUITE["eternity"]["5x5_opt_ind"] = @benchmarkable solve_eternity("eternity_5x5"; height=5, width=5, optimize=true, indicator=true) seconds=120 SUITE["eternity"]["5x5_all"] = @benchmarkable solve_eternity("eternity_5x5"; all_solutions=true) seconds=30 include(joinpath(dir, "benchmark/lp/benchmark.jl")) diff --git a/benchmark/eternity/benchmark.jl b/benchmark/eternity/benchmark.jl index 5303f622..fe2498a1 100644 --- a/benchmark/eternity/benchmark.jl +++ b/benchmark/eternity/benchmark.jl @@ -27,7 +27,7 @@ function get_rotations(puzzle) return rotations end -function solve_eternity(fname="eternity_7"; height=nothing, width=nothing, all_solutions=false) +function solve_eternity(fname="eternity_7"; height=nothing, width=nothing, all_solutions=false, optimize=false, indicator=false) puzzle = read_puzzle(fname) rotations = get_rotations(puzzle) npieces = size(puzzle)[1] @@ -36,15 +36,27 @@ function solve_eternity(fname="eternity_7"; height=nothing, width=nothing, all_s ncolors = maximum(puzzle[:,2:end]) m = Model(optimizer_with_attributes(CS.Optimizer, "logging" => [], "all_solutions"=>all_solutions)) + if optimize + cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + m = Model(optimizer_with_attributes(CS.Optimizer, "logging" => [], "all_solutions"=>all_solutions, "lp_optimizer" => cbc_optimizer)) + end + @variable(m, 1 <= p[1:height, 1:width] <= npieces, Int) @variable(m, 0 <= pu[1:height, 1:width] <= ncolors, Int) @variable(m, 0 <= pr[1:height, 1:width] <= ncolors, Int) @variable(m, 0 <= pd[1:height, 1:width] <= ncolors, Int) @variable(m, 0 <= pl[1:height, 1:width] <= ncolors, Int) + if indicator + @variable(m, b, Bin) + end @constraint(m, p[:] in CS.AllDifferentSet()) for i=1:height, j=1:width - @constraint(m, [p[i,j], pu[i,j], pr[i,j], pd[i,j], pl[i,j]] in CS.TableSet(rotations)) + if indicator + @constraint(m, b => {[p[i,j], pu[i,j], pr[i,j], pd[i,j], pl[i,j]] in CS.TableSet(rotations)}) + else + @constraint(m, [p[i,j], pu[i,j], pr[i,j], pd[i,j], pl[i,j]] in CS.TableSet(rotations)) + end end # borders @@ -75,9 +87,19 @@ function solve_eternity(fname="eternity_7"; height=nothing, width=nothing, all_s @constraint(m, pr[i,j] == pl[i, j+1]) end - if width == height + if !optimize && indicator + @constraint(m, b == 1) + end + + if !optimize && width == height start_piece = findfirst(i->count(c->c == 0, puzzle[i,:]) == 2,1:npieces) @constraint(m, p[1,1] == start_piece) + elseif optimize + if indicator + @objective(m, Max, 1000*b + p[1,1] + p[1,2]) + else + @objective(m, Max, p[1,1] + p[1,2]) + end end optimize!(m) diff --git a/benchmark/run_benchmarks.jl b/benchmark/run_benchmarks.jl index 21956bb3..aa66ddb3 100755 --- a/benchmark/run_benchmarks.jl +++ b/benchmark/run_benchmarks.jl @@ -33,7 +33,7 @@ end if isinteractive() == false args = parse_commandline() using PkgBenchmark - using ConstraintSolver + using ConstraintSolver, Cbc using GitHub, JSON, Statistics github_auth = GitHub.authenticate(ENV["GITHUB_AUTH"]) diff --git a/src/ConstraintSolver.jl b/src/ConstraintSolver.jl index 1b4c299a..b780e4d1 100644 --- a/src/ConstraintSolver.jl +++ b/src/ConstraintSolver.jl @@ -90,6 +90,7 @@ function add_var!(com::CS.CoM, from::Int, to::Int; fix = nothing) push!(com.search_space, var) push!(com.subscription, Int[]) push!(com.bt_infeasible, 0) + push!(com.var_in_obj, false) return var end @@ -488,13 +489,7 @@ function update_best_bound!(backtrack_obj::BacktrackObj, com::CS.CoM, constraint further_pruning = true feasible = true for constraint in constraints - relevant = false - for obj_index in com.objective.indices - if obj_index in constraint.std.indices - relevant = true - break - end - end + relevant = any(com.var_in_obj[i] for i in constraint.std.indices) if relevant feasible = prune_constraint!( com, diff --git a/src/MOI_wrapper/objective.jl b/src/MOI_wrapper/objective.jl index 9c7f8d24..7e11e1cd 100644 --- a/src/MOI_wrapper/objective.jl +++ b/src/MOI_wrapper/objective.jl @@ -14,6 +14,7 @@ end function MOI.set(model::Optimizer, ::MOI.ObjectiveFunction, func::SVF) check_inbounds(model, func) + model.inner.var_in_obj[func.variable.value] = true model.inner.objective = SingleVariableObjective(func, func.variable.value, [func.variable.value]) return @@ -24,6 +25,7 @@ function MOI.set(model::Optimizer, ::MOI.ObjectiveFunction, func::SAF{T}) where indices = [func.terms[i].variable_index.value for i = 1:length(func.terms)] coeffs = [func.terms[i].coefficient for i = 1:length(func.terms)] lc = LinearCombination(indices, coeffs) + model.inner.var_in_obj[indices] .= true model.inner.objective = LinearCombinationObjective(func, lc, func.constant, indices) return end diff --git a/src/MOI_wrapper/variables.jl b/src/MOI_wrapper/variables.jl index 7019c881..3e94ffd0 100644 --- a/src/MOI_wrapper/variables.jl +++ b/src/MOI_wrapper/variables.jl @@ -75,6 +75,7 @@ function MOI.add_variable(model::Optimizer) model.variable_info[index].changes = changes push!(model.inner.subscription, Int[]) push!(model.inner.bt_infeasible, 0) + push!(model.inner.var_in_obj, false) addupd_var_in_inner_model(model, index) return MOI.VariableIndex(index) end diff --git a/src/branching.jl b/src/branching.jl index 48c2bf3e..024487f9 100644 --- a/src/branching.jl +++ b/src/branching.jl @@ -92,18 +92,29 @@ function get_next_branch_variable(com::CS.CoM) biggest_inf = -1 best_ind = -1 biggest_dependent = typemax(Int) + is_in_objective = false found = false for ind = 1:length(com.search_space) if !isfixed(com.search_space[ind]) num_pvals = nvalues(com.search_space[ind]) inf = com.bt_infeasible[ind] - if inf >= biggest_inf - if inf > biggest_inf || num_pvals < lowest_num_pvals - lowest_num_pvals = num_pvals - biggest_inf = inf - best_ind = ind - found = true + if !is_in_objective && com.var_in_obj[ind] + is_in_objective = true + lowest_num_pvals = num_pvals + biggest_inf = inf + best_ind = ind + found = true + continue + end + if !is_in_objective || com.var_in_obj[ind] + if inf >= biggest_inf + if inf > biggest_inf || num_pvals < lowest_num_pvals + lowest_num_pvals = num_pvals + biggest_inf = inf + best_ind = ind + found = true + end end end end diff --git a/src/constraints/indicator.jl b/src/constraints/indicator.jl index a1870330..f47bd538 100644 --- a/src/constraints/indicator.jl +++ b/src/constraints/indicator.jl @@ -136,7 +136,6 @@ the possible values the table constraint allows. `var_idx`, `lb` and `ub` don't Additionally only a rough estimated bound is used which can be computed relatively fast. This method calls the inner_constraint method if it exists and the indicator is activated. """ -#= function update_best_bound_constraint!(com::CS.CoM, constraint::IndicatorConstraint, fct::Union{MOI.VectorOfVariables, VAF{T}}, @@ -162,7 +161,6 @@ function update_best_bound_constraint!(com::CS.CoM, end return true end -=# function single_reverse_pruning_constraint!( com::CoM, diff --git a/src/type_inits.jl b/src/type_inits.jl index e4c56522..73938968 100644 --- a/src/type_inits.jl +++ b/src/type_inits.jl @@ -88,7 +88,8 @@ function ConstraintSolverModel(::Type{T} = Float64) where {T<:Real} 1, # c_backtrack_idx Vector{BacktrackObj{T}}(), # backtrack_vec MOI.FEASIBILITY_SENSE, # - NoObjective(), # + NoObjective(), # + Vector{Bool}(), # var_in_obj get_traverse_strategy(), get_branch_split(), zero(T), # best_sol, diff --git a/src/types.jl b/src/types.jl index 37acf0be..ccb78e2c 100644 --- a/src/types.jl +++ b/src/types.jl @@ -330,6 +330,7 @@ mutable struct ConstraintSolverModel{T<:Real} backtrack_vec::Vector{BacktrackObj{T}} sense::MOI.OptimizationSense objective::ObjectiveFunction + var_in_obj::Vector{Bool} # saves whether a variable is part of the objective function traverse_strategy::Val branch_split::Val best_sol::T # Objective of the best solution