Skip to content

ENH: allow arrays for start and stop in {lin,log,geom}space #12388

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 4 commits into from
Dec 6, 2018

Conversation

mhvk
Copy link
Contributor

@mhvk mhvk commented Nov 14, 2018

As discussed on the mailing list, perhaps it would be good for linspace to allow start and stop to be arrays, which are broadcast against each other. For now, this is just a proof of concept - which presumes the final axis that is counted is the first one. With it, two tests failed, which seem both false positives (in that the tests were not written very well, in one case with a note stating that).

EDIT: TODO from mailing: add axis argument (probably with 0 as a default; should be axis in output, so -1 is last). Could be simple transpose at end, or use reshaping of inputs.

Anyway, with this:

In [4]: np.linspace(np.array([1., 2.]), np.array([3., 4.]), 5)
Out[4]: 
array([[1. , 2. ],
       [1.5, 2.5],
       [2. , 3. ],
       [2.5, 3.5],
       [3. , 4. ]])

In [5]: np.linspace(np.array([1., 2.]), np.array([[3.], [4.]]), 5)
Out[5]: 
array([[[1.  , 2.  ],
        [1.  , 2.  ]],

       [[1.5 , 2.25],
        [1.75, 2.5 ]],

       [[2.  , 2.5 ],
        [2.5 , 3.  ]],

       [[2.5 , 2.75],
        [3.25, 3.5 ]],

       [[3.  , 3.  ],
        [4.  , 4.  ]]])

# Ideally should be `'shape': ()` but the current interface
# does not allow that
return {'shape': (1,), 'typestr': '<i4', 'data': self._data,
return {'shape': (), 'typestr': '<i4', 'data': self._data,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently, the interface is now better, since shape=() works at least on my python installation.


# these should not crash
np.histogram([np.array([0.5]) for i in range(10)] + [.500000000000001])
np.histogram([np.array([0.5]) for i in range(10)] + [.5])
np.histogram([np.array(0.5) for i in range(10)] + [.500000000000001])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test, as written, made no sense; there is no reason you should be allowed to mix 1-d arrays with scalars, even if they are single-element.

Copy link
Member

Choose a reason for hiding this comment

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

I think I wrote these mostly to test that they didn't blow up in a non-sensical internal crash. The thinking was that this is just doing a histogram on object arrays.

If we could commit to making bool(np.array([1]) < 0) an error, then this would no longer be allowed either.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. But just to be sure: is the change here fine? If we want object arrays, it might be better to just create them explicitly.

@mhvk mhvk changed the title Linspace allow array ENH: allow arrays for start and stop in linspace Nov 14, 2018
Copy link
Contributor

@hameerabbasi hameerabbasi left a comment

Choose a reason for hiding this comment

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

I'm +0.2 on this change. I agree that it could make a lot of sense.

Adding the axis kwarg would be nice, it would be a simple transpose operation at the end. :)

@@ -34,9 +34,9 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None):

Parameters
----------
start : scalar
start : scalar or array
Copy link
Contributor

Choose a reason for hiding this comment

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

Would array_like be better?

The starting value of the sequence.
stop : scalar
stop : scalar or array
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

@shoyer
Copy link
Member

shoyer commented Dec 1, 2018

I wrote a small PR to add dispatching to these functions: #12471

@charris
Copy link
Member

charris commented Dec 4, 2018

The request for array_like seems reasonable. What about subtypes?

If this is intended for 1.16 it needs a release note. I'd like to branch 1.16.x soon.

And which failed with the change to linspace.
@mhvk mhvk force-pushed the linspace-allow-array branch from 78cf869 to 099dda6 Compare December 4, 2018 20:31
@mhvk mhvk changed the title ENH: allow arrays for start and stop in linspace ENH: allow arrays for start and stop in {lin,log,geom}space Dec 4, 2018
@mhvk mhvk removed the 25 - WIP label Dec 4, 2018
@mhvk mhvk added this to the 1.16.0 release milestone Dec 4, 2018
@mhvk
Copy link
Contributor Author

