Skip to content

Commit 2bb940b

Browse files
Merge pull request #55 from SciML/generallbc
Add GeneralLazyBufferCache
2 parents e5f27ba + 81d9995 commit 2bb940b

File tree

6 files changed

+200
-5
lines changed

6 files changed

+200
-5
lines changed

Project.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "PreallocationTools"
22
uuid = "d236fae5-4411-538c-8e31-a6e3d9e00b46"
33
authors = ["Chris Rackauckas <[email protected]>"]
4-
version = "0.4.7"
4+
version = "0.4.8"
55

66
[deps]
77
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
@@ -21,11 +21,12 @@ Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
2121
OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
2222
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
2323
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
24+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
2425
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
2526
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
2627
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
2728
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
2829
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2930

3031
[targets]
31-
test = ["LabelledArrays", "LinearAlgebra", "OrdinaryDiffEq", "Test", "RecursiveArrayTools", "Pkg", "SafeTestsets", "Optimization", "OptimizationOptimJL", "SparseArrays", "Symbolics"]
32+
test = ["Random", "LabelledArrays", "LinearAlgebra", "OrdinaryDiffEq", "Test", "RecursiveArrayTools", "Pkg", "SafeTestsets", "Optimization", "OptimizationOptimJL", "SparseArrays", "Symbolics"]

README.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,77 @@ prob = ODEProblem(foo, ones(5, 5), (0., 1.0), (ones(5,5), LazyBufferCache()))
234234
solve(prob, TRBDF2())
235235
```
236236

237-
## Note About ReverseDiff Support for LazyBuffer
237+
### Note About ReverseDiff Support for LazyBuffer
238238

239239
ReverseDiff support is done in SciMLSensitivity.jl to reduce the AD requirements on this package.
240240
Load that package if ReverseDiff overloads are required.
241241

242+
## GeneralLazyBufferCache
243+
244+
```julia
245+
GeneralLazyBufferCache(f=identity)
246+
```
247+
248+
A `GeneralLazyBufferCache` is a `Dict`-like type for the caches which automatically defines
249+
new caches on demand when they are required. The function `f` generates the cache matching
250+
for the type of `u`, and subsequent indexing reuses that cache if that type of `u` has
251+
already ben seen.
252+
253+
Note that `LazyBufferCache` does cause a dynamic dispatch and its return is not type-inferred.
254+
This means it's the slowest of the preallocation methods, but it's the most general.
255+
256+
### Example
257+
258+
In all of the previous cases our cache was an array. However, in this case we want to preallocate
259+
a DifferentialEquations `ODEIntegrator` object. This object is the one created via
260+
`DifferentialEquations.init(ODEProblem(ode_fnc, y₀, (0.0, T), p), Tsit5(); saveat = t)`, and we
261+
want to optimize `p` in a way that changes its type to ForwardDiff. Thus what we can do is make a
262+
GeneralLazyBufferCache which holds these integrator objects, defined by `p`, and indexing it with
263+
`p` in order to retrieve the cache. The first time it's called it will build the integrator, and
264+
in subsequent calls it will reuse the cache.
265+
266+
Defining the cache as a function of `p` to build an integrator thus looks like:
267+
268+
```julia
269+
lbc = GeneralLazyBufferCache(function (p)
270+
DifferentialEquations.init(ODEProblem(ode_fnc, y₀, (0.0, T), p), Tsit5(); saveat = t)
271+
end)
272+
```
273+
274+
then `lbc[p]` will be smart and reuse the caches. A full example looks like the following:
275+
276+
```julia
277+
using Random, DifferentialEquations, LinearAlgebra, Optimization, OptimizationNLopt, OptimizationOptimJL, PreallocationTools
278+
279+
lbc = GeneralLazyBufferCache(function (p)
280+
DifferentialEquations.init(ODEProblem(ode_fnc, y₀, (0.0, T), p), Tsit5(); saveat = t)
281+
end)
282+
283+
Random.seed!(2992999)
284+
λ, y₀, σ = -0.5, 15.0, 0.1
285+
T, n = 5.0, 200
286+
Δt = T / n
287+
t = [j * Δt for j in 0:n]
288+
y = y₀ * exp.(λ * t)
289+
yᵒ = y .+ [0.0, σ * randn(n)...]
290+
ode_fnc(u, p, t) = p * u
291+
function loglik(θ, data, integrator)
292+
yᵒ, n, ε = data
293+
λ, σ, u0 = θ
294+
integrator.p = λ
295+
reinit!(integrator, u0)
296+
solve!(integrator)
297+
ε = yᵒ .- integrator.sol.u
298+
= -0.5n * log(2π * σ^2) - 0.5 / σ^2 * sum.^2)
299+
end
300+
θ₀ = [-1.0, 0.5, 19.73]
301+
negloglik = (θ, p) -> -loglik(θ, p, lbc[θ[1]])
302+
fnc = OptimizationFunction(negloglik, Optimization.AutoForwardDiff())
303+
ε = zeros(n)
304+
prob = OptimizationProblem(fnc, θ₀, (yᵒ, n, ε), lb=[-10.0, 1e-6, 0.5], ub=[10.0, 10.0, 25.0])
305+
solve(prob, LBFGS())
306+
```
307+
242308
## Similar Projects
243309

244310
[AutoPreallocation.jl](https://github.com/oxinabox/AutoPreallocation.jl) tries

docs/src/index.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,77 @@ prob = ODEProblem(foo, ones(5, 5), (0., 1.0), (ones(5,5), LazyBufferCache()))
224224
solve(prob, TRBDF2())
225225
```
226226

