Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Mesh refinement for triangular grids. 

3""" 

4 

5import numpy as np 

6 

7from matplotlib import cbook 

8from matplotlib.tri.triangulation import Triangulation 

9import matplotlib.tri.triinterpolate 

10 

11 

12class TriRefiner: 

13 """ 

14 Abstract base class for classes implementing mesh refinement. 

15 

16 A TriRefiner encapsulates a Triangulation object and provides tools for 

17 mesh refinement and interpolation. 

18 

19 Derived classes must implements: 

20 

21 - ``refine_triangulation(return_tri_index=False, **kwargs)`` , where 

22 the optional keyword arguments *kwargs* are defined in each 

23 TriRefiner concrete implementation, and which returns: 

24 

25 - a refined triangulation 

26 - optionally (depending on *return_tri_index*), for each 

27 point of the refined triangulation: the index of 

28 the initial triangulation triangle to which it belongs. 

29 

30 - ``refine_field(z, triinterpolator=None, **kwargs)`` , where: 

31 

32 - *z* array of field values (to refine) defined at the base 

33 triangulation nodes 

34 - *triinterpolator* is a 

35 :class:`~matplotlib.tri.TriInterpolator` (optional) 

36 - the other optional keyword arguments *kwargs* are defined in 

37 each TriRefiner concrete implementation 

38 

39 and which returns (as a tuple) a refined triangular mesh and the 

40 interpolated values of the field at the refined triangulation nodes. 

41 

42 """ 

43 def __init__(self, triangulation): 

44 cbook._check_isinstance(Triangulation, triangulation=triangulation) 

45 self._triangulation = triangulation 

46 

47 

48class UniformTriRefiner(TriRefiner): 

49 """ 

50 Uniform mesh refinement by recursive subdivisions. 

51 

52 Parameters 

53 ---------- 

54 triangulation : :class:`~matplotlib.tri.Triangulation` 

55 The encapsulated triangulation (to be refined) 

56 """ 

57# See Also 

58# -------- 

59# :class:`~matplotlib.tri.CubicTriInterpolator` and 

60# :class:`~matplotlib.tri.TriAnalyzer`. 

61# """ 

62 def __init__(self, triangulation): 

63 TriRefiner.__init__(self, triangulation) 

64 

65 def refine_triangulation(self, return_tri_index=False, subdiv=3): 

66 """ 

67 Computes an uniformly refined triangulation *refi_triangulation* of 

68 the encapsulated :attr:`triangulation`. 

69 

70 This function refines the encapsulated triangulation by splitting each 

71 father triangle into 4 child sub-triangles built on the edges midside 

72 nodes, recursively (level of recursion *subdiv*). 

73 In the end, each triangle is hence divided into ``4**subdiv`` 

74 child triangles. 

75 The default value for *subdiv* is 3 resulting in 64 refined 

76 subtriangles for each triangle of the initial triangulation. 

77 

78 Parameters 

79 ---------- 

80 return_tri_index : boolean, optional 

81 Boolean indicating whether an index table indicating the father 

82 triangle index of each point will be returned. Default value 

83 False. 

84 subdiv : integer, optional 

85 Recursion level for the subdivision. Defaults value 3. 

86 Each triangle will be divided into ``4**subdiv`` child triangles. 

87 

88 Returns 

89 ------- 

90 refi_triangulation : :class:`~matplotlib.tri.Triangulation` 

91 The returned refined triangulation 

92 found_index : array-like of integers 

93 Index of the initial triangulation containing triangle, for each 

94 point of *refi_triangulation*. 

95 Returned only if *return_tri_index* is set to True. 

96 

97 """ 

98 refi_triangulation = self._triangulation 

99 ntri = refi_triangulation.triangles.shape[0] 

100 

101 # Computes the triangulation ancestors numbers in the reference 

102 # triangulation. 

103 ancestors = np.arange(ntri, dtype=np.int32) 

104 for _ in range(subdiv): 

105 refi_triangulation, ancestors = self._refine_triangulation_once( 

106 refi_triangulation, ancestors) 

107 refi_npts = refi_triangulation.x.shape[0] 

108 refi_triangles = refi_triangulation.triangles 

109 

110 # Now we compute found_index table if needed 

111 if return_tri_index: 

112 # We have to initialize found_index with -1 because some nodes 

113 # may very well belong to no triangle at all, e.g., in case of 

114 # Delaunay Triangulation with DuplicatePointWarning. 

115 found_index = np.full(refi_npts, -1, dtype=np.int32) 

116 tri_mask = self._triangulation.mask 

117 if tri_mask is None: 

118 found_index[refi_triangles] = np.repeat(ancestors, 

119 3).reshape(-1, 3) 

120 else: 

