我遇到了一个scipy函数,无论传递给它的是什么,似乎都会返回一个numpy数组。在我的应用程序中,我需要能够仅传递标量和列表,所以唯一的“问题”是当我将标量传递给函数时,返回一个包含一个元素的数组(当我期望一个标量时)。我应该忽略这种行为,还是修改函数以确保在传递标量时返回一个标量?
示例代码:
#! /usr/bin/env python
import scipy
import scipy.optimize
from numpy import cos
# This a some function we want to compute the inverse of
def f(x):
y = x + 2*cos(x)
return y
# Given y, this returns x such that f(x)=y
def f_inverse(y):
# This will be zero if f(x)=y
def minimize_this(x):
return y-f(x)
# A guess for the solution is required
x_guess = y
x_optimized = scipy.optimize.fsolve(minimize_this, x_guess) # THE PROBLEM COMES FROM HERE
return x_optimized
# If I call f_inverse with a list, a numpy array is returned
print f_inverse([1.0, 2.0, 3.0])
print type( f_inverse([1.0, 2.0, 3.0]) )
# If I call f_inverse with a tuple, a numpy array is returned
print f_inverse((1.0, 2.0, 3.0))
print type( f_inverse((1.0, 2.0, 3.0)) )
# If I call f_inverse with a scalar, a numpy array is returned
print f_inverse(1.0)
print type( f_inverse(1.0) )
# This is the behaviour I expected (scalar passed, scalar returned).
# Adding [0] on the return value is a hackey solution (then thing would break if a list were actually passed).
print f_inverse(1.0)[0] # <- bad solution
print type( f_inverse(1.0)[0] )
在我的系统上,输出是:
[ 2.23872989 1.10914418 4.1187546 ]
<type 'numpy.ndarray'>
[ 2.23872989 1.10914418 4.1187546 ]
<type 'numpy.ndarray'>
[ 2.23872989]
<type 'numpy.ndarray'>
2.23872989209
<type 'numpy.float64'>
我使用的是MacPorts提供的SciPy 0.10.1和Python 2.7.3。
解
在阅读下面的答案后,我决定采用以下解决方案。将f_inverse
中的返回行替换为:
if(type(y).__module__ == np.__name__):
return x_optimized
else:
return type(y)(x_optimized)
这里return type(y)(x_optimized)
导致返回类型与调用函数的类型相同。不幸的是,如果y是一个numpy类型,这不起作用,因此if(type(y).__module__ == np.__name__)
用于detect numpy types using the idea presented here并将其从类型转换中排除。
答案 0 :(得分:3)
scipy.optimize.fsolve
中实施的第一行是:
x0 = array(x0, ndmin=1)
这意味着您的标量将变为单元素序列,并且您的1元素序列将基本不变。
它似乎工作的事实是一个实现细节,我会重构你的代码,不允许将标量发送到fsolve
。我知道这似乎违反了duck-typing,但函数要求为该参数设置ndarray
,因此您应该尊重接口,以便对实现中的更改保持健壮。但是,我没有看到有条件地使用x_guess = array(y, ndmin=1)
将标量转换为包装函数中的ndarray
并在必要时将结果转换回标量时出现任何问题。
以下是fsolve
函数的文档字符串的相关部分:
def fsolve(func, x0, args=(), fprime=None, full_output=0, col_deriv=0, xtol=1.49012e-8, maxfev=0, band=None, epsfcn=0.0, factor=100, diag=None): """ Find the roots of a function. Return the roots of the (non-linear) equations defined by ``func(x) = 0`` given a starting estimate. Parameters ---------- func : callable f(x, *args) A function that takes at least one (possibly vector) argument. x0 : ndarray The starting estimate for the roots of ``func(x) = 0``. ----SNIP---- Returns ------- x : ndarray The solution (or the result of the last iteration for an unsuccessful call). ----SNIP----
答案 1 :(得分:2)
以下是如何将Numpy数组转换为列表和Numpy标量转换为Python标量:
>>> x = np.float32(42)
>>> type(x)
<type 'numpy.float32'>
>>> x.tolist()
42.0
换句话说,tolist
上的np.ndarray
方法专门处理标量。
这仍然留给您单个元素列表,但这些很容易以通常的方式处理。
答案 2 :(得分:1)
我想wims的回答确实已经说明了,但也许这会使差异更加清晰。
numpy返回的标量与array[0]
应该(几乎?)完全兼容标准的python float:
a = np.ones(2, dtype=float)
isinstance(a[0], float) == True # even this is true.
在大多数情况下,1大小的数组既与标量和列表兼容,但是例如它是一个可变对象而浮点数不是:
a = np.ones(1, dtype=float)
import math
math.exp(a) # works
# it is not isinstance though
isinstance(a, float) == False
# The 1-sized array works sometimes more like number:
bool(np.zeros(1)) == bool(np.asscalar(np.zeros(1)))
# While lists would be always True if they have more then one element.
bool([0]) != bool(np.zeros(1))
# And being in place might create confusion:
a = np.ones(1); c = a; c += 3
b = 1.; c = b; c += 3
a != b
因此,如果用户不应该知道它,我认为第一个很好,第二个是危险的。
您还可以使用np.asscalar(result)
将大小为1的数组(任意维度)转换为正确的python标量:
在[29]中:type(np.asscalar(a [0])) 出[29]:浮动
如果你想确保一个不应该知道numpy的用户没有意外,你必须至少获得0的元素,如果传入了一个标量。如果用户应该知道numpy,只是文档可能一样好。
答案 3 :(得分:1)
正如@wim指出的那样,fsolve
会将您的标量转换为ndarray
形状(1,)
并返回一个形状(1,)
的数组。
如果您真的想要将标量作为输出,可以尝试将以下内容放在函数的末尾:
if solution.size == 1:
return solution.item()
return solution
(item
方法复制数组元素并返回标准Python标量)