trirefine.py 13 KB

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