Python科学计算(第2版)
上QQ阅读APP看书,第一时间看更新

2.2.3 自定义ufunc函数

通过NumPy提供的标准ufunc函数,可以组合出复杂的表达式,在C语言级别对数组的每个元素进行计算。但有时这种表达式不易编写,而对每个元素进行计算的程序却很容易用Python实现,这时可以用frompyfunc()将计算单个元素的函数转换成ufunc函数,这样就可以方便地用所产生的ufunc函数对数组进行计算了。

例如,我们可以用一个分段函数描述三角波,三角波的形状如图2-5所示,它分为三段:上升段、下降段和平坦段。

图2-5 三角波可以用分段函数进行计算

根据图2-5,我们很容易写出计算三角波上某点的Y坐标的函数。显然triangle_wave()只能计算单个数值,不能对数组直接进行处理。

    def triangle_wave(x, c, c0, hc):
        x = x - int(x) # 三角波的周期为1,因此只取x坐标的小数部分进行计算
        if x >= c: r = 0.0
        elif x < c0: r = x / c0 * hc
        else: r = (c-x) / (c-c0) * hc
        return r

我们可以用下面的程序,先使用列表推导式计算出一个列表,然后用array()将列表转换为数组。这种做法每次都需要使用列表推导式语法调用函数,这对于多维数组很麻烦。

    x = np.linspace(0, 2, 1000)
    y1 = np.array([triangle_wave(t, 0.6, 0.4, 1.0) for t in x])

通过frompyfunc()可以将计算单个值的函数转换为能对数组的每个元素进行计算的ufunc函数。frompyfunc()的调用格式为:

    frompyfunc(func, nin, nout)

其中:func是计算单个元素的函数,nin是func的输入参数的个数,nout是func的返回值的个数。下面的程序使用frompyfunc()将triangle_wave()转换为ufunc函数对象triangle_ufunc1:

    triangle_ufunc1 = np.frompyfunc(triangle_wave, 4, 1)
    y2 = triangle_ufunc1(x, 0.6, 0.4, 1.0)

值得注意的是,triangle_ufunc1()所返回的数组的元素类型是object,因此还需要调用数组的astype()方法,以将其转换为双精度浮点数组:

    y2.dtype   y2.astype(np.float).dtype
    ----------  -------------------------
    dtype('O') dtype('float64')         

使用vectorize()也可以实现和frompyfunc()类似的功能,但它可以通过otypes参数指定返回的数组的元素类型。otypes参数可以是一个表示元素类型的字符串,也可以是一个类型列表,使用列表可以描述多个返回数组的元素类型。下面的程序使用vectorize()计算三角波:

    triangle_ufunc2 = np.vectorize(triangle_wave, otypes=[np.float])
    y3 = triangle_ufunc2(x, 0.6, 0.4, 1.0)

最后我们验证一下结果:

    np.all(y1 == y2)  np.all(y2 == y3)
    ----------------  ----------------
    True            True