From 62393b22df2559b463e79158bd9bc41fedabc304 Mon Sep 17 00:00:00 2001 From: Hector Perez Date: Sat, 14 Oct 2023 18:32:38 -0400 Subject: [PATCH 1/2] bug fix --- src/variables.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/variables.jl b/src/variables.jl index 922e2cb..1a28da5 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -23,14 +23,14 @@ function JuMP.build_variable( _error("Logical variables cannot have bounds.") elseif info.integer _error("Logical variables cannot be integer valued.") - elseif info.has_fix && !isone(info.fix) && !iszero(info.fix) - _error("Invalid fix value, must be 0 or 1.") + elseif info.has_fix && !isone(info.fixed_value) && !iszero(info.fixed_value) + _error("Invalid fix value, must be false or true.") elseif info.has_start && !isone(info.start) && !iszero(info.start) - _error("Invalid start value, must be 0 or 1.") + _error("Invalid start value, must be false or true.") end # create the variable - fix = info.has_fix ? Bool(info.fix) : nothing + fix = info.has_fix ? Bool(info.fixed_value) : nothing start = info.has_start ? Bool(info.start) : nothing return LogicalVariable(fix, start) end From 9a1fcf3ec12eee2c7b472a8673dd825bc5aea53a Mon Sep 17 00:00:00 2001 From: Hector Perez Date: Sat, 14 Oct 2023 18:32:41 -0400 Subject: [PATCH 2/2] add tests --- src/constraints.jl | 2 +- src/logic.jl | 4 ++-- test/constraints/bigm.jl | 12 +++++----- test/constraints/disjunct.jl | 1 + test/constraints/fallback.jl | 10 ++++++++ test/constraints/hull.jl | 39 ++++++++++++++++++++++++++++++ test/constraints/indicator.jl | 7 +++--- test/constraints/proposition.jl | 42 ++++++++++++++++++++++++++++++++- test/disjunction.jl | 18 ++++++++++++-- test/jump.jl | 13 ++++++++++ test/model.jl | 17 +++++++++++++ test/runtests.jl | 2 ++ test/variables/logical.jl | 24 +++++++++++++++++++ 13 files changed, 176 insertions(+), 15 deletions(-) create mode 100644 test/constraints/fallback.jl create mode 100644 test/jump.jl diff --git a/src/constraints.jl b/src/constraints.jl index 4d19a5e..2a59421 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -379,7 +379,7 @@ function disjunction( model::JuMP.Model, disjunct_indicators, nested_tag::DisjunctConstraint, - name::String = "", + name::String = "" ) # TODO add kw argument to build exactly 1 constraint return _disjunction(error, model, disjunct_indicators, name, nested_tag) end diff --git a/src/logic.jl b/src/logic.jl index 70ed443..60db64f 100644 --- a/src/logic.jl +++ b/src/logic.jl @@ -57,7 +57,7 @@ function _eliminate_equivalence(lexpr::_LogicalExpr) elseif length(lexpr.args) == 2 B = _eliminate_equivalence(lexpr.args[2]) else - error("The equivalence logic operator must have at least two arguments.") + error("The equivalence logic operator must have at least two clauses.") end new_lexpr = _LogicalExpr(:&&, Any[ _LogicalExpr(:(=>), Any[A, B]), @@ -101,7 +101,7 @@ end function _move_negations_inward(lexpr::_LogicalExpr) if lexpr.head == :! if length(lexpr.args) != 1 - error("The negation operator can only have 1 clause.") + error("The negation operator can only have one clause.") end new_lexpr = _negate(lexpr.args[1]) else diff --git a/test/constraints/bigm.jl b/test/constraints/bigm.jl index d31d9e3..eef1872 100644 --- a/test/constraints/bigm.jl +++ b/test/constraints/bigm.jl @@ -202,7 +202,7 @@ function test_nonpositives_bigm() @test ref[1].set == MOI.Nonpositives(2) end -function test_greaterhan_bigm() +function test_greaterthan_bigm() model = GDPModel() @variable(model, x) @variable(model, y, Logical) @@ -231,7 +231,7 @@ function test_nonnegatives_bigm() @test ref[1].set == MOI.Nonnegatives(2) end -function test_greaterhan_bigm() +function test_equalto_bigm() model = GDPModel() @variable(model, x) @variable(model, y, Logical) @@ -247,7 +247,7 @@ function test_greaterhan_bigm() @test ref[2].set == MOI.LessThan(5.0 + 100) end -function test_greaterhan_bigm() +function test_interval_bigm() model = GDPModel() @variable(model, x) @variable(model, y, Logical) @@ -320,10 +320,10 @@ end test_calculate_tight_M() test_lessthan_bigm() test_nonpositives_bigm() - test_greaterhan_bigm() + test_greaterthan_bigm() test_nonnegatives_bigm() - test_greaterhan_bigm() - test_greaterhan_bigm() + test_equalto_bigm() + test_interval_bigm() test_zeros_bigm() test_nested_bigm() end \ No newline at end of file diff --git a/test/constraints/disjunct.jl b/test/constraints/disjunct.jl index 6b7c80b..64f6377 100644 --- a/test/constraints/disjunct.jl +++ b/test/constraints/disjunct.jl @@ -7,6 +7,7 @@ function test_disjunct_add_fail() @variable(model, w, Logical) @variable(model, z, Bin) @test_macro_throws UndefVarError @constraint(model, z == 1, DisjunctConstraint(w)) # binary variable + @test_throws ErrorException build_constraint(error, 1z, MOI.EqualTo(1), DisjunctConstraint(w)) # binary variable end function test_disjunct_add_success() diff --git a/test/constraints/fallback.jl b/test/constraints/fallback.jl new file mode 100644 index 0000000..9e256fb --- /dev/null +++ b/test/constraints/fallback.jl @@ -0,0 +1,10 @@ +function test_reformulate_disjunct_constraint_fallback() + model = GDPModel() + @variable(model, x) + c = build_constraint(error, 1x, MOI.LessThan(1)) + @test_throws ErrorException reformulate_disjunct_constraint(model, c, x, DummyReformulation()) +end + +@testset "Fallbacks" begin + test_reformulate_disjunct_constraint_fallback() +end \ No newline at end of file diff --git a/test/constraints/hull.jl b/test/constraints/hull.jl index 460f60c..f4f9fdf 100644 --- a/test/constraints/hull.jl +++ b/test/constraints/hull.jl @@ -341,6 +341,18 @@ function test_vector_quadratic_hull_1sided(moiset) @test ref[1].set == moiset(2) end #less than, greater than, equalto +function test_scalar_nonlinear_hull_1sided_error() + model = GDPModel() + @variable(model, 10 <= x <= 100) + @variable(model, z, Logical) + @constraint(model, con, log(x) <= 10, DisjunctConstraint(z)) + DP._reformulate_logical_variables(model) + zbin = variable_by_name(model, "z") + ϵ = 1e-3 + method = DP._Hull(Hull(ϵ, Dict(x => (0., 100.))), Set([x])) + DP._disaggregate_variables(model, z, Set([x]), method) + @test_throws ErrorException reformulate_disjunct_constraint(model, constraint_object(con), zbin, method) +end function test_scalar_nonlinear_hull_1sided(moiset) model = GDPModel() @variable(model, 10 <= x <= 100) @@ -371,6 +383,18 @@ function test_scalar_nonlinear_hull_1sided(moiset) @test DP._set_value(ref[1].set) == 0 end #nonpositives, nonnegatives, zeros +function test_vector_nonlinear_hull_1sided_error() + model = GDPModel() + @variable(model, 10 <= x <= 100) + @variable(model, z, Logical) + @constraint(model, con, [log(x),log(x)] <= [10,10], DisjunctConstraint(z)) + DP._reformulate_logical_variables(model) + zbin = variable_by_name(model, "z") + ϵ = 1e-3 + method = DP._Hull(Hull(ϵ, Dict(x => (0., 100.))), Set([x])) + DP._disaggregate_variables(model, z, Set([x]), method) + @test_throws ErrorException reformulate_disjunct_constraint(model, constraint_object(con), zbin, method) +end function test_vector_nonlinear_hull_1sided(moiset) model = GDPModel() @variable(model, 10 <= x <= 100) @@ -471,6 +495,18 @@ function test_scalar_quadratic_hull_2sided() @test DP._set_value(ref[i].set) == 0 end end +function test_scalar_nonlinear_hull_2sided_error() + model = GDPModel() + @variable(model, 10 <= x <= 100) + @variable(model, z, Logical) + @constraint(model, con, 0 <= log(x) <= 10, DisjunctConstraint(z)) + DP._reformulate_logical_variables(model) + zbin = variable_by_name(model, "z") + ϵ = 1e-3 + method = DP._Hull(Hull(ϵ, Dict(x => (0., 100.))), Set([x])) + DP._disaggregate_variables(model, z, Set([x]), method) + @test_throws ErrorException reformulate_disjunct_constraint(model, constraint_object(con), zbin, method) +end function test_scalar_nonlinear_hull_2sided() model = GDPModel() @variable(model, 10 <= x <= 100) @@ -525,14 +561,17 @@ end test_scalar_quadratic_hull_1sided(s) test_scalar_nonlinear_hull_1sided(s) end + test_scalar_nonlinear_hull_1sided_error() for s in (MOI.Nonpositives, MOI.Nonnegatives, MOI.Zeros) test_vector_var_hull_1sided(s) test_vector_affine_hull_1sided(s) test_vector_quadratic_hull_1sided(s) test_vector_nonlinear_hull_1sided(s) end + test_vector_nonlinear_hull_1sided_error() test_scalar_var_hull_2sided() test_scalar_affine_hull_2sided() test_scalar_quadratic_hull_2sided() test_scalar_nonlinear_hull_2sided() + test_scalar_nonlinear_hull_2sided_error() end \ No newline at end of file diff --git a/test/constraints/indicator.jl b/test/constraints/indicator.jl index 2c17f03..f6697c5 100644 --- a/test/constraints/indicator.jl +++ b/test/constraints/indicator.jl @@ -23,15 +23,16 @@ function test_indicator_vector_constraints() model = GDPModel() A = [1 0; 0 1] @variable(model, x) - @variable(model, y[1:2], Logical) + @variable(model, y[1:3], Logical) @constraint(model, A*[x,x] == [5,5], DisjunctConstraint(y[1])) - @constraint(model, A*[x,x] == [10,10], DisjunctConstraint(y[2])) + @constraint(model, A*[x,x] <= [0,0], DisjunctConstraint(y[2])) + @constraint(model, A*[x,x] >= [10,10], DisjunctConstraint(y[3])) @disjunction(model, y) reformulate_model(model, Indicator()) ref_cons = DP._reformulation_constraints(model) ref_cons_obj = constraint_object.(ref_cons) - @test length(ref_cons) == 4 + @test length(ref_cons) == 6 @test all(is_valid.(model, ref_cons)) @test all(isa.(ref_cons_obj, VectorConstraint)) @test all([cobj.set isa MOI.Indicator for cobj in ref_cons_obj]) diff --git a/test/constraints/proposition.jl b/test/constraints/proposition.jl index 429eb77..7e2173f 100644 --- a/test/constraints/proposition.jl +++ b/test/constraints/proposition.jl @@ -1,6 +1,14 @@ +function test_op_fallback() + @test_throws ErrorException iff(1,1) + @test_throws ErrorException implies(1,1) + @test_throws ErrorException 1 ⇔ 1 + @test_throws ErrorException 1 ⟹ 1 +end + function test_proposition_add_fail() m = GDPModel() @variable(m, y[1:3], Logical) + @test_throws ErrorException @constraint(m, y[1] in IsTrue()) @test_throws ErrorException @constraint(Model(), logical_or(y...) in IsTrue()) @test_throws ErrorException @constraint(m, logical_or(y...) == 2) @test_throws ErrorException @constraint(m, logical_or(y...) <= 1) @@ -262,6 +270,13 @@ function test_eliminate_equivalence() @test Set(new_ex.args[2].args) == Set{Any}(y) end +function test_eliminate_equivalence_error() + model = GDPModel() + @variable(model, y, Logical) + ex = iff(y) + @test_throws ErrorException DP._eliminate_equivalence(ex) +end + function test_eliminate_equivalence_flat() model = GDPModel() @variable(model, y[1:3], Logical) @@ -390,7 +405,7 @@ end function test_negate_and_error() model = GDPModel() @variable(model, y, Logical) - @test_throws ErrorException DP._negate_or(∧(y)) + @test_throws ErrorException DP._negate_and(∧(y)) end function test_negate_negation() @@ -419,6 +434,13 @@ function test_distribute_and_over_or() @test y[3] in new_ex.args[1].args || y[3] in new_ex.args[2].args end +function test_distribute_and_over_or_error() + model = GDPModel() + @variable(model, y[1:2], Logical) + ex = ∨(y[1] ∧ y[2]) + @test_throws ErrorException DP._distribute_and_over_or(ex) +end + function test_distribute_and_over_or_nested() model = GDPModel() @variable(model, y[1:4], Logical) @@ -486,7 +508,21 @@ function test_to_cnf() (!(y[1] in new_ex.args[6].args) && !(y[2] in new_ex.args[6].args) && y[3] in new_ex.args[6].args) end +function test_isa_literal_other() + @test !DP._isa_literal(1) +end + +function test_reformulate_clause_error() + model = GDPModel() + @variable(model, y[1:2], Logical) + ex = y[1] ∧ y[2] + @test_throws ErrorException DP._reformulate_clause(model, ex) +end + @testset "Logical Proposition Constraints" begin + @testset "Logical Operators" begin + test_op_fallback() + end @testset "Add Proposition" begin test_proposition_add_fail() test_negation_add_success() @@ -505,10 +541,13 @@ end test_equivalence_reformulation() test_intersection_reformulation() test_implication_reformulation() + test_reformulate_clause_error() end @testset "Conjunctive Normal Form" begin + test_isa_literal_other() test_lvar_cnf_functions() test_eliminate_equivalence() + test_eliminate_equivalence_error() test_eliminate_equivalence_flat() test_eliminate_equivalence_nested() test_eliminate_implication() @@ -525,6 +564,7 @@ end test_negate_negation() test_negate_negation_error() test_distribute_and_over_or() + test_distribute_and_over_or_error() test_distribute_and_over_or_nested() test_to_cnf() end diff --git a/test/disjunction.jl b/test/disjunction.jl index b44620f..728d9da 100644 --- a/test/disjunction.jl +++ b/test/disjunction.jl @@ -3,14 +3,17 @@ function test_disjunction_add_fail() @variable(model, x) @variable(model, y[1:2], Logical) @constraint(model, x == 5, DisjunctConstraint(y[1])) - + @test_macro_throws ErrorException @disjunction(model) #not enough arguments @test_macro_throws UndefVarError @disjunction(model, y) #unassociated indicator @test_macro_throws UndefVarError @disjunction(GDPModel(), y) #wrong model @test_macro_throws ErrorException @disjunction(Model(), y) #not a GDPModel @test_macro_throws UndefVarError @disjunction(model, [y[1], y[1]]) #duplicate indicator - @test_macro_throws UndefVarError @disjunction(model, y[1]) #no disjunction expression + @test_macro_throws UndefVarError @disjunction(model, y[1]) #unrecognized disjunction expression + @test_throws ErrorException disjunction(model, y[1]) #unrecognized disjunction expression + @test_throws ErrorException disjunction(model, [1]) #unrecognized disjunction expression @test_macro_throws UndefVarError @disjunction(model, y, "random_arg") #unrecognized extra argument + @test_throws ErrorException DP._disjunction(error, model, y, "y", "random_arg") #unrecognized extra argument @test_macro_throws ErrorException @disjunction(model, "ABC") #unrecognized structure @test_macro_throws ErrorException @disjunction(model, begin y end) #@disjunctions (plural) @test_macro_throws UndefVarError @disjunction(model, x, y) #name x already exists @@ -106,6 +109,16 @@ function test_disjunction_add_sparse_axis() @test Set(keys(disj.data)) == Set([(1,2),(1,3),(2,3)]) end +function test_disjunctions_add_fail() + model = GDPModel() + @variable(model, x) + @variable(model, y[1:2], Logical) + @variable(model, z[1:2], Logical) + @constraint(model, x <= 5, DisjunctConstraint(y[1])) + @constraint(model, x >= 5, DisjunctConstraint(y[2])) + @test_macro_throws ErrorException @disjunctions(model, y) +end + function test_disjunctions_add_success() model = GDPModel() @variable(model, x) @@ -203,6 +216,7 @@ end test_disjunction_add_array() test_disjunciton_add_dense_axis() test_disjunction_add_sparse_axis() + test_disjunctions_add_fail() test_disjunctions_add_success() test_disjunction_function() test_disjunction_function_nested() diff --git a/test/jump.jl b/test/jump.jl new file mode 100644 index 0000000..22db7ca --- /dev/null +++ b/test/jump.jl @@ -0,0 +1,13 @@ +function test_moi_set() + for (jumpset, moisettype) in [(AtLeast(1), DP._MOIAtLeast), + (AtMost(1), DP._MOIAtMost), + (Exactly(1), DP._MOIExactly)] + moiset = moi_set(jumpset, 10) + @test moiset isa moisettype + @test moiset.dimension == 10 + end +end + +@testset "JuMP" begin + test_moi_set() +end \ No newline at end of file diff --git a/test/model.jl b/test/model.jl index 83a2f4b..efbbd26 100644 --- a/test/model.jl +++ b/test/model.jl @@ -1,5 +1,21 @@ using HiGHS +function test_GDPData() + gdpdata = GDPData( + DP._MOIUC.CleverDict{LogicalVariableIndex, LogicalVariableData}(), + DP._MOIUC.CleverDict{LogicalConstraintIndex, ConstraintData}(), + DP._MOIUC.CleverDict{DisjunctConstraintIndex, ConstraintData}(), + DP._MOIUC.CleverDict{DisjunctionIndex, ConstraintData{Disjunction}}(), + Dict{LogicalVariableRef, JuMP.VariableRef}(), + Dict{LogicalVariableRef, Vector{Union{DisjunctConstraintRef, DisjunctionRef}}}(), + Vector{JuMP.VariableRef}(), + Vector{JuMP.ConstraintRef}(), + nothing, + false + ) + gdpdata isa GDPData +end + function test_empty_model() model = GDPModel() @test gdp_data(model) isa GDPData @@ -32,6 +48,7 @@ function test_set_optimizer() end @testset "GDP Model" begin + test_GDPData() test_empty_model() test_non_gdp_model() test_creation_optimizer() diff --git a/test/runtests.jl b/test/runtests.jl index b443d68..35676e2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -27,6 +27,7 @@ end include("aqua.jl") include("model.jl") +include("jump.jl") include("variables/query.jl") include("variables/logical.jl") include("constraints/selector.jl") @@ -35,5 +36,6 @@ include("constraints/disjunct.jl") include("constraints/indicator.jl") include("constraints/bigm.jl") include("constraints/hull.jl") +include("constraints/fallback.jl") include("disjunction.jl") include("solve.jl") \ No newline at end of file diff --git a/test/variables/logical.jl b/test/variables/logical.jl index b57a024..d187575 100644 --- a/test/variables/logical.jl +++ b/test/variables/logical.jl @@ -1,7 +1,21 @@ # test creating, modifying, and reformulating logical variables + +function test_base() + model = GDPModel() + @variable(model, y, Logical) + @test Base.broadcastable(y) isa Base.RefValue{LogicalVariableRef} + @test length(y) == 1 +end + function test_lvar_add_fail() model = Model() @test_throws ErrorException @variable(model, y, Logical) + @test_throws ErrorException @variable(model, y, Logical; kwarg=true) + @test_throws ErrorException @variable(model, 0 <= y, Logical) + @test_throws ErrorException @variable(model, y <= 1, Logical) + @test_throws ErrorException @variable(model, y, Logical, integer=true) + @test_throws ErrorException @variable(model, y, Logical, start=2) + @test_throws ErrorException @variable(model, y == 2, Logical) end function test_lvar_add_success() @@ -75,6 +89,12 @@ function test_lvar_set_start_value() test_lvar_reformulation(model, y) end +function test_lvar_creation_fix_value() + model = GDPModel() + @variable(model, y == true, Logical) + @test fix_value(y) +end + function test_lvar_fix_value() model = GDPModel() @variable(model, y, Logical) @@ -146,6 +166,9 @@ function test_lvar_reformulation(model::Model, lvref::LogicalVariableRef) end @testset "Logical Variables" begin + @testset "Base Methods" begin + test_base() + end @testset "Add Logical Variables" begin test_lvar_add_fail() test_lvar_add_success() @@ -157,6 +180,7 @@ end test_lvar_set_name() test_lvar_creation_start_value() test_lvar_set_start_value() + test_lvar_creation_fix_value() test_lvar_fix_value() end @testset "Delete Logical Variables" begin