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)
|