Skip to content

Commit b1c20e4

Browse files
committed
feat: remove allocations in construct_diffusion_function
1 parent 84fb0f3 commit b1c20e4

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.15.6"
4+
version = "3.15.7"
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
@@ -254,21 +254,29 @@ function construct_diffusion_function(
254254
A = sqrt(cov)
255255
if IIP
256256
if isdiag(cov)
257-
g = (du, u, p, t) -> du .= noise_strength .* diag(A)
257+
diag_const = collect(noise_strength .* diag(A))
258+
g = let diag_const = diag_const
259+
(du, u, p, t) -> (du .= diag_const; nothing)
260+
end
258261
else
259-
g = (du, u, p, t) -> du .= noise_strength .* A
262+
A_const = collect(noise_strength .* A)
263+
g = let A_const = A_const
264+
(du, u, p, t) -> (du .= A_const; nothing)
265+
end
260266
noise_prototype = zeros(size(A))
261267
# ^ we could make this sparse to make it more performant
262268
end
263269
else
264270
if isdiag(cov)
265-
g = (u, p, t) -> SVector{length(diag(A)), eltype(A)}(
266-
diag(noise_strength .* A)
267-
)
271+
diag_const = SVector{D, eltype(A)}(diag(noise_strength .* A))
272+
g = let diag_const = diag_const
273+
(u, p, t) -> diag_const
274+
end
268275
else
269-
g = (u, p, t) -> SMatrix{size(A)..., eltype(A)}(
270-
noise_strength .* A
271-
)
276+
A_const = SMatrix{size(A)..., eltype(A)}(noise_strength .* A)
277+
g = let A_const = A_const
278+
(u, p, t) -> A_const
279+
end
272280
noise_prototype = zeros(size(A))
273281
end
274282
end

test/stochastic.jl

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

0 commit comments

Comments
 (0)