227-
## Note About ReverseDiff Support for LazyBuffer
227+
### Note About ReverseDiff Support for LazyBuffer
228228

229229
ReverseDiff support is done in SciMLSensitivity.jl to reduce the AD requirements on this package.
230230
Load that package if ReverseDiff overloads are required.
231231

232+
## GeneralLazyBufferCache
233+
234+
```julia
235+
GeneralLazyBufferCache(f=identity)
236+
```
237+
238+
A `GeneralLazyBufferCache` is a `Dict`-like type for the caches which automatically defines
239+
new caches on demand when they are required. The function `f` generates the cache matching
240+
for the type of `u`, and subsequent indexing reuses that cache if that type of `u` has
241+
already ben seen.
242+
243+
Note that `LazyBufferCache` does cause a dynamic dispatch and its return is not type-inferred.
244+
This means it's the slowest of the preallocation methods, but it's the most general.
245+
246+
### Example
247+
248+
In all of the previous cases our cache was an array. However, in this case we want to preallocate
249+
a DifferentialEquations `ODEIntegrator` object. This object is the one created via
250+
`DifferentialEquations.init(ODEProblem(ode_fnc, y₀, (0.0, T), p), Tsit5(); saveat = t)`, and we
251+
want to optimize `p` in a way that changes its type to ForwardDiff. Thus what we can do is make a
252+
GeneralLazyBufferCache which holds these integrator objects, defined by `p`, and indexing it with
253+
`p` in order to retrieve the cache. The first time it's called it will build the integrator, and
254+
in subsequent calls it will reuse the cache.
255+
256+
Defining the cache as a function of `p` to build an integrator thus looks like:
257+
258+
```julia
259+
lbc = GeneralLazyBufferCache(function (p)
260+
DifferentialEquations.init(ODEProblem(ode_fnc, y₀, (0.0, T), p), Tsit5(); saveat = t)
261+
end)
262+
```
263+
264+
then `lbc[p]` will be smart and reuse the caches. A full example looks like the following:
265+
266+
```julia
267+
using Random, DifferentialEquations, LinearAlgebra, Optimization, OptimizationNLopt, OptimizationOptimJL, PreallocationTools
268+
269+
lbc = GeneralLazyBufferCache(function (p)
270+
DifferentialEquations.init(ODEProblem(ode_fnc, y₀, (0.0, T), p), Tsit5(); saveat = t)
271+
end)
272+
273+
Random.seed!(2992999)
274+
λ, y₀, σ = -0.5, 15.0, 0.1
275+
T, n = 5.0, 200
276+
Δt = T / n
277+
t = [j * Δt for j in 0:n]
278+
y = y₀ * exp.(λ * t)
279+
yᵒ = y .+ [0.0, σ * randn(n)...]
280+
ode_fnc(u, p, t) = p * u
281+
function loglik(θ, data, integrator)
282+
yᵒ, n, ε = data
283+
λ, σ, u0 = θ
284+
integrator.p = λ
285+
reinit!(integrator, u0)
286+
solve!(integrator)
287+
ε = yᵒ .- integrator.sol.u
288+
= -0.5n * log(2π * σ^2) - 0.5 / σ^2 * sum.^2)
289+
end
290+
θ₀ = [-1.0, 0.5, 19.73]
291+
negloglik = (θ, p) -> -loglik(θ, p, lbc[θ[1]])
292+
fnc = OptimizationFunction(negloglik, Optimization.AutoForwardDiff())
293+
ε = zeros(n)
294+
prob = OptimizationProblem(fnc, θ₀, (yᵒ, n, ε), lb=[-10.0, 1e-6, 0.5], ub=[10.0, 10.0, 25.0])
295+
solve(prob, LBFGS())
296+
```
297+
232298
## Similar Projects
233299

