Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cf332ae

Browse files
authored
Merge pull request #19951 from QuLogic/qhull-cpp
Convert Qhull wrapper to C++ and array_view
2 parents 96ff114 + b60f072 commit cf332ae

File tree

2 files changed

+102
-127
lines changed

2 files changed

+102
-127
lines changed

setupext.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ def get_extensions(self):
432432
yield ext
433433
# qhull
434434
ext = Extension(
435-
"matplotlib._qhull", ["src/qhull_wrap.c"],
435+
"matplotlib._qhull", ["src/qhull_wrap.cpp"],
436436
define_macros=[("MPL_DEVNULL", os.devnull)])
437437
add_numpy_flags(ext)
438438
Qhull.add_flags(ext)

src/qhull_wrap.c renamed to src/qhull_wrap.cpp

Lines changed: 101 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,18 @@
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
3544
get_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). */
4454
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,
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. */
87128
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)
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. */
263252
static PyObject*
264253
delaunay(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

Comments
 (0)