Skip to content

Commit bb9ded8

Browse files
authored
Merge pull request #24013 from timhoffm/privatize-tri
Deprecate matplotlib.tri.* submodules
2 parents 394748d + 25f77d7 commit bb9ded8

21 files changed

+3065
-2988
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ per-file-ignores =
6060
lib/matplotlib/pyplot.py: F401, F811
6161
lib/matplotlib/tests/test_mathtext.py: E501
6262
lib/matplotlib/transforms.py: E201, E202, E203
63-
lib/matplotlib/tri/triinterpolate.py: E201, E221
63+
lib/matplotlib/tri/_triinterpolate.py: E201, E221
6464
lib/mpl_toolkits/axes_grid1/axes_size.py: E272
6565
lib/mpl_toolkits/axisartist/__init__.py: F401
6666
lib/mpl_toolkits/axisartist/angle_helper.py: E221
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``matplotlib.tri`` submodules are deprecated
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The ``matplotlib.tri.*`` submodules are deprecated. All functionality is
5+
available in ``matplotlib.tri`` directly and should be imported from there.

lib/matplotlib/tests/test_triangulation.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -614,15 +614,15 @@ def poisson_sparse_matrix(n, m):
614614

615615
# Instantiating a sparse Poisson matrix of size 48 x 48:
616616
(n, m) = (12, 4)
617-
mat = mtri.triinterpolate._Sparse_Matrix_coo(*poisson_sparse_matrix(n, m))
617+
mat = mtri._triinterpolate._Sparse_Matrix_coo(*poisson_sparse_matrix(n, m))
618618
mat.compress_csc()
619619
mat_dense = mat.to_dense()
620620
# Testing a sparse solve for all 48 basis vector
621621
for itest in range(n*m):
622622
b = np.zeros(n*m, dtype=np.float64)
623623
b[itest] = 1.
624-
x, _ = mtri.triinterpolate._cg(A=mat, b=b, x0=np.zeros(n*m),
625-
tol=1.e-10)
624+
x, _ = mtri._triinterpolate._cg(A=mat, b=b, x0=np.zeros(n*m),
625+
tol=1.e-10)
626626
assert_array_almost_equal(np.dot(mat_dense, x), b)
627627

628628
# 2) Same matrix with inserting 2 rows - cols with null diag terms
@@ -635,16 +635,16 @@ def poisson_sparse_matrix(n, m):
635635
rows = np.concatenate([rows, [i_zero, i_zero-1, j_zero, j_zero-1]])
636636
cols = np.concatenate([cols, [i_zero-1, i_zero, j_zero-1, j_zero]])
637637
vals = np.concatenate([vals, [1., 1., 1., 1.]])
638-
mat = mtri.triinterpolate._Sparse_Matrix_coo(vals, rows, cols,
639-
(n*m + 2, n*m + 2))
638+
mat = mtri._triinterpolate._Sparse_Matrix_coo(vals, rows, cols,
639+
(n*m + 2, n*m + 2))
640640
mat.compress_csc()
641641
mat_dense = mat.to_dense()
642642
# Testing a sparse solve for all 50 basis vec
643643
for itest in range(n*m + 2):
644644
b = np.zeros(n*m + 2, dtype=np.float64)
645645
b[itest] = 1.
646-
x, _ = mtri.triinterpolate._cg(A=mat, b=b, x0=np.ones(n*m + 2),
647-
tol=1.e-10)
646+
x, _ = mtri._triinterpolate._cg(A=mat, b=b, x0=np.ones(n * m + 2),
647+
tol=1.e-10)
648648
assert_array_almost_equal(np.dot(mat_dense, x), b)
649649

