Skip to content

Commit

Permalink
Indicator constraint (#167)
Browse files Browse the repository at this point in the history
* indicator constraint
* v0.1.8
  • Loading branch information
Wikunia authored Jun 15, 2020
1 parent f71f1eb commit 587dbc8
Show file tree
Hide file tree
Showing 14 changed files with 507 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# ConstrainSolver.jl - Changelog

## v0.1.8 (15th of June 2020)
- Support for indicator constraints
- i.e. `@constraint(m, b => { x + y <= 10 })`

## v0.1.7 (22th of May 2020)
- Better feasibility and pruning in `==`
- **Bugfixes:**
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ConstraintSolver"
uuid = "e0e52ebd-5523-408d-9ca3-7641f1cd1405"
authors = ["Ole Kröger <[email protected]>"]
version = "0.1.7"
version = "0.1.8"

[deps]
Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0"
Expand Down
8 changes: 5 additions & 3 deletions docs/src/supported.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ It's a bit more but still not as fully featured as I would like it to be.
- [X] `==`
- [X] `<=`
- [X] `>=`
- [X] `!=`
- [X] All different
- `@constraint(m, [x,y,z] in CS.AllDifferentSet())`
- [X] Support for `!=`
- [X] Supports `a != b` with `a` and `b` being single variables
- [X] Support for linear unequal constraints [#66](https://github.com/Wikunia/ConstraintSolver.jl/issues/66)
- [X] `TableSet` constraint [#130](https://github.com/Wikunia/ConstraintSolver.jl/pull/130)
- [X] Indicator constraints [#167](https://github.com/Wikunia/ConstraintSolver.jl/pull/167)
- i.e `@constraint(m, b => {x + y >= 12})`
- [X] for affine inner constraints
- [X] for all types of inner constraints
- [ ] Scheduling constraints
- [ ] Cycle constraints

Expand Down
2 changes: 1 addition & 1 deletion src/ConstraintSolver.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ include("constraints/less_than.jl")
include("constraints/svc.jl")
include("constraints/equal.jl")
include("constraints/not_equal.jl")

include("constraints/table.jl")
include("constraints/indicator.jl")

"""
add_var!(com::CS.CoM, from::Int, to::Int; fix=nothing)
Expand Down
1 change: 1 addition & 0 deletions src/MOI_wrapper/MOI_wrapper.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const SVF = MOI.SingleVariable
const SAF = MOI.ScalarAffineFunction
const VAF = MOI.VectorAffineFunction

# indices
const VI = MOI.VariableIndex
Expand Down
133 changes: 127 additions & 6 deletions src/MOI_wrapper/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ JuMP constraints
"""
sense_to_set(::Function, ::Val{:!=}) = NotEqualTo(0.0)

"""
Support for indicator constraints with a set constraint as the right hand side
"""
function JuMP._build_indicator_constraint(
_error::Function, variable::JuMP.AbstractVariableRef,
constraint::JuMP.VectorConstraint, ::Type{MOI.IndicatorSet{A}}) where A

variable_indices = [v.index for v in constraint.func]
set = CS.IndicatorSet{A}(variable, MOI.VectorOfVariables(variable_indices), constraint.set, 0)
vov = VariableRef[variable]
append!(vov, constraint.func)
return JuMP.VectorConstraint(vov, set)
end

### !=

MOIU.shift_constant(set::NotEqualTo, value) = NotEqualTo(set.value + value)
Expand Down Expand Up @@ -39,6 +53,29 @@ MOI.supports_constraint(
::Type{TableSetInternal},
) = true

MOI.supports_constraint(
::Optimizer,
::Type{SAF{T}},
::Type{NotEqualTo{T}},
) where {T<:Real} = true

function MOI.supports_constraint(
::Optimizer,
func::Type{VAF{T}},
set::Type{IS},
) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:MOI.IndicatorSet{A, ASS}}
return A == MOI.ACTIVATE_ON_ONE || A == MOI.ACTIVATE_ON_ZERO
end

function MOI.supports_constraint(
::Optimizer,
func::Type{MOI.VectorOfVariables},
set::Type{IS},
) where {A, IS<:CS.IndicatorSet{A}}
return A == MOI.ACTIVATE_ON_ONE || A == MOI.ACTIVATE_ON_ZERO
end


function check_inbounds(model::Optimizer, aff::SAF{T}) where {T<:Real}
for term in aff.terms
check_inbounds(model, term.variable_index)
Expand Down Expand Up @@ -267,12 +304,6 @@ function MOI.add_constraint(
return MOI.ConstraintIndex{MOI.VectorOfVariables,TableSetInternal}(length(com.constraints))
end

MOI.supports_constraint(
::Optimizer,
::Type{SAF{T}},
::Type{NotEqualTo{T}},
) where {T<:Real} = true

function MOI.add_constraint(
model::Optimizer,
func::SAF{T},
Expand Down Expand Up @@ -310,6 +341,96 @@ function MOI.add_constraint(
return MOI.ConstraintIndex{SAF{T},NotEqualTo{T}}(length(com.constraints))
end

function MOI.add_constraint(
model::Optimizer,
func::VAF{T},
set::IS,
) where {A, T<:Real,ASS<:MOI.AbstractScalarSet, IS<:MOI.IndicatorSet{A, ASS}}
com = model.inner
com.info.n_constraint_types.indicator += 1

indices = [v.scalar_term.variable_index.value for v in func.terms]

# for normal linear constraints
inner_indices = [v.scalar_term.variable_index.value for v in func.terms if v.output_index == 2]
inner_terms = [v.scalar_term for v in func.terms if v.output_index == 2]
inner_constant = func.constants[2]
inner_set = set.set
if ASS isa Type{MOI.GreaterThan{T}}
inner_terms = [MOI.ScalarAffineTerm(-v.scalar_term.coefficient, v.scalar_term.variable_index) for v in func.terms if v.output_index == 2]
inner_constant = -inner_constant
inner_set = MOI.LessThan{T}(-set.set.lower)
end
inner_func = MOI.ScalarAffineFunction{T}(inner_terms, inner_constant)

internals = ConstraintInternals(
length(com.constraints) + 1,
func,
MOI.IndicatorSet{A}(inner_set),
indices
)

lc = LinearConstraint(inner_func, inner_set, inner_indices)
# should not be used...
lc.std.idx = 0

con = IndicatorConstraint(internals, A, lc)

push!(com.constraints, con)
for (i, ind) in enumerate(con.std.indices)
push!(com.subscription[ind], con.std.idx)
end

return MOI.ConstraintIndex{VAF{T},MOI.IndicatorSet{A, ASS}}(length(com.constraints))
end

function MOI.add_constraint(
model::Optimizer,
vars::MOI.VectorOfVariables,
set::IS,
) where {A, T<:Real,IS<:CS.IndicatorSet{A}}
com = model.inner
com.info.n_constraint_types.indicator += 1

internals = ConstraintInternals(
length(com.constraints) + 1,
vars,
set,
Int[v.value for v in vars.variables]
)

inner_constraint = nothing
if set.set isa CS.AllDifferentSetInternal
inner_internals = ConstraintInternals(
0,
MOI.VectorOfVariables(vars.variables[2:end]),
set.set,
Int[v.value for v in vars.variables[2:end]]
)

inner_constraint = AllDifferentConstraint(
inner_internals,
Int[], # pval_mapping will be filled later
Int[], # vertex_mapping => later
Int[], # vertex_mapping_bw => later
Int[], # di_ei => later
Int[], # di_ej => later
MatchingInit(),
Int[]
)
end


con = IndicatorConstraint(internals, A, inner_constraint)

push!(com.constraints, con)
for (i, ind) in enumerate(con.std.indices)
push!(com.subscription[ind], con.std.idx)
end

return MOI.ConstraintIndex{MOI.VectorOfVariables,CS.IndicatorSet{A}}(length(com.constraints))
end

function set_pvals!(model::CS.Optimizer)
com = model.inner
for constraint in com.constraints
Expand Down
90 changes: 90 additions & 0 deletions src/constraints/indicator.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
prune_constraint!(
com::CS.CoM,
constraint::IndicatorConstraint,
fct::Union{MOI.VectorOfVariables, VAF{T}},
set::IS;
logs = true,
) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}}
Prune the search space given the indicator constraint. An indicator constraint is of the form `b => {x + y == 2}`.
Where the constraint in `{ }` is currently a linear constraint.
Return whether the search space is still feasible.
"""
function prune_constraint!(
com::CS.CoM,
constraint::IndicatorConstraint,
fct::Union{MOI.VectorOfVariables, VAF{T}},
set::IS;
logs = true,
) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}}
indicator_var_idx = constraint.std.indices[1]
search_space = com.search_space
indicator_var = search_space[indicator_var_idx]
# still feasible but nothing to prune
!isfixed(indicator_var) && return true
# if active
CS.value(indicator_var) != Int(constraint.activate_on) && return true
inner_constraint = constraint.inner_constraint
return prune_constraint!(com, inner_constraint, inner_constraint.std.fct, inner_constraint.std.set; logs=logs)
end

"""
still_feasible(
com::CoM,
constraint::IndicatorConstraint,
fct::Union{MOI.VectorOfVariables, VAF{T}},
set::IS,
val::Int,
index::Int,
) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}}
Return whether the search space is still feasible when setting `search_space[index]` to value.
"""
function still_feasible(
com::CoM,
constraint::IndicatorConstraint,
fct::Union{MOI.VectorOfVariables, VAF{T}},
set::IS,
val::Int,
index::Int,
) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}}
indicator_var_idx = constraint.std.indices[1]
search_space = com.search_space
indicator_var = search_space[indicator_var_idx]
# still feasible but nothing to prune
!isfixed(indicator_var) && index != indicator_var_idx && return true
# if deactivating or is deactivated
if index != indicator_var_idx
CS.value(indicator_var) != Int(constraint.activate_on) && return true
else
val != Int(constraint.activate_on) && return true
end
# if activating or activated check the inner constraint
inner_constraint = constraint.inner_constraint
return still_feasible(com, inner_constraint, inner_constraint.std.fct, inner_constraint.std.set, val, index)
end

"""
is_solved_constraint(
constraint::IndicatorConstraint,
fct::Union{MOI.VectorOfVariables, VAF{T}},
set::IS,
values::Vector{Int}
) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}}
Return whether given `values` the constraint is fulfilled.
"""
function is_solved_constraint(
constraint::IndicatorConstraint,
fct::Union{MOI.VectorOfVariables, VAF{T}},
set::IS,
values::Vector{Int}
) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}}
if values[1] == Int(constraint.activate_on)
inner_constraint = constraint.inner_constraint
return is_solved_constraint(inner_constraint, inner_constraint.std.fct, inner_constraint.std.set, values[2:end])
end
return true
end
4 changes: 4 additions & 0 deletions src/hashes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ end
function constraint_hash(constraint::SingleVariableConstraint)
return hash([string(typeof(constraint.std.set)), constraint.std.indices])
end

function constraint_hash(constraint::IndicatorConstraint)
return hash([string(typeof(constraint.std.set)), constraint.std.indices, constraint.activate_on, constraint_hash(constraint.inner_constraint)])
end
18 changes: 16 additions & 2 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mutable struct NumberConstraintTypes
notequal::Int
alldifferent::Int
table::Int
indicator::Int
end

mutable struct CSInfo
Expand Down Expand Up @@ -81,6 +82,8 @@ end
====================== TYPES FOR CONSTRAINTS ========================================
====================================================================================#

abstract type Constraint end

"""
BoundRhsVariable
idx - variable index in the lp Model
Expand Down Expand Up @@ -144,6 +147,13 @@ mutable struct TableBacktrackInfo
indices :: Vector{Int}
end

struct IndicatorSet{A} <: MOI.AbstractVectorSet
ind_var :: JuMP.VariableRef
func :: MOI.VectorOfVariables
set :: MOI.AbstractVectorSet
dimension::Int
end

#====================================================================================
====================================================================================#

Expand Down Expand Up @@ -171,8 +181,6 @@ end
====================== CONSTRAINTS ==================================================
====================================================================================#

abstract type Constraint end

mutable struct BasicConstraint <: Constraint
std::ConstraintInternals
end
Expand Down Expand Up @@ -226,6 +234,12 @@ mutable struct TableConstraint <: Constraint
sum_max::Vector{Int}
end

mutable struct IndicatorConstraint <: Constraint
std::ConstraintInternals
activate_on::MOI.ActivationCondition
inner_constraint::Constraint
end

#====================================================================================
====================== OBJECTIVES ==================================================
====================================================================================#
Expand Down
Loading

0 comments on commit 587dbc8

Please sign in to comment.