L-systems are basically rules for recursively rewriting a string, which can be used to characterize e.g. some fractal and plant growth.
I wrote a small class to represent deterministic L-systems and used it for two examples. Any comments would be greatly appreciated, especially about the class design, the structure of the second example, and how to make things more pythonic. I'm new to Python and don't have any training in "grammars", this is just a hobby.
The class LSystem.py
:
class LSystem:
""" Lindenmayer System
LSystem( alphabet, axiom )
axiom: starting "string", a list of Symbols
rules: dictionary with rules governing how each Symbol evolves,
keys are Symbols and values are lists of Symbols
"""
def __init__(self, axiom, rules):
self.axiom = axiom
self.rules = rules
""" Evaluate system by recursively applying the rules on the axiom """
def evaluate(self,depth):
for symbol in self.axiom:
self.evaluate_symbol( symbol, depth )
""" Recursively apply the production rules to a symbol """
def evaluate_symbol( self, symbol, depth ):
if depth <= 0 or symbol not in self.rules:
symbol.leaf_function()
else:
for produced_symbol in self.rules[symbol]:
self.evaluate_symbol( produced_symbol, depth - 1 )
class Symbol:
""" Symbol in an L-system alphabet
Symbol( leaf_function )
leaf_function: Function run when the symbol is evaluated at the final
recursion depth. Could e.g. output a symbol or draw smth.
"""
def __init__(self, leaf_function ):
self.leaf_function = leaf_function
Example: Algae growth (example 1 from the wikipedia article)
import LSystem
# define symbols. their "leaf function" is to print themselves.
A = LSystem.Symbol( lambda:print('A',end='') )
B = LSystem.Symbol( lambda:print('B',end='') )
# define system
algae_system = LSystem.LSystem(
axiom = [A],
rules = { A: [A,B], B: [A] }
)
# run system
algae_system.evaluate(4) # prints "ABAABABA"
Example: Draw a Koch snowflake (as in my previous question)
import LSystem
import pygame
from math import pi, sin, cos
# some constants
WINDOW_SIZE = [300,300]
LINE_WIDTH = 1
LINE_LENGTH = 1
# global variables for "turtle drawing"
# maybe I should pass around a turtle/cursor object instead?
turtle_angle = 0
turtle_x = 0
turtle_y = WINDOW_SIZE[1]*3/4
# define drawing functions used to draw the Koch snowflake
def draw_forward():
global turtle_angle, turtle_x, turtle_y
start = [turtle_x, turtle_y]
turtle_x += LINE_LENGTH * cos(turtle_angle)
turtle_y += LINE_LENGTH * sin(turtle_angle)
end = [turtle_x, turtle_y ]
pygame.draw.line(window, pygame.Color('black'), start, end, LINE_WIDTH )
def turn_left():
global turtle_angle
turtle_angle += pi/3
def turn_right():
global turtle_angle
turtle_angle -= pi/3
# symbols in the L-system
Line = LSystem.Symbol( draw_forward )
Left = LSystem.Symbol( turn_left )
Right = LSystem.Symbol( turn_right )
# L-system axiom and rules
koch_curve_system = LSystem.LSystem(
axiom = [ Line, Right, Right, Line, Right, Right, Line ],
rules = { Line: [ Line, Left, Line, Right, Right, Line, Left, Line ] }
)
# init pygame
pygame.init()
window = pygame.display.set_mode(WINDOW_SIZE)
window.fill(pygame.Color('white'))
# evaluate the L-system, which draws the Koch snowflake
# (recursion depth was chosen manually to fit window size and line length)
koch_curve_system.evaluate(5)
# display
pygame.display.flip()
# wait for the user to exit
while pygame.event.wait().type != pygame.QUIT:
1