Skip to content

Commit 01afcae

Browse files
author
shiv-666
committed
Create linear_constraint_tests.jl
Simple unit tests for functionality, BMR constraint correctness, CHR constraint correctness. Robustness tests for attempts to use package without enough constraints, using CHR without defined variable bounds, using BMR without defined variable bounds or M value, empty constraints on one side of disjunction.
1 parent 6dfbcb4 commit 01afcae

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

test/linear_constraint_tests.jl

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using Test
2+
using JuMP
3+
using DisjunctiveProgramming
4+
5+
@testset "linear constraints" begin
6+
7+
function minimal()
8+
m = Model()
9+
@variable(m, -1<=x<=10)
10+
11+
@constraint(m, con1, x<=3)
12+
@constraint(m, con2, 0<=x)
13+
@constraint(m, con3, x<=9)
14+
@constraint(m, con4, 5<=x)
15+
16+
@disjunction(m,(con1,con2),con3,con4,reformulation=:CHR,name=:y)
17+
18+
@test true
19+
end
20+
21+
function simple_example(reform)
22+
m = Model()
23+
@variable(m, -10<=x<=10)
24+
@constraint(m, con1, x<=-1)
25+
@constraint(m, con2, 1<=x)
26+
27+
if (reform == :BMR)
28+
@disjunction(m, con1, con2, reformulation=:BMR, name=:y)
29+
elseif (reform == :CHR)
30+
@disjunction(m, con1, con2, reformulation=:CHR, name=:y)
31+
end
32+
return m
33+
end
34+
35+
function robustness()
36+
unreg = m -> unregister(m, :original_model_variables)
37+
38+
function fresh_model()
39+
m = Model()
40+
@variable(m, -10<=x<=10)
41+
@constraint(m, con1, x<=-1)
42+
@constraint(m, con2, 1<=x)
43+
return m, con1, con2
44+
end
45+
46+
# not enough constraints
47+
m = fresh_model()[1]
48+
# @test_throws DomainError @disjunction(m, (con1, con2), reformulation=:BMR, name=:y)
49+
unreg(m)
50+
# @test_throws DomainError @disjunction(m, con1, reformulation=:BMR, name=:y)
51+
52+
# Big-M reformulation without variable bounds defined
53+
# should only work if user specifies M value
54+
m = Model()
55+
@variable(m, x)
56+
@constraint(m, con1, x<=-1)
57+
@constraint(m, con2, 1<=x)
58+
@test @disjunction(m, con1, con2, reformulation=:BMR, name=:y, M=11) == nothing
59+
unreg(m)
60+
@test_throws ErrorException @disjunction(m, con1, con2, reformulation=:BMR, name=:y)
61+
62+
# CHR reformulation without variable bounds defined should fail
63+
m = Model()
64+
@variable(m, x)
65+
@constraint(m, con1, x<=-1)
66+
@constraint(m, con2, 1<=x)
67+
@test_throws AssertionError @disjunction(m, con1, con2, reformulation=:CHR, name=:y)
68+
69+
# empty constraints on one side of disjunction
70+
m, con1, con2 = fresh_model()
71+
@test @disjunction(m, con1, nothing, reformulation=:BMR, name=:y) == nothing
72+
m, con1, con2 = fresh_model()
73+
@test @disjunction(m, nothing, con2, reformulation=:CHR, name=:z) == nothing
74+
m, con1, con2 = fresh_model()
75+
@test @disjunction(m, (con1, con2), nothing, reformulation=:BMR, name=:y) == nothing
76+
77+
end
78+
79+
function model_BMR_valid(m)
80+
# Expecting to see following constraints:
81+
# x <= -1 + 11 * (1 - y1)
82+
# -x <= -1 + 11 * (1 - y2)
83+
# y1 + y2 = 1
84+
# -10<=x<=10
85+
cons = (L -> all_constraints(m, L...)).(list_of_constraint_types(m))
86+
@test sum(length.(cons)) == 5 + 2 # add 2 for y1/y2 binary statements
87+
88+
con_strings = replace.(constraints_string(REPLMode, m), "[" => "",
89+
"]" => "", "+ y" => "+y", " y" => "* y",
90+
"con1 :" => "", "con2 :" => "")
91+
cons_actual = Meta.parse.(con_strings[1:5])
92+
cons_expected = [:(y1 + y2 == 1), :(x + 11 * y1 <= 10),
93+
:(-x + 11 * y2 <= 10), :(x >= -10), :(x <= 10)]
94+
matches = sum(map(i->isequal(i[1], i[2]),
95+
Base.product(cons_expected, cons_actual)))
96+
@test matches == length(cons_actual)
97+
end
98+
99+
function model_CHR_valid(m)
100+
# Expecting to see following constraints:
101+
# x_y1 <= -1 * y1
102+
# -x_y2 <= -1 * y2
103+
# y1 + y2 = 1
104+
# x = x_y1 + x_y2
105+
# -10 * y1 <= x_y1 <= 10 * y1
106+
# -10 * y2 <= x_y2 <= 10 * y2
107+
# -10 <= x <= 10
108+
# -10 <= x_y1 <= 10
109+
# -10 <= x_y2 <= 10
110+
cons = (L -> all_constraints(m, L...)).(list_of_constraint_types(m))
111+
@test sum(length.(cons)) == 14 + 2 # add 2 for y1/y2 binary statements
112+
113+
con_strings = replace.(constraints_string(REPLMode, m), "[" => "",
114+
"]" => "", "+ y" => "+y", " y" => "* y",
115+
"con1 :" => "", "con2 :" => "")
116+
println.(con_strings)
117+
cons_expected = [:(y1 + y2 == 1), :(x - x_1 - x_2 == 0), :(x >= -10),
118+
:(x_1 + y1 <= 0), :(-x_2 + y2 <= 0), :(x <= 10),
119+
:(x_1 >= -10), :(x_1 <= 10), :(x_2 >= -10), :(x_2 <= 10),
120+
:(-10 * y1 - x_1 <= 0), :(-10 * y1 + x_1 <= 0),
121+
:(-10 * y2 - x_2 <= 0), :(-10 * y2 + x_2 <= 0)]
122+
cons_actual = Meta.parse.(con_strings[1:14])
123+
matches = sum(map(i->isequal(i[1], i[2]),
124+
Base.product(cons_expected, cons_actual)))
125+
@test matches == length(cons_actual)
126+
end
127+
128+
robustness()
129+
130+
m1 = simple_example(:BMR)
131+
model_BMR_valid(m1)
132+
133+
m2 = simple_example(:CHR)
134+
model_CHR_valid(m2)
135+
136+
end

0 commit comments

Comments
 (0)