Source code for sparseSpatialSampling.geometry.triangle_geometry

"""
Implements a class for using triangles (2D) as geometry object.
"""
import logging
from typing import Union

from torch import Tensor, tensor, float64, cat
from .geometry_base import GeometryObject

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S',
                    force=True)


[docs] class TriangleGeometry(GeometryObject): __short_description__ = "triangles (2D)" def __init__(self, name: str, keep_inside: bool, points: Union[list, Tensor], refine: bool = False, min_refinement_level: int = None): """ Implement a class for using triangles (2D) as geometry objects representing the numerical domain or geometries inside the domain. :param name: Name of the geometry object. :type name: str :param keep_inside: If ``True``, the points inside the object are kept; if ``False``, they are masked out. :type keep_inside: bool :param points: List containing the three points of the triangle. Each point is defined by two coordinates. :type points: list[tuple | list | pt.Tensor | np.ndarray] | pt.Tensor :param refine: If ``True``, the mesh around the geometry object is refined after :math:`S^3` generates the mesh. :type refine: bool :param min_refinement_level: Minimum refinement level for resolving the geometry. If ``None`` and ``refine=True``, the geometry will be resolved with the maximum refinement level present at its surface after :math:`S^3` has generated the grid. :type min_refinement_level: int | None """ super().__init__(name, keep_inside, refine, min_refinement_level) self._type = "triangle" # convert the coordinates to a tensor if that's not yet the case for i, p in enumerate(points): if type(p) != Tensor: try: points[i] = tensor(p) except TypeError: logger.error(f"Could not convert coordinate {i} of type {type(p)} to a tensor.") # since we use the cross product, we need to cast everything to float points[i] = points[i].type(float64) self._points = points # check the user input based on the specified settings self._check_geometry() # we have to compute the main dimension and the midpoint if the name of the GeometryObject is domain self._main_width = self._compute_main_width() self._center = self._compute_center()
[docs] def check_cell(self, cell_nodes: Tensor, refine_geometry: bool = False) -> bool: """ Check if a cell is valid or invalid based on the specified settings. :param cell_nodes: Vertices of the cell to be checked. :type cell_nodes: pt.Tensor :param refine_geometry: If ``False``, cells are masked out while generating the grid. If ``True``, checks whether a cell is located in the vicinity of the geometry surface to refine it subsequently. This parameter is provided by :math:`S^3`. :type refine_geometry: bool :return: ``True`` if the cell is invalid, ``False`` if the cell is valid. :rtype: bool """ # create a mask, the mask is expected to be always 'False' outside the geometry and always 'True' inside it # (independently if it is a geometry or domain) mask = self._mask_triangle(cell_nodes) # check if the cell is valid or invalid return self._apply_mask(mask, refine_geometry=refine_geometry)
def _mask_triangle(self, vertices: Tensor) -> Tensor: """ Select all vertices that are located inside a triangle or on its surface. :param vertices: Tensor of vertices, where each column corresponds to a coordinate. :type vertices: pt.Tensor :return: Boolean mask that is ``True`` for every vertex inside the triangle or on its surface. :rtype: pt.Tensor """ # pytorch doesn't support cross product in 2D, so we have to do it manually # check for line between first and second point of the triangle d1 = self._cross_product_2d(self._points[1] - self._points[0], vertices - self._points[0]) # check for line between last and second point of the triangle d2 = self._cross_product_2d(self._points[2] - self._points[1], vertices - self._points[1]) # check for line between last and first point of the triangle d3 = self._cross_product_2d(self._points[0] - self._points[2], vertices - self._points[0]) # check the signs of the cross product between triangle side vector and vector to the node _neg = (d1 < 0) | (d2 < 0) | (d3 < 0) _pos = (d1 > 0) | (d2 > 0) | (d3 > 0) return ~(_neg & _pos) @staticmethod def _cross_product_2d(a: Tensor, b: Tensor) -> Tensor: # cross product a x b = a0*b1 - a1b0 in 2D return a[0] * b[:, 1] - a[1] * b[:, 0] def _check_geometry(self) -> None: """ Check the user input for correctness. :return: None :rtype: None """ # make sure the points are in some kind of list assert isinstance(self._points, Union[list, Tensor]), (f"Expected the points to be a list or pt.Tensor, " f"but found type {type(self._points)} instead.") # make sure we have exactly 3 points assert len(self._points) == 3, f"Expected 3 points, but found {len(self._points)} points instead." # make sure each point has exactly 2 coordinates (x-y) assert all(len(p) == 2 for p in self._points), ("All given coordinates have to contain exactly 2 entries with " "the x- and y-coordinates.") # make sure that the area of the triangle is larger than zero, pyTorch doesn't support cross product in 2D, # so do it manually _a = self._points[1] - self._points[0] _b = self._points[2] - self._points[0] _area = 0.5 * abs(_a[0] * _b[1] - _a[1] * _b[0]) assert _area > 0, f"The area of the triangle has to be larger than zero. Found an area of {_area}."
[docs] def check_triangle(self, vertices: Tensor) -> Tensor: """ Check if the given vertices are inside this triangle. This method provides access to the `_mask_triangle` method from other classes. :param vertices: Tensor of vertices, where each column corresponds to a coordinate. :type vertices: pt.Tensor :return: Boolean mask that is ``True`` for every vertex inside the triangle or on its surface. :rtype: pt.Tensor """ return self._mask_triangle(vertices)
@property def type(self) -> str: """ Return the name of the geometry object. :return: Name of the geometry object. :rtype: str """ return self._type @property def main_width(self) -> float: """ Return the width of the main dimension of the triangle. :return: Main width of the triangle. :rtype: float """ return self._main_width @property def center(self) -> Tensor: """ Return the center coordinates based on the main width of the triangle. :return: center coordinates of the triangle. :rtype: pt.Tensor """ return self._center def _compute_main_width(self) -> float: """ Compute the center coordinates based on the main width of the triangle. :return: center coordinates of the triangle. :rtype: pt.Tensor """ # _p = cat([p.unsqueeze(0) for p in self._points], dim=0) return (_p.max(0).values - _p.min(0).values).abs().max().item() def _compute_center(self) -> Tensor: """ Compute the geometric center coordinates of the triangle based on its main dimension. :return: center coordinates of the triangle. :rtype: pt.Tensor """ _p = cat([p.unsqueeze(0) for p in self._points], dim=0) return _p.mean(0)
if __name__ == "__main__": pass