Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing #57: Real coefficients #58

Merged
merged 9 commits into from
Jan 16, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benchmark/killer_sudoku/cs.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ConstraintSolver, MathOptInterface, JSON
using ConstraintSolver, JuMP, MathOptInterface, JSON

if !@isdefined CS
const CS = ConstraintSolver
@@ -57,8 +57,8 @@ function solve_all(filenames; benchmark=false, single_times=true)
GC.enable(true)
end
if !benchmark
@show m.inner.info
println("Status: ", status)
@show m.inner.info
solution = zeros(Int, 9, 9)
for r=1:9
solution[r,:] = [MOI.get(m, MOI.VariablePrimal(), x[r][c][1]) for c=1:9]
8 changes: 5 additions & 3 deletions benchmark/sudoku/cs.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using ConstraintSolver, MathOptInterface
using ConstraintSolver, JuMP, MathOptInterface

const CS = ConstraintSolver
if !@isdefined CS
const CS = ConstraintSolver
end
const MOI = MathOptInterface
const MOIU = MOI.Utilities
include("../../test/sudoku_fcts.jl")
@@ -57,8 +59,8 @@ function solve_all(grids; benchmark=false, single_times=true)
GC.enable(true)
end
if !benchmark
@show m.inner.info
println("Status: ", status)
@show m.inner.info
solution = zeros(Int, 9, 9)
for r=1:9
solution[r,:] = [MOI.get(m, MOI.VariablePrimal(), x[r][c][1]) for c=1:9]
132 changes: 93 additions & 39 deletions src/ConstraintSolver.jl
Original file line number Diff line number Diff line change
@@ -83,26 +83,60 @@ struct NotEqualSet{T} <: MOI.AbstractScalarSet
value :: T
end

mutable struct LinearVariables
mutable struct LinearCombination{T <: Real}
indices :: Vector{Int}
coeffs :: Vector{Int}
coeffs :: Vector{T}
end

mutable struct LinearConstraint <: Constraint
mutable struct LinearConstraint{T <: Real} <: Constraint
idx :: Int
fct :: Function
indices :: Vector{Int}
pvals :: Vector{Int}
coeffs :: Vector{Int}
coeffs :: Vector{T}
operator :: Symbol
rhs :: Int
rhs :: T
in_all_different :: Bool
mins :: Vector{Int}
maxs :: Vector{Int}
pre_mins :: Vector{Int}
pre_maxs :: Vector{Int}
mins :: Vector{T}
maxs :: Vector{T}
pre_mins :: Vector{T}
pre_maxs :: Vector{T}
hash :: UInt64
LinearConstraint() = new()
end

function LinearConstraint(fct::Function, operator::Symbol, indices::Vector{Int}, coeffs::Vector{T}, rhs::Real) where T <: Real
# get common type for rhs and coeffs
promote_T = promote_type(typeof(rhs), eltype(coeffs))
if promote_T != eltype(coeffs)
coeffs = convert.(promote_T, coeffs)
end
if promote_T != typeof(rhs)
rhs = convert(promote_T, rhs)
end
maxs = zeros(promote_T, length(indices))
mins = zeros(promote_T, length(indices))
pre_maxs = zeros(promote_T, length(indices))
pre_mins = zeros(promote_T, length(indices))
# this can be changed later in `set_in_all_different!` but needs to be initialized with false
in_all_different = false
pvals = Int[]

lc = LinearConstraint(
0, # idx will be filled later
fct,
indices,
pvals,
coeffs,
operator,
rhs,
in_all_different,
mins,
maxs,
pre_mins,
pre_maxs,
zero(UInt64)
)
return lc
end

mutable struct BacktrackObj
@@ -147,10 +181,12 @@ mutable struct ConstraintSolverModel
info :: CSInfo
input :: Dict{Symbol,Any}
logs :: Vector{TreeLogNode}
options :: SolverOptions
end

const CoM = ConstraintSolverModel

