/* * Wrapper module for libqhull, providing Delaunay triangulation. * * This module's methods should not be accessed directly. To obtain a Delaunay * triangulation, construct an instance of the matplotlib.tri.Triangulation * class without specifying a triangles array. */ #define PY_SSIZE_T_CLEAN #include "Python.h" #include "numpy/noprefix.h" #include "qhull_ra.h" #include <stdio.h> #if PY_MAJOR_VERSION >= 3 #define PY3K 1 #else #define PY3K 0 #endif #ifndef MPL_DEVNULL #error "MPL_DEVNULL must be defined as the OS-equivalent of /dev/null" #endif #define STRINGIFY(x) STR(x) #define STR(x) #x static qhT qhData; static qhT* qh = &qhData; static const char* qhull_error_msg[6] = { "", /* 0 = qh_ERRnone */ "input inconsistency", /* 1 = qh_ERRinput */ "singular input data", /* 2 = qh_ERRsingular */ "precision error", /* 3 = qh_ERRprec */ "insufficient memory", /* 4 = qh_ERRmem */ "internal error"}; /* 5 = qh_ERRqhull */ /* Return the indices of the 3 vertices that comprise the specified facet (i.e. * triangle). */ static void get_facet_vertices(const facetT* facet, int indices[3]) { vertexT *vertex, **vertexp; FOREACHvertex_(facet->vertices) *indices++ = qh_pointid(qh, vertex->point); } /* Return the indices of the 3 triangles that are neighbors of the specified * facet (triangle). */ static void get_facet_neighbours(const facetT* facet, const int* tri_indices, int indices[3]) { facetT *neighbor, **neighborp; FOREACHneighbor_(facet) *indices++ = (neighbor->upperdelaunay ? -1 : tri_indices[neighbor->id]); } /* Return 1 if the specified points arrays contain at least 3 unique points, * or 0 otherwise. */ static int at_least_3_unique_points(int npoints, const double* x, const double* y) { int i; const int unique1 = 0; /* First unique point has index 0. */ int unique2 = 0; /* Second unique point index is 0 until set. */ if (npoints < 3) return 0; for (i = 1; i < npoints; ++i) { if (unique2 == 0) { /* Looking for second unique point. */ if (x[i] != x[unique1] || y[i] != y[unique1]) unique2 = i; } else { /* Looking for third unique point. */ if ( (x[i] != x[unique1] || y[i] != y[unique1]) && (x[i] != x[unique2] || y[i] != y[unique2]) ) { /* 3 unique points found, with indices 0, unique2 and i. */ return 1; } } } /* Run out of points before 3 unique points found. */ return 0; } /* Delaunay implementation methyod. If hide_qhull_errors is 1 then qhull error * messages are discarded; if it is 0 then they are written to stderr. */ static PyObject* delaunay_impl(int npoints, const double* x, const double* y, int hide_qhull_errors) { coordT* points = NULL; facetT* facet; int i, ntri, max_facet_id; FILE* error_file = NULL; /* qhull expects a FILE* to write errors to. */ int exitcode; /* Value returned from qh_new_qhull(). */ int* tri_indices = NULL; /* Maps qhull facet id to triangle index. */ int indices[3]; int curlong, totlong; /* Memory remaining after qh_memfreeshort. */ PyObject* tuple; /* Return tuple (triangles, neighbors). */ const int ndim = 2; npy_intp dims[2]; PyArrayObject* triangles = NULL; PyArrayObject* neighbors = NULL; int* triangles_ptr; int* neighbors_ptr; double x_mean = 0.0; double y_mean = 0.0; QHULL_LIB_CHECK /* Allocate points. */ points = (coordT*)malloc(npoints*ndim*sizeof(coordT)); if (points == NULL) { PyErr_SetString(PyExc_MemoryError, "Could not allocate points array in qhull.delaunay"); goto error_before_qhull; } /* Determine mean x, y coordinates. */ for (i = 0; i < npoints; ++i) { x_mean += x[i]; y_mean += y[i]; } x_mean /= npoints; y_mean /= npoints; /* Prepare points array to pass to qhull. */ for (i = 0; i < npoints; ++i) { points[2*i ] = x[i] - x_mean; points[2*i+1] = y[i] - y_mean; } /* qhull expects a FILE* to write errors to. */ if (hide_qhull_errors) { /* qhull errors are ignored by writing to OS-equivalent of /dev/null. * Rather than have OS-specific code here, instead it is determined by * setupext.py and passed in via the macro MPL_DEVNULL. */ error_file = fopen(STRINGIFY(MPL_DEVNULL), "w"); if (error_file == NULL) { PyErr_SetString(PyExc_RuntimeError, "Could not open devnull in qhull.delaunay"); goto error_before_qhull; } } else { /* qhull errors written to stderr. */ error_file = stderr; } /* Perform Delaunay triangulation. */ exitcode = qh_new_qhull(qh, ndim, npoints, points, False, "qhull d Qt Qbb Qc Qz", NULL, error_file); if (exitcode != qh_ERRnone) { PyErr_Format(PyExc_RuntimeError, "Error in qhull Delaunay triangulation calculation: %s (exitcode=%d)%s", qhull_error_msg[exitcode], exitcode, hide_qhull_errors ? "; use python verbose option (-v) to see original qhull error." : ""); goto error; } /* Split facets so that they only have 3 points each. */ qh_triangulate(qh); /* Determine ntri and max_facet_id. Note that libqhull uses macros to iterate through collections. */ ntri = 0; FORALLfacets { if (!facet->upperdelaunay) ++ntri; } max_facet_id = qh->facet_id - 1; /* Create array to map facet id to triangle index. */ tri_indices = (int*)malloc((max_facet_id+1)*sizeof(int)); if (tri_indices == NULL) { PyErr_SetString(PyExc_MemoryError, "Could not allocate triangle map in qhull.delaunay"); goto error; } /* Allocate python arrays to return. */ dims[0] = ntri; dims[1] = 3; triangles = (PyArrayObject*)PyArray_SimpleNew(ndim, dims, NPY_INT); if (triangles == NULL) { PyErr_SetString(PyExc_MemoryError, "Could not allocate triangles array in qhull.delaunay"); goto error; } neighbors = (PyArrayObject*)PyArray_SimpleNew(ndim, dims, NPY_INT); if (neighbors == NULL) { PyErr_SetString(PyExc_MemoryError, "Could not allocate neighbors array in qhull.delaunay"); goto error; } triangles_ptr = (int*)PyArray_DATA(triangles); neighbors_ptr = (int*)PyArray_DATA(neighbors); /* Determine triangles array and set tri_indices array. */ i = 0; FORALLfacets { if (!facet->upperdelaunay) { tri_indices[facet->id] = i++; get_facet_vertices(facet, indices); *triangles_ptr++ = (facet->toporient ? indices[0] : indices[2]); *triangles_ptr++ = indices[1]; *triangles_ptr++ = (facet->toporient ? indices[2] : indices[0]); } else tri_indices[facet->id] = -1; } /* Determine neighbors array. */ FORALLfacets { if (!facet->upperdelaunay) { get_facet_neighbours(facet, tri_indices, indices); *neighbors_ptr++ = (facet->toporient ? indices[2] : indices[0]); *neighbors_ptr++ = (facet->toporient ? indices[0] : indices[2]); *neighbors_ptr++ = indices[1]; } } /* Clean up. */ qh_freeqhull(qh, !qh_ALL); qh_memfreeshort(qh, &curlong, &totlong); if (curlong || totlong) PyErr_WarnEx(PyExc_RuntimeWarning, "Qhull could not free all allocated memory", 1); if (hide_qhull_errors) fclose(error_file); free(tri_indices); free(points); tuple = PyTuple_New(2); PyTuple_SetItem(tuple, 0, (PyObject*)triangles); PyTuple_SetItem(tuple, 1, (PyObject*)neighbors); return tuple; error: /* Clean up. */ Py_XDECREF(triangles); Py_XDECREF(neighbors); qh_freeqhull(qh, !qh_ALL); qh_memfreeshort(qh, &curlong, &totlong); /* Don't bother checking curlong and totlong as raising error anyway. */ if (hide_qhull_errors) fclose(error_file); free(tri_indices); error_before_qhull: free(points); return NULL; } /* Process python arguments and call Delaunay implementation method. */ static PyObject* delaunay(PyObject *self, PyObject *args) { PyObject* xarg; PyObject* yarg; PyArrayObject* xarray; PyArrayObject* yarray; PyObject* ret; int npoints; const double* x; const double* y; if (!PyArg_ParseTuple(args, "OO", &xarg, &yarg)) { PyErr_SetString(PyExc_ValueError, "expecting x and y arrays"); return NULL; } xarray = (PyArrayObject*)PyArray_ContiguousFromObject(xarg, NPY_DOUBLE, 1, 1); yarray = (PyArrayObject*)PyArray_ContiguousFromObject(yarg, NPY_DOUBLE, 1, 1); if (xarray == 0 || yarray == 0 || PyArray_DIM(xarray,0) != PyArray_DIM(yarray, 0)) { Py_XDECREF(xarray); Py_XDECREF(yarray); PyErr_SetString(PyExc_ValueError, "x and y must be 1D arrays of the same length"); return NULL; } npoints = PyArray_DIM(xarray, 0); if (npoints < 3) { Py_XDECREF(xarray); Py_XDECREF(yarray); PyErr_SetString(PyExc_ValueError, "x and y arrays must have a length of at least 3"); return NULL; } x = (const double*)PyArray_DATA(xarray); y = (const double*)PyArray_DATA(yarray); if (!at_least_3_unique_points(npoints, x, y)) { Py_XDECREF(xarray); Py_XDECREF(yarray); PyErr_SetString(PyExc_ValueError, "x and y arrays must consist of at least 3 unique points"); return NULL; } ret = delaunay_impl(npoints, x, y, Py_VerboseFlag == 0); Py_XDECREF(xarray); Py_XDECREF(yarray); return ret; } /* Return qhull version string for assistance in debugging. */ static PyObject* version(void) { return PyBytes_FromString(qh_version); } static PyMethodDef qhull_methods[] = { {"delaunay", (PyCFunction)delaunay, METH_VARARGS, ""}, {"version", (PyCFunction)version, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} }; #if PY3K static struct PyModuleDef qhull_module = { PyModuleDef_HEAD_INIT, "qhull", "Computing Delaunay triangulations.\n", -1, qhull_methods, NULL, NULL, NULL, NULL }; #define ERROR_RETURN return NULL PyMODINIT_FUNC PyInit__qhull(void) #else #define ERROR_RETURN return PyMODINIT_FUNC init_qhull(void) #endif { PyObject* m; #if PY3K m = PyModule_Create(&qhull_module); #else m = Py_InitModule3("_qhull", qhull_methods, "Computing Delaunay triangulations.\n"); #endif if (m == NULL) { ERROR_RETURN; } import_array(); #if PY3K return m; #endif }