Skip to content

Commit

Permalink
Merge pull request #6 from mtanneau/status
Browse files Browse the repository at this point in the history
Rewrite the termination and solution statuses
  • Loading branch information
joehuchette authored Dec 12, 2020
2 parents 49a34c4 + cf0bb1c commit 5a64d16
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 113 deletions.
12 changes: 6 additions & 6 deletions src/lp/dominated_column.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function remove_dominated_column!(ps::PresolveData{T}, j::Int; tol::T=100 * sqrt
if !isfinite(lb)
# Problem is dual infeasible
@debug "Column $j is (lower) unbounded"
ps.status = Trm_DualInfeasible
ps.status = DUAL_INFEASIBLE
ps.updated = true

# Resize problem
Expand All @@ -43,8 +43,8 @@ function remove_dominated_column!(ps::PresolveData{T}, j::Int; tol::T=100 * sqrt
ps.solution.s_upper .= zero(T)

# Unbounded ray: xj = -1
ps.solution.primal_status = Sln_InfeasibilityCertificate
ps.solution.dual_status = Sln_Unknown
ps.solution.primal_status = INFEASIBILITY_CERTIFICATE
ps.solution.dual_status = NO_SOLUTION
ps.solution.is_primal_ray = true
ps.solution.is_dual_ray = false
ps.solution.z_primal = ps.solution.z_dual = -T(Inf)
Expand Down Expand Up @@ -87,7 +87,7 @@ function remove_dominated_column!(ps::PresolveData{T}, j::Int; tol::T=100 * sqrt
# Problem is unbounded
@debug "Column $j is (upper) unbounded"

ps.status = Trm_DualInfeasible
ps.status = DUAL_INFEASIBLE
ps.updated = true

# Resize solution
Expand All @@ -100,8 +100,8 @@ function remove_dominated_column!(ps::PresolveData{T}, j::Int; tol::T=100 * sqrt
ps.solution.s_upper .= zero(T)

# Unbounded ray: xj = -1
ps.solution.primal_status = Sln_InfeasibilityCertificate
ps.solution.dual_status = Sln_Unknown
ps.solution.primal_status = INFEASIBILITY_CERTIFICATE
ps.solution.dual_status = NO_SOLUTION
ps.solution.is_primal_ray = true
ps.solution.is_dual_ray = false
ps.solution.z_primal = ps.solution.z_dual = -T(Inf)
Expand Down
12 changes: 6 additions & 6 deletions src/lp/empty_column.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function remove_empty_column!(ps::PresolveData{T}, j::Int) where {T}
else
# Problem is dual infeasible
@debug "Column $j is (lower) unbounded"
ps.status = Trm_DualInfeasible
ps.status = DUAL_INFEASIBLE
ps.updated = true

# Resize problem
Expand All @@ -37,8 +37,8 @@ function remove_empty_column!(ps::PresolveData{T}, j::Int) where {T}
ps.solution.s_upper .= zero(T)

# Unbounded ray: xj = -1
ps.solution.primal_status = Sln_InfeasibilityCertificate
ps.solution.dual_status = Sln_Unknown
ps.solution.primal_status = INFEASIBILITY_CERTIFICATE
ps.solution.dual_status = NO_SOLUTION
ps.solution.is_primal_ray = true
ps.solution.is_dual_ray = false
ps.solution.z_primal = ps.solution.z_dual = -T(Inf)
Expand All @@ -56,7 +56,7 @@ function remove_empty_column!(ps::PresolveData{T}, j::Int) where {T}
else
# Problem is dual infeasible
@debug "Column $j is (upper) unbounded"
ps.status = Trm_DualInfeasible
ps.status = DUAL_INFEASIBLE
ps.updated = true

# Resize problem
Expand All @@ -69,8 +69,8 @@ function remove_empty_column!(ps::PresolveData{T}, j::Int) where {T}
ps.solution.s_upper .= zero(T)

# Unbounded ray: xj = 1
ps.solution.primal_status = Sln_InfeasibilityCertificate
ps.solution.dual_status = Sln_Unknown
ps.solution.primal_status = INFEASIBILITY_CERTIFICATE
ps.solution.dual_status = NO_SOLUTION
ps.solution.is_primal_ray = true
ps.solution.is_dual_ray = false
ps.solution.z_primal = ps.solution.z_dual = -T(Inf)
Expand Down
12 changes: 6 additions & 6 deletions src/lp/empty_row.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function remove_empty_row!(ps::PresolveData{T}, i::Int) where {T}
if ub < zero(T)
# Infeasible
@debug "Row $i is primal infeasible"
ps.status = Trm_PrimalInfeasible
ps.status = PRIMAL_INFEASIBLE
ps.updated = true

# Resize problem
Expand All @@ -31,8 +31,8 @@ function remove_empty_row!(ps::PresolveData{T}, i::Int) where {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
ps.solution.primal_status = NO_SOLUTION
ps.solution.dual_status = INFEASIBILITY_CERTIFICATE
ps.solution.is_primal_ray = false
ps.solution.is_dual_ray = true
ps.solution.z_primal = ps.solution.z_dual = T(Inf)
Expand All @@ -41,7 +41,7 @@ function remove_empty_row!(ps::PresolveData{T}, i::Int) where {T}
return
elseif lb > zero(T)
@debug "Row $i is primal infeasible"
ps.status = Trm_PrimalInfeasible
ps.status = PRIMAL_INFEASIBLE
ps.updated = true

# Resize problem
Expand All @@ -54,8 +54,8 @@ function remove_empty_row!(ps::PresolveData{T}, i::Int) where {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
ps.solution.primal_status = NO_SOLUTION
ps.solution.dual_status = INFEASIBILITY_CERTIFICATE
ps.solution.is_primal_ray = false
ps.solution.is_dual_ray = true
ps.solution.z_primal = ps.solution.z_dual = T(Inf)
Expand Down
85 changes: 43 additions & 42 deletions src/presolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ whose dual writes
"""
mutable struct PresolveData{T}
updated::Bool
status::TerminationStatus
status::ModelStatus

# Original problem
pb0::ProblemData{T}
Expand Down Expand Up @@ -90,7 +90,7 @@ mutable struct PresolveData{T}
ps = new{T}()

ps.updated = false
ps.status = Trm_Unknown
ps.status = NOT_INFERRED

ps.pb0 = pb
ps.pb_red = nothing
Expand Down Expand Up @@ -354,6 +354,20 @@ function postsolve!(sol::Solution{T}, sol_::Solution{T}, ps::PresolveData{T}) wh
return nothing
end

"""
Helper macro for running a presolve routine and returning if
that routine was able to infer the model status (e.g. optimal).
"""
macro _return_if_inferred(expr)
@assert expr.head == :call
@assert length(expr.args) == 2
func = expr.args[1]
ps = expr.args[2]
return esc(quote
$func($ps)
$ps.status == NOT_INFERRED || return $ps.status
end)
end

"""
presolve(pb::ProblemData)
Expand All @@ -364,62 +378,49 @@ function presolve!(ps::PresolveData{T}) where {T}

# Check bound consistency on all rows/columns
st = bounds_consistency_checks!(ps)
ps.status == Trm_PrimalInfeasible && return ps.status
ps.status == PRIMAL_INFEASIBLE && return ps.status

# I. Remove all fixed variables, empty rows and columns
# remove_fixed_variables!(ps)
remove_empty_rows!(ps)
remove_empty_columns!(ps)

# TODO: check status for potential early return
ps.status == Trm_Unknown || return ps.status
@_return_if_inferred remove_empty_rows!(ps)
@_return_if_inferred remove_empty_columns!(ps)

# Identify row singletons
ps.row_singletons = [i for (i, nz) in enumerate(ps.nzrow) if ps.rowflag[i] && nz == 1]

# II. Passes
ps.updated = true
npasses = 0 # TODO: maximum number of passes
while ps.updated && ps.status == Trm_Unknown
while ps.updated && ps.status == NOT_INFERRED
npasses += 1
ps.updated = false
@debug "Presolve pass $npasses" ps.nrow ps.ncol

bounds_consistency_checks!(ps)
ps.status == Trm_Unknown || return ps.status
remove_empty_columns!(ps)
ps.status == Trm_Unknown || return ps.status
@_return_if_inferred bounds_consistency_checks!(ps)
@_return_if_inferred remove_empty_columns!(ps)


# Remove all fixed variables
# TODO: remove empty variables as well
remove_row_singletons!(ps)
ps.status == Trm_Unknown || return ps.status
remove_fixed_variables!(ps)
ps.status == Trm_Unknown || return ps.status
@_return_if_inferred remove_row_singletons!(ps)
@_return_if_inferred remove_fixed_variables!(ps)

# Remove forcing & dominated constraints
remove_row_singletons!(ps)
ps.status == Trm_Unknown || return ps.status
remove_forcing_rows!(ps)
ps.status == Trm_Unknown || return ps.status
@_return_if_inferred remove_row_singletons!(ps)
@_return_if_inferred remove_forcing_rows!(ps)

# Remove free and implied free column singletons
remove_row_singletons!(ps)
ps.status == Trm_Unknown || return ps.status
remove_free_column_singletons!(ps)
ps.status == Trm_Unknown || return ps.status
@_return_if_inferred remove_row_singletons!(ps)
@_return_if_inferred remove_free_column_singletons!(ps)

# TODO: remove column singleton with doubleton equation

# Dual reductions
remove_row_singletons!(ps)
ps.status == Trm_Unknown || return ps.status
remove_dominated_columns!(ps)
ps.status == Trm_Unknown || return ps.status
@_return_if_inferred remove_row_singletons!(ps)
@_return_if_inferred remove_dominated_columns!(ps)
end

remove_empty_columns!(ps)
@_return_if_inferred remove_empty_columns!(ps)

@debug("Presolved problem info",
ps.pb0.ncon, ps.nrow,
Expand All @@ -430,12 +431,12 @@ function presolve!(ps::PresolveData{T}) where {T}
# TODO: check problem dimensions and declare optimality if problem is empty
if ps.nrow == 0 && ps.ncol == 0
# Problem is empty: declare optimality now
ps.status = Trm_Optimal
ps.status = OPTIMAL

# Resize solution
resize!(ps.solution, 0, 0)
ps.solution.primal_status = Sln_Optimal
ps.solution.dual_status = Sln_Optimal
ps.solution.primal_status = FEASIBLE_POINT
ps.solution.dual_status = FEASIBLE_POINT
ps.solution.is_primal_ray = false
ps.solution.is_dual_ray = false
ps.solution.z_primal = ps.obj0
Expand Down Expand Up @@ -494,7 +495,7 @@ function bounds_consistency_checks!(ps::PresolveData{T}) where {T}
if ps.rowflag[i] && l > u
# Problem is primal infeasible
@debug "Row $i is primal infeasible"
ps.status = Trm_PrimalInfeasible
ps.status = PRIMAL_INFEASIBLE
ps.updated = true

# Resize problem
Expand All @@ -507,8 +508,8 @@ function bounds_consistency_checks!(ps::PresolveData{T}) where {T}
ps.solution.s_upper .= zero(T)

# Farkas ray: y⁺_i = y⁻_i = 1 (any > 0 value works)
ps.solution.primal_status = Sln_Unknown
ps.solution.dual_status = Sln_InfeasibilityCertificate
ps.solution.primal_status = NO_SOLUTION
ps.solution.dual_status = INFEASIBILITY_CERTIFICATE
ps.solution.is_primal_ray = false
ps.solution.is_dual_ray = true
ps.solution.z_primal = ps.solution.z_dual = T(Inf)
Expand All @@ -523,7 +524,7 @@ function bounds_consistency_checks!(ps::PresolveData{T}) where {T}
if ps.colflag[j] && l > u
# Primal is primal infeasible
@debug "Column $j is primal infeasible"
ps.status = Trm_PrimalInfeasible
ps.status = PRIMAL_INFEASIBLE
ps.updated = true

# Resize problem
Expand All @@ -536,8 +537,8 @@ function bounds_consistency_checks!(ps::PresolveData{T}) where {T}
ps.solution.s_upper .= zero(T)

# Farkas ray: y⁺_i = y⁻_i = 1 (any > 0 value works)
ps.solution.primal_status = Sln_Unknown
ps.solution.dual_status = Sln_InfeasibilityCertificate
ps.solution.primal_status = NO_SOLUTION
ps.solution.dual_status = INFEASIBILITY_CERTIFICATE
ps.solution.is_primal_ray = false
ps.solution.is_dual_ray = true
ps.solution.z_primal = ps.solution.z_dual = T(Inf)
Expand Down Expand Up @@ -584,7 +585,7 @@ If an empty column is created later, it is removed on the spot.
function remove_empty_columns!(ps::PresolveData{T}) where {T}
for j in 1:ps.pb0.nvar
remove_empty_column!(ps, j)
ps.status == Trm_Unknown || break
ps.status == NOT_INFERRED || break
end
return nothing
end
Expand Down Expand Up @@ -655,7 +656,7 @@ function remove_dominated_columns!(ps::PresolveData{T}) where {T}
iszero(aij) && continue # empty column

# Strengthen dual bounds
#=
#=
=#
cj = ps.obj[j]
Expand Down Expand Up @@ -693,7 +694,7 @@ function remove_dominated_columns!(ps::PresolveData{T}) where {T}

for (j, flag) in enumerate(ps.colflag)
remove_dominated_column!(ps, j)
ps.status == Trm_Unknown || break
ps.status == NOT_INFERRED || break
end
return nothing
end
6 changes: 4 additions & 2 deletions src/solution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ mutable struct Solution{T}
s_upper::Vector{T}

Solution{T}(m, n) where {T} = new{T}(
m, n, Sln_Unknown, Sln_Unknown, false, false,
m, n,
NO_SOLUTION, NO_SOLUTION,
false, false,
zero(T), zero(T),
zeros(T, n), zeros(T, m),
zeros(T, m), zeros(T, m),
Expand All @@ -45,4 +47,4 @@ function Base.resize!(sol::Solution, m::Int, n::Int)
resize!(sol.s_upper, n)

return sol
end
end
Loading

0 comments on commit 5a64d16

Please sign in to comment.