From a7ffe7fbadf8e9bab713d120f9cddfadd2b3ce08 Mon Sep 17 00:00:00 2001 From: Sara Sundal Schei Date: Wed, 7 May 2025 10:55:12 +0200 Subject: [PATCH 1/9] Add ElasticElastic2D material class with tests --- .../materials/constitutive_laws/__init__.py | 6 ++ .../constitutive_laws/_elasticplastic_2d.py | 76 +++++++++++++++++++ .../test_materials/test_elasticplastid_2d.py | 51 +++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py create mode 100644 tests/test_materials/test_elasticplastid_2d.py diff --git a/structuralcodes/materials/constitutive_laws/__init__.py b/structuralcodes/materials/constitutive_laws/__init__.py index a9985857..c26f3f98 100644 --- a/structuralcodes/materials/constitutive_laws/__init__.py +++ b/structuralcodes/materials/constitutive_laws/__init__.py @@ -7,7 +7,9 @@ from ._elastic import Elastic from ._elastic_2d import Elastic2D from ._elasticplastic import ElasticPlastic +from ._elasticplastic_2d import ElasticPlastic2D from ._parabolarectangle import ParabolaRectangle +from ._parabolarectangle_2d import ParabolaRectangle2D from ._popovics import Popovics from ._sargin import Sargin from ._userdefined import UserDefined @@ -23,6 +25,8 @@ 'UserDefined', 'get_constitutive_laws_list', 'create_constitutive_law', + 'ElasticPlastic2D', + 'ParabolaRectangle2D', ] CONSTITUTIVE_LAWS: t.Dict[str, ConstitutiveLaw] = { @@ -34,6 +38,8 @@ 'parabolarectangle': ParabolaRectangle, 'popovics': Popovics, 'sargin': Sargin, + 'elasticplastic2d': ElasticPlastic2D, + 'parabolarectangle2d': ParabolaRectangle2D, } diff --git a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py new file mode 100644 index 00000000..01684be0 --- /dev/null +++ b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py @@ -0,0 +1,76 @@ +"""Elastic-plastic constitutive law.""" + +from __future__ import annotations + +import typing as t + +import numpy as np +from numpy.typing import ArrayLike + +from structuralcodes.materials.constitutive_laws._elasticplastic import ( + ElasticPlastic, +) + + +class ElasticPlastic2D(ElasticPlastic): + """Class for elastic-plastic Constitutive Law in 2D.""" + + __materials__: t.Tuple[str] = ( + 'steel', + 'rebars', + ) + + def __init__( + self, + E: float, + fy: float, + Eh: float = 0.0, + eps_su: t.Optional[float] = None, + name: t.Optional[str] = None, + ) -> None: + """Initialize an Elastic-Plastic 2D Material. + + Arguments: + E (float): The elastic modulus. + fy (float): The yield strength. + + Keyword Arguments: + Eh (float): The hardening modulus. + eps_su (float): The ultimate strain. + name (str): A descriptive name for the constitutive law. + """ + name = name if name is not None else 'ElasticPlasticLaw2D' + super().__init__(E=E, fy=fy, Eh=Eh, eps_su=eps_su, name=name) + if E > 0: + self._E = E + else: + raise ValueError('Elastic modulus E must be greater than zero') + self._fy = fy + self._Eh = Eh + self._eps_su = eps_su + self._eps_sy = fy / E + + @property + def E(self) -> float: + """Return the elastic modulus.""" + return self._E + + @property + def C_s(self) -> np.ndarray: + """Return the 2D constitutive matrix.""" + return self.E * np.array( + [ + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 0.0], + ] + ) + + def get_stress(self, eps: ArrayLike) -> np.ndarray: + """Return the stress given strain.""" + sig_s = super().get_stress(eps) + return sig_s @ self.C_s / self.E + + def get_secant(self) -> np.ndarray: + """Compute the 3x3 secant stiffness matrix C.""" + return self.C_s diff --git a/tests/test_materials/test_elasticplastid_2d.py b/tests/test_materials/test_elasticplastid_2d.py new file mode 100644 index 00000000..70008b00 --- /dev/null +++ b/tests/test_materials/test_elasticplastid_2d.py @@ -0,0 +1,51 @@ +import numpy as np +import pytest + +from structuralcodes.materials.constitutive_laws import ElasticPlastic2D + + +@pytest.mark.parametrize( + 'E, fy, strain', + [ + (210000, 410, np.array([0.001, 0.0, 0.0])), + (210000, 410, np.array([-0.002, -0.1, -0.002])), + (200000, 450, np.array([0.003, 0.005, 0.010])), + ], +) +def test_elasticplastic_2d_get_stress(E, fy, strain): + """Test elasticplastic 2D material.""" + mat = ElasticPlastic2D(E, fy) + actual = mat.get_stress(strain) + + expected = np.array( + [ + np.clip(E * strain[0], -fy, +fy), + np.clip(E * strain[1], -fy, +fy), + 0.0, + ] + ) + + assert np.allclose(actual, expected, atol=1e-8) + + +def test_elasticplastic_2d_input_correct(): + """Test invalid input values for ElasticPlastic2D.""" + with pytest.raises(ValueError) as excinfo: + ElasticPlastic2D(-210000, 500) + assert str(excinfo.value) == 'Elastic modulus E must be greater than zero' + + +@pytest.mark.parametrize( + 'E, fy', + [ + (210000, 500), + (200000, 500), + (195000, 500), + ], +) +def test_elasticplastic_get_secant(E, fy): + """Test the elasticPlastic2D material.""" + assert np.allclose( + ElasticPlastic2D(E, fy).get_secant(), + np.array([[E, 0, 0], [0, E, 0], [0, 0, 0]]), + ) From 0a7b7691de784b0477f4cbbe69349cfb8fdf6a09 Mon Sep 17 00:00:00 2001 From: Sara Sundal Schei Date: Wed, 7 May 2025 11:04:16 +0200 Subject: [PATCH 2/9] Removed redundant lines inherited from the parent class --- .../materials/constitutive_laws/_elasticplastic_2d.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py index 01684be0..3ed711a8 100644 --- a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py +++ b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py @@ -41,14 +41,6 @@ def __init__( """ name = name if name is not None else 'ElasticPlasticLaw2D' super().__init__(E=E, fy=fy, Eh=Eh, eps_su=eps_su, name=name) - if E > 0: - self._E = E - else: - raise ValueError('Elastic modulus E must be greater than zero') - self._fy = fy - self._Eh = Eh - self._eps_su = eps_su - self._eps_sy = fy / E @property def E(self) -> float: From 68cc9974c1e4f36e8e69aab057aeb52883143663 Mon Sep 17 00:00:00 2001 From: Sara Sundal Schei Date: Wed, 7 May 2025 11:08:24 +0200 Subject: [PATCH 3/9] Removed redundant lines inherited from the parent class and changed name from secant to tangent --- .../materials/constitutive_laws/_elasticplastic_2d.py | 10 +--------- tests/test_materials/test_elasticplastid_2d.py | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py index 01684be0..72a04717 100644 --- a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py +++ b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py @@ -41,14 +41,6 @@ def __init__( """ name = name if name is not None else 'ElasticPlasticLaw2D' super().__init__(E=E, fy=fy, Eh=Eh, eps_su=eps_su, name=name) - if E > 0: - self._E = E - else: - raise ValueError('Elastic modulus E must be greater than zero') - self._fy = fy - self._Eh = Eh - self._eps_su = eps_su - self._eps_sy = fy / E @property def E(self) -> float: @@ -71,6 +63,6 @@ def get_stress(self, eps: ArrayLike) -> np.ndarray: sig_s = super().get_stress(eps) return sig_s @ self.C_s / self.E - def get_secant(self) -> np.ndarray: + def get_tangent(self) -> np.ndarray: """Compute the 3x3 secant stiffness matrix C.""" return self.C_s diff --git a/tests/test_materials/test_elasticplastid_2d.py b/tests/test_materials/test_elasticplastid_2d.py index 70008b00..21aba32b 100644 --- a/tests/test_materials/test_elasticplastid_2d.py +++ b/tests/test_materials/test_elasticplastid_2d.py @@ -43,9 +43,9 @@ def test_elasticplastic_2d_input_correct(): (195000, 500), ], ) -def test_elasticplastic_get_secant(E, fy): +def test_elasticplastic_get_tangent(E, fy): """Test the elasticPlastic2D material.""" assert np.allclose( - ElasticPlastic2D(E, fy).get_secant(), + ElasticPlastic2D(E, fy).get_tangent(), np.array([[E, 0, 0], [0, E, 0], [0, 0, 0]]), ) From a5f6be7f07d3f272969d3eacf4c913612926df0f Mon Sep 17 00:00:00 2001 From: Sara Sundal Schei Date: Wed, 7 May 2025 11:20:18 +0200 Subject: [PATCH 4/9] Change from secant to tangent --- .../materials/constitutive_laws/_elasticplastic_2d.py | 2 +- tests/test_materials/test_elasticplastid_2d.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py index 72a04717..666c796d 100644 --- a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py +++ b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py @@ -64,5 +64,5 @@ def get_stress(self, eps: ArrayLike) -> np.ndarray: return sig_s @ self.C_s / self.E def get_tangent(self) -> np.ndarray: - """Compute the 3x3 secant stiffness matrix C.""" + """Compute the 3x3 tangent stiffness matrix C.""" return self.C_s diff --git a/tests/test_materials/test_elasticplastid_2d.py b/tests/test_materials/test_elasticplastid_2d.py index 21aba32b..4c785fda 100644 --- a/tests/test_materials/test_elasticplastid_2d.py +++ b/tests/test_materials/test_elasticplastid_2d.py @@ -44,7 +44,7 @@ def test_elasticplastic_2d_input_correct(): ], ) def test_elasticplastic_get_tangent(E, fy): - """Test the elasticPlastic2D material.""" + """Test the elasticPlastic2D tangent matrix.""" assert np.allclose( ElasticPlastic2D(E, fy).get_tangent(), np.array([[E, 0, 0], [0, E, 0], [0, 0, 0]]), From 47e415116dc1677f977ac0f8ccc39545f43f0687 Mon Sep 17 00:00:00 2001 From: Sara Sundal Schei Date: Thu, 8 May 2025 10:13:31 +0200 Subject: [PATCH 5/9] Added tests --- tests/test_materials/test_elasticplastid_2d.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_materials/test_elasticplastid_2d.py b/tests/test_materials/test_elasticplastid_2d.py index 4c785fda..fa056bd7 100644 --- a/tests/test_materials/test_elasticplastid_2d.py +++ b/tests/test_materials/test_elasticplastid_2d.py @@ -4,6 +4,16 @@ from structuralcodes.materials.constitutive_laws import ElasticPlastic2D +def test_elasticplastic_2d_init(): + """Test elasticplastic 2D material.""" + mat = ElasticPlastic2D(210000, 410) + assert mat.E == 210000 + assert mat._fy == 410 + assert mat._Eh == 0.0 + assert mat._eps_su is None + assert mat.name == 'ElasticPlasticLaw2D' + + @pytest.mark.parametrize( 'E, fy, strain', [ From 5322332210a376e4c2e34d4c74914f94ec7e756a Mon Sep 17 00:00:00 2001 From: Sara Sundal Schei Date: Tue, 13 May 2025 15:39:40 +0200 Subject: [PATCH 6/9] Added docstring --- tests/test_materials/test_elasticplastid_2d.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_materials/test_elasticplastid_2d.py b/tests/test_materials/test_elasticplastid_2d.py index fa056bd7..d5f85f30 100644 --- a/tests/test_materials/test_elasticplastid_2d.py +++ b/tests/test_materials/test_elasticplastid_2d.py @@ -1,3 +1,5 @@ +"""Tests for the ElasticPlastic2D class.""" + import numpy as np import pytest From 2a960cd126dc3388bbacea681c18c1e6db49304e Mon Sep 17 00:00:00 2001 From: Sara Sundal Schei Date: Tue, 13 May 2025 17:53:53 +0200 Subject: [PATCH 7/9] Add strain parameter to get_tangent function --- .../materials/constitutive_laws/_elasticplastic_2d.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py index 666c796d..ad517f68 100644 --- a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py +++ b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py @@ -63,6 +63,7 @@ def get_stress(self, eps: ArrayLike) -> np.ndarray: sig_s = super().get_stress(eps) return sig_s @ self.C_s / self.E - def get_tangent(self) -> np.ndarray: + def get_tangent(self, eps: ArrayLike) -> np.ndarray: """Compute the 3x3 tangent stiffness matrix C.""" + eps = np.atleast_1d(eps) return self.C_s From 33629247a6a2445b516ccf9d3b56f0a7dfab873a Mon Sep 17 00:00:00 2001 From: Sara Sundal Schei Date: Wed, 14 May 2025 08:34:29 +0200 Subject: [PATCH 8/9] Removed ParabolaRectangle2D from __init__ --- structuralcodes/materials/constitutive_laws/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/structuralcodes/materials/constitutive_laws/__init__.py b/structuralcodes/materials/constitutive_laws/__init__.py index c26f3f98..66b3c1bb 100644 --- a/structuralcodes/materials/constitutive_laws/__init__.py +++ b/structuralcodes/materials/constitutive_laws/__init__.py @@ -9,7 +9,6 @@ from ._elasticplastic import ElasticPlastic from ._elasticplastic_2d import ElasticPlastic2D from ._parabolarectangle import ParabolaRectangle -from ._parabolarectangle_2d import ParabolaRectangle2D from ._popovics import Popovics from ._sargin import Sargin from ._userdefined import UserDefined @@ -26,7 +25,6 @@ 'get_constitutive_laws_list', 'create_constitutive_law', 'ElasticPlastic2D', - 'ParabolaRectangle2D', ] CONSTITUTIVE_LAWS: t.Dict[str, ConstitutiveLaw] = { @@ -39,7 +37,6 @@ 'popovics': Popovics, 'sargin': Sargin, 'elasticplastic2d': ElasticPlastic2D, - 'parabolarectangle2d': ParabolaRectangle2D, } From 5cf1f0dceb3e1fefbcda10c9dda4aab101b572aa Mon Sep 17 00:00:00 2001 From: Sara Sundal Schei Date: Fri, 16 May 2025 13:47:20 +0200 Subject: [PATCH 9/9] Removed eps from get_tangent --- .../materials/constitutive_laws/_elasticplastic_2d.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py index ad517f68..666c796d 100644 --- a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py +++ b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py @@ -63,7 +63,6 @@ def get_stress(self, eps: ArrayLike) -> np.ndarray: sig_s = super().get_stress(eps) return sig_s @ self.C_s / self.E - def get_tangent(self, eps: ArrayLike) -> np.ndarray: + def get_tangent(self) -> np.ndarray: """Compute the 3x3 tangent stiffness matrix C.""" - eps = np.atleast_1d(eps) return self.C_s