Skip to content

Commit a211d7a

Browse files
empty and singleton rows
1 parent 5369c94 commit a211d7a

File tree

5 files changed

+209
-24
lines changed

5 files changed

+209
-24
lines changed

src/presolve/empty_rows.jl

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

8-
function removed_rows(row_cnt::Vector{Int})
9-
return findall(isequal(0), row_cnt)
10-
end
8+
removed_empty_rows(row_cnt::Vector{Int}) = findall(isequal(0), row_cnt)
119

1210
function empty_rows!(Arows, lcon::Vector{T}, ucon::Vector{T}, ncon, row_cnt::Vector{Int},
13-
rows_rm::Vector{Int}, Arows_sortperm::Vector{Int}) where {T}
11+
rows_rm::Vector{Int}, Arows_s) where {T}
1412
new_ncon = 0
1513
for i=1:ncon
1614
if row_cnt[i] == 0
@@ -24,7 +22,6 @@ function empty_rows!(Arows, lcon::Vector{T}, ucon::Vector{T}, ncon, row_cnt::Vec
2422
resize!(lcon, new_ncon)
2523
resize!(ucon, new_ncon)
2624

27-
Arows_s = @views Arows[Arows_sortperm]
2825
c_rm = 1
2926
nrm = length(rows_rm)
3027
for k=1:length(Arows)
@@ -39,7 +36,7 @@ end
3936
function restore_y!(y::Vector{T}, yout::Vector{T}, row_cnt, ncon) where {T}
4037
c_y = 0
4138
for i = 1:ncon
42-
if row_cnt[i] == 0
39+
if row_cnt[i] == 0 || row_cnt[i] == 1
4340
yout[i] = zero(T)
4441
else
4542
c_y += 1

src/presolve/presolve.jl

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
include("remove_ifix.jl")
22
include("empty_rows.jl")
3+
include("singleton_rows.jl")
34

45
mutable struct PresolvedData{T, S}
6+
ifix::Vector{Int}
57
xrm::S
68
row_cnt::Vector{Int}
79
nconps::Int
@@ -48,7 +50,35 @@ function presolve(
4850
lcon, ucon = psqm.meta.lcon, psqm.meta.ucon
4951
nvar, ncon = psqm.meta.nvar, psqm.meta.ncon
5052

51-
ifix = qm.meta.ifix
53+
# empty rows
54+
row_cnt = zeros(Int, ncon)
55+
row_cnt!(psdata.A.rows, row_cnt) # number of coefficients per row
56+
rows_rm = removed_empty_rows(row_cnt) # indices of the empty rows
57+
if length(rows_rm) > 0
58+
Arows_sortperm = sortperm(psdata.A.rows) # permute rows
59+
Arows_s = @views psdata.A.rows[Arows_sortperm]
60+
nconps = empty_rows!(psdata.A.rows, lcon, ucon, ncon, row_cnt, rows_rm, Arows_s)
61+
else
62+
nconps = ncon
63+
end
64+
65+
# remove singleton rows
66+
if nconps != ncon
67+
row_cnt2 = Vector{Int}(undef, nconps)
68+
else
69+
row_cnt2 = row_cnt
70+
end
71+
row_cnt2 .= 0
72+
row_cnt!(psdata.A.rows, row_cnt2) # number of coefficients per rows
73+
singl_rows = removed_singleton_rows(row_cnt2) # indices of the empty rows
74+
if length(singl_rows) > 0
75+
nconps = singleton_rows!(psdata.A.rows, psdata.A.cols, psdata.A.vals, lcon, ucon, lvar, uvar, nvar, nconps, row_cnt2, singl_rows)
76+
else
77+
nconps = nconps
78+
end
79+
80+
# remove fixed variables
81+
ifix = findall(lvar .== uvar)
5282
if length(ifix) > 0
5383
xrm, psdata.c0, nvarps = remove_ifix!(
5484
ifix,
@@ -71,13 +101,6 @@ function presolve(
71101
xrm = S(undef, 0)
72102
end
73103

74-
# remove constraints
75-
row_cnt = zeros(Int, ncon)
76-
row_cnt!(psdata.A.rows, row_cnt)
77-
rows_rm = removed_rows(row_cnt)
78-
Arows_sortperm = sortperm(psdata.A.rows)
79-
nconps = empty_rows!(psdata.A.rows, lcon, ucon, ncon, row_cnt, rows_rm, Arows_sortperm)
80-
81104
# form meta
82105
nnzh = length(psdata.H.vals)
83106
if !(nnzh == length(psdata.H.rows) == length(psdata.H.cols))
@@ -122,7 +145,7 @@ function presolve(
122145
minimize = qm.meta.minimize,
123146
kwargs...,
124147
)
125-
psd = PresolvedData{T, S}(xrm, row_cnt, nconps)
148+
psd = PresolvedData{T, S}(ifix, xrm, row_cnt, nconps)
126149
ps = PresolvedQuadraticModel(psmeta, Counters(), psdata, psd)
127150
return GenericExecutionStats(
128151
:unknown,
@@ -149,8 +172,8 @@ function postsolve!(
149172
y_in::S,
150173
y_out::S,
151174
) where {T, S}
152-
if length(qm.meta.ifix) > 0
153-
restore_ifix!(qm.meta.ifix, psqm.psd.xrm, x_in, x_out)
175+
if length(psqm.psd.ifix) > 0
176+
restore_ifix!(psqm.psd.ifix, psqm.psd.xrm, x_in, x_out)
154177
else
155178
x_out .= @views x_in[1:(qm.meta.nvar)]
156179
end

src/presolve/remove_ifix.jl

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,9 @@ function remove_ifix!(
9898
lcon[Ai] -= Ax * xifix
9999
ucon[Ai] -= Ax * xifix
100100
else
101-
if Awritepos != k
102-
Arows[Awritepos] = Ai
103-
Acols[Awritepos] = (Aj < newcurrentifix) ? Aj : Aj - 1
104-
Avals[Awritepos] = Ax
105-
end
101+
Arows[Awritepos] = Ai
102+
Acols[Awritepos] = (Aj < newcurrentifix) ? Aj : Aj - 1
103+
Avals[Awritepos] = Ax
106104
Awritepos += 1
107105
end
108106
k += 1

src/presolve/singleton_rows.jl

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
removed_singleton_rows(row_cnt::Vector{Int}) = findall(isequal(1), row_cnt)
2+
3+
function singleton_rows!(Arows, Acols, Avals, lcon::Vector{T}, ucon::Vector{T}, lvar, uvar,
4+
nvar, ncon, row_cnt::Vector{Int},
5+
singl_rows::Vector{Int}) where {T}
6+
7+
# assume Acols is sorted
8+
Annz = length(Arows)
9+
nsingl = length(singl_rows)
10+
11+
# remove ifix 1 by 1 in H and A and update QP data
12+
for idxsingl = 1:nsingl
13+
currentisingl = singl_rows[idxsingl]
14+
# index of the current singleton row that takes the number of
15+
# already removed variables into account:
16+
newcurrentisingl = currentisingl - idxsingl + 1
17+
18+
# remove singleton rows in A rows
19+
Awritepos = 1
20+
currentAn = nvar - idxsingl + 1
21+
k = 1
22+
while k <= Annz - idxsingl + 1
23+
Ai, Aj, Ax = Arows[k], Acols[k], Avals[k]
24+
if Ai == newcurrentisingl
25+
oldAi = Ai + idxsingl - 1
26+
if Ax > zero(T)
27+
lvar[Aj] = max(lvar[Aj], lcon[oldAi] / Ax)
28+
uvar[Aj] = min(uvar[Aj], ucon[oldAi] / Ax)
29+
elseif Ax < zero(T)
30+
lvar[Aj] = max(lvar[Aj], ucon[oldAi] / Ax)
31+
uvar[Aj] = min(uvar[Aj], lcon[oldAi] / Ax)
32+
else
33+
error("remove explicit zeros in A")
34+
end
35+
else
36+
Arows[Awritepos] = (Ai < newcurrentisingl) ? Ai : Ai - 1
37+
Acols[Awritepos] = Aj
38+
Avals[Awritepos] = Ax
39+
Awritepos += 1
40+
end
41+
k += 1
42+
end
43+
end
44+
45+
if nsingl > 0
46+
Annz -= nsingl
47+
resize!(Arows, Annz)
48+
resize!(Acols, Annz)
49+
resize!(Avals, Annz)
50+
end
51+
52+
new_ncon = 0
53+
for i=1:ncon
54+
if row_cnt[i] != 1
55+
new_ncon += 1
56+
lcon[new_ncon] = lcon[i]
57+
ucon[new_ncon] = ucon[i]
58+
end
59+
end
60+
resize!(lcon, new_ncon)
61+
resize!(ucon, new_ncon)
62+
return new_ncon
63+
end

test/test_presolve.jl

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ end
8282
1.0 0.0 1.0
8383
0.0 0.0 0.0
8484
0.0 0.0 0.0
85-
3.2 0.0 0.0
85+
3.2 0.0 2.0
8686
0.0 0.0 0.0
8787
0.0 2.0 1.0
8888
]
@@ -107,7 +107,7 @@ end
107107

108108
Aps_true = [
109109
1.0 0.0 1.0
110-
3.2 0.0 0.0
110+
3.2 0.0 2.0
111111
0.0 2.0 1.0
112112
]
113113
bps_true = [0.0; 4.0; 3.0]
@@ -124,3 +124,107 @@ end
124124
postsolve!(qp, psqp, x_in, x_out, y_in, y_out)
125125
@test y_out == [2.0; 0.0; 0.0; 2.0; 0.0; 4.0]
126126
end
127+
128+
@testset "presolve singleton rows" begin
129+
H = [
130+
6.0 2.0 1.0
131+
2.0 5.0 2.0
132+
1.0 2.0 4.0
133+
]
134+
c = [-8.0; -3; -3]
135+
A = [
136+
1.0 0.0 1.0
137+
0.0 0.0 0.0
138+
0.0 0.0 0.0
139+
3.0 0.0 0.0
140+
0.0 0.0 0.0
141+
0.0 2.0 1.0
142+
]
143+
lcon = [0.0; 0.0; 0.0; 4.0; 0.0; 3]
144+
ucon = [3.0; 0.0; 0.0; 7.0; 0.0; 8.0]
145+
l = [0.0; 0.0; 0]
146+
u = [Inf; 2.0; Inf]
147+
T = eltype(c)
148+
qp = QuadraticModel(
149+
c,
150+
SparseMatrixCOO(tril(H)),
151+
A = SparseMatrixCOO(A),
152+
lcon = lcon,
153+
ucon = ucon,
154+
lvar = l,
155+
uvar = u,
156+
c0 = 0.0,
157+
name = "QM1",
158+
)
159+
160+
statsps = presolve(qp)
161+
psqp = statsps.solver_specific[:presolvedQM]
162+
163+
Aps_true = [
164+
1.0 0.0 1.0
165+
0.0 2.0 1.0
166+
]
167+
168+
Aps = sparse(psqp.data.A.rows, psqp.data.A.cols, psqp.data.A.vals, psqp.meta.ncon, psqp.meta.nvar)
169+
@test Aps == sparse(Aps_true)
170+
@test psqp.meta.ncon == 2
171+
172+
x_in = [4.0; 7.0; 4.0]
173+
x_out = zeros(3)
174+
y_in = [2.0; 4.0]
175+
y_out = zeros(6)
176+
postsolve!(qp, psqp, x_in, x_out, y_in, y_out)
177+
@test y_out == [2.0; 0.0; 0.0; 0.0; 0.0; 4.0]
178+
end
179+
180+
@testset "presolve singleton rows and ifix" begin
181+
H = [
182+
6.0 2.0 1.0
183+
2.0 5.0 2.0
184+
1.0 2.0 4.0
185+
]
186+
c = [-8.0; -3; -3]
187+
A = [
188+
1.0 0.0 1.0
189+
0.0 0.0 0.0
190+
0.0 0.0 0.0
191+
3.0 0.0 0.0
192+
0.0 0.0 0.0
193+
0.0 2.0 1.0
194+
]
195+
b = [2.0; 0.0; 0.0; 4.0; 0.0; 3]
196+
l = [0.0; 0.0; 0]
197+
u = [Inf; 2.0; Inf]
198+
T = eltype(c)
199+
qp = QuadraticModel(
200+
c,
201+
SparseMatrixCOO(tril(H)),
202+
A = SparseMatrixCOO(A),
203+
lcon = b,
204+
ucon = b,
205+
lvar = l,
206+
uvar = u,
207+
c0 = 0.0,
208+
name = "QM1",
209+
)
210+
211+
statsps = presolve(qp)
212+
psqp = statsps.solver_specific[:presolvedQM]
213+
214+
Aps_true = [
215+
0.0 1.0
216+
2.0 1.0
217+
]
218+
219+
Aps = sparse(psqp.data.A.rows, psqp.data.A.cols, psqp.data.A.vals, psqp.meta.ncon, psqp.meta.nvar)
220+
@test Aps == sparse(Aps_true)
221+
@test psqp.meta.ncon == 2
222+
223+
x_in = [7.0; 4.0]
224+
x_out = zeros(3)
225+
y_in = [2.0; 4.0]
226+
y_out = zeros(6)
227+
postsolve!(qp, psqp, x_in, x_out, y_in, y_out)
228+
@test y_out == [2.0; 0.0; 0.0; 0.0; 0.0; 4.0]
229+
@test x_out == [4.0/3.0; 7.0; 4.0]
230+
end

0 commit comments

Comments
 (0)