Skip to content

Unexpected behaviour by numpy.pad() with mode="linear_ramp" and uint dtypes in 1.17 #14191

Closed

Description

@aleju

Since 1.17 numpy.pad() with mode="linear_ramp" seems to not use the array's edge value as the end value, but instead the value range maximum of the array's dtype. This seems to happen only for uint dtypes and only if end_value > edge_value.

Reproducing code example:

import sys, numpy as np; print(np.__version__, sys.version)

for dtype in ["uint8", "uint16", "uint32",
              "int8", "int16", "int32",
              "float16", "float32", "float64"]:
    arr = np.zeros((1,), dtype=dtype)
    arr_pad = np.pad(arr, (3, 0), mode="linear_ramp", end_values=3)
    print("%07s: %s" % (dtype, arr_pad))

Output for 1.17:

1.17.0 3.7.3 | packaged by conda-forge | (default, Jul  1 2019, 21:52:21) 
[GCC 7.3.0]
  uint8: [  3  87 172   0]
 uint16: [    3 21847 43692     0]
 uint32: [         3 1431655767 2863311532          0]
   int8: [3 2 1 0]
  int16: [3 2 1 0]
  int32: [3 2 1 0]
float16: [3. 2. 1. 0.]
float32: [3. 2. 1. 0.]
float64: [3. 2. 1. 0.]

Output for 1.16.4 (on a python 2.7 system):

('1.16.4', '2.7.15 |Anaconda, Inc.| (default, Oct 23 2018, 18:31:10) \n[GCC 7.3.0]')
  uint8: [3 2 1 0]
 uint16: [3 2 1 0]
 uint32: [3 2 1 0]
   int8: [3 2 1 0]
  int16: [3 2 1 0]
  int32: [3 2 1 0]
float16: [3. 2. 1. 0.]
float32: [3. 2. 1. 0.]
float64: [3. 2. 1. 0.]

Further Observations

Using a 2D array does not fix the issue.

Using end_values=(3, 0) instead of end_values=3 does not fix the issue.

However, the issue does not appear in 1.17 so long as the edge value is >=3:

import sys, numpy as np; print(np.__version__, sys.version)

for dtype in ["uint8", "uint16", "uint32",
              "int8", "int16", "int32",
              "float16", "float32", "float64"]:
    arr = np.zeros((1,), dtype=dtype)
    arr = arr + 3
    arr_pad = np.pad(arr, (3, 0), mode="linear_ramp", end_values=(3, 0))
    print("%07s: %s" % (dtype, arr_pad))

Output:

1.17.0 3.7.3 | packaged by conda-forge | (default, Jul  1 2019, 21:52:21) 
[GCC 7.3.0]
  uint8: [3 3 3 3]
 uint16: [3 3 3 3]
 uint32: [3 3 3 3]
   int8: [3 3 3 3]
  int16: [3 3 3 3]
  int32: [3 3 3 3]
float16: [3. 3. 3. 3.]
float32: [3. 3. 3. 3.]
float64: [3. 3. 3. 3.]

Related

Maybe this is related to issue #12293?

There is also a paragraph in the 1.17 release notes saying:

Additionally, there are some corner cases with behavior changes:
Padding max < min has changed to be more consistent across dtypes, but should not be relied upon.

This is in a section about changes to clip, but maybe it is still somehow related?

Activity

  1. seberg commented on Aug 5, 2019

    @seberg
    Member

    @lagru seems like we missed some corner case here, do you wan to have a look? Otherwise I can.

  2. added this to the 1.17.1 release milestone on Aug 5, 2019
  3. lagru commented on Aug 6, 2019

    @lagru
    Contributor

    @seberg Thanks for the summoning.

    @aleju Thank you for the detailed bug report and the time you took investigating! I can reproduce this on 1.18.0.dev0+5f7f8e1 (3.7.3). This line

    gain = (stop - start) / float(size)

    is the culprit. In case the padded array is an unsigned type and the difference is negative we get an overflow because stop has the same type as the padded array. In case the edge value is >= 3 the difference is positive again and everything works as intended.

    Simply casting stop to a float64 does fix this. Not sure if that is the most elegant/efficient way but I'll add a PR where this can be discussed.

  4. lagru commented on Aug 6, 2019

    @lagru
    Contributor

    I'm kind of surprised that this is discovered only now. A similar bug (switching edge and end_value) affects earlier versions as well. E.g. in 1.16:

    >>> x = np.array([3], dtype="u1")
    >>> np.pad(x, 3, mode="linear_ramp", end_values=0)
    array([  0, 172,  87,   3,  87, 172,   0], dtype=uint8)

    Does this mean that either "linear_ramp", unsigned integers or both are only used infrequently?

  5. lagru commented on Aug 6, 2019

    @lagru
    Contributor

    1.16 is still actively maintained, isn't it? That branch would need a patch as well.

    By the way, is there an official place to look which NumPy versions are still maintained? I couldn't find one.

  6. seberg commented on Aug 6, 2019

    @seberg
    Member

    Thanks for the heads up, Chuck already labeled the PR accordingly. We usually do not backport more than one release. 1.16 is the exception, since it will see at least one more bugfix release. My guess is it will not be supported longer then 1.17.x (unless someone outside the current team steps up). It is possible that there will be only one more bugfix release in 1.16.x series.

  7. charris commented on Aug 6, 2019

    @charris
    Member

    I'm going to do a 1.16.5 soon, and maybe one more after that, depending. If a (different) patch is needed, sooner rather than later would be better.

  8. charris commented on Aug 6, 2019

    @charris
    Member

    Note to self: may need 1.16.x backport as well. I wish there could be multiple milestones :)

  9. added a commit that references this issue on Sep 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions