From bd491f2aad7de5ea3c8b6c5a89ad0baa310c04bc Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Mon, 23 Oct 2023 20:43:34 +0200 Subject: [PATCH 01/47] fixed typo in docs --- src/sigmaepsilon/mesh/utils/topology/topo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sigmaepsilon/mesh/utils/topology/topo.py b/src/sigmaepsilon/mesh/utils/topology/topo.py index 0728d9d..7baabf5 100644 --- a/src/sigmaepsilon/mesh/utils/topology/topo.py +++ b/src/sigmaepsilon/mesh/utils/topology/topo.py @@ -852,7 +852,7 @@ def unique_topo_data(topo3d: TopoLike) -> Tuple[ndarray, ndarray]: Parameters ---------- - topo : numpy.ndarray + topo: numpy.ndarray Hierarchical topology array. The array must be 3 dimensional containing node indices for every node as a subarray. For instance for a 2d cell, the node indices of the j-th edge of the i-th element read as `topo[i, j]`. In general, From 83c4362af6bc3ff5edad29f02a5d28c5fc6babf2 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Tue, 24 Oct 2023 08:12:22 +0200 Subject: [PATCH 02/47] added missing type hints --- src/sigmaepsilon/mesh/utils/tri.py | 84 ++++++++++++++++++------------ 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/src/sigmaepsilon/mesh/utils/tri.py b/src/sigmaepsilon/mesh/utils/tri.py index 7e86672..f77f189 100644 --- a/src/sigmaepsilon/mesh/utils/tri.py +++ b/src/sigmaepsilon/mesh/utils/tri.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from typing import Tuple + import numpy as np from numpy import ndarray from numba import njit, prange, vectorize @@ -11,7 +13,7 @@ @njit(nogil=True, parallel=True, cache=__cache) -def triangulate_cell_coords(ecoords: ndarray, trimap: ndarray): +def triangulate_cell_coords(ecoords: ndarray, trimap: ndarray) -> ndarray: nE = ecoords.shape[0] nTE, nNTE = trimap.shape nT = int(nE * nTE) @@ -26,12 +28,12 @@ def triangulate_cell_coords(ecoords: ndarray, trimap: ndarray): @njit(nogil=True, cache=__cache) -def monoms_tri_loc(lcoord: np.ndarray): +def monoms_tri_loc(lcoord: ndarray) -> ndarray: return np.array([1, lcoord[0], lcoord[1]], dtype=lcoord.dtype) @njit(nogil=True, cache=__cache) -def monoms_tri_loc_bulk(lcoord: np.ndarray): +def monoms_tri_loc_bulk(lcoord: ndarray) -> ndarray: res = np.ones((lcoord.shape[0], 3), dtype=lcoord.dtype) res[:, 1] = lcoord[:, 0] res[:, 2] = lcoord[:, 1] @@ -39,27 +41,29 @@ def monoms_tri_loc_bulk(lcoord: np.ndarray): @njit(nogil=True, cache=__cache) -def lcoords_tri(): +def lcoords_tri() -> ndarray: return np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]) @njit(nogil=True, cache=__cache) -def lcenter_tri(): +def lcenter_tri() -> ndarray: return np.array([1 / 3, 1 / 3]) @njit(nogil=True, cache=__cache) -def ncenter_tri(): +def ncenter_tri() -> ndarray: return np.array([1 / 3, 1 / 3, 1 / 3]) @njit(nogil=True, cache=__cache) -def shp_tri_loc(lcoord: np.ndarray): +def shp_tri_loc(lcoord: ndarray) -> ndarray: return np.array([1 - lcoord[0] - lcoord[1], lcoord[0], lcoord[1]]) @njit(nogil=True, parallel=True, cache=__cache) -def shape_function_matrix_tri_loc(lcoord: np.ndarray, nDOFN=2, nNODE=3): +def shape_function_matrix_tri_loc( + lcoord: ndarray, nDOFN: int = 2, nNODE: int = 3 +) -> ndarray: eye = np.eye(nDOFN, dtype=lcoord.dtype) shp = shp_tri_loc(lcoord) res = np.zeros((nDOFN, nNODE * nDOFN), dtype=lcoord.dtype) @@ -69,14 +73,14 @@ def shape_function_matrix_tri_loc(lcoord: np.ndarray, nDOFN=2, nNODE=3): @njit(nogil=True, cache=__cache) -def center_tri_2d(ecoords: np.ndarray): +def center_tri_2d(ecoords: ndarray) -> ndarray: return np.array( [np.mean(ecoords[:, 0]), np.mean(ecoords[:, 1])], dtype=ecoords.dtype ) @njit(nogil=True, cache=__cache) -def center_tri_3d(ecoords: np.ndarray): +def center_tri_3d(ecoords: ndarray) -> ndarray: return np.array( [np.mean(ecoords[:, 0]), np.mean(ecoords[:, 1]), np.mean(ecoords[:, 2])], dtype=ecoords.dtype, @@ -84,7 +88,7 @@ def center_tri_3d(ecoords: np.ndarray): @njit(nogil=True, cache=__cache) -def area_tri(ecoords: np.ndarray): +def area_tri(ecoords: ndarray) -> ndarray: """ Returns the the signed area of a single 3-noded triangle. @@ -112,7 +116,7 @@ def area_tri(ecoords: np.ndarray): @njit(nogil=True, cache=__cache) -def inscribed_radius(ecoords: ndarray): +def inscribed_radius(ecoords: ndarray) -> ndarray: """ Returns the radius of the inscribed circle of a single triangle. @@ -169,7 +173,7 @@ def inscribed_radii(ecoords: ndarray) -> ndarray: @njit(nogil=True, parallel=False, cache=__cache) -def areas_tri(ecoords: np.ndarray) -> ndarray: +def areas_tri(ecoords: ndarray) -> ndarray: """ Returns the total sum of signed areas of several triangles. @@ -205,7 +209,7 @@ def areas_tri(ecoords: np.ndarray) -> ndarray: @njit(nogil=True, parallel=True, cache=__cache) -def area_tri_bulk(ecoords: np.ndarray) -> ndarray: +def area_tri_bulk(ecoords: ndarray) -> ndarray: """ Returns the signed area of several triangles. @@ -266,7 +270,7 @@ def area_tri_u2(x1, x2, x3, y1, y2, y3): @njit(nogil=True, cache=__cache) -def loc_to_glob_tri(lcoord: np.ndarray, gcoords: np.ndarray): +def loc_to_glob_tri(lcoord: ndarray, gcoords: ndarray) -> ndarray: """ Transformation from local to global coordinates within a triangle. @@ -278,7 +282,7 @@ def loc_to_glob_tri(lcoord: np.ndarray, gcoords: np.ndarray): @njit(nogil=True, cache=__cache) -def glob_to_loc_tri(gcoord: np.ndarray, gcoords: np.ndarray): +def glob_to_loc_tri(gcoord: ndarray, gcoords: ndarray) -> ndarray: """ Transformation from global to local coordinates within a triangle. @@ -293,7 +297,7 @@ def glob_to_loc_tri(gcoord: np.ndarray, gcoords: np.ndarray): @njit(nogil=True, cache=__cache) -def glob_to_nat_tri(gcoord: np.ndarray, ecoords: np.ndarray): +def glob_to_nat_tri(gcoord: ndarray, ecoords: ndarray) -> ndarray: """ Transformation from global to natural coordinates within a triangle. @@ -360,7 +364,7 @@ def _pip_tri_bulk_knn_( @njit(nogil=True, cache=__cache) -def nat_to_glob_tri(ncoord: np.ndarray, ecoords: np.ndarray): +def nat_to_glob_tri(ncoord: ndarray, ecoords: ndarray) -> ndarray: """ Transformation from natural to global coordinates within a triangle. @@ -372,7 +376,7 @@ def nat_to_glob_tri(ncoord: np.ndarray, ecoords: np.ndarray): @njit(nogil=True, cache=__cache) -def loc_to_nat_tri(lcoord: np.ndarray): +def loc_to_nat_tri(lcoord: ndarray) -> ndarray: """ Transformation from local to natural coordinates within a triangle. @@ -384,7 +388,7 @@ def loc_to_nat_tri(lcoord: np.ndarray): @njit(nogil=True, cache=__cache) -def nat_to_loc_tri(acoord: np.ndarray): +def nat_to_loc_tri(acoord: ndarray) -> ndarray: """ Transformation from natural to local coordinates within a triangle. @@ -396,7 +400,9 @@ def nat_to_loc_tri(acoord: np.ndarray): @njit(nogil=True, parallel=True, cache=__cache) -def localize_points(points: ndarray, triangles: ndarray, coords: ndarray): +def localize_points( + points: ndarray, triangles: ndarray, coords: ndarray +) -> Tuple[ndarray, ndarray]: nE = triangles.shape[0] nC = coords.shape[0] ecoords = cells_coords(points, triangles) @@ -413,21 +419,29 @@ def localize_points(points: ndarray, triangles: ndarray, coords: ndarray): @njit(nogil=True, parallel=True, cache=__cache) -def _get_points_inside_triangles(points: ndarray, topo: ndarray, coords: ndarray): +def _get_points_inside_triangles( + points: ndarray, topo: ndarray, coords: ndarray +) -> ndarray: inds, _ = localize_points(points, topo, coords) inds[inds > -1] = 1 inds[inds < 0] = 0 return inds -def get_points_inside_triangles(points: ndarray, topo: ndarray, coords: ndarray): +def get_points_inside_triangles( + points: ndarray, topo: ndarray, coords: ndarray +) -> ndarray: return _get_points_inside_triangles(points, topo, coords).astype(bool) @njit(nogil=True, parallel=True, cache=__cache) def approx_data_to_points( - points: ndarray, triangles: ndarray, data: ndarray, coords: ndarray, defval=0.0 -): + points: ndarray, + triangles: ndarray, + data: ndarray, + coords: ndarray, + defval: float = 0.0, +) -> ndarray: nC = coords.shape[0] nD = data.shape[1] inds, shp = localize_points(points, triangles, coords) @@ -440,8 +454,10 @@ def approx_data_to_points( return res -def offset_tri(coords: np.ndarray, topo: np.ndarray, data: np.ndarray, *args, **kwargs): - if isinstance(data, np.ndarray): +def offset_tri( + coords: ndarray, topo: ndarray, data: ndarray, *args, **kwargs +) -> ndarray: + if isinstance(data, ndarray): alpha = np.abs(data) amax = alpha.max() if amax > 1.0: @@ -455,7 +471,7 @@ def offset_tri(coords: np.ndarray, topo: np.ndarray, data: np.ndarray, *args, ** @njit(nogil=True, cache=__cache) -def offset_tri_uniform(coords: np.ndarray, topo: np.ndarray, alpha=0.9): +def offset_tri_uniform(coords: ndarray, topo: ndarray, alpha: float = 0.9) -> ndarray: cellcoords = cells_coords(coords, topo) ncenter = ncenter_tri(coords.dtype) eye = np.eye(3, dtype=coords.dtype) @@ -469,7 +485,7 @@ def offset_tri_uniform(coords: np.ndarray, topo: np.ndarray, alpha=0.9): @njit(nogil=True, parallel=True, cache=__cache) -def _offset_tri_(coords: np.ndarray, topo: np.ndarray, alpha: np.ndarray): +def _offset_tri_(coords: ndarray, topo: ndarray, alpha: ndarray) -> ndarray: cellcoords = cells_coords(coords, topo) ncenter = ncenter_tri() dn = np.eye(3, dtype=coords.dtype) - ncenter @@ -482,7 +498,7 @@ def _offset_tri_(coords: np.ndarray, topo: np.ndarray, alpha: np.ndarray): return res -def edges_tri(triangles: np.ndarray): +def edges_tri(triangles: ndarray) -> ndarray: shp = triangles.shape if len(shp) == 2: return _edges_tri(triangles) @@ -493,7 +509,7 @@ def edges_tri(triangles: np.ndarray): @njit(nogil=True, cache=__cache) -def _edges_tri(triangles: np.ndarray): +def _edges_tri(triangles: ndarray) -> ndarray: nE = len(triangles) edges = np.zeros((nE, 3, 2), dtype=triangles.dtype) edges[:, 0, 0] = triangles[:, 0] @@ -506,7 +522,7 @@ def _edges_tri(triangles: np.ndarray): @njit(nogil=True, parallel=True, cache=__cache) -def _edges_tri_pop(triangles: np.ndarray): +def _edges_tri_pop(triangles: ndarray) -> ndarray: nPop, nE, _ = triangles.shape res = np.zeros((nPop, nE, 3, 2), dtype=triangles.dtype) for i in prange(nPop): @@ -515,7 +531,9 @@ def _edges_tri_pop(triangles: np.ndarray): @njit(nogil=True, parallel=True, cache=__cache) -def tri_glob_to_loc(points: np.ndarray, triangles: np.ndarray): +def tri_glob_to_loc( + points: ndarray, triangles: ndarray +) -> Tuple[ndarray, ndarray, ndarray]: nE = triangles.shape[0] tr = np.zeros((nE, 3, 3), dtype=points.dtype) res = np.zeros((nE, 3, 2), dtype=points.dtype) From 0b805a8c633e8d66f2262ceed71eafeb5586c579 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Tue, 24 Oct 2023 16:18:37 +0200 Subject: [PATCH 03/47] moved origo to cell center --- src/sigmaepsilon/mesh/cells/t3.py | 26 ++++++--- src/sigmaepsilon/mesh/cells/t6.py | 18 +++++-- src/sigmaepsilon/mesh/cells/tet10.py | 24 ++++----- src/sigmaepsilon/mesh/cells/tet4.py | 9 +++- src/sigmaepsilon/mesh/cells/w18.py | 60 ++++++++++----------- src/sigmaepsilon/mesh/cells/w6.py | 16 +++--- src/sigmaepsilon/mesh/utils/cells/numint.py | 36 ++++++------- src/sigmaepsilon/mesh/utils/cells/t3.py | 14 ++--- src/sigmaepsilon/mesh/utils/cells/tet4.py | 16 +++--- src/sigmaepsilon/mesh/utils/tet.py | 7 ++- src/sigmaepsilon/mesh/utils/tri.py | 4 +- 11 files changed, 132 insertions(+), 98 deletions(-) diff --git a/src/sigmaepsilon/mesh/cells/t3.py b/src/sigmaepsilon/mesh/cells/t3.py index 1a0c7ee..a31ea34 100644 --- a/src/sigmaepsilon/mesh/cells/t3.py +++ b/src/sigmaepsilon/mesh/cells/t3.py @@ -37,6 +37,10 @@ class Geometry(PolyCellGeometry2d): @classmethod def trimap(cls) -> ndarray: + """ + Returns a mapping used to transform the topology to triangles. + This is only implemented here for standardization. + """ return np.array([[0, 1, 2]], dtype=int) @classmethod @@ -58,36 +62,46 @@ def polybase(cls) -> Tuple[List]: @classmethod def master_coordinates(cls) -> ndarray: """ - Returns local coordinates of the cell. + Returns local coordinates of the master cell relative to the origo + of the master cell. Returns ------- numpy.ndarray """ - return np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]) + return np.array([[-1 / 3, -1 / 3], [2 / 3, -1 / 3], [-1 / 3, 2 / 3]]) @classmethod def master_center(cls) -> ndarray: """ - Returns the local coordinates of the center of the cell. + Returns the center of the master cell relative to the origo + of the master cell. Returns ------- numpy.ndarray """ - return np.array([[1 / 3, 1 / 3]]) + return np.array([[0.0, 0.0]], dtype=float) def to_triangles(self) -> ndarray: + """ + Returns the topology as triangles. + """ return self.topology().to_numpy() - def areas(self, *args, **kwargs) -> ndarray: + def areas(self, *_, **__) -> ndarray: + """ + Returns the areas of the cells as an 1d NumPy array. + """ coords = self.container.source().coords() topo = self.topology().to_numpy() ec = points_of_cells(coords, topo, local_axes=self.frames) return area_tri_bulk(ec) @classmethod - def from_TriMesh(cls, *args, coords=None, topo=None, **kwargs): + def from_TriMesh( + cls, *args, coords: ndarray = None, topo: ndarray = None, **__ + ) -> Tuple[ndarray, ndarray]: from sigmaepsilon.mesh.data.trimesh import TriMesh if len(args) > 0 and isinstance(args[0], TriMesh): diff --git a/src/sigmaepsilon/mesh/cells/t6.py b/src/sigmaepsilon/mesh/cells/t6.py index 9e43062..245bd49 100644 --- a/src/sigmaepsilon/mesh/cells/t6.py +++ b/src/sigmaepsilon/mesh/cells/t6.py @@ -49,7 +49,7 @@ def polybase(cls) -> Tuple[List]: A list of monomials. """ locvars = r, s = symbols("r s", real=True) - monoms = [1, r, s, r ** 2, s ** 2, r * s] + monoms = [1, r, s, r**2, s**2, r * s] return locvars, monoms @classmethod @@ -62,7 +62,14 @@ def master_coordinates(cls) -> ndarray: numpy.ndarray """ return np.array( - [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.5, 0.0], [0.5, 0.5], [0.0, 0.5]] + [ + [-1 / 3, -1 / 3], + [2 / 3, -1 / 3], + [-1 / 3, 2 / 3], + [1 / 6, -1 / 3], + [1 / 6, 1 / 6], + [-1 / 3, 1 / 6], + ] ) @classmethod @@ -74,16 +81,19 @@ def master_center(cls) -> ndarray: ------- numpy.ndarray """ - return np.array([[1 / 3, 1 / 3]]) + return np.array([[0.0, 0.0]], dtype=float) @classmethod - def trimap(cls, subdivide: bool = True): + def trimap(cls, subdivide: bool = True) -> ndarray: if subdivide: return np.array([[0, 3, 5], [3, 1, 4], [5, 4, 2], [5, 3, 4]], dtype=int) else: return np.array([[0, 1, 2]], dtype=int) def to_triangles(self) -> ndarray: + """ + Returns the topology as triangles. + """ return T6_to_T3(None, self.topology().to_numpy())[1] def areas(self) -> ndarray: diff --git a/src/sigmaepsilon/mesh/cells/tet10.py b/src/sigmaepsilon/mesh/cells/tet10.py index 4417e9b..39d65dd 100644 --- a/src/sigmaepsilon/mesh/cells/tet10.py +++ b/src/sigmaepsilon/mesh/cells/tet10.py @@ -41,29 +41,29 @@ def polybase(cls) -> Tuple[List]: A list of monomials. """ locvars = r, s, t = symbols("r s t", real=True) - monoms = [1, r, s, t, r * s, r * t, s * t, r ** 2, s ** 2, t ** 2] + monoms = [1, r, s, t, r * s, r * t, s * t, r**2, s**2, t**2] return locvars, monoms @classmethod def master_coordinates(cls) -> ndarray: return np.array( [ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.0, 1.0, 0.0], - [0.0, 0.0, 1.0], - [0.5, 0.0, 0.0], - [0.5, 0.5, 0.0], - [0.0, 0.5, 0.0], - [0.0, 0.0, 0.5], - [0.5, 0.0, 0.5], - [0.0, 0.5, 0.5], + [-1 / 3, -1 / 3, -1 / 3], + [2 / 3, -1 / 3, -1 / 3], + [-1 / 3, 2 / 3, -1 / 3], + [-1 / 3, -1 / 3, 2 / 3], + [1 / 6, -1 / 3, -1 / 3], + [1 / 6, 1 / 6, -1 / 3], + [-1 / 3, 1 / 6, -1 / 3], + [-1 / 3, -1 / 3, 1 / 6], + [1 / 6, -1 / 3, 1 / 6], + [-1 / 3, 1 / 6, 1 / 6], ] ) @classmethod def master_center(cls) -> ndarray: - return np.array([[1 / 3, 1 / 3, 1 / 3]]) + return np.array([[0.0, 0.0, 0.0]], dtype=float) @classmethod def tetmap(cls, subdivide: bool = True) -> np.ndarray: diff --git a/src/sigmaepsilon/mesh/cells/tet4.py b/src/sigmaepsilon/mesh/cells/tet4.py index 0ab7b58..976e6e0 100644 --- a/src/sigmaepsilon/mesh/cells/tet4.py +++ b/src/sigmaepsilon/mesh/cells/tet4.py @@ -52,12 +52,17 @@ def polybase(cls) -> Tuple[List]: @classmethod def master_coordinates(cls) -> ndarray: return np.array( - [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] + [ + [-1 / 3, -1 / 3, -1 / 3], + [2 / 3, -1 / 3, -1 / 3], + [-1 / 3, 2 / 3, -1 / 3], + [-1 / 3, -1 / 3, 2 / 3], + ] ) @classmethod def master_center(cls) -> ndarray: - return np.array([[1 / 3, 1 / 3, 1 / 3]]) + return np.array([[0.0, 0.0, 0.0]], dtype=float) @classmethod def tetmap(cls) -> ndarray: diff --git a/src/sigmaepsilon/mesh/cells/w18.py b/src/sigmaepsilon/mesh/cells/w18.py index 9741376..209f4b2 100644 --- a/src/sigmaepsilon/mesh/cells/w18.py +++ b/src/sigmaepsilon/mesh/cells/w18.py @@ -45,21 +45,21 @@ def polybase(cls) -> Tuple[List]: 1, r, s, - r ** 2, - s ** 2, + r**2, + s**2, r * s, t, t * r, t * s, - t * r ** 2, - t * s ** 2, + t * r**2, + t * s**2, t * r * s, - t ** 2, - t ** 2 * r, - t ** 2 * s, - t ** 2 * r ** 2, - t ** 2 * s ** 2, - t ** 2 * r * s, + t**2, + t**2 * r, + t**2 * s, + t**2 * r**2, + t**2 * s**2, + t**2 * r * s, ] return locvars, monoms @@ -67,33 +67,33 @@ def polybase(cls) -> Tuple[List]: def master_coordinates(cls) -> ndarray: return np.array( [ - [0.0, 0.0, -1.0], - [1.0, 0.0, -1.0], - [0.0, 1.0, -1.0], - [0.0, 0.0, 1.0], - [1.0, 0.0, 1.0], - [0.0, 1.0, 1.0], - [0.5, 0.0, -1.0], - [0.5, 0.5, -1.0], - [0.0, 0.5, -1.0], - [0.5, 0.0, 1.0], - [0.5, 0.5, 1.0], - [0.0, 0.5, 1.0], - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.0, 1.0, 0.0], - [0.5, 0.0, 0.0], - [0.5, 0.5, 0.0], - [0.0, 0.5, 0.0], + [-1 / 3, -1 / 3, -1.0], + [2 / 3, -1 / 3, -1.0], + [-1 / 3, 2 / 3, -1.0], + [-1 / 3, -1 / 3, 1.0], + [2 / 3, -1 / 3, 1.0], + [-1 / 3, 2 / 3, 1.0], + [1 / 6, -1 / 3, -1.0], + [1 / 6, 1 / 6, -1.0], + [-1 / 3, 1 / 6, -1.0], + [1 / 6, -1 / 3, 1.0], + [1 / 6, 1 / 6, 1.0], + [-1 / 3, 1 / 6, 1.0], + [-1 / 3, -1 / 3, 0.0], + [2 / 3, -1 / 3, 0.0], + [-1 / 3, 2 / 3, 0.0], + [1 / 6, -1 / 3, 0.0], + [1 / 6, 1 / 6, 0.0], + [-1 / 3, 1 / 6, 0.0], ] ) @classmethod def master_center(cls) -> ndarray: - return np.array([[1 / 3, 1 / 3, 0]]) + return np.array([[0.0, 0.0, 0.0]], dtype=float) @classmethod - def tetmap(cls) -> np.ndarray: + def tetmap(cls) -> ndarray: w18_to_w6 = np.array( [ [15, 13, 16, 9, 4, 10], diff --git a/src/sigmaepsilon/mesh/cells/w6.py b/src/sigmaepsilon/mesh/cells/w6.py index e9d0ebb..c6010c0 100644 --- a/src/sigmaepsilon/mesh/cells/w6.py +++ b/src/sigmaepsilon/mesh/cells/w6.py @@ -46,21 +46,21 @@ def polybase(cls) -> Tuple[List]: def master_coordinates(cls) -> ndarray: return np.array( [ - [0.0, 0.0, -1.0], - [1.0, 0.0, -1.0], - [0.0, 1.0, -1.0], - [0.0, 0.0, 1.0], - [1.0, 0.0, 1.0], - [0.0, 1.0, 1.0], + [-1 / 3, -1 / 3, -1.0], + [2 / 3, -1 / 3, -1.0], + [-1 / 3, 2 / 3, -1.0], + [-1 / 3, -1 / 3, 1.0], + [2 / 3, -1 / 3, 1.0], + [-1 / 3, 2 / 3, 1.0], ] ) @classmethod def master_center(cls) -> ndarray: - return np.array([[1 / 3, 1 / 3, 0]]) + return np.array([[0.0, 0.0, 0.0]], dtype=float) @classmethod - def tetmap(cls) -> np.ndarray: + def tetmap(cls) -> ndarray: return np.array( [[0, 1, 2, 4], [3, 5, 4, 2], [2, 5, 0, 4]], dtype=int, diff --git a/src/sigmaepsilon/mesh/utils/cells/numint.py b/src/sigmaepsilon/mesh/utils/cells/numint.py index c98d083..b8a497f 100644 --- a/src/sigmaepsilon/mesh/utils/cells/numint.py +++ b/src/sigmaepsilon/mesh/utils/cells/numint.py @@ -16,17 +16,17 @@ def Gauss_Legendre_Line_Grid(n: int) -> Tuple[ndarray]: def Gauss_Legendre_Tri_1() -> Tuple[ndarray]: - return np.array([[1 / 3, 1 / 3]]), np.array([1 / 2]) + return np.array([[0.0, 0.0]]), np.array([1 / 2]) def Gauss_Legendre_Tri_3a() -> Tuple[ndarray]: - p = np.array([[1 / 6, 1 / 6], [2 / 3, 1 / 6], [1 / 6, 2 / 3]]) + p = np.array([[-1 / 6, -1 / 6], [1 / 3, -1 / 6], [-1 / 6, 1 / 3]]) w = np.array([1 / 6, 1 / 6, 1 / 6]) return p, w def Gauss_Legendre_Tri_3b() -> Tuple[ndarray]: - p = np.array([[1 / 2, 1 / 2], [0, 1 / 2], [1 / 2, 0]]) + p = np.array([[1 / 6, 1 / 6], [-1 / 3, 1 / 6], [1 / 6, -1 / 3]]) w = np.array([1 / 6, 1 / 6, 1 / 6]) return p, w @@ -55,14 +55,14 @@ def Gauss_Legendre_Quad_9() -> Tuple[ndarray]: def Gauss_Legendre_Tet_1() -> Tuple[ndarray]: - p = np.array([[1 / 4, 1 / 4, 1 / 4]]) + p = np.array([[-1 / 12, -1 / 12, -1 / 12]]) w = np.array([1 / 6]) return p, w def Gauss_Legendre_Tet_4() -> Tuple[ndarray]: - a = (5 + 3 * np.sqrt(5)) / 20 - b = (5 - np.sqrt(5)) / 20 + a = ((5 + 3 * np.sqrt(5)) / 20) - 1 / 3 + b = ((5 - np.sqrt(5)) / 20) - 1 / 3 p = np.array([[a, b, b], [b, a, b], [b, b, a], [b, b, b]]) w = np.full(4, 1 / 24) return p, w @@ -71,11 +71,11 @@ def Gauss_Legendre_Tet_4() -> Tuple[ndarray]: def Gauss_Legendre_Tet_5() -> Tuple[ndarray]: p = np.array( [ - [1 / 4, 1 / 4, 1 / 4], - [1 / 2, 1 / 6, 1 / 6], - [1 / 6, 1 / 2, 1 / 6], - [1 / 6, 1 / 6, 1 / 2], - [1 / 6, 1 / 6, 1 / 6], + [-1 / 12, -1 / 12, -1 / 12], + [1 / 6, -1 / 6, -1 / 6], + [-1 / 6, 1 / 6, -1 / 6], + [-1 / 6, -1 / 6, 1 / 6], + [-1 / 6, -1 / 6, -1 / 6], ] ) w = np.array([-4 / 30, 9 / 120, 9 / 120, 9 / 120, 9 / 120]) @@ -83,15 +83,15 @@ def Gauss_Legendre_Tet_5() -> Tuple[ndarray]: def Gauss_Legendre_Tet_11() -> Tuple[ndarray]: - a = (1 + 3 * np.sqrt(5 / 15)) / 4 - b = (1 - np.sqrt(5 / 14)) / 4 + a = ((1 + 3 * np.sqrt(5 / 15)) / 4) - 1 / 3 + b = ((1 - np.sqrt(5 / 14)) / 4) - 1 / 3 p = np.array( [ - [1 / 4, 1 / 4, 1 / 4], - [11 / 14, 1 / 14, 1 / 14], - [1 / 14, 11 / 14, 1 / 14], - [1 / 14, 1 / 14, 11 / 14], - [1 / 14, 1 / 14, 1 / 14], + [-1 / 12, -1 / 12, -1 / 12], + [19 / 42, -11 / 42, -11 / 42], + [-11 / 42, 19 / 42, -11 / 42], + [-11 / 42, -11 / 42, 19 / 42], + [-11 / 42, -11 / 42, -11 / 42], [a, a, b], [a, b, a], [a, b, b], diff --git a/src/sigmaepsilon/mesh/utils/cells/t3.py b/src/sigmaepsilon/mesh/utils/cells/t3.py index 3978f95..8755d67 100644 --- a/src/sigmaepsilon/mesh/utils/cells/t3.py +++ b/src/sigmaepsilon/mesh/utils/cells/t3.py @@ -12,13 +12,13 @@ def monoms_T3(x: ndarray) -> ndarray: @njit(nogil=True, cache=__cache) -def shp_T3(pcoord: ndarray): +def shp_T3(pcoord: ndarray) -> ndarray: r, s = pcoord - return np.array([1 - r - s, r, s], dtype=pcoord.dtype) + return np.array([1 / 3 - r - s, r + 1 / 3, s + 1 / 3], dtype=pcoord.dtype) @njit(nogil=True, parallel=True, cache=__cache) -def shp_T3_multi(pcoords: ndarray): +def shp_T3_multi(pcoords: ndarray) -> ndarray: nP = pcoords.shape[0] res = np.zeros((nP, 3), dtype=pcoords.dtype) for iP in prange(nP): @@ -27,7 +27,7 @@ def shp_T3_multi(pcoords: ndarray): @njit(nogil=True, parallel=False, cache=__cache) -def shape_function_matrix_T3(pcoord: np.ndarray, ndof: int = 2): +def shape_function_matrix_T3(pcoord: ndarray, ndof: int = 2) -> ndarray: eye = np.eye(ndof, dtype=pcoord.dtype) shp = shp_T3(pcoord) res = np.zeros((ndof, ndof * 3), dtype=pcoord.dtype) @@ -37,7 +37,7 @@ def shape_function_matrix_T3(pcoord: np.ndarray, ndof: int = 2): @njit(nogil=True, parallel=True, cache=__cache) -def shape_function_matrix_T3_multi(pcoords: np.ndarray, ndof: int = 2): +def shape_function_matrix_T3_multi(pcoords: ndarray, ndof: int = 2) -> ndarray: nP = pcoords.shape[0] res = np.zeros((nP, ndof, ndof * 3), dtype=pcoords.dtype) for iP in prange(nP): @@ -46,12 +46,12 @@ def shape_function_matrix_T3_multi(pcoords: np.ndarray, ndof: int = 2): @njit(nogil=True, cache=__cache) -def dshp_T3(x): +def dshp_T3(x) -> ndarray: return np.array([[-1.0, -1.0], [1.0, 0.0], [0.0, 1.0]]) @njit(nogil=True, parallel=True, cache=__cache) -def dshp_T3_multi(pcoords: ndarray): +def dshp_T3_multi(pcoords: ndarray) -> ndarray: nP = pcoords.shape[0] res = np.zeros((nP, 3, 2), dtype=pcoords.dtype) for iP in prange(nP): diff --git a/src/sigmaepsilon/mesh/utils/cells/tet4.py b/src/sigmaepsilon/mesh/utils/cells/tet4.py index 049bc28..9a3e7ac 100644 --- a/src/sigmaepsilon/mesh/utils/cells/tet4.py +++ b/src/sigmaepsilon/mesh/utils/cells/tet4.py @@ -9,7 +9,7 @@ def monoms_TET4_single(x: ndarray) -> ndarray: r, s, t = x res = np.array( - [1, r, s, t, r * s, r * t, s * t, r ** 2, s ** 2, t ** 2], dtype=x.dtype + [1, r, s, t, r * s, r * t, s * t, r**2, s**2, t**2], dtype=x.dtype ) return res @@ -45,13 +45,13 @@ def monoms_TET4(x: ndarray) -> ndarray: @njit(nogil=True, cache=__cache) -def shp_TET4(pcoord: ndarray): +def shp_TET4(pcoord: ndarray) -> ndarray: r, s, t = pcoord - return np.array([1 - r - s - t, r, s, t]) + return np.array([1 / 3 - r - s - t, r + 1 / 3, s + 1 / 3, t + 1 / 3]) @njit(nogil=True, parallel=True, cache=__cache) -def shp_TET4_multi(pcoords: np.ndarray): +def shp_TET4_multi(pcoords: ndarray) -> ndarray: nP = pcoords.shape[0] res = np.zeros((nP, 4), dtype=pcoords.dtype) for iP in prange(nP): @@ -60,7 +60,7 @@ def shp_TET4_multi(pcoords: np.ndarray): @njit(nogil=True, parallel=True, cache=__cache) -def shape_function_matrix_TET4(pcoord: np.ndarray, ndof: int = 3): +def shape_function_matrix_TET4(pcoord: ndarray, ndof: int = 3) -> ndarray: eye = np.eye(ndof, dtype=pcoord.dtype) shp = shp_TET4(pcoord) res = np.zeros((ndof, ndof * 4), dtype=pcoord.dtype) @@ -70,7 +70,7 @@ def shape_function_matrix_TET4(pcoord: np.ndarray, ndof: int = 3): @njit(nogil=True, parallel=True, cache=__cache) -def shape_function_matrix_TET4_multi(pcoords: np.ndarray, ndof: int = 3): +def shape_function_matrix_TET4_multi(pcoords: ndarray, ndof: int = 3) -> ndarray: nP = pcoords.shape[0] res = np.zeros((nP, ndof, ndof * 4), dtype=pcoords.dtype) for iP in prange(nP): @@ -79,7 +79,7 @@ def shape_function_matrix_TET4_multi(pcoords: np.ndarray, ndof: int = 3): @njit(nogil=True, cache=__cache) -def dshp_TET4(x): +def dshp_TET4(x: float) -> ndarray: res = np.array( [[-1.0, -1.0, -1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] ) @@ -87,7 +87,7 @@ def dshp_TET4(x): @njit(nogil=True, parallel=True, cache=__cache) -def dshp_TET4_multi(pcoords: ndarray): +def dshp_TET4_multi(pcoords: ndarray) -> ndarray: nP = pcoords.shape[0] res = np.zeros((nP, 4, 3), dtype=pcoords.dtype) for iP in prange(nP): diff --git a/src/sigmaepsilon/mesh/utils/tet.py b/src/sigmaepsilon/mesh/utils/tet.py index e1350ef..b34aa1c 100644 --- a/src/sigmaepsilon/mesh/utils/tet.py +++ b/src/sigmaepsilon/mesh/utils/tet.py @@ -151,7 +151,12 @@ def lcoords_tet() -> ndarray: of a simplex in 3d. """ return np.array( - [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] + [ + [-1 / 3, -1 / 3, -1 / 3], + [2 / 3, -1 / 3, -1 / 3], + [-1 / 3, 2 / 3, -1 / 3], + [-1 / 3, -1 / 3, 2 / 3], + ] ) diff --git a/src/sigmaepsilon/mesh/utils/tri.py b/src/sigmaepsilon/mesh/utils/tri.py index f77f189..90bb577 100644 --- a/src/sigmaepsilon/mesh/utils/tri.py +++ b/src/sigmaepsilon/mesh/utils/tri.py @@ -42,12 +42,12 @@ def monoms_tri_loc_bulk(lcoord: ndarray) -> ndarray: @njit(nogil=True, cache=__cache) def lcoords_tri() -> ndarray: - return np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]) + return np.array([[-1 / 3, -1 / 3], [2 / 3, -1 / 3], [-1 / 3, 2 / 3]]) @njit(nogil=True, cache=__cache) def lcenter_tri() -> ndarray: - return np.array([1 / 3, 1 / 3]) + return np.array([0.0, 0.0]) @njit(nogil=True, cache=__cache) From 00d6f7ffcf86bbb67b79038ea3f4361623a624b3 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Tue, 24 Oct 2023 16:30:25 +0200 Subject: [PATCH 04/47] added new tests --- tests/cells/test_tet.py | 42 +++++++++++++++++++++++++++++++++ tests/cells/test_tri.py | 51 +++++++++++++++++++++++++++++++++++++++++ tests/test_section.py | 6 ++--- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 tests/cells/test_tri.py diff --git a/tests/cells/test_tet.py b/tests/cells/test_tet.py index 5967099..27b2cd4 100644 --- a/tests/cells/test_tet.py +++ b/tests/cells/test_tet.py @@ -1,10 +1,14 @@ # -*- coding: utf-8 -*- import numpy as np import unittest +from sympy import symbols +from sigmaepsilon.core.testing import SigmaEpsilonTestCase +from sigmaepsilon.math import atleast2d from sigmaepsilon.mesh import PointData, TriMesh, CartesianFrame, triangulate from sigmaepsilon.mesh.recipes import circular_disk from sigmaepsilon.mesh.cells import T3, TET4, TET10 +from sigmaepsilon.mesh.utils.tet import nat_to_loc_tet class TestTet(unittest.TestCase): @@ -60,6 +64,44 @@ def test_shp_TET10(self): shpf(pcoords) shpmf(pcoords) dshpf(pcoords) + + +class TestTET4(SigmaEpsilonTestCase): + def test_TET4(self, N: int = 3): + shp, dshp, shpf, shpmf, dshpf = TET4.Geometry.generate_class_functions( + return_symbolic=True + ) + r, s, t = symbols("r, s, t", real=True) + + for _ in range(N): + A1, A2, A3 = np.random.rand(3) + A4 = 1 - A1 - A2 - A3 + x_nat = np.array([A1, A2, A3, A4]) + x_loc = atleast2d(nat_to_loc_tet(x_nat)) + + shpA = shpf(x_loc) + shpB = TET4.Geometry.shape_function_values(x_loc) + shp_sym = shp.subs({r: x_loc[0, 0], s: x_loc[0, 1], t: x_loc[0, 2]}) + self.assertTrue(np.allclose(shpA, shpB)) + self.assertTrue( + np.allclose(shpA, np.array(shp_sym.tolist(), dtype=float).T) + ) + + dshpA = dshpf(x_loc) + dshpB = TET4.Geometry.shape_function_derivatives(x_loc) + dshp_sym = dshp.subs({r: x_loc[0, 0], s: x_loc[0, 1], t: x_loc[0, 2]}) + self.assertTrue(np.allclose(dshpA, dshpB)) + self.assertTrue( + np.allclose(dshpA, np.array(dshp_sym.tolist(), dtype=float)) + ) + + shpmfA = shpmf(x_loc) + shpmfB = TET4.Geometry.shape_function_matrix(x_loc) + self.assertTrue(np.allclose(shpmfA, shpmfB)) + + nX = 2 + shpmf = TET4.Geometry.shape_function_matrix(x_loc, N=nX) + self.assertEqual(shpmf.shape, (1, nX, 4 * nX)) if __name__ == "__main__": diff --git a/tests/cells/test_tri.py b/tests/cells/test_tri.py new file mode 100644 index 0000000..5024310 --- /dev/null +++ b/tests/cells/test_tri.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +import numpy as np +import unittest +from sympy import symbols + +from sigmaepsilon.core.testing import SigmaEpsilonTestCase +from sigmaepsilon.math import atleast2d +from sigmaepsilon.mesh.cells import T3 +from sigmaepsilon.mesh.utils.tri import nat_to_loc_tri + + +class TestT3(SigmaEpsilonTestCase): + def test_T3(self, N: int = 3): + shp, dshp, shpf, shpmf, dshpf = T3.Geometry.generate_class_functions( + return_symbolic=True + ) + r, s = symbols("r, s", real=True) + + for _ in range(N): + A1, A2 = np.random.rand(2) + A3 = 1 - A1 - A2 + x_nat = np.array([A1, A2, A3]) + x_loc = atleast2d(nat_to_loc_tri(x_nat)) + + shpA = shpf(x_loc) + shpB = T3.Geometry.shape_function_values(x_loc) + shp_sym = shp.subs({r: x_loc[0, 0], s: x_loc[0, 1]}) + self.assertTrue(np.allclose(shpA, shpB)) + self.assertTrue( + np.allclose(shpA, np.array(shp_sym.tolist(), dtype=float).T) + ) + + dshpA = dshpf(x_loc) + dshpB = T3.Geometry.shape_function_derivatives(x_loc) + dshp_sym = dshp.subs({r: x_loc[0, 0], s: x_loc[0, 1]}) + self.assertTrue(np.allclose(dshpA, dshpB)) + self.assertTrue( + np.allclose(dshpA, np.array(dshp_sym.tolist(), dtype=float)) + ) + + shpmfA = shpmf(x_loc) + shpmfB = T3.Geometry.shape_function_matrix(x_loc) + self.assertTrue(np.allclose(shpmfA, shpmfB)) + + nX = 2 + shpmf = T3.Geometry.shape_function_matrix(x_loc, N=nX) + self.assertEqual(shpmf.shape, (1, nX, 3 * nX)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_section.py b/tests/test_section.py index b81ad19..590a361 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -3,13 +3,13 @@ from numpy import ndarray -import sigmaepsilon.mesh.section -from sigmaepsilon.mesh.section import LineSection, get_section +import sigmaepsilon.mesh.domains.section +from sigmaepsilon.mesh.domains.section import LineSection, get_section from sigmaepsilon.mesh import TriMesh, CartesianFrame, PolyData def load_tests(loader, tests, ignore): # pragma: no cover - tests.addTests(doctest.DocTestSuite(sigmaepsilon.mesh.section)) + tests.addTests(doctest.DocTestSuite(sigmaepsilon.mesh.domains.section)) return tests From efed79989135d9bf87c0a7a91ee35abe7313e2c4 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 25 Oct 2023 17:12:03 +0200 Subject: [PATCH 05/47] added more tests --- tests/cells/test_tri.py | 65 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/tests/cells/test_tri.py b/tests/cells/test_tri.py index 5024310..78957e7 100644 --- a/tests/cells/test_tri.py +++ b/tests/cells/test_tri.py @@ -5,8 +5,21 @@ from sigmaepsilon.core.testing import SigmaEpsilonTestCase from sigmaepsilon.math import atleast2d +from sigmaepsilon.mesh import PolyData, PointData, CartesianFrame from sigmaepsilon.mesh.cells import T3 -from sigmaepsilon.mesh.utils.tri import nat_to_loc_tri +from sigmaepsilon.mesh.utils.tri import ( + nat_to_loc_tri, + loc_to_nat_tri, + loc_to_glob_tri, + nat_to_glob_tri, + glob_to_loc_tri, + glob_to_nat_tri, + lcoords_tri, + ncenter_tri, + lcenter_tri, + center_tri_2d, + area_tri, +) class TestT3(SigmaEpsilonTestCase): @@ -47,5 +60,55 @@ def test_T3(self, N: int = 3): self.assertEqual(shpmf.shape, (1, nX, 3 * nX)) +class TestTriutils(SigmaEpsilonTestCase): + def test_triutils(self): + frame = CartesianFrame() + coords = np.array([[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]], dtype=float) + topo = np.array([[0, 1, 2], [0, 2, 3]], dtype=int) + pd = PointData(coords=coords, frame=frame) + cd = T3(topo=topo, frames=frame) + _ = PolyData(pd, cd) + ec = cd.local_coordinates() + nE, nNE = topo.shape + + self.assertTrue(np.allclose(nat_to_loc_tri(ncenter_tri()), lcenter_tri())) + self.assertTrue(np.allclose(loc_to_nat_tri(lcenter_tri()), ncenter_tri())) + + x_tri_loc = lcoords_tri() + x_tri_nat = np.eye(3).astype(float) + c_tri_loc = lcenter_tri() + + for iNE in range(nNE): + x_nat = loc_to_nat_tri(x_tri_loc[iNE]) + self.assertTrue(np.allclose(x_nat, x_tri_nat[iNE])) + + for iE in range(nE): + x_glob = loc_to_glob_tri(c_tri_loc, ec[iE]) + self.assertTrue(np.allclose(center_tri_2d(ec[iE]), x_glob)) + + for iE in range(nE): + self.assertAlmostEqual(area_tri(ec[iE]), 2.0, delta=1e-5) + + for iE in range(nE): + for iNE in range(nNE): + x_glob = loc_to_glob_tri(x_tri_loc[iNE], ec[iE]) + self.assertTrue(np.allclose(x_glob, ec[iE, iNE])) + + for iE in range(nE): + for iNE in range(nNE): + x_glob = nat_to_glob_tri(x_tri_nat[iNE], ec[iE]) + self.assertTrue(np.allclose(x_glob, ec[iE, iNE])) + + for iE in range(nE): + for iNE in range(nNE): + x_loc = glob_to_loc_tri(ec[iE, iNE], ec[iE]) + self.assertTrue(np.allclose(x_loc, x_tri_loc[iNE])) + + for iE in range(nE): + for iNE in range(nNE): + x_nat = glob_to_nat_tri(ec[iE, iNE], ec[iE]) + self.assertTrue(np.allclose(x_nat, x_tri_nat[iNE])) + + if __name__ == "__main__": unittest.main() From 91984b134ec2ac52642fb8ff98d5ac62b5142c3a Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 25 Oct 2023 17:12:20 +0200 Subject: [PATCH 06/47] fixed utility functions --- src/sigmaepsilon/mesh/utils/tri.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sigmaepsilon/mesh/utils/tri.py b/src/sigmaepsilon/mesh/utils/tri.py index 90bb577..9fcc013 100644 --- a/src/sigmaepsilon/mesh/utils/tri.py +++ b/src/sigmaepsilon/mesh/utils/tri.py @@ -57,7 +57,9 @@ def ncenter_tri() -> ndarray: @njit(nogil=True, cache=__cache) def shp_tri_loc(lcoord: ndarray) -> ndarray: - return np.array([1 - lcoord[0] - lcoord[1], lcoord[0], lcoord[1]]) + return np.array( + [1 / 3 - lcoord[0] - lcoord[1], lcoord[0] + 1 / 3, lcoord[1] + 1 / 3] + ) @njit(nogil=True, parallel=True, cache=__cache) @@ -292,7 +294,7 @@ def glob_to_loc_tri(gcoord: ndarray, gcoords: ndarray) -> ndarray: """ monoms = monoms_tri_loc_bulk(gcoords) coeffs = np.linalg.inv(monoms) - shp = coeffs @ monoms_tri_loc(gcoord) + shp = coeffs.T @ monoms_tri_loc(gcoord) return lcoords_tri().T @ shp From d463208744016c7a057375310bbf8186af3efc91 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 00:36:49 +0200 Subject: [PATCH 07/47] added "geometry" to quadratures --- src/sigmaepsilon/mesh/cells/h27.py | 1 + src/sigmaepsilon/mesh/cells/h8.py | 1 + src/sigmaepsilon/mesh/cells/l2.py | 1 + src/sigmaepsilon/mesh/cells/l3.py | 1 + src/sigmaepsilon/mesh/cells/q4.py | 1 + src/sigmaepsilon/mesh/cells/q8.py | 1 + src/sigmaepsilon/mesh/cells/q9.py | 1 + src/sigmaepsilon/mesh/cells/t3.py | 12 ++++++++++++ src/sigmaepsilon/mesh/cells/t6.py | 14 ++++++++++++++ src/sigmaepsilon/mesh/cells/tet10.py | 1 + src/sigmaepsilon/mesh/cells/tet4.py | 1 + src/sigmaepsilon/mesh/cells/w18.py | 1 + src/sigmaepsilon/mesh/cells/w6.py | 1 + 13 files changed, 37 insertions(+) diff --git a/src/sigmaepsilon/mesh/cells/h27.py b/src/sigmaepsilon/mesh/cells/h27.py index 3af3ac3..e0e999e 100644 --- a/src/sigmaepsilon/mesh/cells/h27.py +++ b/src/sigmaepsilon/mesh/cells/h27.py @@ -57,6 +57,7 @@ class Geometry(PolyCellGeometry3d): monomial_evaluator: monoms_H27 quadrature = { "full": Gauss_Legendre_Hex_Grid(3, 3, 3), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/h8.py b/src/sigmaepsilon/mesh/cells/h8.py index 6763910..6d8ab2d 100644 --- a/src/sigmaepsilon/mesh/cells/h8.py +++ b/src/sigmaepsilon/mesh/cells/h8.py @@ -47,6 +47,7 @@ class Geometry(PolyCellGeometry3d): monomial_evaluator: monoms_H8 quadrature = { "full": Gauss_Legendre_Hex_Grid(2, 2, 2), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/l2.py b/src/sigmaepsilon/mesh/cells/l2.py index 5b0d8b2..b6a0131 100644 --- a/src/sigmaepsilon/mesh/cells/l2.py +++ b/src/sigmaepsilon/mesh/cells/l2.py @@ -26,4 +26,5 @@ class Geometry(PolyCellGeometry1d): monomial_evaluator: monoms_L2 quadrature = { "full": Gauss_Legendre_Line_Grid(2), + "geometry": "full", } diff --git a/src/sigmaepsilon/mesh/cells/l3.py b/src/sigmaepsilon/mesh/cells/l3.py index ab9af3a..2635c2b 100644 --- a/src/sigmaepsilon/mesh/cells/l3.py +++ b/src/sigmaepsilon/mesh/cells/l3.py @@ -19,4 +19,5 @@ class Geometry(PolyCellGeometry1d): monomial_evaluator: monoms_L3 quadrature = { "full": Gauss_Legendre_Line_Grid(3), + "geometry": "full", } diff --git a/src/sigmaepsilon/mesh/cells/q4.py b/src/sigmaepsilon/mesh/cells/q4.py index 093993f..1e5f9e7 100644 --- a/src/sigmaepsilon/mesh/cells/q4.py +++ b/src/sigmaepsilon/mesh/cells/q4.py @@ -32,6 +32,7 @@ class Geometry(PolyCellGeometry2d): monomial_evaluator: monoms_Q4 quadrature = { "full": Gauss_Legendre_Quad_4(), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/q8.py b/src/sigmaepsilon/mesh/cells/q8.py index 195da32..cf7786a 100644 --- a/src/sigmaepsilon/mesh/cells/q8.py +++ b/src/sigmaepsilon/mesh/cells/q8.py @@ -31,6 +31,7 @@ class Geometry(PolyCellGeometry2d): monomial_evaluator: monoms_Q8 quadrature = { "full": Gauss_Legendre_Quad_9(), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/q9.py b/src/sigmaepsilon/mesh/cells/q9.py index f5a4778..1aa36f4 100644 --- a/src/sigmaepsilon/mesh/cells/q9.py +++ b/src/sigmaepsilon/mesh/cells/q9.py @@ -31,6 +31,7 @@ class Geometry(PolyCellGeometry2d): monomial_evaluator: monoms_Q9 quadrature = { "full": Gauss_Legendre_Quad_9(), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/t3.py b/src/sigmaepsilon/mesh/cells/t3.py index a31ea34..36ae433 100644 --- a/src/sigmaepsilon/mesh/cells/t3.py +++ b/src/sigmaepsilon/mesh/cells/t3.py @@ -20,6 +20,17 @@ class T3(PolyCell): """ A class to handle 3-noded triangles. + + Example + ------- + >>> from sigmaepsilon.mesh import TriMesh, CartesianFrame, PointData, triangulate + >>> from sigmaepsilon.mesh.cells import T3 as CellData + >>> A = CartesianFrame(dim=3) + >>> coords, topo = triangulate(size=(800, 600), shape=(10, 10)) + >>> pd = PointData(coords=coords, frame=A) + >>> cd = CellData(topo=topo) + >>> trimesh = TriMesh(pd, cd) + >>> trimesh.area() """ label = "T3" @@ -33,6 +44,7 @@ class Geometry(PolyCellGeometry2d): monomial_evaluator: monoms_T3 quadrature = { "full": Gauss_Legendre_Tri_1(), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/t6.py b/src/sigmaepsilon/mesh/cells/t6.py index 245bd49..7597836 100644 --- a/src/sigmaepsilon/mesh/cells/t6.py +++ b/src/sigmaepsilon/mesh/cells/t6.py @@ -21,6 +21,19 @@ class T6(PolyCell): """ A class to handle 6-noded triangles. + + Example + ------- + >>> from sigmaepsilon.mesh import TriMesh, CartesianFrame, PointData, triangulate + >>> from sigmaepsilon.mesh.cells import T6 as CellData + >>> from sigmaepsilon.mesh.utils.topology.tr import T3_to_T6 + >>> A = CartesianFrame(dim=3) + >>> coords, topo = triangulate(size=(800, 600), shape=(10, 10)) + >>> coords, topo = T3_to_T6(coords, topo) + >>> pd = PointData(coords=coords, frame=A) + >>> cd = CellData(topo=topo) + >>> trimesh = TriMesh(pd, cd) + >>> trimesh.area() """ label = "T6" @@ -34,6 +47,7 @@ class Geometry(PolyCellGeometry2d): monomial_evaluator: monoms_T6 quadrature = { "full": Gauss_Legendre_Tri_3a(), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/tet10.py b/src/sigmaepsilon/mesh/cells/tet10.py index 39d65dd..7dfe87c 100644 --- a/src/sigmaepsilon/mesh/cells/tet10.py +++ b/src/sigmaepsilon/mesh/cells/tet10.py @@ -26,6 +26,7 @@ class Geometry(PolyCellGeometry3d): monomial_evaluator: monoms_TET10 quadrature = { "full": Gauss_Legendre_Tet_4(), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/tet4.py b/src/sigmaepsilon/mesh/cells/tet4.py index 976e6e0..416b573 100644 --- a/src/sigmaepsilon/mesh/cells/tet4.py +++ b/src/sigmaepsilon/mesh/cells/tet4.py @@ -31,6 +31,7 @@ class Geometry(PolyCellGeometry3d): monomial_evaluator: monoms_TET4 quadrature = { "full": Gauss_Legendre_Tet_1(), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/w18.py b/src/sigmaepsilon/mesh/cells/w18.py index 209f4b2..b3e7303 100644 --- a/src/sigmaepsilon/mesh/cells/w18.py +++ b/src/sigmaepsilon/mesh/cells/w18.py @@ -26,6 +26,7 @@ class Geometry(PolyCellGeometry3d): monomial_evaluator: monoms_W18 quadrature = { "full": Gauss_Legendre_Wedge_3x3(), + "geometry": "full", } @classmethod diff --git a/src/sigmaepsilon/mesh/cells/w6.py b/src/sigmaepsilon/mesh/cells/w6.py index c6010c0..bbe49ab 100644 --- a/src/sigmaepsilon/mesh/cells/w6.py +++ b/src/sigmaepsilon/mesh/cells/w6.py @@ -24,6 +24,7 @@ class Geometry(PolyCellGeometry3d): monomial_evaluator: monoms_W6 quadrature = { "full": Gauss_Legendre_Wedge_3x2(), + "geometry": "full", } @classmethod From 6ec53b7569901eb02ad5818e01cac87ca376ef0d Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 00:37:21 +0200 Subject: [PATCH 08/47] added docstrings, type hints and new utilities --- src/sigmaepsilon/mesh/data/polycell.py | 216 +++++++++++++++++++------ 1 file changed, 166 insertions(+), 50 deletions(-) diff --git a/src/sigmaepsilon/mesh/data/polycell.py b/src/sigmaepsilon/mesh/data/polycell.py index f91d7ef..87f3bed 100644 --- a/src/sigmaepsilon/mesh/data/polycell.py +++ b/src/sigmaepsilon/mesh/data/polycell.py @@ -9,11 +9,13 @@ TypeVar, Generic, ) +from numbers import Number import numpy as np from numpy import ndarray +from numpy.lib.index_tricks import IndexExpression -from sigmaepsilon.math import atleast1d, atleast2d, ascont +from sigmaepsilon.math import atleast1d, atleast2d, atleastnd, ascont from sigmaepsilon.math.linalg import ReferenceFrame as FrameLike from sigmaepsilon.math.utils import to_range_1d @@ -63,7 +65,7 @@ MapLike = Union[ndarray, MutableMapping] PointDataLike = TypeVar("PointDataLike", bound=PointDataProtocol) MeshDataLike = TypeVar("MeshDataLike", bound=PolyDataProtocol) - +T = TypeVar("T", bound="PolyCell") __all__ = ["PolyCell"] @@ -75,15 +77,59 @@ class PolyCell( ): """ A subclass of :class:`~sigmaepsilon.mesh.data.celldata.CellData` as a base class - for all cell containers. + for all cell containers. The class should not be used directly, the main purpose + here is encapsulation of common behaviour for all kinds of cells. """ label: ClassVar[Optional[str]] = None Geometry: ClassVar[GeometryProtocol] + def _get_cell_slicer( + self, cells: Optional[Union[int, Iterable[int]]] = None + ) -> Union[Iterable[int], IndexExpression]: + if isinstance(cells, Iterable): + cells = atleast1d(cells) + conds = np.isin(cells, self.id) + cells = atleast1d(cells[conds]) + assert ( + len(cells) > 0 + ), "Length of cells is zero. At least one cell must be requested" + else: + cells = np.s_[:] + return cells + + def _get_points_and_range( + self, + points: Optional[Union[None, Iterable[Number]]] = None, + rng: Optional[Union[None, Iterable[Number]]] = None, + ) -> Tuple[ndarray, ndarray]: + nDIM = self.Geometry.number_of_spatial_dimensions + if nDIM == 1: + if points is None: + points = np.array(self.Geometry.master_coordinates()).flatten() + rng = [-1, 1] + else: + points = atleast1d(np.array(points)) + rng = np.array([-1, 1]) if rng is None else np.array(rng) + points = to_range_1d(points, source=rng, target=[-1, 1]).flatten() + rng = [-1, 1] + else: + if points is None: + points = np.array(self.Geometry.master_coordinates()) + + points, rng = np.array(points, dtype=float), np.array(rng, dtype=float) + + if nDIM > 1: + points = atleastnd(points, 2, front=True) + + return points, rng + @CellData.frames.getter def frames(self) -> ndarray: - """Returns local coordinate frames of the cells.""" + """ + Returns local coordinate frames of the cells as a 3d NumPy float array, + where the first axis runs along the cells of the block. + """ if not self.has_frames: if (nD := self.Geometry.number_of_spatial_dimensions) == 1: coords = self.source_coords() @@ -102,9 +148,28 @@ def frames(self) -> ndarray: ) return super().frames + def split(self: T) -> Iterable[T]: + """ + Splits the block to a list of regular blocks. A regular block is one where + the topology can be described with a NumPy matrix, otherwise the topology is + jagged. In the latter case, a list of PolyCell instances are returned. + In the instance has a regular topology, the result is `[self]`. + """ + raise NotImplementedError + topo: TopologyArray = self.topology() + + if not topo.is_jagged(): + return [self] + + topologies = topo.split() + def to_triangles(self) -> ndarray: """ - Returns the topology as a collection of T3 triangles. + Returns the topology as a collection of T3 triangles, represented + as a 2d NumPy integer array, where the first axis runs along the + triangles, and the second along the nodes. + + Only for 2d cells. """ if self.Geometry.number_of_spatial_dimensions == 2: t = self.topology().to_numpy() @@ -114,7 +179,11 @@ def to_triangles(self) -> ndarray: def to_tetrahedra(self, flatten: Optional[bool] = True) -> ndarray: """ - Returns the topology as a collection of TET4 tetrahedra. + Returns the topology as a collection of TET4 tetrahedra, represented + as a 2d NumPy integer array, where the first axis runs along the + tetrahedra, and the second along the nodes. + + Only for 3d cells. Parameters ---------- @@ -139,7 +208,9 @@ def to_tetrahedra(self, flatten: Optional[bool] = True) -> ndarray: def to_simplices(self) -> Tuple[ndarray]: """ - Returns the cells of the block, refactorized into simplices. + Returns the cells of the block, refactorized into simplices. For cells + of dimension 2, the returned 2d NumPy integer array represents 3-noded + triangles, for 3d cells it is a collection of 4-noded tetrahedra. """ NDIM: int = self.Geometry.number_of_spatial_dimensions if NDIM == 1: @@ -152,7 +223,11 @@ def to_simplices(self) -> Tuple[ndarray]: raise NotImplementedError def jacobian_matrix( - self, *, pcoords: Iterable[float] = None, dshp: ndarray = None, **__ + self, + *, + pcoords: Optional[Union[Iterable[float], None]] = None, + dshp: Optional[Union[ndarray, None]] = None, + **kwargs, ) -> ndarray: """ Returns the jacobian matrices of the cells in the block. The evaluation @@ -178,8 +253,13 @@ def jacobian_matrix( are the number of elements, evaluation points and spatial dimensions. The number of evaluation points in the output is governed by the parameter 'dshp' or 'pcoords'. + + Note + ---- + For 1d cells, the returned array is also 4 dimensional, with the last two + axes being dummy. """ - ecoords = self.local_coordinates() + ecoords = kwargs.get("_ec", self.local_coordinates()) if dshp is None: x = ( @@ -194,7 +274,9 @@ def jacobian_matrix( else: return jacobian_matrix_bulk(dshp, ecoords) - def jacobian(self, *, jac: ndarray = None, **kwargs) -> Union[float, ndarray]: + def jacobian( + self, *, jac: Optional[Union[ndarray, None]] = None, **kwargs + ) -> Union[float, ndarray]: """ Returns the jacobian determinant for one or more cells. @@ -339,10 +421,6 @@ def volumes(self, *args, **kwargs) -> ndarray: else: raise NotImplementedError - def extract_surface(self, detach: bool = False): - """Extracts the surface of the mesh. Only for 3d meshes.""" - raise NotImplementedError - def source_points(self) -> PointCloud: """ Returns the hosting pointcloud. @@ -368,26 +446,32 @@ def source_frame(self) -> FrameLike: def points_of_cells( self, *, - points: Union[float, Iterable] = None, - cells: Union[int, Iterable] = None, - target: Union[str, CartesianFrame] = "global", - rng: Iterable = None, + points: Optional[Union[float, Iterable, None]] = None, + cells: Optional[Union[int, Iterable, None]] = None, + rng: Optional[Union[Iterable, None]] = None, ) -> ndarray: """ - Returns the points of selected cells as a NumPy array. - """ - if cells is not None: - cells = atleast1d(cells) - conds = np.isin(cells, self.id) - cells = atleast1d(cells[conds]) - assert len(cells) > 0, "Length of cells is zero!" - else: - cells = np.s_[:] + Returns the points of selected cells as a NumPy array. The returned + array is three dimensional with a shape of (nE, nNE, 2), where `nE` is + the number of cells in the block, `nNE` is the number of nodes per cell + and 2 stands for the 2 spatial dimensions. - if isinstance(target, str): - assert target.lower() in ["global", "g"] - else: - raise NotImplementedError + Parameters + ---------- + points: Optional[Union[float, Iterable, None]] + Points defined in the domain of the master cell. If specified, global + coordinates for each cell are calculated and returned for each cell. + Default is `None`, in which case the locations of the nodes of the cells + are used. + cells: Optional[Union[int, Iterable, None]] + BLock-local indices of the cells of interest, or `None` if all of the + cells in the block are of interest. Default is `None`. + rng: Optional[Union[Iterable, None]] + For 1d cells only, it is possible to provide an iterable of length 2 + as an interval (or range) in which the argument `points` is to be understood. + Default is `None`, in which case the `points` are expected in the range [-1, 1]. + """ + cells = self._get_cell_slicer(cells) NDIM: int = self.Geometry.number_of_spatial_dimensions coords = self.source_coords() @@ -397,12 +481,7 @@ def points_of_cells( if points is None: return ecoords else: - if NDIM == 1: - rng = np.array([-1, 1]) if rng is None else np.array(rng) - points = atleast1d(np.array(points)) - points = to_range_1d(points, source=rng, target=[0, 1]) - else: - points = np.array(points) + points, rng = self._get_points_and_range(points, rng) if NDIM == 1: res = pcoords_to_coords_1d(points, ecoords) # (nE * nP, nD) @@ -416,10 +495,15 @@ def points_of_cells( shp = shp if len(shp) == 2 else shp[cells] return pcoords_to_coords(points, ecoords, shp) # (nE, nP, nD) - def local_coordinates(self, *, target: CartesianFrame = None) -> ndarray: + def local_coordinates( + self, *, target: Optional[Union[str, CartesianFrame, None]] + ) -> ndarray: """ - Returns local coordinates of the cells as a 3d float - numpy array. + Returns local coordinates of the cells as a 3d float NumPy array. + The returned array is three dimensional with a shape of (nE, nNE, 2), + where `nE` is the number of cells in the block, `nNE` is the number of + nodes per cell and 2 stands for the 2 spatial dimensions. The coordinates + are centralized to the centers for each cell. Parameters ---------- @@ -432,11 +516,14 @@ def local_coordinates(self, *, target: CartesianFrame = None) -> ndarray: frames = target.show() else: frames = self.frames + topo = self.topology().to_numpy() + if self.pointdata is not None: coords = self.pointdata.x else: coords = self.container.source().coords() + res = points_of_cells(coords, topo, local_axes=frames, centralize=True) if self.Geometry.number_of_spatial_dimensions == 2: @@ -446,15 +533,15 @@ def local_coordinates(self, *, target: CartesianFrame = None) -> ndarray: def coords(self, *args, **kwargs) -> ndarray: """ - Returns the coordinates of the cells in the database as a 3d - numpy array. + Alias for :func:`points_of_cells`, all arguments are forwarded. """ return self.points_of_cells(*args, **kwargs) def topology(self) -> Union[TopologyArray, None]: """ Returns the numerical representation of the topology of - the cells. + the cells as either a :class:`~sigmaepsilon.mesh.topoarray.TopologyArray` + or `None` if the topology is not specified yet. """ key = self._dbkey_nodes_ if key in self.fields: @@ -691,8 +778,18 @@ def locate( else: raise NotImplementedError - def centers(self, target: FrameLike = None) -> ndarray: - """Returns the centers of the cells of the block.""" + def centers(self, target: Optional[Union[CartesianFrame, None]] = None) -> ndarray: + """ + Returns the centers of the cells of the block as a 1d float + NumPy array. + + Parameters + ---------- + target: CartesianFrame, Optional + A target frame. If provided, coordinates are returned in + this frame, otherwise they are returned in the global frame. + Default is None. + """ coords = self.source_coords() t = self.topology().to_numpy() centers = cell_centers_bulk(coords, t) @@ -703,12 +800,15 @@ def centers(self, target: FrameLike = None) -> ndarray: def unique_indices(self) -> ndarray: """ - Returns the indices of the points involved in the cells of the block. + Returns the indices of the points involved in the cells of the block + as a 1d integer NumPy array. """ return np.unique(self.topology()) def points_involved(self) -> PointCloud: - """Returns the points involved in the cells of the block.""" + """ + Returns the points involved in the cells of the block. + """ return self.source_points()[self.unique_indices()] def detach_points_cells(self) -> Tuple[ndarray]: @@ -722,6 +822,11 @@ def detach_points_cells(self) -> Tuple[ndarray]: def to_vtk(self, detach: bool = False) -> Any: """ Returns the block as a VTK object. + + Parameters + ---------- + detach: bool, Optional + Wether to detach the mesh or not. Default is False. """ coords = self.container.source().coords() topo = self.topology().to_numpy() @@ -738,13 +843,24 @@ def to_pv( self, detach: bool = False ) -> Union[pv.UnstructuredGrid, pv.PolyData]: """ - Returns the block as a pyVista object. + Returns the block as a PyVista object. + + Parameters + ---------- + detach: bool, Optional + Wether to detach the mesh or not. Default is False. """ return pv.wrap(self.to_vtk(detach=detach)) def extract_surface(self, detach: bool = False) -> Tuple[ndarray]: """ - Extracts the surface of the object. + Extracts the surface of the object as a 2-tuple of NumPy arrays + representing the coordinates and the topology of a triangulation. + + Parameters + ---------- + detach: bool, Optional + Wether to detach the mesh or not. Default is False. """ if self.Geometry.number_of_spatial_dimensions == 3: @@ -764,7 +880,7 @@ def extract_surface(self, detach: bool = False) -> Tuple[ndarray]: def boundary(self, detach: bool = False) -> Tuple[ndarray]: """ - Returns the boundary of the block as 2 NumPy arrays. + Alias for :func:`extract_surface`. """ if self.Geometry.number_of_spatial_dimensions == 3: return self.extract_surface(detach=detach) From b5e33cfc4f38f7a451a2ef18ac355ed2fa34c2e9 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 00:37:36 +0200 Subject: [PATCH 09/47] fixed docstrings --- src/sigmaepsilon/mesh/data/trimesh.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/sigmaepsilon/mesh/data/trimesh.py b/src/sigmaepsilon/mesh/data/trimesh.py index 86fd321..2f3902a 100644 --- a/src/sigmaepsilon/mesh/data/trimesh.py +++ b/src/sigmaepsilon/mesh/data/trimesh.py @@ -1,4 +1,4 @@ -from typing import Tuple, Optional +from typing import Tuple, Optional, Any import numpy as np from numpy import ndarray @@ -48,9 +48,13 @@ class TriMesh(PolyData): Triangulate a rectangle of size 800x600 with a subdivision of 10x10 and calculate the area - >>> from sigmaepsilon.mesh import TriMesh, CartesianFrame + >>> from sigmaepsilon.mesh import TriMesh, CartesianFrame, PointData, triangulate + >>> from sigmaepsilon.mesh.cells import T3 >>> A = CartesianFrame(dim=3) - >>> trimesh = TriMesh(size=(800, 600), shape=(10, 10), frame=A) + >>> coords, topo = triangulate(size=(800, 600), shape=(10, 10)) + >>> pd = PointData(coords=coords, frame=A) + >>> cd = T3(topo=topo) + >>> trimesh = TriMesh(pd, cd) >>> trimesh.area() 480000.0 @@ -153,7 +157,7 @@ def edges(self, return_cells: bool = False) -> Tuple[ndarray, Optional[ndarray]] else: return edges - def to_triobj(self, *args, **kwargs): + def to_triobj(self) -> Any: """ Returns a triangulation object of a specified backend. See :func:`~sigmaepsilon.mesh.triang.triangulate` for the details. From c788882b904842a253bf28e48796e85db51db984 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 00:38:02 +0200 Subject: [PATCH 10/47] moved module to new location --- src/sigmaepsilon/mesh/{ => domains}/section.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) rename src/sigmaepsilon/mesh/{ => domains}/section.py (98%) diff --git a/src/sigmaepsilon/mesh/section.py b/src/sigmaepsilon/mesh/domains/section.py similarity index 98% rename from src/sigmaepsilon/mesh/section.py rename to src/sigmaepsilon/mesh/domains/section.py index 2da7f3f..1615e29 100644 --- a/src/sigmaepsilon/mesh/section.py +++ b/src/sigmaepsilon/mesh/domains/section.py @@ -15,18 +15,19 @@ from sectionproperties.analysis.section import Section from sigmaepsilon.core.wrapping import Wrapper -from linkeddeepdict.tools.kwargtools import getallfromkwargs +from sigmaepsilon.core.kwargtools import getallfromkwargs from sigmaepsilon.mesh.utils import centralize from sigmaepsilon.mesh.data import TriMesh, PolyData from sigmaepsilon.mesh.utils.topology import T6_to_T3, detach_mesh_bulk -from .cells import T3 -from .data import PointData -from .space import CartesianFrame -from .utils import xy_to_xyz +from ..cells import T3 +from ..data import PointData +from ..space import CartesianFrame +from ..utils import xy_to_xyz __all__ = ["generate_mesh", "get_section", "LineSection"] + def generate_mesh( geometry: Geometry, *, l_max: float = None, a_max: float = None, n_max: int = None ) -> Geometry: @@ -58,7 +59,7 @@ def generate_mesh( area = geometry.calculate_area() mesh_sizes_max = [] if isinstance(l_max, float): - mesh_sizes_max.append(l_max ** 2 * np.sqrt(3) / 4) + mesh_sizes_max.append(l_max**2 * np.sqrt(3) / 4) if isinstance(a_max, float): mesh_sizes_max.append(a_max) if isinstance(n_max, int): @@ -245,7 +246,7 @@ def __init__( shape=None, mesh_params=None, material: Material = None, - **kwargs + **kwargs, ): if len(args) > 0: try: From 6533c5f4390c76bf04b097f0656040c7b14a0de6 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 00:38:20 +0200 Subject: [PATCH 11/47] removed duplicate item --- src/sigmaepsilon/mesh/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sigmaepsilon/mesh/__init__.py b/src/sigmaepsilon/mesh/__init__.py index 92ec1f5..78c3ac5 100644 --- a/src/sigmaepsilon/mesh/__init__.py +++ b/src/sigmaepsilon/mesh/__init__.py @@ -20,7 +20,7 @@ "CartesianFrame", "PolyData", "LineData", - "PolyData1d", + "PolyData1d", "PointData", "TriMesh", # From 7a7c156ed035586ba996cf7ba80cf013519ba353 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 01:20:29 +0200 Subject: [PATCH 12/47] added tests for pointdata --- tests/test_pointdata.py | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/test_pointdata.py diff --git a/tests/test_pointdata.py b/tests/test_pointdata.py new file mode 100644 index 0000000..3eb2c42 --- /dev/null +++ b/tests/test_pointdata.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +import numpy as np +import unittest + +from sigmaepsilon.core.testing import SigmaEpsilonTestCase +from sigmaepsilon.math.linalg import FrameLike +from sigmaepsilon.mesh import CartesianFrame, PointData, triangulate +from sigmaepsilon.mesh import PointData + + +class TestPointData(SigmaEpsilonTestCase): + def test_pointdata(self): + A = CartesianFrame(dim=3) + coords = triangulate(size=(800, 600), shape=(10, 10))[0] + pd = PointData(coords=coords) + self.assertIsInstance(pd.frame, FrameLike) + pd = PointData(coords=coords, frame=A) + self.assertIsInstance(pd.frame, FrameLike) + nP = len(pd) + + pd.activity = np.ones((nP), dtype=bool) + self.assertTrue(pd.has_activity) + self.assertRaises(TypeError, setattr, pd, "activity", "a") + self.assertRaises( + ValueError, setattr, pd, "activity", np.ones((nP), dtype=float) + ) + self.assertRaises( + ValueError, setattr, pd, "activity", np.ones((nP, 2), dtype=bool) + ) + self.assertRaises( + ValueError, setattr, pd, "activity", np.ones((nP - 1), dtype=bool) + ) + + pd.id = np.arange(nP) + self.assertTrue(pd.has_id) + self.assertRaises(TypeError, setattr, pd, "id", "a") + self.assertRaises(ValueError, setattr, pd, "id", np.ones((nP), dtype=float)) + self.assertRaises(ValueError, setattr, pd, "id", np.ones((nP, 2), dtype=int)) + self.assertRaises(ValueError, setattr, pd, "id", np.ones((nP - 1), dtype=int)) + + pd.x = coords + self.assertTrue(pd.has_x) + self.assertRaises(TypeError, setattr, pd, "x", "_") + self.assertRaises(ValueError, setattr, pd, "x", np.zeros((3, 3, 3))) + + +if __name__ == "__main__": + unittest.main() From dc4ada6c2b3ce60d7c7ac15c4d722c0c2a944c34 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 01:21:01 +0200 Subject: [PATCH 13/47] added type and value checks for setters, docstrings and type hints --- src/sigmaepsilon/mesh/data/pointdata.py | 108 +++++++++++++++++++----- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/src/sigmaepsilon/mesh/data/pointdata.py b/src/sigmaepsilon/mesh/data/pointdata.py index ae93f31..5525ae6 100644 --- a/src/sigmaepsilon/mesh/data/pointdata.py +++ b/src/sigmaepsilon/mesh/data/pointdata.py @@ -7,7 +7,7 @@ from sigmaepsilon.core import classproperty from sigmaepsilon.math.linalg import ReferenceFrame as FrameLike -from sigmaepsilon.math.logical import isboolarray +from sigmaepsilon.math.logical import isboolarray, isintegerarray from sigmaepsilon.math.linalg.sparse import csr_matrix from ..typing.abcakwrapper import ABC_AkWrapper @@ -28,11 +28,17 @@ class PointData(AkWrapper, ABC_AkWrapper): """ A class to handle data related to the pointcloud of a polygonal mesh. - Technicall this is a wrapper around an `awkward.Record` instance. - - If you are not a developer, you probably don't have to ever create any - instance of this class, but since it operates in the background of every - polygonal data structure, it is important to understand how it works. + The class is technicall a wrapper around an `awkward.Record` instance. + + Parameters + ---------- + points: numpy.ndarray, Optional + Coordinates of some points as a 2d NumPy float array. Default is `None`. + coords: numpy.ndarray, Optional + Same as `points`. Default is `None`. + frame: CartesianFrame, Optional + The coordinate frame the points are understood in. Default is `None`, which + means the standard global frame (the ambient frame). """ _point_cls_ = PointCloud @@ -50,7 +56,7 @@ def __init__( coords: Optional[Union[ndarray, None]] = None, wrap: Optional[Union[akRecord, None]] = None, fields: Optional[Union[Iterable, None]] = None, - frame: Optional[Union[FrameLike, None]] = None, + frame: Optional[Union[CartesianFrame, None]] = None, db: Optional[Union[akRecord, None]] = None, container: Optional[Union[PolyDataProtocol, None]] = None, **kwargs, @@ -143,12 +149,28 @@ def _dbkey_activity_(cls) -> str: return cls._attr_map_["activity"] @property - def has_id(self) -> ndarray: + def has_id(self) -> bool: + """ + Returns `True` if the points are equipped with IDs, `False` if + they are not. + """ return self._dbkey_id_ in self._wrapped.fields @property - def has_x(self) -> ndarray: + def has_x(self) -> bool: + """ + Returns `True` if the instance is equipped with coordinates, `False` + if it isn't. + """ return self._dbkey_x_ in self._wrapped.fields + + @property + def has_activity(self) -> bool: + """ + Returns `True` if the instance is equipped with activity information, ű + `False` if it isn't. + """ + return self._dbkey_activity_ in self._wrapped.fields @property def container(self) -> PolyDataProtocol: @@ -158,7 +180,7 @@ def container(self) -> PolyDataProtocol: return self._container @container.setter - def container(self, value: PolyDataProtocol): + def container(self, value: PolyDataProtocol) -> None: """ Sets the container of the block. """ @@ -183,35 +205,81 @@ def frame(self) -> FrameLike: @property def activity(self) -> ndarray: + """ + Returns the activity of the points as an 1d NumPy bool array. + """ return self._wrapped[self._dbkey_activity_].to_numpy() @activity.setter - def activity(self, value: ndarray): + def activity(self, value: ndarray) -> None: + """ + Sets the activity of the points with an 1d NumPy bool array. + """ if not isinstance(value, ndarray): raise TypeError(f"Expected a NumPy array, got {type(value)}.") - + if not isboolarray(value): - raise ValueError(f"Expected a boolean array, got {value.dtype}.") - + raise ValueError(f"Expected a boolean array, got dtype {value.dtype}.") + + if not len(value.shape) == 1: + raise ValueError("The provided array must be 1 dimensional.") + + if self.has_x and not len(value) == len(self): + raise ValueError( + f"The provided array contains {len(value)} values, but there are " + f"{len(self)} points in the dataset." + ) + self._wrapped[self._dbkey_activity_] = value @property def x(self) -> ndarray: + """ + Returns the coordinates as a 2d NumPy array. + """ return self._wrapped[self._dbkey_x_].to_numpy() @x.setter - def x(self, value: ndarray): - assert isinstance(value, ndarray) - self._wrapped[self._dbkey_x_] = value + def x(self, value: ndarray) -> None: + """ + Sets the coordinates with a 2d NumPy float array. + """ + if not isinstance(value, ndarray): + raise TypeError(f"Expected a NumPy array, got {type(value)}") + + if not len(value.shape) == 2: + raise ValueError("The provided array must be 2 dimensional.") + + self._wrapped[self._dbkey_x_] = value.astype(float) @property def id(self) -> ndarray: + """ + Returns the IDs of the points as an 1d NumPy integer array. + """ return self._wrapped[self._dbkey_id_].to_numpy() @id.setter - def id(self, value: ndarray): - assert isinstance(value, ndarray) - self._wrapped[self._dbkey_id_] = value + def id(self, value: ndarray) -> None: + """ + Sets the IDs of the points with an 1d NumPy integer array. + """ + if not isinstance(value, ndarray): + raise TypeError(f"Expected a NumPy array, got {type(value)}") + + if not isintegerarray(value): + raise ValueError(f"Expected an integer array, got dtype {value.dtype}.") + + if not len(value.shape) == 1: + raise ValueError("The provided array must be 1 dimensional.") + + if self.has_x and not len(value) == len(self): + raise ValueError( + f"The provided array contains {len(value)} values, but there are " + f"{len(self)} points in the dataset." + ) + + self._wrapped[self._dbkey_id_] = value.astype(int) def pull(self, key: str, ndf: Union[ndarray, csr_matrix] = None) -> ndarray: """ From 92ef446be26f6cd57102b43d9597878ae8dd73f3 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 01:21:30 +0200 Subject: [PATCH 14/47] added Quadrature class to module --- src/sigmaepsilon/mesh/utils/cells/numint.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sigmaepsilon/mesh/utils/cells/numint.py b/src/sigmaepsilon/mesh/utils/cells/numint.py index b8a497f..8638705 100644 --- a/src/sigmaepsilon/mesh/utils/cells/numint.py +++ b/src/sigmaepsilon/mesh/utils/cells/numint.py @@ -1,9 +1,12 @@ from typing import Tuple +from collections import namedtuple + import numpy as np from numpy import ndarray from sigmaepsilon.math.numint import gauss_points as gp +Quadrature = namedtuple("QuadratureRule", ["inds", "pos", "weight"]) # LINES From f13ea87eb962ee8f4176ac4bc42a63f32ad523a8 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 01:21:59 +0200 Subject: [PATCH 15/47] added gauss point parser utility --- src/sigmaepsilon/mesh/data/polycell.py | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/sigmaepsilon/mesh/data/polycell.py b/src/sigmaepsilon/mesh/data/polycell.py index 87f3bed..e46bc44 100644 --- a/src/sigmaepsilon/mesh/data/polycell.py +++ b/src/sigmaepsilon/mesh/data/polycell.py @@ -8,6 +8,8 @@ Optional, TypeVar, Generic, + Hashable, + Callable ) from numbers import Number @@ -57,6 +59,7 @@ from ..vtkutils import mesh_to_UnstructuredGrid as mesh_to_vtk from ..topoarray import TopologyArray from ..space import CartesianFrame +from ..utils.cells.numint import Quadrature from ..config import __haspyvista__ if __haspyvista__: @@ -123,6 +126,32 @@ def _get_points_and_range( points = atleastnd(points, 2, front=True) return points, rng + + @staticmethod + def _parse_gauss_data(quad_dict: dict, key: Hashable): + value: Union[Callable, str, dict] = quad_dict[key] + + if isinstance(value, dict): + for qinds, qvalue in value.items(): + if isinstance(qvalue, str): + for v in PolyCell._parse_gauss_data(value, qvalue): + v.inds = qinds + yield v + else: + qpos, qweight = qvalue + quad = Quadrature(qinds, qpos, qweight) + yield quad + elif isinstance(value, Callable): + qpos, qweight = value() + quad = Quadrature(np.s_[:], qpos, qweight) + yield quad + elif isinstance(value, str): + for v in PolyCell._parse_gauss_data(quad_dict, value): + yield v + else: + qpos, qweight = value + quad = Quadrature(np.s_[:], qpos, qweight) + yield quad @CellData.frames.getter def frames(self) -> ndarray: From eef4243bae8670633db4933bcbf30e2fa327c24c Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 01:26:23 +0200 Subject: [PATCH 16/47] fixed missing default value for argument --- src/sigmaepsilon/mesh/data/polycell.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sigmaepsilon/mesh/data/polycell.py b/src/sigmaepsilon/mesh/data/polycell.py index e46bc44..ce53258 100644 --- a/src/sigmaepsilon/mesh/data/polycell.py +++ b/src/sigmaepsilon/mesh/data/polycell.py @@ -9,7 +9,7 @@ TypeVar, Generic, Hashable, - Callable + Callable, ) from numbers import Number @@ -126,7 +126,7 @@ def _get_points_and_range( points = atleastnd(points, 2, front=True) return points, rng - + @staticmethod def _parse_gauss_data(quad_dict: dict, key: Hashable): value: Union[Callable, str, dict] = quad_dict[key] @@ -525,7 +525,7 @@ def points_of_cells( return pcoords_to_coords(points, ecoords, shp) # (nE, nP, nD) def local_coordinates( - self, *, target: Optional[Union[str, CartesianFrame, None]] + self, *, target: Optional[Union[str, CartesianFrame, None]] = None ) -> ndarray: """ Returns local coordinates of the cells as a 3d float NumPy array. From 1310d61cebb722adb665199d8526dec2266f1765 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 01:30:59 +0200 Subject: [PATCH 17/47] added example to docstring --- src/sigmaepsilon/mesh/data/pointdata.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sigmaepsilon/mesh/data/pointdata.py b/src/sigmaepsilon/mesh/data/pointdata.py index 5525ae6..8a1e1de 100644 --- a/src/sigmaepsilon/mesh/data/pointdata.py +++ b/src/sigmaepsilon/mesh/data/pointdata.py @@ -39,6 +39,15 @@ class PointData(AkWrapper, ABC_AkWrapper): frame: CartesianFrame, Optional The coordinate frame the points are understood in. Default is `None`, which means the standard global frame (the ambient frame). + + Example + ------- + >>> from sigmaepsilon.mesh import CartesianFrame, PointData, triangulate + >>> A = CartesianFrame(dim=3) + >>> coords = triangulate(size=(800, 600), shape=(10, 10))[0] + >>> pd = PointData(coords=coords, frame=frame) + >>> pd.activity = np.ones((len(pd)), dtype=bool) + >>> pd.id = np.arange(len(pd)) """ _point_cls_ = PointCloud From f623b9a2474a440ff29248f1a564787b4a41aa34 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 01:31:13 +0200 Subject: [PATCH 18/47] removed duplicate import --- tests/test_pointdata.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_pointdata.py b/tests/test_pointdata.py index 3eb2c42..e0be38e 100644 --- a/tests/test_pointdata.py +++ b/tests/test_pointdata.py @@ -5,7 +5,6 @@ from sigmaepsilon.core.testing import SigmaEpsilonTestCase from sigmaepsilon.math.linalg import FrameLike from sigmaepsilon.mesh import CartesianFrame, PointData, triangulate -from sigmaepsilon.mesh import PointData class TestPointData(SigmaEpsilonTestCase): From 8577f1596d9ffac988d6f49f638c133800d9e818 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 01:43:30 +0200 Subject: [PATCH 19/47] code formatting --- src/sigmaepsilon/mesh/topoarray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sigmaepsilon/mesh/topoarray.py b/src/sigmaepsilon/mesh/topoarray.py index aa6b9df..08224a3 100644 --- a/src/sigmaepsilon/mesh/topoarray.py +++ b/src/sigmaepsilon/mesh/topoarray.py @@ -120,7 +120,7 @@ def __array_function__(self, func, types, args, kwargs): # __array_function__ to handle DiagonalArray objects. if not all(issubclass(t, self.__class__) for t in types): return NotImplemented - return HANDLED_FUNCTIONS[func](*args, **kwargs) + return HANDLED_FUNCTIONS[func](*args, **kwargs) def implements(numpy_function): From 6f8fc534fb05f462743600fa4e37b78529fc2e9a Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 26 Oct 2023 01:43:41 +0200 Subject: [PATCH 20/47] added __init__.py --- src/sigmaepsilon/mesh/domains/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/sigmaepsilon/mesh/domains/__init__.py diff --git a/src/sigmaepsilon/mesh/domains/__init__.py b/src/sigmaepsilon/mesh/domains/__init__.py new file mode 100644 index 0000000..e69de29 From 40b21f9017bcc1b079b54690198acbd77169fbbd Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Mon, 30 Oct 2023 22:54:18 +0100 Subject: [PATCH 21/47] fixed T6 shape functions --- src/sigmaepsilon/mesh/utils/cells/t6.py | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/sigmaepsilon/mesh/utils/cells/t6.py b/src/sigmaepsilon/mesh/utils/cells/t6.py index ed7c1f3..afc7bbd 100644 --- a/src/sigmaepsilon/mesh/utils/cells/t6.py +++ b/src/sigmaepsilon/mesh/utils/cells/t6.py @@ -8,7 +8,7 @@ @njit(nogil=True, cache=__cache) def monoms_T6(x: ndarray) -> ndarray: r, s = x - return np.array([1, r, s, r ** 2, s ** 2, r * s], dtype=float) + return np.array([1, r, s, r**2, s**2, r * s], dtype=float) @njit(nogil=True, cache=__cache) @@ -16,12 +16,12 @@ def shp_T6(pcoord: ndarray): r, s = pcoord[0:2] res = np.array( [ - 2.0 * r ** 2 + 4.0 * r * s - 3.0 * r + 2.0 * s ** 2 - 3.0 * s + 1.0, - 2.0 * r ** 2 - 1.0 * r, - 2.0 * s ** 2 - 1.0 * s, - -4.0 * r ** 2 - 4.0 * r * s + 4.0 * r, - 4.0 * r * s, - -4.0 * r * s - 4.0 * s ** 2 + 4.0 * s, + 2.0 * r**2 + 4.0 * r * s - r / 3.0 + 2.0 * s**2 - s / 3.0 - 1 / 9, + 2.0 * r**2 + r / 3.0 - 1 / 9, + 2.0 * s**2 + s / 3.0 - 1 / 9, + -4.0 * r**2 - 4.0 * r * s - 4.0 * s / 3.0 + 4 / 9, + 4.0 * r * s + 4 * r / 3 + 4.0 * s / 3.0 + 4 / 9, + -4.0 * r * s - 4 * r / 3 - 4.0 * s**2 + 4 / 9, ], dtype=pcoord.dtype, ) @@ -61,12 +61,12 @@ def dshp_T6(pcoord): r, s = pcoord[0:2] res = np.array( [ - [4.0 * r + 4.0 * s - 3.0, 4.0 * r + 4.0 * s - 3.0], - [4.0 * r - 1.0, 0], - [0, 4.0 * s - 1.0], - [-8.0 * r - 4.0 * s + 4.0, -4.0 * r], - [4.0 * s, 4.0 * r], - [-4.0 * s, -4.0 * r - 8.0 * s + 4.0], + [4.0*r + 4.0*s - 1/3, 4.0*r + 4.0*s - 1/3], + [4.0*r + 1/3, 0.0], + [0.0, 4.0*s + 1/3], + [-8.0*r - 4.0*s, -4.0*r - 4/3], + [4.0*s + 4/3, 4.0 * r + 4/3], + [-4.0 * s - 4/3, -4.0 * r - 8.0 * s], ] ) return res From 98b5d919f61439c78b279ef9ac0020ce6b4edbb1 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Mon, 30 Oct 2023 22:54:29 +0100 Subject: [PATCH 22/47] added tests for T6 cell --- tests/cells/test_tri.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/cells/test_tri.py b/tests/cells/test_tri.py index 78957e7..f602e78 100644 --- a/tests/cells/test_tri.py +++ b/tests/cells/test_tri.py @@ -6,7 +6,7 @@ from sigmaepsilon.core.testing import SigmaEpsilonTestCase from sigmaepsilon.math import atleast2d from sigmaepsilon.mesh import PolyData, PointData, CartesianFrame -from sigmaepsilon.mesh.cells import T3 +from sigmaepsilon.mesh.cells import T3, T6 from sigmaepsilon.mesh.utils.tri import ( nat_to_loc_tri, loc_to_nat_tri, @@ -58,6 +58,42 @@ def test_T3(self, N: int = 3): nX = 2 shpmf = T3.Geometry.shape_function_matrix(x_loc, N=nX) self.assertEqual(shpmf.shape, (1, nX, 3 * nX)) + + def test_T6(self, N: int = 3): + shp, dshp, shpf, shpmf, dshpf = T6.Geometry.generate_class_functions( + return_symbolic=True + ) + r, s = symbols("r, s", real=True) + + for _ in range(N): + A1, A2 = np.random.rand(2) + A3 = 1 - A1 - A2 + x_nat = np.array([A1, A2, A3]) + x_loc = atleast2d(nat_to_loc_tri(x_nat)) + + shpA = shpf(x_loc) + shpB = T6.Geometry.shape_function_values(x_loc) + shp_sym = shp.subs({r: x_loc[0, 0], s: x_loc[0, 1]}) + self.assertTrue(np.allclose(shpA, shpB)) + self.assertTrue( + np.allclose(shpA, np.array(shp_sym.tolist(), dtype=float).T) + ) + + dshpA = dshpf(x_loc) + dshpB = T6.Geometry.shape_function_derivatives(x_loc) + dshp_sym = dshp.subs({r: x_loc[0, 0], s: x_loc[0, 1]}) + self.assertTrue(np.allclose(dshpA, dshpB)) + self.assertTrue( + np.allclose(dshpA, np.array(dshp_sym.tolist(), dtype=float)) + ) + + shpmfA = shpmf(x_loc) + shpmfB = T6.Geometry.shape_function_matrix(x_loc) + self.assertTrue(np.allclose(shpmfA, shpmfB)) + + nX = 2 + shpmf = T6.Geometry.shape_function_matrix(x_loc, N=nX) + self.assertEqual(shpmf.shape, (1, nX, 6 * nX)) class TestTriutils(SigmaEpsilonTestCase): From fb8180f4ce65eeb2f786143d5c712b8971943fca Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Mon, 30 Oct 2023 23:06:24 +0100 Subject: [PATCH 23/47] blacked --- src/sigmaepsilon/mesh/__init__.py | 2 +- src/sigmaepsilon/mesh/cellapproximator.py | 10 ++- src/sigmaepsilon/mesh/cells/__init__.py | 2 +- src/sigmaepsilon/mesh/cells/t3.py | 2 +- src/sigmaepsilon/mesh/cells/t6.py | 4 +- src/sigmaepsilon/mesh/cells/tet10.py | 2 +- src/sigmaepsilon/mesh/cells/w18.py | 20 ++--- src/sigmaepsilon/mesh/data/pointdata.py | 6 +- src/sigmaepsilon/mesh/data/polydata.py | 15 ++-- src/sigmaepsilon/mesh/domains/section.py | 2 +- src/sigmaepsilon/mesh/examples.py | 4 +- src/sigmaepsilon/mesh/io/from_pyvista.py | 1 + src/sigmaepsilon/mesh/io/to_k3d.py | 1 + src/sigmaepsilon/mesh/io/to_pyvista.py | 3 +- src/sigmaepsilon/mesh/io/to_vtk.py | 1 + src/sigmaepsilon/mesh/plotting/__init__.py | 2 +- src/sigmaepsilon/mesh/plotting/k3dplot.py | 5 +- .../mesh/plotting/mpl/__init__.py | 10 +-- src/sigmaepsilon/mesh/plotting/mpl/triplot.py | 2 +- src/sigmaepsilon/mesh/plotting/mpl/utils.py | 2 +- .../mesh/plotting/plotly/lines.py | 13 +-- .../mesh/plotting/plotly/points.py | 5 +- src/sigmaepsilon/mesh/plotting/plotly/tri.py | 7 +- src/sigmaepsilon/mesh/plotting/pvplot.py | 27 ++++--- src/sigmaepsilon/mesh/recipes.py | 6 +- src/sigmaepsilon/mesh/topoarray.py | 2 +- src/sigmaepsilon/mesh/triang.py | 2 +- src/sigmaepsilon/mesh/utils/cells/q8.py | 80 +++++++++---------- src/sigmaepsilon/mesh/utils/cells/t6.py | 24 +++--- src/sigmaepsilon/mesh/utils/cells/tet4.py | 2 +- .../mesh/utils/topology/__init__.py | 2 +- src/sigmaepsilon/mesh/utils/topology/tr.py | 12 +-- src/sigmaepsilon/mesh/utils/utils.py | 4 +- 33 files changed, 145 insertions(+), 137 deletions(-) diff --git a/src/sigmaepsilon/mesh/__init__.py b/src/sigmaepsilon/mesh/__init__.py index 78c3ac5..92ec1f5 100644 --- a/src/sigmaepsilon/mesh/__init__.py +++ b/src/sigmaepsilon/mesh/__init__.py @@ -20,7 +20,7 @@ "CartesianFrame", "PolyData", "LineData", - "PolyData1d", + "PolyData1d", "PointData", "TriMesh", # diff --git a/src/sigmaepsilon/mesh/cellapproximator.py b/src/sigmaepsilon/mesh/cellapproximator.py index 7c6954b..aed21e1 100644 --- a/src/sigmaepsilon/mesh/cellapproximator.py +++ b/src/sigmaepsilon/mesh/cellapproximator.py @@ -44,17 +44,19 @@ def _approximator( if shp_source_inverse is None: assert isinstance(x_source, Iterable) shp_source = shp_fnc(x_source) # (nP_source, nNE) - + num_rows, num_columns = shp_source.shape rank = np.linalg.matrix_rank(shp_source) - square_and_full_rank = (num_rows == num_columns) and rank == num_columns == num_rows + square_and_full_rank = ( + num_rows == num_columns + ) and rank == num_columns == num_rows if not square_and_full_rank: # pragma: no cover warnings.warn( "The approximation involves the calculation of a generalized inverse " "which probably results in loss of precision.", - SigmaEpsilonPerformanceWarning + SigmaEpsilonPerformanceWarning, ) - + shp_source_inverse = generalized_inverse(shp_source) if not isinstance(values_source, ndarray): diff --git a/src/sigmaepsilon/mesh/cells/__init__.py b/src/sigmaepsilon/mesh/cells/__init__.py index cb82a77..e2b33f9 100644 --- a/src/sigmaepsilon/mesh/cells/__init__.py +++ b/src/sigmaepsilon/mesh/cells/__init__.py @@ -7,7 +7,7 @@ from .t3 import T3 as Tri from .q4 import Q4 from .q4 import Q4 as Quad -from.q8 import Q8 +from .q8 import Q8 from .q9 import Q9 from .t6 import T6 from .h8 import H8 diff --git a/src/sigmaepsilon/mesh/cells/t3.py b/src/sigmaepsilon/mesh/cells/t3.py index 36ae433..bcffb72 100644 --- a/src/sigmaepsilon/mesh/cells/t3.py +++ b/src/sigmaepsilon/mesh/cells/t3.py @@ -20,7 +20,7 @@ class T3(PolyCell): """ A class to handle 3-noded triangles. - + Example ------- >>> from sigmaepsilon.mesh import TriMesh, CartesianFrame, PointData, triangulate diff --git a/src/sigmaepsilon/mesh/cells/t6.py b/src/sigmaepsilon/mesh/cells/t6.py index 7597836..4891172 100644 --- a/src/sigmaepsilon/mesh/cells/t6.py +++ b/src/sigmaepsilon/mesh/cells/t6.py @@ -21,7 +21,7 @@ class T6(PolyCell): """ A class to handle 6-noded triangles. - + Example ------- >>> from sigmaepsilon.mesh import TriMesh, CartesianFrame, PointData, triangulate @@ -63,7 +63,7 @@ def polybase(cls) -> Tuple[List]: A list of monomials. """ locvars = r, s = symbols("r s", real=True) - monoms = [1, r, s, r**2, s**2, r * s] + monoms = [1, r, s, r ** 2, s ** 2, r * s] return locvars, monoms @classmethod diff --git a/src/sigmaepsilon/mesh/cells/tet10.py b/src/sigmaepsilon/mesh/cells/tet10.py index 7dfe87c..aae2a27 100644 --- a/src/sigmaepsilon/mesh/cells/tet10.py +++ b/src/sigmaepsilon/mesh/cells/tet10.py @@ -42,7 +42,7 @@ def polybase(cls) -> Tuple[List]: A list of monomials. """ locvars = r, s, t = symbols("r s t", real=True) - monoms = [1, r, s, t, r * s, r * t, s * t, r**2, s**2, t**2] + monoms = [1, r, s, t, r * s, r * t, s * t, r ** 2, s ** 2, t ** 2] return locvars, monoms @classmethod diff --git a/src/sigmaepsilon/mesh/cells/w18.py b/src/sigmaepsilon/mesh/cells/w18.py index b3e7303..d3bc027 100644 --- a/src/sigmaepsilon/mesh/cells/w18.py +++ b/src/sigmaepsilon/mesh/cells/w18.py @@ -46,21 +46,21 @@ def polybase(cls) -> Tuple[List]: 1, r, s, - r**2, - s**2, + r ** 2, + s ** 2, r * s, t, t * r, t * s, - t * r**2, - t * s**2, + t * r ** 2, + t * s ** 2, t * r * s, - t**2, - t**2 * r, - t**2 * s, - t**2 * r**2, - t**2 * s**2, - t**2 * r * s, + t ** 2, + t ** 2 * r, + t ** 2 * s, + t ** 2 * r ** 2, + t ** 2 * s ** 2, + t ** 2 * r * s, ] return locvars, monoms diff --git a/src/sigmaepsilon/mesh/data/pointdata.py b/src/sigmaepsilon/mesh/data/pointdata.py index 8a1e1de..91ac4f1 100644 --- a/src/sigmaepsilon/mesh/data/pointdata.py +++ b/src/sigmaepsilon/mesh/data/pointdata.py @@ -39,7 +39,7 @@ class PointData(AkWrapper, ABC_AkWrapper): frame: CartesianFrame, Optional The coordinate frame the points are understood in. Default is `None`, which means the standard global frame (the ambient frame). - + Example ------- >>> from sigmaepsilon.mesh import CartesianFrame, PointData, triangulate @@ -172,7 +172,7 @@ def has_x(self) -> bool: if it isn't. """ return self._dbkey_x_ in self._wrapped.fields - + @property def has_activity(self) -> bool: """ @@ -275,7 +275,7 @@ def id(self, value: ndarray) -> None: """ if not isinstance(value, ndarray): raise TypeError(f"Expected a NumPy array, got {type(value)}") - + if not isintegerarray(value): raise ValueError(f"Expected an integer array, got dtype {value.dtype}.") diff --git a/src/sigmaepsilon/mesh/data/polydata.py b/src/sigmaepsilon/mesh/data/polydata.py index 7b3669c..43cf9e0 100644 --- a/src/sigmaepsilon/mesh/data/polydata.py +++ b/src/sigmaepsilon/mesh/data/polydata.py @@ -26,12 +26,7 @@ from sigmaepsilon.math.linalg import Vector, ReferenceFrame as FrameLike from sigmaepsilon.math import atleast1d, minmax -from ..typing import ( - PolyDataProtocol as PDP, - PolyDataLike, - PointDataLike, - PolyCellLike -) +from ..typing import PolyDataProtocol as PDP, PolyDataLike, PointDataLike, PolyCellLike from .akwrapper import AkWrapper from .pointdata import PointData @@ -1785,8 +1780,10 @@ def to_pv( pyvista.UnstructuredGrid or pyvista.MultiBlock """ exporter: Callable = exporters["PyVista"] - return exporter(self, deepcopy=deepcopy, multiblock=multiblock, scalars=scalars) - + return exporter( + self, deepcopy=deepcopy, multiblock=multiblock, scalars=scalars + ) + if __hask3d__: def to_k3d(self, *args, **kwargs) -> k3d.Plot: @@ -1823,7 +1820,7 @@ def k3dplot(self, *args, **kwargs) -> k3d.Plot: ------- k3d.Plot A K3D Plot Widget, which is a result of a call to `k3d.plot`. - + See Also -------- :func:`to_k3d` diff --git a/src/sigmaepsilon/mesh/domains/section.py b/src/sigmaepsilon/mesh/domains/section.py index 1615e29..0d9386d 100644 --- a/src/sigmaepsilon/mesh/domains/section.py +++ b/src/sigmaepsilon/mesh/domains/section.py @@ -59,7 +59,7 @@ def generate_mesh( area = geometry.calculate_area() mesh_sizes_max = [] if isinstance(l_max, float): - mesh_sizes_max.append(l_max**2 * np.sqrt(3) / 4) + mesh_sizes_max.append(l_max ** 2 * np.sqrt(3) / 4) if isinstance(a_max, float): mesh_sizes_max.append(a_max) if isinstance(n_max, int): diff --git a/src/sigmaepsilon/mesh/examples.py b/src/sigmaepsilon/mesh/examples.py index 6b54c7c..a746d18 100644 --- a/src/sigmaepsilon/mesh/examples.py +++ b/src/sigmaepsilon/mesh/examples.py @@ -57,5 +57,5 @@ def compound_mesh() -> PolyData: mesh.to_standard_form() mesh.lock(create_mappers=True) - - return mesh \ No newline at end of file + + return mesh diff --git a/src/sigmaepsilon/mesh/io/from_pyvista.py b/src/sigmaepsilon/mesh/io/from_pyvista.py index 5c3e5cb..8abc3b7 100644 --- a/src/sigmaepsilon/mesh/io/from_pyvista.py +++ b/src/sigmaepsilon/mesh/io/from_pyvista.py @@ -68,6 +68,7 @@ def from_pv(pvobj: pyVistaLike) -> PolyData: return polydata + else: # pragma: no cover def from_pv(*_) -> None: diff --git a/src/sigmaepsilon/mesh/io/to_k3d.py b/src/sigmaepsilon/mesh/io/to_k3d.py index 0b0e869..9ad27b5 100644 --- a/src/sigmaepsilon/mesh/io/to_k3d.py +++ b/src/sigmaepsilon/mesh/io/to_k3d.py @@ -137,6 +137,7 @@ def to_k3d( return scene + else: # pragma: no cover def to_k3d(*_, **__): diff --git a/src/sigmaepsilon/mesh/io/to_pyvista.py b/src/sigmaepsilon/mesh/io/to_pyvista.py index 21dcb8a..72c5b5f 100644 --- a/src/sigmaepsilon/mesh/io/to_pyvista.py +++ b/src/sigmaepsilon/mesh/io/to_pyvista.py @@ -4,7 +4,7 @@ if __haspyvista__: from typing import Union, Optional from contextlib import suppress - + import pyvista as pv import vtk from numpy import ndarray @@ -73,6 +73,7 @@ def to_pv( res.append(pvobj) return res + else: # pragma: no cover def to_pv(*_) -> None: diff --git a/src/sigmaepsilon/mesh/io/to_vtk.py b/src/sigmaepsilon/mesh/io/to_vtk.py index fb9d591..c86ba2c 100644 --- a/src/sigmaepsilon/mesh/io/to_vtk.py +++ b/src/sigmaepsilon/mesh/io/to_vtk.py @@ -46,6 +46,7 @@ def to_vtk( else: return ugrids[0] + else: # pragma: no cover def to_vtk(*_) -> None: diff --git a/src/sigmaepsilon/mesh/plotting/__init__.py b/src/sigmaepsilon/mesh/plotting/__init__.py index 85ccd0d..48c661b 100644 --- a/src/sigmaepsilon/mesh/plotting/__init__.py +++ b/src/sigmaepsilon/mesh/plotting/__init__.py @@ -27,5 +27,5 @@ "scatter_points_plotly", "plot_lines_plotly", "triplot_plotly", - "k3dplot" + "k3dplot", ] diff --git a/src/sigmaepsilon/mesh/plotting/k3dplot.py b/src/sigmaepsilon/mesh/plotting/k3dplot.py index 7480cd2..afcb9f0 100644 --- a/src/sigmaepsilon/mesh/plotting/k3dplot.py +++ b/src/sigmaepsilon/mesh/plotting/k3dplot.py @@ -37,11 +37,11 @@ def k3dplot( Example ------- Get a compound mesh, add some random data to it and plot it with K3D. - + .. code-block:: python # doctest: +SKIP - + from sigmaepsilon.mesh.plotting import k3dplot from sigmaepsilon.mesh.examples import compound_mesh from k3d.colormaps import matplotlib_color_maps @@ -64,6 +64,7 @@ def k3dplot( scene = k3d.plot(menu_visibility=menu_visibility) return obj.to_k3d(scene=scene, **kwargs) + else: # pragma: no cover def k3dplot(*_, **__) -> None: diff --git a/src/sigmaepsilon/mesh/plotting/mpl/__init__.py b/src/sigmaepsilon/mesh/plotting/mpl/__init__.py index b0e7af2..293bb59 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/__init__.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/__init__.py @@ -3,10 +3,10 @@ from .utils import decorate_mpl_ax __all__ = [ - "triplot_mpl_hinton", - "triplot_mpl_mesh", + "triplot_mpl_hinton", + "triplot_mpl_mesh", "triplot_mpl_data", - "parallel_mpl", + "parallel_mpl", "aligned_parallel_mpl", - "decorate_mpl_ax" -] \ No newline at end of file + "decorate_mpl_ax", +] diff --git a/src/sigmaepsilon/mesh/plotting/mpl/triplot.py b/src/sigmaepsilon/mesh/plotting/mpl/triplot.py index dc2f035..b14a55b 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/triplot.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/triplot.py @@ -17,7 +17,6 @@ from .utils import decorate_mpl_ax, triplotter, TriPatchCollection - @triplotter def triplot_mpl_hinton( triobj: Any, @@ -384,6 +383,7 @@ def triplot_mpl_data( ) return axobj + else: # pragma: no cover def triplot_mpl_mesh(*_, **__): diff --git a/src/sigmaepsilon/mesh/plotting/mpl/utils.py b/src/sigmaepsilon/mesh/plotting/mpl/utils.py index 5a172af..7fe3206 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/utils.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/utils.py @@ -98,7 +98,7 @@ def get_fig_axes( """ if isinstance(ax, (tuple, list)): axes = ax - + if fig is not None: if axes is not None: return fig, axes diff --git a/src/sigmaepsilon/mesh/plotting/plotly/lines.py b/src/sigmaepsilon/mesh/plotting/plotly/lines.py index ba8b97f..6a59f14 100644 --- a/src/sigmaepsilon/mesh/plotting/plotly/lines.py +++ b/src/sigmaepsilon/mesh/plotting/plotly/lines.py @@ -47,10 +47,10 @@ def plot_lines_plotly( """ Plots points and lines in 3d space optionally with data defined on the points. If data is provided, the values are shown in a tooltip when howering above a point. - + .. note: Currently only 2 noded linear lines are supported. - + Parameters ---------- coords: numpy.ndarray @@ -58,19 +58,19 @@ def plot_lines_plotly( second along spatial dimensions. topo: numpy.ndarray The topology of the lines, where the first axis runs along the lines, the - second along the nodes. + second along the nodes. scalars: numpy.ndarray The values to show in the tooltips of the points as a 1d or 2d NumPy array. The length of the array must equal the number of points. Default is None. marker_symbol: str, Optional - The symbol to use for the points. Refer to Plotly's documentation for the + The symbol to use for the points. Refer to Plotly's documentation for the possible options. Default is "circle". - + Example ------- .. plotly:: :include-source: True - + from sigmaepsilon.mesh.plotting import plot_lines_plotly from sigmaepsilon.mesh import grid from sigmaepsilon.mesh.utils.topology.tr import H8_to_L2 @@ -117,6 +117,7 @@ def plot_lines_plotly( return fig + else: # pragma: no cover def plot_lines_plotly(*_, **__): diff --git a/src/sigmaepsilon/mesh/plotting/plotly/points.py b/src/sigmaepsilon/mesh/plotting/plotly/points.py index 24fb39d..31fe67d 100644 --- a/src/sigmaepsilon/mesh/plotting/plotly/points.py +++ b/src/sigmaepsilon/mesh/plotting/plotly/points.py @@ -34,12 +34,12 @@ def scatter_points_plotly( The size of the balls at the point coordinates. Default is 1. scalar_labels: Iterable[str], Optional The labels for the columns in 'scalars'. Default is None. - + Example ------- .. plotly:: :include-source: True - + from sigmaepsilon.mesh.plotting import scatter_points_plotly import numpy as np points = np.array([ @@ -115,6 +115,7 @@ def scatter_points_plotly( return fig + else: # pragma: no cover def scatter_points_plotly(*_, **__): diff --git a/src/sigmaepsilon/mesh/plotting/plotly/tri.py b/src/sigmaepsilon/mesh/plotting/plotly/tri.py index a6dace4..2c314f2 100644 --- a/src/sigmaepsilon/mesh/plotting/plotly/tri.py +++ b/src/sigmaepsilon/mesh/plotting/plotly/tri.py @@ -30,17 +30,17 @@ def triplot_plotly( If True, plots the edges of the mesh. Default is False. edges: numpy.ndarray, Optional The edges to plot. If provided, `plot_edges` is ignored. Default is None. - + Returns ------- figure: :class:`plotly.graph_objects.Figure` The figure object. - + Example ------- .. plotly:: :include-source: True - + from sigmaepsilon.mesh.plotting import triplot_plotly from sigmaepsilon.mesh import grid from sigmaepsilon.mesh.utils.topology.tr import Q4_to_T3 @@ -112,6 +112,7 @@ def triplot_plotly( return fig + else: # pragma: no cover def triplot_plotly(*_, **__): diff --git a/src/sigmaepsilon/mesh/plotting/pvplot.py b/src/sigmaepsilon/mesh/plotting/pvplot.py index a334d95..fef7c6f 100644 --- a/src/sigmaepsilon/mesh/plotting/pvplot.py +++ b/src/sigmaepsilon/mesh/plotting/pvplot.py @@ -96,12 +96,12 @@ def pvplot( Union[None, pv.Plotter, numpy.ndarray] A PyVista plotter if `return_plotter` is `True`, a NumPy array if `return_img` is `True`, or nothing. - + Example ------- .. plot:: :include-source: True - + from sigmaepsilon.mesh.plotting import pvplot from sigmaepsilon.mesh.downloads import download_gt40 import matplotlib.pyplot as plt @@ -109,7 +109,7 @@ def pvplot( img=pvplot(mesh, notebook=False, return_img=True) plt.imshow(img) plt.axis('off') - """ + """ if not isinstance(obj, PolyData): # pragma: no cover raise TypeError(f"Expected PolyData, got {type(obj)}.") @@ -145,20 +145,20 @@ def pvplot( if plotter is None: pvparams = dict() - + if window_size is not None: pvparams.update(window_size=window_size) - + pvparams.update(kwargs) pvparams.update(notebook=notebook) pvparams.update(theme=theme) - + if "title" not in pvparams: pvparams["title"] = "SigmaEpsilon" - + if not notebook and return_img: pvparams["off_screen"] = True - + plotter = pv.Plotter(**pvparams) if camera_position is not None: @@ -179,16 +179,16 @@ def pvplot( if has_data: config.pop("color", None) params.update(config) - + if cmap is not None: params["cmap"] = cmap - + if NDIM > 1: params["show_edges"] = show_edges - + if isinstance(show_scalar_bar, bool): params["show_scalar_bar"] = show_scalar_bar - + plotter.add_mesh(poly, **params) if return_plotter: @@ -205,6 +205,7 @@ def pvplot( return plotter.show(**show_params) + else: # pragma: no cover def pvplot(*_, **__) -> None: @@ -216,4 +217,4 @@ def pvplot(*_, **__) -> None: plotters["PyVista"] = pvplot -__all__ = ["pvplot"] \ No newline at end of file +__all__ = ["pvplot"] diff --git a/src/sigmaepsilon/mesh/recipes.py b/src/sigmaepsilon/mesh/recipes.py index 37095b8..80a5fbc 100644 --- a/src/sigmaepsilon/mesh/recipes.py +++ b/src/sigmaepsilon/mesh/recipes.py @@ -373,13 +373,13 @@ def perforated_cube( :class:`~sigmaepsilon.mesh.data.polydata.PolyData` """ size = (lx, ly) - + if lmax is not None: shape = (max([int(lx / lmax), 4]), max([int(ly / lmax), 4])) else: shape = (4, 4) coords, _ = grid(size=size, shape=shape, eshape=(2, 2), centralize=True) - + if lmax is not None: where = np.hypot(coords[:, 0], coords[:, 1]) > (radius + lmax) else: @@ -391,7 +391,7 @@ def perforated_cube( nangles = max(int(2 * np.pi * radius / lmax), 8) else: nangles = 16 - + angles = np.linspace(0, 2 * np.pi, nangles, endpoint=False) x_circle = (radius * np.cos(angles)).flatten() y_circle = (radius * np.sin(angles)).flatten() diff --git a/src/sigmaepsilon/mesh/topoarray.py b/src/sigmaepsilon/mesh/topoarray.py index 08224a3..aa6b9df 100644 --- a/src/sigmaepsilon/mesh/topoarray.py +++ b/src/sigmaepsilon/mesh/topoarray.py @@ -120,7 +120,7 @@ def __array_function__(self, func, types, args, kwargs): # __array_function__ to handle DiagonalArray objects. if not all(issubclass(t, self.__class__) for t in types): return NotImplemented - return HANDLED_FUNCTIONS[func](*args, **kwargs) + return HANDLED_FUNCTIONS[func](*args, **kwargs) def implements(numpy_function): diff --git a/src/sigmaepsilon/mesh/triang.py b/src/sigmaepsilon/mesh/triang.py index d9c20ae..0909651 100644 --- a/src/sigmaepsilon/mesh/triang.py +++ b/src/sigmaepsilon/mesh/triang.py @@ -15,7 +15,7 @@ if __hasvtk__: from vtk import vtkIdList - + if __haspyvista__: import pyvista as pv diff --git a/src/sigmaepsilon/mesh/utils/cells/q8.py b/src/sigmaepsilon/mesh/utils/cells/q8.py index ea7d554..27e2c7d 100644 --- a/src/sigmaepsilon/mesh/utils/cells/q8.py +++ b/src/sigmaepsilon/mesh/utils/cells/q8.py @@ -15,10 +15,10 @@ def monoms_Q8(x: ndarray) -> ndarray: r, s, r * s, - r**2, - s**2, - r * s**2, - s * r**2, + r ** 2, + s ** 2, + r * s ** 2, + s * r ** 2, ], dtype=float, ) @@ -31,41 +31,41 @@ def shp_Q8(pcoord: np.ndarray) -> ndarray: res = np.array( [ [ - -0.25 * r**2 * s - + 0.25 * r**2 - - 0.25 * r * s**2 + -0.25 * r ** 2 * s + + 0.25 * r ** 2 + - 0.25 * r * s ** 2 + 0.25 * r * s - + 0.25 * s**2 + + 0.25 * s ** 2 - 0.25 ], [ - -0.25 * r**2 * s - + 0.25 * r**2 - + 0.25 * r * s**2 + -0.25 * r ** 2 * s + + 0.25 * r ** 2 + + 0.25 * r * s ** 2 - 0.25 * r * s - + 0.25 * s**2 + + 0.25 * s ** 2 - 0.25 ], [ - 0.25 * r**2 * s - + 0.25 * r**2 - + 0.25 * r * s**2 + 0.25 * r ** 2 * s + + 0.25 * r ** 2 + + 0.25 * r * s ** 2 + 0.25 * r * s - + 0.25 * s**2 + + 0.25 * s ** 2 - 0.25 ], [ - 0.25 * r**2 * s - + 0.25 * r**2 - - 0.25 * r * s**2 + 0.25 * r ** 2 * s + + 0.25 * r ** 2 + - 0.25 * r * s ** 2 - 0.25 * r * s - + 0.25 * s**2 + + 0.25 * s ** 2 - 0.25 ], - [0.5 * r**2 * s - 0.5 * r**2 - 0.5 * s + 0.5], - [-0.5 * r * s**2 + 0.5 * r - 0.5 * s**2 + 0.5], - [-0.5 * r**2 * s - 0.5 * r**2 + 0.5 * s + 0.5], - [0.5 * r * s**2 - 0.5 * r - 0.5 * s**2 + 0.5], + [0.5 * r ** 2 * s - 0.5 * r ** 2 - 0.5 * s + 0.5], + [-0.5 * r * s ** 2 + 0.5 * r - 0.5 * s ** 2 + 0.5], + [-0.5 * r ** 2 * s - 0.5 * r ** 2 + 0.5 * s + 0.5], + [0.5 * r * s ** 2 - 0.5 * r - 0.5 * s ** 2 + 0.5], ], dtype=pcoord.dtype, ) @@ -106,36 +106,36 @@ def dshp_Q8(pcoord: np.ndarray) -> ndarray: res = np.array( [ [ - -0.5*r*s + 0.5*r - 0.25*s**2 + 0.25*s, - -0.25*r**2 - 0.5*r*s + 0.25*r + 0.5*s, + -0.5 * r * s + 0.5 * r - 0.25 * s ** 2 + 0.25 * s, + -0.25 * r ** 2 - 0.5 * r * s + 0.25 * r + 0.5 * s, ], [ - -0.5*r*s + 0.5*r + 0.25*s**2 - 0.25*s, - -0.25*r**2 + 0.5*r*s - 0.25*r + 0.5*s, + -0.5 * r * s + 0.5 * r + 0.25 * s ** 2 - 0.25 * s, + -0.25 * r ** 2 + 0.5 * r * s - 0.25 * r + 0.5 * s, ], [ - 0.5*r*s + 0.5*r + 0.25*s**2 + 0.25*s, - 0.25*r**2 + 0.5*r*s + 0.25*r + 0.5*s, + 0.5 * r * s + 0.5 * r + 0.25 * s ** 2 + 0.25 * s, + 0.25 * r ** 2 + 0.5 * r * s + 0.25 * r + 0.5 * s, ], [ - 0.5*r*s + 0.5*r - 0.25*s**2 - 0.25*s, - 0.25*r**2 - 0.5*r*s - 0.25*r + 0.5*s, + 0.5 * r * s + 0.5 * r - 0.25 * s ** 2 - 0.25 * s, + 0.25 * r ** 2 - 0.5 * r * s - 0.25 * r + 0.5 * s, ], [ - 1.0*r*s - 1.0*r, - 0.5*r**2 - 0.5, + 1.0 * r * s - 1.0 * r, + 0.5 * r ** 2 - 0.5, ], [ - 0.5 - 0.5*s**2, - -1.0*r*s - 1.0*s, + 0.5 - 0.5 * s ** 2, + -1.0 * r * s - 1.0 * s, ], [ - -1.0*r*s - 1.0*r, - 0.5 - 0.5*r**2, + -1.0 * r * s - 1.0 * r, + 0.5 - 0.5 * r ** 2, ], [ - 0.5*s**2 - 0.5, - 1.0*r*s - 1.0*s, + 0.5 * s ** 2 - 0.5, + 1.0 * r * s - 1.0 * s, ], ], dtype=pcoord.dtype, diff --git a/src/sigmaepsilon/mesh/utils/cells/t6.py b/src/sigmaepsilon/mesh/utils/cells/t6.py index afc7bbd..003d1d6 100644 --- a/src/sigmaepsilon/mesh/utils/cells/t6.py +++ b/src/sigmaepsilon/mesh/utils/cells/t6.py @@ -8,7 +8,7 @@ @njit(nogil=True, cache=__cache) def monoms_T6(x: ndarray) -> ndarray: r, s = x - return np.array([1, r, s, r**2, s**2, r * s], dtype=float) + return np.array([1, r, s, r ** 2, s ** 2, r * s], dtype=float) @njit(nogil=True, cache=__cache) @@ -16,12 +16,12 @@ def shp_T6(pcoord: ndarray): r, s = pcoord[0:2] res = np.array( [ - 2.0 * r**2 + 4.0 * r * s - r / 3.0 + 2.0 * s**2 - s / 3.0 - 1 / 9, - 2.0 * r**2 + r / 3.0 - 1 / 9, - 2.0 * s**2 + s / 3.0 - 1 / 9, - -4.0 * r**2 - 4.0 * r * s - 4.0 * s / 3.0 + 4 / 9, + 2.0 * r ** 2 + 4.0 * r * s - r / 3.0 + 2.0 * s ** 2 - s / 3.0 - 1 / 9, + 2.0 * r ** 2 + r / 3.0 - 1 / 9, + 2.0 * s ** 2 + s / 3.0 - 1 / 9, + -4.0 * r ** 2 - 4.0 * r * s - 4.0 * s / 3.0 + 4 / 9, 4.0 * r * s + 4 * r / 3 + 4.0 * s / 3.0 + 4 / 9, - -4.0 * r * s - 4 * r / 3 - 4.0 * s**2 + 4 / 9, + -4.0 * r * s - 4 * r / 3 - 4.0 * s ** 2 + 4 / 9, ], dtype=pcoord.dtype, ) @@ -61,12 +61,12 @@ def dshp_T6(pcoord): r, s = pcoord[0:2] res = np.array( [ - [4.0*r + 4.0*s - 1/3, 4.0*r + 4.0*s - 1/3], - [4.0*r + 1/3, 0.0], - [0.0, 4.0*s + 1/3], - [-8.0*r - 4.0*s, -4.0*r - 4/3], - [4.0*s + 4/3, 4.0 * r + 4/3], - [-4.0 * s - 4/3, -4.0 * r - 8.0 * s], + [4.0 * r + 4.0 * s - 1 / 3, 4.0 * r + 4.0 * s - 1 / 3], + [4.0 * r + 1 / 3, 0.0], + [0.0, 4.0 * s + 1 / 3], + [-8.0 * r - 4.0 * s, -4.0 * r - 4 / 3], + [4.0 * s + 4 / 3, 4.0 * r + 4 / 3], + [-4.0 * s - 4 / 3, -4.0 * r - 8.0 * s], ] ) return res diff --git a/src/sigmaepsilon/mesh/utils/cells/tet4.py b/src/sigmaepsilon/mesh/utils/cells/tet4.py index 9a3e7ac..cfa9ccf 100644 --- a/src/sigmaepsilon/mesh/utils/cells/tet4.py +++ b/src/sigmaepsilon/mesh/utils/cells/tet4.py @@ -9,7 +9,7 @@ def monoms_TET4_single(x: ndarray) -> ndarray: r, s, t = x res = np.array( - [1, r, s, t, r * s, r * t, s * t, r**2, s**2, t**2], dtype=x.dtype + [1, r, s, t, r * s, r * t, s * t, r ** 2, s ** 2, t ** 2], dtype=x.dtype ) return res diff --git a/src/sigmaepsilon/mesh/utils/topology/__init__.py b/src/sigmaepsilon/mesh/utils/topology/__init__.py index 3b32233..2fbe4fb 100644 --- a/src/sigmaepsilon/mesh/utils/topology/__init__.py +++ b/src/sigmaepsilon/mesh/utils/topology/__init__.py @@ -1,3 +1,3 @@ from .topo import * from .tr import * -from .trimap import * \ No newline at end of file +from .trimap import * diff --git a/src/sigmaepsilon/mesh/utils/topology/tr.py b/src/sigmaepsilon/mesh/utils/topology/tr.py index ff2358b..06b86eb 100644 --- a/src/sigmaepsilon/mesh/utils/topology/tr.py +++ b/src/sigmaepsilon/mesh/utils/topology/tr.py @@ -217,16 +217,16 @@ def to_T3( ) -> Tuple[ndarray]: if path is None: raise TypeError("Expected Iterable for argument 'path', got 'NoneType'.") - + if not isinstance(path, ndarray): raise TypeError(f"Expected 'ndarray' for argument 'path', got {type(path)}.") - + if not len(path.shape) == 2: raise ValueError("'path' must be a 2d NumPy array") - + if not path.shape[1] == 3: raise ValueError("Invalid 'path'.") - + if data is None: return coords, +transform_topology(topo, path, *args, **kwargs) else: @@ -239,12 +239,12 @@ def Q8_to_T3( data: DataLike = None, *, path: ndarray = None, - **kwargs + **kwargs, ) -> Tuple[ndarray]: if path is None: path = trimap_Q8() elif isinstance(path, str): - raise NotImplementedError + raise NotImplementedError return to_T3(coords, topo, data, path=path, **kwargs) diff --git a/src/sigmaepsilon/mesh/utils/utils.py b/src/sigmaepsilon/mesh/utils/utils.py index e5b158e..3d84366 100644 --- a/src/sigmaepsilon/mesh/utils/utils.py +++ b/src/sigmaepsilon/mesh/utils/utils.py @@ -306,7 +306,7 @@ def cell_coords(coords: ndarray, topo: ndarray) -> ndarray: Returns ------- numpy.ndarray - 2d NumPy array of (nNE, nD) of coordinates for all nodes of all cells + 2d NumPy array of (nNE, nD) of coordinates for all nodes of all cells according to the argument 'topo'. Notes @@ -1070,4 +1070,4 @@ def xy_to_xyz(x: ndarray) -> ndarray: res[:, :2] = x elif N == 1: res[:, 0] = x - return res \ No newline at end of file + return res From 7d18006760f2d2746dc57e7188fd94e38d0c4ab7 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 10:26:04 +0100 Subject: [PATCH 24/47] changed implementation order --- src/sigmaepsilon/mesh/io/from_pyvista.py | 19 +- src/sigmaepsilon/mesh/io/to_k3d.py | 19 +- src/sigmaepsilon/mesh/io/to_pyvista.py | 19 +- src/sigmaepsilon/mesh/io/to_vtk.py | 19 +- src/sigmaepsilon/mesh/plotting/k3dplot.py | 19 +- .../mesh/plotting/mpl/parallel.py | 893 +++++++++--------- src/sigmaepsilon/mesh/plotting/mpl/triplot.py | 43 +- src/sigmaepsilon/mesh/plotting/mpl/utils.py | 397 ++++---- .../mesh/plotting/plotly/lines.py | 31 +- .../mesh/plotting/plotly/points.py | 19 +- src/sigmaepsilon/mesh/plotting/plotly/tri.py | 19 +- src/sigmaepsilon/mesh/plotting/pvplot.py | 19 +- 12 files changed, 777 insertions(+), 739 deletions(-) diff --git a/src/sigmaepsilon/mesh/io/from_pyvista.py b/src/sigmaepsilon/mesh/io/from_pyvista.py index 8abc3b7..5dee02c 100644 --- a/src/sigmaepsilon/mesh/io/from_pyvista.py +++ b/src/sigmaepsilon/mesh/io/from_pyvista.py @@ -1,7 +1,15 @@ from ..config import __haspyvista__ from ..helpers import importers -if __haspyvista__: +if not __haspyvista__: # pragma: no cover + + def from_pv(*_) -> None: + raise ImportError( + "You need PyVista for this. Install it with 'pip install pyvista'. " + "You may also need to restart your kernel and reload the package." + ) + +else: import pyvista as pv from typing import Union @@ -69,15 +77,6 @@ def from_pv(pvobj: pyVistaLike) -> PolyData: return polydata -else: # pragma: no cover - - def from_pv(*_) -> None: - raise ImportError( - "You need PyVista for this. Install it with 'pip install pyvista'. " - "You may also need to restart your kernel and reload the package." - ) - - importers["PyVista"] = from_pv __all__ = ["from_pv"] diff --git a/src/sigmaepsilon/mesh/io/to_k3d.py b/src/sigmaepsilon/mesh/io/to_k3d.py index 9ad27b5..04eb788 100644 --- a/src/sigmaepsilon/mesh/io/to_k3d.py +++ b/src/sigmaepsilon/mesh/io/to_k3d.py @@ -1,7 +1,15 @@ from ..config import __hask3d__, __hasmatplotlib__ from ..helpers import exporters -if __hask3d__ and __hasmatplotlib__: +if not (__hask3d__ and __hasmatplotlib__): # pragma: no cover + + def to_k3d(*_, **__): + raise ImportError( + "You need both K3D and Matplotlib for this. Install it with 'pip install k3d matplotlib'. " + "You may also need to restart your kernel and reload the package." + ) + +else: from copy import copy from typing import Union, Iterable, Optional import warnings @@ -138,15 +146,6 @@ def to_k3d( return scene -else: # pragma: no cover - - def to_k3d(*_, **__): - raise ImportError( - "You need both K3D and Matplotlib for this. Install it with 'pip install k3d matplotlib'. " - "You may also need to restart your kernel and reload the package." - ) - - exporters["k3d"] = to_k3d __all__ = ["to_k3d"] diff --git a/src/sigmaepsilon/mesh/io/to_pyvista.py b/src/sigmaepsilon/mesh/io/to_pyvista.py index 72c5b5f..c40e69e 100644 --- a/src/sigmaepsilon/mesh/io/to_pyvista.py +++ b/src/sigmaepsilon/mesh/io/to_pyvista.py @@ -1,7 +1,15 @@ from ..config import __haspyvista__ from ..helpers import exporters -if __haspyvista__: +if not __haspyvista__: # pragma: no cover + + def to_pv(*_) -> None: + raise ImportError( + "You need PyVista for this. Install it with 'pip install pyvista'. " + "You may also need to restart your kernel and reload the package." + ) + +else: from typing import Union, Optional from contextlib import suppress @@ -74,15 +82,6 @@ def to_pv( return res -else: # pragma: no cover - - def to_pv(*_) -> None: - raise ImportError( - "You need PyVista for this. Install it with 'pip install pyvista'. " - "You may also need to restart your kernel and reload the package." - ) - - exporters["PyVista"] = to_pv __all__ = ["to_pv"] diff --git a/src/sigmaepsilon/mesh/io/to_vtk.py b/src/sigmaepsilon/mesh/io/to_vtk.py index c86ba2c..73f7bd2 100644 --- a/src/sigmaepsilon/mesh/io/to_vtk.py +++ b/src/sigmaepsilon/mesh/io/to_vtk.py @@ -1,7 +1,15 @@ from ..config import __hasvtk__ from ..helpers import exporters -if __hasvtk__: +if not __hasvtk__: # pragma: no cover + + def to_vtk(*_) -> None: + raise ImportError( + "You need VTK for this. Install it with 'pip install vtk'. " + "You may also need to restart your kernel and reload the package." + ) + +else: import vtk from typing import Union @@ -47,15 +55,6 @@ def to_vtk( return ugrids[0] -else: # pragma: no cover - - def to_vtk(*_) -> None: - raise ImportError( - "You need VTK for this. Install it with 'pip install vtk'. " - "You may also need to restart your kernel and reload the package." - ) - - exporters["vtk"] = to_vtk __all__ = ["to_vtk"] diff --git a/src/sigmaepsilon/mesh/plotting/k3dplot.py b/src/sigmaepsilon/mesh/plotting/k3dplot.py index afcb9f0..2a43d20 100644 --- a/src/sigmaepsilon/mesh/plotting/k3dplot.py +++ b/src/sigmaepsilon/mesh/plotting/k3dplot.py @@ -1,7 +1,15 @@ from ..config import __hask3d__ from ..helpers import plotters -if __hask3d__: +if not __hask3d__: # pragma: no cover + + def k3dplot(*_, **__) -> None: + raise ImportError( + "You need K3D for this. Install it with 'pip install k3d'. " + "You may also need to restart your kernel and reload the package." + ) + +else: from typing import Union, Optional import k3d @@ -65,15 +73,6 @@ def k3dplot( return obj.to_k3d(scene=scene, **kwargs) -else: # pragma: no cover - - def k3dplot(*_, **__) -> None: - raise ImportError( - "You need K3D for this. Install it with 'pip install k3d'. " - "You may also need to restart your kernel and reload the package." - ) - - plotters["k3d"] = k3dplot __all__ = ["k3dplot"] diff --git a/src/sigmaepsilon/mesh/plotting/mpl/parallel.py b/src/sigmaepsilon/mesh/plotting/mpl/parallel.py index d71776d..91bfc8e 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/parallel.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/parallel.py @@ -1,451 +1,478 @@ # -*- coding: utf-8 -*- -from typing import Iterable, Hashable, Union, Optional - -import matplotlib as mpl -import matplotlib.pyplot as plt -from matplotlib.path import Path -from matplotlib.patches import PathPatch -import matplotlib.gridspec as gridspec -from matplotlib.widgets import Slider -from matplotlib.figure import Figure -import numpy as np -from numpy import ndarray - -from sigmaepsilon.deepdict import DeepDict -from sigmaepsilon.core.formatting import float_to_str_sig as str_sig -from sigmaepsilon.math import atleast1d +from ...config import __hasmatplotlib__ -__all__ = ["parallel_mpl", "aligned_parallel_mpl"] +if not __hasmatplotlib__: # pragma: no cover + def parallel_mpl(*_, **__): + raise ImportError( + "You need Matplotlib for this. Install it with 'pip install matplotlib'. " + "You may also need to restart your kernel and reload the package." + ) -def parallel_mpl( - data: Union[dict, Iterable[ndarray], ndarray], - *, - labels: Optional[Union[Iterable[str], None]] = None, - padding: Optional[float] = 0.05, - colors: Optional[Union[Iterable[str], None]] = None, - lw: Optional[float] = 0.2, - bezier: Optional[bool] = True, - figsize: Optional[Union[tuple, None]] = None, - title: Optional[Union[str, None]] = None, - ranges: Optional[Union[Iterable[float], None]] = None, - return_figure: Optional[bool] = True, - **_, -) -> Union[Figure, None]: - """ - Parameters - ---------- - data: Union[Iterable[numpy.ndarray], dict, numpy.ndarray] - A list of numpy.ndarray for each column. Each array is 1d with a length of N, - where N is the number of data records (the number of lines). - labels: Iterable, Optional - Labels for the columns. If provided, it must have the same length as `data`. - padding: float, Optional - Controls the padding around the axes. - colors: list of float, Optional - A value for each record. Default is None. - lw: float, Optional - Linewidth. - bezier: bool, Optional - If True, bezier curves are created instead of a linear polinomials. - Default is True. - figsize: tuple, Optional - A tuple to control the size of the figure. Default is None. - title: str, Optional - The title of the figure. - ranges: list of list, Optional - Ranges of the axes. If not provided, it is inferred from - the input values, but this may result in an error. - Default is False. - return_figure: bool, Optional - If True, the figure is returned. Default is False. - - Example - ------- - .. plot:: - :include-source: True - - from sigmaepsilon.mesh.plotting import parallel_mpl - import numpy as np - colors = np.random.rand(150, 3) - labels = [str(i) for i in range(10)] - values = [np.random.rand(150) for i in range(10)] - parallel_mpl( - values, - labels=labels, - padding=0.05, - lw=0.2, - colors=colors, - title="Parallel Plot with Random Data", + def aligned_parallel_mpl(*_, **__): + raise ImportError( + "You need Matplotlib for this. Install it with 'pip install matplotlib'. " + "You may also need to restart your kernel and reload the package." ) - """ - - if isinstance(data, dict): - if labels is None: - labels = list(data.keys()) - ys = np.dstack(list(data.values()))[0] - elif isinstance(data, np.ndarray): - assert labels is not None - ys = data.T - elif isinstance(data, Iterable): - assert labels is not None - ys = np.dstack(data)[0] - else: - raise TypeError("Invalid data type!") - - ynames = labels - N, nY = ys.shape - - figsize = (7.5, 3) if figsize is None else figsize - fig, host = plt.subplots(figsize=figsize) - - if ranges is None: - ymins = ys.min(axis=0) - ymaxs = ys.max(axis=0) - ranges = np.stack((ymins, ymaxs), axis=1) - else: - ranges = np.array(ranges) - - # make sure that upper and lower ranges are not equal - for i in range(nY): - rmin, rmax = ranges[i] - if abs(rmin - rmax) < 1e-12: - rmin -= 1.0 - rmax += 1.0 - ranges[i] = [rmin, rmax] - ymins = ranges[:, 0] - ymaxs = ranges[:, 1] - - dys = ymaxs - ymins - ymins -= dys * padding - ymaxs += dys * padding - dys = ymaxs - ymins - - # transform all data to be compatible with the main axis - zs = np.zeros_like(ys) - zs[:, 0] = ys[:, 0] - zs[:, 1:] = (ys[:, 1:] - ymins[1:]) / dys[1:] * dys[0] + ymins[0] - - axes = [host] + [host.twinx() for i in range(nY - 1)] - for i, ax in enumerate(axes): - ax.set_ylim(ymins[i], ymaxs[i]) - ax.spines["top"].set_visible(False) - ax.spines["bottom"].set_visible(False) - if ax != host: - ax.spines["left"].set_visible(False) - ax.yaxis.set_ticks_position("right") - ax.spines["right"].set_position(("axes", i / (nY - 1))) - - host.set_xlim(0, nY - 1) - host.set_xticks(range(nY)) - host.set_xticklabels(ynames, fontsize=8) - host.tick_params(axis="x", which="major", pad=7) - host.spines["right"].set_visible(False) - host.xaxis.tick_top() - - if title is not None: - host.set_title(title, fontsize=12) - - for j in range(N): - if not bezier: - # to just draw straight lines between the axes: - host.plot(range(nY), zs[j, :], c=colors[j], lw=lw) + +else: + from typing import Iterable, Hashable, Union, Optional + + import matplotlib as mpl + import matplotlib.pyplot as plt + from matplotlib.path import Path + from matplotlib.patches import PathPatch + import matplotlib.gridspec as gridspec + from matplotlib.widgets import Slider + from matplotlib.figure import Figure + import numpy as np + from numpy import ndarray + + from sigmaepsilon.deepdict import DeepDict + from sigmaepsilon.core.formatting import float_to_str_sig as str_sig + from sigmaepsilon.math import atleast1d + + def parallel_mpl( + data: Union[dict, Iterable[ndarray], ndarray], + *, + labels: Optional[Union[Iterable[str], None]] = None, + padding: Optional[float] = 0.05, + colors: Optional[Union[Iterable[str], None]] = None, + lw: Optional[float] = 0.2, + bezier: Optional[bool] = True, + figsize: Optional[Union[tuple, None]] = None, + title: Optional[Union[str, None]] = None, + ranges: Optional[Union[Iterable[float], None]] = None, + return_figure: Optional[bool] = True, + **_, + ) -> Union[Figure, None]: + """ + Parameters + ---------- + data: Union[Iterable[numpy.ndarray], dict, numpy.ndarray] + A list of numpy.ndarray for each column. Each array is 1d with a length of N, + where N is the number of data records (the number of lines). + labels: Iterable, Optional + Labels for the columns. If provided, it must have the same length as `data`. + padding: float, Optional + Controls the padding around the axes. + colors: list of float, Optional + A value for each record. Default is None. + lw: float, Optional + Linewidth. + bezier: bool, Optional + If True, bezier curves are created instead of a linear polinomials. + Default is True. + figsize: tuple, Optional + A tuple to control the size of the figure. Default is None. + title: str, Optional + The title of the figure. + ranges: list of list, Optional + Ranges of the axes. If not provided, it is inferred from + the input values, but this may result in an error. + Default is False. + return_figure: bool, Optional + If True, the figure is returned. Default is False. + + Example + ------- + .. plot:: + :include-source: True + + from sigmaepsilon.mesh.plotting import parallel_mpl + import numpy as np + colors = np.random.rand(150, 3) + labels = [str(i) for i in range(10)] + values = [np.random.rand(150) for i in range(10)] + parallel_mpl( + values, + labels=labels, + padding=0.05, + lw=0.2, + colors=colors, + title="Parallel Plot with Random Data", + ) + """ + + if isinstance(data, dict): + if labels is None: + labels = list(data.keys()) + ys = np.dstack(list(data.values()))[0] + elif isinstance(data, np.ndarray): + assert labels is not None + ys = data.T + elif isinstance(data, Iterable): + assert labels is not None + ys = np.dstack(data)[0] else: - # create bezier curves - # for each axis, there will a control vertex at the point itself, one at 1/3rd towards the previous and one - # at one third towards the next axis; the first and last axis have one less control vertex - # x-coordinate of the control vertices: at each integer (for the axes) and two inbetween - # y-coordinate: repeat every point three times, except the first and last only twice - verts = list( - zip( - [ - x - for x in np.linspace( - 0, len(ys) - 1, len(ys) * 3 - 2, endpoint=True - ) - ], - np.repeat(zs[j, :], 3)[1:-1], + raise TypeError("Invalid data type!") + + ynames = labels + N, nY = ys.shape + + figsize = (7.5, 3) if figsize is None else figsize + fig, host = plt.subplots(figsize=figsize) + + if ranges is None: + ymins = ys.min(axis=0) + ymaxs = ys.max(axis=0) + ranges = np.stack((ymins, ymaxs), axis=1) + else: + ranges = np.array(ranges) + + # make sure that upper and lower ranges are not equal + for i in range(nY): + rmin, rmax = ranges[i] + if abs(rmin - rmax) < 1e-12: + rmin -= 1.0 + rmax += 1.0 + ranges[i] = [rmin, rmax] + ymins = ranges[:, 0] + ymaxs = ranges[:, 1] + + dys = ymaxs - ymins + ymins -= dys * padding + ymaxs += dys * padding + dys = ymaxs - ymins + + # transform all data to be compatible with the main axis + zs = np.zeros_like(ys) + zs[:, 0] = ys[:, 0] + zs[:, 1:] = (ys[:, 1:] - ymins[1:]) / dys[1:] * dys[0] + ymins[0] + + axes = [host] + [host.twinx() for i in range(nY - 1)] + for i, ax in enumerate(axes): + ax.set_ylim(ymins[i], ymaxs[i]) + ax.spines["top"].set_visible(False) + ax.spines["bottom"].set_visible(False) + if ax != host: + ax.spines["left"].set_visible(False) + ax.yaxis.set_ticks_position("right") + ax.spines["right"].set_position(("axes", i / (nY - 1))) + + host.set_xlim(0, nY - 1) + host.set_xticks(range(nY)) + host.set_xticklabels(ynames, fontsize=8) + host.tick_params(axis="x", which="major", pad=7) + host.spines["right"].set_visible(False) + host.xaxis.tick_top() + + if title is not None: + host.set_title(title, fontsize=12) + + for j in range(N): + if not bezier: + # to just draw straight lines between the axes: + host.plot(range(nY), zs[j, :], c=colors[j], lw=lw) + else: + # create bezier curves + # for each axis, there will a control vertex at the point itself, one at 1/3rd towards the previous and one + # at one third towards the next axis; the first and last axis have one less control vertex + # x-coordinate of the control vertices: at each integer (for the axes) and two inbetween + # y-coordinate: repeat every point three times, except the first and last only twice + verts = list( + zip( + [ + x + for x in np.linspace( + 0, len(ys) - 1, len(ys) * 3 - 2, endpoint=True + ) + ], + np.repeat(zs[j, :], 3)[1:-1], + ) ) - ) - # for x,y in verts: host.plot(x, y, 'go') # to show the control points of the beziers - codes = [Path.MOVETO] + [Path.CURVE4 for _ in range(len(verts) - 1)] - path = Path(verts, codes) - patch = PathPatch(path, facecolor="none", lw=lw, edgecolor=colors[j]) - host.add_patch(patch) - - if return_figure: - return fig - - -def aligned_parallel_mpl( - data: Union[ndarray, dict], - datapos: Iterable[float], - *, - yticks=None, - labels=None, - sharelimits=False, - texlabels=None, - xticksrotation=0, - suptitle=None, - slider=False, - slider_label=None, - hlines=None, - vlines=None, - y0=None, - xoffset=0.0, - yoffset=0.0, - return_figure: bool = True, - **kwargs, -) -> Union[Figure, None]: - """ - Parameters - ---------- - data: numpy.ndarray or dict - The values to plot. If it is a NumPy array, labels must be provided - with the argument `labels`, if it is a sictionary, the keys of the - dictionary are used as labels. - datapos: Iterable[float] - Positions of the provided data values. - yticks: Iterable[float], Optional - Positions of ticks on the vertical axes. Default is None. - labels: Iterable, Optional - An iterable of strings specifying labels for the datasets. - Default is None. - sharelimits: bool, Optional - If True, the axes share limits of the vertical axes. - Default is False. - texlabels: Itrable[str], Optional - TeX-formatted labels. If provided, it must have the same length as - `labels`. Default is None. - xticksrotation: int, Optional - Rotation of the ticks along the vertical axes. Expected in degrees. - Default is 0. - suptitle: str, Optional - See Matplotlib's docs for the details. Default is None. - slider: bool, Optional - If True, a slider is added to the figure for interactive plots. - Default is False. - slider_label: str, Optional - A label for the slider. Only if `slider` is true. Default is None. - hlines: Iterable[float], Optional - A list of data values where horizontal lines are to be added to the axes. - Default is None. - vlines[float]: Iterable, Optional - A list of data values where vertical lines are to be added to the axes. - Default is None. - y0: float or int, Optional - Value for the vertical axis. Default is the average of the limits - of the vertical axis (0.5*(datapos[0] + datapos[-1])). - xoffset: float, Optional - Margin of the plot in the vertical direction. Default is 0. - yoffset: float, Optional - Margin of the plot in the horizontal direction. Default is 0. - **kwargs: dict, Optional - Extra keyword arguments are forwarded to the creator of the matplotlib figure. - Default is None. - - Example - ------- - .. plot:: - :include-source: True - - from sigmaepsilon.mesh.plotting.mpl.parallel import aligned_parallel_mpl - import numpy as np - labels = ['a', 'b', 'c'] - values = np.array([np.random.rand(150) for _ in labels]).T - datapos = np.linspace(-1, 1, 150) - aligned_parallel_mpl(values, datapos, labels=labels, yticks=[-1, 1], y0=0.0) - """ - # init - fig = plt.figure(**kwargs) - suptitle = "" if suptitle is None else suptitle - fig.suptitle(suptitle) - plotdata = DeepDict() - axcolor = "lightgoldenrodyellow" - ymin, ymax = np.min(datapos), np.max(datapos) - - hlines = [] if hlines is None else hlines - vlines = [] if vlines is None else vlines - - # init slider - if slider: - if slider_label is None: - slider_label = "" - - if y0 is None: - y0 = 0.5 * (ymin + ymax) - - # read data - if isinstance(data, dict): - if labels is None: - labels = list(data.keys()) - elif isinstance(data, np.ndarray): - nData = data.shape[1] - if labels is None: - labels = list(map(str, range(nData))) - data = {labels[i]: data[:, i] for i in range(nData)} - - for lbl in labels: - plotdata[lbl]["values"] = data[lbl] - - # set min and max values - _min, _max = [], [] - for lbl in labels: - plotdata[lbl]["min"] = np.min(data[lbl]) - plotdata[lbl]["max"] = np.max(data[lbl]) - _min.append(plotdata[lbl]["min"]) - _max.append(plotdata[lbl]["max"]) - - # set global min and max - vmin = np.min(_min) - vmax = np.max(_max) - del _min - del _max - - # setting up figure layout - nData = len(labels) - if slider: - nAxes = nData + 1 # +1 for the Slider - else: - nAxes = nData - - width_ratios = [1 for i in range(nData)] - - if slider: - width_ratios.append(0.15) - - spec = gridspec.GridSpec( - ncols=nAxes, - nrows=1, - width_ratios=width_ratios, - figure=fig, - wspace=0.2, - left=0.1, - ) - - # create axes - for i in range(nData): - plotid = int("{}{}{}".format(1, nAxes, i + 1)) - plotid = spec[0, i] - ax = fig.add_subplot(plotid, facecolor=axcolor) - ax.grid(False) - ax.patch.set_edgecolor("black") - ax.patch.set_linewidth(0.5) - - if i == 0: - if yticks is not None: - ax.set_yticks(yticks) - ax.set_yticklabels([str_sig(val, sig=3) for val in yticks]) + # for x,y in verts: host.plot(x, y, 'go') # to show the control points of the beziers + codes = [Path.MOVETO] + [Path.CURVE4 for _ in range(len(verts) - 1)] + path = Path(verts, codes) + patch = PathPatch(path, facecolor="none", lw=lw, edgecolor=colors[j]) + host.add_patch(patch) + + if return_figure: + return fig + + def aligned_parallel_mpl( + data: Union[ndarray, dict], + datapos: Iterable[float], + *, + yticks=None, + labels=None, + sharelimits=False, + texlabels=None, + xticksrotation=0, + suptitle=None, + slider=False, + slider_label=None, + hlines=None, + vlines=None, + y0=None, + xoffset=0.0, + yoffset=0.0, + return_figure: bool = True, + **kwargs, + ) -> Union[Figure, None]: + """ + Parameters + ---------- + data: numpy.ndarray or dict + The values to plot. If it is a NumPy array, labels must be provided + with the argument `labels`, if it is a sictionary, the keys of the + dictionary are used as labels. + datapos: Iterable[float] + Positions of the provided data values. + yticks: Iterable[float], Optional + Positions of ticks on the vertical axes. Default is None. + labels: Iterable, Optional + An iterable of strings specifying labels for the datasets. + Default is None. + sharelimits: bool, Optional + If True, the axes share limits of the vertical axes. + Default is False. + texlabels: Itrable[str], Optional + TeX-formatted labels. If provided, it must have the same length as + `labels`. Default is None. + xticksrotation: int, Optional + Rotation of the ticks along the vertical axes. Expected in degrees. + Default is 0. + suptitle: str, Optional + See Matplotlib's docs for the details. Default is None. + slider: bool, Optional + If True, a slider is added to the figure for interactive plots. + Default is False. + slider_label: str, Optional + A label for the slider. Only if `slider` is true. Default is None. + hlines: Iterable[float], Optional + A list of data values where horizontal lines are to be added to the axes. + Default is None. + vlines[float]: Iterable, Optional + A list of data values where vertical lines are to be added to the axes. + Default is None. + y0: float or int, Optional + Value for the vertical axis. Default is the average of the limits + of the vertical axis (0.5*(datapos[0] + datapos[-1])). + xoffset: float, Optional + Margin of the plot in the vertical direction. Default is 0. + yoffset: float, Optional + Margin of the plot in the horizontal direction. Default is 0. + **kwargs: dict, Optional + Extra keyword arguments are forwarded to the creator of the matplotlib figure. + Default is None. + + Example + ------- + .. plot:: + :include-source: True + + from sigmaepsilon.mesh.plotting.mpl.parallel import aligned_parallel_mpl + import numpy as np + labels = ['a', 'b', 'c'] + values = np.array([np.random.rand(150) for _ in labels]).T + datapos = np.linspace(-1, 1, 150) + aligned_parallel_mpl(values, datapos, labels=labels, yticks=[-1, 1], y0=0.0) + """ + # init + fig = plt.figure(**kwargs) + suptitle = "" if suptitle is None else suptitle + fig.suptitle(suptitle) + plotdata = DeepDict() + axcolor = "lightgoldenrodyellow" + ymin, ymax = np.min(datapos), np.max(datapos) + + hlines = [] if hlines is None else hlines + vlines = [] if vlines is None else vlines + + # init slider + if slider: + if slider_label is None: + slider_label = "" + + if y0 is None: + y0 = 0.5 * (ymin + ymax) + + # read data + if isinstance(data, dict): + if labels is None: + labels = list(data.keys()) + elif isinstance(data, np.ndarray): + nData = data.shape[1] + if labels is None: + labels = list(map(str, range(nData))) + data = {labels[i]: data[:, i] for i in range(nData)} + + for lbl in labels: + plotdata[lbl]["values"] = data[lbl] + + # set min and max values + _min, _max = [], [] + for lbl in labels: + plotdata[lbl]["min"] = np.min(data[lbl]) + plotdata[lbl]["max"] = np.max(data[lbl]) + _min.append(plotdata[lbl]["min"]) + _max.append(plotdata[lbl]["max"]) + + # set global min and max + vmin = np.min(_min) + vmax = np.max(_max) + del _min + del _max + + # setting up figure layout + nData = len(labels) + if slider: + nAxes = nData + 1 # +1 for the Slider else: - ax.set_yticks([]) - ax.set_yticklabels([]) + nAxes = nData - if y0 is not None: - hline = ax.axhline(y=y0, color="#d62728", linewidth=1) + width_ratios = [1 for i in range(nData)] - bbox = dict(boxstyle="round", ec="black", fc="yellow", alpha=0.8) - txt = ax.text( - 0.0, 0.0, "NaN", size=10, ha="center", va="center", visible=False, bbox=bbox - ) + if slider: + width_ratios.append(0.15) - # horizontal lines - ax.axhline(y=yticks[0], color="black", linewidth=0.5, linestyle="-") - ax.axhline(y=yticks[-1], color="black", linewidth=0.5, linestyle="-") - for hl in hlines: - ax.axhline(y=hl, color="black", linewidth=0.5, linestyle="-") - - # a vertical lines - for vl in vlines: - ax.axvline(x=vl, color="black", linewidth=0.5, linestyle="-") - - # store objects - plotdata[labels[i]]["ax"] = ax - plotdata[labels[i]]["text"] = txt - if y0: - plotdata[labels[i]]["hline"] = hline - - # create slider - if slider: - sliderax = fig.add_subplot(spec[0, nAxes - 1], fc=axcolor) - slider_ = Slider( - sliderax, - slider_label, - valmin=ymin, - valmax=ymax, - valinit=0.0, - orientation="vertical", - valfmt="%.3f", - closedmin=True, - closedmax=True, + spec = gridspec.GridSpec( + ncols=nAxes, + nrows=1, + width_ratios=width_ratios, + figure=fig, + wspace=0.2, + left=0.1, ) - def _approx_at_y(y: float, plotkey: Hashable): - lines2D = plotdata[plotkey]["lines"] - values = lines2D.get_xdata() - locations = lines2D.get_ydata() - return np.interp(y, locations, values) - - def _set_yval(y): - for axkey in plotdata.keys(): - if "hline" in plotdata[axkey]: - plotdata[axkey]["hline"].set_ydata(atleast1d(y)) - - v_at_y = _approx_at_y(y, axkey) - txtparams = { - "visible": True, - "x": v_at_y, - "y": y, - "text": str_sig(v_at_y, sig=4), - } - plotdata[axkey]["text"].update(txtparams) - fig.canvas.draw_idle() - - def _update_slider(y=None): - if y is None: - y = slider.val - _set_yval(y) - - def _set_xlim(axs: mpl.axes, vmin: float, vmax: float): - voffset = (vmax - vmin) * xoffset - if abs(vmin - vmax) > 1e-7: - axs.set_xlim(vmin - voffset, vmax + voffset) - xticks = [vmin, vmax] - axs.set_xticks(xticks) - rotation = kwargs.get("rotation", xticksrotation) - axs.set_xticklabels([str_sig(val, sig=3) for val in xticks], rotation=rotation) - - def _set_ylim(axs: mpl.axes, vmin: float, vmax: float): - voffset = (vmax - vmin) * yoffset - axs.set_ylim(vmin - voffset, vmax + voffset) - - # plot axes - for i, axkey in enumerate(plotdata.keys()): - axis = plotdata[axkey]["ax"] - - # set limits - if sharelimits == True: - _set_xlim(axis, vmin, vmax) - else: - _set_xlim(axis, plotdata[axkey]["min"], plotdata[axkey]["max"]) - _set_ylim(axis, ymin, ymax) + # create axes + for i in range(nData): + plotid = int("{}{}{}".format(1, nAxes, i + 1)) + plotid = spec[0, i] + ax = fig.add_subplot(plotid, facecolor=axcolor) + ax.grid(False) + ax.patch.set_edgecolor("black") + ax.patch.set_linewidth(0.5) + + if i == 0: + if yticks is not None: + ax.set_yticks(yticks) + ax.set_yticklabels([str_sig(val, sig=3) for val in yticks]) + else: + ax.set_yticks([]) + ax.set_yticklabels([]) + + if y0 is not None: + hline = ax.axhline(y=y0, color="#d62728", linewidth=1) + + bbox = dict(boxstyle="round", ec="black", fc="yellow", alpha=0.8) + txt = ax.text( + 0.0, + 0.0, + "NaN", + size=10, + ha="center", + va="center", + visible=False, + bbox=bbox, + ) - # set labels - if texlabels is not None: - axis.set_title(texlabels[i]) - else: - axis.set_title(str(axkey)) + # horizontal lines + ax.axhline(y=yticks[0], color="black", linewidth=0.5, linestyle="-") + ax.axhline(y=yticks[-1], color="black", linewidth=0.5, linestyle="-") + for hl in hlines: + ax.axhline(y=hl, color="black", linewidth=0.5, linestyle="-") + + # a vertical lines + for vl in vlines: + ax.axvline(x=vl, color="black", linewidth=0.5, linestyle="-") + + # store objects + plotdata[labels[i]]["ax"] = ax + plotdata[labels[i]]["text"] = txt + if y0: + plotdata[labels[i]]["hline"] = hline + + # create slider + if slider: + sliderax = fig.add_subplot(spec[0, nAxes - 1], fc=axcolor) + slider_ = Slider( + sliderax, + slider_label, + valmin=ymin, + valmax=ymax, + valinit=0.0, + orientation="vertical", + valfmt="%.3f", + closedmin=True, + closedmax=True, + ) + + def _approx_at_y(y: float, plotkey: Hashable): + lines2D = plotdata[plotkey]["lines"] + values = lines2D.get_xdata() + locations = lines2D.get_ydata() + return np.interp(y, locations, values) + + def _set_yval(y): + for axkey in plotdata.keys(): + if "hline" in plotdata[axkey]: + plotdata[axkey]["hline"].set_ydata(atleast1d(y)) + + v_at_y = _approx_at_y(y, axkey) + txtparams = { + "visible": True, + "x": v_at_y, + "y": y, + "text": str_sig(v_at_y, sig=4), + } + plotdata[axkey]["text"].update(txtparams) + fig.canvas.draw_idle() + + def _update_slider(y=None): + if y is None: + y = slider.val + _set_yval(y) + + def _set_xlim(axs: mpl.axes, vmin: float, vmax: float): + voffset = (vmax - vmin) * xoffset + if abs(vmin - vmax) > 1e-7: + axs.set_xlim(vmin - voffset, vmax + voffset) + xticks = [vmin, vmax] + axs.set_xticks(xticks) + rotation = kwargs.get("rotation", xticksrotation) + axs.set_xticklabels( + [str_sig(val, sig=3) for val in xticks], rotation=rotation + ) - # plot - lines = axis.plot(plotdata[axkey]["values"], datapos, picker=5)[0] - plotdata[axkey]["lines"] = lines + def _set_ylim(axs: mpl.axes, vmin: float, vmax: float): + voffset = (vmax - vmin) * yoffset + axs.set_ylim(vmin - voffset, vmax + voffset) + + # plot axes + for i, axkey in enumerate(plotdata.keys()): + axis = plotdata[axkey]["ax"] + + # set limits + if sharelimits == True: + _set_xlim(axis, vmin, vmax) + else: + _set_xlim(axis, plotdata[axkey]["min"], plotdata[axkey]["max"]) + _set_ylim(axis, ymin, ymax) + + # set labels + if texlabels is not None: + axis.set_title(texlabels[i]) + else: + axis.set_title(str(axkey)) + + # plot + lines = axis.plot(plotdata[axkey]["values"], datapos, picker=5)[0] + plotdata[axkey]["lines"] = lines + + # connect events + if slider: + slider_.on_changed(_update_slider) + fig._slider = ( + slider_ # to keep reference, otherwise slider is not responsive + ) - # connect events - if slider: - slider_.on_changed(_update_slider) - fig._slider = slider_ # to keep reference, otherwise slider is not responsive + if y0 is not None: + _set_yval(y0) - if y0 is not None: - _set_yval(y0) + if return_figure: + return fig - if return_figure: - return fig + +__all__ = ["parallel_mpl", "aligned_parallel_mpl"] diff --git a/src/sigmaepsilon/mesh/plotting/mpl/triplot.py b/src/sigmaepsilon/mesh/plotting/mpl/triplot.py index b14a55b..7e196a1 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/triplot.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/triplot.py @@ -1,6 +1,26 @@ from ...config import __hasmatplotlib__ -if __hasmatplotlib__: +if not __hasmatplotlib__: # pragma: no cover + + def triplot_mpl_mesh(*_, **__): + raise ImportError( + "You need Matplotlib for this. Install it with 'pip install matplotlib'. " + "You may also need to restart your kernel and reload the package." + ) + + def triplot_mpl_hinton(*_, **__): + raise ImportError( + "You need Matplotlib for this. Install it with 'pip install matplotlib'. " + "You may also need to restart your kernel and reload the package." + ) + + def triplot_mpl_data(*_, **__): + raise ImportError( + "You need Matplotlib for this. Install it with 'pip install matplotlib'. " + "You may also need to restart your kernel and reload the package." + ) + +else: from typing import Any, Union, Optional, Iterable import numpy as np @@ -384,25 +404,4 @@ def triplot_mpl_data( return axobj -else: # pragma: no cover - - def triplot_mpl_mesh(*_, **__): - raise ImportError( - "You need Matplotlib for this. Install it with 'pip install matplotlib'. " - "You may also need to restart your kernel and reload the package." - ) - - def triplot_mpl_hinton(*_, **__): - raise ImportError( - "You need Matplotlib for this. Install it with 'pip install matplotlib'. " - "You may also need to restart your kernel and reload the package." - ) - - def triplot_mpl_data(*_, **__): - raise ImportError( - "You need Matplotlib for this. Install it with 'pip install matplotlib'. " - "You may also need to restart your kernel and reload the package." - ) - - __all__ = ["triplot_mpl_hinton", "triplot_mpl_mesh", "triplot_mpl_data"] diff --git a/src/sigmaepsilon/mesh/plotting/mpl/utils.py b/src/sigmaepsilon/mesh/plotting/mpl/utils.py index 7fe3206..19e12bd 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/utils.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/utils.py @@ -1,205 +1,226 @@ # -*- coding: utf-8 -*- -from typing import Iterable, Callable, Any -from functools import wraps +from ...config import __hasmatplotlib__ -import numpy as np -from numpy import ndarray -import matplotlib.pyplot as plt -from matplotlib.figure import Figure, Axes -from matplotlib.patches import Polygon -from matplotlib.collections import PatchCollection +if not __hasmatplotlib__: # pragma: no cover -from sigmaepsilon.mesh.triang import triangulate -from sigmaepsilon.core.typing import issequence - - -class TriPatchCollection(PatchCollection): - def __init__(self, cellcoords, *args, **kwargs): - pmap = map(lambda i: cellcoords[i], np.arange(len(cellcoords))) - - def fnc(points): - return Polygon(points, closed=True) - - patches = list(map(fnc, pmap)) - super().__init__(patches, *args, **kwargs) + def triplotter(*_, **__): + raise ImportError( + "You need Matplotlib for this. Install it with 'pip install matplotlib'. " + "You may also need to restart your kernel and reload the package." + ) + def get_fig_axes(*_, **__): + raise ImportError( + "You need Matplotlib for this. Install it with 'pip install matplotlib'. " + "You may also need to restart your kernel and reload the package." + ) -def triplotter(plotter: Callable) -> Callable: - @wraps(plotter) - def inner( - triobj: Any, - *args, - data: ndarray = None, - title: str = None, - label: str = None, - fig: Figure = None, - ax: Axes = None, - axes: Iterable[Axes] = None, - fig_kw: dict = None, - **kwargs, - ) -> Any: - fig, axes = get_fig_axes( - *args, data=data, ax=ax, axes=axes, fig=fig, fig_kw=fig_kw + def decorate_mpl_ax(*_, **__): + raise ImportError( + "You need Matplotlib for this. Install it with 'pip install matplotlib'. " + "You may also need to restart your kernel and reload the package." ) - if isinstance(triobj, tuple): - coords, topo = triobj - triobj = triangulate(points=coords[:, :2], triangles=topo)[-1] - coords, topo = None, None - - if data is not None: - if not len(data.shape) <= 2: - raise ValueError("Data must be a 1 or 2 dimensional array.") - - nD = 1 if len(data.shape) == 1 else data.shape[1] - - data = data.reshape((data.shape[0], nD)) - - if not issequence(title): - title = nD * (title,) - - if not issequence(label): - label = nD * (label,) - - axobj = [ - plotter( - triobj, - data[:, i], - fig=fig, - ax=ax, - title=title[i], - label=label[i], - **kwargs, - ) - for i, ax in enumerate(axes) - ] - if nD == 1: - data = data.reshape(data.shape[0]) +else: + from typing import Iterable, Callable, Any + from functools import wraps + + import numpy as np + from numpy import ndarray + import matplotlib.pyplot as plt + from matplotlib.figure import Figure, Axes + from matplotlib.patches import Polygon + from matplotlib.collections import PatchCollection + + from sigmaepsilon.mesh.triang import triangulate + from sigmaepsilon.core.typing import issequence + + class TriPatchCollection(PatchCollection): + def __init__(self, cellcoords, *args, **kwargs): + pmap = map(lambda i: cellcoords[i], np.arange(len(cellcoords))) + + def fnc(points): + return Polygon(points, closed=True) + + patches = list(map(fnc, pmap)) + super().__init__(patches, *args, **kwargs) + + def triplotter(plotter: Callable) -> Callable: + @wraps(plotter) + def inner( + triobj: Any, + *args, + data: ndarray = None, + title: str = None, + label: str = None, + fig: Figure = None, + ax: Axes = None, + axes: Iterable[Axes] = None, + fig_kw: dict = None, + **kwargs, + ) -> Any: + fig, axes = get_fig_axes( + *args, data=data, ax=ax, axes=axes, fig=fig, fig_kw=fig_kw + ) + + if isinstance(triobj, tuple): + coords, topo = triobj + triobj = triangulate(points=coords[:, :2], triangles=topo)[-1] + coords, topo = None, None + + if data is not None: + if not len(data.shape) <= 2: + raise ValueError("Data must be a 1 or 2 dimensional array.") + + nD = 1 if len(data.shape) == 1 else data.shape[1] + + data = data.reshape((data.shape[0], nD)) + + if not issequence(title): + title = nD * (title,) + + if not issequence(label): + label = nD * (label,) + + axobj = [ + plotter( + triobj, + data[:, i], + fig=fig, + ax=ax, + title=title[i], + label=label[i], + **kwargs, + ) + for i, ax in enumerate(axes) + ] + if nD == 1: + data = data.reshape(data.shape[0]) + else: + axobj = plotter(triobj, ax=axes[0], fig=fig, title=title, **kwargs) + + return axobj + + return inner + + def get_fig_axes( + *args, + data=None, + fig=None, + axes=None, + shape=None, + horizontal=False, + ax=None, + fig_kw=None, + ) -> tuple: + """ + Returns a figure and an axes object. + """ + if isinstance(ax, (tuple, list)): + axes = ax + + if fig is not None: + if axes is not None: + return fig, axes + elif ax is not None: + return fig, (ax,) else: - axobj = plotter(triobj, ax=axes[0], fig=fig, title=title, **kwargs) - - return axobj - - return inner - - -def get_fig_axes( - *args, - data=None, - fig=None, - axes=None, - shape=None, - horizontal=False, - ax=None, - fig_kw=None, -) -> tuple: - """ - Returns a figure and an axes object. - """ - if isinstance(ax, (tuple, list)): - axes = ax - - if fig is not None: - if axes is not None: - return fig, axes - elif ax is not None: - return fig, (ax,) - else: - if fig_kw is None: - fig_kw = {} - - if data is not None: - nD = 1 if len(data.shape) == 1 else data.shape[1] - - if nD == 1: + if fig_kw is None: + fig_kw = {} + + if data is not None: + nD = 1 if len(data.shape) == 1 else data.shape[1] + + if nD == 1: + try: + ax = args[0] + except Exception: + fig, ax = plt.subplots(**fig_kw) + return fig, (ax,) + + if fig is None or axes is None: + if shape is not None: + if isinstance(shape, int): + shape = (shape, 1) if horizontal else (1, shape) + assert nD == ( + shape[0] * shape[1] + ), "Mismatch in shape and data." + else: + shape = (nD, 1) if horizontal else (1, nD) + + fig, axes = plt.subplots(*shape, **fig_kw) + + if not isinstance(axes, Iterable): + axes = (axes,) + + return fig, axes + else: try: ax = args[0] except Exception: fig, ax = plt.subplots(**fig_kw) return fig, (ax,) - if fig is None or axes is None: - if shape is not None: - if isinstance(shape, int): - shape = (shape, 1) if horizontal else (1, shape) - assert nD == (shape[0] * shape[1]), "Mismatch in shape and data." - else: - shape = (nD, 1) if horizontal else (1, nD) - - fig, axes = plt.subplots(*shape, **fig_kw) - - if not isinstance(axes, Iterable): - axes = (axes,) + return None, None + + def decorate_mpl_ax( + *, + fig=None, + ax=None, + aspect="equal", + xlim=None, + ylim=None, + axis="on", + offset=0.05, + points=None, + axfnc: Callable = None, + title=None, + suptitle=None, + label=None, + ): + """ + Decorates an axis using the most often used modifiers. + """ + assert ax is not None, ( + "A matplotlib Axes object must be provided with " "keyword argument 'ax'!" + ) - return fig, axes - else: + if axfnc is not None: try: - ax = args[0] + axfnc(ax) except Exception: - fig, ax = plt.subplots(**fig_kw) - return fig, (ax,) - - return None, None - - -def decorate_mpl_ax( - *, - fig=None, - ax=None, - aspect="equal", - xlim=None, - ylim=None, - axis="on", - offset=0.05, - points=None, - axfnc: Callable = None, - title=None, - suptitle=None, - label=None, -): - """ - Decorates an axis using the most often used modifiers. - """ - assert ax is not None, ( - "A matplotlib Axes object must be provided with " "keyword argument 'ax'!" - ) - - if axfnc is not None: - try: - axfnc(ax) - except Exception: - raise RuntimeError("Something went wrong when calling axfnc.") - - if xlim is None: - if points is not None: - xlim = points[:, 0].min(), points[:, 0].max() - if offset is not None: - dx = np.abs(xlim[1] - xlim[0]) - xlim = xlim[0] - offset * dx, xlim[1] + offset * dx - - if ylim is None: - if points is not None: - ylim = points[:, 1].min(), points[:, 1].max() - if offset is not None: - dx = np.abs(ylim[1] - ylim[0]) - ylim = ylim[0] - offset * dx, ylim[1] + offset * dx - - ax.set_aspect(aspect) - ax.axis(axis) - - if xlim is not None: - ax.set_xlim(*xlim) - - if ylim is not None: - ax.set_ylim(*ylim) - - if title is not None: - ax.set_title(title) - - if label is not None: - ax.set_xlabel(label) - - if fig is not None and suptitle is not None: - fig.suptitle(suptitle) - - return ax + raise RuntimeError("Something went wrong when calling axfnc.") + + if xlim is None: + if points is not None: + xlim = points[:, 0].min(), points[:, 0].max() + if offset is not None: + dx = np.abs(xlim[1] - xlim[0]) + xlim = xlim[0] - offset * dx, xlim[1] + offset * dx + + if ylim is None: + if points is not None: + ylim = points[:, 1].min(), points[:, 1].max() + if offset is not None: + dx = np.abs(ylim[1] - ylim[0]) + ylim = ylim[0] - offset * dx, ylim[1] + offset * dx + + ax.set_aspect(aspect) + ax.axis(axis) + + if xlim is not None: + ax.set_xlim(*xlim) + + if ylim is not None: + ax.set_ylim(*ylim) + + if title is not None: + ax.set_title(title) + + if label is not None: + ax.set_xlabel(label) + + if fig is not None and suptitle is not None: + fig.suptitle(suptitle) + + return ax diff --git a/src/sigmaepsilon/mesh/plotting/plotly/lines.py b/src/sigmaepsilon/mesh/plotting/plotly/lines.py index 6a59f14..1fb6889 100644 --- a/src/sigmaepsilon/mesh/plotting/plotly/lines.py +++ b/src/sigmaepsilon/mesh/plotting/plotly/lines.py @@ -1,6 +1,20 @@ from ...config import __hasplotly__ -if __hasplotly__: +if not __hasplotly__: # pragma: no cover + + def plot_lines_plotly(*_, **__): + raise ImportError( + "You need Plotly for this. Install it with 'pip install plotly'. " + "You may also need to restart your kernel and reload the package." + ) + + def scatter_lines_plotly(*_, **__): + raise ImportError( + "You need Plotly for this. Install it with 'pip install plotly'. " + "You may also need to restart your kernel and reload the package." + ) + +else: import plotly.graph_objects as go from numpy import ndarray @@ -118,19 +132,4 @@ def plot_lines_plotly( return fig -else: # pragma: no cover - - def plot_lines_plotly(*_, **__): - raise ImportError( - "You need Plotly for this. Install it with 'pip install plotly'. " - "You may also need to restart your kernel and reload the package." - ) - - def scatter_lines_plotly(*_, **__): - raise ImportError( - "You need Plotly for this. Install it with 'pip install plotly'. " - "You may also need to restart your kernel and reload the package." - ) - - __all__ = ["plot_lines_plotly", "scatter_lines_plotly"] diff --git a/src/sigmaepsilon/mesh/plotting/plotly/points.py b/src/sigmaepsilon/mesh/plotting/plotly/points.py index 31fe67d..8e0be79 100644 --- a/src/sigmaepsilon/mesh/plotting/plotly/points.py +++ b/src/sigmaepsilon/mesh/plotting/plotly/points.py @@ -1,6 +1,14 @@ from ...config import __hasplotly__ -if __hasplotly__: +if not __hasplotly__: # pragma: no cover + + def scatter_points_plotly(*_, **__): + raise ImportError( + "You need Plotly for this. Install it with 'pip install plotly'. " + "You may also need to restart your kernel and reload the package." + ) + +else: from typing import Iterable, Optional, Union from numbers import Number @@ -116,13 +124,4 @@ def scatter_points_plotly( return fig -else: # pragma: no cover - - def scatter_points_plotly(*_, **__): - raise ImportError( - "You need Plotly for this. Install it with 'pip install plotly'. " - "You may also need to restart your kernel and reload the package." - ) - - __all__ = ["scatter_points_plotly"] diff --git a/src/sigmaepsilon/mesh/plotting/plotly/tri.py b/src/sigmaepsilon/mesh/plotting/plotly/tri.py index 2c314f2..83765ee 100644 --- a/src/sigmaepsilon/mesh/plotting/plotly/tri.py +++ b/src/sigmaepsilon/mesh/plotting/plotly/tri.py @@ -1,6 +1,14 @@ from ...config import __hasplotly__ -if __hasplotly__: +if not __hasplotly__: # pragma: no cover + + def triplot_plotly(*_, **__): + raise ImportError( + "You need Plotly for this. Install it with 'pip install plotly'. " + "You may also need to restart your kernel and reload the package." + ) + +else: from typing import Optional, Union import plotly.graph_objects as go @@ -113,13 +121,4 @@ def triplot_plotly( return fig -else: # pragma: no cover - - def triplot_plotly(*_, **__): - raise ImportError( - "You need Plotly for this. Install it with 'pip install plotly'. " - "You may also need to restart your kernel and reload the package." - ) - - __all__ = ["triplot_plotly"] diff --git a/src/sigmaepsilon/mesh/plotting/pvplot.py b/src/sigmaepsilon/mesh/plotting/pvplot.py index fef7c6f..1f724eb 100644 --- a/src/sigmaepsilon/mesh/plotting/pvplot.py +++ b/src/sigmaepsilon/mesh/plotting/pvplot.py @@ -1,7 +1,15 @@ from ..config import __haspyvista__ from ..helpers import plotters -if __haspyvista__: +if not __haspyvista__: # pragma: no cover + + def pvplot(*_, **__) -> None: + raise ImportError( + "You need PyVista for this. Install it with 'pip install pyvista'. " + "You may also need to restart your kernel and reload the package." + ) + +else: from typing import Union, Iterable, Tuple from copy import copy @@ -206,15 +214,6 @@ def pvplot( return plotter.show(**show_params) -else: # pragma: no cover - - def pvplot(*_, **__) -> None: - raise ImportError( - "You need PyVista for this. Install it with 'pip install pyvista'. " - "You may also need to restart your kernel and reload the package." - ) - - plotters["PyVista"] = pvplot __all__ = ["pvplot"] From 89e94f84ee1585488b8de927e0c5eb8b0b732f9b Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 10:26:15 +0100 Subject: [PATCH 25/47] added triangle library --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fe95165..a05a090 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ sigmaepsilon.deepdict >=1.2.1, < 2.0.0 sigmaepsilon.math >= 1.0.1 fsspec >= 2023.1.0 # to use awkward.to_parquet sectionproperties >= 2.1.3 -meshio \ No newline at end of file +meshio +triangle \ No newline at end of file From d98000c2dfac9f690d0b3b10efa291acbbc30294 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 10:26:43 +0100 Subject: [PATCH 26/47] better type hints --- src/sigmaepsilon/mesh/utils/cells/t6.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sigmaepsilon/mesh/utils/cells/t6.py b/src/sigmaepsilon/mesh/utils/cells/t6.py index 003d1d6..41c19a7 100644 --- a/src/sigmaepsilon/mesh/utils/cells/t6.py +++ b/src/sigmaepsilon/mesh/utils/cells/t6.py @@ -8,11 +8,11 @@ @njit(nogil=True, cache=__cache) def monoms_T6(x: ndarray) -> ndarray: r, s = x - return np.array([1, r, s, r ** 2, s ** 2, r * s], dtype=float) + return np.array([1, r, s, r ** 2, s ** 2, r * s], dtype=x.dtype) @njit(nogil=True, cache=__cache) -def shp_T6(pcoord: ndarray): +def shp_T6(pcoord: ndarray) -> ndarray: r, s = pcoord[0:2] res = np.array( [ @@ -29,7 +29,7 @@ def shp_T6(pcoord: ndarray): @njit(nogil=True, parallel=True, cache=__cache) -def shp_T6_multi(pcoords: ndarray): +def shp_T6_multi(pcoords: ndarray) -> ndarray: nP = pcoords.shape[0] res = np.zeros((nP, 6), dtype=pcoords.dtype) for iP in prange(nP): @@ -38,7 +38,7 @@ def shp_T6_multi(pcoords: ndarray): @njit(nogil=True, parallel=False, cache=__cache) -def shape_function_matrix_T6(pcoord: ndarray, ndof: int = 2): +def shape_function_matrix_T6(pcoord: ndarray, ndof: int = 2) -> ndarray: eye = np.eye(ndof, dtype=pcoord.dtype) shp = shp_T6(pcoord) res = np.zeros((ndof, ndof * 6), dtype=pcoord.dtype) @@ -48,7 +48,7 @@ def shape_function_matrix_T6(pcoord: ndarray, ndof: int = 2): @njit(nogil=True, parallel=True, cache=__cache) -def shape_function_matrix_T6_multi(pcoords: np.ndarray, ndof: int = 2): +def shape_function_matrix_T6_multi(pcoords: ndarray, ndof: int = 2) -> ndarray: nP = pcoords.shape[0] res = np.zeros((nP, ndof, ndof * 6), dtype=pcoords.dtype) for iP in prange(nP): @@ -57,7 +57,7 @@ def shape_function_matrix_T6_multi(pcoords: np.ndarray, ndof: int = 2): @njit(nogil=True, cache=__cache) -def dshp_T6(pcoord): +def dshp_T6(pcoord: ndarray) -> ndarray: r, s = pcoord[0:2] res = np.array( [ @@ -73,7 +73,7 @@ def dshp_T6(pcoord): @njit(nogil=True, parallel=True, cache=__cache) -def dshp_T6_multi(pcoords: ndarray): +def dshp_T6_multi(pcoords: ndarray) -> ndarray: nP = pcoords.shape[0] res = np.zeros((nP, 6, 2), dtype=pcoords.dtype) for iP in prange(nP): @@ -82,7 +82,7 @@ def dshp_T6_multi(pcoords: ndarray): @njit(nogil=True, parallel=True, fastmath=True, cache=__cache) -def areas_T6(ecoords: ndarray, qpos: ndarray, qweight: ndarray): +def areas_T6(ecoords: ndarray, qpos: ndarray, qweight: ndarray) -> ndarray: nE = len(ecoords) res = np.zeros(nE, dtype=ecoords.dtype) nP = len(qweight) From bd4632d705b9fd16cb35938ec44fd23094a3867c Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 10:26:55 +0100 Subject: [PATCH 27/47] type hints and formatting --- src/sigmaepsilon/mesh/cells/t6.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sigmaepsilon/mesh/cells/t6.py b/src/sigmaepsilon/mesh/cells/t6.py index 4891172..f811845 100644 --- a/src/sigmaepsilon/mesh/cells/t6.py +++ b/src/sigmaepsilon/mesh/cells/t6.py @@ -14,7 +14,7 @@ shape_function_matrix_T6_multi, monoms_T6, ) -from ..utils.cells.numint import Gauss_Legendre_Tri_3a +from ..utils.cells.numint import Quadrature, Gauss_Legendre_Tri_3a from ..utils.topology import T6_to_T3, T3_to_T6 @@ -63,7 +63,7 @@ def polybase(cls) -> Tuple[List]: A list of monomials. """ locvars = r, s = symbols("r s", real=True) - monoms = [1, r, s, r ** 2, s ** 2, r * s] + monoms = [1, r, s, r**2, s**2, r * s] return locvars, monoms @classmethod @@ -121,8 +121,10 @@ def areas(self) -> ndarray: coords = self.source_coords() topo = self.topology().to_numpy() ecoords = cells_coords(coords[:, :2], topo) - qpos, qweight = self.Geometry.quadrature["full"] - return areas_T6(ecoords, qpos, qweight) + quad: Quadrature = next( + self._parse_gauss_data(self.Geometry.quadrature, "geometry") + ) + return areas_T6(ecoords, quad.pos, quad.weight) @classmethod def from_TriMesh(cls, *args, coords=None, topo=None, **kwargs): From 13e3fd6a82b62c54207de39576ec073d5c99651f Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 10:27:10 +0100 Subject: [PATCH 28/47] added type hint --- src/sigmaepsilon/mesh/data/polycell.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/sigmaepsilon/mesh/data/polycell.py b/src/sigmaepsilon/mesh/data/polycell.py index ce53258..2401b50 100644 --- a/src/sigmaepsilon/mesh/data/polycell.py +++ b/src/sigmaepsilon/mesh/data/polycell.py @@ -128,7 +128,7 @@ def _get_points_and_range( return points, rng @staticmethod - def _parse_gauss_data(quad_dict: dict, key: Hashable): + def _parse_gauss_data(quad_dict: dict, key: Hashable) -> Iterable[Quadrature]: value: Union[Callable, str, dict] = quad_dict[key] if isinstance(value, dict): @@ -177,21 +177,6 @@ def frames(self) -> ndarray: ) return super().frames - def split(self: T) -> Iterable[T]: - """ - Splits the block to a list of regular blocks. A regular block is one where - the topology can be described with a NumPy matrix, otherwise the topology is - jagged. In the latter case, a list of PolyCell instances are returned. - In the instance has a regular topology, the result is `[self]`. - """ - raise NotImplementedError - topo: TopologyArray = self.topology() - - if not topo.is_jagged(): - return [self] - - topologies = topo.split() - def to_triangles(self) -> ndarray: """ Returns the topology as a collection of T3 triangles, represented From 92fefb6217170798760db01675fe00b733118fd9 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 10:27:23 +0100 Subject: [PATCH 29/47] added new assertions --- tests/cells/test_tri.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/cells/test_tri.py b/tests/cells/test_tri.py index f602e78..9b6b9a2 100644 --- a/tests/cells/test_tri.py +++ b/tests/cells/test_tri.py @@ -28,6 +28,9 @@ def test_T3(self, N: int = 3): return_symbolic=True ) r, s = symbols("r, s", real=True) + + nNE = T3.Geometry.number_of_nodes + nD = T3.Geometry.number_of_spatial_dimensions for _ in range(N): A1, A2 = np.random.rand(2) @@ -54,16 +57,33 @@ def test_T3(self, N: int = 3): shpmfA = shpmf(x_loc) shpmfB = T3.Geometry.shape_function_matrix(x_loc) self.assertTrue(np.allclose(shpmfA, shpmfB)) + + mc = T3.Geometry.master_coordinates() + shp = T3.Geometry.shape_function_values(mc) + self.assertTrue(np.allclose(np.diag(shp), np.ones((nNE)))) nX = 2 shpmf = T3.Geometry.shape_function_matrix(x_loc, N=nX) self.assertEqual(shpmf.shape, (1, nX, 3 * nX)) + frame = CartesianFrame() + coords = np.zeros((nNE, 3), dtype=float) + coords[:, :nD] = T3.Geometry.master_coordinates() + topo = np.array([list(range(nNE))], dtype=int) + pd = PointData(coords=coords, frame=frame) + cd = T3(topo=topo, frames=frame) + _ = PolyData(pd, cd) + self.assertTrue(np.isclose(cd.area(), 0.5)) + self.assertTrue(np.allclose(cd.jacobian(), np.ones((1, nNE)))) + def test_T6(self, N: int = 3): shp, dshp, shpf, shpmf, dshpf = T6.Geometry.generate_class_functions( return_symbolic=True ) r, s = symbols("r, s", real=True) + + nNE = T6.Geometry.number_of_nodes + nD = T6.Geometry.number_of_spatial_dimensions for _ in range(N): A1, A2 = np.random.rand(2) @@ -91,10 +111,24 @@ def test_T6(self, N: int = 3): shpmfB = T6.Geometry.shape_function_matrix(x_loc) self.assertTrue(np.allclose(shpmfA, shpmfB)) + mc = T6.Geometry.master_coordinates() + shp = T6.Geometry.shape_function_values(mc) + self.assertTrue(np.allclose(np.diag(shp), np.ones((nNE)))) + nX = 2 shpmf = T6.Geometry.shape_function_matrix(x_loc, N=nX) self.assertEqual(shpmf.shape, (1, nX, 6 * nX)) - + + frame = CartesianFrame() + coords = np.zeros((nNE, 3), dtype=float) + coords[:, :nD] = T6.Geometry.master_coordinates() + topo = np.array([list(range(nNE))], dtype=int) + pd = PointData(coords=coords, frame=frame) + cd = T6(topo=topo, frames=frame) + _ = PolyData(pd, cd) + self.assertTrue(np.isclose(cd.area(), 0.5)) + self.assertTrue(np.allclose(cd.jacobian(), np.ones((1, nNE)))) + class TestTriutils(SigmaEpsilonTestCase): def test_triutils(self): From a3b2862c88bb7ff7893f1fba8db01684120c1a50 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 10:27:41 +0100 Subject: [PATCH 30/47] added new examples --- docs/source/examples/shape_functions_Q9.ipynb | 103 ++++++++++++++++++ docs/source/examples/shape_functions_T6.ipynb | 103 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 docs/source/examples/shape_functions_Q9.ipynb create mode 100644 docs/source/examples/shape_functions_T6.ipynb diff --git a/docs/source/examples/shape_functions_Q9.ipynb b/docs/source/examples/shape_functions_Q9.ipynb new file mode 100644 index 0000000..22ba6a4 --- /dev/null +++ b/docs/source/examples/shape_functions_Q9.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plotting the shape functions of the Q9 quadrilateral" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use Richard Shewchuk's two-dimensional quality mesh generator to triangulate the master cell of the Q9 quadrilateral, then we plot the shape functions with Matplotlib." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import triangle as tr\n", + "\n", + "from sigmaepsilon.mesh.cells import Q9\n", + "\n", + "# get the coordinates of the master cell\n", + "mc = Q9.Geometry.master_coordinates()\n", + "\n", + "# triangulate the master cell and plot with the `triangle` library\n", + "data = dict(vertices=mc)\n", + "triangulation = tr.triangulate(data, 'qa0.02')\n", + "tr.compare(plt, data, triangulation)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from sigmaepsilon.mesh.plotting import triplot_mpl_data\n", + "from sigmaepsilon.mesh import triangulate\n", + "\n", + "# get the triangulation as a `matplotlib` triangulation object\n", + "*_, triobj = triangulate(points=triangulation[\"vertices\"], triangles=triangulation[\"triangles\"])\n", + "\n", + "# evaluate shape functions at the vertices of the triangulation\n", + "values = Q9.Geometry.shape_function_values(triangulation[\"vertices\"])\n", + "\n", + "# plot the values\n", + "fig, ((ax1, ax2, ax3), (ax4, ax5, ax6), (ax7, ax8, ax9)) = plt.subplots(3, 3, figsize=(9, 6))\n", + "for i, ax in enumerate([ax1, ax2, ax3, ax4, ax5, ax6, ax7, ax8, ax9]):\n", + " _ = triplot_mpl_data(triobj, fig=fig, ax=ax, data=values[:, i], nlevels=10)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".mesh", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/source/examples/shape_functions_T6.ipynb b/docs/source/examples/shape_functions_T6.ipynb new file mode 100644 index 0000000..7e50971 --- /dev/null +++ b/docs/source/examples/shape_functions_T6.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plotting the shape functions of the T6 triangle" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use Richard Shewchuk's two-dimensional quality mesh generator to triangulate the master cell of the T6 triangle, then we plot the shape functions with Matplotlib." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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0yVtUgRiSggMaWuxfPnyVuUTevz4MS1dupR69eoltu4St7x585K7uzstWbJEzxfNx8fH7Lrd3NxkzZ8eYGHEMEyq8iUIo0/hhJwUuVajq1evihdexYoV6eXLl7Ln8PT0JAA0duxYo8efPXsmQsUBXS2w27dvW1UHTLJSTJgwwaCSvK2trV6OIFMWlVOnTtHixYtp1KhR1LJlS736ZMZarly5qEGDBuTh4UFz5syhgwcP0rNnz0ir1coStQBM+mZ9+PCBwsLC6NixY7R27VoKCgoiPz8/6t27NzVu3JjKly8vItDktv79+5t9bsa24kzlE3r37h1t27aNhg0bRmXLljWYy8HBgVq0aEEzZ86kf//916T/1tatWy2u+0vJkM3CiGGYVOVLEEZSiQdLLX/+/OTv70979+4162djCktWo8uXL1OePHkIAFWpUoVevXpl1fhSBNzcuXMNjq1bt45y5sxJgC6p42+//aYXuSZF0pUoUYIAUEBAgKw5r127Rr/88oteEsbktnz58lGjRo1o6NCh9Mcff1BoaKgsfx1L21uAznH93r178m9mEmJiYmT7M0mtdOnS1K9fP1q4cCFdv36dNBqNxbV6e3vT8ePH6eeff6Z69eoZFay1atUif39/Cg0NlWXF1Gq1IsDA19eXgoKCRM4nV1fXL658CAsjhmFSlfQsjM6dO0e9evVKlr+KUqmkmjVrko+PD23fvp3evHljcb7EVqPff/9dL+vz2bNnRfh5tWrVLNYGS4pKpRLbSt9//73Ymnn9+rUoBAvonJCvXLlichwpysvcdpwppO0hS83JyYmaNm1Kw4cPp/nz59Px48dN5oySS+vWrY0+owEDBggrVv78+Q3yDsklLCzMYBvRnMAz9rm1lieplSxZkjw8PGjTpk2yvmdJOXDgAAE665IkNKOiokSm8L///vuLEkcsjBiGSVXSmzCKj4+n9evX62VBltNsbGxo/vz51LdvX6N+LQqFgipXrkw//vgjbdy40eRWimQ1MtVq1Khh9cvPVKmJTp06CV8lpVKpl37AFGvXriVAV0fOWj53Id3ENGvWjABdss2kJUaePn0qMlE7OTlRaGio7HHVajVNnz5ddj4gycfozZs3tGvXLho7dix988035ODgIPu75ujoSN26daOFCxemSDh/o0aNCAD9+OOPep9LBXQHDx5MRF9O4VkW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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import triangle as tr\n", + "\n", + "from sigmaepsilon.mesh.cells import T6\n", + "\n", + "# get the coordinates of the master cell\n", + "mc = T6.Geometry.master_coordinates()\n", + "\n", + "# triangulate the master cell and plot with the `triangle` library\n", + "data = dict(vertices=mc)\n", + "triangulation = tr.triangulate(data, 'qa0.005')\n", + "tr.compare(plt, data, triangulation)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAH/CAYAAABKNb6SAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3xc1Z3//9e9d5p6cVGxZcm4G1zAgHHKpuDEBEIJkJiSUEJIQkj17ncJuxvIJt/vks1m89tsQpaEhEA2JEAAg0MxIQbScDDYGNwL7kVykdWlaff8/hhpJFkjaVRG0kjv5+MhJF3de+fMYH1033POPccyxhhEREREREREZNDZw90AERERERERkdFKoVtEREREREQkRRS6RURERERERFJEoVtEREREREQkRRS6RURERERERFJEoVtEREREREQkRRS6RURERERERFJEoVtEREREREQkRRS6RURERERERFJEoVtEREREREQkRRS6RWRY/elPf+LSSy+ltLQUy7J46qmnej3mlVde4ZxzzsHv9zN9+nQefPDBLvvce++9VFRUEAgEWLx4MevWrRv8xouIpJDqo4hI99KpRip0i8iwamxsZMGCBdx7771J7b93714uueQSPvCBD7Bx40a++tWv8pnPfIYXXnghvs+jjz7KihUruPvuu9mwYQMLFixg2bJlHDt2LFVPQ0Rk0Kk+ioh0L51qpGWMMQM6g4jIILEsi5UrV3LFFVd0u88dd9zBs88+y+bNm+PbrrnmGmpqali9ejUAixcv5rzzzuNHP/oRAK7rUlZWxpe+9CW+/vWvp/Q5iIikguqjiEj3RnqN9PT7yBHKdV2OHDlCTk4OlmUNd3NE0pIxhvr6ekpLS7Ht2ICYlpYWQqFQ0sef/vvn9/vx+/0DbtvatWtZunRpp23Lli3jq1/9KgChUIj169dz5513xn9u2zZLly5l7dq1A378dKb6KDJwieojJF8jVR9HLtVIkYEbyDVkKusjDG+NHHWh+8iRI5SVlQ13M0RGhYMHDzJ58mRaWloozcjgVJLHZWdn09DQ0Gnb3XffzTe/+c0Bt6myspKioqJO24qKiqirq6O5uZlTp04RjUYT7rN9+/YBP346U30UGTxt9RHoU41UfRy5VCNFBk9/riFTWR9heGvkqAvdOTk5QOx/dG5u7jC3RiQ91dXVUVZWFv99CoVC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/8bV/uGTzm0fKiO5Po66dbpFRERERERERgp7uBsgIiIiIiIiMlopdIuIiIiIiIikiEK3iIiIiIiISIoodIuIiIiIiIikiEJ3N6qrq7n++uvJzc0lPz+fW265hYaGhh73/9KXvsSsWbPIyMhgypQpfPnLX6a2trbTfpZldfl45JFHBtzee++9l4qKCgKBAIsXL2bdunU97v/b3/6W2bNnEwgEmDdvHs8991ynnxtjuOuuuygpKSEjI4OlS5eya9euAbdzMNp///338973vpeCggIKCgpYunRpl/1vuummLq/zRRddNOxtf/DBB7u0KxAIdNpnqF97kb5SfVR9TFX7VSMl3ak+qj6mqv2qj2luALOyj2oXXXSRWbBggfnb3/5m/vznP5vp06eba6+9ttv9N23aZK688kqzatUqs3v3brNmzRozY8YMc9VVV3XaDzC/+MUvzNGjR+Mfzc3NA2rrI488Ynw+n3nggQfMli1bzK233mry8/NNVVVVwv3/+te/GsdxzHe/+12zdetW8y//8i/G6/WaTZs2xff5zne+Y/Ly8sxTTz1l3nrrLXPZZZeZqVOnDritg9H+6667ztx7773mzTffNNu2bTM33XSTycvLM4cOHYrvc+ONN5qLLrqo0+tcXV097G3/xS9+YXJzczu1q7KystM+Q/nai/SH6qPqY6rarxop6U71UfUxVe1XfUxvCt0JbN261QDm9ddfj297/vnnjWVZ5vDhw0mf57HHHjM+n8+Ew+H4NsCsXLlyMJtrzj//fHP77bfHv49Go6a0tNTcc889Cff/xCc+YS655JJO2xYvXmw+97nPGWNia9wVFxeb//iP/4j/vKamxvj9fvOb3/xmUNven/afLhKJmJycHPPQQw/Ft914443m8ssvH+ymdtHXtv/iF78weXl53Z5vqF97kb5SfVR97AvVSBlLVB9VH/tC9XFs0fDyBNauXUt+fj7nnntufNvSpUuxbZvXXnst6fPU1taSm5uLx+PptP32229n/PjxnH/++TzwwAOYASyVHgqFWL9+PUuXLo1vs22bpUuXsnbt2oTHrF27ttP+AMuWLYvvv3fvXiorKzvtk5eXx+LFi7s951C2/3RNTU2Ew2EKCws7bX/llVeYOHEis2bN4rbbbuPkyZMjou0NDQ2Ul5dTVlbG5ZdfzpYtW+I/G8rXXqQ/VB9VH5OlGiljjeqj6mOyVB/HHoXuBCorK5k4cWKnbR6Ph8LCQiorK5M6x4kTJ/j2t7/NZz/72U7bv/Wtb/HYY4/x4osvctVVV/GFL3yBH/7wh/1u64kTJ4hGoxQVFXXaXlRU1G1bKysre9y/7XNfztlf/Wn/6e644w5KS0s7FZmLLrqIX/7yl6xZs4Z///d/549//CMf+chHiEajw9r2WbNm8cADD/D000/zq1/9Ctd1ede73sWhQ4eAoX3tRfpD9VH1MZXtV42UdKb6qPqYyvarPqY3T++7jB5f//rX+fd///ce99m2bduAH6euro5LLrmEuXPn8s1vfrPTz77xjW/Evz777LNpbGzkP/7jP/jyl7884Mcdi77zne/wyCOP8Morr3SaTOKaa66Jfz1v3jzmz5/PtGnTeOWVV7jwwguHo6kALFmyhCVLlsS/f9e73sWcOXP4yU9+wre//e1ha5eI6uPok271EVQjZWRSfRx9VB9lqI2pnu6///u/Z9u2bT1+nHHGGRQXF3Ps2LFOx0YiEaqrqykuLu7xMerr67nooovIyclh5cqVeL3eHvdfvHgxhw4dIhgM9us5jR8/HsdxqKqq6rS9qqqq27YWFxf3uH/b576cs7/60/423/ve9/jOd77D73//e+bPn9/jvmeccQbjx49n9+7dA25zm4G0vY3X6+Xss8+Ot2soX3uRjlQfY1QfB49qpIwWqo8xqo+DR/Vx7BlToXvChAnMnj27xw+fz8eSJUuoqalh/fr18WNfeuklXNdl8eLF3Z6/rq6OD3/4w/h8PlatWtVlGv9ENm7cSEFBAX6/v1/PyefzsWjRItasWRPf5roua9as6fRuWEdLlizptD/Aiy++GN9/6tSpFBcXd9qnrq6O1157rdtz9ld/2g/w3e9+l29/+9usXr26071T3Tl06BAnT56kpKRkUNoN/W97R9FolE2bNsXbNZSvvUhHqo8xqo+DRzVSRgvVxxjVx8Gj+jgGDfNEbiPWRRddZM4++2zz2muvmb/85S9mxowZnZZ8OHTokJk1a5Z57bXXjDHG1NbWmsWLF5t58+aZ3bt3d5rOPxKJGGOMWbVqlbn//vvNpk2bzK5du8yPf/xjk5mZae66664BtfWRRx4xfr/fPPjgg2br1q3ms5/9rMnPz48vI/CpT33KfP3rX4/v/9e//tV4PB7zve99z2zbts3cfffdCZd8yM/PN08//bR5++23zeWXX57SJR/60v7vfOc7xufzmccff7zT61xfX2+MMaa+vt78wz/8g1m7dq3Zu3ev+cMf/mDOOeccM2PGDNPS0jKsbf/Xf/1X88ILL5h33nnHrF+/3lxzzTUmEAiYLVu2dHp+Q/Xai/SH6qPqY6rarxop6U71UfUxVe1XfUxvCt3dOHnypLn22mtNdna2yc3NNTfffHP8l9IYY/bu3WsA8/LLLxtjjHn55ZcNkPBj7969xpjYshELFy402dnZJisryyxYsMDcd999JhqNDri9P/zhD82UKVOMz+cz559/vvnb3/4W/9n73vc+c+ONN3ba/7HHHjMzZ840Pp/PnHnmmebZZ5/t9HPXdc03vvENU1RUZPx+v7nwwgvNjh07BtzOwWh/eXl5wtf57rvvNsYY09TUZD784Q+bCRMmGK/Xa8rLy82tt97aZS3D4Wj7V7/61fi+RUVF5uKLLzYbNmzodL6hfu1F+kr1UfUxVe1XjZR0p/qo+piq9qs+pjfLmAGsNyAiIiIiIiIi3RpT93SLiIiIiIiIDCWFbhEREREREZEUUegWERERERERSRGFbhEREREREZEUUegWERERERERSRGFbhEREREREZEUUegWERERERERSRGFbhEREREREZEUUegWERERERERSRGFbhEREREREZEUUegWERERERERSRGFbhEREREREZEUUegWERERERERSRGFbhEREREREZEUUegWERERERERSRGFbhEREREREZEUUegWkWH1pz/9iUsvvZTS0lIsy+Kpp57q9ZhXXnmFc845B7/fz/Tp03nwwQe77HPvvfdSUVFBIBBg8eLFrFu3bvAbLyKSQqqPIiLdS6caqdAtIsOqsbGRBQsWcO+99ya1/969e7nkkkv4wAc+wMaNG/nqV7/KZz7zGV544YX4Po8++igrVqzg7rvvZsOGDSxYsIBly5Zx7NixVD0NEZFBp/ooItK9dKqRljHGDOgMIiKDxLIsVq5cyRVXXNHtPnfccQfPPvssmzdvjm+75pprqKmpYfXq1QAsXryY8847jx/96EcAuK5LWVkZX/rSl/j617+e0ucgIpIKqo8iIt0b6TXS0+8jRyjXdTly5Ag5OTlYljXczRFJS8YY6uvrKS0txbZjA2JaWloIhUJJH3/675/f78fv9w+4bWvXrmXp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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from sigmaepsilon.mesh.plotting import triplot_mpl_data\n", + "from sigmaepsilon.mesh import triangulate\n", + "\n", + "# get the triangulation as a `matplotlib` triangulation object\n", + "*_, triobj = triangulate(points=triangulation[\"vertices\"], triangles=triangulation[\"triangles\"])\n", + "\n", + "# evaluate shape functions at the vertices of the triangulation\n", + "values = T6.Geometry.shape_function_values(triangulation[\"vertices\"])\n", + "\n", + "# plot the values\n", + "fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(2, 3, figsize=(12, 6))\n", + "for i, ax in enumerate([ax1, ax2, ax3, ax4, ax5, ax6]):\n", + " _ = triplot_mpl_data(triobj, fig=fig, ax=ax, data=values[:, i], nlevels=10)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".mesh", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 69ac15864756f05b674d9766552b4ccd3a72c069 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 10:28:11 +0100 Subject: [PATCH 31/47] formatting with black --- src/sigmaepsilon/mesh/cells/t6.py | 2 +- src/sigmaepsilon/mesh/io/from_pyvista.py | 1 + src/sigmaepsilon/mesh/io/to_k3d.py | 1 + src/sigmaepsilon/mesh/io/to_pyvista.py | 1 + src/sigmaepsilon/mesh/io/to_vtk.py | 1 + src/sigmaepsilon/mesh/plotting/k3dplot.py | 1 + .../mesh/plotting/mpl/parallel.py | 1 + src/sigmaepsilon/mesh/plotting/mpl/triplot.py | 1 + src/sigmaepsilon/mesh/plotting/mpl/utils.py | 1 + .../mesh/plotting/plotly/lines.py | 1 + .../mesh/plotting/plotly/points.py | 1 + src/sigmaepsilon/mesh/plotting/plotly/tri.py | 1 + src/sigmaepsilon/mesh/plotting/pvplot.py | 1 + tests/cells/test_H8.py | 8 ++--- tests/cells/test_Q8.py | 6 ++-- tests/cells/test_tet.py | 2 +- tests/cells/test_tri.py | 32 +++++++++---------- tests/plotting/test_parallel.py | 18 +++++------ tests/plotting/test_plotly.py | 11 +++---- tests/plotting/test_triplot.py | 6 ++-- tests/test_examples.py | 2 +- tests/test_grid.py | 6 ++-- 22 files changed, 58 insertions(+), 47 deletions(-) diff --git a/src/sigmaepsilon/mesh/cells/t6.py b/src/sigmaepsilon/mesh/cells/t6.py index f811845..d77983c 100644 --- a/src/sigmaepsilon/mesh/cells/t6.py +++ b/src/sigmaepsilon/mesh/cells/t6.py @@ -63,7 +63,7 @@ def polybase(cls) -> Tuple[List]: A list of monomials. """ locvars = r, s = symbols("r s", real=True) - monoms = [1, r, s, r**2, s**2, r * s] + monoms = [1, r, s, r ** 2, s ** 2, r * s] return locvars, monoms @classmethod diff --git a/src/sigmaepsilon/mesh/io/from_pyvista.py b/src/sigmaepsilon/mesh/io/from_pyvista.py index 5dee02c..f17191e 100644 --- a/src/sigmaepsilon/mesh/io/from_pyvista.py +++ b/src/sigmaepsilon/mesh/io/from_pyvista.py @@ -9,6 +9,7 @@ def from_pv(*_) -> None: "You may also need to restart your kernel and reload the package." ) + else: import pyvista as pv from typing import Union diff --git a/src/sigmaepsilon/mesh/io/to_k3d.py b/src/sigmaepsilon/mesh/io/to_k3d.py index 04eb788..c8f6692 100644 --- a/src/sigmaepsilon/mesh/io/to_k3d.py +++ b/src/sigmaepsilon/mesh/io/to_k3d.py @@ -9,6 +9,7 @@ def to_k3d(*_, **__): "You may also need to restart your kernel and reload the package." ) + else: from copy import copy from typing import Union, Iterable, Optional diff --git a/src/sigmaepsilon/mesh/io/to_pyvista.py b/src/sigmaepsilon/mesh/io/to_pyvista.py index c40e69e..dc5d408 100644 --- a/src/sigmaepsilon/mesh/io/to_pyvista.py +++ b/src/sigmaepsilon/mesh/io/to_pyvista.py @@ -9,6 +9,7 @@ def to_pv(*_) -> None: "You may also need to restart your kernel and reload the package." ) + else: from typing import Union, Optional from contextlib import suppress diff --git a/src/sigmaepsilon/mesh/io/to_vtk.py b/src/sigmaepsilon/mesh/io/to_vtk.py index 73f7bd2..4395732 100644 --- a/src/sigmaepsilon/mesh/io/to_vtk.py +++ b/src/sigmaepsilon/mesh/io/to_vtk.py @@ -9,6 +9,7 @@ def to_vtk(*_) -> None: "You may also need to restart your kernel and reload the package." ) + else: import vtk from typing import Union diff --git a/src/sigmaepsilon/mesh/plotting/k3dplot.py b/src/sigmaepsilon/mesh/plotting/k3dplot.py index 2a43d20..8efc557 100644 --- a/src/sigmaepsilon/mesh/plotting/k3dplot.py +++ b/src/sigmaepsilon/mesh/plotting/k3dplot.py @@ -9,6 +9,7 @@ def k3dplot(*_, **__) -> None: "You may also need to restart your kernel and reload the package." ) + else: from typing import Union, Optional diff --git a/src/sigmaepsilon/mesh/plotting/mpl/parallel.py b/src/sigmaepsilon/mesh/plotting/mpl/parallel.py index 91bfc8e..db6c79b 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/parallel.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/parallel.py @@ -15,6 +15,7 @@ def aligned_parallel_mpl(*_, **__): "You may also need to restart your kernel and reload the package." ) + else: from typing import Iterable, Hashable, Union, Optional diff --git a/src/sigmaepsilon/mesh/plotting/mpl/triplot.py b/src/sigmaepsilon/mesh/plotting/mpl/triplot.py index 7e196a1..a2f106e 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/triplot.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/triplot.py @@ -20,6 +20,7 @@ def triplot_mpl_data(*_, **__): "You may also need to restart your kernel and reload the package." ) + else: from typing import Any, Union, Optional, Iterable diff --git a/src/sigmaepsilon/mesh/plotting/mpl/utils.py b/src/sigmaepsilon/mesh/plotting/mpl/utils.py index 19e12bd..b55eb2d 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/utils.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/utils.py @@ -21,6 +21,7 @@ def decorate_mpl_ax(*_, **__): "You may also need to restart your kernel and reload the package." ) + else: from typing import Iterable, Callable, Any from functools import wraps diff --git a/src/sigmaepsilon/mesh/plotting/plotly/lines.py b/src/sigmaepsilon/mesh/plotting/plotly/lines.py index 1fb6889..af97263 100644 --- a/src/sigmaepsilon/mesh/plotting/plotly/lines.py +++ b/src/sigmaepsilon/mesh/plotting/plotly/lines.py @@ -14,6 +14,7 @@ def scatter_lines_plotly(*_, **__): "You may also need to restart your kernel and reload the package." ) + else: import plotly.graph_objects as go from numpy import ndarray diff --git a/src/sigmaepsilon/mesh/plotting/plotly/points.py b/src/sigmaepsilon/mesh/plotting/plotly/points.py index 8e0be79..1063cd2 100644 --- a/src/sigmaepsilon/mesh/plotting/plotly/points.py +++ b/src/sigmaepsilon/mesh/plotting/plotly/points.py @@ -8,6 +8,7 @@ def scatter_points_plotly(*_, **__): "You may also need to restart your kernel and reload the package." ) + else: from typing import Iterable, Optional, Union from numbers import Number diff --git a/src/sigmaepsilon/mesh/plotting/plotly/tri.py b/src/sigmaepsilon/mesh/plotting/plotly/tri.py index 83765ee..8ce2fe3 100644 --- a/src/sigmaepsilon/mesh/plotting/plotly/tri.py +++ b/src/sigmaepsilon/mesh/plotting/plotly/tri.py @@ -8,6 +8,7 @@ def triplot_plotly(*_, **__): "You may also need to restart your kernel and reload the package." ) + else: from typing import Optional, Union diff --git a/src/sigmaepsilon/mesh/plotting/pvplot.py b/src/sigmaepsilon/mesh/plotting/pvplot.py index 1f724eb..7f4f63e 100644 --- a/src/sigmaepsilon/mesh/plotting/pvplot.py +++ b/src/sigmaepsilon/mesh/plotting/pvplot.py @@ -9,6 +9,7 @@ def pvplot(*_, **__) -> None: "You may also need to restart your kernel and reload the package." ) + else: from typing import Union, Iterable, Tuple from copy import copy diff --git a/tests/cells/test_H8.py b/tests/cells/test_H8.py index 2a7f664..306718d 100644 --- a/tests/cells/test_H8.py +++ b/tests/cells/test_H8.py @@ -57,7 +57,7 @@ def test_H8_shape_function_derivatives(self): self.assertTrue(gdshp.shape, (topo.shape[0], 2, topo.shape[1], 3)) del gdshp, mesh - + def test_H8_block(self): Lx, Ly, Lz = 800, 600, 100 nx, ny, nz = 8, 6, 2 @@ -70,7 +70,7 @@ def test_H8_block(self): pd = PointData(coords=coords) cd = H8(topo=topo, frames=frame) _ = PolyData(pd, cd, frame=frame) - + cd.to_tetrahedra(flatten=False) cd.to_simplices() self.assertEqual(len(cd.frames), len(topo)) @@ -80,8 +80,8 @@ def test_H8_block(self): cd.source_frame() cd.points_of_cells() cd.coords() - cd.loc_to_glob([0., 0., 0.]) - cd.locate([0., 0., 0.]) + cd.loc_to_glob([0.0, 0.0, 0.0]) + cd.locate([0.0, 0.0, 0.0]) cd.centers() cd.unique_indices() cd.points_involved() diff --git a/tests/cells/test_Q8.py b/tests/cells/test_Q8.py index 5d3a706..e38d53c 100644 --- a/tests/cells/test_Q8.py +++ b/tests/cells/test_Q8.py @@ -21,9 +21,9 @@ def test_Q8(self): pd = PointData(coords=coords) cd = Q8(topo=topo, frames=frame) _ = PolyData(pd, cd) - - self.assertTrue(np.isclose(cd.area(), Lx*Ly)) - self.assertTrue(np.isclose(cd.volume(), Lx*Ly)) + + self.assertTrue(np.isclose(cd.area(), Lx * Ly)) + self.assertTrue(np.isclose(cd.volume(), Lx * Ly)) self.assertEqual(cd.jacobian_matrix().shape, (topo.shape[0], 8, 2, 2)) diff --git a/tests/cells/test_tet.py b/tests/cells/test_tet.py index 27b2cd4..5992dc8 100644 --- a/tests/cells/test_tet.py +++ b/tests/cells/test_tet.py @@ -64,7 +64,7 @@ def test_shp_TET10(self): shpf(pcoords) shpmf(pcoords) dshpf(pcoords) - + class TestTET4(SigmaEpsilonTestCase): def test_TET4(self, N: int = 3): diff --git a/tests/cells/test_tri.py b/tests/cells/test_tri.py index 9b6b9a2..d5b2d9d 100644 --- a/tests/cells/test_tri.py +++ b/tests/cells/test_tri.py @@ -28,7 +28,7 @@ def test_T3(self, N: int = 3): return_symbolic=True ) r, s = symbols("r, s", real=True) - + nNE = T3.Geometry.number_of_nodes nD = T3.Geometry.number_of_spatial_dimensions @@ -57,7 +57,7 @@ def test_T3(self, N: int = 3): shpmfA = shpmf(x_loc) shpmfB = T3.Geometry.shape_function_matrix(x_loc) self.assertTrue(np.allclose(shpmfA, shpmfB)) - + mc = T3.Geometry.master_coordinates() shp = T3.Geometry.shape_function_values(mc) self.assertTrue(np.allclose(np.diag(shp), np.ones((nNE)))) @@ -65,7 +65,7 @@ def test_T3(self, N: int = 3): nX = 2 shpmf = T3.Geometry.shape_function_matrix(x_loc, N=nX) self.assertEqual(shpmf.shape, (1, nX, 3 * nX)) - + frame = CartesianFrame() coords = np.zeros((nNE, 3), dtype=float) coords[:, :nD] = T3.Geometry.master_coordinates() @@ -75,13 +75,13 @@ def test_T3(self, N: int = 3): _ = PolyData(pd, cd) self.assertTrue(np.isclose(cd.area(), 0.5)) self.assertTrue(np.allclose(cd.jacobian(), np.ones((1, nNE)))) - + def test_T6(self, N: int = 3): shp, dshp, shpf, shpmf, dshpf = T6.Geometry.generate_class_functions( return_symbolic=True ) r, s = symbols("r, s", real=True) - + nNE = T6.Geometry.number_of_nodes nD = T6.Geometry.number_of_spatial_dimensions @@ -114,11 +114,11 @@ def test_T6(self, N: int = 3): mc = T6.Geometry.master_coordinates() shp = T6.Geometry.shape_function_values(mc) self.assertTrue(np.allclose(np.diag(shp), np.ones((nNE)))) - + nX = 2 shpmf = T6.Geometry.shape_function_matrix(x_loc, N=nX) self.assertEqual(shpmf.shape, (1, nX, 6 * nX)) - + frame = CartesianFrame() coords = np.zeros((nNE, 3), dtype=float) coords[:, :nD] = T6.Geometry.master_coordinates() @@ -128,7 +128,7 @@ def test_T6(self, N: int = 3): _ = PolyData(pd, cd) self.assertTrue(np.isclose(cd.area(), 0.5)) self.assertTrue(np.allclose(cd.jacobian(), np.ones((1, nNE)))) - + class TestTriutils(SigmaEpsilonTestCase): def test_triutils(self): @@ -140,35 +140,35 @@ def test_triutils(self): _ = PolyData(pd, cd) ec = cd.local_coordinates() nE, nNE = topo.shape - + self.assertTrue(np.allclose(nat_to_loc_tri(ncenter_tri()), lcenter_tri())) self.assertTrue(np.allclose(loc_to_nat_tri(lcenter_tri()), ncenter_tri())) - + x_tri_loc = lcoords_tri() x_tri_nat = np.eye(3).astype(float) c_tri_loc = lcenter_tri() - + for iNE in range(nNE): x_nat = loc_to_nat_tri(x_tri_loc[iNE]) self.assertTrue(np.allclose(x_nat, x_tri_nat[iNE])) - + for iE in range(nE): x_glob = loc_to_glob_tri(c_tri_loc, ec[iE]) self.assertTrue(np.allclose(center_tri_2d(ec[iE]), x_glob)) - + for iE in range(nE): self.assertAlmostEqual(area_tri(ec[iE]), 2.0, delta=1e-5) - + for iE in range(nE): for iNE in range(nNE): x_glob = loc_to_glob_tri(x_tri_loc[iNE], ec[iE]) self.assertTrue(np.allclose(x_glob, ec[iE, iNE])) - + for iE in range(nE): for iNE in range(nNE): x_glob = nat_to_glob_tri(x_tri_nat[iNE], ec[iE]) self.assertTrue(np.allclose(x_glob, ec[iE, iNE])) - + for iE in range(nE): for iNE in range(nNE): x_loc = glob_to_loc_tri(ec[iE, iNE], ec[iE]) diff --git a/tests/plotting/test_parallel.py b/tests/plotting/test_parallel.py index 2b6df40..b825c7f 100644 --- a/tests/plotting/test_parallel.py +++ b/tests/plotting/test_parallel.py @@ -68,7 +68,7 @@ def test_aligned_parallel_mpl_plot_1(self): labels = ["a", "b", "c"] values = np.array([np.random.rand(10) for _ in labels]).T datapos = np.linspace(-1, 1, 10) - + aligned_parallel_mpl( values, datapos, @@ -77,34 +77,34 @@ def test_aligned_parallel_mpl_plot_1(self): return_figure=False, slider=True, ) - + aligned_parallel_mpl( values, datapos, labels=labels, yticks=[-1, 1], return_figure=True, - y0 = 0.0, + y0=0.0, slider=True, ) - + aligned_parallel_mpl( values, datapos, yticks=[-1, 1], return_figure=True, - y0 = 0.0, + y0=0.0, slider=True, ) - - values = {label : np.random.rand(10) for label in labels} - + + values = {label: np.random.rand(10) for label in labels} + aligned_parallel_mpl( values, datapos, yticks=[-1, 1], return_figure=True, - y0 = 0.0, + y0=0.0, slider=True, ) diff --git a/tests/plotting/test_plotly.py b/tests/plotting/test_plotly.py index fd477cf..35b7ae5 100644 --- a/tests/plotting/test_plotly.py +++ b/tests/plotting/test_plotly.py @@ -13,7 +13,6 @@ class TestPlotly(SigmaEpsilonTestCase): - def test_points(self): gridparams = { "size": (1200, 600), @@ -24,7 +23,7 @@ def test_points(self): data = np.random.rand(len(points)) scatter_points_plotly(points) scatter_points_plotly(points, scalars=data) - + def test_lines(self): gridparams = { "size": (10, 10, 10), @@ -37,8 +36,8 @@ def test_lines(self): plot_lines_plotly(coords, topo) plot_lines_plotly(coords, topo, scalars=data) plot_lines_plotly(coords, topo, scalars=data, scalar_labels=["X", "Y"]) - - def test_triplot(self): + + def test_triplot(self): gridparams = { "size": (1200, 600), "shape": (4, 4), @@ -50,7 +49,7 @@ def test_triplot(self): triplot_plotly(points, triangles, plot_edges=False) triplot_plotly(points, triangles, data, plot_edges=False) triplot_plotly(points, triangles, data, plot_edges=True) - - + + if __name__ == "__main__": unittest.main() diff --git a/tests/plotting/test_triplot.py b/tests/plotting/test_triplot.py index c3163d3..bf3bccd 100644 --- a/tests/plotting/test_triplot.py +++ b/tests/plotting/test_triplot.py @@ -11,7 +11,7 @@ triplot_mpl_mesh, triplot_mpl_hinton, ) -import matplotlib.tri as mpltri +import matplotlib.tri as mpltri class TestMplTriplot(SigmaEpsilonTestCase): @@ -33,14 +33,14 @@ def test_triplot(self): data = np.random.rand(len(triangles)) triplot_mpl_data(triobj, data=data) triplot_mpl_hinton(triobj, data=data) - + data = np.random.rand(len(triangles), 3) triplot_mpl_data(triobj, data=data) data = np.random.rand(len(points)) triplot_mpl_data(triobj, data=data, cmap="bwr") triplot_mpl_data(triobj, data=data, cmap="bwr", refine=True, draw_contours=True) - refiner = mpltri.UniformTriRefiner(triobj) + refiner = mpltri.UniformTriRefiner(triobj) triplot_mpl_data(triobj, data=data, cmap="bwr", refiner=refiner, nlevels=10) def circular_disk(self): diff --git a/tests/test_examples.py b/tests/test_examples.py index b1de1c4..9078ab8 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -9,4 +9,4 @@ def test_compound_mesh(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_grid.py b/tests/test_grid.py index c637fdb..845159a 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -19,7 +19,7 @@ def test_grid_Q4(self): self.assertEqual(topo.shape[0], np.prod(shape)) self.assertEqual(topo.shape[1], 4) - + def test_grid_Q8(self): size = 80, 60 shape = 8, 6 @@ -27,7 +27,7 @@ def test_grid_Q8(self): self.assertEqual(topo.shape[0], np.prod(shape)) self.assertEqual(topo.shape[1], 8) - + def test_grid_Q9(self): size = 80, 60 shape = 8, 6 @@ -35,7 +35,7 @@ def test_grid_Q9(self): self.assertEqual(topo.shape[0], np.prod(shape)) self.assertEqual(topo.shape[1], 9) - + def test_grid_H8(self): size = 80, 60, 20 shape = 8, 6, 2 From c3bfb035dcc3d1e1053fd2bbdcc8cf7e19269785 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 11:11:34 +0100 Subject: [PATCH 32/47] refactored Quadrature class --- src/sigmaepsilon/mesh/utils/cells/numint.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/sigmaepsilon/mesh/utils/cells/numint.py b/src/sigmaepsilon/mesh/utils/cells/numint.py index 8638705..af98a5c 100644 --- a/src/sigmaepsilon/mesh/utils/cells/numint.py +++ b/src/sigmaepsilon/mesh/utils/cells/numint.py @@ -1,12 +1,25 @@ -from typing import Tuple -from collections import namedtuple +from typing import Tuple, Iterable +from numbers import Number import numpy as np from numpy import ndarray from sigmaepsilon.math.numint import gauss_points as gp -Quadrature = namedtuple("QuadratureRule", ["inds", "pos", "weight"]) + +class Quadrature: + + def __init__(self, x: Iterable[Number], w: Iterable[Number]): + self._pos = x + self._weight = w + + @property + def pos(self) -> Iterable[Number]: + return self._pos + + @property + def weight(self) -> Iterable[Number]: + return self._weight # LINES From 2a4cf4504d8566cee00f9b8444710d349359d356 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 11:11:49 +0100 Subject: [PATCH 33/47] refactored area and volume calculations --- src/sigmaepsilon/mesh/cells/h27.py | 22 +----- src/sigmaepsilon/mesh/cells/h8.py | 21 +----- src/sigmaepsilon/mesh/cells/l2.py | 4 +- src/sigmaepsilon/mesh/cells/l3.py | 4 +- src/sigmaepsilon/mesh/cells/q4.py | 3 +- src/sigmaepsilon/mesh/cells/q8.py | 3 +- src/sigmaepsilon/mesh/cells/q9.py | 3 +- src/sigmaepsilon/mesh/cells/t3.py | 3 +- src/sigmaepsilon/mesh/cells/t6.py | 19 +---- src/sigmaepsilon/mesh/cells/tet10.py | 13 +--- src/sigmaepsilon/mesh/cells/tet4.py | 15 +++- src/sigmaepsilon/mesh/cells/w18.py | 13 +--- src/sigmaepsilon/mesh/cells/w6.py | 13 +--- src/sigmaepsilon/mesh/data/polycell.py | 80 ++++++++++++---------- src/sigmaepsilon/mesh/utils/cells/utils.py | 14 ++-- 15 files changed, 91 insertions(+), 139 deletions(-) diff --git a/src/sigmaepsilon/mesh/cells/h27.py b/src/sigmaepsilon/mesh/cells/h27.py index e0e999e..a8726fd 100644 --- a/src/sigmaepsilon/mesh/cells/h27.py +++ b/src/sigmaepsilon/mesh/cells/h27.py @@ -1,17 +1,15 @@ from typing import Tuple, List +from functools import partial + import numpy as np from numpy import ndarray import sympy as sy -from sigmaepsilon.math.numint import gauss_points as gp - from ..geometry import PolyCellGeometry3d from ..data.polycell import PolyCell -from ..utils.utils import cells_coords from ..utils.cells.h27 import ( shp_H27_multi, dshp_H27_multi, - volumes_H27, shape_function_matrix_H27_multi, monoms_H27, ) @@ -56,7 +54,7 @@ class Geometry(PolyCellGeometry3d): shape_function_derivative_evaluator: dshp_H27_multi monomial_evaluator: monoms_H27 quadrature = { - "full": Gauss_Legendre_Hex_Grid(3, 3, 3), + "full": partial(Gauss_Legendre_Hex_Grid, 3, 3, 3), "geometry": "full", } @@ -156,17 +154,3 @@ def master_center(cls) -> ndarray: numpy.ndarray """ return np.array([0.0, 0.0, 0.0]) - - def volumes(self) -> ndarray: - """ - Returns the volumes of the cells. - - Returns - ------- - numpy.ndarray - """ - coords = self.source_coords() - topo = self.topology().to_numpy() - ecoords = cells_coords(coords, topo) - qpos, qweight = gp(3, 3, 3) - return volumes_H27(ecoords, qpos, qweight) diff --git a/src/sigmaepsilon/mesh/cells/h8.py b/src/sigmaepsilon/mesh/cells/h8.py index 6d8ab2d..b7eee9d 100644 --- a/src/sigmaepsilon/mesh/cells/h8.py +++ b/src/sigmaepsilon/mesh/cells/h8.py @@ -1,18 +1,15 @@ from typing import Tuple, List +from functools import partial from sympy import symbols import numpy as np from numpy import ndarray -from sigmaepsilon.math.numint import gauss_points as gp - from ..geometry import PolyCellGeometry3d from ..data.polycell import PolyCell -from ..utils.utils import cells_coords from ..utils.cells.h8 import ( shp_H8_multi, dshp_H8_multi, - volumes_H8, shape_function_matrix_H8_multi, monoms_H8, ) @@ -46,7 +43,7 @@ class Geometry(PolyCellGeometry3d): shape_function_derivative_evaluator: dshp_H8_multi monomial_evaluator: monoms_H8 quadrature = { - "full": Gauss_Legendre_Hex_Grid(2, 2, 2), + "full": partial(Gauss_Legendre_Hex_Grid, 2, 2, 2), "geometry": "full", } @@ -105,17 +102,3 @@ def tetmap(cls) -> np.ndarray: [[1, 2, 0, 5], [3, 0, 2, 7], [5, 4, 7, 0], [6, 5, 7, 2], [0, 2, 7, 5]], dtype=int, ) - - def volumes(self) -> ndarray: - """ - Returns the volumes of the cells. - - Returns - ------- - numpy.ndarray - """ - coords = self.source_coords() - topo = self.topology().to_numpy() - ecoords = cells_coords(coords, topo) - qpos, qweight = gp(2, 2, 2) - return volumes_H8(ecoords, qpos, qweight) diff --git a/src/sigmaepsilon/mesh/cells/l2.py b/src/sigmaepsilon/mesh/cells/l2.py index b6a0131..686ddad 100644 --- a/src/sigmaepsilon/mesh/cells/l2.py +++ b/src/sigmaepsilon/mesh/cells/l2.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from functools import partial + from ..geometry import PolyCellGeometry1d from ..data.polycell import PolyCell from ..utils.cells.l2 import ( @@ -25,6 +27,6 @@ class Geometry(PolyCellGeometry1d): shape_function_derivative_evaluator: dshp_L2_multi monomial_evaluator: monoms_L2 quadrature = { - "full": Gauss_Legendre_Line_Grid(2), + "full": partial(Gauss_Legendre_Line_Grid, 2), "geometry": "full", } diff --git a/src/sigmaepsilon/mesh/cells/l3.py b/src/sigmaepsilon/mesh/cells/l3.py index 2635c2b..779dd17 100644 --- a/src/sigmaepsilon/mesh/cells/l3.py +++ b/src/sigmaepsilon/mesh/cells/l3.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from functools import partial + from ..geometry import PolyCellGeometry1d from ..data.polycell import PolyCell from ..utils.cells.numint import Gauss_Legendre_Line_Grid @@ -18,6 +20,6 @@ class Geometry(PolyCellGeometry1d): vtk_cell_id = 21 monomial_evaluator: monoms_L3 quadrature = { - "full": Gauss_Legendre_Line_Grid(3), + "full": partial(Gauss_Legendre_Line_Grid, 3), "geometry": "full", } diff --git a/src/sigmaepsilon/mesh/cells/q4.py b/src/sigmaepsilon/mesh/cells/q4.py index 1e5f9e7..d57a005 100644 --- a/src/sigmaepsilon/mesh/cells/q4.py +++ b/src/sigmaepsilon/mesh/cells/q4.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from typing import Tuple, List + import numpy as np from numpy import ndarray from sympy import symbols @@ -31,7 +32,7 @@ class Geometry(PolyCellGeometry2d): shape_function_derivative_evaluator: dshp_Q4_multi monomial_evaluator: monoms_Q4 quadrature = { - "full": Gauss_Legendre_Quad_4(), + "full": Gauss_Legendre_Quad_4, "geometry": "full", } diff --git a/src/sigmaepsilon/mesh/cells/q8.py b/src/sigmaepsilon/mesh/cells/q8.py index cf7786a..0ab841e 100644 --- a/src/sigmaepsilon/mesh/cells/q8.py +++ b/src/sigmaepsilon/mesh/cells/q8.py @@ -1,4 +1,5 @@ from typing import Tuple, List + import numpy as np from numpy import ndarray from sympy import symbols @@ -30,7 +31,7 @@ class Geometry(PolyCellGeometry2d): shape_function_derivative_evaluator: dshp_Q8_multi monomial_evaluator: monoms_Q8 quadrature = { - "full": Gauss_Legendre_Quad_9(), + "full": Gauss_Legendre_Quad_9, "geometry": "full", } diff --git a/src/sigmaepsilon/mesh/cells/q9.py b/src/sigmaepsilon/mesh/cells/q9.py index 1aa36f4..0980762 100644 --- a/src/sigmaepsilon/mesh/cells/q9.py +++ b/src/sigmaepsilon/mesh/cells/q9.py @@ -1,4 +1,5 @@ from typing import Tuple, List + import numpy as np from numpy import ndarray from sympy import symbols @@ -30,7 +31,7 @@ class Geometry(PolyCellGeometry2d): shape_function_derivative_evaluator: dshp_Q9_multi monomial_evaluator: monoms_Q9 quadrature = { - "full": Gauss_Legendre_Quad_9(), + "full": Gauss_Legendre_Quad_9, "geometry": "full", } diff --git a/src/sigmaepsilon/mesh/cells/t3.py b/src/sigmaepsilon/mesh/cells/t3.py index bcffb72..778060a 100644 --- a/src/sigmaepsilon/mesh/cells/t3.py +++ b/src/sigmaepsilon/mesh/cells/t3.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from typing import Tuple, List + import numpy as np from numpy import ndarray from sympy import symbols @@ -43,7 +44,7 @@ class Geometry(PolyCellGeometry2d): shape_function_derivative_evaluator: dshp_T3_multi monomial_evaluator: monoms_T3 quadrature = { - "full": Gauss_Legendre_Tri_1(), + "full": Gauss_Legendre_Tri_1, "geometry": "full", } diff --git a/src/sigmaepsilon/mesh/cells/t6.py b/src/sigmaepsilon/mesh/cells/t6.py index d77983c..f1c04d2 100644 --- a/src/sigmaepsilon/mesh/cells/t6.py +++ b/src/sigmaepsilon/mesh/cells/t6.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from typing import Tuple, List + import numpy as np from numpy import ndarray from sympy import symbols @@ -46,7 +47,7 @@ class Geometry(PolyCellGeometry2d): shape_function_derivative_evaluator: dshp_T6_multi monomial_evaluator: monoms_T6 quadrature = { - "full": Gauss_Legendre_Tri_3a(), + "full": Gauss_Legendre_Tri_3a, "geometry": "full", } @@ -110,22 +111,6 @@ def to_triangles(self) -> ndarray: """ return T6_to_T3(None, self.topology().to_numpy())[1] - def areas(self) -> ndarray: - """ - Returns the areas of the triangles of the block. - - Returns - ------- - numpy.ndarray - """ - coords = self.source_coords() - topo = self.topology().to_numpy() - ecoords = cells_coords(coords[:, :2], topo) - quad: Quadrature = next( - self._parse_gauss_data(self.Geometry.quadrature, "geometry") - ) - return areas_T6(ecoords, quad.pos, quad.weight) - @classmethod def from_TriMesh(cls, *args, coords=None, topo=None, **kwargs): from sigmaepsilon.mesh.data.trimesh import TriMesh diff --git a/src/sigmaepsilon/mesh/cells/tet10.py b/src/sigmaepsilon/mesh/cells/tet10.py index aae2a27..5080507 100644 --- a/src/sigmaepsilon/mesh/cells/tet10.py +++ b/src/sigmaepsilon/mesh/cells/tet10.py @@ -1,4 +1,5 @@ from typing import Tuple, List + import numpy as np from numpy import ndarray from sympy import symbols @@ -9,8 +10,6 @@ monoms_TET10, ) from ..utils.cells.numint import Gauss_Legendre_Tet_4 -from ..utils.cells.utils import volumes -from ..utils.utils import cells_coords class TET10(PolyCell): @@ -25,7 +24,7 @@ class Geometry(PolyCellGeometry3d): vtk_cell_id = 24 monomial_evaluator: monoms_TET10 quadrature = { - "full": Gauss_Legendre_Tet_4(), + "full": Gauss_Legendre_Tet_4, "geometry": "full", } @@ -72,11 +71,3 @@ def tetmap(cls, subdivide: bool = True) -> np.ndarray: raise NotImplementedError else: return np.array([[0, 1, 2, 3]], dtype=int) - - def volumes(self) -> ndarray: - coords = self.source_coords() - topo = self.topology().to_numpy() - ecoords = cells_coords(coords, topo) - qpos, qweight = self.Geometry.quadrature["full"] - dshp = self.Geometry.shape_function_derivatives(qpos) - return volumes(ecoords, dshp, qweight) diff --git a/src/sigmaepsilon/mesh/cells/tet4.py b/src/sigmaepsilon/mesh/cells/tet4.py index 416b573..304b61b 100644 --- a/src/sigmaepsilon/mesh/cells/tet4.py +++ b/src/sigmaepsilon/mesh/cells/tet4.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from typing import Tuple, List + import numpy as np from numpy import ndarray from sympy import symbols @@ -13,6 +14,8 @@ monoms_TET4, ) from ..utils.cells.numint import Gauss_Legendre_Tet_1 +from ..utils.tet import vol_tet_bulk +from ..utils.utils import cells_coords class TET4(PolyCell): @@ -30,7 +33,7 @@ class Geometry(PolyCellGeometry3d): shape_function_derivative_evaluator: dshp_TET4_multi monomial_evaluator: monoms_TET4 quadrature = { - "full": Gauss_Legendre_Tet_1(), + "full": Gauss_Legendre_Tet_1, "geometry": "full", } @@ -75,3 +78,13 @@ def to_tetrahedra(self, flatten: bool = True) -> ndarray: return tetra else: return tetra.reshape(len(tetra), 1, 4) + + def volumes(self) -> ndarray: + coords = self.source_coords() + topo = self.topology().to_numpy() + volumes = vol_tet_bulk(cells_coords(coords, topo)) + res = np.sum( + volumes.reshape(topo.shape[0], int(len(volumes) / topo.shape[0])), + axis=1, + ) + return res diff --git a/src/sigmaepsilon/mesh/cells/w18.py b/src/sigmaepsilon/mesh/cells/w18.py index d3bc027..206344f 100644 --- a/src/sigmaepsilon/mesh/cells/w18.py +++ b/src/sigmaepsilon/mesh/cells/w18.py @@ -1,4 +1,5 @@ from typing import Tuple, List + import numpy as np from numpy import ndarray from sympy import symbols @@ -6,8 +7,6 @@ from ..geometry import PolyCellGeometry3d from ..data.polycell import PolyCell from ..utils.cells.numint import Gauss_Legendre_Wedge_3x3 -from ..utils.cells.utils import volumes -from ..utils.utils import cells_coords from ..utils.cells.w18 import monoms_W18 from ..utils.topology import compose_trmap from .w6 import W6 @@ -25,7 +24,7 @@ class Geometry(PolyCellGeometry3d): vtk_cell_id = 32 monomial_evaluator: monoms_W18 quadrature = { - "full": Gauss_Legendre_Wedge_3x3(), + "full": Gauss_Legendre_Wedge_3x3, "geometry": "full", } @@ -110,11 +109,3 @@ def tetmap(cls) -> ndarray: ) w6_to_tet4 = W6.Geometry.tetmap() return compose_trmap(w18_to_w6, w6_to_tet4) - - def volumes(self) -> ndarray: - coords = self.source_coords() - topo = self.topology().to_numpy() - ecoords = cells_coords(coords, topo) - qpos, qweight = self.Geometry.quadrature["full"] - dshp = self.Geometry.shape_function_derivatives(qpos) - return volumes(ecoords, dshp, qweight) diff --git a/src/sigmaepsilon/mesh/cells/w6.py b/src/sigmaepsilon/mesh/cells/w6.py index bbe49ab..e756e73 100644 --- a/src/sigmaepsilon/mesh/cells/w6.py +++ b/src/sigmaepsilon/mesh/cells/w6.py @@ -1,4 +1,5 @@ from typing import Tuple, List + import numpy as np from numpy import ndarray from sympy import symbols @@ -6,8 +7,6 @@ from ..geometry import PolyCellGeometry3d from ..data.polycell import PolyCell from ..utils.cells.numint import Gauss_Legendre_Wedge_3x2 -from ..utils.cells.utils import volumes -from ..utils.utils import cells_coords from ..utils.cells.w6 import monoms_W6 @@ -23,7 +22,7 @@ class Geometry(PolyCellGeometry3d): vtk_cell_id = 13 monomial_evaluator: monoms_W6 quadrature = { - "full": Gauss_Legendre_Wedge_3x2(), + "full": Gauss_Legendre_Wedge_3x2, "geometry": "full", } @@ -66,11 +65,3 @@ def tetmap(cls) -> ndarray: [[0, 1, 2, 4], [3, 5, 4, 2], [2, 5, 0, 4]], dtype=int, ) - - def volumes(self) -> ndarray: - coords = self.source_coords() - topo = self.topology().to_numpy() - ecoords = cells_coords(coords, topo) - qpos, qweight = self.Geometry.quadrature["full"] - dshp = self.Geometry.shape_function_derivatives(qpos) - return volumes(ecoords, dshp, qweight) diff --git a/src/sigmaepsilon/mesh/data/polycell.py b/src/sigmaepsilon/mesh/data/polycell.py index 2401b50..e102bbf 100644 --- a/src/sigmaepsilon/mesh/data/polycell.py +++ b/src/sigmaepsilon/mesh/data/polycell.py @@ -38,7 +38,6 @@ _loc_to_glob_bulk_, ) from ..utils.tet import ( - vol_tet_bulk, _pip_tet_bulk_knn_, _pip_tet_bulk_, _glob_to_nat_tet_bulk_, @@ -49,10 +48,11 @@ _find_first_hits_, _find_first_hits_knn_, _ntet_to_loc_bulk_, + cell_measures, ) from ..utils.topology.topo import detach_mesh_bulk, rewire from ..utils.topology import transform_topology -from ..utils.tri import triangulate_cell_coords, area_tri_bulk, _pip_tri_bulk_ +from ..utils.tri import _pip_tri_bulk_ from ..utils.knn import k_nearest_neighbours from ..utils.space import index_of_closest_point, frames_of_lines, frames_of_surfaces from ..utils import cell_centers_bulk @@ -131,26 +131,16 @@ def _get_points_and_range( def _parse_gauss_data(quad_dict: dict, key: Hashable) -> Iterable[Quadrature]: value: Union[Callable, str, dict] = quad_dict[key] - if isinstance(value, dict): - for qinds, qvalue in value.items(): - if isinstance(qvalue, str): - for v in PolyCell._parse_gauss_data(value, qvalue): - v.inds = qinds - yield v - else: - qpos, qweight = qvalue - quad = Quadrature(qinds, qpos, qweight) - yield quad - elif isinstance(value, Callable): + if isinstance(value, Callable): qpos, qweight = value() - quad = Quadrature(np.s_[:], qpos, qweight) + quad = Quadrature(x=qpos, w=qweight) yield quad elif isinstance(value, str): for v in PolyCell._parse_gauss_data(quad_dict, value): yield v else: qpos, qweight = value - quad = Quadrature(np.s_[:], qpos, qweight) + quad = Quadrature(x=qpos, w=qweight) yield quad @CellData.frames.getter @@ -328,7 +318,11 @@ def flip(self) -> "PolyCell": return self def measures(self, *args, **kwargs) -> ndarray: - """Ought to return measures for each cell in the database.""" + """ + Returns generalized measures for each cell in the block. + The generalized measure is length for 1d cells, + area for 2d cells and volume for 3d cells. + """ NDIM: int = self.Geometry.number_of_spatial_dimensions if NDIM == 1: return self.lengths() @@ -340,8 +334,11 @@ def measures(self, *args, **kwargs) -> ndarray: raise NotImplementedError def measure(self, *args, **kwargs) -> float: - """Ought to return the net measure for the cells in the - database as a group.""" + """ + Returns the net generalized measure for the cells in the + block. The generalized measure is length for 1d cells, + area for 2d cells and volume for 3d cells. + """ return np.sum(self.measures(*args, **kwargs)) def thickness(self) -> ndarray: @@ -379,7 +376,8 @@ def lengths(self) -> ndarray: def area(self, *args, **kwargs) -> float: """ - Returns the total area of the cells in the database. Only for 2d entities. + Returns the total area of the cells in the database. + Only for 2d entities. """ if self.Geometry.number_of_spatial_dimensions == 2: return np.sum(self.areas(*args, **kwargs)) @@ -387,7 +385,14 @@ def area(self, *args, **kwargs) -> float: raise NotImplementedError("This is only for 2d cells") def areas(self, *args, **kwargs) -> ndarray: - """Ought to return the areas of the individuall cells in the database.""" + """ + Returns the areas of the individuall cells in the database as + a 1d float NumPy array. + + Note + ---- + For 1d cells, the cross sectional areas are returned. + """ NDIM: int = self.Geometry.number_of_spatial_dimensions if NDIM == 1: areakey = self._dbkey_areas_ @@ -396,25 +401,27 @@ def areas(self, *args, **kwargs) -> ndarray: else: return np.ones((len(self))) elif NDIM == 2: - nE = len(self) coords = self.source_coords() topo = self.topology().to_numpy() - frames = self.frames - ec = points_of_cells(coords, topo, local_axes=frames) - trimap = self.__class__.Geometry.trimap() - ec_tri = triangulate_cell_coords(ec, trimap) - areas_tri = area_tri_bulk(ec_tri) - res = np.sum(areas_tri.reshape(nE, int(len(areas_tri) / nE)), axis=1) - return res + ecoords = cells_coords(coords[:, :2], topo) + quad: Quadrature = next( + self._parse_gauss_data(self.Geometry.quadrature, "geometry") + ) + dshp = self.Geometry.shape_function_derivatives(quad.pos) + return cell_measures(ecoords, dshp, quad.weight) else: raise NotImplementedError("This is only for 2d cells") def volume(self, *args, **kwargs) -> float: - """Returns the volume of the cells in the database.""" + """ + Returns the volume of the cells in the database. + """ return np.sum(self.volumes(*args, **kwargs)) def volumes(self, *args, **kwargs) -> ndarray: - """Returns the volumes of the cells in the database.""" + """ + Returns the volumes of the cells in the database. + """ NDIM: int = self.Geometry.number_of_spatial_dimensions if NDIM == 1: return self.lengths() * self.areas() @@ -425,14 +432,13 @@ def volumes(self, *args, **kwargs) -> ndarray: elif NDIM == 3: coords = self.source_coords() topo = self.topology().to_numpy() - topo_tet = self.to_tetrahedra() - volumes = vol_tet_bulk(cells_coords(coords, topo_tet)) - res = np.sum( - volumes.reshape(topo.shape[0], int(len(volumes) / topo.shape[0])), - axis=1, + ecoords = cells_coords(coords, topo) + quad: Quadrature = next( + self._parse_gauss_data(self.Geometry.quadrature, "geometry") ) - return res - else: + dshp = self.Geometry.shape_function_derivatives(quad.pos) + return cell_measures(ecoords, dshp, quad.weight) + else: # pragma: no cover raise NotImplementedError def source_points(self) -> PointCloud: diff --git a/src/sigmaepsilon/mesh/utils/cells/utils.py b/src/sigmaepsilon/mesh/utils/cells/utils.py index 6fdf668..d584147 100644 --- a/src/sigmaepsilon/mesh/utils/cells/utils.py +++ b/src/sigmaepsilon/mesh/utils/cells/utils.py @@ -8,17 +8,17 @@ @njit(nogil=True, parallel=True, fastmath=True, cache=__cache) -def volumes(ecoords: ndarray, dshp: ndarray, qweight: ndarray) -> ndarray: +def cell_measures(ecoords: ndarray, dshp: ndarray, qweight: ndarray) -> ndarray: nE = ecoords.shape[0] - volumes = np.zeros(nE, dtype=ecoords.dtype) - nQ = len(qweight) + res = np.zeros(nE, dtype=ecoords.dtype) + nQ = qweight.shape[0] for iQ in range(nQ): _dshp = dshp[iQ] - for i in prange(nE): - jac = ecoords[i].T @ _dshp + for iE in prange(nE): + jac = ecoords[iE].T @ _dshp djac = np.linalg.det(jac) - volumes[i] += qweight[iQ] * djac - return volumes + res[iE] += qweight[iQ] * djac + return res @njit(nogil=True, parallel=True, cache=__cache) From 2803d32ebe2c54f136c9301cd79ab59b20c82640 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 16:43:53 +0100 Subject: [PATCH 34/47] refactored polycell class --- src/sigmaepsilon/mesh/data/akwrapper.py | 4 +- src/sigmaepsilon/mesh/data/celldata.py | 153 ++---------- src/sigmaepsilon/mesh/data/polycell.py | 294 +++++++++++++++++++++--- src/sigmaepsilon/mesh/data/polydata.py | 22 +- src/sigmaepsilon/mesh/typing/data.py | 15 ++ 5 files changed, 303 insertions(+), 185 deletions(-) diff --git a/src/sigmaepsilon/mesh/data/akwrapper.py b/src/sigmaepsilon/mesh/data/akwrapper.py index d6d07d2..dd6a118 100644 --- a/src/sigmaepsilon/mesh/data/akwrapper.py +++ b/src/sigmaepsilon/mesh/data/akwrapper.py @@ -74,7 +74,7 @@ def to_dataframe(self, *args, fields: Iterable[str] = None, **kwargs): def to_parquet( self, path: str, *args, fields: Iterable[str] = None, **kwargs - ) -> Any: + ) -> None: """ Saves the data of the database to a parquet file. @@ -227,7 +227,7 @@ def to_list(self, *args, fields: Iterable[str] = None) -> list: res = db.to_list() return res - def __len__(self): + def __len__(self) -> int: return len(self._wrapped) def __hasattr__(self, attr): diff --git a/src/sigmaepsilon/mesh/data/celldata.py b/src/sigmaepsilon/mesh/data/celldata.py index 71e9b16..791d905 100644 --- a/src/sigmaepsilon/mesh/data/celldata.py +++ b/src/sigmaepsilon/mesh/data/celldata.py @@ -1,23 +1,16 @@ from typing import Union, Iterable, Generic, TypeVar, Optional from copy import deepcopy -import contextlib import numpy as np from numpy import ndarray from sigmaepsilon.core import classproperty -from sigmaepsilon.math import atleast2d, atleast3d, repeat -from sigmaepsilon.math.linalg.sparse import csr_matrix +from sigmaepsilon.math import atleast3d, repeat from sigmaepsilon.math.linalg import ReferenceFrame from .akwrapper import AkWrapper from ..typing import PolyDataProtocol, PointDataProtocol from .akwrapper import AwkwardLike -from ..utils import ( - avg_cell_data, - distribute_nodal_data_bulk, - distribute_nodal_data_sparse, -) PointDataLike = TypeVar("PointDataLike", bound=PointDataProtocol) PolyDataLike = TypeVar("PolyDataLike", bound=PolyDataProtocol) @@ -75,7 +68,6 @@ class CellData(Generic[PolyDataLike, PointDataLike], AkWrapper): def __init__( self, *args, - pointdata: Optional[Union[PointDataLike, None]] = None, wrap: Optional[Union[AwkwardLike, None]] = None, topo: Optional[Union[ndarray, None]] = None, fields: Optional[Union[dict, None]] = None, @@ -84,7 +76,6 @@ def __init__( areas: Optional[Union[ndarray, float, None]] = None, t: Optional[Union[ndarray, float, None]] = None, db: Optional[Union[AwkwardLike, None]] = None, - container: Optional[Union[PolyDataLike, None]] = None, i: Optional[Union[ndarray, None]] = None, **kwargs, ): @@ -124,9 +115,6 @@ def __init__( super().__init__(*args, wrap=wrap, fields=fields, **kwargs) - self._pointdata = pointdata - self._container = container - if self.db is not None: if frames is not None: if isinstance(frames, (ReferenceFrame, ndarray)): @@ -158,15 +146,7 @@ def __copy__(self, memo: dict = None) -> "CellData": db = copy_function(self.db) - pd = self.pointdata - pd_copy = None - if pd is not None: - if is_deep: - pd_copy = memo.get(id(pd), None) - if pd_copy is None: - pd_copy = copy_function(pd) - - result = cls(db=db, pointdata=pd_copy) + result = cls(db=db) if is_deep: memo[id(self)] = result @@ -205,6 +185,10 @@ def _dbkey_ndf_(cls) -> str: def _dbkey_id_(cls) -> str: return cls._attr_map_["id"] + @property + def has_nodes(self) -> bool: + return self._dbkey_nodes_ in self._wrapped.fields + @property def has_id(self) -> bool: return self._dbkey_id_ in self._wrapped.fields @@ -225,50 +209,6 @@ def has_areas(self) -> bool: def db(self) -> AwkwardLike: return self._wrapped - @property - def pointdata(self) -> PointDataLike: - """ - Returns the attached point database. This is what - the topology of the cells are referring to. - """ - return self._pointdata - - @pointdata.setter - def pointdata(self, value: PointDataLike): - """ - Sets the attached point database. This is what - the topology of the cells are referring to. - """ - if value is not None: - if not isinstance(value, PointDataProtocol): - raise TypeError("'value' must be a PointData instance") - self._pointdata = value - - @property - def pd(self) -> PointDataLike: - """ - Returns the attached point database. This is what - the topology of the cells are referring to. - """ - return self.pointdata - - @pd.setter - def pd(self, value: PointDataLike): - """Sets the attached pointdata.""" - self.pointdata = value - - @property - def container(self) -> PolyDataLike: - """Returns the container object of the block.""" - return self._container - - @container.setter - def container(self, value: PolyDataLike) -> None: - """Sets the container of the block.""" - if not isinstance(value, PolyDataProtocol): - raise TypeError("'value' must be a PolyData instance") - self._container = value - @property def fields(self) -> Iterable[str]: """Returns the fields in the database object.""" @@ -280,7 +220,7 @@ def nodes(self) -> ndarray: return self._wrapped[self._dbkey_nodes_].to_numpy() @nodes.setter - def nodes(self, value: ndarray): + def nodes(self, value: ndarray) -> None: """ Sets the topology of the cells. @@ -298,7 +238,7 @@ def frames(self) -> ndarray: return self._wrapped[self._dbkey_frames_].to_numpy() @frames.setter - def frames(self, value: Union[ReferenceFrame, ndarray]): + def frames(self, value: Union[ReferenceFrame, ndarray]) -> None: """ Sets local coordinate frames of the cells. @@ -353,7 +293,7 @@ def id(self) -> ndarray: return self._wrapped[self._dbkey_id_].to_numpy() @id.setter - def id(self, value: ndarray): + def id(self, value: ndarray) -> None: """ Sets global indices of the cells. @@ -362,7 +302,15 @@ def id(self, value: ndarray): value: numpy.ndarray An 1d integer array. """ - assert isinstance(value, ndarray) + if isinstance(value, int): + if len(self) == 1: + value = np.array([value,], dtype=int) + else: + raise ValueError(f"Expected an array, got {type(value)}") + + if not isinstance(value, ndarray): + raise TypeError(f"Expected ndarray, got {type(value)}") + self._wrapped[self._dbkey_id_] = value @property @@ -384,23 +332,6 @@ def activity(self, value: ndarray): value = np.full(len(self), value, dtype=bool) self._wrapped[self._dbkey_activity_] = value - def root(self) -> PolyDataLike: - """ - Returns the top level container of the model the block is - the part of. - """ - c = self.container - return None if c is None else c.root - - def source(self) -> PolyDataLike: - """ - Retruns the source of the cells. This is the PolyData block - that stores the PointData object the topology of the cells - are referring to. - """ - c = self.container - return None if c is None else c.source() - def __getattr__(self, attr): """ Modified for being able to fetch data from pointcloud. @@ -410,17 +341,6 @@ def __getattr__(self, attr): try: return getattr(self._wrapped, attr) - except AttributeError: - with contextlib.suppress(Exception): - if self.pointdata is not None: - if attr in self.pointdata.fields: - data = self.pointdata[attr].to_numpy() - topo = self.nodes - return avg_cell_data(data, topo) - - name = self.__class__.__name__ - raise AttributeError(f"'{name}' object has no attribute called {attr}") - except Exception: name = self.__class__.__name__ raise AttributeError(f"'{name}' object has no attribute called {attr}") @@ -446,40 +366,3 @@ def set_nodal_distribution_factors(self, factors: ndarray, key: str = None) -> N else: self._wrapped[key] = factors - def pull( - self, data: Union[str, ndarray], ndf: Union[ndarray, csr_matrix] = None - ) -> ndarray: - """ - Pulls data from the attached pointdata. The pulled data is either copied or - distributed according to a measure. - - Parameters - ---------- - data: str or numpy.ndarray - Either a field key to identify data in the database of the attached - PointData, or a NumPy array. - - See Also - -------- - :func:`~sigmaepsilon.mesh.utils.utils.distribute_nodal_data_bulk` - :func:`~sigmaepsilon.mesh.utils.utils.distribute_nodal_data_sparse` - """ - if isinstance(data, str): - pd = self.source().pd - nodal_data = pd[data].to_numpy() - else: - assert isinstance( - data, ndarray - ), "'data' must be a string or a NumPy array." - nodal_data = data - topo = self.nodes - if ndf is None: - ndf = np.ones_like(topo).astype(float) - if len(nodal_data.shape) == 1: - nodal_data = atleast2d(nodal_data, back=True) - if isinstance(ndf, ndarray): - d = distribute_nodal_data_bulk(nodal_data, topo, ndf) - else: - d = distribute_nodal_data_sparse(nodal_data, topo, self.id, ndf) - # nE, nNE, nDATA - return d diff --git a/src/sigmaepsilon/mesh/data/polycell.py b/src/sigmaepsilon/mesh/data/polycell.py index e102bbf..ba64a6d 100644 --- a/src/sigmaepsilon/mesh/data/polycell.py +++ b/src/sigmaepsilon/mesh/data/polycell.py @@ -12,6 +12,7 @@ Callable, ) from numbers import Number +from copy import deepcopy import numpy as np from numpy import ndarray @@ -20,10 +21,21 @@ from sigmaepsilon.math import atleast1d, atleast2d, atleastnd, ascont from sigmaepsilon.math.linalg import ReferenceFrame as FrameLike from sigmaepsilon.math.utils import to_range_1d +from sigmaepsilon.math.linalg.sparse import csr_matrix -from ..typing import ABC_PolyCell, PolyDataProtocol, PointDataProtocol, GeometryProtocol +from ..typing import ( + ABC_PolyCell, + PolyDataProtocol, + PointDataProtocol, + GeometryProtocol, + CellDataProtocol, +) from .celldata import CellData from ..space import PointCloud, CartesianFrame +from ..utils import ( + distribute_nodal_data_bulk, + distribute_nodal_data_sparse, +) from ..utils.utils import ( jacobian_matrix_bulk, jacobian_matrix_bulk_1d, @@ -73,11 +85,7 @@ __all__ = ["PolyCell"] -class PolyCell( - Generic[MeshDataLike, PointDataLike], - CellData[MeshDataLike, PointDataLike], - ABC_PolyCell, -): +class PolyCell(Generic[MeshDataLike, PointDataLike], ABC_PolyCell): """ A subclass of :class:`~sigmaepsilon.mesh.data.celldata.CellData` as a base class for all cell containers. The class should not be used directly, the main purpose @@ -87,12 +95,159 @@ class PolyCell( label: ClassVar[Optional[str]] = None Geometry: ClassVar[GeometryProtocol] + data_class: type = CellData[MeshDataLike, PointDataLike] + + def __init__( + self, + *args, + db: Optional[Union[CellData[MeshDataLike, PointDataLike], None]] = None, + pointdata: Optional[Union[PointDataLike, None]] = None, + container: Optional[Union[MeshDataLike, None]] = None, + **kwargs, + ): + if db is None: + db_class = self.__class__.data_class + db = db_class(*args, **kwargs) + + self._db = db + self._pointdata = pointdata + self._container = container + + super().__init__() + + @property + def db(self) -> CellDataProtocol[MeshDataLike, PointDataLike]: + """ + Returns the database of the block. + """ + return self._db + + @db.setter + def db(self, value: CellDataProtocol[MeshDataLike, PointDataLike]) -> None: + """ + Sets the database of the block. + """ + self._db = value + + @property + def pointdata(self) -> PointDataLike: + """ + Returns the hosting point database. This is what + the topology of the cells are referring to. + """ + return self._pointdata + + @pointdata.setter + def pointdata(self, value: PointDataLike) -> None: + """ + Sets the hosting point database. This is what + the topology of the cells are referring to. + """ + if value is not None: + if not isinstance(value, PointDataProtocol): + raise TypeError("'value' must be a PointData instance") + self._pointdata = value + + @property + def pd(self) -> PointDataLike: + """ + Returns the attached point database. This is what + the topology of the cells are referring to. + """ + return self.pointdata + + @pd.setter + def pd(self, value: PointDataLike) -> None: + """ + Sets the attached pointdata. + """ + self.pointdata = value + + @property + def container(self) -> MeshDataLike: + """ + Returns the container of the block. + """ + return self._container + + @container.setter + def container(self, value: MeshDataLike) -> None: + """ + Sets the container of the block. + """ + if not isinstance(value, PolyDataProtocol): + raise TypeError("'value' must be a PolyData instance") + self._container = value + + def __getattr__(self, name: str) -> Any: + if len(name) >= 7 and name[:7] == "_dbkey_": + return getattr(self.db, name) + elif hasattr(self.db, name): + return getattr(self.db, name) + else: + return super().__getattr__(name) + + def root(self) -> MeshDataLike: + """ + Returns the top level container of the model the block is + the part of. + """ + c = self.container + return None if c is None else c.root + + def source(self) -> MeshDataLike: + """ + Retruns the source of the cells. This is the PolyData block + that stores the PointData object the topology of the cells + are referring to. + """ + c = self.container + return None if c is None else c.source() + + def pull( + self, data: Union[str, ndarray], ndf: Union[ndarray, csr_matrix] = None + ) -> ndarray: + """ + Pulls data from the attached pointdata. The pulled data is either copied or + distributed according to a measure. + + Parameters + ---------- + data: str or numpy.ndarray + Either a field key to identify data in the database of the attached + PointData, or a NumPy array. + + See Also + -------- + :func:`~sigmaepsilon.mesh.utils.utils.distribute_nodal_data_bulk` + :func:`~sigmaepsilon.mesh.utils.utils.distribute_nodal_data_sparse` + """ + if isinstance(data, str): + pd = self.source().pd + nodal_data = pd[data].to_numpy() + else: + assert isinstance( + data, ndarray + ), "'data' must be a string or a NumPy array." + nodal_data = data + topo = self.nodes + if ndf is None: + ndf = np.ones_like(topo).astype(float) + if len(nodal_data.shape) == 1: + nodal_data = atleast2d(nodal_data, back=True) + if isinstance(ndf, ndarray): + d = distribute_nodal_data_bulk(nodal_data, topo, ndf) + else: + d = distribute_nodal_data_sparse(nodal_data, topo, self.id, ndf) + # nE, nNE, nDATA + return d + def _get_cell_slicer( self, cells: Optional[Union[int, Iterable[int]]] = None ) -> Union[Iterable[int], IndexExpression]: if isinstance(cells, Iterable): cells = atleast1d(cells) - conds = np.isin(cells, self.id) + conds = np.isin(cells, self.db.id) cells = atleast1d(cells[conds]) assert ( len(cells) > 0 @@ -143,30 +298,63 @@ def _parse_gauss_data(quad_dict: dict, key: Hashable) -> Iterable[Quadrature]: quad = Quadrature(x=qpos, w=qweight) yield quad - @CellData.frames.getter + @property def frames(self) -> ndarray: """ Returns local coordinate frames of the cells as a 3d NumPy float array, where the first axis runs along the cells of the block. """ - if not self.has_frames: + if not self.db.has_frames: if (nD := self.Geometry.number_of_spatial_dimensions) == 1: coords = self.source_coords() topo = self.topology().to_numpy() - self.frames = frames_of_lines(coords, topo) + self.db.frames = frames_of_lines(coords, topo) elif nD == 2: coords = self.source_coords() topo = self.topology().to_numpy() - self.frames = frames_of_surfaces(coords, topo) + self.db.frames = frames_of_surfaces(coords, topo) elif nD == 3: - self.frames = self.source_frame() + self.db.frames = self.source_frame() else: # pragma: no cover raise TypeError( "Invalid Geometry class. The 'number of spatial dimensions'" " must be 1, 2 or 3." ) - return super().frames + return self.db.frames + @frames.setter + def frames(self, value: Union[FrameLike, ndarray]) -> None: + self.db.frames=value + + def to_parquet(self, path: str, *args, fields: Iterable[str] = None, **kwargs) -> None: + """ + Saves the data of the database to a parquet file. + + Parameters + ---------- + *args: tuple, Optional + Positional arguments to specify fields. + path: str + Path of the file being created. + fields: Iterable[str], Optional + Valid field names to include in the parquet files. + **kwargs: dict, Optional + Keyword arguments forwarded to :func:`awkward.to_parquet`. + """ + self.db.to_parquet(path, *args, fields=fields, **kwargs) + + @classmethod + def from_parquet(cls, path: str) -> "PolyCell": + """ + Saves the data of the database to a parquet file. + + Parameters + ---------- + path: str + Path of the file being created. + """ + return cls(db=CellData.from_parquet(path)) + def to_triangles(self) -> ndarray: """ Returns the topology as a collection of T3 triangles, represented @@ -314,7 +502,7 @@ def flip(self) -> "PolyCell": Reverse the order of nodes of the topology. """ topo = self.topology().to_numpy() - self.nodes = np.flip(topo, axis=1) + self.db.nodes = np.flip(topo, axis=1) return self def measures(self, *args, **kwargs) -> ndarray: @@ -347,9 +535,8 @@ def thickness(self) -> ndarray: of 1.0 is returned for each cell. Only for 2d cells. """ if self.Geometry.number_of_spatial_dimensions == 2: - dbkey = self._dbkey_thickness_ - if dbkey in self.fields: - t = self.db[dbkey].to_numpy() + if self.db.has_thickness: + t = self.db.t else: t = np.ones(len(self), dtype=float) return t @@ -357,7 +544,9 @@ def thickness(self) -> ndarray: raise NotImplementedError("This is only for 2d cells") def length(self) -> float: - """Returns the total length of the cells in the block.""" + """ + Returns the total length of the cells in the block. + """ if self.Geometry.number_of_spatial_dimensions == 1: return np.sum(self.lengths()) else: @@ -395,9 +584,8 @@ def areas(self, *args, **kwargs) -> ndarray: """ NDIM: int = self.Geometry.number_of_spatial_dimensions if NDIM == 1: - areakey = self._dbkey_areas_ - if areakey in self.fields: - return self[areakey].to_numpy() + if self.db.has_areas: + return self.db.A else: return np.ones((len(self))) elif NDIM == 2: @@ -454,14 +642,14 @@ def source_coords(self) -> ndarray: if self.pointdata is not None: coords = self.pointdata.x else: - coords = self.container.source().coords() + coords = self.source().coords() return coords def source_frame(self) -> FrameLike: """ Returns the frame of the hosting pointcloud. """ - return self.container.source().frame + return self.source().frame def points_of_cells( self, @@ -538,12 +726,7 @@ def local_coordinates( frames = self.frames topo = self.topology().to_numpy() - - if self.pointdata is not None: - coords = self.pointdata.x - else: - coords = self.container.source().coords() - + coords = self.source_coords() res = points_of_cells(coords, topo, local_axes=frames, centralize=True) if self.Geometry.number_of_spatial_dimensions == 2: @@ -563,13 +746,16 @@ def topology(self) -> Union[TopologyArray, None]: the cells as either a :class:`~sigmaepsilon.mesh.topoarray.TopologyArray` or `None` if the topology is not specified yet. """ - key = self._dbkey_nodes_ - if key in self.fields: - return TopologyArray(self.nodes) + if self.db.has_nodes: + return TopologyArray(self.db.nodes) else: return None - def rewire(self, imap: MapLike = None, invert: bool = False) -> "PolyCell": + def rewire( + self, + imap: Optional[Union[MapLike, None]] = None, + invert: Optional[bool] = False, + ) -> "PolyCell": """ Rewires the topology of the block according to the mapping described by the argument `imap`. The mapping of the j-th node @@ -581,7 +767,7 @@ def rewire(self, imap: MapLike = None, invert: bool = False) -> "PolyCell": Parameters ---------- - imap: MapLike + imap: MapLike, Optional Mapping from old to new node indices (global to local). invert: bool, Optional If `True` the argument `imap` describes a local to global @@ -589,10 +775,10 @@ def rewire(self, imap: MapLike = None, invert: bool = False) -> "PolyCell": `imap` must be a `numpy` array. Default is False. """ if imap is None: - imap = self.source().pointdata.id + imap = self.db.source().pointdata.id topo = self.topology().to_array().astype(int) topo = rewire(topo, imap, invert=invert).astype(int) - self._wrapped[self._dbkey_nodes_] = topo + self.db.nodes = topo return self def glob_to_loc(self, x: Union[Iterable, ndarray]) -> ndarray: @@ -917,3 +1103,39 @@ def _rotate_(self, *args, **kwargs): .show(source_frame) ) self.frames = new_frames + + def __len__(self) -> int: + return len(self.db) + + def __deepcopy__(self, memo: dict) -> "PolyCell": + return self.__copy__(memo) + + def __copy__(self, memo: Optional[Union[dict, None]] = None) -> "PolyCell": + cls = type(self) + is_deep = memo is not None + + if is_deep: + copy_function = lambda x: deepcopy(x, memo) + else: + copy_function = lambda x: x + + db = copy_function(self.db) + + pd = self.pointdata + pd_copy = None + if pd is not None: + if is_deep: + pd_copy = memo.get(id(pd), None) + if pd_copy is None: + pd_copy = copy_function(pd) + + result = cls(db=db, pointdata=pd_copy) + if is_deep: + memo[id(self)] = result + + result_dict = result.__dict__ + for k, v in self.__dict__.items(): + if not k in result_dict: + setattr(result, k, copy_function(v)) + + return result diff --git a/src/sigmaepsilon/mesh/data/polydata.py b/src/sigmaepsilon/mesh/data/polydata.py index 43cf9e0..9f7f0cb 100644 --- a/src/sigmaepsilon/mesh/data/polydata.py +++ b/src/sigmaepsilon/mesh/data/polydata.py @@ -31,7 +31,6 @@ from .akwrapper import AkWrapper from .pointdata import PointData from .polycell import PolyCell -from .celldata import CellData from .polycell import PolyCell from ..space import CartesianFrame, PointCloud from ..indexmanager import IndexManager @@ -150,8 +149,8 @@ class PolyData(DeepDict, Generic[PointDataLike, PolyCellLike]): def __init__( self, - pd: Optional[Union[PointData, CellData]] = None, - cd: Optional[CellData] = None, + pd: Optional[Union[PointData, PolyCell, None]] = None, + cd: Optional[Union[PolyCell, None]] = None, *args, **kwargs, ): @@ -170,17 +169,16 @@ def __init__( if isinstance(pd, PointData): self.pointdata = pd - if isinstance(cd, CellData): + if isinstance(cd, PolyCell): self.celldata = cd - elif isinstance(pd, CellData): + elif isinstance(pd, PolyCell): self.celldata = pd if isinstance(cd, PointData): self.pointdata = cd - elif isinstance(cd, CellData): + elif isinstance(cd, PolyCell): self.celldata = cd pidkey = self.__class__._point_class_._dbkey_id_ - cidkey = CellData._dbkey_id_ if self.pointdata is not None: if self.pd.has_id: @@ -193,9 +191,9 @@ def __init__( self.pd.container = self if self.celldata is not None: - N = len(self.celldata) + N = len(self.celldata.db) GIDs = self.root.cim.generate_np(N) - self.cd[cidkey] = GIDs + self.cd.db.id = GIDs try: pd = self.source().pd except Exception: @@ -1896,15 +1894,15 @@ def plot( def __join_parent__(self, parent: DeepDict, key: Hashable = None) -> None: super().__join_parent__(parent, key) if self.celldata is not None: - GIDs = self.root.cim.generate_np(len(self.celldata)) - self.celldata.id = atleast1d(GIDs) + GIDs = self.root.cim.generate_np(len(self.celldata.db)) + self.celldata.db.id = atleast1d(GIDs) if self.celldata.pd is None: self.celldata.pd = self.source().pd self.celldata.container = self def __leave_parent__(self) -> None: if self.celldata is not None: - self.root.cim.recycle(self.celldata.id) + self.root.cim.recycle(self.celldata.db.id) dbkey = self.celldata._dbkey_id_ del self.celldata._wrapped[dbkey] super().__leave_parent__() diff --git a/src/sigmaepsilon/mesh/typing/data.py b/src/sigmaepsilon/mesh/typing/data.py index c7f11ea..6e4cb4b 100644 --- a/src/sigmaepsilon/mesh/typing/data.py +++ b/src/sigmaepsilon/mesh/typing/data.py @@ -81,6 +81,13 @@ def id(self) -> ndarray: def frames(self) -> ndarray: """Ought to return the reference frames of the cells.""" ... + + @property + def nodes(self) -> ndarray: + """ + Ought to return the topology of the cells as a 2d NumPy integer array. + """ + ... @property def pointdata(self) -> PointDataLike: @@ -89,6 +96,14 @@ def pointdata(self) -> PointDataLike: @property def container(self) -> PolyDataLike: """Returns the container object of the block.""" + + @property + def has_frames(self) -> bool: + """ + Ought to return `True` if the cells are equipped with frames, + `False` if they are not. + """ + ... class PolyCellProtocol( From ce9954d85bd396a757f45f4003447bae9370a27d Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 18:48:40 +0100 Subject: [PATCH 35/47] fixed 1d grid generation --- src/sigmaepsilon/mesh/grid.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sigmaepsilon/mesh/grid.py b/src/sigmaepsilon/mesh/grid.py index f48f6b3..606cf73 100644 --- a/src/sigmaepsilon/mesh/grid.py +++ b/src/sigmaepsilon/mesh/grid.py @@ -9,7 +9,7 @@ center_of_points, k_nearest_neighbours as knn, knn_to_lines, - xy_to_xyz, + coords_to_3d, ) __cache = True @@ -200,7 +200,7 @@ def grid( path = np.array(path, dtype=int) topo = transform_topology(topo, path) - return xy_to_xyz(coords), topo + return coords_to_3d(coords), topo def gridQ4(*args, **kwargs) -> Tuple[ndarray, ndarray]: @@ -227,7 +227,7 @@ def gridQ4(*args, **kwargs) -> Tuple[ndarray, ndarray]: """ coords, topo = grid(*args, eshape=(2, 2), **kwargs) path = np.array([0, 2, 3, 1], dtype=int) - return xy_to_xyz(coords), transform_topology(topo, path) + return coords_to_3d(coords), transform_topology(topo, path) def gridQ9(*args, **kwargs) -> Tuple[ndarray, ndarray]: @@ -241,7 +241,7 @@ def gridQ9(*args, **kwargs) -> Tuple[ndarray, ndarray]: """ coords, topo = grid(*args, eshape=(3, 3), **kwargs) path = np.array([0, 6, 8, 2, 3, 7, 5, 1, 4], dtype=int) - return xy_to_xyz(coords), transform_topology(topo, path) + return coords_to_3d(coords), transform_topology(topo, path) def gridQ8(*args, **kwargs) -> Tuple[ndarray, ndarray]: @@ -268,7 +268,7 @@ def gridH8(*args, **kwargs) -> Tuple[ndarray, ndarray]: """ coords, topo = grid(*args, eshape=(2, 2, 2), **kwargs) path = np.array([0, 4, 6, 2, 1, 5, 7, 3], dtype=int) - return xy_to_xyz(coords), transform_topology(topo, path) + return coords_to_3d(coords), transform_topology(topo, path) # fmt: off @@ -412,8 +412,8 @@ def rgridMT(size, shape, eshape, shift, start=0): nDime = len(size) if nDime == 1: lX = size[0] - ndivX = shape - nNodeX = eshape + ndivX = shape[0] + nNodeX = eshape[0] nX = ndivX * (nNodeX - 1) + 1 dX = lX / ndivX ddX = dX / (nNodeX - 1) From d8cd68d93a2218e8e2e7bd99d92d76fe536b73e6 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 18:49:01 +0100 Subject: [PATCH 36/47] bugfix, typehints and docstrings --- src/sigmaepsilon/mesh/data/polycell.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sigmaepsilon/mesh/data/polycell.py b/src/sigmaepsilon/mesh/data/polycell.py index ba64a6d..9f37cc8 100644 --- a/src/sigmaepsilon/mesh/data/polycell.py +++ b/src/sigmaepsilon/mesh/data/polycell.py @@ -95,7 +95,7 @@ class PolyCell(Generic[MeshDataLike, PointDataLike], ABC_PolyCell): label: ClassVar[Optional[str]] = None Geometry: ClassVar[GeometryProtocol] - data_class: type = CellData[MeshDataLike, PointDataLike] + data_class: ClassVar[type] = CellData[MeshDataLike, PointDataLike] def __init__( self, @@ -660,9 +660,10 @@ def points_of_cells( ) -> ndarray: """ Returns the points of selected cells as a NumPy array. The returned - array is three dimensional with a shape of (nE, nNE, 2), where `nE` is - the number of cells in the block, `nNE` is the number of nodes per cell - and 2 stands for the 2 spatial dimensions. + array is three dimensional with a shape of (nE, nNE, nD), where `nE` is + the number of cells in the block, `nNE` is the number of nodes per cell or + the number of the points (if 'points' is specified) and nD stands for the + number of spatial dimensions. Parameters ---------- @@ -775,7 +776,7 @@ def rewire( `imap` must be a `numpy` array. Default is False. """ if imap is None: - imap = self.db.source().pointdata.id + imap = self.source().pointdata.id topo = self.topology().to_array().astype(int) topo = rewire(topo, imap, invert=invert).astype(int) self.db.nodes = topo From f031b3d8a0fb2042324aad778044fd167eadf150 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 18:49:17 +0100 Subject: [PATCH 37/47] renamed function --- src/sigmaepsilon/mesh/domains/section.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sigmaepsilon/mesh/domains/section.py b/src/sigmaepsilon/mesh/domains/section.py index 0d9386d..82c9641 100644 --- a/src/sigmaepsilon/mesh/domains/section.py +++ b/src/sigmaepsilon/mesh/domains/section.py @@ -23,7 +23,7 @@ from ..cells import T3 from ..data import PointData from ..space import CartesianFrame -from ..utils import xy_to_xyz +from ..utils import coords_to_3d __all__ = ["generate_mesh", "get_section", "LineSection"] @@ -307,7 +307,7 @@ def trimesh(self, subdivide: bool = False, order: int = 1, **kwargs) -> TriMesh: >>> section = BeamSection(get_section('CHS', d=1.0, t=0.1, n=64)) >>> trimesh = section.trimesh() """ - points, triangles = xy_to_xyz(self.coords()), self.topology() + points, triangles = coords_to_3d(self.coords()), self.topology() if order == 1: if subdivide: path = np.array([[0, 5, 4], [5, 1, 3], [3, 2, 4], [5, 3, 4]], dtype=int) From 27e50079d7960af3050530a38852931bf6d3da82 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 18:49:27 +0100 Subject: [PATCH 38/47] renamed function --- src/sigmaepsilon/mesh/utils/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sigmaepsilon/mesh/utils/utils.py b/src/sigmaepsilon/mesh/utils/utils.py index 3d84366..78bd710 100644 --- a/src/sigmaepsilon/mesh/utils/utils.py +++ b/src/sigmaepsilon/mesh/utils/utils.py @@ -1060,7 +1060,7 @@ def global_shape_function_derivatives(dshp: ndarray, jac: ndarray) -> ndarray: return res -def xy_to_xyz(x: ndarray) -> ndarray: +def coords_to_3d(x: ndarray) -> ndarray: x = atleast2d(x, back=True) if (N := x.shape[-1]) == 3: return x @@ -1069,5 +1069,5 @@ def xy_to_xyz(x: ndarray) -> ndarray: if N == 2: res[:, :2] = x elif N == 1: - res[:, 0] = x + res[:, 0] = x.flatten() return res From 0a1f31ba0234a5f8427b7723123eff41ed66e76a Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 18:49:37 +0100 Subject: [PATCH 39/47] added tests --- tests/cells/test_polycell.py | 134 ++++++++++++++++++++++++++++++++ tests/cells/test_polycell_1d.py | 93 ++++++++++++++++++++++ tests/test_recipes.py | 8 ++ 3 files changed, 235 insertions(+) create mode 100644 tests/cells/test_polycell.py create mode 100644 tests/cells/test_polycell_1d.py diff --git a/tests/cells/test_polycell.py b/tests/cells/test_polycell.py new file mode 100644 index 0000000..39db4a2 --- /dev/null +++ b/tests/cells/test_polycell.py @@ -0,0 +1,134 @@ +import unittest, doctest + +import numpy as np + +from sigmaepsilon.core.testing import SigmaEpsilonTestCase +import sigmaepsilon.mesh +from sigmaepsilon.mesh import PolyData, PointData, LineData +from sigmaepsilon.mesh.space import CartesianFrame +from sigmaepsilon.mesh.cells import H8, TET4, L2 +from sigmaepsilon.mesh.utils.topology import H8_to_TET4, H8_to_L2 +from sigmaepsilon.mesh.utils.space import frames_of_lines +from sigmaepsilon.mesh.grid import grid as _grid + + +def load_tests(loader, tests, ignore): # pragma: no cover + tests.addTests(doctest.DocTestSuite(sigmaepsilon.mesh.cells)) + return tests + + +class TestPolyCell(SigmaEpsilonTestCase): + def test_polycell(self): + size = 10, 10, 5 + shape = 2, 2, 2 + coords, topo = _grid(size=size, shape=shape, eshape="H8") + pd = PointData(coords=coords) + cd = H8(topo=topo) + grid = PolyData(pd, cd) + grid.centralize() + + coords = grid.coords() + topo = grid.topology().to_numpy() + centers = grid.centers() + + b_left = centers[:, 0] < 0 + b_right = centers[:, 0] >= 0 + b_front = centers[:, 1] >= 0 + b_back = centers[:, 1] < 0 + iTET4 = np.where(b_left)[0] + iH8 = np.where(b_right & b_back)[0] + iL2 = np.where(b_right & b_front)[0] + _, tTET4 = H8_to_TET4(coords, topo[iTET4]) + _, tL2 = H8_to_L2(coords, topo[iL2]) + tH8 = topo[iH8] + + # crate supporting pointcloud + frame = CartesianFrame(dim=3) + pd = PointData(coords=coords, frame=frame) + mesh = PolyData(pd, frame=frame) + + # tetrahedra + cdTET4 = TET4(topo=tTET4) + mesh["tetra"] = PolyData(cdTET4, frame=frame) + mesh["tetra"].config["A", "color"] = "green" + + # hexahedra + cdH8 = H8(topo=tH8) + mesh["hex"] = PolyData(cdH8, frame=frame) + mesh["hex"].config["A", "color"] = "blue" + + # lines + cdL2 = L2(topo=tL2) + mesh["line"] = LineData(cdL2, frame=frame) + mesh["line"].config["A", "color"] = "red" + mesh["line"].config["A", "line_width"] = 3 + mesh["line"].config["A", "render_lines_as_tubes"] = True + + # finalize the mesh and lock the layout + mesh.to_standard_form() + mesh.lock(create_mappers=True) + + cdL2.db = cdL2.db + cdL2._dbkey_id_ + + cdL2.flip().flip() + cdH8.flip().flip() + cdTET4.flip().flip() + + cdL2._get_points_and_range() + cdH8._get_points_and_range() + cdTET4._get_points_and_range() + + cdL2.points_of_cells() + cdL2.points_of_cells(points=[-1.0, 1.0], rng=[-1, 1]) + cdH8.points_of_cells() + cdTET4.points_of_cells() + + cdL2.local_coordinates() + cdL2.local_coordinates(target=frame) + cdH8.local_coordinates() + cdH8.local_coordinates(target=frame) + cdTET4.local_coordinates() + cdTET4.local_coordinates(target=frame) + + cdL2.rewire() + cdL2.rewire(imap=pd.id) + cdH8.rewire() + cdH8.rewire(imap=pd.id) + cdTET4.rewire() + cdTET4.rewire(imap=pd.id) + + self.assertTrue(np.allclose(cdL2.centers(), cdL2.centers(target=frame))) + self.assertTrue(np.allclose(cdH8.centers(), cdH8.centers(target=frame))) + self.assertTrue(np.allclose(cdTET4.centers(), cdTET4.centers(target=frame))) + + self.assertEqual(cdL2.root(), mesh) + self.assertEqual(cdH8.root(), mesh) + self.assertEqual(cdTET4.root(), mesh) + + self.assertTrue(np.allclose(cdL2.lengths(), cdL2.measures())) + self.assertTrue(np.allclose(cdH8.volumes(), cdH8.measures())) + self.assertTrue(np.allclose(cdTET4.volumes(), cdTET4.measures())) + + self.assertTrue(np.isclose(cdL2.length(), cdL2.measure())) + self.assertTrue(np.isclose(cdH8.volume(), cdH8.measure())) + self.assertTrue(np.isclose(cdTET4.volume(), cdTET4.measure())) + + self.assertEqual(cdL2.container, mesh["line"]) + self.assertEqual(cdH8.container, mesh["hex"]) + self.assertEqual(cdTET4.container, mesh["tetra"]) + + self.assertEqual(len(cdL2.jacobian()), len(cdL2)) + self.assertEqual(len(cdH8.jacobian()), len(cdH8)) + self.assertEqual(len(cdTET4.jacobian()), len(cdTET4)) + + self.assertEqual(len(cdL2), len(cdL2.frames)) + + self.assertRaises(TypeError, setattr, cdL2, "pointdata", 1) + self.assertRaises(NotImplementedError, cdL2.to_triangles) + self.assertRaises(NotImplementedError, cdL2.thickness) + self.assertRaises(NotImplementedError, cdH8.thickness) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/cells/test_polycell_1d.py b/tests/cells/test_polycell_1d.py new file mode 100644 index 0000000..c45b31c --- /dev/null +++ b/tests/cells/test_polycell_1d.py @@ -0,0 +1,93 @@ +import unittest + +import numpy as np +from sympy import symbols + +from sigmaepsilon.core.testing import SigmaEpsilonTestCase +from sigmaepsilon.mesh.data import PolyCell +from sigmaepsilon.mesh import PolyData, PointData, CartesianFrame, grid +from sigmaepsilon.mesh.helpers import vtk_to_celltype + + +class TestPolyCell1d(SigmaEpsilonTestCase): + def _test_polycell_1d_single_evaluation(self, CellData: PolyCell[PolyData, PointData]): + """ + Tests the cells for a single point of evaluation. + """ + nNE = CellData.Geometry.number_of_nodes + nD = CellData.Geometry.number_of_spatial_dimensions + self.assertEqual(nD, 1) + + gridparams = { + "size": (1,), + "shape": (2,), + "eshape": (nNE,), + } + coords, topo = grid(**gridparams) + frame = CartesianFrame(dim=3) + + pd = PointData(coords=coords, frame=frame) + cd: PolyCell[PolyData, PointData] = CellData(topo=topo, frames=frame) + + _ = PolyData(pd, cd) + + self.assertTrue(np.isclose(cd.length(), 1.0)) + + shp, dshp, shpf, shpmf, dshpf = CellData.Geometry.generate_class_functions( + return_symbolic=True + ) + r = symbols("r", real=True) + + x_loc = np.random.rand(1) + + shpA = shpf(x_loc) + shpB = CellData.Geometry.shape_function_values(x_loc) + shp_sym = shp.subs({r: x_loc[0]}) + self.assertTrue(np.allclose(shpA, shpB)) + self.assertTrue(np.allclose(shpA, np.array(shp_sym.tolist(), dtype=float).T)) + + dshpA = dshpf(x_loc) + dshpB = CellData.Geometry.shape_function_derivatives(x_loc) + dshp_sym = dshp.subs({r: x_loc[0]}) + self.assertTrue(np.allclose(dshpA, dshpB)) + self.assertTrue(np.allclose(dshpA, np.array(dshp_sym.tolist(), dtype=float))) + + shpmfA = shpmf(x_loc) + shpmfB = CellData.Geometry.shape_function_matrix(x_loc) + self.assertTrue(np.allclose(shpmfA, shpmfB)) + + mc = CellData.Geometry.master_coordinates() + shp = CellData.Geometry.shape_function_values(mc) + self.assertTrue(np.allclose(np.diag(shp), np.ones((nNE)))) + + nX = 2 + shpmf = CellData.Geometry.shape_function_matrix(x_loc, N=nX) + self.assertEqual(shpmf.shape, (1, nX, nNE * nX)) + + def _test_master_cell_1d(self, CellData: PolyCell[PolyData, PointData]): + nNE = CellData.Geometry.number_of_nodes + nD = CellData.Geometry.number_of_spatial_dimensions + self.assertEqual(nD, 1) + + frame = CartesianFrame() + coords = np.zeros((nNE, 3), dtype=float) + coords[:, 0] = CellData.Geometry.master_coordinates() + topo = np.array([list(range(nNE))], dtype=int) + pd = PointData(coords=coords, frame=frame) + cd: PolyCell[PolyData, PointData] = CellData(topo=topo, frames=frame) + _ = PolyData(pd, cd) + self.assertTrue(np.isclose(cd.length(), 2.0)) + self.assertTrue(np.allclose(cd.jacobian(), np.ones((1, nNE)))) + + def test_cells_1d(self): + cells = filter( + lambda c: c.Geometry.number_of_spatial_dimensions == 1, + vtk_to_celltype.values(), + ) + for cell in cells: + self._test_polycell_1d_single_evaluation(cell) + self._test_master_cell_1d(cell) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_recipes.py b/tests/test_recipes.py index e3aa5dc..a6d1ff3 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -7,6 +7,7 @@ ribbed_plate, perforated_cube, cylinder, + circular_helix, ) @@ -60,6 +61,13 @@ def test_cylinder(self): cyl = cylinder(shape, size, voxelize=False) self.assertTrue(np.isclose(cyl.volume(), vol, rtol=1e-2, atol=1e-2)) + + def test_circular_helix(self): + fnc = circular_helix(1, 5) + fnc(1) + + fnc = circular_helix(slope=1, pitch=5) + fnc(1) if __name__ == "__main__": From de9bd8b0816e957a24dc739068c8084e2b453c19 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Wed, 1 Nov 2023 20:56:11 +0100 Subject: [PATCH 40/47] added abstract class --- src/sigmaepsilon/mesh/data/celldata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sigmaepsilon/mesh/data/celldata.py b/src/sigmaepsilon/mesh/data/celldata.py index 791d905..815c9fe 100644 --- a/src/sigmaepsilon/mesh/data/celldata.py +++ b/src/sigmaepsilon/mesh/data/celldata.py @@ -10,6 +10,7 @@ from .akwrapper import AkWrapper from ..typing import PolyDataProtocol, PointDataProtocol +from ..typing.abcakwrapper import ABC_AkWrapper from .akwrapper import AwkwardLike PointDataLike = TypeVar("PointDataLike", bound=PointDataProtocol) @@ -19,7 +20,7 @@ __all__ = ["CellData"] -class CellData(Generic[PolyDataLike, PointDataLike], AkWrapper): +class CellData(Generic[PolyDataLike, PointDataLike], AkWrapper, ABC_AkWrapper): """ A class to handle data related to the cells of a polygonal mesh. From 0ca0856634f99687dc02d2ac47b6101d41dc9719 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 2 Nov 2023 21:53:24 +0100 Subject: [PATCH 41/47] updated numerical integration --- src/sigmaepsilon/mesh/cells/h27.py | 2 +- src/sigmaepsilon/mesh/cells/h8.py | 2 +- src/sigmaepsilon/mesh/cells/l2.py | 2 +- src/sigmaepsilon/mesh/cells/l3.py | 2 +- src/sigmaepsilon/mesh/cells/q4.py | 2 +- src/sigmaepsilon/mesh/cells/q8.py | 2 +- src/sigmaepsilon/mesh/cells/q9.py | 2 +- src/sigmaepsilon/mesh/cells/t3.py | 2 +- src/sigmaepsilon/mesh/cells/t6.py | 4 +- src/sigmaepsilon/mesh/cells/tet10.py | 2 +- src/sigmaepsilon/mesh/cells/tet4.py | 2 +- src/sigmaepsilon/mesh/cells/w18.py | 2 +- src/sigmaepsilon/mesh/cells/w6.py | 2 +- src/sigmaepsilon/mesh/data/polycell.py | 33 +- src/sigmaepsilon/mesh/utils/cells/numint.py | 173 ---------- src/sigmaepsilon/mesh/utils/numint.py | 345 ++++++++++++++++++++ src/sigmaepsilon/mesh/utils/tet.py | 27 +- src/sigmaepsilon/mesh/utils/tri.py | 100 ++++-- src/sigmaepsilon/mesh/utils/utils.py | 30 +- tests/cells/test_tri.py | 21 +- tests/test_numint.py | 83 +++++ 21 files changed, 597 insertions(+), 243 deletions(-) delete mode 100644 src/sigmaepsilon/mesh/utils/cells/numint.py create mode 100644 src/sigmaepsilon/mesh/utils/numint.py create mode 100644 tests/test_numint.py diff --git a/src/sigmaepsilon/mesh/cells/h27.py b/src/sigmaepsilon/mesh/cells/h27.py index a8726fd..8f46704 100644 --- a/src/sigmaepsilon/mesh/cells/h27.py +++ b/src/sigmaepsilon/mesh/cells/h27.py @@ -13,7 +13,7 @@ shape_function_matrix_H27_multi, monoms_H27, ) -from ..utils.cells.numint import Gauss_Legendre_Hex_Grid +from ..utils.numint import Gauss_Legendre_Hex_Grid class H27(PolyCell): diff --git a/src/sigmaepsilon/mesh/cells/h8.py b/src/sigmaepsilon/mesh/cells/h8.py index b7eee9d..97ab111 100644 --- a/src/sigmaepsilon/mesh/cells/h8.py +++ b/src/sigmaepsilon/mesh/cells/h8.py @@ -13,7 +13,7 @@ shape_function_matrix_H8_multi, monoms_H8, ) -from ..utils.cells.numint import Gauss_Legendre_Hex_Grid +from ..utils.numint import Gauss_Legendre_Hex_Grid class H8(PolyCell): diff --git a/src/sigmaepsilon/mesh/cells/l2.py b/src/sigmaepsilon/mesh/cells/l2.py index 686ddad..2a77d7b 100644 --- a/src/sigmaepsilon/mesh/cells/l2.py +++ b/src/sigmaepsilon/mesh/cells/l2.py @@ -9,7 +9,7 @@ shape_function_matrix_L2_multi, monoms_L2, ) -from ..utils.cells.numint import Gauss_Legendre_Line_Grid +from ..utils.numint import Gauss_Legendre_Line_Grid __all__ = ["L2"] diff --git a/src/sigmaepsilon/mesh/cells/l3.py b/src/sigmaepsilon/mesh/cells/l3.py index 779dd17..aadf469 100644 --- a/src/sigmaepsilon/mesh/cells/l3.py +++ b/src/sigmaepsilon/mesh/cells/l3.py @@ -3,7 +3,7 @@ from ..geometry import PolyCellGeometry1d from ..data.polycell import PolyCell -from ..utils.cells.numint import Gauss_Legendre_Line_Grid +from ..utils.numint import Gauss_Legendre_Line_Grid from ..utils.cells.l3 import monoms_L3 diff --git a/src/sigmaepsilon/mesh/cells/q4.py b/src/sigmaepsilon/mesh/cells/q4.py index d57a005..15c0392 100644 --- a/src/sigmaepsilon/mesh/cells/q4.py +++ b/src/sigmaepsilon/mesh/cells/q4.py @@ -13,7 +13,7 @@ shape_function_matrix_Q4_multi, monoms_Q4, ) -from ..utils.cells.numint import Gauss_Legendre_Quad_4 +from ..utils.numint import Gauss_Legendre_Quad_4 from ..utils.topology import Q4_to_T3 diff --git a/src/sigmaepsilon/mesh/cells/q8.py b/src/sigmaepsilon/mesh/cells/q8.py index 0ab841e..e25544d 100644 --- a/src/sigmaepsilon/mesh/cells/q8.py +++ b/src/sigmaepsilon/mesh/cells/q8.py @@ -12,7 +12,7 @@ shape_function_matrix_Q8_multi, monoms_Q8, ) -from ..utils.cells.numint import Gauss_Legendre_Quad_9 +from ..utils.numint import Gauss_Legendre_Quad_9 from ..utils.topology import Q8_to_T3, trimap_Q8 diff --git a/src/sigmaepsilon/mesh/cells/q9.py b/src/sigmaepsilon/mesh/cells/q9.py index 0980762..5d89487 100644 --- a/src/sigmaepsilon/mesh/cells/q9.py +++ b/src/sigmaepsilon/mesh/cells/q9.py @@ -12,7 +12,7 @@ shape_function_matrix_Q9_multi, monoms_Q9, ) -from ..utils.cells.numint import Gauss_Legendre_Quad_9 +from ..utils.numint import Gauss_Legendre_Quad_9 from ..utils.topology import Q4_to_T3, Q9_to_Q4 diff --git a/src/sigmaepsilon/mesh/cells/t3.py b/src/sigmaepsilon/mesh/cells/t3.py index 778060a..2fbf082 100644 --- a/src/sigmaepsilon/mesh/cells/t3.py +++ b/src/sigmaepsilon/mesh/cells/t3.py @@ -7,7 +7,7 @@ from ..geometry import PolyCellGeometry2d from ..data.polycell import PolyCell -from ..utils.cells.numint import Gauss_Legendre_Tri_1 +from ..utils.numint import Gauss_Legendre_Tri_1 from ..utils.cells.t3 import ( shp_T3_multi, dshp_T3_multi, diff --git a/src/sigmaepsilon/mesh/cells/t6.py b/src/sigmaepsilon/mesh/cells/t6.py index f1c04d2..6899462 100644 --- a/src/sigmaepsilon/mesh/cells/t6.py +++ b/src/sigmaepsilon/mesh/cells/t6.py @@ -7,15 +7,13 @@ from ..geometry import PolyCellGeometry2d from ..data.polycell import PolyCell -from ..utils.utils import cells_coords from ..utils.cells.t6 import ( shp_T6_multi, dshp_T6_multi, - areas_T6, shape_function_matrix_T6_multi, monoms_T6, ) -from ..utils.cells.numint import Quadrature, Gauss_Legendre_Tri_3a +from ..utils.numint import Gauss_Legendre_Tri_3a from ..utils.topology import T6_to_T3, T3_to_T6 diff --git a/src/sigmaepsilon/mesh/cells/tet10.py b/src/sigmaepsilon/mesh/cells/tet10.py index 5080507..f135ec1 100644 --- a/src/sigmaepsilon/mesh/cells/tet10.py +++ b/src/sigmaepsilon/mesh/cells/tet10.py @@ -9,7 +9,7 @@ from ..utils.cells.tet10 import ( monoms_TET10, ) -from ..utils.cells.numint import Gauss_Legendre_Tet_4 +from ..utils.numint import Gauss_Legendre_Tet_4 class TET10(PolyCell): diff --git a/src/sigmaepsilon/mesh/cells/tet4.py b/src/sigmaepsilon/mesh/cells/tet4.py index 304b61b..9d14851 100644 --- a/src/sigmaepsilon/mesh/cells/tet4.py +++ b/src/sigmaepsilon/mesh/cells/tet4.py @@ -13,7 +13,7 @@ shape_function_matrix_TET4_multi, monoms_TET4, ) -from ..utils.cells.numint import Gauss_Legendre_Tet_1 +from ..utils.numint import Gauss_Legendre_Tet_1 from ..utils.tet import vol_tet_bulk from ..utils.utils import cells_coords diff --git a/src/sigmaepsilon/mesh/cells/w18.py b/src/sigmaepsilon/mesh/cells/w18.py index 206344f..8ec22d2 100644 --- a/src/sigmaepsilon/mesh/cells/w18.py +++ b/src/sigmaepsilon/mesh/cells/w18.py @@ -6,7 +6,7 @@ from ..geometry import PolyCellGeometry3d from ..data.polycell import PolyCell -from ..utils.cells.numint import Gauss_Legendre_Wedge_3x3 +from ..utils.numint import Gauss_Legendre_Wedge_3x3 from ..utils.cells.w18 import monoms_W18 from ..utils.topology import compose_trmap from .w6 import W6 diff --git a/src/sigmaepsilon/mesh/cells/w6.py b/src/sigmaepsilon/mesh/cells/w6.py index e756e73..b8c0bf4 100644 --- a/src/sigmaepsilon/mesh/cells/w6.py +++ b/src/sigmaepsilon/mesh/cells/w6.py @@ -6,7 +6,7 @@ from ..geometry import PolyCellGeometry3d from ..data.polycell import PolyCell -from ..utils.cells.numint import Gauss_Legendre_Wedge_3x2 +from ..utils.numint import Gauss_Legendre_Wedge_3x2 from ..utils.cells.w6 import monoms_W6 diff --git a/src/sigmaepsilon/mesh/data/polycell.py b/src/sigmaepsilon/mesh/data/polycell.py index 9f37cc8..d9539c2 100644 --- a/src/sigmaepsilon/mesh/data/polycell.py +++ b/src/sigmaepsilon/mesh/data/polycell.py @@ -71,7 +71,7 @@ from ..vtkutils import mesh_to_UnstructuredGrid as mesh_to_vtk from ..topoarray import TopologyArray from ..space import CartesianFrame -from ..utils.cells.numint import Quadrature +from ..utils.numint import Quadrature from ..config import __haspyvista__ if __haspyvista__: @@ -108,11 +108,11 @@ def __init__( if db is None: db_class = self.__class__.data_class db = db_class(*args, **kwargs) - + self._db = db self._pointdata = pointdata self._container = container - + super().__init__() @property @@ -186,7 +186,7 @@ def __getattr__(self, name: str) -> Any: return getattr(self.db, name) else: return super().__getattr__(name) - + def root(self) -> MeshDataLike: """ Returns the top level container of the model the block is @@ -203,7 +203,7 @@ def source(self) -> MeshDataLike: """ c = self.container return None if c is None else c.source() - + def pull( self, data: Union[str, ndarray], ndf: Union[ndarray, csr_matrix] = None ) -> ndarray: @@ -324,9 +324,11 @@ def frames(self) -> ndarray: @frames.setter def frames(self, value: Union[FrameLike, ndarray]) -> None: - self.db.frames=value - - def to_parquet(self, path: str, *args, fields: Iterable[str] = None, **kwargs) -> None: + self.db.frames = value + + def to_parquet( + self, path: str, *args, fields: Iterable[str] = None, **kwargs + ) -> None: """ Saves the data of the database to a parquet file. @@ -342,7 +344,7 @@ def to_parquet(self, path: str, *args, fields: Iterable[str] = None, **kwargs) - Keyword arguments forwarded to :func:`awkward.to_parquet`. """ self.db.to_parquet(path, *args, fields=fields, **kwargs) - + @classmethod def from_parquet(cls, path: str) -> "PolyCell": """ @@ -354,7 +356,7 @@ def from_parquet(cls, path: str) -> "PolyCell": Path of the file being created. """ return cls(db=CellData.from_parquet(path)) - + def to_triangles(self) -> ndarray: """ Returns the topology as a collection of T3 triangles, represented @@ -662,7 +664,7 @@ def points_of_cells( Returns the points of selected cells as a NumPy array. The returned array is three dimensional with a shape of (nE, nNE, nD), where `nE` is the number of cells in the block, `nNE` is the number of nodes per cell or - the number of the points (if 'points' is specified) and nD stands for the + the number of the points (if 'points' is specified) and nD stands for the number of spatial dimensions. Parameters @@ -728,7 +730,10 @@ def local_coordinates( topo = self.topology().to_numpy() coords = self.source_coords() - res = points_of_cells(coords, topo, local_axes=frames, centralize=True) + centers = self.loc_to_glob(self.Geometry.master_center()) + res = points_of_cells( + coords, topo, local_axes=frames, centralize=True, centers=centers + ) if self.Geometry.number_of_spatial_dimensions == 2: return ascont(res[:, :, :2]) @@ -1104,10 +1109,10 @@ def _rotate_(self, *args, **kwargs): .show(source_frame) ) self.frames = new_frames - + def __len__(self) -> int: return len(self.db) - + def __deepcopy__(self, memo: dict) -> "PolyCell": return self.__copy__(memo) diff --git a/src/sigmaepsilon/mesh/utils/cells/numint.py b/src/sigmaepsilon/mesh/utils/cells/numint.py deleted file mode 100644 index af98a5c..0000000 --- a/src/sigmaepsilon/mesh/utils/cells/numint.py +++ /dev/null @@ -1,173 +0,0 @@ -from typing import Tuple, Iterable -from numbers import Number - -import numpy as np -from numpy import ndarray - -from sigmaepsilon.math.numint import gauss_points as gp - - -class Quadrature: - - def __init__(self, x: Iterable[Number], w: Iterable[Number]): - self._pos = x - self._weight = w - - @property - def pos(self) -> Iterable[Number]: - return self._pos - - @property - def weight(self) -> Iterable[Number]: - return self._weight - -# LINES - - -def Gauss_Legendre_Line_Grid(n: int) -> Tuple[ndarray]: - return gp(n) - - -# TRIANGLES - - -def Gauss_Legendre_Tri_1() -> Tuple[ndarray]: - return np.array([[0.0, 0.0]]), np.array([1 / 2]) - - -def Gauss_Legendre_Tri_3a() -> Tuple[ndarray]: - p = np.array([[-1 / 6, -1 / 6], [1 / 3, -1 / 6], [-1 / 6, 1 / 3]]) - w = np.array([1 / 6, 1 / 6, 1 / 6]) - return p, w - - -def Gauss_Legendre_Tri_3b() -> Tuple[ndarray]: - p = np.array([[1 / 6, 1 / 6], [-1 / 3, 1 / 6], [1 / 6, -1 / 3]]) - w = np.array([1 / 6, 1 / 6, 1 / 6]) - return p, w - - -# QUADRILATERALS - - -def Gauss_Legendre_Quad_Grid(i: int, j: int = None) -> Tuple[ndarray]: - j = i if j is None else j - return gp(i, j) - - -def Gauss_Legendre_Quad_1() -> Tuple[ndarray]: - return gp(1, 1) - - -def Gauss_Legendre_Quad_4() -> Tuple[ndarray]: - return gp(2, 2) - - -def Gauss_Legendre_Quad_9() -> Tuple[ndarray]: - return gp(3, 3) - - -# TETRAHEDRA - - -def Gauss_Legendre_Tet_1() -> Tuple[ndarray]: - p = np.array([[-1 / 12, -1 / 12, -1 / 12]]) - w = np.array([1 / 6]) - return p, w - - -def Gauss_Legendre_Tet_4() -> Tuple[ndarray]: - a = ((5 + 3 * np.sqrt(5)) / 20) - 1 / 3 - b = ((5 - np.sqrt(5)) / 20) - 1 / 3 - p = np.array([[a, b, b], [b, a, b], [b, b, a], [b, b, b]]) - w = np.full(4, 1 / 24) - return p, w - - -def Gauss_Legendre_Tet_5() -> Tuple[ndarray]: - p = np.array( - [ - [-1 / 12, -1 / 12, -1 / 12], - [1 / 6, -1 / 6, -1 / 6], - [-1 / 6, 1 / 6, -1 / 6], - [-1 / 6, -1 / 6, 1 / 6], - [-1 / 6, -1 / 6, -1 / 6], - ] - ) - w = np.array([-4 / 30, 9 / 120, 9 / 120, 9 / 120, 9 / 120]) - return p, w - - -def Gauss_Legendre_Tet_11() -> Tuple[ndarray]: - a = ((1 + 3 * np.sqrt(5 / 15)) / 4) - 1 / 3 - b = ((1 - np.sqrt(5 / 14)) / 4) - 1 / 3 - p = np.array( - [ - [-1 / 12, -1 / 12, -1 / 12], - [19 / 42, -11 / 42, -11 / 42], - [-11 / 42, 19 / 42, -11 / 42], - [-11 / 42, -11 / 42, 19 / 42], - [-11 / 42, -11 / 42, -11 / 42], - [a, a, b], - [a, b, a], - [a, b, b], - [b, a, a], - [b, a, b], - [b, b, a], - ] - ) - w = np.array( - [ - -74 / 5625, - 343 / 45000, - 343 / 45000, - 343 / 45000, - 343 / 45000, - 56 / 2250, - 56 / 2250, - 56 / 2250, - 56 / 2250, - 56 / 2250, - 56 / 2250, - ] - ) - return p, w - - -# HEXAHEDRA - - -def Gauss_Legendre_Hex_Grid(i: int, j: int = None, k: int = None) -> Tuple[ndarray]: - j = i if j is None else j - k = j if k is None else k - return gp(i, j, k) - - -# WEDGES - - -def Gauss_Legendre_Wedge_3x2() -> Tuple[ndarray]: - p_tri, w_tri = Gauss_Legendre_Tri_3a() - p_line, w_line = Gauss_Legendre_Line_Grid(2) - p = np.zeros((6, 3), dtype=float) - w = np.zeros((6,), dtype=float) - p[:3, :2] = p_tri - p[:3, 2] = p_line[0] - w[:3] = w_tri * w_line[0] - p[3:6, :2] = p_tri - p[3:6, 2] = p_line[0] - w[3:6] = w_tri * w_line[1] - return p, w - - -def Gauss_Legendre_Wedge_3x3() -> Tuple[ndarray]: - p_tri, w_tri = Gauss_Legendre_Tri_3a() - p_line, w_line = Gauss_Legendre_Line_Grid(3) - n = len(w_line) * len(w_tri) - p = np.zeros((n, 3), dtype=float) - w = np.zeros((n,), dtype=float) - for i in range(len(w_line)): - p[i * 3 : (i + 1) * 3, :2] = p_tri - p[i * 3 : (i + 1) * 3, 2] = p_line[i] - w[i * 3 : (i + 1) * 3] = w_tri * w_line[i] - return p, w diff --git a/src/sigmaepsilon/mesh/utils/numint.py b/src/sigmaepsilon/mesh/utils/numint.py new file mode 100644 index 0000000..d966294 --- /dev/null +++ b/src/sigmaepsilon/mesh/utils/numint.py @@ -0,0 +1,345 @@ +from typing import Tuple, Iterable, Optional, Union +from numbers import Number + +import numpy as np +from numpy import ndarray + +from sigmaepsilon.math.numint import gauss_points as gp +from .tri import nat_to_loc_tri as n2l_tri +from .tet import nat_to_loc_tet as n2l_tet + + +class Quadrature: + def __init__(self, x: Iterable[Number], w: Iterable[Number]): + self._pos = x + self._weight = w + + @property + def pos(self) -> Iterable[Number]: + return self._pos + + @property + def weight(self) -> Iterable[Number]: + return self._weight + + +# LINES + + +def Gauss_Legendre_Line_Grid(n: int) -> Tuple[ndarray, ndarray]: + return gp(n) + + +# TRIANGLES + +# https://mathsfromnothing.au/triangle-quadrature-rules/?i=1 + + +def _complete_natural_coordinates(nat: ndarray) -> ndarray: + res = np.zeros((len(nat), 3), dtype=nat.dtype) + for i in range(len(res)): + res[i, 2] = 1 - res[i, 0] - res[i, 1] + return res + + +def Gauss_Legendre_Tri_1( + center: Optional[Union[ndarray, None]] = None +) -> Tuple[ndarray, ndarray]: + p, w = np.array([[0.0, 0.0]]), np.array([1 / 2]) + if isinstance(center, ndarray): + p += center + return p, w + + +def Gauss_Legendre_Tri_3a( + center: Optional[Union[ndarray, None]] = None +) -> Tuple[ndarray, ndarray]: + nat = np.array( + [ + [2 / 3, 1 / 6, 1 / 6], + [1 / 6, 2 / 3, 1 / 6], + [1 / 6, 1 / 6, 2 / 3], + ], + dtype=float, + ) + p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + w = np.array([1 / 6, 1 / 6, 1 / 6]) + return p, w + + +def Gauss_Legendre_Tri_3b( + center: Optional[Union[ndarray, None]] = None +) -> Tuple[ndarray, ndarray]: + nat = np.array( + [ + [0.0, 1 / 2, 1 / 2], + [1 / 2, 0.0, 1 / 2], + [1 / 2, 1 / 2, 0.0], + ], + dtype=float, + ) + p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + w = np.array([1 / 6, 1 / 6, 1 / 6]) + return p, w + + +def Gauss_Legendre_Tri_4( + center: Optional[Union[ndarray, None]] = None +) -> Tuple[ndarray, ndarray]: + nat = np.array( + [ + [1 / 3, 1 / 3, 1 / 3], + [0.2, 0.6, 0.2], + [0.2, 0.2, 0.6], + [0.6, 0.2, 0.2], + ], + dtype=float, + ) + p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + w = np.array([-0.5625, 0.520833333333333, 0.520833333333333, 0.520833333333333]) / 2 + return p, w + + +def Gauss_Legendre_Tri_6( + center: Optional[Union[ndarray, None]] = None +) -> Tuple[ndarray, ndarray]: + nat = np.array( + [ + [0.445948490915965, 0.108103018168070], + [0.445948490915965, 0.445948490915965], + [0.108103018168070, 0.445948490915965], + [0.091576213509771, 0.816847572980459], + [0.091576213509771, 0.091576213509771], + [0.816847572980459, 0.091576213509771], + ], + dtype=float, + ) + nat = _complete_natural_coordinates(nat) + p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + w = ( + np.array( + [ + 0.223381589678011, + 0.223381589678011, + 0.223381589678011, + 0.109951743655322, + 0.109951743655322, + 0.109951743655322, + ] + ) + / 2 + ) + return p, w + + +# QUADRILATERALS + + +def Gauss_Legendre_Quad_Grid(i: int, j: int = None) -> Tuple[ndarray, ndarray]: + j = i if j is None else j + return gp(i, j) + + +def Gauss_Legendre_Quad_1() -> Tuple[ndarray, ndarray]: + return gp(1, 1) + + +def Gauss_Legendre_Quad_4() -> Tuple[ndarray, ndarray]: + return gp(2, 2) + + +def Gauss_Legendre_Quad_9() -> Tuple[ndarray, ndarray]: + return gp(3, 3) + + +# TETRAHEDRA + + +def Gauss_Legendre_Tet_1( + center: Optional[Union[ndarray, None]] = None +) -> Tuple[ndarray, ndarray]: + nat = np.array([[0.25, 0.25, 0.25, 0.25]]) + p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + w = np.array([1 / 6]) + return p, w + + +def Gauss_Legendre_Tet_4( + center: Optional[Union[ndarray, None]] = None +) -> Tuple[ndarray, ndarray]: + nat = np.array( + [ + [ + 0.585410196624968, + 0.138196601125010, + 0.138196601125010, + 0.138196601125010, + ], + [ + 0.138196601125010, + 0.585410196624968, + 0.138196601125010, + 0.138196601125010, + ], + [ + 0.138196601125010, + 0.138196601125010, + 0.585410196624968, + 0.138196601125010, + ], + [ + 0.138196601125010, + 0.138196601125010, + 0.138196601125010, + 0.585410196624968, + ], + ] + ) + p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + w = np.full(4, 1 / 24) + return p, w + + +def Gauss_Legendre_Tet_5( + center: Optional[Union[ndarray, None]] = None +) -> Tuple[ndarray, ndarray]: + nat = np.array( + [ + [1 / 4, 1 / 4, 1 / 4, 1 / 4], + [1 / 2, 1 / 6, 1 / 6, 1 / 6], + [1 / 6, 1 / 2, 1 / 6, 1 / 6], + [1 / 6, 1 / 6, 1 / 2, 1 / 6], + [1 / 6, 1 / 6, 1 / 6, 1 / 2], + ] + ) + p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + w = np.array([-4 / 30, 9 / 120, 9 / 120, 9 / 120, 9 / 120]) + return p, w + + +def Gauss_Legendre_Tet_11( + center: Optional[Union[ndarray, None]] = None +) -> Tuple[ndarray, ndarray]: + nat = np.array( + [ + [1 / 4, 1 / 4, 1 / 4, 1 / 4], + [ + 0.785714285714286, + 0.0714285714285714, + 0.0714285714285714, + 0.0714285714285714, + ], + [ + 0.0714285714285714, + 0.785714285714286, + 0.0714285714285714, + 0.0714285714285714, + ], + [ + 0.0714285714285714, + 0.0714285714285714, + 0.785714285714286, + 0.0714285714285714, + ], + [ + 0.0714285714285714, + 0.0714285714285714, + 0.0714285714285714, + 0.785714285714286, + ], + [ + 0.399403576166799, + 0.399403576166799, + 0.100596423833201, + 0.100596423833201, + ], + [ + 0.399403576166799, + 0.100596423833201, + 0.399403576166799, + 0.100596423833201, + ], + [ + 0.399403576166799, + 0.100596423833201, + 0.100596423833201, + 0.399403576166799, + ], + [ + 0.100596423833201, + 0.399403576166799, + 0.399403576166799, + 0.100596423833201, + ], + [ + 0.100596423833201, + 0.399403576166799, + 0.100596423833201, + 0.399403576166799, + ], + [ + 0.100596423833201, + 0.100596423833201, + 0.399403576166799, + 0.399403576166799, + ], + ] + ) + p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + w = np.array( + [ + -74 / 5625, + 343 / 45000, + 343 / 45000, + 343 / 45000, + 343 / 45000, + 56 / 2250, + 56 / 2250, + 56 / 2250, + 56 / 2250, + 56 / 2250, + 56 / 2250, + ] + ) + return p, w + + +# HEXAHEDRA + + +def Gauss_Legendre_Hex_Grid( + i: int, j: int = None, k: int = None +) -> Tuple[ndarray, ndarray]: + j = i if j is None else j + k = j if k is None else k + return gp(i, j, k) + + +# WEDGES + + +def Gauss_Legendre_Wedge_3x2() -> Tuple[ndarray, ndarray]: + p_tri, w_tri = Gauss_Legendre_Tri_3a() + p_line, w_line = Gauss_Legendre_Line_Grid(2) + p = np.zeros((6, 3), dtype=float) + w = np.zeros((6,), dtype=float) + p[:3, :2] = p_tri + p[:3, 2] = p_line[0] + w[:3] = w_tri * w_line[0] + p[3:6, :2] = p_tri + p[3:6, 2] = p_line[0] + w[3:6] = w_tri * w_line[1] + return p, w + + +def Gauss_Legendre_Wedge_3x3() -> Tuple[ndarray, ndarray]: + p_tri, w_tri = Gauss_Legendre_Tri_3a() + p_line, w_line = Gauss_Legendre_Line_Grid(3) + n = len(w_line) * len(w_tri) + p = np.zeros((n, 3), dtype=float) + w = np.zeros((n,), dtype=float) + for i in range(len(w_line)): + p[i * 3 : (i + 1) * 3, :2] = p_tri + p[i * 3 : (i + 1) * 3, 2] = p_line[i] + w[i * 3 : (i + 1) * 3] = w_tri * w_line[i] + return p, w diff --git a/src/sigmaepsilon/mesh/utils/tet.py b/src/sigmaepsilon/mesh/utils/tet.py index b34aa1c..7ae70f3 100644 --- a/src/sigmaepsilon/mesh/utils/tet.py +++ b/src/sigmaepsilon/mesh/utils/tet.py @@ -145,12 +145,12 @@ def _glob_to_nat_tet_bulk_knn_( @njit(nogil=True, cache=__cache) -def lcoords_tet() -> ndarray: +def lcoords_tet(center: ndarray = None) -> ndarray: """ Returns coordinates of the master element of a simplex in 3d. """ - return np.array( + res = np.array( [ [-1 / 3, -1 / 3, -1 / 3], [2 / 3, -1 / 3, -1 / 3], @@ -158,16 +158,35 @@ def lcoords_tet() -> ndarray: [-1 / 3, -1 / 3, 2 / 3], ] ) + if center is not None: + res += center + return res @njit(nogil=True, cache=__cache) -def nat_to_loc_tet(acoord: np.ndarray) -> ndarray: +def nat_to_loc_tet( + acoord: ndarray, lcoords: ndarray = None, center: ndarray = None +) -> ndarray: """ Transformation from natural to local coordinates within a tetrahedra. + + Parameters + ---------- + acoord: numpy.ndarray + 1d NumPy array of natural coordinates of a point. + lcoords: numpy.ndarray, Optional + 2d NumPy array of parametric coordinates (r, s, t) of the + master cell of a tetrahedron. + center: numpy.ndarray + The local coordinates (r, s, t) of the geometric center + of the master tetrahedron. If not provided it is assumed to + be at (0, 0, 0). Notes ----- This function is numba-jittable in 'nopython' mode. """ - return acoord.T @ lcoords_tet() + if lcoords is None: + lcoords = lcoords_tet(center) + return acoord.T @ lcoords diff --git a/src/sigmaepsilon/mesh/utils/tri.py b/src/sigmaepsilon/mesh/utils/tri.py index 9fcc013..3852bd5 100644 --- a/src/sigmaepsilon/mesh/utils/tri.py +++ b/src/sigmaepsilon/mesh/utils/tri.py @@ -41,36 +41,66 @@ def monoms_tri_loc_bulk(lcoord: ndarray) -> ndarray: @njit(nogil=True, cache=__cache) -def lcoords_tri() -> ndarray: - return np.array([[-1 / 3, -1 / 3], [2 / 3, -1 / 3], [-1 / 3, 2 / 3]]) +def lcoords_tri(center: ndarray = None) -> ndarray: + """ + Returns the local coordinates (r, s) of the vertices of a triangle. + By default, it is assumed that the origo of the (r, s) system is at + the geometric center of the triangle, unless the coordinates of geometric + center are provided with the argument 'center'. -@njit(nogil=True, cache=__cache) -def lcenter_tri() -> ndarray: - return np.array([0.0, 0.0]) + Example + ------- + >>> import numpy as np + >>> from sigmaepsilon.mesh.utils.tri import lcoords_tri + >>> lcoords = lcoords_tri(np.array([1/3, 1/3])) + """ + res = np.array([[-1 / 3, -1 / 3], [2 / 3, -1 / 3], [-1 / 3, 2 / 3]]) + if center is not None: + res += center + return res @njit(nogil=True, cache=__cache) def ncenter_tri() -> ndarray: + """ + Returns the area coordinates of the geometric center of the + master triangle. + """ return np.array([1 / 3, 1 / 3, 1 / 3]) @njit(nogil=True, cache=__cache) -def shp_tri_loc(lcoord: ndarray) -> ndarray: - return np.array( - [1 / 3 - lcoord[0] - lcoord[1], lcoord[0] + 1 / 3, lcoord[1] + 1 / 3] - ) +def shp_tri_loc(lcoord: ndarray, center: ndarray = None) -> ndarray: + """ + Evaluates the shape functions at the parametric coordinates (r, s). + + By default, it is assumed that the origo of the (r, s) system is at + the geometric center of the triangle, unless the coordinates of geometric + center are provided with the argument 'center'. + + Example + ------- + For a master triangle with centroid at the first vertex: + >>> import numpy as np + >>> from sigmaepsilon.mesh.utils.tri import shp_tri_loc + >>> A1, A2, A3 = shp_tri_loc(np.array([0.0, 0.0]), np.array([1/3, 1/3])) + """ + r, s = lcoord + M = np.ones((3, 3), dtype=lcoord.dtype) + M[1:, :] = lcoords_tri(center).T + return np.linalg.inv(M) @ np.array([1, r, s], dtype=lcoord.dtype) @njit(nogil=True, parallel=True, cache=__cache) def shape_function_matrix_tri_loc( - lcoord: ndarray, nDOFN: int = 2, nNODE: int = 3 + lcoord: ndarray, nDOFN: int = 2, center: ndarray = None ) -> ndarray: eye = np.eye(nDOFN, dtype=lcoord.dtype) - shp = shp_tri_loc(lcoord) - res = np.zeros((nDOFN, nNODE * nDOFN), dtype=lcoord.dtype) - for i in prange(nNODE): - res[:, i * nNODE : (i + 1) * nNODE] = eye * shp[i] + shp = shp_tri_loc(lcoord, center) + res = np.zeros((nDOFN, 3 * nDOFN), dtype=lcoord.dtype) + for i in prange(3): + res[:, i * 3 : (i + 1) * 3] = eye * shp[i] return res @@ -259,7 +289,7 @@ def area_tri_u(x1, y1, x2, y2, x3, y3) -> float: @vectorize("f8(f8, f8, f8, f8, f8, f8)", target="parallel", cache=__cache) -def area_tri_u2(x1, x2, x3, y1, y2, y3): +def area_tri_u2(x1, x2, x3, y1, y2, y3) -> float: """ Another vectorized implementation of `area_tri_bulk` with a different order of arguments. @@ -272,7 +302,9 @@ def area_tri_u2(x1, x2, x3, y1, y2, y3): @njit(nogil=True, cache=__cache) -def loc_to_glob_tri(lcoord: ndarray, gcoords: ndarray) -> ndarray: +def loc_to_glob_tri( + lcoord: ndarray, gcoords: ndarray, center: ndarray = None +) -> ndarray: """ Transformation from local to global coordinates within a triangle. @@ -280,11 +312,13 @@ def loc_to_glob_tri(lcoord: ndarray, gcoords: ndarray) -> ndarray: ----- This function is numba-jittable in 'nopython' mode. """ - return gcoords.T @ shp_tri_loc(lcoord) + return gcoords.T @ shp_tri_loc(lcoord, center) @njit(nogil=True, cache=__cache) -def glob_to_loc_tri(gcoord: ndarray, gcoords: ndarray) -> ndarray: +def glob_to_loc_tri( + gcoord: ndarray, gcoords: ndarray, center: ndarray = None +) -> ndarray: """ Transformation from global to local coordinates within a triangle. @@ -295,7 +329,7 @@ def glob_to_loc_tri(gcoord: ndarray, gcoords: ndarray) -> ndarray: monoms = monoms_tri_loc_bulk(gcoords) coeffs = np.linalg.inv(monoms) shp = coeffs.T @ monoms_tri_loc(gcoord) - return lcoords_tri().T @ shp + return lcoords_tri(center).T @ shp @njit(nogil=True, cache=__cache) @@ -378,7 +412,7 @@ def nat_to_glob_tri(ncoord: ndarray, ecoords: ndarray) -> ndarray: @njit(nogil=True, cache=__cache) -def loc_to_nat_tri(lcoord: ndarray) -> ndarray: +def loc_to_nat_tri(lcoord: ndarray, center: ndarray = None) -> ndarray: """ Transformation from local to natural coordinates within a triangle. @@ -386,19 +420,35 @@ def loc_to_nat_tri(lcoord: ndarray) -> ndarray: ----- This function is numba-jittable in 'nopython' mode. """ - return shp_tri_loc(lcoord) + return shp_tri_loc(lcoord, center) @njit(nogil=True, cache=__cache) -def nat_to_loc_tri(acoord: ndarray) -> ndarray: +def nat_to_loc_tri( + acoord: ndarray, lcoords: ndarray = None, center: ndarray = None +) -> ndarray: """ Transformation from natural to local coordinates within a triangle. + Parameters + ---------- + acoord: numpy.ndarray + 1d NumPy array of area coordinates of a point. + lcoords: numpy.ndarray, Optional + 2d NumPy array of parametric coordinates (r, s) of the + master cell of a triangle. + center: numpy.ndarray + The local coordinates (r, s) of the geometric center + of the master triangle. If not provided it is assumed to + be at (0, 0). + Notes ----- This function is numba-jittable in 'nopython' mode. """ - return acoord.T @ lcoords_tri() + if lcoords is None: + lcoords = lcoords_tri(center) + return acoord.T @ lcoords @njit(nogil=True, parallel=True, cache=__cache) @@ -456,9 +506,7 @@ def approx_data_to_points( return res -def offset_tri( - coords: ndarray, topo: ndarray, data: ndarray, *args, **kwargs -) -> ndarray: +def offset_tri(coords: ndarray, topo: ndarray, data: ndarray) -> ndarray: if isinstance(data, ndarray): alpha = np.abs(data) amax = alpha.max() diff --git a/src/sigmaepsilon/mesh/utils/utils.py b/src/sigmaepsilon/mesh/utils/utils.py index 78bd710..636253b 100644 --- a/src/sigmaepsilon/mesh/utils/utils.py +++ b/src/sigmaepsilon/mesh/utils/utils.py @@ -176,10 +176,10 @@ def _cells_around_MT_(centers: np.ndarray, r_max: float, n_max: int = 10): def points_of_cells( coords: ndarray, topo: ndarray, - *args, + *, local_axes: ndarray = None, centralize: bool = True, - **kwargs, + centers: ndarray = None ) -> ndarray: """ Returns an explicit representation of coordinates of the cells from a @@ -204,6 +204,9 @@ def points_of_cells( centralize: bool, Optional If True, and 'local_axes' is not None, the local coordinates are returned with respect to the geometric center of each element. + centers: numpy.ndarray + Centers for all cells. This is to account for different master cells + with different centers (usually for triangles). Default is None. Returns ------- @@ -217,13 +220,19 @@ def points_of_cells( """ if local_axes is not None: if centralize: - ec = _centralize_cells_coords_(cells_coords(coords, topo)) + if centers is not None: + ec = _centralize_cells_coords_2(cells_coords(coords, topo), centers) + else: + ec = _centralize_cells_coords(cells_coords(coords, topo)) else: ec = cells_coords(coords, topo) return _cells_coords_tr_(ec, local_axes) else: if centralize: - return _centralize_cells_coords_(cells_coords(coords, topo)) + if centers is not None: + return _centralize_cells_coords_2(cells_coords(coords, topo), centers) + else: + return _centralize_cells_coords(cells_coords(coords, topo)) else: return cells_coords(coords, topo) @@ -240,7 +249,7 @@ def _cells_coords_tr_(ecoords: ndarray, local_axes: ndarray) -> ndarray: @njit(nogil=True, parallel=True, cache=__cache) -def _centralize_cells_coords_(ecoords): +def _centralize_cells_coords(ecoords: ndarray) -> ndarray: nE, nNE, _ = ecoords.shape res = np.zeros_like(ecoords) for i in prange(nE): @@ -250,6 +259,17 @@ def _centralize_cells_coords_(ecoords): return res +@njit(nogil=True, parallel=True, cache=__cache) +def _centralize_cells_coords_2(ecoords: ndarray, centers: ndarray) -> ndarray: + nE, nNE, _ = ecoords.shape + res = np.zeros_like(ecoords) + for i in prange(nE): + cc = centers[i] + for j in prange(nNE): + res[i, j, :] = ecoords[i, j, :] - cc + return res + + @njit(nogil=True, parallel=True, cache=__cache) def cells_coords(coords: ndarray, topo: ndarray) -> ndarray: """ diff --git a/tests/cells/test_tri.py b/tests/cells/test_tri.py index d5b2d9d..0317f75 100644 --- a/tests/cells/test_tri.py +++ b/tests/cells/test_tri.py @@ -16,9 +16,9 @@ glob_to_nat_tri, lcoords_tri, ncenter_tri, - lcenter_tri, center_tri_2d, area_tri, + lcoords_tri, ) @@ -32,11 +32,13 @@ def test_T3(self, N: int = 3): nNE = T3.Geometry.number_of_nodes nD = T3.Geometry.number_of_spatial_dimensions + lcoords = lcoords_tri() + for _ in range(N): A1, A2 = np.random.rand(2) A3 = 1 - A1 - A2 x_nat = np.array([A1, A2, A3]) - x_loc = atleast2d(nat_to_loc_tri(x_nat)) + x_loc = atleast2d(nat_to_loc_tri(x_nat, lcoords)) shpA = shpf(x_loc) shpB = T3.Geometry.shape_function_values(x_loc) @@ -85,11 +87,13 @@ def test_T6(self, N: int = 3): nNE = T6.Geometry.number_of_nodes nD = T6.Geometry.number_of_spatial_dimensions + lcoords = lcoords_tri() + for _ in range(N): A1, A2 = np.random.rand(2) A3 = 1 - A1 - A2 x_nat = np.array([A1, A2, A3]) - x_loc = atleast2d(nat_to_loc_tri(x_nat)) + x_loc = atleast2d(nat_to_loc_tri(x_nat, lcoords)) shpA = shpf(x_loc) shpB = T6.Geometry.shape_function_values(x_loc) @@ -140,13 +144,18 @@ def test_triutils(self): _ = PolyData(pd, cd) ec = cd.local_coordinates() nE, nNE = topo.shape + lcoords = lcoords_tri() - self.assertTrue(np.allclose(nat_to_loc_tri(ncenter_tri()), lcenter_tri())) - self.assertTrue(np.allclose(loc_to_nat_tri(lcenter_tri()), ncenter_tri())) + self.assertTrue( + np.allclose(nat_to_loc_tri(ncenter_tri(), lcoords), np.array([0.0, 0.0])) + ) + self.assertTrue( + np.allclose(loc_to_nat_tri(np.array([0.0, 0.0]), lcoords), ncenter_tri()) + ) x_tri_loc = lcoords_tri() x_tri_nat = np.eye(3).astype(float) - c_tri_loc = lcenter_tri() + c_tri_loc = np.array([0.0, 0.0]) for iNE in range(nNE): x_nat = loc_to_nat_tri(x_tri_loc[iNE]) diff --git a/tests/test_numint.py b/tests/test_numint.py new file mode 100644 index 0000000..c247183 --- /dev/null +++ b/tests/test_numint.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +import numpy as np +import unittest + +from sigmaepsilon.core.testing import SigmaEpsilonTestCase +from sigmaepsilon.mesh.utils.numint import ( + Gauss_Legendre_Line_Grid, + Gauss_Legendre_Tri_1, + Gauss_Legendre_Tri_3a, + Gauss_Legendre_Tri_3b, + Gauss_Legendre_Tri_4, + Gauss_Legendre_Tri_6, + Gauss_Legendre_Quad_Grid, + Gauss_Legendre_Quad_1, + Gauss_Legendre_Quad_4, + Gauss_Legendre_Quad_9, + Gauss_Legendre_Tet_1, + Gauss_Legendre_Tet_4, + Gauss_Legendre_Tet_5, + Gauss_Legendre_Tet_11, + Gauss_Legendre_Hex_Grid, + Gauss_Legendre_Wedge_3x2, + Gauss_Legendre_Wedge_3x3, +) +from sigmaepsilon.mesh.data import PolyCell + + +class TestNumint(SigmaEpsilonTestCase): + + def test_numint_main(self): + Gauss_Legendre_Line_Grid(2) + + Gauss_Legendre_Tri_1() + Gauss_Legendre_Tri_1(np.array([0.0, 0.0])) + Gauss_Legendre_Tri_3a() + Gauss_Legendre_Tri_3b() + Gauss_Legendre_Tri_4() + Gauss_Legendre_Tri_6() + + Gauss_Legendre_Quad_Grid(2, 2) + Gauss_Legendre_Quad_1() + Gauss_Legendre_Quad_4() + Gauss_Legendre_Quad_9() + + Gauss_Legendre_Tet_1() + Gauss_Legendre_Tet_4() + Gauss_Legendre_Tet_5() + Gauss_Legendre_Tet_11() + + Gauss_Legendre_Hex_Grid(2, 2, 2) + Gauss_Legendre_Wedge_3x2() + Gauss_Legendre_Wedge_3x3() + + def test_gauss_parser(self): + parser = PolyCell._parse_gauss_data + + quadratures = { + "1": Gauss_Legendre_Tri_1(), + "2": "1", + "3": "2", + "4": Gauss_Legendre_Tri_1 + } + + parser(quadratures, "1") + parser(quadratures, "2") + parser(quadratures, "3") + parser(quadratures, "4") + + for q1, q2 in zip(parser(quadratures, "1"), parser(quadratures, "2")): + self.assertTrue(np.allclose(q1.pos, q2.pos)) + self.assertTrue(np.allclose(q1.weight, q2.weight)) + + for q1, q2 in zip(parser(quadratures, "1"), parser(quadratures, "3")): + self.assertTrue(np.allclose(q1.pos, q2.pos)) + self.assertTrue(np.allclose(q1.weight, q2.weight)) + + for q1, q2 in zip(parser(quadratures, "1"), parser(quadratures, "4")): + self.assertTrue(np.allclose(q1.pos, q2.pos)) + self.assertTrue(np.allclose(q1.weight, q2.weight)) + + +if __name__ == "__main__": + unittest.main() From 434802279099f920c8d86074ad48b147d1cc6e96 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Thu, 2 Nov 2023 21:53:40 +0100 Subject: [PATCH 42/47] increased version number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f7c05d1..1d84c26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta" [project] name = "sigmaepsilon.mesh" -version = "2.1.0" +version = "2.2.0" description = "A Python package to build, manipulate and analyze polygonal meshes." classifiers=[ "Development Status :: 5 - Production/Stable", From c44080cb07ce1b556372851ef7b63bb829edcc88 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Sat, 4 Nov 2023 00:12:36 +0100 Subject: [PATCH 43/47] added more tests --- tests/cells/test_polycell.py | 62 +++++++++++++- tests/polydata/test_polydata.py | 92 +++++++++++++++++++- tests/test_numint.py | 9 ++ tests/test_pointdata.py | 144 +++++++++++++++++++++++++++++++- 4 files changed, 301 insertions(+), 6 deletions(-) diff --git a/tests/cells/test_polycell.py b/tests/cells/test_polycell.py index 39db4a2..36c054e 100644 --- a/tests/cells/test_polycell.py +++ b/tests/cells/test_polycell.py @@ -4,11 +4,17 @@ from sigmaepsilon.core.testing import SigmaEpsilonTestCase import sigmaepsilon.mesh -from sigmaepsilon.mesh import PolyData, PointData, LineData +from sigmaepsilon.mesh import ( + PolyData, + PointData, + LineData, + TriMesh, + triangulate, + CartesianFrame, +) from sigmaepsilon.mesh.space import CartesianFrame -from sigmaepsilon.mesh.cells import H8, TET4, L2 +from sigmaepsilon.mesh.cells import H8, TET4, L2, T3 from sigmaepsilon.mesh.utils.topology import H8_to_TET4, H8_to_L2 -from sigmaepsilon.mesh.utils.space import frames_of_lines from sigmaepsilon.mesh.grid import grid as _grid @@ -17,6 +23,54 @@ def load_tests(loader, tests, ignore): # pragma: no cover return tests +class TestPolyCell2d(SigmaEpsilonTestCase): + def setUp(self): + A = CartesianFrame(dim=3) + coords, topo, _ = triangulate(size=(10, 10), shape=(4, 4)) + pd = PointData(coords=coords, frame=A) + pd["random_data"] = np.random.rand(coords.shape[0]) + cd = T3(topo=topo, frames=A, t=np.ones((len(topo)))) + cd["random_data"] = np.random.rand(topo.shape[0]) + tri = TriMesh(cd, pd) + self.mesh = tri + self.cd = cd + + def test_area(self): + self.assertTrue(np.isclose(self.cd.area(), 100.0)) + self.assertTrue(np.isclose(self.cd.measure(), 100.0)) + self.assertTrue(np.allclose(self.cd.areas(), self.cd.measures())) + + def test_volume(self): + self.assertTrue(np.isclose(self.cd.volume(), 100.0)) + + def test_thickness(self): + self.assertTrue(np.allclose(self.cd.thickness(), np.ones((len(self.cd))))) + + def test_to_triangles(self): + tri = self.cd.to_triangles() + self.assertIsInstance(tri, np.ndarray) + self.assertEqual(tri.shape[1], 3) + + def test_to_simplices(self): + tri = self.cd.to_simplices() + self.assertIsInstance(tri, np.ndarray) + self.assertEqual(tri.shape[1], 3) + + def test_points_of_cells(self): + poc = self.cd.points_of_cells() + self.assertIsInstance(poc, np.ndarray) + self.assertEqual(poc.shape[0], len(self.cd)) + self.assertEqual(poc.shape[1], len(self.cd.nodes[-1])) + + points = np.array(self.cd.__class__.Geometry.master_coordinates()) + poc = self.cd.points_of_cells(points=points) + poc = self.cd.points_of_cells(points=points, cells=[0, 1]) + + def test_pip(self): + res = self.cd.pip([0.0, 0.0, 0.0], lazy=False) + self.assertTrue(res) + + class TestPolyCell(SigmaEpsilonTestCase): def test_polycell(self): size = 10, 10, 5 @@ -78,7 +132,7 @@ def test_polycell(self): cdL2._get_points_and_range() cdH8._get_points_and_range() cdTET4._get_points_and_range() - + cdL2.points_of_cells() cdL2.points_of_cells(points=[-1.0, 1.0], rng=[-1, 1]) cdH8.points_of_cells() diff --git a/tests/polydata/test_polydata.py b/tests/polydata/test_polydata.py index 841b519..4544385 100644 --- a/tests/polydata/test_polydata.py +++ b/tests/polydata/test_polydata.py @@ -8,30 +8,89 @@ import meshio from sigmaepsilon.core.testing import SigmaEpsilonTestCase +from sigmaepsilon.core.warning import SigmaEpsilonPerformanceWarning from sigmaepsilon.mesh import PolyData, PointData, CartesianFrame, triangulate from sigmaepsilon.mesh.data.trimesh import TriMesh +from sigmaepsilon.mesh.data.celldata import CellData from sigmaepsilon.mesh.space import StandardFrame from sigmaepsilon.mesh.cells import H8, Q4, T3 from sigmaepsilon.mesh.grid import grid +class TestPolyDataSingleBlock(SigmaEpsilonTestCase): + def setUp(self) -> None: + A = StandardFrame(dim=3) + coords, topo, _ = triangulate(size=(100, 100), shape=(4, 4)) + pd = PointData(coords=coords, frame=A) + pd["random_data"] = np.random.rand(coords.shape[0]) + cd = T3(topo=topo, frames=A) + cd["random_data"] = np.random.rand(topo.shape[0]) + tri = TriMesh(cd, pd) + self.mesh = tri + + def test_basic(self): + mesh: PolyData = self.mesh + mesh.parent = mesh.parent + self.assertFalse(mesh.topology().is_jagged()) + self.assertIsInstance(mesh.cells_at_nodes(), Iterable) + + def test_set_pointdata_raises_TypeError(self): + with self.assertRaises(TypeError) as cm: + self.mesh.pointdata = "a" + the_exception = cm.exception + self.assertEqual( + the_exception.args[0], + "Value must be a PointData instance.", + ) + + def test_set_celldata_raises_TypeError(self): + with self.assertRaises(TypeError) as cm: + self.mesh.pointdata = "a" + the_exception = cm.exception + self.assertEqual( + the_exception.args[0], + "Value must be a PointData instance.", + ) + + def test_to_lists(self): + self.mesh.to_lists( + point_fields=["random_data"], + cell_fields=["random_data"] + ) + + def test_rewire(self): + self.mesh.rewire() + self.mesh.rewire(deep=True) + + def test_to_standard_form(self): + self.mesh.to_standard_form() + self.mesh.to_standard_form(inplace=True) + + def test_nodal_distribution_factors(self): + self.mesh.nodal_distribution_factors() + + class TestPolyDataMultiBlock(SigmaEpsilonTestCase): def setUp(self) -> None: A = StandardFrame(dim=3) coords, topo, _ = triangulate(size=(100, 100), shape=(4, 4)) pd = PointData(coords=coords, frame=A) + pd["random_data"] = np.random.rand(coords.shape[0]) cd = T3(topo=topo, frames=A) + cd["random_data"] = np.random.rand(topo.shape[0]) tri = TriMesh(pd, cd) coords, topo = grid(size=(100, 100), shape=(4, 4), eshape="Q4") pd = PointData(coords=coords, frame=A) cd = Q4(topo=topo, frames=A) + cd["random_data"] = np.random.rand(topo.shape[0]) grid2d = PolyData(pd, cd) coords, topo = grid(size=(100, 100, 20), shape=(4, 4, 2), eshape="H8") pd = PointData(coords=coords, frame=A) cd = H8(topo=topo, frames=A) + cd["random_data"] = np.random.rand(topo.shape[0]) grid3d = PolyData(pd, cd) mesh = PolyData(frame=A) @@ -71,6 +130,29 @@ def test_misc(self): self.assertIsInstance(mesh["grids", "Q4"].cd.frames, np.ndarray) mesh["grids", "Q4"].cd.frames = mesh["grids", "Q4"].cd.frames + + mesh._in_all_pointdata_("_") + mesh._in_all_celldata_("_") + dbkey = PointData._dbkey_x_ + self.assertTrue(mesh._in_all_pointdata_(dbkey)) + self.assertTrue(mesh._in_all_pointdata_("random_data")) + dbkey = CellData._dbkey_nodes_ + self.assertTrue(mesh._in_all_celldata_(dbkey)) + self.assertTrue(mesh._in_all_celldata_("random_data")) + + def test_root(self): + self.assertEqual(self.mesh["grids", "Q4"].root, self.mesh) + self.assertEqual(self.mesh["grids", "H8"].root, self.mesh) + self.assertEqual(self.mesh["tri", "T3"].root, self.mesh) + self.assertEqual(self.mesh["tri"].root, self.mesh) + self.assertEqual(self.mesh["grids"].root, self.mesh) + + def blocks_of_cells(self): + mesh: PolyData = self.mesh + mesh._cid2bid=None + self.assertWarns(SigmaEpsilonPerformanceWarning, mesh.blocks_of_cells) + mesh.lock() + mesh.blocks_of_cells() def test_coordinates(self): mesh: PolyData = self.mesh @@ -154,6 +236,14 @@ def boo(): self.mesh["grids", "Q4"] self.assertFailsProperly(KeyError, boo) + + """def test_replace(self): + A = StandardFrame(dim=3) + coords, topo, _ = triangulate(size=(100, 100), shape=(4, 4)) + pd = PointData(coords=coords, frame=A) + cd = T3(topo=topo, frames=A) + tri = TriMesh(pd, cd) + self.mesh["tri", "T3"] = tri""" def test_centers(self): self.mesh.centers() @@ -164,7 +254,7 @@ def test_adjacency(self): self.mesh.nodal_adjacency() self.mesh.cells_at_nodes() self.mesh.cells_around_cells(radius=1.0) - + class TestSurfaceExtraction(unittest.TestCase): def test_surface_extraction(self): diff --git a/tests/test_numint.py b/tests/test_numint.py index c247183..365be7f 100644 --- a/tests/test_numint.py +++ b/tests/test_numint.py @@ -31,11 +31,16 @@ def test_numint_main(self): Gauss_Legendre_Line_Grid(2) Gauss_Legendre_Tri_1() + Gauss_Legendre_Tri_1(natural=True) Gauss_Legendre_Tri_1(np.array([0.0, 0.0])) Gauss_Legendre_Tri_3a() + Gauss_Legendre_Tri_3a(natural=True) Gauss_Legendre_Tri_3b() + Gauss_Legendre_Tri_3b(natural=True) Gauss_Legendre_Tri_4() + Gauss_Legendre_Tri_4(natural=True) Gauss_Legendre_Tri_6() + Gauss_Legendre_Tri_6(natural=True) Gauss_Legendre_Quad_Grid(2, 2) Gauss_Legendre_Quad_1() @@ -43,9 +48,13 @@ def test_numint_main(self): Gauss_Legendre_Quad_9() Gauss_Legendre_Tet_1() + Gauss_Legendre_Tet_1(natural=True) Gauss_Legendre_Tet_4() + Gauss_Legendre_Tet_4(natural=True) Gauss_Legendre_Tet_5() + Gauss_Legendre_Tet_5(natural=True) Gauss_Legendre_Tet_11() + Gauss_Legendre_Tet_11(natural=True) Gauss_Legendre_Hex_Grid(2, 2, 2) Gauss_Legendre_Wedge_3x2() diff --git a/tests/test_pointdata.py b/tests/test_pointdata.py index e0be38e..71b599c 100644 --- a/tests/test_pointdata.py +++ b/tests/test_pointdata.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import numpy as np import unittest +from awkward import Array, Record from sigmaepsilon.core.testing import SigmaEpsilonTestCase from sigmaepsilon.math.linalg import FrameLike @@ -10,7 +11,7 @@ class TestPointData(SigmaEpsilonTestCase): def test_pointdata(self): A = CartesianFrame(dim=3) - coords = triangulate(size=(800, 600), shape=(10, 10))[0] + coords = triangulate(size=(10, 10), shape=(10, 10))[0] pd = PointData(coords=coords) self.assertIsInstance(pd.frame, FrameLike) pd = PointData(coords=coords, frame=A) @@ -42,6 +43,147 @@ def test_pointdata(self): self.assertRaises(TypeError, setattr, pd, "x", "_") self.assertRaises(ValueError, setattr, pd, "x", np.zeros((3, 3, 3))) + self.assertIsInstance(pd["x"], Array) + + +class TestPointDataMagicFunctions(SigmaEpsilonTestCase): + def test_contains(self): + A = CartesianFrame(dim=3) + coords, *_ = triangulate(size=(100, 100), shape=(4, 4)) + pd = PointData(coords=coords, frame=A) + self.assertIn("x", pd) + + x = np.array([[0.0, 0.0, 0.0]], dtype=float) + pd = PointData(coords=x) + pd["random_data"] = [1.0] + self.assertIn("random_data", pd) + self.assertFalse("__" in pd) + + def test_setitem(self): + x = np.array([[0.0, 0.0, 0.0]], dtype=float) + pd = PointData(coords=x) + pd["random_data"] = [1.0] + + with self.assertRaises(ValueError) as cm: + pd["random_data"] = [0.0, 0.0] + the_exception = cm.exception + self.assertEqual( + the_exception.args[0], + "The provided value must have the same length as the database.", + ) + + x = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], dtype=float) + pd = PointData(coords=x) + pd["random_data"] = [[0.0], [0.0, 0.0]] + + with self.assertRaises(TypeError) as cm: + pd["random_data"] = "_" + the_exception = cm.exception + self.assertEqual( + the_exception.args[0], + "Expected a sequence, got ", + ) + + with self.assertRaises(TypeError) as cm: + pd[0] = "_" + the_exception = cm.exception + self.assertEqual( + the_exception.args[0], + "Expected a string, got ", + ) + + def test_getitem(self): + x = np.array([[0.0, 0.0, 0.0]], dtype=float) + pd = PointData(coords=x) + pd["random_data"] = [1.0] + + self.assertTrue(len(pd["x"]) == 1) + self.assertTrue(len(pd["random_data"]) == 1) + self.assertIsInstance(pd[0], Record) + + def test_hasattr(self): + x = np.array([[0.0, 0.0, 0.0]], dtype=float) + pd = PointData(coords=x) + pd["random_data"] = [1.0] + + self.assertTrue(hasattr(pd, "x")) + self.assertTrue(hasattr(pd, "random_data")) + + def test_getattr(self): + x = np.array([[10.0, 110.0, 50.0]], dtype=float) + pd = PointData(coords=x) + random_data = [66.0] + pd["random_data"] = random_data + random_data = np.array(random_data) + + self.assertTrue(np.allclose(getattr(pd, "x"), x)) + self.assertTrue(np.allclose(getattr(pd, "random_data").to_numpy(), random_data)) + self.assertTrue(np.allclose(pd.random_data.to_numpy(), random_data)) + + +class TestPointDataExports(SigmaEpsilonTestCase): + def setUp(self) -> None: + x = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], dtype=float) + pd = PointData(coords=x) + pd["random_data"] = [[0.0], [0.0, 0.0]] + pd["random_data_2"] = [[0.0], [0.0]] + self.pd = pd + + def test_to_numpy(self): + self.assertIsInstance(self.pd.to_numpy("random_data_2"), np.ndarray) + + def test_to_ak(self): + arr = self.pd.to_ak() + self.assertIsInstance(arr, Array) + arr = self.pd.to_ak(fields=["random_data"]) + self.assertIsInstance(arr, Array) + + arr = self.pd.to_ak(asarray=True) + self.assertIsInstance(arr, Array) + arr = self.pd.to_ak(asarray=True, fields=["random_data"]) + self.assertIsInstance(arr, Array) + + def test_to_akarray(self): + arr = self.pd.to_akarray() + self.assertIsInstance(arr, Array) + arr[0]["random_data"] + + arr = self.pd.to_akarray(fields=["random_data"]) + self.assertIsInstance(arr, Array) + + def test_to_akrecord(self): + arr = self.pd.to_akrecord() + self.assertIsInstance(arr, Array) + arr[0]["random_data"] + + arr = self.pd.to_akrecord(fields=["random_data"]) + self.assertIsInstance(arr, Array) + + def test_to_dict(self): + d = self.pd.to_dict() + self.assertIsInstance(d, dict) + self.assertIn("random_data", d) + self.assertEqual(len(d), len(self.pd.db.fields)) + + d = self.pd.to_dict(fields=["random_data"]) + self.assertIsInstance(d, dict) + self.assertIn("random_data", d) + self.assertEqual(len(d), 1) + + def test_to_list(self): + res = self.pd.to_list() + self.assertIsInstance(res, list) + self.assertEqual(len(res), len(self.pd)) + self.assertIsInstance(res[0], dict) + self.assertIn("random_data", res[0]) + + res = self.pd.to_list(fields=["random_data"]) + self.assertIsInstance(res, list) + self.assertEqual(len(res), len(self.pd)) + self.assertIsInstance(res[0], dict) + self.assertIn("random_data", res[0]) + self.assertEqual(len(res[0]), 1) + if __name__ == "__main__": unittest.main() From 367227f862b9d14d030559c71b95ba3afa0b0db1 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Sat, 4 Nov 2023 00:13:06 +0100 Subject: [PATCH 44/47] improved data classes --- src/sigmaepsilon/mesh/data/akwrapper.py | 80 +++++++++++++++++++++---- src/sigmaepsilon/mesh/data/celldata.py | 13 ---- src/sigmaepsilon/mesh/data/pointdata.py | 12 +++- src/sigmaepsilon/mesh/data/polycell.py | 50 +++++++++++++--- src/sigmaepsilon/mesh/data/polydata.py | 12 ++-- 5 files changed, 125 insertions(+), 42 deletions(-) diff --git a/src/sigmaepsilon/mesh/data/akwrapper.py b/src/sigmaepsilon/mesh/data/akwrapper.py index dd6a118..835176f 100644 --- a/src/sigmaepsilon/mesh/data/akwrapper.py +++ b/src/sigmaepsilon/mesh/data/akwrapper.py @@ -1,4 +1,4 @@ -from typing import Iterable, Union, Any +from typing import Iterable, Union, Optional, Any import numpy as np from numpy import ndarray @@ -6,6 +6,7 @@ from awkward import Array as akArray, Record as akRecord from sigmaepsilon.core.wrapping import Wrapper +from sigmaepsilon.core.typing import issequence AwkwardLike = Union[akArray, akRecord] @@ -15,13 +16,19 @@ class AkWrapper(Wrapper): """ - A wrapper for Awkward objects. This is the base class of most + A wrapper for Awkward objects. This is the base class of many database classes in SigmaEpsilon projects. """ _attr_map_ = {} - def __init__(self, *args, wrap=None, fields=None, **kwargs): + def __init__( + self, + *args, + wrap: Optional[Union[Any, None]] = None, + fields: Optional[Union[Iterable[str], None]], + **kwargs, + ): fields = {} if fields is None else fields assert isinstance(fields, dict) @@ -52,7 +59,9 @@ def to_numpy(self, key: str) -> ndarray: """ return self._wrapped[key].to_numpy() - def to_dataframe(self, *args, fields: Iterable[str] = None, **kwargs): + def to_dataframe( + self, *args, fields: Optional[Union[Iterable[str], None]] = None, **kwargs + ): """ Returns the data of the database as a DataFrame. @@ -73,7 +82,11 @@ def to_dataframe(self, *args, fields: Iterable[str] = None, **kwargs): return ak.to_dataframe(akdb, **kwargs) def to_parquet( - self, path: str, *args, fields: Iterable[str] = None, **kwargs + self, + path: str, + *args, + fields: Optional[Union[Iterable[str], None]] = None, + **kwargs, ) -> None: """ Saves the data of the database to a parquet file. @@ -108,7 +121,10 @@ def from_parquet(cls, path: str) -> "AkWrapper": return cls(wrap=ak.from_parquet(path)) def to_ak( - self, *args, fields: Iterable[str] = None, asarray: bool = False + self, + *args, + fields: Optional[Union[Iterable[str], None]] = None, + asarray: Optional[bool] = False, ) -> Union[akArray, akRecord]: """ Returns the database with a specified set of fields as either @@ -131,7 +147,9 @@ def to_ak( else: return self.to_akrecord(*args, fields=fields) - def to_akarray(self, *args, fields: Iterable[str] = None) -> akArray: + def to_akarray( + self, *args, fields: Optional[Union[Iterable[str], None]] = None + ) -> akArray: """ Returns the data of the mesh as an Awkward array. @@ -145,7 +163,9 @@ def to_akarray(self, *args, fields: Iterable[str] = None) -> akArray: ldb = self.to_list(*args, fields=fields) return ak.from_iter(ldb) - def to_akrecord(self, *args, fields: Iterable[str] = None) -> akRecord: + def to_akrecord( + self, *args, fields: Optional[Union[Iterable[str], None]] = None + ) -> akRecord: """ Returns the data of the mesh as an Awkward record. @@ -159,7 +179,9 @@ def to_akrecord(self, *args, fields: Iterable[str] = None) -> akRecord: d = self.to_dict(*args, fields=fields) return ak.zip(d, depth_limit=1) - def to_dict(self, *args, fields: Iterable[str] = None) -> dict: + def to_dict( + self, *args, fields: Optional[Union[Iterable[str], None]] = None + ) -> dict: """ Returns data of the object as a dictionary. Unless fields are specified, all fields are returned. @@ -193,7 +215,9 @@ def to_dict(self, *args, fields: Iterable[str] = None) -> dict: return res - def to_list(self, *args, fields: Iterable[str] = None) -> list: + def to_list( + self, *args, fields: Optional[Union[Iterable[str], None]] = None + ) -> list: """ Returns data of the object as lists. Unless fields are specified, all fields are returned. @@ -240,10 +264,44 @@ def __getattr__(self, attr): attr = self.__class__._attr_map_[attr] if attr in self.__dict__: - return getattr(self, attr) + return self.__dict__[attr] try: return getattr(self._wrapped, attr) except Exception: name = self.__class__.__name__ raise AttributeError(f"'{name}' object has no attribute called '{attr}'") + + def __getitem__(self, index: str) -> Any: + is_str = isinstance(index, str) + + if is_str and index in self.__class__._attr_map_: + index = self.__class__._attr_map_[index] + + return self.db[index] + + def __setitem__(self, index: str, value: Iterable[Any]) -> None: + if not isinstance(index, str): + raise TypeError(f"Expected a string, got {type(index)}") + + if not issequence(value): + raise TypeError(f"Expected a sequence, got {type(value)}") + + if not len(value) == len(self): + raise ValueError( + "The provided value must have the same length as the database." + ) + + self._wrapped[index] = value + + def __contains__(self, item: str) -> bool: + if not isinstance(item, str): + return False + + if item in self._wrapped.fields: + return True + + if item in self.__class__._attr_map_: + return self.__class__._attr_map_[item] in self._wrapped.fields + + return False diff --git a/src/sigmaepsilon/mesh/data/celldata.py b/src/sigmaepsilon/mesh/data/celldata.py index 815c9fe..536bbbe 100644 --- a/src/sigmaepsilon/mesh/data/celldata.py +++ b/src/sigmaepsilon/mesh/data/celldata.py @@ -333,19 +333,6 @@ def activity(self, value: ndarray): value = np.full(len(self), value, dtype=bool) self._wrapped[self._dbkey_activity_] = value - def __getattr__(self, attr): - """ - Modified for being able to fetch data from pointcloud. - """ - if attr in self.__dict__: - return getattr(self, attr) - - try: - return getattr(self._wrapped, attr) - except Exception: - name = self.__class__.__name__ - raise AttributeError(f"'{name}' object has no attribute called {attr}") - def set_nodal_distribution_factors(self, factors: ndarray, key: str = None) -> None: """ Sets nodal distribution factors. diff --git a/src/sigmaepsilon/mesh/data/pointdata.py b/src/sigmaepsilon/mesh/data/pointdata.py index 91ac4f1..bcfa2ba 100644 --- a/src/sigmaepsilon/mesh/data/pointdata.py +++ b/src/sigmaepsilon/mesh/data/pointdata.py @@ -30,6 +30,12 @@ class PointData(AkWrapper, ABC_AkWrapper): The class is technicall a wrapper around an `awkward.Record` instance. + .. warning:: + Internal variables used during calculations begin with two leading underscores. Try + to avoid leading double underscores when assigning custom data to a PointData instance, + unless you are sure, that it is of no importance for the correct behaviour of the + class instances. + Parameters ---------- points: numpy.ndarray, Optional @@ -53,9 +59,9 @@ class PointData(AkWrapper, ABC_AkWrapper): _point_cls_ = PointCloud _frame_class_ = CartesianFrame _attr_map_ = { - "x": "_x", # coordinates - "activity": "_activity", # activity of the points - "id": "_id", # global indices of the points + "x": "__x", # coordinates + "activity": "__activity", # activity of the points + "id": "__id", # global indices of the points } def __init__( diff --git a/src/sigmaepsilon/mesh/data/polycell.py b/src/sigmaepsilon/mesh/data/polycell.py index d9539c2..326ca4f 100644 --- a/src/sigmaepsilon/mesh/data/polycell.py +++ b/src/sigmaepsilon/mesh/data/polycell.py @@ -18,6 +18,7 @@ from numpy import ndarray from numpy.lib.index_tricks import IndexExpression +from sigmaepsilon.core.typing import issequence from sigmaepsilon.math import atleast1d, atleast2d, atleastnd, ascont from sigmaepsilon.math.linalg import ReferenceFrame as FrameLike from sigmaepsilon.math.utils import to_range_1d @@ -179,14 +180,6 @@ def container(self, value: MeshDataLike) -> None: raise TypeError("'value' must be a PolyData instance") self._container = value - def __getattr__(self, name: str) -> Any: - if len(name) >= 7 and name[:7] == "_dbkey_": - return getattr(self.db, name) - elif hasattr(self.db, name): - return getattr(self.db, name) - else: - return super().__getattr__(name) - def root(self) -> MeshDataLike: """ Returns the top level container of the model the block is @@ -754,7 +747,7 @@ def topology(self) -> Union[TopologyArray, None]: """ if self.db.has_nodes: return TopologyArray(self.db.nodes) - else: + else: # pragma: no cover return None def rewire( @@ -1113,6 +1106,45 @@ def _rotate_(self, *args, **kwargs): def __len__(self) -> int: return len(self.db) + def __hasattr__(self, attr: str) -> Any: + return attr in self.__dict__ or hasattr(self.db, attr) + + def __getattr__(self, attr: str) -> Any: + if attr in self.__dict__: + return self.__dict__[attr] + try: + return getattr(self.db, attr) + except Exception: + raise AttributeError( + "'{}' object has no attribute \ + called {}".format( + self.__class__.__name__, attr + ) + ) + + def __getitem__(self, index: str) -> Any: + try: + return super().__getitem__(index) + except Exception: + try: + return self.db.__getitem__(index) + except Exception: + raise TypeError( + "'{}' object is not " + "subscriptable".format(self.__class__.__name__) + ) + + def __setitem__(self, index: str, value: Any) -> None: + if not isinstance(index, str): + raise TypeError(f"Expected a string, got {type(index)}") + + if not (issequence(value) and len(value) == len(self.db)): + raise ValueError( + "The length of the provided data must match the number of cells in the block" + ) + + self.db[index] = value + def __deepcopy__(self, memo: dict) -> "PolyCell": return self.__copy__(memo) diff --git a/src/sigmaepsilon/mesh/data/polydata.py b/src/sigmaepsilon/mesh/data/polydata.py index 9f7f0cb..9d00de9 100644 --- a/src/sigmaepsilon/mesh/data/polydata.py +++ b/src/sigmaepsilon/mesh/data/polydata.py @@ -713,7 +713,7 @@ def parent(self: PolyDataLike) -> PolyDataLike: return self._parent @parent.setter - def parent(self, value: PolyDataLike): + def parent(self, value: PolyDataLike) -> None: """Sets the parent.""" self._parent = value @@ -1058,7 +1058,7 @@ def points( coords.append(v.show(global_frame)) inds.append(i) - if len(coords) == 0: + if len(coords) == 0: # pragme: no cover raise Exception("There are no points belonging to this block") coords = np.vstack(list(coords)) @@ -1649,11 +1649,11 @@ def _rotate_attached_cells_(self, *args, **kwargs): def _in_all_pointdata_(self, key: str) -> bool: blocks = self.pointblocks(inclusive=True) - return all(list(map(lambda b: key in b.db.fields, blocks))) + return all(list(map(lambda b: key in b.pointdata.db.fields, blocks))) def _in_all_celldata_(self, key: str) -> bool: blocks = self.cellblocks(inclusive=True) - return all(list(map(lambda b: key in b.db.fields, blocks))) + return all(list(map(lambda b: key in b.celldata.db.fields, blocks))) def _detach_block_data_(self, data: Union[str, ndarray] = None) -> Tuple: blocks = self.cellblocks(inclusive=True, deep=True) @@ -1903,8 +1903,8 @@ def __join_parent__(self, parent: DeepDict, key: Hashable = None) -> None: def __leave_parent__(self) -> None: if self.celldata is not None: self.root.cim.recycle(self.celldata.db.id) - dbkey = self.celldata._dbkey_id_ - del self.celldata._wrapped[dbkey] + dbkey = self.celldata.db._dbkey_id_ + del self.celldata.db._wrapped[dbkey] super().__leave_parent__() def __repr__(self): From ea6a8487f7c4e79d402e8ed62a0b5c89071b2ad0 Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Sat, 4 Nov 2023 00:13:31 +0100 Subject: [PATCH 45/47] added 'natural' option to some quadratures --- src/sigmaepsilon/mesh/utils/numint.py | 71 ++++++++++++++++++++------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/src/sigmaepsilon/mesh/utils/numint.py b/src/sigmaepsilon/mesh/utils/numint.py index d966294..1f4405a 100644 --- a/src/sigmaepsilon/mesh/utils/numint.py +++ b/src/sigmaepsilon/mesh/utils/numint.py @@ -5,7 +5,7 @@ from numpy import ndarray from sigmaepsilon.math.numint import gauss_points as gp -from .tri import nat_to_loc_tri as n2l_tri +from .tri import nat_to_loc_tri as n2l_tri, loc_to_nat_tri as l2n_tri from .tet import nat_to_loc_tet as n2l_tet @@ -43,16 +43,20 @@ def _complete_natural_coordinates(nat: ndarray) -> ndarray: def Gauss_Legendre_Tri_1( - center: Optional[Union[ndarray, None]] = None + center: Optional[Union[ndarray, None]] = None, + natural: Optional[bool] = False ) -> Tuple[ndarray, ndarray]: p, w = np.array([[0.0, 0.0]]), np.array([1 / 2]) if isinstance(center, ndarray): p += center + if natural: + p = np.array([l2n_tri(x, center=center) for x in p], dtype=float) return p, w def Gauss_Legendre_Tri_3a( - center: Optional[Union[ndarray, None]] = None + center: Optional[Union[ndarray, None]] = None, + natural: Optional[bool] = False ) -> Tuple[ndarray, ndarray]: nat = np.array( [ @@ -62,13 +66,17 @@ def Gauss_Legendre_Tri_3a( ], dtype=float, ) - p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + if not natural: + p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + else: + p = nat w = np.array([1 / 6, 1 / 6, 1 / 6]) return p, w def Gauss_Legendre_Tri_3b( - center: Optional[Union[ndarray, None]] = None + center: Optional[Union[ndarray, None]] = None, + natural: Optional[bool] = False ) -> Tuple[ndarray, ndarray]: nat = np.array( [ @@ -78,13 +86,17 @@ def Gauss_Legendre_Tri_3b( ], dtype=float, ) - p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + if not natural: + p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + else: + p = nat w = np.array([1 / 6, 1 / 6, 1 / 6]) return p, w def Gauss_Legendre_Tri_4( - center: Optional[Union[ndarray, None]] = None + center: Optional[Union[ndarray, None]] = None, + natural: Optional[bool] = False ) -> Tuple[ndarray, ndarray]: nat = np.array( [ @@ -95,13 +107,17 @@ def Gauss_Legendre_Tri_4( ], dtype=float, ) - p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + if not natural: + p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + else: + p = nat w = np.array([-0.5625, 0.520833333333333, 0.520833333333333, 0.520833333333333]) / 2 return p, w def Gauss_Legendre_Tri_6( - center: Optional[Union[ndarray, None]] = None + center: Optional[Union[ndarray, None]] = None, + natural: Optional[bool] = False ) -> Tuple[ndarray, ndarray]: nat = np.array( [ @@ -115,7 +131,10 @@ def Gauss_Legendre_Tri_6( dtype=float, ) nat = _complete_natural_coordinates(nat) - p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + if not natural: + p = np.array([n2l_tri(n, center=center) for n in nat], dtype=float) + else: + p = nat w = ( np.array( [ @@ -156,16 +175,21 @@ def Gauss_Legendre_Quad_9() -> Tuple[ndarray, ndarray]: def Gauss_Legendre_Tet_1( - center: Optional[Union[ndarray, None]] = None + center: Optional[Union[ndarray, None]] = None, + natural: Optional[bool] = False ) -> Tuple[ndarray, ndarray]: nat = np.array([[0.25, 0.25, 0.25, 0.25]]) - p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + if not natural: + p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + else: + p = nat w = np.array([1 / 6]) return p, w def Gauss_Legendre_Tet_4( - center: Optional[Union[ndarray, None]] = None + center: Optional[Union[ndarray, None]] = None, + natural: Optional[bool] = False ) -> Tuple[ndarray, ndarray]: nat = np.array( [ @@ -195,13 +219,17 @@ def Gauss_Legendre_Tet_4( ], ] ) - p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + if not natural: + p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + else: + p = nat w = np.full(4, 1 / 24) return p, w def Gauss_Legendre_Tet_5( - center: Optional[Union[ndarray, None]] = None + center: Optional[Union[ndarray, None]] = None, + natural: Optional[bool] = False ) -> Tuple[ndarray, ndarray]: nat = np.array( [ @@ -212,13 +240,17 @@ def Gauss_Legendre_Tet_5( [1 / 6, 1 / 6, 1 / 6, 1 / 2], ] ) - p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + if not natural: + p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + else: + p = nat w = np.array([-4 / 30, 9 / 120, 9 / 120, 9 / 120, 9 / 120]) return p, w def Gauss_Legendre_Tet_11( - center: Optional[Union[ndarray, None]] = None + center: Optional[Union[ndarray, None]] = None, + natural: Optional[bool] = False ) -> Tuple[ndarray, ndarray]: nat = np.array( [ @@ -285,7 +317,10 @@ def Gauss_Legendre_Tet_11( ], ] ) - p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + if not natural: + p = np.array([n2l_tet(n, center=center) for n in nat], dtype=float) + else: + p = nat w = np.array( [ -74 / 5625, From 4c78d9d8925a495ecc88942530e0dc6c8a7adcaa Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Sun, 12 Nov 2023 16:25:17 +0100 Subject: [PATCH 46/47] fixed positional arguments --- src/sigmaepsilon/mesh/utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sigmaepsilon/mesh/utils/utils.py b/src/sigmaepsilon/mesh/utils/utils.py index 636253b..b3d6188 100644 --- a/src/sigmaepsilon/mesh/utils/utils.py +++ b/src/sigmaepsilon/mesh/utils/utils.py @@ -1076,7 +1076,7 @@ def global_shape_function_derivatives(dshp: ndarray, jac: ndarray) -> ndarray: for iE in prange(nE): for iP in prange(nP): invJ = np.linalg.inv(jac[iE, iP]) - res[iE, iP] = dshp[iP] @ invJ + res[iE, iP] = dshp[iP] @ invJ.T return res From 53cac86b836bb19856084588c1132c2ee4508b8a Mon Sep 17 00:00:00 2001 From: Bence Balogh Date: Sun, 12 Nov 2023 16:25:28 +0100 Subject: [PATCH 47/47] fixed positional arguments --- src/sigmaepsilon/mesh/plotting/mpl/triplot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sigmaepsilon/mesh/plotting/mpl/triplot.py b/src/sigmaepsilon/mesh/plotting/mpl/triplot.py index a2f106e..1d7a9e6 100644 --- a/src/sigmaepsilon/mesh/plotting/mpl/triplot.py +++ b/src/sigmaepsilon/mesh/plotting/mpl/triplot.py @@ -42,7 +42,7 @@ def triplot_mpl_data(*_, **__): def triplot_mpl_hinton( triobj: Any, data: ndarray, - *, + *_, fig: Optional[Union[Figure, None]] = None, ax: Optional[Union[Axes, Iterable[Axes], None]] = None, lw: Optional[float] = 0.5, @@ -133,7 +133,7 @@ def triplot_mpl_hinton( @triplotter def triplot_mpl_mesh( triobj: Any, - *, + *_, fig: Optional[Union[Figure, None]] = None, ax: Optional[Union[Axes, None]] = None, lw: Optional[float] = 0.5, @@ -229,7 +229,7 @@ def triplot_mpl_mesh( def triplot_mpl_data( triobj: Any, data: ndarray, - *, + *_, fig: Optional[Union[Figure, None]] = None, ax: Optional[Union[Axes, Iterable[Axes], None]] = None, cmap: Optional[str] = "jet",