I'm currently working on a implementing a matrix inversion method for a Matrix class in C++. One thing that isn't implemented is a check for when the pivot element is 0, meaning I will need to swap the rows (likely with the row just beneath it).

Here is what I have for far for the Inversion method:

Matrix Matrix:: invert()
{
	if (rows != cols) {         // Check for square matrix
		cout << "Matrix must be square." << endl;
		exit(1);
	}

	double ratio, sum;
	int i, j, k;
	Matrix T1(rows, cols*2);		// New temp identity matrix of same order
	Matrix F(rows, cols);				// Final Inverted Matrix
    
	// Add Identity Matrix
	for(i = 1; i <= rows; i++){
		for(j = rows; j <= 2*rows; j++){
			if(i == (j-cols)){
				T1.el[i][j] = 1.0;
			}
			else
				T1.el[i][j] = 0.0;
		}
	}

    // Add original Matrix to 1st half of Temp Matrix
	for (i = 1; i <=rows; i++) {       
		for(j=1;j<=rows; j++) {
			T1.el[i][j] = el[i][j];
		}
	}

	cout << "\n\nOriginal matrix with added Identity Matrix" << endl;
	for (i = 1; i <=rows; i++) {
		cout << setw(5);
		for(j=1;j<=rows*2; j++) {
			cout << T1.el[i][j] << setw(6);
		}
		cout << endl;
	}

	double temp;
	// Row Operations
	for(i = 1; i <= rows; i++){
		for(j = 1; j <= rows; j++){
			if(i != j) {
				ratio = T1.el[j][i]/T1.el[i][i];
				for(k = 1; k <= 2*rows; k++) {
					T1.el[j][k] -= ratio * T1.el[i][k];
				}
			}
		}
	}

	// backwards substitutions
	for(i = 1; i <= rows; i++){
		sum = T1.el[i][i];
		for(j = i+1; j <= 2*rows; j++){
			T1.el[i][j] /= sum;
		}
	}

	// Copy to return Matrix
	for(i=1; i<=rows; i++){
		for (j=1; j<=rows; j++) {
            F.el[i][j] = T1.el[i][j+cols];
		}
	}

	return F;
}

How or where would I go about adding this functionality/check?

Thanks.

Brock

Dani AI

Generated

A few focused fixes will make the current routine robust and correct without rewriting the whole approach.

First, pivot handling: before you compute any ratio that divides by T1.el[i][i], check that the pivot is not effectively zero. If |pivot| is below a tolerance (for example relative to the largest element in the column or using a multiple of numeric_limits<double>::epsilon()), search rows i..rows for the largest absolute value in that column and swap that row into position i. If the best pivot is still below the tolerance, the matrix is singular and you should abort (or return an error). Implement the swap across all columns of the augmented matrix (the full 1..2*n range). This is the partial-pivoting step was pointing toward and it must go immediately before any division by the pivot.

Second, watch the algorithmic order and small bugs in the posted code. The identity-fill loop appears to start at the wrong column index (it should start at n+1 for the augmented half). The current elimination does row reductions before normalizing the pivot row; that order leads to incorrect diagonal normalization later. Either:

  • do Gauss-Jordan: for each pivot row, normalize the whole row (divide by pivot) then eliminate that column from all other rows; or
  • do Gaussian elimination with partial pivoting (eliminate only rows below), then do a proper back-substitution step. Pick one and implement its correct sequence consistently.

Third, practical tips: encapsulate row-swap, find-pivot, and normalize-row as helpers to keep code readable and avoid off-by-one mistakes (and convert to 0-based indexing if your Matrix class currently uses 1-based indexing). Add unit tests that check A * A_inv == I within a small tolerance and test singular and nearly-singular matrices. For production work, prefer a tested numerical library (Eigen/LAPACK) rather than hand-rolled inversion; for learning purposes, the above changes will make your implementation reliable.

Recommended Answers

All 2 Replies

First of all, I don't know if the implementation of "Matrix" class is special, but normally, in C/C++, the indices (i,j) go from 0 to N-1, not from 1 to N (like in Matlab or Fortran). If this Matrix class is implemented to handled 1-based indices, then I highly recommend that it be changed because you should stick with the established convention of the language you are using, which is 0-based indices.

Second, this inner loop:

for(j = 1; j <= rows; j++){
			if(i != j) {
				ratio = T1.el[j][i]/T1.el[i][i];
				for(k = 1; k <= 2*rows; k++) {
					T1.el[j][k] -= ratio * T1.el[i][k];
				}
			}
		}

Should be replaced by this (because you only need to obtain a triangular form for the back-substitution, meaning you only need to eliminate the rows below the diagonal):

for(j = i+1; j <= rows; j++){
			ratio = T1.el[j][i]/T1.el[i][i];
			for(k = 1; k <= 2*rows; k++) {
				T1.el[j][k] -= ratio * T1.el[i][k];
			}
		}

Finally, Gaussian elimination without a pivot is really unstable, doing the pivoting is not optional and cannot just be "the next row if the pivot element is 0". First, a floating-point number is rarely if ever exactly zero. Then, just picking the first row that meets some minimal requirement (absolute value greater than some tolerance) is not good enough in general. The standard thing is to always select the row with the largest pivot element (largest in absolute-value terms). Then, you swap the current row with the selected row, and then you perform the elimination as usual.

Just go by the wikipedia pseudo-code and you'll be fine.


N.B.: I hope you don't think that Gaussian elimination is actually a good method for inverting a matrix.. right? It's a good exercise in programming to implement the method, but it is not really useful in practice, it's slow and unstable. For the cases for which Gaussian elimination would work OK, the LU-decomposition works better (or PLU decomp.). But, for the majority of cases where the accuracy/stability of the LU-decomp would be really questionable, other methods are preferrable, like QR, Cholesky, Jacobi, etc.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.