Skip to content

klayout Python Module Bridging Code

Matthias Köfferlein edited this page Jun 20, 2018 · 1 revision

Problem

Imagine you have a third-party library that accepts polygons, but not KLayout objects. Instead it takes polygons as arrays of x/y tuples.

The Python solution was to iterate over the KLayout polygon and produce the array:

import klayout.db

some_polygon = klayout.db.DSimplePolygon(klayout.db.DBox(0, 0, 100, 200))
# make it more complex:
some_polygon = some_polygon.round_corners(10.0, 10.0, 200)

# array production
a = [ (pt.x,pt.y) for pt in some_polygon.each_point() ]

print repr(a)

Not unexpected, the array production loop isn't quite fast. I measured about 3µs per call.

The solution is a specialized C module that performs this translation. I'd like to call such modules "bridges" as they translate representations between different worlds.

With such a bridge, the code looks this way:

import klayout.tl
import klayout.db
import bridge_sample as bs

some_polygon = klayout.db.DSimplePolygon(klayout.db.DBox(0, 0, 100, 200))
# make it more complex:
some_polygon = some_polygon.round_corners(10.0, 10.0, 200)

a = bs.p2a(some_polygon)

Not surprisingly, this code is much faster (about 120ns in my case for the single call to bs.p2a).

The code for the sample bridge is provided in the source tree bridge_sample.cc.

The bridge module skeleton is the one every Python module uses. To simplify the handling of Python object references, the 'pya::PythonRef' smart pointers are used:

#include <Python.h>

#include "pyaRefs.h"
... custom headers ...

static PyObject *BridgeError;

static PyObject *
bridge_a2p (PyObject * /*self*/, PyObject *args)
{
  PyObject *a = NULL;
  if (! PyArg_ParseTuple (args, "O", &a)) {
    return NULL;
  }

  //  Iterate over the array elements
  pya::PythonRef iterator (PyObject_GetIter (a));
  if (! iterator) {
    return NULL;
  }

  ... translation code array to polygon ...
}

static PyObject *
bridge_p2a (PyObject * /*self*/, PyObject *args)
{
  //  Parse the command line arguments
  PyObject *p = NULL;
  if (! PyArg_ParseTuple (args, "O", &p)) {
    return NULL;
  }

  ... translation code polygon to array ...
}

static PyMethodDef BridgeMethods[] = {
  {
    "p2a", bridge_p2a, METH_VARARGS,
    "Converts a DSimplePolygon to an array."
  },
  {
    "a2p", bridge_a2p, METH_VARARGS,
    "Converts an array to a DSimplePolygon."
  },
  { NULL, NULL, 0, NULL }  //  terminal
};

#if PY_MAJOR_VERSION < 3

PyMODINIT_FUNC
initbridge_sample ()
{
  PyObject *m;

  m = Py_InitModule ("bridge_sample", BridgeMethods);
  if (m == NULL) {
    return;
  }

  BridgeError = PyErr_NewException ((char *) "bridge_sample.error", NULL, NULL);
  Py_INCREF (BridgeError);
  PyModule_AddObject (m, "error", BridgeError);
}

#else

static
struct PyModuleDef bridge_module =
{
  PyModuleDef_HEAD_INIT,
  "bridge_sample",
  NULL,
  -1,
  BridgeMethods
};

PyMODINIT_FUNC
PyInit_bridge_sample ()
{
  PyObject *m;

  m = PyModule_Create (&bridge_module);
  if (m == NULL) {
    return NULL;
  }

  BridgeError = PyErr_NewException ((char *) "bridge_sample.error", NULL, NULL);
  Py_INCREF (BridgeError);
  PyModule_AddObject (m, "error", BridgeError);

  return m;
}

#endif

Custom headers

The custom headers to use are:

#include "pyaConvert.h"
#include "dbPolygon.h"

The first header supplies the algorithms to access the PyObject's internal object (a db::DSimplePolygon). The second header declares the C++ polygon object. From the various classes this header provides, we use db::DSimplePolygon.

Translation code polygon to array

The following code is the translation code to turn polygons into arrays:

  //  Report an error if the input isn't a db::DSimplePolygon
  if (! pya::test_type<const db::DSimplePolygon &> (p)) {
    PyErr_SetString (BridgeError, "Expected a db::DSimplePolygon type");
    return NULL;
  }

  //  Obtain the db::DSimplePolygon
  const db::DSimplePolygon &poly = pya::python2c<const db::DSimplePolygon &> (p);

  //  Prepare an array for the points
  PyObject *array = PyList_New (poly.hull ().size ());
  Py_INCREF (array);

  //  Iterate over the points and fill the array with x/y tuples
  int i = 0;
  for (db::DSimplePolygon::polygon_contour_iterator pt = poly.hull ().begin (); pt != poly.hull ().end (); ++pt, ++i) {
    PyObject *point = PyTuple_New (2);
    PyTuple_SET_ITEM (point, 0, pya::c2python ((*pt).x ()));
    PyTuple_SET_ITEM (point, 1, pya::c2python ((*pt).y ()));
    PyList_SetItem (array, i, point);
  }

  return array;

The pya::test_type<T> template provided by pyaConvert.h is used to verify that the requested object is of the desired type.

pya::python2c<T> provided by pyaConvert.h too is used to extract the raw C++ object behind the Python object.

The remaining piece is about iterating over the points in the usual C++ fashion and producing the Python array with the x/y 2-element tuples.

Translation code array to polygon

  //  Iterate over the array elements
  pya::PythonRef iterator (PyObject_GetIter (a));
  if (! iterator) {
    return NULL;
  }

  //  Prepare a vector of points we can create the polygon from later
  std::vector<db::DPoint> points;

  PyObject *item;
  while ((item = PyIter_Next (iterator.get ())) != NULL) {

    //  Iterate over the x/y pair
    pya::PythonRef xy_iterator (PyObject_GetIter (item));
    if (! xy_iterator) {
      return NULL;
    }

    double c[2] = { 0.0, 0.0 };

    //  Gets the x and y value
    for (int i = 0; i < 2; ++i) {
      pya::PythonRef xy_item (PyIter_Next (xy_iterator.get ()));
      if (! xy_item) {
        return NULL;
      }
      if (pya::test_type<double> (xy_item.get ())) {
        c[i] = pya::python2c<double> (xy_item.get ());
      }
    }

    points.push_back (db::DPoint (c[0], c[1]));

  }

  //  Handle iteration errors
  if (PyErr_Occurred()) {
    return NULL;
  }

  //  Create and return a new object of db::DSimplePolygon type
  db::DSimplePolygon *poly = new db::DSimplePolygon ();
  poly->assign_hull (points.begin (), points.end ());
  return pya::c2python_new<db::DSimplePolygon> (poly);

This implementation utilizes the Iterator interface of Python objects to access the nested array/tuple structure and turn this into a std::vector<db::Point> array. The final step is to produce a db::DSimplePolygon object from that list.

The important function is pya::c2python_new<db::DSimplePolygon> from pyaConvert.h. The '_new' version will accept the new object and manage it's lifetime: if the Python object is not longer required, the underlying C++ object will be deleted too. Hence we can safely pass it a new'd pointer.