Skip to content

Commit

Permalink
Merge pull request #52 from cesaraustralia/chain
Browse files Browse the repository at this point in the history
Refactor, documentation and more tests
  • Loading branch information
rafaqz authored Jul 31, 2020
2 parents c775eed + cda4806 commit 6cf1812
Show file tree
Hide file tree
Showing 25 changed files with 679 additions and 483 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "DynamicGrids"
uuid = "a5dba43e-3abc-5203-bfc5-584ca68d3f5b"
authors = ["Rafael Schouten <[email protected]>"]
version = "0.10.1"
version = "0.10.2"

[deps]
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
Expand Down
3 changes: 1 addition & 2 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Documenter, DynamicGrids
using DynamicGrids: applyrule, applyrule!, setneighbor!, mapsetneighbor!, neighbors, sumneighbors,
AbstractSimData, SimData, GridData, ReadableGridData, WritableGridData
using DynamicGrids: AbstractSimData, SimData, GridData, ReadableGridData, WritableGridData

makedocs(
modules = [DynamicGrids],
Expand Down
73 changes: 52 additions & 21 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
DynamicGrids
```

## Running simulations

```@docs
sim!
resume!
```

## Rules

Rules define simulation behaviour. They hold data relevant to the simulation,
Expand All @@ -14,7 +21,6 @@ any number of grids.
```@docs
Ruleset
Rule
Chain
CellRule
Cell
NeighborhoodRule
Expand All @@ -23,11 +29,25 @@ Life
ManualRule
Manual
ManualNeighborhoodRule
Chain
```

```@docs
applyrule
applyrule!
DynamicGrids.applyrule
DynamicGrids.applyrule!
DynamicGrids.precalcrules
isinferred
```

### Simulation data and methods for use in `applyrule`

```@docs
SimData
DynamicGrids.radius
DynamicGrids.aux
DynamicGrids.timestep
DynamicGrids.currenttimestep
DynamicGrids.currenttime
```

## Neighborhoods
Expand All @@ -45,11 +65,13 @@ Positional
LayeredPositional
```

### Methods for use with Neighborhood objects

```@docs
neighbors
sumneighbors
mapsetneighbor!
setneighbor!
DynamicGrids.neighbors
DynamicGrids.sumneighbors
DynamicGrids.mapsetneighbor!
DynamicGrids.setneighbor!
```


Expand All @@ -66,6 +88,14 @@ ImageOutput
GifOutput
```

### Output methods

```
DynamicGrids.storeframe!
DynamicGrids.showframe
DynamicGrids.showimage
```

### Grid processors

```@docs
Expand Down Expand Up @@ -113,22 +143,23 @@ NoOpt
SparseOpt
```


## Internal data handling

Simdata and Griddata objects are used to manage the simulation
and provide rules with any data they need.
[`SimData`](@ref) and [`GridData`](@ref) objects are used to
manage the simulation and provide rules with any data they need.

```@docs
SimData
GridData
ReadableGridData
WritableGridData
```
These methods and objects are all subject to change.

# Methods

```@autodocs
Modules = [DynamicGrids]
Order = [:function]
```@docs
DynamicGrids.GridData
DynamicGrids.ReadableGridData
DynamicGrids.WritableGridData
DynamicGrids.sequencerules!
DynamicGrids.maprule!
DynamicGrids.optmap
DynamicGrids.readgrids
DynamicGrids.writegrids
DynamicGrids.getgrids
DynamicGrids.combinegrids
DynamicGrids.replacegrids
```
3 changes: 2 additions & 1 deletion src/DynamicGrids.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import FieldMetadata: @description, description,
@default, default


export sim!, resume!, replay, savegif, isinferred, method, methodtype
export sim!, resume!, replay, savegif, isinferred, isinferred

export rules, neighbors, inbounds, isinbounds, radius, gridsize,
currenttime, currenttimestep, timestep
Expand Down Expand Up @@ -95,6 +95,7 @@ include("outputs/repl.jl")
include("outputs/gif.jl")
include("interface.jl")
include("framework.jl")
include("precalc.jl")
include("sequencerules.jl")
include("maprules.jl")
include("overflow.jl")
Expand Down
125 changes: 63 additions & 62 deletions src/chain.jl
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
"""
`Chain`s allow chaining rules together to be completed in a single processing step
without intermediate reads or writes from grids. They are potentially compiled
together into a single function call, especially if you use `@inline` on all
`applyrule`. methods. `Chain` can hold either all [`CellRule`](@ref) or
[`NeighborhoodRule`](@ref) followed by [`CellRule`](@ref).
Chain(rules...)
`Chain`s allow chaining rules together to be completed in a single processing step,
without intermediate reads or writes from grids.
They are potentially compiled together into a single function call, especially if you
use `@inline` on all `applyrule` methods. `Chain` can hold either all [`CellRule`](@ref)
or [`NeighborhoodRule`](@ref) followed by [`CellRule`](@ref).
[`ManualRule`](@ref) can't be used in `Chain`, as it doesn't have a return value.
![Chain rule diagram](https://raw.githubusercontent.com/cesaraustralia/DynamicGrids.jl/media/Chain.png)
"""
struct Chain{R,W,T<:Union{Tuple{},Tuple{Union{<:NeighborhoodRule,<:CellRule},Vararg{<:CellRule}}}} <: Rule{R,W}
rules::T
end
Chain(args...) = begin
rkeys = Tuple{union(map(k -> asiterable(readkeys(k)), args)...)...}
wkeys = Tuple{union(map(k -> asiterable(writekeys(k)), args)...)...}
Chain{rkeys,wkeys,typeof(args)}(args)
Chain(rules...) = Chain(rules)
Chain(rules::Tuple) = begin
rkeys = Tuple{union(map(k -> asiterable(readkeys(k)), rules)...)...}
wkeys = Tuple{union(map(k -> asiterable(writekeys(k)), rules)...)...}
Chain{rkeys,wkeys,typeof(rules)}(rules)
end

rules(chain::Chain) = chain.rules
# Only the first rule in a chain can have a radius larger than zero.
# Only the first rule in a chain can be a NeighborhoodRule
radius(chain::Chain) = radius(chain[1])
neighborhoodkey(chain::Chain) = neighborhoodkey(chain[1])
neighborhood(chain::Chain) = neighborhood(chain[1])
Expand All @@ -33,55 +41,64 @@ Base.lastindex(chain::Chain) = lastindex(rules(chain))
"""
applyrule(data, rules::Chain, state, (i, j))
Chained rules. If a [`Chain`](@ref) of rules is passed to `applyrule`, run them
Chained rules.
If a [`Chain`](@ref) of rules is passed to `applyrule`, run them
sequentially for each cell. This can have much beter performance as no writes
occur between rules, and they are essentially compiled together into compound
rules. This gives correct results only for [`CellRule`](@ref), or for a single
[`NeighborhoodRule`](@ref) followed by [`CellRule`](@ref).
"""
@inline applyrule(data::SimData, chain::Chain, state, index) =
chainrule(data, chain::Chain, chain[1], state, index)
@inline applyrule(data::SimData, chain::Chain{R,W,Tuple{}}, state, index) where {R,W} =
chainstate(chain, map(Val, writekeys(chain)), state)

@inline chainrule(data::SimData, chain::Chain, rule::Rule{RR,RW}, state, index
) where {RR,RW} = begin
# Get the state needed by this rule
read = chainstate(chain, Val{RR}, state)
# Run the rule
write = applyrule(data, rule, read, index)
# Create new state with the result and state from other rules
newstate = update_chainstate(chain, rule, state, write)
# Run applyrule on the rest of the chain
applyrule(data, tail(chain), newstate, index)
end
@inline chainrule(data::SimData, chain::Chain, rule::Rule{RR,RW}, state, index, args...
) where {RR<:Tuple,RW} = begin
read = chainstate(chain, (map(Val, readkeys(rule))...,), state)
write = applyrule(data, rule, read, index)
newstate = update_chainstate(chain, rule, state, write)
applyrule(data, tail(chain), newstate, index)
@generated applyrule(data::SimData, chain::Chain{R,W,T}, state, index) where {R,W,T} = begin
expr = Expr(:block)
for i in 1:length(T.parameters)
rule_expr = quote
rule = chain[$i]
# Get the state needed by this rule
read = readstate(rule, state)
# Run the rule
write = applyrule(data, rule, read, index)
# Create new state with the result and state from other rules
state = update_chainstate(rule, state, write)
end
push!(expr.args, rule_expr)
end
push!(expr.args, :(writestate(chain, state)))
expr
end

# Get state as a NamedTuple or single value
@inline chainstate(chain::Chain, keys::Tuple, state) = begin
keys = map(unwrap, keys)
vals = map(k -> state[k], keys)
NamedTuple{keys,typeof(vals)}(vals)
@generated readstate(::Rule{R,W}, state::NamedTuple) where {R<:Tuple,W} = begin
expr = Expr(:tuple)
keys = Tuple(R.parameters)
for k in keys
push!(expr.args, :(state[$(QuoteNode(k))]))
end
:(NamedTuple{$keys,typeof($expr)}($expr))
end
@inline readstate(::Rule{R,W}, state::NamedTuple) where {R,W} = state[R]

# Get the state to write for the chain
@generated writestate(::Rule{R,W}, state::NamedTuple) where {R<:Tuple,W} = begin
expr = Expr(:tuple)
keys = Tuple(W.parameters)
for k in keys
push!(expr.args, :(state[$(QuoteNode(k))]))
end
expr
end
@inline chainstate(chain::Chain, key::Type{<:Val}, state) =
state[unwrap(key)]
@inline writestate(rule::Rule{R,W}, state::NamedTuple) where {R,W} = state[W]

# Merge new state with previous state
# Returning a new NamedTuple with all keys having the most recent state
@generated update_chainstate(chain::Chain{CR,CW}, rule::Rule{RR,RW}, state::NamedTuple{K,V}, writestate::Tuple
) where {CR,CW,RR,RW,K,V} = begin
@generated function update_chainstate(rule::Rule{R,W}, state::NamedTuple{K,V}, writestate
) where {R,W,K,V}
expr = Expr(:tuple)
wkeys = RW.parameters
keys = (union(K, RW.parameters)...,)
writekeys = W isa Symbol ? (W,) : W.parameters
keys = (union(K, writekeys)...,)
for (i, k) in enumerate(keys)
if k in wkeys
for (j, wkey) in enumerate(wkeys)
if k in writekeys
for (j, wkey) in enumerate(writekeys)
if k == wkey
push!(expr.args, :(writestate[$j]))
end
Expand All @@ -92,22 +109,6 @@ end
end
quote
newstate = $expr
NamedTuple{$keys}(newstate)
end
end
@generated update_chainstate(chain::Chain{CR,CW}, rule::Rule{RR,RW}, state::NamedTuple{K,V}, writestate
) where {CR,CW,RR,RW,K,V} = begin
expr = Expr(:tuple)
keys = (union(K, (RW,))...,)
for (i, k) in enumerate(keys)
if k == RW
push!(expr.args, :(writestate))
else
push!(expr.args, :(state[$i]))
end
end
quote
newstate = $expr
NamedTuple{$keys}(newstate)
NamedTuple{$keys,typeof(newstate)}(newstate)
end
end
2 changes: 0 additions & 2 deletions src/extent.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,3 @@ tspan(e::Extent) = e.tspan
settspan!(e::Extent, tspan) = e.tspan = tspan

gridsize(extent::Extent) = gridsize(init(extent))
gridsize(A::AbstractArray) = size(A)
gridsize(nt::NamedTuple) = size(first(nt))
Loading

2 comments on commit 6cf1812

@rafaqz
Copy link
Member Author

@rafaqz rafaqz commented on 6cf1812 Jul 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/18761

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.10.2 -m "<description of version>" 6cf1812e15047626cb1f0d8b52f99d290435ca2b
git push origin v0.10.2

Please sign in to comment.