L-Systems — 01

10/15/19 7:38

Motivation

The goal is to learn and explore L-Systems, as well as experiment with Jupyter Notebooks.

The purpose is to be able to model a basic L-System, then move on to more complex systems, with a focus on correctness of the model then visualization.

Note

We will work on a concrete example first, then create a framework that can adapt to any (a large amount) L-System.

Resources

We will be using the examples from the L-Systems Wikipedia Article.

https://en.wikipedia.org/wiki/L-system#Examples_of_L-systems


Start

#setup
from enum import Enum
from collections import Iterable
 util

# from https://stackoverflow.com/a/40857703/
def flatten(items):
    """Yield items from any nested iterable; see Reference."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            for sub_x in flatten(x):
                yield sub_x
        else:
            yield x

Variables

We will use an enum to hold the variables of the current L-System

class Variable(Enum):
    A = 'A'
    B = 'B'
    
# for shorthand, we can use the following
A = Variable.A
B = Variable.B

Constants

This system has no constants, so we will not create an enum for it this time.

Axiom

This is the starting point/initial setup

axiom = Variable.A

Rules

We can think of rules as text replacements, or state function transitions. Something like that.

The algae example has two rules, \[ \begin{equation} (A \rightarrow AB), (B \rightarrow A) \end{equation} \]

We will be implementing these rules as one function.

def rule(v: Variable) -> List[Variable]
def rule(var: Variable) -> list:
    if var == A:
        return [A, B]
    elif var == B:
        return [A]
    else:
        print("FATAL: Unknown Variable: " + str(var))
    

Let’s inspect the results

rule(A)
[<Variable.A: 'A'>, <Variable.B: 'B'>]
rule(B)
[<Variable.A: 'A'>]

System State

We will model the system state with the following Class

from typing import NamedTuple

class LSystem(NamedTuple):
    n: int       # the iteration number
    state: list  # the variables
    

Initial State

We can describe our initial state as:

* n = 0
* state = axiom = [A]
algae_initial = LSystem(n=0, state=[axiom])

Inspect

algae_initial
LSystem(n=0, state=[<Variable.A: 'A'>])
algae_sys = LSystem(n=0, state=[axiom])

Transition

Let’s define a transition function that can be used to model what happens in one iteration.

def transition(sys: LSystem, rule_func) -> LSystem:
    new_state = []
    # for each letter, apply the rule function
    # and save the result to the new state
    for letter in sys.state:
        new_state += rule_func(letter)
    
    return LSystem(sys.n + 1, list(flatten(new_state)))

Inspect

sys1 = transition(algae_sys, rule)
sys2 = transition(sys1, rule)
sys3 = transition(sys2, rule)

Evaluate

This function will evalute an LSystem \(n\) number of times and output the final system

def evaluate(sys: LSystem, rule_func, n: int) -> LSystem:
    for _ in range(n):
        sys = transition(sys, rule_func)
    
    return sys

This function converts the state list of an LSystem to a string for easier comprehension and comparison/validation with the examples in the wikipedia article.

def state2str(sys_state: list) -> str:
    return ''.join([letter.value for letter in sys_state])

Inspect

algae7 = evaluate(algae_sys, rule, 7)
algae7str = state2str(algae7.state)
print(algae7str)
ABAABABAABAABABAABABAABAABABAABAAB

Verify

The example for \(n = 7\) in wikipedia gives the following result: \(ABAABABAABAABABAABABAABAABABAABAAB\). Let’s see if our result matches this.

algae7str == 'ABAABABAABAABABAABABAABAABABAABAAB'  # Success!
True

Final Remarks

The purpose of the notebook was to explore L Systems, and correctly implement an example from Wikipedia. In this notebook, we have successfully implemented Example 1: Algae.