15 February 2010

Easy Python generators using SWIG

In my research code, I have a number of classes that act as wrappers for containers, and they provide begin and end functions. I want an easy way to provide a generator for looping over these embedded containers. Because of the way SWIG's STL wrappers work, it's not (easily?) possible to wrap a vector of pointers, and we'd like to avoid all the extra overhead those wrappers have anyway: all we need is a way to increment a pointer and to tell if it's at the end.

So, my solution is to create a thin wrapper for only the increment operator ++, and extend the class with a function to return the "beginning" iterator and to check whether the iterator is at the end position.

For this particular instance, I have a class Mesh that has methods for beginLeftBoundaryFaces and endLeftBoundaryFaces—which return STL vector iterators that point to a Face *.

%inline %{
//! Thin wrapper for ONLY the increment operator
void _bfiter_incr( std::vector<Face *>::const_iterator* iter )
{
    // increment the iterator
    ++(*iter);
}
%}

%extend Mesh {
%insert("python") %{
    def left_boundary_faces(self):
        "A generator to iterate through boundary faces."
        faceIter = self._beginLeftBoundaryFaces()
        keepLooping = True
        while keepLooping == True:
            face = self._bfiter_dereference_Left( faceIter )
            if face:
                _bfiter_incr( faceIter )
                yield face
            else:
                keepLooping = False
%}

//! get the first element in the vector
std::vector<Face *>::const_iterator* _beginLeftBoundaryFaces()
{
    return new std::vector<Face *>::const_iterator(
            ($self->beginLeftBoundaryFaces()) );
}

//! dereference the iterator; return NULL if at the end
const Face* _bfiter_dereference_Left(
        const std::vector<Face *>::const_iterator* iter )
{
    // if at the end, return NULL
    if (*iter == ($self)->endLeftBoundaryFaces() ) {
        return NULL;
    }
    // otherwise, return the face to which this iterator points
    return **iter;
}
}

So now I can do:

for f in mesh.left_boundary_faces():
    print f.area()

or, of course, anything else now that I'm using Python.

See further updates on the thin Python wrappers for C++ iterators.

4 comments:

Anonymous said...

Hi Seth,

I came across this blogpost while looking for ways to access C++ STL:vector elements from python through SWIG. I used your sample code to create a .i file for my project. In my case, 'left_maps' replaces 'left_boundary_faces'.

When I try to do 'for f in left_maps: print "me" ' in python, I get "NameError: global name 'left_maps' is not defined". Any ideas on how to resolve this?

-Matt

Seth said...

Hey Matt,
Good catch, this was an error on my part. `left_boundary_faces` is a method of the class `Mesh` that is actually a generator, so I should have said `mesh.left_boundary_faces()`.

However, rather than using this code for an STL vector, you might consider using SWIG's built-in implementation with `%include `.

Anonymous said...

Hi Seth,

I appreciate the quick response. After putting in your fix, I get "Attribute Error: 'MapSet' has no attribute 'left_maps' ". ('MapSet' is analogous to 'Mesh' in your example)

Also, could you elaborate on what you said about using the built-in implementation using '%include' or give me a pointer to some documentation on it? My understanding after reading your post was that there is no easy way wrap STL:vectors in SWIG.

Thanks.
-Matt

Seth said...

Matt:

Sorry, the `%include <std_vector.i>` got eaten by the HTML parser. Take a look at the SWIG documentation. I only meant that wrapping arbitrary iterators was difficult; wrapping the built-in STL classes is easy with SWIG's functionality. If this doesn't answer your question, perhaps you could email me your code and some explanation. My email link is under the "About me" section.

Post a Comment