Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 77 additions & 166 deletions src/Bridges/Constraint/indicator_sos.jl
Original file line number Diff line number Diff line change
@@ -1,68 +1,35 @@
"""
IndicatorSOS1Bridge{T, BC <: MOI.AbstractScalarSet}
IndicatorSOS1Bridge{T,S<:MOI.AbstractScalarSet}

The `IndicatorSOS1Bridge` replaces an indicator constraint of the following form:
``z \\in \\mathbb{B}, z == 1 \\implies f(x) \\leq b`` with a SOS1 constraint:
``z \\in \\mathbb{B}, w \\leq 0, f(x) + w \\leq b, SOS1(w, z)``.
`GreaterThan` constraints are handled in a symmetric way:
``z \\in \\mathbb{B}, z == 1 \\implies f(x) \\geq b`` is reformulated as:
``z \\in \\mathbb{B}, w \\geq 0, f(x) + w \\geq b, SOS1(w, z)``.
Other scalar sets are handled without a bound constraint:
``z \\in \\mathbb{B}, z == 1 \\implies f(x) == b`` is reformulated as:
``z \\in \\mathbb{B}, w \\text{ free}, f(x) + w == b, SOS1(w, z)``.

If `BC !<: Union{LessThan, GreaterThan}`, `bound_constraint_index` is `nothing`.
The `IndicatorSOS1Bridge` replaces an indicator constraint of the following
form:
``z \\in \\mathbb{B}, z == 1 \\implies f(x) \\in S`` with a SOS1 constraint:
``z \\in \\mathbb{B}, slack \\text{ free}, f(x) + slack \\in S, SOS1(slack, z)``.
"""
struct IndicatorSOS1Bridge{
T,
BC<:MOI.AbstractScalarSet,
MaybeBC<:Union{MOI.ConstraintIndex{MOI.VariableIndex,BC},Nothing},
} <: AbstractBridge
w_variable::MOI.VariableIndex
z_variable::MOI.VariableIndex
affine_func::MOI.ScalarAffineFunction{T}
bound_constraint_index::MaybeBC
sos_constraint_index::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.SOS1{T}}
linear_constraint_index::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},BC}
struct IndicatorSOS1Bridge{T,S<:MOI.AbstractScalarSet} <: AbstractBridge
slack::MOI.VariableIndex
z::MOI.VariableIndex
sos_index::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.SOS1{T}}
affine_index::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}
end

function bridge_constraint(
::Type{IndicatorSOS1Bridge{T,BC,MaybeBC}},
::Type{IndicatorSOS1Bridge{T,S}},
model::MOI.ModelLike,
f::MOI.VectorAffineFunction{T},
s::MOI.Indicator{MOI.ACTIVATE_ON_ONE,BC},
) where {T<:Real,BC,MaybeBC}
f_scalars = MOIU.eachscalar(f)
(w, bound_constraint) = _add_bound_constraint!(model, BC)
s::MOI.Indicator{MOI.ACTIVATE_ON_ONE,S},
) where {T<:Real,S}
f_scalars = MOI.Utilities.eachscalar(f)
z = convert(MOI.VariableIndex, f_scalars[1])
sos_vector = MOI.VectorOfVariables([w, z])
sos_constraint =
MOI.add_constraint(model, sos_vector, MOI.SOS1{T}([0.4, 0.6]))
affine_func = f_scalars[2]
affine_expr = MOIU.operate(+, T, affine_func, w)
linear_constraint = MOI.add_constraint(model, affine_expr, s.set)
return IndicatorSOS1Bridge{T,BC,MaybeBC}(
w,
z,
affine_func,
bound_constraint,
sos_constraint,
linear_constraint,
slack = MOI.add_variable(model)
sos_index = MOI.add_constraint(
model,
MOI.VectorOfVariables([slack, z]),
MOI.SOS1{T}([0.4, 0.6]), # This weight vector is arbitrary!
)
end

function _add_bound_constraint!(
model::MOI.ModelLike,
::Type{BC},
) where {T<:Real,BC<:Union{MOI.LessThan{T},MOI.GreaterThan{T}}}
return MOI.add_constrained_variable(model, BC(zero(T)))
end

function _add_bound_constraint!(
model::MOI.ModelLike,
::Type{<:MOI.AbstractScalarSet},
)
return (MOI.add_variable(model), nothing)
new_f = MOI.Utilities.operate(+, T, f_scalars[2], slack)
affine_index = MOI.add_constraint(model, new_f, s.set)
return IndicatorSOS1Bridge{T,S}(slack, z, sos_index, affine_index)
end

function MOI.supports_constraint(
Expand All @@ -78,58 +45,39 @@ function MOI.get(
attr::MOI.ConstraintSet,
b::IndicatorSOS1Bridge,
)
return MOI.Indicator{MOI.ACTIVATE_ON_ONE}(
MOI.get(model, attr, b.linear_constraint_index),
)
set = MOI.get(model, attr, b.affine_index)
return MOI.Indicator{MOI.ACTIVATE_ON_ONE}(set)
end

function MOI.get(
::MOI.ModelLike,
::MOI.ConstraintFunction,
model::MOI.ModelLike,
attr::MOI.ConstraintFunction,
b::IndicatorSOS1Bridge{T},
) where {T}
z = b.z_variable
terms = [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(one(T), z))]
for affine_term in b.affine_func.terms
push!(terms, MOI.VectorAffineTerm(2, affine_term))
end
return MOI.VectorAffineFunction(terms, [zero(T), b.affine_func.constant])
f = MOI.get(model, attr, b.affine_index)
terms = MOI.VectorAffineTerm{T}[
MOI.VectorAffineTerm(2, t) for t in f.terms if t.variable != b.slack
]
push!(terms, MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(one(T), b.z)))
return MOI.VectorAffineFunction(terms, [zero(T), f.constant])
end

