ANN: DynamicGrids.jl

It’s been a long time coming, but finally it’s time to announce DynamicGrids.jl - a high performance grid-based modelling framework. It’s been developed at cesar for use in our ecological dispersal modelling, but it should be useful for a whole lot more than that.

DynamicGrids.jl is set up to easily write cellular-automata style rules, but can also simulate a wide range of other behaviors like jumps around the grid and interactions between multiple grids. These can also be composed together into large multi rule/multi grid models that still run really fast. Check out the docs and JuliaCon talk below for more details.

After a bunch of iterations I can also tentatively say it has a nice syntax! - you can specify the math and behaviors you want without much boilerplate. Anyway, check it out:

A live REPL simulation:

(note: the simulation is slowed down for viewing, not sped up! without visuals this model can run at ~1000fps on a regular desktop)

And check out my recent JuliaCon talk for a more thorough description:

PS: I’ve been working pretty hard on this and a bunch of other packages, but I’ve either published and not announced or not even published most of what I’m working on. But I’m in pretty serious COVID lockdown now, so what better time?

I hereby pledge to do a package ANN every week for the next 3 months!

27 Likes

Being too damn lazy to write it myself, I guess there is an easy implementation of the Game of Life here?

Yep: Life().

This is the code to run it in the repl:

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())

You can pass in args to Life() if you want other game of life behaviors besides the common 3, (2,3) rule.

But this is a complete script to implement GOL from scratch and run it:

const life_sum_states = 
   (false, false, false, true, false, false, false, false, false), 
   (false, false, true, true, false, false, false, false, false)
life = Neighbors(Moore(1)) do hood, state
    life_sum_states[state + 1][sum(hood) + 1]
end
output = REPLOutput(rand(Bool, 150, 200); tspan=1:200)
sim!(output, life)

So its ok to be lazy

3 Likes

Cool! Works great, thankyou.

1 Like

Hello, I was wondering how you created the grids to have the spacial distribution of particular countries (such as Australia or the US)? Is there an easy way to do this for any given geographical region?

I use GeoData.jl to load rasterized data as an array. It will just pass though the simulation, and your outputs should also be plottable spatial GeoArray.

Normally I’m also running simulations with some kind of auxiliary data that is spatial (like environmental rasters or precalculated growth rates), and should match the size and spatial extent of the the initialization grids. The rules are passed the current cell index, so you can use that to index into the aux array.

In Dispersal.jl (rules you see above in simulations) we use DynamicGrids capability to do this in a generic way. In this growth rule carrycap and rate can be from aux data:
https://github.com/cesaraustralia/Dispersal.jl/blob/master/src/growth.jl#L68-L69

The get method gets the data for the current index from aux data if rule.rate is e.g. Aux{:auxkey}(), or from another grid if it’s Grid{:gridkey}(). It just uses the value directly otherwise.

You pass in the aux array to the simulation Output constructor, using the aux keyword, along with the init conditions and the time span. If the aux data is an AbstractDimensionalArray (like a GeoArray) it will also synchronize the time dimension with the simulation, if it has one. There has been a lot of improvements in this syntax recently, so may not be the best documented.

GeoData.jl also lets you set initial conditions by Lat/Lon -

init[Lon(Near(144)), Lat(Near(-37)] = 1000.0

Or something similar.

1 Like

Thanks! I’ll try this out.

Hello, I have a question on how to use the Interactive outputs. I’ve tried doing it like it says on the github readme, but I get the following error:

Params must include a range or bounds field to generate interactive sliders…

Is there maybe an example I can follow on how to use this package? It seems really cool.

Good to put in an issue at DynamicGridsInteract.jl for this. It’s had a few changes (now built on ModelParameters.jl) so there could be an example that is out of date. Include the code you are trying to run with the issue.

But basically you have to wrap your parameters with ModelParameters.Param with a range (a range) or bounds (a tuple) field, and it will be able to find them and make sliders for them wherever they are in the rule object.

Hello! Thanks for replying so quickly. This is a piece of the code I was using (where the error occurs):

using DynamicGrids,DynamicGridsInteract
import FieldMetadata: limits, @limits, logscaled, @logscaled

const S1,S2,I = 0,1,2

init = fill(S1, 100, 100)

@logscaled @limits struct infectrule{N,PA,PB,PG,PM} <: NeighborhoodRule
    neighborhood::N | _          | _
    p_alpha::PA     | (0.0,1.0)  | true
    p_beta::PB      | (0.0,1.0)  | true
    p_gamma::PG     | (0.0,1.0)  | true
    p_mm::PM        | (0.0,1.0)  | true
end

I tried to make an easy example to begin with by copying the code you show in a video example of the interactive grids…

Yeah that’s the old method, sorry I didn’t update the docs. The readme links to the InteractModels.jl examples now.

This stuff is all very new and in flux - but you should find ModelParameters.jl is a lot nicer than using FieldMetadata.jl - there is not global state, everything is contained in the Param object.

A quick update, DynamicGrids now runs threaded and on GPUs!

It can run classic rules like stochastic forrest fire at over a billion times a second, and large multi-grid simulations are two orders of magnitude faster on GPU than a single CPU.

5 Likes