The Nature of Code閱讀心得與Python實作:8.5 L-systems

更新於 發佈於 閱讀時間約 23 分鐘

L-system是Lindenmayer system的簡稱,是由匈牙利生物學家Aristid Lindenmayer在1968年所開發出來的。Lindenmayer開發L-system的主要目的,是要建立可用於描述植物在生長發展中,其細胞交互作用行為的數學模型。時至今日,L-system也用於描述整株植物的發展型態。

這裡要稍微離題一下。原書寫說Lindenmayer是植物學家,但維基百科上寫的是生物學家,而在《The Algorithmic Beauty of Plants》這本Lindenmayer是共同作者的書中,也提到Lindenmayer是生物學家。

L-system使用文字符號和一組特定的規則來產生樣式(pattern),這就跟依據文法規則來將單字組合成句子是一樣的道理。一個L-system是由三個部分所組成的:

  • 字母表(alphabet):內含所有可以被使用的有效字元;這些字元可以被用來組成字串,形成所謂的「句子」。因此,這個L-system任何有效的「句子」中,就只會看到在字母表中所列出的字元。例如,如果字母表內含有ABC$這三個字元,那就表示這個L-system中的「句子」,就只能由ABC這三個字元來排列組合而成。
  • 公理(axiom):描述系統初始狀態的句子。例如,如果字母表中含有ABC這三個字元,那可能的公理,就可以是AAA、B、ACBAB等句子。根據Yahoo奇摩英英字典,「axiom」這個字的意思是
a statement or proposition on which an abstractly defined structure is based

    從這個解釋可以更清楚地看出來,「公理」在L-system中所扮演的角色。

  • 規則(rules):** 用來描述如何變換句子的製作規則。每條規則會包含稱為前身(predecessor)及接替者(successor)的兩個L-system句子;通常記為
    前身⟶接替者
    例如,規則A⟶AB代表當句子中有A,也就是前身出現時,在下一世代中,就必須用AB,也就是接替者,來取代它。利用這些製作規則,可以從公理中產生新的句子;針對這些新的句子,又可以利用這些製作規則產生新的句子。如此這般遞迴地利用這些製作規則,就可以一代一代地產生新的句子。

接下來就來看看Lindenmayer最初用來描述水藻生長的L-system。這個L-system其實挺簡單的,它長這樣:

字母表:A、B  
公理:A  
規則:A⟶AB、B⟶A

依照第一條規則,公理,也就是A,會轉變成AB。接著針對AB進行轉換。依照第一條規則,A會轉變成AB;而依照第二條規則,B則轉變成為A。所以,最後會把AB轉變成為ABA。這個轉換的過程可以一直持續下去,直到滿意為止。下面這張圖所顯示的,是轉換到第四代時所得到的結果。

raw-image

L-system產生樣式的方式非常簡單,那程式要怎麼寫呢?其實挺容易的,關鍵就在用一個dictionary來存放所有的規則,像這樣:

rules = {'A': 'AB', 'B': 'A'}

如果要轉換的字串是放在string這個變數中,那只要一行就可以完成轉換了:

''.join([rules[char] for char in string])

下面這個例子是轉換到第7代時,每一代所得到的結果。

Example 8.8: Simple L-system Sentence Generation

raw-image
# python version 3.10.9
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 8.8: Simple L-system Sentence Generation")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 200
screen = pygame.display.set_mode(screen_size)

FPS = 30
frame_rate = pygame.time.Clock()

screen.fill(WHITE)

axiom = 'AB'
rules = {'A': 'AB', 'B': 'A'}

# 世代數量
generation = 8

sentences = [axiom]
for i in range(generation):
sentence = ''.join([rules[char] for char in sentences[i]])
sentences.append(sentence)

font = pygame.font.SysFont('courier', 16)
for i in range(generation):
string = str(i) + ': ' + sentences[i]
text = font.render(string, True, (0, 0, 0))
screen.blit(text, (10, 10+20*i))

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

pygame.display.update()
frame_rate.tick(FPS)

L-system就這樣而已,挺簡單的。然後呢?從L-system得到的,就只是一大串的字串而已,那跟碎形的圖案有什麼關聯?其實,關鍵就在於,在L-system的字母表中的那些字元,就是畫圖的指令;只要依照這些指令把圖畫出來,就是漂亮的碎形圖案了。來看個簡單的例子會比較清楚要怎麼做。如果有個L-system長這樣:

字母表:A、B
公理:A
規則:A⟶ABA、B⟶BBB

這個L-system前幾世代的輸出為

世代0:A
世代1:ABA
世代2:ABABBBABA}
世代3:ABABBBABABBBBBBBBBABABBBABA

