Contents:
- Include files
- Construction and Initializing
- Use of C style Arrays with tvmet
- Compare Vectors and Matrices
- Data Types like std::complex<>
- STL support
- Matrix access by rows and columns
- Expression printing
The Tiny Vector and Matrix template library has many include files spread throughout the tvmet include directory. As a user, you need only include <tvmet/Vector>
for vector operations and/or <tvmet/Matrix>
for matrix operations.
- Example:
Simple, isn't it? Don't forget to use the namespace tvmet, but keep in mind that using the using directive inside headers will pollute the namespace. If you write this in a header file, the namespace for all subsequent header files (those which include the one you're writing) will also be polluted. (This is not a tvmet specific phenomenon.) Therefore, write the using statement in the C++ file.
Due to the nature of Expression Templates (ET) you can't write code like
- Example:
The operator= function assigns an expression to the Vector which means that the object must be constructed before you may assign something to it. The solution is to write this as:
- Example:
using namespace tvmet;
Vector<double, 3> v1(1,2,3);
Vector<double, 3> v2;
v2 = v1;
Vector<double, 3> v3(v1);
std::cout << v3 << std::endl;
since the object v2 needs to be constructed before the object's operator=() can be called.
The same rule applies to the Matrix class. You can only assign vectors and matrices of the same dimension or you will get a compile error. This also applies to the argument list for the constructor of the classes.
Initializing can be done as shown above or by using a comma separated list:
- Example:
using namespace tvmet;
Matrix<double, 3, 2> m1;
m1 = 1, 4,
2, 5,
3, 6;
Matrix element initialization always performed column wise! If the length of the comma separated list is longer than the storage size, you will get a compile time error. (tvmet is designed to prevent this -- it will prevent you from accidentally overwriting memory which does not belong to the matrix you are initializing.) You can use a comma separated list to initialize vectors as well.
If you want a clean (zero-valued) vector or matrix you can simple write:
- Example:
using namespace tvmet;
Vector<double, 3> v4(0);
Matrix<double, 3, 4> m2(0);
All elements of v4 and m2 are initialized with zero (or whatever value you provide at construction time). Keep in mind that the uninitialized Matrix and Vector classes will have random data when the are created (since they use a static array for internal storage) unless you initialize them!
Another way to initialize a vector or matrix follows:
- Example:
using namespace tvmet;
Vector<double, 3> v5(1,2,3);
Vector<double, 3> v6(v5);
Vector<double, 3> v7(v5+v6);
This is useful for temporary results. The result will be immediately assigned to the new vector elements using the expression passed to the constructor.
Yet another way of initializing a vector or matrix is similar to the above. We assign an expression to it:
- Example:
using namespace tvmet;
Matrix<double, 3, 3> m3, m4, m5;
m3 = 1, 2, 3,
4, 5, 6,
7, 8, 9;
m4 = m3;
m5 = m3 + m4;
If you have your data inside arrays you can use tvmet's iterator interface to initialize a vector or matrix with it:
- Example:
double data[] = { 1,4,7,
2,5,8,
3,6,9 };
std::size_t sz = sizeof(data)/sizeof(double);
double* first = data;
double* last = data + sz;
tvmet::Matrix<double, 3, 3> m(first, last);
The data will be copied into the matrix itself. When the constructor has finished, there will be no stored reference to the array pointer.
Starting with tvmet release 1.6.0 you can create an identity matrix simply by using the function identity(). Note, we have to specify the matrix type, since ADL can't work here.
- Example:
typedef Matrix<double,3,3> matrix_type;
...
matrix_type E( identity<matrix_type>() );
Sometimes you have some data arranged in a C style array for matrices and vectors. As with tvmet release 1.6.0 you can wrap an expression around using the functions cvector_ref(const T* mem) and cmatrix_ref(const T* mem) where mem is the pointer to the C array.
The returned expressions (XprVector or XprMatrix) can be used as usual like tvmet's vectors and matrices. This means, you can use all mathematical functions on it.
- Example:
static float lhs[3][3] = {
{-1, 0, 1}, { 1, 0, 1}, {-1, 0, -1}
};
static float rhs[3][3] = {
{ 0, 1, 1}, { 0, 1, -1}, { 0, -1, 1}
};
...
typedef tvmet::Matrix<float, 3, 3> matrix_type;
matrix_type M( prod(cmatrix_ref<float, 3, 3>(&lhs[0][0]),
cmatrix_ref<float, 3, 3>(&rhs[0][0])) );
This allows to initialize tvmet's vectors and matrices by an alternative way as described at Construction and Initializing.
Well, the functions tvmet::cvector_ref and tvmet::cmatrix_ref wrap an expression arround C styled vectors and arrays. They create a tvmet::VectorConstReference respective a tvmet::MatrixConstReference with a const pointer to the base address. This means that you can not modify the contents of the C styled vector/arrays by tvmet,
- e.g. writing
int myarr[3];
tvmet::cvector_ref<int,3>(myarr) = 1,2,3;
won't work. tvmet is not a wrapper for math arround C style arrays. It would not be a problem to write a non-const vector_ref/matrix_ref function with appropriate VectorReference respective MatrixReference but there is no direct way to get the results back into the C style arrays later - it was a design decision of tvmet. Therefore you must copy the result into.
If you expect to find global comparison operators for comparing Vectors and Matrices, you are right -- these are provided. But, the return value probably isn't what you expect: a boolean value. Instead, the operator returns an expression (e.g. XprVector<>). The contents of this expression type is a element wise logical operation (depends on the given operator like ==, <, >, etc...)! To get a boolean value you need to evaluate the expression using all_elements() or any_elements(), as follows:
- Example:
using namespace tvmet;
using namespace std;
Vector<double, 3> v1, v2, bv;
v1 = 1,2,3;
v2 = 1,3,3;
bv = v1 == v2;
cout << bv << endl;
cout << "v1 == v2 is "
<< ( all_elements( v1 == v2 ) ? "true" : "false" )
<< endl;
This gives
- [continued]
Vector<d, 3>[1, 0, 1]
v1 == v2 is false
The reason for this is the element wise operation on all elements (for both Vectors and Matrices). Comparing two vectors will result in a "boolean Vector" expression. Using all_elements/any_elements evaluates the result into a single boolean by repeatedly applying the comparison for each element.
An other example on comparing is shown below:
- Example:
if(all_elements(X == Y)) { cout << "matrices are identical" << endl; }
if(any_elements(X == Y)) { cout << "at least one element is equal" << endl; }
if(any_elements(X != Y)) { cout << "not all elements are equal" << endl; }
tvmet prior release 1.2.1 did have a boolean version eval for comparing. The functional and semantic meaning were not clear at all. Therefore I decided to remove it.
- See also:
- ... on operators and namespace element_wise
As we can see above we can use POD (plain old data) types like double
and int
as data type of a Vector or Matrix. However, we are not limited to this - we can use e.g. std::complex<>
as well:
- Example:
using namespace tvmet;
Vector<std::complex<double>,3> v1, v2;
Matrix<std::complex<double>,3,3> m1;
And operate on these...
- [continued]
v1 = 1,2,3;
m1 = 1,4,7,
2,5,8,
3,6,9;
v2 = m1 * v1;
Be careful. std::complex<>
isn't tested well on regression tests.
Since version 0.2.0 tvmet has supported an iterator interface conform to the STL and since version 0.5.0 reverse STL iterators have been supported, too.
With these, you can mix the tvmet Vector and Matrix containers with the STL algorithms.
For example, if you don't like tvmet's ostream operator, you can create your own implementation like this:
- Example:
tvmet::Vector<double, 6> v(1,2,3,4,5,6);
std::cout << v << std::endl;
std::cout << "The Vector is:" << std::endl;
std::copy(v.begin(), v.end(), std::ostream_iterator<double>(std::cout, "\n"));
Or, you create a random matrix and print it as shown here:
- Example:
If you need a specific row or column of a given matrix you can get access to it by using the functions row and col. They will return an XprVector<T>. Unfortunately, you do not get any write access to the vector elements - only reading is permitted due to the expression template concept used here. For write access, you have to use matrix indexing with the parentheses operator.
- Example:
using namespace tvmet;
typedef Matrix<double, 5, 3> matrix_type;
typedef Vector<double, 5> matrix_rowvector;
typedef Vector<double, 3> matrix_colvector;
matrix_type M;
M = ....
matrix_rowvector row2 = row(M, 1);
matrix_colvector col3 = col(M, 2);
...
Expression printing is a nice feature for debugging expressions. (For more about expression templates and expression tree please have a look here).
You can write out a simple matrix-vector multiplication of a vector v1
and a matrix m1
of the dimension of 3 as follows:
- Example:
std::cout << m1 * v1 << std::endl;
which will be expanded to:
- [continued]
XprVector<
XprMVProduct<
d, 3, 3, 3, 1, d, 1
>
3
>
The "d" is a g++ placeholder for double. (This may vary from compiler to compiler since it is an implementation detail of runtime type information [rtti] determined by the compiler's manufacturer). The purpose of this feature is to check the right evaluation of expressions into the tree on complicated mathematical expressions.
A rich source of examples are the regression tests. They show all of the supported operations and functions (if there is a regression test for this of course). Some examples are in the examples directory.