Skip to content

Commit eba2486

Browse files
authored
Implementing #57: Real coefficients (#58)
* Implementing #57: Real coefficients for eq_sum constraint * `SolverOptions()` constructor * `LinearVariables` -> `LinearCombination`
1 parent af58f29 commit eba2486

File tree

12 files changed

+290
-116
lines changed

12 files changed

+290
-116
lines changed

benchmark/killer_sudoku/cs.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using ConstraintSolver, MathOptInterface, JSON
1+
using ConstraintSolver, JuMP, MathOptInterface, JSON
22

33
if !@isdefined CS
44
const CS = ConstraintSolver
@@ -57,8 +57,8 @@ function solve_all(filenames; benchmark=false, single_times=true)
5757
GC.enable(true)
5858
end
5959
if !benchmark
60-
@show m.inner.info
6160
println("Status: ", status)
61+
@show m.inner.info
6262
solution = zeros(Int, 9, 9)
6363
for r=1:9
6464
solution[r,:] = [MOI.get(m, MOI.VariablePrimal(), x[r][c][1]) for c=1:9]

benchmark/sudoku/cs.jl

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
using ConstraintSolver, MathOptInterface
1+
using ConstraintSolver, JuMP, MathOptInterface
22

3-
const CS = ConstraintSolver
3+
if !@isdefined CS
4+
const CS = ConstraintSolver
5+
end
46
const MOI = MathOptInterface
57
const MOIU = MOI.Utilities
68
include("../../test/sudoku_fcts.jl")
@@ -57,8 +59,8 @@ function solve_all(grids; benchmark=false, single_times=true)
5759
GC.enable(true)
5860
end
5961
if !benchmark
60-
@show m.inner.info
6162
println("Status: ", status)
63+
@show m.inner.info
6264
solution = zeros(Int, 9, 9)
6365
for r=1:9
6466
solution[r,:] = [MOI.get(m, MOI.VariablePrimal(), x[r][c][1]) for c=1:9]

src/ConstraintSolver.jl

+93-39
Original file line numberDiff line numberDiff line change
@@ -83,26 +83,60 @@ struct NotEqualSet{T} <: MOI.AbstractScalarSet
8383
value :: T
8484
end
8585

86-
mutable struct LinearVariables
86+
mutable struct LinearCombination{T <: Real}
8787
indices :: Vector{Int}
88-
coeffs :: Vector{Int}
88+
coeffs :: Vector{T}
8989
end
9090

91-
mutable struct LinearConstraint <: Constraint
91+
mutable struct LinearConstraint{T <: Real} <: Constraint
9292
idx :: Int
9393
fct :: Function
9494
indices :: Vector{Int}
9595
pvals :: Vector{Int}
96-
coeffs :: Vector{Int}
96+
coeffs :: Vector{T}
9797
operator :: Symbol
98-
rhs :: Int
98+
rhs :: T
9999
in_all_different :: Bool
100-
mins :: Vector{Int}
101-
maxs :: Vector{Int}
102-
pre_mins :: Vector{Int}
103-
pre_maxs :: Vector{Int}
100+
mins :: Vector{T}
101+
maxs :: Vector{T}
102+
pre_mins :: Vector{T}
103+
pre_maxs :: Vector{T}
104104
hash :: UInt64
105-
LinearConstraint() = new()
105+
end
106+
107+
function LinearConstraint(fct::Function, operator::Symbol, indices::Vector{Int}, coeffs::Vector{T}, rhs::Real) where T <: Real
108+
# get common type for rhs and coeffs
109+
promote_T = promote_type(typeof(rhs), eltype(coeffs))
110+
if promote_T != eltype(coeffs)
111+
coeffs = convert.(promote_T, coeffs)
112+
end
113+
if promote_T != typeof(rhs)
114+
rhs = convert(promote_T, rhs)
115+
end
116+
maxs = zeros(promote_T, length(indices))
117+
mins = zeros(promote_T, length(indices))
118+
pre_maxs = zeros(promote_T, length(indices))
119+
pre_mins = zeros(promote_T, length(indices))
120+
# this can be changed later in `set_in_all_different!` but needs to be initialized with false
121+
in_all_different = false
122+
pvals = Int[]
123+
124+
lc = LinearConstraint(
125+
0, # idx will be filled later
126+
fct,
127+
indices,
128+
pvals,
129+
coeffs,
130+
operator,
131+
rhs,
132+
in_all_different,
133+
mins,
134+
maxs,
135+
pre_mins,
136+
pre_maxs,
137+
zero(UInt64)
138+
)
139+
return lc
106140
end
107141

