Skip to content

Commit 6c8fb69

Browse files
authored
Add infeasibility certificate for variable bounds (#155)
* Add farkas duals for variable bounds and refactor how we compute primal/dual rays. * Simplify column passing
1 parent 5255182 commit 6c8fb69

File tree

4 files changed

+278
-146
lines changed

4 files changed

+278
-146
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "GLPK"
22
uuid = "60bf3e95-4087-53dc-ae20-288a0d20c6a6"
33
repo = "https://github.com/jump-dev/GLPK.jl.git"
4-
version = "0.14.2"
4+
version = "0.14.3"
55

66
[deps]
77
BinaryProvider = "b99e7846-7c00-51b0-8f62-c81ae34c0232"

src/MOI_wrapper/MOI_wrapper.jl

Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ function _indices_and_coefficients(
389389
)
390390
i = 1
391391
for term in f.terms
392-
indices[i] = Cint(_info(model, term.variable_index).column)
392+
indices[i] = Cint(column(model, term.variable_index))
393393
coefficients[i] = term.coefficient
394394
i += 1
395395
end
@@ -422,6 +422,8 @@ function _info(model::Optimizer, key::MOI.VariableIndex)
422422
throw(MOI.InvalidIndex(key))
423423
end
424424

425+
column(model, x::MOI.VariableIndex) = _info(model, x).column
426+
425427
function MOI.add_variable(model::Optimizer)
426428
# Initialize `VariableInfo` with a dummy `VariableIndex` and a column,
427429
# because we need `add_item` to tell us what the `VariableIndex` is.
@@ -569,8 +571,8 @@ function MOI.set(
569571
num_vars = length(model.variable_info)
570572
obj = zeros(Float64, num_vars)
571573
for term in f.terms
572-
column = _info(model, term.variable_index).column
573-
obj[column] += term.coefficient
574+
col = column(model, term.variable_index)
575+
obj[col] += term.coefficient
574576
end
575577
for (col, coef) in enumerate(obj)
576578
glp_set_obj_coef(model, col, coef)
@@ -619,6 +621,10 @@ function _info(
619621
return throw(MOI.InvalidIndex(c))
620622
end
621623

624+
function column(model, c::MOI.ConstraintIndex{MOI.SingleVariable, <:Any})
625+
return _info(model, c).column
626+
end
627+
622628
function MOI.is_valid(
623629
model::Optimizer,
624630
c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{Float64}}
@@ -862,7 +868,7 @@ function MOI.get(
862868
c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{Float64}},
863869
)
864870
MOI.throw_if_not_valid(model, c)
865-
lower = glp_get_col_lb(model, _info(model, c).column)
871+
lower = glp_get_col_lb(model, column(model, c))
866872
return MOI.GreaterThan(lower)
867873
end
868874

@@ -872,7 +878,7 @@ function MOI.get(
872878
c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{Float64}},
873879
)
874880
MOI.throw_if_not_valid(model, c)
875-
upper = glp_get_col_ub(model, _info(model, c).column)
881+
upper = glp_get_col_ub(model, column(model, c))
876882
return MOI.LessThan(upper)
877883
end
878884

@@ -882,7 +888,7 @@ function MOI.get(
882888
c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{Float64}},
883889
)
884890
MOI.throw_if_not_valid(model, c)
885-
lower = glp_get_col_lb(model, _info(model, c).column)
891+
lower = glp_get_col_lb(model, column(model, c))
886892
return MOI.EqualTo(lower)
887893
end
888894

@@ -892,9 +898,9 @@ function MOI.get(
892898
c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.Interval{Float64}},
893899
)
894900
MOI.throw_if_not_valid(model, c)
895-
info = _info(model, c)
896-
lower = glp_get_col_lb(model, info.column)
897-
upper = glp_get_col_ub(model, info.column)
901+
col = column(model, c)
902+
lower = glp_get_col_lb(model, col)
903+
upper = glp_get_col_ub(model, col)
898904
return MOI.Interval(lower, upper)
899905
end
900906

@@ -906,8 +912,7 @@ function MOI.set(
906912
) where {S<:_SCALAR_SETS}
907913
MOI.throw_if_not_valid(model, c)
908914
lower, upper = _bounds(s)
909-
info = _info(model, c)
910-
_set_variable_bound(model, info.column, lower, upper)
915+
_set_variable_bound(model, column(model, c), lower, upper)
911916
return
912917
end
913918

