Skip to content

Commit

Permalink
Update tutorials to latest version
Browse files Browse the repository at this point in the history
  • Loading branch information
AleMorales committed Nov 20, 2024
1 parent cbab473 commit 0eec039
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 140 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ PlantGraphs = "615ad455-9aac-4340-9247-71ef610f781a"
PlantRayTracer = "78485975-e4aa-407d-b3bb-ded5a4265d05"
PlantViz = "358bd95d-d12c-439f-94b7-04b17e500c7f"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
SkyDomes = "1838625c-7cf7-40c6-898b-904883e4b556"

[compat]
PlantGeomPrimitives = "0.0.3"
Expand All @@ -18,4 +19,5 @@ PlantGraphs = "0.0.2"
PlantRayTracer = " 0.0.6"
PlantViz = "0.0.6"
Reexport = "1.2.2"
julia = "1.9"
SkyDomes = "0.1.6"
julia = "1.11"
Binary file added nice_trees.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions test/forest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function VirtualPlantLab.feed!(turtle::Turtle, i::TreeTypes.Internode, data)
## Rotate turtle around the head to implement elliptical phyllotaxis
rh!(turtle, data.phyllotaxis)
HollowCylinder!(turtle, length = i.length, height = i.length/15, width = i.length/15,
move = true, color = RGB(0.5,0.4,0.0))
move = true, colors = RGB(0.5,0.4,0.0))
return nothing
end

