You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			308 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			308 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
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)
 |