Skip to content

Commit ed73618

Browse files
add docs, fix issues ifix
1 parent 3464969 commit ed73618

File tree

7 files changed

+134
-81
lines changed

7 files changed

+134
-81
lines changed

src/QuadraticModels.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import NLPModelsModifiers: SlackModel, slack_meta
3030

3131
import Base.convert
3232

33-
export AbstractQuadraticModel, QuadraticModel, presolve, postsolve!
33+
export AbstractQuadraticModel, QuadraticModel, presolve, postsolve, postsolve!
3434

3535
include("linalg_utils.jl")
3636
include("qpmodel.jl")

src/presolve/empty_rows.jl

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@ function row_cnt!(Arows, row_cnt::Vector{Int})
55
end
66
end
77

8-
removed_empty_rows(row_cnt::Vector{Int}) = findall(isequal(0), row_cnt)
8+
find_empty_rows(row_cnt::Vector{Int}) = findall(isequal(0), row_cnt)
99

10+
"""
11+
new_ncon = empty_rows!(Arows, lcon, ucon, ncon, row_cnt, empty_rows, Arows_s)
12+
13+
Removes the empty rows of A, and the corresponding elements in lcon and ucon that are in `empty_rows`.
14+
`Arows_s` is a view of `Arows` sorted in ascending order.
15+
`row_cnt` is a vector of the number of elements per row.
16+
17+
Returns the new number of constraints `new_ncon` and updates in-place `Arows`, `lcon`, `ucon`.
18+
"""
1019
function empty_rows!(Arows, lcon::Vector{T}, ucon::Vector{T}, ncon, row_cnt::Vector{Int},
11-
rows_rm::Vector{Int}, Arows_s) where {T}
20+
empty_rows::Vector{Int}, Arows_s) where {T}
1221
new_ncon = 0
1322
for i=1:ncon
1423
if row_cnt[i] == 0
@@ -23,24 +32,12 @@ function empty_rows!(Arows, lcon::Vector{T}, ucon::Vector{T}, ncon, row_cnt::Vec
2332
resize!(ucon, new_ncon)
2433

2534
c_rm = 1
26-
nrm = length(rows_rm)
35+
nrm = length(empty_rows)
2736
for k=1:length(Arows)
28-
while c_rm nrm && Arows_s[k] rows_rm[c_rm]
37+
while c_rm nrm && Arows_s[k] empty_rows[c_rm]
2938
c_rm += 1
3039
end
3140
Arows_s[k] -= c_rm - 1
3241
end
3342
return new_ncon
3443
end
35-
36-
function restore_y!(y::Vector{T}, yout::Vector{T}, row_cnt, ncon) where {T}
37-
c_y = 0
38-
for i = 1:ncon
39-
if row_cnt[i] == 0 || row_cnt[i] == 1
40-
yout[i] = zero(T)
41-
else
42-
c_y += 1
43-
yout[i] = y[c_y]
44-
end
45-
end
46-
end

src/presolve/postsolve_utils.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
function restore_ifix!(ifix, xrm, x, xout)
2+
# put x and xrm inside xout
3+
cfix, cx = 1, 1
4+
nfix = length(ifix)
5+
for i = 1:length(xout)
6+
if cfix <= nfix && i == ifix[cfix]
7+
xout[i] = xrm[cfix]
8+
cfix += 1
9+
else
10+
xout[i] = x[cx]
11+
cx += 1
12+
end
13+
end
14+
end
15+
16+
function restore_y!(y::Vector{T}, yout::Vector{T}, kept_rows::Vector{Bool}, ncon) where {T}
17+
c_y = 0
18+
for i = 1:ncon
19+
if !kept_rows[i]
20+
yout[i] = zero(T)
21+
else
22+
c_y += 1
23+
yout[i] = y[c_y]
24+
end
25+
end
26+
end
27+
128
function restore_ilow_iupp!(ilow, iupp, ifix)
229
c_fix = 1
330
nfix = length(ifix)

src/presolve/presolve.jl

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ include("postsolve_utils.jl")
66
mutable struct PresolvedData{T, S}
77
ifix::Vector{Int}
88
xrm::S
9-
row_cnt::Vector{Int}
9+
kept_rows::Vector{Bool}
1010
nconps::Int
1111
end
1212

@@ -17,6 +17,23 @@ mutable struct PresolvedQuadraticModel{T, S, M1, M2} <: AbstractQuadraticModel{T
1717
psd::PresolvedData{T, S}
1818
end
1919

20+
function update_kept_rows!(kept_rows::Vector{Bool}, vec_to_rm::Vector{Int})
21+
ncon = length(kept_rows)
22+
n_rm = length(vec_to_rm)
23+
offset = 0
24+
c_v = 1
25+
for i=1:ncon
26+
if !kept_rows[i]
27+
offset += 1
28+
else
29+
if c_v n_rm && vec_to_rm[c_v] + offset == i
30+
kept_rows[i] = false
31+
c_v += 1
32+
end
33+
end
34+
end
35+
end
36+
2037
"""
2138
stats_ps = presolve(qm::QuadraticModel{T, S}; kwargs...)
2239
@@ -25,13 +42,15 @@ Apply a presolve routine to `qm` and returns a
2542
from the package [`SolverCore.jl`](https://github.com/JuliaSmoothOptimizers/SolverCore.jl).
2643
The presolve operations currently implemented are:
2744
45+
- [`empty_rows!`](@ref) : remove empty rows
46+
- [`singleton_rows!`](@ref) : remove singleton rows
2847
- [`remove_ifix!`](@ref) : remove fixed variables
2948
3049
The `PresolvedQuadraticModel{T, S} <: AbstractQuadraticModel{T, S}` is located in the `solver_specific` field:
3150
3251
psqm = stats_ps.solver_specific[:presolvedQM]
3352
34-
and should be used to call [`postsolve!`](@ref).
53+
and should be used to call [`postsolve`](@ref).
3554
3655
If the presolved problem has 0 variables, `stats_ps.solution` contains a solution of the primal problem,
3756
`stats_ps.multipliers` is a zero `SparseVector`, and, if we define
@@ -50,31 +69,39 @@ function presolve(
5069
lvar, uvar = psqm.meta.lvar, psqm.meta.uvar
5170
lcon, ucon = psqm.meta.lcon, psqm.meta.ucon
5271
nvar, ncon = psqm.meta.nvar, psqm.meta.ncon
72+
# copy if same vector
73+
lcon === ucon && (lcon = copy(lcon))
74+
lvar === uvar && (lvar = copy(lvar))
5375

5476
# empty rows
5577
row_cnt = zeros(Int, ncon)
78+
kept_rows = fill(true, ncon)
5679
row_cnt!(psdata.A.rows, row_cnt) # number of coefficients per row
57-
rows_rm = removed_empty_rows(row_cnt) # indices of the empty rows
58-
if length(rows_rm) > 0
80+
empty_rows = find_empty_rows(row_cnt) # indices of the empty rows
81+
if length(empty_rows) > 0
82+
empty_row_pass = true
83+
update_kept_rows!(kept_rows, empty_rows)
5984
Arows_sortperm = sortperm(psdata.A.rows) # permute rows
6085
Arows_s = @views psdata.A.rows[Arows_sortperm]
61-
nconps = empty_rows!(psdata.A.rows, lcon, ucon, ncon, row_cnt, rows_rm, Arows_s)
86+
nconps = empty_rows!(psdata.A.rows, lcon, ucon, ncon, row_cnt, empty_rows, Arows_s)
6287
else
88+
empty_row_pass = false
6389
nconps = ncon
6490
end
6591

6692
# remove singleton rows
67-
if nconps != ncon
68-
row_cnt2 = Vector{Int}(undef, nconps)
69-
else
70-
row_cnt2 = row_cnt
93+
if empty_row_pass
94+
resize!(row_cnt, nconps)
95+
row_cnt .= 0
96+
row_cnt!(psdata.A.rows, row_cnt) # number of coefficients per rows
7197
end
72-
row_cnt2 .= 0
73-
row_cnt!(psdata.A.rows, row_cnt2) # number of coefficients per rows
74-
singl_rows = removed_singleton_rows(row_cnt2) # indices of the empty rows
98+
singl_rows = find_singleton_rows(row_cnt) # indices of the singleton rows
7599
if length(singl_rows) > 0
76-
nconps = singleton_rows!(psdata.A.rows, psdata.A.cols, psdata.A.vals, lcon, ucon, lvar, uvar, nvar, nconps, row_cnt2, singl_rows)
100+
singl_row_pass = true
101+
update_kept_rows!(kept_rows, singl_rows)
102+
nconps = singleton_rows!(psdata.A.rows, psdata.A.cols, psdata.A.vals, lcon, ucon, lvar, uvar, nvar, nconps, row_cnt, singl_rows)
77103
else
104+
singl_row_pass = false
78105
nconps = nconps
79106
end
80107

@@ -146,7 +173,7 @@ function presolve(
146173
minimize = qm.meta.minimize,
147174
kwargs...,
148175
)
149-
psd = PresolvedData{T, S}(ifix, xrm, row_cnt, nconps)
176+
psd = PresolvedData{T, S}(ifix, xrm, kept_rows, nconps)
150177
ps = PresolvedQuadraticModel(psmeta, Counters(), psdata, psd)
151178
return GenericExecutionStats(
152179
:unknown,
@@ -158,13 +185,6 @@ function presolve(
158185
end
159186
end
160187

161-
"""
162-
postsolve!(qm::QuadraticModel{T, S}, psqm::PresolvedQuadraticModel{T, S},
163-
x_in::S, x_out::S) where {T, S}
164-
165-
Retrieve the solution `x_out` of the original QP `qm` given the solution of the presolved QP (`psqm`)
166-
`x_in`.
167-
"""
168188
function postsolve!(
169189
qm::QuadraticModel{T, S},
170190
psqm::PresolvedQuadraticModel{T, S},
@@ -182,11 +202,34 @@ function postsolve!(
182202
x_out .= @views x_in[1:(qm.meta.nvar)]
183203
end
184204
ncon = length(y_out)
185-
restore_y!(y_in, y_out, psqm.psd.row_cnt, ncon)
205+
restore_y!(y_in, y_out, psqm.psd.kept_rows, ncon)
186206

187207
ilow, iupp = s_l.nzind, s_u.nzind
188208
restore_ilow_iupp!(ilow, iupp, ifix)
189209
s_l_out = SparseVector(qm.meta.nvar, ilow, s_l.nzval)
190210
s_u_out = SparseVector(qm.meta.nvar, iupp, s_u.nzval)
191211
return s_l_out, s_u_out
192212
end
213+
214+
"""
215+
x, y, s_l, s_u = postsolve(qm::QuadraticModel{T, S}, psqm::PresolvedQuadraticModel{T, S},
216+
x_in::S, y_in::S,
217+
s_l_in::SparseVector{T, Int},
218+
s_u_in::SparseVector{T, Int}) where {T, S}
219+
220+
Retrieve the solution `x, y, s_l, s_u` of the original QP `qm` given the solution of the presolved QP (`psqm`)
221+
`x_in, y_in, s_l_in, s_u_in`.
222+
"""
223+
function postsolve(
224+
qm::QuadraticModel{T, S},
225+
psqm::PresolvedQuadraticModel{T, S},
226+
x_in::S,
227+
y_in::S,
228+
s_l::SparseVector{T, Int},
229+
s_u::SparseVector{T, Int},
230+
) where {T, S}
231+
x_out = similar(qm.meta.x0)
232+
y_out = similar(qm.meta.y0)
233+
s_l_out, s_u_out = postsolve!(qm, psqm, x_in, x_out, y_in, y_out, s_l, s_u)
234+
return x_out, y_out, s_l_out, s_u_out
235+
end

src/presolve/remove_ifix.jl

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@ function remove_ifix!(
9595
Ai, Aj, Ax = Arows[k], Acols[k], Avals[k]
9696
if Aj == newcurrentifix
9797
Arm_tmp += 1
98-
lcon[Ai] -= Ax * xifix
99-
ucon[Ai] -= Ax * xifix
98+
con_offset = Ax * xifix
99+
lcon[Ai] -= con_offset
100+
ucon[Ai] -= con_offset
100101
else
101102
Arows[Awritepos] = Ai
102103
Acols[Awritepos] = (Aj < newcurrentifix) ? Aj : Aj - 1
@@ -138,18 +139,3 @@ function remove_ifix!(
138139

139140
return xrm, c0ps, nvarrm
140141
end
141-
142-
function restore_ifix!(ifix, xrm, x, xout)
143-
# put x and xrm inside xout
144-
cfix, cx = 1, 1
145-
nfix = length(ifix)
146-
for i = 1:length(xout)
147-
if cfix <= nfix && i == ifix[cfix]
148-
xout[i] = xrm[cfix]
149-
cfix += 1
150-
else
151-
xout[i] = x[cx]
152-
cx += 1
153-
end
154-
end
155-
end

src/presolve/singleton_rows.jl

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1-
removed_singleton_rows(row_cnt::Vector{Int}) = findall(isequal(1), row_cnt)
1+
find_singleton_rows(row_cnt::Vector{Int}) = findall(isequal(1), row_cnt)
22

3+
"""
4+
new_ncon = singleton_rows!(Arows, Acols, Avals, lcon, ucon,
5+
lvar, uvar, nvar, ncon, row_cnt, singl_rows)
6+
7+
Removes the singleton rows of A, and the corresponding elements in lcon and ucon that are in `singl_rows`.
8+
`row_cnt` is a vector of the number of elements per row.
9+
10+
Returns the new number of constraints `new_ncon` and updates in-place `Arows`, `Acols`, `Avals`, `lcon`, `ucon`, `lvar`, `uvar`.
11+
"""
312
function singleton_rows!(Arows, Acols, Avals, lcon::Vector{T}, ucon::Vector{T}, lvar, uvar,
413
nvar, ncon, row_cnt::Vector{Int},
514
singl_rows::Vector{Int}) where {T}
@@ -21,7 +30,6 @@ function singleton_rows!(Arows, Acols, Avals, lcon::Vector{T}, ucon::Vector{T},
2130
while k <= Annz - idxsingl + 1
2231
Ai, Aj, Ax = Arows[k], Acols[k], Avals[k]
2332
if Ai == newcurrentisingl
24-
# oldAi = Ai + idxsingl - 1
2533
if Ax > zero(T)
2634
lvar[Aj] = max(lvar[Aj], lcon[currentisingl] / Ax)
2735
uvar[Aj] = min(uvar[Aj], ucon[currentisingl] / Ax)
@@ -48,15 +56,7 @@ function singleton_rows!(Arows, Acols, Avals, lcon::Vector{T}, ucon::Vector{T},
4856
resize!(Avals, Annz)
4957
end
5058

51-
new_ncon = 0
52-
for i=1:ncon
53-
if row_cnt[i] != 1
54-
new_ncon += 1
55-
lcon[new_ncon] = lcon[i]
56-
ucon[new_ncon] = ucon[i]
57-
end
58-
end
59-
resize!(lcon, new_ncon)
60-
resize!(ucon, new_ncon)
61-
return new_ncon
59+
deleteat!(lcon, singl_rows)
60+
deleteat!(ucon, singl_rows)
61+
return ncon - nsingl
6262
end

test/test_presolve.jl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@
5353
@test psqp.meta.nvar == 2
5454

5555
x_in = [4.0; 7.0]
56-
x_out = zeros(3)
5756
y_in = [2.0; 2.0]
58-
y_out = zeros(2)
59-
postsolve!(qp, psqp, x_in, x_out, y_in, y_out)
57+
s_l = sparse([3.0; 2.0])
58+
s_u = sparse([0.0; 0.0])
59+
x_out, y_out, s_l, s_u = postsolve(qp, psqp, x_in, y_in, s_l, s_u)
6060
@test x_out == [4.0; 2.0; 7.0]
6161

6262
# test that solves the problem
@@ -118,10 +118,10 @@ end
118118
@test psqp.meta.ncon == 3
119119

120120
x_in = [4.0; 7.0; 4.0]
121-
x_out = zeros(3)
122121
y_in = [2.0; 2.0; 4.0]
123-
y_out = zeros(6)
124-
postsolve!(qp, psqp, x_in, x_out, y_in, y_out)
122+
s_l = sparse([3.0; 4.0; 2.0])
123+
s_u = sparse([0.0; 3.0; 0.0])
124+
x_out, y_out, s_l, s_u = postsolve(qp, psqp, x_in, y_in, s_l, s_u)
125125
@test y_out == [2.0; 0.0; 0.0; 2.0; 0.0; 4.0]
126126
end
127127

@@ -170,10 +170,10 @@ end
170170
@test psqp.meta.ncon == 2
171171

172172
x_in = [4.0; 7.0; 4.0]
173-
x_out = zeros(3)
174173
y_in = [2.0; 4.0]
175-
y_out = zeros(6)
176-
postsolve!(qp, psqp, x_in, x_out, y_in, y_out)
174+
s_l = sparse([3.0; 4.0; 2.0])
175+
s_u = sparse([0.0; 3.0; 0.0])
176+
x_out, y_out, s_l, s_u = postsolve(qp, psqp, x_in, y_in, s_l, s_u)
177177
@test y_out == [2.0; 0.0; 0.0; 0.0; 0.0; 4.0]
178178
end
179179

@@ -221,10 +221,10 @@ end
221221
@test psqp.meta.ncon == 2
222222

223223
x_in = [7.0; 4.0]
224-
x_out = zeros(3)
225224
y_in = [2.0; 4.0]
226-
y_out = zeros(6)
227-
postsolve!(qp, psqp, x_in, x_out, y_in, y_out)
225+
s_l = sparse([4.0; 2.0])
226+
s_u = sparse([3.0; 0.0])
227+
x_out, y_out, s_l, s_u = postsolve(qp, psqp, x_in, y_in, s_l, s_u)
228228
@test y_out == [2.0; 0.0; 0.0; 0.0; 0.0; 4.0]
229229
@test x_out == [4.0/3.0; 7.0; 4.0]
230230
end

0 commit comments

Comments
 (0)