MKhoi
MKhoi

Reputation: 13

"ValueError: The truth value of an array with more than one element is ambiguous." when using scipy.integrate.dblquad

I'm trying to perform a double integration on this function

def prob(x,y):
    ch = np.sqrt((3-y)**2 + x**2)
    hb = np.sqrt((4-x)**2 + y**2)
    if np.isclose(4 * y, 12 - 3 * x):
        # Without the if statement, any x, y values that satisfy the condition will return nan
        return 0.5
    else:
        return (np.arccos((ch**2 + hb**2 - 25) / (2 * ch * hb)))/(2*np.pi)

Contour plot of the function

from scipy import integrate as integ
integ.dblquad(prob(x,y), 0, 4, lambda x: 0, lambda x: 3)

However I got this error message pointing at the if statement

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Trivially, if i use math.isclose instead of np.isclose in the if statement, I get this error message pointing to the same statement

TypeError: only size-1 arrays can be converted to Python scalars

To my understanding, boolean array has something to do with these errors but checking everything including the if statement yields a scalar boolean.

So are there any way around this?

Upvotes: 1

Views: 1764

Answers (3)

hpaulj
hpaulj

Reputation: 231425

Short answer - you need to provide full traceback and calling specification. And you need to call dblquad correctly.

===

What are x,y when you call with

integ.dblquad(prob(x,y), 0, 4, lambda x: 0, lambda x: 3)

Depnding on those 2 variables, the error might be rising before dblquad is even called.

The first argument to dblquad is supposed to be a function, not an array. Using prob instead of prob(x,y) might work.

Given 2 scalars prob works:

In [674]: prob(1,2)                                                                                    
Out[674]: 0.4685835209054995

Given two arrays we get the ambiguity error:

In [675]: prob(np.arange(3),np.arange(1,4))                                                            
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-675-4b16a88f567e> in <module>
----> 1 prob(np.arange(3),np.arange(1,4))

<ipython-input-673-e31785dd54a5> in prob(x, y)
      2     ch = np.sqrt((3-y)**2 + x**2)
      3     hb = np.sqrt((4-x)**2 + y**2)
----> 4     if np.isclose(4 * y, 12 - 3 * x):
      5         # Without the if statement, any x, y values that satisfy the condition will return nan
      6         return 0.5

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

And the full traceback (which you should have given us) shows the problem is with the isclose call:

In [676]: np.isclose(np.arange(3),np.arange(1,4))                                                      
Out[676]: array([False, False, False])

It produces a boolean array, which can't be used in an if clause.

Using math.isclose raises error because math functions only accept scalars. They don't work with arrays (multivalued ones).

Using dblquad correctly, it works:

In [678]: integ.dblquad(prob, 0,4,lambda x:0, lambda x:3)                                              
Out[678]: (4.42854383700761, 1.8525461432365937e-08)

dblquad passes scalar values to your function, so the use of isclose (either numpy or math) isn't problem

Using math functions with scalar inputs is faster:

def probm(x,y):
    ch = math.sqrt((3-y)**2 + x**2)
    hb = math.sqrt((4-x)**2 + y**2)
    if math.isclose(4 * y, 12 - 3 * x):
        # Without the if statement, any x, y values that satisfy the condition will return nan
        return 0.5
    else:
        return (math.acos((ch**2 + hb**2 - 25) / (2 * ch * hb)))/(2*math.pi)

In [683]: integ.dblquad(probm, 0,4,lambda x:0, lambda x:3)                                             
Out[683]: (4.428543836134556, 1.8890715880459652e-08)

much faster:

In [685]: timeit integ.dblquad(prob, 0,4,lambda x:0, lambda x:3)                                       
11.7 s ± 24.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [686]: timeit integ.dblquad(probm, 0,4,lambda x:0, lambda x:3)                                      
272 ms ± 1.12 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Upvotes: 0

Leporello
Leporello

Reputation: 658

Well, the errors you get are sufficiently helpful in terms of diagnostic. The problem is that np.isclose will return an array of booleans, and trying to coerce such an array into a single value (which the if clause does) raises an error. (1)

How to fix it is more dependent on what you want to achieve. Assuming that prob(x,y) should return a numpy array of the same dimensions as x/y, where values are replaced by 0.5 at locations where the condition is true, you can use:

output = (np.arccos((ch**2 + hb**2 - 25) / (2 * ch * hb)))/(2*np.pi)
output[np.isclose(4 * y, 12 - 3 * x)] = .5
return output

Notice that this may not be the best way to solve your actual problem. It often makes more sense to keep NaN in positions where the calculation fails (that's what NaNs are for). If you want to replace NaNs by 0.5, you might be better off testing for NaN rather than by your math condition:

output = (np.arccos((ch**2 + hb**2 - 25) / (2 * ch * hb)))/(2*np.pi)
output[np.isnan(output)] = .5
return output

(1) One could argue that any non-empty array should evaluate to True similarly to what lists do in non-numpy Python, and regardless of values (i.e. np.asarray([False]) evaluates as True). I for one am happy that numpy will raise an exception rather than try to guess what I want in that situation (which could be all, any, len(array)>0, or as here element-wise operation that needs to be rewritten).

Upvotes: 0

Grr
Grr

Reputation: 16079

Perhaps this is a typographical error.

From the edit I made it looks like you were calling:

integ.dblquad(prob(x, y), 0, 4, lambda x: 0, lambda x: 3)

When in fact you should be calling:

integ.dblquad(prob, 0, 4, lambda x: 0, lambda x: 3)

I believe you likely have and array x and array y that are causing the error when calling dblquad the first way.

E.g.

x = np.array([1,2,3])
y = np.array([3,4,5])
integ.dblquad(prob(x, y), 0, 4, lambda x: 0, lambda x: 3)
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

integ.dblquad(prob, 0, 4, lambda x: 0, lambda x: 3)
(4.42854383700761, 1.8525461432365937e-08)

Upvotes: 1

Related Questions