From 5e29a2ad253e0fa9945377576e404b2f1705a712 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sun, 20 Nov 2022 16:27:26 +0000 Subject: [PATCH 1/6] Use pybind11 for tri module --- lib/matplotlib/tests/test_triangulation.py | 23 +- lib/matplotlib/tri/_triangulation.py | 8 +- pyproject.toml | 1 + setupext.py | 7 +- src/tri/_tri.cpp | 284 ++++++----- src/tri/_tri.h | 44 +- src/tri/_tri_wrapper.cpp | 562 ++------------------- 7 files changed, 260 insertions(+), 669 deletions(-) diff --git a/lib/matplotlib/tests/test_triangulation.py b/lib/matplotlib/tests/test_triangulation.py index 9c6dec71e09f..8317c8bfc995 100644 --- a/lib/matplotlib/tests/test_triangulation.py +++ b/lib/matplotlib/tests/test_triangulation.py @@ -1166,43 +1166,43 @@ def test_internal_cpp_api(): # C++ Triangulation. with pytest.raises( TypeError, - match=r'function takes exactly 7 arguments \(0 given\)'): + match=r'__init__\(\): incompatible constructor arguments.'): mpl._tri.Triangulation() with pytest.raises( ValueError, match=r'x and y must be 1D arrays of the same length'): - mpl._tri.Triangulation([], [1], [[]], None, None, None, False) + mpl._tri.Triangulation([], [1], [[]], (), (), (), False) x = [0, 1, 1] y = [0, 0, 1] with pytest.raises( ValueError, match=r'triangles must be a 2D array of shape \(\?,3\)'): - mpl._tri.Triangulation(x, y, [[0, 1]], None, None, None, False) + mpl._tri.Triangulation(x, y, [[0, 1]], (), (), (), False) tris = [[0, 1, 2]] with pytest.raises( ValueError, match=r'mask must be a 1D array with the same length as the ' r'triangles array'): - mpl._tri.Triangulation(x, y, tris, [0, 1], None, None, False) + mpl._tri.Triangulation(x, y, tris, [0, 1], (), (), False) with pytest.raises( ValueError, match=r'edges must be a 2D array with shape \(\?,2\)'): - mpl._tri.Triangulation(x, y, tris, None, [[1]], None, False) + mpl._tri.Triangulation(x, y, tris, (), [[1]], (), False) with pytest.raises( ValueError, match=r'neighbors must be a 2D array with the same shape as the ' r'triangles array'): - mpl._tri.Triangulation(x, y, tris, None, None, [[-1]], False) + mpl._tri.Triangulation(x, y, tris, (), (), [[-1]], False) - triang = mpl._tri.Triangulation(x, y, tris, None, None, None, False) + triang = mpl._tri.Triangulation(x, y, tris, (), (), (), False) with pytest.raises( ValueError, - match=r'z array must have same length as triangulation x and y ' - r'array'): + match=r'z must be a 1D array with the same length as the ' + r'triangulation x and y arrays'): triang.calculate_plane_coefficients([]) with pytest.raises( @@ -1214,7 +1214,7 @@ def test_internal_cpp_api(): # C++ TriContourGenerator. with pytest.raises( TypeError, - match=r'function takes exactly 2 arguments \(0 given\)'): + match=r'__init__\(\): incompatible constructor arguments.'): mpl._tri.TriContourGenerator() with pytest.raises( @@ -1232,7 +1232,8 @@ def test_internal_cpp_api(): # C++ TrapezoidMapTriFinder. with pytest.raises( - TypeError, match=r'function takes exactly 1 argument \(0 given\)'): + TypeError, + match=r'__init__\(\): incompatible constructor arguments.'): mpl._tri.TrapezoidMapTriFinder() trifinder = mpl._tri.TrapezoidMapTriFinder(triang) diff --git a/lib/matplotlib/tri/_triangulation.py b/lib/matplotlib/tri/_triangulation.py index c123cca4c9e0..8824e0385cf3 100644 --- a/lib/matplotlib/tri/_triangulation.py +++ b/lib/matplotlib/tri/_triangulation.py @@ -120,8 +120,12 @@ def get_cpp_triangulation(self): from matplotlib import _tri if self._cpp_triangulation is None: self._cpp_triangulation = _tri.Triangulation( - self.x, self.y, self.triangles, self.mask, self._edges, - self._neighbors, not self.is_delaunay) + # For unset arrays use empty tuple which has size of zero. + self.x, self.y, self.triangles, + self.mask if self.mask is not None else (), + self._edges if self._edges is not None else (), + self._neighbors if self._neighbors is not None else (), + not self.is_delaunay) return self._cpp_triangulation def get_masked_triangles(self): diff --git a/pyproject.toml b/pyproject.toml index bbd0c8baf2b3..907b05a39ba4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,5 +3,6 @@ build-backend = "setuptools.build_meta" requires = [ "certifi>=2020.06.20", "oldest-supported-numpy", + "pybind11>=2.6", "setuptools_scm>=7", ] diff --git a/setupext.py b/setupext.py index 3060718b254d..4d41bdd44a89 100644 --- a/setupext.py +++ b/setupext.py @@ -16,6 +16,7 @@ import textwrap import urllib.request +from pybind11.setup_helpers import Pybind11Extension from setuptools import Distribution, Extension _log = logging.getLogger(__name__) @@ -459,12 +460,12 @@ def get_extensions(self): add_libagg_flags(ext) yield ext # tri - ext = Extension( + ext = Pybind11Extension( "matplotlib._tri", [ "src/tri/_tri.cpp", "src/tri/_tri_wrapper.cpp", - ]) - add_numpy_flags(ext) + ], + cxx_std=11) yield ext # ttconv ext = Extension( diff --git a/src/tri/_tri.cpp b/src/tri/_tri.cpp index 80a14201645b..548e65b3e52d 100644 --- a/src/tri/_tri.cpp +++ b/src/tri/_tri.cpp @@ -5,14 +5,12 @@ * undef_macros=['NDEBUG'] * to the appropriate make_extension call in setupext.py, and then rebuilding. */ -#define NO_IMPORT_ARRAY - #include "../mplutils.h" #include "_tri.h" #include -#include #include +#include TriEdge::TriEdge() @@ -221,7 +219,7 @@ Triangulation::Triangulation(const CoordinateArray& x, const MaskArray& mask, const EdgeArray& edges, const NeighborArray& neighbors, - int correct_triangle_orientations) + bool correct_triangle_orientations) : _x(x), _y(y), _triangles(triangles), @@ -229,6 +227,29 @@ Triangulation::Triangulation(const CoordinateArray& x, _edges(edges), _neighbors(neighbors) { + if (_x.ndim() != 1 || _y.ndim() != 1 || _x.shape(0) != _y.shape(0)) + throw std::invalid_argument("x and y must be 1D arrays of the same length"); + + if (_triangles.ndim() != 2 || _triangles.shape(1) != 3) + throw std::invalid_argument("triangles must be a 2D array of shape (?,3)"); + + // Optional mask. + if (_mask.size() > 0 && + (_mask.ndim() != 1 || _mask.shape(0) != _triangles.shape(0))) + throw std::invalid_argument( + "mask must be a 1D array with the same length as the triangles array"); + + // Optional edges. + if (_edges.size() > 0 && + (_edges.ndim() != 2 || _edges.shape(1) != 2)) + throw std::invalid_argument("edges must be a 2D array with shape (?,2)"); + + // Optional neighbors. + if (_neighbors.size() > 0 && + (_neighbors.ndim() != 2 || _neighbors.shape() != _triangles.shape())) + throw std::invalid_argument( + "neighbors must be a 2D array with the same shape as the triangles array"); + if (correct_triangle_orientations) correct_triangles(); } @@ -291,7 +312,7 @@ void Triangulation::calculate_boundaries() void Triangulation::calculate_edges() { - assert(_edges.empty() && "Expected empty edges array"); + assert(!has_edges() && "Expected empty edges array"); // Create set of all edges, storing them with start point index less than // end point index. @@ -308,29 +329,28 @@ void Triangulation::calculate_edges() } // Convert to python _edges array. - npy_intp dims[2] = {static_cast(edge_set.size()), 2}; + py::ssize_t dims[2] = {static_cast(edge_set.size()), 2}; _edges = EdgeArray(dims); + auto edges = _edges.mutable_data(); int i = 0; for (EdgeSet::const_iterator it = edge_set.begin(); it != edge_set.end(); ++it) { - _edges(i, 0) = it->start; - _edges(i++, 1) = it->end; + edges[i++] = it->start; + edges[i++] = it->end; } } void Triangulation::calculate_neighbors() { - assert(_neighbors.empty() && "Expected empty neighbors array"); + assert(!has_neighbors() && "Expected empty neighbors array"); // Create _neighbors array with shape (ntri,3) and initialise all to -1. - npy_intp dims[2] = {get_ntri(), 3}; + py::ssize_t dims[2] = {get_ntri(), 3}; _neighbors = NeighborArray(dims); + auto* neighbors = _neighbors.mutable_data(); int tri, edge; - for (tri = 0; tri < get_ntri(); ++tri) { - for (edge = 0; edge < 3; ++edge) - _neighbors(tri, edge) = -1; - } + std::fill(neighbors, neighbors+3*get_ntri(), -1); // For each triangle edge (start to end point), find corresponding neighbor // edge from end to start point. Do this by traversing all edges and @@ -353,8 +373,8 @@ void Triangulation::calculate_neighbors() } else { // Neighbor edge found, set the two elements of _neighbors // and remove edge from edge_to_tri_edge_map. - _neighbors(tri, edge)= it->second.tri; - _neighbors(it->second.tri, it->second.edge) = tri; + neighbors[3*tri + edge] = it->second.tri; + neighbors[3*it->second.tri + it->second.edge] = tri; edge_to_tri_edge_map.erase(it); } } @@ -368,8 +388,17 @@ void Triangulation::calculate_neighbors() Triangulation::TwoCoordinateArray Triangulation::calculate_plane_coefficients( const CoordinateArray& z) { - npy_intp dims[2] = {get_ntri(), 3}; - Triangulation::TwoCoordinateArray planes(dims); + if (z.ndim() != 1 || z.shape(0) != _x.shape(0)) + throw std::invalid_argument( + "z must be a 1D array with the same length as the triangulation x and y arrays"); + + int dims[2] = {get_ntri(), 3}; + Triangulation::TwoCoordinateArray planes_array(dims); + auto planes = planes_array.mutable_unchecked<2>(); + auto triangles = _triangles.unchecked<2>(); + auto x = _x.unchecked<1>(); + auto y = _y.unchecked<1>(); + auto z_ptr = z.unchecked<1>(); int point; for (int tri = 0; tri < get_ntri(); ++tri) { @@ -386,12 +415,12 @@ Triangulation::TwoCoordinateArray Triangulation::calculate_plane_coefficients( // and rearrange to give // r_z = (-normal_x/normal_z)*r_x + (-normal_y/normal_z)*r_y + // p/normal_z - point = _triangles(tri, 0); - XYZ point0(_x(point), _y(point), z(point)); - point = _triangles(tri, 1); - XYZ side01 = XYZ(_x(point), _y(point), z(point)) - point0; - point = _triangles(tri, 2); - XYZ side02 = XYZ(_x(point), _y(point), z(point)) - point0; + point = triangles(tri, 0); + XYZ point0(x(point), y(point), z_ptr(point)); + point = triangles(tri, 1); + XYZ side01 = XYZ(x(point), y(point), z_ptr(point)) - point0; + point = triangles(tri, 2); + XYZ side02 = XYZ(x(point), y(point), z_ptr(point)) - point0; XYZ normal = side01.cross(side02); @@ -415,20 +444,23 @@ Triangulation::TwoCoordinateArray Triangulation::calculate_plane_coefficients( } } - return planes; + return planes_array; } void Triangulation::correct_triangles() { + auto triangles = _triangles.mutable_data(); + auto neighbors = _neighbors.mutable_data(); + for (int tri = 0; tri < get_ntri(); ++tri) { - XY point0 = get_point_coords(_triangles(tri, 0)); - XY point1 = get_point_coords(_triangles(tri, 1)); - XY point2 = get_point_coords(_triangles(tri, 2)); + XY point0 = get_point_coords(triangles[3*tri]); + XY point1 = get_point_coords(triangles[3*tri+1]); + XY point2 = get_point_coords(triangles[3*tri+2]); if ( (point1 - point0).cross_z(point2 - point0) < 0.0) { // Triangle points are clockwise, so change them to anticlockwise. - std::swap(_triangles(tri, 1), _triangles(tri, 2)); - if (!_neighbors.empty()) - std::swap(_neighbors(tri, 1), _neighbors(tri, 2)); + std::swap(triangles[3*tri+1], triangles[3*tri+2]); + if (has_neighbors()) + std::swap(neighbors[3*tri+1], neighbors[3*tri+2]); } } } @@ -457,8 +489,11 @@ int Triangulation::get_edge_in_triangle(int tri, int point) const { assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds"); assert(point >= 0 && point < get_npoints() && "Point index out of bounds."); + + auto triangles = _triangles.data(); + for (int edge = 0; edge < 3; ++edge) { - if (_triangles(tri, edge) == point) + if (triangles[3*tri + edge] == point) return edge; } return -1; // point is not in triangle. @@ -466,7 +501,7 @@ int Triangulation::get_edge_in_triangle(int tri, int point) const Triangulation::EdgeArray& Triangulation::get_edges() { - if (_edges.empty()) + if (!has_edges()) calculate_edges(); return _edges; } @@ -475,9 +510,9 @@ int Triangulation::get_neighbor(int tri, int edge) const { assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds"); assert(edge >= 0 && edge < 3 && "Edge index out of bounds"); - if (_neighbors.empty()) + if (!has_neighbors()) const_cast(*this).calculate_neighbors(); - return _neighbors(tri, edge); + return _neighbors.data()[3*tri + edge]; } TriEdge Triangulation::get_neighbor_edge(int tri, int edge) const @@ -494,32 +529,32 @@ TriEdge Triangulation::get_neighbor_edge(int tri, int edge) const Triangulation::NeighborArray& Triangulation::get_neighbors() { - if (_neighbors.empty()) + if (!has_neighbors()) calculate_neighbors(); return _neighbors; } int Triangulation::get_npoints() const { - return _x.size(); + return _x.shape(0); } int Triangulation::get_ntri() const { - return _triangles.size(); + return _triangles.shape(0); } XY Triangulation::get_point_coords(int point) const { assert(point >= 0 && point < get_npoints() && "Point index out of bounds."); - return XY(_x(point), _y(point)); + return XY(_x.data()[point], _y.data()[point]); } int Triangulation::get_triangle_point(int tri, int edge) const { assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds"); assert(edge >= 0 && edge < 3 && "Edge index out of bounds"); - return _triangles(tri, edge); + return _triangles.data()[3*tri + edge]; } int Triangulation::get_triangle_point(const TriEdge& tri_edge) const @@ -527,15 +562,34 @@ int Triangulation::get_triangle_point(const TriEdge& tri_edge) const return get_triangle_point(tri_edge.tri, tri_edge.edge); } +bool Triangulation::has_edges() const +{ + return _edges.size() > 0; +} + +bool Triangulation::has_mask() const +{ + return _mask.size() > 0; +} + +bool Triangulation::has_neighbors() const +{ + return _neighbors.size() > 0; +} + bool Triangulation::is_masked(int tri) const { assert(tri >= 0 && tri < get_ntri() && "Triangle index out of bounds."); - const npy_bool* mask_ptr = reinterpret_cast(_mask.data()); - return !_mask.empty() && mask_ptr[tri]; + return has_mask() && _mask.data()[tri]; } void Triangulation::set_mask(const MaskArray& mask) { + if (mask.size() > 0 && + (mask.ndim() != 1 || mask.shape(0) != _triangles.shape(0))) + throw std::invalid_argument( + "mask must be a 1D array with the same length as the triangles array"); + _mask = mask; // Clear derived fields so they are recalculated when needed. @@ -567,7 +621,11 @@ TriContourGenerator::TriContourGenerator(Triangulation& triangulation, _interior_visited(2*_triangulation.get_ntri()), _boundaries_visited(0), _boundaries_used(0) -{} +{ + if (_z.ndim() != 1 || _z.shape(0) != _triangulation.get_npoints()) + throw std::invalid_argument( + "z must be a 1D array with the same length as the x and y arrays"); +} void TriContourGenerator::clear_visited_flags(bool include_boundaries) { @@ -598,7 +656,7 @@ void TriContourGenerator::clear_visited_flags(bool include_boundaries) } } -PyObject* TriContourGenerator::contour_line_to_segs_and_kinds(const Contour& contour) +py::tuple TriContourGenerator::contour_line_to_segs_and_kinds(const Contour& contour) { // Convert all of the lines generated by a call to create_contour() into // their Python equivalents for return to the calling function. @@ -612,27 +670,20 @@ PyObject* TriContourGenerator::contour_line_to_segs_and_kinds(const Contour& con // and they are appended to the Python lists vertices_list and codes_list // respectively for return to the Python calling function. - PyObject* vertices_list = PyList_New(contour.size()); - if (vertices_list == 0) - throw std::runtime_error("Failed to create Python list"); - - PyObject* codes_list = PyList_New(contour.size()); - if (codes_list == 0) { - Py_XDECREF(vertices_list); - throw std::runtime_error("Failed to create Python list"); - } + py::list vertices_list(contour.size()); + py::list codes_list(contour.size()); for (Contour::size_type i = 0; i < contour.size(); ++i) { const ContourLine& contour_line = contour[i]; - npy_intp npoints = static_cast(contour_line.size()); + py::ssize_t npoints = static_cast(contour_line.size()); - npy_intp segs_dims[2] = {npoints, 2}; - numpy::array_view segs(segs_dims); - double* segs_ptr = segs.data(); + py::ssize_t segs_dims[2] = {npoints, 2}; + CoordinateArray segs(segs_dims); + double* segs_ptr = segs.mutable_data(); - npy_intp codes_dims[1] = {npoints}; - numpy::array_view codes(codes_dims); - unsigned char* codes_ptr = codes.data(); + py::ssize_t codes_dims[1] = {npoints}; + CodeArray codes(codes_dims); + unsigned char* codes_ptr = codes.mutable_data(); for (ContourLine::const_iterator it = contour_line.begin(); it != contour_line.end(); ++it) { @@ -646,25 +697,14 @@ PyObject* TriContourGenerator::contour_line_to_segs_and_kinds(const Contour& con contour_line.front() == contour_line.back()) *(codes_ptr-1) = CLOSEPOLY; - PyList_SET_ITEM(vertices_list, i, segs.pyobj()); - PyList_SET_ITEM(codes_list, i, codes.pyobj()); - } - - PyObject* result = PyTuple_New(2); - if (result == 0) { - Py_XDECREF(vertices_list); - Py_XDECREF(codes_list); - throw std::runtime_error("Failed to create Python tuple"); + vertices_list[i] = segs; + codes_list[i] = codes; } - // No error checking here as filling in a brand new pre-allocated result. - PyTuple_SET_ITEM(result, 0, vertices_list); - PyTuple_SET_ITEM(result, 1, codes_list); - - return result; + return py::make_tuple(vertices_list, codes_list); } -PyObject* TriContourGenerator::contour_to_segs_and_kinds(const Contour& contour) +py::tuple TriContourGenerator::contour_to_segs_and_kinds(const Contour& contour) { // Convert all of the polygons generated by a call to // create_filled_contour() into their Python equivalents for return to the @@ -684,19 +724,19 @@ PyObject* TriContourGenerator::contour_to_segs_and_kinds(const Contour& contour) ContourLine::const_iterator point; // Find total number of points in all contour lines. - npy_intp n_points = 0; + py::ssize_t n_points = 0; for (line = contour.begin(); line != contour.end(); ++line) - n_points += (npy_intp)line->size(); + n_points += static_cast(line->size()); // Create segs array for point coordinates. - npy_intp segs_dims[2] = {n_points, 2}; - numpy::array_view segs(segs_dims); - double* segs_ptr = segs.data(); + py::ssize_t segs_dims[2] = {n_points, 2}; + TwoCoordinateArray segs(segs_dims); + double* segs_ptr = segs.mutable_data(); // Create kinds array for code types. - npy_intp codes_dims[1] = {n_points}; - numpy::array_view codes(codes_dims); - unsigned char* codes_ptr = codes.data(); + py::ssize_t codes_dims[1] = {n_points}; + CodeArray codes(codes_dims); + unsigned char* codes_ptr = codes.mutable_data(); for (line = contour.begin(); line != contour.end(); ++line) { for (point = line->begin(); point != line->end(); point++) { @@ -706,38 +746,16 @@ PyObject* TriContourGenerator::contour_to_segs_and_kinds(const Contour& contour) } } - PyObject* vertices_list = PyList_New(0); - if (vertices_list == 0) - throw std::runtime_error("Failed to create Python list"); - - PyObject* codes_list = PyList_New(0); - if (codes_list == 0) { - Py_XDECREF(vertices_list); - throw std::runtime_error("Failed to create Python list"); - } - - if (PyList_Append(vertices_list, segs.pyobj_steal()) || - PyList_Append(codes_list, codes.pyobj_steal())) { - Py_XDECREF(vertices_list); - Py_XDECREF(codes_list); - throw std::runtime_error("Unable to add contour to vertices and codes lists"); - } - - PyObject* result = PyTuple_New(2); - if (result == 0) { - Py_XDECREF(vertices_list); - Py_XDECREF(codes_list); - throw std::runtime_error("Failed to create Python tuple"); - } + py::list vertices_list(1); + vertices_list[0] = segs; - // No error checking here as filling in a brand new pre-allocated tuple. - PyTuple_SET_ITEM(result, 0, vertices_list); - PyTuple_SET_ITEM(result, 1, codes_list); + py::list codes_list(1); + codes_list[0] = codes; - return result; + return py::make_tuple(vertices_list, codes_list); } -PyObject* TriContourGenerator::create_contour(const double& level) +py::tuple TriContourGenerator::create_contour(const double& level) { clear_visited_flags(false); Contour contour; @@ -748,9 +766,12 @@ PyObject* TriContourGenerator::create_contour(const double& level) return contour_line_to_segs_and_kinds(contour); } -PyObject* TriContourGenerator::create_filled_contour(const double& lower_level, +py::tuple TriContourGenerator::create_filled_contour(const double& lower_level, const double& upper_level) { + if (lower_level >= upper_level) + throw std::invalid_argument("filled contour levels must be increasing"); + clear_visited_flags(true); Contour contour; @@ -1047,7 +1068,7 @@ const double& TriContourGenerator::get_z(int point) const { assert(point >= 0 && point < _triangulation.get_npoints() && "Point index out of bounds."); - return _z(point); + return _z.data()[point]; } XY TriContourGenerator::interp(int point1, @@ -1308,16 +1329,22 @@ TrapezoidMapTriFinder::TriIndexArray TrapezoidMapTriFinder::find_many(const CoordinateArray& x, const CoordinateArray& y) { + if (x.ndim() != 1 || x.shape(0) != y.shape(0)) + throw std::invalid_argument( + "x and y must be array-like with same shape"); + // Create integer array to return. - npy_intp n = x.dim(0); - npy_intp dims[1] = {n}; - TriIndexArray tri_indices(dims); + auto n = x.shape(0); + TriIndexArray tri_indices_array(n); + auto tri_indices = tri_indices_array.mutable_unchecked<1>(); + auto x_data = x.data(); + auto y_data = y.data(); // Fill returned array. - for (npy_intp i = 0; i < n; ++i) - tri_indices(i) = find_one(XY(x(i), y(i))); + for (py::ssize_t i = 0; i < n; ++i) + tri_indices(i) = find_one(XY(x_data[i], y_data[i])); - return tri_indices; + return tri_indices_array; } int @@ -1371,20 +1398,21 @@ TrapezoidMapTriFinder::find_trapezoids_intersecting_edge( return true; } -PyObject* +py::list TrapezoidMapTriFinder::get_tree_stats() { NodeStats stats; _tree->get_stats(0, stats); - return Py_BuildValue("[l,l,l,l,l,l,d]", - stats.node_count, - stats.unique_nodes.size(), - stats.trapezoid_count, - stats.unique_trapezoid_nodes.size(), - stats.max_parent_count, - stats.max_depth, - stats.sum_trapezoid_depth / stats.trapezoid_count); + py::list ret(7); + ret[0] = stats.node_count; + ret[1] = stats.unique_nodes.size(), + ret[2] = stats.trapezoid_count, + ret[3] = stats.unique_trapezoid_nodes.size(), + ret[4] = stats.max_parent_count, + ret[5] = stats.max_depth, + ret[6] = stats.sum_trapezoid_depth / stats.trapezoid_count; + return ret; } void diff --git a/src/tri/_tri.h b/src/tri/_tri.h index 29b4ff81fb17..6c6c66a01120 100644 --- a/src/tri/_tri.h +++ b/src/tri/_tri.h @@ -63,7 +63,8 @@ #ifndef MPL_TRI_H #define MPL_TRI_H -#include "../numpy_cpp.h" +#include +#include #include #include @@ -71,6 +72,7 @@ #include #include +namespace py = pybind11; /* An edge of a triangle consisting of an triangle index in the range 0 to @@ -161,12 +163,12 @@ void write_contour(const Contour& contour); class Triangulation { public: - typedef numpy::array_view CoordinateArray; - typedef numpy::array_view TwoCoordinateArray; - typedef numpy::array_view TriangleArray; - typedef numpy::array_view MaskArray; - typedef numpy::array_view EdgeArray; - typedef numpy::array_view NeighborArray; + typedef py::array_t CoordinateArray; + typedef py::array_t TwoCoordinateArray; + typedef py::array_t TriangleArray; + typedef py::array_t MaskArray; + typedef py::array_t EdgeArray; + typedef py::array_t NeighborArray; /* A single boundary is a vector of the TriEdges that make up that boundary * following it around with unmasked triangles on the left. */ @@ -196,7 +198,7 @@ class Triangulation const MaskArray& mask, const EdgeArray& edges, const NeighborArray& neighbors, - int correct_triangle_orientations); + bool correct_triangle_orientations); /* Calculate plane equation coefficients for all unmasked triangles from * the point (x,y) coordinates and point z-array of shape (npoints) passed @@ -296,7 +298,11 @@ class Triangulation * the specified triangle, or -1 if the point is not in the triangle. */ int get_edge_in_triangle(int tri, int point) const; + bool has_edges() const; + bool has_mask() const; + + bool has_neighbors() const; // Variables shared with python, always set. @@ -304,11 +310,11 @@ class Triangulation TriangleArray _triangles; // int array (ntri,3) of triangle point indices, // ordered anticlockwise. - // Variables shared with python, may be zero. + // Variables shared with python, may be unset (size == 0). MaskArray _mask; // bool array (ntri). - // Derived variables shared with python, may be zero. If zero, are - // recalculated when needed. + // Derived variables shared with python, may be unset (size == 0). + // If unset, are recalculated when needed. EdgeArray _edges; // int array (?,2) of start & end point indices. NeighborArray _neighbors; // int array (ntri,3), neighbor triangle indices // or -1 if no neighbor. @@ -329,6 +335,8 @@ class TriContourGenerator { public: typedef Triangulation::CoordinateArray CoordinateArray; + typedef Triangulation::TwoCoordinateArray TwoCoordinateArray; + typedef py::array_t CodeArray; /* Constructor. * triangulation: Triangulation to generate contours for. @@ -342,7 +350,7 @@ class TriContourGenerator * Returns new python list [segs0, segs1, ...] where * segs0: double array of shape (?,2) of point coordinates of first * contour line, etc. */ - PyObject* create_contour(const double& level); + py::tuple create_contour(const double& level); /* Create and return a filled contour. * lower_level: Lower contour level. @@ -350,7 +358,7 @@ class TriContourGenerator * Returns new python tuple (segs, kinds) where * segs: double array of shape (n_points,2) of all point coordinates, * kinds: ubyte array of shape (n_points) of all point code types. */ - PyObject* create_filled_contour(const double& lower_level, + py::tuple create_filled_contour(const double& lower_level, const double& upper_level); private: @@ -369,13 +377,13 @@ class TriContourGenerator * contour line, etc. * kinds0: ubyte array of shape (n_points) of kinds codes of first contour * line, etc. */ - PyObject* contour_line_to_segs_and_kinds(const Contour& contour); + py::tuple contour_line_to_segs_and_kinds(const Contour& contour); /* Convert a filled Contour from C++ to Python. * Returns new python tuple ([segs], [kinds]) where * segs: double array of shape (n_points,2) of all point coordinates, * kinds: ubyte array of shape (n_points) of all point code types. */ - PyObject* contour_to_segs_and_kinds(const Contour& contour); + py::tuple contour_to_segs_and_kinds(const Contour& contour); /* Return the point on the specified TriEdge that intersects the specified * level. */ @@ -460,7 +468,7 @@ class TriContourGenerator // Variables shared with python, always set. - Triangulation& _triangulation; + Triangulation _triangulation; CoordinateArray _z; // double array (npoints). // Variables internal to C++ only. @@ -507,7 +515,7 @@ class TrapezoidMapTriFinder { public: typedef Triangulation::CoordinateArray CoordinateArray; - typedef numpy::array_view TriIndexArray; + typedef py::array_t TriIndexArray; /* Constructor. A separate call to initialize() is required to initialize * the object before use. @@ -533,7 +541,7 @@ class TrapezoidMapTriFinder * comparisons needed to search through the tree) * 6: mean of all trapezoid depths (one more than the average number of * comparisons needed to search through the tree) */ - PyObject* get_tree_stats(); + py::list get_tree_stats(); /* Initialize this object before use. May be called multiple times, if, * for example, the triangulation is changed by setting the mask. */ diff --git a/src/tri/_tri_wrapper.cpp b/src/tri/_tri_wrapper.cpp index bf58ca02879f..d4170cd19d55 100644 --- a/src/tri/_tri_wrapper.cpp +++ b/src/tri/_tri_wrapper.cpp @@ -1,510 +1,58 @@ #include "_tri.h" -#include "../mplutils.h" -#include "../py_exceptions.h" - -/* Triangulation */ - -typedef struct -{ - PyObject_HEAD - Triangulation* ptr; -} PyTriangulation; - -static PyTypeObject PyTriangulationType; - -static PyObject* PyTriangulation_new(PyTypeObject* type, PyObject* args, PyObject* kwds) -{ - PyTriangulation* self; - self = (PyTriangulation*)type->tp_alloc(type, 0); - self->ptr = NULL; - return (PyObject*)self; -} - -const char* PyTriangulation_init__doc__ = - "Triangulation(x, y, triangles, mask, edges, neighbors)\n" - "--\n\n" - "Create a new C++ Triangulation object.\n" - "This should not be called directly, instead use the python class\n" - "matplotlib.tri.Triangulation instead.\n"; - -static int PyTriangulation_init(PyTriangulation* self, PyObject* args, PyObject* kwds) -{ - Triangulation::CoordinateArray x, y; - Triangulation::TriangleArray triangles; - Triangulation::MaskArray mask; - Triangulation::EdgeArray edges; - Triangulation::NeighborArray neighbors; - int correct_triangle_orientations; - - if (!PyArg_ParseTuple(args, - "O&O&O&O&O&O&i", - &x.converter, &x, - &y.converter, &y, - &triangles.converter, &triangles, - &mask.converter, &mask, - &edges.converter, &edges, - &neighbors.converter, &neighbors, - &correct_triangle_orientations)) { - return -1; - } - - // x and y. - if (x.empty() || y.empty() || x.dim(0) != y.dim(0)) { - PyErr_SetString(PyExc_ValueError, - "x and y must be 1D arrays of the same length"); - return -1; - } - - // triangles. - if (triangles.empty() || triangles.dim(1) != 3) { - PyErr_SetString(PyExc_ValueError, - "triangles must be a 2D array of shape (?,3)"); - return -1; - } - - // Optional mask. - if (!mask.empty() && mask.dim(0) != triangles.dim(0)) { - PyErr_SetString(PyExc_ValueError, - "mask must be a 1D array with the same length as the triangles array"); - return -1; - } - - // Optional edges. - if (!edges.empty() && edges.dim(1) != 2) { - PyErr_SetString(PyExc_ValueError, - "edges must be a 2D array with shape (?,2)"); - return -1; - } - - // Optional neighbors. - if (!neighbors.empty() && (neighbors.dim(0) != triangles.dim(0) || - neighbors.dim(1) != triangles.dim(1))) { - PyErr_SetString(PyExc_ValueError, - "neighbors must be a 2D array with the same shape as the triangles array"); - return -1; - } - - CALL_CPP_INIT("Triangulation", - (self->ptr = new Triangulation(x, y, triangles, mask, - edges, neighbors, - correct_triangle_orientations))); - return 0; -} - -static void PyTriangulation_dealloc(PyTriangulation* self) -{ - delete self->ptr; - Py_TYPE(self)->tp_free((PyObject*)self); -} - -const char* PyTriangulation_calculate_plane_coefficients__doc__ = - "calculate_plane_coefficients(self, z, plane_coefficients)\n" - "--\n\n" - "Calculate plane equation coefficients for all unmasked triangles."; - -static PyObject* PyTriangulation_calculate_plane_coefficients(PyTriangulation* self, PyObject* args) -{ - Triangulation::CoordinateArray z; - if (!PyArg_ParseTuple(args, "O&:calculate_plane_coefficients", - &z.converter, &z)) { - return NULL; - } - - if (z.empty() || z.dim(0) != self->ptr->get_npoints()) { - PyErr_SetString(PyExc_ValueError, - "z array must have same length as triangulation x and y arrays"); - return NULL; - } - - Triangulation::TwoCoordinateArray result; - CALL_CPP("calculate_plane_coefficients", - (result = self->ptr->calculate_plane_coefficients(z))); - return result.pyobj(); -} - -const char* PyTriangulation_get_edges__doc__ = - "get_edges(self)\n" - "--\n\n" - "Return edges array."; - -static PyObject* PyTriangulation_get_edges(PyTriangulation* self, PyObject* args) -{ - Triangulation::EdgeArray* result; - CALL_CPP("get_edges", (result = &self->ptr->get_edges())); - - if (result->empty()) { - Py_RETURN_NONE; - } - else - return result->pyobj(); -} - -const char* PyTriangulation_get_neighbors__doc__ = - "get_neighbors(self)\n" - "--\n\n" - "Return neighbors array."; - -static PyObject* PyTriangulation_get_neighbors(PyTriangulation* self, PyObject* args) -{ - Triangulation::NeighborArray* result; - CALL_CPP("get_neighbors", (result = &self->ptr->get_neighbors())); - - if (result->empty()) { - Py_RETURN_NONE; - } - else - return result->pyobj(); -} - -const char* PyTriangulation_set_mask__doc__ = - "set_mask(self, mask)\n" - "--\n\n" - "Set or clear the mask array."; - -static PyObject* PyTriangulation_set_mask(PyTriangulation* self, PyObject* args) -{ - Triangulation::MaskArray mask; - - if (!PyArg_ParseTuple(args, "O&:set_mask", &mask.converter, &mask)) { - return NULL; - } - - if (!mask.empty() && mask.dim(0) != self->ptr->get_ntri()) { - PyErr_SetString(PyExc_ValueError, - "mask must be a 1D array with the same length as the triangles array"); - return NULL; - } - - CALL_CPP("set_mask", (self->ptr->set_mask(mask))); - Py_RETURN_NONE; -} - -static PyTypeObject *PyTriangulation_init_type() -{ - static PyMethodDef methods[] = { - {"calculate_plane_coefficients", - (PyCFunction)PyTriangulation_calculate_plane_coefficients, - METH_VARARGS, - PyTriangulation_calculate_plane_coefficients__doc__}, - {"get_edges", - (PyCFunction)PyTriangulation_get_edges, - METH_NOARGS, - PyTriangulation_get_edges__doc__}, - {"get_neighbors", - (PyCFunction)PyTriangulation_get_neighbors, - METH_NOARGS, - PyTriangulation_get_neighbors__doc__}, - {"set_mask", - (PyCFunction)PyTriangulation_set_mask, - METH_VARARGS, - PyTriangulation_set_mask__doc__}, - {NULL} - }; - PyTriangulationType.tp_name = "matplotlib._tri.Triangulation"; - PyTriangulationType.tp_doc = PyTriangulation_init__doc__; - PyTriangulationType.tp_basicsize = sizeof(PyTriangulation); - PyTriangulationType.tp_dealloc = (destructor)PyTriangulation_dealloc; - PyTriangulationType.tp_flags = Py_TPFLAGS_DEFAULT; - PyTriangulationType.tp_methods = methods; - PyTriangulationType.tp_new = PyTriangulation_new; - PyTriangulationType.tp_init = (initproc)PyTriangulation_init; - - return &PyTriangulationType; -} - - -/* TriContourGenerator */ - -typedef struct -{ - PyObject_HEAD - TriContourGenerator* ptr; - PyTriangulation* py_triangulation; -} PyTriContourGenerator; - -static PyTypeObject PyTriContourGeneratorType; - -static PyObject* PyTriContourGenerator_new(PyTypeObject* type, PyObject* args, PyObject* kwds) -{ - PyTriContourGenerator* self; - self = (PyTriContourGenerator*)type->tp_alloc(type, 0); - self->ptr = NULL; - self->py_triangulation = NULL; - return (PyObject*)self; -} - -const char* PyTriContourGenerator_init__doc__ = - "TriContourGenerator(triangulation, z)\n" - "--\n\n" - "Create a new C++ TriContourGenerator object.\n" - "This should not be called directly, instead use the functions\n" - "matplotlib.axes.tricontour and tricontourf instead.\n"; - -static int PyTriContourGenerator_init(PyTriContourGenerator* self, PyObject* args, PyObject* kwds) -{ - PyObject* triangulation_arg; - TriContourGenerator::CoordinateArray z; - - if (!PyArg_ParseTuple(args, "O!O&", - &PyTriangulationType, &triangulation_arg, - &z.converter, &z)) { - return -1; - } - - PyTriangulation* py_triangulation = (PyTriangulation*)triangulation_arg; - Py_INCREF(py_triangulation); - self->py_triangulation = py_triangulation; - Triangulation& triangulation = *(py_triangulation->ptr); - - if (z.empty() || z.dim(0) != triangulation.get_npoints()) { - PyErr_SetString(PyExc_ValueError, - "z must be a 1D array with the same length as the x and y arrays"); - return -1; - } - - CALL_CPP_INIT("TriContourGenerator", - (self->ptr = new TriContourGenerator(triangulation, z))); - return 0; -} - -static void PyTriContourGenerator_dealloc(PyTriContourGenerator* self) -{ - delete self->ptr; - Py_XDECREF(self->py_triangulation); - Py_TYPE(self)->tp_free((PyObject *)self); -} - -const char* PyTriContourGenerator_create_contour__doc__ = - "create_contour(self, level)\n" - "--\n\n" - "Create and return a non-filled contour."; - -static PyObject* PyTriContourGenerator_create_contour(PyTriContourGenerator* self, PyObject* args) -{ - double level; - if (!PyArg_ParseTuple(args, "d:create_contour", &level)) { - return NULL; - } - - PyObject* result; - CALL_CPP("create_contour", (result = self->ptr->create_contour(level))); - return result; -} - -const char* PyTriContourGenerator_create_filled_contour__doc__ = - "create_filled_contour(self, lower_level, upper_level)\n" - "--\n\n" - "Create and return a filled contour."; - -static PyObject* PyTriContourGenerator_create_filled_contour(PyTriContourGenerator* self, PyObject* args) -{ - double lower_level, upper_level; - if (!PyArg_ParseTuple(args, "dd:create_filled_contour", - &lower_level, &upper_level)) { - return NULL; - } - - if (lower_level >= upper_level) - { - PyErr_SetString(PyExc_ValueError, - "filled contour levels must be increasing"); - return NULL; - } - - PyObject* result; - CALL_CPP("create_filled_contour", - (result = self->ptr->create_filled_contour(lower_level, - upper_level))); - return result; -} - -static PyTypeObject *PyTriContourGenerator_init_type() -{ - static PyMethodDef methods[] = { - {"create_contour", - (PyCFunction)PyTriContourGenerator_create_contour, - METH_VARARGS, - PyTriContourGenerator_create_contour__doc__}, - {"create_filled_contour", - (PyCFunction)PyTriContourGenerator_create_filled_contour, - METH_VARARGS, - PyTriContourGenerator_create_filled_contour__doc__}, - {NULL} - }; - PyTriContourGeneratorType.tp_name = "matplotlib._tri.TriContourGenerator"; - PyTriContourGeneratorType.tp_doc = PyTriContourGenerator_init__doc__; - PyTriContourGeneratorType.tp_basicsize = sizeof(PyTriContourGenerator); - PyTriContourGeneratorType.tp_dealloc = (destructor)PyTriContourGenerator_dealloc; - PyTriContourGeneratorType.tp_flags = Py_TPFLAGS_DEFAULT; - PyTriContourGeneratorType.tp_methods = methods; - PyTriContourGeneratorType.tp_new = PyTriContourGenerator_new; - PyTriContourGeneratorType.tp_init = (initproc)PyTriContourGenerator_init; - - return &PyTriContourGeneratorType; -} - - -/* TrapezoidMapTriFinder */ - -typedef struct -{ - PyObject_HEAD - TrapezoidMapTriFinder* ptr; - PyTriangulation* py_triangulation; -} PyTrapezoidMapTriFinder; - -static PyTypeObject PyTrapezoidMapTriFinderType; - -static PyObject* PyTrapezoidMapTriFinder_new(PyTypeObject* type, PyObject* args, PyObject* kwds) -{ - PyTrapezoidMapTriFinder* self; - self = (PyTrapezoidMapTriFinder*)type->tp_alloc(type, 0); - self->ptr = NULL; - self->py_triangulation = NULL; - return (PyObject*)self; -} - -const char* PyTrapezoidMapTriFinder_init__doc__ = - "TrapezoidMapTriFinder(triangulation)\n" - "--\n\n" - "Create a new C++ TrapezoidMapTriFinder object.\n" - "This should not be called directly, instead use the python class\n" - "matplotlib.tri.TrapezoidMapTriFinder instead.\n"; - -static int PyTrapezoidMapTriFinder_init(PyTrapezoidMapTriFinder* self, PyObject* args, PyObject* kwds) -{ - PyObject* triangulation_arg; - if (!PyArg_ParseTuple(args, "O!", - &PyTriangulationType, &triangulation_arg)) { - return -1; - } - - PyTriangulation* py_triangulation = (PyTriangulation*)triangulation_arg; - Py_INCREF(py_triangulation); - self->py_triangulation = py_triangulation; - Triangulation& triangulation = *(py_triangulation->ptr); - - CALL_CPP_INIT("TrapezoidMapTriFinder", - (self->ptr = new TrapezoidMapTriFinder(triangulation))); - return 0; -} - -static void PyTrapezoidMapTriFinder_dealloc(PyTrapezoidMapTriFinder* self) -{ - delete self->ptr; - Py_XDECREF(self->py_triangulation); - Py_TYPE(self)->tp_free((PyObject *)self); +PYBIND11_MODULE(_tri, m) { + py::class_(m, "Triangulation") + .def(py::init(), + py::arg("x"), + py::arg("y"), + py::arg("triangles"), + py::arg("mask"), + py::arg("edges"), + py::arg("neighbors"), + py::arg("correct_triangle_orientations"), + "Create a new C++ Triangulation object.\n" + "This should not be called directly, instead use the python class\n" + "matplotlib.tri.Triangulation instead.\n") + .def("calculate_plane_coefficients", &Triangulation::calculate_plane_coefficients, + "Calculate plane equation coefficients for all unmasked triangles.") + .def("get_edges", &Triangulation::get_edges, + "Return edges array.") + .def("get_neighbors", &Triangulation::get_neighbors, + "Return neighbors array.") + .def("set_mask", &Triangulation::set_mask, + "Set or clear the mask array."); + + py::class_(m, "TriContourGenerator") + .def(py::init(), + py::arg("triangulation"), + py::arg("z"), + "Create a new C++ TriContourGenerator object.\n" + "This should not be called directly, instead use the functions\n" + "matplotlib.axes.tricontour and tricontourf instead.\n") + .def("create_contour", &TriContourGenerator::create_contour, + "Create and return a non-filled contour.") + .def("create_filled_contour", &TriContourGenerator::create_filled_contour, + "Create and return a filled contour."); + + py::class_(m, "TrapezoidMapTriFinder") + .def(py::init(), + py::arg("triangulation"), + "Create a new C++ TrapezoidMapTriFinder object.\n" + "This should not be called directly, instead use the python class\n" + "matplotlib.tri.TrapezoidMapTriFinder instead.\n") + .def("find_many", &TrapezoidMapTriFinder::find_many, + "Find indices of triangles containing the point coordinates (x, y).") + .def("get_tree_stats", &TrapezoidMapTriFinder::get_tree_stats, + "Return statistics about the tree used by the trapezoid map.") + .def("initialize", &TrapezoidMapTriFinder::initialize, + "Initialize this object, creating the trapezoid map from the triangulation.") + .def("print_tree", &TrapezoidMapTriFinder::print_tree, + "Print the search tree as text to stdout; useful for debug purposes."); } - -const char* PyTrapezoidMapTriFinder_find_many__doc__ = - "find_many(self, x, y)\n" - "--\n\n" - "Find indices of triangles containing the point coordinates (x, y)."; - -static PyObject* PyTrapezoidMapTriFinder_find_many(PyTrapezoidMapTriFinder* self, PyObject* args) -{ - TrapezoidMapTriFinder::CoordinateArray x, y; - if (!PyArg_ParseTuple(args, "O&O&:find_many", - &x.converter, &x, - &y.converter, &y)) { - return NULL; - } - - if (x.empty() || y.empty() || x.dim(0) != y.dim(0)) { - PyErr_SetString(PyExc_ValueError, - "x and y must be array-like with same shape"); - return NULL; - } - - TrapezoidMapTriFinder::TriIndexArray result; - CALL_CPP("find_many", (result = self->ptr->find_many(x, y))); - return result.pyobj(); -} - -const char* PyTrapezoidMapTriFinder_get_tree_stats__doc__ = - "get_tree_stats(self)\n" - "--\n\n" - "Return statistics about the tree used by the trapezoid map."; - -static PyObject* PyTrapezoidMapTriFinder_get_tree_stats(PyTrapezoidMapTriFinder* self, PyObject* args) -{ - PyObject* result; - CALL_CPP("get_tree_stats", (result = self->ptr->get_tree_stats())); - return result; -} - -const char* PyTrapezoidMapTriFinder_initialize__doc__ = - "initialize(self)\n" - "--\n\n" - "Initialize this object, creating the trapezoid map from the triangulation."; - -static PyObject* PyTrapezoidMapTriFinder_initialize(PyTrapezoidMapTriFinder* self, PyObject* args) -{ - CALL_CPP("initialize", (self->ptr->initialize())); - Py_RETURN_NONE; -} - -const char* PyTrapezoidMapTriFinder_print_tree__doc__ = - "print_tree(self)\n" - "--\n\n" - "Print the search tree as text to stdout; useful for debug purposes."; - -static PyObject* PyTrapezoidMapTriFinder_print_tree(PyTrapezoidMapTriFinder* self, PyObject* args) -{ - CALL_CPP("print_tree", (self->ptr->print_tree())); - Py_RETURN_NONE; -} - -static PyTypeObject *PyTrapezoidMapTriFinder_init_type() -{ - static PyMethodDef methods[] = { - {"find_many", - (PyCFunction)PyTrapezoidMapTriFinder_find_many, - METH_VARARGS, - PyTrapezoidMapTriFinder_find_many__doc__}, - {"get_tree_stats", - (PyCFunction)PyTrapezoidMapTriFinder_get_tree_stats, - METH_NOARGS, - PyTrapezoidMapTriFinder_get_tree_stats__doc__}, - {"initialize", - (PyCFunction)PyTrapezoidMapTriFinder_initialize, - METH_NOARGS, - PyTrapezoidMapTriFinder_initialize__doc__}, - {"print_tree", - (PyCFunction)PyTrapezoidMapTriFinder_print_tree, - METH_NOARGS, - PyTrapezoidMapTriFinder_print_tree__doc__}, - {NULL} - }; - PyTrapezoidMapTriFinderType.tp_name = "matplotlib._tri.TrapezoidMapTriFinder"; - PyTrapezoidMapTriFinderType.tp_doc = PyTrapezoidMapTriFinder_init__doc__; - PyTrapezoidMapTriFinderType.tp_basicsize = sizeof(PyTrapezoidMapTriFinder); - PyTrapezoidMapTriFinderType.tp_dealloc = (destructor)PyTrapezoidMapTriFinder_dealloc; - PyTrapezoidMapTriFinderType.tp_flags = Py_TPFLAGS_DEFAULT; - PyTrapezoidMapTriFinderType.tp_methods = methods; - PyTrapezoidMapTriFinderType.tp_new = PyTrapezoidMapTriFinder_new; - PyTrapezoidMapTriFinderType.tp_init = (initproc)PyTrapezoidMapTriFinder_init; - - return &PyTrapezoidMapTriFinderType; -} - -static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_tri" }; - -#pragma GCC visibility push(default) - -PyMODINIT_FUNC PyInit__tri(void) -{ - import_array(); - PyObject *m; - if (!(m = PyModule_Create(&moduledef)) - || prepare_and_add_type(PyTriangulation_init_type(), m) - || prepare_and_add_type(PyTriContourGenerator_init_type(), m) - || prepare_and_add_type(PyTrapezoidMapTriFinder_init_type(), m)) { - Py_XDECREF(m); - return NULL; - } - return m; -} - -#pragma GCC visibility pop From a4ffb293831026aa4bcc33f09e632bcfab8bf2eb Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sun, 20 Nov 2022 19:56:35 +0000 Subject: [PATCH 2/6] Preinstall pybind11 on no-build-isolation builds --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6798669c6ed6..ef0bb66efff5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -170,6 +170,11 @@ jobs: -r requirements/testing/all.txt \ ${{ matrix.extra-requirements }} + # Preinstall pybind11 on no-build-isolation builds. + if [[ "${{ matrix.name-suffix }}" == '(Minimum Versions)' ]]; then + python -m pip install 'pybind11>=2.6' + fi + # Install optional dependencies from PyPI. # Sphinx is needed to run sphinxext tests python -m pip install --upgrade sphinx From 1e936847d41ad71df6b11ef63da65d18d4ff6a04 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 30 Nov 2022 20:47:31 +0000 Subject: [PATCH 3/6] Triangulation.set_mask fix and tests --- lib/matplotlib/tests/test_triangulation.py | 40 +++++++++++++++++++--- lib/matplotlib/tri/_triangulation.py | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_triangulation.py b/lib/matplotlib/tests/test_triangulation.py index 8317c8bfc995..c292d82812d3 100644 --- a/lib/matplotlib/tests/test_triangulation.py +++ b/lib/matplotlib/tests/test_triangulation.py @@ -72,6 +72,29 @@ def test_triangulation_init(): mtri.Triangulation(x, y, [[0, 1, -1]]) +def test_triangulation_set_mask(): + x = [-1, 0, 1, 0] + y = [0, -1, 0, 1] + triangles = [[0, 1, 2], [2, 3, 0]] + triang = mtri.Triangulation(x, y, triangles) + + # Check neighbors, which forces creation of C++ triangulation + assert_array_equal(triang.neighbors, [[-1, -1, 1], [-1, -1, 0]]) + + # Set mask + triang.set_mask([False, True]) + assert_array_equal(triang.mask, [False, True]) + + # Reset mask + triang.set_mask(None) + assert triang.mask is None + + msg = r"mask array must have same length as triangles array" + for mask in ([False, True, False], [False], [True], False, True): + with pytest.raises(ValueError, match=msg): + triang.set_mask(mask) + + def test_delaunay(): # No duplicate points, regular grid. nx = 5 @@ -1205,11 +1228,18 @@ def test_internal_cpp_api(): r'triangulation x and y arrays'): triang.calculate_plane_coefficients([]) - with pytest.raises( - ValueError, - match=r'mask must be a 1D array with the same length as the ' - r'triangles array'): - triang.set_mask([0, 1]) + for mask in ([0, 1], None): + with pytest.raises( + ValueError, + match=r'mask must be a 1D array with the same length as the ' + r'triangles array'): + triang.set_mask(mask) + + triang.set_mask([True]) + assert_array_equal(triang.get_edges(), np.empty((0, 2))) + + triang.set_mask(()) # Equivalent to Python Triangulation mask=None + assert_array_equal(triang.get_edges(), [[1, 0], [2, 0], [2, 1]]) # C++ TriContourGenerator. with pytest.raises( diff --git a/lib/matplotlib/tri/_triangulation.py b/lib/matplotlib/tri/_triangulation.py index 8824e0385cf3..8f029ab77ad1 100644 --- a/lib/matplotlib/tri/_triangulation.py +++ b/lib/matplotlib/tri/_triangulation.py @@ -233,7 +233,7 @@ def set_mask(self, mask): # Set mask in C++ Triangulation. if self._cpp_triangulation is not None: - self._cpp_triangulation.set_mask(self.mask) + self._cpp_triangulation.set_mask(self.mask if self.mask is not None else ()) # Clear derived fields so they are recalculated when needed. self._edges = None From 286e48f6c5641c2940f4ca865aa48261c346f25c Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 30 Nov 2022 20:48:22 +0000 Subject: [PATCH 4/6] Review comments --- lib/matplotlib/tri/_triangulation.py | 3 ++- src/tri/_tri_wrapper.cpp | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tri/_triangulation.py b/lib/matplotlib/tri/_triangulation.py index 8f029ab77ad1..fa03a9c030f7 100644 --- a/lib/matplotlib/tri/_triangulation.py +++ b/lib/matplotlib/tri/_triangulation.py @@ -233,7 +233,8 @@ def set_mask(self, mask): # Set mask in C++ Triangulation. if self._cpp_triangulation is not None: - self._cpp_triangulation.set_mask(self.mask if self.mask is not None else ()) + self._cpp_triangulation.set_mask( + self.mask if self.mask is not None else ()) # Clear derived fields so they are recalculated when needed. self._edges = None diff --git a/src/tri/_tri_wrapper.cpp b/src/tri/_tri_wrapper.cpp index d4170cd19d55..1b0c3d75555e 100644 --- a/src/tri/_tri_wrapper.cpp +++ b/src/tri/_tri_wrapper.cpp @@ -17,7 +17,7 @@ PYBIND11_MODULE(_tri, m) { py::arg("neighbors"), py::arg("correct_triangle_orientations"), "Create a new C++ Triangulation object.\n" - "This should not be called directly, instead use the python class\n" + "This should not be called directly, use the python class\n" "matplotlib.tri.Triangulation instead.\n") .def("calculate_plane_coefficients", &Triangulation::calculate_plane_coefficients, "Calculate plane equation coefficients for all unmasked triangles.") @@ -34,7 +34,7 @@ PYBIND11_MODULE(_tri, m) { py::arg("triangulation"), py::arg("z"), "Create a new C++ TriContourGenerator object.\n" - "This should not be called directly, instead use the functions\n" + "This should not be called directly, use the functions\n" "matplotlib.axes.tricontour and tricontourf instead.\n") .def("create_contour", &TriContourGenerator::create_contour, "Create and return a non-filled contour.") @@ -45,7 +45,7 @@ PYBIND11_MODULE(_tri, m) { .def(py::init(), py::arg("triangulation"), "Create a new C++ TrapezoidMapTriFinder object.\n" - "This should not be called directly, instead use the python class\n" + "This should not be called directly, use the python class\n" "matplotlib.tri.TrapezoidMapTriFinder instead.\n") .def("find_many", &TrapezoidMapTriFinder::find_many, "Find indices of triangles containing the point coordinates (x, y).") From fbcf71438169d7b011f2316575b2c026befc492b Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 2 Dec 2022 10:09:16 +0000 Subject: [PATCH 5/6] Use MSVC 2017 on appveyor --- .appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index 3f38051bb86b..63801100307d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -11,6 +11,8 @@ branches: clone_depth: 50 +image: Visual Studio 2017 + environment: global: From 536035393b0cf7102d157fb29b01d0791740c226 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 2 Dec 2022 10:46:42 +0000 Subject: [PATCH 6/6] Preinstall pybind11 in CodeQL action --- .github/workflows/codeql-analysis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b2bf7eaba2b6..c10336410178 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,7 +42,8 @@ jobs: # https://github.com/jazzband/pip-tools/pull/1681 python -m pip install --upgrade \ certifi contourpy cycler fonttools kiwisolver importlib_resources \ - numpy packaging pillow pyparsing python-dateutil setuptools-scm + numpy packaging pillow pyparsing python-dateutil setuptools-scm \ + pybind11 echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV - name: Initialize CodeQL