diff --git a/structuralcodes/codes/ec2_2023/__init__.py b/structuralcodes/codes/ec2_2023/__init__.py index 632d7ec5..63cb2f21 100644 --- a/structuralcodes/codes/ec2_2023/__init__.py +++ b/structuralcodes/codes/ec2_2023/__init__.py @@ -48,8 +48,108 @@ wk_cal, wk_cal2, ) +from ._section11_detailing_steel import ( + Ac_headed_laps, + Ac_u_bars, + Ast_min_headed_bars, + Ast_min_headed_single_bars, + Ast_min_u_bar_loops, + Rmin_unconf_concrete, + TRd_c_headed, + TRd_c_u_bar_loops, + a_d, + additional_transverse_confinement_reinf, + bh1_headed_laps, + calculate_bond_condition, + cd, + cd_conf, + check_anchorage_headed_bars_in_tension, + cmin_b, + cs_bars, + cs_laps, + csx_duct, + csy_duct, + cu, + k_bend, + k_trans, + kh_A, + kst_headed_bars, + kst_u_bar_loops, + lb_anchor_compression, + lb_anchor_tension, + lb_anchor_ubar_loops_tension, + lb_anchor_weld_trans, + lbd, + lbd_bar_tension, + lbd_pi, + lbd_simple, + lsd, + max_pRd, + min_clear_distance_between_bars, + min_clear_distance_to_poured_concrete, + min_clear_distante_laps, + min_clear_distante_laps_couplers, + min_mandrel_phi, + min_spacing_bundled_tendons, + phi_b_anchor, + phi_h, + rho_conf, + sigma_sd_bend_concrete, + sigma_sd_prime, + verify_deviation_forces, + verify_interaction_bond, +) __all__ = [ + 'Ac_headed_laps', + 'Ac_u_bars', + 'Ast_min_headed_bars', + 'Ast_min_headed_single_bars', + 'Ast_min_u_bar_loops', + 'Rmin_unconf_concrete', + 'TRd_c_headed', + 'TRd_c_u_bar_loops', + 'a_d', + 'additional_transverse_confinement_reinf', + 'bh1_headed_laps', + 'calculate_bond_condition', + 'cd', + 'cd_conf', + 'check_anchorage_headed_bars_in_tension', + 'cmin_b', + 'cs_bars', + 'cs_laps', + 'csx_duct', + 'csy_duct', + 'cu', + 'k_bend', + 'k_trans', + 'kh_A', + 'kst_headed_bars', + 'kst_u_bar_loops', + 'lb_anchor_compression', + 'lb_anchor_tension', + 'lb_anchor_ubar_loops_tension', + 'lb_anchor_weld_trans', + 'lbd', + 'lbd_bar_tension', + 'lbd_pi', + 'lbd_simple', + 'lsd', + 'max_pRd', + 'min_clear_distance_between_bars', + 'min_clear_distance_to_poured_concrete', + 'min_clear_distante_laps', + 'min_clear_distante_laps_couplers', + 'min_mandrel_phi', + 'min_spacing_bundled_tendons', + 'phi_b_anchor', + 'phi_h', + 'rho_conf', + 'sigma_sd_bend_concrete', + 'sigma_sd_prime', + 'verify_deviation_forces', + 'verify_interaction_bond', 'A_phi_correction_exp', 'alpha_c_th', 'alpha_s_th', diff --git a/structuralcodes/codes/ec2_2023/_section11_detailing_steel.py b/structuralcodes/codes/ec2_2023/_section11_detailing_steel.py new file mode 100644 index 00000000..e03409db --- /dev/null +++ b/structuralcodes/codes/ec2_2023/_section11_detailing_steel.py @@ -0,0 +1,1732 @@ +"""Functions from Section 11 of EN 1992-1-1:2023.""" + +import math +from typing import Literal, Union + +from scipy.interpolate import RegularGridInterpolator + + +def min_clear_distance_between_bars(phi: float, D_upper: float) -> float: + """Calculate the clear distance between individual parallel bars. + + EN1992-1-1:2023 Eq. (11.2-2) + + Args: + phi (float): Diameter of the bar in mm. + D_upper (float): Distance to the upper bar layer in mm. + + Returns: + float: The minimum clear distance in mm. + + Raises: + ValueError: If any input is negative. + """ + if phi < 0: + raise ValueError(f'phi must not be negative. Got {phi}') + if D_upper < 0: + raise ValueError(f'D_upper must not be negative. Got {D_upper}') + + return max(phi, D_upper + 5, 20) + + +def min_clear_distance_to_poured_concrete( + surface_roughness: bool, min_bond_distance: float +) -> float: + """Calculate the clear distance between the face + of already poured concrete and a parallel bar. + + EN1992-1-1:2023 Eq. (11.2-4) + + Args: + surface_roughness (bool): Whether the surface is at least rough. + min_bond_distance (float): Minimum distance required + for bond according to 6.5.2.3(1) in mm. + + Returns: + float: The minimum clear distance in mm. + + Raises: + ValueError: If min_bond_distance is negative. + """ + if min_bond_distance < 0: + raise ValueError( + f'min_bond_distance must not be negative. Got {min_bond_distance}' + ) + + if surface_roughness: + return 5.0 + return min_bond_distance + + +def min_mandrel_phi(phi: float) -> float: + """Calculate the minimum mandrel diameter for bending bars. + + EN1992-1-1:2023 Eq. (11.3-2) + + Args: + phi (float): Diameter of the bar in mm. + + Returns: + float: The minimum mandrel diameter in mm. + + Raises: + ValueError: If phi is negative. + """ + if phi < 0: + raise ValueError(f'phi must not be negative. Got {phi}') + + if phi <= 16: + return 4 * phi + return 7 * phi + + +def k_bend(alpha_bend: float) -> float: + """Calculate the bend parameter k_bend based on the bend angle alpha_bend. + + EN1992-1-1:2023 Eq. (11.3-4) + + Args: + alpha_bend (float): Bend angle in degrees. + + Returns: + float: The bend parameter k_bend. + + Raises: + ValueError: If alpha_bend is not greater than zero. + """ + if alpha_bend <= 0: + raise ValueError( + f'alpha_bend must be greater than zero. Got {alpha_bend}' + ) + + return 32 * (45 / alpha_bend) + + +def sigma_sd_bend_concrete( + fck: float, + gamma_c: float, + d_g: float, + phi: float, + phi_mand: float, + c_d: float, + k_bend: float, +) -> float: + """Calculate the steel stress limit to avoid concrete + failure inside the bend. + + EN1992-1-1:2023 Eq. (11.1) + + Args: + fck (float): Characteristic compressive strength of concrete in MPa. + gamma_c (float): Partial safety factor for concrete. + d_g (float): Maximum aggregate size in mm. + phi (float): Diameter of the bar in mm. + phi_mand (float): Mandrel diameter in mm. + c_d (float): Minimum clear distance from the + edge or between bars in mm. + k_bend (float): Bend parameter considering bend angle alpha_bend. + + Returns: + float: The maximum permissible steel stress σ_sd in MPa. + + Raises: + ValueError: If any input is invalid. + """ + if fck < 0: + raise ValueError(f'fck must not be negative. Got {fck}') + if gamma_c <= 0: + raise ValueError(f'gamma_c must be greater than zero. Got {gamma_c}') + if d_g < 0: + raise ValueError(f'd_g must not be negative. Got {d_g}') + if phi < 0: + raise ValueError(f'phi must not be negative. Got {phi}') + if phi_mand < 0: + raise ValueError(f'phi_mand must not be negative. Got {phi_mand}') + if c_d < 0: + raise ValueError(f'c_d must not be negative. Got {c_d}') + if k_bend < 0: + raise ValueError(f'k_bend must not be negative. Got {k_bend}') + + a = 0.65 * fck / gamma_c * phi_mand / phi + b = math.sqrt(fck) / gamma_c * ((d_g / phi) ** (1 / 3)) + c = c_d / phi + 1 / 2 + d = k_bend + 0.7 * phi_mand / phi + + return a + b * c * d + + +def k_trans( + n_trans: int, + phi: float, + phi_mand: float, + phi_trans: float, + alpha_bend: float, +) -> float: + """Calculate the increase factor for steel stress + limit when transverse bars are within the bend. + + EN1992-1-1:2023 Eq. (11.2) + + Args: + n_trans (int): Number of transverse bars within the bend. + phi (float): Diameter of the main bar in mm. + phi_mand (float): Mandrel diameter in mm. + phi_trans (float): Diameter of the transverse bars in mm. + alpha_bend (float): Bend angle in degrees. Must be greater than zero. + + Returns: + float: The increase factor for steel stress limit. + + Raises: + ValueError: If any input is invalid. + + Reference: + EN1992-1-1:2023 Eq. (11.2) + """ + if n_trans < 0: + raise ValueError(f'n_trans must not be negative. Got {n_trans}') + if phi < 0: + raise ValueError(f'phi must not be negative. Got {phi}') + if phi_mand < 0: + raise ValueError(f'phi_mand must not be negative. Got {phi_mand}') + if phi_trans < 0: + raise ValueError(f'phi_trans must be non-negative. Got {phi_trans}') + if alpha_bend <= 0: + raise ValueError( + f'alpha_bend must be greater than zero. Got {alpha_bend}' + ) + + phi_trans = min(phi_trans, 1.35 * phi) + return 1 + 4 * n_trans * (phi / phi_mand) * pow(phi_trans / phi, 2) * ( + 45 / alpha_bend + ) + + +def lbd_simple( + fck: float, + phi: float, + bond_condition: Literal['good', 'poor'], + poor_bond_multiplier: float = 1.2, +) -> float: + """Calculate the design anchorage length for + straight bars using linear interpolation for fck. + + EN1992-1-1:2023 Table. (11.1) + + Args: + fck (float): Characteristic compressive strength of concrete in MPa. + phi (float): Diameter of the reinforcing bar in mm. + bond_condition (str): Bond condition ('good' or 'poor'). + poor_bond_multiplier (float, optional): Multiplier for + poor bond conditions. Defaults to 1.2. + + Returns: + float: Design anchorage length in mm. + + Raises: + ValueError: If `phi` is not supported or if `fck` is outside + the supported range for the given `phi`. + """ + if phi <= 0: + raise ValueError(f'Bar diameter must be positive. Got {phi}') + + # Good bond conditions anchorage lengths table from Table 11.1 (NDP) + good_bond_length_table = { + 12: {20: 47, 25: 42, 30: 38, 35: 36, 40: 33, 45: 31, 50: 30, 60: 27}, + 14: {20: 50, 25: 44, 30: 41, 35: 38, 40: 35, 45: 33, 50: 31, 60: 29}, + 16: {20: 52, 25: 46, 30: 42, 35: 39, 40: 37, 45: 35, 50: 33, 60: 30}, + 20: {20: 56, 25: 50, 30: 46, 35: 42, 40: 40, 45: 37, 50: 35, 60: 32}, + 25: {20: 60, 25: 54, 30: 49, 35: 46, 40: 43, 45: 40, 50: 38, 60: 35}, + 28: {20: 63, 25: 56, 30: 51, 35: 47, 40: 44, 45: 42, 50: 40, 60: 36}, + 32: {20: 65, 25: 58, 30: 53, 35: 49, 40: 46, 45: 44, 50: 41, 60: 38}, + } + + # Get the unique sorted lists of phis and fck values from the table + phi_values = sorted(good_bond_length_table.keys()) + fck_values = sorted(good_bond_length_table[phi_values[0]].keys()) + + # Create a matrix of lbd/phi values for interpolation + anchorage_matrix = [ + [good_bond_length_table[p][fck] for fck in fck_values] + for p in phi_values + ] + + # Create an interpolation function + interpolation_function = RegularGridInterpolator( + (phi_values, fck_values), anchorage_matrix, method='linear' + ) + + # Validate fck and phi are within the interpolation range + if fck < fck_values[0] or fck > fck_values[-1]: + raise ValueError( + f'fck value {fck} is outside the supported' + + f' range ({fck_values[0]}, {fck_values[-1]})' + ) + if phi < phi_values[0] or phi > phi_values[-1]: + raise ValueError( + f'phi value {phi} is outside the supported' + + f' range ({phi_values[0]}, {phi_values[-1]})' + ) + lbd_phi = interpolation_function((phi, fck)) + + if bond_condition.lower() == 'poor': + lbd_phi *= poor_bond_multiplier + + return lbd_phi * phi + + +def lbd( + phi: float, + fck: float, + sigma_sd: float, + cd: float, + c: float, + bond_condition: Literal['good', 'poor', 'bentonite'], + design_situation: Literal['persistent', 'accidental'] = 'persistent', +) -> float: + """Calculate the design anchorage length for straight bars using. + + EN1992-1-1:2023 Eq. (11.3) + + Args: + phi (float): Diameter of the reinforcing bar in mm. + fck (float): Characteristic compressive strength of concrete in MPa. + sigma_sd (float): Design value of the tensile stress in the bar in MPa. + cd (float): Minimum nominal cover to the reinforcement in mm in X or Y, + whatever is smaller. + cx (float): Minimum separation between bars in mm in X or Y, + whatever is smaller. + bond_condition (str): Bond condition ('good', 'poor', 'bentonite'). + design_situation (str): Design situation ('persistent', 'accidental'). + + Returns: + float: Design anchorage length in mm. + + Raises: + ValueError: If any of the input values are + outside the specified limits. + + """ + # Validate inputs + if phi <= 0: + raise ValueError(f'Bar diameter must be positive. Got {phi}') + if fck <= 0: + raise ValueError( + f'Concrete compressive strength must be positive. Got {fck}' + ) + if sigma_sd <= 0: + raise ValueError(f'Tensile stress must be positive. Got {sigma_sd}') + if cd <= 0: + raise ValueError(f'Nominal cover must be positive. Got {cd}') + + # Define coefficients + if design_situation.lower() == 'persistent': + klb = 50 + n_sigma = 3 / 2 + elif design_situation.lower() == 'accidental': + klb = 35 + n_sigma = 3 / 2 + else: + raise ValueError(f'Unknown design situation: {design_situation}') + + if bond_condition.lower() == 'good': + kcp = 1.0 + elif bond_condition.lower() == 'poor': + kcp = 1.2 + elif bond_condition.lower() == 'bentonite': + kcp = 1.4 + else: + raise ValueError(f'Unknown bond condition: {bond_condition}') + + # Calculate minimum cover cd + min_cd = min(0.5 * cd, c, 3.75 * phi) + + # Calculate the ratios with limits + phi_ratio = max(phi / 20, 0.6) + fck_ratio = max(25 / fck, 0.3) + + # Calculate the design anchorage length + lbd = ( + klb + * kcp + * phi + * ((sigma_sd / 435) ** n_sigma) + * (fck_ratio**0.5) + * (phi_ratio ** (1 / 3)) + * ((1.5 * phi) / min_cd) ** 0.5 + ) + + # Ensure the minimum anchorage length + return max(lbd, 10 * phi) + + +def calculate_bond_condition( + inclination_angle: float, + distance_from_bottom: float, + distance_from_surface: float, +) -> str: + """Determine bond condition based on bar inclination + and positioning during concreting. + + EN-1992-1-1:2023 11.4.2(4) + + Args: + inclination_angle (float): Inclination angle of the + bar to the horizontal (degrees). + distance_from_bottom (float): Distance of the bar from + the bottom of the formwork in mm. + distance_from_surface (float): Distance of the bar from + the free surface during concreting in mm. + + Returns: + str: 'good' for good bond conditions, 'poor' for poor bond conditions. + """ + if inclination_angle < 0 or inclination_angle > 90: + raise ValueError( + 'Inclination angle must be between ' + + f' 0 and 90 degrees. Got {inclination_angle}' + ) + + # Determine bond condition based on provided rules + if inclination_angle >= 45: + return 'good' + if distance_from_bottom <= 300 or distance_from_surface >= 300: + return 'good' + return 'poor' + + +def cd_conf( + cx: float, + cy: float, + phi_t: float, + st: float, + cs: float, + phi: float, + rho_conf: float, + sigma_ccd: float, + fck: float, +) -> float: + """Calculate the design anchorage length reduction parameter `cd,conf`. + + EN-1992-1-1:2023 Eq. 11.4 + + Args: + cx (float): minimum cover to longitudinal + reinforcement along x-axis in mm. + cy (float): minimum cover to longitudinal + reinforcement along y-axis in mm. + phi_t (float): Diameter of transverse reinforcement in mm. + st (float): Spacing of transverse reinforcement along the bar in mm. + cs (float): Clear spacing between bars or bundles in mm. + phi (float): Diameter of the bar to be anchored or spliced in mm. + rho_conf (float): Ratio of the confinement reinforcement + sigma_ccd (float): Design value of mean compression stress + perpendicular to the potential splitting failure in MPa. + fck (float): Characteristic compressive cylinder + strength of concrete in MPa. + + Returns: + float: Reduced design anchorage length in mm. + + Raises: + ValueError: If any input dimension is negative. + """ + fck = abs(fck) + sigma_ccd = abs(sigma_ccd) + if any(x < 0 for x in [cx, cy, phi_t, st, cs, phi, rho_conf]): + raise ValueError('Input values must not be negative.') + + delta_cd = (70 * rho_conf + 12 * sigma_ccd / (fck**0.5)) * phi + cd_conf = min(cx, cy + (25 * phi_t**2 / st), cs / 2, 3.75 * phi) + delta_cd + + return min(cd_conf, 6 * phi) + + +def rho_conf(nc: int, phi_c: float, nb: int, phi: float, sc: float) -> float: + """Calculate the ratio of confinement reinforcement. + + EN1992-1-1:2023 Eq. (11.5) + + Args: + nc (int): Number of legs of confinement reinforcement + crossing the potential splitting surface. + phi_c (float): Diameter of the confinement + reinforcement in mm. + nb (int): Number of anchored bars or pairs of lapped bars + in the potential splitting surface. + phi (float): Diameter of the bar to be + anchored or spliced in mm. + sc (float): Spacing of the confinement + reinforcement along the bar in mm. + + Returns: + float: Ratio of the confinement reinforcement. + + Raises: + ValueError: If any input dimension is negative. + """ + if any(x < 0 for x in [nc, phi_c, nb, phi, sc]): + raise ValueError('Input values must not be negative.') + + return (nc * math.pi * (phi_c**2) / 4) / (nb * phi * sc) + + +def additional_transverse_confinement_reinf( + phi: float, n1: int, n2: int +) -> Union[float, float]: + """Calculate additional transverse and confinement reinforcement. + + EN1992-1-1:2023 (11.5.2(6)) + + Args: + phi (float): Diameter of bars in mm. + n1 (int): Number of layers with bars anchored at the + same point in the member. + n2 (int): Number of bars anchored in each layer. + + Returns: + tuple: Additional transverse and confinement reinforcement in mm2. + + Raises: + ValueError: If any input dimension is negative. + """ + if any(x < 0 for x in [phi, n1, n2]): + raise ValueError('Input values must not be negative.') + + Ast = 0.20 * (phi**2) * n1 + Asc = 0.20 * (phi**2) * n2 + + return Ast, Asc + + +def phi_b_anchor(area_bars: float) -> float: + """Calculate the equivalent diameter of a bundle of bars. + + EN1992-1-1:2023 Eq. (11.6) + + + Args: + area_bars (float): Total area of all bars contained + in the bundle in mm2. + + Returns: + float: Equivalent diameter of the bundle in mm. + + Raises: + ValueError: If the area is negative. + """ + if area_bars < 0: + raise ValueError('Area of bars must not be negative.') + + return (4 * area_bars / math.pi) ** 0.5 + + +def cd( + cs: float, + cx: float, + cy: float, + cyb: float, +) -> float: + """Calculate the nominal cover cd as defined in Figure 11.6c). + + EN-1992-1-1:2023 (11.4.4(Fig 11.6c)) + + Args: + cs (float): Minimum cover to the bar in mm. + cx (float): Cover in x-direction in mm. + cy (float): Cover in y-direction in mm. + cyb (float): Cover to bottom surface in mm. + + Returns: + float: Nominal cover cd in mm. + + Raises: + ValueError: If any input value is negative. + """ + if any(x < 0 for x in (cs, cx, cy, cyb)): + raise ValueError('Cover dimensions must not be negative.') + + return min(cs / 2, cx, cy, cyb) + + +def lb_anchor_tension(lbd: float, phi: float) -> float: + """Calculate the design anchorage length for bars + with bends and hooks in tension. + + EN-1992-1-1:2023 (11.4.4(1)) + + Args: + lbd (float): Basic design anchorage length in mm. + phi (float): Diameter of the bar in mm. + + Returns: + float: Total design anchorage length lbd,tot [mm]. + + Raises: + ValueError: If lbd or phi is negative. + + Reference: + EN1992-1-1:2023 Eq. (XX), Figure 11.7a) + """ + if lbd < 0 or phi < 0: + raise ValueError( + f'lbd and phi must not be negative. Got lbd={lbd}, phi={phi}.' + ) + + lbd_tot = lbd - 15 * phi + return max(lbd_tot, 10 * phi) + + +def lb_anchor_compression( + lbd: float, + phi: float, + d_p: float, +) -> float: + """Calculate the design anchorage length for + bars with bends and hooks in compression. + + Args: + lbd (float): Basic design anchorage length in mm. + phi (float): Diameter of the bar in mm. + d_p (float): Distance of free surfaces + perpendicular to the bar in mm. + + Returns: + float: Total design anchorage length in mm. + + Raises: + ValueError: If lbd, phi, or d_p is negative. + + Reference: + EN1992-1-1:2023 Figure 11.7b) + """ + if lbd < 0 or phi < 0 or d_p < 0: + raise ValueError( + 'lbd, phi, and perpendicular_distance must not be negative. ' + + f'Got lbd={lbd}, phi={phi}, d_p={d_p}.' + ) + + if d_p >= 3.5 * phi: + return max(lbd - 15 * phi, 10 * phi) + + return lbd + + +def lb_anchor_weld_trans( + lbd: float, + phi: float, + phi_t: float, + s: float, + number_of_transverse_bars: int, +) -> float: + """Calculate the design anchorage length for bars + with welded transverse reinforcement in tension and compression. + + EN1992-1-1:2023 (11.4.5) + + Args: + lbd (float): Basic design anchorage length in mm. + phi (float): Diameter of the main bar in mm. + phi_t (float): Diameter of the transverse bar in mm. + s (float): Spacing of transverse bars in mm. + number_of_transverse_bars (int): Number of transverse + bars within the anchorage length. + + Returns: + float: Total design anchorage length in mm. + + Raises: + ValueError: If lbd, phi, phi_t, s are negative or if + the number_of_transverse_bars is not a positive integer. + """ + if lbd < 0 or phi < 0 or phi_t < 0 or s < 0: + raise ValueError( + 'lbd, phi, phi_t, and s must not be negative. Got ' + + f'lbd={lbd}, phi={phi}, phi_t={phi_t}, s={s}.' + ) + if number_of_transverse_bars <= 0: + raise ValueError( + 'number_of_transverse_bars must be a positive integer. ' + + f'Got {number_of_transverse_bars}.' + ) + + if (phi_t >= 0.6 * phi and number_of_transverse_bars < 1) or ( + phi_t < 0.6 + and (not (50 <= s <= 100) or number_of_transverse_bars < 2 or phi > 16) + ): + return lbd + + # Reduce design anchorage length by 15*phi, but not less than 5*phi + lbd_tot = lbd - 15 * phi + return max(lbd_tot, 5 * phi) + + +def lb_anchor_ubar_loops_tension(lbd: float, phi: float) -> float: + """Calculate the design anchorage length for U-bar loops in pure tension. + + EN1992-1-1:2023 (11.4.6) + + Args: + lbd (float): Basic design anchorage length in mm. + phi (float): Diameter of the bar in mm. + + Returns: + float: Total design anchorage length in mm. + + Raises: + ValueError: If lbd or phi is negative. + + Reference: + + """ + if lbd < 0 or phi < 0: + raise ValueError( + f'lbd and phi must not be negative. Got lbd={lbd}, phi={phi}.' + ) + + # Reduce design anchorage length by 20*phi, but not less than 10*phi + lbd_tot = lbd - 20 * phi + return max(lbd_tot, 10 * phi) + + +def phi_h(Ah: Union[float, int]) -> float: + """Calculate the equivalent diameter of a circular head + based on the head area Ah. + + EN1992-1-1:2023 Eq. (11.7) + + Args: + Ah (float): Total area of the head in mm2. + + Returns: + float: Equivalent diameter of the circular head in mm. + + Raises: + ValueError: If Ah is negative. + """ + if Ah < 0: + raise ValueError(f'Ah must not be negative. Got Ah={Ah}.') + + return 2 * math.sqrt(Ah / math.pi) + + +def check_anchorage_headed_bars_in_tension( + fck: float, + phi: float, + phi_h: float, + th: float, + ay: float, + ax: float, + sx: float, + Ah: float, + concrete_cracked: bool, +) -> bool: + """Check if a headed bar in tension satisfies + requirements for anchorage without additional length. + + EN-1992-1-1:2023 11.4.7 + + Args: + fck (float): Characteristic compressive cylinder + strength of concrete in MPa. + phi (float): Diameter of the reinforcing bar in mm. + phi_h (float): Diameter of the circular head or equivalent + circular diameter in mm. + th (float): Thickness of the head in mm. + ay (float): Distance between the bar axis + and the nearest edge in mm. + ax (float): Minimum distance between + the bar axis and a corner in mm. + sx (float): Bar spacing of a group of + bars along the considered edge in mm. + Ah (float): Total area of the head in mm2. + concrete_cracked (bool): True if the concrete + is cracked, False if uncracked. + + Returns: + bool: True if the configuration satisfies + the conditions, False otherwise. + + Raises: + ValueError: If any input value is negative. + """ + if any(x < 0 for x in (fck, phi, phi_h, th, ay, ax, sx, Ah)): + raise ValueError('Input values must not be negative.') + + # Check the given conditions for headed bars in tension + if fck < 25 or phi > 25 or (phi_h < 3 * phi): + return False + + if concrete_cracked and ay < 4 * phi: + return False + + if ay < 3 * phi: + return False + + if not (ax >= 2 * ay + 1.2 * phi_h): + return False + + if not (sx >= 4 * ay): + return False + + return True + + +def kh_A(phi_h: float, phi: float) -> float: + """Calculate the ratio between the net area of the head and the + cross-sectional area of the reinforcement. + + EN-1992-1-1:2023 Eq. (11.9) + + Args: + phi_h (float): Diameter of the circular head in mm. + phi (float): Diameter of the reinforcing bar in mm. + + Returns: + float: Ratio kh,A. + + Raises: + ValueError: If phi_h or phi is negative. + """ + if phi_h < 0 or phi < 0: + raise ValueError( + 'phi_h and phi must not be negative.' + + f' Got phi_h={phi_h}, phi={phi}.' + ) + + return (phi_h / phi) ** 2 - 1 + + +def a_d( + ay: float, + ax: float, + sx: float, + phi: float, + phi_h: float, +) -> float: + """Calculate the nominal value of the distance + a_d between the bar and a free surface. + + EN-1992-1-1:2023 Eq. (11.10) + + Args: + ay (float): Distance between the bar axis and the nearest edge in mm. + ax (float): Minimum distance between the bar axis and a corner in mm. + sx (float): Bar spacing of a group of bars along the + considered edge in mm. + phi (float): Diameter of the reinforcing bar in mm. + phi_h (float): Diameter of the circular head in mm. + + Returns: + float: Nominal distance a_d in mm. + + Raises: + ValueError: If any input value is negative. + """ + if any(x < 0 for x in (ay, ax, sx, phi, phi_h)): + raise ValueError('Input values must not be negative.') + + if ax >= 2 * ay + 1.2 * phi_h and sx >= 4 * ay: + # Single bar near an edge or group of bars with spacing sx >= 4ay + return ay + if ax < 2 * ay + 1.2 * phi_h: + # Single bar near a corner + return 0.5 * ay + 0.25 * ax - 0.3 * phi_h + + # Group of bars with spacing sx < 4ay + a = ay * (sx - phi_h) / (4 * ay - phi_h) + b = 2.3 * (ay - phi_h / 2) + c = (4 * ay - sx) / (4 * ay - phi_h) + d = 1 - 1 / (phi_h / phi) ** 2 + return a + b * c * d + + +def sigma_sd_prime( + fck: float, + gamma_c: float, + phi: float, + phi_h: float, + a_d: float, + kh_A: float, + nu_part: float, + ddg: float, +) -> float: + """Calculate the maximum tensile stress in the + reinforcing steel developed by the head. + + EN1992-1-1:2023 Eq. (11.8) + + Args: + fck (float): Characteristic compressive cylinder + strength of concrete in MPa. + gamma_c (float): safety coefficient for concrete. + phi (float): Diameter of the reinforcing bar in mm. + phi_h (float): Diameter of the circular head in mm. + a_d (float): Nominal value of the distance + between the bar and a free surface in mm. + kh_A (float): Ratio kh_A. + nu_part (float): Coefficient for cracked or uncracked concrete. + 8.0 for concrete cracked region of the head and 11.0 for + uncracked concrete in the region of the head. + ddg (float): Maximum aggregate size in mm. + + Returns: + float: Maximum tensile stress in the reinforcing steel in MPa. + + Raises: + ValueError: If any input value is negative. + + Reference: + + """ + if any(x < 0 for x in (fck, phi, phi_h, a_d, fck, ddg, gamma_c)): + raise ValueError('Input values must not be negative.') + + sigma_sd = kh_A * fck / gamma_c + ( + nu_part + * (math.sqrt(fck) / gamma_c) + * (a_d / phi) + * (phi_h / phi) ** (5 / 6) + * (ddg / phi) ** (1 / 3) + ) + + return min(sigma_sd, kh_A * nu_part * fck / gamma_c) + + +def lbd_bar_tension( + lbd_sigma_sd: Union[float, int], lbd_sigma_sd_prime: Union[float, int] +) -> float: + """Calculate the design length in the reinforcing + bar lbd to develop the remaining stress. + + EN1992-1-1:2023 Eq. (11.11) + + Args: + lbd_sigma_sd (float): Design length corresponding + to the stress sigma_sd, in mm. + lbd_sigma_sd_prime (float): Design length corresponding + to the stress sigma_sd_prime, in mm. + + Returns: + float: Design length lbd in mm. + + Raises: + ValueError: If lbd_sigma_sd or lbd_sigma_sd_prime is negative. + """ + if lbd_sigma_sd < 0 or lbd_sigma_sd_prime < 0: + raise ValueError( + f'lbd_sigma_sd and lbd_sigma_sd_prime must not be negative. ' + f'Got lbd_sigma_sd={lbd_sigma_sd}, ' + + f'lbd_sigma_sd_prime={lbd_sigma_sd_prime}.' + ) + + return 1.1 * (lbd_sigma_sd - lbd_sigma_sd_prime) + + +def cmin_b( + phi: float, + lbd_pi: float, + drilling_method: Literal['rotary_percussion', 'diamond', 'compressed_air'], + use_drilling_aid: bool, +) -> float: + """Calculate the minimum concrete cover cmin,b + for post-installed reinforcing steel bars. + + EN1992-1-1:2023 Table (11.2) + + Args: + phi (float): Diameter of the reinforcing bar in mm. + lbd_pi (float): Design anchorage length for post-installed bars in mm. + drilling_method (str): Drilling method used + ('rotary_percussion', 'diamond', 'compressed_air'). + use_drilling_aid (bool): True if a drilling + aid is used, False otherwise. + + Returns: + float: Minimum concrete cover cmin_b in mm. + + Raises: + ValueError: If any input value is invalid. + """ + if phi < 0 or lbd_pi < 0: + raise ValueError( + 'phi and lbd_pi must not be negative. ' + + f'Got phi={phi}, lbd_pi={lbd_pi}.' + ) + + if drilling_method in ('rotary_percussion', 'diamond'): + if phi < 25: + base_cover = 30 + factor = 0.06 if not use_drilling_aid else 0.02 + else: + base_cover = 40 + factor = 0.06 if not use_drilling_aid else 0.02 + elif drilling_method == 'compressed_air': + if phi < 25: + base_cover = 50 + factor = 0.08 if not use_drilling_aid else 0.02 + else: + base_cover = 60 + factor = 0.08 if not use_drilling_aid else 0.02 + + # Calculate cmin,b + return base_cover + factor * lbd_pi + + +def lbd_pi( + lbd: float, + kb_pi: float, + phi: float, + alpha_lb: float = 1.5, +) -> float: + """Calculate the design anchorage length lbd,pi + of post-installed reinforcing steel bars in tension. + + EN1992-1-1:2023 Eq. (11.12) + + Args: + lbd (float): Design anchorage length calculated + according to 11.4.2 in mm. + kb_pi (float): Bond efficiency factor. + phi (float): Diameter of the reinforcing bar in mm. + alpha_lb (float): Factor accounting for cracks along the bar. + + Returns: + float: Design anchorage length lbd,pi in mm. + + Raises: + ValueError: If any input value is invalid. + + """ + if lbd < 0 or phi < 0 or kb_pi <= 0: + raise ValueError( + f'lbd, phi must not be negative, and kb_pi must be positive. ' + f'Got lbd={lbd}, phi={phi}, kb_pi={kb_pi}.' + ) + + lbd_pi = lbd / kb_pi + return max(lbd_pi, 10 * phi * alpha_lb) + + +def lsd( + type_of_lap: Literal[ + 'straight_bars', + 'bends_and_hooks', + 'loops', + 'headed_bars', + 'intermeshed_fabric', + 'layered_fabric', + 'bonded_postinstalled', + ], + state: Literal['tension', 'compression'], + lbd: float, + phi: float, + kls: float = 1.2, + lbd_pi: float = 0, + alpha_lb: float = 1.5, + phi_mand: float = 0, +) -> float: + """Calculate the design lap length lsd based on the type of lap splice. + + EN1992-1-1:2023 Table (11.3) + + Args: + type_of_lap (str): Type of lap splice + ('straight_bars', 'bends_and_hooks', 'loops', 'headed_bars', + 'intermeshed_fabric', 'layered_fabric', 'bonded_postinstalled'). + state (str): where the lap is in tension or compression. + lbd (float): Basic design anchorage length in mm. + phi (float): Diameter of the reinforcing bar in mm. + kls (float): Coefficient for lap length, default is + 1.2 unless specified otherwise. + lbd_pi (float): Design anchorage length + for post-installed bars in mm. + alpha_lb (float): Factor accounting for cracks along the bar. + Normally the value is 1.5. + phi_mand (float or int): Mandrel diameter for loops in mm. + + Returns: + float: Design lap length lsd in mm. + + Raises: + ValueError: If any input value is invalid or the + result is incompatible according to the Table 11.3. + """ + if ( + phi < 0 + or lbd < 0 + or lbd_pi < 0 + or phi_mand < 0 + or kls < 0 + or alpha_lb < 0 + ): + raise ValueError( + 'Invalid input values. phi, lbd, lbd_pi, alpha_lb, kls' + + 'and phi_mand must not be negative.' + ) + if ( + type_of_lap in ('straight_bars', 'bends_and_hooks') + and state == 'compression' + ): + raise ValueError(f'Not possible to have {type_of_lap} laps in {state}') + + if type_of_lap in ('straight_bars', 'bends_and_hooks'): + return max(kls * lbd, 15 * phi) + if type_of_lap == 'loops': + return phi_mand + 4 * phi # Calculated according to 11.5.4 + if type_of_lap == 'headed_bars': + return lbd # Calculated according to 11.5.5 + if type_of_lap in ('intermeshed_fabric', 'layered_fabric'): + return max(kls * lbd, 15 * phi, 250) + + # If bonded_postinstalled + return max(kls * lbd_pi, 15 * phi * alpha_lb) + + +def cs_laps( + phi: float, +) -> float: + """Calculate the minimum clear distance between adjacent laps. + + EN1992-1-1:2023 Section 11.5.2 (Key 1) + + Args: + phi (float): Diameter of the reinforcing bar in mm. + + Returns: + float: Minimum clear distance between adjacent laps in mm. + + Raises: + ValueError: If phi is negative. + """ + if phi < 0: + raise ValueError('phi must not be negative.') + + return max(2 * phi, 20) + + +def cs_bars(phi: float) -> float: + """Calculate the clear distance between lapping bars. + + EN1992-1-1:2023 Section 11.5.2 (Key 2) + + Args: + phi (float): Diameter of the reinforcing bar in mm. + + Returns: + float: Clear distance between lapping bars in mm. + + Raises: + ValueError: If phi is negative. + """ + if phi < 0: + raise ValueError('phi must not be negative.') + + return min(4 * phi, 50) + + +def min_clear_distante_laps(phi: float) -> float: + """Calculate the minimum clear distance between lapped bars. + + EN1992-1-1:2023 Section 11.5.2(7) + + Args: + phi (float): Diameter of the reinforcing bar in mm. + + Returns: + float: Minimum clear distance between lapped bars in mm. + + Raises: + ValueError: If phi is negative. + """ + if phi < 0: + raise ValueError('phi must not be negative.') + + return min(4 * phi, 50) + + +def Ac_u_bars(phi_mand: float, phi: float, lsd: float) -> float: + """Calculate the total effective concrete area + within the curved parts of the overlapping U-bars. + + EN1992-1-1:2023 Eq. (11.14) + + Args: + phi_mand (float): Mandrel diameter for loops in mm. + phi (float): Diameter of the reinforcing bar in mm. + lsd (float): Lap length in mm. + + Returns: + float: Total effective concrete area Ac in mm2. + + Raises: + ValueError: If any input value is negative. + """ + if phi_mand < 0 or phi < 0 or lsd < 0: + raise ValueError( + 'Invalid input values. phi_mand, phi, and ' + + 'lsd must not be negative.' + ) + + return (phi_mand + phi) * (lsd - 0.21 * (phi_mand + phi)) + + +def kst_u_bar_loops( + Ast: float, + fyd: float, + Ac: float, + fcd: float, + lsd: float, + ddg: float, +) -> float: + """Calculate the resistance factor of the confinement reinforcement kst. + + EN1992-1-1:2023 Eq. (11.15) + + Args: + Ast (float): Total area of the fully + anchored confinement reinforcement positioned within Ac in mm2. + fyd (float): Design yield strength + of the confinement reinforcement in MPa. + Ac (float): total effective concrete area within curved parts + of overlapping U-bars in mm2. + fcd (float): Design compressive strength of concrete in MPa. + lsd (float): lap length in mm. + ddg (float): Coefficient that takes into account + the concrete type and its aggregate properties. + + Returns: + float: Resistance factor kst. + + Raises: + ValueError: If any input value is negative. + """ + if Ast < 0 or fyd < 0 or fcd < 0 or ddg < 0 or Ac < 0: + raise ValueError( + 'Invalid input values. All parameters must not be negative.' + ) + + w = (Ast * fyd) / (0.85 * pow((ddg / lsd), 1 / 3) * Ac * fcd) + + if w >= 0.5: + return 1.0 + + return 4 * w * (1 - w) + + +def TRd_c_u_bar_loops( + fcd: float, + lsd: float, + ddg: float, + kst: float, + cs: float, + Ac: float, +) -> float: + """Calculate the resistance of a single lap splice TRd_c. + + EN1992-1-1:2023 Eq. (11.13) + + Args: + fcd (float): Design compressive strength of concrete in MPa. + lsd (float): Lap length in mm. + ddg (float): Coefficient that takes into account the + concrete type and its aggregate properties. + kst (float): Resistance factor of the confinement reinforcement. + cs (float): Clear spacing of U-bars in mm. + Ac (float): total effective concrete area within curved parts + of overlapping U-bars in mm2. + + Returns: + float: Resistance of a single lap splice TRd,c in kN. + + Raises: + ValueError: If any input value is negative. + """ + if fcd < 0 or lsd < 0 or ddg < 0 or cs < 0 or Ac < 0: + raise ValueError( + 'Invalid input values. All parameters must not be negative.' + ) + + trd_c = ( + (0.2 * fcd * Ac) + * pow((ddg / lsd), 1 / 3) + * (math.sqrt(kst + (cs / lsd) ** 2) - cs / lsd) + ) + return trd_c / 1000 # To convert to kN + + +def Ast_min_u_bar_loops(fck: float, Ac: float, fyk: float) -> float: + """Calculate the minimum amount of confinement reinforcement + in a double symmetric configuration within Ac to avoid + brittle behaviour. + + EN1992-1-1:2023 Eq. (11.16) + + Args: + fck (float): Characteristic compressive + strength of concrete in MPa. + Ac (float): Total effective concrete area in mm2. + fyk (float): Characteristic yield strength of + the confinement reinforcement in MPa. + + Returns: + float: Minimum amount of confinement reinforcement Ast in mm2. + + Raises: + ValueError: If any input value is negative. + """ + if fck < 0 or Ac < 0 or fyk < 0: + raise ValueError( + 'Invalid input values. All parameters must not be negative.' + ) + + return 0.5 * math.sqrt(fck) * Ac / fyk + + +def Ac_headed_laps(lsd: float, phi: float, bh1: float) -> float: + """Calculate the effective concrete area within + the heads of the overlapping bars. + + EN1992-1-1:2023 Eq. (11.18) + + Args: + lsd (float): Design anchorage length in mm. + phi (float): Diameter of the reinforcing bar in mm. + bh1 (float): Effective width of the head + perpendicular to the plane of the lap in mm. + + Returns: + float: Effective concrete area Ac in mm2. + + Raises: + ValueError: If any input value is negative. + """ + if lsd < 0 or phi < 0 or bh1 < 0: + raise ValueError( + 'Invalid input values. lbd, phi, and bh1 must not be negative.' + ) + + return (lsd - 2 * phi) * bh1 + + +def bh1_headed_laps(phi_h: float) -> float: + """Calculate the effective width of the head + perpendicular to the plane of the lap for circular heads. + + EN1992-1-1:2023 Eq. (11.19) + + Args: + phi_h (float): Diameter of the circular head in mm. + + Returns: + float: Effective width bh1 in mm. + + Raises: + ValueError: If phi_h is negative. + """ + if phi_h < 0: + raise ValueError('Invalid input value. phi_h must not be negative.') + + return 0.5 * phi_h * math.sqrt(math.pi) + + +def kst_headed_bars( + Ast: float, + fyd: float, + fcd: float, + Ac: float, + lsd: float, + phi: float, + ddg: float, +) -> float: + """Calculate the resistance factor of the transverse reinforcement kst. + + EN-1992-1-1:2023 Eq. (11.20) + + Args: + Ast (float): Total area of the fully anchored + transverse reinforcement positioned within Ac in mm2. + fyd (float): Design yield strength of the + transverse reinforcement in MPa. + fcd (float): Design compressive + strength of concrete in MPa. + phi (float): Diameter of the reinforcing bar in mm. + Ac (float): Total effective concrete area in mm2. + lsd (float): Design anchorage length in mm. + ddg (float): Coefficient that takes into + account the concrete type and its aggregate properties. + + Returns: + float: Resistance factor kst. + + Raises: + ValueError: If any input value is negative. + """ + if Ast < 0 or fyd < 0 or fcd < 0 or phi < 0 or ddg < 0 or lsd < 0: + raise ValueError( + 'Invalid input values. All parameters must not be negative.' + ) + + w = (Ast * fyd) / (1.3 * pow((ddg / (lsd - 2 * phi)), 1 / 3) * Ac * fcd) + + if w >= 0.5: + return 1.0 + return 4 * w * (1 - w) + + +def TRd_c_headed( + fcd: float, + lsd: float, + ddg: float, + kst: float, + cs: float, + phi: float, + bh1: float, + Ac: float, +) -> float: + """Calculate the resistance of a single headed bar lap TRd_c. + + EN-1992-1-1:2023 Eq. (11.17) + + Args: + fcd (float): Design compressive strength of concrete in MPa. + lsd (float): Design anchorage length in mm. + ddg (float): Coefficient that takes into account + the concrete type and its aggregate properties. + kst (float): Resistance factor of the transverse reinforcement. + cs (float): Clear spacing of headed bars in mm. + phi (float): Diameter of the reinforcing bar in mm. + bh1 (float): Effective width of the head perpendicular + to the plane of the map in mm2. + Ac (float): Effective concrete area in mm2. + + Returns: + float: Resistance of a single headed bar lap TRd_c in kN. + + Raises: + ValueError: If any input value is negative. + """ + if fcd < 0 or lsd < 0 or ddg < 0 or cs < 0 or phi < 0 or bh1 < 0: + raise ValueError( + 'Invalid input values. All parameters must not be negative.' + ) + + tRd_c = ( + (0.6 * fcd * Ac) + * pow((ddg / (lsd - 2 * phi)), 1 / 3) + * (math.sqrt(kst + (cs / (lsd - 2 * phi)) ** 2) - cs / (lsd - 2 * phi)) + ) + return tRd_c / 1000 # Convert to kN + + +def Ast_min_headed_bars( + fck: float, + Ac: float, + fyk: float, + phi: float, +) -> float: + """Calculate the minimum amount of transverse reinforcement. + + EN-1992-1-1:2023 Eq. (11.21) + + Args: + fck (float): Characteristic compressive strength of concrete in MPa. + Ac (float): Effective concrete area in mm2. + fyk (float): Characteristic yield strength + of the transverse reinforcement in MPa. + phi (float): Diameter of the reinforcing bar in mm. + + Returns: + float: Minimum amount of transverse reinforcement Ast in mm2. + + Raises: + ValueError: If any input value is negative. + """ + if fck < 0 or Ac < 0 or fyk < 0 or phi < 0: + raise ValueError( + 'Invalid input values. All parameters must not be negative.' + ) + + return max(0.75 * math.sqrt(fck) / fyk * Ac, (math.pi / 8) * phi**2) + + +def Ast_min_headed_single_bars(phi: float) -> float: + """Calculate the minimum area of tie down reinforcement. + + EN-1992-1-1:2023 Eq. (11.22) + + Args: + phi (float): Diameter of the reinforcing bar in mm. + + Returns: + float: Minimum area of tie down reinforcement Astd in mm2. + + Raises: + ValueError: If phi is negative. + + """ + if phi < 0: + raise ValueError('Invalid input value. phi must not be negative.') + + return 0.12 * phi**2 + + +def min_clear_distante_laps_couplers( + Dupper: float, max_bar_diameter: float +) -> float: + """Calculate the minimum clear distance (horizontal and vertical) + between couplers and between couplers and adjacent bars. + + EN1992-1-1:2023 Section 11.5.6(1) + + Args: + Dupper (float): Upper diameter of the coupler in mm. + max_bar_diameter (float): Maximum diameter of the bars in mm. + + Returns: + float: Minimum clear distance in mm. + + Raises: + ValueError: If any input value is negative. + """ + if Dupper < 0 or max_bar_diameter < 0: + raise ValueError( + 'Invalid input values. Dupper and max_bar_diameter ' + + 'must not be negative.' + ) + + return max(Dupper + 5, max_bar_diameter) + + +def csx_duct(Dupper: float, phi_duct: float) -> float: + """Calculate the minimum horizontal spacing + csx for post-tensioning tendons. + + EN1992-1-1:2023 11.6.2(1) + + Args: + Dupper (float): Upper diameter of the duct in mm. + phi_duct (float): Diameter of the duct in mm. + + Returns: + float: Minimum horizontal spacing csx in mm. + + Raises: + ValueError: If any input value is negative. + """ + if Dupper < 0 or phi_duct < 0: + raise ValueError( + 'Invalid input values. Dupper and phi_duct must not be negative.' + ) + + return max(Dupper + 5, phi_duct, 50) + + +def csy_duct(Dupper: float, phi_duct: float) -> float: + """Calculate the minimum vertical spacing csy + for post-tensioning tendons. + + EN1992-1-1:2023 Section 11.6.2(1) + + Args: + Dupper (float): Upper diameter of the duct in mm. + phi_duct (float): Diameter of the duct in mm. + + Returns: + float: Minimum vertical spacing csy in mm. + + Raises: + ValueError: If any input value is negative. + """ + if Dupper < 0 or phi_duct < 0: + raise ValueError( + 'Invalid input values. Dupper and phi_duct must not be negative.' + ) + + return max(Dupper, phi_duct, 40) + + +def min_spacing_bundled_tendons() -> float: + """Calculate the minimum spacing between bundled tendons. + + EN1992-1-1:2023 Section 11.6.2(2) + + Returns: + float: Minimum spacing between bundled tendons in mm. + """ + return 100.0 + + +def Rmin_unconf_concrete(sigma_pd: float, pRd: float, Ap: float) -> float: + """Calculate the minimum radius of curvature Rmin + for tendons to prevent damage of the unconfined concrete. + + EN-1992-1-1:2023 Eq. (11.23) + + Args: + sigma_pd (float): Tendon design stress in MPa. + pRd (float): Maximum transverse bearing + stress on the prestressing tendon in MPa. + Ap (float): Cross-sectional area + of the prestressing tendon in mm2. + + Returns: + float: Minimum radius of curvature Rmin in mm. + + Raises: + ValueError: If any input value is negative. + """ + if sigma_pd < 0 or pRd < 0 or Ap < 0: + raise ValueError( + 'Invalid input values. sigma_pd, pRd, and Ap must not be negative.' + ) + + return sigma_pd / pRd * math.sqrt(Ap) + + +def max_pRd( + case: Literal[ + 'internal_corrugated', 'internal_U_shape', 'external_smooth' + ], + fcd: float, +) -> float: + """Get the maximum transverse bearing stress pRd + based on the case and concrete design compressive strength. + + EN1992-1-1:2023 Table (11.4) + + Args: + case (str): Case of the tendon ('internal_corrugated', + 'internal_U_shape', 'external_smooth'). + fcd (float): Design compressive strength of concrete in MPa. + + Returns: + float: Maximum transverse bearing stress pRd in MPa. + + Raises: + ValueError: If case is invalid or fcd is negative. + """ + if fcd < 0: + raise ValueError('Invalid input value. fcd must not be negative.') + + if case == 'internal_corrugated': + return min(0.75 * fcd, 15) + if case == 'internal_U_shape': + return 70 + # if external_smooth + return 30 + + +def cu( + cs: float, + cy: float, + phi: float, +) -> float: + """Calculate the parameter cu. + + EN1992-1-1:2023 (11.7) + + Args: + cs (float): Clear spacing of reinforcing bars or ducts in mm. + cy (float): Distance from the center of the bar to the + concrete surface in mm. + phi (float): Diameter of the reinforcing bar or duct in mm. + Use phi_duct for post-tensioning tendons. + is_post_tensioning (bool): True if the calculation is for + post-tensioning tendons, False otherwise. + + Returns: + float: Parameter cu in mm. + + Raises: + ValueError: If any input value is negative. + """ + if cs < 0 or cy < 0 or phi < 0: + raise ValueError( + 'Invalid input values. cs, cy, and phi must not be negative.' + ) + + return min(cs, 2 * math.sqrt(3) * (cy + 0.5 * phi)) + + +def verify_deviation_forces( + Ftd: float, + r: float, + cu: float, + fck: float, + gamma_c: float, +) -> bool: + """Verify if the deviation forces can be resisted by + the concrete in tension. + + EN1992-1-1:2023 Eq. (11.24) + + Args: + Ftd (float): Deviation force in kN. + r (float): Radius of curvature in mm. + cu (float): Parameter cu in mm. + fck (float): Characteristic compressive strength of concrete in MPa. + gamma_c (float): Partial safety factor for concrete. + + Returns: + bool: True if the deviation forces can be + resisted by the concrete in tension, False otherwise. + + Raises: + ValueError: If any input value is negative. + """ + if Ftd < 0 or r < 0 or cu < 0 or fck < 0 or gamma_c <= 0: + raise ValueError( + 'Invalid input values. Ftd, r, cu, and fck must not be ' + + 'negative, and gamma_c must be positive.' + ) + + return (Ftd * 1000 / (r * cu)) <= (0.125 / gamma_c) * math.sqrt(fck) + + +def verify_interaction_bond( + Ftd: float, + r: float, + cu: float, + fck: float, + gamma_c: float, + lsd: float, + ls: float, +) -> bool: + """Verify the interaction between bond and transverse + tensile stresses due to deviation forces. + + EN1992-1-1:2023 Eq. (11.25) + + Args: + Ftd (float): Deviation force in kN. + r (float): Radius of curvature in mm. + cu (float): Parameter cu in mm. + fck (float): Characteristic compressive strength of concrete in MPa. + gamma_c (float): Partial safety factor for concrete. + lsd (float): Design lap length in mm. + ls (float): Actual lap length in mm. + + Returns: + bool: True if the interaction criterion is satisfied, False otherwise. + + Raises: + ValueError: If any input value is negative. + """ + if ( + Ftd < 0 + or r < 0 + or cu < 0 + or fck < 0 + or gamma_c <= 0 + or lsd < 0 + or ls < 0 + ): + raise ValueError( + 'Invalid input values. Ftd, r, cu, fck, lsd, and ls must not be ' + + 'negative, and gamma_c must be positive.' + ) + + return (Ftd * 1000 * 8 * gamma_c) / (r * cu * math.sqrt(fck)) + ( + lsd / ls + ) <= 1 diff --git a/tests/test_ec2_2023/test_ec2_2023_section_11_detailing_steel.py b/tests/test_ec2_2023/test_ec2_2023_section_11_detailing_steel.py new file mode 100644 index 00000000..1406774e --- /dev/null +++ b/tests/test_ec2_2023/test_ec2_2023_section_11_detailing_steel.py @@ -0,0 +1,755 @@ +"""Test for functions from Section 11 of EN 1992-1-1:2023.""" + +import pytest + +from structuralcodes.codes.ec2_2023 import _section11_detailing_steel + + +@pytest.mark.parametrize( + 'phi, D_upper, expected', + [ + (12, 15, 20), + (10, 14, 20), + (25, 10, 25), + ], +) +def test_clear_distance_between_bars(phi, D_upper, expected): + """Test clear distance between parallel bars.""" + result = _section11_detailing_steel.min_clear_distance_between_bars( + phi, D_upper + ) + assert result == expected + + +@pytest.mark.parametrize( + 'surface_roughness, min_bond_distance, expected', + [ + (True, 8, 5), + (False, 8, 8), + ], +) +def test_clear_distance_to_poured_concrete( + surface_roughness, min_bond_distance, expected +): + """Test clear distance between poured concrete and parallel bar.""" + result = _section11_detailing_steel.min_clear_distance_to_poured_concrete( + surface_roughness, min_bond_distance + ) + assert result == expected + + +@pytest.mark.parametrize( + 'phi, expected', + [ + (10, 40), + (20, 140), + ], +) +def test_mandrel_diameter(phi, expected): + """Test minimum mandrel diameter for bending bars.""" + assert _section11_detailing_steel.min_mandrel_phi(phi) == expected + + +@pytest.mark.parametrize( + 'fck, gamma_c, d_g, phi, phi_mand, c_d, k_bend, expected', + [ + (30, 1.5, 16, 10, 40, 20, 32, 423.559), + (40, 1.5, 20, 15, 40, 20, 32, 334.359), + ], +) +def test_steel_stress_limit( + fck, gamma_c, d_g, phi, phi_mand, c_d, k_bend, expected +): + """Test steel stress limit to avoid concrete failure inside the bend.""" + result = _section11_detailing_steel.sigma_sd_bend_concrete( + fck, gamma_c, d_g, phi, phi_mand, c_d, k_bend + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'n_trans, phi, phi_mand, phi_trans, alpha_bend, expected', + [ + (2, 10, 40, 10, 45, 3), + (5, 20, 50, 15, 45, 5.5), + ], +) +def test_k_trans_factor( + n_trans, phi, phi_mand, phi_trans, alpha_bend, expected +): + """Test increase factor for steel stress limit with transverse bars.""" + result = _section11_detailing_steel.k_trans( + n_trans, phi, phi_mand, phi_trans, alpha_bend + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'alpha_bend, expected', + [ + (45, 32), + (30, 48), + ], +) +def test_k_bend(alpha_bend, expected): + """Test test_k_bend.""" + result = _section11_detailing_steel.k_bend(alpha_bend) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'fck, phi, bond_condition, expected', + [ + (20, 12, 'good', 564), + (30, 16, 'good', 672), + (40, 25, 'good', 1075), + (25, 28, 'poor', 1881.60), + (28, 14, 'good', 590.800), + (50, 32, 'good', 1312), + (35, 20, 'poor', 1008.0), + ], +) +def test_lbd_simple(fck, phi, bond_condition, expected): + """Test anchorage length calculation with + various conditions including bilinear interpolation. + """ + result = _section11_detailing_steel.lbd_simple(fck, phi, bond_condition) + assert pytest.approx(result, rel=1e-2) == expected + + +@pytest.mark.parametrize( + 'phi, fck, sigma_sd, cd, c, bond_condition, design_situation, expected', + [ + (20, 30, 400, 30, 20, 'good', 'persistent', 1138.362), + (25, 40, 435, 40, 20, 'poor', 'persistent', 1749.1829), + (32, 50, 400, 50, 20, 'bentonite', 'persistent', 2530.665), + (28, 25, 350, 28, 20, 'good', 'accidental', 1370.4532), + ], +) +def test_lbd( + phi, fck, sigma_sd, cd, c, bond_condition, design_situation, expected +): + """Test design anchorage length calculation with various conditions.""" + result = _section11_detailing_steel.lbd( + phi, fck, sigma_sd, cd, c, bond_condition, design_situation + ) + assert pytest.approx(result, rel=1e-2) == expected + + +@pytest.mark.parametrize( + 'cx, cy, phi_t, st, cs, phi, rho_conf, sigma_ccd, f_ck, expected', + [ + (40, 40, 15, 200, 50, 16, 0.01, 5, 30, 96), + (50, 45, 15, 200, 55, 20, 0.02, 7, 35, 120), + ], +) +def test_cd_conf( + cx, cy, phi_t, st, cs, phi, rho_conf, sigma_ccd, f_ck, expected +): + """Test calculate_cd_conf function with example values.""" + result = _section11_detailing_steel.cd_conf( + cx, cy, phi_t, st, cs, phi, rho_conf, sigma_ccd, f_ck + ) + assert pytest.approx(result, rel=1e-2) == expected + + +@pytest.mark.parametrize( + 'nc, phi_c, nb, phi, sc, expected', + [ + (4, 10, 2, 16, 150, 0.0654), + (3, 12, 1, 20, 100, 0.1696), + ], +) +def test_rho_conf(nc, phi_c, nb, phi, sc, expected): + """Test calculate_rho_conf function with example values.""" + result = _section11_detailing_steel.rho_conf(nc, phi_c, nb, phi, sc) + assert pytest.approx(result, rel=1e-3) == expected + + +@pytest.mark.parametrize( + 'phi, n1, n2, expected_Ast, expected_Asc', + [ + (16, 2, 3, 102.4, 153.6), + (20, 1, 2, 80.0, 160.0), + ], +) +def test_calculate_additional_reinforcement( + phi, n1, n2, expected_Ast, expected_Asc +): + """Test calculate_additional_reinforcement function with example values.""" + Ast, Asc = ( + _section11_detailing_steel.additional_transverse_confinement_reinf( + phi, n1, n2 + ) + ) + assert pytest.approx(Ast, rel=1e-3) == expected_Ast + assert pytest.approx(Asc, rel=1e-3) == expected_Asc + + +@pytest.mark.parametrize( + 'area_bars, expected', + [ + (804, 32.0), + (1256.64, 40.0), + ], +) +def test_phi_b_anchor(area_bars, expected): + """Test calculate_equivalent_diameter function with example values.""" + result = _section11_detailing_steel.phi_b_anchor(area_bars) + assert pytest.approx(result, rel=1e-3) == expected + + +@pytest.mark.parametrize( + 'cs, cx, cy, cyb, expected', + [ + (20, 25, 30, 15, 10), + (30, 40, 20, 25, 15), + (50, 35, 45, 20, 20), + ], +) +def test_cd(cs, cx, cy, cyb, expected): + """Test nominal cover cd calculation.""" + assert _section11_detailing_steel.cd(cs, cx, cy, cyb) == expected + + +@pytest.mark.parametrize( + 'lbd, phi, expected', + [ + (200, 10, 100), + (180, 12, 120), + (220, 8, 100), + ], +) +def test_lb_anchor_tension(lbd, phi, expected): + """Test design anchorage length for tension.""" + assert _section11_detailing_steel.lb_anchor_tension(lbd, phi) == expected + + +@pytest.mark.parametrize( + 'lbd, phi, perpendicular_distance, expected', + [ + (200, 10, 40, 100), + (180, 12, 36, 180), + (220, 8, 28, 100), + ], +) +def test_lb_anchor_compression(lbd, phi, perpendicular_distance, expected): + """Test design anchorage length for compression.""" + assert ( + _section11_detailing_steel.lb_anchor_compression( + lbd, phi, perpendicular_distance + ) + == expected + ) + + +@pytest.mark.parametrize( + 'lbd, phi, phi_t, s, number_of_transverse_bars, expected', + [ + (200, 10, 6, 75, 2, 50), + (180, 12, 8, 80, 1, 60), + (250, 16, 10, 60, 2, 80), + ], +) +def test_anchorage_length_welded_transverse_reinforcement( + lbd, phi, phi_t, s, number_of_transverse_bars, expected +): + """Test design anchorage length calculation + for welded transverse reinforcement. + """ + assert ( + _section11_detailing_steel.lb_anchor_weld_trans( + lbd, phi, phi_t, s, number_of_transverse_bars + ) + == expected + ) + + +@pytest.mark.parametrize( + 'lbd, phi, expected', + [ + (250, 12, 120), + (300, 16, 160), + (200, 10, 100), + ], +) +def test_anchorage_length_u_bar_loops(lbd, phi, expected): + """Test design anchorage length calculation for U-bar loops.""" + assert ( + _section11_detailing_steel.lb_anchor_ubar_loops_tension(lbd, phi) + == expected + ) + + +@pytest.mark.parametrize( + 'Ah, expected', + [ + (2800, 59.708), + (4400, 75), + (6300, 90), + (0, 0), + ], +) +def test_phi_h(Ah, expected): + """Test calculation of equivalent diameter for circular head.""" + result = _section11_detailing_steel.phi_h(Ah) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'fck, phi, phi_h, th, ay, ax, sx, Ah, concrete_cracked, expected', + [ + (30, 20, 60, 15, 60, 130, 240, 2827.43, False, False), + (28, 25, 75, 18, 75, 160, 320, 4417.86, True, False), + (25, 22, 55, 20, 80, 100, 180, 2375.08, True, False), + (40, 20, 60, 15, 50, 120, 200, 2827.43, False, False), + (35, 30, 90, 20, 90, 190, 360, 6361.73, True, False), + ], +) +def test_check_anchorage_headed_bars_in_tension( + fck, phi, phi_h, th, ay, ax, sx, Ah, concrete_cracked, expected +): + """Test if the headed bar configuration + satisfies anchorage requirements. + """ + assert ( + _section11_detailing_steel.check_anchorage_headed_bars_in_tension( + fck, phi, phi_h, th, ay, ax, sx, Ah, concrete_cracked + ) + == expected + ) + + +@pytest.mark.parametrize( + 'phi_h, phi, expected', + [ + (60, 20, 8.0), + (75, 25, 8.0), + (80, 20, 15.0), + ], +) +def test_kh_A(phi_h, phi, expected): + """Test calculation of kh_A.""" + result = _section11_detailing_steel.kh_A(phi_h, phi) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'ay, ax, sx, phi, phi_h, expected', + [ + (60, 130, 240, 20, 60, 44.5), + (75, 100, 200, 25, 75, 40), + (80, 100, 180, 22, 55, 48.5), + (50, 70, 150, 18, 50, 27.5), + ], +) +def test_a_d(ay, ax, sx, phi, phi_h, expected): + """Test calculation of ad.""" + result = _section11_detailing_steel.a_d(ay, ax, sx, phi, phi_h) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'fck, gamma_c, phi, phi_h, a_d, kh_A, nu_part, ddg, expected', + [ + (30, 1.5, 20, 60, 60, 8.0, 11.0, 32, 512.066), + (28, 1.5, 25, 75, 75, 8.25, 8.0, 32, 383.6342), + (25, 1.5, 22, 55, 58.75, 15.0, 8.0, 32, 423.146), + ], +) +def test_sigma_sd_prime( + fck, gamma_c, phi, phi_h, a_d, kh_A, nu_part, ddg, expected +): + """Test calculation of maximum tensile stress sigma_sd_prime.""" + result = _section11_detailing_steel.sigma_sd_prime( + fck, gamma_c, phi, phi_h, a_d, kh_A, nu_part, ddg + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'lbd_sigma_sd, lbd_sigma_sd_prime, expected', + [ + (200, 150, 55), + (300, 100, 220), + (180, 180, 0), + ], +) +def test_calculate_lbd(lbd_sigma_sd, lbd_sigma_sd_prime, expected): + """Test calculation of design length lbd.""" + result = _section11_detailing_steel.lbd_bar_tension( + lbd_sigma_sd, lbd_sigma_sd_prime + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'phi, lbd_pi, drilling_method, use_drilling_aid, expected', + [ + (20, 200, 'rotary_percussion', False, 42), + (26, 300, 'diamond', True, 46), + (24, 150, 'compressed_air', False, 62), + (26, 250, 'compressed_air', True, 65), + (20, 200, 'rotary_percussion', True, 34), + ], +) +def test_cmin_b(phi, lbd_pi, drilling_method, use_drilling_aid, expected): + """Test calculation of minimum concrete cover cmin,b.""" + result = _section11_detailing_steel.cmin_b( + phi, lbd_pi, drilling_method, use_drilling_aid + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'lbd, kb_pi, phi, alpha_lb, expected', + [ + (200, 1.0, 20, 1.5, 300), + (300, 1.5, 25, 1.5, 375), + (250, 1.2, 22, 1.5, 330), + ], +) +def test_lbd_pi(lbd, kb_pi, phi, alpha_lb, expected): + """Test calculation of design anchorage length lbd,pi.""" + assert ( + _section11_detailing_steel.lbd_pi(lbd, kb_pi, phi, alpha_lb) + == expected + ) + + +@pytest.mark.parametrize( + 'type_of_lap, state, lbd, phi, kls, lbd_pi, alpha_lb, phi_mand, expected', + [ + ('straight_bars', 'tension', 200, 10, 1.2, 0, 1.0, 0, 240), + ('bends_and_hooks', 'tension', 250, 15, 1.2, 0, 1.0, 0, 300), + ('loops', 'tension', 0, 12, 1.0, 0, 1.0, 50, 98), + ('headed_bars', 'compression', 300, 0, 1.0, 0, 1.0, 0, 300), + ('intermeshed_fabric', 'tension', 220, 20, 1.2, 0, 1.0, 0, 300), + ('layered_fabric', 'compression', 220, 20, 1.2, 0, 1.0, 0, 300), + ('bonded_postinstalled', 'tension', 0, 15, 1.0, 280, 1.5, 0, 337.5), + ], +) +def test_lsd( + type_of_lap, state, lbd, phi, kls, lbd_pi, alpha_lb, phi_mand, expected +): + """Test calculation of design lap length + lsd for various types of lap splices. + """ + result = _section11_detailing_steel.lsd( + type_of_lap, state, lbd, phi, kls, lbd_pi, alpha_lb, phi_mand + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'phi, expected', + [ + (10, 20), + (15, 30), + (8, 20), + ], +) +def test_cs_laps(phi, expected): + """Test calculation of minimum clear distance between adjacent laps.""" + assert _section11_detailing_steel.cs_laps(phi) == expected + + +@pytest.mark.parametrize( + 'phi, expected', + [ + (10, 40), + (15, 50), + (8, 32), + ], +) +def test_cs_bars(phi, expected): + """Test calculation of clear distance between lapping bars.""" + assert _section11_detailing_steel.cs_bars(phi) == expected + + +@pytest.mark.parametrize( + 'phi, expected', + [ + (10, 40), + (15, 50), + (8, 32), + ], +) +def test_calculate_min_clear_distance(phi, expected): + """Test calculation of minimum clear distance between lapped bars.""" + assert _section11_detailing_steel.min_clear_distante_laps(phi) == expected + + +@pytest.mark.parametrize( + 'phi_mand, phi, lsd, expected', + [ + (50, 20, 200, 12971.0), + (40, 15, 150, 7614.75), + ], +) +def test_Ac_u_bars(phi_mand, phi, lsd, expected): + """Test calculation of total effective concrete area Ac.""" + result = _section11_detailing_steel.Ac_u_bars(phi_mand, phi, lsd) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'Ast, fyd, Ac, lsd, fcd, ddg, expected', + [ + (100, 500, 5000, 200, 30, 16, 1), + (120, 400, 15000, 150, 25, 16, 0.867), + ], +) +def test_kst(Ast, fyd, Ac, lsd, fcd, ddg, expected): + """Test calculation of resistance factor kst.""" + result = _section11_detailing_steel.kst_u_bar_loops( + Ast, fyd, Ac, fcd, lsd, ddg + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'fcd, lsd, ddg, kst, cs, Ac, expected', + [ + (30, 200, 16, 0.8, 50, 10000, 17.546), + (25, 150, 16, 0.9, 40, 12500, 21.305), + ], +) +def test_TRd_c(fcd, lsd, ddg, kst, cs, Ac, expected): + """Test calculation of resistance of a single lap splice TRd_c.""" + result = _section11_detailing_steel.TRd_c_u_bar_loops( + fcd, lsd, ddg, kst, cs, Ac + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'fck, Ac, fyk, expected', + [ + (30, 7640, 500, 41.846), + (25, 4359, 400, 27.243), + ], +) +def test_Ast_min_u_bar_loops(fck, Ac, fyk, expected): + """Test calculation of minimum amount of confinement reinforcement.""" + result = _section11_detailing_steel.Ast_min_u_bar_loops(fck, Ac, fyk) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'lsd, phi, bh1, expected', + [ + (200, 20, 20, 3200), + (150, 15, 15, 1800), + ], +) +def test_Ac_headed_laps(lsd, phi, bh1, expected): + """Test calculation of effective concrete area Ac for headed bars.""" + result = _section11_detailing_steel.Ac_headed_laps(lsd, phi, bh1) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'phi_h, expected', + [ + (50, 44.4), + (40, 35.52), + ], +) +def test_bh1_headed_laps(phi_h, expected): + """Test calculation of effective width bh1 for circular heads.""" + result = _section11_detailing_steel.bh1_headed_laps(phi_h) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'Ast, fyd, lsd, fcd, phi, ddg, Ac, expected', + [ + (100, 500, 200, 30, 20, 16, 10000, 0.8), + (120, 400, 150, 25, 15, 16, 12500, 0.711), + ], +) +def test_kst_headed_bars(Ast, fyd, lsd, fcd, phi, ddg, Ac, expected): + """Test calculation of resistance factor kst + for transverse reinforcement. + """ + result = _section11_detailing_steel.kst_headed_bars( + Ast, fyd, fcd, Ac, lsd, phi, ddg + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'fcd, lsd, ddg, kst, cs, phi, bh1, Ac, expected', + [ + (30, 200, 16, 0.8, 50, 20, 50, 10000, 53.048), + (25, 150, 16, 0.9, 40, 15, 40, 12500, 64.389), + ], +) +def test_TRd_c_headed(fcd, lsd, ddg, kst, cs, phi, bh1, Ac, expected): + """Test calculation of resistance of a single headed bar lap TRd_c.""" + result = _section11_detailing_steel.TRd_c_headed( + fcd, lsd, ddg, kst, cs, phi, bh1, Ac + ) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'fck, Ac, fyk, phi, expected', + [ + (30, 3600, 500, 20, 157.07), + (25, 1575, 400, 15, 88.357), + ], +) +def test_Ast_min_headed_bars(fck, Ac, fyk, phi, expected): + """Test calculation of minimum amount of transverse reinforcement.""" + result = _section11_detailing_steel.Ast_min_headed_bars(fck, Ac, fyk, phi) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'phi, expected', + [ + (20, 48.0), + (15, 27.0), + ], +) +def test_Ast_min_headed_single_bars(phi, expected): + """Test calculation of minimum area of tie down reinforcement.""" + result = _section11_detailing_steel.Ast_min_headed_single_bars(phi) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'Dupper, max_bar_diameter, expected', + [ + (30, 25, 35), + (40, 45, 45), + (50, 55, 55), + ], +) +def test_min_clear_distante_laps_couplers(Dupper, max_bar_diameter, expected): + """Test calculation of minimum clear distance between + couplers and between couplers and adjacent bars. + """ + assert ( + _section11_detailing_steel.min_clear_distante_laps_couplers( + Dupper, max_bar_diameter + ) + == expected + ) + + +@pytest.mark.parametrize( + 'Dupper, phi_duct, expected', + [ + (30, 25, 50), + (40, 45, 50), + (60, 55, 65), + ], +) +def test_csx_duct(Dupper, phi_duct, expected): + """Test calculation of minimum horizontal + spacing csx for post-tensioning tendons. + """ + assert _section11_detailing_steel.csx_duct(Dupper, phi_duct) == expected + + +@pytest.mark.parametrize( + 'Dupper, phi_duct, expected', + [ + (30, 25, 40), + (40, 45, 45), + (60, 55, 60), + ], +) +def test_csy_duct(Dupper, phi_duct, expected): + """Test calculation of minimum vertical spacing + csy for post-tensioning tendons. + """ + assert _section11_detailing_steel.csy_duct(Dupper, phi_duct) == expected + + +def test_min_spacing_bundled_tendons(): + """Test calculation of minimum spacing between bundled tendons.""" + assert _section11_detailing_steel.min_spacing_bundled_tendons() == 100.0 + + +@pytest.mark.parametrize( + 'sigma_pd, pRd, Ap, expected', + [ + (1000, 15, 200, 942.81), + (1200, 70, 150, 209.96), + (1100, 30, 180, 491.93), + ], +) +def test_Rmin_unconf_concrete(sigma_pd, pRd, Ap, expected): + """Test calculation of minimum radius of curvature Rmin for tendons.""" + result = _section11_detailing_steel.Rmin_unconf_concrete(sigma_pd, pRd, Ap) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'case, fcd, expected', + [ + ('internal_corrugated', 20, 15), + ('internal_U_shape', 30, 70), + ('external_smooth', 25, 30), + ], +) +def test_max_pRd(case, fcd, expected): + """Test getting the maximum transverse bearing + stress pRd based on the case. + """ + assert _section11_detailing_steel.max_pRd(case, fcd) == expected + + +@pytest.mark.parametrize( + 'cs, cy, phi, expected', + [ + (100, 10, 12, 55.43), + (30, 15, 20, 30), + ], +) +def test_cu(cs, cy, phi, expected): + """Test calculation of parameter cu.""" + result = _section11_detailing_steel.cu(cs, cy, phi) + assert result == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'Ftd, r, cu, fck, gamma_c, expected', + [ + (100, 2e6, 50, 30, 1.5, True), + (200, 1e4, 30, 25, 1.5, False), + ], +) +def test_verify_deviation_forces(Ftd, r, cu, fck, gamma_c, expected): + """Test verification if the deviation forces can + be resisted by the concrete in tension. + """ + assert ( + _section11_detailing_steel.verify_deviation_forces( + Ftd, r, cu, fck, gamma_c + ) + == expected + ) + + +@pytest.mark.parametrize( + 'Ftd, r, cu, fck, gamma_c, lsd, ls, expected', + [ + (100, 2e6, 50, 30, 1.5, 100, 120, True), + (200, 1e4, 30, 25, 1.5, 80, 100, False), + ], +) +def test_verify_interaction_bond(Ftd, r, cu, fck, gamma_c, lsd, ls, expected): + """Test verification of the interaction between bond + and transverse tensile stresses due to deviation forces. + """ + assert ( + _section11_detailing_steel.verify_interaction_bond( + Ftd, r, cu, fck, gamma_c, lsd, ls + ) + == expected + )