Skip to content
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

Make StepRangeLen in operations which may produce zero step #40320

Merged
merged 14 commits into from
May 15, 2021

Conversation

mcabbott
Copy link
Contributor

@mcabbott mcabbott commented Apr 2, 2021

This allows calculations with integer ranges leading to zero step to work:

julia> 0 * (1:3)
StepRangeLen(0, 0, 3)

julia> (1:3) - (1:3)
StepRangeLen(0, 0, 3)

julia> exp.(ans)
3-element Vector{Float64}:
 1.0
 1.0
 1.0

Will this have awful side effects elsewhere?

Note that it does not change how range behaves, nor allow you to enter 1:0:1. Many floating point ranges already use StepRangeLen and hence already allow zero step, it does change their printing to something which parses:

julia> range(1, 5, step=2) |> typeof
StepRange{Int64, Int64}

 julia> 0 * (1:2.0:10)
StepRangeLen(0.0, 0.0, 5)

Closes #40317. Closes #10391.

base/range.jl Outdated Show resolved Hide resolved
@vtjnash vtjnash added the status:merge me PR is reviewed. Merge when all tests are passing label Apr 3, 2021
@simeonschaub
Copy link
Member

Would it be good to run PkgEval here? I could imagine that some people are implicitly relying on these returning a StepRange.

@simeonschaub
Copy link
Member

@nanosoldier runtests(ALL, vs = ":master")

@nanosoldier
Copy link
Collaborator

Something went wrong when running your job:

NanosoldierError: failed to run tests: TaskFailedException:
failed process: Process(`docker stop ExportAll-WED15I1v`, ProcessExited(1)) [1]

pipeline_error at ./process.jl:525 [inlined]
#run#596 at ./process.jl:440
run at ./process.jl:438 [inlined]
kill_container at /storage/pkgeval/dev/PkgEval/src/run.jl:364 [inlined]
stop at /storage/pkgeval/dev/PkgEval/src/run.jl:167 [inlined]
#36 at /storage/pkgeval/dev/PkgEval/src/run.jl:234 [inlined]
lock at ./lock.jl:161
macro expansion at /storage/pkgeval/dev/PkgEval/src/run.jl:231 [inlined]
#35 at ./task.jl:356
wait at ./task.jl:267 [inlined]
fetch at ./task.jl:282 [inlined]
#run_sandboxed_test#30 at /storage/pkgeval/dev/PkgEval/src/run.jl:258
macro expansion at /storage/pkgeval/dev/PkgEval/src/run.jl:584 [inlined]
#50 at ./task.jl:356

Logs and partial data can be found here
cc @maleadt

@maleadt
Copy link
Member

maleadt commented Apr 4, 2021

Docker hiccup. Let's try again:

@nanosoldier runtests(ALL, vs = ":master")

@nanosoldier
Copy link
Collaborator

Your package evaluation job has completed - possible new issues were detected. A full report can be found here. cc @maleadt

@simeonschaub
Copy link
Member

The failures look mostly real.

The AbstractFFT failures are interesting, since it looks like this change introduced some numerical instabilities with operations involving floating points with integer ranges. Could we perhaps promote to TwicePrecision if a StepRange is multiplied by a float?

InfiniteArrays would be good to look into as well, but I am guessing it wouldn't be too hard to fix the breakage on their side.

The others mostly look like their tests are just a little to strict and explicitly test the type of a range, so I don't think we need to worry too much about those.

@simeonschaub simeonschaub removed the status:merge me PR is reviewed. Merge when all tests are passing label Apr 4, 2021
@mcabbott
Copy link
Contributor Author

I think I fixed the AbstractFFTs test, which was producing this:

julia> using AbstractFFTs

julia> x = fftfreq(5);

julia> fftshift(fftfreq(5)) |> collect
5-element Vector{Float64}:
 -0.4
 -0.2
  0.0
  0.20000000000000007
  0.4

