Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Integer variable modeling #5

Merged
merged 4 commits into from
Dec 10, 2020
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
10 changes: 6 additions & 4 deletions src/lp/dominated_column.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ struct DominatedColumn{T} <: PresolveTransformation{T}
col::Col{T} # Column
end

function remove_dominated_column!(ps::PresolveData{T}, j::Int; tol::T=100*sqrt(eps(T))) where{T}
function remove_dominated_column!(ps::PresolveData{T}, j::Int; tol::T=100 * sqrt(eps(T))) where {T}
ps.pb0.is_continuous || error("Dominated column routine currently only supported for LPs.")

ps.colflag[j] || return nothing

# Compute implied bounds on reduced cost: `ls ≤ s ≤ us`
Expand Down Expand Up @@ -80,7 +82,7 @@ function remove_dominated_column!(ps::PresolveData{T}, j::Int; tol::T=100*sqrt(e
elseif cj - ls < -tol
# Reduced cost is always negative => fix to upper bound (or problem is unbounded)
ub = ps.ucol[j]

if !isfinite(ub)
# Problem is unbounded
@debug "Column $j is (upper) unbounded"
Expand Down Expand Up @@ -140,7 +142,7 @@ function remove_dominated_column!(ps::PresolveData{T}, j::Int; tol::T=100*sqrt(e
return nothing
end

function postsolve!(sol::Solution{T}, op::DominatedColumn{T}) where{T}
function postsolve!(sol::Solution{T}, op::DominatedColumn{T}) where {T}
# Primal value
sol.x[op.j] = op.x

Expand All @@ -154,4 +156,4 @@ function postsolve!(sol::Solution{T}, op::DominatedColumn{T}) where{T}
sol.s_upper[op.j] = neg_part(s)

return nothing
end
end
10 changes: 6 additions & 4 deletions src/lp/empty_column.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ struct EmptyColumn{T} <: PresolveTransformation{T}
s::T # Reduced cost
end

function remove_empty_column!(ps::PresolveData{T}, j::Int) where{T}
function remove_empty_column!(ps::PresolveData{T}, j::Int) where {T}
ps.pb0.is_continuous || error("Empty column routine currently only supported for LPs.")

# Sanity check
(ps.colflag[j] && (ps.nzcol[j] == 0)) || return nothing

Expand Down Expand Up @@ -74,7 +76,7 @@ function remove_empty_column!(ps::PresolveData{T}, j::Int) where{T}
ps.solution.z_primal = ps.solution.z_dual = -T(Inf)
j_ = ps.new_var_idx[j]
ps.solution.x[j_] = one(T)

return
end
else
Expand All @@ -97,9 +99,9 @@ function remove_empty_column!(ps::PresolveData{T}, j::Int) where{T}
return nothing
end

function postsolve!(sol::Solution{T}, op::EmptyColumn{T}) where{T}
function postsolve!(sol::Solution{T}, op::EmptyColumn{T}) where {T}
sol.x[op.j] = op.x
sol.s_lower[op.j] = pos_part(op.s)
sol.s_upper[op.j] = neg_part(op.s)
return nothing
end
end
10 changes: 6 additions & 4 deletions src/lp/empty_row.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ struct EmptyRow{T} <: PresolveTransformation{T}
y::T # dual multiplier
end

function remove_empty_row!(ps::PresolveData{T}, i::Int) where{T}
function remove_empty_row!(ps::PresolveData{T}, i::Int) where {T}
ps.pb0.is_continuous || error("Empty row routine currently only supported for LPs.")

# Sanity checks
(ps.rowflag[i] && ps.nzrow[i] == 0) || return nothing

Expand Down Expand Up @@ -50,7 +52,7 @@ function remove_empty_row!(ps::PresolveData{T}, i::Int) where{T}
ps.solution.y_upper .= zero(T)
ps.solution.s_lower .= zero(T)
ps.solution.s_upper .= zero(T)

# Farkas ray: y⁺_i = 1 (any > 0 value works)
ps.solution.primal_status = Sln_Unknown
ps.solution.dual_status = Sln_InfeasibilityCertificate
Expand All @@ -70,8 +72,8 @@ function remove_empty_row!(ps::PresolveData{T}, i::Int) where{T}
ps.nrow -= 1
end

function postsolve!(sol::Solution{T}, op::EmptyRow{T}) where{T}
function postsolve!(sol::Solution{T}, op::EmptyRow{T}) where {T}
sol.y_lower[op.i] = pos_part(op.y)
sol.y_upper[op.i] = neg_part(op.y)
return nothing
end
end
16 changes: 9 additions & 7 deletions src/lp/forcing_row.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ struct DominatedRow{T} <: PresolveTransformation{T}
i::Int # Row index
end

function remove_forcing_row!(ps::PresolveData{T}, i::Int) where{T}
function remove_forcing_row!(ps::PresolveData{T}, i::Int) where {T}
ps.pb0.is_continuous || error("Forcing row routine currently only supported for LPs.")

ps.rowflag[i] || return
ps.nzrow[i] == 1 && return # skip row singletons
ps.nzrow[i] == 1 && return # skip row singletons

# Implied row bounds
row = ps.pb0.arows[i]
Expand Down Expand Up @@ -89,7 +91,7 @@ function remove_forcing_row!(ps::PresolveData{T}, i::Int) where{T}
# ps.nzrow[k] == 0 && remove_empty_row!(ps, k)
ps.nzrow[k] == 1 && push!(ps.row_singletons, k)
end

cj = ps.obj[j]
push!(cols_, col_)
push!(xs, xj_)
Expand Down Expand Up @@ -151,7 +153,7 @@ function remove_forcing_row!(ps::PresolveData{T}, i::Int) where{T}
# ps.nzrow[k] == 0 && remove_empty_row!(ps, k)
ps.nzrow[k] == 1 && push!(ps.row_singletons, k)
end

cj = ps.obj[j]
push!(cols_, col_)
push!(xs, xj_)
Expand All @@ -175,14 +177,14 @@ function remove_forcing_row!(ps::PresolveData{T}, i::Int) where{T}
return nothing
end

function postsolve!(sol::Solution{T}, op::DominatedRow{T}) where{T}
function postsolve!(sol::Solution{T}, op::DominatedRow{T}) where {T}
sol.y_lower[op.i] = zero(T)
sol.y_upper[op.i] = zero(T)
return nothing
end

# TODO: postsolve of forcing rows
function postsolve!(sol::Solution{T}, op::ForcingRow{T}) where{T}
function postsolve!(sol::Solution{T}, op::ForcingRow{T}) where {T}

# Primal
for (j, xj) in zip(op.row.nzind, op.xs)
Expand Down Expand Up @@ -210,4 +212,4 @@ function postsolve!(sol::Solution{T}, op::ForcingRow{T}) where{T}
sol.s_upper[j] = neg_part(s)
end
return nothing
end
end
9 changes: 5 additions & 4 deletions src/lp/free_column_singleton.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ struct FreeColumnSingleton{T} <: PresolveTransformation{T}
row::Row{T}
end

function remove_free_column_singleton!(ps::PresolveData{T}, j::Int) where{T}
function remove_free_column_singleton!(ps::PresolveData{T}, j::Int) where {T}
ps.pb0.is_continuous || error("Free column routine currently only supported for LPs.")

ps.colflag[j] && ps.nzcol[j] == 1 || return nothing # only column singletons

Expand Down Expand Up @@ -108,7 +109,7 @@ function remove_free_column_singleton!(ps::PresolveData{T}, j::Int) where{T}
return nothing
end

function postsolve!(sol::Solution{T}, op::FreeColumnSingleton{T}) where{T}
function postsolve!(sol::Solution{T}, op::FreeColumnSingleton{T}) where {T}
# Dual
y = op.y
sol.y_lower[op.i] = pos_part(y)
Expand All @@ -122,6 +123,6 @@ function postsolve!(sol::Solution{T}, op::FreeColumnSingleton{T}) where{T}
sol.x[op.j] -= aik * sol.x[k]
end
sol.x[op.j] /= op.aij

return nothing
end
end
8 changes: 5 additions & 3 deletions src/lp/row_singleton.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ struct RowSingleton{T} <: PresolveTransformation{T}
end


function remove_row_singleton!(ps::PresolveData{T}, i::Int) where{T}
function remove_row_singleton!(ps::PresolveData{T}, i::Int) where {T}
ps.pb0.is_continuous || error("Row singleton routine currently only supported for LPs.")

# Sanity checks
(ps.rowflag[i] && ps.nzrow[i] == 1) || return nothing

Expand Down Expand Up @@ -78,7 +80,7 @@ function remove_row_singleton!(ps::PresolveData{T}, i::Int) where{T}
return nothing
end

function postsolve!(sol::Solution{T}, op::RowSingleton{T}) where{T}
function postsolve!(sol::Solution{T}, op::RowSingleton{T}) where {T}

if op.force_lower
if op.aij > zero(T)
Expand All @@ -98,4 +100,4 @@ function postsolve!(sol::Solution{T}, op::RowSingleton{T}) where{T}
end

return nothing
end
end
7 changes: 2 additions & 5 deletions src/presolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ function extract_reduced_problem!(ps::PresolveData{T}) where {T}

# Extract new columns
pb.acols = Vector{Col{T}}(undef, pb.nvar)
pb.var_types = Vector{VariableType}(undef, pb.nvar)
jnew = 0
for (jold, col) in enumerate(ps.pb0.acols)
ps.colflag[jold] || continue
Expand All @@ -238,13 +239,9 @@ function extract_reduced_problem!(ps::PresolveData{T}) where {T}

# Set new column
pb.acols[jnew] = Col{T}(cind, cval)
pb.var_types[jnew] = ps.pb0.var_types[jold]
end

# Variable and constraint names
# TODO: we don't need these
pb.var_names = ps.pb0.var_names[ps.colflag]
pb.con_names = ps.pb0.con_names[ps.rowflag]

# Scaling
rscale = zeros(T, ps.nrow)
cscale = zeros(T, ps.ncol)
Expand Down
33 changes: 21 additions & 12 deletions src/problem_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ end
const Row = RowOrCol
const Col = RowOrCol

@enum VariableType CONTINUOUS BINARY GENERAL_INTEGER

"""
ProblemData{T}

Expand Down Expand Up @@ -46,24 +48,24 @@ mutable struct ProblemData{T}
# TODO: Data structures for QP
# qrows
# qcols

# Bounds
lcon::Vector{T}
ucon::Vector{T}
lvar::Vector{T}
uvar::Vector{T}

# Names
con_names::Vector{String}
var_names::Vector{String}
# Variable types
var_types::Vector{VariableType}
is_continuous::Bool

# Only allow empty problems to be instantiated for now
ProblemData{T}(pbname::String="") where {T} = new{T}(
pbname, 0, 0,
true, T[], zero(T),
Row{T}[], Col{T}[],
T[], T[], T[], T[],
String[], String[]
VariableType[], true,
)
end

Expand All @@ -88,14 +90,14 @@ function Base.empty!(pb::ProblemData{T}) where {T}
pb.lvar = T[]
pb.uvar = T[]

pb.con_names = String[]
pb.var_names = String[]
pb.var_types = VariableType[]
pb.is_continuous = true

return pb
end

"""
load_problem!(pb, )
load_problem!(pb, ...)

Load entire problem.
"""
Expand All @@ -105,19 +107,21 @@ function load_problem!(pb::ProblemData{T},
A::SparseMatrixCSC,
lcon::Vector{T}, ucon::Vector{T},
lvar::Vector{T}, uvar::Vector{T},
con_names::Vector{String}, var_names::Vector{String}
var_types::Union{Nothing,Vector{VariableType}}=nothing,
) where {T}
empty!(pb)

# Sanity checks
ncon, nvar = size(A)
ncon == length(lcon) || error("")
ncon == length(ucon) || error("")
ncon == length(con_names) || error("")
nvar == length(obj)
isfinite(obj0) || error("Objective offset $obj0 is not finite")
nvar == length(lvar) || error("")
nvar == length(uvar) || error("")
if var_types !== nothing
nvar == length(var_types) || error("")
end

# Copy data
pb.name = name
Expand All @@ -130,8 +134,13 @@ function load_problem!(pb::ProblemData{T},
pb.ucon = copy(ucon)
pb.lvar = copy(lvar)
pb.uvar = copy(uvar)
pb.con_names = copy(con_names)
pb.var_names = copy(var_names)
if var_types === nothing
pb.var_types = fill(CONTINUOUS, nvar)
pb.is_continuous = true
else
pb.var_types = copy(var_types)
pb.is_continuous = all(pb.var_types .== CONTINUOUS)
end

# Load coefficients
pb.acols = Vector{Col{T}}(undef, nvar)
Expand Down
4 changes: 2 additions & 2 deletions test/lp/empty_column.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function emtpy_column_tests(T::Type)

MathOptPresolve.load_problem!(pb, "Test",
true, [c], zero(T),
spzeros(T, 0, 1), T[], T[], [l], [u], String[], ["x"]
spzeros(T, 0, 1), T[], T[], [l], [u],
)
return pb
end
Expand Down Expand Up @@ -77,4 +77,4 @@ end
for T in COEFF_TYPES
@testset "$T" begin emtpy_column_tests(T) end
end
end
end
5 changes: 1 addition & 4 deletions test/lp/empty_row.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ function empty_row_tests(T::Type)
MathOptPresolve.load_problem!(pb, "test",
true, c, zero(T),
A, T.([-1, 1]), T.([1, 2]), zeros(T, n), fill(T(Inf), n),
["c1", "c2"], ["x", "y", "z"]
)

ps = MathOptPresolve.PresolveData(pb)
Expand Down Expand Up @@ -78,7 +77,6 @@ function test_empty_row_1(T::Type)
MathOptPresolve.load_problem!(pb, "test",
true, c, zero(T),
A, T.([1]), T.([2]), zeros(T, n), fill(T(Inf), n),
["c1"], ["x"]
)

ps = MathOptPresolve.PresolveData(pb)
Expand Down Expand Up @@ -116,7 +114,6 @@ function test_empty_row_2(T::Type)
MathOptPresolve.load_problem!(pb, "test",
true, c, zero(T),
A, T.([-2]), T.([-1]), zeros(T, n), fill(T(Inf), n),
["c1"], ["x"]
)

ps = MathOptPresolve.PresolveData(pb)
Expand All @@ -143,4 +140,4 @@ end
for T in COEFF_TYPES
@testset "$T" begin empty_row_tests(T) end
end
end
end
3 changes: 1 addition & 2 deletions test/lp/fixed_variable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ function test_fixed_variable_with_zeros(T::Type)
A,
zeros(T, m), ones(T, m),
ones(T, n), ones(T, n),
["" for _ in 1:m], ["" for _ in 1:n]
)

ps = MathOptPresolve.PresolveData(pb)
Expand Down Expand Up @@ -48,4 +47,4 @@ end
test_fixed_variable_with_zeros(T)
end
end
end
end
Loading