Use Matplotlib for C++

Posted by Nino Lau on March 8, 2020

Few days ago, I got a task to explore Matplotlib for C++. As we all known, Matplotlib (MPL) is a powerful Python drawing package, but why should we use it in C++? The first appeal is to improve the performance. C++ is much faster than Python, thus there is still room for Python projects to have a performance improvement. Another reason has something to do with multi-thread programming. Python interpreter is not thread-safe naturally. We can only protect all concurrent access with a mutex. The last reason is to be customized: How to plot using MPL (rather that other plot lib) when you have finished programming in C++ already. Considering the challenges above, we expect a MPL C++ binding or at least an interface to use MPL for C++ codebase.

MPL C++ Binding

What about MPL C++ binding? Until now, there is an extremely simple yet powerful header-only C++ plotting library built on the popular matplotlib — matplotlib-cpp. To use it, you must have python (-V 2 recommended in this repo) installed beforehand, and use CMake to build it.

find_package(Python2 COMPONENTS Development NumPy)
target_include_directories(myproject PRIVATE ${Python2_INCLUDE_DIRS} ${Python2_NumPy_INCLUDE_DIRS})
target_link_libraries(myproject Python2::Python Python2::NumPy)

This truly makes MPL ploting possible for C++ like this:

#include "matplotlibcpp.h"
#include <vector>
#include <cmath>

namespace plt = matplotlibcpp;

int main() {
    std::vector<double> t(1000);
    std::vector<double> x(t.size());

    for(size_t i = 0; i < t.size(); i++) {
        t[i] = i / 100.0;
        x[i] = sin(2.0 * M_PI * 1.0 * t[i]);
    }

    plt::xkcd();
    plt::plot(t, x);
    plt::title("AN ORDINARY SIN WAVE");
    plt::save("xkcd.png");
}

Seems good? Uhn… However, this project has some problems. First, this is only a personal project without an organization, which indicates that codes may not be systematically and efficiently maintained. Secondly, all of the funcationality of MPL Py is not transmitted into this repo. This project looks like an incomplete mirror image for MPL Py. If you only require basis functionality of MPL Py for experiments and practices, you might as well try it. But if you are asking high-quality implementation or do not want to be pested by underlying bugs, you are expected to try some other things. It’s not perfect yet, but hopefully this newborn will do better in the future.

C++ String for Python MPL

Known that we can hardly use MPL as a pure C++ project. Let’s think about how to implement the MPL functionality by using interface. Referring Q&A on Stackoverflow, I make some examples.

#include "Python.h"

int main()
{
   Py_Initialize();
   PyRun_SimpleString("import pylab");
   PyRun_SimpleString("pylab.plot(range(5))");
   PyRun_SimpleString("pylab.show()");
   Py_Exit(0);
   return 0;
}

First you should have your Python installed and export the right Python header using export. (You can use which python command to see the path of your python, and export the Header path of this python.) You can skip this step if it works in your workspace without an error of “‘Python.h’ not found”.

export CPLUS_INCLUDE_PATH=/usr/local/Cellar/python/2.7.6_1/Frameworks/Python.framework/Versions/2.7/Headers

Then compile the C++ file and run.

g++ a.cpp -std=c++11 -I/usr/include/python2.7 -lpython2.7
./a.out

The output looks like this.

It gets a bit more tricky, but still possible with variable data, just concatenate it to a string.

#include <string>
#include "Python.h"
#include <iostream>
#include <sstream>

using namespace std;

int main()
{ 
   stringstream command;
   Py_Initialize();
   int x[5] = {0, 1, 2, 3, 4};
   int y[5] = {5, 3, 6, 4, 3};
   command << "pylab.plot([";
   for(int i = 0; i < 4; i++) {
       command << x[i];
       command << ", ";
   }
   command << x[4];
   command << "], [";
   for(int i = 0; i < 4; i++) {
       command << y[i];
       command << ", ";
   }
   command << y[4];
   command << "])";
   PyRun_SimpleString("import pylab");
   string _command = command.str();
   PyRun_SimpleString(_command.c_str());
   PyRun_SimpleString("pylab.show()");
   Py_Exit(0);
   return 0;
}

Although this method is more powerful than the MPL C++ method, its readability is poor. We usually need to write a Python file before programming C++ plot codes. Thus, if you are aimming at build a customized plot which has functionalities that MPL C++ cannot cover, this is a possible choice. Besides, this method might have problems when facing with millions of points, so it’s not perfect, either.

Underlying C++ Modules as Interface

You must have a problem so far: As MPL itself is made of C++, why not call its underlying parts as interface? Yep, I have thought about this. Two mainly problem.

MPL is a pretty large package which contains many modules. On MPL official website, it only shows the API reference in Python and not it is not made very modular, you can check out the source to find out. That’s not very efficient. In addition, it’s entirely possible that it was built specifically around the Python/C api, maybe it requires Python.

Serialization

I\If the performance requirements are not very high, it is ok to specify the file format through file communication. For example, we can use pickle files as medium to translate between C++ and Python.

For example, if you have a C++ file like this and want to use Python to access the object in it.

#include <string>

struct World
{
World(std::string a_msg) : msg(a_msg) {}
std::string greet() const { return msg; }
std::string msg;
};

Then create a format conversion file.

#include <boost/python.hpp>
#include "src.cpp"

using namespace boost::python;

struct World_picklers : pickle_suite
{
static tuple
getinitargs(World const& w) { return make_tuple(w.greet()); }
};

BOOST_PYTHON_MODULE(test)
{
class_<World>("World", init<std::string>())
.def("greet", &World::greet)
.def_pickle(World_picklers())
;
}

After that, you can import the object in Python.

import test
import pickle
a = test.World("haha")
pickle.dump(a,open("my_world","w"))
print "create pickle file(my_world)" 

print "load from pickle file(my_world)"
b = pickle.load(open("my_world","r"))
print b.greet()

This is the same for MPL object. This is good for it seperate the part of different language programming. It’s readable and preservable. The only defeat is it needs some memory for saving files. But that is not a big problem.

Conclusion

To sum up, after several days of reference, I found four ways to use MPL in C++.

  • The first is to use the MPL C++ binding, but it’s incomplete and lacks some functionalities.
  • The second is to use string stream in C++ to program like using Python, but the code is poor readable.
  • The third is to use MPL underlying C++ modules, but it is not made very modular and may have Python dependency.
  • The last seems to be the most applicable, you can use C++ for multi-thread programming and serialize the useful object, and then deserialized it when try to use MPL.

I hope that in the future, there will be more friendly ways to use MPL for plotting for projects in various languages.