如果規定A和B所要執行的畫圖動作分別為

A:向前移動並畫線
B:向前移動但不畫線

那每一代所產生的句子,就可以畫出這樣的圖案:

raw-image

這個圖案,其實就是先前畫過的Cantor集。

通常,我們會使用稱為烏龜繪圖(turtle graphics)的架構來設計L-system字母表中各字元所代表的繪圖動作。烏龜繪圖源自用來教小孩子寫程式的Logo程式語言,在Python中可以用來畫圖的turtle module,也是源自於此。在L-system中,使用烏龜繪圖架構所設計的繪圖動作,通常會長這樣:

F:向前移動一段距離並畫線
f:向前移動一段距離但不畫線
+:向左轉一個角度
-:向右轉一個角度
[:將目前的狀態push進堆疊中
]:由堆疊中pop出狀態來重置目前的狀態

要注意的是,這些字元所代表的繪圖動作並非絕對的,可依照個人喜好或需求來定義。例如,跟上面常見的設計有所不同,原書所採用的,是另一種也很常見的設計:用G來代表向前移動一段距離但不畫線;用+代表向右轉一個角度,而-則代表向左轉一個角度。

接下來,就來將這一些寫成程式。程式包含兩個類別,一個是用來產生L-system句子的LSystem類別,另一個則是用來進行烏龜繪圖的Turtle類別。

LSystem類別長這樣:

class LSystem:
def __init__(self, axiom, rules):
self.sentence = axiom
self.rules = rules

def generate(self):
self.sentence = ''.join([rules[char] if char in rules else char
for char in self.sentence])

這個類別長得很單純,重點在於用來產生新世代L-system句子的generate()方法。先前轉換字串來產生新世代L-system句子時,程式是這樣寫的:

''.join([rules[char] for char in string])

不過在generate()方法中的寫法卻不一樣;這是因為,先前的寫法是假設在字母表中的字元都會出現在規則中,然而現在加入了烏龜繪圖的功能,像$+、-、[、]$這些字元,並不一定會出現在規則中。所以,當在轉換句子時,如果碰到規則中不存在的字元,那就不需進行轉換,保持原樣就好。

接下來來設計Turtle類別。既然叫烏龜繪圖,那就想像有一隻烏龜爬呀爬的在畫圖。為了要讓烏龜在畫布上畫出我們想要的圖案,我們賦予烏龜下面這些屬性:

surfacepygame.Surface物件;烏龜要在上面畫圖的畫布。
positionpygame.Vector2物件;烏龜的位置。  
heading:烏龜的行進方向,單位是「度」。  
length:烏龜每次移動的距離。  
angle:烏龜每次轉動的角度大小,單位是「度」。  
state_stack:list變數;用來存放烏龜狀態的堆疊。烏龜的狀態包括positionheading這兩個屬性。  
drawing_rules:dictionary變數;存放繪圖指令字元及其對應的畫圖動作。例如F這個key,對應的是move_forward_draw()方法,可以讓烏龜向前移動大小為length的距離,並且畫出線段。

設計好的Turtle類別長這樣:

class Turtle:
def __init__(self, surface, position, heading, length, angle):
self.surface = surface # pygame.Surface物件
self.position = position # pygame.Vector2物件
self.heading = heading # 單位「度」
self.length = length
self.angle = angle # 單位「度」

self.state_stack = []
self.drawing_rules = {
'F': self.move_forward_draw,
'f': self.move_forward_not_draw,
'+': self.turn_left,
'-': self.turn_right,
'[': self.push_state,
']': self.pop_state
}

def render(self, sentence):
for char in sentence:
self.drawing_rules[char]()

def move_forward_draw(self):
direction = pygame.Vector2.from_polar((1, self.heading))
end_position = self.position + self.length*direction
pygame.draw.line(self.surface, (0, 0, 0), self.position, end_position)
self.position = end_position.copy()

def move_forward_not_draw(self):
direction = pygame.Vector2.from_polar((1, self.heading))
self.position += self.length*direction

def turn_left(self):
self.heading += self.angle

def turn_right(self):
self.heading -= self.angle

def push_state(self):
self.state_stack.append([self.position.copy(), self.heading])

def pop_state(self):
self.position, self.heading = self.state_stack.pop()

接下來,就利用LSystemTurtle這兩個類別來將下列這個L-system畫出來:

字母表:F、f、+、-、[、]
公理:F
規則:F⟶FF+[+F-F-F]-[-F+F+F]

這個L-system所畫出來的圖案是一棵樹──一棵看起來還挺逼真的樹。

