2.2 Julia集合的介绍
让我们从Julia集合这个有趣的CPU密集型问题开始。这是一个可以产生复杂的输出图像的分形数列,以数学家Gaston Julia的名字命名。
函数的代码相当长,你不会想要自己实现一个。它包含一个CPU密集型的组件和一个显式的输入集合。这一配置允许我们分析CPU和RAM的使用情况以帮助我们了解代码中哪部分过多地消耗了这两项计算资源。将代码故意做成非最优的实现,这样我们就可以检查耗内存的操作和慢的语句。我们在本章后面将修正一个慢的语句和一个耗内存的语句,然后在第7章,我们还将显著提升整个函数的执行时间。
我们将分析一段能够生成一个伪灰阶图(图2-1)和一个纯灰阶变种(图2-3)的代码,设Julia集合的复数点c=-0.62772-0.42193j。一个Julia集合可以通过独立计算每一个像素点得到,也就是说这是一个“完美并行计算的问题”,每个点之间没有任何数据共享。
如果我们选择一个不同的c,就会得到一个不同的图像。根据我们的选择,有些区域算起来较快而另一些会较慢,这有助于我们的分析。
问题的有趣之处在于我们对每一个像素的计算都需要进行一个次数不定的循环。每一次迭代都需要计算坐标值是趋于无穷,还是收敛。那些经过少数迭代就能算出结果的坐标在图2-1上为黑色,而那些需要大量迭代才能算出结果的坐标为白色。白色区域需要更多计算,因此生成时间更长。
让我们定义一个z的坐标函数进行计算。函数会计算复数z的平方加c:
我们迭代调用该函数并用abs计算逃逸条件。如果逃逸值为False,那么我们终止循环并在该坐标上记录下迭代的次数。如果逃逸条件始终不满足,那么我们在经过maxiter次迭代后终止,并将该z坐标转化为一个彩色像素点。
伪代码如下:
for z in coordinates:
for iteration in range(maxiter): # limited iterations per point
if abs(z) < 2.0: # has the escape condition been broken?
z = z*z + c
else:
break
# store the iteration count for each z and draw later
让我们试算两个坐标来解释这个函数。
首先,我们将使用图的左上角坐标-1.8-1.8j。在坐标更新前我们就必须首先计算逃逸条件abs(z) < 2:
z = -1.8-1.8j
print abs(z)
2.54558441227
我们可以看到第0次迭代逃逸条件即为False,于是我们不需要更新坐标。该坐标的输出值就是0。
现在让我们跳到图中央的z = 0 + 0j并尝试几次迭代:
c = -0.62772-0.42193j
z = 0+0j
for n in range(9):
z = z*z + c
print "{}: z={:33}, abs(z)={:0.2f}, c={}".format(n, z, abs(z), c)
0: z= (-0.62772-0.42193j), abs(z)=0.76, c=(-0.62772-0.42193j)
1: z= (-0.4117125265+0.1077777992j), abs(z)=0.43, c=(-0.62772-0.42193j)
2: z=(-0.469828849523-0.510676940018j), abs(z)=0.69, c=(-0.62772-0.42193j)
3: z=(-0.667771789222+0.057931518414j), abs(z)=0.67, c=(-0.62772-0.42193j)
4: z=(-0.185156898345-0.499300067407j), abs(z)=0.53, c=(-0.62772-0.42193j)
5: z=(-0.842737480308-0.237032296351j), abs(z)=0.88, c=(-0.62772-0.42193j)
6: z=(0.026302151203-0.0224179996428j), abs(z)=0.03, c=(-0.62772-0.42193j)
7: z= (-0.62753076355-0.423109283233j), abs(z)=0.76, c=(-0.62772-0.42193j)
8: z=(-0.412946606356+0.109098183144j), abs(z)=0.43, c=(-0.62772-0.42193j)
我们可以看到,每次迭代都令abs(z) < 2为True。我们可以对该坐标迭代300次依然为True。我们无法得知需要多少次迭代才能令条件为False,可能是个无穷数列。最大迭代次数(maxiter)会确保我们不至于永远迭代下去。
我们在图2-2中可以看到前50个迭代结果。0+0j的结果数列(带圆形标记的实线)似乎每8个迭代出现一次循环,但是每个循环都跟前一个有微小的区别——我们无法得知该坐标是会永远迭代下去,还是将要迭代很长时间,还是马上就会超出边界条件。短划线cutoff表示+2的边界线。
对于-0.82+0j的结果数列(带菱形标记的短划线),我们可以看到第9次迭代后绝对值结果就超出了+2的cutoff边界线,于是迭代终止。