2.2 朱利亚集合简介
朱利亚集合是一个有趣的CPU密集型问题,它是一个生成复杂图形的分形数列,以加斯顿·朱利亚(Gaston Julia)的名字命名。
本书将介绍的代码可能比你编写的要长些,其中包括CPU密集型部分及一组显式输入。这让我们能够剖析CPU占用情况和RAM占用情况,从而明白哪部分代码较多地占用了这两项稀缺的计算资源。这里的实现并非最优的,这是有意为之的,旨在让我们找出消耗内存的操作以及速度缓慢的语句。本章后面将修改一条速度缓慢的逻辑语句,还有一条消耗大量内存的语句,而第7章将显著地缩短这个函数的执行时间。
我们将分析一个代码块,它使用复平面点c= −0.62772−0.42193j生成朱利亚集合的伪灰度图(如图2-1所示)和纯灰度图(如图2-3所示)。朱利亚集合是分别计算每个像素来生成的,这是一个高度并行问题,因为不同的点之间不共享任何数据。
如果选择不同的c值,将得到不同的图像。对于特定的c值,图像有些区域计算起来较快,有些区域计算起来较慢,这对我们的分析大有裨益。
上面这个问题很有趣,因为每个像素都是通过循环计算得到的,但循环迭代的次数随像素而异。在每次迭代中,系统都会检查当前坐标会导致结果趋于无穷大还是受制于一个吸引子。在图2-1中,对于特定的坐标,如果需要执行的迭代次数较少,就涂上深色,如果需要执行的迭代次数较多,就涂上白色。白色区域计算起来更复杂,因此生成它们需要的时间更长。
图2-1 突出了细节的伪灰度朱利亚集合图
我们定义一组需要检查的z坐标,并使用下面的函数来计算复数z的平方加上c的结果:
f(z) = z2 + c
我们在循环中计算这个函数的结果,并使用abs判断是否满足继续循环的条件。如果不满足,就退出循环,并将迭代次数记录下来。如果始终满足继续循环的条件,就在迭代maxiter次后结束循环。然后,根据迭代次数给复数点对应的像素着色。
计算迭代次数的伪代码类似于下面这样:
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
为帮助你弄明白这个函数,我们使用两个坐标来训练它。
先来看图2-1左上角的坐标−1.8−1.8j。修改z值前,必须检查条件abs(z) < 2是否满足:
z = -1.8-1.8j print(abs(z)) 2.54558441227
由此可知,对于左上角的坐标,零次迭代时就不满足继续循环的条件,因为2.54 >= 2.0。因此,不用再修改z的值。对于这个坐标,输出结果为0。
接下来看图2-1中心坐标z=0+0j,并尝试执行几次迭代:
c = -0.62772-0.42193j z = 0+0j for n in range(9): z = z*z + c print(f"{n}: z={z: .5f}, abs(z)={abs(z):0.3f}, c={c: .5f}") 0: z=-0.62772-0.42193j, abs(z)=0.756, c=-0.62772-0.42193j 1: z=-0.41171+0.10778j, abs(z)=0.426, c=-0.62772-0.42193j 2: z=-0.46983-0.51068j, abs(z)=0.694, c=-0.62772-0.42193j 3: z=-0.66777+0.05793j, abs(z)=0.670, c=-0.62772-0.42193j 4: z=-0.18516-0.49930j, abs(z)=0.533, c=-0.62772-0.42193j 5: z=-0.84274-0.23703j, abs(z)=0.875, c=-0.62772-0.42193j 6: z= 0.02630-0.02242j, abs(z)=0.035, c=-0.62772-0.42193j 7: z=-0.62753-0.42311j, abs(z)=0.757, c=-0.62772-0.42193j 8: z=-0.41295+0.10910j, abs(z)=0.427, c=-0.62772-0.42193j
从上述输出可知,在这些迭代中,条件abs(z) < 2都满足。实际上,对于这个坐标,迭代300次时,继续循环条件依然满足。我们不知道要迭代多少次后,继续循环条件才不满足,也许是无穷次。通过指定最大迭代次数(maxiter)可以避免无限地循环下去。
图2-2显示了前50次的迭代结果。对于0+0j(带圆圈的实线),看起来每隔7次迭代结果就重复,但不是完全重复,存在细微的差异。我们无法判断迭代结果是始终在边界内,还是迭代很多次后就会超出边界,或是迭代几次就会超出边界。图中的虚线表示的是位于+2处的边界线。
图2-2 朱利亚集合的两个坐标的演化示例
对于坐标点−0.82+0j(带菱形的虚线),第9次迭代后就超出了位于+2的边界线,因此不再迭代下去。