I have been working with some matrix functions and need the determinant. I have looked at several examples, but they all seem to use a hard coded size for the matrix, or calculate for a matrix of int, etc.
My data is stored in a vector<vector<double>>
and can be of any size. This is a typical square matrix of a matrix and its transpose MT*M.
This is my current code and is based in part on this post.
#include <vector>
#include <iostream>
#include <cstdlib>
// add namespace
using namespace std;
// declare, dynamically size, and return a 2D array of double as pointer to pointer
double** matrix_allocate_size(const unsigned int rows, const unsigned int columns) {
// declare a pointer to a 2 dimensiona array of double
// and allocate the number of rows
double **matrix = (double **) calloc( rows, sizeof(double) );
// allocate space for each column of each row
// using calloc here initalizes array elements to 0
for(unsigned int i=0; i<columns; i++) {
matrix[i] = (double *) calloc(columns, sizeof(double));
}
return matrix;
};
// calculate the determinant of a square matrix
// this function is called recursively
double matrix_determinant(unsigned int size, double **matrix) {
// 2D array version of cofactor matrix
// declare pointer to pointer and size by function call
double **matrix_copy = matrix_allocate_size(size, size);
// return value
double determinant = 0.0;
// sign for each product
double sign = 1.0;
// loop iterator
unsigned int position = 0;
// current location in array_matrix
unsigned int matrix_row = 0, matrix_column = 0;
// current location in matrix copy
unsigned int copy_row = 0, copy_column = 0;
// for matrix with one element
if(size == 1) { return (matrix[0][0]); }
// for a matrix with 4 elements, use the formula
// this is essentially length*width = area
else if(size == 2) { return ( (matrix[0][0]*matrix[1][1])-(matrix[1][0]*matrix[0][1]) ); }
// for a matrix with 9 or more elements
else {
// reinitialize local determinant value
determinant = 0.0;
// loop on size of current array_matrix
for(position=0; position<size; position++) {
// zero out current matrix position
copy_row = 0; copy_column = 0;
// create determinant products by skipping each row and column in sequence
// iterate through array_matrix up to size,size
// size decrements on each recursive call
for(matrix_row=0; matrix_row<size; matrix_row++) {
for(matrix_column=0; matrix_column<size; matrix_column++) {
// skip current row and column when making copy
if(matrix_row != 0 && matrix_column != position) {
// copy matrix elements not part of current row and column
matrix_copy[copy_row][copy_column] = matrix[matrix_row][matrix_column];
// increment column location in matrix copy up to size-2
if( copy_column<(size-2) ) { copy_column++; }
// move to next row in matrix copy, reset column number to 0
else { copy_column=0; copy_row++; }
}
}
}
// calculate current element of determinant and add to sum
// pass matrix copy in all subsequent recursive calls
determinant += sign * ( matrix[0][position] * matrix_determinant(size-1, matrix_copy) );
// reverse sign for next iteration
sign = -1 * sign;
} // for(position=0; position<size; position++) endbrace
} // else endbrace
return (determinant);
};
// re-cast data from vector<vector<double>> to 2D array
// creates a matrix as a 2D array ot pointer to pointer
// populates the matrix, and passed it to a function to calculate the determinant
// returns the calculated determinant
double matrix_determinant_caller(const vector< vector<double> >& M1) {
// return value
double M1_determinant = 0.0;
// store size of passed vector<vector<double>> matrix
unsigned int M1_dimension = M1.size();
// call function to size array version of matrix[rows][columns]
// this is a square matrix, so use the same dimension for rows and columns
double **array_matrix = matrix_allocate_size(M1_dimension, M1_dimension);
// copy M1 to a_matrix
for(unsigned int i=0; i<M1_dimension; i++) {
for(unsigned int j=0; j<M1[0].size(); j++) { array_matrix[i][j] = M1[i][j]; }
}
// call function to get determinant
M1_determinant = matrix_determinant(M1_dimension, array_matrix);
return M1_determinant;
};
// program entry point
int main() {
// vector version of matrix
vector< vector<double> > matrix_vector_version;
// number of matrix rows and matrix columns
unsigned int num_rows = 4, num_columns = 4;
// temp matrix row for insert
vector<double> temp_row;
// size temp row vector
temp_row.resize(num_columns);
// add rows and columns to matrix
for(unsigned int i=0; i<num_rows; i++) { matrix_vector_version.push_back(temp_row); }
// add test data to matrix row 0
matrix_vector_version[0][0] = 1248.14;
matrix_vector_version[0][1] = 1408.68;
matrix_vector_version[0][2] = -828.282;
matrix_vector_version[0][3] = -53.0927;
// add test data to matrix row 1
matrix_vector_version[1][0] = 1408.68 ;
matrix_vector_version[1][1] = 1623.81;
matrix_vector_version[1][2] = -952.41;
matrix_vector_version[1][3] = -67.2946 ;
// add test data to matrix row 2
matrix_vector_version[2][0] = -828.282;
matrix_vector_version[2][1] = -952.41;
matrix_vector_version[2][2] = 559.421;
matrix_vector_version[2][3] = 38.0384;
// add test data to matrix row 3
matrix_vector_version[3][0] = -53.0927;
matrix_vector_version[3][1] = -67.2946;
matrix_vector_version[3][2] = 38.0384;
matrix_vector_version[3][3] = 5.46328;
// call function to calculate determinant
double determinant = matrix_determinant_caller(matrix_vector_version);
// print determinant
// should be 44.1164 for this data
cout << "determinant: " << determinant << endl;
cout << endl;
return 0;
} // main EOF brace
This code compiles and runs and gives the correct value for the determinant of 44.1164 which is confirmed by an online calculator.
It would be nice to have some input on ways to improve the code. In particular, I think there is no exception handling to speak of and I am sure that performance and stability can be improved. The test matrix here is a 4x4 but I will need to use this on matrices that are at least 50x50. I guess in that case I might want to use a long double for the return value.
Current run time for this data is,
real 0m0.000s
user 0m0.030s
sys 0m0.000s
I can post a more realistic version of the data, or a version that reads an input file if that would be helpful.
This is more or less written in c++98. I don't object to more recent versions, but at times I work on c and c++ that is married to very old F77 code and I can't always get the newer gfortran compiler versions to be happy with my very old fortran code. I generally have good luck if I stick with c++98 standards and older c++ compilers.
calloc()
call inmatrix_allocate_size()
usesizeof (double*)
rather thansizeof(double)
? They may be the same on your platform, so it may work, but it doesn't properly coney the meaning. \$\endgroup\$sizeof *matrix
, which clearly connects the allocation to the variable. \$\endgroup\$calloc()
should have used size of(double*)
because it is sizing an array of pointers to double not an array of double. I have already made some modifications based on the post of Phil H below using a matrix class instead of the dynamically sized arraydouble**
. Thanks for the input so far. \$\endgroup\$