650650
# 3) Now a simple test that summation of duplicate (i.e. with same rows,
@@ -655,7 +655,7 @@ def poisson_sparse_matrix(n, m):
655655
cols = np.array([0, 1, 2, 1, 1, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
656656
dtype=np.int32)
657657
dim = (3, 3)
658-
mat = mtri.triinterpolate._Sparse_Matrix_coo(vals, rows, cols, dim)
658+
mat = mtri._triinterpolate._Sparse_Matrix_coo(vals, rows, cols, dim)
659659
mat.compress_csc()
660660
mat_dense = mat.to_dense()
661661
assert_array_almost_equal(mat_dense, np.array([
@@ -678,7 +678,7 @@ def test_triinterpcubic_geom_weights():
678678
y_rot = -np.sin(theta)*x + np.cos(theta)*y
679679
triang = mtri.Triangulation(x_rot, y_rot, triangles)
680680
cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom')
681-
dof_estimator = mtri.triinterpolate._DOF_estimator_geom(cubic_geom)
681+
dof_estimator = mtri._triinterpolate._DOF_estimator_geom(cubic_geom)
682682
weights = dof_estimator.compute_geom_weights()
683683
# Testing for the 4 possibilities...
684684
sum_w[0, :] = np.sum(weights, 1) - 1

lib/matplotlib/tri/__init__.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
Unstructured triangular grid functions.
33
"""
44

5-
from .triangulation import Triangulation
6-
from .tricontour import TriContourSet, tricontour, tricontourf
7-
from .trifinder import TriFinder, TrapezoidMapTriFinder
8-
from .triinterpolate import (TriInterpolator, LinearTriInterpolator,
9-
CubicTriInterpolator)
10-
from .tripcolor import tripcolor
11-
from .triplot import triplot
12-
from .trirefine import TriRefiner, UniformTriRefiner
13-
from .tritools import TriAnalyzer
5+
from ._triangulation import Triangulation
6+
from ._tricontour import TriContourSet, tricontour, tricontourf
7+
from ._trifinder import TriFinder, TrapezoidMapTriFinder
8+
from ._triinterpolate import (TriInterpolator, LinearTriInterpolator,
9+
CubicTriInterpolator)
10+
from ._tripcolor import tripcolor
11+
from ._triplot import triplot
12+
from ._trirefine import TriRefiner, UniformTriRefiner
13+
from ._tritools import TriAnalyzer
1414

1515

1616
__all__ = ["Triangulation",

lib/matplotlib/tri/_triangulation.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import numpy as np
2+
3+
from matplotlib import _api
4+
5+
6+
class Triangulation:
7+
"""
8+
An unstructured triangular grid consisting of npoints points and
9+
ntri triangles. The triangles can either be specified by the user
10+
or automatically generated using a Delaunay triangulation.
11+
12+
Parameters
13+
----------
14+
x, y : (npoints,) array-like
15+
Coordinates of grid points.
16+
triangles : (ntri, 3) array-like of int, optional
17+
For each triangle, the indices of the three points that make
18+
up the triangle, ordered in an anticlockwise manner. If not
19+
specified, the Delaunay triangulation is calculated.
20+
mask : (ntri,) array-like of bool, optional
21+
Which triangles are masked out.
22+
23+
Attributes
24+
----------
25+
triangles : (ntri, 3) array of int
26+
For each triangle, the indices of the three points that make
27+
up the triangle, ordered in an anticlockwise manner. If you want to
28+
take the *mask* into account, use `get_masked_triangles` instead.
29+
mask : (ntri, 3) array of bool
30+
Masked out triangles.
31+
is_delaunay : bool
32+
Whether the Triangulation is a calculated Delaunay
33+
triangulation (where *triangles* was not specified) or not.
34+
35+
Notes
36+
-----
37+
For a Triangulation to be valid it must not have duplicate points,
38+
triangles formed from colinear points, or overlapping triangles.
39+
"""
40+
def __init__(self, x, y, triangles=None, mask=None):
41+
from matplotlib import _qhull
42+
43+
self.x = np.asarray(x, dtype=np.float64)
44+
self.y = np.asarray(y, dtype=np.float64)
45+
if self.x.shape != self.y.shape or self.x.ndim != 1:
46+
raise ValueError("x and y must be equal-length 1D arrays, but "
47+
f"found shapes {self.x.shape!r} and "
48+
f"{self.y.shape!r}")
49+
50+
self.mask = None
51+
self._edges = None
52+
self._neighbors = None
53+
self.is_delaunay = False
54+
55+
if triangles is None:
56+
# No triangulation specified, so use matplotlib._qhull to obtain
57+
# Delaunay triangulation.
58+
self.triangles, self._neighbors = _qhull.delaunay(x, y)
59+
self.is_delaunay = True
60+
else:
61+
# Triangulation specified. Copy, since we may correct triangle
62+
# orientation.
63+
try:
64+
self.triangles = np.array(triangles, dtype=np.int32, order='C')
65+
except ValueError as e:
66+
raise ValueError('triangles must be a (N, 3) int array, not '
67+
f'{triangles!r}') from e
68+
if self.triangles.ndim != 2 or self.triangles.shape[1] != 3:
69+
raise ValueError(
70+
'triangles must be a (N, 3) int array, but found shape '
71+
f'{self.triangles.shape!r}')
72+
if self.triangles.max() >= len(self.x):
73+
raise ValueError(
74+
'triangles are indices into the points and must be in the '
75+
f'range 0 <= i < {len(self.x)} but found value '
76+
f'{self.triangles.max()}')
77+
if self.triangles.min() < 0:
78+
raise ValueError(
79+
'triangles are indices into the points and must be in the '
80+
f'range 0 <= i < {len(self.x)} but found value '
81+
f'{self.triangles.min()}')
82+
83+
# Underlying C++ object is not created until first needed.
84+
self._cpp_triangulation = None
85+
86+
# Default TriFinder not created until needed.
87+
self._trifinder = None
88+
89+
self.set_mask(mask)
90+
91+
def calculate_plane_coefficients(self, z):
92+
"""
93+
Calculate plane equation coefficients for all unmasked triangles from
94+
the point (x, y) coordinates and specified z-array of shape (npoints).
95+
The returned array has shape (npoints, 3) and allows z-value at (x, y)
96+
position in triangle tri to be calculated using
97+
``z = array[tri, 0] * x + array[tri, 1] * y + array[tri, 2]``.
98+
"""
99+
return self.get_cpp_triangulation().calculate_plane_coefficients(z)
100+
101+
@property
102+
def edges(self):
103+
"""
104+
Return integer array of shape (nedges, 2) containing all edges of
105+
non-masked triangles.
106+
107+
Each row defines an edge by its start point index and end point
108+
index. Each edge appears only once, i.e. for an edge between points
109+
*i* and *j*, there will only be either *(i, j)* or *(j, i)*.
110+
"""
111+
if self._edges is None:
112+
self._edges = self.get_cpp_triangulation().get_edges()
113+
return self._edges
114+
115+
def get_cpp_triangulation(self):
116+
"""
117+
Return the underlying C++ Triangulation object, creating it
118+
if necessary.
119+
"""
120+
from matplotlib import _tri
121+
if self._cpp_triangulation is None:
122+
self._cpp_triangulation = _tri.Triangulation(
123+
self.x, self.y, self.triangles, self.mask, self._edges,
124+
self._neighbors, not self.is_delaunay)
125+
return self._cpp_triangulation
126+
127+
def get_masked_triangles(self):
128+
"""
129+
Return an array of triangles taking the mask into account.
130+
"""
131+
if self.mask is not None:
132+
return self.triangles[~self.mask]
133+
else:
134+
return self.triangles
135+
136+
@staticmethod
137+
def get_from_args_and_kwargs(*args, **kwargs):
138+
"""
139+
Return a Triangulation object from the args and kwargs, and
140+
the remaining args and kwargs with the consumed values removed.
141+
142+
There are two alternatives: either the first argument is a
143+
Triangulation object, in which case it is returned, or the args
144+
and kwargs are sufficient to create a new Triangulation to
145+
return. In the latter case, see Triangulation.__init__ for
146+
the possible args and kwargs.
147+
"""
148+
if isinstance(args[0], Triangulation):
149+
triangulation, *args = args
150+
if 'triangles' in kwargs:
151+
_api.warn_external(
152+
"Passing the keyword 'triangles' has no effect when also "
153+
"passing a Triangulation")
154+
if 'mask' in kwargs:
155+
_api.warn_external(
156+
"Passing the keyword 'mask' has no effect when also "
157+
"passing a Triangulation")
158+
else:
159+
x, y, triangles, mask, args, kwargs = \
160+
Triangulation._extract_triangulation_params(args, kwargs)
161+
triangulation = Triangulation(x, y, triangles, mask)
162+
return triangulation, args, kwargs
163+
164+
@staticmethod
165+
def _extract_triangulation_params(args, kwargs):
166+
x, y, *args = args
167+
# Check triangles in kwargs then args.
168+
triangles = kwargs.pop('triangles', None)
169+
from_args = False
170+
if triangles is None and args:
171+
triangles = args[0]
172+
from_args = True
173+
if triangles is not None:
174+
try:
175+
triangles = np.asarray(triangles, dtype=np.int32)
176+
except ValueError:
177+
triangles = None
178+
if triangles is not None and (triangles.ndim != 2 or
179+
triangles.shape[1] != 3):
180+
triangles = None
181+
if triangles is not None and from_args:
182+
args = args[1:] # Consumed first item in args.
183+
# Check for mask in kwargs.
184+
mask = kwargs.pop('mask', None)
185+
return x, y, triangles, mask, args, kwargs
186+
187+
def get_trifinder(self):
188+
"""
189+
Return the default `matplotlib.tri.TriFinder` of this
190+
triangulation, creating it if necessary. This allows the same
191+
TriFinder object to be easily shared.
192+
"""
193+
if self._trifinder is None:
194+
# Default TriFinder class.
195+
from matplotlib.tri._trifinder import TrapezoidMapTriFinder
196+
self._trifinder = TrapezoidMapTriFinder(self)
197+
return self._trifinder
198+
199+
@property
200+
def neighbors(self):
201+
"""
202+
Return integer array of shape (ntri, 3) containing neighbor triangles.
203+
204+
For each triangle, the indices of the three triangles that
205+
share the same edges, or -1 if there is no such neighboring
206+
triangle. ``neighbors[i, j]`` is the triangle that is the neighbor
207+
to the edge from point index ``triangles[i, j]`` to point index
208+
``triangles[i, (j+1)%3]``.
209+
"""
210+
if self._neighbors is None:
211+
self._neighbors = self.get_cpp_triangulation().get_neighbors()
212+
return self._neighbors
213+
214+
def set_mask(self, mask):
215+
"""
216+
Set or clear the mask array.
217+
218+
Parameters
219+
----------
220+
mask : None or bool array of length ntri
221+
"""
222+
if mask is None:
223+
self.mask = None
224+
else:
225+
self.mask = np.asarray(mask, dtype=bool)
226+
if self.mask.shape != (self.triangles.shape[0],):
227+
raise ValueError('mask array must have same length as '
228+
'triangles array')
229+
230+
# Set mask in C++ Triangulation.
231+
if self._cpp_triangulation is not None:
232+
self._cpp_triangulation.set_mask(self.mask)
233+
234+
# Clear derived fields so they are recalculated when needed.
235+
self._edges = None
236+
self._neighbors = None
237+
238+
# Recalculate TriFinder if it exists.
239+
if self._trifinder is not None:
240+
self._trifinder._initialize()

0 commit comments

Comments
 (0)