Skip to content

Commit 0c1915d

Browse files
Merge pull request #432 from ChrisRackauckas-Claude/update-scimlbase-v3-ensemble-interface
Update ensemble interface for SciMLBase v3
2 parents 0af6962 + 90813db commit 0c1915d

20 files changed

Lines changed: 81 additions & 48 deletions

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ MuladdMacro = "0.2"
5959
OpenCL = "0.9, 0.10"
6060
Parameters = "0.12"
6161
RecursiveArrayTools = "3.37, 4"
62-
SciMLBase = "2.144"
62+
SciMLBase = "3"
6363
Setfield = "1"
6464
SimpleDiffEq = "1.11"
6565
SimpleNonlinearSolve = "2"

src/algorithms.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ u0 = Float32[1.0; 0.0; 0.0]
7777
tspan = (0.0f0, 100.0f0)
7878
p = [10.0f0, 28.0f0, 8 / 3.0f0]
7979
prob = ODEProblem(lorenz, u0, tspan, p)
80-
prob_func = (prob, i, repeat) -> remake(prob, p = rand(Float32, 3) .* p)
80+
prob_func = (prob, ctx) -> remake(prob, p = rand(Float32, 3) .* p)
8181
monteprob = EnsembleProblem(prob, prob_func = prob_func, safetycopy = false)
8282
@time sol = solve(monteprob, Tsit5(), EnsembleGPUArray(CUDADevice()),
8383
trajectories = 10_000, saveat = 1.0f0)
@@ -143,7 +143,7 @@ u0 = @SVector [1.0f0; 0.0f0; 0.0f0]
143143
tspan = (0.0f0, 10.0f0)
144144
p = @SVector [10.0f0, 28.0f0, 8 / 3.0f0]
145145
prob = ODEProblem{false}(lorenz, u0, tspan, p)
146-
prob_func = (prob, i, repeat) -> remake(prob, p = (@SVector rand(Float32, 3)) .* p)
146+
prob_func = (prob, ctx) -> remake(prob, p = (@SVector rand(Float32, 3)) .* p)
147147
monteprob = EnsembleProblem(prob, prob_func = prob_func, safetycopy = false)
148148
149149
@time sol = solve(

src/solve.jl

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,30 @@ function SciMLBase.__solve(
1010
};
1111
trajectories, batch_size = trajectories,
1212
unstable_check = (dt, u, p, t) -> false, adaptive = true,
13+
seed = nothing,
14+
rng = nothing,
15+
rng_func = SciMLBase.default_rng_func,
1316
kwargs...
1417
)
1518
if trajectories == 1
1619
return SciMLBase.__solve(
1720
ensembleprob, alg, EnsembleSerial(); trajectories = 1,
18-
kwargs...
21+
seed, rng, rng_func, kwargs...
1922
)
2023
end
2124

