Skip to content

Commit

Permalink
rename to exclusive
Browse files Browse the repository at this point in the history
  • Loading branch information
pulsipher committed Oct 27, 2023
1 parent e5f686f commit a7f7064
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 48 deletions.
28 changes: 15 additions & 13 deletions src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ function JuMP.delete(model::Model, cref::DisjunctionRef)
delete!(gdp_data(model).constraint_to_indicator, cref)
end
delete!(_disjunctions(model), index(cref))
exactly1_dict = gdp_data(model).exactly1_constraints
if haskey(exactly1_dict, cref)
JuMP.delete(model, exactly1_dict[cref])
delete!(exactly1_dict, cref)
exclusive_dict = gdp_data(model).exclusive_constraints
if haskey(exclusive_dict, cref)
JuMP.delete(model, exclusive_dict[cref])
delete!(exclusive_dict, cref)
end
_set_ready_to_optimize(model, false)
return
Expand Down Expand Up @@ -316,7 +316,7 @@ function _disjunction(
model::Model, # TODO: generalize to AbstractModel
structure::AbstractVector, #generalize for containers
name::String;
exactly1::Bool = true,
exclusive::Bool = true,
extra_kwargs...
)
# check for unneeded keywords
Expand All @@ -326,12 +326,12 @@ function _disjunction(
# create the disjunction
dref = _create_disjunction(_error, model, structure, name, false)
# add the exactly one constraint if desired
if exactly1
if exclusive
lvars = JuMP.constraint_object(dref).indicators
func = Union{Number, LogicalVariableRef}[1, lvars...]
set = _MOIExactly(length(lvars) + 1)
cref = JuMP.add_constraint(model, JuMP.VectorConstraint(func, set))
gdp_data(model).exactly1_constraints[dref] = cref
gdp_data(model).exclusive_constraints[dref] = cref
end
return dref
end
Expand All @@ -354,7 +354,7 @@ function _disjunction(
structure,
name::String,
tag::Disjunct;
exactly1::Bool = true,
exclusive::Bool = true,
extra_kwargs...
)
# check for unneeded keywords
Expand All @@ -366,12 +366,12 @@ function _disjunction(
obj = constraint_object(dref)
_add_indicator_var(_DisjunctConstraint(obj, tag.indicator), dref, model)
# add the exactly one constraint if desired
if exactly1
if exclusive
lvars = JuMP.constraint_object(dref).indicators
func = LogicalVariableRef[tag.indicator, lvars...]
set = _MOIExactly(length(lvars) + 1)
cref = JuMP.add_constraint(model, JuMP.VectorConstraint(func, set))
gdp_data(model).exactly1_constraints[dref] = cref
gdp_data(model).exclusive_constraints[dref] = cref
end
return dref
end
Expand All @@ -396,15 +396,17 @@ end
disjunct_indicators::Vector{LogicalVariableRef},
[nested_tag::Disjunct],
[name::String = ""];
[exactly1::Bool = true]
[exclusive::Bool = true]
)
Create a disjunction comprised of disjuncts with indicator variables `disjunct_indicators`
and add it to `model`. For nested disjunctions, the `nested_tag` is required to indicate
which disjunct it will be part of in the parent disjunction. By default, `exactly1` adds
which disjunct it will be part of in the parent disjunction. By default, `exclusive` adds
a constraint of the form `@constraint(model, disjunct_indicators in Exactly(1))` making
the disjuncts exclusive to one another; this is required for certain reformulations like
[`Hull`](@ref). To conveniently generate many disjunctions at once, see [`@disjunction`](@ref)
[`Hull`](@ref). For nested disjunctions, `exclusive` creates a constraint of the form
`@constraint(model, disjunct_indicators in Exactly(nested_tag.indicator))`.
To conveniently generate many disjunctions at once, see [`@disjunction`](@ref)
and [`@disjunctions`](@ref).
"""
function disjunction(
Expand Down
2 changes: 1 addition & 1 deletion src/datatypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ mutable struct GDPData
disjunctions::_MOIUC.CleverDict{DisjunctionIndex, ConstraintData{Disjunction}}

# Exactly one constraint mappings
exactly1_constraints::Dict{DisjunctionRef, LogicalConstraintRef}
exclusive_constraints::Dict{DisjunctionRef, LogicalConstraintRef}

# Indicator variable mappings
indicator_to_binary::Dict{LogicalVariableRef, VariableRef}
Expand Down
2 changes: 1 addition & 1 deletion src/hull.jl
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ end
################################################################################
# HULL REFORMULATION
################################################################################
requires_exactly1(::Hull) = true
requires_exclusive(::Hull) = true

function _reformulate_disjunctions(model::Model, method::Hull)
_query_variable_bounds(model, method)
Expand Down
4 changes: 2 additions & 2 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ The recognized keyword arguments in `kw_args` are the following:
the constraint names are set to `base_name[...]` for each index `...`
of the axes `axes`.
- `container`: Specify the container type.
- `exactly1`: Specify a `Bool` whether an exactly one constraint for the indicator
variables should be added.
- `exclusive`: Specify a `Bool` whether an constraint should be added to make
the disjuncts strictly exlcusive of one another.
To create disjunctions without macros, see [`disjunction`](@ref).
"""
Expand Down
17 changes: 9 additions & 8 deletions src/reformulate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,23 @@ end
# DISJUNCTIONS
################################################################################
"""
requires_exactly1(method::AbstractReformulationMethod)
requires_exclusive(method::AbstractReformulationMethod)
Return a `Bool` whether `method` requires an exactly one constraint for each
disjunction. For new reformulation method types, this should be extended to
in case such a constraint is required (defaults to `false` otherwise).
Return a `Bool` whether `method` requires an the disjuncts to be exactly exclusive
for each disjunction (i.e., it requires an exactly one constraint). For new
reformulation method types, this should be extended to in case such a constraint
is required (defaults to `false` otherwise).
"""
requires_exactly1(::AbstractReformulationMethod) = false
requires_exclusive(::AbstractReformulationMethod) = false

# disjunctions
function _reformulate_all_disjunctions(model::Model, method::AbstractReformulationMethod)
for (idx, disj) in _disjunctions(model)
disj.constraint.nested && continue #only reformulate top level disjunctions
dref = DisjunctionRef(model, idx)
if requires_exactly1(method) && !haskey(gdp_data(model).exactly1_constraints, dref)
error("Reformulation method `$method` requires exactly one constraints for " *
"disjunctions, but `exactly1 = false` for disjunction `$dref`.")
if requires_exclusive(method) && !haskey(gdp_data(model).exclusive_constraints, dref)
error("Reformulation method `$method` requires exclusive disjuncts for " *
"disjunctions, but `exclusive = false` for disjunction `$dref`.")
end
ref_cons = reformulate_disjunction(model, disj.constraint, method)
for (i, ref_con) in enumerate(ref_cons)
Expand Down
4 changes: 2 additions & 2 deletions test/constraints/bigm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,10 @@ function test_nested_bigm()
@variable(model, z[1:2], Logical)
@constraint(model, x <= 5, Disjunct(y[1]))
@constraint(model, x >= 5, Disjunct(y[2]))
@disjunction(model, inner, y, Disjunct(z[1]), exactly1 = false)
@disjunction(model, inner, y, Disjunct(z[1]), exclusive = false)
@constraint(model, x <= 10, Disjunct(z[1]))
@constraint(model, x >= 10, Disjunct(z[2]))
@disjunction(model, outer, z, exactly1 = false)
@disjunction(model, outer, z, exclusive = false)

reformulate_model(model, BigM())
bvrefs = DP._indicator_to_binary(model)
Expand Down
18 changes: 9 additions & 9 deletions test/constraints/disjunction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function test_disjunction_add_success()
disj = DisjunctionRef(model, DisjunctionIndex(1))
disj2 = DisjunctionRef(model, DisjunctionIndex(2))
@test @disjunction(model, y) == disj
@test @disjunction(model, disj2, y, exactly1 = false) == disj2
@test @disjunction(model, disj2, y, exclusive = false) == disj2
@test owner_model(disj) == model
@test is_valid(model, disj)
@test index(disj) == DisjunctionIndex(1)
Expand All @@ -62,8 +62,8 @@ function test_disjunction_add_success()
@test DP._disjunctions(model)[index(disj)] == DP._constraint_data(disj)
@test !constraint_object(disj).nested
@test constraint_object(disj).indicators == y
@test haskey(gdp_data(model).exactly1_constraints, disj)
@test !haskey(gdp_data(model).exactly1_constraints, disj2)
@test haskey(gdp_data(model).exclusive_constraints, disj)
@test !haskey(gdp_data(model).exclusive_constraints, disj2)
@test disj == copy(disj)
end

Expand All @@ -89,7 +89,7 @@ function test_disjunction_add_nested()
@test !constraint_object(outer).nested
@test haskey(DP._indicator_to_constraints(model), z[1])
@test inner in DP._indicator_to_constraints(model)[z[1]]
@test haskey(gdp_data(model).exactly1_constraints, inner)
@test haskey(gdp_data(model).exclusive_constraints, inner)
end

function test_disjunction_add_array()
Expand Down Expand Up @@ -189,15 +189,15 @@ function test_disjunction_delete()
@test delete(model, disj) isa Nothing
@test !haskey(gdp_data(model).disjunctions, index(disj))
@test !DP._ready_to_optimize(model)
@test !haskey(gdp_data(model).exactly1_constraints, disj)
@test !haskey(gdp_data(model).exclusive_constraints, disj)

model = GDPModel()
@variable(model, x)
@variable(model, y[1:2], Logical)
@variable(model, z[1:2], Logical)
@constraint(model, x <= 5, Disjunct(y[1]))
@constraint(model, x >= 5, Disjunct(y[2]))
@disjunction(model, inner, y, Disjunct(z[1]), exactly1 = false)
@disjunction(model, inner, y, Disjunct(z[1]), exclusive = false)

@test delete(model, inner) isa Nothing
@test !haskey(gdp_data(model).disjunctions, index(inner))
Expand All @@ -219,7 +219,7 @@ function test_disjunction_function()
set_name(disj, "new_name")
@test name(disj) == "new_name"
@test haskey(DP._disjunctions(model), index(disj))
@test haskey(gdp_data(model).exactly1_constraints, disj)
@test haskey(gdp_data(model).exclusive_constraints, disj)
end

function test_disjunction_function_nested()
Expand All @@ -233,7 +233,7 @@ function test_disjunction_function_nested()
@constraint(model, x >= 10, Disjunct(z[2]))
disj1 = DisjunctionRef(model, DisjunctionIndex(1))
disj2 = DisjunctionRef(model, DisjunctionIndex(2))
@test disjunction(model, y, Disjunct(z[1]), "inner", exactly1 = false) == disj1
@test disjunction(model, y, Disjunct(z[1]), "inner", exclusive = false) == disj1
@test disjunction(model, z, "outer") == disj2

@test is_valid(model, disj1)
Expand All @@ -244,7 +244,7 @@ function test_disjunction_function_nested()
@test !constraint_object(disj2).nested
@test haskey(DP._indicator_to_constraints(model), z[1])
@test disj1 in DP._indicator_to_constraints(model)[z[1]]
@test !haskey(gdp_data(model).exactly1_constraints, disj1)
@test !haskey(gdp_data(model).exclusive_constraints, disj1)
end

@testset "Disjunction" begin
Expand Down
6 changes: 3 additions & 3 deletions test/constraints/fallback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ function test_reformulate_disjunct_constraint_fallback()
@test_throws ErrorException reformulate_disjunct_constraint(model, c, x, DummyReformulation())
end

function test_exactly1_fallback()
@test requires_exactly1(BigM()) == false
function test_exclusive_fallback()
@test requires_exclusive(BigM()) == false
end

@testset "Fallbacks" begin
test_reformulate_disjunct_constraint_fallback()
test_exactly1_fallback()
test_exclusive_fallback()
end
8 changes: 4 additions & 4 deletions test/constraints/hull.jl
Original file line number Diff line number Diff line change
Expand Up @@ -636,14 +636,14 @@ function test_scalar_nonlinear_hull_2sided()
end
end

function test_exactly1_error()
function test_exclusive_error()
model = GDPModel()
@variable(model, 10 <= x <= 100)
@variable(model, z[1:2], Logical)
@constraint(model, 1 <= x <= 5, Disjunct(z[1]))
@constraint(model, 3 <= x <= 5, Disjunct(z[2]))
disjunction(model, z, exactly1 = false)
@test requires_exactly1(Hull())
disjunction(model, z, exclusive = false)
@test requires_exclusive(Hull())
@test_throws ErrorException reformulate_model(model, Hull())
end

Expand Down Expand Up @@ -686,5 +686,5 @@ end
test_scalar_quadratic_hull_2sided()
test_scalar_nonlinear_hull_2sided()
test_scalar_nonlinear_hull_2sided_error()
test_exactly1_error()
test_exclusive_error()
end
10 changes: 5 additions & 5 deletions test/constraints/indicator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function test_indicator_array()
@variable(model, y[1:2], Logical)
@constraint(model, [1:3, 1:2], x <= 6, Disjunct(y[1]))
@constraint(model, [1:3, 1:2], x >= 6, Disjunct(y[2]))
@disjunction(model, y, exactly1 = false)
@disjunction(model, y, exclusive = false)
reformulate_model(model, Indicator())

ref_cons = DP._reformulation_constraints(model)
Expand All @@ -61,7 +61,7 @@ function test_indicator_dense_axis()
@variable(model, y[1:2], Logical)
@constraint(model, [["a","b","c"],[1,2]], x <= 7, Disjunct(y[1]))
@constraint(model, [["a","b","c"],[1,2]], x >= 7, Disjunct(y[2]))
@disjunction(model, y, exactly1 = false)
@disjunction(model, y, exclusive = false)
reformulate_model(model, Indicator())

ref_cons = DP._reformulation_constraints(model)
Expand All @@ -78,7 +78,7 @@ function test_indicator_sparse_axis()
@variable(model, y[1:2], Logical)
@constraint(model, [i = 1:3, j = 1:3; j > i], x <= 7, Disjunct(y[1]))
@constraint(model, [i = 1:3, j = 1:3; j > i], x >= 7, Disjunct(y[2]))
@disjunction(model, y, exactly1 = false)
@disjunction(model, y, exclusive = false)
reformulate_model(model, Indicator())

ref_cons = DP._reformulation_constraints(model)
Expand All @@ -96,10 +96,10 @@ function test_indicator_nested()
@variable(model, z[1:2], Logical)
@constraint(model, x <= 5, Disjunct(y[1]))
@constraint(model, x >= 5, Disjunct(y[2]))
@disjunction(model, y, Disjunct(z[1]), exactly1 = false)
@disjunction(model, y, Disjunct(z[1]), exclusive = false)
@constraint(model, x <= 10, Disjunct(z[1]))
@constraint(model, x >= 10, Disjunct(z[2]))
@disjunction(model, z, exactly1 = false)
@disjunction(model, z, exclusive = false)
reformulate_model(model, Indicator())

ref_cons = DP._reformulation_constraints(model)
Expand Down

0 comments on commit a7f7064

Please sign in to comment.