在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:cesaraustralia/DynamicGrids.jl开源软件地址:https://github.com/cesaraustralia/DynamicGrids.jl开源编程语言:Julia 100.0%开源软件介绍:DynamicGrids is a generalised framework for building high-performance grid-based spatial simulations, including cellular automata, but also allowing a wider range of behaviours like random jumps and interactions between multiple grids. It is extended by Dispersal.jl for modelling organism dispersal processes. DynamicGridsGtk.jl provides a simple live interface, while DynamicGridsInteract.jl also has live control over model parameters while the simulation runs: real-time visual feedback for manual parametrisation and model exploration. DynamicGrids can run rules on single CPUs, threaded CPUs, and on CUDA GPUs. Simulation run-time is usually measured in fractions of a second. A dispersal simulation with quarantine interactions, using Dispersal.jl, custom rules and the GtkOuput from DynamicGridsGtk. Note that this is indicative of the real-time frame-rate on a laptop. A DynamicGrids.jl simulation is run with a script like this one
running the included game of life model using DynamicGrids, Crayons
init = rand(Bool, 150, 200)
output = REPLOutput(init; tspan=1:200, fps=30, color=Crayon(foreground=:red, background=:black, bold=true))
sim!(output, Life())
# Or define it from scratch (yes this is actually the whole implementation!)
life = Neighbors(Moore(1)) do data, hood, state, I
born_survive = (false, false, false, true, false, false, false, false, false),
(false, false, true, true, false, false, false, false, false)
born_survive[state + 1][sum(hood) + 1]
end
sim!(output, life) A game of life simulation being displayed directly in a terminal. ConceptsThe framework is highly customisable, but there are some central ideas that define how a simulation works: grids, rules, and outputs. GridsSimulations run over one or many grids, derived from Grid contentsOften grids contain simple values of some kind of NOTE: Grids of mutable objects (e.g InitThe init = rand(Float32, 100, 100) An output = ArrayOutput(init; tspan=1:100) or passed in to sim!(output, ruleset; init=init) For multiple grids, init = (predator=rand(100, 100), prey=(rand(100, 100)) Handling and passing of the correct grids to a Dimensional or spatial Non-Number GridsGrids containing custom and non- However, for any multi-values grid element type, you will need to define a method of
RulesRules hold the parameters for running a simulation, and are applied in
ruleset = Ruleset(Life(2, 3); opt=SparseOpt(), proc=CuGPU()) Multiple rules can be combined in a ruleset = Ruleset(rule1, rule2; timestep=Day(1), opt=SparseOpt(), proc=ThreadedCPU()) OutputOutputs
are ways of storing or viewing a simulation. They can be used
interchangeably depending on your needs: output = ArrayOutput(init; tspan=1:10) The output = REPLOutput(init; tspan=1:100)
DynamicGridsInteract.jl provides simulation interfaces for use in Juno, Jupyter, web pages or electron apps, with live interactive control over parameters, using ModelParameters.jl. DynamicGridsGtk.jl is a simple graphical output for Gtk. These packages are kept separate to avoid dependencies when being used in non-graphical simulations. Outputs are also easy to write, and high performance applications may benefit
from writing a custom output to reduce memory use, or using Example: Forest FireThis example implements the classic stochastic forest fire model in a few
different ways, and benchmarks them. Note you will need ImageMagick.jl
installed for First we will define a Forest Fire algorithm that sets the current cell to burning, if a neighbor is burning. Dead cells can come back to life, and living cells can spontaneously catch fire: using DynamicGrids, ColorSchemes, Colors, BenchmarkTools
const DEAD, ALIVE, BURNING = 1, 2, 3
neighbors_rule = let prob_combustion=0.0001, prob_regrowth=0.01
Neighbors(Moore(1)) do data, neighborhood, cell, I
if cell == ALIVE
if BURNING in neighborhood
BURNING
else
rand() <= prob_combustion ? BURNING : ALIVE
end
elseif cell == BURNING
DEAD
else
rand() <= prob_regrowth ? ALIVE : DEAD
end
end
end
# Set up the init array and output (using a Gtk window)
init = fill(ALIVE, 400, 400)
output = GifOutput(init;
filename="forestfire.gif",
tspan=1:200,
fps=25,
minval=DEAD, maxval=BURNING,
scheme=ColorSchemes.rainbow,
zerocolor=RGB24(0.0)
)
# Run the simulation, which will save a gif when it completes
sim!(output, neighbors_rule) Timing the simulation for 200 steps, the performance is quite good. This particular CPU has six cores, and we get a 5.25x speedup by using all of them, which indicates good scaling: bench_output = ResultOutput(init; tspan=1:200)
julia>
@btime sim!($bench_output, $neighbors_rule);
477.183 ms (903 allocations: 2.57 MiB)
julia> @btime sim!($bench_output, $neighbors_rule; proc=ThreadedCPU());
91.321 ms (15188 allocations: 4.07 MiB) We can also invert the algorithm, setting cells in the neighborhood to burning
if the current cell is burning, by using the setneighbors_rule = let prob_combustion=0.0001, prob_regrowth=0.01
SetNeighbors(Moore(1)) do data, neighborhood, cell, I
if cell == DEAD
if rand() <= prob_regrowth
data[I...] = ALIVE
end
elseif cell == BURNING
for pos in positions(neighborhood, I)
if data[pos...] == ALIVE
data[pos...] = BURNING
end
end
data[I...] = DEAD
elseif cell == ALIVE
if rand() <= prob_combustion
data[I...] = BURNING
end
end
end
end Note: we are not using And in this case (a fairly sparse simulation), this rule is faster: julia> @btime sim!($bench_output, $setneighbors_rule);
261.969 ms (903 allocations: 2.57 MiB)
julia> @btime sim!($bench_output, $setneighbors_rule; proc=ThreadedCPU());
65.489 ms (7154 allocations: 3.17 MiB) But the scaling is not quite as good, at 3.9x for 6 cores. The first method may be better on a machine with a lot of cores. Last, we slightly rewrite these rules for GPU, as This way we call using CUDAKernels, CUDA
randomiser = SetGrid{Tuple{},:rand}() do randgrid
CUDA.rand!(parent(randgrid))
end Now we define a Neighbors version for GPU, using the neighbors_gpu = let prob_combustion=0.0001, prob_regrowth=0.01
Neighbors{Tuple{:ff,:rand},:ff}(Moore(1)) do data, neighborhood, (cell, rand), I
if cell == ALIVE
if BURNING in neighborhood
BURNING
else
rand <= prob_combustion ? BURNING : ALIVE
end
elseif cell == BURNING
DEAD
else
rand <= prob_regrowth ? ALIVE : DEAD
end
end
end And a SetNeighbors version for GPU: setneighbors_gpu = let prob_combustion=0.0001, prob_regrowth=0.01
SetNeighbors{Tuple{:ff,:rand},:ff}(Moore(1)) do data, neighborhood, (cell, rand), I
if cell == DEAD
if rand <= prob_regrowth
data[:ff][I...] = ALIVE
end
elseif cell == BURNING
for pos in positions(neighborhood, I)
if data[:ff][pos...] == ALIVE
data[:ff][pos...] = BURNING
end
end
data[:ff][I...] = DEAD
elseif cell == ALIVE
if rand <= prob_combustion
data[:ff][I...] = BURNING
end
end
end
end Now benchmark both version on a GTX 1080 GPU. Despite the overhead of reading and writing two grids, this turns out to be even faster again: bench_output_rand = ResultOutput((ff=init, rand=zeros(size(init))); tspan=1:200)
julia> @btime sim!($bench_output_rand, $randomiser, $neighbors_gpu; proc=CuGPU());
30.621 ms (186284 allocations: 17.19 MiB)
julia> @btime sim!($bench_output_rand, $randomiser, $setneighbors_gpu; proc=CuGPU());
22.685 ms (147339 allocations: 15.61 MiB) That is, we are running the rule at a rate of 1.4 billion times per second.
These timings could be improved (maybe 10-20%) by using grids of |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论