Proposal of Numpy-like "endpoint" option for linspace

proposal

#1

Numpy has an endpoint::Bool for the inclusion or not of the last point on their implementation of linspace.
In my use case (where I use lots of fft’s) that is very handy. I think that would be a useful feature for Julia’s linspace.


That is rather easy to implement and I could do it myself, but I don’t know the “etiquette” for such things. Should I just make a PR with the necessary modifications? Open an Issue? Discuss it here?

And I guess making a signature like linspace(start,stop,n=50,endpoint::Bool=true) would make it a non-breaking change.


#2

Yes, yes, and yes :slight_smile:

So, the thing is. It seems like you really want a function that does this. So if you do write it and make a PR, you already have what you need. However, the problem with a PR first approach is that you might have missed if this has already been discussed 200 times, so it is a good idea to ask here or open an issue. So… Yes :slight_smile:


#3

I find this useful as well, but on the other end… Like when you want a series but would rather not have the first element cause it’s usually zero.
So something like:

linspace(start, stop, n = 50; startpoint :: Bool = true, endpoint :: Bool = true) 

#4

Not sure if there are any unforseen edgecases that would cause issues, but IMO this would definitely be useful, and has come up in my code.

<bikeshed>perhaps withstart and withend would be more clear names. </bikeshed>


#5

A reasonably straightforward way to express this is linspace(a, b, n)[1:end-1], which also has a natural generalization to dropping multiple elements or dropping elements at the start. I don’t know about Numpy but in Julia this is just indexing a range with a range, which is quite efficient.


#6

It’d need to be linspace(a, b, n+1)[1:end-1], which isn’t a monumental cognitive effort but it seems like linspace(a, b, n; withend=false) is more clear.


#7

I think that maybe there would be some resistance to add keywords to such a commonly used function (it might slow it down a bit), but with this recently merged, my meager understanding is that this shouldn’t negatively effect compile times.


#8

So far, NamedTuples are not related to keyword argument at all.


#9

Oops :flushed:


#10

if withstart=false, then does the start point count for the n?


#11

The idea would be to have n points without the first and/or last points of some series. I don’t care about the precise value of the delta between each member, I just care about having exactly n points and not having the first and/or last members of this series.


#12

Following @pkofod advise, I browsed the Issues related to linspace and it shows there was lots of discussion regarding it’s precision, whether it should return a range with precise step or a range where the start and end points are hit exactly (and therefore having a inexact step size). So this makes the discussion a little less simple than I guessed.

I guess it would make sense to maintain the same approach: hitting exactly the end-point. In our case, with endpoint=false, hitting the “virtual” endpoint exactly. I guess the safest way to do that would be to do linspace(start,stop,n,endpoint=false) = linspace(start,stop,n+1)[1:n]. It is not that strait-forward, but it isn’t that much expensive either.


I agree that having keywords would be cumbersome. Here is my idea: just adding those two methods to linspace, without modifying what has already been implemented:

julia> function Base.linspace(start::Number,stop::Number,n::Integer,inclusive::Tuple{Bool,Bool})
       if inclusive == (true,true)
         return linspace(start,stop,n)
       elseif inclusive == (false,false)
         return linspace(start,stop,n+2)[2:end-1]
       elseif inclusive == (true,false)
         return linspace(start,stop,n+1)[1:end-1]
       elseif inclusive == (false,true)
         return linspace(start,stop,n+1)[2:end]
       end
       end

julia> function Base.linspace(start::Number,stop::Number,inclusive::Tuple{Bool,Bool})
       if inclusive == (true,true)
         return linspace(start,stop)
       elseif inclusive == (false,false)
         return linspace(start,stop,52)[2:51]
       elseif inclusive == (true,false)
         return linspace(start,stop,51)[1:50]
       elseif inclusive == (false,true)
         return linspace(start,stop,51)[2:51]
       end
       end

julia> a = linspace(0,1)
0.0:0.02040816326530612:1.0

julia> a = linspace(0,1,(true,true))
0.0:0.02040816326530612:1.0

julia> a = linspace(0,1,(false,true))
0.02:0.02:1.0

julia> a[1] - step(a)
0.0

julia> a = linspace(0,1,(true,false))
0.0:0.02:0.98

julia> a[end] + step(a)
1.0

julia> a = linspace(0,1,(false,false))
0.0196078431372549:0.0196078431372549:0.9803921568627451

julia> a[1]-step(a)
0.0

julia> a[end]+step(a)
1.0

julia> a = linspace(0,1,20)
0.0:0.05263157894736842:1.0

julia> a = linspace(0,1,20,(true,true))
0.0:0.05263157894736842:1.0

julia> a = linspace(0,1,20,(false,true))
0.05:0.05:1.0

julia> a[1]-step(a)
0.0

julia> a = linspace(0,1,20,(false,false))
0.047619047619047616:0.047619047619047616:0.9523809523809523

julia> a[1]-step(a)
0.0

julia> a[end]+step(a)
1.0

#13

Some years ago I had a similar need in my Matlab programming. FWIW here’s what I did: Instead of overloading linspace, I created a new function binspace(a,b,n,[left|center|right]). The meaning is that the interval [a,b] is divided into n equal sized subintervals or “bins”. What is returned is either the left endpoints, right endpoints, or center points of the bins, depending on the option given.


#14

That’s a nice insight — often you actually want to represent the intervals between the values provided by linspace. You could take your idea one step farther with Julia and return a vector of IntervalSets. Then instead of a left|center|right argument, you simply use minimum.(binspace(a,b,n)), mean.(binspace(…)) or maximum.(binspace(…)), and in some cases you could use the intervals directly.