trirefine.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. """
  2. Mesh refinement for triangular grids.
  3. """
  4. import numpy as np
  5. from matplotlib import cbook
  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. cbook._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. TriRefiner.__init__(self, triangulation)
  48. def refine_triangulation(self, return_tri_index=False, subdiv=3):
  49. """
  50. Compute an 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 : 1d-array-like of length ``n_points``
  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. cbook._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 triangulation"
  176. ".masked_triangles and ancestors: {0} and {1}".format(
  177. np.shape(triangles), 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)