Skip to content

Generalized flip #7346

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

Merged
merged 1 commit into from
Mar 12, 2016
Merged

Generalized flip #7346

merged 1 commit into from
Mar 12, 2016

Conversation

erensezener
Copy link
Contributor

This PR generalizes fliplr and flipud for arbitrary axes.

flipud and fliplr reverse the elements of an array along axis=0 and axis=1 respectively. The flip function reverses the elements of an array along any given axis. In case flip is called with axis=0 or axis=1, the function is equivalent to flipud and fliplr respectively.

A similar function is also available in MATLAB™.

This pull request is part of a BCCN Berlin student project by @denisalevi @ge00rg @erensezener

@shoyer
Copy link
Member

shoyer commented Feb 26, 2016

I think this is a nice idea, but we have a policy that new API needs to be discussed on the mailing list. Could you please post your proposal to the numpy-discussion list: https://mail.scipy.org/mailman/listinfo/numpy-discussion

Thanks!

[7, 6]]])

assert_equal(flip(a, 2), b)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should add a test for a flip at higher dimensions. If you don't want to construct the input array and expected result manually, perhaps show something like

a = np.arange(2 * 3 * 4 * 5).reshape(2, 3, 4, 5)
for i in range(a.ndim):
    assert_equal(flip(a, i), np.flipud(a.swapaxes(0, i)).swapaxes(i, 0))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also implicitly demonstrate the claim that the shape of the array is preserved, although you can add an explicit check. All your other test arrays are 2**3, so it would be hard to tell if the dimensions moved around.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added your test example here: 468c4f2

>>> np.all(flip(A,2)==A[:,:,::-1,...])
True
"""
if axis==0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these special cases really necessary? The function seems to be able to handle them already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are not necessary but flipud and fliplr should be slightly faster. Should we remove the cases?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, clarity is more important than unmeasured micro-optimizations.

On Fri, Feb 26, 2016 at 3:10 PM, C. Eren Sezener
notifications@github.com wrote:

In numpy/lib/twodim_base.py:

  •       [[0, 1],
    
  •        [2, 3]]])
    
  • flip(A, 1)

  • array([[[2, 3],
  •        [0, 1]],
    
  •       [[6, 7],
    
  •        [4, 5]]])
    
  • A = np.random.randn(3,4,5)

  • np.all(flip(A,2)==A[:,:,::-1,...])

  • True
  • """
  • if axis==0:

They are not necessary but flipud and fliplr should be slightly faster.
Should we remove the cases?


Reply to this email directly or view it on GitHub.

Nathaniel J. Smith -- https://vorpus.org

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the special cases 4443294

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The special cases increase the speed for axis = 0 and axis = 1 by a factor of approximately 1.5.

Measuring the execution time of flip(a, 0) using timeit and for an array a = np.arange(100**4).reshape(100,100,100,100) I got (from 1000000 loops, best of 3):

0.865 us when using the special cases (returning flipud(a))
1.34 us when using the current implementation from abe1735 using @njsmith suggestion.

Does that still fall into micro-optimization?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. 0.5 us is very, very fast.
On Fri, Feb 26, 2016 at 11:32 PM Denis Alevi notifications@github.com
wrote:

In numpy/lib/twodim_base.py
#7346 (comment):

  •       [[0, 1],
    
  •        [2, 3]]])
    
  • flip(A, 1)

  • array([[[2, 3],
  •        [0, 1]],
    
  •       [[6, 7],
    
  •        [4, 5]]])
    
  • A = np.random.randn(3,4,5)

  • np.all(flip(A,2)==A[:,:,::-1,...])

  • True
  • """
  • if axis==0:

The special cases increase the speed for axis = 0 and axis = 1 by a
factor of approximately 1.5.

Measuring the execution time of flip(a, 0) using timeit and for an array a
= np.arange(100**4).reshape(100,100,100,100) I got (from 1000000 loops,
best of 3):

0.865 us when using the special cases (returning flipud(a))
1.34 us when using the current implementation from abe1735
erensezener@abe1735
using @njsmith https://github.com/njsmith suggestion.

Does that still fall into micro-optimization?


Reply to this email directly or view it on GitHub
https://github.com/numpy/numpy/pull/7346/files#r54328158.

@madphysicist
Copy link
Contributor

Add a note in the release notes. Aside from the possible bug and other comments, nice job, this looks like a really handy addition.

@charris charris added the 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes label Feb 26, 2016
@@ -32,6 +32,76 @@ def _min_int(low, high):
return int64


def flip(m, axis):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a function that works on arbitrary dimensions, not just 2-d, is this the right file to put it? Perhaps function_base.py and tests/test_function_base.py would be a better choice?

@njsmith
Copy link
Member

njsmith commented Feb 26, 2016

Should flipud and fliplr be rewritten in terms of this?

@mhvk
Copy link
Contributor

mhvk commented Feb 26, 2016

In principle, this could be made even more general by allowing an iterable for axis (i.e., flipping over multiple axes at the same time). Probably overkill, though.

@erensezener
Copy link
Contributor Author

I think I addressed all the issues pointed out besides @madphysicist's last comment:

Since this is a function that works on arbitrary dimensions, not just 2-d, is this the right file to put it? Perhaps function_base.py and tests/test_function_base.py would be a better choice?

Should I also change the file of the function? What are other opinions?

"""
if not hasattr(m, 'ndim'):
m = asarray(m)
if m.ndim <= axis:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't cover negative values of axis. I would remove this altogether and replace it by making the assignment part of a try/except, as follows:

    try:
        indexer[axis] = slice(None, None, -1)
    except IndexError:
        raise ValueError(...)

