Main Question:-
If the elements in the band formed by the three diagonals of a tridiagonal matrix X are represented column-wise in an array Y, with X[1,1] being stored in Y[1], then write an algorithm to determine the value of X[i, j], 1 <= i, j <= n from array Y.
I did some calculation in rough and found the size of 1D array required and index to row and column index for main diagonal and upper diagonal but failing to do so for lower diagonal
CONSIDERING A MATRIX WITH 5 ROWS AND 5 COLUMNS
|A11 A12 0 0 0 |
|A21 A22 A23 0 0 |
| 0 A32 A33 A34 0 |
| 0 0 A43 A44 A45|
| 0 0 0 A54 A55|
Size of the array (3*n - 2),
here size of the array = (3*5 - 2) = 13
0 1 2 3 4 5 6 7 8 9 10 11 12
A11 A21 A12 A22 A32 A23 A33 A43 A34 A44 A54 A45 A55
i=row index, 1<=i<=N
j=column index, 1<=j<=N
k=array index
Now for main diagonal
i j k
1 1 0
2 2 3
3 3 6
4 4 9
5 5 12
I got the relation as 3i - 3
For Upper diagonal
i j k
1 2 2
2 3 5
3 4 8
4 5 11
I got the relation as 4i - j
Now for lower diagonal(question)
i j k
2 1 1
3 2 4
4 3 7
5 4 10
Relation ??
Summary
You'll find that (indexing from 1), for an NxN tridiagonal matrix X
linearized in column-major order into an array Z, the entry in row r:
On the main diagonal of X is at index (3 * c - 2) of Y for c = 1..N;
On the upper-diagonal of X is at (3 * c - 3) of Y (for c = 1..N-1);
On the lower-diagonal of X is at (3 * c - 1) of Y (for c = 2..N).
For an element with row r, column c (both in the range 1..N):
if |r - c| > 1 then the value is zero;
if c == r then it is on the leading diagonal;
if c = r + 1 then it is on the upper diagonal;
if c = r - 1 then it is on the lower diagonal.
Of course, in C, arrays are indexed from zero, not one.
Cogitations
NxN tri-diagonal matrix X
Linearized in row-major order into array Y with 3*N-2 entries
Linearized in column-major order into array Z with 3*N-2 entries
Non-zero elements have c = r + { -1, 0, +1 }
Equivalently they have r = c + { -1, 0, +1 }
With one-based indexing, the first row with an element on the lower
diagonal is r = 2 and the last row with an element on the upper
diagonal is r = N - 1.
With zero-based indexing, the first row with an element on the lower
diagonal is r = 1 and the last rows with an element on the upper
diagonal is r = N - 2.
In the linearized vectors, the notation is:
D = diagonal, L = lower diagonal, U = upper diagonal
Row-Major Order with Zero-Based Indexing
X c = 0 1 2 3 4 5
+----------------------
r = 0 | 1 2 0 0 0 0
r = 1 | 3 4 5 0 0 0
r = 2 | 0 6 7 8 0 0
r = 3 | 0 0 9 10 11 0
r = 4 | 0 0 0 12 13 14
r = 5 | 0 0 0 0 15 16
D U L D U L D U L D U L D U L D
Y = { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 }
Index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Elements on main diagonal at indexes: 0, 3, 6, 9, 12, 15
Elements on upper diagonal at indexes: 1, 4, 7, 10, 13
Elements on lower diagonal at indexes: 2, 5, 8, 11, 14
Function get_rm_0():
X[r,c] = 0 if |r - c| > 1
X[r,c] = 3 * r + 0 if r == c + 0 — Main diagonal
X[r,c] = 3 * r + 1 if r == c - 1 — Upper diagonal
X[r,c] = 3 * r - 1 if r == c + 1 — Lower diagonal
Always subject to 0 <= r < N; 0 <= c < N
Row-Major Order with One-Based Indexing
X c = 1 2 3 4 5 6
+----------------------
r = 1 | 1 2 0 0 0 0
r = 2 | 3 4 5 0 0 0
r = 3 | 0 6 7 8 0 0
r = 4 | 0 0 9 10 11 0
r = 5 | 0 0 0 12 13 14
r = 6 | 0 0 0 0 15 16
D U L D U L D U L D U L D U L D
Y = { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 }
Index 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Elements on main diagonal at indexes: 1, 4, 7, 10, 13, 16
Elements on upper diagonal at indexes: 2, 5, 8, 11, 14
Elements on lower diagonal at indexes: 3, 6, 9, 12, 15
Function get_rm_1():
X[r,c] = 0 if |r - c| > 1
X[r,c] = 3 * r - 2 if r == c + 0 — Main diagonal
X[r,c] = 3 * r - 1 if r == c - 1 — Upper diagonal
X[r,c] = 3 * r - 3 if r == c + 1 — Lower diagonal
Always subject to 0 < r <= N; 0 <= c <= N
Column-major instead of row-major order
You are correct. Here you are considering row-major order. I
wanted to store the elements in the array as:
Z = { 1, 3, 2, 4, 6, 5, 7, 9, 8, 10, 12, 11, 13, 15, 14, 16 }
as per your example
Column-Major Order with One-Based Indexing
So, you can make a parallel argument to the one I gave. Annotate
your column-major array Z similarly to the way I annotated the
row-major array Y:
D L U D L U D L U D L U D L U D
Z = { 1, 3, 2, 4, 6, 5, 7, 9, 8, 10, 12, 11, 13, 15, 14, 16 }
Index 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Elements on main diagonal at indexes: 1, 4, 7, 10, 13, 16
Elements on upper diagonal at indexes: 3, 6, 9, 12, 15
Elements on lower diagonal at indexes: 2, 5, 8, 11, 14
Function get_cm_1():
X[r,c] = 0 if |c - r| > 1
X[r,c] = 3 * c - 2 if c == r + 0 — Main diagonal
X[r,c] = 3 * c - 3 if c == r + 1 — Upper diagonal
X[r,c] = 3 * c - 1 if c == r - 1 — Lower diagonal
Always subject to 0 < r <= N; 0 < c <= N.
Column-Major Order with Zero-Based Indexing
D L U D L U D L U D L U D L U D
Z = { 1, 3, 2, 4, 6, 5, 7, 9, 8, 10, 12, 11, 13, 15, 14, 16 }
Index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Elements on main diagonal at indexes: 0, 3, 6, 9, 12, 15
Elements on upper diagonal at indexes: 2, 5, 8, 11, 14
Elements on lower diagonal at indexes: 1, 4, 7, 10, 13
Function get_cm_0():
X[r,c] = 0 if |c - r| > 1
X[r,c] = 3 * c + 0 if c == r + 0 — Main diagonal
X[r,c] = 3 * c - 1 if c == r + 1 — Upper diagonal
X[r,c] = 3 * c + 1 if c == r - 1 — Lower diagonal
Always subject to 0 <= r < N; 0 <= c < N.
Code
Source file td59.c:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
enum { N = 6 };
static const int X[N][N] =
{
{ 1, 2, 0, 0, 0, 0 },
{ 3, 4, 5, 0, 0, 0 },
{ 0, 6, 7, 8, 0, 0 },
{ 0, 0, 9, 10, 11, 0 },
{ 0, 0, 0, 12, 13, 14 },
{ 0, 0, 0, 0, 15, 16 },
};
static const int Y[3*N-2] =
{
1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16,
};
static const int Z[3*N-2] =
{
1, 3, 2, 4, 6, 5, 7, 9,
8, 10, 12, 11, 13, 15, 14, 16,
};
/* rm = row major, cm = column major, 0 = zero-based, 1 = one-based */
static int get_rm_0(int r, int c)
{
assert(r >= 0 && r < N);
assert(c >= 0 && c < N);
//printf("%s(): r = %d, c = %d\n", __func__, r, c);
if (abs(r - c) > 1) /* Off tridiagonal */
return 0;
int index;
if (r == c) /* Main diagonal */
index = 3 * r + 0;
else if (r == c - 1) /* Upper diagonal */
index = 3 * r + 1;
else
{
assert(r == c + 1);
index = 3 * r - 1; /* Lower diagonal */
}
//printf("%s(): r = %d, c = %d, i = %d, v = %d\n", __func__, r, c, index, Y[index]);
return Y[index];
}
static int get_rm_1(int r, int c)
{
assert(r > 0 && r <= N);
assert(c > 0 && c <= N);
//printf("%s(): r = %d, c = %d\n", __func__, r, c);
if (abs(r - c) > 1) /* Off tridiagonal */
return 0;
int index;
if (r == c) /* Main diagonal */
index = 3 * r - 2;
else if (r == c - 1) /* Upper diagonal */
index = 3 * r - 1;
else
{
assert(r == c + 1);
index = 3 * r - 3; /* Lower diagonal */
}
//printf("%s(): r = %d, c = %d, i = %d, v = %d\n", __func__, r, c, index, Y[index-1]);
return Y[index-1];
}
static int get_cm_0(int r, int c)
{
assert(r >= 0 && r < N);
assert(c >= 0 && c < N);
//printf("%s(): r = %d, c = %d\n", __func__, r, c);
if (abs(r - c) > 1) /* Off tridiagonal */
return 0;
int index;
if (c == r) /* Main diagonal */
index = 3 * c + 0;
else if (c == r + 1) /* Upper diagonal */
index = 3 * c - 1;
else
{
assert(c == r - 1);
index = 3 * c + 1; /* Lower diagonal */
}
//printf("%s(): r = %d, c = %d, i = %d, v = %d\n", __func__, r, c, index, Z[index]);
return Z[index];
}
static int get_cm_1(int r, int c)
{
assert(r > 0 && r <= N);
assert(c > 0 && c <= N);
//printf("%s(): r = %d, c = %d\n", __func__, r, c);
if (abs(r - c) > 1) /* Off tridiagonal */
return 0;
int index;
if (c == r) /* Main diagonal */
index = 3 * c - 2;
else if (c == r + 1) /* Upper diagonal */
index = 3 * c - 3;
else
{
assert(c == r - 1);
index = 3 * c - 1; /* Lower diagonal */
}
//printf("%s(): r = %d, c = %d, i = %d, v = %d\n", __func__, r, c, index, Z[index-1]);
return Z[index-1];
}
static void dump_matrix(const char *tag, int rows, int cols,
const int matrix[rows][cols], int base)
{
printf("%s (%dx%d) %d-based:\n", tag, rows, cols, base);
printf(" c =");
for (int c = 0; c < cols; c++)
printf(" %2d", c + base);
putchar('\n');
for (int r = 0; r < rows; r++)
{
printf("r = %d: {", r + base);
for (int c = 0; c < cols; c++)
printf(" %2d", matrix[r][c]);
puts(" }");
}
putchar('\n');
}
static void dump_vector(const char *tag, int values, const int vector[values], int base)
{
printf("Vector %s (%d) %d-based:\n", tag, values, base);
int len = 0;
const char *pad = "";
for (int i = 0; i < values; i++)
{
len += printf("%s[%2d] = %2d,", pad, i + base, vector[i]);
pad = " ";
if (len > 80)
{
putchar('\n');
len = 0;
pad = "";
}
}
if (len > 0)
putchar('\n');
putchar('\n');
}
static void reconstruct_matrix(const char *matrix_tag, const char *vector_tag,
int values, const int vector[values],
int rows, int cols, int (*getter)(int r, int c), int base)
{
dump_vector(vector_tag, values, vector, base);
printf("%s (%dx%d) %d-based:\n", matrix_tag, rows, cols, base);
for (int r = 0 + base; r < rows + base; r++)
{
printf("r = %d: {", r);
for (int c = 0 + base; c < cols + base; c++)
printf(" %2d", (*getter)(r, c));
puts(" }");
}
putchar('\n');
}
int main(void)
{
dump_matrix("Tridiagonal matrix X", N, N, X, 0);
reconstruct_matrix("Reconstructed Row-Major Matrix", "Y", 3 * N - 2, Y, N, N, get_rm_0, 0);
dump_matrix("Tridiagonal matrix X", N, N, X, 1);
reconstruct_matrix("Reconstructed Row-Major Matrix", "Y", 3 * N - 2, Y, N, N, get_rm_1, 1);
puts("\n\n");
dump_matrix("Tridiagonal matrix X", N, N, X, 0);
reconstruct_matrix("Reconstructed Column-Major Matrix", "Z", 3 * N - 2, Z, N, N, get_cm_0, 0);
dump_matrix("Tridiagonal matrix X", N, N, X, 1);
reconstruct_matrix("Reconstructed Column-Major Matrix", "Z", 3 * N - 2, Z, N, N, get_cm_1, 1);
return 0;
}
There are many possible changes that could be made to this code.
Use functions get_[cr]m_[01]_idx() to get the array index in the vector. They would return -1 when the cell is off the tri-diagonal.
Use a modified vector (Y, Z) which has an extra element with value 0 at the start. Use const int *Y1 = &Y[1]; and then you can use: int val = Y1[get_rm_1_idx(r, c)]; to get the value.
Revise the get_[cr]m_[01]() functions to take the vector instead of using global variables Y and Z.
Rename the functions get_[cr]m_[01]() functions using get_[cr]m_[01]_val() to indicate that they get the value.
Add printing to report the index into the vectors Y and Z at which the value is found.
Output
Tridiagonal matrix X (6x6) 0-based:
c = 0 1 2 3 4 5
r = 0: { 1 2 0 0 0 0 }
r = 1: { 3 4 5 0 0 0 }
r = 2: { 0 6 7 8 0 0 }
r = 3: { 0 0 9 10 11 0 }
r = 4: { 0 0 0 12 13 14 }
r = 5: { 0 0 0 0 15 16 }
Vector Y (16) 0-based:
[ 0] = 1, [ 1] = 2, [ 2] = 3, [ 3] = 4, [ 4] = 5, [ 5] = 6, [ 6] = 7, [ 7] = 8,
[ 8] = 9, [ 9] = 10, [10] = 11, [11] = 12, [12] = 13, [13] = 14, [14] = 15, [15] = 16,
Reconstructed Row-Major Matrix (6x6) 0-based:
r = 0: { 1 2 0 0 0 0 }
r = 1: { 3 4 5 0 0 0 }
r = 2: { 0 6 7 8 0 0 }
r = 3: { 0 0 9 10 11 0 }
r = 4: { 0 0 0 12 13 14 }
r = 5: { 0 0 0 0 15 16 }
Tridiagonal matrix X (6x6) 1-based:
c = 1 2 3 4 5 6
r = 1: { 1 2 0 0 0 0 }
r = 2: { 3 4 5 0 0 0 }
r = 3: { 0 6 7 8 0 0 }
r = 4: { 0 0 9 10 11 0 }
r = 5: { 0 0 0 12 13 14 }
r = 6: { 0 0 0 0 15 16 }
Vector Y (16) 1-based:
[ 1] = 1, [ 2] = 2, [ 3] = 3, [ 4] = 4, [ 5] = 5, [ 6] = 6, [ 7] = 7, [ 8] = 8,
[ 9] = 9, [10] = 10, [11] = 11, [12] = 12, [13] = 13, [14] = 14, [15] = 15, [16] = 16,
Reconstructed Row-Major Matrix (6x6) 1-based:
r = 1: { 1 2 0 0 0 0 }
r = 2: { 3 4 5 0 0 0 }
r = 3: { 0 6 7 8 0 0 }
r = 4: { 0 0 9 10 11 0 }
r = 5: { 0 0 0 12 13 14 }
r = 6: { 0 0 0 0 15 16 }
Tridiagonal matrix X (6x6) 0-based:
c = 0 1 2 3 4 5
r = 0: { 1 2 0 0 0 0 }
r = 1: { 3 4 5 0 0 0 }
r = 2: { 0 6 7 8 0 0 }
r = 3: { 0 0 9 10 11 0 }
r = 4: { 0 0 0 12 13 14 }
r = 5: { 0 0 0 0 15 16 }
Vector Z (16) 0-based:
[ 0] = 1, [ 1] = 3, [ 2] = 2, [ 3] = 4, [ 4] = 6, [ 5] = 5, [ 6] = 7, [ 7] = 9,
[ 8] = 8, [ 9] = 10, [10] = 12, [11] = 11, [12] = 13, [13] = 15, [14] = 14, [15] = 16,
Reconstructed Column-Major Matrix (6x6) 0-based:
r = 0: { 1 2 0 0 0 0 }
r = 1: { 3 4 5 0 0 0 }
r = 2: { 0 6 7 8 0 0 }
r = 3: { 0 0 9 10 11 0 }
r = 4: { 0 0 0 12 13 14 }
r = 5: { 0 0 0 0 15 16 }
Tridiagonal matrix X (6x6) 1-based:
c = 1 2 3 4 5 6
r = 1: { 1 2 0 0 0 0 }
r = 2: { 3 4 5 0 0 0 }
r = 3: { 0 6 7 8 0 0 }
r = 4: { 0 0 9 10 11 0 }
r = 5: { 0 0 0 12 13 14 }
r = 6: { 0 0 0 0 15 16 }
Vector Z (16) 1-based:
[ 1] = 1, [ 2] = 3, [ 3] = 2, [ 4] = 4, [ 5] = 6, [ 6] = 5, [ 7] = 7, [ 8] = 9,
[ 9] = 8, [10] = 10, [11] = 12, [12] = 11, [13] = 13, [14] = 15, [15] = 14, [16] = 16,
Reconstructed Column-Major Matrix (6x6) 1-based:
r = 1: { 1 2 0 0 0 0 }
r = 2: { 3 4 5 0 0 0 }
r = 3: { 0 6 7 8 0 0 }
r = 4: { 0 0 9 10 11 0 }
r = 5: { 0 0 0 12 13 14 }
r = 6: { 0 0 0 0 15 16 }
Assuming d is the main diagonal, a is the "above" diagonal, and b is the "below" diagonal:
+---+---+---+---+---+---+---+---+
| d | a | 0 | 0 | 0 | 0 | 0 | 0 |
+---+---+---+---+---+---+---+---+
| b | d | a | 0 | 0 | 0 | 0 | 0 |
+---+---+---+---+---+---+---+---+
| 0 | b | d | a | 0 | 0 | 0 | 0 |
+---+---+---+---+---+---+---+---+
| 0 | 0 | b | d | a | 0 | 0 | 0 |
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | b | d | a | 0 | 0 |
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | b | d | a | 0 |
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | 0 | b | d | a |
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | 0 | 0 | b | d |
+---+---+---+---+---+---+---+---+
Note that the problem assumes indexes are "origin 1" (e.g. 1 <= i <= n) but C arrays want "origin 0" (e.g. 0 <= i < n)
We can use an "inner" struct to represent the Y array (e.g. elem_t) and the basic functions are:
#include <stdio.h>
#include <stdlib.h>
// sparse matrix Y vector
typedef struct {
int a; // above diagonal
int d; // main diagonal
int b; // below diagonal
} elem_t;
// sparse matrix
typedef struct {
int n;
elem_t *base;
} sparse_t;
// sparse_create -- create sparse tridiagonal matrix
sparse_t *
sparse_create(int n)
{
sparse_t *mtx = malloc(sizeof(*mtx));
mtx->n = n;
mtx->base = calloc(n,sizeof(*mtx->base));
return mtx;
}
// sparse_pointer -- point to sparse matrix element
int *
sparse_pointer(sparse_t *mtx,int coli,int rowi)
{
int n = mtx->n;
elem_t *eptr;
int *iptr = NULL;
// convert to origin 0
coli -= 1;
rowi -= 1;
do {
// check column bounds
if (coli < 0)
break;
if (coli >= n)
break;
// check row bounds
if (rowi < 0)
break;
if (rowi >= n)
break;
eptr = &mtx->base[coli];
// main diagonal
if (rowi == coli) {
iptr = &eptr->d;
break;
}
// above diagnonal
if ((rowi == (coli - 1)) && (coli > 0)) {
iptr = &eptr->a;
break;
}
// below diagnonal
if ((rowi == (coli + 1)) && (coli < (n - 1))) {
iptr = &eptr->b;
break;
}
} while (0);
return iptr;
}
// sparse_getval -- get matrix value
int
sparse_getval(sparse_t *mtx,int coli,int rowi)
{
int *iptr = sparse_pointer(mtx,coli,rowi);
int val;
if (iptr != NULL)
val = *iptr;
else
val = 0;
return val;
}
// sparse_setval -- set matrix value
void
sparse_setval(sparse_t *mtx,int coli,int rowi,int val)
{
int *iptr = sparse_pointer(mtx,coli,rowi);
if (iptr != NULL)
*iptr = val;
}
By contrast, a regular/full matrix would be:
#include <stdio.h>
#include <stdlib.h>
// regular/full matrix
typedef struct {
int n;
int *base;
} regmtx_t;
// regmtx_create -- create regular matrix
regmtx_t *
regmtx_create(int n)
{
regmtx_t *mtx = malloc(sizeof(*mtx));
mtx->n = n;
mtx->base = calloc(n * n,sizeof(*mtx->base));
return mtx;
}
// regmtx_pointer -- point to regular matrix element
int *
regmtx_pointer(regmtx_t *mtx,int coli,int rowi)
{
int *iptr = NULL;
// convert to origin 0
coli -= 1;
rowi -= 1;
do {
// check column bounds
if (coli < 0)
break;
if (coli >= mtx->n)
break;
// check row bounds
if (rowi < 0)
break;
if (rowi >= mtx->n)
break;
iptr = &mtx->base[(coli * mtx->n) + rowi];
} while (0);
return iptr;
}