121 # There is a subtlety here: we want to avoid whenever possible 

122 # that refined points container is a masked triangle (which 

123 # would result in artifacts in plots). 

124 # So we impose the numbering from masked ancestors first, 

125 # then overwrite it with unmasked ancestor numbers. 

126 ancestor_mask = tri_mask[ancestors] 

127 found_index[refi_triangles[ancestor_mask, :] 

128 ] = np.repeat(ancestors[ancestor_mask], 

129 3).reshape(-1, 3) 

130 found_index[refi_triangles[~ancestor_mask, :] 

131 ] = np.repeat(ancestors[~ancestor_mask], 

132 3).reshape(-1, 3) 

133 return refi_triangulation, found_index 

134 else: 

135 return refi_triangulation 

136 

137 def refine_field(self, z, triinterpolator=None, subdiv=3): 

138 """ 

139 Refines a field defined on the encapsulated triangulation. 

140 

141 Returns *refi_tri* (refined triangulation), *refi_z* (interpolated 

142 values of the field at the node of the refined triangulation). 

143 

144 Parameters 

145 ---------- 

146 z : 1d-array-like of length ``n_points`` 

147 Values of the field to refine, defined at the nodes of the 

148 encapsulated triangulation. (``n_points`` is the number of points 

149 in the initial triangulation) 

150 triinterpolator : :class:`~matplotlib.tri.TriInterpolator`, optional 

151 Interpolator used for field interpolation. If not specified, 

152 a :class:`~matplotlib.tri.CubicTriInterpolator` will 

153 be used. 

154 subdiv : integer, optional 

155 Recursion level for the subdivision. Defaults to 3. 

156 Each triangle will be divided into ``4**subdiv`` child triangles. 

157 

158 Returns 

159 ------- 

160 refi_tri : :class:`~matplotlib.tri.Triangulation` object 

161 The returned refined triangulation 

162 refi_z : 1d array of length: *refi_tri* node count. 

163 The returned interpolated field (at *refi_tri* nodes) 

164 """ 

165 if triinterpolator is None: 

166 interp = matplotlib.tri.CubicTriInterpolator( 

167 self._triangulation, z) 

168 else: 

169 cbook._check_isinstance(matplotlib.tri.TriInterpolator, 

170 triinterpolator=triinterpolator) 

171 interp = triinterpolator 

172 

173 refi_tri, found_index = self.refine_triangulation( 

174 subdiv=subdiv, return_tri_index=True) 

175 refi_z = interp._interpolate_multikeys( 

176 refi_tri.x, refi_tri.y, tri_index=found_index)[0] 

177 return refi_tri, refi_z 

178 

179 @staticmethod 

180 def _refine_triangulation_once(triangulation, ancestors=None): 

181 """ 

182 This function refines a matplotlib.tri *triangulation* by splitting 

183 each triangle into 4 child-masked_triangles built on the edges midside 

184 nodes. 

185 The masked triangles, if present, are also split but their children 

186 returned masked. 

187 

188 If *ancestors* is not provided, returns only a new triangulation: 

189 child_triangulation. 

190 

191 If the array-like key table *ancestor* is given, it shall be of shape 

192 (ntri,) where ntri is the number of *triangulation* masked_triangles. 

193 In this case, the function returns 

194 (child_triangulation, child_ancestors) 

195 child_ancestors is defined so that the 4 child masked_triangles share 

196 the same index as their father: child_ancestors.shape = (4 * ntri,). 

197 

198 """ 

199 x = triangulation.x 

200 y = triangulation.y 

201 

202 # According to tri.triangulation doc: 

203 # neighbors[i, j] is the triangle that is the neighbor 

204 # to the edge from point index masked_triangles[i, j] to point 

205 # index masked_triangles[i, (j+1)%3]. 

206 neighbors = triangulation.neighbors 

207 triangles = triangulation.triangles 

208 npts = np.shape(x)[0] 

209 ntri = np.shape(triangles)[0] 

210 if ancestors is not None: 

211 ancestors = np.asarray(ancestors) 

212 if np.shape(ancestors) != (ntri,): 

213 raise ValueError( 

214 "Incompatible shapes provide for triangulation" 

215 ".masked_triangles and ancestors: {0} and {1}".format( 

216 np.shape(triangles), np.shape(ancestors))) 

217 

218 # Initiating tables refi_x and refi_y of the refined triangulation 

219 # points 

220 # hint: each apex is shared by 2 masked_triangles except the borders. 

221 borders = np.sum(neighbors == -1) 

222 added_pts = (3*ntri + borders) // 2 

223 refi_npts = npts + added_pts 

224 refi_x = np.zeros(refi_npts) 

225 refi_y = np.zeros(refi_npts) 

226 

227 # First part of refi_x, refi_y is just the initial points 