Expand All @@ -62,7 +62,7 @@ function VirtualPlantLab.feed!(turtle::Turtle, l::TreeTypes.Leaf, data)
ra!(turtle, -data.leaf_angle)
## Generate the leaf
Ellipse!(turtle, length = l.length, width = l.width, move = false,
color = RGB(0.2,0.6,0.2))
colors = RGB(0.2,0.6,0.2))
## Rotate turtle back to original direction
ra!(turtle, data.leaf_angle)
return nothing
Expand Down Expand Up @@ -233,7 +233,7 @@ newforest = deepcopy(forest)
@threads for i in eachindex(forest)
newforest[i] = simulate(forest[i], getInternode, 6)
end
render(Scene(newforest), parallel = true)
render(Scene(newforest, parallel = true))
#=
An alternative way to perform the simulation is to have an outer loop for each timestep and an internal loop over the different trees. Although this approach is not required for this simple model, most FSP models will probably need such a scheme as growth of each individual plant will depend on competition for resources with neighbouring plants. In this case, this approach would look as follows:
Expand All @@ -245,7 +245,7 @@ for step in 1:15
newforest[i] = simulate(newforest[i], getInternode, 1)
end
end
render(Scene(newforest), parallel = true)
render(Scene(newforest, parallel = true))
#=
# Customizing the scene
Expand Down Expand Up @@ -276,7 +276,7 @@ VirtualPlantLab.translate!(soil, Vec(0.0, 10.5, 0.0))
We can now add the `soil` to the `scene` object with the `add!` function.
=#
VirtualPlantLab.add!(scene, mesh = soil, color = RGB(1,1,0))
VirtualPlantLab.add!(scene, mesh = soil, colors = RGB(1,1,0))
#=
We can now render the scene that combines the random forest of binary trees and a yellow soil. Notice that
Expand All @@ -297,5 +297,5 @@ compute the resolution from a physical width and height in cm and a dpi (e.g., u
=#
res = calculate_resolution(width = 16.0, height = 16.0, dpi = 1_000)
output = render(scene, axes = false, resolution = res)
output = render(scene, axes = false, size = res)
export_scene(scene = output, filename = "nice_trees.png")
119 changes: 61 additions & 58 deletions test/growthforest.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
#=
# Growth forest
Alejandro Morales
Alejandro Morales & Ana Ernst
Centre for Crop Systems Analysis - Wageningen University
> ## TL;DR
> Now we want to implement a more extended functionality of our [Forest]()!
> - Growth rules, based on information stored in organs (dimensions, carbon assimilation)
> - Update dimensions in function of assimilation
> - Compute sink strength
> - Merge Scenes
> - Generate forest on grid and retrieve canopy-level data (e.g., LAI)
>
In this example we extend the binary forest example to have more complex, time-
dependent development and growth based on carbon allocation. For simplicity, the
Expand All @@ -24,6 +33,7 @@ import Random
using FastGaussQuadrature
using Distributions
Random.seed!(123456789)
import GLMakie
#=
## Model definition
Expand All @@ -39,41 +49,41 @@ module. The differences with respect to the previous example are:
- Bud break probability is a function of distance to apical meristem
=#
## Data types
# Data types
module TreeTypes
using VirtualPlantLab
using Distributions
## Meristem
# Meristem
Base.@kwdef mutable struct Meristem <: VirtualPlantLab.Node
age::Int64 = 0 ## Age of the meristem
age::Int64 = 0 # Age of the meristem
end
## Bud
# Bud
struct Bud <: VirtualPlantLab.Node end
## Node
# Node
struct Node <: VirtualPlantLab.Node end
## BudNode
# BudNode
struct BudNode <: VirtualPlantLab.Node end
## Internode (needs to be mutable to allow for changes over time)
# Internode (needs to be mutable to allow for changes over time)
Base.@kwdef mutable struct Internode <: VirtualPlantLab.Node
age::Int64 = 0 ## Age of the internode
biomass::Float64 = 0.0 ## Initial biomass
length::Float64 = 0.0 ## Internodes
width::Float64 = 0.0 ## Internodes
sink::Exponential{Float64} = Exponential(5)
end
## Leaf
# Leaf
Base.@kwdef mutable struct Leaf <: VirtualPlantLab.Node
age::Int64 = 0 ## Age of the leaf
biomass::Float64 = 0.0 ## Initial biomass
length::Float64 = 0.0 ## Leaves
width::Float64 = 0.0 ## Leaves
sink::Beta{Float64} = Beta(2,5)
end
## Graph-level variables -> mutable because we need to modify them during growth
# Graph-level variables -> mutable because we need to modify them during growth
Base.@kwdef mutable struct treeparams
## Variables
# Variables
biomass::Float64 = 2e-3 ## Current total biomass (g)
## Parameters
# Parameters
RGR::Float64 = 1.0 ## Relative growth rate (1/d)
IB0::Float64 = 1e-3 ## Initial biomass of an internode (g)
SIW::Float64 = 0.1e6 ## Specific internode weight (g/m3)
Expand All @@ -97,32 +107,32 @@ import .TreeTypes
The methods for creating the geometry and color of the tree are the same as in
the previous example.
Create geometry + color for the internodes
=#
## Create geometry + color for the internodes
function VirtualPlantLab.feed!(turtle::Turtle, i::TreeTypes.Internode, vars)
## Rotate turtle around the head to implement elliptical phyllotaxis
# Rotate turtle around the head to implement elliptical phyllotaxis
rh!(turtle, vars.phyllotaxis)
HollowCylinder!(turtle, length = i.length, height = i.width, width = i.width,
move = true, color = RGB(0.5,0.4,0.0))
move = true, colors = RGB(0.5,0.4,0.0))
return nothing
end

## Create geometry + color for the leaves
# Create geometry + color for the leaves
function VirtualPlantLab.feed!(turtle::Turtle, l::TreeTypes.Leaf, vars)
## Rotate turtle around the arm for insertion angle
# Rotate turtle around the arm for insertion angle
ra!(turtle, -vars.leaf_angle)
## Generate the leaf
# Generate the leaf
Ellipse!(turtle, length = l.length, width = l.width, move = false,
color = RGB(0.2,0.6,0.2))
## Rotate turtle back to original direction
colors = RGB(0.2,0.6,0.2))
# Rotate turtle back to original direction
ra!(turtle, vars.leaf_angle)
return nothing
end