25+
# Pre-generate per-trajectory seeds for reproducibility (matching SciMLBase v3 protocol)
26+
sim_seeds = (rng !== nothing || seed !== nothing) ?
27+
SciMLBase.generate_sim_seeds(rng, seed, trajectories) : nothing
28+
29+
# Bundle ensemble RNG state for passing to SciMLBase.solve_batch (CPU offload path)
30+
ensemble_rng_state = (;
31+
sim_seeds,
32+
_solve_rng_mode = Val(:none),
33+
rng_func,
34+
master_rng = rng,
35+
)
36+
2237
cpu_trajectories = (
2338
(
2439
ensemblealg isa EnsembleGPUArray ||
@@ -53,8 +68,8 @@ function SciMLBase.__solve(
5368

5469
function f()
5570
return SciMLBase.solve_batch(
56-
ensembleprob, _alg, EnsembleThreads(), cpu_II, nothing;
57-
kwargs...
71+
ensembleprob, _alg, EnsembleThreads(), cpu_II, nothing,
72+
ensemble_rng_state; kwargs...
5873
)
5974
end
6075

@@ -69,6 +84,7 @@ function SciMLBase.__solve(
6984
time = @elapsed sol = batch_solve(
7085
ensembleprob, alg, ensemblealg,
7186
1:gpu_trajectories, adaptive;
87+
sim_seeds, rng_func, master_rng = rng,
7288
unstable_check = unstable_check, kwargs...
7389
)
7490
if cpu_trajectories != 0
@@ -83,6 +99,7 @@ function SciMLBase.__solve(
8399
similar(
84100
batch_solve(
85101
ensembleprob, alg, ensemblealg, 1:batch_size, adaptive;
102+
sim_seeds, rng_func, master_rng = rng,
86103
unstable_check = unstable_check, kwargs...
87104
),
88105
0
@@ -100,6 +117,7 @@ function SciMLBase.__solve(
100117
end
101118
batch_data = batch_solve(
102119
ensembleprob, alg, ensemblealg, I, adaptive;
120+
sim_seeds, rng_func, master_rng = rng,
103121
unstable_check = unstable_check, kwargs...
104122
)
105123
if ensembleprob.reduction !== SciMLBase.DEFAULT_REDUCTION
@@ -120,6 +138,7 @@ function SciMLBase.__solve(
120138
end
121139
x = batch_solve(
122140
ensembleprob, alg, ensemblealg, I, adaptive;
141+
sim_seeds, rng_func, master_rng = rng,
123142
unstable_check = unstable_check, kwargs...
124143
)
125144
yield()
@@ -145,10 +164,20 @@ function SciMLBase.__solve(
145164
end
146165
end
147166

167+
function _make_ensemble_context(i, sim_seeds, rng_func, master_rng)
168+
sim_seed = sim_seeds !== nothing ? sim_seeds[i] : nothing
169+
pre_ctx = SciMLBase.EnsembleContext(i, 1, 0, sim_seed, nothing, master_rng)
170+
sim_rng = rng_func(pre_ctx)
171+
return @set pre_ctx.rng = sim_rng
172+
end
173+
148174
function batch_solve(
149175
ensembleprob, alg,
150176
ensemblealg::Union{EnsembleArrayAlgorithm, EnsembleKernelAlgorithm}, I,
151177
adaptive;
178+
sim_seeds = nothing,
179+
rng_func = SciMLBase.default_rng_func,
180+
master_rng = nothing,
152181
kwargs...
153182
)
154183
@assert !isempty(I)
@@ -157,17 +186,18 @@ function batch_solve(
157186
return if ensemblealg isa EnsembleGPUKernel
158187
if ensembleprob.safetycopy
159188
probs = map(I) do i
189+
ctx = _make_ensemble_context(i, sim_seeds, rng_func, master_rng)
160190
make_prob_compatible(
161191
ensembleprob.prob_func(
162192
deepcopy(ensembleprob.prob),
163-
i,
164-
1
193+
ctx
165194
)
166195
)
167196
end
168197
else
169198
probs = map(I) do i
170-
make_prob_compatible(ensembleprob.prob_func(ensembleprob.prob, i, 1))
199+
ctx = _make_ensemble_context(i, sim_seeds, rng_func, master_rng)
200+
make_prob_compatible(ensembleprob.prob_func(ensembleprob.prob, ctx))
171201
end
172202
end
173203
# Using inner saveat requires all of them to be of same size,
@@ -246,7 +276,7 @@ function batch_solve(
246276
ReturnCode.Terminated :
247277
ReturnCode.Success
248278
),
249-
i
279+
_make_ensemble_context(I[i], sim_seeds, rng_func, master_rng)
250280
)[1]
251281
end
252282
for i in eachindex(probs)
@@ -258,11 +288,13 @@ function batch_solve(
258288
else
259289
if ensembleprob.safetycopy
260290
probs = map(I) do i
261-
ensembleprob.prob_func(deepcopy(ensembleprob.prob), i, 1)
291+
ctx = _make_ensemble_context(i, sim_seeds, rng_func, master_rng)
292+
ensembleprob.prob_func(deepcopy(ensembleprob.prob), ctx)
262293
end
263294
else
264295
probs = map(I) do i
265-
ensembleprob.prob_func(ensembleprob.prob, i, 1)
296+
ctx = _make_ensemble_context(i, sim_seeds, rng_func, master_rng)
297+
ensembleprob.prob_func(ensembleprob.prob, ctx)
266298
end
267299
end
268300
u0 = reduce(hcat, Array(probs[i].u0) for i in 1:length(I))
@@ -316,7 +348,7 @@ function batch_solve(
316348
stats = sol.stats,
317349
retcode = sol.retcode
318350
),
319-
i
351+
_make_ensemble_context(I[i], sim_seeds, rng_func, master_rng)
320352
)[1]
321353
for i in 1:length(probs)
322354
]
@@ -339,7 +371,7 @@ function batch_solve(
339371
stats = sol.stats,
340372
retcode = sol.retcode
341373
),
342-
i
374+
_make_ensemble_context(I[i], sim_seeds, rng_func, master_rng)
343375
)[1]
344376
for i in 1:length(probs)
345377
]
@@ -539,13 +571,13 @@ function ChainRulesCore.rrule(
539571
end
540572

541573
function solve_batch(
542-
prob, alg, ensemblealg::EnsembleThreads, II, pmap_batch_size;
543-
kwargs...
574+
prob, alg, ensemblealg::EnsembleThreads, II, pmap_batch_size,
575+
ensemble_rng_state; kwargs...
544576
)
545577
if length(II) == 1 || Threads.nthreads() == 1
546578
return SciMLBase.solve_batch(
547-
prob, alg, EnsembleSerial(), II, pmap_batch_size;
548-
kwargs...
579+
prob, alg, EnsembleSerial(), II, pmap_batch_size,
580+
ensemble_rng_state; kwargs...
549581
)
550582
end
551583

@@ -565,8 +597,8 @@ function solve_batch(
565597
I_local = II[(batch_size * (i - 1) + 1):(batch_size * i)]
566598
end
567599
SciMLBase.solve_batch(
568-
prob, alg, EnsembleSerial(), I_local, pmap_batch_size;
569-
kwargs...
600+
prob, alg, EnsembleSerial(), I_local, pmap_batch_size,
601+
ensemble_rng_state; kwargs...
570602
)
571603
end
572604
return SciMLBase.tighten_container_eltype(batch_data)

test/distributed_multi_gpu.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ addprocs(2)
1414
p = (10.0f0, 28.0f0, 8 / 3.0f0)
1515
Random.seed!(1)
1616
pre_p_distributed = [rand(Float32, 3) for i in 1:10]
17-
function prob_func_distributed(prob, i, repeat)
18-
remake(prob, p = pre_p_distributed[i] .* p)
17+
function prob_func_distributed(prob, ctx)
18+
remake(prob, p = pre_p_distributed[ctx.sim_id] .* p)
1919
end
2020
end
2121

test/ensemblegpuarray.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ tspan = (0.0f0, 100.0f0)
1313
p = (10.0f0, 28.0f0, 8 / 3.0f0)
1414
prob = ODEProblem(lorenz, u0, tspan, p)
1515
const pre_p = [rand(Float32, 3) for i in 1:10]
16-
prob_func = (prob, i, repeat) -> remake(prob, p = pre_p[i] .* p)
16+
prob_func = (prob, ctx) -> remake(prob, p = pre_p[ctx.sim_id] .* p)
1717
monteprob = EnsembleProblem(prob, prob_func = prob_func)
1818

1919
@info "Explicit Methods"
@@ -254,7 +254,8 @@ u0 = Float32[1.0; 0.0; 0.0]
254254
tspan = (0.0f0, 100.0f0)
255255
p = LorenzParameters(10.0f0, 28.0f0, 8 / 3.0f0)
256256
prob = ODEProblem(lorenzp, u0, tspan, p)
257-
function param_prob_func(prob, i, repeat)
257+
function param_prob_func(prob, ctx)
258+
i = ctx.sim_id
258259
p = LorenzParameters(
259260
pre_p[i][1] .* 10.0f0,
260261
pre_p[i][2] .* 28.0f0,
@@ -277,11 +278,11 @@ saveats = 1.0f0:1.0f0:10.0f0
277278
prob = ODEProblem(lorenz, u0, tspan, p)
278279
monteprob = EnsembleProblem(
279280
prob_jac,
280-
prob_func = (prob, i, repeat) -> remake(
281+
prob_func = (prob, ctx) -> remake(
281282
prob;
282283
tspan = (
283284
0.0f0,
284-
saveats[i],
285+
saveats[ctx.sim_id],
285286
)
286287
)
287288
)

test/ensemblegpuarray_inputtypes.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ u0 = [
1515
tspan = (0.0f0, 100.0f0)
1616
p = (10.0f0, 28.0f0, 8 / 3.0f0)
1717
prob = ODEProblem{true, SciMLBase.FullSpecialize}(lorenz, u0, tspan, p)
18-
prob_func = (prob, i, repeat) -> remake(prob, p = rand(Float32, 3) .* p)
18+
prob_func = (prob, ctx) -> remake(prob, p = rand(Float32, 3) .* p)
1919
monteprob = EnsembleProblem(prob, prob_func = prob_func)
2020
@time sol = solve(
2121
monteprob, Tsit5(), EnsembleGPUArray(backend), trajectories = 10_000,
@@ -27,7 +27,7 @@ u0 = [1f0u"m";0u"m";0u"m"]
2727
tspan = (0.0f0u"s",100.0f0u"s")
2828
p = (10.0f0,28.0f0,8/3f0)
2929
prob = ODEProblem(lorenz,u0,tspan,p)
30-
prob_func = (prob,i,repeat) -> remake(prob,p=rand(Float32,3).*p)
30+
prob_func = (prob,ctx) -> remake(prob,p=rand(Float32,3).*p)
3131
monteprob = EnsembleProblem(prob, prob_func = prob_func)
3232
@test_broken sol = solve(monteprob,Tsit5(),EnsembleGPUArray(),trajectories=10_000,saveat=1.0f0u"s")
3333
=#

test/ensemblegpuarray_oop.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ u0 = SA[1.0f0; 0.0f0; 0.0f0]
3232
tspan = (0.0f0, 100.0f0)
3333
p = SA[10.0f0, 28.0f0, 8 / 3.0f0]
3434
prob = ODEProblem(func, u0, tspan, p)
35-
prob_func = (prob, i, repeat) -> remake(prob, p = rand(Float32, 3) .* p)
35+
prob_func = (prob, ctx) -> remake(prob, p = rand(Float32, 3) .* p)
3636
monteprob = EnsembleProblem(prob, prob_func = prob_func, safetycopy = false)
3737
@time sol = solve(
3838
monteprob, Tsit5(), EnsembleGPUArray(backend), trajectories = 10_000,

test/ensemblegpuarray_sde.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ tspan = (0.0f0, 10.0f0)
1919
p = (10.0f0, 28.0f0, 8 / 3.0f0)
2020
prob = SDEProblem(lorenz, multiplicative_noise, u0, tspan, p)
2121
const pre_p = [rand(Float32, 3) for i in 1:10]
22-
prob_func = (prob, i, repeat) -> remake(prob, p = pre_p[i] .* p)
22+
prob_func = (prob, ctx) -> remake(prob, p = pre_p[ctx.sim_id] .* p)
2323
monteprob = EnsembleProblem(prob, prob_func = prob_func)
2424

2525
@info "Explicit Methods"
@@ -54,7 +54,7 @@ tspan = (0.0f0, 10.0f0)
5454
p = (10.0f0, 28.0f0, 8 / 3.0f0)
5555
prob = SDEProblem(lorenz, multiplicative_noise, u0, tspan, p, noise_rate_prototype = NRate)
5656

57-
prob_func = (prob, i, repeat) -> remake(prob, p = p)
57+
prob_func = (prob, ctx) -> remake(prob, p = p)
5858
monteprob = EnsembleProblem(prob, prob_func = prob_func)
5959

6060
@test_throws "Incompatible problem detected. EnsembleGPUArray currently requires `prob.noise_rate_prototype === nothing`, i.e. only diagonal noise is currently supported. Track https://github.com/SciML/DiffEqGPU.jl/issues/331 for more information." sol = solve(

test/gpu_kernel_de/conversions.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ u0 = [1.0f0; 0.0f0; 0.0f0]
1515
tspan = (0.0f0, 10.0f0)
1616
p = [10.0f0, 28.0f0, 8 / 3.0f0]
1717
prob = ODEProblem{false}(lorenz, u0, tspan, p)
18-
prob_func = (prob, i, repeat) -> remake(prob, p = (@SVector rand(Float32, 3)) .* p)
18+
prob_func = (prob, ctx) -> remake(prob, p = (@SVector rand(Float32, 3)) .* p)
1919
monteprob = EnsembleProblem(prob, prob_func = prob_func, safetycopy = false)
2020

2121
## Don't test the problems in which GPUs don't support FP64 completely yet

test/gpu_kernel_de/finite_diff.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ tspan = (0.0f0, 10.0f0)
1414

1515
prob = ODEProblem{false}(f, u0, tspan, p)
1616

17-
prob_func = (prob, i, repeat) -> remake(prob, p = p)
17+
prob_func = (prob, ctx) -> remake(prob, p = p)
1818
monteprob = EnsembleProblem(prob, prob_func = prob_func, safetycopy = false)
1919

2020
osol = solve(prob, Rodas5P(), dt = 0.01f0, save_everystep = false)

0 commit comments

Comments
 (0)