Skip to content
Open
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
10 changes: 5 additions & 5 deletions src/datatypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -388,15 +388,15 @@ end

mutable struct _MBM{O, T, M <: JuMP.AbstractModel} <: AbstractReformulationMethod
optimizer::O
M::Dict{LogicalVariableRef{M}, T}
default_M::T
conlvref::Vector{LogicalVariableRef{M}}
M::Dict{LogicalVariableRef{M}, Union{T, Vector{T}}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now storing M values for vector constraints as vectors to reflect unique values in each of rows.

default_M::T
conlvref::Vector{LogicalVariableRef{M}}

function _MBM(method::MBM{O, T}, model::M) where {O, T, M <: JuMP.AbstractModel}
new{O, T, M}(method.optimizer,
Dict{LogicalVariableRef{M}, T}(),
Dict{LogicalVariableRef{M}, Union{T, Vector{T}}}(),
method.default_M,
Vector{LogicalVariableRef{M}}()
Vector{LogicalVariableRef{M}}()
)
end
end
Expand Down
213 changes: 118 additions & 95 deletions src/mbm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,44 @@ function reformulate_disjunction(
end
return ref_cons
end
#Reformualates a disjunct the disjunct of interest
#represented by lvref and the other indicators in conlvref

# Reformulates a disjunct represented by lvref using per-constraint M values.
# gets its own set of M_{ie,i'} values for each other term i'.
function _reformulate_disjunct(
model::JuMP.AbstractModel,
ref_cons::Vector{JuMP.AbstractConstraint},
model::JuMP.AbstractModel,
ref_cons::Vector{JuMP.AbstractConstraint},
lvref::LogicalVariableRef,
method::_MBM
)

empty!(method.M)
)
!haskey(_indicator_to_constraints(model), lvref) && return
bconref = Dict(d => binary_variable(d) for d in method.conlvref)

constraints = _indicator_to_constraints(model)[lvref]
filtered_constraints = [c for c in constraints if c isa DisjunctConstraintRef]

for d in method.conlvref
d_constraints = _indicator_to_constraints(model)[d]
disjunct_constraints = [c for c in d_constraints if c isa DisjunctConstraintRef]
if !isempty(disjunct_constraints)
method.M[d] = maximum(
_maximize_M(
model,
JuMP.constraint_object(cref),
filtered_constraints = [
c for c in constraints if c isa DisjunctConstraintRef
]

# For each constraint, compute its own set of M values
for cref in filtered_constraints
empty!(method.M)

for d in method.conlvref
d_constraints = _indicator_to_constraints(model)[d]
disjunct_constraints = [
c for c in d_constraints if c isa DisjunctConstraintRef
]
if !isempty(disjunct_constraints)
method.M[d] = _maximize_M(
model,
JuMP.constraint_object(cref),
disjunct_constraints,
method
) for cref in filtered_constraints
)
)
end
end
end
for cref in filtered_constraints
con = JuMP.constraint_object(cref)
append!(ref_cons, reformulate_disjunct_constraint(model, con,

con = JuMP.constraint_object(cref)
append!(ref_cons, reformulate_disjunct_constraint(model, con,
bconref, method))
end
return ref_cons
Expand All @@ -71,50 +76,49 @@ function reformulate_disjunct_constraint(
return new_ref_cons
end

# Uses per-row M values: method.M[d][row] for each disjunct d
function reformulate_disjunct_constraint(
model::JuMP.AbstractModel,
con::JuMP.VectorConstraint{T, S, R},
bconref:: Union{Dict{<:LogicalVariableRef,<:JuMP.AbstractVariableRef},
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
method::_MBM
) where {T, S <: _MOI.Nonpositives, R}
m_sum = sum(method.M[i] * bconref[i] for i in keys(method.M))
new_func = JuMP.@expression(model, [i=1:con.set.dimension],
con.func[i] - m_sum
con.func[i] - sum(method.M[d][i] * bconref[d] for d in keys(method.M))
)
reform_con = JuMP.build_constraint(error, new_func, con.set)
return [reform_con]
end


# Uses per-row M values: method.M[d][row] for each disjunct d
function reformulate_disjunct_constraint(
model::JuMP.AbstractModel,
con::JuMP.VectorConstraint{T, S, R},
bconref:: Union{Dict{<:LogicalVariableRef,<:JuMP.AbstractVariableRef},
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
method::_MBM
) where {T, S <: _MOI.Nonnegatives, R}
m_sum = sum(method.M[i] * bconref[i] for i in keys(method.M))
new_func = JuMP.@expression(model, [i=1:con.set.dimension],
con.func[i] + m_sum
con.func[i] + sum(method.M[d][i] * bconref[d] for d in keys(method.M))
)
reform_con = JuMP.build_constraint(error, new_func, con.set)
return [reform_con]
end

# Uses per-row M values: method.M[d][row] for each disjunct d
function reformulate_disjunct_constraint(
model::JuMP.AbstractModel,
con::JuMP.VectorConstraint{T, S, R},
bconref:: Union{Dict{<:LogicalVariableRef,<:JuMP.AbstractVariableRef},
bconref::Union{Dict{<:LogicalVariableRef,<:JuMP.AbstractVariableRef},
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
method::_MBM
) where {T, S <: _MOI.Zeros, R}
m_sum = sum(method.M[i] * bconref[i] for i in keys(method.M))
upper_expr = JuMP.@expression(model, [i=1:con.set.dimension],
con.func[i] + m_sum
con.func[i] + sum(method.M[d][i] * bconref[d] for d in keys(method.M))
)
lower_expr = JuMP.@expression(model, [i=1:con.set.dimension],
con.func[i] - m_sum
con.func[i] - sum(method.M[d][i] * bconref[d] for d in keys(method.M))
)
upper_con = JuMP.build_constraint(error, upper_expr,
MOI.Nonnegatives(con.set.dimension)
Expand Down Expand Up @@ -153,50 +157,54 @@ function reformulate_disjunct_constraint(
return [reform_con]
end

# Uses per-bound M values: method.M[d][1] for lower, method.M[d][2] for upper
function reformulate_disjunct_constraint(
model::JuMP.AbstractModel,
con::JuMP.ScalarConstraint{T, S},
bconref:: Union{Dict{<:LogicalVariableRef,<:JuMP.AbstractVariableRef},
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
method::_MBM
) where {T, S <: _MOI.EqualTo}
upper_func = JuMP.@expression(model,
con.func - sum(method.M[i] * bconref[i] for i in keys(method.M))
# M[d][1] = M for GreaterThan (lower bound), M[d][2] = M for LessThan
# (upper bound)
lower_func = JuMP.@expression(model,
con.func + sum(method.M[d][1] * bconref[d] for d in keys(method.M))
)
lower_func = JuMP.@expression(model,
con.func + sum(method.M[i] * bconref[i] for i in keys(method.M))
)
upper_con = JuMP.build_constraint(error, upper_func,
MOI.LessThan(con.set.value)
upper_func = JuMP.@expression(model,
con.func - sum(method.M[d][2] * bconref[d] for d in keys(method.M))
)
lower_con = JuMP.build_constraint(error, lower_func,
lower_con = JuMP.build_constraint(error, lower_func,
MOI.GreaterThan(con.set.value)
)
upper_con = JuMP.build_constraint(error, upper_func,
MOI.LessThan(con.set.value)
)
return [lower_con, upper_con]
end

# Uses per-bound M values: method.M[d][1] for lower, method.M[d][2] for upper
function reformulate_disjunct_constraint(
model::JuMP.AbstractModel,
con::JuMP.ScalarConstraint{T, S},
bconref:: Union{Dict{<:LogicalVariableRef,<:JuMP.AbstractVariableRef},
Dict{<:LogicalVariableRef,<:JuMP.GenericAffExpr}},
method::_MBM
) where {T, S <: _MOI.Interval}
set_values = _set_values(con.set)
upper_func = JuMP.@expression(model,
con.func - sum(method.M[i] * bconref[i] for i in keys(method.M))
set_values = _set_values(con.set)
# M[d][1] = M for GreaterThan (lower bound), M[d][2] = M for LessThan
# (upper bound)
lower_func = JuMP.@expression(model,
con.func + sum(method.M[d][1] * bconref[d] for d in keys(method.M))
)
upper_con = JuMP.build_constraint(error, upper_func,
MOI.LessThan(set_values[2])
upper_func = JuMP.@expression(model,
con.func - sum(method.M[d][2] * bconref[d] for d in keys(method.M))
)

lower_func = JuMP.@expression(model,
con.func + sum(method.M[i] * bconref[i] for i in keys(method.M))
)
lower_con = JuMP.build_constraint(error, lower_func,
lower_con = JuMP.build_constraint(error, lower_func,
MOI.GreaterThan(set_values[1])
)

upper_con = JuMP.build_constraint(error, upper_func,
MOI.LessThan(set_values[2])
)
return [lower_con, upper_con]
end

Expand All @@ -216,65 +224,74 @@ end
################################################################################
# Dispatches over constraint types to reformulate into >= or <=
# in order to solve the mini-model
# Returns Vector{T} - one M per row for tighter per-row relaxations
function _maximize_M(
model::JuMP.AbstractModel,
objective::JuMP.VectorConstraint{T, S, R},
constraints::Vector{<:DisjunctConstraintRef},
method::_MBM
) where { T, S <: _MOI.Nonpositives, R}
) where {T, S <: _MOI.Nonpositives, R}
val_type = JuMP.value_type(typeof(model))
return maximum(
return [
_maximize_M(
model,
JuMP.ScalarConstraint(objective.func[i], MOI.LessThan(zero(val_type))),
constraints,
model,
JuMP.ScalarConstraint(
objective.func[i], MOI.LessThan(zero(val_type))
),
constraints,
method
) for i in 1:objective.set.dimension
)
]
end

# Returns Vector{T} - one M per row for tighter per-row relaxations
function _maximize_M(
model::JuMP.AbstractModel,
objective::JuMP.VectorConstraint{T, S, R},
constraints::Vector{<:DisjunctConstraintRef},
method::_MBM
) where { T, S <: _MOI.Nonnegatives, R}
) where {T, S <: _MOI.Nonnegatives, R}
val_type = JuMP.value_type(typeof(model))
return maximum(
return [
_maximize_M(
model,
JuMP.ScalarConstraint(objective.func[i], MOI.GreaterThan(zero(val_type))),
constraints,
model,
JuMP.ScalarConstraint(
objective.func[i], MOI.GreaterThan(zero(val_type))
),
constraints,
method
) for i in 1:objective.set.dimension
)
]
end

# Returns Vector{T} - one M per row, each is max(M_ge, M_le) for that row
function _maximize_M(
model::JuMP.AbstractModel,
objective::JuMP.VectorConstraint{T, S, R},
constraints::Vector{<:DisjunctConstraintRef},
method::_MBM
) where { T, S <: _MOI.Zeros, R}
) where {T, S <: _MOI.Zeros, R}
val_type = JuMP.value_type(typeof(model))
return max(
maximum(
return [
max(
_maximize_M(
model,
JuMP.ScalarConstraint(objective.func[i],MOI.GreaterThan(zero(val_type))),
constraints,
model,
JuMP.ScalarConstraint(
objective.func[i], MOI.GreaterThan(zero(val_type))
),
constraints,
method
) for i in 1:objective.set.dimension
),
maximum(
),
_maximize_M(
model,
JuMP.ScalarConstraint(objective.func[i], MOI.LessThan(zero(val_type))),
constraints,
model,
JuMP.ScalarConstraint(
objective.func[i], MOI.LessThan(zero(val_type))
),
constraints,
method
) for i in 1:objective.set.dimension
)
)
)
) for i in 1:objective.set.dimension
]
end

function _maximize_M(
Expand All @@ -286,50 +303,56 @@ function _maximize_M(
return _mini_model(model, objective, constraints, method)
end

# Returns [M_lower, M_upper] for per-bound relaxations
function _maximize_M(
model::JuMP.AbstractModel,
objective::JuMP.ScalarConstraint{T, S},
constraints::Vector{<:DisjunctConstraintRef},
method::_MBM
) where {T, S <: _MOI.EqualTo}
set_value = objective.set.value
return max(
return [
_mini_model(
model,
JuMP.ScalarConstraint(objective.func, MOI.GreaterThan(set_value)),
constraints,
model,
JuMP.ScalarConstraint(objective.func, MOI.GreaterThan(set_value)),
constraints,
method
),
_mini_model(
model,
JuMP.ScalarConstraint(objective.func, MOI.LessThan(set_value)),
constraints,
model,
JuMP.ScalarConstraint(objective.func, MOI.LessThan(set_value)),
constraints,
method
)
)
]
end

# Returns [M_lower, M_upper] for per-bound relaxations
function _maximize_M(
model::JuMP.AbstractModel,
objective::JuMP.ScalarConstraint{T, S},
constraints::Vector{<:DisjunctConstraintRef},
method::_MBM
) where {T, S <: _MOI.Interval}
set_values = _set_values(objective.set) # Returns (lower, upper)
return max(
return [
_mini_model(
model,
JuMP.ScalarConstraint(objective.func, MOI.GreaterThan(set_values[1])),
constraints,
model,
JuMP.ScalarConstraint(
objective.func, MOI.GreaterThan(set_values[1])
),
constraints,
method
),
_mini_model(
model,
JuMP.ScalarConstraint(objective.func, MOI.LessThan(set_values[2])),
constraints,
model,
JuMP.ScalarConstraint(
objective.func, MOI.LessThan(set_values[2])
),
constraints,
method
)
)
]
end

function _maximize_M(
Expand Down
Loading
Loading