Python 如何确定是否需要numpy.vectorize()?

Python 如何确定是否需要numpy.vectorize()?,python,numpy,Python,Numpy,有没有办法在运行时确定函数是否需要numpy.vectorize()才能按预期运行 作为背景,我问这个问题是因为我在一个程序中使用Numpy,从文献中可用的热力学函数计算相图(基于CALPHAD)。对于给定的温度,计算自由能函数并确定公共切线曲线(二阶导数>0),以定义相共存的组成范围。因此,直接定义二阶导数函数很好。在我试着用一个简单的抛物线自由能函数进行测试之前,真正的自由能函数(不难得到的导数)一切都进行得很顺利,它有一个常数的二阶导数。这破坏了我的算法,因为我没想到numpy广播会查看函

有没有办法在运行时确定函数是否需要numpy.vectorize()才能按预期运行

作为背景,我问这个问题是因为我在一个程序中使用Numpy,从文献中可用的热力学函数计算相图(基于CALPHAD)。对于给定的温度,计算自由能函数并确定公共切线曲线(二阶导数>0),以定义相共存的组成范围。因此,直接定义二阶导数函数很好。在我试着用一个简单的抛物线自由能函数进行测试之前,真正的自由能函数(不难得到的导数)一切都进行得很顺利,它有一个常数的二阶导数。这破坏了我的算法,因为我没想到numpy广播会查看函数内部并决定它不需要广播

困难在于这种行为:

import numpy as np
def f(x):
   return(x*x)
def g(x):
   return(3.0)
def h(x):
   return(0*x+3.0)
def i(x):
   return(x-x+3.0)

x = np.linspace(1.0, 5.0, 5)
在IPython 3.3.2中运行会产生以下输出:

f(x)-> 数组([1,4,9,16,25.])——我所期望的

g(x)-> 3.0(注意只有1个元素和一个float,而不是ndarray)——这不是天真的预期

h(x)-> 数组([3,3,3,3,3.])——好吧,让x做点什么愚弄了广播

i(x)-> 数组([3,3,3,3,3.])——与h(x)相同,但避免乘法,但存在舍入问题

现在我可以用

gv = np.vectorize(g)
得到

gv(x)->数组([3,3,3,3,3.])--预期行为

如果我的程序(最终)接受任意用户输入的自由能函数,这将导致问题,除非所有用户都理解numpy内部广播魔术。 或者,我可以反射性地np.矢量化一切来防止这种情况。问题是,如果函数在numpy中“正常工作”,那么成本是多少

也就是说,在IPython中使用%timeit

h(x) -> 100000 loops, best of 3: 3.45 µs per loop
如果我不必要地对h(x)进行向量化(即hv=np.向量化(h)),我得到

因此,不必要的矢量化是一个巨大的损失(5次函数求值需要40微秒)

我想我可以对一个函数的返回进行初始测试,在一个小的数据数组上求值,看看返回类型是数组还是浮点,然后定义一个新函数,如果它是浮点,比如:

def gv(x):
   return(g(x)+0.0*x)
这看起来像是一个可怕的胡扯


那么-在这种情况下,有没有更好的方法“愚弄”numpy,使其高效地进行广播?

解决上述问题。如果需要新阵列,请执行以下操作:

def g(x):
    return np.ones_like(x)*3
或者,如果要将数组中的所有元素设置为3,请执行以下操作:

def g(x):
    x[:] = 3
注意这里没有返回语句,因为您只是简单地更新数组
x
,以便所有元素都是3


如图所示,def g(x):return(3)的问题在于函数中没有对numpy的引用。您可以为任何给定的输入返回3的状态。在更新指针
x
以指向
3
而不是numpy数组时,声明
x=3
将遇到类似的问题。虽然语句
x[:]=3
numpy.ndarray
类访问称为视图的内部函数,而不是像其他人建议的那样使用
=
语句来更新指针。

,但您可以包装用户提供的函数以确保输出形状正确。例如:

def wrap_user_function(func, x):
   out = func(x)
   if np.isscalar(out):
     return np.zeros_like(x) + out
   return out

这只会专门处理标量输出情况,但它至少应该解决
g(x)
问题,而不会对性能造成太大影响。

您可以检查
g(x)
返回的值的形状,并在需要时广播它。
numpy
(和MATLAB)函数对输入进行按摩,使其成为最佳形状以进行计算,然后进一步重塑输出以匹配输入形状,这并不罕见。例如,看看
np.vectorize
的内部结构。为什么要使用numpy.vectorize?我假设在那之后你想做f(x)+g(x)左右?但当f(x)返回numpy.ndarray,而g(x)返回float时,这也会起作用。出现问题的原因是,如果x是ndarray,则y=f(x)返回ndarray,但y=g(x)返回一个浮点值,而不是len(x)数组。因此,如果试图编写一个库来处理函数,那么当您无法获得预期的数组时,可能会得到不寻常的结果。numpy.vectorize()只是让它工作起来的一个难题。这只是一个想法,但是写一个装饰器来检查能量函数的输出怎么样?如果需要的话,让它重塑到输入数组中(即,如果是标量,调整大小
x
的形状)。我喜欢这样做,因为它避免了我的一些其他难题选项。如果需要的话,我将深入研究numpy代码,但是我仍然不清楚为什么numpy没有将g()的返回值广播到输入ndarray。谢谢。@JonCuster我已经更新了一些答案。我认为你的问题来自于对python工作原理的误解,而不是对numpy方面的误解。我感谢你的温和评论!我已经建立了一个基于f(x)的心理模型。但是,是的,通过一些模型的重新调整,我可以看到,如果函数中没有任何内容表明numpy参与了类型确定(以及基于类型/大小问题的任何广播),那么python本身会愉快地返回似乎被请求的单个浮点。
def wrap_user_function(func, x):
   out = func(x)
   if np.isscalar(out):
     return np.zeros_like(x) + out
   return out