diff --git a/blueprints/codes/eurocode/nen_en_1992_1_1_c2_2011/chapter_5_structural_analysis/formula_5_13n.py b/blueprints/codes/eurocode/nen_en_1992_1_1_c2_2011/chapter_5_structural_analysis/formula_5_13n.py new file mode 100644 index 000000000..d7cf095b7 --- /dev/null +++ b/blueprints/codes/eurocode/nen_en_1992_1_1_c2_2011/chapter_5_structural_analysis/formula_5_13n.py @@ -0,0 +1,210 @@ +"""Formula 5.13N from NEN-EN 1992-1-1+C2:2011: Chapter 5 - Structural Analysis.""" + +import math +from typing import Optional + +from blueprints.codes.eurocode.nen_en_1992_1_1_c2_2011 import NEN_EN_1992_1_1_C2_2011 +from blueprints.codes.formula import Formula +from blueprints.codes.latex_formula import LatexFormula +from blueprints.type_alias import DIMENSIONLESS, KN, MM2, MPA +from blueprints.validations import raise_if_less_or_equal_to_zero, raise_if_negative + + +class Form5Dot13NSlendernessCriterion(Formula): + """Class representing formula 5.13N for the calculation of slenderness criterion for individual elements.""" + + label = "5.13N" + source_document = NEN_EN_1992_1_1_C2_2011 + + def __init__(self, phi_eff: float, omega: DIMENSIONLESS, rm: DIMENSIONLESS, n: DIMENSIONLESS) -> None: + r"""[:math:`\lambda_{lim}`] Slenderness limit for individual elements. + + NEN-EN 1992-1-1+C2:2011 art.5.8.3.1(1) - Formula (5.13N) + + Parameters + ---------- + phi_eff : float + Effective creep coefficient. + omega : SubFormOmegaMechanicalReinforcementRatio + Mechanical reinforcement ratio. + rm : SubFormRmMomentRatio + Moment ratio. + n : SubFormNRelativeNormalForce + Relative normal force. + """ + super().__init__() + self.phi_eff = phi_eff + self.omega = omega + self.rm = rm + self.n = n + + @staticmethod + def calculate_a(phi_eff: Optional[float]) -> float: + """Calculate A based on phi_eff.""" + if phi_eff is None: + return 0.7 + return 1 / (1 + 0.2 * phi_eff) + + @staticmethod + def calculate_b(omega: Optional[float]) -> float: + """Calculate B based on omega.""" + if omega is None: + return 1.1 + return omega + + @staticmethod + def calculate_c(rm: Optional[float]) -> float: + """Calculate C based on rm.""" + if rm is None: + return 0.7 + return 1.7 - rm + + @staticmethod + def _evaluate(phi_eff: DIMENSIONLESS, omega: DIMENSIONLESS, rm: DIMENSIONLESS, n: DIMENSIONLESS) -> DIMENSIONLESS: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(phi_eff=phi_eff, omega=omega, rm=rm, n=n) + raise_if_less_or_equal_to_zero(n=n) + + a = Form5Dot13NSlendernessCriterion.calculate_a(phi_eff) + b = Form5Dot13NSlendernessCriterion.calculate_b(omega) + c = Form5Dot13NSlendernessCriterion.calculate_c(rm) + + return 20 * a * b * c / math.sqrt(n) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for formula 5.13N.""" + a = self.calculate_a(self.phi_eff) + b = self.calculate_b(self.omega) + c = self.calculate_c(self.rm) + + return LatexFormula( + return_symbol=r"\lambda_{lim}", + result=f"{self:.3f}", + equation=r"20 \cdot A \cdot B \cdot C / \sqrt{n}", + numeric_equation=rf"20 \cdot {a} \cdot {b} \cdot {c} / \sqrt{{{self.n}}}", + comparison_operator_label="=", + ) + + +class SubForm5Dot13NOmegaMechanicalReinforcementRatio(Formula): + """Class representing sub-formula for the calculation of the mechanical reinforcement ratio (ω).""" + + label = "5.13N_ω" + source_document = NEN_EN_1992_1_1_C2_2011 + + def __init__(self, a_s: MM2, fyd: MPA, a_c: MM2, fcd: MPA) -> None: + r"""[:math:`\omega`] Mechanical reinforcement ratio. + + Parameters + ---------- + a_s : MM2 + Total area of the cross-section of the longitudinal reinforcement [:math:`mm^2`]. + fyd : MPA + Design yield strength of the reinforcement [:math:`MPa`]. + a_c : MM2 + Area of concrete cross-section [:math:`mm^2`]. + fcd : MPA + Design compressive strength of concrete [:math:`MPa`]. + """ + super().__init__() + self.a_s = a_s + self.fyd = fyd + self.a_c = a_c + self.fcd = fcd + + @staticmethod + def _evaluate(a_s: MM2, fyd: MPA, a_c: MM2, fcd: MPA) -> DIMENSIONLESS: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(a_s=a_s, fyd=fyd, a_c=a_c, fcd=fcd) + raise_if_less_or_equal_to_zero(ratio=a_c * fcd) + + return (a_s * fyd) / (a_c * fcd) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for the sub-formula ω.""" + return LatexFormula( + return_symbol=r"\omega", + result=f"{self:.3f}", + equation=r"\frac{A_s \cdot f_{yd}}{A_c \cdot f_{cd}}", + numeric_equation=rf"\frac{{{self.a_s} \cdot {self.fyd}}}{{{self.a_c} \cdot {self.fcd}}}", + comparison_operator_label="=", + ) + + +class SubForm5Dot13NRelativeNormalForce(Formula): + """Class representing sub-formula for the calculation of the relative normal force (n).""" + + label = "5.13N_n" + source_document = NEN_EN_1992_1_1_C2_2011 + + def __init__(self, n_ed: KN, a_c: MM2, fcd: MPA) -> None: + """[:math:`n`] Relative normal force. + + Parameters + ---------- + n_ed : KN + Design value of axial force [:math:`kN`]. + a_c : MM2 + Area of concrete cross-section [:math:`mm^2`]. + fcd : MPA + Design compressive strength of concrete [:math:`MPa`]. + """ + super().__init__() + self.n_ed = n_ed + self.a_c = a_c + self.fcd = fcd + + @staticmethod + def _evaluate(n_ed: KN, a_c: MM2, fcd: MPA) -> DIMENSIONLESS: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(n_ed=n_ed, a_c=a_c, fcd=fcd) + + return n_ed / (a_c * fcd) + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for the sub-formula n.""" + return LatexFormula( + return_symbol=r"n", + result=f"{self:.3f}", + equation=r"\frac{N_{Ed}}{A_c \cdot f_{cd}}", + numeric_equation=rf"\frac{{{self.n_ed}}}{{{self.a_c} \cdot {self.fcd}}}", + comparison_operator_label="=", + ) + + +class SubForm5Dot13NRmMomentRatio(Formula): + """Class representing sub-formula for the calculation of the moment ratio (rm).""" + + label = "rm" + source_document = NEN_EN_1992_1_1_C2_2011 + + def __init__(self, m_01: float, m_02: float) -> None: + """[:math:`rm`] Moment ratio. + + Parameters + ---------- + m_01 : float + First-order end moment [:math:`kNm`]. + m_02 : float + Second-order end moment [:math:`kNm`]. + """ + super().__init__() + self.m_01 = m_01 + self.m_02 = m_02 + + @staticmethod + def _evaluate(m_01: float, m_02: float) -> DIMENSIONLESS: + """Evaluates the formula, for more information see the __init__ method.""" + raise_if_negative(m_01=m_01, m_02=m_02) + + return m_01 / m_02 + + def latex(self) -> LatexFormula: + """Returns LatexFormula object for the sub-formula rm.""" + return LatexFormula( + return_symbol=r"rm", + result=f"{self:.3f}", + equation=r"\frac{M_{01}}{M_{02}}", + numeric_equation=rf"\frac{{{self.m_01}}}{{{self.m_02}}}", + comparison_operator_label="=", + ) diff --git a/docs/source/codes/eurocode/ec2_1992_1_1_2011/formulas.md b/docs/source/codes/eurocode/ec2_1992_1_1_2011/formulas.md index c66387cd1..bf7390082 100644 --- a/docs/source/codes/eurocode/ec2_1992_1_1_2011/formulas.md +++ b/docs/source/codes/eurocode/ec2_1992_1_1_2011/formulas.md @@ -59,7 +59,7 @@ Total of 304 formulas present. | 5.10b | :heavy_check_mark: | | Form5Dot10bRedistributionOfMomentsUpperFck | | 5.11N | :heavy_check_mark: | | Form5Dot11nShearSlendernessCorrectionFactor | | 5.12N | :heavy_check_mark: | | Form5Dot12nRatioDistancePointZeroAndMaxMoment | -| 5.13N | :x: | | | +| 5.13N | :heavy_check_mark: | | Form5Dot13NSlendernessCriterion | | 5.14 | :heavy_check_mark: | | Form5Dot14SlendernessRatio | | 5.15 | :heavy_check_mark: | | Form5Dot15EffectiveLengthBraced | | 5.16 | :heavy_check_mark: | | Form5Dot16EffectiveLengthUnbraced | diff --git a/tests/codes/eurocode/nen_en_1992_1_1_c2_2011/chapter_5_structural_analysis/test_formula_5_13n.py b/tests/codes/eurocode/nen_en_1992_1_1_c2_2011/chapter_5_structural_analysis/test_formula_5_13n.py new file mode 100644 index 000000000..d42233c28 --- /dev/null +++ b/tests/codes/eurocode/nen_en_1992_1_1_c2_2011/chapter_5_structural_analysis/test_formula_5_13n.py @@ -0,0 +1,120 @@ +"""Testing formula 5.13N of NEN-EN 1992-1-1+C2:2011.""" + +import math +import unittest + +import pytest + +from blueprints.codes.eurocode.nen_en_1992_1_1_c2_2011.chapter_5_structural_analysis.formula_5_13n import ( + Form5Dot13NSlendernessCriterion, + SubForm5Dot13NOmegaMechanicalReinforcementRatio, + SubForm5Dot13NRelativeNormalForce, + SubForm5Dot13NRmMomentRatio, +) + + +class TestForm5Dot13NSlendernessCriterion(unittest.TestCase): + """Validation for formula 5.13N from NEN-EN 1992-1-1+C2:2011.""" + + def test_evaluate(self) -> None: + """Tests the evaluation of the result.""" + phi_eff = 2.0 + omega = SubForm5Dot13NOmegaMechanicalReinforcementRatio(1000, 500, 2000, 30) + rm = SubForm5Dot13NRmMomentRatio(10, 20) + n = SubForm5Dot13NRelativeNormalForce(500, 2000, 30) + + formula = Form5Dot13NSlendernessCriterion(phi_eff, omega, rm, n) + expected = 20 * (1 / (1 + 0.2 * phi_eff)) * (1000 * 500 / (2000 * 30)) * (1.7 - (10 / 20)) / math.sqrt(500 / (2000 * 30)) + + assert formula == pytest.approx(expected=expected, rel=1e-3) + + def test_latex(self) -> None: + """Test the latex representation of the formula.""" + phi_eff = 2.0 + omega = SubForm5Dot13NOmegaMechanicalReinforcementRatio(1000, 500, 2000, 30) + rm = SubForm5Dot13NRmMomentRatio(10, 20) + n = SubForm5Dot13NRelativeNormalForce(500, 2000, 30) + + formula = Form5Dot13NSlendernessCriterion(phi_eff, omega, rm, n) + latex = formula.latex() + + assert latex.return_symbol == r"\lambda_{lim}" + assert latex.equation == r"20 \cdot A \cdot B \cdot C / \sqrt{n}" + assert latex.numeric_equation == rf"20 \cdot {formula.calculate_a(phi_eff)} \cdot {omega} \cdot " rf"{formula.calculate_c(rm)} / \sqrt{{{n}}}" + assert latex.comparison_operator_label == "=" + + +class TestSubForm5Dot13NOmegaMechanicalReinforcementRatio(unittest.TestCase): + """Validation for formula 5.13N Omega sub-formula from NEN-EN 1992-1-1+C2:2011.""" + + def test_evaluate(self) -> None: + """Tests the evaluation of the result.""" + formula = SubForm5Dot13NOmegaMechanicalReinforcementRatio(1000, 500, 2000, 30) + result = formula + assert result == pytest.approx(expected=(1000 * 500) / (2000 * 30), rel=1e-3) + + def test_latex(self) -> None: + """Test the latex representation of the formula.""" + formula = SubForm5Dot13NOmegaMechanicalReinforcementRatio(1000, 500, 2000, 30) + latex = formula.latex() + assert latex.return_symbol == r"\omega" + assert latex.equation == r"\frac{A_s \cdot f_{yd}}{A_c \cdot f_{cd}}" + assert latex.numeric_equation == r"\frac{1000 \cdot 500}{2000 \cdot 30}" + assert latex.comparison_operator_label == "=" + + +class TestSubForm5Dot13NRelativeNormalForce(unittest.TestCase): + """Validation for formula 5.13N Relative Normal Force sub-formula from NEN-EN 1992-1-1+C2:2011.""" + + def test_evaluate(self) -> None: + """Tests the evaluation of the result.""" + formula = SubForm5Dot13NRelativeNormalForce(500, 2000, 30) + result = formula + assert result == pytest.approx(expected=500 / (2000 * 30), rel=1e-3) + + def test_latex(self) -> None: + """Test the latex representation of the formula.""" + formula = SubForm5Dot13NRelativeNormalForce(500, 2000, 30) + latex = formula.latex() + assert latex.return_symbol == r"n" + assert latex.equation == r"\frac{N_{Ed}}{A_c \cdot f_{cd}}" + assert latex.numeric_equation == r"\frac{500}{2000 \cdot 30}" + assert latex.comparison_operator_label == "=" + + +class TestSubForm5Dot13NRmMomentRatio(unittest.TestCase): + """Validation for formula 5.13N Rm Moment Ratio sub-formula from NEN-EN 1992-1-1+C2:2011.""" + + def test_evaluate(self) -> None: + """Tests the evaluation of the result.""" + formula = SubForm5Dot13NRmMomentRatio(10, 20) + result = formula + assert result == pytest.approx(expected=10 / 20, rel=1e-3) + + def test_latex(self) -> None: + """Test the latex representation of the formula.""" + formula = SubForm5Dot13NRmMomentRatio(10, 20) + latex = formula.latex() + assert latex.return_symbol == r"rm" + assert latex.equation == r"\frac{M_{01}}{M_{02}}" + assert latex.numeric_equation == r"\frac{10}{20}" + assert latex.comparison_operator_label == "=" + + +class TestForm5Dot13NSlendernessCriterionStaticMethods(unittest.TestCase): + """Tests for the static methods of Form5Dot13NSlendernessCriterion.""" + + def test_calculate_a(self) -> None: + """Test the calculate_a method.""" + assert Form5Dot13NSlendernessCriterion.calculate_a(0.5) == 1 / (1 + 0.2 * 0.5) + assert Form5Dot13NSlendernessCriterion.calculate_a(None) == 0.7 + + def test_calculate_b(self) -> None: + """Test the calculate_b method.""" + assert Form5Dot13NSlendernessCriterion.calculate_b(0.3) == 0.3 + assert Form5Dot13NSlendernessCriterion.calculate_b(None) == 1.1 + + def test_calculate_c(self) -> None: + """Test the calculate_c method.""" + assert Form5Dot13NSlendernessCriterion.calculate_c(0.2) == 1.7 - 0.2 + assert Form5Dot13NSlendernessCriterion.calculate_c(None) == 0.7