123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- """
- Mesh refinement for triangular grids.
- """
- import numpy as np
- from matplotlib import _api
- from matplotlib.tri._triangulation import Triangulation
- import matplotlib.tri._triinterpolate
- class TriRefiner:
- """
- Abstract base class for classes implementing mesh refinement.
- A TriRefiner encapsulates a Triangulation object and provides tools for
- mesh refinement and interpolation.
- Derived classes must implement:
- - ``refine_triangulation(return_tri_index=False, **kwargs)`` , where
- the optional keyword arguments *kwargs* are defined in each
- TriRefiner concrete implementation, and which returns:
- - a refined triangulation,
- - optionally (depending on *return_tri_index*), for each
- point of the refined triangulation: the index of
- the initial triangulation triangle to which it belongs.
- - ``refine_field(z, triinterpolator=None, **kwargs)``, where:
- - *z* array of field values (to refine) defined at the base
- triangulation nodes,
- - *triinterpolator* is an optional `~matplotlib.tri.TriInterpolator`,
- - the other optional keyword arguments *kwargs* are defined in
- each TriRefiner concrete implementation;
- and which returns (as a tuple) a refined triangular mesh and the
- interpolated values of the field at the refined triangulation nodes.
- """
- def __init__(self, triangulation):
- _api.check_isinstance(Triangulation, triangulation=triangulation)
- self._triangulation = triangulation
- class UniformTriRefiner(TriRefiner):
- """
- Uniform mesh refinement by recursive subdivisions.
- Parameters
- ----------
- triangulation : `~matplotlib.tri.Triangulation`
- The encapsulated triangulation (to be refined)
- """
- # See Also
- # --------
- # :class:`~matplotlib.tri.CubicTriInterpolator` and
- # :class:`~matplotlib.tri.TriAnalyzer`.
- # """
- def __init__(self, triangulation):
- super().__init__(triangulation)
- def refine_triangulation(self, return_tri_index=False, subdiv=3):
- """
- Compute a uniformly refined triangulation *refi_triangulation* of
- the encapsulated :attr:`triangulation`.
- This function refines the encapsulated triangulation by splitting each
- father triangle into 4 child sub-triangles built on the edges midside
- nodes, recursing *subdiv* times. In the end, each triangle is hence
- divided into ``4**subdiv`` child triangles.
- Parameters
- ----------
- return_tri_index : bool, default: False
- Whether an index table indicating the father triangle index of each
- point is returned.
- subdiv : int, default: 3
- Recursion level for the subdivision.
- Each triangle is divided into ``4**subdiv`` child triangles;
- hence, the default results in 64 refined subtriangles for each
- triangle of the initial triangulation.
- Returns
- -------
- refi_triangulation : `~matplotlib.tri.Triangulation`
- The refined triangulation.
- found_index : int array
- Index of the initial triangulation containing triangle, for each
- point of *refi_triangulation*.
- Returned only if *return_tri_index* is set to True.
- """
- refi_triangulation = self._triangulation
- ntri = refi_triangulation.triangles.shape[0]
- # Computes the triangulation ancestors numbers in the reference
- # triangulation.
- ancestors = np.arange(ntri, dtype=np.int32)
- for _ in range(subdiv):
- refi_triangulation, ancestors = self._refine_triangulation_once(
- refi_triangulation, ancestors)
- refi_npts = refi_triangulation.x.shape[0]
- refi_triangles = refi_triangulation.triangles
- # Now we compute found_index table if needed
- if return_tri_index:
- # We have to initialize found_index with -1 because some nodes
- # may very well belong to no triangle at all, e.g., in case of
- # Delaunay Triangulation with DuplicatePointWarning.
- found_index = np.full(refi_npts, -1, dtype=np.int32)
- tri_mask = self._triangulation.mask
- if tri_mask is None:
- found_index[refi_triangles] = np.repeat(ancestors,
- 3).reshape(-1, 3)
- else:
- # There is a subtlety here: we want to avoid whenever possible
- # that refined points container is a masked triangle (which
- # would result in artifacts in plots).
- # So we impose the numbering from masked ancestors first,
- # then overwrite it with unmasked ancestor numbers.
- ancestor_mask = tri_mask[ancestors]
- found_index[refi_triangles[ancestor_mask, :]
- ] = np.repeat(ancestors[ancestor_mask],
- 3).reshape(-1, 3)
- found_index[refi_triangles[~ancestor_mask, :]
- ] = np.repeat(ancestors[~ancestor_mask],
- 3).reshape(-1, 3)
- return refi_triangulation, found_index
- else:
- return refi_triangulation
- def refine_field(self, z, triinterpolator=None, subdiv=3):
- """
- Refine a field defined on the encapsulated triangulation.
- Parameters
- ----------
- z : (npoints,) array-like
- Values of the field to refine, defined at the nodes of the
- encapsulated triangulation. (``n_points`` is the number of points
- in the initial triangulation)
- triinterpolator : `~matplotlib.tri.TriInterpolator`, optional
- Interpolator used for field interpolation. If not specified,
- a `~matplotlib.tri.CubicTriInterpolator` will be used.
- subdiv : int, default: 3
- Recursion level for the subdivision.
- Each triangle is divided into ``4**subdiv`` child triangles.
- Returns
- -------
- refi_tri : `~matplotlib.tri.Triangulation`
- The returned refined triangulation.
- refi_z : 1D array of length: *refi_tri* node count.
- The returned interpolated field (at *refi_tri* nodes).
- """
- if triinterpolator is None:
- interp = matplotlib.tri.CubicTriInterpolator(
- self._triangulation, z)
- else:
- _api.check_isinstance(matplotlib.tri.TriInterpolator,
- triinterpolator=triinterpolator)
- interp = triinterpolator
- refi_tri, found_index = self.refine_triangulation(
- subdiv=subdiv, return_tri_index=True)
- refi_z = interp._interpolate_multikeys(
- refi_tri.x, refi_tri.y, tri_index=found_index)[0]
- return refi_tri, refi_z
- @staticmethod
- def _refine_triangulation_once(triangulation, ancestors=None):
- """
- Refine a `.Triangulation` by splitting each triangle into 4
- child-masked_triangles built on the edges midside nodes.
- Masked triangles, if present, are also split, but their children
- returned masked.
- If *ancestors* is not provided, returns only a new triangulation:
- child_triangulation.
- If the array-like key table *ancestor* is given, it shall be of shape
- (ntri,) where ntri is the number of *triangulation* masked_triangles.
- In this case, the function returns
- (child_triangulation, child_ancestors)
- child_ancestors is defined so that the 4 child masked_triangles share
- the same index as their father: child_ancestors.shape = (4 * ntri,).
- """
- x = triangulation.x
- y = triangulation.y
- # According to tri.triangulation doc:
- # neighbors[i, j] is the triangle that is the neighbor
- # to the edge from point index masked_triangles[i, j] to point
- # index masked_triangles[i, (j+1)%3].
- neighbors = triangulation.neighbors
- triangles = triangulation.triangles
- npts = np.shape(x)[0]
- ntri = np.shape(triangles)[0]
- if ancestors is not None:
- ancestors = np.asarray(ancestors)
- if np.shape(ancestors) != (ntri,):
- raise ValueError(
- "Incompatible shapes provide for "
- "triangulation.masked_triangles and ancestors: "
- f"{np.shape(triangles)} and {np.shape(ancestors)}")
- # Initiating tables refi_x and refi_y of the refined triangulation
- # points
- # hint: each apex is shared by 2 masked_triangles except the borders.
- borders = np.sum(neighbors == -1)
- added_pts = (3*ntri + borders) // 2
- refi_npts = npts + added_pts
- refi_x = np.zeros(refi_npts)
- refi_y = np.zeros(refi_npts)
- # First part of refi_x, refi_y is just the initial points
- refi_x[:npts] = x
- refi_y[:npts] = y
- # Second part contains the edge midside nodes.
- # Each edge belongs to 1 triangle (if border edge) or is shared by 2
- # masked_triangles (interior edge).
- # We first build 2 * ntri arrays of edge starting nodes (edge_elems,
- # edge_apexes); we then extract only the masters to avoid overlaps.
- # The so-called 'master' is the triangle with biggest index
- # The 'slave' is the triangle with lower index
- # (can be -1 if border edge)
- # For slave and master we will identify the apex pointing to the edge
- # start
- edge_elems = np.tile(np.arange(ntri, dtype=np.int32), 3)
- edge_apexes = np.repeat(np.arange(3, dtype=np.int32), ntri)
- edge_neighbors = neighbors[edge_elems, edge_apexes]
- mask_masters = (edge_elems > edge_neighbors)
- # Identifying the "masters" and adding to refi_x, refi_y vec
- masters = edge_elems[mask_masters]
- apex_masters = edge_apexes[mask_masters]
- x_add = (x[triangles[masters, apex_masters]] +
- x[triangles[masters, (apex_masters+1) % 3]]) * 0.5
- y_add = (y[triangles[masters, apex_masters]] +
- y[triangles[masters, (apex_masters+1) % 3]]) * 0.5
- refi_x[npts:] = x_add
- refi_y[npts:] = y_add
- # Building the new masked_triangles; each old masked_triangles hosts
- # 4 new masked_triangles
- # there are 6 pts to identify per 'old' triangle, 3 new_pt_corner and
- # 3 new_pt_midside
- new_pt_corner = triangles
- # What is the index in refi_x, refi_y of point at middle of apex iapex
- # of elem ielem ?
- # If ielem is the apex master: simple count, given the way refi_x was
- # built.
- # If ielem is the apex slave: yet we do not know; but we will soon
- # using the neighbors table.
- new_pt_midside = np.empty([ntri, 3], dtype=np.int32)
- cum_sum = npts
- for imid in range(3):
- mask_st_loc = (imid == apex_masters)
- n_masters_loc = np.sum(mask_st_loc)
- elem_masters_loc = masters[mask_st_loc]
- new_pt_midside[:, imid][elem_masters_loc] = np.arange(
- n_masters_loc, dtype=np.int32) + cum_sum
- cum_sum += n_masters_loc
- # Now dealing with slave elems.
- # for each slave element we identify the master and then the inode
- # once slave_masters is identified, slave_masters_apex is such that:
- # neighbors[slaves_masters, slave_masters_apex] == slaves
- mask_slaves = np.logical_not(mask_masters)
- slaves = edge_elems[mask_slaves]
- slaves_masters = edge_neighbors[mask_slaves]
- diff_table = np.abs(neighbors[slaves_masters, :] -
- np.outer(slaves, np.ones(3, dtype=np.int32)))
- slave_masters_apex = np.argmin(diff_table, axis=1)
- slaves_apex = edge_apexes[mask_slaves]
- new_pt_midside[slaves, slaves_apex] = new_pt_midside[
- slaves_masters, slave_masters_apex]
- # Builds the 4 child masked_triangles
- child_triangles = np.empty([ntri*4, 3], dtype=np.int32)
- child_triangles[0::4, :] = np.vstack([
- new_pt_corner[:, 0], new_pt_midside[:, 0],
- new_pt_midside[:, 2]]).T
- child_triangles[1::4, :] = np.vstack([
- new_pt_corner[:, 1], new_pt_midside[:, 1],
- new_pt_midside[:, 0]]).T
- child_triangles[2::4, :] = np.vstack([
- new_pt_corner[:, 2], new_pt_midside[:, 2],
- new_pt_midside[:, 1]]).T
- child_triangles[3::4, :] = np.vstack([
- new_pt_midside[:, 0], new_pt_midside[:, 1],
- new_pt_midside[:, 2]]).T
- child_triangulation = Triangulation(refi_x, refi_y, child_triangles)
- # Builds the child mask
- if triangulation.mask is not None:
- child_triangulation.set_mask(np.repeat(triangulation.mask, 4))
- if ancestors is None:
- return child_triangulation
- else:
- return child_triangulation, np.repeat(ancestors, 4)
|