Lsystems as Rose Trees
WiebeMarten Wijnja
Summary
 Lsystems?
 Existing Algorithms
 Rose Trees?
 New Algorithms
 Benchmarking
 Conclusions
What is an Lsystem?
 What: Parallel Rewrite System
 Why: Plants, Trees, Cells, Fractals
 How:

generation
 (short) procedural description \(\longrightarrow\) (long) string

interpretation
 (long) string \(\longrightarrow\) 2D/3D picture/statistic/...

generation
Lsystem Generation
Theory
alphabet: A set of symbols \(V\)
axiom: starting string \(\omega \in V^+\)
production rules: \(P \subset V \times V^+\)
Each rule is usually written as \( a \rightarrow \chi \) where:
 \(a\), is called the antecedent
 \(\chi\), is called the consequent
Contextfree Lsystem \(G = \langle V, \omega, P \rangle\)
Deterministic ContextFree ('D0')
more powerful classes of Lsystems expand on this
Lsystem Generation
Theory
alphabet: A set of symbols \(V\)
axiom: starting string \(\omega \in V^+\)
production rules: \(P \subset V \times V^+\)
Each rule is usually written as \( a \rightarrow \chi \) where:
 \(a\), is called the antecedent
 \(\chi\), is called the consequent
Contextfree Lsystem \(G = \langle V, \omega, P \rangle\)
Generation: transforming $$ \mu = a_1 a_2\ldots a_n $$ into
$$ \nu = \chi_1 \chi_2 \ldots \chi_n $$
... and repeat as desired
to arbitrary depth \(d\)
Deterministic ContextFree ('D0')
more powerful classes of Lsystems expand on this
0
1
2
3
Lsystem Generation
Example: Algae
Lsystem Generation
Example: Plant
Lsystem Generation
Algorithm
rewriteString(rules: LSystemRules, string: String) {
string
> map(symbol lookupSymbol(rules, symbol))
> flatten()
}
sequentialRewrite(rules: LSystemRules, string: String, depth: ℤ) {
if depth == 0 {
string
} else {
let result < rewriteString(string, rules);
sequentialRewrite(rules, result, depth  1)
}
}
Lsystem Generation
Algorithm
rewriteString(rules: LSystemRules, string: String) {
string
> map(symbol lookupSymbol(rules, symbol))
> flatten()
}
sequentialRewrite(rules: LSystemRules, string: String, depth: ℤ) {
if depth == 0 {
string
} else {
let result < rewriteString(string, rules);
sequentialRewrite(rules, result, depth  1)
}
}
Oof \(\longrightarrow\)
Lsystem Generation
Problems
 Paralellize?
 Prevent repeated work?
Lsystem Generation
Problems
 Paralellize? \(\rightarrow\) Gathering results = difficult
 Prevent repeated work?
Lsystem Generation
LippWonkaWimmer
lippWonkaWimmerRewrite(rules: LSystemRules, string: String, depth: ℤ)
if depth == 0 {
string
} else {
let rewrite_lengths < scatterJoin(string, string_part
rewriteLength(string_part, rules)
);
let mut result < allocateString(∑(rewrite_lengths));
let indexes < prefixSum(rewrite_lengths);
scatterJoin(⧼string, indexes, rewrite_lengths⧽, ⧼string_part, start_index, length⧽
let end_index < start_index + length;
result[start_index..end_index] < rewriteString(string_part, rules);
);
lippWonkaWimmerRewrite(rules, result, depth  1)
}
 3 boundaries
 threads need to wait for slowest
 still repeated work
Can we do better?
Rose Trees
 'multiway' tree
 Each node has many (ordered) children
Rose Trees
 'multiway' tree
 Each node has many (ordered) children
Rose Trees
 'multiway' tree
 Each node has many (ordered) children
\(\uparrow\) Always the same!
Rose Trees
 Different Lsystem generation algorithms ⟶ different tree traversals
 Rules are selfsimilar at every level ⟶ prevent repeated work!
Still holds true for more powerful classes of Lsystems,
with some exceptions
TreeConquer
 Depthfirst treetraversal
 'Parallel DivideAndConquer'
treeConquerRewrite(rules: LSystemRules, string: String, depth: ℤ){
if(depth == 0) {
string
} else if(*small amount of work*) {
let result < rewriteString(string, rules);
treeConquerRewrite(rules, result, depth  1);
} else { /* string is long, large amount of work */
let (left_half, right_half) < splitInHalf(string);
let (left_res, right_res) < forkJoin(
 treeConquerRewrite(rules, left_half, depth),
 treeConquerRewrite(rules, right_half, depth)
);
concatenate(left_res, right_res)
}
}
Penultimate
 Leverage structure of rules
 same at every level
 flatten, bottomup
penultimateRewrite(rules: LSystemRules, string: String, depth: ℤ) {
if depth == 0 {
string
} else {
let mut flat_rules < rules;
for depth > 1; depth < depth  1 {
flat_rules < flattenRules(rules, flat_rules);
}
rewriteString(string, flat_rules)
}
}
Variant: PenultimateSquaring, based on exponentiation by squaring,
needs only \(\mathcal{O}(\log(d))\) rewrites.
Benchmarking is fun!
Benchmarking is fun!
Benchmarks
 Without Peregrine, it would have taken weeks rather than days...
 ...if finishing at all: 16+ GB of RAM usage for some Lsystems at doubledigit depths
 Thank you, Peregrine!
But now, time for graphs!
Algae
Koch
Plant
AlphabetExplosion
More graphs in the report
Conclusions
Remarks
 How fast it 'fast enough'?
 generation does not seem to be the bottleneck
 At doubledigit depths, gigabytes of RAM are used
Summary
 Lsystems?
 Existing Algorithms
 Rose Trees?
 New Algorithms
 Benchmarking
 Conclusions
Thank you!
Thanks to
 Job Talle for his webapplet to quickly render arbitrary Lsystems
 Luc van den Brand for making me interested in Lsystems in the first place

Jiří Kosinka for always being available to answer my weird questions
Thank you!
Questions?
Lsystems as Rose Trees
By qqwy
Lsystems as Rose Trees
 455