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.

2 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?).

2 Likes