@@ -1411,14 +1416,18 @@ function MOI.optimize!(model::Optimizer)
14111416
else
14121417
_solve_linear_problem(model)
14131418
end
1414-
if MOI.get(model, MOI.ResultCount()) > 0
1415-
if MOI.get(model, MOI.PrimalStatus()) == MOI.INFEASIBILITY_CERTIFICATE
1416-
model.unbounded_ray = fill(NaN, glp_get_num_cols(model))
1417-
_get_unbounded_ray(model, model.unbounded_ray)
1418-
end
1419-
if MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE
1420-
model.infeasibility_cert = fill(NaN, glp_get_num_rows(model))
1421-
_get_infeasibility_ray(model, model.infeasibility_cert)
1419+
if _certificates_potentially_available(model)
1420+
(status, _) = _get_status(model)
1421+
if status == MOI.DUAL_INFEASIBLE
1422+
ray = zeros(glp_get_num_cols(model))
1423+
if _get_unbounded_ray(model, ray)
1424+
model.unbounded_ray = ray
1425+
end
1426+
elseif status == MOI.INFEASIBLE
1427+
ray = zeros(glp_get_num_rows(model))
1428+
if _get_infeasibility_ray(model, ray)
1429+
model.infeasibility_cert = ray
1430+
end
14221431
end
14231432
end
14241433
model.solve_time = time() - start_time
@@ -1561,7 +1570,7 @@ function MOI.get(model::Optimizer, attr::MOI.PrimalStatus)
15611570
elseif status == MOI.LOCALLY_INFEASIBLE
15621571
return MOI.INFEASIBLE_POINT
15631572
elseif status == MOI.DUAL_INFEASIBLE
1564-
if _certificates_potentially_available(model)
1573+
if model.unbounded_ray !== nothing
15651574
return MOI.INFEASIBILITY_CERTIFICATE
15661575
end
15671576
else
@@ -1579,7 +1588,7 @@ function MOI.get(model::Optimizer, attr::MOI.DualStatus)
15791588
if status == MOI.OPTIMAL
15801589
return MOI.FEASIBLE_POINT
15811590
elseif status == MOI.INFEASIBLE
1582-
if _certificates_potentially_available(model)
1591+
if model.infeasibility_cert !== nothing
15831592
return MOI.INFEASIBILITY_CERTIFICATE
15841593
end
15851594
end
@@ -1622,9 +1631,9 @@ function MOI.get(model::Optimizer, attr::MOI.VariablePrimal, x::MOI.VariableInde
16221631
_throw_if_optimize_in_progress(model, attr)
16231632
MOI.check_result_index_bounds(model, attr)
16241633
if model.unbounded_ray !== nothing
1625-
return model.unbounded_ray[_info(model, x).column]
1634+
return model.unbounded_ray[column(model, x)]
16261635
else
1627-
return _get_col_primal(model, _info(model, x).column)
1636+
return _get_col_primal(model, column(model, x))
16281637
end
16291638
end
16301639

@@ -1650,18 +1659,33 @@ function _dual_multiplier(model::Optimizer)
16501659
return MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE ? 1.0 : -1.0
16511660
end
16521661

1662+
function _farkas_variable_dual(model::Optimizer, col::Int)
1663+
nnz = glp_get_mat_col(model, col, C_NULL, C_NULL)
1664+
vind = Vector{Cint}(undef, nnz)
1665+
vval = Vector{Cdouble}(undef, nnz)
1666+
nnz = glp_get_mat_col(model, Cint(col), offset(vind), offset(vval))
1667+
return sum(
1668+
model.infeasibility_cert[row] * val for (row, val) in zip(vind, vval)
1669+
)
1670+
end
1671+
16531672
function MOI.get(
1654-
model::Optimizer, attr::MOI.ConstraintDual,
1655-
c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{Float64}}
1673+
model::Optimizer,
1674+
attr::MOI.ConstraintDual,
1675+
c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{Float64}},
16561676
)
16571677
_throw_if_optimize_in_progress(model, attr)
16581678
MOI.check_result_index_bounds(model, attr)
1659-
column = _info(model, c).column
1679+
col = column(model, c)
1680+
if model.infeasibility_cert !== nothing
1681+
dual = _farkas_variable_dual(model, col)
1682+
return min(dual, 0.0)
1683+
end
16601684
reduced_cost = if model.method == SIMPLEX || model.method == EXACT
1661-
glp_get_col_dual(model, column)
1685+
glp_get_col_dual(model, col)
16621686
else
16631687
@assert model.method == INTERIOR
1664-
glp_ipt_col_dual(model, column)
1688+
glp_ipt_col_dual(model, col)
16651689
end
16661690
sense = MOI.get(model, MOI.ObjectiveSense())
16671691
# The following is a heuristic for determining whether the reduced cost
@@ -1683,17 +1707,22 @@ function MOI.get(
16831707
end
16841708

16851709
function MOI.get(
1686-
model::Optimizer, attr::MOI.ConstraintDual,
1687-
c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{Float64}}
1710+
model::Optimizer,
1711+
attr::MOI.ConstraintDual,
1712+
c::MOI.ConstraintIndex{MOI.SingleVariable, MOI.GreaterThan{Float64}},
16881713
)
16891714
_throw_if_optimize_in_progress(model, attr)
16901715
MOI.check_result_index_bounds(model, attr)
1691-
column = _info(model, c).column
1716+
col = column(model, c)
1717+
if model.infeasibility_cert !== nothing
1718+
dual = _farkas_variable_dual(model, col)
1719+
return max(dual, 0.0)
1720+
end
16921721
reduced_cost = if model.method == SIMPLEX || model.method == EXACT
1693-
glp_get_col_dual(model, column)
1722+
glp_get_col_dual(model, col)
16941723
else
16951724
@assert model.method == INTERIOR
1696-
glp_ipt_col_dual(model, column)
1725+
glp_ipt_col_dual(model, col)
16971726
end
16981727
sense = MOI.get(model, MOI.ObjectiveSense())
16991728
# The following is a heuristic for determining whether the reduced cost
@@ -1715,23 +1744,29 @@ function MOI.get(
17151744
end
17161745

17171746
function MOI.get(
1718-
model::Optimizer, attr::MOI.ConstraintDual,
1719-
c::MOI.ConstraintIndex{MOI.SingleVariable, S}
1747+
model::Optimizer,
1748+
attr::MOI.ConstraintDual,
1749+
c::MOI.ConstraintIndex{MOI.SingleVariable, S},
17201750
) where {S <: Union{MOI.EqualTo, MOI.Interval}}
17211751
_throw_if_optimize_in_progress(model, attr)
17221752
MOI.check_result_index_bounds(model, attr)
1723-
return _get_col_dual(model, _info(model, c).column)
1753+
col = column(model, c)
1754+
if model.infeasibility_cert !== nothing
1755+
return _farkas_variable_dual(model, col)
1756+
end
1757+
return _get_col_dual(model, col)
17241758
end
17251759

17261760
function MOI.get(
1727-
model::Optimizer, attr::MOI.ConstraintDual,
1728-
c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64}, <:Any}
1761+
model::Optimizer,
1762+
attr::MOI.ConstraintDual,
1763+
c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64}, <:Any},
17291764
)
17301765
_throw_if_optimize_in_progress(model, attr)
17311766
MOI.check_result_index_bounds(model, attr)
17321767
row = _info(model, c).row
17331768
if model.infeasibility_cert !== nothing
1734-
return model.infeasibility_cert[row]
1769+
return -model.infeasibility_cert[row]
17351770
else
17361771
@assert !model.last_solved_by_mip
17371772
if model.method == SIMPLEX || model.method == EXACT
@@ -1911,7 +1946,7 @@ function MOI.modify(
19111946
chg::MOI.ScalarCoefficientChange{Float64}
19121947
)
19131948
row = Cint(_info(model, c).row)
1914-
col = _info(model, chg.variable).column
1949+
col = column(model, chg.variable)
19151950
nnz = glp_get_mat_row(model, row, C_NULL, C_NULL)
19161951
indices, coefficients = zeros(Cint, nnz), zeros(Cdouble, nnz)
19171952
glp_get_mat_row(model, row, offset(indices), offset(coefficients))
@@ -1933,9 +1968,7 @@ function MOI.modify(
19331968
c::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}},
19341969
chg::MOI.ScalarCoefficientChange{Float64}
19351970
)
1936-
glp_set_obj_coef(
1937-
model, _info(model, chg.variable).column, chg.new_coefficient
1938-
)
1971+
glp_set_obj_coef(model, column(model, chg.variable), chg.new_coefficient)
19391972
return
19401973
end
19411974

@@ -1979,8 +2012,8 @@ function MOI.get(
19792012
c::MOI.ConstraintIndex{MOI.SingleVariable, S},
19802013
) where {S <: _SCALAR_SETS}
19812014
_throw_if_optimize_in_progress(model, attr)
1982-
column = _info(model, c).column
1983-
vbasis = glp_get_col_stat(model, column)
2015+
col = column(model, c)
2016+
vbasis = glp_get_col_stat(model, col)
19842017
if vbasis == GLP_BS
19852018
return MOI.BASIC
19862019
elseif vbasis == GLP_NL

0 commit comments

Comments
 (0)