function MOI.delete(model::MOI.ModelLike, bridge::IndicatorSOS1Bridge)
if bridge.bound_constraint_index !== nothing
MOI.delete(model, bridge.bound_constraint_index)
end
MOI.delete(model, bridge.sos_constraint_index)
MOI.delete(model, bridge.linear_constraint_index)
MOI.delete(model, bridge.w_variable)
MOI.delete(model, bridge.sos_index)
MOI.delete(model, bridge.affine_index)
MOI.delete(model, bridge.slack)
return
end

function MOIB.added_constrained_variable_types(
::Type{<:IndicatorSOS1Bridge{T,BC}},
) where {T,BC<:Union{MOI.LessThan{T},MOI.GreaterThan{T}}}
return Tuple{Type}[(BC,)]
end

function MOIB.added_constrained_variable_types(
::Type{<:IndicatorSOS1Bridge{T,BC}},
) where {T,BC}
function MOI.Bridges.added_constrained_variable_types(
::Type{<:IndicatorSOS1Bridge},
)
return Tuple{Type}[]
end

function MOIB.added_constraint_types(
::Type{<:IndicatorSOS1Bridge{T,BC}},
) where {T,BC<:Union{MOI.LessThan{T},MOI.GreaterThan{T}}}
return Tuple{Type,Type}[
(MOI.VectorOfVariables, MOI.SOS1{T}),
(MOI.ScalarAffineFunction{T}, BC),
]
end

function MOIB.added_constraint_types(
function MOI.Bridges.added_constraint_types(
::Type{<:IndicatorSOS1Bridge{T,S}},
) where {T,S<:MOI.AbstractScalarSet}
) where {T,S}
return Tuple{Type,Type}[
(MOI.VectorOfVariables, MOI.SOS1{T}),
(MOI.ScalarAffineFunction{T}, S),
Expand All @@ -140,38 +88,14 @@ function concrete_bridge_type(
::Type{<:IndicatorSOS1Bridge{T}},
::Type{<:MOI.AbstractVectorFunction},
::Type{MOI.Indicator{MOI.ACTIVATE_ON_ONE,S}},
) where {T,S<:Union{MOI.LessThan,MOI.GreaterThan}}
return IndicatorSOS1Bridge{T,S,MOI.ConstraintIndex{MOI.VariableIndex,S}}
end

function concrete_bridge_type(
::Type{<:IndicatorSOS1Bridge{T}},
::Type{<:MOI.AbstractVectorFunction},
::Type{MOI.Indicator{MOI.ACTIVATE_ON_ONE,S}},
) where {T,S<:MOI.AbstractScalarSet}
return IndicatorSOS1Bridge{T,S,Nothing}
) where {T,S}
return IndicatorSOS1Bridge{T,S}
end

# Attributes, Bridge acting as a model

MOI.get(::IndicatorSOS1Bridge, ::MOI.NumberOfVariables)::Int64 = 1

function MOI.get(b::IndicatorSOS1Bridge, ::MOI.ListOfVariableIndices)
return [b.w_variable]
end

function MOI.get(
::IndicatorSOS1Bridge{T,BC,Nothing},
::MOI.NumberOfConstraints{MOI.VariableIndex,BC},
)::Int64 where {T,BC}
return 0
end

