-
-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Virtual Brownian Tree #65
Conversation
Yes, avoid MarsenneTwister here.
Is there a good way to do this for the CUDA.jl RNG as well? We can add that to the |
I emailed someone for comments on RNGs. But as is, I think the PR is really great and I don't see major issues with it. I would just test for the cache size and get a fully non-allocating version before merging. Handling RNG splitting for the GPU RNG can get its own issue. Handling other CPU-based RNGs, such as the "standard ones" could get another low priority issue, low priority since using Random123 isn't a bad idea anyways (but it's good to have an issue track the current limitation). We'll need to get this into the DiffEq docs as well, and get a full-scale test setup with Neural SDEs which will likely be the main use case. |
On the CURAND site https://github.com/JuliaAttic/CURAND.jl (which seems to be called for the RNGs in CUDA.jl https://github.com/JuliaGPU/CUDA.jl/blob/fd59518cf6080f74fc8e2ff309c31955009bb39a/src/random.jl#L13). There is CURAND_RNG_PSEUDO_PHILOX4_32_10 This should be a counter-based PRNG (https://sunoru.github.io/RandomNumbers.jl/stable/man/random123/#Random123-Family-1) for which we might be able to do the same. |
You should perhaps test the distributional properties of the resulting bridge. Can you also plot a trajectory for me to eyeball? |
Yes, put it to the test in the bridge test which tests the mean and variance. You'll see this is done for the NoiseProcess, so add a VBT noise process to that test. |
It looks like there is a problem with the distributional properties. For this test: using DiffEqNoiseProcess, DiffEqBase, Test, Random, DiffEqBase.EnsembleAnalysis
W = VirtualBrownianTree(0.0,0.0; Wend=1.0,tree_depth=3)
prob = NoiseProblem(W,(0.0,1.0))
function prob_func(prob,i,repeat)
# to re-instantiate PRNG
Wtmp = VirtualBrownianTree(0.0,0.0; Wend=1.0,tree_depth=3)
remake(prob, noise=Wtmp)
end
ensemble_prob = EnsembleProblem(prob, prob_func=prob_func)
@time sol = solve(ensemble_prob,dt=0.125,trajectories=10000)
# Spot check the mean and the variance
qs = 0:0.125:1
for i in 2:8
q = qs[i]
@test ≈(timestep_mean(sol,i),q,atol=1e-2) ##fails
@test ≈(timestep_meanvar(sol,i)[2],(1-q)*q,atol=1e-2) ##fails
end
@test ≈(timestep_mean(sol,1)[1],0.0,atol=1e-16)
@test ≈(timestep_meanvar(sol,1)[2],0.0,atol=1e-16)
@test ≈(timestep_mean(sol,Int(2^(W.tree_depth)+1))[1],W.W[end],atol=1e-16)
@test ≈(timestep_meanvar(sol,Int(2^(W.tree_depth)+1))[2],0.0,atol=1e-16)
using Plots
plt1 = plot(timeseries_steps_mean(sol),label=false)
plot!(qs,x->x, label="mean")
plt2 = plot(timeseries_steps_meanvar(sol)[2],label=false)
plot!(qs,x->(1-x)*x, label="var")
plt = plot(plt1,plt2, layout=(2,1)) The plot has some weird kinks: This originates from the stage where the cache is built. From a print within
(the small |
Looks like the mean is computed interpolating |
It was mentioned to me that we might want to make use of http://www.sprng.org/, so that should go into a follow up issue as well. |
@mschauer |
Yeah, it's because of how the extra terms are stored on the stack. It's a little odd but it works out in the end. Your change looks like it's the correct thing to do here. |
Here are two plots from the trajectories: using Plots
W = VirtualBrownianTree(0.0,0.0; tree_depth=10)
# step to cached value
dt = W.t[2]-W.t[1]
calculate_step!(W,dt,nothing,nothing)
Ws = []
for i in 1:length(W.t)-1
accept_step!(W,dt,nothing,nothing)
push!(Ws,W.curW)
end
plt = plot(Ws)
savefig(plt, "trajectory1.png")
W = VirtualBrownianTree(0.0,0.0; tree_depth=0)
# step to some interpolated values
dt = 0.001
calculate_step!(W,dt,nothing,nothing)
Ws = []
for i in 1:1000
accept_step!(W,dt,nothing,nothing)
push!(Ws,W.curW)
end
plt = plot(Ws)
savefig(plt, "trajectory2.png")
|
@frankschae This PR introduces a dependency on a C++ compiler because Random123.jl builds a few C++ files from source, see https://github.com/sunoru/Random123.jl/tree/234208c4cbbc1fb961dcd1087fd86bfe5b282684/deps. It would probably be worthwhile to figure out if there is a way to avoid this. Either by using BinaryBuilder, converting the C++ pieces to Julia, or considering an alternative dependency. |
I should be able to translate this to Julia (using llvmcall through SIMD.jl or VectorizationBase.jl). |
This PR adds a first version of a Virtual Brownian Tree (VBT) based on counter-based PRNGs.
The counter-based PRNGs should be good for our purpose:
http://www.thesalmons.org/john/random123/papers/random123sc11.pdf
https://github.com/sunoru/RandomNumbers.jl/issues/37
Though using some other RNGs with a split function (see, e.g., https://dl.acm.org/doi/pdf/10.1145/2578854.2503784) might be better. However, I didn't found any deterministic
split(seed)
function inBase.Random
,RandomNumbers
orRandom123
.. but I found quite a few discussions that simply using arandjump
in the sequence of a MarsenneTwister is dangerous.https://discourse.julialang.org/t/best-practices-for-parallel-generation-of-pseudo-random-numbers/26504
https://discourse.julialang.org/t/parallel-random-number-generation/1950/10
The VBT is build up very similar to
NoiseGrid
in general.The VBT has three unique arguments:
search_depth
keyword which restricts the depth in the tree that should be searched. Setting this argument is necessary at the moment due to the choice of the PRNG (see below).atol
or can be set manually.tree_depth
stores a small cache of time steps, noise values and seeds such that one can trade memory for speed if desired.Since there was no pre-defined splitting function (or at least I haven't fount one). I implemented our own splitting function
split_VBT_seed(rng::Random123.AbstractR123, parent_seed, current_depth, Nt)
This splitting function makes use of the following properties:
t_mid = (tstart - tend)/2
, (Nt+1)/2, and so on. Ultimately, the node with indexj
at levell
is given by seeded_counter + 1 + (2*j+1)*Nt/2^l .