Skip to content

Commit f960e50

Browse files
committed
LSR1: remove allocations, make more generic
1 parent 943d327 commit f960e50

File tree

4 files changed

+62
-27
lines changed

4 files changed

+62
-27
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# A [Julia](http://julialang.org) Linear Operator Package
22

33
[![DOI](https://zenodo.org/badge/20136006.svg)](https://zenodo.org/badge/latestdoi/20136006)
4-
[![Build Status](https://travis-ci.org/JuliaSmoothOptimizers/LinearOperators.jl.svg?branch=master)](https://travis-ci.org/JuliaSmoothOptimizers/LinearOperators.jl)
4+
[![Build Status](https://github.com/JuliaSmoothOptimizers/NLPModels.jl/workflows/CI/badge.svg?branch=master)
55
[![Build status](https://ci.appveyor.com/api/projects/status/kp1o6ejuu6kgskvp/branch/master?svg=true)](https://ci.appveyor.com/project/dpo/linearoperators-jl/branch/master)
66
[![Build Status](https://api.cirrus-ci.com/github/JuliaSmoothOptimizers/LinearOperators.jl.svg)](https://cirrus-ci.com/github/JuliaSmoothOptimizers/LinearOperators.jl)
77
[![Coveralls](https://coveralls.io/repos/JuliaSmoothOptimizers/LinearOperators.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/JuliaSmoothOptimizers/LinearOperators.jl?branch=master)

src/lbfgs.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ end
237237
Extract the diagonal of a L-BFGS operator in forward mode.
238238
"""
239239
function diag(op :: LBFGSOperator{T}) where T
240-
d = zeros(T, op.nrow)
240+
d = Vector{T}(undef, op.nrow)
241241
diag!(op, d)
242242
end
243243

src/lsr1.jl

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,27 @@ mutable struct LSR1Data{T}
55
mem :: Int
66
scaling :: Bool
77
scaling_factor :: T
8-
s :: Matrix{T}
9-
y :: Matrix{T}
8+
s :: Vector{Vector{T}}
9+
y :: Vector{Vector{T}}
1010
ys :: Vector{T}
11-
a :: Matrix{T}
11+
a :: Vector{Vector{T}}
1212
as :: Vector{T}
1313
insert :: Int
14+
Ax :: Vector{T}
1415
end
1516

1617
function LSR1Data(T :: DataType, n :: Int; mem :: Int=5, scaling :: Bool=true, inverse :: Bool=false)
1718
inverse && @warn "inverse LSR1 operator not yet implemented"
1819
LSR1Data{T}(max(mem, 1),
1920
scaling,
2021
convert(T, 1),
21-
zeros(T, n, mem),
22-
zeros(T, n, mem),
22+
[zeros(T, n) for _ = 1 : mem],
23+
[zeros(T, n) for _ = 1 : mem],
2324
zeros(T, mem),
24-
zeros(T, n, mem),
25+
[zeros(T, n) for _ = 1 : mem],
2526
zeros(T, mem),
26-
1)
27+
1,
28+
Vector{T}(undef, n))
2729
end
2830

2931
LSR1Data(n :: Int; kwargs...) = LSR1Data(Float64, n; kwargs...)
@@ -60,15 +62,18 @@ function LSR1Operator(T :: DataType, n :: Int; kwargs...)
6062
function lsr1_multiply(data :: LSR1Data, x :: AbstractArray)
6163
# Multiply operator with a vector.
6264

63-
result_type = promote_type(T, eltype(x))
64-
q = convert(Array{result_type}, copy(x))
65+
q = data.Ax
66+
q .= x
6567

6668
data.scaling && (q ./= data.scaling_factor) # q = B₀ * x
6769

6870
for i = 1 : data.mem
6971
k = mod(data.insert + i - 2, data.mem) + 1
7072
if data.ys[k] != 0
71-
@views q .+= dot(data.a[:, k], x) / data.as[k] * data.a[:, k]
73+
ax = dot(data.a[k], x) / data.as[k]
74+
for j eachindex(q)
75+
q[j] += ax * data.a[k][j]
76+
end
7277
end
7378
end
7479
return q
@@ -97,16 +102,20 @@ function push!(op :: LSR1Operator, s :: AbstractVector, y :: AbstractVector)
97102
Bs = op * s
98103
ymBs = y - Bs
99104
ys = dot(y, s)
105+
sNorm = norm(s)
106+
yy = dot(y, y)
100107

101-
well_defined = abs(dot(ymBs, s)) 1.0e-8 + 1.0e-8 * norm(ymBs)^2
108+
ϵ = eps(eltype(op))
109+
well_defined = abs(dot(ymBs, s)) ϵ + ϵ * norm(ymBs) * sNorm
102110

103111
sufficient_curvature = true
104112
scaling_condition = true
105113
if data.scaling
106-
sufficient_curvature = abs(ys) 1.0e-8
114+
yNorm = yy
115+
sufficient_curvature = abs(ys) ϵ * yNorm * sNorm
107116
if sufficient_curvature
108-
scaling_factor = ys / dot(y, y)
109-
scaling_condition = norm(y - s / scaling_factor) >= 1.0e-8
117+
scaling_factor = ys / yy
118+
scaling_condition = norm(y - s / scaling_factor) >= ϵ * yNorm * sNorm
110119
end
111120
end
112121

@@ -116,12 +125,12 @@ function push!(op :: LSR1Operator, s :: AbstractVector, y :: AbstractVector)
116125
return op
117126
end
118127

119-
data.s[:, data.insert] .= s
120-
data.y[:, data.insert] .= y
128+
data.s[data.insert] .= s
129+
data.y[data.insert] .= y
121130
data.ys[data.insert] = ys
122131

123132
# update scaling factor
124-
data.scaling && (data.scaling_factor = ys / dot(y, y))
133+
data.scaling && (data.scaling_factor = ys / yy)
125134

126135
# update next insertion position
127136
data.insert = mod(data.insert, data.mem) + 1
@@ -130,14 +139,15 @@ function push!(op :: LSR1Operator, s :: AbstractVector, y :: AbstractVector)
130139
for i = 1 : data.mem
131140
k = mod(data.insert + i - 2, data.mem) + 1
132141
if data.ys[k] != 0
133-
data.a[:, k] .= data.y[:, k] - data.s[:, k] / data.scaling_factor # = y - B₀ * s
142+
data.a[k] .= data.y[k] - data.s[k] / data.scaling_factor # = y - B₀ * s
134143
for j = 1 : i-1
135144
l = mod(data.insert + j - 2, data.mem) + 1
136145
if data.ys[l] != 0
137-
@views data.a[:, k] .-= dot(data.a[:, l], data.s[:, k]) / data.as[l] * data.a[:, l]
146+
as = dot(data.a[l], data.s[k]) / data.as[l]
147+
data.a[k] .-= as * data.a[l]
138148
end
139149
end
140-
@views data.as[k] = dot(data.a[:, k], data.s[:, k])
150+
data.as[k] = dot(data.a[k], data.s[k])
141151
end
142152
end
143153

@@ -147,21 +157,27 @@ end
147157

148158
"""
149159
diag(op)
160+
diag!(op, d)
150161
151162
Extract the diagonal of a L-SR1 operator in forward mode.
152163
"""
153164
function diag(op :: LSR1Operator{T}) where T
165+
d = Vector{T}(undef, op.nrow)
166+
diag!(op, d)
167+
end
168+
169+
function diag!(op :: LSR1Operator{T}, d) where T
154170
op.inverse && throw("only the diagonal of a forward L-SR1 approximation is available")
155171
data = op.data
156172

157-
d = ones(T, op.nrow)
173+
fill!(d, 1)
158174
data.scaling && (d ./= data.scaling_factor)
159175

160176
for i = 1 : data.mem
161177
k = mod(data.insert + i - 2, data.mem) + 1
162178
if data.ys[k] != 0.0
163179
for j = 1 : op.nrow
164-
d[j] += data.a[j, k]^2 / data.as[k]
180+
d[j] += data.a[k][j]^2 / data.as[k]
165181
end
166182
end
167183
end
@@ -174,10 +190,12 @@ end
174190
Reset the given LSR1 data.
175191
"""
176192
function reset!(data :: LSR1Data{T}) where T
177-
fill!(data.s, 0)
178-
fill!(data.y, 0)
193+
for i = 1 : data.mem
194+
fill!(data.s[i], 0)
195+
fill!(data.y[i], 0)
196+
fill!(data.a[i], 0)
197+
end
179198
fill!(data.ys, 0)
180-
fill!(data.a, 0)
181199
fill!(data.as, 0)
182200
data.scaling_factor = T(1)
183201
data.insert = 1

test/test_lsr1.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ function test_lsr1()
7777
@test eltype(B * v) == T
7878
end
7979
end
80+
81+
@testset "LSR1 allocations" begin
82+
n = 100
83+
mem = 20
84+
B = LSR1Operator(n, mem=mem)
85+
for _ = 1 :2:n
86+
s = rand(n)
87+
y = rand(n)
88+
push!(B, s, y)
89+
end
90+
x = rand(n)
91+
B * x # warmup
92+
nallocs = @allocated B * x
93+
@test nallocs == 0
94+
nallocs = @allocated diag!(B, x)
95+
@test nallocs == 0
96+
end
8097
end
8198

8299
test_lsr1()

0 commit comments

Comments
 (0)