function MOI.get(
::IndicatorSOS1Bridge{T,BC,CI},
::MOI.NumberOfConstraints{MOI.VariableIndex,BC},
)::Int64 where {T,BC,CI<:MOI.ConstraintIndex{MOI.VariableIndex,BC}}
return 1
return [b.slack]
end

function MOI.get(
Expand All @@ -182,83 +106,70 @@ function MOI.get(
end

function MOI.get(
::IndicatorSOS1Bridge{T,BC},
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},BC},
)::Int64 where {T,BC,CI<:MOI.ConstraintIndex{MOI.VariableIndex,BC}}
::IndicatorSOS1Bridge{T,S},
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},S},
)::Int64 where {T,S}
return 1
end

function MOI.get(
b::IndicatorSOS1Bridge{T,BC,CI},
::MOI.ListOfConstraintIndices{MOI.VariableIndex,BC},
) where {T,BC,CI<:MOI.ConstraintIndex}
return [b.bound_constraint_index]
end

function MOI.get(
::IndicatorSOS1Bridge{T,BC,Nothing},
::MOI.ListOfConstraintIndices{MOI.VariableIndex,BC},
) where {T,BC}
return MOI.ConstraintIndex{MOI.VariableIndex,BC}[]
end

function MOI.get(
b::IndicatorSOS1Bridge{T},
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables,<:MOI.SOS1},
) where {T}
return [b.sos_constraint_index]
return [b.sos_index]
end

function MOI.get(
b::IndicatorSOS1Bridge{T,BC},
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},BC},
) where {T,BC}
return [b.linear_constraint_index]
end

function MOI.supports(
::MOI.ModelLike,
::MOI.ConstraintPrimalStart,
::Type{<:IndicatorSOS1Bridge},
)
return true
b::IndicatorSOS1Bridge{T,S},
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},S},
) where {T,S}
return [b.affine_index]
end

function MOI.get(
model::MOI.ModelLike,
attr::MOI.ConstraintPrimal,
bridge::IndicatorSOS1Bridge,
)
zvalue =
MOI.get(model, MOI.VariablePrimal(attr.result_index), bridge.z_variable)
wvalue =
MOI.get(model, MOI.VariablePrimal(attr.result_index), bridge.w_variable)
lin_primal_start = MOI.get(model, attr, bridge.linear_constraint_index)
return [zvalue, lin_primal_start - wvalue]
z = MOI.get(model, MOI.VariablePrimal(attr.result_index), bridge.z)
w = MOI.get(model, MOI.VariablePrimal(attr.result_index), bridge.slack)
f = MOI.get(model, attr, bridge.affine_index)
return [z, f - w]
end

function MOI.supports(
model::MOI.ModelLike,
attr::MOI.ConstraintPrimalStart,
::Type{IndicatorSOS1Bridge{T,S}},
) where {T,S}
ci = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}
return MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex) &&
MOI.supports(model, attr, ci)
end

function MOI.get(
model::MOI.ModelLike,
attr::MOI.ConstraintPrimalStart,
bridge::IndicatorSOS1Bridge,
)
zstart = MOI.get(model, MOI.VariablePrimalStart(), bridge.z_variable)
wstart = MOI.get(model, MOI.VariablePrimalStart(), bridge.w_variable)
lin_primal_start = MOI.get(model, attr, bridge.linear_constraint_index)
return [zstart, lin_primal_start - wstart]
z = MOI.get(model, MOI.VariablePrimalStart(), bridge.z)
w = MOI.get(model, MOI.VariablePrimalStart(), bridge.slack)
f = MOI.get(model, attr, bridge.affine_index)
return [z, f - w]
end

function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintPrimalStart,
bridge::IndicatorSOS1Bridge{T},
value,
value::AbstractVector,
) where {T}
zvalue = value[1]
lin_start = value[2]
MOI.set(model, MOI.VariablePrimalStart(), bridge.z_variable, zvalue)
wstart = MOI.get(model, MOI.VariablePrimalStart(), bridge.w_variable)
wstart = wstart === nothing ? zero(T) : wstart
MOI.set(model, attr, bridge.linear_constraint_index, lin_start + wstart)
@assert length(value) == 2
MOI.set(model, MOI.VariablePrimalStart(), bridge.z, value[1])
w = something(
MOI.get(model, MOI.VariablePrimalStart(), bridge.slack),
zero(T),
)
MOI.set(model, attr, bridge.affine_index, value[2] + w)
return
end
Loading