Skip to content

Commit ed0defe

Browse files
authored
Merge branch 'main' into sde-seed
2 parents dde7f40 + 3662ffb commit ed0defe

9 files changed

Lines changed: 112 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
# v3.17
1+
# v3.16
22

33
- `reinit!(ds::CoupledSDEs)` now accepts a `seed::UInt64` keyword that re-seeds the noise process random number generator. The default is `rand(UInt64)`, so each `reinit!` produces a fresh, independent noise realization unless an explicit seed is given.
44
- The `CoupledSDEs` constructor's default `seed` is now `rand(UInt64)` instead of `UInt64(0)`. Behavior is unchanged (the SDE problem already used a random seed when given `0`), but `prob.seed` now stores the actual seed used, so a realization can be reproduced by reading it back and passing it to `reinit!`.
5+
- Support StochasticDiffEq v6.96+, where the diffusion function is no longer reachable via `integrator.g`. The extension now reads it from the `SDEProblem` (`prob.g`), which is a stable SciMLBase accessor that works across both old and new StochasticDiffEq versions.
6+
- Run the multiplicative and non-diagonal noise examples on the `CoupledSDEs` docs page so they actually integrate a trajectory.
57

68
# v3.15
79

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.17.0"
4+
version = "3.16.0"
55

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

docs/src/CoupledSDEs.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ function g!(du, u, p, t)
8484
return nothing
8585
end
8686
sde = CoupledSDEs(f!, rand(2)./10; g=g!)
87+
tr = trajectory(sde, 1.0)
88+
plot_trajectory(tr...)
8789
```
8890

8991
#### Non-diagonal noise
@@ -105,6 +107,8 @@ function g!(du, u, p, t)
105107
end
106108
diffeq = (alg = RKMilCommute(), reltol = 1e-3, abstol = 1e-3, dt=0.1)
107109
sde = CoupledSDEs(f!, rand(2)./10; g=g!, noise_prototype = zeros(2, 2), diffeq = diffeq)
110+
tr = trajectory(sde, 1.0)
111+
plot_trajectory(tr...)
108112
```
109113

110114
!!! warning

