Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert triangular matrix indexes in to row, column coordinates?

I have these indexes:

1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,etc...

Which are indexes of nodes in a matrix (including diagonal elements):

1
2  3
4  5  6
7  8  9  10
11 12 13 14 15
16 17 18 19 20 21
etc...

and I need to get i,j coordinates from these indexes:

1,1
2,1 2,2
3,1 3,2 3,3
4,1 4,2 4,3 4,4
5,1 5,2 5,3 5,4 5,5
6,1 6,2 6,3 6,4 6,5 6,6
etc...

When I need to calculate coordinates I have only one index and cannot access others.

like image 825
Martin877 Avatar asked Mar 11 '23 12:03

Martin877


2 Answers

Not optimized at all :

int j = idx;
int i = 1;

while(j > i) {
    j -= i++;
}

Optimized :

int i = std::ceil(std::sqrt(2 * idx + 0.25) - 0.5);
int j = idx - (i-1) * i / 2;

And here is the demonstration:

You're looking for i such that :

sumRange(1, i-1) < idx && idx <= sumRange(1, i)

when sumRange(min, max) sum integers between min and max, both inxluded. But since you know that :

sumRange(1, i) = i * (i + 1) / 2

Then you have :

idx <= i * (i+1) / 2
=> 2 * idx <= i * (i+1)
=> 2 * idx <= i² + i + 1/4 - 1/4
=> 2 * idx + 1/4 <= (i + 1/2)²
=> sqrt(2 * idx + 1/4) - 1/2 <= i
like image 159
Victor Drouin Avatar answered Mar 13 '23 01:03

Victor Drouin


In my case (a CUDA kernel implemented in standard C), I use zero-based indexing (and I want to exclude the diagonal) so I needed to make a few adjustments:

// idx is still one-based
unsigned long int idx = blockIdx.x * blockDim.x + threadIdx.x + 1; // CUDA kernel launch parameters
// but the coordinates are now zero-based
unsigned long int x = ceil(sqrt((2.0 * idx) + 0.25) - 0.5);
unsigned long int y = idx - (x - 1) * x / 2 - 1;

Which results in:

[0]: (1, 0)
[1]: (2, 0)
[2]: (2, 1)
[3]: (3, 0)
[4]: (3, 1)
[5]: (3, 2)

I also re-derived the formula of Flórez-Rueda y Moreno 2001 and arrived at:

unsigned long int x = floor(sqrt(2.0 * pos + 0.25) + 0.5);

CUDA Note: I tried everything I could think of to avoid using double-precision math, but the single-precision sqrt function in CUDA is simply not precise enough to convert positions greater than 121 million or so to x, y coordinates (when using 1,024 threads per block and indexing only along 1 block dimension). Some articles have employed a "correction" to bump the result in a particular direction, but this inevitably falls apart at a certain point.

like image 28
vallismortis Avatar answered Mar 13 '23 00:03

vallismortis