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

+7
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

+1-1
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

+12-1
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

+20
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

+1-1
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

+1
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

+2
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

+11-1
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

+15
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

+108
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

src/constraints.jl

+38-7
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function update_init_constraints!(com::CS.CoM; constraints = com.constraints)
7575
end
7676

7777
"""
78-
call_finished_pruning!(com)
78+
call_finished_pruning!(com)
7979
8080
Call `finished_pruning_constraint!` for every constraint which implements that function as saved in `constraint.impl.finished_pruning`
8181
"""
@@ -86,7 +86,7 @@ function call_finished_pruning!(com)
8686
end
8787

8888
"""
89-
call_restore_pruning!(com, prune_steps)
89+
call_restore_pruning!(com, prune_steps)
9090
9191
Call `call_restore_pruning!` for every constraint which implements that function as saved in `constraint.impl.restore_pruning`
9292
"""
@@ -203,6 +203,37 @@ end
203203
"""
204204
reverse_pruning_constraint!(::CS.CoM, ::Constraint, fct, set, backtrack_id)
205205
206+
Call [`_reverse_pruning_constraint!`](@ref) if the constraint is activated
207+
"""
208+
function reverse_pruning_constraint!(
209+
com::CoM,
210+
constraint::Constraint,
211+
fct,
212+
set,
213+
backtrack_id,
214+
)
215+
constraint.is_deactivated || _reverse_pruning_constraint!(com, constraint, fct, set, backtrack_id)
216+
end
217+
218+
"""
219+
single_reverse_pruning_constraint!(::CS.CoM, ::Constraint, fct, set, variable, backtrack_id)
220+
221+
Call [`_single_reverse_pruning_constraint!`](@ref) if the constraint is activated
222+
"""
223+
function single_reverse_pruning_constraint!(
224+
com::CoM,
225+
constraint::Constraint,
226+
fct,
227+
set,
228+
variable,
229+
backtrack_id,
230+
)
231+
constraint.is_deactivated || _single_reverse_pruning_constraint!(com, constraint, fct, set, variable, backtrack_id)
232+
end
233+
234+
"""
235+
_reverse_pruning_constraint!(::CS.CoM, ::Constraint, fct, set, backtrack_id)
236+
206237
Fallback for `reverse_pruning_constraint!`.
207238
This function will get called when a specific pruning step with id `backtrack_id` needs to get reversed.
208239
Should only be implemented when the data structure of the constraint needs to be updated.
@@ -211,7 +242,7 @@ See also [`single_reverse_pruning_constraint!`](@ref) to change the data structu
211242
212243
Return `nothing`
213244
"""
214-
function reverse_pruning_constraint!(
245+
function _reverse_pruning_constraint!(
215246
com::CoM,
216247
constraint::Constraint,
217248
fct,
@@ -222,9 +253,9 @@ function reverse_pruning_constraint!(
222253
end
223254

224255
"""
225-
single_reverse_pruning_constraint!(::CS.CoM, ::Constraint, fct, set, variable, backtrack_id)
256+
_single_reverse_pruning_constraint!(::CS.CoM, ::Constraint, fct, set, variable, backtrack_id)
226257
227-
Fallback for `single_reverse_pruning_constraint!`.
258+
Fallback for `_single_reverse_pruning_constraint!`.
228259
This function will get called when a specific pruning step with id `backtrack_id` needs to get reversed.
229260
In contrast to [`reverse_pruning_constraint!`](@ref) however this function will be called for each variable individually and is called
230261
before [`reverse_pruning_constraint!`](@ref).
@@ -233,13 +264,13 @@ All variables are updated automatically anyway.
233264
234265
Return `nothing`
235266
"""
236-
function single_reverse_pruning_constraint!(
267+
function _single_reverse_pruning_constraint!(
237268
::CoM,
238269
::Constraint,
239270
fct,
240271
set,
241272
variable,
242-
backtrack_idx,
273+
backtrack_id,
243274
)
244275
nothing
245276
end

src/constraints/activator_constraints.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ function update_best_bound_constraint!(
7979
end
8080
end
8181

82-
function single_reverse_pruning_constraint!(
82+
function _single_reverse_pruning_constraint!(
8383
com::CoM,
8484
constraint::ActivatorConstraint,
8585
fct::Union{MOI.VectorOfVariables,VAF{T}},
@@ -103,7 +103,7 @@ function single_reverse_pruning_constraint!(
103103
end
104104
end
105105

106-
function reverse_pruning_constraint!(
106+
function _reverse_pruning_constraint!(
107107
com::CoM,
108108
constraint::ActivatorConstraint,
109109
fct::Union{MOI.VectorOfVariables,VAF{T}},

0 commit comments

Comments
 (0)