mhvk commented Dec 4, 2018

@charris - OK, I added the array_like and also ensured things work for logspace and geomspace - the latter was a bit more of a change.

Note that I did not add an axis argument - I can do that but felt the case for it was not that strong; it is trivial also to add the swapaxes oneself.

@@ -54,6 +54,18 @@ def test_basic(self):
y = logspace(0, 6, num=7)
assert_array_equal(y, [1, 10, 100, 1e3, 1e4, 1e5, 1e6])

def test_array(self):
Copy link
Member

Choose a reason for hiding this comment

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

test_start_stop_are_arrays or some such would be a more descriptive name here and below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was because the test above was just called test_array_scalar - but really both are unclear, so I changed both now.

@charris
Copy link
Member

charris commented Dec 5, 2018

Just a suggestion for a name change for the new tests.

@@ -72,7 +72,8 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None):
samples : ndarray
There are `num` equally spaced samples in the closed interval
``[start, stop]`` or the half-open interval ``[start, stop)``
(depending on whether `endpoint` is True or False).
(depending on whether `endpoint` is True or False). If start or
stop are array-like, then the samples will be on the first axis.
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps "will be along a new axis inserted at the beginning" would be more accurate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adopted.

start = start.astype(dt, copy=True)
stop = stop.astype(dt, copy=True)

out_sign = _nx.ones((start + stop).shape, dt)
Copy link
Member

Choose a reason for hiding this comment

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

Addition here is pretty wasteful to get np.broadcast(start, stop).shape

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, and clearer in intent too. Done.

start = start + 0j
stop = stop + 0j
if _nx.issubdtype(dt, _nx.complexfloating):
all_imag = (start.real == 0.) & (stop.real == 0)
Copy link
Member

Choose a reason for hiding this comment

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

nit: spell 0. as 0 to make the two terms consistent

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

raise ValueError('Geometric sequence cannot include zero')

dt = result_type(start, stop, float(num))
dt = result_type(start, stop, float(num), _nx.zeros((), dtype))
Copy link
Member

Choose a reason for hiding this comment

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

Does this behave differently than just passing dtype in directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

dtype can be None - I actually tried thinking of a quicker way.

@mhvk mhvk force-pushed the linspace-allow-array branch from 099dda6 to 58ebb6a Compare December 5, 2018 14:47
@mhvk
Copy link
Contributor Author

mhvk commented Dec 5, 2018

OK, thanks. Adopted the suggestions.

@shoyer
Copy link
Member

shoyer commented Dec 5, 2018

I really do this we should add an axis argument, which could default to axis=0. People seem to have different intuitions about what this "should do" (this also came up on the mailing list discussion). The major advantage of the explicit axis argument is that it allows for clearly documenting what this function does at the call site. Also, it will result in clearer errors if you try to rely on support for array-like arguments but are actually using an old version of NumPy that doesn't support it.

Note: from an implementation perspective, you could do this just by adding np.moveaxis(result, 0, axis) at the end.

@mhvk
Copy link
Contributor Author

mhvk commented Dec 5, 2018

OK, I'll do it - I was trying to avoid the extra work of also writing tests... I guess little choice but to put it at the end of the argument list?

@mhvk
Copy link
Contributor Author

mhvk commented Dec 5, 2018

Now added the axis argument...

Copy link
Contributor

@hameerabbasi hameerabbasi left a comment

Choose a reason for hiding this comment

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

Just a minor doc question.

@mhvk
Copy link
Contributor Author

mhvk commented Dec 6, 2018

@hameerabbasi - good point. I also added a versionchanged stanza so that people know when array-like start and stop were implemented.

@charris charris merged commit 2970e41 into numpy:master Dec 6, 2018
@charris
Copy link
Member

charris commented Dec 6, 2018

Thanks Marten.

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.

5 participants