julia> (x.n_nonnegative-x.n:x.n_nonnegative-1)
-2:2

julia> (x.n_nonnegative-x.n:x.n_nonnegative-1)*x.multiplier
-0.4:0.2:0.4

julia> typeof(ans)
StepRangeLen{Float64, Float64, Float64}

# My fix
julia> @eval Base.Broadcast broadcasted(::DefaultArrayStyle{1}, ::typeof(*), r::AbstractRange, x::AbstractFloat) = Base.range_start_step_length(first(r)*x, step(r)*x, length(r))

julia> (x.n_nonnegative-x.n:x.n_nonnegative-1)*x.multiplier |> typeof
StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}}

But now there are other problems... hope nobody is in a rush.

@simeonschaub
Copy link
Member

Any news on this? Not in a rush, but it would be nice to get this into 1.7, since it could be argued that this is really a bugfix. Let me know if there's anything I can help with.

Comment on lines 1161 to 1164
broadcasted(::DefaultArrayStyle{1}, ::typeof(*), r::AbstractRange, x::Number) = StepRangeLen(first(r)*x, step(r)*x, length(r))
broadcasted(::DefaultArrayStyle{1}, ::typeof(*), r::StepRangeLen{T}, x::Number) where {T} = StepRangeLen{typeof(T(r.ref)*x)}(r.ref*x, r.step*x, length(r), r.offset)
broadcasted(::DefaultArrayStyle{1}, ::typeof(*), r::LinRange, x::Number) = LinRange(r.start * x, r.stop * x, r.len)
broadcasted(::DefaultArrayStyle{1}, ::typeof(*), r::OrdinalRange{<:Integer}, x::AbstractFloat) = Base.range_start_step_length(first(r)*x, x, length(r))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The last method here handles the case needed for AbstractFFTs tests, above, of float * UnitRange -> range with twiceprecision.

And (-2:2) * 0.0 works because for floats range_start_step_length does sometimes allow zero step:

julia> Base.range_start_step_length(1.0, 0, 5)
StepRangeLen(1.0, 0.0, 5)

julia> Base.range_start_step_length(1, 0, 5)  # goes to 1st method not 4th
ERROR: ArgumentError: step cannot be zero

But probably it could just be OrdinalRange. Are there other weird cases we should think about?

Copy link
Member

Choose a reason for hiding this comment

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

I wonder whether Base.range_start_step_length(1, 0, 5) should just return a StepRangeLen. Do you expect that to be too breaking?

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 haven't thought about it. Was hoping to change as little as possible, focused on calculations that may produce zero step, rather than constructors.

This one is called by range(1, step=0, length=10) which would otherwise make an integer StepRange. Whereas range(1, 1, length=10) works, and makes a floating point range. It is a bit weird that some constructors work and some don't, but this isn't changed here.

Copy link
Member

Choose a reason for hiding this comment

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

Agree, it's probably better not to change this behavior in this PR. Could you just add a short comment here explaining why we can't use range_start_step_length?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, see what you think of 56d9c27

Copy link
Member

Choose a reason for hiding this comment

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

Looks good! Let's run PkgEval one more time once tests pass, but otherwise this should be good to go.

@simeonschaub simeonschaub added the needs pkgeval Tests for all registered packages should be run with this change label Apr 30, 2021
base/range.jl Outdated Show resolved Hide resolved
@simeonschaub
Copy link
Member

@nanosoldier runtests(["AbstractFFTs", "ArrayLayouts", "BlockArrays", "CUTEst", "Cambrian", "CustomUnitRanges", "DimensionalData", "Hecke", "IdentityRanges", "ImageDistances", "InfiniteArrays", "InverseLaplace", "LoopThrottle", "ProximalAlgorithms"], vs = ":master")

@nanosoldier
Copy link
Collaborator

Your package evaluation job has completed - possible new issues were detected. A full report can be found here. cc @maleadt

@mcabbott
Copy link
Contributor Author

