Skip to content

Commit 908c168

Browse files
committed
Adapt to Literate workflow
1 parent b6b7b73 commit 908c168

9 files changed

Lines changed: 2019 additions & 2115 deletions

File tree

test/algae.jl

Lines changed: 122 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#=
2-
Algae growth
2+
# Algae growth
33
4-
Alejandro Morales - May 2023
4+
Alejandro Morales
5+
6+
Centre for Crop Systems Analysis - Wageningen University
57
68
In this first example, we learn how to create a `Graph` and update it
79
dynamically with rewriting rules.
@@ -10,12 +12,13 @@ The model described here is based on the non-branching model of [algae
1012
growth](https://en.wikipedia.org/wiki/L-system#Example_1:_Algae) proposed by
1113
Lindermayer as one of the first L-systems.
1214
13-
First, we need to load the VirtualPlantLab metapackage, which will automatically load all
14-
the packages in the VirtualPlantLab ecosystem.
15+
First, we need to load the VPL metapackage, which will automatically load all
16+
the packages in the VPL ecosystem.
17+
1518
=#
1619
using VirtualPlantLab
17-
1820
#=
21+
1922
The rewriting rules of the L-system are as follows:
2023
2124
**axiom**: A
@@ -24,115 +27,127 @@ The rewriting rules of the L-system are as follows:
2427
2528
**rule 2**: B $\rightarrow$ A
2629
27-
In VirtualPlantLab, this L-system would be implemented as a graph where the nodes can be of
30+
In VPL, this L-system would be implemented as a graph where the nodes can be of
2831
type `A` or `B` and inherit from the abstract type `Node`. It is advised to
2932
include type definitions in a module to avoid having to restart the Julia
3033
session whenever we want to redefine them. Because each module is an independent
31-
namespace, we need to import `Node` from the VirtualPlantLab package inside the module:
34+
namespace, we need to import `Node` from the VPL package inside the module:
35+
3236
=#
3337
module algae
34-
import VirtualPlantLab: Node
35-
struct A <: Node end
36-
struct B <: Node end
38+
import VirtualPlantLab: Node
39+
struct A <: Node end
40+
struct B <: Node end
3741
end
3842
import .algae
43+
#=
3944
40-
let
41-
#=
42-
Note that in this very example we do not need to store any data or state inside
43-
the nodes, so types `A` and `B` do not require fields.
44-
45-
The axiom is simply defined as an instance of type of `A`:
46-
=#
47-
axiom = algae.A()
48-
49-
#=
50-
The rewriting rules are implemented in VirtualPlantLab as objects of type `Rule`. In VirtualPlantLab, a
51-
rewriting rule substitutes a node in a graph with a new node or subgraph and is
52-
therefore composed of two parts:
53-
54-
1. A condition that is tested against each node in a graph to choose which nodes
55-
to rewrite.
56-
2. A subgraph that will replace each node selected by the condition above.
57-
58-
In VirtualPlantLab, the condition is split into two components:
59-
60-
1. The type of node to be selected (in this example that would be `A` or `B`).
61-
2. A function that is applied to each node in the graph (of the specified type)
62-
to indicate whether the node should be selected or not. This function is
63-
optional (the default is to select every node of the specified type).
64-
65-
The replacement subgraph is specified by a function that takes as input the node
66-
selected and returns a subgraph defined as a combination of node objects.
67-
Subgraphs (which can also be used as axioms) are created by linearly combining
68-
objects that inherit from `Node`. The operation `+` implies a linear
69-
relationship between two nodes and `[]` indicates branching.
70-
71-
The implementation of the two rules of algae growth model in VirtualPlantLab is as follows:
72-
=#
73-
rule1 = Rule(algae.A, rhs = x -> algae.A() + algae.B())
74-
rule2 = Rule(algae.B, rhs = x -> algae.A())
75-
76-
#=
77-
Note that in each case, the argument `rhs` is being assigned an anonymous (aka
78-
*lambda*) function. This is a function without a name that is defined directly
79-
in the assigment to the argument. That is, the Julia expression `x -> A() + B()`
80-
is equivalent to the following function definition:
81-
=#
82-
function rule_1(x)
83-
algae.A() + algae.B()
84-
end
85-
86-
#=
87-
For simple rules (especially if the right hand side is just a line of code) it
88-
is easier to just define the right hand side of the rule with an anonymous
89-
function rather than creating a standalone function with a meaningful name.
90-
However, standalone functions are easier to debug as you can call them directly
91-
from the REPL.
92-
93-
With the axiom and rules we can now create a `Graph` object that represents the
94-
algae organism. The first argument is the axiom and the second is a tuple with
95-
all the rewriting rules:
96-
=#
97-
organism = Graph(axiom = axiom, rules = (rule1, rule2))
98-
99-
#=
100-
If we apply the rewriting rules iteratively, the graph will grow, in this case
101-
representing the growth of the algae organism. The rewriting rules are applied
102-
on the graph with the function `rewrite!()`:
103-
=#
104-
rewrite!(organism)
45+
Note that in this very example we do not need to store any data or state inside
46+
the nodes, so types `A` and `B` do not require fields.
47+
48+
The axiom is simply defined as an instance of type of `A`:
49+
50+
=#
51+
axiom = algae.A()
52+
#=
53+
54+
The rewriting rules are implemented in VPL as objects of type `Rule`. In VPL, a
55+
rewriting rule substitutes a node in a graph with a new node or subgraph and is
56+
therefore composed of two parts:
57+
58+
1. A condition that is tested against each node in a graph to choose which nodes
59+
to rewrite.
60+
2. A subgraph that will replace each node selected by the condition above.
10561
106-
#=
107-
Since there was only one node of type `A`, the only rule that was applied was
108-
`rule1`, so the graph should now have two nodes of types `A` and `B`,
109-
respectively. We can confirm this by drawing the graph. We do this with the
110-
function `draw()` which will always generate the same representation of the
111-
graph, but different options are available depending on the context where the
112-
code is executed. By default, `draw()` will create a new window where an
113-
interactive version of the graph will be drawn and one can zoom and pan with the
114-
mouse.
115-
=#
116-
import GLMakie
117-
draw(organism)
118-
119-
#=
120-
Notice that each node in the network representation is labelled with the type of
121-
node (`A` or `B` in this case) and a number in parenthesis. This number is a
122-
unique identifier associated to each node and it is useful for debugging
123-
purposes (this will be explained in more advanced examples).
124-
125-
Applying multiple iterations of rewriting can be achieved with a simple loop:
126-
=#
127-
for i = 1:4
128-
rewrite!(organism)
129-
end
130-
131-
# And we can verify that the graph grew as expected:
132-
draw(organism)
133-
134-
# The network is rather boring as the system is growing linearly (no branching)
135-
# but it already illustrates how graphs can grow rapidly in just a few
136-
# iterations. Remember that the interactive visualization allows adjusting the
137-
# zoom, which is handy when graphs become large.
62+
In VPL, the condition is split into two components:
63+
64+
1. The type of node to be selected (in this example that would be `A` or `B`).
65+
2. A function that is applied to each node in the graph (of the specified type)
66+
to indicate whether the node should be selected or not. This function is
67+
optional (the default is to select every node of the specified type).
68+
69+
The replacement subgraph is specified by a function that takes as input the node
70+
selected and returns a subgraph defined as a combination of node objects.
71+
Subgraphs (which can also be used as axioms) are created by linearly combining
72+
objects that inherit from `Node`. The operation `+` implies a linear
73+
relationship between two nodes and `[]` indicates branching.
74+
75+
The implementation of the two rules of algae growth model in VPL is as follows:
76+
77+
=#
78+
rule1 = Rule(algae.A, rhs = x -> algae.A() + algae.B())
79+
rule2 = Rule(algae.B, rhs = x -> algae.A())
80+
#=
81+
82+
Note that in each case, the argument `rhs` is being assigned an anonymous (aka
83+
*lambda*) function. This is a function without a name that is defined directly
84+
in the assigment to the argument. That is, the Julia expression `x -> A() + B()`
85+
is equivalent to the following function definition:
86+
87+
=#
88+
function rule_1(x)
89+
algae.A() + algae.B()
13890
end
91+
#=
92+
93+
For simple rules (especially if the right hand side is just a line of code) it
94+
is easier to just define the right hand side of the rule with an anonymous
95+
function rather than creating a standalone function with a meaningful name.
96+
However, standalone functions are easier to debug as you can call them directly
97+
from the REPL.
98+
99+
With the axiom and rules we can now create a `Graph` object that represents the
100+
algae organism. The first argument is the axiom and the second is a tuple with
101+
all the rewriting rules:
102+
103+
=#
104+
organism = Graph(axiom = axiom, rules = (rule1, rule2))
105+
#=
106+
107+
If we apply the rewriting rules iteratively, the graph will grow, in this case
108+
representing the growth of the algae organism. The rewriting rules are applied
109+
on the graph with the function `rewrite!()`:
110+
111+
=#
112+
rewrite!(organism)
113+
#=
114+
115+
Since there was only one node of type `A`, the only rule that was applied was
116+
`rule1`, so the graph should now have two nodes of types `A` and `B`,
117+
respectively. We can confirm this by drawing the graph. We do this with the
118+
function `draw()` which will always generate the same representation of the
119+
graph, but different options are available depending on the context where the
120+
code is executed. By default, `draw()` will create a new window where an
121+
interactive version of the graph will be drawn and one can zoom and pan with the
122+
mouse (in this online document a static version is shown, see
123+
[Backends](../manual/Visualization.md) for details):
124+
125+
=#
126+
import GLMakie
127+
draw(organism)
128+
#=
129+
130+
Notice that each node in the network representation is labelled with the type of
131+
node (`A` or `B` in this case) and a number in parenthesis. This number is a
132+
unique identifier associated to each node and it is useful for debugging
133+
purposes (this will be explained in more advanced examples).
134+
135+
Applying multiple iterations of rewriting can be achieved with a simple loop:
136+
137+
=#
138+
for i in 1:4
139+
rewrite!(organism)
140+
end
141+
#=
142+
143+
And we can verify that the graph grew as expected:
144+
145+
=#
146+
draw(organism)
147+
#=
148+
149+
The network is rather boring as the system is growing linearly (no branching)
150+
but it already illustrates how graphs can grow rapidly in just a few iterations.
151+
Remember that the interactive visualization allows adjusting the zoom, which is
152+
handy when graphs become large.
153+
=#

0 commit comments

Comments
 (0)