7
7
*/
8
8
#define PY_SSIZE_T_CLEAN
9
9
#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
11
19
#include " libqhull_r/qhull_ra.h"
12
- #include <stdio.h>
20
+ #include < cstdio>
21
+ #include < vector>
13
22
14
23
15
24
#ifndef MPL_DEVNULL
@@ -35,88 +44,104 @@ static void
35
44
get_facet_vertices (qhT* qh, const facetT* facet, int indices[3 ])
36
45
{
37
46
vertexT *vertex, **vertexp;
38
- FOREACHvertex_ (facet -> vertices )
47
+ FOREACHvertex_ (facet->vertices ) {
39
48
*indices++ = qh_pointid (qh, vertex->point );
49
+ }
40
50
}
41
51
42
52
/* Return the indices of the 3 triangles that are neighbors of the specified
43
53
* facet (triangle). */
44
54
static void
45
- get_facet_neighbours (const facetT * facet , const int * tri_indices ,
55
+ get_facet_neighbours (const facetT* facet, std::vector< int >& tri_indices,
46
56
int indices[3 ])
47
57
{
48
58
facetT *neighbor, **neighborp;
49
- FOREACHneighbor_ (facet )
59
+ FOREACHneighbor_ (facet) {
50
60
*indices++ = (neighbor->upperdelaunay ? -1 : tri_indices[neighbor->id ]);
61
+ }
51
62
}
52
63
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)
57
68
{
58
69
int i;
59
70
const int unique1 = 0 ; /* First unique point has index 0. */
60
71
int unique2 = 0 ; /* Second unique point index is 0 until set. */
61
72
62
- if (npoints < 3 )
63
- return 0 ;
73
+ if (npoints < 3 ) {
74
+ return false ;
75
+ }
64
76
65
77
for (i = 1 ; i < npoints; ++i) {
66
78
if (unique2 == 0 ) {
67
79
/* 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]) {
69
81
unique2 = i;
82
+ }
70
83
}
71
84
else {
72
85
/* Looking for third unique point. */
73
86
if ( (x[i] != x[unique1] || y[i] != y[unique1]) &&
74
87
(x[i] != x[unique2] || y[i] != y[unique2]) ) {
75
88
/* 3 unique points found, with indices 0, unique2 and i. */
76
- return 1 ;
89
+ return true ;
77
90
}
78
91
}
79
92
}
80
93
81
94
/* Run out of points before 3 unique points found. */
82
- return 0 ;
95
+ return false ;
83
96
}
84
97
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. */
87
128
static 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)
90
131
{
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. */
94
134
facetT* facet;
95
135
int i, ntri, max_facet_id;
96
- FILE * error_file = NULL ; /* qhull expects a FILE* to write errors to. */
97
136
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). */
102
137
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 ;
108
138
double x_mean = 0.0 ;
109
139
double y_mean = 0.0 ;
110
140
111
141
QHULL_LIB_CHECK
112
142
113
143
/* 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);
120
145
121
146
/* Determine mean x, y coordinates. */
122
147
for (i = 0 ; i < npoints; ++i) {
@@ -133,15 +158,14 @@ delaunay_impl(int npoints, const double* x, const double* y,
133
158
}
134
159
135
160
/* qhull expects a FILE* to write errors to. */
161
+ FILE* error_file = NULL ;
136
162
if (hide_qhull_errors) {
137
163
/* qhull errors are ignored by writing to OS-equivalent of /dev/null.
138
164
* Rather than have OS-specific code here, instead it is determined by
139
165
* setupext.py and passed in via the macro MPL_DEVNULL. */
140
166
error_file = fopen (STRINGIFY (MPL_DEVNULL), " w" );
141
167
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" );
145
169
}
146
170
}
147
171
else {
@@ -150,15 +174,16 @@ delaunay_impl(int npoints, const double* x, const double* y,
150
174
}
151
175
152
176
/* Perform Delaunay triangulation. */
177
+ QhullInfo info (error_file, qh);
153
178
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);
156
181
if (exitcode != qh_ERRnone) {
157
182
PyErr_Format (PyExc_RuntimeError,
158
183
" Error in qhull Delaunay triangulation calculation: %s (exitcode=%d)%s" ,
159
184
qhull_error_msg[exitcode], exitcode,
160
185
hide_qhull_errors ? " ; use python verbose option (-v) to see original qhull error." : " " );
161
- goto error ;
186
+ return NULL ;
162
187
}
163
188
164
189
/* Split facets so that they only have 3 points each. */
@@ -168,153 +193,103 @@ delaunay_impl(int npoints, const double* x, const double* y,
168
193
Note that libqhull uses macros to iterate through collections. */
169
194
ntri = 0 ;
170
195
FORALLfacets {
171
- if (!facet -> upperdelaunay )
196
+ if (!facet->upperdelaunay ) {
172
197
++ntri;
198
+ }
173
199
}
174
200
175
201
max_facet_id = qh->facet_id - 1 ;
176
202
177
203
/* 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 );
184
205
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 ();
194
210
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 ();
204
213
205
214
/* Determine triangles array and set tri_indices array. */
206
215
i = 0 ;
207
216
FORALLfacets {
208
217
if (!facet->upperdelaunay ) {
218
+ int indices[3 ];
209
219
tri_indices[facet->id ] = i++;
210
220
get_facet_vertices (qh, facet, indices);
211
221
*triangles_ptr++ = (facet->toporient ? indices[0 ] : indices[2 ]);
212
222
*triangles_ptr++ = indices[1 ];
213
223
*triangles_ptr++ = (facet->toporient ? indices[2 ] : indices[0 ]);
214
224
}
215
- else
225
+ else {
216
226
tri_indices[facet->id ] = -1 ;
227
+ }
217
228
}
218
229
219
230
/* Determine neighbors array. */
220
231
FORALLfacets {
221
232
if (!facet->upperdelaunay ) {
233
+ int indices[3 ];
222
234
get_facet_neighbours (facet, tri_indices, indices);
223
235
*neighbors_ptr++ = (facet->toporient ? indices[2 ] : indices[0 ]);
224
236
*neighbors_ptr++ = (facet->toporient ? indices[0 ] : indices[2 ]);
225
237
*neighbors_ptr++ = indices[1 ];
226
238
}
227
239
}
228
240
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
+ }
244
245
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;
260
249
}
261
250
262
- /* Process python arguments and call Delaunay implementation method. */
251
+ /* Process Python arguments and call Delaunay implementation method. */
263
252
static PyObject*
264
253
delaunay (PyObject *self, PyObject *args)
265
254
{
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;
270
257
PyObject* ret;
271
- int npoints ;
258
+ npy_intp npoints;
272
259
const double * x;
273
260
const double * y;
274
261
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)) {
277
265
return NULL ;
278
266
}
279
267
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 )) {
288
270
PyErr_SetString (PyExc_ValueError,
289
271
" x and y must be 1D arrays of the same length" );
290
272
return NULL ;
291
273
}
292
274
293
- npoints = PyArray_DIM (xarray , 0 );
294
-
295
275
if (npoints < 3 ) {
296
- Py_XDECREF (xarray );
297
- Py_XDECREF (yarray );
298
276
PyErr_SetString (PyExc_ValueError,
299
277
" x and y arrays must have a length of at least 3" );
300
278
return NULL ;
301
279
}
302
280
303
- x = ( const double * ) PyArray_DATA ( xarray );
304
- y = ( const double * ) PyArray_DATA ( yarray );
281
+ x = xarray. data ( );
282
+ y = yarray. data ( );
305
283
306
284
if (!at_least_3_unique_points (npoints, x, y)) {
307
- Py_XDECREF (xarray );
308
- Py_XDECREF (yarray );
309
285
PyErr_SetString (PyExc_ValueError,
310
286
" x and y arrays must consist of at least 3 unique points" );
311
287
return NULL ;
312
288
}
313
289
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 )));
315
292
316
- Py_XDECREF (xarray );
317
- Py_XDECREF (yarray );
318
293
return ret;
319
294
}
320
295
0 commit comments