From 2ab6697114ee9cbe8319577e5d37939887a511b4 Mon Sep 17 00:00:00 2001 From: Andreas Dutzler Date: Fri, 3 Jan 2025 22:52:08 +0100 Subject: [PATCH] Simplify init of base-class `element.Element` (#924) * Simplify `Element` and add `element.Element` to the top-level namespace * Update _lagrange.py * Don't use `Element.shape` in `"tetra10"` and `"triangle6"` --- CHANGELOG.md | 4 ++++ src/felupe/__init__.py | 2 ++ src/felupe/element/_base.py | 13 +++++------- src/felupe/element/_hexahedron.py | 5 ----- src/felupe/element/_lagrange.py | 6 +++--- src/felupe/element/_line.py | 1 - src/felupe/element/_quad.py | 5 ----- src/felupe/element/_tetra.py | 5 +---- src/felupe/element/_triangle.py | 5 +---- tests/test_element.py | 35 ------------------------------- 10 files changed, 16 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e47b42d..878b32db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. The format - Add `BoundaryDict` as a subclassed dict with methods to `plot()`, `screenshot()` and `imshow()`. - Add a new argument to apply a callable on the assembled vector/matrix of a solid body, `SolidBody(..., apply=None)`. This may be used to sum the list of sub-blocks instead of stacking them together, `SolidBody(..., block=False, apply=None)`. This is useful for mixed formulations where both the deformation gradient and the displacement values are required. - Add support for non-symmetric bilinear mixed forms in `IntegralForm`. +- Add `element.Element` to the top-level package namespace. ### Changed - The first Piola-Kirchhoff stress tensor is evaluated if `ViewSolid(stress_type=None)`. @@ -25,6 +26,9 @@ All notable changes to this project will be documented in this file. The format ### Fixed - Fix `Boundary(..., mode="and")` by ignoring any undefined axis. +### Removed +- Remove the unused `shape`-argument in `element.Element(shape)`. Adopt the arbitrary-lagrange element to use its own `dim`-argument. This simplifies the creation of custom finite element formulations. + ## [9.1.0] - 2024-11-23 ### Added diff --git a/src/felupe/__init__.py b/src/felupe/__init__.py index 4d4fb7af..a5e16468 100644 --- a/src/felupe/__init__.py +++ b/src/felupe/__init__.py @@ -46,6 +46,7 @@ BiQuadraticQuad, ConstantHexahedron, ConstantQuad, + Element, Hexahedron, Line, Quad, @@ -173,6 +174,7 @@ "BiQuadraticQuad", "ConstantHexahedron", "ConstantQuad", + "Element", "Hexahedron", "Line", "Quad", diff --git a/src/felupe/element/_base.py b/src/felupe/element/_base.py index 676696e6..0fd3c1af 100644 --- a/src/felupe/element/_base.py +++ b/src/felupe/element/_base.py @@ -20,9 +20,6 @@ class Element: - def __init__(self, shape): - self.shape = shape - self.dim = self.shape[1] def view(self, point_data=None, cell_data=None, cell_type=None): """View the element with optional given dicts of point- and cell-data items. @@ -91,7 +88,7 @@ def plot( ) if add_point_labels: plotter.add_point_labels( - points=np.pad(self.points, ((0, 0), (0, 3 - self.shape[1]))), + points=np.pad(self.points, ((0, 0), (0, 3 - self.points.shape[1]))), labels=[f"{a}" for a in np.arange(len(self.points))], font_size=font_size, show_points=show_points, @@ -108,19 +105,19 @@ def plot( actor.SetNormalizedShaftLength((0.9, 0.9, 0.9)) actor.SetNormalizedTipLength((0.1, 0.1, 0.1)) - if self.shape[1] == 3: + if self.points.shape[1] == 3: actor.SetTotalLength([1.3, 1.3, 1.3]) - elif self.shape[1] == 2: + elif self.points.shape[1] == 2: actor.SetZAxisLabelText("") actor.SetTotalLength([1.3, 1.3, 0]) - elif self.shape[1] == 1: + elif self.points.shape[1] == 1: actor.SetYAxisLabelText("") actor.SetZAxisLabelText("") actor.SetTotalLength([1.3, 0, 0]) plotter.camera.zoom(0.7) - if self.shape[1] == 3: + if self.points.shape[1] == 3: plotter.camera.azimuth = -17 return plotter diff --git a/src/felupe/element/_hexahedron.py b/src/felupe/element/_hexahedron.py index d8264ca4..2721a044 100644 --- a/src/felupe/element/_hexahedron.py +++ b/src/felupe/element/_hexahedron.py @@ -69,7 +69,6 @@ def __init__(self): ) self.cells = np.arange(len(self.points)).reshape(1, -1) self.cell_type = "hexahedron" - super().__init__(shape=(1, 3)) def function(self, rst): "Return the shape functions at given coordinates (r, s, t)." @@ -140,7 +139,6 @@ def __init__(self): ) self.cells = np.arange(len(self.points)).reshape(1, -1) self.cell_type = "hexahedron" - super().__init__(shape=(8, 3)) def function(self, rst): "Return the shape functions at given coordinates (r, s, t)." @@ -263,7 +261,6 @@ def __init__(self): ) self.cells = np.arange(len(self.points)).reshape(1, -1) self.cell_type = "hexahedron20" - super().__init__(shape=(20, 3)) def function(self, rst): "Return the shape functions at given coordinates (r, s, t)." @@ -471,8 +468,6 @@ class TriQuadraticHexahedron(Element): """ def __init__(self): - super().__init__(shape=(27, 3)) - self.points = np.array( [ [-1, -1, -1], diff --git a/src/felupe/element/_lagrange.py b/src/felupe/element/_lagrange.py index fef8fe89..5d30036c 100644 --- a/src/felupe/element/_lagrange.py +++ b/src/felupe/element/_lagrange.py @@ -261,14 +261,14 @@ def __init__(self, order, dim, interval=(-1, 1), permute=True): self._nbasis = self._npoints self._interval = interval + self.dim = dim + self.permute = None if permute: self.permute = [None, lagrange_line, lagrange_quad, lagrange_hexahedron][ dim ](order) - super().__init__(shape=(self._npoints, dim)) - # init curve-parameter matrix n = self._nshape self._AT = np.linalg.inv( @@ -278,7 +278,7 @@ def __init__(self, order, dim, interval=(-1, 1), permute=True): # indices for outer product in einstein notation # idx = ["a", "b", "c", ...][:dim] # subscripts = "a,b,c -> abc" - self._idx = [letter for letter in alphabet][: self.dim] + self._idx = [letter for letter in alphabet][:dim] self._subscripts = ",".join(self._idx) + "->" + "".join(self._idx) # init points diff --git a/src/felupe/element/_line.py b/src/felupe/element/_line.py index 12c44aaf..2b9b3d75 100644 --- a/src/felupe/element/_line.py +++ b/src/felupe/element/_line.py @@ -45,7 +45,6 @@ class Line(Element): """ def __init__(self): - super().__init__(shape=(2, 1)) self.points = np.array([-1, 1], dtype=float).reshape(-1, 1) self.cells = np.arange(len(self.points)).reshape(1, -1) self.cell_type = "line" diff --git a/src/felupe/element/_quad.py b/src/felupe/element/_quad.py index 3491181f..65ecc52e 100644 --- a/src/felupe/element/_quad.py +++ b/src/felupe/element/_quad.py @@ -52,7 +52,6 @@ class ConstantQuad(Element): """ def __init__(self): - super().__init__(shape=(1, 2)) self.points = np.array([[-1, -1], [1, -1], [1, 1], [-1, 1]], dtype=float) self.cells = np.arange(len(self.points)).reshape(1, -1) self.cell_type = "quad" @@ -106,7 +105,6 @@ class Quad(Element): """ def __init__(self): - super().__init__(shape=(4, 2)) self.points = np.array([[-1, -1], [1, -1], [1, 1], [-1, 1]], dtype=float) self.cells = np.arange(len(self.points)).reshape(1, -1) self.cell_type = "quad" @@ -188,7 +186,6 @@ class QuadraticQuad(Element): """ def __init__(self): - super().__init__(shape=(8, 2)) self.points = np.array( [ [-1, -1], @@ -289,8 +286,6 @@ class BiQuadraticQuad(Element): """ def __init__(self): - super().__init__(shape=(9, 2)) - self._lagrange = ArbitraryOrderLagrange(order=2, dim=2, permute=False) self._vertices = np.array([0, 2, 8, 6]) diff --git a/src/felupe/element/_tetra.py b/src/felupe/element/_tetra.py index 58e315c1..2e30974c 100644 --- a/src/felupe/element/_tetra.py +++ b/src/felupe/element/_tetra.py @@ -57,7 +57,6 @@ class Tetra(Element): """ def __init__(self): - super().__init__(shape=(4, 3)) self.points = np.array( [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=float ) @@ -117,7 +116,6 @@ class TetraMINI(Element): """ def __init__(self, bubble_multiplier=1.0): - super().__init__(shape=(5, 3)) self.points = np.array( [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [1 / 3, 1 / 3, 1 / 3]], dtype=float, @@ -237,8 +235,7 @@ class QuadraticTetra(Element): """ def __init__(self): - super().__init__(shape=(10, 3)) - self.points = np.zeros(self.shape) + self.points = np.zeros((10, 3)) self.points[:4] = np.array( [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=float ) diff --git a/src/felupe/element/_triangle.py b/src/felupe/element/_triangle.py index deeaf797..8285c8ea 100644 --- a/src/felupe/element/_triangle.py +++ b/src/felupe/element/_triangle.py @@ -58,7 +58,6 @@ class Triangle(Element): """ def __init__(self): - super().__init__(shape=(3, 2)) self.points = np.array([[0, 0], [1, 0], [0, 1]], dtype=float) self.cells = np.arange(len(self.points)).reshape(1, -1) self.cell_type = "triangle" @@ -115,7 +114,6 @@ class TriangleMINI(Element): """ def __init__(self, bubble_multiplier=1.0): - super().__init__(shape=(4, 2)) self.points = np.array([[0, 0], [1, 0], [0, 1], [1 / 3, 1 / 3]], dtype=float) self.cells = np.arange(len(self.points) - 1).reshape(1, -1) self.cell_type = "triangle" @@ -198,8 +196,7 @@ class QuadraticTriangle(Element): """ def __init__(self): - super().__init__(shape=(6, 2)) - self.points = np.zeros(self.shape) + self.points = np.zeros((6, 2)) self.points[:3] = np.array([[0, 0], [1, 0], [0, 1]], dtype=float) self.points[3] = np.mean(self.points[[0, 1]], axis=0) self.points[4] = np.mean(self.points[[1, 2]], axis=0) diff --git a/tests/test_element.py b/tests/test_element.py index f90aaac8..a8c78571 100644 --- a/tests/test_element.py +++ b/tests/test_element.py @@ -43,8 +43,6 @@ def test_line2(): assert np.all(dhdr[0] == -0.5) assert np.all(d2hdrdr == 0.0) - assert line2.shape == dhdr.shape - line2.plot(off_screen=True) @@ -59,8 +57,6 @@ def test_line_lagrange(): assert np.isclose(h[0], 1) assert np.isclose(dhdr[0, 0], -5.70833333) - assert line6.shape == dhdr.shape - line6.plot(off_screen=True) @@ -77,8 +73,6 @@ def test_quad0(): assert np.all(dhdr[0] == 0) assert np.all(d2hdrdr == 0.0) - assert quad0.shape == dhdr.shape - def test_quad4(): quad4 = fem.element.Quad() @@ -93,8 +87,6 @@ def test_quad4(): assert np.all(dhdr[0] == -0.5) assert d2hdrdr.shape == (4, 2, 2) - assert quad4.shape == dhdr.shape - quad4.plot(off_screen=True) @@ -111,8 +103,6 @@ def test_quad8(): assert np.all(dhdr[0] == -1.5) assert d2hdrdr.shape == (8, 2, 2) - assert quad8.shape == dhdr.shape - def test_quad9(): quad9 = fem.element.BiQuadraticQuad() @@ -125,8 +115,6 @@ def test_quad9(): assert h[0] == 1 assert np.all(dhdr[0] == -1.5) - assert quad9.shape == dhdr.shape - def test_hex0(): hex0 = fem.element.ConstantHexahedron() @@ -141,8 +129,6 @@ def test_hex0(): assert np.all(dhdr[0] == 0) assert np.all(d2hdrdr == 0) - assert hex0.shape == dhdr.shape - def test_hex8(): hex8 = fem.element.Hexahedron() @@ -157,8 +143,6 @@ def test_hex8(): assert np.all(dhdr[0] == -0.5) assert d2hdrdr.shape == (8, 3, 3) - assert hex8.shape == dhdr.shape - def test_hex20(): hex20 = fem.element.QuadraticHexahedron() @@ -171,8 +155,6 @@ def test_hex20(): assert h[0] == 1 assert np.all(dhdr[0] == -1.5) - assert hex20.shape == dhdr.shape - hex20.plot(off_screen=True) @@ -187,8 +169,6 @@ def test_hex27(): assert h[0] == 1 assert np.all(dhdr[0] == -1.5) - assert hex27.shape == dhdr.shape - hex27.plot(off_screen=True) @@ -205,9 +185,6 @@ def test_tri3(): assert np.all(dhdr[0] == -1) assert np.all(d2hdrdr == 0) - assert tri3.shape == dhdr.shape - - def test_tri6(): tri6 = fem.element.QuadraticTriangle() @@ -219,8 +196,6 @@ def test_tri6(): assert h[0] == 1 assert np.all(dhdr[0] == -3) - assert tri6.shape == dhdr.shape - def test_tri_mini(): trim = fem.element.TriangleMINI() @@ -236,8 +211,6 @@ def test_tri_mini(): assert np.all(dhdr[0] == -1) assert d2hdrdr.shape == (4, 2, 2) - assert trim.shape == dhdr.shape - def test_tet4(): tet4 = fem.element.Tetra() @@ -252,8 +225,6 @@ def test_tet4(): assert np.all(dhdr[0] == -1) assert np.all(d2hdrdr == 0) - assert tet4.shape == dhdr.shape - def test_tet10(): tet10 = fem.element.QuadraticTetra() @@ -266,8 +237,6 @@ def test_tet10(): assert h[0] == 1 assert np.all(dhdr[0] == -3) - assert tet10.shape == dhdr.shape - def test_tet_mini(): tetm = fem.element.TetraMINI() @@ -283,8 +252,6 @@ def test_tet_mini(): assert np.all(dhdr[0] == -1) assert d2hdrdr.shape == (5, 3, 3) - assert tetm.shape == dhdr.shape - def test_aol(): aol32 = fem.element.ArbitraryOrderLagrange(order=3, dim=2) @@ -296,7 +263,6 @@ def test_aol(): dhdr = aol32.gradient(r) assert h[0] == 1 - assert aol32.shape == dhdr.shape r = [-1, -1, -1] @@ -304,7 +270,6 @@ def test_aol(): dhdr = aol23.gradient(r) assert h[0] == 1 - assert aol23.shape == dhdr.shape if __name__ == "__main__":