234300
[AutoPreallocation.jl](https://github.com/oxinabox/AutoPreallocation.jl) tries

src/PreallocationTools.jl

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,35 @@ function Base.getindex(b::LazyBufferCache, u::T) where {T <: AbstractArray}
183183
return buf
184184
end
185185

186-
export FixedSizeDiffCache, DiffCache, LazyBufferCache, dualcache
186+
# GeneralLazyBufferCache
187+
188+
"""
189+
b = GeneralLazyBufferCache(f=identity)
190+
191+
A lazily allocated buffer object. Given an array `u`, `b[u]` returns a cache object
192+
generated by `f(u)`, but the generator is only ran the first time (and all subsequent
193+
times it reuses the same cache)
194+
195+
## Limitation
196+
197+
The main limitation of this method is that its return is not type-inferred, and thus
198+
it can be slower than some of the other preallocation techniques. However, if used
199+
correct using things like function barriers, then this is a general technique that
200+
is sufficiently fast.
201+
"""
202+
struct GeneralLazyBufferCache{F <: Function}
203+
bufs::Dict # a dictionary mapping types to buffers
204+
f::F
205+
GeneralLazyBufferCache(f::F = identity) where {F <: Function} = new{F}(Dict(), f) # start with empty dict
206+
end
207+
208+
function Base.getindex(b::GeneralLazyBufferCache, u::T) where {T}
209+
get!(b.bufs, T) do
210+
b.f(u)
211+
end
212+
end
213+
214+
export GeneralLazyBufferCache, FixedSizeDiffCache, DiffCache, LazyBufferCache, dualcache
187215
export get_tmp
188216

189217
end

test/general_lbc.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Random, OrdinaryDiffEq, LinearAlgebra, Optimization, OptimizationOptimJL,
2+
PreallocationTools
3+
4+
lbc = GeneralLazyBufferCache(function (p)
5+
init(ODEProblem(ode_fnc, y₀,
6+
(0.0, T), p),
7+
Tsit5(); saveat = t)
8+
end)
9+
10+
Random.seed!(2992999)
11+
λ, y₀, σ = -0.5, 15.0, 0.1
12+
T, n = 5.0, 200
13+
Δt = T / n
14+
t = [j * Δt for j in 0:n]
15+
y = y₀ * exp.(λ * t)
16+
yᵒ = y .+ [0.0, σ * randn(n)...]
17+
ode_fnc(u, p, t) = p * u
18+
function loglik(θ, data, integrator)
19+
yᵒ, n, ε = data
20+
λ, σ, u0 = θ
21+
integrator.p = λ
22+
reinit!(integrator, u0)
23+
solve!(integrator)
24+
ε = yᵒ .- integrator.sol.u
25+
= -0.5n * log(2π * σ^2) - 0.5 / σ^2 * sum.^ 2)
26+
end
27+
θ₀ = [-1.0, 0.5, 19.73]
28+
negloglik = (θ, p) -> -loglik(θ, p, lbc[θ[1]])
29+
fnc = OptimizationFunction(negloglik, Optimization.AutoForwardDiff())
30+
ε = zeros(n)
31+
prob = OptimizationProblem(fnc, θ₀, (yᵒ, n, ε), lb = [-10.0, 1e-6, 0.5],
32+
ub = [10.0, 10.0, 25.0])
33+
solve(prob, LBFGS())

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ if GROUP == "All" || GROUP == "Core"
1616
@safetestset "DiffCache Resizing" begin include("core_resizing.jl") end
1717
@safetestset "DiffCache Nested Duals" begin include("core_nesteddual.jl") end
1818
@safetestset "DiffCache Sparsity Support" begin include("sparsity_support.jl") end
19+
@safetestset "GeneralLazyBufferCache" begin include("general_lbc.jl") end
1920
end
2021

2122
if !is_APPVEYOR && GROUP == "GPU"

0 commit comments

Comments
 (0)