From 2e70179b15367ce42bda243cfccad7ab88ea210c Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Tue, 13 Dec 2022 21:01:29 +0100 Subject: [PATCH 01/28] Draft initial structure for the concrete class Co-authored-by: talledodiego <38036285+talledodiego@users.noreply.github.com> --- structuralcodes/__init__.py | 10 +- structuralcodes/{code => codes}/__init__.py | 7 + .../{code => codes}/mc2010/__init__.py | 0 .../mc2010/_concrete_material_properties.py | 18 +- structuralcodes/core/__init__.py | 0 structuralcodes/core/base.py | 26 +++ structuralcodes/material/__init__.py | 0 structuralcodes/material/concrete/__init__.py | 58 ++++++ .../material/concrete/_concrete.py | 45 +++++ .../material/concrete/_concreteMC2010.py | 189 ++++++++++++++++++ tests/test_get_set_design_code.py | 12 +- tests/test_mc2010_material_properties.py | 12 +- 12 files changed, 355 insertions(+), 22 deletions(-) rename structuralcodes/{code => codes}/__init__.py (94%) rename structuralcodes/{code => codes}/mc2010/__init__.py (100%) rename structuralcodes/{code => codes}/mc2010/_concrete_material_properties.py (82%) create mode 100644 structuralcodes/core/__init__.py create mode 100644 structuralcodes/core/base.py create mode 100644 structuralcodes/material/__init__.py create mode 100644 structuralcodes/material/concrete/__init__.py create mode 100644 structuralcodes/material/concrete/_concrete.py create mode 100644 structuralcodes/material/concrete/_concreteMC2010.py diff --git a/structuralcodes/__init__.py b/structuralcodes/__init__.py index 440bb17c..8f0abf85 100644 --- a/structuralcodes/__init__.py +++ b/structuralcodes/__init__.py @@ -1,7 +1,9 @@ """A Python package that contains models from structural design codes""" -from .code import set_design_code, get_design_codes, set_national_annex +from .codes import set_design_code, get_design_codes, set_national_annex -from .code import mc2010 +from . import material +from . import core +from . import codes __version__ = '' @@ -9,5 +11,7 @@ 'set_design_code', 'get_design_codes', 'set_national_annex', - 'mc2010', + 'codes', + 'core', + 'material', ] diff --git a/structuralcodes/code/__init__.py b/structuralcodes/codes/__init__.py similarity index 94% rename from structuralcodes/code/__init__.py rename to structuralcodes/codes/__init__.py index 47fcddd1..497e5086 100644 --- a/structuralcodes/code/__init__.py +++ b/structuralcodes/codes/__init__.py @@ -4,6 +4,13 @@ from . import mc2010 +__all__ = [ + 'mc2010', + 'set_design_code', + 'get_design_codes', + 'set_national_annex', +] + # Global code object used by material classes _CODE: t.Optional[types.ModuleType] = None diff --git a/structuralcodes/code/mc2010/__init__.py b/structuralcodes/codes/mc2010/__init__.py similarity index 100% rename from structuralcodes/code/mc2010/__init__.py rename to structuralcodes/codes/mc2010/__init__.py diff --git a/structuralcodes/code/mc2010/_concrete_material_properties.py b/structuralcodes/codes/mc2010/_concrete_material_properties.py similarity index 82% rename from structuralcodes/code/mc2010/_concrete_material_properties.py rename to structuralcodes/codes/mc2010/_concrete_material_properties.py index 65c022c2..6be8b025 100644 --- a/structuralcodes/code/mc2010/_concrete_material_properties.py +++ b/structuralcodes/codes/mc2010/_concrete_material_properties.py @@ -11,7 +11,7 @@ def fcm(fck: float, delta_f: float = 8.0) -> float: Args: fck (float): The characteristic compressive strength in MPa. - Kwargs: + Keyword Args: delta_f (float): The difference between the mean and the characteristic strength. @@ -38,34 +38,34 @@ def fctm(fck: float) -> float: return 2.12 * math.log(1 + 0.1 * fcm(fck)) -def fctkmin(fck: float) -> float: +def fctkmin(_fctm: float) -> float: """Compute the lower bound value of the characteristic tensile strength - from the characteristic compressive strength. + from the mean tensile strength. fib Model Code 2010, Eq. (5.1-4) Args: - fck (float): The characteristic compressive strength in MPa. + _fctm (float): The mean tensile strength in MPa. Returns: float: Lower bound of the characteristic tensile strength in MPa. """ - return 0.7 * fctm(fck) + return 0.7 * _fctm -def fctkmax(fck: float) -> float: +def fctkmax(_fctm: float) -> float: """Compute the upper bound value of the characteristic tensile strength - from the characteristic compressive strength. + from the mean tensile strength. fib Model Code 2010, Eq. (5.1-5) Args: - fck (float): The characteristic compressive strength in MPa. + _fctm (float): The mean tensile strength in MPa. Returns: float: Upper bound of the characteristic tensile strength in MPa. """ - return 1.3 * fctm(fck) + return 1.3 * _fctm def Gf(fck: float) -> float: diff --git a/structuralcodes/core/__init__.py b/structuralcodes/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/structuralcodes/core/base.py b/structuralcodes/core/base.py new file mode 100644 index 00000000..2a9fc080 --- /dev/null +++ b/structuralcodes/core/base.py @@ -0,0 +1,26 @@ +"""Abstract base classes""" +import abc +import typing as t + + +class Material(abc.ABC): + """Abstract base class for materials.""" + + def __init__(self, density: float, name: t.Optional[str] = None) -> None: + """ + Initializes an instance of a new material + :param float density: density of the material in kg/m3 + :param Optional[str] name: descriptive name of the material + """ + self._density = abs(density) + self._name = name if name is not None else "Material" + + @property + def name(self): + """Returns the name of the material""" + return self._name + + @property + def density(self): + """Returns the density of the material in kg/m3""" + return self._density diff --git a/structuralcodes/material/__init__.py b/structuralcodes/material/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/structuralcodes/material/concrete/__init__.py b/structuralcodes/material/concrete/__init__.py new file mode 100644 index 00000000..ca314baf --- /dev/null +++ b/structuralcodes/material/concrete/__init__.py @@ -0,0 +1,58 @@ +"""Concrete material""" +import typing as t +from structuralcodes.codes import _use_design_code +from ._concrete import Concrete +from ._concreteMC2010 import ConcreteMC2010 + +__all__ = [ + 'create_concrete', + 'Concrete', + 'ConcreteMC2010', +] + + +def create_concrete( + fck: float, + name: t.Optional[str] = None, + density: float = 2400.0, + existing: bool = False, + design_code: t.Optional[str] = None, +) -> t.Optional[Concrete]: + """ + A factory function to create the correct type of concrete based on the + desired design code. + + Args: + fck (float): Characteristic strength of concrete in MPa. + (if existing it is intended as the mean strength) + + Keyword Args: + density (float): Density of Concrete in kg/m3 (default: 2400) + existing (bool): Boolean indicating if the concrete is of an + existing structure (default: False) + deisgn_code (str): Optional string (default: None) indicating the + desired standard. If None (default) the globally used design + standard will be adopted. Otherwise the design standard specified + will be used for the instance of the material. + Currently available codes: 'mc2010' + + Raises: + ValueError: if the design code is not valid or does not cover + concrete as a material. + """ + # Get the code from the global variable + _code = _use_design_code(design_code) + + # Check if the code is a proper concrete code + code = _code if 'concrete' in _code.__materials__ else None + if code is None: + raise ValueError( + 'The design code is not set, either use ' + 'structuralcodes.code.set_designcode, or provide a valid ' + 'string in the function.' + ) + + # Create the proper concrete object + if code.__title__ == 'fib Model Code 2010': + return ConcreteMC2010(fck, name, density, existing) + return None diff --git a/structuralcodes/material/concrete/_concrete.py b/structuralcodes/material/concrete/_concrete.py new file mode 100644 index 00000000..19ac2048 --- /dev/null +++ b/structuralcodes/material/concrete/_concrete.py @@ -0,0 +1,45 @@ +"""Core implementation of the concrete material""" +import abc +import typing as t +from structuralcodes.core.base import Material + + +class Concrete(Material): + """The abstract concrete material.""" + + _fck: float + _existing: bool + + def __init__( + self, + fck: float, + name: t.Optional[str] = None, + density: float = 2400, + existing: t.Optional[bool] = False, + ) -> None: + """Initializes an abstract concrete material""" + name = name if name is not None else "Concrete" + super().__init__(density=density, name=name) + + self._fck = abs(fck) + if existing: + raise NotImplementedError( + 'Existing concrete feature not implemented yet' + ) + self._existing = existing + + @property + def fck(self) -> float: + """Returns fck in MPa""" + return self._fck + + @fck.setter + def fck(self, fck: float) -> None: + """Setter for fck (in MPa)""" + self._fck = abs(fck) + self._reset_attributes() + + @abc.abstractmethod + def _reset_attributes(self): + """Each concrete should define its own _reset_attributes method + This is because fck setting, reset the object arguments""" diff --git a/structuralcodes/material/concrete/_concreteMC2010.py b/structuralcodes/material/concrete/_concreteMC2010.py new file mode 100644 index 00000000..faf0ad97 --- /dev/null +++ b/structuralcodes/material/concrete/_concreteMC2010.py @@ -0,0 +1,189 @@ +"""The concrete class for Model Code 2020 Concrete Material""" +import typing as t +import warnings + +from structuralcodes.codes import mc2010 +from ._concrete import Concrete + + +class ConcreteMC2010(Concrete): + """Concrete implementation for MC 2010""" + + _fcm: t.Optional[float] = None + _fctm: t.Optional[float] = None + _fctkmin: t.Optional[float] = None + _fctkmax: t.Optional[float] = None + _Gf: t.Optional[float] = None + + def __init__( + self, + fck: float, + name: t.Optional[str] = None, + density: float = 2400.0, + existing: bool = False, + ): + """Initializes a new instance of Concrete for MC 2010 + + Args: + fck (float): Characteristic strength in MPa if concrete is not + existing. + + Keyword Args: + name (str): A descriptive name for concrete + density (float): Density of material in kg/m3 (default: 2400) + existing (bool): The material is of an existing structure + (default: False) + """ + + if name is None: + name = f'C{round(fck):d}' + super().__init__( + fck=fck, name=name, density=density, existing=existing + ) + + def _reset_attributes(self): + self._fcm = None + self._fctm = None + self._fctkmin = None + self._fctkmax = None + self._Gf = None + + def update_attributes(self, updated_attributes: dict) -> None: + """Function for updating the attributes specified in the input + dictionary + + Args: + updated_attributes (dict): the dictionary of parameters to be + updated (not found parameters are skipped with a warning) + """ + for key, value in updated_attributes.items(): + if not hasattr(self, '_' + key): + str_list_keys = '' + for k in updated_attributes.keys(): + str_list_keys += k + ', ' + str_warn = ( + f'WARNING: attribute {key} not found. Ignoring the entry.' + ) + str_warn += '\nAvailable keys: ' + str_list_keys + warnings.warn(str_warn) + continue + setattr(self, '_' + key, value) + + @property + def fcm(self) -> float: + """Returns fcm in MPa. + + Returns: + float: The mean compressive strength in MPa. + """ + if self._fcm is not None: + return self._fcm + return mc2010.fcm(self._fck) + + @fcm.setter + def fcm(self, value: float): + """Sets a user defined value for fcm + + Args: + value (float): the value of fcm in MPa + + Raises: + ValueError: if value is lower than fck + """ + if abs(value) <= self._fck: + raise ValueError( + ( + 'Mean compressive strength cannot be lower than', + 'characteristic strength.\n', + 'Current characteristing strength: ', + f'fck = {self._fck}.', + f'Current value: value = {value}', + ) + ) + self._fcm = abs(value) + + @property + def fctm(self) -> float: + """Returns fctm in MPa + + Returns: + float: The mean tensile strength in MPa + """ + if self._fctm is not None: + return self._fctm + return mc2010.fctm(self._fck) + + @fctm.setter + def fctm(self, value: float): + """Sets a user defined value for fctm + + Args: + value (float): the value of fctm in MPa + """ + if value > 0.5 * self._fck: + warnings.warn( + 'A suspect value of fctm has been input. Please check.' + ) + self._fctm = abs(value) + + @property + def fctkmin(self) -> float: + """Returns fctkmin in MPa + + Returns: + float: The lower bound tensile strength in MPa + """ + if self._fctkmin is not None: + return self._fctkmin + + return mc2010.fctkmin(self.fctm) + + @fctkmin.setter + def fctkmin(self, value: float): + """Sets a user defined value for fctkmin + + Args: + value (float): the value of fctkmin in MPa + """ + self._fctkmin = abs(value) + + @property + def fctkmax(self) -> float: + """Returns fctkmax in MPa + + Returns: + float: The upper bound tensile strength in MPa + """ + if self._fctkmax is not None: + return self._fctkmax + + return mc2010.fctkmax(self.fctm) + + @fctkmax.setter + def fctkmax(self, value: float): + """Sets a user defined value for fctkmax + + Args: + value (float): the value of fctkmax in MPa + """ + self._fctkmax = abs(value) + + @property + def Gf(self) -> float: + """Fracture energy of concrete + + Returns: + float: The fracture energy in N/m + """ + if self._Gf is not None: + return self._Gf + return mc2010.Gf(self._fck) + + @Gf.setter + def Gf(self, value: float): + """Sets a user defined value for fracture energy Gf + + Args: + value (float): the value of Gf in N/m + """ + self._Gf = abs(value) diff --git a/tests/test_get_set_design_code.py b/tests/test_get_set_design_code.py index 65686dcf..1711be83 100644 --- a/tests/test_get_set_design_code.py +++ b/tests/test_get_set_design_code.py @@ -19,14 +19,14 @@ def test_set_design_code(design_code_to_set): structuralcodes.set_design_code(design_code_to_set) # Assert - assert isinstance(structuralcodes.code._CODE, types.ModuleType) - assert structuralcodes.code._CODE.__title__ == expected_design_code_title + assert isinstance(structuralcodes.codes._CODE, types.ModuleType) + assert structuralcodes.codes._CODE.__title__ == expected_design_code_title def test_get_design_codes(): """Test get a list of implemented design codes.""" # Arrange - expected_list_of_codes = list(structuralcodes.code._DESIGN_CODES.keys()) + expected_list_of_codes = list(structuralcodes.codes._DESIGN_CODES.keys()) # Act available_codes = structuralcodes.get_design_codes() @@ -48,7 +48,7 @@ def test_set_national_annex(na_to_set): structuralcodes.set_national_annex(na_to_set) # Assert - assert structuralcodes.code._NATIONAL_ANNEX == expected_na + assert structuralcodes.codes._NATIONAL_ANNEX == expected_na @pytest.mark.parametrize( @@ -61,7 +61,7 @@ def test_use_design_code(design_code_to_user): expected_design_code_title = 'fib Model Code 2010' # Act - code_to_use = structuralcodes.code._use_design_code(design_code_to_user) + code_to_use = structuralcodes.codes._use_design_code(design_code_to_user) # Assert assert isinstance(code_to_use, types.ModuleType) @@ -76,7 +76,7 @@ def test_use_design_code_none(): structuralcodes.set_design_code(design_code_to_set) # Act - code_to_use = structuralcodes.code._use_design_code() + code_to_use = structuralcodes.codes._use_design_code() # Assert assert isinstance(code_to_use, types.ModuleType) diff --git a/tests/test_mc2010_material_properties.py b/tests/test_mc2010_material_properties.py index 13e339f7..e43bd26e 100644 --- a/tests/test_mc2010_material_properties.py +++ b/tests/test_mc2010_material_properties.py @@ -3,7 +3,7 @@ import pytest -from structuralcodes.code.mc2010 import _concrete_material_properties +from structuralcodes.codes.mc2010 import _concrete_material_properties @pytest.mark.parametrize( @@ -71,7 +71,9 @@ def test_fctm(test_input, expected): def test_fctkmin(test_input, expected): """Test the fctkmin function.""" assert math.isclose( - _concrete_material_properties.fctkmin(test_input), + _concrete_material_properties.fctkmin( + _concrete_material_properties.fctm(test_input) + ), expected, rel_tol=0.031, ) @@ -102,7 +104,9 @@ def test_fctkmin(test_input, expected): def test_fctkmax(test_input, expected): """Test the fctkmax function.""" assert math.isclose( - _concrete_material_properties.fctkmax(test_input), + _concrete_material_properties.fctkmax( + _concrete_material_properties.fctm(test_input) + ), expected, rel_tol=0.028, ) @@ -115,7 +119,7 @@ def test_fctkmax(test_input, expected): (35, 143.664), (55, 153.888), (90, 166.626), - (120, 174.832) + (120, 174.832), ], ) def test_Gf(test_input, expected): From b8f67bbfa78de0b09896870de8210be8c76e8e6f Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Tue, 13 Dec 2022 21:05:57 +0100 Subject: [PATCH 02/28] Update docstring of base material --- structuralcodes/core/base.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/structuralcodes/core/base.py b/structuralcodes/core/base.py index 2a9fc080..e0fa6d91 100644 --- a/structuralcodes/core/base.py +++ b/structuralcodes/core/base.py @@ -7,10 +7,13 @@ class Material(abc.ABC): """Abstract base class for materials.""" def __init__(self, density: float, name: t.Optional[str] = None) -> None: - """ - Initializes an instance of a new material - :param float density: density of the material in kg/m3 - :param Optional[str] name: descriptive name of the material + """Initializes an instance of a new material + + Args: + density (float): density of the material in kg/m3 + + Keyword Args: + name (Optional[str]): descriptive name of the material """ self._density = abs(density) self._name = name if name is not None else "Material" From c299dcc894f019ade6a4da8fe306b484e942a804 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 15 Dec 2022 13:17:10 +0100 Subject: [PATCH 03/28] minimum reinforcement areas functions --- .vscode/settings.json | 11 +- structuralcodes/codes/ec2_2004/__init__.py | 10 + .../codes/ec2_2004/_crack_control.py | 302 ++++++++++++++++++ tests/test_ec2_2004_crack_control.py | 150 +++++++++ 4 files changed, 471 insertions(+), 2 deletions(-) create mode 100644 structuralcodes/codes/ec2_2004/__init__.py create mode 100644 structuralcodes/codes/ec2_2004/_crack_control.py create mode 100644 tests/test_ec2_2004_crack_control.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 72069360..2676da93 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,17 @@ { - "python.formatting.provider": "black", "python.testing.pytestArgs": [ "tests" ], + "python.formatting.provider": "black", "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.linting.pylintEnabled": true, - "python.linting.flake8Enabled": true + "python.linting.flake8Enabled": true, + "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + }, + "editor.defaultFormatter": "ms-python.python", + }, } \ No newline at end of file diff --git a/structuralcodes/codes/ec2_2004/__init__.py b/structuralcodes/codes/ec2_2004/__init__.py new file mode 100644 index 00000000..96694004 --- /dev/null +++ b/structuralcodes/codes/ec2_2004/__init__.py @@ -0,0 +1,10 @@ +"""EUROCODE 2 1992-1-1:2004""" +import typing as t + +from ._crack_control import w_max + +__all__ = ['w_max'] + +__title__: str = 'EUROCODE 2 1992-1-1' +__year__: str = '2004' +__materials__: t.Tuple[str] = ('concrete',) diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py new file mode 100644 index 00000000..76b1e988 --- /dev/null +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -0,0 +1,302 @@ +"""Collection of functions from EUROCODE 1992-1-1:2004 +Chapter 7.3 - Crack control""" +import scipy.interpolate + + +def w_max(exposure_class: str, load_combination: str) -> float: + """Computes the recomended value of the maximum crack width. + + EUROCODE 2 1992-1-1:2004, Table (7.1N) + + Args: + exposure_class (str): The exposure class. + Possible values: X0, XC1, XC2, XC3, XC4, XD1, XD2, XS1, XS2, XS3 + load_combination (str): + - f: for frequent load combination + - qp: for quasi-permanent load combination + + Returns: + float: The maximum recommended value for the crack width wmax in mm. + + Raises: + ValueError: if not valid exposure_class or load_combination values. + """ + _load_combination = load_combination.lower() + _exposure_class = exposure_class.upper() + if _load_combination == 'f': + if _exposure_class in ('X0', 'XC1'): + return 0.2 + if _exposure_class in ('XC2', 'XC3', 'XC4'): + return 0.2 + if _load_combination == 'qp': + if _exposure_class in ('X0', 'XC1'): + return 0.4 + if _exposure_class in ( + 'XC2', + 'XC3', + 'XC4', + 'XD1', + 'XD2', + 'XS1', + 'XS2', + 'XS3', + ): + return 0.3 + raise ValueError( + f'{exposure_class} is not a valid value for exposure_class.' + + ' Please enter one of the following: X0, XC1, XC2, XC3, XC4, XD1' + + ',XD2, XS1, XS2, XS3' + ) + raise ValueError( + f'{load_combination} is not a valid value for load_combination.' + + 'Please enter "f" for frequent load combination or "qp" for' + + 'quasi-permanent load combination.' + ) + + +def crack_min_steel_area( + a_ct: float, s_steel: float, fct_eff: float, k: float, kc: float +) -> float: + """Computes the minimum area of reinforcing steel within the tensile zone + for control of cracking areas + + EUROCODE 2 1992-1-1:2004, Eq. (7.1) + + Args: + a_ct (float): is the area of concrete within the tensile zone in mm2. + The tensile zone is that parg of the section which is calculated + to be in tension just before the formation of the first crack. + s_steel (float): is the absolute value of the maximum stress in MPa + permitted in the reinforcement immediately after the formation + of the crack. This may be taken as theyield strength of the + reinforcement, fyk. A lower value may, however, be needed to + satisfy the crack width limits according to the maximum + bar size of spacing (see 7.3.3 (2)). + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + k (float): is the coefficient which allow for the effect of + non-uniform self-equilibrating stresses, which lead to a + reduction of restraint forces. Use 'k_crack_min_steel_area' + to compute it + k=1 for webs w<=300mm or flanges widths less than 300mm + k=0.65 for webs w>=800mm or flanges with widths greater than 800mm + Intermediate values may be interpolated. + kc (float): is a coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm. + + Returns: + float: the minimm area of reinforcing steel within the tensile + zone in mm2. + + Raises: + ValueError: if k value is not between 0.65 and 1 or kc is not + larger than 0 and lower than 1. + """ + s_steel = abs(s_steel) + fct_eff = abs(fct_eff) + + if k < 0.65 or k > 1.0: + raise ValueError(f'k={k} must be between 0.65 and 1') + if kc > 1 or kc < 0: + raise ValueError(f'kc={kc} must be lower than 1 and larger than 0') + + return kc * k * fct_eff * a_ct / s_steel + + +def k_crack_min_steel_area(h: float) -> float: + """Is the coefficient which allow for the effect of + non-uniform self-equilibrating stresses, which lead to a + reduction of restraint forces. Use 'k_crack_min_steel_area' + to compute it + k=1 for webs w<=300mm or flanges widths less than 300mm + k=0.65 for webs w>=800mm or flanges with widths greater than 800mm + + EUROCODE 2 1992-1-1:2004, Eq. (7.1) + + Args: + h (float): flange length or flange width in mm + + Returns: + float: k coefficient value + + Raises: + ValueError: if h is less than 0 + """ + if h < 0: + raise ValueError(f'h={h} cannot be less than 0mm') + if h <= 300: + return 1 + if h < 800: + interpol = scipy.interpolate.interp1d((300, 800), (1, 0.65)) + return (float)(interpol(h)) + return 0.65 + + +def kc_crack_min_steel_area_pure_tension() -> float: + """Computes the coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm in pure dtension. + + EUROCODE 2 1992-1-1:2004, Eq. (7.1) + + Returns: + float: value of the kc coefficient in pure tension + """ + return 1 + + +def kc_crack_min_steel_area_rectangular( + h: float, b: float, fct_eff: float, n_ed: float +) -> float: + """Computes the coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm for bending+axial combination + in rectangular sections and webs of box sections and T-sections. + + EUROCODE 2 1992-1-1:2004, Eq. (7.2) + + Args: + h (float): heigth of the element in mm + b (float): width of the element in mm + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + n_ed (str): axial force at the serviceability limit state acting on + the part of the cross-section under consideration (compressive + force positive). n_ed should be determined considering the + characteristic values of prestress and axial forces under the + relevant combination of actions + + Returns: + float: value of the kc coefficient + + Raises: + ValueError: is h or b are less than 0 + """ + if h < 0: + raise ValueError(f'h={h} should be larger than 0mm') + if b < 0: + raise ValueError(f'b={b} should be larger than 0mm') + + h_s = min(h, 1000) + k1 = 1.5 if n_ed >= 0 else 2 * h_s / 3 / h + s_concrete = n_ed * 1000 / b / h + h_ratio = h / h_s + return min(max(0.4 * (1 - s_concrete / k1 / h_ratio / fct_eff), 0), 1) + + +def kc_crack_min_steel_area_flanges( + f_cr: float, a_ct: float, fct_eff: float +) -> float: + """Computes the coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm for bending+axial combination + in rectangular sections for flanges of box sections and T-sections. + + EUROCODE 2 1992-1-1:2004, Eq. (7.3) + + Args: + f_cr: is the absolute value in kN of the tensile force within the + flange immediately prior to cracking due to cracking moment + calculated with fct,eff + a_ct (float): is the area of concrete within the tensile zone in mm2. + The tensile zone is that part of the section which is calculated + to be in tension just before the formation of the first crack. + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + + Returns: + float: value of the kc coefficient + + Raises: + ValueError: is a_ct is less than 0mm2 + """ + f_cr = abs(f_cr) + return max(0.9 * f_cr * 1000 / a_ct / fct_eff, 0.5) + + +def crack_min_steel_area_with_prestresed_tendons( + a_ct: float, + s_steel: float, + fct_eff: float, + k: float, + kc: float, + ap: float, + d_steel: float, + d_press: float, + e: float, + incr_stress: float, +) -> float: + """Computes the minimum area of reinforcing steel within the tensile zone + for control of cracking areas in addition with bonded tendons + + EUROCODE 2 1992-1-1:2004, Eq. (7.1) + + Args: + a_ct (float): is the area of concrete within the tensile zone in mm2. + The tensile zone is that part of the section which is calculated + to be in tension just before the formation of the first crack. + s_steel (float): is the absolute value of the maximum stress in MPa + permitted in the reinforcement immediately after the formation + of the crack. This may be taken as theyield strength of the + reinforcement, fyk. A lower value may, however, be needed to + satisfy the crack width limits according to the maximum + bar size of spacing (see 7.3.3 (2)). + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + k (float): is the coefficient which allow for the effect of + non-uniform self-equilibrating stresses, which lead to a + reduction of restraint forces. Use 'k_crack_min_steel_area' + to compute it + k=1 for webs w<=300mm or flanges widths less than 300mm + k=0.65 for webs w>=800mm or flanges with widths greater than 800mm + Intermediate values may be interpolated. + kc (float): is a coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm. + ac_eff (float): is the effective area in mm2 of concrete in tension + surrounding or prestressing tendons if depth hc,ef + ap (float): is the area in mm2 of pre or post-tensioned tendons + within ac_eff + d_steel (float): largest bar diameter in mm of reinforcing steel. + Equal to zero if only prestressing is used in control cracking + d_press (float): equivalent diameter in mm of tendon acoording + to 6.8.2 + e (float): ratio of bond strength of prestressing and reinforcing + steel, according to Table 6.2 in 6.8.2 + incr_stress (float): stress variation in MPa in prestressing tendons + from the state of zero strain of the concrete at the same level + + Returns: + float: the minimm area of reinforcing steel within the tensile + zone in mm2. + + Raises: + ValueError: if k value is not between 0.65 and 1 or kc is not + larger than 0 and lower than 1. If diameters d_steel or + d_press are lower than 0. If ratio of bond strength e + is less than 0 or larger than 1. If area of tendons ac_eff + is less than 0. Is stress variation incr_stress is less than 0 + """ + as_min = crack_min_steel_area(a_ct, s_steel, fct_eff, k, kc) + + if d_press < 0: + raise ValueError(f'd_press={d_press} cannot be less than 0') + if d_steel < 0: + raise ValueError(f'd_steel={d_steel} cannot be less than 0') + if ap < 0: + raise ValueError(f'ap={ap} cannot be less than 0') + if incr_stress < 0: + raise ValueError(f'incr_stress={incr_stress} cannot be less than 0') + + e1 = d_steel > 0 if (e * d_steel / d_press) ** 0.5 else e**0.5 + f = e1 * ap * incr_stress + return as_min * f diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py new file mode 100644 index 00000000..2b49e3d9 --- /dev/null +++ b/tests/test_ec2_2004_crack_control.py @@ -0,0 +1,150 @@ +"""Tests for EUROCODE 2-1-1:2004 Chapter 7.3 Crack Control""" +import math + +import pytest +from structuralcodes.codes.ec2_2004 import _crack_control + + +@pytest.mark.parametrize( + 'test_exposure_class, test_load_combination, expected', + [ + ('X0', 'f', 0.2), + ('x0', 'F', 0.2), + ('X0', 'qp', 0.4), + ('x0', 'QP', 0.4), + ('XC2', 'f', 0.2), + ('xc2', 'F', 0.2), + ('XC3', 'f', 0.2), + ('xc3', 'F', 0.2), + ('XC4', 'f', 0.2), + ('xc4', 'F', 0.2), + ('XC2', 'qp', 0.3), + ('xc2', 'QP', 0.3), + ('XC3', 'qp', 0.3), + ('xc3', 'QP', 0.3), + ('XC4', 'qp', 0.3), + ('xc4', 'QP', 0.3), + ('XD1', 'qp', 0.3), + ('xd1', 'QP', 0.3), + ('XD2', 'qp', 0.3), + ('xd2', 'QP', 0.3), + ('XS1', 'qp', 0.3), + ('xs1', 'QP', 0.3), + ('XS2', 'qp', 0.3), + ('xs2', 'QP', 0.3), + ('XS3', 'qp', 0.3), + ('xs3', 'QP', 0.3), + ], +) +def test_w_max_returns_expected_values( + test_exposure_class, test_load_combination, expected +): + """Test that the w_max function returns expected values""" + w_max = _crack_control.w_max(test_exposure_class, test_load_combination) + assert w_max == expected + + +@pytest.mark.parametrize( + 'test_exposure_class, test_load_combination', + [('dummy1', 'f'), ('dummy2', 'qp'), ('XD1', 'dummy3'), ('XS1', 'dummy4')], +) +def test_w_max_not_valid_input_raises_valueerror( + test_exposure_class, test_load_combination +): + """Test that not valid input returns ValueError""" + with pytest.raises(ValueError): + _crack_control.w_max(test_exposure_class, test_load_combination) + + +@pytest.mark.parametrize( + 'h, expected', + [ + (200, 1), + (300, 1), + (800, 0.65), + (1000, 0.65), + (400, 0.93), + (500, 0.86), + (600, 0.79), + (700, 0.72), + ], +) +def test_k_crack_min_steel_area_returns_expected_values(h, expected): + """Test the k_crack_min_steel_area function""" + k = _crack_control.k_crack_min_steel_area(h) + assert math.isclose(k, expected) + + +def test_k_crack_min_steel_area_raises_valueerror(): + """Test that not valid input returns ValueError exeption""" + with pytest.raises(ValueError): + h = -100 + _crack_control.k_crack_min_steel_area(h) + + +def test_kc_crack_min_steel_area_pure_tension_returns_expected_values(): + """Test the kc_crack_min_steel_area_pure_tension function""" + assert 1 == _crack_control.kc_crack_min_steel_area_pure_tension() + + +@pytest.mark.parametrize( + 'h, b, fct_eff, n_ed, expected', + [ + (600, 400, 3, 20, 0.3925926), + (600, 400, 3, -20, 0.4166667), + (400, 200, 4, 3, 0.397500), + (200, 50, 5, -80, 1), + (200, 50, 5, 80, 0), + ], +) +def test_kc_crack_min_steel_area_rectangular_returns_expected_values( + h, b, fct_eff, n_ed, expected +): + """Test the kc_crack_min_steel_area_rectangular""" + kc = _crack_control.kc_crack_min_steel_area_rectangular( + h, b, fct_eff, n_ed + ) + assert math.isclose(kc, expected, rel_tol=0.000001) + + +def test_kc_crack_min_steel_area_rectangular_rasies_valueerror(): + """Test the kc_crack_min_steel_area_rectangular raises Value + Error for not correct input values for b and h""" + with pytest.raises(ValueError): + _crack_control.kc_crack_min_steel_area_rectangular( + h=-100, b=100, fct_eff=100, n_ed=10 + ) + _crack_control.kc_crack_min_steel_area_rectangular( + h=100, b=-100, fct_eff=100, n_ed=10 + ) + + +@pytest.mark.parametrize( + 'f_cr, a_ct, fct_eff, expected', + [ + (30, 10000, 5, 0.54), + (20, 5000, 3, 1.2), + (55, 7500, 4, 1.65), + (55, 50000, 4, 0.5), + ], +) +def test_kc_crack_min_steel_area_flanges(f_cr, a_ct, fct_eff, expected): + """Test the kc_crack_min_steel_area_flanges function""" + kc = _crack_control.kc_crack_min_steel_area_flanges(f_cr, a_ct, fct_eff) + assert math.isclose(kc, expected, rel_tol=0.000001) + + +@pytest.mark.parametrize( + 'a_ct, s_steel, fct_eff, k, kc, expected', + [ + (10000, 500, 3, 1, 1, 60), + (80000, 500, 5, 0.65, 0.5, 260), + (80000, 400, 4, 0.9, 0.75, 540), + ], +) +def test_crack_min_steel_area_returns_expected_values( + a_ct, s_steel, fct_eff, k, kc, expected +): + """Test the crack_min_steel_area returns expected values""" + as_min = _crack_control.crack_min_steel_area(a_ct, s_steel, fct_eff, k, kc) + assert math.isclose(as_min, expected, rel_tol=0.000001) From 59a04f5e92b7684fdfb6d1c58a186f3c6b2c55c0 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Tue, 27 Dec 2022 11:11:12 +0100 Subject: [PATCH 04/28] raise ValueError test functions for min area --- .../codes/ec2_2004/_crack_control.py | 5 +++- tests/test_ec2_2004_crack_control.py | 24 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index 76b1e988..251c78c2 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -95,9 +95,12 @@ def crack_min_steel_area( ValueError: if k value is not between 0.65 and 1 or kc is not larger than 0 and lower than 1. """ - s_steel = abs(s_steel) fct_eff = abs(fct_eff) + if a_ct <= 0: + raise ValueError(f'a_ct={a_ct} must be larger than 0') + if s_steel < 0: + raise ValueError(f's_steel={s_steel} must be equal or larger than 0') if k < 0.65 or k > 1.0: raise ValueError(f'k={k} must be between 0.65 and 1') if kc > 1 or kc < 0: diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py index 2b49e3d9..802f7bfd 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_crack_control.py @@ -107,7 +107,7 @@ def test_kc_crack_min_steel_area_rectangular_returns_expected_values( assert math.isclose(kc, expected, rel_tol=0.000001) -def test_kc_crack_min_steel_area_rectangular_rasies_valueerror(): +def test_kc_crack_min_steel_area_rectangular_raises_valueerror(): """Test the kc_crack_min_steel_area_rectangular raises Value Error for not correct input values for b and h""" with pytest.raises(ValueError): @@ -148,3 +148,25 @@ def test_crack_min_steel_area_returns_expected_values( """Test the crack_min_steel_area returns expected values""" as_min = _crack_control.crack_min_steel_area(a_ct, s_steel, fct_eff, k, kc) assert math.isclose(as_min, expected, rel_tol=0.000001) + + +@pytest.mark.parametrize( + 'a_ct, s_steel, fct_eff, k, kc', + [ + (-10000, 100, 3, 0.7, 0.67), + (10000, -100, 3, 0.7, 0.65), + (10000, 100, 3, 0.5, 0.65), + (10000, 100, 3, 1.1, 0.65), + (10000, 100, 3, 0.7, -0.1), + (10000, 100, 3, 0.7, 1.1), + ], +) +def test_crack_min_steel_area_raises_valueerror(a_ct, s_steel, fct_eff, k, kc): + """Test the crack_min_steel_area raises value error""" + with pytest.raises(ValueError): + _crack_control.crack_min_steel_area(a_ct, s_steel, fct_eff, k, kc) + + +def test_crack_min_steel_area_with_prestressed_tendons_returns_expected_values(): + """Test the crack_min_steel_area returns expected values""" + pass From b7167aa4b462253002c3581c579180d1488ce4ef Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 12 Jan 2023 09:57:48 +0100 Subject: [PATCH 05/28] crack_min_steel_without_direct_calculation --- requirements.txt | 2 + .../codes/ec2_2004/_crack_control.py | 156 +++++++++++++++++- tests/test_ec2_2004_crack_control.py | 84 +++++++++- 3 files changed, 232 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index e69de29b..80ba09bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy==1.23.5 +scipy==1.9.3 diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index 251c78c2..b3a2ba53 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -1,5 +1,6 @@ """Collection of functions from EUROCODE 1992-1-1:2004 Chapter 7.3 - Crack control""" +import numpy as np import scipy.interpolate @@ -270,7 +271,7 @@ def crack_min_steel_area_with_prestresed_tendons( ap (float): is the area in mm2 of pre or post-tensioned tendons within ac_eff d_steel (float): largest bar diameter in mm of reinforcing steel. - Equal to zero if only prestressing is used in control cracking + Equal to 0 if only prestressing is used in control cracking d_press (float): equivalent diameter in mm of tendon acoording to 6.8.2 e (float): ratio of bond strength of prestressing and reinforcing @@ -289,9 +290,9 @@ def crack_min_steel_area_with_prestresed_tendons( is less than 0 or larger than 1. If area of tendons ac_eff is less than 0. Is stress variation incr_stress is less than 0 """ - as_min = crack_min_steel_area(a_ct, s_steel, fct_eff, k, kc) + fct_eff = abs(fct_eff) - if d_press < 0: + if d_press <= 0: raise ValueError(f'd_press={d_press} cannot be less than 0') if d_steel < 0: raise ValueError(f'd_steel={d_steel} cannot be less than 0') @@ -299,7 +300,150 @@ def crack_min_steel_area_with_prestresed_tendons( raise ValueError(f'ap={ap} cannot be less than 0') if incr_stress < 0: raise ValueError(f'incr_stress={incr_stress} cannot be less than 0') + if e < 0.15: + raise ValueError(f'The minimum value for e={e} is 0.15') + if e > 0.8: + raise ValueError(f'The maximum value for e={e} is 0.8') + if a_ct <= 0: + raise ValueError(f'a_ct={a_ct} must be larger than 0') + if s_steel < 0: + raise ValueError(f's_steel={s_steel} must be equal or larger than 0') + if k < 0.65 or k > 1.0: + raise ValueError(f'k={k} must be between 0.65 and 1') + if kc > 1 or kc < 0: + raise ValueError(f'kc={kc} must be lower than 1 and larger than 0') + + a1 = kc * k * fct_eff * a_ct + e1 = ((e * d_steel / d_press) ** 0.5) if d_steel > 0 else e**0.5 + a2 = e1 * ap * incr_stress + a = a1 - a2 + + return a / s_steel + + +def crack_min_steel_without_direct_calculation( + wk: float, + s_steel: float, + fct_eff: float, + kc: float, + h_cr: float, + h: float, + d: float, + incr_stress: float = 0, +) -> tuple(float, float): + """Computes the minimum area of reinforcing steel within the tensile zone + for control of cracking areas + + EUROCODE 2 1992-1-1:2004, Table (7.2N), Table (7.3N) + + Args: + wk (float): the characteristic crack width value in mm. + s_steel (float): the steel stress value in MPa under the relevant + combination of actions. + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + kc (float): is a coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm. + h_cr (float): is the depth of the tensile zone immediately prior to + cracking, considering the characteristic values of prestress and + axial forces under the quasi-permanent combination of actions. + h (float): the overall depth of the section in mm. + d (float): is the effective depth to the centroid of the outer layer + of the reinforcement. + incr_stress (float, optional): value of prestressed stress in MPa if + applicable + + Returns: + tuple(float, float): with the value of the maximum bar diameters in mm + in the first position and the maximum bar spacing in mm in the + second position + Raises: + ValueError: if wk, fct_eff, h_cr, h or d are less than 0 + ValueError: if kc is not between 0 and 1 + """ + if wk < 0: + raise ValueError(f'wd={wk} cannot be less than 0') + if fct_eff < 0: + raise ValueError(f'fct_eff={fct_eff} is less than 0') + if h_cr < 0: + raise ValueError(f'h_cr={h_cr} is less than 0') + if h < 0: + raise ValueError(f'h={h} is less than 0') + if d < 0: + raise ValueError(f'd={d} is less than 0') + if kc < 0 or kc > 1: + raise ValueError(f'kc={kc} is not between 0 and 1') + + s = s_steel - incr_stress + if s <= 0: + return (0, 0) + + x = (0.4, 0.3, 0.2) + y_phi = (160, 200, 240, 280, 320, 360, 400, 450) + y_spa = (160, 200, 240, 280, 320, 360) + phi_s_v = ( + 40, + 32, + 25, + 32, + 25, + 16, + 20, + 16, + 12, + 16, + 12, + 8, + 12, + 10, + 6, + 10, + 8, + 5, + 8, + 6, + 4, + 6, + 5, + None, + ) + spa_v = ( + 300, + 300, + 200, + 300, + 250, + 150, + 250, + 200, + 100, + 200, + 150, + 50, + 150, + 100, + None, + 100, + 50, + None, + ) + + points_phi = np.meshgrid(x, y_phi) + points_spa = np.meshgrid(x, y_spa) + xi = (wk, s) + + phi_grid = scipy.interpolate.griddata( + points_phi, phi_s_v, xi, method='linear' + ) + phi_star = phi_grid[0] + phi = phi_star * (fct_eff / 2.9) * kc * h_cr / (2 * (h - d)) + + spa_grid = scipy.interpolate.griddata( + points_spa, spa_v, xi, method='linear' + ) + spa = spa_grid[0] - e1 = d_steel > 0 if (e * d_steel / d_press) ** 0.5 else e**0.5 - f = e1 * ap * incr_stress - return as_min * f + return (phi, spa) diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py index 802f7bfd..39fa0f98 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_crack_control.py @@ -102,7 +102,10 @@ def test_kc_crack_min_steel_area_rectangular_returns_expected_values( ): """Test the kc_crack_min_steel_area_rectangular""" kc = _crack_control.kc_crack_min_steel_area_rectangular( - h, b, fct_eff, n_ed + h, + b, + fct_eff, + n_ed, ) assert math.isclose(kc, expected, rel_tol=0.000001) @@ -147,7 +150,7 @@ def test_crack_min_steel_area_returns_expected_values( ): """Test the crack_min_steel_area returns expected values""" as_min = _crack_control.crack_min_steel_area(a_ct, s_steel, fct_eff, k, kc) - assert math.isclose(as_min, expected, rel_tol=0.000001) + assert math.isclose(as_min, expected, rel_tol=10e-6) @pytest.mark.parametrize( @@ -167,6 +170,79 @@ def test_crack_min_steel_area_raises_valueerror(a_ct, s_steel, fct_eff, k, kc): _crack_control.crack_min_steel_area(a_ct, s_steel, fct_eff, k, kc) -def test_crack_min_steel_area_with_prestressed_tendons_returns_expected_values(): +@pytest.mark.parametrize( + ( + 'a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, ' + ' incr_stress, expected' + ), + [ + (80000, 400, 4, 0.9, 0.75, 500, 10, 10, 0.5, 10, 531.161), + (50000, 500, 3, 0.7, 0.4, 700, 10, 30, 0.8, 20, 69.541), + (50000, 500, 4, 1, 1, 1000, 0, 20, 0.8, 20, 364.223), + ], +) +def test_crack_min_steel_area_with_press_tendons_returns_expected_values( + a_ct, + s_steel, + fct_eff, + k, + kc, + ap, + d_steel, + d_press, + e, + incr_stress, + expected, +): """Test the crack_min_steel_area returns expected values""" - pass + as_min = _crack_control.crack_min_steel_area_with_prestresed_tendons( + a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, incr_stress + ) + assert math.isclose(as_min, expected, rel_tol=10e-6) + + +@pytest.mark.parametrize( + 'a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, incr_stress', + [ + (-80000, 400, 4, 0.9, 0.75, 500, 10, 10, 0.5, 10), + (80000, -400, 4, 0.9, 0.75, 500, 10, 10, 0.5, 10), + (80000, 400, 4, 0.5, 0.75, 500, 10, 10, 0.5, 10), + (80000, 400, 4, 1.1, 0.75, 500, 10, 10, 0.5, 10), + (80000, 400, 4, 0.9, -0.1, 500, 10, 10, 0.5, 10), + (80000, 400, 4, 0.9, 1.1, 500, 10, 10, 0.5, 10), + (80000, 400, 4, 0.9, 0.75, -500, 10, 10, 0.5, 10), + (80000, 400, 4, 0.9, 0.75, 500, -10, 10, 0.5, 10), + (80000, 400, 4, 0.9, 0.75, 500, 10, 0, 0.5, 10), + (80000, 400, 4, 0.9, 0.75, 500, 10, 10, 0.1, 10), + (80000, 400, 4, 0.9, 0.75, 500, 10, 10, 0.9, 10), + ], +) +def test_crack_min_steel_area_with_press_tendons_raise_valueerror( + a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, incr_stress +): + """Test the crack_min_steel_area raise ValueError for non valid values""" + with pytest.raises(ValueError): + _crack_control.crack_min_steel_area_with_prestresed_tendons( + a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, incr_stress + ) + + +@pytest.mark.parametrize( + 'wk, s_steel, fct_eff, kc, h_cr, h, d, incr_stress', + [ + (-0.1, 200, 3, 0.7, 250, 300, 280, 0), + (0.2, 200, -3, 0.7, 250, 300, 280, 0), + (0.2, 200, 3, 1.1, 250, 300, 280, 0), + (0.2, 200, 3, 0.7, -250, 300, 280, 0), + (0.2, 200, 3, 0.7, -250, -300, 280, 0), + (0.2, 200, 3, 0.7, -250, -300, -280, 0), + ], +) +def test_crack_min_steel_without_direct_calculation_raise_valueerror( + wk, s_steel, fct_eff, kc, h_cr, h, d, incr_stress +): + """Test the crack_min_steel_area raise ValueError for non valid values""" + with pytest.raises(ValueError): + _crack_control.crack_min_steel_without_direct_calculation( + wk, s_steel, fct_eff, kc, h_cr, h, d, incr_stress + ) From 7189d31c244aafbb3f599def76d85d94fb1e4744 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 12 Jan 2023 11:08:41 +0100 Subject: [PATCH 06/28] Commit --- .../codes/ec2_2004/_crack_control.py | 42 ++++++++++----- tests/test_ec2_2004_crack_control.py | 54 +++++++++++++++---- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index b3a2ba53..d96da5d3 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -1,5 +1,8 @@ """Collection of functions from EUROCODE 1992-1-1:2004 Chapter 7.3 - Crack control""" +import math +import typing as t + import numpy as np import scipy.interpolate @@ -329,8 +332,9 @@ def crack_min_steel_without_direct_calculation( h_cr: float, h: float, d: float, + load_type: str, incr_stress: float = 0, -) -> tuple(float, float): +) -> t.Tuple[float, float]: """Computes the minimum area of reinforcing steel within the tensile zone for control of cracking areas @@ -353,6 +357,9 @@ def crack_min_steel_without_direct_calculation( h (float): the overall depth of the section in mm. d (float): is the effective depth to the centroid of the outer layer of the reinforcement. + load_type (str): load combination type: + - bending: for at least part of section in compression + - tension: uniform axial tension incr_stress (float, optional): value of prestressed stress in MPa if applicable @@ -363,6 +370,7 @@ def crack_min_steel_without_direct_calculation( Raises: ValueError: if wk, fct_eff, h_cr, h or d are less than 0 ValueError: if kc is not between 0 and 1 + ValueError: if combination of wk and stress values are out of scope """ if wk < 0: raise ValueError(f'wd={wk} cannot be less than 0') @@ -376,6 +384,12 @@ def crack_min_steel_without_direct_calculation( raise ValueError(f'd={d} is less than 0') if kc < 0 or kc > 1: raise ValueError(f'kc={kc} is not between 0 and 1') + load_type = load_type.lower() + if load_type != 'bending' and load_type != 'tension': + raise ValueError( + f'load_type={load_type} can only have as values "bending" or' + ' "tension"' + ) s = s_steel - incr_stress if s <= 0: @@ -431,19 +445,23 @@ def crack_min_steel_without_direct_calculation( None, ) - points_phi = np.meshgrid(x, y_phi) - points_spa = np.meshgrid(x, y_spa) - xi = (wk, s) + points_phi = np.array(np.meshgrid(y_phi, x)).T.reshape(-1, 2) + points_spa = np.array(np.meshgrid(y_spa, x)).T.reshape(-1, 2) + xi = (s, wk) - phi_grid = scipy.interpolate.griddata( - points_phi, phi_s_v, xi, method='linear' + phi_star = float( + scipy.interpolate.griddata(points_phi, phi_s_v, xi, method='linear') ) - phi_star = phi_grid[0] - phi = phi_star * (fct_eff / 2.9) * kc * h_cr / (2 * (h - d)) + if load_type == 'bending': + phi = phi_star * (fct_eff / 2.9) * kc * h_cr / (2 * (h - d)) + else: + phi = phi_star * (fct_eff / 2.9) * h_cr / (8 * (h - d)) - spa_grid = scipy.interpolate.griddata( - points_spa, spa_v, xi, method='linear' + spa = float( + scipy.interpolate.griddata(points_spa, spa_v, xi, method='linear') ) - spa = spa_grid[0] - return (phi, spa) + if math.isnan(phi) or math.isnan(spa): + raise ValueError('Combination of wk or stress values out of scope') + + return phi, spa diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py index 39fa0f98..894b0a32 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_crack_control.py @@ -228,21 +228,57 @@ def test_crack_min_steel_area_with_press_tendons_raise_valueerror( @pytest.mark.parametrize( - 'wk, s_steel, fct_eff, kc, h_cr, h, d, incr_stress', + ( + 'wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress, exp_phi,' + ' exp_sep' + ), + [ + (0.3, 240, 2.9, 0.4, 200, 400, 360, 'bending', 40, 25, 250), + (0.2, 260, 2.9, 0.4, 200, 400, 360, 'axial', 40, 14, 125), + (0.35, 360, 2.9, 0.4, 200, 400, 360, 'bending', 40, 11, 125), + (0.35, 360, 2.9, 0.4, 200, 400, 360, 'axial', 40, 11, 125), + ], +) +def test_crack_min_steel_without_direct_calculation_returns_expected_values( + wk, + s_steel, + fct_eff, + kc, + h_cr, + h, + d, + load_type, + incr_stress, + exp_phi, + exp_sep, +): + """Test the crack_min_steel_area raise ValueError for non valid values""" + phi, sep = _crack_control.crack_min_steel_without_direct_calculation( + wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress + ) + assert math.isclose(phi, exp_phi, rel_tol=10e-6) + assert math.isclose(sep, exp_sep, rel_tol=10e-6) + + +@pytest.mark.parametrize( + 'wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress', [ - (-0.1, 200, 3, 0.7, 250, 300, 280, 0), - (0.2, 200, -3, 0.7, 250, 300, 280, 0), - (0.2, 200, 3, 1.1, 250, 300, 280, 0), - (0.2, 200, 3, 0.7, -250, 300, 280, 0), - (0.2, 200, 3, 0.7, -250, -300, 280, 0), - (0.2, 200, 3, 0.7, -250, -300, -280, 0), + (-0.1, 200, 3, 0.7, 250, 300, 280, 'bending', 0), + (0.2, 200, -3, 0.7, 250, 300, 280, 'bending', 0), + (0.2, 200, 3, 0.7, 250, 300, 280, 'bending', 0), + (0.2, 200, 3, 1.1, 250, 300, 280, 'bending', 0), + (0.2, 200, 3, 0.7, -250, 300, 280, 'bending', 0), + (0.2, 200, 3, 0.7, -250, -300, 280, 'bending', 0), + (0.2, 200, 3, 0.7, -250, -300, -280, 'bending', 0), + (0.2, 360, 2.9, 0.4, 200, 400, 360, 'bending', 0), + (0.5, 200, 2.9, 0.4, 200, 400, 360, 'bending', 0), ], ) def test_crack_min_steel_without_direct_calculation_raise_valueerror( - wk, s_steel, fct_eff, kc, h_cr, h, d, incr_stress + wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress ): """Test the crack_min_steel_area raise ValueError for non valid values""" with pytest.raises(ValueError): _crack_control.crack_min_steel_without_direct_calculation( - wk, s_steel, fct_eff, kc, h_cr, h, d, incr_stress + wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress ) From 4a0fcfbb446369218e3ef578c214fd4ad70b064c Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 12 Jan 2023 11:28:57 +0100 Subject: [PATCH 07/28] crack without direct calculation tests --- requirements.txt | 2 - .../codes/ec2_2004/_crack_control.py | 23 ++++------- tests/test_ec2_2004_crack_control.py | 41 ++++++++----------- 3 files changed, 25 insertions(+), 41 deletions(-) diff --git a/requirements.txt b/requirements.txt index 80ba09bb..e69de29b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +0,0 @@ -numpy==1.23.5 -scipy==1.9.3 diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index d96da5d3..a78f572e 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -328,12 +328,11 @@ def crack_min_steel_without_direct_calculation( wk: float, s_steel: float, fct_eff: float, - kc: float, h_cr: float, h: float, d: float, - load_type: str, incr_stress: float = 0, + kc: t.Optional[float] = None, ) -> t.Tuple[float, float]: """Computes the minimum area of reinforcing steel within the tensile zone for control of cracking areas @@ -348,20 +347,18 @@ def crack_min_steel_without_direct_calculation( the concrete effective at the time when the cracks may first be expected to occur: fct,eff=fct or lower (fct(t)), is cracking is expected earlier than 28 days. - kc (float): is a coefficient which takes account of the stress - distribution within the section immediately prior to cracking and - the change of the lever arm. h_cr (float): is the depth of the tensile zone immediately prior to cracking, considering the characteristic values of prestress and axial forces under the quasi-permanent combination of actions. h (float): the overall depth of the section in mm. d (float): is the effective depth to the centroid of the outer layer of the reinforcement. - load_type (str): load combination type: - - bending: for at least part of section in compression - - tension: uniform axial tension incr_stress (float, optional): value of prestressed stress in MPa if applicable + kc (float, optional): is a coefficient which takes account of the + stress distribution within the section immediately prior to + cracking and the change of the lever arm in a bending section. + 'None' for pure tensile uniform axial section. Returns: tuple(float, float): with the value of the maximum bar diameters in mm @@ -382,14 +379,8 @@ def crack_min_steel_without_direct_calculation( raise ValueError(f'h={h} is less than 0') if d < 0: raise ValueError(f'd={d} is less than 0') - if kc < 0 or kc > 1: + if kc is not None and (kc < 0 or kc > 1): raise ValueError(f'kc={kc} is not between 0 and 1') - load_type = load_type.lower() - if load_type != 'bending' and load_type != 'tension': - raise ValueError( - f'load_type={load_type} can only have as values "bending" or' - ' "tension"' - ) s = s_steel - incr_stress if s <= 0: @@ -452,7 +443,7 @@ def crack_min_steel_without_direct_calculation( phi_star = float( scipy.interpolate.griddata(points_phi, phi_s_v, xi, method='linear') ) - if load_type == 'bending': + if kc is not None: phi = phi_star * (fct_eff / 2.9) * kc * h_cr / (2 * (h - d)) else: phi = phi_star * (fct_eff / 2.9) * h_cr / (8 * (h - d)) diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py index 894b0a32..06c23820 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_crack_control.py @@ -228,57 +228,52 @@ def test_crack_min_steel_area_with_press_tendons_raise_valueerror( @pytest.mark.parametrize( - ( - 'wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress, exp_phi,' - ' exp_sep' - ), + 'wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc, exp_phi, exp_sep', [ - (0.3, 240, 2.9, 0.4, 200, 400, 360, 'bending', 40, 25, 250), - (0.2, 260, 2.9, 0.4, 200, 400, 360, 'axial', 40, 14, 125), - (0.35, 360, 2.9, 0.4, 200, 400, 360, 'bending', 40, 11, 125), - (0.35, 360, 2.9, 0.4, 200, 400, 360, 'axial', 40, 11, 125), + (0.3, 240, 2.9, 200, 400, 360, 40, 0.4, 25, 250), + (0.2, 260, 2.9, 200, 400, 360, 40, None, 8.75, 125), + (0.35, 360, 2.9, 200, 400, 360, 40, 0.4, 11, 125), + (0.35, 360, 2.9, 200, 400, 360, 40, None, 6.875, 125), ], ) def test_crack_min_steel_without_direct_calculation_returns_expected_values( wk, s_steel, fct_eff, - kc, h_cr, h, d, - load_type, incr_stress, + kc, exp_phi, exp_sep, ): """Test the crack_min_steel_area raise ValueError for non valid values""" phi, sep = _crack_control.crack_min_steel_without_direct_calculation( - wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress + wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc ) assert math.isclose(phi, exp_phi, rel_tol=10e-6) assert math.isclose(sep, exp_sep, rel_tol=10e-6) @pytest.mark.parametrize( - 'wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress', + 'wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc', [ - (-0.1, 200, 3, 0.7, 250, 300, 280, 'bending', 0), - (0.2, 200, -3, 0.7, 250, 300, 280, 'bending', 0), - (0.2, 200, 3, 0.7, 250, 300, 280, 'bending', 0), - (0.2, 200, 3, 1.1, 250, 300, 280, 'bending', 0), - (0.2, 200, 3, 0.7, -250, 300, 280, 'bending', 0), - (0.2, 200, 3, 0.7, -250, -300, 280, 'bending', 0), - (0.2, 200, 3, 0.7, -250, -300, -280, 'bending', 0), - (0.2, 360, 2.9, 0.4, 200, 400, 360, 'bending', 0), - (0.5, 200, 2.9, 0.4, 200, 400, 360, 'bending', 0), + (-0.1, 200, 3, 250, 300, 280, 0, 0.7), + (0.2, 200, -3, 250, 300, 280, 0, 0.7), + (0.2, 200, 3, 250, 300, 280, 0, 1.1), + (0.2, 200, 3, -250, 300, 280, 0, 0.7), + (0.2, 200, 3, -250, -300, 280, 0, 0.7), + (0.2, 200, 3, -250, -300, -280, 0, 0.7), + (0.2, 360, 2.9, 200, 400, 360, 0, 0.4), + (0.5, 200, 2.9, 200, 400, 360, 0, 0.4), ], ) def test_crack_min_steel_without_direct_calculation_raise_valueerror( - wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress + wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc ): """Test the crack_min_steel_area raise ValueError for non valid values""" with pytest.raises(ValueError): _crack_control.crack_min_steel_without_direct_calculation( - wk, s_steel, fct_eff, kc, h_cr, h, d, load_type, incr_stress + wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc ) From 84c0140cec46e29754ada8f328c1bf427e7b1c97 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 12 Jan 2023 12:39:12 +0100 Subject: [PATCH 08/28] adjusted bond strength --- .../codes/ec2_2004/_crack_control.py | 46 +++++++++++++++---- tests/test_ec2_2004_crack_control.py | 35 ++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index a78f572e..903ced7d 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -228,6 +228,40 @@ def kc_crack_min_steel_area_flanges( return max(0.9 * f_cr * 1000 / a_ct / fct_eff, 0.5) +def adjusted_bond_strength(e: float, d_press: float, d_steel: float) -> float: + """Computes the adjusted ratio of bond strength taking into account + the different diameters of prestressing and reinforcing steel. + + Args: + e (float): ratio of bond strength of prestressing and reinforcing + steel, according to Table 6.2 in 6.8.2 + d_steel (float): largest bar diameter in mm of reinforcing steel. + Equal to 0 if only prestressing is used in control cracking + d_press (float): equivalent diameter in mm of tendon acoording + to 6.8.2 + + Returns: + float: with the value of the ratio + + Raises: + ValueError: if diameters d_steel or d_press are lower than 0. + If ratio of bond strength e is less than 0.15 or larger than 0.8. + If area of tendons ac_eff is less than 0. Is stress variation + incr_stress is less than 0. + """ + + if d_press <= 0: + raise ValueError(f'd_press={d_press} cannot be less than 0') + if d_steel < 0: + raise ValueError(f'd_steel={d_steel} cannot be less than 0') + if e < 0.15: + raise ValueError(f'The minimum value for e={e} is 0.15') + if e > 0.8: + raise ValueError(f'The maximum value for e={e} is 0.8') + + return ((e * d_steel / d_press) ** 0.5) if d_steel > 0 else e**0.5 + + def crack_min_steel_area_with_prestresed_tendons( a_ct: float, s_steel: float, @@ -290,23 +324,15 @@ def crack_min_steel_area_with_prestresed_tendons( ValueError: if k value is not between 0.65 and 1 or kc is not larger than 0 and lower than 1. If diameters d_steel or d_press are lower than 0. If ratio of bond strength e - is less than 0 or larger than 1. If area of tendons ac_eff + is less than 0.15 or larger than 0.8. If area of tendons ac_eff is less than 0. Is stress variation incr_stress is less than 0 """ fct_eff = abs(fct_eff) - if d_press <= 0: - raise ValueError(f'd_press={d_press} cannot be less than 0') - if d_steel < 0: - raise ValueError(f'd_steel={d_steel} cannot be less than 0') if ap < 0: raise ValueError(f'ap={ap} cannot be less than 0') if incr_stress < 0: raise ValueError(f'incr_stress={incr_stress} cannot be less than 0') - if e < 0.15: - raise ValueError(f'The minimum value for e={e} is 0.15') - if e > 0.8: - raise ValueError(f'The maximum value for e={e} is 0.8') if a_ct <= 0: raise ValueError(f'a_ct={a_ct} must be larger than 0') if s_steel < 0: @@ -317,7 +343,7 @@ def crack_min_steel_area_with_prestresed_tendons( raise ValueError(f'kc={kc} must be lower than 1 and larger than 0') a1 = kc * k * fct_eff * a_ct - e1 = ((e * d_steel / d_press) ** 0.5) if d_steel > 0 else e**0.5 + e1 = adjusted_bond_strength(e, d_press, d_steel) a2 = e1 * ap * incr_stress a = a1 - a2 diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py index 06c23820..12c07ce3 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_crack_control.py @@ -277,3 +277,38 @@ def test_crack_min_steel_without_direct_calculation_raise_valueerror( _crack_control.crack_min_steel_without_direct_calculation( wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc ) + + +@pytest.mark.parametrize( + 'e, d_press, d_steel, expected', + [ + (0.8, 20, 0, 0.894427), + (0.6, 25, 10, 0.489898), + (0.5, 10, 10, 0.707107), + ], +) +def test_adjusted_bond_length_return_expected_values( + e, d_press, d_steel, expected +): + """Test the adjusted_bond_length_function returns expected values""" + assert math.isclose( + _crack_control.adjusted_bond_strength(e, d_press, d_steel), + expected, + rel_tol=10e-5, + ) + + +@pytest.mark.parametrize( + 'e, d_press, d_steel', + [ + (0.1, 20, 0), + (-2, 25, 10), + (1.15, 10, 10), + (0.6, -10, 10), + (0.6, 10, -10), + ], +) +def test_adjusted_bond_length_raise_valuerror(e, d_press, d_steel): + """Test the adjusted_bond_length_function raises exceptions""" + with pytest.raises(ValueError): + _crack_control.adjusted_bond_strength(e, d_press, d_steel) From e0f1baac8887cfe4a6386924c648a25453bcfa57 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 12 Jan 2023 13:01:54 +0100 Subject: [PATCH 09/28] hc_eff_concrete_tension formulation and testing --- .../codes/ec2_2004/_crack_control.py | 31 +++++++++++++++++ tests/test_ec2_2004_crack_control.py | 33 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index 903ced7d..56d9f79d 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -262,6 +262,37 @@ def adjusted_bond_strength(e: float, d_press: float, d_steel: float) -> float: return ((e * d_steel / d_press) ** 0.5) if d_steel > 0 else e**0.5 +def hc_eff_concrete_tension(h: float, d: float, x: float): + """Returns the effective height of concrete in tension surrounding + the reinforcement or prestressing tendons. + + Args: + h (float): total depth of the element in mm + d (float): distance in mm to the level of the steel centroid + x (float): distance in mm to the zero tensile stress line + + Returns: + float: the effective height in mm + + Raises: + ValueError: if any of h, d or x is lower than zero. + ValueError: if d is greater than h + ValueError: if x is greater than h + """ + if h < 0: + raise ValueError(f'h={h} cannot be less than 0') + if d < 0: + raise ValueError(f'd={d} cannot be less than 0') + if x < 0: + raise ValueError(f'x={x} cannot be less than zero') + if d > h: + raise ValueError(f'd={d} cannot be larger than h={h}') + if x > h: + raise ValueError(f'x={x} cannot be larger than h={h}') + + return min(2.5 * (h - d), (h - x) / 3, h / 2) + + def crack_min_steel_area_with_prestresed_tendons( a_ct: float, s_steel: float, diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py index 12c07ce3..289d8dfd 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_crack_control.py @@ -312,3 +312,36 @@ def test_adjusted_bond_length_raise_valuerror(e, d_press, d_steel): """Test the adjusted_bond_length_function raises exceptions""" with pytest.raises(ValueError): _crack_control.adjusted_bond_strength(e, d_press, d_steel) + + +@pytest.mark.parametrize( + 'h, d, x, expected', + [ + (400, 200, 100, 100), + (400, 200, 150, 83.333333), + (550, 150, 150, 133.33333), + ], +) +def test_hc_eff_concrete_tension_returns_expected_values(h, d, x, expected): + """Test the hc_eff_concrete_tension returns expected results""" + assert math.isclose( + _crack_control.hc_eff_concrete_tension(h, d, x), + expected, + rel_tol=10e-5, + ) + + +@pytest.mark.parametrize( + 'h, d, x', + [ + (-50, 200, 100), + (50, -200, 100), + (50, 200, -100), + (400, 450, 100), + (400, 200, 450) + ], +) +def test_hc_eff_concrete_tension_raise_exceptions(h, d, x): + """Test hc_eff_concrete tension raises expected exceptions""" + with pytest.raises(ValueError): + _crack_control.hc_eff_concrete_tension(h, d, x) From f2cbb4989a7233e685b8dc55e343ac692866b61d Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 12 Jan 2023 13:03:10 +0100 Subject: [PATCH 10/28] requiremets.txt updated --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index e69de29b..1de8c504 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy==1.23.5 +scipy==1.9.3 \ No newline at end of file From 333dcbe11c4b284fee41d8761817fea107885c82 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 12 Jan 2023 14:04:38 +0100 Subject: [PATCH 11/28] rho_p_eff --- .../codes/ec2_2004/_crack_control.py | 61 ++++++++++++++++++- tests/test_ec2_2004_crack_control.py | 59 +++++++++++++++++- 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index 56d9f79d..e80c6c66 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -232,6 +232,8 @@ def adjusted_bond_strength(e: float, d_press: float, d_steel: float) -> float: """Computes the adjusted ratio of bond strength taking into account the different diameters of prestressing and reinforcing steel. + EUROCODE 2 1992-1-1:2004, Eq. (7.5) + Args: e (float): ratio of bond strength of prestressing and reinforcing steel, according to Table 6.2 in 6.8.2 @@ -262,10 +264,12 @@ def adjusted_bond_strength(e: float, d_press: float, d_steel: float) -> float: return ((e * d_steel / d_press) ** 0.5) if d_steel > 0 else e**0.5 -def hc_eff_concrete_tension(h: float, d: float, x: float): +def hc_eff_concrete_tension(h: float, d: float, x: float) -> float: """Returns the effective height of concrete in tension surrounding the reinforcement or prestressing tendons. + EUROCODE 2 1992-1-1:2004, Section (7.3.2-3) + Args: h (float): total depth of the element in mm d (float): distance in mm to the level of the steel centroid @@ -513,3 +517,58 @@ def crack_min_steel_without_direct_calculation( raise ValueError('Combination of wk or stress values out of scope') return phi, spa + + +def alpha_e(es: float, ecm: float) -> float: + """Compute the ratio between the steel and mean concrete + modules. + + EUROCODE 2 1992-1-1:2004, Section 7.3.4-2 + + Args: + es (float): steel elastic modulus in MPa + ecm (float): ecm concrete mean elastic modulus in MPa + + Returns: + float: ratio between modules + Raise: + ValueError: if any of es or ecm is lower than 0. + """ + if es < 0: + raise ValueError(f'es={es} cannot be less than 0') + if ecm < 0: + raise ValueError(f'ecm={ecm} cannot be less than 0') + + return es / ecm + + +def rho_p_eff(a_s: float, e1: float, a_p, ac_eff: float) -> float: + """Effective bond ratio between areas + + EUROCODE 2 1992-1-1:2004, Eq. (7.10) + + Args: + a_s (float): steel area in mm2 + e1 (float): the adjusted ratio of bond according + to expression (7.5) + a_p (float): the area in mm2 of post-tensioned tendons in ac_eff + ac_eff (float): effective area of concrete in tension surrounding + the reinforcement or prestressing tendons of depth hc_eff. + + Returns: + float: with the retio between areas + + + Raise: + ValueError: if any of a_s, e1, a_p or ac_eff is less than 0 + """ + if a_s < 0: + raise ValueError(f'a_s={a_s} cannot be less than 0') + if e1 < 0: + raise ValueError(f'e1={e1} cannot be less than 0') + if a_p < 0: + raise ValueError(f'a_p={a_p} cannot be less than 0') + if ac_eff < 0: + raise ValueError(f'ac_eff={ac_eff} cannot be less than 0') + + return (a_s + e1**2 * a_p) / ac_eff diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py index 289d8dfd..a935db15 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_crack_control.py @@ -338,10 +338,67 @@ def test_hc_eff_concrete_tension_returns_expected_values(h, d, x, expected): (50, -200, 100), (50, 200, -100), (400, 450, 100), - (400, 200, 450) + (400, 200, 450), ], ) def test_hc_eff_concrete_tension_raise_exceptions(h, d, x): """Test hc_eff_concrete tension raises expected exceptions""" with pytest.raises(ValueError): _crack_control.hc_eff_concrete_tension(h, d, x) + + +@pytest.mark.parametrize( + 'es, ecm, expected', + [ + (10e9, 10e5, 1e4), + ], +) +def test_alpha_e_returns_expected_values(es, ecm, expected): + """Test alpha_e returns expected values""" + assert math.isclose( + _crack_control.alpha_e(es, ecm), + expected, + rel_tol=10e-5, + ) + + +@pytest.mark.parametrize( + 'es, ecm', + [ + (-10e9, 10e5), + (100e9, -10e-5), + ], +) +def test_alpha_e_raise_exceptions(es, ecm): + """Test alpha_e raises exceptions""" + with pytest.raises(ValueError): + _crack_control.alpha_e(es, ecm) + + +@pytest.mark.parametrize( + 'a_s, e1, a_p, ac_eff, expected', + [ + (200, 0.8, 125, 600, 0.46666667), + (125, 1.5, 125, 1200, 0.33854), + ], +) +def test_rho_p_eff_returns_expected_values(a_s, e1, a_p, ac_eff, expected): + """Test rho_p_eff returns expeceted values""" + assert math.isclose( + _crack_control.rho_p_eff(a_s, e1, a_p, ac_eff), expected, rel_tol=10e-5 + ) + + +@pytest.mark.parametrize( + 'a_s, e1, a_p, ac_eff', + [ + (-200, 0.8, 125, 600), + (200, -0.8, 125, 600), + (200, 0.8, -125, 600), + (200, 0.8, 125, -600), + ], +) +def test_rho_p_eff_raise_value_error(a_s, e1, a_p, ac_eff): + """Test rho_p_eff raise exceptions""" + with pytest.raises(ValueError): + _crack_control.rho_p_eff(a_s, e1, a_p, ac_eff) From 59f1198a1aabf6d331d0b499a19e0a5dbaf83c72 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 12 Jan 2023 16:06:32 +0100 Subject: [PATCH 12/28] kt load duration --- .../codes/ec2_2004/_crack_control.py | 27 +++++++++++++++++++ tests/test_ec2_2004_crack_control.py | 21 +++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index e80c6c66..76e30cb4 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -572,3 +572,30 @@ def rho_p_eff(a_s: float, e1: float, a_p, ac_eff: float) -> float: raise ValueError(f'ac_eff={ac_eff} cannot be less than 0') return (a_s + e1**2 * a_p) / ac_eff + + +def kt_load_duration(load_type: str): + """Returns the kt factor dependent on the load duration for + the crack width calculation + + Args: + load_type (str): the load type: + - 'short' for term loading + - 'long' for long term loading + + Returns: + float: with the kt factor + + Raises: + ValueError: if load_type is not 'short' and not 'long' + """ + if not isinstance(load_type, str): + raise TypeError + + load_type = load_type.lower() + if load_type != 'short' and load_type != 'long': + raise ValueError( + f'load_type={load_type} can only have "short" or "long" as a value' + ) + + return 0.6 if load_type == 'short' else 0.4 diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py index a935db15..c7bf42d8 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_crack_control.py @@ -402,3 +402,24 @@ def test_rho_p_eff_raise_value_error(a_s, e1, a_p, ac_eff): """Test rho_p_eff raise exceptions""" with pytest.raises(ValueError): _crack_control.rho_p_eff(a_s, e1, a_p, ac_eff) + + +@pytest.mark.parametrize( + 'load_type, expected', + [ + ('short', 0.6), + ('long', 0.4), + ], +) +def test_kt_load_duration_returns_expected_values(load_type, expected): + """Test kt_load_duration returns expected values""" + assert _crack_control.kt_load_duration(load_type) == expected + + +def test_kt_load_duration_raise_value_errors(): + """Test kt_load_duration raise value errors""" + with pytest.raises(TypeError): + _crack_control.kt_load_duration(load_type=123) + + with pytest.raises(ValueError): + _crack_control.kt_load_duration(load_type='asdf') From 34d85d24609279f074f61cf8620a2dab05d4fba1 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 12 Jan 2023 18:10:44 +0100 Subject: [PATCH 13/28] strain diff formula --- .../codes/ec2_2004/_crack_control.py | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index 76e30cb4..91ab738a 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -574,7 +574,7 @@ def rho_p_eff(a_s: float, e1: float, a_p, ac_eff: float) -> float: return (a_s + e1**2 * a_p) / ac_eff -def kt_load_duration(load_type: str): +def kt_load_duration(load_type: str) -> float: """Returns the kt factor dependent on the load duration for the crack width calculation @@ -599,3 +599,66 @@ def kt_load_duration(load_type: str): ) return 0.6 if load_type == 'short' else 0.4 + + +def steel_stress_strain( + s_steel: float, + alpha_e: float, + rho_p_eff: float, + kt: float, + fct_eff: float, + es: float, +) -> float: + """Returns the strain difference (esm - ecm) needed to compute the crack + width. esm is the mean strain in the reinforcement under the relevant + combination of loads of imposed deformations and taking into account the + effects of tension stiffening. Only the additional tensile strain beyond + the state of zero strain of the concrete is considered. ecm is the mean + strain in the concrete between the cracks. + + EUROCODE 2 1992-1-1:2004, Eq. (7.9) + + Args: + s_steel (float): is the stress in MPa in the tension reinforcement + assuming a cracked section. FOr pretensioned members, s_steel may + be replaced by increment of s_steel stress variation in + prestressing tendons from the state of zero strain of the + concrete at the same level. + alpha_e (float): is the ratio Es/Ecm + rho_p_eff (float): effective bond ratio between areas given by the + Eq. (7.10) + kt (float): is a factor dependent on the load duration + fct_eff (float): is the mean value of the tensile strength in MPa + of the concrete effectvie at the time when the cracks may + first be expected to occur: fct_eff=fctm or fctm(t) if + crack is expected earlier than 28 days. + es: steel elastic mudulus in MPa + + Returns: + float: the strain difference between concrete and steel + + Raises: + ValueError: if any s_steel, alpha_e, rho_p_eff, fct_Eff is less + than 0. + ValueError: if kt is not 0.6 and not 0.4 + """ + if s_steel < 0: + raise ValueError(f's_steel={s_steel} cannot be less than 0') + if alpha_e < 0: + raise ValueError(f'alpha_e={alpha_e} cannot be less than 0') + if rho_p_eff < 0: + raise ValueError(f'rho_p_eff={rho_p_eff} cannot be less than 0') + if fct_eff < 0: + raise ValueError(f'fct_eff={fct_eff} cannot be less than 0') + if es < 0: + raise ValueError(f'es={es} cannot be less than 0') + if kt != 0.6 and kt != 0.4: + raise ValueError(f'kt={kt} can only take as values 0.4 and 0.6') + + min_val = 0.6 * s_steel / es + + a = 1 + alpha_e * rho_p_eff + b = kt * fct_eff / rho_p_eff * a + c = (s_steel - b) / es + + return max(c, min_val) From a8ab1298037c223f97ded415ce6d1662f9fcc029 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Fri, 13 Jan 2023 13:32:14 +0100 Subject: [PATCH 14/28] chapter completed --- .../codes/ec2_2004/_crack_control.py | 288 +++++++++++++++++- tests/test_ec2_2004_crack_control.py | 278 ++++++++++++++++- 2 files changed, 558 insertions(+), 8 deletions(-) diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_crack_control.py index 91ab738a..e0561fe3 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_crack_control.py @@ -25,8 +25,8 @@ def w_max(exposure_class: str, load_combination: str) -> float: Raises: ValueError: if not valid exposure_class or load_combination values. """ - _load_combination = load_combination.lower() - _exposure_class = exposure_class.upper() + _load_combination = load_combination.lower().strip() + _exposure_class = exposure_class.upper().strip() if _load_combination == 'f': if _exposure_class in ('X0', 'XC1'): return 0.2 @@ -519,7 +519,7 @@ def crack_min_steel_without_direct_calculation( return phi, spa -def alpha_e(es: float, ecm: float) -> float: +def get_alpha_e(es: float, ecm: float) -> float: """Compute the ratio between the steel and mean concrete modules. @@ -592,7 +592,7 @@ def kt_load_duration(load_type: str) -> float: if not isinstance(load_type, str): raise TypeError - load_type = load_type.lower() + load_type = load_type.lower().strip() if load_type != 'short' and load_type != 'long': raise ValueError( f'load_type={load_type} can only have "short" or "long" as a value' @@ -601,7 +601,7 @@ def kt_load_duration(load_type: str) -> float: return 0.6 if load_type == 'short' else 0.4 -def steel_stress_strain( +def esm_ecm( s_steel: float, alpha_e: float, rho_p_eff: float, @@ -662,3 +662,281 @@ def steel_stress_strain( c = (s_steel - b) / es return max(c, min_val) + + +def s_threshold(c: float, phi: float) -> float: + """Computes the distance threshold from which the + maximum crack spacing is constant. + + EUROCODE 2 1992-1-1:2004, Sect. (7.3.4-3) + + Args: + c (float): cover of the longitudinal reinforcement in mm + phi (float): is the bar diameter in mm. Where mixed bar diameters + used, then it should be replaced for an equivalente bar diameter. + + Returns: + float: threshold distance in mm + + Raises: + ValueError: if any of c or phi is less than 0. + """ + if c < 0: + raise ValueError(f'c={c} cannot be less than 0') + if phi < 0: + raise ValueError(f'phi={phi} cannot be less than 0') + + return 5 * (c + phi / 2) + + +def phi_eq(n1: int, n2: int, phi1: float, phi2: float) -> float: + """Computes the equivalent diameter. For a section with n1 bars of + diameter phi1 and n2 bars of diameter phi2 + + EUROCODE 2 1992-1-1:2004, Sect. (7.12) + + Args: + n1 (int): number of bars with diameter phi1 + n2 (int): number of bars with diameter phi2 + phi1 (float): diameter of n1 bars in mm + phi2 (float): diamater of n2 bars in mm + + Returns: + float: the equivalent diameter in mm + + Raises: + ValueError: if any of n1 or n2 is less than 0 + ValueError: if any of phi1 or phi2 is less than 0 + TypeError: if any of n1 or n2 is not an integer + """ + if n1 < 0: + raise ValueError(f'n1={n1} cannot be less than 0') + if not isinstance(n1, int): + raise TypeError(f'n1={n1} needs to be an integer value') + if n2 < 0: + raise ValueError(f'n2={n2} cannot be less than 0') + if not isinstance(n2, int): + raise TypeError(f'n2={n2} needs to be an integer value') + if phi1 < 0: + raise ValueError(f'phi1={phi1} cannot be less than 0') + if phi2 < 0: + raise ValueError(f'phi2={phi2} cannot be less than 0') + + a = n1 * phi1**2 + n2 * phi2**2 + b = n1 * phi1 + n2 * phi2 + return a / b + + +def k1(bond_type: str) -> float: + """Get the k1 coefficient which takes account of the bond properties + of the bounded reinforcement + + EUROCODE 2 1992-1-1:2004, Eq. (7.11-k1) + + Args: + bond_type (str): the bond property of the reinforcement. + Possible values: + - 'bond': for high bond bars + - 'plane': for bars with an effectively plain surface (e.g. + prestressing tendons) + + Returns: + (float): value of the k1 coefficient + + Raises: + ValueError: if bond_type is neither 'bond' nor 'plane' + TypeError: if bond_type is not an str + """ + if not isinstance(bond_type, str): + raise TypeError(f'bond_type={bond_type} is not an str') + + bond_type = bond_type.lower().strip() + if bond_type != 'bond' and bond_type != 'plane': + raise ValueError( + f'bond_type={bond_type} can only have "bond" or "plane" as values' + ) + + return 0.8 if bond_type == 'bond' else 1.6 + + +def k2(epsilon_r: float) -> float: + """Computes a coefficient which takes into account of the + distribution of strain: + + EUROCODE 2 1992-1-1:2004, Eq. (7.13) + + Args: + epsilon_r (float): ratio epsilon_2/epsilon_1 where epsilon_1 is + thre greater and epsilon_2 is the lesser strain at the boundaries + of the section considererd, assessed on the basis of a cracked + section. epsilon_r=0 for bending and epsilon_r=1 for pure tension. + + Returns: + float: the k2 coefficient value. + + Raises: + ValueError: if epsilon_r is not between 0 and 1. + """ + if epsilon_r < 0 or epsilon_r > 1: + raise ValueError(f'epsilon_r={epsilon_r} must be between 0 and 1') + + return (1 + epsilon_r) / 2 + + +def k3(): + """Returns the k3 coefficient for computing sr_max + + Returns: + float: value for the coefficient + """ + return 3.4 + + +def k4(): + """Returns the k4 coefficient for computing sr_max + + Returns: + float: value for the coefficient + """ + return 0.425 + + +def sr_max_close( + c: float, + phi: float, + rho_p_eff: float, + k1: float, + k2: float, + k3: float, + k4: float, +) -> float: + """Computes the maximum crack spacing in cases where bonded reinforcement + is fixed at reasonably close centres within the tension zone + (spacing<=5(c+phi/2)). + + EUROCODE 2 1992-1-1:2004, Eq. (7.11) + + Args: + c (float): is the cover in mm of the longitudinal reinforcement + phi (float): is the bar diameter in mm. Where mixed bar diameters + used, then it should be replaced for an equivalente bar diameter. + rho_p_eff (float): effective bond ratio between areas given by the + Eq. (7.10) + k1 (float): coefficient that takes into account the bound properties + of the bonded reinforcement + k2 (float): coefficient that takes into account the distribution of + of the strain + k3 (float): coefficient from the National Annex + k4 (float): coefficient from the National Annex + + Returns: + float: the maximum crack spaing in mm. + + Raises: + ValueError: if one or more of c, phi, rho_p_eff, k3 or k4 + is lower than zero. + ValueError: if k1 is not 0.8 or 1.6 + ValueError: if k2 is not between 0.5 and 1.0 + """ + if c < 0: + raise ValueError(f'c={c} cannot be less than zero') + if phi < 0: + raise ValueError(f'phi={phi} cannot be less than zero') + if rho_p_eff < 0: + raise ValueError(f'rho_p_eff={rho_p_eff} cannot be less than zero') + if k3 < 0: + raise ValueError(f'k3={k3} cannot be less than zero') + if k4 < 0: + raise ValueError(f'k4={k4} cannot be less than zero') + if k1 != 0.8 and k1 != 1.6: + raise ValueError(f'k1={k1} can only take as values 0.8 and 1.6') + if k2 < 0.5 or k2 > 1: + raise ValueError(f'k2={k2} is not between 0.5 and 1.0') + + return k3 * c + k1 * k2 * k4 * phi / rho_p_eff + + +def sr_max_far(h: float, x: float) -> float: + """Computes the maximum crack spacing in cases where bonded reinforcement + is fixed at reasonably close centres within the tension zone + (spacing>5(c+phi/2)). + + EUROCODE 2 1992-1-1:2004, Eq. (7.14) + + Args: + h (float): total depth of the beam in mm + x (float): distance to non tension area of the element mm + + Returns: + float: maximum crack spacing in mm + + Raises: + ValueError: if one of h or x is less than zero. + ValueError: x is greater than h. + """ + if x < 0: + raise ValueError(f'x={x} cannot be less than zero') + if h < 0: + raise ValueError(f'h={h} cannot be less than zero') + if x > h: + raise ValueError(f'x={x} cannot be larger than h={h}') + + return 1.3 * (h - x) + + +def sr_max_theta(sr_max_y: float, sr_max_z: float, theta: float) -> float: + """Computes the crack spacing sr_max when there is an angle + between the angle of principal stress and the direction + of the reinforcement, for members in two orthogonal directions, + that is significant (> 15º). + + EUROCODE 2 1992-1-1:2004, Eq. (7.15) + + Args: + sr_max_y (float): crack spacing in mm in the y-direction. + sr_max_z (float): crack spacing in mm in the z-direction. + theta (float): angle in radians between the reinforcement in the + y-direction and the direction of the principal tensile stress. + + Returns: + float: the crack spacing in mm. + + Raises: + ValueError: if sr_max_y or sr_max_z is negative. + ValueError: if theta is not between 0 and pi/2 + """ + if sr_max_y < 0: + raise ValueError(f'sr_max_y={sr_max_y} cannot be less than zero') + if sr_max_z < 0: + raise ValueError(f'sr_max_z={sr_max_z} cannot be less than zero') + + a = math.cos(theta) / sr_max_y + b = math.sin(theta) / sr_max_z + return 1 / (a + b) + + +def wk(sr_max: float, esm_ecm: float) -> float: + """Computes the crack width + + EUROCODE 2 1992-1-1:2004, Eq. (7.8) + + Args: + sr_max (float): the maximum crack length spacing in mm. + esm_ecm (float): the difference between the mean strain in the + reinforcement under relevant combination of loads, including + the effect of imposed deformations and taking into account + tension stiffening and the mean strain in the concrete + between cracks. + + Returns: + float: crack width in mm. + + Raises: + ValueError: if any of sr_max or esm_ecm is less than zero. + """ + if sr_max < 0: + raise ValueError(f'sr_max={sr_max} cannot be less than zero') + if esm_ecm < 0: + raise ValueError(f'esm_scm={esm_ecm} cannot be less than zero') + + return sr_max * esm_ecm diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_crack_control.py index c7bf42d8..66e8adeb 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_crack_control.py @@ -356,7 +356,7 @@ def test_hc_eff_concrete_tension_raise_exceptions(h, d, x): def test_alpha_e_returns_expected_values(es, ecm, expected): """Test alpha_e returns expected values""" assert math.isclose( - _crack_control.alpha_e(es, ecm), + _crack_control.get_alpha_e(es, ecm), expected, rel_tol=10e-5, ) @@ -372,7 +372,7 @@ def test_alpha_e_returns_expected_values(es, ecm, expected): def test_alpha_e_raise_exceptions(es, ecm): """Test alpha_e raises exceptions""" with pytest.raises(ValueError): - _crack_control.alpha_e(es, ecm) + _crack_control.get_alpha_e(es, ecm) @pytest.mark.parametrize( @@ -385,7 +385,9 @@ def test_alpha_e_raise_exceptions(es, ecm): def test_rho_p_eff_returns_expected_values(a_s, e1, a_p, ac_eff, expected): """Test rho_p_eff returns expeceted values""" assert math.isclose( - _crack_control.rho_p_eff(a_s, e1, a_p, ac_eff), expected, rel_tol=10e-5 + _crack_control.rho_p_eff(a_s, e1, a_p, ac_eff), + expected, + rel_tol=10e-5, ) @@ -423,3 +425,273 @@ def test_kt_load_duration_raise_value_errors(): with pytest.raises(ValueError): _crack_control.kt_load_duration(load_type='asdf') + + +@pytest.mark.parametrize( + 's_steel, alpha_e, rho_p_eff, kt, fct_eff, es, expected', + [ + (250, 5.25, 0.34, 0.4, 2.9, 210000, 0.00114523), + (200, 5.25, 0.4, 0.6, 3.1, 210000, 0.00088374), + (250, 5.25, 0.2, 0.6, 2.5, 210000, 0.00111726), + ], +) +def test_esm_ecm_returns_expected_values( + s_steel, alpha_e, rho_p_eff, kt, fct_eff, es, expected +): + """Test esm_ecm returns the expected values""" + assert math.isclose( + _crack_control.esm_ecm(s_steel, alpha_e, rho_p_eff, kt, fct_eff, es), + expected, + abs_tol=10e-5, + ) + + +@pytest.mark.parametrize( + 's_steel, alpha_e, rho_p_eff, kt, fct_eff, es', + [ + (-250, 5.25, 0.34, 0.4, 2.9, 210000), + (250, -5.25, 0.34, 0.4, 2.9, 210000), + (250, 5.25, -0.34, 0.4, 2.9, 210000), + (250, 5.25, 0.34, 0.4, -2.9, 210000), + (250, 5.25, 0.34, 0.4, 2.9, -210000), + (250, 5.25, 0.34, 0.2, 2.9, 210000), + ], +) +def test_esm_ecm_raises_exception(s_steel, alpha_e, rho_p_eff, kt, fct_eff, es): + """Test esm_ecm raise expected exceptions""" + with pytest.raises(ValueError): + _crack_control.esm_ecm(s_steel, alpha_e, rho_p_eff, kt, fct_eff, es) + + +@pytest.mark.parametrize( + 'c, phi, expected', + [ + (30, 16, 190), + (25, 8, 145), + ], +) +def test_s_returns_expected_returns(c, phi, expected): + """Test s returns expected results""" + assert math.isclose( + _crack_control.s_threshold(c, phi), + expected, + rel_tol=10e-5, + ) + + +@pytest.mark.parametrize( + 'c, phi', + [ + (-20, 18), + (20, -18), + ], +) +def test_s_raise_expected_exceptions(c, phi): + """Test s raise expected exceptions""" + with pytest.raises(ValueError): + _crack_control.s_threshold(c, phi) + + +@pytest.mark.parametrize( + 'n1, n2, phi1, phi2, expected', + [(3, 5, 20, 12, 16), (5, 5, 20, 12, 17), (6, 2, 24, 10, 22.29268)], +) +def test_phi_eq_returns_expected_results(n1, n2, phi1, phi2, expected): + """Test phi_eq returns expected results""" + assert math.isclose( + _crack_control.phi_eq(n1, n2, phi1, phi2), + expected, + rel_tol=10e-5, + ) + + +@pytest.mark.parametrize( + 'n1, n2, phi1, phi2, exception_type', + [ + (-2, 2, 20, 18, ValueError), + (2, -2, 20, 18, ValueError), + (2, 2, -20, 18, ValueError), + (2, 2, 20, -18, ValueError), + (4.5, 2, 20, 18, TypeError), + (3, 4.5, 20, 20, TypeError), + ], +) +def test_phi_eq_raises_expected_values(n1, n2, phi1, phi2, exception_type): + """Test phi_eq raises expected exception""" + with pytest.raises(exception_type): + _crack_control.phi_eq(n1, n2, phi1, phi2) + + +@pytest.mark.parametrize( + 'bond_type, expected', + [('bond', 0.8), ('plane', 1.6), ('BOND ', 0.8), (' PLANE ', 1.6)], +) +def test_k1_returns_expected_values(bond_type, expected): + """Test k1 returns expected values""" + assert _crack_control.k1(bond_type) == expected + + +@pytest.mark.parametrize( + 'bond_type, exception_type', + [('asdf ad', ValueError), (123, TypeError), (14.2, TypeError)], +) +def test_k1_raise_expected_exceptions(bond_type, exception_type): + """Test k1 raises expected exceptions""" + with pytest.raises(exception_type): + _crack_control.k1(bond_type) + + +@pytest.mark.parametrize( + 'epsilon_r, expected', + [(0, 0.5), (1, 1), (0.75, 0.875), (0.67, 0.835)], +) +def test_k2_returns_expected_values(epsilon_r, expected): + """Test k2 returns expected values""" + assert math.isclose( + _crack_control.k2(epsilon_r), + expected, + rel_tol=10e-5, + ) + + +@pytest.mark.parametrize('epsilon_r', [(-0.1), (-2), (1.1), (2)]) +def test_k2_raises_value_exceptions(epsilon_r): + """Test k2 raises expected exceptions""" + with pytest.raises(ValueError): + _crack_control.k2(epsilon_r) + + +def test_k3_returns_expected_values(): + """Test k3 returns the expected values""" + assert _crack_control.k3() == 3.4 + + +def test_k4_returns_expected_values(): + """Test k4 returns the expected values""" + assert _crack_control.k4() == 0.425 + + +@pytest.mark.parametrize( + 'c, phi, rho_p_eff, k1, k2, k3, k4, expected', + [ + (20, 8, 5, 0.8, 0.5, 3.4, 0.425, 68.272), + (30, 15, 0.2, 1.6, 0.5, 3.4, 0.425, 127.5), + (45, 20, 0.4, 0.8, 1, 3.4, 0.425, 170), + ], +) +def test_sr_max_close(c, phi, rho_p_eff, k1, k2, k3, k4, expected): + """Test sr_max_close returns the expected values""" + assert math.isclose( + _crack_control.sr_max_close(c, phi, rho_p_eff, k1, k2, k3, k4), + expected, + rel_tol=10e-5, + ) + + +@pytest.mark.parametrize( + 'c, phi, rho_p_eff, k1, k2, k3, k4', + [ + (-20, 8, 5, 0.8, 0.5, 3.4, 0.425), + (20, -8, 5, 0.8, 0.5, 3.4, 0.425), + (20, 8, -5, 0.8, 0.5, 3.4, 0.425), + (20, 8, 5, -0.8, 0.5, 3.4, 0.425), + (20, 8, 5, 0.8, -0.5, 3.4, 0.425), + (20, 8, 5, 0.8, 0.5, -3.4, 0.425), + (20, 8, 5, 0.8, 0.5, 3.4, -0.425), + (20, 8, 5, 0.9, 0.5, 3.4, 0.425), + (20, 8, 5, 0.8, 0.2, 3.4, 0.425), + (20, 8, 5, 0.8, 1.1, 3.4, 0.425), + ], +) +def test_sr_max_close_raises_exceptions(c, phi, rho_p_eff, k1, k2, k3, k4): + """Test sr_max_close raises the expected value errors""" + with pytest.raises(ValueError): + _crack_control.sr_max_close(c, phi, rho_p_eff, k1, k2, k3, k4) + + +@pytest.mark.parametrize( + 'h, x, expected', + [ + (300, 100, 260), + (200, 75, 162.5), + (400, 100, 390), + ], +) +def test_sr_max_far_returns_expected_values(h, x, expected): + """Test sr_max_far returns the expected values""" + assert math.isclose( + _crack_control.sr_max_far(h, x), expected, rel_tol=10e-5 + ) + + +@pytest.mark.parametrize( + 'h, x', + [ + (-100, 100), + (200, -100), + (100, 200), + ], +) +def test_sr_max_far_raises_exceptions(h, x): + """Test sr_max_far raises exceptions""" + with pytest.raises(ValueError): + _crack_control.sr_max_far(h, x) + + +@pytest.mark.parametrize( + 'sr_max_y, sr_max_z, theta, expected', + [ + (200, 160, 0.2618, 155.10493), + (265, 50, 0.7854, 59.4868), + (140, 10, 1.39626, 10.028), + ], +) +def test_sr_max_theta_returns_expected_values( + sr_max_y, sr_max_z, theta, expected +): + """Test sr_max_theta returns expeceted values""" + assert math.isclose( + _crack_control.sr_max_theta(sr_max_y, sr_max_z, theta), + expected, + rel_tol=10e-5, + ) + + +@pytest.mark.parametrize( + 'sr_max_y, sr_max_z, theta', + [ + (-100, 200, 0), + (100, -200, -0.5), + (100, -200, 150), + ], +) +def test_sr_max_theta_raises_exceptions(sr_max_y, sr_max_z, theta): + """Test sr_max_theta raises value errors""" + with pytest.raises(ValueError): + _crack_control.sr_max_theta(sr_max_y, sr_max_z, theta) + + +@pytest.mark.parametrize( + 'sr_max, esm_ecm, expected', + [ + (200, 0.00112, 0.224), + (260, 0.0007, 0.182), + ], +) +def test_wk_returns_expected_values(sr_max, esm_ecm, expected): + """Test wk returns expected values""" + assert math.isclose( + _crack_control.wk(sr_max, esm_ecm), + expected, + rel_tol=10e-5, + ) + + +@pytest.mark.parametrize( + 'sr_max, esm_ecm', + [(-200, 0.0001), (200, -0.0001)], +) +def test_wk_raises_exceptions(sr_max, esm_ecm: float): + """Test wk raises value errors""" + with pytest.raises(ValueError): + _crack_control.wk(sr_max, esm_ecm) From ce4e432ba2a0059149e58582d2cb45080757cbcf Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Fri, 13 Jan 2023 14:31:24 +0100 Subject: [PATCH 15/28] imports and renamed functions --- prueba.py | 0 structuralcodes/codes/ec2_2004/__init__.py | 54 +- .../{_crack_control.py => _section_7.3.py} | 347 ++++--- .../ec2_2004/_section_7_3_crack_control.py | 933 ++++++++++++++++++ ...est_ec2_2004_section_7_3_crack_control.py} | 127 ++- 5 files changed, 1228 insertions(+), 233 deletions(-) create mode 100644 prueba.py rename structuralcodes/codes/ec2_2004/{_crack_control.py => _section_7.3.py} (74%) create mode 100644 structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py rename tests/{test_ec2_2004_crack_control.py => test_ec2_2004_section_7_3_crack_control.py} (83%) diff --git a/prueba.py b/prueba.py new file mode 100644 index 00000000..e69de29b diff --git a/structuralcodes/codes/ec2_2004/__init__.py b/structuralcodes/codes/ec2_2004/__init__.py index 96694004..d322788b 100644 --- a/structuralcodes/codes/ec2_2004/__init__.py +++ b/structuralcodes/codes/ec2_2004/__init__.py @@ -1,9 +1,59 @@ """EUROCODE 2 1992-1-1:2004""" import typing as t -from ._crack_control import w_max +from ._section_7_3_crack_control import ( + As_min, + As_min_2, + As_min_p, + alpha_e, + esm_ecm, + hc_eff, + k, + k1, + k2, + k3, + k4, + kc_flanges_area, + kc_rect_area, + kc_tension, + kt, + phi_eq, + rho_p_eff, + sr_max_close, + sr_max_far, + sr_max_theta, + w_max, + w_spacing, + wk, + xi1, +) -__all__ = ['w_max'] +__all__ = [ + 'As_min', + 'As_min_2', + 'As_min_p', + 'alpha_e', + 'esm_ecm', + 'hc_eff', + 'k', + 'k1', + 'k2', + 'k3', + 'k4', + 'kc_flanges_area', + 'kc_rect_area', + 'kc_tension', + 'kt', + 'phi_eq', + 'rho_p_eff', + 'sr_max_close', + 'sr_max_far', + 'sr_max_theta', + 'w_max', + 'w_spacing', + 'wk', + 'xi1', +] __title__: str = 'EUROCODE 2 1992-1-1' __year__: str = '2004' diff --git a/structuralcodes/codes/ec2_2004/_crack_control.py b/structuralcodes/codes/ec2_2004/_section_7.3.py similarity index 74% rename from structuralcodes/codes/ec2_2004/_crack_control.py rename to structuralcodes/codes/ec2_2004/_section_7.3.py index e0561fe3..3ad05170 100644 --- a/structuralcodes/codes/ec2_2004/_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_section_7.3.py @@ -58,8 +58,8 @@ def w_max(exposure_class: str, load_combination: str) -> float: ) -def crack_min_steel_area( - a_ct: float, s_steel: float, fct_eff: float, k: float, kc: float +def As_min( + A_ct: float, sigma_s: float, fct_eff: float, _k: float, kc: float ) -> float: """Computes the minimum area of reinforcing steel within the tensile zone for control of cracking areas @@ -67,10 +67,10 @@ def crack_min_steel_area( EUROCODE 2 1992-1-1:2004, Eq. (7.1) Args: - a_ct (float): is the area of concrete within the tensile zone in mm2. + A_ct (float): is the area of concrete within the tensile zone in mm2. The tensile zone is that parg of the section which is calculated to be in tension just before the formation of the first crack. - s_steel (float): is the absolute value of the maximum stress in MPa + sigma_s (float): is the absolute value of the maximum stress in MPa permitted in the reinforcement immediately after the formation of the crack. This may be taken as theyield strength of the reinforcement, fyk. A lower value may, however, be needed to @@ -80,7 +80,7 @@ def crack_min_steel_area( the concrete effective at the time when the cracks may first be expected to occur: fct,eff=fct or lower (fct(t)), is cracking is expected earlier than 28 days. - k (float): is the coefficient which allow for the effect of + _k (float): is the coefficient which allow for the effect of non-uniform self-equilibrating stresses, which lead to a reduction of restraint forces. Use 'k_crack_min_steel_area' to compute it @@ -96,28 +96,27 @@ def crack_min_steel_area( zone in mm2. Raises: - ValueError: if k value is not between 0.65 and 1 or kc is not + ValueError: if _k value is not between 0.65 and 1 or kc is not larger than 0 and lower than 1. """ fct_eff = abs(fct_eff) - if a_ct <= 0: - raise ValueError(f'a_ct={a_ct} must be larger than 0') - if s_steel < 0: - raise ValueError(f's_steel={s_steel} must be equal or larger than 0') - if k < 0.65 or k > 1.0: - raise ValueError(f'k={k} must be between 0.65 and 1') + if A_ct <= 0: + raise ValueError(f'A_ct={A_ct} must be larger than 0') + if sigma_s < 0: + raise ValueError(f'sigma_s={sigma_s} must be equal or larger than 0') + if _k < 0.65 or _k > 1.0: + raise ValueError(f'_k={_k} must be between 0.65 and 1') if kc > 1 or kc < 0: raise ValueError(f'kc={kc} must be lower than 1 and larger than 0') - return kc * k * fct_eff * a_ct / s_steel + return kc * _k * fct_eff * A_ct / sigma_s -def k_crack_min_steel_area(h: float) -> float: +def k(h: float) -> float: """Is the coefficient which allow for the effect of non-uniform self-equilibrating stresses, which lead to a - reduction of restraint forces. Use 'k_crack_min_steel_area' - to compute it + reduction of restraint forces. k=1 for webs w<=300mm or flanges widths less than 300mm k=0.65 for webs w>=800mm or flanges with widths greater than 800mm @@ -142,7 +141,7 @@ def k_crack_min_steel_area(h: float) -> float: return 0.65 -def kc_crack_min_steel_area_pure_tension() -> float: +def kc_pure_tension() -> float: """Computes the coefficient which takes account of the stress distribution within the section immediately prior to cracking and the change of the lever arm in pure dtension. @@ -155,8 +154,8 @@ def kc_crack_min_steel_area_pure_tension() -> float: return 1 -def kc_crack_min_steel_area_rectangular( - h: float, b: float, fct_eff: float, n_ed: float +def kc_rectangular_area( + h: float, b: float, fct_eff: float, N_ed: float ) -> float: """Computes the coefficient which takes account of the stress distribution within the section immediately prior to cracking and @@ -172,7 +171,7 @@ def kc_crack_min_steel_area_rectangular( the concrete effective at the time when the cracks may first be expected to occur: fct,eff=fct or lower (fct(t)), is cracking is expected earlier than 28 days. - n_ed (str): axial force at the serviceability limit state acting on + N_ed (str): axial force at the serviceability limit state acting on the part of the cross-section under consideration (compressive force positive). n_ed should be determined considering the characteristic values of prestress and axial forces under the @@ -190,15 +189,13 @@ def kc_crack_min_steel_area_rectangular( raise ValueError(f'b={b} should be larger than 0mm') h_s = min(h, 1000) - k1 = 1.5 if n_ed >= 0 else 2 * h_s / 3 / h - s_concrete = n_ed * 1000 / b / h + _k1 = 1.5 if N_ed >= 0 else 2 * h_s / 3 / h + s_concrete = N_ed * 1000 / b / h h_ratio = h / h_s - return min(max(0.4 * (1 - s_concrete / k1 / h_ratio / fct_eff), 0), 1) + return min(max(0.4 * (1 - s_concrete / _k1 / h_ratio / fct_eff), 0), 1) -def kc_crack_min_steel_area_flanges( - f_cr: float, a_ct: float, fct_eff: float -) -> float: +def kc_flanges_area(f_cr: float, A_ct: float, fct_eff: float) -> float: """Computes the coefficient which takes account of the stress distribution within the section immediately prior to cracking and the change of the lever arm for bending+axial combination @@ -210,7 +207,7 @@ def kc_crack_min_steel_area_flanges( f_cr: is the absolute value in kN of the tensile force within the flange immediately prior to cracking due to cracking moment calculated with fct,eff - a_ct (float): is the area of concrete within the tensile zone in mm2. + A_ct (float): is the area of concrete within the tensile zone in mm2. The tensile zone is that part of the section which is calculated to be in tension just before the formation of the first crack. fct_eff (float): is the mean value of the tensile strength in MPa of @@ -222,49 +219,47 @@ def kc_crack_min_steel_area_flanges( float: value of the kc coefficient Raises: - ValueError: is a_ct is less than 0mm2 + ValueError: is A_ct is less than 0mm2 """ f_cr = abs(f_cr) - return max(0.9 * f_cr * 1000 / a_ct / fct_eff, 0.5) + return max(0.9 * f_cr * 1000 / A_ct / fct_eff, 0.5) -def adjusted_bond_strength(e: float, d_press: float, d_steel: float) -> float: +def xi_1(xi: float, phi_p: float, phi_s: float) -> float: """Computes the adjusted ratio of bond strength taking into account the different diameters of prestressing and reinforcing steel. EUROCODE 2 1992-1-1:2004, Eq. (7.5) Args: - e (float): ratio of bond strength of prestressing and reinforcing + xi (float): ratio of bond strength of prestressing and reinforcing steel, according to Table 6.2 in 6.8.2 - d_steel (float): largest bar diameter in mm of reinforcing steel. + phi_p (float): largest bar diameter in mm of reinforcing steel. Equal to 0 if only prestressing is used in control cracking - d_press (float): equivalent diameter in mm of tendon acoording + phi_s (float): equivalent diameter in mm of tendon acoording to 6.8.2 Returns: float: with the value of the ratio Raises: - ValueError: if diameters d_steel or d_press are lower than 0. - If ratio of bond strength e is less than 0.15 or larger than 0.8. - If area of tendons ac_eff is less than 0. Is stress variation - incr_stress is less than 0. + ValueError: if diameters phi_s or phi_p are lower than 0. + If ratio of bond strength xi is less than 0.15 or larger than 0.8. """ - if d_press <= 0: - raise ValueError(f'd_press={d_press} cannot be less than 0') - if d_steel < 0: - raise ValueError(f'd_steel={d_steel} cannot be less than 0') - if e < 0.15: - raise ValueError(f'The minimum value for e={e} is 0.15') - if e > 0.8: - raise ValueError(f'The maximum value for e={e} is 0.8') + if phi_p <= 0: + raise ValueError(f'phi_p={phi_p} cannot be less than 0') + if phi_s < 0: + raise ValueError(f'phi_s={phi_s} cannot be less than 0') + if xi < 0.15: + raise ValueError(f'The minimum value for xi={xi} is 0.15') + if xi > 0.8: + raise ValueError(f'The maximum value for xi={xi} is 0.8') - return ((e * d_steel / d_press) ** 0.5) if d_steel > 0 else e**0.5 + return ((xi * phi_s / phi_p) ** 0.5) if phi_s > 0 else xi**0.5 -def hc_eff_concrete_tension(h: float, d: float, x: float) -> float: +def hc_eff(h: float, d: float, x: float) -> float: """Returns the effective height of concrete in tension surrounding the reinforcement or prestressing tendons. @@ -297,17 +292,17 @@ def hc_eff_concrete_tension(h: float, d: float, x: float) -> float: return min(2.5 * (h - d), (h - x) / 3, h / 2) -def crack_min_steel_area_with_prestresed_tendons( - a_ct: float, - s_steel: float, +def As_min_p( + A_ct: float, + sigma_s: float, fct_eff: float, - k: float, + _k: float, kc: float, - ap: float, - d_steel: float, - d_press: float, - e: float, - incr_stress: float, + Ap: float, + phi_s: float, + phi_p: float, + xi: float, + delta_s: float, ) -> float: """Computes the minimum area of reinforcing steel within the tensile zone for control of cracking areas in addition with bonded tendons @@ -315,10 +310,10 @@ def crack_min_steel_area_with_prestresed_tendons( EUROCODE 2 1992-1-1:2004, Eq. (7.1) Args: - a_ct (float): is the area of concrete within the tensile zone in mm2. + A_ct (float): is the area of concrete within the tensile zone in mm2. The tensile zone is that part of the section which is calculated to be in tension just before the formation of the first crack. - s_steel (float): is the absolute value of the maximum stress in MPa + sigma_s (float): is the absolute value of the maximum stress in MPa permitted in the reinforcement immediately after the formation of the crack. This may be taken as theyield strength of the reinforcement, fyk. A lower value may, however, be needed to @@ -328,7 +323,7 @@ def crack_min_steel_area_with_prestresed_tendons( the concrete effective at the time when the cracks may first be expected to occur: fct,eff=fct or lower (fct(t)), is cracking is expected earlier than 28 days. - k (float): is the coefficient which allow for the effect of + _k (float): is the coefficient which allow for the effect of non-uniform self-equilibrating stresses, which lead to a reduction of restraint forces. Use 'k_crack_min_steel_area' to compute it @@ -338,17 +333,15 @@ def crack_min_steel_area_with_prestresed_tendons( kc (float): is a coefficient which takes account of the stress distribution within the section immediately prior to cracking and the change of the lever arm. - ac_eff (float): is the effective area in mm2 of concrete in tension - surrounding or prestressing tendons if depth hc,ef - ap (float): is the area in mm2 of pre or post-tensioned tendons + Ap (float): is the area in mm2 of pre or post-tensioned tendons within ac_eff - d_steel (float): largest bar diameter in mm of reinforcing steel. + phi_s (float): largest bar diameter in mm of reinforcing steel. Equal to 0 if only prestressing is used in control cracking - d_press (float): equivalent diameter in mm of tendon acoording + phi_p (float): equivalent diameter in mm of tendon acoording to 6.8.2 - e (float): ratio of bond strength of prestressing and reinforcing + chi (float): ratio of bond strength of prestressing and reinforcing steel, according to Table 6.2 in 6.8.2 - incr_stress (float): stress variation in MPa in prestressing tendons + delta_s (float): stress variation in MPa in prestressing tendons from the state of zero strain of the concrete at the same level Returns: @@ -356,43 +349,43 @@ def crack_min_steel_area_with_prestresed_tendons( zone in mm2. Raises: - ValueError: if k value is not between 0.65 and 1 or kc is not - larger than 0 and lower than 1. If diameters d_steel or - d_press are lower than 0. If ratio of bond strength e - is less than 0.15 or larger than 0.8. If area of tendons ac_eff - is less than 0. Is stress variation incr_stress is less than 0 + ValueError: if _k value is not between 0.65 and 1 or kc is not + larger than 0 and lower than 1. If diameters phi_s or + phi_p are lower than 0. If ratio of bond xi strength e + is less than 0.15 or larger than 0.8. + Is stress variation incr_stress is less than 0. """ fct_eff = abs(fct_eff) - if ap < 0: - raise ValueError(f'ap={ap} cannot be less than 0') - if incr_stress < 0: - raise ValueError(f'incr_stress={incr_stress} cannot be less than 0') - if a_ct <= 0: - raise ValueError(f'a_ct={a_ct} must be larger than 0') - if s_steel < 0: - raise ValueError(f's_steel={s_steel} must be equal or larger than 0') - if k < 0.65 or k > 1.0: - raise ValueError(f'k={k} must be between 0.65 and 1') + if Ap < 0: + raise ValueError(f'Ap={Ap} cannot be less than 0') + if delta_s < 0: + raise ValueError(f'delta_s={delta_s} cannot be less than 0') + if A_ct <= 0: + raise ValueError(f'A_ct={A_ct} must be larger than 0') + if sigma_s < 0: + raise ValueError(f'sigma_s={sigma_s} must be equal or larger than 0') + if _k < 0.65 or _k > 1.0: + raise ValueError(f'_k={_k} must be between 0.65 and 1') if kc > 1 or kc < 0: raise ValueError(f'kc={kc} must be lower than 1 and larger than 0') - a1 = kc * k * fct_eff * a_ct - e1 = adjusted_bond_strength(e, d_press, d_steel) - a2 = e1 * ap * incr_stress + a1 = kc * _k * fct_eff * A_ct + e1 = xi_1(xi, phi_p, phi_s) + a2 = e1 * Ap * delta_s a = a1 - a2 - return a / s_steel + return a / sigma_s -def crack_min_steel_without_direct_calculation( - wk: float, - s_steel: float, +def As_min_2( + _wk: float, + sigma_s: float, fct_eff: float, h_cr: float, h: float, d: float, - incr_stress: float = 0, + delta_s: float = 0, kc: t.Optional[float] = None, ) -> t.Tuple[float, float]: """Computes the minimum area of reinforcing steel within the tensile zone @@ -401,8 +394,8 @@ def crack_min_steel_without_direct_calculation( EUROCODE 2 1992-1-1:2004, Table (7.2N), Table (7.3N) Args: - wk (float): the characteristic crack width value in mm. - s_steel (float): the steel stress value in MPa under the relevant + _wk (float): the characteristic crack width value in mm. + sigma_s (float): the steel stress value in MPa under the relevant combination of actions. fct_eff (float): is the mean value of the tensile strength in MPa of the concrete effective at the time when the cracks may first be @@ -414,7 +407,7 @@ def crack_min_steel_without_direct_calculation( h (float): the overall depth of the section in mm. d (float): is the effective depth to the centroid of the outer layer of the reinforcement. - incr_stress (float, optional): value of prestressed stress in MPa if + delta_s (float, optional): value of prestressed stress in MPa if applicable kc (float, optional): is a coefficient which takes account of the stress distribution within the section immediately prior to @@ -426,12 +419,12 @@ def crack_min_steel_without_direct_calculation( in the first position and the maximum bar spacing in mm in the second position Raises: - ValueError: if wk, fct_eff, h_cr, h or d are less than 0 + ValueError: if _wk, fct_eff, h_cr, h or d are less than 0 ValueError: if kc is not between 0 and 1 ValueError: if combination of wk and stress values are out of scope """ - if wk < 0: - raise ValueError(f'wd={wk} cannot be less than 0') + if _wk < 0: + raise ValueError(f'_wk={_wk} cannot be less than 0') if fct_eff < 0: raise ValueError(f'fct_eff={fct_eff} is less than 0') if h_cr < 0: @@ -443,7 +436,7 @@ def crack_min_steel_without_direct_calculation( if kc is not None and (kc < 0 or kc > 1): raise ValueError(f'kc={kc} is not between 0 and 1') - s = s_steel - incr_stress + s = sigma_s - delta_s if s <= 0: return (0, 0) @@ -499,7 +492,7 @@ def crack_min_steel_without_direct_calculation( points_phi = np.array(np.meshgrid(y_phi, x)).T.reshape(-1, 2) points_spa = np.array(np.meshgrid(y_spa, x)).T.reshape(-1, 2) - xi = (s, wk) + xi = (s, _wk) phi_star = float( scipy.interpolate.griddata(points_phi, phi_s_v, xi, method='linear') @@ -519,40 +512,40 @@ def crack_min_steel_without_direct_calculation( return phi, spa -def get_alpha_e(es: float, ecm: float) -> float: +def alpha_e(Es: float, Ecm: float) -> float: """Compute the ratio between the steel and mean concrete - modules. + elastic modules. EUROCODE 2 1992-1-1:2004, Section 7.3.4-2 Args: - es (float): steel elastic modulus in MPa - ecm (float): ecm concrete mean elastic modulus in MPa + Es (float): steel elastic modulus in MPa + Ecm (float): concrete mean elastic modulus in MPa Returns: float: ratio between modules Raise: ValueError: if any of es or ecm is lower than 0. """ - if es < 0: - raise ValueError(f'es={es} cannot be less than 0') - if ecm < 0: - raise ValueError(f'ecm={ecm} cannot be less than 0') + if Es < 0: + raise ValueError(f'Es={Es} cannot be less than 0') + if Ecm < 0: + raise ValueError(f'Ecm={Ecm} cannot be less than 0') - return es / ecm + return Es / Ecm -def rho_p_eff(a_s: float, e1: float, a_p, ac_eff: float) -> float: +def rho_p_eff(As: float, xi1: float, Ap: float, Ac_eff: float) -> float: """Effective bond ratio between areas EUROCODE 2 1992-1-1:2004, Eq. (7.10) Args: - a_s (float): steel area in mm2 - e1 (float): the adjusted ratio of bond according + As (float): steel area in mm2 + xi1 (float): the adjusted ratio of bond according to expression (7.5) - a_p (float): the area in mm2 of post-tensioned tendons in ac_eff - ac_eff (float): effective area of concrete in tension surrounding + Ap (float): the area in mm2 of post-tensioned tendons in ac_eff + Ac_eff (float): effective area of concrete in tension surrounding the reinforcement or prestressing tendons of depth hc_eff. Returns: @@ -560,21 +553,21 @@ def rho_p_eff(a_s: float, e1: float, a_p, ac_eff: float) -> float: Raise: - ValueError: if any of a_s, e1, a_p or ac_eff is less than 0 + ValueError: if any of As, xi1, Ap or Ac_eff is less than 0 """ - if a_s < 0: - raise ValueError(f'a_s={a_s} cannot be less than 0') - if e1 < 0: - raise ValueError(f'e1={e1} cannot be less than 0') - if a_p < 0: - raise ValueError(f'a_p={a_p} cannot be less than 0') - if ac_eff < 0: - raise ValueError(f'ac_eff={ac_eff} cannot be less than 0') + if As < 0: + raise ValueError(f'As={As} cannot be less than 0') + if xi1 < 0: + raise ValueError(f'xi1={xi1} cannot be less than 0') + if Ap < 0: + raise ValueError(f'Ap={Ap} cannot be less than 0') + if Ac_eff < 0: + raise ValueError(f'Ac_eff={Ac_eff} cannot be less than 0') - return (a_s + e1**2 * a_p) / ac_eff + return (As + xi1**2 * Ap) / Ac_eff -def kt_load_duration(load_type: str) -> float: +def kt(load_type: str) -> float: """Returns the kt factor dependent on the load duration for the crack width calculation @@ -602,12 +595,12 @@ def kt_load_duration(load_type: str) -> float: def esm_ecm( - s_steel: float, - alpha_e: float, - rho_p_eff: float, - kt: float, + sigma_s: float, + _alpha_e: float, + _rho_p_eff: float, + _kt: float, fct_eff: float, - es: float, + Es: float, ) -> float: """Returns the strain difference (esm - ecm) needed to compute the crack width. esm is the mean strain in the reinforcement under the relevant @@ -619,52 +612,52 @@ def esm_ecm( EUROCODE 2 1992-1-1:2004, Eq. (7.9) Args: - s_steel (float): is the stress in MPa in the tension reinforcement + sigma_s (float): is the stress in MPa in the tension reinforcement assuming a cracked section. FOr pretensioned members, s_steel may be replaced by increment of s_steel stress variation in prestressing tendons from the state of zero strain of the concrete at the same level. - alpha_e (float): is the ratio Es/Ecm - rho_p_eff (float): effective bond ratio between areas given by the + _alpha_e (float): is the ratio Es/Ecm + _rho_p_eff (float): effective bond ratio between areas given by the Eq. (7.10) - kt (float): is a factor dependent on the load duration + _kt (float): is a factor dependent on the load duration fct_eff (float): is the mean value of the tensile strength in MPa of the concrete effectvie at the time when the cracks may first be expected to occur: fct_eff=fctm or fctm(t) if crack is expected earlier than 28 days. - es: steel elastic mudulus in MPa + Es: steel elastic mudulus in MPa Returns: float: the strain difference between concrete and steel Raises: - ValueError: if any s_steel, alpha_e, rho_p_eff, fct_Eff is less + ValueError: if any sigma_s, alpha_e, rho_p_eff, fct_eff or Es is less than 0. ValueError: if kt is not 0.6 and not 0.4 """ - if s_steel < 0: - raise ValueError(f's_steel={s_steel} cannot be less than 0') - if alpha_e < 0: - raise ValueError(f'alpha_e={alpha_e} cannot be less than 0') - if rho_p_eff < 0: - raise ValueError(f'rho_p_eff={rho_p_eff} cannot be less than 0') + if sigma_s < 0: + raise ValueError(f'sigma_s={sigma_s} cannot be less than 0') + if _alpha_e < 0: + raise ValueError(f'_alpha_e={_alpha_e} cannot be less than 0') + if _rho_p_eff < 0: + raise ValueError(f'_rho_p_eff={_rho_p_eff} cannot be less than 0') if fct_eff < 0: raise ValueError(f'fct_eff={fct_eff} cannot be less than 0') - if es < 0: - raise ValueError(f'es={es} cannot be less than 0') - if kt != 0.6 and kt != 0.4: - raise ValueError(f'kt={kt} can only take as values 0.4 and 0.6') + if Es < 0: + raise ValueError(f'Es={Es} cannot be less than 0') + if _kt != 0.6 and _kt != 0.4: + raise ValueError(f'_kt={_kt} can only take as values 0.4 and 0.6') - min_val = 0.6 * s_steel / es + min_val = 0.6 * sigma_s / Es - a = 1 + alpha_e * rho_p_eff - b = kt * fct_eff / rho_p_eff * a - c = (s_steel - b) / es + a = 1 + _alpha_e * _rho_p_eff + b = _kt * fct_eff / _rho_p_eff * a + c = (sigma_s - b) / Es return max(c, min_val) -def s_threshold(c: float, phi: float) -> float: +def w_spacing(c: float, phi: float) -> float: """Computes the distance threshold from which the maximum crack spacing is constant. @@ -804,15 +797,15 @@ def k4(): def sr_max_close( c: float, phi: float, - rho_p_eff: float, - k1: float, - k2: float, - k3: float, - k4: float, + _rho_p_eff: float, + _k1: float, + _k2: float, + _k3: float, + _k4: float, ) -> float: """Computes the maximum crack spacing in cases where bonded reinforcement is fixed at reasonably close centres within the tension zone - (spacing<=5(c+phi/2)). + (w_spacing<=5(c+phi/2)). EUROCODE 2 1992-1-1:2004, Eq. (7.11) @@ -820,14 +813,14 @@ def sr_max_close( c (float): is the cover in mm of the longitudinal reinforcement phi (float): is the bar diameter in mm. Where mixed bar diameters used, then it should be replaced for an equivalente bar diameter. - rho_p_eff (float): effective bond ratio between areas given by the + _rho_p_eff (float): effective bond ratio between areas given by the Eq. (7.10) - k1 (float): coefficient that takes into account the bound properties + _k1 (float): coefficient that takes into account the bound properties of the bonded reinforcement - k2 (float): coefficient that takes into account the distribution of + _k2 (float): coefficient that takes into account the distribution of of the strain - k3 (float): coefficient from the National Annex - k4 (float): coefficient from the National Annex + _k3 (float): coefficient from the National Annex + _k4 (float): coefficient from the National Annex Returns: float: the maximum crack spaing in mm. @@ -842,24 +835,24 @@ def sr_max_close( raise ValueError(f'c={c} cannot be less than zero') if phi < 0: raise ValueError(f'phi={phi} cannot be less than zero') - if rho_p_eff < 0: - raise ValueError(f'rho_p_eff={rho_p_eff} cannot be less than zero') - if k3 < 0: - raise ValueError(f'k3={k3} cannot be less than zero') - if k4 < 0: - raise ValueError(f'k4={k4} cannot be less than zero') - if k1 != 0.8 and k1 != 1.6: - raise ValueError(f'k1={k1} can only take as values 0.8 and 1.6') - if k2 < 0.5 or k2 > 1: - raise ValueError(f'k2={k2} is not between 0.5 and 1.0') + if _rho_p_eff < 0: + raise ValueError(f'_rho_p_eff={_rho_p_eff} cannot be less than zero') + if _k3 < 0: + raise ValueError(f'_k3={_k3} cannot be less than zero') + if _k4 < 0: + raise ValueError(f'_k4={_k4} cannot be less than zero') + if _k1 != 0.8 and _k1 != 1.6: + raise ValueError(f'_k1={_k1} can only take as values 0.8 and 1.6') + if _k2 < 0.5 or _k2 > 1: + raise ValueError(f'_k2={_k2} is not between 0.5 and 1.0') - return k3 * c + k1 * k2 * k4 * phi / rho_p_eff + return _k3 * c + _k1 * _k2 * _k4 * phi / _rho_p_eff def sr_max_far(h: float, x: float) -> float: """Computes the maximum crack spacing in cases where bonded reinforcement is fixed at reasonably close centres within the tension zone - (spacing>5(c+phi/2)). + (w_spacing>5(c+phi/2)). EUROCODE 2 1992-1-1:2004, Eq. (7.14) @@ -915,14 +908,14 @@ def sr_max_theta(sr_max_y: float, sr_max_z: float, theta: float) -> float: return 1 / (a + b) -def wk(sr_max: float, esm_ecm: float) -> float: +def wk(sr_max: float, _esm_ecm: float) -> float: """Computes the crack width EUROCODE 2 1992-1-1:2004, Eq. (7.8) Args: sr_max (float): the maximum crack length spacing in mm. - esm_ecm (float): the difference between the mean strain in the + _esm_ecm (float): the difference between the mean strain in the reinforcement under relevant combination of loads, including the effect of imposed deformations and taking into account tension stiffening and the mean strain in the concrete @@ -932,11 +925,11 @@ def wk(sr_max: float, esm_ecm: float) -> float: float: crack width in mm. Raises: - ValueError: if any of sr_max or esm_ecm is less than zero. + ValueError: if any of sr_max or _esm_ecm is less than zero. """ if sr_max < 0: raise ValueError(f'sr_max={sr_max} cannot be less than zero') - if esm_ecm < 0: - raise ValueError(f'esm_scm={esm_ecm} cannot be less than zero') + if _esm_ecm < 0: + raise ValueError(f'_esm_scm={_esm_ecm} cannot be less than zero') - return sr_max * esm_ecm + return sr_max * _esm_ecm diff --git a/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py b/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py new file mode 100644 index 00000000..1de528de --- /dev/null +++ b/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py @@ -0,0 +1,933 @@ +"""Collection of functions from EUROCODE 1992-1-1:2004 +Chapter 7.3 - Crack control""" +import math +import typing as t + +import numpy as np +import scipy.interpolate + + +def w_max(exposure_class: str, load_combination: str) -> float: + """Computes the recomended value of the maximum crack width. + + EUROCODE 2 1992-1-1:2004, Table (7.1N) + + Args: + exposure_class (str): The exposure class. + Possible values: X0, XC1, XC2, XC3, XC4, XD1, XD2, XS1, XS2, XS3 + load_combination (str): + - f: for frequent load combination + - qp: for quasi-permanent load combination + + Returns: + float: The maximum recommended value for the crack width wmax in mm. + + Raises: + ValueError: if not valid exposure_class or load_combination values. + """ + _load_combination = load_combination.lower().strip() + _exposure_class = exposure_class.upper().strip() + if _load_combination == 'f': + if _exposure_class in ('X0', 'XC1'): + return 0.2 + if _exposure_class in ('XC2', 'XC3', 'XC4'): + return 0.2 + if _load_combination == 'qp': + if _exposure_class in ('X0', 'XC1'): + return 0.4 + if _exposure_class in ( + 'XC2', + 'XC3', + 'XC4', + 'XD1', + 'XD2', + 'XS1', + 'XS2', + 'XS3', + ): + return 0.3 + raise ValueError( + f'{exposure_class} is not a valid value for exposure_class.' + + ' Please enter one of the following: X0, XC1, XC2, XC3, XC4, XD1' + + ',XD2, XS1, XS2, XS3' + ) + raise ValueError( + f'{load_combination} is not a valid value for load_combination.' + + 'Please enter "f" for frequent load combination or "qp" for' + + 'quasi-permanent load combination.' + ) + + +def As_min( + A_ct: float, sigma_s: float, fct_eff: float, _k: float, kc: float +) -> float: + """Computes the minimum area of reinforcing steel within the tensile zone + for control of cracking areas + + EUROCODE 2 1992-1-1:2004, Eq. (7.1) + + Args: + A_ct (float): is the area of concrete within the tensile zone in mm2. + The tensile zone is that parg of the section which is calculated + to be in tension just before the formation of the first crack. + sigma_s (float): is the absolute value of the maximum stress in MPa + permitted in the reinforcement immediately after the formation + of the crack. This may be taken as theyield strength of the + reinforcement, fyk. A lower value may, however, be needed to + satisfy the crack width limits according to the maximum + bar size of spacing (see 7.3.3 (2)). + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + _k (float): is the coefficient which allow for the effect of + non-uniform self-equilibrating stresses, which lead to a + reduction of restraint forces. Use 'k_crack_min_steel_area' + to compute it + k=1 for webs w<=300mm or flanges widths less than 300mm + k=0.65 for webs w>=800mm or flanges with widths greater than 800mm + Intermediate values may be interpolated. + kc (float): is a coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm. + + Returns: + float: the minimm area of reinforcing steel within the tensile + zone in mm2. + + Raises: + ValueError: if _k value is not between 0.65 and 1 or kc is not + larger than 0 and lower than 1. + """ + fct_eff = abs(fct_eff) + + if A_ct <= 0: + raise ValueError(f'A_ct={A_ct} must be larger than 0') + if sigma_s < 0: + raise ValueError(f'sigma_s={sigma_s} must be equal or larger than 0') + if _k < 0.65 or _k > 1.0: + raise ValueError(f'_k={_k} must be between 0.65 and 1') + if kc > 1 or kc < 0: + raise ValueError(f'kc={kc} must be lower than 1 and larger than 0') + + return kc * _k * fct_eff * A_ct / sigma_s + + +def k(h: float) -> float: + """Is the coefficient which allow for the effect of + non-uniform self-equilibrating stresses, which lead to a + reduction of restraint forces. + k=1 for webs w<=300mm or flanges widths less than 300mm + k=0.65 for webs w>=800mm or flanges with widths greater than 800mm + + EUROCODE 2 1992-1-1:2004, Eq. (7.1) + + Args: + h (float): flange length or flange width in mm + + Returns: + float: k coefficient value + + Raises: + ValueError: if h is less than 0 + """ + if h < 0: + raise ValueError(f'h={h} cannot be less than 0mm') + if h <= 300: + return 1 + if h < 800: + interpol = scipy.interpolate.interp1d((300, 800), (1, 0.65)) + return (float)(interpol(h)) + return 0.65 + + +def kc_tension() -> float: + """Computes the coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm in pure dtension. + + EUROCODE 2 1992-1-1:2004, Eq. (7.1) + + Returns: + float: value of the kc coefficient in pure tension + """ + return 1 + + +def kc_rect_area(h: float, b: float, fct_eff: float, N_ed: float) -> float: + """Computes the coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm for bending+axial combination + in rectangular sections and webs of box sections and T-sections. + + EUROCODE 2 1992-1-1:2004, Eq. (7.2) + + Args: + h (float): heigth of the element in mm + b (float): width of the element in mm + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + N_ed (str): axial force at the serviceability limit state acting on + the part of the cross-section under consideration (compressive + force positive). n_ed should be determined considering the + characteristic values of prestress and axial forces under the + relevant combination of actions + + Returns: + float: value of the kc coefficient + + Raises: + ValueError: is h or b are less than 0 + """ + if h < 0: + raise ValueError(f'h={h} should be larger than 0mm') + if b < 0: + raise ValueError(f'b={b} should be larger than 0mm') + + h_s = min(h, 1000) + _k1 = 1.5 if N_ed >= 0 else 2 * h_s / 3 / h + s_concrete = N_ed * 1000 / b / h + h_ratio = h / h_s + return min(max(0.4 * (1 - s_concrete / _k1 / h_ratio / fct_eff), 0), 1) + + +def kc_flanges_area(f_cr: float, A_ct: float, fct_eff: float) -> float: + """Computes the coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm for bending+axial combination + in rectangular sections for flanges of box sections and T-sections. + + EUROCODE 2 1992-1-1:2004, Eq. (7.3) + + Args: + f_cr: is the absolute value in kN of the tensile force within the + flange immediately prior to cracking due to cracking moment + calculated with fct,eff + A_ct (float): is the area of concrete within the tensile zone in mm2. + The tensile zone is that part of the section which is calculated + to be in tension just before the formation of the first crack. + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + + Returns: + float: value of the kc coefficient + + Raises: + ValueError: is A_ct is less than 0mm2 + """ + f_cr = abs(f_cr) + return max(0.9 * f_cr * 1000 / A_ct / fct_eff, 0.5) + + +def xi1(xi: float, phi_p: float, phi_s: float) -> float: + """Computes the adjusted ratio of bond strength taking into account + the different diameters of prestressing and reinforcing steel. + + EUROCODE 2 1992-1-1:2004, Eq. (7.5) + + Args: + xi (float): ratio of bond strength of prestressing and reinforcing + steel, according to Table 6.2 in 6.8.2 + phi_p (float): largest bar diameter in mm of reinforcing steel. + Equal to 0 if only prestressing is used in control cracking + phi_s (float): equivalent diameter in mm of tendon acoording + to 6.8.2 + + Returns: + float: with the value of the ratio + + Raises: + ValueError: if diameters phi_s or phi_p are lower than 0. + If ratio of bond strength xi is less than 0.15 or larger than 0.8. + """ + + if phi_p <= 0: + raise ValueError(f'phi_p={phi_p} cannot be less than 0') + if phi_s < 0: + raise ValueError(f'phi_s={phi_s} cannot be less than 0') + if xi < 0.15: + raise ValueError(f'The minimum value for xi={xi} is 0.15') + if xi > 0.8: + raise ValueError(f'The maximum value for xi={xi} is 0.8') + + return ((xi * phi_s / phi_p) ** 0.5) if phi_s > 0 else xi**0.5 + + +def hc_eff(h: float, d: float, x: float) -> float: + """Returns the effective height of concrete in tension surrounding + the reinforcement or prestressing tendons. + + EUROCODE 2 1992-1-1:2004, Section (7.3.2-3) + + Args: + h (float): total depth of the element in mm + d (float): distance in mm to the level of the steel centroid + x (float): distance in mm to the zero tensile stress line + + Returns: + float: the effective height in mm + + Raises: + ValueError: if any of h, d or x is lower than zero. + ValueError: if d is greater than h + ValueError: if x is greater than h + """ + if h < 0: + raise ValueError(f'h={h} cannot be less than 0') + if d < 0: + raise ValueError(f'd={d} cannot be less than 0') + if x < 0: + raise ValueError(f'x={x} cannot be less than zero') + if d > h: + raise ValueError(f'd={d} cannot be larger than h={h}') + if x > h: + raise ValueError(f'x={x} cannot be larger than h={h}') + + return min(2.5 * (h - d), (h - x) / 3, h / 2) + + +def As_min_p( + A_ct: float, + sigma_s: float, + fct_eff: float, + _k: float, + kc: float, + Ap: float, + phi_s: float, + phi_p: float, + xi: float, + delta_s: float, +) -> float: + """Computes the minimum area of reinforcing steel within the tensile zone + for control of cracking areas in addition with bonded tendons + + EUROCODE 2 1992-1-1:2004, Eq. (7.1) + + Args: + A_ct (float): is the area of concrete within the tensile zone in mm2. + The tensile zone is that part of the section which is calculated + to be in tension just before the formation of the first crack. + sigma_s (float): is the absolute value of the maximum stress in MPa + permitted in the reinforcement immediately after the formation + of the crack. This may be taken as theyield strength of the + reinforcement, fyk. A lower value may, however, be needed to + satisfy the crack width limits according to the maximum + bar size of spacing (see 7.3.3 (2)). + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + _k (float): is the coefficient which allow for the effect of + non-uniform self-equilibrating stresses, which lead to a + reduction of restraint forces. Use 'k_crack_min_steel_area' + to compute it + k=1 for webs w<=300mm or flanges widths less than 300mm + k=0.65 for webs w>=800mm or flanges with widths greater than 800mm + Intermediate values may be interpolated. + kc (float): is a coefficient which takes account of the stress + distribution within the section immediately prior to cracking and + the change of the lever arm. + Ap (float): is the area in mm2 of pre or post-tensioned tendons + within ac_eff + phi_s (float): largest bar diameter in mm of reinforcing steel. + Equal to 0 if only prestressing is used in control cracking + phi_p (float): equivalent diameter in mm of tendon acoording + to 6.8.2 + chi (float): ratio of bond strength of prestressing and reinforcing + steel, according to Table 6.2 in 6.8.2 + delta_s (float): stress variation in MPa in prestressing tendons + from the state of zero strain of the concrete at the same level + + Returns: + float: the minimm area of reinforcing steel within the tensile + zone in mm2. + + Raises: + ValueError: if _k value is not between 0.65 and 1 or kc is not + larger than 0 and lower than 1. If diameters phi_s or + phi_p are lower than 0. If ratio of bond xi strength e + is less than 0.15 or larger than 0.8. + Is stress variation incr_stress is less than 0. + """ + fct_eff = abs(fct_eff) + + if Ap < 0: + raise ValueError(f'Ap={Ap} cannot be less than 0') + if delta_s < 0: + raise ValueError(f'delta_s={delta_s} cannot be less than 0') + if A_ct <= 0: + raise ValueError(f'A_ct={A_ct} must be larger than 0') + if sigma_s < 0: + raise ValueError(f'sigma_s={sigma_s} must be equal or larger than 0') + if _k < 0.65 or _k > 1.0: + raise ValueError(f'_k={_k} must be between 0.65 and 1') + if kc > 1 or kc < 0: + raise ValueError(f'kc={kc} must be lower than 1 and larger than 0') + + a1 = kc * _k * fct_eff * A_ct + e1 = xi1(xi, phi_p, phi_s) + a2 = e1 * Ap * delta_s + a = a1 - a2 + + return a / sigma_s + + +def As_min_2( + _wk: float, + sigma_s: float, + fct_eff: float, + h_cr: float, + h: float, + d: float, + delta_s: float = 0, + kc: t.Optional[float] = None, +) -> t.Tuple[float, float]: + """Computes the minimum area of reinforcing steel within the tensile zone + for control of cracking areas + + EUROCODE 2 1992-1-1:2004, Table (7.2N), Table (7.3N) + + Args: + wk (float): the characteristic crack width value in mm. + sigma_s (float): the steel stress value in MPa under the relevant + combination of actions. + fct_eff (float): is the mean value of the tensile strength in MPa of + the concrete effective at the time when the cracks may first be + expected to occur: fct,eff=fct or lower (fct(t)), is cracking + is expected earlier than 28 days. + h_cr (float): is the depth of the tensile zone immediately prior to + cracking, considering the characteristic values of prestress and + axial forces under the quasi-permanent combination of actions. + h (float): the overall depth of the section in mm. + d (float): is the effective depth to the centroid of the outer layer + of the reinforcement. + delta_s (float, optional): value of prestressed stress in MPa if + applicable + kc (float, optional): is a coefficient which takes account of the + stress distribution within the section immediately prior to + cracking and the change of the lever arm in a bending section. + 'None' for pure tensile uniform axial section. + + Returns: + tuple(float, float): with the value of the maximum bar diameters in mm + in the first position and the maximum bar spacing in mm in the + second position + Raises: + ValueError: if wk, fct_eff, h_cr, h or d are less than 0 + ValueError: if kc is not between 0 and 1 + ValueError: if combination of wk and stress values are out of scope + """ + if _wk < 0: + raise ValueError(f'_wk={_wk} cannot be less than 0') + if fct_eff < 0: + raise ValueError(f'fct_eff={fct_eff} is less than 0') + if h_cr < 0: + raise ValueError(f'h_cr={h_cr} is less than 0') + if h < 0: + raise ValueError(f'h={h} is less than 0') + if d < 0: + raise ValueError(f'd={d} is less than 0') + if kc is not None and (kc < 0 or kc > 1): + raise ValueError(f'kc={kc} is not between 0 and 1') + + s = sigma_s - delta_s + if s <= 0: + return (0, 0) + + x = (0.4, 0.3, 0.2) + y_phi = (160, 200, 240, 280, 320, 360, 400, 450) + y_spa = (160, 200, 240, 280, 320, 360) + phi_s_v = ( + 40, + 32, + 25, + 32, + 25, + 16, + 20, + 16, + 12, + 16, + 12, + 8, + 12, + 10, + 6, + 10, + 8, + 5, + 8, + 6, + 4, + 6, + 5, + None, + ) + spa_v = ( + 300, + 300, + 200, + 300, + 250, + 150, + 250, + 200, + 100, + 200, + 150, + 50, + 150, + 100, + None, + 100, + 50, + None, + ) + + points_phi = np.array(np.meshgrid(y_phi, x)).T.reshape(-1, 2) + points_spa = np.array(np.meshgrid(y_spa, x)).T.reshape(-1, 2) + xi = (s, _wk) + + phi_star = float( + scipy.interpolate.griddata(points_phi, phi_s_v, xi, method='linear') + ) + if kc is not None: + phi = phi_star * (fct_eff / 2.9) * kc * h_cr / (2 * (h - d)) + else: + phi = phi_star * (fct_eff / 2.9) * h_cr / (8 * (h - d)) + + spa = float( + scipy.interpolate.griddata(points_spa, spa_v, xi, method='linear') + ) + + if math.isnan(phi) or math.isnan(spa): + raise ValueError('Combination of _wk or stress values out of scope') + + return phi, spa + + +def alpha_e(Es: float, Ecm: float) -> float: + """Compute the ratio between the steel and mean concrete + elastic modules. + + EUROCODE 2 1992-1-1:2004, Section 7.3.4-2 + + Args: + Es (float): steel elastic modulus in MPa + Ecm (float): concrete mean elastic modulus in MPa + + Returns: + float: ratio between modules + Raise: + ValueError: if any of es or ecm is lower than 0. + """ + if Es < 0: + raise ValueError(f'Es={Es} cannot be less than 0') + if Ecm < 0: + raise ValueError(f'Ecm={Ecm} cannot be less than 0') + + return Es / Ecm + + +def rho_p_eff(As: float, _xi1: float, Ap: float, Ac_eff: float) -> float: + """Effective bond ratio between areas + + EUROCODE 2 1992-1-1:2004, Eq. (7.10) + + Args: + As (float): steel area in mm2 + _xi1 (float): the adjusted ratio of bond according + to expression (7.5) + Ap (float): the area in mm2 of post-tensioned tendons in ac_eff + Ac_eff (float): effective area of concrete in tension surrounding + the reinforcement or prestressing tendons of depth hc_eff. + + Returns: + float: with the retio between areas + + + Raise: + ValueError: if any of As, xi1, Ap or Ac_eff is less than 0 + """ + if As < 0: + raise ValueError(f'As={As} cannot be less than 0') + if _xi1 < 0: + raise ValueError(f'_xi1={_xi1} cannot be less than 0') + if Ap < 0: + raise ValueError(f'Ap={Ap} cannot be less than 0') + if Ac_eff < 0: + raise ValueError(f'Ac_eff={Ac_eff} cannot be less than 0') + + return (As + _xi1**2 * Ap) / Ac_eff + + +def kt(load_type: str) -> float: + """Returns the kt factor dependent on the load duration for + the crack width calculation + + Args: + load_type (str): the load type: + - 'short' for term loading + - 'long' for long term loading + + Returns: + float: with the kt factor + + Raises: + ValueError: if load_type is not 'short' and not 'long' + """ + if not isinstance(load_type, str): + raise TypeError + + load_type = load_type.lower().strip() + if load_type != 'short' and load_type != 'long': + raise ValueError( + f'load_type={load_type} can only have "short" or "long" as a value' + ) + + return 0.6 if load_type == 'short' else 0.4 + + +def esm_ecm( + sigma_s: float, + _alpha_e: float, + _rho_p_eff: float, + _kt: float, + fct_eff: float, + Es: float, +) -> float: + """Returns the strain difference (esm - ecm) needed to compute the crack + width. esm is the mean strain in the reinforcement under the relevant + combination of loads of imposed deformations and taking into account the + effects of tension stiffening. Only the additional tensile strain beyond + the state of zero strain of the concrete is considered. ecm is the mean + strain in the concrete between the cracks. + + EUROCODE 2 1992-1-1:2004, Eq. (7.9) + + Args: + sigma_s (float): is the stress in MPa in the tension reinforcement + assuming a cracked section. FOr pretensioned members, s_steel may + be replaced by increment of s_steel stress variation in + prestressing tendons from the state of zero strain of the + concrete at the same level. + _alpha_e (float): is the ratio Es/Ecm + _rho_p_eff (float): effective bond ratio between areas given by the + Eq. (7.10) + _kt (float): is a factor dependent on the load duration + fct_eff (float): is the mean value of the tensile strength in MPa + of the concrete effectvie at the time when the cracks may + first be expected to occur: fct_eff=fctm or fctm(t) if + crack is expected earlier than 28 days. + Es: steel elastic mudulus in MPa + + Returns: + float: the strain difference between concrete and steel + + Raises: + ValueError: if any sigma_s, _alpha_e, _rho_p_eff, fct_eff or Es is less + than 0. + ValueError: if _kt is not 0.6 and not 0.4 + """ + if sigma_s < 0: + raise ValueError(f'sigma_s={sigma_s} cannot be less than 0') + if _alpha_e < 0: + raise ValueError(f'_alpha_e={_alpha_e} cannot be less than 0') + if _rho_p_eff < 0: + raise ValueError(f'_rho_p_eff={_rho_p_eff} cannot be less than 0') + if fct_eff < 0: + raise ValueError(f'fct_eff={fct_eff} cannot be less than 0') + if Es < 0: + raise ValueError(f'Es={Es} cannot be less than 0') + if _kt != 0.6 and _kt != 0.4: + raise ValueError(f'_kt={_kt} can only take as values 0.4 and 0.6') + + min_val = 0.6 * sigma_s / Es + + a = 1 + _alpha_e * _rho_p_eff + b = _kt * fct_eff / _rho_p_eff * a + c = (sigma_s - b) / Es + + return max(c, min_val) + + +def w_spacing(c: float, phi: float) -> float: + """Computes the distance threshold from which the + maximum crack spacing is constant. + + EUROCODE 2 1992-1-1:2004, Sect. (7.3.4-3) + + Args: + c (float): cover of the longitudinal reinforcement in mm + phi (float): is the bar diameter in mm. Where mixed bar diameters + used, then it should be replaced for an equivalente bar diameter. + + Returns: + float: threshold distance in mm + + Raises: + ValueError: if any of c or phi is less than 0. + """ + if c < 0: + raise ValueError(f'c={c} cannot be less than 0') + if phi < 0: + raise ValueError(f'phi={phi} cannot be less than 0') + + return 5 * (c + phi / 2) + + +def phi_eq(n1: int, n2: int, phi1: float, phi2: float) -> float: + """Computes the equivalent diameter. For a section with n1 bars of + diameter phi1 and n2 bars of diameter phi2 + + EUROCODE 2 1992-1-1:2004, Sect. (7.12) + + Args: + n1 (int): number of bars with diameter phi1 + n2 (int): number of bars with diameter phi2 + phi1 (float): diameter of n1 bars in mm + phi2 (float): diamater of n2 bars in mm + + Returns: + float: the equivalent diameter in mm + + Raises: + ValueError: if any of n1 or n2 is less than 0 + ValueError: if any of phi1 or phi2 is less than 0 + TypeError: if any of n1 or n2 is not an integer + """ + if n1 < 0: + raise ValueError(f'n1={n1} cannot be less than 0') + if not isinstance(n1, int): + raise TypeError(f'n1={n1} needs to be an integer value') + if n2 < 0: + raise ValueError(f'n2={n2} cannot be less than 0') + if not isinstance(n2, int): + raise TypeError(f'n2={n2} needs to be an integer value') + if phi1 < 0: + raise ValueError(f'phi1={phi1} cannot be less than 0') + if phi2 < 0: + raise ValueError(f'phi2={phi2} cannot be less than 0') + + a = n1 * phi1**2 + n2 * phi2**2 + b = n1 * phi1 + n2 * phi2 + return a / b + + +def k1(bond_type: str) -> float: + """Get the k1 coefficient which takes account of the bond properties + of the bounded reinforcement + + EUROCODE 2 1992-1-1:2004, Eq. (7.11-k1) + + Args: + bond_type (str): the bond property of the reinforcement. + Possible values: + - 'bond': for high bond bars + - 'plane': for bars with an effectively plain surface (e.g. + prestressing tendons) + + Returns: + (float): value of the k1 coefficient + + Raises: + ValueError: if bond_type is neither 'bond' nor 'plane' + TypeError: if bond_type is not an str + """ + if not isinstance(bond_type, str): + raise TypeError(f'bond_type={bond_type} is not an str') + + bond_type = bond_type.lower().strip() + if bond_type != 'bond' and bond_type != 'plane': + raise ValueError( + f'bond_type={bond_type} can only have "bond" or "plane" as values' + ) + + return 0.8 if bond_type == 'bond' else 1.6 + + +def k2(epsilon_r: float) -> float: + """Computes a coefficient which takes into account of the + distribution of strain: + + EUROCODE 2 1992-1-1:2004, Eq. (7.13) + + Args: + epsilon_r (float): ratio epsilon_2/epsilon_1 where epsilon_1 is + thre greater and epsilon_2 is the lesser strain at the boundaries + of the section considererd, assessed on the basis of a cracked + section. epsilon_r=0 for bending and epsilon_r=1 for pure tension. + + Returns: + float: the k2 coefficient value. + + Raises: + ValueError: if epsilon_r is not between 0 and 1. + """ + if epsilon_r < 0 or epsilon_r > 1: + raise ValueError(f'epsilon_r={epsilon_r} must be between 0 and 1') + + return (1 + epsilon_r) / 2 + + +def k3(): + """Returns the k3 coefficient for computing sr_max + + Returns: + float: value for the coefficient + """ + return 3.4 + + +def k4(): + """Returns the k4 coefficient for computing sr_max + + Returns: + float: value for the coefficient + """ + return 0.425 + + +def sr_max_close( + c: float, + phi: float, + _rho_p_eff: float, + _k1: float, + _k2: float, + _k3: float, + _k4: float, +) -> float: + """Computes the maximum crack spacing in cases where bonded reinforcement + is fixed at reasonably close centres within the tension zone + (w_spacing<=5(c+phi/2)). + + EUROCODE 2 1992-1-1:2004, Eq. (7.11) + + Args: + c (float): is the cover in mm of the longitudinal reinforcement + phi (float): is the bar diameter in mm. Where mixed bar diameters + used, then it should be replaced for an equivalente bar diameter. + _rho_p_eff (float): effective bond ratio between areas given by the + Eq. (7.10) + _k1 (float): coefficient that takes into account the bound properties + of the bonded reinforcement + _k2 (float): coefficient that takes into account the distribution of + of the strain + _k3 (float): coefficient from the National Annex + _k4 (float): coefficient from the National Annex + + Returns: + float: the maximum crack spaing in mm. + + Raises: + ValueError: if one or more of c, phi, _rho_p_eff, _k3 or _k4 + is lower than zero. + ValueError: if _k1 is not 0.8 or 1.6 + ValueError: if _k2 is not between 0.5 and 1.0 + """ + if c < 0: + raise ValueError(f'c={c} cannot be less than zero') + if phi < 0: + raise ValueError(f'phi={phi} cannot be less than zero') + if _rho_p_eff < 0: + raise ValueError(f'_rho_p_eff={_rho_p_eff} cannot be less than zero') + if _k3 < 0: + raise ValueError(f'_k3={_k3} cannot be less than zero') + if _k4 < 0: + raise ValueError(f'_k4={_k4} cannot be less than zero') + if _k1 != 0.8 and _k1 != 1.6: + raise ValueError(f'_k1={_k1} can only take as values 0.8 and 1.6') + if _k2 < 0.5 or _k2 > 1: + raise ValueError(f'_k2={_k2} is not between 0.5 and 1.0') + + return _k3 * c + _k1 * _k2 * _k4 * phi / _rho_p_eff + + +def sr_max_far(h: float, x: float) -> float: + """Computes the maximum crack spacing in cases where bonded reinforcement + is fixed at reasonably close centres within the tension zone + (w_spacing>5(c+phi/2)). + + EUROCODE 2 1992-1-1:2004, Eq. (7.14) + + Args: + h (float): total depth of the beam in mm + x (float): distance to non tension area of the element mm + + Returns: + float: maximum crack spacing in mm + + Raises: + ValueError: if one of h or x is less than zero. + ValueError: x is greater than h. + """ + if x < 0: + raise ValueError(f'x={x} cannot be less than zero') + if h < 0: + raise ValueError(f'h={h} cannot be less than zero') + if x > h: + raise ValueError(f'x={x} cannot be larger than h={h}') + + return 1.3 * (h - x) + + +def sr_max_theta(sr_max_y: float, sr_max_z: float, theta: float) -> float: + """Computes the crack spacing sr_max when there is an angle + between the angle of principal stress and the direction + of the reinforcement, for members in two orthogonal directions, + that is significant (> 15º). + + EUROCODE 2 1992-1-1:2004, Eq. (7.15) + + Args: + sr_max_y (float): crack spacing in mm in the y-direction. + sr_max_z (float): crack spacing in mm in the z-direction. + theta (float): angle in radians between the reinforcement in the + y-direction and the direction of the principal tensile stress. + + Returns: + float: the crack spacing in mm. + + Raises: + ValueError: if sr_max_y or sr_max_z is negative. + ValueError: if theta is not between 0 and pi/2 + """ + if sr_max_y < 0: + raise ValueError(f'sr_max_y={sr_max_y} cannot be less than zero') + if sr_max_z < 0: + raise ValueError(f'sr_max_z={sr_max_z} cannot be less than zero') + + a = math.cos(theta) / sr_max_y + b = math.sin(theta) / sr_max_z + return 1 / (a + b) + + +def wk(sr_max: float, _esm_ecm: float) -> float: + """Computes the crack width + + EUROCODE 2 1992-1-1:2004, Eq. (7.8) + + Args: + sr_max (float): the maximum crack length spacing in mm. + _esm_ecm (float): the difference between the mean strain in the + reinforcement under relevant combination of loads, including + the effect of imposed deformations and taking into account + tension stiffening and the mean strain in the concrete + between cracks. + + Returns: + float: crack width in mm. + + Raises: + ValueError: if any of sr_max or esm_ecm is less than zero. + """ + if sr_max < 0: + raise ValueError(f'sr_max={sr_max} cannot be less than zero') + if _esm_ecm < 0: + raise ValueError(f'_esm_scm={_esm_ecm} cannot be less than zero') + + return sr_max * _esm_ecm diff --git a/tests/test_ec2_2004_crack_control.py b/tests/test_ec2_2004_section_7_3_crack_control.py similarity index 83% rename from tests/test_ec2_2004_crack_control.py rename to tests/test_ec2_2004_section_7_3_crack_control.py index 66e8adeb..54b14fc7 100644 --- a/tests/test_ec2_2004_crack_control.py +++ b/tests/test_ec2_2004_section_7_3_crack_control.py @@ -2,7 +2,7 @@ import math import pytest -from structuralcodes.codes.ec2_2004 import _crack_control +from structuralcodes.codes.ec2_2004 import _section_7_3_crack_control @pytest.mark.parametrize( @@ -40,7 +40,9 @@ def test_w_max_returns_expected_values( test_exposure_class, test_load_combination, expected ): """Test that the w_max function returns expected values""" - w_max = _crack_control.w_max(test_exposure_class, test_load_combination) + w_max = _section_7_3_crack_control.w_max( + test_exposure_class, test_load_combination + ) assert w_max == expected @@ -53,7 +55,9 @@ def test_w_max_not_valid_input_raises_valueerror( ): """Test that not valid input returns ValueError""" with pytest.raises(ValueError): - _crack_control.w_max(test_exposure_class, test_load_combination) + _section_7_3_crack_control.w_max( + test_exposure_class, test_load_combination + ) @pytest.mark.parametrize( @@ -71,7 +75,7 @@ def test_w_max_not_valid_input_raises_valueerror( ) def test_k_crack_min_steel_area_returns_expected_values(h, expected): """Test the k_crack_min_steel_area function""" - k = _crack_control.k_crack_min_steel_area(h) + k = _section_7_3_crack_control.k(h) assert math.isclose(k, expected) @@ -79,12 +83,12 @@ def test_k_crack_min_steel_area_raises_valueerror(): """Test that not valid input returns ValueError exeption""" with pytest.raises(ValueError): h = -100 - _crack_control.k_crack_min_steel_area(h) + _section_7_3_crack_control.k(h) def test_kc_crack_min_steel_area_pure_tension_returns_expected_values(): """Test the kc_crack_min_steel_area_pure_tension function""" - assert 1 == _crack_control.kc_crack_min_steel_area_pure_tension() + assert 1 == _section_7_3_crack_control.kc_tension() @pytest.mark.parametrize( @@ -101,7 +105,7 @@ def test_kc_crack_min_steel_area_rectangular_returns_expected_values( h, b, fct_eff, n_ed, expected ): """Test the kc_crack_min_steel_area_rectangular""" - kc = _crack_control.kc_crack_min_steel_area_rectangular( + kc = _section_7_3_crack_control.kc_rect_area( h, b, fct_eff, @@ -114,11 +118,11 @@ def test_kc_crack_min_steel_area_rectangular_raises_valueerror(): """Test the kc_crack_min_steel_area_rectangular raises Value Error for not correct input values for b and h""" with pytest.raises(ValueError): - _crack_control.kc_crack_min_steel_area_rectangular( - h=-100, b=100, fct_eff=100, n_ed=10 + _section_7_3_crack_control.kc_rect_area( + h=-100, b=100, fct_eff=100, N_ed=10 ) - _crack_control.kc_crack_min_steel_area_rectangular( - h=100, b=-100, fct_eff=100, n_ed=10 + _section_7_3_crack_control.kc_rect_area( + h=100, b=-100, fct_eff=100, N_ed=10 ) @@ -133,7 +137,7 @@ def test_kc_crack_min_steel_area_rectangular_raises_valueerror(): ) def test_kc_crack_min_steel_area_flanges(f_cr, a_ct, fct_eff, expected): """Test the kc_crack_min_steel_area_flanges function""" - kc = _crack_control.kc_crack_min_steel_area_flanges(f_cr, a_ct, fct_eff) + kc = _section_7_3_crack_control.kc_flanges_area(f_cr, a_ct, fct_eff) assert math.isclose(kc, expected, rel_tol=0.000001) @@ -145,11 +149,11 @@ def test_kc_crack_min_steel_area_flanges(f_cr, a_ct, fct_eff, expected): (80000, 400, 4, 0.9, 0.75, 540), ], ) -def test_crack_min_steel_area_returns_expected_values( +def test_As_min_returns_expected_values( a_ct, s_steel, fct_eff, k, kc, expected ): - """Test the crack_min_steel_area returns expected values""" - as_min = _crack_control.crack_min_steel_area(a_ct, s_steel, fct_eff, k, kc) + """Test the As_min returns expected values""" + as_min = _section_7_3_crack_control.As_min(a_ct, s_steel, fct_eff, k, kc) assert math.isclose(as_min, expected, rel_tol=10e-6) @@ -164,10 +168,10 @@ def test_crack_min_steel_area_returns_expected_values( (10000, 100, 3, 0.7, 1.1), ], ) -def test_crack_min_steel_area_raises_valueerror(a_ct, s_steel, fct_eff, k, kc): - """Test the crack_min_steel_area raises value error""" +def test_crack_As_min_raises_valueerror(a_ct, s_steel, fct_eff, k, kc): + """Test the As_min raises value error""" with pytest.raises(ValueError): - _crack_control.crack_min_steel_area(a_ct, s_steel, fct_eff, k, kc) + _section_7_3_crack_control.As_min(a_ct, s_steel, fct_eff, k, kc) @pytest.mark.parametrize( @@ -195,7 +199,7 @@ def test_crack_min_steel_area_with_press_tendons_returns_expected_values( expected, ): """Test the crack_min_steel_area returns expected values""" - as_min = _crack_control.crack_min_steel_area_with_prestresed_tendons( + as_min = _section_7_3_crack_control.As_min_p( a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, incr_stress ) assert math.isclose(as_min, expected, rel_tol=10e-6) @@ -222,7 +226,7 @@ def test_crack_min_steel_area_with_press_tendons_raise_valueerror( ): """Test the crack_min_steel_area raise ValueError for non valid values""" with pytest.raises(ValueError): - _crack_control.crack_min_steel_area_with_prestresed_tendons( + _section_7_3_crack_control.As_min_p( a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, incr_stress ) @@ -249,7 +253,7 @@ def test_crack_min_steel_without_direct_calculation_returns_expected_values( exp_sep, ): """Test the crack_min_steel_area raise ValueError for non valid values""" - phi, sep = _crack_control.crack_min_steel_without_direct_calculation( + phi, sep = _section_7_3_crack_control.As_min_2( wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc ) assert math.isclose(phi, exp_phi, rel_tol=10e-6) @@ -274,7 +278,7 @@ def test_crack_min_steel_without_direct_calculation_raise_valueerror( ): """Test the crack_min_steel_area raise ValueError for non valid values""" with pytest.raises(ValueError): - _crack_control.crack_min_steel_without_direct_calculation( + _section_7_3_crack_control.As_min_2( wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc ) @@ -292,7 +296,7 @@ def test_adjusted_bond_length_return_expected_values( ): """Test the adjusted_bond_length_function returns expected values""" assert math.isclose( - _crack_control.adjusted_bond_strength(e, d_press, d_steel), + _section_7_3_crack_control.xi1(e, d_press, d_steel), expected, rel_tol=10e-5, ) @@ -311,7 +315,7 @@ def test_adjusted_bond_length_return_expected_values( def test_adjusted_bond_length_raise_valuerror(e, d_press, d_steel): """Test the adjusted_bond_length_function raises exceptions""" with pytest.raises(ValueError): - _crack_control.adjusted_bond_strength(e, d_press, d_steel) + _section_7_3_crack_control.xi1(e, d_press, d_steel) @pytest.mark.parametrize( @@ -325,7 +329,7 @@ def test_adjusted_bond_length_raise_valuerror(e, d_press, d_steel): def test_hc_eff_concrete_tension_returns_expected_values(h, d, x, expected): """Test the hc_eff_concrete_tension returns expected results""" assert math.isclose( - _crack_control.hc_eff_concrete_tension(h, d, x), + _section_7_3_crack_control.hc_eff(h, d, x), expected, rel_tol=10e-5, ) @@ -344,7 +348,7 @@ def test_hc_eff_concrete_tension_returns_expected_values(h, d, x, expected): def test_hc_eff_concrete_tension_raise_exceptions(h, d, x): """Test hc_eff_concrete tension raises expected exceptions""" with pytest.raises(ValueError): - _crack_control.hc_eff_concrete_tension(h, d, x) + _section_7_3_crack_control.hc_eff(h, d, x) @pytest.mark.parametrize( @@ -356,7 +360,7 @@ def test_hc_eff_concrete_tension_raise_exceptions(h, d, x): def test_alpha_e_returns_expected_values(es, ecm, expected): """Test alpha_e returns expected values""" assert math.isclose( - _crack_control.get_alpha_e(es, ecm), + _section_7_3_crack_control.alpha_e(es, ecm), expected, rel_tol=10e-5, ) @@ -372,7 +376,7 @@ def test_alpha_e_returns_expected_values(es, ecm, expected): def test_alpha_e_raise_exceptions(es, ecm): """Test alpha_e raises exceptions""" with pytest.raises(ValueError): - _crack_control.get_alpha_e(es, ecm) + _section_7_3_crack_control.alpha_e(es, ecm) @pytest.mark.parametrize( @@ -385,7 +389,7 @@ def test_alpha_e_raise_exceptions(es, ecm): def test_rho_p_eff_returns_expected_values(a_s, e1, a_p, ac_eff, expected): """Test rho_p_eff returns expeceted values""" assert math.isclose( - _crack_control.rho_p_eff(a_s, e1, a_p, ac_eff), + _section_7_3_crack_control.rho_p_eff(a_s, e1, a_p, ac_eff), expected, rel_tol=10e-5, ) @@ -403,7 +407,7 @@ def test_rho_p_eff_returns_expected_values(a_s, e1, a_p, ac_eff, expected): def test_rho_p_eff_raise_value_error(a_s, e1, a_p, ac_eff): """Test rho_p_eff raise exceptions""" with pytest.raises(ValueError): - _crack_control.rho_p_eff(a_s, e1, a_p, ac_eff) + _section_7_3_crack_control.rho_p_eff(a_s, e1, a_p, ac_eff) @pytest.mark.parametrize( @@ -415,16 +419,16 @@ def test_rho_p_eff_raise_value_error(a_s, e1, a_p, ac_eff): ) def test_kt_load_duration_returns_expected_values(load_type, expected): """Test kt_load_duration returns expected values""" - assert _crack_control.kt_load_duration(load_type) == expected + assert _section_7_3_crack_control.kt(load_type) == expected def test_kt_load_duration_raise_value_errors(): """Test kt_load_duration raise value errors""" with pytest.raises(TypeError): - _crack_control.kt_load_duration(load_type=123) + _section_7_3_crack_control.kt(load_type=123) with pytest.raises(ValueError): - _crack_control.kt_load_duration(load_type='asdf') + _section_7_3_crack_control.kt(load_type='asdf') @pytest.mark.parametrize( @@ -440,7 +444,9 @@ def test_esm_ecm_returns_expected_values( ): """Test esm_ecm returns the expected values""" assert math.isclose( - _crack_control.esm_ecm(s_steel, alpha_e, rho_p_eff, kt, fct_eff, es), + _section_7_3_crack_control.esm_ecm( + s_steel, alpha_e, rho_p_eff, kt, fct_eff, es + ), expected, abs_tol=10e-5, ) @@ -457,10 +463,19 @@ def test_esm_ecm_returns_expected_values( (250, 5.25, 0.34, 0.2, 2.9, 210000), ], ) -def test_esm_ecm_raises_exception(s_steel, alpha_e, rho_p_eff, kt, fct_eff, es): +def test_esm_ecm_raises_exception( + s_steel, + alpha_e, + rho_p_eff, + kt, + fct_eff, + es, +): """Test esm_ecm raise expected exceptions""" with pytest.raises(ValueError): - _crack_control.esm_ecm(s_steel, alpha_e, rho_p_eff, kt, fct_eff, es) + _section_7_3_crack_control.esm_ecm( + s_steel, alpha_e, rho_p_eff, kt, fct_eff, es + ) @pytest.mark.parametrize( @@ -473,7 +488,7 @@ def test_esm_ecm_raises_exception(s_steel, alpha_e, rho_p_eff, kt, fct_eff, es): def test_s_returns_expected_returns(c, phi, expected): """Test s returns expected results""" assert math.isclose( - _crack_control.s_threshold(c, phi), + _section_7_3_crack_control.w_spacing(c, phi), expected, rel_tol=10e-5, ) @@ -489,7 +504,7 @@ def test_s_returns_expected_returns(c, phi, expected): def test_s_raise_expected_exceptions(c, phi): """Test s raise expected exceptions""" with pytest.raises(ValueError): - _crack_control.s_threshold(c, phi) + _section_7_3_crack_control.w_spacing(c, phi) @pytest.mark.parametrize( @@ -499,7 +514,7 @@ def test_s_raise_expected_exceptions(c, phi): def test_phi_eq_returns_expected_results(n1, n2, phi1, phi2, expected): """Test phi_eq returns expected results""" assert math.isclose( - _crack_control.phi_eq(n1, n2, phi1, phi2), + _section_7_3_crack_control.phi_eq(n1, n2, phi1, phi2), expected, rel_tol=10e-5, ) @@ -519,7 +534,7 @@ def test_phi_eq_returns_expected_results(n1, n2, phi1, phi2, expected): def test_phi_eq_raises_expected_values(n1, n2, phi1, phi2, exception_type): """Test phi_eq raises expected exception""" with pytest.raises(exception_type): - _crack_control.phi_eq(n1, n2, phi1, phi2) + _section_7_3_crack_control.phi_eq(n1, n2, phi1, phi2) @pytest.mark.parametrize( @@ -528,7 +543,7 @@ def test_phi_eq_raises_expected_values(n1, n2, phi1, phi2, exception_type): ) def test_k1_returns_expected_values(bond_type, expected): """Test k1 returns expected values""" - assert _crack_control.k1(bond_type) == expected + assert _section_7_3_crack_control.k1(bond_type) == expected @pytest.mark.parametrize( @@ -538,7 +553,7 @@ def test_k1_returns_expected_values(bond_type, expected): def test_k1_raise_expected_exceptions(bond_type, exception_type): """Test k1 raises expected exceptions""" with pytest.raises(exception_type): - _crack_control.k1(bond_type) + _section_7_3_crack_control.k1(bond_type) @pytest.mark.parametrize( @@ -548,7 +563,7 @@ def test_k1_raise_expected_exceptions(bond_type, exception_type): def test_k2_returns_expected_values(epsilon_r, expected): """Test k2 returns expected values""" assert math.isclose( - _crack_control.k2(epsilon_r), + _section_7_3_crack_control.k2(epsilon_r), expected, rel_tol=10e-5, ) @@ -558,17 +573,17 @@ def test_k2_returns_expected_values(epsilon_r, expected): def test_k2_raises_value_exceptions(epsilon_r): """Test k2 raises expected exceptions""" with pytest.raises(ValueError): - _crack_control.k2(epsilon_r) + _section_7_3_crack_control.k2(epsilon_r) def test_k3_returns_expected_values(): """Test k3 returns the expected values""" - assert _crack_control.k3() == 3.4 + assert _section_7_3_crack_control.k3() == 3.4 def test_k4_returns_expected_values(): """Test k4 returns the expected values""" - assert _crack_control.k4() == 0.425 + assert _section_7_3_crack_control.k4() == 0.425 @pytest.mark.parametrize( @@ -582,7 +597,9 @@ def test_k4_returns_expected_values(): def test_sr_max_close(c, phi, rho_p_eff, k1, k2, k3, k4, expected): """Test sr_max_close returns the expected values""" assert math.isclose( - _crack_control.sr_max_close(c, phi, rho_p_eff, k1, k2, k3, k4), + _section_7_3_crack_control.sr_max_close( + c, phi, rho_p_eff, k1, k2, k3, k4 + ), expected, rel_tol=10e-5, ) @@ -606,7 +623,9 @@ def test_sr_max_close(c, phi, rho_p_eff, k1, k2, k3, k4, expected): def test_sr_max_close_raises_exceptions(c, phi, rho_p_eff, k1, k2, k3, k4): """Test sr_max_close raises the expected value errors""" with pytest.raises(ValueError): - _crack_control.sr_max_close(c, phi, rho_p_eff, k1, k2, k3, k4) + _section_7_3_crack_control.sr_max_close( + c, phi, rho_p_eff, k1, k2, k3, k4 + ) @pytest.mark.parametrize( @@ -620,7 +639,7 @@ def test_sr_max_close_raises_exceptions(c, phi, rho_p_eff, k1, k2, k3, k4): def test_sr_max_far_returns_expected_values(h, x, expected): """Test sr_max_far returns the expected values""" assert math.isclose( - _crack_control.sr_max_far(h, x), expected, rel_tol=10e-5 + _section_7_3_crack_control.sr_max_far(h, x), expected, rel_tol=10e-5 ) @@ -635,7 +654,7 @@ def test_sr_max_far_returns_expected_values(h, x, expected): def test_sr_max_far_raises_exceptions(h, x): """Test sr_max_far raises exceptions""" with pytest.raises(ValueError): - _crack_control.sr_max_far(h, x) + _section_7_3_crack_control.sr_max_far(h, x) @pytest.mark.parametrize( @@ -651,7 +670,7 @@ def test_sr_max_theta_returns_expected_values( ): """Test sr_max_theta returns expeceted values""" assert math.isclose( - _crack_control.sr_max_theta(sr_max_y, sr_max_z, theta), + _section_7_3_crack_control.sr_max_theta(sr_max_y, sr_max_z, theta), expected, rel_tol=10e-5, ) @@ -668,7 +687,7 @@ def test_sr_max_theta_returns_expected_values( def test_sr_max_theta_raises_exceptions(sr_max_y, sr_max_z, theta): """Test sr_max_theta raises value errors""" with pytest.raises(ValueError): - _crack_control.sr_max_theta(sr_max_y, sr_max_z, theta) + _section_7_3_crack_control.sr_max_theta(sr_max_y, sr_max_z, theta) @pytest.mark.parametrize( @@ -681,7 +700,7 @@ def test_sr_max_theta_raises_exceptions(sr_max_y, sr_max_z, theta): def test_wk_returns_expected_values(sr_max, esm_ecm, expected): """Test wk returns expected values""" assert math.isclose( - _crack_control.wk(sr_max, esm_ecm), + _section_7_3_crack_control.wk(sr_max, esm_ecm), expected, rel_tol=10e-5, ) @@ -694,4 +713,4 @@ def test_wk_returns_expected_values(sr_max, esm_ecm, expected): def test_wk_raises_exceptions(sr_max, esm_ecm: float): """Test wk raises value errors""" with pytest.raises(ValueError): - _crack_control.wk(sr_max, esm_ecm) + _section_7_3_crack_control.wk(sr_max, esm_ecm) From 938c0f5807cf6a12a11a0ddc8f004422172a8d4c Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Fri, 13 Jan 2023 14:33:29 +0100 Subject: [PATCH 16/28] removed duplicate file --- .../codes/ec2_2004/_section_7.3.py | 935 ------------------ 1 file changed, 935 deletions(-) delete mode 100644 structuralcodes/codes/ec2_2004/_section_7.3.py diff --git a/structuralcodes/codes/ec2_2004/_section_7.3.py b/structuralcodes/codes/ec2_2004/_section_7.3.py deleted file mode 100644 index 3ad05170..00000000 --- a/structuralcodes/codes/ec2_2004/_section_7.3.py +++ /dev/null @@ -1,935 +0,0 @@ -"""Collection of functions from EUROCODE 1992-1-1:2004 -Chapter 7.3 - Crack control""" -import math -import typing as t - -import numpy as np -import scipy.interpolate - - -def w_max(exposure_class: str, load_combination: str) -> float: - """Computes the recomended value of the maximum crack width. - - EUROCODE 2 1992-1-1:2004, Table (7.1N) - - Args: - exposure_class (str): The exposure class. - Possible values: X0, XC1, XC2, XC3, XC4, XD1, XD2, XS1, XS2, XS3 - load_combination (str): - - f: for frequent load combination - - qp: for quasi-permanent load combination - - Returns: - float: The maximum recommended value for the crack width wmax in mm. - - Raises: - ValueError: if not valid exposure_class or load_combination values. - """ - _load_combination = load_combination.lower().strip() - _exposure_class = exposure_class.upper().strip() - if _load_combination == 'f': - if _exposure_class in ('X0', 'XC1'): - return 0.2 - if _exposure_class in ('XC2', 'XC3', 'XC4'): - return 0.2 - if _load_combination == 'qp': - if _exposure_class in ('X0', 'XC1'): - return 0.4 - if _exposure_class in ( - 'XC2', - 'XC3', - 'XC4', - 'XD1', - 'XD2', - 'XS1', - 'XS2', - 'XS3', - ): - return 0.3 - raise ValueError( - f'{exposure_class} is not a valid value for exposure_class.' - + ' Please enter one of the following: X0, XC1, XC2, XC3, XC4, XD1' - + ',XD2, XS1, XS2, XS3' - ) - raise ValueError( - f'{load_combination} is not a valid value for load_combination.' - + 'Please enter "f" for frequent load combination or "qp" for' - + 'quasi-permanent load combination.' - ) - - -def As_min( - A_ct: float, sigma_s: float, fct_eff: float, _k: float, kc: float -) -> float: - """Computes the minimum area of reinforcing steel within the tensile zone - for control of cracking areas - - EUROCODE 2 1992-1-1:2004, Eq. (7.1) - - Args: - A_ct (float): is the area of concrete within the tensile zone in mm2. - The tensile zone is that parg of the section which is calculated - to be in tension just before the formation of the first crack. - sigma_s (float): is the absolute value of the maximum stress in MPa - permitted in the reinforcement immediately after the formation - of the crack. This may be taken as theyield strength of the - reinforcement, fyk. A lower value may, however, be needed to - satisfy the crack width limits according to the maximum - bar size of spacing (see 7.3.3 (2)). - fct_eff (float): is the mean value of the tensile strength in MPa of - the concrete effective at the time when the cracks may first be - expected to occur: fct,eff=fct or lower (fct(t)), is cracking - is expected earlier than 28 days. - _k (float): is the coefficient which allow for the effect of - non-uniform self-equilibrating stresses, which lead to a - reduction of restraint forces. Use 'k_crack_min_steel_area' - to compute it - k=1 for webs w<=300mm or flanges widths less than 300mm - k=0.65 for webs w>=800mm or flanges with widths greater than 800mm - Intermediate values may be interpolated. - kc (float): is a coefficient which takes account of the stress - distribution within the section immediately prior to cracking and - the change of the lever arm. - - Returns: - float: the minimm area of reinforcing steel within the tensile - zone in mm2. - - Raises: - ValueError: if _k value is not between 0.65 and 1 or kc is not - larger than 0 and lower than 1. - """ - fct_eff = abs(fct_eff) - - if A_ct <= 0: - raise ValueError(f'A_ct={A_ct} must be larger than 0') - if sigma_s < 0: - raise ValueError(f'sigma_s={sigma_s} must be equal or larger than 0') - if _k < 0.65 or _k > 1.0: - raise ValueError(f'_k={_k} must be between 0.65 and 1') - if kc > 1 or kc < 0: - raise ValueError(f'kc={kc} must be lower than 1 and larger than 0') - - return kc * _k * fct_eff * A_ct / sigma_s - - -def k(h: float) -> float: - """Is the coefficient which allow for the effect of - non-uniform self-equilibrating stresses, which lead to a - reduction of restraint forces. - k=1 for webs w<=300mm or flanges widths less than 300mm - k=0.65 for webs w>=800mm or flanges with widths greater than 800mm - - EUROCODE 2 1992-1-1:2004, Eq. (7.1) - - Args: - h (float): flange length or flange width in mm - - Returns: - float: k coefficient value - - Raises: - ValueError: if h is less than 0 - """ - if h < 0: - raise ValueError(f'h={h} cannot be less than 0mm') - if h <= 300: - return 1 - if h < 800: - interpol = scipy.interpolate.interp1d((300, 800), (1, 0.65)) - return (float)(interpol(h)) - return 0.65 - - -def kc_pure_tension() -> float: - """Computes the coefficient which takes account of the stress - distribution within the section immediately prior to cracking and - the change of the lever arm in pure dtension. - - EUROCODE 2 1992-1-1:2004, Eq. (7.1) - - Returns: - float: value of the kc coefficient in pure tension - """ - return 1 - - -def kc_rectangular_area( - h: float, b: float, fct_eff: float, N_ed: float -) -> float: - """Computes the coefficient which takes account of the stress - distribution within the section immediately prior to cracking and - the change of the lever arm for bending+axial combination - in rectangular sections and webs of box sections and T-sections. - - EUROCODE 2 1992-1-1:2004, Eq. (7.2) - - Args: - h (float): heigth of the element in mm - b (float): width of the element in mm - fct_eff (float): is the mean value of the tensile strength in MPa of - the concrete effective at the time when the cracks may first be - expected to occur: fct,eff=fct or lower (fct(t)), is cracking - is expected earlier than 28 days. - N_ed (str): axial force at the serviceability limit state acting on - the part of the cross-section under consideration (compressive - force positive). n_ed should be determined considering the - characteristic values of prestress and axial forces under the - relevant combination of actions - - Returns: - float: value of the kc coefficient - - Raises: - ValueError: is h or b are less than 0 - """ - if h < 0: - raise ValueError(f'h={h} should be larger than 0mm') - if b < 0: - raise ValueError(f'b={b} should be larger than 0mm') - - h_s = min(h, 1000) - _k1 = 1.5 if N_ed >= 0 else 2 * h_s / 3 / h - s_concrete = N_ed * 1000 / b / h - h_ratio = h / h_s - return min(max(0.4 * (1 - s_concrete / _k1 / h_ratio / fct_eff), 0), 1) - - -def kc_flanges_area(f_cr: float, A_ct: float, fct_eff: float) -> float: - """Computes the coefficient which takes account of the stress - distribution within the section immediately prior to cracking and - the change of the lever arm for bending+axial combination - in rectangular sections for flanges of box sections and T-sections. - - EUROCODE 2 1992-1-1:2004, Eq. (7.3) - - Args: - f_cr: is the absolute value in kN of the tensile force within the - flange immediately prior to cracking due to cracking moment - calculated with fct,eff - A_ct (float): is the area of concrete within the tensile zone in mm2. - The tensile zone is that part of the section which is calculated - to be in tension just before the formation of the first crack. - fct_eff (float): is the mean value of the tensile strength in MPa of - the concrete effective at the time when the cracks may first be - expected to occur: fct,eff=fct or lower (fct(t)), is cracking - is expected earlier than 28 days. - - Returns: - float: value of the kc coefficient - - Raises: - ValueError: is A_ct is less than 0mm2 - """ - f_cr = abs(f_cr) - return max(0.9 * f_cr * 1000 / A_ct / fct_eff, 0.5) - - -def xi_1(xi: float, phi_p: float, phi_s: float) -> float: - """Computes the adjusted ratio of bond strength taking into account - the different diameters of prestressing and reinforcing steel. - - EUROCODE 2 1992-1-1:2004, Eq. (7.5) - - Args: - xi (float): ratio of bond strength of prestressing and reinforcing - steel, according to Table 6.2 in 6.8.2 - phi_p (float): largest bar diameter in mm of reinforcing steel. - Equal to 0 if only prestressing is used in control cracking - phi_s (float): equivalent diameter in mm of tendon acoording - to 6.8.2 - - Returns: - float: with the value of the ratio - - Raises: - ValueError: if diameters phi_s or phi_p are lower than 0. - If ratio of bond strength xi is less than 0.15 or larger than 0.8. - """ - - if phi_p <= 0: - raise ValueError(f'phi_p={phi_p} cannot be less than 0') - if phi_s < 0: - raise ValueError(f'phi_s={phi_s} cannot be less than 0') - if xi < 0.15: - raise ValueError(f'The minimum value for xi={xi} is 0.15') - if xi > 0.8: - raise ValueError(f'The maximum value for xi={xi} is 0.8') - - return ((xi * phi_s / phi_p) ** 0.5) if phi_s > 0 else xi**0.5 - - -def hc_eff(h: float, d: float, x: float) -> float: - """Returns the effective height of concrete in tension surrounding - the reinforcement or prestressing tendons. - - EUROCODE 2 1992-1-1:2004, Section (7.3.2-3) - - Args: - h (float): total depth of the element in mm - d (float): distance in mm to the level of the steel centroid - x (float): distance in mm to the zero tensile stress line - - Returns: - float: the effective height in mm - - Raises: - ValueError: if any of h, d or x is lower than zero. - ValueError: if d is greater than h - ValueError: if x is greater than h - """ - if h < 0: - raise ValueError(f'h={h} cannot be less than 0') - if d < 0: - raise ValueError(f'd={d} cannot be less than 0') - if x < 0: - raise ValueError(f'x={x} cannot be less than zero') - if d > h: - raise ValueError(f'd={d} cannot be larger than h={h}') - if x > h: - raise ValueError(f'x={x} cannot be larger than h={h}') - - return min(2.5 * (h - d), (h - x) / 3, h / 2) - - -def As_min_p( - A_ct: float, - sigma_s: float, - fct_eff: float, - _k: float, - kc: float, - Ap: float, - phi_s: float, - phi_p: float, - xi: float, - delta_s: float, -) -> float: - """Computes the minimum area of reinforcing steel within the tensile zone - for control of cracking areas in addition with bonded tendons - - EUROCODE 2 1992-1-1:2004, Eq. (7.1) - - Args: - A_ct (float): is the area of concrete within the tensile zone in mm2. - The tensile zone is that part of the section which is calculated - to be in tension just before the formation of the first crack. - sigma_s (float): is the absolute value of the maximum stress in MPa - permitted in the reinforcement immediately after the formation - of the crack. This may be taken as theyield strength of the - reinforcement, fyk. A lower value may, however, be needed to - satisfy the crack width limits according to the maximum - bar size of spacing (see 7.3.3 (2)). - fct_eff (float): is the mean value of the tensile strength in MPa of - the concrete effective at the time when the cracks may first be - expected to occur: fct,eff=fct or lower (fct(t)), is cracking - is expected earlier than 28 days. - _k (float): is the coefficient which allow for the effect of - non-uniform self-equilibrating stresses, which lead to a - reduction of restraint forces. Use 'k_crack_min_steel_area' - to compute it - k=1 for webs w<=300mm or flanges widths less than 300mm - k=0.65 for webs w>=800mm or flanges with widths greater than 800mm - Intermediate values may be interpolated. - kc (float): is a coefficient which takes account of the stress - distribution within the section immediately prior to cracking and - the change of the lever arm. - Ap (float): is the area in mm2 of pre or post-tensioned tendons - within ac_eff - phi_s (float): largest bar diameter in mm of reinforcing steel. - Equal to 0 if only prestressing is used in control cracking - phi_p (float): equivalent diameter in mm of tendon acoording - to 6.8.2 - chi (float): ratio of bond strength of prestressing and reinforcing - steel, according to Table 6.2 in 6.8.2 - delta_s (float): stress variation in MPa in prestressing tendons - from the state of zero strain of the concrete at the same level - - Returns: - float: the minimm area of reinforcing steel within the tensile - zone in mm2. - - Raises: - ValueError: if _k value is not between 0.65 and 1 or kc is not - larger than 0 and lower than 1. If diameters phi_s or - phi_p are lower than 0. If ratio of bond xi strength e - is less than 0.15 or larger than 0.8. - Is stress variation incr_stress is less than 0. - """ - fct_eff = abs(fct_eff) - - if Ap < 0: - raise ValueError(f'Ap={Ap} cannot be less than 0') - if delta_s < 0: - raise ValueError(f'delta_s={delta_s} cannot be less than 0') - if A_ct <= 0: - raise ValueError(f'A_ct={A_ct} must be larger than 0') - if sigma_s < 0: - raise ValueError(f'sigma_s={sigma_s} must be equal or larger than 0') - if _k < 0.65 or _k > 1.0: - raise ValueError(f'_k={_k} must be between 0.65 and 1') - if kc > 1 or kc < 0: - raise ValueError(f'kc={kc} must be lower than 1 and larger than 0') - - a1 = kc * _k * fct_eff * A_ct - e1 = xi_1(xi, phi_p, phi_s) - a2 = e1 * Ap * delta_s - a = a1 - a2 - - return a / sigma_s - - -def As_min_2( - _wk: float, - sigma_s: float, - fct_eff: float, - h_cr: float, - h: float, - d: float, - delta_s: float = 0, - kc: t.Optional[float] = None, -) -> t.Tuple[float, float]: - """Computes the minimum area of reinforcing steel within the tensile zone - for control of cracking areas - - EUROCODE 2 1992-1-1:2004, Table (7.2N), Table (7.3N) - - Args: - _wk (float): the characteristic crack width value in mm. - sigma_s (float): the steel stress value in MPa under the relevant - combination of actions. - fct_eff (float): is the mean value of the tensile strength in MPa of - the concrete effective at the time when the cracks may first be - expected to occur: fct,eff=fct or lower (fct(t)), is cracking - is expected earlier than 28 days. - h_cr (float): is the depth of the tensile zone immediately prior to - cracking, considering the characteristic values of prestress and - axial forces under the quasi-permanent combination of actions. - h (float): the overall depth of the section in mm. - d (float): is the effective depth to the centroid of the outer layer - of the reinforcement. - delta_s (float, optional): value of prestressed stress in MPa if - applicable - kc (float, optional): is a coefficient which takes account of the - stress distribution within the section immediately prior to - cracking and the change of the lever arm in a bending section. - 'None' for pure tensile uniform axial section. - - Returns: - tuple(float, float): with the value of the maximum bar diameters in mm - in the first position and the maximum bar spacing in mm in the - second position - Raises: - ValueError: if _wk, fct_eff, h_cr, h or d are less than 0 - ValueError: if kc is not between 0 and 1 - ValueError: if combination of wk and stress values are out of scope - """ - if _wk < 0: - raise ValueError(f'_wk={_wk} cannot be less than 0') - if fct_eff < 0: - raise ValueError(f'fct_eff={fct_eff} is less than 0') - if h_cr < 0: - raise ValueError(f'h_cr={h_cr} is less than 0') - if h < 0: - raise ValueError(f'h={h} is less than 0') - if d < 0: - raise ValueError(f'd={d} is less than 0') - if kc is not None and (kc < 0 or kc > 1): - raise ValueError(f'kc={kc} is not between 0 and 1') - - s = sigma_s - delta_s - if s <= 0: - return (0, 0) - - x = (0.4, 0.3, 0.2) - y_phi = (160, 200, 240, 280, 320, 360, 400, 450) - y_spa = (160, 200, 240, 280, 320, 360) - phi_s_v = ( - 40, - 32, - 25, - 32, - 25, - 16, - 20, - 16, - 12, - 16, - 12, - 8, - 12, - 10, - 6, - 10, - 8, - 5, - 8, - 6, - 4, - 6, - 5, - None, - ) - spa_v = ( - 300, - 300, - 200, - 300, - 250, - 150, - 250, - 200, - 100, - 200, - 150, - 50, - 150, - 100, - None, - 100, - 50, - None, - ) - - points_phi = np.array(np.meshgrid(y_phi, x)).T.reshape(-1, 2) - points_spa = np.array(np.meshgrid(y_spa, x)).T.reshape(-1, 2) - xi = (s, _wk) - - phi_star = float( - scipy.interpolate.griddata(points_phi, phi_s_v, xi, method='linear') - ) - if kc is not None: - phi = phi_star * (fct_eff / 2.9) * kc * h_cr / (2 * (h - d)) - else: - phi = phi_star * (fct_eff / 2.9) * h_cr / (8 * (h - d)) - - spa = float( - scipy.interpolate.griddata(points_spa, spa_v, xi, method='linear') - ) - - if math.isnan(phi) or math.isnan(spa): - raise ValueError('Combination of wk or stress values out of scope') - - return phi, spa - - -def alpha_e(Es: float, Ecm: float) -> float: - """Compute the ratio between the steel and mean concrete - elastic modules. - - EUROCODE 2 1992-1-1:2004, Section 7.3.4-2 - - Args: - Es (float): steel elastic modulus in MPa - Ecm (float): concrete mean elastic modulus in MPa - - Returns: - float: ratio between modules - Raise: - ValueError: if any of es or ecm is lower than 0. - """ - if Es < 0: - raise ValueError(f'Es={Es} cannot be less than 0') - if Ecm < 0: - raise ValueError(f'Ecm={Ecm} cannot be less than 0') - - return Es / Ecm - - -def rho_p_eff(As: float, xi1: float, Ap: float, Ac_eff: float) -> float: - """Effective bond ratio between areas - - EUROCODE 2 1992-1-1:2004, Eq. (7.10) - - Args: - As (float): steel area in mm2 - xi1 (float): the adjusted ratio of bond according - to expression (7.5) - Ap (float): the area in mm2 of post-tensioned tendons in ac_eff - Ac_eff (float): effective area of concrete in tension surrounding - the reinforcement or prestressing tendons of depth hc_eff. - - Returns: - float: with the retio between areas - - - Raise: - ValueError: if any of As, xi1, Ap or Ac_eff is less than 0 - """ - if As < 0: - raise ValueError(f'As={As} cannot be less than 0') - if xi1 < 0: - raise ValueError(f'xi1={xi1} cannot be less than 0') - if Ap < 0: - raise ValueError(f'Ap={Ap} cannot be less than 0') - if Ac_eff < 0: - raise ValueError(f'Ac_eff={Ac_eff} cannot be less than 0') - - return (As + xi1**2 * Ap) / Ac_eff - - -def kt(load_type: str) -> float: - """Returns the kt factor dependent on the load duration for - the crack width calculation - - Args: - load_type (str): the load type: - - 'short' for term loading - - 'long' for long term loading - - Returns: - float: with the kt factor - - Raises: - ValueError: if load_type is not 'short' and not 'long' - """ - if not isinstance(load_type, str): - raise TypeError - - load_type = load_type.lower().strip() - if load_type != 'short' and load_type != 'long': - raise ValueError( - f'load_type={load_type} can only have "short" or "long" as a value' - ) - - return 0.6 if load_type == 'short' else 0.4 - - -def esm_ecm( - sigma_s: float, - _alpha_e: float, - _rho_p_eff: float, - _kt: float, - fct_eff: float, - Es: float, -) -> float: - """Returns the strain difference (esm - ecm) needed to compute the crack - width. esm is the mean strain in the reinforcement under the relevant - combination of loads of imposed deformations and taking into account the - effects of tension stiffening. Only the additional tensile strain beyond - the state of zero strain of the concrete is considered. ecm is the mean - strain in the concrete between the cracks. - - EUROCODE 2 1992-1-1:2004, Eq. (7.9) - - Args: - sigma_s (float): is the stress in MPa in the tension reinforcement - assuming a cracked section. FOr pretensioned members, s_steel may - be replaced by increment of s_steel stress variation in - prestressing tendons from the state of zero strain of the - concrete at the same level. - _alpha_e (float): is the ratio Es/Ecm - _rho_p_eff (float): effective bond ratio between areas given by the - Eq. (7.10) - _kt (float): is a factor dependent on the load duration - fct_eff (float): is the mean value of the tensile strength in MPa - of the concrete effectvie at the time when the cracks may - first be expected to occur: fct_eff=fctm or fctm(t) if - crack is expected earlier than 28 days. - Es: steel elastic mudulus in MPa - - Returns: - float: the strain difference between concrete and steel - - Raises: - ValueError: if any sigma_s, alpha_e, rho_p_eff, fct_eff or Es is less - than 0. - ValueError: if kt is not 0.6 and not 0.4 - """ - if sigma_s < 0: - raise ValueError(f'sigma_s={sigma_s} cannot be less than 0') - if _alpha_e < 0: - raise ValueError(f'_alpha_e={_alpha_e} cannot be less than 0') - if _rho_p_eff < 0: - raise ValueError(f'_rho_p_eff={_rho_p_eff} cannot be less than 0') - if fct_eff < 0: - raise ValueError(f'fct_eff={fct_eff} cannot be less than 0') - if Es < 0: - raise ValueError(f'Es={Es} cannot be less than 0') - if _kt != 0.6 and _kt != 0.4: - raise ValueError(f'_kt={_kt} can only take as values 0.4 and 0.6') - - min_val = 0.6 * sigma_s / Es - - a = 1 + _alpha_e * _rho_p_eff - b = _kt * fct_eff / _rho_p_eff * a - c = (sigma_s - b) / Es - - return max(c, min_val) - - -def w_spacing(c: float, phi: float) -> float: - """Computes the distance threshold from which the - maximum crack spacing is constant. - - EUROCODE 2 1992-1-1:2004, Sect. (7.3.4-3) - - Args: - c (float): cover of the longitudinal reinforcement in mm - phi (float): is the bar diameter in mm. Where mixed bar diameters - used, then it should be replaced for an equivalente bar diameter. - - Returns: - float: threshold distance in mm - - Raises: - ValueError: if any of c or phi is less than 0. - """ - if c < 0: - raise ValueError(f'c={c} cannot be less than 0') - if phi < 0: - raise ValueError(f'phi={phi} cannot be less than 0') - - return 5 * (c + phi / 2) - - -def phi_eq(n1: int, n2: int, phi1: float, phi2: float) -> float: - """Computes the equivalent diameter. For a section with n1 bars of - diameter phi1 and n2 bars of diameter phi2 - - EUROCODE 2 1992-1-1:2004, Sect. (7.12) - - Args: - n1 (int): number of bars with diameter phi1 - n2 (int): number of bars with diameter phi2 - phi1 (float): diameter of n1 bars in mm - phi2 (float): diamater of n2 bars in mm - - Returns: - float: the equivalent diameter in mm - - Raises: - ValueError: if any of n1 or n2 is less than 0 - ValueError: if any of phi1 or phi2 is less than 0 - TypeError: if any of n1 or n2 is not an integer - """ - if n1 < 0: - raise ValueError(f'n1={n1} cannot be less than 0') - if not isinstance(n1, int): - raise TypeError(f'n1={n1} needs to be an integer value') - if n2 < 0: - raise ValueError(f'n2={n2} cannot be less than 0') - if not isinstance(n2, int): - raise TypeError(f'n2={n2} needs to be an integer value') - if phi1 < 0: - raise ValueError(f'phi1={phi1} cannot be less than 0') - if phi2 < 0: - raise ValueError(f'phi2={phi2} cannot be less than 0') - - a = n1 * phi1**2 + n2 * phi2**2 - b = n1 * phi1 + n2 * phi2 - return a / b - - -def k1(bond_type: str) -> float: - """Get the k1 coefficient which takes account of the bond properties - of the bounded reinforcement - - EUROCODE 2 1992-1-1:2004, Eq. (7.11-k1) - - Args: - bond_type (str): the bond property of the reinforcement. - Possible values: - - 'bond': for high bond bars - - 'plane': for bars with an effectively plain surface (e.g. - prestressing tendons) - - Returns: - (float): value of the k1 coefficient - - Raises: - ValueError: if bond_type is neither 'bond' nor 'plane' - TypeError: if bond_type is not an str - """ - if not isinstance(bond_type, str): - raise TypeError(f'bond_type={bond_type} is not an str') - - bond_type = bond_type.lower().strip() - if bond_type != 'bond' and bond_type != 'plane': - raise ValueError( - f'bond_type={bond_type} can only have "bond" or "plane" as values' - ) - - return 0.8 if bond_type == 'bond' else 1.6 - - -def k2(epsilon_r: float) -> float: - """Computes a coefficient which takes into account of the - distribution of strain: - - EUROCODE 2 1992-1-1:2004, Eq. (7.13) - - Args: - epsilon_r (float): ratio epsilon_2/epsilon_1 where epsilon_1 is - thre greater and epsilon_2 is the lesser strain at the boundaries - of the section considererd, assessed on the basis of a cracked - section. epsilon_r=0 for bending and epsilon_r=1 for pure tension. - - Returns: - float: the k2 coefficient value. - - Raises: - ValueError: if epsilon_r is not between 0 and 1. - """ - if epsilon_r < 0 or epsilon_r > 1: - raise ValueError(f'epsilon_r={epsilon_r} must be between 0 and 1') - - return (1 + epsilon_r) / 2 - - -def k3(): - """Returns the k3 coefficient for computing sr_max - - Returns: - float: value for the coefficient - """ - return 3.4 - - -def k4(): - """Returns the k4 coefficient for computing sr_max - - Returns: - float: value for the coefficient - """ - return 0.425 - - -def sr_max_close( - c: float, - phi: float, - _rho_p_eff: float, - _k1: float, - _k2: float, - _k3: float, - _k4: float, -) -> float: - """Computes the maximum crack spacing in cases where bonded reinforcement - is fixed at reasonably close centres within the tension zone - (w_spacing<=5(c+phi/2)). - - EUROCODE 2 1992-1-1:2004, Eq. (7.11) - - Args: - c (float): is the cover in mm of the longitudinal reinforcement - phi (float): is the bar diameter in mm. Where mixed bar diameters - used, then it should be replaced for an equivalente bar diameter. - _rho_p_eff (float): effective bond ratio between areas given by the - Eq. (7.10) - _k1 (float): coefficient that takes into account the bound properties - of the bonded reinforcement - _k2 (float): coefficient that takes into account the distribution of - of the strain - _k3 (float): coefficient from the National Annex - _k4 (float): coefficient from the National Annex - - Returns: - float: the maximum crack spaing in mm. - - Raises: - ValueError: if one or more of c, phi, rho_p_eff, k3 or k4 - is lower than zero. - ValueError: if k1 is not 0.8 or 1.6 - ValueError: if k2 is not between 0.5 and 1.0 - """ - if c < 0: - raise ValueError(f'c={c} cannot be less than zero') - if phi < 0: - raise ValueError(f'phi={phi} cannot be less than zero') - if _rho_p_eff < 0: - raise ValueError(f'_rho_p_eff={_rho_p_eff} cannot be less than zero') - if _k3 < 0: - raise ValueError(f'_k3={_k3} cannot be less than zero') - if _k4 < 0: - raise ValueError(f'_k4={_k4} cannot be less than zero') - if _k1 != 0.8 and _k1 != 1.6: - raise ValueError(f'_k1={_k1} can only take as values 0.8 and 1.6') - if _k2 < 0.5 or _k2 > 1: - raise ValueError(f'_k2={_k2} is not between 0.5 and 1.0') - - return _k3 * c + _k1 * _k2 * _k4 * phi / _rho_p_eff - - -def sr_max_far(h: float, x: float) -> float: - """Computes the maximum crack spacing in cases where bonded reinforcement - is fixed at reasonably close centres within the tension zone - (w_spacing>5(c+phi/2)). - - EUROCODE 2 1992-1-1:2004, Eq. (7.14) - - Args: - h (float): total depth of the beam in mm - x (float): distance to non tension area of the element mm - - Returns: - float: maximum crack spacing in mm - - Raises: - ValueError: if one of h or x is less than zero. - ValueError: x is greater than h. - """ - if x < 0: - raise ValueError(f'x={x} cannot be less than zero') - if h < 0: - raise ValueError(f'h={h} cannot be less than zero') - if x > h: - raise ValueError(f'x={x} cannot be larger than h={h}') - - return 1.3 * (h - x) - - -def sr_max_theta(sr_max_y: float, sr_max_z: float, theta: float) -> float: - """Computes the crack spacing sr_max when there is an angle - between the angle of principal stress and the direction - of the reinforcement, for members in two orthogonal directions, - that is significant (> 15º). - - EUROCODE 2 1992-1-1:2004, Eq. (7.15) - - Args: - sr_max_y (float): crack spacing in mm in the y-direction. - sr_max_z (float): crack spacing in mm in the z-direction. - theta (float): angle in radians between the reinforcement in the - y-direction and the direction of the principal tensile stress. - - Returns: - float: the crack spacing in mm. - - Raises: - ValueError: if sr_max_y or sr_max_z is negative. - ValueError: if theta is not between 0 and pi/2 - """ - if sr_max_y < 0: - raise ValueError(f'sr_max_y={sr_max_y} cannot be less than zero') - if sr_max_z < 0: - raise ValueError(f'sr_max_z={sr_max_z} cannot be less than zero') - - a = math.cos(theta) / sr_max_y - b = math.sin(theta) / sr_max_z - return 1 / (a + b) - - -def wk(sr_max: float, _esm_ecm: float) -> float: - """Computes the crack width - - EUROCODE 2 1992-1-1:2004, Eq. (7.8) - - Args: - sr_max (float): the maximum crack length spacing in mm. - _esm_ecm (float): the difference between the mean strain in the - reinforcement under relevant combination of loads, including - the effect of imposed deformations and taking into account - tension stiffening and the mean strain in the concrete - between cracks. - - Returns: - float: crack width in mm. - - Raises: - ValueError: if any of sr_max or _esm_ecm is less than zero. - """ - if sr_max < 0: - raise ValueError(f'sr_max={sr_max} cannot be less than zero') - if _esm_ecm < 0: - raise ValueError(f'_esm_scm={_esm_ecm} cannot be less than zero') - - return sr_max * _esm_ecm From 6ba6dc905ffbe76be7c2d9b0e948e3c4c742c285 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Fri, 13 Jan 2023 14:36:25 +0100 Subject: [PATCH 17/28] removed testing file --- prueba.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 prueba.py diff --git a/prueba.py b/prueba.py deleted file mode 100644 index e69de29b..00000000 From a9c926338152606c8e0a57e88127da84d7915e2a Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Mon, 16 Jan 2023 08:29:41 +0100 Subject: [PATCH 18/28] test renaming and docstring corrections --- .../ec2_2004/_section_7_3_crack_control.py | 4 +- ...test_ec2_2004_section_7_3_crack_control.py | 60 +++++++++---------- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py b/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py index 1de528de..529ef679 100644 --- a/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py @@ -392,7 +392,7 @@ def As_min_2( EUROCODE 2 1992-1-1:2004, Table (7.2N), Table (7.3N) Args: - wk (float): the characteristic crack width value in mm. + _wk (float): the characteristic crack width value in mm. sigma_s (float): the steel stress value in MPa under the relevant combination of actions. fct_eff (float): is the mean value of the tensile strength in MPa of @@ -417,7 +417,7 @@ def As_min_2( in the first position and the maximum bar spacing in mm in the second position Raises: - ValueError: if wk, fct_eff, h_cr, h or d are less than 0 + ValueError: if _wk, fct_eff, h_cr, h or d are less than 0 ValueError: if kc is not between 0 and 1 ValueError: if combination of wk and stress values are out of scope """ diff --git a/tests/test_ec2_2004_section_7_3_crack_control.py b/tests/test_ec2_2004_section_7_3_crack_control.py index 54b14fc7..8dc120f8 100644 --- a/tests/test_ec2_2004_section_7_3_crack_control.py +++ b/tests/test_ec2_2004_section_7_3_crack_control.py @@ -73,21 +73,21 @@ def test_w_max_not_valid_input_raises_valueerror( (700, 0.72), ], ) -def test_k_crack_min_steel_area_returns_expected_values(h, expected): - """Test the k_crack_min_steel_area function""" +def test_k_returns_expected_values(h, expected): + """Test the k function""" k = _section_7_3_crack_control.k(h) assert math.isclose(k, expected) -def test_k_crack_min_steel_area_raises_valueerror(): +def test_k_raises_valueerror(): """Test that not valid input returns ValueError exeption""" with pytest.raises(ValueError): h = -100 _section_7_3_crack_control.k(h) -def test_kc_crack_min_steel_area_pure_tension_returns_expected_values(): - """Test the kc_crack_min_steel_area_pure_tension function""" +def test_kc_tension_returns_expected_values(): + """Test the kc_tension function""" assert 1 == _section_7_3_crack_control.kc_tension() @@ -101,10 +101,8 @@ def test_kc_crack_min_steel_area_pure_tension_returns_expected_values(): (200, 50, 5, 80, 0), ], ) -def test_kc_crack_min_steel_area_rectangular_returns_expected_values( - h, b, fct_eff, n_ed, expected -): - """Test the kc_crack_min_steel_area_rectangular""" +def test_kc_rect_area_returns_expected_values(h, b, fct_eff, n_ed, expected): + """Test the kc_rect_area""" kc = _section_7_3_crack_control.kc_rect_area( h, b, @@ -114,8 +112,8 @@ def test_kc_crack_min_steel_area_rectangular_returns_expected_values( assert math.isclose(kc, expected, rel_tol=0.000001) -def test_kc_crack_min_steel_area_rectangular_raises_valueerror(): - """Test the kc_crack_min_steel_area_rectangular raises Value +def test_kc_rect_area_raises_valueerror(): + """Test the kc_rect_area raises Value Error for not correct input values for b and h""" with pytest.raises(ValueError): _section_7_3_crack_control.kc_rect_area( @@ -135,8 +133,8 @@ def test_kc_crack_min_steel_area_rectangular_raises_valueerror(): (55, 50000, 4, 0.5), ], ) -def test_kc_crack_min_steel_area_flanges(f_cr, a_ct, fct_eff, expected): - """Test the kc_crack_min_steel_area_flanges function""" +def test_kc_flanges_area(f_cr, a_ct, fct_eff, expected): + """Test the kc_flanges function""" kc = _section_7_3_crack_control.kc_flanges_area(f_cr, a_ct, fct_eff) assert math.isclose(kc, expected, rel_tol=0.000001) @@ -185,7 +183,7 @@ def test_crack_As_min_raises_valueerror(a_ct, s_steel, fct_eff, k, kc): (50000, 500, 4, 1, 1, 1000, 0, 20, 0.8, 20, 364.223), ], ) -def test_crack_min_steel_area_with_press_tendons_returns_expected_values( +def test_As_min_p_returns_expected_values( a_ct, s_steel, fct_eff, @@ -198,7 +196,7 @@ def test_crack_min_steel_area_with_press_tendons_returns_expected_values( incr_stress, expected, ): - """Test the crack_min_steel_area returns expected values""" + """Test the As_min_p returns expected values""" as_min = _section_7_3_crack_control.As_min_p( a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, incr_stress ) @@ -221,10 +219,10 @@ def test_crack_min_steel_area_with_press_tendons_returns_expected_values( (80000, 400, 4, 0.9, 0.75, 500, 10, 10, 0.9, 10), ], ) -def test_crack_min_steel_area_with_press_tendons_raise_valueerror( +def test_As_min_p_raise_valueerror( a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, incr_stress ): - """Test the crack_min_steel_area raise ValueError for non valid values""" + """Test the As_min_p raise ValueError for non valid values""" with pytest.raises(ValueError): _section_7_3_crack_control.As_min_p( a_ct, s_steel, fct_eff, k, kc, ap, d_steel, d_press, e, incr_stress @@ -240,7 +238,7 @@ def test_crack_min_steel_area_with_press_tendons_raise_valueerror( (0.35, 360, 2.9, 200, 400, 360, 40, None, 6.875, 125), ], ) -def test_crack_min_steel_without_direct_calculation_returns_expected_values( +def test_As_min_2_returns_expected_values( wk, s_steel, fct_eff, @@ -252,7 +250,7 @@ def test_crack_min_steel_without_direct_calculation_returns_expected_values( exp_phi, exp_sep, ): - """Test the crack_min_steel_area raise ValueError for non valid values""" + """Test the As_min_2 raise ValueError for non valid values""" phi, sep = _section_7_3_crack_control.As_min_2( wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc ) @@ -273,10 +271,10 @@ def test_crack_min_steel_without_direct_calculation_returns_expected_values( (0.5, 200, 2.9, 200, 400, 360, 0, 0.4), ], ) -def test_crack_min_steel_without_direct_calculation_raise_valueerror( +def test_As_min_2_raise_valueerror( wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc ): - """Test the crack_min_steel_area raise ValueError for non valid values""" + """Test the As_min_2 raise ValueError for non valid values""" with pytest.raises(ValueError): _section_7_3_crack_control.As_min_2( wk, s_steel, fct_eff, h_cr, h, d, incr_stress, kc @@ -291,10 +289,8 @@ def test_crack_min_steel_without_direct_calculation_raise_valueerror( (0.5, 10, 10, 0.707107), ], ) -def test_adjusted_bond_length_return_expected_values( - e, d_press, d_steel, expected -): - """Test the adjusted_bond_length_function returns expected values""" +def test_xi1_values(e, d_press, d_steel, expected): + """Test xi1 returns expected values""" assert math.isclose( _section_7_3_crack_control.xi1(e, d_press, d_steel), expected, @@ -312,7 +308,7 @@ def test_adjusted_bond_length_return_expected_values( (0.6, 10, -10), ], ) -def test_adjusted_bond_length_raise_valuerror(e, d_press, d_steel): +def test_xi1_raise_valuerror(e, d_press, d_steel): """Test the adjusted_bond_length_function raises exceptions""" with pytest.raises(ValueError): _section_7_3_crack_control.xi1(e, d_press, d_steel) @@ -326,7 +322,7 @@ def test_adjusted_bond_length_raise_valuerror(e, d_press, d_steel): (550, 150, 150, 133.33333), ], ) -def test_hc_eff_concrete_tension_returns_expected_values(h, d, x, expected): +def test_hc_eff_returns_expected_values(h, d, x, expected): """Test the hc_eff_concrete_tension returns expected results""" assert math.isclose( _section_7_3_crack_control.hc_eff(h, d, x), @@ -345,7 +341,7 @@ def test_hc_eff_concrete_tension_returns_expected_values(h, d, x, expected): (400, 200, 450), ], ) -def test_hc_eff_concrete_tension_raise_exceptions(h, d, x): +def test_hc_eff_raise_exceptions(h, d, x): """Test hc_eff_concrete tension raises expected exceptions""" with pytest.raises(ValueError): _section_7_3_crack_control.hc_eff(h, d, x) @@ -417,13 +413,13 @@ def test_rho_p_eff_raise_value_error(a_s, e1, a_p, ac_eff): ('long', 0.4), ], ) -def test_kt_load_duration_returns_expected_values(load_type, expected): - """Test kt_load_duration returns expected values""" +def test_kt_returns_expected_values(load_type, expected): + """Test kt returns expected values""" assert _section_7_3_crack_control.kt(load_type) == expected -def test_kt_load_duration_raise_value_errors(): - """Test kt_load_duration raise value errors""" +def test_kt_raise_value_errors(): + """Test kt raise value errors""" with pytest.raises(TypeError): _section_7_3_crack_control.kt(load_type=123) From 4fd8b7e9ac5dc274cb86f8fcb658679ac3df4312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20de=20la=20Morena?= Date: Thu, 9 Mar 2023 09:53:18 +0100 Subject: [PATCH 19/28] 230309 requested changes applied --- requirements.txt | 4 +- .../ec2_2004/_section_7_3_crack_control.py | 61 +++--- structuralcodes/material/__init__.py | 0 structuralcodes/material/concrete/__init__.py | 58 ------ .../material/concrete/_concrete.py | 45 ----- .../material/concrete/_concreteMC2010.py | 189 ------------------ ...test_ec2_2004_section_7_3_crack_control.py | 5 +- 7 files changed, 39 insertions(+), 323 deletions(-) delete mode 100644 structuralcodes/material/__init__.py delete mode 100644 structuralcodes/material/concrete/__init__.py delete mode 100644 structuralcodes/material/concrete/_concrete.py delete mode 100644 structuralcodes/material/concrete/_concreteMC2010.py diff --git a/requirements.txt b/requirements.txt index 1de8c504..d68dafa2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -numpy==1.23.5 -scipy==1.9.3 \ No newline at end of file +numpy>=1.20.0 +scipy>=1.6.0 \ No newline at end of file diff --git a/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py b/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py index 529ef679..e530a885 100644 --- a/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py @@ -8,7 +8,7 @@ def w_max(exposure_class: str, load_combination: str) -> float: - """Computes the recomended value of the maximum crack width. + """Computes the recommended value of the maximum crack width. EUROCODE 2 1992-1-1:2004, Table (7.1N) @@ -68,7 +68,7 @@ def As_min( Args: A_ct (float): is the area of concrete within the tensile zone in mm2. - The tensile zone is that parg of the section which is calculated + The tensile zone is that part of the section which is calculated to be in tension just before the formation of the first crack. sigma_s (float): is the absolute value of the maximum stress in MPa permitted in the reinforcement immediately after the formation @@ -82,8 +82,7 @@ def As_min( is expected earlier than 28 days. _k (float): is the coefficient which allow for the effect of non-uniform self-equilibrating stresses, which lead to a - reduction of restraint forces. Use 'k_crack_min_steel_area' - to compute it + reduction of restraint forces. k=1 for webs w<=300mm or flanges widths less than 300mm k=0.65 for webs w>=800mm or flanges with widths greater than 800mm Intermediate values may be interpolated. @@ -137,14 +136,14 @@ def k(h: float) -> float: return 1 if h < 800: interpol = scipy.interpolate.interp1d((300, 800), (1, 0.65)) - return (float)(interpol(h)) + return interpol(h) return 0.65 def kc_tension() -> float: """Computes the coefficient which takes account of the stress distribution within the section immediately prior to cracking and - the change of the lever arm in pure dtension. + the change of the lever arm in pure tension. EUROCODE 2 1992-1-1:2004, Eq. (7.1) @@ -313,7 +312,7 @@ def As_min_p( to be in tension just before the formation of the first crack. sigma_s (float): is the absolute value of the maximum stress in MPa permitted in the reinforcement immediately after the formation - of the crack. This may be taken as theyield strength of the + of the crack. This may be taken as the yield strength of the reinforcement, fyk. A lower value may, however, be needed to satisfy the crack width limits according to the maximum bar size of spacing (see 7.3.3 (2)). @@ -323,8 +322,7 @@ def As_min_p( is expected earlier than 28 days. _k (float): is the coefficient which allow for the effect of non-uniform self-equilibrating stresses, which lead to a - reduction of restraint forces. Use 'k_crack_min_steel_area' - to compute it + reduction of restraint forces. k=1 for webs w<=300mm or flanges widths less than 300mm k=0.65 for webs w>=800mm or flanges with widths greater than 800mm Intermediate values may be interpolated. @@ -337,7 +335,7 @@ def As_min_p( Equal to 0 if only prestressing is used in control cracking phi_p (float): equivalent diameter in mm of tendon acoording to 6.8.2 - chi (float): ratio of bond strength of prestressing and reinforcing + xi (float): ratio of bond strength of prestressing and reinforcing steel, according to Table 6.2 in 6.8.2 delta_s (float): stress variation in MPa in prestressing tendons from the state of zero strain of the concrete at the same level @@ -611,7 +609,7 @@ def esm_ecm( Args: sigma_s (float): is the stress in MPa in the tension reinforcement - assuming a cracked section. FOr pretensioned members, s_steel may + assuming a cracked section. For pretensioned members, s_steel may be replaced by increment of s_steel stress variation in prestressing tendons from the state of zero strain of the concrete at the same level. @@ -620,10 +618,10 @@ def esm_ecm( Eq. (7.10) _kt (float): is a factor dependent on the load duration fct_eff (float): is the mean value of the tensile strength in MPa - of the concrete effectvie at the time when the cracks may + of the concrete effective at the time when the cracks may first be expected to occur: fct_eff=fctm or fctm(t) if crack is expected earlier than 28 days. - Es: steel elastic mudulus in MPa + Es: steel elastic modulus in MPa Returns: float: the strain difference between concrete and steel @@ -664,7 +662,7 @@ def w_spacing(c: float, phi: float) -> float: Args: c (float): cover of the longitudinal reinforcement in mm phi (float): is the bar diameter in mm. Where mixed bar diameters - used, then it should be replaced for an equivalente bar diameter. + used, then it should be replaced for an equivalent bar diameter. Returns: float: threshold distance in mm @@ -728,23 +726,23 @@ def k1(bond_type: str) -> float: bond_type (str): the bond property of the reinforcement. Possible values: - 'bond': for high bond bars - - 'plane': for bars with an effectively plain surface (e.g. + - 'plain': for bars with an effectively plain surface (e.g. prestressing tendons) Returns: (float): value of the k1 coefficient Raises: - ValueError: if bond_type is neither 'bond' nor 'plane' + ValueError: if bond_type is neither 'bond' nor 'plain' TypeError: if bond_type is not an str """ if not isinstance(bond_type, str): raise TypeError(f'bond_type={bond_type} is not an str') bond_type = bond_type.lower().strip() - if bond_type != 'bond' and bond_type != 'plane': + if bond_type not in ('bond', 'plain'): raise ValueError( - f'bond_type={bond_type} can only have "bond" or "plane" as values' + f'bond_type={bond_type} can only have "bond" or "plain" as values' ) return 0.8 if bond_type == 'bond' else 1.6 @@ -758,8 +756,8 @@ def k2(epsilon_r: float) -> float: Args: epsilon_r (float): ratio epsilon_2/epsilon_1 where epsilon_1 is - thre greater and epsilon_2 is the lesser strain at the boundaries - of the section considererd, assessed on the basis of a cracked + the greater and epsilon_2 is the lesser strain at the boundaries + of the section considered, assessed on the basis of a cracked section. epsilon_r=0 for bending and epsilon_r=1 for pure tension. Returns: @@ -798,8 +796,8 @@ def sr_max_close( _rho_p_eff: float, _k1: float, _k2: float, - _k3: float, - _k4: float, + _k3: t.Optional[float] = None, + _k4: t.Optional[float] = None, ) -> float: """Computes the maximum crack spacing in cases where bonded reinforcement is fixed at reasonably close centres within the tension zone @@ -810,15 +808,17 @@ def sr_max_close( Args: c (float): is the cover in mm of the longitudinal reinforcement phi (float): is the bar diameter in mm. Where mixed bar diameters - used, then it should be replaced for an equivalente bar diameter. + used, then it should be replaced for an equivalent bar diameter. _rho_p_eff (float): effective bond ratio between areas given by the Eq. (7.10) _k1 (float): coefficient that takes into account the bound properties of the bonded reinforcement _k2 (float): coefficient that takes into account the distribution of of the strain - _k3 (float): coefficient from the National Annex - _k4 (float): coefficient from the National Annex + _k3 (float, optional): coefficient from the National Annex. + If not specified then _k3=3.4 + _k4 (float): coefficient from the National Annex. + If not specified then _k4=0.425 Returns: float: the maximum crack spaing in mm. @@ -829,6 +829,11 @@ def sr_max_close( ValueError: if _k1 is not 0.8 or 1.6 ValueError: if _k2 is not between 0.5 and 1.0 """ + if _k3 is None: + _k3 = k3() + if _k4 is None: + _k4 = k4() + if c < 0: raise ValueError(f'c={c} cannot be less than zero') if phi < 0: @@ -839,7 +844,7 @@ def sr_max_close( raise ValueError(f'_k3={_k3} cannot be less than zero') if _k4 < 0: raise ValueError(f'_k4={_k4} cannot be less than zero') - if _k1 != 0.8 and _k1 != 1.6: + if _k1 not in (0.8, 1.6): raise ValueError(f'_k1={_k1} can only take as values 0.8 and 1.6') if _k2 < 0.5 or _k2 > 1: raise ValueError(f'_k2={_k2} is not between 0.5 and 1.0') @@ -849,8 +854,8 @@ def sr_max_close( def sr_max_far(h: float, x: float) -> float: """Computes the maximum crack spacing in cases where bonded reinforcement - is fixed at reasonably close centres within the tension zone - (w_spacing>5(c+phi/2)). + exceeds (w_spacing>5(c+phi/2)) or where there is no bonded reinforcement + at all. EUROCODE 2 1992-1-1:2004, Eq. (7.14) diff --git a/structuralcodes/material/__init__.py b/structuralcodes/material/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/structuralcodes/material/concrete/__init__.py b/structuralcodes/material/concrete/__init__.py deleted file mode 100644 index ca314baf..00000000 --- a/structuralcodes/material/concrete/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Concrete material""" -import typing as t -from structuralcodes.codes import _use_design_code -from ._concrete import Concrete -from ._concreteMC2010 import ConcreteMC2010 - -__all__ = [ - 'create_concrete', - 'Concrete', - 'ConcreteMC2010', -] - - -def create_concrete( - fck: float, - name: t.Optional[str] = None, - density: float = 2400.0, - existing: bool = False, - design_code: t.Optional[str] = None, -) -> t.Optional[Concrete]: - """ - A factory function to create the correct type of concrete based on the - desired design code. - - Args: - fck (float): Characteristic strength of concrete in MPa. - (if existing it is intended as the mean strength) - - Keyword Args: - density (float): Density of Concrete in kg/m3 (default: 2400) - existing (bool): Boolean indicating if the concrete is of an - existing structure (default: False) - deisgn_code (str): Optional string (default: None) indicating the - desired standard. If None (default) the globally used design - standard will be adopted. Otherwise the design standard specified - will be used for the instance of the material. - Currently available codes: 'mc2010' - - Raises: - ValueError: if the design code is not valid or does not cover - concrete as a material. - """ - # Get the code from the global variable - _code = _use_design_code(design_code) - - # Check if the code is a proper concrete code - code = _code if 'concrete' in _code.__materials__ else None - if code is None: - raise ValueError( - 'The design code is not set, either use ' - 'structuralcodes.code.set_designcode, or provide a valid ' - 'string in the function.' - ) - - # Create the proper concrete object - if code.__title__ == 'fib Model Code 2010': - return ConcreteMC2010(fck, name, density, existing) - return None diff --git a/structuralcodes/material/concrete/_concrete.py b/structuralcodes/material/concrete/_concrete.py deleted file mode 100644 index 19ac2048..00000000 --- a/structuralcodes/material/concrete/_concrete.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Core implementation of the concrete material""" -import abc -import typing as t -from structuralcodes.core.base import Material - - -class Concrete(Material): - """The abstract concrete material.""" - - _fck: float - _existing: bool - - def __init__( - self, - fck: float, - name: t.Optional[str] = None, - density: float = 2400, - existing: t.Optional[bool] = False, - ) -> None: - """Initializes an abstract concrete material""" - name = name if name is not None else "Concrete" - super().__init__(density=density, name=name) - - self._fck = abs(fck) - if existing: - raise NotImplementedError( - 'Existing concrete feature not implemented yet' - ) - self._existing = existing - - @property - def fck(self) -> float: - """Returns fck in MPa""" - return self._fck - - @fck.setter - def fck(self, fck: float) -> None: - """Setter for fck (in MPa)""" - self._fck = abs(fck) - self._reset_attributes() - - @abc.abstractmethod - def _reset_attributes(self): - """Each concrete should define its own _reset_attributes method - This is because fck setting, reset the object arguments""" diff --git a/structuralcodes/material/concrete/_concreteMC2010.py b/structuralcodes/material/concrete/_concreteMC2010.py deleted file mode 100644 index faf0ad97..00000000 --- a/structuralcodes/material/concrete/_concreteMC2010.py +++ /dev/null @@ -1,189 +0,0 @@ -"""The concrete class for Model Code 2020 Concrete Material""" -import typing as t -import warnings - -from structuralcodes.codes import mc2010 -from ._concrete import Concrete - - -class ConcreteMC2010(Concrete): - """Concrete implementation for MC 2010""" - - _fcm: t.Optional[float] = None - _fctm: t.Optional[float] = None - _fctkmin: t.Optional[float] = None - _fctkmax: t.Optional[float] = None - _Gf: t.Optional[float] = None - - def __init__( - self, - fck: float, - name: t.Optional[str] = None, - density: float = 2400.0, - existing: bool = False, - ): - """Initializes a new instance of Concrete for MC 2010 - - Args: - fck (float): Characteristic strength in MPa if concrete is not - existing. - - Keyword Args: - name (str): A descriptive name for concrete - density (float): Density of material in kg/m3 (default: 2400) - existing (bool): The material is of an existing structure - (default: False) - """ - - if name is None: - name = f'C{round(fck):d}' - super().__init__( - fck=fck, name=name, density=density, existing=existing - ) - - def _reset_attributes(self): - self._fcm = None - self._fctm = None - self._fctkmin = None - self._fctkmax = None - self._Gf = None - - def update_attributes(self, updated_attributes: dict) -> None: - """Function for updating the attributes specified in the input - dictionary - - Args: - updated_attributes (dict): the dictionary of parameters to be - updated (not found parameters are skipped with a warning) - """ - for key, value in updated_attributes.items(): - if not hasattr(self, '_' + key): - str_list_keys = '' - for k in updated_attributes.keys(): - str_list_keys += k + ', ' - str_warn = ( - f'WARNING: attribute {key} not found. Ignoring the entry.' - ) - str_warn += '\nAvailable keys: ' + str_list_keys - warnings.warn(str_warn) - continue - setattr(self, '_' + key, value) - - @property - def fcm(self) -> float: - """Returns fcm in MPa. - - Returns: - float: The mean compressive strength in MPa. - """ - if self._fcm is not None: - return self._fcm - return mc2010.fcm(self._fck) - - @fcm.setter - def fcm(self, value: float): - """Sets a user defined value for fcm - - Args: - value (float): the value of fcm in MPa - - Raises: - ValueError: if value is lower than fck - """ - if abs(value) <= self._fck: - raise ValueError( - ( - 'Mean compressive strength cannot be lower than', - 'characteristic strength.\n', - 'Current characteristing strength: ', - f'fck = {self._fck}.', - f'Current value: value = {value}', - ) - ) - self._fcm = abs(value) - - @property - def fctm(self) -> float: - """Returns fctm in MPa - - Returns: - float: The mean tensile strength in MPa - """ - if self._fctm is not None: - return self._fctm - return mc2010.fctm(self._fck) - - @fctm.setter - def fctm(self, value: float): - """Sets a user defined value for fctm - - Args: - value (float): the value of fctm in MPa - """ - if value > 0.5 * self._fck: - warnings.warn( - 'A suspect value of fctm has been input. Please check.' - ) - self._fctm = abs(value) - - @property - def fctkmin(self) -> float: - """Returns fctkmin in MPa - - Returns: - float: The lower bound tensile strength in MPa - """ - if self._fctkmin is not None: - return self._fctkmin - - return mc2010.fctkmin(self.fctm) - - @fctkmin.setter - def fctkmin(self, value: float): - """Sets a user defined value for fctkmin - - Args: - value (float): the value of fctkmin in MPa - """ - self._fctkmin = abs(value) - - @property - def fctkmax(self) -> float: - """Returns fctkmax in MPa - - Returns: - float: The upper bound tensile strength in MPa - """ - if self._fctkmax is not None: - return self._fctkmax - - return mc2010.fctkmax(self.fctm) - - @fctkmax.setter - def fctkmax(self, value: float): - """Sets a user defined value for fctkmax - - Args: - value (float): the value of fctkmax in MPa - """ - self._fctkmax = abs(value) - - @property - def Gf(self) -> float: - """Fracture energy of concrete - - Returns: - float: The fracture energy in N/m - """ - if self._Gf is not None: - return self._Gf - return mc2010.Gf(self._fck) - - @Gf.setter - def Gf(self, value: float): - """Sets a user defined value for fracture energy Gf - - Args: - value (float): the value of Gf in N/m - """ - self._Gf = abs(value) diff --git a/tests/test_ec2_2004_section_7_3_crack_control.py b/tests/test_ec2_2004_section_7_3_crack_control.py index 8dc120f8..d4e8543b 100644 --- a/tests/test_ec2_2004_section_7_3_crack_control.py +++ b/tests/test_ec2_2004_section_7_3_crack_control.py @@ -535,7 +535,7 @@ def test_phi_eq_raises_expected_values(n1, n2, phi1, phi2, exception_type): @pytest.mark.parametrize( 'bond_type, expected', - [('bond', 0.8), ('plane', 1.6), ('BOND ', 0.8), (' PLANE ', 1.6)], + [('bond', 0.8), ('PLAIN', 1.6), ('BOND ', 0.8), (' PLAIN ', 1.6)], ) def test_k1_returns_expected_values(bond_type, expected): """Test k1 returns expected values""" @@ -588,6 +588,9 @@ def test_k4_returns_expected_values(): (20, 8, 5, 0.8, 0.5, 3.4, 0.425, 68.272), (30, 15, 0.2, 1.6, 0.5, 3.4, 0.425, 127.5), (45, 20, 0.4, 0.8, 1, 3.4, 0.425, 170), + (45, 20, 0.4, 0.8, 1, 3.4, None, 170), + (45, 20, 0.4, 0.8, 1, None, 0.425, 170), + (45, 20, 0.4, 0.8, 1, None, None, 170), ], ) def test_sr_max_close(c, phi, rho_p_eff, k1, k2, k3, k4, expected): From 1cffa61f8583e680ad31950de8c4e7288c416ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20de=20la=20Morena?= Date: Thu, 9 Mar 2023 10:01:50 +0100 Subject: [PATCH 20/28] small lint fixes --- structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py b/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py index e530a885..205bb410 100644 --- a/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py +++ b/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py @@ -582,7 +582,7 @@ def kt(load_type: str) -> float: raise TypeError load_type = load_type.lower().strip() - if load_type != 'short' and load_type != 'long': + if load_type not in ('short', 'long'): raise ValueError( f'load_type={load_type} can only have "short" or "long" as a value' ) @@ -641,7 +641,7 @@ def esm_ecm( raise ValueError(f'fct_eff={fct_eff} cannot be less than 0') if Es < 0: raise ValueError(f'Es={Es} cannot be less than 0') - if _kt != 0.6 and _kt != 0.4: + if _kt not in (0.6, 0.4): raise ValueError(f'_kt={_kt} can only take as values 0.4 and 0.6') min_val = 0.6 * sigma_s / Es From b483d4047f4cdecd897cab958fba87afd0a4bc3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20de=20la=20Morena?= Date: Thu, 9 Mar 2023 10:05:28 +0100 Subject: [PATCH 21/28] vscode config updated --- .vscode/settings.json | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2676da93..72069360 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,17 +1,10 @@ { + "python.formatting.provider": "black", "python.testing.pytestArgs": [ "tests" ], - "python.formatting.provider": "black", "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.linting.pylintEnabled": true, - "python.linting.flake8Enabled": true, - "[python]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true - }, - "editor.defaultFormatter": "ms-python.python", - }, + "python.linting.flake8Enabled": true } \ No newline at end of file From b37ba36b8fbb0819de87a7b5d79a567023766f36 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Wed, 31 Jul 2024 12:34:34 +0200 Subject: [PATCH 22/28] ec-2-2023 8.2.2 added --- structuralcodes/codes/ec2_2023/__init__.py | 32 + .../codes/ec2_2023/_section_8_2_shear.py | 564 ++++++++++++++++++ .../test_ec2_2023_section_8_2_shear.py | 294 +++++++++ 3 files changed, 890 insertions(+) create mode 100644 structuralcodes/codes/ec2_2023/_section_8_2_shear.py create mode 100644 tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py diff --git a/structuralcodes/codes/ec2_2023/__init__.py b/structuralcodes/codes/ec2_2023/__init__.py index 632d7ec5..c3809cb8 100644 --- a/structuralcodes/codes/ec2_2023/__init__.py +++ b/structuralcodes/codes/ec2_2023/__init__.py @@ -48,8 +48,40 @@ wk_cal, wk_cal2, ) +from ._section_8_2_shear import ( + a_cs, + a_v, + d_eff, + d_eff_with_angle, + k1, + k_vp, + rho_l, + tao_Ed, + tao_Ed_planar, + tau_Rdc, + tau_Rdc_0, + tau_Rdc_comp, + tau_Rdc_max, + tau_rdc_min, + v_Ed, +) __all__ = [ + 'a_cs', + 'a_v', + 'k1', + 'rho_l', + 'tau_Rdc', + 'tau_Rdc_0', + 'tau_Rdc_comp', + 'tau_Rdc_max', + 'k_vp', + 'tao_Ed', + 'd_eff', + 'd_eff_with_angle', + 'v_Ed', + 'tao_Ed_planar', + 'tau_rdc_min', 'A_phi_correction_exp', 'alpha_c_th', 'alpha_s_th', diff --git a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py new file mode 100644 index 00000000..b45b397a --- /dev/null +++ b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py @@ -0,0 +1,564 @@ +import math + + +def tao_Ed(VEd: float, bw: float, d: float) -> float: + """Calculate the average shear stress over the cross-section + for linear members. + + EN1992-1-1:2923 Eq. (8.18) + + Parameters: + VEd (float): Design shear force at the control section in + linear members in kN. + bw (float): Width of the cross-section of linear members in mm. + d (float): Effective depth of the cross-section in mm. + + Returns: + float: Average shear stress over the cross-section + for linear members in Mpa. + """ + if bw < 0: + raise ValueError(f'bw must not be negative. Got {bw}') + if d < 0: + raise ValueError(f'd must not be negative. Got {d}') + + z = 0.9 * d + return VEd * 1000 / (bw * z) + + +def tao_Ed_planar(vEd: float, d: float) -> float: + """Calculate the average shear stress over the cross-section + for planar members. + + EN1992-1-1:2923 Eq. (8.19) + + Parameters: + vEd (float): Design shear force per unit width in planar members + in kN/m. + d (float): Effective depth of the cross-section in mm. + + Returns: + float: Average shear stress over the cross-section for + planar members in MPa. + """ + if d < 0: + raise ValueError(f'd must not be negative. Got {d}') + + z = 0.9 * d + return vEd / z + + +def tau_rdc_min( + gamma_v: float, + f_ck: float, + f_yd: float, + d: float, + d_lower: float, +) -> float: + """Calculate the minimum shear stress resistance. + + EN1992-1-1:2023 Eq. (8.20) + + Args: + gamma_v (float): Partial factor for shear design. + f_ck (float): Characteristic compressive strength of + concrete in MPa (must be positive). + f_yd (float): Design value of the yield strength + in MPa (must be positive). + d (float): Effective depth of the flexural + reinforcement in mm (must be positive). + d_lower (float): Smallest value of the upper sieve size D in an + aggregate for the coarsest fraction of aggregates + in mm (must be positive). + + Returns: + float: Minimum shear stress resistance in MPa. + + Raises: + ValueError: If any input value is non-positive. + """ + if gamma_v < 0: + raise ValueError(f'gamma_v must not be negative. Got {gamma_v}') + if f_ck < 0: + raise ValueError(f'f_ck must not be negative. Got {f_ck}') + if f_yd < 0: + raise ValueError(f'f_yd must not be negative. Got {f_yd}') + if d < 0: + raise ValueError(f'd must not be negative. Got {d}') + if d_lower < 0: + raise ValueError(f'd_lower must not be negative. Got {d_lower}') + + if f_ck <= 60: + d_dg = min(16 + d_lower, 40) + else: + d_dg = min(16 + d_lower * (60 / f_ck) ** 2, 40) + + return 11 / gamma_v * math.sqrt(f_ck / f_yd * d_dg / d) + + +def v_Ed(vEd_x: float, vEd_y: float) -> float: + """Calculate the design shear force per unit width (vEd). + + EN1992-1-1:2023 Eq. (8.21) + + Args: + vEd_x (float): Shear force in x-direction in kN/m. + vEd_y (float): Shear force in y-direction in kN/m. + + Returns: + float: Design shear force per unit width (vEd) kN/m. + + Raises: + ValueError: If vEd_x or vEd_y is negative. + """ + if vEd_x < 0: + raise ValueError(f'vEd_x must not be negative. Got {vEd_x}') + if vEd_y < 0: + raise ValueError(f'vEd_y must not be negative. Got {vEd_y}') + + return math.sqrt(vEd_x**2 + vEd_y**2) + + +def d_eff(dx: float, dy: float, vEd_x: float, vEd_y: float) -> float: + """Calculate the effective depth (d) based on the ratio of shear forces. + + EN1992-1-1:2023 Eq. (8.22), (8.23), (8.24) + + Args: + dx (float): Effective depth in x-direction in mm. + dy (float): Effective depth in y-direction in mm. + vEd_x (float): Shear force in x-direction in kN/m. + vEd_y (float): Shear force in y-direction in kN/m. + + Returns: + float: Effective depth (d) in mm. + + Raises: + ValueError: If dx, dy, vEd_x, or vEd_y is negative. + """ + if dx < 0: + raise ValueError(f'dx must not be negative. Got {dx}') + if dy < 0: + raise ValueError(f'dy must not be negative. Got {dy}') + + vEd_x = abs(vEd_x) + vEd_y = abs(vEd_y) + + ratio = vEd_y / vEd_x if vEd_x != 0 else float('inf') + + if ratio <= 0.5: + return dx + if 0.5 < ratio < 2: + return 0.5 * (dx + dy) + return dy + + +def d_eff_with_angle( + dx: float, dy: float, vEd_x: float, vEd_y: float +) -> float: + """Calculate the effective depth (d) based on the angle αv. + + EN1992-1-1:2023 Eq. (8.25), (8.26) + + Args: + dx (float): Effective depth in x-direction in mm. + dy (float): Effective depth in y-direction in mm. + vEd_x (float): Shear force in x-direction in kN/m. + vEd_y (float): Shear force in y-direction kN/m. + + Returns: + float: Effective depth (d) mm. + + Raises: + ValueError: If dx, dy, vEd_x, or vEd_y is negative. + """ + if dx < 0: + raise ValueError(f'dx must not be negative. Got {dx}') + if dy < 0: + raise ValueError(f'dy must not be negative. Got {dy}') + + vEd_x = abs(vEd_x) + vEd_y = abs(vEd_y) + + alpha_v = math.atan2(vEd_y, vEd_x) + + return dx * math.cos(alpha_v) ** 2 + dy * math.sin(alpha_v) ** 2 + + +def tau_Rdc( + gamma_v: float, + rho_l: float, + f_ck: float, + d: float, + d_g: float, + tau_rdc_min: float, +) -> float: + """Calculate the design value of the shear stress resistance. + + EN1992-1-1:2023 Eq. (8.27) + + Args: + gamma_v (float): Partial factor for shear (unitless). + rho_l (float): Reinforcement ratio (unitless). + f_ck (float): Characteristic compressive strength of concrete in MPa. + d (float): Effective depth in mm. + d_g (float): Maximum aggregate size in mm. + tau_rdc_min (float): Minimum resistance neede in MPa. + + Returns: + float: The design value of the shear stress resistance MPa. + + Raises: + ValueError: If any of the input values are negative. + """ + if gamma_v <= 0 or rho_l <= 0 or f_ck <= 0 or d <= 0 or d_g <= 0: + raise ValueError( + f'All input values must be positive. Got gamma_v={gamma_v}, ' + + f'rho_l={rho_l}, f_ck={f_ck}, d={d}, d_g={d_g}' + ) + + return max( + 0.66 / gamma_v * (100 * rho_l * f_ck * d_g / d) ** (1 / 3), + abs(tau_rdc_min), + ) + + +def rho_l(A_sl: float, b_w: float, d: float) -> float: + """Calculate the reinforcement ratio. + + EN1992-1-1:2023 Eq. (8.28) + + Args: + A_sl (float): Effective area of tensile reinforcement in mm2. + b_w (float): Width of the cross-section in mm. + d (float): Effective depth in mm. + + Returns: + float: The reinforcement ratio (unitless). + + Raises: + ValueError: If any of the input values are negative. + """ + if A_sl <= 0 or b_w <= 0 or d <= 0: + raise ValueError( + 'All input values must be positive.' + + f'Got A_sl={A_sl}, b_w={b_w}, d={d}' + ) + + return A_sl / (b_w * d) + + +def a_v(a_cs: float, d: float) -> float: + """Calculate the effective shear span. + + EN1992-1-1:2023 Eq. (8.30) + + Args: + M_Ed (float): Bending moment kN·m. + V_Ed (float): Shear force kN. + d (float): Effective depth mm. + + Returns: + float: The effective shear span in mm. + + Raises: + ValueError: If any of the input values are negative. + """ + if a_cs <= 0 or d <= 0: + raise ValueError( + 'All input values must be positive.' + f'Got a_cs={a_cs}, d={d}' + ) + return math.sqrt(a_cs / 4 * d) + + +def a_cs(M_Ed: float, V_Ed: float, d: float) -> float: + """Calculate the effective shear span. + + EN1992-1-1:2023 Eq. (8.30) + + Args: + M_Ed (float): Bending moment kN·m. + V_Ed (float): Shear force kN. + d (float): Effective depth mm. + + Returns: + float: The effective shear span in mm. + + Raises: + ValueError: If any of the input values are negative. + """ + if d <= 0: + raise ValueError('All input values must be positive.' + f'Got d={d}') + + return max(abs(M_Ed * 1000 / V_Ed), d) + + +def k_vp(N_Ed: float, V_Ed: float, d: float, a_cs: float) -> float: + """Calculate the coefficient k_vp. + + EN1992-1-1:2023 Eq. (8.31) + + Args: + N_Ed (float): Axial force in kN. + V_Ed (float): Shear force in kN. + d (float): Effective depth in mm. + a_cs (float): Effective shear span in mm. + + Returns: + float: The coefficient k_vp (unitless). + + Raises: + ValueError: If any of the input values are negative. + + """ + if d <= 0 or a_cs <= 0: + raise ValueError( + 'All input values must be positive. ' + + f'Got N_Ed={N_Ed}, V_Ed={V_Ed}, d={d}, a_cs={a_cs}' + ) + + k_vp = 1 + (N_Ed / abs(V_Ed)) * (d / (3 * a_cs)) + return max(k_vp, 0.1) + + +def tau_Rdc_0( + gamma_v: float, rho_l: float, f_ck: float, d: float, d_g: float +) -> float: + """Calculate the design value of the shear stress + resistance without axial force effects. + + EN1992-1-1:2023 Eq. (8.33) + + Args: + gamma_v (float): Partial factor for shear (unitless). + rho_l (float): Reinforcement ratio (unitless). + f_ck (float): Characteristic compressive strength of concrete in MPa. + d (float): Effective depth in mm. + d_g (float): Maximum aggregate size in mm. + + Returns: + float: The design value of the shear stress resistance in MPa. + + Raises: + ValueError: If any of the input values are negative. + """ + if gamma_v <= 0 or rho_l <= 0 or f_ck <= 0 or d <= 0 or d_g <= 0: + raise ValueError( + 'All input values must be positive. ' + + f'Got gamma_v={gamma_v}, rho_l={rho_l}, ' + + f'f_ck={f_ck}, d={d}, d_g={d_g}' + ) + + return 0.66 / gamma_v * (100 * rho_l * f_ck * d_g / d) ** (1 / 3) + + +def tau_Rdc_comp( + tau_Rdc_0: float, k1: float, sigma_cp: float, tau_Rdc_max: float +) -> float: + """Calculate the design value of the shear stress + resistance considering compressive normal forces. + + EN1992-1-1:2023 Eq. (8.32) + + Args: + tau_Rdc_0 (float): Design value of the shear stress resistance + without axial force effects in MPa. + k1 (float): Factor considering the effect of compressive + normal forces (unitless). + sigma_cp (float): Compressive stress due to axial force in MPa. + tau_Rdc_max (float): Maximum design value of the + shear stress resistance in MPa. + + Returns: + float: The design value of the shear stress resistance in MPa. + + Raises: + ValueError: If any of the input values are negative. + """ + if tau_Rdc_0 <= 0 or k1 <= 0 or sigma_cp < 0 or tau_Rdc_max <= 0: + raise ValueError( + 'All input values must be positive. ' + + f'Got tau_Rdc_0={tau_Rdc_0}, k1={k1}, ' + + f'sigma_cp={sigma_cp}, tau_Rdc_max={tau_Rdc_max}' + ) + + tau_Rdc = tau_Rdc_0 - k1 * sigma_cp + return min(tau_Rdc, tau_Rdc_max) + + +def k1( + a_cs_0: float, + e_p: float, + A_c: float, + b_w: float, + z: float, + d: float, +) -> float: + """Calculate the factor k1 considering the effect of + compressive normal forces. + + EN1992-1-1:2023 Eq. (8.34) + + Args: + a_cs_0 (float): Effective shear span without + considering prestressing effects in mm. + e_p (float): Eccentricity of the prestressing + force or external load in mm. + A_c (float): Area of concrete cross-section in mm2. + b_w (float): Width of the cross-section in mm. + z (float): Lever arm in mm. + d (float): Effective depth in mm. + + Returns: + float: The factor k1 (unitless). + + Raises: + ValueError: If any of the input values are negative. + """ + if a_cs_0 <= 0 or e_p < 0 or A_c <= 0 or b_w <= 0 or z <= 0: + raise ValueError( + 'All input values must be positive.' + + f' Got a_cs_0={a_cs_0}, e_p={e_p}, A_c={A_c}, b_w={b_w}, z={z}' + ) + + k1 = 0.5 * a_cs_0 / (e_p + d / 3) * (A_c / (b_w * z)) + return min(k1, A_c * 0.18 / (b_w * z)) + + +def tau_Rdc_max(tau_Rdc_0: float, a_cs_0: float, d: float) -> float: + """Calculate the maximum design value of the shear stress resistance. + + EN1992-1-1:2023 Eq. (8.35) + + Args: + tau_Rdc_0 (float): Design value of the shear stress + resistance without axial force effects in MPa. + a_cs_0 (float): Effective shear span without + considering prestressing effects in mm. + d (float): Effective depth in mm. + + Returns: + float: The maximum design value of the shear stress resistance in MPa. + + Raises: + ValueError: If any of the input values are negative. + """ + if tau_Rdc_0 <= 0 or a_cs_0 <= 0 or d <= 0: + raise ValueError( + 'All input values must be positive. ' + + f'Got tau_Rdc_0={tau_Rdc_0}, a_cs_0={a_cs_0}, d={d}' + ) + + tau_Rdc_max = 2.15 * tau_Rdc_0 * (a_cs_0 / d) ** (1 / 6) + return min(tau_Rdc_max, 2.7 * tau_Rdc_0) + + +def d_eff_p(ds: float, As: float, dp: float, Ap: float) -> float: + """Calculate the effective depth for prestressed + members with bonded tendons. + + EN1992-1-1:2023 Eq. (8.36) + Parameters: + ds (float): Depth of the tension reinforcement in mm + As (float): Area of the tension reinforcement in mm2 + dp (float): Depth of the prestressed reinforcement in mm + Ap (float): Area of the prestressed reinforcement in mm2 + + Returns: + float: Effective depth in mm + + Raises: + ValueError: If any of the input values are negative + """ + if ds < 0: + raise ValueError(f'ds must not be negative. Got {ds}') + if As < 0: + raise ValueError(f'As must not be negative. Got {As}') + if dp < 0: + raise ValueError(f'dp must not be negative. Got {dp}') + if Ap < 0: + raise ValueError(f'Ap must not be negative. Got {Ap}') + + return (ds**2 * As + dp**2 * Ap) / (ds * As + dp * Ap) + + +def rho_l_p( + ds: float, As: float, dp: float, Ap: float, bw: float, d: float +) -> float: + """Calculate the reinforcement ratio for prestressed + members with bonded tendons. + + EN1992-1-1:2023 Eq. (8.37) + Parameters: + ds (float): Depth of the tension reinforcement in mm + As (float): Area of the tension reinforcement in mm2 + dp (float): Depth of the prestressed reinforcement in mm + Ap (float): Area of the prestressed reinforcement in mm2 + bw (float): Width of the member in mm + d (float): Effective depth in mm + + Returns: + float: Reinforcement ratio + + Raises: + ValueError: If any of the input values are negative + """ + if ds < 0: + raise ValueError(f'ds must not be negative. Got {ds}') + if As < 0: + raise ValueError(f'As must not be negative. Got {As}') + if dp < 0: + raise ValueError(f'dp must not be negative. Got {dp}') + if Ap < 0: + raise ValueError(f'Ap must not be negative. Got {Ap}') + if bw < 0: + raise ValueError(f'bw must not be negative. Got {bw}') + if d < 0: + raise ValueError(f'd must not be negative. Got {d}') + + return (ds * As + dp * Ap) / (bw * d**2) + + +def rho_l_planar( + vEd_y: float, vEd_x: float, rho_l_x: float, rho_l_y: float +) -> float: + """Calculate the reinforcement ratio for planar members with + different reinforcement ratios in both directions. + + EN1992-1-1:2023 Eq. (8.38), (8.39), (8.40) + + Parameters: + vEd_y (float): Shear force in y-direction (kN) + vEd_x (float): Shear force in x-direction (kN) + rho_l_x (float): Reinforcement ratio in x-direction + rho_l_y (float): Reinforcement ratio in y-direction + + Returns: + float: Reinforcement ratio + + Raises: + ValueError: If any of the input values are negative or if the + shear force ratio is not in the valid range + """ + if vEd_y < 0: + raise ValueError(f'vEd_y must not be negative. Got {vEd_y}') + if vEd_x < 0: + raise ValueError(f'vEd_x must not be negative. Got {vEd_x}') + if rho_l_x < 0: + raise ValueError(f'rho_l_x must not be negative. Got {rho_l_x}') + if rho_l_y < 0: + raise ValueError(f'rho_l_y must not be negative. Got {rho_l_y}') + + ratio = vEd_y / vEd_x + + if ratio <= 0.5: + rho_l = rho_l_x + elif ratio >= 2: + rho_l = rho_l_y + else: + alpha_v = math.atan(vEd_y / vEd_x) + rho_l = ( + rho_l_x * math.cos(alpha_v) ** 4 + rho_l_y * math.sin(alpha_v) ** 4 + ) + + return rho_l diff --git a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py new file mode 100644 index 00000000..2aa6f5f9 --- /dev/null +++ b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py @@ -0,0 +1,294 @@ +"""Tests for the section EC2 2023 8.2 Shear.""" + +import math + +import pytest + +from structuralcodes.codes.ec2_2023 import _section_8_2_shear + + +@pytest.mark.parametrize( + 'VEd, bw, d, expected', + [ + (100.0, 0.3, 0.5, 100.0 * 1000.0 / (0.3 * 0.9 * 0.5)), + (200.0, 0.5, 0.6, 200.0 * 1000.0 / (0.5 * 0.9 * 0.6)), + ], +) +def test_tao_Ed(VEd, bw, d, expected): + """Test shear_stress_linear_members.""" + assert _section_8_2_shear.tao_Ed(VEd, bw, d) == pytest.approx(expected) + + +@pytest.mark.parametrize( + 'vEd, d, expected', + [ + (50.0, 0.5, 50.0 / (0.9 * 0.5)), + (100.0, 0.6, 100.0 / (0.9 * 0.6)), + ], +) +def test_tao_Ed_planar(vEd, d, expected): + """Test shear_stress_planar_members.""" + assert _section_8_2_shear.tao_Ed_planar(vEd, d) == pytest.approx(expected) + + +@pytest.mark.parametrize( + 'gamma_v, f_ck, f_yd, d, d_lower, expected', + [ + (1.4, 30, 500, 500, 20, 0.51642), + (1.5, 40, 600, 600, 25, 0.48888), + (1.3, 70, 700, 700, 30, 0.62377), + ], +) +def test_tau_rdc_min(gamma_v, f_ck, f_yd, d, d_lower, expected): + """Test the calculate_tau_rdc_min function with example values.""" + result = _section_8_2_shear.tau_rdc_min(gamma_v, f_ck, f_yd, d, d_lower) + assert result == pytest.approx(expected, rel=1e-4) + + +@pytest.mark.parametrize( + 'vEd_x, vEd_y, expected', + [ + (3.0, 4.0, 5.0), # Pythagorean triplet + (0.0, 4.0, 4.0), # One direction zero + (3.0, 0.0, 3.0), # One direction zero + ], +) +def test_v_Ed(vEd_x, vEd_y, expected): + """Test calculation of design shear force per unit width.""" + assert math.isclose( + _section_8_2_shear.v_Ed(vEd_x, vEd_y), + expected, + rel_tol=1e-9, + ) + + +@pytest.mark.parametrize( + 'dx, dy, vEd_x, vEd_y, expected', + [ + (300, 400, 3.0, 1.0, 300), # Ratio <= 0.5 + (300, 400, 3.0, 3.0, 350), # Ratio between 0.5 and 2 + (300, 400, 1.0, 3.0, 400), # Ratio >= 2 + ], +) +def test_d_eff(dx, dy, vEd_x, vEd_y, expected): + """Test calculation of effective depth based on shear force ratio.""" + assert math.isclose( + _section_8_2_shear.d_eff(dx, dy, vEd_x, vEd_y), expected, rel_tol=1e-9 + ) + + +@pytest.mark.parametrize( + 'dx, dy, vEd_x, vEd_y, expected', + [ + (300, 400, 3.0, 0.0, 300), # alpha_v = 0 + (300, 400, 0.0, 3.0, 400), # alpha_v = 90 degrees + (300, 400, 3.0, 3.0, 350), # alpha_v = 45 degrees + ], +) +def test_ed_eff_with_angle(dx, dy, vEd_x, vEd_y, expected): + """Test calculation of effective depth based on angle alpha_v.""" + assert math.isclose( + _section_8_2_shear.d_eff_with_angle(dx, dy, vEd_x, vEd_y), + expected, + rel_tol=1e-9, + ) + + +@pytest.mark.parametrize( + 'gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min, expected', + [ + (1.5, 0.02, 30, 500, 16, 0.3, 0.5468), + (1.4, 0.03, 40, 450, 20, 0.5, 0.82366), + (1.6, 0.025, 35, 600, 18, 0.1, 0.5690), + ], +) +def test_calculate_tau_Rdc( + gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min, expected +): + """Test the calculation of the shear stress resistance.""" + result = _section_8_2_shear.tau_Rdc( + gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min + ) + assert math.isclose( + result, + expected, + rel_tol=1e-3, + ) + + +@pytest.mark.parametrize( + 'A_sl, b_w, d, expected', + [ + (300, 200, 500, 0.003), + (400, 250, 600, 0.00266667), + (500, 300, 700, 0.00238095), + ], +) +def test_rho_l(A_sl, b_w, d, expected): + """Test the calculation of the reinforcement ratio.""" + assert math.isclose( + _section_8_2_shear.rho_l(A_sl, b_w, d), expected, rel_tol=1e-5 + ) + + +@pytest.mark.parametrize( + 'a_cs, d, expected', + [ + (300, 200, 122.4744), + (400, 250, 158.1138), + (500, 300, 193.6491), + ], +) +def test_a_v(a_cs, d, expected): + """Test the a_v.""" + result = _section_8_2_shear.a_v(a_cs, d) + assert math.isclose(result, expected, rel_tol=1e-5) + + +@pytest.mark.parametrize( + 'M_Ed, V_Ed, d, expected', + [ + (1000, 100, 500, 10000), + (2000, 200, 600, 10000), + (1500, 150, 700, 10000), + ], +) +def test_calculate_a_cs(M_Ed, V_Ed, d, expected): + """Test the calculation of the effective shear span.""" + result = _section_8_2_shear.a_cs(M_Ed, V_Ed, d) + assert math.isclose(result, expected, rel_tol=1e-5) + + +@pytest.mark.parametrize( + 'N_Ed, V_Ed, d, a_cs, expected', + [ + (100, 50, 500, 1500, 1.2222), + (200, 100, 600, 1600, 1.25), + (-150, 75, 700, 1700, 0.7254), + ], +) +def test_calculate_k_vp(N_Ed, V_Ed, d, a_cs, expected): + """Test the calculation of the coefficient k_vp.""" + result = _section_8_2_shear.k_vp(N_Ed, V_Ed, d, a_cs) + assert math.isclose(result, expected, rel_tol=1e-3) + + +@pytest.mark.parametrize( + 'gamma_v, rho_l, f_ck, d, d_g, expected', + [ + (1.5, 0.02, 30, 500, 16, 0.5468), + (1.4, 0.03, 40, 450, 20, 0.8236), + (1.6, 0.025, 35, 600, 18, 0.5690), + ], +) +def test_calculate_tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_g, expected): + """Test the sh stress resistance wo/ axial force effects.""" + result = _section_8_2_shear.tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_g) + assert math.isclose( + result, + expected, + rel_tol=1e-3, + ) + + +@pytest.mark.parametrize( + 'tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, expected', + [ + (1, 0.5, 0.1, 2, 0.95), + (1, 0.6, 0.2, 2, 0.88), + (1, 0.4, 0.3, 2, 0.88), + ], +) +def test_calculate_tau_Rdc_comp( + tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, expected +): + """Test the calculation of the shear considering comp normal forces.""" + assert math.isclose( + _section_8_2_shear.tau_Rdc_comp(tau_Rdc_0, k1, sigma_cp, tau_Rdc_max), + expected, + rel_tol=1e-5, + ) + + +@pytest.mark.parametrize( + 'a_cs_0, e_p, A_c, b_w, z, d, expected', + [ + (1000, 50, 10000, 200, 500, 200, 0.018), + (1200, 60, 12000, 250, 600, 200, 0.0144), + (1100, 55, 11000, 220, 550, 200, 0.01636), + ], +) +def test_calculate_k1(a_cs_0, e_p, A_c, b_w, z, d, expected): + """Test the calculation of the factor k1.""" + result = _section_8_2_shear.k1(a_cs_0, e_p, A_c, b_w, z, d) + assert math.isclose(result, expected, rel_tol=1e-3) + + +@pytest.mark.parametrize( + 'tau_Rdc_0, a_cs_0, d, expected', + [ + (1, 1000, 500, 2.4132), + (0.9, 900, 450, 2.1719), + (1.1, 1100, 550, 2.6546), + ], +) +def test_calculate_tau_Rdc_max(tau_Rdc_0, a_cs_0, d, expected): + """Test the calculation of the maximum shear stress resistance.""" + result = _section_8_2_shear.tau_Rdc_max(tau_Rdc_0, a_cs_0, d) + assert math.isclose( + result, + expected, + rel_tol=1e-3, + ) + + +@pytest.mark.parametrize( + 'ds, As, dp, Ap, expected', + [ + (500, 2000, 600, 1500, 545.45), + (400, 1000, 450, 500, 418.0), + ], +) +def test_d_eff_p(ds, As, dp, Ap, expected): + """Test the calculation of effective depth.""" + result = _section_8_2_shear.d_eff_p(ds, As, dp, Ap) + assert math.isclose( + result, + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'ds, As, dp, Ap, bw, d, expected', + [ + (500, 2000, 600, 1500, 300, 545.45, 0.02128), + (400, 1000, 450, 500, 250, 425.00, 0.0138), + ], +) +def test_calculate_reinforcement_ratio(ds, As, dp, Ap, bw, d, expected): + """Test the calculation of reinforcement ratio.""" + result = _section_8_2_shear.rho_l_p(ds, As, dp, Ap, bw, d) + assert math.isclose( + result, + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'vEd_y, vEd_x, rho_l_x, rho_l_y, expected', + [ + (20, 40, 0.005, 0.008, 0.005), # vEd_y/vEd_x <= 0.5 + (40, 20, 0.005, 0.008, 0.008), # vEd_y/vEd_x >= 2 + (30, 40, 0.005, 0.008, 0.00308), # 0.5 < vEd_y/vEd_x < 2 + ], +) +def test_rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y, expected): + """Test the calculation of reinforcement ratio for planar members.""" + result = _section_8_2_shear.rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y) + assert math.isclose( + result, + expected, + rel_tol=1e-2, + ) From ff14d07c4ae9687e57432ad66aae61b7f999f388 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 1 Aug 2024 12:27:55 +0200 Subject: [PATCH 23/28] 8.2.5 --- structuralcodes/codes/ec2_2023/__init__.py | 64 ++ .../codes/ec2_2023/_section_8_2_shear.py | 954 ++++++++++++++++++ .../test_ec2_2023_section_8_2_shear.py | 379 +++++++ 3 files changed, 1397 insertions(+) diff --git a/structuralcodes/codes/ec2_2023/__init__.py b/structuralcodes/codes/ec2_2023/__init__.py index c3809cb8..2abd094c 100644 --- a/structuralcodes/codes/ec2_2023/__init__.py +++ b/structuralcodes/codes/ec2_2023/__init__.py @@ -49,15 +49,47 @@ wk_cal2, ) from ._section_8_2_shear import ( + Asf_flang, + Ast_min_flang, + Fcd, + Ftd, + Nvd, a_cs, a_v, + bw_nom, + cot_theta, + cot_theta_min, d_eff, + d_eff_p, d_eff_with_angle, + delta_MEd, + eps_x_flang, + epsilon_x, + epsilon_xc_comp, + epsilon_xc_tens, + epsilon_xt, k1, + k_duct, k_vp, + nu, rho_l, + rho_l_p, + rho_l_planar, + rho_w, + sigma_cd, + sigma_cd_flang, + sigma_cd_s, + sigma_swd, + sigma_swd_v2, tao_Ed, + tao_Ed_flang, tao_Ed_planar, + tao_Rd_m, + tau_Rd, + tau_rd, + tau_rd_incl, + tau_Rd_sy, + tau_rd_sy, tau_Rdc, tau_Rdc_0, tau_Rdc_comp, @@ -67,6 +99,37 @@ ) __all__ = [ + 'eps_x_flang', + 'tao_Ed_flang', + 'Ast_min_flang', + 'Asf_flang', + 'sigma_cd_flang', + 'tao_Rd_m', + 'sigma_cd_s', + 'sigma_swd_v2', + 'tau_rd_sy', + 'tau_rd_incl', + 'delta_MEd', + 'tau_rd', + 'sigma_swd', + 'bw_nom', + 'Fcd', + 'Nvd', + 'Ftd', + 'd_eff_p', + 'sigma_cd', + 'rho_l_planar', + 'rho_l_p', + 'epsilon_x', + 'epsilon_xc_comp', + 'epsilon_xc_tens', + 'epsilon_xt', + 'nu', + 'cot_theta_min', + 'rho_w', + 'cot_theta', + 'tau_Rd', + 'tau_Rd_sy', 'a_cs', 'a_v', 'k1', @@ -124,6 +187,7 @@ 'srm_cal', 'wk_cal', 'wk_cal2', + 'k_duct', ] __title__: str = 'EUROCODE 2 1992-1-1:2023' diff --git a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py index b45b397a..1d939259 100644 --- a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py +++ b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py @@ -1,4 +1,5 @@ import math +from typing import List, Literal def tao_Ed(VEd: float, bw: float, d: float) -> float: @@ -562,3 +563,956 @@ def rho_l_planar( ) return rho_l + + +def cot_theta_min( + NEd: float, + VEd: float, + x: float, + d: float, +) -> float: + """Calculate the minimum cotangent of the compression field inclination + angle, θmin, according to the conditions provided. + + EN1992-1-1:2023 Eq. (8.41) + + Args: + NEd (float): Axial force in the member in kN. + VEd (float): Shear force in the member in kN. + x (float): Depth of the compression chord in mm. + d (float): Effective depth of the member in mm. + + Returns: + float: Minimum cotangent of the compression field inclination angle. + + Raises: + ValueError: If any of the dimensions or forces are negative, + or if d is zero. + """ + if d <= 0 or x < 0: + raise ValueError( + 'Dimensions and forces must be positive, and d must not be zero.' + ) + + if NEd > 0: + cot_theta_min_value = 2.5 - 0.1 * NEd / abs(VEd) + return max(cot_theta_min_value, 1.0) + if NEd < 0: + return 3.0 + + return 2.5 # NEd == 0 + + +def tau_Rd_sy(rho_w: float, fywd: float, cot_theta: float) -> float: + """Calculate the shear stress resistance of yielding shear reinforcement. + + EN1992-1-1:2023 Eq. (8.42) + + Args: + rho_w (float): Shear reinforcement ratio (unitless). + fywd (float): Design yield strength of the shear reinforcement in MPa. + cot_theta (float): Cotangent of the angle of the compression field. + + Returns: + float: Shear stress resistance in MPa. + + Raises: + ValueError: If rho_w or fywd is negative. + """ + if rho_w < 0 or fywd < 0: + raise ValueError( + 'Shear reinforcement ratio and yield strength must not be negative' + ) + + return rho_w * fywd * cot_theta + + +def rho_w(Asw: float, bw: float, s: float) -> float: + """Calculate the shear reinforcement ratio ρw. + + EN1992-1-1:2023 Eq. (8.43) + + Args: + Asw (float): Area of shear reinforcement in mm2. + bw (float): Width of the web in mm. + s (float): Spacing of the shear reinforcement in mm. + + Returns: + float: Shear reinforcement ratio, unitless. + + Raises: + ValueError: If Asw, bw, or s is negative or zero. + + """ + if Asw <= 0 or bw <= 0 or s <= 0: + raise ValueError('Asw, bw, and s must be positive and non-zero.') + + return Asw / (bw * s) + + +def sigma_cd( + tau_Ed: float, cot_theta: float, tan_theta: float, nu: float, f_cd: float +) -> float: + """Calculate the stress in the compression field σcd and verify it. + + EN1992-1-1:2023 Eq. (8.44) + + Args: + tau_Ed (float): Design value of the shear stress in MPa. + cot_theta (float): Cotangent of the angle of the compression field. + tan_theta (float): Tangent of the angle of the compression field. + nu (float): Coefficient (usually 0.5 as per the note). + f_cd (float): Design value of the concrete compressive strength in MPa. + + Returns: + float: Stress in the compression field σcd in MPa. + + Raises: + ValueError: If any of the parameters are negative. + """ + if tau_Ed < 0 or cot_theta < 0 or tan_theta < 0 or nu < 0 or f_cd < 0: + raise ValueError('All parameters must be positive.') + + sigma_cd_value = tau_Ed * (cot_theta + tan_theta) + return min(sigma_cd_value, nu * f_cd) + + +def tau_Rd( + rho_w: float, fywd: float, cot_theta: float, nu: float, f_cd: float +) -> float: + """Calculate the shear stress resistance τRd considering the simultaneous + yielding of the shear reinforcement and failure of the compression + field. + + EN1992-1-1:2023 Eq. (8.42) and (8.44) + + Args: + rho_w (float): Shear reinforcement ratio, unitless. + fywd (float): Design yield strength of the shear reinforcement in MPa. + cot_theta (float): Cotangent of the angle of the compression field. + nu (float): Coefficient (usually 0.5 as per the note). + f_cd (float): Design value of the concrete compressive strength in MPa. + + Returns: + float: Shear stress resistance τRd in MPa. + + Raises: + ValueError: If any of the parameters are negative. + """ + if rho_w < 0 or fywd < 0 or cot_theta < 0 or nu < 0 or f_cd < 0: + raise ValueError('All parameters must be positive.') + + tau_Rd_value = rho_w * fywd * cot_theta + return min(tau_Rd_value, nu * f_cd / 2) + + +def cot_theta( + nu: float, f_cd: float, rho_w: float, fywd: float, cot_theta_min: float +) -> float: + """Calculate the cotangent of the angle of the compression field + considering the simultaneous yielding of the shear reinforcement and + failure of the compression field. + + EN1992-1-1:2023 Eq. (8.44) + + Args: + nu (float): Coefficient (usually 0.5 as per the note). + f_cd (float): Design value of the concrete compressive strength in MPa. + rho_w (float): Shear reinforcement ratio, unitless. + fywd (float): Design yield strength of the shear reinforcement in MPa. + cot_theta_min (float): Value of cot_theta_min. + + Returns: + float: Cotangent of the angle of the compression field. + + Raises: + ValueError: If any of the parameters are negative. + """ + if nu < 0 or f_cd < 0 or rho_w < 0 or fywd < 0: + raise ValueError('All parameters must be positive.') + + cot_theta_value = (nu * f_cd) / (rho_w * fywd) - 1 + return min(max(cot_theta_value, 1.0), cot_theta_min) + + +def epsilon_xt(Ftd: float, Est: float, Ast: float) -> float: + """Calculate εxt. + + EN1992-1-1:2023 Eq. (8.47) + + Args: + Ftd (float): Tensile force in the flexural tension chord in kN + Est (float): Modulus of elasticity of the steel in the + tension chord in MPa + Ast (float): Area of the longitudinal reinforcement in the + flexural tension chord in mm2 + + Returns: + float: εxt (strain) + """ + if Est < 0: + raise ValueError(f'Est must not be negative. Got {Est}') + if Ast < 0: + raise ValueError(f'Ast must not be negative. Got {Ast}') + return abs(Ftd) * 1000 / (Est * Ast) + + +def epsilon_xc_comp(Fcd: float, Ecc: float, Acc: float) -> float: + """Calculate εxc for compression. + + EN1992-1-1:2023 Eq. (8.48) + + Args: + Fcd (float): Compressive force in the flexural compression chord in kN + Ecc (float): Modulus of elasticity of the concrete in the + compression chord in MPa + Acc (float): Area of the flexural compression + chord in mm2 + + Returns: + float: εxc (strain) + """ + if Acc < 0: + raise ValueError(f'Acc must not be negative. Got {Acc}') + if Ecc < 0: + raise ValueError(f'Ecc must not be negative. Got {Ecc}') + return abs(Fcd) * 1000 / (Ecc * Acc) + + +def epsilon_xc_tens(Fcd: float, Esc: float, Asc: float) -> float: + """Calculate εxc. + + EN1992-1-1:2023 Eq. (8.49) + + Args: + Fcd (float): Tensile force in the flexural compression chord kN + Esc (float): Modulus of elasticity of the steel in the + compression chord in MPa + Asc (float): Area of the longitudinal reinforcement in the flexural + compression chord mm2 + + Returns: + float: εxc (strain) + """ + if Asc < 0: + raise ValueError(f'Acc must not be negative. Got {Asc}') + if Esc < 0: + raise ValueError(f'Esc must not be negative. Got {Esc}') + return abs(Fcd) * 1000 / (Esc * Asc) + + +def epsilon_x(epsilon_xt: float, epsilon_xc: float) -> float: + """Calculate εx. + + EN1992-1-1:2023 Eq. (8.46) + + Args: + epsilon_xt (float): Strain in the flexural tension chord + epsilon_xc (float): Strain in the flexural compression chord + + Returns: + float: εx (strain) + """ + epsilon_x_value = (epsilon_xt + epsilon_xc) / 2 + return max(epsilon_x_value, 0) + + +def nu(epsilon_x: float, cot_theta: float) -> float: + """Calculate ν. + + EN1992-1-1:2023 Eq. (8.45) + + Args: + epsilon_x (float): Average strain of the bottom and top chords + cot_theta (float): cotan of the compression field + inclination to the member axis. + + Returns: + float: ν (dimensionless factor) + """ + if epsilon_x < 0: + raise ValueError(f'epsilon_x must not be negative. Got {epsilon_x}') + + nu_value = 1 / ( + 1.0 + 110 * (epsilon_x + (epsilon_x + 0.001) * cot_theta**2) + ) + return min(nu_value, 1.0) + + +def Nvd(VEd: float, cot_theta: float) -> float: + """Calculate the additional tensile axial force NVd due to shear VEd. + + EN1992-1-1:2023 Eq. (8.50) + + Args: + VEd (float): Shear force in kN. + cot_theta (float): Cotangent of the angle. + + Returns: + float: Additional tensile axial force NVd in kN. + """ + return abs(VEd) * cot_theta + + +def Ftd( + MEd: float, + z: float, + NVd: float, + NE: float, +) -> float: + """Calculate the chord force Ftd. + + Args: + MEd (float): Moment in kNm. + z (float): Lever arm in mm. + NVd (float): Additional tensile axial force in kN. + NE (float): Axial force in kN. + + Returns: + float: Chord force Ftd in kN. + + Raises: + ValueError: If any input is negative. + + Reference: + EN1992-1-1:2023 Eq. (8.51) + """ + return MEd * 1000 / z + (NVd + NE) / 2 + + +def Fcd( + MEd: float, + z: float, + NVd: float, + NE: float, +) -> float: + """Calculate the chord force Fcd. + + EN1992-1-1:2023 Eq. (8.52) + + Args: + MEd (float): Moment in kNm. + z (float): Lever arm in mm. + NVd (float): Additional tensile axial force in kN. + NE (float): Axial force in kN. + + Returns: + float: Chord force Fcd in kN. + """ + return MEd * 1000 / z - (NVd + NE) / 2 + + +def k_duct( + duct_material: Literal['steel', 'plastic'], + is_grouted: bool, + wall_thickness: float, + duct_diameter: float, +) -> float: + """Calculate the k_duct coefficient based on duct + material, filling, and wall thickness. + + EN1992-1-1:2023 guidelines for k_duct + + Args: + duct_material (str): Material of the duct ('steel' or 'plastic'). + is_grouted (bool): True if the duct is grouted, False otherwise. + wall_thickness (float): Wall thickness of the duct in mm. + duct_diameter (float): Outer diameter of the duct in mm. + + Returns: + float: Coefficient k_duct. + + Raises: + ValueError: If wall_thickness or duct_diameter is negative. + ValueError: If duct_material is not 'steel' or 'plastic'. + + """ + if wall_thickness < 0: + raise ValueError( + f'Wall thickness must not be negative. Got {wall_thickness}' + ) + if duct_diameter < 0: + raise ValueError( + f'Duct diameter must not be negative. Got {duct_diameter}' + ) + + max_thickness = max(0.035 * duct_diameter, 2.0) + + if duct_material == 'steel' and is_grouted: + return 0.5 + if duct_material == 'plastic': + if is_grouted: + if wall_thickness <= max_thickness: + return 0.8 + return 1.2 + return 1.2 + raise ValueError( + 'Invalid duct material. Expected "steel" or "plastic". ' + + f'Got {duct_material}' + ) + + +def bw_nom( + bw: float, + duct_diameters: List[float], + k_duct: float, +) -> float: + """Calculate the nominal web width considering the presence of ducts. + + EN1992-1-1:2023 Eq. (8.54) + + Args: + bw (float): Actual web width in mm. + duct_diameters (List[float]): List of duct diameters in mm + (each must be non-negative). + k_duct (float): Coefficient depending on the + material and filling of the duct. + + Returns: + float: Nominal web width in mm. + + Raises: + ValueError: If bw is negative or if any duct diameter is negative. + ValueError: If the sum of duct diameters exceeds bw/8. + """ + if bw < 0: + raise ValueError(f'bw must not be negative. Got {bw}') + for d in duct_diameters: + if d < 0: + raise ValueError(f'Duct diameters must not be negative. Got {d}') + + sum_phi_duct = sum(duct_diameters) + if sum_phi_duct > bw / 8: + raise ValueError( + 'Sum of duct diameters exceeds bw/8.' + + f'Got sum {sum_phi_duct}, limit {bw/8}' + ) + + return bw - k_duct * sum_phi_duct + + +def tau_rd( + nu: float, + f_cd: float, + cot_theta: float, + cot_beta_incl: float, + rho_w: float, + f_ywd: float, +) -> float: + """Calculate the enhanced shear stress resistance τRd. + + EN1992-1-1:2023 Eq. (8.55) + + Args: + nu (float): The factor ν (unitless). + f_cd (float): Design value of concrete compressive strength in MPa. + cot_theta (float): Cotangent of the inclination of compression field. + cot_beta_incl (float): Cotangent of the inclination of load (cotβincl). + rho_w (float): Reinforcement ratio ρw. + f_ywd (float): Design yield strength of shear reinforcement in MPa. + + Returns: + float: Enhanced shear stress resistance τRd in MPa. + + Raises: + ValueError: If any input parameter is negative where applicable. + """ + if f_cd < 0: + raise ValueError(f'f_cd must not be negative. Got {f_cd}') + if f_ywd < 0: + raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') + + tau_rd_value = ( + nu * f_cd * (cot_theta - cot_beta_incl) / (1 + cot_theta**2) + + rho_w * f_ywd * cot_beta_incl + ) + tau_rd_max = nu * f_cd * cot_theta / (1 + cot_theta**2) + + return min(tau_rd_value, tau_rd_max) + + +def sigma_swd( + Es: float, eps_x: float, f_ywd: float, cot_theta: float +) -> float: + """Calculate the stress σswd in the shear reinforcement. + + EN1992-1-1:2023 Eq. (8.56) + + Args: + Es (float): Modulus of elasticity of steel Es in MPa. + eps_x (float): Longitudinal strain εx. + f_ywd (float): Design yield strength of shear reinforcement in MPa. + cot_theta (float): Cotangent of the inclination of compression field. + + Returns: + float: Stress σswd in the shear reinforcement in MPa. + + Raises: + ValueError: If any input parameter is negative where applicable. + """ + if Es < 0: + raise ValueError(f'e_s must not be negative. Got {Es}') + if f_ywd < 0: + raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') + + sigma_swd_value = Es * (cot_theta**2 * (eps_x + 0.001) - 0.001) + + return min(sigma_swd_value, f_ywd) + + +def delta_MEd( + tau_ed: float, + rho_w: float, + f_ywd: float, + cot_theta: float, + z: float, + b_w: float, + a: float, + x: float, +) -> float: + """Calculate the additional moment ΔMEd. + + EN1992-1-1:2023 Eq. (8.57) + + Args: + tau_ed (float): Shear stress τEd in MPa (should be positive). + rho_w (float): Reinforcement ratio ρw. + f_ywd (float): Design yield strength of shear reinforcement in MPa. + cot_theta (float): Cotangent of the inclination of compression field. + z (float): Lever arm z in mm (should be positive). + b_w (float): Width of the web bw in mm (should be positive). + a (float): Distance between the axis of the support and + the concentrated force in mm. + x (float): Distance between the support and the investigated + cross-section in mm. + + Returns: + float: Additional moment ΔMEd in kNm. + + Raises: + ValueError: If any input parameter is negative where applicable. + """ + if tau_ed < 0: + raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') + if f_ywd < 0: + raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') + if z < 0: + raise ValueError(f'z must not be negative. Got {z}') + if b_w < 0: + raise ValueError(f'b_w must not be negative. Got {b_w}') + if a < 0: + raise ValueError(f'a must not be negative. Got {a}') + if x < 0: + raise ValueError(f'x must not be negative. Got {x}') + + return ( + (tau_ed - rho_w * f_ywd * cot_theta) * z * b_w * (a / 2 - x) + ) / 1e6 # Convert to kNm + + +def tau_rd_sy( + rho_w: float, + f_ywd: float, + cot_theta: float, + alpha_w: float, + cot_theta_min: float, +) -> float: + """Calculate the shear stress resistance τRd,sy for inclined + shear reinforcement. + + Args: + rho_w (float): Reinforcement ratio ρw. + f_ywd (float): Design yield strength of shear reinforcement in MPa. + cot_theta (float): Cotangent of the inclination of compression + field in radians (cotθ). + alpha_w (float): Angle of inclined shear reinforcement αw + in degrees (45° ≤ αw < 90°). + cot_theta_min (float): max value for cot_theta. + + Returns: + float: Shear stress resistance τRd,sy in MPa. + + Raises: + ValueError: If any input parameter is negative where applicable. + + Reference: + EN1992-1-1:2023 Eq. (8.58), (8.59) + """ + if f_ywd < 0: + raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') + if alpha_w < 45 or alpha_w >= 90: + raise ValueError(f'alpha_w must be between 45º and 90º. Got {alpha_w}') + + alpha_w_rad = math.radians(alpha_w) + cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) + cot_alpha_w = 1 / math.tan(alpha_w_rad) + + return rho_w * f_ywd * (cot_theta + cot_alpha_w) * math.sin(alpha_w_rad) + + +def sigma_cd_s( + tau_ed: float, + cot_theta: float, + alpha_w: float, + nu: float, + f_cd: float, + cot_theta_min: float, +) -> float: + """Calculate the compression stress σcd. + + EN1992-1-1:2023 Eq. (8.58), (8.60) + + Args: + tau_ed (float): Shear stress τEd in MPa. + cot_theta (float): Cotangent of the inclination of + compression field in radians (cotθ). + alpha_w (float): Angle of inclined shear reinforcement + αw in degrees (45° ≤ αw < 90°). + nu (float): The factor ν. + f_cd (float): Design value of concrete compressive strength in MPa. + cot_theta_min (float): max value for cot_theta. + + Returns: + float: Compression stress σcd in MPa. + + Raises: + ValueError: If any input parameter is negative where applicable. + """ + if tau_ed < 0: + raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') + if f_cd < 0: + raise ValueError(f'f_cd must not be negative. Got {f_cd}') + if alpha_w < 45 or alpha_w >= 90: + raise ValueError(f'alpha_w must be between 45º and 90º. Got {alpha_w}') + + alpha_w_rad = math.radians(alpha_w) + cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) + cot_alpha_w = 1 / math.tan(alpha_w_rad) + + sigma_cd_value = tau_ed * (1 + cot_theta**2) / (cot_theta + cot_alpha_w) + + return min(sigma_cd_value, nu * f_cd) + + +def NVds( + VEd: float, cot_theta: float, alpha_w: float, cot_theta_min: float +) -> float: + """Calculate the axial tensile force NVd. + + EN1992-1-1:2023 Eq. (8.58), (8.61) + + Args: + VEd (float): Design shear force VEd in kN (should be positive). + cot_theta (float): Cotangent of the nclination of + compression field in radians (cotθ). + alpha_w (float): Angle of inclined shear reinforcement + αw in degrees (45° ≤ αw < 90°). + cot_theta_min (float): max value for cot_theta. + + Returns: + float: Axial tensile force NVd in kN. + + Raises: + ValueError: If v_ed is negative. + """ + if VEd < 0: + raise ValueError(f'VEd must not be negative. Got {VEd}') + if alpha_w < 45 or alpha_w >= 90: + raise ValueError(f'alpha_w must be between 45º and 90º. Got {alpha_w}') + + alpha_w_rad = math.radians(alpha_w) + cot_alpha_w = 1 / math.tan(alpha_w_rad) + cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) + + return abs(VEd) * (cot_theta - cot_alpha_w) + + +def tau_rd_incl( + nu: float, + f_cd: float, + cot_theta: float, + cot_beta_incl: float, + rho_w: float, + f_ywd: float, + alpha_w: float, + cot_theta_min: float, +) -> float: + """Calculate the shear stress resistance τRd for members + with inclined shear reinforcement. + + EN1992-1-1:2023 Eq. (8.62) + + Args: + nu (float): The factor ν (unitless). + f_cd (float): Design value of concrete compressive strength in MPa. + cot_theta (float): Cotangent of the inclination of + compression field in radians. + cot_beta_incl (float): Cotangent of the inclination of + load in radians (cotβincl). + rho_w (float): Reinforcement ratio ρw (unitless). + f_ywd (float): Design yield strength of shear reinforcement in MPa. + alpha_w (float): Angle of inclined shear reinforcement + αw in degrees (45° ≤ αw < 90°). + cot_theta_min (float): max value for cot_theta. + + Returns: + float: Shear stress resistance τRd in MPa. + + Raises: + ValueError: If any input parameter is negative where applicable. + + Reference: + + """ + if f_cd < 0: + raise ValueError(f'f_cd must not be negative. Got {f_cd}') + if f_ywd < 0: + raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') + if alpha_w < 45 or alpha_w >= 90: + raise ValueError(f'alpha_w must be between 45º and 90º. Got {alpha_w}') + + alpha_w_rad = math.radians(alpha_w) + cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) + cot_alpha_w = 1 / math.tan(alpha_w_rad) + + tau_rd_value = nu * f_cd * (cot_theta - cot_beta_incl) / ( + 1 + cot_theta**2 + ) + rho_w * f_ywd * (cot_beta_incl + cot_alpha_w) * math.sin(alpha_w_rad) + + tau_rd_max = nu * f_cd * (cot_theta + cot_alpha_w) / (1 + cot_theta**2) + + return min(tau_rd_value, tau_rd_max) + + +def sigma_swd_v2( + Es: float, eps_x: float, cot_theta: float, alpha_w: float, f_ywd: float +) -> float: + """Calculate the stress σswd in the shear + reinforcement for compression field inclinations. + + EN1992-1-1:2023 Eq. (8.63) + + Args: + Es (float): Modulus of elasticity of steel Es in MPa. + eps_x (float): Longitudinal strain εx (unitless). + cot_theta (float): Cotangent inclination of compression + field in radians. + alpha_w (float): Angle of inclined shear + reinforcement αw in degrees (45° ≤ αw < 90°). + f_ywd (float): Design yield strength of shear + reinforcement in MPa. + + Returns: + float: Stress σswd in the shear reinforcement in MPa. + + Raises: + ValueError: If any input parameter is negative where applicable. + """ + if Es < 0: + raise ValueError(f'Es must not be negative. Got {Es}') + if f_ywd < 0: + raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') + + alpha_w_rad = math.radians(alpha_w) + cot_alpha_w = 1 / math.tan(alpha_w_rad) + + sigma_swd_value = Es * ( + (eps_x + 0.001) + * ((cot_theta + cot_alpha_w) ** 2 / (1 + cot_alpha_w**2)) + - 0.001 + ) + + return min(sigma_swd_value, f_ywd) + + +def tao_Rd_m(tau_rd: float, m_ed: float, m_rd: float) -> float: + """Calculate the shear stress resistance reduced + by the influence of transverse bending. + + EN1992-1-1:2023 Eq. (8.64) + + Args: + tau_rd (float): Shear resistance τRd in MPa. + m_ed (float): Applied transverse bending moment mEd in kNm. + m_rd (float): Bending resistance + without interaction with shear mRd in kNm. + + Returns: + float: Reduced shear stress resistance τRdm in MPa. + + Raises: + ValueError: If any of the input parameters are negative. + """ + if tau_rd < 0: + raise ValueError(f'tau_rd must not be negative. Got {tau_rd}') + if m_ed < 0: + raise ValueError(f'm_ed must not be negative. Got {m_ed}') + if m_rd < 0: + raise ValueError(f'm_rd must not be negative. Got {m_rd}') + + return tau_rd * (1 - (m_ed / m_rd)) + + +def tao_Ed_flang( + delta_Fd: float, + hf: float, + delta_x: float, +) -> float: + """Calculate the longitudinal shear stress at + the junction between a flange and web. + + EN1992-1-1:2023 Eq. (8.65) + + Args: + delta_Fd (float): Change of axial + force in the flange over length Δx in kN. + hf (float): Thickness of the flange at the junction in mm. + delta_x (float): Length under consideration Δx in mm. + + Returns: + float: Longitudinal shear stress τEd in MPa. + + Raises: + ValueError: If any of the input parameters are negative. + """ + if delta_Fd < 0: + raise ValueError(f'delta_fd must not be negative. Got {delta_Fd}') + if hf <= 0: + raise ValueError(f'hf must be positive. Got {hf}') + if delta_x <= 0: + raise ValueError(f'delta_x must be positive. Got {delta_x}') + + return delta_Fd * 1000 / (hf * delta_x) + + +def Ast_min_flang(tau_ed: float, sf: float, hf: float, fyd: float) -> float: + """Calculate the minimum transverse reinforcement in web/flanges. + + EN1992-1-1:2023 Eq. (8.66) + + Args: + tau_ed (float): Longitudinal shear stress τEd MPa. + sf (float): Spacing of reinforcement sf mm. + hf (float): Thickness of the flange at the junction mm. + fyd (float): Design yield strength of reinforcement fyd MPa. + + Returns: + float: Minimum required transverse reinforcement area Asf in mm2. + + Raises: + ValueError: If any of the input parameters are negative or + if cot_theta_f is out of the specified range. + """ + if tau_ed < 0: + raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') + if sf <= 0: + raise ValueError(f'sf must be positive. Got {sf}') + if hf <= 0: + raise ValueError(f'hf must be positive. Got {hf}') + if fyd < 0: + raise ValueError(f'fyd must not be negative. Got {fyd}') + + return (tau_ed * sf * hf) / (fyd) + + +def Asf_flang( + tau_ed: float, sf: float, hf: float, fyd: float, cot_theta_f: float +) -> float: + """Calculate the transverse reinforcement. + + EN1992-1-1:2023 Eq. (8.69) + + Args: + tau_ed (float): Longitudinal shear stress τEd MPa. + sf (float): Spacing of reinforcement sf mm. + hf (float): Thickness of the flange at the junction mm. + fyd (float): Design yield strength of reinforcement fyd MPa. + cot_theta_f (float): Cotangent of the inclination angle of the + compression field in the flange θf. + + Returns: + float: Required transverse reinforcement area Asf in mm2. + + Raises: + ValueError: If any of the input parameters are negative or + if cot_theta_f is out of the specified range. + """ + if tau_ed < 0: + raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') + if sf <= 0: + raise ValueError(f'sf must be positive. Got {sf}') + if hf <= 0: + raise ValueError(f'hf must be positive. Got {hf}') + if fyd < 0: + raise ValueError(f'fyd must not be negative. Got {fyd}') + + return (tau_ed * sf * hf) / (fyd * cot_theta_f) + + +def sigma_cd_flang( + tau_ed: float, + theta_f: float, + fcd: float, + nu: float = 0.5, +) -> float: + """Computes the compression field stress in + the flange. + + EN1992-1-1:2023 Eq. (8.70), (8.71) + + Args: + tau_ed (float or int): Longitudinal shear stress τEd in MPa. + theta_f (float or int): Inclination angle of the + compression field in the flange θf (degrees). + fcd (float or int): Design compressive + strength of concrete fcd in MPa. + nu (float, optional): Strength reduction factor, default is 0.5. + + Returns: + bool: The compression field stress in MPa + + Raises: + ValueError: If any of the input parameters are + negative or if theta_f is out of the specified range. + """ + if tau_ed < 0: + raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') + if theta_f < 0: + raise ValueError(f'theta_f must not be negative. Got {theta_f}') + if fcd < 0: + raise ValueError(f'fcd must not be negative. Got {fcd}') + if nu <= 0: + raise ValueError(f'nu must be positive. Got {nu}') + + theta_rad = math.radians(theta_f) + sigma_cd = tau_ed * (1 / math.tan(theta_rad) + math.tan(theta_rad)) + + return min(sigma_cd, nu * fcd) + + +def eps_x_flang(Ftd: float, Ast: float, Es: float) -> float: + """Calculate the longitudinal strain in the tensile flange based + on the thickness of the tensile flange and the + distance from the neutral axis. + + EN1992-1-1:2023 Eq. (8.72) + + Args: + Ftd (float): force in the tension chord in kN. + As (float): Longitudinal reinforcement area in mm2. + Es (float): Steel modulus of elasticity in MPa. + + Returns: + float: The longitudinal strain in the tensile flange (unitless). + + Raises: + ValueError: If td is non-positive or if sst is non-positive. + """ + if Ftd <= 0: + raise ValueError(f'Ftd must be positive. Got {Ftd}') + if Ast <= 0: + raise ValueError(f'Ast must be positive. Got {Ast}') + if Es <= 0: + raise ValueError(f'Ast must be positive. Got {Es}') + + return Ftd * 1000 / (Ast * Es) diff --git a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py index 2aa6f5f9..ae8dac2e 100644 --- a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py +++ b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py @@ -292,3 +292,382 @@ def test_rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y, expected): expected, rel_tol=1e-2, ) + + +# Tests using pytest +def test_cot_theta_min(): + """Tests the function cot_theta_min with various scenarios.""" + assert _section_8_2_shear.cot_theta_min(0, 100, 10, 400) == 2.5 + assert _section_8_2_shear.cot_theta_min(0, 100, 10, 400) == 2.5 + assert _section_8_2_shear.cot_theta_min(10, 100, 10, 400) == 2.49 + assert _section_8_2_shear.cot_theta_min(100, 100, 50, 400) == 2.4 + + +def test_tau_Rd_sy(): + """Tests the function tau_Rd_sy.""" + assert _section_8_2_shear.tau_Rd_sy(0.01, 500, 2) == 10 + assert _section_8_2_shear.tau_Rd_sy(0.02, 400, 2.5) == 20 + + +def test_rho_w(): + """Tests the function rho_w.""" + assert _section_8_2_shear.rho_w(100, 200, 50) == 0.01 + assert _section_8_2_shear.rho_w(200, 200, 100) == 0.01 + + +def test_sigma_cd(): + """Tests the function sigma_cd.""" + assert _section_8_2_shear.sigma_cd(1, 2, 0.5, 0.5, 20) == 2.5 + + +def test_tau_Rd(): + """Tests the function tau_Rd.""" + assert _section_8_2_shear.tau_Rd(0.01, 500, 2, 0.5, 50) == 10 + assert ( + _section_8_2_shear.tau_Rd(0.01, 500, 3, 0.5, 20) == 5 + ) # Limited by the compression field + + +def test_cot_theta(): + """Tests the function cot_theta.""" + assert _section_8_2_shear.cot_theta(0.5, 20, 0.01, 500, 2) == 1 + assert _section_8_2_shear.cot_theta(0.5, 40, 0.01, 500, 2) == 2 + + +@pytest.mark.parametrize( + 'Ftd, Est, Ast, expected', [(500, 210000, 1000, 0.002380952380952381)] +) +def test_epsilon_xt(Ftd, Est, Ast, expected): + """Test εxt calculation.""" + assert _section_8_2_shear.epsilon_xt(Ftd, Est, Ast) == pytest.approx( + expected + ) + + +@pytest.mark.parametrize( + 'Fcd, Ecc, Acc, expected', [(500, 30000, 1000, 0.016666666666666666)] +) +def test_epsilon_xc_compression(Fcd, Ecc, Acc, expected): + """Test εxc calculation for compression.""" + assert _section_8_2_shear.epsilon_xc_comp(Fcd, Ecc, Acc) == pytest.approx( + expected, rel=10e-3 + ) + + +@pytest.mark.parametrize( + 'Fcd, Esc, Asc, expected', [(500, 210000, 1000, 0.002380952380952381)] +) +def test_epsilon_xc_tension(Fcd, Esc, Asc, expected): + """Test εxc calculation for tension.""" + assert _section_8_2_shear.epsilon_xc_tens(Fcd, Esc, Asc) == pytest.approx( + expected, rel=10e-3 + ) + + +@pytest.mark.parametrize( + 'epsilon_xt, epsilon_xc, expected', [(0.002, 0.001, 0.0015)] +) +def test_epsilon_x(epsilon_xt, epsilon_xc, expected): + """Test εx calculation.""" + assert _section_8_2_shear.epsilon_x( + epsilon_xt, epsilon_xc + ) == pytest.approx(expected) + + +@pytest.mark.parametrize('epsilon_x, theta, expected', [(0.001, 2, 0.5025)]) +def test_nu(epsilon_x, theta, expected): + """Test ν calculation.""" + assert _section_8_2_shear.nu(epsilon_x, theta) == pytest.approx( + expected, rel=10e-3 + ) + + +@pytest.mark.parametrize( + 'VEd, theta, expected', + [ + (100, 2, 200), + (150, 1.5, 225), + ], +) +def test_calculate_nv(VEd, theta, expected): + """Test calculate_nv function with various inputs.""" + assert _section_8_2_shear.Nvd(VEd, theta) == pytest.approx( + expected, rel=1e-6 + ) + + +@pytest.mark.parametrize( + 'MEd, z, NVd, NE, expected', + [ + (200, 400, 100, 50, 575.0), + (300, 600, 150, 100, 625.0), + ], +) +def test_calculate_ftd(MEd, z, NVd, NE, expected): + """Test calculate_ftd function with various inputs.""" + assert _section_8_2_shear.Ftd(MEd, z, NVd, NE) == pytest.approx( + expected, rel=1e-6 + ) + + +@pytest.mark.parametrize( + 'MEd, z, NVd, NE, expected', + [ + (200, 400, 100, 50, 425.0), + (300, 600, 150, 100, 375.0), + ], +) +def test_calculate_fcd(MEd, z, NVd, NE, expected): + """Test calculate_fcd function with various inputs.""" + assert _section_8_2_shear.Fcd(MEd, z, NVd, NE) == pytest.approx( + expected, rel=1e-6 + ) + + +@pytest.mark.parametrize( + 'duct_material, is_grouted, wall_thickness, duct_diameter, expected', + [ + ('steel', True, 3.0, 50.0, 0.5), # Grouted steel ducts + ('plastic', True, 1.0, 50.0, 0.8), # Grouted plastic thin ducts + ('plastic', True, 5.0, 50.0, 1.2), # Grouted plastic thick ducts + ('plastic', False, 1.0, 50.0, 1.2), # Non-grouted plastic ducts + ], +) +def test_calculate_k_duct( + duct_material, is_grouted, wall_thickness, duct_diameter, expected +): + """Test calculate_k_duct with various inputs.""" + assert ( + _section_8_2_shear.k_duct( + duct_material, is_grouted, wall_thickness, duct_diameter + ) + == expected + ) + + +@pytest.mark.parametrize( + 'bw, duct_diameters, k_duct, expected', + [ + (800, [50, 40], 0.5, 755.0), # Grouted steel ducts + (800, [50, 40], 0.8, 728.0), # Grouted plastic thin ducts + (800, [50, 40], 1.2, 692.0), # Non-grouted or thick plastic ducts + ], +) +def test_calculate_nominal_web_width(bw, duct_diameters, k_duct, expected): + """Test calculate_nominal_web_width with various inputs.""" + assert _section_8_2_shear.bw_nom( + bw, duct_diameters, k_duct + ) == pytest.approx(expected, rel=1e-6) + + +@pytest.mark.parametrize( + 'nu, f_cd, theta, beta_incl, rho_w, f_ywd, expected', + [ + (0.6, 30, 1, 1.5, 0.01, 500, 3), + (0.5, 40, 1.5, 1, 0.02, 400, 9.2307), + ], +) +def test_tau_rd(nu, f_cd, theta, beta_incl, rho_w, f_ywd, expected): + """Test calculation of enhanced shear stress resistance τRd.""" + assert math.isclose( + _section_8_2_shear.tau_rd(nu, f_cd, theta, beta_incl, rho_w, f_ywd), + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'e_s, eps_x, f_ywd, cot_theta, expected', + [ + (200000, 0.001, 500, 2, 500), + (210000, 0.002, 450, 1, 420), + ], +) +def test_sigma_swd(e_s, eps_x, f_ywd, cot_theta, expected): + """Test calculation of stress σswd in shear reinforcement.""" + assert math.isclose( + _section_8_2_shear.sigma_swd(e_s, eps_x, f_ywd, cot_theta), + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'tau_ed, rho_w, f_ywd, theta, z, b_w, a, x, expected', + [ + (0.5, 0.01, 500, 1, 300, 200, 500, 250, 0), + (0.4, 0.02, 400, 0.7, 350, 250, 600, 200, -45.5), + ], +) +def test_delta_m_ed(tau_ed, rho_w, f_ywd, theta, z, b_w, a, x, expected): + """Test calculation of additional moment ΔMEd.""" + assert math.isclose( + _section_8_2_shear.delta_MEd( + tau_ed, rho_w, f_ywd, theta, z, b_w, a, x + ), + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'rho_w, f_ywd, theta, alpha_w, cot_theta_min, expected', + [ + (0.01, 500, 1, 45, 1.5, 7.071), + (0.02, 450, 2, 60, 3, 20.088), + ], +) +def test_tau_rd_sy(rho_w, f_ywd, theta, alpha_w, cot_theta_min, expected): + """Test calculation of shear stress resistance τRd,sy.""" + assert math.isclose( + _section_8_2_shear.tau_rd_sy( + rho_w, f_ywd, theta, alpha_w, cot_theta_min + ), + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'tau_ed, theta, alpha_w, nu, f_cd, cot_theta_min, expected', + [ + (0.5, 1, 45, 0.6, 30, 2, 0.5), + (0.7759, 2, 60, 0.5, 40, 2, 1.50522), + ], +) +def test_sigma_cd_s(tau_ed, theta, alpha_w, nu, f_cd, cot_theta_min, expected): + """Test calculation of compression stress σcd.""" + assert math.isclose( + _section_8_2_shear.sigma_cd_s( + tau_ed, theta, alpha_w, nu, f_cd, cot_theta_min + ), + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'v_ed, theta, alpha_w, cot_theta_min, expected', + [ + (100, 0.3, 45, 1, -58.5786), + (80, 0.1, 50, 3, -29.8233), + ], +) +def test_n_vd(v_ed, theta, alpha_w, cot_theta_min, expected): + """Test calculation of axial tensile force NVd.""" + assert math.isclose( + _section_8_2_shear.NVds(v_ed, theta, alpha_w, cot_theta_min), + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'nu, f_cd, theta, beta_incl, rho_w, f_ywd, ' + + 'alpha_w, cot_theta_min, expected', + [ + (0.6, 30, 1, 3, 0.01, 500, 45, 2, -3.8578), + (0.5, 40, 0.7, 1, 0.02, 400, 60, 5, 6.9013), + ], +) +def test_tau_rd_incl( + nu, f_cd, theta, beta_incl, rho_w, f_ywd, alpha_w, cot_theta_min, expected +): + """Test calculation of shear stress resistance τRd.""" + assert math.isclose( + _section_8_2_shear.tau_rd_incl( + nu, f_cd, theta, beta_incl, rho_w, f_ywd, alpha_w, cot_theta_min + ), + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'e_s, eps_x, theta, alpha_w, f_ywd, expected', + [ + (200000, 0.001, 1, 45, 500, 500), # Example values + (210000, 0.002, 2, 60, 450, 450), # Example values + ], +) +def test_sigma_swd_v2(e_s, eps_x, theta, alpha_w, f_ywd, expected): + """Test calculation of stress σswd.""" + assert math.isclose( + _section_8_2_shear.sigma_swd_v2(e_s, eps_x, theta, alpha_w, f_ywd), + expected, + rel_tol=1e-2, + ) + + +@pytest.mark.parametrize( + 'tau_rd, m_ed, m_rd, expected', + [ + (5.0, 2.0, 10.0, 4.0), + (10.0, 3.0, 15.0, 8.0), + ], +) +def test_shear_stress_resistance_reduced(tau_rd, m_ed, m_rd, expected): + """Test calculation of reduced shear stress resistance.""" + assert _section_8_2_shear.tao_Rd_m(tau_rd, m_ed, m_rd) == pytest.approx( + expected, rel=10e-2 + ) + + +@pytest.mark.parametrize( + 'delta_fd, hf, delta_x, expected', + [ + (100.0, 200.0, 1000.0, 0.5), + (50.0, 150.0, 500.0, 0.666), + ], +) +def test_longitudinal_shear_stress(delta_fd, hf, delta_x, expected): + """Test calculation of longitudinal shear stress.""" + assert _section_8_2_shear.tao_Ed_flang( + delta_fd, hf, delta_x + ) == pytest.approx(expected, rel=10e-2) + + +@pytest.mark.parametrize( + 'tau_ed, sf, hf, fyd, cot_theta_f, expected', + [ + (3, 200.0, 200.0, 500.0, 1.0, 240), + (2, 150.0, 150.0, 400.0, 1.5, 75.0), + ], +) +def test_transverse_reinforcement_flange( + tau_ed, sf, hf, fyd, cot_theta_f, expected +): + """Test calculation of transverse reinforcement in the flange.""" + assert _section_8_2_shear.Asf_flang( + tau_ed, sf, hf, fyd, cot_theta_f + ) == pytest.approx(expected, rel=10e-2) + + +@pytest.mark.parametrize( + 'tau_ed, theta_f, fcd, nu, expected', + [ + (5, 45.0, 30.0, 0.5, 10), + (3, 30.0, 25.0, 0.5, 6.9282), + ], +) +def test_sigma_cd_flang(tau_ed, theta_f, fcd, nu, expected): + """Test check of compression field stress in the flange.""" + assert _section_8_2_shear.sigma_cd_flang( + tau_ed, theta_f, fcd, nu + ) == pytest.approx(expected, rel=10e-2) + + +# Valid inputs +@pytest.mark.parametrize( + 'Ftd, Ast, Es, expected', + [ + (100, 500, 200000, 0.001), + (50, 250, 210000, 0.0009523809523809524), + (200, 800, 205000, 0.0012195121951219512), + ], +) +def test_eps_x_flang(Ftd, Ast, Es, expected): + """Test eps_x_flang.""" + assert _section_8_2_shear.eps_x_flang(Ftd, Ast, Es) == pytest.approx( + expected, rel=10e-4 + ) From 0d3d00d28ecb9876af97aa2092f79943a103c4d1 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 1 Aug 2024 14:03:48 +0200 Subject: [PATCH 24/28] shear EC2-8.2 finished --- structuralcodes/codes/ec2_2023/__init__.py | 18 + .../codes/ec2_2023/_section_8_2_shear.py | 339 ++++++++++++++++++ .../test_ec2_2023_section_8_2_shear.py | 178 +++++++++ 3 files changed, 535 insertions(+) diff --git a/structuralcodes/codes/ec2_2023/__init__.py b/structuralcodes/codes/ec2_2023/__init__.py index 2abd094c..9b3cb138 100644 --- a/structuralcodes/codes/ec2_2023/__init__.py +++ b/structuralcodes/codes/ec2_2023/__init__.py @@ -59,6 +59,8 @@ bw_nom, cot_theta, cot_theta_min, + cv1, + cv2, d_eff, d_eff_p, d_eff_with_angle, @@ -71,6 +73,9 @@ k1, k_duct, k_vp, + kdowel, + kv, + mu_v, nu, rho_l, rho_l_p, @@ -85,6 +90,8 @@ tao_Ed_flang, tao_Ed_planar, tao_Rd_m, + tau_Edi, + tau_Edi_composite, tau_Rd, tau_rd, tau_rd_incl, @@ -95,10 +102,21 @@ tau_Rdc_comp, tau_Rdc_max, tau_rdc_min, + tau_Rdi, + tau_Rdi_ny, v_Ed, ) __all__ = [ + 'cv1', + 'kdowel', + 'tau_Rdi_ny', + 'cv2', + 'kv', + 'mu_v', + 'tau_Edi', + 'tau_Rdi', + 'tau_Edi_composite', 'eps_x_flang', 'tao_Ed_flang', 'Ast_min_flang', diff --git a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py index 1d939259..f880929d 100644 --- a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py +++ b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py @@ -1516,3 +1516,342 @@ def eps_x_flang(Ftd: float, Ast: float, Es: float) -> float: raise ValueError(f'Ast must be positive. Got {Es}') return Ftd * 1000 / (Ast * Es) + + +def tau_Edi(VEdi: float, Ai: float) -> float: + """Calculate the design value of the shear stress at an interface. + + EN1992-1-1:2023 Eq. (8.74) + + Args: + VEdi (float): Shear force acting parallel to the interface in kN. + Ai (float): Area of the interface in mm2. + + Returns: + float: Shear stress at the interface in MPa. + + Raises: + ValueError: If any input value is negative. + """ + if VEdi < 0: + raise ValueError(f'VEdi must not be negative. Got {VEdi}') + if Ai < 0: + raise ValueError(f'Ai must not be negative. Got {Ai}') + + return VEdi * 1000 / Ai + + +def tau_Edi_composite( + beta_new: float, VEd: float, z: float, bi: float +) -> float: + """Calculate the longitudinal shear stress between concrete + interfaces due to composite action. + + EN1992-1-1:2023 Eq. (8.75). + + Args: + beta_new (float): Ratio of the longitudinal force in the new concrete + to the total longitudinal force, dimensionless. + VEd (float): Shear force acting perpendicular to the interface in kN. + z (float): Lever arm of the composite section in mm. + bi (float): Width of the interface in mm. + + Returns: + float: Longitudinal shear stress at the interface in MPa. + + Raises: + ValueError: If any input value is negative + or zero when it should not be. + """ + if beta_new < 0: + raise ValueError(f'beta_new must not be negative. Got {beta_new}') + if VEd < 0: + raise ValueError(f'Ved must not be negative. Got {VEd}') + if z <= 0: + raise ValueError(f'z must be positive. Got {z}') + if bi < 0: + raise ValueError(f'bi must not be negative. Got {bi}') + + return beta_new * VEd * 1000 / (z * bi) + + +def tau_Rdi( + fck: float, + sigma_n: float, + Ai: float, + Asi: float, + fyd: float, + alpha_deg: float, + cv1: float, + mu_v: float, + gamma_c: float, +) -> float: + """Calculate the design shear stress resistance at the + interface for scenarios without reinforcement or where + reinforcement is sufficiently anchored. + + EN1992-1-1:2023 Eq. (8.76) + + Args: + fck (float): Lowest compressive strength of the + concretes at the interface in MPa. + sigma_n (float): Compressive or tensile stress + over the interface area in MPa. + Ai (float): Area of the interface in mm2 + Asi (float): Cross-sectional area of + bonded reinforcement crossing the interface in mm2. + fyd (float): Design yield strength of the reinforcement in MPa. + alpha_deg (float): Angle of reinforcement + crossing the interface in degrees. + cv1 (float): Coefficient depending on the + roughness of the interface, dimensionless. + mu_v (float): Friction coefficient depending + on the roughness of the interface, dimensionless. + gamma_c (float): safety factory for concrete. + + Returns: + float: Shear stress resistance at the interface in MPa. + + Raises: + ValueError: If any input value is negative or outside expected ranges. + """ + if fck < 0 or Ai < 0 or Asi < 0 or fyd < 0 or gamma_c < 0: + raise ValueError('Input values must not be negative.') + if not (35 <= alpha_deg <= 135): + raise ValueError('Alpha must be between 35 and 135 degrees.') + + sigma_n = max(0, min(sigma_n, 0.6 * fck / gamma_c)) + + alpha_rad = math.radians(alpha_deg) + rho_i = Asi / Ai + + tau_rdi = ( + cv1 * math.sqrt(fck) / gamma_c + + mu_v * sigma_n + + rho_i * fyd * (mu_v * math.sin(alpha_rad) + math.cos(alpha_rad)) + ) + + # Limiting tau_rdi according to the specification + return min(tau_rdi, 0.30 * fck + rho_i * fyd * math.cos(alpha_rad)) + + +def cv1( + surface_roughness: Literal[ + 'very smooth', 'smooth', 'rough', 'very rough', 'keyed' + ], + tensile_stress: bool = False, +) -> float: + """Get the cv1 coefficient based on the + surface roughness and tensile stress condition. + + EC1992-1-1:2023 Table (8.2) + + Args: + surface_roughness (str): Description of the surface roughness. + tensile_stress (bool): True if tensile stresses are present. + + Returns: + float: The cv1 coefficient. + + Raises: + ValueError: If an unknown surface roughness is provided. + """ + if tensile_stress: + return 0 + + coefficients = { + 'very smooth': 0.01, + 'smooth': 0.08, + 'rough': 0.15, + 'very rough': 0.19, + 'keyed': 0.37, + } + return coefficients[surface_roughness] + + +def mu_v( + surface_roughness: Literal[ + 'very smooth', 'smooth', 'rough', 'very rough', 'keyed' + ], +) -> float: + """Get the mu_v coefficient based on the surface roughness. + + EC1992-1-1:2023 Table (8.2) + + Args: + surface_roughness (str): Description of the surface roughness. + + Returns: + float: The mu_v coefficient. + + Raises: + ValueError: If an unknown surface roughness is provided. + """ + coefficients = { + 'very smooth': 0.5, + 'smooth': 0.6, + 'rough': 0.7, + 'very rough': 0.9, + 'keyed': 0.9, + } + return coefficients[surface_roughness] + + +def cv2( + surface_roughness: Literal['very smooth', 'smooth', 'rough', 'very rough'], + tensile_stress: bool = False, +) -> float: + """Get the cv2 coefficient based on the + surface roughness and tensile stress condition. + + EC1992-1-1:2023 Table (8.2) + + Args: + surface_roughness (str): Description of the surface roughness. + tensile_stress (bool): True if tensile stresses are present. + + Returns: + float: The cv2 coefficient, or None for keyed surfaces. + + Raises: + ValueError: If an unknown surface roughness is provided. + """ + if tensile_stress: + return 0 + + coefficients = { + 'very smooth': 0, + 'smooth': 0, + 'rough': 0.08, + 'very rough': 0.15, + } + return coefficients[surface_roughness] + + +def kv( + surface_roughness: Literal['very smooth', 'smooth', 'rough', 'very rough'], +) -> float: + """Get the kv coefficient based on the surface roughness. + + EC1992-1-1:2023 Table (8.2) + + Args: + surface_roughness (str): Description of the surface roughness. + + Returns: + float: The kv coefficient, or None for keyed surfaces. + + Raises: + ValueError: If an unknown surface roughness is provided. + """ + coefficients = { + 'very smooth': 0, + 'smooth': 0.5, + 'rough': 0.5, + 'very rough': 0.5, + } + return coefficients[surface_roughness] + + +def kdowel( + surface_roughness: Literal['very smooth', 'smooth', 'rough', 'very rough'], +) -> float: + """Get the kdowel coefficient based on the surface roughness. + + EC1992-1-1:2023 Table (8.2) + + Args: + surface_roughness (str): Description of the surface roughness. + + Returns: + float: The kdowel coefficient, or None for keyed surfaces. + + Raises: + ValueError: If an unknown surface roughness is provided. + """ + coefficients = { + 'very smooth': 1.5, + 'smooth': 1.1, + 'rough': 0.9, + 'very rough': 0.9, + } + return coefficients[surface_roughness] + + +def tau_Rdi_ny( + cv2: float, + fck: float, + gamma_c: float, + mu_v: float, + sigma_n: float, + kv: float, + rho_i: float, + fyd: float, + kdowel: float, +) -> float: + """Calculate the shear stress resistance at the interface when yielding + is not ensured at the interface. + + EN 1992-1-1:2022 Eq. (8.77) + + Args: + cv2 (float): Coefficient depending on the roughness + of the interface (unitless). + fck (float): Concrete compressive resistance in MPa. + gamma_c (float): Partial safety factor for concrete (unitless). + mu_v (float): Coefficient mu_v from the Eurocode (unitless). + sigma_n (float): Normal stress in the interface MPa. + kv (float): Coefficient kv from the Eurocode (unitless). + rho_i (float): Reinforcement ratio at the interface (unitless). + fyd (float): Design yield strength of reinforcement MPa. + kdowel (float): Coefficient for dowel action of + reinforcement (unitless). + + Returns: + float: Shear stress resistance τRdi MPa. + + Raises: + ValueError: If any of the dimensions or resistances are negative. + """ + if any(x < 0 for x in [fck, gamma_c, kv, rho_i, fyd, kdowel]): + raise ValueError('Dimensions and resistances must not be negative.') + + sigma_n = max(0, sigma_n) + tau_rdi = ( + cv2 * math.sqrt(fck) / gamma_c + + mu_v * sigma_n + + kv * rho_i * fyd * mu_v + + kdowel * rho_i * math.sqrt(fyd * fck / gamma_c) + ) + return min( + tau_rdi, 0.25 * fck / gamma_c + ) # Cap τRdi according to the formula + + +def as_min(tmin: float, fctm: float, fyk: float) -> float: + """Calculate the minimum interface reinforcement per unit + length along the edge of composite slabs. + + EN 1992-1-1:2022 Eq. (8.78). + + Args: + tmin (float): Smaller value of the thickness of new + and old concrete layers in mm. + fctm (float): Mean tensile strength of the respective + concrete layer in MPa. + fyk (float): Characteristic yield strength of the reinforcement in MPa. + + Returns: + float: Minimum interface reinforcement per unit length in mm/mm. + + Raises: + ValueError: If any input is negative or zero where it shouldn't be. + """ + if tmin <= 0: + raise ValueError(f'tmin must be positive. Got {tmin}') + if fctm <= 0: + raise ValueError(f'fctm must be positive. Got {fctm}') + if fyk <= 0: + raise ValueError(f'fyk must be positive. Got {fyk}') + + return tmin * fctm / fyk diff --git a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py index ae8dac2e..a015a5ef 100644 --- a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py +++ b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py @@ -671,3 +671,181 @@ def test_eps_x_flang(Ftd, Ast, Es, expected): assert _section_8_2_shear.eps_x_flang(Ftd, Ast, Es) == pytest.approx( expected, rel=10e-4 ) + + +@pytest.mark.parametrize( + 'VEdi, Ai, expected', + [(1000, 200000, 5), (0, 100000, 0.0), (500, 250000, 2)], +) +def test_calculate_tau_edi(VEdi, Ai, expected): + """Test the basic functionality of calculate_tau_edi.""" + assert _section_8_2_shear.tau_Edi(VEdi, Ai) == pytest.approx( + expected, rel=10e-2 + ) + + +@pytest.mark.parametrize('VEdi, Ai', [(-1, 200000), (1000, -1)]) +def test_calculate_tau_edi_errors(VEdi, Ai): + """Test that errors are raised for invalid inputs in calculate_tau_edi.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Edi(VEdi, Ai) + + +# Tests for calculate_tau_edi_composite +@pytest.mark.parametrize( + 'beta_new, VEd, z, bi, expected', + [ + (0.5, 1000, 200, 1000, 2.5), + (1.0, 1000, 200, 1000, 5.0), + ], +) +def test_calculate_tau_edi_composite(beta_new, VEd, z, bi, expected): + """Test the basic functionality of calculate_tau_edi_composite.""" + assert _section_8_2_shear.tau_Edi_composite( + beta_new, VEd, z, bi + ) == pytest.approx(expected, rel=1e-2) + + +@pytest.mark.parametrize( + 'beta_new, VEd, z, bi', + [ + (-0.1, 1000, 200, 1000), + (0.5, -1, 200, 1000), + (0.5, 1000, 0, 1000), + (0.5, 1000, 200, -1), + ], +) +def test_calculate_tau_edi_composite_errors(beta_new, VEd, z, bi): + """Test that errors are raised for invalid inputs.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Edi_composite(beta_new, VEd, z, bi) + + +@pytest.mark.parametrize( + 'fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c, expected', + [ + (30, 5, 10000, 100, 500, 45, 0.1, 0.2, 1.5, 5.61), + (35, 0, 20000, 200, 550, 90, 0.2, 0.25, 1.5, 2.1638), + (40, 12, 15000, 150, 600, 135, 0.15, 0.3, 1.5, 1.2626), + ], +) +def test_tau_rdi( + fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c, expected +): + """Test the basic functionality and expected results of tau_Rdi.""" + assert math.isclose( + _section_8_2_shear.tau_Rdi( + fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c + ), + expected, + rel_tol=1e-3, + ) + + +# Test cv1 function +@pytest.mark.parametrize( + 'surface_roughness, tensile_stress, expected', + [ + ('very smooth', False, 0.01), + ('smooth', True, 0), # Test with tensile stress + ('rough', False, 0.15), + ('very rough', False, 0.19), + ('keyed', False, 0.37), + ], +) +def test_cv1(surface_roughness, tensile_stress, expected): + """Test cv1 coefficient.""" + assert ( + _section_8_2_shear.cv1(surface_roughness, tensile_stress) == expected + ) + + +# Test mu_v function +@pytest.mark.parametrize( + 'surface_roughness, expected', + [ + ('very smooth', 0.5), + ('smooth', 0.6), + ('rough', 0.7), + ('very rough', 0.9), + ('keyed', 0.9), + ], +) +def test_mu_v(surface_roughness, expected): + """Test mu_v.""" + assert _section_8_2_shear.mu_v(surface_roughness) == expected + + +# Test cv2 function +@pytest.mark.parametrize( + 'surface_roughness, tensile_stress, expected', + [ + ('very smooth', False, 0), + ('smooth', True, 0), # Test with tensile stress + ('rough', False, 0.08), + ('very rough', False, 0.15), + ], +) +def test_cv2(surface_roughness, tensile_stress, expected): + """Test cv2.""" + assert ( + _section_8_2_shear.cv2(surface_roughness, tensile_stress) == expected + ) + + +# Test kv function +@pytest.mark.parametrize( + 'surface_roughness, expected', + [ + ('very smooth', 0), + ('smooth', 0.5), + ('rough', 0.5), + ('very rough', 0.5), + ], +) +def test_kv(surface_roughness, expected): + """Test kv.""" + assert _section_8_2_shear.kv(surface_roughness) == expected + + +# Test kdowel function +@pytest.mark.parametrize( + 'surface_roughness, expected', + [ + ('very smooth', 1.5), + ('smooth', 1.1), + ('rough', 0.9), + ('very rough', 0.9), + ], +) +def test_kdowel(surface_roughness, expected): + """Test kdowel.""" + assert _section_8_2_shear.kdowel(surface_roughness) == expected + + +@pytest.mark.parametrize( + 'cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel, expected', + [ + (0.1, 30, 1.5, 0.2, 0.3, 0.1, 0.02, 500, 0.0, 0.6251), + ], +) +def test_shear_stress_resistance( + cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel, expected +): + """Test the shear stress resistance calculation.""" + result = _section_8_2_shear.tau_Rdi_ny( + cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel + ) + assert result == pytest.approx(expected, rel=10e-3) + + +@pytest.mark.parametrize( + 'tmin, fctm, fyk, expected', + [ + (200, 2.9, 500, 1.16), + ], +) +def test_min_interface_reinforcement(tmin, fctm, fyk, expected): + """Test the minimum interface reinforcement calculation.""" + result = _section_8_2_shear.as_min(tmin, fctm, fyk) + assert result == pytest.approx(expected, rel=10e-3) From 5260d21b56ea5edec3858a940459e4cffe428087 Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Fri, 11 Oct 2024 14:27:16 +0200 Subject: [PATCH 25/28] Fix docstrings --- .../codes/ec2_2023/_section_8_2_shear.py | 616 +++++++++--------- 1 file changed, 310 insertions(+), 306 deletions(-) diff --git a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py index f880929d..7bad7cc4 100644 --- a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py +++ b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py @@ -1,22 +1,24 @@ +"""Functions from Section 8.2 of EN 1992-1-1:2023.""" + import math from typing import List, Literal def tao_Ed(VEd: float, bw: float, d: float) -> float: - """Calculate the average shear stress over the cross-section - for linear members. + """Calculate the average shear stress over the cross-section for linear + members. - EN1992-1-1:2923 Eq. (8.18) + EN1992-1-1:2923 Eq. (8.18). - Parameters: - VEd (float): Design shear force at the control section in - linear members in kN. + Args: + VEd (float): Design shear force at the control section in linear + members in kN. bw (float): Width of the cross-section of linear members in mm. d (float): Effective depth of the cross-section in mm. Returns: - float: Average shear stress over the cross-section - for linear members in Mpa. + float: Average shear stress over the cross-section for linear members + in Mpa. """ if bw < 0: raise ValueError(f'bw must not be negative. Got {bw}') @@ -28,19 +30,19 @@ def tao_Ed(VEd: float, bw: float, d: float) -> float: def tao_Ed_planar(vEd: float, d: float) -> float: - """Calculate the average shear stress over the cross-section - for planar members. + """Calculate the average shear stress over the cross-section for planar + members. - EN1992-1-1:2923 Eq. (8.19) + EN1992-1-1:2923 Eq. (8.19). - Parameters: - vEd (float): Design shear force per unit width in planar members - in kN/m. + Args: + vEd (float): Design shear force per unit width in planar members in + kN/m. d (float): Effective depth of the cross-section in mm. Returns: - float: Average shear stress over the cross-section for - planar members in MPa. + float: Average shear stress over the cross-section for planar members + in MPa. """ if d < 0: raise ValueError(f'd must not be negative. Got {d}') @@ -58,19 +60,19 @@ def tau_rdc_min( ) -> float: """Calculate the minimum shear stress resistance. - EN1992-1-1:2023 Eq. (8.20) + EN1992-1-1:2023 Eq. (8.20). Args: gamma_v (float): Partial factor for shear design. - f_ck (float): Characteristic compressive strength of - concrete in MPa (must be positive). - f_yd (float): Design value of the yield strength - in MPa (must be positive). - d (float): Effective depth of the flexural - reinforcement in mm (must be positive). + f_ck (float): Characteristic compressive strength of concrete in MPa + (must be positive). + f_yd (float): Design value of the yield strength in MPa (must be + positive). + d (float): Effective depth of the flexural reinforcement in mm (must be + positive). d_lower (float): Smallest value of the upper sieve size D in an - aggregate for the coarsest fraction of aggregates - in mm (must be positive). + aggregate for the coarsest fraction of aggregates in mm (must be + positive). Returns: float: Minimum shear stress resistance in MPa. @@ -100,7 +102,7 @@ def tau_rdc_min( def v_Ed(vEd_x: float, vEd_y: float) -> float: """Calculate the design shear force per unit width (vEd). - EN1992-1-1:2023 Eq. (8.21) + EN1992-1-1:2023 Eq. (8.21). Args: vEd_x (float): Shear force in x-direction in kN/m. @@ -123,7 +125,7 @@ def v_Ed(vEd_x: float, vEd_y: float) -> float: def d_eff(dx: float, dy: float, vEd_x: float, vEd_y: float) -> float: """Calculate the effective depth (d) based on the ratio of shear forces. - EN1992-1-1:2023 Eq. (8.22), (8.23), (8.24) + EN1992-1-1:2023 Eq. (8.22), (8.23), (8.24). Args: dx (float): Effective depth in x-direction in mm. @@ -157,9 +159,9 @@ def d_eff(dx: float, dy: float, vEd_x: float, vEd_y: float) -> float: def d_eff_with_angle( dx: float, dy: float, vEd_x: float, vEd_y: float ) -> float: - """Calculate the effective depth (d) based on the angle αv. + """Calculate the effective depth (d) based on the angle alpha_v. - EN1992-1-1:2023 Eq. (8.25), (8.26) + EN1992-1-1:2023 Eq. (8.25), (8.26). Args: dx (float): Effective depth in x-direction in mm. @@ -196,7 +198,7 @@ def tau_Rdc( ) -> float: """Calculate the design value of the shear stress resistance. - EN1992-1-1:2023 Eq. (8.27) + EN1992-1-1:2023 Eq. (8.27). Args: gamma_v (float): Partial factor for shear (unitless). @@ -227,7 +229,7 @@ def tau_Rdc( def rho_l(A_sl: float, b_w: float, d: float) -> float: """Calculate the reinforcement ratio. - EN1992-1-1:2023 Eq. (8.28) + EN1992-1-1:2023 Eq. (8.28). Args: A_sl (float): Effective area of tensile reinforcement in mm2. @@ -252,7 +254,7 @@ def rho_l(A_sl: float, b_w: float, d: float) -> float: def a_v(a_cs: float, d: float) -> float: """Calculate the effective shear span. - EN1992-1-1:2023 Eq. (8.30) + EN1992-1-1:2023 Eq. (8.30). Args: M_Ed (float): Bending moment kN·m. @@ -275,7 +277,7 @@ def a_v(a_cs: float, d: float) -> float: def a_cs(M_Ed: float, V_Ed: float, d: float) -> float: """Calculate the effective shear span. - EN1992-1-1:2023 Eq. (8.30) + EN1992-1-1:2023 Eq. (8.30). Args: M_Ed (float): Bending moment kN·m. @@ -297,7 +299,7 @@ def a_cs(M_Ed: float, V_Ed: float, d: float) -> float: def k_vp(N_Ed: float, V_Ed: float, d: float, a_cs: float) -> float: """Calculate the coefficient k_vp. - EN1992-1-1:2023 Eq. (8.31) + EN1992-1-1:2023 Eq. (8.31). Args: N_Ed (float): Axial force in kN. @@ -325,10 +327,10 @@ def k_vp(N_Ed: float, V_Ed: float, d: float, a_cs: float) -> float: def tau_Rdc_0( gamma_v: float, rho_l: float, f_ck: float, d: float, d_g: float ) -> float: - """Calculate the design value of the shear stress - resistance without axial force effects. + """Calculate the design value of the shear stress resistance without axial + force effects. - EN1992-1-1:2023 Eq. (8.33) + EN1992-1-1:2023 Eq. (8.33). Args: gamma_v (float): Partial factor for shear (unitless). @@ -356,19 +358,19 @@ def tau_Rdc_0( def tau_Rdc_comp( tau_Rdc_0: float, k1: float, sigma_cp: float, tau_Rdc_max: float ) -> float: - """Calculate the design value of the shear stress - resistance considering compressive normal forces. + """Calculate the design value of the shear stress resistance considering + compressive normal forces. - EN1992-1-1:2023 Eq. (8.32) + EN1992-1-1:2023 Eq. (8.32). Args: - tau_Rdc_0 (float): Design value of the shear stress resistance - without axial force effects in MPa. - k1 (float): Factor considering the effect of compressive - normal forces (unitless). + tau_Rdc_0 (float): Design value of the shear stress resistance without + axial force effects in MPa. + k1 (float): Factor considering the effect of compressive normal forces + (unitless). sigma_cp (float): Compressive stress due to axial force in MPa. - tau_Rdc_max (float): Maximum design value of the - shear stress resistance in MPa. + tau_Rdc_max (float): Maximum design value of the shear stress + resistance in MPa. Returns: float: The design value of the shear stress resistance in MPa. @@ -395,16 +397,16 @@ def k1( z: float, d: float, ) -> float: - """Calculate the factor k1 considering the effect of - compressive normal forces. + """Calculate the factor k1 considering the effect of compressive normal + forces. - EN1992-1-1:2023 Eq. (8.34) + EN1992-1-1:2023 Eq. (8.34). Args: - a_cs_0 (float): Effective shear span without - considering prestressing effects in mm. - e_p (float): Eccentricity of the prestressing - force or external load in mm. + a_cs_0 (float): Effective shear span without considering prestressing + effects in mm. + e_p (float): Eccentricity of the prestressing force or external load in + mm. A_c (float): Area of concrete cross-section in mm2. b_w (float): Width of the cross-section in mm. z (float): Lever arm in mm. @@ -429,13 +431,13 @@ def k1( def tau_Rdc_max(tau_Rdc_0: float, a_cs_0: float, d: float) -> float: """Calculate the maximum design value of the shear stress resistance. - EN1992-1-1:2023 Eq. (8.35) + EN1992-1-1:2023 Eq. (8.35). Args: - tau_Rdc_0 (float): Design value of the shear stress - resistance without axial force effects in MPa. - a_cs_0 (float): Effective shear span without - considering prestressing effects in mm. + tau_Rdc_0 (float): Design value of the shear stress resistance without + axial force effects in MPa. + a_cs_0 (float): Effective shear span without considering prestressing + effects in mm. d (float): Effective depth in mm. Returns: @@ -455,21 +457,22 @@ def tau_Rdc_max(tau_Rdc_0: float, a_cs_0: float, d: float) -> float: def d_eff_p(ds: float, As: float, dp: float, Ap: float) -> float: - """Calculate the effective depth for prestressed - members with bonded tendons. + """Calculate the effective depth for prestressed members with bonded + tendons. + + EN1992-1-1:2023 Eq. (8.36). - EN1992-1-1:2023 Eq. (8.36) - Parameters: - ds (float): Depth of the tension reinforcement in mm - As (float): Area of the tension reinforcement in mm2 - dp (float): Depth of the prestressed reinforcement in mm - Ap (float): Area of the prestressed reinforcement in mm2 + Args: + ds (float): Depth of the tension reinforcement in mm. + As (float): Area of the tension reinforcement in mm2. + dp (float): Depth of the prestressed reinforcement in mm. + Ap (float): Area of the prestressed reinforcement in mm2. Returns: - float: Effective depth in mm + float: Effective depth in mm. Raises: - ValueError: If any of the input values are negative + ValueError: If any of the input values are negative. """ if ds < 0: raise ValueError(f'ds must not be negative. Got {ds}') @@ -486,23 +489,24 @@ def d_eff_p(ds: float, As: float, dp: float, Ap: float) -> float: def rho_l_p( ds: float, As: float, dp: float, Ap: float, bw: float, d: float ) -> float: - """Calculate the reinforcement ratio for prestressed - members with bonded tendons. - - EN1992-1-1:2023 Eq. (8.37) - Parameters: - ds (float): Depth of the tension reinforcement in mm - As (float): Area of the tension reinforcement in mm2 - dp (float): Depth of the prestressed reinforcement in mm - Ap (float): Area of the prestressed reinforcement in mm2 - bw (float): Width of the member in mm - d (float): Effective depth in mm + """Calculate the reinforcement ratio for prestressed members with bonded + tendons. + + EN1992-1-1:2023 Eq. (8.37). + + Args: + ds (float): Depth of the tension reinforcement in mm. + As (float): Area of the tension reinforcement in mm2. + dp (float): Depth of the prestressed reinforcement in mm. + Ap (float): Area of the prestressed reinforcement in mm2. + bw (float): Width of the member in mm. + d (float): Effective depth in mm. Returns: - float: Reinforcement ratio + float: Reinforcement ratio. Raises: - ValueError: If any of the input values are negative + ValueError: If any of the input values are negative. """ if ds < 0: raise ValueError(f'ds must not be negative. Got {ds}') @@ -523,23 +527,23 @@ def rho_l_p( def rho_l_planar( vEd_y: float, vEd_x: float, rho_l_x: float, rho_l_y: float ) -> float: - """Calculate the reinforcement ratio for planar members with - different reinforcement ratios in both directions. + """Calculate the reinforcement ratio for planar members with different + reinforcement ratios in both directions. - EN1992-1-1:2023 Eq. (8.38), (8.39), (8.40) + EN1992-1-1:2023 Eq. (8.38), (8.39), (8.40). - Parameters: - vEd_y (float): Shear force in y-direction (kN) - vEd_x (float): Shear force in x-direction (kN) - rho_l_x (float): Reinforcement ratio in x-direction - rho_l_y (float): Reinforcement ratio in y-direction + Args: + vEd_y (float): Shear force in y-direction (kN). + vEd_x (float): Shear force in x-direction (kN). + rho_l_x (float): Reinforcement ratio in x-direction. + rho_l_y (float): Reinforcement ratio in y-direction. Returns: - float: Reinforcement ratio + float: Reinforcement ratio. Raises: - ValueError: If any of the input values are negative or if the - shear force ratio is not in the valid range + ValueError: If any of the input values are negative or if the shear + force ratio is not in the valid range. """ if vEd_y < 0: raise ValueError(f'vEd_y must not be negative. Got {vEd_y}') @@ -572,9 +576,9 @@ def cot_theta_min( d: float, ) -> float: """Calculate the minimum cotangent of the compression field inclination - angle, θmin, according to the conditions provided. + angle, thetamin, according to the conditions provided. - EN1992-1-1:2023 Eq. (8.41) + EN1992-1-1:2023 Eq. (8.41). Args: NEd (float): Axial force in the member in kN. @@ -586,8 +590,8 @@ def cot_theta_min( float: Minimum cotangent of the compression field inclination angle. Raises: - ValueError: If any of the dimensions or forces are negative, - or if d is zero. + ValueError: If any of the dimensions or forces are negative, or if d is + zero. """ if d <= 0 or x < 0: raise ValueError( @@ -606,7 +610,7 @@ def cot_theta_min( def tau_Rd_sy(rho_w: float, fywd: float, cot_theta: float) -> float: """Calculate the shear stress resistance of yielding shear reinforcement. - EN1992-1-1:2023 Eq. (8.42) + EN1992-1-1:2023 Eq. (8.42). Args: rho_w (float): Shear reinforcement ratio (unitless). @@ -628,9 +632,9 @@ def tau_Rd_sy(rho_w: float, fywd: float, cot_theta: float) -> float: def rho_w(Asw: float, bw: float, s: float) -> float: - """Calculate the shear reinforcement ratio ρw. + """Calculate the shear reinforcement ratio rho_w. - EN1992-1-1:2023 Eq. (8.43) + EN1992-1-1:2023 Eq. (8.43). Args: Asw (float): Area of shear reinforcement in mm2. @@ -653,9 +657,9 @@ def rho_w(Asw: float, bw: float, s: float) -> float: def sigma_cd( tau_Ed: float, cot_theta: float, tan_theta: float, nu: float, f_cd: float ) -> float: - """Calculate the stress in the compression field σcd and verify it. + """Calculate the stress in the compression field sigma_cd and verify it. - EN1992-1-1:2023 Eq. (8.44) + EN1992-1-1:2023 Eq. (8.44). Args: tau_Ed (float): Design value of the shear stress in MPa. @@ -665,7 +669,7 @@ def sigma_cd( f_cd (float): Design value of the concrete compressive strength in MPa. Returns: - float: Stress in the compression field σcd in MPa. + float: Stress in the compression field sigma_cd in MPa. Raises: ValueError: If any of the parameters are negative. @@ -680,11 +684,11 @@ def sigma_cd( def tau_Rd( rho_w: float, fywd: float, cot_theta: float, nu: float, f_cd: float ) -> float: - """Calculate the shear stress resistance τRd considering the simultaneous - yielding of the shear reinforcement and failure of the compression - field. + """Calculate the shear stress resistance tau_Rd considering the + simultaneous yielding of the shear reinforcement and failure of the + compression field. - EN1992-1-1:2023 Eq. (8.42) and (8.44) + EN1992-1-1:2023 Eq. (8.42) and (8.44). Args: rho_w (float): Shear reinforcement ratio, unitless. @@ -694,7 +698,7 @@ def tau_Rd( f_cd (float): Design value of the concrete compressive strength in MPa. Returns: - float: Shear stress resistance τRd in MPa. + float: Shear stress resistance tau_Rd in MPa. Raises: ValueError: If any of the parameters are negative. @@ -710,10 +714,10 @@ def cot_theta( nu: float, f_cd: float, rho_w: float, fywd: float, cot_theta_min: float ) -> float: """Calculate the cotangent of the angle of the compression field - considering the simultaneous yielding of the shear reinforcement and - failure of the compression field. + considering the simultaneous yielding of the shear reinforcement and + failure of the compression field. - EN1992-1-1:2023 Eq. (8.44) + EN1992-1-1:2023 Eq. (8.44). Args: nu (float): Coefficient (usually 0.5 as per the note). @@ -736,19 +740,19 @@ def cot_theta( def epsilon_xt(Ftd: float, Est: float, Ast: float) -> float: - """Calculate εxt. + """Calculate epsilon_xt. - EN1992-1-1:2023 Eq. (8.47) + EN1992-1-1:2023 Eq. (8.47). Args: - Ftd (float): Tensile force in the flexural tension chord in kN - Est (float): Modulus of elasticity of the steel in the - tension chord in MPa - Ast (float): Area of the longitudinal reinforcement in the - flexural tension chord in mm2 + Ftd (float): Tensile force in the flexural tension chord in kN. + Est (float): Modulus of elasticity of the steel in the tension chord in + MPa. + Ast (float): Area of the longitudinal reinforcement in the flexural + tension chord in mm2. Returns: - float: εxt (strain) + float: epsilon_xt (strain). """ if Est < 0: raise ValueError(f'Est must not be negative. Got {Est}') @@ -758,19 +762,18 @@ def epsilon_xt(Ftd: float, Est: float, Ast: float) -> float: def epsilon_xc_comp(Fcd: float, Ecc: float, Acc: float) -> float: - """Calculate εxc for compression. + """Calculate epsilon_xc for compression. - EN1992-1-1:2023 Eq. (8.48) + EN1992-1-1:2023 Eq. (8.48). Args: - Fcd (float): Compressive force in the flexural compression chord in kN - Ecc (float): Modulus of elasticity of the concrete in the - compression chord in MPa - Acc (float): Area of the flexural compression - chord in mm2 + Fcd (float): Compressive force in the flexural compression chord in kN. + Ecc (float): Modulus of elasticity of the concrete in the compression + chord in MPa. + Acc (float): Area of the flexural compression chord in mm2. Returns: - float: εxc (strain) + float: epsilon_xc (strain). """ if Acc < 0: raise ValueError(f'Acc must not be negative. Got {Acc}') @@ -780,19 +783,19 @@ def epsilon_xc_comp(Fcd: float, Ecc: float, Acc: float) -> float: def epsilon_xc_tens(Fcd: float, Esc: float, Asc: float) -> float: - """Calculate εxc. + """Calculate epsilon_xc. - EN1992-1-1:2023 Eq. (8.49) + EN1992-1-1:2023 Eq. (8.49). Args: - Fcd (float): Tensile force in the flexural compression chord kN - Esc (float): Modulus of elasticity of the steel in the - compression chord in MPa + Fcd (float): Tensile force in the flexural compression chord kN. + Esc (float): Modulus of elasticity of the steel in the compression + chord in MPa. Asc (float): Area of the longitudinal reinforcement in the flexural - compression chord mm2 + compression chord mm2. Returns: - float: εxc (strain) + float: epsilon_xc (strain). """ if Asc < 0: raise ValueError(f'Acc must not be negative. Got {Asc}') @@ -802,33 +805,33 @@ def epsilon_xc_tens(Fcd: float, Esc: float, Asc: float) -> float: def epsilon_x(epsilon_xt: float, epsilon_xc: float) -> float: - """Calculate εx. + """Calculate epsilon_x. - EN1992-1-1:2023 Eq. (8.46) + EN1992-1-1:2023 Eq. (8.46). Args: - epsilon_xt (float): Strain in the flexural tension chord - epsilon_xc (float): Strain in the flexural compression chord + epsilon_xt (float): Strain in the flexural tension chord. + epsilon_xc (float): Strain in the flexural compression chord. Returns: - float: εx (strain) + float: epsilon_x (strain). """ epsilon_x_value = (epsilon_xt + epsilon_xc) / 2 return max(epsilon_x_value, 0) def nu(epsilon_x: float, cot_theta: float) -> float: - """Calculate ν. + """Calculate nu. - EN1992-1-1:2023 Eq. (8.45) + EN1992-1-1:2023 Eq. (8.45). Args: - epsilon_x (float): Average strain of the bottom and top chords - cot_theta (float): cotan of the compression field - inclination to the member axis. + epsilon_x (float): Average strain of the bottom and top chords. + cot_theta (float): cotan of the compression field inclination to the + member axis. Returns: - float: ν (dimensionless factor) + float: nu (dimensionless factor). """ if epsilon_x < 0: raise ValueError(f'epsilon_x must not be negative. Got {epsilon_x}') @@ -842,7 +845,7 @@ def nu(epsilon_x: float, cot_theta: float) -> float: def Nvd(VEd: float, cot_theta: float) -> float: """Calculate the additional tensile axial force NVd due to shear VEd. - EN1992-1-1:2023 Eq. (8.50) + EN1992-1-1:2023 Eq. (8.50). Args: VEd (float): Shear force in kN. @@ -862,6 +865,8 @@ def Ftd( ) -> float: """Calculate the chord force Ftd. + EN1992-1-1:2023 Eq. (8.51). + Args: MEd (float): Moment in kNm. z (float): Lever arm in mm. @@ -874,8 +879,6 @@ def Ftd( Raises: ValueError: If any input is negative. - Reference: - EN1992-1-1:2023 Eq. (8.51) """ return MEd * 1000 / z + (NVd + NE) / 2 @@ -888,7 +891,7 @@ def Fcd( ) -> float: """Calculate the chord force Fcd. - EN1992-1-1:2023 Eq. (8.52) + EN1992-1-1:2023 Eq. (8.52). Args: MEd (float): Moment in kNm. @@ -908,10 +911,10 @@ def k_duct( wall_thickness: float, duct_diameter: float, ) -> float: - """Calculate the k_duct coefficient based on duct - material, filling, and wall thickness. + """Calculate the k_duct coefficient based on duct material, filling, and + wall thickness. - EN1992-1-1:2023 guidelines for k_duct + EN1992-1-1:2023 guidelines for k_duct. Args: duct_material (str): Material of the duct ('steel' or 'plastic'). @@ -959,14 +962,14 @@ def bw_nom( ) -> float: """Calculate the nominal web width considering the presence of ducts. - EN1992-1-1:2023 Eq. (8.54) + EN1992-1-1:2023 Eq. (8.54). Args: bw (float): Actual web width in mm. - duct_diameters (List[float]): List of duct diameters in mm - (each must be non-negative). - k_duct (float): Coefficient depending on the - material and filling of the duct. + duct_diameters (List[float]): List of duct diameters in mm (each must + be non-negative). + k_duct (float): Coefficient depending on the material and filling of + the duct. Returns: float: Nominal web width in mm. @@ -999,20 +1002,21 @@ def tau_rd( rho_w: float, f_ywd: float, ) -> float: - """Calculate the enhanced shear stress resistance τRd. + """Calculate the enhanced shear stress resistance tau_Rd. - EN1992-1-1:2023 Eq. (8.55) + EN1992-1-1:2023 Eq. (8.55). Args: - nu (float): The factor ν (unitless). + nu (float): The factor nu (unitless). f_cd (float): Design value of concrete compressive strength in MPa. cot_theta (float): Cotangent of the inclination of compression field. - cot_beta_incl (float): Cotangent of the inclination of load (cotβincl). - rho_w (float): Reinforcement ratio ρw. + cot_beta_incl (float): Cotangent of the inclination of load (cot + beta_incl). + rho_w (float): Reinforcement ratio rho_w. f_ywd (float): Design yield strength of shear reinforcement in MPa. Returns: - float: Enhanced shear stress resistance τRd in MPa. + float: Enhanced shear stress resistance tau_Rd in MPa. Raises: ValueError: If any input parameter is negative where applicable. @@ -1034,18 +1038,18 @@ def tau_rd( def sigma_swd( Es: float, eps_x: float, f_ywd: float, cot_theta: float ) -> float: - """Calculate the stress σswd in the shear reinforcement. + """Calculate the stress sigma_swd in the shear reinforcement. - EN1992-1-1:2023 Eq. (8.56) + EN1992-1-1:2023 Eq. (8.56). Args: Es (float): Modulus of elasticity of steel Es in MPa. - eps_x (float): Longitudinal strain εx. + eps_x (float): Longitudinal strain epsilon_x. f_ywd (float): Design yield strength of shear reinforcement in MPa. cot_theta (float): Cotangent of the inclination of compression field. Returns: - float: Stress σswd in the shear reinforcement in MPa. + float: Stress sigma_swd in the shear reinforcement in MPa. Raises: ValueError: If any input parameter is negative where applicable. @@ -1070,24 +1074,24 @@ def delta_MEd( a: float, x: float, ) -> float: - """Calculate the additional moment ΔMEd. + """Calculate the additional moment delta MEd. - EN1992-1-1:2023 Eq. (8.57) + EN1992-1-1:2023 Eq. (8.57). Args: - tau_ed (float): Shear stress τEd in MPa (should be positive). - rho_w (float): Reinforcement ratio ρw. + tau_ed (float): Shear stress tau_Ed in MPa (should be positive). + rho_w (float): Reinforcement ratio rho_w. f_ywd (float): Design yield strength of shear reinforcement in MPa. cot_theta (float): Cotangent of the inclination of compression field. z (float): Lever arm z in mm (should be positive). b_w (float): Width of the web bw in mm (should be positive). - a (float): Distance between the axis of the support and - the concentrated force in mm. + a (float): Distance between the axis of the support and the + concentrated force in mm. x (float): Distance between the support and the investigated cross-section in mm. Returns: - float: Additional moment ΔMEd in kNm. + float: Additional moment delta MEd in kNm. Raises: ValueError: If any input parameter is negative where applicable. @@ -1117,31 +1121,32 @@ def tau_rd_sy( alpha_w: float, cot_theta_min: float, ) -> float: - """Calculate the shear stress resistance τRd,sy for inclined - shear reinforcement. + """Calculate the shear stress resistance tau_Rd,sy for inclined shear + reinforcement. + + EN1992-1-1:2023 Eq. (8.58), (8.59). Args: - rho_w (float): Reinforcement ratio ρw. + rho_w (float): Reinforcement ratio rho_w. f_ywd (float): Design yield strength of shear reinforcement in MPa. - cot_theta (float): Cotangent of the inclination of compression - field in radians (cotθ). - alpha_w (float): Angle of inclined shear reinforcement αw - in degrees (45° ≤ αw < 90°). + cot_theta (float): Cotangent of the inclination of compression field in + radians (cottheta). + alpha_w (float): Angle of inclined shear reinforcement alpha_w in + degrees (45 ≤ alpha_w < 90). cot_theta_min (float): max value for cot_theta. Returns: - float: Shear stress resistance τRd,sy in MPa. + float: Shear stress resistance tau_Rd,sy in MPa. Raises: ValueError: If any input parameter is negative where applicable. - - Reference: - EN1992-1-1:2023 Eq. (8.58), (8.59) """ if f_ywd < 0: raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') if alpha_w < 45 or alpha_w >= 90: - raise ValueError(f'alpha_w must be between 45º and 90º. Got {alpha_w}') + raise ValueError( + f'alpha_w must be between 45 degrees and 90 degrees. Got {alpha_w}' + ) alpha_w_rad = math.radians(alpha_w) cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) @@ -1158,22 +1163,22 @@ def sigma_cd_s( f_cd: float, cot_theta_min: float, ) -> float: - """Calculate the compression stress σcd. + """Calculate the compression stress sigma_cd. - EN1992-1-1:2023 Eq. (8.58), (8.60) + EN1992-1-1:2023 Eq. (8.58), (8.60). Args: - tau_ed (float): Shear stress τEd in MPa. - cot_theta (float): Cotangent of the inclination of - compression field in radians (cotθ). - alpha_w (float): Angle of inclined shear reinforcement - αw in degrees (45° ≤ αw < 90°). - nu (float): The factor ν. + tau_ed (float): Shear stress tau_Ed in MPa. + cot_theta (float): Cotangent of the inclination of compression field in + radians (cottheta). + alpha_w (float): Angle of inclined shear reinforcement alpha_w in + degrees (45 ≤ alpha_w < 90). + nu (float): The factor nu. f_cd (float): Design value of concrete compressive strength in MPa. cot_theta_min (float): max value for cot_theta. Returns: - float: Compression stress σcd in MPa. + float: Compression stress sigma_cd in MPa. Raises: ValueError: If any input parameter is negative where applicable. @@ -1183,7 +1188,9 @@ def sigma_cd_s( if f_cd < 0: raise ValueError(f'f_cd must not be negative. Got {f_cd}') if alpha_w < 45 or alpha_w >= 90: - raise ValueError(f'alpha_w must be between 45º and 90º. Got {alpha_w}') + raise ValueError( + f'alpha_w must be between 45 degrees and 90 degrees. Got {alpha_w}' + ) alpha_w_rad = math.radians(alpha_w) cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) @@ -1199,14 +1206,14 @@ def NVds( ) -> float: """Calculate the axial tensile force NVd. - EN1992-1-1:2023 Eq. (8.58), (8.61) + EN1992-1-1:2023 Eq. (8.58), (8.61). Args: VEd (float): Design shear force VEd in kN (should be positive). - cot_theta (float): Cotangent of the nclination of - compression field in radians (cotθ). - alpha_w (float): Angle of inclined shear reinforcement - αw in degrees (45° ≤ αw < 90°). + cot_theta (float): Cotangent of the nclination of compression field in + radians (cottheta). + alpha_w (float): Angle of inclined shear reinforcement alpha_w in + degrees (45 ≤ alpha_w < 90). cot_theta_min (float): max value for cot_theta. Returns: @@ -1218,7 +1225,9 @@ def NVds( if VEd < 0: raise ValueError(f'VEd must not be negative. Got {VEd}') if alpha_w < 45 or alpha_w >= 90: - raise ValueError(f'alpha_w must be between 45º and 90º. Got {alpha_w}') + raise ValueError( + f'alpha_w must be between 45 degrees and 90 degrees. Got {alpha_w}' + ) alpha_w_rad = math.radians(alpha_w) cot_alpha_w = 1 / math.tan(alpha_w_rad) @@ -1237,39 +1246,38 @@ def tau_rd_incl( alpha_w: float, cot_theta_min: float, ) -> float: - """Calculate the shear stress resistance τRd for members - with inclined shear reinforcement. + """Calculate the shear stress resistance tau_Rd for members with inclined + shear reinforcement. - EN1992-1-1:2023 Eq. (8.62) + EN1992-1-1:2023 Eq. (8.62). Args: - nu (float): The factor ν (unitless). + nu (float): The factor nu (unitless). f_cd (float): Design value of concrete compressive strength in MPa. - cot_theta (float): Cotangent of the inclination of - compression field in radians. - cot_beta_incl (float): Cotangent of the inclination of - load in radians (cotβincl). - rho_w (float): Reinforcement ratio ρw (unitless). + cot_theta (float): Cotangent of the inclination of compression field in + radians. + cot_beta_incl (float): Cotangent of the inclination of load in radians + (cot beta_incl). + rho_w (float): Reinforcement ratio rho_w (unitless). f_ywd (float): Design yield strength of shear reinforcement in MPa. - alpha_w (float): Angle of inclined shear reinforcement - αw in degrees (45° ≤ αw < 90°). + alpha_w (float): Angle of inclined shear reinforcement alpha_w in + degrees (45 ≤ alpha_w < 90). cot_theta_min (float): max value for cot_theta. Returns: - float: Shear stress resistance τRd in MPa. + float: Shear stress resistance tau_Rd in MPa. Raises: ValueError: If any input parameter is negative where applicable. - - Reference: - """ if f_cd < 0: raise ValueError(f'f_cd must not be negative. Got {f_cd}') if f_ywd < 0: raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') if alpha_w < 45 or alpha_w >= 90: - raise ValueError(f'alpha_w must be between 45º and 90º. Got {alpha_w}') + raise ValueError( + f'alpha_w must be between 45 degrees and 90 degrees. Got {alpha_w}' + ) alpha_w_rad = math.radians(alpha_w) cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) @@ -1287,23 +1295,22 @@ def tau_rd_incl( def sigma_swd_v2( Es: float, eps_x: float, cot_theta: float, alpha_w: float, f_ywd: float ) -> float: - """Calculate the stress σswd in the shear - reinforcement for compression field inclinations. + """Calculate the stress sigma_swd in the shear reinforcement for + compression field inclinations. - EN1992-1-1:2023 Eq. (8.63) + EN1992-1-1:2023 Eq. (8.63). Args: Es (float): Modulus of elasticity of steel Es in MPa. - eps_x (float): Longitudinal strain εx (unitless). - cot_theta (float): Cotangent inclination of compression - field in radians. - alpha_w (float): Angle of inclined shear - reinforcement αw in degrees (45° ≤ αw < 90°). - f_ywd (float): Design yield strength of shear - reinforcement in MPa. + eps_x (float): Longitudinal strain epsilon_x (unitless). + cot_theta (float): Cotangent inclination of compression field in + radians. + alpha_w (float): Angle of inclined shear reinforcement alpha_w in + degrees (45 ≤ alpha_w < 90). + f_ywd (float): Design yield strength of shear reinforcement in MPa. Returns: - float: Stress σswd in the shear reinforcement in MPa. + float: Stress sigma_swd in the shear reinforcement in MPa. Raises: ValueError: If any input parameter is negative where applicable. @@ -1326,19 +1333,19 @@ def sigma_swd_v2( def tao_Rd_m(tau_rd: float, m_ed: float, m_rd: float) -> float: - """Calculate the shear stress resistance reduced - by the influence of transverse bending. + """Calculate the shear stress resistance reduced by the influence of + transverse bending. - EN1992-1-1:2023 Eq. (8.64) + EN1992-1-1:2023 Eq. (8.64). Args: - tau_rd (float): Shear resistance τRd in MPa. + tau_rd (float): Shear resistance tau_Rd in MPa. m_ed (float): Applied transverse bending moment mEd in kNm. - m_rd (float): Bending resistance - without interaction with shear mRd in kNm. + m_rd (float): Bending resistance without interaction with shear mRd in + kNm. Returns: - float: Reduced shear stress resistance τRdm in MPa. + float: Reduced shear stress resistance tau_Rdm in MPa. Raises: ValueError: If any of the input parameters are negative. @@ -1358,19 +1365,19 @@ def tao_Ed_flang( hf: float, delta_x: float, ) -> float: - """Calculate the longitudinal shear stress at - the junction between a flange and web. + """Calculate the longitudinal shear stress at the junction between a flange + and web. - EN1992-1-1:2023 Eq. (8.65) + EN1992-1-1:2023 Eq. (8.65). Args: - delta_Fd (float): Change of axial - force in the flange over length Δx in kN. + delta_Fd (float): Change of axial force in the flange over length delta + x in kN. hf (float): Thickness of the flange at the junction in mm. - delta_x (float): Length under consideration Δx in mm. + delta_x (float): Length under consideration delta x in mm. Returns: - float: Longitudinal shear stress τEd in MPa. + float: Longitudinal shear stress tau_Ed in MPa. Raises: ValueError: If any of the input parameters are negative. @@ -1391,7 +1398,7 @@ def Ast_min_flang(tau_ed: float, sf: float, hf: float, fyd: float) -> float: EN1992-1-1:2023 Eq. (8.66) Args: - tau_ed (float): Longitudinal shear stress τEd MPa. + tau_ed (float): Longitudinal shear stress tau_Ed MPa. sf (float): Spacing of reinforcement sf mm. hf (float): Thickness of the flange at the junction mm. fyd (float): Design yield strength of reinforcement fyd MPa. @@ -1420,22 +1427,22 @@ def Asf_flang( ) -> float: """Calculate the transverse reinforcement. - EN1992-1-1:2023 Eq. (8.69) + EN1992-1-1:2023 Eq. (8.69). Args: - tau_ed (float): Longitudinal shear stress τEd MPa. + tau_ed (float): Longitudinal shear stress tau_Ed MPa. sf (float): Spacing of reinforcement sf mm. hf (float): Thickness of the flange at the junction mm. fyd (float): Design yield strength of reinforcement fyd MPa. cot_theta_f (float): Cotangent of the inclination angle of the - compression field in the flange θf. + compression field in the flange thetaf. Returns: float: Required transverse reinforcement area Asf in mm2. Raises: - ValueError: If any of the input parameters are negative or - if cot_theta_f is out of the specified range. + ValueError: If any of the input parameters are negative or if + cot_theta_f is out of the specified range. """ if tau_ed < 0: raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') @@ -1455,21 +1462,19 @@ def sigma_cd_flang( fcd: float, nu: float = 0.5, ) -> float: - """Computes the compression field stress in - the flange. + """Computes the compression field stress in the flange. - EN1992-1-1:2023 Eq. (8.70), (8.71) + EN1992-1-1:2023 Eq. (8.70), (8.71). Args: - tau_ed (float or int): Longitudinal shear stress τEd in MPa. - theta_f (float or int): Inclination angle of the - compression field in the flange θf (degrees). - fcd (float or int): Design compressive - strength of concrete fcd in MPa. + tau_ed (float or int): Longitudinal shear stress tau_Ed in MPa. + theta_f (float or int): Inclination angle of the compression field in + the flange thetaf (degrees). + fcd (float or int): Design compressive strength of concrete fcd in MPa. nu (float, optional): Strength reduction factor, default is 0.5. Returns: - bool: The compression field stress in MPa + bool: The compression field stress in MPa. Raises: ValueError: If any of the input parameters are @@ -1491,9 +1496,8 @@ def sigma_cd_flang( def eps_x_flang(Ftd: float, Ast: float, Es: float) -> float: - """Calculate the longitudinal strain in the tensile flange based - on the thickness of the tensile flange and the - distance from the neutral axis. + """Calculate the longitudinal strain in the tensile flange based on the + thickness of the tensile flange and the distance from the neutral axis. EN1992-1-1:2023 Eq. (8.72) @@ -1521,7 +1525,7 @@ def eps_x_flang(Ftd: float, Ast: float, Es: float) -> float: def tau_Edi(VEdi: float, Ai: float) -> float: """Calculate the design value of the shear stress at an interface. - EN1992-1-1:2023 Eq. (8.74) + EN1992-1-1:2023 Eq. (8.74). Args: VEdi (float): Shear force acting parallel to the interface in kN. @@ -1544,8 +1548,8 @@ def tau_Edi(VEdi: float, Ai: float) -> float: def tau_Edi_composite( beta_new: float, VEd: float, z: float, bi: float ) -> float: - """Calculate the longitudinal shear stress between concrete - interfaces due to composite action. + """Calculate the longitudinal shear stress between concrete interfaces due + to composite action. EN1992-1-1:2023 Eq. (8.75). @@ -1560,8 +1564,8 @@ def tau_Edi_composite( float: Longitudinal shear stress at the interface in MPa. Raises: - ValueError: If any input value is negative - or zero when it should not be. + ValueError: If any input value is negative or zero when it should not + be. """ if beta_new < 0: raise ValueError(f'beta_new must not be negative. Got {beta_new}') @@ -1586,27 +1590,27 @@ def tau_Rdi( mu_v: float, gamma_c: float, ) -> float: - """Calculate the design shear stress resistance at the - interface for scenarios without reinforcement or where - reinforcement is sufficiently anchored. + """Calculate the design shear stress resistance at the interface for + scenarios without reinforcement or where reinforcement is sufficiently + anchored. - EN1992-1-1:2023 Eq. (8.76) + EN1992-1-1:2023 Eq. (8.76). Args: - fck (float): Lowest compressive strength of the - concretes at the interface in MPa. - sigma_n (float): Compressive or tensile stress - over the interface area in MPa. - Ai (float): Area of the interface in mm2 - Asi (float): Cross-sectional area of - bonded reinforcement crossing the interface in mm2. + fck (float): Lowest compressive strength of the concretes at the + interface in MPa. + sigma_n (float): Compressive or tensile stress over the interface area + in MPa. + Ai (float): Area of the interface in mm2. + Asi (float): Cross-sectional area of bonded reinforcement crossing the + interface in mm2. fyd (float): Design yield strength of the reinforcement in MPa. - alpha_deg (float): Angle of reinforcement - crossing the interface in degrees. - cv1 (float): Coefficient depending on the - roughness of the interface, dimensionless. - mu_v (float): Friction coefficient depending - on the roughness of the interface, dimensionless. + alpha_deg (float): Angle of reinforcement crossing the interface in + degrees. + cv1 (float): Coefficient depending on the roughness of the interface, + dimensionless. + mu_v (float): Friction coefficient depending on the roughness of the + interface, dimensionless. gamma_c (float): safety factory for concrete. Returns: @@ -1641,10 +1645,10 @@ def cv1( ], tensile_stress: bool = False, ) -> float: - """Get the cv1 coefficient based on the - surface roughness and tensile stress condition. + """Get the cv1 coefficient based on the surface roughness and tensile + stress condition. - EC1992-1-1:2023 Table (8.2) + EC1992-1-1:2023 Table (8.2). Args: surface_roughness (str): Description of the surface roughness. @@ -1676,7 +1680,7 @@ def mu_v( ) -> float: """Get the mu_v coefficient based on the surface roughness. - EC1992-1-1:2023 Table (8.2) + EC1992-1-1:2023 Table (8.2). Args: surface_roughness (str): Description of the surface roughness. @@ -1701,10 +1705,10 @@ def cv2( surface_roughness: Literal['very smooth', 'smooth', 'rough', 'very rough'], tensile_stress: bool = False, ) -> float: - """Get the cv2 coefficient based on the - surface roughness and tensile stress condition. + """Get the cv2 coefficient based on the surface roughness and tensile + stress condition. - EC1992-1-1:2023 Table (8.2) + EC1992-1-1:2023 Table (8.2). Args: surface_roughness (str): Description of the surface roughness. @@ -1733,7 +1737,7 @@ def kv( ) -> float: """Get the kv coefficient based on the surface roughness. - EC1992-1-1:2023 Table (8.2) + EC1992-1-1:2023 Table (8.2). Args: surface_roughness (str): Description of the surface roughness. @@ -1758,7 +1762,7 @@ def kdowel( ) -> float: """Get the kdowel coefficient based on the surface roughness. - EC1992-1-1:2023 Table (8.2) + EC1992-1-1:2023 Table (8.2). Args: surface_roughness (str): Description of the surface roughness. @@ -1789,14 +1793,14 @@ def tau_Rdi_ny( fyd: float, kdowel: float, ) -> float: - """Calculate the shear stress resistance at the interface when yielding - is not ensured at the interface. + """Calculate the shear stress resistance at the interface when yielding is + not ensured at the interface. - EN 1992-1-1:2022 Eq. (8.77) + EN 1992-1-1:2022 Eq. (8.77). Args: - cv2 (float): Coefficient depending on the roughness - of the interface (unitless). + cv2 (float): Coefficient depending on the roughness of the interface + (unitless). fck (float): Concrete compressive resistance in MPa. gamma_c (float): Partial safety factor for concrete (unitless). mu_v (float): Coefficient mu_v from the Eurocode (unitless). @@ -1804,11 +1808,11 @@ def tau_Rdi_ny( kv (float): Coefficient kv from the Eurocode (unitless). rho_i (float): Reinforcement ratio at the interface (unitless). fyd (float): Design yield strength of reinforcement MPa. - kdowel (float): Coefficient for dowel action of - reinforcement (unitless). + kdowel (float): Coefficient for dowel action of reinforcement + (unitless). Returns: - float: Shear stress resistance τRdi MPa. + float: Shear stress resistance tau_Rdi MPa. Raises: ValueError: If any of the dimensions or resistances are negative. @@ -1825,20 +1829,20 @@ def tau_Rdi_ny( ) return min( tau_rdi, 0.25 * fck / gamma_c - ) # Cap τRdi according to the formula + ) # Cap tau_Rdi according to the formula def as_min(tmin: float, fctm: float, fyk: float) -> float: - """Calculate the minimum interface reinforcement per unit - length along the edge of composite slabs. + """Calculate the minimum interface reinforcement per unit length along the + edge of composite slabs. EN 1992-1-1:2022 Eq. (8.78). Args: - tmin (float): Smaller value of the thickness of new - and old concrete layers in mm. - fctm (float): Mean tensile strength of the respective - concrete layer in MPa. + tmin (float): Smaller value of the thickness of new and old concrete + layers in mm. + fctm (float): Mean tensile strength of the respective concrete layer in + MPa. fyk (float): Characteristic yield strength of the reinforcement in MPa. Returns: From 82d0ed72870906200df7791b329ae4afddc7c0c1 Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Mon, 26 May 2025 14:20:03 +0200 Subject: [PATCH 26/28] full coverage --- .../test_ec2_2023_section_8_2_shear.py | 882 +++++++++++++++++- 1 file changed, 873 insertions(+), 9 deletions(-) diff --git a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py index a015a5ef..d2c46697 100644 --- a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py +++ b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py @@ -19,16 +19,48 @@ def test_tao_Ed(VEd, bw, d, expected): assert _section_8_2_shear.tao_Ed(VEd, bw, d) == pytest.approx(expected) +@pytest.mark.parametrize( + 'VEd, bw, d', + [ + (100.0, -0.3, 0.5), + (100.0, 0.3, -0.5), + (100.0, -0.3, -0.5), + ], +) +def test_tao_Ed_value_errors(VEd, bw, d): + """Test tao_Ed raises ValueError for negative bw or d.""" + with pytest.raises(ValueError): + _section_8_2_shear.tao_Ed(VEd, bw, d) + + @pytest.mark.parametrize( 'vEd, d, expected', [ - (50.0, 0.5, 50.0 / (0.9 * 0.5)), - (100.0, 0.6, 100.0 / (0.9 * 0.6)), + (100.0, 500.0, 100.0 / (0.9 * 500.0)), + (200.0, 400.0, 200.0 / (0.9 * 400.0)), + (50.0, 250.0, 50.0 / (0.9 * 250.0)), + (0.0, 300.0, 0.0), # Zero shear force + ], +) +def test_tao_Ed_planar_valid(vEd, d, expected): + """Test tao_Ed_planar with valid inputs.""" + assert _section_8_2_shear.tao_Ed_planar(vEd, d) == pytest.approx( + expected, rel=1e-9 + ) + + +@pytest.mark.parametrize( + 'vEd, d', + [ + (100.0, -500.0), + (0.0, -1.0), + (50.0, -0.01), ], ) -def test_tao_Ed_planar(vEd, d, expected): - """Test shear_stress_planar_members.""" - assert _section_8_2_shear.tao_Ed_planar(vEd, d) == pytest.approx(expected) +def test_tao_Ed_planar_invalid_depth(vEd, d): + """Test tao_Ed_planar raises ValueError for negative d.""" + with pytest.raises(ValueError): + _section_8_2_shear.tao_Ed_planar(vEd, d) @pytest.mark.parametrize( @@ -94,6 +126,20 @@ def test_ed_eff_with_angle(dx, dy, vEd_x, vEd_y, expected): ) +@pytest.mark.parametrize( + 'dx, dy, vEd_x, vEd_y', + [ + (-300, 400, 3.0, 1.0), # Negative dx + (300, -400, 3.0, 1.0), # Negative dy + (-300, -400, 3.0, 1.0), # Both dx and dy negative + ], +) +def test_d_eff_with_angle_value_errors(dx, dy, vEd_x, vEd_y): + """Test d_eff_with_angle raises ValueError for negative dx or dy.""" + with pytest.raises(ValueError): + _section_8_2_shear.d_eff_with_angle(dx, dy, vEd_x, vEd_y) + + @pytest.mark.parametrize( 'gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min, expected', [ @@ -116,6 +162,22 @@ def test_calculate_tau_Rdc( ) +@pytest.mark.parametrize( + 'gamma_v, f_ck, f_yd, d, d_lower', + [ + (-1.0, 30, 500, 500, 20), + (1.4, -30, 500, 500, 20), + (1.4, 30, -500, 500, 20), + (1.4, 30, 500, -500, 20), + (1.4, 30, 500, 500, -20), + ], +) +def test_tau_rdc_min_value_errors(gamma_v, f_ck, f_yd, d, d_lower): + """Test tau_rdc_min raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_rdc_min(gamma_v, f_ck, f_yd, d, d_lower) + + @pytest.mark.parametrize( 'A_sl, b_w, d, expected', [ @@ -131,6 +193,72 @@ def test_rho_l(A_sl, b_w, d, expected): ) +@pytest.mark.parametrize( + 'A_sl, b_w, d', + [ + (-1, 200, 500), # Negative A_sl + (300, -200, 500), # Negative b_w + (300, 200, -500), # Negative d + (0, 200, 500), # Zero A_sl + (300, 0, 500), # Zero b_w + (300, 200, 0), # Zero d + ], +) +def test_rho_l_value_errors(A_sl, b_w, d): + """Test that rho_l raises ValueError for non-positive arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.rho_l(A_sl, b_w, d) + + +@pytest.mark.parametrize( + 'vEd_x, vEd_y', + [ + (-1.0, 4.0), # Negative x-direction + (3.0, -2.0), # Negative y-direction + (-5.0, -6.0), # Both negative + ], +) +def test_v_Ed_value_errors(vEd_x, vEd_y): + """Test v_Ed raises ValueError for negative vEd_x or vEd_y.""" + with pytest.raises(ValueError): + _section_8_2_shear.v_Ed(vEd_x, vEd_y) + + +@pytest.mark.parametrize( + 'gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min', + [ + (-1.0, 0.02, 30, 500, 16, 0.3), # Negative gamma_v + (1.5, -0.02, 30, 500, 16, 0.3), # Negative rho_l + (1.5, 0.02, -30, 500, 16, 0.3), # Negative f_ck + (1.5, 0.02, 30, -500, 16, 0.3), # Negative d + (1.5, 0.02, 30, 500, -16, 0.3), # Negative d_g + (0.0, 0.02, 30, 500, 16, 0.3), # Zero gamma_v + (1.5, 0.0, 30, 500, 16, 0.3), # Zero rho_l + (1.5, 0.02, 0.0, 500, 16, 0.3), # Zero f_ck + (1.5, 0.02, 30, 0.0, 16, 0.3), # Zero d + (1.5, 0.02, 30, 500, 0.0, 0.3), # Zero d_g + ], +) +def test_tau_Rdc_value_errors(gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min): + """Test tau_Rdc raises ValueError for non-positive arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Rdc(gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min) + + +@pytest.mark.parametrize( + 'dx, dy, vEd_x, vEd_y', + [ + (-300, 400, 3.0, 1.0), # Negative dx + (300, -400, 3.0, 1.0), # Negative dy + (-300, -400, 3.0, 1.0), # Both dx and dy negative + ], +) +def test_d_eff_value_errors(dx, dy, vEd_x, vEd_y): + """Test d_eff raises ValueError for negative dx or dy.""" + with pytest.raises(ValueError): + _section_8_2_shear.d_eff(dx, dy, vEd_x, vEd_y) + + @pytest.mark.parametrize( 'a_cs, d, expected', [ @@ -145,6 +273,23 @@ def test_a_v(a_cs, d, expected): assert math.isclose(result, expected, rel_tol=1e-5) +@pytest.mark.parametrize( + 'a_cs, d', + [ + (-300, 200), # Negative a_cs + (300, -200), # Negative d + (0, 200), # Zero a_cs + (300, 0), # Zero d + (-300, -200), # Both negative + (0, 0), # Both zero + ], +) +def test_a_v_value_errors(a_cs, d): + """Test that a_v raises ValueError for non-positive arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.a_v(a_cs, d) + + @pytest.mark.parametrize( 'M_Ed, V_Ed, d, expected', [ @@ -159,6 +304,19 @@ def test_calculate_a_cs(M_Ed, V_Ed, d, expected): assert math.isclose(result, expected, rel_tol=1e-5) +@pytest.mark.parametrize( + 'M_Ed, V_Ed, d', + [ + (1000, 100, -500), # Negative d + (1000, 100, 0), # Zero d + ], +) +def test_a_cs_value_error_d(M_Ed, V_Ed, d): + """Test a_cs raises ValueError for non-positive d.""" + with pytest.raises(ValueError): + _section_8_2_shear.a_cs(M_Ed, V_Ed, d) + + @pytest.mark.parametrize( 'N_Ed, V_Ed, d, a_cs, expected', [ @@ -173,6 +331,23 @@ def test_calculate_k_vp(N_Ed, V_Ed, d, a_cs, expected): assert math.isclose(result, expected, rel_tol=1e-3) +@pytest.mark.parametrize( + 'N_Ed, V_Ed, d, a_cs', + [ + (100, 50, -500, 1500), # Negative d + (100, 50, 500, -1500), # Negative a_cs + (100, 50, 0, 1500), # Zero d + (100, 50, 500, 0), # Zero a_cs + (100, 50, 0, 0), # Both zero + (100, 50, -500, -1500), # Both negative + ], +) +def test_k_vp_value_errors(N_Ed, V_Ed, d, a_cs): + """Test k_vp raises ValueError for non-positive d or a_cs.""" + with pytest.raises(ValueError): + _section_8_2_shear.k_vp(N_Ed, V_Ed, d, a_cs) + + @pytest.mark.parametrize( 'gamma_v, rho_l, f_ck, d, d_g, expected', [ @@ -191,6 +366,27 @@ def test_calculate_tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_g, expected): ) +@pytest.mark.parametrize( + 'gamma_v, rho_l, f_ck, d, d_g', + [ + (0, 0.02, 30, 500, 16), # gamma_v zero + (-1, 0.02, 30, 500, 16), # gamma_v negative + (1.5, 0, 30, 500, 16), # rho_l zero + (1.5, -0.01, 30, 500, 16), # rho_l negative + (1.5, 0.02, 0, 500, 16), # f_ck zero + (1.5, 0.02, -30, 500, 16), # f_ck negative + (1.5, 0.02, 30, 0, 16), # d zero + (1.5, 0.02, 30, -500, 16), # d negative + (1.5, 0.02, 30, 500, 0), # d_g zero + (1.5, 0.02, 30, 500, -16), # d_g negative + ], +) +def test_tau_Rdc_0_value_errors(gamma_v, rho_l, f_ck, d, d_g): + """Test tau_Rdc_0 raises ValueError for non-positive arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_g) + + @pytest.mark.parametrize( 'tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, expected', [ @@ -210,6 +406,24 @@ def test_calculate_tau_Rdc_comp( ) +@pytest.mark.parametrize( + 'tau_Rdc_0, k1, sigma_cp, tau_Rdc_max', + [ + (0, 0.5, 0.1, 2), # tau_Rdc_0 zero + (-1, 0.5, 0.1, 2), # tau_Rdc_0 negative + (1, 0, 0.1, 2), # k1 zero + (1, -0.5, 0.1, 2), # k1 negative + (1, 0.5, -0.1, 2), # sigma_cp negative + (1, 0.5, 0.1, 0), # tau_Rdc_max zero + (1, 0.5, 0.1, -2), # tau_Rdc_max negative + ], +) +def test_tau_Rdc_comp_value_errors(tau_Rdc_0, k1, sigma_cp, tau_Rdc_max): + """Test tau_Rdc_comp raises ValueError.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Rdc_comp(tau_Rdc_0, k1, sigma_cp, tau_Rdc_max) + + @pytest.mark.parametrize( 'a_cs_0, e_p, A_c, b_w, z, d, expected', [ @@ -224,6 +438,26 @@ def test_calculate_k1(a_cs_0, e_p, A_c, b_w, z, d, expected): assert math.isclose(result, expected, rel_tol=1e-3) +@pytest.mark.parametrize( + 'a_cs_0, e_p, A_c, b_w, z, d', + [ + (0, 50, 10000, 200, 500, 200), # a_cs_0 zero + (-1000, 50, 10000, 200, 500, 200), # a_cs_0 negative + (1000, -1, 10000, 200, 500, 200), # e_p negative + (1000, 50, 0, 200, 500, 200), # A_c zero + (1000, 50, -10000, 200, 500, 200), # A_c negative + (1000, 50, 10000, 0, 500, 200), # b_w zero + (1000, 50, 10000, -200, 500, 200), # b_w negative + (1000, 50, 10000, 200, 0, 200), # z zero + (1000, 50, 10000, 200, -500, 200), # z negative + ], +) +def test_k1_value_errors(a_cs_0, e_p, A_c, b_w, z, d): + """Test k1 raises ValueError for non-positive or negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.k1(a_cs_0, e_p, A_c, b_w, z, d) + + @pytest.mark.parametrize( 'tau_Rdc_0, a_cs_0, d, expected', [ @@ -242,6 +476,25 @@ def test_calculate_tau_Rdc_max(tau_Rdc_0, a_cs_0, d, expected): ) +@pytest.mark.parametrize( + 'tau_Rdc_0, a_cs_0, d', + [ + (0, 1000, 500), # tau_Rdc_0 zero + (-1, 1000, 500), # tau_Rdc_0 negative + (1, 0, 500), # a_cs_0 zero + (1, -1000, 500), # a_cs_0 negative + (1, 1000, 0), # d zero + (1, 1000, -500), # d negative + (0, 0, 0), # all zero + (-1, -1000, -500), # all negative + ], +) +def test_tau_Rdc_max_value_errors(tau_Rdc_0, a_cs_0, d): + """Test tau_Rdc_max raises ValueError for non-positive arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Rdc_max(tau_Rdc_0, a_cs_0, d) + + @pytest.mark.parametrize( 'ds, As, dp, Ap, expected', [ @@ -259,6 +512,22 @@ def test_d_eff_p(ds, As, dp, Ap, expected): ) +@pytest.mark.parametrize( + 'ds, As, dp, Ap', + [ + (-1, 2000, 600, 1500), # Negative ds + (500, -2000, 600, 1500), # Negative As + (500, 2000, -600, 1500), # Negative dp + (500, 2000, 600, -1500), # Negative Ap + (-1, -2000, -600, -1500), # All negative + ], +) +def test_d_eff_p_value_errors(ds, As, dp, Ap): + """Test that d_eff_p raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.d_eff_p(ds, As, dp, Ap) + + @pytest.mark.parametrize( 'ds, As, dp, Ap, bw, d, expected', [ @@ -276,6 +545,23 @@ def test_calculate_reinforcement_ratio(ds, As, dp, Ap, bw, d, expected): ) +@pytest.mark.parametrize( + 'ds, As, dp, Ap, bw, d', + [ + (-1, 2000, 600, 1500, 300, 545.45), # Negative ds + (500, -2000, 600, 1500, 300, 545.45), # Negative As + (500, 2000, -600, 1500, 300, 545.45), # Negative dp + (500, 2000, 600, -1500, 300, 545.45), # Negative Ap + (500, 2000, 600, 1500, -300, 545.45), # Negative bw + (500, 2000, 600, 1500, 300, -545.45), # Negative d + ], +) +def test_rho_l_p_value_errors(ds, As, dp, Ap, bw, d): + """Test that rho_l_p raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.rho_l_p(ds, As, dp, Ap, bw, d) + + @pytest.mark.parametrize( 'vEd_y, vEd_x, rho_l_x, rho_l_y, expected', [ @@ -294,13 +580,62 @@ def test_rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y, expected): ) +@pytest.mark.parametrize( + 'vEd_y, vEd_x, rho_l_x, rho_l_y', + [ + (-1.0, 40, 0.005, 0.008), # Negative vEd_y + (20, -40, 0.005, 0.008), # Negative vEd_x + (20, 40, -0.005, 0.008), # Negative rho_l_x + (20, 40, 0.005, -0.008), # Negative rho_l_y + ], +) +def test_rho_l_planar_value_errors(vEd_y, vEd_x, rho_l_x, rho_l_y): + """Test that rho_l_planar raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y) + + # Tests using pytest def test_cot_theta_min(): """Tests the function cot_theta_min with various scenarios.""" assert _section_8_2_shear.cot_theta_min(0, 100, 10, 400) == 2.5 assert _section_8_2_shear.cot_theta_min(0, 100, 10, 400) == 2.5 assert _section_8_2_shear.cot_theta_min(10, 100, 10, 400) == 2.49 - assert _section_8_2_shear.cot_theta_min(100, 100, 50, 400) == 2.4 + assert _section_8_2_shear.cot_theta_min(-100, 100, 50, 400) == 3.0 + + +@pytest.mark.parametrize( + 'NEd, VEd, x, d', + [ + (0, 100, -1, 400), # x negative + (0, 100, 10, 0), # d zero + (0, 100, -1, 0), # x negative and d zero + (0, 100, 10, -400), # d negative + (0, 100, -10, -400), # both negative + (-10, 100, 10, 0), # d zero with NEd negative + (10, 100, 10, 0), # d zero with NEd positive + ], +) +def test_cot_theta_min_value_error_all_cases(NEd, VEd, x, d): + """Test cot_theta_min raises ValueError.""" + with pytest.raises(ValueError): + _section_8_2_shear.cot_theta_min(NEd, VEd, x, d) + + +@pytest.mark.parametrize( + 'NEd, VEd, x, d', + [ + (0, 100, 10, 0), # d is zero + (0, 100, -10, 400), # x is negative + (0, 100, -10, 0), # x negative and d zero + (0, 100, 10, -400), # d negative + (0, 100, -10, -400), # both negative + ], +) +def test_cot_theta_min_value_error(NEd, VEd, x, d): + """Test cot_theta_min raises ValueError for invalid dimensions.""" + with pytest.raises(ValueError): + _section_8_2_shear.cot_theta_min(NEd, VEd, x, d) def test_tau_Rd_sy(): @@ -309,17 +644,67 @@ def test_tau_Rd_sy(): assert _section_8_2_shear.tau_Rd_sy(0.02, 400, 2.5) == 20 +@pytest.mark.parametrize( + 'rho_w, fywd, cot_theta', + [ + (-0.01, 500, 2), # Negative rho_w + (0.01, -500, 2), # Negative fywd + (-0.01, -500, 2), # Both negative + ], +) +def test_tau_Rd_sy_value_errors(rho_w, fywd, cot_theta): + """Test tau_Rd_sy raises ValueError for negative rho_w or fywd.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Rd_sy(rho_w, fywd, cot_theta) + + def test_rho_w(): """Tests the function rho_w.""" assert _section_8_2_shear.rho_w(100, 200, 50) == 0.01 assert _section_8_2_shear.rho_w(200, 200, 100) == 0.01 +@pytest.mark.parametrize( + 'Asw, bw, s', + [ + (-100, 200, 50), # Negative Asw + (100, -200, 50), # Negative bw + (100, 200, -50), # Negative s + (0, 200, 50), # Zero Asw + (100, 0, 50), # Zero bw + (100, 200, 0), # Zero s + (0, 0, 0), # All zero + (-100, -200, -50), # All negative + ], +) +def test_rho_w_value_errors(Asw, bw, s): + """Test that rho_w raises ValueError for non-positive arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.rho_w(Asw, bw, s) + + def test_sigma_cd(): """Tests the function sigma_cd.""" assert _section_8_2_shear.sigma_cd(1, 2, 0.5, 0.5, 20) == 2.5 +@pytest.mark.parametrize( + 'tau_Ed, cot_theta, tan_theta, nu, f_cd', + [ + (-1, 1, 1, 0.5, 20), # Negative tau_Ed + (1, -1, 1, 0.5, 20), # Negative cot_theta + (1, 1, -1, 0.5, 20), # Negative tan_theta + (1, 1, 1, -0.5, 20), # Negative nu + (1, 1, 1, 0.5, -20), # Negative f_cd + (-1, -1, -1, -0.5, -20), # All negative + ], +) +def test_sigma_cd_value_errors(tau_Ed, cot_theta, tan_theta, nu, f_cd): + """Test sigma_cd raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.sigma_cd(tau_Ed, cot_theta, tan_theta, nu, f_cd) + + def test_tau_Rd(): """Tests the function tau_Rd.""" assert _section_8_2_shear.tau_Rd(0.01, 500, 2, 0.5, 50) == 10 @@ -328,12 +713,45 @@ def test_tau_Rd(): ) # Limited by the compression field +@pytest.mark.parametrize( + 'rho_w, fywd, cot_theta, nu, f_cd', + [ + (-0.01, 500, 2, 0.5, 50), # Negative rho_w + (0.01, -500, 2, 0.5, 50), # Negative fywd + (0.01, 500, -2, 0.5, 50), # Negative cot_theta + (0.01, 500, 2, -0.5, 50), # Negative nu + (0.01, 500, 2, 0.5, -50), # Negative f_cd + (-0.01, -500, -2, -0.5, -50), # All negative + ], +) +def test_tau_Rd_value_errors(rho_w, fywd, cot_theta, nu, f_cd): + """Test tau_Rd raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Rd(rho_w, fywd, cot_theta, nu, f_cd) + + def test_cot_theta(): """Tests the function cot_theta.""" assert _section_8_2_shear.cot_theta(0.5, 20, 0.01, 500, 2) == 1 assert _section_8_2_shear.cot_theta(0.5, 40, 0.01, 500, 2) == 2 +@pytest.mark.parametrize( + 'nu, f_cd, rho_w, fywd, cot_theta_min', + [ + (-0.1, 20, 0.01, 500, 2), # Negative nu + (0.5, -20, 0.01, 500, 2), # Negative f_cd + (0.5, 20, -0.01, 500, 2), # Negative rho_w + (0.5, 20, 0.01, -500, 2), # Negative fywd + (-0.5, -20, -0.01, -500, 2), # All negative + ], +) +def test_cot_theta_value_errors(nu, f_cd, rho_w, fywd, cot_theta_min): + """Test cot_theta raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.cot_theta(nu, f_cd, rho_w, fywd, cot_theta_min) + + @pytest.mark.parametrize( 'Ftd, Est, Ast, expected', [(500, 210000, 1000, 0.002380952380952381)] ) @@ -344,6 +762,20 @@ def test_epsilon_xt(Ftd, Est, Ast, expected): ) +@pytest.mark.parametrize( + 'Ftd, Est, Ast', + [ + (500, -210000, 1000), # Negative Est + (500, 210000, -1000), # Negative Ast + (500, -210000, -1000), # Both negative + ], +) +def test_epsilon_xt_value_errors(Ftd, Est, Ast): + """Test epsilon_xt raises ValueError for negative Est or Ast.""" + with pytest.raises(ValueError): + _section_8_2_shear.epsilon_xt(Ftd, Est, Ast) + + @pytest.mark.parametrize( 'Fcd, Ecc, Acc, expected', [(500, 30000, 1000, 0.016666666666666666)] ) @@ -354,6 +786,20 @@ def test_epsilon_xc_compression(Fcd, Ecc, Acc, expected): ) +@pytest.mark.parametrize( + 'Fcd, Ecc, Acc', + [ + (500, -30000, 1000), # Negative Ecc + (500, 30000, -1000), # Negative Acc + (500, -30000, -1000), # Both negative + ], +) +def test_epsilon_xc_comp_value_errors(Fcd, Ecc, Acc): + """Test epsilon_xc_comp raises ValueError for negative Ecc or Acc.""" + with pytest.raises(ValueError): + _section_8_2_shear.epsilon_xc_comp(Fcd, Ecc, Acc) + + @pytest.mark.parametrize( 'Fcd, Esc, Asc, expected', [(500, 210000, 1000, 0.002380952380952381)] ) @@ -364,6 +810,20 @@ def test_epsilon_xc_tension(Fcd, Esc, Asc, expected): ) +@pytest.mark.parametrize( + 'Fcd, Esc, Asc', + [ + (500, -210000, 1000), # Negative Esc + (500, 210000, -1000), # Negative Asc + (500, -210000, -1000), # Both negative + ], +) +def test_epsilon_xc_tens_value_errors(Fcd, Esc, Asc): + """Test epsilon_xc_tens raises ValueError for negative Esc or Asc.""" + with pytest.raises(ValueError): + _section_8_2_shear.epsilon_xc_tens(Fcd, Esc, Asc) + + @pytest.mark.parametrize( 'epsilon_xt, epsilon_xc, expected', [(0.002, 0.001, 0.0015)] ) @@ -382,6 +842,20 @@ def test_nu(epsilon_x, theta, expected): ) +@pytest.mark.parametrize( + 'epsilon_x, cot_theta', + [ + (-0.001, 2), # Negative epsilon_x + (-1.0, 0), # Negative epsilon_x, zero cot_theta + (-0.5, -2), # Negative epsilon_x, negative cot_theta + ], +) +def test_nu_value_errors(epsilon_x, cot_theta): + """Test nu raises ValueError for negative epsilon_x.""" + with pytest.raises(ValueError): + _section_8_2_shear.nu(epsilon_x, cot_theta) + + @pytest.mark.parametrize( 'VEd, theta, expected', [ @@ -445,6 +919,45 @@ def test_calculate_k_duct( ) +@pytest.mark.parametrize( + 'duct_material, is_grouted, wall_thickness, duct_diameter', + [ + ('steel', True, -1.0, 50.0), # Negative wall_thickness + ('steel', True, 2.0, -50.0), # Negative duct_diameter + ('plastic', False, -0.5, 40.0), # Negative wall_thickness + ('plastic', False, 1.0, -40.0), # Negative duct_diameter + ], +) +def test_k_duct_negative_dimensions( + duct_material, is_grouted, wall_thickness, duct_diameter +): + """Test k_duct raises ValueError.""" + with pytest.raises(ValueError): + _section_8_2_shear.k_duct( + duct_material, is_grouted, wall_thickness, duct_diameter + ) + + +@pytest.mark.parametrize( + 'duct_material, is_grouted, wall_thickness, duct_diameter', + [ + ('aluminum', True, 2.0, 50.0), # Invalid material + ('concrete', False, 1.0, 40.0), # Invalid material + ('', True, 1.0, 40.0), # Empty string + (None, True, 1.0, 40.0), # None as material + (123, True, 1.0, 40.0), # Non-string type + ], +) +def test_k_duct_invalid_material( + duct_material, is_grouted, wall_thickness, duct_diameter +): + """Test k_duct raises ValueError for invalid duct_material.""" + with pytest.raises(ValueError): + _section_8_2_shear.k_duct( + duct_material, is_grouted, wall_thickness, duct_diameter + ) + + @pytest.mark.parametrize( 'bw, duct_diameters, k_duct, expected', [ @@ -460,6 +973,23 @@ def test_calculate_nominal_web_width(bw, duct_diameters, k_duct, expected): ) == pytest.approx(expected, rel=1e-6) +@pytest.mark.parametrize( + 'bw, duct_diameters, k_duct', + [ + (-800, [50, 40], 0.5), # Negative bw + (800, [-50, 40], 0.5), # Negative duct diameter + (800, [50, -40], 0.5), # Negative duct diameter + (800, [-50, -40], 0.5), # All negative duct diameters + (800, [800 / 8 + 1], 0.5), # Sum of duct diameters exceeds bw/8 + (800, [100, 50, 50, 50, 50, 50, 50, 50, 50], 0.5), # Sum exceeds bw/8 + ], +) +def test_bw_nom_value_errors(bw, duct_diameters, k_duct): + """Test bw_nom raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.bw_nom(bw, duct_diameters, k_duct) + + @pytest.mark.parametrize( 'nu, f_cd, theta, beta_incl, rho_w, f_ywd, expected', [ @@ -476,6 +1006,22 @@ def test_tau_rd(nu, f_cd, theta, beta_incl, rho_w, f_ywd, expected): ) +@pytest.mark.parametrize( + 'nu, f_cd, cot_theta, cot_beta_incl, rho_w, f_ywd', + [ + (0.6, -30, 1, 1.5, 0.01, 500), # Negative f_cd + (0.6, 30, 1, 1.5, 0.01, -500), # Negative f_ywd + (0.6, -30, 1, 1.5, 0.01, -500), # Both negative + ], +) +def test_tau_rd_value_errors(nu, f_cd, cot_theta, cot_beta_incl, rho_w, f_ywd): + """Test tau_rd raises ValueError for negative f_cd or f_ywd.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_rd( + nu, f_cd, cot_theta, cot_beta_incl, rho_w, f_ywd + ) + + @pytest.mark.parametrize( 'e_s, eps_x, f_ywd, cot_theta, expected', [ @@ -492,6 +1038,20 @@ def test_sigma_swd(e_s, eps_x, f_ywd, cot_theta, expected): ) +@pytest.mark.parametrize( + 'Es, eps_x, f_ywd, cot_theta', + [ + (-200000, 0.001, 500, 2), # Negative Es + (200000, 0.001, -500, 2), # Negative f_ywd + (-200000, 0.001, -500, 2), # Both negative + ], +) +def test_sigma_swd_value_errors(Es, eps_x, f_ywd, cot_theta): + """Test sigma_swd raises ValueError for negative Es or f_ywd.""" + with pytest.raises(ValueError): + _section_8_2_shear.sigma_swd(Es, eps_x, f_ywd, cot_theta) + + @pytest.mark.parametrize( 'tau_ed, rho_w, f_ywd, theta, z, b_w, a, x, expected', [ @@ -510,6 +1070,26 @@ def test_delta_m_ed(tau_ed, rho_w, f_ywd, theta, z, b_w, a, x, expected): ) +@pytest.mark.parametrize( + 'tau_ed, rho_w, f_ywd, cot_theta, z, b_w, a, x', + [ + (-0.5, 0.01, 500, 1, 300, 200, 500, 250), # Negative tau_ed + (0.5, 0.01, -500, 1, 300, 200, 500, 250), # Negative f_ywd + (0.5, 0.01, 500, 1, -300, 200, 500, 250), # Negative z + (0.5, 0.01, 500, 1, 300, -200, 500, 250), # Negative b_w + (0.5, 0.01, 500, 1, 300, 200, -500, 250), # Negative a + (0.5, 0.01, 500, 1, 300, 200, 500, -250), # Negative x + (-0.5, 0.01, -500, 1, -300, -200, -500, -250), # All negative + ], +) +def test_delta_MEd_value_errors(tau_ed, rho_w, f_ywd, cot_theta, z, b_w, a, x): + """Test delta_MEd raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.delta_MEd( + tau_ed, rho_w, f_ywd, cot_theta, z, b_w, a, x + ) + + @pytest.mark.parametrize( 'rho_w, f_ywd, theta, alpha_w, cot_theta_min, expected', [ @@ -528,6 +1108,25 @@ def test_tau_rd_sy(rho_w, f_ywd, theta, alpha_w, cot_theta_min, expected): ) +@pytest.mark.parametrize( + 'rho_w, f_ywd, cot_theta, alpha_w, cot_theta_min', + [ + (0.01, -500, 2, 60, 3), # Negative f_ywd + (0.01, 500, 2, 44, 3), # alpha_w < 45 + (0.01, 500, 2, 90, 3), # alpha_w >= 90 + (0.01, 500, 2, 100, 3), # alpha_w > 90 + ], +) +def test_tau_rd_sy_value_errors( + rho_w, f_ywd, cot_theta, alpha_w, cot_theta_min +): + """Test tau_rd_sy raises ValueError for invalid f_ywd or alpha_w.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_rd_sy( + rho_w, f_ywd, cot_theta, alpha_w, cot_theta_min + ) + + @pytest.mark.parametrize( 'tau_ed, theta, alpha_w, nu, f_cd, cot_theta_min, expected', [ @@ -546,6 +1145,27 @@ def test_sigma_cd_s(tau_ed, theta, alpha_w, nu, f_cd, cot_theta_min, expected): ) +@pytest.mark.parametrize( + 'tau_ed, cot_theta, alpha_w, nu, f_cd, cot_theta_min', + [ + (-0.5, 1, 60, 0.6, 30, 2), # Negative tau_ed + (0.5, 1, 60, 0.6, -30, 2), # Negative f_cd + (0.5, 1, 44, 0.6, 30, 2), # alpha_w < 45 + (0.5, 1, 90, 0.6, 30, 2), # alpha_w >= 90 + (0.5, 1, 100, 0.6, 30, 2), # alpha_w > 90 + (-0.5, 1, 44, 0.6, -30, 2), # All invalid + ], +) +def test_sigma_cd_s_value_errors( + tau_ed, cot_theta, alpha_w, nu, f_cd, cot_theta_min +): + """Test sigma_cd_s raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.sigma_cd_s( + tau_ed, cot_theta, alpha_w, nu, f_cd, cot_theta_min + ) + + @pytest.mark.parametrize( 'v_ed, theta, alpha_w, cot_theta_min, expected', [ @@ -562,6 +1182,21 @@ def test_n_vd(v_ed, theta, alpha_w, cot_theta_min, expected): ) +@pytest.mark.parametrize( + 'VEd, cot_theta, alpha_w, cot_theta_min', + [ + (-100, 1, 60, 2), # Negative VEd + (100, 1, 44, 2), # alpha_w < 45 + (100, 1, 90, 2), # alpha_w >= 90 + (100, 1, 100, 2), # alpha_w > 90 + ], +) +def test_NVds_value_errors(VEd, cot_theta, alpha_w, cot_theta_min): + """Test NVds raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.NVds(VEd, cot_theta, alpha_w, cot_theta_min) + + @pytest.mark.parametrize( 'nu, f_cd, theta, beta_incl, rho_w, f_ywd, ' + 'alpha_w, cot_theta_min, expected', @@ -583,6 +1218,33 @@ def test_tau_rd_incl( ) +@pytest.mark.parametrize( + 'nu, f_cd, cot_theta, cot_beta_incl, rho_w, f_ywd, alpha_w, cot_theta_min', + [ + (0.6, -30, 1, 3, 0.01, 500, 45, 2), # Negative f_cd + (0.6, 30, 1, 3, 0.01, -500, 45, 2), # Negative f_ywd + (0.6, 30, 1, 3, 0.01, 500, 44, 2), # alpha_w < 45 + (0.6, 30, 1, 3, 0.01, 500, 90, 2), # alpha_w >= 90 + (0.6, 30, 1, 3, 0.01, 500, 100, 2), # alpha_w > 90 + ], +) +def test_tau_rd_incl_value_errors( + nu, f_cd, cot_theta, cot_beta_incl, rho_w, f_ywd, alpha_w, cot_theta_min +): + """Test tau_rd_incl raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_rd_incl( + nu, + f_cd, + cot_theta, + cot_beta_incl, + rho_w, + f_ywd, + alpha_w, + cot_theta_min, + ) + + @pytest.mark.parametrize( 'e_s, eps_x, theta, alpha_w, f_ywd, expected', [ @@ -599,6 +1261,20 @@ def test_sigma_swd_v2(e_s, eps_x, theta, alpha_w, f_ywd, expected): ) +@pytest.mark.parametrize( + 'Es, eps_x, cot_theta, alpha_w, f_ywd', + [ + (-200000, 0.001, 2, 45, 500), # Negative Es + (200000, 0.001, 2, 45, -500), # Negative f_ywd + (-200000, 0.001, 2, 45, -500), # Both negative + ], +) +def test_sigma_swd_v2_value_errors(Es, eps_x, cot_theta, alpha_w, f_ywd): + """Test sigma_swd_v2 raises ValueError for negative Es or f_ywd.""" + with pytest.raises(ValueError): + _section_8_2_shear.sigma_swd_v2(Es, eps_x, cot_theta, alpha_w, f_ywd) + + @pytest.mark.parametrize( 'tau_rd, m_ed, m_rd, expected', [ @@ -613,6 +1289,21 @@ def test_shear_stress_resistance_reduced(tau_rd, m_ed, m_rd, expected): ) +@pytest.mark.parametrize( + 'tau_rd, m_ed, m_rd', + [ + (-1.0, 2.0, 10.0), # Negative tau_rd + (5.0, -2.0, 10.0), # Negative m_ed + (5.0, 2.0, -10.0), # Negative m_rd + (-1.0, -2.0, -10.0), # All negative + ], +) +def test_tao_Rd_m_value_errors(tau_rd, m_ed, m_rd): + """Test tao_Rd_m raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.tao_Rd_m(tau_rd, m_ed, m_rd) + + @pytest.mark.parametrize( 'delta_fd, hf, delta_x, expected', [ @@ -627,6 +1318,55 @@ def test_longitudinal_shear_stress(delta_fd, hf, delta_x, expected): ) == pytest.approx(expected, rel=10e-2) +@pytest.mark.parametrize( + 'delta_fd, hf, delta_x', + [ + (-1.0, 200.0, 1000.0), # Negative delta_fd + (100.0, 0.0, 1000.0), # Zero hf + (100.0, -200.0, 1000.0), # Negative hf + (100.0, 200.0, 0.0), # Zero delta_x + (100.0, 200.0, -1000.0), # Negative delta_x + (-1.0, -200.0, -1000.0), # All negative + ], +) +def test_tao_Ed_flang_value_errors(delta_fd, hf, delta_x): + """Test tao_Ed_flang raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.tao_Ed_flang(delta_fd, hf, delta_x) + + +@pytest.mark.parametrize( + 'tau_ed, sf, hf, fyd, expected', + [ + (2.0, 150.0, 200.0, 500.0, 120.0), + (1.5, 100.0, 100.0, 400.0, 37.5), + (0.0, 200.0, 300.0, 600.0, 0.0), # zero shear stress + ], +) +def test_Ast_min_flang_valid(tau_ed, sf, hf, fyd, expected): + """Test Ast_min_flang with valid inputs.""" + assert _section_8_2_shear.Ast_min_flang( + tau_ed, sf, hf, fyd + ) == pytest.approx(expected, rel=1e-6) + + +@pytest.mark.parametrize( + 'tau_ed, sf, hf, fyd', + [ + (-1.0, 150.0, 200.0, 500.0), # Negative tau_ed + (2.0, 0.0, 200.0, 500.0), # Zero sf + (2.0, -150.0, 200.0, 500.0), # Negative sf + (2.0, 150.0, 0.0, 500.0), # Zero hf + (2.0, 150.0, -200.0, 500.0), # Negative hf + (2.0, 150.0, 200.0, -500.0), # Negative fyd + ], +) +def test_Ast_min_flang_value_errors(tau_ed, sf, hf, fyd): + """Test Ast_min_flang raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.Ast_min_flang(tau_ed, sf, hf, fyd) + + @pytest.mark.parametrize( 'tau_ed, sf, hf, fyd, cot_theta_f, expected', [ @@ -643,6 +1383,23 @@ def test_transverse_reinforcement_flange( ) == pytest.approx(expected, rel=10e-2) +@pytest.mark.parametrize( + 'tau_ed, sf, hf, fyd, cot_theta_f', + [ + (-1.0, 150.0, 200.0, 500.0, 1.0), # Negative tau_ed + (2.0, 0.0, 200.0, 500.0, 1.0), # Zero sf + (2.0, -150.0, 200.0, 500.0, 1.0), # Negative sf + (2.0, 150.0, 0.0, 500.0, 1.0), # Zero hf + (2.0, 150.0, -200.0, 500.0, 1.0), # Negative hf + (2.0, 150.0, 200.0, -500.0, 1.0), # Negative fyd + ], +) +def test_Asf_flang_value_errors(tau_ed, sf, hf, fyd, cot_theta_f): + """Test Asf_flang raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.Asf_flang(tau_ed, sf, hf, fyd, cot_theta_f) + + @pytest.mark.parametrize( 'tau_ed, theta_f, fcd, nu, expected', [ @@ -657,6 +1414,23 @@ def test_sigma_cd_flang(tau_ed, theta_f, fcd, nu, expected): ) == pytest.approx(expected, rel=10e-2) +@pytest.mark.parametrize( + 'tau_ed, theta_f, fcd, nu', + [ + (-1.0, 45.0, 30.0, 0.5), # Negative tau_ed + (5.0, -45.0, 30.0, 0.5), # Negative theta_f + (5.0, 45.0, -30.0, 0.5), # Negative fcd + (5.0, 45.0, 30.0, 0.0), # nu zero + (5.0, 45.0, 30.0, -0.1), # nu negative + (-1.0, -45.0, -30.0, -0.1), # All negative + ], +) +def test_sigma_cd_flang_value_errors(tau_ed, theta_f, fcd, nu): + """Test sigma_cd_flang raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.sigma_cd_flang(tau_ed, theta_f, fcd, nu) + + # Valid inputs @pytest.mark.parametrize( 'Ftd, Ast, Es, expected', @@ -673,6 +1447,25 @@ def test_eps_x_flang(Ftd, Ast, Es, expected): ) +@pytest.mark.parametrize( + 'Ftd, Ast, Es', + [ + (0, 500, 200000), # Ftd zero + (-100, 500, 200000), # Ftd negative + (100, 0, 200000), # Ast zero + (100, -500, 200000), # Ast negative + (100, 500, 0), # Es zero + (100, 500, -200000), # Es negative + (0, 0, 0), # All zero + (-1, -1, -1), # All negative + ], +) +def test_eps_x_flang_value_errors(Ftd, Ast, Es): + """Test eps_x_flang raises ValueError for non-positive arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.eps_x_flang(Ftd, Ast, Es) + + @pytest.mark.parametrize( 'VEdi, Ai, expected', [(1000, 200000, 5), (0, 100000, 0.0), (500, 250000, 2)], @@ -684,9 +1477,16 @@ def test_calculate_tau_edi(VEdi, Ai, expected): ) -@pytest.mark.parametrize('VEdi, Ai', [(-1, 200000), (1000, -1)]) -def test_calculate_tau_edi_errors(VEdi, Ai): - """Test that errors are raised for invalid inputs in calculate_tau_edi.""" +@pytest.mark.parametrize( + 'VEdi, Ai', + [ + (-10, 1000), # Negative VEdi + (100, -1000), # Negative Ai + (-5, -1000), # Both negative + ], +) +def test_tau_Edi_value_errors(VEdi, Ai): + """Test tau_Edi raises ValueError for negative VEdi or Ai.""" with pytest.raises(ValueError): _section_8_2_shear.tau_Edi(VEdi, Ai) @@ -742,6 +1542,29 @@ def test_tau_rdi( ) +@pytest.mark.parametrize( + 'fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c', + [ + (-30, 5, 10000, 100, 500, 45, 0.1, 0.2, 1.5), # Negative fck + (30, 5, -10000, 100, 500, 45, 0.1, 0.2, 1.5), # Negative Ai + (30, 5, 10000, -100, 500, 45, 0.1, 0.2, 1.5), # Negative Asi + (30, 5, 10000, 100, -500, 45, 0.1, 0.2, 1.5), # Negative fyd + (30, 5, 10000, 100, 500, 20, 0.1, 0.2, 1.5), # alpha_deg < 35 + (30, 5, 10000, 100, 500, 140, 0.1, 0.2, 1.5), # alpha_deg > 135 + (30, 5, 10000, 100, 500, 45, 0.1, 0.2, -1.5), # Negative gamma_c + (-30, 5, -10000, -100, -500, 20, 0.1, 0.2, -1.5), # All negative + ], +) +def test_tau_Rdi_value_errors( + fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c +): + """Test tau_Rdi raises ValueError.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Rdi( + fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c + ) + + # Test cv1 function @pytest.mark.parametrize( 'surface_roughness, tensile_stress, expected', @@ -839,6 +1662,28 @@ def test_shear_stress_resistance( assert result == pytest.approx(expected, rel=10e-3) +@pytest.mark.parametrize( + 'cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel', + [ + (0.1, -30, 1.5, 0.2, 0.3, 0.1, 0.02, 500, 0.0), # Negative fck + (0.1, 30, -1.5, 0.2, 0.3, 0.1, 0.02, 500, 0.0), # Negative gamma_c + (0.1, 30, 1.5, 0.2, 0.3, -0.1, 0.02, 500, 0.0), # Negative kv + (0.1, 30, 1.5, 0.2, 0.3, 0.1, -0.02, 500, 0.0), # Negative rho_i + (0.1, 30, 1.5, 0.2, 0.3, 0.1, 0.02, -500, 0.0), # Negative fyd + (0.1, 30, 1.5, 0.2, 0.3, 0.1, 0.02, 500, -0.1), # Negative kdowel + (0.1, -30, -1.5, 0.2, 0.3, -0.1, -0.02, -500, -0.1), # All negative + ], +) +def test_tau_Rdi_ny_value_errors( + cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel +): + """Test tau_Rdi_ny raises ValueError.""" + with pytest.raises(ValueError): + _section_8_2_shear.tau_Rdi_ny( + cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel + ) + + @pytest.mark.parametrize( 'tmin, fctm, fyk, expected', [ @@ -849,3 +1694,22 @@ def test_min_interface_reinforcement(tmin, fctm, fyk, expected): """Test the minimum interface reinforcement calculation.""" result = _section_8_2_shear.as_min(tmin, fctm, fyk) assert result == pytest.approx(expected, rel=10e-3) + + +@pytest.mark.parametrize( + 'tmin, fctm, fyk', + [ + (0, 2.9, 500), # tmin zero + (-1, 2.9, 500), # tmin negative + (200, 0, 500), # fctm zero + (200, -2.9, 500), # fctm negative + (200, 2.9, 0), # fyk zero + (200, 2.9, -500), # fyk negative + (0, 0, 0), # all zero + (-1, -2.9, -500), # all negative + ], +) +def test_as_min_value_errors(tmin, fctm, fyk): + """Test as_min raises ValueError for non-positive arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.as_min(tmin, fctm, fyk) From 74053ee932cf7dbce33bba2cb524f58482b8949f Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Tue, 21 Oct 2025 16:30:41 +0200 Subject: [PATCH 27/28] first fixes --- docs/conf.py | 2 +- structuralcodes/codes/ec2_2023/__init__.py | 12 +- .../codes/ec2_2023/_section_8_2_shear.py | 177 ++++++++------ .../test_ec2_2023_section_8_2_shear.py | 215 ++++++++++++------ 4 files changed, 262 insertions(+), 144 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 519e3b67..e8cad954 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -67,7 +67,7 @@ - """, + """, # noqa: E501 'class': '', }, ], diff --git a/structuralcodes/codes/ec2_2023/__init__.py b/structuralcodes/codes/ec2_2023/__init__.py index 117ac614..8ef01c19 100644 --- a/structuralcodes/codes/ec2_2023/__init__.py +++ b/structuralcodes/codes/ec2_2023/__init__.py @@ -64,8 +64,8 @@ cv1, cv2, d_eff, + d_eff_angle, d_eff_p, - d_eff_with_angle, delta_MEd, eps_x_flang, epsilon_x, @@ -88,10 +88,10 @@ sigma_cd_s, sigma_swd, sigma_swd_v2, - tao_Ed, tao_Ed_flang, - tao_Ed_planar, tao_Rd_m, + tau_Ed, + tau_Ed_planar, tau_Edi, tau_Edi_composite, tau_Rd, @@ -159,11 +159,11 @@ 'tau_Rdc_comp', 'tau_Rdc_max', 'k_vp', - 'tao_Ed', + 'tau_Ed', 'd_eff', - 'd_eff_with_angle', + 'd_eff_angle', 'v_Ed', - 'tao_Ed_planar', + 'tau_Ed_planar', 'tau_rdc_min', 'A_phi_correction_exp', 'alpha_c_th', diff --git a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py index 7bad7cc4..a5e23276 100644 --- a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py +++ b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py @@ -4,7 +4,7 @@ from typing import List, Literal -def tao_Ed(VEd: float, bw: float, d: float) -> float: +def tau_Ed(VEd: float, bw: float, d: float) -> float: """Calculate the average shear stress over the cross-section for linear members. @@ -29,7 +29,7 @@ def tao_Ed(VEd: float, bw: float, d: float) -> float: return VEd * 1000 / (bw * z) -def tao_Ed_planar(vEd: float, d: float) -> float: +def tau_Ed_planar(vEd: float, d: float) -> float: """Calculate the average shear stress over the cross-section for planar members. @@ -51,12 +51,41 @@ def tao_Ed_planar(vEd: float, d: float) -> float: return vEd / z +def d_dg(f_ck: float, d_lower: float) -> float: + """Calculate the size parameter describing the failure zone roughness. + + EN1992-1-1:2023 Note 2 for Eq. (8.20). + + Args: + f_ck (float): Characteristic compressive strength of concrete in MPa + (must be positive). + d_lower (float): Smallest value of the upper sieve size D in an + aggregate for the coarsest fraction of aggregates in mm (must be + positive). + + Returns: + float: Size parameter ddg in mm. + + Raises: + ValueError: If any input value is non-positive. + """ + if f_ck < 0: + raise ValueError(f'f_ck must not be negative. Got {f_ck}') + if d_lower < 0: + raise ValueError(f'd_lower must not be negative. Got {d_lower}') + + if f_ck <= 60: + return min(16 + d_lower, 40) + + return min(16 + d_lower * (60 / f_ck) ** 2, 40) + + def tau_rdc_min( gamma_v: float, f_ck: float, f_yd: float, d: float, - d_lower: float, + d_dg: float, ) -> float: """Calculate the minimum shear stress resistance. @@ -70,9 +99,8 @@ def tau_rdc_min( positive). d (float): Effective depth of the flexural reinforcement in mm (must be positive). - d_lower (float): Smallest value of the upper sieve size D in an - aggregate for the coarsest fraction of aggregates in mm (must be - positive). + d_dg (float): Size parameter describing the failure zone roughness in + mm (must be positive). Returns: float: Minimum shear stress resistance in MPa. @@ -88,13 +116,8 @@ def tau_rdc_min( raise ValueError(f'f_yd must not be negative. Got {f_yd}') if d < 0: raise ValueError(f'd must not be negative. Got {d}') - if d_lower < 0: - raise ValueError(f'd_lower must not be negative. Got {d_lower}') - - if f_ck <= 60: - d_dg = min(16 + d_lower, 40) - else: - d_dg = min(16 + d_lower * (60 / f_ck) ** 2, 40) + if d_dg < 0: + raise ValueError(f'd_dg must not be negative. Got {d_dg}') return 11 / gamma_v * math.sqrt(f_ck / f_yd * d_dg / d) @@ -110,15 +133,7 @@ def v_Ed(vEd_x: float, vEd_y: float) -> float: Returns: float: Design shear force per unit width (vEd) kN/m. - - Raises: - ValueError: If vEd_x or vEd_y is negative. """ - if vEd_x < 0: - raise ValueError(f'vEd_x must not be negative. Got {vEd_x}') - if vEd_y < 0: - raise ValueError(f'vEd_y must not be negative. Got {vEd_y}') - return math.sqrt(vEd_x**2 + vEd_y**2) @@ -128,8 +143,8 @@ def d_eff(dx: float, dy: float, vEd_x: float, vEd_y: float) -> float: EN1992-1-1:2023 Eq. (8.22), (8.23), (8.24). Args: - dx (float): Effective depth in x-direction in mm. - dy (float): Effective depth in y-direction in mm. + dx (float): Effective depth in x-direction in mm (must be positive). + dy (float): Effective depth in y-direction in mm (must be positive). vEd_x (float): Shear force in x-direction in kN/m. vEd_y (float): Shear force in y-direction in kN/m. @@ -137,7 +152,7 @@ def d_eff(dx: float, dy: float, vEd_x: float, vEd_y: float) -> float: float: Effective depth (d) in mm. Raises: - ValueError: If dx, dy, vEd_x, or vEd_y is negative. + ValueError: If dx or dy is negative. """ if dx < 0: raise ValueError(f'dx must not be negative. Got {dx}') @@ -156,9 +171,7 @@ def d_eff(dx: float, dy: float, vEd_x: float, vEd_y: float) -> float: return dy -def d_eff_with_angle( - dx: float, dy: float, vEd_x: float, vEd_y: float -) -> float: +def d_eff_angle(dx: float, dy: float, vEd_x: float, vEd_y: float) -> float: """Calculate the effective depth (d) based on the angle alpha_v. EN1992-1-1:2023 Eq. (8.25), (8.26). @@ -173,7 +186,7 @@ def d_eff_with_angle( float: Effective depth (d) mm. Raises: - ValueError: If dx, dy, vEd_x, or vEd_y is negative. + ValueError: If dx or dy is negative. """ if dx < 0: raise ValueError(f'dx must not be negative. Got {dx}') @@ -193,7 +206,7 @@ def tau_Rdc( rho_l: float, f_ck: float, d: float, - d_g: float, + d_dg: float, tau_rdc_min: float, ) -> float: """Calculate the design value of the shear stress resistance. @@ -205,23 +218,26 @@ def tau_Rdc( rho_l (float): Reinforcement ratio (unitless). f_ck (float): Characteristic compressive strength of concrete in MPa. d (float): Effective depth in mm. - d_g (float): Maximum aggregate size in mm. + d_dg (float): Size parameter describing the failure zone roughness in + mm. tau_rdc_min (float): Minimum resistance neede in MPa. Returns: float: The design value of the shear stress resistance MPa. Raises: - ValueError: If any of the input values are negative. + ValueError: If any input values are negative + or if gamma_v or d are non-positive. """ - if gamma_v <= 0 or rho_l <= 0 or f_ck <= 0 or d <= 0 or d_g <= 0: + if gamma_v <= 0 or rho_l < 0 or f_ck < 0 or d <= 0 or d_dg < 0: raise ValueError( - f'All input values must be positive. Got gamma_v={gamma_v}, ' - + f'rho_l={rho_l}, f_ck={f_ck}, d={d}, d_g={d_g}' + 'gamma_v and d must be positive other values must be non-negative.' + + f'Got gamma_v={gamma_v}, rho_l={rho_l}, ' + + f' f_ck={f_ck}, d={d}, d_dg={d_dg}' ) return max( - 0.66 / gamma_v * (100 * rho_l * f_ck * d_g / d) ** (1 / 3), + 0.66 / gamma_v * (100 * rho_l * f_ck * d_dg / d) ** (1 / 3), abs(tau_rdc_min), ) @@ -252,20 +268,19 @@ def rho_l(A_sl: float, b_w: float, d: float) -> float: def a_v(a_cs: float, d: float) -> float: - """Calculate the effective shear span. + """Calculate the mechanical shear span. - EN1992-1-1:2023 Eq. (8.30). + EN1992-1-1:2023 Eq. (8.29). Args: - M_Ed (float): Bending moment kN·m. - V_Ed (float): Shear force kN. - d (float): Effective depth mm. + a_cs (float): Effective shear span in mm. + d (float): Effective depth in mm. Returns: - float: The effective shear span in mm. + float: The mechanical shear span av in mm. Raises: - ValueError: If any of the input values are negative. + ValueError: If any of the input values are non-positive. """ if a_cs <= 0 or d <= 0: raise ValueError( @@ -302,7 +317,8 @@ def k_vp(N_Ed: float, V_Ed: float, d: float, a_cs: float) -> float: EN1992-1-1:2023 Eq. (8.31). Args: - N_Ed (float): Axial force in kN. + N_Ed (float): Axial force in kN + (compression negative, tension positive). V_Ed (float): Shear force in kN. d (float): Effective depth in mm. a_cs (float): Effective shear span in mm. @@ -311,13 +327,12 @@ def k_vp(N_Ed: float, V_Ed: float, d: float, a_cs: float) -> float: float: The coefficient k_vp (unitless). Raises: - ValueError: If any of the input values are negative. + ValueError: If d or a_cs are non-positive. """ if d <= 0 or a_cs <= 0: raise ValueError( - 'All input values must be positive. ' - + f'Got N_Ed={N_Ed}, V_Ed={V_Ed}, d={d}, a_cs={a_cs}' + 'd and a_cs must be positive. ' + f'Got d={d}, a_cs={a_cs}' ) k_vp = 1 + (N_Ed / abs(V_Ed)) * (d / (3 * a_cs)) @@ -325,7 +340,7 @@ def k_vp(N_Ed: float, V_Ed: float, d: float, a_cs: float) -> float: def tau_Rdc_0( - gamma_v: float, rho_l: float, f_ck: float, d: float, d_g: float + gamma_v: float, rho_l: float, f_ck: float, d: float, d_dg: float ) -> float: """Calculate the design value of the shear stress resistance without axial force effects. @@ -337,7 +352,8 @@ def tau_Rdc_0( rho_l (float): Reinforcement ratio (unitless). f_ck (float): Characteristic compressive strength of concrete in MPa. d (float): Effective depth in mm. - d_g (float): Maximum aggregate size in mm. + d_dg (float): Size parameter describing the failure zone roughness in + mm. Returns: float: The design value of the shear stress resistance in MPa. @@ -345,18 +361,22 @@ def tau_Rdc_0( Raises: ValueError: If any of the input values are negative. """ - if gamma_v <= 0 or rho_l <= 0 or f_ck <= 0 or d <= 0 or d_g <= 0: + if gamma_v <= 0 or rho_l <= 0 or f_ck <= 0 or d <= 0 or d_dg <= 0: raise ValueError( 'All input values must be positive. ' + f'Got gamma_v={gamma_v}, rho_l={rho_l}, ' - + f'f_ck={f_ck}, d={d}, d_g={d_g}' + + f'f_ck={f_ck}, d={d}, d_dg={d_dg}' ) - return 0.66 / gamma_v * (100 * rho_l * f_ck * d_g / d) ** (1 / 3) + return 0.66 / gamma_v * (100 * rho_l * f_ck * d_dg / d) ** (1 / 3) def tau_Rdc_comp( - tau_Rdc_0: float, k1: float, sigma_cp: float, tau_Rdc_max: float + tau_Rdc_0: float, + k1: float, + sigma_cp: float, + tau_Rdc_max: float, + tau_rdc_min: float, ) -> float: """Calculate the design value of the shear stress resistance considering compressive normal forces. @@ -371,6 +391,8 @@ def tau_Rdc_comp( sigma_cp (float): Compressive stress due to axial force in MPa. tau_Rdc_max (float): Maximum design value of the shear stress resistance in MPa. + tau_rdc_min (float): Minimum design value of the shear stress + resistance in MPa. Returns: float: The design value of the shear stress resistance in MPa. @@ -378,15 +400,22 @@ def tau_Rdc_comp( Raises: ValueError: If any of the input values are negative. """ - if tau_Rdc_0 <= 0 or k1 <= 0 or sigma_cp < 0 or tau_Rdc_max <= 0: + if ( + tau_Rdc_0 <= 0 + or k1 <= 0 + or sigma_cp < 0 + or tau_Rdc_max <= 0 + or tau_rdc_min <= 0 + ): raise ValueError( 'All input values must be positive. ' + f'Got tau_Rdc_0={tau_Rdc_0}, k1={k1}, ' - + f'sigma_cp={sigma_cp}, tau_Rdc_max={tau_Rdc_max}' + + f'sigma_cp={sigma_cp}, tau_Rdc_max={tau_Rdc_max}, ' + + f'tau_rdc_min={tau_rdc_min}' ) tau_Rdc = tau_Rdc_0 - k1 * sigma_cp - return min(tau_Rdc, tau_Rdc_max) + return max(min(tau_Rdc, tau_Rdc_max), tau_rdc_min) def k1( @@ -418,13 +447,14 @@ def k1( Raises: ValueError: If any of the input values are negative. """ - if a_cs_0 <= 0 or e_p < 0 or A_c <= 0 or b_w <= 0 or z <= 0: + if a_cs_0 <= 0 or e_p < 0 or A_c <= 0 or b_w <= 0 or z <= 0 or d <= 0: raise ValueError( 'All input values must be positive.' - + f' Got a_cs_0={a_cs_0}, e_p={e_p}, A_c={A_c}, b_w={b_w}, z={z}' + + f' Got a_cs_0={a_cs_0}, e_p={e_p}, A_c={A_c}, ' + + f'b_w={b_w}, z={z}, d={d}' ) - k1 = 0.5 * a_cs_0 / (e_p + d / 3) * (A_c / (b_w * z)) + k1 = 0.5 / a_cs_0 / (e_p + d / 3) * (A_c / (b_w * z)) return min(k1, A_c * 0.18 / (b_w * z)) @@ -472,7 +502,8 @@ def d_eff_p(ds: float, As: float, dp: float, Ap: float) -> float: float: Effective depth in mm. Raises: - ValueError: If any of the input values are negative. + ValueError: If any of the input values are negative or if + ds*As + dp*Ap equals zero (division by zero). """ if ds < 0: raise ValueError(f'ds must not be negative. Got {ds}') @@ -483,7 +514,14 @@ def d_eff_p(ds: float, As: float, dp: float, Ap: float) -> float: if Ap < 0: raise ValueError(f'Ap must not be negative. Got {Ap}') - return (ds**2 * As + dp**2 * Ap) / (ds * As + dp * Ap) + denominator = ds * As + dp * Ap + if denominator == 0: + raise ValueError( + 'Division by zero: ds*As + dp*Ap cannot be zero. ' + + f'Got ds={ds}, As={As}, dp={dp}, Ap={Ap}' + ) + + return (ds**2 * As + dp**2 * Ap) / denominator def rho_l_p( @@ -506,7 +544,8 @@ def rho_l_p( float: Reinforcement ratio. Raises: - ValueError: If any of the input values are negative. + ValueError: If any of the input values are negative or if bw is not + positive. """ if ds < 0: raise ValueError(f'ds must not be negative. Got {ds}') @@ -516,8 +555,8 @@ def rho_l_p( raise ValueError(f'dp must not be negative. Got {dp}') if Ap < 0: raise ValueError(f'Ap must not be negative. Got {Ap}') - if bw < 0: - raise ValueError(f'bw must not be negative. Got {bw}') + if bw <= 0: + raise ValueError(f'bw must be positive. Got {bw}') if d < 0: raise ValueError(f'd must not be negative. Got {d}') @@ -534,7 +573,7 @@ def rho_l_planar( Args: vEd_y (float): Shear force in y-direction (kN). - vEd_x (float): Shear force in x-direction (kN). + vEd_x (float): Shear force in x-direction (kN) (cannot be zero). rho_l_x (float): Reinforcement ratio in x-direction. rho_l_y (float): Reinforcement ratio in y-direction. @@ -542,8 +581,8 @@ def rho_l_planar( float: Reinforcement ratio. Raises: - ValueError: If any of the input values are negative or if the shear - force ratio is not in the valid range. + ValueError: If any of the input values are negative or if vEd_x is zero + (division by zero). """ if vEd_y < 0: raise ValueError(f'vEd_y must not be negative. Got {vEd_y}') @@ -554,6 +593,12 @@ def rho_l_planar( if rho_l_y < 0: raise ValueError(f'rho_l_y must not be negative. Got {rho_l_y}') + if vEd_x == 0: + raise ValueError( + 'Division by zero: vEd_x cannot be zero for ratio calculation. ' + + f'Got vEd_x={vEd_x}' + ) + ratio = vEd_y / vEd_x if ratio <= 0.5: diff --git a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py index d2c46697..45db5daa 100644 --- a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py +++ b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py @@ -16,7 +16,7 @@ ) def test_tao_Ed(VEd, bw, d, expected): """Test shear_stress_linear_members.""" - assert _section_8_2_shear.tao_Ed(VEd, bw, d) == pytest.approx(expected) + assert _section_8_2_shear.tau_Ed(VEd, bw, d) == pytest.approx(expected) @pytest.mark.parametrize( @@ -27,10 +27,10 @@ def test_tao_Ed(VEd, bw, d, expected): (100.0, -0.3, -0.5), ], ) -def test_tao_Ed_value_errors(VEd, bw, d): +def test_tau_Ed_value_errors(VEd, bw, d): """Test tao_Ed raises ValueError for negative bw or d.""" with pytest.raises(ValueError): - _section_8_2_shear.tao_Ed(VEd, bw, d) + _section_8_2_shear.tau_Ed(VEd, bw, d) @pytest.mark.parametrize( @@ -44,7 +44,7 @@ def test_tao_Ed_value_errors(VEd, bw, d): ) def test_tao_Ed_planar_valid(vEd, d, expected): """Test tao_Ed_planar with valid inputs.""" - assert _section_8_2_shear.tao_Ed_planar(vEd, d) == pytest.approx( + assert _section_8_2_shear.tau_Ed_planar(vEd, d) == pytest.approx( expected, rel=1e-9 ) @@ -60,7 +60,22 @@ def test_tao_Ed_planar_valid(vEd, d, expected): def test_tao_Ed_planar_invalid_depth(vEd, d): """Test tao_Ed_planar raises ValueError for negative d.""" with pytest.raises(ValueError): - _section_8_2_shear.tao_Ed_planar(vEd, d) + _section_8_2_shear.tau_Ed_planar(vEd, d) + + +@pytest.mark.parametrize( + 'f_ck, d_lower, expected', + [ + (30, 20, 36), # f_ck <= 60: min(16 + 20, 40) = 36 + (40, 25, 40), # f_ck <= 60: min(16 + 25, 40) = 40 + (70, 30, 38.0204), # f_ck > 60: min(16 + 30 * (60/70)^2, 40) = 38.0204 + (80, 20, 27.25), # f_ck > 60: min(16 + 20 * (60/80)^2, 40) = 27.25 + ], +) +def test_d_dg(f_ck, d_lower, expected): + """Test the d_dg function with example values.""" + result = _section_8_2_shear.d_dg(f_ck, d_lower) + assert result == pytest.approx(expected, rel=1e-3) @pytest.mark.parametrize( @@ -73,7 +88,8 @@ def test_tao_Ed_planar_invalid_depth(vEd, d): ) def test_tau_rdc_min(gamma_v, f_ck, f_yd, d, d_lower, expected): """Test the calculate_tau_rdc_min function with example values.""" - result = _section_8_2_shear.tau_rdc_min(gamma_v, f_ck, f_yd, d, d_lower) + d_dg = _section_8_2_shear.d_dg(f_ck, d_lower) + result = _section_8_2_shear.tau_rdc_min(gamma_v, f_ck, f_yd, d, d_dg) assert result == pytest.approx(expected, rel=1e-4) @@ -120,7 +136,7 @@ def test_d_eff(dx, dy, vEd_x, vEd_y, expected): def test_ed_eff_with_angle(dx, dy, vEd_x, vEd_y, expected): """Test calculation of effective depth based on angle alpha_v.""" assert math.isclose( - _section_8_2_shear.d_eff_with_angle(dx, dy, vEd_x, vEd_y), + _section_8_2_shear.d_eff_angle(dx, dy, vEd_x, vEd_y), expected, rel_tol=1e-9, ) @@ -137,11 +153,11 @@ def test_ed_eff_with_angle(dx, dy, vEd_x, vEd_y, expected): def test_d_eff_with_angle_value_errors(dx, dy, vEd_x, vEd_y): """Test d_eff_with_angle raises ValueError for negative dx or dy.""" with pytest.raises(ValueError): - _section_8_2_shear.d_eff_with_angle(dx, dy, vEd_x, vEd_y) + _section_8_2_shear.d_eff_angle(dx, dy, vEd_x, vEd_y) @pytest.mark.parametrize( - 'gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min, expected', + 'gamma_v, rho_l, f_ck, d, d_dg, tau_rdc_min, expected', [ (1.5, 0.02, 30, 500, 16, 0.3, 0.5468), (1.4, 0.03, 40, 450, 20, 0.5, 0.82366), @@ -149,11 +165,11 @@ def test_d_eff_with_angle_value_errors(dx, dy, vEd_x, vEd_y): ], ) def test_calculate_tau_Rdc( - gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min, expected + gamma_v, rho_l, f_ck, d, d_dg, tau_rdc_min, expected ): """Test the calculation of the shear stress resistance.""" result = _section_8_2_shear.tau_Rdc( - gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min + gamma_v, rho_l, f_ck, d, d_dg, tau_rdc_min ) assert math.isclose( result, @@ -163,19 +179,32 @@ def test_calculate_tau_Rdc( @pytest.mark.parametrize( - 'gamma_v, f_ck, f_yd, d, d_lower', + 'f_ck, d_lower', + [ + (-30, 20), + (30, -20), + ], +) +def test_d_dg_value_errors(f_ck, d_lower): + """Test d_dg raises ValueError for negative arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.d_dg(f_ck, d_lower) + + +@pytest.mark.parametrize( + 'gamma_v, f_ck, f_yd, d, d_dg', [ - (-1.0, 30, 500, 500, 20), - (1.4, -30, 500, 500, 20), - (1.4, 30, -500, 500, 20), - (1.4, 30, 500, -500, 20), - (1.4, 30, 500, 500, -20), + (-1.0, 30, 500, 500, 36), + (1.4, -30, 500, 500, 36), + (1.4, 30, -500, 500, 36), + (1.4, 30, 500, -500, 36), + (1.4, 30, 500, 500, -36), ], ) -def test_tau_rdc_min_value_errors(gamma_v, f_ck, f_yd, d, d_lower): +def test_tau_rdc_min_value_errors(gamma_v, f_ck, f_yd, d, d_dg): """Test tau_rdc_min raises ValueError for negative arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.tau_rdc_min(gamma_v, f_ck, f_yd, d, d_lower) + _section_8_2_shear.tau_rdc_min(gamma_v, f_ck, f_yd, d, d_dg) @pytest.mark.parametrize( @@ -211,38 +240,63 @@ def test_rho_l_value_errors(A_sl, b_w, d): @pytest.mark.parametrize( - 'vEd_x, vEd_y', - [ - (-1.0, 4.0), # Negative x-direction - (3.0, -2.0), # Negative y-direction - (-5.0, -6.0), # Both negative - ], -) -def test_v_Ed_value_errors(vEd_x, vEd_y): - """Test v_Ed raises ValueError for negative vEd_x or vEd_y.""" - with pytest.raises(ValueError): - _section_8_2_shear.v_Ed(vEd_x, vEd_y) - - -@pytest.mark.parametrize( - 'gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min', + 'gamma_v, rho_l, f_ck, d, d_dg, tau_rdc_min', [ (-1.0, 0.02, 30, 500, 16, 0.3), # Negative gamma_v (1.5, -0.02, 30, 500, 16, 0.3), # Negative rho_l (1.5, 0.02, -30, 500, 16, 0.3), # Negative f_ck (1.5, 0.02, 30, -500, 16, 0.3), # Negative d - (1.5, 0.02, 30, 500, -16, 0.3), # Negative d_g + (1.5, 0.02, 30, 500, -16, 0.3), # Negative d_dg (0.0, 0.02, 30, 500, 16, 0.3), # Zero gamma_v - (1.5, 0.0, 30, 500, 16, 0.3), # Zero rho_l - (1.5, 0.02, 0.0, 500, 16, 0.3), # Zero f_ck (1.5, 0.02, 30, 0.0, 16, 0.3), # Zero d - (1.5, 0.02, 30, 500, 0.0, 0.3), # Zero d_g ], ) -def test_tau_Rdc_value_errors(gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min): - """Test tau_Rdc raises ValueError for non-positive arguments.""" +def test_tau_Rdc_value_errors(gamma_v, rho_l, f_ck, d, d_dg, tau_rdc_min): + """Test tau_Rdc raises ValueError for negative values or zero gamma_v/d.""" with pytest.raises(ValueError): - _section_8_2_shear.tau_Rdc(gamma_v, rho_l, f_ck, d, d_g, tau_rdc_min) + _section_8_2_shear.tau_Rdc(gamma_v, rho_l, f_ck, d, d_dg, tau_rdc_min) + + +@pytest.mark.parametrize( + 'gamma_v, rho_l, f_ck, d, d_dg, tau_rdc_min, expected', + [ + ( + 1.5, + 0.0, + 30, + 500, + 16, + 0.3, + 0.3, + ), # Zero rho_l - should return tau_rdc_min + ( + 1.5, + 0.02, + 0.0, + 500, + 16, + 0.3, + 0.3, + ), # Zero f_ck - should return tau_rdc_min + ( + 1.5, + 0.02, + 30, + 500, + 0.0, + 0.3, + 0.3, + ), # Zero d_dg - should return tau_rdc_min + ], +) +def test_tau_Rdc_zero_values( + gamma_v, rho_l, f_ck, d, d_dg, tau_rdc_min, expected +): + """Test tau_Rdc handles zero values correctly (should not raise errors).""" + result = _section_8_2_shear.tau_Rdc( + gamma_v, rho_l, f_ck, d, d_dg, tau_rdc_min + ) + assert result == pytest.approx(expected, rel=1e-3) @pytest.mark.parametrize( @@ -349,16 +403,16 @@ def test_k_vp_value_errors(N_Ed, V_Ed, d, a_cs): @pytest.mark.parametrize( - 'gamma_v, rho_l, f_ck, d, d_g, expected', + 'gamma_v, rho_l, f_ck, d, d_dg, expected', [ (1.5, 0.02, 30, 500, 16, 0.5468), (1.4, 0.03, 40, 450, 20, 0.8236), (1.6, 0.025, 35, 600, 18, 0.5690), ], ) -def test_calculate_tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_g, expected): +def test_calculate_tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_dg, expected): """Test the sh stress resistance wo/ axial force effects.""" - result = _section_8_2_shear.tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_g) + result = _section_8_2_shear.tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_dg) assert math.isclose( result, expected, @@ -367,7 +421,7 @@ def test_calculate_tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_g, expected): @pytest.mark.parametrize( - 'gamma_v, rho_l, f_ck, d, d_g', + 'gamma_v, rho_l, f_ck, d, d_dg', [ (0, 0.02, 30, 500, 16), # gamma_v zero (-1, 0.02, 30, 500, 16), # gamma_v negative @@ -377,59 +431,69 @@ def test_calculate_tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_g, expected): (1.5, 0.02, -30, 500, 16), # f_ck negative (1.5, 0.02, 30, 0, 16), # d zero (1.5, 0.02, 30, -500, 16), # d negative - (1.5, 0.02, 30, 500, 0), # d_g zero - (1.5, 0.02, 30, 500, -16), # d_g negative + (1.5, 0.02, 30, 500, 0), # d_dg zero + (1.5, 0.02, 30, 500, -16), # d_dg negative ], ) -def test_tau_Rdc_0_value_errors(gamma_v, rho_l, f_ck, d, d_g): +def test_tau_Rdc_0_value_errors(gamma_v, rho_l, f_ck, d, d_dg): """Test tau_Rdc_0 raises ValueError for non-positive arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_g) + _section_8_2_shear.tau_Rdc_0(gamma_v, rho_l, f_ck, d, d_dg) @pytest.mark.parametrize( - 'tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, expected', + 'tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, tau_rdc_min, expected', [ - (1, 0.5, 0.1, 2, 0.95), - (1, 0.6, 0.2, 2, 0.88), - (1, 0.4, 0.3, 2, 0.88), + (1, 0.5, 0.1, 2, 0.3, 0.95), + (1, 0.6, 0.2, 2, 0.3, 0.88), + (1, 0.4, 0.3, 2, 0.3, 0.88), + (1, 0.5, 0.1, 0.8, 0.3, 0.8), # Limited by tau_Rdc_max + (1, 0.5, 0.5, 2, 0.8, 0.8), # Limited by tau_rdc_min ], ) def test_calculate_tau_Rdc_comp( - tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, expected + tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, tau_rdc_min, expected ): """Test the calculation of the shear considering comp normal forces.""" assert math.isclose( - _section_8_2_shear.tau_Rdc_comp(tau_Rdc_0, k1, sigma_cp, tau_Rdc_max), + _section_8_2_shear.tau_Rdc_comp( + tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, tau_rdc_min + ), expected, rel_tol=1e-5, ) @pytest.mark.parametrize( - 'tau_Rdc_0, k1, sigma_cp, tau_Rdc_max', + 'tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, tau_rdc_min', [ - (0, 0.5, 0.1, 2), # tau_Rdc_0 zero - (-1, 0.5, 0.1, 2), # tau_Rdc_0 negative - (1, 0, 0.1, 2), # k1 zero - (1, -0.5, 0.1, 2), # k1 negative - (1, 0.5, -0.1, 2), # sigma_cp negative - (1, 0.5, 0.1, 0), # tau_Rdc_max zero - (1, 0.5, 0.1, -2), # tau_Rdc_max negative + (0, 0.5, 0.1, 2, 0.3), # tau_Rdc_0 zero + (-1, 0.5, 0.1, 2, 0.3), # tau_Rdc_0 negative + (1, 0, 0.1, 2, 0.3), # k1 zero + (1, -0.5, 0.1, 2, 0.3), # k1 negative + (1, 0.5, -0.1, 2, 0.3), # sigma_cp negative + (1, 0.5, 0.1, 0, 0.3), # tau_Rdc_max zero + (1, 0.5, 0.1, -2, 0.3), # tau_Rdc_max negative + (1, 0.5, 0.1, 2, 0), # tau_rdc_min zero + (1, 0.5, 0.1, 2, -0.3), # tau_rdc_min negative ], ) -def test_tau_Rdc_comp_value_errors(tau_Rdc_0, k1, sigma_cp, tau_Rdc_max): +def test_tau_Rdc_comp_value_errors( + tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, tau_rdc_min +): """Test tau_Rdc_comp raises ValueError.""" with pytest.raises(ValueError): - _section_8_2_shear.tau_Rdc_comp(tau_Rdc_0, k1, sigma_cp, tau_Rdc_max) + _section_8_2_shear.tau_Rdc_comp( + tau_Rdc_0, k1, sigma_cp, tau_Rdc_max, tau_rdc_min + ) @pytest.mark.parametrize( 'a_cs_0, e_p, A_c, b_w, z, d, expected', [ - (1000, 50, 10000, 200, 500, 200, 0.018), - (1200, 60, 12000, 250, 600, 200, 0.0144), - (1100, 55, 11000, 220, 550, 200, 0.01636), + (1000, 50, 10000, 200, 500, 200, 0.000000429), + (1200, 60, 12000, 250, 600, 200, 0.000000263), + (1100, 55, 11000, 220, 550, 200, 0.0000003396), ], ) def test_calculate_k1(a_cs_0, e_p, A_c, b_w, z, d, expected): @@ -450,6 +514,8 @@ def test_calculate_k1(a_cs_0, e_p, A_c, b_w, z, d, expected): (1000, 50, 10000, -200, 500, 200), # b_w negative (1000, 50, 10000, 200, 0, 200), # z zero (1000, 50, 10000, 200, -500, 200), # z negative + (1000, 50, 10000, 200, 500, 0), # d zero + (1000, 50, 10000, 200, 500, -200), # d negative ], ) def test_k1_value_errors(a_cs_0, e_p, A_c, b_w, z, d): @@ -520,10 +586,15 @@ def test_d_eff_p(ds, As, dp, Ap, expected): (500, 2000, -600, 1500), # Negative dp (500, 2000, 600, -1500), # Negative Ap (-1, -2000, -600, -1500), # All negative + (0, 0, 0, 0), # All zero (division by zero) + (500, 0, 600, 0), # As and Ap zero (division by zero) + (0, 2000, 0, 1500), # ds and dp zero (division by zero) ], ) def test_d_eff_p_value_errors(ds, As, dp, Ap): - """Test that d_eff_p raises ValueError for negative arguments.""" + """Test that d_eff_p raises ValueError for negative arguments or + division by zero. + """ with pytest.raises(ValueError): _section_8_2_shear.d_eff_p(ds, As, dp, Ap) @@ -553,11 +624,12 @@ def test_calculate_reinforcement_ratio(ds, As, dp, Ap, bw, d, expected): (500, 2000, -600, 1500, 300, 545.45), # Negative dp (500, 2000, 600, -1500, 300, 545.45), # Negative Ap (500, 2000, 600, 1500, -300, 545.45), # Negative bw + (500, 2000, 600, 1500, 0, 545.45), # Zero bw (500, 2000, 600, 1500, 300, -545.45), # Negative d ], ) def test_rho_l_p_value_errors(ds, As, dp, Ap, bw, d): - """Test that rho_l_p raises ValueError for negative arguments.""" + """Test that rho_l_p raises ValueError for neg arguments or zero bw.""" with pytest.raises(ValueError): _section_8_2_shear.rho_l_p(ds, As, dp, Ap, bw, d) @@ -587,10 +659,11 @@ def test_rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y, expected): (20, -40, 0.005, 0.008), # Negative vEd_x (20, 40, -0.005, 0.008), # Negative rho_l_x (20, 40, 0.005, -0.008), # Negative rho_l_y + (20, 0, 0.005, 0.008), # vEd_x zero (division by zero) ], ) def test_rho_l_planar_value_errors(vEd_y, vEd_x, rho_l_x, rho_l_y): - """Test that rho_l_planar raises ValueError for negative arguments.""" + """Test that rho_l_planar raises ValueError for invalid inputs.""" with pytest.raises(ValueError): _section_8_2_shear.rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y) From c850c02a9991b509ad02ca0e46c7ed9eed4f527a Mon Sep 17 00:00:00 2001 From: DanielGMorenaFhecor Date: Thu, 6 Nov 2025 13:07:32 +0100 Subject: [PATCH 28/28] feat: improved _section_8_2_shear functions and testing --- structuralcodes/codes/ec2_2023/__init__.py | 57 +- .../codes/ec2_2023/_section_8_2_shear.py | 1269 ++++++++++++---- .../test_ec2_2023_section_8_2_shear.py | 1343 +++++++++++++---- 3 files changed, 2027 insertions(+), 642 deletions(-) diff --git a/structuralcodes/codes/ec2_2023/__init__.py b/structuralcodes/codes/ec2_2023/__init__.py index 8ef01c19..3905b97e 100644 --- a/structuralcodes/codes/ec2_2023/__init__.py +++ b/structuralcodes/codes/ec2_2023/__init__.py @@ -51,18 +51,25 @@ wk_cal2, ) from ._section_8_2_shear import ( - Asf_flang, - Ast_min_flang, + Asf_flange, Fcd, Ftd, + Ftd_max, Nvd, + NVds_inclined, a_cs, a_v, + as_min, bw_nom, - cot_theta, + check_tau_Ed_flange_verification, + cot_theta_inclined, + cot_theta_max_shear_constant_nu, + cot_theta_max_shear_variable_nu, cot_theta_min, + cot_theta_simultaneous, cv1, cv2, + d_dg, d_eff, d_eff_angle, d_eff_p, @@ -84,35 +91,39 @@ rho_l_planar, rho_w, sigma_cd, - sigma_cd_flang, - sigma_cd_s, + sigma_cd_flange, + sigma_cd_inclined, sigma_swd, - sigma_swd_v2, - tao_Ed_flang, - tao_Rd_m, + sigma_swd_inclined, tau_Ed, + tau_Ed_flange, tau_Ed_planar, tau_Edi, tau_Edi_composite, tau_Rd, tau_rd, - tau_rd_incl, + tau_Rd_inclined, tau_Rd_sy, - tau_rd_sy, + tau_Rd_sy_inclined, tau_Rdc, tau_Rdc_0, tau_Rdc_comp, tau_Rdc_max, tau_rdc_min, tau_Rdi, - tau_Rdi_ny, + tau_Rdi_no_yielding, + tau_Rdm, v_Ed, ) __all__ = [ + 'as_min', + 'check_tau_Ed_flange_verification', + 'd_dg', + 'NVds_inclined', 'cv1', 'kdowel', - 'tau_Rdi_ny', + 'tau_Rdi_no_yielding', 'cv2', 'kv', 'mu_v', @@ -120,15 +131,14 @@ 'tau_Rdi', 'tau_Edi_composite', 'eps_x_flang', - 'tao_Ed_flang', - 'Ast_min_flang', - 'Asf_flang', - 'sigma_cd_flang', - 'tao_Rd_m', + 'tau_Ed_flange', + 'Asf_flange', + 'sigma_cd_flange', + 'tau_Rdm', 'sigma_cd_s', - 'sigma_swd_v2', - 'tau_rd_sy', - 'tau_rd_incl', + 'sigma_swd_inclined', + 'tau_Rd_sy_inclined', + 'tau_Rd_inclined', 'delta_MEd', 'tau_rd', 'sigma_swd', @@ -136,8 +146,10 @@ 'Fcd', 'Nvd', 'Ftd', + 'Ftd_max', 'd_eff_p', 'sigma_cd', + 'sigma_cd_inclined', 'rho_l_planar', 'rho_l_p', 'epsilon_x', @@ -145,9 +157,12 @@ 'epsilon_xc_tens', 'epsilon_xt', 'nu', + 'cot_theta_inclined', + 'cot_theta_max_shear_constant_nu', + 'cot_theta_max_shear_variable_nu', 'cot_theta_min', 'rho_w', - 'cot_theta', + 'cot_theta_simultaneous', 'tau_Rd', 'tau_Rd_sy', 'a_cs', diff --git a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py index a5e23276..f7c2990a 100644 --- a/structuralcodes/codes/ec2_2023/_section_8_2_shear.py +++ b/structuralcodes/codes/ec2_2023/_section_8_2_shear.py @@ -1,7 +1,7 @@ """Functions from Section 8.2 of EN 1992-1-1:2023.""" import math -from typing import List, Literal +from typing import Iterable, Literal def tau_Ed(VEd: float, bw: float, d: float) -> float: @@ -196,7 +196,11 @@ def d_eff_angle(dx: float, dy: float, vEd_x: float, vEd_y: float) -> float: vEd_x = abs(vEd_x) vEd_y = abs(vEd_y) - alpha_v = math.atan2(vEd_y, vEd_x) + if vEd_x == 0: + # When vEd_x = 0, alpha_v = 90 degrees, return dy + return dy + + alpha_v = math.atan(vEd_y / vEd_x) return dx * math.cos(alpha_v) ** 2 + dy * math.sin(alpha_v) ** 2 @@ -573,7 +577,7 @@ def rho_l_planar( Args: vEd_y (float): Shear force in y-direction (kN). - vEd_x (float): Shear force in x-direction (kN) (cannot be zero). + vEd_x (float): Shear force in x-direction (kN). rho_l_x (float): Reinforcement ratio in x-direction. rho_l_y (float): Reinforcement ratio in y-direction. @@ -581,8 +585,7 @@ def rho_l_planar( float: Reinforcement ratio. Raises: - ValueError: If any of the input values are negative or if vEd_x is zero - (division by zero). + ValueError: If any of the input values are negative. """ if vEd_y < 0: raise ValueError(f'vEd_y must not be negative. Got {vEd_y}') @@ -593,13 +596,7 @@ def rho_l_planar( if rho_l_y < 0: raise ValueError(f'rho_l_y must not be negative. Got {rho_l_y}') - if vEd_x == 0: - raise ValueError( - 'Division by zero: vEd_x cannot be zero for ratio calculation. ' - + f'Got vEd_x={vEd_x}' - ) - - ratio = vEd_y / vEd_x + ratio = vEd_y / vEd_x if vEd_x != 0 else float('inf') if ratio <= 0.5: rho_l = rho_l_x @@ -619,40 +616,84 @@ def cot_theta_min( VEd: float, x: float, d: float, + Ac: float, + apply_ductility_class_a_reduction: bool = False, ) -> float: """Calculate the minimum cotangent of the compression field inclination - angle, thetamin, according to the conditions provided. + angle, theta_min, according to the conditions provided. EN1992-1-1:2023 Eq. (8.41). Args: - NEd (float): Axial force in the member in kN. - VEd (float): Shear force in the member in kN. - x (float): Depth of the compression chord in mm. - d (float): Effective depth of the member in mm. + NEd (float): Axial force in the member in kN (positive for tension, + negative for compression). + VEd (float): Shear force in the member in kN (can be positive or + negative). + x (float): Depth of the compression chord in mm (must be non-negative). + d (float): Effective depth of the member in mm (must be positive). + Ac (float): Area of concrete cross-section in mm2 (must be positive). + apply_ductility_class_a_reduction (bool, optional): If True, applies + 20% reduction for ductility class A reinforcement. Default is False Returns: float: Minimum cotangent of the compression field inclination angle. + - For tension (NEd > 0): 2.5 - 0.1*NEd/|VEd| (min 1.0) + - For compression (NEd < 0) with significant stress and x < 0.25d: + 3.0 (with interpolation from 2.5 at 0 MPa to 3.0 at 3 MPa) + - For compression (NEd < 0) with x >= 0.25d: tension formula + - For no axial force (NEd = 0): 2.5 + - For ductility class A: values reduced by 20% (when flag is True) Raises: - ValueError: If any of the dimensions or forces are negative, or if d is - zero. + ValueError: If d or Ac is not positive, or if x is negative, or if + VEd is zero when NEd is not zero (to avoid division by zero). """ if d <= 0 or x < 0: raise ValueError( - 'Dimensions and forces must be positive, and d must not be zero.' + 'd must be positive and x must not be negative. ' + + f'Got d={d}, x={x}' ) + if Ac <= 0: + raise ValueError(f'Ac must be positive. Got {Ac}') + # Check VEd is not zero when needed for division + if VEd == 0 and NEd != 0: + raise ValueError( + 'VEd must not be zero when NEd is not zero to avoid division ' + + f'by zero. Got VEd={VEd}, NEd={NEd}' + ) + + # Calculate base cot_theta_min if NEd > 0: - cot_theta_min_value = 2.5 - 0.1 * NEd / abs(VEd) - return max(cot_theta_min_value, 1.0) - if NEd < 0: - return 3.0 + # Tension case + cot_theta_min = 2.5 - 0.1 * NEd / abs(VEd) + cot_theta_min = max(cot_theta_min, 1.0) + elif NEd < 0: + # Compression case + sigma_c = abs(NEd) * 1000 / Ac # Convert to MPa + if sigma_c >= 0.0 and x < 0.25 * d: + # Significant compressive stress and x < 0.25d + # Interpolate: 2.5 at 0 MPa, 3.0 at 3 MPa and above + # Formula: 2.5 + (3.0-2.5) * sigma_c / (3.0-0.0) + cot_theta_min = 3.0 if sigma_c >= 3.0 else 2.5 + sigma_c / 6.0 + else: + # Use tension formula for other compression cases + cot_theta_min = 2.5 - 0.1 * NEd / abs(VEd) + cot_theta_min = max(cot_theta_min, 1.0) + else: + # NEd == 0 (no axial force) + cot_theta_min = 2.5 - return 2.5 # NEd == 0 + # Apply ductility class A reduction (20% reduction) + if apply_ductility_class_a_reduction: + cot_theta_min *= 0.8 + return cot_theta_min -def tau_Rd_sy(rho_w: float, fywd: float, cot_theta: float) -> float: + +def tau_Rd_sy( + rho_w: float, fywd: float, cot_theta: float, cot_theta_min: float +) -> float: """Calculate the shear stress resistance of yielding shear reinforcement. EN1992-1-1:2023 Eq. (8.42). @@ -660,17 +701,31 @@ def tau_Rd_sy(rho_w: float, fywd: float, cot_theta: float) -> float: Args: rho_w (float): Shear reinforcement ratio (unitless). fywd (float): Design yield strength of the shear reinforcement in MPa. - cot_theta (float): Cotangent of the angle of the compression field. + cot_theta (float): Cotangent of the angle of the compression field + (must be between 1 and cot_theta_min). + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). Returns: float: Shear stress resistance in MPa. Raises: - ValueError: If rho_w or fywd is negative. + ValueError: If rho_w or fywd is not positive, if cot_theta_min is less + than 1, or if cot_theta is not within [1, cot_theta_min]. """ - if rho_w < 0 or fywd < 0: + if rho_w <= 0 or fywd <= 0: + raise ValueError( + 'Shear reinforcement ratio and yield strength must be positive. ' + + f'Got rho_w={rho_w}, fywd={fywd}' + ) + + if cot_theta_min < 1.0: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + + if cot_theta < 1.0 or cot_theta > cot_theta_min: raise ValueError( - 'Shear reinforcement ratio and yield strength must not be negative' + 'cot_theta must be between 1 and cot_theta_min. ' + + f'Got cot_theta={cot_theta}, cot_theta_min={cot_theta_min}' ) return rho_w * fywd * cot_theta @@ -700,45 +755,80 @@ def rho_w(Asw: float, bw: float, s: float) -> float: def sigma_cd( - tau_Ed: float, cot_theta: float, tan_theta: float, nu: float, f_cd: float + tau_Ed: float, + cot_theta: float, + cot_theta_min: float, + nu: float, + f_cd: float, ) -> float: """Calculate the stress in the compression field sigma_cd and verify it. EN1992-1-1:2023 Eq. (8.44). Args: - tau_Ed (float): Design value of the shear stress in MPa. - cot_theta (float): Cotangent of the angle of the compression field. - tan_theta (float): Tangent of the angle of the compression field. - nu (float): Coefficient (usually 0.5 as per the note). - f_cd (float): Design value of the concrete compressive strength in MPa. + tau_Ed (float): Design value of the shear stress in MPa (must be + non-negative). + cot_theta (float): Cotangent of the angle of the compression field + (must be between 1 and cot_theta_min). + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). + nu (float): Coefficient (usually 0.5 as per the note, must be + non-negative). + f_cd (float): Design value of the concrete compressive strength in MPa + (must be positive). Returns: float: Stress in the compression field sigma_cd in MPa. Raises: - ValueError: If any of the parameters are negative. + ValueError: If tau_Ed is negative, if nu is negative or f_cd is not + positive, if cot_theta_min < 1, or if cot_theta is not within + [1, cot_theta_min]. """ - if tau_Ed < 0 or cot_theta < 0 or tan_theta < 0 or nu < 0 or f_cd < 0: - raise ValueError('All parameters must be positive.') + if tau_Ed < 0: + raise ValueError(f'tau_Ed must not be negative. Got {tau_Ed}') + + if nu < 0 or f_cd <= 0: + raise ValueError( + 'nu must be non-negative and f_cd must be positive. ' + + f'Got nu={nu}, f_cd={f_cd}' + ) + + if cot_theta_min < 1.0: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + if cot_theta < 1.0 or cot_theta > cot_theta_min: + raise ValueError( + 'cot_theta must be between 1 and cot_theta_min. ' + + f'Got cot_theta={cot_theta}, cot_theta_min={cot_theta_min}' + ) + + # Calculate tan_theta from cot_theta to ensure consistency + tan_theta = 1.0 / cot_theta sigma_cd_value = tau_Ed * (cot_theta + tan_theta) return min(sigma_cd_value, nu * f_cd) def tau_Rd( - rho_w: float, fywd: float, cot_theta: float, nu: float, f_cd: float + rho_w: float, + fywd: float, + cot_theta: float, + cot_theta_min: float, + nu: float, + f_cd: float, ) -> float: """Calculate the shear stress resistance tau_Rd considering the simultaneous yielding of the shear reinforcement and failure of the compression field. - EN1992-1-1:2023 Eq. (8.42) and (8.44). + EN1992-1-1:2023 Eq. (8.42) and (8.44), NOTE 1. Args: rho_w (float): Shear reinforcement ratio, unitless. fywd (float): Design yield strength of the shear reinforcement in MPa. cot_theta (float): Cotangent of the angle of the compression field. + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). nu (float): Coefficient (usually 0.5 as per the note). f_cd (float): Design value of the concrete compressive strength in MPa. @@ -746,107 +836,174 @@ def tau_Rd( float: Shear stress resistance tau_Rd in MPa. Raises: - ValueError: If any of the parameters are negative. + ValueError: If any of the parameters are + negative, or if cot_theta_min < 1, + or if cot_theta is not within [1, cot_theta_min]. """ if rho_w < 0 or fywd < 0 or cot_theta < 0 or nu < 0 or f_cd < 0: raise ValueError('All parameters must be positive.') + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + if cot_theta < 1 or cot_theta > cot_theta_min: + raise ValueError( + 'cot_theta must be within [1, cot_theta_min]. ' + + f'Got cot_theta={cot_theta}, cot_theta_min={cot_theta_min}' + ) tau_Rd_value = rho_w * fywd * cot_theta return min(tau_Rd_value, nu * f_cd / 2) -def cot_theta( +def cot_theta_simultaneous( nu: float, f_cd: float, rho_w: float, fywd: float, cot_theta_min: float ) -> float: - """Calculate the cotangent of the angle of the compression field - considering the simultaneous yielding of the shear reinforcement and - failure of the compression field. + """Calculate the cotangent of the angle of the compression field that + gives simultaneous yielding of the shear reinforcement and failure of the + compression field. - EN1992-1-1:2023 Eq. (8.44). + EN1992-1-1:2023 Eq. (8.42) and (8.44), NOTE 1. + + The value is calculated by equating the shear stress resistance from + reinforcement and the compression field, then clamped to + [1, cot_theta_min]. + + According to clause (6), when the angle of the compression field is + evaluated according to this method (simultaneous failure), nu may be + adopted as 0.5. Args: - nu (float): Coefficient (usually 0.5 as per the note). + nu (float): Coefficient. According to clause (6), when the angle is + evaluated using this method (simultaneous failure), nu may be + adopted as 0.5. Otherwise, nu should be calculated according to + Eq. (8.45). f_cd (float): Design value of the concrete compressive strength in MPa. - rho_w (float): Shear reinforcement ratio, unitless. - fywd (float): Design yield strength of the shear reinforcement in MPa. - cot_theta_min (float): Value of cot_theta_min. + rho_w (float): Shear reinforcement ratio, unitless (must be positive). + fywd (float): Design yield strength of the shear reinforcement in MPa + (must be positive). + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). Returns: - float: Cotangent of the angle of the compression field. + float: Cotangent of the angle of the compression field that gives + simultaneous failure, clamped to [1, cot_theta_min]. Raises: - ValueError: If any of the parameters are negative. + ValueError: If any of the parameters are negative, if + cot_theta_min < 1, if rho_w * fywd is zero (to avoid division + by zero), or if the expression under the square root is negative. """ if nu < 0 or f_cd < 0 or rho_w < 0 or fywd < 0: - raise ValueError('All parameters must be positive.') + raise ValueError( + 'All parameters must be positive. ' + + f'Got nu={nu}, f_cd={f_cd}, rho_w={rho_w}, fywd={fywd}' + ) + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + if rho_w == 0 or fywd == 0: + raise ValueError( + 'rho_w and fywd must not be zero to avoid division by zero. ' + + f'Got rho_w={rho_w}, fywd={fywd}' + ) - cot_theta_value = (nu * f_cd) / (rho_w * fywd) - 1 + # Calculate the value under the square root + sqrt_arg = (nu * f_cd) / (rho_w * fywd) - 1 + if sqrt_arg < 0: + raise ValueError( + 'Expression under square root must be non-negative. ' + + f'Got (nu * f_cd) / (rho_w * fywd) - 1 = {sqrt_arg}' + ) + cot_theta_value = math.sqrt(sqrt_arg) return min(max(cot_theta_value, 1.0), cot_theta_min) -def epsilon_xt(Ftd: float, Est: float, Ast: float) -> float: +def epsilon_xt(Ftd: float, Es: float, Ast: float) -> float: """Calculate epsilon_xt. EN1992-1-1:2023 Eq. (8.47). + Ftd and Fcd are chord forces according to Figure 8.9 and Formulae (8.51) + to (8.52). + Args: Ftd (float): Tensile force in the flexural tension chord in kN. - Est (float): Modulus of elasticity of the steel in the tension chord in + Es (float): Modulus of elasticity of the steel in the tension chord in MPa. Ast (float): Area of the longitudinal reinforcement in the flexural tension chord in mm2. Returns: float: epsilon_xt (strain). + + Raises: + ValueError: If Es is not positive, or if Ast is not positive (to avoid + division by zero). """ - if Est < 0: - raise ValueError(f'Est must not be negative. Got {Est}') - if Ast < 0: - raise ValueError(f'Ast must not be negative. Got {Ast}') - return abs(Ftd) * 1000 / (Est * Ast) + if Es <= 0: + raise ValueError(f'Es must be positive. Got Es={Es}') + if Ast <= 0: + raise ValueError(f'Ast must be positive. Got Ast={Ast}') + return abs(Ftd) * 1000 / (Es * Ast) -def epsilon_xc_comp(Fcd: float, Ecc: float, Acc: float) -> float: +def epsilon_xc_comp(Fcd: float, Ec: float, Acc: float) -> float: """Calculate epsilon_xc for compression. EN1992-1-1:2023 Eq. (8.48). + Ftd and Fcd are chord forces according to Figure 8.9 and Formulae (8.51) + to (8.52). Positive values of Fcd refer to compression in the compression + chord. + Args: Fcd (float): Compressive force in the flexural compression chord in kN. - Ecc (float): Modulus of elasticity of the concrete in the compression + Positive values refer to compression. + Ec (float): Modulus of elasticity of the concrete in the compression chord in MPa. Acc (float): Area of the flexural compression chord in mm2. Returns: float: epsilon_xc (strain). + + Raises: + ValueError: If Ec is not positive, or if Acc is not positive (to avoid + division by zero). """ - if Acc < 0: - raise ValueError(f'Acc must not be negative. Got {Acc}') - if Ecc < 0: - raise ValueError(f'Ecc must not be negative. Got {Ecc}') - return abs(Fcd) * 1000 / (Ecc * Acc) + if Ec <= 0: + raise ValueError(f'Ec must be positive. Got Ec={Ec}') + if Acc <= 0: + raise ValueError(f'Acc must be positive. Got Acc={Acc}') + return abs(Fcd) * 1000 / (Ec * Acc) -def epsilon_xc_tens(Fcd: float, Esc: float, Asc: float) -> float: +def epsilon_xc_tens(Fcd: float, Es: float, Asc: float) -> float: """Calculate epsilon_xc. EN1992-1-1:2023 Eq. (8.49). + Ftd and Fcd are chord forces according to Figure 8.9 and Formulae (8.51) + to (8.52). Positive values of Fcd refer to compression in the compression + chord. + Args: - Fcd (float): Tensile force in the flexural compression chord kN. - Esc (float): Modulus of elasticity of the steel in the compression + Fcd (float): Compressive force in the flexural compression chord in kN. + Positive values refer to compression. + Es (float): Modulus of elasticity of the steel in the compression chord in MPa. Asc (float): Area of the longitudinal reinforcement in the flexural - compression chord mm2. + compression chord in mm2. Returns: float: epsilon_xc (strain). + + Raises: + ValueError: If Es is not positive, or if Asc is not positive (to avoid + division by zero). """ - if Asc < 0: - raise ValueError(f'Acc must not be negative. Got {Asc}') - if Esc < 0: - raise ValueError(f'Esc must not be negative. Got {Esc}') - return abs(Fcd) * 1000 / (Esc * Asc) + if Es <= 0: + raise ValueError(f'Es must be positive. Got Es={Es}') + if Asc <= 0: + raise ValueError(f'Asc must be positive. Got Asc={Asc}') + return abs(Fcd) * 1000 / (Es * Asc) def epsilon_x(epsilon_xt: float, epsilon_xc: float) -> float: @@ -854,32 +1011,53 @@ def epsilon_x(epsilon_xt: float, epsilon_xc: float) -> float: EN1992-1-1:2023 Eq. (8.46). + Note on sign conventions: + - epsilon_xt: Positive values refer to tension (elongation) in the + flexural tension chord. + - epsilon_xc: Positive values refer to compression (shortening) in the + flexural compression chord. + Args: - epsilon_xt (float): Strain in the flexural tension chord. - epsilon_xc (float): Strain in the flexural compression chord. + epsilon_xt (float): Strain in the flexural tension chord. Positive + values refer to tension. + epsilon_xc (float): Strain in the flexural compression chord. Positive + values refer to compression. Returns: - float: epsilon_x (strain). + float: epsilon_x (strain), clamped to non-negative values. """ epsilon_x_value = (epsilon_xt + epsilon_xc) / 2 return max(epsilon_x_value, 0) -def nu(epsilon_x: float, cot_theta: float) -> float: +def nu(epsilon_x: float, cot_theta: float, cot_theta_min: float) -> float: """Calculate nu. EN1992-1-1:2023 Eq. (8.45). Args: epsilon_x (float): Average strain of the bottom and top chords. - cot_theta (float): cotan of the compression field inclination to the - member axis. + cot_theta (float): Cotangent of the compression field inclination to + the member axis. + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). Returns: float: nu (dimensionless factor). + + Raises: + ValueError: If epsilon_x is negative, if cot_theta_min < 1, or if + cot_theta is not within [1, cot_theta_min]. """ if epsilon_x < 0: raise ValueError(f'epsilon_x must not be negative. Got {epsilon_x}') + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + if cot_theta < 1 or cot_theta > cot_theta_min: + raise ValueError( + 'cot_theta must be within [1, cot_theta_min]. ' + + f'Got cot_theta={cot_theta}, cot_theta_min={cot_theta_min}' + ) nu_value = 1 / ( 1.0 + 110 * (epsilon_x + (epsilon_x + 0.001) * cot_theta**2) @@ -887,7 +1065,7 @@ def nu(epsilon_x: float, cot_theta: float) -> float: return min(nu_value, 1.0) -def Nvd(VEd: float, cot_theta: float) -> float: +def Nvd(VEd: float, cot_theta: float, cot_theta_min: float) -> float: """Calculate the additional tensile axial force NVd due to shear VEd. EN1992-1-1:2023 Eq. (8.50). @@ -895,10 +1073,23 @@ def Nvd(VEd: float, cot_theta: float) -> float: Args: VEd (float): Shear force in kN. cot_theta (float): Cotangent of the angle. + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). Returns: float: Additional tensile axial force NVd in kN. + + Raises: + ValueError: If cot_theta_min < 1, or if cot_theta is not within + [1, cot_theta_min]. """ + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + if cot_theta < 1 or cot_theta > cot_theta_min: + raise ValueError( + 'cot_theta must be within [1, cot_theta_min]. ' + + f'Got cot_theta={cot_theta}, cot_theta_min={cot_theta_min}' + ) return abs(VEd) * cot_theta @@ -913,18 +1104,21 @@ def Ftd( EN1992-1-1:2023 Eq. (8.51). Args: - MEd (float): Moment in kNm. - z (float): Lever arm in mm. + MEd (float): Moment in kNm. Positive values refer to moments with top + compressed. + z (float): Lever arm in mm (must be positive to avoid division by + zero). NVd (float): Additional tensile axial force in kN. - NE (float): Axial force in kN. + NE (float): Axial force in kN. Positive values refer to tension. Returns: float: Chord force Ftd in kN. Raises: - ValueError: If any input is negative. - + ValueError: If z is not positive (to avoid division by zero). """ + if z <= 0: + raise ValueError(f'z must be positive. Got z={z}') return MEd * 1000 / z + (NVd + NE) / 2 @@ -939,17 +1133,52 @@ def Fcd( EN1992-1-1:2023 Eq. (8.52). Args: - MEd (float): Moment in kNm. - z (float): Lever arm in mm. + MEd (float): Moment in kNm. Positive values refer to moments with top + compressed. + z (float): Lever arm in mm (must be positive to avoid division by + zero). NVd (float): Additional tensile axial force in kN. - NE (float): Axial force in kN. + NE (float): Axial force in kN. Positive values refer to tension. Returns: - float: Chord force Fcd in kN. + float: Chord force Fcd in kN. Positive values refer to compression. + + Raises: + ValueError: If z is not positive (to avoid division by zero). """ + if z <= 0: + raise ValueError(f'z must be positive. Got z={z}') return MEd * 1000 / z - (NVd + NE) / 2 +def Ftd_max( + MEd_max: float, + z: float, + NEd: float, +) -> float: + """Calculate the maximum chord force Ftd for cases of direct + intermediate support or concentrated loads. + + EN1992-1-1:2023 Eq. (8.53). + + Args: + MEd_max (float): Maximum moment along the member in kNm. Positive + values refer to moments with top compressed. + z (float): Lever arm in mm (must be positive to avoid division by + zero). + NEd (float): Axial force in kN. Positive values refer to tension. + + Returns: + float: Maximum chord force Ftd in kN. + + Raises: + ValueError: If z is not positive (to avoid division by zero). + """ + if z <= 0: + raise ValueError(f'z must be positive. Got z={z}') + return MEd_max * 1000 / z + NEd / 2 + + def k_duct( duct_material: Literal['steel', 'plastic'], is_grouted: bool, @@ -959,7 +1188,7 @@ def k_duct( """Calculate the k_duct coefficient based on duct material, filling, and wall thickness. - EN1992-1-1:2023 guidelines for k_duct. + EN1992-1-1:2023 8.2.3 (10). Args: duct_material (str): Material of the duct ('steel' or 'plastic'). @@ -971,17 +1200,17 @@ def k_duct( float: Coefficient k_duct. Raises: - ValueError: If wall_thickness or duct_diameter is negative. + ValueError: If wall_thickness or duct_diameter is not positive. ValueError: If duct_material is not 'steel' or 'plastic'. """ - if wall_thickness < 0: + if wall_thickness <= 0: raise ValueError( - f'Wall thickness must not be negative. Got {wall_thickness}' + f'wall_thickness must be positive. Got {wall_thickness}' ) - if duct_diameter < 0: + if duct_diameter <= 0: raise ValueError( - f'Duct diameter must not be negative. Got {duct_diameter}' + f'duct_diameter must be positive. Got {duct_diameter}' ) max_thickness = max(0.035 * duct_diameter, 2.0) @@ -1002,17 +1231,18 @@ def k_duct( def bw_nom( bw: float, - duct_diameters: List[float], + duct_diameters: Iterable[float], k_duct: float, ) -> float: - """Calculate the nominal web width considering the presence of ducts. + """Calculate the nominal web width considering the presence of ducts + when the sum of duct diameters exceeds bw/8. EN1992-1-1:2023 Eq. (8.54). Args: bw (float): Actual web width in mm. - duct_diameters (List[float]): List of duct diameters in mm (each must - be non-negative). + duct_diameters (Iterable[float]): Sequence of duct diameters in mm + (each must be non-negative). Can be a list, tuple, or numpy array. k_duct (float): Coefficient depending on the material and filling of the duct. @@ -1043,6 +1273,7 @@ def tau_rd( nu: float, f_cd: float, cot_theta: float, + cot_theta_min: float, cot_beta_incl: float, rho_w: float, f_ywd: float, @@ -1052,24 +1283,41 @@ def tau_rd( EN1992-1-1:2023 Eq. (8.55). Args: - nu (float): The factor nu (unitless). - f_cd (float): Design value of concrete compressive strength in MPa. + nu (float): The factor nu (unitless) (must be non-negative). + f_cd (float): Design value of concrete compressive strength in MPa + (must be positive). cot_theta (float): Cotangent of the inclination of compression field. + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). cot_beta_incl (float): Cotangent of the inclination of load (cot beta_incl). - rho_w (float): Reinforcement ratio rho_w. - f_ywd (float): Design yield strength of shear reinforcement in MPa. + rho_w (float): Reinforcement ratio rho_w (must be non-negative). + f_ywd (float): Design yield strength of shear reinforcement in MPa + (must be positive). Returns: float: Enhanced shear stress resistance tau_Rd in MPa. Raises: - ValueError: If any input parameter is negative where applicable. + ValueError: If nu, f_cd, rho_w, or f_ywd is negative, if f_cd or + f_ywd is zero, if cot_theta_min < 1, or if cot_theta is not + within [1, cot_theta_min]. """ - if f_cd < 0: - raise ValueError(f'f_cd must not be negative. Got {f_cd}') - if f_ywd < 0: - raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') + if nu < 0: + raise ValueError(f'nu must not be negative. Got {nu}') + if f_cd <= 0: + raise ValueError(f'f_cd must be positive. Got {f_cd}') + if rho_w < 0: + raise ValueError(f'rho_w must not be negative. Got {rho_w}') + if f_ywd <= 0: + raise ValueError(f'f_ywd must be positive. Got {f_ywd}') + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + if cot_theta < 1 or cot_theta > cot_theta_min: + raise ValueError( + 'cot_theta must be within [1, cot_theta_min]. ' + + f'Got cot_theta={cot_theta}, cot_theta_min={cot_theta_min}' + ) tau_rd_value = ( nu * f_cd * (cot_theta - cot_beta_incl) / (1 + cot_theta**2) @@ -1080,29 +1328,136 @@ def tau_rd( return min(tau_rd_value, tau_rd_max) +def cot_theta_max_shear_constant_nu( + cot_beta_incl: float, + cot_theta_min: float, +) -> float: + """Calculate the optimum cot_theta for maximum shear resistance + for the case of a constant value nu according to clause (6). + + EN1992-1-1:2023 Section 8.2, NOTE 3. + + Args: + cot_beta_incl (float): Cotangent of the inclination of load (cot + beta_incl). + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). + + Returns: + float: Optimum cotangent of the inclination of compression field, + capped at cot_theta_min. + + Raises: + ValueError: If cot_theta_min < 1 or if the calculated cot_theta < 1. + """ + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + + # Constant nu according to clause (6) + cot_theta = cot_beta_incl + math.sqrt(1 + cot_beta_incl**2) + + # Cap at cot_theta_min + cot_theta = min(cot_theta, cot_theta_min) + + # Ensure cot_theta >= 1 + if cot_theta < 1: + raise ValueError( + f'Calculated cot_theta ({cot_theta}) is less than 1. ' + + 'This indicates invalid input parameters.' + ) + + return cot_theta + + +def cot_theta_max_shear_variable_nu( + a: float, + z: float, + cot_theta_min: float, +) -> float: + """Calculate the optimum cot_theta for maximum shear resistance + for the case of a variable nu according to clause (7). + + EN1992-1-1:2023 Section 8.2, NOTE 3. + + but should not be larger than cot_theta according to 8.2.3(5), NOTE 1. + + Args: + a (float): Distance from support to concentrated load in mm + (must be positive). + z (float): Lever arm in mm (must be positive). + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). + + Returns: + float: Optimum cotangent of the inclination of compression field, + capped at cot_theta_min. + + Raises: + ValueError: If a or z is non-positive, if cot_theta_min < 1, or if + the calculated cot_theta < 1. + """ + if a <= 0: + raise ValueError(f'a must be positive. Got {a}') + if z <= 0: + raise ValueError(f'z must be positive. Got {z}') + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + + # Variable nu according to clause (7) + cot_theta = 1.3 * a / z + + # Cap at cot_theta_min + cot_theta = min(cot_theta, cot_theta_min) + + # Ensure cot_theta >= 1 + if cot_theta < 1: + raise ValueError( + f'Calculated cot_theta ({cot_theta}) is less than 1. ' + + 'This indicates invalid input parameters.' + ) + + return cot_theta + + def sigma_swd( - Es: float, eps_x: float, f_ywd: float, cot_theta: float + Es: float, + eps_x: float, + f_ywd: float, + cot_theta: float, + cot_theta_min: float, ) -> float: """Calculate the stress sigma_swd in the shear reinforcement. EN1992-1-1:2023 Eq. (8.56). Args: - Es (float): Modulus of elasticity of steel Es in MPa. + Es (float): Modulus of elasticity of steel Es in MPa (must be + positive). eps_x (float): Longitudinal strain epsilon_x. - f_ywd (float): Design yield strength of shear reinforcement in MPa. + f_ywd (float): Design yield strength of shear reinforcement in MPa + (must be positive). cot_theta (float): Cotangent of the inclination of compression field. + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). Returns: float: Stress sigma_swd in the shear reinforcement in MPa. Raises: - ValueError: If any input parameter is negative where applicable. + ValueError: If Es or f_ywd is negative or zero, if cot_theta_min < 1, + or if cot_theta is not within [1, cot_theta_min]. """ - if Es < 0: - raise ValueError(f'e_s must not be negative. Got {Es}') - if f_ywd < 0: - raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') + if Es <= 0: + raise ValueError(f'Es must be positive. Got {Es}') + if f_ywd <= 0: + raise ValueError(f'f_ywd must be positive. Got {f_ywd}') + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + if cot_theta < 1 or cot_theta > cot_theta_min: + raise ValueError( + 'cot_theta must be within [1, cot_theta_min]. ' + + f'Got cot_theta={cot_theta}, cot_theta_min={cot_theta_min}' + ) sigma_swd_value = Es * (cot_theta**2 * (eps_x + 0.001) - 0.001) @@ -1114,6 +1469,7 @@ def delta_MEd( rho_w: float, f_ywd: float, cot_theta: float, + cot_theta_min: float, z: float, b_w: float, a: float, @@ -1124,31 +1480,43 @@ def delta_MEd( EN1992-1-1:2023 Eq. (8.57). Args: - tau_ed (float): Shear stress tau_Ed in MPa (should be positive). + tau_ed (float): Shear stress tau_Ed in MPa (must be non-negative). rho_w (float): Reinforcement ratio rho_w. - f_ywd (float): Design yield strength of shear reinforcement in MPa. + f_ywd (float): Design yield strength of shear reinforcement in MPa + (must be positive). cot_theta (float): Cotangent of the inclination of compression field. - z (float): Lever arm z in mm (should be positive). - b_w (float): Width of the web bw in mm (should be positive). + cot_theta_min (float): Minimum cotangent of the compression field + inclination angle (must be >= 1). + z (float): Lever arm z in mm (must be positive). + b_w (float): Width of the web bw in mm (must be positive). a (float): Distance between the axis of the support and the - concentrated force in mm. + concentrated force in mm (must be non-negative). x (float): Distance between the support and the investigated - cross-section in mm. + cross-section in mm (must be non-negative). Returns: float: Additional moment delta MEd in kNm. Raises: - ValueError: If any input parameter is negative where applicable. + ValueError: If tau_ed, a, or x is negative, if f_ywd, z, or b_w is + non-positive, if cot_theta_min < 1, or if cot_theta is not + within [1, cot_theta_min]. """ if tau_ed < 0: raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') - if f_ywd < 0: - raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') - if z < 0: - raise ValueError(f'z must not be negative. Got {z}') - if b_w < 0: - raise ValueError(f'b_w must not be negative. Got {b_w}') + if f_ywd <= 0: + raise ValueError(f'f_ywd must be positive. Got {f_ywd}') + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + if cot_theta < 1 or cot_theta > cot_theta_min: + raise ValueError( + 'cot_theta must be within [1, cot_theta_min]. ' + + f'Got cot_theta={cot_theta}, cot_theta_min={cot_theta_min}' + ) + if z <= 0: + raise ValueError(f'z must be positive. Got {z}') + if b_w <= 0: + raise ValueError(f'b_w must be positive. Got {b_w}') if a < 0: raise ValueError(f'a must not be negative. Got {a}') if x < 0: @@ -1159,7 +1527,43 @@ def delta_MEd( ) / 1e6 # Convert to kNm -def tau_rd_sy( +def cot_theta_inclined( + cot_theta: float, + alpha_w: float, + cot_theta_min: float, +) -> float: + """Calculate and validate cot_theta for inclined shear reinforcement. + + EN1992-1-1:2023 Eq. (8.58). + + Args: + cot_theta (float): Cotangent of the inclination of compression field. + alpha_w (float): Angle of inclined shear reinforcement alpha_w in + degrees (must be in range [45, 90]). + cot_theta_min (float): Maximum value for cot_theta (must be >= 1). + + Returns: + float: Validated cot_theta value clamped to satisfy the constraint + tan(alpha_w/2) <= cot_theta <= cot_theta_min. + + Raises: + ValueError: If alpha_w is outside the range [45, 90] degrees, or if + cot_theta_min < 1. + """ + if alpha_w < 45 or alpha_w > 90: + raise ValueError( + f'alpha_w must be in the range [45, 90] degrees. Got {alpha_w}' + ) + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + + alpha_w_rad = math.radians(alpha_w) + cot_theta_min_constraint = math.tan(alpha_w_rad / 2) + + return min(max(cot_theta_min_constraint, cot_theta), cot_theta_min) + + +def tau_Rd_sy_inclined( rho_w: float, f_ywd: float, cot_theta: float, @@ -1169,38 +1573,58 @@ def tau_rd_sy( """Calculate the shear stress resistance tau_Rd,sy for inclined shear reinforcement. - EN1992-1-1:2023 Eq. (8.58), (8.59). + EN1992-1-1:2023 Eq. (8.59). Args: - rho_w (float): Reinforcement ratio rho_w. - f_ywd (float): Design yield strength of shear reinforcement in MPa. - cot_theta (float): Cotangent of the inclination of compression field in - radians (cottheta). + rho_w (float): Reinforcement ratio rho_w (must be non-negative). + f_ywd (float): Design yield strength of shear reinforcement in MPa + (must be positive, cannot be zero). + cot_theta (float): Cotangent of the inclination of compression field. + Will be clamped according to Eq. (8.58). alpha_w (float): Angle of inclined shear reinforcement alpha_w in - degrees (45 ≤ alpha_w < 90). - cot_theta_min (float): max value for cot_theta. + degrees (must be in range [45, 90]). + cot_theta_min (float): Maximum value for cot_theta (must be >= 1). Returns: float: Shear stress resistance tau_Rd,sy in MPa. Raises: - ValueError: If any input parameter is negative where applicable. + ValueError: If rho_w is negative, if f_ywd is not positive (cannot be + zero), if alpha_w is outside the range [45, 90] degrees, or if + cot_theta_min < 1. """ - if f_ywd < 0: - raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') - if alpha_w < 45 or alpha_w >= 90: + if rho_w < 0: + raise ValueError(f'rho_w must not be negative. Got {rho_w}') + if f_ywd <= 0: + raise ValueError( + f'f_ywd must be positive (cannot be zero). Got {f_ywd}' + ) + if alpha_w < 45 or alpha_w > 90: raise ValueError( - f'alpha_w must be between 45 degrees and 90 degrees. Got {alpha_w}' + f'alpha_w must be in the range [45, 90] degrees. Got {alpha_w}' ) + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + + # Validate cot_theta using Eq. (8.58) + cot_theta_validated = cot_theta_inclined(cot_theta, alpha_w, cot_theta_min) alpha_w_rad = math.radians(alpha_w) - cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) - cot_alpha_w = 1 / math.tan(alpha_w_rad) + # Handle alpha_w = 90° explicitly to avoid numerical issues with tan(90°) + if math.isclose(alpha_w, 90.0): + cot_alpha_w = 0.0 + else: + cot_alpha_w = 1 / math.tan(alpha_w_rad) - return rho_w * f_ywd * (cot_theta + cot_alpha_w) * math.sin(alpha_w_rad) + return ( + rho_w + * f_ywd + * (cot_theta_validated + cot_alpha_w) + * math.sin(alpha_w_rad) + ) -def sigma_cd_s( +def sigma_cd_inclined( tau_ed: float, cot_theta: float, alpha_w: float, @@ -1208,45 +1632,62 @@ def sigma_cd_s( f_cd: float, cot_theta_min: float, ) -> float: - """Calculate the compression stress sigma_cd. + """Calculate the compression stress sigma_cd for inclined shear + reinforcement. - EN1992-1-1:2023 Eq. (8.58), (8.60). + EN1992-1-1:2023 Eq. (8.60). Args: - tau_ed (float): Shear stress tau_Ed in MPa. - cot_theta (float): Cotangent of the inclination of compression field in - radians (cottheta). + tau_ed (float): Shear stress tau_Ed in MPa (must be non-negative). + cot_theta (float): Cotangent of the inclination of compression field. + Will be clamped according to Eq. (8.58). alpha_w (float): Angle of inclined shear reinforcement alpha_w in - degrees (45 ≤ alpha_w < 90). - nu (float): The factor nu. - f_cd (float): Design value of concrete compressive strength in MPa. - cot_theta_min (float): max value for cot_theta. + degrees (must be in range [45, 90]). + nu (float): The factor nu (must be non-negative). + f_cd (float): Design value of concrete compressive strength in MPa + (must be positive, cannot be zero). + cot_theta_min (float): Maximum value for cot_theta (must be >= 1). Returns: float: Compression stress sigma_cd in MPa. Raises: - ValueError: If any input parameter is negative where applicable. + ValueError: If tau_ed is negative, if nu is negative, if f_cd is not + positive (cannot be zero), if alpha_w is outside the range + [45, 90] degrees, or if cot_theta_min < 1. """ if tau_ed < 0: raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') - if f_cd < 0: - raise ValueError(f'f_cd must not be negative. Got {f_cd}') - if alpha_w < 45 or alpha_w >= 90: + if nu < 0: + raise ValueError(f'nu must be non-negative. Got {nu}') + if f_cd <= 0: + raise ValueError(f'f_cd must be positive (cannot be zero). Got {f_cd}') + if alpha_w < 45 or alpha_w > 90: raise ValueError( - f'alpha_w must be between 45 degrees and 90 degrees. Got {alpha_w}' + f'alpha_w must be in the range [45, 90] degrees. Got {alpha_w}' ) + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + + # Validate cot_theta using Eq. (8.58) + cot_theta_validated = cot_theta_inclined(cot_theta, alpha_w, cot_theta_min) + # Handle alpha_w = 90° explicitly to avoid numerical issues with tan(90°) + if math.isclose(alpha_w, 90.0): + cot_alpha_w = 0.0 + else: + alpha_w_rad = math.radians(alpha_w) + cot_alpha_w = 1 / math.tan(alpha_w_rad) - alpha_w_rad = math.radians(alpha_w) - cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) - cot_alpha_w = 1 / math.tan(alpha_w_rad) - - sigma_cd_value = tau_ed * (1 + cot_theta**2) / (cot_theta + cot_alpha_w) + sigma_cd_value = ( + tau_ed + * (1 + cot_theta_validated**2) + / (cot_theta_validated + cot_alpha_w) + ) return min(sigma_cd_value, nu * f_cd) -def NVds( +def NVds_inclined( VEd: float, cot_theta: float, alpha_w: float, cot_theta_min: float ) -> float: """Calculate the axial tensile force NVd. @@ -1254,34 +1695,41 @@ def NVds( EN1992-1-1:2023 Eq. (8.58), (8.61). Args: - VEd (float): Design shear force VEd in kN (should be positive). - cot_theta (float): Cotangent of the nclination of compression field in - radians (cottheta). + VEd (float): Design shear force VEd in kN. + cot_theta (float): Cotangent of the inclination of compression field. + Will be clamped according to Eq. (8.58). alpha_w (float): Angle of inclined shear reinforcement alpha_w in - degrees (45 ≤ alpha_w < 90). - cot_theta_min (float): max value for cot_theta. + degrees (must be in range [45, 90]). + cot_theta_min (float): Maximum value for cot_theta (must be >= 1). Returns: float: Axial tensile force NVd in kN. Raises: - ValueError: If v_ed is negative. + ValueError: If alpha_w is outside the range [45, 90] degrees, or if + cot_theta_min < 1. """ - if VEd < 0: - raise ValueError(f'VEd must not be negative. Got {VEd}') - if alpha_w < 45 or alpha_w >= 90: + if alpha_w < 45 or alpha_w > 90: raise ValueError( - f'alpha_w must be between 45 degrees and 90 degrees. Got {alpha_w}' + f'alpha_w must be in the range [45, 90] degrees. Got {alpha_w}' ) + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + + # Validate cot_theta using Eq. (8.58) + cot_theta_validated = cot_theta_inclined(cot_theta, alpha_w, cot_theta_min) alpha_w_rad = math.radians(alpha_w) - cot_alpha_w = 1 / math.tan(alpha_w_rad) - cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) + # Handle alpha_w = 90° explicitly to avoid numerical issues with tan(90°) + if math.isclose(alpha_w, 90.0): + cot_alpha_w = 0.0 + else: + cot_alpha_w = 1 / math.tan(alpha_w_rad) - return abs(VEd) * (cot_theta - cot_alpha_w) + return abs(VEd) * (cot_theta_validated - cot_alpha_w) -def tau_rd_incl( +def tau_Rd_inclined( nu: float, f_cd: float, cot_theta: float, @@ -1297,76 +1745,125 @@ def tau_rd_incl( EN1992-1-1:2023 Eq. (8.62). Args: - nu (float): The factor nu (unitless). - f_cd (float): Design value of concrete compressive strength in MPa. - cot_theta (float): Cotangent of the inclination of compression field in - radians. - cot_beta_incl (float): Cotangent of the inclination of load in radians - (cot beta_incl). - rho_w (float): Reinforcement ratio rho_w (unitless). - f_ywd (float): Design yield strength of shear reinforcement in MPa. + nu (float): The factor nu (unitless, must be non-negative). + f_cd (float): Design value of concrete compressive strength in MPa + (must be positive, cannot be zero). + cot_theta (float): Cotangent of the inclination of compression field. + Will be clamped according to Eq. (8.58). + cot_beta_incl (float): Cotangent of the inclination of load (cot + beta_incl). + rho_w (float): Reinforcement ratio rho_w (unitless, must be + non-negative). + f_ywd (float): Design yield strength of shear reinforcement in MPa + (must be positive, cannot be zero). alpha_w (float): Angle of inclined shear reinforcement alpha_w in - degrees (45 ≤ alpha_w < 90). - cot_theta_min (float): max value for cot_theta. + degrees (must be in range [45, 90]). + cot_theta_min (float): Maximum value for cot_theta (must be >= 1). Returns: float: Shear stress resistance tau_Rd in MPa. Raises: - ValueError: If any input parameter is negative where applicable. + ValueError: If nu is negative, if f_cd is not positive (cannot be + zero), if f_ywd is not positive (cannot be zero), if rho_w is + negative, if alpha_w is outside the range [45, 90] degrees, or + if cot_theta_min < 1. """ - if f_cd < 0: - raise ValueError(f'f_cd must not be negative. Got {f_cd}') - if f_ywd < 0: - raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') - if alpha_w < 45 or alpha_w >= 90: + if nu < 0: + raise ValueError(f'nu must be non-negative. Got {nu}') + if f_cd <= 0: + raise ValueError(f'f_cd must be positive (cannot be zero). Got {f_cd}') + if f_ywd <= 0: + raise ValueError( + f'f_ywd must be positive (cannot be zero). Got {f_ywd}' + ) + if rho_w < 0: + raise ValueError(f'rho_w must be non-negative. Got {rho_w}') + if alpha_w < 45 or alpha_w > 90: raise ValueError( - f'alpha_w must be between 45 degrees and 90 degrees. Got {alpha_w}' + f'alpha_w must be in the range [45, 90] degrees. Got {alpha_w}' ) + if cot_theta_min < 1: + raise ValueError(f'cot_theta_min must be >= 1. Got {cot_theta_min}') + + # Validate cot_theta using Eq. (8.58) + cot_theta_validated = cot_theta_inclined(cot_theta, alpha_w, cot_theta_min) alpha_w_rad = math.radians(alpha_w) - cot_theta = min(max(math.tan(alpha_w_rad / 2), cot_theta), cot_theta_min) - cot_alpha_w = 1 / math.tan(alpha_w_rad) + # Handle alpha_w = 90° explicitly to avoid numerical issues with tan(90°) + if math.isclose(alpha_w, 90.0): + cot_alpha_w = 0.0 + else: + cot_alpha_w = 1 / math.tan(alpha_w_rad) - tau_rd_value = nu * f_cd * (cot_theta - cot_beta_incl) / ( - 1 + cot_theta**2 + tau_rd_value = ( + nu + * f_cd + * (cot_theta_validated - cot_beta_incl) + / (1 + cot_theta_validated**2) ) + rho_w * f_ywd * (cot_beta_incl + cot_alpha_w) * math.sin(alpha_w_rad) - tau_rd_max = nu * f_cd * (cot_theta + cot_alpha_w) / (1 + cot_theta**2) + tau_rd_max = ( + nu + * f_cd + * (cot_theta_validated + cot_alpha_w) + / (1 + cot_theta_validated**2) + ) return min(tau_rd_value, tau_rd_max) -def sigma_swd_v2( - Es: float, eps_x: float, cot_theta: float, alpha_w: float, f_ywd: float +def sigma_swd_inclined( + Es: float, + eps_x: float, + cot_theta: float, + alpha_w: float, + f_ywd: float, ) -> float: """Calculate the stress sigma_swd in the shear reinforcement for compression field inclinations. EN1992-1-1:2023 Eq. (8.63). + Compression field inclinations with cotθ < tan(αw/2) are allowed if the + yield strength fywd in Formula (8.62) is replaced by the stress σswd + according to this formula. + Args: - Es (float): Modulus of elasticity of steel Es in MPa. + Es (float): Modulus of elasticity of steel Es in MPa (must be positive, + cannot be zero). eps_x (float): Longitudinal strain epsilon_x (unitless). - cot_theta (float): Cotangent inclination of compression field in - radians. + cot_theta (float): Cotangent inclination of compression field. alpha_w (float): Angle of inclined shear reinforcement alpha_w in - degrees (45 ≤ alpha_w < 90). - f_ywd (float): Design yield strength of shear reinforcement in MPa. + degrees (must be in range [45, 90]). + f_ywd (float): Design yield strength of shear reinforcement in MPa + (must be positive, cannot be zero). Returns: float: Stress sigma_swd in the shear reinforcement in MPa. Raises: - ValueError: If any input parameter is negative where applicable. + ValueError: If Es is not positive (cannot be zero), if f_ywd is not + positive (cannot be zero), or if alpha_w is outside the range + [45, 90] degrees. """ - if Es < 0: - raise ValueError(f'Es must not be negative. Got {Es}') - if f_ywd < 0: - raise ValueError(f'f_ywd must not be negative. Got {f_ywd}') + if Es <= 0: + raise ValueError(f'Es must be positive (cannot be zero). Got {Es}') + if f_ywd <= 0: + raise ValueError( + f'f_ywd must be positive (cannot be zero). Got {f_ywd}' + ) + if alpha_w < 45 or alpha_w > 90: + raise ValueError( + f'alpha_w must be in the range [45, 90] degrees. Got {alpha_w}' + ) alpha_w_rad = math.radians(alpha_w) - cot_alpha_w = 1 / math.tan(alpha_w_rad) + # Handle alpha_w = 90° explicitly to avoid numerical issues with tan(90°) + if math.isclose(alpha_w, 90.0): + cot_alpha_w = 0.0 + else: + cot_alpha_w = 1 / math.tan(alpha_w_rad) sigma_swd_value = Es * ( (eps_x + 0.001) @@ -1377,35 +1874,37 @@ def sigma_swd_v2( return min(sigma_swd_value, f_ywd) -def tao_Rd_m(tau_rd: float, m_ed: float, m_rd: float) -> float: +def tau_Rdm(tau_rd: float, m_ed: float, m_rd: float) -> float: """Calculate the shear stress resistance reduced by the influence of transverse bending. EN1992-1-1:2023 Eq. (8.64). Args: - tau_rd (float): Shear resistance tau_Rd in MPa. - m_ed (float): Applied transverse bending moment mEd in kNm. + tau_rd (float): Shear resistance tau_Rd in MPa (must be non-negative). + m_ed (float): Applied transverse bending moment mEd in kNm (must be + non-negative). m_rd (float): Bending resistance without interaction with shear mRd in - kNm. + kNm (must be positive, cannot be zero). Returns: float: Reduced shear stress resistance tau_Rdm in MPa. Raises: - ValueError: If any of the input parameters are negative. + ValueError: If tau_rd or m_ed is negative, or if m_rd is not positive + (cannot be zero). """ if tau_rd < 0: raise ValueError(f'tau_rd must not be negative. Got {tau_rd}') if m_ed < 0: raise ValueError(f'm_ed must not be negative. Got {m_ed}') - if m_rd < 0: - raise ValueError(f'm_rd must not be negative. Got {m_rd}') + if m_rd <= 0: + raise ValueError(f'm_rd must be positive (cannot be zero). Got {m_rd}') return tau_rd * (1 - (m_ed / m_rd)) -def tao_Ed_flang( +def tau_Ed_flange( delta_Fd: float, hf: float, delta_x: float, @@ -1416,8 +1915,8 @@ def tao_Ed_flang( EN1992-1-1:2023 Eq. (8.65). Args: - delta_Fd (float): Change of axial force in the flange over length delta - x in kN. + delta_Fd (float): Change of axial force in the flange over + the length delta x in kN. hf (float): Thickness of the flange at the junction in mm. delta_x (float): Length under consideration delta x in mm. @@ -1437,57 +1936,73 @@ def tao_Ed_flang( return delta_Fd * 1000 / (hf * delta_x) -def Ast_min_flang(tau_ed: float, sf: float, hf: float, fyd: float) -> float: - """Calculate the minimum transverse reinforcement in web/flanges. +def check_tau_Ed_flange_verification( + tau_ed: float, Ast_min: float, sf: float, hf: float, fyd: float +) -> bool: + """Check if further verification of shear between web and flanges may be + omitted. - EN1992-1-1:2023 Eq. (8.66) + EN1992-1-1:2023 Eq. (8.66). Args: - tau_ed (float): Longitudinal shear stress tau_Ed MPa. - sf (float): Spacing of reinforcement sf mm. - hf (float): Thickness of the flange at the junction mm. - fyd (float): Design yield strength of reinforcement fyd MPa. + tau_ed (float): Longitudinal shear stress tau_Ed in MPa (must be + non-negative). + Ast_min (float): Minimum transverse reinforcement according to Table + 12.1 (NDP) in mm2 (must be non-negative). + sf (float): Spacing of reinforcement sf in mm (must be positive). + hf (float): Thickness of the flange at the junction in mm (must be + positive). + fyd (float): Design yield strength of reinforcement fyd in MPa (must be + positive). Returns: - float: Minimum required transverse reinforcement area Asf in mm2. + bool: True meaning further verification of shear between + web and flanges may be omitted. False otherwise. Raises: - ValueError: If any of the input parameters are negative or - if cot_theta_f is out of the specified range. + ValueError: If tau_ed or Ast_min is negative, or if sf, hf, or fyd is + not positive. """ if tau_ed < 0: raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') + if Ast_min < 0: + raise ValueError(f'Ast_min must not be negative. Got {Ast_min}') if sf <= 0: raise ValueError(f'sf must be positive. Got {sf}') if hf <= 0: raise ValueError(f'hf must be positive. Got {hf}') - if fyd < 0: - raise ValueError(f'fyd must not be negative. Got {fyd}') + if fyd <= 0: + raise ValueError(f'fyd must be positive. Got {fyd}') - return (tau_ed * sf * hf) / (fyd) + return tau_ed <= (Ast_min / (sf * hf)) * fyd -def Asf_flang( +def Asf_flange( tau_ed: float, sf: float, hf: float, fyd: float, cot_theta_f: float ) -> float: - """Calculate the transverse reinforcement. + """Calculate the minimum transverse reinforcement in flanges. EN1992-1-1:2023 Eq. (8.69). Args: - tau_ed (float): Longitudinal shear stress tau_Ed MPa. - sf (float): Spacing of reinforcement sf mm. - hf (float): Thickness of the flange at the junction mm. - fyd (float): Design yield strength of reinforcement fyd MPa. + tau_ed (float): Longitudinal shear stress tau_Ed in MPa (must be + non-negative). + sf (float): Spacing of reinforcement sf in mm (must be positive). + hf (float): Thickness of the flange at the junction in mm (must be + positive). + fyd (float): Design yield strength of reinforcement fyd in MPa (must be + positive, cannot be zero). cot_theta_f (float): Cotangent of the inclination angle of the - compression field in the flange thetaf. + compression field in the flange thetaf (must be >= 1). For + compression flanges: 1 ≤ cot_theta_f ≤ 3.0 (8.67). + For tension flanges: 1 ≤ cot_theta_f ≤ 1.25 (8.68). Returns: float: Required transverse reinforcement area Asf in mm2. Raises: - ValueError: If any of the input parameters are negative or if - cot_theta_f is out of the specified range. + ValueError: If tau_ed is negative, if sf, hf, or fyd is not positive + (cannot be zero), or if cot_theta_f < 1. """ if tau_ed < 0: raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') @@ -1495,47 +2010,54 @@ def Asf_flang( raise ValueError(f'sf must be positive. Got {sf}') if hf <= 0: raise ValueError(f'hf must be positive. Got {hf}') - if fyd < 0: - raise ValueError(f'fyd must not be negative. Got {fyd}') + if fyd <= 0: + raise ValueError(f'fyd must be positive (cannot be zero). Got {fyd}') + if cot_theta_f < 1: + raise ValueError(f'cot_theta_f must be >= 1. Got {cot_theta_f}') return (tau_ed * sf * hf) / (fyd * cot_theta_f) -def sigma_cd_flang( +def sigma_cd_flange( tau_ed: float, - theta_f: float, + cot_theta_f: float, fcd: float, nu: float = 0.5, ) -> float: - """Computes the compression field stress in the flange. + """Calculate the compression field stress in the flange. EN1992-1-1:2023 Eq. (8.70), (8.71). Args: - tau_ed (float or int): Longitudinal shear stress tau_Ed in MPa. - theta_f (float or int): Inclination angle of the compression field in - the flange thetaf (degrees). - fcd (float or int): Design compressive strength of concrete fcd in MPa. - nu (float, optional): Strength reduction factor, default is 0.5. + tau_ed (float): Longitudinal shear stress tau_Ed in MPa (must be + non-negative). + cot_theta_f (float): Cotangent of the inclination angle of the + compression field in the flange thetaf (must be >= 1). For + compression flanges: 1 ≤ cotθf ≤ 3.0. For tension flanges: + 1 ≤ cotθf ≤ 1.25. + fcd (float): Design compressive strength of concrete fcd in MPa (must + be positive, cannot be zero). + nu (float, optional): Strength reduction factor (must be positive, + cannot be zero). Default is 0.5. Returns: - bool: The compression field stress in MPa. + float: The compression field stress in MPa. Raises: - ValueError: If any of the input parameters are - negative or if theta_f is out of the specified range. + ValueError: If tau_ed is negative, if cot_theta_f < 1, if fcd is not + positive (cannot be zero), or if nu is not positive (cannot be + zero). """ if tau_ed < 0: raise ValueError(f'tau_ed must not be negative. Got {tau_ed}') - if theta_f < 0: - raise ValueError(f'theta_f must not be negative. Got {theta_f}') - if fcd < 0: - raise ValueError(f'fcd must not be negative. Got {fcd}') + if cot_theta_f < 1: + raise ValueError(f'cot_theta_f must be >= 1. Got {cot_theta_f}') + if fcd <= 0: + raise ValueError(f'fcd must be positive (cannot be zero). Got {fcd}') if nu <= 0: - raise ValueError(f'nu must be positive. Got {nu}') + raise ValueError(f'nu must be positive (cannot be zero). Got {nu}') - theta_rad = math.radians(theta_f) - sigma_cd = tau_ed * (1 / math.tan(theta_rad) + math.tan(theta_rad)) + sigma_cd = tau_ed * (cot_theta_f + 1 / cot_theta_f) return min(sigma_cd, nu * fcd) @@ -1547,22 +2069,24 @@ def eps_x_flang(Ftd: float, Ast: float, Es: float) -> float: EN1992-1-1:2023 Eq. (8.72) Args: - Ftd (float): force in the tension chord in kN. - As (float): Longitudinal reinforcement area in mm2. - Es (float): Steel modulus of elasticity in MPa. + Ftd (float): Force in the tension chord in kN (must be positive). + Ast (float): Longitudinal reinforcement area in mm2 (must be positive). + Es (float): Steel modulus of elasticity in MPa (must be positive, + cannot be zero). Returns: float: The longitudinal strain in the tensile flange (unitless). Raises: - ValueError: If td is non-positive or if sst is non-positive. + ValueError: If Ftd is not positive, if Ast is not positive, or if Es + is not positive (cannot be zero). """ if Ftd <= 0: raise ValueError(f'Ftd must be positive. Got {Ftd}') if Ast <= 0: raise ValueError(f'Ast must be positive. Got {Ast}') if Es <= 0: - raise ValueError(f'Ast must be positive. Got {Es}') + raise ValueError(f'Es must be positive (cannot be zero). Got {Es}') return Ftd * 1000 / (Ast * Es) @@ -1573,19 +2097,22 @@ def tau_Edi(VEdi: float, Ai: float) -> float: EN1992-1-1:2023 Eq. (8.74). Args: - VEdi (float): Shear force acting parallel to the interface in kN. - Ai (float): Area of the interface in mm2. + VEdi (float): Shear force acting parallel to the interface in kN (must + be non-negative). + Ai (float): Area of the interface in mm2 (must be positive, cannot be + zero). Returns: float: Shear stress at the interface in MPa. Raises: - ValueError: If any input value is negative. + ValueError: If VEdi is negative, or if Ai is not positive (cannot be + zero). """ if VEdi < 0: raise ValueError(f'VEdi must not be negative. Got {VEdi}') - if Ai < 0: - raise ValueError(f'Ai must not be negative. Got {Ai}') + if Ai <= 0: + raise ValueError(f'Ai must be positive (cannot be zero). Got {Ai}') return VEdi * 1000 / Ai @@ -1600,26 +2127,30 @@ def tau_Edi_composite( Args: beta_new (float): Ratio of the longitudinal force in the new concrete - to the total longitudinal force, dimensionless. - VEd (float): Shear force acting perpendicular to the interface in kN. - z (float): Lever arm of the composite section in mm. - bi (float): Width of the interface in mm. + to the total longitudinal force, dimensionless (must be + non-negative). + VEd (float): Shear force acting perpendicular to the interface in kN + (must be non-negative). + z (float): Lever arm of the composite section in mm (must be positive, + cannot be zero). + bi (float): Width of the interface in mm (must be positive, cannot be + zero). Returns: float: Longitudinal shear stress at the interface in MPa. Raises: - ValueError: If any input value is negative or zero when it should not - be. + ValueError: If beta_new or VEd is negative, or if z or bi is not + positive (cannot be zero). """ if beta_new < 0: raise ValueError(f'beta_new must not be negative. Got {beta_new}') if VEd < 0: - raise ValueError(f'Ved must not be negative. Got {VEd}') + raise ValueError(f'VEd must not be negative. Got {VEd}') if z <= 0: - raise ValueError(f'z must be positive. Got {z}') - if bi < 0: - raise ValueError(f'bi must not be negative. Got {bi}') + raise ValueError(f'z must be positive (cannot be zero). Got {z}') + if bi <= 0: + raise ValueError(f'bi must be positive (cannot be zero). Got {bi}') return beta_new * VEd * 1000 / (z * bi) @@ -1634,6 +2165,7 @@ def tau_Rdi( cv1: float, mu_v: float, gamma_c: float, + fcd: float, ) -> float: """Calculate the design shear stress resistance at the interface for scenarios without reinforcement or where reinforcement is sufficiently @@ -1643,33 +2175,59 @@ def tau_Rdi( Args: fck (float): Lowest compressive strength of the concretes at the - interface in MPa. - sigma_n (float): Compressive or tensile stress over the interface area - in MPa. - Ai (float): Area of the interface in mm2. + interface in MPa (must be positive, cannot be zero). + sigma_n (float): Normal stress over the interface area in MPa. Sign + convention: positive values represent compressive stress. + Ai (float): Area of the interface in mm2 (must be positive, cannot be + zero). Asi (float): Cross-sectional area of bonded reinforcement crossing the - interface in mm2. - fyd (float): Design yield strength of the reinforcement in MPa. + interface in mm2 (must be non-negative). + fyd (float): Design yield strength of the reinforcement in MPa (must + be positive, cannot be zero). alpha_deg (float): Angle of reinforcement crossing the interface in - degrees. + degrees (must be between 35 and 135). cv1 (float): Coefficient depending on the roughness of the interface, - dimensionless. + dimensionless (must be non-negative). mu_v (float): Friction coefficient depending on the roughness of the - interface, dimensionless. - gamma_c (float): safety factory for concrete. + interface, dimensionless (must be non-negative). + gamma_c (float): Safety factor for concrete (must be positive, + cannot be zero). + fcd (float): Design compressive strength of concrete in MPa (must be + positive, cannot be zero). Calculated according to EN1992-1-1:2023 + Eq. (5.3) as fcd = eta_cc * k_tc * fck / gamma_c. Returns: float: Shear stress resistance at the interface in MPa. Raises: - ValueError: If any input value is negative or outside expected ranges. + ValueError: If fck, Ai, fyd, gamma_c, or fcd is not positive (cannot be + zero), if Asi, cv1, or mu_v is negative, or if alpha_deg is + outside the range [35, 135]. """ - if fck < 0 or Ai < 0 or Asi < 0 or fyd < 0 or gamma_c < 0: - raise ValueError('Input values must not be negative.') + if fck <= 0: + raise ValueError(f'fck must be positive (cannot be zero). Got {fck}') + if Ai <= 0: + raise ValueError(f'Ai must be positive (cannot be zero). Got {Ai}') + if Asi < 0: + raise ValueError(f'Asi must not be negative. Got {Asi}') + if fyd <= 0: + raise ValueError(f'fyd must be positive (cannot be zero). Got {fyd}') + if gamma_c <= 0: + raise ValueError( + f'gamma_c must be positive (cannot be zero). Got {gamma_c}' + ) + if fcd <= 0: + raise ValueError(f'fcd must be positive (cannot be zero). Got {fcd}') + if cv1 < 0: + raise ValueError(f'cv1 must not be negative. Got {cv1}') + if mu_v < 0: + raise ValueError(f'mu_v must not be negative. Got {mu_v}') if not (35 <= alpha_deg <= 135): - raise ValueError('Alpha must be between 35 and 135 degrees.') + raise ValueError( + f'alpha_deg must be between 35 and 135 degrees. Got {alpha_deg}' + ) - sigma_n = max(0, min(sigma_n, 0.6 * fck / gamma_c)) + sigma_n = max(0, min(sigma_n, 0.6 * fcd)) alpha_rad = math.radians(alpha_deg) rho_i = Asi / Ai @@ -1680,8 +2238,8 @@ def tau_Rdi( + rho_i * fyd * (mu_v * math.sin(alpha_rad) + math.cos(alpha_rad)) ) - # Limiting tau_rdi according to the specification - return min(tau_rdi, 0.30 * fck + rho_i * fyd * math.cos(alpha_rad)) + # Limiting tau_rdi according to EN1992-1-1:2023 Eq. (8.76) + return min(tau_rdi, 0.30 * fcd + rho_i * fyd * math.cos(alpha_rad)) def cv1( @@ -1715,6 +2273,11 @@ def cv1( 'very rough': 0.19, 'keyed': 0.37, } + if surface_roughness not in coefficients: + raise ValueError( + f'Unknown surface roughness: {surface_roughness}. ' + f'Must be one of: {list(coefficients.keys())}' + ) return coefficients[surface_roughness] @@ -1743,6 +2306,11 @@ def mu_v( 'very rough': 0.9, 'keyed': 0.9, } + if surface_roughness not in coefficients: + raise ValueError( + f'Unknown surface roughness: {surface_roughness}. ' + f'Must be one of: {list(coefficients.keys())}' + ) return coefficients[surface_roughness] @@ -1774,6 +2342,11 @@ def cv2( 'rough': 0.08, 'very rough': 0.15, } + if surface_roughness not in coefficients: + raise ValueError( + f'Unknown surface roughness: {surface_roughness}. ' + f'Must be one of: {list(coefficients.keys())}' + ) return coefficients[surface_roughness] @@ -1799,6 +2372,11 @@ def kv( 'rough': 0.5, 'very rough': 0.5, } + if surface_roughness not in coefficients: + raise ValueError( + f'Unknown surface roughness: {surface_roughness}. ' + f'Must be one of: {list(coefficients.keys())}' + ) return coefficients[surface_roughness] @@ -1824,10 +2402,15 @@ def kdowel( 'rough': 0.9, 'very rough': 0.9, } + if surface_roughness not in coefficients: + raise ValueError( + f'Unknown surface roughness: {surface_roughness}. ' + f'Must be one of: {list(coefficients.keys())}' + ) return coefficients[surface_roughness] -def tau_Rdi_ny( +def tau_Rdi_no_yielding( cv2: float, fck: float, gamma_c: float, @@ -1837,6 +2420,7 @@ def tau_Rdi_ny( rho_i: float, fyd: float, kdowel: float, + fcd: float, ) -> float: """Calculate the shear stress resistance at the interface when yielding is not ensured at the interface. @@ -1845,36 +2429,67 @@ def tau_Rdi_ny( Args: cv2 (float): Coefficient depending on the roughness of the interface - (unitless). - fck (float): Concrete compressive resistance in MPa. - gamma_c (float): Partial safety factor for concrete (unitless). - mu_v (float): Coefficient mu_v from the Eurocode (unitless). - sigma_n (float): Normal stress in the interface MPa. - kv (float): Coefficient kv from the Eurocode (unitless). - rho_i (float): Reinforcement ratio at the interface (unitless). - fyd (float): Design yield strength of reinforcement MPa. + (unitless, must be non-negative). + fck (float): Concrete compressive resistance in MPa (must be positive, + cannot be zero). + gamma_c (float): Partial safety factor for concrete (unitless, must be + positive, cannot be zero). + mu_v (float): Coefficient mu_v from the Eurocode (unitless, must be + non-negative). + sigma_n (float): Normal stress in the interface in MPa. Sign + convention: positive values represent compressive stress (acting + perpendicular to the interface, compressing the interface), + negative values represent tensile stress (acting perpendicular to + the interface, pulling apart the interface). The value is clamped + to be non-negative (tensile stresses are treated as zero). + kv (float): Coefficient kv from the Eurocode (unitless, must be + non-negative). + rho_i (float): Reinforcement ratio at the interface (unitless, must be + non-negative). + fyd (float): Design yield strength of reinforcement in MPa (must be + positive, cannot be zero). kdowel (float): Coefficient for dowel action of reinforcement - (unitless). + (unitless, must be non-negative). + fcd (float): Design compressive strength of concrete in MPa (must be + positive, cannot be zero). Calculated according to EN1992-1-1:2023 + Eq. (5.3) as fcd = eta_cc * k_tc * fck / gamma_c. Returns: - float: Shear stress resistance tau_Rdi MPa. + float: Shear stress resistance tau_Rdi in MPa. Raises: - ValueError: If any of the dimensions or resistances are negative. + ValueError: If fck, gamma_c, fcd, or fyd is not positive (cannot be + zero), or if cv2, mu_v, kv, rho_i, or kdowel is negative. """ - if any(x < 0 for x in [fck, gamma_c, kv, rho_i, fyd, kdowel]): - raise ValueError('Dimensions and resistances must not be negative.') + if fck <= 0: + raise ValueError(f'fck must be positive (cannot be zero). Got {fck}') + if gamma_c <= 0: + raise ValueError( + f'gamma_c must be positive (cannot be zero). Got {gamma_c}' + ) + if fcd <= 0: + raise ValueError(f'fcd must be positive (cannot be zero). Got {fcd}') + if fyd <= 0: + raise ValueError(f'fyd must be positive (cannot be zero). Got {fyd}') + if cv2 < 0: + raise ValueError(f'cv2 must not be negative. Got {cv2}') + if mu_v < 0: + raise ValueError(f'mu_v must not be negative. Got {mu_v}') + if kv < 0: + raise ValueError(f'kv must not be negative. Got {kv}') + if rho_i < 0: + raise ValueError(f'rho_i must not be negative. Got {rho_i}') + if kdowel < 0: + raise ValueError(f'kdowel must not be negative. Got {kdowel}') sigma_n = max(0, sigma_n) tau_rdi = ( cv2 * math.sqrt(fck) / gamma_c + mu_v * sigma_n + kv * rho_i * fyd * mu_v - + kdowel * rho_i * math.sqrt(fyd * fck / gamma_c) + + kdowel * rho_i * math.sqrt(fyd * fcd) ) - return min( - tau_rdi, 0.25 * fck / gamma_c - ) # Cap tau_Rdi according to the formula + return min(tau_rdi, 0.25 * fcd) # Cap tau_Rdi according to the formula def as_min(tmin: float, fctm: float, fyk: float) -> float: diff --git a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py index 45db5daa..c978f53f 100644 --- a/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py +++ b/tests/test_ec2_2023/test_ec2_2023_section_8_2_shear.py @@ -640,6 +640,8 @@ def test_rho_l_p_value_errors(ds, As, dp, Ap, bw, d): (20, 40, 0.005, 0.008, 0.005), # vEd_y/vEd_x <= 0.5 (40, 20, 0.005, 0.008, 0.008), # vEd_y/vEd_x >= 2 (30, 40, 0.005, 0.008, 0.00308), # 0.5 < vEd_y/vEd_x < 2 + (20, 0, 0.005, 0.008, 0.008), # vEd_x = 0 (returns rho_l_y) + (25, 50, 0.005, 0.008, 0.005), # Additional test to cover line 598 ], ) def test_rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y, expected): @@ -659,11 +661,10 @@ def test_rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y, expected): (20, -40, 0.005, 0.008), # Negative vEd_x (20, 40, -0.005, 0.008), # Negative rho_l_x (20, 40, 0.005, -0.008), # Negative rho_l_y - (20, 0, 0.005, 0.008), # vEd_x zero (division by zero) ], ) def test_rho_l_planar_value_errors(vEd_y, vEd_x, rho_l_x, rho_l_y): - """Test that rho_l_planar raises ValueError for invalid inputs.""" + """Test that rho_l_planar raises ValueError for negative inputs.""" with pytest.raises(ValueError): _section_8_2_shear.rho_l_planar(vEd_y, vEd_x, rho_l_x, rho_l_y) @@ -671,64 +672,255 @@ def test_rho_l_planar_value_errors(vEd_y, vEd_x, rho_l_x, rho_l_y): # Tests using pytest def test_cot_theta_min(): """Tests the function cot_theta_min with various scenarios.""" - assert _section_8_2_shear.cot_theta_min(0, 100, 10, 400) == 2.5 - assert _section_8_2_shear.cot_theta_min(0, 100, 10, 400) == 2.5 - assert _section_8_2_shear.cot_theta_min(10, 100, 10, 400) == 2.49 - assert _section_8_2_shear.cot_theta_min(-100, 100, 50, 400) == 3.0 + Ac = 100000 # 100,000 mm2 + # No axial force (NEd = 0) + assert _section_8_2_shear.cot_theta_min(0, 100, 10, 400, Ac) == 2.5 + + # Tension case (NEd > 0) + assert _section_8_2_shear.cot_theta_min(10, 100, 10, 400, Ac) == 2.49 + + # Compression case with x < 0.25d (uses interpolation) + # NEd = -100 kN, sigma_c = 1 MPa, interpolation: 2.5 + 1.0/6.0 = 2.666... + assert _section_8_2_shear.cot_theta_min( + -100, 100, 50, 400, Ac + ) == pytest.approx(2.5 + 1.0 / 6.0, rel=1e-9) + + # Compression case with significant stress and x < 0.25d + NEd = -300 # -300 kN (3 MPa stress: 300 * 1000 / 100000 = 3.0 MPa) + assert _section_8_2_shear.cot_theta_min(NEd, 100, 50, 400, Ac) == 3.0 + + # Compression case with very high stress (6 MPa) and x < 0.25d + NEd_high = -600 # -600 kN (6 MPa stress: 600 * 1000 / 100000 = 6.0 MPa) + assert _section_8_2_shear.cot_theta_min(NEd_high, 100, 50, 400, Ac) == 3.0 + + # Compression case with intermediate stress (4.5 MPa) and x < 0.25d + # sigma_c >= 3.0, so returns 3.0 (capped) + NEd_intermediate = -450 # -450 kN (4.5 MPa stress) + assert ( + _section_8_2_shear.cot_theta_min(NEd_intermediate, 100, 50, 400, Ac) + == 3.0 + ) + + # Compression case with significant stress but x >= 0.25d (tension formula) + # sigma_c = 3.0 MPa, x = 120 >= 100, so: 2.5 - 0.1*(-300)/100 = 2.8 + assert _section_8_2_shear.cot_theta_min(NEd, 100, 120, 400, Ac) == 2.8 + + # Compression case with low stress and x < 0.25d (uses interpolation) + NEd_low = -100 # -100 kN (1 MPa stress: 100 * 1000 / 100000 = 1.0 MPa) + assert _section_8_2_shear.cot_theta_min( + NEd_low, 100, 50, 400, Ac + ) == pytest.approx(2.5 + 1.0 / 6.0, rel=1e-9) + + # Test ductility class A (20% reduction) + assert _section_8_2_shear.cot_theta_min( + 0, 100, 10, 400, Ac, apply_ductility_class_a_reduction=True + ) == pytest.approx(2.0, rel=1e-9) + assert _section_8_2_shear.cot_theta_min( + 10, 100, 10, 400, Ac, apply_ductility_class_a_reduction=True + ) == pytest.approx(1.992, rel=1e-9) + assert _section_8_2_shear.cot_theta_min( + NEd_high, 100, 50, 400, Ac, apply_ductility_class_a_reduction=True + ) == pytest.approx(2.4, rel=1e-9) + + +@pytest.mark.parametrize( + 'NEd, VEd, x, d, Ac, expected', + [ + # No axial force cases + (0, 100, 10, 400, 100000, 2.5), + (0, 200, 20, 500, 100000, 2.5), + # Tension cases (NEd > 0) + (10, 100, 10, 400, 100000, 2.49), + (50, 200, 20, 500, 100000, 2.475), + (100, 100, 10, 400, 100000, 2.4), + (200, 100, 10, 400, 100000, 2.3), + (500, 100, 10, 400, 100000, 2.0), # 2.5 - 0.1*500/100 = 2.0 + (1000, 100, 10, 400, 100000, 1.5), # 2.5 - 0.1*1000/100 = 1.5 + # Compression cases with x < 0.25d (uses interpolation) + (-10, 100, 10, 400, 100000, 2.5 + 0.1 / 6.0), # 0.1 MPa + (-50, 200, 20, 500, 100000, 2.5 + 0.5 / 6.0), # 0.5 MPa + (-100, 100, 10, 400, 100000, 2.5 + 1.0 / 6.0), # 1.0 MPa + (-200, 100, 10, 400, 100000, 2.5 + 2.0 / 6.0), # 2.0 MPa + # Compression cases with significant stress and x < 0.25d + (-300, 100, 50, 400, 100000, 3.0), # 3 MPa stress, x < 0.25d + (-400, 200, 80, 500, 100000, 3.0), # 4 MPa stress, x < 0.25d + (-500, 100, 90, 400, 100000, 3.0), # 5 MPa stress, x < 0.25d + # Compression cases with significant stress but x >= 0.25d + # (tension formula) + (-300, 100, 120, 400, 100000, 2.8), # 3 MPa, x >= 0.25d + (-400, 200, 150, 500, 100000, 2.7), # 4 MPa, x >= 0.25d + # Compression cases with low stress and x < 0.25d (uses interpolation) + (-100, 100, 50, 400, 100000, 2.5 + 1.0 / 6.0), # 1 MPa stress + (-200, 200, 80, 500, 100000, 2.5 + 2.0 / 6.0), # 2 MPa stress + ], +) +def test_cot_theta_min_comprehensive(NEd, VEd, x, d, Ac, expected): + """Test cot_theta_min with comprehensive scenarios.""" + result = _section_8_2_shear.cot_theta_min(NEd, VEd, x, d, Ac) + assert result == pytest.approx(expected, rel=1e-9) + + +def test_cot_theta_min_edge_cases(): + """Test cot_theta_min edge cases and boundary conditions.""" + # Test exactly at 3 MPa stress boundary + Ac = 100000 # 100,000 mm2 + NEd_exact = -300 # Exactly 3 MPa: 300 * 1000 / 100000 = 3.0 MPa + assert _section_8_2_shear.cot_theta_min(NEd_exact, 100, 50, 400, Ac) == 3.0 + + # Test just below 3 MPa stress + NEd_below = -299 # 2.99 MPa: 299 * 1000 / 100000 = 2.99 MPa + # sigma_c = 2.99 MPa, x < 0.25d, so: 2.5 + 2.99/6.0 = 2.9983... + assert _section_8_2_shear.cot_theta_min( + NEd_below, 100, 50, 400, Ac + ) == pytest.approx(2.5 + 2.99 / 6.0, rel=1e-9) + + # Test exactly at x = 0.25d boundary (x = 100, not < 100, so tension) + x_exact = 100 # 0.25 * 400 + # sigma_c = 3.0 MPa, x = 100 (not < 100), so: 2.5 - 0.1*(-300)/100 = 2.8 + assert ( + _section_8_2_shear.cot_theta_min(NEd_exact, 100, x_exact, 400, Ac) + == 2.8 + ) + + # Test just above x = 0.25d boundary (tension formula) + x_above = 101 # Just above 0.25 * 400 + assert ( + _section_8_2_shear.cot_theta_min(NEd_exact, 100, x_above, 400, Ac) + == 2.8 + ) + + # Test minimum tension value (1.0) + NEd_high_tension = 1500 # High tension force + result = _section_8_2_shear.cot_theta_min( + NEd_high_tension, 100, 10, 400, Ac + ) + assert result == 1.0 + + # Test with zero VEd when NEd is zero (valid case) + assert _section_8_2_shear.cot_theta_min(0, 0, 10, 400, Ac) == 2.5 + + # Test with zero VEd when NEd is not zero (should raise ValueError) + with pytest.raises(ValueError, match='VEd must not be zero'): + _section_8_2_shear.cot_theta_min(10, 0, 10, 400, Ac) + + +def test_cot_theta_min_ductility_class_a_reduction(): + """Test cot_theta_min ductility class A reduction.""" + Ac = 100000 # 100,000 mm2 + # Test with and without reduction + base_value = _section_8_2_shear.cot_theta_min(0, 100, 10, 400, Ac) + reduced_value = _section_8_2_shear.cot_theta_min( + 0, 100, 10, 400, Ac, apply_ductility_class_a_reduction=True + ) + + assert reduced_value == base_value * 0.8 + assert reduced_value == 2.0 + + +def test_cot_theta_min_interpolation(): + """Test cot_theta_min interpolation for intermediate stress values.""" + Ac = 100000 # 100,000 mm2 + + # Test various stress levels from 0 to 3 MPa and above + test_cases = [ + (0.0, 2.5), # Exactly 0 MPa: 2.5 + 0/6.0 = 2.5 + (1.0, 2.5 + 1.0 / 6.0), # 1 MPa: 2.5 + 1.0/6.0 = 2.666... + (2.0, 2.5 + 2.0 / 6.0), # 2 MPa: 2.5 + 2.0/6.0 = 2.833... + (2.5, 2.5 + 2.5 / 6.0), # 2.5 MPa: 2.5 + 2.5/6.0 = 2.916... + (3.0, 3.0), # Exactly 3 MPa (capped at 3.0) + (4.0, 3.0), # 4 MPa (capped at 3.0) + (5.0, 3.0), # 5 MPa (capped at 3.0) + (6.0, 3.0), # 6 MPa (capped at 3.0) + (7.0, 3.0), # 7 MPa (capped at 3.0) + ] + + for stress_mpa, expected in test_cases: + # Convert stress (MPa) to NEd (kN): sigma_c = abs(NEd) * 1000 / Ac + # So: abs(NEd) = sigma_c * Ac / 1000 + NEd = -stress_mpa * Ac / 1000 # Convert to kN + result = _section_8_2_shear.cot_theta_min(NEd, 100, 50, 400, Ac) + assert result == pytest.approx(expected, rel=1e-3) @pytest.mark.parametrize( - 'NEd, VEd, x, d', + 'NEd, VEd, x, d, Ac', [ - (0, 100, -1, 400), # x negative - (0, 100, 10, 0), # d zero - (0, 100, -1, 0), # x negative and d zero - (0, 100, 10, -400), # d negative - (0, 100, -10, -400), # both negative - (-10, 100, 10, 0), # d zero with NEd negative - (10, 100, 10, 0), # d zero with NEd positive + (0, 100, -1, 400, 100000), # x negative + (0, 100, 10, 0, 100000), # d zero + (0, 100, -1, 0, 100000), # x negative and d zero + (0, 100, 10, -400, 100000), # d negative + (0, 100, -10, -400, 100000), # both negative + (-10, 100, 10, 0, 100000), # d zero with NEd negative + (10, 100, 10, 0, 100000), # d zero with NEd positive + (0, 100, 10, 400, 0), # Ac zero + (0, 100, 10, 400, -100000), # Ac negative + (10, 0, 10, 400, 100000), # VEd zero with NEd positive + (-10, 0, 10, 400, 100000), # VEd zero with NEd negative ], ) -def test_cot_theta_min_value_error_all_cases(NEd, VEd, x, d): +def test_cot_theta_min_value_error_all_cases(NEd, VEd, x, d, Ac): """Test cot_theta_min raises ValueError.""" with pytest.raises(ValueError): - _section_8_2_shear.cot_theta_min(NEd, VEd, x, d) + _section_8_2_shear.cot_theta_min(NEd, VEd, x, d, Ac) @pytest.mark.parametrize( - 'NEd, VEd, x, d', + 'NEd, VEd, x, d, Ac', [ - (0, 100, 10, 0), # d is zero - (0, 100, -10, 400), # x is negative - (0, 100, -10, 0), # x negative and d zero - (0, 100, 10, -400), # d negative - (0, 100, -10, -400), # both negative + (0, 100, 10, 0, 100000), # d is zero + (0, 100, -10, 400, 100000), # x is negative + (0, 100, -10, 0, 100000), # x negative and d zero + (0, 100, 10, -400, 100000), # d negative + (0, 100, -10, -400, 100000), # both negative + (0, 100, 10, 400, 0), # Ac zero + (0, 100, 10, 400, -100000), # Ac negative + (10, 0, 10, 400, 100000), # VEd zero with NEd positive + (-10, 0, 10, 400, 100000), # VEd zero with NEd negative ], ) -def test_cot_theta_min_value_error(NEd, VEd, x, d): +def test_cot_theta_min_value_error(NEd, VEd, x, d, Ac): """Test cot_theta_min raises ValueError for invalid dimensions.""" with pytest.raises(ValueError): - _section_8_2_shear.cot_theta_min(NEd, VEd, x, d) + _section_8_2_shear.cot_theta_min(NEd, VEd, x, d, Ac) def test_tau_Rd_sy(): """Tests the function tau_Rd_sy.""" - assert _section_8_2_shear.tau_Rd_sy(0.01, 500, 2) == 10 - assert _section_8_2_shear.tau_Rd_sy(0.02, 400, 2.5) == 20 - - -@pytest.mark.parametrize( - 'rho_w, fywd, cot_theta', - [ - (-0.01, 500, 2), # Negative rho_w - (0.01, -500, 2), # Negative fywd - (-0.01, -500, 2), # Both negative - ], -) -def test_tau_Rd_sy_value_errors(rho_w, fywd, cot_theta): - """Test tau_Rd_sy raises ValueError for negative rho_w or fywd.""" + assert _section_8_2_shear.tau_Rd_sy(0.01, 500, 2, 2.5) == 10 + assert _section_8_2_shear.tau_Rd_sy(0.02, 400, 2.5, 3.0) == 20 + # Test boundary cases + # cot_theta = 1 (minimum) + assert _section_8_2_shear.tau_Rd_sy(0.01, 500, 1.0, 2.5) == 5 + # cot_theta = cot_theta_min (maximum) + assert _section_8_2_shear.tau_Rd_sy(0.01, 500, 2.5, 2.5) == 12.5 + # cot_theta_min = 1 (minimum allowed) + assert _section_8_2_shear.tau_Rd_sy(0.01, 500, 1.0, 1.0) == 5 + + +@pytest.mark.parametrize( + 'rho_w, fywd, cot_theta, cot_theta_min', + [ + (-0.01, 500, 2, 2.5), # Negative rho_w + (0.01, -500, 2, 2.5), # Negative fywd + (-0.01, -500, 2, 2.5), # Both negative + (0, 500, 2, 2.5), # Zero rho_w + (0.01, 0, 2, 2.5), # Zero fywd + (0, 0, 2, 2.5), # Both zero + (-0.01, 0, 2, 2.5), # Negative rho_w, zero fywd + (0, -500, 2, 2.5), # Zero rho_w, negative fywd + (0.01, 500, 2, 0), # Zero cot_theta_min + (0.01, 500, 2, -2.5), # Negative cot_theta_min + (0.01, 500, 2, 0.5), # cot_theta_min < 1 + (0.01, 500, 2, 0.9), # cot_theta_min < 1 (edge case) + (0.01, 500, 0.5, 2.5), # cot_theta < 1 + (0.01, 500, 3.0, 2.5), # cot_theta > cot_theta_min + (0.01, 500, 2.6, 2.5), # cot_theta > cot_theta_min (edge case) + ], +) +def test_tau_Rd_sy_value_errors(rho_w, fywd, cot_theta, cot_theta_min): + """Test tau_Rd_sy raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.tau_Rd_sy(rho_w, fywd, cot_theta) + _section_8_2_shear.tau_Rd_sy(rho_w, fywd, cot_theta, cot_theta_min) def test_rho_w(): @@ -758,55 +950,99 @@ def test_rho_w_value_errors(Asw, bw, s): def test_sigma_cd(): """Tests the function sigma_cd.""" - assert _section_8_2_shear.sigma_cd(1, 2, 0.5, 0.5, 20) == 2.5 - - -@pytest.mark.parametrize( - 'tau_Ed, cot_theta, tan_theta, nu, f_cd', - [ - (-1, 1, 1, 0.5, 20), # Negative tau_Ed - (1, -1, 1, 0.5, 20), # Negative cot_theta - (1, 1, -1, 0.5, 20), # Negative tan_theta - (1, 1, 1, -0.5, 20), # Negative nu - (1, 1, 1, 0.5, -20), # Negative f_cd - (-1, -1, -1, -0.5, -20), # All negative - ], -) -def test_sigma_cd_value_errors(tau_Ed, cot_theta, tan_theta, nu, f_cd): - """Test sigma_cd raises ValueError for negative arguments.""" + # tan_theta = 1/cot_theta = 1/2 = 0.5 + # sigma_cd = 1 * (2 + 0.5) = 2.5, min(2.5, 0.5 * 20) = 2.5 + assert _section_8_2_shear.sigma_cd(1, 2, 2.5, 0.5, 20) == 2.5 + # Test with cot_theta = cot_theta_min + assert _section_8_2_shear.sigma_cd(1, 2.5, 2.5, 0.5, 20) == 2.9 + # Test with cot_theta = 1 + assert _section_8_2_shear.sigma_cd(1, 1, 2.5, 0.5, 20) == 2.0 + # Test with cot_theta_min = 1 (boundary case) + assert _section_8_2_shear.sigma_cd(1, 1, 1, 0.5, 20) == 2.0 + + +@pytest.mark.parametrize( + 'tau_Ed, cot_theta, cot_theta_min, nu, f_cd', + [ + (-1, 2, 2.5, 0.5, 20), # Negative tau_Ed + (1, 2, 0, 0.5, 20), # Zero cot_theta_min + (1, 2, -2.5, 0.5, 20), # Negative cot_theta_min + (1, 2, 0.5, 0.5, 20), # cot_theta_min < 1 + (1, 2, 0.9, 0.5, 20), # cot_theta_min < 1 (edge case) + (1, 0.5, 2.5, 0.5, 20), # cot_theta < 1 + (1, 3.0, 2.5, 0.5, 20), # cot_theta > cot_theta_min + (1, 2.6, 2.5, 0.5, 20), # cot_theta > cot_theta_min (edge case) + (1, 2, 2.5, -0.5, 20), # Negative nu + (1, 2, 2.5, 0.5, 0), # Zero f_cd + (1, 2, 2.5, 0.5, -20), # Negative f_cd + (-1, 0.5, 0.5, -0.5, -20), # Multiple errors + ], +) +def test_sigma_cd_value_errors(tau_Ed, cot_theta, cot_theta_min, nu, f_cd): + """Test sigma_cd raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.sigma_cd(tau_Ed, cot_theta, tan_theta, nu, f_cd) + _section_8_2_shear.sigma_cd(tau_Ed, cot_theta, cot_theta_min, nu, f_cd) def test_tau_Rd(): """Tests the function tau_Rd.""" - assert _section_8_2_shear.tau_Rd(0.01, 500, 2, 0.5, 50) == 10 + assert _section_8_2_shear.tau_Rd(0.01, 500, 2, 2.5, 0.5, 50) == 10 assert ( - _section_8_2_shear.tau_Rd(0.01, 500, 3, 0.5, 20) == 5 + _section_8_2_shear.tau_Rd(0.01, 500, 3, 3.0, 0.5, 20) == 5 ) # Limited by the compression field + # Test boundary cases + assert _section_8_2_shear.tau_Rd(0.01, 500, 1, 2.5, 0.5, 50) == 5 + assert _section_8_2_shear.tau_Rd(0.01, 500, 2.5, 2.5, 0.5, 50) == 12.5 @pytest.mark.parametrize( - 'rho_w, fywd, cot_theta, nu, f_cd', + 'rho_w, fywd, cot_theta, cot_theta_min, nu, f_cd', [ - (-0.01, 500, 2, 0.5, 50), # Negative rho_w - (0.01, -500, 2, 0.5, 50), # Negative fywd - (0.01, 500, -2, 0.5, 50), # Negative cot_theta - (0.01, 500, 2, -0.5, 50), # Negative nu - (0.01, 500, 2, 0.5, -50), # Negative f_cd - (-0.01, -500, -2, -0.5, -50), # All negative + (-0.01, 500, 2, 2.5, 0.5, 50), # Negative rho_w + (0.01, -500, 2, 2.5, 0.5, 50), # Negative fywd + (0.01, 500, -2, 2.5, 0.5, 50), # Negative cot_theta + (0.01, 500, 2, 0.5, 0.5, 50), # cot_theta_min < 1 + (0.01, 500, 2, 2.5, -0.5, 50), # Negative nu + (0.01, 500, 2, 2.5, 0.5, -50), # Negative f_cd + (-0.01, -500, -2, 0.5, -0.5, -50), # All negative + (0.01, 500, 0.5, 2.5, 0.5, 50), # cot_theta < 1 + (0.01, 500, 3, 2.5, 0.5, 50), # cot_theta > cot_theta_min ], ) -def test_tau_Rd_value_errors(rho_w, fywd, cot_theta, nu, f_cd): - """Test tau_Rd raises ValueError for negative arguments.""" +def test_tau_Rd_value_errors(rho_w, fywd, cot_theta, cot_theta_min, nu, f_cd): + """Test tau_Rd raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.tau_Rd(rho_w, fywd, cot_theta, nu, f_cd) + _section_8_2_shear.tau_Rd( + rho_w, fywd, cot_theta, cot_theta_min, nu, f_cd + ) + +def test_cot_theta_simultaneous(): + """Tests the function cot_theta_simultaneous.""" + # Test basic calculation: sqrt((0.5 * 20) / (0.01 * 500) - 1) = sqrt(1) = 1 + assert ( + _section_8_2_shear.cot_theta_simultaneous(0.5, 20, 0.01, 500, 2) == 1 + ) + # Test: sqrt((0.5 * 40) / (0.01 * 500) - 1) = sqrt(3) ≈ 1.732 + assert _section_8_2_shear.cot_theta_simultaneous( + 0.5, 40, 0.01, 500, 2 + ) == pytest.approx(1.732, rel=1e-3) + # Test clamping to minimum (1.0) + # sqrt((0.5 * 10) / (0.01 * 500) - 1) = sqrt(0) = 0, clamped to 1 + assert ( + _section_8_2_shear.cot_theta_simultaneous(0.5, 10, 0.01, 500, 2.5) + == 1.0 + ) + # Test clamping to cot_theta_min + # sqrt((0.5 * 60) / (0.01 * 500) - 1) = sqrt(5) ≈ 2.236 + assert _section_8_2_shear.cot_theta_simultaneous( + 0.5, 60, 0.01, 500, 2.5 + ) == pytest.approx(2.236, rel=1e-3) -def test_cot_theta(): - """Tests the function cot_theta.""" - assert _section_8_2_shear.cot_theta(0.5, 20, 0.01, 500, 2) == 1 - assert _section_8_2_shear.cot_theta(0.5, 40, 0.01, 500, 2) == 2 + # Test case where expression under square root is negative + # (0.5 * 8) / (0.01 * 500) - 1 = 4 / 5 - 1 = -0.2 < 0 + with pytest.raises(ValueError, match='Expression under square root'): + _section_8_2_shear.cot_theta_simultaneous(0.5, 8, 0.01, 500, 2.5) @pytest.mark.parametrize( @@ -817,84 +1053,102 @@ def test_cot_theta(): (0.5, 20, -0.01, 500, 2), # Negative rho_w (0.5, 20, 0.01, -500, 2), # Negative fywd (-0.5, -20, -0.01, -500, 2), # All negative + (0.5, 20, 0.01, 500, 0.5), # cot_theta_min < 1 + (0.5, 20, 0, 500, 2), # rho_w is zero + (0.5, 20, 0.01, 0, 2), # fywd is zero + (0.5, 8, 0.01, 500, 2), # Expression under square root is negative ], ) -def test_cot_theta_value_errors(nu, f_cd, rho_w, fywd, cot_theta_min): - """Test cot_theta raises ValueError for negative arguments.""" +def test_cot_theta_simultaneous_value_errors( + nu, f_cd, rho_w, fywd, cot_theta_min +): + """Test cot_theta_simultaneous raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.cot_theta(nu, f_cd, rho_w, fywd, cot_theta_min) + _section_8_2_shear.cot_theta_simultaneous( + nu, f_cd, rho_w, fywd, cot_theta_min + ) @pytest.mark.parametrize( - 'Ftd, Est, Ast, expected', [(500, 210000, 1000, 0.002380952380952381)] + 'Ftd, Es, Ast, expected', [(500, 210000, 1000, 0.002380952380952381)] ) -def test_epsilon_xt(Ftd, Est, Ast, expected): +def test_epsilon_xt(Ftd, Es, Ast, expected): """Test εxt calculation.""" - assert _section_8_2_shear.epsilon_xt(Ftd, Est, Ast) == pytest.approx( + assert _section_8_2_shear.epsilon_xt(Ftd, Es, Ast) == pytest.approx( expected ) @pytest.mark.parametrize( - 'Ftd, Est, Ast', + 'Ftd, Es, Ast', [ - (500, -210000, 1000), # Negative Est + (500, -210000, 1000), # Negative Es (500, 210000, -1000), # Negative Ast (500, -210000, -1000), # Both negative + (500, 0, 1000), # Es is zero + (500, 210000, 0), # Ast is zero ], ) -def test_epsilon_xt_value_errors(Ftd, Est, Ast): - """Test epsilon_xt raises ValueError for negative Est or Ast.""" +def test_epsilon_xt_value_errors(Ftd, Es, Ast): + """Test epsilon_xt raises ValueError for negative or zero Es or Ast.""" with pytest.raises(ValueError): - _section_8_2_shear.epsilon_xt(Ftd, Est, Ast) + _section_8_2_shear.epsilon_xt(Ftd, Es, Ast) @pytest.mark.parametrize( - 'Fcd, Ecc, Acc, expected', [(500, 30000, 1000, 0.016666666666666666)] + 'Fcd, Ec, Acc, expected', [(500, 30000, 1000, 0.016666666666666666)] ) -def test_epsilon_xc_compression(Fcd, Ecc, Acc, expected): +def test_epsilon_xc_compression(Fcd, Ec, Acc, expected): """Test εxc calculation for compression.""" - assert _section_8_2_shear.epsilon_xc_comp(Fcd, Ecc, Acc) == pytest.approx( + assert _section_8_2_shear.epsilon_xc_comp(Fcd, Ec, Acc) == pytest.approx( expected, rel=10e-3 ) @pytest.mark.parametrize( - 'Fcd, Ecc, Acc', + 'Fcd, Ec, Acc', [ - (500, -30000, 1000), # Negative Ecc + (500, -30000, 1000), # Negative Ec (500, 30000, -1000), # Negative Acc (500, -30000, -1000), # Both negative + (500, 0, 1000), # Ec is zero + (500, 30000, 0), # Acc is zero ], ) -def test_epsilon_xc_comp_value_errors(Fcd, Ecc, Acc): - """Test epsilon_xc_comp raises ValueError for negative Ecc or Acc.""" +def test_epsilon_xc_comp_value_errors(Fcd, Ec, Acc): + """Test epsilon_xc_comp raises ValueError for negative or zero Ec or + Acc. + """ with pytest.raises(ValueError): - _section_8_2_shear.epsilon_xc_comp(Fcd, Ecc, Acc) + _section_8_2_shear.epsilon_xc_comp(Fcd, Ec, Acc) @pytest.mark.parametrize( - 'Fcd, Esc, Asc, expected', [(500, 210000, 1000, 0.002380952380952381)] + 'Fcd, Es, Asc, expected', [(500, 210000, 1000, 0.002380952380952381)] ) -def test_epsilon_xc_tension(Fcd, Esc, Asc, expected): +def test_epsilon_xc_tension(Fcd, Es, Asc, expected): """Test εxc calculation for tension.""" - assert _section_8_2_shear.epsilon_xc_tens(Fcd, Esc, Asc) == pytest.approx( + assert _section_8_2_shear.epsilon_xc_tens(Fcd, Es, Asc) == pytest.approx( expected, rel=10e-3 ) @pytest.mark.parametrize( - 'Fcd, Esc, Asc', + 'Fcd, Es, Asc', [ - (500, -210000, 1000), # Negative Esc + (500, -210000, 1000), # Negative Es (500, 210000, -1000), # Negative Asc (500, -210000, -1000), # Both negative + (500, 0, 1000), # Es is zero + (500, 210000, 0), # Asc is zero ], ) -def test_epsilon_xc_tens_value_errors(Fcd, Esc, Asc): - """Test epsilon_xc_tens raises ValueError for negative Esc or Asc.""" +def test_epsilon_xc_tens_value_errors(Fcd, Es, Asc): + """Test epsilon_xc_tens raises ValueError for negative or zero Es or + Asc. + """ with pytest.raises(ValueError): - _section_8_2_shear.epsilon_xc_tens(Fcd, Esc, Asc) + _section_8_2_shear.epsilon_xc_tens(Fcd, Es, Asc) @pytest.mark.parametrize( @@ -907,42 +1161,77 @@ def test_epsilon_x(epsilon_xt, epsilon_xc, expected): ) == pytest.approx(expected) -@pytest.mark.parametrize('epsilon_x, theta, expected', [(0.001, 2, 0.5025)]) -def test_nu(epsilon_x, theta, expected): +@pytest.mark.parametrize( + 'epsilon_x, cot_theta, cot_theta_min, expected', + [(0.001, 2, 2.5, 0.5025)], +) +def test_nu(epsilon_x, cot_theta, cot_theta_min, expected): """Test ν calculation.""" - assert _section_8_2_shear.nu(epsilon_x, theta) == pytest.approx( - expected, rel=10e-3 + assert _section_8_2_shear.nu( + epsilon_x, cot_theta, cot_theta_min + ) == pytest.approx(expected, rel=10e-3) + # Test boundary cases + # For cot_theta = 1: nu = 1 / (1.0 + 110 * (0.001 + 0.002 * 1)) ≈ 0.7519 + assert _section_8_2_shear.nu(0.001, 1, 2.5) == pytest.approx( + 0.7519, rel=10e-3 + ) + # For cot_theta = 2.5: nu = 1 / (1.0 + 110 * (0.001 + 0.002 * 6.25)) + # ≈ 0.4024 + assert _section_8_2_shear.nu(0.001, 2.5, 2.5) == pytest.approx( + 0.4024, rel=10e-3 ) @pytest.mark.parametrize( - 'epsilon_x, cot_theta', + 'epsilon_x, cot_theta, cot_theta_min', [ - (-0.001, 2), # Negative epsilon_x - (-1.0, 0), # Negative epsilon_x, zero cot_theta - (-0.5, -2), # Negative epsilon_x, negative cot_theta + (-0.001, 2, 2.5), # Negative epsilon_x + (-1.0, 2, 2.5), # Negative epsilon_x + (-0.5, 2, 2.5), # Negative epsilon_x + (0.001, 2, 0.5), # cot_theta_min < 1 + (0.001, 0.5, 2.5), # cot_theta < 1 + (0.001, 3, 2.5), # cot_theta > cot_theta_min ], ) -def test_nu_value_errors(epsilon_x, cot_theta): - """Test nu raises ValueError for negative epsilon_x.""" +def test_nu_value_errors(epsilon_x, cot_theta, cot_theta_min): + """Test nu raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.nu(epsilon_x, cot_theta) + _section_8_2_shear.nu(epsilon_x, cot_theta, cot_theta_min) @pytest.mark.parametrize( - 'VEd, theta, expected', + 'VEd, cot_theta, cot_theta_min, expected', [ - (100, 2, 200), - (150, 1.5, 225), + (100, 2, 2.5, 200), + (150, 1.5, 2.5, 225), ], ) -def test_calculate_nv(VEd, theta, expected): +def test_calculate_nv(VEd, cot_theta, cot_theta_min, expected): """Test calculate_nv function with various inputs.""" - assert _section_8_2_shear.Nvd(VEd, theta) == pytest.approx( - expected, rel=1e-6 + assert _section_8_2_shear.Nvd( + VEd, cot_theta, cot_theta_min + ) == pytest.approx(expected, rel=1e-6) + # Test boundary cases + assert _section_8_2_shear.Nvd(100, 1, 2.5) == pytest.approx(100, rel=1e-6) + assert _section_8_2_shear.Nvd(100, 2.5, 2.5) == pytest.approx( + 250, rel=1e-6 ) +@pytest.mark.parametrize( + 'VEd, cot_theta, cot_theta_min', + [ + (100, 2, 0.5), # cot_theta_min < 1 + (100, 0.5, 2.5), # cot_theta < 1 + (100, 3, 2.5), # cot_theta > cot_theta_min + ], +) +def test_Nvd_value_errors(VEd, cot_theta, cot_theta_min): + """Test Nvd raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.Nvd(VEd, cot_theta, cot_theta_min) + + @pytest.mark.parametrize( 'MEd, z, NVd, NE, expected', [ @@ -971,6 +1260,61 @@ def test_calculate_fcd(MEd, z, NVd, NE, expected): ) +@pytest.mark.parametrize( + 'MEd, z, NVd, NE', + [ + (200, 0, 100, 50), # z is zero + (200, -400, 100, 50), # z is negative + ], +) +def test_Ftd_value_errors(MEd, z, NVd, NE): + """Test Ftd raises ValueError for invalid z.""" + with pytest.raises(ValueError): + _section_8_2_shear.Ftd(MEd, z, NVd, NE) + + +@pytest.mark.parametrize( + 'MEd, z, NVd, NE', + [ + (200, 0, 100, 50), # z is zero + (200, -400, 100, 50), # z is negative + ], +) +def test_Fcd_value_errors(MEd, z, NVd, NE): + """Test Fcd raises ValueError for invalid z.""" + with pytest.raises(ValueError): + _section_8_2_shear.Fcd(MEd, z, NVd, NE) + + +@pytest.mark.parametrize( + 'MEd_max, z, NEd, expected', + [ + (200, 400, 50, 525.0), # 200*1000/400 + 50/2 = 500 + 25 = 525 + (300, 600, 100, 550.0), # 300*1000/600 + 100/2 = 500 + 50 = 550 + ], +) +def test_Ftd_max(MEd_max, z, NEd, expected): + """Test Ftd_max calculation for direct intermediate support or concentrated + loads. + """ + assert _section_8_2_shear.Ftd_max(MEd_max, z, NEd) == pytest.approx( + expected, rel=1e-6 + ) + + +@pytest.mark.parametrize( + 'MEd_max, z, NEd', + [ + (200, 0, 50), # z is zero + (200, -400, 50), # z is negative + ], +) +def test_Ftd_max_value_errors(MEd_max, z, NEd): + """Test Ftd_max raises ValueError for invalid z.""" + with pytest.raises(ValueError): + _section_8_2_shear.Ftd_max(MEd_max, z, NEd) + + @pytest.mark.parametrize( 'duct_material, is_grouted, wall_thickness, duct_diameter, expected', [ @@ -999,12 +1343,14 @@ def test_calculate_k_duct( ('steel', True, 2.0, -50.0), # Negative duct_diameter ('plastic', False, -0.5, 40.0), # Negative wall_thickness ('plastic', False, 1.0, -40.0), # Negative duct_diameter + ('steel', True, 0, 50.0), # Zero wall_thickness + ('steel', True, 2.0, 0), # Zero duct_diameter ], ) def test_k_duct_negative_dimensions( duct_material, is_grouted, wall_thickness, duct_diameter ): - """Test k_duct raises ValueError.""" + """Test k_duct raises ValueError for non-positive dimensions.""" with pytest.raises(ValueError): _section_8_2_shear.k_duct( duct_material, is_grouted, wall_thickness, duct_diameter @@ -1064,79 +1410,213 @@ def test_bw_nom_value_errors(bw, duct_diameters, k_duct): @pytest.mark.parametrize( - 'nu, f_cd, theta, beta_incl, rho_w, f_ywd, expected', + ( + 'nu, f_cd, cot_theta, cot_theta_min, cot_beta_incl, rho_w, f_ywd, ' + 'expected' + ), [ - (0.6, 30, 1, 1.5, 0.01, 500, 3), - (0.5, 40, 1.5, 1, 0.02, 400, 9.2307), + (0.6, 30, 1, 2.5, 1.5, 0.01, 500, 3), + (0.5, 40, 1.5, 2.5, 1, 0.02, 400, 9.2307), ], ) -def test_tau_rd(nu, f_cd, theta, beta_incl, rho_w, f_ywd, expected): +def test_tau_rd( + nu, f_cd, cot_theta, cot_theta_min, cot_beta_incl, rho_w, f_ywd, expected +): """Test calculation of enhanced shear stress resistance τRd.""" assert math.isclose( - _section_8_2_shear.tau_rd(nu, f_cd, theta, beta_incl, rho_w, f_ywd), + _section_8_2_shear.tau_rd( + nu, f_cd, cot_theta, cot_theta_min, cot_beta_incl, rho_w, f_ywd + ), expected, rel_tol=1e-2, ) + assert math.isclose( + _section_8_2_shear.tau_rd(0.6, 30, 1, 2.5, 1.5, 0.01, 500), + 3, + rel_tol=1e-2, + ) + assert math.isclose( + _section_8_2_shear.tau_rd(0.6, 30, 2.5, 2.5, 1.5, 0.01, 500), + 6.207, + rel_tol=1e-2, + ) @pytest.mark.parametrize( - 'nu, f_cd, cot_theta, cot_beta_incl, rho_w, f_ywd', + 'nu, f_cd, cot_theta, cot_theta_min, cot_beta_incl, rho_w, f_ywd', [ - (0.6, -30, 1, 1.5, 0.01, 500), # Negative f_cd - (0.6, 30, 1, 1.5, 0.01, -500), # Negative f_ywd - (0.6, -30, 1, 1.5, 0.01, -500), # Both negative + (-0.1, 30, 1, 2.5, 1.5, 0.01, 500), # Negative nu + (0.6, -30, 1, 2.5, 1.5, 0.01, 500), # Negative f_cd + (0.6, 0, 1, 2.5, 1.5, 0.01, 500), # Zero f_cd + (0.6, 30, 1, 2.5, 1.5, -0.01, 500), # Negative rho_w + (0.6, 30, 1, 2.5, 1.5, 0.01, -500), # Negative f_ywd + (0.6, 30, 1, 2.5, 1.5, 0.01, 0), # Zero f_ywd + (0.6, -30, 1, 2.5, 1.5, 0.01, -500), # Both f_cd and f_ywd negative + (0.6, 30, 1, 0.5, 1.5, 0.01, 500), # cot_theta_min < 1 + (0.6, 30, 0.5, 2.5, 1.5, 0.01, 500), # cot_theta < 1 + (0.6, 30, 3, 2.5, 1.5, 0.01, 500), # cot_theta > cot_theta_min ], ) -def test_tau_rd_value_errors(nu, f_cd, cot_theta, cot_beta_incl, rho_w, f_ywd): - """Test tau_rd raises ValueError for negative f_cd or f_ywd.""" +def test_tau_rd_value_errors( + nu, f_cd, cot_theta, cot_theta_min, cot_beta_incl, rho_w, f_ywd +): + """Test tau_rd raises ValueError for invalid arguments.""" with pytest.raises(ValueError): _section_8_2_shear.tau_rd( - nu, f_cd, cot_theta, cot_beta_incl, rho_w, f_ywd + nu, + f_cd, + cot_theta, + cot_theta_min, + cot_beta_incl, + rho_w, + f_ywd, ) @pytest.mark.parametrize( - 'e_s, eps_x, f_ywd, cot_theta, expected', + 'cot_beta_incl, cot_theta_min, expected', [ - (200000, 0.001, 500, 2, 500), - (210000, 0.002, 450, 1, 420), + (1.0, 2.5, 2.414213562373095), # sqrt(2) ≈ 1.414 + (1.5, 2.5, 2.5), # Capped at cot_theta_min + (2.0, 3.0, 3.0), # 2.0 + sqrt(5) ≈ 4.236, capped at 3.0 + (0.5, 2.0, 1.618033988749895), # sqrt(1.25) ≈ 1.118 ], ) -def test_sigma_swd(e_s, eps_x, f_ywd, cot_theta, expected): +def test_cot_theta_max_shear_constant_nu( + cot_beta_incl, cot_theta_min, expected +): + """Test calculation of optimum cot_theta for constant nu.""" + result = _section_8_2_shear.cot_theta_max_shear_constant_nu( + cot_beta_incl, cot_theta_min + ) + assert math.isclose(result, expected, rel_tol=1e-2) + + +@pytest.mark.parametrize( + 'a, z, cot_theta_min, expected', + [ + (1000, 500, 2.5, 2.5), # 1.3 * 1000 / 500 = 2.6, capped at 2.5 + (1500, 500, 3.0, 3.0), # 1.3 * 1500 / 500 = 3.9, capped at 3.0 + (1000, 1000, 3.0, 1.3), # 1.3 * 1000 / 1000 = 1.3 + (500, 500, 2.5, 1.3), # 1.3 * 500 / 500 = 1.3 + ], +) +def test_cot_theta_max_shear_variable_nu(a, z, cot_theta_min, expected): + """Test calculation of optimum cot_theta for variable nu.""" + result = _section_8_2_shear.cot_theta_max_shear_variable_nu( + a, z, cot_theta_min + ) + assert math.isclose(result, expected, rel_tol=1e-2) + + +@pytest.mark.parametrize( + 'cot_beta_incl, cot_theta_min', + [ + (1.0, 0.5), # cot_theta_min < 1 + (-10.0, 1.0), # Calculated cot_theta < 1 + ], +) +def test_cot_theta_max_shear_constant_nu_value_errors( + cot_beta_incl, cot_theta_min +): + """Test cot_theta_max_shear_constant_nu raises ValueError for invalid + arguments. + """ + with pytest.raises(ValueError): + _section_8_2_shear.cot_theta_max_shear_constant_nu( + cot_beta_incl, cot_theta_min + ) + + +@pytest.mark.parametrize( + 'a, z, cot_theta_min', + [ + (0, 500, 2.5), # a <= 0 + (-1000, 500, 2.5), # a < 0 + (1000, 0, 2.5), # z <= 0 + (1000, -500, 2.5), # z < 0 + (1000, 500, 0.5), # cot_theta_min < 1 + ( + 100, + 10000, + 1.0, + ), # Calculated cot_theta < 1 (1.3 * 100 / 10000 = 0.013) + ], +) +def test_cot_theta_max_shear_variable_nu_value_errors(a, z, cot_theta_min): + """Test cot_theta_max_shear_variable_nu raises ValueError for invalid + arguments. + """ + with pytest.raises(ValueError): + _section_8_2_shear.cot_theta_max_shear_variable_nu(a, z, cot_theta_min) + + +@pytest.mark.parametrize( + 'e_s, eps_x, f_ywd, cot_theta, cot_theta_min, expected', + [ + (200000, 0.001, 500, 2, 2.5, 500), + (210000, 0.002, 450, 1, 2.5, 420), + (200000, 0.001, 500, 2.5, 2.5, 500), # cot_theta = cot_theta_min + ], +) +def test_sigma_swd(e_s, eps_x, f_ywd, cot_theta, cot_theta_min, expected): """Test calculation of stress σswd in shear reinforcement.""" assert math.isclose( - _section_8_2_shear.sigma_swd(e_s, eps_x, f_ywd, cot_theta), + _section_8_2_shear.sigma_swd( + e_s, eps_x, f_ywd, cot_theta, cot_theta_min + ), expected, rel_tol=1e-2, ) @pytest.mark.parametrize( - 'Es, eps_x, f_ywd, cot_theta', + 'Es, eps_x, f_ywd, cot_theta, cot_theta_min', [ - (-200000, 0.001, 500, 2), # Negative Es - (200000, 0.001, -500, 2), # Negative f_ywd - (-200000, 0.001, -500, 2), # Both negative + (-200000, 0.001, 500, 2, 2.5), # Negative Es + (0, 0.001, 500, 2, 2.5), # Zero Es + (200000, 0.001, -500, 2, 2.5), # Negative f_ywd + (200000, 0.001, 0, 2, 2.5), # Zero f_ywd + (-200000, 0.001, -500, 2, 2.5), # Both negative + (200000, 0.001, 500, 2, 0.5), # cot_theta_min < 1 + (200000, 0.001, 500, 0.5, 2.5), # cot_theta < 1 + (200000, 0.001, 500, 3, 2.5), # cot_theta > cot_theta_min ], ) -def test_sigma_swd_value_errors(Es, eps_x, f_ywd, cot_theta): - """Test sigma_swd raises ValueError for negative Es or f_ywd.""" +def test_sigma_swd_value_errors(Es, eps_x, f_ywd, cot_theta, cot_theta_min): + """Test sigma_swd raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.sigma_swd(Es, eps_x, f_ywd, cot_theta) + _section_8_2_shear.sigma_swd( + Es, eps_x, f_ywd, cot_theta, cot_theta_min + ) @pytest.mark.parametrize( - 'tau_ed, rho_w, f_ywd, theta, z, b_w, a, x, expected', + 'tau_ed, rho_w, f_ywd, cot_theta, cot_theta_min, z, b_w, a, x, expected', [ - (0.5, 0.01, 500, 1, 300, 200, 500, 250, 0), - (0.4, 0.02, 400, 0.7, 350, 250, 600, 200, -45.5), + (0.5, 0.01, 500, 1, 2.5, 300, 200, 500, 250, 0), + (0.4, 0.02, 400, 1.5, 2.5, 350, 250, 600, 200, -101.5), + ( + 0.5, + 0.01, + 500, + 2.5, + 2.5, + 300, + 200, + 500, + 250, + 0, + ), # cot_theta = cot_theta_min ], ) -def test_delta_m_ed(tau_ed, rho_w, f_ywd, theta, z, b_w, a, x, expected): +def test_delta_m_ed( + tau_ed, rho_w, f_ywd, cot_theta, cot_theta_min, z, b_w, a, x, expected +): """Test calculation of additional moment ΔMEd.""" assert math.isclose( _section_8_2_shear.delta_MEd( - tau_ed, rho_w, f_ywd, theta, z, b_w, a, x + tau_ed, rho_w, f_ywd, cot_theta, cot_theta_min, z, b_w, a, x ), expected, rel_tol=1e-2, @@ -1144,37 +1624,94 @@ def test_delta_m_ed(tau_ed, rho_w, f_ywd, theta, z, b_w, a, x, expected): @pytest.mark.parametrize( - 'tau_ed, rho_w, f_ywd, cot_theta, z, b_w, a, x', + 'tau_ed, rho_w, f_ywd, cot_theta, cot_theta_min, z, b_w, a, x', [ - (-0.5, 0.01, 500, 1, 300, 200, 500, 250), # Negative tau_ed - (0.5, 0.01, -500, 1, 300, 200, 500, 250), # Negative f_ywd - (0.5, 0.01, 500, 1, -300, 200, 500, 250), # Negative z - (0.5, 0.01, 500, 1, 300, -200, 500, 250), # Negative b_w - (0.5, 0.01, 500, 1, 300, 200, -500, 250), # Negative a - (0.5, 0.01, 500, 1, 300, 200, 500, -250), # Negative x - (-0.5, 0.01, -500, 1, -300, -200, -500, -250), # All negative + (-0.5, 0.01, 500, 1, 2.5, 300, 200, 500, 250), # Negative tau_ed + (0.5, 0.01, 0, 1, 2.5, 300, 200, 500, 250), # Zero f_ywd + (0.5, 0.01, -500, 1, 2.5, 300, 200, 500, 250), # Negative f_ywd + (0.5, 0.01, 500, 1, 0.5, 300, 200, 500, 250), # cot_theta_min < 1 + (0.5, 0.01, 500, 0.5, 2.5, 300, 200, 500, 250), # cot_theta < 1 + ( + 0.5, + 0.01, + 500, + 3, + 2.5, + 300, + 200, + 500, + 250, + ), # cot_theta > cot_theta_min + (0.5, 0.01, 500, 1, 2.5, 0, 200, 500, 250), # Zero z + (0.5, 0.01, 500, 1, 2.5, -300, 200, 500, 250), # Negative z + (0.5, 0.01, 500, 1, 2.5, 300, 0, 500, 250), # Zero b_w + (0.5, 0.01, 500, 1, 2.5, 300, -200, 500, 250), # Negative b_w + (0.5, 0.01, 500, 1, 2.5, 300, 200, -500, 250), # Negative a + (0.5, 0.01, 500, 1, 2.5, 300, 200, 500, -250), # Negative x + ], +) +def test_delta_MEd_value_errors( + tau_ed, rho_w, f_ywd, cot_theta, cot_theta_min, z, b_w, a, x +): + """Test delta_MEd raises ValueError for invalid arguments.""" + with pytest.raises(ValueError): + _section_8_2_shear.delta_MEd( + tau_ed, rho_w, f_ywd, cot_theta, cot_theta_min, z, b_w, a, x + ) + + +@pytest.mark.parametrize( + 'cot_theta, alpha_w, cot_theta_min, expected', + [ + (1, 45, 2.5, 1.0), # tan(45/2) ≈ 0.414, 1.0 is within range + (2, 60, 3.0, 2.0), # tan(60/2) ≈ 0.577, so 2.0 is valid + (0.5, 60, 3.0, 0.5773502691896257), # Clamped to tan(30) + (4, 60, 3.0, 3.0), # Clamped to cot_theta_min + ], +) +def test_cot_theta_inclined(cot_theta, alpha_w, cot_theta_min, expected): + """Test calculation and validation of cot_theta for inclined + reinforcement (Eq. 8.58). + """ + result = _section_8_2_shear.cot_theta_inclined( + cot_theta, alpha_w, cot_theta_min + ) + assert math.isclose(result, expected, rel_tol=1e-2) + + +@pytest.mark.parametrize( + 'cot_theta, alpha_w, cot_theta_min', + [ + (1, 44, 2.5), # alpha_w < 45 + (1, 100, 2.5), # alpha_w > 90 + (1, 60, 0.5), # cot_theta_min < 1 ], ) -def test_delta_MEd_value_errors(tau_ed, rho_w, f_ywd, cot_theta, z, b_w, a, x): - """Test delta_MEd raises ValueError for negative arguments.""" +def test_cot_theta_inclined_value_errors(cot_theta, alpha_w, cot_theta_min): + """Test cot_theta_inclined raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.delta_MEd( - tau_ed, rho_w, f_ywd, cot_theta, z, b_w, a, x + _section_8_2_shear.cot_theta_inclined( + cot_theta, alpha_w, cot_theta_min ) @pytest.mark.parametrize( - 'rho_w, f_ywd, theta, alpha_w, cot_theta_min, expected', + 'rho_w, f_ywd, cot_theta, alpha_w, cot_theta_min, expected', [ - (0.01, 500, 1, 45, 1.5, 7.071), - (0.02, 450, 2, 60, 3, 20.088), + (0.01, 500, 1, 45, 3.0, 7.071), + (0.02, 450, 2, 60, 3.0, 20.088), + (0.01, 500, 1, 90, 3.0, 5.0), # alpha_w = 90° ], ) -def test_tau_rd_sy(rho_w, f_ywd, theta, alpha_w, cot_theta_min, expected): - """Test calculation of shear stress resistance τRd,sy.""" +def test_tau_Rd_sy_inclined( + rho_w, f_ywd, cot_theta, alpha_w, cot_theta_min, expected +): + """Test calculation of shear stress resistance τRd,sy for inclined + reinforcement (Eq. 8.59). + """ assert math.isclose( - _section_8_2_shear.tau_rd_sy( - rho_w, f_ywd, theta, alpha_w, cot_theta_min + _section_8_2_shear.tau_Rd_sy_inclined( + rho_w, f_ywd, cot_theta, alpha_w, cot_theta_min ), expected, rel_tol=1e-2, @@ -1184,18 +1721,20 @@ def test_tau_rd_sy(rho_w, f_ywd, theta, alpha_w, cot_theta_min, expected): @pytest.mark.parametrize( 'rho_w, f_ywd, cot_theta, alpha_w, cot_theta_min', [ - (0.01, -500, 2, 60, 3), # Negative f_ywd - (0.01, 500, 2, 44, 3), # alpha_w < 45 - (0.01, 500, 2, 90, 3), # alpha_w >= 90 - (0.01, 500, 2, 100, 3), # alpha_w > 90 + (-0.01, 500, 2, 60, 3.0), # Negative rho_w + (0.01, 0, 2, 60, 3.0), # Zero f_ywd + (0.01, -500, 2, 60, 3.0), # Negative f_ywd + (0.01, 500, 2, 44, 3.0), # alpha_w < 45 + (0.01, 500, 2, 100, 3.0), # alpha_w > 90 + (0.01, 500, 2, 60, 0.5), # cot_theta_min < 1 ], ) -def test_tau_rd_sy_value_errors( +def test_tau_Rd_sy_inclined_value_errors( rho_w, f_ywd, cot_theta, alpha_w, cot_theta_min ): - """Test tau_rd_sy raises ValueError for invalid f_ywd or alpha_w.""" + """Test tau_Rd_sy_inclined raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.tau_rd_sy( + _section_8_2_shear.tau_Rd_sy_inclined( rho_w, f_ywd, cot_theta, alpha_w, cot_theta_min ) @@ -1205,12 +1744,17 @@ def test_tau_rd_sy_value_errors( [ (0.5, 1, 45, 0.6, 30, 2, 0.5), (0.7759, 2, 60, 0.5, 40, 2, 1.50522), + (0.5, 1, 90, 0.6, 30, 2, 1.0), # alpha_w = 90° ], ) -def test_sigma_cd_s(tau_ed, theta, alpha_w, nu, f_cd, cot_theta_min, expected): - """Test calculation of compression stress σcd.""" +def test_sigma_cd_inclined( + tau_ed, theta, alpha_w, nu, f_cd, cot_theta_min, expected +): + """Test calculation of compression stress σcd for inclined reinforcement + (Eq. 8.60). + """ assert math.isclose( - _section_8_2_shear.sigma_cd_s( + _section_8_2_shear.sigma_cd_inclined( tau_ed, theta, alpha_w, nu, f_cd, cot_theta_min ), expected, @@ -1222,19 +1766,20 @@ def test_sigma_cd_s(tau_ed, theta, alpha_w, nu, f_cd, cot_theta_min, expected): 'tau_ed, cot_theta, alpha_w, nu, f_cd, cot_theta_min', [ (-0.5, 1, 60, 0.6, 30, 2), # Negative tau_ed + (0.5, 1, 60, -0.6, 30, 2), # Negative nu + (0.5, 1, 60, 0.6, 0, 2), # Zero f_cd (0.5, 1, 60, 0.6, -30, 2), # Negative f_cd (0.5, 1, 44, 0.6, 30, 2), # alpha_w < 45 - (0.5, 1, 90, 0.6, 30, 2), # alpha_w >= 90 (0.5, 1, 100, 0.6, 30, 2), # alpha_w > 90 - (-0.5, 1, 44, 0.6, -30, 2), # All invalid + (0.5, 1, 60, 0.6, 30, 0.5), # cot_theta_min < 1 ], ) -def test_sigma_cd_s_value_errors( +def test_sigma_cd_inclined_value_errors( tau_ed, cot_theta, alpha_w, nu, f_cd, cot_theta_min ): - """Test sigma_cd_s raises ValueError for invalid arguments.""" + """Test sigma_cd_inclined raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.sigma_cd_s( + _section_8_2_shear.sigma_cd_inclined( tau_ed, cot_theta, alpha_w, nu, f_cd, cot_theta_min ) @@ -1244,12 +1789,13 @@ def test_sigma_cd_s_value_errors( [ (100, 0.3, 45, 1, -58.5786), (80, 0.1, 50, 3, -29.8233), + (100, 1, 90, 2, 100.0), # alpha_w = 90° ], ) def test_n_vd(v_ed, theta, alpha_w, cot_theta_min, expected): """Test calculation of axial tensile force NVd.""" assert math.isclose( - _section_8_2_shear.NVds(v_ed, theta, alpha_w, cot_theta_min), + _section_8_2_shear.NVds_inclined(v_ed, theta, alpha_w, cot_theta_min), expected, rel_tol=1e-2, ) @@ -1258,16 +1804,17 @@ def test_n_vd(v_ed, theta, alpha_w, cot_theta_min, expected): @pytest.mark.parametrize( 'VEd, cot_theta, alpha_w, cot_theta_min', [ - (-100, 1, 60, 2), # Negative VEd (100, 1, 44, 2), # alpha_w < 45 - (100, 1, 90, 2), # alpha_w >= 90 (100, 1, 100, 2), # alpha_w > 90 + (100, 1, 60, 0.5), # cot_theta_min < 1 ], ) def test_NVds_value_errors(VEd, cot_theta, alpha_w, cot_theta_min): - """Test NVds raises ValueError for invalid arguments.""" + """Test NVds_inclined raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.NVds(VEd, cot_theta, alpha_w, cot_theta_min) + _section_8_2_shear.NVds_inclined( + VEd, cot_theta, alpha_w, cot_theta_min + ) @pytest.mark.parametrize( @@ -1276,6 +1823,7 @@ def test_NVds_value_errors(VEd, cot_theta, alpha_w, cot_theta_min): [ (0.6, 30, 1, 3, 0.01, 500, 45, 2, -3.8578), (0.5, 40, 0.7, 1, 0.02, 400, 60, 5, 6.9013), + (0.6, 30, 1, 3, 0.01, 500, 90, 2, -3.0), # alpha_w = 90° ], ) def test_tau_rd_incl( @@ -1283,7 +1831,7 @@ def test_tau_rd_incl( ): """Test calculation of shear stress resistance τRd.""" assert math.isclose( - _section_8_2_shear.tau_rd_incl( + _section_8_2_shear.tau_Rd_inclined( nu, f_cd, theta, beta_incl, rho_w, f_ywd, alpha_w, cot_theta_min ), expected, @@ -1294,11 +1842,15 @@ def test_tau_rd_incl( @pytest.mark.parametrize( 'nu, f_cd, cot_theta, cot_beta_incl, rho_w, f_ywd, alpha_w, cot_theta_min', [ + (-0.1, 30, 1, 3, 0.01, 500, 45, 2), # Negative nu (0.6, -30, 1, 3, 0.01, 500, 45, 2), # Negative f_cd + (0.6, 0, 1, 3, 0.01, 500, 45, 2), # Zero f_cd + (0.6, 30, 1, 3, -0.01, 500, 45, 2), # Negative rho_w (0.6, 30, 1, 3, 0.01, -500, 45, 2), # Negative f_ywd + (0.6, 30, 1, 3, 0.01, 0, 45, 2), # Zero f_ywd (0.6, 30, 1, 3, 0.01, 500, 44, 2), # alpha_w < 45 - (0.6, 30, 1, 3, 0.01, 500, 90, 2), # alpha_w >= 90 (0.6, 30, 1, 3, 0.01, 500, 100, 2), # alpha_w > 90 + (0.6, 30, 1, 3, 0.01, 500, 45, 0.5), # cot_theta_min < 1 ], ) def test_tau_rd_incl_value_errors( @@ -1306,7 +1858,7 @@ def test_tau_rd_incl_value_errors( ): """Test tau_rd_incl raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.tau_rd_incl( + _section_8_2_shear.tau_Rd_inclined( nu, f_cd, cot_theta, @@ -1318,34 +1870,53 @@ def test_tau_rd_incl_value_errors( ) -@pytest.mark.parametrize( - 'e_s, eps_x, theta, alpha_w, f_ywd, expected', - [ - (200000, 0.001, 1, 45, 500, 500), # Example values - (210000, 0.002, 2, 60, 450, 450), # Example values - ], -) -def test_sigma_swd_v2(e_s, eps_x, theta, alpha_w, f_ywd, expected): - """Test calculation of stress σswd.""" - assert math.isclose( - _section_8_2_shear.sigma_swd_v2(e_s, eps_x, theta, alpha_w, f_ywd), - expected, - rel_tol=1e-2, - ) - - @pytest.mark.parametrize( 'Es, eps_x, cot_theta, alpha_w, f_ywd', [ (-200000, 0.001, 2, 45, 500), # Negative Es + (0, 0.001, 2, 45, 500), # Zero Es (200000, 0.001, 2, 45, -500), # Negative f_ywd + (200000, 0.001, 2, 45, 0), # Zero f_ywd + (200000, 0.001, 2, 44, 500), # alpha_w < 45 + (200000, 0.001, 2, 100, 500), # alpha_w > 90 (-200000, 0.001, 2, 45, -500), # Both negative ], ) def test_sigma_swd_v2_value_errors(Es, eps_x, cot_theta, alpha_w, f_ywd): - """Test sigma_swd_v2 raises ValueError for negative Es or f_ywd.""" + """Test sigma_swd_inclined raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.sigma_swd_v2(Es, eps_x, cot_theta, alpha_w, f_ywd) + _section_8_2_shear.sigma_swd_inclined( + Es, eps_x, cot_theta, alpha_w, f_ywd + ) + + +# Test for sigma_swd_inclined with valid parameters (covers lines 1863-1874) +@pytest.mark.parametrize( + 'Es, eps_x, cot_theta, alpha_w, f_ywd, expected', + [ + # Test with alpha_w = 90.0 to cover lines 1863-1864 (isclose check) + (200000, 0.001, 1.5, 90.0, 500, 500), + # Test with alpha_w = 45.0 to cover line 1866 (else branch) + (200000, 0.001, 1.5, 45.0, 500, 500), + # Test with alpha_w = 60.0 to cover line 1866 (else branch) + (200000, 0.001, 1.5, 60.0, 500, 500), + # Test with alpha_w = 89.0 to cover line 1866 (else branch) + (200000, 0.001, 1.5, 89.0, 500, 500), + # Test with smaller eps_x to get a value less than f_ywd + (200000, 0.0001, 1.5, 90.0, 500, 295.0), + ], +) +def test_sigma_swd_inclined_valid( + Es, eps_x, cot_theta, alpha_w, f_ywd, expected +): + """Test sigma_swd_inclined with valid parameters. + + Covers lines 1863-1874. + """ + result = _section_8_2_shear.sigma_swd_inclined( + Es, eps_x, cot_theta, alpha_w, f_ywd + ) + assert result == pytest.approx(expected, rel=1e-2) @pytest.mark.parametrize( @@ -1357,7 +1928,7 @@ def test_sigma_swd_v2_value_errors(Es, eps_x, cot_theta, alpha_w, f_ywd): ) def test_shear_stress_resistance_reduced(tau_rd, m_ed, m_rd, expected): """Test calculation of reduced shear stress resistance.""" - assert _section_8_2_shear.tao_Rd_m(tau_rd, m_ed, m_rd) == pytest.approx( + assert _section_8_2_shear.tau_Rdm(tau_rd, m_ed, m_rd) == pytest.approx( expected, rel=10e-2 ) @@ -1368,13 +1939,14 @@ def test_shear_stress_resistance_reduced(tau_rd, m_ed, m_rd, expected): (-1.0, 2.0, 10.0), # Negative tau_rd (5.0, -2.0, 10.0), # Negative m_ed (5.0, 2.0, -10.0), # Negative m_rd + (5.0, 2.0, 0), # Zero m_rd (-1.0, -2.0, -10.0), # All negative ], ) def test_tao_Rd_m_value_errors(tau_rd, m_ed, m_rd): - """Test tao_Rd_m raises ValueError for negative arguments.""" + """Test tau_Rdm raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.tao_Rd_m(tau_rd, m_ed, m_rd) + _section_8_2_shear.tau_Rdm(tau_rd, m_ed, m_rd) @pytest.mark.parametrize( @@ -1386,7 +1958,7 @@ def test_tao_Rd_m_value_errors(tau_rd, m_ed, m_rd): ) def test_longitudinal_shear_stress(delta_fd, hf, delta_x, expected): """Test calculation of longitudinal shear stress.""" - assert _section_8_2_shear.tao_Ed_flang( + assert _section_8_2_shear.tau_Ed_flange( delta_fd, hf, delta_x ) == pytest.approx(expected, rel=10e-2) @@ -1405,39 +1977,7 @@ def test_longitudinal_shear_stress(delta_fd, hf, delta_x, expected): def test_tao_Ed_flang_value_errors(delta_fd, hf, delta_x): """Test tao_Ed_flang raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.tao_Ed_flang(delta_fd, hf, delta_x) - - -@pytest.mark.parametrize( - 'tau_ed, sf, hf, fyd, expected', - [ - (2.0, 150.0, 200.0, 500.0, 120.0), - (1.5, 100.0, 100.0, 400.0, 37.5), - (0.0, 200.0, 300.0, 600.0, 0.0), # zero shear stress - ], -) -def test_Ast_min_flang_valid(tau_ed, sf, hf, fyd, expected): - """Test Ast_min_flang with valid inputs.""" - assert _section_8_2_shear.Ast_min_flang( - tau_ed, sf, hf, fyd - ) == pytest.approx(expected, rel=1e-6) - - -@pytest.mark.parametrize( - 'tau_ed, sf, hf, fyd', - [ - (-1.0, 150.0, 200.0, 500.0), # Negative tau_ed - (2.0, 0.0, 200.0, 500.0), # Zero sf - (2.0, -150.0, 200.0, 500.0), # Negative sf - (2.0, 150.0, 0.0, 500.0), # Zero hf - (2.0, 150.0, -200.0, 500.0), # Negative hf - (2.0, 150.0, 200.0, -500.0), # Negative fyd - ], -) -def test_Ast_min_flang_value_errors(tau_ed, sf, hf, fyd): - """Test Ast_min_flang raises ValueError for invalid arguments.""" - with pytest.raises(ValueError): - _section_8_2_shear.Ast_min_flang(tau_ed, sf, hf, fyd) + _section_8_2_shear.tau_Ed_flange(delta_fd, hf, delta_x) @pytest.mark.parametrize( @@ -1451,7 +1991,7 @@ def test_transverse_reinforcement_flange( tau_ed, sf, hf, fyd, cot_theta_f, expected ): """Test calculation of transverse reinforcement in the flange.""" - assert _section_8_2_shear.Asf_flang( + assert _section_8_2_shear.Asf_flange( tau_ed, sf, hf, fyd, cot_theta_f ) == pytest.approx(expected, rel=10e-2) @@ -1465,43 +2005,110 @@ def test_transverse_reinforcement_flange( (2.0, 150.0, 0.0, 500.0, 1.0), # Zero hf (2.0, 150.0, -200.0, 500.0, 1.0), # Negative hf (2.0, 150.0, 200.0, -500.0, 1.0), # Negative fyd + (2.0, 150.0, 200.0, 0, 1.0), # Zero fyd + (2.0, 150.0, 200.0, 500.0, 0.5), # cot_theta_f < 1 ], ) def test_Asf_flang_value_errors(tau_ed, sf, hf, fyd, cot_theta_f): - """Test Asf_flang raises ValueError for invalid arguments.""" + """Test Asf_flange raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.Asf_flang(tau_ed, sf, hf, fyd, cot_theta_f) + _section_8_2_shear.Asf_flange(tau_ed, sf, hf, fyd, cot_theta_f) @pytest.mark.parametrize( - 'tau_ed, theta_f, fcd, nu, expected', + 'tau_ed, cot_theta_f, fcd, nu, expected', [ - (5, 45.0, 30.0, 0.5, 10), - (3, 30.0, 25.0, 0.5, 6.9282), + (5, 1.0, 30.0, 0.5, 10), # cot(45°) = 1.0 + (3, 1.732, 25.0, 0.5, 6.9282), # cot(30°) ≈ 1.732 ], ) -def test_sigma_cd_flang(tau_ed, theta_f, fcd, nu, expected): +def test_sigma_cd_flange(tau_ed, cot_theta_f, fcd, nu, expected): """Test check of compression field stress in the flange.""" - assert _section_8_2_shear.sigma_cd_flang( - tau_ed, theta_f, fcd, nu + assert _section_8_2_shear.sigma_cd_flange( + tau_ed, cot_theta_f, fcd, nu ) == pytest.approx(expected, rel=10e-2) @pytest.mark.parametrize( - 'tau_ed, theta_f, fcd, nu', + 'tau_ed, Ast_min, sf, hf, fyd, expected', + [ + ( + 1.0, + 100, + 200, + 200, + 500, + True, + ), # tau_ed <= (100/(200*200))*500 = 1.25 + ( + 2.0, + 100, + 200, + 200, + 500, + False, + ), # tau_ed > (100/(200*200))*500 = 1.25 + ( + 1.25, + 100, + 200, + 200, + 500, + True, + ), # tau_ed == (100/(200*200))*500 = 1.25 + ], +) +def test_check_tau_Ed_flange_verification( + tau_ed, Ast_min, sf, hf, fyd, expected +): + """Test check_tau_Ed_flange_verification.""" + assert ( + _section_8_2_shear.check_tau_Ed_flange_verification( + tau_ed, Ast_min, sf, hf, fyd + ) + == expected + ) + + +@pytest.mark.parametrize( + 'tau_ed, Ast_min, sf, hf, fyd', + [ + (-1.0, 100, 200, 200, 500), # Negative tau_ed + (1.0, -100, 200, 200, 500), # Negative Ast_min + (1.0, 100, 0, 200, 500), # Zero sf + (1.0, 100, -200, 200, 500), # Negative sf + (1.0, 100, 200, 0, 500), # Zero hf + (1.0, 100, 200, -200, 500), # Negative hf + (1.0, 100, 200, 200, 0), # Zero fyd + (1.0, 100, 200, 200, -500), # Negative fyd + ], +) +def test_check_tau_Ed_flange_verification_value_errors( + tau_ed, Ast_min, sf, hf, fyd +): + """Test check_tau_Ed_flange_verification raises ValueError.""" + with pytest.raises(ValueError): + _section_8_2_shear.check_tau_Ed_flange_verification( + tau_ed, Ast_min, sf, hf, fyd + ) + + +@pytest.mark.parametrize( + 'tau_ed, cot_theta_f, fcd, nu', [ - (-1.0, 45.0, 30.0, 0.5), # Negative tau_ed - (5.0, -45.0, 30.0, 0.5), # Negative theta_f - (5.0, 45.0, -30.0, 0.5), # Negative fcd - (5.0, 45.0, 30.0, 0.0), # nu zero - (5.0, 45.0, 30.0, -0.1), # nu negative - (-1.0, -45.0, -30.0, -0.1), # All negative + (-1.0, 1.0, 30.0, 0.5), # Negative tau_ed + (5.0, 0.5, 30.0, 0.5), # cot_theta_f < 1 + (5.0, 1.0, -30.0, 0.5), # Negative fcd + (5.0, 1.0, 0, 0.5), # Zero fcd + (5.0, 1.0, 30.0, 0.0), # nu zero + (5.0, 1.0, 30.0, -0.1), # nu negative + (-1.0, 0.5, -30.0, -0.1), # All invalid ], ) -def test_sigma_cd_flang_value_errors(tau_ed, theta_f, fcd, nu): - """Test sigma_cd_flang raises ValueError for invalid arguments.""" +def test_sigma_cd_flange_value_errors(tau_ed, cot_theta_f, fcd, nu): + """Test sigma_cd_flange raises ValueError for invalid arguments.""" with pytest.raises(ValueError): - _section_8_2_shear.sigma_cd_flang(tau_ed, theta_f, fcd, nu) + _section_8_2_shear.sigma_cd_flange(tau_ed, cot_theta_f, fcd, nu) # Valid inputs @@ -1555,11 +2162,12 @@ def test_calculate_tau_edi(VEdi, Ai, expected): [ (-10, 1000), # Negative VEdi (100, -1000), # Negative Ai + (100, 0), # Zero Ai (-5, -1000), # Both negative ], ) def test_tau_Edi_value_errors(VEdi, Ai): - """Test tau_Edi raises ValueError for negative VEdi or Ai.""" + """Test tau_Edi raises ValueError for invalid arguments.""" with pytest.raises(ValueError): _section_8_2_shear.tau_Edi(VEdi, Ai) @@ -1582,10 +2190,11 @@ def test_calculate_tau_edi_composite(beta_new, VEd, z, bi, expected): @pytest.mark.parametrize( 'beta_new, VEd, z, bi', [ - (-0.1, 1000, 200, 1000), - (0.5, -1, 200, 1000), - (0.5, 1000, 0, 1000), - (0.5, 1000, 200, -1), + (-0.1, 1000, 200, 1000), # Negative beta_new + (0.5, -1, 200, 1000), # Negative VEd + (0.5, 1000, 0, 1000), # Zero z + (0.5, 1000, 200, -1), # Negative bi + (0.5, 1000, 200, 0), # Zero bi ], ) def test_calculate_tau_edi_composite_errors(beta_new, VEd, z, bi): @@ -1600,15 +2209,21 @@ def test_calculate_tau_edi_composite_errors(beta_new, VEd, z, bi): (30, 5, 10000, 100, 500, 45, 0.1, 0.2, 1.5, 5.61), (35, 0, 20000, 200, 550, 90, 0.2, 0.25, 1.5, 2.1638), (40, 12, 15000, 150, 600, 135, 0.15, 0.3, 1.5, 1.2626), + # alpha_deg = 35 (boundary, covers lines 2225-2228 and 2246) + (30, 5, 10000, 100, 500, 35, 0.1, 0.2, 1.5, 6.0345), + # alpha_deg = 135 (boundary, covers lines 2225-2228 and 2246) + (30, 5, 10000, 100, 500, 135, 0.1, 0.2, 1.5, -1.4633), ], ) def test_tau_rdi( fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c, expected ): """Test the basic functionality and expected results of tau_Rdi.""" + # Calculate fcd from gamma_c (assuming eta_cc=1, k_tc=1 for test cases) + fcd = fck / gamma_c assert math.isclose( _section_8_2_shear.tau_Rdi( - fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c + fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c, fcd ), expected, rel_tol=1e-3, @@ -1616,25 +2231,57 @@ def test_tau_rdi( @pytest.mark.parametrize( - 'fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c', + 'fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c, fcd', [ - (-30, 5, 10000, 100, 500, 45, 0.1, 0.2, 1.5), # Negative fck - (30, 5, -10000, 100, 500, 45, 0.1, 0.2, 1.5), # Negative Ai - (30, 5, 10000, -100, 500, 45, 0.1, 0.2, 1.5), # Negative Asi - (30, 5, 10000, 100, -500, 45, 0.1, 0.2, 1.5), # Negative fyd - (30, 5, 10000, 100, 500, 20, 0.1, 0.2, 1.5), # alpha_deg < 35 - (30, 5, 10000, 100, 500, 140, 0.1, 0.2, 1.5), # alpha_deg > 135 - (30, 5, 10000, 100, 500, 45, 0.1, 0.2, -1.5), # Negative gamma_c - (-30, 5, -10000, -100, -500, 20, 0.1, 0.2, -1.5), # All negative + (-30, 5, 10000, 100, 500, 45, 0.1, 0.2, 1.5, 20), # Negative fck + (0, 5, 10000, 100, 500, 45, 0.1, 0.2, 1.5, 0), # Zero fck + (30, 5, -10000, 100, 500, 45, 0.1, 0.2, 1.5, 20), # Negative Ai + (30, 5, 0, 100, 500, 45, 0.1, 0.2, 1.5, 20), # Zero Ai + (30, 5, 10000, -100, 500, 45, 0.1, 0.2, 1.5, 20), # Negative Asi + (30, 5, 10000, 100, -500, 45, 0.1, 0.2, 1.5, 20), # Negative fyd + (30, 5, 10000, 100, 0, 45, 0.1, 0.2, 1.5, 20), # Zero fyd + # alpha_deg validation check (line 2226) + (30, 5, 10000, 100, 500, 20, 0.1, 0.2, 1.5, 20), # alpha_deg < 35 + ( + 30, + 5, + 10000, + 100, + 500, + 34, + 0.1, + 0.2, + 1.5, + 20, + ), # alpha_deg < 35 (edge) + (30, 5, 10000, 100, 500, 140, 0.1, 0.2, 1.5, 20), # alpha_deg > 135 + ( + 30, + 5, + 10000, + 100, + 500, + 136, + 0.1, + 0.2, + 1.5, + 20, + ), # alpha_deg > 135 (edge) + (30, 5, 10000, 100, 500, 45, 0.1, 0.2, -1.5, -20), # Negative gamma_c + (30, 5, 10000, 100, 500, 45, 0.1, 0.2, 0, 0), # Zero gamma_c + (30, 5, 10000, 100, 500, 45, 0.1, 0.2, 1.5, 0), # Zero fcd + (30, 5, 10000, 100, 500, 45, -0.1, 0.2, 1.5, 20), # Negative cv1 + (30, 5, 10000, 100, 500, 45, 0.1, -0.2, 1.5, 20), # Negative mu_v + (-30, 5, -10000, -100, -500, 20, 0.1, 0.2, -1.5, 20), # All negative ], ) def test_tau_Rdi_value_errors( - fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c + fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c, fcd ): """Test tau_Rdi raises ValueError.""" with pytest.raises(ValueError): _section_8_2_shear.tau_Rdi( - fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c + fck, sigma_n, Ai, Asi, fyd, alpha_deg, cv1, mu_v, gamma_c, fcd ) @@ -1656,6 +2303,12 @@ def test_cv1(surface_roughness, tensile_stress, expected): ) +def test_cv1_value_errors(): + """Test cv1 raises ValueError for unknown surface roughness.""" + with pytest.raises(ValueError): + _section_8_2_shear.cv1('unknown', False) + + # Test mu_v function @pytest.mark.parametrize( 'surface_roughness, expected', @@ -1672,6 +2325,12 @@ def test_mu_v(surface_roughness, expected): assert _section_8_2_shear.mu_v(surface_roughness) == expected +def test_mu_v_value_errors(): + """Test mu_v raises ValueError for unknown surface roughness.""" + with pytest.raises(ValueError): + _section_8_2_shear.mu_v('unknown') + + # Test cv2 function @pytest.mark.parametrize( 'surface_roughness, tensile_stress, expected', @@ -1689,6 +2348,12 @@ def test_cv2(surface_roughness, tensile_stress, expected): ) +def test_cv2_value_errors(): + """Test cv2 raises ValueError for unknown surface roughness.""" + with pytest.raises(ValueError): + _section_8_2_shear.cv2('unknown', False) + + # Test kv function @pytest.mark.parametrize( 'surface_roughness, expected', @@ -1704,6 +2369,12 @@ def test_kv(surface_roughness, expected): assert _section_8_2_shear.kv(surface_roughness) == expected +def test_kv_value_errors(): + """Test kv raises ValueError for unknown surface roughness.""" + with pytest.raises(ValueError): + _section_8_2_shear.kv('unknown') + + # Test kdowel function @pytest.mark.parametrize( 'surface_roughness, expected', @@ -1719,6 +2390,12 @@ def test_kdowel(surface_roughness, expected): assert _section_8_2_shear.kdowel(surface_roughness) == expected +def test_kdowel_value_errors(): + """Test kdowel raises ValueError for unknown surface roughness.""" + with pytest.raises(ValueError): + _section_8_2_shear.kdowel('unknown') + + @pytest.mark.parametrize( 'cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel, expected', [ @@ -1729,31 +2406,109 @@ def test_shear_stress_resistance( cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel, expected ): """Test the shear stress resistance calculation.""" - result = _section_8_2_shear.tau_Rdi_ny( - cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel + # Calculate fcd from gamma_c (assuming eta_cc=1, k_tc=1 for test cases) + fcd = fck / gamma_c + result = _section_8_2_shear.tau_Rdi_no_yielding( + cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel, fcd ) assert result == pytest.approx(expected, rel=10e-3) @pytest.mark.parametrize( - 'cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel', + 'cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel, fcd', [ - (0.1, -30, 1.5, 0.2, 0.3, 0.1, 0.02, 500, 0.0), # Negative fck - (0.1, 30, -1.5, 0.2, 0.3, 0.1, 0.02, 500, 0.0), # Negative gamma_c - (0.1, 30, 1.5, 0.2, 0.3, -0.1, 0.02, 500, 0.0), # Negative kv - (0.1, 30, 1.5, 0.2, 0.3, 0.1, -0.02, 500, 0.0), # Negative rho_i - (0.1, 30, 1.5, 0.2, 0.3, 0.1, 0.02, -500, 0.0), # Negative fyd - (0.1, 30, 1.5, 0.2, 0.3, 0.1, 0.02, 500, -0.1), # Negative kdowel - (0.1, -30, -1.5, 0.2, 0.3, -0.1, -0.02, -500, -0.1), # All negative + (-0.1, 30, 1.5, 0.2, 0.3, 0.1, 0.02, 500, 0.0, 20), # Negative cv2 + (0.1, -30, 1.5, 0.2, 0.3, 0.1, 0.02, 500, 0.0, 20), # Negative fck + (0.1, 0, 1.5, 0.2, 0.3, 0.1, 0.02, 500, 0.0, 0), # Zero fck + ( + 0.1, + 30, + -1.5, + 0.2, + 0.3, + 0.1, + 0.02, + 500, + 0.0, + -20, + ), # Negative gamma_c + (0.1, 30, 0, 0.2, 0.3, 0.1, 0.02, 500, 0.0, 0), # Zero gamma_c + (0.1, 30, 1.5, 0.2, 0.3, 0.1, 0.02, 500, 0.0, 0), # Zero fcd + # fyd validation check (line 2473) + ( + 0.1, + 30, + 1.5, + 0.2, + 0.3, + 0.1, + 0.02, + -500, + 0.0, + 20, + ), # Negative fyd + (0.1, 30, 1.5, 0.2, 0.3, 0.1, 0.02, 0, 0.0, 20), # Zero fyd + (0.1, 30, 1.5, -0.2, 0.3, 0.1, 0.02, 500, 0.0, 20), # Negative mu_v + # kv validation check (line 2479) + ( + 0.1, + 30, + 1.5, + 0.2, + 0.3, + -0.1, + 0.02, + 500, + 0.0, + 20, + ), # Negative kv + # rho_i validation check (line 2481) + ( + 0.1, + 30, + 1.5, + 0.2, + 0.3, + 0.1, + -0.02, + 500, + 0.0, + 20, + ), # Negative rho_i + # kdowel validation check (line 2483) + ( + 0.1, + 30, + 1.5, + 0.2, + 0.3, + 0.1, + 0.02, + 500, + -0.1, + 20, + ), # Negative kdowel + ( + 0.1, + -30, + -1.5, + 0.2, + 0.3, + -0.1, + -0.02, + -500, + -0.1, + 20, + ), # All negative ], ) -def test_tau_Rdi_ny_value_errors( - cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel +def test_tau_Rdi_no_yielding_value_errors( + cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel, fcd ): - """Test tau_Rdi_ny raises ValueError.""" + """Test tau_Rdi_no_yielding raises ValueError.""" with pytest.raises(ValueError): - _section_8_2_shear.tau_Rdi_ny( - cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel + _section_8_2_shear.tau_Rdi_no_yielding( + cv2, fck, gamma_c, mu_v, sigma_n, kv, rho_i, fyd, kdowel, fcd )