-
Notifications
You must be signed in to change notification settings - Fork 6
Dynamically sized matrices and vectors
Dynamically sized matrices and vectors have their storage allocated at run-time.
Matrices and vectors are allocated as the following example shows:
MatrixNd X(5, 6); // construct a 5 row x 6 column matrix
VectorNd y(5); // construct a 5-dimensional vector
#include <Ravelin/VectorNd.h>
#include <Ravelin/VectorNf.h>
#include <Ravelin/MatrixNd.h>
#include <Ravelin/MatrixNf.h>
Matrices can be resized using resize(rows,columns) and vectors can be resized using resize(length). If the size requires less memory than the current allocation, memory is not reallocated. This (now unused) memory can be reclaimed using the compress(.) function, but this feature allows for simple real-time programming: call your functions that use dynamic memory allocation once with the maximum expected dynamic sizes and no further memory allocation will occur.
Elements can be accessed in a matrix using the (.) operator and in a vector using the [.] operator. For example:
MatrixNd X(5, 5);
VectorNd y(5);
X(3, 3) = 1.0;
y[2] = 4.5;
If many elements are to be accessed, it is faster to use iterators, described below.
Iterators permit the elements of matrices and vectors to be iterated over in an efficient, random-access manner toward use with STL algorithms. For example, one can compute the maximum element of a double-precision matrix:
MatrixNd X(5, 6);
... // entries are determined here
ColumnIteratord_const max_elm = std::max_element(X.column_iterator_begin(), X.column_iterator_end());
std::cout << "Max element is: " << *max_elm << std::endl;
Two different types of iterators are provided: row iterators, e.g., RowIteratord, (that iterate over a matrix row by row) and column iterators, e.g., ColumnIteratord (that iterate over a matrix column by column). Iterators can be constant (e.g., ColumnIteratord_const) or non-constant (e.g., ColumnIteratord); only the latter type permits modifying the entries of the underlying matrix or vector.
A shared matrix or vector is known as a "matrix view" in some libraries and is analogous to a block or segment, respectively, in Eigen. Shared matrices and vectors can be constant or non-constant. The following shows how one can add two blocks together.
MatrixNd X(10, 10), Y(10, 10);
... // matrix values initialized here
SharedConstMatrixNd Yblock = Y.block(3, 6, 0, 10); // get block of rows 3..5 and columns 0..9
X.block(0, 3, 0, 10) += Yblock; // add block of Y to X
Rows, columns, and blocks can be copied using two mechanisms. As described above, one can use a shared matrix or vector. For instance, the following example demonstrates how one can copy a row of one matrix to a row of another:
MatrixNd X(5, 5), Y(4, 5);
... // matrix values initialized here
SharedConstVectorNd row3 = X.row(3);
SharedVectorNd row0 = Y.row(0);
row3 = row0;
Similarly, the following example demonstrates how one can copy a column of one matrix to a column of another:
MatrixNd X(5, 5), Y(4, 5);
... // matrix values initialized here
SharedConstVectorNd column3 = X.column(3);
SharedVectorNd column0 = Y.column(0);
column3 = column0;
The last three lines above can be reduced to a single line (X.row(3) = Y.row(0); or X.columns(3) = Y.column(0);). Another option, which is slower (two copy operations are performed), but may still sometimes be preferable is to use an intermediate vector or matrix and get_row(.), get_column(.), get_sub_mat(.), and set_sub_mat(.) functions. The example above can be written as:
MatrixNd X(5, 5), Y(4, 5);
VectorNd work;
... // matrix values initialized here
X.get_row(3, work); // note: work is automatically resized by get_row(.)
Y.set_row(0, work);
Rows, columns, or blocks of non-contiguous entries in a matrix can be copied or set using the select(.) matrix and vector functions. For example:
std::list<unsigned> rows;
MatrixNd source(5, 5), dest(3, 5);
... // initialize values of source here
rows.push_back(0);
rows.push_back(2);
rows.push_back(4);
source.select_rows(source.begin(), source.end(), dest); // copy rows 0, 2, and 4
Matrix/vector and matrix/matrix multiplication is conducted in such a way that the user should be aware if memory is to be allocated. The following example illustrates both matrix/vector and matrix/matrix multiplication:
MatrixNd A(5, 3), X(3, 4), resultAX;
VectorNd x(3), resultAx;
... // values are initialized here
A.mult(X, resultAX);
A.mult(x, resultAx);
Multiplication using matrix transposes can be performed as illustrated below:
MatrixNd A(3, 5), B(5, 4), X(3,5), resultAX, resultBTXT;
VectorNd x(3), resultATx;
A.transpose_mult(x, resultATx);
B.transpose_mult_transpose(X, resultBTXT);
A.mult_transpose(A, X, resultAX);
Matrix and vector addition and subtraction can be performed in place using the += and -= operators:
MatrixNd A(5, 5), B(5, 5);
... // values are initialized here
A += B;
Multiplying a matrix or vector by a scalar can also be conducted using the *= and /= operators:
MatrixNd A(5, 5);
... // values are initialized here
A *= 0.5;
Square matrices can be transposed in place:
MatrixNd A(5, 5);
... // values are initialized here
A.transpose();
The same operation works for non-square matrices but requires allocating memory.
The semantics of memory allocation are clearer using the static transposition function:
MatrixNd A(5, 6), AT;
... // values are initialized here
MatrixNd::transpose(A, AT); // memory will be allocated for AT
In the example above, if AT had been allocated sufficient memory before the transposition operation, no new memory would be allocated (and real-time performance could be preserved).