diff --git a/docs/api/codes/ec2_2023/index.md b/docs/api/codes/ec2_2023/index.md index 78e1036b..b19c29d5 100644 --- a/docs/api/codes/ec2_2023/index.md +++ b/docs/api/codes/ec2_2023/index.md @@ -8,4 +8,5 @@ Material properties for reinforcement steel Crack width calculation Deflection calculation Creep and shrinkage +Strut-and-tie models and stress fields ::: \ No newline at end of file diff --git a/docs/api/codes/ec2_2023/strut_and_tie.md b/docs/api/codes/ec2_2023/strut_and_tie.md new file mode 100644 index 00000000..79e97884 --- /dev/null +++ b/docs/api/codes/ec2_2023/strut_and_tie.md @@ -0,0 +1,32 @@ +# Design with strut-and-tie models and stress fields + +The following functions are related to design with strut-and-tie models and stress fields. + +## Resistance calculation + +```{eval-rst} +.. autofunction:: structuralcodes.codes.ec2_2023.FRd_tie +``` + +```{eval-rst} +.. autofunction:: structuralcodes.codes.ec2_2023.nu_refined +``` + +```{eval-rst} +.. autofunction:: structuralcodes.codes.ec2_2023.nu_strut +``` + +```{eval-rst} +.. autofunction:: structuralcodes.codes.ec2_2023.nu_strut_no_crack +``` + +## Response calculation + +```{eval-rst} +.. autofunction:: structuralcodes.codes.ec2_2023.sigma_cd_strut +``` + +## Reinforcement calculation +```{eval-rst} +.. autofunction:: structuralcodes.codes.ec2_2023.Ftd_conc +``` diff --git a/structuralcodes/codes/ec2_2023/__init__.py b/structuralcodes/codes/ec2_2023/__init__.py index c8662148..8bedd238 100644 --- a/structuralcodes/codes/ec2_2023/__init__.py +++ b/structuralcodes/codes/ec2_2023/__init__.py @@ -50,8 +50,22 @@ wk_cal, wk_cal2, ) +from ._section_8_5_strut_and_ties import ( + FRd_tie, + Ftd_conc, + nu_refined, + nu_strut, + nu_strut_no_crack, + sigma_cd_strut, +) __all__ = [ + 'nu_strut', + 'Ftd_conc', + 'FRd_tie', + 'nu_refined', + 'nu_strut_no_crack', + 'sigma_cd_strut', 'A_phi_correction_exp', 'alpha_c_th', 'alpha_s_th', diff --git a/structuralcodes/codes/ec2_2023/_section_8_5_strut_and_ties.py b/structuralcodes/codes/ec2_2023/_section_8_5_strut_and_ties.py new file mode 100644 index 00000000..59dae4dd --- /dev/null +++ b/structuralcodes/codes/ec2_2023/_section_8_5_strut_and_ties.py @@ -0,0 +1,184 @@ +"""Functions from Section 8.5 of EN 1992-1-1:2023.""" + +import math + + +def sigma_cd_strut(F_cd: float, b_c: float, t: float) -> float: + """Calculate the compressive stress in a concrete strut or compression + field. + + EN1992-1-1:2023 Eq. (8.113). + + Args: + F_cd (float): Compressive force of the strut in kN. + b_c (float): Width of the strut at the considered location in mm. + t (float): Thickness of the strut in mm. + + Returns: + float: Compressive stress sigma_cd in MPa. + + Raises: + ValueError: If b_c or t are not within valid ranges. + """ + if b_c <= 0: + raise ValueError(f'b_c must be positive. Got {b_c}') + if t <= 0: + raise ValueError(f't must be positive. Got {t}') + + # Convert F_cd from kN to N and calculate σ_cd in MPa + return abs(F_cd) * 1000 / (b_c * t) + + +def nu_strut(theta_cs: float) -> float: + """Determine the strength reduction factor nu based on the smallest angle + theta_cs. + + EN1992-1-1:2023 Eqs. (8.119) + + Args: + theta_cs (float): Angle between the strut and the tie in degrees. Must + be between 0 and 90. + transverse_cracking (bool): Indicates if the region has transverse + cracking. Defaults to True. + + Returns: + float: Strength reduction factor nu. + + Raises: + ValueError: If theta_cs is not within the valid range. + """ + if not 0 <= theta_cs <= 90: + raise ValueError( + f'theta_cs must be between 0° and 90°. Got {theta_cs}' + ) + + theta_rad = math.radians(theta_cs) + epsilon = 1e-10 # To avoid num error in edge case when theta=0 + cot_theta = 1 / math.tan(theta_rad + epsilon) + return 1 / (1.11 + 0.22 * (cot_theta**2)) + + +def nu_strut_no_crack() -> float: + """Determine the strength reduction factor nu in areas without transverse + cracking. + + EN1992-1-1:2023 Eqs. (8.120) + + Returns: + float: Strength reduction factor nu. + """ + return 1.0 + + +def nu_refined(eps_1: float) -> float: + """Calculate a more refined value for the strength reduction factor nu for + compression fields in cracked zones based on principal tensile strain. + + EN1992-1-1:2023 Eq. (8.121) + + Args: + epsilon_1 (float): Maximum principal tensile strain (dimensionless). + + Returns: + float: Refined strength reduction factor nu, capped at a maximum value + of 1.0. + + Raises: + ValueError: If epsilon_1 is negative. + """ + if eps_1 < 0: + raise ValueError(f'epsilon_1 must be non-negative. Got {eps_1}') + + nu = 1 / (1.0 + 110 * eps_1) + return min(nu, 1.0) + + +def FRd_tie( + As: float, + fyd: float, + Ap: float = 0.0, + fpd: float = 0.0, + sigma_pd: float = 0.0, +) -> float: + """Calculate the resistance of a tie. + + EN1992-1-1:2023 Eq. (8.122) + + Args: + As (float): Cross-sectional area of non-prestressed reinforcement in + mm2. + fyd (float): Design yield strength of non-prestressed reinforcement in + MPa. + Ap (float, optional): Cross-sectional area of prestressed reinforcement + in mm2. Default is 0.0. + fpd (float, optional): Design strength of prestressed reinforcement in + MPa. Default is 0.0. + sigma_pd (float, optional): Stress in the prestressed reinforcement + considered as an external action in MPa. Default is 0.0. + + Returns: + float: Resistance of the tie in kN. + + Raises: + ValueError: If any of the input values are negative. + """ + # Input validation + if As < 0: + raise ValueError(f'As must not be negative. Got {As}') + if fyd < 0: + raise ValueError(f'fyd must not be negative. Got {fyd}') + if Ap < 0: + raise ValueError(f'Ap must not be negative. Got {Ap}') + if fpd < 0: + raise ValueError(f'fpd must not be negative. Got {fpd}') + if sigma_pd < 0: + raise ValueError(f'sigma_pd must not be negative. Got {sigma_pd}') + + # Calculate tie resistance + FRd = As * fyd + Ap * (fpd - sigma_pd) + return FRd / 1000 + + +def Ftd_conc( + Fd: float, a: float, b: float, H: float, near_edge: bool = False +) -> float: + """Calculate the transverse reinforcement for concentrated forces + spreading into a member using the strut-and-ties model. + + EN1992-1-1:2023 Eq. (8.123), Eq. (8.124), Eq. (8.125) + + Args: + Fd (float): Design value of concentrated force in kN. + a (float): Width of the concentrated force application area in mm. + b (float): Width of the member in which force spreads in mm. + H (float): Height of the member section in mm. + near_edge (bool, optional): Indicates if the force is acting near an + edge. Default is False. + + Returns: + float: The transverse reinforcement force F_td in kN. + + Raises: + ValueError: If any of the input values are negative. + """ + # Input validation + if Fd < 0: + raise ValueError(f'Fd must not be negative. Got {Fd}') + if a < 0: + raise ValueError(f'a must not be negative. Got {a}') + if b < 0: + raise ValueError(f'b must not be negative. Got {b}') + if H < 0: + raise ValueError(f'H must not be negative. Got {H}') + + # Calculate tan(theta_cf) based on position and geometry + tan_theta_cf = (1 - a / b) / 2 if b <= a + H / 2 else 0.5 + + # For forces near an edge, tan_theta_cf is assumed to be at least 1/4 + if near_edge: + tan_theta_cf = max(tan_theta_cf, 1 / 4) + F_td = Fd * tan_theta_cf + else: + F_td = Fd / 2 * tan_theta_cf + + return F_td diff --git a/tests/test_ec2_2023/test_ec2_2023_section_8_5_struct_and_ties.py b/tests/test_ec2_2023/test_ec2_2023_section_8_5_struct_and_ties.py new file mode 100644 index 00000000..047711e7 --- /dev/null +++ b/tests/test_ec2_2023/test_ec2_2023_section_8_5_struct_and_ties.py @@ -0,0 +1,128 @@ +"""Test for functions from Section 8.5 of EN 1992-1-1:2023.""" + +import pytest + +from structuralcodes.codes.ec2_2023 import _section_8_5_strut_and_ties + + +@pytest.mark.parametrize( + 'F_cd, b_c, t, expected', + [ + (100, 200, 20, 25.0), + (150, 350, 40, 10.714), + (-150, 350, 40, 10.714), + ], +) +def test_calculate_sigma_cd(F_cd, b_c, t, expected): + """Test the calculation of compressive stress σ_cd.""" + assert _section_8_5_strut_and_ties.sigma_cd_strut( + F_cd, b_c, t + ) == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'F_cd, b_c, t', + [ + (100, -200, 20), + (200, 350, -40), + ], +) +def test_calculate_sigma_cd_exceptions(F_cd, b_c, t): + """Test exceptions in compressive stress σ_cd calculation.""" + with pytest.raises(ValueError): + _section_8_5_strut_and_ties.sigma_cd_strut(F_cd, b_c, t) + + +@pytest.mark.parametrize( + 'input_value, expected', + [ + (0, 0), + (25, 0.471), + (35, 0.642), + (50, 0.791), + (75, 0.888), + ], +) +def test_calculate_nu(input_value, expected): + """Test the calculation of strength reduction factor ν.""" + assert _section_8_5_strut_and_ties.nu_strut(input_value) == pytest.approx( + expected, rel=1e-2 + ) + + +@pytest.mark.parametrize( + 'input_value', + [(-10), (180)], +) +def test_calculate_nu_exceptions(input_value): + """Test exceptions in strength reduction factor ν calculation.""" + with pytest.raises(ValueError): + _section_8_5_strut_and_ties.nu_strut(input_value) + + +@pytest.mark.parametrize( + 'input_value, expected', + [ + (0.0, 1.0), + (0.001, 0.9009), + (0.01, 0.4762), + (0.1, 0.0833), + ], +) +def test_calculate_nu_refined(input_value, expected): + """Test the calculation of refined strength reduction factor ν.""" + assert _section_8_5_strut_and_ties.nu_refined( + input_value + ) == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'input_value', + [ + (-0.01), + ], +) +def test_calculate_nu_refined_exceptions(input_value): + """Test exceptions in refined strength reduction factor ν calculation.""" + with pytest.raises(ValueError): + _section_8_5_strut_and_ties.nu_refined(input_value) + + +@pytest.mark.parametrize( + 'As, fyd, Ap, fpd, sigma_pd, expected', + [ + (1000, 500, 500, 1600, 0, 1300), + (1000, 500, 500, 1600, 100, 1250), + (1500, 450, 300, 1450, 0, 1110), + (1200, 550, 400, 1500, 50, 1240), + (0, 500, 500, 1600, 0, 800), + (1000, 0, 500, 1600, 0, 800), + (1000, 500, 0, 1600, 0, 500), + (1000, 500, 500, 0, 0, 500), + ], +) +def test_calculate_tie_resistance(As, fyd, Ap, fpd, sigma_pd, expected): + """Test the calculate_tie_resistance function with various inputs.""" + result = _section_8_5_strut_and_ties.FRd_tie(As, fyd, Ap, fpd, sigma_pd) + assert pytest.approx(result, 0.01) == expected + + +@pytest.mark.parametrize( + 'Fd, a, b, H, near_edge, expected_F_td', + [ + (1000, 200, 400, 600, False, 125), + (1000, 200, 450, 600, False, 138.888), + (1000, 200, 300, 600, True, 250), + (800, 150, 300, 500, False, 100), + (800, 150, 500, 300, True, 400), + (800, 100, 200, 400, False, 100), + ], +) +def test_calculate_transverse_reinforcement( + Fd, a, b, H, near_edge, expected_F_td +): + """Test the calculate_transverse_reinforcement + function with various inputs. + """ + F_td = _section_8_5_strut_and_ties.Ftd_conc(Fd, a, b, H, near_edge) + assert pytest.approx(F_td, 0.01) == expected_F_td