228 refi_x[:npts] = x 

229 refi_y[:npts] = y 

230 

231 # Second part contains the edge midside nodes. 

232 # Each edge belongs to 1 triangle (if border edge) or is shared by 2 

233 # masked_triangles (interior edge). 

234 # We first build 2 * ntri arrays of edge starting nodes (edge_elems, 

235 # edge_apexes); we then extract only the masters to avoid overlaps. 

236 # The so-called 'master' is the triangle with biggest index 

237 # The 'slave' is the triangle with lower index 

238 # (can be -1 if border edge) 

239 # For slave and master we will identify the apex pointing to the edge 

240 # start 

241 edge_elems = np.tile(np.arange(ntri, dtype=np.int32), 3) 

242 edge_apexes = np.repeat(np.arange(3, dtype=np.int32), ntri) 

243 edge_neighbors = neighbors[edge_elems, edge_apexes] 

244 mask_masters = (edge_elems > edge_neighbors) 

245 

246 # Identifying the "masters" and adding to refi_x, refi_y vec 

247 masters = edge_elems[mask_masters] 

248 apex_masters = edge_apexes[mask_masters] 

249 x_add = (x[triangles[masters, apex_masters]] + 

250 x[triangles[masters, (apex_masters+1) % 3]]) * 0.5 

251 y_add = (y[triangles[masters, apex_masters]] + 

252 y[triangles[masters, (apex_masters+1) % 3]]) * 0.5 

253 refi_x[npts:] = x_add 

254 refi_y[npts:] = y_add 

255 

256 # Building the new masked_triangles; each old masked_triangles hosts 

257 # 4 new masked_triangles 

258 # there are 6 pts to identify per 'old' triangle, 3 new_pt_corner and 

259 # 3 new_pt_midside 

260 new_pt_corner = triangles 

261 

262 # What is the index in refi_x, refi_y of point at middle of apex iapex 

263 # of elem ielem ? 

264 # If ielem is the apex master: simple count, given the way refi_x was 

265 # built. 

266 # If ielem is the apex slave: yet we do not know; but we will soon 

267 # using the neighbors table. 

268 new_pt_midside = np.empty([ntri, 3], dtype=np.int32) 

269 cum_sum = npts 

270 for imid in range(3): 

271 mask_st_loc = (imid == apex_masters) 

272 n_masters_loc = np.sum(mask_st_loc) 

273 elem_masters_loc = masters[mask_st_loc] 

274 new_pt_midside[:, imid][elem_masters_loc] = np.arange( 

275 n_masters_loc, dtype=np.int32) + cum_sum 

276 cum_sum += n_masters_loc 

277 

278 # Now dealing with slave elems. 

279 # for each slave element we identify the master and then the inode 

280 # once slave_masters is identified, slave_masters_apex is such that: 

281 # neighbors[slaves_masters, slave_masters_apex] == slaves 

282 mask_slaves = np.logical_not(mask_masters) 

283 slaves = edge_elems[mask_slaves] 

284 slaves_masters = edge_neighbors[mask_slaves] 

285 diff_table = np.abs(neighbors[slaves_masters, :] - 

286 np.outer(slaves, np.ones(3, dtype=np.int32))) 

287 slave_masters_apex = np.argmin(diff_table, axis=1) 

288 slaves_apex = edge_apexes[mask_slaves] 

289 new_pt_midside[slaves, slaves_apex] = new_pt_midside[ 

290 slaves_masters, slave_masters_apex] 

291 

292 # Builds the 4 child masked_triangles 

293 child_triangles = np.empty([ntri*4, 3], dtype=np.int32) 

294 child_triangles[0::4, :] = np.vstack([ 

295 new_pt_corner[:, 0], new_pt_midside[:, 0], 

296 new_pt_midside[:, 2]]).T 

297 child_triangles[1::4, :] = np.vstack([ 

298 new_pt_corner[:, 1], new_pt_midside[:, 1], 

299 new_pt_midside[:, 0]]).T 

300 child_triangles[2::4, :] = np.vstack([ 

301 new_pt_corner[:, 2], new_pt_midside[:, 2], 

302 new_pt_midside[:, 1]]).T 

303 child_triangles[3::4, :] = np.vstack([ 

304 new_pt_midside[:, 0], new_pt_midside[:, 1], 

305 new_pt_midside[:, 2]]).T 

306 child_triangulation = Triangulation(refi_x, refi_y, child_triangles) 

307 

308 # Builds the child mask 

309 if triangulation.mask is not None: 

310 child_triangulation.set_mask(np.repeat(triangulation.mask, 4)) 

311 

312 if ancestors is None: 

313 return child_triangulation 

314 else: 

315 return child_triangulation, np.repeat(ancestors, 4)