Example 8.9: An L-system

raw-image
# python version 3.10.9
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 8.9: An L-system")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 320
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

screen.fill(WHITE)

axiom = 'F'
rules = {'F': 'FF+[+F-F-F]-[-F+F+F]'}

lsystem = LSystem(axiom, rules)

# 演化次數
n = 4
for _ in range(n):
lsystem.generate()

x, y = WIDTH//2, HEIGHT-5
position= pygame.Vector2(x, y)

turtle = Turtle(screen, position, -90, 5, 25)
turtle.render(lsystem.sentence)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

pygame.display.update()
frame_rate.tick(FPS)

Exercise 8.11

設計一個Trail類別,用來描述烏龜所畫下的線段。程式如下:

class Trail:
def __init__(self, start, end):
# 線段起點和終點都是pygame.Vector2物件
self.start = start
self.end = end

Turtle類別的__init__()方法中,新增用來儲存烏龜所畫下線段的屬性:

self.trails = []

修改move_forward_draw()方法,將

pygame.draw.line(self.surface, (0, 0, 0), self.position, end_position)

改成

self.trails.append(Trail(self.position, end_position))

這樣當執行render()方法時,就會將烏龜畫下的線段記錄在trails這個list中。

Exercise 8.12

在L-system中加入隨機性。具體的做法是:在規則中,前身的接替者不再是固定的,而是會隨機變動。例如下面這個L-system

字母表:F、f、+、-、[、]
公理:F
規則:F0.33⟶F[+F]F
F0.33⟶F[+F]F[-F]F
F0.34⟶F[-F]F

在規則中,前身,也就是字母F,右下角所標註的數字,是這條規則會被採用的機率。所以,在這個系統中,前身F會有三個不同的接替者,而每個接替者雀屏中選的機率都大約是1/3。

程式部分,需修改的地方有兩個:一個是rules這個dictionary;另一個是LSystem類別的generate()方法。rules的寫法要改成

rules = {'F': (accessors, probabilities)}

其中

accessors = ('F[+F]F[-F]F', 'F[+F]F', 'F[-F]F')
probabilities = (0.33, 0.33, 0.34)

至於generate()方法,則改成

def generate(self):
self.sentence = ''.join([random.choices(rules[char][0], rules[char][1])[0]
if char in rules else char for char in self.sentence])

主程式

# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 8.12")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 320
screen = pygame.display.set_mode(screen_size)

FPS = 1
frame_rate = pygame.time.Clock()

screen.fill(WHITE)

accessors = ('F[+F]F[-F]F', 'F[+F]F', 'F[-F]F')
probabilities = (0.33, 0.33, 0.34)

axiom = 'F'
rules = {'F': (accessors, probabilities)}

# 演化次數
n = 4

# 起始點位置
x, y = WIDTH//2, HEIGHT-5
position= pygame.Vector2(x, y)

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

lsystem = LSystem(axiom, rules)

for _ in range(n):
lsystem.generate()

turtle = Turtle(screen, position, -90, 5, 25)
turtle.render(lsystem.sentence)

pygame.display.update()
frame_rate.tick(FPS)

執行程式之後,會不斷重新繪製圖案。因為具有隨機性,所以每次畫出來的圖案都不同。下面是兩張擷取出來的圖案:

raw-image
raw-image

Exercise 8.13


