From 2b7375ac1dab408d0aef165f6a4a54680b808b88 Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Fri, 8 Nov 2024 20:03:40 +0100 Subject: [PATCH 01/10] Add draft unitset and unitmanager --- structuralcodes/core/_units.py | 135 +++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 structuralcodes/core/_units.py diff --git a/structuralcodes/core/_units.py b/structuralcodes/core/_units.py new file mode 100644 index 00000000..832711cb --- /dev/null +++ b/structuralcodes/core/_units.py @@ -0,0 +1,135 @@ +"""The unit manager.""" + +import typing as t +from dataclasses import dataclass + +from numpy.typing import ArrayLike + +# Type annotations for units +_LENGTH_LITERAL = t.Literal['mm', 'm'] +_FORCE_LITERAL = t.Literal['N', 'kN', 'MN'] + +# Unit conversion +_METER = 1.0 +_NEWTON = 1.0 + +_UNITS = { + 'length': { + 'm': _METER, + 'mm': _METER * 1e-3, + }, + 'force': { + 'N': _NEWTON, + 'kN': _NEWTON * 1e3, + 'MN': _NEWTON * 1e6, + }, +} + + +@dataclass +class UnitSet: + """A set of units that can be used in the UnitManager.""" + + length: _LENGTH_LITERAL = 'mm' + force: _FORCE_LITERAL = 'N' + + @property + def length_unit(self): + return _UNITS['length'][self.length] + + @property + def force_unit(self): + return _UNITS['force'][self.force] + + @property + def stress_unit(self): + return self.force_unit / self.length_unit**2 + + +class UnitManager: + """A class responsible for converting between different sets of units.""" + + _default_units: UnitSet + _alternative_units: UnitSet + + def __init__( + self, + default_units: UnitSet, + alternative_units: t.Optional[UnitSet] = None, + ) -> None: + self._default_units = default_units + self._alternative_units = alternative_units + + def convert_stress_to_default( + self, stress: t.Union[float, ArrayLike] + ) -> t.Union[float, ArrayLike]: + """Convert stresses from alternative units to default units.""" + return ( + stress + * self.alternative_units.stress_unit + / self.default_units.stress_unit + ) + + def convert_stress_from_default( + self, stress: t.Union[float, ArrayLike] + ) -> t.Union[float, ArrayLike]: + """Convert stresses from default units to alternative units.""" + return ( + stress + * self.default_units.stress_unit + / self.alternative_units.stress_unit + ) + + def convert_length_to_default( + self, length: t.Union[float, ArrayLike] + ) -> t.Union[float, ArrayLike]: + """Convert lengths from alternative units to default units.""" + return ( + length + * self.alternative_units.length_unit + / self.default_units.length_unit + ) + + def convert_length_from_default( + self, length: t.Union[float, ArrayLike] + ) -> t.Union[float, ArrayLike]: + """Convert lengths from default units to alternative units.""" + return ( + length + * self.default_units.length_unit + / self.alternative_units.length_unit + ) + + def convert_force_to_default( + self, force: t.Union[float, ArrayLike] + ) -> t.Union[float, ArrayLike]: + """Convert forces from alternative units to default units.""" + return ( + force + * self.alternative_units.force_unit + / self.default_units.force_unit + ) + + def convert_force_from_default( + self, force: t.Union[float, ArrayLike] + ) -> t.Union[float, ArrayLike]: + """Convert forces from default units to alternative units.""" + return ( + force + * self.default_units.force_unit + / self.alternative_units.force_unit + ) + + @property + def default_units(self) -> UnitSet: + """The default set of units.""" + return self._default_units + + @property + def alternative_units(self) -> UnitSet: + """The alternative set of units.""" + return ( + self._alternative_units + if self._alternative_units is not None + else self._default_units + ) From 5b158a01acc1429eace0d9cee0c0468a6ed9c8d1 Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Tue, 19 Nov 2024 22:18:33 +0100 Subject: [PATCH 02/10] Rename to UnitConverter and add post_init to unitset for validation --- structuralcodes/core/_units.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/structuralcodes/core/_units.py b/structuralcodes/core/_units.py index 832711cb..b64bbe36 100644 --- a/structuralcodes/core/_units.py +++ b/structuralcodes/core/_units.py @@ -28,7 +28,7 @@ @dataclass class UnitSet: - """A set of units that can be used in the UnitManager.""" + """A set of units that can be used in the UnitConverter.""" length: _LENGTH_LITERAL = 'mm' force: _FORCE_LITERAL = 'N' @@ -45,8 +45,28 @@ def force_unit(self): def stress_unit(self): return self.force_unit / self.length_unit**2 - -class UnitManager: + def __post_init__(self): + """Validate the provided units.""" + for attr in ('length', 'force'): + try: + # Try to find the provided unit among the available + _UNITS[attr][getattr(self, attr)] + except KeyError: + # The provided unit was not found + # Try to see if there was perhaps a mix of upper and lower case + # letters + for key in _UNITS[attr]: + if key.lower() == getattr(self, attr).lower().strip(): + setattr(self, attr, key) + break + else: + raise ValueError( + f'{getattr(self, attr)} is not a valid {attr} unit. ' + f'Use one of {", ".join(_UNITS[attr].keys())}.' + ) + + +class UnitConverter: """A class responsible for converting between different sets of units.""" _default_units: UnitSet From f4ce4e772882c97cc08b9c7f3fb78bb7e396fd25 Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Tue, 19 Nov 2024 22:19:17 +0100 Subject: [PATCH 03/10] Rename and refine methods and attrs of UnitConverter --- structuralcodes/core/_units.py | 113 ++++++++++++++++----------------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/structuralcodes/core/_units.py b/structuralcodes/core/_units.py index b64bbe36..3cfe080d 100644 --- a/structuralcodes/core/_units.py +++ b/structuralcodes/core/_units.py @@ -69,87 +69,86 @@ def __post_init__(self): class UnitConverter: """A class responsible for converting between different sets of units.""" - _default_units: UnitSet - _alternative_units: UnitSet + _from_units: UnitSet + _to_units: UnitSet def __init__( self, - default_units: UnitSet, - alternative_units: t.Optional[UnitSet] = None, + from_units: UnitSet, + to_units: t.Optional[UnitSet] = None, ) -> None: - self._default_units = default_units - self._alternative_units = alternative_units - - def convert_stress_to_default( + """Initialize a UnitConverter. + + Args: + from_units (UnitSet): The set of units to convert forwards from or + backwards to. + to_units (Optional(UnitSet)): The set of units to convert forwards + to or backwards from. If None, it is treated as equal to + from_units, and no conversion happens. + """ + self._from_units = from_units + self._to_units = to_units + + def convert_stress_backwards( self, stress: t.Union[float, ArrayLike] ) -> t.Union[float, ArrayLike]: - """Convert stresses from alternative units to default units.""" - return ( - stress - * self.alternative_units.stress_unit - / self.default_units.stress_unit - ) + """Convert stress backwards.""" + if self.from_units == self.to_units: + return stress + return stress * self.to_units.stress_unit / self.from_units.stress_unit - def convert_stress_from_default( + def convert_stress_forwards( self, stress: t.Union[float, ArrayLike] ) -> t.Union[float, ArrayLike]: - """Convert stresses from default units to alternative units.""" - return ( - stress - * self.default_units.stress_unit - / self.alternative_units.stress_unit - ) + """Convert stress forwards.""" + if self.from_units == self.to_units: + return stress + return stress * self.from_units.stress_unit / self.to_units.stress_unit - def convert_length_to_default( + def convert_length_backwards( self, length: t.Union[float, ArrayLike] ) -> t.Union[float, ArrayLike]: - """Convert lengths from alternative units to default units.""" - return ( - length - * self.alternative_units.length_unit - / self.default_units.length_unit - ) + """Convert length backwards.""" + if self.from_units == self.to_units: + return length + return length * self.to_units.length_unit / self.from_units.length_unit - def convert_length_from_default( + def convert_length_forwards( self, length: t.Union[float, ArrayLike] ) -> t.Union[float, ArrayLike]: - """Convert lengths from default units to alternative units.""" - return ( - length - * self.default_units.length_unit - / self.alternative_units.length_unit - ) + """Convert length forwards.""" + if self.from_units == self.to_units: + return length + return length * self.from_units.length_unit / self.to_units.length_unit - def convert_force_to_default( + def convert_force_backwards( self, force: t.Union[float, ArrayLike] ) -> t.Union[float, ArrayLike]: - """Convert forces from alternative units to default units.""" - return ( - force - * self.alternative_units.force_unit - / self.default_units.force_unit - ) + """Convert force backwards.""" + if self.from_units == self.to_units: + return force + return force * self.to_units.force_unit / self.from_units.force_unit - def convert_force_from_default( + def convert_force_forwards( self, force: t.Union[float, ArrayLike] ) -> t.Union[float, ArrayLike]: - """Convert forces from default units to alternative units.""" - return ( - force - * self.default_units.force_unit - / self.alternative_units.force_unit - ) + """Convert length forwards.""" + if self.from_units == self.to_units: + return force + return force * self.from_units.force_unit / self.to_units.force_unit @property - def default_units(self) -> UnitSet: - """The default set of units.""" - return self._default_units + def from_units(self) -> UnitSet: + """The set of units to convert from, i.e. we convert forwards from this + set of units. + """ + return self._from_units @property - def alternative_units(self) -> UnitSet: - """The alternative set of units.""" + def to_units(self) -> UnitSet: + """The set of units to convert to, i.e. we convert forwards to this set + of units. + """ return ( - self._alternative_units - if self._alternative_units is not None - else self._default_units + self._to_units if self._to_units is not None else self._from_units ) From 5669732f18a8c7b1c4f6218ff7d288a1d3a4f42c Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Tue, 19 Nov 2024 22:19:55 +0100 Subject: [PATCH 04/10] Update docstring and reference units --- structuralcodes/core/_units.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/structuralcodes/core/_units.py b/structuralcodes/core/_units.py index 3cfe080d..f25fb10c 100644 --- a/structuralcodes/core/_units.py +++ b/structuralcodes/core/_units.py @@ -1,4 +1,4 @@ -"""The unit manager.""" +"""Classes related to unit handling.""" import typing as t from dataclasses import dataclass @@ -6,22 +6,24 @@ from numpy.typing import ArrayLike # Type annotations for units -_LENGTH_LITERAL = t.Literal['mm', 'm'] +_LENGTH_LITERAL = t.Literal['mm', 'm', 'inch', 'foot'] _FORCE_LITERAL = t.Literal['N', 'kN', 'MN'] # Unit conversion -_METER = 1.0 -_NEWTON = 1.0 +_MILLIMETER = 1.0 # Only used as a reference and has no physical meaning +_NEWTON = 1e-3 # Only used as a reference and has no physical meaning _UNITS = { 'length': { - 'm': _METER, - 'mm': _METER * 1e-3, + 'm': _MILLIMETER * 1e3, # 1000 mm in one m + 'mm': _MILLIMETER, + 'inch': _MILLIMETER * 25.4, # 25.4 mm in one inch + 'foot': _MILLIMETER * 304.8, # 304.8 mm in one foot }, 'force': { 'N': _NEWTON, - 'kN': _NEWTON * 1e3, - 'MN': _NEWTON * 1e6, + 'kN': _NEWTON * 1e3, # 1000 N in one kN + 'MN': _NEWTON * 1e6, # 1000000 N in one MN }, } From 855caa19b0a9e412cba1befc763a481dea39e615 Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Tue, 19 Nov 2024 22:20:28 +0100 Subject: [PATCH 05/10] Add unit tests --- tests/test_core/test_units.py | 160 ++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/test_core/test_units.py diff --git a/tests/test_core/test_units.py b/tests/test_core/test_units.py new file mode 100644 index 00000000..39d4cfa7 --- /dev/null +++ b/tests/test_core/test_units.py @@ -0,0 +1,160 @@ +"""Tests for the classes that handle units.""" + +import math + +import pytest + +from structuralcodes.core._units import _UNITS, UnitConverter, UnitSet + + +@pytest.mark.parametrize( + 'force_unit', + list(_UNITS['force'].keys()), +) +@pytest.mark.parametrize( + 'length_unit', + list(_UNITS['length'].keys()), +) +def test_existing_units(length_unit: str, force_unit: str): + """Test if an existing unit is applied correctly.""" + # Arrange + units = UnitSet(length=length_unit, force=force_unit) + expected_length = _UNITS['length'].get(length_unit) + expected_force = _UNITS['force'].get(force_unit) + expected_stress = expected_force / expected_length**2 + + # Act + current_length = units.length_unit + current_force = units.force_unit + current_stress = units.stress_unit + + # Assert + assert current_length == expected_length + assert current_force == expected_force + assert current_stress == expected_stress + + +@pytest.mark.parametrize( + 'force, expected_force', + [ + ('n', 'N'), + ('KN', 'kN'), + ('mn', 'MN'), + ], +) +@pytest.mark.parametrize( + 'length, expected_length', + [ + ('M', 'm'), + ('MM', 'mm'), + ('Inch', 'inch'), + ('FoOt', 'foot'), + ], +) +def test_existing_unit_not_matching_case( + length: str, expected_length: str, force: str, expected_force: str +): + """Test if the unitset is not case sensitive.""" + # Act + units = UnitSet(length=length, force=force) + + # Assert + assert units.length == expected_length + assert units.force == expected_force + + +@pytest.mark.parametrize( + 'force', + [ + 'not valid force unit', + ], +) +@pytest.mark.parametrize( + 'length', + [ + 'not valid length', + ], +) +def test_not_existing_unit(length: str, force: str): + """Test if a ValueError is raised if an invalid unit is input.""" + # Assert + with pytest.raises(ValueError): + UnitSet(length=length, force=force) + + +@pytest.mark.parametrize( + 'force_from, force_to, scale', + [ + ('N', 'N', 1), + ('N', 'kN', 1e-3), + ('N', 'MN', 1e-6), + ], +) +def test_converting_force(force_from: str, force_to: str, scale: float): + """Test converting force from unit to unit.""" + # Arrange + from_units = UnitSet(force=force_from) + to_units = UnitSet(force=force_to) + converter = UnitConverter(from_units=from_units, to_units=to_units) + + # Act + converted_force_forwards = converter.convert_force_forwards(1) + converted_force_backwards = converter.convert_force_backwards(1) + + # Assert + assert math.isclose(converted_force_forwards, scale) + assert math.isclose(converted_force_backwards, 1 / scale) + + +@pytest.mark.parametrize( + 'length_from, length_to, scale', + [ + ('mm', 'mm', 1), + ('mm', 'm', 1e-3), + ], +) +def test_converting_length(length_from: str, length_to: str, scale: float): + """Test converting length from unit to unit.""" + # Arrange + from_units = UnitSet(length=length_from) + to_units = UnitSet(length=length_to) + converter = UnitConverter(from_units=from_units, to_units=to_units) + + # Act + converted_length_forwards = converter.convert_length_forwards(1) + converted_length_backwards = converter.convert_length_backwards(1) + + # Assert + assert math.isclose(converted_length_forwards, scale) + assert math.isclose(converted_length_backwards, 1 / scale) + + +@pytest.mark.parametrize( + 'force_from, force_to, length_from, length_to, scale', + [ + ('N', 'kN', 'mm', 'm', 1e3), + ('N', 'MN', 'mm', 'm', 1), + ('N', 'MN', 'm', 'm', 1e-6), + ('N', 'N', 'm', 'mm', 1e-6), + ], +) +def test_converting_stress( + force_from: str, + force_to: str, + length_from: str, + length_to: str, + scale: float, +): + """Test converting stress from unit to unit.""" + # Arrange + from_units = UnitSet(force=force_from, length=length_from) + to_units = UnitSet(force=force_to, length=length_to) + converter = UnitConverter(from_units=from_units, to_units=to_units) + + # Act + converted_stress_forwards = converter.convert_stress_forwards(1) + converted_stress_backwards = converter.convert_stress_backwards(1) + + # Assert + assert math.isclose(converted_stress_forwards, scale) + assert math.isclose(converted_stress_backwards, 1 / scale) From 62c320e3f0d3c7072d8cbc8da9370520d88185ff Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Wed, 20 Nov 2024 21:23:59 +0100 Subject: [PATCH 06/10] Add units in base materials --- structuralcodes/core/base.py | 27 ++++++++++++++++--- .../materials/concrete/_concrete.py | 8 +++--- .../materials/reinforcement/_reinforcement.py | 16 ++++++----- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/structuralcodes/core/base.py b/structuralcodes/core/base.py index 834fa05a..af1db6f2 100644 --- a/structuralcodes/core/base.py +++ b/structuralcodes/core/base.py @@ -10,24 +10,38 @@ from numpy.typing import ArrayLike import structuralcodes.core._section_results as s_res +from structuralcodes.core._units import UnitConverter, UnitSet class Material(abc.ABC): """Abstract base class for materials.""" _constitutive_law = None - - def __init__(self, density: float, name: t.Optional[str] = None) -> None: + _unit_converter: UnitConverter + _default_units: UnitSet + _units: UnitSet + + def __init__( + self, + density: float, + name: t.Optional[str] = None, + units: t.Optional[UnitSet] = None, + ) -> None: """Initializes an instance of a new material. Args: - density (float): density of the material in kg/m3 + density (float): Density of the material in kg/m3. Keyword Args: - name (Optional[str]): descriptive name of the material + name (Optional[str]): Descriptive name of the material. + units (Optional[UnitSet]): The selected set of units to work in. If + not set, the default set of units for the specific class is + used. """ self._density = abs(density) self._name = name if name is not None else 'Material' + self._units = units + self._unit_converter = UnitConverter(self._default_units, units) def update_attributes(self, updated_attributes: t.Dict) -> None: """Function for updating the attributes specified in the input @@ -64,6 +78,11 @@ def density(self): """Returns the density of the material in kg/m3.""" return self._density + @property + def unit_converter(self) -> UnitConverter: + """Returns the unit converter of the material.""" + return self._unit_converter + class ConstitutiveLaw(abc.ABC): """Abstract base class for constitutive laws.""" diff --git a/structuralcodes/materials/concrete/_concrete.py b/structuralcodes/materials/concrete/_concrete.py index 05af36d1..eaa23fc7 100644 --- a/structuralcodes/materials/concrete/_concrete.py +++ b/structuralcodes/materials/concrete/_concrete.py @@ -3,6 +3,7 @@ import abc import typing as t +from structuralcodes.core._units import UnitSet from structuralcodes.core.base import ConstitutiveLaw, Material from structuralcodes.materials.constitutive_laws import create_constitutive_law @@ -22,10 +23,11 @@ def __init__( density: float = 2400, gamma_c: t.Optional[float] = None, existing: t.Optional[bool] = False, + units: t.Optional[UnitSet] = None, ) -> None: """Initializes an abstract concrete material.""" name = name if name is not None else 'Concrete' - super().__init__(density=density, name=name) + super().__init__(density=density, name=name, units=units) self._fck = abs(fck) if existing: @@ -38,12 +40,12 @@ def __init__( @property def fck(self) -> float: - """Returns fck in MPa.""" + """Returns fck.""" return self._fck @fck.setter def fck(self, fck: float) -> None: - """Setter for fck (in MPa).""" + """Setter for fck.""" self._fck = abs(fck) self._reset_attributes() diff --git a/structuralcodes/materials/reinforcement/_reinforcement.py b/structuralcodes/materials/reinforcement/_reinforcement.py index f093682b..3736e1bd 100644 --- a/structuralcodes/materials/reinforcement/_reinforcement.py +++ b/structuralcodes/materials/reinforcement/_reinforcement.py @@ -3,6 +3,7 @@ import abc import typing as t +from structuralcodes.core._units import UnitSet from structuralcodes.core.base import ConstitutiveLaw, Material from structuralcodes.materials.constitutive_laws import ( ElasticPlastic, @@ -28,10 +29,11 @@ def __init__( epsuk: float, gamma_s: t.Optional[float] = None, name: t.Optional[str] = None, + units: t.Optional[UnitSet] = None, ) -> None: """Initializes an abstract reinforcement material.""" name = name if name is not None else 'Reinforcement' - super().__init__(density, name) + super().__init__(density, name, units=units) self._fyk = abs(fyk) self._Es = abs(Es) @@ -50,32 +52,32 @@ def __init__( @property def fyk(self) -> float: - """Returns fyk in MPa.""" + """Returns fyk.""" return self._fyk @fyk.setter def fyk(self, fyk: float) -> None: - """Setter for fyk (in MPa).""" + """Setter for fyk.""" self._fyk = abs(fyk) @property def Es(self) -> float: - """Returns Es in MPa.""" + """Returns Es.""" return self._Es @Es.setter def Es(self, Es: float) -> None: - """Setter for Es (in MPa).""" + """Setter for Es.""" self._Es = abs(Es) @property def ftk(self) -> float: - """Returns ftk in MPa.""" + """Returns ftk.""" return self._ftk @ftk.setter def ftk(self, ftk: float) -> None: - """Setter for ftk (in MPa).""" + """Setter for ftk.""" self._ftk = abs(ftk) @property From f95fc06ab267f9bd6b76371a4a5cdba106e9a321 Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Wed, 20 Nov 2024 21:26:13 +0100 Subject: [PATCH 07/10] Expose unitset and unitconverter in public api --- structuralcodes/core/__init__.py | 7 +++++++ tests/test_core/test_units.py | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/structuralcodes/core/__init__.py b/structuralcodes/core/__init__.py index bad5d163..f5b38b45 100644 --- a/structuralcodes/core/__init__.py +++ b/structuralcodes/core/__init__.py @@ -1 +1,8 @@ """Core functionality shared between other modules.""" + +from ._units import UnitConverter, UnitSet + +__all__ = [ + 'UnitConverter', + 'UnitSet', +] diff --git a/tests/test_core/test_units.py b/tests/test_core/test_units.py index 39d4cfa7..bec3ff73 100644 --- a/tests/test_core/test_units.py +++ b/tests/test_core/test_units.py @@ -4,7 +4,8 @@ import pytest -from structuralcodes.core._units import _UNITS, UnitConverter, UnitSet +from structuralcodes.core import UnitConverter, UnitSet +from structuralcodes.core._units import _UNITS @pytest.mark.parametrize( From 6037680360781ee59502ee7e3d1cf0394cb111bb Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Wed, 20 Nov 2024 21:32:32 +0100 Subject: [PATCH 08/10] Set default units and use unit converter in concrete materials --- .../materials/concrete/_concreteEC2_2004.py | 113 +++++++++++----- .../materials/concrete/_concreteEC2_2023.py | 104 ++++++++++---- .../materials/concrete/_concreteMC2010.py | 127 ++++++++++++------ 3 files changed, 246 insertions(+), 98 deletions(-) diff --git a/structuralcodes/materials/concrete/_concreteEC2_2004.py b/structuralcodes/materials/concrete/_concreteEC2_2004.py index ff945a2f..1ef550e9 100644 --- a/structuralcodes/materials/concrete/_concreteEC2_2004.py +++ b/structuralcodes/materials/concrete/_concreteEC2_2004.py @@ -4,6 +4,7 @@ import warnings from structuralcodes.codes import ec2_2004 +from structuralcodes.core._units import UnitSet from ._concrete import Concrete @@ -26,6 +27,7 @@ class ConcreteEC2_2004(Concrete): # noqa: N801 _n_parabolic_rectangular: t.Optional[float] = None _eps_c3: t.Optional[float] = None _eps_cu3: t.Optional[float] = None + _default_units = UnitSet(length='mm', force='N') def __init__( self, @@ -34,13 +36,13 @@ def __init__( density: float = 2400, gamma_c: t.Optional[float] = None, alpha_cc: t.Optional[float] = None, + units: t.Optional[UnitSet] = None, **kwargs, ) -> None: """Initializes a new instance of Concrete for EC2 2004. Arguments: - fck (float): Characteristic strength in MPa if concrete is not - existing. + fck (float): Characteristic strength. Keyword Arguments: name (str): A descriptive name for concrete. @@ -50,6 +52,8 @@ def __init__( alpha_cc (float, optional): A factor for considering long-term effects on the strength, and effects that arise from the way the load is applied. + units (Optional[UnitSet]): The selected set of units to work in. + The default is length=m and force=N. """ del kwargs if name is None: @@ -60,6 +64,7 @@ def __init__( density=density, existing=False, gamma_c=gamma_c, + units=units, ) self._alpha_cc = alpha_cc @@ -80,13 +85,17 @@ def _reset_attributes(self): @property def fcm(self) -> float: - """Returns fcm in MPa. + """Returns fcm. Returns: - float: The mean compressive strength in MPa. + float: The mean compressive strength. """ if self._fcm is None: - self._fcm = ec2_2004.fcm(self._fck) + self._fcm = self.unit_converter.convert_stress_forwards( + ec2_2004.fcm( + self.unit_converter.convert_stress_backwards(self._fck) + ) + ) return self._fcm @fcm.setter @@ -94,7 +103,7 @@ def fcm(self, value: float): """Sets a user defined value for fcm. Arguments: - value (float): The value of fcm in MPa. + value (float): The value of fcm. Raises: ValueError: If value is lower than fck. @@ -113,13 +122,17 @@ def fcm(self, value: float): @property def fctm(self) -> float: - """Returns fctm in MPa. + """Returns fctm. Returns: - float: The mean tensile strength in MPa. + float: The mean tensile strength. """ if self._fctm is None: - self._fctm = ec2_2004.fctm(self._fck) + self._fctm = self.unit_converter.convert_stress_forwards( + ec2_2004.fctm( + self.unit_converter.convert_stress_backwards(self._fck) + ) + ) return self._fctm @fctm.setter @@ -127,7 +140,7 @@ def fctm(self, value: float): """Sets a user defined value for fctm. Arguments: - value (float): The value of fctm in MPa. + value (float): The value of fctm. """ if value > 0.5 * self._fck: warnings.warn( @@ -137,76 +150,92 @@ def fctm(self, value: float): @property def fctk_5(self) -> float: - """Returns fctk_5 in MPa. + """Returns fctk_5. Returns: - float: The lower bound tensile strength in MPa. + float: The lower bound tensile strength. """ if self._fctk_5 is not None: return self._fctk_5 - return ec2_2004.fctk_5(self.fctm) + return self.unit_converter.convert_stress_forwards( + ec2_2004.fctk_5( + self.unit_converter.convert_stress_backwards(self.fctm) + ) + ) @fctk_5.setter def fctk_5(self, value: float): """Sets a user defined value for fctk_5. Arguments: - value (float): The value of fctk_5 in MPa. + value (float): The value of fctk_5. """ self._fctk_5 = abs(value) @property def fctk_95(self) -> float: - """Returns fctk_95 in MPa. + """Returns fctk_95. Returns: - float: The upper bound tensile strength in MPa. + float: The upper bound tensile strength. """ if self._fctk_95 is not None: return self._fctk_95 - return ec2_2004.fctk_95(self.fctm) + return self._unit_converter.convert_stress_forwards( + ec2_2004.fctk_95( + self._unit_converter.convert_stress_backwards(self.fctm) + ) + ) @fctk_95.setter def fctk_95(self, value: float): """Sets a user defined value for fctk_95. Arguments: - value (float): The value of fctk_95 in MPa. + value (float): The value of fctk_95. """ self._fctk_95 = abs(value) @property def Ecm(self) -> float: - """Returns Ecm in MPa. + """Returns Ecm. Returns: - float: The upper bound tensile strength in MPa. + float: The upper bound tensile strength. """ if self._Ecm is not None: return self._Ecm - return ec2_2004.Ecm(self.fcm) + return self.unit_converter.convert_stress_forwards( + ec2_2004.Ecm( + self.unit_converter.convert_stress_backwards(self.fcm) + ) + ) @Ecm.setter def Ecm(self, value: float): """Sets a user defined value for Ecm. Arguments: - value (float): The value of Ecm in MPa. + value (float): The value of Ecm. """ self._Ecm = abs(value) def fcd(self) -> float: - """Return the design compressive strength in MPa. + """Return the design compressive strength. Returns: - float: The design compressive strength of concrete in MPa. + float: The design compressive strength of concrete. """ # This method should perhaps become a property, but is left as a method # for now, to be consistent with other concretes. - return ec2_2004.fcd( - self.fck, alpha_cc=self.alpha_cc, gamma_c=self.gamma_c + return self.unit_converter.convert_stress_forwards( + ec2_2004.fcd( + self.unit_converter.convert_stress_backwards(self.fck), + alpha_cc=self.alpha_cc, + gamma_c=self.gamma_c, + ) ) @property @@ -231,7 +260,9 @@ def eps_c1(self) -> float: Returns: float: The strain at maximum compressive strength of concrete. """ - self._eps_c1 = self._eps_c1 or ec2_2004.eps_c1(self.fcm) + self._eps_c1 = self._eps_c1 or ec2_2004.eps_c1( + self.unit_converter.convert_stress_backwards(self.fcm) + ) return self._eps_c1 @eps_c1.setter @@ -256,7 +287,9 @@ def eps_cu1(self) -> float: Returns: float: The maximum strength at failure of concrete. """ - self._eps_cu1 = self._eps_cu1 or ec2_2004.eps_cu1(self.fcm) + self._eps_cu1 = self._eps_cu1 or ec2_2004.eps_cu1( + self.unit_converter.convert_stress_backwards(self.fcm) + ) return self._eps_cu1 @eps_cu1.setter @@ -281,8 +314,8 @@ def k_sargin(self) -> float: float: The plastic coefficient for Sargin law. """ self._k_sargin = self._k_sargin or ec2_2004.k_sargin( - Ecm=self.Ecm, - fcm=self.fcm, + Ecm=self.unit_converter.convert_stress_backwards(self.Ecm), + fcm=self.unit_converter.convert_stress_backwards(self.fcm), eps_c1=self.eps_c1, ) return self._k_sargin @@ -309,7 +342,9 @@ def eps_c2(self) -> float: Returns: float: The strain at maximum compressive strength of concrete. """ - self._eps_c2 = self._eps_c2 or ec2_2004.eps_c2(self.fck) + self._eps_c2 = self._eps_c2 or ec2_2004.eps_c2( + self.unit_converter.convert_stress_backwards(self.fck) + ) return self._eps_c2 @eps_c2.setter @@ -335,7 +370,9 @@ def eps_cu2(self) -> float: Returns: float: The maximum strain at failure of concrete. """ - self._eps_cu2 = self._eps_cu2 or ec2_2004.eps_cu2(self.fck) + self._eps_cu2 = self._eps_cu2 or ec2_2004.eps_cu2( + self.unit_converter.convert_stress_backwards(self.fck) + ) return self._eps_cu2 @eps_cu2.setter @@ -362,7 +399,9 @@ def n_parabolic_rectangular(self) -> float: """ self._n_parabolic_rectangular = ( self._n_parabolic_rectangular - or ec2_2004.n_parabolic_rectangular(self.fck) + or ec2_2004.n_parabolic_rectangular( + self.unit_converter.convert_stress_backwards(self.fck) + ) ) return self._n_parabolic_rectangular @@ -393,7 +432,9 @@ def eps_c3(self) -> float: Returns: float: The strain at maximum compressive strength of concrete. """ - self._eps_c3 = self._eps_c3 or ec2_2004.eps_c3(self.fck) + self._eps_c3 = self._eps_c3 or ec2_2004.eps_c3( + self.unit_converter.convert_stress_backwards(self.fck) + ) return self._eps_c3 @eps_c3.setter @@ -419,7 +460,9 @@ def eps_cu3(self) -> float: Returns: float: The maximum strain at failure of concrete. """ - self._eps_cu3 = self._eps_cu3 or ec2_2004.eps_cu3(self.fck) + self._eps_cu3 = self._eps_cu3 or ec2_2004.eps_cu3( + self.unit_converter.convert_stress_backwards(self.fck) + ) return self._eps_cu3 @eps_cu3.setter diff --git a/structuralcodes/materials/concrete/_concreteEC2_2023.py b/structuralcodes/materials/concrete/_concreteEC2_2023.py index 553b8d12..e830f92a 100644 --- a/structuralcodes/materials/concrete/_concreteEC2_2023.py +++ b/structuralcodes/materials/concrete/_concreteEC2_2023.py @@ -4,6 +4,7 @@ import warnings from structuralcodes.codes import ec2_2023 +from structuralcodes.core._units import UnitSet from ._concrete import Concrete @@ -11,6 +12,9 @@ class ConcreteEC2_2023(Concrete): # noqa: N801 """Concrete implementation for EC2 2023 Concrete.""" + # Units + _default_units = UnitSet(length='mm', force='N') + # Inherent concrete properties _kE: t.Optional[float] = None # noqa: N815 _strength_dev_class: t.Optional[str] = None @@ -39,13 +43,13 @@ def __init__( ] = 'CN', gamma_c: t.Optional[float] = None, existing: bool = False, + units: t.Optional[UnitSet] = None, **kwargs, ): """Initializes a new instance of Concrete for EC2 2023. Arguments: - fck (float): Characteristic strength in MPa if concrete is not - existing. + fck (float): Characteristic strength. Keyword Arguments: name (str): A descriptive name for concrete. @@ -57,6 +61,12 @@ def __init__( 1.5). existing (bool, optional): The material is of an existing structure (default: False). + units (Optional[UnitSet]): The selected set of units to work in. + The default is length=m and force=N. + + Note: + The arguments should be provided compatible with the selected set + of units. """ del kwargs if name is None: @@ -83,6 +93,7 @@ def __init__( density=density, existing=existing, gamma_c=gamma_c, + units=units, ) self._kE = kE self._strength_dev_class = strength_dev_class @@ -107,9 +118,13 @@ def fcm(self) -> float: """Returns the mean strength of concrete. Returns: - float: The mean compressive strength in MPa. + float: The mean compressive strength. """ - self._fcm = self._fcm or ec2_2023.fcm(self.fck) + self._fcm = self._fcm or self.unit_converter.convert_stress_forwards( + ec2_2023.fcm( + self.unit_converter.convert_stress_backwards(self.fck) + ) + ) return self._fcm @fcm.setter @@ -117,7 +132,7 @@ def fcm(self, value: float): """Sets a user defined value for the mean strength of concrete. Arguments: - value (float): the value of the mean strength of concrete in MPa. + value (float): the value of the mean strength of concrete. Raises: ValueError: If value is less or equal than the value of fck. @@ -141,7 +156,11 @@ def fctm(self) -> None: Returns: float: The mean concrete tensile strength. """ - self._fctm = self._fctm or ec2_2023.fctm(self.fck) + self._fctm = self._fctm or self.unit_converter.convert_stress_forwards( + ec2_2023.fctm( + self.unit_converter.convert_stress_backwards(self.fck) + ) + ) return self._fctm @fctm.setter @@ -150,7 +169,7 @@ def fctm(self, value: float): for the concrete. Arguments: - value (float): The new value for fctm in MPa. + value (float): The new value for fctm. """ self._fctm = value @@ -159,9 +178,16 @@ def fctk_5(self) -> float: """Returns the 5% mean concrete tensile strength fractile. Returns: - float: The 5% mean concrete tensile strength fractile in MPa. + float: The 5% mean concrete tensile strength fractile. """ - self._fctk_5 = self._fctk_5 or ec2_2023.fctk_5(self.fctm) + self._fctk_5 = ( + self._fctk_5 + or self.unit_converter.convert_stress_forwards( + ec2_2023.fctk_5( + self.unit_converter.convert_stress_backwards(self.fctm) + ) + ) + ) return self._fctk_5 @property @@ -169,9 +195,16 @@ def fctk_95(self) -> float: """Returns the 95% mean concrete tensile strength fractile. Returns: - float: The 5% mean concrete tensile strength fractile in MPa. + float: The 5% mean concrete tensile strength fractile. """ - self._fctk_95 = self._fctk_95 or ec2_2023.fctk_95(self.fctm) + self._fctk_95 = ( + self._fctk_95 + or self.unit_converter.convert_stress_forwards( + ec2_2023.fctk_95( + self.unit_converter.convert_stress_backwards(self.fctm) + ) + ) + ) return self._fctk_95 @property @@ -179,9 +212,14 @@ def Ecm(self) -> float: """Returns the secant modulus. Returns: - float: The secant concrete modulus in MPa. + float: The secant concrete modulus. """ - self._Ecm = self._Ecm or ec2_2023.Ecm(self.fcm, self._kE) + self._Ecm = self._Ecm or self.unit_converter.convert_stress_forwards( + ec2_2023.Ecm( + self.unit_converter.convert_stress_backwards(self.fcm), + self._kE, + ) + ) return self._Ecm @Ecm.setter @@ -189,7 +227,7 @@ def Ecm(self, value: float) -> None: """Sets the secant modulus. Arguments: - float: The secand modulus value in MPa. + float: The secant modulus value. """ self._Ecm = value @@ -206,16 +244,26 @@ def fcd( MPa (default is 40 MPa). Returns: - float: The design compressive strength of concrete in MPa. + float: The design compressive strength of concrete. Raises: ValueError: If fck_ref is less or equal to 0. ValueError: If t_ref is less than 0. ValueError: If t0 is less than 0. """ - eta_cc = ec2_2023.eta_cc(self.fck, fck_ref=fck_ref) + eta_cc = ec2_2023.eta_cc( + self.unit_converter.convert_stress_backwards(self.fck), + fck_ref=fck_ref, + ) k_tc = ec2_2023.k_tc(t_ref, t0, self._strength_dev_class) - return ec2_2023.fcd(self.fck, eta_cc, k_tc, self.gamma_c) + return self.unit_converter.convert_stress_forwards( + ec2_2023.fcd( + self.unit_converter.convert_stress_backwards(self.fck), + eta_cc, + k_tc, + self.gamma_c, + ) + ) def fctd(self, t_ref: float = 28) -> float: """Computes the value of the design tensile strength of concrete. @@ -225,7 +273,7 @@ def fctd(self, t_ref: float = 28) -> float: days). Returns: - float: The design tensile strength of concrete in MPa. + float: The design tensile strength of concrete. Raises: ValueError: If t_ref is less than 0. @@ -233,7 +281,13 @@ def fctd(self, t_ref: float = 28) -> float: k_tt = ec2_2023.k_tt( t_ref=t_ref, strength_dev_class=self._strength_dev_class ) - return ec2_2023.fctd(self.fctk_5, k_tt, self.gamma_c) + return self.unit_converter.convert_stress_forwards( + ec2_2023.fctd( + self.unit_converter.convert_stress_backwards(self.fctk_5), + k_tt, + self.gamma_c, + ) + ) @property def eps_c1(self) -> float: @@ -243,7 +297,9 @@ def eps_c1(self) -> float: Returns: float: The strain at maximum compressive strength of concrete. """ - self._eps_c1 = self._eps_c1 or ec2_2023.eps_c1(self.fcm) + self._eps_c1 = self._eps_c1 or ec2_2023.eps_c1( + self.unit_converter.convert_stress_backwards(self.fcm) + ) return self._eps_c1 @eps_c1.setter @@ -268,7 +324,9 @@ def eps_cu1(self) -> float: Returns: float: The maximum strength at failure of concrete. """ - self._eps_cu1 = self._eps_cu1 or ec2_2023.eps_cu1(self.fcm) + self._eps_cu1 = self._eps_cu1 or ec2_2023.eps_cu1( + self.unit_converter.convert_stress_backwards(self.fcm) + ) return self._eps_cu1 @eps_cu1.setter @@ -293,8 +351,8 @@ def k_sargin(self) -> float: float: k coefficient for Sargin constitutive law. """ self._k_sargin = self._k_sargin or ec2_2023.k_sargin( - Ecm=self.Ecm, - fcm=self.fcm, + Ecm=self.unit_converter.convert_stress_backwards(self.Ecm), + fcm=self.unit_converter.convert_stress_backwards(self.fcm), eps_c1=self.eps_c1, ) return self._k_sargin diff --git a/structuralcodes/materials/concrete/_concreteMC2010.py b/structuralcodes/materials/concrete/_concreteMC2010.py index 47cc3d30..7d61a1f7 100644 --- a/structuralcodes/materials/concrete/_concreteMC2010.py +++ b/structuralcodes/materials/concrete/_concreteMC2010.py @@ -4,6 +4,7 @@ import warnings from structuralcodes.codes import mc2010 +from structuralcodes.core._units import UnitSet from ._concrete import Concrete @@ -11,6 +12,9 @@ class ConcreteMC2010(Concrete): """Concrete implementation for MC 2010.""" + # Units + _default_units = UnitSet(length='mm', force='N') + # computed values _fcm: t.Optional[float] = None _fctm: t.Optional[float] = None @@ -37,13 +41,13 @@ def __init__( gamma_c: t.Optional[float] = None, existing: bool = False, alpha_cc: t.Optional[float] = None, + units: t.Optional[UnitSet] = None, **kwargs, ): """Initializes a new instance of Concrete for MC 2010. Arguments: - fck (float): Characteristic strength in MPa if concrete is not - existing. + fck (float): Characteristic strength. Keyword Arguments: name (Optional(str)): A descriptive name for concrete. @@ -54,6 +58,12 @@ def __init__( alpha_cc (float, optional): A factor for considering long-term effects on the strength, and effects that arise from the way the load is applied. + units (Optional[UnitSet]): The selected set of units to work in. + The default is length=m and force=N. + + Note: + The arguments should be provided compatible with the selected set + of units. """ del kwargs if name is None: @@ -64,6 +74,7 @@ def __init__( density=density, existing=existing, gamma_c=gamma_c, + units=units, ) self._alpha_cc = alpha_cc @@ -85,12 +96,14 @@ def _reset_attributes(self): @property def fcm(self) -> float: - """Returns fcm in MPa. + """Returns fcm. Returns: - float: The mean compressive strength in MPa. + float: The mean compressive strength. """ - self._fcm = self._fcm or mc2010.fcm(self._fck) + self._fcm = self._fcm or self.unit_converter.convert_stress_forwards( + mc2010.fcm(self.unit_converter.convert_stress_backwards(self._fck)) + ) return self._fcm @fcm.setter @@ -98,7 +111,7 @@ def fcm(self, value: float): """Sets a user defined value for fcm. Arguments: - value (float): The value of fcm in MPa. + value (float): The value of fcm. Raises: ValueError: If value is lower than fck. @@ -117,12 +130,13 @@ def fcm(self, value: float): @property def Eci(self) -> float: - """Returns the modulus of elasticity in MPa at the concrete age of 28 - days. + """Returns the modulus of elasticity at the concrete age of 28 days. It is assumed a normal concrete with quartzite aggregates (alfa_e = 1) """ - self._Eci = self._Eci or mc2010.Eci(self.fcm) + self._Eci = self._Eci or self.unit_converter.convert_stress_forwards( + mc2010.Eci(self.unit_converter.convert_stress_backwards(self.fcm)) + ) return self._Eci @Eci.setter @@ -131,23 +145,22 @@ def Eci(self, value: float): age of 28 days, Eci. Arguments: - value (float): The value of Eci in MPa. + value (float): The value of Eci. """ - if value < 1e4 or value > 1e5: - warnings.warn( - 'A suspect value of Eci has been input.\n' - 'Please check Eci that should be in MPa ({value} given).' - ) self._Eci = abs(value) @property def fctm(self) -> float: - """Returns fctm in MPa. + """Returns fctm. Returns: - float: The mean tensile strength in MPa. + float: The mean tensile strength. """ - self._fctm = self._fctm or mc2010.fctm(self._fck) + self._fctm = self._fctm or self.unit_converter.convert_stress_forwards( + mc2010.fctm( + self.unit_converter.convert_stress_backwards(self._fck) + ) + ) return self._fctm @fctm.setter @@ -155,7 +168,7 @@ def fctm(self, value: float): """Sets a user defined value for fctm. Arguments: - value (float): The value of fctm in MPa. + value (float): The value of fctm. """ if value > 0.5 * self._fck: warnings.warn( @@ -165,12 +178,19 @@ def fctm(self, value: float): @property def fctkmin(self) -> float: - """Returns fctkmin in MPa. + """Returns fctkmin. Returns: - float: The lower bound tensile strength in MPa. + float: The lower bound tensile strength. """ - self._fctkmin = self._fctkmin or mc2010.fctkmin(self.fctm) + self._fctkmin = ( + self._fctkmin + or self.unit_converter.convert_stress_forwards( + mc2010.fctkmin( + self.unit_converter.convert_stress_backwards(self.fctm) + ) + ) + ) return self._fctkmin @fctkmin.setter @@ -178,18 +198,25 @@ def fctkmin(self, value: float): """Sets a user defined value for fctkmin. Arguments: - value (float): The value of fctkmin in MPa. + value (float): The value of fctkmin. """ self._fctkmin = abs(value) @property def fctkmax(self) -> float: - """Returns fctkmax in MPa. + """Returns fctkmax. Returns: - float: The upper bound tensile strength in MPa. + float: The upper bound tensile strength. """ - self._fctkmax = self._fctkmax or mc2010.fctkmax(self.fctm) + self._fctkmax = ( + self._fctkmax + or self.unit_converter.convert_stress_forwards( + mc2010.fctkmax( + self.unit_converter.convert_stress_backwards(self.fctm) + ) + ) + ) return self._fctkmax @fctkmax.setter @@ -197,7 +224,7 @@ def fctkmax(self, value: float): """Sets a user defined value for fctkmax. Arguments: - value (float): The value of fctkmax in MPa. + value (float): The value of fctkmax. """ self._fctkmax = abs(value) @@ -216,7 +243,7 @@ def Gf(self, value: float): """Sets a user defined value for fracture energy Gf. Arguments: - value (float): The value of Gf in N/m. + value (float): The value of Gf. """ self._Gf = abs(value) @@ -226,15 +253,19 @@ def gamma_c(self) -> float: return self._gamma_c or 1.5 def fcd(self) -> float: - """Return the design compressive strength in MPa. + """Return the design compressive strength. Returns: - float: The design compressive strength of concrete in MPa. + float: The design compressive strength of concrete. """ # This method should perhaps become a property, but is left as a method # for now, to be consistent with other concretes. - return mc2010.fcd( - self.fck, alpha_cc=self.alpha_cc, gamma_c=self.gamma_c + return self.unit_converter.convert_stress_forwards( + mc2010.fcd( + self.unit_converter.convert_stress_backwards(self.fck), + alpha_cc=self.alpha_cc, + gamma_c=self.gamma_c, + ) ) @property @@ -252,7 +283,9 @@ def eps_c1(self) -> float: Returns: float: The strain at maximum compressive strength of concrete. """ - self._eps_c1 = self._eps_c1 or mc2010.eps_c1(self._fck) + self._eps_c1 = self._eps_c1 or mc2010.eps_c1( + self.unit_converter.convert_stress_backwards(self._fck) + ) return self._eps_c1 @eps_c1.setter @@ -277,7 +310,9 @@ def eps_cu1(self) -> float: Returns: float: The maximum strength at failure of concrete. """ - self._eps_cu1 = self._eps_cu1 or mc2010.eps_cu1(self._fck) + self._eps_cu1 = self._eps_cu1 or mc2010.eps_cu1( + self.unit_converter.convert_stress_backwards(self._fck) + ) return self._eps_cu1 @eps_cu1.setter @@ -301,7 +336,9 @@ def k_sargin(self) -> float: Returns: float: The plastic coefficient for Sargin law. """ - self._k_sargin = self._k_sargin or mc2010.k_sargin(self._fck) + self._k_sargin = self._k_sargin or mc2010.k_sargin( + self.unit_converter.convert_stress_backwards(self._fck) + ) return self._k_sargin @k_sargin.setter @@ -326,7 +363,9 @@ def eps_c2(self) -> float: Returns: float: The strain at maximum compressive strength of concrete. """ - self._eps_c2 = self._eps_c2 or mc2010.eps_c2(self.fck) + self._eps_c2 = self._eps_c2 or mc2010.eps_c2( + self.unit_converter.convert_stress_backwards(self.fck) + ) return self._eps_c2 @eps_c2.setter @@ -352,7 +391,9 @@ def eps_cu2(self) -> float: Returns: float: The maximum strain at failure of concrete. """ - self._eps_cu2 = self._eps_cu2 or mc2010.eps_cu2(self.fck) + self._eps_cu2 = self._eps_cu2 or mc2010.eps_cu2( + self.unit_converter.convert_stress_backwards(self.fck) + ) return self._eps_cu2 @eps_cu2.setter @@ -379,7 +420,9 @@ def n_parabolic_rectangular(self) -> float: """ self._n_parabolic_rectangular = ( self._n_parabolic_rectangular - or mc2010.n_parabolic_rectangular(self.fck) + or mc2010.n_parabolic_rectangular( + self.unit_converter.convert_stress_backwards(self.fck) + ) ) return self._n_parabolic_rectangular @@ -410,7 +453,9 @@ def eps_c3(self) -> float: Returns: float: The strain at maximum compressive strength of concrete. """ - self._eps_c3 = self._eps_c3 or mc2010.eps_c3(self.fck) + self._eps_c3 = self._eps_c3 or mc2010.eps_c3( + self.unit_converter.convert_stress_backwards(self.fck) + ) return self._eps_c3 @eps_c3.setter @@ -436,7 +481,9 @@ def eps_cu3(self) -> float: Returns: float: The maximum strain at failure of concrete. """ - self._eps_cu3 = self._eps_cu3 or mc2010.eps_cu3(self.fck) + self._eps_cu3 = self._eps_cu3 or mc2010.eps_cu3( + self.unit_converter.convert_stress_backwards(self.fck) + ) return self._eps_cu3 @eps_cu3.setter From a5fc5755664acd07cea587a6eb0c2317635231cd Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Wed, 20 Nov 2024 21:38:39 +0100 Subject: [PATCH 09/10] Set default units and use unit converter in reinforcement materials --- .../reinforcement/_reinforcementEC2_2004.py | 31 ++++++++++++++++--- .../reinforcement/_reinforcementEC2_2023.py | 31 ++++++++++++++++--- .../reinforcement/_reinforcementMC2010.py | 31 ++++++++++++++++--- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py b/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py index f22dea4a..5d962d61 100644 --- a/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py +++ b/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py @@ -3,6 +3,7 @@ import typing as t from structuralcodes.codes import ec2_2004 +from structuralcodes.core._units import UnitSet from ._reinforcement import Reinforcement @@ -10,6 +11,8 @@ class ReinforcementEC2_2004(Reinforcement): # noqa: N801 """Reinforcement implementation for EC2 2004.""" + _default_units = UnitSet(length='mm', force='N') + def __init__( self, fyk: float, @@ -20,13 +23,14 @@ def __init__( gamma_eps: t.Optional[float] = None, name: t.Optional[str] = None, density: float = 7850.0, + units: t.Optional[UnitSet] = None, ): """Initializes a new instance of Reinforcement for EC2 2004. Arguments: - fyk (float): Characteristic yield strength in MPa. - Es (float): The Young's modulus in MPa. - ftk (float): Characteristic ultimate strength in MPa. + fyk (float): Characteristic yield strength. + Es (float): The Young's modulus. + ftk (float): Characteristic ultimate strength. epsuk (float): The characteristik strain at the ultimate stress level. gamma_s (Optional(float)): The partial factor for reinforcement. @@ -35,6 +39,12 @@ def __init__( Keyword Arguments: name (str): A descriptive name for the reinforcement. density (float): Density of material in kg/m3 (default: 7850). + units (Optional[UnitSet]): The selected set of units to work in. + The default is length=m and force=N. + + Note: + The arguments should be provided compatible with the selected set + of units. """ if name is None: name = f'Reinforcement{round(fyk):d}' @@ -48,11 +58,17 @@ def __init__( ftk=ftk, epsuk=epsuk, gamma_s=gamma_s, + units=units, ) def fyd(self) -> float: """The design yield strength.""" - return ec2_2004.fyd(self.fyk, self.gamma_s) + return self.unit_converter.convert_stress_forwards( + ec2_2004.fyd( + self.unit_converter.convert_stress_backwards(self.fyk), + self.gamma_s, + ) + ) @property def gamma_s(self) -> float: @@ -63,7 +79,12 @@ def gamma_s(self) -> float: def ftd(self) -> float: """The design ultimate strength.""" - return ec2_2004.fyd(self.ftk, self.gamma_s) + return self.unit_converter.convert_stress_forwards( + ec2_2004.fyd( + self.unit_converter.convert_stress_backwards(self.ftk), + self.gamma_s, + ) + ) def epsud(self) -> float: """The design ultimate strain.""" diff --git a/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py b/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py index 61c7e70e..0fed419f 100644 --- a/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py +++ b/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py @@ -3,6 +3,7 @@ import typing as t from structuralcodes.codes import ec2_2023 +from structuralcodes.core._units import UnitSet from ._reinforcement import Reinforcement @@ -10,6 +11,8 @@ class ReinforcementEC2_2023(Reinforcement): # noqa: N801 """Reinforcement implementation for EC2 2023.""" + _default_units = UnitSet(length='mm', force='N') + def __init__( self, fyk: float, @@ -19,13 +22,14 @@ def __init__( gamma_s: t.Optional[float] = None, name: t.Optional[str] = None, density: float = 7850.0, + units: t.Optional[UnitSet] = None, ): """Initializes a new instance of Reinforcement for EC2 2023. Args: - fyk (float): Characteristic yield strength in MPa. - Es (float): The Young's modulus in MPa. - ftk (float): Characteristic ultimate strength in MPa. + fyk (float): Characteristic yield strength. + Es (float): The Young's modulus. + ftk (float): Characteristic ultimate strength. epsuk (float): The characteristik strain at the ultimate stress level. gamma_s (Optional(float)): The partial factor for reinforcement. @@ -34,6 +38,12 @@ def __init__( Keyword Args: name (str): A descriptive name for the reinforcement. density (float): Density of material in kg/m3 (default: 7850). + units (Optional[UnitSet]): The selected set of units to work in. + The default is length=m and force=N. + + Note: + The arguments should be provided compatible with the selected set + of units. """ if name is None: name = f'Reinforcement{round(fyk):d}' @@ -45,15 +55,26 @@ def __init__( ftk=ftk, epsuk=epsuk, gamma_s=gamma_s, + units=units, ) def fyd(self) -> float: """The design yield strength.""" - return ec2_2023.fyd(self.fyk, self.gamma_s) + return self.unit_converter.convert_stress_forwards( + ec2_2023.fyd( + self.unit_converter.convert_stress_backwards(self.fyk), + self.gamma_s, + ) + ) def ftd(self) -> float: """The design ultimate strength.""" - return ec2_2023.fyd(self.ftk, self.gamma_s) + return self.unit_converter.convert_stress_forwards( + ec2_2023.fyd( + self.unit_converter.convert_stress_backwards(self.ftk), + self.gamma_s, + ) + ) def epsud(self) -> float: """The design ultimate strain.""" diff --git a/structuralcodes/materials/reinforcement/_reinforcementMC2010.py b/structuralcodes/materials/reinforcement/_reinforcementMC2010.py index e4c9e4af..544b64e4 100644 --- a/structuralcodes/materials/reinforcement/_reinforcementMC2010.py +++ b/structuralcodes/materials/reinforcement/_reinforcementMC2010.py @@ -3,6 +3,7 @@ import typing as t from structuralcodes.codes import mc2010 +from structuralcodes.core._units import UnitSet from ._reinforcement import Reinforcement @@ -10,6 +11,8 @@ class ReinforcementMC2010(Reinforcement): """Reinforcement implementation for MC 2010.""" + _default_units = UnitSet(length='mm', force='N') + def __init__( self, fyk: float, @@ -20,13 +23,14 @@ def __init__( gamma_eps: t.Optional[float] = None, name: t.Optional[str] = None, density: float = 7850.0, + units: t.Optional[UnitSet] = None, ): """Initializes a new instance of Reinforcement for MC2010. Args: - fyk (float): Characteristic yield strength in MPa. - Es (float): The Young's modulus in MPa. - ftk (float): Characteristic ultimate strength in MPa. + fyk (float): Characteristic yield strength. + Es (float): The Young's modulus. + ftk (float): Characteristic ultimate strength. epsuk (float): The characteristik strain at the ultimate stress level. gamma_s (Optional(float)): The partial factor for reinforcement. @@ -35,6 +39,12 @@ def __init__( Keyword Args: name (str): A descriptive name for the reinforcement. density (float): Density of material in kg/m3 (default: 7850). + units (Optional[UnitSet]): The selected set of units to work in. + The default is length=m and force=N. + + Note: + The arguments should be provided compatible with the selected set + of units. """ if name is None: name = f'Reinforcement{round(fyk):d}' @@ -47,11 +57,17 @@ def __init__( ftk=ftk, epsuk=epsuk, gamma_s=gamma_s, + units=units, ) def fyd(self) -> float: """The design yield strength.""" - return mc2010.fyd(self.fyk, self.gamma_s) + return self.unit_converter.convert_stress_forwards( + mc2010.fyd( + self.unit_converter.convert_stress_backwards(self.fyk), + self.gamma_s, + ) + ) @property def gamma_s(self) -> float: @@ -60,7 +76,12 @@ def gamma_s(self) -> float: def ftd(self) -> float: """The design ultimate strength.""" - return mc2010.fyd(self.ftk, self.gamma_s) + return self.unit_converter.convert_stress_forwards( + mc2010.fyd( + self.unit_converter.convert_stress_backwards(self.ftk), + self.gamma_s, + ) + ) def epsud(self) -> float: """The design ultimate strain.""" From 66a81a9231aec265765a7be0a3b82e7b3f7ba591 Mon Sep 17 00:00:00 2001 From: Morten Engen Date: Tue, 26 Nov 2024 16:58:00 +0100 Subject: [PATCH 10/10] Fix typo in default units of concretes and reinforcements --- structuralcodes/materials/concrete/_concreteEC2_2004.py | 2 +- structuralcodes/materials/concrete/_concreteEC2_2023.py | 2 +- structuralcodes/materials/concrete/_concreteMC2010.py | 2 +- .../materials/reinforcement/_reinforcementEC2_2004.py | 2 +- .../materials/reinforcement/_reinforcementEC2_2023.py | 2 +- structuralcodes/materials/reinforcement/_reinforcementMC2010.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/structuralcodes/materials/concrete/_concreteEC2_2004.py b/structuralcodes/materials/concrete/_concreteEC2_2004.py index 1ef550e9..c784fa74 100644 --- a/structuralcodes/materials/concrete/_concreteEC2_2004.py +++ b/structuralcodes/materials/concrete/_concreteEC2_2004.py @@ -53,7 +53,7 @@ def __init__( effects on the strength, and effects that arise from the way the load is applied. units (Optional[UnitSet]): The selected set of units to work in. - The default is length=m and force=N. + The default is length=mm and force=N. """ del kwargs if name is None: diff --git a/structuralcodes/materials/concrete/_concreteEC2_2023.py b/structuralcodes/materials/concrete/_concreteEC2_2023.py index e830f92a..ca325351 100644 --- a/structuralcodes/materials/concrete/_concreteEC2_2023.py +++ b/structuralcodes/materials/concrete/_concreteEC2_2023.py @@ -62,7 +62,7 @@ def __init__( existing (bool, optional): The material is of an existing structure (default: False). units (Optional[UnitSet]): The selected set of units to work in. - The default is length=m and force=N. + The default is length=mm and force=N. Note: The arguments should be provided compatible with the selected set diff --git a/structuralcodes/materials/concrete/_concreteMC2010.py b/structuralcodes/materials/concrete/_concreteMC2010.py index 7d61a1f7..e578f576 100644 --- a/structuralcodes/materials/concrete/_concreteMC2010.py +++ b/structuralcodes/materials/concrete/_concreteMC2010.py @@ -59,7 +59,7 @@ def __init__( effects on the strength, and effects that arise from the way the load is applied. units (Optional[UnitSet]): The selected set of units to work in. - The default is length=m and force=N. + The default is length=mm and force=N. Note: The arguments should be provided compatible with the selected set diff --git a/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py b/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py index 5d962d61..230e6022 100644 --- a/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py +++ b/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py @@ -40,7 +40,7 @@ def __init__( name (str): A descriptive name for the reinforcement. density (float): Density of material in kg/m3 (default: 7850). units (Optional[UnitSet]): The selected set of units to work in. - The default is length=m and force=N. + The default is length=mm and force=N. Note: The arguments should be provided compatible with the selected set diff --git a/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py b/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py index 0fed419f..7c2bac09 100644 --- a/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py +++ b/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py @@ -39,7 +39,7 @@ def __init__( name (str): A descriptive name for the reinforcement. density (float): Density of material in kg/m3 (default: 7850). units (Optional[UnitSet]): The selected set of units to work in. - The default is length=m and force=N. + The default is length=mm and force=N. Note: The arguments should be provided compatible with the selected set diff --git a/structuralcodes/materials/reinforcement/_reinforcementMC2010.py b/structuralcodes/materials/reinforcement/_reinforcementMC2010.py index 544b64e4..92f85759 100644 --- a/structuralcodes/materials/reinforcement/_reinforcementMC2010.py +++ b/structuralcodes/materials/reinforcement/_reinforcementMC2010.py @@ -40,7 +40,7 @@ def __init__( name (str): A descriptive name for the reinforcement. density (float): Density of material in kg/m3 (default: 7850). units (Optional[UnitSet]): The selected set of units to work in. - The default is length=m and force=N. + The default is length=mm and force=N. Note: The arguments should be provided compatible with the selected set