diff --git a/structuralcodes/codes/ec2_2023/__init__.py b/structuralcodes/codes/ec2_2023/__init__.py index c8662148..e05eef72 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_3_torsion import ( + VEd_i, + tau_t_i, + tau_t_rd, + tau_t_rd_max, + tau_t_rd_sl, + tau_t_rd_sw, +) __all__ = [ + 'tau_t_rd_max', + 'tau_t_rd', + 'tau_t_rd_sl', + 'tau_t_rd_sw', + 'tau_t_i', + 'VEd_i', 'A_phi_correction_exp', 'alpha_c_th', 'alpha_s_th', diff --git a/structuralcodes/codes/ec2_2023/_section_8_3_torsion.py b/structuralcodes/codes/ec2_2023/_section_8_3_torsion.py new file mode 100644 index 00000000..26967f39 --- /dev/null +++ b/structuralcodes/codes/ec2_2023/_section_8_3_torsion.py @@ -0,0 +1,214 @@ +"""Functions from Section 8.3 of EN 1992-1-1:2023.""" + +from typing import List + + +def tau_t_i(TEd: float, Ak: float, teff_i: float) -> float: + """Calculate the torsional shear stress in a wall element. + + EN1992-1-1:2023 Eq. (8.79). + + Args: + TEd (float): Torsional moment applied to the section in kNm. + Ak (float): Area enclosed by the center-lines of the connecting walls, + including inner hollow areas in mm2. + teff_i (float): Effective wall thickness. It may be taken as A/u, but + should not be taken as less than twice the distance between the + outer concrete surface and the center of the longitudinal + reinforcement in mm. + + Returns: + float: Torsional shear stress in i-wall in MPa. + + Raises: + ValueError: If any of the input values Ak or teff_i are not positive. + """ + if Ak <= 0: + raise ValueError(f'Ak must be positive. Got {Ak}') + if teff_i <= 0: + raise ValueError(f'teff_i must be positive. Got {teff_i}') + + return TEd * 1e6 / (2 * Ak * teff_i) + + +def VEd_i(tau_t_i: float, teff_i: float, zi: float) -> float: + """Calculate the shear force in a wall element due to torsion. + + EN1992-1-1:2023 Eq. (8.80). + + Args: + tau_t_i (float): Torsional shear stress in i-wall in MPa. + teff_i (float): Effective wall thickness in mm. + zi (float): Lever arm of i-wall element in mm. + + Returns: + float: Shear force in wall element i due to torsion in kN. + + Raises: + ValueError: If any of the input values are not positive. + """ + if teff_i <= 0: + raise ValueError(f'teff_i must be positive. Got {teff_i}') + if zi <= 0: + raise ValueError(f'zi must be positive. Got {zi}') + + return tau_t_i * teff_i * zi / 1000 + + +def tau_t_rd_sw( + Asw: float, + fywd: float, + teff: float, + s: float, + cot_theta: float, + cot_theta_min: float, +) -> float: + """Calculate the torsional capacity governed by yielding of the shear + reinforcement. + + EN1992-1-1:2023 Eq. (8.82), (8.85). + + Args: + Asw (float): Cross-sectional area of the shear reinforcement in mm2. + fywd (float): Design yield stress of the shear reinforcement in MPa. + teff (float): Effective wall thickness in mm. + s (float): Spacing between the shear reinforcement in mm. + theta (float): Cotangent of the angle of compression field with respect + to the longitudinal axis. + cot_theta_min (float): Limit value for the cotangent. + + Returns: + float: Torsional capacity in MPa. + + Raises: + ValueError: If any of the input values are non-positive. + """ + if Asw < 0: + raise ValueError(f'Asw should not be negative. Got {Asw}') + if fywd < 0: + raise ValueError(f'fywd should not be negative. Got {fywd}') + if teff < 0: + raise ValueError(f'teff should not be negative. Got {teff}') + cot_theta = max(1 / cot_theta_min, min(cot_theta, cot_theta_min)) + return cot_theta * Asw / (teff * s) * fywd + + +def tau_t_rd_sl( + Asl: List[float], + fyd: List[float], + teff: float, + uk: float, + cot_theta: float, + cot_theta_min: float, +) -> float: + """Calculate the torsional capacity governed by yielding of the + longitudinal reinforcement. + + EN1992-1-1:2023 Eq. (8.83), (8.85). + + Args: + Asl (float): List of cross-sectional areas of the longitudinal + reinforcement in mm2. + fyd (float): List of design yield stresses of the longitudinal + reinforcement in MPa. + teff (float): Effective wall thickness in mm. + uk (float): Perimeter of the area in mm. + cot_theta (float): Cotangent of the angle of compression field with + respect to the longitudinal axis. + cot_theta_min (float): Limit value for the cotangent. + + Returns: + float: Torsional capacity in MPa. + + Raises: + ValueError: If any of the input values are non-positive. + """ + if len(Asl) != len(fyd): + raise ValueError('Length of Asl and fyd should be the same.') + for a in Asl: + if a < 0: + raise ValueError(f'Asl should not be negative. Got {a}') + for f in fyd: + if f < 0: + raise ValueError(f'fyd should not be negative. Got {f}') + if uk < 0: + raise ValueError(f'uk should not be negative. Got {uk}') + if teff < 0: + raise ValueError(f'teff should not be negative. Got {teff}') + + sum_r = 0 + n = len(Asl) + for i in range(n): + sum_r += Asl[i] * fyd[i] + + cot_theta = max(1 / cot_theta_min, min(cot_theta, cot_theta_min)) + return sum_r / (teff * uk * cot_theta) + + +def tau_t_rd_max( + nu: float, fcd: float, cot_theta: float, cot_theta_min: float +) -> float: + """Calculate the torsional capacity governed by crushing of the compression + field in concrete. + + EN1992-1-1:2023 Eq. (8.84), (8.85). + + Args: + nu (float): Coefficient as determined by the formulae in Annex G. + fcd (float): Design value of concrete compressive strength in MPa. + cot_theta (float): Cotangent Angle of compression field with respect to + the longitudinal axis. + cot_theta_min (float): Limit value for the cotangent. + + Returns: + float: Torsional capacity in MPa. + + Raises: + ValueError: If any of the input values are non-positive. + """ + if nu < 0: + raise ValueError(f'nu should not be negative. Got {nu}') + if fcd < 0: + raise ValueError(f'fcd should not be negative. Got {fcd}') + + cot_theta = max(1 / cot_theta_min, min(cot_theta, cot_theta_min)) + tan_theta = 1 / cot_theta + return nu * fcd / (cot_theta + tan_theta) + + +def tau_t_rd( + tau_t_rd_sw: float, tau_t_rd_sl: float, tau_t_rd_max: float +) -> float: + """Calculate the design torsional capacity for a single cell or thin-walled + section. + + EN1992-1-1:2023 Eq. (8.81). + + Args: + tau_t_rd_sw (float): Torsional capacity governed by yielding of the + shear reinforcement in MPa. + tau_t_rd_sl (float): Torsional capacity governed by yielding of the + longitudinal reinforcement in MPa. + tau_t_rd_max (float): Torsional capacity governed by crushing of the + compression field in concrete in MPa. + + Returns: + float: Design torsional capacity in MPa. + + Raises: + ValueError: If any of the input values are non-positive. + """ + if tau_t_rd_sw < 0: + raise ValueError( + f'tau_t_rd_sw should not be negative. Got {tau_t_rd_sw}' + ) + if tau_t_rd_sl < 0: + raise ValueError( + f'tau_t_rd_sl should not be negative. Got {tau_t_rd_sl}' + ) + if tau_t_rd_max < 0: + raise ValueError( + f'tau_t_rd_sl should not be negative. Got {tau_t_rd_max}' + ) + + return min(tau_t_rd_sw, tau_t_rd_sl, tau_t_rd_max) diff --git a/tests/test_ec2_2023/test_ec2_2023_section_8_3_torsion.py b/tests/test_ec2_2023/test_ec2_2023_section_8_3_torsion.py new file mode 100644 index 00000000..1b8a1349 --- /dev/null +++ b/tests/test_ec2_2023/test_ec2_2023_section_8_3_torsion.py @@ -0,0 +1,180 @@ +"""Tests for functions from Section 5 of EN 1992-1-1:2023.""" + +import pytest + +from structuralcodes.codes.ec2_2023 import _section_8_3_torsion + + +@pytest.mark.parametrize( + 'TEd, Ak, teff_i, expected', + [ + (100, 50000, 200, 5), + (200, 100000, 400, 2.5), + (50, 25000, 100, 10), + ], +) +def test_tao_t_i(TEd, Ak, teff_i, expected): + """Test calculate_torsional_shear_stress with parameterized inputs.""" + result = _section_8_3_torsion.tau_t_i(TEd, Ak, teff_i) + assert result == pytest.approx(expected, rel=1e-3) + + +@pytest.mark.parametrize( + 'tau_t_i, teff_i, zi, expected', + [ + (10, 200, 300, 600), + (5, 100, 150, 75), + (8, 250, 200, 400), + ], +) +def test_VEd_i(tau_t_i, teff_i, zi, expected): + """Test calculate_shear_force with parameterized inputs.""" + result = _section_8_3_torsion.VEd_i(tau_t_i, teff_i, zi) + assert result == pytest.approx(expected, rel=1e-3) + + +# Test for tau_t_rd_sw +@pytest.mark.parametrize( + 'Asw, fywd, teff, s, cot_theta, cot_theta_min, expected', + [ + (100, 500, 50, 200, 1.5, 1.0, 5.0), + (150, 400, 60, 150, 2.0, 1.5, 10.0), + (200, 450, 80, 250, 2.5, 2.0, 9.0), + (250, 600, 100, 300, 3.0, 2.5, 12.5), + (300, 550, 90, 240, 1.0, 0.8, 9.5486), + ], +) +def test_tau_t_rd_sw(Asw, fywd, teff, s, cot_theta, cot_theta_min, expected): + """Test test_tau_t_rd_sw.""" + result = _section_8_3_torsion.tau_t_rd_sw( + Asw, fywd, teff, s, cot_theta, cot_theta_min + ) + assert pytest.approx(result, rel=1e-2) == expected + + +@pytest.mark.parametrize( + 'Asw, fywd, teff, s, cot_theta, cot_theta_min, expected_exception', + [ + (-100, 500, 50, 200, 1.5, 1.0, ValueError), + (100, -500, 50, 200, 1.5, 1.0, ValueError), + (100, 500, -50, 200, 1.5, 1.0, ValueError), + ], +) +def test_tau_t_rd_sw_exceptions( + Asw, fywd, teff, s, cot_theta, cot_theta_min, expected_exception +): + """Test _tau_t_rd_sw exceptions.""" + with pytest.raises(expected_exception): + _section_8_3_torsion.tau_t_rd_sw( + Asw, fywd, teff, s, cot_theta, cot_theta_min + ) + + +# Test for tau_t_rd_sl +@pytest.mark.parametrize( + 'Asl, fyd, teff, uk, cot_theta, cot_theta_min, expected', + [ + ([100, 200], [500, 600], 50, 300, 1.5, 1.0, 11.334), + ([150, 250], [400, 550], 60, 400, 2.0, 1.5, 5.486), + ([200, 300], [450, 500], 80, 500, 2.5, 2.0, 3.0), + ([250, 350], [600, 650], 100, 600, 3.0, 2.5, 2.5166), + ([300, 400], [550, 700], 90, 700, 1.0, 0.8, 5.650), + ], +) +def test_tau_t_rd_sl(Asl, fyd, teff, uk, cot_theta, cot_theta_min, expected): + """Test tau_t_rd_sl.""" + result = _section_8_3_torsion.tau_t_rd_sl( + Asl, fyd, teff, uk, cot_theta, cot_theta_min + ) + assert pytest.approx(result, rel=1e-2) == expected + + +@pytest.mark.parametrize( + 'Asl, fyd, teff, uk, cot_theta, cot_theta_min, expected_exception', + [ + ([100], [500, 600], 50, 300, 1.5, 1.0, ValueError), + ([100, -200], [500, 600], 50, 300, 1.5, 1.0, ValueError), + ([100, 200], [500, -600], 50, 300, 1.5, 1.0, ValueError), + ([100, 200], [500, 600], -50, 300, 1.5, 1.0, ValueError), + ([100, 200], [500, 600], 50, -300, 1.5, 1.0, ValueError), + ], +) +def test_tau_t_rd_sl_exceptions( + Asl, fyd, teff, uk, cot_theta, cot_theta_min, expected_exception +): + """Test tau_t_rd_sl excptions.""" + with pytest.raises(expected_exception): + _section_8_3_torsion.tau_t_rd_sl( + Asl, fyd, teff, uk, cot_theta, cot_theta_min + ) + + +# Test for tau_t_rd_max +@pytest.mark.parametrize( + 'nu, fcd, cot_theta, cot_theta_min, expected', + [ + (0.6, 30, 1.5, 1.0, 9.0), + (0.7, 35, 2.0, 1.5, 11.307), + (0.8, 40, 2.5, 2.0, 12.8), + (0.9, 45, 3.0, 2.5, 13.965), + (0.5, 25, 1.0, 0.8, 6.0975), + ], +) +def test_tau_t_rd_max(nu, fcd, cot_theta, cot_theta_min, expected): + """Test tau_t_rd_max.""" + result = _section_8_3_torsion.tau_t_rd_max( + nu, fcd, cot_theta, cot_theta_min + ) + assert pytest.approx(result, rel=1e-2) == expected + + +@pytest.mark.parametrize( + 'nu, fcd, cot_theta, cot_theta_min, expected_exception', + [ + (-0.6, 30, 1.5, 1.0, ValueError), + (0.6, -30, 1.5, 1.0, ValueError), + ], +) +def test_tau_t_rd_max_exceptions( + nu, fcd, cot_theta, cot_theta_min, expected_exception +): + """Test tau_t_rd_max exception.""" + with pytest.raises(expected_exception): + _section_8_3_torsion.tau_t_rd_max(nu, fcd, cot_theta, cot_theta_min) + + +@pytest.mark.parametrize( + 'tau_t_rd_sw_val, tau_t_rd_sl_val, tau_t_rd_max_val, expected', + [ + (10.0, 15.0, 20.0, 10.0), + (25.0, 20.0, 15.0, 15.0), + (30.0, 35.0, 40.0, 30.0), + (45.0, 50.0, 55.0, 45.0), + ], +) +def test_tau_t_rd( + tau_t_rd_sw_val, tau_t_rd_sl_val, tau_t_rd_max_val, expected +): + """Test for tau_t_rd.""" + result = _section_8_3_torsion.tau_t_rd( + tau_t_rd_sw_val, tau_t_rd_sl_val, tau_t_rd_max_val + ) + assert result == expected + + +@pytest.mark.parametrize( + 'tau_t_rd_sw_val, tau_t_rd_sl_val, tau_t_rd_max_val, expected_exception', + [ + (-10.0, 15.0, 20.0, ValueError), + (10.0, -15.0, 20.0, ValueError), + (10.0, 15.0, -20.0, ValueError), + ], +) +def test_tau_t_rd_exceptions( + tau_t_rd_sw_val, tau_t_rd_sl_val, tau_t_rd_max_val, expected_exception +): + """Test tau_t_rd exceptions.""" + with pytest.raises(expected_exception): + _section_8_3_torsion.tau_t_rd( + tau_t_rd_sw_val, tau_t_rd_sl_val, tau_t_rd_max_val + )