Skip to content

Commit 633ebbb

Browse files
authored
bin variable symbol as constraint (#267)
* bin variable symbol as constraint
1 parent 29ea78a commit 633ebbb

File tree

6 files changed

+61
-8
lines changed

6 files changed

+61
-8
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# ConstrainSolver.jl - Changelog
2-
2+
33
## Unreleased
4+
- Allow variables as constraint like `a || !b` instead of `a == 1 || b == 0`. [PR #267](https://github.com/Wikunia/ConstraintSolver.jl/pull/267)
5+
- **Attention** Does not check if variable is a binary variable
46
- Support for indicator/reified in indicator/reified (without bridges) [PR #251](https://github.com/Wikunia/ConstraintSolver.jl/pull/251)
57

68
## v0.6.9 (17th of July 2021)

benchmark/steiner/benchmark.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function steiner(n)
4343
for j in i + 1:nb
4444
b = @variable(model, [1:n], Bin)
4545
for k in 1:n
46-
@constraint(model, b[k] := { x[i,k] == 1 && x[j,k] == 1 })
46+
@constraint(model, b[k] := { x[i,k] && x[j,k] })
4747
end
4848
@constraint(model, sum(b) <= 1)
4949
end

src/MOI_wrapper/bool.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,43 @@ function _build_bool_constraint(
2525
)
2626
end
2727

28+
"""
29+
transform_binary_expr(sym::Symbol)
30+
31+
Transform a symbol to a constraint of the form Symbol == 1
32+
"""
33+
function transform_binary_expr(sym::Symbol)
34+
return :($sym == 1)
35+
end
36+
37+
"""
38+
transform_binary_expr(expr::Expr)
39+
40+
Transform a ! (symbol) to a constraint of the form Symbol == 0
41+
or x[...] to x[...] == 1
42+
"""
43+
function transform_binary_expr(expr::Expr)
44+
if expr.head == :ref
45+
expr = :($expr == 1)
46+
elseif expr.head == :call && expr.args[1] == :! && (expr.args[2] isa Symbol || expr.args[2].head == :ref)
47+
expr = :($(expr.args[2]) == 0)
48+
end
49+
return expr
50+
end
51+
2852
function parse_bool_constraint(_error, bool_val::BOOL_VALS, lhs, rhs)
2953
_error1 = deepcopy(_error)
54+
# allow a || b instead of a == 1 || b == 1
55+
lhs = transform_binary_expr(lhs)
56+
3057
lhs_vectorized, lhs_parsecode, lhs_buildcall =
3158
JuMP.parse_constraint_expr(_error, lhs)
3259

3360
if lhs_vectorized
3461
_error("`$(lhs)` should be non vectorized. There is currently no vectorized support for `and` constraints. Please open an issue at ConstraintSolver.jl")
3562
end
3663

64+
rhs = transform_binary_expr(rhs)
3765
rhs_vectorized, rhs_parsecode, rhs_buildcall =
3866
JuMP.parse_constraint_expr(_error1, rhs)
3967

test/small_special.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -876,22 +876,22 @@
876876
# M3 #= 1 #<=> (M1 #/\ M2)
877877
# @constraint(model, m3 := { m1 + m2 == 2})
878878
# @constraint(model, m3 := { m1 == 1 && m2 == 1})
879-
@constraint(model, m3 := { m1 == 1 && m2 == 1})
879+
@constraint(model, m3 := { m1 && m2})
880880

881881
# Monk 4: Doors A and B are both exits.
882882
# M4 #= 1 #<=> (A #/\ B)
883883
# @constraint(model, m4 := { da + db == 2})
884-
@constraint(model, m4 := { da == 1 && db == 1})
884+
@constraint(model, m4 := { da && db })
885885

886886
# Monk 5: Doors A and C are both exits.
887887
# M5 #= 1 #<=> (A #/\ C)
888888
# @constraint(model, m5 := { da + dc == 2})
889-
@constraint(model, m5 := { da == 1 && dc == 1})
889+
@constraint(model, m5 := { da && dc })
890890

891891
# Monk 6: Either Monk 4 or Monk 5 is telling the truth.
892892
# M6 #= 1 #<=> (M4 #\/ M5)
893893
# @constraint(model, m6 := { m4 + m5 == 1})
894-
@constraint(model, m6 := { m4 == 1 || m5 == 1})
894+
@constraint(model, m6 := { m4 || m5 })
895895

896896
# Monk 7: If Monk 3 is telling the truth, so is Monk 6.
897897
# M7 #= 1 #<=> (M3 #=> M6)
@@ -903,7 +903,7 @@
903903
# M8 #= 1 #<=> ((M7 #/\ M8) #=> (M1))
904904
b1 = @variable(model, binary=true)
905905
# @constraint(model, b1 := {m7 + m8 == 2})
906-
@constraint(model, b1 := {m7 == 1 && [m7,m8] in CS.TableSet([1 1; 2 2; 3 3;])})
906+
@constraint(model, b1 := {m7 && [m7,m8] in CS.TableSet([1 1; 2 2; 3 3;])})
907907
@constraint(model, m8 := {b1 <= m1})
908908

909909
# Exactly one door is an exit.

test/steiner.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ end
6565
for j in i+1:nb
6666
b = @variable(model, [1:n], Bin)
6767
for k in 1:n
68-
@constraint(model, b[k] := { x[i,k] == 1 && x[j,k] == 1 })
68+
@constraint(model, b[k] := { x[i,k] && x[j,k] })
6969
end
7070
@constraint(model, sum(b) <= 1)
7171
end

test/unit/constraints/bool.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,29 @@ end
133133
end
134134
end
135135

136+
@testset "all solutions variable as constraint" begin
137+
m = Model(optimizer_with_attributes(CS.Optimizer, "logging" => [], "all_optimal_solutions"=>true))
138+
@variable(m, a, Bin)
139+
@variable(m, b, Bin)
140+
@variable(m, c, Bin)
141+
@constraint(m, a || b || !c)
142+
optimize!(m)
143+
144+
com = CS.get_inner_model(m)
145+
nresults = JuMP.result_count(m)
146+
results = Set{Tuple{Bool,Bool,Bool}}()
147+
for i in 1:nresults
148+
a_val, b_val, c_val = convert.(Bool, round.(JuMP.value.([a,b,c]; result=i)))
149+
@test a_val || b_val || !c_val
150+
push!(results, (a_val, b_val, c_val))
151+
end
152+
for i in [true, false], j in [true, false], k in [true, false]
153+
if i || j || !k
154+
@test (i,j, k) in results
155+
end
156+
end
157+
end
158+
136159
#=
137160
@testset "all solutions combined" begin
138161
m = Model(optimizer_with_attributes(CS.Optimizer, "logging" => [], "all_optimal_solutions"=>true))

0 commit comments

Comments
 (0)