'Calling C++ function using python
I am trying to build python wrapper for a function implemented in C++ that accepts 2d vector and returns 2d vector. I am trying to adapt the code from this to suit my needs.
Input matrix shape: (n*2)
Output matrix shape: (n*2)
I think there is an issue with code.i file but not really sure what exactly is the issue.
If there are any other libraries which can achieve this that works as well. I just need to call the c++ function from python.
Here is complete reproducible example: Google Colab
My files:
code.cpp:
#include <vector>
#include "geomutils.h"
#include "code.h"
using namespace std;
vector< vector<double> > computeConvexHull(vector< vector<double> > i_matrix){
Polygon custompts, customhull;
for (int r = 0; r < i_matrix.size(); r++){
custompts.push_back(Point(i_matrix[r][0], i_matrix[r][1]));
}
computeConvexHull(custompts, customhull);
vector< vector<double> > res;
for(int i = 0;i < customhull.size();i ++) {
res[i][0] = customhull[i].x;
res[i][1] = customhull[i].y;
}
return res;
}
geomutils.cpp:
#include "geomutils.h"
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)
void computeConvexHull(Polygon &pts, Polygon &chull) {
chull.clear();
if(pts.size() == 1) {
chull.push_back(pts[0]);
chull.push_back(pts[0]);
return;
} else if(pts.size() == 2) {
chull.push_back(pts[0]);
chull.push_back(pts[1]);
chull.push_back(pts[0]);
return;
}
typedef boost::tuple<double, double> point;
typedef boost::geometry::model::multi_point<point> mpoints;
typedef boost::geometry::model::polygon<point> polygon;
mpoints mpts;
for(int i = 0;i < pts.size();i ++) {
boost::geometry::append(mpts,point(pts[i].x,pts[i].y));
}
polygon hull;
// Polygon is closed
boost::geometry::convex_hull(mpts, hull);
for(auto pt : hull.outer()) {
chull.push_back(Point(pt.get<0>(), pt.get<1>()));
}
}
geomutils.h:
#ifndef GEOMUTILS_H
#define GEOMUTILS_H
#include <vector>
struct Point {
double x,y;
Point(){}
Point(double x, double y):x(x),y(y){}
};
typedef std::vector<Point> Polygon;
void computeConvexHull(Polygon &pts, Polygon &chull);
#endif // GEOMUTILS_H
code.i:
%module code
%{
#include "code.h"
#include "geomutils.h"
%}
%include "std_vector.i"
namespace std {
/* On a side note, the names VecDouble and VecVecdouble can be changed, but the order of first the inner vector matters! */
%template(VecDouble) vector< vector<double> >;
%template(VecVecdouble) vector< vector<double> >;
}
%include "code.h"
The code uses one external library called boost. The way I am trying to generate the wrapper is as follows:
g++ -c -fPIC code.cpp geomutils.cpp
swig -c++ -python code.i
g++ -c -fPIC code_wrap.cxx -I/usr/include/python3.7 -I/usr/lib/python3.7
The first two commands run without giving any error but the third code gives me an error.
The Error is too long to post in the question. But the error can be reproduced using code link.
Update 1:
After trying the changes suggested in answer 1 and running the following commands the code compiles but nothing happens when I try to run the c++ code from python. The code is updated in the same link. "test.py" is a simple python code that calls the computeConvexHull
function.
Commands:
g++ -c -fPIC code.cpp geomutils.cpp
swig -c++ -python code.i
g++ -c -fPIC code_wrap.cxx -I/usr/include/python3.7 -I/usr/lib/python3.7
g++ -shared -Wl,-soname,_code.so -o _code.so code.o geomutils.o code_wrap.o
python test.py
Solution 1:[1]
First of all, I just want to say that this is one of the most well-written questions I've seen on SO. So thanks for that.
The issue is that you are defining a template for the same type twice, which you are not allowed to do as-per the SWIG documentation:
The %template directive should not be used to wrap the same template instantiation more than once in the same scope. This will generate an error. For example:
%template(intList) List<int>; %template(Listint) List<int>; // Error. Template already wrapped.
This error is caused because the template expansion results in two identical classes with the same name. This generates a symbol table conflict. Besides, it probably more efficient to only wrap a specific instantiation only once in order to reduce the potential for code bloat.
Since the type system knows how to handle typedef, it is generally not necessary to instantiate different versions of a template for typenames that are equivalent. For instance, consider this code:
%template(intList) vector<int>; typedef int Integer; ... void foo(vector<Integer> *x);
In this case, vector is exactly the same type as vector. Any use of Vector is mapped back to the instantiation of vector created earlier. Therefore, it is not necessary to instantiate a new class for the type Integer (doing so is redundant and will simply result in code bloat).
This issue is, as you suspected, in code.i
, where you template both VecDouble
and VecVecDouble
as vector<vector<double>>
.
Based on this answer, I'm guessing what you meant to do was:
%template(VecDouble) vector<double>;
%template(VecVecdouble) vector< vector<double> >;
Which does indeed compile.
Solution 2:[2]
The main problem was this incorrect line:
%template(VecDouble) vector<vector<double>>; // Should be vector<double>
Here's a minimal example that demonstrates the a 2D vector is wrapped appropriately in a single interface file:
test.i
%module test
%include "std_vector.i"
%template(VecDouble) std::vector<double>;
%template(VecVecdouble) std::vector<std::vector<double>>;
%inline %{
#include <vector>
std::vector<std::vector<double>> computeConvexHull(std::vector<std::vector<double>> i_matrix) {
for(auto& m : i_matrix)
for(auto& n : m)
n *= 2.0;
return i_matrix;
}
%}
Demo:
>>> import test
>>> test.computeConvexHull([[1.25,2.5,3.75],[4.25,5.5,6.75]])
((2.5, 5.0, 7.5), (8.5, 11.0, 13.5))
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Layne Bernardo |
Solution 2 | Mark Tolonen |