Skip to content

Commit 3302e98

Browse files
Feasibility presolve (#118)
* feasibility presolve * primal cstr update
1 parent e737a24 commit 3302e98

File tree

6 files changed

+167
-41
lines changed

6 files changed

+167
-41
lines changed

src/presolve/empty_rows.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ end
2020

2121
function postsolve!(sol::QMSolution, operation::EmptyRow, psd::PresolvedData)
2222
psd.kept_rows[operation.i] = true
23+
sol.y[operation.i] = 0
2324
end

src/presolve/postsolve_utils.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
function restore_x!(kept_cols, x_in::S, x::S, nvar) where {S}
2-
# put x and xps inside xout according to kept_cols
2+
# put x_in inside x according to kept_cols
33
cx = 0
44
for i = 1:nvar
55
if kept_cols[i]
@@ -66,3 +66,16 @@ function restore_s!(
6666
restore_x!(kept_cols, s_l_in, s_l, nvar)
6767
restore_x!(kept_cols, s_u_in, s_u, nvar)
6868
end
69+
70+
function add_Hx!(z::Vector{T}, hcols::Vector{Col{T}}, kept_cols::Vector{Bool}, x::Vector{T}) where {T}
71+
for j in 1:length(hcols)
72+
hcolj = hcols[j]
73+
Hxj = zero(T)
74+
for (i, hij) in zip(hcolj.nzind, hcolj.nzval)
75+
kept_cols[i] || continue
76+
Hxj += hij * x[i]
77+
end
78+
z[j] += Hxj
79+
end
80+
return z
81+
end

src/presolve/presolve.jl

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ end
113113

114114
mutable struct PresolvedData{T, S}
115115
xps::S
116+
c::S # copy of c
117+
z::S # c + Qx for postsolve PrimalConstraints
116118
arows::Vector{Row{T}}
117119
acols::Vector{Col{T}}
118120
hcols::Vector{Col{T}}
@@ -289,6 +291,26 @@ function presolve(
289291
psdata.c0 = qmp.c0
290292
end
291293

294+
# c_ps is the new c padded with zeros to have the same size as the init c
295+
c_ps = fill!(S(undef, nvar), zero(T))
296+
restore_x!(qmp.kept_cols, qmp.c, c_ps, nvar)
297+
# group all presolve info into a struct
298+
psd = PresolvedData{T, S}(
299+
qmp.xps,
300+
c_ps,
301+
copy(c_ps),
302+
qmp.arows,
303+
qmp.acols,
304+
qmp.hcols,
305+
qmp.kept_rows,
306+
qmp.kept_cols,
307+
nvarps,
308+
nconps,
309+
nvar,
310+
ncon,
311+
operations,
312+
)
313+
292314
# form meta
293315
nnzh = length(psdata.H.vals)
294316
if !(nnzh == length(psdata.H.rows) == length(psdata.H.cols))
@@ -318,16 +340,20 @@ function presolve(
318340
solver_specific = Dict(:presolvedQM => nothing),
319341
)
320342
elseif nvarps == 0
343+
sol_in = QMSolution(S(undef, 0), fill!(S(undef, nconps), zero(T)), S(undef, 0), S(undef, 0))
344+
sol = postsolve(psd, sol_in)
321345
feasible = all(qm.meta.lcon .<= qm.data.A * qmp.xps .<= qm.meta.ucon)
322-
s = qm.data.c .+ Symmetric(qm.data.H, :L) * qmp.xps
346+
# s = qm.data.c .+ Symmetric(qm.data.H, :L) * qmp.xps
323347
return GenericExecutionStats(
324348
qm,
325349
status = feasible ? :first_order : :infeasible,
326-
solution = qmp.xps,
327-
objective = obj(qm, qmp.xps),
328-
multipliers = zeros(T, ncon),
329-
multipliers_L = max.(s, zero(T)),
330-
multipliers_U = max.(.-s, zero(T)),
350+
solution = sol.x,
351+
objective = obj(qm, sol.x),
352+
primal_feas = feasible ? zero(T) : T(Inf),
353+
dual_feas = feasible ? zero(T) : T(Inf),
354+
multipliers = sol.y,
355+
multipliers_L = sol.s_l,
356+
multipliers_U = sol.s_u,
331357
iter = 0,
332358
elapsed_time = time() - start_time,
333359
solver_specific = Dict(:presolvedQM => nothing, :psoperations => operations),
@@ -349,19 +375,6 @@ function presolve(
349375
minimize = qm.meta.minimize,
350376
kwargs...,
351377
)
352-
psd = PresolvedData{T, S}(
353-
qmp.xps,
354-
qmp.arows,
355-
qmp.acols,
356-
qmp.hcols,
357-
qmp.kept_rows,
358-
qmp.kept_cols,
359-
nvarps,
360-
nconps,
361-
nvar,
362-
ncon,
363-
operations,
364-
)
365378
ps = PresolvedQuadraticModel(psmeta, Counters(), psdata, psd)
366379
return GenericExecutionStats(
367380
ps,
@@ -374,13 +387,11 @@ function presolve(
374387
end
375388

376389
function postsolve!(
377-
qm::QuadraticModel{T, S},
378-
psqm::PresolvedQuadraticModel{T, S},
390+
psd::PresolvedData{T, S},
379391
sol::QMSolution{S},
380392
sol_in::QMSolution{S},
381393
) where {T, S}
382394
x_in, y_in, s_l_in, s_u_in = sol_in.x, sol_in.y, sol_in.s_l, sol_in.s_u
383-
psd = psqm.psd
384395
n_operations = length(psd.operations)
385396
nvar = psd.nvar
386397
@assert nvar == length(sol.x)
@@ -389,13 +400,34 @@ function postsolve!(
389400
@assert ncon == length(sol.y)
390401
restore_y!(psd.kept_rows, y_in, sol.y, ncon)
391402
restore_s!(sol.s_l, sol.s_u, s_l_in, s_u_in, psd.kept_cols)
403+
# add_Hx!(psd.z, psd.hcols, psd.kept_cols) # z = c + Hx
392404

393405
for i = n_operations:-1:1
394406
operation_i = psd.operations[i]
395407
postsolve!(sol, operation_i, psd)
396408
end
397409
end
398410

411+
postsolve!(
412+
psqm::PresolvedQuadraticModel{T, S},
413+
sol::QMSolution{S},
414+
sol_in::QMSolution{S},
415+
) where {T, S} = postsolve!(psqm.psd, sol, sol_in)
416+
417+
function postsolve(
418+
psd::PresolvedData{T, S},
419+
sol_in::QMSolution{S},
420+
) where {T, S}
421+
x = fill!(S(undef, psd.nvar), zero(T))
422+
y = fill!(S(undef, psd.ncon), zero(T))
423+
s_l = fill!(S(undef, psd.nvar), zero(T))
424+
s_u = fill!(S(undef, psd.nvar), zero(T))
425+
426+
sol = QMSolution(x, y, s_l, s_u)
427+
postsolve!(psd, sol, sol_in)
428+
return sol
429+
end
430+
399431
"""
400432
sol = postsolve(qm::QuadraticModel{T, S}, psqm::PresolvedQuadraticModel{T, S},
401433
sol_in::QMSolution{S}) where {T, S}
@@ -404,17 +436,8 @@ Retrieve the solution `sol = (x, y, s_l, s_u)` of the original QP `qm` given the
404436
`sol_in` of type [`QMSolution`](@ref).
405437
`sol_in.s_l` and `sol_in.s_u` can be sparse or dense vectors, but the output `sol.s_l` and `sol.s_u` are dense vectors.
406438
"""
407-
function postsolve(
439+
postsolve(
408440
qm::QuadraticModel{T, S},
409441
psqm::PresolvedQuadraticModel{T, S},
410442
sol_in::QMSolution{S},
411-
) where {T, S}
412-
x = fill!(S(undef, psqm.psd.nvar), zero(T))
413-
y = fill!(S(undef, psqm.psd.ncon), zero(T))
414-
s_l = fill!(S(undef, psqm.psd.nvar), zero(T))
415-
s_u = fill!(S(undef, psqm.psd.nvar), zero(T))
416-
417-
sol = QMSolution(x, y, s_l, s_u)
418-
postsolve!(qm, psqm, sol, sol_in)
419-
return sol
420-
end
443+
) where {T, S} = postsolve(psqm.psd, sol_in)

src/presolve/primal_constraints.jl

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
struct PrimalConstraint{T, S} <: PresolveOperation{T, S}
2+
i::Int
3+
forced_lcon::Bool # row i forced to lcon[i]
4+
end
5+
16
function primal_constraints!(
27
qmp::QuadraticModelPresolveData{T, S},
38
operations::Vector{PresolveOperation{T, S}},
@@ -28,6 +33,7 @@ function primal_constraints!(
2833
uvar[j] = lvar[j]
2934
end
3035
end
36+
push!(operations, PrimalConstraint{T, S}(i, true))
3137
elseif lconi2 == ucon[i]
3238
for (j, aij) in zip(rowi.nzind, rowi.nzval)
3339
kept_cols[j] || continue
@@ -37,6 +43,7 @@ function primal_constraints!(
3743
lvar[j] = uvar[j]
3844
end
3945
end
46+
push!(operations, PrimalConstraint{T, S}(i, false))
4047
elseif lcon[i] < lconi2 && uconi2 < ucon[i]
4148
kept_rows[i] = false
4249
row_cnt[i] = -1
@@ -52,3 +59,55 @@ function primal_constraints!(
5259
end
5360
end
5461
end
62+
63+
function postsolve!(
64+
sol::QMSolution,
65+
operation::PrimalConstraint{T, S},
66+
psd::PresolvedData{T, S},
67+
) where {T, S}
68+
i = operation.i
69+
forced_lcon = operation.forced_lcon
70+
x = sol.x
71+
y = sol.y
72+
s_l = sol.s_l
73+
s_u = sol.s_u
74+
arowi = psd.arows[i]
75+
acols = psd.acols
76+
kept_cols = psd.kept_cols
77+
z = psd.z
78+
z .= psd.c
79+
n = length(z)
80+
81+
add_Hx!(z, psd.hcols, kept_cols, x) # z = c + Hx
82+
for l = 1:n
83+
kept_cols[l] || continue
84+
for (k, akl) in zip(acols[l].nzind, acols[l].nzval)
85+
(psd.kept_rows[i] && k != i) || continue
86+
z[l] -= akl * y[k]
87+
end
88+
end
89+
90+
if forced_lcon
91+
yi = T(-Inf)
92+
for (l, ail) in zip(arowi.nzind, arowi.nzval)
93+
(kept_cols[l]) || continue
94+
trial = z[l] / ail
95+
(trial > yi) && (yi = trial)
96+
end
97+
else
98+
yi = T(Inf)
99+
for (l, ail) in zip(arowi.nzind, arowi.nzval)
100+
(kept_cols[l]) || continue
101+
trial = z[l] / ail
102+
(trial < yi) && (yi = trial)
103+
end
104+
end
105+
y[i] = yi
106+
107+
for (l, ail) in zip(arowi.nzind, arowi.nzval)
108+
(kept_cols[l]) || continue
109+
s = z[l] - ail * yi
110+
s_l[l] = s > 0 ? s : zero(T)
111+
s_u[l] = s < 0 ? -s : zero(T)
112+
end
113+
end

src/presolve/remove_ifix.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ function postsolve!(
6868
hcolj = psd.hcols[j]
6969
x = sol.x
7070
x[j] = operation.xj
71+
cj = operation.cj
7172
y = sol.y
73+
c = psd.c
74+
c[j] = cj
7275

7376
ATyj = zero(T)
7477
for (i, aij) in zip(acolj.nzind, acolj.nzval)
@@ -80,12 +83,10 @@ function postsolve!(
8083
for (i, hij) in zip(hcolj.nzind, hcolj.nzval)
8184
psd.kept_cols[i] || continue
8285
Hxj += hij * x[i]
86+
(i != j) && (c[i] -= hij * x[j])
8387
end
8488

85-
s = operation.cj + Hxj - ATyj
86-
if s > zero(T)
87-
sol.s_l[j] = s
88-
else
89-
sol.s_u[j] = -s
90-
end
89+
s = cj + Hxj - ATyj
90+
sol.s_l[j] = s > zero(T) ? s : zero(T)
91+
sol.s_u[j] = s < zero(T) ? -s : zero(T)
9192
end

test/test_presolve.jl

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,36 @@ end
165165
@test sol.y == [2.0; 0.0; 0.0; 1.0; 0.0; 4.0]
166166
end
167167

168+
@testset "presolve solves problem" begin
169+
Q = [6. 2. 1.
170+
2. 5. 2.
171+
1. 2. 4.]
172+
c = [-8.; -3; -3]
173+
A = [1. 0. 1.
174+
0. 2. 1.]
175+
b = [0.; 3]
176+
l = [0.;0;0]
177+
u = [Inf; Inf; Inf]
178+
qp = QuadraticModel(
179+
c,
180+
SparseMatrixCOO(tril(Q)),
181+
A=SparseMatrixCOO(A),
182+
lcon=b,
183+
ucon=b,
184+
lvar=l,
185+
uvar=u,
186+
c0=0.,
187+
name="QM"
188+
)
189+
statsps = presolve(qp)
190+
psqp = statsps.solver_specific[:presolvedQM]
191+
atol = eps()
192+
@test isapprox(statsps.solution, [0.0; 1.5; 0.0], atol = atol)
193+
@test isapprox(statsps.objective, 1.125, atol = atol)
194+
@test isapprox(statsps.multipliers_L[2], 0.0, atol = atol)
195+
@test isapprox(statsps.multipliers_U, [0.0; 0.0; 0.0], atol = atol)
196+
end
197+
168198
@testset "presolve singleton rows and ifix" begin
169199
H = [
170200
6.0 2.0 1.0
@@ -201,7 +231,6 @@ end
201231

202232
@test statsps.status == :first_order
203233
@test statsps.solution [4.0 / 3.0; 7.0 / 6.0; 2.0 / 3.0]
204-
@test statsps.multipliers == zeros(length(b))
205234
end
206235

207236
@testset "presolve free col singleton" begin

0 commit comments

Comments
 (0)