Skip to content

numpy.linalg.norm() broken for multidimensional integer arrays #5626

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
djrobust opened this issue Mar 3, 2015 · 18 comments
Closed

numpy.linalg.norm() broken for multidimensional integer arrays #5626

djrobust opened this issue Mar 3, 2015 · 18 comments

Comments

@djrobust
Copy link

djrobust commented Mar 3, 2015

import numpy as np

a = np.array([[30, 12, 91],
              [18, 4, 13]], dtype='uint8')
b = a.astype('float')

print(np.linalg.norm(a, axis=1))
print(np.sqrt(np.diag(np.dot(a, a.T))))

print(np.linalg.norm(b, axis=1))
print(np.sqrt(np.diag(np.dot(b, b.T))))

print(np.version.version)
[ 19.10497317  15.90597372]
[ 10.44030666  15.90597343]
[ 96.56603958  22.56102835]
[ 96.56603958  22.56102835]
1.9.2
@charris
Copy link
Member

charris commented Mar 3, 2015

Oh, that's not good. The problem seems to be x = asarray(x), which isn't going to work for some of the norms.

@charris
Copy link
Member

charris commented Mar 3, 2015

The +/-Inf norms aren't going to work for signed integers either, e.g., abs(-128) == -128 for int8.

@charris charris added this to the 1.10 blockers milestone Mar 4, 2015
@charris
Copy link
Member

charris commented Mar 4, 2015

Some examples

In [17]: x = np.array([-128], dtype=np.int8)

In [18]: norm(x, ord=inf)
Out[18]: -128

In [19]: norm(x, ord=-inf)
Out[19]: -128

In [20]: norm(x, ord=1)
Out[20]: -128

In [21]: norm(x, ord=2)
Out[21]: 0.0

@argriffing
Copy link
Contributor

Is it not a bug that np.absolute returns a negative number? Or if that's what people expect then wouldn't they also expect norm to return a negative number?

@argriffing
Copy link
Contributor

A related issue #5018 with the appropriate reference to http://xkcd.com/1172/. If numpy wants to break spacebar heating, what if all functions in np.linalg immediately convert their inputs to the most appropriate BLAS types (single or double precision real or complex numbers)?

@charris
Copy link
Member

charris commented Mar 19, 2015

@argriffing I'm thinking of converting everything to floats or objects for linalg.norm.

@alexirpan
Copy link

It seems like you have to convert to a float before doing any operations on it, if you want it to work as expected.

In the first one, 30 * 30 = 132, 12 * 12 = 144, 91 * 91 = 89 for 8 bit unsigned integers, so linalg.norm returns the square root of 365. For the second, the dtype is still uint8 up to the square root. 365 = 109 mod 256, and you get the square root of that instead.

I think the 2nd example isn't actually a bug, but the behavior is very unintuitive if you don't see what's going on. Although the 1st example (the linalg.norm) call makes sense once you think about it, I'd prefer the float casting solution, having the expected behavior of the standard library match expectations is worth the possible slowdowns.

@argriffing
Copy link
Contributor

@argriffing I'm thinking of converting everything to floats or objects for linalg.norm.

Great! (I assume you would also allow complex numbers)

@charris charris modified the milestones: 1.10.0 release, 1.10 blockers May 4, 2015
@charris charris modified the milestones: 1.11.0 release, 1.10.0 release Oct 13, 2015
@jakirkham
Copy link
Contributor

Converting to floats sounds good. That should help the L0 case, as well.

In [1]: import numpy 

In [2]: import numpy as np

In [3]: np.linalg.norm(np.arange(6), 0)
Out[3]: 5

In [4]: np.linalg.norm(np.arange(6), 1)
Out[4]: 15

In [5]: np.linalg.norm(np.arange(6.0), 1)
Out[5]: 15.0

In [6]: np.linalg.norm(np.arange(6.0), 0)
Out[6]: 5

@charris
Copy link
Member

charris commented Jan 21, 2016

OK, this change should be made. Still time for 1.11 if someone wants to step up...

@jakirkham
Copy link
Contributor

Sure, I'll give it a try. Should be straightforward. All we want to do is cast to float if it is not floating point. Should we raise a warning that we did so too? If so, what kind of warning? Should I PR master and then do a backport PR?

@charris
Copy link
Member

charris commented Jan 21, 2016

I don't think a warning is needed, we should just return floats. I'll add a mention in the release notes when it goes in.

@jakirkham
Copy link
Contributor

Interesting fact, it states it will return float or ndarray, but there are examples that break this constraint.

@charris
Copy link
Member

charris commented Jan 21, 2016

Interesting. IIRC, the current behavior is actually a regression.

@jakirkham
Copy link
Contributor

I added a PR ( #7088 ) for the simple fix to see if that goes ok. Then we probably should go back and clean up the examples. For now, I have allowed other types of floats that are not 64-bit. This way we aren't forcing people to cast up or down. However, if it isn't a kind of float we are always casting to 64-bit floats.

@jakirkham
Copy link
Contributor

It should be pointed out that the OP has also demonstrated that np.dot is broken too.

@charris
Copy link
Member

charris commented Jan 21, 2016

@jakirkham How so? There are no overflow guarantees for dot...

@jakirkham
Copy link
Contributor

Oh ok, if there are no guarantees, I guess it is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants