77 */
88#define PY_SSIZE_T_CLEAN
99#include " Python.h"
10- #include "numpy/ndarrayobject.h"
10+ #include " numpy_cpp.h"
11+ #ifdef _MSC_VER
12+ /* The Qhull header does not declare this as extern "C", but only MSVC seems to
13+ * do name mangling on global variables. We thus need to declare this before
14+ * the header so that it treats it correctly, and doesn't mangle the name. */
15+ extern " C" {
16+ extern const char qh_version[];
17+ }
18+ #endif
1119#include " libqhull_r/qhull_ra.h"
12- #include <stdio.h>
20+ #include < cstdio>
21+ #include < vector>
1322
1423
1524#ifndef MPL_DEVNULL
@@ -35,88 +44,104 @@ static void
3544get_facet_vertices (qhT* qh, const facetT* facet, int indices[3 ])
3645{
3746 vertexT *vertex, **vertexp;
38- FOREACHvertex_ (facet -> vertices )
47+ FOREACHvertex_ (facet->vertices ) {
3948 *indices++ = qh_pointid (qh, vertex->point );
49+ }
4050}
4151
4252/* Return the indices of the 3 triangles that are neighbors of the specified
4353 * facet (triangle). */
4454static void
45- get_facet_neighbours (const facetT * facet , const int * tri_indices ,
55+ get_facet_neighbours (const facetT* facet, std::vector< int >& tri_indices,
4656 int indices[3 ])
4757{
4858 facetT *neighbor, **neighborp;
49- FOREACHneighbor_ (facet )
59+ FOREACHneighbor_ (facet) {
5060 *indices++ = (neighbor->upperdelaunay ? -1 : tri_indices[neighbor->id ]);
61+ }
5162}
5263
53- /* Return 1 if the specified points arrays contain at least 3 unique points,
54- * or 0 otherwise. */
55- static int
56- at_least_3_unique_points (int npoints , const double * x , const double * y )
64+ /* Return true if the specified points arrays contain at least 3 unique points,
65+ * or false otherwise. */
66+ static bool
67+ at_least_3_unique_points (npy_intp npoints, const double * x, const double * y)
5768{
5869 int i;
5970 const int unique1 = 0 ; /* First unique point has index 0. */
6071 int unique2 = 0 ; /* Second unique point index is 0 until set. */
6172
62- if (npoints < 3 )
63- return 0 ;
73+ if (npoints < 3 ) {
74+ return false ;
75+ }
6476
6577 for (i = 1 ; i < npoints; ++i) {
6678 if (unique2 == 0 ) {
6779 /* Looking for second unique point. */
68- if (x [i ] != x [unique1 ] || y [i ] != y [unique1 ])
80+ if (x[i] != x[unique1] || y[i] != y[unique1]) {
6981 unique2 = i;
82+ }
7083 }
7184 else {
7285 /* Looking for third unique point. */
7386 if ( (x[i] != x[unique1] || y[i] != y[unique1]) &&
7487 (x[i] != x[unique2] || y[i] != y[unique2]) ) {
7588 /* 3 unique points found, with indices 0, unique2 and i. */
76- return 1 ;
89+ return true ;
7790 }
7891 }
7992 }
8093
8194 /* Run out of points before 3 unique points found. */
82- return 0 ;
95+ return false ;
8396}
8497
85- /* Delaunay implementation methyod. If hide_qhull_errors is 1 then qhull error
86- * messages are discarded; if it is 0 then they are written to stderr. */
98+ /* Holds on to info from Qhull so that it can be destructed automatically. */
99+ class QhullInfo {
100+ public:
101+ QhullInfo (FILE *error_file, qhT* qh) {
102+ this ->error_file = error_file;
103+ this ->qh = qh;
104+ }
105+
106+ ~QhullInfo () {
107+ qh_freeqhull (this ->qh , !qh_ALL);
108+ int curlong, totlong; /* Memory remaining. */
109+ qh_memfreeshort (this ->qh , &curlong, &totlong);
110+ if (curlong || totlong) {
111+ PyErr_WarnEx (PyExc_RuntimeWarning,
112+ " Qhull could not free all allocated memory" , 1 );
113+ }
114+
115+ if (this ->error_file != stderr) {
116+ fclose (error_file);
117+ }
118+ }
119+
120+ private:
121+ FILE* error_file;
122+ qhT* qh;
123+ };
124+
125+ /* Delaunay implementation method.
126+ * If hide_qhull_errors is true then qhull error messages are discarded;
127+ * if it is false then they are written to stderr. */
87128static PyObject*
88- delaunay_impl (int npoints , const double * x , const double * y ,
89- int hide_qhull_errors )
129+ delaunay_impl (npy_intp npoints, const double * x, const double * y,
130+ bool hide_qhull_errors)
90131{
91- qhT qh_qh ; /* qh variable type and name must be like */
92- qhT * qh = & qh_qh ; /* this for Qhull macros to work correctly. */
93- coordT * points = NULL ;
132+ qhT qh_qh; /* qh variable type and name must be like */
133+ qhT* qh = &qh_qh; /* this for Qhull macros to work correctly. */
94134 facetT* facet;
95135 int i, ntri, max_facet_id;
96- FILE * error_file = NULL ; /* qhull expects a FILE* to write errors to. */
97136 int exitcode; /* Value returned from qh_new_qhull(). */
98- int * tri_indices = NULL ; /* Maps qhull facet id to triangle index. */
99- int indices [3 ];
100- int curlong , totlong ; /* Memory remaining after qh_memfreeshort. */
101- PyObject * tuple ; /* Return tuple (triangles, neighbors). */
102137 const int ndim = 2 ;
103- npy_intp dims [2 ];
104- PyArrayObject * triangles = NULL ;
105- PyArrayObject * neighbors = NULL ;
106- int * triangles_ptr ;
107- int * neighbors_ptr ;
108138 double x_mean = 0.0 ;
109139 double y_mean = 0.0 ;
110140
111141 QHULL_LIB_CHECK
112142
113143 /* Allocate points. */
114- points = (coordT * )malloc (npoints * ndim * sizeof (coordT ));
115- if (points == NULL ) {
116- PyErr_SetString (PyExc_MemoryError ,
117- "Could not allocate points array in qhull.delaunay" );
118- goto error_before_qhull ;
119- }
144+ std::vector<coordT> points (npoints * ndim);
120145
121146 /* Determine mean x, y coordinates. */
122147 for (i = 0 ; i < npoints; ++i) {
@@ -133,15 +158,14 @@ delaunay_impl(int npoints, const double* x, const double* y,
133158 }
134159
135160 /* qhull expects a FILE* to write errors to. */
161+ FILE* error_file = NULL ;
136162 if (hide_qhull_errors) {
137163 /* qhull errors are ignored by writing to OS-equivalent of /dev/null.
138164 * Rather than have OS-specific code here, instead it is determined by
139165 * setupext.py and passed in via the macro MPL_DEVNULL. */
140166 error_file = fopen (STRINGIFY (MPL_DEVNULL), " w" );
141167 if (error_file == NULL ) {
142- PyErr_SetString (PyExc_RuntimeError ,
143- "Could not open devnull in qhull.delaunay" );
144- goto error_before_qhull ;
168+ throw std::runtime_error (" Could not open devnull" );
145169 }
146170 }
147171 else {
@@ -150,15 +174,16 @@ delaunay_impl(int npoints, const double* x, const double* y,
150174 }
151175
152176 /* Perform Delaunay triangulation. */
177+ QhullInfo info (error_file, qh);
153178 qh_zero (qh, error_file);
154- exitcode = qh_new_qhull (qh , ndim , npoints , points , False ,
155- "qhull d Qt Qbb Qc Qz" , NULL , error_file );
179+ exitcode = qh_new_qhull (qh, ndim, ( int ) npoints, points. data () , False,
180+ ( char *) " qhull d Qt Qbb Qc Qz" , NULL , error_file);
156181 if (exitcode != qh_ERRnone) {
157182 PyErr_Format (PyExc_RuntimeError,
158183 " Error in qhull Delaunay triangulation calculation: %s (exitcode=%d)%s" ,
159184 qhull_error_msg[exitcode], exitcode,
160185 hide_qhull_errors ? " ; use python verbose option (-v) to see original qhull error." : " " );
161- goto error ;
186+ return NULL ;
162187 }
163188
164189 /* Split facets so that they only have 3 points each. */
@@ -168,153 +193,103 @@ delaunay_impl(int npoints, const double* x, const double* y,
168193 Note that libqhull uses macros to iterate through collections. */
169194 ntri = 0 ;
170195 FORALLfacets {
171- if (!facet -> upperdelaunay )
196+ if (!facet->upperdelaunay ) {
172197 ++ntri;
198+ }
173199 }
174200
175201 max_facet_id = qh->facet_id - 1 ;
176202
177203 /* Create array to map facet id to triangle index. */
178- tri_indices = (int * )malloc ((max_facet_id + 1 )* sizeof (int ));
179- if (tri_indices == NULL ) {
180- PyErr_SetString (PyExc_MemoryError ,
181- "Could not allocate triangle map in qhull.delaunay" );
182- goto error ;
183- }
204+ std::vector<int > tri_indices (max_facet_id+1 );
184205
185- /* Allocate python arrays to return. */
186- dims [0 ] = ntri ;
187- dims [1 ] = 3 ;
188- triangles = (PyArrayObject * )PyArray_SimpleNew (ndim , dims , NPY_INT );
189- if (triangles == NULL ) {
190- PyErr_SetString (PyExc_MemoryError ,
191- "Could not allocate triangles array in qhull.delaunay" );
192- goto error ;
193- }
206+ /* 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 ();
194210
195- neighbors = (PyArrayObject * )PyArray_SimpleNew (ndim , dims , NPY_INT );
196- if (neighbors == NULL ) {
197- PyErr_SetString (PyExc_MemoryError ,
198- "Could not allocate neighbors array in qhull.delaunay" );
199- goto error ;
200- }
201-
202- triangles_ptr = (int * )PyArray_DATA (triangles );
203- neighbors_ptr = (int * )PyArray_DATA (neighbors );
211+ numpy::array_view<int , ndim> neighbors (dims);
212+ int * neighbors_ptr = neighbors.data ();
204213
205214 /* Determine triangles array and set tri_indices array. */
206215 i = 0 ;
207216 FORALLfacets {
208217 if (!facet->upperdelaunay ) {
218+ int indices[3 ];
209219 tri_indices[facet->id ] = i++;
210220 get_facet_vertices (qh, facet, indices);
211221 *triangles_ptr++ = (facet->toporient ? indices[0 ] : indices[2 ]);
212222 *triangles_ptr++ = indices[1 ];
213223 *triangles_ptr++ = (facet->toporient ? indices[2 ] : indices[0 ]);
214224 }
215- else
225+ else {
216226 tri_indices[facet->id ] = -1 ;
227+ }
217228 }
218229
219230 /* Determine neighbors array. */
220231 FORALLfacets {
221232 if (!facet->upperdelaunay ) {
233+ int indices[3 ];
222234 get_facet_neighbours (facet, tri_indices, indices);
223235 *neighbors_ptr++ = (facet->toporient ? indices[2 ] : indices[0 ]);
224236 *neighbors_ptr++ = (facet->toporient ? indices[0 ] : indices[2 ]);
225237 *neighbors_ptr++ = indices[1 ];
226238 }
227239 }
228240
229- /* Clean up. */
230- qh_freeqhull (qh , !qh_ALL );
231- qh_memfreeshort (qh , & curlong , & totlong );
232- if (curlong || totlong )
233- PyErr_WarnEx (PyExc_RuntimeWarning ,
234- "Qhull could not free all allocated memory" , 1 );
235- if (hide_qhull_errors )
236- fclose (error_file );
237- free (tri_indices );
238- free (points );
239-
240- tuple = PyTuple_New (2 );
241- PyTuple_SetItem (tuple , 0 , (PyObject * )triangles );
242- PyTuple_SetItem (tuple , 1 , (PyObject * )neighbors );
243- return tuple ;
241+ PyObject* tuple = PyTuple_New (2 );
242+ if (tuple == 0 ) {
243+ throw std::runtime_error (" Failed to create Python tuple" );
244+ }
244245
245- error :
246- /* Clean up. */
247- Py_XDECREF (triangles );
248- Py_XDECREF (neighbors );
249- qh_freeqhull (qh , !qh_ALL );
250- qh_memfreeshort (qh , & curlong , & totlong );
251- /* Don't bother checking curlong and totlong as raising error anyway. */
252- if (hide_qhull_errors )
253- fclose (error_file );
254- free (tri_indices );
255-
256- error_before_qhull :
257- free (points );
258-
259- return NULL ;
246+ PyTuple_SET_ITEM (tuple, 0 , triangles.pyobj ());
247+ PyTuple_SET_ITEM (tuple, 1 , neighbors.pyobj ());
248+ return tuple;
260249}
261250
262- /* Process python arguments and call Delaunay implementation method. */
251+ /* Process Python arguments and call Delaunay implementation method. */
263252static PyObject*
264253delaunay (PyObject *self, PyObject *args)
265254{
266- PyObject * xarg ;
267- PyObject * yarg ;
268- PyArrayObject * xarray ;
269- PyArrayObject * yarray ;
255+ numpy::array_view<double , 1 > xarray;
256+ numpy::array_view<double , 1 > yarray;
270257 PyObject* ret;
271- int npoints ;
258+ npy_intp npoints;
272259 const double * x;
273260 const double * y;
274261
275- if (!PyArg_ParseTuple (args , "OO" , & xarg , & yarg )) {
276- PyErr_SetString (PyExc_ValueError , "expecting x and y arrays" );
262+ if (!PyArg_ParseTuple (args, " O&O&" ,
263+ &xarray.converter_contiguous , &xarray,
264+ &yarray.converter_contiguous , &yarray)) {
277265 return NULL ;
278266 }
279267
280- xarray = (PyArrayObject * )PyArray_ContiguousFromObject (xarg , NPY_DOUBLE ,
281- 1 , 1 );
282- yarray = (PyArrayObject * )PyArray_ContiguousFromObject (yarg , NPY_DOUBLE ,
283- 1 , 1 );
284- if (xarray == 0 || yarray == 0 ||
285- PyArray_DIM (xarray ,0 ) != PyArray_DIM (yarray , 0 )) {
286- Py_XDECREF (xarray );
287- Py_XDECREF (yarray );
268+ npoints = xarray.dim (0 );
269+ if (npoints != yarray.dim (0 )) {
288270 PyErr_SetString (PyExc_ValueError,
289271 " x and y must be 1D arrays of the same length" );
290272 return NULL ;
291273 }
292274
293- npoints = PyArray_DIM (xarray , 0 );
294-
295275 if (npoints < 3 ) {
296- Py_XDECREF (xarray );
297- Py_XDECREF (yarray );
298276 PyErr_SetString (PyExc_ValueError,
299277 " x and y arrays must have a length of at least 3" );
300278 return NULL ;
301279 }
302280
303- x = ( const double * ) PyArray_DATA ( xarray );
304- y = ( const double * ) PyArray_DATA ( yarray );
281+ x = xarray. data ( );
282+ y = yarray. data ( );
305283
306284 if (!at_least_3_unique_points (npoints, x, y)) {
307- Py_XDECREF (xarray );
308- Py_XDECREF (yarray );
309285 PyErr_SetString (PyExc_ValueError,
310286 " x and y arrays must consist of at least 3 unique points" );
311287 return NULL ;
312288 }
313289
314- ret = delaunay_impl (npoints , x , y , Py_VerboseFlag == 0 );
290+ CALL_CPP (" qhull.delaunay" ,
291+ (ret = delaunay_impl (npoints, x, y, Py_VerboseFlag == 0 )));
315292
316- Py_XDECREF (xarray );
317- Py_XDECREF (yarray );
318293 return ret;
319294}
320295
0 commit comments