Skip to content

Commit 3662ffb

Browse files
authored
feat: remove allocations in construct_diffusion_function (#253)
1 parent 6854e68 commit 3662ffb

3 files changed

Lines changed: 88 additions & 9 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DynamicalSystemsBase"
22
uuid = "6e36e845-645a-534a-86f2-f5d4aa5a06b4"
33
repo = "https://github.com/JuliaDynamics/DynamicalSystemsBase.jl.git"
4-
version = "3.16.0"
4+
version = "3.16.1"
55

66
[deps]
77
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"

ext/src/CoupledSDEs.jl

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -258,21 +258,29 @@ function construct_diffusion_function(
258258
A = sqrt(cov)
259259
if IIP
260260
if isdiag(cov)
261-
g = (du, u, p, t) -> du .= noise_strength .* diag(A)
261+
diag_const = collect(noise_strength .* diag(A))
262+
g = let diag_const = diag_const
263+
(du, u, p, t) -> (du .= diag_const; nothing)
264+
end
262265
else
263-
g = (du, u, p, t) -> du .= noise_strength .* A
266+
A_const = collect(noise_strength .* A)
267+
g = let A_const = A_const
268+
(du, u, p, t) -> (du .= A_const; nothing)
269+
end
264270
noise_prototype = zeros(size(A))
265271
# ^ we could make this sparse to make it more performant
266272
end
267273
else
268274
if isdiag(cov)
269-
g = (u, p, t) -> SVector{length(diag(A)), eltype(A)}(
270-
diag(noise_strength .* A)
271-
)
275+
diag_const = SVector{D, eltype(A)}(diag(noise_strength .* A))
276+
g = let diag_const = diag_const
277+
(u, p, t) -> diag_const
278+
end
272279
else
273-
g = (u, p, t) -> SMatrix{size(A)..., eltype(A)}(
274-
noise_strength .* A
275-
)
280+
A_const = SMatrix{size(A)..., eltype(A)}(noise_strength .* A)
281+
g = let A_const = A_const
282+
(u, p, t) -> A_const
283+
end
276284
noise_prototype = zeros(size(A))
277285
end
278286
end

test/stochastic.jl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,74 @@ end
167167
@test approx Γ atol = 1.0e-1
168168
end
169169
end
170+
171+
# Regression test for https://github.com/JuliaDynamics/DynamicalSystemsBase.jl/issues/251:
172+
# the auto-generated diffusion closure used to recompute its (constant) output on every
173+
# call, allocating ~1 KB per `step!` and dominating long integrations. The closure must
174+
# now return a precomputed constant with no allocations.
175+
@testset "auto-diffusion closure is allocation-free (#251)" begin
176+
f_oop(u, p, t) = SVector{2}(0.0, 0.0)
177+
f_iip(du, u, p, t) = (du .= 0; nothing)
178+
Γ = [1.0 0.3; 0.3 1.0]
179+
180+
function call_oop(g, n)
181+
s = SVector(0.1, 0.2)
182+
out = SVector(0.0, 0.0)
183+
for _ in 1:n
184+
out = g(s, nothing, 0.0)
185+
end
186+
return out
187+
end
188+
function call_iip!(g, du, n)
189+
u = [0.0, 0.0]
190+
for _ in 1:n
191+
g(du, u, nothing, 0.0)
192+
end
193+
return du
194+
end
195+
196+
@testset "OOP diagonal" begin
197+
ds = CoupledSDEs(f_oop, SVector(0.0, 0.0); noise_strength = 2.5)
198+
g = ds.integ.sol.prob.f.g
199+
@test g(SVector(0.0, 0.0), nothing, 0.0) === g(SVector(1.0, 1.0), nothing, 5.0)
200+
@test g(SVector(0.0, 0.0), nothing, 0.0) == SVector(2.5, 2.5)
201+
call_oop(g, 10) # warmup
202+
@test (@allocated call_oop(g, 10_000)) < 100
203+
end
204+
205+
@testset "OOP non-diagonal" begin
206+
ds = CoupledSDEs(f_oop, SVector(0.0, 0.0); covariance = Γ, noise_strength = 1.5)
207+
g = ds.integ.sol.prob.f.g
208+
@test g(SVector(0.0, 0.0), nothing, 0.0) === g(SVector(1.0, 1.0), nothing, 5.0)
209+
@test g(SVector(0.0, 0.0), nothing, 0.0) 1.5 .* sqrt(Γ)
210+
call_oop(g, 10)
211+
@test (@allocated call_oop(g, 10_000)) < 100
212+
end
213+
214+
@testset "IIP diagonal" begin
215+
ds = CoupledSDEs(f_iip, [0.0, 0.0]; noise_strength = 2.5)
216+
g = ds.integ.sol.prob.f.g
217+
du = zeros(2)
218+
g(du, [0.0, 0.0], nothing, 0.0)
219+
@test du == [2.5, 2.5]
220+
call_iip!(g, du, 10)
221+
@test (@allocated call_iip!(g, du, 10_000)) < 100
222+
end
223+
224+
@testset "IIP non-diagonal" begin
225+
ds = CoupledSDEs(f_iip, [0.0, 0.0]; covariance = Γ, noise_strength = 1.5)
226+
g = ds.integ.sol.prob.f.g
227+
DU = zeros(2, 2)
228+
g(DU, [0.0, 0.0], nothing, 0.0)
229+
@test DU 1.5 .* sqrt(Γ)
230+
function call_iip_mat!(g, DU, n)
231+
u = [0.0, 0.0]
232+
for _ in 1:n
233+
g(DU, u, nothing, 0.0)
234+
end
235+
return DU
236+
end
237+
call_iip_mat!(g, DU, 10)
238+
@test (@allocated call_iip_mat!(g, DU, 10_000)) < 100
239+
end
240+
end

0 commit comments

Comments
 (0)