Eigen
Introduction †
Writing math, vector and matrix library from scratch may not always be the best choice. Utilizing a fast and reliable library could create a huge boost in productivity of software developers besides reliability and performance of their codes. Here, a brief tutorial on a popular C++ library for matrix manipulation and linear algebra called Eigen is composed to provide readers with the essential know-how.
Eigen is a C++ template library for vector and matrix manipulation. It includes modules for major algorithms in linear algebra such as factorization and decomposition. It also supports sparse matrix storage and operations. For more details you may browse here.
While there are two common versions, Eigen2 and Eigen3, in this tutorial more recent Eigen3 is used. For contents regrading Eigen2, please refer to legacy Eigen2 documentations.
Installation †
Eigen is very simple to install. It is only required to get the source files from Eigen's repository, checkout the desired version and include the source headers folder in compiler's search path. Stable release packages are also available at Eigen's home page or through other package managers. Here, two examples of manual installation using Git and installation using Microsoft's Vcpkg package manager are included.
Git †
To obtain Eigen3 using git, clone Eigen's git mirror on github to local folder "eigen", then checkout the version 3.3.4 which was the latest version at the time.
git clone https://github.com/eigenteam/eigen-git-mirror eigen
cd eigen
git checkout tags/3.3.4
or
git clone -b 3.3.4 https://github.com/eigenteam/eigen-git-mirror eigen
Then add include path to the compiler of choice like G++ in Bash as:
g++ -I $EIGEN_PATH sample.c -o sample
Vcpkg †
To obtain Eigen3 using Microsoft's Vcpkg, assuming operating in Microsoft's Windows's Command Prompt, just enter:
vcpkg install eigen3
Then just simply add include path to the compiler of choice as following for Microsoft's Visual Studio C++ compiler in Microsoft's Windows's Command Prompt:
cl /I %VCPKG_PATH%\installed\include sample.c
Tutorial 1 †
In this short example, it is shown how to create dynamically (run-time) and fixed (compile-time) size memory allocated matrices, initializing them and performing basic operations on them. The goal is to apply a Laplacian filter on an image and store the result in a zero padded matrix.
Matrix Variables Definition †
In Eigen storage for arrays and matrices could be allocated during compile-time or run-time. It should be noted that fixed size variables are allocated on the stack and its capacity limitation should be taken into account. Here, variable "laplace_filter" is defined as a fixed 3x3 matrix while image and result are defined dynamically and are allocated at run-time. For more information refer to here.
Eigen::MatrixXf image(16, 16);
Eigen::MatrixXf result(16, 16);
Eigen::Matrix3f laplace_filter;
Matrix Variables Initialization †
Matrices could be initialized in various ways in Eigen. Here, three ways are showcased. Matrix "image" was initialized with constant value 0.5, "result" with zeros and filter with comma-separated constants. Note the number of constants should match the matrix elements.
image = Eigen::MatrixXf::Constant(16, 16, 0.5);
result = Eigen::MatrixXf::Zero(16, 16);
laplace_filter<<0, 1, 0, 1, -4, 1, 0, 1, 0;
Block Operations †
In Eigen, window operations on arrays or matrices could be performed using blocks. Here, value of zero assigned to a window of shape 4x4 at offset of (6, 6) in matrix "image".
image.block<4, 4>(6, 6) = Eigen::MatrixXf::Zero(4, 4);
Accessors †
In Eigen matrix elements could be accessed using parenthesis operator with order of (row, column). It is also possible to access matrix elements using single index while the result depends on the storage order. Following shows assignment of convolution operation result to center element of the window given "i" is iterating row index and "j" is the column's.
result(i + (filter_dim - 1) / 2, j + (filter_dim - 1) / 2) = laplace_filter.cwiseProduct(image.block<3, 3>(i, j)).sum();
Element-wise Operations †
Eigen library is equipped with element-wise operations such as ".cwiseproduct(...)" which was used in this example to convolve Laplacian filter. It also receives a functor via ".unaryExpr(...)" or ".binaryExpr(...)" as shown here:
struct ElementwiseCube{
ElementwiseCube(){}
float operator()(const float& f_) {
return f_ * f_ * f_;
}
};
int main(){
...
image = image.unaryExpr(ElementwiseCube());
...
}
Reduction Operations †
Reduction operations in Eigen are applied on all elements and result in single scalar. Partial reductions are also supported which are applied row-wise or column-wise. For more information please browse here.
In this tutorial ".sum()" is used to reduce the element-wise product to a single element assigned to corresponding location in result matrix.
All Together †
The full version of the code is as follows:
// tutorial_01.cpp
#include <iostream>
#include <Eigen/Dense>
#define STRINGIFY(x) #x
int main(int argc, char** argv){
const int image_dim = 16, filter_dim = 3;
Eigen::MatrixXf image(image_dim, image_dim);
Eigen::MatrixXf result(image_dim, image_dim);
Eigen::Matrix3f laplace_filter;
image = Eigen::MatrixXf::Constant(image_dim, image_dim, 0.5);
image.block<4, 4>(6, 6) = Eigen::MatrixXf::Zero(4, 4);
result = Eigen::MatrixXf::Zero(image_dim, image_dim);
laplace_filter<<0, 1, 0, 1, -4, 1, 0, 1, 0;
for(int i = 0; i < image_dim - filter_dim + 1; ++i){
for (int j = 0; j < image_dim - filter_dim + 1; ++j){
result(i + (filter_dim - 1) / 2, j + (filter_dim - 1) / 2) = laplace_filter.cwiseProduct(image.block<filter_dim, filter_dim>(i, j)).sum();
}
}
std::cout<<STRINGIFY(image)<<std::endl<<image<<std::endl<<std::endl;
std::cout<<STRINGIFY(filter)<<std::endl<<laplace_filter<<std::endl<<std::endl;
std::cout<<STRINGIFY(result)<<std::endl<<result<<std::endl<<std::endl;
return 0;
}
Build and execute:
~$g++ -I $EIGEN_PATH tutorial_01.cpp -o tutorial_01
~$./tutorial_01
image
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0 0 0 0 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0 0 0 0 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0 0 0 0 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0 0 0 0 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5
filter
0 1 0
1 -4 1
0 1 0
result
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 -0.5 -0.5 -0.5 -0.5 0 0 0 0 0 0
0 0 0 0 0 -0.5 1 0.5 0.5 1 -0.5 0 0 0 0 0
0 0 0 0 0 -0.5 0.5 0 0 0.5 -0.5 0 0 0 0 0
0 0 0 0 0 -0.5 0.5 0 0 0.5 -0.5 0 0 0 0 0
0 0 0 0 0 -0.5 1 0.5 0.5 1 -0.5 0 0 0 0 0
0 0 0 0 0 0 -0.5 -0.5 -0.5 -0.5 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0