ext/src/CoupledSDEs.jl

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ function DynamicalSystemsBase.CoupledSDEs(
8585
end
8686

8787
solver, remaining = _decompose_into_sde_solver_and_remaining(diffeq)
88+
# The default `dtmin` from SciML scales with `tspan`. With our open-ended
89+
# `tspan = (0, 1e11)` it becomes ~1e-5, which is too coarse for the SDE
90+
# adaptive controller and causes spurious `DtLessThanMin` aborts.
91+
remaining = haskey(remaining, :dtmin) ? remaining : merge((dtmin = 0.0,), remaining)
8892
integ = __init(
8993
prob,
9094
solver;
@@ -280,21 +284,29 @@ function construct_diffusion_function(
280284
A = sqrt(cov)
281285
if IIP
282286
if isdiag(cov)
283-
g = (du, u, p, t) -> du .= noise_strength .* diag(A)
287+
diag_const = collect(noise_strength .* diag(A))
288+
g = let diag_const = diag_const
289+
(du, u, p, t) -> (du .= diag_const; nothing)
290+
end
284291
else
285-
g = (du, u, p, t) -> du .= noise_strength .* A
292+
A_const = collect(noise_strength .* A)
293+
g = let A_const = A_const
294+
(du, u, p, t) -> (du .= A_const; nothing)
295+
end
286296
noise_prototype = zeros(size(A))
287297
# ^ we could make this sparse to make it more performant
288298
end
289299
else
290300
if isdiag(cov)
291-
g = (u, p, t) -> SVector{length(diag(A)), eltype(A)}(
292-
diag(noise_strength .* A)
293-
)
301+
diag_const = SVector{D, eltype(A)}(diag(noise_strength .* A))
302+
g = let diag_const = diag_const
303+
(u, p, t) -> diag_const
304+
end
294305
else
295-
g = (u, p, t) -> SMatrix{size(A)..., eltype(A)}(
296-
noise_strength .* A
297-
)
306+
A_const = SMatrix{size(A)..., eltype(A)}(noise_strength .* A)
307+
g = let A_const = A_const
308+
(u, p, t) -> A_const
309+
end
298310
noise_prototype = zeros(size(A))
299311
end
300312
end

ext/src/classification.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ function diffusion_function(g, IIP, noise_prototype)
5050
end
5151

5252
function diffusion_function(ds::CoupledSDEs{IIP}) where {IIP}
53-
return diffusion_function(ds.integ.g, IIP, referrenced_sciml_prob(ds).noise_rate_prototype)
53+
prob = referrenced_sciml_prob(ds)
54+
return diffusion_function(prob.g, IIP, prob.noise_rate_prototype)
5455
end
5556

5657
"""

src/core_systems/continuous_time_ode.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ to access the solvers. The default `diffeq` is:
5252
$(DynamicalSystemsBase.DEFAULT_DIFFEQ)
5353
5454
`diffeq` keywords can also include `callback` for [event handling
55-
](http://docs.juliadiffeq.org/latest/features/callback_functions.html).
55+
](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/).
5656
5757
The convenience constructors `CoupledODEs(prob::ODEProblem [, diffeq])` and
5858
`CoupledODEs(ds::CoupledODEs [, diffeq])` are also available.

test/continuous.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ end
4141
prob = lorenz_oop.integ.sol.prob
4242
ds = CoupledODEs(prob, (alg = Vern9(), abstol = 0.0, reltol = 1.0e-6, verbose = false))
4343
@test ds.integ.alg isa Vern9
44-
@test ds.integ.opts.verbose == false
44+
# SciML moved from Bool verbose to a `DEVerbosity` struct of per-toggle verbosities.
45+
@test nameof(typeof(ds.integ.opts.verbose.linear_verbosity)) == :None
4546

4647
end

test/jacobian.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,23 @@ end
4848

4949
jac = jacobian(ds)
5050
@test jac isa GeneratedFunctionWrapper
51-
@test jac([1.0, 1.0], [], 0.0) == [-3 0;0 3]
51+
@test jac([1.0, 1.0], prob.p, 0.0) == [-3 0;0 3]
5252

5353
@testset "CoupledSDEs" begin
5454
# just to check if MTK @brownian does not give any problems
5555
using StochasticDiffEq
56-
@brownian β
56+
@brownians β
5757
eqs = [
5858
D(u[1]) ~ 3.0 * u[1] + β,
5959
D(u[2]) ~ -3.0 * u[2] + β,
6060
]
61-
@mtkbuild sys = System(eqs, t)
61+
@mtkcompile sys = System(eqs, t)
6262

6363
prob = SDEProblem(sys, [1.0, 1.0], (0.0, 1.0), jac = true)
6464
sde = CoupledSDEs(prob)
6565

6666
jac = jacobian(sde)
6767
@test jac isa GeneratedFunctionWrapper
68-
@test jac([1.0, 1.0], [], 0.0) == [-3 0; 0 3]
68+
@test jac([1.0, 1.0], prob.p, 0.0) == [-3 0; 0 3]
6969
end
7070
end

test/stochastic.jl

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,16 @@ end
7373
diffeq = (alg = SRA(), abstol = 1.0e-3, reltol = 1.0e-3, verbose = false)
7474
)
7575
@test lorenz_SRA.integ.alg isa SRA
76-
@test lorenz_SRA.integ.opts.verbose == false
76+
# SciML moved from Bool verbose to a `DEVerbosity` struct of per-toggle verbosities.
77+
@test nameof(typeof(lorenz_SRA.integ.opts.verbose.linear_verbosity)) == :None
7778

7879
# also test SDEproblem creation
7980
prob = lorenz_SRA.integ.sol.prob
8081

8182
ds = CoupledSDEs(prob, (alg = SRA(), abstol = 0.0, reltol = 1.0e-3, verbose = false))
8283

8384
@test ds.integ.alg isa SRA
84-
@test ds.integ.opts.verbose == false
85+
@test nameof(typeof(ds.integ.opts.verbose.linear_verbosity)) == :None
8586

8687
@test_throws ArgumentError CoupledSDEs(prob; diffeq = (alg = SRA(),))
8788

@@ -104,7 +105,10 @@ end
104105
corr = CoupledSDEs(f, zeros(2); covariance = [1 0.3; 0.3 1])
105106
corr_alt = CoupledSDEs(f, zeros(2); g = g, noise_prototype = zeros(2, 2))
106107
@test corr.noise_type == corr_alt.noise_type
107-
@test all(corr.integ.g(zeros(2), (), 0.0) .== corr_alt.integ.g(zeros(2), (), 0.0))
108+
@test all(
109+
DynamicalSystemsBase.referrenced_sciml_prob(corr).g(zeros(2), (), 0.0) .==
110+
DynamicalSystemsBase.referrenced_sciml_prob(corr_alt).g(zeros(2), (), 0.0)
111+
)
108112
end
109113

110114
@testset "ArgumentError" begin
@@ -202,5 +206,73 @@ end
202206
# Different explicit seed → different trajectory
203207
reinit!(ds; seed = UInt64(43)); step!(ds, 1.0); uc = copy(current_state(ds))
204208
@test ua != uc
209+
# Regression test for https://github.com/JuliaDynamics/DynamicalSystemsBase.jl/issues/251:
210+
# the auto-generated diffusion closure used to recompute its (constant) output on every
211+
# call, allocating ~1 KB per `step!` and dominating long integrations. The closure must
212+
# now return a precomputed constant with no allocations.
213+
@testset "auto-diffusion closure is allocation-free (#251)" begin
214+
f_oop(u, p, t) = SVector{2}(0.0, 0.0)
215+
f_iip(du, u, p, t) = (du .= 0; nothing)
216+
Γ = [1.0 0.3; 0.3 1.0]
217+
218+
function call_oop(g, n)
219+
s = SVector(0.1, 0.2)
220+
out = SVector(0.0, 0.0)
221+
for _ in 1:n
222+
out = g(s, nothing, 0.0)
223+
end
224+
return out
225+
end
226+
function call_iip!(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+
234+
@testset "OOP diagonal" begin
235+
ds = CoupledSDEs(f_oop, SVector(0.0, 0.0); noise_strength = 2.5)
236+
g = ds.integ.sol.prob.f.g
237+
@test g(SVector(0.0, 0.0), nothing, 0.0) === g(SVector(1.0, 1.0), nothing, 5.0)
238+
@test g(SVector(0.0, 0.0), nothing, 0.0) == SVector(2.5, 2.5)
239+
call_oop(g, 10) # warmup
240+
@test (@allocated call_oop(g, 10_000)) < 100
241+
end
242+
243+
@testset "OOP non-diagonal" begin
244+
ds = CoupledSDEs(f_oop, SVector(0.0, 0.0); covariance = Γ, noise_strength = 1.5)
245+
g = ds.integ.sol.prob.f.g
246+
@test g(SVector(0.0, 0.0), nothing, 0.0) === g(SVector(1.0, 1.0), nothing, 5.0)
247+
@test g(SVector(0.0, 0.0), nothing, 0.0) 1.5 .* sqrt(Γ)
248+
call_oop(g, 10)
249+
@test (@allocated call_oop(g, 10_000)) < 100
250+
end
251+
252+
@testset "IIP diagonal" begin
253+
ds = CoupledSDEs(f_iip, [0.0, 0.0]; noise_strength = 2.5)
254+
g = ds.integ.sol.prob.f.g
255+
du = zeros(2)
256+
g(du, [0.0, 0.0], nothing, 0.0)
257+
@test du == [2.5, 2.5]
258+
call_iip!(g, du, 10)
259+
@test (@allocated call_iip!(g, du, 10_000)) < 100
260+
end
261+
262+
@testset "IIP non-diagonal" begin
263+
ds = CoupledSDEs(f_iip, [0.0, 0.0]; covariance = Γ, noise_strength = 1.5)
264+
g = ds.integ.sol.prob.f.g
265+
DU = zeros(2, 2)
266+
g(DU, [0.0, 0.0], nothing, 0.0)
267+
@test DU 1.5 .* sqrt(Γ)
268+
function call_iip_mat!(g, DU, n)
269+
u = [0.0, 0.0]
270+
for _ in 1:n
271+
g(DU, u, nothing, 0.0)
272+
end
273+
return DU
274+
end
275+
call_iip_mat!(g, DU, 10)
276+
@test (@allocated call_iip_mat!(g, DU, 10_000)) < 100
205277
end
206278
end

0 commit comments

Comments
 (0)