Skip to content

Commit 8c2015c

Browse files
committed
Improve allocation of LineModel
1 parent 9f316a3 commit 8c2015c

File tree

5 files changed

+152
-22
lines changed

5 files changed

+152
-22
lines changed

Project.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ julia = "^1.3.0"
1616
[extras]
1717
ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a"
1818
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
19-
NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6"
2019
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2120

2221
[targets]
23-
test = ["ADNLPModels", "Logging", "NLPModels", "Test"]
22+
test = ["ADNLPModels", "Logging", "Test"]

src/linesearch/line_model.jl

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import NLPModels: obj, grad, grad!, objgrad!, objgrad, hess
22

33
export LineModel
4-
export obj, grad, derivative, grad!, objgrad!, objgrad, derivative!, hess, redirect!
4+
export obj, grad, derivative, grad!, objgrad!, objgrad, derivative!, hess, hess!, redirect!
55

66
"""A type to represent the restriction of a function to a direction.
77
Given f : R → Rⁿ, x ∈ Rⁿ and a nonzero direction d ∈ Rⁿ,
@@ -12,17 +12,18 @@ represents the function ϕ : R → R defined by
1212
1313
ϕ(t) := f(x + td).
1414
"""
15-
mutable struct LineModel{T, S} <: AbstractNLPModel{T, S}
15+
mutable struct LineModel{T, S, M <: AbstractNLPModel{T, S}} <: AbstractNLPModel{T, S}
1616
meta::NLPModelMeta{T, S}
1717
counters::Counters
18-
nlp::AbstractNLPModel{T, S}
18+
nlp::M
1919
x::S
2020
d::S
21+
xt::S
2122
end
2223

23-
function LineModel(nlp::AbstractNLPModel{T, S}, x::S, d::S) where {T, S}
24+
function LineModel(nlp::AbstractNLPModel{T, S}, x::S, d::S; xt::S = similar(x)) where {T, S}
2425
meta = NLPModelMeta{T, S}(1, x0 = zeros(T, 1), name = "LineModel to $(nlp.meta.name))")
25-
return LineModel(meta, Counters(), nlp, x, d)
26+
return LineModel(meta, Counters(), nlp, x, d, xt)
2627
end
2728

2829
"""`redirect!(ϕ, x, d)`
@@ -40,7 +41,8 @@ end
4041
"""
4142
function obj(f::LineModel, t::AbstractFloat)
4243
NLPModels.increment!(f, :neval_obj)
43-
return obj(f.nlp, f.x + t * f.d)
44+
@. f.xt = f.x + t * f.d
45+
return obj(f.nlp, f.xt)
4446
end
4547

4648
"""`grad(f, t)` evaluates the first derivative of the `LineModel`
@@ -53,7 +55,8 @@ i.e.,
5355
"""
5456
function grad(f::LineModel, t::AbstractFloat)
5557
NLPModels.increment!(f, :neval_grad)
56-
return dot(grad(f.nlp, f.x + t * f.d), f.d)
58+
@. f.xt = f.x + t * f.d
59+
return dot(grad(f.nlp, f.xt), f.d)
5760
end
5861
derivative(f::LineModel, t::AbstractFloat) = grad(f, t)
5962