留言
avatar-img
留言分享你的想法!
avatar-img
ysf的沙龍
19會員
156內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2025/07/07
碎形樹(fractal tree)是碎形界除了Cantor集、Koch曲線外,另一個無人不知、無人不曉的圖案。比較特別的是,製作Cantor集跟Koch區線時,所使用的方法不帶有任何隨機性在裡頭;但在製作碎形樹時,可以加入隨機性,讓畫出來的碎形樹長相,更接近大自然中樹木的真實模樣。
Thumbnail
2025/07/07
碎形樹(fractal tree)是碎形界除了Cantor集、Koch曲線外,另一個無人不知、無人不曉的圖案。比較特別的是,製作Cantor集跟Koch區線時,所使用的方法不帶有任何隨機性在裡頭;但在製作碎形樹時,可以加入隨機性,讓畫出來的碎形樹長相,更接近大自然中樹木的真實模樣。
Thumbnail
2025/06/23
Koch曲線(Koch curve),碎形界另一個名氣響噹噹的圖案,是由瑞典數學家Helge von Koch在1904年所提出的。Koch曲線的製作規則非常簡單,就是把一段線段三等分,然後以中間那等分為底部畫出正三角形,接著把底部挖掉。不斷針對新形成的線段重複同樣的動作,就可以產生Koch曲線了。
Thumbnail
2025/06/23
Koch曲線(Koch curve),碎形界另一個名氣響噹噹的圖案,是由瑞典數學家Helge von Koch在1904年所提出的。Koch曲線的製作規則非常簡單,就是把一段線段三等分,然後以中間那等分為底部畫出正三角形,接著把底部挖掉。不斷針對新形成的線段重複同樣的動作,就可以產生Koch曲線了。
Thumbnail
2025/06/16
除了自我相似性之外,碎形的另一個基本組成部分是遞迴(recursion)。遞迴指的是,利用稱為製作規則(production rule)的同一套規則,不斷地進行迭代,而且每次迭代時,都會把上一次迭代的結果,作為這次迭代的起點。
Thumbnail
2025/06/16
除了自我相似性之外,碎形的另一個基本組成部分是遞迴(recursion)。遞迴指的是,利用稱為製作規則(production rule)的同一套規則,不斷地進行迭代,而且每次迭代時,都會把上一次迭代的結果,作為這次迭代的起點。
Thumbnail
看更多
你可能也想看
Thumbnail
常常被朋友問「哪裡買的?」嗎?透過蝦皮分潤計畫,把日常購物的分享多加一個步驟,就能轉換成現金回饋。門檻低、申請簡單,特別適合學生與上班族,讓零碎時間也能創造小確幸。
Thumbnail
常常被朋友問「哪裡買的?」嗎?透過蝦皮分潤計畫,把日常購物的分享多加一個步驟,就能轉換成現金回饋。門檻低、申請簡單,特別適合學生與上班族,讓零碎時間也能創造小確幸。
Thumbnail
嗨!歡迎來到 vocus vocus 方格子是台灣最大的內容創作與知識變現平台,並且計畫持續拓展東南亞等等國際市場。我們致力於打造讓創作者能夠自由發表、累積影響力並獲得實質收益的創作生態圈!「創作至上」是我們的核心價值,我們致力於透過平台功能與服務,賦予創作者更多的可能。 vocus 平台匯聚了
Thumbnail
嗨!歡迎來到 vocus vocus 方格子是台灣最大的內容創作與知識變現平台,並且計畫持續拓展東南亞等等國際市場。我們致力於打造讓創作者能夠自由發表、累積影響力並獲得實質收益的創作生態圈!「創作至上」是我們的核心價值,我們致力於透過平台功能與服務,賦予創作者更多的可能。 vocus 平台匯聚了
Thumbnail
控制論(Cybernetics)是一個跨學科的科學領域,致力於研究控制和通信在各種系統中的應用,無論是動物、機器還是組織系統。這門學科由美國數學家諾伯特·維納(Norbert Wiener)在20世紀40年代創立,其核心目標是理解和設計複雜系統的行為和功能。
Thumbnail
控制論(Cybernetics)是一個跨學科的科學領域,致力於研究控制和通信在各種系統中的應用,無論是動物、機器還是組織系統。這門學科由美國數學家諾伯特·維納(Norbert Wiener)在20世紀40年代創立,其核心目標是理解和設計複雜系統的行為和功能。
Thumbnail
這本由德國作家彼強.莫伊尼創作的《演算人生》,講述了一個近未來的時空中,由公投接受名為「立方體」的演算法人工智慧作為社會系統,所引發的一系列故事。文章對於人工智慧的影響及可能的警示提出了值得深思的看法(這段摘要還是用人工智慧算出來的,真夠諷刺)。
Thumbnail
這本由德國作家彼強.莫伊尼創作的《演算人生》,講述了一個近未來的時空中,由公投接受名為「立方體」的演算法人工智慧作為社會系統,所引發的一系列故事。文章對於人工智慧的影響及可能的警示提出了值得深思的看法(這段摘要還是用人工智慧算出來的,真夠諷刺)。
Thumbnail
歡迎來到Life Architect.我是你們的人類圖導遊Isaac Yu. 這篇是筆者的感言,因為作為人類圖的研究和實踐者,以及為別人提供報告的人,筆者覺得是有責任和盡自己最大的能力,確保人類圖中的論述是正確和有效的.儘管人類圖一些論述是帶有不能被目前人類科學認知水平所證實的,但筆者的標準線是
Thumbnail
歡迎來到Life Architect.我是你們的人類圖導遊Isaac Yu. 這篇是筆者的感言,因為作為人類圖的研究和實踐者,以及為別人提供報告的人,筆者覺得是有責任和盡自己最大的能力,確保人類圖中的論述是正確和有效的.儘管人類圖一些論述是帶有不能被目前人類科學認知水平所證實的,但筆者的標準線是
Thumbnail
邏輯是我們思考的基礎,影響著我們如何看待世界和進行推論。透過假設前提和推論,我們可以從邏輯的角度來思考生活中的各種情況和決策。深入瞭解邏輯可以幫助我們更清晰地思考,理解事物之間的關聯。
Thumbnail
邏輯是我們思考的基礎,影響著我們如何看待世界和進行推論。透過假設前提和推論,我們可以從邏輯的角度來思考生活中的各種情況和決策。深入瞭解邏輯可以幫助我們更清晰地思考,理解事物之間的關聯。
Thumbnail
《人類大命運:從智人到神人》的研究範疇涵蓋世界歷史、生物科技和自由主義信念對人類的影響。作者哈拉瑞深入探討生物科技、演算法對人文主義與自由主義的可能衝擊,並提出了許多引人思考的問題。本文為讀者提供了對書籍內容的詳細評論和深入閱讀心得,既包含書籍內容的梗概,也提供相關書籍的評價以及個人閱讀心得。
Thumbnail
《人類大命運:從智人到神人》的研究範疇涵蓋世界歷史、生物科技和自由主義信念對人類的影響。作者哈拉瑞深入探討生物科技、演算法對人文主義與自由主義的可能衝擊,並提出了許多引人思考的問題。本文為讀者提供了對書籍內容的詳細評論和深入閱讀心得,既包含書籍內容的梗概,也提供相關書籍的評價以及個人閱讀心得。
Thumbnail
搬運6年前文章:以「刻意為之」的系統化、複雜邏輯化又帶有詩味的語言行文,反諷當時商管界風行一時的「系統思考(systems thinking)」。以哲學和科學發展視角,批判、諷諭「系統思考」。 --更是為了揶揄六年前把「系統思考」當作圭臬/聖杯的創業團隊而作。 *文後還有後記。
Thumbnail
搬運6年前文章:以「刻意為之」的系統化、複雜邏輯化又帶有詩味的語言行文,反諷當時商管界風行一時的「系統思考(systems thinking)」。以哲學和科學發展視角,批判、諷諭「系統思考」。 --更是為了揶揄六年前把「系統思考」當作圭臬/聖杯的創業團隊而作。 *文後還有後記。
Thumbnail
本書作者是愛德華•威爾森,是是美國昆蟲學家、博物學家和生物學家,被尊稱為社會生物學之父 以下彙整書中內容,以及寫出我的心得
Thumbnail
本書作者是愛德華•威爾森,是是美國昆蟲學家、博物學家和生物學家,被尊稱為社會生物學之父 以下彙整書中內容,以及寫出我的心得
Thumbnail
跨年整理時找到以前對《英國皇家植物園巡禮》的筆記,發上來分享一下: 本書就像一場以植物為核心的時空之旅。內容以植物園內各種植物分出不同小篇章,從植物出發,帶出植物園的發展以及當時的社會背景。也有些植物為英國帶來改變、推動時代發展,這些也會收錄在該章節內。我個人也看到很多之前讀相關書籍時沒看到的點,
Thumbnail
跨年整理時找到以前對《英國皇家植物園巡禮》的筆記,發上來分享一下: 本書就像一場以植物為核心的時空之旅。內容以植物園內各種植物分出不同小篇章,從植物出發,帶出植物園的發展以及當時的社會背景。也有些植物為英國帶來改變、推動時代發展,這些也會收錄在該章節內。我個人也看到很多之前讀相關書籍時沒看到的點,
Thumbnail
各位智人(Homo Sapiens)同胞日安, 時間長河中,任何物種所曾達到的輝煌成就,都有灰飛煙滅的一天......
Thumbnail
各位智人(Homo Sapiens)同胞日安, 時間長河中,任何物種所曾達到的輝煌成就,都有灰飛煙滅的一天......
Thumbnail
2016/10/2-10/8   今年的諾貝爾獎,化學:「分子機器的設計與合成」、醫學:「細胞自噬機制」、物理:「用數學上的拓朴原理來解釋物質相變」。人類歷史上,習慣用圖騰與儀式來凝聚群眾的心力,廣大的智慧與力量,早已深藏在微小的世界裡。     「溝通不便、專業不明、金額巨大」造成
Thumbnail
2016/10/2-10/8   今年的諾貝爾獎,化學:「分子機器的設計與合成」、醫學:「細胞自噬機制」、物理:「用數學上的拓朴原理來解釋物質相變」。人類歷史上,習慣用圖騰與儀式來凝聚群眾的心力,廣大的智慧與力量,早已深藏在微小的世界裡。     「溝通不便、專業不明、金額巨大」造成
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News