_trirefine.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. """
  2. Mesh refinement for triangular grids.
  3. """
  4. import numpy as np
  5. from matplotlib import _api
  6. from matplotlib.tri._triangulation import Triangulation
  7. import matplotlib.tri._triinterpolate
  8. class TriRefiner:
  9. """
  10. Abstract base class for classes implementing mesh refinement.
  11. A TriRefiner encapsulates a Triangulation object and provides tools for
  12. mesh refinement and interpolation.
  13. Derived classes must implement:
  14. - ``refine_triangulation(return_tri_index=False, **kwargs)`` , where
  15. the optional keyword arguments *kwargs* are defined in each
  16. TriRefiner concrete implementation, and which returns:
  17. - a refined triangulation,
  18. - optionally (depending on *return_tri_index*), for each
  19. point of the refined triangulation: the index of
  20. the initial triangulation triangle to which it belongs.
  21. - ``refine_field(z, triinterpolator=None, **kwargs)``, where:
  22. - *z* array of field values (to refine) defined at the base
  23. triangulation nodes,
  24. - *triinterpolator* is an optional `~matplotlib.tri.TriInterpolator`,
  25. - the other optional keyword arguments *kwargs* are defined in
  26. each TriRefiner concrete implementation;
  27. and which returns (as a tuple) a refined triangular mesh and the
  28. interpolated values of the field at the refined triangulation nodes.
  29. """
  30. def __init__(self, triangulation):
  31. _api.check_isinstance(Triangulation, triangulation=triangulation)
  32. self._triangulation = triangulation
  33. class UniformTriRefiner(TriRefiner):
  34. """
  35. Uniform mesh refinement by recursive subdivisions.
  36. Parameters
  37. ----------
  38. triangulation : `~matplotlib.tri.Triangulation`
  39. The encapsulated triangulation (to be refined)
  40. """
  41. # See Also
  42. # --------
  43. # :class:`~matplotlib.tri.CubicTriInterpolator` and
  44. # :class:`~matplotlib.tri.TriAnalyzer`.
  45. # """
  46. def __init__(self, triangulation):
  47. super().__init__(triangulation)
  48. def refine_triangulation(self, return_tri_index=False, subdiv=3):
  49. """
  50. Compute a uniformly refined triangulation *refi_triangulation* of
  51. the encapsulated :attr:`triangulation`.
  52. This function refines the encapsulated triangulation by splitting each
  53. father triangle into 4 child sub-triangles built on the edges midside
  54. nodes, recursing *subdiv* times. In the end, each triangle is hence
  55. divided into ``4**subdiv`` child triangles.
  56. Parameters
  57. ----------
  58. return_tri_index : bool, default: False
  59. Whether an index table indicating the father triangle index of each
  60. point is returned.
  61. subdiv : int, default: 3
  62. Recursion level for the subdivision.
  63. Each triangle is divided into ``4**subdiv`` child triangles;
  64. hence, the default results in 64 refined subtriangles for each
  65. triangle of the initial triangulation.
  66. Returns
  67. -------
  68. refi_triangulation : `~matplotlib.tri.Triangulation`
  69. The refined triangulation.
  70. found_index : int array
  71. Index of the initial triangulation containing triangle, for each
  72. point of *refi_triangulation*.
  73. Returned only if *return_tri_index* is set to True.
  74. """
  75. refi_triangulation = self._triangulation
  76. ntri = refi_triangulation.triangles.shape[0]
  77. # Computes the triangulation ancestors numbers in the reference
  78. # triangulation.
  79. ancestors = np.arange(ntri, dtype=np.int32)
  80. for _ in range(subdiv):
  81. refi_triangulation, ancestors = self._refine_triangulation_once(
  82. refi_triangulation, ancestors)
  83. refi_npts = refi_triangulation.x.shape[0]
  84. refi_triangles = refi_triangulation.triangles
  85. # Now we compute found_index table if needed
  86. if return_tri_index:
  87. # We have to initialize found_index with -1 because some nodes
  88. # may very well belong to no triangle at all, e.g., in case of
  89. # Delaunay Triangulation with DuplicatePointWarning.
  90. found_index = np.full(refi_npts, -1, dtype=np.int32)
  91. tri_mask = self._triangulation.mask
  92. if tri_mask is None:
  93. found_index[refi_triangles] = np.repeat(ancestors,
  94. 3).reshape(-1, 3)
  95. else:
  96. # There is a subtlety here: we want to avoid whenever possible
  97. # that refined points container is a masked triangle (which
  98. # would result in artifacts in plots).
  99. # So we impose the numbering from masked ancestors first,
  100. # then overwrite it with unmasked ancestor numbers.
  101. ancestor_mask = tri_mask[ancestors]
  102. found_index[refi_triangles[ancestor_mask, :]
  103. ] = np.repeat(ancestors[ancestor_mask],
  104. 3).reshape(-1, 3)
  105. found_index[refi_triangles[~ancestor_mask, :]
  106. ] = np.repeat(ancestors[~ancestor_mask],
  107. 3).reshape(-1, 3)
  108. return refi_triangulation, found_index
  109. else:
  110. return refi_triangulation
  111. def refine_field(self, z, triinterpolator=None, subdiv=3):
  112. """
  113. Refine a field defined on the encapsulated triangulation.
  114. Parameters
  115. ----------
  116. z : (npoints,) array-like
  117. Values of the field to refine, defined at the nodes of the
  118. encapsulated triangulation. (``n_points`` is the number of points
  119. in the initial triangulation)
  120. triinterpolator : `~matplotlib.tri.TriInterpolator`, optional
  121. Interpolator used for field interpolation. If not specified,
  122. a `~matplotlib.tri.CubicTriInterpolator` will be used.
  123. subdiv : int, default: 3
  124. Recursion level for the subdivision.
  125. Each triangle is divided into ``4**subdiv`` child triangles.
  126. Returns
  127. -------
  128. refi_tri : `~matplotlib.tri.Triangulation`
  129. The returned refined triangulation.
  130. refi_z : 1D array of length: *refi_tri* node count.
  131. The returned interpolated field (at *refi_tri* nodes).
  132. """
  133. if triinterpolator is None:
  134. interp = matplotlib.tri.CubicTriInterpolator(
  135. self._triangulation, z)
  136. else:
  137. _api.check_isinstance(matplotlib.tri.TriInterpolator,
  138. triinterpolator=triinterpolator)
  139. interp = triinterpolator
  140. refi_tri, found_index = self.refine_triangulation(
  141. subdiv=subdiv, return_tri_index=True)
  142. refi_z = interp._interpolate_multikeys(
  143. refi_tri.x, refi_tri.y, tri_index=found_index)[0]
  144. return refi_tri, refi_z
  145. @staticmethod
  146. def _refine_triangulation_once(triangulation, ancestors=None):
  147. """
  148. Refine a `.Triangulation` by splitting each triangle into 4
  149. child-masked_triangles built on the edges midside nodes.
  150. Masked triangles, if present, are also split, but their children
  151. returned masked.
  152. If *ancestors* is not provided, returns only a new triangulation:
  153. child_triangulation.
  154. If the array-like key table *ancestor* is given, it shall be of shape
  155. (ntri,) where ntri is the number of *triangulation* masked_triangles.
  156. In this case, the function returns
  157. (child_triangulation, child_ancestors)
  158. child_ancestors is defined so that the 4 child masked_triangles share
  159. the same index as their father: child_ancestors.shape = (4 * ntri,).
  160. """
  161. x = triangulation.x
  162. y = triangulation.y
  163. # According to tri.triangulation doc:
  164. # neighbors[i, j] is the triangle that is the neighbor
  165. # to the edge from point index masked_triangles[i, j] to point
  166. # index masked_triangles[i, (j+1)%3].
  167. neighbors = triangulation.neighbors
  168. triangles = triangulation.triangles
  169. npts = np.shape(x)[0]
  170. ntri = np.shape(triangles)[0]
  171. if ancestors is not None:
  172. ancestors = np.asarray(ancestors)
  173. if np.shape(ancestors) != (ntri,):
  174. raise ValueError(
  175. "Incompatible shapes provide for "
  176. "triangulation.masked_triangles and ancestors: "
  177. f"{np.shape(triangles)} and {np.shape(ancestors)}")
  178. # Initiating tables refi_x and refi_y of the refined triangulation
  179. # points
  180. # hint: each apex is shared by 2 masked_triangles except the borders.
  181. borders = np.sum(neighbors == -1)
  182. added_pts = (3*ntri + borders) // 2
  183. refi_npts = npts + added_pts
  184. refi_x = np.zeros(refi_npts)
  185. refi_y = np.zeros(refi_npts)
  186. # First part of refi_x, refi_y is just the initial points
  187. refi_x[:npts] = x
  188. refi_y[:npts] = y
  189. # Second part contains the edge midside nodes.
  190. # Each edge belongs to 1 triangle (if border edge) or is shared by 2
  191. # masked_triangles (interior edge).
  192. # We first build 2 * ntri arrays of edge starting nodes (edge_elems,
  193. # edge_apexes); we then extract only the masters to avoid overlaps.
  194. # The so-called 'master' is the triangle with biggest index
  195. # The 'slave' is the triangle with lower index
  196. # (can be -1 if border edge)
  197. # For slave and master we will identify the apex pointing to the edge
  198. # start
  199. edge_elems = np.tile(np.arange(ntri, dtype=np.int32), 3)
  200. edge_apexes = np.repeat(np.arange(3, dtype=np.int32), ntri)
  201. edge_neighbors = neighbors[edge_elems, edge_apexes]
  202. mask_masters = (edge_elems > edge_neighbors)
  203. # Identifying the "masters" and adding to refi_x, refi_y vec
  204. masters = edge_elems[mask_masters]
  205. apex_masters = edge_apexes[mask_masters]
  206. x_add = (x[triangles[masters, apex_masters]] +
  207. x[triangles[masters, (apex_masters+1) % 3]]) * 0.5
  208. y_add = (y[triangles[masters, apex_masters]] +
  209. y[triangles[masters, (apex_masters+1) % 3]]) * 0.5
  210. refi_x[npts:] = x_add
  211. refi_y[npts:] = y_add
  212. # Building the new masked_triangles; each old masked_triangles hosts
  213. # 4 new masked_triangles
  214. # there are 6 pts to identify per 'old' triangle, 3 new_pt_corner and
  215. # 3 new_pt_midside
  216. new_pt_corner = triangles
  217. # What is the index in refi_x, refi_y of point at middle of apex iapex
  218. # of elem ielem ?
  219. # If ielem is the apex master: simple count, given the way refi_x was
  220. # built.
  221. # If ielem is the apex slave: yet we do not know; but we will soon
  222. # using the neighbors table.
  223. new_pt_midside = np.empty([ntri, 3], dtype=np.int32)
  224. cum_sum = npts
  225. for imid in range(3):
  226. mask_st_loc = (imid == apex_masters)
  227. n_masters_loc = np.sum(mask_st_loc)
  228. elem_masters_loc = masters[mask_st_loc]
  229. new_pt_midside[:, imid][elem_masters_loc] = np.arange(
  230. n_masters_loc, dtype=np.int32) + cum_sum
  231. cum_sum += n_masters_loc
  232. # Now dealing with slave elems.
  233. # for each slave element we identify the master and then the inode
  234. # once slave_masters is identified, slave_masters_apex is such that:
  235. # neighbors[slaves_masters, slave_masters_apex] == slaves
  236. mask_slaves = np.logical_not(mask_masters)
  237. slaves = edge_elems[mask_slaves]
  238. slaves_masters = edge_neighbors[mask_slaves]
  239. diff_table = np.abs(neighbors[slaves_masters, :] -
  240. np.outer(slaves, np.ones(3, dtype=np.int32)))
  241. slave_masters_apex = np.argmin(diff_table, axis=1)
  242. slaves_apex = edge_apexes[mask_slaves]
  243. new_pt_midside[slaves, slaves_apex] = new_pt_midside[
  244. slaves_masters, slave_masters_apex]
  245. # Builds the 4 child masked_triangles
  246. child_triangles = np.empty([ntri*4, 3], dtype=np.int32)
  247. child_triangles[0::4, :] = np.vstack([
  248. new_pt_corner[:, 0], new_pt_midside[:, 0],
  249. new_pt_midside[:, 2]]).T
  250. child_triangles[1::4, :] = np.vstack([
  251. new_pt_corner[:, 1], new_pt_midside[:, 1],
  252. new_pt_midside[:, 0]]).T
  253. child_triangles[2::4, :] = np.vstack([
  254. new_pt_corner[:, 2], new_pt_midside[:, 2],
  255. new_pt_midside[:, 1]]).T
  256. child_triangles[3::4, :] = np.vstack([
  257. new_pt_midside[:, 0], new_pt_midside[:, 1],
  258. new_pt_midside[:, 2]]).T
  259. child_triangulation = Triangulation(refi_x, refi_y, child_triangles)
  260. # Builds the child mask
  261. if triangulation.mask is not None:
  262. child_triangulation.set_mask(np.repeat(triangulation.mask, 4))
  263. if ancestors is None:
  264. return child_triangulation
  265. else:
  266. return child_triangulation, np.repeat(ancestors, 4)