Skip to content

Commit c10c37f

Browse files
authored
Feature element 1D const (#213)
* first very basic implementation of element1Dconst * nice syntax for element constraint + sorting test case * test case and first part of move_element_constraint * activate element constraint and minor fixes of activation * have _ functions for single_reverse and reverse to check is deactivated * v0.8.2
1 parent e16a90e commit c10c37f

23 files changed

+796
-24
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
# ConstrainSolver.jl - Changelog
2+
3+
## v0.8.2 (6th of February 2022)
4+
- Support for element constraints with a constant array like `T[y] == z`
5+
- **This is experimental** Quite some stuff that might go wrong when combining it inside an indicator, reified or boolean constraint.
6+
- If you find any problems please open an issue. I release it to get more feedback get this more tested. I plan on refactoring this quite a bit later.
27

38
## v0.8.1 (5th of February 2022)
49
- bugfix when using `CS.Integers` together with an alldifferent constraint. [PR #283](https://github.com/Wikunia/ConstraintSolver.jl/pull/283)
10+
511
## v0.8.0 (8th of January 2022)
612
- Using [TableLogger.jl](https://github.com/Wikunia/TableLogger.jl)
713
- Only support Julia v1.6 and above
14+
815
## v0.7.1 (1st of November 2021)
916
- Using priority queue also for `BFS` problems [PR #274](https://github.com/Wikunia/ConstraintSolver.jl/pull/274)
1017

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ConstraintSolver"
22
uuid = "e0e52ebd-5523-408d-9ca3-7641f1cd1405"
33
authors = ["Ole Kröger <[email protected]>"]
4-
version = "0.8.1"
4+
version = "0.8.2"
55

66
[deps]
77
ConstraintProgrammingExtensions = "b65d079e-ed98-51d9-b0db-edee61a5c5f8"

benchmark/benchmarks.jl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using BenchmarkTools
22
using ConstraintSolver, JuMP, MathOptInterface
33
using GLPK, JSON, Cbc
4+
using Random
45

56
const CS = ConstraintSolver
67
const MOI = MathOptInterface
@@ -150,4 +151,14 @@ SUITE["steiner"] = BenchmarkGroup(["reified", "and"])
150151
steiner(3)
151152
SUITE["steiner"]["steiner_7"] =
152153
@benchmarkable steiner(7) seconds = 5
153-
154+
155+
156+
include(joinpath(dir, "benchmark/sort/benchmark.jl"))
157+
158+
SUITE["sorting"] = BenchmarkGroup(["element1Dconst", "alldifferent", "equal", "less_than"])
159+
# compiling run
160+
sort_element_constr(10)
161+
SUITE["sorting"]["50_elements"] =
162+
@benchmarkable sort_element_constr(50) seconds = 5
163+
SUITE["sorting"]["100_elements"] =
164+
@benchmarkable sort_element_constr(100) seconds = 10

benchmark/sort/benchmark.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
function sort_element_constr(n)
2+
m = Model(optimizer_with_attributes(CS.Optimizer,
3+
"logging" => [],
4+
"seed" => 4,
5+
"traverse_strategy" => :BFS,
6+
))
7+
seed = 1337
8+
Random.seed!(seed)
9+
c = rand(1:1000, n)
10+
@variable(m, 1 <= idx[1:length(c)] <= length(c), Int)
11+
@variable(m, minimum(c) <= val[1:length(c)] <= maximum(c), Int)
12+
for i in 1:length(c)-1
13+
@constraint(m, val[i] <= val[i+1])
14+
end
15+
for i in 1:length(c)
16+
@constraint(m, c[idx[i]] == val[i])
17+
end
18+
@constraint(m, idx in CS.AllDifferent())
19+
optimize!(m)
20+
end

docs/src/supported.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ The following list shows constraints that are implemented and those which are pl
104104
- one can write something like `a || !b`
105105
- **Attention:** Does not check whether `a` and `b` are actually binary variables
106106
- Element constraints
107-
- [ ] 1D array with constant values
107+
- [X] 1D array with constant values
108108
- i.e `T = [12,87,42,1337]` `T[y] == z` with `y` and `z` being variables [#213](https://github.com/Wikunia/ConstraintSolver.jl/pull/213)
109109
- [ ] 2D array with constant values
110110
- where T is an array

src/ConstraintSolver.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ include("constraints/activator_constraints.jl")
8484
include("constraints/indicator.jl")
8585
include("constraints/reified.jl")
8686
include("constraints/geqset.jl")
87+
include("constraints/element1Dconst.jl")
8788

8889
include("pruning.jl")
8990
include("simplify.jl")

src/MOI_wrapper/MOI_wrapper.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ function MOI.optimize!(model::Optimizer)
166166
# check if every variable has bounds and is an Integer
167167
check_var_bounds(model)
168168

169+
move_element_constraint(model)
170+
169171
set_pvals!(model)
170172
set_var_in_all_different!(model)
171173

src/MOI_wrapper/constraints.jl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ sense_to_set(::Function, ::Val{:!=}) = CPE.DifferentFrom(0.0)
55
sense_to_set(::Function, ::Val{:<}) = CPE.Strictly(MOI.LessThan(0.0))
66
sense_to_set(::Function, ::Val{:>}) = CPE.Strictly(MOI.GreaterThan(0.0))
77

8+
include("element.jl")
89
include("indicator.jl")
910
include("reified.jl")
1011
include("bool.jl")
@@ -34,6 +35,7 @@ MOI.supports_constraint(
3435
::Type{MOI.VectorOfVariables},
3536
::Type{CPE.AllEqual},
3637
) = true
38+
3739
MOI.supports_constraint(
3840
::Optimizer,
3941
::Type{MOI.VectorOfVariables},
@@ -46,6 +48,12 @@ MOI.supports_constraint(
4648
::Type{CPE.AllDifferent},
4749
) where {T <: Real} = true
4850

51+
MOI.supports_constraint(
52+
::Optimizer,
53+
::Type{MOI.VectorOfVariables},
54+
::Type{Element1DConstInner},
55+
) = true
56+
4957
MOI.supports_constraint(
5058
::Optimizer,
5159
::Type{MOI.VectorOfVariables},
@@ -299,6 +307,8 @@ function MOI.add_constraint(
299307
com.info.n_constraint_types.table += 1
300308
elseif set isa CPE.AllEqual
301309
com.info.n_constraint_types.equality += 1
310+
elseif set isa Element1DConstInner
311+
com.info.n_constraint_types.element += 1
302312
end
303313

304314
return MOI.ConstraintIndex{MOI.VectorOfVariables,typeof(set)}(length(com.constraints))
@@ -578,7 +588,7 @@ function MOI.add_constraint(
578588
set::IS,
579589
) where {T,A,F,S<:MOI.AbstractVectorSet,IS<:CS.IndicatorSet{A,F,S}}
580590
com = model.inner
581-
com.info.n_constraint_types.reified += 1
591+
com.info.n_constraint_types.indicator += 1
582592

583593
internals = create_internals(com, func, set)
584594

src/MOI_wrapper/element.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""
2+
Support for nice element 1D const constraint
3+
"""
4+
function Base.getindex(v::AbstractVector{<:Integer}, i::VariableRef)
5+
m = JuMP.owner_model(i)
6+
# check if the AbstractVector has standard indexing
7+
if !checkbounds(Bool, v, 1:length(v))
8+
throw(ArgumentError("Currently the specified vector needs to be using standard indexing 1:... so OffsetArrays are not possible."))
9+
end
10+
v = collect(v)
11+
min_val, max_val = extrema(v)
12+
x = @variable(m, integer=true, lower_bound = min_val, upper_bound = max_val)
13+
@constraint(m, [x, i] in CS.Element1DConst(v))
14+
return x
15+
end

src/MOI_wrapper/util.jl

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,114 @@ function get_inner_constraint(com, vars::MOI.VectorOfVariables, set::Union{Reifi
9696
return init_constraint_struct(com, set.set, inner_internals)
9797
end
9898

99+
"""
100+
get_bool_constraint_fct_set(T, constraint::BoolConstraint, lhs_fct, lhs_set, rhs_fct, rhs_set)
101+
102+
Create the fct and set of a [`BoolConstraint`](@ref) which combines the left hand side with the right hand side.
103+
"""
104+
function get_bool_constraint_fct_set(T, constraint::BoolConstraint, lhs_fct, lhs_set, rhs_fct, rhs_set)
105+
BS = typeof_without_params(constraint.set)
106+
fct = MOIU.operate(vcat, T, lhs_fct, rhs_fct)
107+
set = BS{typeof(lhs_fct), typeof(rhs_fct)}(lhs_set, rhs_set)
108+
return fct, set
109+
end
110+
111+
function find_element_var_and_combine(T, constraint::BoolConstraint, element_constraint, element_var)
112+
lhs = constraint.lhs
113+
rhs = constraint.rhs
114+
if element_var in lhs.indices
115+
if lhs isa BoolConstraint
116+
fct, set = find_element_var_and_combine(T, lhs, element_constraint, element_var)
117+
else
118+
fct = MOIU.operate(vcat, T, lhs.fct, element_constraint.fct)
119+
set = XNorSet{typeof(lhs.fct), typeof(element_constraint.fct)}(lhs.set, element_constraint.set)
120+
end
121+
fct, set = get_bool_constraint_fct_set(T, constraint, fct, set, rhs.fct, rhs.set)
122+
else
123+
if rhs isa BoolConstraint
124+
fct, set = find_element_var_and_combine(T, rhs, element_constraint, element_var)
125+
else
126+
fct = MOIU.operate(vcat, T, element_constraint.fct, rhs.fct)
127+
set = XNorSet{typeof(element_constraint.fct), typeof(rhs.fct)}(element_constraint.set, rhs.set)
128+
end
129+
fct, set = get_bool_constraint_fct_set(T, constraint, lhs.fct, lhs.set, fct, set)
130+
end
131+
return fct, set
132+
end
133+
134+
function create_new_activator_constraint(model, activation_constraint::ActivatorConstraint, fct, set)
135+
com = model.inner
136+
T = parametric_type(com)
137+
ACS = typeof_without_params(activation_constraint.set)
138+
A = get_activation_condition(activation_constraint.set)
139+
if ACS == MOI.IndicatorSet
140+
ACS = IndicatorSet
141+
end
142+
f = MOIU.eachscalar(activation_constraint.fct)
143+
144+
activator_fct = f[1]
145+
fct = MOIU.operate(vcat, T, activator_fct, fct)
146+
MOI.add_constraint(model, fct, ACS{A,typeof(fct)}(set))
147+
end
148+
149+
"""
150+
move_element_constraint(model)
151+
152+
If there are element constraints which are only used inside of an indicator or reified constraint
153+
=> combine them with xnor and deactivate the previously added element constraint
154+
this is to avoid filtering based on this element constraint when the inner constraint isn't active
155+
For more see: https://github.com/Wikunia/ConstraintSolver.jl/pull/213#issuecomment-860954185
156+
"""
157+
function move_element_constraint(model)
158+
com = model.inner
159+
T = parametric_type(com)
160+
constraints = com.constraints
161+
subscriptions = com.subscription
162+
for element_cons in constraints
163+
element_cons isa Element1DConstConstraint || continue
164+
element_var = element_cons.indices[1]
165+
166+
# check if the element var only appears in indicator or reified constraints
167+
only_inside = true
168+
for constraint in constraints[subscriptions[element_var]]
169+
# if not inside indicator, reified or OrConstraint and not the current constraint that we check
170+
if !(constraint isa ActivatorConstraint) && !(constraint isa OrConstraint) && constraint.idx != element_cons.idx
171+
only_inside = false
172+
end
173+
end
174+
!only_inside && continue
175+
# check if at least once inside
176+
only_inside = false
177+
for constraint in constraints[subscriptions[element_var]]
178+
if constraint isa ActivatorConstraint || constraint isa OrConstraint
179+
only_inside = true
180+
break
181+
end
182+
end
183+
!only_inside && continue
184+
185+
element_cons.is_deactivated = true
186+
for constraint in constraints[subscriptions[element_var]]
187+
constraint isa Element1DConstConstraint && continue
188+
constraint.is_deactivated && continue
189+
fct, set = nothing, nothing
190+
if constraint isa ActivatorConstraint
191+
if constraint.inner_constraint isa OrConstraint
192+
inner_constraint = constraint.inner_constraint
193+
fct, set = find_element_var_and_combine(T, inner_constraint, element_cons, element_var)
194+
else
195+
fct = MOIU.operate(vcat, T, constraint.inner_constraint.fct, element_cons.fct)
196+
set = XNorSet{typeof(constraint.inner_constraint.fct), typeof(element_cons.fct)}(constraint.inner_constraint.set, element_cons.set)
197+
end
198+
create_new_activator_constraint(model, constraint, fct, set)
199+
else # OrConstraint
200+
fct, set = find_element_var_and_combine(T, constraint, element_cons, element_var)
201+
MOI.add_constraint(model, fct, set)
202+
end
203+
end
204+
end
205+
end
206+
99207
function get_activator_internals(A, indices)
100208
ActivatorConstraintInternals(A, indices[1] in indices[2:end], false, 0)
101209
end

0 commit comments

Comments
 (0)