2.3 计算整个朱利亚集合
本节详细介绍生成朱利亚集合的代码。本章后面将以各种方式对这些代码进行分析。如示例2-1所示,在模块开头,我们为支持第一种剖析方法导入了模块time,还定义了一些坐标常量。
示例2-1 定义表示坐标空间的全局常量
"""Julia set generator without optional PIL-based image drawing""" import time # area of complex space to investigate x1, x2, y1, y2 = -1.8, 1.8, -1.8, 1.8 c_real, c_imag = -0.62772, -.42193
为生成图形,需要创建两个包含输入数据的列表。第一个列表为zs(复平面坐标z),第二个列表为cs(表示初始条件的复数)。这两个列表都是不变的,且对于列表cs,可将其优化为单个常量值c。这里创建两个输入列表,旨在提供一些看起来合理的数据,以便本章后面能够剖析RAM占用情况。
要想创建列表zs和cs,需要知道每个z的坐标。在示例2-2中,使用了xcoord和ycoord以及步长x_step和y_step来生成这些坐标。这里所做的设置工作有点烦琐,但有助于将代码移植到其他工具(如numpy)和其他Python环境中,因为明确地定义了方方面面,这有助于调试。
示例2-2 创建用作计算函数输入的坐标列表
def calc_pure_python(desired_width, max_iterations): """Create a list of complex coordinates (zs) and complex parameters (cs), build Julia set""" x_step = (x2 - x1) / desired_width y_step = (y1 - y2) / desired_width x = [] y = [] ycoord = y2 while ycoord > y1: y.append(ycoord) ycoord += y_step xcoord = x1 while xcoord < x2: x.append(xcoord) xcoord += x_step # build a list of coordinates and the initial condition for each cell. # Note that our initial condition is a constant and could easily be removed, # we use it to simulate a real-world scenario with several inputs to our # function zs = [] cs = [] for ycoord in y: for xcoord in x: zs.append(complex(xcoord, ycoord)) cs.append(complex(c_real, c_imag)) print("Length of x:", len(x)) print("Total elements:", len(zs)) start_time = time.time() output = calculate_z_serial_purepython(max_iterations, zs, cs) end_time = time.time() secs = end_time - start_time print(calculate_z_serial_purepython._ _name_ _ + " took", secs, "seconds") # This sum is expected for a 1000^2 grid with 300 iterations # It ensures that our code evolves exactly as we'd intended assert sum(output) == 33219980
创建列表zs和cs后,我们输出一些关于列表长度的信息,并调用函数calculate_z_serial_purepython生成列表output。最后,我们对列表output中的值求和,并使用assert确认结果与预期的输出值相同。这里这样做旨在确认书中没有出现错误。
由于上述代码是完整且正确的,因此可以对计算得到的值求和来验证函数是否按预期工作。这种完整性检查很有用——当我们修改代码时,检查我们没有破坏算法是明智的。理想情况下,应使用单元测试进行检查,还应检查多个方面。
接下来,我们定义函数calculate_z_serial_purepython,它扩展了本章前面讨论的算法,如示例2-3所示。注意,这个函数开头定义了列表output,其长度与输入列表zs和cs相同。
示例2-3 CPU密集型计算函数
def calculate_z_serial_purepython(maxiter, zs, cs): """Calculate output list using Julia update rule""" output = [0] * len(zs) for i in range(len(zs)): n = 0 z = zs[i] c = cs[i] while abs(z) < 2 and n < maxiter: z = z * z + c n += 1 output[i] = n return output
现在可以调用calc_pure_python了,如示例2-4所示。将调用该函数的语句放在一条执行_ _main_ _检查的if语句中,这样可在尝试剖析方法时安全地导入这个模块,而不启动计算。请注意,这里没有列出绘制输出的方法。
示例2-4 主函数
if _ _name_ _ == "_ _main_ _": # Calculate the Julia set using a pure Python solution with # reasonable defaults for a laptop calc_pure_python(desired_width=1000, max_iterations=300)
如果此时运行这些代码,我们将看到有关问题复杂性的输出:
# running the above produces: Length of x: 1000 Total elements: 1000000 calculate_z_serial_purepython took 8.087012767791748 seconds
在图2-1所示的伪灰度图中,颜色对比度较高,这让我们知道在各个地方函数开销的变化速度是快还是慢。图2-3显示的是线性颜色映射图,其中的黑色表示计算时间短,而白色表示计算时间长。
图2-3 纯灰度的朱利亚集合图
通过呈现数据的两种表示方式,我们可以看到线性颜色映射会丢失大量的细节。在有些情况下,使用不同的表示方式对研究函数开销很有帮助。