@@ -69,7 +72,8 @@ The gradient ∇f(x + td) is stored in `g`.
6972
"""
7073
function grad!(f::LineModel, t::AbstractFloat, g::AbstractVector)
7174
NLPModels.increment!(f, :neval_grad)
72-
return dot(grad!(f.nlp, f.x + t * f.d, g), f.d)
75+
@. f.xt = f.x + t * f.d
76+
return dot(grad!(f.nlp, f.xt, g), f.d)
7377
end
7478
derivative!(f::LineModel, t::AbstractFloat, g::AbstractVector) = grad!(f, t, g)
7579

@@ -86,8 +90,9 @@ The gradient ∇f(x + td) is stored in `g`.
8690
function objgrad!(f::LineModel, t::AbstractFloat, g::AbstractVector)
8791
NLPModels.increment!(f, :neval_obj)
8892
NLPModels.increment!(f, :neval_grad)
89-
fx, gx = objgrad!(f.nlp, f.x + t * f.d, g)
90-
return fx, dot(gx, f.d)
93+
@. f.xt = f.x + t * f.d
94+
fx, _ = objgrad!(f.nlp, f.xt, g)
95+
return fx, dot(g, f.d)
9196
end
9297

9398
"""`objgrad(f, t)` evaluates the objective and first derivative of the `LineModel`
@@ -112,5 +117,20 @@ i.e.,
112117
"""
113118
function hess(f::LineModel, t::AbstractFloat)
114119
NLPModels.increment!(f, :neval_hess)
115-
return dot(f.d, hprod(f.nlp, f.x + t * f.d, f.d))
120+
@. f.xt = f.x + t * f.d
121+
return dot(f.d, hprod(f.nlp, f.xt, f.d))
122+
end
123+
124+
"""Evaluate the second derivative of the `LineModel`
125+
126+
ϕ(t) := f(x + td),
127+
128+
i.e.,
129+
130+
ϕ"(t) = dᵀ∇²f(x + td)d.
131+
"""
132+
function hess!(f::LineModel, t::AbstractFloat, Hv::AbstractVector)
133+
NLPModels.increment!(f, :neval_hess)
134+
@. f.xt = f.x + t * f.d
135+
return dot(f.d, hprod!(f.nlp, f.xt, f.d, Hv))
116136
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ using ADNLPModels, NLPModels
88
using LinearAlgebra, Logging, Test
99

1010
include("dummy_solver.jl")
11+
include("simple_model.jl")
1112

1213
include("test_auxiliary.jl")
1314
include("test_linesearch.jl")

test/simple_model.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
mutable struct SimpleModel{T, S} <: AbstractNLPModel{T, S}
2+
meta :: NLPModelMeta{T, S}
3+
counters :: Counters
4+
end
5+
6+
SimpleModel(n :: Int) = SimpleModel(NLPModelMeta(n, x0=ones(n)), Counters())
7+
8+
function NLPModels.obj(nlp::SimpleModel, x::AbstractVector)
9+
increment!(nlp, :neval_obj)
10+
sum(xi ^ 4 for xi in x) / 12
11+
end
12+
13+
function NLPModels.grad!(nlp::SimpleModel, x::AbstractVector, g::AbstractVector)
14+
increment!(nlp, :neval_grad)
15+
@. g = x ^ 3 / 3
16+
g
17+
end
18+
19+
function NLPModels.objgrad!(nlp::SimpleModel, x::AbstractVector, g::AbstractVector)
20+
increment!(nlp, :neval_obj)
21+
increment!(nlp, :neval_grad)
22+
@. g = x ^ 3 / 3
23+
return sum(xi ^4 for xi in x) / 12, g
24+
end
25+
26+
function NLPModels.hprod!(nlp::SimpleModel, x::AbstractVector{T}, v::AbstractVector, Hv::AbstractVector; obj_weight::T = one(T)) where T
27+
increment!(nlp, :neval_hprod)
28+
@. Hv = obj_weight * x ^ 2 * v
29+
Hv
30+
end
31+
32+
function NLPModels.hess(nlp::SimpleModel, x::AbstractVector{T}; obj_weight::T = one(T)) where T
33+
increment!(nlp, :neval_hprod)
34+
return obj_weight .* diagm(0 => x .^ 2)
35+
end

test/test_linesearch.jl

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,46 @@
1+
"""
2+
@wrappedallocs(expr)
3+
4+
Given an expression, this macro wraps that expression inside a new function
5+
which will evaluate that expression and measure the amount of memory allocated
6+
by the expression. Wrapping the expression in a new function allows for more
7+
accurate memory allocation detection when using global variables (e.g. when
8+
at the REPL).
9+
10+
For example, `@wrappedallocs(x + y)` produces:
11+
12+
```julia
13+
function g(x1, x2)
14+
@allocated x1 + x2
15+
end
16+
g(x, y)
17+
```
18+
19+
You can use this macro in a unit test to verify that a function does not
20+
allocate:
21+
22+
```
23+
@test @wrappedallocs(x + y) == 0
24+
```
25+
"""
26+
macro wrappedallocs(expr)
27+
argnames = [gensym() for a in expr.args]
28+
quote
29+
function g($(argnames...))
30+
@allocated $(Expr(expr.head, argnames...))
31+
end
32+
$(Expr(:call, :g, [esc(a) for a in expr.args]...))
33+
end
34+
end
35+
136
@testset "Linesearch" begin
237
@testset "LineModel" begin
3-
nlp = ADNLPModel(x -> x[1]^2 + 4 * x[2]^2, ones(2))
38+
n = 200
39+
nlp = SimpleModel(n)
440
x = nlp.meta.x0
5-
d = -ones(2)
41+
d = -ones(n)
642
lm = LineModel(nlp, x, d)
7-
g = zeros(2)
43+
g = zeros(n)
844

945
@test obj(lm, 0.0) == obj(nlp, x)
1046
@test grad(lm, 0.0) == dot(grad(nlp, x), d)
@@ -17,19 +53,19 @@
1753
@test g == grad(nlp, x + d)
1854
@test objgrad(lm, 0.0) == (obj(nlp, x), dot(grad(nlp, x), d))
1955
@test hess(lm, 0.0) == dot(d, Symmetric(hess(nlp, x), :L) * d)
56+
@test hess!(lm, 0.0, g) == dot(d, hprod!(nlp, x, d, g))
2057

2158
@test obj(lm, 1.0) == 0.0
2259
@test grad(lm, 1.0) == 0.0
23-
@test hess(lm, 1.0) == 2d[1]^2 + 8d[2]^2
60+
@test hess(lm, 1.0) == 0.0
2461

25-
redirect!(lm, zeros(2), ones(2))
26-
@test obj(lm, 0.0) == 0.0
27-
@test grad(lm, 0.0) == 0.0
28-
@test hess(lm, 0.0) == 10.0
62+
@test obj(lm, 0.0) n / 12
63+
@test grad(lm, 0.0) -n / 3
64+
@test hess(lm, 0.0) == n
2965

3066
@test neval_obj(lm) == 5
3167
@test neval_grad(lm) == 8
32-
@test neval_hess(lm) == 3
68+
@test neval_hess(lm) == 4
3369
end
3470

3571
@testset "Armijo-Wolfe" begin
@@ -63,4 +99,43 @@
6399
@test nbk > 0
64100
@test nbW > 0
65101
end
102+
103+
if VERSION v"1.6"
104+
@testset "Don't allocate" begin
105+
n = 200
106+
nlp = SimpleModel(n)
107+
x = nlp.meta.x0
108+
g = zeros(n)
109+
d = -40 * ones(n)
110+
lm = LineModel(nlp, x, d)
111+
112+
al = @wrappedallocs obj(lm, 1.0)
113+
@test al == 0
114+
115+
al = @wrappedallocs grad!(lm, 1.0, g)
116+
@test al == 0
117+
118+
al = @wrappedallocs objgrad!(lm, 1.0, g)
119+
@test al == 0
120+
121+
al = @wrappedallocs hess!(lm, 1.0, g)
122+
@test al == 0
123+
124+
h₀ = obj(lm, 0.0)
125+
slope = grad(lm, 0.0)
126+
(t, gg, ht, nbk, nbW) = armijo_wolfe(lm, h₀, slope, g)
127+
al = @wrappedallocs armijo_wolfe(lm, h₀, slope, g)
128+
@test al == 0
129+
130+
function armijo_wolfe_alloc(lm, h₀, slope, g, bk_max)
131+
@allocated armijo_wolfe(lm, h₀, slope, g, bk_max=bk_max)
132+
end
133+
134+
for bk_max = 0:8
135+
(t, gg, ht, nbk, nbW) = armijo_wolfe(lm, h₀, slope, g, bk_max=bk_max)
136+
al = armijo_wolfe_alloc(lm, h₀, slope, g, bk_max)
137+
@test al == 0
138+
end
139+
end
140+
end
66141
end

0 commit comments

Comments
 (0)