Besides === tests:

IdentityRanges:

Test Failed at /home/pkgeval/.julia/packages/IdentityRanges/liKDH/test/runtests.jl:3
  Expression: isempty(detect_ambiguities(IdentityRanges, Base, Core))
   Evaluated: isempty(Tuple{Method, Method}[(broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(*), x::Number, r::IdentityRange) in IdentityRanges at /home/pkgeval/.julia/packages/IdentityRanges/liKDH/src/IdentityRanges.jl:139, broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(*), x::AbstractFloat, r::OrdinalRange) in Base.Broadcast at broadcast.jl:1161), (broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(*), r::IdentityRange, x::Number) in IdentityRanges at /home/pkgeval/.julia/packages/IdentityRanges/liKDH/src/IdentityRanges.jl:138, broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(*), r::OrdinalRange, x::AbstractFloat) in Base.Broadcast at broadcast.jl:1168)])

That ambiguity might be avoided if you modified range_start_step_length instead.

BlockArrays:

Special broadcast: Test Failed at /home/pkgeval/.julia/packages/BlockArrays/aLe8n/test/test_blockbroadcast.jl:114
  Expression: broadcast(*, 2, v) isa BlockVector{Int, Vector{StepRange{Int, Int}}}
   Evaluated: [2, 4, 6, 8, 10, 12, 14] isa BlockVector{Int64, Vector{StepRange{Int64, Int64}}}

Maybe that's roughly like ===, something isn't giving a StepRange anymore.

@simeonschaub
Copy link
Member

IdentityRanges:

Test Failed at /home/pkgeval/.julia/packages/IdentityRanges/liKDH/test/runtests.jl:3
  Expression: isempty(detect_ambiguities(IdentityRanges, Base, Core))
   Evaluated: isempty(Tuple{Method, Method}[(broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(*), x::Number, r::IdentityRange) in IdentityRanges at /home/pkgeval/.julia/packages/IdentityRanges/liKDH/src/IdentityRanges.jl:139, broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(*), x::AbstractFloat, r::OrdinalRange) in Base.Broadcast at broadcast.jl:1161), (broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(*), r::IdentityRange, x::Number) in IdentityRanges at /home/pkgeval/.julia/packages/IdentityRanges/liKDH/src/IdentityRanges.jl:138, broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(*), r::OrdinalRange, x::AbstractFloat) in Base.Broadcast at broadcast.jl:1168)])

That ambiguity might be avoided if you modified range_start_step_length instead.

That should be easily fixable on IdentityRanges' side, so I am not too worried about that.

BlockArrays:

Special broadcast: Test Failed at /home/pkgeval/.julia/packages/BlockArrays/aLe8n/test/test_blockbroadcast.jl:114
  Expression: broadcast(*, 2, v) isa BlockVector{Int, Vector{StepRange{Int, Int}}}
   Evaluated: [2, 4, 6, 8, 10, 12, 14] isa BlockVector{Int64, Vector{StepRange{Int64, Int64}}}

Maybe that's roughly like ===, something isn't giving a StepRange anymore.

Yeah, it's just explicitly testing the type, so that should be fine as well.

@simeonschaub simeonschaub added domain:ranges Everything AbstractRange and removed needs pkgeval Tests for all registered packages should be run with this change labels Apr 30, 2021
@mcabbott
Copy link
Contributor Author

Pre-1.7 bump?

@simeonschaub
Copy link
Member

Just realized that it might also be good to have a news entry for this, would you mind adding one? Otherwise will merge later if there are no objections.

@mcabbott
Copy link
Contributor Author

Sure. I added something, perhaps too verbose?

@simeonschaub simeonschaub added the status:merge me PR is reviewed. Merge when all tests are passing label May 14, 2021
@simeonschaub
Copy link
Member

No, looks good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:ranges Everything AbstractRange
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Broadcasting and zero-step ranges Subtracting a range from another range with the same step
5 participants