55 * triangulation, construct an instance of the matplotlib.tri.Triangulation
66 * class without specifying a triangles array.
77 */
8- #define PY_SSIZE_T_CLEAN
9- #include " Python.h "
10- # include " numpy_cpp.h "
8+ #include < pybind11/pybind11.h >
9+ #include < pybind11/numpy.h >
10+
1111#ifdef _MSC_VER
1212/* The Qhull header does not declare this as extern "C", but only MSVC seems to
1313 * do name mangling on global variables. We thus need to declare this before
@@ -16,18 +16,28 @@ extern "C" {
1616extern const char qh_version[];
1717}
1818#endif
19+
1920#include " libqhull_r/qhull_ra.h"
2021#include < cstdio>
2122#include < vector>
2223
23-
2424#ifndef MPL_DEVNULL
2525#error "MPL_DEVNULL must be defined as the OS-equivalent of /dev/null"
2626#endif
2727
2828#define STRINGIFY (x ) STR(x)
2929#define STR (x ) #x
3030
31+ namespace py = pybind11;
32+ using namespace pybind11 ::literals;
33+
34+ // Input numpy array class.
35+ typedef py::array_t <double , py::array::c_style | py::array::forcecast> CoordArray;
36+
37+ // Output numpy array class.
38+ typedef py::array_t <int > IndexArray;
39+
40+
3141
3242static const char * qhull_error_msg[6 ] = {
3343 " " , /* 0 = qh_ERRnone */
@@ -64,17 +74,16 @@ get_facet_neighbours(const facetT* facet, std::vector<int>& tri_indices,
6474/* Return true if the specified points arrays contain at least 3 unique points,
6575 * or false otherwise. */
6676static bool
67- at_least_3_unique_points (npy_intp npoints, const double * x, const double * y)
77+ at_least_3_unique_points (py:: ssize_t npoints, const double * x, const double * y)
6878{
69- int i;
70- const int unique1 = 0 ; /* First unique point has index 0. */
71- int unique2 = 0 ; /* Second unique point index is 0 until set. */
79+ const py::ssize_t unique1 = 0 ; /* First unique point has index 0. */
80+ py::ssize_t unique2 = 0 ; /* Second unique point index is 0 until set. */
7281
7382 if (npoints < 3 ) {
7483 return false ;
7584 }
7685
77- for (i = 1 ; i < npoints; ++i) {
86+ for (py:: ssize_t i = 1 ; i < npoints; ++i) {
7887 if (unique2 == 0 ) {
7988 /* Looking for second unique point. */
8089 if (x[i] != x[unique1] || y[i] != y[unique1]) {
@@ -125,8 +134,8 @@ class QhullInfo {
125134/* Delaunay implementation method.
126135 * If hide_qhull_errors is true then qhull error messages are discarded;
127136 * if it is false then they are written to stderr. */
128- static PyObject*
129- delaunay_impl (npy_intp npoints, const double * x, const double * y,
137+ static py::tuple
138+ delaunay_impl (py:: ssize_t npoints, const double * x, const double * y,
130139 bool hide_qhull_errors)
131140{
132141 qhT qh_qh; /* qh variable type and name must be like */
@@ -179,11 +188,13 @@ delaunay_impl(npy_intp npoints, const double* x, const double* y,
179188 exitcode = qh_new_qhull (qh, ndim, (int )npoints, points.data (), False,
180189 (char *)" qhull d Qt Qbb Qc Qz" , NULL , error_file);
181190 if (exitcode != qh_ERRnone) {
182- PyErr_Format (PyExc_RuntimeError,
183- " Error in qhull Delaunay triangulation calculation: %s (exitcode=%d)%s" ,
184- qhull_error_msg[exitcode], exitcode,
185- hide_qhull_errors ? " ; use python verbose option (-v) to see original qhull error." : " " );
186- return NULL ;
191+ std::string msg =
192+ py::str (" Error in qhull Delaunay triangulation calculation: {} (exitcode={})" )
193+ .format (qhull_error_msg[exitcode], exitcode).cast <std::string>();
194+ if (hide_qhull_errors) {
195+ msg += " ; use python verbose option (-v) to see original qhull error." ;
196+ }
197+ throw std::runtime_error (msg);
187198 }
188199
189200 /* Split facets so that they only have 3 points each. */
@@ -204,12 +215,12 @@ delaunay_impl(npy_intp npoints, const double* x, const double* y,
204215 std::vector<int > tri_indices (max_facet_id+1 );
205216
206217 /* Allocate Python arrays to return. */
207- npy_intp dims[2 ] = {ntri, 3 };
208- numpy::array_view< int , ndim> triangles (dims);
209- int * triangles_ptr = triangles.data ();
218+ int dims[2 ] = {ntri, 3 };
219+ IndexArray triangles (dims);
220+ int * triangles_ptr = triangles.mutable_data ();
210221
211- numpy::array_view< int , ndim> neighbors (dims);
212- int * neighbors_ptr = neighbors.data ();
222+ IndexArray neighbors (dims);
223+ int * neighbors_ptr = neighbors.mutable_data ();
213224
214225 /* Determine triangles array and set tri_indices array. */
215226 i = 0 ;
@@ -238,103 +249,54 @@ delaunay_impl(npy_intp npoints, const double* x, const double* y,
238249 }
239250 }
240251
241- PyObject* tuple = PyTuple_New (2 );
242- if (tuple == 0 ) {
243- throw std::runtime_error (" Failed to create Python tuple" );
244- }
245-
246- PyTuple_SET_ITEM (tuple, 0 , triangles.pyobj ());
247- PyTuple_SET_ITEM (tuple, 1 , neighbors.pyobj ());
248- return tuple;
252+ return py::make_tuple (triangles, neighbors);
249253}
250254
251255/* Process Python arguments and call Delaunay implementation method. */
252- static PyObject*
253- delaunay (PyObject *self, PyObject *args )
256+ static py::tuple
257+ delaunay (const CoordArray& x, const CoordArray& y, int verbose )
254258{
255- numpy::array_view<double , 1 > xarray;
256- numpy::array_view<double , 1 > yarray;
257- PyObject* ret;
258- npy_intp npoints;
259- const double * x;
260- const double * y;
261- int verbose = 0 ;
262-
263- if (!PyArg_ParseTuple (args, " O&O&i:delaunay" ,
264- &xarray.converter_contiguous , &xarray,
265- &yarray.converter_contiguous , &yarray,
266- &verbose)) {
267- return NULL ;
259+ if (x.ndim () != 1 || y.ndim () != 1 ) {
260+ throw std::invalid_argument (" x and y must be 1D arrays" );
268261 }
269262
270- npoints = xarray.shape (0 );
271- if (npoints != yarray.shape (0 )) {
272- PyErr_SetString (PyExc_ValueError,
273- " x and y must be 1D arrays of the same length" );
274- return NULL ;
263+ auto npoints = x.shape (0 );
264+ if (npoints != y.shape (0 )) {
265+ throw std::invalid_argument (" x and y must be 1D arrays of the same length" );
275266 }
276267
277268 if (npoints < 3 ) {
278- PyErr_SetString (PyExc_ValueError,
279- " x and y arrays must have a length of at least 3" );
280- return NULL ;
269+ throw std::invalid_argument (" x and y arrays must have a length of at least 3" );
281270 }
282271
283- x = xarray.data ();
284- y = yarray.data ();
285-
286- if (!at_least_3_unique_points (npoints, x, y)) {
287- PyErr_SetString (PyExc_ValueError,
288- " x and y arrays must consist of at least 3 unique points" );
289- return NULL ;
272+ if (!at_least_3_unique_points (npoints, x.data (), y.data ())) {
273+ throw std::invalid_argument (" x and y arrays must consist of at least 3 unique points" );
290274 }
291275
292- CALL_CPP (" qhull.delaunay" ,
293- (ret = delaunay_impl (npoints, x, y, verbose == 0 )));
294-
295- return ret;
296- }
297-
298- /* Return qhull version string for assistance in debugging. */
299- static PyObject*
300- version (PyObject *self, PyObject *arg)
301- {
302- return PyBytes_FromString (qh_version);
276+ return delaunay_impl (npoints, x.data (), y.data (), verbose == 0 );
303277}
304278
305- static PyMethodDef qhull_methods[] = {
306- {" delaunay" , delaunay, METH_VARARGS,
307- " delaunay(x, y, verbose, /)\n "
308- " --\n\n "
309- " Compute a Delaunay triangulation.\n "
310- " \n "
311- " Parameters\n "
312- " ----------\n "
313- " x, y : 1d arrays\n "
314- " The coordinates of the point set, which must consist of at least\n "
315- " three unique points.\n "
316- " verbose : int\n "
317- " Python's verbosity level.\n "
318- " \n "
319- " Returns\n "
320- " -------\n "
321- " triangles, neighbors : int arrays, shape (ntri, 3)\n "
322- " Indices of triangle vertices and indices of triangle neighbors.\n "
323- },
324- {" version" , version, METH_NOARGS,
325- " version()\n --\n\n "
326- " Return the qhull version string." },
327- {NULL , NULL , 0 , NULL }
328- };
329-
330- static struct PyModuleDef qhull_module = {
331- PyModuleDef_HEAD_INIT,
332- " qhull" , " Computing Delaunay triangulations.\n " , -1 , qhull_methods
333- };
334-
335- PyMODINIT_FUNC
336- PyInit__qhull (void )
337- {
338- import_array ();
339- return PyModule_Create (&qhull_module);
279+ PYBIND11_MODULE (_qhull, m) {
280+ m.doc () = " Computing Delaunay triangulations.\n " ;
281+
282+ m.def (" delaunay" , &delaunay, " x" _a, " y" _a, " verbose" _a,
283+ " --\n\n "
284+ " Compute a Delaunay triangulation.\n "
285+ " \n "
286+ " Parameters\n "
287+ " ----------\n "
288+ " x, y : 1d arrays\n "
289+ " The coordinates of the point set, which must consist of at least\n "
290+ " three unique points.\n "
291+ " verbose : int\n "
292+ " Python's verbosity level.\n "
293+ " \n "
294+ " Returns\n "
295+ " -------\n "
296+ " triangles, neighbors : int arrays, shape (ntri, 3)\n "
297+ " Indices of triangle vertices and indices of triangle neighbors.\n " );
298+
299+ m.def (" version" , []() { return qh_version; },
300+ " version()\n --\n\n "
301+ " Return the qhull version string." );
340302}
0 commit comments