0 V& n8 C0 ~& M
6.2 绘制分形
: ` v7 D5 L) X+ t 分形势复杂的几何图案或形状,但却由简单的数学公式所生成。与圆形和矩形等几何图形相比,分形看上去并不规则,也没有明显的模式或描述。但如果仔细观察,你将发现模式的存在,即整个图形有无数个自身的副本构成。由于分形设计平面上的点的相同几何变换的重复引用,因此非常适合使用计算机程序来构建。这一章中,我们将学习如何绘制Barnsley蕨类植物、Sierpinski三角和Manderlbrot集合(后两个出现在编程挑战中),这些都是分形研究领域的常见例子。分形在自然界中比比皆是,常见的例子包括海岸线、树木和雪花。 9 X) B$ o8 y3 R: A
6.2.1 平面上点的变化
% b2 X7 g; P" v# I 创建分形的基本思想是点的变换。给定x-y平面上的一个点P(x,y),变幻的一个例子是P(x,y)->Q(x+1, y+1),这意味着应用变换后,创建好了一个新点Q,它位于P向上和向右各一个单位处。如果以Q点作为起始点,将得到另一个点R,它位于点Q向上和向右各一个单位处。假设起始点P位于(1,1),下图展示了这些点的位置。 / l8 o* l4 T1 W$ L
- e$ }/ o. t$ l P、Q、R三个点的位置图 ' Q' x: W9 ^+ n S* P/ F
, o' G* f! c/ k6 |, w
因此,变换是一种描述点在x-y平面上移动的规则,从起始位置开始,每一次迭代使得点移动到新的位置。我们可以把变换想象成点在平面上的轨迹。现在换个思路考虑两个变换规则,每次迭代将从这两个规则中随机选择一个。两个规则如下: " C4 ~3 ]' {5 h9 b5 y; b" g
规则1:P1(x,y) -> P2(x+1, y-1) 0 X* n, ^" c. \0 S
规则2:P1(x,y) -> P2(x+1, y+1)
# j; B2 X) ^# T5 P- D J 以P(1, 1)作为起始点,如果我们执行4次迭代,将得到以下一系列的点:
8 n" ]4 H4 o( I7 \5 g P1(1, 1) -> P2(2, 0) (规则1)
5 D* m; g/ d* k( M, a6 _ P2(2, 0) -> P3(3, 1) (规则2) ! J H5 x3 g, E$ ?
P3(3, 1) -> P4(4, 2) (规则2) / N( c/ L Y) S3 i$ ]
P4(4, 2) -> P5(5, 1) (规则2)
2 h, E2 Y- w# w1 C% o b ... " N! a0 e& _+ n* x& p0 q
两个变换规则是随机选择的,其中每个规则被选择的概率相同。无论选择哪一个规则,点都会右移,因为在两种规则中都会增加x坐标。当点向右移动时,他们会向上或向下移动,从而形成一条Z字路径。以下程序绘制了在指定的迭代次数内进行上述转换规则之一时点的路径:
3 O$ C) O1 }% y. `! @1 P Example of selecting a transformation from two equally probable
3 ~5 \3 s8 z9 _6 J5 z, F3 ` transformations$ {+ p( C+ u% p h# N
/ a5 ~8 u5 E! Y" c: p/ Q import matplotlib.pyplot as plt4 U i" X- e* a
import random- I4 R; a& s' E/ M% F- J B" \6 [
R* x- n+ }9 e, C: ]2 C& _
def transformation_1(p):
+ T" ]. _7 S5 z5 n2 ^- `' M6 E x = p[0]$ Z; S" L8 s% [: ? T' K
y = p[1]; r5 `+ a& H6 ~' |% C3 I$ C8 L
return x + 1, y - 13 v' N+ a# F" o* m
def transformation_2(p):5 C) l& B1 @' w- V/ [5 ^$ t
x = p[0]/ _, B; O- z8 m; V5 D
y = p[1] k. V4 q: G8 Q) u* [' R$ A
return x + 1, y + 19 y- F. \ E0 s/ K- E2 \6 f
def transform(p):
: f* R+ A! r4 ? ^+ a* R6 G # List of transformation functions$ J" O- |5 L+ e% w) E
transformations = [transformation_1, transformation_2]
4 j( ]+ c2 G: p- _; n3 t # pick a random transformation function and call it
( W. t4 q" \ L, M+ g t = random.choice(transformations)
3 b, k! U$ H! d7 y( t) H) O" b9 }/ | x, y = t(p)
% }" \( |, _8 e# A return x, y
$ h" p" M+ ~+ l, d def build_trajectory(p, n):
! n; ?! W/ C3 u" E( F, i x = [p[0]]( S' ~+ f! Q5 E1 ?: a: T/ h
y = [p[1]]
1 `3 ]8 K4 J' f$ ~% z' V for i in range(n):1 N+ a( @3 M! s) Q
p = transform(p)
1 g2 w! \/ @! j7 r! [2 A x.append(p[0])
% n; j! b7 } Y; v4 S" _ y.append(p[1])
$ O3 I! O6 }0 s' M return x, y
1 f' `( V! W8 V$ d! n9 @" r
3 a3 u( U" `9 z2 W6 x1 l: | if __name__ == __main__:
' Y$ c: N p+ } # Initial point# h, v* F6 G8 t
p = (1,1)& R' p# |. u5 f' Z0 J
n = int(input(Enter the number of iterations: ))
3 c. C N, { W1 u" { P x , y = build_trajectory(p, n)
1 t L5 p, R# M # PLot
' p* [6 a9 X* h8 C* G4 X$ g plt.plot(x, y)6 H. P+ P7 l# n
plt.xlabel(X)
* j' T5 H b! l- _ a plt.ylabel(Y)3 y5 n6 E3 Z9 w% I; Q q" w+ D
plt.show()我们定义了两个函数transformation_1()和transformation_2(),分别对应于之前的两种变换规则,在transform()函数中,我们创建了一个包含这两个函数名字的列表,然后使用random.choice()函数从该列表中选择去哦中一个进行变换。现在假设我们选择了其中一个变换,蒋点P作为输入参数调用该变换函数,然后分别使用x,y来存储变换后的坐标并且返回它们。 ; p9 C+ N+ e; @# F( Q* A9 ?
从列表中随机选择一个元素 / R+ t: ^! N* C
我们在第一个分形程序中看到的random.choice()函数可以用来从列表中随机选择一个元素,每一个元素被选中的概率相同,下面是一个例子: import random/ X2 n4 \, b% l
>>> l = [1,2,3]
1 t8 R( D: Y k >>> random.choice(l)4 f; i4 S* Z+ _) ~/ a3 y' u
1
! _7 e1 G7 y! B+ m* t' I >>> random.choice(l)
8 f% {8 h! K" b0 y 3! ]! o1 K' _! S3 q( G0 b* }3 l1 R
>>> random.choice(l)3 u: f7 k# Y- e; d
35 |$ S5 B# L$ G# O
>>> random.choice(l)
6 H$ K$ I. z& N# f1 V; o3 L 1
/ T' l# G0 x+ F4 J0 b# B/ X8 C8 L >>> random.choice(l)6 _6 f4 O, w# T* U$ c
12 ~2 @7 l5 C% \! F1 X! N
这个函数也适用于元组和字符串。在后一种情形中,函数从字符串中随即返回一个字符。 ' c! B- \6 B$ i7 B d* [
当运行程序,他首先会询问迭代次数n,即应用变化的次数。然后调用build_trajectory()函数,该函数的输入参数为迭代次数n和起始点坐标p,此处设置初始点坐标为(1,1)。build_trajectory()函数重复调用n次transform()函数,并使用两个列表x和y分别存储变换后的x和y坐标。最后使用这两个列表来绘制图形。 5 m* P4 m: f1 {& Q( F8 E' H) a
下面两张图分别显示了100次和10000次迭代的点的轨迹图,这两幅图中的Z字形运动都很明显,这种Z字形路径通常被称为一条线上的随机游走。 ; ^" _7 B8 ^7 K3 F' Z5 c5 i
! ?- _4 Q* }$ H e
迭代100次的Z字路径
/ ~ y2 |$ \# Z0 U$ @# c' s3 F( x 5 _; z4 t: M/ s" T+ m
/ k: C+ c7 U. u4 M8 y
迭代10000次的Z字路径
0 n: t& u7 s0 U7 ^- p 4 u4 i2 R) [: R* j3 ]6 D, ~2 o
这个例子展示了创建分形的基本思想,即从一个点开始并对其重复应用某种变换。接下来,我们将看到一个用相同想法绘制Barnsley蕨类植物的例子。
* p. {2 |! w, D* T* f' `& V. o 6.2.2 绘制Barnsley蕨类植物
/ Z5 _. m% f. l! u2 t 英国数学家Michael Barnsley描述了如何对一个点进行重复的简单变换,从而创建蕨类植物的结构(如下图)。
+ J) r+ D; d3 K1 G7 m# i& I
, C. X- X! Z, W9 X, T; I; R- _1 J 蕨类植物 * s- o9 t+ J- c
7 ~$ e X% S- ?" k: o ]* G2 i 他提出了以下步骤来创建类似于蕨类植物的结构,以(0,0)为初始点,按事先分配的概率随机选择下述某种变换。 3 w2 ?5 @# R& F! K3 k' y
(1)变换1(概率为0.85):
+ ^- Q" F* q2 x* f7 p% g# Y 
. D8 Q# }; Y+ @. u (2)变换2(概率为0.07):
- h' E, K4 j3 U& _! | 
7 ^4 |& R# s7 }( K a* Y (3)变换3(概率为0.07): 7 h: c- P1 g6 b# s7 Y" t

( @! E5 y; Z7 Y7 g2 z. X) p9 X (4)变换4(概率为0.01):
4 E( E- X9 f2 g3 n9 ^9 T  7 X$ |% C% C+ V! P$ X$ ?
上述每一个变换对应于蕨类植物的一部分。第一个变换被选中的概率最大,因此被执行的次数最多,从而产生了蕨类植物的茎和底部的叶子。第二个和第三个变换分别对应于左边和右边底部的叶子,第四个变换绘制了蕨类植物的茎。 " N- t }- u+ z% j
这是一个非均匀概率选择的例子,我们在第五章中已经学习过,以下程序为指定的点数绘制Barnsley蕨类植物。
6 U; M; w7 |1 G4 q Draw a Barnsley Fern: |- T# X) I2 i' J8 i+ k( \
( ]2 d+ `/ _) H2 E+ n) r# m( x
import random( z2 e7 e, o2 F8 p4 t- W
import matplotlib.pyplot as plt) g( u4 [" n$ U( ]* p( s2 ~
def transformation_1(p):
: d( n8 n4 g `5 T: h, y" p x = p[0]
: }4 W' Z3 `8 Z+ Z/ ]7 J% y3 H& O y = p[1]$ c" d6 s$ D7 \& ?8 ~% w6 w
x1 = 0.85*x + 0.04*y
! \8 A4 l. P* U/ B y1 = -0.04*y + 0.85*y + 1.6
8 w6 }- C ]+ \ \ return x1, y1
1 `" A4 d# s) N" P def transformation_2(p):9 S5 G9 l3 P3 }% D- t
x = p[0]
! i1 E6 [- q" ]8 `0 k y = p[1]
5 T# \4 n4 n Q2 K, k; n% Y x1 = 0.2*x - 0.26*y, ~# C/ O u M$ l9 I, D
y1 = 0.23*y + 0.22*y + 1.6
3 I. @) d4 A, a& s+ Y" t: e return x1, y1 R4 W% W* u! o+ I: l+ _
def transformation_3(p):
+ |. _! |- Y# {. I x = p[0]& Z; b! T% W5 l1 I# w. ^
y = p[1]
9 u& u( I: f0 @, W. r x1 = -0.15*x + 0.28*y
9 g; W4 Y! p5 D+ g0 O y1 = 0.26*y + 0.24*y + 0.44; h$ x* Z, _/ e2 r$ s
return x1, y1 {. J9 u/ E4 \, H {+ D$ e0 h( B
def transformation_4(p):
# H# i u L4 e x = p[0]8 d4 m7 J0 v2 Q6 w0 h9 F
y = p[1]
6 Z. a* L1 a) C* O3 ` x1 = 0
' X0 w9 z% I8 ` ~% C" e y1 = 0.16*y
- i: _- N8 w2 | return x1, y1$ x' A' o( |. `3 D1 Y( {/ E
def get_index(probability):( }; E0 [ d( w2 m
r = random.random(); J# C% a" k6 ?" }3 H* B
c_probability = 0
' A P# [, _# N; L& C: ^+ k# `5 A sum_probability = []
& r' i. }! w* ~$ l- Y for p in probability:6 }$ v t& L( R
c_probability += p
# [: M8 x( {: ]1 Q C8 ^" U5 }5 I sum_probability.append(c_probability)
+ |+ y6 U' F2 S. D% Y8 Q) c% c for item, sp in enumerate(sum_probability):- s5 r5 B3 a+ D) v8 `- n; i, e1 C
if r < sp:5 \* g% D; E. h5 |- K/ X7 P
return item
/ _' m, j1 t2 L6 I' ^ return len(probability) - 1& V5 _/ s# d, H e2 ^, A1 T
" L( c' J: ]% g, s$ ^
def transform(p):
: i; n5 I4 u8 r/ m7 Y # List of transformation functions7 _6 q- {9 y, Q3 F6 `; r1 ]2 D
transformation = [transformation_1, transformation_2, transformation_3, transformation_4]) X4 I$ ^+ a; B: Y" k1 b
probability = [0.85, 0.07, 0.07, 0.01]# g8 F( l0 a8 ]
# Pick a random transformation function and call it, q; |9 h4 X4 D- ^
tindex = get_index(probability)7 P4 I6 }3 f/ c% F3 U
t = transformation[tindex]
" _! ?; J5 {! q! V4 C x, y = t(p)* d' T, |; g p# _
return x, y
0 v2 G4 z" P" O5 Q9 X def draw_fern(n):
* M7 N& Y/ d4 ~3 g" ~ # We start with (0,0)6 _/ D& e5 W5 O% H% ]6 W
x = [0]; |, j/ {: G! P8 i/ j/ g2 m% @
y = [0]
# f& E- Z2 w, i3 p& L" _ x1, y1 = 0, 0# b- B9 L1 I! W6 K/ Y7 T3 `
for i in range(n):
# `" Z9 [$ X9 ]" m" J1 C1 Z9 S x1, y1 = transform((x1, y1))
' Z: z) Y4 O- ^6 ~/ a" J% r x.append(x1)- R6 G0 p8 l+ |, W4 M W
y.append(y1)
! H, d) ~$ n) k! N" ^' T6 S( } return x, y
4 {% T3 g) D+ a7 a if __name__ == __main__:
8 Q( z) m3 Z+ A& K7 N0 _ n = int(input(Enter the number of points in the Ferns: ))4 a% o$ O- G% X6 G" S
x, y = draw_fern(n)
# I3 j g( ^0 M+ c5 V9 n plt.plot(x, y, o)- @2 t1 T/ P, u, u, q* b4 j2 y3 x! M
plt.title(Fern with {0} points.format(n))+ A, U t* K1 \1 Z- [0 k
plt.show(), H; [2 B# e$ q5 c4 ~3 ]5 X) C
运行此程序,他首先询问蕨类植物图中指定的点数,然后开始绘制该图。下面两张图分别展示了具有1000个点和10000个点的蕨类植物图。
- P6 B# s) W* S4 |- w4 V$ ~
9 ]& N1 @! N9 d) _) Y 制定了1000个点的蕨类植物叶片 6 G0 S+ y' G/ U+ C1 M. F0 T
% ?; U3 b; }6 }+ s- H
1 C4 X0 u+ \$ a0 j 指定了10000个点的蕨类植物叶片 2 q- ~2 {. Z2 f5 y5 K/ Y
r6 Y4 y6 C" { 这4个变换规则分别在transformation_1(),transformation_2(),transformation_3(),transformation_4()函数中定义。get_index()函数返回按照给定概率下的一种随机变化的索引值,这个概率我们之前已经讲过了,不清楚的可以返回去在第五章非均匀概率那里再次学习。返回索引之后即可选择某一个具体的变换来应用。 % q! G+ B" O' t$ W8 L! a
初始点(0,0)变换的次数即为程序输入中指定的蕨类植物点的个数。
$ a' S1 O, L8 k* c$ B0 g1 w% J" n3 @0 f' @
1 ?- s+ O, \; H' B7 M n. { Q( m) a, h+ I' k% k2 Q: T
0 u" x) Z3 n' Q/ W$ |
|