Question: Should this be an IndexError rather than a ValueError?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, ValueError is definitely the right error to raise for an invalid axis.
On Fri, Feb 26, 2016 at 9:30 PM Marten van Kerkwijk <
notifications@github.com> wrote:

In numpy/lib/twodim_base.py
#7346 (comment):

  •        [2, 3]]])
    
  • flip(A, 1)

  • array([[[2, 3],
  •        [0, 1]],
    
  •       [[6, 7],
    
  •        [4, 5]]])
    
  • A = np.random.randn(3,4,5)

  • np.all(flip(A,2)==A[:,:,::-1,...])

  • True
  • """
  • if not hasattr(m, 'ndim'):
  •    m = asarray(m)
    
  • if m.ndim <= axis:

This doesn't cover negative values of axis. I would remove this
altogether and replace it by making the assignment part of a try/except, as
follows:

try:
    indexer[axis] = slice(None, None, -1)
except IndexError:
    raise ValueError(...)

Question: Should this be an IndexError rather than a ValueError?


Reply to this email directly or view it on GitHub
https://github.com/numpy/numpy/pull/7346/files#r54326237.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I would recommend adding one more exception test that passes in axis < -m.ndim.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added negative axis support with try/except for index error here bc8ec6b

and an exception test here 3dd2a56

@erensezener
Copy link
Contributor Author

As far as I can see, @denisalevi handled all the remaining issues. Ready for merge?

-------
out : array_like
A view of `m` with the entries of axis reversed. Since a view is
returned, this operation is :math:`\\mathcal O(1)`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would simply say "constant time".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done e10be0e

@charris
Copy link
Member

charris commented Mar 2, 2016

Updated.

@charris
Copy link
Member

charris commented Mar 2, 2016

Needs mention in doc/release/1.12.0-notes.rst.

@charris
Copy link
Member

charris commented Mar 2, 2016

Also squash the 12 commits into one or two. You can use git rebase -i HEAD~24 for that followed by a force push git push --force origin <your branch>.

indexer[axis] = slice(None, None, -1)
except IndexError:
raise ValueError("axis=%i is invalid for the %i-dimensional input array"
%(axis, m.ndim))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per pep8, please add a space after the % operator

@erensezener
Copy link
Contributor Author

@charris under what title should the release note be? New features?

@erensezener erensezener force-pushed the generalized_flip branch 2 times, most recently from c619a79 to 4333f98 Compare March 9, 2016 19:21
@denisalevi
Copy link
Contributor

@charris We moved the files and squashed the commits. If you prefer a single PR for flip and rot90, the rot90 PR (#7247) includes the changes of this one here (the generalized_rot90 branch is rebased on the generalized_flip branch)

@@ -99,6 +99,10 @@ keyword argument. It can be set to False when no write operation
to the returned array is expected to avoid accidental
unpredictable writes.

Generalized ``flip``
~~~~~~~~~~~~~~~~~~~~
``flipud`` and ``fliplr`` reverse the elements of an array along axis=0 and axis=1 respectively. The newly added ``flip`` function reverses the elements of an array along any given axis.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines less than 80 characters in length, need line breaks here.

@charris
Copy link
Member

charris commented Mar 12, 2016

Getting close. Add an entry in doc/source/reference/routines.array-manipulation.rst also. Search for flipud to see where. The test failures are bogus, so don't worry about them.

@denisalevi
Copy link
Contributor

@charris done and squashed.

@charris charris removed the 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes label Mar 12, 2016
def test_3d_swap_axis0(self):
a = np.array([[[0, 1],
[2, 3]],

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can leave out the blank lines.

@charris
Copy link
Member

charris commented Mar 12, 2016

I've made a few comments in the tests for formatting. I'll put this in and you can do the fixes in the rot90 PR.

charris added a commit that referenced this pull request Mar 12, 2016
@charris charris merged commit 5b91628 into numpy:master Mar 12, 2016
@charris
Copy link
Member

charris commented Mar 12, 2016

Thanks @erensezener and co.

@erensezener
Copy link
Contributor Author

Thanks @charris. Did the changes here: 7a57783d84188d24ed18dc16a825b13a556087d3

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

Successfully merging this pull request may close these issues.

7 participants