diff --git a/blueprints/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/formula_8_23.py b/blueprints/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/formula_8_23.py new file mode 100644 index 000000000..e35c0966e --- /dev/null +++ b/blueprints/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/formula_8_23.py @@ -0,0 +1,83 @@ +"""Formula 8.23 from EN 1993-1-1:2022: Chapter 8 - Ultimate Limit State.""" + +import numpy as np + +from blueprints.codes.eurocode.en_1993_1_1_2022 import EN_1993_1_1_2022 +from blueprints.codes.formula import Formula +from blueprints.codes.latex_formula import LatexFormula, latex_replace_symbols +from blueprints.type_alias import DIMENSIONLESS, MM2, MPA, N +from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative + + +class Form8Dot23DesignPlasticShearResistance(Formula): + r"""Class representing formula 8.23 for the calculation of [$V_{pl,Rd}$].""" + + label = "8.23" + source_document = EN_1993_1_1_2022 + + def __init__( + self, + a_v: MM2, + f_y: MPA, + gamma_m0: DIMENSIONLESS, + ) -> None: + r"""[$V_{pl,Rd}$] Calculation of the design plastic shear resistance [$N$]. + + EN 1993-1-1:2022 art.8.2.6(2) - Formula (8.23) + + Parameters + ---------- + a_v : MM2 + [$A_v$] Shear area, to be taken from a subformula from 8.23 [$mm^2$]. + f_y : MPA + [$f_y$] Yield strength of the material [$MPa$]. + gamma_m0 : DIMENSIONLESS + [$\gamma_{M0}$] Partial safety factor for resistance of cross-sections. + """ + super().__init__() + self.a_v = a_v + self.f_y = f_y + self.gamma_m0 = gamma_m0 + + @staticmethod + def _evaluate( + a_v: MM2, + f_y: MPA, + gamma_m0: DIMENSIONLESS, + ) -> N: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(a_v=a_v, f_y=f_y) + raise_if_less_or_equal_to_zero(gamma_m0=gamma_m0) + + return (a_v * (f_y / np.sqrt(3))) / gamma_m0 + + def latex(self, n: int = 3) -> LatexFormula: + """Returns LatexFormula object for formula 8.23.""" + _equation: str = r"\frac{A_v \cdot (f_y / \sqrt{3})}{\gamma_{M0}}" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"A_v": f"{self.a_v:.{n}f}", + r"f_y": f"{self.f_y:.{n}f}", + r"\gamma_{M0}": f"{self.gamma_m0:.{n}f}", + }, + False, + ) + _numeric_equation_with_units: str = latex_replace_symbols( + _equation, + { + r"A_v": rf"{self.a_v:.{n}f} \ mm^2", + r"f_y": rf"{self.f_y:.{n}f} \ MPa", + r"\gamma_{M0}": f"{self.gamma_m0:.{n}f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"V_{pl,Rd}", + result=f"{self:.{n}f}", + equation=_equation, + numeric_equation=_numeric_equation, + numeric_equation_with_units=_numeric_equation_with_units, + comparison_operator_label="=", + unit="N", + ) diff --git a/blueprints/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/formula_8_25.py b/blueprints/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/formula_8_25.py new file mode 100644 index 000000000..3e8166031 --- /dev/null +++ b/blueprints/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/formula_8_25.py @@ -0,0 +1,89 @@ +"""Formula 8.25 from EN 1993-1-1:2022: Chapter 8 - Ultimate Limit State.""" + +from blueprints.codes.eurocode.en_1993_1_1_2022 import EN_1993_1_1_2022 +from blueprints.codes.formula import Formula +from blueprints.codes.latex_formula import LatexFormula, latex_replace_symbols +from blueprints.type_alias import MM, MM3, MM4, MPA, N +from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative + + +class Form8Dot25ShearStress(Formula): + r"""Class representing formula 8.25 for the calculation of [$\tau_{Ed}$].""" + + label = "8.25" + source_document = EN_1993_1_1_2022 + + def __init__( + self, + v_ed: N, + s: MM3, + i: MM4, + t: MM, + ) -> None: + r"""[$\tau_{Ed}$] Calculation of the design elastic shear stress [$MPa$]. + + EN 1993-1-1:2022 art.8.2.6(4) - Formula (8.25) + + Parameters + ---------- + v_ed : N + [$V_{Ed}$] Design value of the shear force [$N$]. + s : MM3 + [$S$] First moment of area about the centroidal axis of that portion of the cross-section between + the point at which the shear is required and the boundary of the cross-section [$mm^3$]. + i : MM4 + [$I$] Second moment of area of the whole cross section [$mm^4$]. + t : MM + [$t$] Thickness at the examined point [$mm$]. + """ + super().__init__() + self.v_ed = v_ed + self.s = s + self.i = i + self.t = t + + @staticmethod + def _evaluate( + v_ed: N, + s: MM3, + i: MM4, + t: MM, + ) -> MPA: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(v_ed=v_ed, s=s) + raise_if_less_or_equal_to_zero(i=i, t=t) + + return (v_ed * s) / (i * t) + + def latex(self, n: int = 3) -> LatexFormula: + """Returns LatexFormula object for formula 8.25.""" + _equation: str = r"\frac{V_{Ed} \cdot S}{I \cdot t}" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"V_{Ed}": f"{self.v_ed:.{n}f}", + r"S": f"{self.s:.{n}f}", + r"I": f"{self.i:.{n}f}", + r" t": f" {self.t:.{n}f}", + }, + False, + ) + _numeric_equation_with_units: str = latex_replace_symbols( + _equation, + { + r"V_{Ed}": rf"{self.v_ed:.{n}f} \ N", + r"S": rf"{self.s:.{n}f} \ mm^3", + r"I": rf"{self.i:.{n}f} \ mm^4", + r" t": rf" {self.t:.{n}f} \ mm", + }, + False, + ) + return LatexFormula( + return_symbol=r"\tau_{Ed}", + result=f"{self:.{n}f}", + equation=_equation, + numeric_equation=_numeric_equation, + numeric_equation_with_units=_numeric_equation_with_units, + comparison_operator_label="=", + unit="MPa", + ) diff --git a/blueprints/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/formula_8_26.py b/blueprints/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/formula_8_26.py new file mode 100644 index 000000000..b0745bdd5 --- /dev/null +++ b/blueprints/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/formula_8_26.py @@ -0,0 +1,84 @@ +"""Formula 8.26 from EN 1993-1-1:2022: Chapter 8 - Ultimate Limit State.""" + +from blueprints.codes.eurocode.en_1993_1_1_2022 import EN_1993_1_1_2022 +from blueprints.codes.formula import Formula +from blueprints.codes.latex_formula import LatexFormula, latex_replace_symbols +from blueprints.type_alias import MM2, MPA, N +from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative + + +class Form8Dot26ShearStressIOrHSection(Formula): + r"""Class representing formula 8.26 for the calculation of [$\tau_{Ed}$].""" + + label = "8.26" + source_document = EN_1993_1_1_2022 + + def __init__( + self, + v_ed: N, + a_w: MM2, + a_f: MM2, + ) -> None: + r"""[$\tau_{Ed}$] Calculation of the design elastic shear stress [$MPa$]. + For I- or H-sections the shear stress in the web may be taken with this equation. + + EN 1993-1-1:2022 art.8.2.6(5) - Formula (8.26) + + Parameters + ---------- + v_ed : N + [$V_{Ed}$] Design shear force [$N$]. + a_w : MM2 + [$A_w$] Area of the web [$mm^2$]. + a_f : MM2 + [$A_f$] Area of one flange [$mm^2$]. + """ + super().__init__() + self.v_ed = v_ed + self.a_w = a_w + self.a_f = a_f + + @staticmethod + def _evaluate( + v_ed: N, + a_w: MM2, + a_f: MM2, + ) -> MPA: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(v_ed=v_ed, a_f=a_f) + raise_if_less_or_equal_to_zero(a_w=a_w) + if not a_f / a_w >= 0.6: + raise ValueError("A_f / A_w must be greater than or equal to 0.6") + + return v_ed / a_w + + def latex(self, n: int = 3) -> LatexFormula: + """Returns LatexFormula object for formula 8.26.""" + _equation: str = r"\frac{V_{Ed}}{A_w} \text{ if } A_f / A_w \ge 0.6" + _numeric_equation: str = latex_replace_symbols( + _equation, + replacements={ + r"V_{Ed}": f"{self.v_ed:.{n}f}", + r"A_w": f"{self.a_w:.{n}f}", + r"A_f": f"{self.a_f:.{n}f}", + }, + unique_symbol_check=False, + ) + _numeric_equation_with_units: str = latex_replace_symbols( + _equation, + replacements={ + r"V_{Ed}": rf"{self.v_ed:.{n}f} \ N", + r"A_w": rf"{self.a_w:.{n}f} \ mm^2", + r"A_f": rf"{self.a_f:.{n}f} \ mm^2", + }, + unique_symbol_check=False, + ) + return LatexFormula( + return_symbol=r"\tau_{Ed}", + result=f"{self:.{n}f}", + equation=_equation, + numeric_equation=_numeric_equation, + numeric_equation_with_units=_numeric_equation_with_units, + comparison_operator_label="=", + unit="MPa", + ) diff --git a/tests/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/test_formula_8_23.py b/tests/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/test_formula_8_23.py new file mode 100644 index 000000000..e2703552d --- /dev/null +++ b/tests/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/test_formula_8_23.py @@ -0,0 +1,84 @@ +"""Testing formula 8.23 from EN 1993-1-1:2022, chapter 8, ultimate limit state.""" + +import pytest + +from blueprints.codes.eurocode.en_1993_1_1_2022.chapter_8_ultimate_limit_state.formula_8_23 import Form8Dot23DesignPlasticShearResistance +from blueprints.validations import LessOrEqualToZeroError, NegativeValueError + + +class TestForm8Dot23DesignPlasticShearResistance: + """Validation for formula 8.23 from EN 1993-1-1:2022, chapter 8, ultimate limit state.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + # Example values + a_v = 2000.0 + f_y = 355.0 + gamma_m0 = 1.0 + + # Object to test + formula = Form8Dot23DesignPlasticShearResistance(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0) + + # Expected result, manually calculated + manually_calculated_result = 409918.6911246343 # N + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("a_v", "f_y", "gamma_m0"), + [ + (-2000.0, 355.0, 1.0), # a_v is negative + (2000.0, -355.0, 1.0), # f_y is negative + (2000.0, 355.0, -1.0), # gamma_m0 is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, a_v: float, f_y: float, gamma_m0: float) -> None: + """Test invalid values.""" + with pytest.raises((NegativeValueError, LessOrEqualToZeroError)): + Form8Dot23DesignPlasticShearResistance(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0) + + @pytest.mark.parametrize( + ("a_v", "f_y", "gamma_m0"), + [ + (2000.0, 355.0, 0.0), # gamma_m0 is zero + (2000.0, 355.0, -1.0), # gamma_m0 is negative + ], + ) + def test_raise_error_when_gamma_m0_is_invalid(self, a_v: float, f_y: float, gamma_m0: float) -> None: + """Test invalid gamma_m0 values.""" + with pytest.raises(LessOrEqualToZeroError): + Form8Dot23DesignPlasticShearResistance(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"V_{pl,Rd} = \frac{A_v \cdot (f_y / \sqrt{3})}{\gamma_{M0}} = " + r"\frac{2000.000 \cdot (355.000 / \sqrt{3})}{1.000} = 409918.691 \ N", + ), + ("short", r"V_{pl,Rd} = 409918.691 \ N"), + ( + "complete_with_units", + r"V_{pl,Rd} = \frac{A_v \cdot (f_y / \sqrt{3})}{\gamma_{M0}} = " + r"\frac{2000.000 \ mm^2 \cdot (355.000 \ MPa / \sqrt{3})}{1.000} = 409918.691 \ N", + ), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + # Example values + a_v = 2000.0 + f_y = 355.0 + gamma_m0 = 1.0 + + # Object to test + latex = Form8Dot23DesignPlasticShearResistance(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + "complete_with_units": latex.complete_with_units, + } + + assert expected == actual[representation], f"{representation} representation failed." diff --git a/tests/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/test_formula_8_25.py b/tests/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/test_formula_8_25.py new file mode 100644 index 000000000..2bca215bc --- /dev/null +++ b/tests/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/test_formula_8_25.py @@ -0,0 +1,76 @@ +"""Testing formula 8.25 from EN 1993-1-1:2022, chapter 8, ultimate limit state.""" + +import pytest + +from blueprints.codes.eurocode.en_1993_1_1_2022.chapter_8_ultimate_limit_state.formula_8_25 import Form8Dot25ShearStress +from blueprints.validations import LessOrEqualToZeroError, NegativeValueError + + +class TestForm8Dot25ShearStress: + """Validation for formula 8.25 from EN 1993-1-1:2022, chapter 8, ultimate limit state.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + # Example values + v_ed = 1000.0 + s = 2000.0 + i = 3000.0 + t = 4.0 + + # Object to test + formula = Form8Dot25ShearStress(v_ed=v_ed, s=s, i=i, t=t) + + # Expected result, manually calculated + manually_calculated_result = 166.66666666666666 # MPa + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("v_ed", "s", "i", "t"), + [ + (-1000.0, 2000.0, 3000.0, 4.0), # v_ed is negative + (1000.0, -2000.0, 3000.0, 4.0), # s is negative + (1000.0, 2000.0, 0.0, 4.0), # i is zero + (1000.0, 2000.0, 3000.0, 0.0), # t is zero + (1000.0, 2000.0, -3000.0, 4.0), # i is negative + (1000.0, 2000.0, 3000.0, -4.0), # t is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, v_ed: float, s: float, i: float, t: float) -> None: + """Test invalid values.""" + with pytest.raises((NegativeValueError, LessOrEqualToZeroError)): + Form8Dot25ShearStress(v_ed=v_ed, s=s, i=i, t=t) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"\tau_{Ed} = \frac{V_{Ed} \cdot S}{I \cdot t} = \frac{1000.000 \cdot 2000.000}{3000.000 \cdot 4.000} = 166.667 \ MPa", + ), + ("short", r"\tau_{Ed} = 166.667 \ MPa"), + ( + "complete_with_units", + r"\tau_{Ed} = \frac{V_{Ed} \cdot S}{I \cdot t} = " + r"\frac{1000.000 \ N \cdot 2000.000 \ mm^3}{3000.000 \ mm^4 \cdot 4.000 \ mm} = 166.667 \ MPa", + ), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + # Example values + v_ed = 1000.0 + s = 2000.0 + i = 3000.0 + t = 4.0 + + # Object to test + latex = Form8Dot25ShearStress(v_ed=v_ed, s=s, i=i, t=t).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + "complete_with_units": latex.complete_with_units, + } + + assert expected == actual[representation], f"{representation} representation failed." diff --git a/tests/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/test_formula_8_26.py b/tests/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/test_formula_8_26.py new file mode 100644 index 000000000..f15219fe2 --- /dev/null +++ b/tests/codes/eurocode/en_1993_1_1_2022/chapter_8_ultimate_limit_state/test_formula_8_26.py @@ -0,0 +1,95 @@ +"""Testing formula 8.26 from EN 1993-1-1:2022, chapter 8, ultimate limit state.""" + +import pytest + +from blueprints.codes.eurocode.en_1993_1_1_2022.chapter_8_ultimate_limit_state.formula_8_26 import Form8Dot26ShearStressIOrHSection +from blueprints.validations import LessOrEqualToZeroError, NegativeValueError + + +class TestForm8Dot26ShearStressIOrHSection: + """Validation for formula 8.26 from EN 1993-1-1:2022, chapter 8, ultimate limit state.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + # Example values + v_ed = 1000.0 + a_w = 200.0 + a_f = 150.0 + + # Object to test + formula = Form8Dot26ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f) + + # Expected result, manually calculated + manually_calculated_result = 5.0 # MPa + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("v_ed", "a_w", "a_f"), + [ + (-1000.0, 200.0, 150.0), # v_ed is negative + (1000.0, 200.0, -150.0), # a_f is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, v_ed: float, a_w: float, a_f: float) -> None: + """Test invalid values.""" + with pytest.raises(NegativeValueError): + Form8Dot26ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f) + + @pytest.mark.parametrize( + ("v_ed", "a_w", "a_f"), + [ + (1000.0, 0.0, 150.0), # a_w is zero + (1000.0, -200.0, 150.0), # a_w is negative + ], + ) + def test_raise_error_when_a_w_is_invalid(self, v_ed: float, a_w: float, a_f: float) -> None: + """Test invalid values for a_w.""" + with pytest.raises(LessOrEqualToZeroError): + Form8Dot26ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f) + + @pytest.mark.parametrize( + ("v_ed", "a_w", "a_f"), + [ + (1000.0, 200.0, 50.0), # a_f / a_w < 0.6 + ], + ) + def test_raise_error_when_a_f_divided_by_a_w_is_invalid(self, v_ed: float, a_w: float, a_f: float) -> None: + """Test invalid values for a_f / a_w.""" + with pytest.raises(ValueError): + Form8Dot26ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"\tau_{Ed} = \frac{V_{Ed}}{A_w} \text{ if } A_f / A_w \ge 0.6 = " + r"\frac{1000.000}{200.000} \text{ if } 150.000 / 200.000 \ge 0.6 = 5.000 \ MPa", + ), + ("short", r"\tau_{Ed} = 5.000 \ MPa"), + ( + "complete_with_units", + r"\tau_{Ed} = \frac{V_{Ed}}{A_w} \text{ if } A_f / A_w \ge 0.6 = " + r"\frac{1000.000 \ N}{200.000 \ mm^2} \text{ if } 150.000 \ mm^2 / 200.000 \ mm^2 " + r"\ge 0.6 = 5.000 \ MPa", + ), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + # Example values + v_ed = 1000.0 + a_w = 200.0 + a_f = 150.0 + + # Object to test + latex = Form8Dot26ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + "complete_with_units": latex.complete_with_units, + } + + assert expected == actual[representation], f"{representation} representation failed."