11
22
3- ```` @example Forest
4- #= Forest
3+ # Forest
54
6- Alejandro Morales
5+ Alejandro Morales & Ana Ernst
76
87Centre for Crop Systems Analysis - Wageningen University
98
9+ > ## TL;DR
10+ > Similar in functionality to [ Tree] ( https://virtualplantlab.com/dev/tutorials/from_tree_forest/tree/ ) tutorial with separate graphs for each tree
11+ > - Modify tree parameters for each tree
12+ > - Multithreaded simulation (grow trees in parallel)
13+ > - Scene customization (e.g., add soil)
14+ > - Export Scenes
15+ >
16+
1017In this example we extend the tree example into a forest, where
1118each tree is described by a separate graph object and parameters driving the
1219growth of these trees vary across individuals following a predefined distribution.
@@ -15,7 +22,7 @@ This tutorial requires using the Distributions.jl package:
1522The data types, rendering methods and growth rules are the same as in the binary
1623tree example:
1724
18- =#
25+ ``` julia
1926using VirtualPlantLab
2027using Distributions, Plots, ColorTypes
2128import GLMakie
104111branch_rule = Rule (TreeTypes. Bud,
105112 lhs = prob_break,
106113 rhs = bud -> TreeTypes. BudNode () + TreeTypes. Internode () + TreeTypes. Meristem ())
107- ````
114+ ```
108115
109116The main difference with respect to the tree is that several of the parameters
110117will vary per TreeTypes. Also, the location of the tree and initial orientation of
@@ -116,19 +123,19 @@ of each tree and rotates it.
116123(ii) Wrap the axiom, rules and the creation of the graph into a function that
117124takes the required parameters as inputs.
118125
119- ```` @example Forest
126+ ``` julia
120127function create_tree (origin, growth, budbreak, orientation)
121128 axiom = T (origin) + RH (orientation) + TreeTypes. Internode () + TreeTypes. Meristem ()
122129 tree = Graph (axiom = axiom, rules = (meristem_rule, branch_rule),
123130 data = TreeTypes. treeparams (growth = growth, budbreak = budbreak))
124131 return tree
125132end
126- ````
133+ ```
127134
128135The code for elongating the internodes to simulate growth remains the same as for
129136the binary tree example
130137
131- ```` @example Forest
138+ ``` julia
132139getInternode = Query (TreeTypes. Internode)
133140
134141function elongate! (tree, query)
@@ -149,53 +156,53 @@ function simulate(tree, query, nsteps)
149156 end
150157 return new_tree
151158end
152- ````
159+ ```
153160
154161Let's simulate a forest of 10 x 10 trees with a distance between (and within) rows
155162of 2 meters. First we generate the original positions of the trees. For the
156163position we just need to pass a ` Vec ` object with the x, y, and z coordinates of
157164the location of each TreeTypes. The code below will generate a matrix with the coordinates:
158165
159- ```` @example Forest
166+ ``` julia
160167origins = [Vec (i,j,0 ) for i = 1 : 2.0 : 20.0 , j = 1 : 2.0 : 20.0 ]
161- ````
168+ ```
162169
163170We may assume that the initial orientation is uniformly distributed between 0 and 360 degrees:
164171
165- ```` @example Forest
172+ ``` julia
166173orientations = [rand ()* 360.0 for i = 1 : 2.0 : 20.0 , j = 1 : 2.0 : 20.0 ]
167- ````
174+ ```
168175
169176For the ` growth ` and ` budbreak ` parameters we will assumed that they follow a
170177LogNormal and Beta distribution, respectively. We can generate random
171178values from these distributions using the ` Distributions ` package. For the
172179relative growth rate:
173180
174- ```` @example Forest
181+ ``` julia
175182growths = rand (LogNormal (- 2 , 0.3 ), 10 , 10 )
176183histogram (vec (growths))
177184savefig (" growths.png" ) # # hide
178- ````
185+ ```
179186
180187![ ] ( growths.png )
181188
182189And for the budbreak parameter:
183190
184- ```` @example Forest
191+ ``` julia
185192budbreaks = rand (Beta (2.0 , 10 ), 10 , 10 )
186193histogram (vec (budbreaks))
187194savefig (" budbreaks.png" ) # # hide
188- ````
195+ ```
189196
190197![ ] ( budbreaks.png )
191198
192199Now we can create our forest by calling the ` create_tree ` function we defined earlier
193200with the correct inputs per tree:
194201
195- ```` @example Forest
202+ ``` julia
196203forest = vec (create_tree .(origins, growths, budbreaks, orientations));
197204nothing # hide
198- ````
205+ ```
199206
200207By vectorizing ` create_tree() ` over the different arrays, we end up with an array
201208of trees. Each tree is a different Graph, with its own nodes, rewriting rules
@@ -210,29 +217,29 @@ as you may need to change some settings in your computer).
210217We can simulate the growth of each tree by applying the method ` simulate ` to each
211218tree, creating a new version of the forest (the code below is an array comprehension)
212219
213- ```` @example Forest
220+ ``` julia
214221newforest = [simulate (tree, getInternode, 2 ) for tree in forest];
215222nothing # hide
216- ````
223+ ```
217224
218225And we can render the forest with the function ` render ` as in the binary tree
219226example but passing the whole forest at once
220227
221- ```` @example Forest
228+ ``` julia
222229pl = render (Scene (newforest))
223230GLMakie. save (" newforest1.png" , pl) # # hide
224- ````
231+ ```
225232
226233![ ] ( newforest1.png )
227234
228235If we iterate 4 more iterations we will start seeing the different individuals
229236diverging in size due to the differences in growth rates
230237
231- ```` @example Forest
238+ ``` julia
232239newforest = [simulate (tree, getInternode, 15 ) for tree in newforest];
233240pl = render (Scene (newforest))
234241GLMakie. save (" newforest2.png" , pl) # # hide
235- ````
242+ ```
236243
237244![ ] ( newforest2.png )
238245
@@ -245,21 +252,21 @@ and execute the iterations of the loop in multiple threads using the macro `@thr
245252Note that the rendering function can also be ran in parallel (i.e. the geometry will be
246253generated separately for each plant and the merge together):
247254
248- ```` @example Forest
255+ ``` julia
249256using Base. Threads
250257newforest = deepcopy (forest)
251258@threads for i in 1 : length (forest)
252259 newforest[i] = simulate (forest[i], getInternode, 6 )
253260end
254261pl = render (Scene (newforest, parallel = true ))
255262GLMakie. save (" newforest3.png" , pl) # # hide
256- ````
263+ ```
257264
258265![ ] ( newforest3.png )
259266
260267An 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:
261268
262- ```` @example Forest
269+ ``` julia
263270newforest = deepcopy (forest)
264271for step in 1 : 15
265272 @threads for i in 1 : length (newforest)
@@ -268,7 +275,7 @@ for step in 1:15
268275end
269276pl = render (Scene (newforest, parallel = true ))
270277GLMakie. save (" newforest4.png" , pl) # # hide
271- ````
278+ ```
272279
273280![ ] ( newforest4.png )
274281
@@ -279,10 +286,10 @@ tweaking the 3D representation. When we want to combine plants generated from gr
279286geometric element it is best to combine all these geometries in a ` GLScene ` object. We can start the scene
280287with the ` newforest ` generated in the above:
281288
282- ```` @example Forest
289+ ``` julia
283290scene = Scene (newforest);
284291nothing # hide
285- ````
292+ ```
286293
287294We can create the soil tile directly, without having to create a graph. The simplest approach is two use
288295a special constructor ` Rectangle ` where one species a corner of the rectangle and two vectors defining the
@@ -292,28 +299,28 @@ above when we determined the origin of each plant. VPL offers some shortcuts: `O
292299passing the desired length as input. Below, a rectangle is created on the XY plane with the origin as a
293300corner and each side being 11 units long:
294301
295- ```` @example Forest
302+ ``` julia
296303soil = Rectangle (length = 21.0 , width = 21.0 )
297304rotatey! (soil, pi / 2 )
298305VirtualPlantLab. translate! (soil, Vec (0.0 , 10.5 , 0.0 ))
299- ````
306+ ```
300307
301308We can now add the ` soil ` to the ` scene ` object with the ` add! ` function.
302309
303- ```` @example Forest
310+ ``` julia
304311VirtualPlantLab. add! (scene, mesh = soil, colors = RGB (1 ,1 ,0 ))
305- ````
312+ ```
306313
307314We can now render the scene that combines the random forest of binary trees and a yellow soil. Notice that
308315in all previous figures, a coordinate system with grids was being depicted. This is helpful for debugging
309316your code but also to help setup the scene (e.g. if you are not sure how big the soil tile should be).
310317Howver, it may be distracting for the visualization. It turns out that we can turn that off with
311318` axes = false ` :
312319
313- ```` @example Forest
320+ ``` julia
314321pl = render (scene, axes = false )
315322GLMakie. save (" newforest5.png" , pl) # # hide
316- ````
323+ ```
317324
318325![ ] ( newforest5.png )
319326
@@ -323,11 +330,11 @@ we can run the `save_scene` function on the object returned from `render`. The a
323330` render ` to increase the number of pixels in the final image. A helper function ` calculate_resolution ` is provided to
324331compute the resolution from a physical width and height in cm and a dpi (e.g., useful for publications and posters):
325332
326- ```` @example Forest
333+ ``` julia
327334res = calculate_resolution (width = 16.0 , height = 16.0 , dpi = 1_000 )
328335output = render (scene, axes = false , size = res)
329336export_scene (scene = output, filename = " nice_trees.png" )
330- ````
337+ ```
331338
332339---
333340
0 commit comments