Is there a log scale range function?

Is there a function like range() but the steps are multiplicative?
Something like

logrange(start,stepmul,length) =  cumprod([start,(ones(length)*stepmul)...])
1 Like

You can use broadcasting to apply a log transform to a range:

julia> logrange(start,stepmul,length) = start .* stepmul .^ (0:(length-1))
logrange (generic function with 1 method)

julia> logrange(3, 4, 5)
5-element Vector{Int64}:
   3
  12
  48
 192
 768
1 Like

Thereā€™s a PR to add it to base Add a lazy `LogRange` function by mcabbott Ā· Pull Request #39071 Ā· JuliaLang/julia Ā· GitHub. It tuns out that getting it perfect is surprisingly difficult.

3 Likes

The tricky thing about a function which takes the ratio (stepmul above) is that it will tend to create an expectation that this be interpreted exactly.

Linear ranges like 0:0.1:1 do this, by some serious black magic. It knows you mean 1//10 even though that isnā€™t precisely represented by any Float64. Doing cumsum(fill(0.1, 10)) will miss the ā€œobviousā€ endpoint of 1.0.

Similar things go wrong for ratio 0.1:

julia> logrange1(100, 0.1, 5)  # from 1st message, with stepmul
6-element Vector{Float64}:
 100.0
  10.0
   1.0
   0.1
   0.010000000000000002
   0.0010000000000000002

julia> logrange2(100, 0.1, 5)  # from 2nd message above
5-element Vector{Float64}:
 100.0
  10.0
   1.0000000000000002
   0.10000000000000002
   0.010000000000000002

julia> logrange(100, ratio=0.1, length=5)  # lazy PowerRange, somewhere in PR
5-element PowerRange{Float64}:
 100.0
  10.0
   1.0000000000000002
   0.10000000000000002
   0.010000000000000002

The linked PR avoids this by taking the first & last values. Then thereā€™s no chance that it doesnā€™t stop where you want, it can simply guarantee to hit 0.01 exactly, without enquiring further. (It tries hard to be accurate in between, but isnā€™t perfect.)

julia> Base.logrange(100, 0.01, length=5)
5-element LogRange{Float64, Base.TwicePrecision{Float64}}:
 100.0, 10.0, 1.0, 0.1, 0.01

julia> collect(ans)
5-element Vector{Float64}:
 100.0
  10.0
   1.0
   0.1
   0.01

And apart from such details, in real use I think itā€™s common to do that, and then decide you want more / less points (on your plot), or a wider / narrower range (to exclude the bad ones). With the ratio specified, you have to experiment a bit. Specifying the ends and length seems to me more often useful.

5 Likes

FlexiMaps.jl provides logrange already, lazy/general/performant:

julia> using FlexiMaps

julia> maprange(log, 0.1, 100, length=4)  # ā‰ˆ [0.1, 1, 10, 100]

Specifying the step ratio directly would also be useful sometimes, but not supported (yet?).

3 Likes