Skip to content

Commit c88194f

Browse files
fix postsolve, check primal constraints (#90)
1 parent acb6614 commit c88194f

File tree

7 files changed

+165
-28
lines changed

7 files changed

+165
-28
lines changed

src/presolve/free_rows.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
struct FreeRow{T, S} <: PresolveOperation{T, S}
2+
i::Int # idx of free row
3+
end
4+
5+
function free_rows!(
6+
operations::Vector{PresolveOperation{T, S}},
7+
lcon::S,
8+
ucon::S,
9+
ncon,
10+
row_cnt,
11+
kept_rows::Vector{Bool},
12+
) where {T, S}
13+
free_row_pass = false
14+
for i = 1:ncon
15+
(kept_rows[i] && lcon[i] == -T(Inf) && ucon[i] == T(Inf)) || continue
16+
free_row_pass = true
17+
row_cnt[i] = -1
18+
kept_rows[i] = false
19+
push!(operations, FreeRow{T, S}(i))
20+
end
21+
return free_row_pass
22+
end
23+
24+
postsolve!(pt::OutputPoint, operation::FreeRow) = nothing

src/presolve/postsolve_utils.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ function restore_ilow_iupp!(ilow, iupp, kept_cols)
3232
offset += 1
3333
end
3434
if c_low nlow && ilow[c_low] + offset == i
35-
ilow[c_low] += 1
35+
ilow[c_low] += offset
3636
c_low += 1
3737
end
3838
if c_upp nupp && iupp[c_upp] + offset == i
39-
iupp[c_upp] += 1
39+
iupp[c_upp] += offset
4040
c_upp += 1
4141
end
4242
end

src/presolve/presolve.jl

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ include("empty_rows.jl")
7070
include("singleton_rows.jl")
7171
include("unconstrained_reductions.jl")
7272
include("linear_singleton_columns.jl")
73+
include("primal_constraints.jl")
74+
include("free_rows.jl")
7375
include("postsolve_utils.jl")
7476

7577
mutable struct PresolvedData{T, S}
@@ -210,6 +212,23 @@ function presolve(
210212
kept_cols,
211213
)
212214

215+
infeasible_cst = primal_constraints!(
216+
operations,
217+
arows,
218+
lcon,
219+
ucon,
220+
lvar,
221+
uvar,
222+
nvar,
223+
ncon,
224+
kept_rows,
225+
kept_cols,
226+
row_cnt,
227+
col_cnt,
228+
)
229+
230+
free_rows_pass = free_rows!(operations, lcon, ucon, ncon, row_cnt, kept_rows)
231+
213232
psdata.c0, ifix_pass = remove_ifix!(
214233
operations,
215234
hcols,
@@ -228,11 +247,11 @@ function presolve(
228247
xps,
229248
)
230249

231-
infeasible = check_bounds(lvar, uvar, lcon, ucon, nvar, ncon, kept_rows, kept_cols)
250+
infeasible_bnd = check_bounds(lvar, uvar, lcon, ucon, nvar, ncon, kept_rows, kept_cols)
232251

233-
keep_iterating =
234-
(empty_row_pass || singl_row_pass || ifix_pass || free_lsc_pass) &&
235-
(!unbounded || !infeasible)
252+
infeasible = infeasible_bnd || infeasible_cst
253+
reduction_pass = empty_row_pass || singl_row_pass || ifix_pass || free_lsc_pass || free_rows_pass
254+
keep_iterating = reduction_pass && !unbounded && !infeasible
236255
keep_iterating && (nb_pass += 1)
237256
end
238257

@@ -295,7 +314,8 @@ function presolve(
295314
multipliers_U = s_u,
296315
iter = 0,
297316
elapsed_time = time() - start_time,
298-
solver_specific = Dict(:presolvedQM => nothing),
317+
solver_specific = Dict(:presolvedQM => nothing,
318+
:psoperations => operations),
299319
)
300320
else
301321
psmeta = NLPModelMeta{T, S}(
@@ -319,7 +339,7 @@ function presolve(
319339
ps,
320340
iter = 0,
321341
elapsed_time = time() - start_time,
322-
solver_specific = Dict(:presolvedQM => ps),
342+
solver_specific = Dict(:presolvedQM => ps, :psoperations => operations),
323343
)
324344
end
325345
end
@@ -343,12 +363,12 @@ function postsolve!(
343363
end
344364

345365
"""
346-
x, y, s_l, s_u = postsolve(qm::QuadraticModel{T, S}, psqm::PresolvedQuadraticModel{T, S},
347-
x_in::S, y_in::S,
348-
s_l_in::SparseVector{T, Int},
349-
s_u_in::SparseVector{T, Int}) where {T, S}
366+
pt = postsolve(qm::QuadraticModel{T, S}, psqm::PresolvedQuadraticModel{T, S},
367+
x_in::S, y_in::S,
368+
s_l_in::SparseVector{T, Int},
369+
s_u_in::SparseVector{T, Int}) where {T, S}
350370
351-
Retrieve the solution `x, y, s_l, s_u` of the original QP `qm` given the solution of the presolved QP (`psqm`)
371+
Retrieve the solution `(x, y, s_l, s_u)` of the original QP `qm` given the solution of the presolved QP (`psqm`)
352372
`x_in, y_in, s_l_in, s_u_in`.
353373
"""
354374
function postsolve(

src/presolve/primal_constraints.jl

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
function primal_constraints!(
2+
operations::Vector{PresolveOperation{T, S}},
3+
arows::Vector{Row{T}},
4+
lcon::S,
5+
ucon::S,
6+
lvar::S,
7+
uvar::S,
8+
nvar,
9+
ncon,
10+
kept_rows,
11+
kept_cols,
12+
row_cnt,
13+
col_cnt,
14+
) where {T, S}
15+
16+
for i=1:ncon
17+
(kept_rows[i] && !(lcon[i] == -T(Inf) && ucon[i] == T(Inf))) || continue
18+
rowi = arows[i]
19+
uconi2 = zero(T)
20+
lconi2 = zero(T)
21+
for (j, aij) in zip(rowi.nzind, rowi.nzval)
22+
kept_cols[j] || continue
23+
uconi2 += (aij > zero(T)) ? aij * uvar[j] : aij * lvar[j]
24+
lconi2 += (aij > zero(T)) ? aij * lvar[j] : aij * uvar[j]
25+
end
26+
if uconi2 < lcon[i] || lconi2 > ucon[i]
27+
@warn "implied bounds for row $i lead to a primal infeasible constraint"
28+
return true
29+
elseif uconi2 == lcon[i]
30+
for (j, aij) in zip(rowi.nzind, rowi.nzval)
31+
kept_cols[j] || continue
32+
if aij > zero(T)
33+
lvar[j] = uvar[j]
34+
else
35+
uvar[j] = lvar[j]
36+
end
37+
end
38+
elseif lconi2 == ucon[i]
39+
for (j, aij) in zip(rowi.nzind, rowi.nzval)
40+
kept_cols[j] || continue
41+
if aij > zero(T)
42+
uvar[j] = lvar[j]
43+
else
44+
lvar[j] = uvar[j]
45+
end
46+
end
47+
elseif lcon[i] < lconi2 && uconi2 < ucon[i]
48+
kept_rows[i] = false
49+
row_cnt[i] = -1
50+
for j in rowi.nzind
51+
kept_cols[j] || continue
52+
col_cnt[j] -= 1
53+
end
54+
push!(operations, EmptyRow{T, S}(i))
55+
elseif lcon[i] < lconi2
56+
lcon[i] = -T(Inf)
57+
elseif uconi2 < ucon[i]
58+
ucon[i] = T(Inf)
59+
end
60+
end
61+
return false
62+
end

src/presolve/remove_ifix.jl

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
struct RemoveIfix{T, S} <: PresolveOperation{T, S}
22
j::Int
33
xj::T
4+
cj::T
5+
acolj::Col{T}
6+
hcolj::Col{T}
47
end
58

69
# ̃xᵀ̃Hx̃ + ̃ĉᵀx̃ + lⱼ²Hⱼⱼ + cⱼxⱼ + c₀
@@ -41,23 +44,42 @@ function remove_ifix!(
4144
end
4245

4346
# remove ifix in A cols
44-
for k = 1:length(acols[j].nzind)
45-
i = acols[j].nzind[k]
47+
c_acolj = 0
48+
for (i, aij) in zip(acols[j].nzind, acols[j].nzval)
4649
if kept_rows[i]
4750
row_cnt[i] -= 1
48-
con_offset = acols[j].nzval[k] * xj
51+
con_offset = aij * xj
4952
lcon[i] -= con_offset
5053
ucon[i] -= con_offset
54+
c_acolj += 1 # count number of rows in acols[j]
5155
end
5256
end
5357

58+
# store acolj for postsolve
59+
acolj = Col(zeros(Int, c_acolj), zeros(T, c_acolj))
60+
c_acolj = 1
61+
for k in 1:length(acols[j].nzind)
62+
i = acols[j].nzind[k]
63+
kept_rows[i] || continue
64+
aij = acols[j].nzval[k]
65+
acolj.nzind[c_acolj] = i
66+
acolj.nzval[c_acolj] = aij
67+
c_acolj += 1
68+
end
69+
70+
# store hcolj for postsolve
71+
hcolj = Col{T}(
72+
[i for i in hcols[j].nzind if kept_cols[i]],
73+
[hij for (i, hij) in zip(hcols[j].nzind, hcols[j].nzval) if kept_cols[i]],
74+
)
75+
5476
# update c0 with c[j] coeff
5577
c0_offset += c[j] * xj
5678
xps[j] = xj
5779
kept_cols[j] = false
5880
col_cnt[j] = -1
5981

60-
push!(operations, RemoveIfix{T, S}(j, xj))
82+
push!(operations, RemoveIfix{T, S}(j, xj, c[j], acolj, hcolj))
6183
end
6284
# update c0
6385
c0ps = c0 + c0_offset
@@ -66,5 +88,14 @@ function remove_ifix!(
6688
end
6789

6890
function postsolve!(pt::OutputPoint{T, S}, operation::RemoveIfix{T, S}) where {T, S}
69-
pt.x[operation.j] = operation.xj
91+
j = operation.j
92+
pt.x[j] = operation.xj
93+
ATyj = @views dot(operation.acolj.nzval, pt.y[operation.acolj.nzind])
94+
Hxj = @views dot(operation.hcolj.nzval, pt.x[operation.hcolj.nzind])
95+
s = operation.cj + Hxj - ATyj
96+
if s > zero(T)
97+
pt.s_l[j] = s
98+
else
99+
pt.s_u[j] = -s
100+
end
70101
end

src/presolve/singleton_rows.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ function singleton_rows!(
3737
end
3838
Ax = rowi.nzval[k]
3939

40-
col_cnt[j] -= 1
4140
if Ax > zero(T)
4241
lvar2 = lcon[i] / Ax
4342
uvar2 = ucon[i] / Ax
@@ -47,32 +46,33 @@ function singleton_rows!(
4746
else
4847
error("remove explicit zeros in A")
4948
end
50-
if lvar[j] < lvar2
49+
if lvar[j] lvar2
5150
lvar[j] = lvar2
5251
tightened_lvar = true
5352
end
54-
if uvar[j] > uvar2
53+
if uvar[j] uvar2
5554
uvar[j] = uvar2
5655
tightened_uvar = true
5756
end
5857

5958
row_cnt[i] = -1
6059
kept_rows[i] = false
60+
col_cnt[j] -= 1
6161
push!(operations, SingletonRow{T, S}(i, j, Ax, tightened_lvar, tightened_uvar))
6262
end
6363
return singl_row_pass
6464
end
6565

6666
function postsolve!(pt::OutputPoint{T, S}, operation::SingletonRow{T, S}) where {T, S}
6767
i, j = operation.i, operation.j
68-
dual_slack = -pt.s_l[j] + pt.s_u[j]
6968
Aij = operation.Aij
70-
if operation.tightened_lvar
71-
pt.y[i] = dual_slack / Aij
69+
pt.y[i] = zero(T)
70+
if operation.tightened_lvar
71+
pt.y[i] += pt.s_l[j] / Aij
7272
pt.s_l[j] = zero(T)
7373
end
7474
if operation.tightened_uvar
75-
pt.y[i] = dual_slack / Aij
75+
pt.y[i] -= pt.s_u[j] / Aij
7676
pt.s_u[j] = zero(T)
7777
end
7878
if !operation.tightened_lvar && !operation.tightened_uvar

test/test_presolve.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
SparseMatrixCOO(tril(H)),
1919
A = SparseMatrixCOO(A),
2020
lcon = [-3.0; -4.0],
21-
ucon = [-2.0; Inf],
21+
ucon = [20.0; Inf],
2222
lvar = l,
2323
uvar = u,
2424
c0 = 0.0,
@@ -83,7 +83,7 @@ end
8383
0.0 2.0 1.0
8484
]
8585
b = [0.0; 0.0; 0.0; 4.0; 0.0; 3]
86-
l = [0.0; 0.0; 0]
86+
l = [-2.0; -4.0; 0]
8787
u = [Inf; 2.0; Inf]
8888
T = eltype(c)
8989
qp = QuadraticModel(
@@ -170,7 +170,7 @@ end
170170
s_l = sparse([3.0; 4.0; 2.0])
171171
s_u = sparse([0.0; 3.0; 0.0])
172172
pt_out = postsolve(qp, psqp, x_in, y_in, s_l, s_u)
173-
@test pt_out.y == [2.0; 0.0; 0.0; 0.0; 0.0; 4.0]
173+
@test pt_out.y == [2.0; 0.0; 0.0; 1.0; 0.0; 4.0]
174174
end
175175

176176
@testset "presolve singleton rows and ifix" begin

0 commit comments

Comments
 (0)