Combine multiple ranges with different resolution

I’m computing a function that is smooth in some domain and has quite a few fine features somewhere else, so I would like my x variable to have a different resolution in different regions. I’m using range() for each subdomain, and while doing my numerical explorations and adjusting those ranges I want the computer to automatically remove duplicates and sort the different ranges without worrying about it myself.

I’ve come up with this, but it feels very clumsy,

sort(unique(hcat([collect(range(0.3,1,length=100)),
collect(range(1,3,length=500)),
collect(range(1,10,length=100)),
collect(range(10,100,length=100))]...)))

Am I missing a more straightforward approach? (in Base Julia – I’m asking this to learn better practice, rather than for the end-result).

To do explicitly what you want, no. Ranges are basically stored as (start,stop,steplength), so they can’t really be “mixed”.

To address your actual problem, you should consider something like log-spacing. Create a linearly spaced grid, then log transform it. You will get more points closer to zero, and the points at the other end of the grid space out more (because now the growth rate between two points is equal, not the distance between them). You can also apply this transformation as many times as you want.

1 Like

log isn’t really helping because there are various regions of interest requiring high def (sure, I could invent an ad hoc transform). I guess what bothers me is the need to collect() each range explicitly just to be able to call unique and sort on the resulting array. Is there an alternative to range() that already expands to an explicit array?

Is this what you want? https://github.com/SciML/MultiScaleArrays.jl

Speed up your original code? That we can do.

You can drop all of the collects. Try this instead.

sort(unique(Iterators.flatten((range(0.3,1,length=100),
    range(1,3,length=500),
    range(1,10,length=100),
    range(10,100,length=100)))))

Many things in Julia just need arguments that are iterable. They don’t need actual arrays.

2 Likes

If you are willing to invest a bit more into this, you could make a partition of n adjacent ranges by storing n+1 endpoints and n lengths. This should be easy to code in a custom type, and you can reuse a lot of optimized code for ranges (eg searchsortedfirst for locating bins).

There are a few things wrong with this code.

Firstly, I guess you mean vcat not hcat, since hcat will try to do horizontal concatenation and create a matrix. It will actually error here.

Secondly, you put each range in a vector and then splat that vector, like this:

vcat([a, b, c]...)

This is identical to writing

vcat(a, b, c)

except that it creates a temporary array for no reason that you just throw away again, and which reduces performance and increases allocations.

Thirdly, you are using collect, which you should basically never do. collect is a function you should rarely need to use, and certainly not here, it just takes a fast, lightweight, cheap, smart range-vector, and turns it into a heavy, expensive and dumb vector.

So re-writing your code:

sort(unique(vcat(range(0.3,1,length=100),
                 range(1,3,length=500),
                 range(1,10,length=100),
                 range(10,100,length=100))))

There are still probably better ways of doing what you want.

Rule of thumb: Don’t use collect. Only use it if your code fails otherwise.

Edit: You can also write [a; b; c] instead of vcat(a, b, c) if you prefer. They are the same thing.

2 Likes

Thanks – indeed, collect() struck me as something to be avoided, but since I was wrongly using hcat* instead of vcat I mistakenly assumed I needed to explicitly expand the ranges before combining them.

It often seems obvious in hindsight, but not when typing new things in a new language.

*: (Though by some unfortunate coincidence my code with hcat did work – all the ranges had the same number of elements in my original example)

True, I can imagine a few ways to abstract this problem out, but here I really wanted to clarify my understanding of those low-level basic functions. Thanks, though.

In the spirit of premature optimization, I tried to eliminate allocations for you by defining an iterator that is equivalent to range when collected. Surprisingly, it doesn’t appear to be faster when you substitute it into your problem, but there could be some benchmarking trickery going on.

linspace(a,b,N)= (a+ (b-a)*(i)/(N-1) for i=0:(N-1))

You could play around with it though.

1 Like