Skip to content

Commit a06bb9e

Browse files
authored
feat: reinit! seed kwarg (#254)
* feat: reinit! seed kwarg * format
1 parent 3662ffb commit a06bb9e

6 files changed

Lines changed: 79 additions & 17 deletions

File tree

CHANGELOG.md

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

3+
- `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.
4+
- 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!`.
35
- 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.
46
- Run the multiplicative and non-diagonal noise examples on the `CoupledSDEs` docs page so they actually integrate a trajectory.
57

Project.toml

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

66
[deps]
77
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
88
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
99
OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
10+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1011
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1112
Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
1213
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"

ext/StochasticSystemsBase.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ module StochasticSystemsBase
22

33
using DynamicalSystemsBase: DynamicalSystemsBase, SciMLBase, correct_state, CoupledODEs,
44
CoupledSDEs, StateSpaceSets, isinplace, _delete, set_parameter!,
5-
set_state!, dynamic_rule, isdeterministic, current_state,
5+
set_parameters!, set_state!, dynamic_rule, isdeterministic, current_state,
66
DynamicalSystemsBase, _set_parameter!, u_modified!,
77
additional_details, referrenced_sciml_prob, DEFAULT_DIFFEQ,
8-
SVector, SMatrix, current_parameters
8+
SVector, SMatrix, current_parameters, initial_state, initial_time
99
using SciMLBase: SDEProblem, AbstractSDEIntegrator, __init, SDEFunction, step!
10-
using StochasticDiffEq: SOSRA
10+
using StochasticDiffEq: SOSRA, setup_next_step!
1111
using LinearAlgebra
12+
import Random
1213

1314
include("src/CoupledSDEs.jl")
1415
include("src/classification.jl")

ext/src/CoupledSDEs.jl

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function DynamicalSystemsBase.CoupledSDEs(
3535
diffeq = DEFAULT_STOCH_DIFFEQ,
3636
noise_prototype = nothing,
3737
noise_process = nothing,
38-
seed = UInt64(0)
38+
seed = rand(UInt64)
3939
)
4040
IIP = isinplace(f, 4) # from SciMLBase
4141
if !isnothing(g)
@@ -108,6 +108,32 @@ end
108108
# This preserves the referrenced MTK system and the originally passed diffeq kwargs
109109
CoupledSDEs(ds::CoupledSDEs, diffeq) = CoupledSDEs(SDEProblem(ds), merge(ds.diffeq, diffeq))
110110

111+
"""
112+
reinit!(ds::CoupledSDEs, u = initial_state(ds); kwargs...) → ds
113+
114+
Re-initialize a [`CoupledSDEs`](@ref). In addition to the keywords accepted by
115+
`reinit!` for any [`DynamicalSystem`](@ref), the following is supported:
116+
117+
- `seed::UInt64`: re-seed the noise process random number generator. Defaults to
118+
`rand(UInt64)`, so by default every `reinit!` produces a fresh, independent noise
119+
realization. Pass an explicit `seed` to obtain a reproducible noise stream.
120+
"""
121+
function SciMLBase.reinit!(
122+
ds::CoupledSDEs, u::AbstractArray = DynamicalSystemsBase.initial_state(ds);
123+
p = DynamicalSystemsBase.current_parameters(ds),
124+
t0 = DynamicalSystemsBase.initial_time(ds),
125+
seed::UInt64 = rand(UInt64), kw...
126+
)
127+
DynamicalSystemsBase.set_parameters!(ds, p)
128+
# Adaptive solvers carry the previous run's last `dt`; resetting it ensures
129+
# `seed` reproducibility. Non-adaptive solvers must keep their user-specified `dt`.
130+
reset_dt = ds.integ.opts.adaptive
131+
SciMLBase.reinit!(ds.integ, u; reinit_dae = false, reset_dt, t0, kw...)
132+
Random.seed!(ds.integ.W.rng, seed)
133+
setup_next_step!(ds.integ.W, ds.integ.u, ds.integ.p)
134+
return ds
135+
end
136+
111137
"""
112138
CoupledSDEs(ds::CoupledODEs, p = current_parameters(ds); kwargs...)
113139
@@ -124,7 +150,7 @@ function DynamicalSystemsBase.CoupledSDEs(
124150
diffeq = DEFAULT_STOCH_DIFFEQ,
125151
noise_prototype = nothing,
126152
noise_process = nothing,
127-
seed = UInt64(0)
153+
seed = rand(UInt64)
128154
)
129155
prob = referrenced_sciml_prob(ds)
130156
# we want the symbolic jacobian to be transfered over

src/core_systems/continuous_time_sde.jl

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ found in the [online docs for `CoupledSDEs`](@ref defining-stochastic-dynamics).
7676
- `noise_process`: stochastic process as provided by [DiffEqNoiseProcess.jl](https://docs.sciml.ai/DiffEqNoiseProcess/stable/) (default `nothing`, i.e. standard Wiener process)
7777
- `t0`: initial time (default `0.0`)
7878
- `diffeq`: DiffEq solver settings (see below)
79-
- `seed`: random number seed (default `UInt64(0)`)
79+
- `seed`: random number seed (default `rand(UInt64)`, so each `CoupledSDEs` produces a different noise realization unless a seed is given explicitly)
8080
8181
## DifferentialEquations.jl interfacing
8282
@@ -112,12 +112,3 @@ struct CoupledSDEs{IIP, D, I, P} <: ContinuousTimeDynamicalSystem
112112
diffeq # isn't parameterized because it is only used for display
113113
noise_type::NamedTuple
114114
end
115-
116-
function SciMLBase.reinit!(
117-
ds::CoupledSDEs, u::AbstractArray = initial_state(ds);
118-
p = current_parameters(ds), t0 = initial_time(ds), kw...
119-
)
120-
set_parameters!(ds, p)
121-
reinit!(ds.integ, u; t0, kw...)
122-
return ds
123-
end

test/stochastic.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,47 @@ end
168168
end
169169
end
170170

171+
@testset "seed" begin
172+
f(u, p, t) = -u
173+
174+
@testset "constructor default is randomized" begin
175+
# Two CoupledSDEs without explicit seed get different seeds and different trajectories.
176+
ds1 = CoupledSDEs(f, [1.0])
177+
ds2 = CoupledSDEs(f, [1.0])
178+
@test ds1.integ.sol.prob.seed != 0
179+
@test ds1.integ.sol.prob.seed != ds2.integ.sol.prob.seed
180+
step!(ds1, 1.0); step!(ds2, 1.0)
181+
@test current_state(ds1) != current_state(ds2)
182+
end
183+
184+
@testset "constructor explicit seed is reproducible" begin
185+
seed = UInt64(20250502)
186+
ds1 = CoupledSDEs(f, [1.0]; seed = seed)
187+
ds2 = CoupledSDEs(f, [1.0]; seed = seed)
188+
step!(ds1, 1.0); step!(ds2, 1.0)
189+
@test current_state(ds1) current_state(ds2)
190+
end
191+
192+
@testset "reinit! default reseeds with fresh randomness" begin
193+
ds = CoupledSDEs(f, [1.0]; seed = UInt64(1))
194+
reinit!(ds); step!(ds, 1.0); ua = copy(current_state(ds))
195+
reinit!(ds); step!(ds, 1.0); ub = copy(current_state(ds))
196+
# Different default seeds → different trajectories
197+
@test ua != ub
198+
end
199+
200+
@testset "reinit! accepts explicit seed kwarg" begin
201+
ds = CoupledSDEs(f, [1.0])
202+
# Same explicit seed → same noise stream after reinit
203+
reinit!(ds; seed = UInt64(42)); step!(ds, 1.0); ua = copy(current_state(ds))
204+
reinit!(ds; seed = UInt64(42)); step!(ds, 1.0); ub = copy(current_state(ds))
205+
@test ua ub
206+
# Different explicit seed → different trajectory
207+
reinit!(ds; seed = UInt64(43)); step!(ds, 1.0); uc = copy(current_state(ds))
208+
@test ua != uc
209+
end
210+
end
211+
171212
# Regression test for https://github.com/JuliaDynamics/DynamicalSystemsBase.jl/issues/251:
172213
# the auto-generated diffusion closure used to recompute its (constant) output on every
173214
# call, allocating ~1 KB per `step!` and dominating long integrations. The closure must

0 commit comments

Comments
 (0)