From cb0846ec80bba514425664f3f887ff6c465eacb5 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Tue, 25 Feb 2025 19:42:40 +0100 Subject: [PATCH 01/14] add formula 6.17 implementation and corresponding tests for shear force evaluation --- .../formula_6_17.py | 65 +++++++++++++++++++ .../test_formula_6_17.py | 64 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_17.py create mode 100644 tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_17.py diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_17.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_17.py new file mode 100644 index 000000000..28301e873 --- /dev/null +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_17.py @@ -0,0 +1,65 @@ +"""Formula 6.17 from NEN-EN 1993-1-1+C2+A1:2016: Chapter 6 - Ultimate Limit State.""" + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016 import NEN_EN_1993_1_1_C2_A1_2016 +from blueprints.codes.formula import Formula +from blueprints.codes.latex_formula import LatexFormula, latex_replace_symbols +from blueprints.type_alias import N +from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative + + +class Form6Dot17CheckShearForce(Formula): + r"""Class representing formula 6.17 for checking the design value of the shear force.""" + + label = "6.17" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + v_ed: N, + v_c_rd: N, + ) -> None: + r"""Check the design value of the shear force at each cross section. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(1) - Formula (6.17) + + Parameters + ---------- + v_ed : N + [$V_{Ed}$] Design value of the shear force [$N$]. + v_c_rd : N + [$V_{c,Rd}$] Design shear resistance [$N$]. + """ + super().__init__() + self.v_ed = v_ed + self.v_c_rd = v_c_rd + + @staticmethod + def _evaluate( + v_ed: N, + v_c_rd: N, + ) -> bool: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_less_or_equal_to_zero(v_c_rd=v_c_rd) + raise_if_negative(v_ed=v_ed) + + return v_ed / v_c_rd <= 1.0 + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.17.""" + _equation: str = r"\left( \frac{V_{Ed}}{V_{c,Rd}} \leq 1.0 \right)" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + "V_{Ed}": f"{self.v_ed:.3f}", + "V_{c,Rd}": f"{self.v_c_rd:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"CHECK", + result="OK" if self.__bool__() else "\\text{Not OK}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="\\to", + unit="", + ) diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_17.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_17.py new file mode 100644 index 000000000..e399349e3 --- /dev/null +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_17.py @@ -0,0 +1,64 @@ +"""Testing formula 6.17 of NEN-EN 1993-1-1+C2+A1:2016.""" + +import pytest + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_17 import Form6Dot17CheckShearForce +from blueprints.validations import LessOrEqualToZeroError, NegativeValueError + + +class TestForm6Dot17CheckShearForce: + """Validation for formula 6.17 from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + # Example values + v_ed = 100.0 + v_c_rd = 150.0 + + # Object to test + formula = Form6Dot17CheckShearForce(v_ed=v_ed, v_c_rd=v_c_rd) + + # Expected result, manually calculated + expected_result = True + + assert formula == expected_result + + @pytest.mark.parametrize( + ("v_ed", "v_c_rd"), + [ + (-100.0, 150.0), # v_ed is negative + (100.0, -150.0), # v_c_rd is negative + (100.0, 0.0), # v_c_rd is zero + ], + ) + def test_raise_error_when_invalid_values_are_given(self, v_ed: float, v_c_rd: float) -> None: + """Test invalid values.""" + with pytest.raises((NegativeValueError, LessOrEqualToZeroError)): + Form6Dot17CheckShearForce(v_ed=v_ed, v_c_rd=v_c_rd) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"CHECK \to \left( \frac{V_{Ed}}{V_{c,Rd}} \leq 1.0 \right) \to " + r"\left( \frac{100.000}{150.000} \leq 1.0 \right) \to OK", + ), + ("short", r"CHECK \to OK"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + # Example values + v_ed = 100.0 + v_c_rd = 150.0 + + # Object to test + latex = Form6Dot17CheckShearForce(v_ed=v_ed, v_c_rd=v_c_rd).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." From 2e722be5e1fe1c5192f680aeaf7f671b24ef5d07 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Tue, 25 Feb 2025 19:42:52 +0100 Subject: [PATCH 02/14] add formula 6.18 implementation and corresponding tests for shear force evaluation --- .../formula_6_18.py | 73 +++++++++++++++++ .../test_formula_6_18.py | 78 +++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py create mode 100644 tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py new file mode 100644 index 000000000..56adac448 --- /dev/null +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py @@ -0,0 +1,73 @@ +"""Formula 6.18 from NEN-EN 1993-1-1+C2+A1:2016: Chapter 6 - Ultimate Limit State.""" + +import numpy as np + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016 import NEN_EN_1993_1_1_C2_A1_2016 +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 Form6Dot18VplRd(Formula): + r"""Class representing formula 6.18 for the calculation of [$V_{pl,Rd}$].""" + + label = "6.18" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + 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$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(2) - Formula (6.18) + + Parameters + ---------- + a_v : MM2 + [$A_v$] Shear area [$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) -> LatexFormula: + """Returns LatexFormula object for formula 6.18.""" + _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:.3f}", + r"f_y": f"{self.f_y:.3f}", + r"\gamma_{M0}": f"{self.gamma_m0:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"V_{pl,Rd}", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="N", + ) diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py new file mode 100644 index 000000000..df99993bf --- /dev/null +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py @@ -0,0 +1,78 @@ +"""Testing formula 6.18 of NEN-EN 1993-1-1+C2+A1:2016.""" + +import pytest + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_18 import Form6Dot18VplRd +from blueprints.validations import LessOrEqualToZeroError, NegativeValueError + + +class TestForm6Dot18VplRd: + """Validation for formula 6.18 from NEN-EN 1993-1-1+C2+A1:2016.""" + + 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 = Form6Dot18VplRd(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)): + Form6Dot18VplRd(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): + Form6Dot18VplRd(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"), + ], + ) + 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 = Form6Dot18VplRd(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." From 6513e32524bb771a37b70d1118781615cf24cb34 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Tue, 25 Feb 2025 19:43:44 +0100 Subject: [PATCH 03/14] add subformula of 6.18 implementation and corresponding tests for shear force evaluation --- .../formula_6_18_sub_av.py | 548 ++++++++++++++++++ .../test_formula_6_18_sub_av.py | 462 +++++++++++++++ 2 files changed, 1010 insertions(+) create mode 100644 blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18_sub_av.py create mode 100644 tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18_sub_av.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18_sub_av.py new file mode 100644 index 000000000..eedd8dc91 --- /dev/null +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18_sub_av.py @@ -0,0 +1,548 @@ +"""Subformula a trough g from 6.18 from NEN-EN 1993-1-1+C2+A1:2016: Chapter 6 - Ultimate Limit State.""" + +import numpy as np + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016 import NEN_EN_1993_1_1_C2_A1_2016 +from blueprints.codes.formula import Formula +from blueprints.codes.latex_formula import LatexFormula, latex_replace_symbols +from blueprints.type_alias import DIMENSIONLESS, MM, MM2 +from blueprints.validations import raise_if_lists_differ_in_length, raise_if_negative + + +class Form6Dot18SubARolledIandHSection(Formula): + r"""Class representing formula 6.18suba for the calculation of shear area for a rolled I and H section.""" + + label = "6.18suba" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + a: MM2, + b: MM, + hw: MM, + r: MM, + tf: MM, + tw: MM, + eta: DIMENSIONLESS, + ) -> None: + r"""[$A_v$] Calculation of the shear area for a rolled I and H section with load parallel to web [$mm^2$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(3) - Formula (6.18suba) + + Parameters + ---------- + a : MM2 + [$A$] Cross-sectional area [$mm^2$]. + b : MM + [$b$] Overall breadth [$mm$]. + hw : MM + [$h_w$] Depth of the web [$mm$]. + r : MM + [$r$] Root radius [$mm$]. + tf : MM + [$t_f$] Flange thickness [$mm$]. + tw : MM + [$t_w$] Web thickness [$mm$]. If the web thickness is not constant, tw should be taken as the minimum thickness. + eta : DIMENSIONLESS, optional + [$\eta$] See EN 1993-1-5. Note, $eta$ may be conservatively taken equal to 1.0. + """ + super().__init__() + self.a = a + self.b = b + self.hw = hw + self.r = r + self.tf = tf + self.tw = tw + self.eta = eta + + @staticmethod + def _evaluate( + a: MM2, + b: MM, + hw: MM, + r: MM, + tf: MM, + tw: MM, + eta: DIMENSIONLESS, + ) -> MM2: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(a=a, b=b, hw=hw, r=r, tf=tf, tw=tw, eta=eta) + + av = a - 2 * b * tf + (tw + 2 * r) * tf + av_min = eta * hw * tw + + return max(av, av_min) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.18suba.""" + _equation: str = r"max(A - 2 \cdot b \cdot t_f + (t_w + 2 \cdot r) \cdot t_f; \eta \cdot h_w \cdot t_w)" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"A": f"{self.a:.3f}", + r"b": f"{self.b:.3f}", + r"h_w": f"{self.hw:.3f}", + r"r": f"{self.r:.3f}", + r"t_f": f"{self.tf:.3f}", + r"t_w": f"{self.tw:.3f}", + r"\eta": f"{self.eta:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"A_v", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="mm^2", + ) + + +class Form6Dot18SubBRolledChannelSection(Formula): + r"""Class representing formula 6.18subb for the calculation of shear area for a rolled channel section.""" + + label = "6.18subb" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + a: MM2, + b: MM, + tf: MM, + tw: MM, + r: MM, + ) -> None: + r"""[$A_v$] Calculation of the shear area for a rolled channel section with load parallel to web [$mm^2$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(3) - Formula (6.18subb) + + Parameters + ---------- + a : MM2 + [$A$] Cross-sectional area [$mm^2$]. + b : MM + [$b$] Overall breadth [$mm$]. + tf : MM + [$t_f$] Flange thickness [$mm$]. + tw : MM + [$t_w$] Web thickness [$mm$]. If the web thickness is not constant, tw should be taken as the minimum thickness. + r : MM + [$r$] Root radius [$mm$]. + """ + super().__init__() + self.a = a + self.b = b + self.tf = tf + self.tw = tw + self.r = r + + @staticmethod + def _evaluate( + a: MM2, + b: MM, + tf: MM, + tw: MM, + r: MM, + ) -> MM2: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(a=a, b=b, tf=tf, tw=tw, r=r) + + return a - 2 * b * tf + (tw + r) * tf + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.18subb.""" + _equation: str = r"A - 2 \cdot b \cdot t_f + (t_w + r) \cdot t_f" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"A": f"{self.a:.3f}", + r"b": f"{self.b:.3f}", + r"t_f": f"{self.tf:.3f}", + r"t_w": f"{self.tw:.3f}", + r"r": f"{self.r:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"A_v", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="mm^2", + ) + + +class Form6Dot18SubCTSection(Formula): + r"""Class representing formula 6.18subc for the calculation of shear area for a T-section with load parallel to web.""" + + label = "6.18subc" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + a: MM2, + b: MM, + tf: MM, + ) -> None: + r"""[$A_v$] Calculation of the shear area for a T-section with load parallel to web [$mm^2$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(3) - Formula (6.18subc) + + Parameters + ---------- + a : MM2 + [$A$] Cross-sectional area [$mm^2$]. + b : MM + [$b$] Overall breadth [$mm$]. + tf : MM + [$t_f$] Flange thickness [$mm$]. + """ + super().__init__() + self.a = a + self.b = b + self.tf = tf + + @staticmethod + def _evaluate( + a: MM2, + b: MM, + tf: MM, + ) -> MM2: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(a=a, b=b, tf=tf) + + return 0.9 * (a - b * tf) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.18subc.""" + _equation: str = r"0.9 \cdot (A - b \cdot t_f)" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"A": f"{self.a:.3f}", + r"b": f"{self.b:.3f}", + r"t_f": f"{self.tf:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"A_v", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="mm^2", + ) + + +class Form6Dot18SubDWeldedIHandBoxSection(Formula): + r"""Class representing formula 6.18subd for the calculation of shear area for welded I, H, and box sections with load parallel to web.""" + + label = "6.18subd" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + hw: list[MM], + tw: list[MM], + eta: DIMENSIONLESS, + ) -> None: + r"""[$A_v$] Calculation of the shear area for welded I, H, and box sections with load parallel to web [$mm^2$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(3) - Formula (6.18subd) + + Parameters + ---------- + hw : list[MM] + [$h_w$] List of depths of the web [$mm$]. + tw : list[MM] + [$t_w$] List of web thicknesses [$mm$]. If the web thickness is not constant, tw should be taken as the minimum thickness. + eta : DIMENSIONLESS + [$\eta$] See EN 1993-1-5. Note, $eta$ may be conservatively taken equal to 1.0. + """ + super().__init__() + self.hw = hw + self.tw = tw + self.eta = eta + + @staticmethod + def _evaluate( + hw: list[MM], + tw: list[MM], + eta: DIMENSIONLESS, + ) -> MM2: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(eta=eta) + for h, t in zip(hw, tw): + raise_if_negative(h=h, t=t) + raise_if_lists_differ_in_length(hw=hw, tw=tw) + + return eta * sum(h * t for h, t in zip(hw, tw)) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.18subd.""" + _equation: str = r"\eta \cdot \sum (h_{w0} \cdot t_{w0}" + for i in range(1, len(self.hw)): + _equation += rf" + h_{{w{i}}} \cdot t_{{w{i}}}" + _equation += ")" + _numeric_equation: str = rf"{self.eta:.3f} \cdot (" + rf"{self.hw[0]:.3f} \cdot {self.tw[0]:.3f}" + for i in range(1, len(self.hw)): + _numeric_equation += rf" + {self.hw[i]:.3f} \cdot {self.tw[i]:.3f}" + _numeric_equation += ")" + return LatexFormula( + return_symbol=r"A_v", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="mm^2", + ) + + +class Form6Dot18SubEWeldedIHandBoxSection(Formula): + r"""Class representing formula 6.18sube for the calculation of shear area for welded I, H, channel, and box sections with + load parallel to flanges. + """ + + label = "6.18sube" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + a: MM2, + hw: list[MM], + tw: list[MM], + ) -> None: + r"""[$A_v$] Calculation of the shear area for welded I, H, channel, and box sections with load parallel to flanges [$mm^2$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(3) - Formula (6.18sube) + + Parameters + ---------- + a : MM2 + [$A$] Cross-sectional area [$mm^2$]. + hw : list[MM] + [$h_w$] List of depths of the web [$mm$]. + tw : list[MM] + [$t_w$] List of web thicknesses [$mm$]. If the web thickness is not constant, tw should be taken as the minimum thickness. + """ + super().__init__() + self.a = a + self.hw = hw + self.tw = tw + + @staticmethod + def _evaluate( + a: MM2, + hw: list[MM], + tw: list[MM], + ) -> MM2: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(a=a) + for h, t in zip(hw, tw): + raise_if_negative(h=h, t=t) + raise_if_lists_differ_in_length(hw=hw, tw=tw) + + return a - sum(h * t for h, t in zip(hw, tw)) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.18sube.""" + _equation: str = r"A - \sum (h_{w0} \cdot t_{w0}" + for i in range(1, len(self.hw)): + _equation += rf" + h_{{w{i}}} \cdot t_{{w{i}}}" + _equation += ")" + _numeric_equation: str = rf"{self.a:.3f} - (" + rf"{self.hw[0]:.3f} \cdot {self.tw[0]:.3f}" + for i in range(1, len(self.hw)): + _numeric_equation += rf" + {self.hw[i]:.3f} \cdot {self.tw[i]:.3f}" + _numeric_equation += ")" + return LatexFormula( + return_symbol=r"A_v", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="mm^2", + ) + + +class Form6Dot18SubF1RolledRectangularHollowSectionDepth(Formula): + r"""Class representing formula 6.18subf1 for the calculation of shear area for rolled rectangular hollow sections of uniform thickness with + load parallel to depth. + """ + + label = "6.18subf1" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + a: MM2, + b: MM, + h: MM, + ) -> None: + r"""[$A_v$] Calculation of the shear area for rolled rectangular hollow sections of uniform thickness with load parallel to depth [$mm^2$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(3) - Formula (6.18subf1) + + Parameters + ---------- + a : MM2 + [$A$] Cross-sectional area [$mm^2$]. + b : MM + [$b$] Overall breadth [$mm$]. + h : MM + [$h$] Overall depth [$mm$]. + """ + super().__init__() + self.a = a + self.b = b + self.h = h + + @staticmethod + def _evaluate( + a: MM2, + b: MM, + h: MM, + ) -> MM2: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(a=a, b=b, h=h) + + return a * h / (b + h) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.18subf1.""" + _equation: str = r"\frac{A \cdot h}{b + h}" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"A": f"{self.a:.3f}", + r"b": f"{self.b:.3f}", + r"h": f"{self.h:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"A_v", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="mm^2", + ) + + +class Form6Dot18SubF2RolledRectangularHollowSectionWidth(Formula): + r"""Class representing formula 6.18subf2 for the calculation of shear area for rolled rectangular hollow sections of uniform thickness with + load parallel to width. + """ + + label = "6.18subf2" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + a: MM2, + b: MM, + h: MM, + ) -> None: + r"""[$A_v$] Calculation of the shear area for rolled rectangular hollow sections of uniform thickness with load parallel to width [$mm^2$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(3) - Formula (6.18subf2) + + Parameters + ---------- + a : MM2 + [$A$] Cross-sectional area [$mm^2$]. + b : MM + [$b$] Overall breadth [$mm$]. + h : MM + [$h$] Overall depth [$mm$]. + """ + super().__init__() + self.a = a + self.b = b + self.h = h + + @staticmethod + def _evaluate( + a: MM2, + b: MM, + h: MM, + ) -> MM2: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(a=a, b=b, h=h) + + return a * b / (b + h) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.18subf2.""" + _equation: str = r"\frac{A \cdot b}{b + h}" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"A": f"{self.a:.3f}", + r"b": f"{self.b:.3f}", + r"h": f"{self.h:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"A_v", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="mm^2", + ) + + +class Form6Dot18SubGCircularHollowSection(Formula): + r"""Class representing formula 6.18subg for the calculation of shear area for circular hollow sections and tubes of uniform thickness.""" + + label = "6.18subg" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + a: MM2, + ) -> None: + r"""[$A_v$] Calculation of the shear area for circular hollow sections and tubes of uniform thickness [$mm^2$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(3) - Formula (6.18subg) + + Parameters + ---------- + a : MM2 + [$A$] Cross-sectional area [$mm^2$]. + """ + super().__init__() + self.a = a + + @staticmethod + def _evaluate( + a: MM2, + ) -> MM2: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(a=a) + + return 2 * a / np.pi + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.18subg.""" + _equation: str = r"\frac{2 \cdot A}{\pi}" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"A": f"{self.a:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"A_v", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="mm^2", + ) diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py new file mode 100644 index 000000000..92609ad4e --- /dev/null +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py @@ -0,0 +1,462 @@ +"""Testing subformulas a to g from 6.18 of NEN-EN 1993-1-1+C2+A1:2016.""" + +import pytest + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_18_sub_av import ( + Form6Dot18SubARolledIandHSection, + Form6Dot18SubBRolledChannelSection, + Form6Dot18SubCTSection, + Form6Dot18SubDWeldedIHandBoxSection, + Form6Dot18SubEWeldedIHandBoxSection, + Form6Dot18SubF1RolledRectangularHollowSectionDepth, + Form6Dot18SubF2RolledRectangularHollowSectionWidth, + Form6Dot18SubGCircularHollowSection, +) +from blueprints.validations import ListsNotSameLengthError, NegativeValueError + + +class TestForm6Dot18SubARolledIandHSection: + """Validation for formula 6.18suba from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + a = 10000.0 + b = 200.0 + hw = 250.0 + r = 10.0 + tf = 15.0 + tw = 8.0 + eta = 1.0 + + formula = Form6Dot18SubARolledIandHSection(a=a, b=b, hw=hw, r=r, tf=tf, tw=tw, eta=eta) + manually_calculated_result = 4420.0 # mm^2 + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("a", "b", "hw", "r", "tf", "tw", "eta"), + [ + (-10000.0, 200.0, 250.0, 10.0, 15.0, 8.0, 1.0), # a is negative + (10000.0, -200.0, 250.0, 10.0, 15.0, 8.0, 1.0), # b is negative + (10000.0, 200.0, -250.0, 10.0, 15.0, 8.0, 1.0), # hw is negative + (10000.0, 200.0, 250.0, -10.0, 15.0, 8.0, 1.0), # r is negative + (10000.0, 200.0, 250.0, 10.0, -15.0, 8.0, 1.0), # tf is negative + (10000.0, 200.0, 250.0, 10.0, 15.0, -8.0, 1.0), # tw is negative + (10000.0, 200.0, 250.0, 10.0, 15.0, 8.0, -1.0), # eta is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, hw: float, r: float, tf: float, tw: float, eta: float) -> None: + """Test invalid values.""" + with pytest.raises(NegativeValueError): + Form6Dot18SubARolledIandHSection(a=a, b=b, hw=hw, r=r, tf=tf, tw=tw, eta=eta) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"A_v = max(A - 2 \cdot b \cdot t_f + (t_w + 2 \cdot r) \cdot t_f; \eta \cdot h_w \cdot t_w) = " + r"max(10000.000 - 2 \cdot 200.000 \cdot 15.000 + (8.000 + 2 \cdot 10.000) \cdot 15.000; 1.000 \cdot 250.000 \cdot 8.000) = " + r"4420.000 mm^2", + ), + ("short", r"A_v = 4420.000 mm^2"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + a = 10000.0 + b = 200.0 + hw = 250.0 + r = 10.0 + tf = 15.0 + tw = 8.0 + eta = 1.0 + + latex = Form6Dot18SubARolledIandHSection(a=a, b=b, hw=hw, r=r, tf=tf, tw=tw, eta=eta).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." + + +class TestForm6Dot18SubBRolledChannelSection: + """Validation for formula 6.18subb from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + a = 8000.0 + b = 150.0 + tf = 12.0 + tw = 6.0 + r = 8.0 + + formula = Form6Dot18SubBRolledChannelSection(a=a, b=b, tf=tf, tw=tw, r=r) + manually_calculated_result = 4568.0 # mm^2 + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("a", "b", "tf", "tw", "r"), + [ + (-8000.0, 150.0, 12.0, 6.0, 8.0), # a is negative + (8000.0, -150.0, 12.0, 6.0, 8.0), # b is negative + (8000.0, 150.0, -12.0, 6.0, 8.0), # tf is negative + (8000.0, 150.0, 12.0, -6.0, 8.0), # tw is negative + (8000.0, 150.0, 12.0, 6.0, -8.0), # r is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, tf: float, tw: float, r: float) -> None: + """Test invalid values.""" + with pytest.raises(NegativeValueError): + Form6Dot18SubBRolledChannelSection(a=a, b=b, tf=tf, tw=tw, r=r) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"A_v = A - 2 \cdot b \cdot t_f + (t_w + r) \cdot t_f = " + r"8000.000 - 2 \cdot 150.000 \cdot 12.000 + (6.000 + 8.000) \cdot 12.000 = 4568.000 mm^2", + ), + ("short", r"A_v = 4568.000 mm^2"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + a = 8000.0 + b = 150.0 + tf = 12.0 + tw = 6.0 + r = 8.0 + + latex = Form6Dot18SubBRolledChannelSection(a=a, b=b, tf=tf, tw=tw, r=r).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." + + +class TestForm6Dot18SubCTSection: + """Validation for formula 6.18subc from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + a = 6000.0 + b = 100.0 + tf = 10.0 + + formula = Form6Dot18SubCTSection(a=a, b=b, tf=tf) + manually_calculated_result = 4500.0 # mm^2 + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("a", "b", "tf"), + [ + (-6000.0, 100.0, 10.0), # a is negative + (6000.0, -100.0, 10.0), # b is negative + (6000.0, 100.0, -10.0), # tf is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, tf: float) -> None: + """Test invalid values.""" + with pytest.raises(NegativeValueError): + Form6Dot18SubCTSection(a=a, b=b, tf=tf) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"A_v = 0.9 \cdot (A - b \cdot t_f) = " + r"0.9 \cdot (6000.000 - 100.000 \cdot 10.000) = 4500.000 mm^2", + ), + ("short", r"A_v = 4500.000 mm^2"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + a = 6000.0 + b = 100.0 + tf = 10.0 + + latex = Form6Dot18SubCTSection(a=a, b=b, tf=tf).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." + + +class TestForm6Dot18SubDWeldedIHandBoxSection: + """Validation for formula 6.18subd from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + hw = [250.0, 300.0] + tw = [8.0, 10.0] + eta = 1.0 + + formula = Form6Dot18SubDWeldedIHandBoxSection(hw=hw, tw=tw, eta=eta) + manually_calculated_result = 5000.0 # mm^2 + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("hw", "tw", "eta"), + [ + ([250.0, -300.0], [8.0, 10.0], 1.0), # hw contains negative value + ([250.0, 300.0], [8.0, -10.0], 1.0), # tw contains negative value + ([250.0, 300.0], [8.0, 10.0], -1.0), # eta is negative + ([250.0], [8.0, 10.0], 1.0), # hw and tw are not the same length + ], + ) + def test_raise_error_when_invalid_values_are_given(self, hw: list[float], tw: list[float], eta: float) -> None: + """Test invalid values.""" + with pytest.raises((NegativeValueError, ListsNotSameLengthError)): + Form6Dot18SubDWeldedIHandBoxSection(hw=hw, tw=tw, eta=eta) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"A_v = \eta \cdot \sum (h_{w0} \cdot t_{w0} + h_{w1} \cdot t_{w1}) = " + r"1.000 \cdot (250.000 \cdot 8.000 + 300.000 \cdot 10.000) = 5000.000 mm^2", + ), + ("short", r"A_v = 5000.000 mm^2"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + hw = [250.0, 300.0] + tw = [8.0, 10.0] + eta = 1.0 + + latex = Form6Dot18SubDWeldedIHandBoxSection(hw=hw, tw=tw, eta=eta).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." + + +class TestForm6Dot18SubEWeldedIHandBoxSection: + """Validation for formula 6.18sube from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + a = 12000.0 + hw = [250.0, 300.0] + tw = [8.0, 10.0] + + formula = Form6Dot18SubEWeldedIHandBoxSection(a=a, hw=hw, tw=tw) + manually_calculated_result = 7000.0 # mm^2 + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("a", "hw", "tw"), + [ + (-12000.0, [250.0, 300.0], [8.0, 10.0]), # a is negative + (12000.0, [250.0, -300.0], [8.0, 10.0]), # hw contains negative value + (12000.0, [250.0, 300.0], [8.0, -10.0]), # tw contains negative value + (12000.0, [250.0, 300.0], [8.0, 10.0, 12.0]), # hw and tw are not the same length + ], + ) + def test_raise_error_when_invalid_values_are_given(self, a: float, hw: list[float], tw: list[float]) -> None: + """Test invalid values.""" + with pytest.raises((NegativeValueError, ListsNotSameLengthError)): + Form6Dot18SubEWeldedIHandBoxSection(a=a, hw=hw, tw=tw) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"A_v = A - \sum (h_{w0} \cdot t_{w0} + h_{w1} \cdot t_{w1}) = " + r"12000.000 - (250.000 \cdot 8.000 + 300.000 \cdot 10.000) = 7000.000 mm^2", + ), + ("short", r"A_v = 7000.000 mm^2"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + a = 12000.0 + hw = [250.0, 300.0] + tw = [8.0, 10.0] + + latex = Form6Dot18SubEWeldedIHandBoxSection(a=a, hw=hw, tw=tw).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." + + +class TestForm6Dot18SubF1RolledRectangularHollowSectionDepth: + """Validation for formula 6.18subf1 from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + a = 5000.0 + b = 100.0 + h = 200.0 + + formula = Form6Dot18SubF1RolledRectangularHollowSectionDepth(a=a, b=b, h=h) + manually_calculated_result = 3333.333 # mm^2 + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("a", "b", "h"), + [ + (-5000.0, 100.0, 200.0), # a is negative + (5000.0, -100.0, 200.0), # b is negative + (5000.0, 100.0, -200.0), # h is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, h: float) -> None: + """Test invalid values.""" + with pytest.raises(NegativeValueError): + Form6Dot18SubF1RolledRectangularHollowSectionDepth(a=a, b=b, h=h) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"A_v = \frac{A \cdot h}{b + h} = " + r"\frac{5000.000 \cdot 200.000}{100.000 + 200.000} = 3333.333 mm^2", + ), + ("short", r"A_v = 3333.333 mm^2"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + a = 5000.0 + b = 100.0 + h = 200.0 + + latex = Form6Dot18SubF1RolledRectangularHollowSectionDepth(a=a, b=b, h=h).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." + + +class TestForm6Dot18SubF2RolledRectangularHollowSectionWidth: + """Validation for formula 6.18subf2 from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + a = 5000.0 + b = 100.0 + h = 200.0 + + formula = Form6Dot18SubF2RolledRectangularHollowSectionWidth(a=a, b=b, h=h) + manually_calculated_result = 1666.667 # mm^2 + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("a", "b", "h"), + [ + (-5000.0, 100.0, 200.0), # a is negative + (5000.0, -100.0, 200.0), # b is negative + (5000.0, 100.0, -200.0), # h is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, h: float) -> None: + """Test invalid values.""" + with pytest.raises(NegativeValueError): + Form6Dot18SubF2RolledRectangularHollowSectionWidth(a=a, b=b, h=h) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"A_v = \frac{A \cdot b}{b + h} = " + r"\frac{5000.000 \cdot 100.000}{100.000 + 200.000} = 1666.667 mm^2", + ), + ("short", r"A_v = 1666.667 mm^2"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + a = 5000.0 + b = 100.0 + h = 200.0 + + latex = Form6Dot18SubF2RolledRectangularHollowSectionWidth(a=a, b=b, h=h).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." + + +class TestForm6Dot18SubGCircularHollowSection: + """Validation for formula 6.18subg from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + a = 4000.0 + + formula = Form6Dot18SubGCircularHollowSection(a=a) + manually_calculated_result = 2546.4790899483937 # mm^2 + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + "a", + [ + -4000.0, # a is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, a: float) -> None: + """Test invalid values.""" + with pytest.raises(NegativeValueError): + Form6Dot18SubGCircularHollowSection(a=a) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"A_v = \frac{2 \cdot A}{\pi} = " + r"\frac{2 \cdot 4000.000}{\pi} = 2546.479 mm^2", + ), + ("short", r"A_v = 2546.479 mm^2"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + a = 4000.0 + + latex = Form6Dot18SubGCircularHollowSection(a=a).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." From 03056d298fd066bbe9150f885d80d7db76e2da9b Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Tue, 25 Feb 2025 19:43:52 +0100 Subject: [PATCH 04/14] add formula 6.19 implementation and corresponding tests for shear force evaluation --- .../formula_6_19.py | 71 +++++++++++++++++++ .../test_formula_6_19.py | 70 ++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_19.py create mode 100644 tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_19.py diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_19.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_19.py new file mode 100644 index 000000000..ee50ede27 --- /dev/null +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_19.py @@ -0,0 +1,71 @@ +"""Formula 6.19 from NEN-EN 1993-1-1+C2+A1:2016: Chapter 6 - Ultimate Limit State.""" + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016 import NEN_EN_1993_1_1_C2_A1_2016 +from blueprints.codes.formula import Formula +from blueprints.codes.latex_formula import LatexFormula, latex_replace_symbols +from blueprints.type_alias import DIMENSIONLESS, MPA +from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative + + +class Form6Dot19CheckDesignElasticShearResistance(Formula): + r"""Class representing formula 6.19 for checking the design elastic shear resistance.""" + + label = "6.19" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + tau_ed: MPA, + f_y: MPA, + gamma_m0: DIMENSIONLESS, + ) -> None: + r"""Check the design elastic shear resistance. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(4) - Formula (6.19) + + Parameters + ---------- + tau_ed : MPA + [$\tau_{Ed}$] Design shear stress [MPa]. + f_y : MPA + [$f_{y}$] Yield strength of the material [MPa]. + gamma_m0 : DIMENSIONLESS + [$\gamma_{M0}$] Partial safety factor [-]. + """ + super().__init__() + self.tau_ed = tau_ed + self.f_y = f_y + self.gamma_m0 = gamma_m0 + + @staticmethod + def _evaluate( + tau_ed: MPA, + f_y: MPA, + gamma_m0: DIMENSIONLESS, + ) -> bool: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_less_or_equal_to_zero(gamma_m0=gamma_m0, f_y=f_y) + raise_if_negative(tau_ed=tau_ed) + + return tau_ed / (f_y / (3**0.5 * gamma_m0)) <= 1.0 + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.19.""" + _equation: str = r"\left( \frac{\tau_{Ed}}{f_{y} / (\sqrt{3} \cdot \gamma_{M0})} \leq 1.0 \right)" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"\tau_{Ed}": f"{self.tau_ed:.3f}", + r"f_{y}": f"{self.f_y:.3f}", + r"\gamma_{M0}": f"{self.gamma_m0:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"CHECK", + result="OK" if self.__bool__() else "\\text{Not OK}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="\\to", + unit="", + ) diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_19.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_19.py new file mode 100644 index 000000000..9cf2c3ade --- /dev/null +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_19.py @@ -0,0 +1,70 @@ +"""Testing formula 6.19 of NEN-EN 1993-1-1+C2+A1:2016.""" + +import pytest + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_19 import ( + Form6Dot19CheckDesignElasticShearResistance, +) +from blueprints.validations import LessOrEqualToZeroError, NegativeValueError + + +class TestForm6Dot19CheckDesignElasticShearResistance: + """Validation for formula 6.19 from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + # Example values + tau_ed = 100.0 + f_y = 355.0 + gamma_m0 = 1.0 + + # Object to test + formula = Form6Dot19CheckDesignElasticShearResistance(tau_ed=tau_ed, f_y=f_y, gamma_m0=gamma_m0) + + # Expected result, manually calculated + expected_result = True + + assert formula == expected_result + + @pytest.mark.parametrize( + ("tau_ed", "f_y", "gamma_m0"), + [ + (-100.0, 355.0, 1.0), # tau_ed is negative + (100.0, -355.0, 1.0), # f_y is negative + (100.0, 355.0, -1.0), # gamma_m0 is negative + (100.0, 0.0, 1.0), # f_y is zero + (100.0, 355.0, 0.0), # gamma_m0 is zero + ], + ) + def test_raise_error_when_invalid_values_are_given(self, tau_ed: float, f_y: float, gamma_m0: float) -> None: + """Test invalid values.""" + with pytest.raises((NegativeValueError, LessOrEqualToZeroError)): + Form6Dot19CheckDesignElasticShearResistance(tau_ed=tau_ed, f_y=f_y, gamma_m0=gamma_m0) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"CHECK \to \left( \frac{\tau_{Ed}}{f_{y} / (\sqrt{3} \cdot \gamma_{M0})} \leq 1.0 \right) \to " + r"\left( \frac{100.000}{355.000 / (\sqrt{3} \cdot 1.000)} \leq 1.0 \right) \to OK", + ), + ("short", r"CHECK \to OK"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + # Example values + tau_ed = 100.0 + f_y = 355.0 + gamma_m0 = 1.0 + + # Object to test + latex = Form6Dot19CheckDesignElasticShearResistance(tau_ed=tau_ed, f_y=f_y, gamma_m0=gamma_m0).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." From 70a02209b708b5c7f51a534a199786c6536397a3 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Tue, 25 Feb 2025 19:43:59 +0100 Subject: [PATCH 05/14] add formula 6.20 implementation and corresponding tests for shear force evaluation --- .../formula_6_20.py | 78 +++++++++++++++++++ .../test_formula_6_20.py | 70 +++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_20.py create mode 100644 tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_20.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_20.py new file mode 100644 index 000000000..87f9de20b --- /dev/null +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_20.py @@ -0,0 +1,78 @@ +"""Formula 6.20 from NEN-EN 1993-1-1+C2+A1:2016: Chapter 6 - Ultimate Limit State.""" + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016 import NEN_EN_1993_1_1_C2_A1_2016 +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 Form6Dot20TauEd(Formula): + r"""Class representing formula 6.20 for the calculation of [$\tau_{Ed}$].""" + + label = "6.20" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + v_ed: N, + s: MM3, + i: MM4, + t: MM, + ) -> None: + r"""[$\tau_{Ed}$] Calculation of the design elastic shear stress [$MPa$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(4) - Formula (6.20) + + 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) -> LatexFormula: + """Returns LatexFormula object for formula 6.20.""" + _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:.3f}", + r"S": f"{self.s:.3f}", + r"I": f"{self.i:.3f}", + r" t": f" {self.t:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"\tau_{Ed}", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="MPa", + ) diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py new file mode 100644 index 000000000..f42b2f433 --- /dev/null +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py @@ -0,0 +1,70 @@ +"""Testing formula 6.20 of NEN-EN 1993-1-1+C2+A1:2016.""" + +import pytest + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_20 import Form6Dot20TauEd +from blueprints.validations import LessOrEqualToZeroError, NegativeValueError + + +class TestForm6Dot20TauEd: + """Validation for formula 6.20 from NEN-EN 1993-1-1+C2+A1:2016.""" + + 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 = Form6Dot20TauEd(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)): + Form6Dot20TauEd(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"), + ], + ) + 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 = Form6Dot20TauEd(v_ed=v_ed, s=s, i=i, t=t).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." From 02438b98f2615a933675c10a93a31fb03d95ea04 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Tue, 25 Feb 2025 19:44:06 +0100 Subject: [PATCH 06/14] add formula 6.21 implementation and corresponding tests for shear force evaluation --- .../formula_6_21.py | 74 ++++++++++++++++ .../test_formula_6_21.py | 88 +++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py create mode 100644 tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py new file mode 100644 index 000000000..4cb06ccff --- /dev/null +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py @@ -0,0 +1,74 @@ +"""Formula 6.20 from NEN-EN 1993-1-1+C2+A1:2016: Chapter 6 - Ultimate Limit State.""" + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016 import NEN_EN_1993_1_1_C2_A1_2016 +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 Form6Dot20TauEd(Formula): + r"""Class representing formula 6.20 for the calculation of [$\tau_{Ed}$].""" + + label = "6.20" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + 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. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(5) - Formula (6.20) + + 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 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) -> LatexFormula: + """Returns LatexFormula object for formula 6.20.""" + _equation: str = r"\frac{V_{Ed}}{A_w} \text{ if } A_f / A_w \ge 0.6" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"V_{Ed}": f"{self.v_ed:.3f}", + r"A_w": f"{self.a_w:.3f}", + r"A_f": f"{self.a_f:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"\tau_{Ed}", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="MPa", + ) diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py new file mode 100644 index 000000000..f59bbc090 --- /dev/null +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py @@ -0,0 +1,88 @@ +"""Testing formula 6.20 of NEN-EN 1993-1-1+C2+A1:2016.""" + +import pytest + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_21 import Form6Dot20TauEd +from blueprints.validations import LessOrEqualToZeroError, NegativeValueError + + +class TestForm6Dot20TauEd: + """Validation for formula 6.20 from NEN-EN 1993-1-1+C2+A1:2016.""" + + 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 = Form6Dot20TauEd(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): + Form6Dot20TauEd(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): + Form6Dot20TauEd(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): + Form6Dot20TauEd(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"), + ], + ) + 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 = Form6Dot20TauEd(v_ed=v_ed, a_w=a_w, a_f=a_f).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." From 34cff007487c86bda9a40673a9dcc5d51783bba3 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Tue, 25 Feb 2025 19:44:11 +0100 Subject: [PATCH 07/14] add formula 6.22 implementation and corresponding tests for shear buckling resistance evaluation --- .../formula_6_22.py | 77 +++++++++++++++++++ .../test_formula_6_22.py | 71 +++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_22.py create mode 100644 tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_22.py diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_22.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_22.py new file mode 100644 index 000000000..268d27b62 --- /dev/null +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_22.py @@ -0,0 +1,77 @@ +"""Formula 6.22 from NEN-EN 1993-1-1+C2+A1:2016: Chapter 6 - Ultimate Limit State.""" + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016 import NEN_EN_1993_1_1_C2_A1_2016 +from blueprints.codes.formula import Formula +from blueprints.codes.latex_formula import LatexFormula, latex_replace_symbols +from blueprints.type_alias import DIMENSIONLESS, MM +from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative + + +class Form6Dot22CheckShearBucklingResistance(Formula): + r"""Class representing formula 6.22 for checking shear buckling resistance for webs without intermediate stiffeners.""" + + label = "6.22" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + h_w: MM, + t_w: MM, + epsilon: DIMENSIONLESS, + eta: DIMENSIONLESS = 1.0, + ) -> None: + r"""Check the shear buckling resistance for webs without intermediate stiffeners. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(6) - Formula (6.22) + + Parameters + ---------- + h_w : MM + [$h_{w}$] Web height [mm]. + t_w : MM + [$t_{w}$] Web thickness [mm]. + epsilon : DIMENSIONLESS + [$\epsilon$] Coefficient depending on $f_y$ [-]. + eta : DIMENSIONLESS, optional + [$\eta$] See section 5 of EN 1993-1-5, conservatively taken as 1.0 [-]. + """ + super().__init__() + self.h_w = h_w + self.t_w = t_w + self.epsilon = epsilon + self.eta = eta + + @staticmethod + def _evaluate( + h_w: MM, + t_w: MM, + epsilon: DIMENSIONLESS, + eta: DIMENSIONLESS, + ) -> bool: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_less_or_equal_to_zero(h_w=h_w, epsilon=epsilon) + raise_if_negative(t_w=t_w, eta=eta) + + return (h_w / t_w) > (72 * (epsilon / eta)) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.22.""" + _equation: str = r"\left( \frac{h_w}{t_w} > 72 \cdot \frac{\epsilon}{\eta} \right)" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"h_w": f"{self.h_w:.3f}", + r"t_w": f"{self.t_w:.3f}", + r"\epsilon": f"{self.epsilon:.3f}", + r"\eta": f"{self.eta:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"CHECK", + result="OK" if self.__bool__() else "\\text{Not OK}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="\\to", + unit="", + ) diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_22.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_22.py new file mode 100644 index 000000000..b88f0c421 --- /dev/null +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_22.py @@ -0,0 +1,71 @@ +"""Testing formula 6.22 of NEN-EN 1993-1-1+C2+A1:2016.""" + +import pytest + +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_22 import Form6Dot22CheckShearBucklingResistance +from blueprints.validations import LessOrEqualToZeroError, NegativeValueError + + +class TestForm6Dot22CheckShearBucklingResistance: + """Validation for formula 6.22 from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + # Example values + h_w = 500.0 + t_w = 5.0 + epsilon = 1.0 + eta = 1.0 + + # Object to test + formula = Form6Dot22CheckShearBucklingResistance(h_w=h_w, t_w=t_w, epsilon=epsilon, eta=eta) + + # Expected result, manually calculated + expected_result = True + + assert formula == expected_result + + @pytest.mark.parametrize( + ("h_w", "t_w", "epsilon", "eta"), + [ + (0.0, 5.0, 1.0, 1.0), # h_w is zero + (500.0, 5.0, 0.0, 1.0), # epsilon is zero + (-500.0, 5.0, 1.0, 1.0), # h_w is negative + (500.0, 5.0, -1.0, 1.0), # epsilon is negative + (500.0, -5.0, 1.0, 1.0), # t_w is negative + (500.0, 5.0, 1.0, -1.0), # eta is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, h_w: float, t_w: float, epsilon: float, eta: float) -> None: + """Test invalid values.""" + with pytest.raises((NegativeValueError, LessOrEqualToZeroError)): + Form6Dot22CheckShearBucklingResistance(h_w=h_w, t_w=t_w, epsilon=epsilon, eta=eta) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"CHECK \to \left( \frac{h_w}{t_w} > 72 \cdot \frac{\epsilon}{\eta} \right) \to " + r"\left( \frac{500.000}{5.000} > 72 \cdot \frac{1.000}{1.000} \right) \to OK", + ), + ("short", r"CHECK \to OK"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + # Example values + h_w = 500.0 + t_w = 5.0 + epsilon = 1.0 + eta = 1.0 + + # Object to test + latex = Form6Dot22CheckShearBucklingResistance(h_w=h_w, t_w=t_w, epsilon=epsilon, eta=eta).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." From 1729a9a9081343dcedf34818f9f63fae979a179b Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Tue, 25 Feb 2025 19:48:42 +0100 Subject: [PATCH 08/14] refactor formula classes for shear stress and design plastic shear resistance; update corresponding tests and documentation --- .../formula_6_18.py | 2 +- .../formula_6_20.py | 2 +- .../formula_6_21.py | 12 ++++++------ .../eurocode/ec3_1993_1_1_2016/formulas.md | 13 +++++++------ .../test_formula_6_18.py | 12 ++++++------ .../test_formula_6_20.py | 10 +++++----- .../test_formula_6_21.py | 18 +++++++++--------- 7 files changed, 35 insertions(+), 34 deletions(-) diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py index 56adac448..69f95addf 100644 --- a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py @@ -9,7 +9,7 @@ from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative -class Form6Dot18VplRd(Formula): +class Form6Dot18DesignPlasticShearResistance(Formula): r"""Class representing formula 6.18 for the calculation of [$V_{pl,Rd}$].""" label = "6.18" diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_20.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_20.py index 87f9de20b..01ba7f2b4 100644 --- a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_20.py +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_20.py @@ -7,7 +7,7 @@ from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative -class Form6Dot20TauEd(Formula): +class Form6Dot20ShearStress(Formula): r"""Class representing formula 6.20 for the calculation of [$\tau_{Ed}$].""" label = "6.20" diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py index 4cb06ccff..583865ae2 100644 --- a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py @@ -1,4 +1,4 @@ -"""Formula 6.20 from NEN-EN 1993-1-1+C2+A1:2016: Chapter 6 - Ultimate Limit State.""" +"""Formula 6.21 from NEN-EN 1993-1-1+C2+A1:2016: Chapter 6 - Ultimate Limit State.""" from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016 import NEN_EN_1993_1_1_C2_A1_2016 from blueprints.codes.formula import Formula @@ -7,10 +7,10 @@ from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative -class Form6Dot20TauEd(Formula): - r"""Class representing formula 6.20 for the calculation of [$\tau_{Ed}$].""" +class Form6Dot21ShearStressIOrHSection(Formula): + r"""Class representing formula 6.21 for the calculation of [$\tau_{Ed}$].""" - label = "6.20" + label = "6.21" source_document = NEN_EN_1993_1_1_C2_A1_2016 def __init__( @@ -22,7 +22,7 @@ def __init__( 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. - NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(5) - Formula (6.20) + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(5) - Formula (6.21) Parameters ---------- @@ -53,7 +53,7 @@ def _evaluate( return v_ed / a_w def latex(self) -> LatexFormula: - """Returns LatexFormula object for formula 6.20.""" + """Returns LatexFormula object for formula 6.21.""" _equation: str = r"\frac{V_{Ed}}{A_w} \text{ if } A_f / A_w \ge 0.6" _numeric_equation: str = latex_replace_symbols( _equation, diff --git a/docs/objects_overview/eurocode/ec3_1993_1_1_2016/formulas.md b/docs/objects_overview/eurocode/ec3_1993_1_1_2016/formulas.md index a894a2269..4050f8298 100644 --- a/docs/objects_overview/eurocode/ec3_1993_1_1_2016/formulas.md +++ b/docs/objects_overview/eurocode/ec3_1993_1_1_2016/formulas.md @@ -41,12 +41,13 @@ Total of 108 formulas present. | 6.14 | :x: | | | | 6.15 | :x: | | | | 6.16 | :x: | | | -| 6.17 | :x: | | | -| 6.18 | :x: | | | -| 6.19 | :x: | | | -| 6.20 | :x: | | | -| 6.21 | :x: | | | -| 6.22 | :x: | | | +| 6.17 | :heavy_check_mark: | | Form6Dot17CheckShearForce | +| 6.18 | :heavy_check_mark: | | Form6Dot18DesignPlasticShearResistance | +| 6.18 A_v | :heavy_check_mark: | | Various equations | +| 6.19 | :heavy_check_mark: | | Form6Dot19CheckDesignElasticShearResistance | +| 6.20 | :heavy_check_mark: | | Form6Dot20ShearStress | +| 6.21 | :heavy_check_mark: | | Form6Dot21ShearStressIOrHSection | +| 6.22 | :heavy_check_mark: | | Form6Dot22CheckShearBucklingResistance | | 6.23 | :x: | | | | 6.24 | :x: | | | | 6.25 | :x: | | | diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py index df99993bf..3ee83e357 100644 --- a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py @@ -2,11 +2,11 @@ import pytest -from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_18 import Form6Dot18VplRd +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_18 import Form6Dot18DesignPlasticShearResistance from blueprints.validations import LessOrEqualToZeroError, NegativeValueError -class TestForm6Dot18VplRd: +class TestForm6Dot18DesignPlasticShearResistance: """Validation for formula 6.18 from NEN-EN 1993-1-1+C2+A1:2016.""" def test_evaluation(self) -> None: @@ -17,7 +17,7 @@ def test_evaluation(self) -> None: gamma_m0 = 1.0 # Object to test - formula = Form6Dot18VplRd(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0) + formula = Form6Dot18DesignPlasticShearResistance(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0) # Expected result, manually calculated manually_calculated_result = 409918.6911246343 # N @@ -35,7 +35,7 @@ def test_evaluation(self) -> None: 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)): - Form6Dot18VplRd(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0) + Form6Dot18DesignPlasticShearResistance(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0) @pytest.mark.parametrize( ("a_v", "f_y", "gamma_m0"), @@ -47,7 +47,7 @@ def test_raise_error_when_invalid_values_are_given(self, a_v: float, f_y: float, 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): - Form6Dot18VplRd(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0) + Form6Dot18DesignPlasticShearResistance(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0) @pytest.mark.parametrize( ("representation", "expected"), @@ -68,7 +68,7 @@ def test_latex(self, representation: str, expected: str) -> None: gamma_m0 = 1.0 # Object to test - latex = Form6Dot18VplRd(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0).latex() + latex = Form6Dot18DesignPlasticShearResistance(a_v=a_v, f_y=f_y, gamma_m0=gamma_m0).latex() actual = { "complete": latex.complete, diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py index f42b2f433..1696ea684 100644 --- a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py @@ -2,11 +2,11 @@ import pytest -from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_20 import Form6Dot20TauEd +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_20 import Form6Dot20ShearStress from blueprints.validations import LessOrEqualToZeroError, NegativeValueError -class TestForm6Dot20TauEd: +class TestForm6Dot20ShearStress: """Validation for formula 6.20 from NEN-EN 1993-1-1+C2+A1:2016.""" def test_evaluation(self) -> None: @@ -18,7 +18,7 @@ def test_evaluation(self) -> None: t = 4.0 # Object to test - formula = Form6Dot20TauEd(v_ed=v_ed, s=s, i=i, t=t) + formula = Form6Dot20ShearStress(v_ed=v_ed, s=s, i=i, t=t) # Expected result, manually calculated manually_calculated_result = 166.66666666666666 # MPa @@ -39,7 +39,7 @@ def test_evaluation(self) -> None: 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)): - Form6Dot20TauEd(v_ed=v_ed, s=s, i=i, t=t) + Form6Dot20ShearStress(v_ed=v_ed, s=s, i=i, t=t) @pytest.mark.parametrize( ("representation", "expected"), @@ -60,7 +60,7 @@ def test_latex(self, representation: str, expected: str) -> None: t = 4.0 # Object to test - latex = Form6Dot20TauEd(v_ed=v_ed, s=s, i=i, t=t).latex() + latex = Form6Dot20ShearStress(v_ed=v_ed, s=s, i=i, t=t).latex() actual = { "complete": latex.complete, diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py index f59bbc090..ee04898b3 100644 --- a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py @@ -1,13 +1,13 @@ -"""Testing formula 6.20 of NEN-EN 1993-1-1+C2+A1:2016.""" +"""Testing formula 6.21 of NEN-EN 1993-1-1+C2+A1:2016.""" import pytest -from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_21 import Form6Dot20TauEd +from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_21 import Form6Dot21ShearStressIOrHSection from blueprints.validations import LessOrEqualToZeroError, NegativeValueError -class TestForm6Dot20TauEd: - """Validation for formula 6.20 from NEN-EN 1993-1-1+C2+A1:2016.""" +class TestForm6Dot21ShearStressIOrHSection: + """Validation for formula 6.21 from NEN-EN 1993-1-1+C2+A1:2016.""" def test_evaluation(self) -> None: """Tests the evaluation of the result.""" @@ -17,7 +17,7 @@ def test_evaluation(self) -> None: a_f = 150.0 # Object to test - formula = Form6Dot20TauEd(v_ed=v_ed, a_w=a_w, a_f=a_f) + formula = Form6Dot21ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f) # Expected result, manually calculated manually_calculated_result = 5.0 # MPa @@ -34,7 +34,7 @@ def test_evaluation(self) -> None: 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): - Form6Dot20TauEd(v_ed=v_ed, a_w=a_w, a_f=a_f) + Form6Dot21ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f) @pytest.mark.parametrize( ("v_ed", "a_w", "a_f"), @@ -46,7 +46,7 @@ def test_raise_error_when_invalid_values_are_given(self, v_ed: float, a_w: float 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): - Form6Dot20TauEd(v_ed=v_ed, a_w=a_w, a_f=a_f) + Form6Dot21ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f) @pytest.mark.parametrize( ("v_ed", "a_w", "a_f"), @@ -57,7 +57,7 @@ def test_raise_error_when_a_w_is_invalid(self, v_ed: float, a_w: float, a_f: flo 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): - Form6Dot20TauEd(v_ed=v_ed, a_w=a_w, a_f=a_f) + Form6Dot21ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f) @pytest.mark.parametrize( ("representation", "expected"), @@ -78,7 +78,7 @@ def test_latex(self, representation: str, expected: str) -> None: a_f = 150.0 # Object to test - latex = Form6Dot20TauEd(v_ed=v_ed, a_w=a_w, a_f=a_f).latex() + latex = Form6Dot21ShearStressIOrHSection(v_ed=v_ed, a_w=a_w, a_f=a_f).latex() actual = { "complete": latex.complete, From 4d1739eac83fae296b0bdcaff26c8fa8fae632ff Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Tue, 25 Feb 2025 20:09:17 +0100 Subject: [PATCH 09/14] update parameter description for shear area in formula 6.18 implementation --- .../chapter_6_ultimate_limit_state/formula_6_18.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py index 69f95addf..e2c0d7bfd 100644 --- a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18.py @@ -28,7 +28,7 @@ def __init__( Parameters ---------- a_v : MM2 - [$A_v$] Shear area [$mm^2$]. + [$A_v$] Shear area, to be taken from a subformula from 6.18 [$mm^2$]. f_y : MPA [$f_y$] Yield strength of the material [$MPa$]. gamma_m0 : DIMENSIONLESS From c19c99c5851a0822d30e1800e43f2c3411c167aa Mon Sep 17 00:00:00 2001 From: Gerjan Dorgelo Date: Wed, 9 Apr 2025 11:05:58 +0200 Subject: [PATCH 10/14] Fix LaTeX formatting in test formulas for consistency --- .../test_formula_6_18.py | 4 +-- .../test_formula_6_18_sub_av.py | 32 +++++++++---------- .../test_formula_6_20.py | 4 +-- .../test_formula_6_21.py | 4 +-- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py index 3ee83e357..a4a26c40a 100644 --- a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18.py @@ -55,9 +55,9 @@ def test_raise_error_when_gamma_m0_is_invalid(self, a_v: float, f_y: float, gamm ( "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", + r"\frac{2000.000 \cdot (355.000 / \sqrt{3})}{1.000} = 409918.691 \ N", ), - ("short", r"V_{pl,Rd} = 409918.691 N"), + ("short", r"V_{pl,Rd} = 409918.691 \ N"), ], ) def test_latex(self, representation: str, expected: str) -> None: diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py index 92609ad4e..4cdd80c5e 100644 --- a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py @@ -57,9 +57,9 @@ def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, hw: "complete", r"A_v = max(A - 2 \cdot b \cdot t_f + (t_w + 2 \cdot r) \cdot t_f; \eta \cdot h_w \cdot t_w) = " r"max(10000.000 - 2 \cdot 200.000 \cdot 15.000 + (8.000 + 2 \cdot 10.000) \cdot 15.000; 1.000 \cdot 250.000 \cdot 8.000) = " - r"4420.000 mm^2", + r"4420.000 \ mm^2", ), - ("short", r"A_v = 4420.000 mm^2"), + ("short", r"A_v = 4420.000 \ mm^2"), ], ) def test_latex(self, representation: str, expected: str) -> None: @@ -119,9 +119,9 @@ def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, tf: ( "complete", r"A_v = A - 2 \cdot b \cdot t_f + (t_w + r) \cdot t_f = " - r"8000.000 - 2 \cdot 150.000 \cdot 12.000 + (6.000 + 8.000) \cdot 12.000 = 4568.000 mm^2", + r"8000.000 - 2 \cdot 150.000 \cdot 12.000 + (6.000 + 8.000) \cdot 12.000 = 4568.000 \ mm^2", ), - ("short", r"A_v = 4568.000 mm^2"), + ("short", r"A_v = 4568.000 \ mm^2"), ], ) def test_latex(self, representation: str, expected: str) -> None: @@ -175,9 +175,9 @@ def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, tf: ( "complete", r"A_v = 0.9 \cdot (A - b \cdot t_f) = " - r"0.9 \cdot (6000.000 - 100.000 \cdot 10.000) = 4500.000 mm^2", + r"0.9 \cdot (6000.000 - 100.000 \cdot 10.000) = 4500.000 \ mm^2", ), - ("short", r"A_v = 4500.000 mm^2"), + ("short", r"A_v = 4500.000 \ mm^2"), ], ) def test_latex(self, representation: str, expected: str) -> None: @@ -230,9 +230,9 @@ def test_raise_error_when_invalid_values_are_given(self, hw: list[float], tw: li ( "complete", r"A_v = \eta \cdot \sum (h_{w0} \cdot t_{w0} + h_{w1} \cdot t_{w1}) = " - r"1.000 \cdot (250.000 \cdot 8.000 + 300.000 \cdot 10.000) = 5000.000 mm^2", + r"1.000 \cdot (250.000 \cdot 8.000 + 300.000 \cdot 10.000) = 5000.000 \ mm^2", ), - ("short", r"A_v = 5000.000 mm^2"), + ("short", r"A_v = 5000.000 \ mm^2"), ], ) def test_latex(self, representation: str, expected: str) -> None: @@ -285,9 +285,9 @@ def test_raise_error_when_invalid_values_are_given(self, a: float, hw: list[floa ( "complete", r"A_v = A - \sum (h_{w0} \cdot t_{w0} + h_{w1} \cdot t_{w1}) = " - r"12000.000 - (250.000 \cdot 8.000 + 300.000 \cdot 10.000) = 7000.000 mm^2", + r"12000.000 - (250.000 \cdot 8.000 + 300.000 \cdot 10.000) = 7000.000 \ mm^2", ), - ("short", r"A_v = 7000.000 mm^2"), + ("short", r"A_v = 7000.000 \ mm^2"), ], ) def test_latex(self, representation: str, expected: str) -> None: @@ -339,9 +339,9 @@ def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, h: ( "complete", r"A_v = \frac{A \cdot h}{b + h} = " - r"\frac{5000.000 \cdot 200.000}{100.000 + 200.000} = 3333.333 mm^2", + r"\frac{5000.000 \cdot 200.000}{100.000 + 200.000} = 3333.333 \ mm^2", ), - ("short", r"A_v = 3333.333 mm^2"), + ("short", r"A_v = 3333.333 \ mm^2"), ], ) def test_latex(self, representation: str, expected: str) -> None: @@ -393,9 +393,9 @@ def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, h: ( "complete", r"A_v = \frac{A \cdot b}{b + h} = " - r"\frac{5000.000 \cdot 100.000}{100.000 + 200.000} = 1666.667 mm^2", + r"\frac{5000.000 \cdot 100.000}{100.000 + 200.000} = 1666.667 \ mm^2", ), - ("short", r"A_v = 1666.667 mm^2"), + ("short", r"A_v = 1666.667 \ mm^2"), ], ) def test_latex(self, representation: str, expected: str) -> None: @@ -443,9 +443,9 @@ def test_raise_error_when_invalid_values_are_given(self, a: float) -> None: ( "complete", r"A_v = \frac{2 \cdot A}{\pi} = " - r"\frac{2 \cdot 4000.000}{\pi} = 2546.479 mm^2", + r"\frac{2 \cdot 4000.000}{\pi} = 2546.479 \ mm^2", ), - ("short", r"A_v = 2546.479 mm^2"), + ("short", r"A_v = 2546.479 \ mm^2"), ], ) def test_latex(self, representation: str, expected: str) -> None: diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py index 1696ea684..b6a265639 100644 --- a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_20.py @@ -46,9 +46,9 @@ def test_raise_error_when_invalid_values_are_given(self, v_ed: float, s: float, [ ( "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", + 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"), + ("short", r"\tau_{Ed} = 166.667 \ MPa"), ], ) def test_latex(self, representation: str, expected: str) -> None: diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py index ee04898b3..1555cb9c8 100644 --- a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_21.py @@ -65,9 +65,9 @@ def test_raise_error_when_a_f_divided_by_a_w_is_invalid(self, v_ed: float, a_w: ( "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", + 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"), + ("short", r"\tau_{Ed} = 5.000 \ MPa"), ], ) def test_latex(self, representation: str, expected: str) -> None: From 539b5864d95f01060fe538939c036f0f62fc8a19 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Sun, 27 Apr 2025 16:09:00 +0200 Subject: [PATCH 11/14] Update blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py Co-authored-by: PabloVasconez --- .../chapter_6_ultimate_limit_state/formula_6_21.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py index 583865ae2..c18c73516 100644 --- a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_21.py @@ -47,7 +47,7 @@ def _evaluate( """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 a_f / a_w < 0.6: + 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 From 86505b625cf81c8d90630398cdf3e932425404c8 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Sun, 27 Apr 2025 16:12:41 +0200 Subject: [PATCH 12/14] Refactor formulas in NEN-EN 1993-1-1+C2+A1:2016 to improve validation and evaluation logic; update tests accordingly --- .../formula_6_18_sub_av.py | 171 +++++++++++++----- .../test_formula_6_18_sub_av.py | 141 +++++++++++---- 2 files changed, 222 insertions(+), 90 deletions(-) diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18_sub_av.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18_sub_av.py index eedd8dc91..8ce78493a 100644 --- a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18_sub_av.py +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_18_sub_av.py @@ -6,7 +6,7 @@ from blueprints.codes.formula import Formula from blueprints.codes.latex_formula import LatexFormula, latex_replace_symbols from blueprints.type_alias import DIMENSIONLESS, MM, MM2 -from blueprints.validations import raise_if_lists_differ_in_length, raise_if_negative +from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_lists_differ_in_length, raise_if_negative class Form6Dot18SubARolledIandHSection(Formula): @@ -44,7 +44,7 @@ def __init__( tw : MM [$t_w$] Web thickness [$mm$]. If the web thickness is not constant, tw should be taken as the minimum thickness. eta : DIMENSIONLESS, optional - [$\eta$] See EN 1993-1-5. Note, $eta$ may be conservatively taken equal to 1.0. + [$\eta$] Dimensionless conversionfactor, see EN 1993-1-5 5.1. Note, $eta$ may be conservatively taken equal to 1.0. """ super().__init__() self.a = a @@ -71,7 +71,7 @@ def _evaluate( av = a - 2 * b * tf + (tw + 2 * r) * tf av_min = eta * hw * tw - return max(av, av_min) + return max(0, av, av_min) def latex(self) -> LatexFormula: """Returns LatexFormula object for formula 6.18suba.""" @@ -148,7 +148,7 @@ def _evaluate( """Evaluates the formula, for more information see the __init__ method.""" raise_if_negative(a=a, b=b, tf=tf, tw=tw, r=r) - return a - 2 * b * tf + (tw + r) * tf + return max(0, a - 2 * b * tf + (tw + r) * tf) def latex(self) -> LatexFormula: """Returns LatexFormula object for formula 6.18subb.""" @@ -174,8 +174,8 @@ def latex(self) -> LatexFormula: ) -class Form6Dot18SubCTSection(Formula): - r"""Class representing formula 6.18subc for the calculation of shear area for a T-section with load parallel to web.""" +class Form6Dot18SubCTSectionRolled(Formula): + r"""Class representing formula 6.18subc for the calculation of shear area for a rolled T-section with load parallel to web.""" label = "6.18subc" source_document = NEN_EN_1993_1_1_C2_A1_2016 @@ -185,6 +185,8 @@ def __init__( a: MM2, b: MM, tf: MM, + tw: MM, + r: MM, ) -> None: r"""[$A_v$] Calculation of the shear area for a T-section with load parallel to web [$mm^2$]. @@ -198,32 +200,105 @@ def __init__( [$b$] Overall breadth [$mm$]. tf : MM [$t_f$] Flange thickness [$mm$]. + tw : MM + [$t_w$] Web thickness [$mm$]. If the web thickness is not constant, tw should be taken as the minimum thickness. + r : MM + [$r$] Root radius [$mm$]. """ super().__init__() self.a = a self.b = b self.tf = tf + self.tw = tw + self.r = r @staticmethod def _evaluate( a: MM2, b: MM, tf: MM, + tw: MM, + r: MM, ) -> MM2: """Evaluates the formula, for more information see the __init__ method.""" - raise_if_negative(a=a, b=b, tf=tf) + raise_if_negative(a=a, b=b, tf=tf, tw=tw, r=r) - return 0.9 * (a - b * tf) + return max(0, a - b * tf + (tw + 2 * r) * tf / 2) def latex(self) -> LatexFormula: """Returns LatexFormula object for formula 6.18subc.""" - _equation: str = r"0.9 \cdot (A - b \cdot t_f)" + _equation: str = r"A - b \cdot t_f + (t_w + 2 \cdot r) \cdot \frac{t_f}{2}" _numeric_equation: str = latex_replace_symbols( _equation, { r"A": f"{self.a:.3f}", r"b": f"{self.b:.3f}", r"t_f": f"{self.tf:.3f}", + r"t_w": f"{self.tw:.3f}", + r" r": f" {self.r:.3f}", + }, + False, + ) + return LatexFormula( + return_symbol=r"A_v", + result=f"{self:.3f}", + equation=_equation, + numeric_equation=_numeric_equation, + comparison_operator_label="=", + unit="mm^2", + ) + + +class Form6Dot18SubCTSectionWelded(Formula): + r"""Class representing formula 6.18subc for the calculation of shear area for a welded T-section with load parallel to web.""" + + label = "6.18subc" + source_document = NEN_EN_1993_1_1_C2_A1_2016 + + def __init__( + self, + tf: MM, + tw: MM, + h: MM, + ) -> None: + r"""[$A_v$] Calculation of the shear area for a T-section with load parallel to web [$mm^2$]. + + NEN-EN 1993-1-1+C2+A1:2016 art.6.2.6(3) - Formula (6.18subc) + + Parameters + ---------- + tf : MM + [$t_f$] Flange thickness [$mm$]. + tw : MM + [$t_w$] Web thickness [$mm$]. If the web thickness is not constant, tw should be taken as the minimum thickness. + h : MM + [$h$] Overall depth [$mm$]. + """ + super().__init__() + self.tf = tf + self.tw = tw + self.h = h + + @staticmethod + def _evaluate( + tf: MM, + tw: MM, + h: MM, + ) -> MM2: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(tf=tf, tw=tw, h=h) + + return max(0, tw * (h * tf / 2)) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 6.18subc.""" + _equation: str = r"t_w \cdot (h \cdot t_f / 2)" + _numeric_equation: str = latex_replace_symbols( + _equation, + { + r"t_f": f"{self.tf:.3f}", + r"t_w": f"{self.tw:.3f}", + r"h": f"{self.h:.3f}", }, False, ) @@ -245,8 +320,8 @@ class Form6Dot18SubDWeldedIHandBoxSection(Formula): def __init__( self, - hw: list[MM], - tw: list[MM], + hw_list: list[MM], + tw_list: list[MM], eta: DIMENSIONLESS, ) -> None: r"""[$A_v$] Calculation of the shear area for welded I, H, and box sections with load parallel to web [$mm^2$]. @@ -255,41 +330,38 @@ def __init__( Parameters ---------- - hw : list[MM] + hw_list : list[MM] [$h_w$] List of depths of the web [$mm$]. - tw : list[MM] + tw_list : list[MM] [$t_w$] List of web thicknesses [$mm$]. If the web thickness is not constant, tw should be taken as the minimum thickness. eta : DIMENSIONLESS [$\eta$] See EN 1993-1-5. Note, $eta$ may be conservatively taken equal to 1.0. """ super().__init__() - self.hw = hw - self.tw = tw + self.hw_list = hw_list + self.tw_list = tw_list self.eta = eta @staticmethod def _evaluate( - hw: list[MM], - tw: list[MM], + hw_list: list[MM], + tw_list: list[MM], eta: DIMENSIONLESS, ) -> MM2: """Evaluates the formula, for more information see the __init__ method.""" raise_if_negative(eta=eta) - for h, t in zip(hw, tw): + for h, t in zip(hw_list, tw_list): raise_if_negative(h=h, t=t) - raise_if_lists_differ_in_length(hw=hw, tw=tw) + raise_if_lists_differ_in_length(hw_list=hw_list, tw_list=tw_list) - return eta * sum(h * t for h, t in zip(hw, tw)) + return max(0, eta * sum(h * t for h, t in zip(hw_list, tw_list))) def latex(self) -> LatexFormula: """Returns LatexFormula object for formula 6.18subd.""" - _equation: str = r"\eta \cdot \sum (h_{w0} \cdot t_{w0}" - for i in range(1, len(self.hw)): - _equation += rf" + h_{{w{i}}} \cdot t_{{w{i}}}" - _equation += ")" - _numeric_equation: str = rf"{self.eta:.3f} \cdot (" + rf"{self.hw[0]:.3f} \cdot {self.tw[0]:.3f}" - for i in range(1, len(self.hw)): - _numeric_equation += rf" + {self.hw[i]:.3f} \cdot {self.tw[i]:.3f}" + _equation: str = r"\eta \cdot \sum (h_{w} \cdot t_{w})" + _numeric_equation: str = rf"{self.eta:.3f} \cdot (" + rf"{self.hw_list[0]:.3f} \cdot {self.tw_list[0]:.3f}" + for i in range(1, len(self.hw_list)): + _numeric_equation += rf" + {self.hw_list[i]:.3f} \cdot {self.tw_list[i]:.3f}" _numeric_equation += ")" return LatexFormula( return_symbol=r"A_v", @@ -312,8 +384,8 @@ class Form6Dot18SubEWeldedIHandBoxSection(Formula): def __init__( self, a: MM2, - hw: list[MM], - tw: list[MM], + hw_list: list[MM], + tw_list: list[MM], ) -> None: r"""[$A_v$] Calculation of the shear area for welded I, H, channel, and box sections with load parallel to flanges [$mm^2$]. @@ -323,39 +395,36 @@ def __init__( ---------- a : MM2 [$A$] Cross-sectional area [$mm^2$]. - hw : list[MM] + hw_list : list[MM] [$h_w$] List of depths of the web [$mm$]. - tw : list[MM] + tw_list : list[MM] [$t_w$] List of web thicknesses [$mm$]. If the web thickness is not constant, tw should be taken as the minimum thickness. """ super().__init__() self.a = a - self.hw = hw - self.tw = tw + self.hw_list = hw_list + self.tw_list = tw_list @staticmethod def _evaluate( a: MM2, - hw: list[MM], - tw: list[MM], + hw_list: list[MM], + tw_list: list[MM], ) -> MM2: """Evaluates the formula, for more information see the __init__ method.""" raise_if_negative(a=a) - for h, t in zip(hw, tw): + for h, t in zip(hw_list, tw_list): raise_if_negative(h=h, t=t) - raise_if_lists_differ_in_length(hw=hw, tw=tw) + raise_if_lists_differ_in_length(hw=hw_list, tw_list=tw_list) - return a - sum(h * t for h, t in zip(hw, tw)) + return max(0, a - sum(h * t for h, t in zip(hw_list, tw_list))) def latex(self) -> LatexFormula: """Returns LatexFormula object for formula 6.18sube.""" - _equation: str = r"A - \sum (h_{w0} \cdot t_{w0}" - for i in range(1, len(self.hw)): - _equation += rf" + h_{{w{i}}} \cdot t_{{w{i}}}" - _equation += ")" - _numeric_equation: str = rf"{self.a:.3f} - (" + rf"{self.hw[0]:.3f} \cdot {self.tw[0]:.3f}" - for i in range(1, len(self.hw)): - _numeric_equation += rf" + {self.hw[i]:.3f} \cdot {self.tw[i]:.3f}" + _equation: str = r"A - \sum (h_{w} \cdot t_{w})" + _numeric_equation: str = rf"{self.a:.3f} - (" + rf"{self.hw_list[0]:.3f} \cdot {self.tw_list[0]:.3f}" + for i in range(1, len(self.hw_list)): + _numeric_equation += rf" + {self.hw_list[i]:.3f} \cdot {self.tw_list[i]:.3f}" _numeric_equation += ")" return LatexFormula( return_symbol=r"A_v", @@ -407,8 +476,9 @@ def _evaluate( ) -> MM2: """Evaluates the formula, for more information see the __init__ method.""" raise_if_negative(a=a, b=b, h=h) - - return a * h / (b + h) + denominator = b + h + raise_if_less_or_equal_to_zero(denominator=denominator) + return max(0, a * h / (b + h)) def latex(self) -> LatexFormula: """Returns LatexFormula object for formula 6.18subf1.""" @@ -472,8 +542,9 @@ def _evaluate( ) -> MM2: """Evaluates the formula, for more information see the __init__ method.""" raise_if_negative(a=a, b=b, h=h) - - return a * b / (b + h) + denominator = b + h + raise_if_less_or_equal_to_zero(denominator=denominator) + return max(0, a * b / (b + h)) def latex(self) -> LatexFormula: """Returns LatexFormula object for formula 6.18subf2.""" @@ -526,7 +597,7 @@ def _evaluate( """Evaluates the formula, for more information see the __init__ method.""" raise_if_negative(a=a) - return 2 * a / np.pi + return max(0, 2 * a / np.pi) def latex(self) -> LatexFormula: """Returns LatexFormula object for formula 6.18subg.""" diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py index 4cdd80c5e..b79588595 100644 --- a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_18_sub_av.py @@ -5,7 +5,8 @@ from blueprints.codes.eurocode.nen_en_1993_1_1_c2_a1_2016.chapter_6_ultimate_limit_state.formula_6_18_sub_av import ( Form6Dot18SubARolledIandHSection, Form6Dot18SubBRolledChannelSection, - Form6Dot18SubCTSection, + Form6Dot18SubCTSectionRolled, + Form6Dot18SubCTSectionWelded, Form6Dot18SubDWeldedIHandBoxSection, Form6Dot18SubEWeldedIHandBoxSection, Form6Dot18SubF1RolledRectangularHollowSectionDepth, @@ -142,7 +143,7 @@ def test_latex(self, representation: str, expected: str) -> None: assert expected == actual[representation], f"{representation} representation failed." -class TestForm6Dot18SubCTSection: +class TestForm6Dot18SubCRolledTSectionRolled: """Validation for formula 6.18subc from NEN-EN 1993-1-1+C2+A1:2016.""" def test_evaluation(self) -> None: @@ -150,34 +151,38 @@ def test_evaluation(self) -> None: a = 6000.0 b = 100.0 tf = 10.0 + tw = 8.0 + r = 5.0 - formula = Form6Dot18SubCTSection(a=a, b=b, tf=tf) - manually_calculated_result = 4500.0 # mm^2 + formula = Form6Dot18SubCTSectionRolled(a=a, b=b, tf=tf, tw=tw, r=r) + manually_calculated_result = 5090.0 # mm^2 assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) @pytest.mark.parametrize( - ("a", "b", "tf"), + ("a", "b", "tf", "tw", "r"), [ - (-6000.0, 100.0, 10.0), # a is negative - (6000.0, -100.0, 10.0), # b is negative - (6000.0, 100.0, -10.0), # tf is negative + (-6000.0, 100.0, 10.0, 8.0, 5.0), # a is negative + (6000.0, -100.0, 10.0, 8.0, 5.0), # b is negative + (6000.0, 100.0, -10.0, 8.0, 5.0), # tf is negative + (6000.0, 100.0, 10.0, -8.0, 5.0), # tw is negative + (6000.0, 100.0, 10.0, 8.0, -5.0), # r is negative ], ) - def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, tf: float) -> None: + def test_raise_error_when_invalid_values_are_given(self, a: float, b: float, tf: float, tw: float, r: float) -> None: """Test invalid values.""" with pytest.raises(NegativeValueError): - Form6Dot18SubCTSection(a=a, b=b, tf=tf) + Form6Dot18SubCTSectionRolled(a=a, b=b, tf=tf, tw=tw, r=r) @pytest.mark.parametrize( ("representation", "expected"), [ ( "complete", - r"A_v = 0.9 \cdot (A - b \cdot t_f) = " - r"0.9 \cdot (6000.000 - 100.000 \cdot 10.000) = 4500.000 \ mm^2", + r"A_v = A - b \cdot t_f + (t_w + 2 \cdot r) \cdot \frac{t_f}{2} = " + r"6000.000 - 100.000 \cdot 10.000 + (8.000 + 2 \cdot 5.000) \cdot \frac{10.000}{2} = 5090.000 \ mm^2", ), - ("short", r"A_v = 4500.000 \ mm^2"), + ("short", r"A_v = 5090.000 \ mm^2"), ], ) def test_latex(self, representation: str, expected: str) -> None: @@ -185,8 +190,64 @@ def test_latex(self, representation: str, expected: str) -> None: a = 6000.0 b = 100.0 tf = 10.0 + tw = 8.0 + r = 5.0 + + latex = Form6Dot18SubCTSectionRolled(a=a, b=b, tf=tf, tw=tw, r=r).latex() + + actual = { + "complete": latex.complete, + "short": latex.short, + } + + assert expected == actual[representation], f"{representation} representation failed." + + +class TestForm6Dot18SubCRolledTSectionWelded: + """Validation for formula 6.18subc from NEN-EN 1993-1-1+C2+A1:2016.""" + + def test_evaluation(self) -> None: + """Tests the evaluation of the result.""" + tf = 10.0 + tw = 8.0 + h = 200.0 + + formula = Form6Dot18SubCTSectionWelded(tf=tf, tw=tw, h=h) + manually_calculated_result = 8000.0 # mm^2 + + assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) + + @pytest.mark.parametrize( + ("tf", "tw", "h"), + [ + (-10.0, 8.0, 200.0), # tf is negative + (10.0, -8.0, 200.0), # tw is negative + (10.0, 8.0, -200.0), # h is negative + ], + ) + def test_raise_error_when_invalid_values_are_given(self, tf: float, tw: float, h: float) -> None: + """Test invalid values.""" + with pytest.raises(NegativeValueError): + Form6Dot18SubCTSectionWelded(tf=tf, tw=tw, h=h) + + @pytest.mark.parametrize( + ("representation", "expected"), + [ + ( + "complete", + r"A_v = t_w \cdot (h \cdot t_f / 2) = " + r"8.000 \cdot (200.000 \cdot 10.000 / 2) = 8000.000 \ mm^2", + ), + ("short", r"A_v = 8000.000 \ mm^2"), + ], + ) + def test_latex(self, representation: str, expected: str) -> None: + """Test the latex representation of the formula.""" + tf = 10.0 + tw = 8.0 + h = 200.0 - latex = Form6Dot18SubCTSection(a=a, b=b, tf=tf).latex() + latex = Form6Dot18SubCTSectionWelded(tf=tf, tw=tw, h=h).latex() actual = { "complete": latex.complete, @@ -201,35 +262,35 @@ class TestForm6Dot18SubDWeldedIHandBoxSection: def test_evaluation(self) -> None: """Tests the evaluation of the result.""" - hw = [250.0, 300.0] - tw = [8.0, 10.0] + hw_list = [250.0, 300.0] + tw_list = [8.0, 10.0] eta = 1.0 - formula = Form6Dot18SubDWeldedIHandBoxSection(hw=hw, tw=tw, eta=eta) + formula = Form6Dot18SubDWeldedIHandBoxSection(hw_list=hw_list, tw_list=tw_list, eta=eta) manually_calculated_result = 5000.0 # mm^2 assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) @pytest.mark.parametrize( - ("hw", "tw", "eta"), + ("hw_list", "tw_list", "eta"), [ - ([250.0, -300.0], [8.0, 10.0], 1.0), # hw contains negative value - ([250.0, 300.0], [8.0, -10.0], 1.0), # tw contains negative value + ([250.0, -300.0], [8.0, 10.0], 1.0), # hw_list contains negative value + ([250.0, 300.0], [8.0, -10.0], 1.0), # tw_list contains negative value ([250.0, 300.0], [8.0, 10.0], -1.0), # eta is negative - ([250.0], [8.0, 10.0], 1.0), # hw and tw are not the same length + ([250.0], [8.0, 10.0], 1.0), # hw_list and tw_list are not the same length ], ) - def test_raise_error_when_invalid_values_are_given(self, hw: list[float], tw: list[float], eta: float) -> None: + def test_raise_error_when_invalid_values_are_given(self, hw_list: list[float], tw_list: list[float], eta: float) -> None: """Test invalid values.""" with pytest.raises((NegativeValueError, ListsNotSameLengthError)): - Form6Dot18SubDWeldedIHandBoxSection(hw=hw, tw=tw, eta=eta) + Form6Dot18SubDWeldedIHandBoxSection(hw_list=hw_list, tw_list=tw_list, eta=eta) @pytest.mark.parametrize( ("representation", "expected"), [ ( "complete", - r"A_v = \eta \cdot \sum (h_{w0} \cdot t_{w0} + h_{w1} \cdot t_{w1}) = " + r"A_v = \eta \cdot \sum (h_{w} \cdot t_{w}) = " r"1.000 \cdot (250.000 \cdot 8.000 + 300.000 \cdot 10.000) = 5000.000 \ mm^2", ), ("short", r"A_v = 5000.000 \ mm^2"), @@ -237,11 +298,11 @@ def test_raise_error_when_invalid_values_are_given(self, hw: list[float], tw: li ) def test_latex(self, representation: str, expected: str) -> None: """Test the latex representation of the formula.""" - hw = [250.0, 300.0] - tw = [8.0, 10.0] + hw_list = [250.0, 300.0] + tw_list = [8.0, 10.0] eta = 1.0 - latex = Form6Dot18SubDWeldedIHandBoxSection(hw=hw, tw=tw, eta=eta).latex() + latex = Form6Dot18SubDWeldedIHandBoxSection(hw_list=hw_list, tw_list=tw_list, eta=eta).latex() actual = { "complete": latex.complete, @@ -257,34 +318,34 @@ class TestForm6Dot18SubEWeldedIHandBoxSection: def test_evaluation(self) -> None: """Tests the evaluation of the result.""" a = 12000.0 - hw = [250.0, 300.0] - tw = [8.0, 10.0] + hw_list = [250.0, 300.0] + tw_list = [8.0, 10.0] - formula = Form6Dot18SubEWeldedIHandBoxSection(a=a, hw=hw, tw=tw) + formula = Form6Dot18SubEWeldedIHandBoxSection(a=a, hw_list=hw_list, tw_list=tw_list) manually_calculated_result = 7000.0 # mm^2 assert formula == pytest.approx(expected=manually_calculated_result, rel=1e-4) @pytest.mark.parametrize( - ("a", "hw", "tw"), + ("a", "hw_list", "tw_list"), [ (-12000.0, [250.0, 300.0], [8.0, 10.0]), # a is negative - (12000.0, [250.0, -300.0], [8.0, 10.0]), # hw contains negative value - (12000.0, [250.0, 300.0], [8.0, -10.0]), # tw contains negative value - (12000.0, [250.0, 300.0], [8.0, 10.0, 12.0]), # hw and tw are not the same length + (12000.0, [250.0, -300.0], [8.0, 10.0]), # hw_list contains negative value + (12000.0, [250.0, 300.0], [8.0, -10.0]), # tw_list contains negative value + (12000.0, [250.0, 300.0], [8.0, 10.0, 12.0]), # hw_list and tw_list are not the same length ], ) - def test_raise_error_when_invalid_values_are_given(self, a: float, hw: list[float], tw: list[float]) -> None: + def test_raise_error_when_invalid_values_are_given(self, a: float, hw_list: list[float], tw_list: list[float]) -> None: """Test invalid values.""" with pytest.raises((NegativeValueError, ListsNotSameLengthError)): - Form6Dot18SubEWeldedIHandBoxSection(a=a, hw=hw, tw=tw) + Form6Dot18SubEWeldedIHandBoxSection(a=a, hw_list=hw_list, tw_list=tw_list) @pytest.mark.parametrize( ("representation", "expected"), [ ( "complete", - r"A_v = A - \sum (h_{w0} \cdot t_{w0} + h_{w1} \cdot t_{w1}) = " + r"A_v = A - \sum (h_{w} \cdot t_{w}) = " r"12000.000 - (250.000 \cdot 8.000 + 300.000 \cdot 10.000) = 7000.000 \ mm^2", ), ("short", r"A_v = 7000.000 \ mm^2"), @@ -293,10 +354,10 @@ def test_raise_error_when_invalid_values_are_given(self, a: float, hw: list[floa def test_latex(self, representation: str, expected: str) -> None: """Test the latex representation of the formula.""" a = 12000.0 - hw = [250.0, 300.0] - tw = [8.0, 10.0] + hw_list = [250.0, 300.0] + tw_list = [8.0, 10.0] - latex = Form6Dot18SubEWeldedIHandBoxSection(a=a, hw=hw, tw=tw).latex() + latex = Form6Dot18SubEWeldedIHandBoxSection(a=a, hw_list=hw_list, tw_list=tw_list).latex() actual = { "complete": latex.complete, From daab1181e54d6a4d03ab14438b4060beeb92d750 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Sun, 27 Apr 2025 16:12:48 +0200 Subject: [PATCH 13/14] Fix validation logic in formula 6.22 for shear buckling resistance; update test cases to cover additional edge cases --- .../chapter_6_ultimate_limit_state/formula_6_22.py | 4 ++-- .../chapter_6_ultimate_limit_state/test_formula_6_22.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_22.py b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_22.py index 268d27b62..85bd1d398 100644 --- a/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_22.py +++ b/blueprints/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/formula_6_22.py @@ -49,8 +49,8 @@ def _evaluate( eta: DIMENSIONLESS, ) -> bool: """Evaluates the formula, for more information see the __init__ method.""" - raise_if_less_or_equal_to_zero(h_w=h_w, epsilon=epsilon) - raise_if_negative(t_w=t_w, eta=eta) + raise_if_less_or_equal_to_zero(t_w=t_w, eta=eta) + raise_if_negative(h_w=h_w, epsilon=epsilon) return (h_w / t_w) > (72 * (epsilon / eta)) diff --git a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_22.py b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_22.py index b88f0c421..24ea065f2 100644 --- a/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_22.py +++ b/tests/codes/eurocode/nen_en_1993_1_1_c2_a1_2016/chapter_6_ultimate_limit_state/test_formula_6_22.py @@ -28,12 +28,12 @@ def test_evaluation(self) -> None: @pytest.mark.parametrize( ("h_w", "t_w", "epsilon", "eta"), [ - (0.0, 5.0, 1.0, 1.0), # h_w is zero - (500.0, 5.0, 0.0, 1.0), # epsilon is zero (-500.0, 5.0, 1.0, 1.0), # h_w is negative (500.0, 5.0, -1.0, 1.0), # epsilon is negative (500.0, -5.0, 1.0, 1.0), # t_w is negative (500.0, 5.0, 1.0, -1.0), # eta is negative + (500.0, 5.0, 1.0, 0.0), # eta is less than or equal to zero + (500.0, 0.0, 1.0, 1.0), # t_w is less than or equal to zero ], ) def test_raise_error_when_invalid_values_are_given(self, h_w: float, t_w: float, epsilon: float, eta: float) -> None: From e561c9d3fe54058eda3945fa2dfd75f61214a564 Mon Sep 17 00:00:00 2001 From: GerjanDorgelo Date: Sun, 27 Apr 2025 16:16:40 +0200 Subject: [PATCH 14/14] Update implementation status for formulas 6.9 to 6.16 in Eurocode 3 documentation --- .../eurocode/ec3_1993_1_1_2016/formulas.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/objects_overview/eurocode/ec3_1993_1_1_2016/formulas.md b/docs/objects_overview/eurocode/ec3_1993_1_1_2016/formulas.md index d0b6fe505..8c053a4c1 100644 --- a/docs/objects_overview/eurocode/ec3_1993_1_1_2016/formulas.md +++ b/docs/objects_overview/eurocode/ec3_1993_1_1_2016/formulas.md @@ -33,14 +33,14 @@ Total of 108 formulas present. | 6.6 | :x: | | | | 6.7 | :x: | | | | 6.8 | :x: | | | -| 6.9 | :x: | | Form6Dot9CheckCompressionForce | -| 6.10 | :x: | | Form6Dot10NcRdClass1And2And3 | -| 6.11 | :x: | | Form6Dot11NcRdClass4 | -| 6.12 | :x: | | Form6Dot12CheckBendingMoment | -| 6.13 | :x: | | Form6Dot13MCRdClass1And2 | -| 6.14 | :x: | | Form6Dot14MCRdClass3 | -| 6.15 | :x: | | Form6Dot15McRdClass4 | -| 6.16 | :x: | | Form6Dot16CheckFlangeWithFastenerHoles | +| 6.9 | :heavy_check_mark: | | Form6Dot9CheckCompressionForce | +| 6.10 | :heavy_check_mark: | | Form6Dot10NcRdClass1And2And3 | +| 6.11 | :heavy_check_mark: | | Form6Dot11NcRdClass4 | +| 6.12 | :heavy_check_mark: | | Form6Dot12CheckBendingMoment | +| 6.13 | :heavy_check_mark: | | Form6Dot13MCRdClass1And2 | +| 6.14 | :heavy_check_mark: | | Form6Dot14MCRdClass3 | +| 6.15 | :heavy_check_mark: | | Form6Dot15McRdClass4 | +| 6.16 | :heavy_check_mark: | | Form6Dot16CheckFlangeWithFastenerHoles | | 6.17 | :heavy_check_mark: | | Form6Dot17CheckShearForce | | 6.18 | :heavy_check_mark: | | Form6Dot18DesignPlasticShearResistance | | 6.18 A_v | :heavy_check_mark: | | Various equations |