## Insertion angle for the bud nodes
# Insertion angle for the bud nodes
function VirtualPlantLab.feed!(turtle::Turtle, b::TreeTypes.BudNode, vars)
## Rotate turtle around the arm for insertion angle
# Rotate turtle around the arm for insertion angle
ra!(turtle, -vars.branch_angle)
end
#=
Expand All @@ -131,10 +141,10 @@ end
The meristem rule is now parameterized by the initial states of the leaves and
internodes and will only be triggered every X days where X is the plastochron.
Create right side of the growth rule (parameterized by the initial states
of the leaves and internodes)
=#
## Create right side of the growth rule (parameterized by the initial states
## of the leaves and internodes)
function create_meristem_rule(vleaf, vint)
meristem_rule = Rule(TreeTypes.Meristem,
lhs = mer -> mod(data(mer).age, graph_data(mer).plastochron) == 0,
Expand All @@ -155,36 +165,36 @@ rather than the number of internodes. An adhoc traversal is used to compute this
length of the main branch a bud belongs to (ignoring the lateral branches).
=#
## Compute the probability that a bud breaks as function of distance to the meristem
# Compute the probability that a bud breaks as function of distance to the meristem
function prob_break(bud)
## We move to parent node in the branch where the bud was created
# We move to parent node in the branch where the bud was created
node = parent(bud)
## Extract the first internode
# Extract the first internode
child = filter(x -> data(x) isa TreeTypes.Internode, children(node))[1]
data_child = data(child)
## We measure the length of the branch until we find the meristem
# We measure the length of the branch until we find the meristem
distance = 0.0
while !isa(data_child, TreeTypes.Meristem)
## If we encounter an internode, store the length and move to the next node
# If we encounter an internode, store the length and move to the next node
if data_child isa TreeTypes.Internode
distance += data_child.length
child = children(child)[1]
data_child = data(child)
## If we encounter a node, extract the next internode
# If we encounter a node, extract the next internode
elseif data_child isa TreeTypes.Node
child = filter(x -> data(x) isa TreeTypes.Internode, children(child))[1]
data_child = data(child)
else
error("Should be Internode, Node or Meristem")
end
end
## Compute the probability of bud break as function of distance and
## make stochastic decision
# Compute the probability of bud break as function of distance and
# make stochastic decision
prob = min(1.0, distance*graph_data(bud).budbreak)
return rand() < prob
end

## Branch rule parameterized by initial states of internodes
# Branch rule parameterized by initial states of internodes
function create_branch_rule(vint)
branch_rule = Rule(TreeTypes.Bud,
lhs = prob_break,
Expand All @@ -196,7 +206,6 @@ function create_branch_rule(vint)
end
#=
### Growth
We need some functions to compute the length and width of a leaf or internode
Expand Down Expand Up @@ -236,9 +245,7 @@ sink_strength(leaf, vars) = leaf.age > vars.leaf_expansion ? 0.0 :
pdf(leaf.sink, leaf.age/vars.leaf_expansion)/100.0
plot(0:1:50, x -> sink_strength(TreeTypes.Leaf(age = x), TreeTypes.treeparams()),
xlabel = "Age", ylabel = "Sink strength", label = "Leaf")
#=

=#
sink_strength(int) = pdf(int.sink, int.age)
plot!(0:1:50, x -> sink_strength(TreeTypes.Internode(age = x)), label = "Internode")
#=
Expand Down Expand Up @@ -275,19 +282,19 @@ strength.
=#
function grow!(tree, all_leaves, all_internodes)
## Compute total biomass increment
# Compute total biomass increment
tvars = data(tree)
ΔB = tvars.RGR*tvars.biomass
tvars.biomass += ΔB
## Total sink strength
# Total sink strength
total_sink = 0.0
for leaf in all_leaves
total_sink += sink_strength(leaf, tvars)
end
for int in all_internodes
total_sink += sink_strength(int)
end
## Allocate biomass to leaves and internodes
# Allocate biomass to leaves and internodes
for leaf in all_leaves
leaf.biomass += ΔB*sink_strength(leaf, tvars)/total_sink
end
Expand Down Expand Up @@ -325,18 +332,18 @@ parallel.
get_meristems(tree) = apply(tree, Query(TreeTypes.Meristem))
function daily_step!(forest)
@threads for tree in forest
## Retrieve all the relevant organs
# Retrieve all the relevant organs
all_leaves = get_leaves(tree)
all_internodes = get_internodes(tree)
all_meristems = get_meristems(tree)
## Update the age of the organs
# Update the age of the organs
age!(all_leaves, all_internodes, all_meristems)
## Grow the tree
# Grow the tree
grow!(tree, all_leaves, all_internodes)
tvars = data(tree)
size_leaves!(all_leaves, tvars)
size_internodes!(all_internodes, tvars)
## Developmental rules
# Developmental rules
rewrite!(tree)
end
end
Expand All @@ -350,30 +357,27 @@ orientation and RGR:
=#
RGRs = rand(Normal(0.3,0.01), 10, 10)
histogram(vec(RGRs))
#=

=#
orientations = [rand()*360.0 for i = 1:2.0:20.0, j = 1:2.0:20.0]
histogram(vec(orientations))
#=

=#
origins = [Vec(i,j,0) for i = 1:2.0:20.0, j = 1:2.0:20.0];
nothing #hide
#=
The following initalizes a tree based on the origin, orientation and RGR:
=#
function create_tree(origin, orientation, RGR)
## Initial state and parameters of the tree
# Initial state and parameters of the tree
vars = TreeTypes.treeparams(RGR = RGR)
## Initial states of the leaves
# Initial states of the leaves
leaf_length, leaf_width = leaf_dims(vars.LB0, vars)
vleaf = (biomass = vars.LB0, length = leaf_length, width = leaf_width)
## Initial states of the internodes
# Initial states of the internodes
int_length, int_width = int_dims(vars.LB0, vars)
vint = (biomass = vars.IB0, length = int_length, width = int_width)
## Growth rules
# Growth rules
meristem_rule = create_meristem_rule(vleaf, vint)
branch_rule = create_branch_rule(vint)
axiom = T(origin) + RH(orientation) +
Expand All @@ -387,7 +391,6 @@ function create_tree(origin, orientation, RGR)
end
#=
## Visualization
As in the previous example, it makes sense to visualize the forest with a soil
Expand All @@ -401,10 +404,10 @@ Base.@kwdef struct Soil <: VirtualPlantLab.Node
width::Float64
end
function VirtualPlantLab.feed!(turtle::Turtle, s::Soil, vars)
Rectangle!(turtle, length = s.length, width = s.width, color = RGB(255/255, 236/255, 179/255))
Rectangle!(turtle, length = s.length, width = s.width, colors = RGB(255/255, 236/255, 179/255))
end
soil_graph = RA(-90.0) + T(Vec(0.0, 10.0, 0.0)) + # Moves into position
Soil(length = 20.0, width = 20.0) # Draws the soil tile
soil_graph = RA(-90.0) + T(Vec(0.0, 10.0, 0.0)) + ## Moves into position
Soil(length = 20.0, width = 20.0) ## Draws the soil tile
soil = Scene(Graph(axiom = soil_graph));
render(soil, axes = false)
#=
Expand All @@ -415,13 +418,12 @@ or a function):
=#
function render_forest(forest, soil)
scene = Scene(vec(forest)) # create scene from forest
scene = Scene([scene, soil]) # merges the two scenes
scene = Scene(vec(forest)) ## create scene from forest
scene = Scene([scene, soil]) ## merges the two scenes
render(scene)
end
#=
## Retrieving canopy-level data
We may want to extract some information at the canopy level such as LAI. This is
Expand Down Expand Up @@ -456,3 +458,4 @@ And compute the leaf area index:
=#
get_LAI(forest)
#=
Loading

0 comments on commit 0eec039

Please sign in to comment.