/*
* 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_cpp.h"
#ifdef _MSC_VER
/* The Qhull header does not declare this as extern "C", but only MSVC seems to
* do name mangling on global variables. We thus need to declare this before
* the header so that it treats it correctly, and doesn't mangle the name. */
extern "C" {
extern const char qh_version[];
}
#endif
#include "libqhull_r/qhull_ra.h"
#include <cstdio>
#include <vector>
#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 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(qhT* qh, 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, std::vector<int>& tri_indices,
int indices[3])
{
facetT *neighbor, **neighborp;
FOREACHneighbor_(facet) {
*indices++ = (neighbor->upperdelaunay ? -1 : tri_indices[neighbor->id]);
}
}
/* Return true if the specified points arrays contain at least 3 unique points,
* or false otherwise. */
static bool
at_least_3_unique_points(npy_intp 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 false;
}
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 true;
}
}
}
/* Run out of points before 3 unique points found. */
return false;
}
/* Holds on to info from Qhull so that it can be destructed automatically. */
class QhullInfo {
public:
QhullInfo(FILE *error_file, qhT* qh) {
this->error_file = error_file;
this->qh = qh;
}
~QhullInfo() {
qh_freeqhull(this->qh, !qh_ALL);
int curlong, totlong; /* Memory remaining. */
qh_memfreeshort(this->qh, &curlong, &totlong);
if (curlong || totlong) {
PyErr_WarnEx(PyExc_RuntimeWarning,
"Qhull could not free all allocated memory", 1);
}
if (this->error_file != stderr) {
fclose(error_file);
}
}
private:
FILE* error_file;
qhT* qh;
};
/* Delaunay implementation method.
* If hide_qhull_errors is true then qhull error messages are discarded;
* if it is false then they are written to stderr. */
static PyObject*
delaunay_impl(npy_intp npoints, const double* x, const double* y,
bool hide_qhull_errors)
{
qhT qh_qh; /* qh variable type and name must be like */
qhT* qh = &qh_qh; /* this for Qhull macros to work correctly. */
facetT* facet;
int i, ntri, max_facet_id;
int exitcode; /* Value returned from qh_new_qhull(). */
const int ndim = 2;
double x_mean = 0.0;
double y_mean = 0.0;
QHULL_LIB_CHECK
/* Allocate points. */
std::vector<coordT> points(npoints * ndim);
/* 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. */
FILE* error_file = NULL;
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) {
throw std::runtime_error("Could not open devnull");
}
}
else {
/* qhull errors written to stderr. */
error_file = stderr;
}
/* Perform Delaunay triangulation. */
QhullInfo info(error_file, qh);
qh_zero(qh, error_file);
exitcode = qh_new_qhull(qh, ndim, (int)npoints, points.data(), False,
(char*)"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." : "");
return NULL;
}
/* 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. */
std::vector<int> tri_indices(max_facet_id+1);
/* Allocate Python arrays to return. */
npy_intp dims[2] = {ntri, 3};
numpy::array_view<int, ndim> triangles(dims);
int* triangles_ptr = triangles.data();
numpy::array_view<int, ndim> neighbors(dims);
int* neighbors_ptr = neighbors.data();
/* Determine triangles array and set tri_indices array. */
i = 0;
FORALLfacets {
if (!facet->upperdelaunay) {
int indices[3];
tri_indices[facet->id] = i++;
get_facet_vertices(qh, 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) {
int indices[3];
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];
}
}
PyObject* tuple = PyTuple_New(2);
if (tuple == 0) {
throw std::runtime_error("Failed to create Python tuple");
}
PyTuple_SET_ITEM(tuple, 0, triangles.pyobj());
PyTuple_SET_ITEM(tuple, 1, neighbors.pyobj());
return tuple;
}
/* Process Python arguments and call Delaunay implementation method. */
static PyObject*
delaunay(PyObject *self, PyObject *args)
{
numpy::array_view<double, 1> xarray;
numpy::array_view<double, 1> yarray;
PyObject* ret;
npy_intp npoints;
const double* x;
const double* y;
int verbose = 0;
if (!PyArg_ParseTuple(args, "O&O&i:delaunay",
&xarray.converter_contiguous, &xarray,
&yarray.converter_contiguous, &yarray,
&verbose)) {
return NULL;
}
npoints = xarray.dim(0);
if (npoints != yarray.dim(0)) {
PyErr_SetString(PyExc_ValueError,
"x and y must be 1D arrays of the same length");
return NULL;
}
if (npoints < 3) {
PyErr_SetString(PyExc_ValueError,
"x and y arrays must have a length of at least 3");
return NULL;
}
x = xarray.data();
y = yarray.data();
if (!at_least_3_unique_points(npoints, x, y)) {
PyErr_SetString(PyExc_ValueError,
"x and y arrays must consist of at least 3 unique points");
return NULL;
}
CALL_CPP("qhull.delaunay",
(ret = delaunay_impl(npoints, x, y, verbose == 0)));
return ret;
}
/* Return qhull version string for assistance in debugging. */
static PyObject*
version(PyObject *self, PyObject *arg)
{
return PyBytes_FromString(qh_version);
}
static PyMethodDef qhull_methods[] = {
{"delaunay", delaunay, METH_VARARGS,
"delaunay(x, y, verbose, /)\n"
"--\n\n"
"Compute a Delaunay triangulation.\n"
"\n"
"Parameters\n"
"----------\n"
"x, y : 1d arrays\n"
" The coordinates of the point set, which must consist of at least\n"
" three unique points.\n"
"verbose : int\n"
" Python's verbosity level.\n"
"\n"
"Returns\n"
"-------\n"
"triangles, neighbors : int arrays, shape (ntri, 3)\n"
" Indices of triangle vertices and indices of triangle neighbors.\n"
},
{"version", version, METH_NOARGS,
"version()\n--\n\n"
"Return the qhull version string."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef qhull_module = {
PyModuleDef_HEAD_INIT,
"qhull", "Computing Delaunay triangulations.\n", -1, qhull_methods
};
PyMODINIT_FUNC
PyInit__qhull(void)
{
import_array();
return PyModule_Create(&qhull_module);
}