In this post, we explain how to perform two crucial tasks in the Eigen matrix library. First, we explain how to save a matrix in a Comma Separated Values (CSV) file. Then we explain how to load an Eigen matrix object from a CSV file. These two steps are important for interfacing the Eigen matrix library with other programming languages and simulation software such as MATLAB or COMSOL Multiphysics. The video accompanying this post is given below. Here is the link to the GitHub page.
Before you start reading this post, we advise you to read a post that explains how to install and how to start with the Eigen matrix library. The post can be found here. Consequently, we assume that you have installed the Eigen matrix library. All the codes that are given in this post are tested in MS Visual Studio 2017 programming environment.
First, at the top of your file, you need to include the following libraries/preprocessor directives:
#include <iostream>
#include<Eigen/Dense>
#include<fstream>
using namespace std;
using namespace Eigen;
The library that is included in the third line of code is used for saving data in files and for loading data from files. Saving data in a file is relatively straightforward. We wrote the following function to enable file saving (the complete code is given at end of this code and it is posted on GitHub).
void saveData(string fileName, MatrixXd matrix)
{
//https://eigen.tuxfamily.org/dox/structEigen_1_1IOFormat.html
const static IOFormat CSVFormat(FullPrecision, DontAlignCols, ", ", "\n");
ofstream file(fileName);
if (file.is_open())
{
file << matrix.format(CSVFormat);
file.close();
}
}
The function has two input arguments. The first string input argument describes the file name. For example, this argument can be “matrix.csv”. The second input argument is the Eigen matrix object to be saved in the file. The code line 4 is used to specify the format for saving and displaying the data. More details about formatting parameters can be found here. For us, the following input parameters are important. The third parameter “,” means that numbers are separated by commas, and the last input argument “\n” means that at the end of every row, we place a newline character.
The code line 6 is used to define an “ofstream” object that enables to save open files and save data in opened files. The code line 9 is used to write the Eigen matrix object into the specified file. Other code lines are self-explanatory.
Loading data from files and formatting them such that they fit the Eigen matrix framework is more complex than saving data to files. We wrote the following function that loads data from a file and converts the data to the Eigen matrix format.
MatrixXd openData(string fileToOpen)
{
// the inspiration for creating this function was drawn from here (I did NOT copy and paste the code)
// https://stackoverflow.com/questions/34247057/how-to-read-csv-file-and-assign-to-eigen-matrix
// the input is the file: "fileToOpen.csv":
// a,b,c
// d,e,f
// This function converts input file data into the Eigen matrix format
// the matrix entries are stored in this variable row-wise. For example if we have the matrix:
// M=[a b c
// d e f]
// the entries are stored as matrixEntries=[a,b,c,d,e,f], that is the variable "matrixEntries" is a row vector
// later on, this vector is mapped into the Eigen matrix format
vector<double> matrixEntries;
// in this object we store the data from the matrix
ifstream matrixDataFile(fileToOpen);
// this variable is used to store the row of the matrix that contains commas
string matrixRowString;
// this variable is used to store the matrix entry;
string matrixEntry;
// this variable is used to track the number of rows
int matrixRowNumber = 0;
while (getline(matrixDataFile, matrixRowString)) // here we read a row by row of matrixDataFile and store every line into the string variable matrixRowString
{
stringstream matrixRowStringStream(matrixRowString); //convert matrixRowString that is a string to a stream variable.
while (getline(matrixRowStringStream, matrixEntry, ',')) // here we read pieces of the stream matrixRowStringStream until every comma, and store the resulting character into the matrixEntry
{
matrixEntries.push_back(stod(matrixEntry)); //here we convert the string to double and fill in the row vector storing all the matrix entries
}
matrixRowNumber++; //update the column numbers
}
// here we convet the vector variable into the matrix and return the resulting object,
// note that matrixEntries.data() is the pointer to the first memory location at which the entries of the vector matrixEntries are stored;
return Map<Matrix<double, Dynamic, Dynamic, RowMajor>>(matrixEntries.data(), matrixRowNumber, matrixEntries.size() / matrixRowNumber);
}
Before we explain this code in more detail, let us briefly explain the main idea for converting CSV files in Eigen matrix formats. The idea is to open the CSV file as an “ifstream” object, and then to read line-by-line strings in this object. A line represents a string of text, and the matrix entries are read from this line (by ignoring the comma characters), and stored in a C++ “vector” object. This is repeated for every line of the CSV file. At the end, we obtain a single “vector” object with all the matrix entries stored row-wise. For example, if the CSV file has the following structure
a, b, c
d, e, f
p, q, r
where the letters are numbers. The resulting vector object will be
vector={a,b,c,d,e,f,p,q,r}
Then this vector is converted to an Eigen matrix object. Now, we comment the code. The code line 19 defines the final C++ vector object that is used to store the matrix entries row-wise. This vector is converted to the Eigen matrix object at the end of the code. The code line 22 is used to load the CSV data as an “ifstream” object.
The code line 25 is used to define a string object for storing the lines of the CSV data. The code line 28 is used to define a string object for storing matrix entries (we can also use characters). The code line 31 is used to track the number of matrix rows (this variable is necessary for converting the final vector object into the Eigen matrix object). The code line 34 is used to read the file data line by line. The code line 36 is used to convert the string object into a C++ “stringstream” object. The code line 38 is used to read the entries of the row that is now converted to the “stringstream” object. We specify the delimiters (commas in our case, and the process is finished once the end of the line character is being read). Finally, the code line 40 is used to fill-in the C++ vector object (before that strings are being converted into a double variable). Finally, the code line 47 is used to convert the C++ vector object into the Eigen matrix form. The parameter “matrixEntries.data()” is the pointer to the first entry of the vector that is being stored in computer memory, and the following two parameters are being used to specify the number of rows and columns.
Finally, here is the driver code to test the functions.
int main()
{
// test matrix to be saved
MatrixXd matrix_test(4, 4);
// define the test matrix
matrix_test << 1.2, 1.4, 1.6, 1.8,
1.5, 1.7, 1.9, 1.10,
0.8, 1.2, -0.1, -0.2,
1.3, 99, 100, 112;
// save test matrix
saveData("matrix.csv", matrix_test);
// matrix to be loaded from a file
MatrixXd matrix_test2;
// load the matrix from the file
matrix_test2 = openData("matrix.csv");
// print the matrix in console
cout << matrix_test2;
// test the load function on a matrix defined outside this environment.
// make sure that the "matrix_outside.csv" file exhists
/*MatrixXd matrix_test3;
matrix_test3 = openData("matrix_outside.csv");
cout <<"\n\n"<< matrix_test3<<"\n\n";
cout << 10*matrix_test3 << "\n";*/
}
First, we define an Eigen matrix. Then, we save the matrix to a file, and finally, we load the matrix from a file and we print it out. The commented code is used to test the loading procedure on a matrix that is defined outside of the C++ environment.