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