diff --git a/structuralcodes/materials/constitutive_laws/__init__.py b/structuralcodes/materials/constitutive_laws/__init__.py index e2ff8f4e..97268c47 100644 --- a/structuralcodes/materials/constitutive_laws/__init__.py +++ b/structuralcodes/materials/constitutive_laws/__init__.py @@ -7,6 +7,7 @@ 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 @@ -25,6 +26,7 @@ 'UserDefined', 'get_constitutive_laws_list', 'create_constitutive_law', + 'ElasticPlastic2D', ] CONSTITUTIVE_LAWS: t.Dict[str, ConstitutiveLaw] = { @@ -37,6 +39,7 @@ 'parabolarectangle2d': ParabolaRectangle2D, 'popovics': Popovics, 'sargin': Sargin, + 'elasticplastic2d': ElasticPlastic2D, } diff --git a/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py new file mode 100644 index 00000000..666c796d --- /dev/null +++ b/structuralcodes/materials/constitutive_laws/_elasticplastic_2d.py @@ -0,0 +1,68 @@ +"""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) + + @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_tangent(self) -> np.ndarray: + """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 new file mode 100644 index 00000000..d5f85f30 --- /dev/null +++ b/tests/test_materials/test_elasticplastid_2d.py @@ -0,0 +1,63 @@ +"""Tests for the ElasticPlastic2D class.""" + +import numpy as np +import pytest + +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', + [ + (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_tangent(E, fy): + """Test the elasticPlastic2D tangent matrix.""" + assert np.allclose( + ElasticPlastic2D(E, fy).get_tangent(), + np.array([[E, 0, 0], [0, E, 0], [0, 0, 0]]), + )