include("util.jl")
include("MOI_wrapper/MOI_wrapper.jl")
include("printing.jl")
include("logs.jl")
@@ -185,7 +221,8 @@ function ConstraintSolverModel()
Vector{Int}(), # solutions
CSInfo(0, false, 0, 0, 0), # info
Dict{Symbol,Any}(), # input
Vector{TreeLogNode}() # logs
Vector{TreeLogNode}(), # logs
SolverOptions() # options
)
end

@@ -629,9 +666,38 @@ function update_best_bound!(backtrack_obj::BacktrackObj, com::CS.CoM, constraint
if backtrack_obj.best_bound != new_bb
further_pruning = false
end
if backtrack_obj.best_bound == com.best_bound
backtrack_obj.best_bound = new_bb
update_best_bound!(com)
else
backtrack_obj.best_bound = new_bb
end
return true, further_pruning
end

function update_best_bound!(com::CS.CoM)
if com.sense == MOI.MIN_SENSE
max_val = typemax(Int64)
com.best_bound = minimum([bo.status == :Open ? bo.best_bound : max_val for bo in com.backtrack_vec])
elseif com.sense == MOI.MAX_SENSE
min_val = typemin(Int64)
com.best_bound = maximum([bo.status == :Open ? bo.best_bound : min_val for bo in com.backtrack_vec])
else
com.best_bound = 0
end
end

function set_state_to_best_sol!(com::CS.CoM, last_backtrack_id::Int)
obj_factor = com.sense == MOI.MIN_SENSE ? 1 : -1
backtrack_vec = com.backtrack_vec
# find one of the best solutions
sol, sol_id = findmin([backtrack_vec[sol_id].best_bound*obj_factor for sol_id in com.solutions])
backtrack_id = com.solutions[sol_id]
checkout_from_to!(com, last_backtrack_id, backtrack_id)
# prune the last step as checkout_from_to! excludes the to part
prune!(com, [backtrack_id])
end

"""
backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
@@ -731,15 +797,7 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
if l <= 0 || l > length(backtrack_vec)
break
end
if com.sense == MOI.MIN_SENSE
max_val = typemax(Int64)
com.best_bound = minimum([bo.status == :Open ? bo.best_bound : max_val for bo in backtrack_vec])
elseif com.sense == MOI.MAX_SENSE
min_val = typemin(Int64)
com.best_bound = maximum([bo.status == :Open ? bo.best_bound : min_val for bo in backtrack_vec])
else
com.best_bound = 0
end
update_best_bound!(com)

ind = backtrack_obj.variable_idx

@@ -782,7 +840,6 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
# first update the best bound (only constraints which have an index in the objective function)
if com.sense != MOI.FEASIBILITY_SENSE
feasible, further_pruning = update_best_bound!(backtrack_obj, com, constraints)

if !feasible
com.info.backtrack_reverses += 1
continue
@@ -792,7 +849,7 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
if further_pruning
# prune completely start with all that changed by the fix or by updating best bound
feasible = prune!(com)

if !feasible
com.info.backtrack_reverses += 1
continue
@@ -804,35 +861,36 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
# no index found => solution found
if !found
new_sol = get_best_bound(com)
if length(com.solutions) == 0
if length(com.solutions) == 0 || obj_factor*new_sol <= obj_factor*com.best_sol
push!(com.solutions, backtrack_obj.idx)
com.best_sol = new_sol
elseif obj_factor*new_sol < obj_factor*com.best_sol
com.best_sol = new_sol
end
push!(com.solutions, backtrack_obj.idx)
if com.best_sol == com.best_bound
return :Solved
else
if com.best_sol == com.best_bound
return :Solved
end
# set all nodes to :Worse if they can't achieve a better solution
for bo in backtrack_vec
if bo.status == :Open && obj_factor*bo.best_bound >= com.best_sol
bo.status = :Worse
end
end
continue
else
if com.best_sol == com.best_bound
set_state_to_best_sol!(com, last_backtrack_id)
return :Solved
end
continue
end
end

if com.info.backtrack_fixes > max_bt_steps
return :NotSolved
end



if com.input[:logs]
com.logs[backtrack_obj.idx] = log_one_node(com, length(com.search_space), backtrack_obj.idx, step_nr)
end

pvals = reverse!(values(com.search_space[ind]))
last_backtrack_obj = backtrack_vec[last_backtrack_id]
for pval in pvals
@@ -861,12 +919,7 @@ function backtrack!(com::CS.CoM, max_bt_steps; sorting=true)
end
end
if length(com.solutions) > 0
# find one of the best solutions
sol, sol_id = findmin([backtrack_vec[sol_id].best_bound*obj_factor for sol_id in com.solutions])
backtrack_id = com.solutions[sol_id]
checkout_from_to!(com, last_backtrack_id, backtrack_id)
# prune the last step as checkout_from_to! excludes the to part
prune!(com, [backtrack_id])
set_state_to_best_sol!(com, last_backtrack_id)
return :Solved
end
return :Infeasible
@@ -997,6 +1050,7 @@ function solve!(com::CS.CoM, options::SolverOptions)
return :Solved
end

com.options = options
backtrack = options.backtrack
max_bt_steps = options.max_bt_steps
backtrack_sorting = options.backtrack_sorting
19 changes: 6 additions & 13 deletions src/MOI_wrapper/constraints.jl
Original file line number Diff line number Diff line change
@@ -23,21 +23,14 @@ function MOI.add_constraint(model::Optimizer, func::SAF, set::MOI.EqualTo{Float6
fix!(model.inner, model.variable_info[func.terms[1].variable_index.value], convert(Int64, set.value/func.terms[1].coefficient))
return MOI.ConstraintIndex{SAF, MOI.EqualTo{Float64}}(0)
end

lc = LinearConstraint()

indices = [v.variable_index.value for v in func.terms]
coeffs = [v.coefficient for v in func.terms]
lc.fct = eq_sum
lc.indices = indices
lc.coeffs = coeffs
lc.operator = :(==)
lc.rhs = set.value
lc.maxs = zeros(Int, length(indices))
lc.mins = zeros(Int, length(indices))
lc.pre_maxs = zeros(Int, length(indices))
lc.pre_mins = zeros(Int, length(indices))
# this can be changed later in `set_in_all_different!` but needs to be initialized with false
lc.in_all_different = false
fct = eq_sum
operator = :(==)
rhs = set.value

lc = LinearConstraint(fct, operator, indices, coeffs, rhs)
lc.idx = length(model.inner.constraints)+1

push!(model.inner.constraints, lc)
67 changes: 34 additions & 33 deletions src/eq_sum.jl
Original file line number Diff line number Diff line change
@@ -1,50 +1,46 @@
"""
Base.:(==)(x::LinearVariables, y::Int)
Base.:(==)(x::LinearCombination, y::Real)
Create a linear constraint with `LinearVariables` and an integer rhs `y`. \n
Create a linear constraint with `LinearCombination` and an integer rhs `y`. \n
Can be used i.e by `add_constraint!(com, x+y = 2)`.
"""
function Base.:(==)(x::LinearVariables, y::Int)
lc = LinearConstraint()
function Base.:(==)(x::LinearCombination, y::Real)
indices, coeffs, constant_lhs = simplify(x)
lc.fct = eq_sum
lc.indices = indices
lc.coeffs = coeffs
lc.operator = :(==)
lc.rhs = y-constant_lhs
lc.maxs = zeros(Int, length(indices))
lc.mins = zeros(Int, length(indices))
lc.pre_maxs = zeros(Int, length(indices))
lc.pre_mins = zeros(Int, length(indices))
# this can be changed later in `set_in_all_different!` but needs to be initialized with false
lc.in_all_different = false

rhs = y-constant_lhs
fct = eq_sum
operator = :(==)
rhs = y-constant_lhs
lc = LinearConstraint(fct, operator, indices, coeffs, rhs)

lc.hash = constraint_hash(lc)
return lc
end

"""
Base.:(==)(x::LinearVariables, y::Variable)
Base.:(==)(x::LinearCombination, y::Variable)
Create a linear constraint with `LinearVariables` and a variable rhs `y`. \n
Create a linear constraint with `LinearCombination` and a variable rhs `y`. \n
Can be used i.e by `add_constraint!(com, x+y = z)`.
"""
function Base.:(==)(x::LinearVariables, y::Variable)
return x == LinearVariables([y.idx], [1])
function Base.:(==)(x::LinearCombination, y::Variable)
return x == LinearCombination([y.idx], [1])
end

"""
Base.:(==)(x::LinearVariables, y::LinearVariables)
Base.:(==)(x::LinearCombination, y::LinearCombination)
Create a linear constraint with `LinearVariables` on the left and right hand side. \n
Create a linear constraint with `LinearCombination` on the left and right hand side. \n
Can be used i.e by `add_constraint!(com, x+y = a+b)`.
"""
function Base.:(==)(x::LinearVariables, y::LinearVariables)
function Base.:(==)(x::LinearCombination, y::LinearCombination)
return x-y == 0
end

function eq_sum(com::CS.CoM, constraint::LinearConstraint; logs = true)
indices = constraint.indices
search_space = com.search_space
# println("constraint: ", constraint)

# compute max and min values for each index
maxs = constraint.maxs
@@ -99,20 +95,22 @@ function eq_sum(com::CS.CoM, constraint::LinearConstraint; logs = true)
# update all
for (i,idx) in enumerate(indices)
if maxs[i] < pre_maxs[i]
threshold = get_safe_upper_threshold(com, maxs[i], constraint.coeffs[i])
if constraint.coeffs[i] > 0
still_feasible = remove_above!(com, search_space[idx], fld(maxs[i], constraint.coeffs[i]))
still_feasible = remove_above!(com, search_space[idx], threshold)
else
still_feasible = remove_below!(com, search_space[idx], fld(maxs[i], constraint.coeffs[i]))
still_feasible = remove_below!(com, search_space[idx], threshold)
end
if !still_feasible
return false
end
end
if mins[i] > pre_mins[i]
threshold = get_safe_lower_threshold(com, mins[i], constraint.coeffs[i])
if constraint.coeffs[i] > 0
still_feasible = remove_below!(com, search_space[idx], cld(mins[i], constraint.coeffs[i]))
still_feasible = remove_below!(com, search_space[idx], threshold)
else
still_feasible = remove_above!(com, search_space[idx], cld(mins[i], constraint.coeffs[i]))
still_feasible = remove_above!(com, search_space[idx], threshold)
end
if !still_feasible
return false
@@ -146,12 +144,12 @@ function eq_sum(com::CS.CoM, constraint::LinearConstraint; logs = true)

# only a single one left
if n_unfixed == 1
if unfixed_rhs % constraint.coeffs[unfixed_local_ind_1] != 0
if !isapprox_discrete(com, unfixed_rhs % constraint.coeffs[unfixed_local_ind_1])
com.bt_infeasible[unfixed_ind_1] += 1
return false
else
# divide rhs such that it is comparable with the variable directly without coefficient
unfixed_rhs = fld(unfixed_rhs, constraint.coeffs[unfixed_local_ind_1])
unfixed_rhs = get_approx_discrete(unfixed_rhs / constraint.coeffs[unfixed_local_ind_1])
end
if !has(search_space[unfixed_ind_1], unfixed_rhs)
com.bt_infeasible[unfixed_ind_1] += 1
@@ -185,14 +183,17 @@ function eq_sum(com::CS.CoM, constraint::LinearConstraint; logs = true)

for val in values(search_space[this])
# if we choose this value but the other wouldn't be an integer => remove this value
if (unfixed_rhs-val*constraint.coeffs[local_this]) % constraint.coeffs[local_other] != 0
if !isapprox_divisible(com, (unfixed_rhs-val*constraint.coeffs[local_this]), constraint.coeffs[local_other])
if !rm!(com, search_space[this], val)
return false
end
continue
end

check_other_val = fld(unfixed_rhs-val*constraint.coeffs[local_this], constraint.coeffs[local_other])
# get discrete other value
check_other_val_float = (unfixed_rhs-val*constraint.coeffs[local_this])/constraint.coeffs[local_other]
check_other_val = get_approx_discrete(check_other_val_float)

# if all different but those two are the same
if is_all_different && check_other_val == val
if !rm!(com, search_space[this], val)
@@ -241,15 +242,15 @@ function eq_sum(com::CoM, constraint::LinearConstraint, val::Int, index::Int)
end
end
end
if num_not_fixed == 0 && csum + val != constraint.rhs
if num_not_fixed == 0 && !isapprox(csum + val, constraint.rhs; atol=com.options.atol, rtol=com.options.rtol)
return false
end

if csum + val + min_extra > constraint.rhs
if csum + val + min_extra > constraint.rhs+com.options.atol
return false
end

if csum + val + max_extra < constraint.rhs
if csum + val + max_extra < constraint.rhs-com.options.atol
return false
end

38 changes: 19 additions & 19 deletions src/linearcombination.jl
Original file line number Diff line number Diff line change
@@ -1,66 +1,66 @@
function Base.:+(x::Variable, y::Variable)
return LinearVariables([x.idx,y.idx],[1,1])
return LinearCombination([x.idx,y.idx],[1,1])
end

function Base.:-(x::Variable, y::Variable)
return LinearVariables([x.idx,y.idx],[1,-1])
return LinearCombination([x.idx,y.idx],[1,-1])
end

function Base.:+(x::LinearVariables, y::Variable)
lv = LinearVariables(x.indices, x.coeffs)
function Base.:+(x::LinearCombination, y::Variable)
lv = LinearCombination(x.indices, x.coeffs)
push!(lv.indices, y.idx)
push!(lv.coeffs, 1)
return lv
end

function Base.:-(x::LinearVariables, y::Variable)
lv = LinearVariables(x.indices, x.coeffs)
function Base.:-(x::LinearCombination, y::Variable)
lv = LinearCombination(x.indices, x.coeffs)
push!(lv.indices, y.idx)
push!(lv.coeffs, -1)
return lv
end

function Base.:+(x::Variable, y::Int)
return LinearVariables([x.idx], [1])+y
return LinearCombination([x.idx], [1])+y
end

function Base.:+(x::LinearVariables, y::Int)
lv = LinearVariables(x.indices, x.coeffs)
function Base.:+(x::LinearCombination, y::Int)
lv = LinearCombination(x.indices, x.coeffs)
push!(lv.indices, 0)
push!(lv.coeffs, y)
return lv
end

function Base.:-(x::Variable, y::Int)
return LinearVariables([x.idx], [1])-y
return LinearCombination([x.idx], [1])-y
end

function Base.:-(x::LinearVariables, y::Int)
function Base.:-(x::LinearCombination, y::Int)
return x+(-y)
end

function Base.:+(x::Variable, y::LinearVariables)
function Base.:+(x::Variable, y::LinearCombination)
return y+x # commutative
end

function Base.:+(x::LinearVariables, y::LinearVariables)
return LinearVariables(vcat(x.indices, y.indices), vcat(x.coeffs, y.coeffs))
function Base.:+(x::LinearCombination, y::LinearCombination)
return LinearCombination(vcat(x.indices, y.indices), vcat(x.coeffs, y.coeffs))
end

function Base.:-(x::LinearVariables, y::LinearVariables)
return LinearVariables(vcat(x.indices, y.indices), vcat(x.coeffs, -y.coeffs))
function Base.:-(x::LinearCombination, y::LinearCombination)
return LinearCombination(vcat(x.indices, y.indices), vcat(x.coeffs, -y.coeffs))
end

function Base.:*(x::Int, y::Variable)
return LinearVariables([y.idx],[x])
return LinearCombination([y.idx],[x])
end

function Base.:*(y::Variable, x::Int)
return LinearVariables([y.idx],[x])
return LinearCombination([y.idx],[x])
end


function simplify(x::LinearVariables)
function simplify(x::LinearCombination)
set_indices = Set(x.indices)
# if unique
if length(set_indices) == length(x.indices)
10 changes: 7 additions & 3 deletions src/options.jl
Original file line number Diff line number Diff line change
@@ -3,19 +3,23 @@ mutable struct SolverOptions
max_bt_steps :: Int64
backtrack_sorting :: Bool
keep_logs :: Bool
rtol :: Float64
atol :: Float64
end

function get_default_options()
function SolverOptions()
backtrack = true
max_bt_steps = typemax(Int64)
backtrack_sorting = true
keep_logs = false
rtol = 1e-6
atol = 1e-6

return SolverOptions(backtrack, max_bt_steps, backtrack_sorting, keep_logs)
return SolverOptions(backtrack, max_bt_steps, backtrack_sorting, keep_logs, rtol, atol)
end

function combine_options(options)
defaults = get_default_options()
defaults = SolverOptions()
options_dict = Dict{Symbol,Any}()
for kv in options
if !in(kv[1], fieldnames(SolverOptions))
35 changes: 35 additions & 0 deletions src/util.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function isapprox_discrete(com::CS.CoM, val)
return isapprox(val, round(val); atol=com.options.atol, rtol=com.options.rtol)
end

function isapprox_divisible(com::CS.CoM, val, divider)
modulo_near_0 = isapprox(val % divider, 0; atol=com.options.atol, rtol=com.options.rtol)
modulo_near_divider = isapprox(val % divider, divider; atol=com.options.atol, rtol=com.options.rtol)
return modulo_near_0 || modulo_near_divider
end

function get_approx_discrete(val)
return convert(Int, round(val))
end

function get_safe_upper_threshold(com::CS.CoM, val, divider)
floor_threshold = fld(val, divider)
float_threshold = val/divider
threshold = convert(Int, floor_threshold)
# if the difference is almost 1 we round in the other direction to provide a safe upper bound
if isapprox(float_threshold-floor_threshold, 1.0; rtol=com.options.rtol, atol=com.options.atol)
threshold = convert(Int, cld(val, divider))
end
return threshold
end

function get_safe_lower_threshold(com::CS.CoM, val, divider)
ceil_threshold = cld(val, divider)
float_threshold = val/divider
threshold = convert(Int, ceil_threshold)
# if the difference is almost 1 we round in the other direction to provide a safe lower bound
if isapprox(ceil_threshold-float_threshold, 1.0; rtol=com.options.rtol, atol=com.options.atol)
threshold = convert(Int, fld(val, divider))
end
return threshold
end
3 changes: 2 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ include("moi.jl")

include("sudoku_fcts.jl")
include("small_special_tests.jl")
include("small_eq_sum_real.jl")
include("sudoku_tests.jl")
include("killer_sudoku_tests.jl")
include("graph_color_tests.jl")
include("graph_color_tests.jl")
84 changes: 84 additions & 0 deletions test/small_eq_sum_real.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
@testset "Real coefficients" begin
@testset "Basic all true" begin
m = Model(with_optimizer(CS.Optimizer))
@variable(m, x[1:4], Bin)
weights = [1.7, 0.7, 0.3, 1.3]
@variable(m, 0 <= max_val <= 10, Int)
@constraint(m, sum(weights.*x) == max_val)
@objective(m, Max, max_val)
optimize!(m)
@test JuMP.termination_status(m) == MOI.OPTIMAL
@test JuMP.objective_value(m) == 4
@test JuMP.value.(x) == [1,1,1,1]
end

@testset "Some true some false" begin
# disallow that x1 and x2 are both allowed
m = Model(with_optimizer(CS.Optimizer))
@variable(m, x[1:4], Bin)
@variable(m, z, Bin)
# x[1]+x[2] <= 1
@constraint(m, x[1]+x[2]+z == 1)

weights = [1.7, 0.7, 0.3, 1.3]
@variable(m, 0 <= max_val <= 10, Int)
@constraint(m, sum(weights.*x) == max_val)
@objective(m, Max, max_val)
optimize!(m)
@test JuMP.termination_status(m) == MOI.OPTIMAL
@test JuMP.objective_value(m) == 3
@test JuMP.value.(x) == [1,0,0,1]
end

@testset "Negative coefficients" begin
# must use negative coefficient for optimum
m = Model(with_optimizer(CS.Optimizer))
@variable(m, x[1:4], Bin)
@variable(m, z, Bin)
# x[1]+x[2] <= 1
@constraint(m, x[1]+x[2]+z == 1)

weights = [1.7, 0.7, -0.3, 1.6]
@variable(m, 0 <= max_val <= 10, Int)
@constraint(m, sum(weights.*x) == max_val)
@objective(m, Max, max_val)
optimize!(m)
@test JuMP.termination_status(m) == MOI.OPTIMAL
@test JuMP.objective_value(m) == 3
@test JuMP.value.(x) == [1,0,1,1]
end

@testset "Minimization negative coefficients" begin
# must use negative coefficient for optimum
m = Model(with_optimizer(CS.Optimizer))
@variable(m, x[1:4], Bin)
@variable(m, z, Bin)
# x[1]+x[2] <= 1
@constraint(m, x[1]+x[2]+z == 1)

weights = [0.3, 0.7, -0.3, 1.6]
@variable(m, 1 <= max_val <= 10, Int)
@constraint(m, sum(weights.*x) == max_val)
@objective(m, Min, max_val)
optimize!(m)
@test JuMP.termination_status(m) == MOI.OPTIMAL
@test JuMP.objective_value(m) == 2
@test JuMP.value.(x) == [0,1,1,1]
end

@testset "Getting safe upper/lower bounds" begin
# must use negative coefficient for optimum
m = Model(with_optimizer(CS.Optimizer))
@variable(m, x[1:4], Bin)
@variable(m, z, Bin)

weights = [0.1, 0.2, 0.4, -1.3]
@variable(m, 0 <= max_val <= 10, Int)
@constraint(m, sum(weights.*x) == 0.3*max_val)
@objective(m, Max, max_val)
optimize!(m)
@test JuMP.termination_status(m) == MOI.OPTIMAL
@test JuMP.objective_value(m) == 2
@test JuMP.value.(x) == [0,1,1,0]
end
end
2 changes: 1 addition & 1 deletion test/small_special_tests.jl
Original file line number Diff line number Diff line change
@@ -83,7 +83,7 @@ end
CS.add_constraint!(com, z-2 == x)
CS.add_constraint!(com, 2x+x == z+3y-6)

options = CS.get_default_options()
options = CS.SolverOptions()
status = CS.solve!(com, options)
@test status == :Solved
@test CS.isfixed(x) && CS.value(x) == 1
4 changes: 2 additions & 2 deletions test/sudoku_tests.jl
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ end
com_grid = create_sudoku_grid!(com, grid)
add_sudoku_constr!(com, com_grid)

@test CS.solve!(com, CS.get_default_options()) == :Solved
@test CS.solve!(com, CS.SolverOptions()) == :Solved
@test fulfills_sudoku_constr(com_grid)
end

@@ -166,7 +166,7 @@ end

add_sudoku_constr!(com, com_grid)

@test CS.solve!(com, CS.get_default_options()) == :Solved
@test CS.solve!(com, CS.SolverOptions()) == :Solved
@test fulfills_sudoku_constr(com_grid)
end