108142
mutable struct BacktrackObj
@@ -147,10 +181,12 @@ mutable struct ConstraintSolverModel
147181
info :: CSInfo
148182
input :: Dict{Symbol,Any}
149183
logs :: Vector{TreeLogNode}
184+
options :: SolverOptions
150185
end
151186

152187
const CoM = ConstraintSolverModel
153188

189+
include("util.jl")
154190
include("MOI_wrapper/MOI_wrapper.jl")
155191
include("printing.jl")
156192
include("logs.jl")
@@ -185,7 +221,8 @@ function ConstraintSolverModel()
185221
Vector{Int}(), # solutions
186222
CSInfo(0, false, 0, 0, 0), # info
187223
Dict{Symbol,Any}(), # input
188-
Vector{TreeLogNode}() # logs
224+
Vector{TreeLogNode}(), # logs
225+
SolverOptions() # options
189226
)
190227
end
191228

@@ -629,9 +666,38 @@ function update_best_bound!(backtrack_obj::BacktrackObj, com::CS.CoM, constraint
629666
if backtrack_obj.best_bound != new_bb
630667
further_pruning = false
631668
end
669+
if backtrack_obj.best_bound == com.best_bound
670+
backtrack_obj.best_bound = new_bb
671+
update_best_bound!(com)
672+
else
673+
backtrack_obj.best_bound = new_bb
674+
end
632675
return true, further_pruning
633676
end
634677

678+
function update_best_bound!(com::CS.CoM)
679+
if com.sense == MOI.MIN_SENSE
680+
max_val = typemax(Int64)
681+
com.best_bound = minimum([bo.status == :Open ? bo.best_bound : max_val for bo in com.backtrack_vec])
682+
elseif com.sense == MOI.MAX_SENSE
683+
min_val = typemin(Int64)
684+
com.best_bound = maximum([bo.status == :Open ? bo.best_bound : min_val for bo in com.backtrack_vec])
685+
else
686+
com.best_bound = 0
687+
end
688+
end
689+
690+
function set_state_to_best_sol!(com::CS.CoM, last_backtrack_id::Int)
691+
obj_factor = com.sense == MOI.MIN_SENSE ? 1 : -1
692+
backtrack_vec = com.backtrack_vec
693+
# find one of the best solutions
694+
sol, sol_id = findmin([backtrack_vec[sol_id].best_bound*obj_factor for sol_id in com.solutions])
695+
backtrack_id = com.solutions[sol_id]
696+
checkout_from_to!(com, last_backtrack_id, backtrack_id)
697+
# prune the last step as checkout_from_to! excludes the to part
698+
prune!(com, [backtrack_id])
699+
end
700+
635701
"""
636702
backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
637703
@@ -731,15 +797,7 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
731797
if l <= 0 || l > length(backtrack_vec)
732798
break
733799
end
734-
if com.sense == MOI.MIN_SENSE
735-
max_val = typemax(Int64)
736-
com.best_bound = minimum([bo.status == :Open ? bo.best_bound : max_val for bo in backtrack_vec])
737-
elseif com.sense == MOI.MAX_SENSE
738-
min_val = typemin(Int64)
739-
com.best_bound = maximum([bo.status == :Open ? bo.best_bound : min_val for bo in backtrack_vec])
740-
else
741-
com.best_bound = 0
742-
end
800+
update_best_bound!(com)
743801

744802
ind = backtrack_obj.variable_idx
745803

@@ -782,7 +840,6 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
782840
# first update the best bound (only constraints which have an index in the objective function)
783841
if com.sense != MOI.FEASIBILITY_SENSE
784842
feasible, further_pruning = update_best_bound!(backtrack_obj, com, constraints)
785-
786843
if !feasible
787844
com.info.backtrack_reverses += 1
788845
continue
@@ -792,7 +849,7 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
792849
if further_pruning
793850
# prune completely start with all that changed by the fix or by updating best bound
794851
feasible = prune!(com)
795-
852+
796853
if !feasible
797854
com.info.backtrack_reverses += 1
798855
continue
@@ -804,35 +861,36 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
804861
# no index found => solution found
805862
if !found
806863
new_sol = get_best_bound(com)
807-
if length(com.solutions) == 0
864+
if length(com.solutions) == 0 || obj_factor*new_sol <= obj_factor*com.best_sol
865+
push!(com.solutions, backtrack_obj.idx)
808866
com.best_sol = new_sol
809-
elseif obj_factor*new_sol < obj_factor*com.best_sol
810-
com.best_sol = new_sol
811-
end
812-
push!(com.solutions, backtrack_obj.idx)
813-
if com.best_sol == com.best_bound
814-
return :Solved
815-
else
867+
if com.best_sol == com.best_bound
868+
return :Solved
869+
end
816870
# set all nodes to :Worse if they can't achieve a better solution
817871
for bo in backtrack_vec
818872
if bo.status == :Open && obj_factor*bo.best_bound >= com.best_sol
819873
bo.status = :Worse
820874
end
821875
end
822876
continue
877+
else
878+
if com.best_sol == com.best_bound
879+
set_state_to_best_sol!(com, last_backtrack_id)
880+
return :Solved
881+
end
882+
continue
823883
end
824884
end
825885

826886
if com.info.backtrack_fixes > max_bt_steps
827887
return :NotSolved
828888
end
829889

830-
831-
832890
if com.input[:logs]
833891
com.logs[backtrack_obj.idx] = log_one_node(com, length(com.search_space), backtrack_obj.idx, step_nr)
834892
end
835-
893+
836894
pvals = reverse!(values(com.search_space[ind]))
837895
last_backtrack_obj = backtrack_vec[last_backtrack_id]
838896
for pval in pvals
@@ -861,12 +919,7 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
861919
end
862920
end
863921
if length(com.solutions) > 0
864-
# find one of the best solutions
865-
sol, sol_id = findmin([backtrack_vec[sol_id].best_bound*obj_factor for sol_id in com.solutions])
866-
backtrack_id = com.solutions[sol_id]
867-
checkout_from_to!(com, last_backtrack_id, backtrack_id)
868-
# prune the last step as checkout_from_to! excludes the to part
869-
prune!(com, [backtrack_id])
922+
set_state_to_best_sol!(com, last_backtrack_id)
870923
return :Solved
871924
end
872925
return :Infeasible
@@ -997,6 +1050,7 @@ function solve!(com::CS.CoM, options::SolverOptions)
9971050
return :Solved
9981051
end
9991052

1053+
com.options = options
10001054
backtrack = options.backtrack
10011055
max_bt_steps = options.max_bt_steps
10021056
backtrack_sorting = options.backtrack_sorting

src/MOI_wrapper/constraints.jl

+6-13
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,14 @@ function MOI.add_constraint(model::Optimizer, func::SAF, set::MOI.EqualTo{Float6
2323
fix!(model.inner, model.variable_info[func.terms[1].variable_index.value], convert(Int64, set.value/func.terms[1].coefficient))
2424
return MOI.ConstraintIndex{SAF, MOI.EqualTo{Float64}}(0)
2525
end
26-
27-
lc = LinearConstraint()
26+
2827
indices = [v.variable_index.value for v in func.terms]
2928
coeffs = [v.coefficient for v in func.terms]
30-
lc.fct = eq_sum
31-
lc.indices = indices
32-
lc.coeffs = coeffs
33-
lc.operator = :(==)
34-
lc.rhs = set.value
35-
lc.maxs = zeros(Int, length(indices))
36-
lc.mins = zeros(Int, length(indices))
37-
lc.pre_maxs = zeros(Int, length(indices))
38-
lc.pre_mins = zeros(Int, length(indices))
39-
# this can be changed later in `set_in_all_different!` but needs to be initialized with false
40-
lc.in_all_different = false
29+
fct = eq_sum
30+
operator = :(==)
31+
rhs = set.value
32+
33+
lc = LinearConstraint(fct, operator, indices, coeffs, rhs)
4134
lc.idx = length(model.inner.constraints)+1
4235

4336
push!(model.inner.constraints, lc)

0 commit comments

Comments
 (0)